##// END OF EJS Templates
auth-tokens: fixed tests
marcink -
r1482:9278d852 default
parent child Browse files
Show More

The requested changes are too big and content was truncated. Show full diff

@@ -1,42 +1,52 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import pytest
21 import pytest
22
22
23 from rhodecode.model.meta import Session
23 from rhodecode.model.meta import Session
24 from rhodecode.model.user import UserModel
24 from rhodecode.model.user import UserModel
25 from rhodecode.model.auth_token import AuthTokenModel
25 from rhodecode.tests import TEST_USER_ADMIN_LOGIN
26 from rhodecode.tests import TEST_USER_ADMIN_LOGIN
26
27
27
28
28 @pytest.fixture(scope="class")
29 @pytest.fixture(scope="class")
29 def testuser_api(request, pylonsapp):
30 def testuser_api(request, pylonsapp):
30 cls = request.cls
31 cls = request.cls
32
33 # ADMIN USER
31 cls.usr = UserModel().get_by_username(TEST_USER_ADMIN_LOGIN)
34 cls.usr = UserModel().get_by_username(TEST_USER_ADMIN_LOGIN)
32 cls.apikey = cls.usr.api_key
35 cls.apikey = cls.usr.api_key
36
37 # REGULAR USER
33 cls.test_user = UserModel().create_or_update(
38 cls.test_user = UserModel().create_or_update(
34 username='test-api',
39 username='test-api',
35 password='test',
40 password='test',
36 email='test@api.rhodecode.org',
41 email='test@api.rhodecode.org',
37 firstname='first',
42 firstname='first',
38 lastname='last'
43 lastname='last'
39 )
44 )
45 # create TOKEN for user, if he doesn't have one
46 if not cls.test_user.api_key:
47 AuthTokenModel().create(
48 user=cls.test_user, description='TEST_USER_TOKEN')
49
40 Session().commit()
50 Session().commit()
41 cls.TEST_USER_LOGIN = cls.test_user.username
51 cls.TEST_USER_LOGIN = cls.test_user.username
42 cls.apikey_regular = cls.test_user.api_key
52 cls.apikey_regular = cls.test_user.api_key
@@ -1,111 +1,112 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import pytest
21 import pytest
22
22
23 from rhodecode.model.db import UserLog
23 from rhodecode.model.db import UserLog
24 from rhodecode.model.pull_request import PullRequestModel
24 from rhodecode.model.pull_request import PullRequestModel
25 from rhodecode.tests import TEST_USER_ADMIN_LOGIN
25 from rhodecode.tests import TEST_USER_ADMIN_LOGIN
26 from rhodecode.api.tests.utils import (
26 from rhodecode.api.tests.utils import (
27 build_data, api_call, assert_error, assert_ok)
27 build_data, api_call, assert_error, assert_ok)
28
28
29
29
30 @pytest.mark.usefixtures("testuser_api", "app")
30 @pytest.mark.usefixtures("testuser_api", "app")
31 class TestClosePullRequest(object):
31 class TestClosePullRequest(object):
32
32 @pytest.mark.backends("git", "hg")
33 @pytest.mark.backends("git", "hg")
33 def test_api_close_pull_request(self, pr_util):
34 def test_api_close_pull_request(self, pr_util):
34 pull_request = pr_util.create_pull_request()
35 pull_request = pr_util.create_pull_request()
35 pull_request_id = pull_request.pull_request_id
36 pull_request_id = pull_request.pull_request_id
36 author = pull_request.user_id
37 author = pull_request.user_id
37 repo = pull_request.target_repo.repo_id
38 repo = pull_request.target_repo.repo_id
38 id_, params = build_data(
39 id_, params = build_data(
39 self.apikey, 'close_pull_request',
40 self.apikey, 'close_pull_request',
40 repoid=pull_request.target_repo.repo_name,
41 repoid=pull_request.target_repo.repo_name,
41 pullrequestid=pull_request.pull_request_id)
42 pullrequestid=pull_request.pull_request_id)
42 response = api_call(self.app, params)
43 response = api_call(self.app, params)
43 expected = {
44 expected = {
44 'pull_request_id': pull_request_id,
45 'pull_request_id': pull_request_id,
45 'closed': True,
46 'closed': True,
46 }
47 }
47 assert_ok(id_, expected, response.body)
48 assert_ok(id_, expected, response.body)
48 action = 'user_closed_pull_request:%d' % pull_request_id
49 action = 'user_closed_pull_request:%d' % pull_request_id
49 journal = UserLog.query()\
50 journal = UserLog.query()\
50 .filter(UserLog.user_id == author)\
51 .filter(UserLog.user_id == author)\
51 .filter(UserLog.repository_id == repo)\
52 .filter(UserLog.repository_id == repo)\
52 .filter(UserLog.action == action)\
53 .filter(UserLog.action == action)\
53 .all()
54 .all()
54 assert len(journal) == 1
55 assert len(journal) == 1
55
56
56 @pytest.mark.backends("git", "hg")
57 @pytest.mark.backends("git", "hg")
57 def test_api_close_pull_request_already_closed_error(self, pr_util):
58 def test_api_close_pull_request_already_closed_error(self, pr_util):
58 pull_request = pr_util.create_pull_request()
59 pull_request = pr_util.create_pull_request()
59 pull_request_id = pull_request.pull_request_id
60 pull_request_id = pull_request.pull_request_id
60 pull_request_repo = pull_request.target_repo.repo_name
61 pull_request_repo = pull_request.target_repo.repo_name
61 PullRequestModel().close_pull_request(
62 PullRequestModel().close_pull_request(
62 pull_request, pull_request.author)
63 pull_request, pull_request.author)
63 id_, params = build_data(
64 id_, params = build_data(
64 self.apikey, 'close_pull_request',
65 self.apikey, 'close_pull_request',
65 repoid=pull_request_repo, pullrequestid=pull_request_id)
66 repoid=pull_request_repo, pullrequestid=pull_request_id)
66 response = api_call(self.app, params)
67 response = api_call(self.app, params)
67
68
68 expected = 'pull request `%s` is already closed' % pull_request_id
69 expected = 'pull request `%s` is already closed' % pull_request_id
69 assert_error(id_, expected, given=response.body)
70 assert_error(id_, expected, given=response.body)
70
71
71 @pytest.mark.backends("git", "hg")
72 @pytest.mark.backends("git", "hg")
72 def test_api_close_pull_request_repo_error(self):
73 def test_api_close_pull_request_repo_error(self):
73 id_, params = build_data(
74 id_, params = build_data(
74 self.apikey, 'close_pull_request',
75 self.apikey, 'close_pull_request',
75 repoid=666, pullrequestid=1)
76 repoid=666, pullrequestid=1)
76 response = api_call(self.app, params)
77 response = api_call(self.app, params)
77
78
78 expected = 'repository `666` does not exist'
79 expected = 'repository `666` does not exist'
79 assert_error(id_, expected, given=response.body)
80 assert_error(id_, expected, given=response.body)
80
81
81 @pytest.mark.backends("git", "hg")
82 @pytest.mark.backends("git", "hg")
82 def test_api_close_pull_request_non_admin_with_userid_error(self,
83 def test_api_close_pull_request_non_admin_with_userid_error(self,
83 pr_util):
84 pr_util):
84 pull_request = pr_util.create_pull_request()
85 pull_request = pr_util.create_pull_request()
85 id_, params = build_data(
86 id_, params = build_data(
86 self.apikey_regular, 'close_pull_request',
87 self.apikey_regular, 'close_pull_request',
87 repoid=pull_request.target_repo.repo_name,
88 repoid=pull_request.target_repo.repo_name,
88 pullrequestid=pull_request.pull_request_id,
89 pullrequestid=pull_request.pull_request_id,
89 userid=TEST_USER_ADMIN_LOGIN)
90 userid=TEST_USER_ADMIN_LOGIN)
90 response = api_call(self.app, params)
91 response = api_call(self.app, params)
91
92
92 expected = 'userid is not the same as your user'
93 expected = 'userid is not the same as your user'
93 assert_error(id_, expected, given=response.body)
94 assert_error(id_, expected, given=response.body)
94
95
95 @pytest.mark.backends("git", "hg")
96 @pytest.mark.backends("git", "hg")
96 def test_api_close_pull_request_no_perms_to_close(
97 def test_api_close_pull_request_no_perms_to_close(
97 self, user_util, pr_util):
98 self, user_util, pr_util):
98 user = user_util.create_user()
99 user = user_util.create_user()
99 pull_request = pr_util.create_pull_request()
100 pull_request = pr_util.create_pull_request()
100
101
101 id_, params = build_data(
102 id_, params = build_data(
102 user.api_key, 'close_pull_request',
103 user.api_key, 'close_pull_request',
103 repoid=pull_request.target_repo.repo_name,
104 repoid=pull_request.target_repo.repo_name,
104 pullrequestid=pull_request.pull_request_id,)
105 pullrequestid=pull_request.pull_request_id,)
105 response = api_call(self.app, params)
106 response = api_call(self.app, params)
106
107
107 expected = ('pull request `%s` close failed, '
108 expected = ('pull request `%s` close failed, '
108 'no permission to close.') % pull_request.pull_request_id
109 'no permission to close.') % pull_request.pull_request_id
109
110
110 response_json = response.json['error']
111 response_json = response.json['error']
111 assert response_json == expected
112 assert response_json == expected
@@ -1,73 +1,72 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import mock
21 import mock
22 import pytest
22 import pytest
23
23
24 from rhodecode.model.repo import RepoModel
24 from rhodecode.model.repo import RepoModel
25 from rhodecode.api.tests.utils import (
25 from rhodecode.api.tests.utils import (
26 build_data, api_call, assert_error, assert_ok, crash)
26 build_data, api_call, assert_error, assert_ok, crash)
27
27
28
28
29 @pytest.mark.usefixtures("testuser_api", "app")
29 @pytest.mark.usefixtures("testuser_api", "app")
30 class TestApiDeleteRepo(object):
30 class TestApiDeleteRepo(object):
31 def test_api_delete_repo(self, backend):
31 def test_api_delete_repo(self, backend):
32 repo = backend.create_repo()
32 repo = backend.create_repo()
33
33
34 id_, params = build_data(
34 id_, params = build_data(
35 self.apikey, 'delete_repo', repoid=repo.repo_name, )
35 self.apikey, 'delete_repo', repoid=repo.repo_name, )
36 response = api_call(self.app, params)
36 response = api_call(self.app, params)
37
37
38 expected = {
38 expected = {
39 'msg': 'Deleted repository `%s`' % (repo.repo_name,),
39 'msg': 'Deleted repository `%s`' % (repo.repo_name,),
40 'success': True
40 'success': True
41 }
41 }
42 assert_ok(id_, expected, given=response.body)
42 assert_ok(id_, expected, given=response.body)
43
43
44 def test_api_delete_repo_by_non_admin(self, backend, user_regular):
44 def test_api_delete_repo_by_non_admin(self, backend, user_regular):
45 repo = backend.create_repo(cur_user=user_regular.username)
45 repo = backend.create_repo(cur_user=user_regular.username)
46 id_, params = build_data(
46 id_, params = build_data(
47 user_regular.api_key, 'delete_repo', repoid=repo.repo_name, )
47 user_regular.api_key, 'delete_repo', repoid=repo.repo_name, )
48 response = api_call(self.app, params)
48 response = api_call(self.app, params)
49
49
50 expected = {
50 expected = {
51 'msg': 'Deleted repository `%s`' % (repo.repo_name,),
51 'msg': 'Deleted repository `%s`' % (repo.repo_name,),
52 'success': True
52 'success': True
53 }
53 }
54 assert_ok(id_, expected, given=response.body)
54 assert_ok(id_, expected, given=response.body)
55
55
56 def test_api_delete_repo_by_non_admin_no_permission(
56 def test_api_delete_repo_by_non_admin_no_permission(self, backend):
57 self, backend, user_regular):
58 repo = backend.create_repo()
57 repo = backend.create_repo()
59 id_, params = build_data(
58 id_, params = build_data(
60 user_regular.api_key, 'delete_repo', repoid=repo.repo_name, )
59 self.apikey_regular, 'delete_repo', repoid=repo.repo_name, )
61 response = api_call(self.app, params)
60 response = api_call(self.app, params)
62 expected = 'repository `%s` does not exist' % (repo.repo_name)
61 expected = 'repository `%s` does not exist' % (repo.repo_name)
63 assert_error(id_, expected, given=response.body)
62 assert_error(id_, expected, given=response.body)
64
63
65 def test_api_delete_repo_exception_occurred(self, backend):
64 def test_api_delete_repo_exception_occurred(self, backend):
66 repo = backend.create_repo()
65 repo = backend.create_repo()
67 id_, params = build_data(
66 id_, params = build_data(
68 self.apikey, 'delete_repo', repoid=repo.repo_name, )
67 self.apikey, 'delete_repo', repoid=repo.repo_name, )
69 with mock.patch.object(RepoModel, 'delete', crash):
68 with mock.patch.object(RepoModel, 'delete', crash):
70 response = api_call(self.app, params)
69 response = api_call(self.app, params)
71 expected = 'failed to delete repository `%s`' % (
70 expected = 'failed to delete repository `%s`' % (
72 repo.repo_name,)
71 repo.repo_name,)
73 assert_error(id_, expected, given=response.body)
72 assert_error(id_, expected, given=response.body)
@@ -1,472 +1,471 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2011-2017 RhodeCode GmbH
3 # Copyright (C) 2011-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import logging
21 import logging
22
22
23 from rhodecode.api import jsonrpc_method, JSONRPCError, JSONRPCForbidden
23 from rhodecode.api import jsonrpc_method, JSONRPCError, JSONRPCForbidden
24 from rhodecode.api.utils import (
24 from rhodecode.api.utils import (
25 Optional, OAttr, has_superadmin_permission, get_user_or_error, store_update)
25 Optional, OAttr, has_superadmin_permission, get_user_or_error, store_update)
26 from rhodecode.lib.auth import AuthUser, PasswordGenerator
26 from rhodecode.lib.auth import AuthUser, PasswordGenerator
27 from rhodecode.lib.exceptions import DefaultUserException
27 from rhodecode.lib.exceptions import DefaultUserException
28 from rhodecode.lib.utils2 import safe_int, str2bool
28 from rhodecode.lib.utils2 import safe_int, str2bool
29 from rhodecode.model.db import Session, User, Repository
29 from rhodecode.model.db import Session, User, Repository
30 from rhodecode.model.user import UserModel
30 from rhodecode.model.user import UserModel
31
31
32
32
33 log = logging.getLogger(__name__)
33 log = logging.getLogger(__name__)
34
34
35
35
36 @jsonrpc_method()
36 @jsonrpc_method()
37 def get_user(request, apiuser, userid=Optional(OAttr('apiuser'))):
37 def get_user(request, apiuser, userid=Optional(OAttr('apiuser'))):
38 """
38 """
39 Returns the information associated with a username or userid.
39 Returns the information associated with a username or userid.
40
40
41 * If the ``userid`` is not set, this command returns the information
41 * If the ``userid`` is not set, this command returns the information
42 for the ``userid`` calling the method.
42 for the ``userid`` calling the method.
43
43
44 .. note::
44 .. note::
45
45
46 Normal users may only run this command against their ``userid``. For
46 Normal users may only run this command against their ``userid``. For
47 full privileges you must run this command using an |authtoken| with
47 full privileges you must run this command using an |authtoken| with
48 admin rights.
48 admin rights.
49
49
50 :param apiuser: This is filled automatically from the |authtoken|.
50 :param apiuser: This is filled automatically from the |authtoken|.
51 :type apiuser: AuthUser
51 :type apiuser: AuthUser
52 :param userid: Sets the userid for which data will be returned.
52 :param userid: Sets the userid for which data will be returned.
53 :type userid: Optional(str or int)
53 :type userid: Optional(str or int)
54
54
55 Example output:
55 Example output:
56
56
57 .. code-block:: bash
57 .. code-block:: bash
58
58
59 {
59 {
60 "error": null,
60 "error": null,
61 "id": <id>,
61 "id": <id>,
62 "result": {
62 "result": {
63 "active": true,
63 "active": true,
64 "admin": false,
64 "admin": false,
65 "api_key": "api-key",
66 "api_keys": [ list of keys ],
65 "api_keys": [ list of keys ],
67 "email": "user@example.com",
66 "email": "user@example.com",
68 "emails": [
67 "emails": [
69 "user@example.com"
68 "user@example.com"
70 ],
69 ],
71 "extern_name": "rhodecode",
70 "extern_name": "rhodecode",
72 "extern_type": "rhodecode",
71 "extern_type": "rhodecode",
73 "firstname": "username",
72 "firstname": "username",
74 "ip_addresses": [],
73 "ip_addresses": [],
75 "language": null,
74 "language": null,
76 "last_login": "Timestamp",
75 "last_login": "Timestamp",
77 "lastname": "surnae",
76 "lastname": "surnae",
78 "permissions": {
77 "permissions": {
79 "global": [
78 "global": [
80 "hg.inherit_default_perms.true",
79 "hg.inherit_default_perms.true",
81 "usergroup.read",
80 "usergroup.read",
82 "hg.repogroup.create.false",
81 "hg.repogroup.create.false",
83 "hg.create.none",
82 "hg.create.none",
84 "hg.password_reset.enabled",
83 "hg.password_reset.enabled",
85 "hg.extern_activate.manual",
84 "hg.extern_activate.manual",
86 "hg.create.write_on_repogroup.false",
85 "hg.create.write_on_repogroup.false",
87 "hg.usergroup.create.false",
86 "hg.usergroup.create.false",
88 "group.none",
87 "group.none",
89 "repository.none",
88 "repository.none",
90 "hg.register.none",
89 "hg.register.none",
91 "hg.fork.repository"
90 "hg.fork.repository"
92 ],
91 ],
93 "repositories": { "username/example": "repository.write"},
92 "repositories": { "username/example": "repository.write"},
94 "repositories_groups": { "user-group/repo": "group.none" },
93 "repositories_groups": { "user-group/repo": "group.none" },
95 "user_groups": { "user_group_name": "usergroup.read" }
94 "user_groups": { "user_group_name": "usergroup.read" }
96 },
95 },
97 "user_id": 32,
96 "user_id": 32,
98 "username": "username"
97 "username": "username"
99 }
98 }
100 }
99 }
101 """
100 """
102
101
103 if not has_superadmin_permission(apiuser):
102 if not has_superadmin_permission(apiuser):
104 # make sure normal user does not pass someone else userid,
103 # make sure normal user does not pass someone else userid,
105 # he is not allowed to do that
104 # he is not allowed to do that
106 if not isinstance(userid, Optional) and userid != apiuser.user_id:
105 if not isinstance(userid, Optional) and userid != apiuser.user_id:
107 raise JSONRPCError('userid is not the same as your user')
106 raise JSONRPCError('userid is not the same as your user')
108
107
109 userid = Optional.extract(userid, evaluate_locals=locals())
108 userid = Optional.extract(userid, evaluate_locals=locals())
110 userid = getattr(userid, 'user_id', userid)
109 userid = getattr(userid, 'user_id', userid)
111
110
112 user = get_user_or_error(userid)
111 user = get_user_or_error(userid)
113 data = user.get_api_data(include_secrets=True)
112 data = user.get_api_data(include_secrets=True)
114 data['permissions'] = AuthUser(user_id=user.user_id).permissions
113 data['permissions'] = AuthUser(user_id=user.user_id).permissions
115 return data
114 return data
116
115
117
116
118 @jsonrpc_method()
117 @jsonrpc_method()
119 def get_users(request, apiuser):
118 def get_users(request, apiuser):
120 """
119 """
121 Lists all users in the |RCE| user database.
120 Lists all users in the |RCE| user database.
122
121
123 This command can only be run using an |authtoken| with admin rights to
122 This command can only be run using an |authtoken| with admin rights to
124 the specified repository.
123 the specified repository.
125
124
126 This command takes the following options:
125 This command takes the following options:
127
126
128 :param apiuser: This is filled automatically from the |authtoken|.
127 :param apiuser: This is filled automatically from the |authtoken|.
129 :type apiuser: AuthUser
128 :type apiuser: AuthUser
130
129
131 Example output:
130 Example output:
132
131
133 .. code-block:: bash
132 .. code-block:: bash
134
133
135 id : <id_given_in_input>
134 id : <id_given_in_input>
136 result: [<user_object>, ...]
135 result: [<user_object>, ...]
137 error: null
136 error: null
138 """
137 """
139
138
140 if not has_superadmin_permission(apiuser):
139 if not has_superadmin_permission(apiuser):
141 raise JSONRPCForbidden()
140 raise JSONRPCForbidden()
142
141
143 result = []
142 result = []
144 users_list = User.query().order_by(User.username) \
143 users_list = User.query().order_by(User.username) \
145 .filter(User.username != User.DEFAULT_USER) \
144 .filter(User.username != User.DEFAULT_USER) \
146 .all()
145 .all()
147 for user in users_list:
146 for user in users_list:
148 result.append(user.get_api_data(include_secrets=True))
147 result.append(user.get_api_data(include_secrets=True))
149 return result
148 return result
150
149
151
150
152 @jsonrpc_method()
151 @jsonrpc_method()
153 def create_user(request, apiuser, username, email, password=Optional(''),
152 def create_user(request, apiuser, username, email, password=Optional(''),
154 firstname=Optional(''), lastname=Optional(''),
153 firstname=Optional(''), lastname=Optional(''),
155 active=Optional(True), admin=Optional(False),
154 active=Optional(True), admin=Optional(False),
156 extern_name=Optional('rhodecode'),
155 extern_name=Optional('rhodecode'),
157 extern_type=Optional('rhodecode'),
156 extern_type=Optional('rhodecode'),
158 force_password_change=Optional(False),
157 force_password_change=Optional(False),
159 create_personal_repo_group=Optional(None)):
158 create_personal_repo_group=Optional(None)):
160 """
159 """
161 Creates a new user and returns the new user object.
160 Creates a new user and returns the new user object.
162
161
163 This command can only be run using an |authtoken| with admin rights to
162 This command can only be run using an |authtoken| with admin rights to
164 the specified repository.
163 the specified repository.
165
164
166 This command takes the following options:
165 This command takes the following options:
167
166
168 :param apiuser: This is filled automatically from the |authtoken|.
167 :param apiuser: This is filled automatically from the |authtoken|.
169 :type apiuser: AuthUser
168 :type apiuser: AuthUser
170 :param username: Set the new username.
169 :param username: Set the new username.
171 :type username: str or int
170 :type username: str or int
172 :param email: Set the user email address.
171 :param email: Set the user email address.
173 :type email: str
172 :type email: str
174 :param password: Set the new user password.
173 :param password: Set the new user password.
175 :type password: Optional(str)
174 :type password: Optional(str)
176 :param firstname: Set the new user firstname.
175 :param firstname: Set the new user firstname.
177 :type firstname: Optional(str)
176 :type firstname: Optional(str)
178 :param lastname: Set the new user surname.
177 :param lastname: Set the new user surname.
179 :type lastname: Optional(str)
178 :type lastname: Optional(str)
180 :param active: Set the user as active.
179 :param active: Set the user as active.
181 :type active: Optional(``True`` | ``False``)
180 :type active: Optional(``True`` | ``False``)
182 :param admin: Give the new user admin rights.
181 :param admin: Give the new user admin rights.
183 :type admin: Optional(``True`` | ``False``)
182 :type admin: Optional(``True`` | ``False``)
184 :param extern_name: Set the authentication plugin name.
183 :param extern_name: Set the authentication plugin name.
185 Using LDAP this is filled with LDAP UID.
184 Using LDAP this is filled with LDAP UID.
186 :type extern_name: Optional(str)
185 :type extern_name: Optional(str)
187 :param extern_type: Set the new user authentication plugin.
186 :param extern_type: Set the new user authentication plugin.
188 :type extern_type: Optional(str)
187 :type extern_type: Optional(str)
189 :param force_password_change: Force the new user to change password
188 :param force_password_change: Force the new user to change password
190 on next login.
189 on next login.
191 :type force_password_change: Optional(``True`` | ``False``)
190 :type force_password_change: Optional(``True`` | ``False``)
192 :param create_personal_repo_group: Create personal repo group for this user
191 :param create_personal_repo_group: Create personal repo group for this user
193 :type create_personal_repo_group: Optional(``True`` | ``False``)
192 :type create_personal_repo_group: Optional(``True`` | ``False``)
194 Example output:
193 Example output:
195
194
196 .. code-block:: bash
195 .. code-block:: bash
197
196
198 id : <id_given_in_input>
197 id : <id_given_in_input>
199 result: {
198 result: {
200 "msg" : "created new user `<username>`",
199 "msg" : "created new user `<username>`",
201 "user": <user_obj>
200 "user": <user_obj>
202 }
201 }
203 error: null
202 error: null
204
203
205 Example error output:
204 Example error output:
206
205
207 .. code-block:: bash
206 .. code-block:: bash
208
207
209 id : <id_given_in_input>
208 id : <id_given_in_input>
210 result : null
209 result : null
211 error : {
210 error : {
212 "user `<username>` already exist"
211 "user `<username>` already exist"
213 or
212 or
214 "email `<email>` already exist"
213 "email `<email>` already exist"
215 or
214 or
216 "failed to create user `<username>`"
215 "failed to create user `<username>`"
217 }
216 }
218
217
219 """
218 """
220 if not has_superadmin_permission(apiuser):
219 if not has_superadmin_permission(apiuser):
221 raise JSONRPCForbidden()
220 raise JSONRPCForbidden()
222
221
223 if UserModel().get_by_username(username):
222 if UserModel().get_by_username(username):
224 raise JSONRPCError("user `%s` already exist" % (username,))
223 raise JSONRPCError("user `%s` already exist" % (username,))
225
224
226 if UserModel().get_by_email(email, case_insensitive=True):
225 if UserModel().get_by_email(email, case_insensitive=True):
227 raise JSONRPCError("email `%s` already exist" % (email,))
226 raise JSONRPCError("email `%s` already exist" % (email,))
228
227
229 # generate random password if we actually given the
228 # generate random password if we actually given the
230 # extern_name and it's not rhodecode
229 # extern_name and it's not rhodecode
231 if (not isinstance(extern_name, Optional) and
230 if (not isinstance(extern_name, Optional) and
232 Optional.extract(extern_name) != 'rhodecode'):
231 Optional.extract(extern_name) != 'rhodecode'):
233 # generate temporary password if user is external
232 # generate temporary password if user is external
234 password = PasswordGenerator().gen_password(length=16)
233 password = PasswordGenerator().gen_password(length=16)
235 create_repo_group = Optional.extract(create_personal_repo_group)
234 create_repo_group = Optional.extract(create_personal_repo_group)
236 if isinstance(create_repo_group, basestring):
235 if isinstance(create_repo_group, basestring):
237 create_repo_group = str2bool(create_repo_group)
236 create_repo_group = str2bool(create_repo_group)
238
237
239 try:
238 try:
240 user = UserModel().create_or_update(
239 user = UserModel().create_or_update(
241 username=Optional.extract(username),
240 username=Optional.extract(username),
242 password=Optional.extract(password),
241 password=Optional.extract(password),
243 email=Optional.extract(email),
242 email=Optional.extract(email),
244 firstname=Optional.extract(firstname),
243 firstname=Optional.extract(firstname),
245 lastname=Optional.extract(lastname),
244 lastname=Optional.extract(lastname),
246 active=Optional.extract(active),
245 active=Optional.extract(active),
247 admin=Optional.extract(admin),
246 admin=Optional.extract(admin),
248 extern_type=Optional.extract(extern_type),
247 extern_type=Optional.extract(extern_type),
249 extern_name=Optional.extract(extern_name),
248 extern_name=Optional.extract(extern_name),
250 force_password_change=Optional.extract(force_password_change),
249 force_password_change=Optional.extract(force_password_change),
251 create_repo_group=create_repo_group
250 create_repo_group=create_repo_group
252 )
251 )
253 Session().commit()
252 Session().commit()
254 return {
253 return {
255 'msg': 'created new user `%s`' % username,
254 'msg': 'created new user `%s`' % username,
256 'user': user.get_api_data(include_secrets=True)
255 'user': user.get_api_data(include_secrets=True)
257 }
256 }
258 except Exception:
257 except Exception:
259 log.exception('Error occurred during creation of user')
258 log.exception('Error occurred during creation of user')
260 raise JSONRPCError('failed to create user `%s`' % (username,))
259 raise JSONRPCError('failed to create user `%s`' % (username,))
261
260
262
261
263 @jsonrpc_method()
262 @jsonrpc_method()
264 def update_user(request, apiuser, userid, username=Optional(None),
263 def update_user(request, apiuser, userid, username=Optional(None),
265 email=Optional(None), password=Optional(None),
264 email=Optional(None), password=Optional(None),
266 firstname=Optional(None), lastname=Optional(None),
265 firstname=Optional(None), lastname=Optional(None),
267 active=Optional(None), admin=Optional(None),
266 active=Optional(None), admin=Optional(None),
268 extern_type=Optional(None), extern_name=Optional(None), ):
267 extern_type=Optional(None), extern_name=Optional(None), ):
269 """
268 """
270 Updates the details for the specified user, if that user exists.
269 Updates the details for the specified user, if that user exists.
271
270
272 This command can only be run using an |authtoken| with admin rights to
271 This command can only be run using an |authtoken| with admin rights to
273 the specified repository.
272 the specified repository.
274
273
275 This command takes the following options:
274 This command takes the following options:
276
275
277 :param apiuser: This is filled automatically from |authtoken|.
276 :param apiuser: This is filled automatically from |authtoken|.
278 :type apiuser: AuthUser
277 :type apiuser: AuthUser
279 :param userid: Set the ``userid`` to update.
278 :param userid: Set the ``userid`` to update.
280 :type userid: str or int
279 :type userid: str or int
281 :param username: Set the new username.
280 :param username: Set the new username.
282 :type username: str or int
281 :type username: str or int
283 :param email: Set the new email.
282 :param email: Set the new email.
284 :type email: str
283 :type email: str
285 :param password: Set the new password.
284 :param password: Set the new password.
286 :type password: Optional(str)
285 :type password: Optional(str)
287 :param firstname: Set the new first name.
286 :param firstname: Set the new first name.
288 :type firstname: Optional(str)
287 :type firstname: Optional(str)
289 :param lastname: Set the new surname.
288 :param lastname: Set the new surname.
290 :type lastname: Optional(str)
289 :type lastname: Optional(str)
291 :param active: Set the new user as active.
290 :param active: Set the new user as active.
292 :type active: Optional(``True`` | ``False``)
291 :type active: Optional(``True`` | ``False``)
293 :param admin: Give the user admin rights.
292 :param admin: Give the user admin rights.
294 :type admin: Optional(``True`` | ``False``)
293 :type admin: Optional(``True`` | ``False``)
295 :param extern_name: Set the authentication plugin user name.
294 :param extern_name: Set the authentication plugin user name.
296 Using LDAP this is filled with LDAP UID.
295 Using LDAP this is filled with LDAP UID.
297 :type extern_name: Optional(str)
296 :type extern_name: Optional(str)
298 :param extern_type: Set the authentication plugin type.
297 :param extern_type: Set the authentication plugin type.
299 :type extern_type: Optional(str)
298 :type extern_type: Optional(str)
300
299
301
300
302 Example output:
301 Example output:
303
302
304 .. code-block:: bash
303 .. code-block:: bash
305
304
306 id : <id_given_in_input>
305 id : <id_given_in_input>
307 result: {
306 result: {
308 "msg" : "updated user ID:<userid> <username>",
307 "msg" : "updated user ID:<userid> <username>",
309 "user": <user_object>,
308 "user": <user_object>,
310 }
309 }
311 error: null
310 error: null
312
311
313 Example error output:
312 Example error output:
314
313
315 .. code-block:: bash
314 .. code-block:: bash
316
315
317 id : <id_given_in_input>
316 id : <id_given_in_input>
318 result : null
317 result : null
319 error : {
318 error : {
320 "failed to update user `<username>`"
319 "failed to update user `<username>`"
321 }
320 }
322
321
323 """
322 """
324 if not has_superadmin_permission(apiuser):
323 if not has_superadmin_permission(apiuser):
325 raise JSONRPCForbidden()
324 raise JSONRPCForbidden()
326
325
327 user = get_user_or_error(userid)
326 user = get_user_or_error(userid)
328
327
329 # only non optional arguments will be stored in updates
328 # only non optional arguments will be stored in updates
330 updates = {}
329 updates = {}
331
330
332 try:
331 try:
333
332
334 store_update(updates, username, 'username')
333 store_update(updates, username, 'username')
335 store_update(updates, password, 'password')
334 store_update(updates, password, 'password')
336 store_update(updates, email, 'email')
335 store_update(updates, email, 'email')
337 store_update(updates, firstname, 'name')
336 store_update(updates, firstname, 'name')
338 store_update(updates, lastname, 'lastname')
337 store_update(updates, lastname, 'lastname')
339 store_update(updates, active, 'active')
338 store_update(updates, active, 'active')
340 store_update(updates, admin, 'admin')
339 store_update(updates, admin, 'admin')
341 store_update(updates, extern_name, 'extern_name')
340 store_update(updates, extern_name, 'extern_name')
342 store_update(updates, extern_type, 'extern_type')
341 store_update(updates, extern_type, 'extern_type')
343
342
344 user = UserModel().update_user(user, **updates)
343 user = UserModel().update_user(user, **updates)
345 Session().commit()
344 Session().commit()
346 return {
345 return {
347 'msg': 'updated user ID:%s %s' % (user.user_id, user.username),
346 'msg': 'updated user ID:%s %s' % (user.user_id, user.username),
348 'user': user.get_api_data(include_secrets=True)
347 'user': user.get_api_data(include_secrets=True)
349 }
348 }
350 except DefaultUserException:
349 except DefaultUserException:
351 log.exception("Default user edit exception")
350 log.exception("Default user edit exception")
352 raise JSONRPCError('editing default user is forbidden')
351 raise JSONRPCError('editing default user is forbidden')
353 except Exception:
352 except Exception:
354 log.exception("Error occurred during update of user")
353 log.exception("Error occurred during update of user")
355 raise JSONRPCError('failed to update user `%s`' % (userid,))
354 raise JSONRPCError('failed to update user `%s`' % (userid,))
356
355
357
356
358 @jsonrpc_method()
357 @jsonrpc_method()
359 def delete_user(request, apiuser, userid):
358 def delete_user(request, apiuser, userid):
360 """
359 """
361 Deletes the specified user from the |RCE| user database.
360 Deletes the specified user from the |RCE| user database.
362
361
363 This command can only be run using an |authtoken| with admin rights to
362 This command can only be run using an |authtoken| with admin rights to
364 the specified repository.
363 the specified repository.
365
364
366 .. important::
365 .. important::
367
366
368 Ensure all open pull requests and open code review
367 Ensure all open pull requests and open code review
369 requests to this user are close.
368 requests to this user are close.
370
369
371 Also ensure all repositories, or repository groups owned by this
370 Also ensure all repositories, or repository groups owned by this
372 user are reassigned before deletion.
371 user are reassigned before deletion.
373
372
374 This command takes the following options:
373 This command takes the following options:
375
374
376 :param apiuser: This is filled automatically from the |authtoken|.
375 :param apiuser: This is filled automatically from the |authtoken|.
377 :type apiuser: AuthUser
376 :type apiuser: AuthUser
378 :param userid: Set the user to delete.
377 :param userid: Set the user to delete.
379 :type userid: str or int
378 :type userid: str or int
380
379
381 Example output:
380 Example output:
382
381
383 .. code-block:: bash
382 .. code-block:: bash
384
383
385 id : <id_given_in_input>
384 id : <id_given_in_input>
386 result: {
385 result: {
387 "msg" : "deleted user ID:<userid> <username>",
386 "msg" : "deleted user ID:<userid> <username>",
388 "user": null
387 "user": null
389 }
388 }
390 error: null
389 error: null
391
390
392 Example error output:
391 Example error output:
393
392
394 .. code-block:: bash
393 .. code-block:: bash
395
394
396 id : <id_given_in_input>
395 id : <id_given_in_input>
397 result : null
396 result : null
398 error : {
397 error : {
399 "failed to delete user ID:<userid> <username>"
398 "failed to delete user ID:<userid> <username>"
400 }
399 }
401
400
402 """
401 """
403 if not has_superadmin_permission(apiuser):
402 if not has_superadmin_permission(apiuser):
404 raise JSONRPCForbidden()
403 raise JSONRPCForbidden()
405
404
406 user = get_user_or_error(userid)
405 user = get_user_or_error(userid)
407
406
408 try:
407 try:
409 UserModel().delete(userid)
408 UserModel().delete(userid)
410 Session().commit()
409 Session().commit()
411 return {
410 return {
412 'msg': 'deleted user ID:%s %s' % (user.user_id, user.username),
411 'msg': 'deleted user ID:%s %s' % (user.user_id, user.username),
413 'user': None
412 'user': None
414 }
413 }
415 except Exception:
414 except Exception:
416 log.exception("Error occurred during deleting of user")
415 log.exception("Error occurred during deleting of user")
417 raise JSONRPCError(
416 raise JSONRPCError(
418 'failed to delete user ID:%s %s' % (user.user_id, user.username))
417 'failed to delete user ID:%s %s' % (user.user_id, user.username))
419
418
420
419
421 @jsonrpc_method()
420 @jsonrpc_method()
422 def get_user_locks(request, apiuser, userid=Optional(OAttr('apiuser'))):
421 def get_user_locks(request, apiuser, userid=Optional(OAttr('apiuser'))):
423 """
422 """
424 Displays all repositories locked by the specified user.
423 Displays all repositories locked by the specified user.
425
424
426 * If this command is run by a non-admin user, it returns
425 * If this command is run by a non-admin user, it returns
427 a list of |repos| locked by that user.
426 a list of |repos| locked by that user.
428
427
429 This command takes the following options:
428 This command takes the following options:
430
429
431 :param apiuser: This is filled automatically from the |authtoken|.
430 :param apiuser: This is filled automatically from the |authtoken|.
432 :type apiuser: AuthUser
431 :type apiuser: AuthUser
433 :param userid: Sets the userid whose list of locked |repos| will be
432 :param userid: Sets the userid whose list of locked |repos| will be
434 displayed.
433 displayed.
435 :type userid: Optional(str or int)
434 :type userid: Optional(str or int)
436
435
437 Example output:
436 Example output:
438
437
439 .. code-block:: bash
438 .. code-block:: bash
440
439
441 id : <id_given_in_input>
440 id : <id_given_in_input>
442 result : {
441 result : {
443 [repo_object, repo_object,...]
442 [repo_object, repo_object,...]
444 }
443 }
445 error : null
444 error : null
446 """
445 """
447
446
448 include_secrets = False
447 include_secrets = False
449 if not has_superadmin_permission(apiuser):
448 if not has_superadmin_permission(apiuser):
450 # make sure normal user does not pass someone else userid,
449 # make sure normal user does not pass someone else userid,
451 # he is not allowed to do that
450 # he is not allowed to do that
452 if not isinstance(userid, Optional) and userid != apiuser.user_id:
451 if not isinstance(userid, Optional) and userid != apiuser.user_id:
453 raise JSONRPCError('userid is not the same as your user')
452 raise JSONRPCError('userid is not the same as your user')
454 else:
453 else:
455 include_secrets = True
454 include_secrets = True
456
455
457 userid = Optional.extract(userid, evaluate_locals=locals())
456 userid = Optional.extract(userid, evaluate_locals=locals())
458 userid = getattr(userid, 'user_id', userid)
457 userid = getattr(userid, 'user_id', userid)
459 user = get_user_or_error(userid)
458 user = get_user_or_error(userid)
460
459
461 ret = []
460 ret = []
462
461
463 # show all locks
462 # show all locks
464 for r in Repository.getAll():
463 for r in Repository.getAll():
465 _user_id, _time, _reason = r.locked
464 _user_id, _time, _reason = r.locked
466 if _user_id and _time:
465 if _user_id and _time:
467 _api_data = r.get_api_data(include_secrets=include_secrets)
466 _api_data = r.get_api_data(include_secrets=include_secrets)
468 # if we use user filter just show the locks for this user
467 # if we use user filter just show the locks for this user
469 if safe_int(_user_id) == user.user_id:
468 if safe_int(_user_id) == user.user_id:
470 ret.append(_api_data)
469 ret.append(_api_data)
471
470
472 return ret
471 return ret
@@ -1,598 +1,600 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 """
21 """
22 Database creation, and setup module for RhodeCode Enterprise. Used for creation
22 Database creation, and setup module for RhodeCode Enterprise. Used for creation
23 of database as well as for migration operations
23 of database as well as for migration operations
24 """
24 """
25
25
26 import os
26 import os
27 import sys
27 import sys
28 import time
28 import time
29 import uuid
29 import uuid
30 import logging
30 import logging
31 import getpass
31 import getpass
32 from os.path import dirname as dn, join as jn
32 from os.path import dirname as dn, join as jn
33
33
34 from sqlalchemy.engine import create_engine
34 from sqlalchemy.engine import create_engine
35
35
36 from rhodecode import __dbversion__
36 from rhodecode import __dbversion__
37 from rhodecode.model import init_model
37 from rhodecode.model import init_model
38 from rhodecode.model.user import UserModel
38 from rhodecode.model.user import UserModel
39 from rhodecode.model.db import (
39 from rhodecode.model.db import (
40 User, Permission, RhodeCodeUi, RhodeCodeSetting, UserToPerm,
40 User, Permission, RhodeCodeUi, RhodeCodeSetting, UserToPerm,
41 DbMigrateVersion, RepoGroup, UserRepoGroupToPerm, CacheKey, Repository)
41 DbMigrateVersion, RepoGroup, UserRepoGroupToPerm, CacheKey, Repository)
42 from rhodecode.model.meta import Session, Base
42 from rhodecode.model.meta import Session, Base
43 from rhodecode.model.permission import PermissionModel
43 from rhodecode.model.permission import PermissionModel
44 from rhodecode.model.repo import RepoModel
44 from rhodecode.model.repo import RepoModel
45 from rhodecode.model.repo_group import RepoGroupModel
45 from rhodecode.model.repo_group import RepoGroupModel
46 from rhodecode.model.settings import SettingsModel
46 from rhodecode.model.settings import SettingsModel
47
47
48
48
49 log = logging.getLogger(__name__)
49 log = logging.getLogger(__name__)
50
50
51
51
52 def notify(msg):
52 def notify(msg):
53 """
53 """
54 Notification for migrations messages
54 Notification for migrations messages
55 """
55 """
56 ml = len(msg) + (4 * 2)
56 ml = len(msg) + (4 * 2)
57 print('\n%s\n*** %s ***\n%s' % ('*' * ml, msg, '*' * ml)).upper()
57 print('\n%s\n*** %s ***\n%s' % ('*' * ml, msg, '*' * ml)).upper()
58
58
59
59
60 class DbManage(object):
60 class DbManage(object):
61
61
62 def __init__(self, log_sql, dbconf, root, tests=False,
62 def __init__(self, log_sql, dbconf, root, tests=False,
63 SESSION=None, cli_args={}):
63 SESSION=None, cli_args={}):
64 self.dbname = dbconf.split('/')[-1]
64 self.dbname = dbconf.split('/')[-1]
65 self.tests = tests
65 self.tests = tests
66 self.root = root
66 self.root = root
67 self.dburi = dbconf
67 self.dburi = dbconf
68 self.log_sql = log_sql
68 self.log_sql = log_sql
69 self.db_exists = False
69 self.db_exists = False
70 self.cli_args = cli_args
70 self.cli_args = cli_args
71 self.init_db(SESSION=SESSION)
71 self.init_db(SESSION=SESSION)
72 self.ask_ok = self.get_ask_ok_func(self.cli_args.get('force_ask'))
72 self.ask_ok = self.get_ask_ok_func(self.cli_args.get('force_ask'))
73
73
74 def get_ask_ok_func(self, param):
74 def get_ask_ok_func(self, param):
75 if param not in [None]:
75 if param not in [None]:
76 # return a function lambda that has a default set to param
76 # return a function lambda that has a default set to param
77 return lambda *args, **kwargs: param
77 return lambda *args, **kwargs: param
78 else:
78 else:
79 from rhodecode.lib.utils import ask_ok
79 from rhodecode.lib.utils import ask_ok
80 return ask_ok
80 return ask_ok
81
81
82 def init_db(self, SESSION=None):
82 def init_db(self, SESSION=None):
83 if SESSION:
83 if SESSION:
84 self.sa = SESSION
84 self.sa = SESSION
85 else:
85 else:
86 # init new sessions
86 # init new sessions
87 engine = create_engine(self.dburi, echo=self.log_sql)
87 engine = create_engine(self.dburi, echo=self.log_sql)
88 init_model(engine)
88 init_model(engine)
89 self.sa = Session()
89 self.sa = Session()
90
90
91 def create_tables(self, override=False):
91 def create_tables(self, override=False):
92 """
92 """
93 Create a auth database
93 Create a auth database
94 """
94 """
95
95
96 log.info("Existing database with the same name is going to be destroyed.")
96 log.info("Existing database with the same name is going to be destroyed.")
97 log.info("Setup command will run DROP ALL command on that database.")
97 log.info("Setup command will run DROP ALL command on that database.")
98 if self.tests:
98 if self.tests:
99 destroy = True
99 destroy = True
100 else:
100 else:
101 destroy = self.ask_ok('Are you sure that you want to destroy the old database? [y/n]')
101 destroy = self.ask_ok('Are you sure that you want to destroy the old database? [y/n]')
102 if not destroy:
102 if not destroy:
103 log.info('Nothing done.')
103 log.info('Nothing done.')
104 sys.exit(0)
104 sys.exit(0)
105 if destroy:
105 if destroy:
106 Base.metadata.drop_all()
106 Base.metadata.drop_all()
107
107
108 checkfirst = not override
108 checkfirst = not override
109 Base.metadata.create_all(checkfirst=checkfirst)
109 Base.metadata.create_all(checkfirst=checkfirst)
110 log.info('Created tables for %s' % self.dbname)
110 log.info('Created tables for %s' % self.dbname)
111
111
112 def set_db_version(self):
112 def set_db_version(self):
113 ver = DbMigrateVersion()
113 ver = DbMigrateVersion()
114 ver.version = __dbversion__
114 ver.version = __dbversion__
115 ver.repository_id = 'rhodecode_db_migrations'
115 ver.repository_id = 'rhodecode_db_migrations'
116 ver.repository_path = 'versions'
116 ver.repository_path = 'versions'
117 self.sa.add(ver)
117 self.sa.add(ver)
118 log.info('db version set to: %s' % __dbversion__)
118 log.info('db version set to: %s' % __dbversion__)
119
119
120 def run_pre_migration_tasks(self):
120 def run_pre_migration_tasks(self):
121 """
121 """
122 Run various tasks before actually doing migrations
122 Run various tasks before actually doing migrations
123 """
123 """
124 # delete cache keys on each upgrade
124 # delete cache keys on each upgrade
125 total = CacheKey.query().count()
125 total = CacheKey.query().count()
126 log.info("Deleting (%s) cache keys now...", total)
126 log.info("Deleting (%s) cache keys now...", total)
127 CacheKey.delete_all_cache()
127 CacheKey.delete_all_cache()
128
128
129 def upgrade(self):
129 def upgrade(self):
130 """
130 """
131 Upgrades given database schema to given revision following
131 Upgrades given database schema to given revision following
132 all needed steps, to perform the upgrade
132 all needed steps, to perform the upgrade
133
133
134 """
134 """
135
135
136 from rhodecode.lib.dbmigrate.migrate.versioning import api
136 from rhodecode.lib.dbmigrate.migrate.versioning import api
137 from rhodecode.lib.dbmigrate.migrate.exceptions import \
137 from rhodecode.lib.dbmigrate.migrate.exceptions import \
138 DatabaseNotControlledError
138 DatabaseNotControlledError
139
139
140 if 'sqlite' in self.dburi:
140 if 'sqlite' in self.dburi:
141 print (
141 print (
142 '********************** WARNING **********************\n'
142 '********************** WARNING **********************\n'
143 'Make sure your version of sqlite is at least 3.7.X. \n'
143 'Make sure your version of sqlite is at least 3.7.X. \n'
144 'Earlier versions are known to fail on some migrations\n'
144 'Earlier versions are known to fail on some migrations\n'
145 '*****************************************************\n')
145 '*****************************************************\n')
146
146
147 upgrade = self.ask_ok(
147 upgrade = self.ask_ok(
148 'You are about to perform a database upgrade. Make '
148 'You are about to perform a database upgrade. Make '
149 'sure you have backed up your database. '
149 'sure you have backed up your database. '
150 'Continue ? [y/n]')
150 'Continue ? [y/n]')
151 if not upgrade:
151 if not upgrade:
152 log.info('No upgrade performed')
152 log.info('No upgrade performed')
153 sys.exit(0)
153 sys.exit(0)
154
154
155 repository_path = jn(dn(dn(dn(os.path.realpath(__file__)))),
155 repository_path = jn(dn(dn(dn(os.path.realpath(__file__)))),
156 'rhodecode/lib/dbmigrate')
156 'rhodecode/lib/dbmigrate')
157 db_uri = self.dburi
157 db_uri = self.dburi
158
158
159 try:
159 try:
160 curr_version = api.db_version(db_uri, repository_path)
160 curr_version = api.db_version(db_uri, repository_path)
161 msg = ('Found current database under version '
161 msg = ('Found current database under version '
162 'control with version %s' % curr_version)
162 'control with version %s' % curr_version)
163
163
164 except (RuntimeError, DatabaseNotControlledError):
164 except (RuntimeError, DatabaseNotControlledError):
165 curr_version = 1
165 curr_version = 1
166 msg = ('Current database is not under version control. Setting '
166 msg = ('Current database is not under version control. Setting '
167 'as version %s' % curr_version)
167 'as version %s' % curr_version)
168 api.version_control(db_uri, repository_path, curr_version)
168 api.version_control(db_uri, repository_path, curr_version)
169
169
170 notify(msg)
170 notify(msg)
171
171
172 self.run_pre_migration_tasks()
172 self.run_pre_migration_tasks()
173
173
174 if curr_version == __dbversion__:
174 if curr_version == __dbversion__:
175 log.info('This database is already at the newest version')
175 log.info('This database is already at the newest version')
176 sys.exit(0)
176 sys.exit(0)
177
177
178 upgrade_steps = range(curr_version + 1, __dbversion__ + 1)
178 upgrade_steps = range(curr_version + 1, __dbversion__ + 1)
179 notify('attempting to upgrade database from '
179 notify('attempting to upgrade database from '
180 'version %s to version %s' % (curr_version, __dbversion__))
180 'version %s to version %s' % (curr_version, __dbversion__))
181
181
182 # CALL THE PROPER ORDER OF STEPS TO PERFORM FULL UPGRADE
182 # CALL THE PROPER ORDER OF STEPS TO PERFORM FULL UPGRADE
183 _step = None
183 _step = None
184 for step in upgrade_steps:
184 for step in upgrade_steps:
185 notify('performing upgrade step %s' % step)
185 notify('performing upgrade step %s' % step)
186 time.sleep(0.5)
186 time.sleep(0.5)
187
187
188 api.upgrade(db_uri, repository_path, step)
188 api.upgrade(db_uri, repository_path, step)
189 self.sa.rollback()
189 self.sa.rollback()
190 notify('schema upgrade for step %s completed' % (step,))
190 notify('schema upgrade for step %s completed' % (step,))
191
191
192 _step = step
192 _step = step
193
193
194 notify('upgrade to version %s successful' % _step)
194 notify('upgrade to version %s successful' % _step)
195
195
196 def fix_repo_paths(self):
196 def fix_repo_paths(self):
197 """
197 """
198 Fixes an old RhodeCode version path into new one without a '*'
198 Fixes an old RhodeCode version path into new one without a '*'
199 """
199 """
200
200
201 paths = self.sa.query(RhodeCodeUi)\
201 paths = self.sa.query(RhodeCodeUi)\
202 .filter(RhodeCodeUi.ui_key == '/')\
202 .filter(RhodeCodeUi.ui_key == '/')\
203 .scalar()
203 .scalar()
204
204
205 paths.ui_value = paths.ui_value.replace('*', '')
205 paths.ui_value = paths.ui_value.replace('*', '')
206
206
207 try:
207 try:
208 self.sa.add(paths)
208 self.sa.add(paths)
209 self.sa.commit()
209 self.sa.commit()
210 except Exception:
210 except Exception:
211 self.sa.rollback()
211 self.sa.rollback()
212 raise
212 raise
213
213
214 def fix_default_user(self):
214 def fix_default_user(self):
215 """
215 """
216 Fixes an old default user with some 'nicer' default values,
216 Fixes an old default user with some 'nicer' default values,
217 used mostly for anonymous access
217 used mostly for anonymous access
218 """
218 """
219 def_user = self.sa.query(User)\
219 def_user = self.sa.query(User)\
220 .filter(User.username == User.DEFAULT_USER)\
220 .filter(User.username == User.DEFAULT_USER)\
221 .one()
221 .one()
222
222
223 def_user.name = 'Anonymous'
223 def_user.name = 'Anonymous'
224 def_user.lastname = 'User'
224 def_user.lastname = 'User'
225 def_user.email = User.DEFAULT_USER_EMAIL
225 def_user.email = User.DEFAULT_USER_EMAIL
226
226
227 try:
227 try:
228 self.sa.add(def_user)
228 self.sa.add(def_user)
229 self.sa.commit()
229 self.sa.commit()
230 except Exception:
230 except Exception:
231 self.sa.rollback()
231 self.sa.rollback()
232 raise
232 raise
233
233
234 def fix_settings(self):
234 def fix_settings(self):
235 """
235 """
236 Fixes rhodecode settings and adds ga_code key for google analytics
236 Fixes rhodecode settings and adds ga_code key for google analytics
237 """
237 """
238
238
239 hgsettings3 = RhodeCodeSetting('ga_code', '')
239 hgsettings3 = RhodeCodeSetting('ga_code', '')
240
240
241 try:
241 try:
242 self.sa.add(hgsettings3)
242 self.sa.add(hgsettings3)
243 self.sa.commit()
243 self.sa.commit()
244 except Exception:
244 except Exception:
245 self.sa.rollback()
245 self.sa.rollback()
246 raise
246 raise
247
247
248 def create_admin_and_prompt(self):
248 def create_admin_and_prompt(self):
249
249
250 # defaults
250 # defaults
251 defaults = self.cli_args
251 defaults = self.cli_args
252 username = defaults.get('username')
252 username = defaults.get('username')
253 password = defaults.get('password')
253 password = defaults.get('password')
254 email = defaults.get('email')
254 email = defaults.get('email')
255
255
256 if username is None:
256 if username is None:
257 username = raw_input('Specify admin username:')
257 username = raw_input('Specify admin username:')
258 if password is None:
258 if password is None:
259 password = self._get_admin_password()
259 password = self._get_admin_password()
260 if not password:
260 if not password:
261 # second try
261 # second try
262 password = self._get_admin_password()
262 password = self._get_admin_password()
263 if not password:
263 if not password:
264 sys.exit()
264 sys.exit()
265 if email is None:
265 if email is None:
266 email = raw_input('Specify admin email:')
266 email = raw_input('Specify admin email:')
267 api_key = self.cli_args.get('api_key')
267 api_key = self.cli_args.get('api_key')
268 self.create_user(username, password, email, True,
268 self.create_user(username, password, email, True,
269 strict_creation_check=False,
269 strict_creation_check=False,
270 api_key=api_key)
270 api_key=api_key)
271
271
272 def _get_admin_password(self):
272 def _get_admin_password(self):
273 password = getpass.getpass('Specify admin password '
273 password = getpass.getpass('Specify admin password '
274 '(min 6 chars):')
274 '(min 6 chars):')
275 confirm = getpass.getpass('Confirm password:')
275 confirm = getpass.getpass('Confirm password:')
276
276
277 if password != confirm:
277 if password != confirm:
278 log.error('passwords mismatch')
278 log.error('passwords mismatch')
279 return False
279 return False
280 if len(password) < 6:
280 if len(password) < 6:
281 log.error('password is too short - use at least 6 characters')
281 log.error('password is too short - use at least 6 characters')
282 return False
282 return False
283
283
284 return password
284 return password
285
285
286 def create_test_admin_and_users(self):
286 def create_test_admin_and_users(self):
287 log.info('creating admin and regular test users')
287 log.info('creating admin and regular test users')
288 from rhodecode.tests import TEST_USER_ADMIN_LOGIN, \
288 from rhodecode.tests import TEST_USER_ADMIN_LOGIN, \
289 TEST_USER_ADMIN_PASS, TEST_USER_ADMIN_EMAIL, \
289 TEST_USER_ADMIN_PASS, TEST_USER_ADMIN_EMAIL, \
290 TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS, \
290 TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS, \
291 TEST_USER_REGULAR_EMAIL, TEST_USER_REGULAR2_LOGIN, \
291 TEST_USER_REGULAR_EMAIL, TEST_USER_REGULAR2_LOGIN, \
292 TEST_USER_REGULAR2_PASS, TEST_USER_REGULAR2_EMAIL
292 TEST_USER_REGULAR2_PASS, TEST_USER_REGULAR2_EMAIL
293
293
294 self.create_user(TEST_USER_ADMIN_LOGIN, TEST_USER_ADMIN_PASS,
294 self.create_user(TEST_USER_ADMIN_LOGIN, TEST_USER_ADMIN_PASS,
295 TEST_USER_ADMIN_EMAIL, True)
295 TEST_USER_ADMIN_EMAIL, True, api_key=True)
296
296
297 self.create_user(TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS,
297 self.create_user(TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS,
298 TEST_USER_REGULAR_EMAIL, False)
298 TEST_USER_REGULAR_EMAIL, False, api_key=True)
299
299
300 self.create_user(TEST_USER_REGULAR2_LOGIN, TEST_USER_REGULAR2_PASS,
300 self.create_user(TEST_USER_REGULAR2_LOGIN, TEST_USER_REGULAR2_PASS,
301 TEST_USER_REGULAR2_EMAIL, False)
301 TEST_USER_REGULAR2_EMAIL, False, api_key=True)
302
302
303 def create_ui_settings(self, repo_store_path):
303 def create_ui_settings(self, repo_store_path):
304 """
304 """
305 Creates ui settings, fills out hooks
305 Creates ui settings, fills out hooks
306 and disables dotencode
306 and disables dotencode
307 """
307 """
308 settings_model = SettingsModel(sa=self.sa)
308 settings_model = SettingsModel(sa=self.sa)
309
309
310 # Build HOOKS
310 # Build HOOKS
311 hooks = [
311 hooks = [
312 (RhodeCodeUi.HOOK_REPO_SIZE, 'python:vcsserver.hooks.repo_size'),
312 (RhodeCodeUi.HOOK_REPO_SIZE, 'python:vcsserver.hooks.repo_size'),
313
313
314 # HG
314 # HG
315 (RhodeCodeUi.HOOK_PRE_PULL, 'python:vcsserver.hooks.pre_pull'),
315 (RhodeCodeUi.HOOK_PRE_PULL, 'python:vcsserver.hooks.pre_pull'),
316 (RhodeCodeUi.HOOK_PULL, 'python:vcsserver.hooks.log_pull_action'),
316 (RhodeCodeUi.HOOK_PULL, 'python:vcsserver.hooks.log_pull_action'),
317 (RhodeCodeUi.HOOK_PRE_PUSH, 'python:vcsserver.hooks.pre_push'),
317 (RhodeCodeUi.HOOK_PRE_PUSH, 'python:vcsserver.hooks.pre_push'),
318 (RhodeCodeUi.HOOK_PRETX_PUSH, 'python:vcsserver.hooks.pre_push'),
318 (RhodeCodeUi.HOOK_PRETX_PUSH, 'python:vcsserver.hooks.pre_push'),
319 (RhodeCodeUi.HOOK_PUSH, 'python:vcsserver.hooks.log_push_action'),
319 (RhodeCodeUi.HOOK_PUSH, 'python:vcsserver.hooks.log_push_action'),
320
320
321 ]
321 ]
322
322
323 for key, value in hooks:
323 for key, value in hooks:
324 hook_obj = settings_model.get_ui_by_key(key)
324 hook_obj = settings_model.get_ui_by_key(key)
325 hooks2 = hook_obj if hook_obj else RhodeCodeUi()
325 hooks2 = hook_obj if hook_obj else RhodeCodeUi()
326 hooks2.ui_section = 'hooks'
326 hooks2.ui_section = 'hooks'
327 hooks2.ui_key = key
327 hooks2.ui_key = key
328 hooks2.ui_value = value
328 hooks2.ui_value = value
329 self.sa.add(hooks2)
329 self.sa.add(hooks2)
330
330
331 # enable largefiles
331 # enable largefiles
332 largefiles = RhodeCodeUi()
332 largefiles = RhodeCodeUi()
333 largefiles.ui_section = 'extensions'
333 largefiles.ui_section = 'extensions'
334 largefiles.ui_key = 'largefiles'
334 largefiles.ui_key = 'largefiles'
335 largefiles.ui_value = ''
335 largefiles.ui_value = ''
336 self.sa.add(largefiles)
336 self.sa.add(largefiles)
337
337
338 # set default largefiles cache dir, defaults to
338 # set default largefiles cache dir, defaults to
339 # /repo location/.cache/largefiles
339 # /repo location/.cache/largefiles
340 largefiles = RhodeCodeUi()
340 largefiles = RhodeCodeUi()
341 largefiles.ui_section = 'largefiles'
341 largefiles.ui_section = 'largefiles'
342 largefiles.ui_key = 'usercache'
342 largefiles.ui_key = 'usercache'
343 largefiles.ui_value = os.path.join(repo_store_path, '.cache',
343 largefiles.ui_value = os.path.join(repo_store_path, '.cache',
344 'largefiles')
344 'largefiles')
345 self.sa.add(largefiles)
345 self.sa.add(largefiles)
346
346
347 # enable hgsubversion disabled by default
347 # enable hgsubversion disabled by default
348 hgsubversion = RhodeCodeUi()
348 hgsubversion = RhodeCodeUi()
349 hgsubversion.ui_section = 'extensions'
349 hgsubversion.ui_section = 'extensions'
350 hgsubversion.ui_key = 'hgsubversion'
350 hgsubversion.ui_key = 'hgsubversion'
351 hgsubversion.ui_value = ''
351 hgsubversion.ui_value = ''
352 hgsubversion.ui_active = False
352 hgsubversion.ui_active = False
353 self.sa.add(hgsubversion)
353 self.sa.add(hgsubversion)
354
354
355 # enable hggit disabled by default
355 # enable hggit disabled by default
356 hggit = RhodeCodeUi()
356 hggit = RhodeCodeUi()
357 hggit.ui_section = 'extensions'
357 hggit.ui_section = 'extensions'
358 hggit.ui_key = 'hggit'
358 hggit.ui_key = 'hggit'
359 hggit.ui_value = ''
359 hggit.ui_value = ''
360 hggit.ui_active = False
360 hggit.ui_active = False
361 self.sa.add(hggit)
361 self.sa.add(hggit)
362
362
363 # set svn branch defaults
363 # set svn branch defaults
364 branches = ["/branches/*", "/trunk"]
364 branches = ["/branches/*", "/trunk"]
365 tags = ["/tags/*"]
365 tags = ["/tags/*"]
366
366
367 for branch in branches:
367 for branch in branches:
368 settings_model.create_ui_section_value(
368 settings_model.create_ui_section_value(
369 RhodeCodeUi.SVN_BRANCH_ID, branch)
369 RhodeCodeUi.SVN_BRANCH_ID, branch)
370
370
371 for tag in tags:
371 for tag in tags:
372 settings_model.create_ui_section_value(RhodeCodeUi.SVN_TAG_ID, tag)
372 settings_model.create_ui_section_value(RhodeCodeUi.SVN_TAG_ID, tag)
373
373
374 def create_auth_plugin_options(self, skip_existing=False):
374 def create_auth_plugin_options(self, skip_existing=False):
375 """
375 """
376 Create default auth plugin settings, and make it active
376 Create default auth plugin settings, and make it active
377
377
378 :param skip_existing:
378 :param skip_existing:
379 """
379 """
380
380
381 for k, v, t in [('auth_plugins', 'egg:rhodecode-enterprise-ce#rhodecode', 'list'),
381 for k, v, t in [('auth_plugins', 'egg:rhodecode-enterprise-ce#rhodecode', 'list'),
382 ('auth_rhodecode_enabled', 'True', 'bool')]:
382 ('auth_rhodecode_enabled', 'True', 'bool')]:
383 if (skip_existing and
383 if (skip_existing and
384 SettingsModel().get_setting_by_name(k) is not None):
384 SettingsModel().get_setting_by_name(k) is not None):
385 log.debug('Skipping option %s' % k)
385 log.debug('Skipping option %s' % k)
386 continue
386 continue
387 setting = RhodeCodeSetting(k, v, t)
387 setting = RhodeCodeSetting(k, v, t)
388 self.sa.add(setting)
388 self.sa.add(setting)
389
389
390 def create_default_options(self, skip_existing=False):
390 def create_default_options(self, skip_existing=False):
391 """Creates default settings"""
391 """Creates default settings"""
392
392
393 for k, v, t in [
393 for k, v, t in [
394 ('default_repo_enable_locking', False, 'bool'),
394 ('default_repo_enable_locking', False, 'bool'),
395 ('default_repo_enable_downloads', False, 'bool'),
395 ('default_repo_enable_downloads', False, 'bool'),
396 ('default_repo_enable_statistics', False, 'bool'),
396 ('default_repo_enable_statistics', False, 'bool'),
397 ('default_repo_private', False, 'bool'),
397 ('default_repo_private', False, 'bool'),
398 ('default_repo_type', 'hg', 'unicode')]:
398 ('default_repo_type', 'hg', 'unicode')]:
399
399
400 if (skip_existing and
400 if (skip_existing and
401 SettingsModel().get_setting_by_name(k) is not None):
401 SettingsModel().get_setting_by_name(k) is not None):
402 log.debug('Skipping option %s' % k)
402 log.debug('Skipping option %s' % k)
403 continue
403 continue
404 setting = RhodeCodeSetting(k, v, t)
404 setting = RhodeCodeSetting(k, v, t)
405 self.sa.add(setting)
405 self.sa.add(setting)
406
406
407 def fixup_groups(self):
407 def fixup_groups(self):
408 def_usr = User.get_default_user()
408 def_usr = User.get_default_user()
409 for g in RepoGroup.query().all():
409 for g in RepoGroup.query().all():
410 g.group_name = g.get_new_name(g.name)
410 g.group_name = g.get_new_name(g.name)
411 self.sa.add(g)
411 self.sa.add(g)
412 # get default perm
412 # get default perm
413 default = UserRepoGroupToPerm.query()\
413 default = UserRepoGroupToPerm.query()\
414 .filter(UserRepoGroupToPerm.group == g)\
414 .filter(UserRepoGroupToPerm.group == g)\
415 .filter(UserRepoGroupToPerm.user == def_usr)\
415 .filter(UserRepoGroupToPerm.user == def_usr)\
416 .scalar()
416 .scalar()
417
417
418 if default is None:
418 if default is None:
419 log.debug('missing default permission for group %s adding' % g)
419 log.debug('missing default permission for group %s adding' % g)
420 perm_obj = RepoGroupModel()._create_default_perms(g)
420 perm_obj = RepoGroupModel()._create_default_perms(g)
421 self.sa.add(perm_obj)
421 self.sa.add(perm_obj)
422
422
423 def reset_permissions(self, username):
423 def reset_permissions(self, username):
424 """
424 """
425 Resets permissions to default state, useful when old systems had
425 Resets permissions to default state, useful when old systems had
426 bad permissions, we must clean them up
426 bad permissions, we must clean them up
427
427
428 :param username:
428 :param username:
429 """
429 """
430 default_user = User.get_by_username(username)
430 default_user = User.get_by_username(username)
431 if not default_user:
431 if not default_user:
432 return
432 return
433
433
434 u2p = UserToPerm.query()\
434 u2p = UserToPerm.query()\
435 .filter(UserToPerm.user == default_user).all()
435 .filter(UserToPerm.user == default_user).all()
436 fixed = False
436 fixed = False
437 if len(u2p) != len(Permission.DEFAULT_USER_PERMISSIONS):
437 if len(u2p) != len(Permission.DEFAULT_USER_PERMISSIONS):
438 for p in u2p:
438 for p in u2p:
439 Session().delete(p)
439 Session().delete(p)
440 fixed = True
440 fixed = True
441 self.populate_default_permissions()
441 self.populate_default_permissions()
442 return fixed
442 return fixed
443
443
444 def update_repo_info(self):
444 def update_repo_info(self):
445 RepoModel.update_repoinfo()
445 RepoModel.update_repoinfo()
446
446
447 def config_prompt(self, test_repo_path='', retries=3):
447 def config_prompt(self, test_repo_path='', retries=3):
448 defaults = self.cli_args
448 defaults = self.cli_args
449 _path = defaults.get('repos_location')
449 _path = defaults.get('repos_location')
450 if retries == 3:
450 if retries == 3:
451 log.info('Setting up repositories config')
451 log.info('Setting up repositories config')
452
452
453 if _path is not None:
453 if _path is not None:
454 path = _path
454 path = _path
455 elif not self.tests and not test_repo_path:
455 elif not self.tests and not test_repo_path:
456 path = raw_input(
456 path = raw_input(
457 'Enter a valid absolute path to store repositories. '
457 'Enter a valid absolute path to store repositories. '
458 'All repositories in that path will be added automatically:'
458 'All repositories in that path will be added automatically:'
459 )
459 )
460 else:
460 else:
461 path = test_repo_path
461 path = test_repo_path
462 path_ok = True
462 path_ok = True
463
463
464 # check proper dir
464 # check proper dir
465 if not os.path.isdir(path):
465 if not os.path.isdir(path):
466 path_ok = False
466 path_ok = False
467 log.error('Given path %s is not a valid directory' % (path,))
467 log.error('Given path %s is not a valid directory' % (path,))
468
468
469 elif not os.path.isabs(path):
469 elif not os.path.isabs(path):
470 path_ok = False
470 path_ok = False
471 log.error('Given path %s is not an absolute path' % (path,))
471 log.error('Given path %s is not an absolute path' % (path,))
472
472
473 # check if path is at least readable.
473 # check if path is at least readable.
474 if not os.access(path, os.R_OK):
474 if not os.access(path, os.R_OK):
475 path_ok = False
475 path_ok = False
476 log.error('Given path %s is not readable' % (path,))
476 log.error('Given path %s is not readable' % (path,))
477
477
478 # check write access, warn user about non writeable paths
478 # check write access, warn user about non writeable paths
479 elif not os.access(path, os.W_OK) and path_ok:
479 elif not os.access(path, os.W_OK) and path_ok:
480 log.warning('No write permission to given path %s' % (path,))
480 log.warning('No write permission to given path %s' % (path,))
481
481
482 q = ('Given path %s is not writeable, do you want to '
482 q = ('Given path %s is not writeable, do you want to '
483 'continue with read only mode ? [y/n]' % (path,))
483 'continue with read only mode ? [y/n]' % (path,))
484 if not self.ask_ok(q):
484 if not self.ask_ok(q):
485 log.error('Canceled by user')
485 log.error('Canceled by user')
486 sys.exit(-1)
486 sys.exit(-1)
487
487
488 if retries == 0:
488 if retries == 0:
489 sys.exit('max retries reached')
489 sys.exit('max retries reached')
490 if not path_ok:
490 if not path_ok:
491 retries -= 1
491 retries -= 1
492 return self.config_prompt(test_repo_path, retries)
492 return self.config_prompt(test_repo_path, retries)
493
493
494 real_path = os.path.normpath(os.path.realpath(path))
494 real_path = os.path.normpath(os.path.realpath(path))
495
495
496 if real_path != os.path.normpath(path):
496 if real_path != os.path.normpath(path):
497 q = ('Path looks like a symlink, RhodeCode Enterprise will store '
497 q = ('Path looks like a symlink, RhodeCode Enterprise will store '
498 'given path as %s ? [y/n]') % (real_path,)
498 'given path as %s ? [y/n]') % (real_path,)
499 if not self.ask_ok(q):
499 if not self.ask_ok(q):
500 log.error('Canceled by user')
500 log.error('Canceled by user')
501 sys.exit(-1)
501 sys.exit(-1)
502
502
503 return real_path
503 return real_path
504
504
505 def create_settings(self, path):
505 def create_settings(self, path):
506
506
507 self.create_ui_settings(path)
507 self.create_ui_settings(path)
508
508
509 ui_config = [
509 ui_config = [
510 ('web', 'push_ssl', 'false'),
510 ('web', 'push_ssl', 'false'),
511 ('web', 'allow_archive', 'gz zip bz2'),
511 ('web', 'allow_archive', 'gz zip bz2'),
512 ('web', 'allow_push', '*'),
512 ('web', 'allow_push', '*'),
513 ('web', 'baseurl', '/'),
513 ('web', 'baseurl', '/'),
514 ('paths', '/', path),
514 ('paths', '/', path),
515 ('phases', 'publish', 'true')
515 ('phases', 'publish', 'true')
516 ]
516 ]
517 for section, key, value in ui_config:
517 for section, key, value in ui_config:
518 ui_conf = RhodeCodeUi()
518 ui_conf = RhodeCodeUi()
519 setattr(ui_conf, 'ui_section', section)
519 setattr(ui_conf, 'ui_section', section)
520 setattr(ui_conf, 'ui_key', key)
520 setattr(ui_conf, 'ui_key', key)
521 setattr(ui_conf, 'ui_value', value)
521 setattr(ui_conf, 'ui_value', value)
522 self.sa.add(ui_conf)
522 self.sa.add(ui_conf)
523
523
524 # rhodecode app settings
524 # rhodecode app settings
525 settings = [
525 settings = [
526 ('realm', 'RhodeCode', 'unicode'),
526 ('realm', 'RhodeCode', 'unicode'),
527 ('title', '', 'unicode'),
527 ('title', '', 'unicode'),
528 ('pre_code', '', 'unicode'),
528 ('pre_code', '', 'unicode'),
529 ('post_code', '', 'unicode'),
529 ('post_code', '', 'unicode'),
530 ('show_public_icon', True, 'bool'),
530 ('show_public_icon', True, 'bool'),
531 ('show_private_icon', True, 'bool'),
531 ('show_private_icon', True, 'bool'),
532 ('stylify_metatags', False, 'bool'),
532 ('stylify_metatags', False, 'bool'),
533 ('dashboard_items', 100, 'int'),
533 ('dashboard_items', 100, 'int'),
534 ('admin_grid_items', 25, 'int'),
534 ('admin_grid_items', 25, 'int'),
535 ('show_version', True, 'bool'),
535 ('show_version', True, 'bool'),
536 ('use_gravatar', False, 'bool'),
536 ('use_gravatar', False, 'bool'),
537 ('gravatar_url', User.DEFAULT_GRAVATAR_URL, 'unicode'),
537 ('gravatar_url', User.DEFAULT_GRAVATAR_URL, 'unicode'),
538 ('clone_uri_tmpl', Repository.DEFAULT_CLONE_URI, 'unicode'),
538 ('clone_uri_tmpl', Repository.DEFAULT_CLONE_URI, 'unicode'),
539 ('support_url', '', 'unicode'),
539 ('support_url', '', 'unicode'),
540 ('update_url', RhodeCodeSetting.DEFAULT_UPDATE_URL, 'unicode'),
540 ('update_url', RhodeCodeSetting.DEFAULT_UPDATE_URL, 'unicode'),
541 ('show_revision_number', True, 'bool'),
541 ('show_revision_number', True, 'bool'),
542 ('show_sha_length', 12, 'int'),
542 ('show_sha_length', 12, 'int'),
543 ]
543 ]
544
544
545 for key, val, type_ in settings:
545 for key, val, type_ in settings:
546 sett = RhodeCodeSetting(key, val, type_)
546 sett = RhodeCodeSetting(key, val, type_)
547 self.sa.add(sett)
547 self.sa.add(sett)
548
548
549 self.create_auth_plugin_options()
549 self.create_auth_plugin_options()
550 self.create_default_options()
550 self.create_default_options()
551
551
552 log.info('created ui config')
552 log.info('created ui config')
553
553
554 def create_user(self, username, password, email='', admin=False,
554 def create_user(self, username, password, email='', admin=False,
555 strict_creation_check=True, api_key=None):
555 strict_creation_check=True, api_key=None):
556 log.info('creating user %s' % username)
556 log.info('creating user %s' % username)
557 user = UserModel().create_or_update(
557 user = UserModel().create_or_update(
558 username, password, email, firstname='RhodeCode', lastname='Admin',
558 username, password, email, firstname='RhodeCode', lastname='Admin',
559 active=True, admin=admin, extern_type="rhodecode",
559 active=True, admin=admin, extern_type="rhodecode",
560 strict_creation_check=strict_creation_check)
560 strict_creation_check=strict_creation_check)
561
561
562 if api_key:
562 if api_key:
563 log.info('setting a provided api key for the user %s', username)
563 log.info('setting a provided api key for the user %s', username)
564 user.api_key = api_key
564 from rhodecode.model.auth_token import AuthTokenModel
565 AuthTokenModel().create(
566 user=user, description='BUILTIN TOKEN')
565
567
566 def create_default_user(self):
568 def create_default_user(self):
567 log.info('creating default user')
569 log.info('creating default user')
568 # create default user for handling default permissions.
570 # create default user for handling default permissions.
569 user = UserModel().create_or_update(username=User.DEFAULT_USER,
571 user = UserModel().create_or_update(username=User.DEFAULT_USER,
570 password=str(uuid.uuid1())[:20],
572 password=str(uuid.uuid1())[:20],
571 email=User.DEFAULT_USER_EMAIL,
573 email=User.DEFAULT_USER_EMAIL,
572 firstname='Anonymous',
574 firstname='Anonymous',
573 lastname='User',
575 lastname='User',
574 strict_creation_check=False)
576 strict_creation_check=False)
575 # based on configuration options activate/deactive this user which
577 # based on configuration options activate/deactive this user which
576 # controlls anonymous access
578 # controlls anonymous access
577 if self.cli_args.get('public_access') is False:
579 if self.cli_args.get('public_access') is False:
578 log.info('Public access disabled')
580 log.info('Public access disabled')
579 user.active = False
581 user.active = False
580 Session().add(user)
582 Session().add(user)
581 Session().commit()
583 Session().commit()
582
584
583 def create_permissions(self):
585 def create_permissions(self):
584 """
586 """
585 Creates all permissions defined in the system
587 Creates all permissions defined in the system
586 """
588 """
587 # module.(access|create|change|delete)_[name]
589 # module.(access|create|change|delete)_[name]
588 # module.(none|read|write|admin)
590 # module.(none|read|write|admin)
589 log.info('creating permissions')
591 log.info('creating permissions')
590 PermissionModel(self.sa).create_permissions()
592 PermissionModel(self.sa).create_permissions()
591
593
592 def populate_default_permissions(self):
594 def populate_default_permissions(self):
593 """
595 """
594 Populate default permissions. It will create only the default
596 Populate default permissions. It will create only the default
595 permissions that are missing, and not alter already defined ones
597 permissions that are missing, and not alter already defined ones
596 """
598 """
597 log.info('creating default user permissions')
599 log.info('creating default user permissions')
598 PermissionModel(self.sa).create_default_user_permissions(user=User.DEFAULT_USER)
600 PermissionModel(self.sa).create_default_user_permissions(user=User.DEFAULT_USER)
@@ -1,396 +1,401 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2013-2017 RhodeCode GmbH
3 # Copyright (C) 2013-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21
21
22 """
22 """
23 Set of hooks run by RhodeCode Enterprise
23 Set of hooks run by RhodeCode Enterprise
24 """
24 """
25
25
26 import os
26 import os
27 import collections
27 import collections
28 import logging
28 import logging
29
29
30 import rhodecode
30 import rhodecode
31 from rhodecode import events
31 from rhodecode import events
32 from rhodecode.lib import helpers as h
32 from rhodecode.lib import helpers as h
33 from rhodecode.lib.utils import action_logger
33 from rhodecode.lib.utils import action_logger
34 from rhodecode.lib.utils2 import safe_str
34 from rhodecode.lib.utils2 import safe_str
35 from rhodecode.lib.exceptions import HTTPLockedRC, UserCreationError
35 from rhodecode.lib.exceptions import HTTPLockedRC, UserCreationError
36 from rhodecode.model.db import Repository, User
36 from rhodecode.model.db import Repository, User
37
37
38 log = logging.getLogger(__name__)
38 log = logging.getLogger(__name__)
39
39
40
40
41 HookResponse = collections.namedtuple('HookResponse', ('status', 'output'))
41 HookResponse = collections.namedtuple('HookResponse', ('status', 'output'))
42
42
43
43
44 def is_shadow_repo(extras):
44 def is_shadow_repo(extras):
45 """
45 """
46 Returns ``True`` if this is an action executed against a shadow repository.
46 Returns ``True`` if this is an action executed against a shadow repository.
47 """
47 """
48 return extras['is_shadow_repo']
48 return extras['is_shadow_repo']
49
49
50
50
51 def _get_scm_size(alias, root_path):
51 def _get_scm_size(alias, root_path):
52
52
53 if not alias.startswith('.'):
53 if not alias.startswith('.'):
54 alias += '.'
54 alias += '.'
55
55
56 size_scm, size_root = 0, 0
56 size_scm, size_root = 0, 0
57 for path, unused_dirs, files in os.walk(safe_str(root_path)):
57 for path, unused_dirs, files in os.walk(safe_str(root_path)):
58 if path.find(alias) != -1:
58 if path.find(alias) != -1:
59 for f in files:
59 for f in files:
60 try:
60 try:
61 size_scm += os.path.getsize(os.path.join(path, f))
61 size_scm += os.path.getsize(os.path.join(path, f))
62 except OSError:
62 except OSError:
63 pass
63 pass
64 else:
64 else:
65 for f in files:
65 for f in files:
66 try:
66 try:
67 size_root += os.path.getsize(os.path.join(path, f))
67 size_root += os.path.getsize(os.path.join(path, f))
68 except OSError:
68 except OSError:
69 pass
69 pass
70
70
71 size_scm_f = h.format_byte_size_binary(size_scm)
71 size_scm_f = h.format_byte_size_binary(size_scm)
72 size_root_f = h.format_byte_size_binary(size_root)
72 size_root_f = h.format_byte_size_binary(size_root)
73 size_total_f = h.format_byte_size_binary(size_root + size_scm)
73 size_total_f = h.format_byte_size_binary(size_root + size_scm)
74
74
75 return size_scm_f, size_root_f, size_total_f
75 return size_scm_f, size_root_f, size_total_f
76
76
77
77
78 # actual hooks called by Mercurial internally, and GIT by our Python Hooks
78 # actual hooks called by Mercurial internally, and GIT by our Python Hooks
79 def repo_size(extras):
79 def repo_size(extras):
80 """Present size of repository after push."""
80 """Present size of repository after push."""
81 repo = Repository.get_by_repo_name(extras.repository)
81 repo = Repository.get_by_repo_name(extras.repository)
82 vcs_part = safe_str(u'.%s' % repo.repo_type)
82 vcs_part = safe_str(u'.%s' % repo.repo_type)
83 size_vcs, size_root, size_total = _get_scm_size(vcs_part,
83 size_vcs, size_root, size_total = _get_scm_size(vcs_part,
84 repo.repo_full_path)
84 repo.repo_full_path)
85 msg = ('Repository `%s` size summary %s:%s repo:%s total:%s\n'
85 msg = ('Repository `%s` size summary %s:%s repo:%s total:%s\n'
86 % (repo.repo_name, vcs_part, size_vcs, size_root, size_total))
86 % (repo.repo_name, vcs_part, size_vcs, size_root, size_total))
87 return HookResponse(0, msg)
87 return HookResponse(0, msg)
88
88
89
89
90 def pre_push(extras):
90 def pre_push(extras):
91 """
91 """
92 Hook executed before pushing code.
92 Hook executed before pushing code.
93
93
94 It bans pushing when the repository is locked.
94 It bans pushing when the repository is locked.
95 """
95 """
96
96
97 usr = User.get_by_username(extras.username)
97 usr = User.get_by_username(extras.username)
98 output = ''
98 output = ''
99 if extras.locked_by[0] and usr.user_id != int(extras.locked_by[0]):
99 if extras.locked_by[0] and usr.user_id != int(extras.locked_by[0]):
100 locked_by = User.get(extras.locked_by[0]).username
100 locked_by = User.get(extras.locked_by[0]).username
101 reason = extras.locked_by[2]
101 reason = extras.locked_by[2]
102 # this exception is interpreted in git/hg middlewares and based
102 # this exception is interpreted in git/hg middlewares and based
103 # on that proper return code is server to client
103 # on that proper return code is server to client
104 _http_ret = HTTPLockedRC(
104 _http_ret = HTTPLockedRC(
105 _locked_by_explanation(extras.repository, locked_by, reason))
105 _locked_by_explanation(extras.repository, locked_by, reason))
106 if str(_http_ret.code).startswith('2'):
106 if str(_http_ret.code).startswith('2'):
107 # 2xx Codes don't raise exceptions
107 # 2xx Codes don't raise exceptions
108 output = _http_ret.title
108 output = _http_ret.title
109 else:
109 else:
110 raise _http_ret
110 raise _http_ret
111
111
112 # Propagate to external components. This is done after checking the
112 # Propagate to external components. This is done after checking the
113 # lock, for consistent behavior.
113 # lock, for consistent behavior.
114 if not is_shadow_repo(extras):
114 if not is_shadow_repo(extras):
115 pre_push_extension(repo_store_path=Repository.base_path(), **extras)
115 pre_push_extension(repo_store_path=Repository.base_path(), **extras)
116 events.trigger(events.RepoPrePushEvent(
116 events.trigger(events.RepoPrePushEvent(
117 repo_name=extras.repository, extras=extras))
117 repo_name=extras.repository, extras=extras))
118
118
119 return HookResponse(0, output)
119 return HookResponse(0, output)
120
120
121
121
122 def pre_pull(extras):
122 def pre_pull(extras):
123 """
123 """
124 Hook executed before pulling the code.
124 Hook executed before pulling the code.
125
125
126 It bans pulling when the repository is locked.
126 It bans pulling when the repository is locked.
127 """
127 """
128
128
129 output = ''
129 output = ''
130 if extras.locked_by[0]:
130 if extras.locked_by[0]:
131 locked_by = User.get(extras.locked_by[0]).username
131 locked_by = User.get(extras.locked_by[0]).username
132 reason = extras.locked_by[2]
132 reason = extras.locked_by[2]
133 # this exception is interpreted in git/hg middlewares and based
133 # this exception is interpreted in git/hg middlewares and based
134 # on that proper return code is server to client
134 # on that proper return code is server to client
135 _http_ret = HTTPLockedRC(
135 _http_ret = HTTPLockedRC(
136 _locked_by_explanation(extras.repository, locked_by, reason))
136 _locked_by_explanation(extras.repository, locked_by, reason))
137 if str(_http_ret.code).startswith('2'):
137 if str(_http_ret.code).startswith('2'):
138 # 2xx Codes don't raise exceptions
138 # 2xx Codes don't raise exceptions
139 output = _http_ret.title
139 output = _http_ret.title
140 else:
140 else:
141 raise _http_ret
141 raise _http_ret
142
142
143 # Propagate to external components. This is done after checking the
143 # Propagate to external components. This is done after checking the
144 # lock, for consistent behavior.
144 # lock, for consistent behavior.
145 if not is_shadow_repo(extras):
145 if not is_shadow_repo(extras):
146 pre_pull_extension(**extras)
146 pre_pull_extension(**extras)
147 events.trigger(events.RepoPrePullEvent(
147 events.trigger(events.RepoPrePullEvent(
148 repo_name=extras.repository, extras=extras))
148 repo_name=extras.repository, extras=extras))
149
149
150 return HookResponse(0, output)
150 return HookResponse(0, output)
151
151
152
152
153 def post_pull(extras):
153 def post_pull(extras):
154 """Hook executed after client pulls the code."""
154 """Hook executed after client pulls the code."""
155 user = User.get_by_username(extras.username)
155 user = User.get_by_username(extras.username)
156 action = 'pull'
156 action = 'pull'
157 action_logger(user, action, extras.repository, extras.ip, commit=True)
157 action_logger(user, action, extras.repository, extras.ip, commit=True)
158
158
159 # Propagate to external components.
159 # Propagate to external components.
160 if not is_shadow_repo(extras):
160 if not is_shadow_repo(extras):
161 post_pull_extension(**extras)
161 post_pull_extension(**extras)
162 events.trigger(events.RepoPullEvent(
162 events.trigger(events.RepoPullEvent(
163 repo_name=extras.repository, extras=extras))
163 repo_name=extras.repository, extras=extras))
164
164
165 output = ''
165 output = ''
166 # make lock is a tri state False, True, None. We only make lock on True
166 # make lock is a tri state False, True, None. We only make lock on True
167 if extras.make_lock is True and not is_shadow_repo(extras):
167 if extras.make_lock is True and not is_shadow_repo(extras):
168 Repository.lock(Repository.get_by_repo_name(extras.repository),
168 Repository.lock(Repository.get_by_repo_name(extras.repository),
169 user.user_id,
169 user.user_id,
170 lock_reason=Repository.LOCK_PULL)
170 lock_reason=Repository.LOCK_PULL)
171 msg = 'Made lock on repo `%s`' % (extras.repository,)
171 msg = 'Made lock on repo `%s`' % (extras.repository,)
172 output += msg
172 output += msg
173
173
174 if extras.locked_by[0]:
174 if extras.locked_by[0]:
175 locked_by = User.get(extras.locked_by[0]).username
175 locked_by = User.get(extras.locked_by[0]).username
176 reason = extras.locked_by[2]
176 reason = extras.locked_by[2]
177 _http_ret = HTTPLockedRC(
177 _http_ret = HTTPLockedRC(
178 _locked_by_explanation(extras.repository, locked_by, reason))
178 _locked_by_explanation(extras.repository, locked_by, reason))
179 if str(_http_ret.code).startswith('2'):
179 if str(_http_ret.code).startswith('2'):
180 # 2xx Codes don't raise exceptions
180 # 2xx Codes don't raise exceptions
181 output += _http_ret.title
181 output += _http_ret.title
182
182
183 return HookResponse(0, output)
183 return HookResponse(0, output)
184
184
185
185
186 def post_push(extras):
186 def post_push(extras):
187 """Hook executed after user pushes to the repository."""
187 """Hook executed after user pushes to the repository."""
188 action_tmpl = extras.action + ':%s'
188 action_tmpl = extras.action + ':%s'
189 commit_ids = extras.commit_ids[:29000]
189 commit_ids = extras.commit_ids[:29000]
190
190
191 action = action_tmpl % ','.join(commit_ids)
191 action = action_tmpl % ','.join(commit_ids)
192 action_logger(
192 action_logger(
193 extras.username, action, extras.repository, extras.ip, commit=True)
193 extras.username, action, extras.repository, extras.ip, commit=True)
194
194
195 # Propagate to external components.
195 # Propagate to external components.
196 if not is_shadow_repo(extras):
196 if not is_shadow_repo(extras):
197 post_push_extension(
197 post_push_extension(
198 repo_store_path=Repository.base_path(),
198 repo_store_path=Repository.base_path(),
199 pushed_revs=commit_ids,
199 pushed_revs=commit_ids,
200 **extras)
200 **extras)
201 events.trigger(events.RepoPushEvent(
201 events.trigger(events.RepoPushEvent(
202 repo_name=extras.repository,
202 repo_name=extras.repository,
203 pushed_commit_ids=commit_ids,
203 pushed_commit_ids=commit_ids,
204 extras=extras))
204 extras=extras))
205
205
206 output = ''
206 output = ''
207 # make lock is a tri state False, True, None. We only release lock on False
207 # make lock is a tri state False, True, None. We only release lock on False
208 if extras.make_lock is False and not is_shadow_repo(extras):
208 if extras.make_lock is False and not is_shadow_repo(extras):
209 Repository.unlock(Repository.get_by_repo_name(extras.repository))
209 Repository.unlock(Repository.get_by_repo_name(extras.repository))
210 msg = 'Released lock on repo `%s`\n' % extras.repository
210 msg = 'Released lock on repo `%s`\n' % extras.repository
211 output += msg
211 output += msg
212
212
213 if extras.locked_by[0]:
213 if extras.locked_by[0]:
214 locked_by = User.get(extras.locked_by[0]).username
214 locked_by = User.get(extras.locked_by[0]).username
215 reason = extras.locked_by[2]
215 reason = extras.locked_by[2]
216 _http_ret = HTTPLockedRC(
216 _http_ret = HTTPLockedRC(
217 _locked_by_explanation(extras.repository, locked_by, reason))
217 _locked_by_explanation(extras.repository, locked_by, reason))
218 # TODO: johbo: if not?
218 # TODO: johbo: if not?
219 if str(_http_ret.code).startswith('2'):
219 if str(_http_ret.code).startswith('2'):
220 # 2xx Codes don't raise exceptions
220 # 2xx Codes don't raise exceptions
221 output += _http_ret.title
221 output += _http_ret.title
222
222
223 output += 'RhodeCode: push completed\n'
223 output += 'RhodeCode: push completed\n'
224
224
225 return HookResponse(0, output)
225 return HookResponse(0, output)
226
226
227
227
228 def _locked_by_explanation(repo_name, user_name, reason):
228 def _locked_by_explanation(repo_name, user_name, reason):
229 message = (
229 message = (
230 'Repository `%s` locked by user `%s`. Reason:`%s`'
230 'Repository `%s` locked by user `%s`. Reason:`%s`'
231 % (repo_name, user_name, reason))
231 % (repo_name, user_name, reason))
232 return message
232 return message
233
233
234
234
235 def check_allowed_create_user(user_dict, created_by, **kwargs):
235 def check_allowed_create_user(user_dict, created_by, **kwargs):
236 # pre create hooks
236 # pre create hooks
237 if pre_create_user.is_active():
237 if pre_create_user.is_active():
238 allowed, reason = pre_create_user(created_by=created_by, **user_dict)
238 allowed, reason = pre_create_user(created_by=created_by, **user_dict)
239 if not allowed:
239 if not allowed:
240 raise UserCreationError(reason)
240 raise UserCreationError(reason)
241
241
242
242
243 class ExtensionCallback(object):
243 class ExtensionCallback(object):
244 """
244 """
245 Forwards a given call to rcextensions, sanitizes keyword arguments.
245 Forwards a given call to rcextensions, sanitizes keyword arguments.
246
246
247 Does check if there is an extension active for that hook. If it is
247 Does check if there is an extension active for that hook. If it is
248 there, it will forward all `kwargs_keys` keyword arguments to the
248 there, it will forward all `kwargs_keys` keyword arguments to the
249 extension callback.
249 extension callback.
250 """
250 """
251
251
252 def __init__(self, hook_name, kwargs_keys):
252 def __init__(self, hook_name, kwargs_keys):
253 self._hook_name = hook_name
253 self._hook_name = hook_name
254 self._kwargs_keys = set(kwargs_keys)
254 self._kwargs_keys = set(kwargs_keys)
255
255
256 def __call__(self, *args, **kwargs):
256 def __call__(self, *args, **kwargs):
257 log.debug('Calling extension callback for %s', self._hook_name)
257 log.debug('Calling extension callback for %s', self._hook_name)
258
258
259 kwargs_to_pass = dict((key, kwargs[key]) for key in self._kwargs_keys)
259 kwargs_to_pass = dict((key, kwargs[key]) for key in self._kwargs_keys)
260 # backward compat for removed api_key for old hooks. THis was it works
261 # with older rcextensions that require api_key present
262 if self._hook_name in ['CREATE_USER_HOOK', 'DELETE_USER_HOOK']:
263 kwargs_to_pass['api_key'] = '_DEPRECATED_'
264
260 callback = self._get_callback()
265 callback = self._get_callback()
261 if callback:
266 if callback:
262 return callback(**kwargs_to_pass)
267 return callback(**kwargs_to_pass)
263 else:
268 else:
264 log.debug('extensions callback not found skipping...')
269 log.debug('extensions callback not found skipping...')
265
270
266 def is_active(self):
271 def is_active(self):
267 return hasattr(rhodecode.EXTENSIONS, self._hook_name)
272 return hasattr(rhodecode.EXTENSIONS, self._hook_name)
268
273
269 def _get_callback(self):
274 def _get_callback(self):
270 return getattr(rhodecode.EXTENSIONS, self._hook_name, None)
275 return getattr(rhodecode.EXTENSIONS, self._hook_name, None)
271
276
272
277
273 pre_pull_extension = ExtensionCallback(
278 pre_pull_extension = ExtensionCallback(
274 hook_name='PRE_PULL_HOOK',
279 hook_name='PRE_PULL_HOOK',
275 kwargs_keys=(
280 kwargs_keys=(
276 'server_url', 'config', 'scm', 'username', 'ip', 'action',
281 'server_url', 'config', 'scm', 'username', 'ip', 'action',
277 'repository'))
282 'repository'))
278
283
279
284
280 post_pull_extension = ExtensionCallback(
285 post_pull_extension = ExtensionCallback(
281 hook_name='PULL_HOOK',
286 hook_name='PULL_HOOK',
282 kwargs_keys=(
287 kwargs_keys=(
283 'server_url', 'config', 'scm', 'username', 'ip', 'action',
288 'server_url', 'config', 'scm', 'username', 'ip', 'action',
284 'repository'))
289 'repository'))
285
290
286
291
287 pre_push_extension = ExtensionCallback(
292 pre_push_extension = ExtensionCallback(
288 hook_name='PRE_PUSH_HOOK',
293 hook_name='PRE_PUSH_HOOK',
289 kwargs_keys=(
294 kwargs_keys=(
290 'server_url', 'config', 'scm', 'username', 'ip', 'action',
295 'server_url', 'config', 'scm', 'username', 'ip', 'action',
291 'repository', 'repo_store_path', 'commit_ids'))
296 'repository', 'repo_store_path', 'commit_ids'))
292
297
293
298
294 post_push_extension = ExtensionCallback(
299 post_push_extension = ExtensionCallback(
295 hook_name='PUSH_HOOK',
300 hook_name='PUSH_HOOK',
296 kwargs_keys=(
301 kwargs_keys=(
297 'server_url', 'config', 'scm', 'username', 'ip', 'action',
302 'server_url', 'config', 'scm', 'username', 'ip', 'action',
298 'repository', 'repo_store_path', 'pushed_revs'))
303 'repository', 'repo_store_path', 'pushed_revs'))
299
304
300
305
301 pre_create_user = ExtensionCallback(
306 pre_create_user = ExtensionCallback(
302 hook_name='PRE_CREATE_USER_HOOK',
307 hook_name='PRE_CREATE_USER_HOOK',
303 kwargs_keys=(
308 kwargs_keys=(
304 'username', 'password', 'email', 'firstname', 'lastname', 'active',
309 'username', 'password', 'email', 'firstname', 'lastname', 'active',
305 'admin', 'created_by'))
310 'admin', 'created_by'))
306
311
307
312
308 log_create_pull_request = ExtensionCallback(
313 log_create_pull_request = ExtensionCallback(
309 hook_name='CREATE_PULL_REQUEST',
314 hook_name='CREATE_PULL_REQUEST',
310 kwargs_keys=(
315 kwargs_keys=(
311 'server_url', 'config', 'scm', 'username', 'ip', 'action',
316 'server_url', 'config', 'scm', 'username', 'ip', 'action',
312 'repository', 'pull_request_id', 'url', 'title', 'description',
317 'repository', 'pull_request_id', 'url', 'title', 'description',
313 'status', 'created_on', 'updated_on', 'commit_ids', 'review_status',
318 'status', 'created_on', 'updated_on', 'commit_ids', 'review_status',
314 'mergeable', 'source', 'target', 'author', 'reviewers'))
319 'mergeable', 'source', 'target', 'author', 'reviewers'))
315
320
316
321
317 log_merge_pull_request = ExtensionCallback(
322 log_merge_pull_request = ExtensionCallback(
318 hook_name='MERGE_PULL_REQUEST',
323 hook_name='MERGE_PULL_REQUEST',
319 kwargs_keys=(
324 kwargs_keys=(
320 'server_url', 'config', 'scm', 'username', 'ip', 'action',
325 'server_url', 'config', 'scm', 'username', 'ip', 'action',
321 'repository', 'pull_request_id', 'url', 'title', 'description',
326 'repository', 'pull_request_id', 'url', 'title', 'description',
322 'status', 'created_on', 'updated_on', 'commit_ids', 'review_status',
327 'status', 'created_on', 'updated_on', 'commit_ids', 'review_status',
323 'mergeable', 'source', 'target', 'author', 'reviewers'))
328 'mergeable', 'source', 'target', 'author', 'reviewers'))
324
329
325
330
326 log_close_pull_request = ExtensionCallback(
331 log_close_pull_request = ExtensionCallback(
327 hook_name='CLOSE_PULL_REQUEST',
332 hook_name='CLOSE_PULL_REQUEST',
328 kwargs_keys=(
333 kwargs_keys=(
329 'server_url', 'config', 'scm', 'username', 'ip', 'action',
334 'server_url', 'config', 'scm', 'username', 'ip', 'action',
330 'repository', 'pull_request_id', 'url', 'title', 'description',
335 'repository', 'pull_request_id', 'url', 'title', 'description',
331 'status', 'created_on', 'updated_on', 'commit_ids', 'review_status',
336 'status', 'created_on', 'updated_on', 'commit_ids', 'review_status',
332 'mergeable', 'source', 'target', 'author', 'reviewers'))
337 'mergeable', 'source', 'target', 'author', 'reviewers'))
333
338
334
339
335 log_review_pull_request = ExtensionCallback(
340 log_review_pull_request = ExtensionCallback(
336 hook_name='REVIEW_PULL_REQUEST',
341 hook_name='REVIEW_PULL_REQUEST',
337 kwargs_keys=(
342 kwargs_keys=(
338 'server_url', 'config', 'scm', 'username', 'ip', 'action',
343 'server_url', 'config', 'scm', 'username', 'ip', 'action',
339 'repository', 'pull_request_id', 'url', 'title', 'description',
344 'repository', 'pull_request_id', 'url', 'title', 'description',
340 'status', 'created_on', 'updated_on', 'commit_ids', 'review_status',
345 'status', 'created_on', 'updated_on', 'commit_ids', 'review_status',
341 'mergeable', 'source', 'target', 'author', 'reviewers'))
346 'mergeable', 'source', 'target', 'author', 'reviewers'))
342
347
343
348
344 log_update_pull_request = ExtensionCallback(
349 log_update_pull_request = ExtensionCallback(
345 hook_name='UPDATE_PULL_REQUEST',
350 hook_name='UPDATE_PULL_REQUEST',
346 kwargs_keys=(
351 kwargs_keys=(
347 'server_url', 'config', 'scm', 'username', 'ip', 'action',
352 'server_url', 'config', 'scm', 'username', 'ip', 'action',
348 'repository', 'pull_request_id', 'url', 'title', 'description',
353 'repository', 'pull_request_id', 'url', 'title', 'description',
349 'status', 'created_on', 'updated_on', 'commit_ids', 'review_status',
354 'status', 'created_on', 'updated_on', 'commit_ids', 'review_status',
350 'mergeable', 'source', 'target', 'author', 'reviewers'))
355 'mergeable', 'source', 'target', 'author', 'reviewers'))
351
356
352
357
353 log_create_user = ExtensionCallback(
358 log_create_user = ExtensionCallback(
354 hook_name='CREATE_USER_HOOK',
359 hook_name='CREATE_USER_HOOK',
355 kwargs_keys=(
360 kwargs_keys=(
356 'username', 'full_name_or_username', 'full_contact', 'user_id',
361 'username', 'full_name_or_username', 'full_contact', 'user_id',
357 'name', 'firstname', 'short_contact', 'admin', 'lastname',
362 'name', 'firstname', 'short_contact', 'admin', 'lastname',
358 'ip_addresses', 'extern_type', 'extern_name',
363 'ip_addresses', 'extern_type', 'extern_name',
359 'email', 'api_key', 'api_keys', 'last_login',
364 'email', 'api_keys', 'last_login',
360 'full_name', 'active', 'password', 'emails',
365 'full_name', 'active', 'password', 'emails',
361 'inherit_default_permissions', 'created_by', 'created_on'))
366 'inherit_default_permissions', 'created_by', 'created_on'))
362
367
363
368
364 log_delete_user = ExtensionCallback(
369 log_delete_user = ExtensionCallback(
365 hook_name='DELETE_USER_HOOK',
370 hook_name='DELETE_USER_HOOK',
366 kwargs_keys=(
371 kwargs_keys=(
367 'username', 'full_name_or_username', 'full_contact', 'user_id',
372 'username', 'full_name_or_username', 'full_contact', 'user_id',
368 'name', 'firstname', 'short_contact', 'admin', 'lastname',
373 'name', 'firstname', 'short_contact', 'admin', 'lastname',
369 'ip_addresses',
374 'ip_addresses',
370 'email', 'api_key', 'last_login',
375 'email', 'last_login',
371 'full_name', 'active', 'password', 'emails',
376 'full_name', 'active', 'password', 'emails',
372 'inherit_default_permissions', 'deleted_by'))
377 'inherit_default_permissions', 'deleted_by'))
373
378
374
379
375 log_create_repository = ExtensionCallback(
380 log_create_repository = ExtensionCallback(
376 hook_name='CREATE_REPO_HOOK',
381 hook_name='CREATE_REPO_HOOK',
377 kwargs_keys=(
382 kwargs_keys=(
378 'repo_name', 'repo_type', 'description', 'private', 'created_on',
383 'repo_name', 'repo_type', 'description', 'private', 'created_on',
379 'enable_downloads', 'repo_id', 'user_id', 'enable_statistics',
384 'enable_downloads', 'repo_id', 'user_id', 'enable_statistics',
380 'clone_uri', 'fork_id', 'group_id', 'created_by'))
385 'clone_uri', 'fork_id', 'group_id', 'created_by'))
381
386
382
387
383 log_delete_repository = ExtensionCallback(
388 log_delete_repository = ExtensionCallback(
384 hook_name='DELETE_REPO_HOOK',
389 hook_name='DELETE_REPO_HOOK',
385 kwargs_keys=(
390 kwargs_keys=(
386 'repo_name', 'repo_type', 'description', 'private', 'created_on',
391 'repo_name', 'repo_type', 'description', 'private', 'created_on',
387 'enable_downloads', 'repo_id', 'user_id', 'enable_statistics',
392 'enable_downloads', 'repo_id', 'user_id', 'enable_statistics',
388 'clone_uri', 'fork_id', 'group_id', 'deleted_by', 'deleted_on'))
393 'clone_uri', 'fork_id', 'group_id', 'deleted_by', 'deleted_on'))
389
394
390
395
391 log_create_repository_group = ExtensionCallback(
396 log_create_repository_group = ExtensionCallback(
392 hook_name='CREATE_REPO_GROUP_HOOK',
397 hook_name='CREATE_REPO_GROUP_HOOK',
393 kwargs_keys=(
398 kwargs_keys=(
394 'group_name', 'group_parent_id', 'group_description',
399 'group_name', 'group_parent_id', 'group_description',
395 'group_id', 'user_id', 'created_by', 'created_on',
400 'group_id', 'user_id', 'created_by', 'created_on',
396 'enable_locking'))
401 'enable_locking'))
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
@@ -1,331 +1,337 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 """
21 """
22 Helpers for fixture generation
22 Helpers for fixture generation
23 """
23 """
24
24
25 import os
25 import os
26 import time
26 import time
27 import tempfile
27 import tempfile
28 import shutil
28 import shutil
29
29
30 import configobj
30 import configobj
31
31
32 from rhodecode.tests import *
32 from rhodecode.tests import *
33 from rhodecode.model.db import Repository, User, RepoGroup, UserGroup, Gist
33 from rhodecode.model.db import Repository, User, RepoGroup, UserGroup, Gist
34 from rhodecode.model.meta import Session
34 from rhodecode.model.meta import Session
35 from rhodecode.model.repo import RepoModel
35 from rhodecode.model.repo import RepoModel
36 from rhodecode.model.user import UserModel
36 from rhodecode.model.user import UserModel
37 from rhodecode.model.repo_group import RepoGroupModel
37 from rhodecode.model.repo_group import RepoGroupModel
38 from rhodecode.model.user_group import UserGroupModel
38 from rhodecode.model.user_group import UserGroupModel
39 from rhodecode.model.gist import GistModel
39 from rhodecode.model.gist import GistModel
40 from rhodecode.model.auth_token import AuthTokenModel
40
41
41 dn = os.path.dirname
42 dn = os.path.dirname
42 FIXTURES = os.path.join(dn(dn(os.path.abspath(__file__))), 'tests', 'fixtures')
43 FIXTURES = os.path.join(dn(dn(os.path.abspath(__file__))), 'tests', 'fixtures')
43
44
44
45
45 def error_function(*args, **kwargs):
46 def error_function(*args, **kwargs):
46 raise Exception('Total Crash !')
47 raise Exception('Total Crash !')
47
48
48
49
49 class TestINI(object):
50 class TestINI(object):
50 """
51 """
51 Allows to create a new test.ini file as a copy of existing one with edited
52 Allows to create a new test.ini file as a copy of existing one with edited
52 data. Example usage::
53 data. Example usage::
53
54
54 with TestINI('test.ini', [{'section':{'key':val'}]) as new_test_ini_path:
55 with TestINI('test.ini', [{'section':{'key':val'}]) as new_test_ini_path:
55 print 'paster server %s' % new_test_ini
56 print 'paster server %s' % new_test_ini
56 """
57 """
57
58
58 def __init__(self, ini_file_path, ini_params, new_file_prefix='DEFAULT',
59 def __init__(self, ini_file_path, ini_params, new_file_prefix='DEFAULT',
59 destroy=True, dir=None):
60 destroy=True, dir=None):
60 self.ini_file_path = ini_file_path
61 self.ini_file_path = ini_file_path
61 self.ini_params = ini_params
62 self.ini_params = ini_params
62 self.new_path = None
63 self.new_path = None
63 self.new_path_prefix = new_file_prefix
64 self.new_path_prefix = new_file_prefix
64 self._destroy = destroy
65 self._destroy = destroy
65 self._dir = dir
66 self._dir = dir
66
67
67 def __enter__(self):
68 def __enter__(self):
68 return self.create()
69 return self.create()
69
70
70 def __exit__(self, exc_type, exc_val, exc_tb):
71 def __exit__(self, exc_type, exc_val, exc_tb):
71 self.destroy()
72 self.destroy()
72
73
73 def create(self):
74 def create(self):
74 config = configobj.ConfigObj(
75 config = configobj.ConfigObj(
75 self.ini_file_path, file_error=True, write_empty_values=True)
76 self.ini_file_path, file_error=True, write_empty_values=True)
76
77
77 for data in self.ini_params:
78 for data in self.ini_params:
78 section, ini_params = data.items()[0]
79 section, ini_params = data.items()[0]
79 for key, val in ini_params.items():
80 for key, val in ini_params.items():
80 config[section][key] = val
81 config[section][key] = val
81 with tempfile.NamedTemporaryFile(
82 with tempfile.NamedTemporaryFile(
82 prefix=self.new_path_prefix, suffix='.ini', dir=self._dir,
83 prefix=self.new_path_prefix, suffix='.ini', dir=self._dir,
83 delete=False) as new_ini_file:
84 delete=False) as new_ini_file:
84 config.write(new_ini_file)
85 config.write(new_ini_file)
85 self.new_path = new_ini_file.name
86 self.new_path = new_ini_file.name
86
87
87 return self.new_path
88 return self.new_path
88
89
89 def destroy(self):
90 def destroy(self):
90 if self._destroy:
91 if self._destroy:
91 os.remove(self.new_path)
92 os.remove(self.new_path)
92
93
93
94
94 class Fixture(object):
95 class Fixture(object):
95
96
96 def anon_access(self, status):
97 def anon_access(self, status):
97 """
98 """
98 Context process for disabling anonymous access. use like:
99 Context process for disabling anonymous access. use like:
99 fixture = Fixture()
100 fixture = Fixture()
100 with fixture.anon_access(False):
101 with fixture.anon_access(False):
101 #tests
102 #tests
102
103
103 after this block anon access will be set to `not status`
104 after this block anon access will be set to `not status`
104 """
105 """
105
106
106 class context(object):
107 class context(object):
107 def __enter__(self):
108 def __enter__(self):
108 anon = User.get_default_user()
109 anon = User.get_default_user()
109 anon.active = status
110 anon.active = status
110 Session().add(anon)
111 Session().add(anon)
111 Session().commit()
112 Session().commit()
112 time.sleep(1.5) # must sleep for cache (1s to expire)
113 time.sleep(1.5) # must sleep for cache (1s to expire)
113
114
114 def __exit__(self, exc_type, exc_val, exc_tb):
115 def __exit__(self, exc_type, exc_val, exc_tb):
115 anon = User.get_default_user()
116 anon = User.get_default_user()
116 anon.active = not status
117 anon.active = not status
117 Session().add(anon)
118 Session().add(anon)
118 Session().commit()
119 Session().commit()
119
120
120 return context()
121 return context()
121
122
122 def _get_repo_create_params(self, **custom):
123 def _get_repo_create_params(self, **custom):
123 defs = {
124 defs = {
124 'repo_name': None,
125 'repo_name': None,
125 'repo_type': 'hg',
126 'repo_type': 'hg',
126 'clone_uri': '',
127 'clone_uri': '',
127 'repo_group': '-1',
128 'repo_group': '-1',
128 'repo_description': 'DESC',
129 'repo_description': 'DESC',
129 'repo_private': False,
130 'repo_private': False,
130 'repo_landing_rev': 'rev:tip',
131 'repo_landing_rev': 'rev:tip',
131 'repo_copy_permissions': False,
132 'repo_copy_permissions': False,
132 'repo_state': Repository.STATE_CREATED,
133 'repo_state': Repository.STATE_CREATED,
133 }
134 }
134 defs.update(custom)
135 defs.update(custom)
135 if 'repo_name_full' not in custom:
136 if 'repo_name_full' not in custom:
136 defs.update({'repo_name_full': defs['repo_name']})
137 defs.update({'repo_name_full': defs['repo_name']})
137
138
138 # fix the repo name if passed as repo_name_full
139 # fix the repo name if passed as repo_name_full
139 if defs['repo_name']:
140 if defs['repo_name']:
140 defs['repo_name'] = defs['repo_name'].split('/')[-1]
141 defs['repo_name'] = defs['repo_name'].split('/')[-1]
141
142
142 return defs
143 return defs
143
144
144 def _get_group_create_params(self, **custom):
145 def _get_group_create_params(self, **custom):
145 defs = {
146 defs = {
146 'group_name': None,
147 'group_name': None,
147 'group_description': 'DESC',
148 'group_description': 'DESC',
148 'perm_updates': [],
149 'perm_updates': [],
149 'perm_additions': [],
150 'perm_additions': [],
150 'perm_deletions': [],
151 'perm_deletions': [],
151 'group_parent_id': -1,
152 'group_parent_id': -1,
152 'enable_locking': False,
153 'enable_locking': False,
153 'recursive': False,
154 'recursive': False,
154 }
155 }
155 defs.update(custom)
156 defs.update(custom)
156
157
157 return defs
158 return defs
158
159
159 def _get_user_create_params(self, name, **custom):
160 def _get_user_create_params(self, name, **custom):
160 defs = {
161 defs = {
161 'username': name,
162 'username': name,
162 'password': 'qweqwe',
163 'password': 'qweqwe',
163 'email': '%s+test@rhodecode.org' % name,
164 'email': '%s+test@rhodecode.org' % name,
164 'firstname': 'TestUser',
165 'firstname': 'TestUser',
165 'lastname': 'Test',
166 'lastname': 'Test',
166 'active': True,
167 'active': True,
167 'admin': False,
168 'admin': False,
168 'extern_type': 'rhodecode',
169 'extern_type': 'rhodecode',
169 'extern_name': None,
170 'extern_name': None,
170 }
171 }
171 defs.update(custom)
172 defs.update(custom)
172
173
173 return defs
174 return defs
174
175
175 def _get_user_group_create_params(self, name, **custom):
176 def _get_user_group_create_params(self, name, **custom):
176 defs = {
177 defs = {
177 'users_group_name': name,
178 'users_group_name': name,
178 'user_group_description': 'DESC',
179 'user_group_description': 'DESC',
179 'users_group_active': True,
180 'users_group_active': True,
180 'user_group_data': {},
181 'user_group_data': {},
181 }
182 }
182 defs.update(custom)
183 defs.update(custom)
183
184
184 return defs
185 return defs
185
186
186 def create_repo(self, name, **kwargs):
187 def create_repo(self, name, **kwargs):
187 repo_group = kwargs.get('repo_group')
188 repo_group = kwargs.get('repo_group')
188 if isinstance(repo_group, RepoGroup):
189 if isinstance(repo_group, RepoGroup):
189 kwargs['repo_group'] = repo_group.group_id
190 kwargs['repo_group'] = repo_group.group_id
190 name = name.split(Repository.NAME_SEP)[-1]
191 name = name.split(Repository.NAME_SEP)[-1]
191 name = Repository.NAME_SEP.join((repo_group.group_name, name))
192 name = Repository.NAME_SEP.join((repo_group.group_name, name))
192
193
193 if 'skip_if_exists' in kwargs:
194 if 'skip_if_exists' in kwargs:
194 del kwargs['skip_if_exists']
195 del kwargs['skip_if_exists']
195 r = Repository.get_by_repo_name(name)
196 r = Repository.get_by_repo_name(name)
196 if r:
197 if r:
197 return r
198 return r
198
199
199 form_data = self._get_repo_create_params(repo_name=name, **kwargs)
200 form_data = self._get_repo_create_params(repo_name=name, **kwargs)
200 cur_user = kwargs.get('cur_user', TEST_USER_ADMIN_LOGIN)
201 cur_user = kwargs.get('cur_user', TEST_USER_ADMIN_LOGIN)
201 RepoModel().create(form_data, cur_user)
202 RepoModel().create(form_data, cur_user)
202 Session().commit()
203 Session().commit()
203 repo = Repository.get_by_repo_name(name)
204 repo = Repository.get_by_repo_name(name)
204 assert repo
205 assert repo
205 return repo
206 return repo
206
207
207 def create_fork(self, repo_to_fork, fork_name, **kwargs):
208 def create_fork(self, repo_to_fork, fork_name, **kwargs):
208 repo_to_fork = Repository.get_by_repo_name(repo_to_fork)
209 repo_to_fork = Repository.get_by_repo_name(repo_to_fork)
209
210
210 form_data = self._get_repo_create_params(repo_name=fork_name,
211 form_data = self._get_repo_create_params(repo_name=fork_name,
211 fork_parent_id=repo_to_fork.repo_id,
212 fork_parent_id=repo_to_fork.repo_id,
212 repo_type=repo_to_fork.repo_type,
213 repo_type=repo_to_fork.repo_type,
213 **kwargs)
214 **kwargs)
214 #TODO: fix it !!
215 #TODO: fix it !!
215 form_data['description'] = form_data['repo_description']
216 form_data['description'] = form_data['repo_description']
216 form_data['private'] = form_data['repo_private']
217 form_data['private'] = form_data['repo_private']
217 form_data['landing_rev'] = form_data['repo_landing_rev']
218 form_data['landing_rev'] = form_data['repo_landing_rev']
218
219
219 owner = kwargs.get('cur_user', TEST_USER_ADMIN_LOGIN)
220 owner = kwargs.get('cur_user', TEST_USER_ADMIN_LOGIN)
220 RepoModel().create_fork(form_data, cur_user=owner)
221 RepoModel().create_fork(form_data, cur_user=owner)
221 Session().commit()
222 Session().commit()
222 r = Repository.get_by_repo_name(fork_name)
223 r = Repository.get_by_repo_name(fork_name)
223 assert r
224 assert r
224 return r
225 return r
225
226
226 def destroy_repo(self, repo_name, **kwargs):
227 def destroy_repo(self, repo_name, **kwargs):
227 RepoModel().delete(repo_name, **kwargs)
228 RepoModel().delete(repo_name, **kwargs)
228 Session().commit()
229 Session().commit()
229
230
230 def destroy_repo_on_filesystem(self, repo_name):
231 def destroy_repo_on_filesystem(self, repo_name):
231 rm_path = os.path.join(RepoModel().repos_path, repo_name)
232 rm_path = os.path.join(RepoModel().repos_path, repo_name)
232 if os.path.isdir(rm_path):
233 if os.path.isdir(rm_path):
233 shutil.rmtree(rm_path)
234 shutil.rmtree(rm_path)
234
235
235 def create_repo_group(self, name, **kwargs):
236 def create_repo_group(self, name, **kwargs):
236 if 'skip_if_exists' in kwargs:
237 if 'skip_if_exists' in kwargs:
237 del kwargs['skip_if_exists']
238 del kwargs['skip_if_exists']
238 gr = RepoGroup.get_by_group_name(group_name=name)
239 gr = RepoGroup.get_by_group_name(group_name=name)
239 if gr:
240 if gr:
240 return gr
241 return gr
241 form_data = self._get_group_create_params(group_name=name, **kwargs)
242 form_data = self._get_group_create_params(group_name=name, **kwargs)
242 owner = kwargs.get('cur_user', TEST_USER_ADMIN_LOGIN)
243 owner = kwargs.get('cur_user', TEST_USER_ADMIN_LOGIN)
243 gr = RepoGroupModel().create(
244 gr = RepoGroupModel().create(
244 group_name=form_data['group_name'],
245 group_name=form_data['group_name'],
245 group_description=form_data['group_name'],
246 group_description=form_data['group_name'],
246 owner=owner)
247 owner=owner)
247 Session().commit()
248 Session().commit()
248 gr = RepoGroup.get_by_group_name(gr.group_name)
249 gr = RepoGroup.get_by_group_name(gr.group_name)
249 return gr
250 return gr
250
251
251 def destroy_repo_group(self, repogroupid):
252 def destroy_repo_group(self, repogroupid):
252 RepoGroupModel().delete(repogroupid)
253 RepoGroupModel().delete(repogroupid)
253 Session().commit()
254 Session().commit()
254
255
255 def create_user(self, name, **kwargs):
256 def create_user(self, name, **kwargs):
256 if 'skip_if_exists' in kwargs:
257 if 'skip_if_exists' in kwargs:
257 del kwargs['skip_if_exists']
258 del kwargs['skip_if_exists']
258 user = User.get_by_username(name)
259 user = User.get_by_username(name)
259 if user:
260 if user:
260 return user
261 return user
261 form_data = self._get_user_create_params(name, **kwargs)
262 form_data = self._get_user_create_params(name, **kwargs)
262 user = UserModel().create(form_data)
263 user = UserModel().create(form_data)
264
265 # create token for user
266 AuthTokenModel().create(
267 user=user, description='TEST_USER_TOKEN')
268
263 Session().commit()
269 Session().commit()
264 user = User.get_by_username(user.username)
270 user = User.get_by_username(user.username)
265 return user
271 return user
266
272
267 def destroy_user(self, userid):
273 def destroy_user(self, userid):
268 UserModel().delete(userid)
274 UserModel().delete(userid)
269 Session().commit()
275 Session().commit()
270
276
271 def destroy_users(self, userid_iter):
277 def destroy_users(self, userid_iter):
272 for user_id in userid_iter:
278 for user_id in userid_iter:
273 if User.get_by_username(user_id):
279 if User.get_by_username(user_id):
274 UserModel().delete(user_id)
280 UserModel().delete(user_id)
275 Session().commit()
281 Session().commit()
276
282
277 def create_user_group(self, name, **kwargs):
283 def create_user_group(self, name, **kwargs):
278 if 'skip_if_exists' in kwargs:
284 if 'skip_if_exists' in kwargs:
279 del kwargs['skip_if_exists']
285 del kwargs['skip_if_exists']
280 gr = UserGroup.get_by_group_name(group_name=name)
286 gr = UserGroup.get_by_group_name(group_name=name)
281 if gr:
287 if gr:
282 return gr
288 return gr
283 form_data = self._get_user_group_create_params(name, **kwargs)
289 form_data = self._get_user_group_create_params(name, **kwargs)
284 owner = kwargs.get('cur_user', TEST_USER_ADMIN_LOGIN)
290 owner = kwargs.get('cur_user', TEST_USER_ADMIN_LOGIN)
285 user_group = UserGroupModel().create(
291 user_group = UserGroupModel().create(
286 name=form_data['users_group_name'],
292 name=form_data['users_group_name'],
287 description=form_data['user_group_description'],
293 description=form_data['user_group_description'],
288 owner=owner, active=form_data['users_group_active'],
294 owner=owner, active=form_data['users_group_active'],
289 group_data=form_data['user_group_data'])
295 group_data=form_data['user_group_data'])
290 Session().commit()
296 Session().commit()
291 user_group = UserGroup.get_by_group_name(user_group.users_group_name)
297 user_group = UserGroup.get_by_group_name(user_group.users_group_name)
292 return user_group
298 return user_group
293
299
294 def destroy_user_group(self, usergroupid):
300 def destroy_user_group(self, usergroupid):
295 UserGroupModel().delete(user_group=usergroupid, force=True)
301 UserGroupModel().delete(user_group=usergroupid, force=True)
296 Session().commit()
302 Session().commit()
297
303
298 def create_gist(self, **kwargs):
304 def create_gist(self, **kwargs):
299 form_data = {
305 form_data = {
300 'description': 'new-gist',
306 'description': 'new-gist',
301 'owner': TEST_USER_ADMIN_LOGIN,
307 'owner': TEST_USER_ADMIN_LOGIN,
302 'gist_type': GistModel.cls.GIST_PUBLIC,
308 'gist_type': GistModel.cls.GIST_PUBLIC,
303 'lifetime': -1,
309 'lifetime': -1,
304 'acl_level': Gist.ACL_LEVEL_PUBLIC,
310 'acl_level': Gist.ACL_LEVEL_PUBLIC,
305 'gist_mapping': {'filename1.txt': {'content': 'hello world'},}
311 'gist_mapping': {'filename1.txt': {'content': 'hello world'},}
306 }
312 }
307 form_data.update(kwargs)
313 form_data.update(kwargs)
308 gist = GistModel().create(
314 gist = GistModel().create(
309 description=form_data['description'], owner=form_data['owner'],
315 description=form_data['description'], owner=form_data['owner'],
310 gist_mapping=form_data['gist_mapping'], gist_type=form_data['gist_type'],
316 gist_mapping=form_data['gist_mapping'], gist_type=form_data['gist_type'],
311 lifetime=form_data['lifetime'], gist_acl_level=form_data['acl_level']
317 lifetime=form_data['lifetime'], gist_acl_level=form_data['acl_level']
312 )
318 )
313 Session().commit()
319 Session().commit()
314 return gist
320 return gist
315
321
316 def destroy_gists(self, gistid=None):
322 def destroy_gists(self, gistid=None):
317 for g in GistModel.cls.get_all():
323 for g in GistModel.cls.get_all():
318 if gistid:
324 if gistid:
319 if gistid == g.gist_access_id:
325 if gistid == g.gist_access_id:
320 GistModel().delete(g)
326 GistModel().delete(g)
321 else:
327 else:
322 GistModel().delete(g)
328 GistModel().delete(g)
323 Session().commit()
329 Session().commit()
324
330
325 def load_resource(self, resource_name, strip=False):
331 def load_resource(self, resource_name, strip=False):
326 with open(os.path.join(FIXTURES, resource_name)) as f:
332 with open(os.path.join(FIXTURES, resource_name)) as f:
327 source = f.read()
333 source = f.read()
328 if strip:
334 if strip:
329 source = source.strip()
335 source = source.strip()
330
336
331 return source
337 return source
@@ -1,384 +1,383 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import pytest
21 import pytest
22
22
23 from rhodecode.lib import helpers as h
23 from rhodecode.lib import helpers as h
24 from rhodecode.lib.auth import check_password
24 from rhodecode.lib.auth import check_password
25 from rhodecode.model.db import User, UserFollowing, Repository, UserApiKeys
25 from rhodecode.model.db import User, UserFollowing, Repository, UserApiKeys
26 from rhodecode.model.meta import Session
26 from rhodecode.model.meta import Session
27 from rhodecode.tests import (
27 from rhodecode.tests import (
28 TestController, url, TEST_USER_ADMIN_LOGIN, TEST_USER_REGULAR_EMAIL,
28 TestController, url, TEST_USER_ADMIN_LOGIN, TEST_USER_REGULAR_EMAIL,
29 assert_session_flash)
29 assert_session_flash)
30 from rhodecode.tests.fixture import Fixture
30 from rhodecode.tests.fixture import Fixture
31 from rhodecode.tests.utils import AssertResponse
31 from rhodecode.tests.utils import AssertResponse
32
32
33 fixture = Fixture()
33 fixture = Fixture()
34
34
35
35
36 class TestMyAccountController(TestController):
36 class TestMyAccountController(TestController):
37 test_user_1 = 'testme'
37 test_user_1 = 'testme'
38 test_user_1_password = '0jd83nHNS/d23n'
38 test_user_1_password = '0jd83nHNS/d23n'
39 destroy_users = set()
39 destroy_users = set()
40
40
41 @classmethod
41 @classmethod
42 def teardown_class(cls):
42 def teardown_class(cls):
43 fixture.destroy_users(cls.destroy_users)
43 fixture.destroy_users(cls.destroy_users)
44
44
45 def test_my_account(self):
45 def test_my_account(self):
46 self.log_user()
46 self.log_user()
47 response = self.app.get(url('my_account'))
47 response = self.app.get(url('my_account'))
48
48
49 response.mustcontain('test_admin')
49 response.mustcontain('test_admin')
50 response.mustcontain('href="/_admin/my_account/edit"')
50 response.mustcontain('href="/_admin/my_account/edit"')
51
51
52 def test_logout_form_contains_csrf(self, autologin_user, csrf_token):
52 def test_logout_form_contains_csrf(self, autologin_user, csrf_token):
53 response = self.app.get(url('my_account'))
53 response = self.app.get(url('my_account'))
54 assert_response = AssertResponse(response)
54 assert_response = AssertResponse(response)
55 element = assert_response.get_element('.logout #csrf_token')
55 element = assert_response.get_element('.logout #csrf_token')
56 assert element.value == csrf_token
56 assert element.value == csrf_token
57
57
58 def test_my_account_edit(self):
58 def test_my_account_edit(self):
59 self.log_user()
59 self.log_user()
60 response = self.app.get(url('my_account_edit'))
60 response = self.app.get(url('my_account_edit'))
61
61
62 response.mustcontain('value="test_admin')
62 response.mustcontain('value="test_admin')
63
63
64 def test_my_account_my_repos(self):
64 def test_my_account_my_repos(self):
65 self.log_user()
65 self.log_user()
66 response = self.app.get(url('my_account_repos'))
66 response = self.app.get(url('my_account_repos'))
67 repos = Repository.query().filter(
67 repos = Repository.query().filter(
68 Repository.user == User.get_by_username(
68 Repository.user == User.get_by_username(
69 TEST_USER_ADMIN_LOGIN)).all()
69 TEST_USER_ADMIN_LOGIN)).all()
70 for repo in repos:
70 for repo in repos:
71 response.mustcontain('"name_raw": "%s"' % repo.repo_name)
71 response.mustcontain('"name_raw": "%s"' % repo.repo_name)
72
72
73 def test_my_account_my_watched(self):
73 def test_my_account_my_watched(self):
74 self.log_user()
74 self.log_user()
75 response = self.app.get(url('my_account_watched'))
75 response = self.app.get(url('my_account_watched'))
76
76
77 repos = UserFollowing.query().filter(
77 repos = UserFollowing.query().filter(
78 UserFollowing.user == User.get_by_username(
78 UserFollowing.user == User.get_by_username(
79 TEST_USER_ADMIN_LOGIN)).all()
79 TEST_USER_ADMIN_LOGIN)).all()
80 for repo in repos:
80 for repo in repos:
81 response.mustcontain(
81 response.mustcontain(
82 '"name_raw": "%s"' % repo.follows_repository.repo_name)
82 '"name_raw": "%s"' % repo.follows_repository.repo_name)
83
83
84 @pytest.mark.backends("git", "hg")
84 @pytest.mark.backends("git", "hg")
85 def test_my_account_my_pullrequests(self, pr_util):
85 def test_my_account_my_pullrequests(self, pr_util):
86 self.log_user()
86 self.log_user()
87 response = self.app.get(url('my_account_pullrequests'))
87 response = self.app.get(url('my_account_pullrequests'))
88 response.mustcontain('There are currently no open pull '
88 response.mustcontain('There are currently no open pull '
89 'requests requiring your participation.')
89 'requests requiring your participation.')
90
90
91 pr = pr_util.create_pull_request(title='TestMyAccountPR')
91 pr = pr_util.create_pull_request(title='TestMyAccountPR')
92 response = self.app.get(url('my_account_pullrequests'))
92 response = self.app.get(url('my_account_pullrequests'))
93 response.mustcontain('"name_raw": %s' % pr.pull_request_id)
93 response.mustcontain('"name_raw": %s' % pr.pull_request_id)
94 response.mustcontain('TestMyAccountPR')
94 response.mustcontain('TestMyAccountPR')
95
95
96 def test_my_account_my_emails(self):
96 def test_my_account_my_emails(self):
97 self.log_user()
97 self.log_user()
98 response = self.app.get(url('my_account_emails'))
98 response = self.app.get(url('my_account_emails'))
99 response.mustcontain('No additional emails specified')
99 response.mustcontain('No additional emails specified')
100
100
101 def test_my_account_my_emails_add_existing_email(self):
101 def test_my_account_my_emails_add_existing_email(self):
102 self.log_user()
102 self.log_user()
103 response = self.app.get(url('my_account_emails'))
103 response = self.app.get(url('my_account_emails'))
104 response.mustcontain('No additional emails specified')
104 response.mustcontain('No additional emails specified')
105 response = self.app.post(url('my_account_emails'),
105 response = self.app.post(url('my_account_emails'),
106 {'new_email': TEST_USER_REGULAR_EMAIL,
106 {'new_email': TEST_USER_REGULAR_EMAIL,
107 'csrf_token': self.csrf_token})
107 'csrf_token': self.csrf_token})
108 assert_session_flash(response, 'This e-mail address is already taken')
108 assert_session_flash(response, 'This e-mail address is already taken')
109
109
110 def test_my_account_my_emails_add_mising_email_in_form(self):
110 def test_my_account_my_emails_add_mising_email_in_form(self):
111 self.log_user()
111 self.log_user()
112 response = self.app.get(url('my_account_emails'))
112 response = self.app.get(url('my_account_emails'))
113 response.mustcontain('No additional emails specified')
113 response.mustcontain('No additional emails specified')
114 response = self.app.post(url('my_account_emails'),
114 response = self.app.post(url('my_account_emails'),
115 {'csrf_token': self.csrf_token})
115 {'csrf_token': self.csrf_token})
116 assert_session_flash(response, 'Please enter an email address')
116 assert_session_flash(response, 'Please enter an email address')
117
117
118 def test_my_account_my_emails_add_remove(self):
118 def test_my_account_my_emails_add_remove(self):
119 self.log_user()
119 self.log_user()
120 response = self.app.get(url('my_account_emails'))
120 response = self.app.get(url('my_account_emails'))
121 response.mustcontain('No additional emails specified')
121 response.mustcontain('No additional emails specified')
122
122
123 response = self.app.post(url('my_account_emails'),
123 response = self.app.post(url('my_account_emails'),
124 {'new_email': 'foo@barz.com',
124 {'new_email': 'foo@barz.com',
125 'csrf_token': self.csrf_token})
125 'csrf_token': self.csrf_token})
126
126
127 response = self.app.get(url('my_account_emails'))
127 response = self.app.get(url('my_account_emails'))
128
128
129 from rhodecode.model.db import UserEmailMap
129 from rhodecode.model.db import UserEmailMap
130 email_id = UserEmailMap.query().filter(
130 email_id = UserEmailMap.query().filter(
131 UserEmailMap.user == User.get_by_username(
131 UserEmailMap.user == User.get_by_username(
132 TEST_USER_ADMIN_LOGIN)).filter(
132 TEST_USER_ADMIN_LOGIN)).filter(
133 UserEmailMap.email == 'foo@barz.com').one().email_id
133 UserEmailMap.email == 'foo@barz.com').one().email_id
134
134
135 response.mustcontain('foo@barz.com')
135 response.mustcontain('foo@barz.com')
136 response.mustcontain('<input id="del_email_id" name="del_email_id" '
136 response.mustcontain('<input id="del_email_id" name="del_email_id" '
137 'type="hidden" value="%s" />' % email_id)
137 'type="hidden" value="%s" />' % email_id)
138
138
139 response = self.app.post(
139 response = self.app.post(
140 url('my_account_emails'), {
140 url('my_account_emails'), {
141 'del_email_id': email_id, '_method': 'delete',
141 'del_email_id': email_id, '_method': 'delete',
142 'csrf_token': self.csrf_token})
142 'csrf_token': self.csrf_token})
143 assert_session_flash(response, 'Removed email address from user account')
143 assert_session_flash(response, 'Removed email address from user account')
144 response = self.app.get(url('my_account_emails'))
144 response = self.app.get(url('my_account_emails'))
145 response.mustcontain('No additional emails specified')
145 response.mustcontain('No additional emails specified')
146
146
147 @pytest.mark.parametrize(
147 @pytest.mark.parametrize(
148 "name, attrs", [
148 "name, attrs", [
149 ('firstname', {'firstname': 'new_username'}),
149 ('firstname', {'firstname': 'new_username'}),
150 ('lastname', {'lastname': 'new_username'}),
150 ('lastname', {'lastname': 'new_username'}),
151 ('admin', {'admin': True}),
151 ('admin', {'admin': True}),
152 ('admin', {'admin': False}),
152 ('admin', {'admin': False}),
153 ('extern_type', {'extern_type': 'ldap'}),
153 ('extern_type', {'extern_type': 'ldap'}),
154 ('extern_type', {'extern_type': None}),
154 ('extern_type', {'extern_type': None}),
155 # ('extern_name', {'extern_name': 'test'}),
155 # ('extern_name', {'extern_name': 'test'}),
156 # ('extern_name', {'extern_name': None}),
156 # ('extern_name', {'extern_name': None}),
157 ('active', {'active': False}),
157 ('active', {'active': False}),
158 ('active', {'active': True}),
158 ('active', {'active': True}),
159 ('email', {'email': 'some@email.com'}),
159 ('email', {'email': 'some@email.com'}),
160 ])
160 ])
161 def test_my_account_update(self, name, attrs):
161 def test_my_account_update(self, name, attrs):
162 usr = fixture.create_user(self.test_user_1,
162 usr = fixture.create_user(self.test_user_1,
163 password=self.test_user_1_password,
163 password=self.test_user_1_password,
164 email='testme@rhodecode.org',
164 email='testme@rhodecode.org',
165 extern_type='rhodecode',
165 extern_type='rhodecode',
166 extern_name=self.test_user_1,
166 extern_name=self.test_user_1,
167 skip_if_exists=True)
167 skip_if_exists=True)
168 self.destroy_users.add(self.test_user_1)
168 self.destroy_users.add(self.test_user_1)
169
169
170 params = usr.get_api_data() # current user data
170 params = usr.get_api_data() # current user data
171 user_id = usr.user_id
171 user_id = usr.user_id
172 self.log_user(
172 self.log_user(
173 username=self.test_user_1, password=self.test_user_1_password)
173 username=self.test_user_1, password=self.test_user_1_password)
174
174
175 params.update({'password_confirmation': ''})
175 params.update({'password_confirmation': ''})
176 params.update({'new_password': ''})
176 params.update({'new_password': ''})
177 params.update({'extern_type': 'rhodecode'})
177 params.update({'extern_type': 'rhodecode'})
178 params.update({'extern_name': self.test_user_1})
178 params.update({'extern_name': self.test_user_1})
179 params.update({'csrf_token': self.csrf_token})
179 params.update({'csrf_token': self.csrf_token})
180
180
181 params.update(attrs)
181 params.update(attrs)
182 # my account page cannot set language param yet, only for admins
182 # my account page cannot set language param yet, only for admins
183 del params['language']
183 del params['language']
184 response = self.app.post(url('my_account'), params)
184 response = self.app.post(url('my_account'), params)
185
185
186 assert_session_flash(
186 assert_session_flash(
187 response, 'Your account was updated successfully')
187 response, 'Your account was updated successfully')
188
188
189 del params['csrf_token']
189 del params['csrf_token']
190
190
191 updated_user = User.get_by_username(self.test_user_1)
191 updated_user = User.get_by_username(self.test_user_1)
192 updated_params = updated_user.get_api_data()
192 updated_params = updated_user.get_api_data()
193 updated_params.update({'password_confirmation': ''})
193 updated_params.update({'password_confirmation': ''})
194 updated_params.update({'new_password': ''})
194 updated_params.update({'new_password': ''})
195
195
196 params['last_login'] = updated_params['last_login']
196 params['last_login'] = updated_params['last_login']
197 # my account page cannot set language param yet, only for admins
197 # my account page cannot set language param yet, only for admins
198 # but we get this info from API anyway
198 # but we get this info from API anyway
199 params['language'] = updated_params['language']
199 params['language'] = updated_params['language']
200
200
201 if name == 'email':
201 if name == 'email':
202 params['emails'] = [attrs['email']]
202 params['emails'] = [attrs['email']]
203 if name == 'extern_type':
203 if name == 'extern_type':
204 # cannot update this via form, expected value is original one
204 # cannot update this via form, expected value is original one
205 params['extern_type'] = "rhodecode"
205 params['extern_type'] = "rhodecode"
206 if name == 'extern_name':
206 if name == 'extern_name':
207 # cannot update this via form, expected value is original one
207 # cannot update this via form, expected value is original one
208 params['extern_name'] = str(user_id)
208 params['extern_name'] = str(user_id)
209 if name == 'active':
209 if name == 'active':
210 # my account cannot deactivate account
210 # my account cannot deactivate account
211 params['active'] = True
211 params['active'] = True
212 if name == 'admin':
212 if name == 'admin':
213 # my account cannot make you an admin !
213 # my account cannot make you an admin !
214 params['admin'] = False
214 params['admin'] = False
215
215
216 assert params == updated_params
216 assert params == updated_params
217
217
218 def test_my_account_update_err_email_exists(self):
218 def test_my_account_update_err_email_exists(self):
219 self.log_user()
219 self.log_user()
220
220
221 new_email = 'test_regular@mail.com' # already exisitn email
221 new_email = 'test_regular@mail.com' # already exisitn email
222 response = self.app.post(url('my_account'),
222 response = self.app.post(url('my_account'),
223 params={
223 params={
224 'username': 'test_admin',
224 'username': 'test_admin',
225 'new_password': 'test12',
225 'new_password': 'test12',
226 'password_confirmation': 'test122',
226 'password_confirmation': 'test122',
227 'firstname': 'NewName',
227 'firstname': 'NewName',
228 'lastname': 'NewLastname',
228 'lastname': 'NewLastname',
229 'email': new_email,
229 'email': new_email,
230 'csrf_token': self.csrf_token,
230 'csrf_token': self.csrf_token,
231 })
231 })
232
232
233 response.mustcontain('This e-mail address is already taken')
233 response.mustcontain('This e-mail address is already taken')
234
234
235 def test_my_account_update_err(self):
235 def test_my_account_update_err(self):
236 self.log_user('test_regular2', 'test12')
236 self.log_user('test_regular2', 'test12')
237
237
238 new_email = 'newmail.pl'
238 new_email = 'newmail.pl'
239 response = self.app.post(url('my_account'),
239 response = self.app.post(url('my_account'),
240 params={
240 params={
241 'username': 'test_admin',
241 'username': 'test_admin',
242 'new_password': 'test12',
242 'new_password': 'test12',
243 'password_confirmation': 'test122',
243 'password_confirmation': 'test122',
244 'firstname': 'NewName',
244 'firstname': 'NewName',
245 'lastname': 'NewLastname',
245 'lastname': 'NewLastname',
246 'email': new_email,
246 'email': new_email,
247 'csrf_token': self.csrf_token,
247 'csrf_token': self.csrf_token,
248 })
248 })
249
249
250 response.mustcontain('An email address must contain a single @')
250 response.mustcontain('An email address must contain a single @')
251 from rhodecode.model import validators
251 from rhodecode.model import validators
252 msg = validators.ValidUsername(
252 msg = validators.ValidUsername(
253 edit=False, old_data={})._messages['username_exists']
253 edit=False, old_data={})._messages['username_exists']
254 msg = h.html_escape(msg % {'username': 'test_admin'})
254 msg = h.html_escape(msg % {'username': 'test_admin'})
255 response.mustcontain(u"%s" % msg)
255 response.mustcontain(u"%s" % msg)
256
256
257 def test_my_account_auth_tokens(self):
257 def test_my_account_auth_tokens(self):
258 usr = self.log_user('test_regular2', 'test12')
258 usr = self.log_user('test_regular2', 'test12')
259 user = User.get(usr['user_id'])
259 user = User.get(usr['user_id'])
260 response = self.app.get(url('my_account_auth_tokens'))
260 response = self.app.get(url('my_account_auth_tokens'))
261 response.mustcontain(user.api_key)
261 for token in user.auth_tokens:
262 response.mustcontain('expires: never')
262 response.mustcontain(token)
263 response.mustcontain('never')
263
264
264 @pytest.mark.parametrize("desc, lifetime", [
265 @pytest.mark.parametrize("desc, lifetime", [
265 ('forever', -1),
266 ('forever', -1),
266 ('5mins', 60*5),
267 ('5mins', 60*5),
267 ('30days', 60*60*24*30),
268 ('30days', 60*60*24*30),
268 ])
269 ])
269 def test_my_account_add_auth_tokens(self, desc, lifetime):
270 def test_my_account_add_auth_tokens(self, desc, lifetime, user_util):
270 usr = self.log_user('test_regular2', 'test12')
271 user = user_util.create_user(password='qweqwe')
271 user = User.get(usr['user_id'])
272 user_id = user.user_id
273 self.log_user(user.username, 'qweqwe')
274
272 response = self.app.post(url('my_account_auth_tokens'),
275 response = self.app.post(url('my_account_auth_tokens'),
273 {'description': desc, 'lifetime': lifetime,
276 {'description': desc, 'lifetime': lifetime,
274 'csrf_token': self.csrf_token})
277 'csrf_token': self.csrf_token})
275 assert_session_flash(response, 'Auth token successfully created')
278 assert_session_flash(response, 'Auth token successfully created')
276 try:
279
277 response = response.follow()
280 response = response.follow()
278 user = User.get(usr['user_id'])
281 user = User.get(user_id)
279 for auth_token in user.auth_tokens:
282 for auth_token in user.auth_tokens:
280 response.mustcontain(auth_token)
283 response.mustcontain(auth_token)
281 finally:
282 for auth_token in UserApiKeys.query().all():
283 Session().delete(auth_token)
284 Session().commit()
285
284
286 def test_my_account_remove_auth_token(self, user_util):
285 def test_my_account_remove_auth_token(self, user_util):
287 user = user_util.create_user(password=self.test_user_1_password)
286 user = user_util.create_user(password='qweqwe')
288 user_id = user.user_id
287 user_id = user.user_id
289 self.log_user(user.username, self.test_user_1_password)
288 self.log_user(user.username, 'qweqwe')
290
289
291 user = User.get(user_id)
290 user = User.get(user_id)
292 keys = user.extra_auth_tokens
291 keys = user.extra_auth_tokens
293 assert 1 == len(keys)
292 assert 2 == len(keys)
294
293
295 response = self.app.post(url('my_account_auth_tokens'),
294 response = self.app.post(url('my_account_auth_tokens'),
296 {'description': 'desc', 'lifetime': -1,
295 {'description': 'desc', 'lifetime': -1,
297 'csrf_token': self.csrf_token})
296 'csrf_token': self.csrf_token})
298 assert_session_flash(response, 'Auth token successfully created')
297 assert_session_flash(response, 'Auth token successfully created')
299 response.follow()
298 response.follow()
300
299
301 user = User.get(user_id)
300 user = User.get(user_id)
302 keys = user.extra_auth_tokens
301 keys = user.extra_auth_tokens
303 assert 2 == len(keys)
302 assert 3 == len(keys)
304
303
305 response = self.app.post(
304 response = self.app.post(
306 url('my_account_auth_tokens'),
305 url('my_account_auth_tokens'),
307 {'_method': 'delete', 'del_auth_token': keys[0].api_key,
306 {'_method': 'delete', 'del_auth_token': keys[0].api_key,
308 'csrf_token': self.csrf_token})
307 'csrf_token': self.csrf_token})
309 assert_session_flash(response, 'Auth token successfully deleted')
308 assert_session_flash(response, 'Auth token successfully deleted')
310
309
311 user = User.get(user_id)
310 user = User.get(user_id)
312 keys = user.extra_auth_tokens
311 keys = user.extra_auth_tokens
313 assert 1 == len(keys)
312 assert 2 == len(keys)
314
313
315 def test_valid_change_password(self, user_util):
314 def test_valid_change_password(self, user_util):
316 new_password = 'my_new_valid_password'
315 new_password = 'my_new_valid_password'
317 user = user_util.create_user(password=self.test_user_1_password)
316 user = user_util.create_user(password=self.test_user_1_password)
318 session = self.log_user(user.username, self.test_user_1_password)
317 session = self.log_user(user.username, self.test_user_1_password)
319 form_data = [
318 form_data = [
320 ('current_password', self.test_user_1_password),
319 ('current_password', self.test_user_1_password),
321 ('__start__', 'new_password:mapping'),
320 ('__start__', 'new_password:mapping'),
322 ('new_password', new_password),
321 ('new_password', new_password),
323 ('new_password-confirm', new_password),
322 ('new_password-confirm', new_password),
324 ('__end__', 'new_password:mapping'),
323 ('__end__', 'new_password:mapping'),
325 ('csrf_token', self.csrf_token),
324 ('csrf_token', self.csrf_token),
326 ]
325 ]
327 response = self.app.post(url('my_account_password'), form_data).follow()
326 response = self.app.post(url('my_account_password'), form_data).follow()
328 assert 'Successfully updated password' in response
327 assert 'Successfully updated password' in response
329
328
330 # check_password depends on user being in session
329 # check_password depends on user being in session
331 Session().add(user)
330 Session().add(user)
332 try:
331 try:
333 assert check_password(new_password, user.password)
332 assert check_password(new_password, user.password)
334 finally:
333 finally:
335 Session().expunge(user)
334 Session().expunge(user)
336
335
337 @pytest.mark.parametrize('current_pw,new_pw,confirm_pw', [
336 @pytest.mark.parametrize('current_pw,new_pw,confirm_pw', [
338 ('', 'abcdef123', 'abcdef123'),
337 ('', 'abcdef123', 'abcdef123'),
339 ('wrong_pw', 'abcdef123', 'abcdef123'),
338 ('wrong_pw', 'abcdef123', 'abcdef123'),
340 (test_user_1_password, test_user_1_password, test_user_1_password),
339 (test_user_1_password, test_user_1_password, test_user_1_password),
341 (test_user_1_password, '', ''),
340 (test_user_1_password, '', ''),
342 (test_user_1_password, 'abcdef123', ''),
341 (test_user_1_password, 'abcdef123', ''),
343 (test_user_1_password, '', 'abcdef123'),
342 (test_user_1_password, '', 'abcdef123'),
344 (test_user_1_password, 'not_the', 'same_pw'),
343 (test_user_1_password, 'not_the', 'same_pw'),
345 (test_user_1_password, 'short', 'short'),
344 (test_user_1_password, 'short', 'short'),
346 ])
345 ])
347 def test_invalid_change_password(self, current_pw, new_pw, confirm_pw,
346 def test_invalid_change_password(self, current_pw, new_pw, confirm_pw,
348 user_util):
347 user_util):
349 user = user_util.create_user(password=self.test_user_1_password)
348 user = user_util.create_user(password=self.test_user_1_password)
350 session = self.log_user(user.username, self.test_user_1_password)
349 session = self.log_user(user.username, self.test_user_1_password)
351 old_password_hash = session['password']
350 old_password_hash = session['password']
352 form_data = [
351 form_data = [
353 ('current_password', current_pw),
352 ('current_password', current_pw),
354 ('__start__', 'new_password:mapping'),
353 ('__start__', 'new_password:mapping'),
355 ('new_password', new_pw),
354 ('new_password', new_pw),
356 ('new_password-confirm', confirm_pw),
355 ('new_password-confirm', confirm_pw),
357 ('__end__', 'new_password:mapping'),
356 ('__end__', 'new_password:mapping'),
358 ('csrf_token', self.csrf_token),
357 ('csrf_token', self.csrf_token),
359 ]
358 ]
360 response = self.app.post(url('my_account_password'), form_data)
359 response = self.app.post(url('my_account_password'), form_data)
361 assert 'Error occurred' in response
360 assert 'Error occurred' in response
362
361
363 def test_password_is_updated_in_session_on_password_change(self, user_util):
362 def test_password_is_updated_in_session_on_password_change(self, user_util):
364 old_password = 'abcdef123'
363 old_password = 'abcdef123'
365 new_password = 'abcdef124'
364 new_password = 'abcdef124'
366
365
367 user = user_util.create_user(password=old_password)
366 user = user_util.create_user(password=old_password)
368 session = self.log_user(user.username, old_password)
367 session = self.log_user(user.username, old_password)
369 old_password_hash = session['password']
368 old_password_hash = session['password']
370
369
371 form_data = [
370 form_data = [
372 ('current_password', old_password),
371 ('current_password', old_password),
373 ('__start__', 'new_password:mapping'),
372 ('__start__', 'new_password:mapping'),
374 ('new_password', new_password),
373 ('new_password', new_password),
375 ('new_password-confirm', new_password),
374 ('new_password-confirm', new_password),
376 ('__end__', 'new_password:mapping'),
375 ('__end__', 'new_password:mapping'),
377 ('csrf_token', self.csrf_token),
376 ('csrf_token', self.csrf_token),
378 ]
377 ]
379 self.app.post(url('my_account_password'), form_data)
378 self.app.post(url('my_account_password'), form_data)
380
379
381 response = self.app.get(url('home'))
380 response = self.app.get(url('home'))
382 new_password_hash = response.session['rhodecode_user']['password']
381 new_password_hash = response.session['rhodecode_user']['password']
383
382
384 assert old_password_hash != new_password_hash
383 assert old_password_hash != new_password_hash
@@ -1,627 +1,623 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import pytest
21 import pytest
22 from sqlalchemy.orm.exc import NoResultFound
22 from sqlalchemy.orm.exc import NoResultFound
23
23
24 from rhodecode.lib.auth import check_password
24 from rhodecode.lib.auth import check_password
25 from rhodecode.lib import helpers as h
25 from rhodecode.lib import helpers as h
26 from rhodecode.model import validators
26 from rhodecode.model import validators
27 from rhodecode.model.db import User, UserIpMap, UserApiKeys
27 from rhodecode.model.db import User, UserIpMap, UserApiKeys
28 from rhodecode.model.meta import Session
28 from rhodecode.model.meta import Session
29 from rhodecode.model.user import UserModel
29 from rhodecode.model.user import UserModel
30 from rhodecode.tests import (
30 from rhodecode.tests import (
31 TestController, url, link_to, TEST_USER_ADMIN_LOGIN,
31 TestController, url, link_to, TEST_USER_ADMIN_LOGIN,
32 TEST_USER_REGULAR_LOGIN, assert_session_flash)
32 TEST_USER_REGULAR_LOGIN, assert_session_flash)
33 from rhodecode.tests.fixture import Fixture
33 from rhodecode.tests.fixture import Fixture
34 from rhodecode.tests.utils import AssertResponse
34 from rhodecode.tests.utils import AssertResponse
35
35
36 fixture = Fixture()
36 fixture = Fixture()
37
37
38
38
39 class TestAdminUsersController(TestController):
39 class TestAdminUsersController(TestController):
40 test_user_1 = 'testme'
40 test_user_1 = 'testme'
41 destroy_users = set()
41 destroy_users = set()
42
42
43 @classmethod
43 @classmethod
44 def teardown_method(cls, method):
44 def teardown_method(cls, method):
45 fixture.destroy_users(cls.destroy_users)
45 fixture.destroy_users(cls.destroy_users)
46
46
47 def test_index(self):
47 def test_index(self):
48 self.log_user()
48 self.log_user()
49 self.app.get(url('users'))
49 self.app.get(url('users'))
50
50
51 def test_create(self):
51 def test_create(self):
52 self.log_user()
52 self.log_user()
53 username = 'newtestuser'
53 username = 'newtestuser'
54 password = 'test12'
54 password = 'test12'
55 password_confirmation = password
55 password_confirmation = password
56 name = 'name'
56 name = 'name'
57 lastname = 'lastname'
57 lastname = 'lastname'
58 email = 'mail@mail.com'
58 email = 'mail@mail.com'
59
59
60 response = self.app.get(url('new_user'))
60 response = self.app.get(url('new_user'))
61
61
62 response = self.app.post(url('users'), params={
62 response = self.app.post(url('users'), params={
63 'username': username,
63 'username': username,
64 'password': password,
64 'password': password,
65 'password_confirmation': password_confirmation,
65 'password_confirmation': password_confirmation,
66 'firstname': name,
66 'firstname': name,
67 'active': True,
67 'active': True,
68 'lastname': lastname,
68 'lastname': lastname,
69 'extern_name': 'rhodecode',
69 'extern_name': 'rhodecode',
70 'extern_type': 'rhodecode',
70 'extern_type': 'rhodecode',
71 'email': email,
71 'email': email,
72 'csrf_token': self.csrf_token,
72 'csrf_token': self.csrf_token,
73 })
73 })
74 user_link = link_to(
74 user_link = link_to(
75 username,
75 username,
76 url('edit_user', user_id=User.get_by_username(username).user_id))
76 url('edit_user', user_id=User.get_by_username(username).user_id))
77 assert_session_flash(response, 'Created user %s' % (user_link,))
77 assert_session_flash(response, 'Created user %s' % (user_link,))
78 self.destroy_users.add(username)
78 self.destroy_users.add(username)
79
79
80 new_user = User.query().filter(User.username == username).one()
80 new_user = User.query().filter(User.username == username).one()
81
81
82 assert new_user.username == username
82 assert new_user.username == username
83 assert check_password(password, new_user.password)
83 assert check_password(password, new_user.password)
84 assert new_user.name == name
84 assert new_user.name == name
85 assert new_user.lastname == lastname
85 assert new_user.lastname == lastname
86 assert new_user.email == email
86 assert new_user.email == email
87
87
88 response.follow()
88 response.follow()
89 response = response.follow()
89 response = response.follow()
90 response.mustcontain(username)
90 response.mustcontain(username)
91
91
92 def test_create_err(self):
92 def test_create_err(self):
93 self.log_user()
93 self.log_user()
94 username = 'new_user'
94 username = 'new_user'
95 password = ''
95 password = ''
96 name = 'name'
96 name = 'name'
97 lastname = 'lastname'
97 lastname = 'lastname'
98 email = 'errmail.com'
98 email = 'errmail.com'
99
99
100 response = self.app.get(url('new_user'))
100 response = self.app.get(url('new_user'))
101
101
102 response = self.app.post(url('users'), params={
102 response = self.app.post(url('users'), params={
103 'username': username,
103 'username': username,
104 'password': password,
104 'password': password,
105 'name': name,
105 'name': name,
106 'active': False,
106 'active': False,
107 'lastname': lastname,
107 'lastname': lastname,
108 'email': email,
108 'email': email,
109 'csrf_token': self.csrf_token,
109 'csrf_token': self.csrf_token,
110 })
110 })
111
111
112 msg = validators.ValidUsername(
112 msg = validators.ValidUsername(
113 False, {})._messages['system_invalid_username']
113 False, {})._messages['system_invalid_username']
114 msg = h.html_escape(msg % {'username': 'new_user'})
114 msg = h.html_escape(msg % {'username': 'new_user'})
115 response.mustcontain('<span class="error-message">%s</span>' % msg)
115 response.mustcontain('<span class="error-message">%s</span>' % msg)
116 response.mustcontain(
116 response.mustcontain(
117 '<span class="error-message">Please enter a value</span>')
117 '<span class="error-message">Please enter a value</span>')
118 response.mustcontain(
118 response.mustcontain(
119 '<span class="error-message">An email address must contain a'
119 '<span class="error-message">An email address must contain a'
120 ' single @</span>')
120 ' single @</span>')
121
121
122 def get_user():
122 def get_user():
123 Session().query(User).filter(User.username == username).one()
123 Session().query(User).filter(User.username == username).one()
124
124
125 with pytest.raises(NoResultFound):
125 with pytest.raises(NoResultFound):
126 get_user()
126 get_user()
127
127
128 def test_new(self):
128 def test_new(self):
129 self.log_user()
129 self.log_user()
130 self.app.get(url('new_user'))
130 self.app.get(url('new_user'))
131
131
132 @pytest.mark.parametrize("name, attrs", [
132 @pytest.mark.parametrize("name, attrs", [
133 ('firstname', {'firstname': 'new_username'}),
133 ('firstname', {'firstname': 'new_username'}),
134 ('lastname', {'lastname': 'new_username'}),
134 ('lastname', {'lastname': 'new_username'}),
135 ('admin', {'admin': True}),
135 ('admin', {'admin': True}),
136 ('admin', {'admin': False}),
136 ('admin', {'admin': False}),
137 ('extern_type', {'extern_type': 'ldap'}),
137 ('extern_type', {'extern_type': 'ldap'}),
138 ('extern_type', {'extern_type': None}),
138 ('extern_type', {'extern_type': None}),
139 ('extern_name', {'extern_name': 'test'}),
139 ('extern_name', {'extern_name': 'test'}),
140 ('extern_name', {'extern_name': None}),
140 ('extern_name', {'extern_name': None}),
141 ('active', {'active': False}),
141 ('active', {'active': False}),
142 ('active', {'active': True}),
142 ('active', {'active': True}),
143 ('email', {'email': 'some@email.com'}),
143 ('email', {'email': 'some@email.com'}),
144 ('language', {'language': 'de'}),
144 ('language', {'language': 'de'}),
145 ('language', {'language': 'en'}),
145 ('language', {'language': 'en'}),
146 # ('new_password', {'new_password': 'foobar123',
146 # ('new_password', {'new_password': 'foobar123',
147 # 'password_confirmation': 'foobar123'})
147 # 'password_confirmation': 'foobar123'})
148 ])
148 ])
149 def test_update(self, name, attrs):
149 def test_update(self, name, attrs):
150 self.log_user()
150 self.log_user()
151 usr = fixture.create_user(self.test_user_1, password='qweqwe',
151 usr = fixture.create_user(self.test_user_1, password='qweqwe',
152 email='testme@rhodecode.org',
152 email='testme@rhodecode.org',
153 extern_type='rhodecode',
153 extern_type='rhodecode',
154 extern_name=self.test_user_1,
154 extern_name=self.test_user_1,
155 skip_if_exists=True)
155 skip_if_exists=True)
156 Session().commit()
156 Session().commit()
157 self.destroy_users.add(self.test_user_1)
157 self.destroy_users.add(self.test_user_1)
158 params = usr.get_api_data()
158 params = usr.get_api_data()
159 cur_lang = params['language'] or 'en'
159 cur_lang = params['language'] or 'en'
160 params.update({
160 params.update({
161 'password_confirmation': '',
161 'password_confirmation': '',
162 'new_password': '',
162 'new_password': '',
163 'language': cur_lang,
163 'language': cur_lang,
164 '_method': 'put',
164 '_method': 'put',
165 'csrf_token': self.csrf_token,
165 'csrf_token': self.csrf_token,
166 })
166 })
167 params.update({'new_password': ''})
167 params.update({'new_password': ''})
168 params.update(attrs)
168 params.update(attrs)
169 if name == 'email':
169 if name == 'email':
170 params['emails'] = [attrs['email']]
170 params['emails'] = [attrs['email']]
171 elif name == 'extern_type':
171 elif name == 'extern_type':
172 # cannot update this via form, expected value is original one
172 # cannot update this via form, expected value is original one
173 params['extern_type'] = "rhodecode"
173 params['extern_type'] = "rhodecode"
174 elif name == 'extern_name':
174 elif name == 'extern_name':
175 # cannot update this via form, expected value is original one
175 # cannot update this via form, expected value is original one
176 params['extern_name'] = self.test_user_1
176 params['extern_name'] = self.test_user_1
177 # special case since this user is not
177 # special case since this user is not
178 # logged in yet his data is not filled
178 # logged in yet his data is not filled
179 # so we use creation data
179 # so we use creation data
180
180
181 response = self.app.post(url('user', user_id=usr.user_id), params)
181 response = self.app.post(url('user', user_id=usr.user_id), params)
182 assert response.status_int == 302
182 assert response.status_int == 302
183 assert_session_flash(response, 'User updated successfully')
183 assert_session_flash(response, 'User updated successfully')
184
184
185 updated_user = User.get_by_username(self.test_user_1)
185 updated_user = User.get_by_username(self.test_user_1)
186 updated_params = updated_user.get_api_data()
186 updated_params = updated_user.get_api_data()
187 updated_params.update({'password_confirmation': ''})
187 updated_params.update({'password_confirmation': ''})
188 updated_params.update({'new_password': ''})
188 updated_params.update({'new_password': ''})
189
189
190 del params['_method']
190 del params['_method']
191 del params['csrf_token']
191 del params['csrf_token']
192 assert params == updated_params
192 assert params == updated_params
193
193
194 def test_update_and_migrate_password(
194 def test_update_and_migrate_password(
195 self, autologin_user, real_crypto_backend):
195 self, autologin_user, real_crypto_backend):
196 from rhodecode.lib import auth
196 from rhodecode.lib import auth
197
197
198 # create new user, with sha256 password
198 # create new user, with sha256 password
199 temp_user = 'test_admin_sha256'
199 temp_user = 'test_admin_sha256'
200 user = fixture.create_user(temp_user)
200 user = fixture.create_user(temp_user)
201 user.password = auth._RhodeCodeCryptoSha256().hash_create(
201 user.password = auth._RhodeCodeCryptoSha256().hash_create(
202 b'test123')
202 b'test123')
203 Session().add(user)
203 Session().add(user)
204 Session().commit()
204 Session().commit()
205 self.destroy_users.add('test_admin_sha256')
205 self.destroy_users.add('test_admin_sha256')
206
206
207 params = user.get_api_data()
207 params = user.get_api_data()
208
208
209 params.update({
209 params.update({
210 'password_confirmation': 'qweqwe123',
210 'password_confirmation': 'qweqwe123',
211 'new_password': 'qweqwe123',
211 'new_password': 'qweqwe123',
212 'language': 'en',
212 'language': 'en',
213 '_method': 'put',
213 '_method': 'put',
214 'csrf_token': autologin_user.csrf_token,
214 'csrf_token': autologin_user.csrf_token,
215 })
215 })
216
216
217 response = self.app.post(url('user', user_id=user.user_id), params)
217 response = self.app.post(url('user', user_id=user.user_id), params)
218 assert response.status_int == 302
218 assert response.status_int == 302
219 assert_session_flash(response, 'User updated successfully')
219 assert_session_flash(response, 'User updated successfully')
220
220
221 # new password should be bcrypted, after log-in and transfer
221 # new password should be bcrypted, after log-in and transfer
222 user = User.get_by_username(temp_user)
222 user = User.get_by_username(temp_user)
223 assert user.password.startswith('$')
223 assert user.password.startswith('$')
224
224
225 updated_user = User.get_by_username(temp_user)
225 updated_user = User.get_by_username(temp_user)
226 updated_params = updated_user.get_api_data()
226 updated_params = updated_user.get_api_data()
227 updated_params.update({'password_confirmation': 'qweqwe123'})
227 updated_params.update({'password_confirmation': 'qweqwe123'})
228 updated_params.update({'new_password': 'qweqwe123'})
228 updated_params.update({'new_password': 'qweqwe123'})
229
229
230 del params['_method']
230 del params['_method']
231 del params['csrf_token']
231 del params['csrf_token']
232 assert params == updated_params
232 assert params == updated_params
233
233
234 def test_delete(self):
234 def test_delete(self):
235 self.log_user()
235 self.log_user()
236 username = 'newtestuserdeleteme'
236 username = 'newtestuserdeleteme'
237
237
238 fixture.create_user(name=username)
238 fixture.create_user(name=username)
239
239
240 new_user = Session().query(User)\
240 new_user = Session().query(User)\
241 .filter(User.username == username).one()
241 .filter(User.username == username).one()
242 response = self.app.post(url('user', user_id=new_user.user_id),
242 response = self.app.post(url('user', user_id=new_user.user_id),
243 params={'_method': 'delete',
243 params={'_method': 'delete',
244 'csrf_token': self.csrf_token})
244 'csrf_token': self.csrf_token})
245
245
246 assert_session_flash(response, 'Successfully deleted user')
246 assert_session_flash(response, 'Successfully deleted user')
247
247
248 def test_delete_owner_of_repository(self):
248 def test_delete_owner_of_repository(self):
249 self.log_user()
249 self.log_user()
250 username = 'newtestuserdeleteme_repo_owner'
250 username = 'newtestuserdeleteme_repo_owner'
251 obj_name = 'test_repo'
251 obj_name = 'test_repo'
252 usr = fixture.create_user(name=username)
252 usr = fixture.create_user(name=username)
253 self.destroy_users.add(username)
253 self.destroy_users.add(username)
254 fixture.create_repo(obj_name, cur_user=usr.username)
254 fixture.create_repo(obj_name, cur_user=usr.username)
255
255
256 new_user = Session().query(User)\
256 new_user = Session().query(User)\
257 .filter(User.username == username).one()
257 .filter(User.username == username).one()
258 response = self.app.post(url('user', user_id=new_user.user_id),
258 response = self.app.post(url('user', user_id=new_user.user_id),
259 params={'_method': 'delete',
259 params={'_method': 'delete',
260 'csrf_token': self.csrf_token})
260 'csrf_token': self.csrf_token})
261
261
262 msg = 'user "%s" still owns 1 repositories and cannot be removed. ' \
262 msg = 'user "%s" still owns 1 repositories and cannot be removed. ' \
263 'Switch owners or remove those repositories:%s' % (username,
263 'Switch owners or remove those repositories:%s' % (username,
264 obj_name)
264 obj_name)
265 assert_session_flash(response, msg)
265 assert_session_flash(response, msg)
266 fixture.destroy_repo(obj_name)
266 fixture.destroy_repo(obj_name)
267
267
268 def test_delete_owner_of_repository_detaching(self):
268 def test_delete_owner_of_repository_detaching(self):
269 self.log_user()
269 self.log_user()
270 username = 'newtestuserdeleteme_repo_owner_detach'
270 username = 'newtestuserdeleteme_repo_owner_detach'
271 obj_name = 'test_repo'
271 obj_name = 'test_repo'
272 usr = fixture.create_user(name=username)
272 usr = fixture.create_user(name=username)
273 self.destroy_users.add(username)
273 self.destroy_users.add(username)
274 fixture.create_repo(obj_name, cur_user=usr.username)
274 fixture.create_repo(obj_name, cur_user=usr.username)
275
275
276 new_user = Session().query(User)\
276 new_user = Session().query(User)\
277 .filter(User.username == username).one()
277 .filter(User.username == username).one()
278 response = self.app.post(url('user', user_id=new_user.user_id),
278 response = self.app.post(url('user', user_id=new_user.user_id),
279 params={'_method': 'delete',
279 params={'_method': 'delete',
280 'user_repos': 'detach',
280 'user_repos': 'detach',
281 'csrf_token': self.csrf_token})
281 'csrf_token': self.csrf_token})
282
282
283 msg = 'Detached 1 repositories'
283 msg = 'Detached 1 repositories'
284 assert_session_flash(response, msg)
284 assert_session_flash(response, msg)
285 fixture.destroy_repo(obj_name)
285 fixture.destroy_repo(obj_name)
286
286
287 def test_delete_owner_of_repository_deleting(self):
287 def test_delete_owner_of_repository_deleting(self):
288 self.log_user()
288 self.log_user()
289 username = 'newtestuserdeleteme_repo_owner_delete'
289 username = 'newtestuserdeleteme_repo_owner_delete'
290 obj_name = 'test_repo'
290 obj_name = 'test_repo'
291 usr = fixture.create_user(name=username)
291 usr = fixture.create_user(name=username)
292 self.destroy_users.add(username)
292 self.destroy_users.add(username)
293 fixture.create_repo(obj_name, cur_user=usr.username)
293 fixture.create_repo(obj_name, cur_user=usr.username)
294
294
295 new_user = Session().query(User)\
295 new_user = Session().query(User)\
296 .filter(User.username == username).one()
296 .filter(User.username == username).one()
297 response = self.app.post(url('user', user_id=new_user.user_id),
297 response = self.app.post(url('user', user_id=new_user.user_id),
298 params={'_method': 'delete',
298 params={'_method': 'delete',
299 'user_repos': 'delete',
299 'user_repos': 'delete',
300 'csrf_token': self.csrf_token})
300 'csrf_token': self.csrf_token})
301
301
302 msg = 'Deleted 1 repositories'
302 msg = 'Deleted 1 repositories'
303 assert_session_flash(response, msg)
303 assert_session_flash(response, msg)
304
304
305 def test_delete_owner_of_repository_group(self):
305 def test_delete_owner_of_repository_group(self):
306 self.log_user()
306 self.log_user()
307 username = 'newtestuserdeleteme_repo_group_owner'
307 username = 'newtestuserdeleteme_repo_group_owner'
308 obj_name = 'test_group'
308 obj_name = 'test_group'
309 usr = fixture.create_user(name=username)
309 usr = fixture.create_user(name=username)
310 self.destroy_users.add(username)
310 self.destroy_users.add(username)
311 fixture.create_repo_group(obj_name, cur_user=usr.username)
311 fixture.create_repo_group(obj_name, cur_user=usr.username)
312
312
313 new_user = Session().query(User)\
313 new_user = Session().query(User)\
314 .filter(User.username == username).one()
314 .filter(User.username == username).one()
315 response = self.app.post(url('user', user_id=new_user.user_id),
315 response = self.app.post(url('user', user_id=new_user.user_id),
316 params={'_method': 'delete',
316 params={'_method': 'delete',
317 'csrf_token': self.csrf_token})
317 'csrf_token': self.csrf_token})
318
318
319 msg = 'user "%s" still owns 1 repository groups and cannot be removed. ' \
319 msg = 'user "%s" still owns 1 repository groups and cannot be removed. ' \
320 'Switch owners or remove those repository groups:%s' % (username,
320 'Switch owners or remove those repository groups:%s' % (username,
321 obj_name)
321 obj_name)
322 assert_session_flash(response, msg)
322 assert_session_flash(response, msg)
323 fixture.destroy_repo_group(obj_name)
323 fixture.destroy_repo_group(obj_name)
324
324
325 def test_delete_owner_of_repository_group_detaching(self):
325 def test_delete_owner_of_repository_group_detaching(self):
326 self.log_user()
326 self.log_user()
327 username = 'newtestuserdeleteme_repo_group_owner_detach'
327 username = 'newtestuserdeleteme_repo_group_owner_detach'
328 obj_name = 'test_group'
328 obj_name = 'test_group'
329 usr = fixture.create_user(name=username)
329 usr = fixture.create_user(name=username)
330 self.destroy_users.add(username)
330 self.destroy_users.add(username)
331 fixture.create_repo_group(obj_name, cur_user=usr.username)
331 fixture.create_repo_group(obj_name, cur_user=usr.username)
332
332
333 new_user = Session().query(User)\
333 new_user = Session().query(User)\
334 .filter(User.username == username).one()
334 .filter(User.username == username).one()
335 response = self.app.post(url('user', user_id=new_user.user_id),
335 response = self.app.post(url('user', user_id=new_user.user_id),
336 params={'_method': 'delete',
336 params={'_method': 'delete',
337 'user_repo_groups': 'delete',
337 'user_repo_groups': 'delete',
338 'csrf_token': self.csrf_token})
338 'csrf_token': self.csrf_token})
339
339
340 msg = 'Deleted 1 repository groups'
340 msg = 'Deleted 1 repository groups'
341 assert_session_flash(response, msg)
341 assert_session_flash(response, msg)
342
342
343 def test_delete_owner_of_repository_group_deleting(self):
343 def test_delete_owner_of_repository_group_deleting(self):
344 self.log_user()
344 self.log_user()
345 username = 'newtestuserdeleteme_repo_group_owner_delete'
345 username = 'newtestuserdeleteme_repo_group_owner_delete'
346 obj_name = 'test_group'
346 obj_name = 'test_group'
347 usr = fixture.create_user(name=username)
347 usr = fixture.create_user(name=username)
348 self.destroy_users.add(username)
348 self.destroy_users.add(username)
349 fixture.create_repo_group(obj_name, cur_user=usr.username)
349 fixture.create_repo_group(obj_name, cur_user=usr.username)
350
350
351 new_user = Session().query(User)\
351 new_user = Session().query(User)\
352 .filter(User.username == username).one()
352 .filter(User.username == username).one()
353 response = self.app.post(url('user', user_id=new_user.user_id),
353 response = self.app.post(url('user', user_id=new_user.user_id),
354 params={'_method': 'delete',
354 params={'_method': 'delete',
355 'user_repo_groups': 'detach',
355 'user_repo_groups': 'detach',
356 'csrf_token': self.csrf_token})
356 'csrf_token': self.csrf_token})
357
357
358 msg = 'Detached 1 repository groups'
358 msg = 'Detached 1 repository groups'
359 assert_session_flash(response, msg)
359 assert_session_flash(response, msg)
360 fixture.destroy_repo_group(obj_name)
360 fixture.destroy_repo_group(obj_name)
361
361
362 def test_delete_owner_of_user_group(self):
362 def test_delete_owner_of_user_group(self):
363 self.log_user()
363 self.log_user()
364 username = 'newtestuserdeleteme_user_group_owner'
364 username = 'newtestuserdeleteme_user_group_owner'
365 obj_name = 'test_user_group'
365 obj_name = 'test_user_group'
366 usr = fixture.create_user(name=username)
366 usr = fixture.create_user(name=username)
367 self.destroy_users.add(username)
367 self.destroy_users.add(username)
368 fixture.create_user_group(obj_name, cur_user=usr.username)
368 fixture.create_user_group(obj_name, cur_user=usr.username)
369
369
370 new_user = Session().query(User)\
370 new_user = Session().query(User)\
371 .filter(User.username == username).one()
371 .filter(User.username == username).one()
372 response = self.app.post(url('user', user_id=new_user.user_id),
372 response = self.app.post(url('user', user_id=new_user.user_id),
373 params={'_method': 'delete',
373 params={'_method': 'delete',
374 'csrf_token': self.csrf_token})
374 'csrf_token': self.csrf_token})
375
375
376 msg = 'user "%s" still owns 1 user groups and cannot be removed. ' \
376 msg = 'user "%s" still owns 1 user groups and cannot be removed. ' \
377 'Switch owners or remove those user groups:%s' % (username,
377 'Switch owners or remove those user groups:%s' % (username,
378 obj_name)
378 obj_name)
379 assert_session_flash(response, msg)
379 assert_session_flash(response, msg)
380 fixture.destroy_user_group(obj_name)
380 fixture.destroy_user_group(obj_name)
381
381
382 def test_delete_owner_of_user_group_detaching(self):
382 def test_delete_owner_of_user_group_detaching(self):
383 self.log_user()
383 self.log_user()
384 username = 'newtestuserdeleteme_user_group_owner_detaching'
384 username = 'newtestuserdeleteme_user_group_owner_detaching'
385 obj_name = 'test_user_group'
385 obj_name = 'test_user_group'
386 usr = fixture.create_user(name=username)
386 usr = fixture.create_user(name=username)
387 self.destroy_users.add(username)
387 self.destroy_users.add(username)
388 fixture.create_user_group(obj_name, cur_user=usr.username)
388 fixture.create_user_group(obj_name, cur_user=usr.username)
389
389
390 new_user = Session().query(User)\
390 new_user = Session().query(User)\
391 .filter(User.username == username).one()
391 .filter(User.username == username).one()
392 try:
392 try:
393 response = self.app.post(url('user', user_id=new_user.user_id),
393 response = self.app.post(url('user', user_id=new_user.user_id),
394 params={'_method': 'delete',
394 params={'_method': 'delete',
395 'user_user_groups': 'detach',
395 'user_user_groups': 'detach',
396 'csrf_token': self.csrf_token})
396 'csrf_token': self.csrf_token})
397
397
398 msg = 'Detached 1 user groups'
398 msg = 'Detached 1 user groups'
399 assert_session_flash(response, msg)
399 assert_session_flash(response, msg)
400 finally:
400 finally:
401 fixture.destroy_user_group(obj_name)
401 fixture.destroy_user_group(obj_name)
402
402
403 def test_delete_owner_of_user_group_deleting(self):
403 def test_delete_owner_of_user_group_deleting(self):
404 self.log_user()
404 self.log_user()
405 username = 'newtestuserdeleteme_user_group_owner_deleting'
405 username = 'newtestuserdeleteme_user_group_owner_deleting'
406 obj_name = 'test_user_group'
406 obj_name = 'test_user_group'
407 usr = fixture.create_user(name=username)
407 usr = fixture.create_user(name=username)
408 self.destroy_users.add(username)
408 self.destroy_users.add(username)
409 fixture.create_user_group(obj_name, cur_user=usr.username)
409 fixture.create_user_group(obj_name, cur_user=usr.username)
410
410
411 new_user = Session().query(User)\
411 new_user = Session().query(User)\
412 .filter(User.username == username).one()
412 .filter(User.username == username).one()
413 response = self.app.post(url('user', user_id=new_user.user_id),
413 response = self.app.post(url('user', user_id=new_user.user_id),
414 params={'_method': 'delete',
414 params={'_method': 'delete',
415 'user_user_groups': 'delete',
415 'user_user_groups': 'delete',
416 'csrf_token': self.csrf_token})
416 'csrf_token': self.csrf_token})
417
417
418 msg = 'Deleted 1 user groups'
418 msg = 'Deleted 1 user groups'
419 assert_session_flash(response, msg)
419 assert_session_flash(response, msg)
420
420
421 def test_show(self):
421 def test_show(self):
422 self.app.get(url('user', user_id=1))
422 self.app.get(url('user', user_id=1))
423
423
424 def test_edit(self):
424 def test_edit(self):
425 self.log_user()
425 self.log_user()
426 user = User.get_by_username(TEST_USER_ADMIN_LOGIN)
426 user = User.get_by_username(TEST_USER_ADMIN_LOGIN)
427 self.app.get(url('edit_user', user_id=user.user_id))
427 self.app.get(url('edit_user', user_id=user.user_id))
428
428
429 @pytest.mark.parametrize(
429 @pytest.mark.parametrize(
430 'repo_create, repo_create_write, user_group_create, repo_group_create,'
430 'repo_create, repo_create_write, user_group_create, repo_group_create,'
431 'fork_create, inherit_default_permissions, expect_error,'
431 'fork_create, inherit_default_permissions, expect_error,'
432 'expect_form_error', [
432 'expect_form_error', [
433 ('hg.create.none', 'hg.create.write_on_repogroup.false',
433 ('hg.create.none', 'hg.create.write_on_repogroup.false',
434 'hg.usergroup.create.false', 'hg.repogroup.create.false',
434 'hg.usergroup.create.false', 'hg.repogroup.create.false',
435 'hg.fork.none', 'hg.inherit_default_perms.false', False, False),
435 'hg.fork.none', 'hg.inherit_default_perms.false', False, False),
436 ('hg.create.repository', 'hg.create.write_on_repogroup.false',
436 ('hg.create.repository', 'hg.create.write_on_repogroup.false',
437 'hg.usergroup.create.false', 'hg.repogroup.create.false',
437 'hg.usergroup.create.false', 'hg.repogroup.create.false',
438 'hg.fork.none', 'hg.inherit_default_perms.false', False, False),
438 'hg.fork.none', 'hg.inherit_default_perms.false', False, False),
439 ('hg.create.repository', 'hg.create.write_on_repogroup.true',
439 ('hg.create.repository', 'hg.create.write_on_repogroup.true',
440 'hg.usergroup.create.true', 'hg.repogroup.create.true',
440 'hg.usergroup.create.true', 'hg.repogroup.create.true',
441 'hg.fork.repository', 'hg.inherit_default_perms.false', False,
441 'hg.fork.repository', 'hg.inherit_default_perms.false', False,
442 False),
442 False),
443 ('hg.create.XXX', 'hg.create.write_on_repogroup.true',
443 ('hg.create.XXX', 'hg.create.write_on_repogroup.true',
444 'hg.usergroup.create.true', 'hg.repogroup.create.true',
444 'hg.usergroup.create.true', 'hg.repogroup.create.true',
445 'hg.fork.repository', 'hg.inherit_default_perms.false', False,
445 'hg.fork.repository', 'hg.inherit_default_perms.false', False,
446 True),
446 True),
447 ('', '', '', '', '', '', True, False),
447 ('', '', '', '', '', '', True, False),
448 ])
448 ])
449 def test_global_perms_on_user(
449 def test_global_perms_on_user(
450 self, repo_create, repo_create_write, user_group_create,
450 self, repo_create, repo_create_write, user_group_create,
451 repo_group_create, fork_create, expect_error, expect_form_error,
451 repo_group_create, fork_create, expect_error, expect_form_error,
452 inherit_default_permissions):
452 inherit_default_permissions):
453 self.log_user()
453 self.log_user()
454 user = fixture.create_user('dummy')
454 user = fixture.create_user('dummy')
455 uid = user.user_id
455 uid = user.user_id
456
456
457 # ENABLE REPO CREATE ON A GROUP
457 # ENABLE REPO CREATE ON A GROUP
458 perm_params = {
458 perm_params = {
459 'inherit_default_permissions': False,
459 'inherit_default_permissions': False,
460 'default_repo_create': repo_create,
460 'default_repo_create': repo_create,
461 'default_repo_create_on_write': repo_create_write,
461 'default_repo_create_on_write': repo_create_write,
462 'default_user_group_create': user_group_create,
462 'default_user_group_create': user_group_create,
463 'default_repo_group_create': repo_group_create,
463 'default_repo_group_create': repo_group_create,
464 'default_fork_create': fork_create,
464 'default_fork_create': fork_create,
465 'default_inherit_default_permissions': inherit_default_permissions,
465 'default_inherit_default_permissions': inherit_default_permissions,
466 '_method': 'put',
466 '_method': 'put',
467 'csrf_token': self.csrf_token,
467 'csrf_token': self.csrf_token,
468 }
468 }
469 response = self.app.post(
469 response = self.app.post(
470 url('edit_user_global_perms', user_id=uid),
470 url('edit_user_global_perms', user_id=uid),
471 params=perm_params)
471 params=perm_params)
472
472
473 if expect_form_error:
473 if expect_form_error:
474 assert response.status_int == 200
474 assert response.status_int == 200
475 response.mustcontain('Value must be one of')
475 response.mustcontain('Value must be one of')
476 else:
476 else:
477 if expect_error:
477 if expect_error:
478 msg = 'An error occurred during permissions saving'
478 msg = 'An error occurred during permissions saving'
479 else:
479 else:
480 msg = 'User global permissions updated successfully'
480 msg = 'User global permissions updated successfully'
481 ug = User.get(uid)
481 ug = User.get(uid)
482 del perm_params['_method']
482 del perm_params['_method']
483 del perm_params['inherit_default_permissions']
483 del perm_params['inherit_default_permissions']
484 del perm_params['csrf_token']
484 del perm_params['csrf_token']
485 assert perm_params == ug.get_default_perms()
485 assert perm_params == ug.get_default_perms()
486 assert_session_flash(response, msg)
486 assert_session_flash(response, msg)
487 fixture.destroy_user(uid)
487 fixture.destroy_user(uid)
488
488
489 def test_global_permissions_initial_values(self, user_util):
489 def test_global_permissions_initial_values(self, user_util):
490 self.log_user()
490 self.log_user()
491 user = user_util.create_user()
491 user = user_util.create_user()
492 uid = user.user_id
492 uid = user.user_id
493 response = self.app.get(url('edit_user_global_perms', user_id=uid))
493 response = self.app.get(url('edit_user_global_perms', user_id=uid))
494 default_user = User.get_default_user()
494 default_user = User.get_default_user()
495 default_permissions = default_user.get_default_perms()
495 default_permissions = default_user.get_default_perms()
496 assert_response = AssertResponse(response)
496 assert_response = AssertResponse(response)
497 expected_permissions = (
497 expected_permissions = (
498 'default_repo_create', 'default_repo_create_on_write',
498 'default_repo_create', 'default_repo_create_on_write',
499 'default_fork_create', 'default_repo_group_create',
499 'default_fork_create', 'default_repo_group_create',
500 'default_user_group_create', 'default_inherit_default_permissions')
500 'default_user_group_create', 'default_inherit_default_permissions')
501 for permission in expected_permissions:
501 for permission in expected_permissions:
502 css_selector = '[name={}][checked=checked]'.format(permission)
502 css_selector = '[name={}][checked=checked]'.format(permission)
503 element = assert_response.get_element(css_selector)
503 element = assert_response.get_element(css_selector)
504 assert element.value == default_permissions[permission]
504 assert element.value == default_permissions[permission]
505
505
506 def test_ips(self):
506 def test_ips(self):
507 self.log_user()
507 self.log_user()
508 user = User.get_by_username(TEST_USER_REGULAR_LOGIN)
508 user = User.get_by_username(TEST_USER_REGULAR_LOGIN)
509 response = self.app.get(url('edit_user_ips', user_id=user.user_id))
509 response = self.app.get(url('edit_user_ips', user_id=user.user_id))
510 response.mustcontain('All IP addresses are allowed')
510 response.mustcontain('All IP addresses are allowed')
511
511
512 @pytest.mark.parametrize("test_name, ip, ip_range, failure", [
512 @pytest.mark.parametrize("test_name, ip, ip_range, failure", [
513 ('127/24', '127.0.0.1/24', '127.0.0.0 - 127.0.0.255', False),
513 ('127/24', '127.0.0.1/24', '127.0.0.0 - 127.0.0.255', False),
514 ('10/32', '10.0.0.10/32', '10.0.0.10 - 10.0.0.10', False),
514 ('10/32', '10.0.0.10/32', '10.0.0.10 - 10.0.0.10', False),
515 ('0/16', '0.0.0.0/16', '0.0.0.0 - 0.0.255.255', False),
515 ('0/16', '0.0.0.0/16', '0.0.0.0 - 0.0.255.255', False),
516 ('0/8', '0.0.0.0/8', '0.0.0.0 - 0.255.255.255', False),
516 ('0/8', '0.0.0.0/8', '0.0.0.0 - 0.255.255.255', False),
517 ('127_bad_mask', '127.0.0.1/99', '127.0.0.1 - 127.0.0.1', True),
517 ('127_bad_mask', '127.0.0.1/99', '127.0.0.1 - 127.0.0.1', True),
518 ('127_bad_ip', 'foobar', 'foobar', True),
518 ('127_bad_ip', 'foobar', 'foobar', True),
519 ])
519 ])
520 def test_add_ip(self, test_name, ip, ip_range, failure):
520 def test_add_ip(self, test_name, ip, ip_range, failure):
521 self.log_user()
521 self.log_user()
522 user = User.get_by_username(TEST_USER_REGULAR_LOGIN)
522 user = User.get_by_username(TEST_USER_REGULAR_LOGIN)
523 user_id = user.user_id
523 user_id = user.user_id
524
524
525 response = self.app.post(url('edit_user_ips', user_id=user_id),
525 response = self.app.post(url('edit_user_ips', user_id=user_id),
526 params={'new_ip': ip, '_method': 'put',
526 params={'new_ip': ip, '_method': 'put',
527 'csrf_token': self.csrf_token})
527 'csrf_token': self.csrf_token})
528
528
529 if failure:
529 if failure:
530 assert_session_flash(
530 assert_session_flash(
531 response, 'Please enter a valid IPv4 or IpV6 address')
531 response, 'Please enter a valid IPv4 or IpV6 address')
532 response = self.app.get(url('edit_user_ips', user_id=user_id))
532 response = self.app.get(url('edit_user_ips', user_id=user_id))
533 response.mustcontain(no=[ip])
533 response.mustcontain(no=[ip])
534 response.mustcontain(no=[ip_range])
534 response.mustcontain(no=[ip_range])
535
535
536 else:
536 else:
537 response = self.app.get(url('edit_user_ips', user_id=user_id))
537 response = self.app.get(url('edit_user_ips', user_id=user_id))
538 response.mustcontain(ip)
538 response.mustcontain(ip)
539 response.mustcontain(ip_range)
539 response.mustcontain(ip_range)
540
540
541 # cleanup
541 # cleanup
542 for del_ip in UserIpMap.query().filter(
542 for del_ip in UserIpMap.query().filter(
543 UserIpMap.user_id == user_id).all():
543 UserIpMap.user_id == user_id).all():
544 Session().delete(del_ip)
544 Session().delete(del_ip)
545 Session().commit()
545 Session().commit()
546
546
547 def test_delete_ip(self):
547 def test_delete_ip(self):
548 self.log_user()
548 self.log_user()
549 user = User.get_by_username(TEST_USER_REGULAR_LOGIN)
549 user = User.get_by_username(TEST_USER_REGULAR_LOGIN)
550 user_id = user.user_id
550 user_id = user.user_id
551 ip = '127.0.0.1/32'
551 ip = '127.0.0.1/32'
552 ip_range = '127.0.0.1 - 127.0.0.1'
552 ip_range = '127.0.0.1 - 127.0.0.1'
553 new_ip = UserModel().add_extra_ip(user_id, ip)
553 new_ip = UserModel().add_extra_ip(user_id, ip)
554 Session().commit()
554 Session().commit()
555 new_ip_id = new_ip.ip_id
555 new_ip_id = new_ip.ip_id
556
556
557 response = self.app.get(url('edit_user_ips', user_id=user_id))
557 response = self.app.get(url('edit_user_ips', user_id=user_id))
558 response.mustcontain(ip)
558 response.mustcontain(ip)
559 response.mustcontain(ip_range)
559 response.mustcontain(ip_range)
560
560
561 self.app.post(url('edit_user_ips', user_id=user_id),
561 self.app.post(url('edit_user_ips', user_id=user_id),
562 params={'_method': 'delete', 'del_ip_id': new_ip_id,
562 params={'_method': 'delete', 'del_ip_id': new_ip_id,
563 'csrf_token': self.csrf_token})
563 'csrf_token': self.csrf_token})
564
564
565 response = self.app.get(url('edit_user_ips', user_id=user_id))
565 response = self.app.get(url('edit_user_ips', user_id=user_id))
566 response.mustcontain('All IP addresses are allowed')
566 response.mustcontain('All IP addresses are allowed')
567 response.mustcontain(no=[ip])
567 response.mustcontain(no=[ip])
568 response.mustcontain(no=[ip_range])
568 response.mustcontain(no=[ip_range])
569
569
570 def test_auth_tokens(self):
570 def test_auth_tokens(self):
571 self.log_user()
571 self.log_user()
572
572
573 user = User.get_by_username(TEST_USER_REGULAR_LOGIN)
573 user = User.get_by_username(TEST_USER_REGULAR_LOGIN)
574 response = self.app.get(
574 response = self.app.get(
575 url('edit_user_auth_tokens', user_id=user.user_id))
575 url('edit_user_auth_tokens', user_id=user.user_id))
576 response.mustcontain(user.api_key)
576 for token in user.auth_tokens:
577 response.mustcontain('expires: never')
577 response.mustcontain(token)
578 response.mustcontain('never')
578
579
579 @pytest.mark.parametrize("desc, lifetime", [
580 @pytest.mark.parametrize("desc, lifetime", [
580 ('forever', -1),
581 ('forever', -1),
581 ('5mins', 60*5),
582 ('5mins', 60*5),
582 ('30days', 60*60*24*30),
583 ('30days', 60*60*24*30),
583 ])
584 ])
584 def test_add_auth_token(self, desc, lifetime):
585 def test_add_auth_token(self, desc, lifetime, user_util):
585 self.log_user()
586 self.log_user()
586 user = User.get_by_username(TEST_USER_REGULAR_LOGIN)
587 user = user_util.create_user()
587 user_id = user.user_id
588 user_id = user.user_id
588
589
589 response = self.app.post(
590 response = self.app.post(
590 url('edit_user_auth_tokens', user_id=user_id),
591 url('edit_user_auth_tokens', user_id=user_id),
591 {'_method': 'put', 'description': desc, 'lifetime': lifetime,
592 {'_method': 'put', 'description': desc, 'lifetime': lifetime,
592 'csrf_token': self.csrf_token})
593 'csrf_token': self.csrf_token})
593 assert_session_flash(response, 'Auth token successfully created')
594 assert_session_flash(response, 'Auth token successfully created')
594 try:
595 response = response.follow()
596 user = User.get(user_id)
597 for auth_token in user.auth_tokens:
598 response.mustcontain(auth_token)
599 finally:
600 for api_key in UserApiKeys.query().filter(
601 UserApiKeys.user_id == user_id).all():
602 Session().delete(api_key)
603 Session().commit()
604
595
605 def test_remove_auth_token(self):
596 response = response.follow()
597 user = User.get(user_id)
598 for auth_token in user.auth_tokens:
599 response.mustcontain(auth_token)
600
601 def test_remove_auth_token(self, user_util):
606 self.log_user()
602 self.log_user()
607 user = User.get_by_username(TEST_USER_REGULAR_LOGIN)
603 user = user_util.create_user()
608 user_id = user.user_id
604 user_id = user.user_id
609
605
610 response = self.app.post(
606 response = self.app.post(
611 url('edit_user_auth_tokens', user_id=user_id),
607 url('edit_user_auth_tokens', user_id=user_id),
612 {'_method': 'put', 'description': 'desc', 'lifetime': -1,
608 {'_method': 'put', 'description': 'desc', 'lifetime': -1,
613 'csrf_token': self.csrf_token})
609 'csrf_token': self.csrf_token})
614 assert_session_flash(response, 'Auth token successfully created')
610 assert_session_flash(response, 'Auth token successfully created')
615 response = response.follow()
611 response = response.follow()
616
612
617 # now delete our key
613 # now delete our key
618 keys = UserApiKeys.query().filter(UserApiKeys.user_id == user_id).all()
614 keys = UserApiKeys.query().filter(UserApiKeys.user_id == user_id).all()
619 assert 1 == len(keys)
615 assert 3 == len(keys)
620
616
621 response = self.app.post(
617 response = self.app.post(
622 url('edit_user_auth_tokens', user_id=user_id),
618 url('edit_user_auth_tokens', user_id=user_id),
623 {'_method': 'delete', 'del_auth_token': keys[0].api_key,
619 {'_method': 'delete', 'del_auth_token': keys[0].api_key,
624 'csrf_token': self.csrf_token})
620 'csrf_token': self.csrf_token})
625 assert_session_flash(response, 'Auth token successfully deleted')
621 assert_session_flash(response, 'Auth token successfully deleted')
626 keys = UserApiKeys.query().filter(UserApiKeys.user_id == user_id).all()
622 keys = UserApiKeys.query().filter(UserApiKeys.user_id == user_id).all()
627 assert 0 == len(keys)
623 assert 2 == len(keys)
@@ -1,510 +1,511 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import urlparse
21 import urlparse
22
22
23 import mock
23 import mock
24 import pytest
24 import pytest
25
25
26 from rhodecode.config.routing import ADMIN_PREFIX
26 from rhodecode.config.routing import ADMIN_PREFIX
27 from rhodecode.tests import (
27 from rhodecode.tests import (
28 assert_session_flash, url, HG_REPO, TEST_USER_ADMIN_LOGIN)
28 assert_session_flash, url, HG_REPO, TEST_USER_ADMIN_LOGIN)
29 from rhodecode.tests.fixture import Fixture
29 from rhodecode.tests.fixture import Fixture
30 from rhodecode.tests.utils import AssertResponse, get_session_from_response
30 from rhodecode.tests.utils import AssertResponse, get_session_from_response
31 from rhodecode.lib.auth import check_password
31 from rhodecode.lib.auth import check_password
32 from rhodecode.model.auth_token import AuthTokenModel
32 from rhodecode.model.auth_token import AuthTokenModel
33 from rhodecode.model import validators
33 from rhodecode.model import validators
34 from rhodecode.model.db import User, Notification, UserApiKeys
34 from rhodecode.model.db import User, Notification, UserApiKeys
35 from rhodecode.model.meta import Session
35 from rhodecode.model.meta import Session
36
36
37 fixture = Fixture()
37 fixture = Fixture()
38
38
39 # Hardcode URLs because we don't have a request object to use
39 # Hardcode URLs because we don't have a request object to use
40 # pyramids URL generation methods.
40 # pyramids URL generation methods.
41 index_url = '/'
41 index_url = '/'
42 login_url = ADMIN_PREFIX + '/login'
42 login_url = ADMIN_PREFIX + '/login'
43 logut_url = ADMIN_PREFIX + '/logout'
43 logut_url = ADMIN_PREFIX + '/logout'
44 register_url = ADMIN_PREFIX + '/register'
44 register_url = ADMIN_PREFIX + '/register'
45 pwd_reset_url = ADMIN_PREFIX + '/password_reset'
45 pwd_reset_url = ADMIN_PREFIX + '/password_reset'
46 pwd_reset_confirm_url = ADMIN_PREFIX + '/password_reset_confirmation'
46 pwd_reset_confirm_url = ADMIN_PREFIX + '/password_reset_confirmation'
47
47
48
48
49 @pytest.mark.usefixtures('app')
49 @pytest.mark.usefixtures('app')
50 class TestLoginController(object):
50 class TestLoginController(object):
51 destroy_users = set()
51 destroy_users = set()
52
52
53 @classmethod
53 @classmethod
54 def teardown_class(cls):
54 def teardown_class(cls):
55 fixture.destroy_users(cls.destroy_users)
55 fixture.destroy_users(cls.destroy_users)
56
56
57 def teardown_method(self, method):
57 def teardown_method(self, method):
58 for n in Notification.query().all():
58 for n in Notification.query().all():
59 Session().delete(n)
59 Session().delete(n)
60
60
61 Session().commit()
61 Session().commit()
62 assert Notification.query().all() == []
62 assert Notification.query().all() == []
63
63
64 def test_index(self):
64 def test_index(self):
65 response = self.app.get(login_url)
65 response = self.app.get(login_url)
66 assert response.status == '200 OK'
66 assert response.status == '200 OK'
67 # Test response...
67 # Test response...
68
68
69 def test_login_admin_ok(self):
69 def test_login_admin_ok(self):
70 response = self.app.post(login_url,
70 response = self.app.post(login_url,
71 {'username': 'test_admin',
71 {'username': 'test_admin',
72 'password': 'test12'})
72 'password': 'test12'})
73 assert response.status == '302 Found'
73 assert response.status == '302 Found'
74 session = get_session_from_response(response)
74 session = get_session_from_response(response)
75 username = session['rhodecode_user'].get('username')
75 username = session['rhodecode_user'].get('username')
76 assert username == 'test_admin'
76 assert username == 'test_admin'
77 response = response.follow()
77 response = response.follow()
78 response.mustcontain('/%s' % HG_REPO)
78 response.mustcontain('/%s' % HG_REPO)
79
79
80 def test_login_regular_ok(self):
80 def test_login_regular_ok(self):
81 response = self.app.post(login_url,
81 response = self.app.post(login_url,
82 {'username': 'test_regular',
82 {'username': 'test_regular',
83 'password': 'test12'})
83 'password': 'test12'})
84
84
85 assert response.status == '302 Found'
85 assert response.status == '302 Found'
86 session = get_session_from_response(response)
86 session = get_session_from_response(response)
87 username = session['rhodecode_user'].get('username')
87 username = session['rhodecode_user'].get('username')
88 assert username == 'test_regular'
88 assert username == 'test_regular'
89 response = response.follow()
89 response = response.follow()
90 response.mustcontain('/%s' % HG_REPO)
90 response.mustcontain('/%s' % HG_REPO)
91
91
92 def test_login_ok_came_from(self):
92 def test_login_ok_came_from(self):
93 test_came_from = '/_admin/users?branch=stable'
93 test_came_from = '/_admin/users?branch=stable'
94 _url = '{}?came_from={}'.format(login_url, test_came_from)
94 _url = '{}?came_from={}'.format(login_url, test_came_from)
95 response = self.app.post(
95 response = self.app.post(
96 _url, {'username': 'test_admin', 'password': 'test12'})
96 _url, {'username': 'test_admin', 'password': 'test12'})
97 assert response.status == '302 Found'
97 assert response.status == '302 Found'
98 assert 'branch=stable' in response.location
98 assert 'branch=stable' in response.location
99 response = response.follow()
99 response = response.follow()
100
100
101 assert response.status == '200 OK'
101 assert response.status == '200 OK'
102 response.mustcontain('Users administration')
102 response.mustcontain('Users administration')
103
103
104 def test_redirect_to_login_with_get_args(self):
104 def test_redirect_to_login_with_get_args(self):
105 with fixture.anon_access(False):
105 with fixture.anon_access(False):
106 kwargs = {'branch': 'stable'}
106 kwargs = {'branch': 'stable'}
107 response = self.app.get(
107 response = self.app.get(
108 url('summary_home', repo_name=HG_REPO, **kwargs))
108 url('summary_home', repo_name=HG_REPO, **kwargs))
109 assert response.status == '302 Found'
109 assert response.status == '302 Found'
110 response_query = urlparse.parse_qsl(response.location)
110 response_query = urlparse.parse_qsl(response.location)
111 assert 'branch=stable' in response_query[0][1]
111 assert 'branch=stable' in response_query[0][1]
112
112
113 def test_login_form_with_get_args(self):
113 def test_login_form_with_get_args(self):
114 _url = '{}?came_from=/_admin/users,branch=stable'.format(login_url)
114 _url = '{}?came_from=/_admin/users,branch=stable'.format(login_url)
115 response = self.app.get(_url)
115 response = self.app.get(_url)
116 assert 'branch%3Dstable' in response.form.action
116 assert 'branch%3Dstable' in response.form.action
117
117
118 @pytest.mark.parametrize("url_came_from", [
118 @pytest.mark.parametrize("url_came_from", [
119 'data:text/html,<script>window.alert("xss")</script>',
119 'data:text/html,<script>window.alert("xss")</script>',
120 'mailto:test@rhodecode.org',
120 'mailto:test@rhodecode.org',
121 'file:///etc/passwd',
121 'file:///etc/passwd',
122 'ftp://some.ftp.server',
122 'ftp://some.ftp.server',
123 'http://other.domain',
123 'http://other.domain',
124 '/\r\nX-Forwarded-Host: http://example.org',
124 '/\r\nX-Forwarded-Host: http://example.org',
125 ])
125 ])
126 def test_login_bad_came_froms(self, url_came_from):
126 def test_login_bad_came_froms(self, url_came_from):
127 _url = '{}?came_from={}'.format(login_url, url_came_from)
127 _url = '{}?came_from={}'.format(login_url, url_came_from)
128 response = self.app.post(
128 response = self.app.post(
129 _url,
129 _url,
130 {'username': 'test_admin', 'password': 'test12'})
130 {'username': 'test_admin', 'password': 'test12'})
131 assert response.status == '302 Found'
131 assert response.status == '302 Found'
132 response = response.follow()
132 response = response.follow()
133 assert response.status == '200 OK'
133 assert response.status == '200 OK'
134 assert response.request.path == '/'
134 assert response.request.path == '/'
135
135
136 def test_login_short_password(self):
136 def test_login_short_password(self):
137 response = self.app.post(login_url,
137 response = self.app.post(login_url,
138 {'username': 'test_admin',
138 {'username': 'test_admin',
139 'password': 'as'})
139 'password': 'as'})
140 assert response.status == '200 OK'
140 assert response.status == '200 OK'
141
141
142 response.mustcontain('Enter 3 characters or more')
142 response.mustcontain('Enter 3 characters or more')
143
143
144 def test_login_wrong_non_ascii_password(self, user_regular):
144 def test_login_wrong_non_ascii_password(self, user_regular):
145 response = self.app.post(
145 response = self.app.post(
146 login_url,
146 login_url,
147 {'username': user_regular.username,
147 {'username': user_regular.username,
148 'password': u'invalid-non-asci\xe4'.encode('utf8')})
148 'password': u'invalid-non-asci\xe4'.encode('utf8')})
149
149
150 response.mustcontain('invalid user name')
150 response.mustcontain('invalid user name')
151 response.mustcontain('invalid password')
151 response.mustcontain('invalid password')
152
152
153 def test_login_with_non_ascii_password(self, user_util):
153 def test_login_with_non_ascii_password(self, user_util):
154 password = u'valid-non-ascii\xe4'
154 password = u'valid-non-ascii\xe4'
155 user = user_util.create_user(password=password)
155 user = user_util.create_user(password=password)
156 response = self.app.post(
156 response = self.app.post(
157 login_url,
157 login_url,
158 {'username': user.username,
158 {'username': user.username,
159 'password': password.encode('utf-8')})
159 'password': password.encode('utf-8')})
160 assert response.status_code == 302
160 assert response.status_code == 302
161
161
162 def test_login_wrong_username_password(self):
162 def test_login_wrong_username_password(self):
163 response = self.app.post(login_url,
163 response = self.app.post(login_url,
164 {'username': 'error',
164 {'username': 'error',
165 'password': 'test12'})
165 'password': 'test12'})
166
166
167 response.mustcontain('invalid user name')
167 response.mustcontain('invalid user name')
168 response.mustcontain('invalid password')
168 response.mustcontain('invalid password')
169
169
170 def test_login_admin_ok_password_migration(self, real_crypto_backend):
170 def test_login_admin_ok_password_migration(self, real_crypto_backend):
171 from rhodecode.lib import auth
171 from rhodecode.lib import auth
172
172
173 # create new user, with sha256 password
173 # create new user, with sha256 password
174 temp_user = 'test_admin_sha256'
174 temp_user = 'test_admin_sha256'
175 user = fixture.create_user(temp_user)
175 user = fixture.create_user(temp_user)
176 user.password = auth._RhodeCodeCryptoSha256().hash_create(
176 user.password = auth._RhodeCodeCryptoSha256().hash_create(
177 b'test123')
177 b'test123')
178 Session().add(user)
178 Session().add(user)
179 Session().commit()
179 Session().commit()
180 self.destroy_users.add(temp_user)
180 self.destroy_users.add(temp_user)
181 response = self.app.post(login_url,
181 response = self.app.post(login_url,
182 {'username': temp_user,
182 {'username': temp_user,
183 'password': 'test123'})
183 'password': 'test123'})
184
184
185 assert response.status == '302 Found'
185 assert response.status == '302 Found'
186 session = get_session_from_response(response)
186 session = get_session_from_response(response)
187 username = session['rhodecode_user'].get('username')
187 username = session['rhodecode_user'].get('username')
188 assert username == temp_user
188 assert username == temp_user
189 response = response.follow()
189 response = response.follow()
190 response.mustcontain('/%s' % HG_REPO)
190 response.mustcontain('/%s' % HG_REPO)
191
191
192 # new password should be bcrypted, after log-in and transfer
192 # new password should be bcrypted, after log-in and transfer
193 user = User.get_by_username(temp_user)
193 user = User.get_by_username(temp_user)
194 assert user.password.startswith('$')
194 assert user.password.startswith('$')
195
195
196 # REGISTRATIONS
196 # REGISTRATIONS
197 def test_register(self):
197 def test_register(self):
198 response = self.app.get(register_url)
198 response = self.app.get(register_url)
199 response.mustcontain('Create an Account')
199 response.mustcontain('Create an Account')
200
200
201 def test_register_err_same_username(self):
201 def test_register_err_same_username(self):
202 uname = 'test_admin'
202 uname = 'test_admin'
203 response = self.app.post(
203 response = self.app.post(
204 register_url,
204 register_url,
205 {
205 {
206 'username': uname,
206 'username': uname,
207 'password': 'test12',
207 'password': 'test12',
208 'password_confirmation': 'test12',
208 'password_confirmation': 'test12',
209 'email': 'goodmail@domain.com',
209 'email': 'goodmail@domain.com',
210 'firstname': 'test',
210 'firstname': 'test',
211 'lastname': 'test'
211 'lastname': 'test'
212 }
212 }
213 )
213 )
214
214
215 assertr = AssertResponse(response)
215 assertr = AssertResponse(response)
216 msg = validators.ValidUsername()._messages['username_exists']
216 msg = validators.ValidUsername()._messages['username_exists']
217 msg = msg % {'username': uname}
217 msg = msg % {'username': uname}
218 assertr.element_contains('#username+.error-message', msg)
218 assertr.element_contains('#username+.error-message', msg)
219
219
220 def test_register_err_same_email(self):
220 def test_register_err_same_email(self):
221 response = self.app.post(
221 response = self.app.post(
222 register_url,
222 register_url,
223 {
223 {
224 'username': 'test_admin_0',
224 'username': 'test_admin_0',
225 'password': 'test12',
225 'password': 'test12',
226 'password_confirmation': 'test12',
226 'password_confirmation': 'test12',
227 'email': 'test_admin@mail.com',
227 'email': 'test_admin@mail.com',
228 'firstname': 'test',
228 'firstname': 'test',
229 'lastname': 'test'
229 'lastname': 'test'
230 }
230 }
231 )
231 )
232
232
233 assertr = AssertResponse(response)
233 assertr = AssertResponse(response)
234 msg = validators.UniqSystemEmail()()._messages['email_taken']
234 msg = validators.UniqSystemEmail()()._messages['email_taken']
235 assertr.element_contains('#email+.error-message', msg)
235 assertr.element_contains('#email+.error-message', msg)
236
236
237 def test_register_err_same_email_case_sensitive(self):
237 def test_register_err_same_email_case_sensitive(self):
238 response = self.app.post(
238 response = self.app.post(
239 register_url,
239 register_url,
240 {
240 {
241 'username': 'test_admin_1',
241 'username': 'test_admin_1',
242 'password': 'test12',
242 'password': 'test12',
243 'password_confirmation': 'test12',
243 'password_confirmation': 'test12',
244 'email': 'TesT_Admin@mail.COM',
244 'email': 'TesT_Admin@mail.COM',
245 'firstname': 'test',
245 'firstname': 'test',
246 'lastname': 'test'
246 'lastname': 'test'
247 }
247 }
248 )
248 )
249 assertr = AssertResponse(response)
249 assertr = AssertResponse(response)
250 msg = validators.UniqSystemEmail()()._messages['email_taken']
250 msg = validators.UniqSystemEmail()()._messages['email_taken']
251 assertr.element_contains('#email+.error-message', msg)
251 assertr.element_contains('#email+.error-message', msg)
252
252
253 def test_register_err_wrong_data(self):
253 def test_register_err_wrong_data(self):
254 response = self.app.post(
254 response = self.app.post(
255 register_url,
255 register_url,
256 {
256 {
257 'username': 'xs',
257 'username': 'xs',
258 'password': 'test',
258 'password': 'test',
259 'password_confirmation': 'test',
259 'password_confirmation': 'test',
260 'email': 'goodmailm',
260 'email': 'goodmailm',
261 'firstname': 'test',
261 'firstname': 'test',
262 'lastname': 'test'
262 'lastname': 'test'
263 }
263 }
264 )
264 )
265 assert response.status == '200 OK'
265 assert response.status == '200 OK'
266 response.mustcontain('An email address must contain a single @')
266 response.mustcontain('An email address must contain a single @')
267 response.mustcontain('Enter a value 6 characters long or more')
267 response.mustcontain('Enter a value 6 characters long or more')
268
268
269 def test_register_err_username(self):
269 def test_register_err_username(self):
270 response = self.app.post(
270 response = self.app.post(
271 register_url,
271 register_url,
272 {
272 {
273 'username': 'error user',
273 'username': 'error user',
274 'password': 'test12',
274 'password': 'test12',
275 'password_confirmation': 'test12',
275 'password_confirmation': 'test12',
276 'email': 'goodmailm',
276 'email': 'goodmailm',
277 'firstname': 'test',
277 'firstname': 'test',
278 'lastname': 'test'
278 'lastname': 'test'
279 }
279 }
280 )
280 )
281
281
282 response.mustcontain('An email address must contain a single @')
282 response.mustcontain('An email address must contain a single @')
283 response.mustcontain(
283 response.mustcontain(
284 'Username may only contain '
284 'Username may only contain '
285 'alphanumeric characters underscores, '
285 'alphanumeric characters underscores, '
286 'periods or dashes and must begin with '
286 'periods or dashes and must begin with '
287 'alphanumeric character')
287 'alphanumeric character')
288
288
289 def test_register_err_case_sensitive(self):
289 def test_register_err_case_sensitive(self):
290 usr = 'Test_Admin'
290 usr = 'Test_Admin'
291 response = self.app.post(
291 response = self.app.post(
292 register_url,
292 register_url,
293 {
293 {
294 'username': usr,
294 'username': usr,
295 'password': 'test12',
295 'password': 'test12',
296 'password_confirmation': 'test12',
296 'password_confirmation': 'test12',
297 'email': 'goodmailm',
297 'email': 'goodmailm',
298 'firstname': 'test',
298 'firstname': 'test',
299 'lastname': 'test'
299 'lastname': 'test'
300 }
300 }
301 )
301 )
302
302
303 assertr = AssertResponse(response)
303 assertr = AssertResponse(response)
304 msg = validators.ValidUsername()._messages['username_exists']
304 msg = validators.ValidUsername()._messages['username_exists']
305 msg = msg % {'username': usr}
305 msg = msg % {'username': usr}
306 assertr.element_contains('#username+.error-message', msg)
306 assertr.element_contains('#username+.error-message', msg)
307
307
308 def test_register_special_chars(self):
308 def test_register_special_chars(self):
309 response = self.app.post(
309 response = self.app.post(
310 register_url,
310 register_url,
311 {
311 {
312 'username': 'xxxaxn',
312 'username': 'xxxaxn',
313 'password': 'Δ…Δ‡ΕΊΕΌΔ…Ε›Ε›Ε›Ε›',
313 'password': 'Δ…Δ‡ΕΊΕΌΔ…Ε›Ε›Ε›Ε›',
314 'password_confirmation': 'Δ…Δ‡ΕΊΕΌΔ…Ε›Ε›Ε›Ε›',
314 'password_confirmation': 'Δ…Δ‡ΕΊΕΌΔ…Ε›Ε›Ε›Ε›',
315 'email': 'goodmailm@test.plx',
315 'email': 'goodmailm@test.plx',
316 'firstname': 'test',
316 'firstname': 'test',
317 'lastname': 'test'
317 'lastname': 'test'
318 }
318 }
319 )
319 )
320
320
321 msg = validators.ValidPassword()._messages['invalid_password']
321 msg = validators.ValidPassword()._messages['invalid_password']
322 response.mustcontain(msg)
322 response.mustcontain(msg)
323
323
324 def test_register_password_mismatch(self):
324 def test_register_password_mismatch(self):
325 response = self.app.post(
325 response = self.app.post(
326 register_url,
326 register_url,
327 {
327 {
328 'username': 'xs',
328 'username': 'xs',
329 'password': '123qwe',
329 'password': '123qwe',
330 'password_confirmation': 'qwe123',
330 'password_confirmation': 'qwe123',
331 'email': 'goodmailm@test.plxa',
331 'email': 'goodmailm@test.plxa',
332 'firstname': 'test',
332 'firstname': 'test',
333 'lastname': 'test'
333 'lastname': 'test'
334 }
334 }
335 )
335 )
336 msg = validators.ValidPasswordsMatch()._messages['password_mismatch']
336 msg = validators.ValidPasswordsMatch()._messages['password_mismatch']
337 response.mustcontain(msg)
337 response.mustcontain(msg)
338
338
339 def test_register_ok(self):
339 def test_register_ok(self):
340 username = 'test_regular4'
340 username = 'test_regular4'
341 password = 'qweqwe'
341 password = 'qweqwe'
342 email = 'marcin@test.com'
342 email = 'marcin@test.com'
343 name = 'testname'
343 name = 'testname'
344 lastname = 'testlastname'
344 lastname = 'testlastname'
345
345
346 response = self.app.post(
346 response = self.app.post(
347 register_url,
347 register_url,
348 {
348 {
349 'username': username,
349 'username': username,
350 'password': password,
350 'password': password,
351 'password_confirmation': password,
351 'password_confirmation': password,
352 'email': email,
352 'email': email,
353 'firstname': name,
353 'firstname': name,
354 'lastname': lastname,
354 'lastname': lastname,
355 'admin': True
355 'admin': True
356 }
356 }
357 ) # This should be overriden
357 ) # This should be overriden
358 assert response.status == '302 Found'
358 assert response.status == '302 Found'
359 assert_session_flash(
359 assert_session_flash(
360 response, 'You have successfully registered with RhodeCode')
360 response, 'You have successfully registered with RhodeCode')
361
361
362 ret = Session().query(User).filter(
362 ret = Session().query(User).filter(
363 User.username == 'test_regular4').one()
363 User.username == 'test_regular4').one()
364 assert ret.username == username
364 assert ret.username == username
365 assert check_password(password, ret.password)
365 assert check_password(password, ret.password)
366 assert ret.email == email
366 assert ret.email == email
367 assert ret.name == name
367 assert ret.name == name
368 assert ret.lastname == lastname
368 assert ret.lastname == lastname
369 assert ret.api_key is not None
369 assert ret.auth_tokens is not None
370 assert not ret.admin
370 assert not ret.admin
371
371
372 def test_forgot_password_wrong_mail(self):
372 def test_forgot_password_wrong_mail(self):
373 bad_email = 'marcin@wrongmail.org'
373 bad_email = 'marcin@wrongmail.org'
374 response = self.app.post(
374 response = self.app.post(
375 pwd_reset_url, {'email': bad_email, }
375 pwd_reset_url, {'email': bad_email, }
376 )
376 )
377 assert_session_flash(response,
377 assert_session_flash(response,
378 'If such email exists, a password reset link was sent to it.')
378 'If such email exists, a password reset link was sent to it.')
379
379
380 def test_forgot_password(self, user_util):
380 def test_forgot_password(self, user_util):
381 response = self.app.get(pwd_reset_url)
381 response = self.app.get(pwd_reset_url)
382 assert response.status == '200 OK'
382 assert response.status == '200 OK'
383
383
384 user = user_util.create_user()
384 user = user_util.create_user()
385 user_id = user.user_id
385 user_id = user.user_id
386 email = user.email
386 email = user.email
387
387
388 response = self.app.post(pwd_reset_url, {'email': email, })
388 response = self.app.post(pwd_reset_url, {'email': email, })
389
389
390 assert_session_flash(response,
390 assert_session_flash(response,
391 'If such email exists, a password reset link was sent to it.')
391 'If such email exists, a password reset link was sent to it.')
392
392
393 # BAD KEY
393 # BAD KEY
394 confirm_url = '{}?key={}'.format(pwd_reset_confirm_url, 'badkey')
394 confirm_url = '{}?key={}'.format(pwd_reset_confirm_url, 'badkey')
395 response = self.app.get(confirm_url)
395 response = self.app.get(confirm_url)
396 assert response.status == '302 Found'
396 assert response.status == '302 Found'
397 assert response.location.endswith(pwd_reset_url)
397 assert response.location.endswith(pwd_reset_url)
398 assert_session_flash(response, 'Given reset token is invalid')
398 assert_session_flash(response, 'Given reset token is invalid')
399
399
400 response.follow() # cleanup flash
400 response.follow() # cleanup flash
401
401
402 # GOOD KEY
402 # GOOD KEY
403 key = UserApiKeys.query()\
403 key = UserApiKeys.query()\
404 .filter(UserApiKeys.user_id == user_id)\
404 .filter(UserApiKeys.user_id == user_id)\
405 .filter(UserApiKeys.role == UserApiKeys.ROLE_PASSWORD_RESET)\
405 .filter(UserApiKeys.role == UserApiKeys.ROLE_PASSWORD_RESET)\
406 .first()
406 .first()
407
407
408 assert key
408 assert key
409
409
410 confirm_url = '{}?key={}'.format(pwd_reset_confirm_url, key.api_key)
410 confirm_url = '{}?key={}'.format(pwd_reset_confirm_url, key.api_key)
411 response = self.app.get(confirm_url)
411 response = self.app.get(confirm_url)
412 assert response.status == '302 Found'
412 assert response.status == '302 Found'
413 assert response.location.endswith(login_url)
413 assert response.location.endswith(login_url)
414
414
415 assert_session_flash(
415 assert_session_flash(
416 response,
416 response,
417 'Your password reset was successful, '
417 'Your password reset was successful, '
418 'a new password has been sent to your email')
418 'a new password has been sent to your email')
419
419
420 response.follow()
420 response.follow()
421
421
422 def _get_api_whitelist(self, values=None):
422 def _get_api_whitelist(self, values=None):
423 config = {'api_access_controllers_whitelist': values or []}
423 config = {'api_access_controllers_whitelist': values or []}
424 return config
424 return config
425
425
426 @pytest.mark.parametrize("test_name, auth_token", [
426 @pytest.mark.parametrize("test_name, auth_token", [
427 ('none', None),
427 ('none', None),
428 ('empty_string', ''),
428 ('empty_string', ''),
429 ('fake_number', '123456'),
429 ('fake_number', '123456'),
430 ('proper_auth_token', None)
430 ('proper_auth_token', None)
431 ])
431 ])
432 def test_access_not_whitelisted_page_via_auth_token(
432 def test_access_not_whitelisted_page_via_auth_token(
433 self, test_name, auth_token, user_admin):
433 self, test_name, auth_token, user_admin):
434
434
435 whitelist = self._get_api_whitelist([])
435 whitelist = self._get_api_whitelist([])
436 with mock.patch.dict('rhodecode.CONFIG', whitelist):
436 with mock.patch.dict('rhodecode.CONFIG', whitelist):
437 assert [] == whitelist['api_access_controllers_whitelist']
437 assert [] == whitelist['api_access_controllers_whitelist']
438 if test_name == 'proper_auth_token':
438 if test_name == 'proper_auth_token':
439 # use builtin if api_key is None
439 # use builtin if api_key is None
440 auth_token = user_admin.api_key
440 auth_token = user_admin.api_key
441
441
442 with fixture.anon_access(False):
442 with fixture.anon_access(False):
443 self.app.get(url(controller='changeset',
443 self.app.get(url(controller='changeset',
444 action='changeset_raw',
444 action='changeset_raw',
445 repo_name=HG_REPO, revision='tip',
445 repo_name=HG_REPO, revision='tip',
446 api_key=auth_token),
446 api_key=auth_token),
447 status=302)
447 status=302)
448
448
449 @pytest.mark.parametrize("test_name, auth_token, code", [
449 @pytest.mark.parametrize("test_name, auth_token, code", [
450 ('none', None, 302),
450 ('none', None, 302),
451 ('empty_string', '', 302),
451 ('empty_string', '', 302),
452 ('fake_number', '123456', 302),
452 ('fake_number', '123456', 302),
453 ('proper_auth_token', None, 200)
453 ('proper_auth_token', None, 200)
454 ])
454 ])
455 def test_access_whitelisted_page_via_auth_token(
455 def test_access_whitelisted_page_via_auth_token(
456 self, test_name, auth_token, code, user_admin):
456 self, test_name, auth_token, code, user_admin):
457
457
458 whitelist_entry = ['ChangesetController:changeset_raw']
458 whitelist_entry = ['ChangesetController:changeset_raw']
459 whitelist = self._get_api_whitelist(whitelist_entry)
459 whitelist = self._get_api_whitelist(whitelist_entry)
460
460
461 with mock.patch.dict('rhodecode.CONFIG', whitelist):
461 with mock.patch.dict('rhodecode.CONFIG', whitelist):
462 assert whitelist_entry == whitelist['api_access_controllers_whitelist']
462 assert whitelist_entry == whitelist['api_access_controllers_whitelist']
463
463
464 if test_name == 'proper_auth_token':
464 if test_name == 'proper_auth_token':
465 auth_token = user_admin.api_key
465 auth_token = user_admin.api_key
466 assert auth_token
466
467
467 with fixture.anon_access(False):
468 with fixture.anon_access(False):
468 self.app.get(url(controller='changeset',
469 self.app.get(url(controller='changeset',
469 action='changeset_raw',
470 action='changeset_raw',
470 repo_name=HG_REPO, revision='tip',
471 repo_name=HG_REPO, revision='tip',
471 api_key=auth_token),
472 api_key=auth_token),
472 status=code)
473 status=code)
473
474
474 def test_access_page_via_extra_auth_token(self):
475 def test_access_page_via_extra_auth_token(self):
475 whitelist = self._get_api_whitelist(
476 whitelist = self._get_api_whitelist(
476 ['ChangesetController:changeset_raw'])
477 ['ChangesetController:changeset_raw'])
477 with mock.patch.dict('rhodecode.CONFIG', whitelist):
478 with mock.patch.dict('rhodecode.CONFIG', whitelist):
478 assert ['ChangesetController:changeset_raw'] == \
479 assert ['ChangesetController:changeset_raw'] == \
479 whitelist['api_access_controllers_whitelist']
480 whitelist['api_access_controllers_whitelist']
480
481
481 new_auth_token = AuthTokenModel().create(
482 new_auth_token = AuthTokenModel().create(
482 TEST_USER_ADMIN_LOGIN, 'test')
483 TEST_USER_ADMIN_LOGIN, 'test')
483 Session().commit()
484 Session().commit()
484 with fixture.anon_access(False):
485 with fixture.anon_access(False):
485 self.app.get(url(controller='changeset',
486 self.app.get(url(controller='changeset',
486 action='changeset_raw',
487 action='changeset_raw',
487 repo_name=HG_REPO, revision='tip',
488 repo_name=HG_REPO, revision='tip',
488 api_key=new_auth_token.api_key),
489 api_key=new_auth_token.api_key),
489 status=200)
490 status=200)
490
491
491 def test_access_page_via_expired_auth_token(self):
492 def test_access_page_via_expired_auth_token(self):
492 whitelist = self._get_api_whitelist(
493 whitelist = self._get_api_whitelist(
493 ['ChangesetController:changeset_raw'])
494 ['ChangesetController:changeset_raw'])
494 with mock.patch.dict('rhodecode.CONFIG', whitelist):
495 with mock.patch.dict('rhodecode.CONFIG', whitelist):
495 assert ['ChangesetController:changeset_raw'] == \
496 assert ['ChangesetController:changeset_raw'] == \
496 whitelist['api_access_controllers_whitelist']
497 whitelist['api_access_controllers_whitelist']
497
498
498 new_auth_token = AuthTokenModel().create(
499 new_auth_token = AuthTokenModel().create(
499 TEST_USER_ADMIN_LOGIN, 'test')
500 TEST_USER_ADMIN_LOGIN, 'test')
500 Session().commit()
501 Session().commit()
501 # patch the api key and make it expired
502 # patch the api key and make it expired
502 new_auth_token.expires = 0
503 new_auth_token.expires = 0
503 Session().add(new_auth_token)
504 Session().add(new_auth_token)
504 Session().commit()
505 Session().commit()
505 with fixture.anon_access(False):
506 with fixture.anon_access(False):
506 self.app.get(url(controller='changeset',
507 self.app.get(url(controller='changeset',
507 action='changeset_raw',
508 action='changeset_raw',
508 repo_name=HG_REPO, revision='tip',
509 repo_name=HG_REPO, revision='tip',
509 api_key=new_auth_token.api_key),
510 api_key=new_auth_token.api_key),
510 status=302)
511 status=302)
@@ -1,86 +1,92 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import mock
21 import mock
22 import pytest
22 import pytest
23
23
24 from rhodecode.lib.db_manage import DbManage
24 from rhodecode.lib.db_manage import DbManage
25 from rhodecode.model import db
25 from rhodecode.model import db
26
26
27
27
28 @pytest.fixture
28 @pytest.fixture
29 def db_manage(pylonsapp):
29 def db_manage(pylonsapp):
30 db_manage = DbManage(
30 db_manage = DbManage(
31 log_sql=True, dbconf='fake', root='fake', tests=False,
31 log_sql=True, dbconf='fake', root='fake', tests=False,
32 cli_args={}, SESSION=db.Session())
32 cli_args={}, SESSION=db.Session())
33 return db_manage
33 return db_manage
34
34
35
35
36 @pytest.fixture(autouse=True)
36 @pytest.fixture(autouse=True)
37 def session_rollback(pylonsapp, request):
37 def session_rollback(pylonsapp, request):
38 """
38 """
39 Rollback the database session after the test run.
39 Rollback the database session after the test run.
40
40
41 Intended usage is for tests wich mess with the database but don't
41 Intended usage is for tests wich mess with the database but don't
42 commit. In this case a rollback after the test run will leave the database
42 commit. In this case a rollback after the test run will leave the database
43 in a clean state.
43 in a clean state.
44
44
45 This is still a workaround until we find a way to isolate the tests better
45 This is still a workaround until we find a way to isolate the tests better
46 from each other.
46 from each other.
47 """
47 """
48 @request.addfinalizer
48 @request.addfinalizer
49 def cleanup():
49 def cleanup():
50 db.Session().rollback()
50 db.Session().rollback()
51
51
52
52
53 def test_create_admin_and_prompt_uses_getpass(db_manage):
53 def test_create_admin_and_prompt_uses_getpass(db_manage):
54 db_manage.cli_args = {
54 db_manage.cli_args = {
55 'username': 'test',
55 'username': 'test',
56 'email': 'test@example.com'}
56 'email': 'test@example.com'}
57 with mock.patch('getpass.getpass', return_value='password') as getpass:
57 with mock.patch('getpass.getpass', return_value='password') as getpass:
58 db_manage.create_admin_and_prompt()
58 db_manage.create_admin_and_prompt()
59 assert getpass.called
59 assert getpass.called
60
60
61
61
62 def test_create_admin_and_prompt_sets_the_api_key(db_manage):
62 def test_create_admin_and_prompt_sets_the_api_key(db_manage):
63 db_manage.cli_args = {
63 db_manage.cli_args = {
64 'username': 'test',
64 'username': 'test',
65 'password': 'testpassword',
65 'password': 'testpassword',
66 'email': 'test@example.com',
66 'email': 'test@example.com',
67 'api_key': 'testkey'}
67 'api_key': 'testkey'}
68 with mock.patch.object(db_manage, 'create_user') as create_user:
68 with mock.patch.object(db_manage, 'create_user') as create_user:
69 db_manage.create_admin_and_prompt()
69 db_manage.create_admin_and_prompt()
70
70
71 assert create_user.call_args[1]['api_key'] == 'testkey'
71 assert create_user.call_args[1]['api_key'] == 'testkey'
72
72
73
73
74 def test_create_user_sets_the_api_key(db_manage):
74 @pytest.mark.parametrize('add_keys', [True, False])
75 def test_create_user_sets_the_api_key(db_manage, add_keys):
76 username = 'test_add_keys_{}'.format(add_keys)
75 db_manage.create_user(
77 db_manage.create_user(
76 'test', 'testpassword', 'test@example.com',
78 username, 'testpassword', 'test@example.com',
77 api_key='testkey')
79 api_key=add_keys)
78
80
79 user = db.User.get_by_username('test')
81 user = db.User.get_by_username(username)
80 assert user.api_key == 'testkey'
82 if add_keys:
83 assert 2 == len(user.auth_tokens)
84 else:
85 # only feed token
86 assert 1 == len(user.auth_tokens)
81
87
82
88
83 def test_create_user_without_api_key(db_manage):
89 def test_create_user_without_api_key(db_manage):
84 db_manage.create_user('test', 'testpassword', 'test@example.com')
90 db_manage.create_user('test', 'testpassword', 'test@example.com')
85 user = db.User.get_by_username('test')
91 user = db.User.get_by_username('test')
86 assert user.api_key
92 assert user.api_key is None
@@ -1,245 +1,242 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import pytest
21 import pytest
22 from sqlalchemy.sql.expression import true
22 from sqlalchemy.sql.expression import true
23
23
24 from rhodecode.model.db import User, UserGroup, UserGroupMember, UserEmailMap,\
24 from rhodecode.model.db import User, UserGroup, UserGroupMember, UserEmailMap,\
25 Permission, UserIpMap
25 Permission, UserIpMap
26 from rhodecode.model.meta import Session
26 from rhodecode.model.meta import Session
27 from rhodecode.model.user import UserModel
27 from rhodecode.model.user import UserModel
28 from rhodecode.model.user_group import UserGroupModel
28 from rhodecode.model.user_group import UserGroupModel
29 from rhodecode.model.repo import RepoModel
29 from rhodecode.model.repo import RepoModel
30 from rhodecode.model.repo_group import RepoGroupModel
30 from rhodecode.model.repo_group import RepoGroupModel
31 from rhodecode.tests.fixture import Fixture
31 from rhodecode.tests.fixture import Fixture
32
32
33 fixture = Fixture()
33 fixture = Fixture()
34
34
35
35
36 @pytest.fixture
36 @pytest.fixture
37 def test_user(request, pylonsapp):
37 def test_user(request, pylonsapp):
38 usr = UserModel().create_or_update(
38 usr = UserModel().create_or_update(
39 username=u'test_user',
39 username=u'test_user',
40 password=u'qweqwe',
40 password=u'qweqwe',
41 email=u'main_email@rhodecode.org',
41 email=u'main_email@rhodecode.org',
42 firstname=u'u1', lastname=u'u1')
42 firstname=u'u1', lastname=u'u1')
43 Session().commit()
43 Session().commit()
44 assert User.get_by_username(u'test_user') == usr
44 assert User.get_by_username(u'test_user') == usr
45
45
46 @request.addfinalizer
46 @request.addfinalizer
47 def cleanup():
47 def cleanup():
48 if UserModel().get_user(usr.user_id) is None:
48 if UserModel().get_user(usr.user_id) is None:
49 return
49 return
50
50
51 perm = Permission.query().all()
51 perm = Permission.query().all()
52 for p in perm:
52 for p in perm:
53 UserModel().revoke_perm(usr, p)
53 UserModel().revoke_perm(usr, p)
54
54
55 UserModel().delete(usr.user_id)
55 UserModel().delete(usr.user_id)
56 Session().commit()
56 Session().commit()
57
57
58 return usr
58 return usr
59
59
60
60
61 def test_create_and_remove(test_user):
61 def test_create_and_remove(test_user):
62 usr = test_user
62 usr = test_user
63
63
64 # make user group
64 # make user group
65 user_group = fixture.create_user_group('some_example_group')
65 user_group = fixture.create_user_group('some_example_group')
66 Session().commit()
66 Session().commit()
67
67
68 UserGroupModel().add_user_to_group(user_group, usr)
68 UserGroupModel().add_user_to_group(user_group, usr)
69 Session().commit()
69 Session().commit()
70
70
71 assert UserGroup.get(user_group.users_group_id) == user_group
71 assert UserGroup.get(user_group.users_group_id) == user_group
72 assert UserGroupMember.query().count() == 1
72 assert UserGroupMember.query().count() == 1
73 UserModel().delete(usr.user_id)
73 UserModel().delete(usr.user_id)
74 Session().commit()
74 Session().commit()
75
75
76 assert UserGroupMember.query().all() == []
76 assert UserGroupMember.query().all() == []
77
77
78
78
79 def test_additonal_email_as_main(test_user):
79 def test_additonal_email_as_main(test_user):
80 with pytest.raises(AttributeError):
80 with pytest.raises(AttributeError):
81 m = UserEmailMap()
81 m = UserEmailMap()
82 m.email = test_user.email
82 m.email = test_user.email
83 m.user = test_user
83 m.user = test_user
84 Session().add(m)
84 Session().add(m)
85 Session().commit()
85 Session().commit()
86
86
87
87
88 def test_extra_email_map(test_user):
88 def test_extra_email_map(test_user):
89
89
90 m = UserEmailMap()
90 m = UserEmailMap()
91 m.email = u'main_email2@rhodecode.org'
91 m.email = u'main_email2@rhodecode.org'
92 m.user = test_user
92 m.user = test_user
93 Session().add(m)
93 Session().add(m)
94 Session().commit()
94 Session().commit()
95
95
96 u = User.get_by_email(email='main_email@rhodecode.org')
96 u = User.get_by_email(email='main_email@rhodecode.org')
97 assert test_user.user_id == u.user_id
97 assert test_user.user_id == u.user_id
98 assert test_user.username == u.username
98 assert test_user.username == u.username
99
99
100 u = User.get_by_email(email='main_email2@rhodecode.org')
100 u = User.get_by_email(email='main_email2@rhodecode.org')
101 assert test_user.user_id == u.user_id
101 assert test_user.user_id == u.user_id
102 assert test_user.username == u.username
102 assert test_user.username == u.username
103 u = User.get_by_email(email='main_email3@rhodecode.org')
103 u = User.get_by_email(email='main_email3@rhodecode.org')
104 assert u is None
104 assert u is None
105
105
106
106
107 def test_get_api_data_replaces_secret_data_by_default(test_user):
107 def test_get_api_data_replaces_secret_data_by_default(test_user):
108 api_data = test_user.get_api_data()
108 api_data = test_user.get_api_data()
109 api_key_length = 40
109 api_key_length = 40
110 expected_replacement = '*' * api_key_length
110 expected_replacement = '*' * api_key_length
111
111
112 assert api_data['api_key'] == expected_replacement
113 for key in api_data['api_keys']:
112 for key in api_data['api_keys']:
114 assert key == expected_replacement
113 assert key == expected_replacement
115
114
116
115
117 def test_get_api_data_includes_secret_data_if_activated(test_user):
116 def test_get_api_data_includes_secret_data_if_activated(test_user):
118 api_data = test_user.get_api_data(include_secrets=True)
117 api_data = test_user.get_api_data(include_secrets=True)
119
120 assert api_data['api_key'] == test_user.api_key
121 assert api_data['api_keys'] == test_user.auth_tokens
118 assert api_data['api_keys'] == test_user.auth_tokens
122
119
123
120
124 def test_add_perm(test_user):
121 def test_add_perm(test_user):
125 perm = Permission.query().all()[0]
122 perm = Permission.query().all()[0]
126 UserModel().grant_perm(test_user, perm)
123 UserModel().grant_perm(test_user, perm)
127 Session().commit()
124 Session().commit()
128 assert UserModel().has_perm(test_user, perm)
125 assert UserModel().has_perm(test_user, perm)
129
126
130
127
131 def test_has_perm(test_user):
128 def test_has_perm(test_user):
132 perm = Permission.query().all()
129 perm = Permission.query().all()
133 for p in perm:
130 for p in perm:
134 assert not UserModel().has_perm(test_user, p)
131 assert not UserModel().has_perm(test_user, p)
135
132
136
133
137 def test_revoke_perm(test_user):
134 def test_revoke_perm(test_user):
138 perm = Permission.query().all()[0]
135 perm = Permission.query().all()[0]
139 UserModel().grant_perm(test_user, perm)
136 UserModel().grant_perm(test_user, perm)
140 Session().commit()
137 Session().commit()
141 assert UserModel().has_perm(test_user, perm)
138 assert UserModel().has_perm(test_user, perm)
142
139
143 # revoke
140 # revoke
144 UserModel().revoke_perm(test_user, perm)
141 UserModel().revoke_perm(test_user, perm)
145 Session().commit()
142 Session().commit()
146 assert not UserModel().has_perm(test_user, perm)
143 assert not UserModel().has_perm(test_user, perm)
147
144
148
145
149 @pytest.mark.parametrize("ip_range, expected, expect_errors", [
146 @pytest.mark.parametrize("ip_range, expected, expect_errors", [
150 ('', [], False),
147 ('', [], False),
151 ('127.0.0.1', ['127.0.0.1'], False),
148 ('127.0.0.1', ['127.0.0.1'], False),
152 ('127.0.0.1,127.0.0.2', ['127.0.0.1', '127.0.0.2'], False),
149 ('127.0.0.1,127.0.0.2', ['127.0.0.1', '127.0.0.2'], False),
153 ('127.0.0.1 , 127.0.0.2', ['127.0.0.1', '127.0.0.2'], False),
150 ('127.0.0.1 , 127.0.0.2', ['127.0.0.1', '127.0.0.2'], False),
154 (
151 (
155 '127.0.0.1,172.172.172.0,127.0.0.2',
152 '127.0.0.1,172.172.172.0,127.0.0.2',
156 ['127.0.0.1', '172.172.172.0', '127.0.0.2'], False),
153 ['127.0.0.1', '172.172.172.0', '127.0.0.2'], False),
157 (
154 (
158 '127.0.0.1-127.0.0.5',
155 '127.0.0.1-127.0.0.5',
159 ['127.0.0.1', '127.0.0.2', '127.0.0.3', '127.0.0.4', '127.0.0.5'],
156 ['127.0.0.1', '127.0.0.2', '127.0.0.3', '127.0.0.4', '127.0.0.5'],
160 False),
157 False),
161 (
158 (
162 '127.0.0.1 - 127.0.0.5',
159 '127.0.0.1 - 127.0.0.5',
163 ['127.0.0.1', '127.0.0.2', '127.0.0.3', '127.0.0.4', '127.0.0.5'],
160 ['127.0.0.1', '127.0.0.2', '127.0.0.3', '127.0.0.4', '127.0.0.5'],
164 False
161 False
165 ),
162 ),
166 ('-', [], True),
163 ('-', [], True),
167 ('127.0.0.1-32', [], True),
164 ('127.0.0.1-32', [], True),
168 (
165 (
169 '127.0.0.1,127.0.0.1,127.0.0.1,127.0.0.1-127.0.0.2,127.0.0.2',
166 '127.0.0.1,127.0.0.1,127.0.0.1,127.0.0.1-127.0.0.2,127.0.0.2',
170 ['127.0.0.1', '127.0.0.2'], False),
167 ['127.0.0.1', '127.0.0.2'], False),
171 (
168 (
172 '127.0.0.1-127.0.0.2,127.0.0.4-127.0.0.6,',
169 '127.0.0.1-127.0.0.2,127.0.0.4-127.0.0.6,',
173 ['127.0.0.1', '127.0.0.2', '127.0.0.4', '127.0.0.5', '127.0.0.6'],
170 ['127.0.0.1', '127.0.0.2', '127.0.0.4', '127.0.0.5', '127.0.0.6'],
174 False
171 False
175 ),
172 ),
176 (
173 (
177 '127.0.0.1-127.0.0.2,127.0.0.1-127.0.0.6,',
174 '127.0.0.1-127.0.0.2,127.0.0.1-127.0.0.6,',
178 ['127.0.0.1', '127.0.0.2', '127.0.0.3', '127.0.0.4', '127.0.0.5',
175 ['127.0.0.1', '127.0.0.2', '127.0.0.3', '127.0.0.4', '127.0.0.5',
179 '127.0.0.6'],
176 '127.0.0.6'],
180 False
177 False
181 ),
178 ),
182 ])
179 ])
183 def test_ip_range_generator(ip_range, expected, expect_errors):
180 def test_ip_range_generator(ip_range, expected, expect_errors):
184 func = UserModel().parse_ip_range
181 func = UserModel().parse_ip_range
185 if expect_errors:
182 if expect_errors:
186 pytest.raises(Exception, func, ip_range)
183 pytest.raises(Exception, func, ip_range)
187 else:
184 else:
188 parsed_list = func(ip_range)
185 parsed_list = func(ip_range)
189 assert parsed_list == expected
186 assert parsed_list == expected
190
187
191
188
192 def test_user_delete_cascades_ip_whitelist(test_user):
189 def test_user_delete_cascades_ip_whitelist(test_user):
193 sample_ip = '1.1.1.1'
190 sample_ip = '1.1.1.1'
194 uid_map = UserIpMap(user_id=test_user.user_id, ip_addr=sample_ip)
191 uid_map = UserIpMap(user_id=test_user.user_id, ip_addr=sample_ip)
195 Session().add(uid_map)
192 Session().add(uid_map)
196 Session().delete(test_user)
193 Session().delete(test_user)
197 try:
194 try:
198 Session().flush()
195 Session().flush()
199 finally:
196 finally:
200 Session().rollback()
197 Session().rollback()
201
198
202
199
203 def test_account_for_deactivation_generation(test_user):
200 def test_account_for_deactivation_generation(test_user):
204 accounts = UserModel().get_accounts_in_creation_order(
201 accounts = UserModel().get_accounts_in_creation_order(
205 current_user=test_user)
202 current_user=test_user)
206 # current user should be #1 in the list
203 # current user should be #1 in the list
207 assert accounts[0] == test_user.user_id
204 assert accounts[0] == test_user.user_id
208 active_users = User.query().filter(User.active == true()).count()
205 active_users = User.query().filter(User.active == true()).count()
209 assert active_users == len(accounts)
206 assert active_users == len(accounts)
210
207
211
208
212 def test_user_delete_cascades_permissions_on_repo(backend, test_user):
209 def test_user_delete_cascades_permissions_on_repo(backend, test_user):
213 test_repo = backend.create_repo()
210 test_repo = backend.create_repo()
214 RepoModel().grant_user_permission(
211 RepoModel().grant_user_permission(
215 test_repo, test_user, 'repository.write')
212 test_repo, test_user, 'repository.write')
216 Session().commit()
213 Session().commit()
217
214
218 assert test_user.repo_to_perm
215 assert test_user.repo_to_perm
219
216
220 UserModel().delete(test_user)
217 UserModel().delete(test_user)
221 Session().commit()
218 Session().commit()
222
219
223
220
224 def test_user_delete_cascades_permissions_on_repo_group(
221 def test_user_delete_cascades_permissions_on_repo_group(
225 test_repo_group, test_user):
222 test_repo_group, test_user):
226 RepoGroupModel().grant_user_permission(
223 RepoGroupModel().grant_user_permission(
227 test_repo_group, test_user, 'group.write')
224 test_repo_group, test_user, 'group.write')
228 Session().commit()
225 Session().commit()
229
226
230 assert test_user.repo_group_to_perm
227 assert test_user.repo_group_to_perm
231
228
232 Session().delete(test_user)
229 Session().delete(test_user)
233 Session().commit()
230 Session().commit()
234
231
235
232
236 def test_user_delete_cascades_permissions_on_user_group(
233 def test_user_delete_cascades_permissions_on_user_group(
237 test_user_group, test_user):
234 test_user_group, test_user):
238 UserGroupModel().grant_user_permission(
235 UserGroupModel().grant_user_permission(
239 test_user_group, test_user, 'usergroup.write')
236 test_user_group, test_user, 'usergroup.write')
240 Session().commit()
237 Session().commit()
241
238
242 assert test_user.user_group_to_perm
239 assert test_user.user_group_to_perm
243
240
244 Session().delete(test_user)
241 Session().delete(test_user)
245 Session().commit()
242 Session().commit()
General Comments 0
You need to be logged in to leave comments. Login now