##// END OF EJS Templates
auth-tokens: fixed tests
marcink -
r1482:9278d852 default
parent child Browse files
Show More
@@ -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,3928 +1,3934 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 Models for RhodeCode Enterprise
22 Database Models for RhodeCode Enterprise
23 """
23 """
24
24
25 import re
25 import re
26 import os
26 import os
27 import time
27 import time
28 import hashlib
28 import hashlib
29 import logging
29 import logging
30 import datetime
30 import datetime
31 import warnings
31 import warnings
32 import ipaddress
32 import ipaddress
33 import functools
33 import functools
34 import traceback
34 import traceback
35 import collections
35 import collections
36
36
37
37
38 from sqlalchemy import *
38 from sqlalchemy import *
39 from sqlalchemy.ext.declarative import declared_attr
39 from sqlalchemy.ext.declarative import declared_attr
40 from sqlalchemy.ext.hybrid import hybrid_property
40 from sqlalchemy.ext.hybrid import hybrid_property
41 from sqlalchemy.orm import (
41 from sqlalchemy.orm import (
42 relationship, joinedload, class_mapper, validates, aliased)
42 relationship, joinedload, class_mapper, validates, aliased)
43 from sqlalchemy.sql.expression import true
43 from sqlalchemy.sql.expression import true
44 from beaker.cache import cache_region
44 from beaker.cache import cache_region
45 from webob.exc import HTTPNotFound
45 from webob.exc import HTTPNotFound
46 from zope.cachedescriptors.property import Lazy as LazyProperty
46 from zope.cachedescriptors.property import Lazy as LazyProperty
47
47
48 from pylons import url
48 from pylons import url
49 from pylons.i18n.translation import lazy_ugettext as _
49 from pylons.i18n.translation import lazy_ugettext as _
50
50
51 from rhodecode.lib.vcs import get_vcs_instance
51 from rhodecode.lib.vcs import get_vcs_instance
52 from rhodecode.lib.vcs.backends.base import EmptyCommit, Reference
52 from rhodecode.lib.vcs.backends.base import EmptyCommit, Reference
53 from rhodecode.lib.utils2 import (
53 from rhodecode.lib.utils2 import (
54 str2bool, safe_str, get_commit_safe, safe_unicode, md5_safe,
54 str2bool, safe_str, get_commit_safe, safe_unicode, md5_safe,
55 time_to_datetime, aslist, Optional, safe_int, get_clone_url, AttributeDict,
55 time_to_datetime, aslist, Optional, safe_int, get_clone_url, AttributeDict,
56 glob2re, StrictAttributeDict, cleaned_uri)
56 glob2re, StrictAttributeDict, cleaned_uri)
57 from rhodecode.lib.jsonalchemy import MutationObj, MutationList, JsonType
57 from rhodecode.lib.jsonalchemy import MutationObj, MutationList, JsonType
58 from rhodecode.lib.ext_json import json
58 from rhodecode.lib.ext_json import json
59 from rhodecode.lib.caching_query import FromCache
59 from rhodecode.lib.caching_query import FromCache
60 from rhodecode.lib.encrypt import AESCipher
60 from rhodecode.lib.encrypt import AESCipher
61
61
62 from rhodecode.model.meta import Base, Session
62 from rhodecode.model.meta import Base, Session
63
63
64 URL_SEP = '/'
64 URL_SEP = '/'
65 log = logging.getLogger(__name__)
65 log = logging.getLogger(__name__)
66
66
67 # =============================================================================
67 # =============================================================================
68 # BASE CLASSES
68 # BASE CLASSES
69 # =============================================================================
69 # =============================================================================
70
70
71 # this is propagated from .ini file rhodecode.encrypted_values.secret or
71 # this is propagated from .ini file rhodecode.encrypted_values.secret or
72 # beaker.session.secret if first is not set.
72 # beaker.session.secret if first is not set.
73 # and initialized at environment.py
73 # and initialized at environment.py
74 ENCRYPTION_KEY = None
74 ENCRYPTION_KEY = None
75
75
76 # used to sort permissions by types, '#' used here is not allowed to be in
76 # used to sort permissions by types, '#' used here is not allowed to be in
77 # usernames, and it's very early in sorted string.printable table.
77 # usernames, and it's very early in sorted string.printable table.
78 PERMISSION_TYPE_SORT = {
78 PERMISSION_TYPE_SORT = {
79 'admin': '####',
79 'admin': '####',
80 'write': '###',
80 'write': '###',
81 'read': '##',
81 'read': '##',
82 'none': '#',
82 'none': '#',
83 }
83 }
84
84
85
85
86 def display_sort(obj):
86 def display_sort(obj):
87 """
87 """
88 Sort function used to sort permissions in .permissions() function of
88 Sort function used to sort permissions in .permissions() function of
89 Repository, RepoGroup, UserGroup. Also it put the default user in front
89 Repository, RepoGroup, UserGroup. Also it put the default user in front
90 of all other resources
90 of all other resources
91 """
91 """
92
92
93 if obj.username == User.DEFAULT_USER:
93 if obj.username == User.DEFAULT_USER:
94 return '#####'
94 return '#####'
95 prefix = PERMISSION_TYPE_SORT.get(obj.permission.split('.')[-1], '')
95 prefix = PERMISSION_TYPE_SORT.get(obj.permission.split('.')[-1], '')
96 return prefix + obj.username
96 return prefix + obj.username
97
97
98
98
99 def _hash_key(k):
99 def _hash_key(k):
100 return md5_safe(k)
100 return md5_safe(k)
101
101
102
102
103 class EncryptedTextValue(TypeDecorator):
103 class EncryptedTextValue(TypeDecorator):
104 """
104 """
105 Special column for encrypted long text data, use like::
105 Special column for encrypted long text data, use like::
106
106
107 value = Column("encrypted_value", EncryptedValue(), nullable=False)
107 value = Column("encrypted_value", EncryptedValue(), nullable=False)
108
108
109 This column is intelligent so if value is in unencrypted form it return
109 This column is intelligent so if value is in unencrypted form it return
110 unencrypted form, but on save it always encrypts
110 unencrypted form, but on save it always encrypts
111 """
111 """
112 impl = Text
112 impl = Text
113
113
114 def process_bind_param(self, value, dialect):
114 def process_bind_param(self, value, dialect):
115 if not value:
115 if not value:
116 return value
116 return value
117 if value.startswith('enc$aes$') or value.startswith('enc$aes_hmac$'):
117 if value.startswith('enc$aes$') or value.startswith('enc$aes_hmac$'):
118 # protect against double encrypting if someone manually starts
118 # protect against double encrypting if someone manually starts
119 # doing
119 # doing
120 raise ValueError('value needs to be in unencrypted format, ie. '
120 raise ValueError('value needs to be in unencrypted format, ie. '
121 'not starting with enc$aes')
121 'not starting with enc$aes')
122 return 'enc$aes_hmac$%s' % AESCipher(
122 return 'enc$aes_hmac$%s' % AESCipher(
123 ENCRYPTION_KEY, hmac=True).encrypt(value)
123 ENCRYPTION_KEY, hmac=True).encrypt(value)
124
124
125 def process_result_value(self, value, dialect):
125 def process_result_value(self, value, dialect):
126 import rhodecode
126 import rhodecode
127
127
128 if not value:
128 if not value:
129 return value
129 return value
130
130
131 parts = value.split('$', 3)
131 parts = value.split('$', 3)
132 if not len(parts) == 3:
132 if not len(parts) == 3:
133 # probably not encrypted values
133 # probably not encrypted values
134 return value
134 return value
135 else:
135 else:
136 if parts[0] != 'enc':
136 if parts[0] != 'enc':
137 # parts ok but without our header ?
137 # parts ok but without our header ?
138 return value
138 return value
139 enc_strict_mode = str2bool(rhodecode.CONFIG.get(
139 enc_strict_mode = str2bool(rhodecode.CONFIG.get(
140 'rhodecode.encrypted_values.strict') or True)
140 'rhodecode.encrypted_values.strict') or True)
141 # at that stage we know it's our encryption
141 # at that stage we know it's our encryption
142 if parts[1] == 'aes':
142 if parts[1] == 'aes':
143 decrypted_data = AESCipher(ENCRYPTION_KEY).decrypt(parts[2])
143 decrypted_data = AESCipher(ENCRYPTION_KEY).decrypt(parts[2])
144 elif parts[1] == 'aes_hmac':
144 elif parts[1] == 'aes_hmac':
145 decrypted_data = AESCipher(
145 decrypted_data = AESCipher(
146 ENCRYPTION_KEY, hmac=True,
146 ENCRYPTION_KEY, hmac=True,
147 strict_verification=enc_strict_mode).decrypt(parts[2])
147 strict_verification=enc_strict_mode).decrypt(parts[2])
148 else:
148 else:
149 raise ValueError(
149 raise ValueError(
150 'Encryption type part is wrong, must be `aes` '
150 'Encryption type part is wrong, must be `aes` '
151 'or `aes_hmac`, got `%s` instead' % (parts[1]))
151 'or `aes_hmac`, got `%s` instead' % (parts[1]))
152 return decrypted_data
152 return decrypted_data
153
153
154
154
155 class BaseModel(object):
155 class BaseModel(object):
156 """
156 """
157 Base Model for all classes
157 Base Model for all classes
158 """
158 """
159
159
160 @classmethod
160 @classmethod
161 def _get_keys(cls):
161 def _get_keys(cls):
162 """return column names for this model """
162 """return column names for this model """
163 return class_mapper(cls).c.keys()
163 return class_mapper(cls).c.keys()
164
164
165 def get_dict(self):
165 def get_dict(self):
166 """
166 """
167 return dict with keys and values corresponding
167 return dict with keys and values corresponding
168 to this model data """
168 to this model data """
169
169
170 d = {}
170 d = {}
171 for k in self._get_keys():
171 for k in self._get_keys():
172 d[k] = getattr(self, k)
172 d[k] = getattr(self, k)
173
173
174 # also use __json__() if present to get additional fields
174 # also use __json__() if present to get additional fields
175 _json_attr = getattr(self, '__json__', None)
175 _json_attr = getattr(self, '__json__', None)
176 if _json_attr:
176 if _json_attr:
177 # update with attributes from __json__
177 # update with attributes from __json__
178 if callable(_json_attr):
178 if callable(_json_attr):
179 _json_attr = _json_attr()
179 _json_attr = _json_attr()
180 for k, val in _json_attr.iteritems():
180 for k, val in _json_attr.iteritems():
181 d[k] = val
181 d[k] = val
182 return d
182 return d
183
183
184 def get_appstruct(self):
184 def get_appstruct(self):
185 """return list with keys and values tuples corresponding
185 """return list with keys and values tuples corresponding
186 to this model data """
186 to this model data """
187
187
188 l = []
188 l = []
189 for k in self._get_keys():
189 for k in self._get_keys():
190 l.append((k, getattr(self, k),))
190 l.append((k, getattr(self, k),))
191 return l
191 return l
192
192
193 def populate_obj(self, populate_dict):
193 def populate_obj(self, populate_dict):
194 """populate model with data from given populate_dict"""
194 """populate model with data from given populate_dict"""
195
195
196 for k in self._get_keys():
196 for k in self._get_keys():
197 if k in populate_dict:
197 if k in populate_dict:
198 setattr(self, k, populate_dict[k])
198 setattr(self, k, populate_dict[k])
199
199
200 @classmethod
200 @classmethod
201 def query(cls):
201 def query(cls):
202 return Session().query(cls)
202 return Session().query(cls)
203
203
204 @classmethod
204 @classmethod
205 def get(cls, id_):
205 def get(cls, id_):
206 if id_:
206 if id_:
207 return cls.query().get(id_)
207 return cls.query().get(id_)
208
208
209 @classmethod
209 @classmethod
210 def get_or_404(cls, id_):
210 def get_or_404(cls, id_):
211 try:
211 try:
212 id_ = int(id_)
212 id_ = int(id_)
213 except (TypeError, ValueError):
213 except (TypeError, ValueError):
214 raise HTTPNotFound
214 raise HTTPNotFound
215
215
216 res = cls.query().get(id_)
216 res = cls.query().get(id_)
217 if not res:
217 if not res:
218 raise HTTPNotFound
218 raise HTTPNotFound
219 return res
219 return res
220
220
221 @classmethod
221 @classmethod
222 def getAll(cls):
222 def getAll(cls):
223 # deprecated and left for backward compatibility
223 # deprecated and left for backward compatibility
224 return cls.get_all()
224 return cls.get_all()
225
225
226 @classmethod
226 @classmethod
227 def get_all(cls):
227 def get_all(cls):
228 return cls.query().all()
228 return cls.query().all()
229
229
230 @classmethod
230 @classmethod
231 def delete(cls, id_):
231 def delete(cls, id_):
232 obj = cls.query().get(id_)
232 obj = cls.query().get(id_)
233 Session().delete(obj)
233 Session().delete(obj)
234
234
235 @classmethod
235 @classmethod
236 def identity_cache(cls, session, attr_name, value):
236 def identity_cache(cls, session, attr_name, value):
237 exist_in_session = []
237 exist_in_session = []
238 for (item_cls, pkey), instance in session.identity_map.items():
238 for (item_cls, pkey), instance in session.identity_map.items():
239 if cls == item_cls and getattr(instance, attr_name) == value:
239 if cls == item_cls and getattr(instance, attr_name) == value:
240 exist_in_session.append(instance)
240 exist_in_session.append(instance)
241 if exist_in_session:
241 if exist_in_session:
242 if len(exist_in_session) == 1:
242 if len(exist_in_session) == 1:
243 return exist_in_session[0]
243 return exist_in_session[0]
244 log.exception(
244 log.exception(
245 'multiple objects with attr %s and '
245 'multiple objects with attr %s and '
246 'value %s found with same name: %r',
246 'value %s found with same name: %r',
247 attr_name, value, exist_in_session)
247 attr_name, value, exist_in_session)
248
248
249 def __repr__(self):
249 def __repr__(self):
250 if hasattr(self, '__unicode__'):
250 if hasattr(self, '__unicode__'):
251 # python repr needs to return str
251 # python repr needs to return str
252 try:
252 try:
253 return safe_str(self.__unicode__())
253 return safe_str(self.__unicode__())
254 except UnicodeDecodeError:
254 except UnicodeDecodeError:
255 pass
255 pass
256 return '<DB:%s>' % (self.__class__.__name__)
256 return '<DB:%s>' % (self.__class__.__name__)
257
257
258
258
259 class RhodeCodeSetting(Base, BaseModel):
259 class RhodeCodeSetting(Base, BaseModel):
260 __tablename__ = 'rhodecode_settings'
260 __tablename__ = 'rhodecode_settings'
261 __table_args__ = (
261 __table_args__ = (
262 UniqueConstraint('app_settings_name'),
262 UniqueConstraint('app_settings_name'),
263 {'extend_existing': True, 'mysql_engine': 'InnoDB',
263 {'extend_existing': True, 'mysql_engine': 'InnoDB',
264 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
264 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
265 )
265 )
266
266
267 SETTINGS_TYPES = {
267 SETTINGS_TYPES = {
268 'str': safe_str,
268 'str': safe_str,
269 'int': safe_int,
269 'int': safe_int,
270 'unicode': safe_unicode,
270 'unicode': safe_unicode,
271 'bool': str2bool,
271 'bool': str2bool,
272 'list': functools.partial(aslist, sep=',')
272 'list': functools.partial(aslist, sep=',')
273 }
273 }
274 DEFAULT_UPDATE_URL = 'https://rhodecode.com/api/v1/info/versions'
274 DEFAULT_UPDATE_URL = 'https://rhodecode.com/api/v1/info/versions'
275 GLOBAL_CONF_KEY = 'app_settings'
275 GLOBAL_CONF_KEY = 'app_settings'
276
276
277 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
277 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
278 app_settings_name = Column("app_settings_name", String(255), nullable=True, unique=None, default=None)
278 app_settings_name = Column("app_settings_name", String(255), nullable=True, unique=None, default=None)
279 _app_settings_value = Column("app_settings_value", String(4096), nullable=True, unique=None, default=None)
279 _app_settings_value = Column("app_settings_value", String(4096), nullable=True, unique=None, default=None)
280 _app_settings_type = Column("app_settings_type", String(255), nullable=True, unique=None, default=None)
280 _app_settings_type = Column("app_settings_type", String(255), nullable=True, unique=None, default=None)
281
281
282 def __init__(self, key='', val='', type='unicode'):
282 def __init__(self, key='', val='', type='unicode'):
283 self.app_settings_name = key
283 self.app_settings_name = key
284 self.app_settings_type = type
284 self.app_settings_type = type
285 self.app_settings_value = val
285 self.app_settings_value = val
286
286
287 @validates('_app_settings_value')
287 @validates('_app_settings_value')
288 def validate_settings_value(self, key, val):
288 def validate_settings_value(self, key, val):
289 assert type(val) == unicode
289 assert type(val) == unicode
290 return val
290 return val
291
291
292 @hybrid_property
292 @hybrid_property
293 def app_settings_value(self):
293 def app_settings_value(self):
294 v = self._app_settings_value
294 v = self._app_settings_value
295 _type = self.app_settings_type
295 _type = self.app_settings_type
296 if _type:
296 if _type:
297 _type = self.app_settings_type.split('.')[0]
297 _type = self.app_settings_type.split('.')[0]
298 # decode the encrypted value
298 # decode the encrypted value
299 if 'encrypted' in self.app_settings_type:
299 if 'encrypted' in self.app_settings_type:
300 cipher = EncryptedTextValue()
300 cipher = EncryptedTextValue()
301 v = safe_unicode(cipher.process_result_value(v, None))
301 v = safe_unicode(cipher.process_result_value(v, None))
302
302
303 converter = self.SETTINGS_TYPES.get(_type) or \
303 converter = self.SETTINGS_TYPES.get(_type) or \
304 self.SETTINGS_TYPES['unicode']
304 self.SETTINGS_TYPES['unicode']
305 return converter(v)
305 return converter(v)
306
306
307 @app_settings_value.setter
307 @app_settings_value.setter
308 def app_settings_value(self, val):
308 def app_settings_value(self, val):
309 """
309 """
310 Setter that will always make sure we use unicode in app_settings_value
310 Setter that will always make sure we use unicode in app_settings_value
311
311
312 :param val:
312 :param val:
313 """
313 """
314 val = safe_unicode(val)
314 val = safe_unicode(val)
315 # encode the encrypted value
315 # encode the encrypted value
316 if 'encrypted' in self.app_settings_type:
316 if 'encrypted' in self.app_settings_type:
317 cipher = EncryptedTextValue()
317 cipher = EncryptedTextValue()
318 val = safe_unicode(cipher.process_bind_param(val, None))
318 val = safe_unicode(cipher.process_bind_param(val, None))
319 self._app_settings_value = val
319 self._app_settings_value = val
320
320
321 @hybrid_property
321 @hybrid_property
322 def app_settings_type(self):
322 def app_settings_type(self):
323 return self._app_settings_type
323 return self._app_settings_type
324
324
325 @app_settings_type.setter
325 @app_settings_type.setter
326 def app_settings_type(self, val):
326 def app_settings_type(self, val):
327 if val.split('.')[0] not in self.SETTINGS_TYPES:
327 if val.split('.')[0] not in self.SETTINGS_TYPES:
328 raise Exception('type must be one of %s got %s'
328 raise Exception('type must be one of %s got %s'
329 % (self.SETTINGS_TYPES.keys(), val))
329 % (self.SETTINGS_TYPES.keys(), val))
330 self._app_settings_type = val
330 self._app_settings_type = val
331
331
332 def __unicode__(self):
332 def __unicode__(self):
333 return u"<%s('%s:%s[%s]')>" % (
333 return u"<%s('%s:%s[%s]')>" % (
334 self.__class__.__name__,
334 self.__class__.__name__,
335 self.app_settings_name, self.app_settings_value,
335 self.app_settings_name, self.app_settings_value,
336 self.app_settings_type
336 self.app_settings_type
337 )
337 )
338
338
339
339
340 class RhodeCodeUi(Base, BaseModel):
340 class RhodeCodeUi(Base, BaseModel):
341 __tablename__ = 'rhodecode_ui'
341 __tablename__ = 'rhodecode_ui'
342 __table_args__ = (
342 __table_args__ = (
343 UniqueConstraint('ui_key'),
343 UniqueConstraint('ui_key'),
344 {'extend_existing': True, 'mysql_engine': 'InnoDB',
344 {'extend_existing': True, 'mysql_engine': 'InnoDB',
345 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
345 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
346 )
346 )
347
347
348 HOOK_REPO_SIZE = 'changegroup.repo_size'
348 HOOK_REPO_SIZE = 'changegroup.repo_size'
349 # HG
349 # HG
350 HOOK_PRE_PULL = 'preoutgoing.pre_pull'
350 HOOK_PRE_PULL = 'preoutgoing.pre_pull'
351 HOOK_PULL = 'outgoing.pull_logger'
351 HOOK_PULL = 'outgoing.pull_logger'
352 HOOK_PRE_PUSH = 'prechangegroup.pre_push'
352 HOOK_PRE_PUSH = 'prechangegroup.pre_push'
353 HOOK_PRETX_PUSH = 'pretxnchangegroup.pre_push'
353 HOOK_PRETX_PUSH = 'pretxnchangegroup.pre_push'
354 HOOK_PUSH = 'changegroup.push_logger'
354 HOOK_PUSH = 'changegroup.push_logger'
355
355
356 # TODO: johbo: Unify way how hooks are configured for git and hg,
356 # TODO: johbo: Unify way how hooks are configured for git and hg,
357 # git part is currently hardcoded.
357 # git part is currently hardcoded.
358
358
359 # SVN PATTERNS
359 # SVN PATTERNS
360 SVN_BRANCH_ID = 'vcs_svn_branch'
360 SVN_BRANCH_ID = 'vcs_svn_branch'
361 SVN_TAG_ID = 'vcs_svn_tag'
361 SVN_TAG_ID = 'vcs_svn_tag'
362
362
363 ui_id = Column(
363 ui_id = Column(
364 "ui_id", Integer(), nullable=False, unique=True, default=None,
364 "ui_id", Integer(), nullable=False, unique=True, default=None,
365 primary_key=True)
365 primary_key=True)
366 ui_section = Column(
366 ui_section = Column(
367 "ui_section", String(255), nullable=True, unique=None, default=None)
367 "ui_section", String(255), nullable=True, unique=None, default=None)
368 ui_key = Column(
368 ui_key = Column(
369 "ui_key", String(255), nullable=True, unique=None, default=None)
369 "ui_key", String(255), nullable=True, unique=None, default=None)
370 ui_value = Column(
370 ui_value = Column(
371 "ui_value", String(255), nullable=True, unique=None, default=None)
371 "ui_value", String(255), nullable=True, unique=None, default=None)
372 ui_active = Column(
372 ui_active = Column(
373 "ui_active", Boolean(), nullable=True, unique=None, default=True)
373 "ui_active", Boolean(), nullable=True, unique=None, default=True)
374
374
375 def __repr__(self):
375 def __repr__(self):
376 return '<%s[%s]%s=>%s]>' % (self.__class__.__name__, self.ui_section,
376 return '<%s[%s]%s=>%s]>' % (self.__class__.__name__, self.ui_section,
377 self.ui_key, self.ui_value)
377 self.ui_key, self.ui_value)
378
378
379
379
380 class RepoRhodeCodeSetting(Base, BaseModel):
380 class RepoRhodeCodeSetting(Base, BaseModel):
381 __tablename__ = 'repo_rhodecode_settings'
381 __tablename__ = 'repo_rhodecode_settings'
382 __table_args__ = (
382 __table_args__ = (
383 UniqueConstraint(
383 UniqueConstraint(
384 'app_settings_name', 'repository_id',
384 'app_settings_name', 'repository_id',
385 name='uq_repo_rhodecode_setting_name_repo_id'),
385 name='uq_repo_rhodecode_setting_name_repo_id'),
386 {'extend_existing': True, 'mysql_engine': 'InnoDB',
386 {'extend_existing': True, 'mysql_engine': 'InnoDB',
387 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
387 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
388 )
388 )
389
389
390 repository_id = Column(
390 repository_id = Column(
391 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
391 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
392 nullable=False)
392 nullable=False)
393 app_settings_id = Column(
393 app_settings_id = Column(
394 "app_settings_id", Integer(), nullable=False, unique=True,
394 "app_settings_id", Integer(), nullable=False, unique=True,
395 default=None, primary_key=True)
395 default=None, primary_key=True)
396 app_settings_name = Column(
396 app_settings_name = Column(
397 "app_settings_name", String(255), nullable=True, unique=None,
397 "app_settings_name", String(255), nullable=True, unique=None,
398 default=None)
398 default=None)
399 _app_settings_value = Column(
399 _app_settings_value = Column(
400 "app_settings_value", String(4096), nullable=True, unique=None,
400 "app_settings_value", String(4096), nullable=True, unique=None,
401 default=None)
401 default=None)
402 _app_settings_type = Column(
402 _app_settings_type = Column(
403 "app_settings_type", String(255), nullable=True, unique=None,
403 "app_settings_type", String(255), nullable=True, unique=None,
404 default=None)
404 default=None)
405
405
406 repository = relationship('Repository')
406 repository = relationship('Repository')
407
407
408 def __init__(self, repository_id, key='', val='', type='unicode'):
408 def __init__(self, repository_id, key='', val='', type='unicode'):
409 self.repository_id = repository_id
409 self.repository_id = repository_id
410 self.app_settings_name = key
410 self.app_settings_name = key
411 self.app_settings_type = type
411 self.app_settings_type = type
412 self.app_settings_value = val
412 self.app_settings_value = val
413
413
414 @validates('_app_settings_value')
414 @validates('_app_settings_value')
415 def validate_settings_value(self, key, val):
415 def validate_settings_value(self, key, val):
416 assert type(val) == unicode
416 assert type(val) == unicode
417 return val
417 return val
418
418
419 @hybrid_property
419 @hybrid_property
420 def app_settings_value(self):
420 def app_settings_value(self):
421 v = self._app_settings_value
421 v = self._app_settings_value
422 type_ = self.app_settings_type
422 type_ = self.app_settings_type
423 SETTINGS_TYPES = RhodeCodeSetting.SETTINGS_TYPES
423 SETTINGS_TYPES = RhodeCodeSetting.SETTINGS_TYPES
424 converter = SETTINGS_TYPES.get(type_) or SETTINGS_TYPES['unicode']
424 converter = SETTINGS_TYPES.get(type_) or SETTINGS_TYPES['unicode']
425 return converter(v)
425 return converter(v)
426
426
427 @app_settings_value.setter
427 @app_settings_value.setter
428 def app_settings_value(self, val):
428 def app_settings_value(self, val):
429 """
429 """
430 Setter that will always make sure we use unicode in app_settings_value
430 Setter that will always make sure we use unicode in app_settings_value
431
431
432 :param val:
432 :param val:
433 """
433 """
434 self._app_settings_value = safe_unicode(val)
434 self._app_settings_value = safe_unicode(val)
435
435
436 @hybrid_property
436 @hybrid_property
437 def app_settings_type(self):
437 def app_settings_type(self):
438 return self._app_settings_type
438 return self._app_settings_type
439
439
440 @app_settings_type.setter
440 @app_settings_type.setter
441 def app_settings_type(self, val):
441 def app_settings_type(self, val):
442 SETTINGS_TYPES = RhodeCodeSetting.SETTINGS_TYPES
442 SETTINGS_TYPES = RhodeCodeSetting.SETTINGS_TYPES
443 if val not in SETTINGS_TYPES:
443 if val not in SETTINGS_TYPES:
444 raise Exception('type must be one of %s got %s'
444 raise Exception('type must be one of %s got %s'
445 % (SETTINGS_TYPES.keys(), val))
445 % (SETTINGS_TYPES.keys(), val))
446 self._app_settings_type = val
446 self._app_settings_type = val
447
447
448 def __unicode__(self):
448 def __unicode__(self):
449 return u"<%s('%s:%s:%s[%s]')>" % (
449 return u"<%s('%s:%s:%s[%s]')>" % (
450 self.__class__.__name__, self.repository.repo_name,
450 self.__class__.__name__, self.repository.repo_name,
451 self.app_settings_name, self.app_settings_value,
451 self.app_settings_name, self.app_settings_value,
452 self.app_settings_type
452 self.app_settings_type
453 )
453 )
454
454
455
455
456 class RepoRhodeCodeUi(Base, BaseModel):
456 class RepoRhodeCodeUi(Base, BaseModel):
457 __tablename__ = 'repo_rhodecode_ui'
457 __tablename__ = 'repo_rhodecode_ui'
458 __table_args__ = (
458 __table_args__ = (
459 UniqueConstraint(
459 UniqueConstraint(
460 'repository_id', 'ui_section', 'ui_key',
460 'repository_id', 'ui_section', 'ui_key',
461 name='uq_repo_rhodecode_ui_repository_id_section_key'),
461 name='uq_repo_rhodecode_ui_repository_id_section_key'),
462 {'extend_existing': True, 'mysql_engine': 'InnoDB',
462 {'extend_existing': True, 'mysql_engine': 'InnoDB',
463 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
463 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
464 )
464 )
465
465
466 repository_id = Column(
466 repository_id = Column(
467 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
467 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
468 nullable=False)
468 nullable=False)
469 ui_id = Column(
469 ui_id = Column(
470 "ui_id", Integer(), nullable=False, unique=True, default=None,
470 "ui_id", Integer(), nullable=False, unique=True, default=None,
471 primary_key=True)
471 primary_key=True)
472 ui_section = Column(
472 ui_section = Column(
473 "ui_section", String(255), nullable=True, unique=None, default=None)
473 "ui_section", String(255), nullable=True, unique=None, default=None)
474 ui_key = Column(
474 ui_key = Column(
475 "ui_key", String(255), nullable=True, unique=None, default=None)
475 "ui_key", String(255), nullable=True, unique=None, default=None)
476 ui_value = Column(
476 ui_value = Column(
477 "ui_value", String(255), nullable=True, unique=None, default=None)
477 "ui_value", String(255), nullable=True, unique=None, default=None)
478 ui_active = Column(
478 ui_active = Column(
479 "ui_active", Boolean(), nullable=True, unique=None, default=True)
479 "ui_active", Boolean(), nullable=True, unique=None, default=True)
480
480
481 repository = relationship('Repository')
481 repository = relationship('Repository')
482
482
483 def __repr__(self):
483 def __repr__(self):
484 return '<%s[%s:%s]%s=>%s]>' % (
484 return '<%s[%s:%s]%s=>%s]>' % (
485 self.__class__.__name__, self.repository.repo_name,
485 self.__class__.__name__, self.repository.repo_name,
486 self.ui_section, self.ui_key, self.ui_value)
486 self.ui_section, self.ui_key, self.ui_value)
487
487
488
488
489 class User(Base, BaseModel):
489 class User(Base, BaseModel):
490 __tablename__ = 'users'
490 __tablename__ = 'users'
491 __table_args__ = (
491 __table_args__ = (
492 UniqueConstraint('username'), UniqueConstraint('email'),
492 UniqueConstraint('username'), UniqueConstraint('email'),
493 Index('u_username_idx', 'username'),
493 Index('u_username_idx', 'username'),
494 Index('u_email_idx', 'email'),
494 Index('u_email_idx', 'email'),
495 {'extend_existing': True, 'mysql_engine': 'InnoDB',
495 {'extend_existing': True, 'mysql_engine': 'InnoDB',
496 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
496 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
497 )
497 )
498 DEFAULT_USER = 'default'
498 DEFAULT_USER = 'default'
499 DEFAULT_USER_EMAIL = 'anonymous@rhodecode.org'
499 DEFAULT_USER_EMAIL = 'anonymous@rhodecode.org'
500 DEFAULT_GRAVATAR_URL = 'https://secure.gravatar.com/avatar/{md5email}?d=identicon&s={size}'
500 DEFAULT_GRAVATAR_URL = 'https://secure.gravatar.com/avatar/{md5email}?d=identicon&s={size}'
501
501
502 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
502 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
503 username = Column("username", String(255), nullable=True, unique=None, default=None)
503 username = Column("username", String(255), nullable=True, unique=None, default=None)
504 password = Column("password", String(255), nullable=True, unique=None, default=None)
504 password = Column("password", String(255), nullable=True, unique=None, default=None)
505 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
505 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
506 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
506 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
507 name = Column("firstname", String(255), nullable=True, unique=None, default=None)
507 name = Column("firstname", String(255), nullable=True, unique=None, default=None)
508 lastname = Column("lastname", String(255), nullable=True, unique=None, default=None)
508 lastname = Column("lastname", String(255), nullable=True, unique=None, default=None)
509 _email = Column("email", String(255), nullable=True, unique=None, default=None)
509 _email = Column("email", String(255), nullable=True, unique=None, default=None)
510 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
510 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
511 extern_type = Column("extern_type", String(255), nullable=True, unique=None, default=None)
511 extern_type = Column("extern_type", String(255), nullable=True, unique=None, default=None)
512 extern_name = Column("extern_name", String(255), nullable=True, unique=None, default=None)
512 extern_name = Column("extern_name", String(255), nullable=True, unique=None, default=None)
513 _api_key = Column("api_key", String(255), nullable=True, unique=None, default=None)
513 _api_key = Column("api_key", String(255), nullable=True, unique=None, default=None)
514 inherit_default_permissions = Column("inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
514 inherit_default_permissions = Column("inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
515 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
515 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
516 _user_data = Column("user_data", LargeBinary(), nullable=True) # JSON data
516 _user_data = Column("user_data", LargeBinary(), nullable=True) # JSON data
517
517
518 user_log = relationship('UserLog')
518 user_log = relationship('UserLog')
519 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
519 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
520
520
521 repositories = relationship('Repository')
521 repositories = relationship('Repository')
522 repository_groups = relationship('RepoGroup')
522 repository_groups = relationship('RepoGroup')
523 user_groups = relationship('UserGroup')
523 user_groups = relationship('UserGroup')
524
524
525 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
525 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
526 followings = relationship('UserFollowing', primaryjoin='UserFollowing.user_id==User.user_id', cascade='all')
526 followings = relationship('UserFollowing', primaryjoin='UserFollowing.user_id==User.user_id', cascade='all')
527
527
528 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
528 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
529 repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all')
529 repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all')
530 user_group_to_perm = relationship('UserUserGroupToPerm', primaryjoin='UserUserGroupToPerm.user_id==User.user_id', cascade='all')
530 user_group_to_perm = relationship('UserUserGroupToPerm', primaryjoin='UserUserGroupToPerm.user_id==User.user_id', cascade='all')
531
531
532 group_member = relationship('UserGroupMember', cascade='all')
532 group_member = relationship('UserGroupMember', cascade='all')
533
533
534 notifications = relationship('UserNotification', cascade='all')
534 notifications = relationship('UserNotification', cascade='all')
535 # notifications assigned to this user
535 # notifications assigned to this user
536 user_created_notifications = relationship('Notification', cascade='all')
536 user_created_notifications = relationship('Notification', cascade='all')
537 # comments created by this user
537 # comments created by this user
538 user_comments = relationship('ChangesetComment', cascade='all')
538 user_comments = relationship('ChangesetComment', cascade='all')
539 # user profile extra info
539 # user profile extra info
540 user_emails = relationship('UserEmailMap', cascade='all')
540 user_emails = relationship('UserEmailMap', cascade='all')
541 user_ip_map = relationship('UserIpMap', cascade='all')
541 user_ip_map = relationship('UserIpMap', cascade='all')
542 user_auth_tokens = relationship('UserApiKeys', cascade='all')
542 user_auth_tokens = relationship('UserApiKeys', cascade='all')
543 # gists
543 # gists
544 user_gists = relationship('Gist', cascade='all')
544 user_gists = relationship('Gist', cascade='all')
545 # user pull requests
545 # user pull requests
546 user_pull_requests = relationship('PullRequest', cascade='all')
546 user_pull_requests = relationship('PullRequest', cascade='all')
547 # external identities
547 # external identities
548 extenal_identities = relationship(
548 extenal_identities = relationship(
549 'ExternalIdentity',
549 'ExternalIdentity',
550 primaryjoin="User.user_id==ExternalIdentity.local_user_id",
550 primaryjoin="User.user_id==ExternalIdentity.local_user_id",
551 cascade='all')
551 cascade='all')
552
552
553 def __unicode__(self):
553 def __unicode__(self):
554 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
554 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
555 self.user_id, self.username)
555 self.user_id, self.username)
556
556
557 @hybrid_property
557 @hybrid_property
558 def email(self):
558 def email(self):
559 return self._email
559 return self._email
560
560
561 @email.setter
561 @email.setter
562 def email(self, val):
562 def email(self, val):
563 self._email = val.lower() if val else None
563 self._email = val.lower() if val else None
564
564
565 @hybrid_property
565 @hybrid_property
566 def api_key(self):
566 def api_key(self):
567 """
567 """
568 Fetch if exist an auth-token with role ALL connected to this user
568 Fetch if exist an auth-token with role ALL connected to this user
569 """
569 """
570 user_auth_token = UserApiKeys.query()\
570 user_auth_token = UserApiKeys.query()\
571 .filter(UserApiKeys.user_id == self.user_id)\
571 .filter(UserApiKeys.user_id == self.user_id)\
572 .filter(or_(UserApiKeys.expires == -1,
572 .filter(or_(UserApiKeys.expires == -1,
573 UserApiKeys.expires >= time.time()))\
573 UserApiKeys.expires >= time.time()))\
574 .filter(UserApiKeys.role == UserApiKeys.ROLE_ALL).first()
574 .filter(UserApiKeys.role == UserApiKeys.ROLE_ALL).first()
575 if user_auth_token:
576 user_auth_token = user_auth_token.api_key
577
575 return user_auth_token
578 return user_auth_token
576
579
577 @api_key.setter
580 @api_key.setter
578 def api_key(self, val):
581 def api_key(self, val):
579 # don't allow to set API key this is deprecated for now
582 # don't allow to set API key this is deprecated for now
580 self._api_key = None
583 self._api_key = None
581
584
582 @property
585 @property
583 def firstname(self):
586 def firstname(self):
584 # alias for future
587 # alias for future
585 return self.name
588 return self.name
586
589
587 @property
590 @property
588 def emails(self):
591 def emails(self):
589 other = UserEmailMap.query().filter(UserEmailMap.user==self).all()
592 other = UserEmailMap.query().filter(UserEmailMap.user==self).all()
590 return [self.email] + [x.email for x in other]
593 return [self.email] + [x.email for x in other]
591
594
592 @property
595 @property
593 def auth_tokens(self):
596 def auth_tokens(self):
594 return [x.api_key for x in self.extra_auth_tokens]
597 return [x.api_key for x in self.extra_auth_tokens]
595
598
596 @property
599 @property
597 def extra_auth_tokens(self):
600 def extra_auth_tokens(self):
598 return UserApiKeys.query().filter(UserApiKeys.user == self).all()
601 return UserApiKeys.query().filter(UserApiKeys.user == self).all()
599
602
600 @property
603 @property
601 def feed_token(self):
604 def feed_token(self):
602 return self.get_feed_token()
605 return self.get_feed_token()
603
606
604 def get_feed_token(self):
607 def get_feed_token(self):
605 feed_tokens = UserApiKeys.query()\
608 feed_tokens = UserApiKeys.query()\
606 .filter(UserApiKeys.user == self)\
609 .filter(UserApiKeys.user == self)\
607 .filter(UserApiKeys.role == UserApiKeys.ROLE_FEED)\
610 .filter(UserApiKeys.role == UserApiKeys.ROLE_FEED)\
608 .all()
611 .all()
609 if feed_tokens:
612 if feed_tokens:
610 return feed_tokens[0].api_key
613 return feed_tokens[0].api_key
611 return 'NO_FEED_TOKEN_AVAILABLE'
614 return 'NO_FEED_TOKEN_AVAILABLE'
612
615
613 @classmethod
616 @classmethod
614 def extra_valid_auth_tokens(cls, user, role=None):
617 def extra_valid_auth_tokens(cls, user, role=None):
615 tokens = UserApiKeys.query().filter(UserApiKeys.user == user)\
618 tokens = UserApiKeys.query().filter(UserApiKeys.user == user)\
616 .filter(or_(UserApiKeys.expires == -1,
619 .filter(or_(UserApiKeys.expires == -1,
617 UserApiKeys.expires >= time.time()))
620 UserApiKeys.expires >= time.time()))
618 if role:
621 if role:
619 tokens = tokens.filter(or_(UserApiKeys.role == role,
622 tokens = tokens.filter(or_(UserApiKeys.role == role,
620 UserApiKeys.role == UserApiKeys.ROLE_ALL))
623 UserApiKeys.role == UserApiKeys.ROLE_ALL))
621 return tokens.all()
624 return tokens.all()
622
625
623 def authenticate_by_token(self, auth_token, roles=None):
626 def authenticate_by_token(self, auth_token, roles=None):
624 from rhodecode.lib import auth
627 from rhodecode.lib import auth
625
628
626 log.debug('Trying to authenticate user: %s via auth-token, '
629 log.debug('Trying to authenticate user: %s via auth-token, '
627 'and roles: %s', self, roles)
630 'and roles: %s', self, roles)
628
631
629 if not auth_token:
632 if not auth_token:
630 return False
633 return False
631
634
632 crypto_backend = auth.crypto_backend()
635 crypto_backend = auth.crypto_backend()
633
636
634 roles = (roles or []) + [UserApiKeys.ROLE_ALL]
637 roles = (roles or []) + [UserApiKeys.ROLE_ALL]
635 tokens_q = UserApiKeys.query()\
638 tokens_q = UserApiKeys.query()\
636 .filter(UserApiKeys.user_id == self.user_id)\
639 .filter(UserApiKeys.user_id == self.user_id)\
637 .filter(or_(UserApiKeys.expires == -1,
640 .filter(or_(UserApiKeys.expires == -1,
638 UserApiKeys.expires >= time.time()))
641 UserApiKeys.expires >= time.time()))
639
642
640 tokens_q = tokens_q.filter(UserApiKeys.role.in_(roles))
643 tokens_q = tokens_q.filter(UserApiKeys.role.in_(roles))
641
644
642 plain_tokens = []
645 plain_tokens = []
643 hash_tokens = []
646 hash_tokens = []
644
647
645 for token in tokens_q.all():
648 for token in tokens_q.all():
646 if token.api_key.startswith(crypto_backend.ENC_PREF):
649 if token.api_key.startswith(crypto_backend.ENC_PREF):
647 hash_tokens.append(token.api_key)
650 hash_tokens.append(token.api_key)
648 else:
651 else:
649 plain_tokens.append(token.api_key)
652 plain_tokens.append(token.api_key)
650
653
651 is_plain_match = auth_token in plain_tokens
654 is_plain_match = auth_token in plain_tokens
652 if is_plain_match:
655 if is_plain_match:
653 return True
656 return True
654
657
655 for hashed in hash_tokens:
658 for hashed in hash_tokens:
656 # marcink: this is expensive to calculate, but the most secure
659 # marcink: this is expensive to calculate, but the most secure
657 match = crypto_backend.hash_check(auth_token, hashed)
660 match = crypto_backend.hash_check(auth_token, hashed)
658 if match:
661 if match:
659 return True
662 return True
660
663
661 return False
664 return False
662
665
663 @property
666 @property
664 def ip_addresses(self):
667 def ip_addresses(self):
665 ret = UserIpMap.query().filter(UserIpMap.user == self).all()
668 ret = UserIpMap.query().filter(UserIpMap.user == self).all()
666 return [x.ip_addr for x in ret]
669 return [x.ip_addr for x in ret]
667
670
668 @property
671 @property
669 def username_and_name(self):
672 def username_and_name(self):
670 return '%s (%s %s)' % (self.username, self.firstname, self.lastname)
673 return '%s (%s %s)' % (self.username, self.firstname, self.lastname)
671
674
672 @property
675 @property
673 def username_or_name_or_email(self):
676 def username_or_name_or_email(self):
674 full_name = self.full_name if self.full_name is not ' ' else None
677 full_name = self.full_name if self.full_name is not ' ' else None
675 return self.username or full_name or self.email
678 return self.username or full_name or self.email
676
679
677 @property
680 @property
678 def full_name(self):
681 def full_name(self):
679 return '%s %s' % (self.firstname, self.lastname)
682 return '%s %s' % (self.firstname, self.lastname)
680
683
681 @property
684 @property
682 def full_name_or_username(self):
685 def full_name_or_username(self):
683 return ('%s %s' % (self.firstname, self.lastname)
686 return ('%s %s' % (self.firstname, self.lastname)
684 if (self.firstname and self.lastname) else self.username)
687 if (self.firstname and self.lastname) else self.username)
685
688
686 @property
689 @property
687 def full_contact(self):
690 def full_contact(self):
688 return '%s %s <%s>' % (self.firstname, self.lastname, self.email)
691 return '%s %s <%s>' % (self.firstname, self.lastname, self.email)
689
692
690 @property
693 @property
691 def short_contact(self):
694 def short_contact(self):
692 return '%s %s' % (self.firstname, self.lastname)
695 return '%s %s' % (self.firstname, self.lastname)
693
696
694 @property
697 @property
695 def is_admin(self):
698 def is_admin(self):
696 return self.admin
699 return self.admin
697
700
698 @property
701 @property
699 def AuthUser(self):
702 def AuthUser(self):
700 """
703 """
701 Returns instance of AuthUser for this user
704 Returns instance of AuthUser for this user
702 """
705 """
703 from rhodecode.lib.auth import AuthUser
706 from rhodecode.lib.auth import AuthUser
704 return AuthUser(user_id=self.user_id, username=self.username)
707 return AuthUser(user_id=self.user_id, username=self.username)
705
708
706 @hybrid_property
709 @hybrid_property
707 def user_data(self):
710 def user_data(self):
708 if not self._user_data:
711 if not self._user_data:
709 return {}
712 return {}
710
713
711 try:
714 try:
712 return json.loads(self._user_data)
715 return json.loads(self._user_data)
713 except TypeError:
716 except TypeError:
714 return {}
717 return {}
715
718
716 @user_data.setter
719 @user_data.setter
717 def user_data(self, val):
720 def user_data(self, val):
718 if not isinstance(val, dict):
721 if not isinstance(val, dict):
719 raise Exception('user_data must be dict, got %s' % type(val))
722 raise Exception('user_data must be dict, got %s' % type(val))
720 try:
723 try:
721 self._user_data = json.dumps(val)
724 self._user_data = json.dumps(val)
722 except Exception:
725 except Exception:
723 log.error(traceback.format_exc())
726 log.error(traceback.format_exc())
724
727
725 @classmethod
728 @classmethod
726 def get_by_username(cls, username, case_insensitive=False,
729 def get_by_username(cls, username, case_insensitive=False,
727 cache=False, identity_cache=False):
730 cache=False, identity_cache=False):
728 session = Session()
731 session = Session()
729
732
730 if case_insensitive:
733 if case_insensitive:
731 q = cls.query().filter(
734 q = cls.query().filter(
732 func.lower(cls.username) == func.lower(username))
735 func.lower(cls.username) == func.lower(username))
733 else:
736 else:
734 q = cls.query().filter(cls.username == username)
737 q = cls.query().filter(cls.username == username)
735
738
736 if cache:
739 if cache:
737 if identity_cache:
740 if identity_cache:
738 val = cls.identity_cache(session, 'username', username)
741 val = cls.identity_cache(session, 'username', username)
739 if val:
742 if val:
740 return val
743 return val
741 else:
744 else:
742 q = q.options(
745 q = q.options(
743 FromCache("sql_cache_short",
746 FromCache("sql_cache_short",
744 "get_user_by_name_%s" % _hash_key(username)))
747 "get_user_by_name_%s" % _hash_key(username)))
745
748
746 return q.scalar()
749 return q.scalar()
747
750
748 @classmethod
751 @classmethod
749 def get_by_auth_token(cls, auth_token, cache=False):
752 def get_by_auth_token(cls, auth_token, cache=False):
750 q = UserApiKeys.query()\
753 q = UserApiKeys.query()\
751 .filter(UserApiKeys.api_key == auth_token)\
754 .filter(UserApiKeys.api_key == auth_token)\
752 .filter(or_(UserApiKeys.expires == -1,
755 .filter(or_(UserApiKeys.expires == -1,
753 UserApiKeys.expires >= time.time()))
756 UserApiKeys.expires >= time.time()))
754 if cache:
757 if cache:
755 q = q.options(FromCache("sql_cache_short",
758 q = q.options(FromCache("sql_cache_short",
756 "get_auth_token_%s" % auth_token))
759 "get_auth_token_%s" % auth_token))
757
760
758 match = q.first()
761 match = q.first()
759 if match:
762 if match:
760 return match.user
763 return match.user
761
764
762 @classmethod
765 @classmethod
763 def get_by_email(cls, email, case_insensitive=False, cache=False):
766 def get_by_email(cls, email, case_insensitive=False, cache=False):
764
767
765 if case_insensitive:
768 if case_insensitive:
766 q = cls.query().filter(func.lower(cls.email) == func.lower(email))
769 q = cls.query().filter(func.lower(cls.email) == func.lower(email))
767
770
768 else:
771 else:
769 q = cls.query().filter(cls.email == email)
772 q = cls.query().filter(cls.email == email)
770
773
771 if cache:
774 if cache:
772 q = q.options(FromCache("sql_cache_short",
775 q = q.options(FromCache("sql_cache_short",
773 "get_email_key_%s" % _hash_key(email)))
776 "get_email_key_%s" % _hash_key(email)))
774
777
775 ret = q.scalar()
778 ret = q.scalar()
776 if ret is None:
779 if ret is None:
777 q = UserEmailMap.query()
780 q = UserEmailMap.query()
778 # try fetching in alternate email map
781 # try fetching in alternate email map
779 if case_insensitive:
782 if case_insensitive:
780 q = q.filter(func.lower(UserEmailMap.email) == func.lower(email))
783 q = q.filter(func.lower(UserEmailMap.email) == func.lower(email))
781 else:
784 else:
782 q = q.filter(UserEmailMap.email == email)
785 q = q.filter(UserEmailMap.email == email)
783 q = q.options(joinedload(UserEmailMap.user))
786 q = q.options(joinedload(UserEmailMap.user))
784 if cache:
787 if cache:
785 q = q.options(FromCache("sql_cache_short",
788 q = q.options(FromCache("sql_cache_short",
786 "get_email_map_key_%s" % email))
789 "get_email_map_key_%s" % email))
787 ret = getattr(q.scalar(), 'user', None)
790 ret = getattr(q.scalar(), 'user', None)
788
791
789 return ret
792 return ret
790
793
791 @classmethod
794 @classmethod
792 def get_from_cs_author(cls, author):
795 def get_from_cs_author(cls, author):
793 """
796 """
794 Tries to get User objects out of commit author string
797 Tries to get User objects out of commit author string
795
798
796 :param author:
799 :param author:
797 """
800 """
798 from rhodecode.lib.helpers import email, author_name
801 from rhodecode.lib.helpers import email, author_name
799 # Valid email in the attribute passed, see if they're in the system
802 # Valid email in the attribute passed, see if they're in the system
800 _email = email(author)
803 _email = email(author)
801 if _email:
804 if _email:
802 user = cls.get_by_email(_email, case_insensitive=True)
805 user = cls.get_by_email(_email, case_insensitive=True)
803 if user:
806 if user:
804 return user
807 return user
805 # Maybe we can match by username?
808 # Maybe we can match by username?
806 _author = author_name(author)
809 _author = author_name(author)
807 user = cls.get_by_username(_author, case_insensitive=True)
810 user = cls.get_by_username(_author, case_insensitive=True)
808 if user:
811 if user:
809 return user
812 return user
810
813
811 def update_userdata(self, **kwargs):
814 def update_userdata(self, **kwargs):
812 usr = self
815 usr = self
813 old = usr.user_data
816 old = usr.user_data
814 old.update(**kwargs)
817 old.update(**kwargs)
815 usr.user_data = old
818 usr.user_data = old
816 Session().add(usr)
819 Session().add(usr)
817 log.debug('updated userdata with ', kwargs)
820 log.debug('updated userdata with ', kwargs)
818
821
819 def update_lastlogin(self):
822 def update_lastlogin(self):
820 """Update user lastlogin"""
823 """Update user lastlogin"""
821 self.last_login = datetime.datetime.now()
824 self.last_login = datetime.datetime.now()
822 Session().add(self)
825 Session().add(self)
823 log.debug('updated user %s lastlogin', self.username)
826 log.debug('updated user %s lastlogin', self.username)
824
827
825 def update_lastactivity(self):
828 def update_lastactivity(self):
826 """Update user lastactivity"""
829 """Update user lastactivity"""
827 usr = self
830 usr = self
828 old = usr.user_data
831 old = usr.user_data
829 old.update({'last_activity': time.time()})
832 old.update({'last_activity': time.time()})
830 usr.user_data = old
833 usr.user_data = old
831 Session().add(usr)
834 Session().add(usr)
832 log.debug('updated user %s lastactivity', usr.username)
835 log.debug('updated user %s lastactivity', usr.username)
833
836
834 def update_password(self, new_password):
837 def update_password(self, new_password):
835 from rhodecode.lib.auth import get_crypt_password
838 from rhodecode.lib.auth import get_crypt_password
836
839
837 self.password = get_crypt_password(new_password)
840 self.password = get_crypt_password(new_password)
838 Session().add(self)
841 Session().add(self)
839
842
840 @classmethod
843 @classmethod
841 def get_first_super_admin(cls):
844 def get_first_super_admin(cls):
842 user = User.query().filter(User.admin == true()).first()
845 user = User.query().filter(User.admin == true()).first()
843 if user is None:
846 if user is None:
844 raise Exception('FATAL: Missing administrative account!')
847 raise Exception('FATAL: Missing administrative account!')
845 return user
848 return user
846
849
847 @classmethod
850 @classmethod
848 def get_all_super_admins(cls):
851 def get_all_super_admins(cls):
849 """
852 """
850 Returns all admin accounts sorted by username
853 Returns all admin accounts sorted by username
851 """
854 """
852 return User.query().filter(User.admin == true())\
855 return User.query().filter(User.admin == true())\
853 .order_by(User.username.asc()).all()
856 .order_by(User.username.asc()).all()
854
857
855 @classmethod
858 @classmethod
856 def get_default_user(cls, cache=False):
859 def get_default_user(cls, cache=False):
857 user = User.get_by_username(User.DEFAULT_USER, cache=cache)
860 user = User.get_by_username(User.DEFAULT_USER, cache=cache)
858 if user is None:
861 if user is None:
859 raise Exception('FATAL: Missing default account!')
862 raise Exception('FATAL: Missing default account!')
860 return user
863 return user
861
864
862 def _get_default_perms(self, user, suffix=''):
865 def _get_default_perms(self, user, suffix=''):
863 from rhodecode.model.permission import PermissionModel
866 from rhodecode.model.permission import PermissionModel
864 return PermissionModel().get_default_perms(user.user_perms, suffix)
867 return PermissionModel().get_default_perms(user.user_perms, suffix)
865
868
866 def get_default_perms(self, suffix=''):
869 def get_default_perms(self, suffix=''):
867 return self._get_default_perms(self, suffix)
870 return self._get_default_perms(self, suffix)
868
871
869 def get_api_data(self, include_secrets=False, details='full'):
872 def get_api_data(self, include_secrets=False, details='full'):
870 """
873 """
871 Common function for generating user related data for API
874 Common function for generating user related data for API
872
875
873 :param include_secrets: By default secrets in the API data will be replaced
876 :param include_secrets: By default secrets in the API data will be replaced
874 by a placeholder value to prevent exposing this data by accident. In case
877 by a placeholder value to prevent exposing this data by accident. In case
875 this data shall be exposed, set this flag to ``True``.
878 this data shall be exposed, set this flag to ``True``.
876
879
877 :param details: details can be 'basic|full' basic gives only a subset of
880 :param details: details can be 'basic|full' basic gives only a subset of
878 the available user information that includes user_id, name and emails.
881 the available user information that includes user_id, name and emails.
879 """
882 """
880 user = self
883 user = self
881 user_data = self.user_data
884 user_data = self.user_data
882 data = {
885 data = {
883 'user_id': user.user_id,
886 'user_id': user.user_id,
884 'username': user.username,
887 'username': user.username,
885 'firstname': user.name,
888 'firstname': user.name,
886 'lastname': user.lastname,
889 'lastname': user.lastname,
887 'email': user.email,
890 'email': user.email,
888 'emails': user.emails,
891 'emails': user.emails,
889 }
892 }
890 if details == 'basic':
893 if details == 'basic':
891 return data
894 return data
892
895
893 api_key_length = 40
896 api_key_length = 40
894 api_key_replacement = '*' * api_key_length
897 api_key_replacement = '*' * api_key_length
895
898
896 extras = {
899 extras = {
897 'api_keys': [api_key_replacement],
900 'api_keys': [api_key_replacement],
898 'active': user.active,
901 'active': user.active,
899 'admin': user.admin,
902 'admin': user.admin,
900 'extern_type': user.extern_type,
903 'extern_type': user.extern_type,
901 'extern_name': user.extern_name,
904 'extern_name': user.extern_name,
902 'last_login': user.last_login,
905 'last_login': user.last_login,
903 'ip_addresses': user.ip_addresses,
906 'ip_addresses': user.ip_addresses,
904 'language': user_data.get('language')
907 'language': user_data.get('language')
905 }
908 }
906 data.update(extras)
909 data.update(extras)
907
910
908 if include_secrets:
911 if include_secrets:
909 data['api_keys'] = user.auth_tokens
912 data['api_keys'] = user.auth_tokens
910 return data
913 return data
911
914
912 def __json__(self):
915 def __json__(self):
913 data = {
916 data = {
914 'full_name': self.full_name,
917 'full_name': self.full_name,
915 'full_name_or_username': self.full_name_or_username,
918 'full_name_or_username': self.full_name_or_username,
916 'short_contact': self.short_contact,
919 'short_contact': self.short_contact,
917 'full_contact': self.full_contact,
920 'full_contact': self.full_contact,
918 }
921 }
919 data.update(self.get_api_data())
922 data.update(self.get_api_data())
920 return data
923 return data
921
924
922
925
923 class UserApiKeys(Base, BaseModel):
926 class UserApiKeys(Base, BaseModel):
924 __tablename__ = 'user_api_keys'
927 __tablename__ = 'user_api_keys'
925 __table_args__ = (
928 __table_args__ = (
926 Index('uak_api_key_idx', 'api_key'),
929 Index('uak_api_key_idx', 'api_key'),
927 Index('uak_api_key_expires_idx', 'api_key', 'expires'),
930 Index('uak_api_key_expires_idx', 'api_key', 'expires'),
928 UniqueConstraint('api_key'),
931 UniqueConstraint('api_key'),
929 {'extend_existing': True, 'mysql_engine': 'InnoDB',
932 {'extend_existing': True, 'mysql_engine': 'InnoDB',
930 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
933 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
931 )
934 )
932 __mapper_args__ = {}
935 __mapper_args__ = {}
933
936
934 # ApiKey role
937 # ApiKey role
935 ROLE_ALL = 'token_role_all'
938 ROLE_ALL = 'token_role_all'
936 ROLE_HTTP = 'token_role_http'
939 ROLE_HTTP = 'token_role_http'
937 ROLE_VCS = 'token_role_vcs'
940 ROLE_VCS = 'token_role_vcs'
938 ROLE_API = 'token_role_api'
941 ROLE_API = 'token_role_api'
939 ROLE_FEED = 'token_role_feed'
942 ROLE_FEED = 'token_role_feed'
940 ROLE_PASSWORD_RESET = 'token_password_reset'
943 ROLE_PASSWORD_RESET = 'token_password_reset'
941
944
942 ROLES = [ROLE_ALL, ROLE_HTTP, ROLE_VCS, ROLE_API, ROLE_FEED]
945 ROLES = [ROLE_ALL, ROLE_HTTP, ROLE_VCS, ROLE_API, ROLE_FEED]
943
946
944 user_api_key_id = Column("user_api_key_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
947 user_api_key_id = Column("user_api_key_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
945 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
948 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
946 api_key = Column("api_key", String(255), nullable=False, unique=True)
949 api_key = Column("api_key", String(255), nullable=False, unique=True)
947 description = Column('description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
950 description = Column('description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
948 expires = Column('expires', Float(53), nullable=False)
951 expires = Column('expires', Float(53), nullable=False)
949 role = Column('role', String(255), nullable=True)
952 role = Column('role', String(255), nullable=True)
950 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
953 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
951
954
952 # scope columns
955 # scope columns
953 repo_id = Column(
956 repo_id = Column(
954 'repo_id', Integer(), ForeignKey('repositories.repo_id'),
957 'repo_id', Integer(), ForeignKey('repositories.repo_id'),
955 nullable=True, unique=None, default=None)
958 nullable=True, unique=None, default=None)
956 repo = relationship('Repository', lazy='joined')
959 repo = relationship('Repository', lazy='joined')
957
960
958 repo_group_id = Column(
961 repo_group_id = Column(
959 'repo_group_id', Integer(), ForeignKey('groups.group_id'),
962 'repo_group_id', Integer(), ForeignKey('groups.group_id'),
960 nullable=True, unique=None, default=None)
963 nullable=True, unique=None, default=None)
961 repo_group = relationship('RepoGroup', lazy='joined')
964 repo_group = relationship('RepoGroup', lazy='joined')
962
965
963 user = relationship('User', lazy='joined')
966 user = relationship('User', lazy='joined')
964
967
968 def __unicode__(self):
969 return u"<%s('%s')>" % (self.__class__.__name__, self.role)
970
965 @classmethod
971 @classmethod
966 def _get_role_name(cls, role):
972 def _get_role_name(cls, role):
967 return {
973 return {
968 cls.ROLE_ALL: _('all'),
974 cls.ROLE_ALL: _('all'),
969 cls.ROLE_HTTP: _('http/web interface'),
975 cls.ROLE_HTTP: _('http/web interface'),
970 cls.ROLE_VCS: _('vcs (git/hg/svn protocol)'),
976 cls.ROLE_VCS: _('vcs (git/hg/svn protocol)'),
971 cls.ROLE_API: _('api calls'),
977 cls.ROLE_API: _('api calls'),
972 cls.ROLE_FEED: _('feed access'),
978 cls.ROLE_FEED: _('feed access'),
973 }.get(role, role)
979 }.get(role, role)
974
980
975 @property
981 @property
976 def expired(self):
982 def expired(self):
977 if self.expires == -1:
983 if self.expires == -1:
978 return False
984 return False
979 return time.time() > self.expires
985 return time.time() > self.expires
980
986
981 @property
987 @property
982 def role_humanized(self):
988 def role_humanized(self):
983 return self._get_role_name(self.role)
989 return self._get_role_name(self.role)
984
990
985 def _get_scope(self):
991 def _get_scope(self):
986 if self.repo:
992 if self.repo:
987 return repr(self.repo)
993 return repr(self.repo)
988 if self.repo_group:
994 if self.repo_group:
989 return repr(self.repo_group) + ' (recursive)'
995 return repr(self.repo_group) + ' (recursive)'
990 return 'global'
996 return 'global'
991
997
992 @property
998 @property
993 def scope_humanized(self):
999 def scope_humanized(self):
994 return self._get_scope()
1000 return self._get_scope()
995
1001
996
1002
997 class UserEmailMap(Base, BaseModel):
1003 class UserEmailMap(Base, BaseModel):
998 __tablename__ = 'user_email_map'
1004 __tablename__ = 'user_email_map'
999 __table_args__ = (
1005 __table_args__ = (
1000 Index('uem_email_idx', 'email'),
1006 Index('uem_email_idx', 'email'),
1001 UniqueConstraint('email'),
1007 UniqueConstraint('email'),
1002 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1008 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1003 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
1009 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
1004 )
1010 )
1005 __mapper_args__ = {}
1011 __mapper_args__ = {}
1006
1012
1007 email_id = Column("email_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1013 email_id = Column("email_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1008 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1014 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1009 _email = Column("email", String(255), nullable=True, unique=False, default=None)
1015 _email = Column("email", String(255), nullable=True, unique=False, default=None)
1010 user = relationship('User', lazy='joined')
1016 user = relationship('User', lazy='joined')
1011
1017
1012 @validates('_email')
1018 @validates('_email')
1013 def validate_email(self, key, email):
1019 def validate_email(self, key, email):
1014 # check if this email is not main one
1020 # check if this email is not main one
1015 main_email = Session().query(User).filter(User.email == email).scalar()
1021 main_email = Session().query(User).filter(User.email == email).scalar()
1016 if main_email is not None:
1022 if main_email is not None:
1017 raise AttributeError('email %s is present is user table' % email)
1023 raise AttributeError('email %s is present is user table' % email)
1018 return email
1024 return email
1019
1025
1020 @hybrid_property
1026 @hybrid_property
1021 def email(self):
1027 def email(self):
1022 return self._email
1028 return self._email
1023
1029
1024 @email.setter
1030 @email.setter
1025 def email(self, val):
1031 def email(self, val):
1026 self._email = val.lower() if val else None
1032 self._email = val.lower() if val else None
1027
1033
1028
1034
1029 class UserIpMap(Base, BaseModel):
1035 class UserIpMap(Base, BaseModel):
1030 __tablename__ = 'user_ip_map'
1036 __tablename__ = 'user_ip_map'
1031 __table_args__ = (
1037 __table_args__ = (
1032 UniqueConstraint('user_id', 'ip_addr'),
1038 UniqueConstraint('user_id', 'ip_addr'),
1033 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1039 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1034 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
1040 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
1035 )
1041 )
1036 __mapper_args__ = {}
1042 __mapper_args__ = {}
1037
1043
1038 ip_id = Column("ip_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1044 ip_id = Column("ip_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1039 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1045 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1040 ip_addr = Column("ip_addr", String(255), nullable=True, unique=False, default=None)
1046 ip_addr = Column("ip_addr", String(255), nullable=True, unique=False, default=None)
1041 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
1047 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
1042 description = Column("description", String(10000), nullable=True, unique=None, default=None)
1048 description = Column("description", String(10000), nullable=True, unique=None, default=None)
1043 user = relationship('User', lazy='joined')
1049 user = relationship('User', lazy='joined')
1044
1050
1045 @classmethod
1051 @classmethod
1046 def _get_ip_range(cls, ip_addr):
1052 def _get_ip_range(cls, ip_addr):
1047 net = ipaddress.ip_network(ip_addr, strict=False)
1053 net = ipaddress.ip_network(ip_addr, strict=False)
1048 return [str(net.network_address), str(net.broadcast_address)]
1054 return [str(net.network_address), str(net.broadcast_address)]
1049
1055
1050 def __json__(self):
1056 def __json__(self):
1051 return {
1057 return {
1052 'ip_addr': self.ip_addr,
1058 'ip_addr': self.ip_addr,
1053 'ip_range': self._get_ip_range(self.ip_addr),
1059 'ip_range': self._get_ip_range(self.ip_addr),
1054 }
1060 }
1055
1061
1056 def __unicode__(self):
1062 def __unicode__(self):
1057 return u"<%s('user_id:%s=>%s')>" % (self.__class__.__name__,
1063 return u"<%s('user_id:%s=>%s')>" % (self.__class__.__name__,
1058 self.user_id, self.ip_addr)
1064 self.user_id, self.ip_addr)
1059
1065
1060
1066
1061 class UserLog(Base, BaseModel):
1067 class UserLog(Base, BaseModel):
1062 __tablename__ = 'user_logs'
1068 __tablename__ = 'user_logs'
1063 __table_args__ = (
1069 __table_args__ = (
1064 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1070 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1065 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1071 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1066 )
1072 )
1067 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1073 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1068 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1074 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1069 username = Column("username", String(255), nullable=True, unique=None, default=None)
1075 username = Column("username", String(255), nullable=True, unique=None, default=None)
1070 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True)
1076 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True)
1071 repository_name = Column("repository_name", String(255), nullable=True, unique=None, default=None)
1077 repository_name = Column("repository_name", String(255), nullable=True, unique=None, default=None)
1072 user_ip = Column("user_ip", String(255), nullable=True, unique=None, default=None)
1078 user_ip = Column("user_ip", String(255), nullable=True, unique=None, default=None)
1073 action = Column("action", Text().with_variant(Text(1200000), 'mysql'), nullable=True, unique=None, default=None)
1079 action = Column("action", Text().with_variant(Text(1200000), 'mysql'), nullable=True, unique=None, default=None)
1074 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
1080 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
1075
1081
1076 def __unicode__(self):
1082 def __unicode__(self):
1077 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
1083 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
1078 self.repository_name,
1084 self.repository_name,
1079 self.action)
1085 self.action)
1080
1086
1081 @property
1087 @property
1082 def action_as_day(self):
1088 def action_as_day(self):
1083 return datetime.date(*self.action_date.timetuple()[:3])
1089 return datetime.date(*self.action_date.timetuple()[:3])
1084
1090
1085 user = relationship('User')
1091 user = relationship('User')
1086 repository = relationship('Repository', cascade='')
1092 repository = relationship('Repository', cascade='')
1087
1093
1088
1094
1089 class UserGroup(Base, BaseModel):
1095 class UserGroup(Base, BaseModel):
1090 __tablename__ = 'users_groups'
1096 __tablename__ = 'users_groups'
1091 __table_args__ = (
1097 __table_args__ = (
1092 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1098 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1093 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1099 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1094 )
1100 )
1095
1101
1096 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1102 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1097 users_group_name = Column("users_group_name", String(255), nullable=False, unique=True, default=None)
1103 users_group_name = Column("users_group_name", String(255), nullable=False, unique=True, default=None)
1098 user_group_description = Column("user_group_description", String(10000), nullable=True, unique=None, default=None)
1104 user_group_description = Column("user_group_description", String(10000), nullable=True, unique=None, default=None)
1099 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
1105 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
1100 inherit_default_permissions = Column("users_group_inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
1106 inherit_default_permissions = Column("users_group_inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
1101 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
1107 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
1102 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1108 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1103 _group_data = Column("group_data", LargeBinary(), nullable=True) # JSON data
1109 _group_data = Column("group_data", LargeBinary(), nullable=True) # JSON data
1104
1110
1105 members = relationship('UserGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
1111 members = relationship('UserGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
1106 users_group_to_perm = relationship('UserGroupToPerm', cascade='all')
1112 users_group_to_perm = relationship('UserGroupToPerm', cascade='all')
1107 users_group_repo_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1113 users_group_repo_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1108 users_group_repo_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
1114 users_group_repo_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
1109 user_user_group_to_perm = relationship('UserUserGroupToPerm', cascade='all')
1115 user_user_group_to_perm = relationship('UserUserGroupToPerm', cascade='all')
1110 user_group_user_group_to_perm = relationship('UserGroupUserGroupToPerm ', primaryjoin="UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id", cascade='all')
1116 user_group_user_group_to_perm = relationship('UserGroupUserGroupToPerm ', primaryjoin="UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id", cascade='all')
1111
1117
1112 user = relationship('User')
1118 user = relationship('User')
1113
1119
1114 @hybrid_property
1120 @hybrid_property
1115 def group_data(self):
1121 def group_data(self):
1116 if not self._group_data:
1122 if not self._group_data:
1117 return {}
1123 return {}
1118
1124
1119 try:
1125 try:
1120 return json.loads(self._group_data)
1126 return json.loads(self._group_data)
1121 except TypeError:
1127 except TypeError:
1122 return {}
1128 return {}
1123
1129
1124 @group_data.setter
1130 @group_data.setter
1125 def group_data(self, val):
1131 def group_data(self, val):
1126 try:
1132 try:
1127 self._group_data = json.dumps(val)
1133 self._group_data = json.dumps(val)
1128 except Exception:
1134 except Exception:
1129 log.error(traceback.format_exc())
1135 log.error(traceback.format_exc())
1130
1136
1131 def __unicode__(self):
1137 def __unicode__(self):
1132 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
1138 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
1133 self.users_group_id,
1139 self.users_group_id,
1134 self.users_group_name)
1140 self.users_group_name)
1135
1141
1136 @classmethod
1142 @classmethod
1137 def get_by_group_name(cls, group_name, cache=False,
1143 def get_by_group_name(cls, group_name, cache=False,
1138 case_insensitive=False):
1144 case_insensitive=False):
1139 if case_insensitive:
1145 if case_insensitive:
1140 q = cls.query().filter(func.lower(cls.users_group_name) ==
1146 q = cls.query().filter(func.lower(cls.users_group_name) ==
1141 func.lower(group_name))
1147 func.lower(group_name))
1142
1148
1143 else:
1149 else:
1144 q = cls.query().filter(cls.users_group_name == group_name)
1150 q = cls.query().filter(cls.users_group_name == group_name)
1145 if cache:
1151 if cache:
1146 q = q.options(FromCache(
1152 q = q.options(FromCache(
1147 "sql_cache_short",
1153 "sql_cache_short",
1148 "get_group_%s" % _hash_key(group_name)))
1154 "get_group_%s" % _hash_key(group_name)))
1149 return q.scalar()
1155 return q.scalar()
1150
1156
1151 @classmethod
1157 @classmethod
1152 def get(cls, user_group_id, cache=False):
1158 def get(cls, user_group_id, cache=False):
1153 user_group = cls.query()
1159 user_group = cls.query()
1154 if cache:
1160 if cache:
1155 user_group = user_group.options(FromCache("sql_cache_short",
1161 user_group = user_group.options(FromCache("sql_cache_short",
1156 "get_users_group_%s" % user_group_id))
1162 "get_users_group_%s" % user_group_id))
1157 return user_group.get(user_group_id)
1163 return user_group.get(user_group_id)
1158
1164
1159 def permissions(self, with_admins=True, with_owner=True):
1165 def permissions(self, with_admins=True, with_owner=True):
1160 q = UserUserGroupToPerm.query().filter(UserUserGroupToPerm.user_group == self)
1166 q = UserUserGroupToPerm.query().filter(UserUserGroupToPerm.user_group == self)
1161 q = q.options(joinedload(UserUserGroupToPerm.user_group),
1167 q = q.options(joinedload(UserUserGroupToPerm.user_group),
1162 joinedload(UserUserGroupToPerm.user),
1168 joinedload(UserUserGroupToPerm.user),
1163 joinedload(UserUserGroupToPerm.permission),)
1169 joinedload(UserUserGroupToPerm.permission),)
1164
1170
1165 # get owners and admins and permissions. We do a trick of re-writing
1171 # get owners and admins and permissions. We do a trick of re-writing
1166 # objects from sqlalchemy to named-tuples due to sqlalchemy session
1172 # objects from sqlalchemy to named-tuples due to sqlalchemy session
1167 # has a global reference and changing one object propagates to all
1173 # has a global reference and changing one object propagates to all
1168 # others. This means if admin is also an owner admin_row that change
1174 # others. This means if admin is also an owner admin_row that change
1169 # would propagate to both objects
1175 # would propagate to both objects
1170 perm_rows = []
1176 perm_rows = []
1171 for _usr in q.all():
1177 for _usr in q.all():
1172 usr = AttributeDict(_usr.user.get_dict())
1178 usr = AttributeDict(_usr.user.get_dict())
1173 usr.permission = _usr.permission.permission_name
1179 usr.permission = _usr.permission.permission_name
1174 perm_rows.append(usr)
1180 perm_rows.append(usr)
1175
1181
1176 # filter the perm rows by 'default' first and then sort them by
1182 # filter the perm rows by 'default' first and then sort them by
1177 # admin,write,read,none permissions sorted again alphabetically in
1183 # admin,write,read,none permissions sorted again alphabetically in
1178 # each group
1184 # each group
1179 perm_rows = sorted(perm_rows, key=display_sort)
1185 perm_rows = sorted(perm_rows, key=display_sort)
1180
1186
1181 _admin_perm = 'usergroup.admin'
1187 _admin_perm = 'usergroup.admin'
1182 owner_row = []
1188 owner_row = []
1183 if with_owner:
1189 if with_owner:
1184 usr = AttributeDict(self.user.get_dict())
1190 usr = AttributeDict(self.user.get_dict())
1185 usr.owner_row = True
1191 usr.owner_row = True
1186 usr.permission = _admin_perm
1192 usr.permission = _admin_perm
1187 owner_row.append(usr)
1193 owner_row.append(usr)
1188
1194
1189 super_admin_rows = []
1195 super_admin_rows = []
1190 if with_admins:
1196 if with_admins:
1191 for usr in User.get_all_super_admins():
1197 for usr in User.get_all_super_admins():
1192 # if this admin is also owner, don't double the record
1198 # if this admin is also owner, don't double the record
1193 if usr.user_id == owner_row[0].user_id:
1199 if usr.user_id == owner_row[0].user_id:
1194 owner_row[0].admin_row = True
1200 owner_row[0].admin_row = True
1195 else:
1201 else:
1196 usr = AttributeDict(usr.get_dict())
1202 usr = AttributeDict(usr.get_dict())
1197 usr.admin_row = True
1203 usr.admin_row = True
1198 usr.permission = _admin_perm
1204 usr.permission = _admin_perm
1199 super_admin_rows.append(usr)
1205 super_admin_rows.append(usr)
1200
1206
1201 return super_admin_rows + owner_row + perm_rows
1207 return super_admin_rows + owner_row + perm_rows
1202
1208
1203 def permission_user_groups(self):
1209 def permission_user_groups(self):
1204 q = UserGroupUserGroupToPerm.query().filter(UserGroupUserGroupToPerm.target_user_group == self)
1210 q = UserGroupUserGroupToPerm.query().filter(UserGroupUserGroupToPerm.target_user_group == self)
1205 q = q.options(joinedload(UserGroupUserGroupToPerm.user_group),
1211 q = q.options(joinedload(UserGroupUserGroupToPerm.user_group),
1206 joinedload(UserGroupUserGroupToPerm.target_user_group),
1212 joinedload(UserGroupUserGroupToPerm.target_user_group),
1207 joinedload(UserGroupUserGroupToPerm.permission),)
1213 joinedload(UserGroupUserGroupToPerm.permission),)
1208
1214
1209 perm_rows = []
1215 perm_rows = []
1210 for _user_group in q.all():
1216 for _user_group in q.all():
1211 usr = AttributeDict(_user_group.user_group.get_dict())
1217 usr = AttributeDict(_user_group.user_group.get_dict())
1212 usr.permission = _user_group.permission.permission_name
1218 usr.permission = _user_group.permission.permission_name
1213 perm_rows.append(usr)
1219 perm_rows.append(usr)
1214
1220
1215 return perm_rows
1221 return perm_rows
1216
1222
1217 def _get_default_perms(self, user_group, suffix=''):
1223 def _get_default_perms(self, user_group, suffix=''):
1218 from rhodecode.model.permission import PermissionModel
1224 from rhodecode.model.permission import PermissionModel
1219 return PermissionModel().get_default_perms(user_group.users_group_to_perm, suffix)
1225 return PermissionModel().get_default_perms(user_group.users_group_to_perm, suffix)
1220
1226
1221 def get_default_perms(self, suffix=''):
1227 def get_default_perms(self, suffix=''):
1222 return self._get_default_perms(self, suffix)
1228 return self._get_default_perms(self, suffix)
1223
1229
1224 def get_api_data(self, with_group_members=True, include_secrets=False):
1230 def get_api_data(self, with_group_members=True, include_secrets=False):
1225 """
1231 """
1226 :param include_secrets: See :meth:`User.get_api_data`, this parameter is
1232 :param include_secrets: See :meth:`User.get_api_data`, this parameter is
1227 basically forwarded.
1233 basically forwarded.
1228
1234
1229 """
1235 """
1230 user_group = self
1236 user_group = self
1231
1237
1232 data = {
1238 data = {
1233 'users_group_id': user_group.users_group_id,
1239 'users_group_id': user_group.users_group_id,
1234 'group_name': user_group.users_group_name,
1240 'group_name': user_group.users_group_name,
1235 'group_description': user_group.user_group_description,
1241 'group_description': user_group.user_group_description,
1236 'active': user_group.users_group_active,
1242 'active': user_group.users_group_active,
1237 'owner': user_group.user.username,
1243 'owner': user_group.user.username,
1238 }
1244 }
1239 if with_group_members:
1245 if with_group_members:
1240 users = []
1246 users = []
1241 for user in user_group.members:
1247 for user in user_group.members:
1242 user = user.user
1248 user = user.user
1243 users.append(user.get_api_data(include_secrets=include_secrets))
1249 users.append(user.get_api_data(include_secrets=include_secrets))
1244 data['users'] = users
1250 data['users'] = users
1245
1251
1246 return data
1252 return data
1247
1253
1248
1254
1249 class UserGroupMember(Base, BaseModel):
1255 class UserGroupMember(Base, BaseModel):
1250 __tablename__ = 'users_groups_members'
1256 __tablename__ = 'users_groups_members'
1251 __table_args__ = (
1257 __table_args__ = (
1252 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1258 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1253 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1259 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1254 )
1260 )
1255
1261
1256 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1262 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1257 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1263 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1258 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1264 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1259
1265
1260 user = relationship('User', lazy='joined')
1266 user = relationship('User', lazy='joined')
1261 users_group = relationship('UserGroup')
1267 users_group = relationship('UserGroup')
1262
1268
1263 def __init__(self, gr_id='', u_id=''):
1269 def __init__(self, gr_id='', u_id=''):
1264 self.users_group_id = gr_id
1270 self.users_group_id = gr_id
1265 self.user_id = u_id
1271 self.user_id = u_id
1266
1272
1267
1273
1268 class RepositoryField(Base, BaseModel):
1274 class RepositoryField(Base, BaseModel):
1269 __tablename__ = 'repositories_fields'
1275 __tablename__ = 'repositories_fields'
1270 __table_args__ = (
1276 __table_args__ = (
1271 UniqueConstraint('repository_id', 'field_key'), # no-multi field
1277 UniqueConstraint('repository_id', 'field_key'), # no-multi field
1272 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1278 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1273 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1279 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1274 )
1280 )
1275 PREFIX = 'ex_' # prefix used in form to not conflict with already existing fields
1281 PREFIX = 'ex_' # prefix used in form to not conflict with already existing fields
1276
1282
1277 repo_field_id = Column("repo_field_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1283 repo_field_id = Column("repo_field_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1278 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1284 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1279 field_key = Column("field_key", String(250))
1285 field_key = Column("field_key", String(250))
1280 field_label = Column("field_label", String(1024), nullable=False)
1286 field_label = Column("field_label", String(1024), nullable=False)
1281 field_value = Column("field_value", String(10000), nullable=False)
1287 field_value = Column("field_value", String(10000), nullable=False)
1282 field_desc = Column("field_desc", String(1024), nullable=False)
1288 field_desc = Column("field_desc", String(1024), nullable=False)
1283 field_type = Column("field_type", String(255), nullable=False, unique=None)
1289 field_type = Column("field_type", String(255), nullable=False, unique=None)
1284 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1290 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1285
1291
1286 repository = relationship('Repository')
1292 repository = relationship('Repository')
1287
1293
1288 @property
1294 @property
1289 def field_key_prefixed(self):
1295 def field_key_prefixed(self):
1290 return 'ex_%s' % self.field_key
1296 return 'ex_%s' % self.field_key
1291
1297
1292 @classmethod
1298 @classmethod
1293 def un_prefix_key(cls, key):
1299 def un_prefix_key(cls, key):
1294 if key.startswith(cls.PREFIX):
1300 if key.startswith(cls.PREFIX):
1295 return key[len(cls.PREFIX):]
1301 return key[len(cls.PREFIX):]
1296 return key
1302 return key
1297
1303
1298 @classmethod
1304 @classmethod
1299 def get_by_key_name(cls, key, repo):
1305 def get_by_key_name(cls, key, repo):
1300 row = cls.query()\
1306 row = cls.query()\
1301 .filter(cls.repository == repo)\
1307 .filter(cls.repository == repo)\
1302 .filter(cls.field_key == key).scalar()
1308 .filter(cls.field_key == key).scalar()
1303 return row
1309 return row
1304
1310
1305
1311
1306 class Repository(Base, BaseModel):
1312 class Repository(Base, BaseModel):
1307 __tablename__ = 'repositories'
1313 __tablename__ = 'repositories'
1308 __table_args__ = (
1314 __table_args__ = (
1309 Index('r_repo_name_idx', 'repo_name', mysql_length=255),
1315 Index('r_repo_name_idx', 'repo_name', mysql_length=255),
1310 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1316 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1311 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1317 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1312 )
1318 )
1313 DEFAULT_CLONE_URI = '{scheme}://{user}@{netloc}/{repo}'
1319 DEFAULT_CLONE_URI = '{scheme}://{user}@{netloc}/{repo}'
1314 DEFAULT_CLONE_URI_ID = '{scheme}://{user}@{netloc}/_{repoid}'
1320 DEFAULT_CLONE_URI_ID = '{scheme}://{user}@{netloc}/_{repoid}'
1315
1321
1316 STATE_CREATED = 'repo_state_created'
1322 STATE_CREATED = 'repo_state_created'
1317 STATE_PENDING = 'repo_state_pending'
1323 STATE_PENDING = 'repo_state_pending'
1318 STATE_ERROR = 'repo_state_error'
1324 STATE_ERROR = 'repo_state_error'
1319
1325
1320 LOCK_AUTOMATIC = 'lock_auto'
1326 LOCK_AUTOMATIC = 'lock_auto'
1321 LOCK_API = 'lock_api'
1327 LOCK_API = 'lock_api'
1322 LOCK_WEB = 'lock_web'
1328 LOCK_WEB = 'lock_web'
1323 LOCK_PULL = 'lock_pull'
1329 LOCK_PULL = 'lock_pull'
1324
1330
1325 NAME_SEP = URL_SEP
1331 NAME_SEP = URL_SEP
1326
1332
1327 repo_id = Column(
1333 repo_id = Column(
1328 "repo_id", Integer(), nullable=False, unique=True, default=None,
1334 "repo_id", Integer(), nullable=False, unique=True, default=None,
1329 primary_key=True)
1335 primary_key=True)
1330 _repo_name = Column(
1336 _repo_name = Column(
1331 "repo_name", Text(), nullable=False, default=None)
1337 "repo_name", Text(), nullable=False, default=None)
1332 _repo_name_hash = Column(
1338 _repo_name_hash = Column(
1333 "repo_name_hash", String(255), nullable=False, unique=True)
1339 "repo_name_hash", String(255), nullable=False, unique=True)
1334 repo_state = Column("repo_state", String(255), nullable=True)
1340 repo_state = Column("repo_state", String(255), nullable=True)
1335
1341
1336 clone_uri = Column(
1342 clone_uri = Column(
1337 "clone_uri", EncryptedTextValue(), nullable=True, unique=False,
1343 "clone_uri", EncryptedTextValue(), nullable=True, unique=False,
1338 default=None)
1344 default=None)
1339 repo_type = Column(
1345 repo_type = Column(
1340 "repo_type", String(255), nullable=False, unique=False, default=None)
1346 "repo_type", String(255), nullable=False, unique=False, default=None)
1341 user_id = Column(
1347 user_id = Column(
1342 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
1348 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
1343 unique=False, default=None)
1349 unique=False, default=None)
1344 private = Column(
1350 private = Column(
1345 "private", Boolean(), nullable=True, unique=None, default=None)
1351 "private", Boolean(), nullable=True, unique=None, default=None)
1346 enable_statistics = Column(
1352 enable_statistics = Column(
1347 "statistics", Boolean(), nullable=True, unique=None, default=True)
1353 "statistics", Boolean(), nullable=True, unique=None, default=True)
1348 enable_downloads = Column(
1354 enable_downloads = Column(
1349 "downloads", Boolean(), nullable=True, unique=None, default=True)
1355 "downloads", Boolean(), nullable=True, unique=None, default=True)
1350 description = Column(
1356 description = Column(
1351 "description", String(10000), nullable=True, unique=None, default=None)
1357 "description", String(10000), nullable=True, unique=None, default=None)
1352 created_on = Column(
1358 created_on = Column(
1353 'created_on', DateTime(timezone=False), nullable=True, unique=None,
1359 'created_on', DateTime(timezone=False), nullable=True, unique=None,
1354 default=datetime.datetime.now)
1360 default=datetime.datetime.now)
1355 updated_on = Column(
1361 updated_on = Column(
1356 'updated_on', DateTime(timezone=False), nullable=True, unique=None,
1362 'updated_on', DateTime(timezone=False), nullable=True, unique=None,
1357 default=datetime.datetime.now)
1363 default=datetime.datetime.now)
1358 _landing_revision = Column(
1364 _landing_revision = Column(
1359 "landing_revision", String(255), nullable=False, unique=False,
1365 "landing_revision", String(255), nullable=False, unique=False,
1360 default=None)
1366 default=None)
1361 enable_locking = Column(
1367 enable_locking = Column(
1362 "enable_locking", Boolean(), nullable=False, unique=None,
1368 "enable_locking", Boolean(), nullable=False, unique=None,
1363 default=False)
1369 default=False)
1364 _locked = Column(
1370 _locked = Column(
1365 "locked", String(255), nullable=True, unique=False, default=None)
1371 "locked", String(255), nullable=True, unique=False, default=None)
1366 _changeset_cache = Column(
1372 _changeset_cache = Column(
1367 "changeset_cache", LargeBinary(), nullable=True) # JSON data
1373 "changeset_cache", LargeBinary(), nullable=True) # JSON data
1368
1374
1369 fork_id = Column(
1375 fork_id = Column(
1370 "fork_id", Integer(), ForeignKey('repositories.repo_id'),
1376 "fork_id", Integer(), ForeignKey('repositories.repo_id'),
1371 nullable=True, unique=False, default=None)
1377 nullable=True, unique=False, default=None)
1372 group_id = Column(
1378 group_id = Column(
1373 "group_id", Integer(), ForeignKey('groups.group_id'), nullable=True,
1379 "group_id", Integer(), ForeignKey('groups.group_id'), nullable=True,
1374 unique=False, default=None)
1380 unique=False, default=None)
1375
1381
1376 user = relationship('User', lazy='joined')
1382 user = relationship('User', lazy='joined')
1377 fork = relationship('Repository', remote_side=repo_id, lazy='joined')
1383 fork = relationship('Repository', remote_side=repo_id, lazy='joined')
1378 group = relationship('RepoGroup', lazy='joined')
1384 group = relationship('RepoGroup', lazy='joined')
1379 repo_to_perm = relationship(
1385 repo_to_perm = relationship(
1380 'UserRepoToPerm', cascade='all',
1386 'UserRepoToPerm', cascade='all',
1381 order_by='UserRepoToPerm.repo_to_perm_id')
1387 order_by='UserRepoToPerm.repo_to_perm_id')
1382 users_group_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1388 users_group_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1383 stats = relationship('Statistics', cascade='all', uselist=False)
1389 stats = relationship('Statistics', cascade='all', uselist=False)
1384
1390
1385 followers = relationship(
1391 followers = relationship(
1386 'UserFollowing',
1392 'UserFollowing',
1387 primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id',
1393 primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id',
1388 cascade='all')
1394 cascade='all')
1389 extra_fields = relationship(
1395 extra_fields = relationship(
1390 'RepositoryField', cascade="all, delete, delete-orphan")
1396 'RepositoryField', cascade="all, delete, delete-orphan")
1391 logs = relationship('UserLog')
1397 logs = relationship('UserLog')
1392 comments = relationship(
1398 comments = relationship(
1393 'ChangesetComment', cascade="all, delete, delete-orphan")
1399 'ChangesetComment', cascade="all, delete, delete-orphan")
1394 pull_requests_source = relationship(
1400 pull_requests_source = relationship(
1395 'PullRequest',
1401 'PullRequest',
1396 primaryjoin='PullRequest.source_repo_id==Repository.repo_id',
1402 primaryjoin='PullRequest.source_repo_id==Repository.repo_id',
1397 cascade="all, delete, delete-orphan")
1403 cascade="all, delete, delete-orphan")
1398 pull_requests_target = relationship(
1404 pull_requests_target = relationship(
1399 'PullRequest',
1405 'PullRequest',
1400 primaryjoin='PullRequest.target_repo_id==Repository.repo_id',
1406 primaryjoin='PullRequest.target_repo_id==Repository.repo_id',
1401 cascade="all, delete, delete-orphan")
1407 cascade="all, delete, delete-orphan")
1402 ui = relationship('RepoRhodeCodeUi', cascade="all")
1408 ui = relationship('RepoRhodeCodeUi', cascade="all")
1403 settings = relationship('RepoRhodeCodeSetting', cascade="all")
1409 settings = relationship('RepoRhodeCodeSetting', cascade="all")
1404 integrations = relationship('Integration',
1410 integrations = relationship('Integration',
1405 cascade="all, delete, delete-orphan")
1411 cascade="all, delete, delete-orphan")
1406
1412
1407 def __unicode__(self):
1413 def __unicode__(self):
1408 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id,
1414 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id,
1409 safe_unicode(self.repo_name))
1415 safe_unicode(self.repo_name))
1410
1416
1411 @hybrid_property
1417 @hybrid_property
1412 def landing_rev(self):
1418 def landing_rev(self):
1413 # always should return [rev_type, rev]
1419 # always should return [rev_type, rev]
1414 if self._landing_revision:
1420 if self._landing_revision:
1415 _rev_info = self._landing_revision.split(':')
1421 _rev_info = self._landing_revision.split(':')
1416 if len(_rev_info) < 2:
1422 if len(_rev_info) < 2:
1417 _rev_info.insert(0, 'rev')
1423 _rev_info.insert(0, 'rev')
1418 return [_rev_info[0], _rev_info[1]]
1424 return [_rev_info[0], _rev_info[1]]
1419 return [None, None]
1425 return [None, None]
1420
1426
1421 @landing_rev.setter
1427 @landing_rev.setter
1422 def landing_rev(self, val):
1428 def landing_rev(self, val):
1423 if ':' not in val:
1429 if ':' not in val:
1424 raise ValueError('value must be delimited with `:` and consist '
1430 raise ValueError('value must be delimited with `:` and consist '
1425 'of <rev_type>:<rev>, got %s instead' % val)
1431 'of <rev_type>:<rev>, got %s instead' % val)
1426 self._landing_revision = val
1432 self._landing_revision = val
1427
1433
1428 @hybrid_property
1434 @hybrid_property
1429 def locked(self):
1435 def locked(self):
1430 if self._locked:
1436 if self._locked:
1431 user_id, timelocked, reason = self._locked.split(':')
1437 user_id, timelocked, reason = self._locked.split(':')
1432 lock_values = int(user_id), timelocked, reason
1438 lock_values = int(user_id), timelocked, reason
1433 else:
1439 else:
1434 lock_values = [None, None, None]
1440 lock_values = [None, None, None]
1435 return lock_values
1441 return lock_values
1436
1442
1437 @locked.setter
1443 @locked.setter
1438 def locked(self, val):
1444 def locked(self, val):
1439 if val and isinstance(val, (list, tuple)):
1445 if val and isinstance(val, (list, tuple)):
1440 self._locked = ':'.join(map(str, val))
1446 self._locked = ':'.join(map(str, val))
1441 else:
1447 else:
1442 self._locked = None
1448 self._locked = None
1443
1449
1444 @hybrid_property
1450 @hybrid_property
1445 def changeset_cache(self):
1451 def changeset_cache(self):
1446 from rhodecode.lib.vcs.backends.base import EmptyCommit
1452 from rhodecode.lib.vcs.backends.base import EmptyCommit
1447 dummy = EmptyCommit().__json__()
1453 dummy = EmptyCommit().__json__()
1448 if not self._changeset_cache:
1454 if not self._changeset_cache:
1449 return dummy
1455 return dummy
1450 try:
1456 try:
1451 return json.loads(self._changeset_cache)
1457 return json.loads(self._changeset_cache)
1452 except TypeError:
1458 except TypeError:
1453 return dummy
1459 return dummy
1454 except Exception:
1460 except Exception:
1455 log.error(traceback.format_exc())
1461 log.error(traceback.format_exc())
1456 return dummy
1462 return dummy
1457
1463
1458 @changeset_cache.setter
1464 @changeset_cache.setter
1459 def changeset_cache(self, val):
1465 def changeset_cache(self, val):
1460 try:
1466 try:
1461 self._changeset_cache = json.dumps(val)
1467 self._changeset_cache = json.dumps(val)
1462 except Exception:
1468 except Exception:
1463 log.error(traceback.format_exc())
1469 log.error(traceback.format_exc())
1464
1470
1465 @hybrid_property
1471 @hybrid_property
1466 def repo_name(self):
1472 def repo_name(self):
1467 return self._repo_name
1473 return self._repo_name
1468
1474
1469 @repo_name.setter
1475 @repo_name.setter
1470 def repo_name(self, value):
1476 def repo_name(self, value):
1471 self._repo_name = value
1477 self._repo_name = value
1472 self._repo_name_hash = hashlib.sha1(safe_str(value)).hexdigest()
1478 self._repo_name_hash = hashlib.sha1(safe_str(value)).hexdigest()
1473
1479
1474 @classmethod
1480 @classmethod
1475 def normalize_repo_name(cls, repo_name):
1481 def normalize_repo_name(cls, repo_name):
1476 """
1482 """
1477 Normalizes os specific repo_name to the format internally stored inside
1483 Normalizes os specific repo_name to the format internally stored inside
1478 database using URL_SEP
1484 database using URL_SEP
1479
1485
1480 :param cls:
1486 :param cls:
1481 :param repo_name:
1487 :param repo_name:
1482 """
1488 """
1483 return cls.NAME_SEP.join(repo_name.split(os.sep))
1489 return cls.NAME_SEP.join(repo_name.split(os.sep))
1484
1490
1485 @classmethod
1491 @classmethod
1486 def get_by_repo_name(cls, repo_name, cache=False, identity_cache=False):
1492 def get_by_repo_name(cls, repo_name, cache=False, identity_cache=False):
1487 session = Session()
1493 session = Session()
1488 q = session.query(cls).filter(cls.repo_name == repo_name)
1494 q = session.query(cls).filter(cls.repo_name == repo_name)
1489
1495
1490 if cache:
1496 if cache:
1491 if identity_cache:
1497 if identity_cache:
1492 val = cls.identity_cache(session, 'repo_name', repo_name)
1498 val = cls.identity_cache(session, 'repo_name', repo_name)
1493 if val:
1499 if val:
1494 return val
1500 return val
1495 else:
1501 else:
1496 q = q.options(
1502 q = q.options(
1497 FromCache("sql_cache_short",
1503 FromCache("sql_cache_short",
1498 "get_repo_by_name_%s" % _hash_key(repo_name)))
1504 "get_repo_by_name_%s" % _hash_key(repo_name)))
1499
1505
1500 return q.scalar()
1506 return q.scalar()
1501
1507
1502 @classmethod
1508 @classmethod
1503 def get_by_full_path(cls, repo_full_path):
1509 def get_by_full_path(cls, repo_full_path):
1504 repo_name = repo_full_path.split(cls.base_path(), 1)[-1]
1510 repo_name = repo_full_path.split(cls.base_path(), 1)[-1]
1505 repo_name = cls.normalize_repo_name(repo_name)
1511 repo_name = cls.normalize_repo_name(repo_name)
1506 return cls.get_by_repo_name(repo_name.strip(URL_SEP))
1512 return cls.get_by_repo_name(repo_name.strip(URL_SEP))
1507
1513
1508 @classmethod
1514 @classmethod
1509 def get_repo_forks(cls, repo_id):
1515 def get_repo_forks(cls, repo_id):
1510 return cls.query().filter(Repository.fork_id == repo_id)
1516 return cls.query().filter(Repository.fork_id == repo_id)
1511
1517
1512 @classmethod
1518 @classmethod
1513 def base_path(cls):
1519 def base_path(cls):
1514 """
1520 """
1515 Returns base path when all repos are stored
1521 Returns base path when all repos are stored
1516
1522
1517 :param cls:
1523 :param cls:
1518 """
1524 """
1519 q = Session().query(RhodeCodeUi)\
1525 q = Session().query(RhodeCodeUi)\
1520 .filter(RhodeCodeUi.ui_key == cls.NAME_SEP)
1526 .filter(RhodeCodeUi.ui_key == cls.NAME_SEP)
1521 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1527 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1522 return q.one().ui_value
1528 return q.one().ui_value
1523
1529
1524 @classmethod
1530 @classmethod
1525 def is_valid(cls, repo_name):
1531 def is_valid(cls, repo_name):
1526 """
1532 """
1527 returns True if given repo name is a valid filesystem repository
1533 returns True if given repo name is a valid filesystem repository
1528
1534
1529 :param cls:
1535 :param cls:
1530 :param repo_name:
1536 :param repo_name:
1531 """
1537 """
1532 from rhodecode.lib.utils import is_valid_repo
1538 from rhodecode.lib.utils import is_valid_repo
1533
1539
1534 return is_valid_repo(repo_name, cls.base_path())
1540 return is_valid_repo(repo_name, cls.base_path())
1535
1541
1536 @classmethod
1542 @classmethod
1537 def get_all_repos(cls, user_id=Optional(None), group_id=Optional(None),
1543 def get_all_repos(cls, user_id=Optional(None), group_id=Optional(None),
1538 case_insensitive=True):
1544 case_insensitive=True):
1539 q = Repository.query()
1545 q = Repository.query()
1540
1546
1541 if not isinstance(user_id, Optional):
1547 if not isinstance(user_id, Optional):
1542 q = q.filter(Repository.user_id == user_id)
1548 q = q.filter(Repository.user_id == user_id)
1543
1549
1544 if not isinstance(group_id, Optional):
1550 if not isinstance(group_id, Optional):
1545 q = q.filter(Repository.group_id == group_id)
1551 q = q.filter(Repository.group_id == group_id)
1546
1552
1547 if case_insensitive:
1553 if case_insensitive:
1548 q = q.order_by(func.lower(Repository.repo_name))
1554 q = q.order_by(func.lower(Repository.repo_name))
1549 else:
1555 else:
1550 q = q.order_by(Repository.repo_name)
1556 q = q.order_by(Repository.repo_name)
1551 return q.all()
1557 return q.all()
1552
1558
1553 @property
1559 @property
1554 def forks(self):
1560 def forks(self):
1555 """
1561 """
1556 Return forks of this repo
1562 Return forks of this repo
1557 """
1563 """
1558 return Repository.get_repo_forks(self.repo_id)
1564 return Repository.get_repo_forks(self.repo_id)
1559
1565
1560 @property
1566 @property
1561 def parent(self):
1567 def parent(self):
1562 """
1568 """
1563 Returns fork parent
1569 Returns fork parent
1564 """
1570 """
1565 return self.fork
1571 return self.fork
1566
1572
1567 @property
1573 @property
1568 def just_name(self):
1574 def just_name(self):
1569 return self.repo_name.split(self.NAME_SEP)[-1]
1575 return self.repo_name.split(self.NAME_SEP)[-1]
1570
1576
1571 @property
1577 @property
1572 def groups_with_parents(self):
1578 def groups_with_parents(self):
1573 groups = []
1579 groups = []
1574 if self.group is None:
1580 if self.group is None:
1575 return groups
1581 return groups
1576
1582
1577 cur_gr = self.group
1583 cur_gr = self.group
1578 groups.insert(0, cur_gr)
1584 groups.insert(0, cur_gr)
1579 while 1:
1585 while 1:
1580 gr = getattr(cur_gr, 'parent_group', None)
1586 gr = getattr(cur_gr, 'parent_group', None)
1581 cur_gr = cur_gr.parent_group
1587 cur_gr = cur_gr.parent_group
1582 if gr is None:
1588 if gr is None:
1583 break
1589 break
1584 groups.insert(0, gr)
1590 groups.insert(0, gr)
1585
1591
1586 return groups
1592 return groups
1587
1593
1588 @property
1594 @property
1589 def groups_and_repo(self):
1595 def groups_and_repo(self):
1590 return self.groups_with_parents, self
1596 return self.groups_with_parents, self
1591
1597
1592 @LazyProperty
1598 @LazyProperty
1593 def repo_path(self):
1599 def repo_path(self):
1594 """
1600 """
1595 Returns base full path for that repository means where it actually
1601 Returns base full path for that repository means where it actually
1596 exists on a filesystem
1602 exists on a filesystem
1597 """
1603 """
1598 q = Session().query(RhodeCodeUi).filter(
1604 q = Session().query(RhodeCodeUi).filter(
1599 RhodeCodeUi.ui_key == self.NAME_SEP)
1605 RhodeCodeUi.ui_key == self.NAME_SEP)
1600 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1606 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1601 return q.one().ui_value
1607 return q.one().ui_value
1602
1608
1603 @property
1609 @property
1604 def repo_full_path(self):
1610 def repo_full_path(self):
1605 p = [self.repo_path]
1611 p = [self.repo_path]
1606 # we need to split the name by / since this is how we store the
1612 # we need to split the name by / since this is how we store the
1607 # names in the database, but that eventually needs to be converted
1613 # names in the database, but that eventually needs to be converted
1608 # into a valid system path
1614 # into a valid system path
1609 p += self.repo_name.split(self.NAME_SEP)
1615 p += self.repo_name.split(self.NAME_SEP)
1610 return os.path.join(*map(safe_unicode, p))
1616 return os.path.join(*map(safe_unicode, p))
1611
1617
1612 @property
1618 @property
1613 def cache_keys(self):
1619 def cache_keys(self):
1614 """
1620 """
1615 Returns associated cache keys for that repo
1621 Returns associated cache keys for that repo
1616 """
1622 """
1617 return CacheKey.query()\
1623 return CacheKey.query()\
1618 .filter(CacheKey.cache_args == self.repo_name)\
1624 .filter(CacheKey.cache_args == self.repo_name)\
1619 .order_by(CacheKey.cache_key)\
1625 .order_by(CacheKey.cache_key)\
1620 .all()
1626 .all()
1621
1627
1622 def get_new_name(self, repo_name):
1628 def get_new_name(self, repo_name):
1623 """
1629 """
1624 returns new full repository name based on assigned group and new new
1630 returns new full repository name based on assigned group and new new
1625
1631
1626 :param group_name:
1632 :param group_name:
1627 """
1633 """
1628 path_prefix = self.group.full_path_splitted if self.group else []
1634 path_prefix = self.group.full_path_splitted if self.group else []
1629 return self.NAME_SEP.join(path_prefix + [repo_name])
1635 return self.NAME_SEP.join(path_prefix + [repo_name])
1630
1636
1631 @property
1637 @property
1632 def _config(self):
1638 def _config(self):
1633 """
1639 """
1634 Returns db based config object.
1640 Returns db based config object.
1635 """
1641 """
1636 from rhodecode.lib.utils import make_db_config
1642 from rhodecode.lib.utils import make_db_config
1637 return make_db_config(clear_session=False, repo=self)
1643 return make_db_config(clear_session=False, repo=self)
1638
1644
1639 def permissions(self, with_admins=True, with_owner=True):
1645 def permissions(self, with_admins=True, with_owner=True):
1640 q = UserRepoToPerm.query().filter(UserRepoToPerm.repository == self)
1646 q = UserRepoToPerm.query().filter(UserRepoToPerm.repository == self)
1641 q = q.options(joinedload(UserRepoToPerm.repository),
1647 q = q.options(joinedload(UserRepoToPerm.repository),
1642 joinedload(UserRepoToPerm.user),
1648 joinedload(UserRepoToPerm.user),
1643 joinedload(UserRepoToPerm.permission),)
1649 joinedload(UserRepoToPerm.permission),)
1644
1650
1645 # get owners and admins and permissions. We do a trick of re-writing
1651 # get owners and admins and permissions. We do a trick of re-writing
1646 # objects from sqlalchemy to named-tuples due to sqlalchemy session
1652 # objects from sqlalchemy to named-tuples due to sqlalchemy session
1647 # has a global reference and changing one object propagates to all
1653 # has a global reference and changing one object propagates to all
1648 # others. This means if admin is also an owner admin_row that change
1654 # others. This means if admin is also an owner admin_row that change
1649 # would propagate to both objects
1655 # would propagate to both objects
1650 perm_rows = []
1656 perm_rows = []
1651 for _usr in q.all():
1657 for _usr in q.all():
1652 usr = AttributeDict(_usr.user.get_dict())
1658 usr = AttributeDict(_usr.user.get_dict())
1653 usr.permission = _usr.permission.permission_name
1659 usr.permission = _usr.permission.permission_name
1654 perm_rows.append(usr)
1660 perm_rows.append(usr)
1655
1661
1656 # filter the perm rows by 'default' first and then sort them by
1662 # filter the perm rows by 'default' first and then sort them by
1657 # admin,write,read,none permissions sorted again alphabetically in
1663 # admin,write,read,none permissions sorted again alphabetically in
1658 # each group
1664 # each group
1659 perm_rows = sorted(perm_rows, key=display_sort)
1665 perm_rows = sorted(perm_rows, key=display_sort)
1660
1666
1661 _admin_perm = 'repository.admin'
1667 _admin_perm = 'repository.admin'
1662 owner_row = []
1668 owner_row = []
1663 if with_owner:
1669 if with_owner:
1664 usr = AttributeDict(self.user.get_dict())
1670 usr = AttributeDict(self.user.get_dict())
1665 usr.owner_row = True
1671 usr.owner_row = True
1666 usr.permission = _admin_perm
1672 usr.permission = _admin_perm
1667 owner_row.append(usr)
1673 owner_row.append(usr)
1668
1674
1669 super_admin_rows = []
1675 super_admin_rows = []
1670 if with_admins:
1676 if with_admins:
1671 for usr in User.get_all_super_admins():
1677 for usr in User.get_all_super_admins():
1672 # if this admin is also owner, don't double the record
1678 # if this admin is also owner, don't double the record
1673 if usr.user_id == owner_row[0].user_id:
1679 if usr.user_id == owner_row[0].user_id:
1674 owner_row[0].admin_row = True
1680 owner_row[0].admin_row = True
1675 else:
1681 else:
1676 usr = AttributeDict(usr.get_dict())
1682 usr = AttributeDict(usr.get_dict())
1677 usr.admin_row = True
1683 usr.admin_row = True
1678 usr.permission = _admin_perm
1684 usr.permission = _admin_perm
1679 super_admin_rows.append(usr)
1685 super_admin_rows.append(usr)
1680
1686
1681 return super_admin_rows + owner_row + perm_rows
1687 return super_admin_rows + owner_row + perm_rows
1682
1688
1683 def permission_user_groups(self):
1689 def permission_user_groups(self):
1684 q = UserGroupRepoToPerm.query().filter(
1690 q = UserGroupRepoToPerm.query().filter(
1685 UserGroupRepoToPerm.repository == self)
1691 UserGroupRepoToPerm.repository == self)
1686 q = q.options(joinedload(UserGroupRepoToPerm.repository),
1692 q = q.options(joinedload(UserGroupRepoToPerm.repository),
1687 joinedload(UserGroupRepoToPerm.users_group),
1693 joinedload(UserGroupRepoToPerm.users_group),
1688 joinedload(UserGroupRepoToPerm.permission),)
1694 joinedload(UserGroupRepoToPerm.permission),)
1689
1695
1690 perm_rows = []
1696 perm_rows = []
1691 for _user_group in q.all():
1697 for _user_group in q.all():
1692 usr = AttributeDict(_user_group.users_group.get_dict())
1698 usr = AttributeDict(_user_group.users_group.get_dict())
1693 usr.permission = _user_group.permission.permission_name
1699 usr.permission = _user_group.permission.permission_name
1694 perm_rows.append(usr)
1700 perm_rows.append(usr)
1695
1701
1696 return perm_rows
1702 return perm_rows
1697
1703
1698 def get_api_data(self, include_secrets=False):
1704 def get_api_data(self, include_secrets=False):
1699 """
1705 """
1700 Common function for generating repo api data
1706 Common function for generating repo api data
1701
1707
1702 :param include_secrets: See :meth:`User.get_api_data`.
1708 :param include_secrets: See :meth:`User.get_api_data`.
1703
1709
1704 """
1710 """
1705 # TODO: mikhail: Here there is an anti-pattern, we probably need to
1711 # TODO: mikhail: Here there is an anti-pattern, we probably need to
1706 # move this methods on models level.
1712 # move this methods on models level.
1707 from rhodecode.model.settings import SettingsModel
1713 from rhodecode.model.settings import SettingsModel
1708
1714
1709 repo = self
1715 repo = self
1710 _user_id, _time, _reason = self.locked
1716 _user_id, _time, _reason = self.locked
1711
1717
1712 data = {
1718 data = {
1713 'repo_id': repo.repo_id,
1719 'repo_id': repo.repo_id,
1714 'repo_name': repo.repo_name,
1720 'repo_name': repo.repo_name,
1715 'repo_type': repo.repo_type,
1721 'repo_type': repo.repo_type,
1716 'clone_uri': repo.clone_uri or '',
1722 'clone_uri': repo.clone_uri or '',
1717 'url': url('summary_home', repo_name=self.repo_name, qualified=True),
1723 'url': url('summary_home', repo_name=self.repo_name, qualified=True),
1718 'private': repo.private,
1724 'private': repo.private,
1719 'created_on': repo.created_on,
1725 'created_on': repo.created_on,
1720 'description': repo.description,
1726 'description': repo.description,
1721 'landing_rev': repo.landing_rev,
1727 'landing_rev': repo.landing_rev,
1722 'owner': repo.user.username,
1728 'owner': repo.user.username,
1723 'fork_of': repo.fork.repo_name if repo.fork else None,
1729 'fork_of': repo.fork.repo_name if repo.fork else None,
1724 'enable_statistics': repo.enable_statistics,
1730 'enable_statistics': repo.enable_statistics,
1725 'enable_locking': repo.enable_locking,
1731 'enable_locking': repo.enable_locking,
1726 'enable_downloads': repo.enable_downloads,
1732 'enable_downloads': repo.enable_downloads,
1727 'last_changeset': repo.changeset_cache,
1733 'last_changeset': repo.changeset_cache,
1728 'locked_by': User.get(_user_id).get_api_data(
1734 'locked_by': User.get(_user_id).get_api_data(
1729 include_secrets=include_secrets) if _user_id else None,
1735 include_secrets=include_secrets) if _user_id else None,
1730 'locked_date': time_to_datetime(_time) if _time else None,
1736 'locked_date': time_to_datetime(_time) if _time else None,
1731 'lock_reason': _reason if _reason else None,
1737 'lock_reason': _reason if _reason else None,
1732 }
1738 }
1733
1739
1734 # TODO: mikhail: should be per-repo settings here
1740 # TODO: mikhail: should be per-repo settings here
1735 rc_config = SettingsModel().get_all_settings()
1741 rc_config = SettingsModel().get_all_settings()
1736 repository_fields = str2bool(
1742 repository_fields = str2bool(
1737 rc_config.get('rhodecode_repository_fields'))
1743 rc_config.get('rhodecode_repository_fields'))
1738 if repository_fields:
1744 if repository_fields:
1739 for f in self.extra_fields:
1745 for f in self.extra_fields:
1740 data[f.field_key_prefixed] = f.field_value
1746 data[f.field_key_prefixed] = f.field_value
1741
1747
1742 return data
1748 return data
1743
1749
1744 @classmethod
1750 @classmethod
1745 def lock(cls, repo, user_id, lock_time=None, lock_reason=None):
1751 def lock(cls, repo, user_id, lock_time=None, lock_reason=None):
1746 if not lock_time:
1752 if not lock_time:
1747 lock_time = time.time()
1753 lock_time = time.time()
1748 if not lock_reason:
1754 if not lock_reason:
1749 lock_reason = cls.LOCK_AUTOMATIC
1755 lock_reason = cls.LOCK_AUTOMATIC
1750 repo.locked = [user_id, lock_time, lock_reason]
1756 repo.locked = [user_id, lock_time, lock_reason]
1751 Session().add(repo)
1757 Session().add(repo)
1752 Session().commit()
1758 Session().commit()
1753
1759
1754 @classmethod
1760 @classmethod
1755 def unlock(cls, repo):
1761 def unlock(cls, repo):
1756 repo.locked = None
1762 repo.locked = None
1757 Session().add(repo)
1763 Session().add(repo)
1758 Session().commit()
1764 Session().commit()
1759
1765
1760 @classmethod
1766 @classmethod
1761 def getlock(cls, repo):
1767 def getlock(cls, repo):
1762 return repo.locked
1768 return repo.locked
1763
1769
1764 def is_user_lock(self, user_id):
1770 def is_user_lock(self, user_id):
1765 if self.lock[0]:
1771 if self.lock[0]:
1766 lock_user_id = safe_int(self.lock[0])
1772 lock_user_id = safe_int(self.lock[0])
1767 user_id = safe_int(user_id)
1773 user_id = safe_int(user_id)
1768 # both are ints, and they are equal
1774 # both are ints, and they are equal
1769 return all([lock_user_id, user_id]) and lock_user_id == user_id
1775 return all([lock_user_id, user_id]) and lock_user_id == user_id
1770
1776
1771 return False
1777 return False
1772
1778
1773 def get_locking_state(self, action, user_id, only_when_enabled=True):
1779 def get_locking_state(self, action, user_id, only_when_enabled=True):
1774 """
1780 """
1775 Checks locking on this repository, if locking is enabled and lock is
1781 Checks locking on this repository, if locking is enabled and lock is
1776 present returns a tuple of make_lock, locked, locked_by.
1782 present returns a tuple of make_lock, locked, locked_by.
1777 make_lock can have 3 states None (do nothing) True, make lock
1783 make_lock can have 3 states None (do nothing) True, make lock
1778 False release lock, This value is later propagated to hooks, which
1784 False release lock, This value is later propagated to hooks, which
1779 do the locking. Think about this as signals passed to hooks what to do.
1785 do the locking. Think about this as signals passed to hooks what to do.
1780
1786
1781 """
1787 """
1782 # TODO: johbo: This is part of the business logic and should be moved
1788 # TODO: johbo: This is part of the business logic and should be moved
1783 # into the RepositoryModel.
1789 # into the RepositoryModel.
1784
1790
1785 if action not in ('push', 'pull'):
1791 if action not in ('push', 'pull'):
1786 raise ValueError("Invalid action value: %s" % repr(action))
1792 raise ValueError("Invalid action value: %s" % repr(action))
1787
1793
1788 # defines if locked error should be thrown to user
1794 # defines if locked error should be thrown to user
1789 currently_locked = False
1795 currently_locked = False
1790 # defines if new lock should be made, tri-state
1796 # defines if new lock should be made, tri-state
1791 make_lock = None
1797 make_lock = None
1792 repo = self
1798 repo = self
1793 user = User.get(user_id)
1799 user = User.get(user_id)
1794
1800
1795 lock_info = repo.locked
1801 lock_info = repo.locked
1796
1802
1797 if repo and (repo.enable_locking or not only_when_enabled):
1803 if repo and (repo.enable_locking or not only_when_enabled):
1798 if action == 'push':
1804 if action == 'push':
1799 # check if it's already locked !, if it is compare users
1805 # check if it's already locked !, if it is compare users
1800 locked_by_user_id = lock_info[0]
1806 locked_by_user_id = lock_info[0]
1801 if user.user_id == locked_by_user_id:
1807 if user.user_id == locked_by_user_id:
1802 log.debug(
1808 log.debug(
1803 'Got `push` action from user %s, now unlocking', user)
1809 'Got `push` action from user %s, now unlocking', user)
1804 # unlock if we have push from user who locked
1810 # unlock if we have push from user who locked
1805 make_lock = False
1811 make_lock = False
1806 else:
1812 else:
1807 # we're not the same user who locked, ban with
1813 # we're not the same user who locked, ban with
1808 # code defined in settings (default is 423 HTTP Locked) !
1814 # code defined in settings (default is 423 HTTP Locked) !
1809 log.debug('Repo %s is currently locked by %s', repo, user)
1815 log.debug('Repo %s is currently locked by %s', repo, user)
1810 currently_locked = True
1816 currently_locked = True
1811 elif action == 'pull':
1817 elif action == 'pull':
1812 # [0] user [1] date
1818 # [0] user [1] date
1813 if lock_info[0] and lock_info[1]:
1819 if lock_info[0] and lock_info[1]:
1814 log.debug('Repo %s is currently locked by %s', repo, user)
1820 log.debug('Repo %s is currently locked by %s', repo, user)
1815 currently_locked = True
1821 currently_locked = True
1816 else:
1822 else:
1817 log.debug('Setting lock on repo %s by %s', repo, user)
1823 log.debug('Setting lock on repo %s by %s', repo, user)
1818 make_lock = True
1824 make_lock = True
1819
1825
1820 else:
1826 else:
1821 log.debug('Repository %s do not have locking enabled', repo)
1827 log.debug('Repository %s do not have locking enabled', repo)
1822
1828
1823 log.debug('FINAL locking values make_lock:%s,locked:%s,locked_by:%s',
1829 log.debug('FINAL locking values make_lock:%s,locked:%s,locked_by:%s',
1824 make_lock, currently_locked, lock_info)
1830 make_lock, currently_locked, lock_info)
1825
1831
1826 from rhodecode.lib.auth import HasRepoPermissionAny
1832 from rhodecode.lib.auth import HasRepoPermissionAny
1827 perm_check = HasRepoPermissionAny('repository.write', 'repository.admin')
1833 perm_check = HasRepoPermissionAny('repository.write', 'repository.admin')
1828 if make_lock and not perm_check(repo_name=repo.repo_name, user=user):
1834 if make_lock and not perm_check(repo_name=repo.repo_name, user=user):
1829 # if we don't have at least write permission we cannot make a lock
1835 # if we don't have at least write permission we cannot make a lock
1830 log.debug('lock state reset back to FALSE due to lack '
1836 log.debug('lock state reset back to FALSE due to lack '
1831 'of at least read permission')
1837 'of at least read permission')
1832 make_lock = False
1838 make_lock = False
1833
1839
1834 return make_lock, currently_locked, lock_info
1840 return make_lock, currently_locked, lock_info
1835
1841
1836 @property
1842 @property
1837 def last_db_change(self):
1843 def last_db_change(self):
1838 return self.updated_on
1844 return self.updated_on
1839
1845
1840 @property
1846 @property
1841 def clone_uri_hidden(self):
1847 def clone_uri_hidden(self):
1842 clone_uri = self.clone_uri
1848 clone_uri = self.clone_uri
1843 if clone_uri:
1849 if clone_uri:
1844 import urlobject
1850 import urlobject
1845 url_obj = urlobject.URLObject(cleaned_uri(clone_uri))
1851 url_obj = urlobject.URLObject(cleaned_uri(clone_uri))
1846 if url_obj.password:
1852 if url_obj.password:
1847 clone_uri = url_obj.with_password('*****')
1853 clone_uri = url_obj.with_password('*****')
1848 return clone_uri
1854 return clone_uri
1849
1855
1850 def clone_url(self, **override):
1856 def clone_url(self, **override):
1851 qualified_home_url = url('home', qualified=True)
1857 qualified_home_url = url('home', qualified=True)
1852
1858
1853 uri_tmpl = None
1859 uri_tmpl = None
1854 if 'with_id' in override:
1860 if 'with_id' in override:
1855 uri_tmpl = self.DEFAULT_CLONE_URI_ID
1861 uri_tmpl = self.DEFAULT_CLONE_URI_ID
1856 del override['with_id']
1862 del override['with_id']
1857
1863
1858 if 'uri_tmpl' in override:
1864 if 'uri_tmpl' in override:
1859 uri_tmpl = override['uri_tmpl']
1865 uri_tmpl = override['uri_tmpl']
1860 del override['uri_tmpl']
1866 del override['uri_tmpl']
1861
1867
1862 # we didn't override our tmpl from **overrides
1868 # we didn't override our tmpl from **overrides
1863 if not uri_tmpl:
1869 if not uri_tmpl:
1864 uri_tmpl = self.DEFAULT_CLONE_URI
1870 uri_tmpl = self.DEFAULT_CLONE_URI
1865 try:
1871 try:
1866 from pylons import tmpl_context as c
1872 from pylons import tmpl_context as c
1867 uri_tmpl = c.clone_uri_tmpl
1873 uri_tmpl = c.clone_uri_tmpl
1868 except Exception:
1874 except Exception:
1869 # in any case if we call this outside of request context,
1875 # in any case if we call this outside of request context,
1870 # ie, not having tmpl_context set up
1876 # ie, not having tmpl_context set up
1871 pass
1877 pass
1872
1878
1873 return get_clone_url(uri_tmpl=uri_tmpl,
1879 return get_clone_url(uri_tmpl=uri_tmpl,
1874 qualifed_home_url=qualified_home_url,
1880 qualifed_home_url=qualified_home_url,
1875 repo_name=self.repo_name,
1881 repo_name=self.repo_name,
1876 repo_id=self.repo_id, **override)
1882 repo_id=self.repo_id, **override)
1877
1883
1878 def set_state(self, state):
1884 def set_state(self, state):
1879 self.repo_state = state
1885 self.repo_state = state
1880 Session().add(self)
1886 Session().add(self)
1881 #==========================================================================
1887 #==========================================================================
1882 # SCM PROPERTIES
1888 # SCM PROPERTIES
1883 #==========================================================================
1889 #==========================================================================
1884
1890
1885 def get_commit(self, commit_id=None, commit_idx=None, pre_load=None):
1891 def get_commit(self, commit_id=None, commit_idx=None, pre_load=None):
1886 return get_commit_safe(
1892 return get_commit_safe(
1887 self.scm_instance(), commit_id, commit_idx, pre_load=pre_load)
1893 self.scm_instance(), commit_id, commit_idx, pre_load=pre_load)
1888
1894
1889 def get_changeset(self, rev=None, pre_load=None):
1895 def get_changeset(self, rev=None, pre_load=None):
1890 warnings.warn("Use get_commit", DeprecationWarning)
1896 warnings.warn("Use get_commit", DeprecationWarning)
1891 commit_id = None
1897 commit_id = None
1892 commit_idx = None
1898 commit_idx = None
1893 if isinstance(rev, basestring):
1899 if isinstance(rev, basestring):
1894 commit_id = rev
1900 commit_id = rev
1895 else:
1901 else:
1896 commit_idx = rev
1902 commit_idx = rev
1897 return self.get_commit(commit_id=commit_id, commit_idx=commit_idx,
1903 return self.get_commit(commit_id=commit_id, commit_idx=commit_idx,
1898 pre_load=pre_load)
1904 pre_load=pre_load)
1899
1905
1900 def get_landing_commit(self):
1906 def get_landing_commit(self):
1901 """
1907 """
1902 Returns landing commit, or if that doesn't exist returns the tip
1908 Returns landing commit, or if that doesn't exist returns the tip
1903 """
1909 """
1904 _rev_type, _rev = self.landing_rev
1910 _rev_type, _rev = self.landing_rev
1905 commit = self.get_commit(_rev)
1911 commit = self.get_commit(_rev)
1906 if isinstance(commit, EmptyCommit):
1912 if isinstance(commit, EmptyCommit):
1907 return self.get_commit()
1913 return self.get_commit()
1908 return commit
1914 return commit
1909
1915
1910 def update_commit_cache(self, cs_cache=None, config=None):
1916 def update_commit_cache(self, cs_cache=None, config=None):
1911 """
1917 """
1912 Update cache of last changeset for repository, keys should be::
1918 Update cache of last changeset for repository, keys should be::
1913
1919
1914 short_id
1920 short_id
1915 raw_id
1921 raw_id
1916 revision
1922 revision
1917 parents
1923 parents
1918 message
1924 message
1919 date
1925 date
1920 author
1926 author
1921
1927
1922 :param cs_cache:
1928 :param cs_cache:
1923 """
1929 """
1924 from rhodecode.lib.vcs.backends.base import BaseChangeset
1930 from rhodecode.lib.vcs.backends.base import BaseChangeset
1925 if cs_cache is None:
1931 if cs_cache is None:
1926 # use no-cache version here
1932 # use no-cache version here
1927 scm_repo = self.scm_instance(cache=False, config=config)
1933 scm_repo = self.scm_instance(cache=False, config=config)
1928 if scm_repo:
1934 if scm_repo:
1929 cs_cache = scm_repo.get_commit(
1935 cs_cache = scm_repo.get_commit(
1930 pre_load=["author", "date", "message", "parents"])
1936 pre_load=["author", "date", "message", "parents"])
1931 else:
1937 else:
1932 cs_cache = EmptyCommit()
1938 cs_cache = EmptyCommit()
1933
1939
1934 if isinstance(cs_cache, BaseChangeset):
1940 if isinstance(cs_cache, BaseChangeset):
1935 cs_cache = cs_cache.__json__()
1941 cs_cache = cs_cache.__json__()
1936
1942
1937 def is_outdated(new_cs_cache):
1943 def is_outdated(new_cs_cache):
1938 if (new_cs_cache['raw_id'] != self.changeset_cache['raw_id'] or
1944 if (new_cs_cache['raw_id'] != self.changeset_cache['raw_id'] or
1939 new_cs_cache['revision'] != self.changeset_cache['revision']):
1945 new_cs_cache['revision'] != self.changeset_cache['revision']):
1940 return True
1946 return True
1941 return False
1947 return False
1942
1948
1943 # check if we have maybe already latest cached revision
1949 # check if we have maybe already latest cached revision
1944 if is_outdated(cs_cache) or not self.changeset_cache:
1950 if is_outdated(cs_cache) or not self.changeset_cache:
1945 _default = datetime.datetime.fromtimestamp(0)
1951 _default = datetime.datetime.fromtimestamp(0)
1946 last_change = cs_cache.get('date') or _default
1952 last_change = cs_cache.get('date') or _default
1947 log.debug('updated repo %s with new cs cache %s',
1953 log.debug('updated repo %s with new cs cache %s',
1948 self.repo_name, cs_cache)
1954 self.repo_name, cs_cache)
1949 self.updated_on = last_change
1955 self.updated_on = last_change
1950 self.changeset_cache = cs_cache
1956 self.changeset_cache = cs_cache
1951 Session().add(self)
1957 Session().add(self)
1952 Session().commit()
1958 Session().commit()
1953 else:
1959 else:
1954 log.debug('Skipping update_commit_cache for repo:`%s` '
1960 log.debug('Skipping update_commit_cache for repo:`%s` '
1955 'commit already with latest changes', self.repo_name)
1961 'commit already with latest changes', self.repo_name)
1956
1962
1957 @property
1963 @property
1958 def tip(self):
1964 def tip(self):
1959 return self.get_commit('tip')
1965 return self.get_commit('tip')
1960
1966
1961 @property
1967 @property
1962 def author(self):
1968 def author(self):
1963 return self.tip.author
1969 return self.tip.author
1964
1970
1965 @property
1971 @property
1966 def last_change(self):
1972 def last_change(self):
1967 return self.scm_instance().last_change
1973 return self.scm_instance().last_change
1968
1974
1969 def get_comments(self, revisions=None):
1975 def get_comments(self, revisions=None):
1970 """
1976 """
1971 Returns comments for this repository grouped by revisions
1977 Returns comments for this repository grouped by revisions
1972
1978
1973 :param revisions: filter query by revisions only
1979 :param revisions: filter query by revisions only
1974 """
1980 """
1975 cmts = ChangesetComment.query()\
1981 cmts = ChangesetComment.query()\
1976 .filter(ChangesetComment.repo == self)
1982 .filter(ChangesetComment.repo == self)
1977 if revisions:
1983 if revisions:
1978 cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
1984 cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
1979 grouped = collections.defaultdict(list)
1985 grouped = collections.defaultdict(list)
1980 for cmt in cmts.all():
1986 for cmt in cmts.all():
1981 grouped[cmt.revision].append(cmt)
1987 grouped[cmt.revision].append(cmt)
1982 return grouped
1988 return grouped
1983
1989
1984 def statuses(self, revisions=None):
1990 def statuses(self, revisions=None):
1985 """
1991 """
1986 Returns statuses for this repository
1992 Returns statuses for this repository
1987
1993
1988 :param revisions: list of revisions to get statuses for
1994 :param revisions: list of revisions to get statuses for
1989 """
1995 """
1990 statuses = ChangesetStatus.query()\
1996 statuses = ChangesetStatus.query()\
1991 .filter(ChangesetStatus.repo == self)\
1997 .filter(ChangesetStatus.repo == self)\
1992 .filter(ChangesetStatus.version == 0)
1998 .filter(ChangesetStatus.version == 0)
1993
1999
1994 if revisions:
2000 if revisions:
1995 # Try doing the filtering in chunks to avoid hitting limits
2001 # Try doing the filtering in chunks to avoid hitting limits
1996 size = 500
2002 size = 500
1997 status_results = []
2003 status_results = []
1998 for chunk in xrange(0, len(revisions), size):
2004 for chunk in xrange(0, len(revisions), size):
1999 status_results += statuses.filter(
2005 status_results += statuses.filter(
2000 ChangesetStatus.revision.in_(
2006 ChangesetStatus.revision.in_(
2001 revisions[chunk: chunk+size])
2007 revisions[chunk: chunk+size])
2002 ).all()
2008 ).all()
2003 else:
2009 else:
2004 status_results = statuses.all()
2010 status_results = statuses.all()
2005
2011
2006 grouped = {}
2012 grouped = {}
2007
2013
2008 # maybe we have open new pullrequest without a status?
2014 # maybe we have open new pullrequest without a status?
2009 stat = ChangesetStatus.STATUS_UNDER_REVIEW
2015 stat = ChangesetStatus.STATUS_UNDER_REVIEW
2010 status_lbl = ChangesetStatus.get_status_lbl(stat)
2016 status_lbl = ChangesetStatus.get_status_lbl(stat)
2011 for pr in PullRequest.query().filter(PullRequest.source_repo == self).all():
2017 for pr in PullRequest.query().filter(PullRequest.source_repo == self).all():
2012 for rev in pr.revisions:
2018 for rev in pr.revisions:
2013 pr_id = pr.pull_request_id
2019 pr_id = pr.pull_request_id
2014 pr_repo = pr.target_repo.repo_name
2020 pr_repo = pr.target_repo.repo_name
2015 grouped[rev] = [stat, status_lbl, pr_id, pr_repo]
2021 grouped[rev] = [stat, status_lbl, pr_id, pr_repo]
2016
2022
2017 for stat in status_results:
2023 for stat in status_results:
2018 pr_id = pr_repo = None
2024 pr_id = pr_repo = None
2019 if stat.pull_request:
2025 if stat.pull_request:
2020 pr_id = stat.pull_request.pull_request_id
2026 pr_id = stat.pull_request.pull_request_id
2021 pr_repo = stat.pull_request.target_repo.repo_name
2027 pr_repo = stat.pull_request.target_repo.repo_name
2022 grouped[stat.revision] = [str(stat.status), stat.status_lbl,
2028 grouped[stat.revision] = [str(stat.status), stat.status_lbl,
2023 pr_id, pr_repo]
2029 pr_id, pr_repo]
2024 return grouped
2030 return grouped
2025
2031
2026 # ==========================================================================
2032 # ==========================================================================
2027 # SCM CACHE INSTANCE
2033 # SCM CACHE INSTANCE
2028 # ==========================================================================
2034 # ==========================================================================
2029
2035
2030 def scm_instance(self, **kwargs):
2036 def scm_instance(self, **kwargs):
2031 import rhodecode
2037 import rhodecode
2032
2038
2033 # Passing a config will not hit the cache currently only used
2039 # Passing a config will not hit the cache currently only used
2034 # for repo2dbmapper
2040 # for repo2dbmapper
2035 config = kwargs.pop('config', None)
2041 config = kwargs.pop('config', None)
2036 cache = kwargs.pop('cache', None)
2042 cache = kwargs.pop('cache', None)
2037 full_cache = str2bool(rhodecode.CONFIG.get('vcs_full_cache'))
2043 full_cache = str2bool(rhodecode.CONFIG.get('vcs_full_cache'))
2038 # if cache is NOT defined use default global, else we have a full
2044 # if cache is NOT defined use default global, else we have a full
2039 # control over cache behaviour
2045 # control over cache behaviour
2040 if cache is None and full_cache and not config:
2046 if cache is None and full_cache and not config:
2041 return self._get_instance_cached()
2047 return self._get_instance_cached()
2042 return self._get_instance(cache=bool(cache), config=config)
2048 return self._get_instance(cache=bool(cache), config=config)
2043
2049
2044 def _get_instance_cached(self):
2050 def _get_instance_cached(self):
2045 @cache_region('long_term')
2051 @cache_region('long_term')
2046 def _get_repo(cache_key):
2052 def _get_repo(cache_key):
2047 return self._get_instance()
2053 return self._get_instance()
2048
2054
2049 invalidator_context = CacheKey.repo_context_cache(
2055 invalidator_context = CacheKey.repo_context_cache(
2050 _get_repo, self.repo_name, None, thread_scoped=True)
2056 _get_repo, self.repo_name, None, thread_scoped=True)
2051
2057
2052 with invalidator_context as context:
2058 with invalidator_context as context:
2053 context.invalidate()
2059 context.invalidate()
2054 repo = context.compute()
2060 repo = context.compute()
2055
2061
2056 return repo
2062 return repo
2057
2063
2058 def _get_instance(self, cache=True, config=None):
2064 def _get_instance(self, cache=True, config=None):
2059 config = config or self._config
2065 config = config or self._config
2060 custom_wire = {
2066 custom_wire = {
2061 'cache': cache # controls the vcs.remote cache
2067 'cache': cache # controls the vcs.remote cache
2062 }
2068 }
2063 repo = get_vcs_instance(
2069 repo = get_vcs_instance(
2064 repo_path=safe_str(self.repo_full_path),
2070 repo_path=safe_str(self.repo_full_path),
2065 config=config,
2071 config=config,
2066 with_wire=custom_wire,
2072 with_wire=custom_wire,
2067 create=False,
2073 create=False,
2068 _vcs_alias=self.repo_type)
2074 _vcs_alias=self.repo_type)
2069
2075
2070 return repo
2076 return repo
2071
2077
2072 def __json__(self):
2078 def __json__(self):
2073 return {'landing_rev': self.landing_rev}
2079 return {'landing_rev': self.landing_rev}
2074
2080
2075 def get_dict(self):
2081 def get_dict(self):
2076
2082
2077 # Since we transformed `repo_name` to a hybrid property, we need to
2083 # Since we transformed `repo_name` to a hybrid property, we need to
2078 # keep compatibility with the code which uses `repo_name` field.
2084 # keep compatibility with the code which uses `repo_name` field.
2079
2085
2080 result = super(Repository, self).get_dict()
2086 result = super(Repository, self).get_dict()
2081 result['repo_name'] = result.pop('_repo_name', None)
2087 result['repo_name'] = result.pop('_repo_name', None)
2082 return result
2088 return result
2083
2089
2084
2090
2085 class RepoGroup(Base, BaseModel):
2091 class RepoGroup(Base, BaseModel):
2086 __tablename__ = 'groups'
2092 __tablename__ = 'groups'
2087 __table_args__ = (
2093 __table_args__ = (
2088 UniqueConstraint('group_name', 'group_parent_id'),
2094 UniqueConstraint('group_name', 'group_parent_id'),
2089 CheckConstraint('group_id != group_parent_id'),
2095 CheckConstraint('group_id != group_parent_id'),
2090 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2096 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2091 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2097 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2092 )
2098 )
2093 __mapper_args__ = {'order_by': 'group_name'}
2099 __mapper_args__ = {'order_by': 'group_name'}
2094
2100
2095 CHOICES_SEPARATOR = '/' # used to generate select2 choices for nested groups
2101 CHOICES_SEPARATOR = '/' # used to generate select2 choices for nested groups
2096
2102
2097 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2103 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2098 group_name = Column("group_name", String(255), nullable=False, unique=True, default=None)
2104 group_name = Column("group_name", String(255), nullable=False, unique=True, default=None)
2099 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
2105 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
2100 group_description = Column("group_description", String(10000), nullable=True, unique=None, default=None)
2106 group_description = Column("group_description", String(10000), nullable=True, unique=None, default=None)
2101 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
2107 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
2102 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
2108 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
2103 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2109 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2104 personal = Column('personal', Boolean(), nullable=True, unique=None, default=None)
2110 personal = Column('personal', Boolean(), nullable=True, unique=None, default=None)
2105
2111
2106 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
2112 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
2107 users_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
2113 users_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
2108 parent_group = relationship('RepoGroup', remote_side=group_id)
2114 parent_group = relationship('RepoGroup', remote_side=group_id)
2109 user = relationship('User')
2115 user = relationship('User')
2110 integrations = relationship('Integration',
2116 integrations = relationship('Integration',
2111 cascade="all, delete, delete-orphan")
2117 cascade="all, delete, delete-orphan")
2112
2118
2113 def __init__(self, group_name='', parent_group=None):
2119 def __init__(self, group_name='', parent_group=None):
2114 self.group_name = group_name
2120 self.group_name = group_name
2115 self.parent_group = parent_group
2121 self.parent_group = parent_group
2116
2122
2117 def __unicode__(self):
2123 def __unicode__(self):
2118 return u"<%s('id:%s:%s')>" % (self.__class__.__name__, self.group_id,
2124 return u"<%s('id:%s:%s')>" % (self.__class__.__name__, self.group_id,
2119 self.group_name)
2125 self.group_name)
2120
2126
2121 @classmethod
2127 @classmethod
2122 def _generate_choice(cls, repo_group):
2128 def _generate_choice(cls, repo_group):
2123 from webhelpers.html import literal as _literal
2129 from webhelpers.html import literal as _literal
2124 _name = lambda k: _literal(cls.CHOICES_SEPARATOR.join(k))
2130 _name = lambda k: _literal(cls.CHOICES_SEPARATOR.join(k))
2125 return repo_group.group_id, _name(repo_group.full_path_splitted)
2131 return repo_group.group_id, _name(repo_group.full_path_splitted)
2126
2132
2127 @classmethod
2133 @classmethod
2128 def groups_choices(cls, groups=None, show_empty_group=True):
2134 def groups_choices(cls, groups=None, show_empty_group=True):
2129 if not groups:
2135 if not groups:
2130 groups = cls.query().all()
2136 groups = cls.query().all()
2131
2137
2132 repo_groups = []
2138 repo_groups = []
2133 if show_empty_group:
2139 if show_empty_group:
2134 repo_groups = [('-1', u'-- %s --' % _('No parent'))]
2140 repo_groups = [('-1', u'-- %s --' % _('No parent'))]
2135
2141
2136 repo_groups.extend([cls._generate_choice(x) for x in groups])
2142 repo_groups.extend([cls._generate_choice(x) for x in groups])
2137
2143
2138 repo_groups = sorted(
2144 repo_groups = sorted(
2139 repo_groups, key=lambda t: t[1].split(cls.CHOICES_SEPARATOR)[0])
2145 repo_groups, key=lambda t: t[1].split(cls.CHOICES_SEPARATOR)[0])
2140 return repo_groups
2146 return repo_groups
2141
2147
2142 @classmethod
2148 @classmethod
2143 def url_sep(cls):
2149 def url_sep(cls):
2144 return URL_SEP
2150 return URL_SEP
2145
2151
2146 @classmethod
2152 @classmethod
2147 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
2153 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
2148 if case_insensitive:
2154 if case_insensitive:
2149 gr = cls.query().filter(func.lower(cls.group_name)
2155 gr = cls.query().filter(func.lower(cls.group_name)
2150 == func.lower(group_name))
2156 == func.lower(group_name))
2151 else:
2157 else:
2152 gr = cls.query().filter(cls.group_name == group_name)
2158 gr = cls.query().filter(cls.group_name == group_name)
2153 if cache:
2159 if cache:
2154 gr = gr.options(FromCache(
2160 gr = gr.options(FromCache(
2155 "sql_cache_short",
2161 "sql_cache_short",
2156 "get_group_%s" % _hash_key(group_name)))
2162 "get_group_%s" % _hash_key(group_name)))
2157 return gr.scalar()
2163 return gr.scalar()
2158
2164
2159 @classmethod
2165 @classmethod
2160 def get_user_personal_repo_group(cls, user_id):
2166 def get_user_personal_repo_group(cls, user_id):
2161 user = User.get(user_id)
2167 user = User.get(user_id)
2162 return cls.query()\
2168 return cls.query()\
2163 .filter(cls.personal == true())\
2169 .filter(cls.personal == true())\
2164 .filter(cls.user == user).scalar()
2170 .filter(cls.user == user).scalar()
2165
2171
2166 @classmethod
2172 @classmethod
2167 def get_all_repo_groups(cls, user_id=Optional(None), group_id=Optional(None),
2173 def get_all_repo_groups(cls, user_id=Optional(None), group_id=Optional(None),
2168 case_insensitive=True):
2174 case_insensitive=True):
2169 q = RepoGroup.query()
2175 q = RepoGroup.query()
2170
2176
2171 if not isinstance(user_id, Optional):
2177 if not isinstance(user_id, Optional):
2172 q = q.filter(RepoGroup.user_id == user_id)
2178 q = q.filter(RepoGroup.user_id == user_id)
2173
2179
2174 if not isinstance(group_id, Optional):
2180 if not isinstance(group_id, Optional):
2175 q = q.filter(RepoGroup.group_parent_id == group_id)
2181 q = q.filter(RepoGroup.group_parent_id == group_id)
2176
2182
2177 if case_insensitive:
2183 if case_insensitive:
2178 q = q.order_by(func.lower(RepoGroup.group_name))
2184 q = q.order_by(func.lower(RepoGroup.group_name))
2179 else:
2185 else:
2180 q = q.order_by(RepoGroup.group_name)
2186 q = q.order_by(RepoGroup.group_name)
2181 return q.all()
2187 return q.all()
2182
2188
2183 @property
2189 @property
2184 def parents(self):
2190 def parents(self):
2185 parents_recursion_limit = 10
2191 parents_recursion_limit = 10
2186 groups = []
2192 groups = []
2187 if self.parent_group is None:
2193 if self.parent_group is None:
2188 return groups
2194 return groups
2189 cur_gr = self.parent_group
2195 cur_gr = self.parent_group
2190 groups.insert(0, cur_gr)
2196 groups.insert(0, cur_gr)
2191 cnt = 0
2197 cnt = 0
2192 while 1:
2198 while 1:
2193 cnt += 1
2199 cnt += 1
2194 gr = getattr(cur_gr, 'parent_group', None)
2200 gr = getattr(cur_gr, 'parent_group', None)
2195 cur_gr = cur_gr.parent_group
2201 cur_gr = cur_gr.parent_group
2196 if gr is None:
2202 if gr is None:
2197 break
2203 break
2198 if cnt == parents_recursion_limit:
2204 if cnt == parents_recursion_limit:
2199 # this will prevent accidental infinit loops
2205 # this will prevent accidental infinit loops
2200 log.error(('more than %s parents found for group %s, stopping '
2206 log.error(('more than %s parents found for group %s, stopping '
2201 'recursive parent fetching' % (parents_recursion_limit, self)))
2207 'recursive parent fetching' % (parents_recursion_limit, self)))
2202 break
2208 break
2203
2209
2204 groups.insert(0, gr)
2210 groups.insert(0, gr)
2205 return groups
2211 return groups
2206
2212
2207 @property
2213 @property
2208 def children(self):
2214 def children(self):
2209 return RepoGroup.query().filter(RepoGroup.parent_group == self)
2215 return RepoGroup.query().filter(RepoGroup.parent_group == self)
2210
2216
2211 @property
2217 @property
2212 def name(self):
2218 def name(self):
2213 return self.group_name.split(RepoGroup.url_sep())[-1]
2219 return self.group_name.split(RepoGroup.url_sep())[-1]
2214
2220
2215 @property
2221 @property
2216 def full_path(self):
2222 def full_path(self):
2217 return self.group_name
2223 return self.group_name
2218
2224
2219 @property
2225 @property
2220 def full_path_splitted(self):
2226 def full_path_splitted(self):
2221 return self.group_name.split(RepoGroup.url_sep())
2227 return self.group_name.split(RepoGroup.url_sep())
2222
2228
2223 @property
2229 @property
2224 def repositories(self):
2230 def repositories(self):
2225 return Repository.query()\
2231 return Repository.query()\
2226 .filter(Repository.group == self)\
2232 .filter(Repository.group == self)\
2227 .order_by(Repository.repo_name)
2233 .order_by(Repository.repo_name)
2228
2234
2229 @property
2235 @property
2230 def repositories_recursive_count(self):
2236 def repositories_recursive_count(self):
2231 cnt = self.repositories.count()
2237 cnt = self.repositories.count()
2232
2238
2233 def children_count(group):
2239 def children_count(group):
2234 cnt = 0
2240 cnt = 0
2235 for child in group.children:
2241 for child in group.children:
2236 cnt += child.repositories.count()
2242 cnt += child.repositories.count()
2237 cnt += children_count(child)
2243 cnt += children_count(child)
2238 return cnt
2244 return cnt
2239
2245
2240 return cnt + children_count(self)
2246 return cnt + children_count(self)
2241
2247
2242 def _recursive_objects(self, include_repos=True):
2248 def _recursive_objects(self, include_repos=True):
2243 all_ = []
2249 all_ = []
2244
2250
2245 def _get_members(root_gr):
2251 def _get_members(root_gr):
2246 if include_repos:
2252 if include_repos:
2247 for r in root_gr.repositories:
2253 for r in root_gr.repositories:
2248 all_.append(r)
2254 all_.append(r)
2249 childs = root_gr.children.all()
2255 childs = root_gr.children.all()
2250 if childs:
2256 if childs:
2251 for gr in childs:
2257 for gr in childs:
2252 all_.append(gr)
2258 all_.append(gr)
2253 _get_members(gr)
2259 _get_members(gr)
2254
2260
2255 _get_members(self)
2261 _get_members(self)
2256 return [self] + all_
2262 return [self] + all_
2257
2263
2258 def recursive_groups_and_repos(self):
2264 def recursive_groups_and_repos(self):
2259 """
2265 """
2260 Recursive return all groups, with repositories in those groups
2266 Recursive return all groups, with repositories in those groups
2261 """
2267 """
2262 return self._recursive_objects()
2268 return self._recursive_objects()
2263
2269
2264 def recursive_groups(self):
2270 def recursive_groups(self):
2265 """
2271 """
2266 Returns all children groups for this group including children of children
2272 Returns all children groups for this group including children of children
2267 """
2273 """
2268 return self._recursive_objects(include_repos=False)
2274 return self._recursive_objects(include_repos=False)
2269
2275
2270 def get_new_name(self, group_name):
2276 def get_new_name(self, group_name):
2271 """
2277 """
2272 returns new full group name based on parent and new name
2278 returns new full group name based on parent and new name
2273
2279
2274 :param group_name:
2280 :param group_name:
2275 """
2281 """
2276 path_prefix = (self.parent_group.full_path_splitted if
2282 path_prefix = (self.parent_group.full_path_splitted if
2277 self.parent_group else [])
2283 self.parent_group else [])
2278 return RepoGroup.url_sep().join(path_prefix + [group_name])
2284 return RepoGroup.url_sep().join(path_prefix + [group_name])
2279
2285
2280 def permissions(self, with_admins=True, with_owner=True):
2286 def permissions(self, with_admins=True, with_owner=True):
2281 q = UserRepoGroupToPerm.query().filter(UserRepoGroupToPerm.group == self)
2287 q = UserRepoGroupToPerm.query().filter(UserRepoGroupToPerm.group == self)
2282 q = q.options(joinedload(UserRepoGroupToPerm.group),
2288 q = q.options(joinedload(UserRepoGroupToPerm.group),
2283 joinedload(UserRepoGroupToPerm.user),
2289 joinedload(UserRepoGroupToPerm.user),
2284 joinedload(UserRepoGroupToPerm.permission),)
2290 joinedload(UserRepoGroupToPerm.permission),)
2285
2291
2286 # get owners and admins and permissions. We do a trick of re-writing
2292 # get owners and admins and permissions. We do a trick of re-writing
2287 # objects from sqlalchemy to named-tuples due to sqlalchemy session
2293 # objects from sqlalchemy to named-tuples due to sqlalchemy session
2288 # has a global reference and changing one object propagates to all
2294 # has a global reference and changing one object propagates to all
2289 # others. This means if admin is also an owner admin_row that change
2295 # others. This means if admin is also an owner admin_row that change
2290 # would propagate to both objects
2296 # would propagate to both objects
2291 perm_rows = []
2297 perm_rows = []
2292 for _usr in q.all():
2298 for _usr in q.all():
2293 usr = AttributeDict(_usr.user.get_dict())
2299 usr = AttributeDict(_usr.user.get_dict())
2294 usr.permission = _usr.permission.permission_name
2300 usr.permission = _usr.permission.permission_name
2295 perm_rows.append(usr)
2301 perm_rows.append(usr)
2296
2302
2297 # filter the perm rows by 'default' first and then sort them by
2303 # filter the perm rows by 'default' first and then sort them by
2298 # admin,write,read,none permissions sorted again alphabetically in
2304 # admin,write,read,none permissions sorted again alphabetically in
2299 # each group
2305 # each group
2300 perm_rows = sorted(perm_rows, key=display_sort)
2306 perm_rows = sorted(perm_rows, key=display_sort)
2301
2307
2302 _admin_perm = 'group.admin'
2308 _admin_perm = 'group.admin'
2303 owner_row = []
2309 owner_row = []
2304 if with_owner:
2310 if with_owner:
2305 usr = AttributeDict(self.user.get_dict())
2311 usr = AttributeDict(self.user.get_dict())
2306 usr.owner_row = True
2312 usr.owner_row = True
2307 usr.permission = _admin_perm
2313 usr.permission = _admin_perm
2308 owner_row.append(usr)
2314 owner_row.append(usr)
2309
2315
2310 super_admin_rows = []
2316 super_admin_rows = []
2311 if with_admins:
2317 if with_admins:
2312 for usr in User.get_all_super_admins():
2318 for usr in User.get_all_super_admins():
2313 # if this admin is also owner, don't double the record
2319 # if this admin is also owner, don't double the record
2314 if usr.user_id == owner_row[0].user_id:
2320 if usr.user_id == owner_row[0].user_id:
2315 owner_row[0].admin_row = True
2321 owner_row[0].admin_row = True
2316 else:
2322 else:
2317 usr = AttributeDict(usr.get_dict())
2323 usr = AttributeDict(usr.get_dict())
2318 usr.admin_row = True
2324 usr.admin_row = True
2319 usr.permission = _admin_perm
2325 usr.permission = _admin_perm
2320 super_admin_rows.append(usr)
2326 super_admin_rows.append(usr)
2321
2327
2322 return super_admin_rows + owner_row + perm_rows
2328 return super_admin_rows + owner_row + perm_rows
2323
2329
2324 def permission_user_groups(self):
2330 def permission_user_groups(self):
2325 q = UserGroupRepoGroupToPerm.query().filter(UserGroupRepoGroupToPerm.group == self)
2331 q = UserGroupRepoGroupToPerm.query().filter(UserGroupRepoGroupToPerm.group == self)
2326 q = q.options(joinedload(UserGroupRepoGroupToPerm.group),
2332 q = q.options(joinedload(UserGroupRepoGroupToPerm.group),
2327 joinedload(UserGroupRepoGroupToPerm.users_group),
2333 joinedload(UserGroupRepoGroupToPerm.users_group),
2328 joinedload(UserGroupRepoGroupToPerm.permission),)
2334 joinedload(UserGroupRepoGroupToPerm.permission),)
2329
2335
2330 perm_rows = []
2336 perm_rows = []
2331 for _user_group in q.all():
2337 for _user_group in q.all():
2332 usr = AttributeDict(_user_group.users_group.get_dict())
2338 usr = AttributeDict(_user_group.users_group.get_dict())
2333 usr.permission = _user_group.permission.permission_name
2339 usr.permission = _user_group.permission.permission_name
2334 perm_rows.append(usr)
2340 perm_rows.append(usr)
2335
2341
2336 return perm_rows
2342 return perm_rows
2337
2343
2338 def get_api_data(self):
2344 def get_api_data(self):
2339 """
2345 """
2340 Common function for generating api data
2346 Common function for generating api data
2341
2347
2342 """
2348 """
2343 group = self
2349 group = self
2344 data = {
2350 data = {
2345 'group_id': group.group_id,
2351 'group_id': group.group_id,
2346 'group_name': group.group_name,
2352 'group_name': group.group_name,
2347 'group_description': group.group_description,
2353 'group_description': group.group_description,
2348 'parent_group': group.parent_group.group_name if group.parent_group else None,
2354 'parent_group': group.parent_group.group_name if group.parent_group else None,
2349 'repositories': [x.repo_name for x in group.repositories],
2355 'repositories': [x.repo_name for x in group.repositories],
2350 'owner': group.user.username,
2356 'owner': group.user.username,
2351 }
2357 }
2352 return data
2358 return data
2353
2359
2354
2360
2355 class Permission(Base, BaseModel):
2361 class Permission(Base, BaseModel):
2356 __tablename__ = 'permissions'
2362 __tablename__ = 'permissions'
2357 __table_args__ = (
2363 __table_args__ = (
2358 Index('p_perm_name_idx', 'permission_name'),
2364 Index('p_perm_name_idx', 'permission_name'),
2359 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2365 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2360 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2366 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2361 )
2367 )
2362 PERMS = [
2368 PERMS = [
2363 ('hg.admin', _('RhodeCode Super Administrator')),
2369 ('hg.admin', _('RhodeCode Super Administrator')),
2364
2370
2365 ('repository.none', _('Repository no access')),
2371 ('repository.none', _('Repository no access')),
2366 ('repository.read', _('Repository read access')),
2372 ('repository.read', _('Repository read access')),
2367 ('repository.write', _('Repository write access')),
2373 ('repository.write', _('Repository write access')),
2368 ('repository.admin', _('Repository admin access')),
2374 ('repository.admin', _('Repository admin access')),
2369
2375
2370 ('group.none', _('Repository group no access')),
2376 ('group.none', _('Repository group no access')),
2371 ('group.read', _('Repository group read access')),
2377 ('group.read', _('Repository group read access')),
2372 ('group.write', _('Repository group write access')),
2378 ('group.write', _('Repository group write access')),
2373 ('group.admin', _('Repository group admin access')),
2379 ('group.admin', _('Repository group admin access')),
2374
2380
2375 ('usergroup.none', _('User group no access')),
2381 ('usergroup.none', _('User group no access')),
2376 ('usergroup.read', _('User group read access')),
2382 ('usergroup.read', _('User group read access')),
2377 ('usergroup.write', _('User group write access')),
2383 ('usergroup.write', _('User group write access')),
2378 ('usergroup.admin', _('User group admin access')),
2384 ('usergroup.admin', _('User group admin access')),
2379
2385
2380 ('hg.repogroup.create.false', _('Repository Group creation disabled')),
2386 ('hg.repogroup.create.false', _('Repository Group creation disabled')),
2381 ('hg.repogroup.create.true', _('Repository Group creation enabled')),
2387 ('hg.repogroup.create.true', _('Repository Group creation enabled')),
2382
2388
2383 ('hg.usergroup.create.false', _('User Group creation disabled')),
2389 ('hg.usergroup.create.false', _('User Group creation disabled')),
2384 ('hg.usergroup.create.true', _('User Group creation enabled')),
2390 ('hg.usergroup.create.true', _('User Group creation enabled')),
2385
2391
2386 ('hg.create.none', _('Repository creation disabled')),
2392 ('hg.create.none', _('Repository creation disabled')),
2387 ('hg.create.repository', _('Repository creation enabled')),
2393 ('hg.create.repository', _('Repository creation enabled')),
2388 ('hg.create.write_on_repogroup.true', _('Repository creation enabled with write permission to a repository group')),
2394 ('hg.create.write_on_repogroup.true', _('Repository creation enabled with write permission to a repository group')),
2389 ('hg.create.write_on_repogroup.false', _('Repository creation disabled with write permission to a repository group')),
2395 ('hg.create.write_on_repogroup.false', _('Repository creation disabled with write permission to a repository group')),
2390
2396
2391 ('hg.fork.none', _('Repository forking disabled')),
2397 ('hg.fork.none', _('Repository forking disabled')),
2392 ('hg.fork.repository', _('Repository forking enabled')),
2398 ('hg.fork.repository', _('Repository forking enabled')),
2393
2399
2394 ('hg.register.none', _('Registration disabled')),
2400 ('hg.register.none', _('Registration disabled')),
2395 ('hg.register.manual_activate', _('User Registration with manual account activation')),
2401 ('hg.register.manual_activate', _('User Registration with manual account activation')),
2396 ('hg.register.auto_activate', _('User Registration with automatic account activation')),
2402 ('hg.register.auto_activate', _('User Registration with automatic account activation')),
2397
2403
2398 ('hg.password_reset.enabled', _('Password reset enabled')),
2404 ('hg.password_reset.enabled', _('Password reset enabled')),
2399 ('hg.password_reset.hidden', _('Password reset hidden')),
2405 ('hg.password_reset.hidden', _('Password reset hidden')),
2400 ('hg.password_reset.disabled', _('Password reset disabled')),
2406 ('hg.password_reset.disabled', _('Password reset disabled')),
2401
2407
2402 ('hg.extern_activate.manual', _('Manual activation of external account')),
2408 ('hg.extern_activate.manual', _('Manual activation of external account')),
2403 ('hg.extern_activate.auto', _('Automatic activation of external account')),
2409 ('hg.extern_activate.auto', _('Automatic activation of external account')),
2404
2410
2405 ('hg.inherit_default_perms.false', _('Inherit object permissions from default user disabled')),
2411 ('hg.inherit_default_perms.false', _('Inherit object permissions from default user disabled')),
2406 ('hg.inherit_default_perms.true', _('Inherit object permissions from default user enabled')),
2412 ('hg.inherit_default_perms.true', _('Inherit object permissions from default user enabled')),
2407 ]
2413 ]
2408
2414
2409 # definition of system default permissions for DEFAULT user
2415 # definition of system default permissions for DEFAULT user
2410 DEFAULT_USER_PERMISSIONS = [
2416 DEFAULT_USER_PERMISSIONS = [
2411 'repository.read',
2417 'repository.read',
2412 'group.read',
2418 'group.read',
2413 'usergroup.read',
2419 'usergroup.read',
2414 'hg.create.repository',
2420 'hg.create.repository',
2415 'hg.repogroup.create.false',
2421 'hg.repogroup.create.false',
2416 'hg.usergroup.create.false',
2422 'hg.usergroup.create.false',
2417 'hg.create.write_on_repogroup.true',
2423 'hg.create.write_on_repogroup.true',
2418 'hg.fork.repository',
2424 'hg.fork.repository',
2419 'hg.register.manual_activate',
2425 'hg.register.manual_activate',
2420 'hg.password_reset.enabled',
2426 'hg.password_reset.enabled',
2421 'hg.extern_activate.auto',
2427 'hg.extern_activate.auto',
2422 'hg.inherit_default_perms.true',
2428 'hg.inherit_default_perms.true',
2423 ]
2429 ]
2424
2430
2425 # defines which permissions are more important higher the more important
2431 # defines which permissions are more important higher the more important
2426 # Weight defines which permissions are more important.
2432 # Weight defines which permissions are more important.
2427 # The higher number the more important.
2433 # The higher number the more important.
2428 PERM_WEIGHTS = {
2434 PERM_WEIGHTS = {
2429 'repository.none': 0,
2435 'repository.none': 0,
2430 'repository.read': 1,
2436 'repository.read': 1,
2431 'repository.write': 3,
2437 'repository.write': 3,
2432 'repository.admin': 4,
2438 'repository.admin': 4,
2433
2439
2434 'group.none': 0,
2440 'group.none': 0,
2435 'group.read': 1,
2441 'group.read': 1,
2436 'group.write': 3,
2442 'group.write': 3,
2437 'group.admin': 4,
2443 'group.admin': 4,
2438
2444
2439 'usergroup.none': 0,
2445 'usergroup.none': 0,
2440 'usergroup.read': 1,
2446 'usergroup.read': 1,
2441 'usergroup.write': 3,
2447 'usergroup.write': 3,
2442 'usergroup.admin': 4,
2448 'usergroup.admin': 4,
2443
2449
2444 'hg.repogroup.create.false': 0,
2450 'hg.repogroup.create.false': 0,
2445 'hg.repogroup.create.true': 1,
2451 'hg.repogroup.create.true': 1,
2446
2452
2447 'hg.usergroup.create.false': 0,
2453 'hg.usergroup.create.false': 0,
2448 'hg.usergroup.create.true': 1,
2454 'hg.usergroup.create.true': 1,
2449
2455
2450 'hg.fork.none': 0,
2456 'hg.fork.none': 0,
2451 'hg.fork.repository': 1,
2457 'hg.fork.repository': 1,
2452 'hg.create.none': 0,
2458 'hg.create.none': 0,
2453 'hg.create.repository': 1
2459 'hg.create.repository': 1
2454 }
2460 }
2455
2461
2456 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2462 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2457 permission_name = Column("permission_name", String(255), nullable=True, unique=None, default=None)
2463 permission_name = Column("permission_name", String(255), nullable=True, unique=None, default=None)
2458 permission_longname = Column("permission_longname", String(255), nullable=True, unique=None, default=None)
2464 permission_longname = Column("permission_longname", String(255), nullable=True, unique=None, default=None)
2459
2465
2460 def __unicode__(self):
2466 def __unicode__(self):
2461 return u"<%s('%s:%s')>" % (
2467 return u"<%s('%s:%s')>" % (
2462 self.__class__.__name__, self.permission_id, self.permission_name
2468 self.__class__.__name__, self.permission_id, self.permission_name
2463 )
2469 )
2464
2470
2465 @classmethod
2471 @classmethod
2466 def get_by_key(cls, key):
2472 def get_by_key(cls, key):
2467 return cls.query().filter(cls.permission_name == key).scalar()
2473 return cls.query().filter(cls.permission_name == key).scalar()
2468
2474
2469 @classmethod
2475 @classmethod
2470 def get_default_repo_perms(cls, user_id, repo_id=None):
2476 def get_default_repo_perms(cls, user_id, repo_id=None):
2471 q = Session().query(UserRepoToPerm, Repository, Permission)\
2477 q = Session().query(UserRepoToPerm, Repository, Permission)\
2472 .join((Permission, UserRepoToPerm.permission_id == Permission.permission_id))\
2478 .join((Permission, UserRepoToPerm.permission_id == Permission.permission_id))\
2473 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
2479 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
2474 .filter(UserRepoToPerm.user_id == user_id)
2480 .filter(UserRepoToPerm.user_id == user_id)
2475 if repo_id:
2481 if repo_id:
2476 q = q.filter(UserRepoToPerm.repository_id == repo_id)
2482 q = q.filter(UserRepoToPerm.repository_id == repo_id)
2477 return q.all()
2483 return q.all()
2478
2484
2479 @classmethod
2485 @classmethod
2480 def get_default_repo_perms_from_user_group(cls, user_id, repo_id=None):
2486 def get_default_repo_perms_from_user_group(cls, user_id, repo_id=None):
2481 q = Session().query(UserGroupRepoToPerm, Repository, Permission)\
2487 q = Session().query(UserGroupRepoToPerm, Repository, Permission)\
2482 .join(
2488 .join(
2483 Permission,
2489 Permission,
2484 UserGroupRepoToPerm.permission_id == Permission.permission_id)\
2490 UserGroupRepoToPerm.permission_id == Permission.permission_id)\
2485 .join(
2491 .join(
2486 Repository,
2492 Repository,
2487 UserGroupRepoToPerm.repository_id == Repository.repo_id)\
2493 UserGroupRepoToPerm.repository_id == Repository.repo_id)\
2488 .join(
2494 .join(
2489 UserGroup,
2495 UserGroup,
2490 UserGroupRepoToPerm.users_group_id ==
2496 UserGroupRepoToPerm.users_group_id ==
2491 UserGroup.users_group_id)\
2497 UserGroup.users_group_id)\
2492 .join(
2498 .join(
2493 UserGroupMember,
2499 UserGroupMember,
2494 UserGroupRepoToPerm.users_group_id ==
2500 UserGroupRepoToPerm.users_group_id ==
2495 UserGroupMember.users_group_id)\
2501 UserGroupMember.users_group_id)\
2496 .filter(
2502 .filter(
2497 UserGroupMember.user_id == user_id,
2503 UserGroupMember.user_id == user_id,
2498 UserGroup.users_group_active == true())
2504 UserGroup.users_group_active == true())
2499 if repo_id:
2505 if repo_id:
2500 q = q.filter(UserGroupRepoToPerm.repository_id == repo_id)
2506 q = q.filter(UserGroupRepoToPerm.repository_id == repo_id)
2501 return q.all()
2507 return q.all()
2502
2508
2503 @classmethod
2509 @classmethod
2504 def get_default_group_perms(cls, user_id, repo_group_id=None):
2510 def get_default_group_perms(cls, user_id, repo_group_id=None):
2505 q = Session().query(UserRepoGroupToPerm, RepoGroup, Permission)\
2511 q = Session().query(UserRepoGroupToPerm, RepoGroup, Permission)\
2506 .join((Permission, UserRepoGroupToPerm.permission_id == Permission.permission_id))\
2512 .join((Permission, UserRepoGroupToPerm.permission_id == Permission.permission_id))\
2507 .join((RepoGroup, UserRepoGroupToPerm.group_id == RepoGroup.group_id))\
2513 .join((RepoGroup, UserRepoGroupToPerm.group_id == RepoGroup.group_id))\
2508 .filter(UserRepoGroupToPerm.user_id == user_id)
2514 .filter(UserRepoGroupToPerm.user_id == user_id)
2509 if repo_group_id:
2515 if repo_group_id:
2510 q = q.filter(UserRepoGroupToPerm.group_id == repo_group_id)
2516 q = q.filter(UserRepoGroupToPerm.group_id == repo_group_id)
2511 return q.all()
2517 return q.all()
2512
2518
2513 @classmethod
2519 @classmethod
2514 def get_default_group_perms_from_user_group(
2520 def get_default_group_perms_from_user_group(
2515 cls, user_id, repo_group_id=None):
2521 cls, user_id, repo_group_id=None):
2516 q = Session().query(UserGroupRepoGroupToPerm, RepoGroup, Permission)\
2522 q = Session().query(UserGroupRepoGroupToPerm, RepoGroup, Permission)\
2517 .join(
2523 .join(
2518 Permission,
2524 Permission,
2519 UserGroupRepoGroupToPerm.permission_id ==
2525 UserGroupRepoGroupToPerm.permission_id ==
2520 Permission.permission_id)\
2526 Permission.permission_id)\
2521 .join(
2527 .join(
2522 RepoGroup,
2528 RepoGroup,
2523 UserGroupRepoGroupToPerm.group_id == RepoGroup.group_id)\
2529 UserGroupRepoGroupToPerm.group_id == RepoGroup.group_id)\
2524 .join(
2530 .join(
2525 UserGroup,
2531 UserGroup,
2526 UserGroupRepoGroupToPerm.users_group_id ==
2532 UserGroupRepoGroupToPerm.users_group_id ==
2527 UserGroup.users_group_id)\
2533 UserGroup.users_group_id)\
2528 .join(
2534 .join(
2529 UserGroupMember,
2535 UserGroupMember,
2530 UserGroupRepoGroupToPerm.users_group_id ==
2536 UserGroupRepoGroupToPerm.users_group_id ==
2531 UserGroupMember.users_group_id)\
2537 UserGroupMember.users_group_id)\
2532 .filter(
2538 .filter(
2533 UserGroupMember.user_id == user_id,
2539 UserGroupMember.user_id == user_id,
2534 UserGroup.users_group_active == true())
2540 UserGroup.users_group_active == true())
2535 if repo_group_id:
2541 if repo_group_id:
2536 q = q.filter(UserGroupRepoGroupToPerm.group_id == repo_group_id)
2542 q = q.filter(UserGroupRepoGroupToPerm.group_id == repo_group_id)
2537 return q.all()
2543 return q.all()
2538
2544
2539 @classmethod
2545 @classmethod
2540 def get_default_user_group_perms(cls, user_id, user_group_id=None):
2546 def get_default_user_group_perms(cls, user_id, user_group_id=None):
2541 q = Session().query(UserUserGroupToPerm, UserGroup, Permission)\
2547 q = Session().query(UserUserGroupToPerm, UserGroup, Permission)\
2542 .join((Permission, UserUserGroupToPerm.permission_id == Permission.permission_id))\
2548 .join((Permission, UserUserGroupToPerm.permission_id == Permission.permission_id))\
2543 .join((UserGroup, UserUserGroupToPerm.user_group_id == UserGroup.users_group_id))\
2549 .join((UserGroup, UserUserGroupToPerm.user_group_id == UserGroup.users_group_id))\
2544 .filter(UserUserGroupToPerm.user_id == user_id)
2550 .filter(UserUserGroupToPerm.user_id == user_id)
2545 if user_group_id:
2551 if user_group_id:
2546 q = q.filter(UserUserGroupToPerm.user_group_id == user_group_id)
2552 q = q.filter(UserUserGroupToPerm.user_group_id == user_group_id)
2547 return q.all()
2553 return q.all()
2548
2554
2549 @classmethod
2555 @classmethod
2550 def get_default_user_group_perms_from_user_group(
2556 def get_default_user_group_perms_from_user_group(
2551 cls, user_id, user_group_id=None):
2557 cls, user_id, user_group_id=None):
2552 TargetUserGroup = aliased(UserGroup, name='target_user_group')
2558 TargetUserGroup = aliased(UserGroup, name='target_user_group')
2553 q = Session().query(UserGroupUserGroupToPerm, UserGroup, Permission)\
2559 q = Session().query(UserGroupUserGroupToPerm, UserGroup, Permission)\
2554 .join(
2560 .join(
2555 Permission,
2561 Permission,
2556 UserGroupUserGroupToPerm.permission_id ==
2562 UserGroupUserGroupToPerm.permission_id ==
2557 Permission.permission_id)\
2563 Permission.permission_id)\
2558 .join(
2564 .join(
2559 TargetUserGroup,
2565 TargetUserGroup,
2560 UserGroupUserGroupToPerm.target_user_group_id ==
2566 UserGroupUserGroupToPerm.target_user_group_id ==
2561 TargetUserGroup.users_group_id)\
2567 TargetUserGroup.users_group_id)\
2562 .join(
2568 .join(
2563 UserGroup,
2569 UserGroup,
2564 UserGroupUserGroupToPerm.user_group_id ==
2570 UserGroupUserGroupToPerm.user_group_id ==
2565 UserGroup.users_group_id)\
2571 UserGroup.users_group_id)\
2566 .join(
2572 .join(
2567 UserGroupMember,
2573 UserGroupMember,
2568 UserGroupUserGroupToPerm.user_group_id ==
2574 UserGroupUserGroupToPerm.user_group_id ==
2569 UserGroupMember.users_group_id)\
2575 UserGroupMember.users_group_id)\
2570 .filter(
2576 .filter(
2571 UserGroupMember.user_id == user_id,
2577 UserGroupMember.user_id == user_id,
2572 UserGroup.users_group_active == true())
2578 UserGroup.users_group_active == true())
2573 if user_group_id:
2579 if user_group_id:
2574 q = q.filter(
2580 q = q.filter(
2575 UserGroupUserGroupToPerm.user_group_id == user_group_id)
2581 UserGroupUserGroupToPerm.user_group_id == user_group_id)
2576
2582
2577 return q.all()
2583 return q.all()
2578
2584
2579
2585
2580 class UserRepoToPerm(Base, BaseModel):
2586 class UserRepoToPerm(Base, BaseModel):
2581 __tablename__ = 'repo_to_perm'
2587 __tablename__ = 'repo_to_perm'
2582 __table_args__ = (
2588 __table_args__ = (
2583 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
2589 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
2584 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2590 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2585 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2591 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2586 )
2592 )
2587 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2593 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2588 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2594 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2589 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2595 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2590 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
2596 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
2591
2597
2592 user = relationship('User')
2598 user = relationship('User')
2593 repository = relationship('Repository')
2599 repository = relationship('Repository')
2594 permission = relationship('Permission')
2600 permission = relationship('Permission')
2595
2601
2596 @classmethod
2602 @classmethod
2597 def create(cls, user, repository, permission):
2603 def create(cls, user, repository, permission):
2598 n = cls()
2604 n = cls()
2599 n.user = user
2605 n.user = user
2600 n.repository = repository
2606 n.repository = repository
2601 n.permission = permission
2607 n.permission = permission
2602 Session().add(n)
2608 Session().add(n)
2603 return n
2609 return n
2604
2610
2605 def __unicode__(self):
2611 def __unicode__(self):
2606 return u'<%s => %s >' % (self.user, self.repository)
2612 return u'<%s => %s >' % (self.user, self.repository)
2607
2613
2608
2614
2609 class UserUserGroupToPerm(Base, BaseModel):
2615 class UserUserGroupToPerm(Base, BaseModel):
2610 __tablename__ = 'user_user_group_to_perm'
2616 __tablename__ = 'user_user_group_to_perm'
2611 __table_args__ = (
2617 __table_args__ = (
2612 UniqueConstraint('user_id', 'user_group_id', 'permission_id'),
2618 UniqueConstraint('user_id', 'user_group_id', 'permission_id'),
2613 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2619 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2614 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2620 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2615 )
2621 )
2616 user_user_group_to_perm_id = Column("user_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2622 user_user_group_to_perm_id = Column("user_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2617 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2623 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2618 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2624 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2619 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2625 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2620
2626
2621 user = relationship('User')
2627 user = relationship('User')
2622 user_group = relationship('UserGroup')
2628 user_group = relationship('UserGroup')
2623 permission = relationship('Permission')
2629 permission = relationship('Permission')
2624
2630
2625 @classmethod
2631 @classmethod
2626 def create(cls, user, user_group, permission):
2632 def create(cls, user, user_group, permission):
2627 n = cls()
2633 n = cls()
2628 n.user = user
2634 n.user = user
2629 n.user_group = user_group
2635 n.user_group = user_group
2630 n.permission = permission
2636 n.permission = permission
2631 Session().add(n)
2637 Session().add(n)
2632 return n
2638 return n
2633
2639
2634 def __unicode__(self):
2640 def __unicode__(self):
2635 return u'<%s => %s >' % (self.user, self.user_group)
2641 return u'<%s => %s >' % (self.user, self.user_group)
2636
2642
2637
2643
2638 class UserToPerm(Base, BaseModel):
2644 class UserToPerm(Base, BaseModel):
2639 __tablename__ = 'user_to_perm'
2645 __tablename__ = 'user_to_perm'
2640 __table_args__ = (
2646 __table_args__ = (
2641 UniqueConstraint('user_id', 'permission_id'),
2647 UniqueConstraint('user_id', 'permission_id'),
2642 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2648 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2643 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2649 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2644 )
2650 )
2645 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2651 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2646 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2652 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2647 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2653 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2648
2654
2649 user = relationship('User')
2655 user = relationship('User')
2650 permission = relationship('Permission', lazy='joined')
2656 permission = relationship('Permission', lazy='joined')
2651
2657
2652 def __unicode__(self):
2658 def __unicode__(self):
2653 return u'<%s => %s >' % (self.user, self.permission)
2659 return u'<%s => %s >' % (self.user, self.permission)
2654
2660
2655
2661
2656 class UserGroupRepoToPerm(Base, BaseModel):
2662 class UserGroupRepoToPerm(Base, BaseModel):
2657 __tablename__ = 'users_group_repo_to_perm'
2663 __tablename__ = 'users_group_repo_to_perm'
2658 __table_args__ = (
2664 __table_args__ = (
2659 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
2665 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
2660 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2666 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2661 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2667 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2662 )
2668 )
2663 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2669 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2664 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2670 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2665 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2671 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2666 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
2672 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
2667
2673
2668 users_group = relationship('UserGroup')
2674 users_group = relationship('UserGroup')
2669 permission = relationship('Permission')
2675 permission = relationship('Permission')
2670 repository = relationship('Repository')
2676 repository = relationship('Repository')
2671
2677
2672 @classmethod
2678 @classmethod
2673 def create(cls, users_group, repository, permission):
2679 def create(cls, users_group, repository, permission):
2674 n = cls()
2680 n = cls()
2675 n.users_group = users_group
2681 n.users_group = users_group
2676 n.repository = repository
2682 n.repository = repository
2677 n.permission = permission
2683 n.permission = permission
2678 Session().add(n)
2684 Session().add(n)
2679 return n
2685 return n
2680
2686
2681 def __unicode__(self):
2687 def __unicode__(self):
2682 return u'<UserGroupRepoToPerm:%s => %s >' % (self.users_group, self.repository)
2688 return u'<UserGroupRepoToPerm:%s => %s >' % (self.users_group, self.repository)
2683
2689
2684
2690
2685 class UserGroupUserGroupToPerm(Base, BaseModel):
2691 class UserGroupUserGroupToPerm(Base, BaseModel):
2686 __tablename__ = 'user_group_user_group_to_perm'
2692 __tablename__ = 'user_group_user_group_to_perm'
2687 __table_args__ = (
2693 __table_args__ = (
2688 UniqueConstraint('target_user_group_id', 'user_group_id', 'permission_id'),
2694 UniqueConstraint('target_user_group_id', 'user_group_id', 'permission_id'),
2689 CheckConstraint('target_user_group_id != user_group_id'),
2695 CheckConstraint('target_user_group_id != user_group_id'),
2690 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2696 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2691 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2697 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2692 )
2698 )
2693 user_group_user_group_to_perm_id = Column("user_group_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2699 user_group_user_group_to_perm_id = Column("user_group_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2694 target_user_group_id = Column("target_user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2700 target_user_group_id = Column("target_user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2695 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2701 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2696 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2702 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2697
2703
2698 target_user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id')
2704 target_user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id')
2699 user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.user_group_id==UserGroup.users_group_id')
2705 user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.user_group_id==UserGroup.users_group_id')
2700 permission = relationship('Permission')
2706 permission = relationship('Permission')
2701
2707
2702 @classmethod
2708 @classmethod
2703 def create(cls, target_user_group, user_group, permission):
2709 def create(cls, target_user_group, user_group, permission):
2704 n = cls()
2710 n = cls()
2705 n.target_user_group = target_user_group
2711 n.target_user_group = target_user_group
2706 n.user_group = user_group
2712 n.user_group = user_group
2707 n.permission = permission
2713 n.permission = permission
2708 Session().add(n)
2714 Session().add(n)
2709 return n
2715 return n
2710
2716
2711 def __unicode__(self):
2717 def __unicode__(self):
2712 return u'<UserGroupUserGroup:%s => %s >' % (self.target_user_group, self.user_group)
2718 return u'<UserGroupUserGroup:%s => %s >' % (self.target_user_group, self.user_group)
2713
2719
2714
2720
2715 class UserGroupToPerm(Base, BaseModel):
2721 class UserGroupToPerm(Base, BaseModel):
2716 __tablename__ = 'users_group_to_perm'
2722 __tablename__ = 'users_group_to_perm'
2717 __table_args__ = (
2723 __table_args__ = (
2718 UniqueConstraint('users_group_id', 'permission_id',),
2724 UniqueConstraint('users_group_id', 'permission_id',),
2719 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2725 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2720 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2726 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2721 )
2727 )
2722 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2728 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2723 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2729 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2724 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2730 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2725
2731
2726 users_group = relationship('UserGroup')
2732 users_group = relationship('UserGroup')
2727 permission = relationship('Permission')
2733 permission = relationship('Permission')
2728
2734
2729
2735
2730 class UserRepoGroupToPerm(Base, BaseModel):
2736 class UserRepoGroupToPerm(Base, BaseModel):
2731 __tablename__ = 'user_repo_group_to_perm'
2737 __tablename__ = 'user_repo_group_to_perm'
2732 __table_args__ = (
2738 __table_args__ = (
2733 UniqueConstraint('user_id', 'group_id', 'permission_id'),
2739 UniqueConstraint('user_id', 'group_id', 'permission_id'),
2734 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2740 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2735 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2741 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2736 )
2742 )
2737
2743
2738 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2744 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2739 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2745 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2740 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
2746 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
2741 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2747 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2742
2748
2743 user = relationship('User')
2749 user = relationship('User')
2744 group = relationship('RepoGroup')
2750 group = relationship('RepoGroup')
2745 permission = relationship('Permission')
2751 permission = relationship('Permission')
2746
2752
2747 @classmethod
2753 @classmethod
2748 def create(cls, user, repository_group, permission):
2754 def create(cls, user, repository_group, permission):
2749 n = cls()
2755 n = cls()
2750 n.user = user
2756 n.user = user
2751 n.group = repository_group
2757 n.group = repository_group
2752 n.permission = permission
2758 n.permission = permission
2753 Session().add(n)
2759 Session().add(n)
2754 return n
2760 return n
2755
2761
2756
2762
2757 class UserGroupRepoGroupToPerm(Base, BaseModel):
2763 class UserGroupRepoGroupToPerm(Base, BaseModel):
2758 __tablename__ = 'users_group_repo_group_to_perm'
2764 __tablename__ = 'users_group_repo_group_to_perm'
2759 __table_args__ = (
2765 __table_args__ = (
2760 UniqueConstraint('users_group_id', 'group_id'),
2766 UniqueConstraint('users_group_id', 'group_id'),
2761 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2767 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2762 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2768 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2763 )
2769 )
2764
2770
2765 users_group_repo_group_to_perm_id = Column("users_group_repo_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2771 users_group_repo_group_to_perm_id = Column("users_group_repo_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2766 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2772 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2767 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
2773 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
2768 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2774 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2769
2775
2770 users_group = relationship('UserGroup')
2776 users_group = relationship('UserGroup')
2771 permission = relationship('Permission')
2777 permission = relationship('Permission')
2772 group = relationship('RepoGroup')
2778 group = relationship('RepoGroup')
2773
2779
2774 @classmethod
2780 @classmethod
2775 def create(cls, user_group, repository_group, permission):
2781 def create(cls, user_group, repository_group, permission):
2776 n = cls()
2782 n = cls()
2777 n.users_group = user_group
2783 n.users_group = user_group
2778 n.group = repository_group
2784 n.group = repository_group
2779 n.permission = permission
2785 n.permission = permission
2780 Session().add(n)
2786 Session().add(n)
2781 return n
2787 return n
2782
2788
2783 def __unicode__(self):
2789 def __unicode__(self):
2784 return u'<UserGroupRepoGroupToPerm:%s => %s >' % (self.users_group, self.group)
2790 return u'<UserGroupRepoGroupToPerm:%s => %s >' % (self.users_group, self.group)
2785
2791
2786
2792
2787 class Statistics(Base, BaseModel):
2793 class Statistics(Base, BaseModel):
2788 __tablename__ = 'statistics'
2794 __tablename__ = 'statistics'
2789 __table_args__ = (
2795 __table_args__ = (
2790 UniqueConstraint('repository_id'),
2796 UniqueConstraint('repository_id'),
2791 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2797 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2792 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2798 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2793 )
2799 )
2794 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2800 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2795 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
2801 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
2796 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
2802 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
2797 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
2803 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
2798 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
2804 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
2799 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
2805 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
2800
2806
2801 repository = relationship('Repository', single_parent=True)
2807 repository = relationship('Repository', single_parent=True)
2802
2808
2803
2809
2804 class UserFollowing(Base, BaseModel):
2810 class UserFollowing(Base, BaseModel):
2805 __tablename__ = 'user_followings'
2811 __tablename__ = 'user_followings'
2806 __table_args__ = (
2812 __table_args__ = (
2807 UniqueConstraint('user_id', 'follows_repository_id'),
2813 UniqueConstraint('user_id', 'follows_repository_id'),
2808 UniqueConstraint('user_id', 'follows_user_id'),
2814 UniqueConstraint('user_id', 'follows_user_id'),
2809 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2815 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2810 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2816 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2811 )
2817 )
2812
2818
2813 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2819 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2814 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2820 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2815 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
2821 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
2816 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
2822 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
2817 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
2823 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
2818
2824
2819 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
2825 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
2820
2826
2821 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
2827 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
2822 follows_repository = relationship('Repository', order_by='Repository.repo_name')
2828 follows_repository = relationship('Repository', order_by='Repository.repo_name')
2823
2829
2824 @classmethod
2830 @classmethod
2825 def get_repo_followers(cls, repo_id):
2831 def get_repo_followers(cls, repo_id):
2826 return cls.query().filter(cls.follows_repo_id == repo_id)
2832 return cls.query().filter(cls.follows_repo_id == repo_id)
2827
2833
2828
2834
2829 class CacheKey(Base, BaseModel):
2835 class CacheKey(Base, BaseModel):
2830 __tablename__ = 'cache_invalidation'
2836 __tablename__ = 'cache_invalidation'
2831 __table_args__ = (
2837 __table_args__ = (
2832 UniqueConstraint('cache_key'),
2838 UniqueConstraint('cache_key'),
2833 Index('key_idx', 'cache_key'),
2839 Index('key_idx', 'cache_key'),
2834 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2840 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2835 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2841 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2836 )
2842 )
2837 CACHE_TYPE_ATOM = 'ATOM'
2843 CACHE_TYPE_ATOM = 'ATOM'
2838 CACHE_TYPE_RSS = 'RSS'
2844 CACHE_TYPE_RSS = 'RSS'
2839 CACHE_TYPE_README = 'README'
2845 CACHE_TYPE_README = 'README'
2840
2846
2841 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2847 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2842 cache_key = Column("cache_key", String(255), nullable=True, unique=None, default=None)
2848 cache_key = Column("cache_key", String(255), nullable=True, unique=None, default=None)
2843 cache_args = Column("cache_args", String(255), nullable=True, unique=None, default=None)
2849 cache_args = Column("cache_args", String(255), nullable=True, unique=None, default=None)
2844 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
2850 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
2845
2851
2846 def __init__(self, cache_key, cache_args=''):
2852 def __init__(self, cache_key, cache_args=''):
2847 self.cache_key = cache_key
2853 self.cache_key = cache_key
2848 self.cache_args = cache_args
2854 self.cache_args = cache_args
2849 self.cache_active = False
2855 self.cache_active = False
2850
2856
2851 def __unicode__(self):
2857 def __unicode__(self):
2852 return u"<%s('%s:%s[%s]')>" % (
2858 return u"<%s('%s:%s[%s]')>" % (
2853 self.__class__.__name__,
2859 self.__class__.__name__,
2854 self.cache_id, self.cache_key, self.cache_active)
2860 self.cache_id, self.cache_key, self.cache_active)
2855
2861
2856 def _cache_key_partition(self):
2862 def _cache_key_partition(self):
2857 prefix, repo_name, suffix = self.cache_key.partition(self.cache_args)
2863 prefix, repo_name, suffix = self.cache_key.partition(self.cache_args)
2858 return prefix, repo_name, suffix
2864 return prefix, repo_name, suffix
2859
2865
2860 def get_prefix(self):
2866 def get_prefix(self):
2861 """
2867 """
2862 Try to extract prefix from existing cache key. The key could consist
2868 Try to extract prefix from existing cache key. The key could consist
2863 of prefix, repo_name, suffix
2869 of prefix, repo_name, suffix
2864 """
2870 """
2865 # this returns prefix, repo_name, suffix
2871 # this returns prefix, repo_name, suffix
2866 return self._cache_key_partition()[0]
2872 return self._cache_key_partition()[0]
2867
2873
2868 def get_suffix(self):
2874 def get_suffix(self):
2869 """
2875 """
2870 get suffix that might have been used in _get_cache_key to
2876 get suffix that might have been used in _get_cache_key to
2871 generate self.cache_key. Only used for informational purposes
2877 generate self.cache_key. Only used for informational purposes
2872 in repo_edit.mako.
2878 in repo_edit.mako.
2873 """
2879 """
2874 # prefix, repo_name, suffix
2880 # prefix, repo_name, suffix
2875 return self._cache_key_partition()[2]
2881 return self._cache_key_partition()[2]
2876
2882
2877 @classmethod
2883 @classmethod
2878 def delete_all_cache(cls):
2884 def delete_all_cache(cls):
2879 """
2885 """
2880 Delete all cache keys from database.
2886 Delete all cache keys from database.
2881 Should only be run when all instances are down and all entries
2887 Should only be run when all instances are down and all entries
2882 thus stale.
2888 thus stale.
2883 """
2889 """
2884 cls.query().delete()
2890 cls.query().delete()
2885 Session().commit()
2891 Session().commit()
2886
2892
2887 @classmethod
2893 @classmethod
2888 def get_cache_key(cls, repo_name, cache_type):
2894 def get_cache_key(cls, repo_name, cache_type):
2889 """
2895 """
2890
2896
2891 Generate a cache key for this process of RhodeCode instance.
2897 Generate a cache key for this process of RhodeCode instance.
2892 Prefix most likely will be process id or maybe explicitly set
2898 Prefix most likely will be process id or maybe explicitly set
2893 instance_id from .ini file.
2899 instance_id from .ini file.
2894 """
2900 """
2895 import rhodecode
2901 import rhodecode
2896 prefix = safe_unicode(rhodecode.CONFIG.get('instance_id') or '')
2902 prefix = safe_unicode(rhodecode.CONFIG.get('instance_id') or '')
2897
2903
2898 repo_as_unicode = safe_unicode(repo_name)
2904 repo_as_unicode = safe_unicode(repo_name)
2899 key = u'{}_{}'.format(repo_as_unicode, cache_type) \
2905 key = u'{}_{}'.format(repo_as_unicode, cache_type) \
2900 if cache_type else repo_as_unicode
2906 if cache_type else repo_as_unicode
2901
2907
2902 return u'{}{}'.format(prefix, key)
2908 return u'{}{}'.format(prefix, key)
2903
2909
2904 @classmethod
2910 @classmethod
2905 def set_invalidate(cls, repo_name, delete=False):
2911 def set_invalidate(cls, repo_name, delete=False):
2906 """
2912 """
2907 Mark all caches of a repo as invalid in the database.
2913 Mark all caches of a repo as invalid in the database.
2908 """
2914 """
2909
2915
2910 try:
2916 try:
2911 qry = Session().query(cls).filter(cls.cache_args == repo_name)
2917 qry = Session().query(cls).filter(cls.cache_args == repo_name)
2912 if delete:
2918 if delete:
2913 log.debug('cache objects deleted for repo %s',
2919 log.debug('cache objects deleted for repo %s',
2914 safe_str(repo_name))
2920 safe_str(repo_name))
2915 qry.delete()
2921 qry.delete()
2916 else:
2922 else:
2917 log.debug('cache objects marked as invalid for repo %s',
2923 log.debug('cache objects marked as invalid for repo %s',
2918 safe_str(repo_name))
2924 safe_str(repo_name))
2919 qry.update({"cache_active": False})
2925 qry.update({"cache_active": False})
2920
2926
2921 Session().commit()
2927 Session().commit()
2922 except Exception:
2928 except Exception:
2923 log.exception(
2929 log.exception(
2924 'Cache key invalidation failed for repository %s',
2930 'Cache key invalidation failed for repository %s',
2925 safe_str(repo_name))
2931 safe_str(repo_name))
2926 Session().rollback()
2932 Session().rollback()
2927
2933
2928 @classmethod
2934 @classmethod
2929 def get_active_cache(cls, cache_key):
2935 def get_active_cache(cls, cache_key):
2930 inv_obj = cls.query().filter(cls.cache_key == cache_key).scalar()
2936 inv_obj = cls.query().filter(cls.cache_key == cache_key).scalar()
2931 if inv_obj:
2937 if inv_obj:
2932 return inv_obj
2938 return inv_obj
2933 return None
2939 return None
2934
2940
2935 @classmethod
2941 @classmethod
2936 def repo_context_cache(cls, compute_func, repo_name, cache_type,
2942 def repo_context_cache(cls, compute_func, repo_name, cache_type,
2937 thread_scoped=False):
2943 thread_scoped=False):
2938 """
2944 """
2939 @cache_region('long_term')
2945 @cache_region('long_term')
2940 def _heavy_calculation(cache_key):
2946 def _heavy_calculation(cache_key):
2941 return 'result'
2947 return 'result'
2942
2948
2943 cache_context = CacheKey.repo_context_cache(
2949 cache_context = CacheKey.repo_context_cache(
2944 _heavy_calculation, repo_name, cache_type)
2950 _heavy_calculation, repo_name, cache_type)
2945
2951
2946 with cache_context as context:
2952 with cache_context as context:
2947 context.invalidate()
2953 context.invalidate()
2948 computed = context.compute()
2954 computed = context.compute()
2949
2955
2950 assert computed == 'result'
2956 assert computed == 'result'
2951 """
2957 """
2952 from rhodecode.lib import caches
2958 from rhodecode.lib import caches
2953 return caches.InvalidationContext(
2959 return caches.InvalidationContext(
2954 compute_func, repo_name, cache_type, thread_scoped=thread_scoped)
2960 compute_func, repo_name, cache_type, thread_scoped=thread_scoped)
2955
2961
2956
2962
2957 class ChangesetComment(Base, BaseModel):
2963 class ChangesetComment(Base, BaseModel):
2958 __tablename__ = 'changeset_comments'
2964 __tablename__ = 'changeset_comments'
2959 __table_args__ = (
2965 __table_args__ = (
2960 Index('cc_revision_idx', 'revision'),
2966 Index('cc_revision_idx', 'revision'),
2961 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2967 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2962 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2968 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2963 )
2969 )
2964
2970
2965 COMMENT_OUTDATED = u'comment_outdated'
2971 COMMENT_OUTDATED = u'comment_outdated'
2966 COMMENT_TYPE_NOTE = u'note'
2972 COMMENT_TYPE_NOTE = u'note'
2967 COMMENT_TYPE_TODO = u'todo'
2973 COMMENT_TYPE_TODO = u'todo'
2968 COMMENT_TYPES = [COMMENT_TYPE_NOTE, COMMENT_TYPE_TODO]
2974 COMMENT_TYPES = [COMMENT_TYPE_NOTE, COMMENT_TYPE_TODO]
2969
2975
2970 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
2976 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
2971 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
2977 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
2972 revision = Column('revision', String(40), nullable=True)
2978 revision = Column('revision', String(40), nullable=True)
2973 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
2979 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
2974 pull_request_version_id = Column("pull_request_version_id", Integer(), ForeignKey('pull_request_versions.pull_request_version_id'), nullable=True)
2980 pull_request_version_id = Column("pull_request_version_id", Integer(), ForeignKey('pull_request_versions.pull_request_version_id'), nullable=True)
2975 line_no = Column('line_no', Unicode(10), nullable=True)
2981 line_no = Column('line_no', Unicode(10), nullable=True)
2976 hl_lines = Column('hl_lines', Unicode(512), nullable=True)
2982 hl_lines = Column('hl_lines', Unicode(512), nullable=True)
2977 f_path = Column('f_path', Unicode(1000), nullable=True)
2983 f_path = Column('f_path', Unicode(1000), nullable=True)
2978 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
2984 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
2979 text = Column('text', UnicodeText().with_variant(UnicodeText(25000), 'mysql'), nullable=False)
2985 text = Column('text', UnicodeText().with_variant(UnicodeText(25000), 'mysql'), nullable=False)
2980 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2986 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2981 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2987 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2982 renderer = Column('renderer', Unicode(64), nullable=True)
2988 renderer = Column('renderer', Unicode(64), nullable=True)
2983 display_state = Column('display_state', Unicode(128), nullable=True)
2989 display_state = Column('display_state', Unicode(128), nullable=True)
2984
2990
2985 comment_type = Column('comment_type', Unicode(128), nullable=True, default=COMMENT_TYPE_NOTE)
2991 comment_type = Column('comment_type', Unicode(128), nullable=True, default=COMMENT_TYPE_NOTE)
2986 resolved_comment_id = Column('resolved_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'), nullable=True)
2992 resolved_comment_id = Column('resolved_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'), nullable=True)
2987 resolved_comment = relationship('ChangesetComment', remote_side=comment_id, backref='resolved_by')
2993 resolved_comment = relationship('ChangesetComment', remote_side=comment_id, backref='resolved_by')
2988 author = relationship('User', lazy='joined')
2994 author = relationship('User', lazy='joined')
2989 repo = relationship('Repository')
2995 repo = relationship('Repository')
2990 status_change = relationship('ChangesetStatus', cascade="all, delete, delete-orphan", lazy='joined')
2996 status_change = relationship('ChangesetStatus', cascade="all, delete, delete-orphan", lazy='joined')
2991 pull_request = relationship('PullRequest', lazy='joined')
2997 pull_request = relationship('PullRequest', lazy='joined')
2992 pull_request_version = relationship('PullRequestVersion')
2998 pull_request_version = relationship('PullRequestVersion')
2993
2999
2994 @classmethod
3000 @classmethod
2995 def get_users(cls, revision=None, pull_request_id=None):
3001 def get_users(cls, revision=None, pull_request_id=None):
2996 """
3002 """
2997 Returns user associated with this ChangesetComment. ie those
3003 Returns user associated with this ChangesetComment. ie those
2998 who actually commented
3004 who actually commented
2999
3005
3000 :param cls:
3006 :param cls:
3001 :param revision:
3007 :param revision:
3002 """
3008 """
3003 q = Session().query(User)\
3009 q = Session().query(User)\
3004 .join(ChangesetComment.author)
3010 .join(ChangesetComment.author)
3005 if revision:
3011 if revision:
3006 q = q.filter(cls.revision == revision)
3012 q = q.filter(cls.revision == revision)
3007 elif pull_request_id:
3013 elif pull_request_id:
3008 q = q.filter(cls.pull_request_id == pull_request_id)
3014 q = q.filter(cls.pull_request_id == pull_request_id)
3009 return q.all()
3015 return q.all()
3010
3016
3011 @classmethod
3017 @classmethod
3012 def get_index_from_version(cls, pr_version, versions):
3018 def get_index_from_version(cls, pr_version, versions):
3013 num_versions = [x.pull_request_version_id for x in versions]
3019 num_versions = [x.pull_request_version_id for x in versions]
3014 try:
3020 try:
3015 return num_versions.index(pr_version) +1
3021 return num_versions.index(pr_version) +1
3016 except (IndexError, ValueError):
3022 except (IndexError, ValueError):
3017 return
3023 return
3018
3024
3019 @property
3025 @property
3020 def outdated(self):
3026 def outdated(self):
3021 return self.display_state == self.COMMENT_OUTDATED
3027 return self.display_state == self.COMMENT_OUTDATED
3022
3028
3023 def outdated_at_version(self, version):
3029 def outdated_at_version(self, version):
3024 """
3030 """
3025 Checks if comment is outdated for given pull request version
3031 Checks if comment is outdated for given pull request version
3026 """
3032 """
3027 return self.outdated and self.pull_request_version_id != version
3033 return self.outdated and self.pull_request_version_id != version
3028
3034
3029 def older_than_version(self, version):
3035 def older_than_version(self, version):
3030 """
3036 """
3031 Checks if comment is made from previous version than given
3037 Checks if comment is made from previous version than given
3032 """
3038 """
3033 if version is None:
3039 if version is None:
3034 return self.pull_request_version_id is not None
3040 return self.pull_request_version_id is not None
3035
3041
3036 return self.pull_request_version_id < version
3042 return self.pull_request_version_id < version
3037
3043
3038 @property
3044 @property
3039 def resolved(self):
3045 def resolved(self):
3040 return self.resolved_by[0] if self.resolved_by else None
3046 return self.resolved_by[0] if self.resolved_by else None
3041
3047
3042 @property
3048 @property
3043 def is_todo(self):
3049 def is_todo(self):
3044 return self.comment_type == self.COMMENT_TYPE_TODO
3050 return self.comment_type == self.COMMENT_TYPE_TODO
3045
3051
3046 def get_index_version(self, versions):
3052 def get_index_version(self, versions):
3047 return self.get_index_from_version(
3053 return self.get_index_from_version(
3048 self.pull_request_version_id, versions)
3054 self.pull_request_version_id, versions)
3049
3055
3050 def render(self, mentions=False):
3056 def render(self, mentions=False):
3051 from rhodecode.lib import helpers as h
3057 from rhodecode.lib import helpers as h
3052 return h.render(self.text, renderer=self.renderer, mentions=mentions)
3058 return h.render(self.text, renderer=self.renderer, mentions=mentions)
3053
3059
3054 def __repr__(self):
3060 def __repr__(self):
3055 if self.comment_id:
3061 if self.comment_id:
3056 return '<DB:Comment #%s>' % self.comment_id
3062 return '<DB:Comment #%s>' % self.comment_id
3057 else:
3063 else:
3058 return '<DB:Comment at %#x>' % id(self)
3064 return '<DB:Comment at %#x>' % id(self)
3059
3065
3060
3066
3061 class ChangesetStatus(Base, BaseModel):
3067 class ChangesetStatus(Base, BaseModel):
3062 __tablename__ = 'changeset_statuses'
3068 __tablename__ = 'changeset_statuses'
3063 __table_args__ = (
3069 __table_args__ = (
3064 Index('cs_revision_idx', 'revision'),
3070 Index('cs_revision_idx', 'revision'),
3065 Index('cs_version_idx', 'version'),
3071 Index('cs_version_idx', 'version'),
3066 UniqueConstraint('repo_id', 'revision', 'version'),
3072 UniqueConstraint('repo_id', 'revision', 'version'),
3067 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3073 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3068 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3074 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3069 )
3075 )
3070 STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed'
3076 STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed'
3071 STATUS_APPROVED = 'approved'
3077 STATUS_APPROVED = 'approved'
3072 STATUS_REJECTED = 'rejected'
3078 STATUS_REJECTED = 'rejected'
3073 STATUS_UNDER_REVIEW = 'under_review'
3079 STATUS_UNDER_REVIEW = 'under_review'
3074
3080
3075 STATUSES = [
3081 STATUSES = [
3076 (STATUS_NOT_REVIEWED, _("Not Reviewed")), # (no icon) and default
3082 (STATUS_NOT_REVIEWED, _("Not Reviewed")), # (no icon) and default
3077 (STATUS_APPROVED, _("Approved")),
3083 (STATUS_APPROVED, _("Approved")),
3078 (STATUS_REJECTED, _("Rejected")),
3084 (STATUS_REJECTED, _("Rejected")),
3079 (STATUS_UNDER_REVIEW, _("Under Review")),
3085 (STATUS_UNDER_REVIEW, _("Under Review")),
3080 ]
3086 ]
3081
3087
3082 changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True)
3088 changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True)
3083 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
3089 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
3084 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
3090 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
3085 revision = Column('revision', String(40), nullable=False)
3091 revision = Column('revision', String(40), nullable=False)
3086 status = Column('status', String(128), nullable=False, default=DEFAULT)
3092 status = Column('status', String(128), nullable=False, default=DEFAULT)
3087 changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'))
3093 changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'))
3088 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
3094 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
3089 version = Column('version', Integer(), nullable=False, default=0)
3095 version = Column('version', Integer(), nullable=False, default=0)
3090 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
3096 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
3091
3097
3092 author = relationship('User', lazy='joined')
3098 author = relationship('User', lazy='joined')
3093 repo = relationship('Repository')
3099 repo = relationship('Repository')
3094 comment = relationship('ChangesetComment', lazy='joined')
3100 comment = relationship('ChangesetComment', lazy='joined')
3095 pull_request = relationship('PullRequest', lazy='joined')
3101 pull_request = relationship('PullRequest', lazy='joined')
3096
3102
3097 def __unicode__(self):
3103 def __unicode__(self):
3098 return u"<%s('%s[v%s]:%s')>" % (
3104 return u"<%s('%s[v%s]:%s')>" % (
3099 self.__class__.__name__,
3105 self.__class__.__name__,
3100 self.status, self.version, self.author
3106 self.status, self.version, self.author
3101 )
3107 )
3102
3108
3103 @classmethod
3109 @classmethod
3104 def get_status_lbl(cls, value):
3110 def get_status_lbl(cls, value):
3105 return dict(cls.STATUSES).get(value)
3111 return dict(cls.STATUSES).get(value)
3106
3112
3107 @property
3113 @property
3108 def status_lbl(self):
3114 def status_lbl(self):
3109 return ChangesetStatus.get_status_lbl(self.status)
3115 return ChangesetStatus.get_status_lbl(self.status)
3110
3116
3111
3117
3112 class _PullRequestBase(BaseModel):
3118 class _PullRequestBase(BaseModel):
3113 """
3119 """
3114 Common attributes of pull request and version entries.
3120 Common attributes of pull request and version entries.
3115 """
3121 """
3116
3122
3117 # .status values
3123 # .status values
3118 STATUS_NEW = u'new'
3124 STATUS_NEW = u'new'
3119 STATUS_OPEN = u'open'
3125 STATUS_OPEN = u'open'
3120 STATUS_CLOSED = u'closed'
3126 STATUS_CLOSED = u'closed'
3121
3127
3122 title = Column('title', Unicode(255), nullable=True)
3128 title = Column('title', Unicode(255), nullable=True)
3123 description = Column(
3129 description = Column(
3124 'description', UnicodeText().with_variant(UnicodeText(10240), 'mysql'),
3130 'description', UnicodeText().with_variant(UnicodeText(10240), 'mysql'),
3125 nullable=True)
3131 nullable=True)
3126 # new/open/closed status of pull request (not approve/reject/etc)
3132 # new/open/closed status of pull request (not approve/reject/etc)
3127 status = Column('status', Unicode(255), nullable=False, default=STATUS_NEW)
3133 status = Column('status', Unicode(255), nullable=False, default=STATUS_NEW)
3128 created_on = Column(
3134 created_on = Column(
3129 'created_on', DateTime(timezone=False), nullable=False,
3135 'created_on', DateTime(timezone=False), nullable=False,
3130 default=datetime.datetime.now)
3136 default=datetime.datetime.now)
3131 updated_on = Column(
3137 updated_on = Column(
3132 'updated_on', DateTime(timezone=False), nullable=False,
3138 'updated_on', DateTime(timezone=False), nullable=False,
3133 default=datetime.datetime.now)
3139 default=datetime.datetime.now)
3134
3140
3135 @declared_attr
3141 @declared_attr
3136 def user_id(cls):
3142 def user_id(cls):
3137 return Column(
3143 return Column(
3138 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
3144 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
3139 unique=None)
3145 unique=None)
3140
3146
3141 # 500 revisions max
3147 # 500 revisions max
3142 _revisions = Column(
3148 _revisions = Column(
3143 'revisions', UnicodeText().with_variant(UnicodeText(20500), 'mysql'))
3149 'revisions', UnicodeText().with_variant(UnicodeText(20500), 'mysql'))
3144
3150
3145 @declared_attr
3151 @declared_attr
3146 def source_repo_id(cls):
3152 def source_repo_id(cls):
3147 # TODO: dan: rename column to source_repo_id
3153 # TODO: dan: rename column to source_repo_id
3148 return Column(
3154 return Column(
3149 'org_repo_id', Integer(), ForeignKey('repositories.repo_id'),
3155 'org_repo_id', Integer(), ForeignKey('repositories.repo_id'),
3150 nullable=False)
3156 nullable=False)
3151
3157
3152 source_ref = Column('org_ref', Unicode(255), nullable=False)
3158 source_ref = Column('org_ref', Unicode(255), nullable=False)
3153
3159
3154 @declared_attr
3160 @declared_attr
3155 def target_repo_id(cls):
3161 def target_repo_id(cls):
3156 # TODO: dan: rename column to target_repo_id
3162 # TODO: dan: rename column to target_repo_id
3157 return Column(
3163 return Column(
3158 'other_repo_id', Integer(), ForeignKey('repositories.repo_id'),
3164 'other_repo_id', Integer(), ForeignKey('repositories.repo_id'),
3159 nullable=False)
3165 nullable=False)
3160
3166
3161 target_ref = Column('other_ref', Unicode(255), nullable=False)
3167 target_ref = Column('other_ref', Unicode(255), nullable=False)
3162 _shadow_merge_ref = Column('shadow_merge_ref', Unicode(255), nullable=True)
3168 _shadow_merge_ref = Column('shadow_merge_ref', Unicode(255), nullable=True)
3163
3169
3164 # TODO: dan: rename column to last_merge_source_rev
3170 # TODO: dan: rename column to last_merge_source_rev
3165 _last_merge_source_rev = Column(
3171 _last_merge_source_rev = Column(
3166 'last_merge_org_rev', String(40), nullable=True)
3172 'last_merge_org_rev', String(40), nullable=True)
3167 # TODO: dan: rename column to last_merge_target_rev
3173 # TODO: dan: rename column to last_merge_target_rev
3168 _last_merge_target_rev = Column(
3174 _last_merge_target_rev = Column(
3169 'last_merge_other_rev', String(40), nullable=True)
3175 'last_merge_other_rev', String(40), nullable=True)
3170 _last_merge_status = Column('merge_status', Integer(), nullable=True)
3176 _last_merge_status = Column('merge_status', Integer(), nullable=True)
3171 merge_rev = Column('merge_rev', String(40), nullable=True)
3177 merge_rev = Column('merge_rev', String(40), nullable=True)
3172
3178
3173 @hybrid_property
3179 @hybrid_property
3174 def revisions(self):
3180 def revisions(self):
3175 return self._revisions.split(':') if self._revisions else []
3181 return self._revisions.split(':') if self._revisions else []
3176
3182
3177 @revisions.setter
3183 @revisions.setter
3178 def revisions(self, val):
3184 def revisions(self, val):
3179 self._revisions = ':'.join(val)
3185 self._revisions = ':'.join(val)
3180
3186
3181 @declared_attr
3187 @declared_attr
3182 def author(cls):
3188 def author(cls):
3183 return relationship('User', lazy='joined')
3189 return relationship('User', lazy='joined')
3184
3190
3185 @declared_attr
3191 @declared_attr
3186 def source_repo(cls):
3192 def source_repo(cls):
3187 return relationship(
3193 return relationship(
3188 'Repository',
3194 'Repository',
3189 primaryjoin='%s.source_repo_id==Repository.repo_id' % cls.__name__)
3195 primaryjoin='%s.source_repo_id==Repository.repo_id' % cls.__name__)
3190
3196
3191 @property
3197 @property
3192 def source_ref_parts(self):
3198 def source_ref_parts(self):
3193 return self.unicode_to_reference(self.source_ref)
3199 return self.unicode_to_reference(self.source_ref)
3194
3200
3195 @declared_attr
3201 @declared_attr
3196 def target_repo(cls):
3202 def target_repo(cls):
3197 return relationship(
3203 return relationship(
3198 'Repository',
3204 'Repository',
3199 primaryjoin='%s.target_repo_id==Repository.repo_id' % cls.__name__)
3205 primaryjoin='%s.target_repo_id==Repository.repo_id' % cls.__name__)
3200
3206
3201 @property
3207 @property
3202 def target_ref_parts(self):
3208 def target_ref_parts(self):
3203 return self.unicode_to_reference(self.target_ref)
3209 return self.unicode_to_reference(self.target_ref)
3204
3210
3205 @property
3211 @property
3206 def shadow_merge_ref(self):
3212 def shadow_merge_ref(self):
3207 return self.unicode_to_reference(self._shadow_merge_ref)
3213 return self.unicode_to_reference(self._shadow_merge_ref)
3208
3214
3209 @shadow_merge_ref.setter
3215 @shadow_merge_ref.setter
3210 def shadow_merge_ref(self, ref):
3216 def shadow_merge_ref(self, ref):
3211 self._shadow_merge_ref = self.reference_to_unicode(ref)
3217 self._shadow_merge_ref = self.reference_to_unicode(ref)
3212
3218
3213 def unicode_to_reference(self, raw):
3219 def unicode_to_reference(self, raw):
3214 """
3220 """
3215 Convert a unicode (or string) to a reference object.
3221 Convert a unicode (or string) to a reference object.
3216 If unicode evaluates to False it returns None.
3222 If unicode evaluates to False it returns None.
3217 """
3223 """
3218 if raw:
3224 if raw:
3219 refs = raw.split(':')
3225 refs = raw.split(':')
3220 return Reference(*refs)
3226 return Reference(*refs)
3221 else:
3227 else:
3222 return None
3228 return None
3223
3229
3224 def reference_to_unicode(self, ref):
3230 def reference_to_unicode(self, ref):
3225 """
3231 """
3226 Convert a reference object to unicode.
3232 Convert a reference object to unicode.
3227 If reference is None it returns None.
3233 If reference is None it returns None.
3228 """
3234 """
3229 if ref:
3235 if ref:
3230 return u':'.join(ref)
3236 return u':'.join(ref)
3231 else:
3237 else:
3232 return None
3238 return None
3233
3239
3234 def get_api_data(self):
3240 def get_api_data(self):
3235 from rhodecode.model.pull_request import PullRequestModel
3241 from rhodecode.model.pull_request import PullRequestModel
3236 pull_request = self
3242 pull_request = self
3237 merge_status = PullRequestModel().merge_status(pull_request)
3243 merge_status = PullRequestModel().merge_status(pull_request)
3238
3244
3239 pull_request_url = url(
3245 pull_request_url = url(
3240 'pullrequest_show', repo_name=self.target_repo.repo_name,
3246 'pullrequest_show', repo_name=self.target_repo.repo_name,
3241 pull_request_id=self.pull_request_id, qualified=True)
3247 pull_request_id=self.pull_request_id, qualified=True)
3242
3248
3243 merge_data = {
3249 merge_data = {
3244 'clone_url': PullRequestModel().get_shadow_clone_url(pull_request),
3250 'clone_url': PullRequestModel().get_shadow_clone_url(pull_request),
3245 'reference': (
3251 'reference': (
3246 pull_request.shadow_merge_ref._asdict()
3252 pull_request.shadow_merge_ref._asdict()
3247 if pull_request.shadow_merge_ref else None),
3253 if pull_request.shadow_merge_ref else None),
3248 }
3254 }
3249
3255
3250 data = {
3256 data = {
3251 'pull_request_id': pull_request.pull_request_id,
3257 'pull_request_id': pull_request.pull_request_id,
3252 'url': pull_request_url,
3258 'url': pull_request_url,
3253 'title': pull_request.title,
3259 'title': pull_request.title,
3254 'description': pull_request.description,
3260 'description': pull_request.description,
3255 'status': pull_request.status,
3261 'status': pull_request.status,
3256 'created_on': pull_request.created_on,
3262 'created_on': pull_request.created_on,
3257 'updated_on': pull_request.updated_on,
3263 'updated_on': pull_request.updated_on,
3258 'commit_ids': pull_request.revisions,
3264 'commit_ids': pull_request.revisions,
3259 'review_status': pull_request.calculated_review_status(),
3265 'review_status': pull_request.calculated_review_status(),
3260 'mergeable': {
3266 'mergeable': {
3261 'status': merge_status[0],
3267 'status': merge_status[0],
3262 'message': unicode(merge_status[1]),
3268 'message': unicode(merge_status[1]),
3263 },
3269 },
3264 'source': {
3270 'source': {
3265 'clone_url': pull_request.source_repo.clone_url(),
3271 'clone_url': pull_request.source_repo.clone_url(),
3266 'repository': pull_request.source_repo.repo_name,
3272 'repository': pull_request.source_repo.repo_name,
3267 'reference': {
3273 'reference': {
3268 'name': pull_request.source_ref_parts.name,
3274 'name': pull_request.source_ref_parts.name,
3269 'type': pull_request.source_ref_parts.type,
3275 'type': pull_request.source_ref_parts.type,
3270 'commit_id': pull_request.source_ref_parts.commit_id,
3276 'commit_id': pull_request.source_ref_parts.commit_id,
3271 },
3277 },
3272 },
3278 },
3273 'target': {
3279 'target': {
3274 'clone_url': pull_request.target_repo.clone_url(),
3280 'clone_url': pull_request.target_repo.clone_url(),
3275 'repository': pull_request.target_repo.repo_name,
3281 'repository': pull_request.target_repo.repo_name,
3276 'reference': {
3282 'reference': {
3277 'name': pull_request.target_ref_parts.name,
3283 'name': pull_request.target_ref_parts.name,
3278 'type': pull_request.target_ref_parts.type,
3284 'type': pull_request.target_ref_parts.type,
3279 'commit_id': pull_request.target_ref_parts.commit_id,
3285 'commit_id': pull_request.target_ref_parts.commit_id,
3280 },
3286 },
3281 },
3287 },
3282 'merge': merge_data,
3288 'merge': merge_data,
3283 'author': pull_request.author.get_api_data(include_secrets=False,
3289 'author': pull_request.author.get_api_data(include_secrets=False,
3284 details='basic'),
3290 details='basic'),
3285 'reviewers': [
3291 'reviewers': [
3286 {
3292 {
3287 'user': reviewer.get_api_data(include_secrets=False,
3293 'user': reviewer.get_api_data(include_secrets=False,
3288 details='basic'),
3294 details='basic'),
3289 'reasons': reasons,
3295 'reasons': reasons,
3290 'review_status': st[0][1].status if st else 'not_reviewed',
3296 'review_status': st[0][1].status if st else 'not_reviewed',
3291 }
3297 }
3292 for reviewer, reasons, st in pull_request.reviewers_statuses()
3298 for reviewer, reasons, st in pull_request.reviewers_statuses()
3293 ]
3299 ]
3294 }
3300 }
3295
3301
3296 return data
3302 return data
3297
3303
3298
3304
3299 class PullRequest(Base, _PullRequestBase):
3305 class PullRequest(Base, _PullRequestBase):
3300 __tablename__ = 'pull_requests'
3306 __tablename__ = 'pull_requests'
3301 __table_args__ = (
3307 __table_args__ = (
3302 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3308 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3303 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3309 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3304 )
3310 )
3305
3311
3306 pull_request_id = Column(
3312 pull_request_id = Column(
3307 'pull_request_id', Integer(), nullable=False, primary_key=True)
3313 'pull_request_id', Integer(), nullable=False, primary_key=True)
3308
3314
3309 def __repr__(self):
3315 def __repr__(self):
3310 if self.pull_request_id:
3316 if self.pull_request_id:
3311 return '<DB:PullRequest #%s>' % self.pull_request_id
3317 return '<DB:PullRequest #%s>' % self.pull_request_id
3312 else:
3318 else:
3313 return '<DB:PullRequest at %#x>' % id(self)
3319 return '<DB:PullRequest at %#x>' % id(self)
3314
3320
3315 reviewers = relationship('PullRequestReviewers',
3321 reviewers = relationship('PullRequestReviewers',
3316 cascade="all, delete, delete-orphan")
3322 cascade="all, delete, delete-orphan")
3317 statuses = relationship('ChangesetStatus')
3323 statuses = relationship('ChangesetStatus')
3318 comments = relationship('ChangesetComment',
3324 comments = relationship('ChangesetComment',
3319 cascade="all, delete, delete-orphan")
3325 cascade="all, delete, delete-orphan")
3320 versions = relationship('PullRequestVersion',
3326 versions = relationship('PullRequestVersion',
3321 cascade="all, delete, delete-orphan",
3327 cascade="all, delete, delete-orphan",
3322 lazy='dynamic')
3328 lazy='dynamic')
3323
3329
3324 @classmethod
3330 @classmethod
3325 def get_pr_display_object(cls, pull_request_obj, org_pull_request_obj,
3331 def get_pr_display_object(cls, pull_request_obj, org_pull_request_obj,
3326 internal_methods=None):
3332 internal_methods=None):
3327
3333
3328 class PullRequestDisplay(object):
3334 class PullRequestDisplay(object):
3329 """
3335 """
3330 Special object wrapper for showing PullRequest data via Versions
3336 Special object wrapper for showing PullRequest data via Versions
3331 It mimics PR object as close as possible. This is read only object
3337 It mimics PR object as close as possible. This is read only object
3332 just for display
3338 just for display
3333 """
3339 """
3334
3340
3335 def __init__(self, attrs, internal=None):
3341 def __init__(self, attrs, internal=None):
3336 self.attrs = attrs
3342 self.attrs = attrs
3337 # internal have priority over the given ones via attrs
3343 # internal have priority over the given ones via attrs
3338 self.internal = internal or ['versions']
3344 self.internal = internal or ['versions']
3339
3345
3340 def __getattr__(self, item):
3346 def __getattr__(self, item):
3341 if item in self.internal:
3347 if item in self.internal:
3342 return getattr(self, item)
3348 return getattr(self, item)
3343 try:
3349 try:
3344 return self.attrs[item]
3350 return self.attrs[item]
3345 except KeyError:
3351 except KeyError:
3346 raise AttributeError(
3352 raise AttributeError(
3347 '%s object has no attribute %s' % (self, item))
3353 '%s object has no attribute %s' % (self, item))
3348
3354
3349 def __repr__(self):
3355 def __repr__(self):
3350 return '<DB:PullRequestDisplay #%s>' % self.attrs.get('pull_request_id')
3356 return '<DB:PullRequestDisplay #%s>' % self.attrs.get('pull_request_id')
3351
3357
3352 def versions(self):
3358 def versions(self):
3353 return pull_request_obj.versions.order_by(
3359 return pull_request_obj.versions.order_by(
3354 PullRequestVersion.pull_request_version_id).all()
3360 PullRequestVersion.pull_request_version_id).all()
3355
3361
3356 def is_closed(self):
3362 def is_closed(self):
3357 return pull_request_obj.is_closed()
3363 return pull_request_obj.is_closed()
3358
3364
3359 @property
3365 @property
3360 def pull_request_version_id(self):
3366 def pull_request_version_id(self):
3361 return getattr(pull_request_obj, 'pull_request_version_id', None)
3367 return getattr(pull_request_obj, 'pull_request_version_id', None)
3362
3368
3363 attrs = StrictAttributeDict(pull_request_obj.get_api_data())
3369 attrs = StrictAttributeDict(pull_request_obj.get_api_data())
3364
3370
3365 attrs.author = StrictAttributeDict(
3371 attrs.author = StrictAttributeDict(
3366 pull_request_obj.author.get_api_data())
3372 pull_request_obj.author.get_api_data())
3367 if pull_request_obj.target_repo:
3373 if pull_request_obj.target_repo:
3368 attrs.target_repo = StrictAttributeDict(
3374 attrs.target_repo = StrictAttributeDict(
3369 pull_request_obj.target_repo.get_api_data())
3375 pull_request_obj.target_repo.get_api_data())
3370 attrs.target_repo.clone_url = pull_request_obj.target_repo.clone_url
3376 attrs.target_repo.clone_url = pull_request_obj.target_repo.clone_url
3371
3377
3372 if pull_request_obj.source_repo:
3378 if pull_request_obj.source_repo:
3373 attrs.source_repo = StrictAttributeDict(
3379 attrs.source_repo = StrictAttributeDict(
3374 pull_request_obj.source_repo.get_api_data())
3380 pull_request_obj.source_repo.get_api_data())
3375 attrs.source_repo.clone_url = pull_request_obj.source_repo.clone_url
3381 attrs.source_repo.clone_url = pull_request_obj.source_repo.clone_url
3376
3382
3377 attrs.source_ref_parts = pull_request_obj.source_ref_parts
3383 attrs.source_ref_parts = pull_request_obj.source_ref_parts
3378 attrs.target_ref_parts = pull_request_obj.target_ref_parts
3384 attrs.target_ref_parts = pull_request_obj.target_ref_parts
3379 attrs.revisions = pull_request_obj.revisions
3385 attrs.revisions = pull_request_obj.revisions
3380
3386
3381 attrs.shadow_merge_ref = org_pull_request_obj.shadow_merge_ref
3387 attrs.shadow_merge_ref = org_pull_request_obj.shadow_merge_ref
3382
3388
3383 return PullRequestDisplay(attrs, internal=internal_methods)
3389 return PullRequestDisplay(attrs, internal=internal_methods)
3384
3390
3385 def is_closed(self):
3391 def is_closed(self):
3386 return self.status == self.STATUS_CLOSED
3392 return self.status == self.STATUS_CLOSED
3387
3393
3388 def __json__(self):
3394 def __json__(self):
3389 return {
3395 return {
3390 'revisions': self.revisions,
3396 'revisions': self.revisions,
3391 }
3397 }
3392
3398
3393 def calculated_review_status(self):
3399 def calculated_review_status(self):
3394 from rhodecode.model.changeset_status import ChangesetStatusModel
3400 from rhodecode.model.changeset_status import ChangesetStatusModel
3395 return ChangesetStatusModel().calculated_review_status(self)
3401 return ChangesetStatusModel().calculated_review_status(self)
3396
3402
3397 def reviewers_statuses(self):
3403 def reviewers_statuses(self):
3398 from rhodecode.model.changeset_status import ChangesetStatusModel
3404 from rhodecode.model.changeset_status import ChangesetStatusModel
3399 return ChangesetStatusModel().reviewers_statuses(self)
3405 return ChangesetStatusModel().reviewers_statuses(self)
3400
3406
3401 @property
3407 @property
3402 def workspace_id(self):
3408 def workspace_id(self):
3403 from rhodecode.model.pull_request import PullRequestModel
3409 from rhodecode.model.pull_request import PullRequestModel
3404 return PullRequestModel()._workspace_id(self)
3410 return PullRequestModel()._workspace_id(self)
3405
3411
3406 def get_shadow_repo(self):
3412 def get_shadow_repo(self):
3407 workspace_id = self.workspace_id
3413 workspace_id = self.workspace_id
3408 vcs_obj = self.target_repo.scm_instance()
3414 vcs_obj = self.target_repo.scm_instance()
3409 shadow_repository_path = vcs_obj._get_shadow_repository_path(
3415 shadow_repository_path = vcs_obj._get_shadow_repository_path(
3410 workspace_id)
3416 workspace_id)
3411 return vcs_obj._get_shadow_instance(shadow_repository_path)
3417 return vcs_obj._get_shadow_instance(shadow_repository_path)
3412
3418
3413
3419
3414 class PullRequestVersion(Base, _PullRequestBase):
3420 class PullRequestVersion(Base, _PullRequestBase):
3415 __tablename__ = 'pull_request_versions'
3421 __tablename__ = 'pull_request_versions'
3416 __table_args__ = (
3422 __table_args__ = (
3417 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3423 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3418 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3424 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3419 )
3425 )
3420
3426
3421 pull_request_version_id = Column(
3427 pull_request_version_id = Column(
3422 'pull_request_version_id', Integer(), nullable=False, primary_key=True)
3428 'pull_request_version_id', Integer(), nullable=False, primary_key=True)
3423 pull_request_id = Column(
3429 pull_request_id = Column(
3424 'pull_request_id', Integer(),
3430 'pull_request_id', Integer(),
3425 ForeignKey('pull_requests.pull_request_id'), nullable=False)
3431 ForeignKey('pull_requests.pull_request_id'), nullable=False)
3426 pull_request = relationship('PullRequest')
3432 pull_request = relationship('PullRequest')
3427
3433
3428 def __repr__(self):
3434 def __repr__(self):
3429 if self.pull_request_version_id:
3435 if self.pull_request_version_id:
3430 return '<DB:PullRequestVersion #%s>' % self.pull_request_version_id
3436 return '<DB:PullRequestVersion #%s>' % self.pull_request_version_id
3431 else:
3437 else:
3432 return '<DB:PullRequestVersion at %#x>' % id(self)
3438 return '<DB:PullRequestVersion at %#x>' % id(self)
3433
3439
3434 @property
3440 @property
3435 def reviewers(self):
3441 def reviewers(self):
3436 return self.pull_request.reviewers
3442 return self.pull_request.reviewers
3437
3443
3438 @property
3444 @property
3439 def versions(self):
3445 def versions(self):
3440 return self.pull_request.versions
3446 return self.pull_request.versions
3441
3447
3442 def is_closed(self):
3448 def is_closed(self):
3443 # calculate from original
3449 # calculate from original
3444 return self.pull_request.status == self.STATUS_CLOSED
3450 return self.pull_request.status == self.STATUS_CLOSED
3445
3451
3446 def calculated_review_status(self):
3452 def calculated_review_status(self):
3447 return self.pull_request.calculated_review_status()
3453 return self.pull_request.calculated_review_status()
3448
3454
3449 def reviewers_statuses(self):
3455 def reviewers_statuses(self):
3450 return self.pull_request.reviewers_statuses()
3456 return self.pull_request.reviewers_statuses()
3451
3457
3452
3458
3453 class PullRequestReviewers(Base, BaseModel):
3459 class PullRequestReviewers(Base, BaseModel):
3454 __tablename__ = 'pull_request_reviewers'
3460 __tablename__ = 'pull_request_reviewers'
3455 __table_args__ = (
3461 __table_args__ = (
3456 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3462 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3457 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3463 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3458 )
3464 )
3459
3465
3460 def __init__(self, user=None, pull_request=None, reasons=None):
3466 def __init__(self, user=None, pull_request=None, reasons=None):
3461 self.user = user
3467 self.user = user
3462 self.pull_request = pull_request
3468 self.pull_request = pull_request
3463 self.reasons = reasons or []
3469 self.reasons = reasons or []
3464
3470
3465 @hybrid_property
3471 @hybrid_property
3466 def reasons(self):
3472 def reasons(self):
3467 if not self._reasons:
3473 if not self._reasons:
3468 return []
3474 return []
3469 return self._reasons
3475 return self._reasons
3470
3476
3471 @reasons.setter
3477 @reasons.setter
3472 def reasons(self, val):
3478 def reasons(self, val):
3473 val = val or []
3479 val = val or []
3474 if any(not isinstance(x, basestring) for x in val):
3480 if any(not isinstance(x, basestring) for x in val):
3475 raise Exception('invalid reasons type, must be list of strings')
3481 raise Exception('invalid reasons type, must be list of strings')
3476 self._reasons = val
3482 self._reasons = val
3477
3483
3478 pull_requests_reviewers_id = Column(
3484 pull_requests_reviewers_id = Column(
3479 'pull_requests_reviewers_id', Integer(), nullable=False,
3485 'pull_requests_reviewers_id', Integer(), nullable=False,
3480 primary_key=True)
3486 primary_key=True)
3481 pull_request_id = Column(
3487 pull_request_id = Column(
3482 "pull_request_id", Integer(),
3488 "pull_request_id", Integer(),
3483 ForeignKey('pull_requests.pull_request_id'), nullable=False)
3489 ForeignKey('pull_requests.pull_request_id'), nullable=False)
3484 user_id = Column(
3490 user_id = Column(
3485 "user_id", Integer(), ForeignKey('users.user_id'), nullable=True)
3491 "user_id", Integer(), ForeignKey('users.user_id'), nullable=True)
3486 _reasons = Column(
3492 _reasons = Column(
3487 'reason', MutationList.as_mutable(
3493 'reason', MutationList.as_mutable(
3488 JsonType('list', dialect_map=dict(mysql=UnicodeText(16384)))))
3494 JsonType('list', dialect_map=dict(mysql=UnicodeText(16384)))))
3489
3495
3490 user = relationship('User')
3496 user = relationship('User')
3491 pull_request = relationship('PullRequest')
3497 pull_request = relationship('PullRequest')
3492
3498
3493
3499
3494 class Notification(Base, BaseModel):
3500 class Notification(Base, BaseModel):
3495 __tablename__ = 'notifications'
3501 __tablename__ = 'notifications'
3496 __table_args__ = (
3502 __table_args__ = (
3497 Index('notification_type_idx', 'type'),
3503 Index('notification_type_idx', 'type'),
3498 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3504 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3499 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3505 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3500 )
3506 )
3501
3507
3502 TYPE_CHANGESET_COMMENT = u'cs_comment'
3508 TYPE_CHANGESET_COMMENT = u'cs_comment'
3503 TYPE_MESSAGE = u'message'
3509 TYPE_MESSAGE = u'message'
3504 TYPE_MENTION = u'mention'
3510 TYPE_MENTION = u'mention'
3505 TYPE_REGISTRATION = u'registration'
3511 TYPE_REGISTRATION = u'registration'
3506 TYPE_PULL_REQUEST = u'pull_request'
3512 TYPE_PULL_REQUEST = u'pull_request'
3507 TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment'
3513 TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment'
3508
3514
3509 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
3515 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
3510 subject = Column('subject', Unicode(512), nullable=True)
3516 subject = Column('subject', Unicode(512), nullable=True)
3511 body = Column('body', UnicodeText().with_variant(UnicodeText(50000), 'mysql'), nullable=True)
3517 body = Column('body', UnicodeText().with_variant(UnicodeText(50000), 'mysql'), nullable=True)
3512 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
3518 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
3513 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3519 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3514 type_ = Column('type', Unicode(255))
3520 type_ = Column('type', Unicode(255))
3515
3521
3516 created_by_user = relationship('User')
3522 created_by_user = relationship('User')
3517 notifications_to_users = relationship('UserNotification', lazy='joined',
3523 notifications_to_users = relationship('UserNotification', lazy='joined',
3518 cascade="all, delete, delete-orphan")
3524 cascade="all, delete, delete-orphan")
3519
3525
3520 @property
3526 @property
3521 def recipients(self):
3527 def recipients(self):
3522 return [x.user for x in UserNotification.query()\
3528 return [x.user for x in UserNotification.query()\
3523 .filter(UserNotification.notification == self)\
3529 .filter(UserNotification.notification == self)\
3524 .order_by(UserNotification.user_id.asc()).all()]
3530 .order_by(UserNotification.user_id.asc()).all()]
3525
3531
3526 @classmethod
3532 @classmethod
3527 def create(cls, created_by, subject, body, recipients, type_=None):
3533 def create(cls, created_by, subject, body, recipients, type_=None):
3528 if type_ is None:
3534 if type_ is None:
3529 type_ = Notification.TYPE_MESSAGE
3535 type_ = Notification.TYPE_MESSAGE
3530
3536
3531 notification = cls()
3537 notification = cls()
3532 notification.created_by_user = created_by
3538 notification.created_by_user = created_by
3533 notification.subject = subject
3539 notification.subject = subject
3534 notification.body = body
3540 notification.body = body
3535 notification.type_ = type_
3541 notification.type_ = type_
3536 notification.created_on = datetime.datetime.now()
3542 notification.created_on = datetime.datetime.now()
3537
3543
3538 for u in recipients:
3544 for u in recipients:
3539 assoc = UserNotification()
3545 assoc = UserNotification()
3540 assoc.notification = notification
3546 assoc.notification = notification
3541
3547
3542 # if created_by is inside recipients mark his notification
3548 # if created_by is inside recipients mark his notification
3543 # as read
3549 # as read
3544 if u.user_id == created_by.user_id:
3550 if u.user_id == created_by.user_id:
3545 assoc.read = True
3551 assoc.read = True
3546
3552
3547 u.notifications.append(assoc)
3553 u.notifications.append(assoc)
3548 Session().add(notification)
3554 Session().add(notification)
3549
3555
3550 return notification
3556 return notification
3551
3557
3552 @property
3558 @property
3553 def description(self):
3559 def description(self):
3554 from rhodecode.model.notification import NotificationModel
3560 from rhodecode.model.notification import NotificationModel
3555 return NotificationModel().make_description(self)
3561 return NotificationModel().make_description(self)
3556
3562
3557
3563
3558 class UserNotification(Base, BaseModel):
3564 class UserNotification(Base, BaseModel):
3559 __tablename__ = 'user_to_notification'
3565 __tablename__ = 'user_to_notification'
3560 __table_args__ = (
3566 __table_args__ = (
3561 UniqueConstraint('user_id', 'notification_id'),
3567 UniqueConstraint('user_id', 'notification_id'),
3562 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3568 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3563 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3569 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3564 )
3570 )
3565 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
3571 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
3566 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
3572 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
3567 read = Column('read', Boolean, default=False)
3573 read = Column('read', Boolean, default=False)
3568 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
3574 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
3569
3575
3570 user = relationship('User', lazy="joined")
3576 user = relationship('User', lazy="joined")
3571 notification = relationship('Notification', lazy="joined",
3577 notification = relationship('Notification', lazy="joined",
3572 order_by=lambda: Notification.created_on.desc(),)
3578 order_by=lambda: Notification.created_on.desc(),)
3573
3579
3574 def mark_as_read(self):
3580 def mark_as_read(self):
3575 self.read = True
3581 self.read = True
3576 Session().add(self)
3582 Session().add(self)
3577
3583
3578
3584
3579 class Gist(Base, BaseModel):
3585 class Gist(Base, BaseModel):
3580 __tablename__ = 'gists'
3586 __tablename__ = 'gists'
3581 __table_args__ = (
3587 __table_args__ = (
3582 Index('g_gist_access_id_idx', 'gist_access_id'),
3588 Index('g_gist_access_id_idx', 'gist_access_id'),
3583 Index('g_created_on_idx', 'created_on'),
3589 Index('g_created_on_idx', 'created_on'),
3584 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3590 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3585 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3591 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3586 )
3592 )
3587 GIST_PUBLIC = u'public'
3593 GIST_PUBLIC = u'public'
3588 GIST_PRIVATE = u'private'
3594 GIST_PRIVATE = u'private'
3589 DEFAULT_FILENAME = u'gistfile1.txt'
3595 DEFAULT_FILENAME = u'gistfile1.txt'
3590
3596
3591 ACL_LEVEL_PUBLIC = u'acl_public'
3597 ACL_LEVEL_PUBLIC = u'acl_public'
3592 ACL_LEVEL_PRIVATE = u'acl_private'
3598 ACL_LEVEL_PRIVATE = u'acl_private'
3593
3599
3594 gist_id = Column('gist_id', Integer(), primary_key=True)
3600 gist_id = Column('gist_id', Integer(), primary_key=True)
3595 gist_access_id = Column('gist_access_id', Unicode(250))
3601 gist_access_id = Column('gist_access_id', Unicode(250))
3596 gist_description = Column('gist_description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
3602 gist_description = Column('gist_description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
3597 gist_owner = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True)
3603 gist_owner = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True)
3598 gist_expires = Column('gist_expires', Float(53), nullable=False)
3604 gist_expires = Column('gist_expires', Float(53), nullable=False)
3599 gist_type = Column('gist_type', Unicode(128), nullable=False)
3605 gist_type = Column('gist_type', Unicode(128), nullable=False)
3600 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3606 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3601 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3607 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3602 acl_level = Column('acl_level', Unicode(128), nullable=True)
3608 acl_level = Column('acl_level', Unicode(128), nullable=True)
3603
3609
3604 owner = relationship('User')
3610 owner = relationship('User')
3605
3611
3606 def __repr__(self):
3612 def __repr__(self):
3607 return '<Gist:[%s]%s>' % (self.gist_type, self.gist_access_id)
3613 return '<Gist:[%s]%s>' % (self.gist_type, self.gist_access_id)
3608
3614
3609 @classmethod
3615 @classmethod
3610 def get_or_404(cls, id_):
3616 def get_or_404(cls, id_):
3611 res = cls.query().filter(cls.gist_access_id == id_).scalar()
3617 res = cls.query().filter(cls.gist_access_id == id_).scalar()
3612 if not res:
3618 if not res:
3613 raise HTTPNotFound
3619 raise HTTPNotFound
3614 return res
3620 return res
3615
3621
3616 @classmethod
3622 @classmethod
3617 def get_by_access_id(cls, gist_access_id):
3623 def get_by_access_id(cls, gist_access_id):
3618 return cls.query().filter(cls.gist_access_id == gist_access_id).scalar()
3624 return cls.query().filter(cls.gist_access_id == gist_access_id).scalar()
3619
3625
3620 def gist_url(self):
3626 def gist_url(self):
3621 import rhodecode
3627 import rhodecode
3622 alias_url = rhodecode.CONFIG.get('gist_alias_url')
3628 alias_url = rhodecode.CONFIG.get('gist_alias_url')
3623 if alias_url:
3629 if alias_url:
3624 return alias_url.replace('{gistid}', self.gist_access_id)
3630 return alias_url.replace('{gistid}', self.gist_access_id)
3625
3631
3626 return url('gist', gist_id=self.gist_access_id, qualified=True)
3632 return url('gist', gist_id=self.gist_access_id, qualified=True)
3627
3633
3628 @classmethod
3634 @classmethod
3629 def base_path(cls):
3635 def base_path(cls):
3630 """
3636 """
3631 Returns base path when all gists are stored
3637 Returns base path when all gists are stored
3632
3638
3633 :param cls:
3639 :param cls:
3634 """
3640 """
3635 from rhodecode.model.gist import GIST_STORE_LOC
3641 from rhodecode.model.gist import GIST_STORE_LOC
3636 q = Session().query(RhodeCodeUi)\
3642 q = Session().query(RhodeCodeUi)\
3637 .filter(RhodeCodeUi.ui_key == URL_SEP)
3643 .filter(RhodeCodeUi.ui_key == URL_SEP)
3638 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
3644 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
3639 return os.path.join(q.one().ui_value, GIST_STORE_LOC)
3645 return os.path.join(q.one().ui_value, GIST_STORE_LOC)
3640
3646
3641 def get_api_data(self):
3647 def get_api_data(self):
3642 """
3648 """
3643 Common function for generating gist related data for API
3649 Common function for generating gist related data for API
3644 """
3650 """
3645 gist = self
3651 gist = self
3646 data = {
3652 data = {
3647 'gist_id': gist.gist_id,
3653 'gist_id': gist.gist_id,
3648 'type': gist.gist_type,
3654 'type': gist.gist_type,
3649 'access_id': gist.gist_access_id,
3655 'access_id': gist.gist_access_id,
3650 'description': gist.gist_description,
3656 'description': gist.gist_description,
3651 'url': gist.gist_url(),
3657 'url': gist.gist_url(),
3652 'expires': gist.gist_expires,
3658 'expires': gist.gist_expires,
3653 'created_on': gist.created_on,
3659 'created_on': gist.created_on,
3654 'modified_at': gist.modified_at,
3660 'modified_at': gist.modified_at,
3655 'content': None,
3661 'content': None,
3656 'acl_level': gist.acl_level,
3662 'acl_level': gist.acl_level,
3657 }
3663 }
3658 return data
3664 return data
3659
3665
3660 def __json__(self):
3666 def __json__(self):
3661 data = dict(
3667 data = dict(
3662 )
3668 )
3663 data.update(self.get_api_data())
3669 data.update(self.get_api_data())
3664 return data
3670 return data
3665 # SCM functions
3671 # SCM functions
3666
3672
3667 def scm_instance(self, **kwargs):
3673 def scm_instance(self, **kwargs):
3668 full_repo_path = os.path.join(self.base_path(), self.gist_access_id)
3674 full_repo_path = os.path.join(self.base_path(), self.gist_access_id)
3669 return get_vcs_instance(
3675 return get_vcs_instance(
3670 repo_path=safe_str(full_repo_path), create=False)
3676 repo_path=safe_str(full_repo_path), create=False)
3671
3677
3672
3678
3673 class ExternalIdentity(Base, BaseModel):
3679 class ExternalIdentity(Base, BaseModel):
3674 __tablename__ = 'external_identities'
3680 __tablename__ = 'external_identities'
3675 __table_args__ = (
3681 __table_args__ = (
3676 Index('local_user_id_idx', 'local_user_id'),
3682 Index('local_user_id_idx', 'local_user_id'),
3677 Index('external_id_idx', 'external_id'),
3683 Index('external_id_idx', 'external_id'),
3678 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3684 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3679 'mysql_charset': 'utf8'})
3685 'mysql_charset': 'utf8'})
3680
3686
3681 external_id = Column('external_id', Unicode(255), default=u'',
3687 external_id = Column('external_id', Unicode(255), default=u'',
3682 primary_key=True)
3688 primary_key=True)
3683 external_username = Column('external_username', Unicode(1024), default=u'')
3689 external_username = Column('external_username', Unicode(1024), default=u'')
3684 local_user_id = Column('local_user_id', Integer(),
3690 local_user_id = Column('local_user_id', Integer(),
3685 ForeignKey('users.user_id'), primary_key=True)
3691 ForeignKey('users.user_id'), primary_key=True)
3686 provider_name = Column('provider_name', Unicode(255), default=u'',
3692 provider_name = Column('provider_name', Unicode(255), default=u'',
3687 primary_key=True)
3693 primary_key=True)
3688 access_token = Column('access_token', String(1024), default=u'')
3694 access_token = Column('access_token', String(1024), default=u'')
3689 alt_token = Column('alt_token', String(1024), default=u'')
3695 alt_token = Column('alt_token', String(1024), default=u'')
3690 token_secret = Column('token_secret', String(1024), default=u'')
3696 token_secret = Column('token_secret', String(1024), default=u'')
3691
3697
3692 @classmethod
3698 @classmethod
3693 def by_external_id_and_provider(cls, external_id, provider_name,
3699 def by_external_id_and_provider(cls, external_id, provider_name,
3694 local_user_id=None):
3700 local_user_id=None):
3695 """
3701 """
3696 Returns ExternalIdentity instance based on search params
3702 Returns ExternalIdentity instance based on search params
3697
3703
3698 :param external_id:
3704 :param external_id:
3699 :param provider_name:
3705 :param provider_name:
3700 :return: ExternalIdentity
3706 :return: ExternalIdentity
3701 """
3707 """
3702 query = cls.query()
3708 query = cls.query()
3703 query = query.filter(cls.external_id == external_id)
3709 query = query.filter(cls.external_id == external_id)
3704 query = query.filter(cls.provider_name == provider_name)
3710 query = query.filter(cls.provider_name == provider_name)
3705 if local_user_id:
3711 if local_user_id:
3706 query = query.filter(cls.local_user_id == local_user_id)
3712 query = query.filter(cls.local_user_id == local_user_id)
3707 return query.first()
3713 return query.first()
3708
3714
3709 @classmethod
3715 @classmethod
3710 def user_by_external_id_and_provider(cls, external_id, provider_name):
3716 def user_by_external_id_and_provider(cls, external_id, provider_name):
3711 """
3717 """
3712 Returns User instance based on search params
3718 Returns User instance based on search params
3713
3719
3714 :param external_id:
3720 :param external_id:
3715 :param provider_name:
3721 :param provider_name:
3716 :return: User
3722 :return: User
3717 """
3723 """
3718 query = User.query()
3724 query = User.query()
3719 query = query.filter(cls.external_id == external_id)
3725 query = query.filter(cls.external_id == external_id)
3720 query = query.filter(cls.provider_name == provider_name)
3726 query = query.filter(cls.provider_name == provider_name)
3721 query = query.filter(User.user_id == cls.local_user_id)
3727 query = query.filter(User.user_id == cls.local_user_id)
3722 return query.first()
3728 return query.first()
3723
3729
3724 @classmethod
3730 @classmethod
3725 def by_local_user_id(cls, local_user_id):
3731 def by_local_user_id(cls, local_user_id):
3726 """
3732 """
3727 Returns all tokens for user
3733 Returns all tokens for user
3728
3734
3729 :param local_user_id:
3735 :param local_user_id:
3730 :return: ExternalIdentity
3736 :return: ExternalIdentity
3731 """
3737 """
3732 query = cls.query()
3738 query = cls.query()
3733 query = query.filter(cls.local_user_id == local_user_id)
3739 query = query.filter(cls.local_user_id == local_user_id)
3734 return query
3740 return query
3735
3741
3736
3742
3737 class Integration(Base, BaseModel):
3743 class Integration(Base, BaseModel):
3738 __tablename__ = 'integrations'
3744 __tablename__ = 'integrations'
3739 __table_args__ = (
3745 __table_args__ = (
3740 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3746 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3741 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3747 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3742 )
3748 )
3743
3749
3744 integration_id = Column('integration_id', Integer(), primary_key=True)
3750 integration_id = Column('integration_id', Integer(), primary_key=True)
3745 integration_type = Column('integration_type', String(255))
3751 integration_type = Column('integration_type', String(255))
3746 enabled = Column('enabled', Boolean(), nullable=False)
3752 enabled = Column('enabled', Boolean(), nullable=False)
3747 name = Column('name', String(255), nullable=False)
3753 name = Column('name', String(255), nullable=False)
3748 child_repos_only = Column('child_repos_only', Boolean(), nullable=False,
3754 child_repos_only = Column('child_repos_only', Boolean(), nullable=False,
3749 default=False)
3755 default=False)
3750
3756
3751 settings = Column(
3757 settings = Column(
3752 'settings_json', MutationObj.as_mutable(
3758 'settings_json', MutationObj.as_mutable(
3753 JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
3759 JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
3754 repo_id = Column(
3760 repo_id = Column(
3755 'repo_id', Integer(), ForeignKey('repositories.repo_id'),
3761 'repo_id', Integer(), ForeignKey('repositories.repo_id'),
3756 nullable=True, unique=None, default=None)
3762 nullable=True, unique=None, default=None)
3757 repo = relationship('Repository', lazy='joined')
3763 repo = relationship('Repository', lazy='joined')
3758
3764
3759 repo_group_id = Column(
3765 repo_group_id = Column(
3760 'repo_group_id', Integer(), ForeignKey('groups.group_id'),
3766 'repo_group_id', Integer(), ForeignKey('groups.group_id'),
3761 nullable=True, unique=None, default=None)
3767 nullable=True, unique=None, default=None)
3762 repo_group = relationship('RepoGroup', lazy='joined')
3768 repo_group = relationship('RepoGroup', lazy='joined')
3763
3769
3764 @property
3770 @property
3765 def scope(self):
3771 def scope(self):
3766 if self.repo:
3772 if self.repo:
3767 return repr(self.repo)
3773 return repr(self.repo)
3768 if self.repo_group:
3774 if self.repo_group:
3769 if self.child_repos_only:
3775 if self.child_repos_only:
3770 return repr(self.repo_group) + ' (child repos only)'
3776 return repr(self.repo_group) + ' (child repos only)'
3771 else:
3777 else:
3772 return repr(self.repo_group) + ' (recursive)'
3778 return repr(self.repo_group) + ' (recursive)'
3773 if self.child_repos_only:
3779 if self.child_repos_only:
3774 return 'root_repos'
3780 return 'root_repos'
3775 return 'global'
3781 return 'global'
3776
3782
3777 def __repr__(self):
3783 def __repr__(self):
3778 return '<Integration(%r, %r)>' % (self.integration_type, self.scope)
3784 return '<Integration(%r, %r)>' % (self.integration_type, self.scope)
3779
3785
3780
3786
3781 class RepoReviewRuleUser(Base, BaseModel):
3787 class RepoReviewRuleUser(Base, BaseModel):
3782 __tablename__ = 'repo_review_rules_users'
3788 __tablename__ = 'repo_review_rules_users'
3783 __table_args__ = (
3789 __table_args__ = (
3784 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3790 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3785 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
3791 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
3786 )
3792 )
3787 repo_review_rule_user_id = Column(
3793 repo_review_rule_user_id = Column(
3788 'repo_review_rule_user_id', Integer(), primary_key=True)
3794 'repo_review_rule_user_id', Integer(), primary_key=True)
3789 repo_review_rule_id = Column("repo_review_rule_id",
3795 repo_review_rule_id = Column("repo_review_rule_id",
3790 Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
3796 Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
3791 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'),
3797 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'),
3792 nullable=False)
3798 nullable=False)
3793 user = relationship('User')
3799 user = relationship('User')
3794
3800
3795
3801
3796 class RepoReviewRuleUserGroup(Base, BaseModel):
3802 class RepoReviewRuleUserGroup(Base, BaseModel):
3797 __tablename__ = 'repo_review_rules_users_groups'
3803 __tablename__ = 'repo_review_rules_users_groups'
3798 __table_args__ = (
3804 __table_args__ = (
3799 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3805 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3800 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
3806 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
3801 )
3807 )
3802 repo_review_rule_users_group_id = Column(
3808 repo_review_rule_users_group_id = Column(
3803 'repo_review_rule_users_group_id', Integer(), primary_key=True)
3809 'repo_review_rule_users_group_id', Integer(), primary_key=True)
3804 repo_review_rule_id = Column("repo_review_rule_id",
3810 repo_review_rule_id = Column("repo_review_rule_id",
3805 Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
3811 Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
3806 users_group_id = Column("users_group_id", Integer(),
3812 users_group_id = Column("users_group_id", Integer(),
3807 ForeignKey('users_groups.users_group_id'), nullable=False)
3813 ForeignKey('users_groups.users_group_id'), nullable=False)
3808 users_group = relationship('UserGroup')
3814 users_group = relationship('UserGroup')
3809
3815
3810
3816
3811 class RepoReviewRule(Base, BaseModel):
3817 class RepoReviewRule(Base, BaseModel):
3812 __tablename__ = 'repo_review_rules'
3818 __tablename__ = 'repo_review_rules'
3813 __table_args__ = (
3819 __table_args__ = (
3814 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3820 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3815 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
3821 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
3816 )
3822 )
3817
3823
3818 repo_review_rule_id = Column(
3824 repo_review_rule_id = Column(
3819 'repo_review_rule_id', Integer(), primary_key=True)
3825 'repo_review_rule_id', Integer(), primary_key=True)
3820 repo_id = Column(
3826 repo_id = Column(
3821 "repo_id", Integer(), ForeignKey('repositories.repo_id'))
3827 "repo_id", Integer(), ForeignKey('repositories.repo_id'))
3822 repo = relationship('Repository', backref='review_rules')
3828 repo = relationship('Repository', backref='review_rules')
3823
3829
3824 _branch_pattern = Column("branch_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'),
3830 _branch_pattern = Column("branch_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'),
3825 default=u'*') # glob
3831 default=u'*') # glob
3826 _file_pattern = Column("file_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'),
3832 _file_pattern = Column("file_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'),
3827 default=u'*') # glob
3833 default=u'*') # glob
3828
3834
3829 use_authors_for_review = Column("use_authors_for_review", Boolean(),
3835 use_authors_for_review = Column("use_authors_for_review", Boolean(),
3830 nullable=False, default=False)
3836 nullable=False, default=False)
3831 rule_users = relationship('RepoReviewRuleUser')
3837 rule_users = relationship('RepoReviewRuleUser')
3832 rule_user_groups = relationship('RepoReviewRuleUserGroup')
3838 rule_user_groups = relationship('RepoReviewRuleUserGroup')
3833
3839
3834 @hybrid_property
3840 @hybrid_property
3835 def branch_pattern(self):
3841 def branch_pattern(self):
3836 return self._branch_pattern or '*'
3842 return self._branch_pattern or '*'
3837
3843
3838 def _validate_glob(self, value):
3844 def _validate_glob(self, value):
3839 re.compile('^' + glob2re(value) + '$')
3845 re.compile('^' + glob2re(value) + '$')
3840
3846
3841 @branch_pattern.setter
3847 @branch_pattern.setter
3842 def branch_pattern(self, value):
3848 def branch_pattern(self, value):
3843 self._validate_glob(value)
3849 self._validate_glob(value)
3844 self._branch_pattern = value or '*'
3850 self._branch_pattern = value or '*'
3845
3851
3846 @hybrid_property
3852 @hybrid_property
3847 def file_pattern(self):
3853 def file_pattern(self):
3848 return self._file_pattern or '*'
3854 return self._file_pattern or '*'
3849
3855
3850 @file_pattern.setter
3856 @file_pattern.setter
3851 def file_pattern(self, value):
3857 def file_pattern(self, value):
3852 self._validate_glob(value)
3858 self._validate_glob(value)
3853 self._file_pattern = value or '*'
3859 self._file_pattern = value or '*'
3854
3860
3855 def matches(self, branch, files_changed):
3861 def matches(self, branch, files_changed):
3856 """
3862 """
3857 Check if this review rule matches a branch/files in a pull request
3863 Check if this review rule matches a branch/files in a pull request
3858
3864
3859 :param branch: branch name for the commit
3865 :param branch: branch name for the commit
3860 :param files_changed: list of file paths changed in the pull request
3866 :param files_changed: list of file paths changed in the pull request
3861 """
3867 """
3862
3868
3863 branch = branch or ''
3869 branch = branch or ''
3864 files_changed = files_changed or []
3870 files_changed = files_changed or []
3865
3871
3866 branch_matches = True
3872 branch_matches = True
3867 if branch:
3873 if branch:
3868 branch_regex = re.compile('^' + glob2re(self.branch_pattern) + '$')
3874 branch_regex = re.compile('^' + glob2re(self.branch_pattern) + '$')
3869 branch_matches = bool(branch_regex.search(branch))
3875 branch_matches = bool(branch_regex.search(branch))
3870
3876
3871 files_matches = True
3877 files_matches = True
3872 if self.file_pattern != '*':
3878 if self.file_pattern != '*':
3873 files_matches = False
3879 files_matches = False
3874 file_regex = re.compile(glob2re(self.file_pattern))
3880 file_regex = re.compile(glob2re(self.file_pattern))
3875 for filename in files_changed:
3881 for filename in files_changed:
3876 if file_regex.search(filename):
3882 if file_regex.search(filename):
3877 files_matches = True
3883 files_matches = True
3878 break
3884 break
3879
3885
3880 return branch_matches and files_matches
3886 return branch_matches and files_matches
3881
3887
3882 @property
3888 @property
3883 def review_users(self):
3889 def review_users(self):
3884 """ Returns the users which this rule applies to """
3890 """ Returns the users which this rule applies to """
3885
3891
3886 users = set()
3892 users = set()
3887 users |= set([
3893 users |= set([
3888 rule_user.user for rule_user in self.rule_users
3894 rule_user.user for rule_user in self.rule_users
3889 if rule_user.user.active])
3895 if rule_user.user.active])
3890 users |= set(
3896 users |= set(
3891 member.user
3897 member.user
3892 for rule_user_group in self.rule_user_groups
3898 for rule_user_group in self.rule_user_groups
3893 for member in rule_user_group.users_group.members
3899 for member in rule_user_group.users_group.members
3894 if member.user.active
3900 if member.user.active
3895 )
3901 )
3896 return users
3902 return users
3897
3903
3898 def __repr__(self):
3904 def __repr__(self):
3899 return '<RepoReviewerRule(id=%r, repo=%r)>' % (
3905 return '<RepoReviewerRule(id=%r, repo=%r)>' % (
3900 self.repo_review_rule_id, self.repo)
3906 self.repo_review_rule_id, self.repo)
3901
3907
3902
3908
3903 class DbMigrateVersion(Base, BaseModel):
3909 class DbMigrateVersion(Base, BaseModel):
3904 __tablename__ = 'db_migrate_version'
3910 __tablename__ = 'db_migrate_version'
3905 __table_args__ = (
3911 __table_args__ = (
3906 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3912 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3907 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3913 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3908 )
3914 )
3909 repository_id = Column('repository_id', String(250), primary_key=True)
3915 repository_id = Column('repository_id', String(250), primary_key=True)
3910 repository_path = Column('repository_path', Text)
3916 repository_path = Column('repository_path', Text)
3911 version = Column('version', Integer)
3917 version = Column('version', Integer)
3912
3918
3913
3919
3914 class DbSession(Base, BaseModel):
3920 class DbSession(Base, BaseModel):
3915 __tablename__ = 'db_session'
3921 __tablename__ = 'db_session'
3916 __table_args__ = (
3922 __table_args__ = (
3917 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3923 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3918 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3924 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3919 )
3925 )
3920
3926
3921 def __repr__(self):
3927 def __repr__(self):
3922 return '<DB:DbSession({})>'.format(self.id)
3928 return '<DB:DbSession({})>'.format(self.id)
3923
3929
3924 id = Column('id', Integer())
3930 id = Column('id', Integer())
3925 namespace = Column('namespace', String(255), primary_key=True)
3931 namespace = Column('namespace', String(255), primary_key=True)
3926 accessed = Column('accessed', DateTime, nullable=False)
3932 accessed = Column('accessed', DateTime, nullable=False)
3927 created = Column('created', DateTime, nullable=False)
3933 created = Column('created', DateTime, nullable=False)
3928 data = Column('data', PickleType, nullable=False)
3934 data = Column('data', PickleType, nullable=False)
@@ -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