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