##// END OF EJS Templates
artifacts: handle detach/delete of artifacts for users who own them and are to be deleted....
marcink -
r4011:e2f9b772 default
parent child Browse files
Show More
@@ -1,790 +1,790 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2019 RhodeCode GmbH
3 # Copyright (C) 2010-2019 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import pytest
21 import pytest
22 from sqlalchemy.orm.exc import NoResultFound
22 from sqlalchemy.orm.exc import NoResultFound
23
23
24 from rhodecode.lib import auth
24 from rhodecode.lib import auth
25 from rhodecode.lib import helpers as h
25 from rhodecode.lib import helpers as h
26 from rhodecode.model.db import User, UserApiKeys, UserEmailMap, Repository
26 from rhodecode.model.db import User, UserApiKeys, UserEmailMap, Repository
27 from rhodecode.model.meta import Session
27 from rhodecode.model.meta import Session
28 from rhodecode.model.user import UserModel
28 from rhodecode.model.user import UserModel
29
29
30 from rhodecode.tests import (
30 from rhodecode.tests import (
31 TestController, TEST_USER_REGULAR_LOGIN, assert_session_flash)
31 TestController, TEST_USER_REGULAR_LOGIN, assert_session_flash)
32 from rhodecode.tests.fixture import Fixture
32 from rhodecode.tests.fixture import Fixture
33
33
34 fixture = Fixture()
34 fixture = Fixture()
35
35
36
36
37 def route_path(name, params=None, **kwargs):
37 def route_path(name, params=None, **kwargs):
38 import urllib
38 import urllib
39 from rhodecode.apps._base import ADMIN_PREFIX
39 from rhodecode.apps._base import ADMIN_PREFIX
40
40
41 base_url = {
41 base_url = {
42 'users':
42 'users':
43 ADMIN_PREFIX + '/users',
43 ADMIN_PREFIX + '/users',
44 'users_data':
44 'users_data':
45 ADMIN_PREFIX + '/users_data',
45 ADMIN_PREFIX + '/users_data',
46 'users_create':
46 'users_create':
47 ADMIN_PREFIX + '/users/create',
47 ADMIN_PREFIX + '/users/create',
48 'users_new':
48 'users_new':
49 ADMIN_PREFIX + '/users/new',
49 ADMIN_PREFIX + '/users/new',
50 'user_edit':
50 'user_edit':
51 ADMIN_PREFIX + '/users/{user_id}/edit',
51 ADMIN_PREFIX + '/users/{user_id}/edit',
52 'user_edit_advanced':
52 'user_edit_advanced':
53 ADMIN_PREFIX + '/users/{user_id}/edit/advanced',
53 ADMIN_PREFIX + '/users/{user_id}/edit/advanced',
54 'user_edit_global_perms':
54 'user_edit_global_perms':
55 ADMIN_PREFIX + '/users/{user_id}/edit/global_permissions',
55 ADMIN_PREFIX + '/users/{user_id}/edit/global_permissions',
56 'user_edit_global_perms_update':
56 'user_edit_global_perms_update':
57 ADMIN_PREFIX + '/users/{user_id}/edit/global_permissions/update',
57 ADMIN_PREFIX + '/users/{user_id}/edit/global_permissions/update',
58 'user_update':
58 'user_update':
59 ADMIN_PREFIX + '/users/{user_id}/update',
59 ADMIN_PREFIX + '/users/{user_id}/update',
60 'user_delete':
60 'user_delete':
61 ADMIN_PREFIX + '/users/{user_id}/delete',
61 ADMIN_PREFIX + '/users/{user_id}/delete',
62 'user_create_personal_repo_group':
62 'user_create_personal_repo_group':
63 ADMIN_PREFIX + '/users/{user_id}/create_repo_group',
63 ADMIN_PREFIX + '/users/{user_id}/create_repo_group',
64
64
65 'edit_user_auth_tokens':
65 'edit_user_auth_tokens':
66 ADMIN_PREFIX + '/users/{user_id}/edit/auth_tokens',
66 ADMIN_PREFIX + '/users/{user_id}/edit/auth_tokens',
67 'edit_user_auth_tokens_add':
67 'edit_user_auth_tokens_add':
68 ADMIN_PREFIX + '/users/{user_id}/edit/auth_tokens/new',
68 ADMIN_PREFIX + '/users/{user_id}/edit/auth_tokens/new',
69 'edit_user_auth_tokens_delete':
69 'edit_user_auth_tokens_delete':
70 ADMIN_PREFIX + '/users/{user_id}/edit/auth_tokens/delete',
70 ADMIN_PREFIX + '/users/{user_id}/edit/auth_tokens/delete',
71
71
72 'edit_user_emails':
72 'edit_user_emails':
73 ADMIN_PREFIX + '/users/{user_id}/edit/emails',
73 ADMIN_PREFIX + '/users/{user_id}/edit/emails',
74 'edit_user_emails_add':
74 'edit_user_emails_add':
75 ADMIN_PREFIX + '/users/{user_id}/edit/emails/new',
75 ADMIN_PREFIX + '/users/{user_id}/edit/emails/new',
76 'edit_user_emails_delete':
76 'edit_user_emails_delete':
77 ADMIN_PREFIX + '/users/{user_id}/edit/emails/delete',
77 ADMIN_PREFIX + '/users/{user_id}/edit/emails/delete',
78
78
79 'edit_user_ips':
79 'edit_user_ips':
80 ADMIN_PREFIX + '/users/{user_id}/edit/ips',
80 ADMIN_PREFIX + '/users/{user_id}/edit/ips',
81 'edit_user_ips_add':
81 'edit_user_ips_add':
82 ADMIN_PREFIX + '/users/{user_id}/edit/ips/new',
82 ADMIN_PREFIX + '/users/{user_id}/edit/ips/new',
83 'edit_user_ips_delete':
83 'edit_user_ips_delete':
84 ADMIN_PREFIX + '/users/{user_id}/edit/ips/delete',
84 ADMIN_PREFIX + '/users/{user_id}/edit/ips/delete',
85
85
86 'edit_user_perms_summary':
86 'edit_user_perms_summary':
87 ADMIN_PREFIX + '/users/{user_id}/edit/permissions_summary',
87 ADMIN_PREFIX + '/users/{user_id}/edit/permissions_summary',
88 'edit_user_perms_summary_json':
88 'edit_user_perms_summary_json':
89 ADMIN_PREFIX + '/users/{user_id}/edit/permissions_summary/json',
89 ADMIN_PREFIX + '/users/{user_id}/edit/permissions_summary/json',
90
90
91 'edit_user_audit_logs':
91 'edit_user_audit_logs':
92 ADMIN_PREFIX + '/users/{user_id}/edit/audit',
92 ADMIN_PREFIX + '/users/{user_id}/edit/audit',
93
93
94 'edit_user_audit_logs_download':
94 'edit_user_audit_logs_download':
95 ADMIN_PREFIX + '/users/{user_id}/edit/audit/download',
95 ADMIN_PREFIX + '/users/{user_id}/edit/audit/download',
96
96
97 }[name].format(**kwargs)
97 }[name].format(**kwargs)
98
98
99 if params:
99 if params:
100 base_url = '{}?{}'.format(base_url, urllib.urlencode(params))
100 base_url = '{}?{}'.format(base_url, urllib.urlencode(params))
101 return base_url
101 return base_url
102
102
103
103
104 class TestAdminUsersView(TestController):
104 class TestAdminUsersView(TestController):
105
105
106 def test_show_users(self):
106 def test_show_users(self):
107 self.log_user()
107 self.log_user()
108 self.app.get(route_path('users'))
108 self.app.get(route_path('users'))
109
109
110 def test_show_users_data(self, xhr_header):
110 def test_show_users_data(self, xhr_header):
111 self.log_user()
111 self.log_user()
112 response = self.app.get(route_path(
112 response = self.app.get(route_path(
113 'users_data'), extra_environ=xhr_header)
113 'users_data'), extra_environ=xhr_header)
114
114
115 all_users = User.query().filter(
115 all_users = User.query().filter(
116 User.username != User.DEFAULT_USER).count()
116 User.username != User.DEFAULT_USER).count()
117 assert response.json['recordsTotal'] == all_users
117 assert response.json['recordsTotal'] == all_users
118
118
119 def test_show_users_data_filtered(self, xhr_header):
119 def test_show_users_data_filtered(self, xhr_header):
120 self.log_user()
120 self.log_user()
121 response = self.app.get(route_path(
121 response = self.app.get(route_path(
122 'users_data', params={'search[value]': 'empty_search'}),
122 'users_data', params={'search[value]': 'empty_search'}),
123 extra_environ=xhr_header)
123 extra_environ=xhr_header)
124
124
125 all_users = User.query().filter(
125 all_users = User.query().filter(
126 User.username != User.DEFAULT_USER).count()
126 User.username != User.DEFAULT_USER).count()
127 assert response.json['recordsTotal'] == all_users
127 assert response.json['recordsTotal'] == all_users
128 assert response.json['recordsFiltered'] == 0
128 assert response.json['recordsFiltered'] == 0
129
129
130 def test_auth_tokens_default_user(self):
130 def test_auth_tokens_default_user(self):
131 self.log_user()
131 self.log_user()
132 user = User.get_default_user()
132 user = User.get_default_user()
133 response = self.app.get(
133 response = self.app.get(
134 route_path('edit_user_auth_tokens', user_id=user.user_id),
134 route_path('edit_user_auth_tokens', user_id=user.user_id),
135 status=302)
135 status=302)
136
136
137 def test_auth_tokens(self):
137 def test_auth_tokens(self):
138 self.log_user()
138 self.log_user()
139
139
140 user = User.get_by_username(TEST_USER_REGULAR_LOGIN)
140 user = User.get_by_username(TEST_USER_REGULAR_LOGIN)
141 user_id = user.user_id
141 user_id = user.user_id
142 auth_tokens = user.auth_tokens
142 auth_tokens = user.auth_tokens
143 response = self.app.get(
143 response = self.app.get(
144 route_path('edit_user_auth_tokens', user_id=user_id))
144 route_path('edit_user_auth_tokens', user_id=user_id))
145 for token in auth_tokens:
145 for token in auth_tokens:
146 response.mustcontain(token)
146 response.mustcontain(token)
147 response.mustcontain('never')
147 response.mustcontain('never')
148
148
149 @pytest.mark.parametrize("desc, lifetime", [
149 @pytest.mark.parametrize("desc, lifetime", [
150 ('forever', -1),
150 ('forever', -1),
151 ('5mins', 60*5),
151 ('5mins', 60*5),
152 ('30days', 60*60*24*30),
152 ('30days', 60*60*24*30),
153 ])
153 ])
154 def test_add_auth_token(self, desc, lifetime, user_util):
154 def test_add_auth_token(self, desc, lifetime, user_util):
155 self.log_user()
155 self.log_user()
156 user = user_util.create_user()
156 user = user_util.create_user()
157 user_id = user.user_id
157 user_id = user.user_id
158
158
159 response = self.app.post(
159 response = self.app.post(
160 route_path('edit_user_auth_tokens_add', user_id=user_id),
160 route_path('edit_user_auth_tokens_add', user_id=user_id),
161 {'description': desc, 'lifetime': lifetime,
161 {'description': desc, 'lifetime': lifetime,
162 'csrf_token': self.csrf_token})
162 'csrf_token': self.csrf_token})
163 assert_session_flash(response, 'Auth token successfully created')
163 assert_session_flash(response, 'Auth token successfully created')
164
164
165 response = response.follow()
165 response = response.follow()
166 user = User.get(user_id)
166 user = User.get(user_id)
167 for auth_token in user.auth_tokens:
167 for auth_token in user.auth_tokens:
168 response.mustcontain(auth_token)
168 response.mustcontain(auth_token)
169
169
170 def test_delete_auth_token(self, user_util):
170 def test_delete_auth_token(self, user_util):
171 self.log_user()
171 self.log_user()
172 user = user_util.create_user()
172 user = user_util.create_user()
173 user_id = user.user_id
173 user_id = user.user_id
174 keys = user.auth_tokens
174 keys = user.auth_tokens
175 assert 2 == len(keys)
175 assert 2 == len(keys)
176
176
177 response = self.app.post(
177 response = self.app.post(
178 route_path('edit_user_auth_tokens_add', user_id=user_id),
178 route_path('edit_user_auth_tokens_add', user_id=user_id),
179 {'description': 'desc', 'lifetime': -1,
179 {'description': 'desc', 'lifetime': -1,
180 'csrf_token': self.csrf_token})
180 'csrf_token': self.csrf_token})
181 assert_session_flash(response, 'Auth token successfully created')
181 assert_session_flash(response, 'Auth token successfully created')
182 response.follow()
182 response.follow()
183
183
184 # now delete our key
184 # now delete our key
185 keys = UserApiKeys.query().filter(UserApiKeys.user_id == user_id).all()
185 keys = UserApiKeys.query().filter(UserApiKeys.user_id == user_id).all()
186 assert 3 == len(keys)
186 assert 3 == len(keys)
187
187
188 response = self.app.post(
188 response = self.app.post(
189 route_path('edit_user_auth_tokens_delete', user_id=user_id),
189 route_path('edit_user_auth_tokens_delete', user_id=user_id),
190 {'del_auth_token': keys[0].user_api_key_id,
190 {'del_auth_token': keys[0].user_api_key_id,
191 'csrf_token': self.csrf_token})
191 'csrf_token': self.csrf_token})
192
192
193 assert_session_flash(response, 'Auth token successfully deleted')
193 assert_session_flash(response, 'Auth token successfully deleted')
194 keys = UserApiKeys.query().filter(UserApiKeys.user_id == user_id).all()
194 keys = UserApiKeys.query().filter(UserApiKeys.user_id == user_id).all()
195 assert 2 == len(keys)
195 assert 2 == len(keys)
196
196
197 def test_ips(self):
197 def test_ips(self):
198 self.log_user()
198 self.log_user()
199 user = User.get_by_username(TEST_USER_REGULAR_LOGIN)
199 user = User.get_by_username(TEST_USER_REGULAR_LOGIN)
200 response = self.app.get(route_path('edit_user_ips', user_id=user.user_id))
200 response = self.app.get(route_path('edit_user_ips', user_id=user.user_id))
201 response.mustcontain('All IP addresses are allowed')
201 response.mustcontain('All IP addresses are allowed')
202
202
203 @pytest.mark.parametrize("test_name, ip, ip_range, failure", [
203 @pytest.mark.parametrize("test_name, ip, ip_range, failure", [
204 ('127/24', '127.0.0.1/24', '127.0.0.0 - 127.0.0.255', False),
204 ('127/24', '127.0.0.1/24', '127.0.0.0 - 127.0.0.255', False),
205 ('10/32', '10.0.0.10/32', '10.0.0.10 - 10.0.0.10', False),
205 ('10/32', '10.0.0.10/32', '10.0.0.10 - 10.0.0.10', False),
206 ('0/16', '0.0.0.0/16', '0.0.0.0 - 0.0.255.255', False),
206 ('0/16', '0.0.0.0/16', '0.0.0.0 - 0.0.255.255', False),
207 ('0/8', '0.0.0.0/8', '0.0.0.0 - 0.255.255.255', False),
207 ('0/8', '0.0.0.0/8', '0.0.0.0 - 0.255.255.255', False),
208 ('127_bad_mask', '127.0.0.1/99', '127.0.0.1 - 127.0.0.1', True),
208 ('127_bad_mask', '127.0.0.1/99', '127.0.0.1 - 127.0.0.1', True),
209 ('127_bad_ip', 'foobar', 'foobar', True),
209 ('127_bad_ip', 'foobar', 'foobar', True),
210 ])
210 ])
211 def test_ips_add(self, user_util, test_name, ip, ip_range, failure):
211 def test_ips_add(self, user_util, test_name, ip, ip_range, failure):
212 self.log_user()
212 self.log_user()
213 user = user_util.create_user(username=test_name)
213 user = user_util.create_user(username=test_name)
214 user_id = user.user_id
214 user_id = user.user_id
215
215
216 response = self.app.post(
216 response = self.app.post(
217 route_path('edit_user_ips_add', user_id=user_id),
217 route_path('edit_user_ips_add', user_id=user_id),
218 params={'new_ip': ip, 'csrf_token': self.csrf_token})
218 params={'new_ip': ip, 'csrf_token': self.csrf_token})
219
219
220 if failure:
220 if failure:
221 assert_session_flash(
221 assert_session_flash(
222 response, 'Please enter a valid IPv4 or IpV6 address')
222 response, 'Please enter a valid IPv4 or IpV6 address')
223 response = self.app.get(route_path('edit_user_ips', user_id=user_id))
223 response = self.app.get(route_path('edit_user_ips', user_id=user_id))
224
224
225 response.mustcontain(no=[ip])
225 response.mustcontain(no=[ip])
226 response.mustcontain(no=[ip_range])
226 response.mustcontain(no=[ip_range])
227
227
228 else:
228 else:
229 response = self.app.get(route_path('edit_user_ips', user_id=user_id))
229 response = self.app.get(route_path('edit_user_ips', user_id=user_id))
230 response.mustcontain(ip)
230 response.mustcontain(ip)
231 response.mustcontain(ip_range)
231 response.mustcontain(ip_range)
232
232
233 def test_ips_delete(self, user_util):
233 def test_ips_delete(self, user_util):
234 self.log_user()
234 self.log_user()
235 user = user_util.create_user()
235 user = user_util.create_user()
236 user_id = user.user_id
236 user_id = user.user_id
237 ip = '127.0.0.1/32'
237 ip = '127.0.0.1/32'
238 ip_range = '127.0.0.1 - 127.0.0.1'
238 ip_range = '127.0.0.1 - 127.0.0.1'
239 new_ip = UserModel().add_extra_ip(user_id, ip)
239 new_ip = UserModel().add_extra_ip(user_id, ip)
240 Session().commit()
240 Session().commit()
241 new_ip_id = new_ip.ip_id
241 new_ip_id = new_ip.ip_id
242
242
243 response = self.app.get(route_path('edit_user_ips', user_id=user_id))
243 response = self.app.get(route_path('edit_user_ips', user_id=user_id))
244 response.mustcontain(ip)
244 response.mustcontain(ip)
245 response.mustcontain(ip_range)
245 response.mustcontain(ip_range)
246
246
247 self.app.post(
247 self.app.post(
248 route_path('edit_user_ips_delete', user_id=user_id),
248 route_path('edit_user_ips_delete', user_id=user_id),
249 params={'del_ip_id': new_ip_id, 'csrf_token': self.csrf_token})
249 params={'del_ip_id': new_ip_id, 'csrf_token': self.csrf_token})
250
250
251 response = self.app.get(route_path('edit_user_ips', user_id=user_id))
251 response = self.app.get(route_path('edit_user_ips', user_id=user_id))
252 response.mustcontain('All IP addresses are allowed')
252 response.mustcontain('All IP addresses are allowed')
253 response.mustcontain(no=[ip])
253 response.mustcontain(no=[ip])
254 response.mustcontain(no=[ip_range])
254 response.mustcontain(no=[ip_range])
255
255
256 def test_emails(self):
256 def test_emails(self):
257 self.log_user()
257 self.log_user()
258 user = User.get_by_username(TEST_USER_REGULAR_LOGIN)
258 user = User.get_by_username(TEST_USER_REGULAR_LOGIN)
259 response = self.app.get(
259 response = self.app.get(
260 route_path('edit_user_emails', user_id=user.user_id))
260 route_path('edit_user_emails', user_id=user.user_id))
261 response.mustcontain('No additional emails specified')
261 response.mustcontain('No additional emails specified')
262
262
263 def test_emails_add(self, user_util):
263 def test_emails_add(self, user_util):
264 self.log_user()
264 self.log_user()
265 user = user_util.create_user()
265 user = user_util.create_user()
266 user_id = user.user_id
266 user_id = user.user_id
267
267
268 self.app.post(
268 self.app.post(
269 route_path('edit_user_emails_add', user_id=user_id),
269 route_path('edit_user_emails_add', user_id=user_id),
270 params={'new_email': 'example@rhodecode.com',
270 params={'new_email': 'example@rhodecode.com',
271 'csrf_token': self.csrf_token})
271 'csrf_token': self.csrf_token})
272
272
273 response = self.app.get(
273 response = self.app.get(
274 route_path('edit_user_emails', user_id=user_id))
274 route_path('edit_user_emails', user_id=user_id))
275 response.mustcontain('example@rhodecode.com')
275 response.mustcontain('example@rhodecode.com')
276
276
277 def test_emails_add_existing_email(self, user_util, user_regular):
277 def test_emails_add_existing_email(self, user_util, user_regular):
278 existing_email = user_regular.email
278 existing_email = user_regular.email
279
279
280 self.log_user()
280 self.log_user()
281 user = user_util.create_user()
281 user = user_util.create_user()
282 user_id = user.user_id
282 user_id = user.user_id
283
283
284 response = self.app.post(
284 response = self.app.post(
285 route_path('edit_user_emails_add', user_id=user_id),
285 route_path('edit_user_emails_add', user_id=user_id),
286 params={'new_email': existing_email,
286 params={'new_email': existing_email,
287 'csrf_token': self.csrf_token})
287 'csrf_token': self.csrf_token})
288 assert_session_flash(
288 assert_session_flash(
289 response, 'This e-mail address is already taken')
289 response, 'This e-mail address is already taken')
290
290
291 response = self.app.get(
291 response = self.app.get(
292 route_path('edit_user_emails', user_id=user_id))
292 route_path('edit_user_emails', user_id=user_id))
293 response.mustcontain(no=[existing_email])
293 response.mustcontain(no=[existing_email])
294
294
295 def test_emails_delete(self, user_util):
295 def test_emails_delete(self, user_util):
296 self.log_user()
296 self.log_user()
297 user = user_util.create_user()
297 user = user_util.create_user()
298 user_id = user.user_id
298 user_id = user.user_id
299
299
300 self.app.post(
300 self.app.post(
301 route_path('edit_user_emails_add', user_id=user_id),
301 route_path('edit_user_emails_add', user_id=user_id),
302 params={'new_email': 'example@rhodecode.com',
302 params={'new_email': 'example@rhodecode.com',
303 'csrf_token': self.csrf_token})
303 'csrf_token': self.csrf_token})
304
304
305 response = self.app.get(
305 response = self.app.get(
306 route_path('edit_user_emails', user_id=user_id))
306 route_path('edit_user_emails', user_id=user_id))
307 response.mustcontain('example@rhodecode.com')
307 response.mustcontain('example@rhodecode.com')
308
308
309 user_email = UserEmailMap.query()\
309 user_email = UserEmailMap.query()\
310 .filter(UserEmailMap.email == 'example@rhodecode.com') \
310 .filter(UserEmailMap.email == 'example@rhodecode.com') \
311 .filter(UserEmailMap.user_id == user_id)\
311 .filter(UserEmailMap.user_id == user_id)\
312 .one()
312 .one()
313
313
314 del_email_id = user_email.email_id
314 del_email_id = user_email.email_id
315 self.app.post(
315 self.app.post(
316 route_path('edit_user_emails_delete', user_id=user_id),
316 route_path('edit_user_emails_delete', user_id=user_id),
317 params={'del_email_id': del_email_id,
317 params={'del_email_id': del_email_id,
318 'csrf_token': self.csrf_token})
318 'csrf_token': self.csrf_token})
319
319
320 response = self.app.get(
320 response = self.app.get(
321 route_path('edit_user_emails', user_id=user_id))
321 route_path('edit_user_emails', user_id=user_id))
322 response.mustcontain(no=['example@rhodecode.com'])
322 response.mustcontain(no=['example@rhodecode.com'])
323
323
324 def test_create(self, request, xhr_header):
324 def test_create(self, request, xhr_header):
325 self.log_user()
325 self.log_user()
326 username = 'newtestuser'
326 username = 'newtestuser'
327 password = 'test12'
327 password = 'test12'
328 password_confirmation = password
328 password_confirmation = password
329 name = 'name'
329 name = 'name'
330 lastname = 'lastname'
330 lastname = 'lastname'
331 email = 'mail@mail.com'
331 email = 'mail@mail.com'
332
332
333 self.app.get(route_path('users_new'))
333 self.app.get(route_path('users_new'))
334
334
335 response = self.app.post(route_path('users_create'), params={
335 response = self.app.post(route_path('users_create'), params={
336 'username': username,
336 'username': username,
337 'password': password,
337 'password': password,
338 'password_confirmation': password_confirmation,
338 'password_confirmation': password_confirmation,
339 'firstname': name,
339 'firstname': name,
340 'active': True,
340 'active': True,
341 'lastname': lastname,
341 'lastname': lastname,
342 'extern_name': 'rhodecode',
342 'extern_name': 'rhodecode',
343 'extern_type': 'rhodecode',
343 'extern_type': 'rhodecode',
344 'email': email,
344 'email': email,
345 'csrf_token': self.csrf_token,
345 'csrf_token': self.csrf_token,
346 })
346 })
347 user_link = h.link_to(
347 user_link = h.link_to(
348 username,
348 username,
349 route_path(
349 route_path(
350 'user_edit', user_id=User.get_by_username(username).user_id))
350 'user_edit', user_id=User.get_by_username(username).user_id))
351 assert_session_flash(response, 'Created user %s' % (user_link,))
351 assert_session_flash(response, 'Created user %s' % (user_link,))
352
352
353 @request.addfinalizer
353 @request.addfinalizer
354 def cleanup():
354 def cleanup():
355 fixture.destroy_user(username)
355 fixture.destroy_user(username)
356 Session().commit()
356 Session().commit()
357
357
358 new_user = User.query().filter(User.username == username).one()
358 new_user = User.query().filter(User.username == username).one()
359
359
360 assert new_user.username == username
360 assert new_user.username == username
361 assert auth.check_password(password, new_user.password)
361 assert auth.check_password(password, new_user.password)
362 assert new_user.name == name
362 assert new_user.name == name
363 assert new_user.lastname == lastname
363 assert new_user.lastname == lastname
364 assert new_user.email == email
364 assert new_user.email == email
365
365
366 response = self.app.get(route_path('users_data'),
366 response = self.app.get(route_path('users_data'),
367 extra_environ=xhr_header)
367 extra_environ=xhr_header)
368 response.mustcontain(username)
368 response.mustcontain(username)
369
369
370 def test_create_err(self):
370 def test_create_err(self):
371 self.log_user()
371 self.log_user()
372 username = 'new_user'
372 username = 'new_user'
373 password = ''
373 password = ''
374 name = 'name'
374 name = 'name'
375 lastname = 'lastname'
375 lastname = 'lastname'
376 email = 'errmail.com'
376 email = 'errmail.com'
377
377
378 self.app.get(route_path('users_new'))
378 self.app.get(route_path('users_new'))
379
379
380 response = self.app.post(route_path('users_create'), params={
380 response = self.app.post(route_path('users_create'), params={
381 'username': username,
381 'username': username,
382 'password': password,
382 'password': password,
383 'name': name,
383 'name': name,
384 'active': False,
384 'active': False,
385 'lastname': lastname,
385 'lastname': lastname,
386 'email': email,
386 'email': email,
387 'csrf_token': self.csrf_token,
387 'csrf_token': self.csrf_token,
388 })
388 })
389
389
390 msg = u'Username "%(username)s" is forbidden'
390 msg = u'Username "%(username)s" is forbidden'
391 msg = h.html_escape(msg % {'username': 'new_user'})
391 msg = h.html_escape(msg % {'username': 'new_user'})
392 response.mustcontain('<span class="error-message">%s</span>' % msg)
392 response.mustcontain('<span class="error-message">%s</span>' % msg)
393 response.mustcontain(
393 response.mustcontain(
394 '<span class="error-message">Please enter a value</span>')
394 '<span class="error-message">Please enter a value</span>')
395 response.mustcontain(
395 response.mustcontain(
396 '<span class="error-message">An email address must contain a'
396 '<span class="error-message">An email address must contain a'
397 ' single @</span>')
397 ' single @</span>')
398
398
399 def get_user():
399 def get_user():
400 Session().query(User).filter(User.username == username).one()
400 Session().query(User).filter(User.username == username).one()
401
401
402 with pytest.raises(NoResultFound):
402 with pytest.raises(NoResultFound):
403 get_user()
403 get_user()
404
404
405 def test_new(self):
405 def test_new(self):
406 self.log_user()
406 self.log_user()
407 self.app.get(route_path('users_new'))
407 self.app.get(route_path('users_new'))
408
408
409 @pytest.mark.parametrize("name, attrs", [
409 @pytest.mark.parametrize("name, attrs", [
410 ('firstname', {'firstname': 'new_username'}),
410 ('firstname', {'firstname': 'new_username'}),
411 ('lastname', {'lastname': 'new_username'}),
411 ('lastname', {'lastname': 'new_username'}),
412 ('admin', {'admin': True}),
412 ('admin', {'admin': True}),
413 ('admin', {'admin': False}),
413 ('admin', {'admin': False}),
414 ('extern_type', {'extern_type': 'ldap'}),
414 ('extern_type', {'extern_type': 'ldap'}),
415 ('extern_type', {'extern_type': None}),
415 ('extern_type', {'extern_type': None}),
416 ('extern_name', {'extern_name': 'test'}),
416 ('extern_name', {'extern_name': 'test'}),
417 ('extern_name', {'extern_name': None}),
417 ('extern_name', {'extern_name': None}),
418 ('active', {'active': False}),
418 ('active', {'active': False}),
419 ('active', {'active': True}),
419 ('active', {'active': True}),
420 ('email', {'email': 'some@email.com'}),
420 ('email', {'email': 'some@email.com'}),
421 ('language', {'language': 'de'}),
421 ('language', {'language': 'de'}),
422 ('language', {'language': 'en'}),
422 ('language', {'language': 'en'}),
423 # ('new_password', {'new_password': 'foobar123',
423 # ('new_password', {'new_password': 'foobar123',
424 # 'password_confirmation': 'foobar123'})
424 # 'password_confirmation': 'foobar123'})
425 ])
425 ])
426 def test_update(self, name, attrs, user_util):
426 def test_update(self, name, attrs, user_util):
427 self.log_user()
427 self.log_user()
428 usr = user_util.create_user(
428 usr = user_util.create_user(
429 password='qweqwe',
429 password='qweqwe',
430 email='testme@rhodecode.org',
430 email='testme@rhodecode.org',
431 extern_type='rhodecode',
431 extern_type='rhodecode',
432 extern_name='xxx',
432 extern_name='xxx',
433 )
433 )
434 user_id = usr.user_id
434 user_id = usr.user_id
435 Session().commit()
435 Session().commit()
436
436
437 params = usr.get_api_data()
437 params = usr.get_api_data()
438 cur_lang = params['language'] or 'en'
438 cur_lang = params['language'] or 'en'
439 params.update({
439 params.update({
440 'password_confirmation': '',
440 'password_confirmation': '',
441 'new_password': '',
441 'new_password': '',
442 'language': cur_lang,
442 'language': cur_lang,
443 'csrf_token': self.csrf_token,
443 'csrf_token': self.csrf_token,
444 })
444 })
445 params.update({'new_password': ''})
445 params.update({'new_password': ''})
446 params.update(attrs)
446 params.update(attrs)
447 if name == 'email':
447 if name == 'email':
448 params['emails'] = [attrs['email']]
448 params['emails'] = [attrs['email']]
449 elif name == 'extern_type':
449 elif name == 'extern_type':
450 # cannot update this via form, expected value is original one
450 # cannot update this via form, expected value is original one
451 params['extern_type'] = "rhodecode"
451 params['extern_type'] = "rhodecode"
452 elif name == 'extern_name':
452 elif name == 'extern_name':
453 # cannot update this via form, expected value is original one
453 # cannot update this via form, expected value is original one
454 params['extern_name'] = 'xxx'
454 params['extern_name'] = 'xxx'
455 # special case since this user is not
455 # special case since this user is not
456 # logged in yet his data is not filled
456 # logged in yet his data is not filled
457 # so we use creation data
457 # so we use creation data
458
458
459 response = self.app.post(
459 response = self.app.post(
460 route_path('user_update', user_id=usr.user_id), params)
460 route_path('user_update', user_id=usr.user_id), params)
461 assert response.status_int == 302
461 assert response.status_int == 302
462 assert_session_flash(response, 'User updated successfully')
462 assert_session_flash(response, 'User updated successfully')
463
463
464 updated_user = User.get(user_id)
464 updated_user = User.get(user_id)
465 updated_params = updated_user.get_api_data()
465 updated_params = updated_user.get_api_data()
466 updated_params.update({'password_confirmation': ''})
466 updated_params.update({'password_confirmation': ''})
467 updated_params.update({'new_password': ''})
467 updated_params.update({'new_password': ''})
468
468
469 del params['csrf_token']
469 del params['csrf_token']
470 assert params == updated_params
470 assert params == updated_params
471
471
472 def test_update_and_migrate_password(
472 def test_update_and_migrate_password(
473 self, autologin_user, real_crypto_backend, user_util):
473 self, autologin_user, real_crypto_backend, user_util):
474
474
475 user = user_util.create_user()
475 user = user_util.create_user()
476 temp_user = user.username
476 temp_user = user.username
477 user.password = auth._RhodeCodeCryptoSha256().hash_create(
477 user.password = auth._RhodeCodeCryptoSha256().hash_create(
478 b'test123')
478 b'test123')
479 Session().add(user)
479 Session().add(user)
480 Session().commit()
480 Session().commit()
481
481
482 params = user.get_api_data()
482 params = user.get_api_data()
483
483
484 params.update({
484 params.update({
485 'password_confirmation': 'qweqwe123',
485 'password_confirmation': 'qweqwe123',
486 'new_password': 'qweqwe123',
486 'new_password': 'qweqwe123',
487 'language': 'en',
487 'language': 'en',
488 'csrf_token': autologin_user.csrf_token,
488 'csrf_token': autologin_user.csrf_token,
489 })
489 })
490
490
491 response = self.app.post(
491 response = self.app.post(
492 route_path('user_update', user_id=user.user_id), params)
492 route_path('user_update', user_id=user.user_id), params)
493 assert response.status_int == 302
493 assert response.status_int == 302
494 assert_session_flash(response, 'User updated successfully')
494 assert_session_flash(response, 'User updated successfully')
495
495
496 # new password should be bcrypted, after log-in and transfer
496 # new password should be bcrypted, after log-in and transfer
497 user = User.get_by_username(temp_user)
497 user = User.get_by_username(temp_user)
498 assert user.password.startswith('$')
498 assert user.password.startswith('$')
499
499
500 updated_user = User.get_by_username(temp_user)
500 updated_user = User.get_by_username(temp_user)
501 updated_params = updated_user.get_api_data()
501 updated_params = updated_user.get_api_data()
502 updated_params.update({'password_confirmation': 'qweqwe123'})
502 updated_params.update({'password_confirmation': 'qweqwe123'})
503 updated_params.update({'new_password': 'qweqwe123'})
503 updated_params.update({'new_password': 'qweqwe123'})
504
504
505 del params['csrf_token']
505 del params['csrf_token']
506 assert params == updated_params
506 assert params == updated_params
507
507
508 def test_delete(self):
508 def test_delete(self):
509 self.log_user()
509 self.log_user()
510 username = 'newtestuserdeleteme'
510 username = 'newtestuserdeleteme'
511
511
512 fixture.create_user(name=username)
512 fixture.create_user(name=username)
513
513
514 new_user = Session().query(User)\
514 new_user = Session().query(User)\
515 .filter(User.username == username).one()
515 .filter(User.username == username).one()
516 response = self.app.post(
516 response = self.app.post(
517 route_path('user_delete', user_id=new_user.user_id),
517 route_path('user_delete', user_id=new_user.user_id),
518 params={'csrf_token': self.csrf_token})
518 params={'csrf_token': self.csrf_token})
519
519
520 assert_session_flash(response, 'Successfully deleted user')
520 assert_session_flash(response, 'Successfully deleted user `{}`'.format(username))
521
521
522 def test_delete_owner_of_repository(self, request, user_util):
522 def test_delete_owner_of_repository(self, request, user_util):
523 self.log_user()
523 self.log_user()
524 obj_name = 'test_repo'
524 obj_name = 'test_repo'
525 usr = user_util.create_user()
525 usr = user_util.create_user()
526 username = usr.username
526 username = usr.username
527 fixture.create_repo(obj_name, cur_user=usr.username)
527 fixture.create_repo(obj_name, cur_user=usr.username)
528
528
529 new_user = Session().query(User)\
529 new_user = Session().query(User)\
530 .filter(User.username == username).one()
530 .filter(User.username == username).one()
531 response = self.app.post(
531 response = self.app.post(
532 route_path('user_delete', user_id=new_user.user_id),
532 route_path('user_delete', user_id=new_user.user_id),
533 params={'csrf_token': self.csrf_token})
533 params={'csrf_token': self.csrf_token})
534
534
535 msg = 'user "%s" still owns 1 repositories and cannot be removed. ' \
535 msg = 'user "%s" still owns 1 repositories and cannot be removed. ' \
536 'Switch owners or remove those repositories:%s' % (username, obj_name)
536 'Switch owners or remove those repositories:%s' % (username, obj_name)
537 assert_session_flash(response, msg)
537 assert_session_flash(response, msg)
538 fixture.destroy_repo(obj_name)
538 fixture.destroy_repo(obj_name)
539
539
540 def test_delete_owner_of_repository_detaching(self, request, user_util):
540 def test_delete_owner_of_repository_detaching(self, request, user_util):
541 self.log_user()
541 self.log_user()
542 obj_name = 'test_repo'
542 obj_name = 'test_repo'
543 usr = user_util.create_user(auto_cleanup=False)
543 usr = user_util.create_user(auto_cleanup=False)
544 username = usr.username
544 username = usr.username
545 fixture.create_repo(obj_name, cur_user=usr.username)
545 fixture.create_repo(obj_name, cur_user=usr.username)
546
546
547 new_user = Session().query(User)\
547 new_user = Session().query(User)\
548 .filter(User.username == username).one()
548 .filter(User.username == username).one()
549 response = self.app.post(
549 response = self.app.post(
550 route_path('user_delete', user_id=new_user.user_id),
550 route_path('user_delete', user_id=new_user.user_id),
551 params={'user_repos': 'detach', 'csrf_token': self.csrf_token})
551 params={'user_repos': 'detach', 'csrf_token': self.csrf_token})
552
552
553 msg = 'Detached 1 repositories'
553 msg = 'Detached 1 repositories'
554 assert_session_flash(response, msg)
554 assert_session_flash(response, msg)
555 fixture.destroy_repo(obj_name)
555 fixture.destroy_repo(obj_name)
556
556
557 def test_delete_owner_of_repository_deleting(self, request, user_util):
557 def test_delete_owner_of_repository_deleting(self, request, user_util):
558 self.log_user()
558 self.log_user()
559 obj_name = 'test_repo'
559 obj_name = 'test_repo'
560 usr = user_util.create_user(auto_cleanup=False)
560 usr = user_util.create_user(auto_cleanup=False)
561 username = usr.username
561 username = usr.username
562 fixture.create_repo(obj_name, cur_user=usr.username)
562 fixture.create_repo(obj_name, cur_user=usr.username)
563
563
564 new_user = Session().query(User)\
564 new_user = Session().query(User)\
565 .filter(User.username == username).one()
565 .filter(User.username == username).one()
566 response = self.app.post(
566 response = self.app.post(
567 route_path('user_delete', user_id=new_user.user_id),
567 route_path('user_delete', user_id=new_user.user_id),
568 params={'user_repos': 'delete', 'csrf_token': self.csrf_token})
568 params={'user_repos': 'delete', 'csrf_token': self.csrf_token})
569
569
570 msg = 'Deleted 1 repositories'
570 msg = 'Deleted 1 repositories'
571 assert_session_flash(response, msg)
571 assert_session_flash(response, msg)
572
572
573 def test_delete_owner_of_repository_group(self, request, user_util):
573 def test_delete_owner_of_repository_group(self, request, user_util):
574 self.log_user()
574 self.log_user()
575 obj_name = 'test_group'
575 obj_name = 'test_group'
576 usr = user_util.create_user()
576 usr = user_util.create_user()
577 username = usr.username
577 username = usr.username
578 fixture.create_repo_group(obj_name, cur_user=usr.username)
578 fixture.create_repo_group(obj_name, cur_user=usr.username)
579
579
580 new_user = Session().query(User)\
580 new_user = Session().query(User)\
581 .filter(User.username == username).one()
581 .filter(User.username == username).one()
582 response = self.app.post(
582 response = self.app.post(
583 route_path('user_delete', user_id=new_user.user_id),
583 route_path('user_delete', user_id=new_user.user_id),
584 params={'csrf_token': self.csrf_token})
584 params={'csrf_token': self.csrf_token})
585
585
586 msg = 'user "%s" still owns 1 repository groups and cannot be removed. ' \
586 msg = 'user "%s" still owns 1 repository groups and cannot be removed. ' \
587 'Switch owners or remove those repository groups:%s' % (username, obj_name)
587 'Switch owners or remove those repository groups:%s' % (username, obj_name)
588 assert_session_flash(response, msg)
588 assert_session_flash(response, msg)
589 fixture.destroy_repo_group(obj_name)
589 fixture.destroy_repo_group(obj_name)
590
590
591 def test_delete_owner_of_repository_group_detaching(self, request, user_util):
591 def test_delete_owner_of_repository_group_detaching(self, request, user_util):
592 self.log_user()
592 self.log_user()
593 obj_name = 'test_group'
593 obj_name = 'test_group'
594 usr = user_util.create_user(auto_cleanup=False)
594 usr = user_util.create_user(auto_cleanup=False)
595 username = usr.username
595 username = usr.username
596 fixture.create_repo_group(obj_name, cur_user=usr.username)
596 fixture.create_repo_group(obj_name, cur_user=usr.username)
597
597
598 new_user = Session().query(User)\
598 new_user = Session().query(User)\
599 .filter(User.username == username).one()
599 .filter(User.username == username).one()
600 response = self.app.post(
600 response = self.app.post(
601 route_path('user_delete', user_id=new_user.user_id),
601 route_path('user_delete', user_id=new_user.user_id),
602 params={'user_repo_groups': 'delete', 'csrf_token': self.csrf_token})
602 params={'user_repo_groups': 'delete', 'csrf_token': self.csrf_token})
603
603
604 msg = 'Deleted 1 repository groups'
604 msg = 'Deleted 1 repository groups'
605 assert_session_flash(response, msg)
605 assert_session_flash(response, msg)
606
606
607 def test_delete_owner_of_repository_group_deleting(self, request, user_util):
607 def test_delete_owner_of_repository_group_deleting(self, request, user_util):
608 self.log_user()
608 self.log_user()
609 obj_name = 'test_group'
609 obj_name = 'test_group'
610 usr = user_util.create_user(auto_cleanup=False)
610 usr = user_util.create_user(auto_cleanup=False)
611 username = usr.username
611 username = usr.username
612 fixture.create_repo_group(obj_name, cur_user=usr.username)
612 fixture.create_repo_group(obj_name, cur_user=usr.username)
613
613
614 new_user = Session().query(User)\
614 new_user = Session().query(User)\
615 .filter(User.username == username).one()
615 .filter(User.username == username).one()
616 response = self.app.post(
616 response = self.app.post(
617 route_path('user_delete', user_id=new_user.user_id),
617 route_path('user_delete', user_id=new_user.user_id),
618 params={'user_repo_groups': 'detach', 'csrf_token': self.csrf_token})
618 params={'user_repo_groups': 'detach', 'csrf_token': self.csrf_token})
619
619
620 msg = 'Detached 1 repository groups'
620 msg = 'Detached 1 repository groups'
621 assert_session_flash(response, msg)
621 assert_session_flash(response, msg)
622 fixture.destroy_repo_group(obj_name)
622 fixture.destroy_repo_group(obj_name)
623
623
624 def test_delete_owner_of_user_group(self, request, user_util):
624 def test_delete_owner_of_user_group(self, request, user_util):
625 self.log_user()
625 self.log_user()
626 obj_name = 'test_user_group'
626 obj_name = 'test_user_group'
627 usr = user_util.create_user()
627 usr = user_util.create_user()
628 username = usr.username
628 username = usr.username
629 fixture.create_user_group(obj_name, cur_user=usr.username)
629 fixture.create_user_group(obj_name, cur_user=usr.username)
630
630
631 new_user = Session().query(User)\
631 new_user = Session().query(User)\
632 .filter(User.username == username).one()
632 .filter(User.username == username).one()
633 response = self.app.post(
633 response = self.app.post(
634 route_path('user_delete', user_id=new_user.user_id),
634 route_path('user_delete', user_id=new_user.user_id),
635 params={'csrf_token': self.csrf_token})
635 params={'csrf_token': self.csrf_token})
636
636
637 msg = 'user "%s" still owns 1 user groups and cannot be removed. ' \
637 msg = 'user "%s" still owns 1 user groups and cannot be removed. ' \
638 'Switch owners or remove those user groups:%s' % (username, obj_name)
638 'Switch owners or remove those user groups:%s' % (username, obj_name)
639 assert_session_flash(response, msg)
639 assert_session_flash(response, msg)
640 fixture.destroy_user_group(obj_name)
640 fixture.destroy_user_group(obj_name)
641
641
642 def test_delete_owner_of_user_group_detaching(self, request, user_util):
642 def test_delete_owner_of_user_group_detaching(self, request, user_util):
643 self.log_user()
643 self.log_user()
644 obj_name = 'test_user_group'
644 obj_name = 'test_user_group'
645 usr = user_util.create_user(auto_cleanup=False)
645 usr = user_util.create_user(auto_cleanup=False)
646 username = usr.username
646 username = usr.username
647 fixture.create_user_group(obj_name, cur_user=usr.username)
647 fixture.create_user_group(obj_name, cur_user=usr.username)
648
648
649 new_user = Session().query(User)\
649 new_user = Session().query(User)\
650 .filter(User.username == username).one()
650 .filter(User.username == username).one()
651 try:
651 try:
652 response = self.app.post(
652 response = self.app.post(
653 route_path('user_delete', user_id=new_user.user_id),
653 route_path('user_delete', user_id=new_user.user_id),
654 params={'user_user_groups': 'detach',
654 params={'user_user_groups': 'detach',
655 'csrf_token': self.csrf_token})
655 'csrf_token': self.csrf_token})
656
656
657 msg = 'Detached 1 user groups'
657 msg = 'Detached 1 user groups'
658 assert_session_flash(response, msg)
658 assert_session_flash(response, msg)
659 finally:
659 finally:
660 fixture.destroy_user_group(obj_name)
660 fixture.destroy_user_group(obj_name)
661
661
662 def test_delete_owner_of_user_group_deleting(self, request, user_util):
662 def test_delete_owner_of_user_group_deleting(self, request, user_util):
663 self.log_user()
663 self.log_user()
664 obj_name = 'test_user_group'
664 obj_name = 'test_user_group'
665 usr = user_util.create_user(auto_cleanup=False)
665 usr = user_util.create_user(auto_cleanup=False)
666 username = usr.username
666 username = usr.username
667 fixture.create_user_group(obj_name, cur_user=usr.username)
667 fixture.create_user_group(obj_name, cur_user=usr.username)
668
668
669 new_user = Session().query(User)\
669 new_user = Session().query(User)\
670 .filter(User.username == username).one()
670 .filter(User.username == username).one()
671 response = self.app.post(
671 response = self.app.post(
672 route_path('user_delete', user_id=new_user.user_id),
672 route_path('user_delete', user_id=new_user.user_id),
673 params={'user_user_groups': 'delete', 'csrf_token': self.csrf_token})
673 params={'user_user_groups': 'delete', 'csrf_token': self.csrf_token})
674
674
675 msg = 'Deleted 1 user groups'
675 msg = 'Deleted 1 user groups'
676 assert_session_flash(response, msg)
676 assert_session_flash(response, msg)
677
677
678 def test_edit(self, user_util):
678 def test_edit(self, user_util):
679 self.log_user()
679 self.log_user()
680 user = user_util.create_user()
680 user = user_util.create_user()
681 self.app.get(route_path('user_edit', user_id=user.user_id))
681 self.app.get(route_path('user_edit', user_id=user.user_id))
682
682
683 def test_edit_default_user_redirect(self):
683 def test_edit_default_user_redirect(self):
684 self.log_user()
684 self.log_user()
685 user = User.get_default_user()
685 user = User.get_default_user()
686 self.app.get(route_path('user_edit', user_id=user.user_id), status=302)
686 self.app.get(route_path('user_edit', user_id=user.user_id), status=302)
687
687
688 @pytest.mark.parametrize(
688 @pytest.mark.parametrize(
689 'repo_create, repo_create_write, user_group_create, repo_group_create,'
689 'repo_create, repo_create_write, user_group_create, repo_group_create,'
690 'fork_create, inherit_default_permissions, expect_error,'
690 'fork_create, inherit_default_permissions, expect_error,'
691 'expect_form_error', [
691 'expect_form_error', [
692 ('hg.create.none', 'hg.create.write_on_repogroup.false',
692 ('hg.create.none', 'hg.create.write_on_repogroup.false',
693 'hg.usergroup.create.false', 'hg.repogroup.create.false',
693 'hg.usergroup.create.false', 'hg.repogroup.create.false',
694 'hg.fork.none', 'hg.inherit_default_perms.false', False, False),
694 'hg.fork.none', 'hg.inherit_default_perms.false', False, False),
695 ('hg.create.repository', 'hg.create.write_on_repogroup.false',
695 ('hg.create.repository', 'hg.create.write_on_repogroup.false',
696 'hg.usergroup.create.false', 'hg.repogroup.create.false',
696 'hg.usergroup.create.false', 'hg.repogroup.create.false',
697 'hg.fork.none', 'hg.inherit_default_perms.false', False, False),
697 'hg.fork.none', 'hg.inherit_default_perms.false', False, False),
698 ('hg.create.repository', 'hg.create.write_on_repogroup.true',
698 ('hg.create.repository', 'hg.create.write_on_repogroup.true',
699 'hg.usergroup.create.true', 'hg.repogroup.create.true',
699 'hg.usergroup.create.true', 'hg.repogroup.create.true',
700 'hg.fork.repository', 'hg.inherit_default_perms.false', False,
700 'hg.fork.repository', 'hg.inherit_default_perms.false', False,
701 False),
701 False),
702 ('hg.create.XXX', 'hg.create.write_on_repogroup.true',
702 ('hg.create.XXX', 'hg.create.write_on_repogroup.true',
703 'hg.usergroup.create.true', 'hg.repogroup.create.true',
703 'hg.usergroup.create.true', 'hg.repogroup.create.true',
704 'hg.fork.repository', 'hg.inherit_default_perms.false', False,
704 'hg.fork.repository', 'hg.inherit_default_perms.false', False,
705 True),
705 True),
706 ('', '', '', '', '', '', True, False),
706 ('', '', '', '', '', '', True, False),
707 ])
707 ])
708 def test_global_perms_on_user(
708 def test_global_perms_on_user(
709 self, repo_create, repo_create_write, user_group_create,
709 self, repo_create, repo_create_write, user_group_create,
710 repo_group_create, fork_create, expect_error, expect_form_error,
710 repo_group_create, fork_create, expect_error, expect_form_error,
711 inherit_default_permissions, user_util):
711 inherit_default_permissions, user_util):
712 self.log_user()
712 self.log_user()
713 user = user_util.create_user()
713 user = user_util.create_user()
714 uid = user.user_id
714 uid = user.user_id
715
715
716 # ENABLE REPO CREATE ON A GROUP
716 # ENABLE REPO CREATE ON A GROUP
717 perm_params = {
717 perm_params = {
718 'inherit_default_permissions': False,
718 'inherit_default_permissions': False,
719 'default_repo_create': repo_create,
719 'default_repo_create': repo_create,
720 'default_repo_create_on_write': repo_create_write,
720 'default_repo_create_on_write': repo_create_write,
721 'default_user_group_create': user_group_create,
721 'default_user_group_create': user_group_create,
722 'default_repo_group_create': repo_group_create,
722 'default_repo_group_create': repo_group_create,
723 'default_fork_create': fork_create,
723 'default_fork_create': fork_create,
724 'default_inherit_default_permissions': inherit_default_permissions,
724 'default_inherit_default_permissions': inherit_default_permissions,
725 'csrf_token': self.csrf_token,
725 'csrf_token': self.csrf_token,
726 }
726 }
727 response = self.app.post(
727 response = self.app.post(
728 route_path('user_edit_global_perms_update', user_id=uid),
728 route_path('user_edit_global_perms_update', user_id=uid),
729 params=perm_params)
729 params=perm_params)
730
730
731 if expect_form_error:
731 if expect_form_error:
732 assert response.status_int == 200
732 assert response.status_int == 200
733 response.mustcontain('Value must be one of')
733 response.mustcontain('Value must be one of')
734 else:
734 else:
735 if expect_error:
735 if expect_error:
736 msg = 'An error occurred during permissions saving'
736 msg = 'An error occurred during permissions saving'
737 else:
737 else:
738 msg = 'User global permissions updated successfully'
738 msg = 'User global permissions updated successfully'
739 ug = User.get(uid)
739 ug = User.get(uid)
740 del perm_params['inherit_default_permissions']
740 del perm_params['inherit_default_permissions']
741 del perm_params['csrf_token']
741 del perm_params['csrf_token']
742 assert perm_params == ug.get_default_perms()
742 assert perm_params == ug.get_default_perms()
743 assert_session_flash(response, msg)
743 assert_session_flash(response, msg)
744
744
745 def test_global_permissions_initial_values(self, user_util):
745 def test_global_permissions_initial_values(self, user_util):
746 self.log_user()
746 self.log_user()
747 user = user_util.create_user()
747 user = user_util.create_user()
748 uid = user.user_id
748 uid = user.user_id
749 response = self.app.get(
749 response = self.app.get(
750 route_path('user_edit_global_perms', user_id=uid))
750 route_path('user_edit_global_perms', user_id=uid))
751 default_user = User.get_default_user()
751 default_user = User.get_default_user()
752 default_permissions = default_user.get_default_perms()
752 default_permissions = default_user.get_default_perms()
753 assert_response = response.assert_response()
753 assert_response = response.assert_response()
754 expected_permissions = (
754 expected_permissions = (
755 'default_repo_create', 'default_repo_create_on_write',
755 'default_repo_create', 'default_repo_create_on_write',
756 'default_fork_create', 'default_repo_group_create',
756 'default_fork_create', 'default_repo_group_create',
757 'default_user_group_create', 'default_inherit_default_permissions')
757 'default_user_group_create', 'default_inherit_default_permissions')
758 for permission in expected_permissions:
758 for permission in expected_permissions:
759 css_selector = '[name={}][checked=checked]'.format(permission)
759 css_selector = '[name={}][checked=checked]'.format(permission)
760 element = assert_response.get_element(css_selector)
760 element = assert_response.get_element(css_selector)
761 assert element.value == default_permissions[permission]
761 assert element.value == default_permissions[permission]
762
762
763 def test_perms_summary_page(self):
763 def test_perms_summary_page(self):
764 user = self.log_user()
764 user = self.log_user()
765 response = self.app.get(
765 response = self.app.get(
766 route_path('edit_user_perms_summary', user_id=user['user_id']))
766 route_path('edit_user_perms_summary', user_id=user['user_id']))
767 for repo in Repository.query().all():
767 for repo in Repository.query().all():
768 response.mustcontain(repo.repo_name)
768 response.mustcontain(repo.repo_name)
769
769
770 def test_perms_summary_page_json(self):
770 def test_perms_summary_page_json(self):
771 user = self.log_user()
771 user = self.log_user()
772 response = self.app.get(
772 response = self.app.get(
773 route_path('edit_user_perms_summary_json', user_id=user['user_id']))
773 route_path('edit_user_perms_summary_json', user_id=user['user_id']))
774 for repo in Repository.query().all():
774 for repo in Repository.query().all():
775 response.mustcontain(repo.repo_name)
775 response.mustcontain(repo.repo_name)
776
776
777 def test_audit_log_page(self):
777 def test_audit_log_page(self):
778 user = self.log_user()
778 user = self.log_user()
779 self.app.get(
779 self.app.get(
780 route_path('edit_user_audit_logs', user_id=user['user_id']))
780 route_path('edit_user_audit_logs', user_id=user['user_id']))
781
781
782 def test_audit_log_page_download(self):
782 def test_audit_log_page_download(self):
783 user = self.log_user()
783 user = self.log_user()
784 user_id = user['user_id']
784 user_id = user['user_id']
785 response = self.app.get(
785 response = self.app.get(
786 route_path('edit_user_audit_logs_download', user_id=user_id))
786 route_path('edit_user_audit_logs_download', user_id=user_id))
787
787
788 assert response.content_disposition == \
788 assert response.content_disposition == \
789 'attachment; filename=user_{}_audit_logs.json'.format(user_id)
789 'attachment; filename=user_{}_audit_logs.json'.format(user_id)
790 assert response.content_type == "application/json"
790 assert response.content_type == "application/json"
@@ -1,1317 +1,1329 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2016-2019 RhodeCode GmbH
3 # Copyright (C) 2016-2019 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 datetime
22 import datetime
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.renderers import render
28 from pyramid.renderers import render
29 from pyramid.response import Response
29 from pyramid.response import Response
30
30
31 from rhodecode import events
31 from rhodecode import events
32 from rhodecode.apps._base import BaseAppView, DataGridAppView, UserAppView
32 from rhodecode.apps._base import BaseAppView, DataGridAppView, UserAppView
33 from rhodecode.apps.ssh_support import SshKeyFileChangeEvent
33 from rhodecode.apps.ssh_support import SshKeyFileChangeEvent
34 from rhodecode.authentication.base import get_authn_registry, RhodeCodeExternalAuthPlugin
34 from rhodecode.authentication.base import get_authn_registry, RhodeCodeExternalAuthPlugin
35 from rhodecode.authentication.plugins import auth_rhodecode
35 from rhodecode.authentication.plugins import auth_rhodecode
36 from rhodecode.events import trigger
36 from rhodecode.events import trigger
37 from rhodecode.model.db import true
37 from rhodecode.model.db import true
38
38
39 from rhodecode.lib import audit_logger, rc_cache
39 from rhodecode.lib import audit_logger, rc_cache
40 from rhodecode.lib.exceptions import (
40 from rhodecode.lib.exceptions import (
41 UserCreationError, UserOwnsReposException, UserOwnsRepoGroupsException,
41 UserCreationError, UserOwnsReposException, UserOwnsRepoGroupsException,
42 UserOwnsUserGroupsException, DefaultUserException)
42 UserOwnsUserGroupsException, DefaultUserException)
43 from rhodecode.lib.ext_json import json
43 from rhodecode.lib.ext_json import json
44 from rhodecode.lib.auth import (
44 from rhodecode.lib.auth import (
45 LoginRequired, HasPermissionAllDecorator, CSRFRequired)
45 LoginRequired, HasPermissionAllDecorator, CSRFRequired)
46 from rhodecode.lib import helpers as h
46 from rhodecode.lib import helpers as h
47 from rhodecode.lib.utils2 import safe_int, safe_unicode, AttributeDict
47 from rhodecode.lib.utils2 import safe_int, safe_unicode, AttributeDict
48 from rhodecode.model.auth_token import AuthTokenModel
48 from rhodecode.model.auth_token import AuthTokenModel
49 from rhodecode.model.forms import (
49 from rhodecode.model.forms import (
50 UserForm, UserIndividualPermissionsForm, UserPermissionsForm,
50 UserForm, UserIndividualPermissionsForm, UserPermissionsForm,
51 UserExtraEmailForm, UserExtraIpForm)
51 UserExtraEmailForm, UserExtraIpForm)
52 from rhodecode.model.permission import PermissionModel
52 from rhodecode.model.permission import PermissionModel
53 from rhodecode.model.repo_group import RepoGroupModel
53 from rhodecode.model.repo_group import RepoGroupModel
54 from rhodecode.model.ssh_key import SshKeyModel
54 from rhodecode.model.ssh_key import SshKeyModel
55 from rhodecode.model.user import UserModel
55 from rhodecode.model.user import UserModel
56 from rhodecode.model.user_group import UserGroupModel
56 from rhodecode.model.user_group import UserGroupModel
57 from rhodecode.model.db import (
57 from rhodecode.model.db import (
58 or_, coalesce,IntegrityError, User, UserGroup, UserIpMap, UserEmailMap,
58 or_, coalesce,IntegrityError, User, UserGroup, UserIpMap, UserEmailMap,
59 UserApiKeys, UserSshKeys, RepoGroup)
59 UserApiKeys, UserSshKeys, RepoGroup)
60 from rhodecode.model.meta import Session
60 from rhodecode.model.meta import Session
61
61
62 log = logging.getLogger(__name__)
62 log = logging.getLogger(__name__)
63
63
64
64
65 class AdminUsersView(BaseAppView, DataGridAppView):
65 class AdminUsersView(BaseAppView, DataGridAppView):
66
66
67 def load_default_context(self):
67 def load_default_context(self):
68 c = self._get_local_tmpl_context()
68 c = self._get_local_tmpl_context()
69 return c
69 return c
70
70
71 @LoginRequired()
71 @LoginRequired()
72 @HasPermissionAllDecorator('hg.admin')
72 @HasPermissionAllDecorator('hg.admin')
73 @view_config(
73 @view_config(
74 route_name='users', request_method='GET',
74 route_name='users', request_method='GET',
75 renderer='rhodecode:templates/admin/users/users.mako')
75 renderer='rhodecode:templates/admin/users/users.mako')
76 def users_list(self):
76 def users_list(self):
77 c = self.load_default_context()
77 c = self.load_default_context()
78 return self._get_template_context(c)
78 return self._get_template_context(c)
79
79
80 @LoginRequired()
80 @LoginRequired()
81 @HasPermissionAllDecorator('hg.admin')
81 @HasPermissionAllDecorator('hg.admin')
82 @view_config(
82 @view_config(
83 # renderer defined below
83 # renderer defined below
84 route_name='users_data', request_method='GET',
84 route_name='users_data', request_method='GET',
85 renderer='json_ext', xhr=True)
85 renderer='json_ext', xhr=True)
86 def users_list_data(self):
86 def users_list_data(self):
87 self.load_default_context()
87 self.load_default_context()
88 column_map = {
88 column_map = {
89 'first_name': 'name',
89 'first_name': 'name',
90 'last_name': 'lastname',
90 'last_name': 'lastname',
91 }
91 }
92 draw, start, limit = self._extract_chunk(self.request)
92 draw, start, limit = self._extract_chunk(self.request)
93 search_q, order_by, order_dir = self._extract_ordering(
93 search_q, order_by, order_dir = self._extract_ordering(
94 self.request, column_map=column_map)
94 self.request, column_map=column_map)
95 _render = self.request.get_partial_renderer(
95 _render = self.request.get_partial_renderer(
96 'rhodecode:templates/data_table/_dt_elements.mako')
96 'rhodecode:templates/data_table/_dt_elements.mako')
97
97
98 def user_actions(user_id, username):
98 def user_actions(user_id, username):
99 return _render("user_actions", user_id, username)
99 return _render("user_actions", user_id, username)
100
100
101 users_data_total_count = User.query()\
101 users_data_total_count = User.query()\
102 .filter(User.username != User.DEFAULT_USER) \
102 .filter(User.username != User.DEFAULT_USER) \
103 .count()
103 .count()
104
104
105 users_data_total_inactive_count = User.query()\
105 users_data_total_inactive_count = User.query()\
106 .filter(User.username != User.DEFAULT_USER) \
106 .filter(User.username != User.DEFAULT_USER) \
107 .filter(User.active != true())\
107 .filter(User.active != true())\
108 .count()
108 .count()
109
109
110 # json generate
110 # json generate
111 base_q = User.query().filter(User.username != User.DEFAULT_USER)
111 base_q = User.query().filter(User.username != User.DEFAULT_USER)
112 base_inactive_q = base_q.filter(User.active != true())
112 base_inactive_q = base_q.filter(User.active != true())
113
113
114 if search_q:
114 if search_q:
115 like_expression = u'%{}%'.format(safe_unicode(search_q))
115 like_expression = u'%{}%'.format(safe_unicode(search_q))
116 base_q = base_q.filter(or_(
116 base_q = base_q.filter(or_(
117 User.username.ilike(like_expression),
117 User.username.ilike(like_expression),
118 User._email.ilike(like_expression),
118 User._email.ilike(like_expression),
119 User.name.ilike(like_expression),
119 User.name.ilike(like_expression),
120 User.lastname.ilike(like_expression),
120 User.lastname.ilike(like_expression),
121 ))
121 ))
122 base_inactive_q = base_q.filter(User.active != true())
122 base_inactive_q = base_q.filter(User.active != true())
123
123
124 users_data_total_filtered_count = base_q.count()
124 users_data_total_filtered_count = base_q.count()
125 users_data_total_filtered_inactive_count = base_inactive_q.count()
125 users_data_total_filtered_inactive_count = base_inactive_q.count()
126
126
127 sort_col = getattr(User, order_by, None)
127 sort_col = getattr(User, order_by, None)
128 if sort_col:
128 if sort_col:
129 if order_dir == 'asc':
129 if order_dir == 'asc':
130 # handle null values properly to order by NULL last
130 # handle null values properly to order by NULL last
131 if order_by in ['last_activity']:
131 if order_by in ['last_activity']:
132 sort_col = coalesce(sort_col, datetime.date.max)
132 sort_col = coalesce(sort_col, datetime.date.max)
133 sort_col = sort_col.asc()
133 sort_col = sort_col.asc()
134 else:
134 else:
135 # handle null values properly to order by NULL last
135 # handle null values properly to order by NULL last
136 if order_by in ['last_activity']:
136 if order_by in ['last_activity']:
137 sort_col = coalesce(sort_col, datetime.date.min)
137 sort_col = coalesce(sort_col, datetime.date.min)
138 sort_col = sort_col.desc()
138 sort_col = sort_col.desc()
139
139
140 base_q = base_q.order_by(sort_col)
140 base_q = base_q.order_by(sort_col)
141 base_q = base_q.offset(start).limit(limit)
141 base_q = base_q.offset(start).limit(limit)
142
142
143 users_list = base_q.all()
143 users_list = base_q.all()
144
144
145 users_data = []
145 users_data = []
146 for user in users_list:
146 for user in users_list:
147 users_data.append({
147 users_data.append({
148 "username": h.gravatar_with_user(self.request, user.username),
148 "username": h.gravatar_with_user(self.request, user.username),
149 "email": user.email,
149 "email": user.email,
150 "first_name": user.first_name,
150 "first_name": user.first_name,
151 "last_name": user.last_name,
151 "last_name": user.last_name,
152 "last_login": h.format_date(user.last_login),
152 "last_login": h.format_date(user.last_login),
153 "last_activity": h.format_date(user.last_activity),
153 "last_activity": h.format_date(user.last_activity),
154 "active": h.bool2icon(user.active),
154 "active": h.bool2icon(user.active),
155 "active_raw": user.active,
155 "active_raw": user.active,
156 "admin": h.bool2icon(user.admin),
156 "admin": h.bool2icon(user.admin),
157 "extern_type": user.extern_type,
157 "extern_type": user.extern_type,
158 "extern_name": user.extern_name,
158 "extern_name": user.extern_name,
159 "action": user_actions(user.user_id, user.username),
159 "action": user_actions(user.user_id, user.username),
160 })
160 })
161 data = ({
161 data = ({
162 'draw': draw,
162 'draw': draw,
163 'data': users_data,
163 'data': users_data,
164 'recordsTotal': users_data_total_count,
164 'recordsTotal': users_data_total_count,
165 'recordsFiltered': users_data_total_filtered_count,
165 'recordsFiltered': users_data_total_filtered_count,
166 'recordsTotalInactive': users_data_total_inactive_count,
166 'recordsTotalInactive': users_data_total_inactive_count,
167 'recordsFilteredInactive': users_data_total_filtered_inactive_count
167 'recordsFilteredInactive': users_data_total_filtered_inactive_count
168 })
168 })
169
169
170 return data
170 return data
171
171
172 def _set_personal_repo_group_template_vars(self, c_obj):
172 def _set_personal_repo_group_template_vars(self, c_obj):
173 DummyUser = AttributeDict({
173 DummyUser = AttributeDict({
174 'username': '${username}',
174 'username': '${username}',
175 'user_id': '${user_id}',
175 'user_id': '${user_id}',
176 })
176 })
177 c_obj.default_create_repo_group = RepoGroupModel() \
177 c_obj.default_create_repo_group = RepoGroupModel() \
178 .get_default_create_personal_repo_group()
178 .get_default_create_personal_repo_group()
179 c_obj.personal_repo_group_name = RepoGroupModel() \
179 c_obj.personal_repo_group_name = RepoGroupModel() \
180 .get_personal_group_name(DummyUser)
180 .get_personal_group_name(DummyUser)
181
181
182 @LoginRequired()
182 @LoginRequired()
183 @HasPermissionAllDecorator('hg.admin')
183 @HasPermissionAllDecorator('hg.admin')
184 @view_config(
184 @view_config(
185 route_name='users_new', request_method='GET',
185 route_name='users_new', request_method='GET',
186 renderer='rhodecode:templates/admin/users/user_add.mako')
186 renderer='rhodecode:templates/admin/users/user_add.mako')
187 def users_new(self):
187 def users_new(self):
188 _ = self.request.translate
188 _ = self.request.translate
189 c = self.load_default_context()
189 c = self.load_default_context()
190 c.default_extern_type = auth_rhodecode.RhodeCodeAuthPlugin.uid
190 c.default_extern_type = auth_rhodecode.RhodeCodeAuthPlugin.uid
191 self._set_personal_repo_group_template_vars(c)
191 self._set_personal_repo_group_template_vars(c)
192 return self._get_template_context(c)
192 return self._get_template_context(c)
193
193
194 @LoginRequired()
194 @LoginRequired()
195 @HasPermissionAllDecorator('hg.admin')
195 @HasPermissionAllDecorator('hg.admin')
196 @CSRFRequired()
196 @CSRFRequired()
197 @view_config(
197 @view_config(
198 route_name='users_create', request_method='POST',
198 route_name='users_create', request_method='POST',
199 renderer='rhodecode:templates/admin/users/user_add.mako')
199 renderer='rhodecode:templates/admin/users/user_add.mako')
200 def users_create(self):
200 def users_create(self):
201 _ = self.request.translate
201 _ = self.request.translate
202 c = self.load_default_context()
202 c = self.load_default_context()
203 c.default_extern_type = auth_rhodecode.RhodeCodeAuthPlugin.uid
203 c.default_extern_type = auth_rhodecode.RhodeCodeAuthPlugin.uid
204 user_model = UserModel()
204 user_model = UserModel()
205 user_form = UserForm(self.request.translate)()
205 user_form = UserForm(self.request.translate)()
206 try:
206 try:
207 form_result = user_form.to_python(dict(self.request.POST))
207 form_result = user_form.to_python(dict(self.request.POST))
208 user = user_model.create(form_result)
208 user = user_model.create(form_result)
209 Session().flush()
209 Session().flush()
210 creation_data = user.get_api_data()
210 creation_data = user.get_api_data()
211 username = form_result['username']
211 username = form_result['username']
212
212
213 audit_logger.store_web(
213 audit_logger.store_web(
214 'user.create', action_data={'data': creation_data},
214 'user.create', action_data={'data': creation_data},
215 user=c.rhodecode_user)
215 user=c.rhodecode_user)
216
216
217 user_link = h.link_to(
217 user_link = h.link_to(
218 h.escape(username),
218 h.escape(username),
219 h.route_path('user_edit', user_id=user.user_id))
219 h.route_path('user_edit', user_id=user.user_id))
220 h.flash(h.literal(_('Created user %(user_link)s')
220 h.flash(h.literal(_('Created user %(user_link)s')
221 % {'user_link': user_link}), category='success')
221 % {'user_link': user_link}), category='success')
222 Session().commit()
222 Session().commit()
223 except formencode.Invalid as errors:
223 except formencode.Invalid as errors:
224 self._set_personal_repo_group_template_vars(c)
224 self._set_personal_repo_group_template_vars(c)
225 data = render(
225 data = render(
226 'rhodecode:templates/admin/users/user_add.mako',
226 'rhodecode:templates/admin/users/user_add.mako',
227 self._get_template_context(c), self.request)
227 self._get_template_context(c), self.request)
228 html = formencode.htmlfill.render(
228 html = formencode.htmlfill.render(
229 data,
229 data,
230 defaults=errors.value,
230 defaults=errors.value,
231 errors=errors.error_dict or {},
231 errors=errors.error_dict or {},
232 prefix_error=False,
232 prefix_error=False,
233 encoding="UTF-8",
233 encoding="UTF-8",
234 force_defaults=False
234 force_defaults=False
235 )
235 )
236 return Response(html)
236 return Response(html)
237 except UserCreationError as e:
237 except UserCreationError as e:
238 h.flash(e, 'error')
238 h.flash(e, 'error')
239 except Exception:
239 except Exception:
240 log.exception("Exception creation of user")
240 log.exception("Exception creation of user")
241 h.flash(_('Error occurred during creation of user %s')
241 h.flash(_('Error occurred during creation of user %s')
242 % self.request.POST.get('username'), category='error')
242 % self.request.POST.get('username'), category='error')
243 raise HTTPFound(h.route_path('users'))
243 raise HTTPFound(h.route_path('users'))
244
244
245
245
246 class UsersView(UserAppView):
246 class UsersView(UserAppView):
247 ALLOW_SCOPED_TOKENS = False
247 ALLOW_SCOPED_TOKENS = False
248 """
248 """
249 This view has alternative version inside EE, if modified please take a look
249 This view has alternative version inside EE, if modified please take a look
250 in there as well.
250 in there as well.
251 """
251 """
252
252
253 def get_auth_plugins(self):
253 def get_auth_plugins(self):
254 valid_plugins = []
254 valid_plugins = []
255 authn_registry = get_authn_registry(self.request.registry)
255 authn_registry = get_authn_registry(self.request.registry)
256 for plugin in authn_registry.get_plugins_for_authentication():
256 for plugin in authn_registry.get_plugins_for_authentication():
257 if isinstance(plugin, RhodeCodeExternalAuthPlugin):
257 if isinstance(plugin, RhodeCodeExternalAuthPlugin):
258 valid_plugins.append(plugin)
258 valid_plugins.append(plugin)
259 elif plugin.name == 'rhodecode':
259 elif plugin.name == 'rhodecode':
260 valid_plugins.append(plugin)
260 valid_plugins.append(plugin)
261
261
262 # extend our choices if user has set a bound plugin which isn't enabled at the
262 # extend our choices if user has set a bound plugin which isn't enabled at the
263 # moment
263 # moment
264 extern_type = self.db_user.extern_type
264 extern_type = self.db_user.extern_type
265 if extern_type not in [x.uid for x in valid_plugins]:
265 if extern_type not in [x.uid for x in valid_plugins]:
266 try:
266 try:
267 plugin = authn_registry.get_plugin_by_uid(extern_type)
267 plugin = authn_registry.get_plugin_by_uid(extern_type)
268 if plugin:
268 if plugin:
269 valid_plugins.append(plugin)
269 valid_plugins.append(plugin)
270
270
271 except Exception:
271 except Exception:
272 log.exception(
272 log.exception(
273 'Could not extend user plugins with `{}`'.format(extern_type))
273 'Could not extend user plugins with `{}`'.format(extern_type))
274 return valid_plugins
274 return valid_plugins
275
275
276 def load_default_context(self):
276 def load_default_context(self):
277 req = self.request
277 req = self.request
278
278
279 c = self._get_local_tmpl_context()
279 c = self._get_local_tmpl_context()
280 c.allow_scoped_tokens = self.ALLOW_SCOPED_TOKENS
280 c.allow_scoped_tokens = self.ALLOW_SCOPED_TOKENS
281 c.allowed_languages = [
281 c.allowed_languages = [
282 ('en', 'English (en)'),
282 ('en', 'English (en)'),
283 ('de', 'German (de)'),
283 ('de', 'German (de)'),
284 ('fr', 'French (fr)'),
284 ('fr', 'French (fr)'),
285 ('it', 'Italian (it)'),
285 ('it', 'Italian (it)'),
286 ('ja', 'Japanese (ja)'),
286 ('ja', 'Japanese (ja)'),
287 ('pl', 'Polish (pl)'),
287 ('pl', 'Polish (pl)'),
288 ('pt', 'Portuguese (pt)'),
288 ('pt', 'Portuguese (pt)'),
289 ('ru', 'Russian (ru)'),
289 ('ru', 'Russian (ru)'),
290 ('zh', 'Chinese (zh)'),
290 ('zh', 'Chinese (zh)'),
291 ]
291 ]
292
292
293 c.allowed_extern_types = [
293 c.allowed_extern_types = [
294 (x.uid, x.get_display_name()) for x in self.get_auth_plugins()
294 (x.uid, x.get_display_name()) for x in self.get_auth_plugins()
295 ]
295 ]
296
296
297 c.available_permissions = req.registry.settings['available_permissions']
297 c.available_permissions = req.registry.settings['available_permissions']
298 PermissionModel().set_global_permission_choices(
298 PermissionModel().set_global_permission_choices(
299 c, gettext_translator=req.translate)
299 c, gettext_translator=req.translate)
300
300
301 return c
301 return c
302
302
303 @LoginRequired()
303 @LoginRequired()
304 @HasPermissionAllDecorator('hg.admin')
304 @HasPermissionAllDecorator('hg.admin')
305 @CSRFRequired()
305 @CSRFRequired()
306 @view_config(
306 @view_config(
307 route_name='user_update', request_method='POST',
307 route_name='user_update', request_method='POST',
308 renderer='rhodecode:templates/admin/users/user_edit.mako')
308 renderer='rhodecode:templates/admin/users/user_edit.mako')
309 def user_update(self):
309 def user_update(self):
310 _ = self.request.translate
310 _ = self.request.translate
311 c = self.load_default_context()
311 c = self.load_default_context()
312
312
313 user_id = self.db_user_id
313 user_id = self.db_user_id
314 c.user = self.db_user
314 c.user = self.db_user
315
315
316 c.active = 'profile'
316 c.active = 'profile'
317 c.extern_type = c.user.extern_type
317 c.extern_type = c.user.extern_type
318 c.extern_name = c.user.extern_name
318 c.extern_name = c.user.extern_name
319 c.perm_user = c.user.AuthUser(ip_addr=self.request.remote_addr)
319 c.perm_user = c.user.AuthUser(ip_addr=self.request.remote_addr)
320 available_languages = [x[0] for x in c.allowed_languages]
320 available_languages = [x[0] for x in c.allowed_languages]
321 _form = UserForm(self.request.translate, edit=True,
321 _form = UserForm(self.request.translate, edit=True,
322 available_languages=available_languages,
322 available_languages=available_languages,
323 old_data={'user_id': user_id,
323 old_data={'user_id': user_id,
324 'email': c.user.email})()
324 'email': c.user.email})()
325 form_result = {}
325 form_result = {}
326 old_values = c.user.get_api_data()
326 old_values = c.user.get_api_data()
327 try:
327 try:
328 form_result = _form.to_python(dict(self.request.POST))
328 form_result = _form.to_python(dict(self.request.POST))
329 skip_attrs = ['extern_name']
329 skip_attrs = ['extern_name']
330 # TODO: plugin should define if username can be updated
330 # TODO: plugin should define if username can be updated
331 if c.extern_type != "rhodecode":
331 if c.extern_type != "rhodecode":
332 # forbid updating username for external accounts
332 # forbid updating username for external accounts
333 skip_attrs.append('username')
333 skip_attrs.append('username')
334
334
335 UserModel().update_user(
335 UserModel().update_user(
336 user_id, skip_attrs=skip_attrs, **form_result)
336 user_id, skip_attrs=skip_attrs, **form_result)
337
337
338 audit_logger.store_web(
338 audit_logger.store_web(
339 'user.edit', action_data={'old_data': old_values},
339 'user.edit', action_data={'old_data': old_values},
340 user=c.rhodecode_user)
340 user=c.rhodecode_user)
341
341
342 Session().commit()
342 Session().commit()
343 h.flash(_('User updated successfully'), category='success')
343 h.flash(_('User updated successfully'), category='success')
344 except formencode.Invalid as errors:
344 except formencode.Invalid as errors:
345 data = render(
345 data = render(
346 'rhodecode:templates/admin/users/user_edit.mako',
346 'rhodecode:templates/admin/users/user_edit.mako',
347 self._get_template_context(c), self.request)
347 self._get_template_context(c), self.request)
348 html = formencode.htmlfill.render(
348 html = formencode.htmlfill.render(
349 data,
349 data,
350 defaults=errors.value,
350 defaults=errors.value,
351 errors=errors.error_dict or {},
351 errors=errors.error_dict or {},
352 prefix_error=False,
352 prefix_error=False,
353 encoding="UTF-8",
353 encoding="UTF-8",
354 force_defaults=False
354 force_defaults=False
355 )
355 )
356 return Response(html)
356 return Response(html)
357 except UserCreationError as e:
357 except UserCreationError as e:
358 h.flash(e, 'error')
358 h.flash(e, 'error')
359 except Exception:
359 except Exception:
360 log.exception("Exception updating user")
360 log.exception("Exception updating user")
361 h.flash(_('Error occurred during update of user %s')
361 h.flash(_('Error occurred during update of user %s')
362 % form_result.get('username'), category='error')
362 % form_result.get('username'), category='error')
363 raise HTTPFound(h.route_path('user_edit', user_id=user_id))
363 raise HTTPFound(h.route_path('user_edit', user_id=user_id))
364
364
365 @LoginRequired()
365 @LoginRequired()
366 @HasPermissionAllDecorator('hg.admin')
366 @HasPermissionAllDecorator('hg.admin')
367 @CSRFRequired()
367 @CSRFRequired()
368 @view_config(
368 @view_config(
369 route_name='user_delete', request_method='POST',
369 route_name='user_delete', request_method='POST',
370 renderer='rhodecode:templates/admin/users/user_edit.mako')
370 renderer='rhodecode:templates/admin/users/user_edit.mako')
371 def user_delete(self):
371 def user_delete(self):
372 _ = self.request.translate
372 _ = self.request.translate
373 c = self.load_default_context()
373 c = self.load_default_context()
374 c.user = self.db_user
374 c.user = self.db_user
375
375
376 _repos = c.user.repositories
376 _repos = c.user.repositories
377 _repo_groups = c.user.repository_groups
377 _repo_groups = c.user.repository_groups
378 _user_groups = c.user.user_groups
378 _user_groups = c.user.user_groups
379 _artifacts = c.user.artifacts
379
380
380 handle_repos = None
381 handle_repos = None
381 handle_repo_groups = None
382 handle_repo_groups = None
382 handle_user_groups = None
383 handle_user_groups = None
383 # dummy call for flash of handle
384 handle_artifacts = None
384 set_handle_flash_repos = lambda: None
385
385 set_handle_flash_repo_groups = lambda: None
386 # calls for flash of handle based on handle case detach or delete
386 set_handle_flash_user_groups = lambda: None
387 def set_handle_flash_repos():
388 handle = handle_repos
389 if handle == 'detach':
390 h.flash(_('Detached %s repositories') % len(_repos),
391 category='success')
392 elif handle == 'delete':
393 h.flash(_('Deleted %s repositories') % len(_repos),
394 category='success')
395
396 def set_handle_flash_repo_groups():
397 handle = handle_repo_groups
398 if handle == 'detach':
399 h.flash(_('Detached %s repository groups') % len(_repo_groups),
400 category='success')
401 elif handle == 'delete':
402 h.flash(_('Deleted %s repository groups') % len(_repo_groups),
403 category='success')
404
405 def set_handle_flash_user_groups():
406 handle = handle_user_groups
407 if handle == 'detach':
408 h.flash(_('Detached %s user groups') % len(_user_groups),
409 category='success')
410 elif handle == 'delete':
411 h.flash(_('Deleted %s user groups') % len(_user_groups),
412 category='success')
413
414 def set_handle_flash_artifacts():
415 handle = handle_artifacts
416 if handle == 'detach':
417 h.flash(_('Detached %s artifacts') % len(_artifacts),
418 category='success')
419 elif handle == 'delete':
420 h.flash(_('Deleted %s artifacts') % len(_artifacts),
421 category='success')
387
422
388 if _repos and self.request.POST.get('user_repos'):
423 if _repos and self.request.POST.get('user_repos'):
389 do = self.request.POST['user_repos']
424 handle_repos = self.request.POST['user_repos']
390 if do == 'detach':
391 handle_repos = 'detach'
392 set_handle_flash_repos = lambda: h.flash(
393 _('Detached %s repositories') % len(_repos),
394 category='success')
395 elif do == 'delete':
396 handle_repos = 'delete'
397 set_handle_flash_repos = lambda: h.flash(
398 _('Deleted %s repositories') % len(_repos),
399 category='success')
400
425
401 if _repo_groups and self.request.POST.get('user_repo_groups'):
426 if _repo_groups and self.request.POST.get('user_repo_groups'):
402 do = self.request.POST['user_repo_groups']
427 handle_repo_groups = self.request.POST['user_repo_groups']
403 if do == 'detach':
404 handle_repo_groups = 'detach'
405 set_handle_flash_repo_groups = lambda: h.flash(
406 _('Detached %s repository groups') % len(_repo_groups),
407 category='success')
408 elif do == 'delete':
409 handle_repo_groups = 'delete'
410 set_handle_flash_repo_groups = lambda: h.flash(
411 _('Deleted %s repository groups') % len(_repo_groups),
412 category='success')
413
428
414 if _user_groups and self.request.POST.get('user_user_groups'):
429 if _user_groups and self.request.POST.get('user_user_groups'):
415 do = self.request.POST['user_user_groups']
430 handle_user_groups = self.request.POST['user_user_groups']
416 if do == 'detach':
431
417 handle_user_groups = 'detach'
432 if _artifacts and self.request.POST.get('user_artifacts'):
418 set_handle_flash_user_groups = lambda: h.flash(
433 handle_artifacts = self.request.POST['user_artifacts']
419 _('Detached %s user groups') % len(_user_groups),
420 category='success')
421 elif do == 'delete':
422 handle_user_groups = 'delete'
423 set_handle_flash_user_groups = lambda: h.flash(
424 _('Deleted %s user groups') % len(_user_groups),
425 category='success')
426
434
427 old_values = c.user.get_api_data()
435 old_values = c.user.get_api_data()
436
428 try:
437 try:
429 UserModel().delete(c.user, handle_repos=handle_repos,
438 UserModel().delete(c.user, handle_repos=handle_repos,
430 handle_repo_groups=handle_repo_groups,
439 handle_repo_groups=handle_repo_groups,
431 handle_user_groups=handle_user_groups)
440 handle_user_groups=handle_user_groups,
441 handle_artifacts=handle_artifacts)
432
442
433 audit_logger.store_web(
443 audit_logger.store_web(
434 'user.delete', action_data={'old_data': old_values},
444 'user.delete', action_data={'old_data': old_values},
435 user=c.rhodecode_user)
445 user=c.rhodecode_user)
436
446
437 Session().commit()
447 Session().commit()
438 set_handle_flash_repos()
448 set_handle_flash_repos()
439 set_handle_flash_repo_groups()
449 set_handle_flash_repo_groups()
440 set_handle_flash_user_groups()
450 set_handle_flash_user_groups()
441 h.flash(_('Successfully deleted user'), category='success')
451 set_handle_flash_artifacts()
452 username = h.escape(old_values['username'])
453 h.flash(_('Successfully deleted user `{}`').format(username), category='success')
442 except (UserOwnsReposException, UserOwnsRepoGroupsException,
454 except (UserOwnsReposException, UserOwnsRepoGroupsException,
443 UserOwnsUserGroupsException, DefaultUserException) as e:
455 UserOwnsUserGroupsException, DefaultUserException) as e:
444 h.flash(e, category='warning')
456 h.flash(e, category='warning')
445 except Exception:
457 except Exception:
446 log.exception("Exception during deletion of user")
458 log.exception("Exception during deletion of user")
447 h.flash(_('An error occurred during deletion of user'),
459 h.flash(_('An error occurred during deletion of user'),
448 category='error')
460 category='error')
449 raise HTTPFound(h.route_path('users'))
461 raise HTTPFound(h.route_path('users'))
450
462
451 @LoginRequired()
463 @LoginRequired()
452 @HasPermissionAllDecorator('hg.admin')
464 @HasPermissionAllDecorator('hg.admin')
453 @view_config(
465 @view_config(
454 route_name='user_edit', request_method='GET',
466 route_name='user_edit', request_method='GET',
455 renderer='rhodecode:templates/admin/users/user_edit.mako')
467 renderer='rhodecode:templates/admin/users/user_edit.mako')
456 def user_edit(self):
468 def user_edit(self):
457 _ = self.request.translate
469 _ = self.request.translate
458 c = self.load_default_context()
470 c = self.load_default_context()
459 c.user = self.db_user
471 c.user = self.db_user
460
472
461 c.active = 'profile'
473 c.active = 'profile'
462 c.extern_type = c.user.extern_type
474 c.extern_type = c.user.extern_type
463 c.extern_name = c.user.extern_name
475 c.extern_name = c.user.extern_name
464 c.perm_user = c.user.AuthUser(ip_addr=self.request.remote_addr)
476 c.perm_user = c.user.AuthUser(ip_addr=self.request.remote_addr)
465
477
466 defaults = c.user.get_dict()
478 defaults = c.user.get_dict()
467 defaults.update({'language': c.user.user_data.get('language')})
479 defaults.update({'language': c.user.user_data.get('language')})
468
480
469 data = render(
481 data = render(
470 'rhodecode:templates/admin/users/user_edit.mako',
482 'rhodecode:templates/admin/users/user_edit.mako',
471 self._get_template_context(c), self.request)
483 self._get_template_context(c), self.request)
472 html = formencode.htmlfill.render(
484 html = formencode.htmlfill.render(
473 data,
485 data,
474 defaults=defaults,
486 defaults=defaults,
475 encoding="UTF-8",
487 encoding="UTF-8",
476 force_defaults=False
488 force_defaults=False
477 )
489 )
478 return Response(html)
490 return Response(html)
479
491
480 @LoginRequired()
492 @LoginRequired()
481 @HasPermissionAllDecorator('hg.admin')
493 @HasPermissionAllDecorator('hg.admin')
482 @view_config(
494 @view_config(
483 route_name='user_edit_advanced', request_method='GET',
495 route_name='user_edit_advanced', request_method='GET',
484 renderer='rhodecode:templates/admin/users/user_edit.mako')
496 renderer='rhodecode:templates/admin/users/user_edit.mako')
485 def user_edit_advanced(self):
497 def user_edit_advanced(self):
486 _ = self.request.translate
498 _ = self.request.translate
487 c = self.load_default_context()
499 c = self.load_default_context()
488
500
489 user_id = self.db_user_id
501 user_id = self.db_user_id
490 c.user = self.db_user
502 c.user = self.db_user
491
503
492 c.active = 'advanced'
504 c.active = 'advanced'
493 c.personal_repo_group = RepoGroup.get_user_personal_repo_group(user_id)
505 c.personal_repo_group = RepoGroup.get_user_personal_repo_group(user_id)
494 c.personal_repo_group_name = RepoGroupModel()\
506 c.personal_repo_group_name = RepoGroupModel()\
495 .get_personal_group_name(c.user)
507 .get_personal_group_name(c.user)
496
508
497 c.user_to_review_rules = sorted(
509 c.user_to_review_rules = sorted(
498 (x.user for x in c.user.user_review_rules),
510 (x.user for x in c.user.user_review_rules),
499 key=lambda u: u.username.lower())
511 key=lambda u: u.username.lower())
500
512
501 c.first_admin = User.get_first_super_admin()
513 c.first_admin = User.get_first_super_admin()
502 defaults = c.user.get_dict()
514 defaults = c.user.get_dict()
503
515
504 # Interim workaround if the user participated on any pull requests as a
516 # Interim workaround if the user participated on any pull requests as a
505 # reviewer.
517 # reviewer.
506 has_review = len(c.user.reviewer_pull_requests)
518 has_review = len(c.user.reviewer_pull_requests)
507 c.can_delete_user = not has_review
519 c.can_delete_user = not has_review
508 c.can_delete_user_message = ''
520 c.can_delete_user_message = ''
509 inactive_link = h.link_to(
521 inactive_link = h.link_to(
510 'inactive', h.route_path('user_edit', user_id=user_id, _anchor='active'))
522 'inactive', h.route_path('user_edit', user_id=user_id, _anchor='active'))
511 if has_review == 1:
523 if has_review == 1:
512 c.can_delete_user_message = h.literal(_(
524 c.can_delete_user_message = h.literal(_(
513 'The user participates as reviewer in {} pull request and '
525 'The user participates as reviewer in {} pull request and '
514 'cannot be deleted. \nYou can set the user to '
526 'cannot be deleted. \nYou can set the user to '
515 '"{}" instead of deleting it.').format(
527 '"{}" instead of deleting it.').format(
516 has_review, inactive_link))
528 has_review, inactive_link))
517 elif has_review:
529 elif has_review:
518 c.can_delete_user_message = h.literal(_(
530 c.can_delete_user_message = h.literal(_(
519 'The user participates as reviewer in {} pull requests and '
531 'The user participates as reviewer in {} pull requests and '
520 'cannot be deleted. \nYou can set the user to '
532 'cannot be deleted. \nYou can set the user to '
521 '"{}" instead of deleting it.').format(
533 '"{}" instead of deleting it.').format(
522 has_review, inactive_link))
534 has_review, inactive_link))
523
535
524 data = render(
536 data = render(
525 'rhodecode:templates/admin/users/user_edit.mako',
537 'rhodecode:templates/admin/users/user_edit.mako',
526 self._get_template_context(c), self.request)
538 self._get_template_context(c), self.request)
527 html = formencode.htmlfill.render(
539 html = formencode.htmlfill.render(
528 data,
540 data,
529 defaults=defaults,
541 defaults=defaults,
530 encoding="UTF-8",
542 encoding="UTF-8",
531 force_defaults=False
543 force_defaults=False
532 )
544 )
533 return Response(html)
545 return Response(html)
534
546
535 @LoginRequired()
547 @LoginRequired()
536 @HasPermissionAllDecorator('hg.admin')
548 @HasPermissionAllDecorator('hg.admin')
537 @view_config(
549 @view_config(
538 route_name='user_edit_global_perms', request_method='GET',
550 route_name='user_edit_global_perms', request_method='GET',
539 renderer='rhodecode:templates/admin/users/user_edit.mako')
551 renderer='rhodecode:templates/admin/users/user_edit.mako')
540 def user_edit_global_perms(self):
552 def user_edit_global_perms(self):
541 _ = self.request.translate
553 _ = self.request.translate
542 c = self.load_default_context()
554 c = self.load_default_context()
543 c.user = self.db_user
555 c.user = self.db_user
544
556
545 c.active = 'global_perms'
557 c.active = 'global_perms'
546
558
547 c.default_user = User.get_default_user()
559 c.default_user = User.get_default_user()
548 defaults = c.user.get_dict()
560 defaults = c.user.get_dict()
549 defaults.update(c.default_user.get_default_perms(suffix='_inherited'))
561 defaults.update(c.default_user.get_default_perms(suffix='_inherited'))
550 defaults.update(c.default_user.get_default_perms())
562 defaults.update(c.default_user.get_default_perms())
551 defaults.update(c.user.get_default_perms())
563 defaults.update(c.user.get_default_perms())
552
564
553 data = render(
565 data = render(
554 'rhodecode:templates/admin/users/user_edit.mako',
566 'rhodecode:templates/admin/users/user_edit.mako',
555 self._get_template_context(c), self.request)
567 self._get_template_context(c), self.request)
556 html = formencode.htmlfill.render(
568 html = formencode.htmlfill.render(
557 data,
569 data,
558 defaults=defaults,
570 defaults=defaults,
559 encoding="UTF-8",
571 encoding="UTF-8",
560 force_defaults=False
572 force_defaults=False
561 )
573 )
562 return Response(html)
574 return Response(html)
563
575
564 @LoginRequired()
576 @LoginRequired()
565 @HasPermissionAllDecorator('hg.admin')
577 @HasPermissionAllDecorator('hg.admin')
566 @CSRFRequired()
578 @CSRFRequired()
567 @view_config(
579 @view_config(
568 route_name='user_edit_global_perms_update', request_method='POST',
580 route_name='user_edit_global_perms_update', request_method='POST',
569 renderer='rhodecode:templates/admin/users/user_edit.mako')
581 renderer='rhodecode:templates/admin/users/user_edit.mako')
570 def user_edit_global_perms_update(self):
582 def user_edit_global_perms_update(self):
571 _ = self.request.translate
583 _ = self.request.translate
572 c = self.load_default_context()
584 c = self.load_default_context()
573
585
574 user_id = self.db_user_id
586 user_id = self.db_user_id
575 c.user = self.db_user
587 c.user = self.db_user
576
588
577 c.active = 'global_perms'
589 c.active = 'global_perms'
578 try:
590 try:
579 # first stage that verifies the checkbox
591 # first stage that verifies the checkbox
580 _form = UserIndividualPermissionsForm(self.request.translate)
592 _form = UserIndividualPermissionsForm(self.request.translate)
581 form_result = _form.to_python(dict(self.request.POST))
593 form_result = _form.to_python(dict(self.request.POST))
582 inherit_perms = form_result['inherit_default_permissions']
594 inherit_perms = form_result['inherit_default_permissions']
583 c.user.inherit_default_permissions = inherit_perms
595 c.user.inherit_default_permissions = inherit_perms
584 Session().add(c.user)
596 Session().add(c.user)
585
597
586 if not inherit_perms:
598 if not inherit_perms:
587 # only update the individual ones if we un check the flag
599 # only update the individual ones if we un check the flag
588 _form = UserPermissionsForm(
600 _form = UserPermissionsForm(
589 self.request.translate,
601 self.request.translate,
590 [x[0] for x in c.repo_create_choices],
602 [x[0] for x in c.repo_create_choices],
591 [x[0] for x in c.repo_create_on_write_choices],
603 [x[0] for x in c.repo_create_on_write_choices],
592 [x[0] for x in c.repo_group_create_choices],
604 [x[0] for x in c.repo_group_create_choices],
593 [x[0] for x in c.user_group_create_choices],
605 [x[0] for x in c.user_group_create_choices],
594 [x[0] for x in c.fork_choices],
606 [x[0] for x in c.fork_choices],
595 [x[0] for x in c.inherit_default_permission_choices])()
607 [x[0] for x in c.inherit_default_permission_choices])()
596
608
597 form_result = _form.to_python(dict(self.request.POST))
609 form_result = _form.to_python(dict(self.request.POST))
598 form_result.update({'perm_user_id': c.user.user_id})
610 form_result.update({'perm_user_id': c.user.user_id})
599
611
600 PermissionModel().update_user_permissions(form_result)
612 PermissionModel().update_user_permissions(form_result)
601
613
602 # TODO(marcink): implement global permissions
614 # TODO(marcink): implement global permissions
603 # audit_log.store_web('user.edit.permissions')
615 # audit_log.store_web('user.edit.permissions')
604
616
605 Session().commit()
617 Session().commit()
606
618
607 h.flash(_('User global permissions updated successfully'),
619 h.flash(_('User global permissions updated successfully'),
608 category='success')
620 category='success')
609
621
610 except formencode.Invalid as errors:
622 except formencode.Invalid as errors:
611 data = render(
623 data = render(
612 'rhodecode:templates/admin/users/user_edit.mako',
624 'rhodecode:templates/admin/users/user_edit.mako',
613 self._get_template_context(c), self.request)
625 self._get_template_context(c), self.request)
614 html = formencode.htmlfill.render(
626 html = formencode.htmlfill.render(
615 data,
627 data,
616 defaults=errors.value,
628 defaults=errors.value,
617 errors=errors.error_dict or {},
629 errors=errors.error_dict or {},
618 prefix_error=False,
630 prefix_error=False,
619 encoding="UTF-8",
631 encoding="UTF-8",
620 force_defaults=False
632 force_defaults=False
621 )
633 )
622 return Response(html)
634 return Response(html)
623 except Exception:
635 except Exception:
624 log.exception("Exception during permissions saving")
636 log.exception("Exception during permissions saving")
625 h.flash(_('An error occurred during permissions saving'),
637 h.flash(_('An error occurred during permissions saving'),
626 category='error')
638 category='error')
627
639
628 affected_user_ids = [user_id]
640 affected_user_ids = [user_id]
629 PermissionModel().trigger_permission_flush(affected_user_ids)
641 PermissionModel().trigger_permission_flush(affected_user_ids)
630 raise HTTPFound(h.route_path('user_edit_global_perms', user_id=user_id))
642 raise HTTPFound(h.route_path('user_edit_global_perms', user_id=user_id))
631
643
632 @LoginRequired()
644 @LoginRequired()
633 @HasPermissionAllDecorator('hg.admin')
645 @HasPermissionAllDecorator('hg.admin')
634 @CSRFRequired()
646 @CSRFRequired()
635 @view_config(
647 @view_config(
636 route_name='user_enable_force_password_reset', request_method='POST',
648 route_name='user_enable_force_password_reset', request_method='POST',
637 renderer='rhodecode:templates/admin/users/user_edit.mako')
649 renderer='rhodecode:templates/admin/users/user_edit.mako')
638 def user_enable_force_password_reset(self):
650 def user_enable_force_password_reset(self):
639 _ = self.request.translate
651 _ = self.request.translate
640 c = self.load_default_context()
652 c = self.load_default_context()
641
653
642 user_id = self.db_user_id
654 user_id = self.db_user_id
643 c.user = self.db_user
655 c.user = self.db_user
644
656
645 try:
657 try:
646 c.user.update_userdata(force_password_change=True)
658 c.user.update_userdata(force_password_change=True)
647
659
648 msg = _('Force password change enabled for user')
660 msg = _('Force password change enabled for user')
649 audit_logger.store_web('user.edit.password_reset.enabled',
661 audit_logger.store_web('user.edit.password_reset.enabled',
650 user=c.rhodecode_user)
662 user=c.rhodecode_user)
651
663
652 Session().commit()
664 Session().commit()
653 h.flash(msg, category='success')
665 h.flash(msg, category='success')
654 except Exception:
666 except Exception:
655 log.exception("Exception during password reset for user")
667 log.exception("Exception during password reset for user")
656 h.flash(_('An error occurred during password reset for user'),
668 h.flash(_('An error occurred during password reset for user'),
657 category='error')
669 category='error')
658
670
659 raise HTTPFound(h.route_path('user_edit_advanced', user_id=user_id))
671 raise HTTPFound(h.route_path('user_edit_advanced', user_id=user_id))
660
672
661 @LoginRequired()
673 @LoginRequired()
662 @HasPermissionAllDecorator('hg.admin')
674 @HasPermissionAllDecorator('hg.admin')
663 @CSRFRequired()
675 @CSRFRequired()
664 @view_config(
676 @view_config(
665 route_name='user_disable_force_password_reset', request_method='POST',
677 route_name='user_disable_force_password_reset', request_method='POST',
666 renderer='rhodecode:templates/admin/users/user_edit.mako')
678 renderer='rhodecode:templates/admin/users/user_edit.mako')
667 def user_disable_force_password_reset(self):
679 def user_disable_force_password_reset(self):
668 _ = self.request.translate
680 _ = self.request.translate
669 c = self.load_default_context()
681 c = self.load_default_context()
670
682
671 user_id = self.db_user_id
683 user_id = self.db_user_id
672 c.user = self.db_user
684 c.user = self.db_user
673
685
674 try:
686 try:
675 c.user.update_userdata(force_password_change=False)
687 c.user.update_userdata(force_password_change=False)
676
688
677 msg = _('Force password change disabled for user')
689 msg = _('Force password change disabled for user')
678 audit_logger.store_web(
690 audit_logger.store_web(
679 'user.edit.password_reset.disabled',
691 'user.edit.password_reset.disabled',
680 user=c.rhodecode_user)
692 user=c.rhodecode_user)
681
693
682 Session().commit()
694 Session().commit()
683 h.flash(msg, category='success')
695 h.flash(msg, category='success')
684 except Exception:
696 except Exception:
685 log.exception("Exception during password reset for user")
697 log.exception("Exception during password reset for user")
686 h.flash(_('An error occurred during password reset for user'),
698 h.flash(_('An error occurred during password reset for user'),
687 category='error')
699 category='error')
688
700
689 raise HTTPFound(h.route_path('user_edit_advanced', user_id=user_id))
701 raise HTTPFound(h.route_path('user_edit_advanced', user_id=user_id))
690
702
691 @LoginRequired()
703 @LoginRequired()
692 @HasPermissionAllDecorator('hg.admin')
704 @HasPermissionAllDecorator('hg.admin')
693 @CSRFRequired()
705 @CSRFRequired()
694 @view_config(
706 @view_config(
695 route_name='user_create_personal_repo_group', request_method='POST',
707 route_name='user_create_personal_repo_group', request_method='POST',
696 renderer='rhodecode:templates/admin/users/user_edit.mako')
708 renderer='rhodecode:templates/admin/users/user_edit.mako')
697 def user_create_personal_repo_group(self):
709 def user_create_personal_repo_group(self):
698 """
710 """
699 Create personal repository group for this user
711 Create personal repository group for this user
700 """
712 """
701 from rhodecode.model.repo_group import RepoGroupModel
713 from rhodecode.model.repo_group import RepoGroupModel
702
714
703 _ = self.request.translate
715 _ = self.request.translate
704 c = self.load_default_context()
716 c = self.load_default_context()
705
717
706 user_id = self.db_user_id
718 user_id = self.db_user_id
707 c.user = self.db_user
719 c.user = self.db_user
708
720
709 personal_repo_group = RepoGroup.get_user_personal_repo_group(
721 personal_repo_group = RepoGroup.get_user_personal_repo_group(
710 c.user.user_id)
722 c.user.user_id)
711 if personal_repo_group:
723 if personal_repo_group:
712 raise HTTPFound(h.route_path('user_edit_advanced', user_id=user_id))
724 raise HTTPFound(h.route_path('user_edit_advanced', user_id=user_id))
713
725
714 personal_repo_group_name = RepoGroupModel().get_personal_group_name(c.user)
726 personal_repo_group_name = RepoGroupModel().get_personal_group_name(c.user)
715 named_personal_group = RepoGroup.get_by_group_name(
727 named_personal_group = RepoGroup.get_by_group_name(
716 personal_repo_group_name)
728 personal_repo_group_name)
717 try:
729 try:
718
730
719 if named_personal_group and named_personal_group.user_id == c.user.user_id:
731 if named_personal_group and named_personal_group.user_id == c.user.user_id:
720 # migrate the same named group, and mark it as personal
732 # migrate the same named group, and mark it as personal
721 named_personal_group.personal = True
733 named_personal_group.personal = True
722 Session().add(named_personal_group)
734 Session().add(named_personal_group)
723 Session().commit()
735 Session().commit()
724 msg = _('Linked repository group `%s` as personal' % (
736 msg = _('Linked repository group `%s` as personal' % (
725 personal_repo_group_name,))
737 personal_repo_group_name,))
726 h.flash(msg, category='success')
738 h.flash(msg, category='success')
727 elif not named_personal_group:
739 elif not named_personal_group:
728 RepoGroupModel().create_personal_repo_group(c.user)
740 RepoGroupModel().create_personal_repo_group(c.user)
729
741
730 msg = _('Created repository group `%s`' % (
742 msg = _('Created repository group `%s`' % (
731 personal_repo_group_name,))
743 personal_repo_group_name,))
732 h.flash(msg, category='success')
744 h.flash(msg, category='success')
733 else:
745 else:
734 msg = _('Repository group `%s` is already taken' % (
746 msg = _('Repository group `%s` is already taken' % (
735 personal_repo_group_name,))
747 personal_repo_group_name,))
736 h.flash(msg, category='warning')
748 h.flash(msg, category='warning')
737 except Exception:
749 except Exception:
738 log.exception("Exception during repository group creation")
750 log.exception("Exception during repository group creation")
739 msg = _(
751 msg = _(
740 'An error occurred during repository group creation for user')
752 'An error occurred during repository group creation for user')
741 h.flash(msg, category='error')
753 h.flash(msg, category='error')
742 Session().rollback()
754 Session().rollback()
743
755
744 raise HTTPFound(h.route_path('user_edit_advanced', user_id=user_id))
756 raise HTTPFound(h.route_path('user_edit_advanced', user_id=user_id))
745
757
746 @LoginRequired()
758 @LoginRequired()
747 @HasPermissionAllDecorator('hg.admin')
759 @HasPermissionAllDecorator('hg.admin')
748 @view_config(
760 @view_config(
749 route_name='edit_user_auth_tokens', request_method='GET',
761 route_name='edit_user_auth_tokens', request_method='GET',
750 renderer='rhodecode:templates/admin/users/user_edit.mako')
762 renderer='rhodecode:templates/admin/users/user_edit.mako')
751 def auth_tokens(self):
763 def auth_tokens(self):
752 _ = self.request.translate
764 _ = self.request.translate
753 c = self.load_default_context()
765 c = self.load_default_context()
754 c.user = self.db_user
766 c.user = self.db_user
755
767
756 c.active = 'auth_tokens'
768 c.active = 'auth_tokens'
757
769
758 c.lifetime_values = AuthTokenModel.get_lifetime_values(translator=_)
770 c.lifetime_values = AuthTokenModel.get_lifetime_values(translator=_)
759 c.role_values = [
771 c.role_values = [
760 (x, AuthTokenModel.cls._get_role_name(x))
772 (x, AuthTokenModel.cls._get_role_name(x))
761 for x in AuthTokenModel.cls.ROLES]
773 for x in AuthTokenModel.cls.ROLES]
762 c.role_options = [(c.role_values, _("Role"))]
774 c.role_options = [(c.role_values, _("Role"))]
763 c.user_auth_tokens = AuthTokenModel().get_auth_tokens(
775 c.user_auth_tokens = AuthTokenModel().get_auth_tokens(
764 c.user.user_id, show_expired=True)
776 c.user.user_id, show_expired=True)
765 c.role_vcs = AuthTokenModel.cls.ROLE_VCS
777 c.role_vcs = AuthTokenModel.cls.ROLE_VCS
766 return self._get_template_context(c)
778 return self._get_template_context(c)
767
779
768 def maybe_attach_token_scope(self, token):
780 def maybe_attach_token_scope(self, token):
769 # implemented in EE edition
781 # implemented in EE edition
770 pass
782 pass
771
783
772 @LoginRequired()
784 @LoginRequired()
773 @HasPermissionAllDecorator('hg.admin')
785 @HasPermissionAllDecorator('hg.admin')
774 @CSRFRequired()
786 @CSRFRequired()
775 @view_config(
787 @view_config(
776 route_name='edit_user_auth_tokens_add', request_method='POST')
788 route_name='edit_user_auth_tokens_add', request_method='POST')
777 def auth_tokens_add(self):
789 def auth_tokens_add(self):
778 _ = self.request.translate
790 _ = self.request.translate
779 c = self.load_default_context()
791 c = self.load_default_context()
780
792
781 user_id = self.db_user_id
793 user_id = self.db_user_id
782 c.user = self.db_user
794 c.user = self.db_user
783
795
784 user_data = c.user.get_api_data()
796 user_data = c.user.get_api_data()
785 lifetime = safe_int(self.request.POST.get('lifetime'), -1)
797 lifetime = safe_int(self.request.POST.get('lifetime'), -1)
786 description = self.request.POST.get('description')
798 description = self.request.POST.get('description')
787 role = self.request.POST.get('role')
799 role = self.request.POST.get('role')
788
800
789 token = UserModel().add_auth_token(
801 token = UserModel().add_auth_token(
790 user=c.user.user_id,
802 user=c.user.user_id,
791 lifetime_minutes=lifetime, role=role, description=description,
803 lifetime_minutes=lifetime, role=role, description=description,
792 scope_callback=self.maybe_attach_token_scope)
804 scope_callback=self.maybe_attach_token_scope)
793 token_data = token.get_api_data()
805 token_data = token.get_api_data()
794
806
795 audit_logger.store_web(
807 audit_logger.store_web(
796 'user.edit.token.add', action_data={
808 'user.edit.token.add', action_data={
797 'data': {'token': token_data, 'user': user_data}},
809 'data': {'token': token_data, 'user': user_data}},
798 user=self._rhodecode_user, )
810 user=self._rhodecode_user, )
799 Session().commit()
811 Session().commit()
800
812
801 h.flash(_("Auth token successfully created"), category='success')
813 h.flash(_("Auth token successfully created"), category='success')
802 return HTTPFound(h.route_path('edit_user_auth_tokens', user_id=user_id))
814 return HTTPFound(h.route_path('edit_user_auth_tokens', user_id=user_id))
803
815
804 @LoginRequired()
816 @LoginRequired()
805 @HasPermissionAllDecorator('hg.admin')
817 @HasPermissionAllDecorator('hg.admin')
806 @CSRFRequired()
818 @CSRFRequired()
807 @view_config(
819 @view_config(
808 route_name='edit_user_auth_tokens_delete', request_method='POST')
820 route_name='edit_user_auth_tokens_delete', request_method='POST')
809 def auth_tokens_delete(self):
821 def auth_tokens_delete(self):
810 _ = self.request.translate
822 _ = self.request.translate
811 c = self.load_default_context()
823 c = self.load_default_context()
812
824
813 user_id = self.db_user_id
825 user_id = self.db_user_id
814 c.user = self.db_user
826 c.user = self.db_user
815
827
816 user_data = c.user.get_api_data()
828 user_data = c.user.get_api_data()
817
829
818 del_auth_token = self.request.POST.get('del_auth_token')
830 del_auth_token = self.request.POST.get('del_auth_token')
819
831
820 if del_auth_token:
832 if del_auth_token:
821 token = UserApiKeys.get_or_404(del_auth_token)
833 token = UserApiKeys.get_or_404(del_auth_token)
822 token_data = token.get_api_data()
834 token_data = token.get_api_data()
823
835
824 AuthTokenModel().delete(del_auth_token, c.user.user_id)
836 AuthTokenModel().delete(del_auth_token, c.user.user_id)
825 audit_logger.store_web(
837 audit_logger.store_web(
826 'user.edit.token.delete', action_data={
838 'user.edit.token.delete', action_data={
827 'data': {'token': token_data, 'user': user_data}},
839 'data': {'token': token_data, 'user': user_data}},
828 user=self._rhodecode_user,)
840 user=self._rhodecode_user,)
829 Session().commit()
841 Session().commit()
830 h.flash(_("Auth token successfully deleted"), category='success')
842 h.flash(_("Auth token successfully deleted"), category='success')
831
843
832 return HTTPFound(h.route_path('edit_user_auth_tokens', user_id=user_id))
844 return HTTPFound(h.route_path('edit_user_auth_tokens', user_id=user_id))
833
845
834 @LoginRequired()
846 @LoginRequired()
835 @HasPermissionAllDecorator('hg.admin')
847 @HasPermissionAllDecorator('hg.admin')
836 @view_config(
848 @view_config(
837 route_name='edit_user_ssh_keys', request_method='GET',
849 route_name='edit_user_ssh_keys', request_method='GET',
838 renderer='rhodecode:templates/admin/users/user_edit.mako')
850 renderer='rhodecode:templates/admin/users/user_edit.mako')
839 def ssh_keys(self):
851 def ssh_keys(self):
840 _ = self.request.translate
852 _ = self.request.translate
841 c = self.load_default_context()
853 c = self.load_default_context()
842 c.user = self.db_user
854 c.user = self.db_user
843
855
844 c.active = 'ssh_keys'
856 c.active = 'ssh_keys'
845 c.default_key = self.request.GET.get('default_key')
857 c.default_key = self.request.GET.get('default_key')
846 c.user_ssh_keys = SshKeyModel().get_ssh_keys(c.user.user_id)
858 c.user_ssh_keys = SshKeyModel().get_ssh_keys(c.user.user_id)
847 return self._get_template_context(c)
859 return self._get_template_context(c)
848
860
849 @LoginRequired()
861 @LoginRequired()
850 @HasPermissionAllDecorator('hg.admin')
862 @HasPermissionAllDecorator('hg.admin')
851 @view_config(
863 @view_config(
852 route_name='edit_user_ssh_keys_generate_keypair', request_method='GET',
864 route_name='edit_user_ssh_keys_generate_keypair', request_method='GET',
853 renderer='rhodecode:templates/admin/users/user_edit.mako')
865 renderer='rhodecode:templates/admin/users/user_edit.mako')
854 def ssh_keys_generate_keypair(self):
866 def ssh_keys_generate_keypair(self):
855 _ = self.request.translate
867 _ = self.request.translate
856 c = self.load_default_context()
868 c = self.load_default_context()
857
869
858 c.user = self.db_user
870 c.user = self.db_user
859
871
860 c.active = 'ssh_keys_generate'
872 c.active = 'ssh_keys_generate'
861 comment = 'RhodeCode-SSH {}'.format(c.user.email or '')
873 comment = 'RhodeCode-SSH {}'.format(c.user.email or '')
862 c.private, c.public = SshKeyModel().generate_keypair(comment=comment)
874 c.private, c.public = SshKeyModel().generate_keypair(comment=comment)
863
875
864 return self._get_template_context(c)
876 return self._get_template_context(c)
865
877
866 @LoginRequired()
878 @LoginRequired()
867 @HasPermissionAllDecorator('hg.admin')
879 @HasPermissionAllDecorator('hg.admin')
868 @CSRFRequired()
880 @CSRFRequired()
869 @view_config(
881 @view_config(
870 route_name='edit_user_ssh_keys_add', request_method='POST')
882 route_name='edit_user_ssh_keys_add', request_method='POST')
871 def ssh_keys_add(self):
883 def ssh_keys_add(self):
872 _ = self.request.translate
884 _ = self.request.translate
873 c = self.load_default_context()
885 c = self.load_default_context()
874
886
875 user_id = self.db_user_id
887 user_id = self.db_user_id
876 c.user = self.db_user
888 c.user = self.db_user
877
889
878 user_data = c.user.get_api_data()
890 user_data = c.user.get_api_data()
879 key_data = self.request.POST.get('key_data')
891 key_data = self.request.POST.get('key_data')
880 description = self.request.POST.get('description')
892 description = self.request.POST.get('description')
881
893
882 fingerprint = 'unknown'
894 fingerprint = 'unknown'
883 try:
895 try:
884 if not key_data:
896 if not key_data:
885 raise ValueError('Please add a valid public key')
897 raise ValueError('Please add a valid public key')
886
898
887 key = SshKeyModel().parse_key(key_data.strip())
899 key = SshKeyModel().parse_key(key_data.strip())
888 fingerprint = key.hash_md5()
900 fingerprint = key.hash_md5()
889
901
890 ssh_key = SshKeyModel().create(
902 ssh_key = SshKeyModel().create(
891 c.user.user_id, fingerprint, key.keydata, description)
903 c.user.user_id, fingerprint, key.keydata, description)
892 ssh_key_data = ssh_key.get_api_data()
904 ssh_key_data = ssh_key.get_api_data()
893
905
894 audit_logger.store_web(
906 audit_logger.store_web(
895 'user.edit.ssh_key.add', action_data={
907 'user.edit.ssh_key.add', action_data={
896 'data': {'ssh_key': ssh_key_data, 'user': user_data}},
908 'data': {'ssh_key': ssh_key_data, 'user': user_data}},
897 user=self._rhodecode_user, )
909 user=self._rhodecode_user, )
898 Session().commit()
910 Session().commit()
899
911
900 # Trigger an event on change of keys.
912 # Trigger an event on change of keys.
901 trigger(SshKeyFileChangeEvent(), self.request.registry)
913 trigger(SshKeyFileChangeEvent(), self.request.registry)
902
914
903 h.flash(_("Ssh Key successfully created"), category='success')
915 h.flash(_("Ssh Key successfully created"), category='success')
904
916
905 except IntegrityError:
917 except IntegrityError:
906 log.exception("Exception during ssh key saving")
918 log.exception("Exception during ssh key saving")
907 err = 'Such key with fingerprint `{}` already exists, ' \
919 err = 'Such key with fingerprint `{}` already exists, ' \
908 'please use a different one'.format(fingerprint)
920 'please use a different one'.format(fingerprint)
909 h.flash(_('An error occurred during ssh key saving: {}').format(err),
921 h.flash(_('An error occurred during ssh key saving: {}').format(err),
910 category='error')
922 category='error')
911 except Exception as e:
923 except Exception as e:
912 log.exception("Exception during ssh key saving")
924 log.exception("Exception during ssh key saving")
913 h.flash(_('An error occurred during ssh key saving: {}').format(e),
925 h.flash(_('An error occurred during ssh key saving: {}').format(e),
914 category='error')
926 category='error')
915
927
916 return HTTPFound(
928 return HTTPFound(
917 h.route_path('edit_user_ssh_keys', user_id=user_id))
929 h.route_path('edit_user_ssh_keys', user_id=user_id))
918
930
919 @LoginRequired()
931 @LoginRequired()
920 @HasPermissionAllDecorator('hg.admin')
932 @HasPermissionAllDecorator('hg.admin')
921 @CSRFRequired()
933 @CSRFRequired()
922 @view_config(
934 @view_config(
923 route_name='edit_user_ssh_keys_delete', request_method='POST')
935 route_name='edit_user_ssh_keys_delete', request_method='POST')
924 def ssh_keys_delete(self):
936 def ssh_keys_delete(self):
925 _ = self.request.translate
937 _ = self.request.translate
926 c = self.load_default_context()
938 c = self.load_default_context()
927
939
928 user_id = self.db_user_id
940 user_id = self.db_user_id
929 c.user = self.db_user
941 c.user = self.db_user
930
942
931 user_data = c.user.get_api_data()
943 user_data = c.user.get_api_data()
932
944
933 del_ssh_key = self.request.POST.get('del_ssh_key')
945 del_ssh_key = self.request.POST.get('del_ssh_key')
934
946
935 if del_ssh_key:
947 if del_ssh_key:
936 ssh_key = UserSshKeys.get_or_404(del_ssh_key)
948 ssh_key = UserSshKeys.get_or_404(del_ssh_key)
937 ssh_key_data = ssh_key.get_api_data()
949 ssh_key_data = ssh_key.get_api_data()
938
950
939 SshKeyModel().delete(del_ssh_key, c.user.user_id)
951 SshKeyModel().delete(del_ssh_key, c.user.user_id)
940 audit_logger.store_web(
952 audit_logger.store_web(
941 'user.edit.ssh_key.delete', action_data={
953 'user.edit.ssh_key.delete', action_data={
942 'data': {'ssh_key': ssh_key_data, 'user': user_data}},
954 'data': {'ssh_key': ssh_key_data, 'user': user_data}},
943 user=self._rhodecode_user,)
955 user=self._rhodecode_user,)
944 Session().commit()
956 Session().commit()
945 # Trigger an event on change of keys.
957 # Trigger an event on change of keys.
946 trigger(SshKeyFileChangeEvent(), self.request.registry)
958 trigger(SshKeyFileChangeEvent(), self.request.registry)
947 h.flash(_("Ssh key successfully deleted"), category='success')
959 h.flash(_("Ssh key successfully deleted"), category='success')
948
960
949 return HTTPFound(h.route_path('edit_user_ssh_keys', user_id=user_id))
961 return HTTPFound(h.route_path('edit_user_ssh_keys', user_id=user_id))
950
962
951 @LoginRequired()
963 @LoginRequired()
952 @HasPermissionAllDecorator('hg.admin')
964 @HasPermissionAllDecorator('hg.admin')
953 @view_config(
965 @view_config(
954 route_name='edit_user_emails', request_method='GET',
966 route_name='edit_user_emails', request_method='GET',
955 renderer='rhodecode:templates/admin/users/user_edit.mako')
967 renderer='rhodecode:templates/admin/users/user_edit.mako')
956 def emails(self):
968 def emails(self):
957 _ = self.request.translate
969 _ = self.request.translate
958 c = self.load_default_context()
970 c = self.load_default_context()
959 c.user = self.db_user
971 c.user = self.db_user
960
972
961 c.active = 'emails'
973 c.active = 'emails'
962 c.user_email_map = UserEmailMap.query() \
974 c.user_email_map = UserEmailMap.query() \
963 .filter(UserEmailMap.user == c.user).all()
975 .filter(UserEmailMap.user == c.user).all()
964
976
965 return self._get_template_context(c)
977 return self._get_template_context(c)
966
978
967 @LoginRequired()
979 @LoginRequired()
968 @HasPermissionAllDecorator('hg.admin')
980 @HasPermissionAllDecorator('hg.admin')
969 @CSRFRequired()
981 @CSRFRequired()
970 @view_config(
982 @view_config(
971 route_name='edit_user_emails_add', request_method='POST')
983 route_name='edit_user_emails_add', request_method='POST')
972 def emails_add(self):
984 def emails_add(self):
973 _ = self.request.translate
985 _ = self.request.translate
974 c = self.load_default_context()
986 c = self.load_default_context()
975
987
976 user_id = self.db_user_id
988 user_id = self.db_user_id
977 c.user = self.db_user
989 c.user = self.db_user
978
990
979 email = self.request.POST.get('new_email')
991 email = self.request.POST.get('new_email')
980 user_data = c.user.get_api_data()
992 user_data = c.user.get_api_data()
981 try:
993 try:
982
994
983 form = UserExtraEmailForm(self.request.translate)()
995 form = UserExtraEmailForm(self.request.translate)()
984 data = form.to_python({'email': email})
996 data = form.to_python({'email': email})
985 email = data['email']
997 email = data['email']
986
998
987 UserModel().add_extra_email(c.user.user_id, email)
999 UserModel().add_extra_email(c.user.user_id, email)
988 audit_logger.store_web(
1000 audit_logger.store_web(
989 'user.edit.email.add',
1001 'user.edit.email.add',
990 action_data={'email': email, 'user': user_data},
1002 action_data={'email': email, 'user': user_data},
991 user=self._rhodecode_user)
1003 user=self._rhodecode_user)
992 Session().commit()
1004 Session().commit()
993 h.flash(_("Added new email address `%s` for user account") % email,
1005 h.flash(_("Added new email address `%s` for user account") % email,
994 category='success')
1006 category='success')
995 except formencode.Invalid as error:
1007 except formencode.Invalid as error:
996 h.flash(h.escape(error.error_dict['email']), category='error')
1008 h.flash(h.escape(error.error_dict['email']), category='error')
997 except IntegrityError:
1009 except IntegrityError:
998 log.warning("Email %s already exists", email)
1010 log.warning("Email %s already exists", email)
999 h.flash(_('Email `{}` is already registered for another user.').format(email),
1011 h.flash(_('Email `{}` is already registered for another user.').format(email),
1000 category='error')
1012 category='error')
1001 except Exception:
1013 except Exception:
1002 log.exception("Exception during email saving")
1014 log.exception("Exception during email saving")
1003 h.flash(_('An error occurred during email saving'),
1015 h.flash(_('An error occurred during email saving'),
1004 category='error')
1016 category='error')
1005 raise HTTPFound(h.route_path('edit_user_emails', user_id=user_id))
1017 raise HTTPFound(h.route_path('edit_user_emails', user_id=user_id))
1006
1018
1007 @LoginRequired()
1019 @LoginRequired()
1008 @HasPermissionAllDecorator('hg.admin')
1020 @HasPermissionAllDecorator('hg.admin')
1009 @CSRFRequired()
1021 @CSRFRequired()
1010 @view_config(
1022 @view_config(
1011 route_name='edit_user_emails_delete', request_method='POST')
1023 route_name='edit_user_emails_delete', request_method='POST')
1012 def emails_delete(self):
1024 def emails_delete(self):
1013 _ = self.request.translate
1025 _ = self.request.translate
1014 c = self.load_default_context()
1026 c = self.load_default_context()
1015
1027
1016 user_id = self.db_user_id
1028 user_id = self.db_user_id
1017 c.user = self.db_user
1029 c.user = self.db_user
1018
1030
1019 email_id = self.request.POST.get('del_email_id')
1031 email_id = self.request.POST.get('del_email_id')
1020 user_model = UserModel()
1032 user_model = UserModel()
1021
1033
1022 email = UserEmailMap.query().get(email_id).email
1034 email = UserEmailMap.query().get(email_id).email
1023 user_data = c.user.get_api_data()
1035 user_data = c.user.get_api_data()
1024 user_model.delete_extra_email(c.user.user_id, email_id)
1036 user_model.delete_extra_email(c.user.user_id, email_id)
1025 audit_logger.store_web(
1037 audit_logger.store_web(
1026 'user.edit.email.delete',
1038 'user.edit.email.delete',
1027 action_data={'email': email, 'user': user_data},
1039 action_data={'email': email, 'user': user_data},
1028 user=self._rhodecode_user)
1040 user=self._rhodecode_user)
1029 Session().commit()
1041 Session().commit()
1030 h.flash(_("Removed email address from user account"),
1042 h.flash(_("Removed email address from user account"),
1031 category='success')
1043 category='success')
1032 raise HTTPFound(h.route_path('edit_user_emails', user_id=user_id))
1044 raise HTTPFound(h.route_path('edit_user_emails', user_id=user_id))
1033
1045
1034 @LoginRequired()
1046 @LoginRequired()
1035 @HasPermissionAllDecorator('hg.admin')
1047 @HasPermissionAllDecorator('hg.admin')
1036 @view_config(
1048 @view_config(
1037 route_name='edit_user_ips', request_method='GET',
1049 route_name='edit_user_ips', request_method='GET',
1038 renderer='rhodecode:templates/admin/users/user_edit.mako')
1050 renderer='rhodecode:templates/admin/users/user_edit.mako')
1039 def ips(self):
1051 def ips(self):
1040 _ = self.request.translate
1052 _ = self.request.translate
1041 c = self.load_default_context()
1053 c = self.load_default_context()
1042 c.user = self.db_user
1054 c.user = self.db_user
1043
1055
1044 c.active = 'ips'
1056 c.active = 'ips'
1045 c.user_ip_map = UserIpMap.query() \
1057 c.user_ip_map = UserIpMap.query() \
1046 .filter(UserIpMap.user == c.user).all()
1058 .filter(UserIpMap.user == c.user).all()
1047
1059
1048 c.inherit_default_ips = c.user.inherit_default_permissions
1060 c.inherit_default_ips = c.user.inherit_default_permissions
1049 c.default_user_ip_map = UserIpMap.query() \
1061 c.default_user_ip_map = UserIpMap.query() \
1050 .filter(UserIpMap.user == User.get_default_user()).all()
1062 .filter(UserIpMap.user == User.get_default_user()).all()
1051
1063
1052 return self._get_template_context(c)
1064 return self._get_template_context(c)
1053
1065
1054 @LoginRequired()
1066 @LoginRequired()
1055 @HasPermissionAllDecorator('hg.admin')
1067 @HasPermissionAllDecorator('hg.admin')
1056 @CSRFRequired()
1068 @CSRFRequired()
1057 @view_config(
1069 @view_config(
1058 route_name='edit_user_ips_add', request_method='POST')
1070 route_name='edit_user_ips_add', request_method='POST')
1059 # NOTE(marcink): this view is allowed for default users, as we can
1071 # NOTE(marcink): this view is allowed for default users, as we can
1060 # edit their IP white list
1072 # edit their IP white list
1061 def ips_add(self):
1073 def ips_add(self):
1062 _ = self.request.translate
1074 _ = self.request.translate
1063 c = self.load_default_context()
1075 c = self.load_default_context()
1064
1076
1065 user_id = self.db_user_id
1077 user_id = self.db_user_id
1066 c.user = self.db_user
1078 c.user = self.db_user
1067
1079
1068 user_model = UserModel()
1080 user_model = UserModel()
1069 desc = self.request.POST.get('description')
1081 desc = self.request.POST.get('description')
1070 try:
1082 try:
1071 ip_list = user_model.parse_ip_range(
1083 ip_list = user_model.parse_ip_range(
1072 self.request.POST.get('new_ip'))
1084 self.request.POST.get('new_ip'))
1073 except Exception as e:
1085 except Exception as e:
1074 ip_list = []
1086 ip_list = []
1075 log.exception("Exception during ip saving")
1087 log.exception("Exception during ip saving")
1076 h.flash(_('An error occurred during ip saving:%s' % (e,)),
1088 h.flash(_('An error occurred during ip saving:%s' % (e,)),
1077 category='error')
1089 category='error')
1078 added = []
1090 added = []
1079 user_data = c.user.get_api_data()
1091 user_data = c.user.get_api_data()
1080 for ip in ip_list:
1092 for ip in ip_list:
1081 try:
1093 try:
1082 form = UserExtraIpForm(self.request.translate)()
1094 form = UserExtraIpForm(self.request.translate)()
1083 data = form.to_python({'ip': ip})
1095 data = form.to_python({'ip': ip})
1084 ip = data['ip']
1096 ip = data['ip']
1085
1097
1086 user_model.add_extra_ip(c.user.user_id, ip, desc)
1098 user_model.add_extra_ip(c.user.user_id, ip, desc)
1087 audit_logger.store_web(
1099 audit_logger.store_web(
1088 'user.edit.ip.add',
1100 'user.edit.ip.add',
1089 action_data={'ip': ip, 'user': user_data},
1101 action_data={'ip': ip, 'user': user_data},
1090 user=self._rhodecode_user)
1102 user=self._rhodecode_user)
1091 Session().commit()
1103 Session().commit()
1092 added.append(ip)
1104 added.append(ip)
1093 except formencode.Invalid as error:
1105 except formencode.Invalid as error:
1094 msg = error.error_dict['ip']
1106 msg = error.error_dict['ip']
1095 h.flash(msg, category='error')
1107 h.flash(msg, category='error')
1096 except Exception:
1108 except Exception:
1097 log.exception("Exception during ip saving")
1109 log.exception("Exception during ip saving")
1098 h.flash(_('An error occurred during ip saving'),
1110 h.flash(_('An error occurred during ip saving'),
1099 category='error')
1111 category='error')
1100 if added:
1112 if added:
1101 h.flash(
1113 h.flash(
1102 _("Added ips %s to user whitelist") % (', '.join(ip_list), ),
1114 _("Added ips %s to user whitelist") % (', '.join(ip_list), ),
1103 category='success')
1115 category='success')
1104 if 'default_user' in self.request.POST:
1116 if 'default_user' in self.request.POST:
1105 # case for editing global IP list we do it for 'DEFAULT' user
1117 # case for editing global IP list we do it for 'DEFAULT' user
1106 raise HTTPFound(h.route_path('admin_permissions_ips'))
1118 raise HTTPFound(h.route_path('admin_permissions_ips'))
1107 raise HTTPFound(h.route_path('edit_user_ips', user_id=user_id))
1119 raise HTTPFound(h.route_path('edit_user_ips', user_id=user_id))
1108
1120
1109 @LoginRequired()
1121 @LoginRequired()
1110 @HasPermissionAllDecorator('hg.admin')
1122 @HasPermissionAllDecorator('hg.admin')
1111 @CSRFRequired()
1123 @CSRFRequired()
1112 @view_config(
1124 @view_config(
1113 route_name='edit_user_ips_delete', request_method='POST')
1125 route_name='edit_user_ips_delete', request_method='POST')
1114 # NOTE(marcink): this view is allowed for default users, as we can
1126 # NOTE(marcink): this view is allowed for default users, as we can
1115 # edit their IP white list
1127 # edit their IP white list
1116 def ips_delete(self):
1128 def ips_delete(self):
1117 _ = self.request.translate
1129 _ = self.request.translate
1118 c = self.load_default_context()
1130 c = self.load_default_context()
1119
1131
1120 user_id = self.db_user_id
1132 user_id = self.db_user_id
1121 c.user = self.db_user
1133 c.user = self.db_user
1122
1134
1123 ip_id = self.request.POST.get('del_ip_id')
1135 ip_id = self.request.POST.get('del_ip_id')
1124 user_model = UserModel()
1136 user_model = UserModel()
1125 user_data = c.user.get_api_data()
1137 user_data = c.user.get_api_data()
1126 ip = UserIpMap.query().get(ip_id).ip_addr
1138 ip = UserIpMap.query().get(ip_id).ip_addr
1127 user_model.delete_extra_ip(c.user.user_id, ip_id)
1139 user_model.delete_extra_ip(c.user.user_id, ip_id)
1128 audit_logger.store_web(
1140 audit_logger.store_web(
1129 'user.edit.ip.delete', action_data={'ip': ip, 'user': user_data},
1141 'user.edit.ip.delete', action_data={'ip': ip, 'user': user_data},
1130 user=self._rhodecode_user)
1142 user=self._rhodecode_user)
1131 Session().commit()
1143 Session().commit()
1132 h.flash(_("Removed ip address from user whitelist"), category='success')
1144 h.flash(_("Removed ip address from user whitelist"), category='success')
1133
1145
1134 if 'default_user' in self.request.POST:
1146 if 'default_user' in self.request.POST:
1135 # case for editing global IP list we do it for 'DEFAULT' user
1147 # case for editing global IP list we do it for 'DEFAULT' user
1136 raise HTTPFound(h.route_path('admin_permissions_ips'))
1148 raise HTTPFound(h.route_path('admin_permissions_ips'))
1137 raise HTTPFound(h.route_path('edit_user_ips', user_id=user_id))
1149 raise HTTPFound(h.route_path('edit_user_ips', user_id=user_id))
1138
1150
1139 @LoginRequired()
1151 @LoginRequired()
1140 @HasPermissionAllDecorator('hg.admin')
1152 @HasPermissionAllDecorator('hg.admin')
1141 @view_config(
1153 @view_config(
1142 route_name='edit_user_groups_management', request_method='GET',
1154 route_name='edit_user_groups_management', request_method='GET',
1143 renderer='rhodecode:templates/admin/users/user_edit.mako')
1155 renderer='rhodecode:templates/admin/users/user_edit.mako')
1144 def groups_management(self):
1156 def groups_management(self):
1145 c = self.load_default_context()
1157 c = self.load_default_context()
1146 c.user = self.db_user
1158 c.user = self.db_user
1147 c.data = c.user.group_member
1159 c.data = c.user.group_member
1148
1160
1149 groups = [UserGroupModel.get_user_groups_as_dict(group.users_group)
1161 groups = [UserGroupModel.get_user_groups_as_dict(group.users_group)
1150 for group in c.user.group_member]
1162 for group in c.user.group_member]
1151 c.groups = json.dumps(groups)
1163 c.groups = json.dumps(groups)
1152 c.active = 'groups'
1164 c.active = 'groups'
1153
1165
1154 return self._get_template_context(c)
1166 return self._get_template_context(c)
1155
1167
1156 @LoginRequired()
1168 @LoginRequired()
1157 @HasPermissionAllDecorator('hg.admin')
1169 @HasPermissionAllDecorator('hg.admin')
1158 @CSRFRequired()
1170 @CSRFRequired()
1159 @view_config(
1171 @view_config(
1160 route_name='edit_user_groups_management_updates', request_method='POST')
1172 route_name='edit_user_groups_management_updates', request_method='POST')
1161 def groups_management_updates(self):
1173 def groups_management_updates(self):
1162 _ = self.request.translate
1174 _ = self.request.translate
1163 c = self.load_default_context()
1175 c = self.load_default_context()
1164
1176
1165 user_id = self.db_user_id
1177 user_id = self.db_user_id
1166 c.user = self.db_user
1178 c.user = self.db_user
1167
1179
1168 user_groups = set(self.request.POST.getall('users_group_id'))
1180 user_groups = set(self.request.POST.getall('users_group_id'))
1169 user_groups_objects = []
1181 user_groups_objects = []
1170
1182
1171 for ugid in user_groups:
1183 for ugid in user_groups:
1172 user_groups_objects.append(
1184 user_groups_objects.append(
1173 UserGroupModel().get_group(safe_int(ugid)))
1185 UserGroupModel().get_group(safe_int(ugid)))
1174 user_group_model = UserGroupModel()
1186 user_group_model = UserGroupModel()
1175 added_to_groups, removed_from_groups = \
1187 added_to_groups, removed_from_groups = \
1176 user_group_model.change_groups(c.user, user_groups_objects)
1188 user_group_model.change_groups(c.user, user_groups_objects)
1177
1189
1178 user_data = c.user.get_api_data()
1190 user_data = c.user.get_api_data()
1179 for user_group_id in added_to_groups:
1191 for user_group_id in added_to_groups:
1180 user_group = UserGroup.get(user_group_id)
1192 user_group = UserGroup.get(user_group_id)
1181 old_values = user_group.get_api_data()
1193 old_values = user_group.get_api_data()
1182 audit_logger.store_web(
1194 audit_logger.store_web(
1183 'user_group.edit.member.add',
1195 'user_group.edit.member.add',
1184 action_data={'user': user_data, 'old_data': old_values},
1196 action_data={'user': user_data, 'old_data': old_values},
1185 user=self._rhodecode_user)
1197 user=self._rhodecode_user)
1186
1198
1187 for user_group_id in removed_from_groups:
1199 for user_group_id in removed_from_groups:
1188 user_group = UserGroup.get(user_group_id)
1200 user_group = UserGroup.get(user_group_id)
1189 old_values = user_group.get_api_data()
1201 old_values = user_group.get_api_data()
1190 audit_logger.store_web(
1202 audit_logger.store_web(
1191 'user_group.edit.member.delete',
1203 'user_group.edit.member.delete',
1192 action_data={'user': user_data, 'old_data': old_values},
1204 action_data={'user': user_data, 'old_data': old_values},
1193 user=self._rhodecode_user)
1205 user=self._rhodecode_user)
1194
1206
1195 Session().commit()
1207 Session().commit()
1196 c.active = 'user_groups_management'
1208 c.active = 'user_groups_management'
1197 h.flash(_("Groups successfully changed"), category='success')
1209 h.flash(_("Groups successfully changed"), category='success')
1198
1210
1199 return HTTPFound(h.route_path(
1211 return HTTPFound(h.route_path(
1200 'edit_user_groups_management', user_id=user_id))
1212 'edit_user_groups_management', user_id=user_id))
1201
1213
1202 @LoginRequired()
1214 @LoginRequired()
1203 @HasPermissionAllDecorator('hg.admin')
1215 @HasPermissionAllDecorator('hg.admin')
1204 @view_config(
1216 @view_config(
1205 route_name='edit_user_audit_logs', request_method='GET',
1217 route_name='edit_user_audit_logs', request_method='GET',
1206 renderer='rhodecode:templates/admin/users/user_edit.mako')
1218 renderer='rhodecode:templates/admin/users/user_edit.mako')
1207 def user_audit_logs(self):
1219 def user_audit_logs(self):
1208 _ = self.request.translate
1220 _ = self.request.translate
1209 c = self.load_default_context()
1221 c = self.load_default_context()
1210 c.user = self.db_user
1222 c.user = self.db_user
1211
1223
1212 c.active = 'audit'
1224 c.active = 'audit'
1213
1225
1214 p = safe_int(self.request.GET.get('page', 1), 1)
1226 p = safe_int(self.request.GET.get('page', 1), 1)
1215
1227
1216 filter_term = self.request.GET.get('filter')
1228 filter_term = self.request.GET.get('filter')
1217 user_log = UserModel().get_user_log(c.user, filter_term)
1229 user_log = UserModel().get_user_log(c.user, filter_term)
1218
1230
1219 def url_generator(**kw):
1231 def url_generator(**kw):
1220 if filter_term:
1232 if filter_term:
1221 kw['filter'] = filter_term
1233 kw['filter'] = filter_term
1222 return self.request.current_route_path(_query=kw)
1234 return self.request.current_route_path(_query=kw)
1223
1235
1224 c.audit_logs = h.Page(
1236 c.audit_logs = h.Page(
1225 user_log, page=p, items_per_page=10, url=url_generator)
1237 user_log, page=p, items_per_page=10, url=url_generator)
1226 c.filter_term = filter_term
1238 c.filter_term = filter_term
1227 return self._get_template_context(c)
1239 return self._get_template_context(c)
1228
1240
1229 @LoginRequired()
1241 @LoginRequired()
1230 @HasPermissionAllDecorator('hg.admin')
1242 @HasPermissionAllDecorator('hg.admin')
1231 @view_config(
1243 @view_config(
1232 route_name='edit_user_audit_logs_download', request_method='GET',
1244 route_name='edit_user_audit_logs_download', request_method='GET',
1233 renderer='string')
1245 renderer='string')
1234 def user_audit_logs_download(self):
1246 def user_audit_logs_download(self):
1235 _ = self.request.translate
1247 _ = self.request.translate
1236 c = self.load_default_context()
1248 c = self.load_default_context()
1237 c.user = self.db_user
1249 c.user = self.db_user
1238
1250
1239 user_log = UserModel().get_user_log(c.user, filter_term=None)
1251 user_log = UserModel().get_user_log(c.user, filter_term=None)
1240
1252
1241 audit_log_data = {}
1253 audit_log_data = {}
1242 for entry in user_log:
1254 for entry in user_log:
1243 audit_log_data[entry.user_log_id] = entry.get_dict()
1255 audit_log_data[entry.user_log_id] = entry.get_dict()
1244
1256
1245 response = Response(json.dumps(audit_log_data, indent=4))
1257 response = Response(json.dumps(audit_log_data, indent=4))
1246 response.content_disposition = str(
1258 response.content_disposition = str(
1247 'attachment; filename=%s' % 'user_{}_audit_logs.json'.format(c.user.user_id))
1259 'attachment; filename=%s' % 'user_{}_audit_logs.json'.format(c.user.user_id))
1248 response.content_type = 'application/json'
1260 response.content_type = 'application/json'
1249
1261
1250 return response
1262 return response
1251
1263
1252 @LoginRequired()
1264 @LoginRequired()
1253 @HasPermissionAllDecorator('hg.admin')
1265 @HasPermissionAllDecorator('hg.admin')
1254 @view_config(
1266 @view_config(
1255 route_name='edit_user_perms_summary', request_method='GET',
1267 route_name='edit_user_perms_summary', request_method='GET',
1256 renderer='rhodecode:templates/admin/users/user_edit.mako')
1268 renderer='rhodecode:templates/admin/users/user_edit.mako')
1257 def user_perms_summary(self):
1269 def user_perms_summary(self):
1258 _ = self.request.translate
1270 _ = self.request.translate
1259 c = self.load_default_context()
1271 c = self.load_default_context()
1260 c.user = self.db_user
1272 c.user = self.db_user
1261
1273
1262 c.active = 'perms_summary'
1274 c.active = 'perms_summary'
1263 c.perm_user = c.user.AuthUser(ip_addr=self.request.remote_addr)
1275 c.perm_user = c.user.AuthUser(ip_addr=self.request.remote_addr)
1264
1276
1265 return self._get_template_context(c)
1277 return self._get_template_context(c)
1266
1278
1267 @LoginRequired()
1279 @LoginRequired()
1268 @HasPermissionAllDecorator('hg.admin')
1280 @HasPermissionAllDecorator('hg.admin')
1269 @view_config(
1281 @view_config(
1270 route_name='edit_user_perms_summary_json', request_method='GET',
1282 route_name='edit_user_perms_summary_json', request_method='GET',
1271 renderer='json_ext')
1283 renderer='json_ext')
1272 def user_perms_summary_json(self):
1284 def user_perms_summary_json(self):
1273 self.load_default_context()
1285 self.load_default_context()
1274 perm_user = self.db_user.AuthUser(ip_addr=self.request.remote_addr)
1286 perm_user = self.db_user.AuthUser(ip_addr=self.request.remote_addr)
1275
1287
1276 return perm_user.permissions
1288 return perm_user.permissions
1277
1289
1278 @LoginRequired()
1290 @LoginRequired()
1279 @HasPermissionAllDecorator('hg.admin')
1291 @HasPermissionAllDecorator('hg.admin')
1280 @view_config(
1292 @view_config(
1281 route_name='edit_user_caches', request_method='GET',
1293 route_name='edit_user_caches', request_method='GET',
1282 renderer='rhodecode:templates/admin/users/user_edit.mako')
1294 renderer='rhodecode:templates/admin/users/user_edit.mako')
1283 def user_caches(self):
1295 def user_caches(self):
1284 _ = self.request.translate
1296 _ = self.request.translate
1285 c = self.load_default_context()
1297 c = self.load_default_context()
1286 c.user = self.db_user
1298 c.user = self.db_user
1287
1299
1288 c.active = 'caches'
1300 c.active = 'caches'
1289 c.perm_user = c.user.AuthUser(ip_addr=self.request.remote_addr)
1301 c.perm_user = c.user.AuthUser(ip_addr=self.request.remote_addr)
1290
1302
1291 cache_namespace_uid = 'cache_user_auth.{}'.format(self.db_user.user_id)
1303 cache_namespace_uid = 'cache_user_auth.{}'.format(self.db_user.user_id)
1292 c.region = rc_cache.get_or_create_region('cache_perms', cache_namespace_uid)
1304 c.region = rc_cache.get_or_create_region('cache_perms', cache_namespace_uid)
1293 c.backend = c.region.backend
1305 c.backend = c.region.backend
1294 c.user_keys = sorted(c.region.backend.list_keys(prefix=cache_namespace_uid))
1306 c.user_keys = sorted(c.region.backend.list_keys(prefix=cache_namespace_uid))
1295
1307
1296 return self._get_template_context(c)
1308 return self._get_template_context(c)
1297
1309
1298 @LoginRequired()
1310 @LoginRequired()
1299 @HasPermissionAllDecorator('hg.admin')
1311 @HasPermissionAllDecorator('hg.admin')
1300 @CSRFRequired()
1312 @CSRFRequired()
1301 @view_config(
1313 @view_config(
1302 route_name='edit_user_caches_update', request_method='POST')
1314 route_name='edit_user_caches_update', request_method='POST')
1303 def user_caches_update(self):
1315 def user_caches_update(self):
1304 _ = self.request.translate
1316 _ = self.request.translate
1305 c = self.load_default_context()
1317 c = self.load_default_context()
1306 c.user = self.db_user
1318 c.user = self.db_user
1307
1319
1308 c.active = 'caches'
1320 c.active = 'caches'
1309 c.perm_user = c.user.AuthUser(ip_addr=self.request.remote_addr)
1321 c.perm_user = c.user.AuthUser(ip_addr=self.request.remote_addr)
1310
1322
1311 cache_namespace_uid = 'cache_user_auth.{}'.format(self.db_user.user_id)
1323 cache_namespace_uid = 'cache_user_auth.{}'.format(self.db_user.user_id)
1312 del_keys = rc_cache.clear_cache_namespace('cache_perms', cache_namespace_uid)
1324 del_keys = rc_cache.clear_cache_namespace('cache_perms', cache_namespace_uid)
1313
1325
1314 h.flash(_("Deleted {} cache keys").format(del_keys), category='success')
1326 h.flash(_("Deleted {} cache keys").format(del_keys), category='success')
1315
1327
1316 return HTTPFound(h.route_path(
1328 return HTTPFound(h.route_path(
1317 'edit_user_caches', user_id=c.user.user_id))
1329 'edit_user_caches', user_id=c.user.user_id))
@@ -1,171 +1,175 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2019 RhodeCode GmbH
3 # Copyright (C) 2010-2019 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 Set of custom exceptions used in RhodeCode
22 Set of custom exceptions used in RhodeCode
23 """
23 """
24
24
25 from webob.exc import HTTPClientError
25 from webob.exc import HTTPClientError
26 from pyramid.httpexceptions import HTTPBadGateway
26 from pyramid.httpexceptions import HTTPBadGateway
27
27
28
28
29 class LdapUsernameError(Exception):
29 class LdapUsernameError(Exception):
30 pass
30 pass
31
31
32
32
33 class LdapPasswordError(Exception):
33 class LdapPasswordError(Exception):
34 pass
34 pass
35
35
36
36
37 class LdapConnectionError(Exception):
37 class LdapConnectionError(Exception):
38 pass
38 pass
39
39
40
40
41 class LdapImportError(Exception):
41 class LdapImportError(Exception):
42 pass
42 pass
43
43
44
44
45 class DefaultUserException(Exception):
45 class DefaultUserException(Exception):
46 pass
46 pass
47
47
48
48
49 class UserOwnsReposException(Exception):
49 class UserOwnsReposException(Exception):
50 pass
50 pass
51
51
52
52
53 class UserOwnsRepoGroupsException(Exception):
53 class UserOwnsRepoGroupsException(Exception):
54 pass
54 pass
55
55
56
56
57 class UserOwnsUserGroupsException(Exception):
57 class UserOwnsUserGroupsException(Exception):
58 pass
58 pass
59
59
60
60
61 class UserOwnsArtifactsException(Exception):
62 pass
63
64
61 class UserGroupAssignedException(Exception):
65 class UserGroupAssignedException(Exception):
62 pass
66 pass
63
67
64
68
65 class StatusChangeOnClosedPullRequestError(Exception):
69 class StatusChangeOnClosedPullRequestError(Exception):
66 pass
70 pass
67
71
68
72
69 class AttachedForksError(Exception):
73 class AttachedForksError(Exception):
70 pass
74 pass
71
75
72
76
73 class AttachedPullRequestsError(Exception):
77 class AttachedPullRequestsError(Exception):
74 pass
78 pass
75
79
76
80
77 class RepoGroupAssignmentError(Exception):
81 class RepoGroupAssignmentError(Exception):
78 pass
82 pass
79
83
80
84
81 class NonRelativePathError(Exception):
85 class NonRelativePathError(Exception):
82 pass
86 pass
83
87
84
88
85 class HTTPRequirementError(HTTPClientError):
89 class HTTPRequirementError(HTTPClientError):
86 title = explanation = 'Repository Requirement Missing'
90 title = explanation = 'Repository Requirement Missing'
87 reason = None
91 reason = None
88
92
89 def __init__(self, message, *args, **kwargs):
93 def __init__(self, message, *args, **kwargs):
90 self.title = self.explanation = message
94 self.title = self.explanation = message
91 super(HTTPRequirementError, self).__init__(*args, **kwargs)
95 super(HTTPRequirementError, self).__init__(*args, **kwargs)
92 self.args = (message, )
96 self.args = (message, )
93
97
94
98
95 class HTTPLockedRC(HTTPClientError):
99 class HTTPLockedRC(HTTPClientError):
96 """
100 """
97 Special Exception For locked Repos in RhodeCode, the return code can
101 Special Exception For locked Repos in RhodeCode, the return code can
98 be overwritten by _code keyword argument passed into constructors
102 be overwritten by _code keyword argument passed into constructors
99 """
103 """
100 code = 423
104 code = 423
101 title = explanation = 'Repository Locked'
105 title = explanation = 'Repository Locked'
102 reason = None
106 reason = None
103
107
104 def __init__(self, message, *args, **kwargs):
108 def __init__(self, message, *args, **kwargs):
105 from rhodecode import CONFIG
109 from rhodecode import CONFIG
106 from rhodecode.lib.utils2 import safe_int
110 from rhodecode.lib.utils2 import safe_int
107 _code = CONFIG.get('lock_ret_code')
111 _code = CONFIG.get('lock_ret_code')
108 self.code = safe_int(_code, self.code)
112 self.code = safe_int(_code, self.code)
109 self.title = self.explanation = message
113 self.title = self.explanation = message
110 super(HTTPLockedRC, self).__init__(*args, **kwargs)
114 super(HTTPLockedRC, self).__init__(*args, **kwargs)
111 self.args = (message, )
115 self.args = (message, )
112
116
113
117
114 class HTTPBranchProtected(HTTPClientError):
118 class HTTPBranchProtected(HTTPClientError):
115 """
119 """
116 Special Exception For Indicating that branch is protected in RhodeCode, the
120 Special Exception For Indicating that branch is protected in RhodeCode, the
117 return code can be overwritten by _code keyword argument passed into constructors
121 return code can be overwritten by _code keyword argument passed into constructors
118 """
122 """
119 code = 403
123 code = 403
120 title = explanation = 'Branch Protected'
124 title = explanation = 'Branch Protected'
121 reason = None
125 reason = None
122
126
123 def __init__(self, message, *args, **kwargs):
127 def __init__(self, message, *args, **kwargs):
124 self.title = self.explanation = message
128 self.title = self.explanation = message
125 super(HTTPBranchProtected, self).__init__(*args, **kwargs)
129 super(HTTPBranchProtected, self).__init__(*args, **kwargs)
126 self.args = (message, )
130 self.args = (message, )
127
131
128
132
129 class IMCCommitError(Exception):
133 class IMCCommitError(Exception):
130 pass
134 pass
131
135
132
136
133 class UserCreationError(Exception):
137 class UserCreationError(Exception):
134 pass
138 pass
135
139
136
140
137 class NotAllowedToCreateUserError(Exception):
141 class NotAllowedToCreateUserError(Exception):
138 pass
142 pass
139
143
140
144
141 class RepositoryCreationError(Exception):
145 class RepositoryCreationError(Exception):
142 pass
146 pass
143
147
144
148
145 class VCSServerUnavailable(HTTPBadGateway):
149 class VCSServerUnavailable(HTTPBadGateway):
146 """ HTTP Exception class for VCS Server errors """
150 """ HTTP Exception class for VCS Server errors """
147 code = 502
151 code = 502
148 title = 'VCS Server Error'
152 title = 'VCS Server Error'
149 causes = [
153 causes = [
150 'VCS Server is not running',
154 'VCS Server is not running',
151 'Incorrect vcs.server=host:port',
155 'Incorrect vcs.server=host:port',
152 'Incorrect vcs.server.protocol',
156 'Incorrect vcs.server.protocol',
153 ]
157 ]
154
158
155 def __init__(self, message=''):
159 def __init__(self, message=''):
156 self.explanation = 'Could not connect to VCS Server'
160 self.explanation = 'Could not connect to VCS Server'
157 if message:
161 if message:
158 self.explanation += ': ' + message
162 self.explanation += ': ' + message
159 super(VCSServerUnavailable, self).__init__()
163 super(VCSServerUnavailable, self).__init__()
160
164
161
165
162 class ArtifactMetadataDuplicate(ValueError):
166 class ArtifactMetadataDuplicate(ValueError):
163
167
164 def __init__(self, *args, **kwargs):
168 def __init__(self, *args, **kwargs):
165 self.err_section = kwargs.pop('err_section', None)
169 self.err_section = kwargs.pop('err_section', None)
166 self.err_key = kwargs.pop('err_key', None)
170 self.err_key = kwargs.pop('err_key', None)
167 super(ArtifactMetadataDuplicate, self).__init__(*args, **kwargs)
171 super(ArtifactMetadataDuplicate, self).__init__(*args, **kwargs)
168
172
169
173
170 class ArtifactMetadataBadValueType(ValueError):
174 class ArtifactMetadataBadValueType(ValueError):
171 pass
175 pass
@@ -1,5422 +1,5429 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2019 RhodeCode GmbH
3 # Copyright (C) 2010-2019 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 string
28 import string
29 import hashlib
29 import hashlib
30 import logging
30 import logging
31 import datetime
31 import datetime
32 import uuid
32 import uuid
33 import warnings
33 import warnings
34 import ipaddress
34 import ipaddress
35 import functools
35 import functools
36 import traceback
36 import traceback
37 import collections
37 import collections
38
38
39 from sqlalchemy import (
39 from sqlalchemy import (
40 or_, and_, not_, func, TypeDecorator, event,
40 or_, and_, not_, func, TypeDecorator, event,
41 Index, Sequence, UniqueConstraint, ForeignKey, CheckConstraint, Column,
41 Index, Sequence, UniqueConstraint, ForeignKey, CheckConstraint, Column,
42 Boolean, String, Unicode, UnicodeText, DateTime, Integer, LargeBinary,
42 Boolean, String, Unicode, UnicodeText, DateTime, Integer, LargeBinary,
43 Text, Float, PickleType, BigInteger)
43 Text, Float, PickleType, BigInteger)
44 from sqlalchemy.sql.expression import true, false, case
44 from sqlalchemy.sql.expression import true, false, case
45 from sqlalchemy.sql.functions import coalesce, count # pragma: no cover
45 from sqlalchemy.sql.functions import coalesce, count # pragma: no cover
46 from sqlalchemy.orm import (
46 from sqlalchemy.orm import (
47 relationship, joinedload, class_mapper, validates, aliased)
47 relationship, joinedload, class_mapper, validates, aliased)
48 from sqlalchemy.ext.declarative import declared_attr
48 from sqlalchemy.ext.declarative import declared_attr
49 from sqlalchemy.ext.hybrid import hybrid_property
49 from sqlalchemy.ext.hybrid import hybrid_property
50 from sqlalchemy.exc import IntegrityError # pragma: no cover
50 from sqlalchemy.exc import IntegrityError # pragma: no cover
51 from sqlalchemy.dialects.mysql import LONGTEXT
51 from sqlalchemy.dialects.mysql import LONGTEXT
52 from zope.cachedescriptors.property import Lazy as LazyProperty
52 from zope.cachedescriptors.property import Lazy as LazyProperty
53 from pyramid import compat
53 from pyramid import compat
54 from pyramid.threadlocal import get_current_request
54 from pyramid.threadlocal import get_current_request
55 from webhelpers.text import collapse, remove_formatting
55 from webhelpers.text import collapse, remove_formatting
56
56
57 from rhodecode.translation import _
57 from rhodecode.translation import _
58 from rhodecode.lib.vcs import get_vcs_instance
58 from rhodecode.lib.vcs import get_vcs_instance
59 from rhodecode.lib.vcs.backends.base import EmptyCommit, Reference
59 from rhodecode.lib.vcs.backends.base import EmptyCommit, Reference
60 from rhodecode.lib.utils2 import (
60 from rhodecode.lib.utils2 import (
61 str2bool, safe_str, get_commit_safe, safe_unicode, sha1_safe,
61 str2bool, safe_str, get_commit_safe, safe_unicode, sha1_safe,
62 time_to_datetime, aslist, Optional, safe_int, get_clone_url, AttributeDict,
62 time_to_datetime, aslist, Optional, safe_int, get_clone_url, AttributeDict,
63 glob2re, StrictAttributeDict, cleaned_uri, datetime_to_time, OrderedDefaultDict)
63 glob2re, StrictAttributeDict, cleaned_uri, datetime_to_time, OrderedDefaultDict)
64 from rhodecode.lib.jsonalchemy import MutationObj, MutationList, JsonType, \
64 from rhodecode.lib.jsonalchemy import MutationObj, MutationList, JsonType, \
65 JsonRaw
65 JsonRaw
66 from rhodecode.lib.ext_json import json
66 from rhodecode.lib.ext_json import json
67 from rhodecode.lib.caching_query import FromCache
67 from rhodecode.lib.caching_query import FromCache
68 from rhodecode.lib.encrypt import AESCipher, validate_and_get_enc_data
68 from rhodecode.lib.encrypt import AESCipher, validate_and_get_enc_data
69 from rhodecode.lib.encrypt2 import Encryptor
69 from rhodecode.lib.encrypt2 import Encryptor
70 from rhodecode.lib.exceptions import (
70 from rhodecode.lib.exceptions import (
71 ArtifactMetadataDuplicate, ArtifactMetadataBadValueType)
71 ArtifactMetadataDuplicate, ArtifactMetadataBadValueType)
72 from rhodecode.model.meta import Base, Session
72 from rhodecode.model.meta import Base, Session
73
73
74 URL_SEP = '/'
74 URL_SEP = '/'
75 log = logging.getLogger(__name__)
75 log = logging.getLogger(__name__)
76
76
77 # =============================================================================
77 # =============================================================================
78 # BASE CLASSES
78 # BASE CLASSES
79 # =============================================================================
79 # =============================================================================
80
80
81 # this is propagated from .ini file rhodecode.encrypted_values.secret or
81 # this is propagated from .ini file rhodecode.encrypted_values.secret or
82 # beaker.session.secret if first is not set.
82 # beaker.session.secret if first is not set.
83 # and initialized at environment.py
83 # and initialized at environment.py
84 ENCRYPTION_KEY = None
84 ENCRYPTION_KEY = None
85
85
86 # used to sort permissions by types, '#' used here is not allowed to be in
86 # used to sort permissions by types, '#' used here is not allowed to be in
87 # usernames, and it's very early in sorted string.printable table.
87 # usernames, and it's very early in sorted string.printable table.
88 PERMISSION_TYPE_SORT = {
88 PERMISSION_TYPE_SORT = {
89 'admin': '####',
89 'admin': '####',
90 'write': '###',
90 'write': '###',
91 'read': '##',
91 'read': '##',
92 'none': '#',
92 'none': '#',
93 }
93 }
94
94
95
95
96 def display_user_sort(obj):
96 def display_user_sort(obj):
97 """
97 """
98 Sort function used to sort permissions in .permissions() function of
98 Sort function used to sort permissions in .permissions() function of
99 Repository, RepoGroup, UserGroup. Also it put the default user in front
99 Repository, RepoGroup, UserGroup. Also it put the default user in front
100 of all other resources
100 of all other resources
101 """
101 """
102
102
103 if obj.username == User.DEFAULT_USER:
103 if obj.username == User.DEFAULT_USER:
104 return '#####'
104 return '#####'
105 prefix = PERMISSION_TYPE_SORT.get(obj.permission.split('.')[-1], '')
105 prefix = PERMISSION_TYPE_SORT.get(obj.permission.split('.')[-1], '')
106 return prefix + obj.username
106 return prefix + obj.username
107
107
108
108
109 def display_user_group_sort(obj):
109 def display_user_group_sort(obj):
110 """
110 """
111 Sort function used to sort permissions in .permissions() function of
111 Sort function used to sort permissions in .permissions() function of
112 Repository, RepoGroup, UserGroup. Also it put the default user in front
112 Repository, RepoGroup, UserGroup. Also it put the default user in front
113 of all other resources
113 of all other resources
114 """
114 """
115
115
116 prefix = PERMISSION_TYPE_SORT.get(obj.permission.split('.')[-1], '')
116 prefix = PERMISSION_TYPE_SORT.get(obj.permission.split('.')[-1], '')
117 return prefix + obj.users_group_name
117 return prefix + obj.users_group_name
118
118
119
119
120 def _hash_key(k):
120 def _hash_key(k):
121 return sha1_safe(k)
121 return sha1_safe(k)
122
122
123
123
124 def in_filter_generator(qry, items, limit=500):
124 def in_filter_generator(qry, items, limit=500):
125 """
125 """
126 Splits IN() into multiple with OR
126 Splits IN() into multiple with OR
127 e.g.::
127 e.g.::
128 cnt = Repository.query().filter(
128 cnt = Repository.query().filter(
129 or_(
129 or_(
130 *in_filter_generator(Repository.repo_id, range(100000))
130 *in_filter_generator(Repository.repo_id, range(100000))
131 )).count()
131 )).count()
132 """
132 """
133 if not items:
133 if not items:
134 # empty list will cause empty query which might cause security issues
134 # empty list will cause empty query which might cause security issues
135 # this can lead to hidden unpleasant results
135 # this can lead to hidden unpleasant results
136 items = [-1]
136 items = [-1]
137
137
138 parts = []
138 parts = []
139 for chunk in xrange(0, len(items), limit):
139 for chunk in xrange(0, len(items), limit):
140 parts.append(
140 parts.append(
141 qry.in_(items[chunk: chunk + limit])
141 qry.in_(items[chunk: chunk + limit])
142 )
142 )
143
143
144 return parts
144 return parts
145
145
146
146
147 base_table_args = {
147 base_table_args = {
148 'extend_existing': True,
148 'extend_existing': True,
149 'mysql_engine': 'InnoDB',
149 'mysql_engine': 'InnoDB',
150 'mysql_charset': 'utf8',
150 'mysql_charset': 'utf8',
151 'sqlite_autoincrement': True
151 'sqlite_autoincrement': True
152 }
152 }
153
153
154
154
155 class EncryptedTextValue(TypeDecorator):
155 class EncryptedTextValue(TypeDecorator):
156 """
156 """
157 Special column for encrypted long text data, use like::
157 Special column for encrypted long text data, use like::
158
158
159 value = Column("encrypted_value", EncryptedValue(), nullable=False)
159 value = Column("encrypted_value", EncryptedValue(), nullable=False)
160
160
161 This column is intelligent so if value is in unencrypted form it return
161 This column is intelligent so if value is in unencrypted form it return
162 unencrypted form, but on save it always encrypts
162 unencrypted form, but on save it always encrypts
163 """
163 """
164 impl = Text
164 impl = Text
165
165
166 def process_bind_param(self, value, dialect):
166 def process_bind_param(self, value, dialect):
167 """
167 """
168 Setter for storing value
168 Setter for storing value
169 """
169 """
170 import rhodecode
170 import rhodecode
171 if not value:
171 if not value:
172 return value
172 return value
173
173
174 # protect against double encrypting if values is already encrypted
174 # protect against double encrypting if values is already encrypted
175 if value.startswith('enc$aes$') \
175 if value.startswith('enc$aes$') \
176 or value.startswith('enc$aes_hmac$') \
176 or value.startswith('enc$aes_hmac$') \
177 or value.startswith('enc2$'):
177 or value.startswith('enc2$'):
178 raise ValueError('value needs to be in unencrypted format, '
178 raise ValueError('value needs to be in unencrypted format, '
179 'ie. not starting with enc$ or enc2$')
179 'ie. not starting with enc$ or enc2$')
180
180
181 algo = rhodecode.CONFIG.get('rhodecode.encrypted_values.algorithm') or 'aes'
181 algo = rhodecode.CONFIG.get('rhodecode.encrypted_values.algorithm') or 'aes'
182 if algo == 'aes':
182 if algo == 'aes':
183 return 'enc$aes_hmac$%s' % AESCipher(ENCRYPTION_KEY, hmac=True).encrypt(value)
183 return 'enc$aes_hmac$%s' % AESCipher(ENCRYPTION_KEY, hmac=True).encrypt(value)
184 elif algo == 'fernet':
184 elif algo == 'fernet':
185 return Encryptor(ENCRYPTION_KEY).encrypt(value)
185 return Encryptor(ENCRYPTION_KEY).encrypt(value)
186 else:
186 else:
187 ValueError('Bad encryption algorithm, should be fernet or aes, got: {}'.format(algo))
187 ValueError('Bad encryption algorithm, should be fernet or aes, got: {}'.format(algo))
188
188
189 def process_result_value(self, value, dialect):
189 def process_result_value(self, value, dialect):
190 """
190 """
191 Getter for retrieving value
191 Getter for retrieving value
192 """
192 """
193
193
194 import rhodecode
194 import rhodecode
195 if not value:
195 if not value:
196 return value
196 return value
197
197
198 algo = rhodecode.CONFIG.get('rhodecode.encrypted_values.algorithm') or 'aes'
198 algo = rhodecode.CONFIG.get('rhodecode.encrypted_values.algorithm') or 'aes'
199 enc_strict_mode = str2bool(rhodecode.CONFIG.get('rhodecode.encrypted_values.strict') or True)
199 enc_strict_mode = str2bool(rhodecode.CONFIG.get('rhodecode.encrypted_values.strict') or True)
200 if algo == 'aes':
200 if algo == 'aes':
201 decrypted_data = validate_and_get_enc_data(value, ENCRYPTION_KEY, enc_strict_mode)
201 decrypted_data = validate_and_get_enc_data(value, ENCRYPTION_KEY, enc_strict_mode)
202 elif algo == 'fernet':
202 elif algo == 'fernet':
203 return Encryptor(ENCRYPTION_KEY).decrypt(value)
203 return Encryptor(ENCRYPTION_KEY).decrypt(value)
204 else:
204 else:
205 ValueError('Bad encryption algorithm, should be fernet or aes, got: {}'.format(algo))
205 ValueError('Bad encryption algorithm, should be fernet or aes, got: {}'.format(algo))
206 return decrypted_data
206 return decrypted_data
207
207
208
208
209 class BaseModel(object):
209 class BaseModel(object):
210 """
210 """
211 Base Model for all classes
211 Base Model for all classes
212 """
212 """
213
213
214 @classmethod
214 @classmethod
215 def _get_keys(cls):
215 def _get_keys(cls):
216 """return column names for this model """
216 """return column names for this model """
217 return class_mapper(cls).c.keys()
217 return class_mapper(cls).c.keys()
218
218
219 def get_dict(self):
219 def get_dict(self):
220 """
220 """
221 return dict with keys and values corresponding
221 return dict with keys and values corresponding
222 to this model data """
222 to this model data """
223
223
224 d = {}
224 d = {}
225 for k in self._get_keys():
225 for k in self._get_keys():
226 d[k] = getattr(self, k)
226 d[k] = getattr(self, k)
227
227
228 # also use __json__() if present to get additional fields
228 # also use __json__() if present to get additional fields
229 _json_attr = getattr(self, '__json__', None)
229 _json_attr = getattr(self, '__json__', None)
230 if _json_attr:
230 if _json_attr:
231 # update with attributes from __json__
231 # update with attributes from __json__
232 if callable(_json_attr):
232 if callable(_json_attr):
233 _json_attr = _json_attr()
233 _json_attr = _json_attr()
234 for k, val in _json_attr.iteritems():
234 for k, val in _json_attr.iteritems():
235 d[k] = val
235 d[k] = val
236 return d
236 return d
237
237
238 def get_appstruct(self):
238 def get_appstruct(self):
239 """return list with keys and values tuples corresponding
239 """return list with keys and values tuples corresponding
240 to this model data """
240 to this model data """
241
241
242 lst = []
242 lst = []
243 for k in self._get_keys():
243 for k in self._get_keys():
244 lst.append((k, getattr(self, k),))
244 lst.append((k, getattr(self, k),))
245 return lst
245 return lst
246
246
247 def populate_obj(self, populate_dict):
247 def populate_obj(self, populate_dict):
248 """populate model with data from given populate_dict"""
248 """populate model with data from given populate_dict"""
249
249
250 for k in self._get_keys():
250 for k in self._get_keys():
251 if k in populate_dict:
251 if k in populate_dict:
252 setattr(self, k, populate_dict[k])
252 setattr(self, k, populate_dict[k])
253
253
254 @classmethod
254 @classmethod
255 def query(cls):
255 def query(cls):
256 return Session().query(cls)
256 return Session().query(cls)
257
257
258 @classmethod
258 @classmethod
259 def get(cls, id_):
259 def get(cls, id_):
260 if id_:
260 if id_:
261 return cls.query().get(id_)
261 return cls.query().get(id_)
262
262
263 @classmethod
263 @classmethod
264 def get_or_404(cls, id_):
264 def get_or_404(cls, id_):
265 from pyramid.httpexceptions import HTTPNotFound
265 from pyramid.httpexceptions import HTTPNotFound
266
266
267 try:
267 try:
268 id_ = int(id_)
268 id_ = int(id_)
269 except (TypeError, ValueError):
269 except (TypeError, ValueError):
270 raise HTTPNotFound()
270 raise HTTPNotFound()
271
271
272 res = cls.query().get(id_)
272 res = cls.query().get(id_)
273 if not res:
273 if not res:
274 raise HTTPNotFound()
274 raise HTTPNotFound()
275 return res
275 return res
276
276
277 @classmethod
277 @classmethod
278 def getAll(cls):
278 def getAll(cls):
279 # deprecated and left for backward compatibility
279 # deprecated and left for backward compatibility
280 return cls.get_all()
280 return cls.get_all()
281
281
282 @classmethod
282 @classmethod
283 def get_all(cls):
283 def get_all(cls):
284 return cls.query().all()
284 return cls.query().all()
285
285
286 @classmethod
286 @classmethod
287 def delete(cls, id_):
287 def delete(cls, id_):
288 obj = cls.query().get(id_)
288 obj = cls.query().get(id_)
289 Session().delete(obj)
289 Session().delete(obj)
290
290
291 @classmethod
291 @classmethod
292 def identity_cache(cls, session, attr_name, value):
292 def identity_cache(cls, session, attr_name, value):
293 exist_in_session = []
293 exist_in_session = []
294 for (item_cls, pkey), instance in session.identity_map.items():
294 for (item_cls, pkey), instance in session.identity_map.items():
295 if cls == item_cls and getattr(instance, attr_name) == value:
295 if cls == item_cls and getattr(instance, attr_name) == value:
296 exist_in_session.append(instance)
296 exist_in_session.append(instance)
297 if exist_in_session:
297 if exist_in_session:
298 if len(exist_in_session) == 1:
298 if len(exist_in_session) == 1:
299 return exist_in_session[0]
299 return exist_in_session[0]
300 log.exception(
300 log.exception(
301 'multiple objects with attr %s and '
301 'multiple objects with attr %s and '
302 'value %s found with same name: %r',
302 'value %s found with same name: %r',
303 attr_name, value, exist_in_session)
303 attr_name, value, exist_in_session)
304
304
305 def __repr__(self):
305 def __repr__(self):
306 if hasattr(self, '__unicode__'):
306 if hasattr(self, '__unicode__'):
307 # python repr needs to return str
307 # python repr needs to return str
308 try:
308 try:
309 return safe_str(self.__unicode__())
309 return safe_str(self.__unicode__())
310 except UnicodeDecodeError:
310 except UnicodeDecodeError:
311 pass
311 pass
312 return '<DB:%s>' % (self.__class__.__name__)
312 return '<DB:%s>' % (self.__class__.__name__)
313
313
314
314
315 class RhodeCodeSetting(Base, BaseModel):
315 class RhodeCodeSetting(Base, BaseModel):
316 __tablename__ = 'rhodecode_settings'
316 __tablename__ = 'rhodecode_settings'
317 __table_args__ = (
317 __table_args__ = (
318 UniqueConstraint('app_settings_name'),
318 UniqueConstraint('app_settings_name'),
319 base_table_args
319 base_table_args
320 )
320 )
321
321
322 SETTINGS_TYPES = {
322 SETTINGS_TYPES = {
323 'str': safe_str,
323 'str': safe_str,
324 'int': safe_int,
324 'int': safe_int,
325 'unicode': safe_unicode,
325 'unicode': safe_unicode,
326 'bool': str2bool,
326 'bool': str2bool,
327 'list': functools.partial(aslist, sep=',')
327 'list': functools.partial(aslist, sep=',')
328 }
328 }
329 DEFAULT_UPDATE_URL = 'https://rhodecode.com/api/v1/info/versions'
329 DEFAULT_UPDATE_URL = 'https://rhodecode.com/api/v1/info/versions'
330 GLOBAL_CONF_KEY = 'app_settings'
330 GLOBAL_CONF_KEY = 'app_settings'
331
331
332 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
332 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
333 app_settings_name = Column("app_settings_name", String(255), nullable=True, unique=None, default=None)
333 app_settings_name = Column("app_settings_name", String(255), nullable=True, unique=None, default=None)
334 _app_settings_value = Column("app_settings_value", String(4096), nullable=True, unique=None, default=None)
334 _app_settings_value = Column("app_settings_value", String(4096), nullable=True, unique=None, default=None)
335 _app_settings_type = Column("app_settings_type", String(255), nullable=True, unique=None, default=None)
335 _app_settings_type = Column("app_settings_type", String(255), nullable=True, unique=None, default=None)
336
336
337 def __init__(self, key='', val='', type='unicode'):
337 def __init__(self, key='', val='', type='unicode'):
338 self.app_settings_name = key
338 self.app_settings_name = key
339 self.app_settings_type = type
339 self.app_settings_type = type
340 self.app_settings_value = val
340 self.app_settings_value = val
341
341
342 @validates('_app_settings_value')
342 @validates('_app_settings_value')
343 def validate_settings_value(self, key, val):
343 def validate_settings_value(self, key, val):
344 assert type(val) == unicode
344 assert type(val) == unicode
345 return val
345 return val
346
346
347 @hybrid_property
347 @hybrid_property
348 def app_settings_value(self):
348 def app_settings_value(self):
349 v = self._app_settings_value
349 v = self._app_settings_value
350 _type = self.app_settings_type
350 _type = self.app_settings_type
351 if _type:
351 if _type:
352 _type = self.app_settings_type.split('.')[0]
352 _type = self.app_settings_type.split('.')[0]
353 # decode the encrypted value
353 # decode the encrypted value
354 if 'encrypted' in self.app_settings_type:
354 if 'encrypted' in self.app_settings_type:
355 cipher = EncryptedTextValue()
355 cipher = EncryptedTextValue()
356 v = safe_unicode(cipher.process_result_value(v, None))
356 v = safe_unicode(cipher.process_result_value(v, None))
357
357
358 converter = self.SETTINGS_TYPES.get(_type) or \
358 converter = self.SETTINGS_TYPES.get(_type) or \
359 self.SETTINGS_TYPES['unicode']
359 self.SETTINGS_TYPES['unicode']
360 return converter(v)
360 return converter(v)
361
361
362 @app_settings_value.setter
362 @app_settings_value.setter
363 def app_settings_value(self, val):
363 def app_settings_value(self, val):
364 """
364 """
365 Setter that will always make sure we use unicode in app_settings_value
365 Setter that will always make sure we use unicode in app_settings_value
366
366
367 :param val:
367 :param val:
368 """
368 """
369 val = safe_unicode(val)
369 val = safe_unicode(val)
370 # encode the encrypted value
370 # encode the encrypted value
371 if 'encrypted' in self.app_settings_type:
371 if 'encrypted' in self.app_settings_type:
372 cipher = EncryptedTextValue()
372 cipher = EncryptedTextValue()
373 val = safe_unicode(cipher.process_bind_param(val, None))
373 val = safe_unicode(cipher.process_bind_param(val, None))
374 self._app_settings_value = val
374 self._app_settings_value = val
375
375
376 @hybrid_property
376 @hybrid_property
377 def app_settings_type(self):
377 def app_settings_type(self):
378 return self._app_settings_type
378 return self._app_settings_type
379
379
380 @app_settings_type.setter
380 @app_settings_type.setter
381 def app_settings_type(self, val):
381 def app_settings_type(self, val):
382 if val.split('.')[0] not in self.SETTINGS_TYPES:
382 if val.split('.')[0] not in self.SETTINGS_TYPES:
383 raise Exception('type must be one of %s got %s'
383 raise Exception('type must be one of %s got %s'
384 % (self.SETTINGS_TYPES.keys(), val))
384 % (self.SETTINGS_TYPES.keys(), val))
385 self._app_settings_type = val
385 self._app_settings_type = val
386
386
387 @classmethod
387 @classmethod
388 def get_by_prefix(cls, prefix):
388 def get_by_prefix(cls, prefix):
389 return RhodeCodeSetting.query()\
389 return RhodeCodeSetting.query()\
390 .filter(RhodeCodeSetting.app_settings_name.startswith(prefix))\
390 .filter(RhodeCodeSetting.app_settings_name.startswith(prefix))\
391 .all()
391 .all()
392
392
393 def __unicode__(self):
393 def __unicode__(self):
394 return u"<%s('%s:%s[%s]')>" % (
394 return u"<%s('%s:%s[%s]')>" % (
395 self.__class__.__name__,
395 self.__class__.__name__,
396 self.app_settings_name, self.app_settings_value,
396 self.app_settings_name, self.app_settings_value,
397 self.app_settings_type
397 self.app_settings_type
398 )
398 )
399
399
400
400
401 class RhodeCodeUi(Base, BaseModel):
401 class RhodeCodeUi(Base, BaseModel):
402 __tablename__ = 'rhodecode_ui'
402 __tablename__ = 'rhodecode_ui'
403 __table_args__ = (
403 __table_args__ = (
404 UniqueConstraint('ui_key'),
404 UniqueConstraint('ui_key'),
405 base_table_args
405 base_table_args
406 )
406 )
407
407
408 HOOK_REPO_SIZE = 'changegroup.repo_size'
408 HOOK_REPO_SIZE = 'changegroup.repo_size'
409 # HG
409 # HG
410 HOOK_PRE_PULL = 'preoutgoing.pre_pull'
410 HOOK_PRE_PULL = 'preoutgoing.pre_pull'
411 HOOK_PULL = 'outgoing.pull_logger'
411 HOOK_PULL = 'outgoing.pull_logger'
412 HOOK_PRE_PUSH = 'prechangegroup.pre_push'
412 HOOK_PRE_PUSH = 'prechangegroup.pre_push'
413 HOOK_PRETX_PUSH = 'pretxnchangegroup.pre_push'
413 HOOK_PRETX_PUSH = 'pretxnchangegroup.pre_push'
414 HOOK_PUSH = 'changegroup.push_logger'
414 HOOK_PUSH = 'changegroup.push_logger'
415 HOOK_PUSH_KEY = 'pushkey.key_push'
415 HOOK_PUSH_KEY = 'pushkey.key_push'
416
416
417 HOOKS_BUILTIN = [
417 HOOKS_BUILTIN = [
418 HOOK_PRE_PULL,
418 HOOK_PRE_PULL,
419 HOOK_PULL,
419 HOOK_PULL,
420 HOOK_PRE_PUSH,
420 HOOK_PRE_PUSH,
421 HOOK_PRETX_PUSH,
421 HOOK_PRETX_PUSH,
422 HOOK_PUSH,
422 HOOK_PUSH,
423 HOOK_PUSH_KEY,
423 HOOK_PUSH_KEY,
424 ]
424 ]
425
425
426 # TODO: johbo: Unify way how hooks are configured for git and hg,
426 # TODO: johbo: Unify way how hooks are configured for git and hg,
427 # git part is currently hardcoded.
427 # git part is currently hardcoded.
428
428
429 # SVN PATTERNS
429 # SVN PATTERNS
430 SVN_BRANCH_ID = 'vcs_svn_branch'
430 SVN_BRANCH_ID = 'vcs_svn_branch'
431 SVN_TAG_ID = 'vcs_svn_tag'
431 SVN_TAG_ID = 'vcs_svn_tag'
432
432
433 ui_id = Column(
433 ui_id = Column(
434 "ui_id", Integer(), nullable=False, unique=True, default=None,
434 "ui_id", Integer(), nullable=False, unique=True, default=None,
435 primary_key=True)
435 primary_key=True)
436 ui_section = Column(
436 ui_section = Column(
437 "ui_section", String(255), nullable=True, unique=None, default=None)
437 "ui_section", String(255), nullable=True, unique=None, default=None)
438 ui_key = Column(
438 ui_key = Column(
439 "ui_key", String(255), nullable=True, unique=None, default=None)
439 "ui_key", String(255), nullable=True, unique=None, default=None)
440 ui_value = Column(
440 ui_value = Column(
441 "ui_value", String(255), nullable=True, unique=None, default=None)
441 "ui_value", String(255), nullable=True, unique=None, default=None)
442 ui_active = Column(
442 ui_active = Column(
443 "ui_active", Boolean(), nullable=True, unique=None, default=True)
443 "ui_active", Boolean(), nullable=True, unique=None, default=True)
444
444
445 def __repr__(self):
445 def __repr__(self):
446 return '<%s[%s]%s=>%s]>' % (self.__class__.__name__, self.ui_section,
446 return '<%s[%s]%s=>%s]>' % (self.__class__.__name__, self.ui_section,
447 self.ui_key, self.ui_value)
447 self.ui_key, self.ui_value)
448
448
449
449
450 class RepoRhodeCodeSetting(Base, BaseModel):
450 class RepoRhodeCodeSetting(Base, BaseModel):
451 __tablename__ = 'repo_rhodecode_settings'
451 __tablename__ = 'repo_rhodecode_settings'
452 __table_args__ = (
452 __table_args__ = (
453 UniqueConstraint(
453 UniqueConstraint(
454 'app_settings_name', 'repository_id',
454 'app_settings_name', 'repository_id',
455 name='uq_repo_rhodecode_setting_name_repo_id'),
455 name='uq_repo_rhodecode_setting_name_repo_id'),
456 base_table_args
456 base_table_args
457 )
457 )
458
458
459 repository_id = Column(
459 repository_id = Column(
460 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
460 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
461 nullable=False)
461 nullable=False)
462 app_settings_id = Column(
462 app_settings_id = Column(
463 "app_settings_id", Integer(), nullable=False, unique=True,
463 "app_settings_id", Integer(), nullable=False, unique=True,
464 default=None, primary_key=True)
464 default=None, primary_key=True)
465 app_settings_name = Column(
465 app_settings_name = Column(
466 "app_settings_name", String(255), nullable=True, unique=None,
466 "app_settings_name", String(255), nullable=True, unique=None,
467 default=None)
467 default=None)
468 _app_settings_value = Column(
468 _app_settings_value = Column(
469 "app_settings_value", String(4096), nullable=True, unique=None,
469 "app_settings_value", String(4096), nullable=True, unique=None,
470 default=None)
470 default=None)
471 _app_settings_type = Column(
471 _app_settings_type = Column(
472 "app_settings_type", String(255), nullable=True, unique=None,
472 "app_settings_type", String(255), nullable=True, unique=None,
473 default=None)
473 default=None)
474
474
475 repository = relationship('Repository')
475 repository = relationship('Repository')
476
476
477 def __init__(self, repository_id, key='', val='', type='unicode'):
477 def __init__(self, repository_id, key='', val='', type='unicode'):
478 self.repository_id = repository_id
478 self.repository_id = repository_id
479 self.app_settings_name = key
479 self.app_settings_name = key
480 self.app_settings_type = type
480 self.app_settings_type = type
481 self.app_settings_value = val
481 self.app_settings_value = val
482
482
483 @validates('_app_settings_value')
483 @validates('_app_settings_value')
484 def validate_settings_value(self, key, val):
484 def validate_settings_value(self, key, val):
485 assert type(val) == unicode
485 assert type(val) == unicode
486 return val
486 return val
487
487
488 @hybrid_property
488 @hybrid_property
489 def app_settings_value(self):
489 def app_settings_value(self):
490 v = self._app_settings_value
490 v = self._app_settings_value
491 type_ = self.app_settings_type
491 type_ = self.app_settings_type
492 SETTINGS_TYPES = RhodeCodeSetting.SETTINGS_TYPES
492 SETTINGS_TYPES = RhodeCodeSetting.SETTINGS_TYPES
493 converter = SETTINGS_TYPES.get(type_) or SETTINGS_TYPES['unicode']
493 converter = SETTINGS_TYPES.get(type_) or SETTINGS_TYPES['unicode']
494 return converter(v)
494 return converter(v)
495
495
496 @app_settings_value.setter
496 @app_settings_value.setter
497 def app_settings_value(self, val):
497 def app_settings_value(self, val):
498 """
498 """
499 Setter that will always make sure we use unicode in app_settings_value
499 Setter that will always make sure we use unicode in app_settings_value
500
500
501 :param val:
501 :param val:
502 """
502 """
503 self._app_settings_value = safe_unicode(val)
503 self._app_settings_value = safe_unicode(val)
504
504
505 @hybrid_property
505 @hybrid_property
506 def app_settings_type(self):
506 def app_settings_type(self):
507 return self._app_settings_type
507 return self._app_settings_type
508
508
509 @app_settings_type.setter
509 @app_settings_type.setter
510 def app_settings_type(self, val):
510 def app_settings_type(self, val):
511 SETTINGS_TYPES = RhodeCodeSetting.SETTINGS_TYPES
511 SETTINGS_TYPES = RhodeCodeSetting.SETTINGS_TYPES
512 if val not in SETTINGS_TYPES:
512 if val not in SETTINGS_TYPES:
513 raise Exception('type must be one of %s got %s'
513 raise Exception('type must be one of %s got %s'
514 % (SETTINGS_TYPES.keys(), val))
514 % (SETTINGS_TYPES.keys(), val))
515 self._app_settings_type = val
515 self._app_settings_type = val
516
516
517 def __unicode__(self):
517 def __unicode__(self):
518 return u"<%s('%s:%s:%s[%s]')>" % (
518 return u"<%s('%s:%s:%s[%s]')>" % (
519 self.__class__.__name__, self.repository.repo_name,
519 self.__class__.__name__, self.repository.repo_name,
520 self.app_settings_name, self.app_settings_value,
520 self.app_settings_name, self.app_settings_value,
521 self.app_settings_type
521 self.app_settings_type
522 )
522 )
523
523
524
524
525 class RepoRhodeCodeUi(Base, BaseModel):
525 class RepoRhodeCodeUi(Base, BaseModel):
526 __tablename__ = 'repo_rhodecode_ui'
526 __tablename__ = 'repo_rhodecode_ui'
527 __table_args__ = (
527 __table_args__ = (
528 UniqueConstraint(
528 UniqueConstraint(
529 'repository_id', 'ui_section', 'ui_key',
529 'repository_id', 'ui_section', 'ui_key',
530 name='uq_repo_rhodecode_ui_repository_id_section_key'),
530 name='uq_repo_rhodecode_ui_repository_id_section_key'),
531 base_table_args
531 base_table_args
532 )
532 )
533
533
534 repository_id = Column(
534 repository_id = Column(
535 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
535 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
536 nullable=False)
536 nullable=False)
537 ui_id = Column(
537 ui_id = Column(
538 "ui_id", Integer(), nullable=False, unique=True, default=None,
538 "ui_id", Integer(), nullable=False, unique=True, default=None,
539 primary_key=True)
539 primary_key=True)
540 ui_section = Column(
540 ui_section = Column(
541 "ui_section", String(255), nullable=True, unique=None, default=None)
541 "ui_section", String(255), nullable=True, unique=None, default=None)
542 ui_key = Column(
542 ui_key = Column(
543 "ui_key", String(255), nullable=True, unique=None, default=None)
543 "ui_key", String(255), nullable=True, unique=None, default=None)
544 ui_value = Column(
544 ui_value = Column(
545 "ui_value", String(255), nullable=True, unique=None, default=None)
545 "ui_value", String(255), nullable=True, unique=None, default=None)
546 ui_active = Column(
546 ui_active = Column(
547 "ui_active", Boolean(), nullable=True, unique=None, default=True)
547 "ui_active", Boolean(), nullable=True, unique=None, default=True)
548
548
549 repository = relationship('Repository')
549 repository = relationship('Repository')
550
550
551 def __repr__(self):
551 def __repr__(self):
552 return '<%s[%s:%s]%s=>%s]>' % (
552 return '<%s[%s:%s]%s=>%s]>' % (
553 self.__class__.__name__, self.repository.repo_name,
553 self.__class__.__name__, self.repository.repo_name,
554 self.ui_section, self.ui_key, self.ui_value)
554 self.ui_section, self.ui_key, self.ui_value)
555
555
556
556
557 class User(Base, BaseModel):
557 class User(Base, BaseModel):
558 __tablename__ = 'users'
558 __tablename__ = 'users'
559 __table_args__ = (
559 __table_args__ = (
560 UniqueConstraint('username'), UniqueConstraint('email'),
560 UniqueConstraint('username'), UniqueConstraint('email'),
561 Index('u_username_idx', 'username'),
561 Index('u_username_idx', 'username'),
562 Index('u_email_idx', 'email'),
562 Index('u_email_idx', 'email'),
563 base_table_args
563 base_table_args
564 )
564 )
565
565
566 DEFAULT_USER = 'default'
566 DEFAULT_USER = 'default'
567 DEFAULT_USER_EMAIL = 'anonymous@rhodecode.org'
567 DEFAULT_USER_EMAIL = 'anonymous@rhodecode.org'
568 DEFAULT_GRAVATAR_URL = 'https://secure.gravatar.com/avatar/{md5email}?d=identicon&s={size}'
568 DEFAULT_GRAVATAR_URL = 'https://secure.gravatar.com/avatar/{md5email}?d=identicon&s={size}'
569
569
570 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
570 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
571 username = Column("username", String(255), nullable=True, unique=None, default=None)
571 username = Column("username", String(255), nullable=True, unique=None, default=None)
572 password = Column("password", String(255), nullable=True, unique=None, default=None)
572 password = Column("password", String(255), nullable=True, unique=None, default=None)
573 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
573 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
574 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
574 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
575 name = Column("firstname", String(255), nullable=True, unique=None, default=None)
575 name = Column("firstname", String(255), nullable=True, unique=None, default=None)
576 lastname = Column("lastname", String(255), nullable=True, unique=None, default=None)
576 lastname = Column("lastname", String(255), nullable=True, unique=None, default=None)
577 _email = Column("email", String(255), nullable=True, unique=None, default=None)
577 _email = Column("email", String(255), nullable=True, unique=None, default=None)
578 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
578 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
579 last_activity = Column('last_activity', DateTime(timezone=False), nullable=True, unique=None, default=None)
579 last_activity = Column('last_activity', DateTime(timezone=False), nullable=True, unique=None, default=None)
580
580
581 extern_type = Column("extern_type", String(255), nullable=True, unique=None, default=None)
581 extern_type = Column("extern_type", String(255), nullable=True, unique=None, default=None)
582 extern_name = Column("extern_name", String(255), nullable=True, unique=None, default=None)
582 extern_name = Column("extern_name", String(255), nullable=True, unique=None, default=None)
583 _api_key = Column("api_key", String(255), nullable=True, unique=None, default=None)
583 _api_key = Column("api_key", String(255), nullable=True, unique=None, default=None)
584 inherit_default_permissions = Column("inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
584 inherit_default_permissions = Column("inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
585 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
585 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
586 _user_data = Column("user_data", LargeBinary(), nullable=True) # JSON data
586 _user_data = Column("user_data", LargeBinary(), nullable=True) # JSON data
587
587
588 user_log = relationship('UserLog')
588 user_log = relationship('UserLog')
589 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all, delete-orphan')
589 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all, delete-orphan')
590
590
591 repositories = relationship('Repository')
591 repositories = relationship('Repository')
592 repository_groups = relationship('RepoGroup')
592 repository_groups = relationship('RepoGroup')
593 user_groups = relationship('UserGroup')
593 user_groups = relationship('UserGroup')
594
594
595 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
595 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
596 followings = relationship('UserFollowing', primaryjoin='UserFollowing.user_id==User.user_id', cascade='all')
596 followings = relationship('UserFollowing', primaryjoin='UserFollowing.user_id==User.user_id', cascade='all')
597
597
598 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all, delete-orphan')
598 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all, delete-orphan')
599 repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all, delete-orphan')
599 repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all, delete-orphan')
600 user_group_to_perm = relationship('UserUserGroupToPerm', primaryjoin='UserUserGroupToPerm.user_id==User.user_id', cascade='all, delete-orphan')
600 user_group_to_perm = relationship('UserUserGroupToPerm', primaryjoin='UserUserGroupToPerm.user_id==User.user_id', cascade='all, delete-orphan')
601
601
602 group_member = relationship('UserGroupMember', cascade='all')
602 group_member = relationship('UserGroupMember', cascade='all')
603
603
604 notifications = relationship('UserNotification', cascade='all')
604 notifications = relationship('UserNotification', cascade='all')
605 # notifications assigned to this user
605 # notifications assigned to this user
606 user_created_notifications = relationship('Notification', cascade='all')
606 user_created_notifications = relationship('Notification', cascade='all')
607 # comments created by this user
607 # comments created by this user
608 user_comments = relationship('ChangesetComment', cascade='all')
608 user_comments = relationship('ChangesetComment', cascade='all')
609 # user profile extra info
609 # user profile extra info
610 user_emails = relationship('UserEmailMap', cascade='all')
610 user_emails = relationship('UserEmailMap', cascade='all')
611 user_ip_map = relationship('UserIpMap', cascade='all')
611 user_ip_map = relationship('UserIpMap', cascade='all')
612 user_auth_tokens = relationship('UserApiKeys', cascade='all')
612 user_auth_tokens = relationship('UserApiKeys', cascade='all')
613 user_ssh_keys = relationship('UserSshKeys', cascade='all')
613 user_ssh_keys = relationship('UserSshKeys', cascade='all')
614
614
615 # gists
615 # gists
616 user_gists = relationship('Gist', cascade='all')
616 user_gists = relationship('Gist', cascade='all')
617 # user pull requests
617 # user pull requests
618 user_pull_requests = relationship('PullRequest', cascade='all')
618 user_pull_requests = relationship('PullRequest', cascade='all')
619 # external identities
619 # external identities
620 extenal_identities = relationship(
620 external_identities = relationship(
621 'ExternalIdentity',
621 'ExternalIdentity',
622 primaryjoin="User.user_id==ExternalIdentity.local_user_id",
622 primaryjoin="User.user_id==ExternalIdentity.local_user_id",
623 cascade='all')
623 cascade='all')
624 # review rules
624 # review rules
625 user_review_rules = relationship('RepoReviewRuleUser', cascade='all')
625 user_review_rules = relationship('RepoReviewRuleUser', cascade='all')
626
626
627 # artifacts owned
628 artifacts = relationship('FileStore', primaryjoin='FileStore.user_id==User.user_id')
629
630 # no cascade, set NULL
631 scope_artifacts = relationship('FileStore', primaryjoin='FileStore.scope_user_id==User.user_id')
632
627 def __unicode__(self):
633 def __unicode__(self):
628 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
634 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
629 self.user_id, self.username)
635 self.user_id, self.username)
630
636
631 @hybrid_property
637 @hybrid_property
632 def email(self):
638 def email(self):
633 return self._email
639 return self._email
634
640
635 @email.setter
641 @email.setter
636 def email(self, val):
642 def email(self, val):
637 self._email = val.lower() if val else None
643 self._email = val.lower() if val else None
638
644
639 @hybrid_property
645 @hybrid_property
640 def first_name(self):
646 def first_name(self):
641 from rhodecode.lib import helpers as h
647 from rhodecode.lib import helpers as h
642 if self.name:
648 if self.name:
643 return h.escape(self.name)
649 return h.escape(self.name)
644 return self.name
650 return self.name
645
651
646 @hybrid_property
652 @hybrid_property
647 def last_name(self):
653 def last_name(self):
648 from rhodecode.lib import helpers as h
654 from rhodecode.lib import helpers as h
649 if self.lastname:
655 if self.lastname:
650 return h.escape(self.lastname)
656 return h.escape(self.lastname)
651 return self.lastname
657 return self.lastname
652
658
653 @hybrid_property
659 @hybrid_property
654 def api_key(self):
660 def api_key(self):
655 """
661 """
656 Fetch if exist an auth-token with role ALL connected to this user
662 Fetch if exist an auth-token with role ALL connected to this user
657 """
663 """
658 user_auth_token = UserApiKeys.query()\
664 user_auth_token = UserApiKeys.query()\
659 .filter(UserApiKeys.user_id == self.user_id)\
665 .filter(UserApiKeys.user_id == self.user_id)\
660 .filter(or_(UserApiKeys.expires == -1,
666 .filter(or_(UserApiKeys.expires == -1,
661 UserApiKeys.expires >= time.time()))\
667 UserApiKeys.expires >= time.time()))\
662 .filter(UserApiKeys.role == UserApiKeys.ROLE_ALL).first()
668 .filter(UserApiKeys.role == UserApiKeys.ROLE_ALL).first()
663 if user_auth_token:
669 if user_auth_token:
664 user_auth_token = user_auth_token.api_key
670 user_auth_token = user_auth_token.api_key
665
671
666 return user_auth_token
672 return user_auth_token
667
673
668 @api_key.setter
674 @api_key.setter
669 def api_key(self, val):
675 def api_key(self, val):
670 # don't allow to set API key this is deprecated for now
676 # don't allow to set API key this is deprecated for now
671 self._api_key = None
677 self._api_key = None
672
678
673 @property
679 @property
674 def reviewer_pull_requests(self):
680 def reviewer_pull_requests(self):
675 return PullRequestReviewers.query() \
681 return PullRequestReviewers.query() \
676 .options(joinedload(PullRequestReviewers.pull_request)) \
682 .options(joinedload(PullRequestReviewers.pull_request)) \
677 .filter(PullRequestReviewers.user_id == self.user_id) \
683 .filter(PullRequestReviewers.user_id == self.user_id) \
678 .all()
684 .all()
679
685
680 @property
686 @property
681 def firstname(self):
687 def firstname(self):
682 # alias for future
688 # alias for future
683 return self.name
689 return self.name
684
690
685 @property
691 @property
686 def emails(self):
692 def emails(self):
687 other = UserEmailMap.query()\
693 other = UserEmailMap.query()\
688 .filter(UserEmailMap.user == self) \
694 .filter(UserEmailMap.user == self) \
689 .order_by(UserEmailMap.email_id.asc()) \
695 .order_by(UserEmailMap.email_id.asc()) \
690 .all()
696 .all()
691 return [self.email] + [x.email for x in other]
697 return [self.email] + [x.email for x in other]
692
698
693 @property
699 @property
694 def auth_tokens(self):
700 def auth_tokens(self):
695 auth_tokens = self.get_auth_tokens()
701 auth_tokens = self.get_auth_tokens()
696 return [x.api_key for x in auth_tokens]
702 return [x.api_key for x in auth_tokens]
697
703
698 def get_auth_tokens(self):
704 def get_auth_tokens(self):
699 return UserApiKeys.query()\
705 return UserApiKeys.query()\
700 .filter(UserApiKeys.user == self)\
706 .filter(UserApiKeys.user == self)\
701 .order_by(UserApiKeys.user_api_key_id.asc())\
707 .order_by(UserApiKeys.user_api_key_id.asc())\
702 .all()
708 .all()
703
709
704 @LazyProperty
710 @LazyProperty
705 def feed_token(self):
711 def feed_token(self):
706 return self.get_feed_token()
712 return self.get_feed_token()
707
713
708 def get_feed_token(self, cache=True):
714 def get_feed_token(self, cache=True):
709 feed_tokens = UserApiKeys.query()\
715 feed_tokens = UserApiKeys.query()\
710 .filter(UserApiKeys.user == self)\
716 .filter(UserApiKeys.user == self)\
711 .filter(UserApiKeys.role == UserApiKeys.ROLE_FEED)
717 .filter(UserApiKeys.role == UserApiKeys.ROLE_FEED)
712 if cache:
718 if cache:
713 feed_tokens = feed_tokens.options(
719 feed_tokens = feed_tokens.options(
714 FromCache("sql_cache_short", "get_user_feed_token_%s" % self.user_id))
720 FromCache("sql_cache_short", "get_user_feed_token_%s" % self.user_id))
715
721
716 feed_tokens = feed_tokens.all()
722 feed_tokens = feed_tokens.all()
717 if feed_tokens:
723 if feed_tokens:
718 return feed_tokens[0].api_key
724 return feed_tokens[0].api_key
719 return 'NO_FEED_TOKEN_AVAILABLE'
725 return 'NO_FEED_TOKEN_AVAILABLE'
720
726
721 @LazyProperty
727 @LazyProperty
722 def artifact_token(self):
728 def artifact_token(self):
723 return self.get_artifact_token()
729 return self.get_artifact_token()
724
730
725 def get_artifact_token(self, cache=True):
731 def get_artifact_token(self, cache=True):
726 artifacts_tokens = UserApiKeys.query()\
732 artifacts_tokens = UserApiKeys.query()\
727 .filter(UserApiKeys.user == self)\
733 .filter(UserApiKeys.user == self)\
728 .filter(UserApiKeys.role == UserApiKeys.ROLE_ARTIFACT_DOWNLOAD)
734 .filter(UserApiKeys.role == UserApiKeys.ROLE_ARTIFACT_DOWNLOAD)
729 if cache:
735 if cache:
730 artifacts_tokens = artifacts_tokens.options(
736 artifacts_tokens = artifacts_tokens.options(
731 FromCache("sql_cache_short", "get_user_artifact_token_%s" % self.user_id))
737 FromCache("sql_cache_short", "get_user_artifact_token_%s" % self.user_id))
732
738
733 artifacts_tokens = artifacts_tokens.all()
739 artifacts_tokens = artifacts_tokens.all()
734 if artifacts_tokens:
740 if artifacts_tokens:
735 return artifacts_tokens[0].api_key
741 return artifacts_tokens[0].api_key
736 return 'NO_ARTIFACT_TOKEN_AVAILABLE'
742 return 'NO_ARTIFACT_TOKEN_AVAILABLE'
737
743
738 @classmethod
744 @classmethod
739 def get(cls, user_id, cache=False):
745 def get(cls, user_id, cache=False):
740 if not user_id:
746 if not user_id:
741 return
747 return
742
748
743 user = cls.query()
749 user = cls.query()
744 if cache:
750 if cache:
745 user = user.options(
751 user = user.options(
746 FromCache("sql_cache_short", "get_users_%s" % user_id))
752 FromCache("sql_cache_short", "get_users_%s" % user_id))
747 return user.get(user_id)
753 return user.get(user_id)
748
754
749 @classmethod
755 @classmethod
750 def extra_valid_auth_tokens(cls, user, role=None):
756 def extra_valid_auth_tokens(cls, user, role=None):
751 tokens = UserApiKeys.query().filter(UserApiKeys.user == user)\
757 tokens = UserApiKeys.query().filter(UserApiKeys.user == user)\
752 .filter(or_(UserApiKeys.expires == -1,
758 .filter(or_(UserApiKeys.expires == -1,
753 UserApiKeys.expires >= time.time()))
759 UserApiKeys.expires >= time.time()))
754 if role:
760 if role:
755 tokens = tokens.filter(or_(UserApiKeys.role == role,
761 tokens = tokens.filter(or_(UserApiKeys.role == role,
756 UserApiKeys.role == UserApiKeys.ROLE_ALL))
762 UserApiKeys.role == UserApiKeys.ROLE_ALL))
757 return tokens.all()
763 return tokens.all()
758
764
759 def authenticate_by_token(self, auth_token, roles=None, scope_repo_id=None):
765 def authenticate_by_token(self, auth_token, roles=None, scope_repo_id=None):
760 from rhodecode.lib import auth
766 from rhodecode.lib import auth
761
767
762 log.debug('Trying to authenticate user: %s via auth-token, '
768 log.debug('Trying to authenticate user: %s via auth-token, '
763 'and roles: %s', self, roles)
769 'and roles: %s', self, roles)
764
770
765 if not auth_token:
771 if not auth_token:
766 return False
772 return False
767
773
768 roles = (roles or []) + [UserApiKeys.ROLE_ALL]
774 roles = (roles or []) + [UserApiKeys.ROLE_ALL]
769 tokens_q = UserApiKeys.query()\
775 tokens_q = UserApiKeys.query()\
770 .filter(UserApiKeys.user_id == self.user_id)\
776 .filter(UserApiKeys.user_id == self.user_id)\
771 .filter(or_(UserApiKeys.expires == -1,
777 .filter(or_(UserApiKeys.expires == -1,
772 UserApiKeys.expires >= time.time()))
778 UserApiKeys.expires >= time.time()))
773
779
774 tokens_q = tokens_q.filter(UserApiKeys.role.in_(roles))
780 tokens_q = tokens_q.filter(UserApiKeys.role.in_(roles))
775
781
776 crypto_backend = auth.crypto_backend()
782 crypto_backend = auth.crypto_backend()
777 enc_token_map = {}
783 enc_token_map = {}
778 plain_token_map = {}
784 plain_token_map = {}
779 for token in tokens_q:
785 for token in tokens_q:
780 if token.api_key.startswith(crypto_backend.ENC_PREF):
786 if token.api_key.startswith(crypto_backend.ENC_PREF):
781 enc_token_map[token.api_key] = token
787 enc_token_map[token.api_key] = token
782 else:
788 else:
783 plain_token_map[token.api_key] = token
789 plain_token_map[token.api_key] = token
784 log.debug(
790 log.debug(
785 'Found %s plain and %s encrypted tokens to check for authentication for this user',
791 'Found %s plain and %s encrypted tokens to check for authentication for this user',
786 len(plain_token_map), len(enc_token_map))
792 len(plain_token_map), len(enc_token_map))
787
793
788 # plain token match comes first
794 # plain token match comes first
789 match = plain_token_map.get(auth_token)
795 match = plain_token_map.get(auth_token)
790
796
791 # check encrypted tokens now
797 # check encrypted tokens now
792 if not match:
798 if not match:
793 for token_hash, token in enc_token_map.items():
799 for token_hash, token in enc_token_map.items():
794 # NOTE(marcink): this is expensive to calculate, but most secure
800 # NOTE(marcink): this is expensive to calculate, but most secure
795 if crypto_backend.hash_check(auth_token, token_hash):
801 if crypto_backend.hash_check(auth_token, token_hash):
796 match = token
802 match = token
797 break
803 break
798
804
799 if match:
805 if match:
800 log.debug('Found matching token %s', match)
806 log.debug('Found matching token %s', match)
801 if match.repo_id:
807 if match.repo_id:
802 log.debug('Found scope, checking for scope match of token %s', match)
808 log.debug('Found scope, checking for scope match of token %s', match)
803 if match.repo_id == scope_repo_id:
809 if match.repo_id == scope_repo_id:
804 return True
810 return True
805 else:
811 else:
806 log.debug(
812 log.debug(
807 'AUTH_TOKEN: scope mismatch, token has a set repo scope: %s, '
813 'AUTH_TOKEN: scope mismatch, token has a set repo scope: %s, '
808 'and calling scope is:%s, skipping further checks',
814 'and calling scope is:%s, skipping further checks',
809 match.repo, scope_repo_id)
815 match.repo, scope_repo_id)
810 return False
816 return False
811 else:
817 else:
812 return True
818 return True
813
819
814 return False
820 return False
815
821
816 @property
822 @property
817 def ip_addresses(self):
823 def ip_addresses(self):
818 ret = UserIpMap.query().filter(UserIpMap.user == self).all()
824 ret = UserIpMap.query().filter(UserIpMap.user == self).all()
819 return [x.ip_addr for x in ret]
825 return [x.ip_addr for x in ret]
820
826
821 @property
827 @property
822 def username_and_name(self):
828 def username_and_name(self):
823 return '%s (%s %s)' % (self.username, self.first_name, self.last_name)
829 return '%s (%s %s)' % (self.username, self.first_name, self.last_name)
824
830
825 @property
831 @property
826 def username_or_name_or_email(self):
832 def username_or_name_or_email(self):
827 full_name = self.full_name if self.full_name is not ' ' else None
833 full_name = self.full_name if self.full_name is not ' ' else None
828 return self.username or full_name or self.email
834 return self.username or full_name or self.email
829
835
830 @property
836 @property
831 def full_name(self):
837 def full_name(self):
832 return '%s %s' % (self.first_name, self.last_name)
838 return '%s %s' % (self.first_name, self.last_name)
833
839
834 @property
840 @property
835 def full_name_or_username(self):
841 def full_name_or_username(self):
836 return ('%s %s' % (self.first_name, self.last_name)
842 return ('%s %s' % (self.first_name, self.last_name)
837 if (self.first_name and self.last_name) else self.username)
843 if (self.first_name and self.last_name) else self.username)
838
844
839 @property
845 @property
840 def full_contact(self):
846 def full_contact(self):
841 return '%s %s <%s>' % (self.first_name, self.last_name, self.email)
847 return '%s %s <%s>' % (self.first_name, self.last_name, self.email)
842
848
843 @property
849 @property
844 def short_contact(self):
850 def short_contact(self):
845 return '%s %s' % (self.first_name, self.last_name)
851 return '%s %s' % (self.first_name, self.last_name)
846
852
847 @property
853 @property
848 def is_admin(self):
854 def is_admin(self):
849 return self.admin
855 return self.admin
850
856
851 def AuthUser(self, **kwargs):
857 def AuthUser(self, **kwargs):
852 """
858 """
853 Returns instance of AuthUser for this user
859 Returns instance of AuthUser for this user
854 """
860 """
855 from rhodecode.lib.auth import AuthUser
861 from rhodecode.lib.auth import AuthUser
856 return AuthUser(user_id=self.user_id, username=self.username, **kwargs)
862 return AuthUser(user_id=self.user_id, username=self.username, **kwargs)
857
863
858 @hybrid_property
864 @hybrid_property
859 def user_data(self):
865 def user_data(self):
860 if not self._user_data:
866 if not self._user_data:
861 return {}
867 return {}
862
868
863 try:
869 try:
864 return json.loads(self._user_data)
870 return json.loads(self._user_data)
865 except TypeError:
871 except TypeError:
866 return {}
872 return {}
867
873
868 @user_data.setter
874 @user_data.setter
869 def user_data(self, val):
875 def user_data(self, val):
870 if not isinstance(val, dict):
876 if not isinstance(val, dict):
871 raise Exception('user_data must be dict, got %s' % type(val))
877 raise Exception('user_data must be dict, got %s' % type(val))
872 try:
878 try:
873 self._user_data = json.dumps(val)
879 self._user_data = json.dumps(val)
874 except Exception:
880 except Exception:
875 log.error(traceback.format_exc())
881 log.error(traceback.format_exc())
876
882
877 @classmethod
883 @classmethod
878 def get_by_username(cls, username, case_insensitive=False,
884 def get_by_username(cls, username, case_insensitive=False,
879 cache=False, identity_cache=False):
885 cache=False, identity_cache=False):
880 session = Session()
886 session = Session()
881
887
882 if case_insensitive:
888 if case_insensitive:
883 q = cls.query().filter(
889 q = cls.query().filter(
884 func.lower(cls.username) == func.lower(username))
890 func.lower(cls.username) == func.lower(username))
885 else:
891 else:
886 q = cls.query().filter(cls.username == username)
892 q = cls.query().filter(cls.username == username)
887
893
888 if cache:
894 if cache:
889 if identity_cache:
895 if identity_cache:
890 val = cls.identity_cache(session, 'username', username)
896 val = cls.identity_cache(session, 'username', username)
891 if val:
897 if val:
892 return val
898 return val
893 else:
899 else:
894 cache_key = "get_user_by_name_%s" % _hash_key(username)
900 cache_key = "get_user_by_name_%s" % _hash_key(username)
895 q = q.options(
901 q = q.options(
896 FromCache("sql_cache_short", cache_key))
902 FromCache("sql_cache_short", cache_key))
897
903
898 return q.scalar()
904 return q.scalar()
899
905
900 @classmethod
906 @classmethod
901 def get_by_auth_token(cls, auth_token, cache=False):
907 def get_by_auth_token(cls, auth_token, cache=False):
902 q = UserApiKeys.query()\
908 q = UserApiKeys.query()\
903 .filter(UserApiKeys.api_key == auth_token)\
909 .filter(UserApiKeys.api_key == auth_token)\
904 .filter(or_(UserApiKeys.expires == -1,
910 .filter(or_(UserApiKeys.expires == -1,
905 UserApiKeys.expires >= time.time()))
911 UserApiKeys.expires >= time.time()))
906 if cache:
912 if cache:
907 q = q.options(
913 q = q.options(
908 FromCache("sql_cache_short", "get_auth_token_%s" % auth_token))
914 FromCache("sql_cache_short", "get_auth_token_%s" % auth_token))
909
915
910 match = q.first()
916 match = q.first()
911 if match:
917 if match:
912 return match.user
918 return match.user
913
919
914 @classmethod
920 @classmethod
915 def get_by_email(cls, email, case_insensitive=False, cache=False):
921 def get_by_email(cls, email, case_insensitive=False, cache=False):
916
922
917 if case_insensitive:
923 if case_insensitive:
918 q = cls.query().filter(func.lower(cls.email) == func.lower(email))
924 q = cls.query().filter(func.lower(cls.email) == func.lower(email))
919
925
920 else:
926 else:
921 q = cls.query().filter(cls.email == email)
927 q = cls.query().filter(cls.email == email)
922
928
923 email_key = _hash_key(email)
929 email_key = _hash_key(email)
924 if cache:
930 if cache:
925 q = q.options(
931 q = q.options(
926 FromCache("sql_cache_short", "get_email_key_%s" % email_key))
932 FromCache("sql_cache_short", "get_email_key_%s" % email_key))
927
933
928 ret = q.scalar()
934 ret = q.scalar()
929 if ret is None:
935 if ret is None:
930 q = UserEmailMap.query()
936 q = UserEmailMap.query()
931 # try fetching in alternate email map
937 # try fetching in alternate email map
932 if case_insensitive:
938 if case_insensitive:
933 q = q.filter(func.lower(UserEmailMap.email) == func.lower(email))
939 q = q.filter(func.lower(UserEmailMap.email) == func.lower(email))
934 else:
940 else:
935 q = q.filter(UserEmailMap.email == email)
941 q = q.filter(UserEmailMap.email == email)
936 q = q.options(joinedload(UserEmailMap.user))
942 q = q.options(joinedload(UserEmailMap.user))
937 if cache:
943 if cache:
938 q = q.options(
944 q = q.options(
939 FromCache("sql_cache_short", "get_email_map_key_%s" % email_key))
945 FromCache("sql_cache_short", "get_email_map_key_%s" % email_key))
940 ret = getattr(q.scalar(), 'user', None)
946 ret = getattr(q.scalar(), 'user', None)
941
947
942 return ret
948 return ret
943
949
944 @classmethod
950 @classmethod
945 def get_from_cs_author(cls, author):
951 def get_from_cs_author(cls, author):
946 """
952 """
947 Tries to get User objects out of commit author string
953 Tries to get User objects out of commit author string
948
954
949 :param author:
955 :param author:
950 """
956 """
951 from rhodecode.lib.helpers import email, author_name
957 from rhodecode.lib.helpers import email, author_name
952 # Valid email in the attribute passed, see if they're in the system
958 # Valid email in the attribute passed, see if they're in the system
953 _email = email(author)
959 _email = email(author)
954 if _email:
960 if _email:
955 user = cls.get_by_email(_email, case_insensitive=True)
961 user = cls.get_by_email(_email, case_insensitive=True)
956 if user:
962 if user:
957 return user
963 return user
958 # Maybe we can match by username?
964 # Maybe we can match by username?
959 _author = author_name(author)
965 _author = author_name(author)
960 user = cls.get_by_username(_author, case_insensitive=True)
966 user = cls.get_by_username(_author, case_insensitive=True)
961 if user:
967 if user:
962 return user
968 return user
963
969
964 def update_userdata(self, **kwargs):
970 def update_userdata(self, **kwargs):
965 usr = self
971 usr = self
966 old = usr.user_data
972 old = usr.user_data
967 old.update(**kwargs)
973 old.update(**kwargs)
968 usr.user_data = old
974 usr.user_data = old
969 Session().add(usr)
975 Session().add(usr)
970 log.debug('updated userdata with %s', kwargs)
976 log.debug('updated userdata with %s', kwargs)
971
977
972 def update_lastlogin(self):
978 def update_lastlogin(self):
973 """Update user lastlogin"""
979 """Update user lastlogin"""
974 self.last_login = datetime.datetime.now()
980 self.last_login = datetime.datetime.now()
975 Session().add(self)
981 Session().add(self)
976 log.debug('updated user %s lastlogin', self.username)
982 log.debug('updated user %s lastlogin', self.username)
977
983
978 def update_password(self, new_password):
984 def update_password(self, new_password):
979 from rhodecode.lib.auth import get_crypt_password
985 from rhodecode.lib.auth import get_crypt_password
980
986
981 self.password = get_crypt_password(new_password)
987 self.password = get_crypt_password(new_password)
982 Session().add(self)
988 Session().add(self)
983
989
984 @classmethod
990 @classmethod
985 def get_first_super_admin(cls):
991 def get_first_super_admin(cls):
986 user = User.query()\
992 user = User.query()\
987 .filter(User.admin == true()) \
993 .filter(User.admin == true()) \
988 .order_by(User.user_id.asc()) \
994 .order_by(User.user_id.asc()) \
989 .first()
995 .first()
990
996
991 if user is None:
997 if user is None:
992 raise Exception('FATAL: Missing administrative account!')
998 raise Exception('FATAL: Missing administrative account!')
993 return user
999 return user
994
1000
995 @classmethod
1001 @classmethod
996 def get_all_super_admins(cls, only_active=False):
1002 def get_all_super_admins(cls, only_active=False):
997 """
1003 """
998 Returns all admin accounts sorted by username
1004 Returns all admin accounts sorted by username
999 """
1005 """
1000 qry = User.query().filter(User.admin == true()).order_by(User.username.asc())
1006 qry = User.query().filter(User.admin == true()).order_by(User.username.asc())
1001 if only_active:
1007 if only_active:
1002 qry = qry.filter(User.active == true())
1008 qry = qry.filter(User.active == true())
1003 return qry.all()
1009 return qry.all()
1004
1010
1005 @classmethod
1011 @classmethod
1006 def get_default_user(cls, cache=False, refresh=False):
1012 def get_default_user(cls, cache=False, refresh=False):
1007 user = User.get_by_username(User.DEFAULT_USER, cache=cache)
1013 user = User.get_by_username(User.DEFAULT_USER, cache=cache)
1008 if user is None:
1014 if user is None:
1009 raise Exception('FATAL: Missing default account!')
1015 raise Exception('FATAL: Missing default account!')
1010 if refresh:
1016 if refresh:
1011 # The default user might be based on outdated state which
1017 # The default user might be based on outdated state which
1012 # has been loaded from the cache.
1018 # has been loaded from the cache.
1013 # A call to refresh() ensures that the
1019 # A call to refresh() ensures that the
1014 # latest state from the database is used.
1020 # latest state from the database is used.
1015 Session().refresh(user)
1021 Session().refresh(user)
1016 return user
1022 return user
1017
1023
1018 def _get_default_perms(self, user, suffix=''):
1024 def _get_default_perms(self, user, suffix=''):
1019 from rhodecode.model.permission import PermissionModel
1025 from rhodecode.model.permission import PermissionModel
1020 return PermissionModel().get_default_perms(user.user_perms, suffix)
1026 return PermissionModel().get_default_perms(user.user_perms, suffix)
1021
1027
1022 def get_default_perms(self, suffix=''):
1028 def get_default_perms(self, suffix=''):
1023 return self._get_default_perms(self, suffix)
1029 return self._get_default_perms(self, suffix)
1024
1030
1025 def get_api_data(self, include_secrets=False, details='full'):
1031 def get_api_data(self, include_secrets=False, details='full'):
1026 """
1032 """
1027 Common function for generating user related data for API
1033 Common function for generating user related data for API
1028
1034
1029 :param include_secrets: By default secrets in the API data will be replaced
1035 :param include_secrets: By default secrets in the API data will be replaced
1030 by a placeholder value to prevent exposing this data by accident. In case
1036 by a placeholder value to prevent exposing this data by accident. In case
1031 this data shall be exposed, set this flag to ``True``.
1037 this data shall be exposed, set this flag to ``True``.
1032
1038
1033 :param details: details can be 'basic|full' basic gives only a subset of
1039 :param details: details can be 'basic|full' basic gives only a subset of
1034 the available user information that includes user_id, name and emails.
1040 the available user information that includes user_id, name and emails.
1035 """
1041 """
1036 user = self
1042 user = self
1037 user_data = self.user_data
1043 user_data = self.user_data
1038 data = {
1044 data = {
1039 'user_id': user.user_id,
1045 'user_id': user.user_id,
1040 'username': user.username,
1046 'username': user.username,
1041 'firstname': user.name,
1047 'firstname': user.name,
1042 'lastname': user.lastname,
1048 'lastname': user.lastname,
1043 'email': user.email,
1049 'email': user.email,
1044 'emails': user.emails,
1050 'emails': user.emails,
1045 }
1051 }
1046 if details == 'basic':
1052 if details == 'basic':
1047 return data
1053 return data
1048
1054
1049 auth_token_length = 40
1055 auth_token_length = 40
1050 auth_token_replacement = '*' * auth_token_length
1056 auth_token_replacement = '*' * auth_token_length
1051
1057
1052 extras = {
1058 extras = {
1053 'auth_tokens': [auth_token_replacement],
1059 'auth_tokens': [auth_token_replacement],
1054 'active': user.active,
1060 'active': user.active,
1055 'admin': user.admin,
1061 'admin': user.admin,
1056 'extern_type': user.extern_type,
1062 'extern_type': user.extern_type,
1057 'extern_name': user.extern_name,
1063 'extern_name': user.extern_name,
1058 'last_login': user.last_login,
1064 'last_login': user.last_login,
1059 'last_activity': user.last_activity,
1065 'last_activity': user.last_activity,
1060 'ip_addresses': user.ip_addresses,
1066 'ip_addresses': user.ip_addresses,
1061 'language': user_data.get('language')
1067 'language': user_data.get('language')
1062 }
1068 }
1063 data.update(extras)
1069 data.update(extras)
1064
1070
1065 if include_secrets:
1071 if include_secrets:
1066 data['auth_tokens'] = user.auth_tokens
1072 data['auth_tokens'] = user.auth_tokens
1067 return data
1073 return data
1068
1074
1069 def __json__(self):
1075 def __json__(self):
1070 data = {
1076 data = {
1071 'full_name': self.full_name,
1077 'full_name': self.full_name,
1072 'full_name_or_username': self.full_name_or_username,
1078 'full_name_or_username': self.full_name_or_username,
1073 'short_contact': self.short_contact,
1079 'short_contact': self.short_contact,
1074 'full_contact': self.full_contact,
1080 'full_contact': self.full_contact,
1075 }
1081 }
1076 data.update(self.get_api_data())
1082 data.update(self.get_api_data())
1077 return data
1083 return data
1078
1084
1079
1085
1080 class UserApiKeys(Base, BaseModel):
1086 class UserApiKeys(Base, BaseModel):
1081 __tablename__ = 'user_api_keys'
1087 __tablename__ = 'user_api_keys'
1082 __table_args__ = (
1088 __table_args__ = (
1083 Index('uak_api_key_idx', 'api_key'),
1089 Index('uak_api_key_idx', 'api_key'),
1084 Index('uak_api_key_expires_idx', 'api_key', 'expires'),
1090 Index('uak_api_key_expires_idx', 'api_key', 'expires'),
1085 base_table_args
1091 base_table_args
1086 )
1092 )
1087 __mapper_args__ = {}
1093 __mapper_args__ = {}
1088
1094
1089 # ApiKey role
1095 # ApiKey role
1090 ROLE_ALL = 'token_role_all'
1096 ROLE_ALL = 'token_role_all'
1091 ROLE_HTTP = 'token_role_http'
1097 ROLE_HTTP = 'token_role_http'
1092 ROLE_VCS = 'token_role_vcs'
1098 ROLE_VCS = 'token_role_vcs'
1093 ROLE_API = 'token_role_api'
1099 ROLE_API = 'token_role_api'
1094 ROLE_FEED = 'token_role_feed'
1100 ROLE_FEED = 'token_role_feed'
1095 ROLE_ARTIFACT_DOWNLOAD = 'role_artifact_download'
1101 ROLE_ARTIFACT_DOWNLOAD = 'role_artifact_download'
1096 ROLE_PASSWORD_RESET = 'token_password_reset'
1102 ROLE_PASSWORD_RESET = 'token_password_reset'
1097
1103
1098 ROLES = [ROLE_ALL, ROLE_HTTP, ROLE_VCS, ROLE_API, ROLE_FEED, ROLE_ARTIFACT_DOWNLOAD]
1104 ROLES = [ROLE_ALL, ROLE_HTTP, ROLE_VCS, ROLE_API, ROLE_FEED, ROLE_ARTIFACT_DOWNLOAD]
1099
1105
1100 user_api_key_id = Column("user_api_key_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1106 user_api_key_id = Column("user_api_key_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1101 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1107 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1102 api_key = Column("api_key", String(255), nullable=False, unique=True)
1108 api_key = Column("api_key", String(255), nullable=False, unique=True)
1103 description = Column('description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
1109 description = Column('description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
1104 expires = Column('expires', Float(53), nullable=False)
1110 expires = Column('expires', Float(53), nullable=False)
1105 role = Column('role', String(255), nullable=True)
1111 role = Column('role', String(255), nullable=True)
1106 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1112 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1107
1113
1108 # scope columns
1114 # scope columns
1109 repo_id = Column(
1115 repo_id = Column(
1110 'repo_id', Integer(), ForeignKey('repositories.repo_id'),
1116 'repo_id', Integer(), ForeignKey('repositories.repo_id'),
1111 nullable=True, unique=None, default=None)
1117 nullable=True, unique=None, default=None)
1112 repo = relationship('Repository', lazy='joined')
1118 repo = relationship('Repository', lazy='joined')
1113
1119
1114 repo_group_id = Column(
1120 repo_group_id = Column(
1115 'repo_group_id', Integer(), ForeignKey('groups.group_id'),
1121 'repo_group_id', Integer(), ForeignKey('groups.group_id'),
1116 nullable=True, unique=None, default=None)
1122 nullable=True, unique=None, default=None)
1117 repo_group = relationship('RepoGroup', lazy='joined')
1123 repo_group = relationship('RepoGroup', lazy='joined')
1118
1124
1119 user = relationship('User', lazy='joined')
1125 user = relationship('User', lazy='joined')
1120
1126
1121 def __unicode__(self):
1127 def __unicode__(self):
1122 return u"<%s('%s')>" % (self.__class__.__name__, self.role)
1128 return u"<%s('%s')>" % (self.__class__.__name__, self.role)
1123
1129
1124 def __json__(self):
1130 def __json__(self):
1125 data = {
1131 data = {
1126 'auth_token': self.api_key,
1132 'auth_token': self.api_key,
1127 'role': self.role,
1133 'role': self.role,
1128 'scope': self.scope_humanized,
1134 'scope': self.scope_humanized,
1129 'expired': self.expired
1135 'expired': self.expired
1130 }
1136 }
1131 return data
1137 return data
1132
1138
1133 def get_api_data(self, include_secrets=False):
1139 def get_api_data(self, include_secrets=False):
1134 data = self.__json__()
1140 data = self.__json__()
1135 if include_secrets:
1141 if include_secrets:
1136 return data
1142 return data
1137 else:
1143 else:
1138 data['auth_token'] = self.token_obfuscated
1144 data['auth_token'] = self.token_obfuscated
1139 return data
1145 return data
1140
1146
1141 @hybrid_property
1147 @hybrid_property
1142 def description_safe(self):
1148 def description_safe(self):
1143 from rhodecode.lib import helpers as h
1149 from rhodecode.lib import helpers as h
1144 return h.escape(self.description)
1150 return h.escape(self.description)
1145
1151
1146 @property
1152 @property
1147 def expired(self):
1153 def expired(self):
1148 if self.expires == -1:
1154 if self.expires == -1:
1149 return False
1155 return False
1150 return time.time() > self.expires
1156 return time.time() > self.expires
1151
1157
1152 @classmethod
1158 @classmethod
1153 def _get_role_name(cls, role):
1159 def _get_role_name(cls, role):
1154 return {
1160 return {
1155 cls.ROLE_ALL: _('all'),
1161 cls.ROLE_ALL: _('all'),
1156 cls.ROLE_HTTP: _('http/web interface'),
1162 cls.ROLE_HTTP: _('http/web interface'),
1157 cls.ROLE_VCS: _('vcs (git/hg/svn protocol)'),
1163 cls.ROLE_VCS: _('vcs (git/hg/svn protocol)'),
1158 cls.ROLE_API: _('api calls'),
1164 cls.ROLE_API: _('api calls'),
1159 cls.ROLE_FEED: _('feed access'),
1165 cls.ROLE_FEED: _('feed access'),
1160 cls.ROLE_ARTIFACT_DOWNLOAD: _('artifacts downloads'),
1166 cls.ROLE_ARTIFACT_DOWNLOAD: _('artifacts downloads'),
1161 }.get(role, role)
1167 }.get(role, role)
1162
1168
1163 @property
1169 @property
1164 def role_humanized(self):
1170 def role_humanized(self):
1165 return self._get_role_name(self.role)
1171 return self._get_role_name(self.role)
1166
1172
1167 def _get_scope(self):
1173 def _get_scope(self):
1168 if self.repo:
1174 if self.repo:
1169 return 'Repository: {}'.format(self.repo.repo_name)
1175 return 'Repository: {}'.format(self.repo.repo_name)
1170 if self.repo_group:
1176 if self.repo_group:
1171 return 'RepositoryGroup: {} (recursive)'.format(self.repo_group.group_name)
1177 return 'RepositoryGroup: {} (recursive)'.format(self.repo_group.group_name)
1172 return 'Global'
1178 return 'Global'
1173
1179
1174 @property
1180 @property
1175 def scope_humanized(self):
1181 def scope_humanized(self):
1176 return self._get_scope()
1182 return self._get_scope()
1177
1183
1178 @property
1184 @property
1179 def token_obfuscated(self):
1185 def token_obfuscated(self):
1180 if self.api_key:
1186 if self.api_key:
1181 return self.api_key[:4] + "****"
1187 return self.api_key[:4] + "****"
1182
1188
1183
1189
1184 class UserEmailMap(Base, BaseModel):
1190 class UserEmailMap(Base, BaseModel):
1185 __tablename__ = 'user_email_map'
1191 __tablename__ = 'user_email_map'
1186 __table_args__ = (
1192 __table_args__ = (
1187 Index('uem_email_idx', 'email'),
1193 Index('uem_email_idx', 'email'),
1188 UniqueConstraint('email'),
1194 UniqueConstraint('email'),
1189 base_table_args
1195 base_table_args
1190 )
1196 )
1191 __mapper_args__ = {}
1197 __mapper_args__ = {}
1192
1198
1193 email_id = Column("email_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1199 email_id = Column("email_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1194 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1200 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1195 _email = Column("email", String(255), nullable=True, unique=False, default=None)
1201 _email = Column("email", String(255), nullable=True, unique=False, default=None)
1196 user = relationship('User', lazy='joined')
1202 user = relationship('User', lazy='joined')
1197
1203
1198 @validates('_email')
1204 @validates('_email')
1199 def validate_email(self, key, email):
1205 def validate_email(self, key, email):
1200 # check if this email is not main one
1206 # check if this email is not main one
1201 main_email = Session().query(User).filter(User.email == email).scalar()
1207 main_email = Session().query(User).filter(User.email == email).scalar()
1202 if main_email is not None:
1208 if main_email is not None:
1203 raise AttributeError('email %s is present is user table' % email)
1209 raise AttributeError('email %s is present is user table' % email)
1204 return email
1210 return email
1205
1211
1206 @hybrid_property
1212 @hybrid_property
1207 def email(self):
1213 def email(self):
1208 return self._email
1214 return self._email
1209
1215
1210 @email.setter
1216 @email.setter
1211 def email(self, val):
1217 def email(self, val):
1212 self._email = val.lower() if val else None
1218 self._email = val.lower() if val else None
1213
1219
1214
1220
1215 class UserIpMap(Base, BaseModel):
1221 class UserIpMap(Base, BaseModel):
1216 __tablename__ = 'user_ip_map'
1222 __tablename__ = 'user_ip_map'
1217 __table_args__ = (
1223 __table_args__ = (
1218 UniqueConstraint('user_id', 'ip_addr'),
1224 UniqueConstraint('user_id', 'ip_addr'),
1219 base_table_args
1225 base_table_args
1220 )
1226 )
1221 __mapper_args__ = {}
1227 __mapper_args__ = {}
1222
1228
1223 ip_id = Column("ip_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1229 ip_id = Column("ip_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1224 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1230 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1225 ip_addr = Column("ip_addr", String(255), nullable=True, unique=False, default=None)
1231 ip_addr = Column("ip_addr", String(255), nullable=True, unique=False, default=None)
1226 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
1232 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
1227 description = Column("description", String(10000), nullable=True, unique=None, default=None)
1233 description = Column("description", String(10000), nullable=True, unique=None, default=None)
1228 user = relationship('User', lazy='joined')
1234 user = relationship('User', lazy='joined')
1229
1235
1230 @hybrid_property
1236 @hybrid_property
1231 def description_safe(self):
1237 def description_safe(self):
1232 from rhodecode.lib import helpers as h
1238 from rhodecode.lib import helpers as h
1233 return h.escape(self.description)
1239 return h.escape(self.description)
1234
1240
1235 @classmethod
1241 @classmethod
1236 def _get_ip_range(cls, ip_addr):
1242 def _get_ip_range(cls, ip_addr):
1237 net = ipaddress.ip_network(safe_unicode(ip_addr), strict=False)
1243 net = ipaddress.ip_network(safe_unicode(ip_addr), strict=False)
1238 return [str(net.network_address), str(net.broadcast_address)]
1244 return [str(net.network_address), str(net.broadcast_address)]
1239
1245
1240 def __json__(self):
1246 def __json__(self):
1241 return {
1247 return {
1242 'ip_addr': self.ip_addr,
1248 'ip_addr': self.ip_addr,
1243 'ip_range': self._get_ip_range(self.ip_addr),
1249 'ip_range': self._get_ip_range(self.ip_addr),
1244 }
1250 }
1245
1251
1246 def __unicode__(self):
1252 def __unicode__(self):
1247 return u"<%s('user_id:%s=>%s')>" % (self.__class__.__name__,
1253 return u"<%s('user_id:%s=>%s')>" % (self.__class__.__name__,
1248 self.user_id, self.ip_addr)
1254 self.user_id, self.ip_addr)
1249
1255
1250
1256
1251 class UserSshKeys(Base, BaseModel):
1257 class UserSshKeys(Base, BaseModel):
1252 __tablename__ = 'user_ssh_keys'
1258 __tablename__ = 'user_ssh_keys'
1253 __table_args__ = (
1259 __table_args__ = (
1254 Index('usk_ssh_key_fingerprint_idx', 'ssh_key_fingerprint'),
1260 Index('usk_ssh_key_fingerprint_idx', 'ssh_key_fingerprint'),
1255
1261
1256 UniqueConstraint('ssh_key_fingerprint'),
1262 UniqueConstraint('ssh_key_fingerprint'),
1257
1263
1258 base_table_args
1264 base_table_args
1259 )
1265 )
1260 __mapper_args__ = {}
1266 __mapper_args__ = {}
1261
1267
1262 ssh_key_id = Column('ssh_key_id', Integer(), nullable=False, unique=True, default=None, primary_key=True)
1268 ssh_key_id = Column('ssh_key_id', Integer(), nullable=False, unique=True, default=None, primary_key=True)
1263 ssh_key_data = Column('ssh_key_data', String(10240), nullable=False, unique=None, default=None)
1269 ssh_key_data = Column('ssh_key_data', String(10240), nullable=False, unique=None, default=None)
1264 ssh_key_fingerprint = Column('ssh_key_fingerprint', String(255), nullable=False, unique=None, default=None)
1270 ssh_key_fingerprint = Column('ssh_key_fingerprint', String(255), nullable=False, unique=None, default=None)
1265
1271
1266 description = Column('description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
1272 description = Column('description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
1267
1273
1268 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1274 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1269 accessed_on = Column('accessed_on', DateTime(timezone=False), nullable=True, default=None)
1275 accessed_on = Column('accessed_on', DateTime(timezone=False), nullable=True, default=None)
1270 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1276 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1271
1277
1272 user = relationship('User', lazy='joined')
1278 user = relationship('User', lazy='joined')
1273
1279
1274 def __json__(self):
1280 def __json__(self):
1275 data = {
1281 data = {
1276 'ssh_fingerprint': self.ssh_key_fingerprint,
1282 'ssh_fingerprint': self.ssh_key_fingerprint,
1277 'description': self.description,
1283 'description': self.description,
1278 'created_on': self.created_on
1284 'created_on': self.created_on
1279 }
1285 }
1280 return data
1286 return data
1281
1287
1282 def get_api_data(self):
1288 def get_api_data(self):
1283 data = self.__json__()
1289 data = self.__json__()
1284 return data
1290 return data
1285
1291
1286
1292
1287 class UserLog(Base, BaseModel):
1293 class UserLog(Base, BaseModel):
1288 __tablename__ = 'user_logs'
1294 __tablename__ = 'user_logs'
1289 __table_args__ = (
1295 __table_args__ = (
1290 base_table_args,
1296 base_table_args,
1291 )
1297 )
1292
1298
1293 VERSION_1 = 'v1'
1299 VERSION_1 = 'v1'
1294 VERSION_2 = 'v2'
1300 VERSION_2 = 'v2'
1295 VERSIONS = [VERSION_1, VERSION_2]
1301 VERSIONS = [VERSION_1, VERSION_2]
1296
1302
1297 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1303 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1298 user_id = Column("user_id", Integer(), ForeignKey('users.user_id',ondelete='SET NULL'), nullable=True, unique=None, default=None)
1304 user_id = Column("user_id", Integer(), ForeignKey('users.user_id',ondelete='SET NULL'), nullable=True, unique=None, default=None)
1299 username = Column("username", String(255), nullable=True, unique=None, default=None)
1305 username = Column("username", String(255), nullable=True, unique=None, default=None)
1300 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id', ondelete='SET NULL'), nullable=True, unique=None, default=None)
1306 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id', ondelete='SET NULL'), nullable=True, unique=None, default=None)
1301 repository_name = Column("repository_name", String(255), nullable=True, unique=None, default=None)
1307 repository_name = Column("repository_name", String(255), nullable=True, unique=None, default=None)
1302 user_ip = Column("user_ip", String(255), nullable=True, unique=None, default=None)
1308 user_ip = Column("user_ip", String(255), nullable=True, unique=None, default=None)
1303 action = Column("action", Text().with_variant(Text(1200000), 'mysql'), nullable=True, unique=None, default=None)
1309 action = Column("action", Text().with_variant(Text(1200000), 'mysql'), nullable=True, unique=None, default=None)
1304 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
1310 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
1305
1311
1306 version = Column("version", String(255), nullable=True, default=VERSION_1)
1312 version = Column("version", String(255), nullable=True, default=VERSION_1)
1307 user_data = Column('user_data_json', MutationObj.as_mutable(JsonType(dialect_map=dict(mysql=LONGTEXT()))))
1313 user_data = Column('user_data_json', MutationObj.as_mutable(JsonType(dialect_map=dict(mysql=LONGTEXT()))))
1308 action_data = Column('action_data_json', MutationObj.as_mutable(JsonType(dialect_map=dict(mysql=LONGTEXT()))))
1314 action_data = Column('action_data_json', MutationObj.as_mutable(JsonType(dialect_map=dict(mysql=LONGTEXT()))))
1309
1315
1310 def __unicode__(self):
1316 def __unicode__(self):
1311 return u"<%s('id:%s:%s')>" % (
1317 return u"<%s('id:%s:%s')>" % (
1312 self.__class__.__name__, self.repository_name, self.action)
1318 self.__class__.__name__, self.repository_name, self.action)
1313
1319
1314 def __json__(self):
1320 def __json__(self):
1315 return {
1321 return {
1316 'user_id': self.user_id,
1322 'user_id': self.user_id,
1317 'username': self.username,
1323 'username': self.username,
1318 'repository_id': self.repository_id,
1324 'repository_id': self.repository_id,
1319 'repository_name': self.repository_name,
1325 'repository_name': self.repository_name,
1320 'user_ip': self.user_ip,
1326 'user_ip': self.user_ip,
1321 'action_date': self.action_date,
1327 'action_date': self.action_date,
1322 'action': self.action,
1328 'action': self.action,
1323 }
1329 }
1324
1330
1325 @hybrid_property
1331 @hybrid_property
1326 def entry_id(self):
1332 def entry_id(self):
1327 return self.user_log_id
1333 return self.user_log_id
1328
1334
1329 @property
1335 @property
1330 def action_as_day(self):
1336 def action_as_day(self):
1331 return datetime.date(*self.action_date.timetuple()[:3])
1337 return datetime.date(*self.action_date.timetuple()[:3])
1332
1338
1333 user = relationship('User')
1339 user = relationship('User')
1334 repository = relationship('Repository', cascade='')
1340 repository = relationship('Repository', cascade='')
1335
1341
1336
1342
1337 class UserGroup(Base, BaseModel):
1343 class UserGroup(Base, BaseModel):
1338 __tablename__ = 'users_groups'
1344 __tablename__ = 'users_groups'
1339 __table_args__ = (
1345 __table_args__ = (
1340 base_table_args,
1346 base_table_args,
1341 )
1347 )
1342
1348
1343 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1349 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1344 users_group_name = Column("users_group_name", String(255), nullable=False, unique=True, default=None)
1350 users_group_name = Column("users_group_name", String(255), nullable=False, unique=True, default=None)
1345 user_group_description = Column("user_group_description", String(10000), nullable=True, unique=None, default=None)
1351 user_group_description = Column("user_group_description", String(10000), nullable=True, unique=None, default=None)
1346 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
1352 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
1347 inherit_default_permissions = Column("users_group_inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
1353 inherit_default_permissions = Column("users_group_inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
1348 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
1354 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
1349 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1355 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1350 _group_data = Column("group_data", LargeBinary(), nullable=True) # JSON data
1356 _group_data = Column("group_data", LargeBinary(), nullable=True) # JSON data
1351
1357
1352 members = relationship('UserGroupMember', cascade="all, delete-orphan", lazy="joined")
1358 members = relationship('UserGroupMember', cascade="all, delete-orphan", lazy="joined")
1353 users_group_to_perm = relationship('UserGroupToPerm', cascade='all')
1359 users_group_to_perm = relationship('UserGroupToPerm', cascade='all')
1354 users_group_repo_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1360 users_group_repo_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1355 users_group_repo_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
1361 users_group_repo_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
1356 user_user_group_to_perm = relationship('UserUserGroupToPerm', cascade='all')
1362 user_user_group_to_perm = relationship('UserUserGroupToPerm', cascade='all')
1357 user_group_user_group_to_perm = relationship('UserGroupUserGroupToPerm ', primaryjoin="UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id", cascade='all')
1363 user_group_user_group_to_perm = relationship('UserGroupUserGroupToPerm ', primaryjoin="UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id", cascade='all')
1358
1364
1359 user_group_review_rules = relationship('RepoReviewRuleUserGroup', cascade='all')
1365 user_group_review_rules = relationship('RepoReviewRuleUserGroup', cascade='all')
1360 user = relationship('User', primaryjoin="User.user_id==UserGroup.user_id")
1366 user = relationship('User', primaryjoin="User.user_id==UserGroup.user_id")
1361
1367
1362 @classmethod
1368 @classmethod
1363 def _load_group_data(cls, column):
1369 def _load_group_data(cls, column):
1364 if not column:
1370 if not column:
1365 return {}
1371 return {}
1366
1372
1367 try:
1373 try:
1368 return json.loads(column) or {}
1374 return json.loads(column) or {}
1369 except TypeError:
1375 except TypeError:
1370 return {}
1376 return {}
1371
1377
1372 @hybrid_property
1378 @hybrid_property
1373 def description_safe(self):
1379 def description_safe(self):
1374 from rhodecode.lib import helpers as h
1380 from rhodecode.lib import helpers as h
1375 return h.escape(self.user_group_description)
1381 return h.escape(self.user_group_description)
1376
1382
1377 @hybrid_property
1383 @hybrid_property
1378 def group_data(self):
1384 def group_data(self):
1379 return self._load_group_data(self._group_data)
1385 return self._load_group_data(self._group_data)
1380
1386
1381 @group_data.expression
1387 @group_data.expression
1382 def group_data(self, **kwargs):
1388 def group_data(self, **kwargs):
1383 return self._group_data
1389 return self._group_data
1384
1390
1385 @group_data.setter
1391 @group_data.setter
1386 def group_data(self, val):
1392 def group_data(self, val):
1387 try:
1393 try:
1388 self._group_data = json.dumps(val)
1394 self._group_data = json.dumps(val)
1389 except Exception:
1395 except Exception:
1390 log.error(traceback.format_exc())
1396 log.error(traceback.format_exc())
1391
1397
1392 @classmethod
1398 @classmethod
1393 def _load_sync(cls, group_data):
1399 def _load_sync(cls, group_data):
1394 if group_data:
1400 if group_data:
1395 return group_data.get('extern_type')
1401 return group_data.get('extern_type')
1396
1402
1397 @property
1403 @property
1398 def sync(self):
1404 def sync(self):
1399 return self._load_sync(self.group_data)
1405 return self._load_sync(self.group_data)
1400
1406
1401 def __unicode__(self):
1407 def __unicode__(self):
1402 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
1408 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
1403 self.users_group_id,
1409 self.users_group_id,
1404 self.users_group_name)
1410 self.users_group_name)
1405
1411
1406 @classmethod
1412 @classmethod
1407 def get_by_group_name(cls, group_name, cache=False,
1413 def get_by_group_name(cls, group_name, cache=False,
1408 case_insensitive=False):
1414 case_insensitive=False):
1409 if case_insensitive:
1415 if case_insensitive:
1410 q = cls.query().filter(func.lower(cls.users_group_name) ==
1416 q = cls.query().filter(func.lower(cls.users_group_name) ==
1411 func.lower(group_name))
1417 func.lower(group_name))
1412
1418
1413 else:
1419 else:
1414 q = cls.query().filter(cls.users_group_name == group_name)
1420 q = cls.query().filter(cls.users_group_name == group_name)
1415 if cache:
1421 if cache:
1416 q = q.options(
1422 q = q.options(
1417 FromCache("sql_cache_short", "get_group_%s" % _hash_key(group_name)))
1423 FromCache("sql_cache_short", "get_group_%s" % _hash_key(group_name)))
1418 return q.scalar()
1424 return q.scalar()
1419
1425
1420 @classmethod
1426 @classmethod
1421 def get(cls, user_group_id, cache=False):
1427 def get(cls, user_group_id, cache=False):
1422 if not user_group_id:
1428 if not user_group_id:
1423 return
1429 return
1424
1430
1425 user_group = cls.query()
1431 user_group = cls.query()
1426 if cache:
1432 if cache:
1427 user_group = user_group.options(
1433 user_group = user_group.options(
1428 FromCache("sql_cache_short", "get_users_group_%s" % user_group_id))
1434 FromCache("sql_cache_short", "get_users_group_%s" % user_group_id))
1429 return user_group.get(user_group_id)
1435 return user_group.get(user_group_id)
1430
1436
1431 def permissions(self, with_admins=True, with_owner=True,
1437 def permissions(self, with_admins=True, with_owner=True,
1432 expand_from_user_groups=False):
1438 expand_from_user_groups=False):
1433 """
1439 """
1434 Permissions for user groups
1440 Permissions for user groups
1435 """
1441 """
1436 _admin_perm = 'usergroup.admin'
1442 _admin_perm = 'usergroup.admin'
1437
1443
1438 owner_row = []
1444 owner_row = []
1439 if with_owner:
1445 if with_owner:
1440 usr = AttributeDict(self.user.get_dict())
1446 usr = AttributeDict(self.user.get_dict())
1441 usr.owner_row = True
1447 usr.owner_row = True
1442 usr.permission = _admin_perm
1448 usr.permission = _admin_perm
1443 owner_row.append(usr)
1449 owner_row.append(usr)
1444
1450
1445 super_admin_ids = []
1451 super_admin_ids = []
1446 super_admin_rows = []
1452 super_admin_rows = []
1447 if with_admins:
1453 if with_admins:
1448 for usr in User.get_all_super_admins():
1454 for usr in User.get_all_super_admins():
1449 super_admin_ids.append(usr.user_id)
1455 super_admin_ids.append(usr.user_id)
1450 # if this admin is also owner, don't double the record
1456 # if this admin is also owner, don't double the record
1451 if usr.user_id == owner_row[0].user_id:
1457 if usr.user_id == owner_row[0].user_id:
1452 owner_row[0].admin_row = True
1458 owner_row[0].admin_row = True
1453 else:
1459 else:
1454 usr = AttributeDict(usr.get_dict())
1460 usr = AttributeDict(usr.get_dict())
1455 usr.admin_row = True
1461 usr.admin_row = True
1456 usr.permission = _admin_perm
1462 usr.permission = _admin_perm
1457 super_admin_rows.append(usr)
1463 super_admin_rows.append(usr)
1458
1464
1459 q = UserUserGroupToPerm.query().filter(UserUserGroupToPerm.user_group == self)
1465 q = UserUserGroupToPerm.query().filter(UserUserGroupToPerm.user_group == self)
1460 q = q.options(joinedload(UserUserGroupToPerm.user_group),
1466 q = q.options(joinedload(UserUserGroupToPerm.user_group),
1461 joinedload(UserUserGroupToPerm.user),
1467 joinedload(UserUserGroupToPerm.user),
1462 joinedload(UserUserGroupToPerm.permission),)
1468 joinedload(UserUserGroupToPerm.permission),)
1463
1469
1464 # get owners and admins and permissions. We do a trick of re-writing
1470 # get owners and admins and permissions. We do a trick of re-writing
1465 # objects from sqlalchemy to named-tuples due to sqlalchemy session
1471 # objects from sqlalchemy to named-tuples due to sqlalchemy session
1466 # has a global reference and changing one object propagates to all
1472 # has a global reference and changing one object propagates to all
1467 # others. This means if admin is also an owner admin_row that change
1473 # others. This means if admin is also an owner admin_row that change
1468 # would propagate to both objects
1474 # would propagate to both objects
1469 perm_rows = []
1475 perm_rows = []
1470 for _usr in q.all():
1476 for _usr in q.all():
1471 usr = AttributeDict(_usr.user.get_dict())
1477 usr = AttributeDict(_usr.user.get_dict())
1472 # if this user is also owner/admin, mark as duplicate record
1478 # if this user is also owner/admin, mark as duplicate record
1473 if usr.user_id == owner_row[0].user_id or usr.user_id in super_admin_ids:
1479 if usr.user_id == owner_row[0].user_id or usr.user_id in super_admin_ids:
1474 usr.duplicate_perm = True
1480 usr.duplicate_perm = True
1475 usr.permission = _usr.permission.permission_name
1481 usr.permission = _usr.permission.permission_name
1476 perm_rows.append(usr)
1482 perm_rows.append(usr)
1477
1483
1478 # filter the perm rows by 'default' first and then sort them by
1484 # filter the perm rows by 'default' first and then sort them by
1479 # admin,write,read,none permissions sorted again alphabetically in
1485 # admin,write,read,none permissions sorted again alphabetically in
1480 # each group
1486 # each group
1481 perm_rows = sorted(perm_rows, key=display_user_sort)
1487 perm_rows = sorted(perm_rows, key=display_user_sort)
1482
1488
1483 user_groups_rows = []
1489 user_groups_rows = []
1484 if expand_from_user_groups:
1490 if expand_from_user_groups:
1485 for ug in self.permission_user_groups(with_members=True):
1491 for ug in self.permission_user_groups(with_members=True):
1486 for user_data in ug.members:
1492 for user_data in ug.members:
1487 user_groups_rows.append(user_data)
1493 user_groups_rows.append(user_data)
1488
1494
1489 return super_admin_rows + owner_row + perm_rows + user_groups_rows
1495 return super_admin_rows + owner_row + perm_rows + user_groups_rows
1490
1496
1491 def permission_user_groups(self, with_members=False):
1497 def permission_user_groups(self, with_members=False):
1492 q = UserGroupUserGroupToPerm.query()\
1498 q = UserGroupUserGroupToPerm.query()\
1493 .filter(UserGroupUserGroupToPerm.target_user_group == self)
1499 .filter(UserGroupUserGroupToPerm.target_user_group == self)
1494 q = q.options(joinedload(UserGroupUserGroupToPerm.user_group),
1500 q = q.options(joinedload(UserGroupUserGroupToPerm.user_group),
1495 joinedload(UserGroupUserGroupToPerm.target_user_group),
1501 joinedload(UserGroupUserGroupToPerm.target_user_group),
1496 joinedload(UserGroupUserGroupToPerm.permission),)
1502 joinedload(UserGroupUserGroupToPerm.permission),)
1497
1503
1498 perm_rows = []
1504 perm_rows = []
1499 for _user_group in q.all():
1505 for _user_group in q.all():
1500 entry = AttributeDict(_user_group.user_group.get_dict())
1506 entry = AttributeDict(_user_group.user_group.get_dict())
1501 entry.permission = _user_group.permission.permission_name
1507 entry.permission = _user_group.permission.permission_name
1502 if with_members:
1508 if with_members:
1503 entry.members = [x.user.get_dict()
1509 entry.members = [x.user.get_dict()
1504 for x in _user_group.user_group.members]
1510 for x in _user_group.user_group.members]
1505 perm_rows.append(entry)
1511 perm_rows.append(entry)
1506
1512
1507 perm_rows = sorted(perm_rows, key=display_user_group_sort)
1513 perm_rows = sorted(perm_rows, key=display_user_group_sort)
1508 return perm_rows
1514 return perm_rows
1509
1515
1510 def _get_default_perms(self, user_group, suffix=''):
1516 def _get_default_perms(self, user_group, suffix=''):
1511 from rhodecode.model.permission import PermissionModel
1517 from rhodecode.model.permission import PermissionModel
1512 return PermissionModel().get_default_perms(user_group.users_group_to_perm, suffix)
1518 return PermissionModel().get_default_perms(user_group.users_group_to_perm, suffix)
1513
1519
1514 def get_default_perms(self, suffix=''):
1520 def get_default_perms(self, suffix=''):
1515 return self._get_default_perms(self, suffix)
1521 return self._get_default_perms(self, suffix)
1516
1522
1517 def get_api_data(self, with_group_members=True, include_secrets=False):
1523 def get_api_data(self, with_group_members=True, include_secrets=False):
1518 """
1524 """
1519 :param include_secrets: See :meth:`User.get_api_data`, this parameter is
1525 :param include_secrets: See :meth:`User.get_api_data`, this parameter is
1520 basically forwarded.
1526 basically forwarded.
1521
1527
1522 """
1528 """
1523 user_group = self
1529 user_group = self
1524 data = {
1530 data = {
1525 'users_group_id': user_group.users_group_id,
1531 'users_group_id': user_group.users_group_id,
1526 'group_name': user_group.users_group_name,
1532 'group_name': user_group.users_group_name,
1527 'group_description': user_group.user_group_description,
1533 'group_description': user_group.user_group_description,
1528 'active': user_group.users_group_active,
1534 'active': user_group.users_group_active,
1529 'owner': user_group.user.username,
1535 'owner': user_group.user.username,
1530 'sync': user_group.sync,
1536 'sync': user_group.sync,
1531 'owner_email': user_group.user.email,
1537 'owner_email': user_group.user.email,
1532 }
1538 }
1533
1539
1534 if with_group_members:
1540 if with_group_members:
1535 users = []
1541 users = []
1536 for user in user_group.members:
1542 for user in user_group.members:
1537 user = user.user
1543 user = user.user
1538 users.append(user.get_api_data(include_secrets=include_secrets))
1544 users.append(user.get_api_data(include_secrets=include_secrets))
1539 data['users'] = users
1545 data['users'] = users
1540
1546
1541 return data
1547 return data
1542
1548
1543
1549
1544 class UserGroupMember(Base, BaseModel):
1550 class UserGroupMember(Base, BaseModel):
1545 __tablename__ = 'users_groups_members'
1551 __tablename__ = 'users_groups_members'
1546 __table_args__ = (
1552 __table_args__ = (
1547 base_table_args,
1553 base_table_args,
1548 )
1554 )
1549
1555
1550 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1556 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1551 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1557 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1552 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1558 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1553
1559
1554 user = relationship('User', lazy='joined')
1560 user = relationship('User', lazy='joined')
1555 users_group = relationship('UserGroup')
1561 users_group = relationship('UserGroup')
1556
1562
1557 def __init__(self, gr_id='', u_id=''):
1563 def __init__(self, gr_id='', u_id=''):
1558 self.users_group_id = gr_id
1564 self.users_group_id = gr_id
1559 self.user_id = u_id
1565 self.user_id = u_id
1560
1566
1561
1567
1562 class RepositoryField(Base, BaseModel):
1568 class RepositoryField(Base, BaseModel):
1563 __tablename__ = 'repositories_fields'
1569 __tablename__ = 'repositories_fields'
1564 __table_args__ = (
1570 __table_args__ = (
1565 UniqueConstraint('repository_id', 'field_key'), # no-multi field
1571 UniqueConstraint('repository_id', 'field_key'), # no-multi field
1566 base_table_args,
1572 base_table_args,
1567 )
1573 )
1568
1574
1569 PREFIX = 'ex_' # prefix used in form to not conflict with already existing fields
1575 PREFIX = 'ex_' # prefix used in form to not conflict with already existing fields
1570
1576
1571 repo_field_id = Column("repo_field_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1577 repo_field_id = Column("repo_field_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1572 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1578 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1573 field_key = Column("field_key", String(250))
1579 field_key = Column("field_key", String(250))
1574 field_label = Column("field_label", String(1024), nullable=False)
1580 field_label = Column("field_label", String(1024), nullable=False)
1575 field_value = Column("field_value", String(10000), nullable=False)
1581 field_value = Column("field_value", String(10000), nullable=False)
1576 field_desc = Column("field_desc", String(1024), nullable=False)
1582 field_desc = Column("field_desc", String(1024), nullable=False)
1577 field_type = Column("field_type", String(255), nullable=False, unique=None)
1583 field_type = Column("field_type", String(255), nullable=False, unique=None)
1578 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1584 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1579
1585
1580 repository = relationship('Repository')
1586 repository = relationship('Repository')
1581
1587
1582 @property
1588 @property
1583 def field_key_prefixed(self):
1589 def field_key_prefixed(self):
1584 return 'ex_%s' % self.field_key
1590 return 'ex_%s' % self.field_key
1585
1591
1586 @classmethod
1592 @classmethod
1587 def un_prefix_key(cls, key):
1593 def un_prefix_key(cls, key):
1588 if key.startswith(cls.PREFIX):
1594 if key.startswith(cls.PREFIX):
1589 return key[len(cls.PREFIX):]
1595 return key[len(cls.PREFIX):]
1590 return key
1596 return key
1591
1597
1592 @classmethod
1598 @classmethod
1593 def get_by_key_name(cls, key, repo):
1599 def get_by_key_name(cls, key, repo):
1594 row = cls.query()\
1600 row = cls.query()\
1595 .filter(cls.repository == repo)\
1601 .filter(cls.repository == repo)\
1596 .filter(cls.field_key == key).scalar()
1602 .filter(cls.field_key == key).scalar()
1597 return row
1603 return row
1598
1604
1599
1605
1600 class Repository(Base, BaseModel):
1606 class Repository(Base, BaseModel):
1601 __tablename__ = 'repositories'
1607 __tablename__ = 'repositories'
1602 __table_args__ = (
1608 __table_args__ = (
1603 Index('r_repo_name_idx', 'repo_name', mysql_length=255),
1609 Index('r_repo_name_idx', 'repo_name', mysql_length=255),
1604 base_table_args,
1610 base_table_args,
1605 )
1611 )
1606 DEFAULT_CLONE_URI = '{scheme}://{user}@{netloc}/{repo}'
1612 DEFAULT_CLONE_URI = '{scheme}://{user}@{netloc}/{repo}'
1607 DEFAULT_CLONE_URI_ID = '{scheme}://{user}@{netloc}/_{repoid}'
1613 DEFAULT_CLONE_URI_ID = '{scheme}://{user}@{netloc}/_{repoid}'
1608 DEFAULT_CLONE_URI_SSH = 'ssh://{sys_user}@{hostname}/{repo}'
1614 DEFAULT_CLONE_URI_SSH = 'ssh://{sys_user}@{hostname}/{repo}'
1609
1615
1610 STATE_CREATED = 'repo_state_created'
1616 STATE_CREATED = 'repo_state_created'
1611 STATE_PENDING = 'repo_state_pending'
1617 STATE_PENDING = 'repo_state_pending'
1612 STATE_ERROR = 'repo_state_error'
1618 STATE_ERROR = 'repo_state_error'
1613
1619
1614 LOCK_AUTOMATIC = 'lock_auto'
1620 LOCK_AUTOMATIC = 'lock_auto'
1615 LOCK_API = 'lock_api'
1621 LOCK_API = 'lock_api'
1616 LOCK_WEB = 'lock_web'
1622 LOCK_WEB = 'lock_web'
1617 LOCK_PULL = 'lock_pull'
1623 LOCK_PULL = 'lock_pull'
1618
1624
1619 NAME_SEP = URL_SEP
1625 NAME_SEP = URL_SEP
1620
1626
1621 repo_id = Column(
1627 repo_id = Column(
1622 "repo_id", Integer(), nullable=False, unique=True, default=None,
1628 "repo_id", Integer(), nullable=False, unique=True, default=None,
1623 primary_key=True)
1629 primary_key=True)
1624 _repo_name = Column(
1630 _repo_name = Column(
1625 "repo_name", Text(), nullable=False, default=None)
1631 "repo_name", Text(), nullable=False, default=None)
1626 _repo_name_hash = Column(
1632 _repo_name_hash = Column(
1627 "repo_name_hash", String(255), nullable=False, unique=True)
1633 "repo_name_hash", String(255), nullable=False, unique=True)
1628 repo_state = Column("repo_state", String(255), nullable=True)
1634 repo_state = Column("repo_state", String(255), nullable=True)
1629
1635
1630 clone_uri = Column(
1636 clone_uri = Column(
1631 "clone_uri", EncryptedTextValue(), nullable=True, unique=False,
1637 "clone_uri", EncryptedTextValue(), nullable=True, unique=False,
1632 default=None)
1638 default=None)
1633 push_uri = Column(
1639 push_uri = Column(
1634 "push_uri", EncryptedTextValue(), nullable=True, unique=False,
1640 "push_uri", EncryptedTextValue(), nullable=True, unique=False,
1635 default=None)
1641 default=None)
1636 repo_type = Column(
1642 repo_type = Column(
1637 "repo_type", String(255), nullable=False, unique=False, default=None)
1643 "repo_type", String(255), nullable=False, unique=False, default=None)
1638 user_id = Column(
1644 user_id = Column(
1639 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
1645 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
1640 unique=False, default=None)
1646 unique=False, default=None)
1641 private = Column(
1647 private = Column(
1642 "private", Boolean(), nullable=True, unique=None, default=None)
1648 "private", Boolean(), nullable=True, unique=None, default=None)
1643 archived = Column(
1649 archived = Column(
1644 "archived", Boolean(), nullable=True, unique=None, default=None)
1650 "archived", Boolean(), nullable=True, unique=None, default=None)
1645 enable_statistics = Column(
1651 enable_statistics = Column(
1646 "statistics", Boolean(), nullable=True, unique=None, default=True)
1652 "statistics", Boolean(), nullable=True, unique=None, default=True)
1647 enable_downloads = Column(
1653 enable_downloads = Column(
1648 "downloads", Boolean(), nullable=True, unique=None, default=True)
1654 "downloads", Boolean(), nullable=True, unique=None, default=True)
1649 description = Column(
1655 description = Column(
1650 "description", String(10000), nullable=True, unique=None, default=None)
1656 "description", String(10000), nullable=True, unique=None, default=None)
1651 created_on = Column(
1657 created_on = Column(
1652 'created_on', DateTime(timezone=False), nullable=True, unique=None,
1658 'created_on', DateTime(timezone=False), nullable=True, unique=None,
1653 default=datetime.datetime.now)
1659 default=datetime.datetime.now)
1654 updated_on = Column(
1660 updated_on = Column(
1655 'updated_on', DateTime(timezone=False), nullable=True, unique=None,
1661 'updated_on', DateTime(timezone=False), nullable=True, unique=None,
1656 default=datetime.datetime.now)
1662 default=datetime.datetime.now)
1657 _landing_revision = Column(
1663 _landing_revision = Column(
1658 "landing_revision", String(255), nullable=False, unique=False,
1664 "landing_revision", String(255), nullable=False, unique=False,
1659 default=None)
1665 default=None)
1660 enable_locking = Column(
1666 enable_locking = Column(
1661 "enable_locking", Boolean(), nullable=False, unique=None,
1667 "enable_locking", Boolean(), nullable=False, unique=None,
1662 default=False)
1668 default=False)
1663 _locked = Column(
1669 _locked = Column(
1664 "locked", String(255), nullable=True, unique=False, default=None)
1670 "locked", String(255), nullable=True, unique=False, default=None)
1665 _changeset_cache = Column(
1671 _changeset_cache = Column(
1666 "changeset_cache", LargeBinary(), nullable=True) # JSON data
1672 "changeset_cache", LargeBinary(), nullable=True) # JSON data
1667
1673
1668 fork_id = Column(
1674 fork_id = Column(
1669 "fork_id", Integer(), ForeignKey('repositories.repo_id'),
1675 "fork_id", Integer(), ForeignKey('repositories.repo_id'),
1670 nullable=True, unique=False, default=None)
1676 nullable=True, unique=False, default=None)
1671 group_id = Column(
1677 group_id = Column(
1672 "group_id", Integer(), ForeignKey('groups.group_id'), nullable=True,
1678 "group_id", Integer(), ForeignKey('groups.group_id'), nullable=True,
1673 unique=False, default=None)
1679 unique=False, default=None)
1674
1680
1675 user = relationship('User', lazy='joined')
1681 user = relationship('User', lazy='joined')
1676 fork = relationship('Repository', remote_side=repo_id, lazy='joined')
1682 fork = relationship('Repository', remote_side=repo_id, lazy='joined')
1677 group = relationship('RepoGroup', lazy='joined')
1683 group = relationship('RepoGroup', lazy='joined')
1678 repo_to_perm = relationship(
1684 repo_to_perm = relationship(
1679 'UserRepoToPerm', cascade='all',
1685 'UserRepoToPerm', cascade='all',
1680 order_by='UserRepoToPerm.repo_to_perm_id')
1686 order_by='UserRepoToPerm.repo_to_perm_id')
1681 users_group_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1687 users_group_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1682 stats = relationship('Statistics', cascade='all', uselist=False)
1688 stats = relationship('Statistics', cascade='all', uselist=False)
1683
1689
1684 followers = relationship(
1690 followers = relationship(
1685 'UserFollowing',
1691 'UserFollowing',
1686 primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id',
1692 primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id',
1687 cascade='all')
1693 cascade='all')
1688 extra_fields = relationship(
1694 extra_fields = relationship(
1689 'RepositoryField', cascade="all, delete-orphan")
1695 'RepositoryField', cascade="all, delete-orphan")
1690 logs = relationship('UserLog')
1696 logs = relationship('UserLog')
1691 comments = relationship(
1697 comments = relationship(
1692 'ChangesetComment', cascade="all, delete-orphan")
1698 'ChangesetComment', cascade="all, delete-orphan")
1693 pull_requests_source = relationship(
1699 pull_requests_source = relationship(
1694 'PullRequest',
1700 'PullRequest',
1695 primaryjoin='PullRequest.source_repo_id==Repository.repo_id',
1701 primaryjoin='PullRequest.source_repo_id==Repository.repo_id',
1696 cascade="all, delete-orphan")
1702 cascade="all, delete-orphan")
1697 pull_requests_target = relationship(
1703 pull_requests_target = relationship(
1698 'PullRequest',
1704 'PullRequest',
1699 primaryjoin='PullRequest.target_repo_id==Repository.repo_id',
1705 primaryjoin='PullRequest.target_repo_id==Repository.repo_id',
1700 cascade="all, delete-orphan")
1706 cascade="all, delete-orphan")
1701 ui = relationship('RepoRhodeCodeUi', cascade="all")
1707 ui = relationship('RepoRhodeCodeUi', cascade="all")
1702 settings = relationship('RepoRhodeCodeSetting', cascade="all")
1708 settings = relationship('RepoRhodeCodeSetting', cascade="all")
1703 integrations = relationship('Integration', cascade="all, delete-orphan")
1709 integrations = relationship('Integration', cascade="all, delete-orphan")
1704
1710
1705 scoped_tokens = relationship('UserApiKeys', cascade="all")
1711 scoped_tokens = relationship('UserApiKeys', cascade="all")
1706
1712
1707 artifacts = relationship('FileStore', cascade="all")
1713 # no cascade, set NULL
1714 artifacts = relationship('FileStore', primaryjoin='FileStore.scope_repo_id==Repository.repo_id')
1708
1715
1709 def __unicode__(self):
1716 def __unicode__(self):
1710 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id,
1717 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id,
1711 safe_unicode(self.repo_name))
1718 safe_unicode(self.repo_name))
1712
1719
1713 @hybrid_property
1720 @hybrid_property
1714 def description_safe(self):
1721 def description_safe(self):
1715 from rhodecode.lib import helpers as h
1722 from rhodecode.lib import helpers as h
1716 return h.escape(self.description)
1723 return h.escape(self.description)
1717
1724
1718 @hybrid_property
1725 @hybrid_property
1719 def landing_rev(self):
1726 def landing_rev(self):
1720 # always should return [rev_type, rev]
1727 # always should return [rev_type, rev]
1721 if self._landing_revision:
1728 if self._landing_revision:
1722 _rev_info = self._landing_revision.split(':')
1729 _rev_info = self._landing_revision.split(':')
1723 if len(_rev_info) < 2:
1730 if len(_rev_info) < 2:
1724 _rev_info.insert(0, 'rev')
1731 _rev_info.insert(0, 'rev')
1725 return [_rev_info[0], _rev_info[1]]
1732 return [_rev_info[0], _rev_info[1]]
1726 return [None, None]
1733 return [None, None]
1727
1734
1728 @landing_rev.setter
1735 @landing_rev.setter
1729 def landing_rev(self, val):
1736 def landing_rev(self, val):
1730 if ':' not in val:
1737 if ':' not in val:
1731 raise ValueError('value must be delimited with `:` and consist '
1738 raise ValueError('value must be delimited with `:` and consist '
1732 'of <rev_type>:<rev>, got %s instead' % val)
1739 'of <rev_type>:<rev>, got %s instead' % val)
1733 self._landing_revision = val
1740 self._landing_revision = val
1734
1741
1735 @hybrid_property
1742 @hybrid_property
1736 def locked(self):
1743 def locked(self):
1737 if self._locked:
1744 if self._locked:
1738 user_id, timelocked, reason = self._locked.split(':')
1745 user_id, timelocked, reason = self._locked.split(':')
1739 lock_values = int(user_id), timelocked, reason
1746 lock_values = int(user_id), timelocked, reason
1740 else:
1747 else:
1741 lock_values = [None, None, None]
1748 lock_values = [None, None, None]
1742 return lock_values
1749 return lock_values
1743
1750
1744 @locked.setter
1751 @locked.setter
1745 def locked(self, val):
1752 def locked(self, val):
1746 if val and isinstance(val, (list, tuple)):
1753 if val and isinstance(val, (list, tuple)):
1747 self._locked = ':'.join(map(str, val))
1754 self._locked = ':'.join(map(str, val))
1748 else:
1755 else:
1749 self._locked = None
1756 self._locked = None
1750
1757
1751 @hybrid_property
1758 @hybrid_property
1752 def changeset_cache(self):
1759 def changeset_cache(self):
1753 from rhodecode.lib.vcs.backends.base import EmptyCommit
1760 from rhodecode.lib.vcs.backends.base import EmptyCommit
1754 dummy = EmptyCommit().__json__()
1761 dummy = EmptyCommit().__json__()
1755 if not self._changeset_cache:
1762 if not self._changeset_cache:
1756 dummy['source_repo_id'] = self.repo_id
1763 dummy['source_repo_id'] = self.repo_id
1757 return json.loads(json.dumps(dummy))
1764 return json.loads(json.dumps(dummy))
1758
1765
1759 try:
1766 try:
1760 return json.loads(self._changeset_cache)
1767 return json.loads(self._changeset_cache)
1761 except TypeError:
1768 except TypeError:
1762 return dummy
1769 return dummy
1763 except Exception:
1770 except Exception:
1764 log.error(traceback.format_exc())
1771 log.error(traceback.format_exc())
1765 return dummy
1772 return dummy
1766
1773
1767 @changeset_cache.setter
1774 @changeset_cache.setter
1768 def changeset_cache(self, val):
1775 def changeset_cache(self, val):
1769 try:
1776 try:
1770 self._changeset_cache = json.dumps(val)
1777 self._changeset_cache = json.dumps(val)
1771 except Exception:
1778 except Exception:
1772 log.error(traceback.format_exc())
1779 log.error(traceback.format_exc())
1773
1780
1774 @hybrid_property
1781 @hybrid_property
1775 def repo_name(self):
1782 def repo_name(self):
1776 return self._repo_name
1783 return self._repo_name
1777
1784
1778 @repo_name.setter
1785 @repo_name.setter
1779 def repo_name(self, value):
1786 def repo_name(self, value):
1780 self._repo_name = value
1787 self._repo_name = value
1781 self._repo_name_hash = hashlib.sha1(safe_str(value)).hexdigest()
1788 self._repo_name_hash = hashlib.sha1(safe_str(value)).hexdigest()
1782
1789
1783 @classmethod
1790 @classmethod
1784 def normalize_repo_name(cls, repo_name):
1791 def normalize_repo_name(cls, repo_name):
1785 """
1792 """
1786 Normalizes os specific repo_name to the format internally stored inside
1793 Normalizes os specific repo_name to the format internally stored inside
1787 database using URL_SEP
1794 database using URL_SEP
1788
1795
1789 :param cls:
1796 :param cls:
1790 :param repo_name:
1797 :param repo_name:
1791 """
1798 """
1792 return cls.NAME_SEP.join(repo_name.split(os.sep))
1799 return cls.NAME_SEP.join(repo_name.split(os.sep))
1793
1800
1794 @classmethod
1801 @classmethod
1795 def get_by_repo_name(cls, repo_name, cache=False, identity_cache=False):
1802 def get_by_repo_name(cls, repo_name, cache=False, identity_cache=False):
1796 session = Session()
1803 session = Session()
1797 q = session.query(cls).filter(cls.repo_name == repo_name)
1804 q = session.query(cls).filter(cls.repo_name == repo_name)
1798
1805
1799 if cache:
1806 if cache:
1800 if identity_cache:
1807 if identity_cache:
1801 val = cls.identity_cache(session, 'repo_name', repo_name)
1808 val = cls.identity_cache(session, 'repo_name', repo_name)
1802 if val:
1809 if val:
1803 return val
1810 return val
1804 else:
1811 else:
1805 cache_key = "get_repo_by_name_%s" % _hash_key(repo_name)
1812 cache_key = "get_repo_by_name_%s" % _hash_key(repo_name)
1806 q = q.options(
1813 q = q.options(
1807 FromCache("sql_cache_short", cache_key))
1814 FromCache("sql_cache_short", cache_key))
1808
1815
1809 return q.scalar()
1816 return q.scalar()
1810
1817
1811 @classmethod
1818 @classmethod
1812 def get_by_id_or_repo_name(cls, repoid):
1819 def get_by_id_or_repo_name(cls, repoid):
1813 if isinstance(repoid, (int, long)):
1820 if isinstance(repoid, (int, long)):
1814 try:
1821 try:
1815 repo = cls.get(repoid)
1822 repo = cls.get(repoid)
1816 except ValueError:
1823 except ValueError:
1817 repo = None
1824 repo = None
1818 else:
1825 else:
1819 repo = cls.get_by_repo_name(repoid)
1826 repo = cls.get_by_repo_name(repoid)
1820 return repo
1827 return repo
1821
1828
1822 @classmethod
1829 @classmethod
1823 def get_by_full_path(cls, repo_full_path):
1830 def get_by_full_path(cls, repo_full_path):
1824 repo_name = repo_full_path.split(cls.base_path(), 1)[-1]
1831 repo_name = repo_full_path.split(cls.base_path(), 1)[-1]
1825 repo_name = cls.normalize_repo_name(repo_name)
1832 repo_name = cls.normalize_repo_name(repo_name)
1826 return cls.get_by_repo_name(repo_name.strip(URL_SEP))
1833 return cls.get_by_repo_name(repo_name.strip(URL_SEP))
1827
1834
1828 @classmethod
1835 @classmethod
1829 def get_repo_forks(cls, repo_id):
1836 def get_repo_forks(cls, repo_id):
1830 return cls.query().filter(Repository.fork_id == repo_id)
1837 return cls.query().filter(Repository.fork_id == repo_id)
1831
1838
1832 @classmethod
1839 @classmethod
1833 def base_path(cls):
1840 def base_path(cls):
1834 """
1841 """
1835 Returns base path when all repos are stored
1842 Returns base path when all repos are stored
1836
1843
1837 :param cls:
1844 :param cls:
1838 """
1845 """
1839 q = Session().query(RhodeCodeUi)\
1846 q = Session().query(RhodeCodeUi)\
1840 .filter(RhodeCodeUi.ui_key == cls.NAME_SEP)
1847 .filter(RhodeCodeUi.ui_key == cls.NAME_SEP)
1841 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1848 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1842 return q.one().ui_value
1849 return q.one().ui_value
1843
1850
1844 @classmethod
1851 @classmethod
1845 def get_all_repos(cls, user_id=Optional(None), group_id=Optional(None),
1852 def get_all_repos(cls, user_id=Optional(None), group_id=Optional(None),
1846 case_insensitive=True, archived=False):
1853 case_insensitive=True, archived=False):
1847 q = Repository.query()
1854 q = Repository.query()
1848
1855
1849 if not archived:
1856 if not archived:
1850 q = q.filter(Repository.archived.isnot(true()))
1857 q = q.filter(Repository.archived.isnot(true()))
1851
1858
1852 if not isinstance(user_id, Optional):
1859 if not isinstance(user_id, Optional):
1853 q = q.filter(Repository.user_id == user_id)
1860 q = q.filter(Repository.user_id == user_id)
1854
1861
1855 if not isinstance(group_id, Optional):
1862 if not isinstance(group_id, Optional):
1856 q = q.filter(Repository.group_id == group_id)
1863 q = q.filter(Repository.group_id == group_id)
1857
1864
1858 if case_insensitive:
1865 if case_insensitive:
1859 q = q.order_by(func.lower(Repository.repo_name))
1866 q = q.order_by(func.lower(Repository.repo_name))
1860 else:
1867 else:
1861 q = q.order_by(Repository.repo_name)
1868 q = q.order_by(Repository.repo_name)
1862
1869
1863 return q.all()
1870 return q.all()
1864
1871
1865 @property
1872 @property
1866 def repo_uid(self):
1873 def repo_uid(self):
1867 return '_{}'.format(self.repo_id)
1874 return '_{}'.format(self.repo_id)
1868
1875
1869 @property
1876 @property
1870 def forks(self):
1877 def forks(self):
1871 """
1878 """
1872 Return forks of this repo
1879 Return forks of this repo
1873 """
1880 """
1874 return Repository.get_repo_forks(self.repo_id)
1881 return Repository.get_repo_forks(self.repo_id)
1875
1882
1876 @property
1883 @property
1877 def parent(self):
1884 def parent(self):
1878 """
1885 """
1879 Returns fork parent
1886 Returns fork parent
1880 """
1887 """
1881 return self.fork
1888 return self.fork
1882
1889
1883 @property
1890 @property
1884 def just_name(self):
1891 def just_name(self):
1885 return self.repo_name.split(self.NAME_SEP)[-1]
1892 return self.repo_name.split(self.NAME_SEP)[-1]
1886
1893
1887 @property
1894 @property
1888 def groups_with_parents(self):
1895 def groups_with_parents(self):
1889 groups = []
1896 groups = []
1890 if self.group is None:
1897 if self.group is None:
1891 return groups
1898 return groups
1892
1899
1893 cur_gr = self.group
1900 cur_gr = self.group
1894 groups.insert(0, cur_gr)
1901 groups.insert(0, cur_gr)
1895 while 1:
1902 while 1:
1896 gr = getattr(cur_gr, 'parent_group', None)
1903 gr = getattr(cur_gr, 'parent_group', None)
1897 cur_gr = cur_gr.parent_group
1904 cur_gr = cur_gr.parent_group
1898 if gr is None:
1905 if gr is None:
1899 break
1906 break
1900 groups.insert(0, gr)
1907 groups.insert(0, gr)
1901
1908
1902 return groups
1909 return groups
1903
1910
1904 @property
1911 @property
1905 def groups_and_repo(self):
1912 def groups_and_repo(self):
1906 return self.groups_with_parents, self
1913 return self.groups_with_parents, self
1907
1914
1908 @LazyProperty
1915 @LazyProperty
1909 def repo_path(self):
1916 def repo_path(self):
1910 """
1917 """
1911 Returns base full path for that repository means where it actually
1918 Returns base full path for that repository means where it actually
1912 exists on a filesystem
1919 exists on a filesystem
1913 """
1920 """
1914 q = Session().query(RhodeCodeUi).filter(
1921 q = Session().query(RhodeCodeUi).filter(
1915 RhodeCodeUi.ui_key == self.NAME_SEP)
1922 RhodeCodeUi.ui_key == self.NAME_SEP)
1916 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1923 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1917 return q.one().ui_value
1924 return q.one().ui_value
1918
1925
1919 @property
1926 @property
1920 def repo_full_path(self):
1927 def repo_full_path(self):
1921 p = [self.repo_path]
1928 p = [self.repo_path]
1922 # we need to split the name by / since this is how we store the
1929 # we need to split the name by / since this is how we store the
1923 # names in the database, but that eventually needs to be converted
1930 # names in the database, but that eventually needs to be converted
1924 # into a valid system path
1931 # into a valid system path
1925 p += self.repo_name.split(self.NAME_SEP)
1932 p += self.repo_name.split(self.NAME_SEP)
1926 return os.path.join(*map(safe_unicode, p))
1933 return os.path.join(*map(safe_unicode, p))
1927
1934
1928 @property
1935 @property
1929 def cache_keys(self):
1936 def cache_keys(self):
1930 """
1937 """
1931 Returns associated cache keys for that repo
1938 Returns associated cache keys for that repo
1932 """
1939 """
1933 invalidation_namespace = CacheKey.REPO_INVALIDATION_NAMESPACE.format(
1940 invalidation_namespace = CacheKey.REPO_INVALIDATION_NAMESPACE.format(
1934 repo_id=self.repo_id)
1941 repo_id=self.repo_id)
1935 return CacheKey.query()\
1942 return CacheKey.query()\
1936 .filter(CacheKey.cache_args == invalidation_namespace)\
1943 .filter(CacheKey.cache_args == invalidation_namespace)\
1937 .order_by(CacheKey.cache_key)\
1944 .order_by(CacheKey.cache_key)\
1938 .all()
1945 .all()
1939
1946
1940 @property
1947 @property
1941 def cached_diffs_relative_dir(self):
1948 def cached_diffs_relative_dir(self):
1942 """
1949 """
1943 Return a relative to the repository store path of cached diffs
1950 Return a relative to the repository store path of cached diffs
1944 used for safe display for users, who shouldn't know the absolute store
1951 used for safe display for users, who shouldn't know the absolute store
1945 path
1952 path
1946 """
1953 """
1947 return os.path.join(
1954 return os.path.join(
1948 os.path.dirname(self.repo_name),
1955 os.path.dirname(self.repo_name),
1949 self.cached_diffs_dir.split(os.path.sep)[-1])
1956 self.cached_diffs_dir.split(os.path.sep)[-1])
1950
1957
1951 @property
1958 @property
1952 def cached_diffs_dir(self):
1959 def cached_diffs_dir(self):
1953 path = self.repo_full_path
1960 path = self.repo_full_path
1954 return os.path.join(
1961 return os.path.join(
1955 os.path.dirname(path),
1962 os.path.dirname(path),
1956 '.__shadow_diff_cache_repo_{}'.format(self.repo_id))
1963 '.__shadow_diff_cache_repo_{}'.format(self.repo_id))
1957
1964
1958 def cached_diffs(self):
1965 def cached_diffs(self):
1959 diff_cache_dir = self.cached_diffs_dir
1966 diff_cache_dir = self.cached_diffs_dir
1960 if os.path.isdir(diff_cache_dir):
1967 if os.path.isdir(diff_cache_dir):
1961 return os.listdir(diff_cache_dir)
1968 return os.listdir(diff_cache_dir)
1962 return []
1969 return []
1963
1970
1964 def shadow_repos(self):
1971 def shadow_repos(self):
1965 shadow_repos_pattern = '.__shadow_repo_{}'.format(self.repo_id)
1972 shadow_repos_pattern = '.__shadow_repo_{}'.format(self.repo_id)
1966 return [
1973 return [
1967 x for x in os.listdir(os.path.dirname(self.repo_full_path))
1974 x for x in os.listdir(os.path.dirname(self.repo_full_path))
1968 if x.startswith(shadow_repos_pattern)]
1975 if x.startswith(shadow_repos_pattern)]
1969
1976
1970 def get_new_name(self, repo_name):
1977 def get_new_name(self, repo_name):
1971 """
1978 """
1972 returns new full repository name based on assigned group and new new
1979 returns new full repository name based on assigned group and new new
1973
1980
1974 :param group_name:
1981 :param group_name:
1975 """
1982 """
1976 path_prefix = self.group.full_path_splitted if self.group else []
1983 path_prefix = self.group.full_path_splitted if self.group else []
1977 return self.NAME_SEP.join(path_prefix + [repo_name])
1984 return self.NAME_SEP.join(path_prefix + [repo_name])
1978
1985
1979 @property
1986 @property
1980 def _config(self):
1987 def _config(self):
1981 """
1988 """
1982 Returns db based config object.
1989 Returns db based config object.
1983 """
1990 """
1984 from rhodecode.lib.utils import make_db_config
1991 from rhodecode.lib.utils import make_db_config
1985 return make_db_config(clear_session=False, repo=self)
1992 return make_db_config(clear_session=False, repo=self)
1986
1993
1987 def permissions(self, with_admins=True, with_owner=True,
1994 def permissions(self, with_admins=True, with_owner=True,
1988 expand_from_user_groups=False):
1995 expand_from_user_groups=False):
1989 """
1996 """
1990 Permissions for repositories
1997 Permissions for repositories
1991 """
1998 """
1992 _admin_perm = 'repository.admin'
1999 _admin_perm = 'repository.admin'
1993
2000
1994 owner_row = []
2001 owner_row = []
1995 if with_owner:
2002 if with_owner:
1996 usr = AttributeDict(self.user.get_dict())
2003 usr = AttributeDict(self.user.get_dict())
1997 usr.owner_row = True
2004 usr.owner_row = True
1998 usr.permission = _admin_perm
2005 usr.permission = _admin_perm
1999 usr.permission_id = None
2006 usr.permission_id = None
2000 owner_row.append(usr)
2007 owner_row.append(usr)
2001
2008
2002 super_admin_ids = []
2009 super_admin_ids = []
2003 super_admin_rows = []
2010 super_admin_rows = []
2004 if with_admins:
2011 if with_admins:
2005 for usr in User.get_all_super_admins():
2012 for usr in User.get_all_super_admins():
2006 super_admin_ids.append(usr.user_id)
2013 super_admin_ids.append(usr.user_id)
2007 # if this admin is also owner, don't double the record
2014 # if this admin is also owner, don't double the record
2008 if usr.user_id == owner_row[0].user_id:
2015 if usr.user_id == owner_row[0].user_id:
2009 owner_row[0].admin_row = True
2016 owner_row[0].admin_row = True
2010 else:
2017 else:
2011 usr = AttributeDict(usr.get_dict())
2018 usr = AttributeDict(usr.get_dict())
2012 usr.admin_row = True
2019 usr.admin_row = True
2013 usr.permission = _admin_perm
2020 usr.permission = _admin_perm
2014 usr.permission_id = None
2021 usr.permission_id = None
2015 super_admin_rows.append(usr)
2022 super_admin_rows.append(usr)
2016
2023
2017 q = UserRepoToPerm.query().filter(UserRepoToPerm.repository == self)
2024 q = UserRepoToPerm.query().filter(UserRepoToPerm.repository == self)
2018 q = q.options(joinedload(UserRepoToPerm.repository),
2025 q = q.options(joinedload(UserRepoToPerm.repository),
2019 joinedload(UserRepoToPerm.user),
2026 joinedload(UserRepoToPerm.user),
2020 joinedload(UserRepoToPerm.permission),)
2027 joinedload(UserRepoToPerm.permission),)
2021
2028
2022 # get owners and admins and permissions. We do a trick of re-writing
2029 # get owners and admins and permissions. We do a trick of re-writing
2023 # objects from sqlalchemy to named-tuples due to sqlalchemy session
2030 # objects from sqlalchemy to named-tuples due to sqlalchemy session
2024 # has a global reference and changing one object propagates to all
2031 # has a global reference and changing one object propagates to all
2025 # others. This means if admin is also an owner admin_row that change
2032 # others. This means if admin is also an owner admin_row that change
2026 # would propagate to both objects
2033 # would propagate to both objects
2027 perm_rows = []
2034 perm_rows = []
2028 for _usr in q.all():
2035 for _usr in q.all():
2029 usr = AttributeDict(_usr.user.get_dict())
2036 usr = AttributeDict(_usr.user.get_dict())
2030 # if this user is also owner/admin, mark as duplicate record
2037 # if this user is also owner/admin, mark as duplicate record
2031 if usr.user_id == owner_row[0].user_id or usr.user_id in super_admin_ids:
2038 if usr.user_id == owner_row[0].user_id or usr.user_id in super_admin_ids:
2032 usr.duplicate_perm = True
2039 usr.duplicate_perm = True
2033 # also check if this permission is maybe used by branch_permissions
2040 # also check if this permission is maybe used by branch_permissions
2034 if _usr.branch_perm_entry:
2041 if _usr.branch_perm_entry:
2035 usr.branch_rules = [x.branch_rule_id for x in _usr.branch_perm_entry]
2042 usr.branch_rules = [x.branch_rule_id for x in _usr.branch_perm_entry]
2036
2043
2037 usr.permission = _usr.permission.permission_name
2044 usr.permission = _usr.permission.permission_name
2038 usr.permission_id = _usr.repo_to_perm_id
2045 usr.permission_id = _usr.repo_to_perm_id
2039 perm_rows.append(usr)
2046 perm_rows.append(usr)
2040
2047
2041 # filter the perm rows by 'default' first and then sort them by
2048 # filter the perm rows by 'default' first and then sort them by
2042 # admin,write,read,none permissions sorted again alphabetically in
2049 # admin,write,read,none permissions sorted again alphabetically in
2043 # each group
2050 # each group
2044 perm_rows = sorted(perm_rows, key=display_user_sort)
2051 perm_rows = sorted(perm_rows, key=display_user_sort)
2045
2052
2046 user_groups_rows = []
2053 user_groups_rows = []
2047 if expand_from_user_groups:
2054 if expand_from_user_groups:
2048 for ug in self.permission_user_groups(with_members=True):
2055 for ug in self.permission_user_groups(with_members=True):
2049 for user_data in ug.members:
2056 for user_data in ug.members:
2050 user_groups_rows.append(user_data)
2057 user_groups_rows.append(user_data)
2051
2058
2052 return super_admin_rows + owner_row + perm_rows + user_groups_rows
2059 return super_admin_rows + owner_row + perm_rows + user_groups_rows
2053
2060
2054 def permission_user_groups(self, with_members=True):
2061 def permission_user_groups(self, with_members=True):
2055 q = UserGroupRepoToPerm.query()\
2062 q = UserGroupRepoToPerm.query()\
2056 .filter(UserGroupRepoToPerm.repository == self)
2063 .filter(UserGroupRepoToPerm.repository == self)
2057 q = q.options(joinedload(UserGroupRepoToPerm.repository),
2064 q = q.options(joinedload(UserGroupRepoToPerm.repository),
2058 joinedload(UserGroupRepoToPerm.users_group),
2065 joinedload(UserGroupRepoToPerm.users_group),
2059 joinedload(UserGroupRepoToPerm.permission),)
2066 joinedload(UserGroupRepoToPerm.permission),)
2060
2067
2061 perm_rows = []
2068 perm_rows = []
2062 for _user_group in q.all():
2069 for _user_group in q.all():
2063 entry = AttributeDict(_user_group.users_group.get_dict())
2070 entry = AttributeDict(_user_group.users_group.get_dict())
2064 entry.permission = _user_group.permission.permission_name
2071 entry.permission = _user_group.permission.permission_name
2065 if with_members:
2072 if with_members:
2066 entry.members = [x.user.get_dict()
2073 entry.members = [x.user.get_dict()
2067 for x in _user_group.users_group.members]
2074 for x in _user_group.users_group.members]
2068 perm_rows.append(entry)
2075 perm_rows.append(entry)
2069
2076
2070 perm_rows = sorted(perm_rows, key=display_user_group_sort)
2077 perm_rows = sorted(perm_rows, key=display_user_group_sort)
2071 return perm_rows
2078 return perm_rows
2072
2079
2073 def get_api_data(self, include_secrets=False):
2080 def get_api_data(self, include_secrets=False):
2074 """
2081 """
2075 Common function for generating repo api data
2082 Common function for generating repo api data
2076
2083
2077 :param include_secrets: See :meth:`User.get_api_data`.
2084 :param include_secrets: See :meth:`User.get_api_data`.
2078
2085
2079 """
2086 """
2080 # TODO: mikhail: Here there is an anti-pattern, we probably need to
2087 # TODO: mikhail: Here there is an anti-pattern, we probably need to
2081 # move this methods on models level.
2088 # move this methods on models level.
2082 from rhodecode.model.settings import SettingsModel
2089 from rhodecode.model.settings import SettingsModel
2083 from rhodecode.model.repo import RepoModel
2090 from rhodecode.model.repo import RepoModel
2084
2091
2085 repo = self
2092 repo = self
2086 _user_id, _time, _reason = self.locked
2093 _user_id, _time, _reason = self.locked
2087
2094
2088 data = {
2095 data = {
2089 'repo_id': repo.repo_id,
2096 'repo_id': repo.repo_id,
2090 'repo_name': repo.repo_name,
2097 'repo_name': repo.repo_name,
2091 'repo_type': repo.repo_type,
2098 'repo_type': repo.repo_type,
2092 'clone_uri': repo.clone_uri or '',
2099 'clone_uri': repo.clone_uri or '',
2093 'push_uri': repo.push_uri or '',
2100 'push_uri': repo.push_uri or '',
2094 'url': RepoModel().get_url(self),
2101 'url': RepoModel().get_url(self),
2095 'private': repo.private,
2102 'private': repo.private,
2096 'created_on': repo.created_on,
2103 'created_on': repo.created_on,
2097 'description': repo.description_safe,
2104 'description': repo.description_safe,
2098 'landing_rev': repo.landing_rev,
2105 'landing_rev': repo.landing_rev,
2099 'owner': repo.user.username,
2106 'owner': repo.user.username,
2100 'fork_of': repo.fork.repo_name if repo.fork else None,
2107 'fork_of': repo.fork.repo_name if repo.fork else None,
2101 'fork_of_id': repo.fork.repo_id if repo.fork else None,
2108 'fork_of_id': repo.fork.repo_id if repo.fork else None,
2102 'enable_statistics': repo.enable_statistics,
2109 'enable_statistics': repo.enable_statistics,
2103 'enable_locking': repo.enable_locking,
2110 'enable_locking': repo.enable_locking,
2104 'enable_downloads': repo.enable_downloads,
2111 'enable_downloads': repo.enable_downloads,
2105 'last_changeset': repo.changeset_cache,
2112 'last_changeset': repo.changeset_cache,
2106 'locked_by': User.get(_user_id).get_api_data(
2113 'locked_by': User.get(_user_id).get_api_data(
2107 include_secrets=include_secrets) if _user_id else None,
2114 include_secrets=include_secrets) if _user_id else None,
2108 'locked_date': time_to_datetime(_time) if _time else None,
2115 'locked_date': time_to_datetime(_time) if _time else None,
2109 'lock_reason': _reason if _reason else None,
2116 'lock_reason': _reason if _reason else None,
2110 }
2117 }
2111
2118
2112 # TODO: mikhail: should be per-repo settings here
2119 # TODO: mikhail: should be per-repo settings here
2113 rc_config = SettingsModel().get_all_settings()
2120 rc_config = SettingsModel().get_all_settings()
2114 repository_fields = str2bool(
2121 repository_fields = str2bool(
2115 rc_config.get('rhodecode_repository_fields'))
2122 rc_config.get('rhodecode_repository_fields'))
2116 if repository_fields:
2123 if repository_fields:
2117 for f in self.extra_fields:
2124 for f in self.extra_fields:
2118 data[f.field_key_prefixed] = f.field_value
2125 data[f.field_key_prefixed] = f.field_value
2119
2126
2120 return data
2127 return data
2121
2128
2122 @classmethod
2129 @classmethod
2123 def lock(cls, repo, user_id, lock_time=None, lock_reason=None):
2130 def lock(cls, repo, user_id, lock_time=None, lock_reason=None):
2124 if not lock_time:
2131 if not lock_time:
2125 lock_time = time.time()
2132 lock_time = time.time()
2126 if not lock_reason:
2133 if not lock_reason:
2127 lock_reason = cls.LOCK_AUTOMATIC
2134 lock_reason = cls.LOCK_AUTOMATIC
2128 repo.locked = [user_id, lock_time, lock_reason]
2135 repo.locked = [user_id, lock_time, lock_reason]
2129 Session().add(repo)
2136 Session().add(repo)
2130 Session().commit()
2137 Session().commit()
2131
2138
2132 @classmethod
2139 @classmethod
2133 def unlock(cls, repo):
2140 def unlock(cls, repo):
2134 repo.locked = None
2141 repo.locked = None
2135 Session().add(repo)
2142 Session().add(repo)
2136 Session().commit()
2143 Session().commit()
2137
2144
2138 @classmethod
2145 @classmethod
2139 def getlock(cls, repo):
2146 def getlock(cls, repo):
2140 return repo.locked
2147 return repo.locked
2141
2148
2142 def is_user_lock(self, user_id):
2149 def is_user_lock(self, user_id):
2143 if self.lock[0]:
2150 if self.lock[0]:
2144 lock_user_id = safe_int(self.lock[0])
2151 lock_user_id = safe_int(self.lock[0])
2145 user_id = safe_int(user_id)
2152 user_id = safe_int(user_id)
2146 # both are ints, and they are equal
2153 # both are ints, and they are equal
2147 return all([lock_user_id, user_id]) and lock_user_id == user_id
2154 return all([lock_user_id, user_id]) and lock_user_id == user_id
2148
2155
2149 return False
2156 return False
2150
2157
2151 def get_locking_state(self, action, user_id, only_when_enabled=True):
2158 def get_locking_state(self, action, user_id, only_when_enabled=True):
2152 """
2159 """
2153 Checks locking on this repository, if locking is enabled and lock is
2160 Checks locking on this repository, if locking is enabled and lock is
2154 present returns a tuple of make_lock, locked, locked_by.
2161 present returns a tuple of make_lock, locked, locked_by.
2155 make_lock can have 3 states None (do nothing) True, make lock
2162 make_lock can have 3 states None (do nothing) True, make lock
2156 False release lock, This value is later propagated to hooks, which
2163 False release lock, This value is later propagated to hooks, which
2157 do the locking. Think about this as signals passed to hooks what to do.
2164 do the locking. Think about this as signals passed to hooks what to do.
2158
2165
2159 """
2166 """
2160 # TODO: johbo: This is part of the business logic and should be moved
2167 # TODO: johbo: This is part of the business logic and should be moved
2161 # into the RepositoryModel.
2168 # into the RepositoryModel.
2162
2169
2163 if action not in ('push', 'pull'):
2170 if action not in ('push', 'pull'):
2164 raise ValueError("Invalid action value: %s" % repr(action))
2171 raise ValueError("Invalid action value: %s" % repr(action))
2165
2172
2166 # defines if locked error should be thrown to user
2173 # defines if locked error should be thrown to user
2167 currently_locked = False
2174 currently_locked = False
2168 # defines if new lock should be made, tri-state
2175 # defines if new lock should be made, tri-state
2169 make_lock = None
2176 make_lock = None
2170 repo = self
2177 repo = self
2171 user = User.get(user_id)
2178 user = User.get(user_id)
2172
2179
2173 lock_info = repo.locked
2180 lock_info = repo.locked
2174
2181
2175 if repo and (repo.enable_locking or not only_when_enabled):
2182 if repo and (repo.enable_locking or not only_when_enabled):
2176 if action == 'push':
2183 if action == 'push':
2177 # check if it's already locked !, if it is compare users
2184 # check if it's already locked !, if it is compare users
2178 locked_by_user_id = lock_info[0]
2185 locked_by_user_id = lock_info[0]
2179 if user.user_id == locked_by_user_id:
2186 if user.user_id == locked_by_user_id:
2180 log.debug(
2187 log.debug(
2181 'Got `push` action from user %s, now unlocking', user)
2188 'Got `push` action from user %s, now unlocking', user)
2182 # unlock if we have push from user who locked
2189 # unlock if we have push from user who locked
2183 make_lock = False
2190 make_lock = False
2184 else:
2191 else:
2185 # we're not the same user who locked, ban with
2192 # we're not the same user who locked, ban with
2186 # code defined in settings (default is 423 HTTP Locked) !
2193 # code defined in settings (default is 423 HTTP Locked) !
2187 log.debug('Repo %s is currently locked by %s', repo, user)
2194 log.debug('Repo %s is currently locked by %s', repo, user)
2188 currently_locked = True
2195 currently_locked = True
2189 elif action == 'pull':
2196 elif action == 'pull':
2190 # [0] user [1] date
2197 # [0] user [1] date
2191 if lock_info[0] and lock_info[1]:
2198 if lock_info[0] and lock_info[1]:
2192 log.debug('Repo %s is currently locked by %s', repo, user)
2199 log.debug('Repo %s is currently locked by %s', repo, user)
2193 currently_locked = True
2200 currently_locked = True
2194 else:
2201 else:
2195 log.debug('Setting lock on repo %s by %s', repo, user)
2202 log.debug('Setting lock on repo %s by %s', repo, user)
2196 make_lock = True
2203 make_lock = True
2197
2204
2198 else:
2205 else:
2199 log.debug('Repository %s do not have locking enabled', repo)
2206 log.debug('Repository %s do not have locking enabled', repo)
2200
2207
2201 log.debug('FINAL locking values make_lock:%s,locked:%s,locked_by:%s',
2208 log.debug('FINAL locking values make_lock:%s,locked:%s,locked_by:%s',
2202 make_lock, currently_locked, lock_info)
2209 make_lock, currently_locked, lock_info)
2203
2210
2204 from rhodecode.lib.auth import HasRepoPermissionAny
2211 from rhodecode.lib.auth import HasRepoPermissionAny
2205 perm_check = HasRepoPermissionAny('repository.write', 'repository.admin')
2212 perm_check = HasRepoPermissionAny('repository.write', 'repository.admin')
2206 if make_lock and not perm_check(repo_name=repo.repo_name, user=user):
2213 if make_lock and not perm_check(repo_name=repo.repo_name, user=user):
2207 # if we don't have at least write permission we cannot make a lock
2214 # if we don't have at least write permission we cannot make a lock
2208 log.debug('lock state reset back to FALSE due to lack '
2215 log.debug('lock state reset back to FALSE due to lack '
2209 'of at least read permission')
2216 'of at least read permission')
2210 make_lock = False
2217 make_lock = False
2211
2218
2212 return make_lock, currently_locked, lock_info
2219 return make_lock, currently_locked, lock_info
2213
2220
2214 @property
2221 @property
2215 def last_commit_cache_update_diff(self):
2222 def last_commit_cache_update_diff(self):
2216 return time.time() - (safe_int(self.changeset_cache.get('updated_on')) or 0)
2223 return time.time() - (safe_int(self.changeset_cache.get('updated_on')) or 0)
2217
2224
2218 @property
2225 @property
2219 def last_commit_change(self):
2226 def last_commit_change(self):
2220 from rhodecode.lib.vcs.utils.helpers import parse_datetime
2227 from rhodecode.lib.vcs.utils.helpers import parse_datetime
2221 empty_date = datetime.datetime.fromtimestamp(0)
2228 empty_date = datetime.datetime.fromtimestamp(0)
2222 date_latest = self.changeset_cache.get('date', empty_date)
2229 date_latest = self.changeset_cache.get('date', empty_date)
2223 try:
2230 try:
2224 return parse_datetime(date_latest)
2231 return parse_datetime(date_latest)
2225 except Exception:
2232 except Exception:
2226 return empty_date
2233 return empty_date
2227
2234
2228 @property
2235 @property
2229 def last_db_change(self):
2236 def last_db_change(self):
2230 return self.updated_on
2237 return self.updated_on
2231
2238
2232 @property
2239 @property
2233 def clone_uri_hidden(self):
2240 def clone_uri_hidden(self):
2234 clone_uri = self.clone_uri
2241 clone_uri = self.clone_uri
2235 if clone_uri:
2242 if clone_uri:
2236 import urlobject
2243 import urlobject
2237 url_obj = urlobject.URLObject(cleaned_uri(clone_uri))
2244 url_obj = urlobject.URLObject(cleaned_uri(clone_uri))
2238 if url_obj.password:
2245 if url_obj.password:
2239 clone_uri = url_obj.with_password('*****')
2246 clone_uri = url_obj.with_password('*****')
2240 return clone_uri
2247 return clone_uri
2241
2248
2242 @property
2249 @property
2243 def push_uri_hidden(self):
2250 def push_uri_hidden(self):
2244 push_uri = self.push_uri
2251 push_uri = self.push_uri
2245 if push_uri:
2252 if push_uri:
2246 import urlobject
2253 import urlobject
2247 url_obj = urlobject.URLObject(cleaned_uri(push_uri))
2254 url_obj = urlobject.URLObject(cleaned_uri(push_uri))
2248 if url_obj.password:
2255 if url_obj.password:
2249 push_uri = url_obj.with_password('*****')
2256 push_uri = url_obj.with_password('*****')
2250 return push_uri
2257 return push_uri
2251
2258
2252 def clone_url(self, **override):
2259 def clone_url(self, **override):
2253 from rhodecode.model.settings import SettingsModel
2260 from rhodecode.model.settings import SettingsModel
2254
2261
2255 uri_tmpl = None
2262 uri_tmpl = None
2256 if 'with_id' in override:
2263 if 'with_id' in override:
2257 uri_tmpl = self.DEFAULT_CLONE_URI_ID
2264 uri_tmpl = self.DEFAULT_CLONE_URI_ID
2258 del override['with_id']
2265 del override['with_id']
2259
2266
2260 if 'uri_tmpl' in override:
2267 if 'uri_tmpl' in override:
2261 uri_tmpl = override['uri_tmpl']
2268 uri_tmpl = override['uri_tmpl']
2262 del override['uri_tmpl']
2269 del override['uri_tmpl']
2263
2270
2264 ssh = False
2271 ssh = False
2265 if 'ssh' in override:
2272 if 'ssh' in override:
2266 ssh = True
2273 ssh = True
2267 del override['ssh']
2274 del override['ssh']
2268
2275
2269 # we didn't override our tmpl from **overrides
2276 # we didn't override our tmpl from **overrides
2270 request = get_current_request()
2277 request = get_current_request()
2271 if not uri_tmpl:
2278 if not uri_tmpl:
2272 if hasattr(request, 'call_context') and hasattr(request.call_context, 'rc_config'):
2279 if hasattr(request, 'call_context') and hasattr(request.call_context, 'rc_config'):
2273 rc_config = request.call_context.rc_config
2280 rc_config = request.call_context.rc_config
2274 else:
2281 else:
2275 rc_config = SettingsModel().get_all_settings(cache=True)
2282 rc_config = SettingsModel().get_all_settings(cache=True)
2276 if ssh:
2283 if ssh:
2277 uri_tmpl = rc_config.get(
2284 uri_tmpl = rc_config.get(
2278 'rhodecode_clone_uri_ssh_tmpl') or self.DEFAULT_CLONE_URI_SSH
2285 'rhodecode_clone_uri_ssh_tmpl') or self.DEFAULT_CLONE_URI_SSH
2279 else:
2286 else:
2280 uri_tmpl = rc_config.get(
2287 uri_tmpl = rc_config.get(
2281 'rhodecode_clone_uri_tmpl') or self.DEFAULT_CLONE_URI
2288 'rhodecode_clone_uri_tmpl') or self.DEFAULT_CLONE_URI
2282
2289
2283 return get_clone_url(request=request,
2290 return get_clone_url(request=request,
2284 uri_tmpl=uri_tmpl,
2291 uri_tmpl=uri_tmpl,
2285 repo_name=self.repo_name,
2292 repo_name=self.repo_name,
2286 repo_id=self.repo_id, **override)
2293 repo_id=self.repo_id, **override)
2287
2294
2288 def set_state(self, state):
2295 def set_state(self, state):
2289 self.repo_state = state
2296 self.repo_state = state
2290 Session().add(self)
2297 Session().add(self)
2291 #==========================================================================
2298 #==========================================================================
2292 # SCM PROPERTIES
2299 # SCM PROPERTIES
2293 #==========================================================================
2300 #==========================================================================
2294
2301
2295 def get_commit(self, commit_id=None, commit_idx=None, pre_load=None):
2302 def get_commit(self, commit_id=None, commit_idx=None, pre_load=None):
2296 return get_commit_safe(
2303 return get_commit_safe(
2297 self.scm_instance(), commit_id, commit_idx, pre_load=pre_load)
2304 self.scm_instance(), commit_id, commit_idx, pre_load=pre_load)
2298
2305
2299 def get_changeset(self, rev=None, pre_load=None):
2306 def get_changeset(self, rev=None, pre_load=None):
2300 warnings.warn("Use get_commit", DeprecationWarning)
2307 warnings.warn("Use get_commit", DeprecationWarning)
2301 commit_id = None
2308 commit_id = None
2302 commit_idx = None
2309 commit_idx = None
2303 if isinstance(rev, compat.string_types):
2310 if isinstance(rev, compat.string_types):
2304 commit_id = rev
2311 commit_id = rev
2305 else:
2312 else:
2306 commit_idx = rev
2313 commit_idx = rev
2307 return self.get_commit(commit_id=commit_id, commit_idx=commit_idx,
2314 return self.get_commit(commit_id=commit_id, commit_idx=commit_idx,
2308 pre_load=pre_load)
2315 pre_load=pre_load)
2309
2316
2310 def get_landing_commit(self):
2317 def get_landing_commit(self):
2311 """
2318 """
2312 Returns landing commit, or if that doesn't exist returns the tip
2319 Returns landing commit, or if that doesn't exist returns the tip
2313 """
2320 """
2314 _rev_type, _rev = self.landing_rev
2321 _rev_type, _rev = self.landing_rev
2315 commit = self.get_commit(_rev)
2322 commit = self.get_commit(_rev)
2316 if isinstance(commit, EmptyCommit):
2323 if isinstance(commit, EmptyCommit):
2317 return self.get_commit()
2324 return self.get_commit()
2318 return commit
2325 return commit
2319
2326
2320 def flush_commit_cache(self):
2327 def flush_commit_cache(self):
2321 self.update_commit_cache(cs_cache={'raw_id':'0'})
2328 self.update_commit_cache(cs_cache={'raw_id':'0'})
2322 self.update_commit_cache()
2329 self.update_commit_cache()
2323
2330
2324 def update_commit_cache(self, cs_cache=None, config=None):
2331 def update_commit_cache(self, cs_cache=None, config=None):
2325 """
2332 """
2326 Update cache of last commit for repository, keys should be::
2333 Update cache of last commit for repository, keys should be::
2327
2334
2328 source_repo_id
2335 source_repo_id
2329 short_id
2336 short_id
2330 raw_id
2337 raw_id
2331 revision
2338 revision
2332 parents
2339 parents
2333 message
2340 message
2334 date
2341 date
2335 author
2342 author
2336 updated_on
2343 updated_on
2337
2344
2338 """
2345 """
2339 from rhodecode.lib.vcs.backends.base import BaseChangeset
2346 from rhodecode.lib.vcs.backends.base import BaseChangeset
2340 if cs_cache is None:
2347 if cs_cache is None:
2341 # use no-cache version here
2348 # use no-cache version here
2342 scm_repo = self.scm_instance(cache=False, config=config)
2349 scm_repo = self.scm_instance(cache=False, config=config)
2343
2350
2344 empty = scm_repo is None or scm_repo.is_empty()
2351 empty = scm_repo is None or scm_repo.is_empty()
2345 if not empty:
2352 if not empty:
2346 cs_cache = scm_repo.get_commit(
2353 cs_cache = scm_repo.get_commit(
2347 pre_load=["author", "date", "message", "parents", "branch"])
2354 pre_load=["author", "date", "message", "parents", "branch"])
2348 else:
2355 else:
2349 cs_cache = EmptyCommit()
2356 cs_cache = EmptyCommit()
2350
2357
2351 if isinstance(cs_cache, BaseChangeset):
2358 if isinstance(cs_cache, BaseChangeset):
2352 cs_cache = cs_cache.__json__()
2359 cs_cache = cs_cache.__json__()
2353
2360
2354 def is_outdated(new_cs_cache):
2361 def is_outdated(new_cs_cache):
2355 if (new_cs_cache['raw_id'] != self.changeset_cache['raw_id'] or
2362 if (new_cs_cache['raw_id'] != self.changeset_cache['raw_id'] or
2356 new_cs_cache['revision'] != self.changeset_cache['revision']):
2363 new_cs_cache['revision'] != self.changeset_cache['revision']):
2357 return True
2364 return True
2358 return False
2365 return False
2359
2366
2360 # check if we have maybe already latest cached revision
2367 # check if we have maybe already latest cached revision
2361 if is_outdated(cs_cache) or not self.changeset_cache:
2368 if is_outdated(cs_cache) or not self.changeset_cache:
2362 _default = datetime.datetime.utcnow()
2369 _default = datetime.datetime.utcnow()
2363 last_change = cs_cache.get('date') or _default
2370 last_change = cs_cache.get('date') or _default
2364 # we check if last update is newer than the new value
2371 # we check if last update is newer than the new value
2365 # if yes, we use the current timestamp instead. Imagine you get
2372 # if yes, we use the current timestamp instead. Imagine you get
2366 # old commit pushed 1y ago, we'd set last update 1y to ago.
2373 # old commit pushed 1y ago, we'd set last update 1y to ago.
2367 last_change_timestamp = datetime_to_time(last_change)
2374 last_change_timestamp = datetime_to_time(last_change)
2368 current_timestamp = datetime_to_time(last_change)
2375 current_timestamp = datetime_to_time(last_change)
2369 if last_change_timestamp > current_timestamp:
2376 if last_change_timestamp > current_timestamp:
2370 cs_cache['date'] = _default
2377 cs_cache['date'] = _default
2371
2378
2372 cs_cache['updated_on'] = time.time()
2379 cs_cache['updated_on'] = time.time()
2373 self.changeset_cache = cs_cache
2380 self.changeset_cache = cs_cache
2374 self.updated_on = last_change
2381 self.updated_on = last_change
2375 Session().add(self)
2382 Session().add(self)
2376 Session().commit()
2383 Session().commit()
2377
2384
2378 log.debug('updated repo `%s` with new commit cache %s',
2385 log.debug('updated repo `%s` with new commit cache %s',
2379 self.repo_name, cs_cache)
2386 self.repo_name, cs_cache)
2380 else:
2387 else:
2381 cs_cache = self.changeset_cache
2388 cs_cache = self.changeset_cache
2382 cs_cache['updated_on'] = time.time()
2389 cs_cache['updated_on'] = time.time()
2383 self.changeset_cache = cs_cache
2390 self.changeset_cache = cs_cache
2384 Session().add(self)
2391 Session().add(self)
2385 Session().commit()
2392 Session().commit()
2386
2393
2387 log.debug('Skipping update_commit_cache for repo:`%s` '
2394 log.debug('Skipping update_commit_cache for repo:`%s` '
2388 'commit already with latest changes', self.repo_name)
2395 'commit already with latest changes', self.repo_name)
2389
2396
2390 @property
2397 @property
2391 def tip(self):
2398 def tip(self):
2392 return self.get_commit('tip')
2399 return self.get_commit('tip')
2393
2400
2394 @property
2401 @property
2395 def author(self):
2402 def author(self):
2396 return self.tip.author
2403 return self.tip.author
2397
2404
2398 @property
2405 @property
2399 def last_change(self):
2406 def last_change(self):
2400 return self.scm_instance().last_change
2407 return self.scm_instance().last_change
2401
2408
2402 def get_comments(self, revisions=None):
2409 def get_comments(self, revisions=None):
2403 """
2410 """
2404 Returns comments for this repository grouped by revisions
2411 Returns comments for this repository grouped by revisions
2405
2412
2406 :param revisions: filter query by revisions only
2413 :param revisions: filter query by revisions only
2407 """
2414 """
2408 cmts = ChangesetComment.query()\
2415 cmts = ChangesetComment.query()\
2409 .filter(ChangesetComment.repo == self)
2416 .filter(ChangesetComment.repo == self)
2410 if revisions:
2417 if revisions:
2411 cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
2418 cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
2412 grouped = collections.defaultdict(list)
2419 grouped = collections.defaultdict(list)
2413 for cmt in cmts.all():
2420 for cmt in cmts.all():
2414 grouped[cmt.revision].append(cmt)
2421 grouped[cmt.revision].append(cmt)
2415 return grouped
2422 return grouped
2416
2423
2417 def statuses(self, revisions=None):
2424 def statuses(self, revisions=None):
2418 """
2425 """
2419 Returns statuses for this repository
2426 Returns statuses for this repository
2420
2427
2421 :param revisions: list of revisions to get statuses for
2428 :param revisions: list of revisions to get statuses for
2422 """
2429 """
2423 statuses = ChangesetStatus.query()\
2430 statuses = ChangesetStatus.query()\
2424 .filter(ChangesetStatus.repo == self)\
2431 .filter(ChangesetStatus.repo == self)\
2425 .filter(ChangesetStatus.version == 0)
2432 .filter(ChangesetStatus.version == 0)
2426
2433
2427 if revisions:
2434 if revisions:
2428 # Try doing the filtering in chunks to avoid hitting limits
2435 # Try doing the filtering in chunks to avoid hitting limits
2429 size = 500
2436 size = 500
2430 status_results = []
2437 status_results = []
2431 for chunk in xrange(0, len(revisions), size):
2438 for chunk in xrange(0, len(revisions), size):
2432 status_results += statuses.filter(
2439 status_results += statuses.filter(
2433 ChangesetStatus.revision.in_(
2440 ChangesetStatus.revision.in_(
2434 revisions[chunk: chunk+size])
2441 revisions[chunk: chunk+size])
2435 ).all()
2442 ).all()
2436 else:
2443 else:
2437 status_results = statuses.all()
2444 status_results = statuses.all()
2438
2445
2439 grouped = {}
2446 grouped = {}
2440
2447
2441 # maybe we have open new pullrequest without a status?
2448 # maybe we have open new pullrequest without a status?
2442 stat = ChangesetStatus.STATUS_UNDER_REVIEW
2449 stat = ChangesetStatus.STATUS_UNDER_REVIEW
2443 status_lbl = ChangesetStatus.get_status_lbl(stat)
2450 status_lbl = ChangesetStatus.get_status_lbl(stat)
2444 for pr in PullRequest.query().filter(PullRequest.source_repo == self).all():
2451 for pr in PullRequest.query().filter(PullRequest.source_repo == self).all():
2445 for rev in pr.revisions:
2452 for rev in pr.revisions:
2446 pr_id = pr.pull_request_id
2453 pr_id = pr.pull_request_id
2447 pr_repo = pr.target_repo.repo_name
2454 pr_repo = pr.target_repo.repo_name
2448 grouped[rev] = [stat, status_lbl, pr_id, pr_repo]
2455 grouped[rev] = [stat, status_lbl, pr_id, pr_repo]
2449
2456
2450 for stat in status_results:
2457 for stat in status_results:
2451 pr_id = pr_repo = None
2458 pr_id = pr_repo = None
2452 if stat.pull_request:
2459 if stat.pull_request:
2453 pr_id = stat.pull_request.pull_request_id
2460 pr_id = stat.pull_request.pull_request_id
2454 pr_repo = stat.pull_request.target_repo.repo_name
2461 pr_repo = stat.pull_request.target_repo.repo_name
2455 grouped[stat.revision] = [str(stat.status), stat.status_lbl,
2462 grouped[stat.revision] = [str(stat.status), stat.status_lbl,
2456 pr_id, pr_repo]
2463 pr_id, pr_repo]
2457 return grouped
2464 return grouped
2458
2465
2459 # ==========================================================================
2466 # ==========================================================================
2460 # SCM CACHE INSTANCE
2467 # SCM CACHE INSTANCE
2461 # ==========================================================================
2468 # ==========================================================================
2462
2469
2463 def scm_instance(self, **kwargs):
2470 def scm_instance(self, **kwargs):
2464 import rhodecode
2471 import rhodecode
2465
2472
2466 # Passing a config will not hit the cache currently only used
2473 # Passing a config will not hit the cache currently only used
2467 # for repo2dbmapper
2474 # for repo2dbmapper
2468 config = kwargs.pop('config', None)
2475 config = kwargs.pop('config', None)
2469 cache = kwargs.pop('cache', None)
2476 cache = kwargs.pop('cache', None)
2470 vcs_full_cache = kwargs.pop('vcs_full_cache', None)
2477 vcs_full_cache = kwargs.pop('vcs_full_cache', None)
2471 if vcs_full_cache is not None:
2478 if vcs_full_cache is not None:
2472 # allows override global config
2479 # allows override global config
2473 full_cache = vcs_full_cache
2480 full_cache = vcs_full_cache
2474 else:
2481 else:
2475 full_cache = str2bool(rhodecode.CONFIG.get('vcs_full_cache'))
2482 full_cache = str2bool(rhodecode.CONFIG.get('vcs_full_cache'))
2476 # if cache is NOT defined use default global, else we have a full
2483 # if cache is NOT defined use default global, else we have a full
2477 # control over cache behaviour
2484 # control over cache behaviour
2478 if cache is None and full_cache and not config:
2485 if cache is None and full_cache and not config:
2479 log.debug('Initializing pure cached instance for %s', self.repo_path)
2486 log.debug('Initializing pure cached instance for %s', self.repo_path)
2480 return self._get_instance_cached()
2487 return self._get_instance_cached()
2481
2488
2482 # cache here is sent to the "vcs server"
2489 # cache here is sent to the "vcs server"
2483 return self._get_instance(cache=bool(cache), config=config)
2490 return self._get_instance(cache=bool(cache), config=config)
2484
2491
2485 def _get_instance_cached(self):
2492 def _get_instance_cached(self):
2486 from rhodecode.lib import rc_cache
2493 from rhodecode.lib import rc_cache
2487
2494
2488 cache_namespace_uid = 'cache_repo_instance.{}'.format(self.repo_id)
2495 cache_namespace_uid = 'cache_repo_instance.{}'.format(self.repo_id)
2489 invalidation_namespace = CacheKey.REPO_INVALIDATION_NAMESPACE.format(
2496 invalidation_namespace = CacheKey.REPO_INVALIDATION_NAMESPACE.format(
2490 repo_id=self.repo_id)
2497 repo_id=self.repo_id)
2491 region = rc_cache.get_or_create_region('cache_repo_longterm', cache_namespace_uid)
2498 region = rc_cache.get_or_create_region('cache_repo_longterm', cache_namespace_uid)
2492
2499
2493 @region.conditional_cache_on_arguments(namespace=cache_namespace_uid)
2500 @region.conditional_cache_on_arguments(namespace=cache_namespace_uid)
2494 def get_instance_cached(repo_id, context_id, _cache_state_uid):
2501 def get_instance_cached(repo_id, context_id, _cache_state_uid):
2495 return self._get_instance(repo_state_uid=_cache_state_uid)
2502 return self._get_instance(repo_state_uid=_cache_state_uid)
2496
2503
2497 # we must use thread scoped cache here,
2504 # we must use thread scoped cache here,
2498 # because each thread of gevent needs it's own not shared connection and cache
2505 # because each thread of gevent needs it's own not shared connection and cache
2499 # we also alter `args` so the cache key is individual for every green thread.
2506 # we also alter `args` so the cache key is individual for every green thread.
2500 inv_context_manager = rc_cache.InvalidationContext(
2507 inv_context_manager = rc_cache.InvalidationContext(
2501 uid=cache_namespace_uid, invalidation_namespace=invalidation_namespace,
2508 uid=cache_namespace_uid, invalidation_namespace=invalidation_namespace,
2502 thread_scoped=True)
2509 thread_scoped=True)
2503 with inv_context_manager as invalidation_context:
2510 with inv_context_manager as invalidation_context:
2504 cache_state_uid = invalidation_context.cache_data['cache_state_uid']
2511 cache_state_uid = invalidation_context.cache_data['cache_state_uid']
2505 args = (self.repo_id, inv_context_manager.cache_key, cache_state_uid)
2512 args = (self.repo_id, inv_context_manager.cache_key, cache_state_uid)
2506
2513
2507 # re-compute and store cache if we get invalidate signal
2514 # re-compute and store cache if we get invalidate signal
2508 if invalidation_context.should_invalidate():
2515 if invalidation_context.should_invalidate():
2509 instance = get_instance_cached.refresh(*args)
2516 instance = get_instance_cached.refresh(*args)
2510 else:
2517 else:
2511 instance = get_instance_cached(*args)
2518 instance = get_instance_cached(*args)
2512
2519
2513 log.debug('Repo instance fetched in %.4fs', inv_context_manager.compute_time)
2520 log.debug('Repo instance fetched in %.4fs', inv_context_manager.compute_time)
2514 return instance
2521 return instance
2515
2522
2516 def _get_instance(self, cache=True, config=None, repo_state_uid=None):
2523 def _get_instance(self, cache=True, config=None, repo_state_uid=None):
2517 log.debug('Initializing %s instance `%s` with cache flag set to: %s',
2524 log.debug('Initializing %s instance `%s` with cache flag set to: %s',
2518 self.repo_type, self.repo_path, cache)
2525 self.repo_type, self.repo_path, cache)
2519 config = config or self._config
2526 config = config or self._config
2520 custom_wire = {
2527 custom_wire = {
2521 'cache': cache, # controls the vcs.remote cache
2528 'cache': cache, # controls the vcs.remote cache
2522 'repo_state_uid': repo_state_uid
2529 'repo_state_uid': repo_state_uid
2523 }
2530 }
2524 repo = get_vcs_instance(
2531 repo = get_vcs_instance(
2525 repo_path=safe_str(self.repo_full_path),
2532 repo_path=safe_str(self.repo_full_path),
2526 config=config,
2533 config=config,
2527 with_wire=custom_wire,
2534 with_wire=custom_wire,
2528 create=False,
2535 create=False,
2529 _vcs_alias=self.repo_type)
2536 _vcs_alias=self.repo_type)
2530 if repo is not None:
2537 if repo is not None:
2531 repo.count() # cache rebuild
2538 repo.count() # cache rebuild
2532 return repo
2539 return repo
2533
2540
2534 def get_shadow_repository_path(self, workspace_id):
2541 def get_shadow_repository_path(self, workspace_id):
2535 from rhodecode.lib.vcs.backends.base import BaseRepository
2542 from rhodecode.lib.vcs.backends.base import BaseRepository
2536 shadow_repo_path = BaseRepository._get_shadow_repository_path(
2543 shadow_repo_path = BaseRepository._get_shadow_repository_path(
2537 self.repo_full_path, self.repo_id, workspace_id)
2544 self.repo_full_path, self.repo_id, workspace_id)
2538 return shadow_repo_path
2545 return shadow_repo_path
2539
2546
2540 def __json__(self):
2547 def __json__(self):
2541 return {'landing_rev': self.landing_rev}
2548 return {'landing_rev': self.landing_rev}
2542
2549
2543 def get_dict(self):
2550 def get_dict(self):
2544
2551
2545 # Since we transformed `repo_name` to a hybrid property, we need to
2552 # Since we transformed `repo_name` to a hybrid property, we need to
2546 # keep compatibility with the code which uses `repo_name` field.
2553 # keep compatibility with the code which uses `repo_name` field.
2547
2554
2548 result = super(Repository, self).get_dict()
2555 result = super(Repository, self).get_dict()
2549 result['repo_name'] = result.pop('_repo_name', None)
2556 result['repo_name'] = result.pop('_repo_name', None)
2550 return result
2557 return result
2551
2558
2552
2559
2553 class RepoGroup(Base, BaseModel):
2560 class RepoGroup(Base, BaseModel):
2554 __tablename__ = 'groups'
2561 __tablename__ = 'groups'
2555 __table_args__ = (
2562 __table_args__ = (
2556 UniqueConstraint('group_name', 'group_parent_id'),
2563 UniqueConstraint('group_name', 'group_parent_id'),
2557 base_table_args,
2564 base_table_args,
2558 )
2565 )
2559 __mapper_args__ = {'order_by': 'group_name'}
2566 __mapper_args__ = {'order_by': 'group_name'}
2560
2567
2561 CHOICES_SEPARATOR = '/' # used to generate select2 choices for nested groups
2568 CHOICES_SEPARATOR = '/' # used to generate select2 choices for nested groups
2562
2569
2563 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2570 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2564 _group_name = Column("group_name", String(255), nullable=False, unique=True, default=None)
2571 _group_name = Column("group_name", String(255), nullable=False, unique=True, default=None)
2565 group_name_hash = Column("repo_group_name_hash", String(1024), nullable=False, unique=False)
2572 group_name_hash = Column("repo_group_name_hash", String(1024), nullable=False, unique=False)
2566 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
2573 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
2567 group_description = Column("group_description", String(10000), nullable=True, unique=None, default=None)
2574 group_description = Column("group_description", String(10000), nullable=True, unique=None, default=None)
2568 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
2575 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
2569 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
2576 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
2570 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2577 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2571 updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
2578 updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
2572 personal = Column('personal', Boolean(), nullable=True, unique=None, default=None)
2579 personal = Column('personal', Boolean(), nullable=True, unique=None, default=None)
2573 _changeset_cache = Column(
2580 _changeset_cache = Column(
2574 "changeset_cache", LargeBinary(), nullable=True) # JSON data
2581 "changeset_cache", LargeBinary(), nullable=True) # JSON data
2575
2582
2576 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
2583 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
2577 users_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
2584 users_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
2578 parent_group = relationship('RepoGroup', remote_side=group_id)
2585 parent_group = relationship('RepoGroup', remote_side=group_id)
2579 user = relationship('User')
2586 user = relationship('User')
2580 integrations = relationship('Integration', cascade="all, delete-orphan")
2587 integrations = relationship('Integration', cascade="all, delete-orphan")
2581
2588
2589 # no cascade, set NULL
2590 scope_artifacts = relationship('FileStore', primaryjoin='FileStore.scope_repo_group_id==RepoGroup.group_id')
2591
2582 def __init__(self, group_name='', parent_group=None):
2592 def __init__(self, group_name='', parent_group=None):
2583 self.group_name = group_name
2593 self.group_name = group_name
2584 self.parent_group = parent_group
2594 self.parent_group = parent_group
2585
2595
2586 def __unicode__(self):
2596 def __unicode__(self):
2587 return u"<%s('id:%s:%s')>" % (
2597 return u"<%s('id:%s:%s')>" % (
2588 self.__class__.__name__, self.group_id, self.group_name)
2598 self.__class__.__name__, self.group_id, self.group_name)
2589
2599
2590 @hybrid_property
2600 @hybrid_property
2591 def group_name(self):
2601 def group_name(self):
2592 return self._group_name
2602 return self._group_name
2593
2603
2594 @group_name.setter
2604 @group_name.setter
2595 def group_name(self, value):
2605 def group_name(self, value):
2596 self._group_name = value
2606 self._group_name = value
2597 self.group_name_hash = self.hash_repo_group_name(value)
2607 self.group_name_hash = self.hash_repo_group_name(value)
2598
2608
2599 @hybrid_property
2609 @hybrid_property
2600 def changeset_cache(self):
2610 def changeset_cache(self):
2601 from rhodecode.lib.vcs.backends.base import EmptyCommit
2611 from rhodecode.lib.vcs.backends.base import EmptyCommit
2602 dummy = EmptyCommit().__json__()
2612 dummy = EmptyCommit().__json__()
2603 if not self._changeset_cache:
2613 if not self._changeset_cache:
2604 dummy['source_repo_id'] = ''
2614 dummy['source_repo_id'] = ''
2605 return json.loads(json.dumps(dummy))
2615 return json.loads(json.dumps(dummy))
2606
2616
2607 try:
2617 try:
2608 return json.loads(self._changeset_cache)
2618 return json.loads(self._changeset_cache)
2609 except TypeError:
2619 except TypeError:
2610 return dummy
2620 return dummy
2611 except Exception:
2621 except Exception:
2612 log.error(traceback.format_exc())
2622 log.error(traceback.format_exc())
2613 return dummy
2623 return dummy
2614
2624
2615 @changeset_cache.setter
2625 @changeset_cache.setter
2616 def changeset_cache(self, val):
2626 def changeset_cache(self, val):
2617 try:
2627 try:
2618 self._changeset_cache = json.dumps(val)
2628 self._changeset_cache = json.dumps(val)
2619 except Exception:
2629 except Exception:
2620 log.error(traceback.format_exc())
2630 log.error(traceback.format_exc())
2621
2631
2622 @validates('group_parent_id')
2632 @validates('group_parent_id')
2623 def validate_group_parent_id(self, key, val):
2633 def validate_group_parent_id(self, key, val):
2624 """
2634 """
2625 Check cycle references for a parent group to self
2635 Check cycle references for a parent group to self
2626 """
2636 """
2627 if self.group_id and val:
2637 if self.group_id and val:
2628 assert val != self.group_id
2638 assert val != self.group_id
2629
2639
2630 return val
2640 return val
2631
2641
2632 @hybrid_property
2642 @hybrid_property
2633 def description_safe(self):
2643 def description_safe(self):
2634 from rhodecode.lib import helpers as h
2644 from rhodecode.lib import helpers as h
2635 return h.escape(self.group_description)
2645 return h.escape(self.group_description)
2636
2646
2637 @classmethod
2647 @classmethod
2638 def hash_repo_group_name(cls, repo_group_name):
2648 def hash_repo_group_name(cls, repo_group_name):
2639 val = remove_formatting(repo_group_name)
2649 val = remove_formatting(repo_group_name)
2640 val = safe_str(val).lower()
2650 val = safe_str(val).lower()
2641 chars = []
2651 chars = []
2642 for c in val:
2652 for c in val:
2643 if c not in string.ascii_letters:
2653 if c not in string.ascii_letters:
2644 c = str(ord(c))
2654 c = str(ord(c))
2645 chars.append(c)
2655 chars.append(c)
2646
2656
2647 return ''.join(chars)
2657 return ''.join(chars)
2648
2658
2649 @classmethod
2659 @classmethod
2650 def _generate_choice(cls, repo_group):
2660 def _generate_choice(cls, repo_group):
2651 from webhelpers.html import literal as _literal
2661 from webhelpers.html import literal as _literal
2652 _name = lambda k: _literal(cls.CHOICES_SEPARATOR.join(k))
2662 _name = lambda k: _literal(cls.CHOICES_SEPARATOR.join(k))
2653 return repo_group.group_id, _name(repo_group.full_path_splitted)
2663 return repo_group.group_id, _name(repo_group.full_path_splitted)
2654
2664
2655 @classmethod
2665 @classmethod
2656 def groups_choices(cls, groups=None, show_empty_group=True):
2666 def groups_choices(cls, groups=None, show_empty_group=True):
2657 if not groups:
2667 if not groups:
2658 groups = cls.query().all()
2668 groups = cls.query().all()
2659
2669
2660 repo_groups = []
2670 repo_groups = []
2661 if show_empty_group:
2671 if show_empty_group:
2662 repo_groups = [(-1, u'-- %s --' % _('No parent'))]
2672 repo_groups = [(-1, u'-- %s --' % _('No parent'))]
2663
2673
2664 repo_groups.extend([cls._generate_choice(x) for x in groups])
2674 repo_groups.extend([cls._generate_choice(x) for x in groups])
2665
2675
2666 repo_groups = sorted(
2676 repo_groups = sorted(
2667 repo_groups, key=lambda t: t[1].split(cls.CHOICES_SEPARATOR)[0])
2677 repo_groups, key=lambda t: t[1].split(cls.CHOICES_SEPARATOR)[0])
2668 return repo_groups
2678 return repo_groups
2669
2679
2670 @classmethod
2680 @classmethod
2671 def url_sep(cls):
2681 def url_sep(cls):
2672 return URL_SEP
2682 return URL_SEP
2673
2683
2674 @classmethod
2684 @classmethod
2675 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
2685 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
2676 if case_insensitive:
2686 if case_insensitive:
2677 gr = cls.query().filter(func.lower(cls.group_name)
2687 gr = cls.query().filter(func.lower(cls.group_name)
2678 == func.lower(group_name))
2688 == func.lower(group_name))
2679 else:
2689 else:
2680 gr = cls.query().filter(cls.group_name == group_name)
2690 gr = cls.query().filter(cls.group_name == group_name)
2681 if cache:
2691 if cache:
2682 name_key = _hash_key(group_name)
2692 name_key = _hash_key(group_name)
2683 gr = gr.options(
2693 gr = gr.options(
2684 FromCache("sql_cache_short", "get_group_%s" % name_key))
2694 FromCache("sql_cache_short", "get_group_%s" % name_key))
2685 return gr.scalar()
2695 return gr.scalar()
2686
2696
2687 @classmethod
2697 @classmethod
2688 def get_user_personal_repo_group(cls, user_id):
2698 def get_user_personal_repo_group(cls, user_id):
2689 user = User.get(user_id)
2699 user = User.get(user_id)
2690 if user.username == User.DEFAULT_USER:
2700 if user.username == User.DEFAULT_USER:
2691 return None
2701 return None
2692
2702
2693 return cls.query()\
2703 return cls.query()\
2694 .filter(cls.personal == true()) \
2704 .filter(cls.personal == true()) \
2695 .filter(cls.user == user) \
2705 .filter(cls.user == user) \
2696 .order_by(cls.group_id.asc()) \
2706 .order_by(cls.group_id.asc()) \
2697 .first()
2707 .first()
2698
2708
2699 @classmethod
2709 @classmethod
2700 def get_all_repo_groups(cls, user_id=Optional(None), group_id=Optional(None),
2710 def get_all_repo_groups(cls, user_id=Optional(None), group_id=Optional(None),
2701 case_insensitive=True):
2711 case_insensitive=True):
2702 q = RepoGroup.query()
2712 q = RepoGroup.query()
2703
2713
2704 if not isinstance(user_id, Optional):
2714 if not isinstance(user_id, Optional):
2705 q = q.filter(RepoGroup.user_id == user_id)
2715 q = q.filter(RepoGroup.user_id == user_id)
2706
2716
2707 if not isinstance(group_id, Optional):
2717 if not isinstance(group_id, Optional):
2708 q = q.filter(RepoGroup.group_parent_id == group_id)
2718 q = q.filter(RepoGroup.group_parent_id == group_id)
2709
2719
2710 if case_insensitive:
2720 if case_insensitive:
2711 q = q.order_by(func.lower(RepoGroup.group_name))
2721 q = q.order_by(func.lower(RepoGroup.group_name))
2712 else:
2722 else:
2713 q = q.order_by(RepoGroup.group_name)
2723 q = q.order_by(RepoGroup.group_name)
2714 return q.all()
2724 return q.all()
2715
2725
2716 @property
2726 @property
2717 def parents(self, parents_recursion_limit = 10):
2727 def parents(self, parents_recursion_limit = 10):
2718 groups = []
2728 groups = []
2719 if self.parent_group is None:
2729 if self.parent_group is None:
2720 return groups
2730 return groups
2721 cur_gr = self.parent_group
2731 cur_gr = self.parent_group
2722 groups.insert(0, cur_gr)
2732 groups.insert(0, cur_gr)
2723 cnt = 0
2733 cnt = 0
2724 while 1:
2734 while 1:
2725 cnt += 1
2735 cnt += 1
2726 gr = getattr(cur_gr, 'parent_group', None)
2736 gr = getattr(cur_gr, 'parent_group', None)
2727 cur_gr = cur_gr.parent_group
2737 cur_gr = cur_gr.parent_group
2728 if gr is None:
2738 if gr is None:
2729 break
2739 break
2730 if cnt == parents_recursion_limit:
2740 if cnt == parents_recursion_limit:
2731 # this will prevent accidental infinit loops
2741 # this will prevent accidental infinit loops
2732 log.error('more than %s parents found for group %s, stopping '
2742 log.error('more than %s parents found for group %s, stopping '
2733 'recursive parent fetching', parents_recursion_limit, self)
2743 'recursive parent fetching', parents_recursion_limit, self)
2734 break
2744 break
2735
2745
2736 groups.insert(0, gr)
2746 groups.insert(0, gr)
2737 return groups
2747 return groups
2738
2748
2739 @property
2749 @property
2740 def last_commit_cache_update_diff(self):
2750 def last_commit_cache_update_diff(self):
2741 return time.time() - (safe_int(self.changeset_cache.get('updated_on')) or 0)
2751 return time.time() - (safe_int(self.changeset_cache.get('updated_on')) or 0)
2742
2752
2743 @property
2753 @property
2744 def last_commit_change(self):
2754 def last_commit_change(self):
2745 from rhodecode.lib.vcs.utils.helpers import parse_datetime
2755 from rhodecode.lib.vcs.utils.helpers import parse_datetime
2746 empty_date = datetime.datetime.fromtimestamp(0)
2756 empty_date = datetime.datetime.fromtimestamp(0)
2747 date_latest = self.changeset_cache.get('date', empty_date)
2757 date_latest = self.changeset_cache.get('date', empty_date)
2748 try:
2758 try:
2749 return parse_datetime(date_latest)
2759 return parse_datetime(date_latest)
2750 except Exception:
2760 except Exception:
2751 return empty_date
2761 return empty_date
2752
2762
2753 @property
2763 @property
2754 def last_db_change(self):
2764 def last_db_change(self):
2755 return self.updated_on
2765 return self.updated_on
2756
2766
2757 @property
2767 @property
2758 def children(self):
2768 def children(self):
2759 return RepoGroup.query().filter(RepoGroup.parent_group == self)
2769 return RepoGroup.query().filter(RepoGroup.parent_group == self)
2760
2770
2761 @property
2771 @property
2762 def name(self):
2772 def name(self):
2763 return self.group_name.split(RepoGroup.url_sep())[-1]
2773 return self.group_name.split(RepoGroup.url_sep())[-1]
2764
2774
2765 @property
2775 @property
2766 def full_path(self):
2776 def full_path(self):
2767 return self.group_name
2777 return self.group_name
2768
2778
2769 @property
2779 @property
2770 def full_path_splitted(self):
2780 def full_path_splitted(self):
2771 return self.group_name.split(RepoGroup.url_sep())
2781 return self.group_name.split(RepoGroup.url_sep())
2772
2782
2773 @property
2783 @property
2774 def repositories(self):
2784 def repositories(self):
2775 return Repository.query()\
2785 return Repository.query()\
2776 .filter(Repository.group == self)\
2786 .filter(Repository.group == self)\
2777 .order_by(Repository.repo_name)
2787 .order_by(Repository.repo_name)
2778
2788
2779 @property
2789 @property
2780 def repositories_recursive_count(self):
2790 def repositories_recursive_count(self):
2781 cnt = self.repositories.count()
2791 cnt = self.repositories.count()
2782
2792
2783 def children_count(group):
2793 def children_count(group):
2784 cnt = 0
2794 cnt = 0
2785 for child in group.children:
2795 for child in group.children:
2786 cnt += child.repositories.count()
2796 cnt += child.repositories.count()
2787 cnt += children_count(child)
2797 cnt += children_count(child)
2788 return cnt
2798 return cnt
2789
2799
2790 return cnt + children_count(self)
2800 return cnt + children_count(self)
2791
2801
2792 def _recursive_objects(self, include_repos=True, include_groups=True):
2802 def _recursive_objects(self, include_repos=True, include_groups=True):
2793 all_ = []
2803 all_ = []
2794
2804
2795 def _get_members(root_gr):
2805 def _get_members(root_gr):
2796 if include_repos:
2806 if include_repos:
2797 for r in root_gr.repositories:
2807 for r in root_gr.repositories:
2798 all_.append(r)
2808 all_.append(r)
2799 childs = root_gr.children.all()
2809 childs = root_gr.children.all()
2800 if childs:
2810 if childs:
2801 for gr in childs:
2811 for gr in childs:
2802 if include_groups:
2812 if include_groups:
2803 all_.append(gr)
2813 all_.append(gr)
2804 _get_members(gr)
2814 _get_members(gr)
2805
2815
2806 root_group = []
2816 root_group = []
2807 if include_groups:
2817 if include_groups:
2808 root_group = [self]
2818 root_group = [self]
2809
2819
2810 _get_members(self)
2820 _get_members(self)
2811 return root_group + all_
2821 return root_group + all_
2812
2822
2813 def recursive_groups_and_repos(self):
2823 def recursive_groups_and_repos(self):
2814 """
2824 """
2815 Recursive return all groups, with repositories in those groups
2825 Recursive return all groups, with repositories in those groups
2816 """
2826 """
2817 return self._recursive_objects()
2827 return self._recursive_objects()
2818
2828
2819 def recursive_groups(self):
2829 def recursive_groups(self):
2820 """
2830 """
2821 Returns all children groups for this group including children of children
2831 Returns all children groups for this group including children of children
2822 """
2832 """
2823 return self._recursive_objects(include_repos=False)
2833 return self._recursive_objects(include_repos=False)
2824
2834
2825 def recursive_repos(self):
2835 def recursive_repos(self):
2826 """
2836 """
2827 Returns all children repositories for this group
2837 Returns all children repositories for this group
2828 """
2838 """
2829 return self._recursive_objects(include_groups=False)
2839 return self._recursive_objects(include_groups=False)
2830
2840
2831 def get_new_name(self, group_name):
2841 def get_new_name(self, group_name):
2832 """
2842 """
2833 returns new full group name based on parent and new name
2843 returns new full group name based on parent and new name
2834
2844
2835 :param group_name:
2845 :param group_name:
2836 """
2846 """
2837 path_prefix = (self.parent_group.full_path_splitted if
2847 path_prefix = (self.parent_group.full_path_splitted if
2838 self.parent_group else [])
2848 self.parent_group else [])
2839 return RepoGroup.url_sep().join(path_prefix + [group_name])
2849 return RepoGroup.url_sep().join(path_prefix + [group_name])
2840
2850
2841 def update_commit_cache(self, config=None):
2851 def update_commit_cache(self, config=None):
2842 """
2852 """
2843 Update cache of last changeset for newest repository inside this group, keys should be::
2853 Update cache of last changeset for newest repository inside this group, keys should be::
2844
2854
2845 source_repo_id
2855 source_repo_id
2846 short_id
2856 short_id
2847 raw_id
2857 raw_id
2848 revision
2858 revision
2849 parents
2859 parents
2850 message
2860 message
2851 date
2861 date
2852 author
2862 author
2853
2863
2854 """
2864 """
2855 from rhodecode.lib.vcs.utils.helpers import parse_datetime
2865 from rhodecode.lib.vcs.utils.helpers import parse_datetime
2856
2866
2857 def repo_groups_and_repos():
2867 def repo_groups_and_repos():
2858 all_entries = OrderedDefaultDict(list)
2868 all_entries = OrderedDefaultDict(list)
2859
2869
2860 def _get_members(root_gr, pos=0):
2870 def _get_members(root_gr, pos=0):
2861
2871
2862 for repo in root_gr.repositories:
2872 for repo in root_gr.repositories:
2863 all_entries[root_gr].append(repo)
2873 all_entries[root_gr].append(repo)
2864
2874
2865 # fill in all parent positions
2875 # fill in all parent positions
2866 for parent_group in root_gr.parents:
2876 for parent_group in root_gr.parents:
2867 all_entries[parent_group].extend(all_entries[root_gr])
2877 all_entries[parent_group].extend(all_entries[root_gr])
2868
2878
2869 children_groups = root_gr.children.all()
2879 children_groups = root_gr.children.all()
2870 if children_groups:
2880 if children_groups:
2871 for cnt, gr in enumerate(children_groups, 1):
2881 for cnt, gr in enumerate(children_groups, 1):
2872 _get_members(gr, pos=pos+cnt)
2882 _get_members(gr, pos=pos+cnt)
2873
2883
2874 _get_members(root_gr=self)
2884 _get_members(root_gr=self)
2875 return all_entries
2885 return all_entries
2876
2886
2877 empty_date = datetime.datetime.fromtimestamp(0)
2887 empty_date = datetime.datetime.fromtimestamp(0)
2878 for repo_group, repos in repo_groups_and_repos().items():
2888 for repo_group, repos in repo_groups_and_repos().items():
2879
2889
2880 latest_repo_cs_cache = {}
2890 latest_repo_cs_cache = {}
2881 _date_latest = empty_date
2891 _date_latest = empty_date
2882 for repo in repos:
2892 for repo in repos:
2883 repo_cs_cache = repo.changeset_cache
2893 repo_cs_cache = repo.changeset_cache
2884 date_latest = latest_repo_cs_cache.get('date', empty_date)
2894 date_latest = latest_repo_cs_cache.get('date', empty_date)
2885 date_current = repo_cs_cache.get('date', empty_date)
2895 date_current = repo_cs_cache.get('date', empty_date)
2886 current_timestamp = datetime_to_time(parse_datetime(date_latest))
2896 current_timestamp = datetime_to_time(parse_datetime(date_latest))
2887 if current_timestamp < datetime_to_time(parse_datetime(date_current)):
2897 if current_timestamp < datetime_to_time(parse_datetime(date_current)):
2888 latest_repo_cs_cache = repo_cs_cache
2898 latest_repo_cs_cache = repo_cs_cache
2889 latest_repo_cs_cache['source_repo_id'] = repo.repo_id
2899 latest_repo_cs_cache['source_repo_id'] = repo.repo_id
2890 _date_latest = parse_datetime(latest_repo_cs_cache['date'])
2900 _date_latest = parse_datetime(latest_repo_cs_cache['date'])
2891
2901
2892 latest_repo_cs_cache['updated_on'] = time.time()
2902 latest_repo_cs_cache['updated_on'] = time.time()
2893 repo_group.changeset_cache = latest_repo_cs_cache
2903 repo_group.changeset_cache = latest_repo_cs_cache
2894 repo_group.updated_on = _date_latest
2904 repo_group.updated_on = _date_latest
2895 Session().add(repo_group)
2905 Session().add(repo_group)
2896 Session().commit()
2906 Session().commit()
2897
2907
2898 log.debug('updated repo group `%s` with new commit cache %s',
2908 log.debug('updated repo group `%s` with new commit cache %s',
2899 repo_group.group_name, latest_repo_cs_cache)
2909 repo_group.group_name, latest_repo_cs_cache)
2900
2910
2901 def permissions(self, with_admins=True, with_owner=True,
2911 def permissions(self, with_admins=True, with_owner=True,
2902 expand_from_user_groups=False):
2912 expand_from_user_groups=False):
2903 """
2913 """
2904 Permissions for repository groups
2914 Permissions for repository groups
2905 """
2915 """
2906 _admin_perm = 'group.admin'
2916 _admin_perm = 'group.admin'
2907
2917
2908 owner_row = []
2918 owner_row = []
2909 if with_owner:
2919 if with_owner:
2910 usr = AttributeDict(self.user.get_dict())
2920 usr = AttributeDict(self.user.get_dict())
2911 usr.owner_row = True
2921 usr.owner_row = True
2912 usr.permission = _admin_perm
2922 usr.permission = _admin_perm
2913 owner_row.append(usr)
2923 owner_row.append(usr)
2914
2924
2915 super_admin_ids = []
2925 super_admin_ids = []
2916 super_admin_rows = []
2926 super_admin_rows = []
2917 if with_admins:
2927 if with_admins:
2918 for usr in User.get_all_super_admins():
2928 for usr in User.get_all_super_admins():
2919 super_admin_ids.append(usr.user_id)
2929 super_admin_ids.append(usr.user_id)
2920 # if this admin is also owner, don't double the record
2930 # if this admin is also owner, don't double the record
2921 if usr.user_id == owner_row[0].user_id:
2931 if usr.user_id == owner_row[0].user_id:
2922 owner_row[0].admin_row = True
2932 owner_row[0].admin_row = True
2923 else:
2933 else:
2924 usr = AttributeDict(usr.get_dict())
2934 usr = AttributeDict(usr.get_dict())
2925 usr.admin_row = True
2935 usr.admin_row = True
2926 usr.permission = _admin_perm
2936 usr.permission = _admin_perm
2927 super_admin_rows.append(usr)
2937 super_admin_rows.append(usr)
2928
2938
2929 q = UserRepoGroupToPerm.query().filter(UserRepoGroupToPerm.group == self)
2939 q = UserRepoGroupToPerm.query().filter(UserRepoGroupToPerm.group == self)
2930 q = q.options(joinedload(UserRepoGroupToPerm.group),
2940 q = q.options(joinedload(UserRepoGroupToPerm.group),
2931 joinedload(UserRepoGroupToPerm.user),
2941 joinedload(UserRepoGroupToPerm.user),
2932 joinedload(UserRepoGroupToPerm.permission),)
2942 joinedload(UserRepoGroupToPerm.permission),)
2933
2943
2934 # get owners and admins and permissions. We do a trick of re-writing
2944 # get owners and admins and permissions. We do a trick of re-writing
2935 # objects from sqlalchemy to named-tuples due to sqlalchemy session
2945 # objects from sqlalchemy to named-tuples due to sqlalchemy session
2936 # has a global reference and changing one object propagates to all
2946 # has a global reference and changing one object propagates to all
2937 # others. This means if admin is also an owner admin_row that change
2947 # others. This means if admin is also an owner admin_row that change
2938 # would propagate to both objects
2948 # would propagate to both objects
2939 perm_rows = []
2949 perm_rows = []
2940 for _usr in q.all():
2950 for _usr in q.all():
2941 usr = AttributeDict(_usr.user.get_dict())
2951 usr = AttributeDict(_usr.user.get_dict())
2942 # if this user is also owner/admin, mark as duplicate record
2952 # if this user is also owner/admin, mark as duplicate record
2943 if usr.user_id == owner_row[0].user_id or usr.user_id in super_admin_ids:
2953 if usr.user_id == owner_row[0].user_id or usr.user_id in super_admin_ids:
2944 usr.duplicate_perm = True
2954 usr.duplicate_perm = True
2945 usr.permission = _usr.permission.permission_name
2955 usr.permission = _usr.permission.permission_name
2946 perm_rows.append(usr)
2956 perm_rows.append(usr)
2947
2957
2948 # filter the perm rows by 'default' first and then sort them by
2958 # filter the perm rows by 'default' first and then sort them by
2949 # admin,write,read,none permissions sorted again alphabetically in
2959 # admin,write,read,none permissions sorted again alphabetically in
2950 # each group
2960 # each group
2951 perm_rows = sorted(perm_rows, key=display_user_sort)
2961 perm_rows = sorted(perm_rows, key=display_user_sort)
2952
2962
2953 user_groups_rows = []
2963 user_groups_rows = []
2954 if expand_from_user_groups:
2964 if expand_from_user_groups:
2955 for ug in self.permission_user_groups(with_members=True):
2965 for ug in self.permission_user_groups(with_members=True):
2956 for user_data in ug.members:
2966 for user_data in ug.members:
2957 user_groups_rows.append(user_data)
2967 user_groups_rows.append(user_data)
2958
2968
2959 return super_admin_rows + owner_row + perm_rows + user_groups_rows
2969 return super_admin_rows + owner_row + perm_rows + user_groups_rows
2960
2970
2961 def permission_user_groups(self, with_members=False):
2971 def permission_user_groups(self, with_members=False):
2962 q = UserGroupRepoGroupToPerm.query()\
2972 q = UserGroupRepoGroupToPerm.query()\
2963 .filter(UserGroupRepoGroupToPerm.group == self)
2973 .filter(UserGroupRepoGroupToPerm.group == self)
2964 q = q.options(joinedload(UserGroupRepoGroupToPerm.group),
2974 q = q.options(joinedload(UserGroupRepoGroupToPerm.group),
2965 joinedload(UserGroupRepoGroupToPerm.users_group),
2975 joinedload(UserGroupRepoGroupToPerm.users_group),
2966 joinedload(UserGroupRepoGroupToPerm.permission),)
2976 joinedload(UserGroupRepoGroupToPerm.permission),)
2967
2977
2968 perm_rows = []
2978 perm_rows = []
2969 for _user_group in q.all():
2979 for _user_group in q.all():
2970 entry = AttributeDict(_user_group.users_group.get_dict())
2980 entry = AttributeDict(_user_group.users_group.get_dict())
2971 entry.permission = _user_group.permission.permission_name
2981 entry.permission = _user_group.permission.permission_name
2972 if with_members:
2982 if with_members:
2973 entry.members = [x.user.get_dict()
2983 entry.members = [x.user.get_dict()
2974 for x in _user_group.users_group.members]
2984 for x in _user_group.users_group.members]
2975 perm_rows.append(entry)
2985 perm_rows.append(entry)
2976
2986
2977 perm_rows = sorted(perm_rows, key=display_user_group_sort)
2987 perm_rows = sorted(perm_rows, key=display_user_group_sort)
2978 return perm_rows
2988 return perm_rows
2979
2989
2980 def get_api_data(self):
2990 def get_api_data(self):
2981 """
2991 """
2982 Common function for generating api data
2992 Common function for generating api data
2983
2993
2984 """
2994 """
2985 group = self
2995 group = self
2986 data = {
2996 data = {
2987 'group_id': group.group_id,
2997 'group_id': group.group_id,
2988 'group_name': group.group_name,
2998 'group_name': group.group_name,
2989 'group_description': group.description_safe,
2999 'group_description': group.description_safe,
2990 'parent_group': group.parent_group.group_name if group.parent_group else None,
3000 'parent_group': group.parent_group.group_name if group.parent_group else None,
2991 'repositories': [x.repo_name for x in group.repositories],
3001 'repositories': [x.repo_name for x in group.repositories],
2992 'owner': group.user.username,
3002 'owner': group.user.username,
2993 }
3003 }
2994 return data
3004 return data
2995
3005
2996 def get_dict(self):
3006 def get_dict(self):
2997 # Since we transformed `group_name` to a hybrid property, we need to
3007 # Since we transformed `group_name` to a hybrid property, we need to
2998 # keep compatibility with the code which uses `group_name` field.
3008 # keep compatibility with the code which uses `group_name` field.
2999 result = super(RepoGroup, self).get_dict()
3009 result = super(RepoGroup, self).get_dict()
3000 result['group_name'] = result.pop('_group_name', None)
3010 result['group_name'] = result.pop('_group_name', None)
3001 return result
3011 return result
3002
3012
3003
3013
3004 class Permission(Base, BaseModel):
3014 class Permission(Base, BaseModel):
3005 __tablename__ = 'permissions'
3015 __tablename__ = 'permissions'
3006 __table_args__ = (
3016 __table_args__ = (
3007 Index('p_perm_name_idx', 'permission_name'),
3017 Index('p_perm_name_idx', 'permission_name'),
3008 base_table_args,
3018 base_table_args,
3009 )
3019 )
3010
3020
3011 PERMS = [
3021 PERMS = [
3012 ('hg.admin', _('RhodeCode Super Administrator')),
3022 ('hg.admin', _('RhodeCode Super Administrator')),
3013
3023
3014 ('repository.none', _('Repository no access')),
3024 ('repository.none', _('Repository no access')),
3015 ('repository.read', _('Repository read access')),
3025 ('repository.read', _('Repository read access')),
3016 ('repository.write', _('Repository write access')),
3026 ('repository.write', _('Repository write access')),
3017 ('repository.admin', _('Repository admin access')),
3027 ('repository.admin', _('Repository admin access')),
3018
3028
3019 ('group.none', _('Repository group no access')),
3029 ('group.none', _('Repository group no access')),
3020 ('group.read', _('Repository group read access')),
3030 ('group.read', _('Repository group read access')),
3021 ('group.write', _('Repository group write access')),
3031 ('group.write', _('Repository group write access')),
3022 ('group.admin', _('Repository group admin access')),
3032 ('group.admin', _('Repository group admin access')),
3023
3033
3024 ('usergroup.none', _('User group no access')),
3034 ('usergroup.none', _('User group no access')),
3025 ('usergroup.read', _('User group read access')),
3035 ('usergroup.read', _('User group read access')),
3026 ('usergroup.write', _('User group write access')),
3036 ('usergroup.write', _('User group write access')),
3027 ('usergroup.admin', _('User group admin access')),
3037 ('usergroup.admin', _('User group admin access')),
3028
3038
3029 ('branch.none', _('Branch no permissions')),
3039 ('branch.none', _('Branch no permissions')),
3030 ('branch.merge', _('Branch access by web merge')),
3040 ('branch.merge', _('Branch access by web merge')),
3031 ('branch.push', _('Branch access by push')),
3041 ('branch.push', _('Branch access by push')),
3032 ('branch.push_force', _('Branch access by push with force')),
3042 ('branch.push_force', _('Branch access by push with force')),
3033
3043
3034 ('hg.repogroup.create.false', _('Repository Group creation disabled')),
3044 ('hg.repogroup.create.false', _('Repository Group creation disabled')),
3035 ('hg.repogroup.create.true', _('Repository Group creation enabled')),
3045 ('hg.repogroup.create.true', _('Repository Group creation enabled')),
3036
3046
3037 ('hg.usergroup.create.false', _('User Group creation disabled')),
3047 ('hg.usergroup.create.false', _('User Group creation disabled')),
3038 ('hg.usergroup.create.true', _('User Group creation enabled')),
3048 ('hg.usergroup.create.true', _('User Group creation enabled')),
3039
3049
3040 ('hg.create.none', _('Repository creation disabled')),
3050 ('hg.create.none', _('Repository creation disabled')),
3041 ('hg.create.repository', _('Repository creation enabled')),
3051 ('hg.create.repository', _('Repository creation enabled')),
3042 ('hg.create.write_on_repogroup.true', _('Repository creation enabled with write permission to a repository group')),
3052 ('hg.create.write_on_repogroup.true', _('Repository creation enabled with write permission to a repository group')),
3043 ('hg.create.write_on_repogroup.false', _('Repository creation disabled with write permission to a repository group')),
3053 ('hg.create.write_on_repogroup.false', _('Repository creation disabled with write permission to a repository group')),
3044
3054
3045 ('hg.fork.none', _('Repository forking disabled')),
3055 ('hg.fork.none', _('Repository forking disabled')),
3046 ('hg.fork.repository', _('Repository forking enabled')),
3056 ('hg.fork.repository', _('Repository forking enabled')),
3047
3057
3048 ('hg.register.none', _('Registration disabled')),
3058 ('hg.register.none', _('Registration disabled')),
3049 ('hg.register.manual_activate', _('User Registration with manual account activation')),
3059 ('hg.register.manual_activate', _('User Registration with manual account activation')),
3050 ('hg.register.auto_activate', _('User Registration with automatic account activation')),
3060 ('hg.register.auto_activate', _('User Registration with automatic account activation')),
3051
3061
3052 ('hg.password_reset.enabled', _('Password reset enabled')),
3062 ('hg.password_reset.enabled', _('Password reset enabled')),
3053 ('hg.password_reset.hidden', _('Password reset hidden')),
3063 ('hg.password_reset.hidden', _('Password reset hidden')),
3054 ('hg.password_reset.disabled', _('Password reset disabled')),
3064 ('hg.password_reset.disabled', _('Password reset disabled')),
3055
3065
3056 ('hg.extern_activate.manual', _('Manual activation of external account')),
3066 ('hg.extern_activate.manual', _('Manual activation of external account')),
3057 ('hg.extern_activate.auto', _('Automatic activation of external account')),
3067 ('hg.extern_activate.auto', _('Automatic activation of external account')),
3058
3068
3059 ('hg.inherit_default_perms.false', _('Inherit object permissions from default user disabled')),
3069 ('hg.inherit_default_perms.false', _('Inherit object permissions from default user disabled')),
3060 ('hg.inherit_default_perms.true', _('Inherit object permissions from default user enabled')),
3070 ('hg.inherit_default_perms.true', _('Inherit object permissions from default user enabled')),
3061 ]
3071 ]
3062
3072
3063 # definition of system default permissions for DEFAULT user, created on
3073 # definition of system default permissions for DEFAULT user, created on
3064 # system setup
3074 # system setup
3065 DEFAULT_USER_PERMISSIONS = [
3075 DEFAULT_USER_PERMISSIONS = [
3066 # object perms
3076 # object perms
3067 'repository.read',
3077 'repository.read',
3068 'group.read',
3078 'group.read',
3069 'usergroup.read',
3079 'usergroup.read',
3070 # branch, for backward compat we need same value as before so forced pushed
3080 # branch, for backward compat we need same value as before so forced pushed
3071 'branch.push_force',
3081 'branch.push_force',
3072 # global
3082 # global
3073 'hg.create.repository',
3083 'hg.create.repository',
3074 'hg.repogroup.create.false',
3084 'hg.repogroup.create.false',
3075 'hg.usergroup.create.false',
3085 'hg.usergroup.create.false',
3076 'hg.create.write_on_repogroup.true',
3086 'hg.create.write_on_repogroup.true',
3077 'hg.fork.repository',
3087 'hg.fork.repository',
3078 'hg.register.manual_activate',
3088 'hg.register.manual_activate',
3079 'hg.password_reset.enabled',
3089 'hg.password_reset.enabled',
3080 'hg.extern_activate.auto',
3090 'hg.extern_activate.auto',
3081 'hg.inherit_default_perms.true',
3091 'hg.inherit_default_perms.true',
3082 ]
3092 ]
3083
3093
3084 # defines which permissions are more important higher the more important
3094 # defines which permissions are more important higher the more important
3085 # Weight defines which permissions are more important.
3095 # Weight defines which permissions are more important.
3086 # The higher number the more important.
3096 # The higher number the more important.
3087 PERM_WEIGHTS = {
3097 PERM_WEIGHTS = {
3088 'repository.none': 0,
3098 'repository.none': 0,
3089 'repository.read': 1,
3099 'repository.read': 1,
3090 'repository.write': 3,
3100 'repository.write': 3,
3091 'repository.admin': 4,
3101 'repository.admin': 4,
3092
3102
3093 'group.none': 0,
3103 'group.none': 0,
3094 'group.read': 1,
3104 'group.read': 1,
3095 'group.write': 3,
3105 'group.write': 3,
3096 'group.admin': 4,
3106 'group.admin': 4,
3097
3107
3098 'usergroup.none': 0,
3108 'usergroup.none': 0,
3099 'usergroup.read': 1,
3109 'usergroup.read': 1,
3100 'usergroup.write': 3,
3110 'usergroup.write': 3,
3101 'usergroup.admin': 4,
3111 'usergroup.admin': 4,
3102
3112
3103 'branch.none': 0,
3113 'branch.none': 0,
3104 'branch.merge': 1,
3114 'branch.merge': 1,
3105 'branch.push': 3,
3115 'branch.push': 3,
3106 'branch.push_force': 4,
3116 'branch.push_force': 4,
3107
3117
3108 'hg.repogroup.create.false': 0,
3118 'hg.repogroup.create.false': 0,
3109 'hg.repogroup.create.true': 1,
3119 'hg.repogroup.create.true': 1,
3110
3120
3111 'hg.usergroup.create.false': 0,
3121 'hg.usergroup.create.false': 0,
3112 'hg.usergroup.create.true': 1,
3122 'hg.usergroup.create.true': 1,
3113
3123
3114 'hg.fork.none': 0,
3124 'hg.fork.none': 0,
3115 'hg.fork.repository': 1,
3125 'hg.fork.repository': 1,
3116 'hg.create.none': 0,
3126 'hg.create.none': 0,
3117 'hg.create.repository': 1
3127 'hg.create.repository': 1
3118 }
3128 }
3119
3129
3120 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3130 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3121 permission_name = Column("permission_name", String(255), nullable=True, unique=None, default=None)
3131 permission_name = Column("permission_name", String(255), nullable=True, unique=None, default=None)
3122 permission_longname = Column("permission_longname", String(255), nullable=True, unique=None, default=None)
3132 permission_longname = Column("permission_longname", String(255), nullable=True, unique=None, default=None)
3123
3133
3124 def __unicode__(self):
3134 def __unicode__(self):
3125 return u"<%s('%s:%s')>" % (
3135 return u"<%s('%s:%s')>" % (
3126 self.__class__.__name__, self.permission_id, self.permission_name
3136 self.__class__.__name__, self.permission_id, self.permission_name
3127 )
3137 )
3128
3138
3129 @classmethod
3139 @classmethod
3130 def get_by_key(cls, key):
3140 def get_by_key(cls, key):
3131 return cls.query().filter(cls.permission_name == key).scalar()
3141 return cls.query().filter(cls.permission_name == key).scalar()
3132
3142
3133 @classmethod
3143 @classmethod
3134 def get_default_repo_perms(cls, user_id, repo_id=None):
3144 def get_default_repo_perms(cls, user_id, repo_id=None):
3135 q = Session().query(UserRepoToPerm, Repository, Permission)\
3145 q = Session().query(UserRepoToPerm, Repository, Permission)\
3136 .join((Permission, UserRepoToPerm.permission_id == Permission.permission_id))\
3146 .join((Permission, UserRepoToPerm.permission_id == Permission.permission_id))\
3137 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
3147 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
3138 .filter(UserRepoToPerm.user_id == user_id)
3148 .filter(UserRepoToPerm.user_id == user_id)
3139 if repo_id:
3149 if repo_id:
3140 q = q.filter(UserRepoToPerm.repository_id == repo_id)
3150 q = q.filter(UserRepoToPerm.repository_id == repo_id)
3141 return q.all()
3151 return q.all()
3142
3152
3143 @classmethod
3153 @classmethod
3144 def get_default_repo_branch_perms(cls, user_id, repo_id=None):
3154 def get_default_repo_branch_perms(cls, user_id, repo_id=None):
3145 q = Session().query(UserToRepoBranchPermission, UserRepoToPerm, Permission) \
3155 q = Session().query(UserToRepoBranchPermission, UserRepoToPerm, Permission) \
3146 .join(
3156 .join(
3147 Permission,
3157 Permission,
3148 UserToRepoBranchPermission.permission_id == Permission.permission_id) \
3158 UserToRepoBranchPermission.permission_id == Permission.permission_id) \
3149 .join(
3159 .join(
3150 UserRepoToPerm,
3160 UserRepoToPerm,
3151 UserToRepoBranchPermission.rule_to_perm_id == UserRepoToPerm.repo_to_perm_id) \
3161 UserToRepoBranchPermission.rule_to_perm_id == UserRepoToPerm.repo_to_perm_id) \
3152 .filter(UserRepoToPerm.user_id == user_id)
3162 .filter(UserRepoToPerm.user_id == user_id)
3153
3163
3154 if repo_id:
3164 if repo_id:
3155 q = q.filter(UserToRepoBranchPermission.repository_id == repo_id)
3165 q = q.filter(UserToRepoBranchPermission.repository_id == repo_id)
3156 return q.order_by(UserToRepoBranchPermission.rule_order).all()
3166 return q.order_by(UserToRepoBranchPermission.rule_order).all()
3157
3167
3158 @classmethod
3168 @classmethod
3159 def get_default_repo_perms_from_user_group(cls, user_id, repo_id=None):
3169 def get_default_repo_perms_from_user_group(cls, user_id, repo_id=None):
3160 q = Session().query(UserGroupRepoToPerm, Repository, Permission)\
3170 q = Session().query(UserGroupRepoToPerm, Repository, Permission)\
3161 .join(
3171 .join(
3162 Permission,
3172 Permission,
3163 UserGroupRepoToPerm.permission_id == Permission.permission_id)\
3173 UserGroupRepoToPerm.permission_id == Permission.permission_id)\
3164 .join(
3174 .join(
3165 Repository,
3175 Repository,
3166 UserGroupRepoToPerm.repository_id == Repository.repo_id)\
3176 UserGroupRepoToPerm.repository_id == Repository.repo_id)\
3167 .join(
3177 .join(
3168 UserGroup,
3178 UserGroup,
3169 UserGroupRepoToPerm.users_group_id ==
3179 UserGroupRepoToPerm.users_group_id ==
3170 UserGroup.users_group_id)\
3180 UserGroup.users_group_id)\
3171 .join(
3181 .join(
3172 UserGroupMember,
3182 UserGroupMember,
3173 UserGroupRepoToPerm.users_group_id ==
3183 UserGroupRepoToPerm.users_group_id ==
3174 UserGroupMember.users_group_id)\
3184 UserGroupMember.users_group_id)\
3175 .filter(
3185 .filter(
3176 UserGroupMember.user_id == user_id,
3186 UserGroupMember.user_id == user_id,
3177 UserGroup.users_group_active == true())
3187 UserGroup.users_group_active == true())
3178 if repo_id:
3188 if repo_id:
3179 q = q.filter(UserGroupRepoToPerm.repository_id == repo_id)
3189 q = q.filter(UserGroupRepoToPerm.repository_id == repo_id)
3180 return q.all()
3190 return q.all()
3181
3191
3182 @classmethod
3192 @classmethod
3183 def get_default_repo_branch_perms_from_user_group(cls, user_id, repo_id=None):
3193 def get_default_repo_branch_perms_from_user_group(cls, user_id, repo_id=None):
3184 q = Session().query(UserGroupToRepoBranchPermission, UserGroupRepoToPerm, Permission) \
3194 q = Session().query(UserGroupToRepoBranchPermission, UserGroupRepoToPerm, Permission) \
3185 .join(
3195 .join(
3186 Permission,
3196 Permission,
3187 UserGroupToRepoBranchPermission.permission_id == Permission.permission_id) \
3197 UserGroupToRepoBranchPermission.permission_id == Permission.permission_id) \
3188 .join(
3198 .join(
3189 UserGroupRepoToPerm,
3199 UserGroupRepoToPerm,
3190 UserGroupToRepoBranchPermission.rule_to_perm_id == UserGroupRepoToPerm.users_group_to_perm_id) \
3200 UserGroupToRepoBranchPermission.rule_to_perm_id == UserGroupRepoToPerm.users_group_to_perm_id) \
3191 .join(
3201 .join(
3192 UserGroup,
3202 UserGroup,
3193 UserGroupRepoToPerm.users_group_id == UserGroup.users_group_id) \
3203 UserGroupRepoToPerm.users_group_id == UserGroup.users_group_id) \
3194 .join(
3204 .join(
3195 UserGroupMember,
3205 UserGroupMember,
3196 UserGroupRepoToPerm.users_group_id == UserGroupMember.users_group_id) \
3206 UserGroupRepoToPerm.users_group_id == UserGroupMember.users_group_id) \
3197 .filter(
3207 .filter(
3198 UserGroupMember.user_id == user_id,
3208 UserGroupMember.user_id == user_id,
3199 UserGroup.users_group_active == true())
3209 UserGroup.users_group_active == true())
3200
3210
3201 if repo_id:
3211 if repo_id:
3202 q = q.filter(UserGroupToRepoBranchPermission.repository_id == repo_id)
3212 q = q.filter(UserGroupToRepoBranchPermission.repository_id == repo_id)
3203 return q.order_by(UserGroupToRepoBranchPermission.rule_order).all()
3213 return q.order_by(UserGroupToRepoBranchPermission.rule_order).all()
3204
3214
3205 @classmethod
3215 @classmethod
3206 def get_default_group_perms(cls, user_id, repo_group_id=None):
3216 def get_default_group_perms(cls, user_id, repo_group_id=None):
3207 q = Session().query(UserRepoGroupToPerm, RepoGroup, Permission)\
3217 q = Session().query(UserRepoGroupToPerm, RepoGroup, Permission)\
3208 .join(
3218 .join(
3209 Permission,
3219 Permission,
3210 UserRepoGroupToPerm.permission_id == Permission.permission_id)\
3220 UserRepoGroupToPerm.permission_id == Permission.permission_id)\
3211 .join(
3221 .join(
3212 RepoGroup,
3222 RepoGroup,
3213 UserRepoGroupToPerm.group_id == RepoGroup.group_id)\
3223 UserRepoGroupToPerm.group_id == RepoGroup.group_id)\
3214 .filter(UserRepoGroupToPerm.user_id == user_id)
3224 .filter(UserRepoGroupToPerm.user_id == user_id)
3215 if repo_group_id:
3225 if repo_group_id:
3216 q = q.filter(UserRepoGroupToPerm.group_id == repo_group_id)
3226 q = q.filter(UserRepoGroupToPerm.group_id == repo_group_id)
3217 return q.all()
3227 return q.all()
3218
3228
3219 @classmethod
3229 @classmethod
3220 def get_default_group_perms_from_user_group(
3230 def get_default_group_perms_from_user_group(
3221 cls, user_id, repo_group_id=None):
3231 cls, user_id, repo_group_id=None):
3222 q = Session().query(UserGroupRepoGroupToPerm, RepoGroup, Permission)\
3232 q = Session().query(UserGroupRepoGroupToPerm, RepoGroup, Permission)\
3223 .join(
3233 .join(
3224 Permission,
3234 Permission,
3225 UserGroupRepoGroupToPerm.permission_id ==
3235 UserGroupRepoGroupToPerm.permission_id ==
3226 Permission.permission_id)\
3236 Permission.permission_id)\
3227 .join(
3237 .join(
3228 RepoGroup,
3238 RepoGroup,
3229 UserGroupRepoGroupToPerm.group_id == RepoGroup.group_id)\
3239 UserGroupRepoGroupToPerm.group_id == RepoGroup.group_id)\
3230 .join(
3240 .join(
3231 UserGroup,
3241 UserGroup,
3232 UserGroupRepoGroupToPerm.users_group_id ==
3242 UserGroupRepoGroupToPerm.users_group_id ==
3233 UserGroup.users_group_id)\
3243 UserGroup.users_group_id)\
3234 .join(
3244 .join(
3235 UserGroupMember,
3245 UserGroupMember,
3236 UserGroupRepoGroupToPerm.users_group_id ==
3246 UserGroupRepoGroupToPerm.users_group_id ==
3237 UserGroupMember.users_group_id)\
3247 UserGroupMember.users_group_id)\
3238 .filter(
3248 .filter(
3239 UserGroupMember.user_id == user_id,
3249 UserGroupMember.user_id == user_id,
3240 UserGroup.users_group_active == true())
3250 UserGroup.users_group_active == true())
3241 if repo_group_id:
3251 if repo_group_id:
3242 q = q.filter(UserGroupRepoGroupToPerm.group_id == repo_group_id)
3252 q = q.filter(UserGroupRepoGroupToPerm.group_id == repo_group_id)
3243 return q.all()
3253 return q.all()
3244
3254
3245 @classmethod
3255 @classmethod
3246 def get_default_user_group_perms(cls, user_id, user_group_id=None):
3256 def get_default_user_group_perms(cls, user_id, user_group_id=None):
3247 q = Session().query(UserUserGroupToPerm, UserGroup, Permission)\
3257 q = Session().query(UserUserGroupToPerm, UserGroup, Permission)\
3248 .join((Permission, UserUserGroupToPerm.permission_id == Permission.permission_id))\
3258 .join((Permission, UserUserGroupToPerm.permission_id == Permission.permission_id))\
3249 .join((UserGroup, UserUserGroupToPerm.user_group_id == UserGroup.users_group_id))\
3259 .join((UserGroup, UserUserGroupToPerm.user_group_id == UserGroup.users_group_id))\
3250 .filter(UserUserGroupToPerm.user_id == user_id)
3260 .filter(UserUserGroupToPerm.user_id == user_id)
3251 if user_group_id:
3261 if user_group_id:
3252 q = q.filter(UserUserGroupToPerm.user_group_id == user_group_id)
3262 q = q.filter(UserUserGroupToPerm.user_group_id == user_group_id)
3253 return q.all()
3263 return q.all()
3254
3264
3255 @classmethod
3265 @classmethod
3256 def get_default_user_group_perms_from_user_group(
3266 def get_default_user_group_perms_from_user_group(
3257 cls, user_id, user_group_id=None):
3267 cls, user_id, user_group_id=None):
3258 TargetUserGroup = aliased(UserGroup, name='target_user_group')
3268 TargetUserGroup = aliased(UserGroup, name='target_user_group')
3259 q = Session().query(UserGroupUserGroupToPerm, UserGroup, Permission)\
3269 q = Session().query(UserGroupUserGroupToPerm, UserGroup, Permission)\
3260 .join(
3270 .join(
3261 Permission,
3271 Permission,
3262 UserGroupUserGroupToPerm.permission_id ==
3272 UserGroupUserGroupToPerm.permission_id ==
3263 Permission.permission_id)\
3273 Permission.permission_id)\
3264 .join(
3274 .join(
3265 TargetUserGroup,
3275 TargetUserGroup,
3266 UserGroupUserGroupToPerm.target_user_group_id ==
3276 UserGroupUserGroupToPerm.target_user_group_id ==
3267 TargetUserGroup.users_group_id)\
3277 TargetUserGroup.users_group_id)\
3268 .join(
3278 .join(
3269 UserGroup,
3279 UserGroup,
3270 UserGroupUserGroupToPerm.user_group_id ==
3280 UserGroupUserGroupToPerm.user_group_id ==
3271 UserGroup.users_group_id)\
3281 UserGroup.users_group_id)\
3272 .join(
3282 .join(
3273 UserGroupMember,
3283 UserGroupMember,
3274 UserGroupUserGroupToPerm.user_group_id ==
3284 UserGroupUserGroupToPerm.user_group_id ==
3275 UserGroupMember.users_group_id)\
3285 UserGroupMember.users_group_id)\
3276 .filter(
3286 .filter(
3277 UserGroupMember.user_id == user_id,
3287 UserGroupMember.user_id == user_id,
3278 UserGroup.users_group_active == true())
3288 UserGroup.users_group_active == true())
3279 if user_group_id:
3289 if user_group_id:
3280 q = q.filter(
3290 q = q.filter(
3281 UserGroupUserGroupToPerm.user_group_id == user_group_id)
3291 UserGroupUserGroupToPerm.user_group_id == user_group_id)
3282
3292
3283 return q.all()
3293 return q.all()
3284
3294
3285
3295
3286 class UserRepoToPerm(Base, BaseModel):
3296 class UserRepoToPerm(Base, BaseModel):
3287 __tablename__ = 'repo_to_perm'
3297 __tablename__ = 'repo_to_perm'
3288 __table_args__ = (
3298 __table_args__ = (
3289 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
3299 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
3290 base_table_args
3300 base_table_args
3291 )
3301 )
3292
3302
3293 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3303 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3294 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3304 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3295 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3305 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3296 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
3306 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
3297
3307
3298 user = relationship('User')
3308 user = relationship('User')
3299 repository = relationship('Repository')
3309 repository = relationship('Repository')
3300 permission = relationship('Permission')
3310 permission = relationship('Permission')
3301
3311
3302 branch_perm_entry = relationship('UserToRepoBranchPermission', cascade="all, delete-orphan", lazy='joined')
3312 branch_perm_entry = relationship('UserToRepoBranchPermission', cascade="all, delete-orphan", lazy='joined')
3303
3313
3304 @classmethod
3314 @classmethod
3305 def create(cls, user, repository, permission):
3315 def create(cls, user, repository, permission):
3306 n = cls()
3316 n = cls()
3307 n.user = user
3317 n.user = user
3308 n.repository = repository
3318 n.repository = repository
3309 n.permission = permission
3319 n.permission = permission
3310 Session().add(n)
3320 Session().add(n)
3311 return n
3321 return n
3312
3322
3313 def __unicode__(self):
3323 def __unicode__(self):
3314 return u'<%s => %s >' % (self.user, self.repository)
3324 return u'<%s => %s >' % (self.user, self.repository)
3315
3325
3316
3326
3317 class UserUserGroupToPerm(Base, BaseModel):
3327 class UserUserGroupToPerm(Base, BaseModel):
3318 __tablename__ = 'user_user_group_to_perm'
3328 __tablename__ = 'user_user_group_to_perm'
3319 __table_args__ = (
3329 __table_args__ = (
3320 UniqueConstraint('user_id', 'user_group_id', 'permission_id'),
3330 UniqueConstraint('user_id', 'user_group_id', 'permission_id'),
3321 base_table_args
3331 base_table_args
3322 )
3332 )
3323
3333
3324 user_user_group_to_perm_id = Column("user_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3334 user_user_group_to_perm_id = Column("user_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3325 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3335 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3326 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3336 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3327 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3337 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3328
3338
3329 user = relationship('User')
3339 user = relationship('User')
3330 user_group = relationship('UserGroup')
3340 user_group = relationship('UserGroup')
3331 permission = relationship('Permission')
3341 permission = relationship('Permission')
3332
3342
3333 @classmethod
3343 @classmethod
3334 def create(cls, user, user_group, permission):
3344 def create(cls, user, user_group, permission):
3335 n = cls()
3345 n = cls()
3336 n.user = user
3346 n.user = user
3337 n.user_group = user_group
3347 n.user_group = user_group
3338 n.permission = permission
3348 n.permission = permission
3339 Session().add(n)
3349 Session().add(n)
3340 return n
3350 return n
3341
3351
3342 def __unicode__(self):
3352 def __unicode__(self):
3343 return u'<%s => %s >' % (self.user, self.user_group)
3353 return u'<%s => %s >' % (self.user, self.user_group)
3344
3354
3345
3355
3346 class UserToPerm(Base, BaseModel):
3356 class UserToPerm(Base, BaseModel):
3347 __tablename__ = 'user_to_perm'
3357 __tablename__ = 'user_to_perm'
3348 __table_args__ = (
3358 __table_args__ = (
3349 UniqueConstraint('user_id', 'permission_id'),
3359 UniqueConstraint('user_id', 'permission_id'),
3350 base_table_args
3360 base_table_args
3351 )
3361 )
3352
3362
3353 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3363 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3354 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3364 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3355 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3365 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3356
3366
3357 user = relationship('User')
3367 user = relationship('User')
3358 permission = relationship('Permission', lazy='joined')
3368 permission = relationship('Permission', lazy='joined')
3359
3369
3360 def __unicode__(self):
3370 def __unicode__(self):
3361 return u'<%s => %s >' % (self.user, self.permission)
3371 return u'<%s => %s >' % (self.user, self.permission)
3362
3372
3363
3373
3364 class UserGroupRepoToPerm(Base, BaseModel):
3374 class UserGroupRepoToPerm(Base, BaseModel):
3365 __tablename__ = 'users_group_repo_to_perm'
3375 __tablename__ = 'users_group_repo_to_perm'
3366 __table_args__ = (
3376 __table_args__ = (
3367 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
3377 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
3368 base_table_args
3378 base_table_args
3369 )
3379 )
3370
3380
3371 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3381 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3372 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3382 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3373 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3383 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3374 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
3384 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
3375
3385
3376 users_group = relationship('UserGroup')
3386 users_group = relationship('UserGroup')
3377 permission = relationship('Permission')
3387 permission = relationship('Permission')
3378 repository = relationship('Repository')
3388 repository = relationship('Repository')
3379 user_group_branch_perms = relationship('UserGroupToRepoBranchPermission', cascade='all')
3389 user_group_branch_perms = relationship('UserGroupToRepoBranchPermission', cascade='all')
3380
3390
3381 @classmethod
3391 @classmethod
3382 def create(cls, users_group, repository, permission):
3392 def create(cls, users_group, repository, permission):
3383 n = cls()
3393 n = cls()
3384 n.users_group = users_group
3394 n.users_group = users_group
3385 n.repository = repository
3395 n.repository = repository
3386 n.permission = permission
3396 n.permission = permission
3387 Session().add(n)
3397 Session().add(n)
3388 return n
3398 return n
3389
3399
3390 def __unicode__(self):
3400 def __unicode__(self):
3391 return u'<UserGroupRepoToPerm:%s => %s >' % (self.users_group, self.repository)
3401 return u'<UserGroupRepoToPerm:%s => %s >' % (self.users_group, self.repository)
3392
3402
3393
3403
3394 class UserGroupUserGroupToPerm(Base, BaseModel):
3404 class UserGroupUserGroupToPerm(Base, BaseModel):
3395 __tablename__ = 'user_group_user_group_to_perm'
3405 __tablename__ = 'user_group_user_group_to_perm'
3396 __table_args__ = (
3406 __table_args__ = (
3397 UniqueConstraint('target_user_group_id', 'user_group_id', 'permission_id'),
3407 UniqueConstraint('target_user_group_id', 'user_group_id', 'permission_id'),
3398 CheckConstraint('target_user_group_id != user_group_id'),
3408 CheckConstraint('target_user_group_id != user_group_id'),
3399 base_table_args
3409 base_table_args
3400 )
3410 )
3401
3411
3402 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)
3412 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)
3403 target_user_group_id = Column("target_user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3413 target_user_group_id = Column("target_user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3404 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3414 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3405 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3415 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3406
3416
3407 target_user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id')
3417 target_user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id')
3408 user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.user_group_id==UserGroup.users_group_id')
3418 user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.user_group_id==UserGroup.users_group_id')
3409 permission = relationship('Permission')
3419 permission = relationship('Permission')
3410
3420
3411 @classmethod
3421 @classmethod
3412 def create(cls, target_user_group, user_group, permission):
3422 def create(cls, target_user_group, user_group, permission):
3413 n = cls()
3423 n = cls()
3414 n.target_user_group = target_user_group
3424 n.target_user_group = target_user_group
3415 n.user_group = user_group
3425 n.user_group = user_group
3416 n.permission = permission
3426 n.permission = permission
3417 Session().add(n)
3427 Session().add(n)
3418 return n
3428 return n
3419
3429
3420 def __unicode__(self):
3430 def __unicode__(self):
3421 return u'<UserGroupUserGroup:%s => %s >' % (self.target_user_group, self.user_group)
3431 return u'<UserGroupUserGroup:%s => %s >' % (self.target_user_group, self.user_group)
3422
3432
3423
3433
3424 class UserGroupToPerm(Base, BaseModel):
3434 class UserGroupToPerm(Base, BaseModel):
3425 __tablename__ = 'users_group_to_perm'
3435 __tablename__ = 'users_group_to_perm'
3426 __table_args__ = (
3436 __table_args__ = (
3427 UniqueConstraint('users_group_id', 'permission_id',),
3437 UniqueConstraint('users_group_id', 'permission_id',),
3428 base_table_args
3438 base_table_args
3429 )
3439 )
3430
3440
3431 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3441 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3432 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3442 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3433 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3443 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3434
3444
3435 users_group = relationship('UserGroup')
3445 users_group = relationship('UserGroup')
3436 permission = relationship('Permission')
3446 permission = relationship('Permission')
3437
3447
3438
3448
3439 class UserRepoGroupToPerm(Base, BaseModel):
3449 class UserRepoGroupToPerm(Base, BaseModel):
3440 __tablename__ = 'user_repo_group_to_perm'
3450 __tablename__ = 'user_repo_group_to_perm'
3441 __table_args__ = (
3451 __table_args__ = (
3442 UniqueConstraint('user_id', 'group_id', 'permission_id'),
3452 UniqueConstraint('user_id', 'group_id', 'permission_id'),
3443 base_table_args
3453 base_table_args
3444 )
3454 )
3445
3455
3446 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3456 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3447 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3457 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3448 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
3458 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
3449 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3459 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3450
3460
3451 user = relationship('User')
3461 user = relationship('User')
3452 group = relationship('RepoGroup')
3462 group = relationship('RepoGroup')
3453 permission = relationship('Permission')
3463 permission = relationship('Permission')
3454
3464
3455 @classmethod
3465 @classmethod
3456 def create(cls, user, repository_group, permission):
3466 def create(cls, user, repository_group, permission):
3457 n = cls()
3467 n = cls()
3458 n.user = user
3468 n.user = user
3459 n.group = repository_group
3469 n.group = repository_group
3460 n.permission = permission
3470 n.permission = permission
3461 Session().add(n)
3471 Session().add(n)
3462 return n
3472 return n
3463
3473
3464
3474
3465 class UserGroupRepoGroupToPerm(Base, BaseModel):
3475 class UserGroupRepoGroupToPerm(Base, BaseModel):
3466 __tablename__ = 'users_group_repo_group_to_perm'
3476 __tablename__ = 'users_group_repo_group_to_perm'
3467 __table_args__ = (
3477 __table_args__ = (
3468 UniqueConstraint('users_group_id', 'group_id'),
3478 UniqueConstraint('users_group_id', 'group_id'),
3469 base_table_args
3479 base_table_args
3470 )
3480 )
3471
3481
3472 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)
3482 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)
3473 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3483 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3474 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
3484 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
3475 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3485 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3476
3486
3477 users_group = relationship('UserGroup')
3487 users_group = relationship('UserGroup')
3478 permission = relationship('Permission')
3488 permission = relationship('Permission')
3479 group = relationship('RepoGroup')
3489 group = relationship('RepoGroup')
3480
3490
3481 @classmethod
3491 @classmethod
3482 def create(cls, user_group, repository_group, permission):
3492 def create(cls, user_group, repository_group, permission):
3483 n = cls()
3493 n = cls()
3484 n.users_group = user_group
3494 n.users_group = user_group
3485 n.group = repository_group
3495 n.group = repository_group
3486 n.permission = permission
3496 n.permission = permission
3487 Session().add(n)
3497 Session().add(n)
3488 return n
3498 return n
3489
3499
3490 def __unicode__(self):
3500 def __unicode__(self):
3491 return u'<UserGroupRepoGroupToPerm:%s => %s >' % (self.users_group, self.group)
3501 return u'<UserGroupRepoGroupToPerm:%s => %s >' % (self.users_group, self.group)
3492
3502
3493
3503
3494 class Statistics(Base, BaseModel):
3504 class Statistics(Base, BaseModel):
3495 __tablename__ = 'statistics'
3505 __tablename__ = 'statistics'
3496 __table_args__ = (
3506 __table_args__ = (
3497 base_table_args
3507 base_table_args
3498 )
3508 )
3499
3509
3500 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3510 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3501 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
3511 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
3502 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
3512 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
3503 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
3513 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
3504 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
3514 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
3505 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
3515 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
3506
3516
3507 repository = relationship('Repository', single_parent=True)
3517 repository = relationship('Repository', single_parent=True)
3508
3518
3509
3519
3510 class UserFollowing(Base, BaseModel):
3520 class UserFollowing(Base, BaseModel):
3511 __tablename__ = 'user_followings'
3521 __tablename__ = 'user_followings'
3512 __table_args__ = (
3522 __table_args__ = (
3513 UniqueConstraint('user_id', 'follows_repository_id'),
3523 UniqueConstraint('user_id', 'follows_repository_id'),
3514 UniqueConstraint('user_id', 'follows_user_id'),
3524 UniqueConstraint('user_id', 'follows_user_id'),
3515 base_table_args
3525 base_table_args
3516 )
3526 )
3517
3527
3518 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3528 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3519 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3529 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3520 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
3530 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
3521 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
3531 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
3522 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
3532 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
3523
3533
3524 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
3534 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
3525
3535
3526 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
3536 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
3527 follows_repository = relationship('Repository', order_by='Repository.repo_name')
3537 follows_repository = relationship('Repository', order_by='Repository.repo_name')
3528
3538
3529 @classmethod
3539 @classmethod
3530 def get_repo_followers(cls, repo_id):
3540 def get_repo_followers(cls, repo_id):
3531 return cls.query().filter(cls.follows_repo_id == repo_id)
3541 return cls.query().filter(cls.follows_repo_id == repo_id)
3532
3542
3533
3543
3534 class CacheKey(Base, BaseModel):
3544 class CacheKey(Base, BaseModel):
3535 __tablename__ = 'cache_invalidation'
3545 __tablename__ = 'cache_invalidation'
3536 __table_args__ = (
3546 __table_args__ = (
3537 UniqueConstraint('cache_key'),
3547 UniqueConstraint('cache_key'),
3538 Index('key_idx', 'cache_key'),
3548 Index('key_idx', 'cache_key'),
3539 base_table_args,
3549 base_table_args,
3540 )
3550 )
3541
3551
3542 CACHE_TYPE_FEED = 'FEED'
3552 CACHE_TYPE_FEED = 'FEED'
3543
3553
3544 # namespaces used to register process/thread aware caches
3554 # namespaces used to register process/thread aware caches
3545 REPO_INVALIDATION_NAMESPACE = 'repo_cache:{repo_id}'
3555 REPO_INVALIDATION_NAMESPACE = 'repo_cache:{repo_id}'
3546 SETTINGS_INVALIDATION_NAMESPACE = 'system_settings'
3556 SETTINGS_INVALIDATION_NAMESPACE = 'system_settings'
3547
3557
3548 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3558 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3549 cache_key = Column("cache_key", String(255), nullable=True, unique=None, default=None)
3559 cache_key = Column("cache_key", String(255), nullable=True, unique=None, default=None)
3550 cache_args = Column("cache_args", String(255), nullable=True, unique=None, default=None)
3560 cache_args = Column("cache_args", String(255), nullable=True, unique=None, default=None)
3551 cache_state_uid = Column("cache_state_uid", String(255), nullable=True, unique=None, default=None)
3561 cache_state_uid = Column("cache_state_uid", String(255), nullable=True, unique=None, default=None)
3552 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
3562 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
3553
3563
3554 def __init__(self, cache_key, cache_args='', cache_state_uid=None):
3564 def __init__(self, cache_key, cache_args='', cache_state_uid=None):
3555 self.cache_key = cache_key
3565 self.cache_key = cache_key
3556 self.cache_args = cache_args
3566 self.cache_args = cache_args
3557 self.cache_active = False
3567 self.cache_active = False
3558 # first key should be same for all entries, since all workers should share it
3568 # first key should be same for all entries, since all workers should share it
3559 self.cache_state_uid = cache_state_uid or self.generate_new_state_uid()
3569 self.cache_state_uid = cache_state_uid or self.generate_new_state_uid()
3560
3570
3561 def __unicode__(self):
3571 def __unicode__(self):
3562 return u"<%s('%s:%s[%s]')>" % (
3572 return u"<%s('%s:%s[%s]')>" % (
3563 self.__class__.__name__,
3573 self.__class__.__name__,
3564 self.cache_id, self.cache_key, self.cache_active)
3574 self.cache_id, self.cache_key, self.cache_active)
3565
3575
3566 def _cache_key_partition(self):
3576 def _cache_key_partition(self):
3567 prefix, repo_name, suffix = self.cache_key.partition(self.cache_args)
3577 prefix, repo_name, suffix = self.cache_key.partition(self.cache_args)
3568 return prefix, repo_name, suffix
3578 return prefix, repo_name, suffix
3569
3579
3570 def get_prefix(self):
3580 def get_prefix(self):
3571 """
3581 """
3572 Try to extract prefix from existing cache key. The key could consist
3582 Try to extract prefix from existing cache key. The key could consist
3573 of prefix, repo_name, suffix
3583 of prefix, repo_name, suffix
3574 """
3584 """
3575 # this returns prefix, repo_name, suffix
3585 # this returns prefix, repo_name, suffix
3576 return self._cache_key_partition()[0]
3586 return self._cache_key_partition()[0]
3577
3587
3578 def get_suffix(self):
3588 def get_suffix(self):
3579 """
3589 """
3580 get suffix that might have been used in _get_cache_key to
3590 get suffix that might have been used in _get_cache_key to
3581 generate self.cache_key. Only used for informational purposes
3591 generate self.cache_key. Only used for informational purposes
3582 in repo_edit.mako.
3592 in repo_edit.mako.
3583 """
3593 """
3584 # prefix, repo_name, suffix
3594 # prefix, repo_name, suffix
3585 return self._cache_key_partition()[2]
3595 return self._cache_key_partition()[2]
3586
3596
3587 @classmethod
3597 @classmethod
3588 def generate_new_state_uid(cls, based_on=None):
3598 def generate_new_state_uid(cls, based_on=None):
3589 if based_on:
3599 if based_on:
3590 return str(uuid.uuid5(uuid.NAMESPACE_URL, safe_str(based_on)))
3600 return str(uuid.uuid5(uuid.NAMESPACE_URL, safe_str(based_on)))
3591 else:
3601 else:
3592 return str(uuid.uuid4())
3602 return str(uuid.uuid4())
3593
3603
3594 @classmethod
3604 @classmethod
3595 def delete_all_cache(cls):
3605 def delete_all_cache(cls):
3596 """
3606 """
3597 Delete all cache keys from database.
3607 Delete all cache keys from database.
3598 Should only be run when all instances are down and all entries
3608 Should only be run when all instances are down and all entries
3599 thus stale.
3609 thus stale.
3600 """
3610 """
3601 cls.query().delete()
3611 cls.query().delete()
3602 Session().commit()
3612 Session().commit()
3603
3613
3604 @classmethod
3614 @classmethod
3605 def set_invalidate(cls, cache_uid, delete=False):
3615 def set_invalidate(cls, cache_uid, delete=False):
3606 """
3616 """
3607 Mark all caches of a repo as invalid in the database.
3617 Mark all caches of a repo as invalid in the database.
3608 """
3618 """
3609
3619
3610 try:
3620 try:
3611 qry = Session().query(cls).filter(cls.cache_args == cache_uid)
3621 qry = Session().query(cls).filter(cls.cache_args == cache_uid)
3612 if delete:
3622 if delete:
3613 qry.delete()
3623 qry.delete()
3614 log.debug('cache objects deleted for cache args %s',
3624 log.debug('cache objects deleted for cache args %s',
3615 safe_str(cache_uid))
3625 safe_str(cache_uid))
3616 else:
3626 else:
3617 qry.update({"cache_active": False,
3627 qry.update({"cache_active": False,
3618 "cache_state_uid": cls.generate_new_state_uid()})
3628 "cache_state_uid": cls.generate_new_state_uid()})
3619 log.debug('cache objects marked as invalid for cache args %s',
3629 log.debug('cache objects marked as invalid for cache args %s',
3620 safe_str(cache_uid))
3630 safe_str(cache_uid))
3621
3631
3622 Session().commit()
3632 Session().commit()
3623 except Exception:
3633 except Exception:
3624 log.exception(
3634 log.exception(
3625 'Cache key invalidation failed for cache args %s',
3635 'Cache key invalidation failed for cache args %s',
3626 safe_str(cache_uid))
3636 safe_str(cache_uid))
3627 Session().rollback()
3637 Session().rollback()
3628
3638
3629 @classmethod
3639 @classmethod
3630 def get_active_cache(cls, cache_key):
3640 def get_active_cache(cls, cache_key):
3631 inv_obj = cls.query().filter(cls.cache_key == cache_key).scalar()
3641 inv_obj = cls.query().filter(cls.cache_key == cache_key).scalar()
3632 if inv_obj:
3642 if inv_obj:
3633 return inv_obj
3643 return inv_obj
3634 return None
3644 return None
3635
3645
3636 @classmethod
3646 @classmethod
3637 def get_namespace_map(cls, namespace):
3647 def get_namespace_map(cls, namespace):
3638 return {
3648 return {
3639 x.cache_key: x
3649 x.cache_key: x
3640 for x in cls.query().filter(cls.cache_args == namespace)}
3650 for x in cls.query().filter(cls.cache_args == namespace)}
3641
3651
3642
3652
3643 class ChangesetComment(Base, BaseModel):
3653 class ChangesetComment(Base, BaseModel):
3644 __tablename__ = 'changeset_comments'
3654 __tablename__ = 'changeset_comments'
3645 __table_args__ = (
3655 __table_args__ = (
3646 Index('cc_revision_idx', 'revision'),
3656 Index('cc_revision_idx', 'revision'),
3647 base_table_args,
3657 base_table_args,
3648 )
3658 )
3649
3659
3650 COMMENT_OUTDATED = u'comment_outdated'
3660 COMMENT_OUTDATED = u'comment_outdated'
3651 COMMENT_TYPE_NOTE = u'note'
3661 COMMENT_TYPE_NOTE = u'note'
3652 COMMENT_TYPE_TODO = u'todo'
3662 COMMENT_TYPE_TODO = u'todo'
3653 COMMENT_TYPES = [COMMENT_TYPE_NOTE, COMMENT_TYPE_TODO]
3663 COMMENT_TYPES = [COMMENT_TYPE_NOTE, COMMENT_TYPE_TODO]
3654
3664
3655 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
3665 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
3656 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
3666 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
3657 revision = Column('revision', String(40), nullable=True)
3667 revision = Column('revision', String(40), nullable=True)
3658 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
3668 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
3659 pull_request_version_id = Column("pull_request_version_id", Integer(), ForeignKey('pull_request_versions.pull_request_version_id'), nullable=True)
3669 pull_request_version_id = Column("pull_request_version_id", Integer(), ForeignKey('pull_request_versions.pull_request_version_id'), nullable=True)
3660 line_no = Column('line_no', Unicode(10), nullable=True)
3670 line_no = Column('line_no', Unicode(10), nullable=True)
3661 hl_lines = Column('hl_lines', Unicode(512), nullable=True)
3671 hl_lines = Column('hl_lines', Unicode(512), nullable=True)
3662 f_path = Column('f_path', Unicode(1000), nullable=True)
3672 f_path = Column('f_path', Unicode(1000), nullable=True)
3663 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
3673 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
3664 text = Column('text', UnicodeText().with_variant(UnicodeText(25000), 'mysql'), nullable=False)
3674 text = Column('text', UnicodeText().with_variant(UnicodeText(25000), 'mysql'), nullable=False)
3665 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3675 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3666 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3676 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3667 renderer = Column('renderer', Unicode(64), nullable=True)
3677 renderer = Column('renderer', Unicode(64), nullable=True)
3668 display_state = Column('display_state', Unicode(128), nullable=True)
3678 display_state = Column('display_state', Unicode(128), nullable=True)
3669
3679
3670 comment_type = Column('comment_type', Unicode(128), nullable=True, default=COMMENT_TYPE_NOTE)
3680 comment_type = Column('comment_type', Unicode(128), nullable=True, default=COMMENT_TYPE_NOTE)
3671 resolved_comment_id = Column('resolved_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'), nullable=True)
3681 resolved_comment_id = Column('resolved_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'), nullable=True)
3672
3682
3673 resolved_comment = relationship('ChangesetComment', remote_side=comment_id, back_populates='resolved_by')
3683 resolved_comment = relationship('ChangesetComment', remote_side=comment_id, back_populates='resolved_by')
3674 resolved_by = relationship('ChangesetComment', back_populates='resolved_comment')
3684 resolved_by = relationship('ChangesetComment', back_populates='resolved_comment')
3675
3685
3676 author = relationship('User', lazy='joined')
3686 author = relationship('User', lazy='joined')
3677 repo = relationship('Repository')
3687 repo = relationship('Repository')
3678 status_change = relationship('ChangesetStatus', cascade="all, delete-orphan", lazy='joined')
3688 status_change = relationship('ChangesetStatus', cascade="all, delete-orphan", lazy='joined')
3679 pull_request = relationship('PullRequest', lazy='joined')
3689 pull_request = relationship('PullRequest', lazy='joined')
3680 pull_request_version = relationship('PullRequestVersion')
3690 pull_request_version = relationship('PullRequestVersion')
3681
3691
3682 @classmethod
3692 @classmethod
3683 def get_users(cls, revision=None, pull_request_id=None):
3693 def get_users(cls, revision=None, pull_request_id=None):
3684 """
3694 """
3685 Returns user associated with this ChangesetComment. ie those
3695 Returns user associated with this ChangesetComment. ie those
3686 who actually commented
3696 who actually commented
3687
3697
3688 :param cls:
3698 :param cls:
3689 :param revision:
3699 :param revision:
3690 """
3700 """
3691 q = Session().query(User)\
3701 q = Session().query(User)\
3692 .join(ChangesetComment.author)
3702 .join(ChangesetComment.author)
3693 if revision:
3703 if revision:
3694 q = q.filter(cls.revision == revision)
3704 q = q.filter(cls.revision == revision)
3695 elif pull_request_id:
3705 elif pull_request_id:
3696 q = q.filter(cls.pull_request_id == pull_request_id)
3706 q = q.filter(cls.pull_request_id == pull_request_id)
3697 return q.all()
3707 return q.all()
3698
3708
3699 @classmethod
3709 @classmethod
3700 def get_index_from_version(cls, pr_version, versions):
3710 def get_index_from_version(cls, pr_version, versions):
3701 num_versions = [x.pull_request_version_id for x in versions]
3711 num_versions = [x.pull_request_version_id for x in versions]
3702 try:
3712 try:
3703 return num_versions.index(pr_version) +1
3713 return num_versions.index(pr_version) +1
3704 except (IndexError, ValueError):
3714 except (IndexError, ValueError):
3705 return
3715 return
3706
3716
3707 @property
3717 @property
3708 def outdated(self):
3718 def outdated(self):
3709 return self.display_state == self.COMMENT_OUTDATED
3719 return self.display_state == self.COMMENT_OUTDATED
3710
3720
3711 def outdated_at_version(self, version):
3721 def outdated_at_version(self, version):
3712 """
3722 """
3713 Checks if comment is outdated for given pull request version
3723 Checks if comment is outdated for given pull request version
3714 """
3724 """
3715 return self.outdated and self.pull_request_version_id != version
3725 return self.outdated and self.pull_request_version_id != version
3716
3726
3717 def older_than_version(self, version):
3727 def older_than_version(self, version):
3718 """
3728 """
3719 Checks if comment is made from previous version than given
3729 Checks if comment is made from previous version than given
3720 """
3730 """
3721 if version is None:
3731 if version is None:
3722 return self.pull_request_version_id is not None
3732 return self.pull_request_version_id is not None
3723
3733
3724 return self.pull_request_version_id < version
3734 return self.pull_request_version_id < version
3725
3735
3726 @property
3736 @property
3727 def resolved(self):
3737 def resolved(self):
3728 return self.resolved_by[0] if self.resolved_by else None
3738 return self.resolved_by[0] if self.resolved_by else None
3729
3739
3730 @property
3740 @property
3731 def is_todo(self):
3741 def is_todo(self):
3732 return self.comment_type == self.COMMENT_TYPE_TODO
3742 return self.comment_type == self.COMMENT_TYPE_TODO
3733
3743
3734 @property
3744 @property
3735 def is_inline(self):
3745 def is_inline(self):
3736 return self.line_no and self.f_path
3746 return self.line_no and self.f_path
3737
3747
3738 def get_index_version(self, versions):
3748 def get_index_version(self, versions):
3739 return self.get_index_from_version(
3749 return self.get_index_from_version(
3740 self.pull_request_version_id, versions)
3750 self.pull_request_version_id, versions)
3741
3751
3742 def __repr__(self):
3752 def __repr__(self):
3743 if self.comment_id:
3753 if self.comment_id:
3744 return '<DB:Comment #%s>' % self.comment_id
3754 return '<DB:Comment #%s>' % self.comment_id
3745 else:
3755 else:
3746 return '<DB:Comment at %#x>' % id(self)
3756 return '<DB:Comment at %#x>' % id(self)
3747
3757
3748 def get_api_data(self):
3758 def get_api_data(self):
3749 comment = self
3759 comment = self
3750 data = {
3760 data = {
3751 'comment_id': comment.comment_id,
3761 'comment_id': comment.comment_id,
3752 'comment_type': comment.comment_type,
3762 'comment_type': comment.comment_type,
3753 'comment_text': comment.text,
3763 'comment_text': comment.text,
3754 'comment_status': comment.status_change,
3764 'comment_status': comment.status_change,
3755 'comment_f_path': comment.f_path,
3765 'comment_f_path': comment.f_path,
3756 'comment_lineno': comment.line_no,
3766 'comment_lineno': comment.line_no,
3757 'comment_author': comment.author,
3767 'comment_author': comment.author,
3758 'comment_created_on': comment.created_on,
3768 'comment_created_on': comment.created_on,
3759 'comment_resolved_by': self.resolved
3769 'comment_resolved_by': self.resolved
3760 }
3770 }
3761 return data
3771 return data
3762
3772
3763 def __json__(self):
3773 def __json__(self):
3764 data = dict()
3774 data = dict()
3765 data.update(self.get_api_data())
3775 data.update(self.get_api_data())
3766 return data
3776 return data
3767
3777
3768
3778
3769 class ChangesetStatus(Base, BaseModel):
3779 class ChangesetStatus(Base, BaseModel):
3770 __tablename__ = 'changeset_statuses'
3780 __tablename__ = 'changeset_statuses'
3771 __table_args__ = (
3781 __table_args__ = (
3772 Index('cs_revision_idx', 'revision'),
3782 Index('cs_revision_idx', 'revision'),
3773 Index('cs_version_idx', 'version'),
3783 Index('cs_version_idx', 'version'),
3774 UniqueConstraint('repo_id', 'revision', 'version'),
3784 UniqueConstraint('repo_id', 'revision', 'version'),
3775 base_table_args
3785 base_table_args
3776 )
3786 )
3777
3787
3778 STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed'
3788 STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed'
3779 STATUS_APPROVED = 'approved'
3789 STATUS_APPROVED = 'approved'
3780 STATUS_REJECTED = 'rejected'
3790 STATUS_REJECTED = 'rejected'
3781 STATUS_UNDER_REVIEW = 'under_review'
3791 STATUS_UNDER_REVIEW = 'under_review'
3782
3792
3783 STATUSES = [
3793 STATUSES = [
3784 (STATUS_NOT_REVIEWED, _("Not Reviewed")), # (no icon) and default
3794 (STATUS_NOT_REVIEWED, _("Not Reviewed")), # (no icon) and default
3785 (STATUS_APPROVED, _("Approved")),
3795 (STATUS_APPROVED, _("Approved")),
3786 (STATUS_REJECTED, _("Rejected")),
3796 (STATUS_REJECTED, _("Rejected")),
3787 (STATUS_UNDER_REVIEW, _("Under Review")),
3797 (STATUS_UNDER_REVIEW, _("Under Review")),
3788 ]
3798 ]
3789
3799
3790 changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True)
3800 changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True)
3791 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
3801 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
3792 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
3802 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
3793 revision = Column('revision', String(40), nullable=False)
3803 revision = Column('revision', String(40), nullable=False)
3794 status = Column('status', String(128), nullable=False, default=DEFAULT)
3804 status = Column('status', String(128), nullable=False, default=DEFAULT)
3795 changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'))
3805 changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'))
3796 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
3806 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
3797 version = Column('version', Integer(), nullable=False, default=0)
3807 version = Column('version', Integer(), nullable=False, default=0)
3798 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
3808 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
3799
3809
3800 author = relationship('User', lazy='joined')
3810 author = relationship('User', lazy='joined')
3801 repo = relationship('Repository')
3811 repo = relationship('Repository')
3802 comment = relationship('ChangesetComment', lazy='joined')
3812 comment = relationship('ChangesetComment', lazy='joined')
3803 pull_request = relationship('PullRequest', lazy='joined')
3813 pull_request = relationship('PullRequest', lazy='joined')
3804
3814
3805 def __unicode__(self):
3815 def __unicode__(self):
3806 return u"<%s('%s[v%s]:%s')>" % (
3816 return u"<%s('%s[v%s]:%s')>" % (
3807 self.__class__.__name__,
3817 self.__class__.__name__,
3808 self.status, self.version, self.author
3818 self.status, self.version, self.author
3809 )
3819 )
3810
3820
3811 @classmethod
3821 @classmethod
3812 def get_status_lbl(cls, value):
3822 def get_status_lbl(cls, value):
3813 return dict(cls.STATUSES).get(value)
3823 return dict(cls.STATUSES).get(value)
3814
3824
3815 @property
3825 @property
3816 def status_lbl(self):
3826 def status_lbl(self):
3817 return ChangesetStatus.get_status_lbl(self.status)
3827 return ChangesetStatus.get_status_lbl(self.status)
3818
3828
3819 def get_api_data(self):
3829 def get_api_data(self):
3820 status = self
3830 status = self
3821 data = {
3831 data = {
3822 'status_id': status.changeset_status_id,
3832 'status_id': status.changeset_status_id,
3823 'status': status.status,
3833 'status': status.status,
3824 }
3834 }
3825 return data
3835 return data
3826
3836
3827 def __json__(self):
3837 def __json__(self):
3828 data = dict()
3838 data = dict()
3829 data.update(self.get_api_data())
3839 data.update(self.get_api_data())
3830 return data
3840 return data
3831
3841
3832
3842
3833 class _SetState(object):
3843 class _SetState(object):
3834 """
3844 """
3835 Context processor allowing changing state for sensitive operation such as
3845 Context processor allowing changing state for sensitive operation such as
3836 pull request update or merge
3846 pull request update or merge
3837 """
3847 """
3838
3848
3839 def __init__(self, pull_request, pr_state, back_state=None):
3849 def __init__(self, pull_request, pr_state, back_state=None):
3840 self._pr = pull_request
3850 self._pr = pull_request
3841 self._org_state = back_state or pull_request.pull_request_state
3851 self._org_state = back_state or pull_request.pull_request_state
3842 self._pr_state = pr_state
3852 self._pr_state = pr_state
3843 self._current_state = None
3853 self._current_state = None
3844
3854
3845 def __enter__(self):
3855 def __enter__(self):
3846 log.debug('StateLock: entering set state context, setting state to: `%s`',
3856 log.debug('StateLock: entering set state context, setting state to: `%s`',
3847 self._pr_state)
3857 self._pr_state)
3848 self.set_pr_state(self._pr_state)
3858 self.set_pr_state(self._pr_state)
3849 return self
3859 return self
3850
3860
3851 def __exit__(self, exc_type, exc_val, exc_tb):
3861 def __exit__(self, exc_type, exc_val, exc_tb):
3852 if exc_val is not None:
3862 if exc_val is not None:
3853 log.error(traceback.format_exc(exc_tb))
3863 log.error(traceback.format_exc(exc_tb))
3854 return None
3864 return None
3855
3865
3856 self.set_pr_state(self._org_state)
3866 self.set_pr_state(self._org_state)
3857 log.debug('StateLock: exiting set state context, setting state to: `%s`',
3867 log.debug('StateLock: exiting set state context, setting state to: `%s`',
3858 self._org_state)
3868 self._org_state)
3859 @property
3869 @property
3860 def state(self):
3870 def state(self):
3861 return self._current_state
3871 return self._current_state
3862
3872
3863 def set_pr_state(self, pr_state):
3873 def set_pr_state(self, pr_state):
3864 try:
3874 try:
3865 self._pr.pull_request_state = pr_state
3875 self._pr.pull_request_state = pr_state
3866 Session().add(self._pr)
3876 Session().add(self._pr)
3867 Session().commit()
3877 Session().commit()
3868 self._current_state = pr_state
3878 self._current_state = pr_state
3869 except Exception:
3879 except Exception:
3870 log.exception('Failed to set PullRequest %s state to %s', self._pr, pr_state)
3880 log.exception('Failed to set PullRequest %s state to %s', self._pr, pr_state)
3871 raise
3881 raise
3872
3882
3883
3873 class _PullRequestBase(BaseModel):
3884 class _PullRequestBase(BaseModel):
3874 """
3885 """
3875 Common attributes of pull request and version entries.
3886 Common attributes of pull request and version entries.
3876 """
3887 """
3877
3888
3878 # .status values
3889 # .status values
3879 STATUS_NEW = u'new'
3890 STATUS_NEW = u'new'
3880 STATUS_OPEN = u'open'
3891 STATUS_OPEN = u'open'
3881 STATUS_CLOSED = u'closed'
3892 STATUS_CLOSED = u'closed'
3882
3893
3883 # available states
3894 # available states
3884 STATE_CREATING = u'creating'
3895 STATE_CREATING = u'creating'
3885 STATE_UPDATING = u'updating'
3896 STATE_UPDATING = u'updating'
3886 STATE_MERGING = u'merging'
3897 STATE_MERGING = u'merging'
3887 STATE_CREATED = u'created'
3898 STATE_CREATED = u'created'
3888
3899
3889 title = Column('title', Unicode(255), nullable=True)
3900 title = Column('title', Unicode(255), nullable=True)
3890 description = Column(
3901 description = Column(
3891 'description', UnicodeText().with_variant(UnicodeText(10240), 'mysql'),
3902 'description', UnicodeText().with_variant(UnicodeText(10240), 'mysql'),
3892 nullable=True)
3903 nullable=True)
3893 description_renderer = Column('description_renderer', Unicode(64), nullable=True)
3904 description_renderer = Column('description_renderer', Unicode(64), nullable=True)
3894
3905
3895 # new/open/closed status of pull request (not approve/reject/etc)
3906 # new/open/closed status of pull request (not approve/reject/etc)
3896 status = Column('status', Unicode(255), nullable=False, default=STATUS_NEW)
3907 status = Column('status', Unicode(255), nullable=False, default=STATUS_NEW)
3897 created_on = Column(
3908 created_on = Column(
3898 'created_on', DateTime(timezone=False), nullable=False,
3909 'created_on', DateTime(timezone=False), nullable=False,
3899 default=datetime.datetime.now)
3910 default=datetime.datetime.now)
3900 updated_on = Column(
3911 updated_on = Column(
3901 'updated_on', DateTime(timezone=False), nullable=False,
3912 'updated_on', DateTime(timezone=False), nullable=False,
3902 default=datetime.datetime.now)
3913 default=datetime.datetime.now)
3903
3914
3904 pull_request_state = Column("pull_request_state", String(255), nullable=True)
3915 pull_request_state = Column("pull_request_state", String(255), nullable=True)
3905
3916
3906 @declared_attr
3917 @declared_attr
3907 def user_id(cls):
3918 def user_id(cls):
3908 return Column(
3919 return Column(
3909 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
3920 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
3910 unique=None)
3921 unique=None)
3911
3922
3912 # 500 revisions max
3923 # 500 revisions max
3913 _revisions = Column(
3924 _revisions = Column(
3914 'revisions', UnicodeText().with_variant(UnicodeText(20500), 'mysql'))
3925 'revisions', UnicodeText().with_variant(UnicodeText(20500), 'mysql'))
3915
3926
3916 @declared_attr
3927 @declared_attr
3917 def source_repo_id(cls):
3928 def source_repo_id(cls):
3918 # TODO: dan: rename column to source_repo_id
3929 # TODO: dan: rename column to source_repo_id
3919 return Column(
3930 return Column(
3920 'org_repo_id', Integer(), ForeignKey('repositories.repo_id'),
3931 'org_repo_id', Integer(), ForeignKey('repositories.repo_id'),
3921 nullable=False)
3932 nullable=False)
3922
3933
3923 _source_ref = Column('org_ref', Unicode(255), nullable=False)
3934 _source_ref = Column('org_ref', Unicode(255), nullable=False)
3924
3935
3925 @hybrid_property
3936 @hybrid_property
3926 def source_ref(self):
3937 def source_ref(self):
3927 return self._source_ref
3938 return self._source_ref
3928
3939
3929 @source_ref.setter
3940 @source_ref.setter
3930 def source_ref(self, val):
3941 def source_ref(self, val):
3931 parts = (val or '').split(':')
3942 parts = (val or '').split(':')
3932 if len(parts) != 3:
3943 if len(parts) != 3:
3933 raise ValueError(
3944 raise ValueError(
3934 'Invalid reference format given: {}, expected X:Y:Z'.format(val))
3945 'Invalid reference format given: {}, expected X:Y:Z'.format(val))
3935 self._source_ref = safe_unicode(val)
3946 self._source_ref = safe_unicode(val)
3936
3947
3937 _target_ref = Column('other_ref', Unicode(255), nullable=False)
3948 _target_ref = Column('other_ref', Unicode(255), nullable=False)
3938
3949
3939 @hybrid_property
3950 @hybrid_property
3940 def target_ref(self):
3951 def target_ref(self):
3941 return self._target_ref
3952 return self._target_ref
3942
3953
3943 @target_ref.setter
3954 @target_ref.setter
3944 def target_ref(self, val):
3955 def target_ref(self, val):
3945 parts = (val or '').split(':')
3956 parts = (val or '').split(':')
3946 if len(parts) != 3:
3957 if len(parts) != 3:
3947 raise ValueError(
3958 raise ValueError(
3948 'Invalid reference format given: {}, expected X:Y:Z'.format(val))
3959 'Invalid reference format given: {}, expected X:Y:Z'.format(val))
3949 self._target_ref = safe_unicode(val)
3960 self._target_ref = safe_unicode(val)
3950
3961
3951 @declared_attr
3962 @declared_attr
3952 def target_repo_id(cls):
3963 def target_repo_id(cls):
3953 # TODO: dan: rename column to target_repo_id
3964 # TODO: dan: rename column to target_repo_id
3954 return Column(
3965 return Column(
3955 'other_repo_id', Integer(), ForeignKey('repositories.repo_id'),
3966 'other_repo_id', Integer(), ForeignKey('repositories.repo_id'),
3956 nullable=False)
3967 nullable=False)
3957
3968
3958 _shadow_merge_ref = Column('shadow_merge_ref', Unicode(255), nullable=True)
3969 _shadow_merge_ref = Column('shadow_merge_ref', Unicode(255), nullable=True)
3959
3970
3960 # TODO: dan: rename column to last_merge_source_rev
3971 # TODO: dan: rename column to last_merge_source_rev
3961 _last_merge_source_rev = Column(
3972 _last_merge_source_rev = Column(
3962 'last_merge_org_rev', String(40), nullable=True)
3973 'last_merge_org_rev', String(40), nullable=True)
3963 # TODO: dan: rename column to last_merge_target_rev
3974 # TODO: dan: rename column to last_merge_target_rev
3964 _last_merge_target_rev = Column(
3975 _last_merge_target_rev = Column(
3965 'last_merge_other_rev', String(40), nullable=True)
3976 'last_merge_other_rev', String(40), nullable=True)
3966 _last_merge_status = Column('merge_status', Integer(), nullable=True)
3977 _last_merge_status = Column('merge_status', Integer(), nullable=True)
3967 merge_rev = Column('merge_rev', String(40), nullable=True)
3978 merge_rev = Column('merge_rev', String(40), nullable=True)
3968
3979
3969 reviewer_data = Column(
3980 reviewer_data = Column(
3970 'reviewer_data_json', MutationObj.as_mutable(
3981 'reviewer_data_json', MutationObj.as_mutable(
3971 JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
3982 JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
3972
3983
3973 @property
3984 @property
3974 def reviewer_data_json(self):
3985 def reviewer_data_json(self):
3975 return json.dumps(self.reviewer_data)
3986 return json.dumps(self.reviewer_data)
3976
3987
3977 @hybrid_property
3988 @hybrid_property
3978 def description_safe(self):
3989 def description_safe(self):
3979 from rhodecode.lib import helpers as h
3990 from rhodecode.lib import helpers as h
3980 return h.escape(self.description)
3991 return h.escape(self.description)
3981
3992
3982 @hybrid_property
3993 @hybrid_property
3983 def revisions(self):
3994 def revisions(self):
3984 return self._revisions.split(':') if self._revisions else []
3995 return self._revisions.split(':') if self._revisions else []
3985
3996
3986 @revisions.setter
3997 @revisions.setter
3987 def revisions(self, val):
3998 def revisions(self, val):
3988 self._revisions = u':'.join(val)
3999 self._revisions = u':'.join(val)
3989
4000
3990 @hybrid_property
4001 @hybrid_property
3991 def last_merge_status(self):
4002 def last_merge_status(self):
3992 return safe_int(self._last_merge_status)
4003 return safe_int(self._last_merge_status)
3993
4004
3994 @last_merge_status.setter
4005 @last_merge_status.setter
3995 def last_merge_status(self, val):
4006 def last_merge_status(self, val):
3996 self._last_merge_status = val
4007 self._last_merge_status = val
3997
4008
3998 @declared_attr
4009 @declared_attr
3999 def author(cls):
4010 def author(cls):
4000 return relationship('User', lazy='joined')
4011 return relationship('User', lazy='joined')
4001
4012
4002 @declared_attr
4013 @declared_attr
4003 def source_repo(cls):
4014 def source_repo(cls):
4004 return relationship(
4015 return relationship(
4005 'Repository',
4016 'Repository',
4006 primaryjoin='%s.source_repo_id==Repository.repo_id' % cls.__name__)
4017 primaryjoin='%s.source_repo_id==Repository.repo_id' % cls.__name__)
4007
4018
4008 @property
4019 @property
4009 def source_ref_parts(self):
4020 def source_ref_parts(self):
4010 return self.unicode_to_reference(self.source_ref)
4021 return self.unicode_to_reference(self.source_ref)
4011
4022
4012 @declared_attr
4023 @declared_attr
4013 def target_repo(cls):
4024 def target_repo(cls):
4014 return relationship(
4025 return relationship(
4015 'Repository',
4026 'Repository',
4016 primaryjoin='%s.target_repo_id==Repository.repo_id' % cls.__name__)
4027 primaryjoin='%s.target_repo_id==Repository.repo_id' % cls.__name__)
4017
4028
4018 @property
4029 @property
4019 def target_ref_parts(self):
4030 def target_ref_parts(self):
4020 return self.unicode_to_reference(self.target_ref)
4031 return self.unicode_to_reference(self.target_ref)
4021
4032
4022 @property
4033 @property
4023 def shadow_merge_ref(self):
4034 def shadow_merge_ref(self):
4024 return self.unicode_to_reference(self._shadow_merge_ref)
4035 return self.unicode_to_reference(self._shadow_merge_ref)
4025
4036
4026 @shadow_merge_ref.setter
4037 @shadow_merge_ref.setter
4027 def shadow_merge_ref(self, ref):
4038 def shadow_merge_ref(self, ref):
4028 self._shadow_merge_ref = self.reference_to_unicode(ref)
4039 self._shadow_merge_ref = self.reference_to_unicode(ref)
4029
4040
4030 @staticmethod
4041 @staticmethod
4031 def unicode_to_reference(raw):
4042 def unicode_to_reference(raw):
4032 """
4043 """
4033 Convert a unicode (or string) to a reference object.
4044 Convert a unicode (or string) to a reference object.
4034 If unicode evaluates to False it returns None.
4045 If unicode evaluates to False it returns None.
4035 """
4046 """
4036 if raw:
4047 if raw:
4037 refs = raw.split(':')
4048 refs = raw.split(':')
4038 return Reference(*refs)
4049 return Reference(*refs)
4039 else:
4050 else:
4040 return None
4051 return None
4041
4052
4042 @staticmethod
4053 @staticmethod
4043 def reference_to_unicode(ref):
4054 def reference_to_unicode(ref):
4044 """
4055 """
4045 Convert a reference object to unicode.
4056 Convert a reference object to unicode.
4046 If reference is None it returns None.
4057 If reference is None it returns None.
4047 """
4058 """
4048 if ref:
4059 if ref:
4049 return u':'.join(ref)
4060 return u':'.join(ref)
4050 else:
4061 else:
4051 return None
4062 return None
4052
4063
4053 def get_api_data(self, with_merge_state=True):
4064 def get_api_data(self, with_merge_state=True):
4054 from rhodecode.model.pull_request import PullRequestModel
4065 from rhodecode.model.pull_request import PullRequestModel
4055
4066
4056 pull_request = self
4067 pull_request = self
4057 if with_merge_state:
4068 if with_merge_state:
4058 merge_status = PullRequestModel().merge_status(pull_request)
4069 merge_status = PullRequestModel().merge_status(pull_request)
4059 merge_state = {
4070 merge_state = {
4060 'status': merge_status[0],
4071 'status': merge_status[0],
4061 'message': safe_unicode(merge_status[1]),
4072 'message': safe_unicode(merge_status[1]),
4062 }
4073 }
4063 else:
4074 else:
4064 merge_state = {'status': 'not_available',
4075 merge_state = {'status': 'not_available',
4065 'message': 'not_available'}
4076 'message': 'not_available'}
4066
4077
4067 merge_data = {
4078 merge_data = {
4068 'clone_url': PullRequestModel().get_shadow_clone_url(pull_request),
4079 'clone_url': PullRequestModel().get_shadow_clone_url(pull_request),
4069 'reference': (
4080 'reference': (
4070 pull_request.shadow_merge_ref._asdict()
4081 pull_request.shadow_merge_ref._asdict()
4071 if pull_request.shadow_merge_ref else None),
4082 if pull_request.shadow_merge_ref else None),
4072 }
4083 }
4073
4084
4074 data = {
4085 data = {
4075 'pull_request_id': pull_request.pull_request_id,
4086 'pull_request_id': pull_request.pull_request_id,
4076 'url': PullRequestModel().get_url(pull_request),
4087 'url': PullRequestModel().get_url(pull_request),
4077 'title': pull_request.title,
4088 'title': pull_request.title,
4078 'description': pull_request.description,
4089 'description': pull_request.description,
4079 'status': pull_request.status,
4090 'status': pull_request.status,
4080 'state': pull_request.pull_request_state,
4091 'state': pull_request.pull_request_state,
4081 'created_on': pull_request.created_on,
4092 'created_on': pull_request.created_on,
4082 'updated_on': pull_request.updated_on,
4093 'updated_on': pull_request.updated_on,
4083 'commit_ids': pull_request.revisions,
4094 'commit_ids': pull_request.revisions,
4084 'review_status': pull_request.calculated_review_status(),
4095 'review_status': pull_request.calculated_review_status(),
4085 'mergeable': merge_state,
4096 'mergeable': merge_state,
4086 'source': {
4097 'source': {
4087 'clone_url': pull_request.source_repo.clone_url(),
4098 'clone_url': pull_request.source_repo.clone_url(),
4088 'repository': pull_request.source_repo.repo_name,
4099 'repository': pull_request.source_repo.repo_name,
4089 'reference': {
4100 'reference': {
4090 'name': pull_request.source_ref_parts.name,
4101 'name': pull_request.source_ref_parts.name,
4091 'type': pull_request.source_ref_parts.type,
4102 'type': pull_request.source_ref_parts.type,
4092 'commit_id': pull_request.source_ref_parts.commit_id,
4103 'commit_id': pull_request.source_ref_parts.commit_id,
4093 },
4104 },
4094 },
4105 },
4095 'target': {
4106 'target': {
4096 'clone_url': pull_request.target_repo.clone_url(),
4107 'clone_url': pull_request.target_repo.clone_url(),
4097 'repository': pull_request.target_repo.repo_name,
4108 'repository': pull_request.target_repo.repo_name,
4098 'reference': {
4109 'reference': {
4099 'name': pull_request.target_ref_parts.name,
4110 'name': pull_request.target_ref_parts.name,
4100 'type': pull_request.target_ref_parts.type,
4111 'type': pull_request.target_ref_parts.type,
4101 'commit_id': pull_request.target_ref_parts.commit_id,
4112 'commit_id': pull_request.target_ref_parts.commit_id,
4102 },
4113 },
4103 },
4114 },
4104 'merge': merge_data,
4115 'merge': merge_data,
4105 'author': pull_request.author.get_api_data(include_secrets=False,
4116 'author': pull_request.author.get_api_data(include_secrets=False,
4106 details='basic'),
4117 details='basic'),
4107 'reviewers': [
4118 'reviewers': [
4108 {
4119 {
4109 'user': reviewer.get_api_data(include_secrets=False,
4120 'user': reviewer.get_api_data(include_secrets=False,
4110 details='basic'),
4121 details='basic'),
4111 'reasons': reasons,
4122 'reasons': reasons,
4112 'review_status': st[0][1].status if st else 'not_reviewed',
4123 'review_status': st[0][1].status if st else 'not_reviewed',
4113 }
4124 }
4114 for obj, reviewer, reasons, mandatory, st in
4125 for obj, reviewer, reasons, mandatory, st in
4115 pull_request.reviewers_statuses()
4126 pull_request.reviewers_statuses()
4116 ]
4127 ]
4117 }
4128 }
4118
4129
4119 return data
4130 return data
4120
4131
4121 def set_state(self, pull_request_state, final_state=None):
4132 def set_state(self, pull_request_state, final_state=None):
4122 """
4133 """
4123 # goes from initial state to updating to initial state.
4134 # goes from initial state to updating to initial state.
4124 # initial state can be changed by specifying back_state=
4135 # initial state can be changed by specifying back_state=
4125 with pull_request_obj.set_state(PullRequest.STATE_UPDATING):
4136 with pull_request_obj.set_state(PullRequest.STATE_UPDATING):
4126 pull_request.merge()
4137 pull_request.merge()
4127
4138
4128 :param pull_request_state:
4139 :param pull_request_state:
4129 :param final_state:
4140 :param final_state:
4130
4141
4131 """
4142 """
4132
4143
4133 return _SetState(self, pull_request_state, back_state=final_state)
4144 return _SetState(self, pull_request_state, back_state=final_state)
4134
4145
4135
4146
4136 class PullRequest(Base, _PullRequestBase):
4147 class PullRequest(Base, _PullRequestBase):
4137 __tablename__ = 'pull_requests'
4148 __tablename__ = 'pull_requests'
4138 __table_args__ = (
4149 __table_args__ = (
4139 base_table_args,
4150 base_table_args,
4140 )
4151 )
4141
4152
4142 pull_request_id = Column(
4153 pull_request_id = Column(
4143 'pull_request_id', Integer(), nullable=False, primary_key=True)
4154 'pull_request_id', Integer(), nullable=False, primary_key=True)
4144
4155
4145 def __repr__(self):
4156 def __repr__(self):
4146 if self.pull_request_id:
4157 if self.pull_request_id:
4147 return '<DB:PullRequest #%s>' % self.pull_request_id
4158 return '<DB:PullRequest #%s>' % self.pull_request_id
4148 else:
4159 else:
4149 return '<DB:PullRequest at %#x>' % id(self)
4160 return '<DB:PullRequest at %#x>' % id(self)
4150
4161
4151 reviewers = relationship('PullRequestReviewers',
4162 reviewers = relationship('PullRequestReviewers', cascade="all, delete-orphan")
4152 cascade="all, delete-orphan")
4163 statuses = relationship('ChangesetStatus', cascade="all, delete-orphan")
4153 statuses = relationship('ChangesetStatus',
4164 comments = relationship('ChangesetComment', cascade="all, delete-orphan")
4154 cascade="all, delete-orphan")
4165 versions = relationship('PullRequestVersion', cascade="all, delete-orphan",
4155 comments = relationship('ChangesetComment',
4156 cascade="all, delete-orphan")
4157 versions = relationship('PullRequestVersion',
4158 cascade="all, delete-orphan",
4159 lazy='dynamic')
4166 lazy='dynamic')
4160
4167
4161 @classmethod
4168 @classmethod
4162 def get_pr_display_object(cls, pull_request_obj, org_pull_request_obj,
4169 def get_pr_display_object(cls, pull_request_obj, org_pull_request_obj,
4163 internal_methods=None):
4170 internal_methods=None):
4164
4171
4165 class PullRequestDisplay(object):
4172 class PullRequestDisplay(object):
4166 """
4173 """
4167 Special object wrapper for showing PullRequest data via Versions
4174 Special object wrapper for showing PullRequest data via Versions
4168 It mimics PR object as close as possible. This is read only object
4175 It mimics PR object as close as possible. This is read only object
4169 just for display
4176 just for display
4170 """
4177 """
4171
4178
4172 def __init__(self, attrs, internal=None):
4179 def __init__(self, attrs, internal=None):
4173 self.attrs = attrs
4180 self.attrs = attrs
4174 # internal have priority over the given ones via attrs
4181 # internal have priority over the given ones via attrs
4175 self.internal = internal or ['versions']
4182 self.internal = internal or ['versions']
4176
4183
4177 def __getattr__(self, item):
4184 def __getattr__(self, item):
4178 if item in self.internal:
4185 if item in self.internal:
4179 return getattr(self, item)
4186 return getattr(self, item)
4180 try:
4187 try:
4181 return self.attrs[item]
4188 return self.attrs[item]
4182 except KeyError:
4189 except KeyError:
4183 raise AttributeError(
4190 raise AttributeError(
4184 '%s object has no attribute %s' % (self, item))
4191 '%s object has no attribute %s' % (self, item))
4185
4192
4186 def __repr__(self):
4193 def __repr__(self):
4187 return '<DB:PullRequestDisplay #%s>' % self.attrs.get('pull_request_id')
4194 return '<DB:PullRequestDisplay #%s>' % self.attrs.get('pull_request_id')
4188
4195
4189 def versions(self):
4196 def versions(self):
4190 return pull_request_obj.versions.order_by(
4197 return pull_request_obj.versions.order_by(
4191 PullRequestVersion.pull_request_version_id).all()
4198 PullRequestVersion.pull_request_version_id).all()
4192
4199
4193 def is_closed(self):
4200 def is_closed(self):
4194 return pull_request_obj.is_closed()
4201 return pull_request_obj.is_closed()
4195
4202
4196 @property
4203 @property
4197 def pull_request_version_id(self):
4204 def pull_request_version_id(self):
4198 return getattr(pull_request_obj, 'pull_request_version_id', None)
4205 return getattr(pull_request_obj, 'pull_request_version_id', None)
4199
4206
4200 attrs = StrictAttributeDict(pull_request_obj.get_api_data(with_merge_state=False))
4207 attrs = StrictAttributeDict(pull_request_obj.get_api_data(with_merge_state=False))
4201
4208
4202 attrs.author = StrictAttributeDict(
4209 attrs.author = StrictAttributeDict(
4203 pull_request_obj.author.get_api_data())
4210 pull_request_obj.author.get_api_data())
4204 if pull_request_obj.target_repo:
4211 if pull_request_obj.target_repo:
4205 attrs.target_repo = StrictAttributeDict(
4212 attrs.target_repo = StrictAttributeDict(
4206 pull_request_obj.target_repo.get_api_data())
4213 pull_request_obj.target_repo.get_api_data())
4207 attrs.target_repo.clone_url = pull_request_obj.target_repo.clone_url
4214 attrs.target_repo.clone_url = pull_request_obj.target_repo.clone_url
4208
4215
4209 if pull_request_obj.source_repo:
4216 if pull_request_obj.source_repo:
4210 attrs.source_repo = StrictAttributeDict(
4217 attrs.source_repo = StrictAttributeDict(
4211 pull_request_obj.source_repo.get_api_data())
4218 pull_request_obj.source_repo.get_api_data())
4212 attrs.source_repo.clone_url = pull_request_obj.source_repo.clone_url
4219 attrs.source_repo.clone_url = pull_request_obj.source_repo.clone_url
4213
4220
4214 attrs.source_ref_parts = pull_request_obj.source_ref_parts
4221 attrs.source_ref_parts = pull_request_obj.source_ref_parts
4215 attrs.target_ref_parts = pull_request_obj.target_ref_parts
4222 attrs.target_ref_parts = pull_request_obj.target_ref_parts
4216 attrs.revisions = pull_request_obj.revisions
4223 attrs.revisions = pull_request_obj.revisions
4217
4224
4218 attrs.shadow_merge_ref = org_pull_request_obj.shadow_merge_ref
4225 attrs.shadow_merge_ref = org_pull_request_obj.shadow_merge_ref
4219 attrs.reviewer_data = org_pull_request_obj.reviewer_data
4226 attrs.reviewer_data = org_pull_request_obj.reviewer_data
4220 attrs.reviewer_data_json = org_pull_request_obj.reviewer_data_json
4227 attrs.reviewer_data_json = org_pull_request_obj.reviewer_data_json
4221
4228
4222 return PullRequestDisplay(attrs, internal=internal_methods)
4229 return PullRequestDisplay(attrs, internal=internal_methods)
4223
4230
4224 def is_closed(self):
4231 def is_closed(self):
4225 return self.status == self.STATUS_CLOSED
4232 return self.status == self.STATUS_CLOSED
4226
4233
4227 def __json__(self):
4234 def __json__(self):
4228 return {
4235 return {
4229 'revisions': self.revisions,
4236 'revisions': self.revisions,
4230 }
4237 }
4231
4238
4232 def calculated_review_status(self):
4239 def calculated_review_status(self):
4233 from rhodecode.model.changeset_status import ChangesetStatusModel
4240 from rhodecode.model.changeset_status import ChangesetStatusModel
4234 return ChangesetStatusModel().calculated_review_status(self)
4241 return ChangesetStatusModel().calculated_review_status(self)
4235
4242
4236 def reviewers_statuses(self):
4243 def reviewers_statuses(self):
4237 from rhodecode.model.changeset_status import ChangesetStatusModel
4244 from rhodecode.model.changeset_status import ChangesetStatusModel
4238 return ChangesetStatusModel().reviewers_statuses(self)
4245 return ChangesetStatusModel().reviewers_statuses(self)
4239
4246
4240 @property
4247 @property
4241 def workspace_id(self):
4248 def workspace_id(self):
4242 from rhodecode.model.pull_request import PullRequestModel
4249 from rhodecode.model.pull_request import PullRequestModel
4243 return PullRequestModel()._workspace_id(self)
4250 return PullRequestModel()._workspace_id(self)
4244
4251
4245 def get_shadow_repo(self):
4252 def get_shadow_repo(self):
4246 workspace_id = self.workspace_id
4253 workspace_id = self.workspace_id
4247 shadow_repository_path = self.target_repo.get_shadow_repository_path(workspace_id)
4254 shadow_repository_path = self.target_repo.get_shadow_repository_path(workspace_id)
4248 if os.path.isdir(shadow_repository_path):
4255 if os.path.isdir(shadow_repository_path):
4249 vcs_obj = self.target_repo.scm_instance()
4256 vcs_obj = self.target_repo.scm_instance()
4250 return vcs_obj.get_shadow_instance(shadow_repository_path)
4257 return vcs_obj.get_shadow_instance(shadow_repository_path)
4251
4258
4252
4259
4253 class PullRequestVersion(Base, _PullRequestBase):
4260 class PullRequestVersion(Base, _PullRequestBase):
4254 __tablename__ = 'pull_request_versions'
4261 __tablename__ = 'pull_request_versions'
4255 __table_args__ = (
4262 __table_args__ = (
4256 base_table_args,
4263 base_table_args,
4257 )
4264 )
4258
4265
4259 pull_request_version_id = Column(
4266 pull_request_version_id = Column(
4260 'pull_request_version_id', Integer(), nullable=False, primary_key=True)
4267 'pull_request_version_id', Integer(), nullable=False, primary_key=True)
4261 pull_request_id = Column(
4268 pull_request_id = Column(
4262 'pull_request_id', Integer(),
4269 'pull_request_id', Integer(),
4263 ForeignKey('pull_requests.pull_request_id'), nullable=False)
4270 ForeignKey('pull_requests.pull_request_id'), nullable=False)
4264 pull_request = relationship('PullRequest')
4271 pull_request = relationship('PullRequest')
4265
4272
4266 def __repr__(self):
4273 def __repr__(self):
4267 if self.pull_request_version_id:
4274 if self.pull_request_version_id:
4268 return '<DB:PullRequestVersion #%s>' % self.pull_request_version_id
4275 return '<DB:PullRequestVersion #%s>' % self.pull_request_version_id
4269 else:
4276 else:
4270 return '<DB:PullRequestVersion at %#x>' % id(self)
4277 return '<DB:PullRequestVersion at %#x>' % id(self)
4271
4278
4272 @property
4279 @property
4273 def reviewers(self):
4280 def reviewers(self):
4274 return self.pull_request.reviewers
4281 return self.pull_request.reviewers
4275
4282
4276 @property
4283 @property
4277 def versions(self):
4284 def versions(self):
4278 return self.pull_request.versions
4285 return self.pull_request.versions
4279
4286
4280 def is_closed(self):
4287 def is_closed(self):
4281 # calculate from original
4288 # calculate from original
4282 return self.pull_request.status == self.STATUS_CLOSED
4289 return self.pull_request.status == self.STATUS_CLOSED
4283
4290
4284 def calculated_review_status(self):
4291 def calculated_review_status(self):
4285 return self.pull_request.calculated_review_status()
4292 return self.pull_request.calculated_review_status()
4286
4293
4287 def reviewers_statuses(self):
4294 def reviewers_statuses(self):
4288 return self.pull_request.reviewers_statuses()
4295 return self.pull_request.reviewers_statuses()
4289
4296
4290
4297
4291 class PullRequestReviewers(Base, BaseModel):
4298 class PullRequestReviewers(Base, BaseModel):
4292 __tablename__ = 'pull_request_reviewers'
4299 __tablename__ = 'pull_request_reviewers'
4293 __table_args__ = (
4300 __table_args__ = (
4294 base_table_args,
4301 base_table_args,
4295 )
4302 )
4296
4303
4297 @hybrid_property
4304 @hybrid_property
4298 def reasons(self):
4305 def reasons(self):
4299 if not self._reasons:
4306 if not self._reasons:
4300 return []
4307 return []
4301 return self._reasons
4308 return self._reasons
4302
4309
4303 @reasons.setter
4310 @reasons.setter
4304 def reasons(self, val):
4311 def reasons(self, val):
4305 val = val or []
4312 val = val or []
4306 if any(not isinstance(x, compat.string_types) for x in val):
4313 if any(not isinstance(x, compat.string_types) for x in val):
4307 raise Exception('invalid reasons type, must be list of strings')
4314 raise Exception('invalid reasons type, must be list of strings')
4308 self._reasons = val
4315 self._reasons = val
4309
4316
4310 pull_requests_reviewers_id = Column(
4317 pull_requests_reviewers_id = Column(
4311 'pull_requests_reviewers_id', Integer(), nullable=False,
4318 'pull_requests_reviewers_id', Integer(), nullable=False,
4312 primary_key=True)
4319 primary_key=True)
4313 pull_request_id = Column(
4320 pull_request_id = Column(
4314 "pull_request_id", Integer(),
4321 "pull_request_id", Integer(),
4315 ForeignKey('pull_requests.pull_request_id'), nullable=False)
4322 ForeignKey('pull_requests.pull_request_id'), nullable=False)
4316 user_id = Column(
4323 user_id = Column(
4317 "user_id", Integer(), ForeignKey('users.user_id'), nullable=True)
4324 "user_id", Integer(), ForeignKey('users.user_id'), nullable=True)
4318 _reasons = Column(
4325 _reasons = Column(
4319 'reason', MutationList.as_mutable(
4326 'reason', MutationList.as_mutable(
4320 JsonType('list', dialect_map=dict(mysql=UnicodeText(16384)))))
4327 JsonType('list', dialect_map=dict(mysql=UnicodeText(16384)))))
4321
4328
4322 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
4329 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
4323 user = relationship('User')
4330 user = relationship('User')
4324 pull_request = relationship('PullRequest')
4331 pull_request = relationship('PullRequest')
4325
4332
4326 rule_data = Column(
4333 rule_data = Column(
4327 'rule_data_json',
4334 'rule_data_json',
4328 JsonType(dialect_map=dict(mysql=UnicodeText(16384))))
4335 JsonType(dialect_map=dict(mysql=UnicodeText(16384))))
4329
4336
4330 def rule_user_group_data(self):
4337 def rule_user_group_data(self):
4331 """
4338 """
4332 Returns the voting user group rule data for this reviewer
4339 Returns the voting user group rule data for this reviewer
4333 """
4340 """
4334
4341
4335 if self.rule_data and 'vote_rule' in self.rule_data:
4342 if self.rule_data and 'vote_rule' in self.rule_data:
4336 user_group_data = {}
4343 user_group_data = {}
4337 if 'rule_user_group_entry_id' in self.rule_data:
4344 if 'rule_user_group_entry_id' in self.rule_data:
4338 # means a group with voting rules !
4345 # means a group with voting rules !
4339 user_group_data['id'] = self.rule_data['rule_user_group_entry_id']
4346 user_group_data['id'] = self.rule_data['rule_user_group_entry_id']
4340 user_group_data['name'] = self.rule_data['rule_name']
4347 user_group_data['name'] = self.rule_data['rule_name']
4341 user_group_data['vote_rule'] = self.rule_data['vote_rule']
4348 user_group_data['vote_rule'] = self.rule_data['vote_rule']
4342
4349
4343 return user_group_data
4350 return user_group_data
4344
4351
4345 def __unicode__(self):
4352 def __unicode__(self):
4346 return u"<%s('id:%s')>" % (self.__class__.__name__,
4353 return u"<%s('id:%s')>" % (self.__class__.__name__,
4347 self.pull_requests_reviewers_id)
4354 self.pull_requests_reviewers_id)
4348
4355
4349
4356
4350 class Notification(Base, BaseModel):
4357 class Notification(Base, BaseModel):
4351 __tablename__ = 'notifications'
4358 __tablename__ = 'notifications'
4352 __table_args__ = (
4359 __table_args__ = (
4353 Index('notification_type_idx', 'type'),
4360 Index('notification_type_idx', 'type'),
4354 base_table_args,
4361 base_table_args,
4355 )
4362 )
4356
4363
4357 TYPE_CHANGESET_COMMENT = u'cs_comment'
4364 TYPE_CHANGESET_COMMENT = u'cs_comment'
4358 TYPE_MESSAGE = u'message'
4365 TYPE_MESSAGE = u'message'
4359 TYPE_MENTION = u'mention'
4366 TYPE_MENTION = u'mention'
4360 TYPE_REGISTRATION = u'registration'
4367 TYPE_REGISTRATION = u'registration'
4361 TYPE_PULL_REQUEST = u'pull_request'
4368 TYPE_PULL_REQUEST = u'pull_request'
4362 TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment'
4369 TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment'
4363
4370
4364 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
4371 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
4365 subject = Column('subject', Unicode(512), nullable=True)
4372 subject = Column('subject', Unicode(512), nullable=True)
4366 body = Column('body', UnicodeText().with_variant(UnicodeText(50000), 'mysql'), nullable=True)
4373 body = Column('body', UnicodeText().with_variant(UnicodeText(50000), 'mysql'), nullable=True)
4367 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
4374 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
4368 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4375 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4369 type_ = Column('type', Unicode(255))
4376 type_ = Column('type', Unicode(255))
4370
4377
4371 created_by_user = relationship('User')
4378 created_by_user = relationship('User')
4372 notifications_to_users = relationship('UserNotification', lazy='joined',
4379 notifications_to_users = relationship('UserNotification', lazy='joined',
4373 cascade="all, delete-orphan")
4380 cascade="all, delete-orphan")
4374
4381
4375 @property
4382 @property
4376 def recipients(self):
4383 def recipients(self):
4377 return [x.user for x in UserNotification.query()\
4384 return [x.user for x in UserNotification.query()\
4378 .filter(UserNotification.notification == self)\
4385 .filter(UserNotification.notification == self)\
4379 .order_by(UserNotification.user_id.asc()).all()]
4386 .order_by(UserNotification.user_id.asc()).all()]
4380
4387
4381 @classmethod
4388 @classmethod
4382 def create(cls, created_by, subject, body, recipients, type_=None):
4389 def create(cls, created_by, subject, body, recipients, type_=None):
4383 if type_ is None:
4390 if type_ is None:
4384 type_ = Notification.TYPE_MESSAGE
4391 type_ = Notification.TYPE_MESSAGE
4385
4392
4386 notification = cls()
4393 notification = cls()
4387 notification.created_by_user = created_by
4394 notification.created_by_user = created_by
4388 notification.subject = subject
4395 notification.subject = subject
4389 notification.body = body
4396 notification.body = body
4390 notification.type_ = type_
4397 notification.type_ = type_
4391 notification.created_on = datetime.datetime.now()
4398 notification.created_on = datetime.datetime.now()
4392
4399
4393 # For each recipient link the created notification to his account
4400 # For each recipient link the created notification to his account
4394 for u in recipients:
4401 for u in recipients:
4395 assoc = UserNotification()
4402 assoc = UserNotification()
4396 assoc.user_id = u.user_id
4403 assoc.user_id = u.user_id
4397 assoc.notification = notification
4404 assoc.notification = notification
4398
4405
4399 # if created_by is inside recipients mark his notification
4406 # if created_by is inside recipients mark his notification
4400 # as read
4407 # as read
4401 if u.user_id == created_by.user_id:
4408 if u.user_id == created_by.user_id:
4402 assoc.read = True
4409 assoc.read = True
4403 Session().add(assoc)
4410 Session().add(assoc)
4404
4411
4405 Session().add(notification)
4412 Session().add(notification)
4406
4413
4407 return notification
4414 return notification
4408
4415
4409
4416
4410 class UserNotification(Base, BaseModel):
4417 class UserNotification(Base, BaseModel):
4411 __tablename__ = 'user_to_notification'
4418 __tablename__ = 'user_to_notification'
4412 __table_args__ = (
4419 __table_args__ = (
4413 UniqueConstraint('user_id', 'notification_id'),
4420 UniqueConstraint('user_id', 'notification_id'),
4414 base_table_args
4421 base_table_args
4415 )
4422 )
4416
4423
4417 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
4424 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
4418 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
4425 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
4419 read = Column('read', Boolean, default=False)
4426 read = Column('read', Boolean, default=False)
4420 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
4427 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
4421
4428
4422 user = relationship('User', lazy="joined")
4429 user = relationship('User', lazy="joined")
4423 notification = relationship('Notification', lazy="joined",
4430 notification = relationship('Notification', lazy="joined",
4424 order_by=lambda: Notification.created_on.desc(),)
4431 order_by=lambda: Notification.created_on.desc(),)
4425
4432
4426 def mark_as_read(self):
4433 def mark_as_read(self):
4427 self.read = True
4434 self.read = True
4428 Session().add(self)
4435 Session().add(self)
4429
4436
4430
4437
4431 class Gist(Base, BaseModel):
4438 class Gist(Base, BaseModel):
4432 __tablename__ = 'gists'
4439 __tablename__ = 'gists'
4433 __table_args__ = (
4440 __table_args__ = (
4434 Index('g_gist_access_id_idx', 'gist_access_id'),
4441 Index('g_gist_access_id_idx', 'gist_access_id'),
4435 Index('g_created_on_idx', 'created_on'),
4442 Index('g_created_on_idx', 'created_on'),
4436 base_table_args
4443 base_table_args
4437 )
4444 )
4438
4445
4439 GIST_PUBLIC = u'public'
4446 GIST_PUBLIC = u'public'
4440 GIST_PRIVATE = u'private'
4447 GIST_PRIVATE = u'private'
4441 DEFAULT_FILENAME = u'gistfile1.txt'
4448 DEFAULT_FILENAME = u'gistfile1.txt'
4442
4449
4443 ACL_LEVEL_PUBLIC = u'acl_public'
4450 ACL_LEVEL_PUBLIC = u'acl_public'
4444 ACL_LEVEL_PRIVATE = u'acl_private'
4451 ACL_LEVEL_PRIVATE = u'acl_private'
4445
4452
4446 gist_id = Column('gist_id', Integer(), primary_key=True)
4453 gist_id = Column('gist_id', Integer(), primary_key=True)
4447 gist_access_id = Column('gist_access_id', Unicode(250))
4454 gist_access_id = Column('gist_access_id', Unicode(250))
4448 gist_description = Column('gist_description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
4455 gist_description = Column('gist_description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
4449 gist_owner = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True)
4456 gist_owner = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True)
4450 gist_expires = Column('gist_expires', Float(53), nullable=False)
4457 gist_expires = Column('gist_expires', Float(53), nullable=False)
4451 gist_type = Column('gist_type', Unicode(128), nullable=False)
4458 gist_type = Column('gist_type', Unicode(128), nullable=False)
4452 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4459 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4453 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4460 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4454 acl_level = Column('acl_level', Unicode(128), nullable=True)
4461 acl_level = Column('acl_level', Unicode(128), nullable=True)
4455
4462
4456 owner = relationship('User')
4463 owner = relationship('User')
4457
4464
4458 def __repr__(self):
4465 def __repr__(self):
4459 return '<Gist:[%s]%s>' % (self.gist_type, self.gist_access_id)
4466 return '<Gist:[%s]%s>' % (self.gist_type, self.gist_access_id)
4460
4467
4461 @hybrid_property
4468 @hybrid_property
4462 def description_safe(self):
4469 def description_safe(self):
4463 from rhodecode.lib import helpers as h
4470 from rhodecode.lib import helpers as h
4464 return h.escape(self.gist_description)
4471 return h.escape(self.gist_description)
4465
4472
4466 @classmethod
4473 @classmethod
4467 def get_or_404(cls, id_):
4474 def get_or_404(cls, id_):
4468 from pyramid.httpexceptions import HTTPNotFound
4475 from pyramid.httpexceptions import HTTPNotFound
4469
4476
4470 res = cls.query().filter(cls.gist_access_id == id_).scalar()
4477 res = cls.query().filter(cls.gist_access_id == id_).scalar()
4471 if not res:
4478 if not res:
4472 raise HTTPNotFound()
4479 raise HTTPNotFound()
4473 return res
4480 return res
4474
4481
4475 @classmethod
4482 @classmethod
4476 def get_by_access_id(cls, gist_access_id):
4483 def get_by_access_id(cls, gist_access_id):
4477 return cls.query().filter(cls.gist_access_id == gist_access_id).scalar()
4484 return cls.query().filter(cls.gist_access_id == gist_access_id).scalar()
4478
4485
4479 def gist_url(self):
4486 def gist_url(self):
4480 from rhodecode.model.gist import GistModel
4487 from rhodecode.model.gist import GistModel
4481 return GistModel().get_url(self)
4488 return GistModel().get_url(self)
4482
4489
4483 @classmethod
4490 @classmethod
4484 def base_path(cls):
4491 def base_path(cls):
4485 """
4492 """
4486 Returns base path when all gists are stored
4493 Returns base path when all gists are stored
4487
4494
4488 :param cls:
4495 :param cls:
4489 """
4496 """
4490 from rhodecode.model.gist import GIST_STORE_LOC
4497 from rhodecode.model.gist import GIST_STORE_LOC
4491 q = Session().query(RhodeCodeUi)\
4498 q = Session().query(RhodeCodeUi)\
4492 .filter(RhodeCodeUi.ui_key == URL_SEP)
4499 .filter(RhodeCodeUi.ui_key == URL_SEP)
4493 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
4500 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
4494 return os.path.join(q.one().ui_value, GIST_STORE_LOC)
4501 return os.path.join(q.one().ui_value, GIST_STORE_LOC)
4495
4502
4496 def get_api_data(self):
4503 def get_api_data(self):
4497 """
4504 """
4498 Common function for generating gist related data for API
4505 Common function for generating gist related data for API
4499 """
4506 """
4500 gist = self
4507 gist = self
4501 data = {
4508 data = {
4502 'gist_id': gist.gist_id,
4509 'gist_id': gist.gist_id,
4503 'type': gist.gist_type,
4510 'type': gist.gist_type,
4504 'access_id': gist.gist_access_id,
4511 'access_id': gist.gist_access_id,
4505 'description': gist.gist_description,
4512 'description': gist.gist_description,
4506 'url': gist.gist_url(),
4513 'url': gist.gist_url(),
4507 'expires': gist.gist_expires,
4514 'expires': gist.gist_expires,
4508 'created_on': gist.created_on,
4515 'created_on': gist.created_on,
4509 'modified_at': gist.modified_at,
4516 'modified_at': gist.modified_at,
4510 'content': None,
4517 'content': None,
4511 'acl_level': gist.acl_level,
4518 'acl_level': gist.acl_level,
4512 }
4519 }
4513 return data
4520 return data
4514
4521
4515 def __json__(self):
4522 def __json__(self):
4516 data = dict(
4523 data = dict(
4517 )
4524 )
4518 data.update(self.get_api_data())
4525 data.update(self.get_api_data())
4519 return data
4526 return data
4520 # SCM functions
4527 # SCM functions
4521
4528
4522 def scm_instance(self, **kwargs):
4529 def scm_instance(self, **kwargs):
4523 """
4530 """
4524 Get an instance of VCS Repository
4531 Get an instance of VCS Repository
4525
4532
4526 :param kwargs:
4533 :param kwargs:
4527 """
4534 """
4528 from rhodecode.model.gist import GistModel
4535 from rhodecode.model.gist import GistModel
4529 full_repo_path = os.path.join(self.base_path(), self.gist_access_id)
4536 full_repo_path = os.path.join(self.base_path(), self.gist_access_id)
4530 return get_vcs_instance(
4537 return get_vcs_instance(
4531 repo_path=safe_str(full_repo_path), create=False,
4538 repo_path=safe_str(full_repo_path), create=False,
4532 _vcs_alias=GistModel.vcs_backend)
4539 _vcs_alias=GistModel.vcs_backend)
4533
4540
4534
4541
4535 class ExternalIdentity(Base, BaseModel):
4542 class ExternalIdentity(Base, BaseModel):
4536 __tablename__ = 'external_identities'
4543 __tablename__ = 'external_identities'
4537 __table_args__ = (
4544 __table_args__ = (
4538 Index('local_user_id_idx', 'local_user_id'),
4545 Index('local_user_id_idx', 'local_user_id'),
4539 Index('external_id_idx', 'external_id'),
4546 Index('external_id_idx', 'external_id'),
4540 base_table_args
4547 base_table_args
4541 )
4548 )
4542
4549
4543 external_id = Column('external_id', Unicode(255), default=u'', primary_key=True)
4550 external_id = Column('external_id', Unicode(255), default=u'', primary_key=True)
4544 external_username = Column('external_username', Unicode(1024), default=u'')
4551 external_username = Column('external_username', Unicode(1024), default=u'')
4545 local_user_id = Column('local_user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
4552 local_user_id = Column('local_user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
4546 provider_name = Column('provider_name', Unicode(255), default=u'', primary_key=True)
4553 provider_name = Column('provider_name', Unicode(255), default=u'', primary_key=True)
4547 access_token = Column('access_token', String(1024), default=u'')
4554 access_token = Column('access_token', String(1024), default=u'')
4548 alt_token = Column('alt_token', String(1024), default=u'')
4555 alt_token = Column('alt_token', String(1024), default=u'')
4549 token_secret = Column('token_secret', String(1024), default=u'')
4556 token_secret = Column('token_secret', String(1024), default=u'')
4550
4557
4551 @classmethod
4558 @classmethod
4552 def by_external_id_and_provider(cls, external_id, provider_name, local_user_id=None):
4559 def by_external_id_and_provider(cls, external_id, provider_name, local_user_id=None):
4553 """
4560 """
4554 Returns ExternalIdentity instance based on search params
4561 Returns ExternalIdentity instance based on search params
4555
4562
4556 :param external_id:
4563 :param external_id:
4557 :param provider_name:
4564 :param provider_name:
4558 :return: ExternalIdentity
4565 :return: ExternalIdentity
4559 """
4566 """
4560 query = cls.query()
4567 query = cls.query()
4561 query = query.filter(cls.external_id == external_id)
4568 query = query.filter(cls.external_id == external_id)
4562 query = query.filter(cls.provider_name == provider_name)
4569 query = query.filter(cls.provider_name == provider_name)
4563 if local_user_id:
4570 if local_user_id:
4564 query = query.filter(cls.local_user_id == local_user_id)
4571 query = query.filter(cls.local_user_id == local_user_id)
4565 return query.first()
4572 return query.first()
4566
4573
4567 @classmethod
4574 @classmethod
4568 def user_by_external_id_and_provider(cls, external_id, provider_name):
4575 def user_by_external_id_and_provider(cls, external_id, provider_name):
4569 """
4576 """
4570 Returns User instance based on search params
4577 Returns User instance based on search params
4571
4578
4572 :param external_id:
4579 :param external_id:
4573 :param provider_name:
4580 :param provider_name:
4574 :return: User
4581 :return: User
4575 """
4582 """
4576 query = User.query()
4583 query = User.query()
4577 query = query.filter(cls.external_id == external_id)
4584 query = query.filter(cls.external_id == external_id)
4578 query = query.filter(cls.provider_name == provider_name)
4585 query = query.filter(cls.provider_name == provider_name)
4579 query = query.filter(User.user_id == cls.local_user_id)
4586 query = query.filter(User.user_id == cls.local_user_id)
4580 return query.first()
4587 return query.first()
4581
4588
4582 @classmethod
4589 @classmethod
4583 def by_local_user_id(cls, local_user_id):
4590 def by_local_user_id(cls, local_user_id):
4584 """
4591 """
4585 Returns all tokens for user
4592 Returns all tokens for user
4586
4593
4587 :param local_user_id:
4594 :param local_user_id:
4588 :return: ExternalIdentity
4595 :return: ExternalIdentity
4589 """
4596 """
4590 query = cls.query()
4597 query = cls.query()
4591 query = query.filter(cls.local_user_id == local_user_id)
4598 query = query.filter(cls.local_user_id == local_user_id)
4592 return query
4599 return query
4593
4600
4594 @classmethod
4601 @classmethod
4595 def load_provider_plugin(cls, plugin_id):
4602 def load_provider_plugin(cls, plugin_id):
4596 from rhodecode.authentication.base import loadplugin
4603 from rhodecode.authentication.base import loadplugin
4597 _plugin_id = 'egg:rhodecode-enterprise-ee#{}'.format(plugin_id)
4604 _plugin_id = 'egg:rhodecode-enterprise-ee#{}'.format(plugin_id)
4598 auth_plugin = loadplugin(_plugin_id)
4605 auth_plugin = loadplugin(_plugin_id)
4599 return auth_plugin
4606 return auth_plugin
4600
4607
4601
4608
4602 class Integration(Base, BaseModel):
4609 class Integration(Base, BaseModel):
4603 __tablename__ = 'integrations'
4610 __tablename__ = 'integrations'
4604 __table_args__ = (
4611 __table_args__ = (
4605 base_table_args
4612 base_table_args
4606 )
4613 )
4607
4614
4608 integration_id = Column('integration_id', Integer(), primary_key=True)
4615 integration_id = Column('integration_id', Integer(), primary_key=True)
4609 integration_type = Column('integration_type', String(255))
4616 integration_type = Column('integration_type', String(255))
4610 enabled = Column('enabled', Boolean(), nullable=False)
4617 enabled = Column('enabled', Boolean(), nullable=False)
4611 name = Column('name', String(255), nullable=False)
4618 name = Column('name', String(255), nullable=False)
4612 child_repos_only = Column('child_repos_only', Boolean(), nullable=False,
4619 child_repos_only = Column('child_repos_only', Boolean(), nullable=False,
4613 default=False)
4620 default=False)
4614
4621
4615 settings = Column(
4622 settings = Column(
4616 'settings_json', MutationObj.as_mutable(
4623 'settings_json', MutationObj.as_mutable(
4617 JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
4624 JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
4618 repo_id = Column(
4625 repo_id = Column(
4619 'repo_id', Integer(), ForeignKey('repositories.repo_id'),
4626 'repo_id', Integer(), ForeignKey('repositories.repo_id'),
4620 nullable=True, unique=None, default=None)
4627 nullable=True, unique=None, default=None)
4621 repo = relationship('Repository', lazy='joined')
4628 repo = relationship('Repository', lazy='joined')
4622
4629
4623 repo_group_id = Column(
4630 repo_group_id = Column(
4624 'repo_group_id', Integer(), ForeignKey('groups.group_id'),
4631 'repo_group_id', Integer(), ForeignKey('groups.group_id'),
4625 nullable=True, unique=None, default=None)
4632 nullable=True, unique=None, default=None)
4626 repo_group = relationship('RepoGroup', lazy='joined')
4633 repo_group = relationship('RepoGroup', lazy='joined')
4627
4634
4628 @property
4635 @property
4629 def scope(self):
4636 def scope(self):
4630 if self.repo:
4637 if self.repo:
4631 return repr(self.repo)
4638 return repr(self.repo)
4632 if self.repo_group:
4639 if self.repo_group:
4633 if self.child_repos_only:
4640 if self.child_repos_only:
4634 return repr(self.repo_group) + ' (child repos only)'
4641 return repr(self.repo_group) + ' (child repos only)'
4635 else:
4642 else:
4636 return repr(self.repo_group) + ' (recursive)'
4643 return repr(self.repo_group) + ' (recursive)'
4637 if self.child_repos_only:
4644 if self.child_repos_only:
4638 return 'root_repos'
4645 return 'root_repos'
4639 return 'global'
4646 return 'global'
4640
4647
4641 def __repr__(self):
4648 def __repr__(self):
4642 return '<Integration(%r, %r)>' % (self.integration_type, self.scope)
4649 return '<Integration(%r, %r)>' % (self.integration_type, self.scope)
4643
4650
4644
4651
4645 class RepoReviewRuleUser(Base, BaseModel):
4652 class RepoReviewRuleUser(Base, BaseModel):
4646 __tablename__ = 'repo_review_rules_users'
4653 __tablename__ = 'repo_review_rules_users'
4647 __table_args__ = (
4654 __table_args__ = (
4648 base_table_args
4655 base_table_args
4649 )
4656 )
4650
4657
4651 repo_review_rule_user_id = Column('repo_review_rule_user_id', Integer(), primary_key=True)
4658 repo_review_rule_user_id = Column('repo_review_rule_user_id', Integer(), primary_key=True)
4652 repo_review_rule_id = Column("repo_review_rule_id", Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
4659 repo_review_rule_id = Column("repo_review_rule_id", Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
4653 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False)
4660 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False)
4654 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
4661 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
4655 user = relationship('User')
4662 user = relationship('User')
4656
4663
4657 def rule_data(self):
4664 def rule_data(self):
4658 return {
4665 return {
4659 'mandatory': self.mandatory
4666 'mandatory': self.mandatory
4660 }
4667 }
4661
4668
4662
4669
4663 class RepoReviewRuleUserGroup(Base, BaseModel):
4670 class RepoReviewRuleUserGroup(Base, BaseModel):
4664 __tablename__ = 'repo_review_rules_users_groups'
4671 __tablename__ = 'repo_review_rules_users_groups'
4665 __table_args__ = (
4672 __table_args__ = (
4666 base_table_args
4673 base_table_args
4667 )
4674 )
4668
4675
4669 VOTE_RULE_ALL = -1
4676 VOTE_RULE_ALL = -1
4670
4677
4671 repo_review_rule_users_group_id = Column('repo_review_rule_users_group_id', Integer(), primary_key=True)
4678 repo_review_rule_users_group_id = Column('repo_review_rule_users_group_id', Integer(), primary_key=True)
4672 repo_review_rule_id = Column("repo_review_rule_id", Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
4679 repo_review_rule_id = Column("repo_review_rule_id", Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
4673 users_group_id = Column("users_group_id", Integer(),ForeignKey('users_groups.users_group_id'), nullable=False)
4680 users_group_id = Column("users_group_id", Integer(),ForeignKey('users_groups.users_group_id'), nullable=False)
4674 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
4681 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
4675 vote_rule = Column("vote_rule", Integer(), nullable=True, default=VOTE_RULE_ALL)
4682 vote_rule = Column("vote_rule", Integer(), nullable=True, default=VOTE_RULE_ALL)
4676 users_group = relationship('UserGroup')
4683 users_group = relationship('UserGroup')
4677
4684
4678 def rule_data(self):
4685 def rule_data(self):
4679 return {
4686 return {
4680 'mandatory': self.mandatory,
4687 'mandatory': self.mandatory,
4681 'vote_rule': self.vote_rule
4688 'vote_rule': self.vote_rule
4682 }
4689 }
4683
4690
4684 @property
4691 @property
4685 def vote_rule_label(self):
4692 def vote_rule_label(self):
4686 if not self.vote_rule or self.vote_rule == self.VOTE_RULE_ALL:
4693 if not self.vote_rule or self.vote_rule == self.VOTE_RULE_ALL:
4687 return 'all must vote'
4694 return 'all must vote'
4688 else:
4695 else:
4689 return 'min. vote {}'.format(self.vote_rule)
4696 return 'min. vote {}'.format(self.vote_rule)
4690
4697
4691
4698
4692 class RepoReviewRule(Base, BaseModel):
4699 class RepoReviewRule(Base, BaseModel):
4693 __tablename__ = 'repo_review_rules'
4700 __tablename__ = 'repo_review_rules'
4694 __table_args__ = (
4701 __table_args__ = (
4695 base_table_args
4702 base_table_args
4696 )
4703 )
4697
4704
4698 repo_review_rule_id = Column(
4705 repo_review_rule_id = Column(
4699 'repo_review_rule_id', Integer(), primary_key=True)
4706 'repo_review_rule_id', Integer(), primary_key=True)
4700 repo_id = Column(
4707 repo_id = Column(
4701 "repo_id", Integer(), ForeignKey('repositories.repo_id'))
4708 "repo_id", Integer(), ForeignKey('repositories.repo_id'))
4702 repo = relationship('Repository', backref='review_rules')
4709 repo = relationship('Repository', backref='review_rules')
4703
4710
4704 review_rule_name = Column('review_rule_name', String(255))
4711 review_rule_name = Column('review_rule_name', String(255))
4705 _branch_pattern = Column("branch_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default=u'*') # glob
4712 _branch_pattern = Column("branch_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default=u'*') # glob
4706 _target_branch_pattern = Column("target_branch_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default=u'*') # glob
4713 _target_branch_pattern = Column("target_branch_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default=u'*') # glob
4707 _file_pattern = Column("file_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default=u'*') # glob
4714 _file_pattern = Column("file_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default=u'*') # glob
4708
4715
4709 use_authors_for_review = Column("use_authors_for_review", Boolean(), nullable=False, default=False)
4716 use_authors_for_review = Column("use_authors_for_review", Boolean(), nullable=False, default=False)
4710 forbid_author_to_review = Column("forbid_author_to_review", Boolean(), nullable=False, default=False)
4717 forbid_author_to_review = Column("forbid_author_to_review", Boolean(), nullable=False, default=False)
4711 forbid_commit_author_to_review = Column("forbid_commit_author_to_review", Boolean(), nullable=False, default=False)
4718 forbid_commit_author_to_review = Column("forbid_commit_author_to_review", Boolean(), nullable=False, default=False)
4712 forbid_adding_reviewers = Column("forbid_adding_reviewers", Boolean(), nullable=False, default=False)
4719 forbid_adding_reviewers = Column("forbid_adding_reviewers", Boolean(), nullable=False, default=False)
4713
4720
4714 rule_users = relationship('RepoReviewRuleUser')
4721 rule_users = relationship('RepoReviewRuleUser')
4715 rule_user_groups = relationship('RepoReviewRuleUserGroup')
4722 rule_user_groups = relationship('RepoReviewRuleUserGroup')
4716
4723
4717 def _validate_pattern(self, value):
4724 def _validate_pattern(self, value):
4718 re.compile('^' + glob2re(value) + '$')
4725 re.compile('^' + glob2re(value) + '$')
4719
4726
4720 @hybrid_property
4727 @hybrid_property
4721 def source_branch_pattern(self):
4728 def source_branch_pattern(self):
4722 return self._branch_pattern or '*'
4729 return self._branch_pattern or '*'
4723
4730
4724 @source_branch_pattern.setter
4731 @source_branch_pattern.setter
4725 def source_branch_pattern(self, value):
4732 def source_branch_pattern(self, value):
4726 self._validate_pattern(value)
4733 self._validate_pattern(value)
4727 self._branch_pattern = value or '*'
4734 self._branch_pattern = value or '*'
4728
4735
4729 @hybrid_property
4736 @hybrid_property
4730 def target_branch_pattern(self):
4737 def target_branch_pattern(self):
4731 return self._target_branch_pattern or '*'
4738 return self._target_branch_pattern or '*'
4732
4739
4733 @target_branch_pattern.setter
4740 @target_branch_pattern.setter
4734 def target_branch_pattern(self, value):
4741 def target_branch_pattern(self, value):
4735 self._validate_pattern(value)
4742 self._validate_pattern(value)
4736 self._target_branch_pattern = value or '*'
4743 self._target_branch_pattern = value or '*'
4737
4744
4738 @hybrid_property
4745 @hybrid_property
4739 def file_pattern(self):
4746 def file_pattern(self):
4740 return self._file_pattern or '*'
4747 return self._file_pattern or '*'
4741
4748
4742 @file_pattern.setter
4749 @file_pattern.setter
4743 def file_pattern(self, value):
4750 def file_pattern(self, value):
4744 self._validate_pattern(value)
4751 self._validate_pattern(value)
4745 self._file_pattern = value or '*'
4752 self._file_pattern = value or '*'
4746
4753
4747 def matches(self, source_branch, target_branch, files_changed):
4754 def matches(self, source_branch, target_branch, files_changed):
4748 """
4755 """
4749 Check if this review rule matches a branch/files in a pull request
4756 Check if this review rule matches a branch/files in a pull request
4750
4757
4751 :param source_branch: source branch name for the commit
4758 :param source_branch: source branch name for the commit
4752 :param target_branch: target branch name for the commit
4759 :param target_branch: target branch name for the commit
4753 :param files_changed: list of file paths changed in the pull request
4760 :param files_changed: list of file paths changed in the pull request
4754 """
4761 """
4755
4762
4756 source_branch = source_branch or ''
4763 source_branch = source_branch or ''
4757 target_branch = target_branch or ''
4764 target_branch = target_branch or ''
4758 files_changed = files_changed or []
4765 files_changed = files_changed or []
4759
4766
4760 branch_matches = True
4767 branch_matches = True
4761 if source_branch or target_branch:
4768 if source_branch or target_branch:
4762 if self.source_branch_pattern == '*':
4769 if self.source_branch_pattern == '*':
4763 source_branch_match = True
4770 source_branch_match = True
4764 else:
4771 else:
4765 if self.source_branch_pattern.startswith('re:'):
4772 if self.source_branch_pattern.startswith('re:'):
4766 source_pattern = self.source_branch_pattern[3:]
4773 source_pattern = self.source_branch_pattern[3:]
4767 else:
4774 else:
4768 source_pattern = '^' + glob2re(self.source_branch_pattern) + '$'
4775 source_pattern = '^' + glob2re(self.source_branch_pattern) + '$'
4769 source_branch_regex = re.compile(source_pattern)
4776 source_branch_regex = re.compile(source_pattern)
4770 source_branch_match = bool(source_branch_regex.search(source_branch))
4777 source_branch_match = bool(source_branch_regex.search(source_branch))
4771 if self.target_branch_pattern == '*':
4778 if self.target_branch_pattern == '*':
4772 target_branch_match = True
4779 target_branch_match = True
4773 else:
4780 else:
4774 if self.target_branch_pattern.startswith('re:'):
4781 if self.target_branch_pattern.startswith('re:'):
4775 target_pattern = self.target_branch_pattern[3:]
4782 target_pattern = self.target_branch_pattern[3:]
4776 else:
4783 else:
4777 target_pattern = '^' + glob2re(self.target_branch_pattern) + '$'
4784 target_pattern = '^' + glob2re(self.target_branch_pattern) + '$'
4778 target_branch_regex = re.compile(target_pattern)
4785 target_branch_regex = re.compile(target_pattern)
4779 target_branch_match = bool(target_branch_regex.search(target_branch))
4786 target_branch_match = bool(target_branch_regex.search(target_branch))
4780
4787
4781 branch_matches = source_branch_match and target_branch_match
4788 branch_matches = source_branch_match and target_branch_match
4782
4789
4783 files_matches = True
4790 files_matches = True
4784 if self.file_pattern != '*':
4791 if self.file_pattern != '*':
4785 files_matches = False
4792 files_matches = False
4786 if self.file_pattern.startswith('re:'):
4793 if self.file_pattern.startswith('re:'):
4787 file_pattern = self.file_pattern[3:]
4794 file_pattern = self.file_pattern[3:]
4788 else:
4795 else:
4789 file_pattern = glob2re(self.file_pattern)
4796 file_pattern = glob2re(self.file_pattern)
4790 file_regex = re.compile(file_pattern)
4797 file_regex = re.compile(file_pattern)
4791 for filename in files_changed:
4798 for filename in files_changed:
4792 if file_regex.search(filename):
4799 if file_regex.search(filename):
4793 files_matches = True
4800 files_matches = True
4794 break
4801 break
4795
4802
4796 return branch_matches and files_matches
4803 return branch_matches and files_matches
4797
4804
4798 @property
4805 @property
4799 def review_users(self):
4806 def review_users(self):
4800 """ Returns the users which this rule applies to """
4807 """ Returns the users which this rule applies to """
4801
4808
4802 users = collections.OrderedDict()
4809 users = collections.OrderedDict()
4803
4810
4804 for rule_user in self.rule_users:
4811 for rule_user in self.rule_users:
4805 if rule_user.user.active:
4812 if rule_user.user.active:
4806 if rule_user.user not in users:
4813 if rule_user.user not in users:
4807 users[rule_user.user.username] = {
4814 users[rule_user.user.username] = {
4808 'user': rule_user.user,
4815 'user': rule_user.user,
4809 'source': 'user',
4816 'source': 'user',
4810 'source_data': {},
4817 'source_data': {},
4811 'data': rule_user.rule_data()
4818 'data': rule_user.rule_data()
4812 }
4819 }
4813
4820
4814 for rule_user_group in self.rule_user_groups:
4821 for rule_user_group in self.rule_user_groups:
4815 source_data = {
4822 source_data = {
4816 'user_group_id': rule_user_group.users_group.users_group_id,
4823 'user_group_id': rule_user_group.users_group.users_group_id,
4817 'name': rule_user_group.users_group.users_group_name,
4824 'name': rule_user_group.users_group.users_group_name,
4818 'members': len(rule_user_group.users_group.members)
4825 'members': len(rule_user_group.users_group.members)
4819 }
4826 }
4820 for member in rule_user_group.users_group.members:
4827 for member in rule_user_group.users_group.members:
4821 if member.user.active:
4828 if member.user.active:
4822 key = member.user.username
4829 key = member.user.username
4823 if key in users:
4830 if key in users:
4824 # skip this member as we have him already
4831 # skip this member as we have him already
4825 # this prevents from override the "first" matched
4832 # this prevents from override the "first" matched
4826 # users with duplicates in multiple groups
4833 # users with duplicates in multiple groups
4827 continue
4834 continue
4828
4835
4829 users[key] = {
4836 users[key] = {
4830 'user': member.user,
4837 'user': member.user,
4831 'source': 'user_group',
4838 'source': 'user_group',
4832 'source_data': source_data,
4839 'source_data': source_data,
4833 'data': rule_user_group.rule_data()
4840 'data': rule_user_group.rule_data()
4834 }
4841 }
4835
4842
4836 return users
4843 return users
4837
4844
4838 def user_group_vote_rule(self, user_id):
4845 def user_group_vote_rule(self, user_id):
4839
4846
4840 rules = []
4847 rules = []
4841 if not self.rule_user_groups:
4848 if not self.rule_user_groups:
4842 return rules
4849 return rules
4843
4850
4844 for user_group in self.rule_user_groups:
4851 for user_group in self.rule_user_groups:
4845 user_group_members = [x.user_id for x in user_group.users_group.members]
4852 user_group_members = [x.user_id for x in user_group.users_group.members]
4846 if user_id in user_group_members:
4853 if user_id in user_group_members:
4847 rules.append(user_group)
4854 rules.append(user_group)
4848 return rules
4855 return rules
4849
4856
4850 def __repr__(self):
4857 def __repr__(self):
4851 return '<RepoReviewerRule(id=%r, repo=%r)>' % (
4858 return '<RepoReviewerRule(id=%r, repo=%r)>' % (
4852 self.repo_review_rule_id, self.repo)
4859 self.repo_review_rule_id, self.repo)
4853
4860
4854
4861
4855 class ScheduleEntry(Base, BaseModel):
4862 class ScheduleEntry(Base, BaseModel):
4856 __tablename__ = 'schedule_entries'
4863 __tablename__ = 'schedule_entries'
4857 __table_args__ = (
4864 __table_args__ = (
4858 UniqueConstraint('schedule_name', name='s_schedule_name_idx'),
4865 UniqueConstraint('schedule_name', name='s_schedule_name_idx'),
4859 UniqueConstraint('task_uid', name='s_task_uid_idx'),
4866 UniqueConstraint('task_uid', name='s_task_uid_idx'),
4860 base_table_args,
4867 base_table_args,
4861 )
4868 )
4862
4869
4863 schedule_types = ['crontab', 'timedelta', 'integer']
4870 schedule_types = ['crontab', 'timedelta', 'integer']
4864 schedule_entry_id = Column('schedule_entry_id', Integer(), primary_key=True)
4871 schedule_entry_id = Column('schedule_entry_id', Integer(), primary_key=True)
4865
4872
4866 schedule_name = Column("schedule_name", String(255), nullable=False, unique=None, default=None)
4873 schedule_name = Column("schedule_name", String(255), nullable=False, unique=None, default=None)
4867 schedule_description = Column("schedule_description", String(10000), nullable=True, unique=None, default=None)
4874 schedule_description = Column("schedule_description", String(10000), nullable=True, unique=None, default=None)
4868 schedule_enabled = Column("schedule_enabled", Boolean(), nullable=False, unique=None, default=True)
4875 schedule_enabled = Column("schedule_enabled", Boolean(), nullable=False, unique=None, default=True)
4869
4876
4870 _schedule_type = Column("schedule_type", String(255), nullable=False, unique=None, default=None)
4877 _schedule_type = Column("schedule_type", String(255), nullable=False, unique=None, default=None)
4871 schedule_definition = Column('schedule_definition_json', MutationObj.as_mutable(JsonType(default=lambda: "", dialect_map=dict(mysql=LONGTEXT()))))
4878 schedule_definition = Column('schedule_definition_json', MutationObj.as_mutable(JsonType(default=lambda: "", dialect_map=dict(mysql=LONGTEXT()))))
4872
4879
4873 schedule_last_run = Column('schedule_last_run', DateTime(timezone=False), nullable=True, unique=None, default=None)
4880 schedule_last_run = Column('schedule_last_run', DateTime(timezone=False), nullable=True, unique=None, default=None)
4874 schedule_total_run_count = Column('schedule_total_run_count', Integer(), nullable=True, unique=None, default=0)
4881 schedule_total_run_count = Column('schedule_total_run_count', Integer(), nullable=True, unique=None, default=0)
4875
4882
4876 # task
4883 # task
4877 task_uid = Column("task_uid", String(255), nullable=False, unique=None, default=None)
4884 task_uid = Column("task_uid", String(255), nullable=False, unique=None, default=None)
4878 task_dot_notation = Column("task_dot_notation", String(4096), nullable=False, unique=None, default=None)
4885 task_dot_notation = Column("task_dot_notation", String(4096), nullable=False, unique=None, default=None)
4879 task_args = Column('task_args_json', MutationObj.as_mutable(JsonType(default=list, dialect_map=dict(mysql=LONGTEXT()))))
4886 task_args = Column('task_args_json', MutationObj.as_mutable(JsonType(default=list, dialect_map=dict(mysql=LONGTEXT()))))
4880 task_kwargs = Column('task_kwargs_json', MutationObj.as_mutable(JsonType(default=dict, dialect_map=dict(mysql=LONGTEXT()))))
4887 task_kwargs = Column('task_kwargs_json', MutationObj.as_mutable(JsonType(default=dict, dialect_map=dict(mysql=LONGTEXT()))))
4881
4888
4882 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4889 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4883 updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=None)
4890 updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=None)
4884
4891
4885 @hybrid_property
4892 @hybrid_property
4886 def schedule_type(self):
4893 def schedule_type(self):
4887 return self._schedule_type
4894 return self._schedule_type
4888
4895
4889 @schedule_type.setter
4896 @schedule_type.setter
4890 def schedule_type(self, val):
4897 def schedule_type(self, val):
4891 if val not in self.schedule_types:
4898 if val not in self.schedule_types:
4892 raise ValueError('Value must be on of `{}` and got `{}`'.format(
4899 raise ValueError('Value must be on of `{}` and got `{}`'.format(
4893 val, self.schedule_type))
4900 val, self.schedule_type))
4894
4901
4895 self._schedule_type = val
4902 self._schedule_type = val
4896
4903
4897 @classmethod
4904 @classmethod
4898 def get_uid(cls, obj):
4905 def get_uid(cls, obj):
4899 args = obj.task_args
4906 args = obj.task_args
4900 kwargs = obj.task_kwargs
4907 kwargs = obj.task_kwargs
4901 if isinstance(args, JsonRaw):
4908 if isinstance(args, JsonRaw):
4902 try:
4909 try:
4903 args = json.loads(args)
4910 args = json.loads(args)
4904 except ValueError:
4911 except ValueError:
4905 args = tuple()
4912 args = tuple()
4906
4913
4907 if isinstance(kwargs, JsonRaw):
4914 if isinstance(kwargs, JsonRaw):
4908 try:
4915 try:
4909 kwargs = json.loads(kwargs)
4916 kwargs = json.loads(kwargs)
4910 except ValueError:
4917 except ValueError:
4911 kwargs = dict()
4918 kwargs = dict()
4912
4919
4913 dot_notation = obj.task_dot_notation
4920 dot_notation = obj.task_dot_notation
4914 val = '.'.join(map(safe_str, [
4921 val = '.'.join(map(safe_str, [
4915 sorted(dot_notation), args, sorted(kwargs.items())]))
4922 sorted(dot_notation), args, sorted(kwargs.items())]))
4916 return hashlib.sha1(val).hexdigest()
4923 return hashlib.sha1(val).hexdigest()
4917
4924
4918 @classmethod
4925 @classmethod
4919 def get_by_schedule_name(cls, schedule_name):
4926 def get_by_schedule_name(cls, schedule_name):
4920 return cls.query().filter(cls.schedule_name == schedule_name).scalar()
4927 return cls.query().filter(cls.schedule_name == schedule_name).scalar()
4921
4928
4922 @classmethod
4929 @classmethod
4923 def get_by_schedule_id(cls, schedule_id):
4930 def get_by_schedule_id(cls, schedule_id):
4924 return cls.query().filter(cls.schedule_entry_id == schedule_id).scalar()
4931 return cls.query().filter(cls.schedule_entry_id == schedule_id).scalar()
4925
4932
4926 @property
4933 @property
4927 def task(self):
4934 def task(self):
4928 return self.task_dot_notation
4935 return self.task_dot_notation
4929
4936
4930 @property
4937 @property
4931 def schedule(self):
4938 def schedule(self):
4932 from rhodecode.lib.celerylib.utils import raw_2_schedule
4939 from rhodecode.lib.celerylib.utils import raw_2_schedule
4933 schedule = raw_2_schedule(self.schedule_definition, self.schedule_type)
4940 schedule = raw_2_schedule(self.schedule_definition, self.schedule_type)
4934 return schedule
4941 return schedule
4935
4942
4936 @property
4943 @property
4937 def args(self):
4944 def args(self):
4938 try:
4945 try:
4939 return list(self.task_args or [])
4946 return list(self.task_args or [])
4940 except ValueError:
4947 except ValueError:
4941 return list()
4948 return list()
4942
4949
4943 @property
4950 @property
4944 def kwargs(self):
4951 def kwargs(self):
4945 try:
4952 try:
4946 return dict(self.task_kwargs or {})
4953 return dict(self.task_kwargs or {})
4947 except ValueError:
4954 except ValueError:
4948 return dict()
4955 return dict()
4949
4956
4950 def _as_raw(self, val):
4957 def _as_raw(self, val):
4951 if hasattr(val, 'de_coerce'):
4958 if hasattr(val, 'de_coerce'):
4952 val = val.de_coerce()
4959 val = val.de_coerce()
4953 if val:
4960 if val:
4954 val = json.dumps(val)
4961 val = json.dumps(val)
4955
4962
4956 return val
4963 return val
4957
4964
4958 @property
4965 @property
4959 def schedule_definition_raw(self):
4966 def schedule_definition_raw(self):
4960 return self._as_raw(self.schedule_definition)
4967 return self._as_raw(self.schedule_definition)
4961
4968
4962 @property
4969 @property
4963 def args_raw(self):
4970 def args_raw(self):
4964 return self._as_raw(self.task_args)
4971 return self._as_raw(self.task_args)
4965
4972
4966 @property
4973 @property
4967 def kwargs_raw(self):
4974 def kwargs_raw(self):
4968 return self._as_raw(self.task_kwargs)
4975 return self._as_raw(self.task_kwargs)
4969
4976
4970 def __repr__(self):
4977 def __repr__(self):
4971 return '<DB:ScheduleEntry({}:{})>'.format(
4978 return '<DB:ScheduleEntry({}:{})>'.format(
4972 self.schedule_entry_id, self.schedule_name)
4979 self.schedule_entry_id, self.schedule_name)
4973
4980
4974
4981
4975 @event.listens_for(ScheduleEntry, 'before_update')
4982 @event.listens_for(ScheduleEntry, 'before_update')
4976 def update_task_uid(mapper, connection, target):
4983 def update_task_uid(mapper, connection, target):
4977 target.task_uid = ScheduleEntry.get_uid(target)
4984 target.task_uid = ScheduleEntry.get_uid(target)
4978
4985
4979
4986
4980 @event.listens_for(ScheduleEntry, 'before_insert')
4987 @event.listens_for(ScheduleEntry, 'before_insert')
4981 def set_task_uid(mapper, connection, target):
4988 def set_task_uid(mapper, connection, target):
4982 target.task_uid = ScheduleEntry.get_uid(target)
4989 target.task_uid = ScheduleEntry.get_uid(target)
4983
4990
4984
4991
4985 class _BaseBranchPerms(BaseModel):
4992 class _BaseBranchPerms(BaseModel):
4986 @classmethod
4993 @classmethod
4987 def compute_hash(cls, value):
4994 def compute_hash(cls, value):
4988 return sha1_safe(value)
4995 return sha1_safe(value)
4989
4996
4990 @hybrid_property
4997 @hybrid_property
4991 def branch_pattern(self):
4998 def branch_pattern(self):
4992 return self._branch_pattern or '*'
4999 return self._branch_pattern or '*'
4993
5000
4994 @hybrid_property
5001 @hybrid_property
4995 def branch_hash(self):
5002 def branch_hash(self):
4996 return self._branch_hash
5003 return self._branch_hash
4997
5004
4998 def _validate_glob(self, value):
5005 def _validate_glob(self, value):
4999 re.compile('^' + glob2re(value) + '$')
5006 re.compile('^' + glob2re(value) + '$')
5000
5007
5001 @branch_pattern.setter
5008 @branch_pattern.setter
5002 def branch_pattern(self, value):
5009 def branch_pattern(self, value):
5003 self._validate_glob(value)
5010 self._validate_glob(value)
5004 self._branch_pattern = value or '*'
5011 self._branch_pattern = value or '*'
5005 # set the Hash when setting the branch pattern
5012 # set the Hash when setting the branch pattern
5006 self._branch_hash = self.compute_hash(self._branch_pattern)
5013 self._branch_hash = self.compute_hash(self._branch_pattern)
5007
5014
5008 def matches(self, branch):
5015 def matches(self, branch):
5009 """
5016 """
5010 Check if this the branch matches entry
5017 Check if this the branch matches entry
5011
5018
5012 :param branch: branch name for the commit
5019 :param branch: branch name for the commit
5013 """
5020 """
5014
5021
5015 branch = branch or ''
5022 branch = branch or ''
5016
5023
5017 branch_matches = True
5024 branch_matches = True
5018 if branch:
5025 if branch:
5019 branch_regex = re.compile('^' + glob2re(self.branch_pattern) + '$')
5026 branch_regex = re.compile('^' + glob2re(self.branch_pattern) + '$')
5020 branch_matches = bool(branch_regex.search(branch))
5027 branch_matches = bool(branch_regex.search(branch))
5021
5028
5022 return branch_matches
5029 return branch_matches
5023
5030
5024
5031
5025 class UserToRepoBranchPermission(Base, _BaseBranchPerms):
5032 class UserToRepoBranchPermission(Base, _BaseBranchPerms):
5026 __tablename__ = 'user_to_repo_branch_permissions'
5033 __tablename__ = 'user_to_repo_branch_permissions'
5027 __table_args__ = (
5034 __table_args__ = (
5028 base_table_args
5035 base_table_args
5029 )
5036 )
5030
5037
5031 branch_rule_id = Column('branch_rule_id', Integer(), primary_key=True)
5038 branch_rule_id = Column('branch_rule_id', Integer(), primary_key=True)
5032
5039
5033 repository_id = Column('repository_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
5040 repository_id = Column('repository_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
5034 repo = relationship('Repository', backref='user_branch_perms')
5041 repo = relationship('Repository', backref='user_branch_perms')
5035
5042
5036 permission_id = Column('permission_id', Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
5043 permission_id = Column('permission_id', Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
5037 permission = relationship('Permission')
5044 permission = relationship('Permission')
5038
5045
5039 rule_to_perm_id = Column('rule_to_perm_id', Integer(), ForeignKey('repo_to_perm.repo_to_perm_id'), nullable=False, unique=None, default=None)
5046 rule_to_perm_id = Column('rule_to_perm_id', Integer(), ForeignKey('repo_to_perm.repo_to_perm_id'), nullable=False, unique=None, default=None)
5040 user_repo_to_perm = relationship('UserRepoToPerm')
5047 user_repo_to_perm = relationship('UserRepoToPerm')
5041
5048
5042 rule_order = Column('rule_order', Integer(), nullable=False)
5049 rule_order = Column('rule_order', Integer(), nullable=False)
5043 _branch_pattern = Column('branch_pattern', UnicodeText().with_variant(UnicodeText(2048), 'mysql'), default=u'*') # glob
5050 _branch_pattern = Column('branch_pattern', UnicodeText().with_variant(UnicodeText(2048), 'mysql'), default=u'*') # glob
5044 _branch_hash = Column('branch_hash', UnicodeText().with_variant(UnicodeText(2048), 'mysql'))
5051 _branch_hash = Column('branch_hash', UnicodeText().with_variant(UnicodeText(2048), 'mysql'))
5045
5052
5046 def __unicode__(self):
5053 def __unicode__(self):
5047 return u'<UserBranchPermission(%s => %r)>' % (
5054 return u'<UserBranchPermission(%s => %r)>' % (
5048 self.user_repo_to_perm, self.branch_pattern)
5055 self.user_repo_to_perm, self.branch_pattern)
5049
5056
5050
5057
5051 class UserGroupToRepoBranchPermission(Base, _BaseBranchPerms):
5058 class UserGroupToRepoBranchPermission(Base, _BaseBranchPerms):
5052 __tablename__ = 'user_group_to_repo_branch_permissions'
5059 __tablename__ = 'user_group_to_repo_branch_permissions'
5053 __table_args__ = (
5060 __table_args__ = (
5054 base_table_args
5061 base_table_args
5055 )
5062 )
5056
5063
5057 branch_rule_id = Column('branch_rule_id', Integer(), primary_key=True)
5064 branch_rule_id = Column('branch_rule_id', Integer(), primary_key=True)
5058
5065
5059 repository_id = Column('repository_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
5066 repository_id = Column('repository_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
5060 repo = relationship('Repository', backref='user_group_branch_perms')
5067 repo = relationship('Repository', backref='user_group_branch_perms')
5061
5068
5062 permission_id = Column('permission_id', Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
5069 permission_id = Column('permission_id', Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
5063 permission = relationship('Permission')
5070 permission = relationship('Permission')
5064
5071
5065 rule_to_perm_id = Column('rule_to_perm_id', Integer(), ForeignKey('users_group_repo_to_perm.users_group_to_perm_id'), nullable=False, unique=None, default=None)
5072 rule_to_perm_id = Column('rule_to_perm_id', Integer(), ForeignKey('users_group_repo_to_perm.users_group_to_perm_id'), nullable=False, unique=None, default=None)
5066 user_group_repo_to_perm = relationship('UserGroupRepoToPerm')
5073 user_group_repo_to_perm = relationship('UserGroupRepoToPerm')
5067
5074
5068 rule_order = Column('rule_order', Integer(), nullable=False)
5075 rule_order = Column('rule_order', Integer(), nullable=False)
5069 _branch_pattern = Column('branch_pattern', UnicodeText().with_variant(UnicodeText(2048), 'mysql'), default=u'*') # glob
5076 _branch_pattern = Column('branch_pattern', UnicodeText().with_variant(UnicodeText(2048), 'mysql'), default=u'*') # glob
5070 _branch_hash = Column('branch_hash', UnicodeText().with_variant(UnicodeText(2048), 'mysql'))
5077 _branch_hash = Column('branch_hash', UnicodeText().with_variant(UnicodeText(2048), 'mysql'))
5071
5078
5072 def __unicode__(self):
5079 def __unicode__(self):
5073 return u'<UserBranchPermission(%s => %r)>' % (
5080 return u'<UserBranchPermission(%s => %r)>' % (
5074 self.user_group_repo_to_perm, self.branch_pattern)
5081 self.user_group_repo_to_perm, self.branch_pattern)
5075
5082
5076
5083
5077 class UserBookmark(Base, BaseModel):
5084 class UserBookmark(Base, BaseModel):
5078 __tablename__ = 'user_bookmarks'
5085 __tablename__ = 'user_bookmarks'
5079 __table_args__ = (
5086 __table_args__ = (
5080 UniqueConstraint('user_id', 'bookmark_repo_id'),
5087 UniqueConstraint('user_id', 'bookmark_repo_id'),
5081 UniqueConstraint('user_id', 'bookmark_repo_group_id'),
5088 UniqueConstraint('user_id', 'bookmark_repo_group_id'),
5082 UniqueConstraint('user_id', 'bookmark_position'),
5089 UniqueConstraint('user_id', 'bookmark_position'),
5083 base_table_args
5090 base_table_args
5084 )
5091 )
5085
5092
5086 user_bookmark_id = Column("user_bookmark_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
5093 user_bookmark_id = Column("user_bookmark_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
5087 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
5094 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
5088 position = Column("bookmark_position", Integer(), nullable=False)
5095 position = Column("bookmark_position", Integer(), nullable=False)
5089 title = Column("bookmark_title", String(255), nullable=True, unique=None, default=None)
5096 title = Column("bookmark_title", String(255), nullable=True, unique=None, default=None)
5090 redirect_url = Column("bookmark_redirect_url", String(10240), nullable=True, unique=None, default=None)
5097 redirect_url = Column("bookmark_redirect_url", String(10240), nullable=True, unique=None, default=None)
5091 created_on = Column("created_on", DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
5098 created_on = Column("created_on", DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
5092
5099
5093 bookmark_repo_id = Column("bookmark_repo_id", Integer(), ForeignKey("repositories.repo_id"), nullable=True, unique=None, default=None)
5100 bookmark_repo_id = Column("bookmark_repo_id", Integer(), ForeignKey("repositories.repo_id"), nullable=True, unique=None, default=None)
5094 bookmark_repo_group_id = Column("bookmark_repo_group_id", Integer(), ForeignKey("groups.group_id"), nullable=True, unique=None, default=None)
5101 bookmark_repo_group_id = Column("bookmark_repo_group_id", Integer(), ForeignKey("groups.group_id"), nullable=True, unique=None, default=None)
5095
5102
5096 user = relationship("User")
5103 user = relationship("User")
5097
5104
5098 repository = relationship("Repository")
5105 repository = relationship("Repository")
5099 repository_group = relationship("RepoGroup")
5106 repository_group = relationship("RepoGroup")
5100
5107
5101 @classmethod
5108 @classmethod
5102 def get_by_position_for_user(cls, position, user_id):
5109 def get_by_position_for_user(cls, position, user_id):
5103 return cls.query() \
5110 return cls.query() \
5104 .filter(UserBookmark.user_id == user_id) \
5111 .filter(UserBookmark.user_id == user_id) \
5105 .filter(UserBookmark.position == position).scalar()
5112 .filter(UserBookmark.position == position).scalar()
5106
5113
5107 @classmethod
5114 @classmethod
5108 def get_bookmarks_for_user(cls, user_id):
5115 def get_bookmarks_for_user(cls, user_id):
5109 return cls.query() \
5116 return cls.query() \
5110 .filter(UserBookmark.user_id == user_id) \
5117 .filter(UserBookmark.user_id == user_id) \
5111 .options(joinedload(UserBookmark.repository)) \
5118 .options(joinedload(UserBookmark.repository)) \
5112 .options(joinedload(UserBookmark.repository_group)) \
5119 .options(joinedload(UserBookmark.repository_group)) \
5113 .order_by(UserBookmark.position.asc()) \
5120 .order_by(UserBookmark.position.asc()) \
5114 .all()
5121 .all()
5115
5122
5116 def __unicode__(self):
5123 def __unicode__(self):
5117 return u'<UserBookmark(%s @ %r)>' % (self.position, self.redirect_url)
5124 return u'<UserBookmark(%s @ %r)>' % (self.position, self.redirect_url)
5118
5125
5119
5126
5120 class FileStore(Base, BaseModel):
5127 class FileStore(Base, BaseModel):
5121 __tablename__ = 'file_store'
5128 __tablename__ = 'file_store'
5122 __table_args__ = (
5129 __table_args__ = (
5123 base_table_args
5130 base_table_args
5124 )
5131 )
5125
5132
5126 file_store_id = Column('file_store_id', Integer(), primary_key=True)
5133 file_store_id = Column('file_store_id', Integer(), primary_key=True)
5127 file_uid = Column('file_uid', String(1024), nullable=False)
5134 file_uid = Column('file_uid', String(1024), nullable=False)
5128 file_display_name = Column('file_display_name', UnicodeText().with_variant(UnicodeText(2048), 'mysql'), nullable=True)
5135 file_display_name = Column('file_display_name', UnicodeText().with_variant(UnicodeText(2048), 'mysql'), nullable=True)
5129 file_description = Column('file_description', UnicodeText().with_variant(UnicodeText(10240), 'mysql'), nullable=True)
5136 file_description = Column('file_description', UnicodeText().with_variant(UnicodeText(10240), 'mysql'), nullable=True)
5130 file_org_name = Column('file_org_name', UnicodeText().with_variant(UnicodeText(10240), 'mysql'), nullable=False)
5137 file_org_name = Column('file_org_name', UnicodeText().with_variant(UnicodeText(10240), 'mysql'), nullable=False)
5131
5138
5132 # sha256 hash
5139 # sha256 hash
5133 file_hash = Column('file_hash', String(512), nullable=False)
5140 file_hash = Column('file_hash', String(512), nullable=False)
5134 file_size = Column('file_size', BigInteger(), nullable=False)
5141 file_size = Column('file_size', BigInteger(), nullable=False)
5135
5142
5136 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
5143 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
5137 accessed_on = Column('accessed_on', DateTime(timezone=False), nullable=True)
5144 accessed_on = Column('accessed_on', DateTime(timezone=False), nullable=True)
5138 accessed_count = Column('accessed_count', Integer(), default=0)
5145 accessed_count = Column('accessed_count', Integer(), default=0)
5139
5146
5140 enabled = Column('enabled', Boolean(), nullable=False, default=True)
5147 enabled = Column('enabled', Boolean(), nullable=False, default=True)
5141
5148
5142 # if repo/repo_group reference is set, check for permissions
5149 # if repo/repo_group reference is set, check for permissions
5143 check_acl = Column('check_acl', Boolean(), nullable=False, default=True)
5150 check_acl = Column('check_acl', Boolean(), nullable=False, default=True)
5144
5151
5145 # hidden defines an attachment that should be hidden from showing in artifact listing
5152 # hidden defines an attachment that should be hidden from showing in artifact listing
5146 hidden = Column('hidden', Boolean(), nullable=False, default=False)
5153 hidden = Column('hidden', Boolean(), nullable=False, default=False)
5147
5154
5148 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
5155 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
5149 upload_user = relationship('User', lazy='joined', primaryjoin='User.user_id==FileStore.user_id')
5156 upload_user = relationship('User', lazy='joined', primaryjoin='User.user_id==FileStore.user_id')
5150
5157
5151 file_metadata = relationship('FileStoreMetadata', lazy='joined')
5158 file_metadata = relationship('FileStoreMetadata', lazy='joined')
5152
5159
5153 # scope limited to user, which requester have access to
5160 # scope limited to user, which requester have access to
5154 scope_user_id = Column(
5161 scope_user_id = Column(
5155 'scope_user_id', Integer(), ForeignKey('users.user_id'),
5162 'scope_user_id', Integer(), ForeignKey('users.user_id'),
5156 nullable=True, unique=None, default=None)
5163 nullable=True, unique=None, default=None)
5157 user = relationship('User', lazy='joined', primaryjoin='User.user_id==FileStore.scope_user_id')
5164 user = relationship('User', lazy='joined', primaryjoin='User.user_id==FileStore.scope_user_id')
5158
5165
5159 # scope limited to user group, which requester have access to
5166 # scope limited to user group, which requester have access to
5160 scope_user_group_id = Column(
5167 scope_user_group_id = Column(
5161 'scope_user_group_id', Integer(), ForeignKey('users_groups.users_group_id'),
5168 'scope_user_group_id', Integer(), ForeignKey('users_groups.users_group_id'),
5162 nullable=True, unique=None, default=None)
5169 nullable=True, unique=None, default=None)
5163 user_group = relationship('UserGroup', lazy='joined')
5170 user_group = relationship('UserGroup', lazy='joined')
5164
5171
5165 # scope limited to repo, which requester have access to
5172 # scope limited to repo, which requester have access to
5166 scope_repo_id = Column(
5173 scope_repo_id = Column(
5167 'scope_repo_id', Integer(), ForeignKey('repositories.repo_id'),
5174 'scope_repo_id', Integer(), ForeignKey('repositories.repo_id'),
5168 nullable=True, unique=None, default=None)
5175 nullable=True, unique=None, default=None)
5169 repo = relationship('Repository', lazy='joined')
5176 repo = relationship('Repository', lazy='joined')
5170
5177
5171 # scope limited to repo group, which requester have access to
5178 # scope limited to repo group, which requester have access to
5172 scope_repo_group_id = Column(
5179 scope_repo_group_id = Column(
5173 'scope_repo_group_id', Integer(), ForeignKey('groups.group_id'),
5180 'scope_repo_group_id', Integer(), ForeignKey('groups.group_id'),
5174 nullable=True, unique=None, default=None)
5181 nullable=True, unique=None, default=None)
5175 repo_group = relationship('RepoGroup', lazy='joined')
5182 repo_group = relationship('RepoGroup', lazy='joined')
5176
5183
5177 @classmethod
5184 @classmethod
5178 def get_by_store_uid(cls, file_store_uid):
5185 def get_by_store_uid(cls, file_store_uid):
5179 return FileStore.query().filter(FileStore.file_uid == file_store_uid).scalar()
5186 return FileStore.query().filter(FileStore.file_uid == file_store_uid).scalar()
5180
5187
5181 @classmethod
5188 @classmethod
5182 def create(cls, file_uid, filename, file_hash, file_size, file_display_name='',
5189 def create(cls, file_uid, filename, file_hash, file_size, file_display_name='',
5183 file_description='', enabled=True, hidden=False, check_acl=True,
5190 file_description='', enabled=True, hidden=False, check_acl=True,
5184 user_id=None, scope_user_id=None, scope_repo_id=None, scope_repo_group_id=None):
5191 user_id=None, scope_user_id=None, scope_repo_id=None, scope_repo_group_id=None):
5185
5192
5186 store_entry = FileStore()
5193 store_entry = FileStore()
5187 store_entry.file_uid = file_uid
5194 store_entry.file_uid = file_uid
5188 store_entry.file_display_name = file_display_name
5195 store_entry.file_display_name = file_display_name
5189 store_entry.file_org_name = filename
5196 store_entry.file_org_name = filename
5190 store_entry.file_size = file_size
5197 store_entry.file_size = file_size
5191 store_entry.file_hash = file_hash
5198 store_entry.file_hash = file_hash
5192 store_entry.file_description = file_description
5199 store_entry.file_description = file_description
5193
5200
5194 store_entry.check_acl = check_acl
5201 store_entry.check_acl = check_acl
5195 store_entry.enabled = enabled
5202 store_entry.enabled = enabled
5196 store_entry.hidden = hidden
5203 store_entry.hidden = hidden
5197
5204
5198 store_entry.user_id = user_id
5205 store_entry.user_id = user_id
5199 store_entry.scope_user_id = scope_user_id
5206 store_entry.scope_user_id = scope_user_id
5200 store_entry.scope_repo_id = scope_repo_id
5207 store_entry.scope_repo_id = scope_repo_id
5201 store_entry.scope_repo_group_id = scope_repo_group_id
5208 store_entry.scope_repo_group_id = scope_repo_group_id
5202
5209
5203 return store_entry
5210 return store_entry
5204
5211
5205 @classmethod
5212 @classmethod
5206 def store_metadata(cls, file_store_id, args, commit=True):
5213 def store_metadata(cls, file_store_id, args, commit=True):
5207 file_store = FileStore.get(file_store_id)
5214 file_store = FileStore.get(file_store_id)
5208 if file_store is None:
5215 if file_store is None:
5209 return
5216 return
5210
5217
5211 for section, key, value, value_type in args:
5218 for section, key, value, value_type in args:
5212 has_key = FileStoreMetadata().query() \
5219 has_key = FileStoreMetadata().query() \
5213 .filter(FileStoreMetadata.file_store_id == file_store.file_store_id) \
5220 .filter(FileStoreMetadata.file_store_id == file_store.file_store_id) \
5214 .filter(FileStoreMetadata.file_store_meta_section == section) \
5221 .filter(FileStoreMetadata.file_store_meta_section == section) \
5215 .filter(FileStoreMetadata.file_store_meta_key == key) \
5222 .filter(FileStoreMetadata.file_store_meta_key == key) \
5216 .scalar()
5223 .scalar()
5217 if has_key:
5224 if has_key:
5218 msg = 'key `{}` already defined under section `{}` for this file.'\
5225 msg = 'key `{}` already defined under section `{}` for this file.'\
5219 .format(key, section)
5226 .format(key, section)
5220 raise ArtifactMetadataDuplicate(msg, err_section=section, err_key=key)
5227 raise ArtifactMetadataDuplicate(msg, err_section=section, err_key=key)
5221
5228
5222 # NOTE(marcink): raises ArtifactMetadataBadValueType
5229 # NOTE(marcink): raises ArtifactMetadataBadValueType
5223 FileStoreMetadata.valid_value_type(value_type)
5230 FileStoreMetadata.valid_value_type(value_type)
5224
5231
5225 meta_entry = FileStoreMetadata()
5232 meta_entry = FileStoreMetadata()
5226 meta_entry.file_store = file_store
5233 meta_entry.file_store = file_store
5227 meta_entry.file_store_meta_section = section
5234 meta_entry.file_store_meta_section = section
5228 meta_entry.file_store_meta_key = key
5235 meta_entry.file_store_meta_key = key
5229 meta_entry.file_store_meta_value_type = value_type
5236 meta_entry.file_store_meta_value_type = value_type
5230 meta_entry.file_store_meta_value = value
5237 meta_entry.file_store_meta_value = value
5231
5238
5232 Session().add(meta_entry)
5239 Session().add(meta_entry)
5233
5240
5234 try:
5241 try:
5235 if commit:
5242 if commit:
5236 Session().commit()
5243 Session().commit()
5237 except IntegrityError:
5244 except IntegrityError:
5238 Session().rollback()
5245 Session().rollback()
5239 raise ArtifactMetadataDuplicate('Duplicate section/key found for this file.')
5246 raise ArtifactMetadataDuplicate('Duplicate section/key found for this file.')
5240
5247
5241 @classmethod
5248 @classmethod
5242 def bump_access_counter(cls, file_uid, commit=True):
5249 def bump_access_counter(cls, file_uid, commit=True):
5243 FileStore().query()\
5250 FileStore().query()\
5244 .filter(FileStore.file_uid == file_uid)\
5251 .filter(FileStore.file_uid == file_uid)\
5245 .update({FileStore.accessed_count: (FileStore.accessed_count + 1),
5252 .update({FileStore.accessed_count: (FileStore.accessed_count + 1),
5246 FileStore.accessed_on: datetime.datetime.now()})
5253 FileStore.accessed_on: datetime.datetime.now()})
5247 if commit:
5254 if commit:
5248 Session().commit()
5255 Session().commit()
5249
5256
5250 def __json__(self):
5257 def __json__(self):
5251 data = {
5258 data = {
5252 'filename': self.file_display_name,
5259 'filename': self.file_display_name,
5253 'filename_org': self.file_org_name,
5260 'filename_org': self.file_org_name,
5254 'file_uid': self.file_uid,
5261 'file_uid': self.file_uid,
5255 'description': self.file_description,
5262 'description': self.file_description,
5256 'hidden': self.hidden,
5263 'hidden': self.hidden,
5257 'size': self.file_size,
5264 'size': self.file_size,
5258 'created_on': self.created_on,
5265 'created_on': self.created_on,
5259 'uploaded_by': self.upload_user.get_api_data(details='basic'),
5266 'uploaded_by': self.upload_user.get_api_data(details='basic'),
5260 'downloaded_times': self.accessed_count,
5267 'downloaded_times': self.accessed_count,
5261 'sha256': self.file_hash,
5268 'sha256': self.file_hash,
5262 'metadata': self.file_metadata,
5269 'metadata': self.file_metadata,
5263 }
5270 }
5264
5271
5265 return data
5272 return data
5266
5273
5267 def __repr__(self):
5274 def __repr__(self):
5268 return '<FileStore({})>'.format(self.file_store_id)
5275 return '<FileStore({})>'.format(self.file_store_id)
5269
5276
5270
5277
5271 class FileStoreMetadata(Base, BaseModel):
5278 class FileStoreMetadata(Base, BaseModel):
5272 __tablename__ = 'file_store_metadata'
5279 __tablename__ = 'file_store_metadata'
5273 __table_args__ = (
5280 __table_args__ = (
5274 UniqueConstraint('file_store_id', 'file_store_meta_section_hash', 'file_store_meta_key_hash'),
5281 UniqueConstraint('file_store_id', 'file_store_meta_section_hash', 'file_store_meta_key_hash'),
5275 Index('file_store_meta_section_idx', 'file_store_meta_section', mysql_length=255),
5282 Index('file_store_meta_section_idx', 'file_store_meta_section', mysql_length=255),
5276 Index('file_store_meta_key_idx', 'file_store_meta_key', mysql_length=255),
5283 Index('file_store_meta_key_idx', 'file_store_meta_key', mysql_length=255),
5277 base_table_args
5284 base_table_args
5278 )
5285 )
5279 SETTINGS_TYPES = {
5286 SETTINGS_TYPES = {
5280 'str': safe_str,
5287 'str': safe_str,
5281 'int': safe_int,
5288 'int': safe_int,
5282 'unicode': safe_unicode,
5289 'unicode': safe_unicode,
5283 'bool': str2bool,
5290 'bool': str2bool,
5284 'list': functools.partial(aslist, sep=',')
5291 'list': functools.partial(aslist, sep=',')
5285 }
5292 }
5286
5293
5287 file_store_meta_id = Column(
5294 file_store_meta_id = Column(
5288 "file_store_meta_id", Integer(), nullable=False, unique=True, default=None,
5295 "file_store_meta_id", Integer(), nullable=False, unique=True, default=None,
5289 primary_key=True)
5296 primary_key=True)
5290 _file_store_meta_section = Column(
5297 _file_store_meta_section = Column(
5291 "file_store_meta_section", UnicodeText().with_variant(UnicodeText(1024), 'mysql'),
5298 "file_store_meta_section", UnicodeText().with_variant(UnicodeText(1024), 'mysql'),
5292 nullable=True, unique=None, default=None)
5299 nullable=True, unique=None, default=None)
5293 _file_store_meta_section_hash = Column(
5300 _file_store_meta_section_hash = Column(
5294 "file_store_meta_section_hash", String(255),
5301 "file_store_meta_section_hash", String(255),
5295 nullable=True, unique=None, default=None)
5302 nullable=True, unique=None, default=None)
5296 _file_store_meta_key = Column(
5303 _file_store_meta_key = Column(
5297 "file_store_meta_key", UnicodeText().with_variant(UnicodeText(1024), 'mysql'),
5304 "file_store_meta_key", UnicodeText().with_variant(UnicodeText(1024), 'mysql'),
5298 nullable=True, unique=None, default=None)
5305 nullable=True, unique=None, default=None)
5299 _file_store_meta_key_hash = Column(
5306 _file_store_meta_key_hash = Column(
5300 "file_store_meta_key_hash", String(255), nullable=True, unique=None, default=None)
5307 "file_store_meta_key_hash", String(255), nullable=True, unique=None, default=None)
5301 _file_store_meta_value = Column(
5308 _file_store_meta_value = Column(
5302 "file_store_meta_value", UnicodeText().with_variant(UnicodeText(20480), 'mysql'),
5309 "file_store_meta_value", UnicodeText().with_variant(UnicodeText(20480), 'mysql'),
5303 nullable=True, unique=None, default=None)
5310 nullable=True, unique=None, default=None)
5304 _file_store_meta_value_type = Column(
5311 _file_store_meta_value_type = Column(
5305 "file_store_meta_value_type", String(255), nullable=True, unique=None,
5312 "file_store_meta_value_type", String(255), nullable=True, unique=None,
5306 default='unicode')
5313 default='unicode')
5307
5314
5308 file_store_id = Column(
5315 file_store_id = Column(
5309 'file_store_id', Integer(), ForeignKey('file_store.file_store_id'),
5316 'file_store_id', Integer(), ForeignKey('file_store.file_store_id'),
5310 nullable=True, unique=None, default=None)
5317 nullable=True, unique=None, default=None)
5311
5318
5312 file_store = relationship('FileStore', lazy='joined')
5319 file_store = relationship('FileStore', lazy='joined')
5313
5320
5314 @classmethod
5321 @classmethod
5315 def valid_value_type(cls, value):
5322 def valid_value_type(cls, value):
5316 if value.split('.')[0] not in cls.SETTINGS_TYPES:
5323 if value.split('.')[0] not in cls.SETTINGS_TYPES:
5317 raise ArtifactMetadataBadValueType(
5324 raise ArtifactMetadataBadValueType(
5318 'value_type must be one of %s got %s' % (cls.SETTINGS_TYPES.keys(), value))
5325 'value_type must be one of %s got %s' % (cls.SETTINGS_TYPES.keys(), value))
5319
5326
5320 @hybrid_property
5327 @hybrid_property
5321 def file_store_meta_section(self):
5328 def file_store_meta_section(self):
5322 return self._file_store_meta_section
5329 return self._file_store_meta_section
5323
5330
5324 @file_store_meta_section.setter
5331 @file_store_meta_section.setter
5325 def file_store_meta_section(self, value):
5332 def file_store_meta_section(self, value):
5326 self._file_store_meta_section = value
5333 self._file_store_meta_section = value
5327 self._file_store_meta_section_hash = _hash_key(value)
5334 self._file_store_meta_section_hash = _hash_key(value)
5328
5335
5329 @hybrid_property
5336 @hybrid_property
5330 def file_store_meta_key(self):
5337 def file_store_meta_key(self):
5331 return self._file_store_meta_key
5338 return self._file_store_meta_key
5332
5339
5333 @file_store_meta_key.setter
5340 @file_store_meta_key.setter
5334 def file_store_meta_key(self, value):
5341 def file_store_meta_key(self, value):
5335 self._file_store_meta_key = value
5342 self._file_store_meta_key = value
5336 self._file_store_meta_key_hash = _hash_key(value)
5343 self._file_store_meta_key_hash = _hash_key(value)
5337
5344
5338 @hybrid_property
5345 @hybrid_property
5339 def file_store_meta_value(self):
5346 def file_store_meta_value(self):
5340 val = self._file_store_meta_value
5347 val = self._file_store_meta_value
5341
5348
5342 if self._file_store_meta_value_type:
5349 if self._file_store_meta_value_type:
5343 # e.g unicode.encrypted == unicode
5350 # e.g unicode.encrypted == unicode
5344 _type = self._file_store_meta_value_type.split('.')[0]
5351 _type = self._file_store_meta_value_type.split('.')[0]
5345 # decode the encrypted value if it's encrypted field type
5352 # decode the encrypted value if it's encrypted field type
5346 if '.encrypted' in self._file_store_meta_value_type:
5353 if '.encrypted' in self._file_store_meta_value_type:
5347 cipher = EncryptedTextValue()
5354 cipher = EncryptedTextValue()
5348 val = safe_unicode(cipher.process_result_value(val, None))
5355 val = safe_unicode(cipher.process_result_value(val, None))
5349 # do final type conversion
5356 # do final type conversion
5350 converter = self.SETTINGS_TYPES.get(_type) or self.SETTINGS_TYPES['unicode']
5357 converter = self.SETTINGS_TYPES.get(_type) or self.SETTINGS_TYPES['unicode']
5351 val = converter(val)
5358 val = converter(val)
5352
5359
5353 return val
5360 return val
5354
5361
5355 @file_store_meta_value.setter
5362 @file_store_meta_value.setter
5356 def file_store_meta_value(self, val):
5363 def file_store_meta_value(self, val):
5357 val = safe_unicode(val)
5364 val = safe_unicode(val)
5358 # encode the encrypted value
5365 # encode the encrypted value
5359 if '.encrypted' in self.file_store_meta_value_type:
5366 if '.encrypted' in self.file_store_meta_value_type:
5360 cipher = EncryptedTextValue()
5367 cipher = EncryptedTextValue()
5361 val = safe_unicode(cipher.process_bind_param(val, None))
5368 val = safe_unicode(cipher.process_bind_param(val, None))
5362 self._file_store_meta_value = val
5369 self._file_store_meta_value = val
5363
5370
5364 @hybrid_property
5371 @hybrid_property
5365 def file_store_meta_value_type(self):
5372 def file_store_meta_value_type(self):
5366 return self._file_store_meta_value_type
5373 return self._file_store_meta_value_type
5367
5374
5368 @file_store_meta_value_type.setter
5375 @file_store_meta_value_type.setter
5369 def file_store_meta_value_type(self, val):
5376 def file_store_meta_value_type(self, val):
5370 # e.g unicode.encrypted
5377 # e.g unicode.encrypted
5371 self.valid_value_type(val)
5378 self.valid_value_type(val)
5372 self._file_store_meta_value_type = val
5379 self._file_store_meta_value_type = val
5373
5380
5374 def __json__(self):
5381 def __json__(self):
5375 data = {
5382 data = {
5376 'artifact': self.file_store.file_uid,
5383 'artifact': self.file_store.file_uid,
5377 'section': self.file_store_meta_section,
5384 'section': self.file_store_meta_section,
5378 'key': self.file_store_meta_key,
5385 'key': self.file_store_meta_key,
5379 'value': self.file_store_meta_value,
5386 'value': self.file_store_meta_value,
5380 }
5387 }
5381
5388
5382 return data
5389 return data
5383
5390
5384 def __repr__(self):
5391 def __repr__(self):
5385 return '<%s[%s]%s=>%s]>' % (self.__class__.__name__, self.file_store_meta_section,
5392 return '<%s[%s]%s=>%s]>' % (self.__class__.__name__, self.file_store_meta_section,
5386 self.file_store_meta_key, self.file_store_meta_value)
5393 self.file_store_meta_key, self.file_store_meta_value)
5387
5394
5388
5395
5389 class DbMigrateVersion(Base, BaseModel):
5396 class DbMigrateVersion(Base, BaseModel):
5390 __tablename__ = 'db_migrate_version'
5397 __tablename__ = 'db_migrate_version'
5391 __table_args__ = (
5398 __table_args__ = (
5392 base_table_args,
5399 base_table_args,
5393 )
5400 )
5394
5401
5395 repository_id = Column('repository_id', String(250), primary_key=True)
5402 repository_id = Column('repository_id', String(250), primary_key=True)
5396 repository_path = Column('repository_path', Text)
5403 repository_path = Column('repository_path', Text)
5397 version = Column('version', Integer)
5404 version = Column('version', Integer)
5398
5405
5399 @classmethod
5406 @classmethod
5400 def set_version(cls, version):
5407 def set_version(cls, version):
5401 """
5408 """
5402 Helper for forcing a different version, usually for debugging purposes via ishell.
5409 Helper for forcing a different version, usually for debugging purposes via ishell.
5403 """
5410 """
5404 ver = DbMigrateVersion.query().first()
5411 ver = DbMigrateVersion.query().first()
5405 ver.version = version
5412 ver.version = version
5406 Session().commit()
5413 Session().commit()
5407
5414
5408
5415
5409 class DbSession(Base, BaseModel):
5416 class DbSession(Base, BaseModel):
5410 __tablename__ = 'db_session'
5417 __tablename__ = 'db_session'
5411 __table_args__ = (
5418 __table_args__ = (
5412 base_table_args,
5419 base_table_args,
5413 )
5420 )
5414
5421
5415 def __repr__(self):
5422 def __repr__(self):
5416 return '<DB:DbSession({})>'.format(self.id)
5423 return '<DB:DbSession({})>'.format(self.id)
5417
5424
5418 id = Column('id', Integer())
5425 id = Column('id', Integer())
5419 namespace = Column('namespace', String(255), primary_key=True)
5426 namespace = Column('namespace', String(255), primary_key=True)
5420 accessed = Column('accessed', DateTime, nullable=False)
5427 accessed = Column('accessed', DateTime, nullable=False)
5421 created = Column('created', DateTime, nullable=False)
5428 created = Column('created', DateTime, nullable=False)
5422 data = Column('data', PickleType, nullable=False)
5429 data = Column('data', PickleType, nullable=False)
@@ -1,945 +1,979 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2019 RhodeCode GmbH
3 # Copyright (C) 2010-2019 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 users model for RhodeCode
22 users model for RhodeCode
23 """
23 """
24
24
25 import logging
25 import logging
26 import traceback
26 import traceback
27 import datetime
27 import datetime
28 import ipaddress
28 import ipaddress
29
29
30 from pyramid.threadlocal import get_current_request
30 from pyramid.threadlocal import get_current_request
31 from sqlalchemy.exc import DatabaseError
31 from sqlalchemy.exc import DatabaseError
32
32
33 from rhodecode import events
33 from rhodecode import events
34 from rhodecode.lib.user_log_filter import user_log_filter
34 from rhodecode.lib.user_log_filter import user_log_filter
35 from rhodecode.lib.utils2 import (
35 from rhodecode.lib.utils2 import (
36 safe_unicode, get_current_rhodecode_user, action_logger_generic,
36 safe_unicode, get_current_rhodecode_user, action_logger_generic,
37 AttributeDict, str2bool)
37 AttributeDict, str2bool)
38 from rhodecode.lib.exceptions import (
38 from rhodecode.lib.exceptions import (
39 DefaultUserException, UserOwnsReposException, UserOwnsRepoGroupsException,
39 DefaultUserException, UserOwnsReposException, UserOwnsRepoGroupsException,
40 UserOwnsUserGroupsException, NotAllowedToCreateUserError)
40 UserOwnsUserGroupsException, NotAllowedToCreateUserError, UserOwnsArtifactsException)
41 from rhodecode.lib.caching_query import FromCache
41 from rhodecode.lib.caching_query import FromCache
42 from rhodecode.model import BaseModel
42 from rhodecode.model import BaseModel
43 from rhodecode.model.auth_token import AuthTokenModel
43 from rhodecode.model.auth_token import AuthTokenModel
44 from rhodecode.model.db import (
44 from rhodecode.model.db import (
45 _hash_key, true, false, or_, joinedload, User, UserToPerm,
45 _hash_key, true, false, or_, joinedload, User, UserToPerm,
46 UserEmailMap, UserIpMap, UserLog)
46 UserEmailMap, UserIpMap, UserLog)
47 from rhodecode.model.meta import Session
47 from rhodecode.model.meta import Session
48 from rhodecode.model.repo_group import RepoGroupModel
48 from rhodecode.model.repo_group import RepoGroupModel
49
49
50
50
51 log = logging.getLogger(__name__)
51 log = logging.getLogger(__name__)
52
52
53
53
54 class UserModel(BaseModel):
54 class UserModel(BaseModel):
55 cls = User
55 cls = User
56
56
57 def get(self, user_id, cache=False):
57 def get(self, user_id, cache=False):
58 user = self.sa.query(User)
58 user = self.sa.query(User)
59 if cache:
59 if cache:
60 user = user.options(
60 user = user.options(
61 FromCache("sql_cache_short", "get_user_%s" % user_id))
61 FromCache("sql_cache_short", "get_user_%s" % user_id))
62 return user.get(user_id)
62 return user.get(user_id)
63
63
64 def get_user(self, user):
64 def get_user(self, user):
65 return self._get_user(user)
65 return self._get_user(user)
66
66
67 def _serialize_user(self, user):
67 def _serialize_user(self, user):
68 import rhodecode.lib.helpers as h
68 import rhodecode.lib.helpers as h
69
69
70 return {
70 return {
71 'id': user.user_id,
71 'id': user.user_id,
72 'first_name': user.first_name,
72 'first_name': user.first_name,
73 'last_name': user.last_name,
73 'last_name': user.last_name,
74 'username': user.username,
74 'username': user.username,
75 'email': user.email,
75 'email': user.email,
76 'icon_link': h.gravatar_url(user.email, 30),
76 'icon_link': h.gravatar_url(user.email, 30),
77 'profile_link': h.link_to_user(user),
77 'profile_link': h.link_to_user(user),
78 'value_display': h.escape(h.person(user)),
78 'value_display': h.escape(h.person(user)),
79 'value': user.username,
79 'value': user.username,
80 'value_type': 'user',
80 'value_type': 'user',
81 'active': user.active,
81 'active': user.active,
82 }
82 }
83
83
84 def get_users(self, name_contains=None, limit=20, only_active=True):
84 def get_users(self, name_contains=None, limit=20, only_active=True):
85
85
86 query = self.sa.query(User)
86 query = self.sa.query(User)
87 if only_active:
87 if only_active:
88 query = query.filter(User.active == true())
88 query = query.filter(User.active == true())
89
89
90 if name_contains:
90 if name_contains:
91 ilike_expression = u'%{}%'.format(safe_unicode(name_contains))
91 ilike_expression = u'%{}%'.format(safe_unicode(name_contains))
92 query = query.filter(
92 query = query.filter(
93 or_(
93 or_(
94 User.name.ilike(ilike_expression),
94 User.name.ilike(ilike_expression),
95 User.lastname.ilike(ilike_expression),
95 User.lastname.ilike(ilike_expression),
96 User.username.ilike(ilike_expression)
96 User.username.ilike(ilike_expression)
97 )
97 )
98 )
98 )
99 query = query.limit(limit)
99 query = query.limit(limit)
100 users = query.all()
100 users = query.all()
101
101
102 _users = [
102 _users = [
103 self._serialize_user(user) for user in users
103 self._serialize_user(user) for user in users
104 ]
104 ]
105 return _users
105 return _users
106
106
107 def get_by_username(self, username, cache=False, case_insensitive=False):
107 def get_by_username(self, username, cache=False, case_insensitive=False):
108
108
109 if case_insensitive:
109 if case_insensitive:
110 user = self.sa.query(User).filter(User.username.ilike(username))
110 user = self.sa.query(User).filter(User.username.ilike(username))
111 else:
111 else:
112 user = self.sa.query(User)\
112 user = self.sa.query(User)\
113 .filter(User.username == username)
113 .filter(User.username == username)
114 if cache:
114 if cache:
115 name_key = _hash_key(username)
115 name_key = _hash_key(username)
116 user = user.options(
116 user = user.options(
117 FromCache("sql_cache_short", "get_user_%s" % name_key))
117 FromCache("sql_cache_short", "get_user_%s" % name_key))
118 return user.scalar()
118 return user.scalar()
119
119
120 def get_by_email(self, email, cache=False, case_insensitive=False):
120 def get_by_email(self, email, cache=False, case_insensitive=False):
121 return User.get_by_email(email, case_insensitive, cache)
121 return User.get_by_email(email, case_insensitive, cache)
122
122
123 def get_by_auth_token(self, auth_token, cache=False):
123 def get_by_auth_token(self, auth_token, cache=False):
124 return User.get_by_auth_token(auth_token, cache)
124 return User.get_by_auth_token(auth_token, cache)
125
125
126 def get_active_user_count(self, cache=False):
126 def get_active_user_count(self, cache=False):
127 qry = User.query().filter(
127 qry = User.query().filter(
128 User.active == true()).filter(
128 User.active == true()).filter(
129 User.username != User.DEFAULT_USER)
129 User.username != User.DEFAULT_USER)
130 if cache:
130 if cache:
131 qry = qry.options(
131 qry = qry.options(
132 FromCache("sql_cache_short", "get_active_users"))
132 FromCache("sql_cache_short", "get_active_users"))
133 return qry.count()
133 return qry.count()
134
134
135 def create(self, form_data, cur_user=None):
135 def create(self, form_data, cur_user=None):
136 if not cur_user:
136 if not cur_user:
137 cur_user = getattr(get_current_rhodecode_user(), 'username', None)
137 cur_user = getattr(get_current_rhodecode_user(), 'username', None)
138
138
139 user_data = {
139 user_data = {
140 'username': form_data['username'],
140 'username': form_data['username'],
141 'password': form_data['password'],
141 'password': form_data['password'],
142 'email': form_data['email'],
142 'email': form_data['email'],
143 'firstname': form_data['firstname'],
143 'firstname': form_data['firstname'],
144 'lastname': form_data['lastname'],
144 'lastname': form_data['lastname'],
145 'active': form_data['active'],
145 'active': form_data['active'],
146 'extern_type': form_data['extern_type'],
146 'extern_type': form_data['extern_type'],
147 'extern_name': form_data['extern_name'],
147 'extern_name': form_data['extern_name'],
148 'admin': False,
148 'admin': False,
149 'cur_user': cur_user
149 'cur_user': cur_user
150 }
150 }
151
151
152 if 'create_repo_group' in form_data:
152 if 'create_repo_group' in form_data:
153 user_data['create_repo_group'] = str2bool(
153 user_data['create_repo_group'] = str2bool(
154 form_data.get('create_repo_group'))
154 form_data.get('create_repo_group'))
155
155
156 try:
156 try:
157 if form_data.get('password_change'):
157 if form_data.get('password_change'):
158 user_data['force_password_change'] = True
158 user_data['force_password_change'] = True
159 return UserModel().create_or_update(**user_data)
159 return UserModel().create_or_update(**user_data)
160 except Exception:
160 except Exception:
161 log.error(traceback.format_exc())
161 log.error(traceback.format_exc())
162 raise
162 raise
163
163
164 def update_user(self, user, skip_attrs=None, **kwargs):
164 def update_user(self, user, skip_attrs=None, **kwargs):
165 from rhodecode.lib.auth import get_crypt_password
165 from rhodecode.lib.auth import get_crypt_password
166
166
167 user = self._get_user(user)
167 user = self._get_user(user)
168 if user.username == User.DEFAULT_USER:
168 if user.username == User.DEFAULT_USER:
169 raise DefaultUserException(
169 raise DefaultUserException(
170 "You can't edit this user (`%(username)s`) since it's "
170 "You can't edit this user (`%(username)s`) since it's "
171 "crucial for entire application" % {
171 "crucial for entire application" % {
172 'username': user.username})
172 'username': user.username})
173
173
174 # first store only defaults
174 # first store only defaults
175 user_attrs = {
175 user_attrs = {
176 'updating_user_id': user.user_id,
176 'updating_user_id': user.user_id,
177 'username': user.username,
177 'username': user.username,
178 'password': user.password,
178 'password': user.password,
179 'email': user.email,
179 'email': user.email,
180 'firstname': user.name,
180 'firstname': user.name,
181 'lastname': user.lastname,
181 'lastname': user.lastname,
182 'active': user.active,
182 'active': user.active,
183 'admin': user.admin,
183 'admin': user.admin,
184 'extern_name': user.extern_name,
184 'extern_name': user.extern_name,
185 'extern_type': user.extern_type,
185 'extern_type': user.extern_type,
186 'language': user.user_data.get('language')
186 'language': user.user_data.get('language')
187 }
187 }
188
188
189 # in case there's new_password, that comes from form, use it to
189 # in case there's new_password, that comes from form, use it to
190 # store password
190 # store password
191 if kwargs.get('new_password'):
191 if kwargs.get('new_password'):
192 kwargs['password'] = kwargs['new_password']
192 kwargs['password'] = kwargs['new_password']
193
193
194 # cleanups, my_account password change form
194 # cleanups, my_account password change form
195 kwargs.pop('current_password', None)
195 kwargs.pop('current_password', None)
196 kwargs.pop('new_password', None)
196 kwargs.pop('new_password', None)
197
197
198 # cleanups, user edit password change form
198 # cleanups, user edit password change form
199 kwargs.pop('password_confirmation', None)
199 kwargs.pop('password_confirmation', None)
200 kwargs.pop('password_change', None)
200 kwargs.pop('password_change', None)
201
201
202 # create repo group on user creation
202 # create repo group on user creation
203 kwargs.pop('create_repo_group', None)
203 kwargs.pop('create_repo_group', None)
204
204
205 # legacy forms send name, which is the firstname
205 # legacy forms send name, which is the firstname
206 firstname = kwargs.pop('name', None)
206 firstname = kwargs.pop('name', None)
207 if firstname:
207 if firstname:
208 kwargs['firstname'] = firstname
208 kwargs['firstname'] = firstname
209
209
210 for k, v in kwargs.items():
210 for k, v in kwargs.items():
211 # skip if we don't want to update this
211 # skip if we don't want to update this
212 if skip_attrs and k in skip_attrs:
212 if skip_attrs and k in skip_attrs:
213 continue
213 continue
214
214
215 user_attrs[k] = v
215 user_attrs[k] = v
216
216
217 try:
217 try:
218 return self.create_or_update(**user_attrs)
218 return self.create_or_update(**user_attrs)
219 except Exception:
219 except Exception:
220 log.error(traceback.format_exc())
220 log.error(traceback.format_exc())
221 raise
221 raise
222
222
223 def create_or_update(
223 def create_or_update(
224 self, username, password, email, firstname='', lastname='',
224 self, username, password, email, firstname='', lastname='',
225 active=True, admin=False, extern_type=None, extern_name=None,
225 active=True, admin=False, extern_type=None, extern_name=None,
226 cur_user=None, plugin=None, force_password_change=False,
226 cur_user=None, plugin=None, force_password_change=False,
227 allow_to_create_user=True, create_repo_group=None,
227 allow_to_create_user=True, create_repo_group=None,
228 updating_user_id=None, language=None, strict_creation_check=True):
228 updating_user_id=None, language=None, strict_creation_check=True):
229 """
229 """
230 Creates a new instance if not found, or updates current one
230 Creates a new instance if not found, or updates current one
231
231
232 :param username:
232 :param username:
233 :param password:
233 :param password:
234 :param email:
234 :param email:
235 :param firstname:
235 :param firstname:
236 :param lastname:
236 :param lastname:
237 :param active:
237 :param active:
238 :param admin:
238 :param admin:
239 :param extern_type:
239 :param extern_type:
240 :param extern_name:
240 :param extern_name:
241 :param cur_user:
241 :param cur_user:
242 :param plugin: optional plugin this method was called from
242 :param plugin: optional plugin this method was called from
243 :param force_password_change: toggles new or existing user flag
243 :param force_password_change: toggles new or existing user flag
244 for password change
244 for password change
245 :param allow_to_create_user: Defines if the method can actually create
245 :param allow_to_create_user: Defines if the method can actually create
246 new users
246 new users
247 :param create_repo_group: Defines if the method should also
247 :param create_repo_group: Defines if the method should also
248 create an repo group with user name, and owner
248 create an repo group with user name, and owner
249 :param updating_user_id: if we set it up this is the user we want to
249 :param updating_user_id: if we set it up this is the user we want to
250 update this allows to editing username.
250 update this allows to editing username.
251 :param language: language of user from interface.
251 :param language: language of user from interface.
252
252
253 :returns: new User object with injected `is_new_user` attribute.
253 :returns: new User object with injected `is_new_user` attribute.
254 """
254 """
255
255
256 if not cur_user:
256 if not cur_user:
257 cur_user = getattr(get_current_rhodecode_user(), 'username', None)
257 cur_user = getattr(get_current_rhodecode_user(), 'username', None)
258
258
259 from rhodecode.lib.auth import (
259 from rhodecode.lib.auth import (
260 get_crypt_password, check_password, generate_auth_token)
260 get_crypt_password, check_password, generate_auth_token)
261 from rhodecode.lib.hooks_base import (
261 from rhodecode.lib.hooks_base import (
262 log_create_user, check_allowed_create_user)
262 log_create_user, check_allowed_create_user)
263
263
264 def _password_change(new_user, password):
264 def _password_change(new_user, password):
265 old_password = new_user.password or ''
265 old_password = new_user.password or ''
266 # empty password
266 # empty password
267 if not old_password:
267 if not old_password:
268 return False
268 return False
269
269
270 # password check is only needed for RhodeCode internal auth calls
270 # password check is only needed for RhodeCode internal auth calls
271 # in case it's a plugin we don't care
271 # in case it's a plugin we don't care
272 if not plugin:
272 if not plugin:
273
273
274 # first check if we gave crypted password back, and if it
274 # first check if we gave crypted password back, and if it
275 # matches it's not password change
275 # matches it's not password change
276 if new_user.password == password:
276 if new_user.password == password:
277 return False
277 return False
278
278
279 password_match = check_password(password, old_password)
279 password_match = check_password(password, old_password)
280 if not password_match:
280 if not password_match:
281 return True
281 return True
282
282
283 return False
283 return False
284
284
285 # read settings on default personal repo group creation
285 # read settings on default personal repo group creation
286 if create_repo_group is None:
286 if create_repo_group is None:
287 default_create_repo_group = RepoGroupModel()\
287 default_create_repo_group = RepoGroupModel()\
288 .get_default_create_personal_repo_group()
288 .get_default_create_personal_repo_group()
289 create_repo_group = default_create_repo_group
289 create_repo_group = default_create_repo_group
290
290
291 user_data = {
291 user_data = {
292 'username': username,
292 'username': username,
293 'password': password,
293 'password': password,
294 'email': email,
294 'email': email,
295 'firstname': firstname,
295 'firstname': firstname,
296 'lastname': lastname,
296 'lastname': lastname,
297 'active': active,
297 'active': active,
298 'admin': admin
298 'admin': admin
299 }
299 }
300
300
301 if updating_user_id:
301 if updating_user_id:
302 log.debug('Checking for existing account in RhodeCode '
302 log.debug('Checking for existing account in RhodeCode '
303 'database with user_id `%s` ', updating_user_id)
303 'database with user_id `%s` ', updating_user_id)
304 user = User.get(updating_user_id)
304 user = User.get(updating_user_id)
305 else:
305 else:
306 log.debug('Checking for existing account in RhodeCode '
306 log.debug('Checking for existing account in RhodeCode '
307 'database with username `%s` ', username)
307 'database with username `%s` ', username)
308 user = User.get_by_username(username, case_insensitive=True)
308 user = User.get_by_username(username, case_insensitive=True)
309
309
310 if user is None:
310 if user is None:
311 # we check internal flag if this method is actually allowed to
311 # we check internal flag if this method is actually allowed to
312 # create new user
312 # create new user
313 if not allow_to_create_user:
313 if not allow_to_create_user:
314 msg = ('Method wants to create new user, but it is not '
314 msg = ('Method wants to create new user, but it is not '
315 'allowed to do so')
315 'allowed to do so')
316 log.warning(msg)
316 log.warning(msg)
317 raise NotAllowedToCreateUserError(msg)
317 raise NotAllowedToCreateUserError(msg)
318
318
319 log.debug('Creating new user %s', username)
319 log.debug('Creating new user %s', username)
320
320
321 # only if we create user that is active
321 # only if we create user that is active
322 new_active_user = active
322 new_active_user = active
323 if new_active_user and strict_creation_check:
323 if new_active_user and strict_creation_check:
324 # raises UserCreationError if it's not allowed for any reason to
324 # raises UserCreationError if it's not allowed for any reason to
325 # create new active user, this also executes pre-create hooks
325 # create new active user, this also executes pre-create hooks
326 check_allowed_create_user(user_data, cur_user, strict_check=True)
326 check_allowed_create_user(user_data, cur_user, strict_check=True)
327 events.trigger(events.UserPreCreate(user_data))
327 events.trigger(events.UserPreCreate(user_data))
328 new_user = User()
328 new_user = User()
329 edit = False
329 edit = False
330 else:
330 else:
331 log.debug('updating user `%s`', username)
331 log.debug('updating user `%s`', username)
332 events.trigger(events.UserPreUpdate(user, user_data))
332 events.trigger(events.UserPreUpdate(user, user_data))
333 new_user = user
333 new_user = user
334 edit = True
334 edit = True
335
335
336 # we're not allowed to edit default user
336 # we're not allowed to edit default user
337 if user.username == User.DEFAULT_USER:
337 if user.username == User.DEFAULT_USER:
338 raise DefaultUserException(
338 raise DefaultUserException(
339 "You can't edit this user (`%(username)s`) since it's "
339 "You can't edit this user (`%(username)s`) since it's "
340 "crucial for entire application"
340 "crucial for entire application"
341 % {'username': user.username})
341 % {'username': user.username})
342
342
343 # inject special attribute that will tell us if User is new or old
343 # inject special attribute that will tell us if User is new or old
344 new_user.is_new_user = not edit
344 new_user.is_new_user = not edit
345 # for users that didn's specify auth type, we use RhodeCode built in
345 # for users that didn's specify auth type, we use RhodeCode built in
346 from rhodecode.authentication.plugins import auth_rhodecode
346 from rhodecode.authentication.plugins import auth_rhodecode
347 extern_name = extern_name or auth_rhodecode.RhodeCodeAuthPlugin.uid
347 extern_name = extern_name or auth_rhodecode.RhodeCodeAuthPlugin.uid
348 extern_type = extern_type or auth_rhodecode.RhodeCodeAuthPlugin.uid
348 extern_type = extern_type or auth_rhodecode.RhodeCodeAuthPlugin.uid
349
349
350 try:
350 try:
351 new_user.username = username
351 new_user.username = username
352 new_user.admin = admin
352 new_user.admin = admin
353 new_user.email = email
353 new_user.email = email
354 new_user.active = active
354 new_user.active = active
355 new_user.extern_name = safe_unicode(extern_name)
355 new_user.extern_name = safe_unicode(extern_name)
356 new_user.extern_type = safe_unicode(extern_type)
356 new_user.extern_type = safe_unicode(extern_type)
357 new_user.name = firstname
357 new_user.name = firstname
358 new_user.lastname = lastname
358 new_user.lastname = lastname
359
359
360 # set password only if creating an user or password is changed
360 # set password only if creating an user or password is changed
361 if not edit or _password_change(new_user, password):
361 if not edit or _password_change(new_user, password):
362 reason = 'new password' if edit else 'new user'
362 reason = 'new password' if edit else 'new user'
363 log.debug('Updating password reason=>%s', reason)
363 log.debug('Updating password reason=>%s', reason)
364 new_user.password = get_crypt_password(password) if password else None
364 new_user.password = get_crypt_password(password) if password else None
365
365
366 if force_password_change:
366 if force_password_change:
367 new_user.update_userdata(force_password_change=True)
367 new_user.update_userdata(force_password_change=True)
368 if language:
368 if language:
369 new_user.update_userdata(language=language)
369 new_user.update_userdata(language=language)
370 new_user.update_userdata(notification_status=True)
370 new_user.update_userdata(notification_status=True)
371
371
372 self.sa.add(new_user)
372 self.sa.add(new_user)
373
373
374 if not edit and create_repo_group:
374 if not edit and create_repo_group:
375 RepoGroupModel().create_personal_repo_group(
375 RepoGroupModel().create_personal_repo_group(
376 new_user, commit_early=False)
376 new_user, commit_early=False)
377
377
378 if not edit:
378 if not edit:
379 # add the RSS token
379 # add the RSS token
380 self.add_auth_token(
380 self.add_auth_token(
381 user=username, lifetime_minutes=-1,
381 user=username, lifetime_minutes=-1,
382 role=self.auth_token_role.ROLE_FEED,
382 role=self.auth_token_role.ROLE_FEED,
383 description=u'Generated feed token')
383 description=u'Generated feed token')
384
384
385 kwargs = new_user.get_dict()
385 kwargs = new_user.get_dict()
386 # backward compat, require api_keys present
386 # backward compat, require api_keys present
387 kwargs['api_keys'] = kwargs['auth_tokens']
387 kwargs['api_keys'] = kwargs['auth_tokens']
388 log_create_user(created_by=cur_user, **kwargs)
388 log_create_user(created_by=cur_user, **kwargs)
389 events.trigger(events.UserPostCreate(user_data))
389 events.trigger(events.UserPostCreate(user_data))
390 return new_user
390 return new_user
391 except (DatabaseError,):
391 except (DatabaseError,):
392 log.error(traceback.format_exc())
392 log.error(traceback.format_exc())
393 raise
393 raise
394
394
395 def create_registration(self, form_data,
395 def create_registration(self, form_data,
396 extern_name='rhodecode', extern_type='rhodecode'):
396 extern_name='rhodecode', extern_type='rhodecode'):
397 from rhodecode.model.notification import NotificationModel
397 from rhodecode.model.notification import NotificationModel
398 from rhodecode.model.notification import EmailNotificationModel
398 from rhodecode.model.notification import EmailNotificationModel
399
399
400 try:
400 try:
401 form_data['admin'] = False
401 form_data['admin'] = False
402 form_data['extern_name'] = extern_name
402 form_data['extern_name'] = extern_name
403 form_data['extern_type'] = extern_type
403 form_data['extern_type'] = extern_type
404 new_user = self.create(form_data)
404 new_user = self.create(form_data)
405
405
406 self.sa.add(new_user)
406 self.sa.add(new_user)
407 self.sa.flush()
407 self.sa.flush()
408
408
409 user_data = new_user.get_dict()
409 user_data = new_user.get_dict()
410 kwargs = {
410 kwargs = {
411 # use SQLALCHEMY safe dump of user data
411 # use SQLALCHEMY safe dump of user data
412 'user': AttributeDict(user_data),
412 'user': AttributeDict(user_data),
413 'date': datetime.datetime.now()
413 'date': datetime.datetime.now()
414 }
414 }
415 notification_type = EmailNotificationModel.TYPE_REGISTRATION
415 notification_type = EmailNotificationModel.TYPE_REGISTRATION
416 # pre-generate the subject for notification itself
416 # pre-generate the subject for notification itself
417 (subject,
417 (subject,
418 _h, _e, # we don't care about those
418 _h, _e, # we don't care about those
419 body_plaintext) = EmailNotificationModel().render_email(
419 body_plaintext) = EmailNotificationModel().render_email(
420 notification_type, **kwargs)
420 notification_type, **kwargs)
421
421
422 # create notification objects, and emails
422 # create notification objects, and emails
423 NotificationModel().create(
423 NotificationModel().create(
424 created_by=new_user,
424 created_by=new_user,
425 notification_subject=subject,
425 notification_subject=subject,
426 notification_body=body_plaintext,
426 notification_body=body_plaintext,
427 notification_type=notification_type,
427 notification_type=notification_type,
428 recipients=None, # all admins
428 recipients=None, # all admins
429 email_kwargs=kwargs,
429 email_kwargs=kwargs,
430 )
430 )
431
431
432 return new_user
432 return new_user
433 except Exception:
433 except Exception:
434 log.error(traceback.format_exc())
434 log.error(traceback.format_exc())
435 raise
435 raise
436
436
437 def _handle_user_repos(self, username, repositories, handle_mode=None):
437 def _handle_user_repos(self, username, repositories, handle_mode=None):
438 _superadmin = self.cls.get_first_super_admin()
438 _superadmin = self.cls.get_first_super_admin()
439 left_overs = True
439 left_overs = True
440
440
441 from rhodecode.model.repo import RepoModel
441 from rhodecode.model.repo import RepoModel
442
442
443 if handle_mode == 'detach':
443 if handle_mode == 'detach':
444 for obj in repositories:
444 for obj in repositories:
445 obj.user = _superadmin
445 obj.user = _superadmin
446 # set description we know why we super admin now owns
446 # set description we know why we super admin now owns
447 # additional repositories that were orphaned !
447 # additional repositories that were orphaned !
448 obj.description += ' \n::detached repository from deleted user: %s' % (username,)
448 obj.description += ' \n::detached repository from deleted user: %s' % (username,)
449 self.sa.add(obj)
449 self.sa.add(obj)
450 left_overs = False
450 left_overs = False
451 elif handle_mode == 'delete':
451 elif handle_mode == 'delete':
452 for obj in repositories:
452 for obj in repositories:
453 RepoModel().delete(obj, forks='detach')
453 RepoModel().delete(obj, forks='detach')
454 left_overs = False
454 left_overs = False
455
455
456 # if nothing is done we have left overs left
456 # if nothing is done we have left overs left
457 return left_overs
457 return left_overs
458
458
459 def _handle_user_repo_groups(self, username, repository_groups,
459 def _handle_user_repo_groups(self, username, repository_groups,
460 handle_mode=None):
460 handle_mode=None):
461 _superadmin = self.cls.get_first_super_admin()
461 _superadmin = self.cls.get_first_super_admin()
462 left_overs = True
462 left_overs = True
463
463
464 from rhodecode.model.repo_group import RepoGroupModel
464 from rhodecode.model.repo_group import RepoGroupModel
465
465
466 if handle_mode == 'detach':
466 if handle_mode == 'detach':
467 for r in repository_groups:
467 for r in repository_groups:
468 r.user = _superadmin
468 r.user = _superadmin
469 # set description we know why we super admin now owns
469 # set description we know why we super admin now owns
470 # additional repositories that were orphaned !
470 # additional repositories that were orphaned !
471 r.group_description += ' \n::detached repository group from deleted user: %s' % (username,)
471 r.group_description += ' \n::detached repository group from deleted user: %s' % (username,)
472 r.personal = False
472 r.personal = False
473 self.sa.add(r)
473 self.sa.add(r)
474 left_overs = False
474 left_overs = False
475 elif handle_mode == 'delete':
475 elif handle_mode == 'delete':
476 for r in repository_groups:
476 for r in repository_groups:
477 RepoGroupModel().delete(r)
477 RepoGroupModel().delete(r)
478 left_overs = False
478 left_overs = False
479
479
480 # if nothing is done we have left overs left
480 # if nothing is done we have left overs left
481 return left_overs
481 return left_overs
482
482
483 def _handle_user_user_groups(self, username, user_groups, handle_mode=None):
483 def _handle_user_user_groups(self, username, user_groups, handle_mode=None):
484 _superadmin = self.cls.get_first_super_admin()
484 _superadmin = self.cls.get_first_super_admin()
485 left_overs = True
485 left_overs = True
486
486
487 from rhodecode.model.user_group import UserGroupModel
487 from rhodecode.model.user_group import UserGroupModel
488
488
489 if handle_mode == 'detach':
489 if handle_mode == 'detach':
490 for r in user_groups:
490 for r in user_groups:
491 for user_user_group_to_perm in r.user_user_group_to_perm:
491 for user_user_group_to_perm in r.user_user_group_to_perm:
492 if user_user_group_to_perm.user.username == username:
492 if user_user_group_to_perm.user.username == username:
493 user_user_group_to_perm.user = _superadmin
493 user_user_group_to_perm.user = _superadmin
494 r.user = _superadmin
494 r.user = _superadmin
495 # set description we know why we super admin now owns
495 # set description we know why we super admin now owns
496 # additional repositories that were orphaned !
496 # additional repositories that were orphaned !
497 r.user_group_description += ' \n::detached user group from deleted user: %s' % (username,)
497 r.user_group_description += ' \n::detached user group from deleted user: %s' % (username,)
498 self.sa.add(r)
498 self.sa.add(r)
499 left_overs = False
499 left_overs = False
500 elif handle_mode == 'delete':
500 elif handle_mode == 'delete':
501 for r in user_groups:
501 for r in user_groups:
502 UserGroupModel().delete(r)
502 UserGroupModel().delete(r)
503 left_overs = False
503 left_overs = False
504
504
505 # if nothing is done we have left overs left
505 # if nothing is done we have left overs left
506 return left_overs
506 return left_overs
507
507
508 def _handle_user_artifacts(self, username, artifacts, handle_mode=None):
509 _superadmin = self.cls.get_first_super_admin()
510 left_overs = True
511
512 if handle_mode == 'detach':
513 for a in artifacts:
514 a.upload_user = _superadmin
515 # set description we know why we super admin now owns
516 # additional artifacts that were orphaned !
517 a.file_description += ' \n::detached artifact from deleted user: %s' % (username,)
518 self.sa.add(a)
519 left_overs = False
520 elif handle_mode == 'delete':
521 from rhodecode.apps.file_store import utils as store_utils
522 storage = store_utils.get_file_storage(self.request.registry.settings)
523 for a in artifacts:
524 file_uid = a.file_uid
525 storage.delete(file_uid)
526 self.sa.delete(a)
527
528 left_overs = False
529
530 # if nothing is done we have left overs left
531 return left_overs
532
508 def delete(self, user, cur_user=None, handle_repos=None,
533 def delete(self, user, cur_user=None, handle_repos=None,
509 handle_repo_groups=None, handle_user_groups=None):
534 handle_repo_groups=None, handle_user_groups=None, handle_artifacts=None):
510 from rhodecode.lib.hooks_base import log_delete_user
535 from rhodecode.lib.hooks_base import log_delete_user
511
536
512 if not cur_user:
537 if not cur_user:
513 cur_user = getattr(get_current_rhodecode_user(), 'username', None)
538 cur_user = getattr(get_current_rhodecode_user(), 'username', None)
514 user = self._get_user(user)
539 user = self._get_user(user)
515
540
516 try:
541 try:
517 if user.username == User.DEFAULT_USER:
542 if user.username == User.DEFAULT_USER:
518 raise DefaultUserException(
543 raise DefaultUserException(
519 u"You can't remove this user since it's"
544 u"You can't remove this user since it's"
520 u" crucial for entire application")
545 u" crucial for entire application")
521
546
522 left_overs = self._handle_user_repos(
547 left_overs = self._handle_user_repos(
523 user.username, user.repositories, handle_repos)
548 user.username, user.repositories, handle_repos)
524 if left_overs and user.repositories:
549 if left_overs and user.repositories:
525 repos = [x.repo_name for x in user.repositories]
550 repos = [x.repo_name for x in user.repositories]
526 raise UserOwnsReposException(
551 raise UserOwnsReposException(
527 u'user "%(username)s" still owns %(len_repos)s repositories and cannot be '
552 u'user "%(username)s" still owns %(len_repos)s repositories and cannot be '
528 u'removed. Switch owners or remove those repositories:%(list_repos)s'
553 u'removed. Switch owners or remove those repositories:%(list_repos)s'
529 % {'username': user.username, 'len_repos': len(repos),
554 % {'username': user.username, 'len_repos': len(repos),
530 'list_repos': ', '.join(repos)})
555 'list_repos': ', '.join(repos)})
531
556
532 left_overs = self._handle_user_repo_groups(
557 left_overs = self._handle_user_repo_groups(
533 user.username, user.repository_groups, handle_repo_groups)
558 user.username, user.repository_groups, handle_repo_groups)
534 if left_overs and user.repository_groups:
559 if left_overs and user.repository_groups:
535 repo_groups = [x.group_name for x in user.repository_groups]
560 repo_groups = [x.group_name for x in user.repository_groups]
536 raise UserOwnsRepoGroupsException(
561 raise UserOwnsRepoGroupsException(
537 u'user "%(username)s" still owns %(len_repo_groups)s repository groups and cannot be '
562 u'user "%(username)s" still owns %(len_repo_groups)s repository groups and cannot be '
538 u'removed. Switch owners or remove those repository groups:%(list_repo_groups)s'
563 u'removed. Switch owners or remove those repository groups:%(list_repo_groups)s'
539 % {'username': user.username, 'len_repo_groups': len(repo_groups),
564 % {'username': user.username, 'len_repo_groups': len(repo_groups),
540 'list_repo_groups': ', '.join(repo_groups)})
565 'list_repo_groups': ', '.join(repo_groups)})
541
566
542 left_overs = self._handle_user_user_groups(
567 left_overs = self._handle_user_user_groups(
543 user.username, user.user_groups, handle_user_groups)
568 user.username, user.user_groups, handle_user_groups)
544 if left_overs and user.user_groups:
569 if left_overs and user.user_groups:
545 user_groups = [x.users_group_name for x in user.user_groups]
570 user_groups = [x.users_group_name for x in user.user_groups]
546 raise UserOwnsUserGroupsException(
571 raise UserOwnsUserGroupsException(
547 u'user "%s" still owns %s user groups and cannot be '
572 u'user "%s" still owns %s user groups and cannot be '
548 u'removed. Switch owners or remove those user groups:%s'
573 u'removed. Switch owners or remove those user groups:%s'
549 % (user.username, len(user_groups), ', '.join(user_groups)))
574 % (user.username, len(user_groups), ', '.join(user_groups)))
550
575
576 left_overs = self._handle_user_artifacts(
577 user.username, user.artifacts, handle_artifacts)
578 if left_overs and user.artifacts:
579 artifacts = [x.file_uid for x in user.artifacts]
580 raise UserOwnsArtifactsException(
581 u'user "%s" still owns %s artifacts and cannot be '
582 u'removed. Switch owners or remove those artifacts:%s'
583 % (user.username, len(artifacts), ', '.join(artifacts)))
584
551 user_data = user.get_dict() # fetch user data before expire
585 user_data = user.get_dict() # fetch user data before expire
552
586
553 # we might change the user data with detach/delete, make sure
587 # we might change the user data with detach/delete, make sure
554 # the object is marked as expired before actually deleting !
588 # the object is marked as expired before actually deleting !
555 self.sa.expire(user)
589 self.sa.expire(user)
556 self.sa.delete(user)
590 self.sa.delete(user)
557
591
558 log_delete_user(deleted_by=cur_user, **user_data)
592 log_delete_user(deleted_by=cur_user, **user_data)
559 except Exception:
593 except Exception:
560 log.error(traceback.format_exc())
594 log.error(traceback.format_exc())
561 raise
595 raise
562
596
563 def reset_password_link(self, data, pwd_reset_url):
597 def reset_password_link(self, data, pwd_reset_url):
564 from rhodecode.lib.celerylib import tasks, run_task
598 from rhodecode.lib.celerylib import tasks, run_task
565 from rhodecode.model.notification import EmailNotificationModel
599 from rhodecode.model.notification import EmailNotificationModel
566 user_email = data['email']
600 user_email = data['email']
567 try:
601 try:
568 user = User.get_by_email(user_email)
602 user = User.get_by_email(user_email)
569 if user:
603 if user:
570 log.debug('password reset user found %s', user)
604 log.debug('password reset user found %s', user)
571
605
572 email_kwargs = {
606 email_kwargs = {
573 'password_reset_url': pwd_reset_url,
607 'password_reset_url': pwd_reset_url,
574 'user': user,
608 'user': user,
575 'email': user_email,
609 'email': user_email,
576 'date': datetime.datetime.now()
610 'date': datetime.datetime.now()
577 }
611 }
578
612
579 (subject, headers, email_body,
613 (subject, headers, email_body,
580 email_body_plaintext) = EmailNotificationModel().render_email(
614 email_body_plaintext) = EmailNotificationModel().render_email(
581 EmailNotificationModel.TYPE_PASSWORD_RESET, **email_kwargs)
615 EmailNotificationModel.TYPE_PASSWORD_RESET, **email_kwargs)
582
616
583 recipients = [user_email]
617 recipients = [user_email]
584
618
585 action_logger_generic(
619 action_logger_generic(
586 'sending password reset email to user: {}'.format(
620 'sending password reset email to user: {}'.format(
587 user), namespace='security.password_reset')
621 user), namespace='security.password_reset')
588
622
589 run_task(tasks.send_email, recipients, subject,
623 run_task(tasks.send_email, recipients, subject,
590 email_body_plaintext, email_body)
624 email_body_plaintext, email_body)
591
625
592 else:
626 else:
593 log.debug("password reset email %s not found", user_email)
627 log.debug("password reset email %s not found", user_email)
594 except Exception:
628 except Exception:
595 log.error(traceback.format_exc())
629 log.error(traceback.format_exc())
596 return False
630 return False
597
631
598 return True
632 return True
599
633
600 def reset_password(self, data):
634 def reset_password(self, data):
601 from rhodecode.lib.celerylib import tasks, run_task
635 from rhodecode.lib.celerylib import tasks, run_task
602 from rhodecode.model.notification import EmailNotificationModel
636 from rhodecode.model.notification import EmailNotificationModel
603 from rhodecode.lib import auth
637 from rhodecode.lib import auth
604 user_email = data['email']
638 user_email = data['email']
605 pre_db = True
639 pre_db = True
606 try:
640 try:
607 user = User.get_by_email(user_email)
641 user = User.get_by_email(user_email)
608 new_passwd = auth.PasswordGenerator().gen_password(
642 new_passwd = auth.PasswordGenerator().gen_password(
609 12, auth.PasswordGenerator.ALPHABETS_BIG_SMALL)
643 12, auth.PasswordGenerator.ALPHABETS_BIG_SMALL)
610 if user:
644 if user:
611 user.password = auth.get_crypt_password(new_passwd)
645 user.password = auth.get_crypt_password(new_passwd)
612 # also force this user to reset his password !
646 # also force this user to reset his password !
613 user.update_userdata(force_password_change=True)
647 user.update_userdata(force_password_change=True)
614
648
615 Session().add(user)
649 Session().add(user)
616
650
617 # now delete the token in question
651 # now delete the token in question
618 UserApiKeys = AuthTokenModel.cls
652 UserApiKeys = AuthTokenModel.cls
619 UserApiKeys().query().filter(
653 UserApiKeys().query().filter(
620 UserApiKeys.api_key == data['token']).delete()
654 UserApiKeys.api_key == data['token']).delete()
621
655
622 Session().commit()
656 Session().commit()
623 log.info('successfully reset password for `%s`', user_email)
657 log.info('successfully reset password for `%s`', user_email)
624
658
625 if new_passwd is None:
659 if new_passwd is None:
626 raise Exception('unable to generate new password')
660 raise Exception('unable to generate new password')
627
661
628 pre_db = False
662 pre_db = False
629
663
630 email_kwargs = {
664 email_kwargs = {
631 'new_password': new_passwd,
665 'new_password': new_passwd,
632 'user': user,
666 'user': user,
633 'email': user_email,
667 'email': user_email,
634 'date': datetime.datetime.now()
668 'date': datetime.datetime.now()
635 }
669 }
636
670
637 (subject, headers, email_body,
671 (subject, headers, email_body,
638 email_body_plaintext) = EmailNotificationModel().render_email(
672 email_body_plaintext) = EmailNotificationModel().render_email(
639 EmailNotificationModel.TYPE_PASSWORD_RESET_CONFIRMATION,
673 EmailNotificationModel.TYPE_PASSWORD_RESET_CONFIRMATION,
640 **email_kwargs)
674 **email_kwargs)
641
675
642 recipients = [user_email]
676 recipients = [user_email]
643
677
644 action_logger_generic(
678 action_logger_generic(
645 'sent new password to user: {} with email: {}'.format(
679 'sent new password to user: {} with email: {}'.format(
646 user, user_email), namespace='security.password_reset')
680 user, user_email), namespace='security.password_reset')
647
681
648 run_task(tasks.send_email, recipients, subject,
682 run_task(tasks.send_email, recipients, subject,
649 email_body_plaintext, email_body)
683 email_body_plaintext, email_body)
650
684
651 except Exception:
685 except Exception:
652 log.error('Failed to update user password')
686 log.error('Failed to update user password')
653 log.error(traceback.format_exc())
687 log.error(traceback.format_exc())
654 if pre_db:
688 if pre_db:
655 # we rollback only if local db stuff fails. If it goes into
689 # we rollback only if local db stuff fails. If it goes into
656 # run_task, we're pass rollback state this wouldn't work then
690 # run_task, we're pass rollback state this wouldn't work then
657 Session().rollback()
691 Session().rollback()
658
692
659 return True
693 return True
660
694
661 def fill_data(self, auth_user, user_id=None, api_key=None, username=None):
695 def fill_data(self, auth_user, user_id=None, api_key=None, username=None):
662 """
696 """
663 Fetches auth_user by user_id,or api_key if present.
697 Fetches auth_user by user_id,or api_key if present.
664 Fills auth_user attributes with those taken from database.
698 Fills auth_user attributes with those taken from database.
665 Additionally set's is_authenitated if lookup fails
699 Additionally set's is_authenitated if lookup fails
666 present in database
700 present in database
667
701
668 :param auth_user: instance of user to set attributes
702 :param auth_user: instance of user to set attributes
669 :param user_id: user id to fetch by
703 :param user_id: user id to fetch by
670 :param api_key: api key to fetch by
704 :param api_key: api key to fetch by
671 :param username: username to fetch by
705 :param username: username to fetch by
672 """
706 """
673 def token_obfuscate(token):
707 def token_obfuscate(token):
674 if token:
708 if token:
675 return token[:4] + "****"
709 return token[:4] + "****"
676
710
677 if user_id is None and api_key is None and username is None:
711 if user_id is None and api_key is None and username is None:
678 raise Exception('You need to pass user_id, api_key or username')
712 raise Exception('You need to pass user_id, api_key or username')
679
713
680 log.debug(
714 log.debug(
681 'AuthUser: fill data execution based on: '
715 'AuthUser: fill data execution based on: '
682 'user_id:%s api_key:%s username:%s', user_id, api_key, username)
716 'user_id:%s api_key:%s username:%s', user_id, api_key, username)
683 try:
717 try:
684 dbuser = None
718 dbuser = None
685 if user_id:
719 if user_id:
686 dbuser = self.get(user_id)
720 dbuser = self.get(user_id)
687 elif api_key:
721 elif api_key:
688 dbuser = self.get_by_auth_token(api_key)
722 dbuser = self.get_by_auth_token(api_key)
689 elif username:
723 elif username:
690 dbuser = self.get_by_username(username)
724 dbuser = self.get_by_username(username)
691
725
692 if not dbuser:
726 if not dbuser:
693 log.warning(
727 log.warning(
694 'Unable to lookup user by id:%s api_key:%s username:%s',
728 'Unable to lookup user by id:%s api_key:%s username:%s',
695 user_id, token_obfuscate(api_key), username)
729 user_id, token_obfuscate(api_key), username)
696 return False
730 return False
697 if not dbuser.active:
731 if not dbuser.active:
698 log.debug('User `%s:%s` is inactive, skipping fill data',
732 log.debug('User `%s:%s` is inactive, skipping fill data',
699 username, user_id)
733 username, user_id)
700 return False
734 return False
701
735
702 log.debug('AuthUser: filling found user:%s data', dbuser)
736 log.debug('AuthUser: filling found user:%s data', dbuser)
703 user_data = dbuser.get_dict()
737 user_data = dbuser.get_dict()
704
738
705 user_data.update({
739 user_data.update({
706 # set explicit the safe escaped values
740 # set explicit the safe escaped values
707 'first_name': dbuser.first_name,
741 'first_name': dbuser.first_name,
708 'last_name': dbuser.last_name,
742 'last_name': dbuser.last_name,
709 })
743 })
710
744
711 for k, v in user_data.items():
745 for k, v in user_data.items():
712 # properties of auth user we dont update
746 # properties of auth user we dont update
713 if k not in ['auth_tokens', 'permissions']:
747 if k not in ['auth_tokens', 'permissions']:
714 setattr(auth_user, k, v)
748 setattr(auth_user, k, v)
715
749
716 except Exception:
750 except Exception:
717 log.error(traceback.format_exc())
751 log.error(traceback.format_exc())
718 auth_user.is_authenticated = False
752 auth_user.is_authenticated = False
719 return False
753 return False
720
754
721 return True
755 return True
722
756
723 def has_perm(self, user, perm):
757 def has_perm(self, user, perm):
724 perm = self._get_perm(perm)
758 perm = self._get_perm(perm)
725 user = self._get_user(user)
759 user = self._get_user(user)
726
760
727 return UserToPerm.query().filter(UserToPerm.user == user)\
761 return UserToPerm.query().filter(UserToPerm.user == user)\
728 .filter(UserToPerm.permission == perm).scalar() is not None
762 .filter(UserToPerm.permission == perm).scalar() is not None
729
763
730 def grant_perm(self, user, perm):
764 def grant_perm(self, user, perm):
731 """
765 """
732 Grant user global permissions
766 Grant user global permissions
733
767
734 :param user:
768 :param user:
735 :param perm:
769 :param perm:
736 """
770 """
737 user = self._get_user(user)
771 user = self._get_user(user)
738 perm = self._get_perm(perm)
772 perm = self._get_perm(perm)
739 # if this permission is already granted skip it
773 # if this permission is already granted skip it
740 _perm = UserToPerm.query()\
774 _perm = UserToPerm.query()\
741 .filter(UserToPerm.user == user)\
775 .filter(UserToPerm.user == user)\
742 .filter(UserToPerm.permission == perm)\
776 .filter(UserToPerm.permission == perm)\
743 .scalar()
777 .scalar()
744 if _perm:
778 if _perm:
745 return
779 return
746 new = UserToPerm()
780 new = UserToPerm()
747 new.user = user
781 new.user = user
748 new.permission = perm
782 new.permission = perm
749 self.sa.add(new)
783 self.sa.add(new)
750 return new
784 return new
751
785
752 def revoke_perm(self, user, perm):
786 def revoke_perm(self, user, perm):
753 """
787 """
754 Revoke users global permissions
788 Revoke users global permissions
755
789
756 :param user:
790 :param user:
757 :param perm:
791 :param perm:
758 """
792 """
759 user = self._get_user(user)
793 user = self._get_user(user)
760 perm = self._get_perm(perm)
794 perm = self._get_perm(perm)
761
795
762 obj = UserToPerm.query()\
796 obj = UserToPerm.query()\
763 .filter(UserToPerm.user == user)\
797 .filter(UserToPerm.user == user)\
764 .filter(UserToPerm.permission == perm)\
798 .filter(UserToPerm.permission == perm)\
765 .scalar()
799 .scalar()
766 if obj:
800 if obj:
767 self.sa.delete(obj)
801 self.sa.delete(obj)
768
802
769 def add_extra_email(self, user, email):
803 def add_extra_email(self, user, email):
770 """
804 """
771 Adds email address to UserEmailMap
805 Adds email address to UserEmailMap
772
806
773 :param user:
807 :param user:
774 :param email:
808 :param email:
775 """
809 """
776
810
777 user = self._get_user(user)
811 user = self._get_user(user)
778
812
779 obj = UserEmailMap()
813 obj = UserEmailMap()
780 obj.user = user
814 obj.user = user
781 obj.email = email
815 obj.email = email
782 self.sa.add(obj)
816 self.sa.add(obj)
783 return obj
817 return obj
784
818
785 def delete_extra_email(self, user, email_id):
819 def delete_extra_email(self, user, email_id):
786 """
820 """
787 Removes email address from UserEmailMap
821 Removes email address from UserEmailMap
788
822
789 :param user:
823 :param user:
790 :param email_id:
824 :param email_id:
791 """
825 """
792 user = self._get_user(user)
826 user = self._get_user(user)
793 obj = UserEmailMap.query().get(email_id)
827 obj = UserEmailMap.query().get(email_id)
794 if obj and obj.user_id == user.user_id:
828 if obj and obj.user_id == user.user_id:
795 self.sa.delete(obj)
829 self.sa.delete(obj)
796
830
797 def parse_ip_range(self, ip_range):
831 def parse_ip_range(self, ip_range):
798 ip_list = []
832 ip_list = []
799
833
800 def make_unique(value):
834 def make_unique(value):
801 seen = []
835 seen = []
802 return [c for c in value if not (c in seen or seen.append(c))]
836 return [c for c in value if not (c in seen or seen.append(c))]
803
837
804 # firsts split by commas
838 # firsts split by commas
805 for ip_range in ip_range.split(','):
839 for ip_range in ip_range.split(','):
806 if not ip_range:
840 if not ip_range:
807 continue
841 continue
808 ip_range = ip_range.strip()
842 ip_range = ip_range.strip()
809 if '-' in ip_range:
843 if '-' in ip_range:
810 start_ip, end_ip = ip_range.split('-', 1)
844 start_ip, end_ip = ip_range.split('-', 1)
811 start_ip = ipaddress.ip_address(safe_unicode(start_ip.strip()))
845 start_ip = ipaddress.ip_address(safe_unicode(start_ip.strip()))
812 end_ip = ipaddress.ip_address(safe_unicode(end_ip.strip()))
846 end_ip = ipaddress.ip_address(safe_unicode(end_ip.strip()))
813 parsed_ip_range = []
847 parsed_ip_range = []
814
848
815 for index in xrange(int(start_ip), int(end_ip) + 1):
849 for index in xrange(int(start_ip), int(end_ip) + 1):
816 new_ip = ipaddress.ip_address(index)
850 new_ip = ipaddress.ip_address(index)
817 parsed_ip_range.append(str(new_ip))
851 parsed_ip_range.append(str(new_ip))
818 ip_list.extend(parsed_ip_range)
852 ip_list.extend(parsed_ip_range)
819 else:
853 else:
820 ip_list.append(ip_range)
854 ip_list.append(ip_range)
821
855
822 return make_unique(ip_list)
856 return make_unique(ip_list)
823
857
824 def add_extra_ip(self, user, ip, description=None):
858 def add_extra_ip(self, user, ip, description=None):
825 """
859 """
826 Adds ip address to UserIpMap
860 Adds ip address to UserIpMap
827
861
828 :param user:
862 :param user:
829 :param ip:
863 :param ip:
830 """
864 """
831
865
832 user = self._get_user(user)
866 user = self._get_user(user)
833 obj = UserIpMap()
867 obj = UserIpMap()
834 obj.user = user
868 obj.user = user
835 obj.ip_addr = ip
869 obj.ip_addr = ip
836 obj.description = description
870 obj.description = description
837 self.sa.add(obj)
871 self.sa.add(obj)
838 return obj
872 return obj
839
873
840 auth_token_role = AuthTokenModel.cls
874 auth_token_role = AuthTokenModel.cls
841
875
842 def add_auth_token(self, user, lifetime_minutes, role, description=u'',
876 def add_auth_token(self, user, lifetime_minutes, role, description=u'',
843 scope_callback=None):
877 scope_callback=None):
844 """
878 """
845 Add AuthToken for user.
879 Add AuthToken for user.
846
880
847 :param user: username/user_id
881 :param user: username/user_id
848 :param lifetime_minutes: in minutes the lifetime for token, -1 equals no limit
882 :param lifetime_minutes: in minutes the lifetime for token, -1 equals no limit
849 :param role: one of AuthTokenModel.cls.ROLE_*
883 :param role: one of AuthTokenModel.cls.ROLE_*
850 :param description: optional string description
884 :param description: optional string description
851 """
885 """
852
886
853 token = AuthTokenModel().create(
887 token = AuthTokenModel().create(
854 user, description, lifetime_minutes, role)
888 user, description, lifetime_minutes, role)
855 if scope_callback and callable(scope_callback):
889 if scope_callback and callable(scope_callback):
856 # call the callback if we provide, used to attach scope for EE edition
890 # call the callback if we provide, used to attach scope for EE edition
857 scope_callback(token)
891 scope_callback(token)
858 return token
892 return token
859
893
860 def delete_extra_ip(self, user, ip_id):
894 def delete_extra_ip(self, user, ip_id):
861 """
895 """
862 Removes ip address from UserIpMap
896 Removes ip address from UserIpMap
863
897
864 :param user:
898 :param user:
865 :param ip_id:
899 :param ip_id:
866 """
900 """
867 user = self._get_user(user)
901 user = self._get_user(user)
868 obj = UserIpMap.query().get(ip_id)
902 obj = UserIpMap.query().get(ip_id)
869 if obj and obj.user_id == user.user_id:
903 if obj and obj.user_id == user.user_id:
870 self.sa.delete(obj)
904 self.sa.delete(obj)
871
905
872 def get_accounts_in_creation_order(self, current_user=None):
906 def get_accounts_in_creation_order(self, current_user=None):
873 """
907 """
874 Get accounts in order of creation for deactivation for license limits
908 Get accounts in order of creation for deactivation for license limits
875
909
876 pick currently logged in user, and append to the list in position 0
910 pick currently logged in user, and append to the list in position 0
877 pick all super-admins in order of creation date and add it to the list
911 pick all super-admins in order of creation date and add it to the list
878 pick all other accounts in order of creation and add it to the list.
912 pick all other accounts in order of creation and add it to the list.
879
913
880 Based on that list, the last accounts can be disabled as they are
914 Based on that list, the last accounts can be disabled as they are
881 created at the end and don't include any of the super admins as well
915 created at the end and don't include any of the super admins as well
882 as the current user.
916 as the current user.
883
917
884 :param current_user: optionally current user running this operation
918 :param current_user: optionally current user running this operation
885 """
919 """
886
920
887 if not current_user:
921 if not current_user:
888 current_user = get_current_rhodecode_user()
922 current_user = get_current_rhodecode_user()
889 active_super_admins = [
923 active_super_admins = [
890 x.user_id for x in User.query()
924 x.user_id for x in User.query()
891 .filter(User.user_id != current_user.user_id)
925 .filter(User.user_id != current_user.user_id)
892 .filter(User.active == true())
926 .filter(User.active == true())
893 .filter(User.admin == true())
927 .filter(User.admin == true())
894 .order_by(User.created_on.asc())]
928 .order_by(User.created_on.asc())]
895
929
896 active_regular_users = [
930 active_regular_users = [
897 x.user_id for x in User.query()
931 x.user_id for x in User.query()
898 .filter(User.user_id != current_user.user_id)
932 .filter(User.user_id != current_user.user_id)
899 .filter(User.active == true())
933 .filter(User.active == true())
900 .filter(User.admin == false())
934 .filter(User.admin == false())
901 .order_by(User.created_on.asc())]
935 .order_by(User.created_on.asc())]
902
936
903 list_of_accounts = [current_user.user_id]
937 list_of_accounts = [current_user.user_id]
904 list_of_accounts += active_super_admins
938 list_of_accounts += active_super_admins
905 list_of_accounts += active_regular_users
939 list_of_accounts += active_regular_users
906
940
907 return list_of_accounts
941 return list_of_accounts
908
942
909 def deactivate_last_users(self, expected_users, current_user=None):
943 def deactivate_last_users(self, expected_users, current_user=None):
910 """
944 """
911 Deactivate accounts that are over the license limits.
945 Deactivate accounts that are over the license limits.
912 Algorithm of which accounts to disabled is based on the formula:
946 Algorithm of which accounts to disabled is based on the formula:
913
947
914 Get current user, then super admins in creation order, then regular
948 Get current user, then super admins in creation order, then regular
915 active users in creation order.
949 active users in creation order.
916
950
917 Using that list we mark all accounts from the end of it as inactive.
951 Using that list we mark all accounts from the end of it as inactive.
918 This way we block only latest created accounts.
952 This way we block only latest created accounts.
919
953
920 :param expected_users: list of users in special order, we deactivate
954 :param expected_users: list of users in special order, we deactivate
921 the end N amount of users from that list
955 the end N amount of users from that list
922 """
956 """
923
957
924 list_of_accounts = self.get_accounts_in_creation_order(
958 list_of_accounts = self.get_accounts_in_creation_order(
925 current_user=current_user)
959 current_user=current_user)
926
960
927 for acc_id in list_of_accounts[expected_users + 1:]:
961 for acc_id in list_of_accounts[expected_users + 1:]:
928 user = User.get(acc_id)
962 user = User.get(acc_id)
929 log.info('Deactivating account %s for license unlock', user)
963 log.info('Deactivating account %s for license unlock', user)
930 user.active = False
964 user.active = False
931 Session().add(user)
965 Session().add(user)
932 Session().commit()
966 Session().commit()
933
967
934 return
968 return
935
969
936 def get_user_log(self, user, filter_term):
970 def get_user_log(self, user, filter_term):
937 user_log = UserLog.query()\
971 user_log = UserLog.query()\
938 .filter(or_(UserLog.user_id == user.user_id,
972 .filter(or_(UserLog.user_id == user.user_id,
939 UserLog.username == user.username))\
973 UserLog.username == user.username))\
940 .options(joinedload(UserLog.user))\
974 .options(joinedload(UserLog.user))\
941 .options(joinedload(UserLog.repository))\
975 .options(joinedload(UserLog.repository))\
942 .order_by(UserLog.action_date.desc())
976 .order_by(UserLog.action_date.desc())
943
977
944 user_log = user_log_filter(user_log, filter_term)
978 user_log = user_log_filter(user_log, filter_term)
945 return user_log
979 return user_log
@@ -1,171 +1,195 b''
1 <%namespace name="base" file="/base/base.mako"/>
1 <%namespace name="base" file="/base/base.mako"/>
2
2
3 <%
3 <%
4 elems = [
4 elems = [
5 (_('User ID'), c.user.user_id, '', ''),
5 (_('User ID'), c.user.user_id, '', ''),
6 (_('Created on'), h.format_date(c.user.created_on), '', ''),
6 (_('Created on'), h.format_date(c.user.created_on), '', ''),
7 (_('Source of Record'), c.user.extern_type, '', ''),
7 (_('Source of Record'), c.user.extern_type, '', ''),
8
8
9 (_('Last login'), c.user.last_login or '-', '', ''),
9 (_('Last login'), c.user.last_login or '-', '', ''),
10 (_('Last activity'), c.user.last_activity, '', ''),
10 (_('Last activity'), c.user.last_activity, '', ''),
11
11
12 (_('Repositories'), len(c.user.repositories), '', [x.repo_name for x in c.user.repositories]),
12 (_('Repositories'), len(c.user.repositories), '', [x.repo_name for x in c.user.repositories]),
13 (_('Repository groups'), len(c.user.repository_groups), '', [x.group_name for x in c.user.repository_groups]),
13 (_('Repository groups'), len(c.user.repository_groups), '', [x.group_name for x in c.user.repository_groups]),
14 (_('User groups'), len(c.user.user_groups), '', [x.users_group_name for x in c.user.user_groups]),
14 (_('User groups'), len(c.user.user_groups), '', [x.users_group_name for x in c.user.user_groups]),
15
15
16 (_('Owned Artifacts'), len(c.user.artifacts), '', [x.file_uid for x in c.user.artifacts]),
17
16 (_('Reviewer of pull requests'), len(c.user.reviewer_pull_requests), '', ['Pull Request #{}'.format(x.pull_request.pull_request_id) for x in c.user.reviewer_pull_requests]),
18 (_('Reviewer of pull requests'), len(c.user.reviewer_pull_requests), '', ['Pull Request #{}'.format(x.pull_request.pull_request_id) for x in c.user.reviewer_pull_requests]),
17 (_('Assigned to review rules'), len(c.user_to_review_rules), '', [x for x in c.user_to_review_rules]),
19 (_('Assigned to review rules'), len(c.user_to_review_rules), '', [x for x in c.user_to_review_rules]),
18
20
19 (_('Member of User groups'), len(c.user.group_member), '', [x.users_group.users_group_name for x in c.user.group_member]),
21 (_('Member of User groups'), len(c.user.group_member), '', [x.users_group.users_group_name for x in c.user.group_member]),
20 (_('Force password change'), c.user.user_data.get('force_password_change', 'False'), '', ''),
22 (_('Force password change'), c.user.user_data.get('force_password_change', 'False'), '', ''),
21 ]
23 ]
22 %>
24 %>
23
25
24 <div class="panel panel-default">
26 <div class="panel panel-default">
25 <div class="panel-heading">
27 <div class="panel-heading">
26 <h3 class="panel-title">${_('User: %s') % c.user.username}</h3>
28 <h3 class="panel-title">${_('User: {}').format(c.user.username)}</h3>
27 </div>
29 </div>
28 <div class="panel-body">
30 <div class="panel-body">
29 ${base.dt_info_panel(elems)}
31 <table class="rctable">
32 <tr>
33 <th>Name</th>
34 <th>Value</th>
35 <th>Action</th>
36 </tr>
37 % for elem in elems:
38 ${base.tr_info_entry(elem)}
39 % endfor
40 </table>
30 </div>
41 </div>
31 </div>
42 </div>
32
43
33 <div class="panel panel-default">
44 <div class="panel panel-default">
34 <div class="panel-heading">
45 <div class="panel-heading">
35 <h3 class="panel-title">${_('Force Password Reset')}</h3>
46 <h3 class="panel-title">${_('Force Password Reset')}</h3>
36 </div>
47 </div>
37 <div class="panel-body">
48 <div class="panel-body">
38 ${h.secure_form(h.route_path('user_disable_force_password_reset', user_id=c.user.user_id), request=request)}
49 ${h.secure_form(h.route_path('user_disable_force_password_reset', user_id=c.user.user_id), request=request)}
39 <div class="field">
50 <div class="field">
40 <button class="btn btn-default" type="submit">
51 <button class="btn btn-default" type="submit">
41 <i class="icon-unlock"></i> ${_('Disable forced password reset')}
52 <i class="icon-unlock"></i> ${_('Disable forced password reset')}
42 </button>
53 </button>
43 </div>
54 </div>
44 <div class="field">
55 <div class="field">
45 <span class="help-block">
56 <span class="help-block">
46 ${_("Clear the forced password change flag.")}
57 ${_("Clear the forced password change flag.")}
47 </span>
58 </span>
48 </div>
59 </div>
49 ${h.end_form()}
60 ${h.end_form()}
50
61
51 ${h.secure_form(h.route_path('user_enable_force_password_reset', user_id=c.user.user_id), request=request)}
62 ${h.secure_form(h.route_path('user_enable_force_password_reset', user_id=c.user.user_id), request=request)}
52 <div class="field">
63 <div class="field">
53 <button class="btn btn-default" type="submit" onclick="return confirm('${_('Confirm to enable forced password change')}');">
64 <button class="btn btn-default" type="submit" onclick="return confirm('${_('Confirm to enable forced password change')}');">
54 <i class="icon-lock"></i> ${_('Enable forced password reset')}
65 <i class="icon-lock"></i> ${_('Enable forced password reset')}
55 </button>
66 </button>
56 </div>
67 </div>
57 <div class="field">
68 <div class="field">
58 <span class="help-block">
69 <span class="help-block">
59 ${_("When this is enabled user will have to change they password when they next use RhodeCode system. This will also forbid vcs operations until someone makes a password change in the web interface")}
70 ${_("When this is enabled user will have to change they password when they next use RhodeCode system. This will also forbid vcs operations until someone makes a password change in the web interface")}
60 </span>
71 </span>
61 </div>
72 </div>
62 ${h.end_form()}
73 ${h.end_form()}
63
74
64 </div>
75 </div>
65 </div>
76 </div>
66
77
67 <div class="panel panel-default">
78 <div class="panel panel-default">
68 <div class="panel-heading">
79 <div class="panel-heading">
69 <h3 class="panel-title">${_('Personal Repository Group')}</h3>
80 <h3 class="panel-title">${_('Personal Repository Group')}</h3>
70 </div>
81 </div>
71 <div class="panel-body">
82 <div class="panel-body">
72 ${h.secure_form(h.route_path('user_create_personal_repo_group', user_id=c.user.user_id), request=request)}
83 ${h.secure_form(h.route_path('user_create_personal_repo_group', user_id=c.user.user_id), request=request)}
73
84
74 %if c.personal_repo_group:
85 %if c.personal_repo_group:
75 <div class="panel-body-title-text">${_('Users personal repository group')} : ${h.link_to(c.personal_repo_group.group_name, h.route_path('repo_group_home', repo_group_name=c.personal_repo_group.group_name))}</div>
86 <div class="panel-body-title-text">${_('Users personal repository group')} : ${h.link_to(c.personal_repo_group.group_name, h.route_path('repo_group_home', repo_group_name=c.personal_repo_group.group_name))}</div>
76 %else:
87 %else:
77 <div class="panel-body-title-text">
88 <div class="panel-body-title-text">
78 ${_('This user currently does not have a personal repository group')}
89 ${_('This user currently does not have a personal repository group')}
79 <br/>
90 <br/>
80 ${_('New group will be created at: `/%(path)s`') % {'path': c.personal_repo_group_name}}
91 ${_('New group will be created at: `/%(path)s`') % {'path': c.personal_repo_group_name}}
81 </div>
92 </div>
82 %endif
93 %endif
83 <button class="btn btn-default" type="submit" ${'disabled="disabled"' if c.personal_repo_group else ''}>
94 <button class="btn btn-default" type="submit" ${'disabled="disabled"' if c.personal_repo_group else ''}>
84 <i class="icon-repo-group"></i>
95 <i class="icon-repo-group"></i>
85 ${_('Create personal repository group')}
96 ${_('Create personal repository group')}
86 </button>
97 </button>
87 ${h.end_form()}
98 ${h.end_form()}
88 </div>
99 </div>
89 </div>
100 </div>
90
101
91
102
92 <div class="panel panel-danger">
103 <div class="panel panel-danger">
93 <div class="panel-heading">
104 <div class="panel-heading">
94 <h3 class="panel-title">${_('Delete User')}</h3>
105 <h3 class="panel-title">${_('Delete User')}</h3>
95 </div>
106 </div>
96 <div class="panel-body">
107 <div class="panel-body">
97 ${h.secure_form(h.route_path('user_delete', user_id=c.user.user_id), request=request)}
108 ${h.secure_form(h.route_path('user_delete', user_id=c.user.user_id), request=request)}
98
109
99 <table class="display rctable">
110 <table class="display rctable">
100 <tr>
111 <tr>
101 <td>
112 <td>
102 ${_ungettext('This user owns %s repository.', 'This user owns %s repositories.', len(c.user.repositories)) % len(c.user.repositories)}
113 ${_ungettext('This user owns %s repository.', 'This user owns %s repositories.', len(c.user.repositories)) % len(c.user.repositories)}
103 </td>
114 </td>
104 <td>
115 <td>
105 <input type="radio" id="user_repos_1" name="user_repos" value="detach" checked="checked" ${'disabled=1' if len(c.user.repositories) == 0 else ''} /> <label for="user_repos_1">${_('Detach repositories')}</label>
116 <input type="radio" id="user_repos_1" name="user_repos" value="detach" checked="checked" ${'disabled=1' if len(c.user.repositories) == 0 else ''} /> <label for="user_repos_1">${_('Detach repositories')}</label>
106 </td>
117 </td>
107 <td>
118 <td>
108 <input type="radio" id="user_repos_2" name="user_repos" value="delete" ${'disabled=1' if len(c.user.repositories) == 0 else ''} /> <label for="user_repos_2">${_('Delete repositories')}</label>
119 <input type="radio" id="user_repos_2" name="user_repos" value="delete" ${'disabled=1' if len(c.user.repositories) == 0 else ''} /> <label for="user_repos_2">${_('Delete repositories')}</label>
109 </td>
120 </td>
110 </tr>
121 </tr>
111
122
112 <tr>
123 <tr>
113 <td>
124 <td>
114 ${_ungettext('This user owns %s repository group.', 'This user owns %s repository groups.', len(c.user.repository_groups)) % len(c.user.repository_groups)}
125 ${_ungettext('This user owns %s repository group.', 'This user owns %s repository groups.', len(c.user.repository_groups)) % len(c.user.repository_groups)}
115 </td>
126 </td>
116 <td>
127 <td>
117 <input type="radio" id="user_repo_groups_1" name="user_repo_groups" value="detach" checked="checked" ${'disabled=1' if len(c.user.repository_groups) == 0 else ''} /> <label for="user_repo_groups_1">${_('Detach repository groups')}</label>
128 <input type="radio" id="user_repo_groups_1" name="user_repo_groups" value="detach" checked="checked" ${'disabled=1' if len(c.user.repository_groups) == 0 else ''} /> <label for="user_repo_groups_1">${_('Detach repository groups')}</label>
118 </td>
129 </td>
119 <td>
130 <td>
120 <input type="radio" id="user_repo_groups_2" name="user_repo_groups" value="delete" ${'disabled=1' if len(c.user.repository_groups) == 0 else ''}/> <label for="user_repo_groups_2">${_('Delete repositories')}</label>
131 <input type="radio" id="user_repo_groups_2" name="user_repo_groups" value="delete" ${'disabled=1' if len(c.user.repository_groups) == 0 else ''}/> <label for="user_repo_groups_2">${_('Delete repositories')}</label>
121 </td>
132 </td>
122 </tr>
133 </tr>
123
134
124 <tr>
135 <tr>
125 <td>
136 <td>
126 ${_ungettext('This user owns %s user group.', 'This user owns %s user groups.', len(c.user.user_groups)) % len(c.user.user_groups)}
137 ${_ungettext('This user owns %s user group.', 'This user owns %s user groups.', len(c.user.user_groups)) % len(c.user.user_groups)}
127 </td>
138 </td>
128 <td>
139 <td>
129 <input type="radio" id="user_user_groups_1" name="user_user_groups" value="detach" checked="checked" ${'disabled=1' if len(c.user.user_groups) == 0 else ''}/> <label for="user_user_groups_1">${_('Detach user groups')}</label>
140 <input type="radio" id="user_user_groups_1" name="user_user_groups" value="detach" checked="checked" ${'disabled=1' if len(c.user.user_groups) == 0 else ''}/> <label for="user_user_groups_1">${_('Detach user groups')}</label>
130 </td>
141 </td>
131 <td>
142 <td>
132 <input type="radio" id="user_user_groups_2" name="user_user_groups" value="delete" ${'disabled=1' if len(c.user.user_groups) == 0 else ''}/> <label for="user_user_groups_2">${_('Delete repositories')}</label>
143 <input type="radio" id="user_user_groups_2" name="user_user_groups" value="delete" ${'disabled=1' if len(c.user.user_groups) == 0 else ''}/> <label for="user_user_groups_2">${_('Delete repositories')}</label>
133 </td>
144 </td>
134 </tr>
145 </tr>
146
147 <tr>
148 <td>
149 ${_ungettext('This user owns %s artifact.', 'This user owns %s artifacts.', len(c.user.artifacts)) % len(c.user.artifacts)}
150 </td>
151 <td>
152 <input type="radio" id="user_artifacts_1" name="user_artifacts" value="detach" checked="checked" ${'disabled=1' if len(c.user.artifacts) == 0 else ''}/> <label for="user_artifacts_1">${_('Detach Artifacts')}</label>
153 </td>
154 <td>
155 <input type="radio" id="user_artifacts_2" name="user_artifacts" value="delete" ${'disabled=1' if len(c.user.artifacts) == 0 else ''}/> <label for="user_artifacts_2">${_('Delete Artifacts')}</label>
156 </td>
157 </tr>
158
135 </table>
159 </table>
136 <div style="margin: 0 0 20px 0" class="fake-space"></div>
160 <div style="margin: 0 0 20px 0" class="fake-space"></div>
137 <div class="pull-left">
161 <div class="pull-left">
138 % if len(c.user.repositories) > 0 or len(c.user.repository_groups) > 0 or len(c.user.user_groups) > 0:
162 % if len(c.user.repositories) > 0 or len(c.user.repository_groups) > 0 or len(c.user.user_groups) > 0:
139 % endif
163 % endif
140
164
141 <span style="padding: 0 5px 0 0">${_('New owner for detached objects')}:</span>
165 <span style="padding: 0 5px 0 0">${_('New owner for detached objects')}:</span>
142 <div class="pull-right">${base.gravatar_with_user(c.first_admin.email, 16)}</div>
166 <div class="pull-right">${base.gravatar_with_user(c.first_admin.email, 16)}</div>
143 </div>
167 </div>
144 <div style="clear: both">
168 <div style="clear: both">
145
169
146 <div>
170 <div>
147 <p class="help-block">
171 <p class="help-block">
148 ${_("When selecting the detach option, the depending objects owned by this user will be assigned to the above user.")}
172 ${_("When selecting the detach option, the depending objects owned by this user will be assigned to the above user.")}
149 <br/>
173 <br/>
150 ${_("The delete option will delete the user and all his owned objects!")}
174 ${_("The delete option will delete the user and all his owned objects!")}
151 </p>
175 </p>
152 </div>
176 </div>
153
177
154 % if c.can_delete_user_message:
178 % if c.can_delete_user_message:
155 <p class="pre-formatting">${c.can_delete_user_message}</p>
179 <p class="pre-formatting">${c.can_delete_user_message}</p>
156 % endif
180 % endif
157 </div>
181 </div>
158
182
159 <div style="margin: 0 0 20px 0" class="fake-space"></div>
183 <div style="margin: 0 0 20px 0" class="fake-space"></div>
160
184
161 <div class="field">
185 <div class="field">
162 <button class="btn btn-small btn-danger" type="submit"
186 <button class="btn btn-small btn-danger" type="submit"
163 onclick="return confirm('${_('Confirm to delete this user: %s') % c.user.username}');"
187 onclick="return confirm('${_('Confirm to delete this user: %s') % c.user.username}');"
164 ${"disabled" if not c.can_delete_user else ""}>
188 ${"disabled" if not c.can_delete_user else ""}>
165 ${_('Delete this user')}
189 ${_('Delete this user')}
166 </button>
190 </button>
167 </div>
191 </div>
168
192
169 ${h.end_form()}
193 ${h.end_form()}
170 </div>
194 </div>
171 </div>
195 </div>
@@ -1,1063 +1,1093 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2 <%inherit file="root.mako"/>
2 <%inherit file="root.mako"/>
3
3
4 <%include file="/ejs_templates/templates.html"/>
4 <%include file="/ejs_templates/templates.html"/>
5
5
6 <div class="outerwrapper">
6 <div class="outerwrapper">
7 <!-- HEADER -->
7 <!-- HEADER -->
8 <div class="header">
8 <div class="header">
9 <div id="header-inner" class="wrapper">
9 <div id="header-inner" class="wrapper">
10 <div id="logo">
10 <div id="logo">
11 <div class="logo-wrapper">
11 <div class="logo-wrapper">
12 <a href="${h.route_path('home')}"><img src="${h.asset('images/rhodecode-logo-white-60x60.png')}" alt="RhodeCode"/></a>
12 <a href="${h.route_path('home')}"><img src="${h.asset('images/rhodecode-logo-white-60x60.png')}" alt="RhodeCode"/></a>
13 </div>
13 </div>
14 % if c.rhodecode_name:
14 % if c.rhodecode_name:
15 <div class="branding">
15 <div class="branding">
16 <a href="${h.route_path('home')}">${h.branding(c.rhodecode_name)}</a>
16 <a href="${h.route_path('home')}">${h.branding(c.rhodecode_name)}</a>
17 </div>
17 </div>
18 % endif
18 % endif
19 </div>
19 </div>
20 <!-- MENU BAR NAV -->
20 <!-- MENU BAR NAV -->
21 ${self.menu_bar_nav()}
21 ${self.menu_bar_nav()}
22 <!-- END MENU BAR NAV -->
22 <!-- END MENU BAR NAV -->
23 </div>
23 </div>
24 </div>
24 </div>
25 ${self.menu_bar_subnav()}
25 ${self.menu_bar_subnav()}
26 <!-- END HEADER -->
26 <!-- END HEADER -->
27
27
28 <!-- CONTENT -->
28 <!-- CONTENT -->
29 <div id="content" class="wrapper">
29 <div id="content" class="wrapper">
30
30
31 <rhodecode-toast id="notifications"></rhodecode-toast>
31 <rhodecode-toast id="notifications"></rhodecode-toast>
32
32
33 <div class="main">
33 <div class="main">
34 ${next.main()}
34 ${next.main()}
35 </div>
35 </div>
36 </div>
36 </div>
37 <!-- END CONTENT -->
37 <!-- END CONTENT -->
38
38
39 </div>
39 </div>
40 <!-- FOOTER -->
40 <!-- FOOTER -->
41 <div id="footer">
41 <div id="footer">
42 <div id="footer-inner" class="title wrapper">
42 <div id="footer-inner" class="title wrapper">
43 <div>
43 <div>
44 <p class="footer-link-right">
44 <p class="footer-link-right">
45 % if c.visual.show_version:
45 % if c.visual.show_version:
46 RhodeCode Enterprise ${c.rhodecode_version} ${c.rhodecode_edition}
46 RhodeCode Enterprise ${c.rhodecode_version} ${c.rhodecode_edition}
47 % endif
47 % endif
48 &copy; 2010-${h.datetime.today().year}, <a href="${h.route_url('rhodecode_official')}" target="_blank">RhodeCode GmbH</a>. All rights reserved.
48 &copy; 2010-${h.datetime.today().year}, <a href="${h.route_url('rhodecode_official')}" target="_blank">RhodeCode GmbH</a>. All rights reserved.
49 % if c.visual.rhodecode_support_url:
49 % if c.visual.rhodecode_support_url:
50 <a href="${c.visual.rhodecode_support_url}" target="_blank">${_('Support')}</a>
50 <a href="${c.visual.rhodecode_support_url}" target="_blank">${_('Support')}</a>
51 % endif
51 % endif
52 </p>
52 </p>
53 <% sid = 'block' if request.GET.get('showrcid') else 'none' %>
53 <% sid = 'block' if request.GET.get('showrcid') else 'none' %>
54 <p class="server-instance" style="display:${sid}">
54 <p class="server-instance" style="display:${sid}">
55 ## display hidden instance ID if specially defined
55 ## display hidden instance ID if specially defined
56 % if c.rhodecode_instanceid:
56 % if c.rhodecode_instanceid:
57 ${_('RhodeCode instance id: {}').format(c.rhodecode_instanceid)}
57 ${_('RhodeCode instance id: {}').format(c.rhodecode_instanceid)}
58 % endif
58 % endif
59 </p>
59 </p>
60 </div>
60 </div>
61 </div>
61 </div>
62 </div>
62 </div>
63
63
64 <!-- END FOOTER -->
64 <!-- END FOOTER -->
65
65
66 ### MAKO DEFS ###
66 ### MAKO DEFS ###
67
67
68 <%def name="menu_bar_subnav()">
68 <%def name="menu_bar_subnav()">
69 </%def>
69 </%def>
70
70
71 <%def name="breadcrumbs(class_='breadcrumbs')">
71 <%def name="breadcrumbs(class_='breadcrumbs')">
72 <div class="${class_}">
72 <div class="${class_}">
73 ${self.breadcrumbs_links()}
73 ${self.breadcrumbs_links()}
74 </div>
74 </div>
75 </%def>
75 </%def>
76
76
77 <%def name="admin_menu(active=None)">
77 <%def name="admin_menu(active=None)">
78 <%
78 <%
79 def is_active(selected):
79 def is_active(selected):
80 if selected == active:
80 if selected == active:
81 return "active"
81 return "active"
82 %>
82 %>
83
83
84 <div id="context-bar">
84 <div id="context-bar">
85 <div class="wrapper">
85 <div class="wrapper">
86 <div class="title">
86 <div class="title">
87 <div class="title-content">
87 <div class="title-content">
88 <div class="title-main">
88 <div class="title-main">
89 % if c.is_super_admin:
89 % if c.is_super_admin:
90 ${_('Super Admin Panel')}
90 ${_('Super Admin Panel')}
91 % else:
91 % else:
92 ${_('Delegated Admin Panel')}
92 ${_('Delegated Admin Panel')}
93 % endif
93 % endif
94 </div>
94 </div>
95 </div>
95 </div>
96 </div>
96 </div>
97
97
98 <ul id="context-pages" class="navigation horizontal-list">
98 <ul id="context-pages" class="navigation horizontal-list">
99
99
100 ## super admin case
100 ## super admin case
101 % if c.is_super_admin:
101 % if c.is_super_admin:
102 <li class="${is_active('audit_logs')}"><a href="${h.route_path('admin_audit_logs')}">${_('Admin audit logs')}</a></li>
102 <li class="${is_active('audit_logs')}"><a href="${h.route_path('admin_audit_logs')}">${_('Admin audit logs')}</a></li>
103 <li class="${is_active('repositories')}"><a href="${h.route_path('repos')}">${_('Repositories')}</a></li>
103 <li class="${is_active('repositories')}"><a href="${h.route_path('repos')}">${_('Repositories')}</a></li>
104 <li class="${is_active('repository_groups')}"><a href="${h.route_path('repo_groups')}">${_('Repository groups')}</a></li>
104 <li class="${is_active('repository_groups')}"><a href="${h.route_path('repo_groups')}">${_('Repository groups')}</a></li>
105 <li class="${is_active('users')}"><a href="${h.route_path('users')}">${_('Users')}</a></li>
105 <li class="${is_active('users')}"><a href="${h.route_path('users')}">${_('Users')}</a></li>
106 <li class="${is_active('user_groups')}"><a href="${h.route_path('user_groups')}">${_('User groups')}</a></li>
106 <li class="${is_active('user_groups')}"><a href="${h.route_path('user_groups')}">${_('User groups')}</a></li>
107 <li class="${is_active('permissions')}"><a href="${h.route_path('admin_permissions_application')}">${_('Permissions')}</a></li>
107 <li class="${is_active('permissions')}"><a href="${h.route_path('admin_permissions_application')}">${_('Permissions')}</a></li>
108 <li class="${is_active('authentication')}"><a href="${h.route_path('auth_home', traverse='')}">${_('Authentication')}</a></li>
108 <li class="${is_active('authentication')}"><a href="${h.route_path('auth_home', traverse='')}">${_('Authentication')}</a></li>
109 <li class="${is_active('integrations')}"><a href="${h.route_path('global_integrations_home')}">${_('Integrations')}</a></li>
109 <li class="${is_active('integrations')}"><a href="${h.route_path('global_integrations_home')}">${_('Integrations')}</a></li>
110 <li class="${is_active('defaults')}"><a href="${h.route_path('admin_defaults_repositories')}">${_('Defaults')}</a></li>
110 <li class="${is_active('defaults')}"><a href="${h.route_path('admin_defaults_repositories')}">${_('Defaults')}</a></li>
111 <li class="${is_active('settings')}"><a href="${h.route_path('admin_settings')}">${_('Settings')}</a></li>
111 <li class="${is_active('settings')}"><a href="${h.route_path('admin_settings')}">${_('Settings')}</a></li>
112
112
113 ## delegated admin
113 ## delegated admin
114 % elif c.is_delegated_admin:
114 % elif c.is_delegated_admin:
115 <%
115 <%
116 repositories=c.auth_user.repositories_admin or c.can_create_repo
116 repositories=c.auth_user.repositories_admin or c.can_create_repo
117 repository_groups=c.auth_user.repository_groups_admin or c.can_create_repo_group
117 repository_groups=c.auth_user.repository_groups_admin or c.can_create_repo_group
118 user_groups=c.auth_user.user_groups_admin or c.can_create_user_group
118 user_groups=c.auth_user.user_groups_admin or c.can_create_user_group
119 %>
119 %>
120
120
121 %if repositories:
121 %if repositories:
122 <li class="${is_active('repositories')} local-admin-repos"><a href="${h.route_path('repos')}">${_('Repositories')}</a></li>
122 <li class="${is_active('repositories')} local-admin-repos"><a href="${h.route_path('repos')}">${_('Repositories')}</a></li>
123 %endif
123 %endif
124 %if repository_groups:
124 %if repository_groups:
125 <li class="${is_active('repository_groups')} local-admin-repo-groups"><a href="${h.route_path('repo_groups')}">${_('Repository groups')}</a></li>
125 <li class="${is_active('repository_groups')} local-admin-repo-groups"><a href="${h.route_path('repo_groups')}">${_('Repository groups')}</a></li>
126 %endif
126 %endif
127 %if user_groups:
127 %if user_groups:
128 <li class="${is_active('user_groups')} local-admin-user-groups"><a href="${h.route_path('user_groups')}">${_('User groups')}</a></li>
128 <li class="${is_active('user_groups')} local-admin-user-groups"><a href="${h.route_path('user_groups')}">${_('User groups')}</a></li>
129 %endif
129 %endif
130 % endif
130 % endif
131 </ul>
131 </ul>
132
132
133 </div>
133 </div>
134 <div class="clear"></div>
134 <div class="clear"></div>
135 </div>
135 </div>
136 </%def>
136 </%def>
137
137
138 <%def name="dt_info_panel(elements)">
138 <%def name="dt_info_panel(elements)">
139 <dl class="dl-horizontal">
139 <dl class="dl-horizontal">
140 %for dt, dd, title, show_items in elements:
140 %for dt, dd, title, show_items in elements:
141 <dt>${dt}:</dt>
141 <dt>${dt}:</dt>
142 <dd title="${h.tooltip(title)}">
142 <dd title="${h.tooltip(title)}">
143 %if callable(dd):
143 %if callable(dd):
144 ## allow lazy evaluation of elements
144 ## allow lazy evaluation of elements
145 ${dd()}
145 ${dd()}
146 %else:
146 %else:
147 ${dd}
147 ${dd}
148 %endif
148 %endif
149 %if show_items:
149 %if show_items:
150 <span class="btn-collapse" data-toggle="item-${h.md5_safe(dt)[:6]}-details">${_('Show More')} </span>
150 <span class="btn-collapse" data-toggle="item-${h.md5_safe(dt)[:6]}-details">${_('Show More')} </span>
151 %endif
151 %endif
152 </dd>
152 </dd>
153
153
154 %if show_items:
154 %if show_items:
155 <div class="collapsable-content" data-toggle="item-${h.md5_safe(dt)[:6]}-details" style="display: none">
155 <div class="collapsable-content" data-toggle="item-${h.md5_safe(dt)[:6]}-details" style="display: none">
156 %for item in show_items:
156 %for item in show_items:
157 <dt></dt>
157 <dt></dt>
158 <dd>${item}</dd>
158 <dd>${item}</dd>
159 %endfor
159 %endfor
160 </div>
160 </div>
161 %endif
161 %endif
162
162
163 %endfor
163 %endfor
164 </dl>
164 </dl>
165 </%def>
165 </%def>
166
166
167 <%def name="tr_info_entry(element)">
168 <% key, val, title, show_items = element %>
169
170 <tr>
171 <td style="vertical-align: top">${key}</td>
172 <td title="${h.tooltip(title)}">
173 %if callable(val):
174 ## allow lazy evaluation of elements
175 ${val()}
176 %else:
177 ${val}
178 %endif
179 %if show_items:
180 <div class="collapsable-content" data-toggle="item-${h.md5_safe(val)[:6]}-details" style="display: none">
181 % for item in show_items:
182 <dt></dt>
183 <dd>${item}</dd>
184 % endfor
185 </div>
186 %endif
187 </td>
188 <td style="vertical-align: top">
189 %if show_items:
190 <span class="btn-collapse" data-toggle="item-${h.md5_safe(val)[:6]}-details">${_('Show More')} </span>
191 %endif
192 </td>
193 </tr>
194
195 </%def>
196
167 <%def name="gravatar(email, size=16)">
197 <%def name="gravatar(email, size=16)">
168 <%
198 <%
169 if (size > 16):
199 if (size > 16):
170 gravatar_class = 'gravatar gravatar-large'
200 gravatar_class = 'gravatar gravatar-large'
171 else:
201 else:
172 gravatar_class = 'gravatar'
202 gravatar_class = 'gravatar'
173 %>
203 %>
174 <%doc>
204 <%doc>
175 TODO: johbo: For now we serve double size images to make it smooth
205 TODO: johbo: For now we serve double size images to make it smooth
176 for retina. This is how it worked until now. Should be replaced
206 for retina. This is how it worked until now. Should be replaced
177 with a better solution at some point.
207 with a better solution at some point.
178 </%doc>
208 </%doc>
179 <img class="${gravatar_class}" src="${h.gravatar_url(email, size * 2)}" height="${size}" width="${size}">
209 <img class="${gravatar_class}" src="${h.gravatar_url(email, size * 2)}" height="${size}" width="${size}">
180 </%def>
210 </%def>
181
211
182
212
183 <%def name="gravatar_with_user(contact, size=16, show_disabled=False)">
213 <%def name="gravatar_with_user(contact, size=16, show_disabled=False)">
184 <% email = h.email_or_none(contact) %>
214 <% email = h.email_or_none(contact) %>
185 <div class="rc-user tooltip" title="${h.tooltip(h.author_string(email))}">
215 <div class="rc-user tooltip" title="${h.tooltip(h.author_string(email))}">
186 ${self.gravatar(email, size)}
216 ${self.gravatar(email, size)}
187 <span class="${'user user-disabled' if show_disabled else 'user'}"> ${h.link_to_user(contact)}</span>
217 <span class="${'user user-disabled' if show_disabled else 'user'}"> ${h.link_to_user(contact)}</span>
188 </div>
218 </div>
189 </%def>
219 </%def>
190
220
191
221
192 <%def name="repo_page_title(repo_instance)">
222 <%def name="repo_page_title(repo_instance)">
193 <div class="title-content repo-title">
223 <div class="title-content repo-title">
194
224
195 <div class="title-main">
225 <div class="title-main">
196 ## SVN/HG/GIT icons
226 ## SVN/HG/GIT icons
197 %if h.is_hg(repo_instance):
227 %if h.is_hg(repo_instance):
198 <i class="icon-hg"></i>
228 <i class="icon-hg"></i>
199 %endif
229 %endif
200 %if h.is_git(repo_instance):
230 %if h.is_git(repo_instance):
201 <i class="icon-git"></i>
231 <i class="icon-git"></i>
202 %endif
232 %endif
203 %if h.is_svn(repo_instance):
233 %if h.is_svn(repo_instance):
204 <i class="icon-svn"></i>
234 <i class="icon-svn"></i>
205 %endif
235 %endif
206
236
207 ## public/private
237 ## public/private
208 %if repo_instance.private:
238 %if repo_instance.private:
209 <i class="icon-repo-private"></i>
239 <i class="icon-repo-private"></i>
210 %else:
240 %else:
211 <i class="icon-repo-public"></i>
241 <i class="icon-repo-public"></i>
212 %endif
242 %endif
213
243
214 ## repo name with group name
244 ## repo name with group name
215 ${h.breadcrumb_repo_link(repo_instance)}
245 ${h.breadcrumb_repo_link(repo_instance)}
216
246
217 ## Context Actions
247 ## Context Actions
218 <div class="pull-right">
248 <div class="pull-right">
219 %if c.rhodecode_user.username != h.DEFAULT_USER:
249 %if c.rhodecode_user.username != h.DEFAULT_USER:
220 <a href="${h.route_path('atom_feed_home', repo_name=c.rhodecode_db_repo.repo_uid, _query=dict(auth_token=c.rhodecode_user.feed_token))}" title="${_('RSS Feed')}" class="btn btn-sm"><i class="icon-rss-sign"></i>RSS</a>
250 <a href="${h.route_path('atom_feed_home', repo_name=c.rhodecode_db_repo.repo_uid, _query=dict(auth_token=c.rhodecode_user.feed_token))}" title="${_('RSS Feed')}" class="btn btn-sm"><i class="icon-rss-sign"></i>RSS</a>
221
251
222 <a href="#WatchRepo" onclick="toggleFollowingRepo(this, templateContext.repo_id); return false" title="${_('Watch this Repository and actions on it in your personalized journal')}" class="btn btn-sm ${('watching' if c.repository_is_user_following else '')}">
252 <a href="#WatchRepo" onclick="toggleFollowingRepo(this, templateContext.repo_id); return false" title="${_('Watch this Repository and actions on it in your personalized journal')}" class="btn btn-sm ${('watching' if c.repository_is_user_following else '')}">
223 % if c.repository_is_user_following:
253 % if c.repository_is_user_following:
224 <i class="icon-eye-off"></i>${_('Unwatch')}
254 <i class="icon-eye-off"></i>${_('Unwatch')}
225 % else:
255 % else:
226 <i class="icon-eye"></i>${_('Watch')}
256 <i class="icon-eye"></i>${_('Watch')}
227 % endif
257 % endif
228
258
229 </a>
259 </a>
230 %else:
260 %else:
231 <a href="${h.route_path('atom_feed_home', repo_name=c.rhodecode_db_repo.repo_uid)}" title="${_('RSS Feed')}" class="btn btn-sm"><i class="icon-rss-sign"></i>RSS</a>
261 <a href="${h.route_path('atom_feed_home', repo_name=c.rhodecode_db_repo.repo_uid)}" title="${_('RSS Feed')}" class="btn btn-sm"><i class="icon-rss-sign"></i>RSS</a>
232 %endif
262 %endif
233 </div>
263 </div>
234
264
235 </div>
265 </div>
236
266
237 ## FORKED
267 ## FORKED
238 %if repo_instance.fork:
268 %if repo_instance.fork:
239 <p class="discreet">
269 <p class="discreet">
240 <i class="icon-code-fork"></i> ${_('Fork of')}
270 <i class="icon-code-fork"></i> ${_('Fork of')}
241 ${h.link_to_if(c.has_origin_repo_read_perm,repo_instance.fork.repo_name, h.route_path('repo_summary', repo_name=repo_instance.fork.repo_name))}
271 ${h.link_to_if(c.has_origin_repo_read_perm,repo_instance.fork.repo_name, h.route_path('repo_summary', repo_name=repo_instance.fork.repo_name))}
242 </p>
272 </p>
243 %endif
273 %endif
244
274
245 ## IMPORTED FROM REMOTE
275 ## IMPORTED FROM REMOTE
246 %if repo_instance.clone_uri:
276 %if repo_instance.clone_uri:
247 <p class="discreet">
277 <p class="discreet">
248 <i class="icon-code-fork"></i> ${_('Clone from')}
278 <i class="icon-code-fork"></i> ${_('Clone from')}
249 <a href="${h.safe_str(h.hide_credentials(repo_instance.clone_uri))}">${h.hide_credentials(repo_instance.clone_uri)}</a>
279 <a href="${h.safe_str(h.hide_credentials(repo_instance.clone_uri))}">${h.hide_credentials(repo_instance.clone_uri)}</a>
250 </p>
280 </p>
251 %endif
281 %endif
252
282
253 ## LOCKING STATUS
283 ## LOCKING STATUS
254 %if repo_instance.locked[0]:
284 %if repo_instance.locked[0]:
255 <p class="locking_locked discreet">
285 <p class="locking_locked discreet">
256 <i class="icon-repo-lock"></i>
286 <i class="icon-repo-lock"></i>
257 ${_('Repository locked by %(user)s') % {'user': h.person_by_id(repo_instance.locked[0])}}
287 ${_('Repository locked by %(user)s') % {'user': h.person_by_id(repo_instance.locked[0])}}
258 </p>
288 </p>
259 %elif repo_instance.enable_locking:
289 %elif repo_instance.enable_locking:
260 <p class="locking_unlocked discreet">
290 <p class="locking_unlocked discreet">
261 <i class="icon-repo-unlock"></i>
291 <i class="icon-repo-unlock"></i>
262 ${_('Repository not locked. Pull repository to lock it.')}
292 ${_('Repository not locked. Pull repository to lock it.')}
263 </p>
293 </p>
264 %endif
294 %endif
265
295
266 </div>
296 </div>
267 </%def>
297 </%def>
268
298
269 <%def name="repo_menu(active=None)">
299 <%def name="repo_menu(active=None)">
270 <%
300 <%
271 def is_active(selected):
301 def is_active(selected):
272 if selected == active:
302 if selected == active:
273 return "active"
303 return "active"
274 ## determine if we have "any" option available
304 ## determine if we have "any" option available
275 can_lock = h.HasRepoPermissionAny('repository.write','repository.admin')(c.repo_name) and c.rhodecode_db_repo.enable_locking
305 can_lock = h.HasRepoPermissionAny('repository.write','repository.admin')(c.repo_name) and c.rhodecode_db_repo.enable_locking
276 has_actions = can_lock
306 has_actions = can_lock
277
307
278 %>
308 %>
279 % if c.rhodecode_db_repo.archived:
309 % if c.rhodecode_db_repo.archived:
280 <div class="alert alert-warning text-center">
310 <div class="alert alert-warning text-center">
281 <strong>${_('This repository has been archived. It is now read-only.')}</strong>
311 <strong>${_('This repository has been archived. It is now read-only.')}</strong>
282 </div>
312 </div>
283 % endif
313 % endif
284
314
285 <!--- REPO CONTEXT BAR -->
315 <!--- REPO CONTEXT BAR -->
286 <div id="context-bar">
316 <div id="context-bar">
287 <div class="wrapper">
317 <div class="wrapper">
288
318
289 <div class="title">
319 <div class="title">
290 ${self.repo_page_title(c.rhodecode_db_repo)}
320 ${self.repo_page_title(c.rhodecode_db_repo)}
291 </div>
321 </div>
292
322
293 <ul id="context-pages" class="navigation horizontal-list">
323 <ul id="context-pages" class="navigation horizontal-list">
294 <li class="${is_active('summary')}"><a class="menulink" href="${h.route_path('repo_summary', repo_name=c.repo_name)}"><div class="menulabel">${_('Summary')}</div></a></li>
324 <li class="${is_active('summary')}"><a class="menulink" href="${h.route_path('repo_summary', repo_name=c.repo_name)}"><div class="menulabel">${_('Summary')}</div></a></li>
295 <li class="${is_active('commits')}"><a class="menulink" href="${h.route_path('repo_commits', repo_name=c.repo_name)}"><div class="menulabel">${_('Commits')}</div></a></li>
325 <li class="${is_active('commits')}"><a class="menulink" href="${h.route_path('repo_commits', repo_name=c.repo_name)}"><div class="menulabel">${_('Commits')}</div></a></li>
296 <li class="${is_active('files')}"><a class="menulink" href="${h.route_path('repo_files', repo_name=c.repo_name, commit_id=c.rhodecode_db_repo.landing_rev[1], f_path='')}"><div class="menulabel">${_('Files')}</div></a></li>
326 <li class="${is_active('files')}"><a class="menulink" href="${h.route_path('repo_files', repo_name=c.repo_name, commit_id=c.rhodecode_db_repo.landing_rev[1], f_path='')}"><div class="menulabel">${_('Files')}</div></a></li>
297 <li class="${is_active('compare')}"><a class="menulink" href="${h.route_path('repo_compare_select',repo_name=c.repo_name)}"><div class="menulabel">${_('Compare')}</div></a></li>
327 <li class="${is_active('compare')}"><a class="menulink" href="${h.route_path('repo_compare_select',repo_name=c.repo_name)}"><div class="menulabel">${_('Compare')}</div></a></li>
298
328
299 ## TODO: anderson: ideally it would have a function on the scm_instance "enable_pullrequest() and enable_fork()"
329 ## TODO: anderson: ideally it would have a function on the scm_instance "enable_pullrequest() and enable_fork()"
300 %if c.rhodecode_db_repo.repo_type in ['git','hg']:
330 %if c.rhodecode_db_repo.repo_type in ['git','hg']:
301 <li class="${is_active('showpullrequest')}">
331 <li class="${is_active('showpullrequest')}">
302 <a class="menulink" href="${h.route_path('pullrequest_show_all', repo_name=c.repo_name)}" title="${h.tooltip(_('Show Pull Requests for %s') % c.repo_name)}">
332 <a class="menulink" href="${h.route_path('pullrequest_show_all', repo_name=c.repo_name)}" title="${h.tooltip(_('Show Pull Requests for %s') % c.repo_name)}">
303 <div class="menulabel">
333 <div class="menulabel">
304 ${_('Pull Requests')} <span class="menulink-counter">${c.repository_pull_requests}</span>
334 ${_('Pull Requests')} <span class="menulink-counter">${c.repository_pull_requests}</span>
305 </div>
335 </div>
306 </a>
336 </a>
307 </li>
337 </li>
308 %endif
338 %endif
309
339
310 <li class="${is_active('artifacts')}">
340 <li class="${is_active('artifacts')}">
311 <a class="menulink" href="${h.route_path('repo_artifacts_list',repo_name=c.repo_name)}">
341 <a class="menulink" href="${h.route_path('repo_artifacts_list',repo_name=c.repo_name)}">
312 <div class="menulabel">
342 <div class="menulabel">
313 ${_('Artifacts')} <span class="menulink-counter">${c.repository_artifacts}</span>
343 ${_('Artifacts')} <span class="menulink-counter">${c.repository_artifacts}</span>
314 </div>
344 </div>
315 </a>
345 </a>
316 </li>
346 </li>
317
347
318 %if h.HasRepoPermissionAll('repository.admin')(c.repo_name):
348 %if h.HasRepoPermissionAll('repository.admin')(c.repo_name):
319 <li class="${is_active('settings')}"><a class="menulink" href="${h.route_path('edit_repo',repo_name=c.repo_name)}"><div class="menulabel">${_('Repository Settings')}</div></a></li>
349 <li class="${is_active('settings')}"><a class="menulink" href="${h.route_path('edit_repo',repo_name=c.repo_name)}"><div class="menulabel">${_('Repository Settings')}</div></a></li>
320 %endif
350 %endif
321
351
322 <li class="${is_active('options')}">
352 <li class="${is_active('options')}">
323 % if has_actions:
353 % if has_actions:
324 <a class="menulink dropdown">
354 <a class="menulink dropdown">
325 <div class="menulabel">${_('Options')}<div class="show_more"></div></div>
355 <div class="menulabel">${_('Options')}<div class="show_more"></div></div>
326 </a>
356 </a>
327 <ul class="submenu">
357 <ul class="submenu">
328 %if can_lock:
358 %if can_lock:
329 %if c.rhodecode_db_repo.locked[0]:
359 %if c.rhodecode_db_repo.locked[0]:
330 <li><a class="locking_del" href="${h.route_path('repo_edit_toggle_locking',repo_name=c.repo_name)}">${_('Unlock Repository')}</a></li>
360 <li><a class="locking_del" href="${h.route_path('repo_edit_toggle_locking',repo_name=c.repo_name)}">${_('Unlock Repository')}</a></li>
331 %else:
361 %else:
332 <li><a class="locking_add" href="${h.route_path('repo_edit_toggle_locking',repo_name=c.repo_name)}">${_('Lock Repository')}</a></li>
362 <li><a class="locking_add" href="${h.route_path('repo_edit_toggle_locking',repo_name=c.repo_name)}">${_('Lock Repository')}</a></li>
333 %endif
363 %endif
334 %endif
364 %endif
335 </ul>
365 </ul>
336 % else:
366 % else:
337 <a class="menulink disabled">
367 <a class="menulink disabled">
338 <div class="menulabel">${_('Options')}<div class="show_more"></div></div>
368 <div class="menulabel">${_('Options')}<div class="show_more"></div></div>
339 </a>
369 </a>
340 % endif
370 % endif
341 </li>
371 </li>
342
372
343 </ul>
373 </ul>
344 </div>
374 </div>
345 <div class="clear"></div>
375 <div class="clear"></div>
346 </div>
376 </div>
347
377
348 <!--- REPO END CONTEXT BAR -->
378 <!--- REPO END CONTEXT BAR -->
349
379
350 </%def>
380 </%def>
351
381
352 <%def name="repo_group_page_title(repo_group_instance)">
382 <%def name="repo_group_page_title(repo_group_instance)">
353 <div class="title-content">
383 <div class="title-content">
354 <div class="title-main">
384 <div class="title-main">
355 ## Repository Group icon
385 ## Repository Group icon
356 <i class="icon-repo-group"></i>
386 <i class="icon-repo-group"></i>
357
387
358 ## repo name with group name
388 ## repo name with group name
359 ${h.breadcrumb_repo_group_link(repo_group_instance)}
389 ${h.breadcrumb_repo_group_link(repo_group_instance)}
360 </div>
390 </div>
361
391
362 <%namespace name="dt" file="/data_table/_dt_elements.mako"/>
392 <%namespace name="dt" file="/data_table/_dt_elements.mako"/>
363 <div class="repo-group-desc discreet">
393 <div class="repo-group-desc discreet">
364 ${dt.repo_group_desc(repo_group_instance.description_safe, repo_group_instance.personal, c.visual.stylify_metatags)}
394 ${dt.repo_group_desc(repo_group_instance.description_safe, repo_group_instance.personal, c.visual.stylify_metatags)}
365 </div>
395 </div>
366
396
367 </div>
397 </div>
368 </%def>
398 </%def>
369
399
370
400
371 <%def name="repo_group_menu(active=None)">
401 <%def name="repo_group_menu(active=None)">
372 <%
402 <%
373 def is_active(selected):
403 def is_active(selected):
374 if selected == active:
404 if selected == active:
375 return "active"
405 return "active"
376
406
377 gr_name = c.repo_group.group_name if c.repo_group else None
407 gr_name = c.repo_group.group_name if c.repo_group else None
378 # create repositories with write permission on group is set to true
408 # create repositories with write permission on group is set to true
379 group_admin = h.HasRepoGroupPermissionAny('group.admin')(gr_name, 'group admin index page')
409 group_admin = h.HasRepoGroupPermissionAny('group.admin')(gr_name, 'group admin index page')
380
410
381 %>
411 %>
382
412
383
413
384 <!--- REPO GROUP CONTEXT BAR -->
414 <!--- REPO GROUP CONTEXT BAR -->
385 <div id="context-bar">
415 <div id="context-bar">
386 <div class="wrapper">
416 <div class="wrapper">
387 <div class="title">
417 <div class="title">
388 ${self.repo_group_page_title(c.repo_group)}
418 ${self.repo_group_page_title(c.repo_group)}
389 </div>
419 </div>
390
420
391 <ul id="context-pages" class="navigation horizontal-list">
421 <ul id="context-pages" class="navigation horizontal-list">
392 <li class="${is_active('home')}">
422 <li class="${is_active('home')}">
393 <a class="menulink" href="${h.route_path('repo_group_home', repo_group_name=c.repo_group.group_name)}"><div class="menulabel">${_('Group Home')}</div></a>
423 <a class="menulink" href="${h.route_path('repo_group_home', repo_group_name=c.repo_group.group_name)}"><div class="menulabel">${_('Group Home')}</div></a>
394 </li>
424 </li>
395 % if c.is_super_admin or group_admin:
425 % if c.is_super_admin or group_admin:
396 <li class="${is_active('settings')}">
426 <li class="${is_active('settings')}">
397 <a class="menulink" href="${h.route_path('edit_repo_group',repo_group_name=c.repo_group.group_name)}" title="${_('You have admin right to this group, and can edit it')}"><div class="menulabel">${_('Group Settings')}</div></a>
427 <a class="menulink" href="${h.route_path('edit_repo_group',repo_group_name=c.repo_group.group_name)}" title="${_('You have admin right to this group, and can edit it')}"><div class="menulabel">${_('Group Settings')}</div></a>
398 </li>
428 </li>
399 % endif
429 % endif
400
430
401 </ul>
431 </ul>
402 </div>
432 </div>
403 <div class="clear"></div>
433 <div class="clear"></div>
404 </div>
434 </div>
405
435
406 <!--- REPO GROUP CONTEXT BAR -->
436 <!--- REPO GROUP CONTEXT BAR -->
407
437
408 </%def>
438 </%def>
409
439
410
440
411 <%def name="usermenu(active=False)">
441 <%def name="usermenu(active=False)">
412 <%
442 <%
413 not_anonymous = c.rhodecode_user.username != h.DEFAULT_USER
443 not_anonymous = c.rhodecode_user.username != h.DEFAULT_USER
414
444
415 gr_name = c.repo_group.group_name if (hasattr(c, 'repo_group') and c.repo_group) else None
445 gr_name = c.repo_group.group_name if (hasattr(c, 'repo_group') and c.repo_group) else None
416 # create repositories with write permission on group is set to true
446 # create repositories with write permission on group is set to true
417
447
418 can_fork = c.is_super_admin or h.HasPermissionAny('hg.fork.repository')()
448 can_fork = c.is_super_admin or h.HasPermissionAny('hg.fork.repository')()
419 create_on_write = h.HasPermissionAny('hg.create.write_on_repogroup.true')()
449 create_on_write = h.HasPermissionAny('hg.create.write_on_repogroup.true')()
420 group_write = h.HasRepoGroupPermissionAny('group.write')(gr_name, 'can write into group index page')
450 group_write = h.HasRepoGroupPermissionAny('group.write')(gr_name, 'can write into group index page')
421 group_admin = h.HasRepoGroupPermissionAny('group.admin')(gr_name, 'group admin index page')
451 group_admin = h.HasRepoGroupPermissionAny('group.admin')(gr_name, 'group admin index page')
422
452
423 can_create_repos = c.is_super_admin or c.can_create_repo
453 can_create_repos = c.is_super_admin or c.can_create_repo
424 can_create_repo_groups = c.is_super_admin or c.can_create_repo_group
454 can_create_repo_groups = c.is_super_admin or c.can_create_repo_group
425
455
426 can_create_repos_in_group = c.is_super_admin or group_admin or (group_write and create_on_write)
456 can_create_repos_in_group = c.is_super_admin or group_admin or (group_write and create_on_write)
427 can_create_repo_groups_in_group = c.is_super_admin or group_admin
457 can_create_repo_groups_in_group = c.is_super_admin or group_admin
428 %>
458 %>
429
459
430 % if not_anonymous:
460 % if not_anonymous:
431 <%
461 <%
432 default_target_group = dict()
462 default_target_group = dict()
433 if c.rhodecode_user.personal_repo_group:
463 if c.rhodecode_user.personal_repo_group:
434 default_target_group = dict(parent_group=c.rhodecode_user.personal_repo_group.group_id)
464 default_target_group = dict(parent_group=c.rhodecode_user.personal_repo_group.group_id)
435 %>
465 %>
436
466
437 ## create action
467 ## create action
438 <li>
468 <li>
439 <a href="#create-actions" onclick="return false;" class="menulink childs">
469 <a href="#create-actions" onclick="return false;" class="menulink childs">
440 <i class="icon-plus-circled"></i>
470 <i class="icon-plus-circled"></i>
441 </a>
471 </a>
442
472
443 <div class="action-menu submenu">
473 <div class="action-menu submenu">
444
474
445 <ol>
475 <ol>
446 ## scope of within a repository
476 ## scope of within a repository
447 % if hasattr(c, 'rhodecode_db_repo') and c.rhodecode_db_repo:
477 % if hasattr(c, 'rhodecode_db_repo') and c.rhodecode_db_repo:
448 <li class="submenu-title">${_('This Repository')}</li>
478 <li class="submenu-title">${_('This Repository')}</li>
449 <li>
479 <li>
450 <a href="${h.route_path('pullrequest_new',repo_name=c.repo_name)}">${_('Create Pull Request')}</a>
480 <a href="${h.route_path('pullrequest_new',repo_name=c.repo_name)}">${_('Create Pull Request')}</a>
451 </li>
481 </li>
452 % if can_fork:
482 % if can_fork:
453 <li>
483 <li>
454 <a href="${h.route_path('repo_fork_new',repo_name=c.repo_name,_query=default_target_group)}">${_('Fork this repository')}</a>
484 <a href="${h.route_path('repo_fork_new',repo_name=c.repo_name,_query=default_target_group)}">${_('Fork this repository')}</a>
455 </li>
485 </li>
456 % endif
486 % endif
457 % endif
487 % endif
458
488
459 ## scope of within repository groups
489 ## scope of within repository groups
460 % if hasattr(c, 'repo_group') and c.repo_group and (can_create_repos_in_group or can_create_repo_groups_in_group):
490 % if hasattr(c, 'repo_group') and c.repo_group and (can_create_repos_in_group or can_create_repo_groups_in_group):
461 <li class="submenu-title">${_('This Repository Group')}</li>
491 <li class="submenu-title">${_('This Repository Group')}</li>
462
492
463 % if can_create_repos_in_group:
493 % if can_create_repos_in_group:
464 <li>
494 <li>
465 <a href="${h.route_path('repo_new',_query=default_target_group)}">${_('New Repository')}</a>
495 <a href="${h.route_path('repo_new',_query=default_target_group)}">${_('New Repository')}</a>
466 </li>
496 </li>
467 % endif
497 % endif
468
498
469 % if can_create_repo_groups_in_group:
499 % if can_create_repo_groups_in_group:
470 <li>
500 <li>
471 <a href="${h.route_path('repo_group_new',_query=default_target_group)}">${_(u'New Repository Group')}</a>
501 <a href="${h.route_path('repo_group_new',_query=default_target_group)}">${_(u'New Repository Group')}</a>
472 </li>
502 </li>
473 % endif
503 % endif
474 % endif
504 % endif
475
505
476 ## personal group
506 ## personal group
477 % if c.rhodecode_user.personal_repo_group:
507 % if c.rhodecode_user.personal_repo_group:
478 <li class="submenu-title">Personal Group</li>
508 <li class="submenu-title">Personal Group</li>
479
509
480 <li>
510 <li>
481 <a href="${h.route_path('repo_new',_query=dict(parent_group=c.rhodecode_user.personal_repo_group.group_id))}" >${_('New Repository')} </a>
511 <a href="${h.route_path('repo_new',_query=dict(parent_group=c.rhodecode_user.personal_repo_group.group_id))}" >${_('New Repository')} </a>
482 </li>
512 </li>
483
513
484 <li>
514 <li>
485 <a href="${h.route_path('repo_group_new',_query=dict(parent_group=c.rhodecode_user.personal_repo_group.group_id))}">${_('New Repository Group')} </a>
515 <a href="${h.route_path('repo_group_new',_query=dict(parent_group=c.rhodecode_user.personal_repo_group.group_id))}">${_('New Repository Group')} </a>
486 </li>
516 </li>
487 % endif
517 % endif
488
518
489 ## Global actions
519 ## Global actions
490 <li class="submenu-title">RhodeCode</li>
520 <li class="submenu-title">RhodeCode</li>
491 % if can_create_repos:
521 % if can_create_repos:
492 <li>
522 <li>
493 <a href="${h.route_path('repo_new')}" >${_('New Repository')}</a>
523 <a href="${h.route_path('repo_new')}" >${_('New Repository')}</a>
494 </li>
524 </li>
495 % endif
525 % endif
496
526
497 % if can_create_repo_groups:
527 % if can_create_repo_groups:
498 <li>
528 <li>
499 <a href="${h.route_path('repo_group_new')}" >${_(u'New Repository Group')}</a>
529 <a href="${h.route_path('repo_group_new')}" >${_(u'New Repository Group')}</a>
500 </li>
530 </li>
501 % endif
531 % endif
502
532
503 <li>
533 <li>
504 <a href="${h.route_path('gists_new')}">${_(u'New Gist')}</a>
534 <a href="${h.route_path('gists_new')}">${_(u'New Gist')}</a>
505 </li>
535 </li>
506
536
507 </ol>
537 </ol>
508
538
509 </div>
539 </div>
510 </li>
540 </li>
511
541
512 ## notifications
542 ## notifications
513 <li>
543 <li>
514 <a class="${('empty' if c.unread_notifications == 0 else '')}" href="${h.route_path('notifications_show_all')}">
544 <a class="${('empty' if c.unread_notifications == 0 else '')}" href="${h.route_path('notifications_show_all')}">
515 ${c.unread_notifications}
545 ${c.unread_notifications}
516 </a>
546 </a>
517 </li>
547 </li>
518 % endif
548 % endif
519
549
520 ## USER MENU
550 ## USER MENU
521 <li id="quick_login_li" class="${'active' if active else ''}">
551 <li id="quick_login_li" class="${'active' if active else ''}">
522 % if c.rhodecode_user.username == h.DEFAULT_USER:
552 % if c.rhodecode_user.username == h.DEFAULT_USER:
523 <a id="quick_login_link" class="menulink childs" href="${h.route_path('login', _query={'came_from': h.current_route_path(request)})}">
553 <a id="quick_login_link" class="menulink childs" href="${h.route_path('login', _query={'came_from': h.current_route_path(request)})}">
524 ${gravatar(c.rhodecode_user.email, 20)}
554 ${gravatar(c.rhodecode_user.email, 20)}
525 <span class="user">
555 <span class="user">
526 <span>${_('Sign in')}</span>
556 <span>${_('Sign in')}</span>
527 </span>
557 </span>
528 </a>
558 </a>
529 % else:
559 % else:
530 ## logged in user
560 ## logged in user
531 <a id="quick_login_link" class="menulink childs">
561 <a id="quick_login_link" class="menulink childs">
532 ${gravatar(c.rhodecode_user.email, 20)}
562 ${gravatar(c.rhodecode_user.email, 20)}
533 <span class="user">
563 <span class="user">
534 <span class="menu_link_user">${c.rhodecode_user.username}</span>
564 <span class="menu_link_user">${c.rhodecode_user.username}</span>
535 <div class="show_more"></div>
565 <div class="show_more"></div>
536 </span>
566 </span>
537 </a>
567 </a>
538 ## subnav with menu for logged in user
568 ## subnav with menu for logged in user
539 <div class="user-menu submenu">
569 <div class="user-menu submenu">
540 <div id="quick_login">
570 <div id="quick_login">
541 %if c.rhodecode_user.username != h.DEFAULT_USER:
571 %if c.rhodecode_user.username != h.DEFAULT_USER:
542 <div class="">
572 <div class="">
543 <div class="big_gravatar">${gravatar(c.rhodecode_user.email, 48)}</div>
573 <div class="big_gravatar">${gravatar(c.rhodecode_user.email, 48)}</div>
544 <div class="full_name">${c.rhodecode_user.full_name_or_username}</div>
574 <div class="full_name">${c.rhodecode_user.full_name_or_username}</div>
545 <div class="email">${c.rhodecode_user.email}</div>
575 <div class="email">${c.rhodecode_user.email}</div>
546 </div>
576 </div>
547 <div class="">
577 <div class="">
548 <ol class="links">
578 <ol class="links">
549 <li>${h.link_to(_(u'My account'),h.route_path('my_account_profile'))}</li>
579 <li>${h.link_to(_(u'My account'),h.route_path('my_account_profile'))}</li>
550 % if c.rhodecode_user.personal_repo_group:
580 % if c.rhodecode_user.personal_repo_group:
551 <li>${h.link_to(_(u'My personal group'), h.route_path('repo_group_home', repo_group_name=c.rhodecode_user.personal_repo_group.group_name))}</li>
581 <li>${h.link_to(_(u'My personal group'), h.route_path('repo_group_home', repo_group_name=c.rhodecode_user.personal_repo_group.group_name))}</li>
552 % endif
582 % endif
553 <li>${h.link_to(_(u'Pull Requests'), h.route_path('my_account_pullrequests'))}</li>
583 <li>${h.link_to(_(u'Pull Requests'), h.route_path('my_account_pullrequests'))}</li>
554
584
555 % if c.debug_style:
585 % if c.debug_style:
556 <li>
586 <li>
557 <a class="menulink" title="${_('Style')}" href="${h.route_path('debug_style_home')}">
587 <a class="menulink" title="${_('Style')}" href="${h.route_path('debug_style_home')}">
558 <div class="menulabel">${_('[Style]')}</div>
588 <div class="menulabel">${_('[Style]')}</div>
559 </a>
589 </a>
560 </li>
590 </li>
561 % endif
591 % endif
562
592
563 ## bookmark-items
593 ## bookmark-items
564 <li class="bookmark-items">
594 <li class="bookmark-items">
565 ${_('Bookmarks')}
595 ${_('Bookmarks')}
566 <div class="pull-right">
596 <div class="pull-right">
567 <a href="${h.route_path('my_account_bookmarks')}">
597 <a href="${h.route_path('my_account_bookmarks')}">
568
598
569 <i class="icon-cog"></i>
599 <i class="icon-cog"></i>
570 </a>
600 </a>
571 </div>
601 </div>
572 </li>
602 </li>
573 % if not c.bookmark_items:
603 % if not c.bookmark_items:
574 <li>
604 <li>
575 <a href="${h.route_path('my_account_bookmarks')}">${_('No Bookmarks yet.')}</a>
605 <a href="${h.route_path('my_account_bookmarks')}">${_('No Bookmarks yet.')}</a>
576 </li>
606 </li>
577 % endif
607 % endif
578 % for item in c.bookmark_items:
608 % for item in c.bookmark_items:
579 <li>
609 <li>
580 % if item.repository:
610 % if item.repository:
581 <div>
611 <div>
582 <a class="bookmark-item" href="${h.route_path('my_account_goto_bookmark', bookmark_id=item.position)}">
612 <a class="bookmark-item" href="${h.route_path('my_account_goto_bookmark', bookmark_id=item.position)}">
583 <code>${item.position}</code>
613 <code>${item.position}</code>
584 % if item.repository.repo_type == 'hg':
614 % if item.repository.repo_type == 'hg':
585 <i class="icon-hg" title="${_('Repository')}" style="font-size: 16px"></i>
615 <i class="icon-hg" title="${_('Repository')}" style="font-size: 16px"></i>
586 % elif item.repository.repo_type == 'git':
616 % elif item.repository.repo_type == 'git':
587 <i class="icon-git" title="${_('Repository')}" style="font-size: 16px"></i>
617 <i class="icon-git" title="${_('Repository')}" style="font-size: 16px"></i>
588 % elif item.repository.repo_type == 'svn':
618 % elif item.repository.repo_type == 'svn':
589 <i class="icon-svn" title="${_('Repository')}" style="font-size: 16px"></i>
619 <i class="icon-svn" title="${_('Repository')}" style="font-size: 16px"></i>
590 % endif
620 % endif
591 ${(item.title or h.shorter(item.repository.repo_name, 30))}
621 ${(item.title or h.shorter(item.repository.repo_name, 30))}
592 </a>
622 </a>
593 </div>
623 </div>
594 % elif item.repository_group:
624 % elif item.repository_group:
595 <div>
625 <div>
596 <a class="bookmark-item" href="${h.route_path('my_account_goto_bookmark', bookmark_id=item.position)}">
626 <a class="bookmark-item" href="${h.route_path('my_account_goto_bookmark', bookmark_id=item.position)}">
597 <code>${item.position}</code>
627 <code>${item.position}</code>
598 <i class="icon-repo-group" title="${_('Repository group')}" style="font-size: 14px"></i>
628 <i class="icon-repo-group" title="${_('Repository group')}" style="font-size: 14px"></i>
599 ${(item.title or h.shorter(item.repository_group.group_name, 30))}
629 ${(item.title or h.shorter(item.repository_group.group_name, 30))}
600 </a>
630 </a>
601 </div>
631 </div>
602 % else:
632 % else:
603 <a class="bookmark-item" href="${h.route_path('my_account_goto_bookmark', bookmark_id=item.position)}">
633 <a class="bookmark-item" href="${h.route_path('my_account_goto_bookmark', bookmark_id=item.position)}">
604 <code>${item.position}</code>
634 <code>${item.position}</code>
605 ${item.title}
635 ${item.title}
606 </a>
636 </a>
607 % endif
637 % endif
608 </li>
638 </li>
609 % endfor
639 % endfor
610
640
611 <li class="logout">
641 <li class="logout">
612 ${h.secure_form(h.route_path('logout'), request=request)}
642 ${h.secure_form(h.route_path('logout'), request=request)}
613 ${h.submit('log_out', _(u'Sign Out'),class_="btn btn-primary")}
643 ${h.submit('log_out', _(u'Sign Out'),class_="btn btn-primary")}
614 ${h.end_form()}
644 ${h.end_form()}
615 </li>
645 </li>
616 </ol>
646 </ol>
617 </div>
647 </div>
618 %endif
648 %endif
619 </div>
649 </div>
620 </div>
650 </div>
621
651
622 % endif
652 % endif
623 </li>
653 </li>
624 </%def>
654 </%def>
625
655
626 <%def name="menu_items(active=None)">
656 <%def name="menu_items(active=None)">
627 <%
657 <%
628 def is_active(selected):
658 def is_active(selected):
629 if selected == active:
659 if selected == active:
630 return "active"
660 return "active"
631 return ""
661 return ""
632 %>
662 %>
633
663
634 <ul id="quick" class="main_nav navigation horizontal-list">
664 <ul id="quick" class="main_nav navigation horizontal-list">
635 ## notice box for important system messages
665 ## notice box for important system messages
636 <li style="display: none">
666 <li style="display: none">
637 <a class="notice-box" href="#openNotice" onclick="return false">
667 <a class="notice-box" href="#openNotice" onclick="return false">
638 <div class="menulabel-notice" >
668 <div class="menulabel-notice" >
639 0
669 0
640 </div>
670 </div>
641 </a>
671 </a>
642 </li>
672 </li>
643
673
644 ## Main filter
674 ## Main filter
645 <li>
675 <li>
646 <div class="menulabel main_filter_box">
676 <div class="menulabel main_filter_box">
647 <div class="main_filter_input_box">
677 <div class="main_filter_input_box">
648 <ul class="searchItems">
678 <ul class="searchItems">
649
679
650 % if c.template_context['search_context']['repo_id']:
680 % if c.template_context['search_context']['repo_id']:
651 <li class="searchTag searchTagFilter searchTagHidable" >
681 <li class="searchTag searchTagFilter searchTagHidable" >
652 ##<a href="${h.route_path('search_repo',repo_name=c.template_context['search_context']['repo_name'])}">
682 ##<a href="${h.route_path('search_repo',repo_name=c.template_context['search_context']['repo_name'])}">
653 <span class="tag">
683 <span class="tag">
654 This repo
684 This repo
655 <a href="#removeGoToFilter" onclick="removeGoToFilter(); return false"><i class="icon-cancel-circled"></i></a>
685 <a href="#removeGoToFilter" onclick="removeGoToFilter(); return false"><i class="icon-cancel-circled"></i></a>
656 </span>
686 </span>
657 ##</a>
687 ##</a>
658 </li>
688 </li>
659 % elif c.template_context['search_context']['repo_group_id']:
689 % elif c.template_context['search_context']['repo_group_id']:
660 <li class="searchTag searchTagFilter searchTagHidable">
690 <li class="searchTag searchTagFilter searchTagHidable">
661 ##<a href="${h.route_path('search_repo_group',repo_group_name=c.template_context['search_context']['repo_group_name'])}">
691 ##<a href="${h.route_path('search_repo_group',repo_group_name=c.template_context['search_context']['repo_group_name'])}">
662 <span class="tag">
692 <span class="tag">
663 This group
693 This group
664 <a href="#removeGoToFilter" onclick="removeGoToFilter(); return false"><i class="icon-cancel-circled"></i></a>
694 <a href="#removeGoToFilter" onclick="removeGoToFilter(); return false"><i class="icon-cancel-circled"></i></a>
665 </span>
695 </span>
666 ##</a>
696 ##</a>
667 </li>
697 </li>
668 % endif
698 % endif
669
699
670 <li class="searchTagInput">
700 <li class="searchTagInput">
671 <input class="main_filter_input" id="main_filter" size="25" type="text" name="main_filter" placeholder="${_('search / go to...')}" value="" />
701 <input class="main_filter_input" id="main_filter" size="25" type="text" name="main_filter" placeholder="${_('search / go to...')}" value="" />
672 </li>
702 </li>
673 <li class="searchTag searchTagHelp">
703 <li class="searchTag searchTagHelp">
674 <a href="#showFilterHelp" onclick="showMainFilterBox(); return false">?</a>
704 <a href="#showFilterHelp" onclick="showMainFilterBox(); return false">?</a>
675 </li>
705 </li>
676 </ul>
706 </ul>
677 </div>
707 </div>
678 </div>
708 </div>
679
709
680 <div id="main_filter_help" style="display: none">
710 <div id="main_filter_help" style="display: none">
681 - Use '/' key to quickly access this field.
711 - Use '/' key to quickly access this field.
682
712
683 - Enter a name of repository, or repository group for quick search.
713 - Enter a name of repository, or repository group for quick search.
684
714
685 - Prefix query to allow special search:
715 - Prefix query to allow special search:
686
716
687 user:admin, to search for usernames, always global
717 user:admin, to search for usernames, always global
688
718
689 user_group:devops, to search for user groups, always global
719 user_group:devops, to search for user groups, always global
690
720
691 commit:efced4, to search for commits, scoped to repositories or groups
721 commit:efced4, to search for commits, scoped to repositories or groups
692
722
693 file:models.py, to search for file paths, scoped to repositories or groups
723 file:models.py, to search for file paths, scoped to repositories or groups
694
724
695 % if c.template_context['search_context']['repo_id']:
725 % if c.template_context['search_context']['repo_id']:
696 For advanced full text search visit: <a href="${h.route_path('search_repo',repo_name=c.template_context['search_context']['repo_name'])}">repository search</a>
726 For advanced full text search visit: <a href="${h.route_path('search_repo',repo_name=c.template_context['search_context']['repo_name'])}">repository search</a>
697 % elif c.template_context['search_context']['repo_group_id']:
727 % elif c.template_context['search_context']['repo_group_id']:
698 For advanced full text search visit: <a href="${h.route_path('search_repo_group',repo_group_name=c.template_context['search_context']['repo_group_name'])}">repository group search</a>
728 For advanced full text search visit: <a href="${h.route_path('search_repo_group',repo_group_name=c.template_context['search_context']['repo_group_name'])}">repository group search</a>
699 % else:
729 % else:
700 For advanced full text search visit: <a href="${h.route_path('search')}">global search</a>
730 For advanced full text search visit: <a href="${h.route_path('search')}">global search</a>
701 % endif
731 % endif
702 </div>
732 </div>
703 </li>
733 </li>
704
734
705 ## ROOT MENU
735 ## ROOT MENU
706 <li class="${is_active('home')}">
736 <li class="${is_active('home')}">
707 <a class="menulink" title="${_('Home')}" href="${h.route_path('home')}">
737 <a class="menulink" title="${_('Home')}" href="${h.route_path('home')}">
708 <div class="menulabel">${_('Home')}</div>
738 <div class="menulabel">${_('Home')}</div>
709 </a>
739 </a>
710 </li>
740 </li>
711
741
712 %if c.rhodecode_user.username != h.DEFAULT_USER:
742 %if c.rhodecode_user.username != h.DEFAULT_USER:
713 <li class="${is_active('journal')}">
743 <li class="${is_active('journal')}">
714 <a class="menulink" title="${_('Show activity journal')}" href="${h.route_path('journal')}">
744 <a class="menulink" title="${_('Show activity journal')}" href="${h.route_path('journal')}">
715 <div class="menulabel">${_('Journal')}</div>
745 <div class="menulabel">${_('Journal')}</div>
716 </a>
746 </a>
717 </li>
747 </li>
718 %else:
748 %else:
719 <li class="${is_active('journal')}">
749 <li class="${is_active('journal')}">
720 <a class="menulink" title="${_('Show Public activity journal')}" href="${h.route_path('journal_public')}">
750 <a class="menulink" title="${_('Show Public activity journal')}" href="${h.route_path('journal_public')}">
721 <div class="menulabel">${_('Public journal')}</div>
751 <div class="menulabel">${_('Public journal')}</div>
722 </a>
752 </a>
723 </li>
753 </li>
724 %endif
754 %endif
725
755
726 <li class="${is_active('gists')}">
756 <li class="${is_active('gists')}">
727 <a class="menulink childs" title="${_('Show Gists')}" href="${h.route_path('gists_show')}">
757 <a class="menulink childs" title="${_('Show Gists')}" href="${h.route_path('gists_show')}">
728 <div class="menulabel">${_('Gists')}</div>
758 <div class="menulabel">${_('Gists')}</div>
729 </a>
759 </a>
730 </li>
760 </li>
731
761
732 % if c.is_super_admin or c.is_delegated_admin:
762 % if c.is_super_admin or c.is_delegated_admin:
733 <li class="${is_active('admin')}">
763 <li class="${is_active('admin')}">
734 <a class="menulink childs" title="${_('Admin settings')}" href="${h.route_path('admin_home')}">
764 <a class="menulink childs" title="${_('Admin settings')}" href="${h.route_path('admin_home')}">
735 <div class="menulabel">${_('Admin')} </div>
765 <div class="menulabel">${_('Admin')} </div>
736 </a>
766 </a>
737 </li>
767 </li>
738 % endif
768 % endif
739
769
740 ## render extra user menu
770 ## render extra user menu
741 ${usermenu(active=(active=='my_account'))}
771 ${usermenu(active=(active=='my_account'))}
742
772
743 </ul>
773 </ul>
744
774
745 <script type="text/javascript">
775 <script type="text/javascript">
746 var visualShowPublicIcon = "${c.visual.show_public_icon}" == "True";
776 var visualShowPublicIcon = "${c.visual.show_public_icon}" == "True";
747
777
748 var formatRepoResult = function(result, container, query, escapeMarkup) {
778 var formatRepoResult = function(result, container, query, escapeMarkup) {
749 return function(data, escapeMarkup) {
779 return function(data, escapeMarkup) {
750 if (!data.repo_id){
780 if (!data.repo_id){
751 return data.text; // optgroup text Repositories
781 return data.text; // optgroup text Repositories
752 }
782 }
753
783
754 var tmpl = '';
784 var tmpl = '';
755 var repoType = data['repo_type'];
785 var repoType = data['repo_type'];
756 var repoName = data['text'];
786 var repoName = data['text'];
757
787
758 if(data && data.type == 'repo'){
788 if(data && data.type == 'repo'){
759 if(repoType === 'hg'){
789 if(repoType === 'hg'){
760 tmpl += '<i class="icon-hg"></i> ';
790 tmpl += '<i class="icon-hg"></i> ';
761 }
791 }
762 else if(repoType === 'git'){
792 else if(repoType === 'git'){
763 tmpl += '<i class="icon-git"></i> ';
793 tmpl += '<i class="icon-git"></i> ';
764 }
794 }
765 else if(repoType === 'svn'){
795 else if(repoType === 'svn'){
766 tmpl += '<i class="icon-svn"></i> ';
796 tmpl += '<i class="icon-svn"></i> ';
767 }
797 }
768 if(data['private']){
798 if(data['private']){
769 tmpl += '<i class="icon-lock" ></i> ';
799 tmpl += '<i class="icon-lock" ></i> ';
770 }
800 }
771 else if(visualShowPublicIcon){
801 else if(visualShowPublicIcon){
772 tmpl += '<i class="icon-unlock-alt"></i> ';
802 tmpl += '<i class="icon-unlock-alt"></i> ';
773 }
803 }
774 }
804 }
775 tmpl += escapeMarkup(repoName);
805 tmpl += escapeMarkup(repoName);
776 return tmpl;
806 return tmpl;
777
807
778 }(result, escapeMarkup);
808 }(result, escapeMarkup);
779 };
809 };
780
810
781 var formatRepoGroupResult = function(result, container, query, escapeMarkup) {
811 var formatRepoGroupResult = function(result, container, query, escapeMarkup) {
782 return function(data, escapeMarkup) {
812 return function(data, escapeMarkup) {
783 if (!data.repo_group_id){
813 if (!data.repo_group_id){
784 return data.text; // optgroup text Repositories
814 return data.text; // optgroup text Repositories
785 }
815 }
786
816
787 var tmpl = '';
817 var tmpl = '';
788 var repoGroupName = data['text'];
818 var repoGroupName = data['text'];
789
819
790 if(data){
820 if(data){
791
821
792 tmpl += '<i class="icon-repo-group"></i> ';
822 tmpl += '<i class="icon-repo-group"></i> ';
793
823
794 }
824 }
795 tmpl += escapeMarkup(repoGroupName);
825 tmpl += escapeMarkup(repoGroupName);
796 return tmpl;
826 return tmpl;
797
827
798 }(result, escapeMarkup);
828 }(result, escapeMarkup);
799 };
829 };
800
830
801 var escapeRegExChars = function (value) {
831 var escapeRegExChars = function (value) {
802 return value.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&");
832 return value.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&");
803 };
833 };
804
834
805 var getRepoIcon = function(repo_type) {
835 var getRepoIcon = function(repo_type) {
806 if (repo_type === 'hg') {
836 if (repo_type === 'hg') {
807 return '<i class="icon-hg"></i> ';
837 return '<i class="icon-hg"></i> ';
808 }
838 }
809 else if (repo_type === 'git') {
839 else if (repo_type === 'git') {
810 return '<i class="icon-git"></i> ';
840 return '<i class="icon-git"></i> ';
811 }
841 }
812 else if (repo_type === 'svn') {
842 else if (repo_type === 'svn') {
813 return '<i class="icon-svn"></i> ';
843 return '<i class="icon-svn"></i> ';
814 }
844 }
815 return ''
845 return ''
816 };
846 };
817
847
818 var autocompleteMainFilterFormatResult = function (data, value, org_formatter) {
848 var autocompleteMainFilterFormatResult = function (data, value, org_formatter) {
819
849
820 if (value.split(':').length === 2) {
850 if (value.split(':').length === 2) {
821 value = value.split(':')[1]
851 value = value.split(':')[1]
822 }
852 }
823
853
824 var searchType = data['type'];
854 var searchType = data['type'];
825 var searchSubType = data['subtype'];
855 var searchSubType = data['subtype'];
826 var valueDisplay = data['value_display'];
856 var valueDisplay = data['value_display'];
827
857
828 var pattern = '(' + escapeRegExChars(value) + ')';
858 var pattern = '(' + escapeRegExChars(value) + ')';
829
859
830 valueDisplay = Select2.util.escapeMarkup(valueDisplay);
860 valueDisplay = Select2.util.escapeMarkup(valueDisplay);
831
861
832 // highlight match
862 // highlight match
833 if (searchType != 'text') {
863 if (searchType != 'text') {
834 valueDisplay = valueDisplay.replace(new RegExp(pattern, 'gi'), '<strong>$1<\/strong>');
864 valueDisplay = valueDisplay.replace(new RegExp(pattern, 'gi'), '<strong>$1<\/strong>');
835 }
865 }
836
866
837 var icon = '';
867 var icon = '';
838
868
839 if (searchType === 'hint') {
869 if (searchType === 'hint') {
840 icon += '<i class="icon-repo-group"></i> ';
870 icon += '<i class="icon-repo-group"></i> ';
841 }
871 }
842 // full text search/hints
872 // full text search/hints
843 else if (searchType === 'search') {
873 else if (searchType === 'search') {
844 icon += '<i class="icon-more"></i> ';
874 icon += '<i class="icon-more"></i> ';
845 if (searchSubType !== undefined && searchSubType == 'repo') {
875 if (searchSubType !== undefined && searchSubType == 'repo') {
846 valueDisplay += '<div class="pull-right tag">repository</div>';
876 valueDisplay += '<div class="pull-right tag">repository</div>';
847 }
877 }
848 else if (searchSubType !== undefined && searchSubType == 'repo_group') {
878 else if (searchSubType !== undefined && searchSubType == 'repo_group') {
849 valueDisplay += '<div class="pull-right tag">repo group</div>';
879 valueDisplay += '<div class="pull-right tag">repo group</div>';
850 }
880 }
851 }
881 }
852 // repository
882 // repository
853 else if (searchType === 'repo') {
883 else if (searchType === 'repo') {
854
884
855 var repoIcon = getRepoIcon(data['repo_type']);
885 var repoIcon = getRepoIcon(data['repo_type']);
856 icon += repoIcon;
886 icon += repoIcon;
857
887
858 if (data['private']) {
888 if (data['private']) {
859 icon += '<i class="icon-lock" ></i> ';
889 icon += '<i class="icon-lock" ></i> ';
860 }
890 }
861 else if (visualShowPublicIcon) {
891 else if (visualShowPublicIcon) {
862 icon += '<i class="icon-unlock-alt"></i> ';
892 icon += '<i class="icon-unlock-alt"></i> ';
863 }
893 }
864 }
894 }
865 // repository groups
895 // repository groups
866 else if (searchType === 'repo_group') {
896 else if (searchType === 'repo_group') {
867 icon += '<i class="icon-repo-group"></i> ';
897 icon += '<i class="icon-repo-group"></i> ';
868 }
898 }
869 // user group
899 // user group
870 else if (searchType === 'user_group') {
900 else if (searchType === 'user_group') {
871 icon += '<i class="icon-group"></i> ';
901 icon += '<i class="icon-group"></i> ';
872 }
902 }
873 // user
903 // user
874 else if (searchType === 'user') {
904 else if (searchType === 'user') {
875 icon += '<img class="gravatar" src="{0}"/>'.format(data['icon_link']);
905 icon += '<img class="gravatar" src="{0}"/>'.format(data['icon_link']);
876 }
906 }
877 // commit
907 // commit
878 else if (searchType === 'commit') {
908 else if (searchType === 'commit') {
879 var repo_data = data['repo_data'];
909 var repo_data = data['repo_data'];
880 var repoIcon = getRepoIcon(repo_data['repository_type']);
910 var repoIcon = getRepoIcon(repo_data['repository_type']);
881 if (repoIcon) {
911 if (repoIcon) {
882 icon += repoIcon;
912 icon += repoIcon;
883 } else {
913 } else {
884 icon += '<i class="icon-tag"></i>';
914 icon += '<i class="icon-tag"></i>';
885 }
915 }
886 }
916 }
887 // file
917 // file
888 else if (searchType === 'file') {
918 else if (searchType === 'file') {
889 var repo_data = data['repo_data'];
919 var repo_data = data['repo_data'];
890 var repoIcon = getRepoIcon(repo_data['repository_type']);
920 var repoIcon = getRepoIcon(repo_data['repository_type']);
891 if (repoIcon) {
921 if (repoIcon) {
892 icon += repoIcon;
922 icon += repoIcon;
893 } else {
923 } else {
894 icon += '<i class="icon-tag"></i>';
924 icon += '<i class="icon-tag"></i>';
895 }
925 }
896 }
926 }
897 // generic text
927 // generic text
898 else if (searchType === 'text') {
928 else if (searchType === 'text') {
899 icon = '';
929 icon = '';
900 }
930 }
901
931
902 var tmpl = '<div class="ac-container-wrap">{0}{1}</div>';
932 var tmpl = '<div class="ac-container-wrap">{0}{1}</div>';
903 return tmpl.format(icon, valueDisplay);
933 return tmpl.format(icon, valueDisplay);
904 };
934 };
905
935
906 var handleSelect = function(element, suggestion) {
936 var handleSelect = function(element, suggestion) {
907 if (suggestion.type === "hint") {
937 if (suggestion.type === "hint") {
908 // we skip action
938 // we skip action
909 $('#main_filter').focus();
939 $('#main_filter').focus();
910 }
940 }
911 else if (suggestion.type === "text") {
941 else if (suggestion.type === "text") {
912 // we skip action
942 // we skip action
913 $('#main_filter').focus();
943 $('#main_filter').focus();
914
944
915 } else {
945 } else {
916 window.location = suggestion['url'];
946 window.location = suggestion['url'];
917 }
947 }
918 };
948 };
919
949
920 var autocompleteMainFilterResult = function (suggestion, originalQuery, queryLowerCase) {
950 var autocompleteMainFilterResult = function (suggestion, originalQuery, queryLowerCase) {
921 if (queryLowerCase.split(':').length === 2) {
951 if (queryLowerCase.split(':').length === 2) {
922 queryLowerCase = queryLowerCase.split(':')[1]
952 queryLowerCase = queryLowerCase.split(':')[1]
923 }
953 }
924 if (suggestion.type === "text") {
954 if (suggestion.type === "text") {
925 // special case we don't want to "skip" display for
955 // special case we don't want to "skip" display for
926 return true
956 return true
927 }
957 }
928 return suggestion.value_display.toLowerCase().indexOf(queryLowerCase) !== -1;
958 return suggestion.value_display.toLowerCase().indexOf(queryLowerCase) !== -1;
929 };
959 };
930
960
931 var cleanContext = {
961 var cleanContext = {
932 repo_view_type: null,
962 repo_view_type: null,
933
963
934 repo_id: null,
964 repo_id: null,
935 repo_name: "",
965 repo_name: "",
936
966
937 repo_group_id: null,
967 repo_group_id: null,
938 repo_group_name: null
968 repo_group_name: null
939 };
969 };
940 var removeGoToFilter = function () {
970 var removeGoToFilter = function () {
941 $('.searchTagHidable').hide();
971 $('.searchTagHidable').hide();
942 $('#main_filter').autocomplete(
972 $('#main_filter').autocomplete(
943 'setOptions', {params:{search_context: cleanContext}});
973 'setOptions', {params:{search_context: cleanContext}});
944 };
974 };
945
975
946 $('#main_filter').autocomplete({
976 $('#main_filter').autocomplete({
947 serviceUrl: pyroutes.url('goto_switcher_data'),
977 serviceUrl: pyroutes.url('goto_switcher_data'),
948 params: {
978 params: {
949 "search_context": templateContext.search_context
979 "search_context": templateContext.search_context
950 },
980 },
951 minChars:2,
981 minChars:2,
952 maxHeight:400,
982 maxHeight:400,
953 deferRequestBy: 300, //miliseconds
983 deferRequestBy: 300, //miliseconds
954 tabDisabled: true,
984 tabDisabled: true,
955 autoSelectFirst: false,
985 autoSelectFirst: false,
956 containerClass: 'autocomplete-qfilter-suggestions',
986 containerClass: 'autocomplete-qfilter-suggestions',
957 formatResult: autocompleteMainFilterFormatResult,
987 formatResult: autocompleteMainFilterFormatResult,
958 lookupFilter: autocompleteMainFilterResult,
988 lookupFilter: autocompleteMainFilterResult,
959 onSelect: function (element, suggestion) {
989 onSelect: function (element, suggestion) {
960 handleSelect(element, suggestion);
990 handleSelect(element, suggestion);
961 return false;
991 return false;
962 },
992 },
963 onSearchError: function (element, query, jqXHR, textStatus, errorThrown) {
993 onSearchError: function (element, query, jqXHR, textStatus, errorThrown) {
964 if (jqXHR !== 'abort') {
994 if (jqXHR !== 'abort') {
965 alert("Error during search.\nError code: {0}".format(textStatus));
995 alert("Error during search.\nError code: {0}".format(textStatus));
966 window.location = '';
996 window.location = '';
967 }
997 }
968 }
998 }
969 });
999 });
970
1000
971 showMainFilterBox = function () {
1001 showMainFilterBox = function () {
972 $('#main_filter_help').toggle();
1002 $('#main_filter_help').toggle();
973 };
1003 };
974
1004
975 $('#main_filter').on('keydown.autocomplete', function (e) {
1005 $('#main_filter').on('keydown.autocomplete', function (e) {
976
1006
977 var BACKSPACE = 8;
1007 var BACKSPACE = 8;
978 var el = $(e.currentTarget);
1008 var el = $(e.currentTarget);
979 if(e.which === BACKSPACE){
1009 if(e.which === BACKSPACE){
980 var inputVal = el.val();
1010 var inputVal = el.val();
981 if (inputVal === ""){
1011 if (inputVal === ""){
982 removeGoToFilter()
1012 removeGoToFilter()
983 }
1013 }
984 }
1014 }
985 });
1015 });
986
1016
987 </script>
1017 </script>
988 <script src="${h.asset('js/rhodecode/base/keyboard-bindings.js', ver=c.rhodecode_version_hash)}"></script>
1018 <script src="${h.asset('js/rhodecode/base/keyboard-bindings.js', ver=c.rhodecode_version_hash)}"></script>
989 </%def>
1019 </%def>
990
1020
991 <div class="modal" id="help_kb" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
1021 <div class="modal" id="help_kb" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
992 <div class="modal-dialog">
1022 <div class="modal-dialog">
993 <div class="modal-content">
1023 <div class="modal-content">
994 <div class="modal-header">
1024 <div class="modal-header">
995 <button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
1025 <button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
996 <h4 class="modal-title" id="myModalLabel">${_('Keyboard shortcuts')}</h4>
1026 <h4 class="modal-title" id="myModalLabel">${_('Keyboard shortcuts')}</h4>
997 </div>
1027 </div>
998 <div class="modal-body">
1028 <div class="modal-body">
999 <div class="block-left">
1029 <div class="block-left">
1000 <table class="keyboard-mappings">
1030 <table class="keyboard-mappings">
1001 <tbody>
1031 <tbody>
1002 <tr>
1032 <tr>
1003 <th></th>
1033 <th></th>
1004 <th>${_('Site-wide shortcuts')}</th>
1034 <th>${_('Site-wide shortcuts')}</th>
1005 </tr>
1035 </tr>
1006 <%
1036 <%
1007 elems = [
1037 elems = [
1008 ('/', 'Use quick search box'),
1038 ('/', 'Use quick search box'),
1009 ('g h', 'Goto home page'),
1039 ('g h', 'Goto home page'),
1010 ('g g', 'Goto my private gists page'),
1040 ('g g', 'Goto my private gists page'),
1011 ('g G', 'Goto my public gists page'),
1041 ('g G', 'Goto my public gists page'),
1012 ('g 0-9', 'Goto bookmarked items from 0-9'),
1042 ('g 0-9', 'Goto bookmarked items from 0-9'),
1013 ('n r', 'New repository page'),
1043 ('n r', 'New repository page'),
1014 ('n g', 'New gist page'),
1044 ('n g', 'New gist page'),
1015 ]
1045 ]
1016 %>
1046 %>
1017 %for key, desc in elems:
1047 %for key, desc in elems:
1018 <tr>
1048 <tr>
1019 <td class="keys">
1049 <td class="keys">
1020 <span class="key tag">${key}</span>
1050 <span class="key tag">${key}</span>
1021 </td>
1051 </td>
1022 <td>${desc}</td>
1052 <td>${desc}</td>
1023 </tr>
1053 </tr>
1024 %endfor
1054 %endfor
1025 </tbody>
1055 </tbody>
1026 </table>
1056 </table>
1027 </div>
1057 </div>
1028 <div class="block-left">
1058 <div class="block-left">
1029 <table class="keyboard-mappings">
1059 <table class="keyboard-mappings">
1030 <tbody>
1060 <tbody>
1031 <tr>
1061 <tr>
1032 <th></th>
1062 <th></th>
1033 <th>${_('Repositories')}</th>
1063 <th>${_('Repositories')}</th>
1034 </tr>
1064 </tr>
1035 <%
1065 <%
1036 elems = [
1066 elems = [
1037 ('g s', 'Goto summary page'),
1067 ('g s', 'Goto summary page'),
1038 ('g c', 'Goto changelog page'),
1068 ('g c', 'Goto changelog page'),
1039 ('g f', 'Goto files page'),
1069 ('g f', 'Goto files page'),
1040 ('g F', 'Goto files page with file search activated'),
1070 ('g F', 'Goto files page with file search activated'),
1041 ('g p', 'Goto pull requests page'),
1071 ('g p', 'Goto pull requests page'),
1042 ('g o', 'Goto repository settings'),
1072 ('g o', 'Goto repository settings'),
1043 ('g O', 'Goto repository access permissions settings'),
1073 ('g O', 'Goto repository access permissions settings'),
1044 ]
1074 ]
1045 %>
1075 %>
1046 %for key, desc in elems:
1076 %for key, desc in elems:
1047 <tr>
1077 <tr>
1048 <td class="keys">
1078 <td class="keys">
1049 <span class="key tag">${key}</span>
1079 <span class="key tag">${key}</span>
1050 </td>
1080 </td>
1051 <td>${desc}</td>
1081 <td>${desc}</td>
1052 </tr>
1082 </tr>
1053 %endfor
1083 %endfor
1054 </tbody>
1084 </tbody>
1055 </table>
1085 </table>
1056 </div>
1086 </div>
1057 </div>
1087 </div>
1058 <div class="modal-footer">
1088 <div class="modal-footer">
1059 </div>
1089 </div>
1060 </div><!-- /.modal-content -->
1090 </div><!-- /.modal-content -->
1061 </div><!-- /.modal-dialog -->
1091 </div><!-- /.modal-dialog -->
1062 </div><!-- /.modal -->
1092 </div><!-- /.modal -->
1063
1093
General Comments 0
You need to be logged in to leave comments. Login now