##// END OF EJS Templates
api: enable setting sync flag for user groups on create/edit.
marcink -
r2660:499461c1 default
parent child Browse files
Show More
@@ -1,110 +1,125 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2018 RhodeCode GmbH
3 # Copyright (C) 2010-2018 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_ADMIN_EMAIL
26 from rhodecode.tests import TEST_USER_ADMIN_EMAIL
27 from rhodecode.api.tests.utils import (
27 from rhodecode.api.tests.utils import (
28 build_data, api_call, assert_error, assert_ok, crash, jsonify)
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 ('owner_email', {'owner_email': TEST_USER_ADMIN_EMAIL}),
37 ('owner_email', {'owner_email': TEST_USER_ADMIN_EMAIL}),
38 ('active', {'active': False}),
38 ('active', {'active': False}),
39 ('active', {'active': True})
39 ('active', {'active': True}),
40 ('sync', {'sync': False}),
41 ('sync', {'sync': True})
40 ])
42 ])
41 def test_api_update_user_group(self, changing_attr, updates, user_util):
43 def test_api_update_user_group(self, changing_attr, updates, user_util):
42 user_group = user_util.create_user_group()
44 user_group = user_util.create_user_group()
43 group_name = user_group.users_group_name
45 group_name = user_group.users_group_name
44 expected_api_data = user_group.get_api_data()
46 expected_api_data = user_group.get_api_data()
45 expected_api_data.update(updates)
47 expected_api_data.update(updates)
46
48
47 id_, params = build_data(
49 id_, params = build_data(
48 self.apikey, 'update_user_group', usergroupid=group_name,
50 self.apikey, 'update_user_group', usergroupid=group_name,
49 **updates)
51 **updates)
50 response = api_call(self.app, params)
52 response = api_call(self.app, params)
51
53
54 # special case for sync
55 if changing_attr == 'sync' and updates['sync'] is False:
56 expected_api_data['sync'] = None
57 elif changing_attr == 'sync' and updates['sync'] is True:
58 expected_api_data['sync'] = 'manual_api'
59
52 expected = {
60 expected = {
53 'msg': 'updated user group ID:%s %s' % (
61 'msg': 'updated user group ID:%s %s' % (
54 user_group.users_group_id, user_group.users_group_name),
62 user_group.users_group_id, user_group.users_group_name),
55 'user_group': jsonify(expected_api_data)
63 'user_group': jsonify(expected_api_data)
56 }
64 }
57 assert_ok(id_, expected, given=response.body)
65 assert_ok(id_, expected, given=response.body)
58
66
59 @pytest.mark.parametrize("changing_attr, updates", [
67 @pytest.mark.parametrize("changing_attr, updates", [
60 # TODO: mikhail: decide if we need to test against the commented params
68 # TODO: mikhail: decide if we need to test against the commented params
61 # ('group_name', {'group_name': 'new_group_name'}),
69 # ('group_name', {'group_name': 'new_group_name'}),
62 # ('group_name', {'group_name': 'test_group_for_update'}),
70 # ('group_name', {'group_name': 'test_group_for_update'}),
63 # ('owner', {'owner': TEST_USER_REGULAR_LOGIN}),
71 # ('owner', {'owner': TEST_USER_REGULAR_LOGIN}),
64 ('owner_email', {'owner_email': TEST_USER_ADMIN_EMAIL}),
72 ('owner_email', {'owner_email': TEST_USER_ADMIN_EMAIL}),
65 ('active', {'active': False}),
73 ('active', {'active': False}),
66 ('active', {'active': True})
74 ('active', {'active': True}),
75 ('sync', {'sync': False}),
76 ('sync', {'sync': True})
67 ])
77 ])
68 def test_api_update_user_group_regular_user(
78 def test_api_update_user_group_regular_user(
69 self, changing_attr, updates, user_util):
79 self, changing_attr, updates, user_util):
70 user_group = user_util.create_user_group()
80 user_group = user_util.create_user_group()
71 group_name = user_group.users_group_name
81 group_name = user_group.users_group_name
72 expected_api_data = user_group.get_api_data()
82 expected_api_data = user_group.get_api_data()
73 expected_api_data.update(updates)
83 expected_api_data.update(updates)
74
84
75
76 # grant permission to this user
85 # grant permission to this user
77 user = UserModel().get_by_username(self.TEST_USER_LOGIN)
86 user = UserModel().get_by_username(self.TEST_USER_LOGIN)
78
87
79 user_util.grant_user_permission_to_user_group(
88 user_util.grant_user_permission_to_user_group(
80 user_group, user, 'usergroup.admin')
89 user_group, user, 'usergroup.admin')
81 id_, params = build_data(
90 id_, params = build_data(
82 self.apikey_regular, 'update_user_group',
91 self.apikey_regular, 'update_user_group',
83 usergroupid=group_name, **updates)
92 usergroupid=group_name, **updates)
84 response = api_call(self.app, params)
93 response = api_call(self.app, params)
94 # special case for sync
95 if changing_attr == 'sync' and updates['sync'] is False:
96 expected_api_data['sync'] = None
97 elif changing_attr == 'sync' and updates['sync'] is True:
98 expected_api_data['sync'] = 'manual_api'
99
85 expected = {
100 expected = {
86 'msg': 'updated user group ID:%s %s' % (
101 'msg': 'updated user group ID:%s %s' % (
87 user_group.users_group_id, user_group.users_group_name),
102 user_group.users_group_id, user_group.users_group_name),
88 'user_group': jsonify(expected_api_data)
103 'user_group': jsonify(expected_api_data)
89 }
104 }
90 assert_ok(id_, expected, given=response.body)
105 assert_ok(id_, expected, given=response.body)
91
106
92 def test_api_update_user_group_regular_user_no_permission(self, user_util):
107 def test_api_update_user_group_regular_user_no_permission(self, user_util):
93 user_group = user_util.create_user_group()
108 user_group = user_util.create_user_group()
94 group_name = user_group.users_group_name
109 group_name = user_group.users_group_name
95 id_, params = build_data(
110 id_, params = build_data(
96 self.apikey_regular, 'update_user_group', usergroupid=group_name)
111 self.apikey_regular, 'update_user_group', usergroupid=group_name)
97 response = api_call(self.app, params)
112 response = api_call(self.app, params)
98
113
99 expected = 'user group `%s` does not exist' % (group_name)
114 expected = 'user group `%s` does not exist' % (group_name)
100 assert_error(id_, expected, given=response.body)
115 assert_error(id_, expected, given=response.body)
101
116
102 @mock.patch.object(UserGroupModel, 'update', crash)
117 @mock.patch.object(UserGroupModel, 'update', crash)
103 def test_api_update_user_group_exception_occurred(self, user_util):
118 def test_api_update_user_group_exception_occurred(self, user_util):
104 user_group = user_util.create_user_group()
119 user_group = user_util.create_user_group()
105 group_name = user_group.users_group_name
120 group_name = user_group.users_group_name
106 id_, params = build_data(
121 id_, params = build_data(
107 self.apikey, 'update_user_group', usergroupid=group_name)
122 self.apikey, 'update_user_group', usergroupid=group_name)
108 response = api_call(self.app, params)
123 response = api_call(self.app, params)
109 expected = 'failed to update user group `%s`' % (group_name,)
124 expected = 'failed to update user group `%s`' % (group_name,)
110 assert_error(id_, expected, given=response.body)
125 assert_error(id_, expected, given=response.body)
@@ -1,829 +1,858 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2011-2018 RhodeCode GmbH
3 # Copyright (C) 2011-2018 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import logging
21 import logging
22
22
23 from rhodecode.api import (
23 from rhodecode.api import (
24 jsonrpc_method, JSONRPCError, JSONRPCForbidden, JSONRPCValidationError)
24 jsonrpc_method, JSONRPCError, JSONRPCForbidden, JSONRPCValidationError)
25 from rhodecode.api.utils import (
25 from rhodecode.api.utils import (
26 Optional, OAttr, store_update, has_superadmin_permission, get_origin,
26 Optional, OAttr, store_update, has_superadmin_permission, get_origin,
27 get_user_or_error, get_user_group_or_error, get_perm_or_error)
27 get_user_or_error, get_user_group_or_error, get_perm_or_error)
28 from rhodecode.lib import audit_logger
28 from rhodecode.lib import audit_logger
29 from rhodecode.lib.auth import HasUserGroupPermissionAnyApi, HasPermissionAnyApi
29 from rhodecode.lib.auth import HasUserGroupPermissionAnyApi, HasPermissionAnyApi
30 from rhodecode.lib.exceptions import UserGroupAssignedException
30 from rhodecode.lib.exceptions import UserGroupAssignedException
31 from rhodecode.model.db import Session
31 from rhodecode.model.db import Session
32 from rhodecode.model.scm import UserGroupList
32 from rhodecode.model.scm import UserGroupList
33 from rhodecode.model.user_group import UserGroupModel
33 from rhodecode.model.user_group import UserGroupModel
34 from rhodecode.model import validation_schema
34 from rhodecode.model import validation_schema
35 from rhodecode.model.validation_schema.schemas import user_group_schema
35 from rhodecode.model.validation_schema.schemas import user_group_schema
36
36
37 log = logging.getLogger(__name__)
37 log = logging.getLogger(__name__)
38
38
39
39
40 @jsonrpc_method()
40 @jsonrpc_method()
41 def get_user_group(request, apiuser, usergroupid):
41 def get_user_group(request, apiuser, usergroupid):
42 """
42 """
43 Returns the data of an existing user group.
43 Returns the data of an existing user group.
44
44
45 This command can only be run using an |authtoken| with admin rights to
45 This command can only be run using an |authtoken| with admin rights to
46 the specified repository.
46 the specified repository.
47
47
48 :param apiuser: This is filled automatically from the |authtoken|.
48 :param apiuser: This is filled automatically from the |authtoken|.
49 :type apiuser: AuthUser
49 :type apiuser: AuthUser
50 :param usergroupid: Set the user group from which to return data.
50 :param usergroupid: Set the user group from which to return data.
51 :type usergroupid: str or int
51 :type usergroupid: str or int
52
52
53 Example error output:
53 Example error output:
54
54
55 .. code-block:: bash
55 .. code-block:: bash
56
56
57 {
57 {
58 "error": null,
58 "error": null,
59 "id": <id>,
59 "id": <id>,
60 "result": {
60 "result": {
61 "active": true,
61 "active": true,
62 "group_description": "group description",
62 "group_description": "group description",
63 "group_name": "group name",
63 "group_name": "group name",
64 "permissions": [
64 "permissions": [
65 {
65 {
66 "name": "owner-name",
66 "name": "owner-name",
67 "origin": "owner",
67 "origin": "owner",
68 "permission": "usergroup.admin",
68 "permission": "usergroup.admin",
69 "type": "user"
69 "type": "user"
70 },
70 },
71 {
71 {
72 {
72 {
73 "name": "user name",
73 "name": "user name",
74 "origin": "permission",
74 "origin": "permission",
75 "permission": "usergroup.admin",
75 "permission": "usergroup.admin",
76 "type": "user"
76 "type": "user"
77 },
77 },
78 {
78 {
79 "name": "user group name",
79 "name": "user group name",
80 "origin": "permission",
80 "origin": "permission",
81 "permission": "usergroup.write",
81 "permission": "usergroup.write",
82 "type": "user_group"
82 "type": "user_group"
83 }
83 }
84 ],
84 ],
85 "permissions_summary": {
85 "permissions_summary": {
86 "repositories": {
86 "repositories": {
87 "aa-root-level-repo-1": "repository.admin"
87 "aa-root-level-repo-1": "repository.admin"
88 },
88 },
89 "repositories_groups": {}
89 "repositories_groups": {}
90 },
90 },
91 "owner": "owner name",
91 "owner": "owner name",
92 "users": [],
92 "users": [],
93 "users_group_id": 2
93 "users_group_id": 2
94 }
94 }
95 }
95 }
96
96
97 """
97 """
98
98
99 user_group = get_user_group_or_error(usergroupid)
99 user_group = get_user_group_or_error(usergroupid)
100 if not has_superadmin_permission(apiuser):
100 if not has_superadmin_permission(apiuser):
101 # check if we have at least read permission for this user group !
101 # check if we have at least read permission for this user group !
102 _perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin',)
102 _perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin',)
103 if not HasUserGroupPermissionAnyApi(*_perms)(
103 if not HasUserGroupPermissionAnyApi(*_perms)(
104 user=apiuser, user_group_name=user_group.users_group_name):
104 user=apiuser, user_group_name=user_group.users_group_name):
105 raise JSONRPCError('user group `%s` does not exist' % (
105 raise JSONRPCError('user group `%s` does not exist' % (
106 usergroupid,))
106 usergroupid,))
107
107
108 permissions = []
108 permissions = []
109 for _user in user_group.permissions():
109 for _user in user_group.permissions():
110 user_data = {
110 user_data = {
111 'name': _user.username,
111 'name': _user.username,
112 'permission': _user.permission,
112 'permission': _user.permission,
113 'origin': get_origin(_user),
113 'origin': get_origin(_user),
114 'type': "user",
114 'type': "user",
115 }
115 }
116 permissions.append(user_data)
116 permissions.append(user_data)
117
117
118 for _user_group in user_group.permission_user_groups():
118 for _user_group in user_group.permission_user_groups():
119 user_group_data = {
119 user_group_data = {
120 'name': _user_group.users_group_name,
120 'name': _user_group.users_group_name,
121 'permission': _user_group.permission,
121 'permission': _user_group.permission,
122 'origin': get_origin(_user_group),
122 'origin': get_origin(_user_group),
123 'type': "user_group",
123 'type': "user_group",
124 }
124 }
125 permissions.append(user_group_data)
125 permissions.append(user_group_data)
126
126
127 data = user_group.get_api_data()
127 data = user_group.get_api_data()
128 data["permissions"] = permissions
128 data["permissions"] = permissions
129 data["permissions_summary"] = UserGroupModel().get_perms_summary(
129 data["permissions_summary"] = UserGroupModel().get_perms_summary(
130 user_group.users_group_id)
130 user_group.users_group_id)
131 return data
131 return data
132
132
133
133
134 @jsonrpc_method()
134 @jsonrpc_method()
135 def get_user_groups(request, apiuser):
135 def get_user_groups(request, apiuser):
136 """
136 """
137 Lists all the existing user groups within RhodeCode.
137 Lists all the existing user groups within RhodeCode.
138
138
139 This command can only be run using an |authtoken| with admin rights to
139 This command can only be run using an |authtoken| with admin rights to
140 the specified repository.
140 the specified repository.
141
141
142 This command takes the following options:
142 This command takes the following options:
143
143
144 :param apiuser: This is filled automatically from the |authtoken|.
144 :param apiuser: This is filled automatically from the |authtoken|.
145 :type apiuser: AuthUser
145 :type apiuser: AuthUser
146
146
147 Example error output:
147 Example error output:
148
148
149 .. code-block:: bash
149 .. code-block:: bash
150
150
151 id : <id_given_in_input>
151 id : <id_given_in_input>
152 result : [<user_group_obj>,...]
152 result : [<user_group_obj>,...]
153 error : null
153 error : null
154 """
154 """
155
155
156 include_secrets = has_superadmin_permission(apiuser)
156 include_secrets = has_superadmin_permission(apiuser)
157
157
158 result = []
158 result = []
159 _perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin',)
159 _perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin',)
160 extras = {'user': apiuser}
160 extras = {'user': apiuser}
161 for user_group in UserGroupList(UserGroupModel().get_all(),
161 for user_group in UserGroupList(UserGroupModel().get_all(),
162 perm_set=_perms, extra_kwargs=extras):
162 perm_set=_perms, extra_kwargs=extras):
163 result.append(
163 result.append(
164 user_group.get_api_data(include_secrets=include_secrets))
164 user_group.get_api_data(include_secrets=include_secrets))
165 return result
165 return result
166
166
167
167
168 @jsonrpc_method()
168 @jsonrpc_method()
169 def create_user_group(
169 def create_user_group(
170 request, apiuser, group_name, description=Optional(''),
170 request, apiuser, group_name, description=Optional(''),
171 owner=Optional(OAttr('apiuser')), active=Optional(True)):
171 owner=Optional(OAttr('apiuser')), active=Optional(True),
172 sync=Optional(None)):
172 """
173 """
173 Creates a new user group.
174 Creates a new user group.
174
175
175 This command can only be run using an |authtoken| with admin rights to
176 This command can only be run using an |authtoken| with admin rights to
176 the specified repository.
177 the specified repository.
177
178
178 This command takes the following options:
179 This command takes the following options:
179
180
180 :param apiuser: This is filled automatically from the |authtoken|.
181 :param apiuser: This is filled automatically from the |authtoken|.
181 :type apiuser: AuthUser
182 :type apiuser: AuthUser
182 :param group_name: Set the name of the new user group.
183 :param group_name: Set the name of the new user group.
183 :type group_name: str
184 :type group_name: str
184 :param description: Give a description of the new user group.
185 :param description: Give a description of the new user group.
185 :type description: str
186 :type description: str
186 :param owner: Set the owner of the new user group.
187 :param owner: Set the owner of the new user group.
187 If not set, the owner is the |authtoken| user.
188 If not set, the owner is the |authtoken| user.
188 :type owner: Optional(str or int)
189 :type owner: Optional(str or int)
189 :param active: Set this group as active.
190 :param active: Set this group as active.
190 :type active: Optional(``True`` | ``False``)
191 :type active: Optional(``True`` | ``False``)
192 :param sync: Set enabled or disabled the automatically sync from
193 external authentication types like ldap.
194 :type sync: Optional(``True`` | ``False``)
191
195
192 Example output:
196 Example output:
193
197
194 .. code-block:: bash
198 .. code-block:: bash
195
199
196 id : <id_given_in_input>
200 id : <id_given_in_input>
197 result: {
201 result: {
198 "msg": "created new user group `<groupname>`",
202 "msg": "created new user group `<groupname>`",
199 "user_group": <user_group_object>
203 "user_group": <user_group_object>
200 }
204 }
201 error: null
205 error: null
202
206
203 Example error output:
207 Example error output:
204
208
205 .. code-block:: bash
209 .. code-block:: bash
206
210
207 id : <id_given_in_input>
211 id : <id_given_in_input>
208 result : null
212 result : null
209 error : {
213 error : {
210 "user group `<group name>` already exist"
214 "user group `<group name>` already exist"
211 or
215 or
212 "failed to create group `<group name>`"
216 "failed to create group `<group name>`"
213 }
217 }
214
218
215 """
219 """
216
220
217 if not has_superadmin_permission(apiuser):
221 if not has_superadmin_permission(apiuser):
218 if not HasPermissionAnyApi('hg.usergroup.create.true')(user=apiuser):
222 if not HasPermissionAnyApi('hg.usergroup.create.true')(user=apiuser):
219 raise JSONRPCForbidden()
223 raise JSONRPCForbidden()
220
224
221 if UserGroupModel().get_by_name(group_name):
225 if UserGroupModel().get_by_name(group_name):
222 raise JSONRPCError("user group `%s` already exist" % (group_name,))
226 raise JSONRPCError("user group `%s` already exist" % (group_name,))
223
227
224 if isinstance(owner, Optional):
228 if isinstance(owner, Optional):
225 owner = apiuser.user_id
229 owner = apiuser.user_id
226
230
227 owner = get_user_or_error(owner)
231 owner = get_user_or_error(owner)
228 active = Optional.extract(active)
232 active = Optional.extract(active)
229 description = Optional.extract(description)
233 description = Optional.extract(description)
234 sync = Optional.extract(sync)
235
236 # set the sync option based on group_data
237 group_data = None
238 if sync:
239 group_data = {
240 'extern_type': 'manual_api',
241 'extern_type_set_by': apiuser.username
242 }
230
243
231 schema = user_group_schema.UserGroupSchema().bind(
244 schema = user_group_schema.UserGroupSchema().bind(
232 # user caller
245 # user caller
233 user=apiuser)
246 user=apiuser)
234 try:
247 try:
235 schema_data = schema.deserialize(dict(
248 schema_data = schema.deserialize(dict(
236 user_group_name=group_name,
249 user_group_name=group_name,
237 user_group_description=description,
250 user_group_description=description,
238 user_group_owner=owner.username,
251 user_group_owner=owner.username,
239 user_group_active=active,
252 user_group_active=active,
240 ))
253 ))
241 except validation_schema.Invalid as err:
254 except validation_schema.Invalid as err:
242 raise JSONRPCValidationError(colander_exc=err)
255 raise JSONRPCValidationError(colander_exc=err)
243
256
244 try:
257 try:
245 user_group = UserGroupModel().create(
258 user_group = UserGroupModel().create(
246 name=schema_data['user_group_name'],
259 name=schema_data['user_group_name'],
247 description=schema_data['user_group_description'],
260 description=schema_data['user_group_description'],
248 owner=owner,
261 owner=owner,
249 active=schema_data['user_group_active'])
262 active=schema_data['user_group_active'], group_data=group_data)
250 Session().flush()
263 Session().flush()
251 creation_data = user_group.get_api_data()
264 creation_data = user_group.get_api_data()
252 audit_logger.store_api(
265 audit_logger.store_api(
253 'user_group.create', action_data={'data': creation_data},
266 'user_group.create', action_data={'data': creation_data},
254 user=apiuser)
267 user=apiuser)
255 Session().commit()
268 Session().commit()
256 return {
269 return {
257 'msg': 'created new user group `%s`' % group_name,
270 'msg': 'created new user group `%s`' % group_name,
258 'user_group': creation_data
271 'user_group': creation_data
259 }
272 }
260 except Exception:
273 except Exception:
261 log.exception("Error occurred during creation of user group")
274 log.exception("Error occurred during creation of user group")
262 raise JSONRPCError('failed to create group `%s`' % (group_name,))
275 raise JSONRPCError('failed to create group `%s`' % (group_name,))
263
276
264
277
265 @jsonrpc_method()
278 @jsonrpc_method()
266 def update_user_group(request, apiuser, usergroupid, group_name=Optional(''),
279 def update_user_group(request, apiuser, usergroupid, group_name=Optional(''),
267 description=Optional(''), owner=Optional(None),
280 description=Optional(''), owner=Optional(None),
268 active=Optional(True)):
281 active=Optional(True), sync=Optional(None)):
269 """
282 """
270 Updates the specified `user group` with the details provided.
283 Updates the specified `user group` with the details provided.
271
284
272 This command can only be run using an |authtoken| with admin rights to
285 This command can only be run using an |authtoken| with admin rights to
273 the specified repository.
286 the specified repository.
274
287
275 :param apiuser: This is filled automatically from the |authtoken|.
288 :param apiuser: This is filled automatically from the |authtoken|.
276 :type apiuser: AuthUser
289 :type apiuser: AuthUser
277 :param usergroupid: Set the id of the `user group` to update.
290 :param usergroupid: Set the id of the `user group` to update.
278 :type usergroupid: str or int
291 :type usergroupid: str or int
279 :param group_name: Set the new name the `user group`
292 :param group_name: Set the new name the `user group`
280 :type group_name: str
293 :type group_name: str
281 :param description: Give a description for the `user group`
294 :param description: Give a description for the `user group`
282 :type description: str
295 :type description: str
283 :param owner: Set the owner of the `user group`.
296 :param owner: Set the owner of the `user group`.
284 :type owner: Optional(str or int)
297 :type owner: Optional(str or int)
285 :param active: Set the group as active.
298 :param active: Set the group as active.
286 :type active: Optional(``True`` | ``False``)
299 :type active: Optional(``True`` | ``False``)
300 :param sync: Set enabled or disabled the automatically sync from
301 external authentication types like ldap.
302 :type sync: Optional(``True`` | ``False``)
287
303
288 Example output:
304 Example output:
289
305
290 .. code-block:: bash
306 .. code-block:: bash
291
307
292 id : <id_given_in_input>
308 id : <id_given_in_input>
293 result : {
309 result : {
294 "msg": 'updated user group ID:<user group id> <user group name>',
310 "msg": 'updated user group ID:<user group id> <user group name>',
295 "user_group": <user_group_object>
311 "user_group": <user_group_object>
296 }
312 }
297 error : null
313 error : null
298
314
299 Example error output:
315 Example error output:
300
316
301 .. code-block:: bash
317 .. code-block:: bash
302
318
303 id : <id_given_in_input>
319 id : <id_given_in_input>
304 result : null
320 result : null
305 error : {
321 error : {
306 "failed to update user group `<user group name>`"
322 "failed to update user group `<user group name>`"
307 }
323 }
308
324
309 """
325 """
310
326
311 user_group = get_user_group_or_error(usergroupid)
327 user_group = get_user_group_or_error(usergroupid)
312 include_secrets = False
328 include_secrets = False
313 if not has_superadmin_permission(apiuser):
329 if not has_superadmin_permission(apiuser):
314 # check if we have admin permission for this user group !
330 # check if we have admin permission for this user group !
315 _perms = ('usergroup.admin',)
331 _perms = ('usergroup.admin',)
316 if not HasUserGroupPermissionAnyApi(*_perms)(
332 if not HasUserGroupPermissionAnyApi(*_perms)(
317 user=apiuser, user_group_name=user_group.users_group_name):
333 user=apiuser, user_group_name=user_group.users_group_name):
318 raise JSONRPCError(
334 raise JSONRPCError(
319 'user group `%s` does not exist' % (usergroupid,))
335 'user group `%s` does not exist' % (usergroupid,))
320 else:
336 else:
321 include_secrets = True
337 include_secrets = True
322
338
323 if not isinstance(owner, Optional):
339 if not isinstance(owner, Optional):
324 owner = get_user_or_error(owner)
340 owner = get_user_or_error(owner)
325
341
326 old_data = user_group.get_api_data()
342 old_data = user_group.get_api_data()
327 updates = {}
343 updates = {}
328 store_update(updates, group_name, 'users_group_name')
344 store_update(updates, group_name, 'users_group_name')
329 store_update(updates, description, 'user_group_description')
345 store_update(updates, description, 'user_group_description')
330 store_update(updates, owner, 'user')
346 store_update(updates, owner, 'user')
331 store_update(updates, active, 'users_group_active')
347 store_update(updates, active, 'users_group_active')
348
349 sync = Optional.extract(sync)
350 group_data = None
351 if sync is True:
352 group_data = {
353 'extern_type': 'manual_api',
354 'extern_type_set_by': apiuser.username
355 }
356 if sync is False:
357 group_data = user_group.group_data
358 if group_data and "extern_type" in group_data:
359 del group_data["extern_type"]
360
332 try:
361 try:
333 UserGroupModel().update(user_group, updates)
362 UserGroupModel().update(user_group, updates, group_data=group_data)
334 audit_logger.store_api(
363 audit_logger.store_api(
335 'user_group.edit', action_data={'old_data': old_data},
364 'user_group.edit', action_data={'old_data': old_data},
336 user=apiuser)
365 user=apiuser)
337 Session().commit()
366 Session().commit()
338 return {
367 return {
339 'msg': 'updated user group ID:%s %s' % (
368 'msg': 'updated user group ID:%s %s' % (
340 user_group.users_group_id, user_group.users_group_name),
369 user_group.users_group_id, user_group.users_group_name),
341 'user_group': user_group.get_api_data(
370 'user_group': user_group.get_api_data(
342 include_secrets=include_secrets)
371 include_secrets=include_secrets)
343 }
372 }
344 except Exception:
373 except Exception:
345 log.exception("Error occurred during update of user group")
374 log.exception("Error occurred during update of user group")
346 raise JSONRPCError(
375 raise JSONRPCError(
347 'failed to update user group `%s`' % (usergroupid,))
376 'failed to update user group `%s`' % (usergroupid,))
348
377
349
378
350 @jsonrpc_method()
379 @jsonrpc_method()
351 def delete_user_group(request, apiuser, usergroupid):
380 def delete_user_group(request, apiuser, usergroupid):
352 """
381 """
353 Deletes the specified `user group`.
382 Deletes the specified `user group`.
354
383
355 This command can only be run using an |authtoken| with admin rights to
384 This command can only be run using an |authtoken| with admin rights to
356 the specified repository.
385 the specified repository.
357
386
358 This command takes the following options:
387 This command takes the following options:
359
388
360 :param apiuser: filled automatically from apikey
389 :param apiuser: filled automatically from apikey
361 :type apiuser: AuthUser
390 :type apiuser: AuthUser
362 :param usergroupid:
391 :param usergroupid:
363 :type usergroupid: int
392 :type usergroupid: int
364
393
365 Example output:
394 Example output:
366
395
367 .. code-block:: bash
396 .. code-block:: bash
368
397
369 id : <id_given_in_input>
398 id : <id_given_in_input>
370 result : {
399 result : {
371 "msg": "deleted user group ID:<user_group_id> <user_group_name>"
400 "msg": "deleted user group ID:<user_group_id> <user_group_name>"
372 }
401 }
373 error : null
402 error : null
374
403
375 Example error output:
404 Example error output:
376
405
377 .. code-block:: bash
406 .. code-block:: bash
378
407
379 id : <id_given_in_input>
408 id : <id_given_in_input>
380 result : null
409 result : null
381 error : {
410 error : {
382 "failed to delete user group ID:<user_group_id> <user_group_name>"
411 "failed to delete user group ID:<user_group_id> <user_group_name>"
383 or
412 or
384 "RepoGroup assigned to <repo_groups_list>"
413 "RepoGroup assigned to <repo_groups_list>"
385 }
414 }
386
415
387 """
416 """
388
417
389 user_group = get_user_group_or_error(usergroupid)
418 user_group = get_user_group_or_error(usergroupid)
390 if not has_superadmin_permission(apiuser):
419 if not has_superadmin_permission(apiuser):
391 # check if we have admin permission for this user group !
420 # check if we have admin permission for this user group !
392 _perms = ('usergroup.admin',)
421 _perms = ('usergroup.admin',)
393 if not HasUserGroupPermissionAnyApi(*_perms)(
422 if not HasUserGroupPermissionAnyApi(*_perms)(
394 user=apiuser, user_group_name=user_group.users_group_name):
423 user=apiuser, user_group_name=user_group.users_group_name):
395 raise JSONRPCError(
424 raise JSONRPCError(
396 'user group `%s` does not exist' % (usergroupid,))
425 'user group `%s` does not exist' % (usergroupid,))
397
426
398 old_data = user_group.get_api_data()
427 old_data = user_group.get_api_data()
399 try:
428 try:
400 UserGroupModel().delete(user_group)
429 UserGroupModel().delete(user_group)
401 audit_logger.store_api(
430 audit_logger.store_api(
402 'user_group.delete', action_data={'old_data': old_data},
431 'user_group.delete', action_data={'old_data': old_data},
403 user=apiuser)
432 user=apiuser)
404 Session().commit()
433 Session().commit()
405 return {
434 return {
406 'msg': 'deleted user group ID:%s %s' % (
435 'msg': 'deleted user group ID:%s %s' % (
407 user_group.users_group_id, user_group.users_group_name),
436 user_group.users_group_id, user_group.users_group_name),
408 'user_group': None
437 'user_group': None
409 }
438 }
410 except UserGroupAssignedException as e:
439 except UserGroupAssignedException as e:
411 log.exception("UserGroupAssigned error")
440 log.exception("UserGroupAssigned error")
412 raise JSONRPCError(str(e))
441 raise JSONRPCError(str(e))
413 except Exception:
442 except Exception:
414 log.exception("Error occurred during deletion of user group")
443 log.exception("Error occurred during deletion of user group")
415 raise JSONRPCError(
444 raise JSONRPCError(
416 'failed to delete user group ID:%s %s' %(
445 'failed to delete user group ID:%s %s' %(
417 user_group.users_group_id, user_group.users_group_name))
446 user_group.users_group_id, user_group.users_group_name))
418
447
419
448
420 @jsonrpc_method()
449 @jsonrpc_method()
421 def add_user_to_user_group(request, apiuser, usergroupid, userid):
450 def add_user_to_user_group(request, apiuser, usergroupid, userid):
422 """
451 """
423 Adds a user to a `user group`. If the user already exists in the group
452 Adds a user to a `user group`. If the user already exists in the group
424 this command will return false.
453 this command will return false.
425
454
426 This command can only be run using an |authtoken| with admin rights to
455 This command can only be run using an |authtoken| with admin rights to
427 the specified user group.
456 the specified user group.
428
457
429 This command takes the following options:
458 This command takes the following options:
430
459
431 :param apiuser: This is filled automatically from the |authtoken|.
460 :param apiuser: This is filled automatically from the |authtoken|.
432 :type apiuser: AuthUser
461 :type apiuser: AuthUser
433 :param usergroupid: Set the name of the `user group` to which a
462 :param usergroupid: Set the name of the `user group` to which a
434 user will be added.
463 user will be added.
435 :type usergroupid: int
464 :type usergroupid: int
436 :param userid: Set the `user_id` of the user to add to the group.
465 :param userid: Set the `user_id` of the user to add to the group.
437 :type userid: int
466 :type userid: int
438
467
439 Example output:
468 Example output:
440
469
441 .. code-block:: bash
470 .. code-block:: bash
442
471
443 id : <id_given_in_input>
472 id : <id_given_in_input>
444 result : {
473 result : {
445 "success": True|False # depends on if member is in group
474 "success": True|False # depends on if member is in group
446 "msg": "added member `<username>` to user group `<groupname>` |
475 "msg": "added member `<username>` to user group `<groupname>` |
447 User is already in that group"
476 User is already in that group"
448
477
449 }
478 }
450 error : null
479 error : null
451
480
452 Example error output:
481 Example error output:
453
482
454 .. code-block:: bash
483 .. code-block:: bash
455
484
456 id : <id_given_in_input>
485 id : <id_given_in_input>
457 result : null
486 result : null
458 error : {
487 error : {
459 "failed to add member to user group `<user_group_name>`"
488 "failed to add member to user group `<user_group_name>`"
460 }
489 }
461
490
462 """
491 """
463
492
464 user = get_user_or_error(userid)
493 user = get_user_or_error(userid)
465 user_group = get_user_group_or_error(usergroupid)
494 user_group = get_user_group_or_error(usergroupid)
466 if not has_superadmin_permission(apiuser):
495 if not has_superadmin_permission(apiuser):
467 # check if we have admin permission for this user group !
496 # check if we have admin permission for this user group !
468 _perms = ('usergroup.admin',)
497 _perms = ('usergroup.admin',)
469 if not HasUserGroupPermissionAnyApi(*_perms)(
498 if not HasUserGroupPermissionAnyApi(*_perms)(
470 user=apiuser, user_group_name=user_group.users_group_name):
499 user=apiuser, user_group_name=user_group.users_group_name):
471 raise JSONRPCError('user group `%s` does not exist' % (
500 raise JSONRPCError('user group `%s` does not exist' % (
472 usergroupid,))
501 usergroupid,))
473
502
474 old_values = user_group.get_api_data()
503 old_values = user_group.get_api_data()
475 try:
504 try:
476 ugm = UserGroupModel().add_user_to_group(user_group, user)
505 ugm = UserGroupModel().add_user_to_group(user_group, user)
477 success = True if ugm is not True else False
506 success = True if ugm is not True else False
478 msg = 'added member `%s` to user group `%s`' % (
507 msg = 'added member `%s` to user group `%s`' % (
479 user.username, user_group.users_group_name
508 user.username, user_group.users_group_name
480 )
509 )
481 msg = msg if success else 'User is already in that group'
510 msg = msg if success else 'User is already in that group'
482 if success:
511 if success:
483 user_data = user.get_api_data()
512 user_data = user.get_api_data()
484 audit_logger.store_api(
513 audit_logger.store_api(
485 'user_group.edit.member.add',
514 'user_group.edit.member.add',
486 action_data={'user': user_data, 'old_data': old_values},
515 action_data={'user': user_data, 'old_data': old_values},
487 user=apiuser)
516 user=apiuser)
488
517
489 Session().commit()
518 Session().commit()
490
519
491 return {
520 return {
492 'success': success,
521 'success': success,
493 'msg': msg
522 'msg': msg
494 }
523 }
495 except Exception:
524 except Exception:
496 log.exception("Error occurred during adding a member to user group")
525 log.exception("Error occurred during adding a member to user group")
497 raise JSONRPCError(
526 raise JSONRPCError(
498 'failed to add member to user group `%s`' % (
527 'failed to add member to user group `%s`' % (
499 user_group.users_group_name,
528 user_group.users_group_name,
500 )
529 )
501 )
530 )
502
531
503
532
504 @jsonrpc_method()
533 @jsonrpc_method()
505 def remove_user_from_user_group(request, apiuser, usergroupid, userid):
534 def remove_user_from_user_group(request, apiuser, usergroupid, userid):
506 """
535 """
507 Removes a user from a user group.
536 Removes a user from a user group.
508
537
509 * If the specified user is not in the group, this command will return
538 * If the specified user is not in the group, this command will return
510 `false`.
539 `false`.
511
540
512 This command can only be run using an |authtoken| with admin rights to
541 This command can only be run using an |authtoken| with admin rights to
513 the specified user group.
542 the specified user group.
514
543
515 :param apiuser: This is filled automatically from the |authtoken|.
544 :param apiuser: This is filled automatically from the |authtoken|.
516 :type apiuser: AuthUser
545 :type apiuser: AuthUser
517 :param usergroupid: Sets the user group name.
546 :param usergroupid: Sets the user group name.
518 :type usergroupid: str or int
547 :type usergroupid: str or int
519 :param userid: The user you wish to remove from |RCE|.
548 :param userid: The user you wish to remove from |RCE|.
520 :type userid: str or int
549 :type userid: str or int
521
550
522 Example output:
551 Example output:
523
552
524 .. code-block:: bash
553 .. code-block:: bash
525
554
526 id : <id_given_in_input>
555 id : <id_given_in_input>
527 result: {
556 result: {
528 "success": True|False, # depends on if member is in group
557 "success": True|False, # depends on if member is in group
529 "msg": "removed member <username> from user group <groupname> |
558 "msg": "removed member <username> from user group <groupname> |
530 User wasn't in group"
559 User wasn't in group"
531 }
560 }
532 error: null
561 error: null
533
562
534 """
563 """
535
564
536 user = get_user_or_error(userid)
565 user = get_user_or_error(userid)
537 user_group = get_user_group_or_error(usergroupid)
566 user_group = get_user_group_or_error(usergroupid)
538 if not has_superadmin_permission(apiuser):
567 if not has_superadmin_permission(apiuser):
539 # check if we have admin permission for this user group !
568 # check if we have admin permission for this user group !
540 _perms = ('usergroup.admin',)
569 _perms = ('usergroup.admin',)
541 if not HasUserGroupPermissionAnyApi(*_perms)(
570 if not HasUserGroupPermissionAnyApi(*_perms)(
542 user=apiuser, user_group_name=user_group.users_group_name):
571 user=apiuser, user_group_name=user_group.users_group_name):
543 raise JSONRPCError(
572 raise JSONRPCError(
544 'user group `%s` does not exist' % (usergroupid,))
573 'user group `%s` does not exist' % (usergroupid,))
545
574
546 old_values = user_group.get_api_data()
575 old_values = user_group.get_api_data()
547 try:
576 try:
548 success = UserGroupModel().remove_user_from_group(user_group, user)
577 success = UserGroupModel().remove_user_from_group(user_group, user)
549 msg = 'removed member `%s` from user group `%s`' % (
578 msg = 'removed member `%s` from user group `%s`' % (
550 user.username, user_group.users_group_name
579 user.username, user_group.users_group_name
551 )
580 )
552 msg = msg if success else "User wasn't in group"
581 msg = msg if success else "User wasn't in group"
553 if success:
582 if success:
554 user_data = user.get_api_data()
583 user_data = user.get_api_data()
555 audit_logger.store_api(
584 audit_logger.store_api(
556 'user_group.edit.member.delete',
585 'user_group.edit.member.delete',
557 action_data={'user': user_data, 'old_data': old_values},
586 action_data={'user': user_data, 'old_data': old_values},
558 user=apiuser)
587 user=apiuser)
559
588
560 Session().commit()
589 Session().commit()
561 return {'success': success, 'msg': msg}
590 return {'success': success, 'msg': msg}
562 except Exception:
591 except Exception:
563 log.exception("Error occurred during removing an member from user group")
592 log.exception("Error occurred during removing an member from user group")
564 raise JSONRPCError(
593 raise JSONRPCError(
565 'failed to remove member from user group `%s`' % (
594 'failed to remove member from user group `%s`' % (
566 user_group.users_group_name,
595 user_group.users_group_name,
567 )
596 )
568 )
597 )
569
598
570
599
571 @jsonrpc_method()
600 @jsonrpc_method()
572 def grant_user_permission_to_user_group(
601 def grant_user_permission_to_user_group(
573 request, apiuser, usergroupid, userid, perm):
602 request, apiuser, usergroupid, userid, perm):
574 """
603 """
575 Set permissions for a user in a user group.
604 Set permissions for a user in a user group.
576
605
577 :param apiuser: This is filled automatically from the |authtoken|.
606 :param apiuser: This is filled automatically from the |authtoken|.
578 :type apiuser: AuthUser
607 :type apiuser: AuthUser
579 :param usergroupid: Set the user group to edit permissions on.
608 :param usergroupid: Set the user group to edit permissions on.
580 :type usergroupid: str or int
609 :type usergroupid: str or int
581 :param userid: Set the user from whom you wish to set permissions.
610 :param userid: Set the user from whom you wish to set permissions.
582 :type userid: str
611 :type userid: str
583 :param perm: (usergroup.(none|read|write|admin))
612 :param perm: (usergroup.(none|read|write|admin))
584 :type perm: str
613 :type perm: str
585
614
586 Example output:
615 Example output:
587
616
588 .. code-block:: bash
617 .. code-block:: bash
589
618
590 id : <id_given_in_input>
619 id : <id_given_in_input>
591 result : {
620 result : {
592 "msg": "Granted perm: `<perm_name>` for user: `<username>` in user group: `<user_group_name>`",
621 "msg": "Granted perm: `<perm_name>` for user: `<username>` in user group: `<user_group_name>`",
593 "success": true
622 "success": true
594 }
623 }
595 error : null
624 error : null
596 """
625 """
597
626
598 user_group = get_user_group_or_error(usergroupid)
627 user_group = get_user_group_or_error(usergroupid)
599
628
600 if not has_superadmin_permission(apiuser):
629 if not has_superadmin_permission(apiuser):
601 # check if we have admin permission for this user group !
630 # check if we have admin permission for this user group !
602 _perms = ('usergroup.admin',)
631 _perms = ('usergroup.admin',)
603 if not HasUserGroupPermissionAnyApi(*_perms)(
632 if not HasUserGroupPermissionAnyApi(*_perms)(
604 user=apiuser, user_group_name=user_group.users_group_name):
633 user=apiuser, user_group_name=user_group.users_group_name):
605 raise JSONRPCError(
634 raise JSONRPCError(
606 'user group `%s` does not exist' % (usergroupid,))
635 'user group `%s` does not exist' % (usergroupid,))
607
636
608 user = get_user_or_error(userid)
637 user = get_user_or_error(userid)
609 perm = get_perm_or_error(perm, prefix='usergroup.')
638 perm = get_perm_or_error(perm, prefix='usergroup.')
610
639
611 try:
640 try:
612 UserGroupModel().grant_user_permission(
641 UserGroupModel().grant_user_permission(
613 user_group=user_group, user=user, perm=perm)
642 user_group=user_group, user=user, perm=perm)
614 Session().commit()
643 Session().commit()
615 return {
644 return {
616 'msg':
645 'msg':
617 'Granted perm: `%s` for user: `%s` in user group: `%s`' % (
646 'Granted perm: `%s` for user: `%s` in user group: `%s`' % (
618 perm.permission_name, user.username,
647 perm.permission_name, user.username,
619 user_group.users_group_name
648 user_group.users_group_name
620 ),
649 ),
621 'success': True
650 'success': True
622 }
651 }
623 except Exception:
652 except Exception:
624 log.exception("Error occurred during editing permissions "
653 log.exception("Error occurred during editing permissions "
625 "for user in user group")
654 "for user in user group")
626 raise JSONRPCError(
655 raise JSONRPCError(
627 'failed to edit permission for user: '
656 'failed to edit permission for user: '
628 '`%s` in user group: `%s`' % (
657 '`%s` in user group: `%s`' % (
629 userid, user_group.users_group_name))
658 userid, user_group.users_group_name))
630
659
631
660
632 @jsonrpc_method()
661 @jsonrpc_method()
633 def revoke_user_permission_from_user_group(
662 def revoke_user_permission_from_user_group(
634 request, apiuser, usergroupid, userid):
663 request, apiuser, usergroupid, userid):
635 """
664 """
636 Revoke a users permissions in a user group.
665 Revoke a users permissions in a user group.
637
666
638 :param apiuser: This is filled automatically from the |authtoken|.
667 :param apiuser: This is filled automatically from the |authtoken|.
639 :type apiuser: AuthUser
668 :type apiuser: AuthUser
640 :param usergroupid: Set the user group from which to revoke the user
669 :param usergroupid: Set the user group from which to revoke the user
641 permissions.
670 permissions.
642 :type: usergroupid: str or int
671 :type: usergroupid: str or int
643 :param userid: Set the userid of the user whose permissions will be
672 :param userid: Set the userid of the user whose permissions will be
644 revoked.
673 revoked.
645 :type userid: str
674 :type userid: str
646
675
647 Example output:
676 Example output:
648
677
649 .. code-block:: bash
678 .. code-block:: bash
650
679
651 id : <id_given_in_input>
680 id : <id_given_in_input>
652 result : {
681 result : {
653 "msg": "Revoked perm for user: `<username>` in user group: `<user_group_name>`",
682 "msg": "Revoked perm for user: `<username>` in user group: `<user_group_name>`",
654 "success": true
683 "success": true
655 }
684 }
656 error : null
685 error : null
657 """
686 """
658
687
659 user_group = get_user_group_or_error(usergroupid)
688 user_group = get_user_group_or_error(usergroupid)
660
689
661 if not has_superadmin_permission(apiuser):
690 if not has_superadmin_permission(apiuser):
662 # check if we have admin permission for this user group !
691 # check if we have admin permission for this user group !
663 _perms = ('usergroup.admin',)
692 _perms = ('usergroup.admin',)
664 if not HasUserGroupPermissionAnyApi(*_perms)(
693 if not HasUserGroupPermissionAnyApi(*_perms)(
665 user=apiuser, user_group_name=user_group.users_group_name):
694 user=apiuser, user_group_name=user_group.users_group_name):
666 raise JSONRPCError(
695 raise JSONRPCError(
667 'user group `%s` does not exist' % (usergroupid,))
696 'user group `%s` does not exist' % (usergroupid,))
668
697
669 user = get_user_or_error(userid)
698 user = get_user_or_error(userid)
670
699
671 try:
700 try:
672 UserGroupModel().revoke_user_permission(
701 UserGroupModel().revoke_user_permission(
673 user_group=user_group, user=user)
702 user_group=user_group, user=user)
674 Session().commit()
703 Session().commit()
675 return {
704 return {
676 'msg': 'Revoked perm for user: `%s` in user group: `%s`' % (
705 'msg': 'Revoked perm for user: `%s` in user group: `%s`' % (
677 user.username, user_group.users_group_name
706 user.username, user_group.users_group_name
678 ),
707 ),
679 'success': True
708 'success': True
680 }
709 }
681 except Exception:
710 except Exception:
682 log.exception("Error occurred during editing permissions "
711 log.exception("Error occurred during editing permissions "
683 "for user in user group")
712 "for user in user group")
684 raise JSONRPCError(
713 raise JSONRPCError(
685 'failed to edit permission for user: `%s` in user group: `%s`'
714 'failed to edit permission for user: `%s` in user group: `%s`'
686 % (userid, user_group.users_group_name))
715 % (userid, user_group.users_group_name))
687
716
688
717
689 @jsonrpc_method()
718 @jsonrpc_method()
690 def grant_user_group_permission_to_user_group(
719 def grant_user_group_permission_to_user_group(
691 request, apiuser, usergroupid, sourceusergroupid, perm):
720 request, apiuser, usergroupid, sourceusergroupid, perm):
692 """
721 """
693 Give one user group permissions to another user group.
722 Give one user group permissions to another user group.
694
723
695 :param apiuser: This is filled automatically from the |authtoken|.
724 :param apiuser: This is filled automatically from the |authtoken|.
696 :type apiuser: AuthUser
725 :type apiuser: AuthUser
697 :param usergroupid: Set the user group on which to edit permissions.
726 :param usergroupid: Set the user group on which to edit permissions.
698 :type usergroupid: str or int
727 :type usergroupid: str or int
699 :param sourceusergroupid: Set the source user group to which
728 :param sourceusergroupid: Set the source user group to which
700 access/permissions will be granted.
729 access/permissions will be granted.
701 :type sourceusergroupid: str or int
730 :type sourceusergroupid: str or int
702 :param perm: (usergroup.(none|read|write|admin))
731 :param perm: (usergroup.(none|read|write|admin))
703 :type perm: str
732 :type perm: str
704
733
705 Example output:
734 Example output:
706
735
707 .. code-block:: bash
736 .. code-block:: bash
708
737
709 id : <id_given_in_input>
738 id : <id_given_in_input>
710 result : {
739 result : {
711 "msg": "Granted perm: `<perm_name>` for user group: `<source_user_group_name>` in user group: `<user_group_name>`",
740 "msg": "Granted perm: `<perm_name>` for user group: `<source_user_group_name>` in user group: `<user_group_name>`",
712 "success": true
741 "success": true
713 }
742 }
714 error : null
743 error : null
715 """
744 """
716
745
717 user_group = get_user_group_or_error(sourceusergroupid)
746 user_group = get_user_group_or_error(sourceusergroupid)
718 target_user_group = get_user_group_or_error(usergroupid)
747 target_user_group = get_user_group_or_error(usergroupid)
719 perm = get_perm_or_error(perm, prefix='usergroup.')
748 perm = get_perm_or_error(perm, prefix='usergroup.')
720
749
721 if not has_superadmin_permission(apiuser):
750 if not has_superadmin_permission(apiuser):
722 # check if we have admin permission for this user group !
751 # check if we have admin permission for this user group !
723 _perms = ('usergroup.admin',)
752 _perms = ('usergroup.admin',)
724 if not HasUserGroupPermissionAnyApi(*_perms)(
753 if not HasUserGroupPermissionAnyApi(*_perms)(
725 user=apiuser,
754 user=apiuser,
726 user_group_name=target_user_group.users_group_name):
755 user_group_name=target_user_group.users_group_name):
727 raise JSONRPCError(
756 raise JSONRPCError(
728 'to user group `%s` does not exist' % (usergroupid,))
757 'to user group `%s` does not exist' % (usergroupid,))
729
758
730 # check if we have at least read permission for source user group !
759 # check if we have at least read permission for source user group !
731 _perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin',)
760 _perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin',)
732 if not HasUserGroupPermissionAnyApi(*_perms)(
761 if not HasUserGroupPermissionAnyApi(*_perms)(
733 user=apiuser, user_group_name=user_group.users_group_name):
762 user=apiuser, user_group_name=user_group.users_group_name):
734 raise JSONRPCError(
763 raise JSONRPCError(
735 'user group `%s` does not exist' % (sourceusergroupid,))
764 'user group `%s` does not exist' % (sourceusergroupid,))
736
765
737 try:
766 try:
738 UserGroupModel().grant_user_group_permission(
767 UserGroupModel().grant_user_group_permission(
739 target_user_group=target_user_group,
768 target_user_group=target_user_group,
740 user_group=user_group, perm=perm)
769 user_group=user_group, perm=perm)
741 Session().commit()
770 Session().commit()
742
771
743 return {
772 return {
744 'msg': 'Granted perm: `%s` for user group: `%s` '
773 'msg': 'Granted perm: `%s` for user group: `%s` '
745 'in user group: `%s`' % (
774 'in user group: `%s`' % (
746 perm.permission_name, user_group.users_group_name,
775 perm.permission_name, user_group.users_group_name,
747 target_user_group.users_group_name
776 target_user_group.users_group_name
748 ),
777 ),
749 'success': True
778 'success': True
750 }
779 }
751 except Exception:
780 except Exception:
752 log.exception("Error occurred during editing permissions "
781 log.exception("Error occurred during editing permissions "
753 "for user group in user group")
782 "for user group in user group")
754 raise JSONRPCError(
783 raise JSONRPCError(
755 'failed to edit permission for user group: `%s` in '
784 'failed to edit permission for user group: `%s` in '
756 'user group: `%s`' % (
785 'user group: `%s`' % (
757 sourceusergroupid, target_user_group.users_group_name
786 sourceusergroupid, target_user_group.users_group_name
758 )
787 )
759 )
788 )
760
789
761
790
762 @jsonrpc_method()
791 @jsonrpc_method()
763 def revoke_user_group_permission_from_user_group(
792 def revoke_user_group_permission_from_user_group(
764 request, apiuser, usergroupid, sourceusergroupid):
793 request, apiuser, usergroupid, sourceusergroupid):
765 """
794 """
766 Revoke the permissions that one user group has to another.
795 Revoke the permissions that one user group has to another.
767
796
768 :param apiuser: This is filled automatically from the |authtoken|.
797 :param apiuser: This is filled automatically from the |authtoken|.
769 :type apiuser: AuthUser
798 :type apiuser: AuthUser
770 :param usergroupid: Set the user group on which to edit permissions.
799 :param usergroupid: Set the user group on which to edit permissions.
771 :type usergroupid: str or int
800 :type usergroupid: str or int
772 :param sourceusergroupid: Set the user group from which permissions
801 :param sourceusergroupid: Set the user group from which permissions
773 are revoked.
802 are revoked.
774 :type sourceusergroupid: str or int
803 :type sourceusergroupid: str or int
775
804
776 Example output:
805 Example output:
777
806
778 .. code-block:: bash
807 .. code-block:: bash
779
808
780 id : <id_given_in_input>
809 id : <id_given_in_input>
781 result : {
810 result : {
782 "msg": "Revoked perm for user group: `<user_group_name>` in user group: `<target_user_group_name>`",
811 "msg": "Revoked perm for user group: `<user_group_name>` in user group: `<target_user_group_name>`",
783 "success": true
812 "success": true
784 }
813 }
785 error : null
814 error : null
786 """
815 """
787
816
788 user_group = get_user_group_or_error(sourceusergroupid)
817 user_group = get_user_group_or_error(sourceusergroupid)
789 target_user_group = get_user_group_or_error(usergroupid)
818 target_user_group = get_user_group_or_error(usergroupid)
790
819
791 if not has_superadmin_permission(apiuser):
820 if not has_superadmin_permission(apiuser):
792 # check if we have admin permission for this user group !
821 # check if we have admin permission for this user group !
793 _perms = ('usergroup.admin',)
822 _perms = ('usergroup.admin',)
794 if not HasUserGroupPermissionAnyApi(*_perms)(
823 if not HasUserGroupPermissionAnyApi(*_perms)(
795 user=apiuser,
824 user=apiuser,
796 user_group_name=target_user_group.users_group_name):
825 user_group_name=target_user_group.users_group_name):
797 raise JSONRPCError(
826 raise JSONRPCError(
798 'to user group `%s` does not exist' % (usergroupid,))
827 'to user group `%s` does not exist' % (usergroupid,))
799
828
800 # check if we have at least read permission
829 # check if we have at least read permission
801 # for the source user group !
830 # for the source user group !
802 _perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin',)
831 _perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin',)
803 if not HasUserGroupPermissionAnyApi(*_perms)(
832 if not HasUserGroupPermissionAnyApi(*_perms)(
804 user=apiuser, user_group_name=user_group.users_group_name):
833 user=apiuser, user_group_name=user_group.users_group_name):
805 raise JSONRPCError(
834 raise JSONRPCError(
806 'user group `%s` does not exist' % (sourceusergroupid,))
835 'user group `%s` does not exist' % (sourceusergroupid,))
807
836
808 try:
837 try:
809 UserGroupModel().revoke_user_group_permission(
838 UserGroupModel().revoke_user_group_permission(
810 target_user_group=target_user_group, user_group=user_group)
839 target_user_group=target_user_group, user_group=user_group)
811 Session().commit()
840 Session().commit()
812
841
813 return {
842 return {
814 'msg': 'Revoked perm for user group: '
843 'msg': 'Revoked perm for user group: '
815 '`%s` in user group: `%s`' % (
844 '`%s` in user group: `%s`' % (
816 user_group.users_group_name,
845 user_group.users_group_name,
817 target_user_group.users_group_name
846 target_user_group.users_group_name
818 ),
847 ),
819 'success': True
848 'success': True
820 }
849 }
821 except Exception:
850 except Exception:
822 log.exception("Error occurred during editing permissions "
851 log.exception("Error occurred during editing permissions "
823 "for user group in user group")
852 "for user group in user group")
824 raise JSONRPCError(
853 raise JSONRPCError(
825 'failed to edit permission for user group: '
854 'failed to edit permission for user group: '
826 '`%s` in user group: `%s`' % (
855 '`%s` in user group: `%s`' % (
827 sourceusergroupid, target_user_group.users_group_name
856 sourceusergroupid, target_user_group.users_group_name
828 )
857 )
829 )
858 )
@@ -1,246 +1,245 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2016-2018 RhodeCode GmbH
3 # Copyright (C) 2016-2018 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import logging
21 import logging
22
22
23 import formencode
23 import formencode
24 import formencode.htmlfill
24 import formencode.htmlfill
25
25
26 from pyramid.httpexceptions import HTTPFound
26 from pyramid.httpexceptions import HTTPFound
27 from pyramid.view import view_config
27 from pyramid.view import view_config
28 from pyramid.response import Response
28 from pyramid.response import Response
29 from pyramid.renderers import render
29 from pyramid.renderers import render
30
30
31 from rhodecode.apps._base import BaseAppView, DataGridAppView
31 from rhodecode.apps._base import BaseAppView, DataGridAppView
32 from rhodecode.lib.auth import (
32 from rhodecode.lib.auth import (
33 LoginRequired, NotAnonymous, CSRFRequired, HasPermissionAnyDecorator)
33 LoginRequired, NotAnonymous, CSRFRequired, HasPermissionAnyDecorator)
34 from rhodecode.lib import helpers as h, audit_logger
34 from rhodecode.lib import helpers as h, audit_logger
35 from rhodecode.lib.utils2 import safe_unicode
35 from rhodecode.lib.utils2 import safe_unicode
36
36
37 from rhodecode.model.forms import UserGroupForm
37 from rhodecode.model.forms import UserGroupForm
38 from rhodecode.model.permission import PermissionModel
38 from rhodecode.model.permission import PermissionModel
39 from rhodecode.model.scm import UserGroupList
39 from rhodecode.model.scm import UserGroupList
40 from rhodecode.model.db import (
40 from rhodecode.model.db import (
41 or_, count, User, UserGroup, UserGroupMember)
41 or_, count, User, UserGroup, UserGroupMember)
42 from rhodecode.model.meta import Session
42 from rhodecode.model.meta import Session
43 from rhodecode.model.user_group import UserGroupModel
43 from rhodecode.model.user_group import UserGroupModel
44
44
45 log = logging.getLogger(__name__)
45 log = logging.getLogger(__name__)
46
46
47
47
48 class AdminUserGroupsView(BaseAppView, DataGridAppView):
48 class AdminUserGroupsView(BaseAppView, DataGridAppView):
49
49
50 def load_default_context(self):
50 def load_default_context(self):
51 c = self._get_local_tmpl_context()
51 c = self._get_local_tmpl_context()
52
52
53 PermissionModel().set_global_permission_choices(
53 PermissionModel().set_global_permission_choices(
54 c, gettext_translator=self.request.translate)
54 c, gettext_translator=self.request.translate)
55
55
56 return c
56 return c
57
57
58 # permission check in data loading of
58 # permission check in data loading of
59 # `user_groups_list_data` via UserGroupList
59 # `user_groups_list_data` via UserGroupList
60 @LoginRequired()
60 @LoginRequired()
61 @NotAnonymous()
61 @NotAnonymous()
62 @view_config(
62 @view_config(
63 route_name='user_groups', request_method='GET',
63 route_name='user_groups', request_method='GET',
64 renderer='rhodecode:templates/admin/user_groups/user_groups.mako')
64 renderer='rhodecode:templates/admin/user_groups/user_groups.mako')
65 def user_groups_list(self):
65 def user_groups_list(self):
66 c = self.load_default_context()
66 c = self.load_default_context()
67 return self._get_template_context(c)
67 return self._get_template_context(c)
68
68
69 # permission check inside
69 # permission check inside
70 @LoginRequired()
70 @LoginRequired()
71 @NotAnonymous()
71 @NotAnonymous()
72 @view_config(
72 @view_config(
73 route_name='user_groups_data', request_method='GET',
73 route_name='user_groups_data', request_method='GET',
74 renderer='json_ext', xhr=True)
74 renderer='json_ext', xhr=True)
75 def user_groups_list_data(self):
75 def user_groups_list_data(self):
76 self.load_default_context()
76 self.load_default_context()
77 column_map = {
77 column_map = {
78 'active': 'users_group_active',
78 'active': 'users_group_active',
79 'description': 'user_group_description',
79 'description': 'user_group_description',
80 'members': 'members_total',
80 'members': 'members_total',
81 'owner': 'user_username',
81 'owner': 'user_username',
82 'sync': 'group_data'
82 'sync': 'group_data'
83 }
83 }
84 draw, start, limit = self._extract_chunk(self.request)
84 draw, start, limit = self._extract_chunk(self.request)
85 search_q, order_by, order_dir = self._extract_ordering(
85 search_q, order_by, order_dir = self._extract_ordering(
86 self.request, column_map=column_map)
86 self.request, column_map=column_map)
87
87
88 _render = self.request.get_partial_renderer(
88 _render = self.request.get_partial_renderer(
89 'rhodecode:templates/data_table/_dt_elements.mako')
89 'rhodecode:templates/data_table/_dt_elements.mako')
90
90
91 def user_group_name(user_group_name):
91 def user_group_name(user_group_name):
92 return _render("user_group_name", user_group_name)
92 return _render("user_group_name", user_group_name)
93
93
94 def user_group_actions(user_group_id, user_group_name):
94 def user_group_actions(user_group_id, user_group_name):
95 return _render("user_group_actions", user_group_id, user_group_name)
95 return _render("user_group_actions", user_group_id, user_group_name)
96
96
97 def user_profile(username):
97 def user_profile(username):
98 return _render('user_profile', username)
98 return _render('user_profile', username)
99
99
100 auth_user_group_list = UserGroupList(
100 auth_user_group_list = UserGroupList(
101 UserGroup.query().all(), perm_set=['usergroup.admin'])
101 UserGroup.query().all(), perm_set=['usergroup.admin'])
102
102
103 allowed_ids = [-1]
103 allowed_ids = [-1]
104 for user_group in auth_user_group_list:
104 for user_group in auth_user_group_list:
105 allowed_ids.append(user_group.users_group_id)
105 allowed_ids.append(user_group.users_group_id)
106
106
107 user_groups_data_total_count = UserGroup.query()\
107 user_groups_data_total_count = UserGroup.query()\
108 .filter(UserGroup.users_group_id.in_(allowed_ids))\
108 .filter(UserGroup.users_group_id.in_(allowed_ids))\
109 .count()
109 .count()
110
110
111 member_count = count(UserGroupMember.user_id)
111 member_count = count(UserGroupMember.user_id)
112 base_q = Session.query(
112 base_q = Session.query(
113 UserGroup.users_group_name,
113 UserGroup.users_group_name,
114 UserGroup.user_group_description,
114 UserGroup.user_group_description,
115 UserGroup.users_group_active,
115 UserGroup.users_group_active,
116 UserGroup.users_group_id,
116 UserGroup.users_group_id,
117 UserGroup.group_data,
117 UserGroup.group_data,
118 User,
118 User,
119 member_count.label('member_count')
119 member_count.label('member_count')
120 ) \
120 ) \
121 .filter(UserGroup.users_group_id.in_(allowed_ids)) \
121 .filter(UserGroup.users_group_id.in_(allowed_ids)) \
122 .outerjoin(UserGroupMember) \
122 .outerjoin(UserGroupMember) \
123 .join(User, User.user_id == UserGroup.user_id) \
123 .join(User, User.user_id == UserGroup.user_id) \
124 .group_by(UserGroup, User)
124 .group_by(UserGroup, User)
125
125
126 if search_q:
126 if search_q:
127 like_expression = u'%{}%'.format(safe_unicode(search_q))
127 like_expression = u'%{}%'.format(safe_unicode(search_q))
128 base_q = base_q.filter(or_(
128 base_q = base_q.filter(or_(
129 UserGroup.users_group_name.ilike(like_expression),
129 UserGroup.users_group_name.ilike(like_expression),
130 ))
130 ))
131
131
132 user_groups_data_total_filtered_count = base_q.count()
132 user_groups_data_total_filtered_count = base_q.count()
133
133
134 if order_by == 'members_total':
134 if order_by == 'members_total':
135 sort_col = member_count
135 sort_col = member_count
136 elif order_by == 'user_username':
136 elif order_by == 'user_username':
137 sort_col = User.username
137 sort_col = User.username
138 else:
138 else:
139 sort_col = getattr(UserGroup, order_by, None)
139 sort_col = getattr(UserGroup, order_by, None)
140
140
141 if isinstance(sort_col, count) or sort_col:
141 if isinstance(sort_col, count) or sort_col:
142 if order_dir == 'asc':
142 if order_dir == 'asc':
143 sort_col = sort_col.asc()
143 sort_col = sort_col.asc()
144 else:
144 else:
145 sort_col = sort_col.desc()
145 sort_col = sort_col.desc()
146
146
147 base_q = base_q.order_by(sort_col)
147 base_q = base_q.order_by(sort_col)
148 base_q = base_q.offset(start).limit(limit)
148 base_q = base_q.offset(start).limit(limit)
149
149
150 # authenticated access to user groups
150 # authenticated access to user groups
151 auth_user_group_list = base_q.all()
151 auth_user_group_list = base_q.all()
152
152
153 user_groups_data = []
153 user_groups_data = []
154 for user_gr in auth_user_group_list:
154 for user_gr in auth_user_group_list:
155 user_groups_data.append({
155 user_groups_data.append({
156 "users_group_name": user_group_name(user_gr.users_group_name),
156 "users_group_name": user_group_name(user_gr.users_group_name),
157 "name_raw": h.escape(user_gr.users_group_name),
157 "name_raw": h.escape(user_gr.users_group_name),
158 "description": h.escape(user_gr.user_group_description),
158 "description": h.escape(user_gr.user_group_description),
159 "members": user_gr.member_count,
159 "members": user_gr.member_count,
160 # NOTE(marcink): because of advanced query we
160 # NOTE(marcink): because of advanced query we
161 # need to load it like that
161 # need to load it like that
162 "sync": UserGroup._load_group_data(
162 "sync": UserGroup._load_sync(user_gr.group_data),
163 user_gr.group_data).get('extern_type'),
164 "active": h.bool2icon(user_gr.users_group_active),
163 "active": h.bool2icon(user_gr.users_group_active),
165 "owner": user_profile(user_gr.User.username),
164 "owner": user_profile(user_gr.User.username),
166 "action": user_group_actions(
165 "action": user_group_actions(
167 user_gr.users_group_id, user_gr.users_group_name)
166 user_gr.users_group_id, user_gr.users_group_name)
168 })
167 })
169
168
170 data = ({
169 data = ({
171 'draw': draw,
170 'draw': draw,
172 'data': user_groups_data,
171 'data': user_groups_data,
173 'recordsTotal': user_groups_data_total_count,
172 'recordsTotal': user_groups_data_total_count,
174 'recordsFiltered': user_groups_data_total_filtered_count,
173 'recordsFiltered': user_groups_data_total_filtered_count,
175 })
174 })
176
175
177 return data
176 return data
178
177
179 @LoginRequired()
178 @LoginRequired()
180 @HasPermissionAnyDecorator('hg.admin', 'hg.usergroup.create.true')
179 @HasPermissionAnyDecorator('hg.admin', 'hg.usergroup.create.true')
181 @view_config(
180 @view_config(
182 route_name='user_groups_new', request_method='GET',
181 route_name='user_groups_new', request_method='GET',
183 renderer='rhodecode:templates/admin/user_groups/user_group_add.mako')
182 renderer='rhodecode:templates/admin/user_groups/user_group_add.mako')
184 def user_groups_new(self):
183 def user_groups_new(self):
185 c = self.load_default_context()
184 c = self.load_default_context()
186 return self._get_template_context(c)
185 return self._get_template_context(c)
187
186
188 @LoginRequired()
187 @LoginRequired()
189 @HasPermissionAnyDecorator('hg.admin', 'hg.usergroup.create.true')
188 @HasPermissionAnyDecorator('hg.admin', 'hg.usergroup.create.true')
190 @CSRFRequired()
189 @CSRFRequired()
191 @view_config(
190 @view_config(
192 route_name='user_groups_create', request_method='POST',
191 route_name='user_groups_create', request_method='POST',
193 renderer='rhodecode:templates/admin/user_groups/user_group_add.mako')
192 renderer='rhodecode:templates/admin/user_groups/user_group_add.mako')
194 def user_groups_create(self):
193 def user_groups_create(self):
195 _ = self.request.translate
194 _ = self.request.translate
196 c = self.load_default_context()
195 c = self.load_default_context()
197 users_group_form = UserGroupForm(self.request.translate)()
196 users_group_form = UserGroupForm(self.request.translate)()
198
197
199 user_group_name = self.request.POST.get('users_group_name')
198 user_group_name = self.request.POST.get('users_group_name')
200 try:
199 try:
201 form_result = users_group_form.to_python(dict(self.request.POST))
200 form_result = users_group_form.to_python(dict(self.request.POST))
202 user_group = UserGroupModel().create(
201 user_group = UserGroupModel().create(
203 name=form_result['users_group_name'],
202 name=form_result['users_group_name'],
204 description=form_result['user_group_description'],
203 description=form_result['user_group_description'],
205 owner=self._rhodecode_user.user_id,
204 owner=self._rhodecode_user.user_id,
206 active=form_result['users_group_active'])
205 active=form_result['users_group_active'])
207 Session().flush()
206 Session().flush()
208 creation_data = user_group.get_api_data()
207 creation_data = user_group.get_api_data()
209 user_group_name = form_result['users_group_name']
208 user_group_name = form_result['users_group_name']
210
209
211 audit_logger.store_web(
210 audit_logger.store_web(
212 'user_group.create', action_data={'data': creation_data},
211 'user_group.create', action_data={'data': creation_data},
213 user=self._rhodecode_user)
212 user=self._rhodecode_user)
214
213
215 user_group_link = h.link_to(
214 user_group_link = h.link_to(
216 h.escape(user_group_name),
215 h.escape(user_group_name),
217 h.route_path(
216 h.route_path(
218 'edit_user_group', user_group_id=user_group.users_group_id))
217 'edit_user_group', user_group_id=user_group.users_group_id))
219 h.flash(h.literal(_('Created user group %(user_group_link)s')
218 h.flash(h.literal(_('Created user group %(user_group_link)s')
220 % {'user_group_link': user_group_link}),
219 % {'user_group_link': user_group_link}),
221 category='success')
220 category='success')
222 Session().commit()
221 Session().commit()
223 user_group_id = user_group.users_group_id
222 user_group_id = user_group.users_group_id
224 except formencode.Invalid as errors:
223 except formencode.Invalid as errors:
225
224
226 data = render(
225 data = render(
227 'rhodecode:templates/admin/user_groups/user_group_add.mako',
226 'rhodecode:templates/admin/user_groups/user_group_add.mako',
228 self._get_template_context(c), self.request)
227 self._get_template_context(c), self.request)
229 html = formencode.htmlfill.render(
228 html = formencode.htmlfill.render(
230 data,
229 data,
231 defaults=errors.value,
230 defaults=errors.value,
232 errors=errors.error_dict or {},
231 errors=errors.error_dict or {},
233 prefix_error=False,
232 prefix_error=False,
234 encoding="UTF-8",
233 encoding="UTF-8",
235 force_defaults=False
234 force_defaults=False
236 )
235 )
237 return Response(html)
236 return Response(html)
238
237
239 except Exception:
238 except Exception:
240 log.exception("Exception creating user group")
239 log.exception("Exception creating user group")
241 h.flash(_('Error occurred during creation of user group %s') \
240 h.flash(_('Error occurred during creation of user group %s') \
242 % user_group_name, category='error')
241 % user_group_name, category='error')
243 raise HTTPFound(h.route_path('user_groups_new'))
242 raise HTTPFound(h.route_path('user_groups_new'))
244
243
245 raise HTTPFound(
244 raise HTTPFound(
246 h.route_path('edit_user_group', user_group_id=user_group_id))
245 h.route_path('edit_user_group', user_group_id=user_group_id))
@@ -1,4471 +1,4481 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2018 RhodeCode GmbH
3 # Copyright (C) 2010-2018 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 """
21 """
22 Database Models for RhodeCode Enterprise
22 Database Models for RhodeCode Enterprise
23 """
23 """
24
24
25 import re
25 import re
26 import os
26 import os
27 import time
27 import time
28 import hashlib
28 import hashlib
29 import logging
29 import logging
30 import datetime
30 import datetime
31 import warnings
31 import warnings
32 import ipaddress
32 import ipaddress
33 import functools
33 import functools
34 import traceback
34 import traceback
35 import collections
35 import collections
36
36
37 from sqlalchemy import (
37 from sqlalchemy import (
38 or_, and_, not_, func, TypeDecorator, event,
38 or_, and_, not_, func, TypeDecorator, event,
39 Index, Sequence, UniqueConstraint, ForeignKey, CheckConstraint, Column,
39 Index, Sequence, UniqueConstraint, ForeignKey, CheckConstraint, Column,
40 Boolean, String, Unicode, UnicodeText, DateTime, Integer, LargeBinary,
40 Boolean, String, Unicode, UnicodeText, DateTime, Integer, LargeBinary,
41 Text, Float, PickleType)
41 Text, Float, PickleType)
42 from sqlalchemy.sql.expression import true, false
42 from sqlalchemy.sql.expression import true, false
43 from sqlalchemy.sql.functions import coalesce, count # noqa
43 from sqlalchemy.sql.functions import coalesce, count # noqa
44 from sqlalchemy.orm import (
44 from sqlalchemy.orm import (
45 relationship, joinedload, class_mapper, validates, aliased)
45 relationship, joinedload, class_mapper, validates, aliased)
46 from sqlalchemy.ext.declarative import declared_attr
46 from sqlalchemy.ext.declarative import declared_attr
47 from sqlalchemy.ext.hybrid import hybrid_property
47 from sqlalchemy.ext.hybrid import hybrid_property
48 from sqlalchemy.exc import IntegrityError # noqa
48 from sqlalchemy.exc import IntegrityError # noqa
49 from sqlalchemy.dialects.mysql import LONGTEXT
49 from sqlalchemy.dialects.mysql import LONGTEXT
50 from beaker.cache import cache_region
50 from beaker.cache import cache_region
51 from zope.cachedescriptors.property import Lazy as LazyProperty
51 from zope.cachedescriptors.property import Lazy as LazyProperty
52
52
53 from pyramid.threadlocal import get_current_request
53 from pyramid.threadlocal import get_current_request
54
54
55 from rhodecode.translation import _
55 from rhodecode.translation import _
56 from rhodecode.lib.vcs import get_vcs_instance
56 from rhodecode.lib.vcs import get_vcs_instance
57 from rhodecode.lib.vcs.backends.base import EmptyCommit, Reference
57 from rhodecode.lib.vcs.backends.base import EmptyCommit, Reference
58 from rhodecode.lib.utils2 import (
58 from rhodecode.lib.utils2 import (
59 str2bool, safe_str, get_commit_safe, safe_unicode, md5_safe,
59 str2bool, safe_str, get_commit_safe, safe_unicode, md5_safe,
60 time_to_datetime, aslist, Optional, safe_int, get_clone_url, AttributeDict,
60 time_to_datetime, aslist, Optional, safe_int, get_clone_url, AttributeDict,
61 glob2re, StrictAttributeDict, cleaned_uri)
61 glob2re, StrictAttributeDict, cleaned_uri)
62 from rhodecode.lib.jsonalchemy import MutationObj, MutationList, JsonType, \
62 from rhodecode.lib.jsonalchemy import MutationObj, MutationList, JsonType, \
63 JsonRaw
63 JsonRaw
64 from rhodecode.lib.ext_json import json
64 from rhodecode.lib.ext_json import json
65 from rhodecode.lib.caching_query import FromCache
65 from rhodecode.lib.caching_query import FromCache
66 from rhodecode.lib.encrypt import AESCipher
66 from rhodecode.lib.encrypt import AESCipher
67
67
68 from rhodecode.model.meta import Base, Session
68 from rhodecode.model.meta import Base, Session
69
69
70 URL_SEP = '/'
70 URL_SEP = '/'
71 log = logging.getLogger(__name__)
71 log = logging.getLogger(__name__)
72
72
73 # =============================================================================
73 # =============================================================================
74 # BASE CLASSES
74 # BASE CLASSES
75 # =============================================================================
75 # =============================================================================
76
76
77 # this is propagated from .ini file rhodecode.encrypted_values.secret or
77 # this is propagated from .ini file rhodecode.encrypted_values.secret or
78 # beaker.session.secret if first is not set.
78 # beaker.session.secret if first is not set.
79 # and initialized at environment.py
79 # and initialized at environment.py
80 ENCRYPTION_KEY = None
80 ENCRYPTION_KEY = None
81
81
82 # used to sort permissions by types, '#' used here is not allowed to be in
82 # used to sort permissions by types, '#' used here is not allowed to be in
83 # usernames, and it's very early in sorted string.printable table.
83 # usernames, and it's very early in sorted string.printable table.
84 PERMISSION_TYPE_SORT = {
84 PERMISSION_TYPE_SORT = {
85 'admin': '####',
85 'admin': '####',
86 'write': '###',
86 'write': '###',
87 'read': '##',
87 'read': '##',
88 'none': '#',
88 'none': '#',
89 }
89 }
90
90
91
91
92 def display_user_sort(obj):
92 def display_user_sort(obj):
93 """
93 """
94 Sort function used to sort permissions in .permissions() function of
94 Sort function used to sort permissions in .permissions() function of
95 Repository, RepoGroup, UserGroup. Also it put the default user in front
95 Repository, RepoGroup, UserGroup. Also it put the default user in front
96 of all other resources
96 of all other resources
97 """
97 """
98
98
99 if obj.username == User.DEFAULT_USER:
99 if obj.username == User.DEFAULT_USER:
100 return '#####'
100 return '#####'
101 prefix = PERMISSION_TYPE_SORT.get(obj.permission.split('.')[-1], '')
101 prefix = PERMISSION_TYPE_SORT.get(obj.permission.split('.')[-1], '')
102 return prefix + obj.username
102 return prefix + obj.username
103
103
104
104
105 def display_user_group_sort(obj):
105 def display_user_group_sort(obj):
106 """
106 """
107 Sort function used to sort permissions in .permissions() function of
107 Sort function used to sort permissions in .permissions() function of
108 Repository, RepoGroup, UserGroup. Also it put the default user in front
108 Repository, RepoGroup, UserGroup. Also it put the default user in front
109 of all other resources
109 of all other resources
110 """
110 """
111
111
112 prefix = PERMISSION_TYPE_SORT.get(obj.permission.split('.')[-1], '')
112 prefix = PERMISSION_TYPE_SORT.get(obj.permission.split('.')[-1], '')
113 return prefix + obj.users_group_name
113 return prefix + obj.users_group_name
114
114
115
115
116 def _hash_key(k):
116 def _hash_key(k):
117 return md5_safe(k)
117 return md5_safe(k)
118
118
119
119
120 def in_filter_generator(qry, items, limit=500):
120 def in_filter_generator(qry, items, limit=500):
121 """
121 """
122 Splits IN() into multiple with OR
122 Splits IN() into multiple with OR
123 e.g.::
123 e.g.::
124 cnt = Repository.query().filter(
124 cnt = Repository.query().filter(
125 or_(
125 or_(
126 *in_filter_generator(Repository.repo_id, range(100000))
126 *in_filter_generator(Repository.repo_id, range(100000))
127 )).count()
127 )).count()
128 """
128 """
129 if not items:
129 if not items:
130 # empty list will cause empty query which might cause security issues
130 # empty list will cause empty query which might cause security issues
131 # this can lead to hidden unpleasant results
131 # this can lead to hidden unpleasant results
132 items = [-1]
132 items = [-1]
133
133
134 parts = []
134 parts = []
135 for chunk in xrange(0, len(items), limit):
135 for chunk in xrange(0, len(items), limit):
136 parts.append(
136 parts.append(
137 qry.in_(items[chunk: chunk + limit])
137 qry.in_(items[chunk: chunk + limit])
138 )
138 )
139
139
140 return parts
140 return parts
141
141
142
142
143 class EncryptedTextValue(TypeDecorator):
143 class EncryptedTextValue(TypeDecorator):
144 """
144 """
145 Special column for encrypted long text data, use like::
145 Special column for encrypted long text data, use like::
146
146
147 value = Column("encrypted_value", EncryptedValue(), nullable=False)
147 value = Column("encrypted_value", EncryptedValue(), nullable=False)
148
148
149 This column is intelligent so if value is in unencrypted form it return
149 This column is intelligent so if value is in unencrypted form it return
150 unencrypted form, but on save it always encrypts
150 unencrypted form, but on save it always encrypts
151 """
151 """
152 impl = Text
152 impl = Text
153
153
154 def process_bind_param(self, value, dialect):
154 def process_bind_param(self, value, dialect):
155 if not value:
155 if not value:
156 return value
156 return value
157 if value.startswith('enc$aes$') or value.startswith('enc$aes_hmac$'):
157 if value.startswith('enc$aes$') or value.startswith('enc$aes_hmac$'):
158 # protect against double encrypting if someone manually starts
158 # protect against double encrypting if someone manually starts
159 # doing
159 # doing
160 raise ValueError('value needs to be in unencrypted format, ie. '
160 raise ValueError('value needs to be in unencrypted format, ie. '
161 'not starting with enc$aes')
161 'not starting with enc$aes')
162 return 'enc$aes_hmac$%s' % AESCipher(
162 return 'enc$aes_hmac$%s' % AESCipher(
163 ENCRYPTION_KEY, hmac=True).encrypt(value)
163 ENCRYPTION_KEY, hmac=True).encrypt(value)
164
164
165 def process_result_value(self, value, dialect):
165 def process_result_value(self, value, dialect):
166 import rhodecode
166 import rhodecode
167
167
168 if not value:
168 if not value:
169 return value
169 return value
170
170
171 parts = value.split('$', 3)
171 parts = value.split('$', 3)
172 if not len(parts) == 3:
172 if not len(parts) == 3:
173 # probably not encrypted values
173 # probably not encrypted values
174 return value
174 return value
175 else:
175 else:
176 if parts[0] != 'enc':
176 if parts[0] != 'enc':
177 # parts ok but without our header ?
177 # parts ok but without our header ?
178 return value
178 return value
179 enc_strict_mode = str2bool(rhodecode.CONFIG.get(
179 enc_strict_mode = str2bool(rhodecode.CONFIG.get(
180 'rhodecode.encrypted_values.strict') or True)
180 'rhodecode.encrypted_values.strict') or True)
181 # at that stage we know it's our encryption
181 # at that stage we know it's our encryption
182 if parts[1] == 'aes':
182 if parts[1] == 'aes':
183 decrypted_data = AESCipher(ENCRYPTION_KEY).decrypt(parts[2])
183 decrypted_data = AESCipher(ENCRYPTION_KEY).decrypt(parts[2])
184 elif parts[1] == 'aes_hmac':
184 elif parts[1] == 'aes_hmac':
185 decrypted_data = AESCipher(
185 decrypted_data = AESCipher(
186 ENCRYPTION_KEY, hmac=True,
186 ENCRYPTION_KEY, hmac=True,
187 strict_verification=enc_strict_mode).decrypt(parts[2])
187 strict_verification=enc_strict_mode).decrypt(parts[2])
188 else:
188 else:
189 raise ValueError(
189 raise ValueError(
190 'Encryption type part is wrong, must be `aes` '
190 'Encryption type part is wrong, must be `aes` '
191 'or `aes_hmac`, got `%s` instead' % (parts[1]))
191 'or `aes_hmac`, got `%s` instead' % (parts[1]))
192 return decrypted_data
192 return decrypted_data
193
193
194
194
195 class BaseModel(object):
195 class BaseModel(object):
196 """
196 """
197 Base Model for all classes
197 Base Model for all classes
198 """
198 """
199
199
200 @classmethod
200 @classmethod
201 def _get_keys(cls):
201 def _get_keys(cls):
202 """return column names for this model """
202 """return column names for this model """
203 return class_mapper(cls).c.keys()
203 return class_mapper(cls).c.keys()
204
204
205 def get_dict(self):
205 def get_dict(self):
206 """
206 """
207 return dict with keys and values corresponding
207 return dict with keys and values corresponding
208 to this model data """
208 to this model data """
209
209
210 d = {}
210 d = {}
211 for k in self._get_keys():
211 for k in self._get_keys():
212 d[k] = getattr(self, k)
212 d[k] = getattr(self, k)
213
213
214 # also use __json__() if present to get additional fields
214 # also use __json__() if present to get additional fields
215 _json_attr = getattr(self, '__json__', None)
215 _json_attr = getattr(self, '__json__', None)
216 if _json_attr:
216 if _json_attr:
217 # update with attributes from __json__
217 # update with attributes from __json__
218 if callable(_json_attr):
218 if callable(_json_attr):
219 _json_attr = _json_attr()
219 _json_attr = _json_attr()
220 for k, val in _json_attr.iteritems():
220 for k, val in _json_attr.iteritems():
221 d[k] = val
221 d[k] = val
222 return d
222 return d
223
223
224 def get_appstruct(self):
224 def get_appstruct(self):
225 """return list with keys and values tuples corresponding
225 """return list with keys and values tuples corresponding
226 to this model data """
226 to this model data """
227
227
228 lst = []
228 lst = []
229 for k in self._get_keys():
229 for k in self._get_keys():
230 lst.append((k, getattr(self, k),))
230 lst.append((k, getattr(self, k),))
231 return lst
231 return lst
232
232
233 def populate_obj(self, populate_dict):
233 def populate_obj(self, populate_dict):
234 """populate model with data from given populate_dict"""
234 """populate model with data from given populate_dict"""
235
235
236 for k in self._get_keys():
236 for k in self._get_keys():
237 if k in populate_dict:
237 if k in populate_dict:
238 setattr(self, k, populate_dict[k])
238 setattr(self, k, populate_dict[k])
239
239
240 @classmethod
240 @classmethod
241 def query(cls):
241 def query(cls):
242 return Session().query(cls)
242 return Session().query(cls)
243
243
244 @classmethod
244 @classmethod
245 def get(cls, id_):
245 def get(cls, id_):
246 if id_:
246 if id_:
247 return cls.query().get(id_)
247 return cls.query().get(id_)
248
248
249 @classmethod
249 @classmethod
250 def get_or_404(cls, id_):
250 def get_or_404(cls, id_):
251 from pyramid.httpexceptions import HTTPNotFound
251 from pyramid.httpexceptions import HTTPNotFound
252
252
253 try:
253 try:
254 id_ = int(id_)
254 id_ = int(id_)
255 except (TypeError, ValueError):
255 except (TypeError, ValueError):
256 raise HTTPNotFound()
256 raise HTTPNotFound()
257
257
258 res = cls.query().get(id_)
258 res = cls.query().get(id_)
259 if not res:
259 if not res:
260 raise HTTPNotFound()
260 raise HTTPNotFound()
261 return res
261 return res
262
262
263 @classmethod
263 @classmethod
264 def getAll(cls):
264 def getAll(cls):
265 # deprecated and left for backward compatibility
265 # deprecated and left for backward compatibility
266 return cls.get_all()
266 return cls.get_all()
267
267
268 @classmethod
268 @classmethod
269 def get_all(cls):
269 def get_all(cls):
270 return cls.query().all()
270 return cls.query().all()
271
271
272 @classmethod
272 @classmethod
273 def delete(cls, id_):
273 def delete(cls, id_):
274 obj = cls.query().get(id_)
274 obj = cls.query().get(id_)
275 Session().delete(obj)
275 Session().delete(obj)
276
276
277 @classmethod
277 @classmethod
278 def identity_cache(cls, session, attr_name, value):
278 def identity_cache(cls, session, attr_name, value):
279 exist_in_session = []
279 exist_in_session = []
280 for (item_cls, pkey), instance in session.identity_map.items():
280 for (item_cls, pkey), instance in session.identity_map.items():
281 if cls == item_cls and getattr(instance, attr_name) == value:
281 if cls == item_cls and getattr(instance, attr_name) == value:
282 exist_in_session.append(instance)
282 exist_in_session.append(instance)
283 if exist_in_session:
283 if exist_in_session:
284 if len(exist_in_session) == 1:
284 if len(exist_in_session) == 1:
285 return exist_in_session[0]
285 return exist_in_session[0]
286 log.exception(
286 log.exception(
287 'multiple objects with attr %s and '
287 'multiple objects with attr %s and '
288 'value %s found with same name: %r',
288 'value %s found with same name: %r',
289 attr_name, value, exist_in_session)
289 attr_name, value, exist_in_session)
290
290
291 def __repr__(self):
291 def __repr__(self):
292 if hasattr(self, '__unicode__'):
292 if hasattr(self, '__unicode__'):
293 # python repr needs to return str
293 # python repr needs to return str
294 try:
294 try:
295 return safe_str(self.__unicode__())
295 return safe_str(self.__unicode__())
296 except UnicodeDecodeError:
296 except UnicodeDecodeError:
297 pass
297 pass
298 return '<DB:%s>' % (self.__class__.__name__)
298 return '<DB:%s>' % (self.__class__.__name__)
299
299
300
300
301 class RhodeCodeSetting(Base, BaseModel):
301 class RhodeCodeSetting(Base, BaseModel):
302 __tablename__ = 'rhodecode_settings'
302 __tablename__ = 'rhodecode_settings'
303 __table_args__ = (
303 __table_args__ = (
304 UniqueConstraint('app_settings_name'),
304 UniqueConstraint('app_settings_name'),
305 {'extend_existing': True, 'mysql_engine': 'InnoDB',
305 {'extend_existing': True, 'mysql_engine': 'InnoDB',
306 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
306 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
307 )
307 )
308
308
309 SETTINGS_TYPES = {
309 SETTINGS_TYPES = {
310 'str': safe_str,
310 'str': safe_str,
311 'int': safe_int,
311 'int': safe_int,
312 'unicode': safe_unicode,
312 'unicode': safe_unicode,
313 'bool': str2bool,
313 'bool': str2bool,
314 'list': functools.partial(aslist, sep=',')
314 'list': functools.partial(aslist, sep=',')
315 }
315 }
316 DEFAULT_UPDATE_URL = 'https://rhodecode.com/api/v1/info/versions'
316 DEFAULT_UPDATE_URL = 'https://rhodecode.com/api/v1/info/versions'
317 GLOBAL_CONF_KEY = 'app_settings'
317 GLOBAL_CONF_KEY = 'app_settings'
318
318
319 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
319 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
320 app_settings_name = Column("app_settings_name", String(255), nullable=True, unique=None, default=None)
320 app_settings_name = Column("app_settings_name", String(255), nullable=True, unique=None, default=None)
321 _app_settings_value = Column("app_settings_value", String(4096), nullable=True, unique=None, default=None)
321 _app_settings_value = Column("app_settings_value", String(4096), nullable=True, unique=None, default=None)
322 _app_settings_type = Column("app_settings_type", String(255), nullable=True, unique=None, default=None)
322 _app_settings_type = Column("app_settings_type", String(255), nullable=True, unique=None, default=None)
323
323
324 def __init__(self, key='', val='', type='unicode'):
324 def __init__(self, key='', val='', type='unicode'):
325 self.app_settings_name = key
325 self.app_settings_name = key
326 self.app_settings_type = type
326 self.app_settings_type = type
327 self.app_settings_value = val
327 self.app_settings_value = val
328
328
329 @validates('_app_settings_value')
329 @validates('_app_settings_value')
330 def validate_settings_value(self, key, val):
330 def validate_settings_value(self, key, val):
331 assert type(val) == unicode
331 assert type(val) == unicode
332 return val
332 return val
333
333
334 @hybrid_property
334 @hybrid_property
335 def app_settings_value(self):
335 def app_settings_value(self):
336 v = self._app_settings_value
336 v = self._app_settings_value
337 _type = self.app_settings_type
337 _type = self.app_settings_type
338 if _type:
338 if _type:
339 _type = self.app_settings_type.split('.')[0]
339 _type = self.app_settings_type.split('.')[0]
340 # decode the encrypted value
340 # decode the encrypted value
341 if 'encrypted' in self.app_settings_type:
341 if 'encrypted' in self.app_settings_type:
342 cipher = EncryptedTextValue()
342 cipher = EncryptedTextValue()
343 v = safe_unicode(cipher.process_result_value(v, None))
343 v = safe_unicode(cipher.process_result_value(v, None))
344
344
345 converter = self.SETTINGS_TYPES.get(_type) or \
345 converter = self.SETTINGS_TYPES.get(_type) or \
346 self.SETTINGS_TYPES['unicode']
346 self.SETTINGS_TYPES['unicode']
347 return converter(v)
347 return converter(v)
348
348
349 @app_settings_value.setter
349 @app_settings_value.setter
350 def app_settings_value(self, val):
350 def app_settings_value(self, val):
351 """
351 """
352 Setter that will always make sure we use unicode in app_settings_value
352 Setter that will always make sure we use unicode in app_settings_value
353
353
354 :param val:
354 :param val:
355 """
355 """
356 val = safe_unicode(val)
356 val = safe_unicode(val)
357 # encode the encrypted value
357 # encode the encrypted value
358 if 'encrypted' in self.app_settings_type:
358 if 'encrypted' in self.app_settings_type:
359 cipher = EncryptedTextValue()
359 cipher = EncryptedTextValue()
360 val = safe_unicode(cipher.process_bind_param(val, None))
360 val = safe_unicode(cipher.process_bind_param(val, None))
361 self._app_settings_value = val
361 self._app_settings_value = val
362
362
363 @hybrid_property
363 @hybrid_property
364 def app_settings_type(self):
364 def app_settings_type(self):
365 return self._app_settings_type
365 return self._app_settings_type
366
366
367 @app_settings_type.setter
367 @app_settings_type.setter
368 def app_settings_type(self, val):
368 def app_settings_type(self, val):
369 if val.split('.')[0] not in self.SETTINGS_TYPES:
369 if val.split('.')[0] not in self.SETTINGS_TYPES:
370 raise Exception('type must be one of %s got %s'
370 raise Exception('type must be one of %s got %s'
371 % (self.SETTINGS_TYPES.keys(), val))
371 % (self.SETTINGS_TYPES.keys(), val))
372 self._app_settings_type = val
372 self._app_settings_type = val
373
373
374 def __unicode__(self):
374 def __unicode__(self):
375 return u"<%s('%s:%s[%s]')>" % (
375 return u"<%s('%s:%s[%s]')>" % (
376 self.__class__.__name__,
376 self.__class__.__name__,
377 self.app_settings_name, self.app_settings_value,
377 self.app_settings_name, self.app_settings_value,
378 self.app_settings_type
378 self.app_settings_type
379 )
379 )
380
380
381
381
382 class RhodeCodeUi(Base, BaseModel):
382 class RhodeCodeUi(Base, BaseModel):
383 __tablename__ = 'rhodecode_ui'
383 __tablename__ = 'rhodecode_ui'
384 __table_args__ = (
384 __table_args__ = (
385 UniqueConstraint('ui_key'),
385 UniqueConstraint('ui_key'),
386 {'extend_existing': True, 'mysql_engine': 'InnoDB',
386 {'extend_existing': True, 'mysql_engine': 'InnoDB',
387 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
387 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
388 )
388 )
389
389
390 HOOK_REPO_SIZE = 'changegroup.repo_size'
390 HOOK_REPO_SIZE = 'changegroup.repo_size'
391 # HG
391 # HG
392 HOOK_PRE_PULL = 'preoutgoing.pre_pull'
392 HOOK_PRE_PULL = 'preoutgoing.pre_pull'
393 HOOK_PULL = 'outgoing.pull_logger'
393 HOOK_PULL = 'outgoing.pull_logger'
394 HOOK_PRE_PUSH = 'prechangegroup.pre_push'
394 HOOK_PRE_PUSH = 'prechangegroup.pre_push'
395 HOOK_PRETX_PUSH = 'pretxnchangegroup.pre_push'
395 HOOK_PRETX_PUSH = 'pretxnchangegroup.pre_push'
396 HOOK_PUSH = 'changegroup.push_logger'
396 HOOK_PUSH = 'changegroup.push_logger'
397 HOOK_PUSH_KEY = 'pushkey.key_push'
397 HOOK_PUSH_KEY = 'pushkey.key_push'
398
398
399 # TODO: johbo: Unify way how hooks are configured for git and hg,
399 # TODO: johbo: Unify way how hooks are configured for git and hg,
400 # git part is currently hardcoded.
400 # git part is currently hardcoded.
401
401
402 # SVN PATTERNS
402 # SVN PATTERNS
403 SVN_BRANCH_ID = 'vcs_svn_branch'
403 SVN_BRANCH_ID = 'vcs_svn_branch'
404 SVN_TAG_ID = 'vcs_svn_tag'
404 SVN_TAG_ID = 'vcs_svn_tag'
405
405
406 ui_id = Column(
406 ui_id = Column(
407 "ui_id", Integer(), nullable=False, unique=True, default=None,
407 "ui_id", Integer(), nullable=False, unique=True, default=None,
408 primary_key=True)
408 primary_key=True)
409 ui_section = Column(
409 ui_section = Column(
410 "ui_section", String(255), nullable=True, unique=None, default=None)
410 "ui_section", String(255), nullable=True, unique=None, default=None)
411 ui_key = Column(
411 ui_key = Column(
412 "ui_key", String(255), nullable=True, unique=None, default=None)
412 "ui_key", String(255), nullable=True, unique=None, default=None)
413 ui_value = Column(
413 ui_value = Column(
414 "ui_value", String(255), nullable=True, unique=None, default=None)
414 "ui_value", String(255), nullable=True, unique=None, default=None)
415 ui_active = Column(
415 ui_active = Column(
416 "ui_active", Boolean(), nullable=True, unique=None, default=True)
416 "ui_active", Boolean(), nullable=True, unique=None, default=True)
417
417
418 def __repr__(self):
418 def __repr__(self):
419 return '<%s[%s]%s=>%s]>' % (self.__class__.__name__, self.ui_section,
419 return '<%s[%s]%s=>%s]>' % (self.__class__.__name__, self.ui_section,
420 self.ui_key, self.ui_value)
420 self.ui_key, self.ui_value)
421
421
422
422
423 class RepoRhodeCodeSetting(Base, BaseModel):
423 class RepoRhodeCodeSetting(Base, BaseModel):
424 __tablename__ = 'repo_rhodecode_settings'
424 __tablename__ = 'repo_rhodecode_settings'
425 __table_args__ = (
425 __table_args__ = (
426 UniqueConstraint(
426 UniqueConstraint(
427 'app_settings_name', 'repository_id',
427 'app_settings_name', 'repository_id',
428 name='uq_repo_rhodecode_setting_name_repo_id'),
428 name='uq_repo_rhodecode_setting_name_repo_id'),
429 {'extend_existing': True, 'mysql_engine': 'InnoDB',
429 {'extend_existing': True, 'mysql_engine': 'InnoDB',
430 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
430 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
431 )
431 )
432
432
433 repository_id = Column(
433 repository_id = Column(
434 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
434 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
435 nullable=False)
435 nullable=False)
436 app_settings_id = Column(
436 app_settings_id = Column(
437 "app_settings_id", Integer(), nullable=False, unique=True,
437 "app_settings_id", Integer(), nullable=False, unique=True,
438 default=None, primary_key=True)
438 default=None, primary_key=True)
439 app_settings_name = Column(
439 app_settings_name = Column(
440 "app_settings_name", String(255), nullable=True, unique=None,
440 "app_settings_name", String(255), nullable=True, unique=None,
441 default=None)
441 default=None)
442 _app_settings_value = Column(
442 _app_settings_value = Column(
443 "app_settings_value", String(4096), nullable=True, unique=None,
443 "app_settings_value", String(4096), nullable=True, unique=None,
444 default=None)
444 default=None)
445 _app_settings_type = Column(
445 _app_settings_type = Column(
446 "app_settings_type", String(255), nullable=True, unique=None,
446 "app_settings_type", String(255), nullable=True, unique=None,
447 default=None)
447 default=None)
448
448
449 repository = relationship('Repository')
449 repository = relationship('Repository')
450
450
451 def __init__(self, repository_id, key='', val='', type='unicode'):
451 def __init__(self, repository_id, key='', val='', type='unicode'):
452 self.repository_id = repository_id
452 self.repository_id = repository_id
453 self.app_settings_name = key
453 self.app_settings_name = key
454 self.app_settings_type = type
454 self.app_settings_type = type
455 self.app_settings_value = val
455 self.app_settings_value = val
456
456
457 @validates('_app_settings_value')
457 @validates('_app_settings_value')
458 def validate_settings_value(self, key, val):
458 def validate_settings_value(self, key, val):
459 assert type(val) == unicode
459 assert type(val) == unicode
460 return val
460 return val
461
461
462 @hybrid_property
462 @hybrid_property
463 def app_settings_value(self):
463 def app_settings_value(self):
464 v = self._app_settings_value
464 v = self._app_settings_value
465 type_ = self.app_settings_type
465 type_ = self.app_settings_type
466 SETTINGS_TYPES = RhodeCodeSetting.SETTINGS_TYPES
466 SETTINGS_TYPES = RhodeCodeSetting.SETTINGS_TYPES
467 converter = SETTINGS_TYPES.get(type_) or SETTINGS_TYPES['unicode']
467 converter = SETTINGS_TYPES.get(type_) or SETTINGS_TYPES['unicode']
468 return converter(v)
468 return converter(v)
469
469
470 @app_settings_value.setter
470 @app_settings_value.setter
471 def app_settings_value(self, val):
471 def app_settings_value(self, val):
472 """
472 """
473 Setter that will always make sure we use unicode in app_settings_value
473 Setter that will always make sure we use unicode in app_settings_value
474
474
475 :param val:
475 :param val:
476 """
476 """
477 self._app_settings_value = safe_unicode(val)
477 self._app_settings_value = safe_unicode(val)
478
478
479 @hybrid_property
479 @hybrid_property
480 def app_settings_type(self):
480 def app_settings_type(self):
481 return self._app_settings_type
481 return self._app_settings_type
482
482
483 @app_settings_type.setter
483 @app_settings_type.setter
484 def app_settings_type(self, val):
484 def app_settings_type(self, val):
485 SETTINGS_TYPES = RhodeCodeSetting.SETTINGS_TYPES
485 SETTINGS_TYPES = RhodeCodeSetting.SETTINGS_TYPES
486 if val not in SETTINGS_TYPES:
486 if val not in SETTINGS_TYPES:
487 raise Exception('type must be one of %s got %s'
487 raise Exception('type must be one of %s got %s'
488 % (SETTINGS_TYPES.keys(), val))
488 % (SETTINGS_TYPES.keys(), val))
489 self._app_settings_type = val
489 self._app_settings_type = val
490
490
491 def __unicode__(self):
491 def __unicode__(self):
492 return u"<%s('%s:%s:%s[%s]')>" % (
492 return u"<%s('%s:%s:%s[%s]')>" % (
493 self.__class__.__name__, self.repository.repo_name,
493 self.__class__.__name__, self.repository.repo_name,
494 self.app_settings_name, self.app_settings_value,
494 self.app_settings_name, self.app_settings_value,
495 self.app_settings_type
495 self.app_settings_type
496 )
496 )
497
497
498
498
499 class RepoRhodeCodeUi(Base, BaseModel):
499 class RepoRhodeCodeUi(Base, BaseModel):
500 __tablename__ = 'repo_rhodecode_ui'
500 __tablename__ = 'repo_rhodecode_ui'
501 __table_args__ = (
501 __table_args__ = (
502 UniqueConstraint(
502 UniqueConstraint(
503 'repository_id', 'ui_section', 'ui_key',
503 'repository_id', 'ui_section', 'ui_key',
504 name='uq_repo_rhodecode_ui_repository_id_section_key'),
504 name='uq_repo_rhodecode_ui_repository_id_section_key'),
505 {'extend_existing': True, 'mysql_engine': 'InnoDB',
505 {'extend_existing': True, 'mysql_engine': 'InnoDB',
506 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
506 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
507 )
507 )
508
508
509 repository_id = Column(
509 repository_id = Column(
510 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
510 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
511 nullable=False)
511 nullable=False)
512 ui_id = Column(
512 ui_id = Column(
513 "ui_id", Integer(), nullable=False, unique=True, default=None,
513 "ui_id", Integer(), nullable=False, unique=True, default=None,
514 primary_key=True)
514 primary_key=True)
515 ui_section = Column(
515 ui_section = Column(
516 "ui_section", String(255), nullable=True, unique=None, default=None)
516 "ui_section", String(255), nullable=True, unique=None, default=None)
517 ui_key = Column(
517 ui_key = Column(
518 "ui_key", String(255), nullable=True, unique=None, default=None)
518 "ui_key", String(255), nullable=True, unique=None, default=None)
519 ui_value = Column(
519 ui_value = Column(
520 "ui_value", String(255), nullable=True, unique=None, default=None)
520 "ui_value", String(255), nullable=True, unique=None, default=None)
521 ui_active = Column(
521 ui_active = Column(
522 "ui_active", Boolean(), nullable=True, unique=None, default=True)
522 "ui_active", Boolean(), nullable=True, unique=None, default=True)
523
523
524 repository = relationship('Repository')
524 repository = relationship('Repository')
525
525
526 def __repr__(self):
526 def __repr__(self):
527 return '<%s[%s:%s]%s=>%s]>' % (
527 return '<%s[%s:%s]%s=>%s]>' % (
528 self.__class__.__name__, self.repository.repo_name,
528 self.__class__.__name__, self.repository.repo_name,
529 self.ui_section, self.ui_key, self.ui_value)
529 self.ui_section, self.ui_key, self.ui_value)
530
530
531
531
532 class User(Base, BaseModel):
532 class User(Base, BaseModel):
533 __tablename__ = 'users'
533 __tablename__ = 'users'
534 __table_args__ = (
534 __table_args__ = (
535 UniqueConstraint('username'), UniqueConstraint('email'),
535 UniqueConstraint('username'), UniqueConstraint('email'),
536 Index('u_username_idx', 'username'),
536 Index('u_username_idx', 'username'),
537 Index('u_email_idx', 'email'),
537 Index('u_email_idx', 'email'),
538 {'extend_existing': True, 'mysql_engine': 'InnoDB',
538 {'extend_existing': True, 'mysql_engine': 'InnoDB',
539 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
539 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
540 )
540 )
541 DEFAULT_USER = 'default'
541 DEFAULT_USER = 'default'
542 DEFAULT_USER_EMAIL = 'anonymous@rhodecode.org'
542 DEFAULT_USER_EMAIL = 'anonymous@rhodecode.org'
543 DEFAULT_GRAVATAR_URL = 'https://secure.gravatar.com/avatar/{md5email}?d=identicon&s={size}'
543 DEFAULT_GRAVATAR_URL = 'https://secure.gravatar.com/avatar/{md5email}?d=identicon&s={size}'
544
544
545 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
545 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
546 username = Column("username", String(255), nullable=True, unique=None, default=None)
546 username = Column("username", String(255), nullable=True, unique=None, default=None)
547 password = Column("password", String(255), nullable=True, unique=None, default=None)
547 password = Column("password", String(255), nullable=True, unique=None, default=None)
548 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
548 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
549 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
549 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
550 name = Column("firstname", String(255), nullable=True, unique=None, default=None)
550 name = Column("firstname", String(255), nullable=True, unique=None, default=None)
551 lastname = Column("lastname", String(255), nullable=True, unique=None, default=None)
551 lastname = Column("lastname", String(255), nullable=True, unique=None, default=None)
552 _email = Column("email", String(255), nullable=True, unique=None, default=None)
552 _email = Column("email", String(255), nullable=True, unique=None, default=None)
553 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
553 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
554 last_activity = Column('last_activity', DateTime(timezone=False), nullable=True, unique=None, default=None)
554 last_activity = Column('last_activity', DateTime(timezone=False), nullable=True, unique=None, default=None)
555
555
556 extern_type = Column("extern_type", String(255), nullable=True, unique=None, default=None)
556 extern_type = Column("extern_type", String(255), nullable=True, unique=None, default=None)
557 extern_name = Column("extern_name", String(255), nullable=True, unique=None, default=None)
557 extern_name = Column("extern_name", String(255), nullable=True, unique=None, default=None)
558 _api_key = Column("api_key", String(255), nullable=True, unique=None, default=None)
558 _api_key = Column("api_key", String(255), nullable=True, unique=None, default=None)
559 inherit_default_permissions = Column("inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
559 inherit_default_permissions = Column("inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
560 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
560 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
561 _user_data = Column("user_data", LargeBinary(), nullable=True) # JSON data
561 _user_data = Column("user_data", LargeBinary(), nullable=True) # JSON data
562
562
563 user_log = relationship('UserLog')
563 user_log = relationship('UserLog')
564 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
564 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
565
565
566 repositories = relationship('Repository')
566 repositories = relationship('Repository')
567 repository_groups = relationship('RepoGroup')
567 repository_groups = relationship('RepoGroup')
568 user_groups = relationship('UserGroup')
568 user_groups = relationship('UserGroup')
569
569
570 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
570 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
571 followings = relationship('UserFollowing', primaryjoin='UserFollowing.user_id==User.user_id', cascade='all')
571 followings = relationship('UserFollowing', primaryjoin='UserFollowing.user_id==User.user_id', cascade='all')
572
572
573 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
573 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
574 repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all')
574 repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all')
575 user_group_to_perm = relationship('UserUserGroupToPerm', primaryjoin='UserUserGroupToPerm.user_id==User.user_id', cascade='all')
575 user_group_to_perm = relationship('UserUserGroupToPerm', primaryjoin='UserUserGroupToPerm.user_id==User.user_id', cascade='all')
576
576
577 group_member = relationship('UserGroupMember', cascade='all')
577 group_member = relationship('UserGroupMember', cascade='all')
578
578
579 notifications = relationship('UserNotification', cascade='all')
579 notifications = relationship('UserNotification', cascade='all')
580 # notifications assigned to this user
580 # notifications assigned to this user
581 user_created_notifications = relationship('Notification', cascade='all')
581 user_created_notifications = relationship('Notification', cascade='all')
582 # comments created by this user
582 # comments created by this user
583 user_comments = relationship('ChangesetComment', cascade='all')
583 user_comments = relationship('ChangesetComment', cascade='all')
584 # user profile extra info
584 # user profile extra info
585 user_emails = relationship('UserEmailMap', cascade='all')
585 user_emails = relationship('UserEmailMap', cascade='all')
586 user_ip_map = relationship('UserIpMap', cascade='all')
586 user_ip_map = relationship('UserIpMap', cascade='all')
587 user_auth_tokens = relationship('UserApiKeys', cascade='all')
587 user_auth_tokens = relationship('UserApiKeys', cascade='all')
588 user_ssh_keys = relationship('UserSshKeys', cascade='all')
588 user_ssh_keys = relationship('UserSshKeys', cascade='all')
589
589
590 # gists
590 # gists
591 user_gists = relationship('Gist', cascade='all')
591 user_gists = relationship('Gist', cascade='all')
592 # user pull requests
592 # user pull requests
593 user_pull_requests = relationship('PullRequest', cascade='all')
593 user_pull_requests = relationship('PullRequest', cascade='all')
594 # external identities
594 # external identities
595 extenal_identities = relationship(
595 extenal_identities = relationship(
596 'ExternalIdentity',
596 'ExternalIdentity',
597 primaryjoin="User.user_id==ExternalIdentity.local_user_id",
597 primaryjoin="User.user_id==ExternalIdentity.local_user_id",
598 cascade='all')
598 cascade='all')
599 # review rules
599 # review rules
600 user_review_rules = relationship('RepoReviewRuleUser', cascade='all')
600 user_review_rules = relationship('RepoReviewRuleUser', cascade='all')
601
601
602 def __unicode__(self):
602 def __unicode__(self):
603 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
603 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
604 self.user_id, self.username)
604 self.user_id, self.username)
605
605
606 @hybrid_property
606 @hybrid_property
607 def email(self):
607 def email(self):
608 return self._email
608 return self._email
609
609
610 @email.setter
610 @email.setter
611 def email(self, val):
611 def email(self, val):
612 self._email = val.lower() if val else None
612 self._email = val.lower() if val else None
613
613
614 @hybrid_property
614 @hybrid_property
615 def first_name(self):
615 def first_name(self):
616 from rhodecode.lib import helpers as h
616 from rhodecode.lib import helpers as h
617 if self.name:
617 if self.name:
618 return h.escape(self.name)
618 return h.escape(self.name)
619 return self.name
619 return self.name
620
620
621 @hybrid_property
621 @hybrid_property
622 def last_name(self):
622 def last_name(self):
623 from rhodecode.lib import helpers as h
623 from rhodecode.lib import helpers as h
624 if self.lastname:
624 if self.lastname:
625 return h.escape(self.lastname)
625 return h.escape(self.lastname)
626 return self.lastname
626 return self.lastname
627
627
628 @hybrid_property
628 @hybrid_property
629 def api_key(self):
629 def api_key(self):
630 """
630 """
631 Fetch if exist an auth-token with role ALL connected to this user
631 Fetch if exist an auth-token with role ALL connected to this user
632 """
632 """
633 user_auth_token = UserApiKeys.query()\
633 user_auth_token = UserApiKeys.query()\
634 .filter(UserApiKeys.user_id == self.user_id)\
634 .filter(UserApiKeys.user_id == self.user_id)\
635 .filter(or_(UserApiKeys.expires == -1,
635 .filter(or_(UserApiKeys.expires == -1,
636 UserApiKeys.expires >= time.time()))\
636 UserApiKeys.expires >= time.time()))\
637 .filter(UserApiKeys.role == UserApiKeys.ROLE_ALL).first()
637 .filter(UserApiKeys.role == UserApiKeys.ROLE_ALL).first()
638 if user_auth_token:
638 if user_auth_token:
639 user_auth_token = user_auth_token.api_key
639 user_auth_token = user_auth_token.api_key
640
640
641 return user_auth_token
641 return user_auth_token
642
642
643 @api_key.setter
643 @api_key.setter
644 def api_key(self, val):
644 def api_key(self, val):
645 # don't allow to set API key this is deprecated for now
645 # don't allow to set API key this is deprecated for now
646 self._api_key = None
646 self._api_key = None
647
647
648 @property
648 @property
649 def reviewer_pull_requests(self):
649 def reviewer_pull_requests(self):
650 return PullRequestReviewers.query() \
650 return PullRequestReviewers.query() \
651 .options(joinedload(PullRequestReviewers.pull_request)) \
651 .options(joinedload(PullRequestReviewers.pull_request)) \
652 .filter(PullRequestReviewers.user_id == self.user_id) \
652 .filter(PullRequestReviewers.user_id == self.user_id) \
653 .all()
653 .all()
654
654
655 @property
655 @property
656 def firstname(self):
656 def firstname(self):
657 # alias for future
657 # alias for future
658 return self.name
658 return self.name
659
659
660 @property
660 @property
661 def emails(self):
661 def emails(self):
662 other = UserEmailMap.query()\
662 other = UserEmailMap.query()\
663 .filter(UserEmailMap.user == self) \
663 .filter(UserEmailMap.user == self) \
664 .order_by(UserEmailMap.email_id.asc()) \
664 .order_by(UserEmailMap.email_id.asc()) \
665 .all()
665 .all()
666 return [self.email] + [x.email for x in other]
666 return [self.email] + [x.email for x in other]
667
667
668 @property
668 @property
669 def auth_tokens(self):
669 def auth_tokens(self):
670 auth_tokens = self.get_auth_tokens()
670 auth_tokens = self.get_auth_tokens()
671 return [x.api_key for x in auth_tokens]
671 return [x.api_key for x in auth_tokens]
672
672
673 def get_auth_tokens(self):
673 def get_auth_tokens(self):
674 return UserApiKeys.query()\
674 return UserApiKeys.query()\
675 .filter(UserApiKeys.user == self)\
675 .filter(UserApiKeys.user == self)\
676 .order_by(UserApiKeys.user_api_key_id.asc())\
676 .order_by(UserApiKeys.user_api_key_id.asc())\
677 .all()
677 .all()
678
678
679 @LazyProperty
679 @LazyProperty
680 def feed_token(self):
680 def feed_token(self):
681 return self.get_feed_token()
681 return self.get_feed_token()
682
682
683 def get_feed_token(self, cache=True):
683 def get_feed_token(self, cache=True):
684 feed_tokens = UserApiKeys.query()\
684 feed_tokens = UserApiKeys.query()\
685 .filter(UserApiKeys.user == self)\
685 .filter(UserApiKeys.user == self)\
686 .filter(UserApiKeys.role == UserApiKeys.ROLE_FEED)
686 .filter(UserApiKeys.role == UserApiKeys.ROLE_FEED)
687 if cache:
687 if cache:
688 feed_tokens = feed_tokens.options(
688 feed_tokens = feed_tokens.options(
689 FromCache("long_term", "get_user_feed_token_%s" % self.user_id))
689 FromCache("long_term", "get_user_feed_token_%s" % self.user_id))
690
690
691 feed_tokens = feed_tokens.all()
691 feed_tokens = feed_tokens.all()
692 if feed_tokens:
692 if feed_tokens:
693 return feed_tokens[0].api_key
693 return feed_tokens[0].api_key
694 return 'NO_FEED_TOKEN_AVAILABLE'
694 return 'NO_FEED_TOKEN_AVAILABLE'
695
695
696 @classmethod
696 @classmethod
697 def get(cls, user_id, cache=False):
697 def get(cls, user_id, cache=False):
698 if not user_id:
698 if not user_id:
699 return
699 return
700
700
701 user = cls.query()
701 user = cls.query()
702 if cache:
702 if cache:
703 user = user.options(
703 user = user.options(
704 FromCache("sql_cache_short", "get_users_%s" % user_id))
704 FromCache("sql_cache_short", "get_users_%s" % user_id))
705 return user.get(user_id)
705 return user.get(user_id)
706
706
707 @classmethod
707 @classmethod
708 def extra_valid_auth_tokens(cls, user, role=None):
708 def extra_valid_auth_tokens(cls, user, role=None):
709 tokens = UserApiKeys.query().filter(UserApiKeys.user == user)\
709 tokens = UserApiKeys.query().filter(UserApiKeys.user == user)\
710 .filter(or_(UserApiKeys.expires == -1,
710 .filter(or_(UserApiKeys.expires == -1,
711 UserApiKeys.expires >= time.time()))
711 UserApiKeys.expires >= time.time()))
712 if role:
712 if role:
713 tokens = tokens.filter(or_(UserApiKeys.role == role,
713 tokens = tokens.filter(or_(UserApiKeys.role == role,
714 UserApiKeys.role == UserApiKeys.ROLE_ALL))
714 UserApiKeys.role == UserApiKeys.ROLE_ALL))
715 return tokens.all()
715 return tokens.all()
716
716
717 def authenticate_by_token(self, auth_token, roles=None, scope_repo_id=None):
717 def authenticate_by_token(self, auth_token, roles=None, scope_repo_id=None):
718 from rhodecode.lib import auth
718 from rhodecode.lib import auth
719
719
720 log.debug('Trying to authenticate user: %s via auth-token, '
720 log.debug('Trying to authenticate user: %s via auth-token, '
721 'and roles: %s', self, roles)
721 'and roles: %s', self, roles)
722
722
723 if not auth_token:
723 if not auth_token:
724 return False
724 return False
725
725
726 crypto_backend = auth.crypto_backend()
726 crypto_backend = auth.crypto_backend()
727
727
728 roles = (roles or []) + [UserApiKeys.ROLE_ALL]
728 roles = (roles or []) + [UserApiKeys.ROLE_ALL]
729 tokens_q = UserApiKeys.query()\
729 tokens_q = UserApiKeys.query()\
730 .filter(UserApiKeys.user_id == self.user_id)\
730 .filter(UserApiKeys.user_id == self.user_id)\
731 .filter(or_(UserApiKeys.expires == -1,
731 .filter(or_(UserApiKeys.expires == -1,
732 UserApiKeys.expires >= time.time()))
732 UserApiKeys.expires >= time.time()))
733
733
734 tokens_q = tokens_q.filter(UserApiKeys.role.in_(roles))
734 tokens_q = tokens_q.filter(UserApiKeys.role.in_(roles))
735
735
736 plain_tokens = []
736 plain_tokens = []
737 hash_tokens = []
737 hash_tokens = []
738
738
739 for token in tokens_q.all():
739 for token in tokens_q.all():
740 # verify scope first
740 # verify scope first
741 if token.repo_id:
741 if token.repo_id:
742 # token has a scope, we need to verify it
742 # token has a scope, we need to verify it
743 if scope_repo_id != token.repo_id:
743 if scope_repo_id != token.repo_id:
744 log.debug(
744 log.debug(
745 'Scope mismatch: token has a set repo scope: %s, '
745 'Scope mismatch: token has a set repo scope: %s, '
746 'and calling scope is:%s, skipping further checks',
746 'and calling scope is:%s, skipping further checks',
747 token.repo, scope_repo_id)
747 token.repo, scope_repo_id)
748 # token has a scope, and it doesn't match, skip token
748 # token has a scope, and it doesn't match, skip token
749 continue
749 continue
750
750
751 if token.api_key.startswith(crypto_backend.ENC_PREF):
751 if token.api_key.startswith(crypto_backend.ENC_PREF):
752 hash_tokens.append(token.api_key)
752 hash_tokens.append(token.api_key)
753 else:
753 else:
754 plain_tokens.append(token.api_key)
754 plain_tokens.append(token.api_key)
755
755
756 is_plain_match = auth_token in plain_tokens
756 is_plain_match = auth_token in plain_tokens
757 if is_plain_match:
757 if is_plain_match:
758 return True
758 return True
759
759
760 for hashed in hash_tokens:
760 for hashed in hash_tokens:
761 # TODO(marcink): this is expensive to calculate, but most secure
761 # TODO(marcink): this is expensive to calculate, but most secure
762 match = crypto_backend.hash_check(auth_token, hashed)
762 match = crypto_backend.hash_check(auth_token, hashed)
763 if match:
763 if match:
764 return True
764 return True
765
765
766 return False
766 return False
767
767
768 @property
768 @property
769 def ip_addresses(self):
769 def ip_addresses(self):
770 ret = UserIpMap.query().filter(UserIpMap.user == self).all()
770 ret = UserIpMap.query().filter(UserIpMap.user == self).all()
771 return [x.ip_addr for x in ret]
771 return [x.ip_addr for x in ret]
772
772
773 @property
773 @property
774 def username_and_name(self):
774 def username_and_name(self):
775 return '%s (%s %s)' % (self.username, self.first_name, self.last_name)
775 return '%s (%s %s)' % (self.username, self.first_name, self.last_name)
776
776
777 @property
777 @property
778 def username_or_name_or_email(self):
778 def username_or_name_or_email(self):
779 full_name = self.full_name if self.full_name is not ' ' else None
779 full_name = self.full_name if self.full_name is not ' ' else None
780 return self.username or full_name or self.email
780 return self.username or full_name or self.email
781
781
782 @property
782 @property
783 def full_name(self):
783 def full_name(self):
784 return '%s %s' % (self.first_name, self.last_name)
784 return '%s %s' % (self.first_name, self.last_name)
785
785
786 @property
786 @property
787 def full_name_or_username(self):
787 def full_name_or_username(self):
788 return ('%s %s' % (self.first_name, self.last_name)
788 return ('%s %s' % (self.first_name, self.last_name)
789 if (self.first_name and self.last_name) else self.username)
789 if (self.first_name and self.last_name) else self.username)
790
790
791 @property
791 @property
792 def full_contact(self):
792 def full_contact(self):
793 return '%s %s <%s>' % (self.first_name, self.last_name, self.email)
793 return '%s %s <%s>' % (self.first_name, self.last_name, self.email)
794
794
795 @property
795 @property
796 def short_contact(self):
796 def short_contact(self):
797 return '%s %s' % (self.first_name, self.last_name)
797 return '%s %s' % (self.first_name, self.last_name)
798
798
799 @property
799 @property
800 def is_admin(self):
800 def is_admin(self):
801 return self.admin
801 return self.admin
802
802
803 def AuthUser(self, **kwargs):
803 def AuthUser(self, **kwargs):
804 """
804 """
805 Returns instance of AuthUser for this user
805 Returns instance of AuthUser for this user
806 """
806 """
807 from rhodecode.lib.auth import AuthUser
807 from rhodecode.lib.auth import AuthUser
808 return AuthUser(user_id=self.user_id, username=self.username, **kwargs)
808 return AuthUser(user_id=self.user_id, username=self.username, **kwargs)
809
809
810 @hybrid_property
810 @hybrid_property
811 def user_data(self):
811 def user_data(self):
812 if not self._user_data:
812 if not self._user_data:
813 return {}
813 return {}
814
814
815 try:
815 try:
816 return json.loads(self._user_data)
816 return json.loads(self._user_data)
817 except TypeError:
817 except TypeError:
818 return {}
818 return {}
819
819
820 @user_data.setter
820 @user_data.setter
821 def user_data(self, val):
821 def user_data(self, val):
822 if not isinstance(val, dict):
822 if not isinstance(val, dict):
823 raise Exception('user_data must be dict, got %s' % type(val))
823 raise Exception('user_data must be dict, got %s' % type(val))
824 try:
824 try:
825 self._user_data = json.dumps(val)
825 self._user_data = json.dumps(val)
826 except Exception:
826 except Exception:
827 log.error(traceback.format_exc())
827 log.error(traceback.format_exc())
828
828
829 @classmethod
829 @classmethod
830 def get_by_username(cls, username, case_insensitive=False,
830 def get_by_username(cls, username, case_insensitive=False,
831 cache=False, identity_cache=False):
831 cache=False, identity_cache=False):
832 session = Session()
832 session = Session()
833
833
834 if case_insensitive:
834 if case_insensitive:
835 q = cls.query().filter(
835 q = cls.query().filter(
836 func.lower(cls.username) == func.lower(username))
836 func.lower(cls.username) == func.lower(username))
837 else:
837 else:
838 q = cls.query().filter(cls.username == username)
838 q = cls.query().filter(cls.username == username)
839
839
840 if cache:
840 if cache:
841 if identity_cache:
841 if identity_cache:
842 val = cls.identity_cache(session, 'username', username)
842 val = cls.identity_cache(session, 'username', username)
843 if val:
843 if val:
844 return val
844 return val
845 else:
845 else:
846 cache_key = "get_user_by_name_%s" % _hash_key(username)
846 cache_key = "get_user_by_name_%s" % _hash_key(username)
847 q = q.options(
847 q = q.options(
848 FromCache("sql_cache_short", cache_key))
848 FromCache("sql_cache_short", cache_key))
849
849
850 return q.scalar()
850 return q.scalar()
851
851
852 @classmethod
852 @classmethod
853 def get_by_auth_token(cls, auth_token, cache=False):
853 def get_by_auth_token(cls, auth_token, cache=False):
854 q = UserApiKeys.query()\
854 q = UserApiKeys.query()\
855 .filter(UserApiKeys.api_key == auth_token)\
855 .filter(UserApiKeys.api_key == auth_token)\
856 .filter(or_(UserApiKeys.expires == -1,
856 .filter(or_(UserApiKeys.expires == -1,
857 UserApiKeys.expires >= time.time()))
857 UserApiKeys.expires >= time.time()))
858 if cache:
858 if cache:
859 q = q.options(
859 q = q.options(
860 FromCache("sql_cache_short", "get_auth_token_%s" % auth_token))
860 FromCache("sql_cache_short", "get_auth_token_%s" % auth_token))
861
861
862 match = q.first()
862 match = q.first()
863 if match:
863 if match:
864 return match.user
864 return match.user
865
865
866 @classmethod
866 @classmethod
867 def get_by_email(cls, email, case_insensitive=False, cache=False):
867 def get_by_email(cls, email, case_insensitive=False, cache=False):
868
868
869 if case_insensitive:
869 if case_insensitive:
870 q = cls.query().filter(func.lower(cls.email) == func.lower(email))
870 q = cls.query().filter(func.lower(cls.email) == func.lower(email))
871
871
872 else:
872 else:
873 q = cls.query().filter(cls.email == email)
873 q = cls.query().filter(cls.email == email)
874
874
875 email_key = _hash_key(email)
875 email_key = _hash_key(email)
876 if cache:
876 if cache:
877 q = q.options(
877 q = q.options(
878 FromCache("sql_cache_short", "get_email_key_%s" % email_key))
878 FromCache("sql_cache_short", "get_email_key_%s" % email_key))
879
879
880 ret = q.scalar()
880 ret = q.scalar()
881 if ret is None:
881 if ret is None:
882 q = UserEmailMap.query()
882 q = UserEmailMap.query()
883 # try fetching in alternate email map
883 # try fetching in alternate email map
884 if case_insensitive:
884 if case_insensitive:
885 q = q.filter(func.lower(UserEmailMap.email) == func.lower(email))
885 q = q.filter(func.lower(UserEmailMap.email) == func.lower(email))
886 else:
886 else:
887 q = q.filter(UserEmailMap.email == email)
887 q = q.filter(UserEmailMap.email == email)
888 q = q.options(joinedload(UserEmailMap.user))
888 q = q.options(joinedload(UserEmailMap.user))
889 if cache:
889 if cache:
890 q = q.options(
890 q = q.options(
891 FromCache("sql_cache_short", "get_email_map_key_%s" % email_key))
891 FromCache("sql_cache_short", "get_email_map_key_%s" % email_key))
892 ret = getattr(q.scalar(), 'user', None)
892 ret = getattr(q.scalar(), 'user', None)
893
893
894 return ret
894 return ret
895
895
896 @classmethod
896 @classmethod
897 def get_from_cs_author(cls, author):
897 def get_from_cs_author(cls, author):
898 """
898 """
899 Tries to get User objects out of commit author string
899 Tries to get User objects out of commit author string
900
900
901 :param author:
901 :param author:
902 """
902 """
903 from rhodecode.lib.helpers import email, author_name
903 from rhodecode.lib.helpers import email, author_name
904 # Valid email in the attribute passed, see if they're in the system
904 # Valid email in the attribute passed, see if they're in the system
905 _email = email(author)
905 _email = email(author)
906 if _email:
906 if _email:
907 user = cls.get_by_email(_email, case_insensitive=True)
907 user = cls.get_by_email(_email, case_insensitive=True)
908 if user:
908 if user:
909 return user
909 return user
910 # Maybe we can match by username?
910 # Maybe we can match by username?
911 _author = author_name(author)
911 _author = author_name(author)
912 user = cls.get_by_username(_author, case_insensitive=True)
912 user = cls.get_by_username(_author, case_insensitive=True)
913 if user:
913 if user:
914 return user
914 return user
915
915
916 def update_userdata(self, **kwargs):
916 def update_userdata(self, **kwargs):
917 usr = self
917 usr = self
918 old = usr.user_data
918 old = usr.user_data
919 old.update(**kwargs)
919 old.update(**kwargs)
920 usr.user_data = old
920 usr.user_data = old
921 Session().add(usr)
921 Session().add(usr)
922 log.debug('updated userdata with ', kwargs)
922 log.debug('updated userdata with ', kwargs)
923
923
924 def update_lastlogin(self):
924 def update_lastlogin(self):
925 """Update user lastlogin"""
925 """Update user lastlogin"""
926 self.last_login = datetime.datetime.now()
926 self.last_login = datetime.datetime.now()
927 Session().add(self)
927 Session().add(self)
928 log.debug('updated user %s lastlogin', self.username)
928 log.debug('updated user %s lastlogin', self.username)
929
929
930 def update_lastactivity(self):
930 def update_lastactivity(self):
931 """Update user lastactivity"""
931 """Update user lastactivity"""
932 self.last_activity = datetime.datetime.now()
932 self.last_activity = datetime.datetime.now()
933 Session().add(self)
933 Session().add(self)
934 log.debug('updated user `%s` last activity', self.username)
934 log.debug('updated user `%s` last activity', self.username)
935
935
936 def update_password(self, new_password):
936 def update_password(self, new_password):
937 from rhodecode.lib.auth import get_crypt_password
937 from rhodecode.lib.auth import get_crypt_password
938
938
939 self.password = get_crypt_password(new_password)
939 self.password = get_crypt_password(new_password)
940 Session().add(self)
940 Session().add(self)
941
941
942 @classmethod
942 @classmethod
943 def get_first_super_admin(cls):
943 def get_first_super_admin(cls):
944 user = User.query().filter(User.admin == true()).first()
944 user = User.query().filter(User.admin == true()).first()
945 if user is None:
945 if user is None:
946 raise Exception('FATAL: Missing administrative account!')
946 raise Exception('FATAL: Missing administrative account!')
947 return user
947 return user
948
948
949 @classmethod
949 @classmethod
950 def get_all_super_admins(cls):
950 def get_all_super_admins(cls):
951 """
951 """
952 Returns all admin accounts sorted by username
952 Returns all admin accounts sorted by username
953 """
953 """
954 return User.query().filter(User.admin == true())\
954 return User.query().filter(User.admin == true())\
955 .order_by(User.username.asc()).all()
955 .order_by(User.username.asc()).all()
956
956
957 @classmethod
957 @classmethod
958 def get_default_user(cls, cache=False, refresh=False):
958 def get_default_user(cls, cache=False, refresh=False):
959 user = User.get_by_username(User.DEFAULT_USER, cache=cache)
959 user = User.get_by_username(User.DEFAULT_USER, cache=cache)
960 if user is None:
960 if user is None:
961 raise Exception('FATAL: Missing default account!')
961 raise Exception('FATAL: Missing default account!')
962 if refresh:
962 if refresh:
963 # The default user might be based on outdated state which
963 # The default user might be based on outdated state which
964 # has been loaded from the cache.
964 # has been loaded from the cache.
965 # A call to refresh() ensures that the
965 # A call to refresh() ensures that the
966 # latest state from the database is used.
966 # latest state from the database is used.
967 Session().refresh(user)
967 Session().refresh(user)
968 return user
968 return user
969
969
970 def _get_default_perms(self, user, suffix=''):
970 def _get_default_perms(self, user, suffix=''):
971 from rhodecode.model.permission import PermissionModel
971 from rhodecode.model.permission import PermissionModel
972 return PermissionModel().get_default_perms(user.user_perms, suffix)
972 return PermissionModel().get_default_perms(user.user_perms, suffix)
973
973
974 def get_default_perms(self, suffix=''):
974 def get_default_perms(self, suffix=''):
975 return self._get_default_perms(self, suffix)
975 return self._get_default_perms(self, suffix)
976
976
977 def get_api_data(self, include_secrets=False, details='full'):
977 def get_api_data(self, include_secrets=False, details='full'):
978 """
978 """
979 Common function for generating user related data for API
979 Common function for generating user related data for API
980
980
981 :param include_secrets: By default secrets in the API data will be replaced
981 :param include_secrets: By default secrets in the API data will be replaced
982 by a placeholder value to prevent exposing this data by accident. In case
982 by a placeholder value to prevent exposing this data by accident. In case
983 this data shall be exposed, set this flag to ``True``.
983 this data shall be exposed, set this flag to ``True``.
984
984
985 :param details: details can be 'basic|full' basic gives only a subset of
985 :param details: details can be 'basic|full' basic gives only a subset of
986 the available user information that includes user_id, name and emails.
986 the available user information that includes user_id, name and emails.
987 """
987 """
988 user = self
988 user = self
989 user_data = self.user_data
989 user_data = self.user_data
990 data = {
990 data = {
991 'user_id': user.user_id,
991 'user_id': user.user_id,
992 'username': user.username,
992 'username': user.username,
993 'firstname': user.name,
993 'firstname': user.name,
994 'lastname': user.lastname,
994 'lastname': user.lastname,
995 'email': user.email,
995 'email': user.email,
996 'emails': user.emails,
996 'emails': user.emails,
997 }
997 }
998 if details == 'basic':
998 if details == 'basic':
999 return data
999 return data
1000
1000
1001 auth_token_length = 40
1001 auth_token_length = 40
1002 auth_token_replacement = '*' * auth_token_length
1002 auth_token_replacement = '*' * auth_token_length
1003
1003
1004 extras = {
1004 extras = {
1005 'auth_tokens': [auth_token_replacement],
1005 'auth_tokens': [auth_token_replacement],
1006 'active': user.active,
1006 'active': user.active,
1007 'admin': user.admin,
1007 'admin': user.admin,
1008 'extern_type': user.extern_type,
1008 'extern_type': user.extern_type,
1009 'extern_name': user.extern_name,
1009 'extern_name': user.extern_name,
1010 'last_login': user.last_login,
1010 'last_login': user.last_login,
1011 'last_activity': user.last_activity,
1011 'last_activity': user.last_activity,
1012 'ip_addresses': user.ip_addresses,
1012 'ip_addresses': user.ip_addresses,
1013 'language': user_data.get('language')
1013 'language': user_data.get('language')
1014 }
1014 }
1015 data.update(extras)
1015 data.update(extras)
1016
1016
1017 if include_secrets:
1017 if include_secrets:
1018 data['auth_tokens'] = user.auth_tokens
1018 data['auth_tokens'] = user.auth_tokens
1019 return data
1019 return data
1020
1020
1021 def __json__(self):
1021 def __json__(self):
1022 data = {
1022 data = {
1023 'full_name': self.full_name,
1023 'full_name': self.full_name,
1024 'full_name_or_username': self.full_name_or_username,
1024 'full_name_or_username': self.full_name_or_username,
1025 'short_contact': self.short_contact,
1025 'short_contact': self.short_contact,
1026 'full_contact': self.full_contact,
1026 'full_contact': self.full_contact,
1027 }
1027 }
1028 data.update(self.get_api_data())
1028 data.update(self.get_api_data())
1029 return data
1029 return data
1030
1030
1031
1031
1032 class UserApiKeys(Base, BaseModel):
1032 class UserApiKeys(Base, BaseModel):
1033 __tablename__ = 'user_api_keys'
1033 __tablename__ = 'user_api_keys'
1034 __table_args__ = (
1034 __table_args__ = (
1035 Index('uak_api_key_idx', 'api_key', unique=True),
1035 Index('uak_api_key_idx', 'api_key', unique=True),
1036 Index('uak_api_key_expires_idx', 'api_key', 'expires'),
1036 Index('uak_api_key_expires_idx', 'api_key', 'expires'),
1037 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1037 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1038 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
1038 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
1039 )
1039 )
1040 __mapper_args__ = {}
1040 __mapper_args__ = {}
1041
1041
1042 # ApiKey role
1042 # ApiKey role
1043 ROLE_ALL = 'token_role_all'
1043 ROLE_ALL = 'token_role_all'
1044 ROLE_HTTP = 'token_role_http'
1044 ROLE_HTTP = 'token_role_http'
1045 ROLE_VCS = 'token_role_vcs'
1045 ROLE_VCS = 'token_role_vcs'
1046 ROLE_API = 'token_role_api'
1046 ROLE_API = 'token_role_api'
1047 ROLE_FEED = 'token_role_feed'
1047 ROLE_FEED = 'token_role_feed'
1048 ROLE_PASSWORD_RESET = 'token_password_reset'
1048 ROLE_PASSWORD_RESET = 'token_password_reset'
1049
1049
1050 ROLES = [ROLE_ALL, ROLE_HTTP, ROLE_VCS, ROLE_API, ROLE_FEED]
1050 ROLES = [ROLE_ALL, ROLE_HTTP, ROLE_VCS, ROLE_API, ROLE_FEED]
1051
1051
1052 user_api_key_id = Column("user_api_key_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1052 user_api_key_id = Column("user_api_key_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1053 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1053 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1054 api_key = Column("api_key", String(255), nullable=False, unique=True)
1054 api_key = Column("api_key", String(255), nullable=False, unique=True)
1055 description = Column('description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
1055 description = Column('description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
1056 expires = Column('expires', Float(53), nullable=False)
1056 expires = Column('expires', Float(53), nullable=False)
1057 role = Column('role', String(255), nullable=True)
1057 role = Column('role', String(255), nullable=True)
1058 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1058 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1059
1059
1060 # scope columns
1060 # scope columns
1061 repo_id = Column(
1061 repo_id = Column(
1062 'repo_id', Integer(), ForeignKey('repositories.repo_id'),
1062 'repo_id', Integer(), ForeignKey('repositories.repo_id'),
1063 nullable=True, unique=None, default=None)
1063 nullable=True, unique=None, default=None)
1064 repo = relationship('Repository', lazy='joined')
1064 repo = relationship('Repository', lazy='joined')
1065
1065
1066 repo_group_id = Column(
1066 repo_group_id = Column(
1067 'repo_group_id', Integer(), ForeignKey('groups.group_id'),
1067 'repo_group_id', Integer(), ForeignKey('groups.group_id'),
1068 nullable=True, unique=None, default=None)
1068 nullable=True, unique=None, default=None)
1069 repo_group = relationship('RepoGroup', lazy='joined')
1069 repo_group = relationship('RepoGroup', lazy='joined')
1070
1070
1071 user = relationship('User', lazy='joined')
1071 user = relationship('User', lazy='joined')
1072
1072
1073 def __unicode__(self):
1073 def __unicode__(self):
1074 return u"<%s('%s')>" % (self.__class__.__name__, self.role)
1074 return u"<%s('%s')>" % (self.__class__.__name__, self.role)
1075
1075
1076 def __json__(self):
1076 def __json__(self):
1077 data = {
1077 data = {
1078 'auth_token': self.api_key,
1078 'auth_token': self.api_key,
1079 'role': self.role,
1079 'role': self.role,
1080 'scope': self.scope_humanized,
1080 'scope': self.scope_humanized,
1081 'expired': self.expired
1081 'expired': self.expired
1082 }
1082 }
1083 return data
1083 return data
1084
1084
1085 def get_api_data(self, include_secrets=False):
1085 def get_api_data(self, include_secrets=False):
1086 data = self.__json__()
1086 data = self.__json__()
1087 if include_secrets:
1087 if include_secrets:
1088 return data
1088 return data
1089 else:
1089 else:
1090 data['auth_token'] = self.token_obfuscated
1090 data['auth_token'] = self.token_obfuscated
1091 return data
1091 return data
1092
1092
1093 @hybrid_property
1093 @hybrid_property
1094 def description_safe(self):
1094 def description_safe(self):
1095 from rhodecode.lib import helpers as h
1095 from rhodecode.lib import helpers as h
1096 return h.escape(self.description)
1096 return h.escape(self.description)
1097
1097
1098 @property
1098 @property
1099 def expired(self):
1099 def expired(self):
1100 if self.expires == -1:
1100 if self.expires == -1:
1101 return False
1101 return False
1102 return time.time() > self.expires
1102 return time.time() > self.expires
1103
1103
1104 @classmethod
1104 @classmethod
1105 def _get_role_name(cls, role):
1105 def _get_role_name(cls, role):
1106 return {
1106 return {
1107 cls.ROLE_ALL: _('all'),
1107 cls.ROLE_ALL: _('all'),
1108 cls.ROLE_HTTP: _('http/web interface'),
1108 cls.ROLE_HTTP: _('http/web interface'),
1109 cls.ROLE_VCS: _('vcs (git/hg/svn protocol)'),
1109 cls.ROLE_VCS: _('vcs (git/hg/svn protocol)'),
1110 cls.ROLE_API: _('api calls'),
1110 cls.ROLE_API: _('api calls'),
1111 cls.ROLE_FEED: _('feed access'),
1111 cls.ROLE_FEED: _('feed access'),
1112 }.get(role, role)
1112 }.get(role, role)
1113
1113
1114 @property
1114 @property
1115 def role_humanized(self):
1115 def role_humanized(self):
1116 return self._get_role_name(self.role)
1116 return self._get_role_name(self.role)
1117
1117
1118 def _get_scope(self):
1118 def _get_scope(self):
1119 if self.repo:
1119 if self.repo:
1120 return repr(self.repo)
1120 return repr(self.repo)
1121 if self.repo_group:
1121 if self.repo_group:
1122 return repr(self.repo_group) + ' (recursive)'
1122 return repr(self.repo_group) + ' (recursive)'
1123 return 'global'
1123 return 'global'
1124
1124
1125 @property
1125 @property
1126 def scope_humanized(self):
1126 def scope_humanized(self):
1127 return self._get_scope()
1127 return self._get_scope()
1128
1128
1129 @property
1129 @property
1130 def token_obfuscated(self):
1130 def token_obfuscated(self):
1131 if self.api_key:
1131 if self.api_key:
1132 return self.api_key[:4] + "****"
1132 return self.api_key[:4] + "****"
1133
1133
1134
1134
1135 class UserEmailMap(Base, BaseModel):
1135 class UserEmailMap(Base, BaseModel):
1136 __tablename__ = 'user_email_map'
1136 __tablename__ = 'user_email_map'
1137 __table_args__ = (
1137 __table_args__ = (
1138 Index('uem_email_idx', 'email'),
1138 Index('uem_email_idx', 'email'),
1139 UniqueConstraint('email'),
1139 UniqueConstraint('email'),
1140 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1140 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1141 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
1141 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
1142 )
1142 )
1143 __mapper_args__ = {}
1143 __mapper_args__ = {}
1144
1144
1145 email_id = Column("email_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1145 email_id = Column("email_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1146 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1146 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1147 _email = Column("email", String(255), nullable=True, unique=False, default=None)
1147 _email = Column("email", String(255), nullable=True, unique=False, default=None)
1148 user = relationship('User', lazy='joined')
1148 user = relationship('User', lazy='joined')
1149
1149
1150 @validates('_email')
1150 @validates('_email')
1151 def validate_email(self, key, email):
1151 def validate_email(self, key, email):
1152 # check if this email is not main one
1152 # check if this email is not main one
1153 main_email = Session().query(User).filter(User.email == email).scalar()
1153 main_email = Session().query(User).filter(User.email == email).scalar()
1154 if main_email is not None:
1154 if main_email is not None:
1155 raise AttributeError('email %s is present is user table' % email)
1155 raise AttributeError('email %s is present is user table' % email)
1156 return email
1156 return email
1157
1157
1158 @hybrid_property
1158 @hybrid_property
1159 def email(self):
1159 def email(self):
1160 return self._email
1160 return self._email
1161
1161
1162 @email.setter
1162 @email.setter
1163 def email(self, val):
1163 def email(self, val):
1164 self._email = val.lower() if val else None
1164 self._email = val.lower() if val else None
1165
1165
1166
1166
1167 class UserIpMap(Base, BaseModel):
1167 class UserIpMap(Base, BaseModel):
1168 __tablename__ = 'user_ip_map'
1168 __tablename__ = 'user_ip_map'
1169 __table_args__ = (
1169 __table_args__ = (
1170 UniqueConstraint('user_id', 'ip_addr'),
1170 UniqueConstraint('user_id', 'ip_addr'),
1171 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1171 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1172 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
1172 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
1173 )
1173 )
1174 __mapper_args__ = {}
1174 __mapper_args__ = {}
1175
1175
1176 ip_id = Column("ip_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1176 ip_id = Column("ip_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1177 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1177 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1178 ip_addr = Column("ip_addr", String(255), nullable=True, unique=False, default=None)
1178 ip_addr = Column("ip_addr", String(255), nullable=True, unique=False, default=None)
1179 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
1179 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
1180 description = Column("description", String(10000), nullable=True, unique=None, default=None)
1180 description = Column("description", String(10000), nullable=True, unique=None, default=None)
1181 user = relationship('User', lazy='joined')
1181 user = relationship('User', lazy='joined')
1182
1182
1183 @hybrid_property
1183 @hybrid_property
1184 def description_safe(self):
1184 def description_safe(self):
1185 from rhodecode.lib import helpers as h
1185 from rhodecode.lib import helpers as h
1186 return h.escape(self.description)
1186 return h.escape(self.description)
1187
1187
1188 @classmethod
1188 @classmethod
1189 def _get_ip_range(cls, ip_addr):
1189 def _get_ip_range(cls, ip_addr):
1190 net = ipaddress.ip_network(safe_unicode(ip_addr), strict=False)
1190 net = ipaddress.ip_network(safe_unicode(ip_addr), strict=False)
1191 return [str(net.network_address), str(net.broadcast_address)]
1191 return [str(net.network_address), str(net.broadcast_address)]
1192
1192
1193 def __json__(self):
1193 def __json__(self):
1194 return {
1194 return {
1195 'ip_addr': self.ip_addr,
1195 'ip_addr': self.ip_addr,
1196 'ip_range': self._get_ip_range(self.ip_addr),
1196 'ip_range': self._get_ip_range(self.ip_addr),
1197 }
1197 }
1198
1198
1199 def __unicode__(self):
1199 def __unicode__(self):
1200 return u"<%s('user_id:%s=>%s')>" % (self.__class__.__name__,
1200 return u"<%s('user_id:%s=>%s')>" % (self.__class__.__name__,
1201 self.user_id, self.ip_addr)
1201 self.user_id, self.ip_addr)
1202
1202
1203
1203
1204 class UserSshKeys(Base, BaseModel):
1204 class UserSshKeys(Base, BaseModel):
1205 __tablename__ = 'user_ssh_keys'
1205 __tablename__ = 'user_ssh_keys'
1206 __table_args__ = (
1206 __table_args__ = (
1207 Index('usk_ssh_key_fingerprint_idx', 'ssh_key_fingerprint'),
1207 Index('usk_ssh_key_fingerprint_idx', 'ssh_key_fingerprint'),
1208
1208
1209 UniqueConstraint('ssh_key_fingerprint'),
1209 UniqueConstraint('ssh_key_fingerprint'),
1210
1210
1211 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1211 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1212 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
1212 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
1213 )
1213 )
1214 __mapper_args__ = {}
1214 __mapper_args__ = {}
1215
1215
1216 ssh_key_id = Column('ssh_key_id', Integer(), nullable=False, unique=True, default=None, primary_key=True)
1216 ssh_key_id = Column('ssh_key_id', Integer(), nullable=False, unique=True, default=None, primary_key=True)
1217 ssh_key_data = Column('ssh_key_data', String(10240), nullable=False, unique=None, default=None)
1217 ssh_key_data = Column('ssh_key_data', String(10240), nullable=False, unique=None, default=None)
1218 ssh_key_fingerprint = Column('ssh_key_fingerprint', String(255), nullable=False, unique=None, default=None)
1218 ssh_key_fingerprint = Column('ssh_key_fingerprint', String(255), nullable=False, unique=None, default=None)
1219
1219
1220 description = Column('description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
1220 description = Column('description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
1221
1221
1222 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1222 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1223 accessed_on = Column('accessed_on', DateTime(timezone=False), nullable=True, default=None)
1223 accessed_on = Column('accessed_on', DateTime(timezone=False), nullable=True, default=None)
1224 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1224 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1225
1225
1226 user = relationship('User', lazy='joined')
1226 user = relationship('User', lazy='joined')
1227
1227
1228 def __json__(self):
1228 def __json__(self):
1229 data = {
1229 data = {
1230 'ssh_fingerprint': self.ssh_key_fingerprint,
1230 'ssh_fingerprint': self.ssh_key_fingerprint,
1231 'description': self.description,
1231 'description': self.description,
1232 'created_on': self.created_on
1232 'created_on': self.created_on
1233 }
1233 }
1234 return data
1234 return data
1235
1235
1236 def get_api_data(self):
1236 def get_api_data(self):
1237 data = self.__json__()
1237 data = self.__json__()
1238 return data
1238 return data
1239
1239
1240
1240
1241 class UserLog(Base, BaseModel):
1241 class UserLog(Base, BaseModel):
1242 __tablename__ = 'user_logs'
1242 __tablename__ = 'user_logs'
1243 __table_args__ = (
1243 __table_args__ = (
1244 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1244 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1245 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1245 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1246 )
1246 )
1247 VERSION_1 = 'v1'
1247 VERSION_1 = 'v1'
1248 VERSION_2 = 'v2'
1248 VERSION_2 = 'v2'
1249 VERSIONS = [VERSION_1, VERSION_2]
1249 VERSIONS = [VERSION_1, VERSION_2]
1250
1250
1251 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1251 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1252 user_id = Column("user_id", Integer(), ForeignKey('users.user_id',ondelete='SET NULL'), nullable=True, unique=None, default=None)
1252 user_id = Column("user_id", Integer(), ForeignKey('users.user_id',ondelete='SET NULL'), nullable=True, unique=None, default=None)
1253 username = Column("username", String(255), nullable=True, unique=None, default=None)
1253 username = Column("username", String(255), nullable=True, unique=None, default=None)
1254 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id', ondelete='SET NULL'), nullable=True, unique=None, default=None)
1254 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id', ondelete='SET NULL'), nullable=True, unique=None, default=None)
1255 repository_name = Column("repository_name", String(255), nullable=True, unique=None, default=None)
1255 repository_name = Column("repository_name", String(255), nullable=True, unique=None, default=None)
1256 user_ip = Column("user_ip", String(255), nullable=True, unique=None, default=None)
1256 user_ip = Column("user_ip", String(255), nullable=True, unique=None, default=None)
1257 action = Column("action", Text().with_variant(Text(1200000), 'mysql'), nullable=True, unique=None, default=None)
1257 action = Column("action", Text().with_variant(Text(1200000), 'mysql'), nullable=True, unique=None, default=None)
1258 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
1258 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
1259
1259
1260 version = Column("version", String(255), nullable=True, default=VERSION_1)
1260 version = Column("version", String(255), nullable=True, default=VERSION_1)
1261 user_data = Column('user_data_json', MutationObj.as_mutable(JsonType(dialect_map=dict(mysql=LONGTEXT()))))
1261 user_data = Column('user_data_json', MutationObj.as_mutable(JsonType(dialect_map=dict(mysql=LONGTEXT()))))
1262 action_data = Column('action_data_json', MutationObj.as_mutable(JsonType(dialect_map=dict(mysql=LONGTEXT()))))
1262 action_data = Column('action_data_json', MutationObj.as_mutable(JsonType(dialect_map=dict(mysql=LONGTEXT()))))
1263
1263
1264 def __unicode__(self):
1264 def __unicode__(self):
1265 return u"<%s('id:%s:%s')>" % (
1265 return u"<%s('id:%s:%s')>" % (
1266 self.__class__.__name__, self.repository_name, self.action)
1266 self.__class__.__name__, self.repository_name, self.action)
1267
1267
1268 def __json__(self):
1268 def __json__(self):
1269 return {
1269 return {
1270 'user_id': self.user_id,
1270 'user_id': self.user_id,
1271 'username': self.username,
1271 'username': self.username,
1272 'repository_id': self.repository_id,
1272 'repository_id': self.repository_id,
1273 'repository_name': self.repository_name,
1273 'repository_name': self.repository_name,
1274 'user_ip': self.user_ip,
1274 'user_ip': self.user_ip,
1275 'action_date': self.action_date,
1275 'action_date': self.action_date,
1276 'action': self.action,
1276 'action': self.action,
1277 }
1277 }
1278
1278
1279 @hybrid_property
1279 @hybrid_property
1280 def entry_id(self):
1280 def entry_id(self):
1281 return self.user_log_id
1281 return self.user_log_id
1282
1282
1283 @property
1283 @property
1284 def action_as_day(self):
1284 def action_as_day(self):
1285 return datetime.date(*self.action_date.timetuple()[:3])
1285 return datetime.date(*self.action_date.timetuple()[:3])
1286
1286
1287 user = relationship('User')
1287 user = relationship('User')
1288 repository = relationship('Repository', cascade='')
1288 repository = relationship('Repository', cascade='')
1289
1289
1290
1290
1291 class UserGroup(Base, BaseModel):
1291 class UserGroup(Base, BaseModel):
1292 __tablename__ = 'users_groups'
1292 __tablename__ = 'users_groups'
1293 __table_args__ = (
1293 __table_args__ = (
1294 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1294 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1295 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1295 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1296 )
1296 )
1297
1297
1298 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1298 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1299 users_group_name = Column("users_group_name", String(255), nullable=False, unique=True, default=None)
1299 users_group_name = Column("users_group_name", String(255), nullable=False, unique=True, default=None)
1300 user_group_description = Column("user_group_description", String(10000), nullable=True, unique=None, default=None)
1300 user_group_description = Column("user_group_description", String(10000), nullable=True, unique=None, default=None)
1301 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
1301 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
1302 inherit_default_permissions = Column("users_group_inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
1302 inherit_default_permissions = Column("users_group_inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
1303 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
1303 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
1304 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1304 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1305 _group_data = Column("group_data", LargeBinary(), nullable=True) # JSON data
1305 _group_data = Column("group_data", LargeBinary(), nullable=True) # JSON data
1306
1306
1307 members = relationship('UserGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
1307 members = relationship('UserGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
1308 users_group_to_perm = relationship('UserGroupToPerm', cascade='all')
1308 users_group_to_perm = relationship('UserGroupToPerm', cascade='all')
1309 users_group_repo_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1309 users_group_repo_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1310 users_group_repo_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
1310 users_group_repo_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
1311 user_user_group_to_perm = relationship('UserUserGroupToPerm', cascade='all')
1311 user_user_group_to_perm = relationship('UserUserGroupToPerm', cascade='all')
1312 user_group_user_group_to_perm = relationship('UserGroupUserGroupToPerm ', primaryjoin="UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id", cascade='all')
1312 user_group_user_group_to_perm = relationship('UserGroupUserGroupToPerm ', primaryjoin="UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id", cascade='all')
1313
1313
1314 user_group_review_rules = relationship('RepoReviewRuleUserGroup', cascade='all')
1314 user_group_review_rules = relationship('RepoReviewRuleUserGroup', cascade='all')
1315 user = relationship('User', primaryjoin="User.user_id==UserGroup.user_id")
1315 user = relationship('User', primaryjoin="User.user_id==UserGroup.user_id")
1316
1316
1317 @classmethod
1317 @classmethod
1318 def _load_group_data(cls, column):
1318 def _load_group_data(cls, column):
1319 if not column:
1319 if not column:
1320 return {}
1320 return {}
1321
1321
1322 try:
1322 try:
1323 return json.loads(column) or {}
1323 return json.loads(column) or {}
1324 except TypeError:
1324 except TypeError:
1325 return {}
1325 return {}
1326
1326
1327 @hybrid_property
1327 @hybrid_property
1328 def description_safe(self):
1328 def description_safe(self):
1329 from rhodecode.lib import helpers as h
1329 from rhodecode.lib import helpers as h
1330 return h.escape(self.user_group_description)
1330 return h.escape(self.user_group_description)
1331
1331
1332 @hybrid_property
1332 @hybrid_property
1333 def group_data(self):
1333 def group_data(self):
1334 return self._load_group_data(self._group_data)
1334 return self._load_group_data(self._group_data)
1335
1335
1336 @group_data.expression
1336 @group_data.expression
1337 def group_data(self, **kwargs):
1337 def group_data(self, **kwargs):
1338 return self._group_data
1338 return self._group_data
1339
1339
1340 @group_data.setter
1340 @group_data.setter
1341 def group_data(self, val):
1341 def group_data(self, val):
1342 try:
1342 try:
1343 self._group_data = json.dumps(val)
1343 self._group_data = json.dumps(val)
1344 except Exception:
1344 except Exception:
1345 log.error(traceback.format_exc())
1345 log.error(traceback.format_exc())
1346
1346
1347 @classmethod
1348 def _load_sync(cls, group_data):
1349 if group_data:
1350 return group_data.get('extern_type')
1351
1352 @property
1353 def sync(self):
1354 return self._load_sync(self.group_data)
1355
1347 def __unicode__(self):
1356 def __unicode__(self):
1348 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
1357 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
1349 self.users_group_id,
1358 self.users_group_id,
1350 self.users_group_name)
1359 self.users_group_name)
1351
1360
1352 @classmethod
1361 @classmethod
1353 def get_by_group_name(cls, group_name, cache=False,
1362 def get_by_group_name(cls, group_name, cache=False,
1354 case_insensitive=False):
1363 case_insensitive=False):
1355 if case_insensitive:
1364 if case_insensitive:
1356 q = cls.query().filter(func.lower(cls.users_group_name) ==
1365 q = cls.query().filter(func.lower(cls.users_group_name) ==
1357 func.lower(group_name))
1366 func.lower(group_name))
1358
1367
1359 else:
1368 else:
1360 q = cls.query().filter(cls.users_group_name == group_name)
1369 q = cls.query().filter(cls.users_group_name == group_name)
1361 if cache:
1370 if cache:
1362 q = q.options(
1371 q = q.options(
1363 FromCache("sql_cache_short", "get_group_%s" % _hash_key(group_name)))
1372 FromCache("sql_cache_short", "get_group_%s" % _hash_key(group_name)))
1364 return q.scalar()
1373 return q.scalar()
1365
1374
1366 @classmethod
1375 @classmethod
1367 def get(cls, user_group_id, cache=False):
1376 def get(cls, user_group_id, cache=False):
1368 if not user_group_id:
1377 if not user_group_id:
1369 return
1378 return
1370
1379
1371 user_group = cls.query()
1380 user_group = cls.query()
1372 if cache:
1381 if cache:
1373 user_group = user_group.options(
1382 user_group = user_group.options(
1374 FromCache("sql_cache_short", "get_users_group_%s" % user_group_id))
1383 FromCache("sql_cache_short", "get_users_group_%s" % user_group_id))
1375 return user_group.get(user_group_id)
1384 return user_group.get(user_group_id)
1376
1385
1377 def permissions(self, with_admins=True, with_owner=True):
1386 def permissions(self, with_admins=True, with_owner=True):
1378 q = UserUserGroupToPerm.query().filter(UserUserGroupToPerm.user_group == self)
1387 q = UserUserGroupToPerm.query().filter(UserUserGroupToPerm.user_group == self)
1379 q = q.options(joinedload(UserUserGroupToPerm.user_group),
1388 q = q.options(joinedload(UserUserGroupToPerm.user_group),
1380 joinedload(UserUserGroupToPerm.user),
1389 joinedload(UserUserGroupToPerm.user),
1381 joinedload(UserUserGroupToPerm.permission),)
1390 joinedload(UserUserGroupToPerm.permission),)
1382
1391
1383 # get owners and admins and permissions. We do a trick of re-writing
1392 # get owners and admins and permissions. We do a trick of re-writing
1384 # objects from sqlalchemy to named-tuples due to sqlalchemy session
1393 # objects from sqlalchemy to named-tuples due to sqlalchemy session
1385 # has a global reference and changing one object propagates to all
1394 # has a global reference and changing one object propagates to all
1386 # others. This means if admin is also an owner admin_row that change
1395 # others. This means if admin is also an owner admin_row that change
1387 # would propagate to both objects
1396 # would propagate to both objects
1388 perm_rows = []
1397 perm_rows = []
1389 for _usr in q.all():
1398 for _usr in q.all():
1390 usr = AttributeDict(_usr.user.get_dict())
1399 usr = AttributeDict(_usr.user.get_dict())
1391 usr.permission = _usr.permission.permission_name
1400 usr.permission = _usr.permission.permission_name
1392 perm_rows.append(usr)
1401 perm_rows.append(usr)
1393
1402
1394 # filter the perm rows by 'default' first and then sort them by
1403 # filter the perm rows by 'default' first and then sort them by
1395 # admin,write,read,none permissions sorted again alphabetically in
1404 # admin,write,read,none permissions sorted again alphabetically in
1396 # each group
1405 # each group
1397 perm_rows = sorted(perm_rows, key=display_user_sort)
1406 perm_rows = sorted(perm_rows, key=display_user_sort)
1398
1407
1399 _admin_perm = 'usergroup.admin'
1408 _admin_perm = 'usergroup.admin'
1400 owner_row = []
1409 owner_row = []
1401 if with_owner:
1410 if with_owner:
1402 usr = AttributeDict(self.user.get_dict())
1411 usr = AttributeDict(self.user.get_dict())
1403 usr.owner_row = True
1412 usr.owner_row = True
1404 usr.permission = _admin_perm
1413 usr.permission = _admin_perm
1405 owner_row.append(usr)
1414 owner_row.append(usr)
1406
1415
1407 super_admin_rows = []
1416 super_admin_rows = []
1408 if with_admins:
1417 if with_admins:
1409 for usr in User.get_all_super_admins():
1418 for usr in User.get_all_super_admins():
1410 # if this admin is also owner, don't double the record
1419 # if this admin is also owner, don't double the record
1411 if usr.user_id == owner_row[0].user_id:
1420 if usr.user_id == owner_row[0].user_id:
1412 owner_row[0].admin_row = True
1421 owner_row[0].admin_row = True
1413 else:
1422 else:
1414 usr = AttributeDict(usr.get_dict())
1423 usr = AttributeDict(usr.get_dict())
1415 usr.admin_row = True
1424 usr.admin_row = True
1416 usr.permission = _admin_perm
1425 usr.permission = _admin_perm
1417 super_admin_rows.append(usr)
1426 super_admin_rows.append(usr)
1418
1427
1419 return super_admin_rows + owner_row + perm_rows
1428 return super_admin_rows + owner_row + perm_rows
1420
1429
1421 def permission_user_groups(self):
1430 def permission_user_groups(self):
1422 q = UserGroupUserGroupToPerm.query().filter(UserGroupUserGroupToPerm.target_user_group == self)
1431 q = UserGroupUserGroupToPerm.query().filter(UserGroupUserGroupToPerm.target_user_group == self)
1423 q = q.options(joinedload(UserGroupUserGroupToPerm.user_group),
1432 q = q.options(joinedload(UserGroupUserGroupToPerm.user_group),
1424 joinedload(UserGroupUserGroupToPerm.target_user_group),
1433 joinedload(UserGroupUserGroupToPerm.target_user_group),
1425 joinedload(UserGroupUserGroupToPerm.permission),)
1434 joinedload(UserGroupUserGroupToPerm.permission),)
1426
1435
1427 perm_rows = []
1436 perm_rows = []
1428 for _user_group in q.all():
1437 for _user_group in q.all():
1429 usr = AttributeDict(_user_group.user_group.get_dict())
1438 usr = AttributeDict(_user_group.user_group.get_dict())
1430 usr.permission = _user_group.permission.permission_name
1439 usr.permission = _user_group.permission.permission_name
1431 perm_rows.append(usr)
1440 perm_rows.append(usr)
1432
1441
1433 perm_rows = sorted(perm_rows, key=display_user_group_sort)
1442 perm_rows = sorted(perm_rows, key=display_user_group_sort)
1434 return perm_rows
1443 return perm_rows
1435
1444
1436 def _get_default_perms(self, user_group, suffix=''):
1445 def _get_default_perms(self, user_group, suffix=''):
1437 from rhodecode.model.permission import PermissionModel
1446 from rhodecode.model.permission import PermissionModel
1438 return PermissionModel().get_default_perms(user_group.users_group_to_perm, suffix)
1447 return PermissionModel().get_default_perms(user_group.users_group_to_perm, suffix)
1439
1448
1440 def get_default_perms(self, suffix=''):
1449 def get_default_perms(self, suffix=''):
1441 return self._get_default_perms(self, suffix)
1450 return self._get_default_perms(self, suffix)
1442
1451
1443 def get_api_data(self, with_group_members=True, include_secrets=False):
1452 def get_api_data(self, with_group_members=True, include_secrets=False):
1444 """
1453 """
1445 :param include_secrets: See :meth:`User.get_api_data`, this parameter is
1454 :param include_secrets: See :meth:`User.get_api_data`, this parameter is
1446 basically forwarded.
1455 basically forwarded.
1447
1456
1448 """
1457 """
1449 user_group = self
1458 user_group = self
1450 data = {
1459 data = {
1451 'users_group_id': user_group.users_group_id,
1460 'users_group_id': user_group.users_group_id,
1452 'group_name': user_group.users_group_name,
1461 'group_name': user_group.users_group_name,
1453 'group_description': user_group.user_group_description,
1462 'group_description': user_group.user_group_description,
1454 'active': user_group.users_group_active,
1463 'active': user_group.users_group_active,
1455 'owner': user_group.user.username,
1464 'owner': user_group.user.username,
1465 'sync': user_group.sync,
1456 'owner_email': user_group.user.email,
1466 'owner_email': user_group.user.email,
1457 }
1467 }
1458
1468
1459 if with_group_members:
1469 if with_group_members:
1460 users = []
1470 users = []
1461 for user in user_group.members:
1471 for user in user_group.members:
1462 user = user.user
1472 user = user.user
1463 users.append(user.get_api_data(include_secrets=include_secrets))
1473 users.append(user.get_api_data(include_secrets=include_secrets))
1464 data['users'] = users
1474 data['users'] = users
1465
1475
1466 return data
1476 return data
1467
1477
1468
1478
1469 class UserGroupMember(Base, BaseModel):
1479 class UserGroupMember(Base, BaseModel):
1470 __tablename__ = 'users_groups_members'
1480 __tablename__ = 'users_groups_members'
1471 __table_args__ = (
1481 __table_args__ = (
1472 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1482 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1473 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1483 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1474 )
1484 )
1475
1485
1476 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1486 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1477 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1487 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1478 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1488 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1479
1489
1480 user = relationship('User', lazy='joined')
1490 user = relationship('User', lazy='joined')
1481 users_group = relationship('UserGroup')
1491 users_group = relationship('UserGroup')
1482
1492
1483 def __init__(self, gr_id='', u_id=''):
1493 def __init__(self, gr_id='', u_id=''):
1484 self.users_group_id = gr_id
1494 self.users_group_id = gr_id
1485 self.user_id = u_id
1495 self.user_id = u_id
1486
1496
1487
1497
1488 class RepositoryField(Base, BaseModel):
1498 class RepositoryField(Base, BaseModel):
1489 __tablename__ = 'repositories_fields'
1499 __tablename__ = 'repositories_fields'
1490 __table_args__ = (
1500 __table_args__ = (
1491 UniqueConstraint('repository_id', 'field_key'), # no-multi field
1501 UniqueConstraint('repository_id', 'field_key'), # no-multi field
1492 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1502 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1493 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1503 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1494 )
1504 )
1495 PREFIX = 'ex_' # prefix used in form to not conflict with already existing fields
1505 PREFIX = 'ex_' # prefix used in form to not conflict with already existing fields
1496
1506
1497 repo_field_id = Column("repo_field_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1507 repo_field_id = Column("repo_field_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1498 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1508 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1499 field_key = Column("field_key", String(250))
1509 field_key = Column("field_key", String(250))
1500 field_label = Column("field_label", String(1024), nullable=False)
1510 field_label = Column("field_label", String(1024), nullable=False)
1501 field_value = Column("field_value", String(10000), nullable=False)
1511 field_value = Column("field_value", String(10000), nullable=False)
1502 field_desc = Column("field_desc", String(1024), nullable=False)
1512 field_desc = Column("field_desc", String(1024), nullable=False)
1503 field_type = Column("field_type", String(255), nullable=False, unique=None)
1513 field_type = Column("field_type", String(255), nullable=False, unique=None)
1504 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1514 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1505
1515
1506 repository = relationship('Repository')
1516 repository = relationship('Repository')
1507
1517
1508 @property
1518 @property
1509 def field_key_prefixed(self):
1519 def field_key_prefixed(self):
1510 return 'ex_%s' % self.field_key
1520 return 'ex_%s' % self.field_key
1511
1521
1512 @classmethod
1522 @classmethod
1513 def un_prefix_key(cls, key):
1523 def un_prefix_key(cls, key):
1514 if key.startswith(cls.PREFIX):
1524 if key.startswith(cls.PREFIX):
1515 return key[len(cls.PREFIX):]
1525 return key[len(cls.PREFIX):]
1516 return key
1526 return key
1517
1527
1518 @classmethod
1528 @classmethod
1519 def get_by_key_name(cls, key, repo):
1529 def get_by_key_name(cls, key, repo):
1520 row = cls.query()\
1530 row = cls.query()\
1521 .filter(cls.repository == repo)\
1531 .filter(cls.repository == repo)\
1522 .filter(cls.field_key == key).scalar()
1532 .filter(cls.field_key == key).scalar()
1523 return row
1533 return row
1524
1534
1525
1535
1526 class Repository(Base, BaseModel):
1536 class Repository(Base, BaseModel):
1527 __tablename__ = 'repositories'
1537 __tablename__ = 'repositories'
1528 __table_args__ = (
1538 __table_args__ = (
1529 Index('r_repo_name_idx', 'repo_name', mysql_length=255),
1539 Index('r_repo_name_idx', 'repo_name', mysql_length=255),
1530 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1540 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1531 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1541 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1532 )
1542 )
1533 DEFAULT_CLONE_URI = '{scheme}://{user}@{netloc}/{repo}'
1543 DEFAULT_CLONE_URI = '{scheme}://{user}@{netloc}/{repo}'
1534 DEFAULT_CLONE_URI_ID = '{scheme}://{user}@{netloc}/_{repoid}'
1544 DEFAULT_CLONE_URI_ID = '{scheme}://{user}@{netloc}/_{repoid}'
1535 DEFAULT_CLONE_URI_SSH = 'ssh://{sys_user}@{hostname}/{repo}'
1545 DEFAULT_CLONE_URI_SSH = 'ssh://{sys_user}@{hostname}/{repo}'
1536
1546
1537 STATE_CREATED = 'repo_state_created'
1547 STATE_CREATED = 'repo_state_created'
1538 STATE_PENDING = 'repo_state_pending'
1548 STATE_PENDING = 'repo_state_pending'
1539 STATE_ERROR = 'repo_state_error'
1549 STATE_ERROR = 'repo_state_error'
1540
1550
1541 LOCK_AUTOMATIC = 'lock_auto'
1551 LOCK_AUTOMATIC = 'lock_auto'
1542 LOCK_API = 'lock_api'
1552 LOCK_API = 'lock_api'
1543 LOCK_WEB = 'lock_web'
1553 LOCK_WEB = 'lock_web'
1544 LOCK_PULL = 'lock_pull'
1554 LOCK_PULL = 'lock_pull'
1545
1555
1546 NAME_SEP = URL_SEP
1556 NAME_SEP = URL_SEP
1547
1557
1548 repo_id = Column(
1558 repo_id = Column(
1549 "repo_id", Integer(), nullable=False, unique=True, default=None,
1559 "repo_id", Integer(), nullable=False, unique=True, default=None,
1550 primary_key=True)
1560 primary_key=True)
1551 _repo_name = Column(
1561 _repo_name = Column(
1552 "repo_name", Text(), nullable=False, default=None)
1562 "repo_name", Text(), nullable=False, default=None)
1553 _repo_name_hash = Column(
1563 _repo_name_hash = Column(
1554 "repo_name_hash", String(255), nullable=False, unique=True)
1564 "repo_name_hash", String(255), nullable=False, unique=True)
1555 repo_state = Column("repo_state", String(255), nullable=True)
1565 repo_state = Column("repo_state", String(255), nullable=True)
1556
1566
1557 clone_uri = Column(
1567 clone_uri = Column(
1558 "clone_uri", EncryptedTextValue(), nullable=True, unique=False,
1568 "clone_uri", EncryptedTextValue(), nullable=True, unique=False,
1559 default=None)
1569 default=None)
1560 push_uri = Column(
1570 push_uri = Column(
1561 "push_uri", EncryptedTextValue(), nullable=True, unique=False,
1571 "push_uri", EncryptedTextValue(), nullable=True, unique=False,
1562 default=None)
1572 default=None)
1563 repo_type = Column(
1573 repo_type = Column(
1564 "repo_type", String(255), nullable=False, unique=False, default=None)
1574 "repo_type", String(255), nullable=False, unique=False, default=None)
1565 user_id = Column(
1575 user_id = Column(
1566 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
1576 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
1567 unique=False, default=None)
1577 unique=False, default=None)
1568 private = Column(
1578 private = Column(
1569 "private", Boolean(), nullable=True, unique=None, default=None)
1579 "private", Boolean(), nullable=True, unique=None, default=None)
1570 enable_statistics = Column(
1580 enable_statistics = Column(
1571 "statistics", Boolean(), nullable=True, unique=None, default=True)
1581 "statistics", Boolean(), nullable=True, unique=None, default=True)
1572 enable_downloads = Column(
1582 enable_downloads = Column(
1573 "downloads", Boolean(), nullable=True, unique=None, default=True)
1583 "downloads", Boolean(), nullable=True, unique=None, default=True)
1574 description = Column(
1584 description = Column(
1575 "description", String(10000), nullable=True, unique=None, default=None)
1585 "description", String(10000), nullable=True, unique=None, default=None)
1576 created_on = Column(
1586 created_on = Column(
1577 'created_on', DateTime(timezone=False), nullable=True, unique=None,
1587 'created_on', DateTime(timezone=False), nullable=True, unique=None,
1578 default=datetime.datetime.now)
1588 default=datetime.datetime.now)
1579 updated_on = Column(
1589 updated_on = Column(
1580 'updated_on', DateTime(timezone=False), nullable=True, unique=None,
1590 'updated_on', DateTime(timezone=False), nullable=True, unique=None,
1581 default=datetime.datetime.now)
1591 default=datetime.datetime.now)
1582 _landing_revision = Column(
1592 _landing_revision = Column(
1583 "landing_revision", String(255), nullable=False, unique=False,
1593 "landing_revision", String(255), nullable=False, unique=False,
1584 default=None)
1594 default=None)
1585 enable_locking = Column(
1595 enable_locking = Column(
1586 "enable_locking", Boolean(), nullable=False, unique=None,
1596 "enable_locking", Boolean(), nullable=False, unique=None,
1587 default=False)
1597 default=False)
1588 _locked = Column(
1598 _locked = Column(
1589 "locked", String(255), nullable=True, unique=False, default=None)
1599 "locked", String(255), nullable=True, unique=False, default=None)
1590 _changeset_cache = Column(
1600 _changeset_cache = Column(
1591 "changeset_cache", LargeBinary(), nullable=True) # JSON data
1601 "changeset_cache", LargeBinary(), nullable=True) # JSON data
1592
1602
1593 fork_id = Column(
1603 fork_id = Column(
1594 "fork_id", Integer(), ForeignKey('repositories.repo_id'),
1604 "fork_id", Integer(), ForeignKey('repositories.repo_id'),
1595 nullable=True, unique=False, default=None)
1605 nullable=True, unique=False, default=None)
1596 group_id = Column(
1606 group_id = Column(
1597 "group_id", Integer(), ForeignKey('groups.group_id'), nullable=True,
1607 "group_id", Integer(), ForeignKey('groups.group_id'), nullable=True,
1598 unique=False, default=None)
1608 unique=False, default=None)
1599
1609
1600 user = relationship('User', lazy='joined')
1610 user = relationship('User', lazy='joined')
1601 fork = relationship('Repository', remote_side=repo_id, lazy='joined')
1611 fork = relationship('Repository', remote_side=repo_id, lazy='joined')
1602 group = relationship('RepoGroup', lazy='joined')
1612 group = relationship('RepoGroup', lazy='joined')
1603 repo_to_perm = relationship(
1613 repo_to_perm = relationship(
1604 'UserRepoToPerm', cascade='all',
1614 'UserRepoToPerm', cascade='all',
1605 order_by='UserRepoToPerm.repo_to_perm_id')
1615 order_by='UserRepoToPerm.repo_to_perm_id')
1606 users_group_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1616 users_group_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1607 stats = relationship('Statistics', cascade='all', uselist=False)
1617 stats = relationship('Statistics', cascade='all', uselist=False)
1608
1618
1609 followers = relationship(
1619 followers = relationship(
1610 'UserFollowing',
1620 'UserFollowing',
1611 primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id',
1621 primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id',
1612 cascade='all')
1622 cascade='all')
1613 extra_fields = relationship(
1623 extra_fields = relationship(
1614 'RepositoryField', cascade="all, delete, delete-orphan")
1624 'RepositoryField', cascade="all, delete, delete-orphan")
1615 logs = relationship('UserLog')
1625 logs = relationship('UserLog')
1616 comments = relationship(
1626 comments = relationship(
1617 'ChangesetComment', cascade="all, delete, delete-orphan")
1627 'ChangesetComment', cascade="all, delete, delete-orphan")
1618 pull_requests_source = relationship(
1628 pull_requests_source = relationship(
1619 'PullRequest',
1629 'PullRequest',
1620 primaryjoin='PullRequest.source_repo_id==Repository.repo_id',
1630 primaryjoin='PullRequest.source_repo_id==Repository.repo_id',
1621 cascade="all, delete, delete-orphan")
1631 cascade="all, delete, delete-orphan")
1622 pull_requests_target = relationship(
1632 pull_requests_target = relationship(
1623 'PullRequest',
1633 'PullRequest',
1624 primaryjoin='PullRequest.target_repo_id==Repository.repo_id',
1634 primaryjoin='PullRequest.target_repo_id==Repository.repo_id',
1625 cascade="all, delete, delete-orphan")
1635 cascade="all, delete, delete-orphan")
1626 ui = relationship('RepoRhodeCodeUi', cascade="all")
1636 ui = relationship('RepoRhodeCodeUi', cascade="all")
1627 settings = relationship('RepoRhodeCodeSetting', cascade="all")
1637 settings = relationship('RepoRhodeCodeSetting', cascade="all")
1628 integrations = relationship('Integration',
1638 integrations = relationship('Integration',
1629 cascade="all, delete, delete-orphan")
1639 cascade="all, delete, delete-orphan")
1630
1640
1631 scoped_tokens = relationship('UserApiKeys', cascade="all")
1641 scoped_tokens = relationship('UserApiKeys', cascade="all")
1632
1642
1633 def __unicode__(self):
1643 def __unicode__(self):
1634 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id,
1644 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id,
1635 safe_unicode(self.repo_name))
1645 safe_unicode(self.repo_name))
1636
1646
1637 @hybrid_property
1647 @hybrid_property
1638 def description_safe(self):
1648 def description_safe(self):
1639 from rhodecode.lib import helpers as h
1649 from rhodecode.lib import helpers as h
1640 return h.escape(self.description)
1650 return h.escape(self.description)
1641
1651
1642 @hybrid_property
1652 @hybrid_property
1643 def landing_rev(self):
1653 def landing_rev(self):
1644 # always should return [rev_type, rev]
1654 # always should return [rev_type, rev]
1645 if self._landing_revision:
1655 if self._landing_revision:
1646 _rev_info = self._landing_revision.split(':')
1656 _rev_info = self._landing_revision.split(':')
1647 if len(_rev_info) < 2:
1657 if len(_rev_info) < 2:
1648 _rev_info.insert(0, 'rev')
1658 _rev_info.insert(0, 'rev')
1649 return [_rev_info[0], _rev_info[1]]
1659 return [_rev_info[0], _rev_info[1]]
1650 return [None, None]
1660 return [None, None]
1651
1661
1652 @landing_rev.setter
1662 @landing_rev.setter
1653 def landing_rev(self, val):
1663 def landing_rev(self, val):
1654 if ':' not in val:
1664 if ':' not in val:
1655 raise ValueError('value must be delimited with `:` and consist '
1665 raise ValueError('value must be delimited with `:` and consist '
1656 'of <rev_type>:<rev>, got %s instead' % val)
1666 'of <rev_type>:<rev>, got %s instead' % val)
1657 self._landing_revision = val
1667 self._landing_revision = val
1658
1668
1659 @hybrid_property
1669 @hybrid_property
1660 def locked(self):
1670 def locked(self):
1661 if self._locked:
1671 if self._locked:
1662 user_id, timelocked, reason = self._locked.split(':')
1672 user_id, timelocked, reason = self._locked.split(':')
1663 lock_values = int(user_id), timelocked, reason
1673 lock_values = int(user_id), timelocked, reason
1664 else:
1674 else:
1665 lock_values = [None, None, None]
1675 lock_values = [None, None, None]
1666 return lock_values
1676 return lock_values
1667
1677
1668 @locked.setter
1678 @locked.setter
1669 def locked(self, val):
1679 def locked(self, val):
1670 if val and isinstance(val, (list, tuple)):
1680 if val and isinstance(val, (list, tuple)):
1671 self._locked = ':'.join(map(str, val))
1681 self._locked = ':'.join(map(str, val))
1672 else:
1682 else:
1673 self._locked = None
1683 self._locked = None
1674
1684
1675 @hybrid_property
1685 @hybrid_property
1676 def changeset_cache(self):
1686 def changeset_cache(self):
1677 from rhodecode.lib.vcs.backends.base import EmptyCommit
1687 from rhodecode.lib.vcs.backends.base import EmptyCommit
1678 dummy = EmptyCommit().__json__()
1688 dummy = EmptyCommit().__json__()
1679 if not self._changeset_cache:
1689 if not self._changeset_cache:
1680 return dummy
1690 return dummy
1681 try:
1691 try:
1682 return json.loads(self._changeset_cache)
1692 return json.loads(self._changeset_cache)
1683 except TypeError:
1693 except TypeError:
1684 return dummy
1694 return dummy
1685 except Exception:
1695 except Exception:
1686 log.error(traceback.format_exc())
1696 log.error(traceback.format_exc())
1687 return dummy
1697 return dummy
1688
1698
1689 @changeset_cache.setter
1699 @changeset_cache.setter
1690 def changeset_cache(self, val):
1700 def changeset_cache(self, val):
1691 try:
1701 try:
1692 self._changeset_cache = json.dumps(val)
1702 self._changeset_cache = json.dumps(val)
1693 except Exception:
1703 except Exception:
1694 log.error(traceback.format_exc())
1704 log.error(traceback.format_exc())
1695
1705
1696 @hybrid_property
1706 @hybrid_property
1697 def repo_name(self):
1707 def repo_name(self):
1698 return self._repo_name
1708 return self._repo_name
1699
1709
1700 @repo_name.setter
1710 @repo_name.setter
1701 def repo_name(self, value):
1711 def repo_name(self, value):
1702 self._repo_name = value
1712 self._repo_name = value
1703 self._repo_name_hash = hashlib.sha1(safe_str(value)).hexdigest()
1713 self._repo_name_hash = hashlib.sha1(safe_str(value)).hexdigest()
1704
1714
1705 @classmethod
1715 @classmethod
1706 def normalize_repo_name(cls, repo_name):
1716 def normalize_repo_name(cls, repo_name):
1707 """
1717 """
1708 Normalizes os specific repo_name to the format internally stored inside
1718 Normalizes os specific repo_name to the format internally stored inside
1709 database using URL_SEP
1719 database using URL_SEP
1710
1720
1711 :param cls:
1721 :param cls:
1712 :param repo_name:
1722 :param repo_name:
1713 """
1723 """
1714 return cls.NAME_SEP.join(repo_name.split(os.sep))
1724 return cls.NAME_SEP.join(repo_name.split(os.sep))
1715
1725
1716 @classmethod
1726 @classmethod
1717 def get_by_repo_name(cls, repo_name, cache=False, identity_cache=False):
1727 def get_by_repo_name(cls, repo_name, cache=False, identity_cache=False):
1718 session = Session()
1728 session = Session()
1719 q = session.query(cls).filter(cls.repo_name == repo_name)
1729 q = session.query(cls).filter(cls.repo_name == repo_name)
1720
1730
1721 if cache:
1731 if cache:
1722 if identity_cache:
1732 if identity_cache:
1723 val = cls.identity_cache(session, 'repo_name', repo_name)
1733 val = cls.identity_cache(session, 'repo_name', repo_name)
1724 if val:
1734 if val:
1725 return val
1735 return val
1726 else:
1736 else:
1727 cache_key = "get_repo_by_name_%s" % _hash_key(repo_name)
1737 cache_key = "get_repo_by_name_%s" % _hash_key(repo_name)
1728 q = q.options(
1738 q = q.options(
1729 FromCache("sql_cache_short", cache_key))
1739 FromCache("sql_cache_short", cache_key))
1730
1740
1731 return q.scalar()
1741 return q.scalar()
1732
1742
1733 @classmethod
1743 @classmethod
1734 def get_by_id_or_repo_name(cls, repoid):
1744 def get_by_id_or_repo_name(cls, repoid):
1735 if isinstance(repoid, (int, long)):
1745 if isinstance(repoid, (int, long)):
1736 try:
1746 try:
1737 repo = cls.get(repoid)
1747 repo = cls.get(repoid)
1738 except ValueError:
1748 except ValueError:
1739 repo = None
1749 repo = None
1740 else:
1750 else:
1741 repo = cls.get_by_repo_name(repoid)
1751 repo = cls.get_by_repo_name(repoid)
1742 return repo
1752 return repo
1743
1753
1744 @classmethod
1754 @classmethod
1745 def get_by_full_path(cls, repo_full_path):
1755 def get_by_full_path(cls, repo_full_path):
1746 repo_name = repo_full_path.split(cls.base_path(), 1)[-1]
1756 repo_name = repo_full_path.split(cls.base_path(), 1)[-1]
1747 repo_name = cls.normalize_repo_name(repo_name)
1757 repo_name = cls.normalize_repo_name(repo_name)
1748 return cls.get_by_repo_name(repo_name.strip(URL_SEP))
1758 return cls.get_by_repo_name(repo_name.strip(URL_SEP))
1749
1759
1750 @classmethod
1760 @classmethod
1751 def get_repo_forks(cls, repo_id):
1761 def get_repo_forks(cls, repo_id):
1752 return cls.query().filter(Repository.fork_id == repo_id)
1762 return cls.query().filter(Repository.fork_id == repo_id)
1753
1763
1754 @classmethod
1764 @classmethod
1755 def base_path(cls):
1765 def base_path(cls):
1756 """
1766 """
1757 Returns base path when all repos are stored
1767 Returns base path when all repos are stored
1758
1768
1759 :param cls:
1769 :param cls:
1760 """
1770 """
1761 q = Session().query(RhodeCodeUi)\
1771 q = Session().query(RhodeCodeUi)\
1762 .filter(RhodeCodeUi.ui_key == cls.NAME_SEP)
1772 .filter(RhodeCodeUi.ui_key == cls.NAME_SEP)
1763 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1773 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1764 return q.one().ui_value
1774 return q.one().ui_value
1765
1775
1766 @classmethod
1776 @classmethod
1767 def get_all_repos(cls, user_id=Optional(None), group_id=Optional(None),
1777 def get_all_repos(cls, user_id=Optional(None), group_id=Optional(None),
1768 case_insensitive=True):
1778 case_insensitive=True):
1769 q = Repository.query()
1779 q = Repository.query()
1770
1780
1771 if not isinstance(user_id, Optional):
1781 if not isinstance(user_id, Optional):
1772 q = q.filter(Repository.user_id == user_id)
1782 q = q.filter(Repository.user_id == user_id)
1773
1783
1774 if not isinstance(group_id, Optional):
1784 if not isinstance(group_id, Optional):
1775 q = q.filter(Repository.group_id == group_id)
1785 q = q.filter(Repository.group_id == group_id)
1776
1786
1777 if case_insensitive:
1787 if case_insensitive:
1778 q = q.order_by(func.lower(Repository.repo_name))
1788 q = q.order_by(func.lower(Repository.repo_name))
1779 else:
1789 else:
1780 q = q.order_by(Repository.repo_name)
1790 q = q.order_by(Repository.repo_name)
1781 return q.all()
1791 return q.all()
1782
1792
1783 @property
1793 @property
1784 def forks(self):
1794 def forks(self):
1785 """
1795 """
1786 Return forks of this repo
1796 Return forks of this repo
1787 """
1797 """
1788 return Repository.get_repo_forks(self.repo_id)
1798 return Repository.get_repo_forks(self.repo_id)
1789
1799
1790 @property
1800 @property
1791 def parent(self):
1801 def parent(self):
1792 """
1802 """
1793 Returns fork parent
1803 Returns fork parent
1794 """
1804 """
1795 return self.fork
1805 return self.fork
1796
1806
1797 @property
1807 @property
1798 def just_name(self):
1808 def just_name(self):
1799 return self.repo_name.split(self.NAME_SEP)[-1]
1809 return self.repo_name.split(self.NAME_SEP)[-1]
1800
1810
1801 @property
1811 @property
1802 def groups_with_parents(self):
1812 def groups_with_parents(self):
1803 groups = []
1813 groups = []
1804 if self.group is None:
1814 if self.group is None:
1805 return groups
1815 return groups
1806
1816
1807 cur_gr = self.group
1817 cur_gr = self.group
1808 groups.insert(0, cur_gr)
1818 groups.insert(0, cur_gr)
1809 while 1:
1819 while 1:
1810 gr = getattr(cur_gr, 'parent_group', None)
1820 gr = getattr(cur_gr, 'parent_group', None)
1811 cur_gr = cur_gr.parent_group
1821 cur_gr = cur_gr.parent_group
1812 if gr is None:
1822 if gr is None:
1813 break
1823 break
1814 groups.insert(0, gr)
1824 groups.insert(0, gr)
1815
1825
1816 return groups
1826 return groups
1817
1827
1818 @property
1828 @property
1819 def groups_and_repo(self):
1829 def groups_and_repo(self):
1820 return self.groups_with_parents, self
1830 return self.groups_with_parents, self
1821
1831
1822 @LazyProperty
1832 @LazyProperty
1823 def repo_path(self):
1833 def repo_path(self):
1824 """
1834 """
1825 Returns base full path for that repository means where it actually
1835 Returns base full path for that repository means where it actually
1826 exists on a filesystem
1836 exists on a filesystem
1827 """
1837 """
1828 q = Session().query(RhodeCodeUi).filter(
1838 q = Session().query(RhodeCodeUi).filter(
1829 RhodeCodeUi.ui_key == self.NAME_SEP)
1839 RhodeCodeUi.ui_key == self.NAME_SEP)
1830 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1840 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1831 return q.one().ui_value
1841 return q.one().ui_value
1832
1842
1833 @property
1843 @property
1834 def repo_full_path(self):
1844 def repo_full_path(self):
1835 p = [self.repo_path]
1845 p = [self.repo_path]
1836 # we need to split the name by / since this is how we store the
1846 # we need to split the name by / since this is how we store the
1837 # names in the database, but that eventually needs to be converted
1847 # names in the database, but that eventually needs to be converted
1838 # into a valid system path
1848 # into a valid system path
1839 p += self.repo_name.split(self.NAME_SEP)
1849 p += self.repo_name.split(self.NAME_SEP)
1840 return os.path.join(*map(safe_unicode, p))
1850 return os.path.join(*map(safe_unicode, p))
1841
1851
1842 @property
1852 @property
1843 def cache_keys(self):
1853 def cache_keys(self):
1844 """
1854 """
1845 Returns associated cache keys for that repo
1855 Returns associated cache keys for that repo
1846 """
1856 """
1847 return CacheKey.query()\
1857 return CacheKey.query()\
1848 .filter(CacheKey.cache_args == self.repo_name)\
1858 .filter(CacheKey.cache_args == self.repo_name)\
1849 .order_by(CacheKey.cache_key)\
1859 .order_by(CacheKey.cache_key)\
1850 .all()
1860 .all()
1851
1861
1852 def get_new_name(self, repo_name):
1862 def get_new_name(self, repo_name):
1853 """
1863 """
1854 returns new full repository name based on assigned group and new new
1864 returns new full repository name based on assigned group and new new
1855
1865
1856 :param group_name:
1866 :param group_name:
1857 """
1867 """
1858 path_prefix = self.group.full_path_splitted if self.group else []
1868 path_prefix = self.group.full_path_splitted if self.group else []
1859 return self.NAME_SEP.join(path_prefix + [repo_name])
1869 return self.NAME_SEP.join(path_prefix + [repo_name])
1860
1870
1861 @property
1871 @property
1862 def _config(self):
1872 def _config(self):
1863 """
1873 """
1864 Returns db based config object.
1874 Returns db based config object.
1865 """
1875 """
1866 from rhodecode.lib.utils import make_db_config
1876 from rhodecode.lib.utils import make_db_config
1867 return make_db_config(clear_session=False, repo=self)
1877 return make_db_config(clear_session=False, repo=self)
1868
1878
1869 def permissions(self, with_admins=True, with_owner=True):
1879 def permissions(self, with_admins=True, with_owner=True):
1870 q = UserRepoToPerm.query().filter(UserRepoToPerm.repository == self)
1880 q = UserRepoToPerm.query().filter(UserRepoToPerm.repository == self)
1871 q = q.options(joinedload(UserRepoToPerm.repository),
1881 q = q.options(joinedload(UserRepoToPerm.repository),
1872 joinedload(UserRepoToPerm.user),
1882 joinedload(UserRepoToPerm.user),
1873 joinedload(UserRepoToPerm.permission),)
1883 joinedload(UserRepoToPerm.permission),)
1874
1884
1875 # get owners and admins and permissions. We do a trick of re-writing
1885 # get owners and admins and permissions. We do a trick of re-writing
1876 # objects from sqlalchemy to named-tuples due to sqlalchemy session
1886 # objects from sqlalchemy to named-tuples due to sqlalchemy session
1877 # has a global reference and changing one object propagates to all
1887 # has a global reference and changing one object propagates to all
1878 # others. This means if admin is also an owner admin_row that change
1888 # others. This means if admin is also an owner admin_row that change
1879 # would propagate to both objects
1889 # would propagate to both objects
1880 perm_rows = []
1890 perm_rows = []
1881 for _usr in q.all():
1891 for _usr in q.all():
1882 usr = AttributeDict(_usr.user.get_dict())
1892 usr = AttributeDict(_usr.user.get_dict())
1883 usr.permission = _usr.permission.permission_name
1893 usr.permission = _usr.permission.permission_name
1884 perm_rows.append(usr)
1894 perm_rows.append(usr)
1885
1895
1886 # filter the perm rows by 'default' first and then sort them by
1896 # filter the perm rows by 'default' first and then sort them by
1887 # admin,write,read,none permissions sorted again alphabetically in
1897 # admin,write,read,none permissions sorted again alphabetically in
1888 # each group
1898 # each group
1889 perm_rows = sorted(perm_rows, key=display_user_sort)
1899 perm_rows = sorted(perm_rows, key=display_user_sort)
1890
1900
1891 _admin_perm = 'repository.admin'
1901 _admin_perm = 'repository.admin'
1892 owner_row = []
1902 owner_row = []
1893 if with_owner:
1903 if with_owner:
1894 usr = AttributeDict(self.user.get_dict())
1904 usr = AttributeDict(self.user.get_dict())
1895 usr.owner_row = True
1905 usr.owner_row = True
1896 usr.permission = _admin_perm
1906 usr.permission = _admin_perm
1897 owner_row.append(usr)
1907 owner_row.append(usr)
1898
1908
1899 super_admin_rows = []
1909 super_admin_rows = []
1900 if with_admins:
1910 if with_admins:
1901 for usr in User.get_all_super_admins():
1911 for usr in User.get_all_super_admins():
1902 # if this admin is also owner, don't double the record
1912 # if this admin is also owner, don't double the record
1903 if usr.user_id == owner_row[0].user_id:
1913 if usr.user_id == owner_row[0].user_id:
1904 owner_row[0].admin_row = True
1914 owner_row[0].admin_row = True
1905 else:
1915 else:
1906 usr = AttributeDict(usr.get_dict())
1916 usr = AttributeDict(usr.get_dict())
1907 usr.admin_row = True
1917 usr.admin_row = True
1908 usr.permission = _admin_perm
1918 usr.permission = _admin_perm
1909 super_admin_rows.append(usr)
1919 super_admin_rows.append(usr)
1910
1920
1911 return super_admin_rows + owner_row + perm_rows
1921 return super_admin_rows + owner_row + perm_rows
1912
1922
1913 def permission_user_groups(self):
1923 def permission_user_groups(self):
1914 q = UserGroupRepoToPerm.query().filter(
1924 q = UserGroupRepoToPerm.query().filter(
1915 UserGroupRepoToPerm.repository == self)
1925 UserGroupRepoToPerm.repository == self)
1916 q = q.options(joinedload(UserGroupRepoToPerm.repository),
1926 q = q.options(joinedload(UserGroupRepoToPerm.repository),
1917 joinedload(UserGroupRepoToPerm.users_group),
1927 joinedload(UserGroupRepoToPerm.users_group),
1918 joinedload(UserGroupRepoToPerm.permission),)
1928 joinedload(UserGroupRepoToPerm.permission),)
1919
1929
1920 perm_rows = []
1930 perm_rows = []
1921 for _user_group in q.all():
1931 for _user_group in q.all():
1922 usr = AttributeDict(_user_group.users_group.get_dict())
1932 usr = AttributeDict(_user_group.users_group.get_dict())
1923 usr.permission = _user_group.permission.permission_name
1933 usr.permission = _user_group.permission.permission_name
1924 perm_rows.append(usr)
1934 perm_rows.append(usr)
1925
1935
1926 perm_rows = sorted(perm_rows, key=display_user_group_sort)
1936 perm_rows = sorted(perm_rows, key=display_user_group_sort)
1927 return perm_rows
1937 return perm_rows
1928
1938
1929 def get_api_data(self, include_secrets=False):
1939 def get_api_data(self, include_secrets=False):
1930 """
1940 """
1931 Common function for generating repo api data
1941 Common function for generating repo api data
1932
1942
1933 :param include_secrets: See :meth:`User.get_api_data`.
1943 :param include_secrets: See :meth:`User.get_api_data`.
1934
1944
1935 """
1945 """
1936 # TODO: mikhail: Here there is an anti-pattern, we probably need to
1946 # TODO: mikhail: Here there is an anti-pattern, we probably need to
1937 # move this methods on models level.
1947 # move this methods on models level.
1938 from rhodecode.model.settings import SettingsModel
1948 from rhodecode.model.settings import SettingsModel
1939 from rhodecode.model.repo import RepoModel
1949 from rhodecode.model.repo import RepoModel
1940
1950
1941 repo = self
1951 repo = self
1942 _user_id, _time, _reason = self.locked
1952 _user_id, _time, _reason = self.locked
1943
1953
1944 data = {
1954 data = {
1945 'repo_id': repo.repo_id,
1955 'repo_id': repo.repo_id,
1946 'repo_name': repo.repo_name,
1956 'repo_name': repo.repo_name,
1947 'repo_type': repo.repo_type,
1957 'repo_type': repo.repo_type,
1948 'clone_uri': repo.clone_uri or '',
1958 'clone_uri': repo.clone_uri or '',
1949 'push_uri': repo.push_uri or '',
1959 'push_uri': repo.push_uri or '',
1950 'url': RepoModel().get_url(self),
1960 'url': RepoModel().get_url(self),
1951 'private': repo.private,
1961 'private': repo.private,
1952 'created_on': repo.created_on,
1962 'created_on': repo.created_on,
1953 'description': repo.description_safe,
1963 'description': repo.description_safe,
1954 'landing_rev': repo.landing_rev,
1964 'landing_rev': repo.landing_rev,
1955 'owner': repo.user.username,
1965 'owner': repo.user.username,
1956 'fork_of': repo.fork.repo_name if repo.fork else None,
1966 'fork_of': repo.fork.repo_name if repo.fork else None,
1957 'fork_of_id': repo.fork.repo_id if repo.fork else None,
1967 'fork_of_id': repo.fork.repo_id if repo.fork else None,
1958 'enable_statistics': repo.enable_statistics,
1968 'enable_statistics': repo.enable_statistics,
1959 'enable_locking': repo.enable_locking,
1969 'enable_locking': repo.enable_locking,
1960 'enable_downloads': repo.enable_downloads,
1970 'enable_downloads': repo.enable_downloads,
1961 'last_changeset': repo.changeset_cache,
1971 'last_changeset': repo.changeset_cache,
1962 'locked_by': User.get(_user_id).get_api_data(
1972 'locked_by': User.get(_user_id).get_api_data(
1963 include_secrets=include_secrets) if _user_id else None,
1973 include_secrets=include_secrets) if _user_id else None,
1964 'locked_date': time_to_datetime(_time) if _time else None,
1974 'locked_date': time_to_datetime(_time) if _time else None,
1965 'lock_reason': _reason if _reason else None,
1975 'lock_reason': _reason if _reason else None,
1966 }
1976 }
1967
1977
1968 # TODO: mikhail: should be per-repo settings here
1978 # TODO: mikhail: should be per-repo settings here
1969 rc_config = SettingsModel().get_all_settings()
1979 rc_config = SettingsModel().get_all_settings()
1970 repository_fields = str2bool(
1980 repository_fields = str2bool(
1971 rc_config.get('rhodecode_repository_fields'))
1981 rc_config.get('rhodecode_repository_fields'))
1972 if repository_fields:
1982 if repository_fields:
1973 for f in self.extra_fields:
1983 for f in self.extra_fields:
1974 data[f.field_key_prefixed] = f.field_value
1984 data[f.field_key_prefixed] = f.field_value
1975
1985
1976 return data
1986 return data
1977
1987
1978 @classmethod
1988 @classmethod
1979 def lock(cls, repo, user_id, lock_time=None, lock_reason=None):
1989 def lock(cls, repo, user_id, lock_time=None, lock_reason=None):
1980 if not lock_time:
1990 if not lock_time:
1981 lock_time = time.time()
1991 lock_time = time.time()
1982 if not lock_reason:
1992 if not lock_reason:
1983 lock_reason = cls.LOCK_AUTOMATIC
1993 lock_reason = cls.LOCK_AUTOMATIC
1984 repo.locked = [user_id, lock_time, lock_reason]
1994 repo.locked = [user_id, lock_time, lock_reason]
1985 Session().add(repo)
1995 Session().add(repo)
1986 Session().commit()
1996 Session().commit()
1987
1997
1988 @classmethod
1998 @classmethod
1989 def unlock(cls, repo):
1999 def unlock(cls, repo):
1990 repo.locked = None
2000 repo.locked = None
1991 Session().add(repo)
2001 Session().add(repo)
1992 Session().commit()
2002 Session().commit()
1993
2003
1994 @classmethod
2004 @classmethod
1995 def getlock(cls, repo):
2005 def getlock(cls, repo):
1996 return repo.locked
2006 return repo.locked
1997
2007
1998 def is_user_lock(self, user_id):
2008 def is_user_lock(self, user_id):
1999 if self.lock[0]:
2009 if self.lock[0]:
2000 lock_user_id = safe_int(self.lock[0])
2010 lock_user_id = safe_int(self.lock[0])
2001 user_id = safe_int(user_id)
2011 user_id = safe_int(user_id)
2002 # both are ints, and they are equal
2012 # both are ints, and they are equal
2003 return all([lock_user_id, user_id]) and lock_user_id == user_id
2013 return all([lock_user_id, user_id]) and lock_user_id == user_id
2004
2014
2005 return False
2015 return False
2006
2016
2007 def get_locking_state(self, action, user_id, only_when_enabled=True):
2017 def get_locking_state(self, action, user_id, only_when_enabled=True):
2008 """
2018 """
2009 Checks locking on this repository, if locking is enabled and lock is
2019 Checks locking on this repository, if locking is enabled and lock is
2010 present returns a tuple of make_lock, locked, locked_by.
2020 present returns a tuple of make_lock, locked, locked_by.
2011 make_lock can have 3 states None (do nothing) True, make lock
2021 make_lock can have 3 states None (do nothing) True, make lock
2012 False release lock, This value is later propagated to hooks, which
2022 False release lock, This value is later propagated to hooks, which
2013 do the locking. Think about this as signals passed to hooks what to do.
2023 do the locking. Think about this as signals passed to hooks what to do.
2014
2024
2015 """
2025 """
2016 # TODO: johbo: This is part of the business logic and should be moved
2026 # TODO: johbo: This is part of the business logic and should be moved
2017 # into the RepositoryModel.
2027 # into the RepositoryModel.
2018
2028
2019 if action not in ('push', 'pull'):
2029 if action not in ('push', 'pull'):
2020 raise ValueError("Invalid action value: %s" % repr(action))
2030 raise ValueError("Invalid action value: %s" % repr(action))
2021
2031
2022 # defines if locked error should be thrown to user
2032 # defines if locked error should be thrown to user
2023 currently_locked = False
2033 currently_locked = False
2024 # defines if new lock should be made, tri-state
2034 # defines if new lock should be made, tri-state
2025 make_lock = None
2035 make_lock = None
2026 repo = self
2036 repo = self
2027 user = User.get(user_id)
2037 user = User.get(user_id)
2028
2038
2029 lock_info = repo.locked
2039 lock_info = repo.locked
2030
2040
2031 if repo and (repo.enable_locking or not only_when_enabled):
2041 if repo and (repo.enable_locking or not only_when_enabled):
2032 if action == 'push':
2042 if action == 'push':
2033 # check if it's already locked !, if it is compare users
2043 # check if it's already locked !, if it is compare users
2034 locked_by_user_id = lock_info[0]
2044 locked_by_user_id = lock_info[0]
2035 if user.user_id == locked_by_user_id:
2045 if user.user_id == locked_by_user_id:
2036 log.debug(
2046 log.debug(
2037 'Got `push` action from user %s, now unlocking', user)
2047 'Got `push` action from user %s, now unlocking', user)
2038 # unlock if we have push from user who locked
2048 # unlock if we have push from user who locked
2039 make_lock = False
2049 make_lock = False
2040 else:
2050 else:
2041 # we're not the same user who locked, ban with
2051 # we're not the same user who locked, ban with
2042 # code defined in settings (default is 423 HTTP Locked) !
2052 # code defined in settings (default is 423 HTTP Locked) !
2043 log.debug('Repo %s is currently locked by %s', repo, user)
2053 log.debug('Repo %s is currently locked by %s', repo, user)
2044 currently_locked = True
2054 currently_locked = True
2045 elif action == 'pull':
2055 elif action == 'pull':
2046 # [0] user [1] date
2056 # [0] user [1] date
2047 if lock_info[0] and lock_info[1]:
2057 if lock_info[0] and lock_info[1]:
2048 log.debug('Repo %s is currently locked by %s', repo, user)
2058 log.debug('Repo %s is currently locked by %s', repo, user)
2049 currently_locked = True
2059 currently_locked = True
2050 else:
2060 else:
2051 log.debug('Setting lock on repo %s by %s', repo, user)
2061 log.debug('Setting lock on repo %s by %s', repo, user)
2052 make_lock = True
2062 make_lock = True
2053
2063
2054 else:
2064 else:
2055 log.debug('Repository %s do not have locking enabled', repo)
2065 log.debug('Repository %s do not have locking enabled', repo)
2056
2066
2057 log.debug('FINAL locking values make_lock:%s,locked:%s,locked_by:%s',
2067 log.debug('FINAL locking values make_lock:%s,locked:%s,locked_by:%s',
2058 make_lock, currently_locked, lock_info)
2068 make_lock, currently_locked, lock_info)
2059
2069
2060 from rhodecode.lib.auth import HasRepoPermissionAny
2070 from rhodecode.lib.auth import HasRepoPermissionAny
2061 perm_check = HasRepoPermissionAny('repository.write', 'repository.admin')
2071 perm_check = HasRepoPermissionAny('repository.write', 'repository.admin')
2062 if make_lock and not perm_check(repo_name=repo.repo_name, user=user):
2072 if make_lock and not perm_check(repo_name=repo.repo_name, user=user):
2063 # if we don't have at least write permission we cannot make a lock
2073 # if we don't have at least write permission we cannot make a lock
2064 log.debug('lock state reset back to FALSE due to lack '
2074 log.debug('lock state reset back to FALSE due to lack '
2065 'of at least read permission')
2075 'of at least read permission')
2066 make_lock = False
2076 make_lock = False
2067
2077
2068 return make_lock, currently_locked, lock_info
2078 return make_lock, currently_locked, lock_info
2069
2079
2070 @property
2080 @property
2071 def last_db_change(self):
2081 def last_db_change(self):
2072 return self.updated_on
2082 return self.updated_on
2073
2083
2074 @property
2084 @property
2075 def clone_uri_hidden(self):
2085 def clone_uri_hidden(self):
2076 clone_uri = self.clone_uri
2086 clone_uri = self.clone_uri
2077 if clone_uri:
2087 if clone_uri:
2078 import urlobject
2088 import urlobject
2079 url_obj = urlobject.URLObject(cleaned_uri(clone_uri))
2089 url_obj = urlobject.URLObject(cleaned_uri(clone_uri))
2080 if url_obj.password:
2090 if url_obj.password:
2081 clone_uri = url_obj.with_password('*****')
2091 clone_uri = url_obj.with_password('*****')
2082 return clone_uri
2092 return clone_uri
2083
2093
2084 @property
2094 @property
2085 def push_uri_hidden(self):
2095 def push_uri_hidden(self):
2086 push_uri = self.push_uri
2096 push_uri = self.push_uri
2087 if push_uri:
2097 if push_uri:
2088 import urlobject
2098 import urlobject
2089 url_obj = urlobject.URLObject(cleaned_uri(push_uri))
2099 url_obj = urlobject.URLObject(cleaned_uri(push_uri))
2090 if url_obj.password:
2100 if url_obj.password:
2091 push_uri = url_obj.with_password('*****')
2101 push_uri = url_obj.with_password('*****')
2092 return push_uri
2102 return push_uri
2093
2103
2094 def clone_url(self, **override):
2104 def clone_url(self, **override):
2095 from rhodecode.model.settings import SettingsModel
2105 from rhodecode.model.settings import SettingsModel
2096
2106
2097 uri_tmpl = None
2107 uri_tmpl = None
2098 if 'with_id' in override:
2108 if 'with_id' in override:
2099 uri_tmpl = self.DEFAULT_CLONE_URI_ID
2109 uri_tmpl = self.DEFAULT_CLONE_URI_ID
2100 del override['with_id']
2110 del override['with_id']
2101
2111
2102 if 'uri_tmpl' in override:
2112 if 'uri_tmpl' in override:
2103 uri_tmpl = override['uri_tmpl']
2113 uri_tmpl = override['uri_tmpl']
2104 del override['uri_tmpl']
2114 del override['uri_tmpl']
2105
2115
2106 ssh = False
2116 ssh = False
2107 if 'ssh' in override:
2117 if 'ssh' in override:
2108 ssh = True
2118 ssh = True
2109 del override['ssh']
2119 del override['ssh']
2110
2120
2111 # we didn't override our tmpl from **overrides
2121 # we didn't override our tmpl from **overrides
2112 if not uri_tmpl:
2122 if not uri_tmpl:
2113 rc_config = SettingsModel().get_all_settings(cache=True)
2123 rc_config = SettingsModel().get_all_settings(cache=True)
2114 if ssh:
2124 if ssh:
2115 uri_tmpl = rc_config.get(
2125 uri_tmpl = rc_config.get(
2116 'rhodecode_clone_uri_ssh_tmpl') or self.DEFAULT_CLONE_URI_SSH
2126 'rhodecode_clone_uri_ssh_tmpl') or self.DEFAULT_CLONE_URI_SSH
2117 else:
2127 else:
2118 uri_tmpl = rc_config.get(
2128 uri_tmpl = rc_config.get(
2119 'rhodecode_clone_uri_tmpl') or self.DEFAULT_CLONE_URI
2129 'rhodecode_clone_uri_tmpl') or self.DEFAULT_CLONE_URI
2120
2130
2121 request = get_current_request()
2131 request = get_current_request()
2122 return get_clone_url(request=request,
2132 return get_clone_url(request=request,
2123 uri_tmpl=uri_tmpl,
2133 uri_tmpl=uri_tmpl,
2124 repo_name=self.repo_name,
2134 repo_name=self.repo_name,
2125 repo_id=self.repo_id, **override)
2135 repo_id=self.repo_id, **override)
2126
2136
2127 def set_state(self, state):
2137 def set_state(self, state):
2128 self.repo_state = state
2138 self.repo_state = state
2129 Session().add(self)
2139 Session().add(self)
2130 #==========================================================================
2140 #==========================================================================
2131 # SCM PROPERTIES
2141 # SCM PROPERTIES
2132 #==========================================================================
2142 #==========================================================================
2133
2143
2134 def get_commit(self, commit_id=None, commit_idx=None, pre_load=None):
2144 def get_commit(self, commit_id=None, commit_idx=None, pre_load=None):
2135 return get_commit_safe(
2145 return get_commit_safe(
2136 self.scm_instance(), commit_id, commit_idx, pre_load=pre_load)
2146 self.scm_instance(), commit_id, commit_idx, pre_load=pre_load)
2137
2147
2138 def get_changeset(self, rev=None, pre_load=None):
2148 def get_changeset(self, rev=None, pre_load=None):
2139 warnings.warn("Use get_commit", DeprecationWarning)
2149 warnings.warn("Use get_commit", DeprecationWarning)
2140 commit_id = None
2150 commit_id = None
2141 commit_idx = None
2151 commit_idx = None
2142 if isinstance(rev, basestring):
2152 if isinstance(rev, basestring):
2143 commit_id = rev
2153 commit_id = rev
2144 else:
2154 else:
2145 commit_idx = rev
2155 commit_idx = rev
2146 return self.get_commit(commit_id=commit_id, commit_idx=commit_idx,
2156 return self.get_commit(commit_id=commit_id, commit_idx=commit_idx,
2147 pre_load=pre_load)
2157 pre_load=pre_load)
2148
2158
2149 def get_landing_commit(self):
2159 def get_landing_commit(self):
2150 """
2160 """
2151 Returns landing commit, or if that doesn't exist returns the tip
2161 Returns landing commit, or if that doesn't exist returns the tip
2152 """
2162 """
2153 _rev_type, _rev = self.landing_rev
2163 _rev_type, _rev = self.landing_rev
2154 commit = self.get_commit(_rev)
2164 commit = self.get_commit(_rev)
2155 if isinstance(commit, EmptyCommit):
2165 if isinstance(commit, EmptyCommit):
2156 return self.get_commit()
2166 return self.get_commit()
2157 return commit
2167 return commit
2158
2168
2159 def update_commit_cache(self, cs_cache=None, config=None):
2169 def update_commit_cache(self, cs_cache=None, config=None):
2160 """
2170 """
2161 Update cache of last changeset for repository, keys should be::
2171 Update cache of last changeset for repository, keys should be::
2162
2172
2163 short_id
2173 short_id
2164 raw_id
2174 raw_id
2165 revision
2175 revision
2166 parents
2176 parents
2167 message
2177 message
2168 date
2178 date
2169 author
2179 author
2170
2180
2171 :param cs_cache:
2181 :param cs_cache:
2172 """
2182 """
2173 from rhodecode.lib.vcs.backends.base import BaseChangeset
2183 from rhodecode.lib.vcs.backends.base import BaseChangeset
2174 if cs_cache is None:
2184 if cs_cache is None:
2175 # use no-cache version here
2185 # use no-cache version here
2176 scm_repo = self.scm_instance(cache=False, config=config)
2186 scm_repo = self.scm_instance(cache=False, config=config)
2177 if scm_repo:
2187 if scm_repo:
2178 cs_cache = scm_repo.get_commit(
2188 cs_cache = scm_repo.get_commit(
2179 pre_load=["author", "date", "message", "parents"])
2189 pre_load=["author", "date", "message", "parents"])
2180 else:
2190 else:
2181 cs_cache = EmptyCommit()
2191 cs_cache = EmptyCommit()
2182
2192
2183 if isinstance(cs_cache, BaseChangeset):
2193 if isinstance(cs_cache, BaseChangeset):
2184 cs_cache = cs_cache.__json__()
2194 cs_cache = cs_cache.__json__()
2185
2195
2186 def is_outdated(new_cs_cache):
2196 def is_outdated(new_cs_cache):
2187 if (new_cs_cache['raw_id'] != self.changeset_cache['raw_id'] or
2197 if (new_cs_cache['raw_id'] != self.changeset_cache['raw_id'] or
2188 new_cs_cache['revision'] != self.changeset_cache['revision']):
2198 new_cs_cache['revision'] != self.changeset_cache['revision']):
2189 return True
2199 return True
2190 return False
2200 return False
2191
2201
2192 # check if we have maybe already latest cached revision
2202 # check if we have maybe already latest cached revision
2193 if is_outdated(cs_cache) or not self.changeset_cache:
2203 if is_outdated(cs_cache) or not self.changeset_cache:
2194 _default = datetime.datetime.fromtimestamp(0)
2204 _default = datetime.datetime.fromtimestamp(0)
2195 last_change = cs_cache.get('date') or _default
2205 last_change = cs_cache.get('date') or _default
2196 log.debug('updated repo %s with new cs cache %s',
2206 log.debug('updated repo %s with new cs cache %s',
2197 self.repo_name, cs_cache)
2207 self.repo_name, cs_cache)
2198 self.updated_on = last_change
2208 self.updated_on = last_change
2199 self.changeset_cache = cs_cache
2209 self.changeset_cache = cs_cache
2200 Session().add(self)
2210 Session().add(self)
2201 Session().commit()
2211 Session().commit()
2202 else:
2212 else:
2203 log.debug('Skipping update_commit_cache for repo:`%s` '
2213 log.debug('Skipping update_commit_cache for repo:`%s` '
2204 'commit already with latest changes', self.repo_name)
2214 'commit already with latest changes', self.repo_name)
2205
2215
2206 @property
2216 @property
2207 def tip(self):
2217 def tip(self):
2208 return self.get_commit('tip')
2218 return self.get_commit('tip')
2209
2219
2210 @property
2220 @property
2211 def author(self):
2221 def author(self):
2212 return self.tip.author
2222 return self.tip.author
2213
2223
2214 @property
2224 @property
2215 def last_change(self):
2225 def last_change(self):
2216 return self.scm_instance().last_change
2226 return self.scm_instance().last_change
2217
2227
2218 def get_comments(self, revisions=None):
2228 def get_comments(self, revisions=None):
2219 """
2229 """
2220 Returns comments for this repository grouped by revisions
2230 Returns comments for this repository grouped by revisions
2221
2231
2222 :param revisions: filter query by revisions only
2232 :param revisions: filter query by revisions only
2223 """
2233 """
2224 cmts = ChangesetComment.query()\
2234 cmts = ChangesetComment.query()\
2225 .filter(ChangesetComment.repo == self)
2235 .filter(ChangesetComment.repo == self)
2226 if revisions:
2236 if revisions:
2227 cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
2237 cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
2228 grouped = collections.defaultdict(list)
2238 grouped = collections.defaultdict(list)
2229 for cmt in cmts.all():
2239 for cmt in cmts.all():
2230 grouped[cmt.revision].append(cmt)
2240 grouped[cmt.revision].append(cmt)
2231 return grouped
2241 return grouped
2232
2242
2233 def statuses(self, revisions=None):
2243 def statuses(self, revisions=None):
2234 """
2244 """
2235 Returns statuses for this repository
2245 Returns statuses for this repository
2236
2246
2237 :param revisions: list of revisions to get statuses for
2247 :param revisions: list of revisions to get statuses for
2238 """
2248 """
2239 statuses = ChangesetStatus.query()\
2249 statuses = ChangesetStatus.query()\
2240 .filter(ChangesetStatus.repo == self)\
2250 .filter(ChangesetStatus.repo == self)\
2241 .filter(ChangesetStatus.version == 0)
2251 .filter(ChangesetStatus.version == 0)
2242
2252
2243 if revisions:
2253 if revisions:
2244 # Try doing the filtering in chunks to avoid hitting limits
2254 # Try doing the filtering in chunks to avoid hitting limits
2245 size = 500
2255 size = 500
2246 status_results = []
2256 status_results = []
2247 for chunk in xrange(0, len(revisions), size):
2257 for chunk in xrange(0, len(revisions), size):
2248 status_results += statuses.filter(
2258 status_results += statuses.filter(
2249 ChangesetStatus.revision.in_(
2259 ChangesetStatus.revision.in_(
2250 revisions[chunk: chunk+size])
2260 revisions[chunk: chunk+size])
2251 ).all()
2261 ).all()
2252 else:
2262 else:
2253 status_results = statuses.all()
2263 status_results = statuses.all()
2254
2264
2255 grouped = {}
2265 grouped = {}
2256
2266
2257 # maybe we have open new pullrequest without a status?
2267 # maybe we have open new pullrequest without a status?
2258 stat = ChangesetStatus.STATUS_UNDER_REVIEW
2268 stat = ChangesetStatus.STATUS_UNDER_REVIEW
2259 status_lbl = ChangesetStatus.get_status_lbl(stat)
2269 status_lbl = ChangesetStatus.get_status_lbl(stat)
2260 for pr in PullRequest.query().filter(PullRequest.source_repo == self).all():
2270 for pr in PullRequest.query().filter(PullRequest.source_repo == self).all():
2261 for rev in pr.revisions:
2271 for rev in pr.revisions:
2262 pr_id = pr.pull_request_id
2272 pr_id = pr.pull_request_id
2263 pr_repo = pr.target_repo.repo_name
2273 pr_repo = pr.target_repo.repo_name
2264 grouped[rev] = [stat, status_lbl, pr_id, pr_repo]
2274 grouped[rev] = [stat, status_lbl, pr_id, pr_repo]
2265
2275
2266 for stat in status_results:
2276 for stat in status_results:
2267 pr_id = pr_repo = None
2277 pr_id = pr_repo = None
2268 if stat.pull_request:
2278 if stat.pull_request:
2269 pr_id = stat.pull_request.pull_request_id
2279 pr_id = stat.pull_request.pull_request_id
2270 pr_repo = stat.pull_request.target_repo.repo_name
2280 pr_repo = stat.pull_request.target_repo.repo_name
2271 grouped[stat.revision] = [str(stat.status), stat.status_lbl,
2281 grouped[stat.revision] = [str(stat.status), stat.status_lbl,
2272 pr_id, pr_repo]
2282 pr_id, pr_repo]
2273 return grouped
2283 return grouped
2274
2284
2275 # ==========================================================================
2285 # ==========================================================================
2276 # SCM CACHE INSTANCE
2286 # SCM CACHE INSTANCE
2277 # ==========================================================================
2287 # ==========================================================================
2278
2288
2279 def scm_instance(self, **kwargs):
2289 def scm_instance(self, **kwargs):
2280 import rhodecode
2290 import rhodecode
2281
2291
2282 # Passing a config will not hit the cache currently only used
2292 # Passing a config will not hit the cache currently only used
2283 # for repo2dbmapper
2293 # for repo2dbmapper
2284 config = kwargs.pop('config', None)
2294 config = kwargs.pop('config', None)
2285 cache = kwargs.pop('cache', None)
2295 cache = kwargs.pop('cache', None)
2286 full_cache = str2bool(rhodecode.CONFIG.get('vcs_full_cache'))
2296 full_cache = str2bool(rhodecode.CONFIG.get('vcs_full_cache'))
2287 # if cache is NOT defined use default global, else we have a full
2297 # if cache is NOT defined use default global, else we have a full
2288 # control over cache behaviour
2298 # control over cache behaviour
2289 if cache is None and full_cache and not config:
2299 if cache is None and full_cache and not config:
2290 return self._get_instance_cached()
2300 return self._get_instance_cached()
2291 return self._get_instance(cache=bool(cache), config=config)
2301 return self._get_instance(cache=bool(cache), config=config)
2292
2302
2293 def _get_instance_cached(self):
2303 def _get_instance_cached(self):
2294 @cache_region('long_term')
2304 @cache_region('long_term')
2295 def _get_repo(cache_key):
2305 def _get_repo(cache_key):
2296 return self._get_instance()
2306 return self._get_instance()
2297
2307
2298 invalidator_context = CacheKey.repo_context_cache(
2308 invalidator_context = CacheKey.repo_context_cache(
2299 _get_repo, self.repo_name, None, thread_scoped=True)
2309 _get_repo, self.repo_name, None, thread_scoped=True)
2300
2310
2301 with invalidator_context as context:
2311 with invalidator_context as context:
2302 context.invalidate()
2312 context.invalidate()
2303 repo = context.compute()
2313 repo = context.compute()
2304
2314
2305 return repo
2315 return repo
2306
2316
2307 def _get_instance(self, cache=True, config=None):
2317 def _get_instance(self, cache=True, config=None):
2308 config = config or self._config
2318 config = config or self._config
2309 custom_wire = {
2319 custom_wire = {
2310 'cache': cache # controls the vcs.remote cache
2320 'cache': cache # controls the vcs.remote cache
2311 }
2321 }
2312 repo = get_vcs_instance(
2322 repo = get_vcs_instance(
2313 repo_path=safe_str(self.repo_full_path),
2323 repo_path=safe_str(self.repo_full_path),
2314 config=config,
2324 config=config,
2315 with_wire=custom_wire,
2325 with_wire=custom_wire,
2316 create=False,
2326 create=False,
2317 _vcs_alias=self.repo_type)
2327 _vcs_alias=self.repo_type)
2318
2328
2319 return repo
2329 return repo
2320
2330
2321 def __json__(self):
2331 def __json__(self):
2322 return {'landing_rev': self.landing_rev}
2332 return {'landing_rev': self.landing_rev}
2323
2333
2324 def get_dict(self):
2334 def get_dict(self):
2325
2335
2326 # Since we transformed `repo_name` to a hybrid property, we need to
2336 # Since we transformed `repo_name` to a hybrid property, we need to
2327 # keep compatibility with the code which uses `repo_name` field.
2337 # keep compatibility with the code which uses `repo_name` field.
2328
2338
2329 result = super(Repository, self).get_dict()
2339 result = super(Repository, self).get_dict()
2330 result['repo_name'] = result.pop('_repo_name', None)
2340 result['repo_name'] = result.pop('_repo_name', None)
2331 return result
2341 return result
2332
2342
2333
2343
2334 class RepoGroup(Base, BaseModel):
2344 class RepoGroup(Base, BaseModel):
2335 __tablename__ = 'groups'
2345 __tablename__ = 'groups'
2336 __table_args__ = (
2346 __table_args__ = (
2337 UniqueConstraint('group_name', 'group_parent_id'),
2347 UniqueConstraint('group_name', 'group_parent_id'),
2338 CheckConstraint('group_id != group_parent_id'),
2348 CheckConstraint('group_id != group_parent_id'),
2339 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2349 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2340 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2350 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2341 )
2351 )
2342 __mapper_args__ = {'order_by': 'group_name'}
2352 __mapper_args__ = {'order_by': 'group_name'}
2343
2353
2344 CHOICES_SEPARATOR = '/' # used to generate select2 choices for nested groups
2354 CHOICES_SEPARATOR = '/' # used to generate select2 choices for nested groups
2345
2355
2346 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2356 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2347 group_name = Column("group_name", String(255), nullable=False, unique=True, default=None)
2357 group_name = Column("group_name", String(255), nullable=False, unique=True, default=None)
2348 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
2358 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
2349 group_description = Column("group_description", String(10000), nullable=True, unique=None, default=None)
2359 group_description = Column("group_description", String(10000), nullable=True, unique=None, default=None)
2350 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
2360 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
2351 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
2361 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
2352 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2362 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2353 updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
2363 updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
2354 personal = Column('personal', Boolean(), nullable=True, unique=None, default=None)
2364 personal = Column('personal', Boolean(), nullable=True, unique=None, default=None)
2355
2365
2356 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
2366 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
2357 users_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
2367 users_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
2358 parent_group = relationship('RepoGroup', remote_side=group_id)
2368 parent_group = relationship('RepoGroup', remote_side=group_id)
2359 user = relationship('User')
2369 user = relationship('User')
2360 integrations = relationship('Integration',
2370 integrations = relationship('Integration',
2361 cascade="all, delete, delete-orphan")
2371 cascade="all, delete, delete-orphan")
2362
2372
2363 def __init__(self, group_name='', parent_group=None):
2373 def __init__(self, group_name='', parent_group=None):
2364 self.group_name = group_name
2374 self.group_name = group_name
2365 self.parent_group = parent_group
2375 self.parent_group = parent_group
2366
2376
2367 def __unicode__(self):
2377 def __unicode__(self):
2368 return u"<%s('id:%s:%s')>" % (
2378 return u"<%s('id:%s:%s')>" % (
2369 self.__class__.__name__, self.group_id, self.group_name)
2379 self.__class__.__name__, self.group_id, self.group_name)
2370
2380
2371 @hybrid_property
2381 @hybrid_property
2372 def description_safe(self):
2382 def description_safe(self):
2373 from rhodecode.lib import helpers as h
2383 from rhodecode.lib import helpers as h
2374 return h.escape(self.group_description)
2384 return h.escape(self.group_description)
2375
2385
2376 @classmethod
2386 @classmethod
2377 def _generate_choice(cls, repo_group):
2387 def _generate_choice(cls, repo_group):
2378 from webhelpers.html import literal as _literal
2388 from webhelpers.html import literal as _literal
2379 _name = lambda k: _literal(cls.CHOICES_SEPARATOR.join(k))
2389 _name = lambda k: _literal(cls.CHOICES_SEPARATOR.join(k))
2380 return repo_group.group_id, _name(repo_group.full_path_splitted)
2390 return repo_group.group_id, _name(repo_group.full_path_splitted)
2381
2391
2382 @classmethod
2392 @classmethod
2383 def groups_choices(cls, groups=None, show_empty_group=True):
2393 def groups_choices(cls, groups=None, show_empty_group=True):
2384 if not groups:
2394 if not groups:
2385 groups = cls.query().all()
2395 groups = cls.query().all()
2386
2396
2387 repo_groups = []
2397 repo_groups = []
2388 if show_empty_group:
2398 if show_empty_group:
2389 repo_groups = [(-1, u'-- %s --' % _('No parent'))]
2399 repo_groups = [(-1, u'-- %s --' % _('No parent'))]
2390
2400
2391 repo_groups.extend([cls._generate_choice(x) for x in groups])
2401 repo_groups.extend([cls._generate_choice(x) for x in groups])
2392
2402
2393 repo_groups = sorted(
2403 repo_groups = sorted(
2394 repo_groups, key=lambda t: t[1].split(cls.CHOICES_SEPARATOR)[0])
2404 repo_groups, key=lambda t: t[1].split(cls.CHOICES_SEPARATOR)[0])
2395 return repo_groups
2405 return repo_groups
2396
2406
2397 @classmethod
2407 @classmethod
2398 def url_sep(cls):
2408 def url_sep(cls):
2399 return URL_SEP
2409 return URL_SEP
2400
2410
2401 @classmethod
2411 @classmethod
2402 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
2412 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
2403 if case_insensitive:
2413 if case_insensitive:
2404 gr = cls.query().filter(func.lower(cls.group_name)
2414 gr = cls.query().filter(func.lower(cls.group_name)
2405 == func.lower(group_name))
2415 == func.lower(group_name))
2406 else:
2416 else:
2407 gr = cls.query().filter(cls.group_name == group_name)
2417 gr = cls.query().filter(cls.group_name == group_name)
2408 if cache:
2418 if cache:
2409 name_key = _hash_key(group_name)
2419 name_key = _hash_key(group_name)
2410 gr = gr.options(
2420 gr = gr.options(
2411 FromCache("sql_cache_short", "get_group_%s" % name_key))
2421 FromCache("sql_cache_short", "get_group_%s" % name_key))
2412 return gr.scalar()
2422 return gr.scalar()
2413
2423
2414 @classmethod
2424 @classmethod
2415 def get_user_personal_repo_group(cls, user_id):
2425 def get_user_personal_repo_group(cls, user_id):
2416 user = User.get(user_id)
2426 user = User.get(user_id)
2417 if user.username == User.DEFAULT_USER:
2427 if user.username == User.DEFAULT_USER:
2418 return None
2428 return None
2419
2429
2420 return cls.query()\
2430 return cls.query()\
2421 .filter(cls.personal == true()) \
2431 .filter(cls.personal == true()) \
2422 .filter(cls.user == user).scalar()
2432 .filter(cls.user == user).scalar()
2423
2433
2424 @classmethod
2434 @classmethod
2425 def get_all_repo_groups(cls, user_id=Optional(None), group_id=Optional(None),
2435 def get_all_repo_groups(cls, user_id=Optional(None), group_id=Optional(None),
2426 case_insensitive=True):
2436 case_insensitive=True):
2427 q = RepoGroup.query()
2437 q = RepoGroup.query()
2428
2438
2429 if not isinstance(user_id, Optional):
2439 if not isinstance(user_id, Optional):
2430 q = q.filter(RepoGroup.user_id == user_id)
2440 q = q.filter(RepoGroup.user_id == user_id)
2431
2441
2432 if not isinstance(group_id, Optional):
2442 if not isinstance(group_id, Optional):
2433 q = q.filter(RepoGroup.group_parent_id == group_id)
2443 q = q.filter(RepoGroup.group_parent_id == group_id)
2434
2444
2435 if case_insensitive:
2445 if case_insensitive:
2436 q = q.order_by(func.lower(RepoGroup.group_name))
2446 q = q.order_by(func.lower(RepoGroup.group_name))
2437 else:
2447 else:
2438 q = q.order_by(RepoGroup.group_name)
2448 q = q.order_by(RepoGroup.group_name)
2439 return q.all()
2449 return q.all()
2440
2450
2441 @property
2451 @property
2442 def parents(self):
2452 def parents(self):
2443 parents_recursion_limit = 10
2453 parents_recursion_limit = 10
2444 groups = []
2454 groups = []
2445 if self.parent_group is None:
2455 if self.parent_group is None:
2446 return groups
2456 return groups
2447 cur_gr = self.parent_group
2457 cur_gr = self.parent_group
2448 groups.insert(0, cur_gr)
2458 groups.insert(0, cur_gr)
2449 cnt = 0
2459 cnt = 0
2450 while 1:
2460 while 1:
2451 cnt += 1
2461 cnt += 1
2452 gr = getattr(cur_gr, 'parent_group', None)
2462 gr = getattr(cur_gr, 'parent_group', None)
2453 cur_gr = cur_gr.parent_group
2463 cur_gr = cur_gr.parent_group
2454 if gr is None:
2464 if gr is None:
2455 break
2465 break
2456 if cnt == parents_recursion_limit:
2466 if cnt == parents_recursion_limit:
2457 # this will prevent accidental infinit loops
2467 # this will prevent accidental infinit loops
2458 log.error(('more than %s parents found for group %s, stopping '
2468 log.error(('more than %s parents found for group %s, stopping '
2459 'recursive parent fetching' % (parents_recursion_limit, self)))
2469 'recursive parent fetching' % (parents_recursion_limit, self)))
2460 break
2470 break
2461
2471
2462 groups.insert(0, gr)
2472 groups.insert(0, gr)
2463 return groups
2473 return groups
2464
2474
2465 @property
2475 @property
2466 def last_db_change(self):
2476 def last_db_change(self):
2467 return self.updated_on
2477 return self.updated_on
2468
2478
2469 @property
2479 @property
2470 def children(self):
2480 def children(self):
2471 return RepoGroup.query().filter(RepoGroup.parent_group == self)
2481 return RepoGroup.query().filter(RepoGroup.parent_group == self)
2472
2482
2473 @property
2483 @property
2474 def name(self):
2484 def name(self):
2475 return self.group_name.split(RepoGroup.url_sep())[-1]
2485 return self.group_name.split(RepoGroup.url_sep())[-1]
2476
2486
2477 @property
2487 @property
2478 def full_path(self):
2488 def full_path(self):
2479 return self.group_name
2489 return self.group_name
2480
2490
2481 @property
2491 @property
2482 def full_path_splitted(self):
2492 def full_path_splitted(self):
2483 return self.group_name.split(RepoGroup.url_sep())
2493 return self.group_name.split(RepoGroup.url_sep())
2484
2494
2485 @property
2495 @property
2486 def repositories(self):
2496 def repositories(self):
2487 return Repository.query()\
2497 return Repository.query()\
2488 .filter(Repository.group == self)\
2498 .filter(Repository.group == self)\
2489 .order_by(Repository.repo_name)
2499 .order_by(Repository.repo_name)
2490
2500
2491 @property
2501 @property
2492 def repositories_recursive_count(self):
2502 def repositories_recursive_count(self):
2493 cnt = self.repositories.count()
2503 cnt = self.repositories.count()
2494
2504
2495 def children_count(group):
2505 def children_count(group):
2496 cnt = 0
2506 cnt = 0
2497 for child in group.children:
2507 for child in group.children:
2498 cnt += child.repositories.count()
2508 cnt += child.repositories.count()
2499 cnt += children_count(child)
2509 cnt += children_count(child)
2500 return cnt
2510 return cnt
2501
2511
2502 return cnt + children_count(self)
2512 return cnt + children_count(self)
2503
2513
2504 def _recursive_objects(self, include_repos=True):
2514 def _recursive_objects(self, include_repos=True):
2505 all_ = []
2515 all_ = []
2506
2516
2507 def _get_members(root_gr):
2517 def _get_members(root_gr):
2508 if include_repos:
2518 if include_repos:
2509 for r in root_gr.repositories:
2519 for r in root_gr.repositories:
2510 all_.append(r)
2520 all_.append(r)
2511 childs = root_gr.children.all()
2521 childs = root_gr.children.all()
2512 if childs:
2522 if childs:
2513 for gr in childs:
2523 for gr in childs:
2514 all_.append(gr)
2524 all_.append(gr)
2515 _get_members(gr)
2525 _get_members(gr)
2516
2526
2517 _get_members(self)
2527 _get_members(self)
2518 return [self] + all_
2528 return [self] + all_
2519
2529
2520 def recursive_groups_and_repos(self):
2530 def recursive_groups_and_repos(self):
2521 """
2531 """
2522 Recursive return all groups, with repositories in those groups
2532 Recursive return all groups, with repositories in those groups
2523 """
2533 """
2524 return self._recursive_objects()
2534 return self._recursive_objects()
2525
2535
2526 def recursive_groups(self):
2536 def recursive_groups(self):
2527 """
2537 """
2528 Returns all children groups for this group including children of children
2538 Returns all children groups for this group including children of children
2529 """
2539 """
2530 return self._recursive_objects(include_repos=False)
2540 return self._recursive_objects(include_repos=False)
2531
2541
2532 def get_new_name(self, group_name):
2542 def get_new_name(self, group_name):
2533 """
2543 """
2534 returns new full group name based on parent and new name
2544 returns new full group name based on parent and new name
2535
2545
2536 :param group_name:
2546 :param group_name:
2537 """
2547 """
2538 path_prefix = (self.parent_group.full_path_splitted if
2548 path_prefix = (self.parent_group.full_path_splitted if
2539 self.parent_group else [])
2549 self.parent_group else [])
2540 return RepoGroup.url_sep().join(path_prefix + [group_name])
2550 return RepoGroup.url_sep().join(path_prefix + [group_name])
2541
2551
2542 def permissions(self, with_admins=True, with_owner=True):
2552 def permissions(self, with_admins=True, with_owner=True):
2543 q = UserRepoGroupToPerm.query().filter(UserRepoGroupToPerm.group == self)
2553 q = UserRepoGroupToPerm.query().filter(UserRepoGroupToPerm.group == self)
2544 q = q.options(joinedload(UserRepoGroupToPerm.group),
2554 q = q.options(joinedload(UserRepoGroupToPerm.group),
2545 joinedload(UserRepoGroupToPerm.user),
2555 joinedload(UserRepoGroupToPerm.user),
2546 joinedload(UserRepoGroupToPerm.permission),)
2556 joinedload(UserRepoGroupToPerm.permission),)
2547
2557
2548 # get owners and admins and permissions. We do a trick of re-writing
2558 # get owners and admins and permissions. We do a trick of re-writing
2549 # objects from sqlalchemy to named-tuples due to sqlalchemy session
2559 # objects from sqlalchemy to named-tuples due to sqlalchemy session
2550 # has a global reference and changing one object propagates to all
2560 # has a global reference and changing one object propagates to all
2551 # others. This means if admin is also an owner admin_row that change
2561 # others. This means if admin is also an owner admin_row that change
2552 # would propagate to both objects
2562 # would propagate to both objects
2553 perm_rows = []
2563 perm_rows = []
2554 for _usr in q.all():
2564 for _usr in q.all():
2555 usr = AttributeDict(_usr.user.get_dict())
2565 usr = AttributeDict(_usr.user.get_dict())
2556 usr.permission = _usr.permission.permission_name
2566 usr.permission = _usr.permission.permission_name
2557 perm_rows.append(usr)
2567 perm_rows.append(usr)
2558
2568
2559 # filter the perm rows by 'default' first and then sort them by
2569 # filter the perm rows by 'default' first and then sort them by
2560 # admin,write,read,none permissions sorted again alphabetically in
2570 # admin,write,read,none permissions sorted again alphabetically in
2561 # each group
2571 # each group
2562 perm_rows = sorted(perm_rows, key=display_user_sort)
2572 perm_rows = sorted(perm_rows, key=display_user_sort)
2563
2573
2564 _admin_perm = 'group.admin'
2574 _admin_perm = 'group.admin'
2565 owner_row = []
2575 owner_row = []
2566 if with_owner:
2576 if with_owner:
2567 usr = AttributeDict(self.user.get_dict())
2577 usr = AttributeDict(self.user.get_dict())
2568 usr.owner_row = True
2578 usr.owner_row = True
2569 usr.permission = _admin_perm
2579 usr.permission = _admin_perm
2570 owner_row.append(usr)
2580 owner_row.append(usr)
2571
2581
2572 super_admin_rows = []
2582 super_admin_rows = []
2573 if with_admins:
2583 if with_admins:
2574 for usr in User.get_all_super_admins():
2584 for usr in User.get_all_super_admins():
2575 # if this admin is also owner, don't double the record
2585 # if this admin is also owner, don't double the record
2576 if usr.user_id == owner_row[0].user_id:
2586 if usr.user_id == owner_row[0].user_id:
2577 owner_row[0].admin_row = True
2587 owner_row[0].admin_row = True
2578 else:
2588 else:
2579 usr = AttributeDict(usr.get_dict())
2589 usr = AttributeDict(usr.get_dict())
2580 usr.admin_row = True
2590 usr.admin_row = True
2581 usr.permission = _admin_perm
2591 usr.permission = _admin_perm
2582 super_admin_rows.append(usr)
2592 super_admin_rows.append(usr)
2583
2593
2584 return super_admin_rows + owner_row + perm_rows
2594 return super_admin_rows + owner_row + perm_rows
2585
2595
2586 def permission_user_groups(self):
2596 def permission_user_groups(self):
2587 q = UserGroupRepoGroupToPerm.query().filter(UserGroupRepoGroupToPerm.group == self)
2597 q = UserGroupRepoGroupToPerm.query().filter(UserGroupRepoGroupToPerm.group == self)
2588 q = q.options(joinedload(UserGroupRepoGroupToPerm.group),
2598 q = q.options(joinedload(UserGroupRepoGroupToPerm.group),
2589 joinedload(UserGroupRepoGroupToPerm.users_group),
2599 joinedload(UserGroupRepoGroupToPerm.users_group),
2590 joinedload(UserGroupRepoGroupToPerm.permission),)
2600 joinedload(UserGroupRepoGroupToPerm.permission),)
2591
2601
2592 perm_rows = []
2602 perm_rows = []
2593 for _user_group in q.all():
2603 for _user_group in q.all():
2594 usr = AttributeDict(_user_group.users_group.get_dict())
2604 usr = AttributeDict(_user_group.users_group.get_dict())
2595 usr.permission = _user_group.permission.permission_name
2605 usr.permission = _user_group.permission.permission_name
2596 perm_rows.append(usr)
2606 perm_rows.append(usr)
2597
2607
2598 perm_rows = sorted(perm_rows, key=display_user_group_sort)
2608 perm_rows = sorted(perm_rows, key=display_user_group_sort)
2599 return perm_rows
2609 return perm_rows
2600
2610
2601 def get_api_data(self):
2611 def get_api_data(self):
2602 """
2612 """
2603 Common function for generating api data
2613 Common function for generating api data
2604
2614
2605 """
2615 """
2606 group = self
2616 group = self
2607 data = {
2617 data = {
2608 'group_id': group.group_id,
2618 'group_id': group.group_id,
2609 'group_name': group.group_name,
2619 'group_name': group.group_name,
2610 'group_description': group.description_safe,
2620 'group_description': group.description_safe,
2611 'parent_group': group.parent_group.group_name if group.parent_group else None,
2621 'parent_group': group.parent_group.group_name if group.parent_group else None,
2612 'repositories': [x.repo_name for x in group.repositories],
2622 'repositories': [x.repo_name for x in group.repositories],
2613 'owner': group.user.username,
2623 'owner': group.user.username,
2614 }
2624 }
2615 return data
2625 return data
2616
2626
2617
2627
2618 class Permission(Base, BaseModel):
2628 class Permission(Base, BaseModel):
2619 __tablename__ = 'permissions'
2629 __tablename__ = 'permissions'
2620 __table_args__ = (
2630 __table_args__ = (
2621 Index('p_perm_name_idx', 'permission_name'),
2631 Index('p_perm_name_idx', 'permission_name'),
2622 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2632 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2623 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2633 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2624 )
2634 )
2625 PERMS = [
2635 PERMS = [
2626 ('hg.admin', _('RhodeCode Super Administrator')),
2636 ('hg.admin', _('RhodeCode Super Administrator')),
2627
2637
2628 ('repository.none', _('Repository no access')),
2638 ('repository.none', _('Repository no access')),
2629 ('repository.read', _('Repository read access')),
2639 ('repository.read', _('Repository read access')),
2630 ('repository.write', _('Repository write access')),
2640 ('repository.write', _('Repository write access')),
2631 ('repository.admin', _('Repository admin access')),
2641 ('repository.admin', _('Repository admin access')),
2632
2642
2633 ('group.none', _('Repository group no access')),
2643 ('group.none', _('Repository group no access')),
2634 ('group.read', _('Repository group read access')),
2644 ('group.read', _('Repository group read access')),
2635 ('group.write', _('Repository group write access')),
2645 ('group.write', _('Repository group write access')),
2636 ('group.admin', _('Repository group admin access')),
2646 ('group.admin', _('Repository group admin access')),
2637
2647
2638 ('usergroup.none', _('User group no access')),
2648 ('usergroup.none', _('User group no access')),
2639 ('usergroup.read', _('User group read access')),
2649 ('usergroup.read', _('User group read access')),
2640 ('usergroup.write', _('User group write access')),
2650 ('usergroup.write', _('User group write access')),
2641 ('usergroup.admin', _('User group admin access')),
2651 ('usergroup.admin', _('User group admin access')),
2642
2652
2643 ('hg.repogroup.create.false', _('Repository Group creation disabled')),
2653 ('hg.repogroup.create.false', _('Repository Group creation disabled')),
2644 ('hg.repogroup.create.true', _('Repository Group creation enabled')),
2654 ('hg.repogroup.create.true', _('Repository Group creation enabled')),
2645
2655
2646 ('hg.usergroup.create.false', _('User Group creation disabled')),
2656 ('hg.usergroup.create.false', _('User Group creation disabled')),
2647 ('hg.usergroup.create.true', _('User Group creation enabled')),
2657 ('hg.usergroup.create.true', _('User Group creation enabled')),
2648
2658
2649 ('hg.create.none', _('Repository creation disabled')),
2659 ('hg.create.none', _('Repository creation disabled')),
2650 ('hg.create.repository', _('Repository creation enabled')),
2660 ('hg.create.repository', _('Repository creation enabled')),
2651 ('hg.create.write_on_repogroup.true', _('Repository creation enabled with write permission to a repository group')),
2661 ('hg.create.write_on_repogroup.true', _('Repository creation enabled with write permission to a repository group')),
2652 ('hg.create.write_on_repogroup.false', _('Repository creation disabled with write permission to a repository group')),
2662 ('hg.create.write_on_repogroup.false', _('Repository creation disabled with write permission to a repository group')),
2653
2663
2654 ('hg.fork.none', _('Repository forking disabled')),
2664 ('hg.fork.none', _('Repository forking disabled')),
2655 ('hg.fork.repository', _('Repository forking enabled')),
2665 ('hg.fork.repository', _('Repository forking enabled')),
2656
2666
2657 ('hg.register.none', _('Registration disabled')),
2667 ('hg.register.none', _('Registration disabled')),
2658 ('hg.register.manual_activate', _('User Registration with manual account activation')),
2668 ('hg.register.manual_activate', _('User Registration with manual account activation')),
2659 ('hg.register.auto_activate', _('User Registration with automatic account activation')),
2669 ('hg.register.auto_activate', _('User Registration with automatic account activation')),
2660
2670
2661 ('hg.password_reset.enabled', _('Password reset enabled')),
2671 ('hg.password_reset.enabled', _('Password reset enabled')),
2662 ('hg.password_reset.hidden', _('Password reset hidden')),
2672 ('hg.password_reset.hidden', _('Password reset hidden')),
2663 ('hg.password_reset.disabled', _('Password reset disabled')),
2673 ('hg.password_reset.disabled', _('Password reset disabled')),
2664
2674
2665 ('hg.extern_activate.manual', _('Manual activation of external account')),
2675 ('hg.extern_activate.manual', _('Manual activation of external account')),
2666 ('hg.extern_activate.auto', _('Automatic activation of external account')),
2676 ('hg.extern_activate.auto', _('Automatic activation of external account')),
2667
2677
2668 ('hg.inherit_default_perms.false', _('Inherit object permissions from default user disabled')),
2678 ('hg.inherit_default_perms.false', _('Inherit object permissions from default user disabled')),
2669 ('hg.inherit_default_perms.true', _('Inherit object permissions from default user enabled')),
2679 ('hg.inherit_default_perms.true', _('Inherit object permissions from default user enabled')),
2670 ]
2680 ]
2671
2681
2672 # definition of system default permissions for DEFAULT user
2682 # definition of system default permissions for DEFAULT user
2673 DEFAULT_USER_PERMISSIONS = [
2683 DEFAULT_USER_PERMISSIONS = [
2674 'repository.read',
2684 'repository.read',
2675 'group.read',
2685 'group.read',
2676 'usergroup.read',
2686 'usergroup.read',
2677 'hg.create.repository',
2687 'hg.create.repository',
2678 'hg.repogroup.create.false',
2688 'hg.repogroup.create.false',
2679 'hg.usergroup.create.false',
2689 'hg.usergroup.create.false',
2680 'hg.create.write_on_repogroup.true',
2690 'hg.create.write_on_repogroup.true',
2681 'hg.fork.repository',
2691 'hg.fork.repository',
2682 'hg.register.manual_activate',
2692 'hg.register.manual_activate',
2683 'hg.password_reset.enabled',
2693 'hg.password_reset.enabled',
2684 'hg.extern_activate.auto',
2694 'hg.extern_activate.auto',
2685 'hg.inherit_default_perms.true',
2695 'hg.inherit_default_perms.true',
2686 ]
2696 ]
2687
2697
2688 # defines which permissions are more important higher the more important
2698 # defines which permissions are more important higher the more important
2689 # Weight defines which permissions are more important.
2699 # Weight defines which permissions are more important.
2690 # The higher number the more important.
2700 # The higher number the more important.
2691 PERM_WEIGHTS = {
2701 PERM_WEIGHTS = {
2692 'repository.none': 0,
2702 'repository.none': 0,
2693 'repository.read': 1,
2703 'repository.read': 1,
2694 'repository.write': 3,
2704 'repository.write': 3,
2695 'repository.admin': 4,
2705 'repository.admin': 4,
2696
2706
2697 'group.none': 0,
2707 'group.none': 0,
2698 'group.read': 1,
2708 'group.read': 1,
2699 'group.write': 3,
2709 'group.write': 3,
2700 'group.admin': 4,
2710 'group.admin': 4,
2701
2711
2702 'usergroup.none': 0,
2712 'usergroup.none': 0,
2703 'usergroup.read': 1,
2713 'usergroup.read': 1,
2704 'usergroup.write': 3,
2714 'usergroup.write': 3,
2705 'usergroup.admin': 4,
2715 'usergroup.admin': 4,
2706
2716
2707 'hg.repogroup.create.false': 0,
2717 'hg.repogroup.create.false': 0,
2708 'hg.repogroup.create.true': 1,
2718 'hg.repogroup.create.true': 1,
2709
2719
2710 'hg.usergroup.create.false': 0,
2720 'hg.usergroup.create.false': 0,
2711 'hg.usergroup.create.true': 1,
2721 'hg.usergroup.create.true': 1,
2712
2722
2713 'hg.fork.none': 0,
2723 'hg.fork.none': 0,
2714 'hg.fork.repository': 1,
2724 'hg.fork.repository': 1,
2715 'hg.create.none': 0,
2725 'hg.create.none': 0,
2716 'hg.create.repository': 1
2726 'hg.create.repository': 1
2717 }
2727 }
2718
2728
2719 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2729 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2720 permission_name = Column("permission_name", String(255), nullable=True, unique=None, default=None)
2730 permission_name = Column("permission_name", String(255), nullable=True, unique=None, default=None)
2721 permission_longname = Column("permission_longname", String(255), nullable=True, unique=None, default=None)
2731 permission_longname = Column("permission_longname", String(255), nullable=True, unique=None, default=None)
2722
2732
2723 def __unicode__(self):
2733 def __unicode__(self):
2724 return u"<%s('%s:%s')>" % (
2734 return u"<%s('%s:%s')>" % (
2725 self.__class__.__name__, self.permission_id, self.permission_name
2735 self.__class__.__name__, self.permission_id, self.permission_name
2726 )
2736 )
2727
2737
2728 @classmethod
2738 @classmethod
2729 def get_by_key(cls, key):
2739 def get_by_key(cls, key):
2730 return cls.query().filter(cls.permission_name == key).scalar()
2740 return cls.query().filter(cls.permission_name == key).scalar()
2731
2741
2732 @classmethod
2742 @classmethod
2733 def get_default_repo_perms(cls, user_id, repo_id=None):
2743 def get_default_repo_perms(cls, user_id, repo_id=None):
2734 q = Session().query(UserRepoToPerm, Repository, Permission)\
2744 q = Session().query(UserRepoToPerm, Repository, Permission)\
2735 .join((Permission, UserRepoToPerm.permission_id == Permission.permission_id))\
2745 .join((Permission, UserRepoToPerm.permission_id == Permission.permission_id))\
2736 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
2746 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
2737 .filter(UserRepoToPerm.user_id == user_id)
2747 .filter(UserRepoToPerm.user_id == user_id)
2738 if repo_id:
2748 if repo_id:
2739 q = q.filter(UserRepoToPerm.repository_id == repo_id)
2749 q = q.filter(UserRepoToPerm.repository_id == repo_id)
2740 return q.all()
2750 return q.all()
2741
2751
2742 @classmethod
2752 @classmethod
2743 def get_default_repo_perms_from_user_group(cls, user_id, repo_id=None):
2753 def get_default_repo_perms_from_user_group(cls, user_id, repo_id=None):
2744 q = Session().query(UserGroupRepoToPerm, Repository, Permission)\
2754 q = Session().query(UserGroupRepoToPerm, Repository, Permission)\
2745 .join(
2755 .join(
2746 Permission,
2756 Permission,
2747 UserGroupRepoToPerm.permission_id == Permission.permission_id)\
2757 UserGroupRepoToPerm.permission_id == Permission.permission_id)\
2748 .join(
2758 .join(
2749 Repository,
2759 Repository,
2750 UserGroupRepoToPerm.repository_id == Repository.repo_id)\
2760 UserGroupRepoToPerm.repository_id == Repository.repo_id)\
2751 .join(
2761 .join(
2752 UserGroup,
2762 UserGroup,
2753 UserGroupRepoToPerm.users_group_id ==
2763 UserGroupRepoToPerm.users_group_id ==
2754 UserGroup.users_group_id)\
2764 UserGroup.users_group_id)\
2755 .join(
2765 .join(
2756 UserGroupMember,
2766 UserGroupMember,
2757 UserGroupRepoToPerm.users_group_id ==
2767 UserGroupRepoToPerm.users_group_id ==
2758 UserGroupMember.users_group_id)\
2768 UserGroupMember.users_group_id)\
2759 .filter(
2769 .filter(
2760 UserGroupMember.user_id == user_id,
2770 UserGroupMember.user_id == user_id,
2761 UserGroup.users_group_active == true())
2771 UserGroup.users_group_active == true())
2762 if repo_id:
2772 if repo_id:
2763 q = q.filter(UserGroupRepoToPerm.repository_id == repo_id)
2773 q = q.filter(UserGroupRepoToPerm.repository_id == repo_id)
2764 return q.all()
2774 return q.all()
2765
2775
2766 @classmethod
2776 @classmethod
2767 def get_default_group_perms(cls, user_id, repo_group_id=None):
2777 def get_default_group_perms(cls, user_id, repo_group_id=None):
2768 q = Session().query(UserRepoGroupToPerm, RepoGroup, Permission)\
2778 q = Session().query(UserRepoGroupToPerm, RepoGroup, Permission)\
2769 .join((Permission, UserRepoGroupToPerm.permission_id == Permission.permission_id))\
2779 .join((Permission, UserRepoGroupToPerm.permission_id == Permission.permission_id))\
2770 .join((RepoGroup, UserRepoGroupToPerm.group_id == RepoGroup.group_id))\
2780 .join((RepoGroup, UserRepoGroupToPerm.group_id == RepoGroup.group_id))\
2771 .filter(UserRepoGroupToPerm.user_id == user_id)
2781 .filter(UserRepoGroupToPerm.user_id == user_id)
2772 if repo_group_id:
2782 if repo_group_id:
2773 q = q.filter(UserRepoGroupToPerm.group_id == repo_group_id)
2783 q = q.filter(UserRepoGroupToPerm.group_id == repo_group_id)
2774 return q.all()
2784 return q.all()
2775
2785
2776 @classmethod
2786 @classmethod
2777 def get_default_group_perms_from_user_group(
2787 def get_default_group_perms_from_user_group(
2778 cls, user_id, repo_group_id=None):
2788 cls, user_id, repo_group_id=None):
2779 q = Session().query(UserGroupRepoGroupToPerm, RepoGroup, Permission)\
2789 q = Session().query(UserGroupRepoGroupToPerm, RepoGroup, Permission)\
2780 .join(
2790 .join(
2781 Permission,
2791 Permission,
2782 UserGroupRepoGroupToPerm.permission_id ==
2792 UserGroupRepoGroupToPerm.permission_id ==
2783 Permission.permission_id)\
2793 Permission.permission_id)\
2784 .join(
2794 .join(
2785 RepoGroup,
2795 RepoGroup,
2786 UserGroupRepoGroupToPerm.group_id == RepoGroup.group_id)\
2796 UserGroupRepoGroupToPerm.group_id == RepoGroup.group_id)\
2787 .join(
2797 .join(
2788 UserGroup,
2798 UserGroup,
2789 UserGroupRepoGroupToPerm.users_group_id ==
2799 UserGroupRepoGroupToPerm.users_group_id ==
2790 UserGroup.users_group_id)\
2800 UserGroup.users_group_id)\
2791 .join(
2801 .join(
2792 UserGroupMember,
2802 UserGroupMember,
2793 UserGroupRepoGroupToPerm.users_group_id ==
2803 UserGroupRepoGroupToPerm.users_group_id ==
2794 UserGroupMember.users_group_id)\
2804 UserGroupMember.users_group_id)\
2795 .filter(
2805 .filter(
2796 UserGroupMember.user_id == user_id,
2806 UserGroupMember.user_id == user_id,
2797 UserGroup.users_group_active == true())
2807 UserGroup.users_group_active == true())
2798 if repo_group_id:
2808 if repo_group_id:
2799 q = q.filter(UserGroupRepoGroupToPerm.group_id == repo_group_id)
2809 q = q.filter(UserGroupRepoGroupToPerm.group_id == repo_group_id)
2800 return q.all()
2810 return q.all()
2801
2811
2802 @classmethod
2812 @classmethod
2803 def get_default_user_group_perms(cls, user_id, user_group_id=None):
2813 def get_default_user_group_perms(cls, user_id, user_group_id=None):
2804 q = Session().query(UserUserGroupToPerm, UserGroup, Permission)\
2814 q = Session().query(UserUserGroupToPerm, UserGroup, Permission)\
2805 .join((Permission, UserUserGroupToPerm.permission_id == Permission.permission_id))\
2815 .join((Permission, UserUserGroupToPerm.permission_id == Permission.permission_id))\
2806 .join((UserGroup, UserUserGroupToPerm.user_group_id == UserGroup.users_group_id))\
2816 .join((UserGroup, UserUserGroupToPerm.user_group_id == UserGroup.users_group_id))\
2807 .filter(UserUserGroupToPerm.user_id == user_id)
2817 .filter(UserUserGroupToPerm.user_id == user_id)
2808 if user_group_id:
2818 if user_group_id:
2809 q = q.filter(UserUserGroupToPerm.user_group_id == user_group_id)
2819 q = q.filter(UserUserGroupToPerm.user_group_id == user_group_id)
2810 return q.all()
2820 return q.all()
2811
2821
2812 @classmethod
2822 @classmethod
2813 def get_default_user_group_perms_from_user_group(
2823 def get_default_user_group_perms_from_user_group(
2814 cls, user_id, user_group_id=None):
2824 cls, user_id, user_group_id=None):
2815 TargetUserGroup = aliased(UserGroup, name='target_user_group')
2825 TargetUserGroup = aliased(UserGroup, name='target_user_group')
2816 q = Session().query(UserGroupUserGroupToPerm, UserGroup, Permission)\
2826 q = Session().query(UserGroupUserGroupToPerm, UserGroup, Permission)\
2817 .join(
2827 .join(
2818 Permission,
2828 Permission,
2819 UserGroupUserGroupToPerm.permission_id ==
2829 UserGroupUserGroupToPerm.permission_id ==
2820 Permission.permission_id)\
2830 Permission.permission_id)\
2821 .join(
2831 .join(
2822 TargetUserGroup,
2832 TargetUserGroup,
2823 UserGroupUserGroupToPerm.target_user_group_id ==
2833 UserGroupUserGroupToPerm.target_user_group_id ==
2824 TargetUserGroup.users_group_id)\
2834 TargetUserGroup.users_group_id)\
2825 .join(
2835 .join(
2826 UserGroup,
2836 UserGroup,
2827 UserGroupUserGroupToPerm.user_group_id ==
2837 UserGroupUserGroupToPerm.user_group_id ==
2828 UserGroup.users_group_id)\
2838 UserGroup.users_group_id)\
2829 .join(
2839 .join(
2830 UserGroupMember,
2840 UserGroupMember,
2831 UserGroupUserGroupToPerm.user_group_id ==
2841 UserGroupUserGroupToPerm.user_group_id ==
2832 UserGroupMember.users_group_id)\
2842 UserGroupMember.users_group_id)\
2833 .filter(
2843 .filter(
2834 UserGroupMember.user_id == user_id,
2844 UserGroupMember.user_id == user_id,
2835 UserGroup.users_group_active == true())
2845 UserGroup.users_group_active == true())
2836 if user_group_id:
2846 if user_group_id:
2837 q = q.filter(
2847 q = q.filter(
2838 UserGroupUserGroupToPerm.user_group_id == user_group_id)
2848 UserGroupUserGroupToPerm.user_group_id == user_group_id)
2839
2849
2840 return q.all()
2850 return q.all()
2841
2851
2842
2852
2843 class UserRepoToPerm(Base, BaseModel):
2853 class UserRepoToPerm(Base, BaseModel):
2844 __tablename__ = 'repo_to_perm'
2854 __tablename__ = 'repo_to_perm'
2845 __table_args__ = (
2855 __table_args__ = (
2846 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
2856 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
2847 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2857 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2848 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2858 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2849 )
2859 )
2850 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2860 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2851 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2861 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2852 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2862 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2853 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
2863 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
2854
2864
2855 user = relationship('User')
2865 user = relationship('User')
2856 repository = relationship('Repository')
2866 repository = relationship('Repository')
2857 permission = relationship('Permission')
2867 permission = relationship('Permission')
2858
2868
2859 @classmethod
2869 @classmethod
2860 def create(cls, user, repository, permission):
2870 def create(cls, user, repository, permission):
2861 n = cls()
2871 n = cls()
2862 n.user = user
2872 n.user = user
2863 n.repository = repository
2873 n.repository = repository
2864 n.permission = permission
2874 n.permission = permission
2865 Session().add(n)
2875 Session().add(n)
2866 return n
2876 return n
2867
2877
2868 def __unicode__(self):
2878 def __unicode__(self):
2869 return u'<%s => %s >' % (self.user, self.repository)
2879 return u'<%s => %s >' % (self.user, self.repository)
2870
2880
2871
2881
2872 class UserUserGroupToPerm(Base, BaseModel):
2882 class UserUserGroupToPerm(Base, BaseModel):
2873 __tablename__ = 'user_user_group_to_perm'
2883 __tablename__ = 'user_user_group_to_perm'
2874 __table_args__ = (
2884 __table_args__ = (
2875 UniqueConstraint('user_id', 'user_group_id', 'permission_id'),
2885 UniqueConstraint('user_id', 'user_group_id', 'permission_id'),
2876 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2886 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2877 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2887 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2878 )
2888 )
2879 user_user_group_to_perm_id = Column("user_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2889 user_user_group_to_perm_id = Column("user_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2880 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2890 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2881 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2891 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2882 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2892 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2883
2893
2884 user = relationship('User')
2894 user = relationship('User')
2885 user_group = relationship('UserGroup')
2895 user_group = relationship('UserGroup')
2886 permission = relationship('Permission')
2896 permission = relationship('Permission')
2887
2897
2888 @classmethod
2898 @classmethod
2889 def create(cls, user, user_group, permission):
2899 def create(cls, user, user_group, permission):
2890 n = cls()
2900 n = cls()
2891 n.user = user
2901 n.user = user
2892 n.user_group = user_group
2902 n.user_group = user_group
2893 n.permission = permission
2903 n.permission = permission
2894 Session().add(n)
2904 Session().add(n)
2895 return n
2905 return n
2896
2906
2897 def __unicode__(self):
2907 def __unicode__(self):
2898 return u'<%s => %s >' % (self.user, self.user_group)
2908 return u'<%s => %s >' % (self.user, self.user_group)
2899
2909
2900
2910
2901 class UserToPerm(Base, BaseModel):
2911 class UserToPerm(Base, BaseModel):
2902 __tablename__ = 'user_to_perm'
2912 __tablename__ = 'user_to_perm'
2903 __table_args__ = (
2913 __table_args__ = (
2904 UniqueConstraint('user_id', 'permission_id'),
2914 UniqueConstraint('user_id', 'permission_id'),
2905 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2915 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2906 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2916 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2907 )
2917 )
2908 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2918 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2909 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2919 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2910 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2920 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2911
2921
2912 user = relationship('User')
2922 user = relationship('User')
2913 permission = relationship('Permission', lazy='joined')
2923 permission = relationship('Permission', lazy='joined')
2914
2924
2915 def __unicode__(self):
2925 def __unicode__(self):
2916 return u'<%s => %s >' % (self.user, self.permission)
2926 return u'<%s => %s >' % (self.user, self.permission)
2917
2927
2918
2928
2919 class UserGroupRepoToPerm(Base, BaseModel):
2929 class UserGroupRepoToPerm(Base, BaseModel):
2920 __tablename__ = 'users_group_repo_to_perm'
2930 __tablename__ = 'users_group_repo_to_perm'
2921 __table_args__ = (
2931 __table_args__ = (
2922 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
2932 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
2923 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2933 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2924 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2934 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2925 )
2935 )
2926 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2936 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2927 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2937 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2928 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2938 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2929 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
2939 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
2930
2940
2931 users_group = relationship('UserGroup')
2941 users_group = relationship('UserGroup')
2932 permission = relationship('Permission')
2942 permission = relationship('Permission')
2933 repository = relationship('Repository')
2943 repository = relationship('Repository')
2934
2944
2935 @classmethod
2945 @classmethod
2936 def create(cls, users_group, repository, permission):
2946 def create(cls, users_group, repository, permission):
2937 n = cls()
2947 n = cls()
2938 n.users_group = users_group
2948 n.users_group = users_group
2939 n.repository = repository
2949 n.repository = repository
2940 n.permission = permission
2950 n.permission = permission
2941 Session().add(n)
2951 Session().add(n)
2942 return n
2952 return n
2943
2953
2944 def __unicode__(self):
2954 def __unicode__(self):
2945 return u'<UserGroupRepoToPerm:%s => %s >' % (self.users_group, self.repository)
2955 return u'<UserGroupRepoToPerm:%s => %s >' % (self.users_group, self.repository)
2946
2956
2947
2957
2948 class UserGroupUserGroupToPerm(Base, BaseModel):
2958 class UserGroupUserGroupToPerm(Base, BaseModel):
2949 __tablename__ = 'user_group_user_group_to_perm'
2959 __tablename__ = 'user_group_user_group_to_perm'
2950 __table_args__ = (
2960 __table_args__ = (
2951 UniqueConstraint('target_user_group_id', 'user_group_id', 'permission_id'),
2961 UniqueConstraint('target_user_group_id', 'user_group_id', 'permission_id'),
2952 CheckConstraint('target_user_group_id != user_group_id'),
2962 CheckConstraint('target_user_group_id != user_group_id'),
2953 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2963 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2954 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2964 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2955 )
2965 )
2956 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)
2966 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)
2957 target_user_group_id = Column("target_user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2967 target_user_group_id = Column("target_user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2958 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2968 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2959 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2969 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2960
2970
2961 target_user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id')
2971 target_user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id')
2962 user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.user_group_id==UserGroup.users_group_id')
2972 user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.user_group_id==UserGroup.users_group_id')
2963 permission = relationship('Permission')
2973 permission = relationship('Permission')
2964
2974
2965 @classmethod
2975 @classmethod
2966 def create(cls, target_user_group, user_group, permission):
2976 def create(cls, target_user_group, user_group, permission):
2967 n = cls()
2977 n = cls()
2968 n.target_user_group = target_user_group
2978 n.target_user_group = target_user_group
2969 n.user_group = user_group
2979 n.user_group = user_group
2970 n.permission = permission
2980 n.permission = permission
2971 Session().add(n)
2981 Session().add(n)
2972 return n
2982 return n
2973
2983
2974 def __unicode__(self):
2984 def __unicode__(self):
2975 return u'<UserGroupUserGroup:%s => %s >' % (self.target_user_group, self.user_group)
2985 return u'<UserGroupUserGroup:%s => %s >' % (self.target_user_group, self.user_group)
2976
2986
2977
2987
2978 class UserGroupToPerm(Base, BaseModel):
2988 class UserGroupToPerm(Base, BaseModel):
2979 __tablename__ = 'users_group_to_perm'
2989 __tablename__ = 'users_group_to_perm'
2980 __table_args__ = (
2990 __table_args__ = (
2981 UniqueConstraint('users_group_id', 'permission_id',),
2991 UniqueConstraint('users_group_id', 'permission_id',),
2982 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2992 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2983 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2993 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2984 )
2994 )
2985 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2995 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2986 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2996 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2987 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2997 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2988
2998
2989 users_group = relationship('UserGroup')
2999 users_group = relationship('UserGroup')
2990 permission = relationship('Permission')
3000 permission = relationship('Permission')
2991
3001
2992
3002
2993 class UserRepoGroupToPerm(Base, BaseModel):
3003 class UserRepoGroupToPerm(Base, BaseModel):
2994 __tablename__ = 'user_repo_group_to_perm'
3004 __tablename__ = 'user_repo_group_to_perm'
2995 __table_args__ = (
3005 __table_args__ = (
2996 UniqueConstraint('user_id', 'group_id', 'permission_id'),
3006 UniqueConstraint('user_id', 'group_id', 'permission_id'),
2997 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3007 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2998 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3008 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2999 )
3009 )
3000
3010
3001 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3011 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3002 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3012 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3003 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
3013 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
3004 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3014 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3005
3015
3006 user = relationship('User')
3016 user = relationship('User')
3007 group = relationship('RepoGroup')
3017 group = relationship('RepoGroup')
3008 permission = relationship('Permission')
3018 permission = relationship('Permission')
3009
3019
3010 @classmethod
3020 @classmethod
3011 def create(cls, user, repository_group, permission):
3021 def create(cls, user, repository_group, permission):
3012 n = cls()
3022 n = cls()
3013 n.user = user
3023 n.user = user
3014 n.group = repository_group
3024 n.group = repository_group
3015 n.permission = permission
3025 n.permission = permission
3016 Session().add(n)
3026 Session().add(n)
3017 return n
3027 return n
3018
3028
3019
3029
3020 class UserGroupRepoGroupToPerm(Base, BaseModel):
3030 class UserGroupRepoGroupToPerm(Base, BaseModel):
3021 __tablename__ = 'users_group_repo_group_to_perm'
3031 __tablename__ = 'users_group_repo_group_to_perm'
3022 __table_args__ = (
3032 __table_args__ = (
3023 UniqueConstraint('users_group_id', 'group_id'),
3033 UniqueConstraint('users_group_id', 'group_id'),
3024 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3034 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3025 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3035 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3026 )
3036 )
3027
3037
3028 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)
3038 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)
3029 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3039 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3030 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
3040 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
3031 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3041 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3032
3042
3033 users_group = relationship('UserGroup')
3043 users_group = relationship('UserGroup')
3034 permission = relationship('Permission')
3044 permission = relationship('Permission')
3035 group = relationship('RepoGroup')
3045 group = relationship('RepoGroup')
3036
3046
3037 @classmethod
3047 @classmethod
3038 def create(cls, user_group, repository_group, permission):
3048 def create(cls, user_group, repository_group, permission):
3039 n = cls()
3049 n = cls()
3040 n.users_group = user_group
3050 n.users_group = user_group
3041 n.group = repository_group
3051 n.group = repository_group
3042 n.permission = permission
3052 n.permission = permission
3043 Session().add(n)
3053 Session().add(n)
3044 return n
3054 return n
3045
3055
3046 def __unicode__(self):
3056 def __unicode__(self):
3047 return u'<UserGroupRepoGroupToPerm:%s => %s >' % (self.users_group, self.group)
3057 return u'<UserGroupRepoGroupToPerm:%s => %s >' % (self.users_group, self.group)
3048
3058
3049
3059
3050 class Statistics(Base, BaseModel):
3060 class Statistics(Base, BaseModel):
3051 __tablename__ = 'statistics'
3061 __tablename__ = 'statistics'
3052 __table_args__ = (
3062 __table_args__ = (
3053 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3063 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3054 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3064 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3055 )
3065 )
3056 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3066 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3057 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
3067 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
3058 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
3068 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
3059 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
3069 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
3060 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
3070 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
3061 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
3071 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
3062
3072
3063 repository = relationship('Repository', single_parent=True)
3073 repository = relationship('Repository', single_parent=True)
3064
3074
3065
3075
3066 class UserFollowing(Base, BaseModel):
3076 class UserFollowing(Base, BaseModel):
3067 __tablename__ = 'user_followings'
3077 __tablename__ = 'user_followings'
3068 __table_args__ = (
3078 __table_args__ = (
3069 UniqueConstraint('user_id', 'follows_repository_id'),
3079 UniqueConstraint('user_id', 'follows_repository_id'),
3070 UniqueConstraint('user_id', 'follows_user_id'),
3080 UniqueConstraint('user_id', 'follows_user_id'),
3071 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3081 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3072 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3082 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3073 )
3083 )
3074
3084
3075 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3085 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3076 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3086 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3077 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
3087 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
3078 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
3088 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
3079 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
3089 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
3080
3090
3081 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
3091 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
3082
3092
3083 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
3093 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
3084 follows_repository = relationship('Repository', order_by='Repository.repo_name')
3094 follows_repository = relationship('Repository', order_by='Repository.repo_name')
3085
3095
3086 @classmethod
3096 @classmethod
3087 def get_repo_followers(cls, repo_id):
3097 def get_repo_followers(cls, repo_id):
3088 return cls.query().filter(cls.follows_repo_id == repo_id)
3098 return cls.query().filter(cls.follows_repo_id == repo_id)
3089
3099
3090
3100
3091 class CacheKey(Base, BaseModel):
3101 class CacheKey(Base, BaseModel):
3092 __tablename__ = 'cache_invalidation'
3102 __tablename__ = 'cache_invalidation'
3093 __table_args__ = (
3103 __table_args__ = (
3094 UniqueConstraint('cache_key'),
3104 UniqueConstraint('cache_key'),
3095 Index('key_idx', 'cache_key'),
3105 Index('key_idx', 'cache_key'),
3096 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3106 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3097 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3107 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3098 )
3108 )
3099 CACHE_TYPE_ATOM = 'ATOM'
3109 CACHE_TYPE_ATOM = 'ATOM'
3100 CACHE_TYPE_RSS = 'RSS'
3110 CACHE_TYPE_RSS = 'RSS'
3101 CACHE_TYPE_README = 'README'
3111 CACHE_TYPE_README = 'README'
3102
3112
3103 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3113 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3104 cache_key = Column("cache_key", String(255), nullable=True, unique=None, default=None)
3114 cache_key = Column("cache_key", String(255), nullable=True, unique=None, default=None)
3105 cache_args = Column("cache_args", String(255), nullable=True, unique=None, default=None)
3115 cache_args = Column("cache_args", String(255), nullable=True, unique=None, default=None)
3106 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
3116 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
3107
3117
3108 def __init__(self, cache_key, cache_args=''):
3118 def __init__(self, cache_key, cache_args=''):
3109 self.cache_key = cache_key
3119 self.cache_key = cache_key
3110 self.cache_args = cache_args
3120 self.cache_args = cache_args
3111 self.cache_active = False
3121 self.cache_active = False
3112
3122
3113 def __unicode__(self):
3123 def __unicode__(self):
3114 return u"<%s('%s:%s[%s]')>" % (
3124 return u"<%s('%s:%s[%s]')>" % (
3115 self.__class__.__name__,
3125 self.__class__.__name__,
3116 self.cache_id, self.cache_key, self.cache_active)
3126 self.cache_id, self.cache_key, self.cache_active)
3117
3127
3118 def _cache_key_partition(self):
3128 def _cache_key_partition(self):
3119 prefix, repo_name, suffix = self.cache_key.partition(self.cache_args)
3129 prefix, repo_name, suffix = self.cache_key.partition(self.cache_args)
3120 return prefix, repo_name, suffix
3130 return prefix, repo_name, suffix
3121
3131
3122 def get_prefix(self):
3132 def get_prefix(self):
3123 """
3133 """
3124 Try to extract prefix from existing cache key. The key could consist
3134 Try to extract prefix from existing cache key. The key could consist
3125 of prefix, repo_name, suffix
3135 of prefix, repo_name, suffix
3126 """
3136 """
3127 # this returns prefix, repo_name, suffix
3137 # this returns prefix, repo_name, suffix
3128 return self._cache_key_partition()[0]
3138 return self._cache_key_partition()[0]
3129
3139
3130 def get_suffix(self):
3140 def get_suffix(self):
3131 """
3141 """
3132 get suffix that might have been used in _get_cache_key to
3142 get suffix that might have been used in _get_cache_key to
3133 generate self.cache_key. Only used for informational purposes
3143 generate self.cache_key. Only used for informational purposes
3134 in repo_edit.mako.
3144 in repo_edit.mako.
3135 """
3145 """
3136 # prefix, repo_name, suffix
3146 # prefix, repo_name, suffix
3137 return self._cache_key_partition()[2]
3147 return self._cache_key_partition()[2]
3138
3148
3139 @classmethod
3149 @classmethod
3140 def delete_all_cache(cls):
3150 def delete_all_cache(cls):
3141 """
3151 """
3142 Delete all cache keys from database.
3152 Delete all cache keys from database.
3143 Should only be run when all instances are down and all entries
3153 Should only be run when all instances are down and all entries
3144 thus stale.
3154 thus stale.
3145 """
3155 """
3146 cls.query().delete()
3156 cls.query().delete()
3147 Session().commit()
3157 Session().commit()
3148
3158
3149 @classmethod
3159 @classmethod
3150 def get_cache_key(cls, repo_name, cache_type):
3160 def get_cache_key(cls, repo_name, cache_type):
3151 """
3161 """
3152
3162
3153 Generate a cache key for this process of RhodeCode instance.
3163 Generate a cache key for this process of RhodeCode instance.
3154 Prefix most likely will be process id or maybe explicitly set
3164 Prefix most likely will be process id or maybe explicitly set
3155 instance_id from .ini file.
3165 instance_id from .ini file.
3156 """
3166 """
3157 import rhodecode
3167 import rhodecode
3158 prefix = safe_unicode(rhodecode.CONFIG.get('instance_id') or '')
3168 prefix = safe_unicode(rhodecode.CONFIG.get('instance_id') or '')
3159
3169
3160 repo_as_unicode = safe_unicode(repo_name)
3170 repo_as_unicode = safe_unicode(repo_name)
3161 key = u'{}_{}'.format(repo_as_unicode, cache_type) \
3171 key = u'{}_{}'.format(repo_as_unicode, cache_type) \
3162 if cache_type else repo_as_unicode
3172 if cache_type else repo_as_unicode
3163
3173
3164 return u'{}{}'.format(prefix, key)
3174 return u'{}{}'.format(prefix, key)
3165
3175
3166 @classmethod
3176 @classmethod
3167 def set_invalidate(cls, repo_name, delete=False):
3177 def set_invalidate(cls, repo_name, delete=False):
3168 """
3178 """
3169 Mark all caches of a repo as invalid in the database.
3179 Mark all caches of a repo as invalid in the database.
3170 """
3180 """
3171
3181
3172 try:
3182 try:
3173 qry = Session().query(cls).filter(cls.cache_args == repo_name)
3183 qry = Session().query(cls).filter(cls.cache_args == repo_name)
3174 if delete:
3184 if delete:
3175 log.debug('cache objects deleted for repo %s',
3185 log.debug('cache objects deleted for repo %s',
3176 safe_str(repo_name))
3186 safe_str(repo_name))
3177 qry.delete()
3187 qry.delete()
3178 else:
3188 else:
3179 log.debug('cache objects marked as invalid for repo %s',
3189 log.debug('cache objects marked as invalid for repo %s',
3180 safe_str(repo_name))
3190 safe_str(repo_name))
3181 qry.update({"cache_active": False})
3191 qry.update({"cache_active": False})
3182
3192
3183 Session().commit()
3193 Session().commit()
3184 except Exception:
3194 except Exception:
3185 log.exception(
3195 log.exception(
3186 'Cache key invalidation failed for repository %s',
3196 'Cache key invalidation failed for repository %s',
3187 safe_str(repo_name))
3197 safe_str(repo_name))
3188 Session().rollback()
3198 Session().rollback()
3189
3199
3190 @classmethod
3200 @classmethod
3191 def get_active_cache(cls, cache_key):
3201 def get_active_cache(cls, cache_key):
3192 inv_obj = cls.query().filter(cls.cache_key == cache_key).scalar()
3202 inv_obj = cls.query().filter(cls.cache_key == cache_key).scalar()
3193 if inv_obj:
3203 if inv_obj:
3194 return inv_obj
3204 return inv_obj
3195 return None
3205 return None
3196
3206
3197 @classmethod
3207 @classmethod
3198 def repo_context_cache(cls, compute_func, repo_name, cache_type,
3208 def repo_context_cache(cls, compute_func, repo_name, cache_type,
3199 thread_scoped=False):
3209 thread_scoped=False):
3200 """
3210 """
3201 @cache_region('long_term')
3211 @cache_region('long_term')
3202 def _heavy_calculation(cache_key):
3212 def _heavy_calculation(cache_key):
3203 return 'result'
3213 return 'result'
3204
3214
3205 cache_context = CacheKey.repo_context_cache(
3215 cache_context = CacheKey.repo_context_cache(
3206 _heavy_calculation, repo_name, cache_type)
3216 _heavy_calculation, repo_name, cache_type)
3207
3217
3208 with cache_context as context:
3218 with cache_context as context:
3209 context.invalidate()
3219 context.invalidate()
3210 computed = context.compute()
3220 computed = context.compute()
3211
3221
3212 assert computed == 'result'
3222 assert computed == 'result'
3213 """
3223 """
3214 from rhodecode.lib import caches
3224 from rhodecode.lib import caches
3215 return caches.InvalidationContext(
3225 return caches.InvalidationContext(
3216 compute_func, repo_name, cache_type, thread_scoped=thread_scoped)
3226 compute_func, repo_name, cache_type, thread_scoped=thread_scoped)
3217
3227
3218
3228
3219 class ChangesetComment(Base, BaseModel):
3229 class ChangesetComment(Base, BaseModel):
3220 __tablename__ = 'changeset_comments'
3230 __tablename__ = 'changeset_comments'
3221 __table_args__ = (
3231 __table_args__ = (
3222 Index('cc_revision_idx', 'revision'),
3232 Index('cc_revision_idx', 'revision'),
3223 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3233 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3224 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3234 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3225 )
3235 )
3226
3236
3227 COMMENT_OUTDATED = u'comment_outdated'
3237 COMMENT_OUTDATED = u'comment_outdated'
3228 COMMENT_TYPE_NOTE = u'note'
3238 COMMENT_TYPE_NOTE = u'note'
3229 COMMENT_TYPE_TODO = u'todo'
3239 COMMENT_TYPE_TODO = u'todo'
3230 COMMENT_TYPES = [COMMENT_TYPE_NOTE, COMMENT_TYPE_TODO]
3240 COMMENT_TYPES = [COMMENT_TYPE_NOTE, COMMENT_TYPE_TODO]
3231
3241
3232 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
3242 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
3233 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
3243 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
3234 revision = Column('revision', String(40), nullable=True)
3244 revision = Column('revision', String(40), nullable=True)
3235 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
3245 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
3236 pull_request_version_id = Column("pull_request_version_id", Integer(), ForeignKey('pull_request_versions.pull_request_version_id'), nullable=True)
3246 pull_request_version_id = Column("pull_request_version_id", Integer(), ForeignKey('pull_request_versions.pull_request_version_id'), nullable=True)
3237 line_no = Column('line_no', Unicode(10), nullable=True)
3247 line_no = Column('line_no', Unicode(10), nullable=True)
3238 hl_lines = Column('hl_lines', Unicode(512), nullable=True)
3248 hl_lines = Column('hl_lines', Unicode(512), nullable=True)
3239 f_path = Column('f_path', Unicode(1000), nullable=True)
3249 f_path = Column('f_path', Unicode(1000), nullable=True)
3240 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
3250 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
3241 text = Column('text', UnicodeText().with_variant(UnicodeText(25000), 'mysql'), nullable=False)
3251 text = Column('text', UnicodeText().with_variant(UnicodeText(25000), 'mysql'), nullable=False)
3242 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3252 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3243 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3253 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3244 renderer = Column('renderer', Unicode(64), nullable=True)
3254 renderer = Column('renderer', Unicode(64), nullable=True)
3245 display_state = Column('display_state', Unicode(128), nullable=True)
3255 display_state = Column('display_state', Unicode(128), nullable=True)
3246
3256
3247 comment_type = Column('comment_type', Unicode(128), nullable=True, default=COMMENT_TYPE_NOTE)
3257 comment_type = Column('comment_type', Unicode(128), nullable=True, default=COMMENT_TYPE_NOTE)
3248 resolved_comment_id = Column('resolved_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'), nullable=True)
3258 resolved_comment_id = Column('resolved_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'), nullable=True)
3249 resolved_comment = relationship('ChangesetComment', remote_side=comment_id, backref='resolved_by')
3259 resolved_comment = relationship('ChangesetComment', remote_side=comment_id, backref='resolved_by')
3250 author = relationship('User', lazy='joined')
3260 author = relationship('User', lazy='joined')
3251 repo = relationship('Repository')
3261 repo = relationship('Repository')
3252 status_change = relationship('ChangesetStatus', cascade="all, delete, delete-orphan", lazy='joined')
3262 status_change = relationship('ChangesetStatus', cascade="all, delete, delete-orphan", lazy='joined')
3253 pull_request = relationship('PullRequest', lazy='joined')
3263 pull_request = relationship('PullRequest', lazy='joined')
3254 pull_request_version = relationship('PullRequestVersion')
3264 pull_request_version = relationship('PullRequestVersion')
3255
3265
3256 @classmethod
3266 @classmethod
3257 def get_users(cls, revision=None, pull_request_id=None):
3267 def get_users(cls, revision=None, pull_request_id=None):
3258 """
3268 """
3259 Returns user associated with this ChangesetComment. ie those
3269 Returns user associated with this ChangesetComment. ie those
3260 who actually commented
3270 who actually commented
3261
3271
3262 :param cls:
3272 :param cls:
3263 :param revision:
3273 :param revision:
3264 """
3274 """
3265 q = Session().query(User)\
3275 q = Session().query(User)\
3266 .join(ChangesetComment.author)
3276 .join(ChangesetComment.author)
3267 if revision:
3277 if revision:
3268 q = q.filter(cls.revision == revision)
3278 q = q.filter(cls.revision == revision)
3269 elif pull_request_id:
3279 elif pull_request_id:
3270 q = q.filter(cls.pull_request_id == pull_request_id)
3280 q = q.filter(cls.pull_request_id == pull_request_id)
3271 return q.all()
3281 return q.all()
3272
3282
3273 @classmethod
3283 @classmethod
3274 def get_index_from_version(cls, pr_version, versions):
3284 def get_index_from_version(cls, pr_version, versions):
3275 num_versions = [x.pull_request_version_id for x in versions]
3285 num_versions = [x.pull_request_version_id for x in versions]
3276 try:
3286 try:
3277 return num_versions.index(pr_version) +1
3287 return num_versions.index(pr_version) +1
3278 except (IndexError, ValueError):
3288 except (IndexError, ValueError):
3279 return
3289 return
3280
3290
3281 @property
3291 @property
3282 def outdated(self):
3292 def outdated(self):
3283 return self.display_state == self.COMMENT_OUTDATED
3293 return self.display_state == self.COMMENT_OUTDATED
3284
3294
3285 def outdated_at_version(self, version):
3295 def outdated_at_version(self, version):
3286 """
3296 """
3287 Checks if comment is outdated for given pull request version
3297 Checks if comment is outdated for given pull request version
3288 """
3298 """
3289 return self.outdated and self.pull_request_version_id != version
3299 return self.outdated and self.pull_request_version_id != version
3290
3300
3291 def older_than_version(self, version):
3301 def older_than_version(self, version):
3292 """
3302 """
3293 Checks if comment is made from previous version than given
3303 Checks if comment is made from previous version than given
3294 """
3304 """
3295 if version is None:
3305 if version is None:
3296 return self.pull_request_version_id is not None
3306 return self.pull_request_version_id is not None
3297
3307
3298 return self.pull_request_version_id < version
3308 return self.pull_request_version_id < version
3299
3309
3300 @property
3310 @property
3301 def resolved(self):
3311 def resolved(self):
3302 return self.resolved_by[0] if self.resolved_by else None
3312 return self.resolved_by[0] if self.resolved_by else None
3303
3313
3304 @property
3314 @property
3305 def is_todo(self):
3315 def is_todo(self):
3306 return self.comment_type == self.COMMENT_TYPE_TODO
3316 return self.comment_type == self.COMMENT_TYPE_TODO
3307
3317
3308 @property
3318 @property
3309 def is_inline(self):
3319 def is_inline(self):
3310 return self.line_no and self.f_path
3320 return self.line_no and self.f_path
3311
3321
3312 def get_index_version(self, versions):
3322 def get_index_version(self, versions):
3313 return self.get_index_from_version(
3323 return self.get_index_from_version(
3314 self.pull_request_version_id, versions)
3324 self.pull_request_version_id, versions)
3315
3325
3316 def __repr__(self):
3326 def __repr__(self):
3317 if self.comment_id:
3327 if self.comment_id:
3318 return '<DB:Comment #%s>' % self.comment_id
3328 return '<DB:Comment #%s>' % self.comment_id
3319 else:
3329 else:
3320 return '<DB:Comment at %#x>' % id(self)
3330 return '<DB:Comment at %#x>' % id(self)
3321
3331
3322 def get_api_data(self):
3332 def get_api_data(self):
3323 comment = self
3333 comment = self
3324 data = {
3334 data = {
3325 'comment_id': comment.comment_id,
3335 'comment_id': comment.comment_id,
3326 'comment_type': comment.comment_type,
3336 'comment_type': comment.comment_type,
3327 'comment_text': comment.text,
3337 'comment_text': comment.text,
3328 'comment_status': comment.status_change,
3338 'comment_status': comment.status_change,
3329 'comment_f_path': comment.f_path,
3339 'comment_f_path': comment.f_path,
3330 'comment_lineno': comment.line_no,
3340 'comment_lineno': comment.line_no,
3331 'comment_author': comment.author,
3341 'comment_author': comment.author,
3332 'comment_created_on': comment.created_on
3342 'comment_created_on': comment.created_on
3333 }
3343 }
3334 return data
3344 return data
3335
3345
3336 def __json__(self):
3346 def __json__(self):
3337 data = dict()
3347 data = dict()
3338 data.update(self.get_api_data())
3348 data.update(self.get_api_data())
3339 return data
3349 return data
3340
3350
3341
3351
3342 class ChangesetStatus(Base, BaseModel):
3352 class ChangesetStatus(Base, BaseModel):
3343 __tablename__ = 'changeset_statuses'
3353 __tablename__ = 'changeset_statuses'
3344 __table_args__ = (
3354 __table_args__ = (
3345 Index('cs_revision_idx', 'revision'),
3355 Index('cs_revision_idx', 'revision'),
3346 Index('cs_version_idx', 'version'),
3356 Index('cs_version_idx', 'version'),
3347 UniqueConstraint('repo_id', 'revision', 'version'),
3357 UniqueConstraint('repo_id', 'revision', 'version'),
3348 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3358 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3349 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3359 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3350 )
3360 )
3351 STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed'
3361 STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed'
3352 STATUS_APPROVED = 'approved'
3362 STATUS_APPROVED = 'approved'
3353 STATUS_REJECTED = 'rejected'
3363 STATUS_REJECTED = 'rejected'
3354 STATUS_UNDER_REVIEW = 'under_review'
3364 STATUS_UNDER_REVIEW = 'under_review'
3355
3365
3356 STATUSES = [
3366 STATUSES = [
3357 (STATUS_NOT_REVIEWED, _("Not Reviewed")), # (no icon) and default
3367 (STATUS_NOT_REVIEWED, _("Not Reviewed")), # (no icon) and default
3358 (STATUS_APPROVED, _("Approved")),
3368 (STATUS_APPROVED, _("Approved")),
3359 (STATUS_REJECTED, _("Rejected")),
3369 (STATUS_REJECTED, _("Rejected")),
3360 (STATUS_UNDER_REVIEW, _("Under Review")),
3370 (STATUS_UNDER_REVIEW, _("Under Review")),
3361 ]
3371 ]
3362
3372
3363 changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True)
3373 changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True)
3364 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
3374 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
3365 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
3375 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
3366 revision = Column('revision', String(40), nullable=False)
3376 revision = Column('revision', String(40), nullable=False)
3367 status = Column('status', String(128), nullable=False, default=DEFAULT)
3377 status = Column('status', String(128), nullable=False, default=DEFAULT)
3368 changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'))
3378 changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'))
3369 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
3379 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
3370 version = Column('version', Integer(), nullable=False, default=0)
3380 version = Column('version', Integer(), nullable=False, default=0)
3371 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
3381 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
3372
3382
3373 author = relationship('User', lazy='joined')
3383 author = relationship('User', lazy='joined')
3374 repo = relationship('Repository')
3384 repo = relationship('Repository')
3375 comment = relationship('ChangesetComment', lazy='joined')
3385 comment = relationship('ChangesetComment', lazy='joined')
3376 pull_request = relationship('PullRequest', lazy='joined')
3386 pull_request = relationship('PullRequest', lazy='joined')
3377
3387
3378 def __unicode__(self):
3388 def __unicode__(self):
3379 return u"<%s('%s[v%s]:%s')>" % (
3389 return u"<%s('%s[v%s]:%s')>" % (
3380 self.__class__.__name__,
3390 self.__class__.__name__,
3381 self.status, self.version, self.author
3391 self.status, self.version, self.author
3382 )
3392 )
3383
3393
3384 @classmethod
3394 @classmethod
3385 def get_status_lbl(cls, value):
3395 def get_status_lbl(cls, value):
3386 return dict(cls.STATUSES).get(value)
3396 return dict(cls.STATUSES).get(value)
3387
3397
3388 @property
3398 @property
3389 def status_lbl(self):
3399 def status_lbl(self):
3390 return ChangesetStatus.get_status_lbl(self.status)
3400 return ChangesetStatus.get_status_lbl(self.status)
3391
3401
3392 def get_api_data(self):
3402 def get_api_data(self):
3393 status = self
3403 status = self
3394 data = {
3404 data = {
3395 'status_id': status.changeset_status_id,
3405 'status_id': status.changeset_status_id,
3396 'status': status.status,
3406 'status': status.status,
3397 }
3407 }
3398 return data
3408 return data
3399
3409
3400 def __json__(self):
3410 def __json__(self):
3401 data = dict()
3411 data = dict()
3402 data.update(self.get_api_data())
3412 data.update(self.get_api_data())
3403 return data
3413 return data
3404
3414
3405
3415
3406 class _PullRequestBase(BaseModel):
3416 class _PullRequestBase(BaseModel):
3407 """
3417 """
3408 Common attributes of pull request and version entries.
3418 Common attributes of pull request and version entries.
3409 """
3419 """
3410
3420
3411 # .status values
3421 # .status values
3412 STATUS_NEW = u'new'
3422 STATUS_NEW = u'new'
3413 STATUS_OPEN = u'open'
3423 STATUS_OPEN = u'open'
3414 STATUS_CLOSED = u'closed'
3424 STATUS_CLOSED = u'closed'
3415
3425
3416 title = Column('title', Unicode(255), nullable=True)
3426 title = Column('title', Unicode(255), nullable=True)
3417 description = Column(
3427 description = Column(
3418 'description', UnicodeText().with_variant(UnicodeText(10240), 'mysql'),
3428 'description', UnicodeText().with_variant(UnicodeText(10240), 'mysql'),
3419 nullable=True)
3429 nullable=True)
3420 # new/open/closed status of pull request (not approve/reject/etc)
3430 # new/open/closed status of pull request (not approve/reject/etc)
3421 status = Column('status', Unicode(255), nullable=False, default=STATUS_NEW)
3431 status = Column('status', Unicode(255), nullable=False, default=STATUS_NEW)
3422 created_on = Column(
3432 created_on = Column(
3423 'created_on', DateTime(timezone=False), nullable=False,
3433 'created_on', DateTime(timezone=False), nullable=False,
3424 default=datetime.datetime.now)
3434 default=datetime.datetime.now)
3425 updated_on = Column(
3435 updated_on = Column(
3426 'updated_on', DateTime(timezone=False), nullable=False,
3436 'updated_on', DateTime(timezone=False), nullable=False,
3427 default=datetime.datetime.now)
3437 default=datetime.datetime.now)
3428
3438
3429 @declared_attr
3439 @declared_attr
3430 def user_id(cls):
3440 def user_id(cls):
3431 return Column(
3441 return Column(
3432 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
3442 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
3433 unique=None)
3443 unique=None)
3434
3444
3435 # 500 revisions max
3445 # 500 revisions max
3436 _revisions = Column(
3446 _revisions = Column(
3437 'revisions', UnicodeText().with_variant(UnicodeText(20500), 'mysql'))
3447 'revisions', UnicodeText().with_variant(UnicodeText(20500), 'mysql'))
3438
3448
3439 @declared_attr
3449 @declared_attr
3440 def source_repo_id(cls):
3450 def source_repo_id(cls):
3441 # TODO: dan: rename column to source_repo_id
3451 # TODO: dan: rename column to source_repo_id
3442 return Column(
3452 return Column(
3443 'org_repo_id', Integer(), ForeignKey('repositories.repo_id'),
3453 'org_repo_id', Integer(), ForeignKey('repositories.repo_id'),
3444 nullable=False)
3454 nullable=False)
3445
3455
3446 source_ref = Column('org_ref', Unicode(255), nullable=False)
3456 source_ref = Column('org_ref', Unicode(255), nullable=False)
3447
3457
3448 @declared_attr
3458 @declared_attr
3449 def target_repo_id(cls):
3459 def target_repo_id(cls):
3450 # TODO: dan: rename column to target_repo_id
3460 # TODO: dan: rename column to target_repo_id
3451 return Column(
3461 return Column(
3452 'other_repo_id', Integer(), ForeignKey('repositories.repo_id'),
3462 'other_repo_id', Integer(), ForeignKey('repositories.repo_id'),
3453 nullable=False)
3463 nullable=False)
3454
3464
3455 target_ref = Column('other_ref', Unicode(255), nullable=False)
3465 target_ref = Column('other_ref', Unicode(255), nullable=False)
3456 _shadow_merge_ref = Column('shadow_merge_ref', Unicode(255), nullable=True)
3466 _shadow_merge_ref = Column('shadow_merge_ref', Unicode(255), nullable=True)
3457
3467
3458 # TODO: dan: rename column to last_merge_source_rev
3468 # TODO: dan: rename column to last_merge_source_rev
3459 _last_merge_source_rev = Column(
3469 _last_merge_source_rev = Column(
3460 'last_merge_org_rev', String(40), nullable=True)
3470 'last_merge_org_rev', String(40), nullable=True)
3461 # TODO: dan: rename column to last_merge_target_rev
3471 # TODO: dan: rename column to last_merge_target_rev
3462 _last_merge_target_rev = Column(
3472 _last_merge_target_rev = Column(
3463 'last_merge_other_rev', String(40), nullable=True)
3473 'last_merge_other_rev', String(40), nullable=True)
3464 _last_merge_status = Column('merge_status', Integer(), nullable=True)
3474 _last_merge_status = Column('merge_status', Integer(), nullable=True)
3465 merge_rev = Column('merge_rev', String(40), nullable=True)
3475 merge_rev = Column('merge_rev', String(40), nullable=True)
3466
3476
3467 reviewer_data = Column(
3477 reviewer_data = Column(
3468 'reviewer_data_json', MutationObj.as_mutable(
3478 'reviewer_data_json', MutationObj.as_mutable(
3469 JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
3479 JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
3470
3480
3471 @property
3481 @property
3472 def reviewer_data_json(self):
3482 def reviewer_data_json(self):
3473 return json.dumps(self.reviewer_data)
3483 return json.dumps(self.reviewer_data)
3474
3484
3475 @hybrid_property
3485 @hybrid_property
3476 def description_safe(self):
3486 def description_safe(self):
3477 from rhodecode.lib import helpers as h
3487 from rhodecode.lib import helpers as h
3478 return h.escape(self.description)
3488 return h.escape(self.description)
3479
3489
3480 @hybrid_property
3490 @hybrid_property
3481 def revisions(self):
3491 def revisions(self):
3482 return self._revisions.split(':') if self._revisions else []
3492 return self._revisions.split(':') if self._revisions else []
3483
3493
3484 @revisions.setter
3494 @revisions.setter
3485 def revisions(self, val):
3495 def revisions(self, val):
3486 self._revisions = ':'.join(val)
3496 self._revisions = ':'.join(val)
3487
3497
3488 @hybrid_property
3498 @hybrid_property
3489 def last_merge_status(self):
3499 def last_merge_status(self):
3490 return safe_int(self._last_merge_status)
3500 return safe_int(self._last_merge_status)
3491
3501
3492 @last_merge_status.setter
3502 @last_merge_status.setter
3493 def last_merge_status(self, val):
3503 def last_merge_status(self, val):
3494 self._last_merge_status = val
3504 self._last_merge_status = val
3495
3505
3496 @declared_attr
3506 @declared_attr
3497 def author(cls):
3507 def author(cls):
3498 return relationship('User', lazy='joined')
3508 return relationship('User', lazy='joined')
3499
3509
3500 @declared_attr
3510 @declared_attr
3501 def source_repo(cls):
3511 def source_repo(cls):
3502 return relationship(
3512 return relationship(
3503 'Repository',
3513 'Repository',
3504 primaryjoin='%s.source_repo_id==Repository.repo_id' % cls.__name__)
3514 primaryjoin='%s.source_repo_id==Repository.repo_id' % cls.__name__)
3505
3515
3506 @property
3516 @property
3507 def source_ref_parts(self):
3517 def source_ref_parts(self):
3508 return self.unicode_to_reference(self.source_ref)
3518 return self.unicode_to_reference(self.source_ref)
3509
3519
3510 @declared_attr
3520 @declared_attr
3511 def target_repo(cls):
3521 def target_repo(cls):
3512 return relationship(
3522 return relationship(
3513 'Repository',
3523 'Repository',
3514 primaryjoin='%s.target_repo_id==Repository.repo_id' % cls.__name__)
3524 primaryjoin='%s.target_repo_id==Repository.repo_id' % cls.__name__)
3515
3525
3516 @property
3526 @property
3517 def target_ref_parts(self):
3527 def target_ref_parts(self):
3518 return self.unicode_to_reference(self.target_ref)
3528 return self.unicode_to_reference(self.target_ref)
3519
3529
3520 @property
3530 @property
3521 def shadow_merge_ref(self):
3531 def shadow_merge_ref(self):
3522 return self.unicode_to_reference(self._shadow_merge_ref)
3532 return self.unicode_to_reference(self._shadow_merge_ref)
3523
3533
3524 @shadow_merge_ref.setter
3534 @shadow_merge_ref.setter
3525 def shadow_merge_ref(self, ref):
3535 def shadow_merge_ref(self, ref):
3526 self._shadow_merge_ref = self.reference_to_unicode(ref)
3536 self._shadow_merge_ref = self.reference_to_unicode(ref)
3527
3537
3528 def unicode_to_reference(self, raw):
3538 def unicode_to_reference(self, raw):
3529 """
3539 """
3530 Convert a unicode (or string) to a reference object.
3540 Convert a unicode (or string) to a reference object.
3531 If unicode evaluates to False it returns None.
3541 If unicode evaluates to False it returns None.
3532 """
3542 """
3533 if raw:
3543 if raw:
3534 refs = raw.split(':')
3544 refs = raw.split(':')
3535 return Reference(*refs)
3545 return Reference(*refs)
3536 else:
3546 else:
3537 return None
3547 return None
3538
3548
3539 def reference_to_unicode(self, ref):
3549 def reference_to_unicode(self, ref):
3540 """
3550 """
3541 Convert a reference object to unicode.
3551 Convert a reference object to unicode.
3542 If reference is None it returns None.
3552 If reference is None it returns None.
3543 """
3553 """
3544 if ref:
3554 if ref:
3545 return u':'.join(ref)
3555 return u':'.join(ref)
3546 else:
3556 else:
3547 return None
3557 return None
3548
3558
3549 def get_api_data(self, with_merge_state=True):
3559 def get_api_data(self, with_merge_state=True):
3550 from rhodecode.model.pull_request import PullRequestModel
3560 from rhodecode.model.pull_request import PullRequestModel
3551
3561
3552 pull_request = self
3562 pull_request = self
3553 if with_merge_state:
3563 if with_merge_state:
3554 merge_status = PullRequestModel().merge_status(pull_request)
3564 merge_status = PullRequestModel().merge_status(pull_request)
3555 merge_state = {
3565 merge_state = {
3556 'status': merge_status[0],
3566 'status': merge_status[0],
3557 'message': safe_unicode(merge_status[1]),
3567 'message': safe_unicode(merge_status[1]),
3558 }
3568 }
3559 else:
3569 else:
3560 merge_state = {'status': 'not_available',
3570 merge_state = {'status': 'not_available',
3561 'message': 'not_available'}
3571 'message': 'not_available'}
3562
3572
3563 merge_data = {
3573 merge_data = {
3564 'clone_url': PullRequestModel().get_shadow_clone_url(pull_request),
3574 'clone_url': PullRequestModel().get_shadow_clone_url(pull_request),
3565 'reference': (
3575 'reference': (
3566 pull_request.shadow_merge_ref._asdict()
3576 pull_request.shadow_merge_ref._asdict()
3567 if pull_request.shadow_merge_ref else None),
3577 if pull_request.shadow_merge_ref else None),
3568 }
3578 }
3569
3579
3570 data = {
3580 data = {
3571 'pull_request_id': pull_request.pull_request_id,
3581 'pull_request_id': pull_request.pull_request_id,
3572 'url': PullRequestModel().get_url(pull_request),
3582 'url': PullRequestModel().get_url(pull_request),
3573 'title': pull_request.title,
3583 'title': pull_request.title,
3574 'description': pull_request.description,
3584 'description': pull_request.description,
3575 'status': pull_request.status,
3585 'status': pull_request.status,
3576 'created_on': pull_request.created_on,
3586 'created_on': pull_request.created_on,
3577 'updated_on': pull_request.updated_on,
3587 'updated_on': pull_request.updated_on,
3578 'commit_ids': pull_request.revisions,
3588 'commit_ids': pull_request.revisions,
3579 'review_status': pull_request.calculated_review_status(),
3589 'review_status': pull_request.calculated_review_status(),
3580 'mergeable': merge_state,
3590 'mergeable': merge_state,
3581 'source': {
3591 'source': {
3582 'clone_url': pull_request.source_repo.clone_url(),
3592 'clone_url': pull_request.source_repo.clone_url(),
3583 'repository': pull_request.source_repo.repo_name,
3593 'repository': pull_request.source_repo.repo_name,
3584 'reference': {
3594 'reference': {
3585 'name': pull_request.source_ref_parts.name,
3595 'name': pull_request.source_ref_parts.name,
3586 'type': pull_request.source_ref_parts.type,
3596 'type': pull_request.source_ref_parts.type,
3587 'commit_id': pull_request.source_ref_parts.commit_id,
3597 'commit_id': pull_request.source_ref_parts.commit_id,
3588 },
3598 },
3589 },
3599 },
3590 'target': {
3600 'target': {
3591 'clone_url': pull_request.target_repo.clone_url(),
3601 'clone_url': pull_request.target_repo.clone_url(),
3592 'repository': pull_request.target_repo.repo_name,
3602 'repository': pull_request.target_repo.repo_name,
3593 'reference': {
3603 'reference': {
3594 'name': pull_request.target_ref_parts.name,
3604 'name': pull_request.target_ref_parts.name,
3595 'type': pull_request.target_ref_parts.type,
3605 'type': pull_request.target_ref_parts.type,
3596 'commit_id': pull_request.target_ref_parts.commit_id,
3606 'commit_id': pull_request.target_ref_parts.commit_id,
3597 },
3607 },
3598 },
3608 },
3599 'merge': merge_data,
3609 'merge': merge_data,
3600 'author': pull_request.author.get_api_data(include_secrets=False,
3610 'author': pull_request.author.get_api_data(include_secrets=False,
3601 details='basic'),
3611 details='basic'),
3602 'reviewers': [
3612 'reviewers': [
3603 {
3613 {
3604 'user': reviewer.get_api_data(include_secrets=False,
3614 'user': reviewer.get_api_data(include_secrets=False,
3605 details='basic'),
3615 details='basic'),
3606 'reasons': reasons,
3616 'reasons': reasons,
3607 'review_status': st[0][1].status if st else 'not_reviewed',
3617 'review_status': st[0][1].status if st else 'not_reviewed',
3608 }
3618 }
3609 for obj, reviewer, reasons, mandatory, st in
3619 for obj, reviewer, reasons, mandatory, st in
3610 pull_request.reviewers_statuses()
3620 pull_request.reviewers_statuses()
3611 ]
3621 ]
3612 }
3622 }
3613
3623
3614 return data
3624 return data
3615
3625
3616
3626
3617 class PullRequest(Base, _PullRequestBase):
3627 class PullRequest(Base, _PullRequestBase):
3618 __tablename__ = 'pull_requests'
3628 __tablename__ = 'pull_requests'
3619 __table_args__ = (
3629 __table_args__ = (
3620 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3630 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3621 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3631 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3622 )
3632 )
3623
3633
3624 pull_request_id = Column(
3634 pull_request_id = Column(
3625 'pull_request_id', Integer(), nullable=False, primary_key=True)
3635 'pull_request_id', Integer(), nullable=False, primary_key=True)
3626
3636
3627 def __repr__(self):
3637 def __repr__(self):
3628 if self.pull_request_id:
3638 if self.pull_request_id:
3629 return '<DB:PullRequest #%s>' % self.pull_request_id
3639 return '<DB:PullRequest #%s>' % self.pull_request_id
3630 else:
3640 else:
3631 return '<DB:PullRequest at %#x>' % id(self)
3641 return '<DB:PullRequest at %#x>' % id(self)
3632
3642
3633 reviewers = relationship('PullRequestReviewers',
3643 reviewers = relationship('PullRequestReviewers',
3634 cascade="all, delete, delete-orphan")
3644 cascade="all, delete, delete-orphan")
3635 statuses = relationship('ChangesetStatus',
3645 statuses = relationship('ChangesetStatus',
3636 cascade="all, delete, delete-orphan")
3646 cascade="all, delete, delete-orphan")
3637 comments = relationship('ChangesetComment',
3647 comments = relationship('ChangesetComment',
3638 cascade="all, delete, delete-orphan")
3648 cascade="all, delete, delete-orphan")
3639 versions = relationship('PullRequestVersion',
3649 versions = relationship('PullRequestVersion',
3640 cascade="all, delete, delete-orphan",
3650 cascade="all, delete, delete-orphan",
3641 lazy='dynamic')
3651 lazy='dynamic')
3642
3652
3643 @classmethod
3653 @classmethod
3644 def get_pr_display_object(cls, pull_request_obj, org_pull_request_obj,
3654 def get_pr_display_object(cls, pull_request_obj, org_pull_request_obj,
3645 internal_methods=None):
3655 internal_methods=None):
3646
3656
3647 class PullRequestDisplay(object):
3657 class PullRequestDisplay(object):
3648 """
3658 """
3649 Special object wrapper for showing PullRequest data via Versions
3659 Special object wrapper for showing PullRequest data via Versions
3650 It mimics PR object as close as possible. This is read only object
3660 It mimics PR object as close as possible. This is read only object
3651 just for display
3661 just for display
3652 """
3662 """
3653
3663
3654 def __init__(self, attrs, internal=None):
3664 def __init__(self, attrs, internal=None):
3655 self.attrs = attrs
3665 self.attrs = attrs
3656 # internal have priority over the given ones via attrs
3666 # internal have priority over the given ones via attrs
3657 self.internal = internal or ['versions']
3667 self.internal = internal or ['versions']
3658
3668
3659 def __getattr__(self, item):
3669 def __getattr__(self, item):
3660 if item in self.internal:
3670 if item in self.internal:
3661 return getattr(self, item)
3671 return getattr(self, item)
3662 try:
3672 try:
3663 return self.attrs[item]
3673 return self.attrs[item]
3664 except KeyError:
3674 except KeyError:
3665 raise AttributeError(
3675 raise AttributeError(
3666 '%s object has no attribute %s' % (self, item))
3676 '%s object has no attribute %s' % (self, item))
3667
3677
3668 def __repr__(self):
3678 def __repr__(self):
3669 return '<DB:PullRequestDisplay #%s>' % self.attrs.get('pull_request_id')
3679 return '<DB:PullRequestDisplay #%s>' % self.attrs.get('pull_request_id')
3670
3680
3671 def versions(self):
3681 def versions(self):
3672 return pull_request_obj.versions.order_by(
3682 return pull_request_obj.versions.order_by(
3673 PullRequestVersion.pull_request_version_id).all()
3683 PullRequestVersion.pull_request_version_id).all()
3674
3684
3675 def is_closed(self):
3685 def is_closed(self):
3676 return pull_request_obj.is_closed()
3686 return pull_request_obj.is_closed()
3677
3687
3678 @property
3688 @property
3679 def pull_request_version_id(self):
3689 def pull_request_version_id(self):
3680 return getattr(pull_request_obj, 'pull_request_version_id', None)
3690 return getattr(pull_request_obj, 'pull_request_version_id', None)
3681
3691
3682 attrs = StrictAttributeDict(pull_request_obj.get_api_data())
3692 attrs = StrictAttributeDict(pull_request_obj.get_api_data())
3683
3693
3684 attrs.author = StrictAttributeDict(
3694 attrs.author = StrictAttributeDict(
3685 pull_request_obj.author.get_api_data())
3695 pull_request_obj.author.get_api_data())
3686 if pull_request_obj.target_repo:
3696 if pull_request_obj.target_repo:
3687 attrs.target_repo = StrictAttributeDict(
3697 attrs.target_repo = StrictAttributeDict(
3688 pull_request_obj.target_repo.get_api_data())
3698 pull_request_obj.target_repo.get_api_data())
3689 attrs.target_repo.clone_url = pull_request_obj.target_repo.clone_url
3699 attrs.target_repo.clone_url = pull_request_obj.target_repo.clone_url
3690
3700
3691 if pull_request_obj.source_repo:
3701 if pull_request_obj.source_repo:
3692 attrs.source_repo = StrictAttributeDict(
3702 attrs.source_repo = StrictAttributeDict(
3693 pull_request_obj.source_repo.get_api_data())
3703 pull_request_obj.source_repo.get_api_data())
3694 attrs.source_repo.clone_url = pull_request_obj.source_repo.clone_url
3704 attrs.source_repo.clone_url = pull_request_obj.source_repo.clone_url
3695
3705
3696 attrs.source_ref_parts = pull_request_obj.source_ref_parts
3706 attrs.source_ref_parts = pull_request_obj.source_ref_parts
3697 attrs.target_ref_parts = pull_request_obj.target_ref_parts
3707 attrs.target_ref_parts = pull_request_obj.target_ref_parts
3698 attrs.revisions = pull_request_obj.revisions
3708 attrs.revisions = pull_request_obj.revisions
3699
3709
3700 attrs.shadow_merge_ref = org_pull_request_obj.shadow_merge_ref
3710 attrs.shadow_merge_ref = org_pull_request_obj.shadow_merge_ref
3701 attrs.reviewer_data = org_pull_request_obj.reviewer_data
3711 attrs.reviewer_data = org_pull_request_obj.reviewer_data
3702 attrs.reviewer_data_json = org_pull_request_obj.reviewer_data_json
3712 attrs.reviewer_data_json = org_pull_request_obj.reviewer_data_json
3703
3713
3704 return PullRequestDisplay(attrs, internal=internal_methods)
3714 return PullRequestDisplay(attrs, internal=internal_methods)
3705
3715
3706 def is_closed(self):
3716 def is_closed(self):
3707 return self.status == self.STATUS_CLOSED
3717 return self.status == self.STATUS_CLOSED
3708
3718
3709 def __json__(self):
3719 def __json__(self):
3710 return {
3720 return {
3711 'revisions': self.revisions,
3721 'revisions': self.revisions,
3712 }
3722 }
3713
3723
3714 def calculated_review_status(self):
3724 def calculated_review_status(self):
3715 from rhodecode.model.changeset_status import ChangesetStatusModel
3725 from rhodecode.model.changeset_status import ChangesetStatusModel
3716 return ChangesetStatusModel().calculated_review_status(self)
3726 return ChangesetStatusModel().calculated_review_status(self)
3717
3727
3718 def reviewers_statuses(self):
3728 def reviewers_statuses(self):
3719 from rhodecode.model.changeset_status import ChangesetStatusModel
3729 from rhodecode.model.changeset_status import ChangesetStatusModel
3720 return ChangesetStatusModel().reviewers_statuses(self)
3730 return ChangesetStatusModel().reviewers_statuses(self)
3721
3731
3722 @property
3732 @property
3723 def workspace_id(self):
3733 def workspace_id(self):
3724 from rhodecode.model.pull_request import PullRequestModel
3734 from rhodecode.model.pull_request import PullRequestModel
3725 return PullRequestModel()._workspace_id(self)
3735 return PullRequestModel()._workspace_id(self)
3726
3736
3727 def get_shadow_repo(self):
3737 def get_shadow_repo(self):
3728 workspace_id = self.workspace_id
3738 workspace_id = self.workspace_id
3729 vcs_obj = self.target_repo.scm_instance()
3739 vcs_obj = self.target_repo.scm_instance()
3730 shadow_repository_path = vcs_obj._get_shadow_repository_path(
3740 shadow_repository_path = vcs_obj._get_shadow_repository_path(
3731 workspace_id)
3741 workspace_id)
3732 return vcs_obj._get_shadow_instance(shadow_repository_path)
3742 return vcs_obj._get_shadow_instance(shadow_repository_path)
3733
3743
3734
3744
3735 class PullRequestVersion(Base, _PullRequestBase):
3745 class PullRequestVersion(Base, _PullRequestBase):
3736 __tablename__ = 'pull_request_versions'
3746 __tablename__ = 'pull_request_versions'
3737 __table_args__ = (
3747 __table_args__ = (
3738 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3748 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3739 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3749 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3740 )
3750 )
3741
3751
3742 pull_request_version_id = Column(
3752 pull_request_version_id = Column(
3743 'pull_request_version_id', Integer(), nullable=False, primary_key=True)
3753 'pull_request_version_id', Integer(), nullable=False, primary_key=True)
3744 pull_request_id = Column(
3754 pull_request_id = Column(
3745 'pull_request_id', Integer(),
3755 'pull_request_id', Integer(),
3746 ForeignKey('pull_requests.pull_request_id'), nullable=False)
3756 ForeignKey('pull_requests.pull_request_id'), nullable=False)
3747 pull_request = relationship('PullRequest')
3757 pull_request = relationship('PullRequest')
3748
3758
3749 def __repr__(self):
3759 def __repr__(self):
3750 if self.pull_request_version_id:
3760 if self.pull_request_version_id:
3751 return '<DB:PullRequestVersion #%s>' % self.pull_request_version_id
3761 return '<DB:PullRequestVersion #%s>' % self.pull_request_version_id
3752 else:
3762 else:
3753 return '<DB:PullRequestVersion at %#x>' % id(self)
3763 return '<DB:PullRequestVersion at %#x>' % id(self)
3754
3764
3755 @property
3765 @property
3756 def reviewers(self):
3766 def reviewers(self):
3757 return self.pull_request.reviewers
3767 return self.pull_request.reviewers
3758
3768
3759 @property
3769 @property
3760 def versions(self):
3770 def versions(self):
3761 return self.pull_request.versions
3771 return self.pull_request.versions
3762
3772
3763 def is_closed(self):
3773 def is_closed(self):
3764 # calculate from original
3774 # calculate from original
3765 return self.pull_request.status == self.STATUS_CLOSED
3775 return self.pull_request.status == self.STATUS_CLOSED
3766
3776
3767 def calculated_review_status(self):
3777 def calculated_review_status(self):
3768 return self.pull_request.calculated_review_status()
3778 return self.pull_request.calculated_review_status()
3769
3779
3770 def reviewers_statuses(self):
3780 def reviewers_statuses(self):
3771 return self.pull_request.reviewers_statuses()
3781 return self.pull_request.reviewers_statuses()
3772
3782
3773
3783
3774 class PullRequestReviewers(Base, BaseModel):
3784 class PullRequestReviewers(Base, BaseModel):
3775 __tablename__ = 'pull_request_reviewers'
3785 __tablename__ = 'pull_request_reviewers'
3776 __table_args__ = (
3786 __table_args__ = (
3777 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3787 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3778 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3788 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3779 )
3789 )
3780
3790
3781 @hybrid_property
3791 @hybrid_property
3782 def reasons(self):
3792 def reasons(self):
3783 if not self._reasons:
3793 if not self._reasons:
3784 return []
3794 return []
3785 return self._reasons
3795 return self._reasons
3786
3796
3787 @reasons.setter
3797 @reasons.setter
3788 def reasons(self, val):
3798 def reasons(self, val):
3789 val = val or []
3799 val = val or []
3790 if any(not isinstance(x, basestring) for x in val):
3800 if any(not isinstance(x, basestring) for x in val):
3791 raise Exception('invalid reasons type, must be list of strings')
3801 raise Exception('invalid reasons type, must be list of strings')
3792 self._reasons = val
3802 self._reasons = val
3793
3803
3794 pull_requests_reviewers_id = Column(
3804 pull_requests_reviewers_id = Column(
3795 'pull_requests_reviewers_id', Integer(), nullable=False,
3805 'pull_requests_reviewers_id', Integer(), nullable=False,
3796 primary_key=True)
3806 primary_key=True)
3797 pull_request_id = Column(
3807 pull_request_id = Column(
3798 "pull_request_id", Integer(),
3808 "pull_request_id", Integer(),
3799 ForeignKey('pull_requests.pull_request_id'), nullable=False)
3809 ForeignKey('pull_requests.pull_request_id'), nullable=False)
3800 user_id = Column(
3810 user_id = Column(
3801 "user_id", Integer(), ForeignKey('users.user_id'), nullable=True)
3811 "user_id", Integer(), ForeignKey('users.user_id'), nullable=True)
3802 _reasons = Column(
3812 _reasons = Column(
3803 'reason', MutationList.as_mutable(
3813 'reason', MutationList.as_mutable(
3804 JsonType('list', dialect_map=dict(mysql=UnicodeText(16384)))))
3814 JsonType('list', dialect_map=dict(mysql=UnicodeText(16384)))))
3805
3815
3806 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
3816 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
3807 user = relationship('User')
3817 user = relationship('User')
3808 pull_request = relationship('PullRequest')
3818 pull_request = relationship('PullRequest')
3809
3819
3810 rule_data = Column(
3820 rule_data = Column(
3811 'rule_data_json',
3821 'rule_data_json',
3812 JsonType(dialect_map=dict(mysql=UnicodeText(16384))))
3822 JsonType(dialect_map=dict(mysql=UnicodeText(16384))))
3813
3823
3814 def rule_user_group_data(self):
3824 def rule_user_group_data(self):
3815 """
3825 """
3816 Returns the voting user group rule data for this reviewer
3826 Returns the voting user group rule data for this reviewer
3817 """
3827 """
3818
3828
3819 if self.rule_data and 'vote_rule' in self.rule_data:
3829 if self.rule_data and 'vote_rule' in self.rule_data:
3820 user_group_data = {}
3830 user_group_data = {}
3821 if 'rule_user_group_entry_id' in self.rule_data:
3831 if 'rule_user_group_entry_id' in self.rule_data:
3822 # means a group with voting rules !
3832 # means a group with voting rules !
3823 user_group_data['id'] = self.rule_data['rule_user_group_entry_id']
3833 user_group_data['id'] = self.rule_data['rule_user_group_entry_id']
3824 user_group_data['name'] = self.rule_data['rule_name']
3834 user_group_data['name'] = self.rule_data['rule_name']
3825 user_group_data['vote_rule'] = self.rule_data['vote_rule']
3835 user_group_data['vote_rule'] = self.rule_data['vote_rule']
3826
3836
3827 return user_group_data
3837 return user_group_data
3828
3838
3829 def __unicode__(self):
3839 def __unicode__(self):
3830 return u"<%s('id:%s')>" % (self.__class__.__name__,
3840 return u"<%s('id:%s')>" % (self.__class__.__name__,
3831 self.pull_requests_reviewers_id)
3841 self.pull_requests_reviewers_id)
3832
3842
3833
3843
3834 class Notification(Base, BaseModel):
3844 class Notification(Base, BaseModel):
3835 __tablename__ = 'notifications'
3845 __tablename__ = 'notifications'
3836 __table_args__ = (
3846 __table_args__ = (
3837 Index('notification_type_idx', 'type'),
3847 Index('notification_type_idx', 'type'),
3838 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3848 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3839 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3849 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3840 )
3850 )
3841
3851
3842 TYPE_CHANGESET_COMMENT = u'cs_comment'
3852 TYPE_CHANGESET_COMMENT = u'cs_comment'
3843 TYPE_MESSAGE = u'message'
3853 TYPE_MESSAGE = u'message'
3844 TYPE_MENTION = u'mention'
3854 TYPE_MENTION = u'mention'
3845 TYPE_REGISTRATION = u'registration'
3855 TYPE_REGISTRATION = u'registration'
3846 TYPE_PULL_REQUEST = u'pull_request'
3856 TYPE_PULL_REQUEST = u'pull_request'
3847 TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment'
3857 TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment'
3848
3858
3849 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
3859 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
3850 subject = Column('subject', Unicode(512), nullable=True)
3860 subject = Column('subject', Unicode(512), nullable=True)
3851 body = Column('body', UnicodeText().with_variant(UnicodeText(50000), 'mysql'), nullable=True)
3861 body = Column('body', UnicodeText().with_variant(UnicodeText(50000), 'mysql'), nullable=True)
3852 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
3862 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
3853 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3863 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3854 type_ = Column('type', Unicode(255))
3864 type_ = Column('type', Unicode(255))
3855
3865
3856 created_by_user = relationship('User')
3866 created_by_user = relationship('User')
3857 notifications_to_users = relationship('UserNotification', lazy='joined',
3867 notifications_to_users = relationship('UserNotification', lazy='joined',
3858 cascade="all, delete, delete-orphan")
3868 cascade="all, delete, delete-orphan")
3859
3869
3860 @property
3870 @property
3861 def recipients(self):
3871 def recipients(self):
3862 return [x.user for x in UserNotification.query()\
3872 return [x.user for x in UserNotification.query()\
3863 .filter(UserNotification.notification == self)\
3873 .filter(UserNotification.notification == self)\
3864 .order_by(UserNotification.user_id.asc()).all()]
3874 .order_by(UserNotification.user_id.asc()).all()]
3865
3875
3866 @classmethod
3876 @classmethod
3867 def create(cls, created_by, subject, body, recipients, type_=None):
3877 def create(cls, created_by, subject, body, recipients, type_=None):
3868 if type_ is None:
3878 if type_ is None:
3869 type_ = Notification.TYPE_MESSAGE
3879 type_ = Notification.TYPE_MESSAGE
3870
3880
3871 notification = cls()
3881 notification = cls()
3872 notification.created_by_user = created_by
3882 notification.created_by_user = created_by
3873 notification.subject = subject
3883 notification.subject = subject
3874 notification.body = body
3884 notification.body = body
3875 notification.type_ = type_
3885 notification.type_ = type_
3876 notification.created_on = datetime.datetime.now()
3886 notification.created_on = datetime.datetime.now()
3877
3887
3878 for u in recipients:
3888 for u in recipients:
3879 assoc = UserNotification()
3889 assoc = UserNotification()
3880 assoc.notification = notification
3890 assoc.notification = notification
3881
3891
3882 # if created_by is inside recipients mark his notification
3892 # if created_by is inside recipients mark his notification
3883 # as read
3893 # as read
3884 if u.user_id == created_by.user_id:
3894 if u.user_id == created_by.user_id:
3885 assoc.read = True
3895 assoc.read = True
3886
3896
3887 u.notifications.append(assoc)
3897 u.notifications.append(assoc)
3888 Session().add(notification)
3898 Session().add(notification)
3889
3899
3890 return notification
3900 return notification
3891
3901
3892
3902
3893 class UserNotification(Base, BaseModel):
3903 class UserNotification(Base, BaseModel):
3894 __tablename__ = 'user_to_notification'
3904 __tablename__ = 'user_to_notification'
3895 __table_args__ = (
3905 __table_args__ = (
3896 UniqueConstraint('user_id', 'notification_id'),
3906 UniqueConstraint('user_id', 'notification_id'),
3897 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3907 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3898 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3908 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3899 )
3909 )
3900 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
3910 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
3901 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
3911 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
3902 read = Column('read', Boolean, default=False)
3912 read = Column('read', Boolean, default=False)
3903 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
3913 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
3904
3914
3905 user = relationship('User', lazy="joined")
3915 user = relationship('User', lazy="joined")
3906 notification = relationship('Notification', lazy="joined",
3916 notification = relationship('Notification', lazy="joined",
3907 order_by=lambda: Notification.created_on.desc(),)
3917 order_by=lambda: Notification.created_on.desc(),)
3908
3918
3909 def mark_as_read(self):
3919 def mark_as_read(self):
3910 self.read = True
3920 self.read = True
3911 Session().add(self)
3921 Session().add(self)
3912
3922
3913
3923
3914 class Gist(Base, BaseModel):
3924 class Gist(Base, BaseModel):
3915 __tablename__ = 'gists'
3925 __tablename__ = 'gists'
3916 __table_args__ = (
3926 __table_args__ = (
3917 Index('g_gist_access_id_idx', 'gist_access_id'),
3927 Index('g_gist_access_id_idx', 'gist_access_id'),
3918 Index('g_created_on_idx', 'created_on'),
3928 Index('g_created_on_idx', 'created_on'),
3919 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3929 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3920 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3930 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3921 )
3931 )
3922 GIST_PUBLIC = u'public'
3932 GIST_PUBLIC = u'public'
3923 GIST_PRIVATE = u'private'
3933 GIST_PRIVATE = u'private'
3924 DEFAULT_FILENAME = u'gistfile1.txt'
3934 DEFAULT_FILENAME = u'gistfile1.txt'
3925
3935
3926 ACL_LEVEL_PUBLIC = u'acl_public'
3936 ACL_LEVEL_PUBLIC = u'acl_public'
3927 ACL_LEVEL_PRIVATE = u'acl_private'
3937 ACL_LEVEL_PRIVATE = u'acl_private'
3928
3938
3929 gist_id = Column('gist_id', Integer(), primary_key=True)
3939 gist_id = Column('gist_id', Integer(), primary_key=True)
3930 gist_access_id = Column('gist_access_id', Unicode(250))
3940 gist_access_id = Column('gist_access_id', Unicode(250))
3931 gist_description = Column('gist_description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
3941 gist_description = Column('gist_description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
3932 gist_owner = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True)
3942 gist_owner = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True)
3933 gist_expires = Column('gist_expires', Float(53), nullable=False)
3943 gist_expires = Column('gist_expires', Float(53), nullable=False)
3934 gist_type = Column('gist_type', Unicode(128), nullable=False)
3944 gist_type = Column('gist_type', Unicode(128), nullable=False)
3935 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3945 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3936 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3946 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3937 acl_level = Column('acl_level', Unicode(128), nullable=True)
3947 acl_level = Column('acl_level', Unicode(128), nullable=True)
3938
3948
3939 owner = relationship('User')
3949 owner = relationship('User')
3940
3950
3941 def __repr__(self):
3951 def __repr__(self):
3942 return '<Gist:[%s]%s>' % (self.gist_type, self.gist_access_id)
3952 return '<Gist:[%s]%s>' % (self.gist_type, self.gist_access_id)
3943
3953
3944 @hybrid_property
3954 @hybrid_property
3945 def description_safe(self):
3955 def description_safe(self):
3946 from rhodecode.lib import helpers as h
3956 from rhodecode.lib import helpers as h
3947 return h.escape(self.gist_description)
3957 return h.escape(self.gist_description)
3948
3958
3949 @classmethod
3959 @classmethod
3950 def get_or_404(cls, id_):
3960 def get_or_404(cls, id_):
3951 from pyramid.httpexceptions import HTTPNotFound
3961 from pyramid.httpexceptions import HTTPNotFound
3952
3962
3953 res = cls.query().filter(cls.gist_access_id == id_).scalar()
3963 res = cls.query().filter(cls.gist_access_id == id_).scalar()
3954 if not res:
3964 if not res:
3955 raise HTTPNotFound()
3965 raise HTTPNotFound()
3956 return res
3966 return res
3957
3967
3958 @classmethod
3968 @classmethod
3959 def get_by_access_id(cls, gist_access_id):
3969 def get_by_access_id(cls, gist_access_id):
3960 return cls.query().filter(cls.gist_access_id == gist_access_id).scalar()
3970 return cls.query().filter(cls.gist_access_id == gist_access_id).scalar()
3961
3971
3962 def gist_url(self):
3972 def gist_url(self):
3963 from rhodecode.model.gist import GistModel
3973 from rhodecode.model.gist import GistModel
3964 return GistModel().get_url(self)
3974 return GistModel().get_url(self)
3965
3975
3966 @classmethod
3976 @classmethod
3967 def base_path(cls):
3977 def base_path(cls):
3968 """
3978 """
3969 Returns base path when all gists are stored
3979 Returns base path when all gists are stored
3970
3980
3971 :param cls:
3981 :param cls:
3972 """
3982 """
3973 from rhodecode.model.gist import GIST_STORE_LOC
3983 from rhodecode.model.gist import GIST_STORE_LOC
3974 q = Session().query(RhodeCodeUi)\
3984 q = Session().query(RhodeCodeUi)\
3975 .filter(RhodeCodeUi.ui_key == URL_SEP)
3985 .filter(RhodeCodeUi.ui_key == URL_SEP)
3976 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
3986 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
3977 return os.path.join(q.one().ui_value, GIST_STORE_LOC)
3987 return os.path.join(q.one().ui_value, GIST_STORE_LOC)
3978
3988
3979 def get_api_data(self):
3989 def get_api_data(self):
3980 """
3990 """
3981 Common function for generating gist related data for API
3991 Common function for generating gist related data for API
3982 """
3992 """
3983 gist = self
3993 gist = self
3984 data = {
3994 data = {
3985 'gist_id': gist.gist_id,
3995 'gist_id': gist.gist_id,
3986 'type': gist.gist_type,
3996 'type': gist.gist_type,
3987 'access_id': gist.gist_access_id,
3997 'access_id': gist.gist_access_id,
3988 'description': gist.gist_description,
3998 'description': gist.gist_description,
3989 'url': gist.gist_url(),
3999 'url': gist.gist_url(),
3990 'expires': gist.gist_expires,
4000 'expires': gist.gist_expires,
3991 'created_on': gist.created_on,
4001 'created_on': gist.created_on,
3992 'modified_at': gist.modified_at,
4002 'modified_at': gist.modified_at,
3993 'content': None,
4003 'content': None,
3994 'acl_level': gist.acl_level,
4004 'acl_level': gist.acl_level,
3995 }
4005 }
3996 return data
4006 return data
3997
4007
3998 def __json__(self):
4008 def __json__(self):
3999 data = dict(
4009 data = dict(
4000 )
4010 )
4001 data.update(self.get_api_data())
4011 data.update(self.get_api_data())
4002 return data
4012 return data
4003 # SCM functions
4013 # SCM functions
4004
4014
4005 def scm_instance(self, **kwargs):
4015 def scm_instance(self, **kwargs):
4006 full_repo_path = os.path.join(self.base_path(), self.gist_access_id)
4016 full_repo_path = os.path.join(self.base_path(), self.gist_access_id)
4007 return get_vcs_instance(
4017 return get_vcs_instance(
4008 repo_path=safe_str(full_repo_path), create=False)
4018 repo_path=safe_str(full_repo_path), create=False)
4009
4019
4010
4020
4011 class ExternalIdentity(Base, BaseModel):
4021 class ExternalIdentity(Base, BaseModel):
4012 __tablename__ = 'external_identities'
4022 __tablename__ = 'external_identities'
4013 __table_args__ = (
4023 __table_args__ = (
4014 Index('local_user_id_idx', 'local_user_id'),
4024 Index('local_user_id_idx', 'local_user_id'),
4015 Index('external_id_idx', 'external_id'),
4025 Index('external_id_idx', 'external_id'),
4016 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4026 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4017 'mysql_charset': 'utf8'})
4027 'mysql_charset': 'utf8'})
4018
4028
4019 external_id = Column('external_id', Unicode(255), default=u'',
4029 external_id = Column('external_id', Unicode(255), default=u'',
4020 primary_key=True)
4030 primary_key=True)
4021 external_username = Column('external_username', Unicode(1024), default=u'')
4031 external_username = Column('external_username', Unicode(1024), default=u'')
4022 local_user_id = Column('local_user_id', Integer(),
4032 local_user_id = Column('local_user_id', Integer(),
4023 ForeignKey('users.user_id'), primary_key=True)
4033 ForeignKey('users.user_id'), primary_key=True)
4024 provider_name = Column('provider_name', Unicode(255), default=u'',
4034 provider_name = Column('provider_name', Unicode(255), default=u'',
4025 primary_key=True)
4035 primary_key=True)
4026 access_token = Column('access_token', String(1024), default=u'')
4036 access_token = Column('access_token', String(1024), default=u'')
4027 alt_token = Column('alt_token', String(1024), default=u'')
4037 alt_token = Column('alt_token', String(1024), default=u'')
4028 token_secret = Column('token_secret', String(1024), default=u'')
4038 token_secret = Column('token_secret', String(1024), default=u'')
4029
4039
4030 @classmethod
4040 @classmethod
4031 def by_external_id_and_provider(cls, external_id, provider_name,
4041 def by_external_id_and_provider(cls, external_id, provider_name,
4032 local_user_id=None):
4042 local_user_id=None):
4033 """
4043 """
4034 Returns ExternalIdentity instance based on search params
4044 Returns ExternalIdentity instance based on search params
4035
4045
4036 :param external_id:
4046 :param external_id:
4037 :param provider_name:
4047 :param provider_name:
4038 :return: ExternalIdentity
4048 :return: ExternalIdentity
4039 """
4049 """
4040 query = cls.query()
4050 query = cls.query()
4041 query = query.filter(cls.external_id == external_id)
4051 query = query.filter(cls.external_id == external_id)
4042 query = query.filter(cls.provider_name == provider_name)
4052 query = query.filter(cls.provider_name == provider_name)
4043 if local_user_id:
4053 if local_user_id:
4044 query = query.filter(cls.local_user_id == local_user_id)
4054 query = query.filter(cls.local_user_id == local_user_id)
4045 return query.first()
4055 return query.first()
4046
4056
4047 @classmethod
4057 @classmethod
4048 def user_by_external_id_and_provider(cls, external_id, provider_name):
4058 def user_by_external_id_and_provider(cls, external_id, provider_name):
4049 """
4059 """
4050 Returns User instance based on search params
4060 Returns User instance based on search params
4051
4061
4052 :param external_id:
4062 :param external_id:
4053 :param provider_name:
4063 :param provider_name:
4054 :return: User
4064 :return: User
4055 """
4065 """
4056 query = User.query()
4066 query = User.query()
4057 query = query.filter(cls.external_id == external_id)
4067 query = query.filter(cls.external_id == external_id)
4058 query = query.filter(cls.provider_name == provider_name)
4068 query = query.filter(cls.provider_name == provider_name)
4059 query = query.filter(User.user_id == cls.local_user_id)
4069 query = query.filter(User.user_id == cls.local_user_id)
4060 return query.first()
4070 return query.first()
4061
4071
4062 @classmethod
4072 @classmethod
4063 def by_local_user_id(cls, local_user_id):
4073 def by_local_user_id(cls, local_user_id):
4064 """
4074 """
4065 Returns all tokens for user
4075 Returns all tokens for user
4066
4076
4067 :param local_user_id:
4077 :param local_user_id:
4068 :return: ExternalIdentity
4078 :return: ExternalIdentity
4069 """
4079 """
4070 query = cls.query()
4080 query = cls.query()
4071 query = query.filter(cls.local_user_id == local_user_id)
4081 query = query.filter(cls.local_user_id == local_user_id)
4072 return query
4082 return query
4073
4083
4074
4084
4075 class Integration(Base, BaseModel):
4085 class Integration(Base, BaseModel):
4076 __tablename__ = 'integrations'
4086 __tablename__ = 'integrations'
4077 __table_args__ = (
4087 __table_args__ = (
4078 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4088 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4079 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
4089 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
4080 )
4090 )
4081
4091
4082 integration_id = Column('integration_id', Integer(), primary_key=True)
4092 integration_id = Column('integration_id', Integer(), primary_key=True)
4083 integration_type = Column('integration_type', String(255))
4093 integration_type = Column('integration_type', String(255))
4084 enabled = Column('enabled', Boolean(), nullable=False)
4094 enabled = Column('enabled', Boolean(), nullable=False)
4085 name = Column('name', String(255), nullable=False)
4095 name = Column('name', String(255), nullable=False)
4086 child_repos_only = Column('child_repos_only', Boolean(), nullable=False,
4096 child_repos_only = Column('child_repos_only', Boolean(), nullable=False,
4087 default=False)
4097 default=False)
4088
4098
4089 settings = Column(
4099 settings = Column(
4090 'settings_json', MutationObj.as_mutable(
4100 'settings_json', MutationObj.as_mutable(
4091 JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
4101 JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
4092 repo_id = Column(
4102 repo_id = Column(
4093 'repo_id', Integer(), ForeignKey('repositories.repo_id'),
4103 'repo_id', Integer(), ForeignKey('repositories.repo_id'),
4094 nullable=True, unique=None, default=None)
4104 nullable=True, unique=None, default=None)
4095 repo = relationship('Repository', lazy='joined')
4105 repo = relationship('Repository', lazy='joined')
4096
4106
4097 repo_group_id = Column(
4107 repo_group_id = Column(
4098 'repo_group_id', Integer(), ForeignKey('groups.group_id'),
4108 'repo_group_id', Integer(), ForeignKey('groups.group_id'),
4099 nullable=True, unique=None, default=None)
4109 nullable=True, unique=None, default=None)
4100 repo_group = relationship('RepoGroup', lazy='joined')
4110 repo_group = relationship('RepoGroup', lazy='joined')
4101
4111
4102 @property
4112 @property
4103 def scope(self):
4113 def scope(self):
4104 if self.repo:
4114 if self.repo:
4105 return repr(self.repo)
4115 return repr(self.repo)
4106 if self.repo_group:
4116 if self.repo_group:
4107 if self.child_repos_only:
4117 if self.child_repos_only:
4108 return repr(self.repo_group) + ' (child repos only)'
4118 return repr(self.repo_group) + ' (child repos only)'
4109 else:
4119 else:
4110 return repr(self.repo_group) + ' (recursive)'
4120 return repr(self.repo_group) + ' (recursive)'
4111 if self.child_repos_only:
4121 if self.child_repos_only:
4112 return 'root_repos'
4122 return 'root_repos'
4113 return 'global'
4123 return 'global'
4114
4124
4115 def __repr__(self):
4125 def __repr__(self):
4116 return '<Integration(%r, %r)>' % (self.integration_type, self.scope)
4126 return '<Integration(%r, %r)>' % (self.integration_type, self.scope)
4117
4127
4118
4128
4119 class RepoReviewRuleUser(Base, BaseModel):
4129 class RepoReviewRuleUser(Base, BaseModel):
4120 __tablename__ = 'repo_review_rules_users'
4130 __tablename__ = 'repo_review_rules_users'
4121 __table_args__ = (
4131 __table_args__ = (
4122 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4132 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4123 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
4133 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
4124 )
4134 )
4125
4135
4126 repo_review_rule_user_id = Column('repo_review_rule_user_id', Integer(), primary_key=True)
4136 repo_review_rule_user_id = Column('repo_review_rule_user_id', Integer(), primary_key=True)
4127 repo_review_rule_id = Column("repo_review_rule_id", Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
4137 repo_review_rule_id = Column("repo_review_rule_id", Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
4128 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False)
4138 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False)
4129 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
4139 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
4130 user = relationship('User')
4140 user = relationship('User')
4131
4141
4132 def rule_data(self):
4142 def rule_data(self):
4133 return {
4143 return {
4134 'mandatory': self.mandatory
4144 'mandatory': self.mandatory
4135 }
4145 }
4136
4146
4137
4147
4138 class RepoReviewRuleUserGroup(Base, BaseModel):
4148 class RepoReviewRuleUserGroup(Base, BaseModel):
4139 __tablename__ = 'repo_review_rules_users_groups'
4149 __tablename__ = 'repo_review_rules_users_groups'
4140 __table_args__ = (
4150 __table_args__ = (
4141 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4151 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4142 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
4152 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
4143 )
4153 )
4144 VOTE_RULE_ALL = -1
4154 VOTE_RULE_ALL = -1
4145
4155
4146 repo_review_rule_users_group_id = Column('repo_review_rule_users_group_id', Integer(), primary_key=True)
4156 repo_review_rule_users_group_id = Column('repo_review_rule_users_group_id', Integer(), primary_key=True)
4147 repo_review_rule_id = Column("repo_review_rule_id", Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
4157 repo_review_rule_id = Column("repo_review_rule_id", Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
4148 users_group_id = Column("users_group_id", Integer(),ForeignKey('users_groups.users_group_id'), nullable=False)
4158 users_group_id = Column("users_group_id", Integer(),ForeignKey('users_groups.users_group_id'), nullable=False)
4149 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
4159 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
4150 vote_rule = Column("vote_rule", Integer(), nullable=True, default=VOTE_RULE_ALL)
4160 vote_rule = Column("vote_rule", Integer(), nullable=True, default=VOTE_RULE_ALL)
4151 users_group = relationship('UserGroup')
4161 users_group = relationship('UserGroup')
4152
4162
4153 def rule_data(self):
4163 def rule_data(self):
4154 return {
4164 return {
4155 'mandatory': self.mandatory,
4165 'mandatory': self.mandatory,
4156 'vote_rule': self.vote_rule
4166 'vote_rule': self.vote_rule
4157 }
4167 }
4158
4168
4159 @property
4169 @property
4160 def vote_rule_label(self):
4170 def vote_rule_label(self):
4161 if not self.vote_rule or self.vote_rule == self.VOTE_RULE_ALL:
4171 if not self.vote_rule or self.vote_rule == self.VOTE_RULE_ALL:
4162 return 'all must vote'
4172 return 'all must vote'
4163 else:
4173 else:
4164 return 'min. vote {}'.format(self.vote_rule)
4174 return 'min. vote {}'.format(self.vote_rule)
4165
4175
4166
4176
4167 class RepoReviewRule(Base, BaseModel):
4177 class RepoReviewRule(Base, BaseModel):
4168 __tablename__ = 'repo_review_rules'
4178 __tablename__ = 'repo_review_rules'
4169 __table_args__ = (
4179 __table_args__ = (
4170 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4180 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4171 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
4181 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
4172 )
4182 )
4173
4183
4174 repo_review_rule_id = Column(
4184 repo_review_rule_id = Column(
4175 'repo_review_rule_id', Integer(), primary_key=True)
4185 'repo_review_rule_id', Integer(), primary_key=True)
4176 repo_id = Column(
4186 repo_id = Column(
4177 "repo_id", Integer(), ForeignKey('repositories.repo_id'))
4187 "repo_id", Integer(), ForeignKey('repositories.repo_id'))
4178 repo = relationship('Repository', backref='review_rules')
4188 repo = relationship('Repository', backref='review_rules')
4179
4189
4180 review_rule_name = Column('review_rule_name', String(255))
4190 review_rule_name = Column('review_rule_name', String(255))
4181 _branch_pattern = Column("branch_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default=u'*') # glob
4191 _branch_pattern = Column("branch_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default=u'*') # glob
4182 _target_branch_pattern = Column("target_branch_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default=u'*') # glob
4192 _target_branch_pattern = Column("target_branch_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default=u'*') # glob
4183 _file_pattern = Column("file_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default=u'*') # glob
4193 _file_pattern = Column("file_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default=u'*') # glob
4184
4194
4185 use_authors_for_review = Column("use_authors_for_review", Boolean(), nullable=False, default=False)
4195 use_authors_for_review = Column("use_authors_for_review", Boolean(), nullable=False, default=False)
4186 forbid_author_to_review = Column("forbid_author_to_review", Boolean(), nullable=False, default=False)
4196 forbid_author_to_review = Column("forbid_author_to_review", Boolean(), nullable=False, default=False)
4187 forbid_commit_author_to_review = Column("forbid_commit_author_to_review", Boolean(), nullable=False, default=False)
4197 forbid_commit_author_to_review = Column("forbid_commit_author_to_review", Boolean(), nullable=False, default=False)
4188 forbid_adding_reviewers = Column("forbid_adding_reviewers", Boolean(), nullable=False, default=False)
4198 forbid_adding_reviewers = Column("forbid_adding_reviewers", Boolean(), nullable=False, default=False)
4189
4199
4190 rule_users = relationship('RepoReviewRuleUser')
4200 rule_users = relationship('RepoReviewRuleUser')
4191 rule_user_groups = relationship('RepoReviewRuleUserGroup')
4201 rule_user_groups = relationship('RepoReviewRuleUserGroup')
4192
4202
4193 def _validate_glob(self, value):
4203 def _validate_glob(self, value):
4194 re.compile('^' + glob2re(value) + '$')
4204 re.compile('^' + glob2re(value) + '$')
4195
4205
4196 @hybrid_property
4206 @hybrid_property
4197 def source_branch_pattern(self):
4207 def source_branch_pattern(self):
4198 return self._branch_pattern or '*'
4208 return self._branch_pattern or '*'
4199
4209
4200 @source_branch_pattern.setter
4210 @source_branch_pattern.setter
4201 def source_branch_pattern(self, value):
4211 def source_branch_pattern(self, value):
4202 self._validate_glob(value)
4212 self._validate_glob(value)
4203 self._branch_pattern = value or '*'
4213 self._branch_pattern = value or '*'
4204
4214
4205 @hybrid_property
4215 @hybrid_property
4206 def target_branch_pattern(self):
4216 def target_branch_pattern(self):
4207 return self._target_branch_pattern or '*'
4217 return self._target_branch_pattern or '*'
4208
4218
4209 @target_branch_pattern.setter
4219 @target_branch_pattern.setter
4210 def target_branch_pattern(self, value):
4220 def target_branch_pattern(self, value):
4211 self._validate_glob(value)
4221 self._validate_glob(value)
4212 self._target_branch_pattern = value or '*'
4222 self._target_branch_pattern = value or '*'
4213
4223
4214 @hybrid_property
4224 @hybrid_property
4215 def file_pattern(self):
4225 def file_pattern(self):
4216 return self._file_pattern or '*'
4226 return self._file_pattern or '*'
4217
4227
4218 @file_pattern.setter
4228 @file_pattern.setter
4219 def file_pattern(self, value):
4229 def file_pattern(self, value):
4220 self._validate_glob(value)
4230 self._validate_glob(value)
4221 self._file_pattern = value or '*'
4231 self._file_pattern = value or '*'
4222
4232
4223 def matches(self, source_branch, target_branch, files_changed):
4233 def matches(self, source_branch, target_branch, files_changed):
4224 """
4234 """
4225 Check if this review rule matches a branch/files in a pull request
4235 Check if this review rule matches a branch/files in a pull request
4226
4236
4227 :param source_branch: source branch name for the commit
4237 :param source_branch: source branch name for the commit
4228 :param target_branch: target branch name for the commit
4238 :param target_branch: target branch name for the commit
4229 :param files_changed: list of file paths changed in the pull request
4239 :param files_changed: list of file paths changed in the pull request
4230 """
4240 """
4231
4241
4232 source_branch = source_branch or ''
4242 source_branch = source_branch or ''
4233 target_branch = target_branch or ''
4243 target_branch = target_branch or ''
4234 files_changed = files_changed or []
4244 files_changed = files_changed or []
4235
4245
4236 branch_matches = True
4246 branch_matches = True
4237 if source_branch or target_branch:
4247 if source_branch or target_branch:
4238 if self.source_branch_pattern == '*':
4248 if self.source_branch_pattern == '*':
4239 source_branch_match = True
4249 source_branch_match = True
4240 else:
4250 else:
4241 source_branch_regex = re.compile(
4251 source_branch_regex = re.compile(
4242 '^' + glob2re(self.source_branch_pattern) + '$')
4252 '^' + glob2re(self.source_branch_pattern) + '$')
4243 source_branch_match = bool(source_branch_regex.search(source_branch))
4253 source_branch_match = bool(source_branch_regex.search(source_branch))
4244 if self.target_branch_pattern == '*':
4254 if self.target_branch_pattern == '*':
4245 target_branch_match = True
4255 target_branch_match = True
4246 else:
4256 else:
4247 target_branch_regex = re.compile(
4257 target_branch_regex = re.compile(
4248 '^' + glob2re(self.target_branch_pattern) + '$')
4258 '^' + glob2re(self.target_branch_pattern) + '$')
4249 target_branch_match = bool(target_branch_regex.search(target_branch))
4259 target_branch_match = bool(target_branch_regex.search(target_branch))
4250
4260
4251 branch_matches = source_branch_match and target_branch_match
4261 branch_matches = source_branch_match and target_branch_match
4252
4262
4253 files_matches = True
4263 files_matches = True
4254 if self.file_pattern != '*':
4264 if self.file_pattern != '*':
4255 files_matches = False
4265 files_matches = False
4256 file_regex = re.compile(glob2re(self.file_pattern))
4266 file_regex = re.compile(glob2re(self.file_pattern))
4257 for filename in files_changed:
4267 for filename in files_changed:
4258 if file_regex.search(filename):
4268 if file_regex.search(filename):
4259 files_matches = True
4269 files_matches = True
4260 break
4270 break
4261
4271
4262 return branch_matches and files_matches
4272 return branch_matches and files_matches
4263
4273
4264 @property
4274 @property
4265 def review_users(self):
4275 def review_users(self):
4266 """ Returns the users which this rule applies to """
4276 """ Returns the users which this rule applies to """
4267
4277
4268 users = collections.OrderedDict()
4278 users = collections.OrderedDict()
4269
4279
4270 for rule_user in self.rule_users:
4280 for rule_user in self.rule_users:
4271 if rule_user.user.active:
4281 if rule_user.user.active:
4272 if rule_user.user not in users:
4282 if rule_user.user not in users:
4273 users[rule_user.user.username] = {
4283 users[rule_user.user.username] = {
4274 'user': rule_user.user,
4284 'user': rule_user.user,
4275 'source': 'user',
4285 'source': 'user',
4276 'source_data': {},
4286 'source_data': {},
4277 'data': rule_user.rule_data()
4287 'data': rule_user.rule_data()
4278 }
4288 }
4279
4289
4280 for rule_user_group in self.rule_user_groups:
4290 for rule_user_group in self.rule_user_groups:
4281 source_data = {
4291 source_data = {
4282 'user_group_id': rule_user_group.users_group.users_group_id,
4292 'user_group_id': rule_user_group.users_group.users_group_id,
4283 'name': rule_user_group.users_group.users_group_name,
4293 'name': rule_user_group.users_group.users_group_name,
4284 'members': len(rule_user_group.users_group.members)
4294 'members': len(rule_user_group.users_group.members)
4285 }
4295 }
4286 for member in rule_user_group.users_group.members:
4296 for member in rule_user_group.users_group.members:
4287 if member.user.active:
4297 if member.user.active:
4288 key = member.user.username
4298 key = member.user.username
4289 if key in users:
4299 if key in users:
4290 # skip this member as we have him already
4300 # skip this member as we have him already
4291 # this prevents from override the "first" matched
4301 # this prevents from override the "first" matched
4292 # users with duplicates in multiple groups
4302 # users with duplicates in multiple groups
4293 continue
4303 continue
4294
4304
4295 users[key] = {
4305 users[key] = {
4296 'user': member.user,
4306 'user': member.user,
4297 'source': 'user_group',
4307 'source': 'user_group',
4298 'source_data': source_data,
4308 'source_data': source_data,
4299 'data': rule_user_group.rule_data()
4309 'data': rule_user_group.rule_data()
4300 }
4310 }
4301
4311
4302 return users
4312 return users
4303
4313
4304 def user_group_vote_rule(self):
4314 def user_group_vote_rule(self):
4305 rules = []
4315 rules = []
4306 if self.rule_user_groups:
4316 if self.rule_user_groups:
4307 for user_group in self.rule_user_groups:
4317 for user_group in self.rule_user_groups:
4308 rules.append(user_group)
4318 rules.append(user_group)
4309 return rules
4319 return rules
4310
4320
4311 def __repr__(self):
4321 def __repr__(self):
4312 return '<RepoReviewerRule(id=%r, repo=%r)>' % (
4322 return '<RepoReviewerRule(id=%r, repo=%r)>' % (
4313 self.repo_review_rule_id, self.repo)
4323 self.repo_review_rule_id, self.repo)
4314
4324
4315
4325
4316 class ScheduleEntry(Base, BaseModel):
4326 class ScheduleEntry(Base, BaseModel):
4317 __tablename__ = 'schedule_entries'
4327 __tablename__ = 'schedule_entries'
4318 __table_args__ = (
4328 __table_args__ = (
4319 UniqueConstraint('schedule_name', name='s_schedule_name_idx'),
4329 UniqueConstraint('schedule_name', name='s_schedule_name_idx'),
4320 UniqueConstraint('task_uid', name='s_task_uid_idx'),
4330 UniqueConstraint('task_uid', name='s_task_uid_idx'),
4321 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4331 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4322 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
4332 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
4323 )
4333 )
4324 schedule_types = ['crontab', 'timedelta', 'integer']
4334 schedule_types = ['crontab', 'timedelta', 'integer']
4325 schedule_entry_id = Column('schedule_entry_id', Integer(), primary_key=True)
4335 schedule_entry_id = Column('schedule_entry_id', Integer(), primary_key=True)
4326
4336
4327 schedule_name = Column("schedule_name", String(255), nullable=False, unique=None, default=None)
4337 schedule_name = Column("schedule_name", String(255), nullable=False, unique=None, default=None)
4328 schedule_description = Column("schedule_description", String(10000), nullable=True, unique=None, default=None)
4338 schedule_description = Column("schedule_description", String(10000), nullable=True, unique=None, default=None)
4329 schedule_enabled = Column("schedule_enabled", Boolean(), nullable=False, unique=None, default=True)
4339 schedule_enabled = Column("schedule_enabled", Boolean(), nullable=False, unique=None, default=True)
4330
4340
4331 _schedule_type = Column("schedule_type", String(255), nullable=False, unique=None, default=None)
4341 _schedule_type = Column("schedule_type", String(255), nullable=False, unique=None, default=None)
4332 schedule_definition = Column('schedule_definition_json', MutationObj.as_mutable(JsonType(default=lambda: "", dialect_map=dict(mysql=LONGTEXT()))))
4342 schedule_definition = Column('schedule_definition_json', MutationObj.as_mutable(JsonType(default=lambda: "", dialect_map=dict(mysql=LONGTEXT()))))
4333
4343
4334 schedule_last_run = Column('schedule_last_run', DateTime(timezone=False), nullable=True, unique=None, default=None)
4344 schedule_last_run = Column('schedule_last_run', DateTime(timezone=False), nullable=True, unique=None, default=None)
4335 schedule_total_run_count = Column('schedule_total_run_count', Integer(), nullable=True, unique=None, default=0)
4345 schedule_total_run_count = Column('schedule_total_run_count', Integer(), nullable=True, unique=None, default=0)
4336
4346
4337 # task
4347 # task
4338 task_uid = Column("task_uid", String(255), nullable=False, unique=None, default=None)
4348 task_uid = Column("task_uid", String(255), nullable=False, unique=None, default=None)
4339 task_dot_notation = Column("task_dot_notation", String(4096), nullable=False, unique=None, default=None)
4349 task_dot_notation = Column("task_dot_notation", String(4096), nullable=False, unique=None, default=None)
4340 task_args = Column('task_args_json', MutationObj.as_mutable(JsonType(default=list, dialect_map=dict(mysql=LONGTEXT()))))
4350 task_args = Column('task_args_json', MutationObj.as_mutable(JsonType(default=list, dialect_map=dict(mysql=LONGTEXT()))))
4341 task_kwargs = Column('task_kwargs_json', MutationObj.as_mutable(JsonType(default=dict, dialect_map=dict(mysql=LONGTEXT()))))
4351 task_kwargs = Column('task_kwargs_json', MutationObj.as_mutable(JsonType(default=dict, dialect_map=dict(mysql=LONGTEXT()))))
4342
4352
4343 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4353 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4344 updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=None)
4354 updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=None)
4345
4355
4346 @hybrid_property
4356 @hybrid_property
4347 def schedule_type(self):
4357 def schedule_type(self):
4348 return self._schedule_type
4358 return self._schedule_type
4349
4359
4350 @schedule_type.setter
4360 @schedule_type.setter
4351 def schedule_type(self, val):
4361 def schedule_type(self, val):
4352 if val not in self.schedule_types:
4362 if val not in self.schedule_types:
4353 raise ValueError('Value must be on of `{}` and got `{}`'.format(
4363 raise ValueError('Value must be on of `{}` and got `{}`'.format(
4354 val, self.schedule_type))
4364 val, self.schedule_type))
4355
4365
4356 self._schedule_type = val
4366 self._schedule_type = val
4357
4367
4358 @classmethod
4368 @classmethod
4359 def get_uid(cls, obj):
4369 def get_uid(cls, obj):
4360 args = obj.task_args
4370 args = obj.task_args
4361 kwargs = obj.task_kwargs
4371 kwargs = obj.task_kwargs
4362 if isinstance(args, JsonRaw):
4372 if isinstance(args, JsonRaw):
4363 try:
4373 try:
4364 args = json.loads(args)
4374 args = json.loads(args)
4365 except ValueError:
4375 except ValueError:
4366 args = tuple()
4376 args = tuple()
4367
4377
4368 if isinstance(kwargs, JsonRaw):
4378 if isinstance(kwargs, JsonRaw):
4369 try:
4379 try:
4370 kwargs = json.loads(kwargs)
4380 kwargs = json.loads(kwargs)
4371 except ValueError:
4381 except ValueError:
4372 kwargs = dict()
4382 kwargs = dict()
4373
4383
4374 dot_notation = obj.task_dot_notation
4384 dot_notation = obj.task_dot_notation
4375 val = '.'.join(map(safe_str, [
4385 val = '.'.join(map(safe_str, [
4376 sorted(dot_notation), args, sorted(kwargs.items())]))
4386 sorted(dot_notation), args, sorted(kwargs.items())]))
4377 return hashlib.sha1(val).hexdigest()
4387 return hashlib.sha1(val).hexdigest()
4378
4388
4379 @classmethod
4389 @classmethod
4380 def get_by_schedule_name(cls, schedule_name):
4390 def get_by_schedule_name(cls, schedule_name):
4381 return cls.query().filter(cls.schedule_name == schedule_name).scalar()
4391 return cls.query().filter(cls.schedule_name == schedule_name).scalar()
4382
4392
4383 @classmethod
4393 @classmethod
4384 def get_by_schedule_id(cls, schedule_id):
4394 def get_by_schedule_id(cls, schedule_id):
4385 return cls.query().filter(cls.schedule_entry_id == schedule_id).scalar()
4395 return cls.query().filter(cls.schedule_entry_id == schedule_id).scalar()
4386
4396
4387 @property
4397 @property
4388 def task(self):
4398 def task(self):
4389 return self.task_dot_notation
4399 return self.task_dot_notation
4390
4400
4391 @property
4401 @property
4392 def schedule(self):
4402 def schedule(self):
4393 from rhodecode.lib.celerylib.utils import raw_2_schedule
4403 from rhodecode.lib.celerylib.utils import raw_2_schedule
4394 schedule = raw_2_schedule(self.schedule_definition, self.schedule_type)
4404 schedule = raw_2_schedule(self.schedule_definition, self.schedule_type)
4395 return schedule
4405 return schedule
4396
4406
4397 @property
4407 @property
4398 def args(self):
4408 def args(self):
4399 try:
4409 try:
4400 return list(self.task_args or [])
4410 return list(self.task_args or [])
4401 except ValueError:
4411 except ValueError:
4402 return list()
4412 return list()
4403
4413
4404 @property
4414 @property
4405 def kwargs(self):
4415 def kwargs(self):
4406 try:
4416 try:
4407 return dict(self.task_kwargs or {})
4417 return dict(self.task_kwargs or {})
4408 except ValueError:
4418 except ValueError:
4409 return dict()
4419 return dict()
4410
4420
4411 def _as_raw(self, val):
4421 def _as_raw(self, val):
4412 if hasattr(val, 'de_coerce'):
4422 if hasattr(val, 'de_coerce'):
4413 val = val.de_coerce()
4423 val = val.de_coerce()
4414 if val:
4424 if val:
4415 val = json.dumps(val)
4425 val = json.dumps(val)
4416
4426
4417 return val
4427 return val
4418
4428
4419 @property
4429 @property
4420 def schedule_definition_raw(self):
4430 def schedule_definition_raw(self):
4421 return self._as_raw(self.schedule_definition)
4431 return self._as_raw(self.schedule_definition)
4422
4432
4423 @property
4433 @property
4424 def args_raw(self):
4434 def args_raw(self):
4425 return self._as_raw(self.task_args)
4435 return self._as_raw(self.task_args)
4426
4436
4427 @property
4437 @property
4428 def kwargs_raw(self):
4438 def kwargs_raw(self):
4429 return self._as_raw(self.task_kwargs)
4439 return self._as_raw(self.task_kwargs)
4430
4440
4431 def __repr__(self):
4441 def __repr__(self):
4432 return '<DB:ScheduleEntry({}:{})>'.format(
4442 return '<DB:ScheduleEntry({}:{})>'.format(
4433 self.schedule_entry_id, self.schedule_name)
4443 self.schedule_entry_id, self.schedule_name)
4434
4444
4435
4445
4436 @event.listens_for(ScheduleEntry, 'before_update')
4446 @event.listens_for(ScheduleEntry, 'before_update')
4437 def update_task_uid(mapper, connection, target):
4447 def update_task_uid(mapper, connection, target):
4438 target.task_uid = ScheduleEntry.get_uid(target)
4448 target.task_uid = ScheduleEntry.get_uid(target)
4439
4449
4440
4450
4441 @event.listens_for(ScheduleEntry, 'before_insert')
4451 @event.listens_for(ScheduleEntry, 'before_insert')
4442 def set_task_uid(mapper, connection, target):
4452 def set_task_uid(mapper, connection, target):
4443 target.task_uid = ScheduleEntry.get_uid(target)
4453 target.task_uid = ScheduleEntry.get_uid(target)
4444
4454
4445
4455
4446 class DbMigrateVersion(Base, BaseModel):
4456 class DbMigrateVersion(Base, BaseModel):
4447 __tablename__ = 'db_migrate_version'
4457 __tablename__ = 'db_migrate_version'
4448 __table_args__ = (
4458 __table_args__ = (
4449 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4459 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4450 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
4460 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
4451 )
4461 )
4452 repository_id = Column('repository_id', String(250), primary_key=True)
4462 repository_id = Column('repository_id', String(250), primary_key=True)
4453 repository_path = Column('repository_path', Text)
4463 repository_path = Column('repository_path', Text)
4454 version = Column('version', Integer)
4464 version = Column('version', Integer)
4455
4465
4456
4466
4457 class DbSession(Base, BaseModel):
4467 class DbSession(Base, BaseModel):
4458 __tablename__ = 'db_session'
4468 __tablename__ = 'db_session'
4459 __table_args__ = (
4469 __table_args__ = (
4460 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4470 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4461 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
4471 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
4462 )
4472 )
4463
4473
4464 def __repr__(self):
4474 def __repr__(self):
4465 return '<DB:DbSession({})>'.format(self.id)
4475 return '<DB:DbSession({})>'.format(self.id)
4466
4476
4467 id = Column('id', Integer())
4477 id = Column('id', Integer())
4468 namespace = Column('namespace', String(255), primary_key=True)
4478 namespace = Column('namespace', String(255), primary_key=True)
4469 accessed = Column('accessed', DateTime, nullable=False)
4479 accessed = Column('accessed', DateTime, nullable=False)
4470 created = Column('created', DateTime, nullable=False)
4480 created = Column('created', DateTime, nullable=False)
4471 data = Column('data', PickleType, nullable=False)
4481 data = Column('data', PickleType, nullable=False)
@@ -1,673 +1,678 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2011-2018 RhodeCode GmbH
3 # Copyright (C) 2011-2018 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 traceback
22 import traceback
23
23
24 from rhodecode.lib.utils2 import safe_str, safe_unicode
24 from rhodecode.lib.utils2 import safe_str, safe_unicode
25 from rhodecode.lib.exceptions import (
25 from rhodecode.lib.exceptions import (
26 UserGroupAssignedException, RepoGroupAssignmentError)
26 UserGroupAssignedException, RepoGroupAssignmentError)
27 from rhodecode.lib.utils2 import (
27 from rhodecode.lib.utils2 import (
28 get_current_rhodecode_user, action_logger_generic)
28 get_current_rhodecode_user, action_logger_generic)
29 from rhodecode.model import BaseModel
29 from rhodecode.model import BaseModel
30 from rhodecode.model.scm import UserGroupList
30 from rhodecode.model.scm import UserGroupList
31 from rhodecode.model.db import (
31 from rhodecode.model.db import (
32 joinedload, true, func, User, UserGroupMember, UserGroup,
32 joinedload, true, func, User, UserGroupMember, UserGroup,
33 UserGroupRepoToPerm, Permission, UserGroupToPerm, UserUserGroupToPerm,
33 UserGroupRepoToPerm, Permission, UserGroupToPerm, UserUserGroupToPerm,
34 UserGroupUserGroupToPerm, UserGroupRepoGroupToPerm)
34 UserGroupUserGroupToPerm, UserGroupRepoGroupToPerm)
35
35
36
36
37 log = logging.getLogger(__name__)
37 log = logging.getLogger(__name__)
38
38
39
39
40 class UserGroupModel(BaseModel):
40 class UserGroupModel(BaseModel):
41
41
42 cls = UserGroup
42 cls = UserGroup
43
43
44 def _get_user_group(self, user_group):
44 def _get_user_group(self, user_group):
45 return self._get_instance(UserGroup, user_group,
45 return self._get_instance(UserGroup, user_group,
46 callback=UserGroup.get_by_group_name)
46 callback=UserGroup.get_by_group_name)
47
47
48 def _create_default_perms(self, user_group):
48 def _create_default_perms(self, user_group):
49 # create default permission
49 # create default permission
50 default_perm = 'usergroup.read'
50 default_perm = 'usergroup.read'
51 def_user = User.get_default_user()
51 def_user = User.get_default_user()
52 for p in def_user.user_perms:
52 for p in def_user.user_perms:
53 if p.permission.permission_name.startswith('usergroup.'):
53 if p.permission.permission_name.startswith('usergroup.'):
54 default_perm = p.permission.permission_name
54 default_perm = p.permission.permission_name
55 break
55 break
56
56
57 user_group_to_perm = UserUserGroupToPerm()
57 user_group_to_perm = UserUserGroupToPerm()
58 user_group_to_perm.permission = Permission.get_by_key(default_perm)
58 user_group_to_perm.permission = Permission.get_by_key(default_perm)
59
59
60 user_group_to_perm.user_group = user_group
60 user_group_to_perm.user_group = user_group
61 user_group_to_perm.user_id = def_user.user_id
61 user_group_to_perm.user_id = def_user.user_id
62 return user_group_to_perm
62 return user_group_to_perm
63
63
64 def update_permissions(
64 def update_permissions(
65 self, user_group, perm_additions=None, perm_updates=None,
65 self, user_group, perm_additions=None, perm_updates=None,
66 perm_deletions=None, check_perms=True, cur_user=None):
66 perm_deletions=None, check_perms=True, cur_user=None):
67
67
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 changes = {
78 changes = {
79 'added': [],
79 'added': [],
80 'updated': [],
80 'updated': [],
81 'deleted': []
81 'deleted': []
82 }
82 }
83 # update permissions
83 # update permissions
84 for member_id, perm, member_type in perm_updates:
84 for member_id, perm, member_type in perm_updates:
85 member_id = int(member_id)
85 member_id = int(member_id)
86 if member_type == 'user':
86 if member_type == 'user':
87 member_name = User.get(member_id).username
87 member_name = User.get(member_id).username
88 # this updates existing one
88 # this updates existing one
89 self.grant_user_permission(
89 self.grant_user_permission(
90 user_group=user_group, user=member_id, perm=perm
90 user_group=user_group, user=member_id, perm=perm
91 )
91 )
92 else:
92 else:
93 # check if we have permissions to alter this usergroup
93 # check if we have permissions to alter this usergroup
94 member_name = UserGroup.get(member_id).users_group_name
94 member_name = UserGroup.get(member_id).users_group_name
95 if not check_perms or HasUserGroupPermissionAny(
95 if not check_perms or HasUserGroupPermissionAny(
96 *req_perms)(member_name, user=cur_user):
96 *req_perms)(member_name, user=cur_user):
97 self.grant_user_group_permission(
97 self.grant_user_group_permission(
98 target_user_group=user_group, user_group=member_id, perm=perm)
98 target_user_group=user_group, user_group=member_id, perm=perm)
99
99
100 changes['updated'].append({'type': member_type, 'id': member_id,
100 changes['updated'].append({'type': member_type, 'id': member_id,
101 'name': member_name, 'new_perm': perm})
101 'name': member_name, 'new_perm': perm})
102
102
103 # set new permissions
103 # set new permissions
104 for member_id, perm, member_type in perm_additions:
104 for member_id, perm, member_type in perm_additions:
105 member_id = int(member_id)
105 member_id = int(member_id)
106 if member_type == 'user':
106 if member_type == 'user':
107 member_name = User.get(member_id).username
107 member_name = User.get(member_id).username
108 self.grant_user_permission(
108 self.grant_user_permission(
109 user_group=user_group, user=member_id, perm=perm)
109 user_group=user_group, user=member_id, perm=perm)
110 else:
110 else:
111 # check if we have permissions to alter this usergroup
111 # check if we have permissions to alter this usergroup
112 member_name = UserGroup.get(member_id).users_group_name
112 member_name = UserGroup.get(member_id).users_group_name
113 if not check_perms or HasUserGroupPermissionAny(
113 if not check_perms or HasUserGroupPermissionAny(
114 *req_perms)(member_name, user=cur_user):
114 *req_perms)(member_name, user=cur_user):
115 self.grant_user_group_permission(
115 self.grant_user_group_permission(
116 target_user_group=user_group, user_group=member_id, perm=perm)
116 target_user_group=user_group, user_group=member_id, perm=perm)
117
117
118 changes['added'].append({'type': member_type, 'id': member_id,
118 changes['added'].append({'type': member_type, 'id': member_id,
119 'name': member_name, 'new_perm': perm})
119 'name': member_name, 'new_perm': perm})
120
120
121 # delete permissions
121 # delete permissions
122 for member_id, perm, member_type in perm_deletions:
122 for member_id, perm, member_type in perm_deletions:
123 member_id = int(member_id)
123 member_id = int(member_id)
124 if member_type == 'user':
124 if member_type == 'user':
125 member_name = User.get(member_id).username
125 member_name = User.get(member_id).username
126 self.revoke_user_permission(user_group=user_group, user=member_id)
126 self.revoke_user_permission(user_group=user_group, user=member_id)
127 else:
127 else:
128 # check if we have permissions to alter this usergroup
128 # check if we have permissions to alter this usergroup
129 member_name = UserGroup.get(member_id).users_group_name
129 member_name = UserGroup.get(member_id).users_group_name
130 if not check_perms or HasUserGroupPermissionAny(
130 if not check_perms or HasUserGroupPermissionAny(
131 *req_perms)(member_name, user=cur_user):
131 *req_perms)(member_name, user=cur_user):
132 self.revoke_user_group_permission(
132 self.revoke_user_group_permission(
133 target_user_group=user_group, user_group=member_id)
133 target_user_group=user_group, user_group=member_id)
134
134
135 changes['deleted'].append({'type': member_type, 'id': member_id,
135 changes['deleted'].append({'type': member_type, 'id': member_id,
136 'name': member_name, 'new_perm': perm})
136 'name': member_name, 'new_perm': perm})
137 return changes
137 return changes
138
138
139 def get(self, user_group_id, cache=False):
139 def get(self, user_group_id, cache=False):
140 return UserGroup.get(user_group_id)
140 return UserGroup.get(user_group_id)
141
141
142 def get_group(self, user_group):
142 def get_group(self, user_group):
143 return self._get_user_group(user_group)
143 return self._get_user_group(user_group)
144
144
145 def get_by_name(self, name, cache=False, case_insensitive=False):
145 def get_by_name(self, name, cache=False, case_insensitive=False):
146 return UserGroup.get_by_group_name(name, cache, case_insensitive)
146 return UserGroup.get_by_group_name(name, cache, case_insensitive)
147
147
148 def create(self, name, description, owner, active=True, group_data=None):
148 def create(self, name, description, owner, active=True, group_data=None):
149 try:
149 try:
150 new_user_group = UserGroup()
150 new_user_group = UserGroup()
151 new_user_group.user = self._get_user(owner)
151 new_user_group.user = self._get_user(owner)
152 new_user_group.users_group_name = name
152 new_user_group.users_group_name = name
153 new_user_group.user_group_description = description
153 new_user_group.user_group_description = description
154 new_user_group.users_group_active = active
154 new_user_group.users_group_active = active
155 if group_data:
155 if group_data:
156 new_user_group.group_data = group_data
156 new_user_group.group_data = group_data
157 self.sa.add(new_user_group)
157 self.sa.add(new_user_group)
158 perm_obj = self._create_default_perms(new_user_group)
158 perm_obj = self._create_default_perms(new_user_group)
159 self.sa.add(perm_obj)
159 self.sa.add(perm_obj)
160
160
161 self.grant_user_permission(user_group=new_user_group,
161 self.grant_user_permission(user_group=new_user_group,
162 user=owner, perm='usergroup.admin')
162 user=owner, perm='usergroup.admin')
163
163
164 return new_user_group
164 return new_user_group
165 except Exception:
165 except Exception:
166 log.error(traceback.format_exc())
166 log.error(traceback.format_exc())
167 raise
167 raise
168
168
169 def _get_memberships_for_user_ids(self, user_group, user_id_list):
169 def _get_memberships_for_user_ids(self, user_group, user_id_list):
170 members = []
170 members = []
171 for user_id in user_id_list:
171 for user_id in user_id_list:
172 member = self._get_membership(user_group.users_group_id, user_id)
172 member = self._get_membership(user_group.users_group_id, user_id)
173 members.append(member)
173 members.append(member)
174 return members
174 return members
175
175
176 def _get_added_and_removed_user_ids(self, user_group, user_id_list):
176 def _get_added_and_removed_user_ids(self, user_group, user_id_list):
177 current_members = user_group.members or []
177 current_members = user_group.members or []
178 current_members_ids = [m.user.user_id for m in current_members]
178 current_members_ids = [m.user.user_id for m in current_members]
179
179
180 added_members = [
180 added_members = [
181 user_id for user_id in user_id_list
181 user_id for user_id in user_id_list
182 if user_id not in current_members_ids]
182 if user_id not in current_members_ids]
183 if user_id_list == []:
183 if user_id_list == []:
184 # all members were deleted
184 # all members were deleted
185 deleted_members = current_members_ids
185 deleted_members = current_members_ids
186 else:
186 else:
187 deleted_members = [
187 deleted_members = [
188 user_id for user_id in current_members_ids
188 user_id for user_id in current_members_ids
189 if user_id not in user_id_list]
189 if user_id not in user_id_list]
190
190
191 return added_members, deleted_members
191 return added_members, deleted_members
192
192
193 def _set_users_as_members(self, user_group, user_ids):
193 def _set_users_as_members(self, user_group, user_ids):
194 user_group.members = []
194 user_group.members = []
195 self.sa.flush()
195 self.sa.flush()
196 members = self._get_memberships_for_user_ids(
196 members = self._get_memberships_for_user_ids(
197 user_group, user_ids)
197 user_group, user_ids)
198 user_group.members = members
198 user_group.members = members
199 self.sa.add(user_group)
199 self.sa.add(user_group)
200
200
201 def _update_members_from_user_ids(self, user_group, user_ids):
201 def _update_members_from_user_ids(self, user_group, user_ids):
202 added, removed = self._get_added_and_removed_user_ids(
202 added, removed = self._get_added_and_removed_user_ids(
203 user_group, user_ids)
203 user_group, user_ids)
204 self._set_users_as_members(user_group, user_ids)
204 self._set_users_as_members(user_group, user_ids)
205 self._log_user_changes('added to', user_group, added)
205 self._log_user_changes('added to', user_group, added)
206 self._log_user_changes('removed from', user_group, removed)
206 self._log_user_changes('removed from', user_group, removed)
207 return added, removed
207 return added, removed
208
208
209 def _clean_members_data(self, members_data):
209 def _clean_members_data(self, members_data):
210 if not members_data:
210 if not members_data:
211 members_data = []
211 members_data = []
212
212
213 members = []
213 members = []
214 for user in members_data:
214 for user in members_data:
215 uid = int(user['member_user_id'])
215 uid = int(user['member_user_id'])
216 if uid not in members and user['type'] in ['new', 'existing']:
216 if uid not in members and user['type'] in ['new', 'existing']:
217 members.append(uid)
217 members.append(uid)
218 return members
218 return members
219
219
220 def update(self, user_group, form_data):
220 def update(self, user_group, form_data, group_data=None):
221 user_group = self._get_user_group(user_group)
221 user_group = self._get_user_group(user_group)
222 if 'users_group_name' in form_data:
222 if 'users_group_name' in form_data:
223 user_group.users_group_name = form_data['users_group_name']
223 user_group.users_group_name = form_data['users_group_name']
224 if 'users_group_active' in form_data:
224 if 'users_group_active' in form_data:
225 user_group.users_group_active = form_data['users_group_active']
225 user_group.users_group_active = form_data['users_group_active']
226 if 'user_group_description' in form_data:
226 if 'user_group_description' in form_data:
227 user_group.user_group_description = form_data[
227 user_group.user_group_description = form_data[
228 'user_group_description']
228 'user_group_description']
229
229
230 # handle owner change
230 # handle owner change
231 if 'user' in form_data:
231 if 'user' in form_data:
232 owner = form_data['user']
232 owner = form_data['user']
233 if isinstance(owner, basestring):
233 if isinstance(owner, basestring):
234 owner = User.get_by_username(form_data['user'])
234 owner = User.get_by_username(form_data['user'])
235
235
236 if not isinstance(owner, User):
236 if not isinstance(owner, User):
237 raise ValueError(
237 raise ValueError(
238 'invalid owner for user group: %s' % form_data['user'])
238 'invalid owner for user group: %s' % form_data['user'])
239
239
240 user_group.user = owner
240 user_group.user = owner
241
241
242 added_user_ids = []
242 added_user_ids = []
243 removed_user_ids = []
243 removed_user_ids = []
244 if 'users_group_members' in form_data:
244 if 'users_group_members' in form_data:
245 members_id_list = self._clean_members_data(
245 members_id_list = self._clean_members_data(
246 form_data['users_group_members'])
246 form_data['users_group_members'])
247 added_user_ids, removed_user_ids = \
247 added_user_ids, removed_user_ids = \
248 self._update_members_from_user_ids(user_group, members_id_list)
248 self._update_members_from_user_ids(user_group, members_id_list)
249
249
250 if group_data:
251 new_group_data = {}
252 new_group_data.update(group_data)
253 user_group.group_data = new_group_data
254
250 self.sa.add(user_group)
255 self.sa.add(user_group)
251 return user_group, added_user_ids, removed_user_ids
256 return user_group, added_user_ids, removed_user_ids
252
257
253 def delete(self, user_group, force=False):
258 def delete(self, user_group, force=False):
254 """
259 """
255 Deletes repository group, unless force flag is used
260 Deletes repository group, unless force flag is used
256 raises exception if there are members in that group, else deletes
261 raises exception if there are members in that group, else deletes
257 group and users
262 group and users
258
263
259 :param user_group:
264 :param user_group:
260 :param force:
265 :param force:
261 """
266 """
262 user_group = self._get_user_group(user_group)
267 user_group = self._get_user_group(user_group)
263 if not user_group:
268 if not user_group:
264 return
269 return
265
270
266 try:
271 try:
267 # check if this group is not assigned to repo
272 # check if this group is not assigned to repo
268 assigned_to_repo = [x.repository for x in UserGroupRepoToPerm.query()\
273 assigned_to_repo = [x.repository for x in UserGroupRepoToPerm.query()\
269 .filter(UserGroupRepoToPerm.users_group == user_group).all()]
274 .filter(UserGroupRepoToPerm.users_group == user_group).all()]
270 # check if this group is not assigned to repo
275 # check if this group is not assigned to repo
271 assigned_to_repo_group = [x.group for x in UserGroupRepoGroupToPerm.query()\
276 assigned_to_repo_group = [x.group for x in UserGroupRepoGroupToPerm.query()\
272 .filter(UserGroupRepoGroupToPerm.users_group == user_group).all()]
277 .filter(UserGroupRepoGroupToPerm.users_group == user_group).all()]
273
278
274 if (assigned_to_repo or assigned_to_repo_group) and not force:
279 if (assigned_to_repo or assigned_to_repo_group) and not force:
275 assigned = ','.join(map(safe_str,
280 assigned = ','.join(map(safe_str,
276 assigned_to_repo+assigned_to_repo_group))
281 assigned_to_repo+assigned_to_repo_group))
277
282
278 raise UserGroupAssignedException(
283 raise UserGroupAssignedException(
279 'UserGroup assigned to %s' % (assigned,))
284 'UserGroup assigned to %s' % (assigned,))
280 self.sa.delete(user_group)
285 self.sa.delete(user_group)
281 except Exception:
286 except Exception:
282 log.error(traceback.format_exc())
287 log.error(traceback.format_exc())
283 raise
288 raise
284
289
285 def _log_user_changes(self, action, user_group, user_or_users):
290 def _log_user_changes(self, action, user_group, user_or_users):
286 users = user_or_users
291 users = user_or_users
287 if not isinstance(users, (list, tuple)):
292 if not isinstance(users, (list, tuple)):
288 users = [users]
293 users = [users]
289
294
290 group_name = user_group.users_group_name
295 group_name = user_group.users_group_name
291
296
292 for user_or_user_id in users:
297 for user_or_user_id in users:
293 user = self._get_user(user_or_user_id)
298 user = self._get_user(user_or_user_id)
294 log_text = 'User {user} {action} {group}'.format(
299 log_text = 'User {user} {action} {group}'.format(
295 action=action, user=user.username, group=group_name)
300 action=action, user=user.username, group=group_name)
296 action_logger_generic(log_text)
301 action_logger_generic(log_text)
297
302
298 def _find_user_in_group(self, user, user_group):
303 def _find_user_in_group(self, user, user_group):
299 user_group_member = None
304 user_group_member = None
300 for m in user_group.members:
305 for m in user_group.members:
301 if m.user_id == user.user_id:
306 if m.user_id == user.user_id:
302 # Found this user's membership row
307 # Found this user's membership row
303 user_group_member = m
308 user_group_member = m
304 break
309 break
305
310
306 return user_group_member
311 return user_group_member
307
312
308 def _get_membership(self, user_group_id, user_id):
313 def _get_membership(self, user_group_id, user_id):
309 user_group_member = UserGroupMember(user_group_id, user_id)
314 user_group_member = UserGroupMember(user_group_id, user_id)
310 return user_group_member
315 return user_group_member
311
316
312 def add_user_to_group(self, user_group, user):
317 def add_user_to_group(self, user_group, user):
313 user_group = self._get_user_group(user_group)
318 user_group = self._get_user_group(user_group)
314 user = self._get_user(user)
319 user = self._get_user(user)
315 user_member = self._find_user_in_group(user, user_group)
320 user_member = self._find_user_in_group(user, user_group)
316 if user_member:
321 if user_member:
317 # user already in the group, skip
322 # user already in the group, skip
318 return True
323 return True
319
324
320 member = self._get_membership(
325 member = self._get_membership(
321 user_group.users_group_id, user.user_id)
326 user_group.users_group_id, user.user_id)
322 user_group.members.append(member)
327 user_group.members.append(member)
323
328
324 try:
329 try:
325 self.sa.add(member)
330 self.sa.add(member)
326 except Exception:
331 except Exception:
327 # what could go wrong here?
332 # what could go wrong here?
328 log.error(traceback.format_exc())
333 log.error(traceback.format_exc())
329 raise
334 raise
330
335
331 self._log_user_changes('added to', user_group, user)
336 self._log_user_changes('added to', user_group, user)
332 return member
337 return member
333
338
334 def remove_user_from_group(self, user_group, user):
339 def remove_user_from_group(self, user_group, user):
335 user_group = self._get_user_group(user_group)
340 user_group = self._get_user_group(user_group)
336 user = self._get_user(user)
341 user = self._get_user(user)
337 user_group_member = self._find_user_in_group(user, user_group)
342 user_group_member = self._find_user_in_group(user, user_group)
338
343
339 if not user_group_member:
344 if not user_group_member:
340 # User isn't in that group
345 # User isn't in that group
341 return False
346 return False
342
347
343 try:
348 try:
344 self.sa.delete(user_group_member)
349 self.sa.delete(user_group_member)
345 except Exception:
350 except Exception:
346 log.error(traceback.format_exc())
351 log.error(traceback.format_exc())
347 raise
352 raise
348
353
349 self._log_user_changes('removed from', user_group, user)
354 self._log_user_changes('removed from', user_group, user)
350 return True
355 return True
351
356
352 def has_perm(self, user_group, perm):
357 def has_perm(self, user_group, perm):
353 user_group = self._get_user_group(user_group)
358 user_group = self._get_user_group(user_group)
354 perm = self._get_perm(perm)
359 perm = self._get_perm(perm)
355
360
356 return UserGroupToPerm.query()\
361 return UserGroupToPerm.query()\
357 .filter(UserGroupToPerm.users_group == user_group)\
362 .filter(UserGroupToPerm.users_group == user_group)\
358 .filter(UserGroupToPerm.permission == perm).scalar() is not None
363 .filter(UserGroupToPerm.permission == perm).scalar() is not None
359
364
360 def grant_perm(self, user_group, perm):
365 def grant_perm(self, user_group, perm):
361 user_group = self._get_user_group(user_group)
366 user_group = self._get_user_group(user_group)
362 perm = self._get_perm(perm)
367 perm = self._get_perm(perm)
363
368
364 # if this permission is already granted skip it
369 # if this permission is already granted skip it
365 _perm = UserGroupToPerm.query()\
370 _perm = UserGroupToPerm.query()\
366 .filter(UserGroupToPerm.users_group == user_group)\
371 .filter(UserGroupToPerm.users_group == user_group)\
367 .filter(UserGroupToPerm.permission == perm)\
372 .filter(UserGroupToPerm.permission == perm)\
368 .scalar()
373 .scalar()
369 if _perm:
374 if _perm:
370 return
375 return
371
376
372 new = UserGroupToPerm()
377 new = UserGroupToPerm()
373 new.users_group = user_group
378 new.users_group = user_group
374 new.permission = perm
379 new.permission = perm
375 self.sa.add(new)
380 self.sa.add(new)
376 return new
381 return new
377
382
378 def revoke_perm(self, user_group, perm):
383 def revoke_perm(self, user_group, perm):
379 user_group = self._get_user_group(user_group)
384 user_group = self._get_user_group(user_group)
380 perm = self._get_perm(perm)
385 perm = self._get_perm(perm)
381
386
382 obj = UserGroupToPerm.query()\
387 obj = UserGroupToPerm.query()\
383 .filter(UserGroupToPerm.users_group == user_group)\
388 .filter(UserGroupToPerm.users_group == user_group)\
384 .filter(UserGroupToPerm.permission == perm).scalar()
389 .filter(UserGroupToPerm.permission == perm).scalar()
385 if obj:
390 if obj:
386 self.sa.delete(obj)
391 self.sa.delete(obj)
387
392
388 def grant_user_permission(self, user_group, user, perm):
393 def grant_user_permission(self, user_group, user, perm):
389 """
394 """
390 Grant permission for user on given user group, or update
395 Grant permission for user on given user group, or update
391 existing one if found
396 existing one if found
392
397
393 :param user_group: Instance of UserGroup, users_group_id,
398 :param user_group: Instance of UserGroup, users_group_id,
394 or users_group_name
399 or users_group_name
395 :param user: Instance of User, user_id or username
400 :param user: Instance of User, user_id or username
396 :param perm: Instance of Permission, or permission_name
401 :param perm: Instance of Permission, or permission_name
397 """
402 """
398
403
399 user_group = self._get_user_group(user_group)
404 user_group = self._get_user_group(user_group)
400 user = self._get_user(user)
405 user = self._get_user(user)
401 permission = self._get_perm(perm)
406 permission = self._get_perm(perm)
402
407
403 # check if we have that permission already
408 # check if we have that permission already
404 obj = self.sa.query(UserUserGroupToPerm)\
409 obj = self.sa.query(UserUserGroupToPerm)\
405 .filter(UserUserGroupToPerm.user == user)\
410 .filter(UserUserGroupToPerm.user == user)\
406 .filter(UserUserGroupToPerm.user_group == user_group)\
411 .filter(UserUserGroupToPerm.user_group == user_group)\
407 .scalar()
412 .scalar()
408 if obj is None:
413 if obj is None:
409 # create new !
414 # create new !
410 obj = UserUserGroupToPerm()
415 obj = UserUserGroupToPerm()
411 obj.user_group = user_group
416 obj.user_group = user_group
412 obj.user = user
417 obj.user = user
413 obj.permission = permission
418 obj.permission = permission
414 self.sa.add(obj)
419 self.sa.add(obj)
415 log.debug('Granted perm %s to %s on %s', perm, user, user_group)
420 log.debug('Granted perm %s to %s on %s', perm, user, user_group)
416 action_logger_generic(
421 action_logger_generic(
417 'granted permission: {} to user: {} on usergroup: {}'.format(
422 'granted permission: {} to user: {} on usergroup: {}'.format(
418 perm, user, user_group), namespace='security.usergroup')
423 perm, user, user_group), namespace='security.usergroup')
419
424
420 return obj
425 return obj
421
426
422 def revoke_user_permission(self, user_group, user):
427 def revoke_user_permission(self, user_group, user):
423 """
428 """
424 Revoke permission for user on given user group
429 Revoke permission for user on given user group
425
430
426 :param user_group: Instance of UserGroup, users_group_id,
431 :param user_group: Instance of UserGroup, users_group_id,
427 or users_group name
432 or users_group name
428 :param user: Instance of User, user_id or username
433 :param user: Instance of User, user_id or username
429 """
434 """
430
435
431 user_group = self._get_user_group(user_group)
436 user_group = self._get_user_group(user_group)
432 user = self._get_user(user)
437 user = self._get_user(user)
433
438
434 obj = self.sa.query(UserUserGroupToPerm)\
439 obj = self.sa.query(UserUserGroupToPerm)\
435 .filter(UserUserGroupToPerm.user == user)\
440 .filter(UserUserGroupToPerm.user == user)\
436 .filter(UserUserGroupToPerm.user_group == user_group)\
441 .filter(UserUserGroupToPerm.user_group == user_group)\
437 .scalar()
442 .scalar()
438 if obj:
443 if obj:
439 self.sa.delete(obj)
444 self.sa.delete(obj)
440 log.debug('Revoked perm on %s on %s', user_group, user)
445 log.debug('Revoked perm on %s on %s', user_group, user)
441 action_logger_generic(
446 action_logger_generic(
442 'revoked permission from user: {} on usergroup: {}'.format(
447 'revoked permission from user: {} on usergroup: {}'.format(
443 user, user_group), namespace='security.usergroup')
448 user, user_group), namespace='security.usergroup')
444
449
445 def grant_user_group_permission(self, target_user_group, user_group, perm):
450 def grant_user_group_permission(self, target_user_group, user_group, perm):
446 """
451 """
447 Grant user group permission for given target_user_group
452 Grant user group permission for given target_user_group
448
453
449 :param target_user_group:
454 :param target_user_group:
450 :param user_group:
455 :param user_group:
451 :param perm:
456 :param perm:
452 """
457 """
453 target_user_group = self._get_user_group(target_user_group)
458 target_user_group = self._get_user_group(target_user_group)
454 user_group = self._get_user_group(user_group)
459 user_group = self._get_user_group(user_group)
455 permission = self._get_perm(perm)
460 permission = self._get_perm(perm)
456 # forbid assigning same user group to itself
461 # forbid assigning same user group to itself
457 if target_user_group == user_group:
462 if target_user_group == user_group:
458 raise RepoGroupAssignmentError('target repo:%s cannot be '
463 raise RepoGroupAssignmentError('target repo:%s cannot be '
459 'assigned to itself' % target_user_group)
464 'assigned to itself' % target_user_group)
460
465
461 # check if we have that permission already
466 # check if we have that permission already
462 obj = self.sa.query(UserGroupUserGroupToPerm)\
467 obj = self.sa.query(UserGroupUserGroupToPerm)\
463 .filter(UserGroupUserGroupToPerm.target_user_group == target_user_group)\
468 .filter(UserGroupUserGroupToPerm.target_user_group == target_user_group)\
464 .filter(UserGroupUserGroupToPerm.user_group == user_group)\
469 .filter(UserGroupUserGroupToPerm.user_group == user_group)\
465 .scalar()
470 .scalar()
466 if obj is None:
471 if obj is None:
467 # create new !
472 # create new !
468 obj = UserGroupUserGroupToPerm()
473 obj = UserGroupUserGroupToPerm()
469 obj.user_group = user_group
474 obj.user_group = user_group
470 obj.target_user_group = target_user_group
475 obj.target_user_group = target_user_group
471 obj.permission = permission
476 obj.permission = permission
472 self.sa.add(obj)
477 self.sa.add(obj)
473 log.debug(
478 log.debug(
474 'Granted perm %s to %s on %s', perm, target_user_group, user_group)
479 'Granted perm %s to %s on %s', perm, target_user_group, user_group)
475 action_logger_generic(
480 action_logger_generic(
476 'granted permission: {} to usergroup: {} on usergroup: {}'.format(
481 'granted permission: {} to usergroup: {} on usergroup: {}'.format(
477 perm, user_group, target_user_group),
482 perm, user_group, target_user_group),
478 namespace='security.usergroup')
483 namespace='security.usergroup')
479
484
480 return obj
485 return obj
481
486
482 def revoke_user_group_permission(self, target_user_group, user_group):
487 def revoke_user_group_permission(self, target_user_group, user_group):
483 """
488 """
484 Revoke user group permission for given target_user_group
489 Revoke user group permission for given target_user_group
485
490
486 :param target_user_group:
491 :param target_user_group:
487 :param user_group:
492 :param user_group:
488 """
493 """
489 target_user_group = self._get_user_group(target_user_group)
494 target_user_group = self._get_user_group(target_user_group)
490 user_group = self._get_user_group(user_group)
495 user_group = self._get_user_group(user_group)
491
496
492 obj = self.sa.query(UserGroupUserGroupToPerm)\
497 obj = self.sa.query(UserGroupUserGroupToPerm)\
493 .filter(UserGroupUserGroupToPerm.target_user_group == target_user_group)\
498 .filter(UserGroupUserGroupToPerm.target_user_group == target_user_group)\
494 .filter(UserGroupUserGroupToPerm.user_group == user_group)\
499 .filter(UserGroupUserGroupToPerm.user_group == user_group)\
495 .scalar()
500 .scalar()
496 if obj:
501 if obj:
497 self.sa.delete(obj)
502 self.sa.delete(obj)
498 log.debug(
503 log.debug(
499 'Revoked perm on %s on %s', target_user_group, user_group)
504 'Revoked perm on %s on %s', target_user_group, user_group)
500 action_logger_generic(
505 action_logger_generic(
501 'revoked permission from usergroup: {} on usergroup: {}'.format(
506 'revoked permission from usergroup: {} on usergroup: {}'.format(
502 user_group, target_user_group),
507 user_group, target_user_group),
503 namespace='security.repogroup')
508 namespace='security.repogroup')
504
509
505 def get_perms_summary(self, user_group_id):
510 def get_perms_summary(self, user_group_id):
506 permissions = {
511 permissions = {
507 'repositories': {},
512 'repositories': {},
508 'repositories_groups': {},
513 'repositories_groups': {},
509 }
514 }
510 ugroup_repo_perms = UserGroupRepoToPerm.query()\
515 ugroup_repo_perms = UserGroupRepoToPerm.query()\
511 .options(joinedload(UserGroupRepoToPerm.permission))\
516 .options(joinedload(UserGroupRepoToPerm.permission))\
512 .options(joinedload(UserGroupRepoToPerm.repository))\
517 .options(joinedload(UserGroupRepoToPerm.repository))\
513 .filter(UserGroupRepoToPerm.users_group_id == user_group_id)\
518 .filter(UserGroupRepoToPerm.users_group_id == user_group_id)\
514 .all()
519 .all()
515
520
516 for gr in ugroup_repo_perms:
521 for gr in ugroup_repo_perms:
517 permissions['repositories'][gr.repository.repo_name] \
522 permissions['repositories'][gr.repository.repo_name] \
518 = gr.permission.permission_name
523 = gr.permission.permission_name
519
524
520 ugroup_group_perms = UserGroupRepoGroupToPerm.query()\
525 ugroup_group_perms = UserGroupRepoGroupToPerm.query()\
521 .options(joinedload(UserGroupRepoGroupToPerm.permission))\
526 .options(joinedload(UserGroupRepoGroupToPerm.permission))\
522 .options(joinedload(UserGroupRepoGroupToPerm.group))\
527 .options(joinedload(UserGroupRepoGroupToPerm.group))\
523 .filter(UserGroupRepoGroupToPerm.users_group_id == user_group_id)\
528 .filter(UserGroupRepoGroupToPerm.users_group_id == user_group_id)\
524 .all()
529 .all()
525
530
526 for gr in ugroup_group_perms:
531 for gr in ugroup_group_perms:
527 permissions['repositories_groups'][gr.group.group_name] \
532 permissions['repositories_groups'][gr.group.group_name] \
528 = gr.permission.permission_name
533 = gr.permission.permission_name
529 return permissions
534 return permissions
530
535
531 def enforce_groups(self, user, groups, extern_type=None):
536 def enforce_groups(self, user, groups, extern_type=None):
532 user = self._get_user(user)
537 user = self._get_user(user)
533 current_groups = user.group_member
538 current_groups = user.group_member
534
539
535 # find the external created groups, i.e automatically created
540 # find the external created groups, i.e automatically created
536 log.debug('Enforcing user group set `%s` on user %s', groups, user)
541 log.debug('Enforcing user group set `%s` on user %s', groups, user)
537 # calculate from what groups user should be removed
542 # calculate from what groups user should be removed
538 # external_groups that are not in groups
543 # external_groups that are not in groups
539 for gr in [x.users_group for x in current_groups]:
544 for gr in [x.users_group for x in current_groups]:
540 managed = gr.group_data.get('extern_type')
545 managed = gr.group_data.get('extern_type')
541 if managed:
546 if managed:
542 if gr.users_group_name not in groups:
547 if gr.users_group_name not in groups:
543 log.debug('Removing user %s from user group %s. '
548 log.debug('Removing user %s from user group %s. '
544 'Group sync managed by: %s', user, gr, managed)
549 'Group sync managed by: %s', user, gr, managed)
545 self.remove_user_from_group(gr, user)
550 self.remove_user_from_group(gr, user)
546 else:
551 else:
547 log.debug('Skipping removal from group %s since it is '
552 log.debug('Skipping removal from group %s since it is '
548 'not set to be automatically synchronized' % gr)
553 'not set to be automatically synchronized' % gr)
549
554
550 # now we calculate in which groups user should be == groups params
555 # now we calculate in which groups user should be == groups params
551 owner = User.get_first_super_admin().username
556 owner = User.get_first_super_admin().username
552 for gr in set(groups):
557 for gr in set(groups):
553 existing_group = UserGroup.get_by_group_name(gr)
558 existing_group = UserGroup.get_by_group_name(gr)
554 if not existing_group:
559 if not existing_group:
555 desc = 'Automatically created from plugin:%s' % extern_type
560 desc = 'Automatically created from plugin:%s' % extern_type
556 # we use first admin account to set the owner of the group
561 # we use first admin account to set the owner of the group
557 existing_group = UserGroupModel().create(
562 existing_group = UserGroupModel().create(
558 gr, desc, owner, group_data={'extern_type': extern_type})
563 gr, desc, owner, group_data={'extern_type': extern_type})
559
564
560 # we can only add users to groups which have set sync flag via
565 # we can only add users to groups which have set sync flag via
561 # extern_type attribute.
566 # extern_type attribute.
562 # This is either set and created via plugins, or manually
567 # This is either set and created via plugins, or manually
563 managed = existing_group.group_data.get('extern_type')
568 managed = existing_group.group_data.get('extern_type')
564 if managed:
569 if managed:
565 log.debug('Adding user %s to user group %s', user, gr)
570 log.debug('Adding user %s to user group %s', user, gr)
566 UserGroupModel().add_user_to_group(existing_group, user)
571 UserGroupModel().add_user_to_group(existing_group, user)
567 else:
572 else:
568 log.debug('Skipping addition to group %s since it is '
573 log.debug('Skipping addition to group %s since it is '
569 'not set to be automatically synchronized' % gr)
574 'not set to be automatically synchronized' % gr)
570
575
571 def change_groups(self, user, groups):
576 def change_groups(self, user, groups):
572 """
577 """
573 This method changes user group assignment
578 This method changes user group assignment
574 :param user: User
579 :param user: User
575 :param groups: array of UserGroupModel
580 :param groups: array of UserGroupModel
576 """
581 """
577 user = self._get_user(user)
582 user = self._get_user(user)
578 log.debug('Changing user(%s) assignment to groups(%s)', user, groups)
583 log.debug('Changing user(%s) assignment to groups(%s)', user, groups)
579 current_groups = user.group_member
584 current_groups = user.group_member
580 current_groups = [x.users_group for x in current_groups]
585 current_groups = [x.users_group for x in current_groups]
581
586
582 # calculate from what groups user should be removed/add
587 # calculate from what groups user should be removed/add
583 groups = set(groups)
588 groups = set(groups)
584 current_groups = set(current_groups)
589 current_groups = set(current_groups)
585
590
586 groups_to_remove = current_groups - groups
591 groups_to_remove = current_groups - groups
587 groups_to_add = groups - current_groups
592 groups_to_add = groups - current_groups
588
593
589 removed_from_groups = []
594 removed_from_groups = []
590 added_to_groups = []
595 added_to_groups = []
591 for gr in groups_to_remove:
596 for gr in groups_to_remove:
592 log.debug('Removing user %s from user group %s',
597 log.debug('Removing user %s from user group %s',
593 user.username, gr.users_group_name)
598 user.username, gr.users_group_name)
594 removed_from_groups.append(gr.users_group_id)
599 removed_from_groups.append(gr.users_group_id)
595 self.remove_user_from_group(gr.users_group_name, user.username)
600 self.remove_user_from_group(gr.users_group_name, user.username)
596 for gr in groups_to_add:
601 for gr in groups_to_add:
597 log.debug('Adding user %s to user group %s',
602 log.debug('Adding user %s to user group %s',
598 user.username, gr.users_group_name)
603 user.username, gr.users_group_name)
599 added_to_groups.append(gr.users_group_id)
604 added_to_groups.append(gr.users_group_id)
600 UserGroupModel().add_user_to_group(
605 UserGroupModel().add_user_to_group(
601 gr.users_group_name, user.username)
606 gr.users_group_name, user.username)
602
607
603 return added_to_groups, removed_from_groups
608 return added_to_groups, removed_from_groups
604
609
605 def _serialize_user_group(self, user_group):
610 def _serialize_user_group(self, user_group):
606 import rhodecode.lib.helpers as h
611 import rhodecode.lib.helpers as h
607 return {
612 return {
608 'id': user_group.users_group_id,
613 'id': user_group.users_group_id,
609 # TODO: marcink figure out a way to generate the url for the
614 # TODO: marcink figure out a way to generate the url for the
610 # icon
615 # icon
611 'icon_link': '',
616 'icon_link': '',
612 'value_display': 'Group: %s (%d members)' % (
617 'value_display': 'Group: %s (%d members)' % (
613 user_group.users_group_name, len(user_group.members),),
618 user_group.users_group_name, len(user_group.members),),
614 'value': user_group.users_group_name,
619 'value': user_group.users_group_name,
615 'description': user_group.user_group_description,
620 'description': user_group.user_group_description,
616 'owner': user_group.user.username,
621 'owner': user_group.user.username,
617
622
618 'owner_icon': h.gravatar_url(user_group.user.email, 30),
623 'owner_icon': h.gravatar_url(user_group.user.email, 30),
619 'value_display_owner': h.person(user_group.user.email),
624 'value_display_owner': h.person(user_group.user.email),
620
625
621 'value_type': 'user_group',
626 'value_type': 'user_group',
622 'active': user_group.users_group_active,
627 'active': user_group.users_group_active,
623 }
628 }
624
629
625 def get_user_groups(self, name_contains=None, limit=20, only_active=True,
630 def get_user_groups(self, name_contains=None, limit=20, only_active=True,
626 expand_groups=False):
631 expand_groups=False):
627 query = self.sa.query(UserGroup)
632 query = self.sa.query(UserGroup)
628 if only_active:
633 if only_active:
629 query = query.filter(UserGroup.users_group_active == true())
634 query = query.filter(UserGroup.users_group_active == true())
630
635
631 if name_contains:
636 if name_contains:
632 ilike_expression = u'%{}%'.format(safe_unicode(name_contains))
637 ilike_expression = u'%{}%'.format(safe_unicode(name_contains))
633 query = query.filter(
638 query = query.filter(
634 UserGroup.users_group_name.ilike(ilike_expression))\
639 UserGroup.users_group_name.ilike(ilike_expression))\
635 .order_by(func.length(UserGroup.users_group_name))\
640 .order_by(func.length(UserGroup.users_group_name))\
636 .order_by(UserGroup.users_group_name)
641 .order_by(UserGroup.users_group_name)
637
642
638 query = query.limit(limit)
643 query = query.limit(limit)
639 user_groups = query.all()
644 user_groups = query.all()
640 perm_set = ['usergroup.read', 'usergroup.write', 'usergroup.admin']
645 perm_set = ['usergroup.read', 'usergroup.write', 'usergroup.admin']
641 user_groups = UserGroupList(user_groups, perm_set=perm_set)
646 user_groups = UserGroupList(user_groups, perm_set=perm_set)
642
647
643 # store same serialize method to extract data from User
648 # store same serialize method to extract data from User
644 from rhodecode.model.user import UserModel
649 from rhodecode.model.user import UserModel
645 serialize_user = UserModel()._serialize_user
650 serialize_user = UserModel()._serialize_user
646
651
647 _groups = []
652 _groups = []
648 for group in user_groups:
653 for group in user_groups:
649 entry = self._serialize_user_group(group)
654 entry = self._serialize_user_group(group)
650 if expand_groups:
655 if expand_groups:
651 expanded_members = []
656 expanded_members = []
652 for member in group.members:
657 for member in group.members:
653 expanded_members.append(serialize_user(member.user))
658 expanded_members.append(serialize_user(member.user))
654 entry['members'] = expanded_members
659 entry['members'] = expanded_members
655 _groups.append(entry)
660 _groups.append(entry)
656 return _groups
661 return _groups
657
662
658 @staticmethod
663 @staticmethod
659 def get_user_groups_as_dict(user_group):
664 def get_user_groups_as_dict(user_group):
660 import rhodecode.lib.helpers as h
665 import rhodecode.lib.helpers as h
661
666
662 data = {
667 data = {
663 'users_group_id': user_group.users_group_id,
668 'users_group_id': user_group.users_group_id,
664 'group_name': user_group.users_group_name,
669 'group_name': user_group.users_group_name,
665 'group_description': user_group.user_group_description,
670 'group_description': user_group.user_group_description,
666 'active': user_group.users_group_active,
671 'active': user_group.users_group_active,
667 "owner": user_group.user.username,
672 "owner": user_group.user.username,
668 'owner_icon': h.gravatar_url(user_group.user.email, 30),
673 'owner_icon': h.gravatar_url(user_group.user.email, 30),
669 "owner_data": {
674 "owner_data": {
670 'owner': user_group.user.username,
675 'owner': user_group.user.username,
671 'owner_icon': h.gravatar_url(user_group.user.email, 30)}
676 'owner_icon': h.gravatar_url(user_group.user.email, 30)}
672 }
677 }
673 return data
678 return data
General Comments 0
You need to be logged in to leave comments. Login now