##// END OF EJS Templates
settings: reduce number of settings fetch since it uses locking for cache invalidation and is generally slow....
marcink -
r3855:e1ec64bd default
parent child Browse files
Show More
@@ -1,518 +1,519 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 re
21 import re
22 import logging
22 import logging
23 import formencode
23 import formencode
24 import formencode.htmlfill
24 import formencode.htmlfill
25 import datetime
25 import datetime
26 from pyramid.interfaces import IRoutesMapper
26 from pyramid.interfaces import IRoutesMapper
27
27
28 from pyramid.view import view_config
28 from pyramid.view import view_config
29 from pyramid.httpexceptions import HTTPFound
29 from pyramid.httpexceptions import HTTPFound
30 from pyramid.renderers import render
30 from pyramid.renderers import render
31 from pyramid.response import Response
31 from pyramid.response import Response
32
32
33 from rhodecode.apps._base import BaseAppView, DataGridAppView
33 from rhodecode.apps._base import BaseAppView, DataGridAppView
34 from rhodecode.apps.ssh_support import SshKeyFileChangeEvent
34 from rhodecode.apps.ssh_support import SshKeyFileChangeEvent
35 from rhodecode import events
35 from rhodecode import events
36
36
37 from rhodecode.lib import helpers as h
37 from rhodecode.lib import helpers as h
38 from rhodecode.lib.auth import (
38 from rhodecode.lib.auth import (
39 LoginRequired, HasPermissionAllDecorator, CSRFRequired)
39 LoginRequired, HasPermissionAllDecorator, CSRFRequired)
40 from rhodecode.lib.utils2 import aslist, safe_unicode
40 from rhodecode.lib.utils2 import aslist, safe_unicode
41 from rhodecode.model.db import (
41 from rhodecode.model.db import (
42 or_, coalesce, User, UserIpMap, UserSshKeys)
42 or_, coalesce, User, UserIpMap, UserSshKeys)
43 from rhodecode.model.forms import (
43 from rhodecode.model.forms import (
44 ApplicationPermissionsForm, ObjectPermissionsForm, UserPermissionsForm)
44 ApplicationPermissionsForm, ObjectPermissionsForm, UserPermissionsForm)
45 from rhodecode.model.meta import Session
45 from rhodecode.model.meta import Session
46 from rhodecode.model.permission import PermissionModel
46 from rhodecode.model.permission import PermissionModel
47 from rhodecode.model.settings import SettingsModel
47 from rhodecode.model.settings import SettingsModel
48
48
49
49
50 log = logging.getLogger(__name__)
50 log = logging.getLogger(__name__)
51
51
52
52
53 class AdminPermissionsView(BaseAppView, DataGridAppView):
53 class AdminPermissionsView(BaseAppView, DataGridAppView):
54 def load_default_context(self):
54 def load_default_context(self):
55 c = self._get_local_tmpl_context()
55 c = self._get_local_tmpl_context()
56 PermissionModel().set_global_permission_choices(
56 PermissionModel().set_global_permission_choices(
57 c, gettext_translator=self.request.translate)
57 c, gettext_translator=self.request.translate)
58 return c
58 return c
59
59
60 @LoginRequired()
60 @LoginRequired()
61 @HasPermissionAllDecorator('hg.admin')
61 @HasPermissionAllDecorator('hg.admin')
62 @view_config(
62 @view_config(
63 route_name='admin_permissions_application', request_method='GET',
63 route_name='admin_permissions_application', request_method='GET',
64 renderer='rhodecode:templates/admin/permissions/permissions.mako')
64 renderer='rhodecode:templates/admin/permissions/permissions.mako')
65 def permissions_application(self):
65 def permissions_application(self):
66 c = self.load_default_context()
66 c = self.load_default_context()
67 c.active = 'application'
67 c.active = 'application'
68
68
69 c.user = User.get_default_user(refresh=True)
69 c.user = User.get_default_user(refresh=True)
70
70
71 app_settings = SettingsModel().get_all_settings()
71 app_settings = c.rc_config
72
72 defaults = {
73 defaults = {
73 'anonymous': c.user.active,
74 'anonymous': c.user.active,
74 'default_register_message': app_settings.get(
75 'default_register_message': app_settings.get(
75 'rhodecode_register_message')
76 'rhodecode_register_message')
76 }
77 }
77 defaults.update(c.user.get_default_perms())
78 defaults.update(c.user.get_default_perms())
78
79
79 data = render('rhodecode:templates/admin/permissions/permissions.mako',
80 data = render('rhodecode:templates/admin/permissions/permissions.mako',
80 self._get_template_context(c), self.request)
81 self._get_template_context(c), self.request)
81 html = formencode.htmlfill.render(
82 html = formencode.htmlfill.render(
82 data,
83 data,
83 defaults=defaults,
84 defaults=defaults,
84 encoding="UTF-8",
85 encoding="UTF-8",
85 force_defaults=False
86 force_defaults=False
86 )
87 )
87 return Response(html)
88 return Response(html)
88
89
89 @LoginRequired()
90 @LoginRequired()
90 @HasPermissionAllDecorator('hg.admin')
91 @HasPermissionAllDecorator('hg.admin')
91 @CSRFRequired()
92 @CSRFRequired()
92 @view_config(
93 @view_config(
93 route_name='admin_permissions_application_update', request_method='POST',
94 route_name='admin_permissions_application_update', request_method='POST',
94 renderer='rhodecode:templates/admin/permissions/permissions.mako')
95 renderer='rhodecode:templates/admin/permissions/permissions.mako')
95 def permissions_application_update(self):
96 def permissions_application_update(self):
96 _ = self.request.translate
97 _ = self.request.translate
97 c = self.load_default_context()
98 c = self.load_default_context()
98 c.active = 'application'
99 c.active = 'application'
99
100
100 _form = ApplicationPermissionsForm(
101 _form = ApplicationPermissionsForm(
101 self.request.translate,
102 self.request.translate,
102 [x[0] for x in c.register_choices],
103 [x[0] for x in c.register_choices],
103 [x[0] for x in c.password_reset_choices],
104 [x[0] for x in c.password_reset_choices],
104 [x[0] for x in c.extern_activate_choices])()
105 [x[0] for x in c.extern_activate_choices])()
105
106
106 try:
107 try:
107 form_result = _form.to_python(dict(self.request.POST))
108 form_result = _form.to_python(dict(self.request.POST))
108 form_result.update({'perm_user_name': User.DEFAULT_USER})
109 form_result.update({'perm_user_name': User.DEFAULT_USER})
109 PermissionModel().update_application_permissions(form_result)
110 PermissionModel().update_application_permissions(form_result)
110
111
111 settings = [
112 settings = [
112 ('register_message', 'default_register_message'),
113 ('register_message', 'default_register_message'),
113 ]
114 ]
114 for setting, form_key in settings:
115 for setting, form_key in settings:
115 sett = SettingsModel().create_or_update_setting(
116 sett = SettingsModel().create_or_update_setting(
116 setting, form_result[form_key])
117 setting, form_result[form_key])
117 Session().add(sett)
118 Session().add(sett)
118
119
119 Session().commit()
120 Session().commit()
120 h.flash(_('Application permissions updated successfully'),
121 h.flash(_('Application permissions updated successfully'),
121 category='success')
122 category='success')
122
123
123 except formencode.Invalid as errors:
124 except formencode.Invalid as errors:
124 defaults = errors.value
125 defaults = errors.value
125
126
126 data = render(
127 data = render(
127 'rhodecode:templates/admin/permissions/permissions.mako',
128 'rhodecode:templates/admin/permissions/permissions.mako',
128 self._get_template_context(c), self.request)
129 self._get_template_context(c), self.request)
129 html = formencode.htmlfill.render(
130 html = formencode.htmlfill.render(
130 data,
131 data,
131 defaults=defaults,
132 defaults=defaults,
132 errors=errors.error_dict or {},
133 errors=errors.error_dict or {},
133 prefix_error=False,
134 prefix_error=False,
134 encoding="UTF-8",
135 encoding="UTF-8",
135 force_defaults=False
136 force_defaults=False
136 )
137 )
137 return Response(html)
138 return Response(html)
138
139
139 except Exception:
140 except Exception:
140 log.exception("Exception during update of permissions")
141 log.exception("Exception during update of permissions")
141 h.flash(_('Error occurred during update of permissions'),
142 h.flash(_('Error occurred during update of permissions'),
142 category='error')
143 category='error')
143
144
144 affected_user_ids = [User.get_default_user().user_id]
145 affected_user_ids = [User.get_default_user().user_id]
145 events.trigger(events.UserPermissionsChange(affected_user_ids))
146 events.trigger(events.UserPermissionsChange(affected_user_ids))
146
147
147 raise HTTPFound(h.route_path('admin_permissions_application'))
148 raise HTTPFound(h.route_path('admin_permissions_application'))
148
149
149 @LoginRequired()
150 @LoginRequired()
150 @HasPermissionAllDecorator('hg.admin')
151 @HasPermissionAllDecorator('hg.admin')
151 @view_config(
152 @view_config(
152 route_name='admin_permissions_object', request_method='GET',
153 route_name='admin_permissions_object', request_method='GET',
153 renderer='rhodecode:templates/admin/permissions/permissions.mako')
154 renderer='rhodecode:templates/admin/permissions/permissions.mako')
154 def permissions_objects(self):
155 def permissions_objects(self):
155 c = self.load_default_context()
156 c = self.load_default_context()
156 c.active = 'objects'
157 c.active = 'objects'
157
158
158 c.user = User.get_default_user(refresh=True)
159 c.user = User.get_default_user(refresh=True)
159 defaults = {}
160 defaults = {}
160 defaults.update(c.user.get_default_perms())
161 defaults.update(c.user.get_default_perms())
161
162
162 data = render(
163 data = render(
163 'rhodecode:templates/admin/permissions/permissions.mako',
164 'rhodecode:templates/admin/permissions/permissions.mako',
164 self._get_template_context(c), self.request)
165 self._get_template_context(c), self.request)
165 html = formencode.htmlfill.render(
166 html = formencode.htmlfill.render(
166 data,
167 data,
167 defaults=defaults,
168 defaults=defaults,
168 encoding="UTF-8",
169 encoding="UTF-8",
169 force_defaults=False
170 force_defaults=False
170 )
171 )
171 return Response(html)
172 return Response(html)
172
173
173 @LoginRequired()
174 @LoginRequired()
174 @HasPermissionAllDecorator('hg.admin')
175 @HasPermissionAllDecorator('hg.admin')
175 @CSRFRequired()
176 @CSRFRequired()
176 @view_config(
177 @view_config(
177 route_name='admin_permissions_object_update', request_method='POST',
178 route_name='admin_permissions_object_update', request_method='POST',
178 renderer='rhodecode:templates/admin/permissions/permissions.mako')
179 renderer='rhodecode:templates/admin/permissions/permissions.mako')
179 def permissions_objects_update(self):
180 def permissions_objects_update(self):
180 _ = self.request.translate
181 _ = self.request.translate
181 c = self.load_default_context()
182 c = self.load_default_context()
182 c.active = 'objects'
183 c.active = 'objects'
183
184
184 _form = ObjectPermissionsForm(
185 _form = ObjectPermissionsForm(
185 self.request.translate,
186 self.request.translate,
186 [x[0] for x in c.repo_perms_choices],
187 [x[0] for x in c.repo_perms_choices],
187 [x[0] for x in c.group_perms_choices],
188 [x[0] for x in c.group_perms_choices],
188 [x[0] for x in c.user_group_perms_choices],
189 [x[0] for x in c.user_group_perms_choices],
189 )()
190 )()
190
191
191 try:
192 try:
192 form_result = _form.to_python(dict(self.request.POST))
193 form_result = _form.to_python(dict(self.request.POST))
193 form_result.update({'perm_user_name': User.DEFAULT_USER})
194 form_result.update({'perm_user_name': User.DEFAULT_USER})
194 PermissionModel().update_object_permissions(form_result)
195 PermissionModel().update_object_permissions(form_result)
195
196
196 Session().commit()
197 Session().commit()
197 h.flash(_('Object permissions updated successfully'),
198 h.flash(_('Object permissions updated successfully'),
198 category='success')
199 category='success')
199
200
200 except formencode.Invalid as errors:
201 except formencode.Invalid as errors:
201 defaults = errors.value
202 defaults = errors.value
202
203
203 data = render(
204 data = render(
204 'rhodecode:templates/admin/permissions/permissions.mako',
205 'rhodecode:templates/admin/permissions/permissions.mako',
205 self._get_template_context(c), self.request)
206 self._get_template_context(c), self.request)
206 html = formencode.htmlfill.render(
207 html = formencode.htmlfill.render(
207 data,
208 data,
208 defaults=defaults,
209 defaults=defaults,
209 errors=errors.error_dict or {},
210 errors=errors.error_dict or {},
210 prefix_error=False,
211 prefix_error=False,
211 encoding="UTF-8",
212 encoding="UTF-8",
212 force_defaults=False
213 force_defaults=False
213 )
214 )
214 return Response(html)
215 return Response(html)
215 except Exception:
216 except Exception:
216 log.exception("Exception during update of permissions")
217 log.exception("Exception during update of permissions")
217 h.flash(_('Error occurred during update of permissions'),
218 h.flash(_('Error occurred during update of permissions'),
218 category='error')
219 category='error')
219
220
220 affected_user_ids = [User.get_default_user().user_id]
221 affected_user_ids = [User.get_default_user().user_id]
221 events.trigger(events.UserPermissionsChange(affected_user_ids))
222 events.trigger(events.UserPermissionsChange(affected_user_ids))
222
223
223 raise HTTPFound(h.route_path('admin_permissions_object'))
224 raise HTTPFound(h.route_path('admin_permissions_object'))
224
225
225 @LoginRequired()
226 @LoginRequired()
226 @HasPermissionAllDecorator('hg.admin')
227 @HasPermissionAllDecorator('hg.admin')
227 @view_config(
228 @view_config(
228 route_name='admin_permissions_branch', request_method='GET',
229 route_name='admin_permissions_branch', request_method='GET',
229 renderer='rhodecode:templates/admin/permissions/permissions.mako')
230 renderer='rhodecode:templates/admin/permissions/permissions.mako')
230 def permissions_branch(self):
231 def permissions_branch(self):
231 c = self.load_default_context()
232 c = self.load_default_context()
232 c.active = 'branch'
233 c.active = 'branch'
233
234
234 c.user = User.get_default_user(refresh=True)
235 c.user = User.get_default_user(refresh=True)
235 defaults = {}
236 defaults = {}
236 defaults.update(c.user.get_default_perms())
237 defaults.update(c.user.get_default_perms())
237
238
238 data = render(
239 data = render(
239 'rhodecode:templates/admin/permissions/permissions.mako',
240 'rhodecode:templates/admin/permissions/permissions.mako',
240 self._get_template_context(c), self.request)
241 self._get_template_context(c), self.request)
241 html = formencode.htmlfill.render(
242 html = formencode.htmlfill.render(
242 data,
243 data,
243 defaults=defaults,
244 defaults=defaults,
244 encoding="UTF-8",
245 encoding="UTF-8",
245 force_defaults=False
246 force_defaults=False
246 )
247 )
247 return Response(html)
248 return Response(html)
248
249
249 @LoginRequired()
250 @LoginRequired()
250 @HasPermissionAllDecorator('hg.admin')
251 @HasPermissionAllDecorator('hg.admin')
251 @view_config(
252 @view_config(
252 route_name='admin_permissions_global', request_method='GET',
253 route_name='admin_permissions_global', request_method='GET',
253 renderer='rhodecode:templates/admin/permissions/permissions.mako')
254 renderer='rhodecode:templates/admin/permissions/permissions.mako')
254 def permissions_global(self):
255 def permissions_global(self):
255 c = self.load_default_context()
256 c = self.load_default_context()
256 c.active = 'global'
257 c.active = 'global'
257
258
258 c.user = User.get_default_user(refresh=True)
259 c.user = User.get_default_user(refresh=True)
259 defaults = {}
260 defaults = {}
260 defaults.update(c.user.get_default_perms())
261 defaults.update(c.user.get_default_perms())
261
262
262 data = render(
263 data = render(
263 'rhodecode:templates/admin/permissions/permissions.mako',
264 'rhodecode:templates/admin/permissions/permissions.mako',
264 self._get_template_context(c), self.request)
265 self._get_template_context(c), self.request)
265 html = formencode.htmlfill.render(
266 html = formencode.htmlfill.render(
266 data,
267 data,
267 defaults=defaults,
268 defaults=defaults,
268 encoding="UTF-8",
269 encoding="UTF-8",
269 force_defaults=False
270 force_defaults=False
270 )
271 )
271 return Response(html)
272 return Response(html)
272
273
273 @LoginRequired()
274 @LoginRequired()
274 @HasPermissionAllDecorator('hg.admin')
275 @HasPermissionAllDecorator('hg.admin')
275 @CSRFRequired()
276 @CSRFRequired()
276 @view_config(
277 @view_config(
277 route_name='admin_permissions_global_update', request_method='POST',
278 route_name='admin_permissions_global_update', request_method='POST',
278 renderer='rhodecode:templates/admin/permissions/permissions.mako')
279 renderer='rhodecode:templates/admin/permissions/permissions.mako')
279 def permissions_global_update(self):
280 def permissions_global_update(self):
280 _ = self.request.translate
281 _ = self.request.translate
281 c = self.load_default_context()
282 c = self.load_default_context()
282 c.active = 'global'
283 c.active = 'global'
283
284
284 _form = UserPermissionsForm(
285 _form = UserPermissionsForm(
285 self.request.translate,
286 self.request.translate,
286 [x[0] for x in c.repo_create_choices],
287 [x[0] for x in c.repo_create_choices],
287 [x[0] for x in c.repo_create_on_write_choices],
288 [x[0] for x in c.repo_create_on_write_choices],
288 [x[0] for x in c.repo_group_create_choices],
289 [x[0] for x in c.repo_group_create_choices],
289 [x[0] for x in c.user_group_create_choices],
290 [x[0] for x in c.user_group_create_choices],
290 [x[0] for x in c.fork_choices],
291 [x[0] for x in c.fork_choices],
291 [x[0] for x in c.inherit_default_permission_choices])()
292 [x[0] for x in c.inherit_default_permission_choices])()
292
293
293 try:
294 try:
294 form_result = _form.to_python(dict(self.request.POST))
295 form_result = _form.to_python(dict(self.request.POST))
295 form_result.update({'perm_user_name': User.DEFAULT_USER})
296 form_result.update({'perm_user_name': User.DEFAULT_USER})
296 PermissionModel().update_user_permissions(form_result)
297 PermissionModel().update_user_permissions(form_result)
297
298
298 Session().commit()
299 Session().commit()
299 h.flash(_('Global permissions updated successfully'),
300 h.flash(_('Global permissions updated successfully'),
300 category='success')
301 category='success')
301
302
302 except formencode.Invalid as errors:
303 except formencode.Invalid as errors:
303 defaults = errors.value
304 defaults = errors.value
304
305
305 data = render(
306 data = render(
306 'rhodecode:templates/admin/permissions/permissions.mako',
307 'rhodecode:templates/admin/permissions/permissions.mako',
307 self._get_template_context(c), self.request)
308 self._get_template_context(c), self.request)
308 html = formencode.htmlfill.render(
309 html = formencode.htmlfill.render(
309 data,
310 data,
310 defaults=defaults,
311 defaults=defaults,
311 errors=errors.error_dict or {},
312 errors=errors.error_dict or {},
312 prefix_error=False,
313 prefix_error=False,
313 encoding="UTF-8",
314 encoding="UTF-8",
314 force_defaults=False
315 force_defaults=False
315 )
316 )
316 return Response(html)
317 return Response(html)
317 except Exception:
318 except Exception:
318 log.exception("Exception during update of permissions")
319 log.exception("Exception during update of permissions")
319 h.flash(_('Error occurred during update of permissions'),
320 h.flash(_('Error occurred during update of permissions'),
320 category='error')
321 category='error')
321
322
322 affected_user_ids = [User.get_default_user().user_id]
323 affected_user_ids = [User.get_default_user().user_id]
323 events.trigger(events.UserPermissionsChange(affected_user_ids))
324 events.trigger(events.UserPermissionsChange(affected_user_ids))
324
325
325 raise HTTPFound(h.route_path('admin_permissions_global'))
326 raise HTTPFound(h.route_path('admin_permissions_global'))
326
327
327 @LoginRequired()
328 @LoginRequired()
328 @HasPermissionAllDecorator('hg.admin')
329 @HasPermissionAllDecorator('hg.admin')
329 @view_config(
330 @view_config(
330 route_name='admin_permissions_ips', request_method='GET',
331 route_name='admin_permissions_ips', request_method='GET',
331 renderer='rhodecode:templates/admin/permissions/permissions.mako')
332 renderer='rhodecode:templates/admin/permissions/permissions.mako')
332 def permissions_ips(self):
333 def permissions_ips(self):
333 c = self.load_default_context()
334 c = self.load_default_context()
334 c.active = 'ips'
335 c.active = 'ips'
335
336
336 c.user = User.get_default_user(refresh=True)
337 c.user = User.get_default_user(refresh=True)
337 c.user_ip_map = (
338 c.user_ip_map = (
338 UserIpMap.query().filter(UserIpMap.user == c.user).all())
339 UserIpMap.query().filter(UserIpMap.user == c.user).all())
339
340
340 return self._get_template_context(c)
341 return self._get_template_context(c)
341
342
342 @LoginRequired()
343 @LoginRequired()
343 @HasPermissionAllDecorator('hg.admin')
344 @HasPermissionAllDecorator('hg.admin')
344 @view_config(
345 @view_config(
345 route_name='admin_permissions_overview', request_method='GET',
346 route_name='admin_permissions_overview', request_method='GET',
346 renderer='rhodecode:templates/admin/permissions/permissions.mako')
347 renderer='rhodecode:templates/admin/permissions/permissions.mako')
347 def permissions_overview(self):
348 def permissions_overview(self):
348 c = self.load_default_context()
349 c = self.load_default_context()
349 c.active = 'perms'
350 c.active = 'perms'
350
351
351 c.user = User.get_default_user(refresh=True)
352 c.user = User.get_default_user(refresh=True)
352 c.perm_user = c.user.AuthUser()
353 c.perm_user = c.user.AuthUser()
353 return self._get_template_context(c)
354 return self._get_template_context(c)
354
355
355 @LoginRequired()
356 @LoginRequired()
356 @HasPermissionAllDecorator('hg.admin')
357 @HasPermissionAllDecorator('hg.admin')
357 @view_config(
358 @view_config(
358 route_name='admin_permissions_auth_token_access', request_method='GET',
359 route_name='admin_permissions_auth_token_access', request_method='GET',
359 renderer='rhodecode:templates/admin/permissions/permissions.mako')
360 renderer='rhodecode:templates/admin/permissions/permissions.mako')
360 def auth_token_access(self):
361 def auth_token_access(self):
361 from rhodecode import CONFIG
362 from rhodecode import CONFIG
362
363
363 c = self.load_default_context()
364 c = self.load_default_context()
364 c.active = 'auth_token_access'
365 c.active = 'auth_token_access'
365
366
366 c.user = User.get_default_user(refresh=True)
367 c.user = User.get_default_user(refresh=True)
367 c.perm_user = c.user.AuthUser()
368 c.perm_user = c.user.AuthUser()
368
369
369 mapper = self.request.registry.queryUtility(IRoutesMapper)
370 mapper = self.request.registry.queryUtility(IRoutesMapper)
370 c.view_data = []
371 c.view_data = []
371
372
372 _argument_prog = re.compile('\{(.*?)\}|:\((.*)\)')
373 _argument_prog = re.compile('\{(.*?)\}|:\((.*)\)')
373 introspector = self.request.registry.introspector
374 introspector = self.request.registry.introspector
374
375
375 view_intr = {}
376 view_intr = {}
376 for view_data in introspector.get_category('views'):
377 for view_data in introspector.get_category('views'):
377 intr = view_data['introspectable']
378 intr = view_data['introspectable']
378
379
379 if 'route_name' in intr and intr['attr']:
380 if 'route_name' in intr and intr['attr']:
380 view_intr[intr['route_name']] = '{}:{}'.format(
381 view_intr[intr['route_name']] = '{}:{}'.format(
381 str(intr['derived_callable'].func_name), intr['attr']
382 str(intr['derived_callable'].func_name), intr['attr']
382 )
383 )
383
384
384 c.whitelist_key = 'api_access_controllers_whitelist'
385 c.whitelist_key = 'api_access_controllers_whitelist'
385 c.whitelist_file = CONFIG.get('__file__')
386 c.whitelist_file = CONFIG.get('__file__')
386 whitelist_views = aslist(
387 whitelist_views = aslist(
387 CONFIG.get(c.whitelist_key), sep=',')
388 CONFIG.get(c.whitelist_key), sep=',')
388
389
389 for route_info in mapper.get_routes():
390 for route_info in mapper.get_routes():
390 if not route_info.name.startswith('__'):
391 if not route_info.name.startswith('__'):
391 routepath = route_info.pattern
392 routepath = route_info.pattern
392
393
393 def replace(matchobj):
394 def replace(matchobj):
394 if matchobj.group(1):
395 if matchobj.group(1):
395 return "{%s}" % matchobj.group(1).split(':')[0]
396 return "{%s}" % matchobj.group(1).split(':')[0]
396 else:
397 else:
397 return "{%s}" % matchobj.group(2)
398 return "{%s}" % matchobj.group(2)
398
399
399 routepath = _argument_prog.sub(replace, routepath)
400 routepath = _argument_prog.sub(replace, routepath)
400
401
401 if not routepath.startswith('/'):
402 if not routepath.startswith('/'):
402 routepath = '/' + routepath
403 routepath = '/' + routepath
403
404
404 view_fqn = view_intr.get(route_info.name, 'NOT AVAILABLE')
405 view_fqn = view_intr.get(route_info.name, 'NOT AVAILABLE')
405 active = view_fqn in whitelist_views
406 active = view_fqn in whitelist_views
406 c.view_data.append((route_info.name, view_fqn, routepath, active))
407 c.view_data.append((route_info.name, view_fqn, routepath, active))
407
408
408 c.whitelist_views = whitelist_views
409 c.whitelist_views = whitelist_views
409 return self._get_template_context(c)
410 return self._get_template_context(c)
410
411
411 def ssh_enabled(self):
412 def ssh_enabled(self):
412 return self.request.registry.settings.get(
413 return self.request.registry.settings.get(
413 'ssh.generate_authorized_keyfile')
414 'ssh.generate_authorized_keyfile')
414
415
415 @LoginRequired()
416 @LoginRequired()
416 @HasPermissionAllDecorator('hg.admin')
417 @HasPermissionAllDecorator('hg.admin')
417 @view_config(
418 @view_config(
418 route_name='admin_permissions_ssh_keys', request_method='GET',
419 route_name='admin_permissions_ssh_keys', request_method='GET',
419 renderer='rhodecode:templates/admin/permissions/permissions.mako')
420 renderer='rhodecode:templates/admin/permissions/permissions.mako')
420 def ssh_keys(self):
421 def ssh_keys(self):
421 c = self.load_default_context()
422 c = self.load_default_context()
422 c.active = 'ssh_keys'
423 c.active = 'ssh_keys'
423 c.ssh_enabled = self.ssh_enabled()
424 c.ssh_enabled = self.ssh_enabled()
424 return self._get_template_context(c)
425 return self._get_template_context(c)
425
426
426 @LoginRequired()
427 @LoginRequired()
427 @HasPermissionAllDecorator('hg.admin')
428 @HasPermissionAllDecorator('hg.admin')
428 @view_config(
429 @view_config(
429 route_name='admin_permissions_ssh_keys_data', request_method='GET',
430 route_name='admin_permissions_ssh_keys_data', request_method='GET',
430 renderer='json_ext', xhr=True)
431 renderer='json_ext', xhr=True)
431 def ssh_keys_data(self):
432 def ssh_keys_data(self):
432 _ = self.request.translate
433 _ = self.request.translate
433 self.load_default_context()
434 self.load_default_context()
434 column_map = {
435 column_map = {
435 'fingerprint': 'ssh_key_fingerprint',
436 'fingerprint': 'ssh_key_fingerprint',
436 'username': User.username
437 'username': User.username
437 }
438 }
438 draw, start, limit = self._extract_chunk(self.request)
439 draw, start, limit = self._extract_chunk(self.request)
439 search_q, order_by, order_dir = self._extract_ordering(
440 search_q, order_by, order_dir = self._extract_ordering(
440 self.request, column_map=column_map)
441 self.request, column_map=column_map)
441
442
442 ssh_keys_data_total_count = UserSshKeys.query()\
443 ssh_keys_data_total_count = UserSshKeys.query()\
443 .count()
444 .count()
444
445
445 # json generate
446 # json generate
446 base_q = UserSshKeys.query().join(UserSshKeys.user)
447 base_q = UserSshKeys.query().join(UserSshKeys.user)
447
448
448 if search_q:
449 if search_q:
449 like_expression = u'%{}%'.format(safe_unicode(search_q))
450 like_expression = u'%{}%'.format(safe_unicode(search_q))
450 base_q = base_q.filter(or_(
451 base_q = base_q.filter(or_(
451 User.username.ilike(like_expression),
452 User.username.ilike(like_expression),
452 UserSshKeys.ssh_key_fingerprint.ilike(like_expression),
453 UserSshKeys.ssh_key_fingerprint.ilike(like_expression),
453 ))
454 ))
454
455
455 users_data_total_filtered_count = base_q.count()
456 users_data_total_filtered_count = base_q.count()
456
457
457 sort_col = self._get_order_col(order_by, UserSshKeys)
458 sort_col = self._get_order_col(order_by, UserSshKeys)
458 if sort_col:
459 if sort_col:
459 if order_dir == 'asc':
460 if order_dir == 'asc':
460 # handle null values properly to order by NULL last
461 # handle null values properly to order by NULL last
461 if order_by in ['created_on']:
462 if order_by in ['created_on']:
462 sort_col = coalesce(sort_col, datetime.date.max)
463 sort_col = coalesce(sort_col, datetime.date.max)
463 sort_col = sort_col.asc()
464 sort_col = sort_col.asc()
464 else:
465 else:
465 # handle null values properly to order by NULL last
466 # handle null values properly to order by NULL last
466 if order_by in ['created_on']:
467 if order_by in ['created_on']:
467 sort_col = coalesce(sort_col, datetime.date.min)
468 sort_col = coalesce(sort_col, datetime.date.min)
468 sort_col = sort_col.desc()
469 sort_col = sort_col.desc()
469
470
470 base_q = base_q.order_by(sort_col)
471 base_q = base_q.order_by(sort_col)
471 base_q = base_q.offset(start).limit(limit)
472 base_q = base_q.offset(start).limit(limit)
472
473
473 ssh_keys = base_q.all()
474 ssh_keys = base_q.all()
474
475
475 ssh_keys_data = []
476 ssh_keys_data = []
476 for ssh_key in ssh_keys:
477 for ssh_key in ssh_keys:
477 ssh_keys_data.append({
478 ssh_keys_data.append({
478 "username": h.gravatar_with_user(self.request, ssh_key.user.username),
479 "username": h.gravatar_with_user(self.request, ssh_key.user.username),
479 "fingerprint": ssh_key.ssh_key_fingerprint,
480 "fingerprint": ssh_key.ssh_key_fingerprint,
480 "description": ssh_key.description,
481 "description": ssh_key.description,
481 "created_on": h.format_date(ssh_key.created_on),
482 "created_on": h.format_date(ssh_key.created_on),
482 "accessed_on": h.format_date(ssh_key.accessed_on),
483 "accessed_on": h.format_date(ssh_key.accessed_on),
483 "action": h.link_to(
484 "action": h.link_to(
484 _('Edit'), h.route_path('edit_user_ssh_keys',
485 _('Edit'), h.route_path('edit_user_ssh_keys',
485 user_id=ssh_key.user.user_id))
486 user_id=ssh_key.user.user_id))
486 })
487 })
487
488
488 data = ({
489 data = ({
489 'draw': draw,
490 'draw': draw,
490 'data': ssh_keys_data,
491 'data': ssh_keys_data,
491 'recordsTotal': ssh_keys_data_total_count,
492 'recordsTotal': ssh_keys_data_total_count,
492 'recordsFiltered': users_data_total_filtered_count,
493 'recordsFiltered': users_data_total_filtered_count,
493 })
494 })
494
495
495 return data
496 return data
496
497
497 @LoginRequired()
498 @LoginRequired()
498 @HasPermissionAllDecorator('hg.admin')
499 @HasPermissionAllDecorator('hg.admin')
499 @CSRFRequired()
500 @CSRFRequired()
500 @view_config(
501 @view_config(
501 route_name='admin_permissions_ssh_keys_update', request_method='POST',
502 route_name='admin_permissions_ssh_keys_update', request_method='POST',
502 renderer='rhodecode:templates/admin/permissions/permissions.mako')
503 renderer='rhodecode:templates/admin/permissions/permissions.mako')
503 def ssh_keys_update(self):
504 def ssh_keys_update(self):
504 _ = self.request.translate
505 _ = self.request.translate
505 self.load_default_context()
506 self.load_default_context()
506
507
507 ssh_enabled = self.ssh_enabled()
508 ssh_enabled = self.ssh_enabled()
508 key_file = self.request.registry.settings.get(
509 key_file = self.request.registry.settings.get(
509 'ssh.authorized_keys_file_path')
510 'ssh.authorized_keys_file_path')
510 if ssh_enabled:
511 if ssh_enabled:
511 events.trigger(SshKeyFileChangeEvent(), self.request.registry)
512 events.trigger(SshKeyFileChangeEvent(), self.request.registry)
512 h.flash(_('Updated SSH keys file: {}').format(key_file),
513 h.flash(_('Updated SSH keys file: {}').format(key_file),
513 category='success')
514 category='success')
514 else:
515 else:
515 h.flash(_('SSH key support is disabled in .ini file'),
516 h.flash(_('SSH key support is disabled in .ini file'),
516 category='warning')
517 category='warning')
517
518
518 raise HTTPFound(h.route_path('admin_permissions_ssh_keys'))
519 raise HTTPFound(h.route_path('admin_permissions_ssh_keys'))
@@ -1,780 +1,780 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 import logging
22 import logging
23 import collections
23 import collections
24
24
25 import datetime
25 import datetime
26 import formencode
26 import formencode
27 import formencode.htmlfill
27 import formencode.htmlfill
28
28
29 import rhodecode
29 import rhodecode
30 from pyramid.view import view_config
30 from pyramid.view import view_config
31 from pyramid.httpexceptions import HTTPFound, HTTPNotFound
31 from pyramid.httpexceptions import HTTPFound, HTTPNotFound
32 from pyramid.renderers import render
32 from pyramid.renderers import render
33 from pyramid.response import Response
33 from pyramid.response import Response
34
34
35 from rhodecode.apps._base import BaseAppView
35 from rhodecode.apps._base import BaseAppView
36 from rhodecode.apps._base.navigation import navigation_list
36 from rhodecode.apps._base.navigation import navigation_list
37 from rhodecode.apps.svn_support.config_keys import generate_config
37 from rhodecode.apps.svn_support.config_keys import generate_config
38 from rhodecode.lib import helpers as h
38 from rhodecode.lib import helpers as h
39 from rhodecode.lib.auth import (
39 from rhodecode.lib.auth import (
40 LoginRequired, HasPermissionAllDecorator, CSRFRequired)
40 LoginRequired, HasPermissionAllDecorator, CSRFRequired)
41 from rhodecode.lib.celerylib import tasks, run_task
41 from rhodecode.lib.celerylib import tasks, run_task
42 from rhodecode.lib.utils import repo2db_mapper
42 from rhodecode.lib.utils import repo2db_mapper
43 from rhodecode.lib.utils2 import str2bool, safe_unicode, AttributeDict
43 from rhodecode.lib.utils2 import str2bool, safe_unicode, AttributeDict
44 from rhodecode.lib.index import searcher_from_config
44 from rhodecode.lib.index import searcher_from_config
45
45
46 from rhodecode.model.db import RhodeCodeUi, Repository
46 from rhodecode.model.db import RhodeCodeUi, Repository
47 from rhodecode.model.forms import (ApplicationSettingsForm,
47 from rhodecode.model.forms import (ApplicationSettingsForm,
48 ApplicationUiSettingsForm, ApplicationVisualisationForm,
48 ApplicationUiSettingsForm, ApplicationVisualisationForm,
49 LabsSettingsForm, IssueTrackerPatternsForm)
49 LabsSettingsForm, IssueTrackerPatternsForm)
50 from rhodecode.model.repo_group import RepoGroupModel
50 from rhodecode.model.repo_group import RepoGroupModel
51
51
52 from rhodecode.model.scm import ScmModel
52 from rhodecode.model.scm import ScmModel
53 from rhodecode.model.notification import EmailNotificationModel
53 from rhodecode.model.notification import EmailNotificationModel
54 from rhodecode.model.meta import Session
54 from rhodecode.model.meta import Session
55 from rhodecode.model.settings import (
55 from rhodecode.model.settings import (
56 IssueTrackerSettingsModel, VcsSettingsModel, SettingNotFound,
56 IssueTrackerSettingsModel, VcsSettingsModel, SettingNotFound,
57 SettingsModel)
57 SettingsModel)
58
58
59
59
60 log = logging.getLogger(__name__)
60 log = logging.getLogger(__name__)
61
61
62
62
63 class AdminSettingsView(BaseAppView):
63 class AdminSettingsView(BaseAppView):
64
64
65 def load_default_context(self):
65 def load_default_context(self):
66 c = self._get_local_tmpl_context()
66 c = self._get_local_tmpl_context()
67 c.labs_active = str2bool(
67 c.labs_active = str2bool(
68 rhodecode.CONFIG.get('labs_settings_active', 'true'))
68 rhodecode.CONFIG.get('labs_settings_active', 'true'))
69 c.navlist = navigation_list(self.request)
69 c.navlist = navigation_list(self.request)
70
70
71 return c
71 return c
72
72
73 @classmethod
73 @classmethod
74 def _get_ui_settings(cls):
74 def _get_ui_settings(cls):
75 ret = RhodeCodeUi.query().all()
75 ret = RhodeCodeUi.query().all()
76
76
77 if not ret:
77 if not ret:
78 raise Exception('Could not get application ui settings !')
78 raise Exception('Could not get application ui settings !')
79 settings = {}
79 settings = {}
80 for each in ret:
80 for each in ret:
81 k = each.ui_key
81 k = each.ui_key
82 v = each.ui_value
82 v = each.ui_value
83 if k == '/':
83 if k == '/':
84 k = 'root_path'
84 k = 'root_path'
85
85
86 if k in ['push_ssl', 'publish', 'enabled']:
86 if k in ['push_ssl', 'publish', 'enabled']:
87 v = str2bool(v)
87 v = str2bool(v)
88
88
89 if k.find('.') != -1:
89 if k.find('.') != -1:
90 k = k.replace('.', '_')
90 k = k.replace('.', '_')
91
91
92 if each.ui_section in ['hooks', 'extensions']:
92 if each.ui_section in ['hooks', 'extensions']:
93 v = each.ui_active
93 v = each.ui_active
94
94
95 settings[each.ui_section + '_' + k] = v
95 settings[each.ui_section + '_' + k] = v
96 return settings
96 return settings
97
97
98 @classmethod
98 @classmethod
99 def _form_defaults(cls):
99 def _form_defaults(cls):
100 defaults = SettingsModel().get_all_settings()
100 defaults = SettingsModel().get_all_settings()
101 defaults.update(cls._get_ui_settings())
101 defaults.update(cls._get_ui_settings())
102
102
103 defaults.update({
103 defaults.update({
104 'new_svn_branch': '',
104 'new_svn_branch': '',
105 'new_svn_tag': '',
105 'new_svn_tag': '',
106 })
106 })
107 return defaults
107 return defaults
108
108
109 @LoginRequired()
109 @LoginRequired()
110 @HasPermissionAllDecorator('hg.admin')
110 @HasPermissionAllDecorator('hg.admin')
111 @view_config(
111 @view_config(
112 route_name='admin_settings_vcs', request_method='GET',
112 route_name='admin_settings_vcs', request_method='GET',
113 renderer='rhodecode:templates/admin/settings/settings.mako')
113 renderer='rhodecode:templates/admin/settings/settings.mako')
114 def settings_vcs(self):
114 def settings_vcs(self):
115 c = self.load_default_context()
115 c = self.load_default_context()
116 c.active = 'vcs'
116 c.active = 'vcs'
117 model = VcsSettingsModel()
117 model = VcsSettingsModel()
118 c.svn_branch_patterns = model.get_global_svn_branch_patterns()
118 c.svn_branch_patterns = model.get_global_svn_branch_patterns()
119 c.svn_tag_patterns = model.get_global_svn_tag_patterns()
119 c.svn_tag_patterns = model.get_global_svn_tag_patterns()
120
120
121 settings = self.request.registry.settings
121 settings = self.request.registry.settings
122 c.svn_proxy_generate_config = settings[generate_config]
122 c.svn_proxy_generate_config = settings[generate_config]
123
123
124 defaults = self._form_defaults()
124 defaults = self._form_defaults()
125
125
126 model.create_largeobjects_dirs_if_needed(defaults['paths_root_path'])
126 model.create_largeobjects_dirs_if_needed(defaults['paths_root_path'])
127
127
128 data = render('rhodecode:templates/admin/settings/settings.mako',
128 data = render('rhodecode:templates/admin/settings/settings.mako',
129 self._get_template_context(c), self.request)
129 self._get_template_context(c), self.request)
130 html = formencode.htmlfill.render(
130 html = formencode.htmlfill.render(
131 data,
131 data,
132 defaults=defaults,
132 defaults=defaults,
133 encoding="UTF-8",
133 encoding="UTF-8",
134 force_defaults=False
134 force_defaults=False
135 )
135 )
136 return Response(html)
136 return Response(html)
137
137
138 @LoginRequired()
138 @LoginRequired()
139 @HasPermissionAllDecorator('hg.admin')
139 @HasPermissionAllDecorator('hg.admin')
140 @CSRFRequired()
140 @CSRFRequired()
141 @view_config(
141 @view_config(
142 route_name='admin_settings_vcs_update', request_method='POST',
142 route_name='admin_settings_vcs_update', request_method='POST',
143 renderer='rhodecode:templates/admin/settings/settings.mako')
143 renderer='rhodecode:templates/admin/settings/settings.mako')
144 def settings_vcs_update(self):
144 def settings_vcs_update(self):
145 _ = self.request.translate
145 _ = self.request.translate
146 c = self.load_default_context()
146 c = self.load_default_context()
147 c.active = 'vcs'
147 c.active = 'vcs'
148
148
149 model = VcsSettingsModel()
149 model = VcsSettingsModel()
150 c.svn_branch_patterns = model.get_global_svn_branch_patterns()
150 c.svn_branch_patterns = model.get_global_svn_branch_patterns()
151 c.svn_tag_patterns = model.get_global_svn_tag_patterns()
151 c.svn_tag_patterns = model.get_global_svn_tag_patterns()
152
152
153 settings = self.request.registry.settings
153 settings = self.request.registry.settings
154 c.svn_proxy_generate_config = settings[generate_config]
154 c.svn_proxy_generate_config = settings[generate_config]
155
155
156 application_form = ApplicationUiSettingsForm(self.request.translate)()
156 application_form = ApplicationUiSettingsForm(self.request.translate)()
157
157
158 try:
158 try:
159 form_result = application_form.to_python(dict(self.request.POST))
159 form_result = application_form.to_python(dict(self.request.POST))
160 except formencode.Invalid as errors:
160 except formencode.Invalid as errors:
161 h.flash(
161 h.flash(
162 _("Some form inputs contain invalid data."),
162 _("Some form inputs contain invalid data."),
163 category='error')
163 category='error')
164 data = render('rhodecode:templates/admin/settings/settings.mako',
164 data = render('rhodecode:templates/admin/settings/settings.mako',
165 self._get_template_context(c), self.request)
165 self._get_template_context(c), self.request)
166 html = formencode.htmlfill.render(
166 html = formencode.htmlfill.render(
167 data,
167 data,
168 defaults=errors.value,
168 defaults=errors.value,
169 errors=errors.error_dict or {},
169 errors=errors.error_dict or {},
170 prefix_error=False,
170 prefix_error=False,
171 encoding="UTF-8",
171 encoding="UTF-8",
172 force_defaults=False
172 force_defaults=False
173 )
173 )
174 return Response(html)
174 return Response(html)
175
175
176 try:
176 try:
177 if c.visual.allow_repo_location_change:
177 if c.visual.allow_repo_location_change:
178 model.update_global_path_setting(form_result['paths_root_path'])
178 model.update_global_path_setting(form_result['paths_root_path'])
179
179
180 model.update_global_ssl_setting(form_result['web_push_ssl'])
180 model.update_global_ssl_setting(form_result['web_push_ssl'])
181 model.update_global_hook_settings(form_result)
181 model.update_global_hook_settings(form_result)
182
182
183 model.create_or_update_global_svn_settings(form_result)
183 model.create_or_update_global_svn_settings(form_result)
184 model.create_or_update_global_hg_settings(form_result)
184 model.create_or_update_global_hg_settings(form_result)
185 model.create_or_update_global_git_settings(form_result)
185 model.create_or_update_global_git_settings(form_result)
186 model.create_or_update_global_pr_settings(form_result)
186 model.create_or_update_global_pr_settings(form_result)
187 except Exception:
187 except Exception:
188 log.exception("Exception while updating settings")
188 log.exception("Exception while updating settings")
189 h.flash(_('Error occurred during updating '
189 h.flash(_('Error occurred during updating '
190 'application settings'), category='error')
190 'application settings'), category='error')
191 else:
191 else:
192 Session().commit()
192 Session().commit()
193 h.flash(_('Updated VCS settings'), category='success')
193 h.flash(_('Updated VCS settings'), category='success')
194 raise HTTPFound(h.route_path('admin_settings_vcs'))
194 raise HTTPFound(h.route_path('admin_settings_vcs'))
195
195
196 data = render('rhodecode:templates/admin/settings/settings.mako',
196 data = render('rhodecode:templates/admin/settings/settings.mako',
197 self._get_template_context(c), self.request)
197 self._get_template_context(c), self.request)
198 html = formencode.htmlfill.render(
198 html = formencode.htmlfill.render(
199 data,
199 data,
200 defaults=self._form_defaults(),
200 defaults=self._form_defaults(),
201 encoding="UTF-8",
201 encoding="UTF-8",
202 force_defaults=False
202 force_defaults=False
203 )
203 )
204 return Response(html)
204 return Response(html)
205
205
206 @LoginRequired()
206 @LoginRequired()
207 @HasPermissionAllDecorator('hg.admin')
207 @HasPermissionAllDecorator('hg.admin')
208 @CSRFRequired()
208 @CSRFRequired()
209 @view_config(
209 @view_config(
210 route_name='admin_settings_vcs_svn_pattern_delete', request_method='POST',
210 route_name='admin_settings_vcs_svn_pattern_delete', request_method='POST',
211 renderer='json_ext', xhr=True)
211 renderer='json_ext', xhr=True)
212 def settings_vcs_delete_svn_pattern(self):
212 def settings_vcs_delete_svn_pattern(self):
213 delete_pattern_id = self.request.POST.get('delete_svn_pattern')
213 delete_pattern_id = self.request.POST.get('delete_svn_pattern')
214 model = VcsSettingsModel()
214 model = VcsSettingsModel()
215 try:
215 try:
216 model.delete_global_svn_pattern(delete_pattern_id)
216 model.delete_global_svn_pattern(delete_pattern_id)
217 except SettingNotFound:
217 except SettingNotFound:
218 log.exception(
218 log.exception(
219 'Failed to delete svn_pattern with id %s', delete_pattern_id)
219 'Failed to delete svn_pattern with id %s', delete_pattern_id)
220 raise HTTPNotFound()
220 raise HTTPNotFound()
221
221
222 Session().commit()
222 Session().commit()
223 return True
223 return True
224
224
225 @LoginRequired()
225 @LoginRequired()
226 @HasPermissionAllDecorator('hg.admin')
226 @HasPermissionAllDecorator('hg.admin')
227 @view_config(
227 @view_config(
228 route_name='admin_settings_mapping', request_method='GET',
228 route_name='admin_settings_mapping', request_method='GET',
229 renderer='rhodecode:templates/admin/settings/settings.mako')
229 renderer='rhodecode:templates/admin/settings/settings.mako')
230 def settings_mapping(self):
230 def settings_mapping(self):
231 c = self.load_default_context()
231 c = self.load_default_context()
232 c.active = 'mapping'
232 c.active = 'mapping'
233
233
234 data = render('rhodecode:templates/admin/settings/settings.mako',
234 data = render('rhodecode:templates/admin/settings/settings.mako',
235 self._get_template_context(c), self.request)
235 self._get_template_context(c), self.request)
236 html = formencode.htmlfill.render(
236 html = formencode.htmlfill.render(
237 data,
237 data,
238 defaults=self._form_defaults(),
238 defaults=self._form_defaults(),
239 encoding="UTF-8",
239 encoding="UTF-8",
240 force_defaults=False
240 force_defaults=False
241 )
241 )
242 return Response(html)
242 return Response(html)
243
243
244 @LoginRequired()
244 @LoginRequired()
245 @HasPermissionAllDecorator('hg.admin')
245 @HasPermissionAllDecorator('hg.admin')
246 @CSRFRequired()
246 @CSRFRequired()
247 @view_config(
247 @view_config(
248 route_name='admin_settings_mapping_update', request_method='POST',
248 route_name='admin_settings_mapping_update', request_method='POST',
249 renderer='rhodecode:templates/admin/settings/settings.mako')
249 renderer='rhodecode:templates/admin/settings/settings.mako')
250 def settings_mapping_update(self):
250 def settings_mapping_update(self):
251 _ = self.request.translate
251 _ = self.request.translate
252 c = self.load_default_context()
252 c = self.load_default_context()
253 c.active = 'mapping'
253 c.active = 'mapping'
254 rm_obsolete = self.request.POST.get('destroy', False)
254 rm_obsolete = self.request.POST.get('destroy', False)
255 invalidate_cache = self.request.POST.get('invalidate', False)
255 invalidate_cache = self.request.POST.get('invalidate', False)
256 log.debug(
256 log.debug(
257 'rescanning repo location with destroy obsolete=%s', rm_obsolete)
257 'rescanning repo location with destroy obsolete=%s', rm_obsolete)
258
258
259 if invalidate_cache:
259 if invalidate_cache:
260 log.debug('invalidating all repositories cache')
260 log.debug('invalidating all repositories cache')
261 for repo in Repository.get_all():
261 for repo in Repository.get_all():
262 ScmModel().mark_for_invalidation(repo.repo_name, delete=True)
262 ScmModel().mark_for_invalidation(repo.repo_name, delete=True)
263
263
264 filesystem_repos = ScmModel().repo_scan()
264 filesystem_repos = ScmModel().repo_scan()
265 added, removed = repo2db_mapper(filesystem_repos, rm_obsolete)
265 added, removed = repo2db_mapper(filesystem_repos, rm_obsolete)
266 _repr = lambda l: ', '.join(map(safe_unicode, l)) or '-'
266 _repr = lambda l: ', '.join(map(safe_unicode, l)) or '-'
267 h.flash(_('Repositories successfully '
267 h.flash(_('Repositories successfully '
268 'rescanned added: %s ; removed: %s') %
268 'rescanned added: %s ; removed: %s') %
269 (_repr(added), _repr(removed)),
269 (_repr(added), _repr(removed)),
270 category='success')
270 category='success')
271 raise HTTPFound(h.route_path('admin_settings_mapping'))
271 raise HTTPFound(h.route_path('admin_settings_mapping'))
272
272
273 @LoginRequired()
273 @LoginRequired()
274 @HasPermissionAllDecorator('hg.admin')
274 @HasPermissionAllDecorator('hg.admin')
275 @view_config(
275 @view_config(
276 route_name='admin_settings', request_method='GET',
276 route_name='admin_settings', request_method='GET',
277 renderer='rhodecode:templates/admin/settings/settings.mako')
277 renderer='rhodecode:templates/admin/settings/settings.mako')
278 @view_config(
278 @view_config(
279 route_name='admin_settings_global', request_method='GET',
279 route_name='admin_settings_global', request_method='GET',
280 renderer='rhodecode:templates/admin/settings/settings.mako')
280 renderer='rhodecode:templates/admin/settings/settings.mako')
281 def settings_global(self):
281 def settings_global(self):
282 c = self.load_default_context()
282 c = self.load_default_context()
283 c.active = 'global'
283 c.active = 'global'
284 c.personal_repo_group_default_pattern = RepoGroupModel()\
284 c.personal_repo_group_default_pattern = RepoGroupModel()\
285 .get_personal_group_name_pattern()
285 .get_personal_group_name_pattern()
286
286
287 data = render('rhodecode:templates/admin/settings/settings.mako',
287 data = render('rhodecode:templates/admin/settings/settings.mako',
288 self._get_template_context(c), self.request)
288 self._get_template_context(c), self.request)
289 html = formencode.htmlfill.render(
289 html = formencode.htmlfill.render(
290 data,
290 data,
291 defaults=self._form_defaults(),
291 defaults=self._form_defaults(),
292 encoding="UTF-8",
292 encoding="UTF-8",
293 force_defaults=False
293 force_defaults=False
294 )
294 )
295 return Response(html)
295 return Response(html)
296
296
297 @LoginRequired()
297 @LoginRequired()
298 @HasPermissionAllDecorator('hg.admin')
298 @HasPermissionAllDecorator('hg.admin')
299 @CSRFRequired()
299 @CSRFRequired()
300 @view_config(
300 @view_config(
301 route_name='admin_settings_update', request_method='POST',
301 route_name='admin_settings_update', request_method='POST',
302 renderer='rhodecode:templates/admin/settings/settings.mako')
302 renderer='rhodecode:templates/admin/settings/settings.mako')
303 @view_config(
303 @view_config(
304 route_name='admin_settings_global_update', request_method='POST',
304 route_name='admin_settings_global_update', request_method='POST',
305 renderer='rhodecode:templates/admin/settings/settings.mako')
305 renderer='rhodecode:templates/admin/settings/settings.mako')
306 def settings_global_update(self):
306 def settings_global_update(self):
307 _ = self.request.translate
307 _ = self.request.translate
308 c = self.load_default_context()
308 c = self.load_default_context()
309 c.active = 'global'
309 c.active = 'global'
310 c.personal_repo_group_default_pattern = RepoGroupModel()\
310 c.personal_repo_group_default_pattern = RepoGroupModel()\
311 .get_personal_group_name_pattern()
311 .get_personal_group_name_pattern()
312 application_form = ApplicationSettingsForm(self.request.translate)()
312 application_form = ApplicationSettingsForm(self.request.translate)()
313 try:
313 try:
314 form_result = application_form.to_python(dict(self.request.POST))
314 form_result = application_form.to_python(dict(self.request.POST))
315 except formencode.Invalid as errors:
315 except formencode.Invalid as errors:
316 h.flash(
316 h.flash(
317 _("Some form inputs contain invalid data."),
317 _("Some form inputs contain invalid data."),
318 category='error')
318 category='error')
319 data = render('rhodecode:templates/admin/settings/settings.mako',
319 data = render('rhodecode:templates/admin/settings/settings.mako',
320 self._get_template_context(c), self.request)
320 self._get_template_context(c), self.request)
321 html = formencode.htmlfill.render(
321 html = formencode.htmlfill.render(
322 data,
322 data,
323 defaults=errors.value,
323 defaults=errors.value,
324 errors=errors.error_dict or {},
324 errors=errors.error_dict or {},
325 prefix_error=False,
325 prefix_error=False,
326 encoding="UTF-8",
326 encoding="UTF-8",
327 force_defaults=False
327 force_defaults=False
328 )
328 )
329 return Response(html)
329 return Response(html)
330
330
331 settings = [
331 settings = [
332 ('title', 'rhodecode_title', 'unicode'),
332 ('title', 'rhodecode_title', 'unicode'),
333 ('realm', 'rhodecode_realm', 'unicode'),
333 ('realm', 'rhodecode_realm', 'unicode'),
334 ('pre_code', 'rhodecode_pre_code', 'unicode'),
334 ('pre_code', 'rhodecode_pre_code', 'unicode'),
335 ('post_code', 'rhodecode_post_code', 'unicode'),
335 ('post_code', 'rhodecode_post_code', 'unicode'),
336 ('captcha_public_key', 'rhodecode_captcha_public_key', 'unicode'),
336 ('captcha_public_key', 'rhodecode_captcha_public_key', 'unicode'),
337 ('captcha_private_key', 'rhodecode_captcha_private_key', 'unicode'),
337 ('captcha_private_key', 'rhodecode_captcha_private_key', 'unicode'),
338 ('create_personal_repo_group', 'rhodecode_create_personal_repo_group', 'bool'),
338 ('create_personal_repo_group', 'rhodecode_create_personal_repo_group', 'bool'),
339 ('personal_repo_group_pattern', 'rhodecode_personal_repo_group_pattern', 'unicode'),
339 ('personal_repo_group_pattern', 'rhodecode_personal_repo_group_pattern', 'unicode'),
340 ]
340 ]
341 try:
341 try:
342 for setting, form_key, type_ in settings:
342 for setting, form_key, type_ in settings:
343 sett = SettingsModel().create_or_update_setting(
343 sett = SettingsModel().create_or_update_setting(
344 setting, form_result[form_key], type_)
344 setting, form_result[form_key], type_)
345 Session().add(sett)
345 Session().add(sett)
346
346
347 Session().commit()
347 Session().commit()
348 SettingsModel().invalidate_settings_cache()
348 SettingsModel().invalidate_settings_cache()
349 h.flash(_('Updated application settings'), category='success')
349 h.flash(_('Updated application settings'), category='success')
350 except Exception:
350 except Exception:
351 log.exception("Exception while updating application settings")
351 log.exception("Exception while updating application settings")
352 h.flash(
352 h.flash(
353 _('Error occurred during updating application settings'),
353 _('Error occurred during updating application settings'),
354 category='error')
354 category='error')
355
355
356 raise HTTPFound(h.route_path('admin_settings_global'))
356 raise HTTPFound(h.route_path('admin_settings_global'))
357
357
358 @LoginRequired()
358 @LoginRequired()
359 @HasPermissionAllDecorator('hg.admin')
359 @HasPermissionAllDecorator('hg.admin')
360 @view_config(
360 @view_config(
361 route_name='admin_settings_visual', request_method='GET',
361 route_name='admin_settings_visual', request_method='GET',
362 renderer='rhodecode:templates/admin/settings/settings.mako')
362 renderer='rhodecode:templates/admin/settings/settings.mako')
363 def settings_visual(self):
363 def settings_visual(self):
364 c = self.load_default_context()
364 c = self.load_default_context()
365 c.active = 'visual'
365 c.active = 'visual'
366
366
367 data = render('rhodecode:templates/admin/settings/settings.mako',
367 data = render('rhodecode:templates/admin/settings/settings.mako',
368 self._get_template_context(c), self.request)
368 self._get_template_context(c), self.request)
369 html = formencode.htmlfill.render(
369 html = formencode.htmlfill.render(
370 data,
370 data,
371 defaults=self._form_defaults(),
371 defaults=self._form_defaults(),
372 encoding="UTF-8",
372 encoding="UTF-8",
373 force_defaults=False
373 force_defaults=False
374 )
374 )
375 return Response(html)
375 return Response(html)
376
376
377 @LoginRequired()
377 @LoginRequired()
378 @HasPermissionAllDecorator('hg.admin')
378 @HasPermissionAllDecorator('hg.admin')
379 @CSRFRequired()
379 @CSRFRequired()
380 @view_config(
380 @view_config(
381 route_name='admin_settings_visual_update', request_method='POST',
381 route_name='admin_settings_visual_update', request_method='POST',
382 renderer='rhodecode:templates/admin/settings/settings.mako')
382 renderer='rhodecode:templates/admin/settings/settings.mako')
383 def settings_visual_update(self):
383 def settings_visual_update(self):
384 _ = self.request.translate
384 _ = self.request.translate
385 c = self.load_default_context()
385 c = self.load_default_context()
386 c.active = 'visual'
386 c.active = 'visual'
387 application_form = ApplicationVisualisationForm(self.request.translate)()
387 application_form = ApplicationVisualisationForm(self.request.translate)()
388 try:
388 try:
389 form_result = application_form.to_python(dict(self.request.POST))
389 form_result = application_form.to_python(dict(self.request.POST))
390 except formencode.Invalid as errors:
390 except formencode.Invalid as errors:
391 h.flash(
391 h.flash(
392 _("Some form inputs contain invalid data."),
392 _("Some form inputs contain invalid data."),
393 category='error')
393 category='error')
394 data = render('rhodecode:templates/admin/settings/settings.mako',
394 data = render('rhodecode:templates/admin/settings/settings.mako',
395 self._get_template_context(c), self.request)
395 self._get_template_context(c), self.request)
396 html = formencode.htmlfill.render(
396 html = formencode.htmlfill.render(
397 data,
397 data,
398 defaults=errors.value,
398 defaults=errors.value,
399 errors=errors.error_dict or {},
399 errors=errors.error_dict or {},
400 prefix_error=False,
400 prefix_error=False,
401 encoding="UTF-8",
401 encoding="UTF-8",
402 force_defaults=False
402 force_defaults=False
403 )
403 )
404 return Response(html)
404 return Response(html)
405
405
406 try:
406 try:
407 settings = [
407 settings = [
408 ('show_public_icon', 'rhodecode_show_public_icon', 'bool'),
408 ('show_public_icon', 'rhodecode_show_public_icon', 'bool'),
409 ('show_private_icon', 'rhodecode_show_private_icon', 'bool'),
409 ('show_private_icon', 'rhodecode_show_private_icon', 'bool'),
410 ('stylify_metatags', 'rhodecode_stylify_metatags', 'bool'),
410 ('stylify_metatags', 'rhodecode_stylify_metatags', 'bool'),
411 ('repository_fields', 'rhodecode_repository_fields', 'bool'),
411 ('repository_fields', 'rhodecode_repository_fields', 'bool'),
412 ('dashboard_items', 'rhodecode_dashboard_items', 'int'),
412 ('dashboard_items', 'rhodecode_dashboard_items', 'int'),
413 ('admin_grid_items', 'rhodecode_admin_grid_items', 'int'),
413 ('admin_grid_items', 'rhodecode_admin_grid_items', 'int'),
414 ('show_version', 'rhodecode_show_version', 'bool'),
414 ('show_version', 'rhodecode_show_version', 'bool'),
415 ('use_gravatar', 'rhodecode_use_gravatar', 'bool'),
415 ('use_gravatar', 'rhodecode_use_gravatar', 'bool'),
416 ('markup_renderer', 'rhodecode_markup_renderer', 'unicode'),
416 ('markup_renderer', 'rhodecode_markup_renderer', 'unicode'),
417 ('gravatar_url', 'rhodecode_gravatar_url', 'unicode'),
417 ('gravatar_url', 'rhodecode_gravatar_url', 'unicode'),
418 ('clone_uri_tmpl', 'rhodecode_clone_uri_tmpl', 'unicode'),
418 ('clone_uri_tmpl', 'rhodecode_clone_uri_tmpl', 'unicode'),
419 ('clone_uri_ssh_tmpl', 'rhodecode_clone_uri_ssh_tmpl', 'unicode'),
419 ('clone_uri_ssh_tmpl', 'rhodecode_clone_uri_ssh_tmpl', 'unicode'),
420 ('support_url', 'rhodecode_support_url', 'unicode'),
420 ('support_url', 'rhodecode_support_url', 'unicode'),
421 ('show_revision_number', 'rhodecode_show_revision_number', 'bool'),
421 ('show_revision_number', 'rhodecode_show_revision_number', 'bool'),
422 ('show_sha_length', 'rhodecode_show_sha_length', 'int'),
422 ('show_sha_length', 'rhodecode_show_sha_length', 'int'),
423 ]
423 ]
424 for setting, form_key, type_ in settings:
424 for setting, form_key, type_ in settings:
425 sett = SettingsModel().create_or_update_setting(
425 sett = SettingsModel().create_or_update_setting(
426 setting, form_result[form_key], type_)
426 setting, form_result[form_key], type_)
427 Session().add(sett)
427 Session().add(sett)
428
428
429 Session().commit()
429 Session().commit()
430 SettingsModel().invalidate_settings_cache()
430 SettingsModel().invalidate_settings_cache()
431 h.flash(_('Updated visualisation settings'), category='success')
431 h.flash(_('Updated visualisation settings'), category='success')
432 except Exception:
432 except Exception:
433 log.exception("Exception updating visualization settings")
433 log.exception("Exception updating visualization settings")
434 h.flash(_('Error occurred during updating '
434 h.flash(_('Error occurred during updating '
435 'visualisation settings'),
435 'visualisation settings'),
436 category='error')
436 category='error')
437
437
438 raise HTTPFound(h.route_path('admin_settings_visual'))
438 raise HTTPFound(h.route_path('admin_settings_visual'))
439
439
440 @LoginRequired()
440 @LoginRequired()
441 @HasPermissionAllDecorator('hg.admin')
441 @HasPermissionAllDecorator('hg.admin')
442 @view_config(
442 @view_config(
443 route_name='admin_settings_issuetracker', request_method='GET',
443 route_name='admin_settings_issuetracker', request_method='GET',
444 renderer='rhodecode:templates/admin/settings/settings.mako')
444 renderer='rhodecode:templates/admin/settings/settings.mako')
445 def settings_issuetracker(self):
445 def settings_issuetracker(self):
446 c = self.load_default_context()
446 c = self.load_default_context()
447 c.active = 'issuetracker'
447 c.active = 'issuetracker'
448 defaults = SettingsModel().get_all_settings()
448 defaults = c.rc_config
449
449
450 entry_key = 'rhodecode_issuetracker_pat_'
450 entry_key = 'rhodecode_issuetracker_pat_'
451
451
452 c.issuetracker_entries = {}
452 c.issuetracker_entries = {}
453 for k, v in defaults.items():
453 for k, v in defaults.items():
454 if k.startswith(entry_key):
454 if k.startswith(entry_key):
455 uid = k[len(entry_key):]
455 uid = k[len(entry_key):]
456 c.issuetracker_entries[uid] = None
456 c.issuetracker_entries[uid] = None
457
457
458 for uid in c.issuetracker_entries:
458 for uid in c.issuetracker_entries:
459 c.issuetracker_entries[uid] = AttributeDict({
459 c.issuetracker_entries[uid] = AttributeDict({
460 'pat': defaults.get('rhodecode_issuetracker_pat_' + uid),
460 'pat': defaults.get('rhodecode_issuetracker_pat_' + uid),
461 'url': defaults.get('rhodecode_issuetracker_url_' + uid),
461 'url': defaults.get('rhodecode_issuetracker_url_' + uid),
462 'pref': defaults.get('rhodecode_issuetracker_pref_' + uid),
462 'pref': defaults.get('rhodecode_issuetracker_pref_' + uid),
463 'desc': defaults.get('rhodecode_issuetracker_desc_' + uid),
463 'desc': defaults.get('rhodecode_issuetracker_desc_' + uid),
464 })
464 })
465
465
466 return self._get_template_context(c)
466 return self._get_template_context(c)
467
467
468 @LoginRequired()
468 @LoginRequired()
469 @HasPermissionAllDecorator('hg.admin')
469 @HasPermissionAllDecorator('hg.admin')
470 @CSRFRequired()
470 @CSRFRequired()
471 @view_config(
471 @view_config(
472 route_name='admin_settings_issuetracker_test', request_method='POST',
472 route_name='admin_settings_issuetracker_test', request_method='POST',
473 renderer='string', xhr=True)
473 renderer='string', xhr=True)
474 def settings_issuetracker_test(self):
474 def settings_issuetracker_test(self):
475 return h.urlify_commit_message(
475 return h.urlify_commit_message(
476 self.request.POST.get('test_text', ''),
476 self.request.POST.get('test_text', ''),
477 'repo_group/test_repo1')
477 'repo_group/test_repo1')
478
478
479 @LoginRequired()
479 @LoginRequired()
480 @HasPermissionAllDecorator('hg.admin')
480 @HasPermissionAllDecorator('hg.admin')
481 @CSRFRequired()
481 @CSRFRequired()
482 @view_config(
482 @view_config(
483 route_name='admin_settings_issuetracker_update', request_method='POST',
483 route_name='admin_settings_issuetracker_update', request_method='POST',
484 renderer='rhodecode:templates/admin/settings/settings.mako')
484 renderer='rhodecode:templates/admin/settings/settings.mako')
485 def settings_issuetracker_update(self):
485 def settings_issuetracker_update(self):
486 _ = self.request.translate
486 _ = self.request.translate
487 self.load_default_context()
487 self.load_default_context()
488 settings_model = IssueTrackerSettingsModel()
488 settings_model = IssueTrackerSettingsModel()
489
489
490 try:
490 try:
491 form = IssueTrackerPatternsForm(self.request.translate)()
491 form = IssueTrackerPatternsForm(self.request.translate)()
492 data = form.to_python(self.request.POST)
492 data = form.to_python(self.request.POST)
493 except formencode.Invalid as errors:
493 except formencode.Invalid as errors:
494 log.exception('Failed to add new pattern')
494 log.exception('Failed to add new pattern')
495 error = errors
495 error = errors
496 h.flash(_('Invalid issue tracker pattern: {}'.format(error)),
496 h.flash(_('Invalid issue tracker pattern: {}'.format(error)),
497 category='error')
497 category='error')
498 raise HTTPFound(h.route_path('admin_settings_issuetracker'))
498 raise HTTPFound(h.route_path('admin_settings_issuetracker'))
499
499
500 if data:
500 if data:
501 for uid in data.get('delete_patterns', []):
501 for uid in data.get('delete_patterns', []):
502 settings_model.delete_entries(uid)
502 settings_model.delete_entries(uid)
503
503
504 for pattern in data.get('patterns', []):
504 for pattern in data.get('patterns', []):
505 for setting, value, type_ in pattern:
505 for setting, value, type_ in pattern:
506 sett = settings_model.create_or_update_setting(
506 sett = settings_model.create_or_update_setting(
507 setting, value, type_)
507 setting, value, type_)
508 Session().add(sett)
508 Session().add(sett)
509
509
510 Session().commit()
510 Session().commit()
511
511
512 SettingsModel().invalidate_settings_cache()
512 SettingsModel().invalidate_settings_cache()
513 h.flash(_('Updated issue tracker entries'), category='success')
513 h.flash(_('Updated issue tracker entries'), category='success')
514 raise HTTPFound(h.route_path('admin_settings_issuetracker'))
514 raise HTTPFound(h.route_path('admin_settings_issuetracker'))
515
515
516 @LoginRequired()
516 @LoginRequired()
517 @HasPermissionAllDecorator('hg.admin')
517 @HasPermissionAllDecorator('hg.admin')
518 @CSRFRequired()
518 @CSRFRequired()
519 @view_config(
519 @view_config(
520 route_name='admin_settings_issuetracker_delete', request_method='POST',
520 route_name='admin_settings_issuetracker_delete', request_method='POST',
521 renderer='rhodecode:templates/admin/settings/settings.mako')
521 renderer='rhodecode:templates/admin/settings/settings.mako')
522 def settings_issuetracker_delete(self):
522 def settings_issuetracker_delete(self):
523 _ = self.request.translate
523 _ = self.request.translate
524 self.load_default_context()
524 self.load_default_context()
525 uid = self.request.POST.get('uid')
525 uid = self.request.POST.get('uid')
526 try:
526 try:
527 IssueTrackerSettingsModel().delete_entries(uid)
527 IssueTrackerSettingsModel().delete_entries(uid)
528 except Exception:
528 except Exception:
529 log.exception('Failed to delete issue tracker setting %s', uid)
529 log.exception('Failed to delete issue tracker setting %s', uid)
530 raise HTTPNotFound()
530 raise HTTPNotFound()
531 h.flash(_('Removed issue tracker entry'), category='success')
531 h.flash(_('Removed issue tracker entry'), category='success')
532 raise HTTPFound(h.route_path('admin_settings_issuetracker'))
532 raise HTTPFound(h.route_path('admin_settings_issuetracker'))
533
533
534 @LoginRequired()
534 @LoginRequired()
535 @HasPermissionAllDecorator('hg.admin')
535 @HasPermissionAllDecorator('hg.admin')
536 @view_config(
536 @view_config(
537 route_name='admin_settings_email', request_method='GET',
537 route_name='admin_settings_email', request_method='GET',
538 renderer='rhodecode:templates/admin/settings/settings.mako')
538 renderer='rhodecode:templates/admin/settings/settings.mako')
539 def settings_email(self):
539 def settings_email(self):
540 c = self.load_default_context()
540 c = self.load_default_context()
541 c.active = 'email'
541 c.active = 'email'
542 c.rhodecode_ini = rhodecode.CONFIG
542 c.rhodecode_ini = rhodecode.CONFIG
543
543
544 data = render('rhodecode:templates/admin/settings/settings.mako',
544 data = render('rhodecode:templates/admin/settings/settings.mako',
545 self._get_template_context(c), self.request)
545 self._get_template_context(c), self.request)
546 html = formencode.htmlfill.render(
546 html = formencode.htmlfill.render(
547 data,
547 data,
548 defaults=self._form_defaults(),
548 defaults=self._form_defaults(),
549 encoding="UTF-8",
549 encoding="UTF-8",
550 force_defaults=False
550 force_defaults=False
551 )
551 )
552 return Response(html)
552 return Response(html)
553
553
554 @LoginRequired()
554 @LoginRequired()
555 @HasPermissionAllDecorator('hg.admin')
555 @HasPermissionAllDecorator('hg.admin')
556 @CSRFRequired()
556 @CSRFRequired()
557 @view_config(
557 @view_config(
558 route_name='admin_settings_email_update', request_method='POST',
558 route_name='admin_settings_email_update', request_method='POST',
559 renderer='rhodecode:templates/admin/settings/settings.mako')
559 renderer='rhodecode:templates/admin/settings/settings.mako')
560 def settings_email_update(self):
560 def settings_email_update(self):
561 _ = self.request.translate
561 _ = self.request.translate
562 c = self.load_default_context()
562 c = self.load_default_context()
563 c.active = 'email'
563 c.active = 'email'
564
564
565 test_email = self.request.POST.get('test_email')
565 test_email = self.request.POST.get('test_email')
566
566
567 if not test_email:
567 if not test_email:
568 h.flash(_('Please enter email address'), category='error')
568 h.flash(_('Please enter email address'), category='error')
569 raise HTTPFound(h.route_path('admin_settings_email'))
569 raise HTTPFound(h.route_path('admin_settings_email'))
570
570
571 email_kwargs = {
571 email_kwargs = {
572 'date': datetime.datetime.now(),
572 'date': datetime.datetime.now(),
573 'user': c.rhodecode_user,
573 'user': c.rhodecode_user,
574 'rhodecode_version': c.rhodecode_version
574 'rhodecode_version': c.rhodecode_version
575 }
575 }
576
576
577 (subject, headers, email_body,
577 (subject, headers, email_body,
578 email_body_plaintext) = EmailNotificationModel().render_email(
578 email_body_plaintext) = EmailNotificationModel().render_email(
579 EmailNotificationModel.TYPE_EMAIL_TEST, **email_kwargs)
579 EmailNotificationModel.TYPE_EMAIL_TEST, **email_kwargs)
580
580
581 recipients = [test_email] if test_email else None
581 recipients = [test_email] if test_email else None
582
582
583 run_task(tasks.send_email, recipients, subject,
583 run_task(tasks.send_email, recipients, subject,
584 email_body_plaintext, email_body)
584 email_body_plaintext, email_body)
585
585
586 h.flash(_('Send email task created'), category='success')
586 h.flash(_('Send email task created'), category='success')
587 raise HTTPFound(h.route_path('admin_settings_email'))
587 raise HTTPFound(h.route_path('admin_settings_email'))
588
588
589 @LoginRequired()
589 @LoginRequired()
590 @HasPermissionAllDecorator('hg.admin')
590 @HasPermissionAllDecorator('hg.admin')
591 @view_config(
591 @view_config(
592 route_name='admin_settings_hooks', request_method='GET',
592 route_name='admin_settings_hooks', request_method='GET',
593 renderer='rhodecode:templates/admin/settings/settings.mako')
593 renderer='rhodecode:templates/admin/settings/settings.mako')
594 def settings_hooks(self):
594 def settings_hooks(self):
595 c = self.load_default_context()
595 c = self.load_default_context()
596 c.active = 'hooks'
596 c.active = 'hooks'
597
597
598 model = SettingsModel()
598 model = SettingsModel()
599 c.hooks = model.get_builtin_hooks()
599 c.hooks = model.get_builtin_hooks()
600 c.custom_hooks = model.get_custom_hooks()
600 c.custom_hooks = model.get_custom_hooks()
601
601
602 data = render('rhodecode:templates/admin/settings/settings.mako',
602 data = render('rhodecode:templates/admin/settings/settings.mako',
603 self._get_template_context(c), self.request)
603 self._get_template_context(c), self.request)
604 html = formencode.htmlfill.render(
604 html = formencode.htmlfill.render(
605 data,
605 data,
606 defaults=self._form_defaults(),
606 defaults=self._form_defaults(),
607 encoding="UTF-8",
607 encoding="UTF-8",
608 force_defaults=False
608 force_defaults=False
609 )
609 )
610 return Response(html)
610 return Response(html)
611
611
612 @LoginRequired()
612 @LoginRequired()
613 @HasPermissionAllDecorator('hg.admin')
613 @HasPermissionAllDecorator('hg.admin')
614 @CSRFRequired()
614 @CSRFRequired()
615 @view_config(
615 @view_config(
616 route_name='admin_settings_hooks_update', request_method='POST',
616 route_name='admin_settings_hooks_update', request_method='POST',
617 renderer='rhodecode:templates/admin/settings/settings.mako')
617 renderer='rhodecode:templates/admin/settings/settings.mako')
618 @view_config(
618 @view_config(
619 route_name='admin_settings_hooks_delete', request_method='POST',
619 route_name='admin_settings_hooks_delete', request_method='POST',
620 renderer='rhodecode:templates/admin/settings/settings.mako')
620 renderer='rhodecode:templates/admin/settings/settings.mako')
621 def settings_hooks_update(self):
621 def settings_hooks_update(self):
622 _ = self.request.translate
622 _ = self.request.translate
623 c = self.load_default_context()
623 c = self.load_default_context()
624 c.active = 'hooks'
624 c.active = 'hooks'
625 if c.visual.allow_custom_hooks_settings:
625 if c.visual.allow_custom_hooks_settings:
626 ui_key = self.request.POST.get('new_hook_ui_key')
626 ui_key = self.request.POST.get('new_hook_ui_key')
627 ui_value = self.request.POST.get('new_hook_ui_value')
627 ui_value = self.request.POST.get('new_hook_ui_value')
628
628
629 hook_id = self.request.POST.get('hook_id')
629 hook_id = self.request.POST.get('hook_id')
630 new_hook = False
630 new_hook = False
631
631
632 model = SettingsModel()
632 model = SettingsModel()
633 try:
633 try:
634 if ui_value and ui_key:
634 if ui_value and ui_key:
635 model.create_or_update_hook(ui_key, ui_value)
635 model.create_or_update_hook(ui_key, ui_value)
636 h.flash(_('Added new hook'), category='success')
636 h.flash(_('Added new hook'), category='success')
637 new_hook = True
637 new_hook = True
638 elif hook_id:
638 elif hook_id:
639 RhodeCodeUi.delete(hook_id)
639 RhodeCodeUi.delete(hook_id)
640 Session().commit()
640 Session().commit()
641
641
642 # check for edits
642 # check for edits
643 update = False
643 update = False
644 _d = self.request.POST.dict_of_lists()
644 _d = self.request.POST.dict_of_lists()
645 for k, v in zip(_d.get('hook_ui_key', []),
645 for k, v in zip(_d.get('hook_ui_key', []),
646 _d.get('hook_ui_value_new', [])):
646 _d.get('hook_ui_value_new', [])):
647 model.create_or_update_hook(k, v)
647 model.create_or_update_hook(k, v)
648 update = True
648 update = True
649
649
650 if update and not new_hook:
650 if update and not new_hook:
651 h.flash(_('Updated hooks'), category='success')
651 h.flash(_('Updated hooks'), category='success')
652 Session().commit()
652 Session().commit()
653 except Exception:
653 except Exception:
654 log.exception("Exception during hook creation")
654 log.exception("Exception during hook creation")
655 h.flash(_('Error occurred during hook creation'),
655 h.flash(_('Error occurred during hook creation'),
656 category='error')
656 category='error')
657
657
658 raise HTTPFound(h.route_path('admin_settings_hooks'))
658 raise HTTPFound(h.route_path('admin_settings_hooks'))
659
659
660 @LoginRequired()
660 @LoginRequired()
661 @HasPermissionAllDecorator('hg.admin')
661 @HasPermissionAllDecorator('hg.admin')
662 @view_config(
662 @view_config(
663 route_name='admin_settings_search', request_method='GET',
663 route_name='admin_settings_search', request_method='GET',
664 renderer='rhodecode:templates/admin/settings/settings.mako')
664 renderer='rhodecode:templates/admin/settings/settings.mako')
665 def settings_search(self):
665 def settings_search(self):
666 c = self.load_default_context()
666 c = self.load_default_context()
667 c.active = 'search'
667 c.active = 'search'
668
668
669 c.searcher = searcher_from_config(self.request.registry.settings)
669 c.searcher = searcher_from_config(self.request.registry.settings)
670 c.statistics = c.searcher.statistics(self.request.translate)
670 c.statistics = c.searcher.statistics(self.request.translate)
671
671
672 return self._get_template_context(c)
672 return self._get_template_context(c)
673
673
674 @LoginRequired()
674 @LoginRequired()
675 @HasPermissionAllDecorator('hg.admin')
675 @HasPermissionAllDecorator('hg.admin')
676 @view_config(
676 @view_config(
677 route_name='admin_settings_automation', request_method='GET',
677 route_name='admin_settings_automation', request_method='GET',
678 renderer='rhodecode:templates/admin/settings/settings.mako')
678 renderer='rhodecode:templates/admin/settings/settings.mako')
679 def settings_automation(self):
679 def settings_automation(self):
680 c = self.load_default_context()
680 c = self.load_default_context()
681 c.active = 'automation'
681 c.active = 'automation'
682
682
683 return self._get_template_context(c)
683 return self._get_template_context(c)
684
684
685 @LoginRequired()
685 @LoginRequired()
686 @HasPermissionAllDecorator('hg.admin')
686 @HasPermissionAllDecorator('hg.admin')
687 @view_config(
687 @view_config(
688 route_name='admin_settings_labs', request_method='GET',
688 route_name='admin_settings_labs', request_method='GET',
689 renderer='rhodecode:templates/admin/settings/settings.mako')
689 renderer='rhodecode:templates/admin/settings/settings.mako')
690 def settings_labs(self):
690 def settings_labs(self):
691 c = self.load_default_context()
691 c = self.load_default_context()
692 if not c.labs_active:
692 if not c.labs_active:
693 raise HTTPFound(h.route_path('admin_settings'))
693 raise HTTPFound(h.route_path('admin_settings'))
694
694
695 c.active = 'labs'
695 c.active = 'labs'
696 c.lab_settings = _LAB_SETTINGS
696 c.lab_settings = _LAB_SETTINGS
697
697
698 data = render('rhodecode:templates/admin/settings/settings.mako',
698 data = render('rhodecode:templates/admin/settings/settings.mako',
699 self._get_template_context(c), self.request)
699 self._get_template_context(c), self.request)
700 html = formencode.htmlfill.render(
700 html = formencode.htmlfill.render(
701 data,
701 data,
702 defaults=self._form_defaults(),
702 defaults=self._form_defaults(),
703 encoding="UTF-8",
703 encoding="UTF-8",
704 force_defaults=False
704 force_defaults=False
705 )
705 )
706 return Response(html)
706 return Response(html)
707
707
708 @LoginRequired()
708 @LoginRequired()
709 @HasPermissionAllDecorator('hg.admin')
709 @HasPermissionAllDecorator('hg.admin')
710 @CSRFRequired()
710 @CSRFRequired()
711 @view_config(
711 @view_config(
712 route_name='admin_settings_labs_update', request_method='POST',
712 route_name='admin_settings_labs_update', request_method='POST',
713 renderer='rhodecode:templates/admin/settings/settings.mako')
713 renderer='rhodecode:templates/admin/settings/settings.mako')
714 def settings_labs_update(self):
714 def settings_labs_update(self):
715 _ = self.request.translate
715 _ = self.request.translate
716 c = self.load_default_context()
716 c = self.load_default_context()
717 c.active = 'labs'
717 c.active = 'labs'
718
718
719 application_form = LabsSettingsForm(self.request.translate)()
719 application_form = LabsSettingsForm(self.request.translate)()
720 try:
720 try:
721 form_result = application_form.to_python(dict(self.request.POST))
721 form_result = application_form.to_python(dict(self.request.POST))
722 except formencode.Invalid as errors:
722 except formencode.Invalid as errors:
723 h.flash(
723 h.flash(
724 _("Some form inputs contain invalid data."),
724 _("Some form inputs contain invalid data."),
725 category='error')
725 category='error')
726 data = render('rhodecode:templates/admin/settings/settings.mako',
726 data = render('rhodecode:templates/admin/settings/settings.mako',
727 self._get_template_context(c), self.request)
727 self._get_template_context(c), self.request)
728 html = formencode.htmlfill.render(
728 html = formencode.htmlfill.render(
729 data,
729 data,
730 defaults=errors.value,
730 defaults=errors.value,
731 errors=errors.error_dict or {},
731 errors=errors.error_dict or {},
732 prefix_error=False,
732 prefix_error=False,
733 encoding="UTF-8",
733 encoding="UTF-8",
734 force_defaults=False
734 force_defaults=False
735 )
735 )
736 return Response(html)
736 return Response(html)
737
737
738 try:
738 try:
739 session = Session()
739 session = Session()
740 for setting in _LAB_SETTINGS:
740 for setting in _LAB_SETTINGS:
741 setting_name = setting.key[len('rhodecode_'):]
741 setting_name = setting.key[len('rhodecode_'):]
742 sett = SettingsModel().create_or_update_setting(
742 sett = SettingsModel().create_or_update_setting(
743 setting_name, form_result[setting.key], setting.type)
743 setting_name, form_result[setting.key], setting.type)
744 session.add(sett)
744 session.add(sett)
745
745
746 except Exception:
746 except Exception:
747 log.exception('Exception while updating lab settings')
747 log.exception('Exception while updating lab settings')
748 h.flash(_('Error occurred during updating labs settings'),
748 h.flash(_('Error occurred during updating labs settings'),
749 category='error')
749 category='error')
750 else:
750 else:
751 Session().commit()
751 Session().commit()
752 SettingsModel().invalidate_settings_cache()
752 SettingsModel().invalidate_settings_cache()
753 h.flash(_('Updated Labs settings'), category='success')
753 h.flash(_('Updated Labs settings'), category='success')
754 raise HTTPFound(h.route_path('admin_settings_labs'))
754 raise HTTPFound(h.route_path('admin_settings_labs'))
755
755
756 data = render('rhodecode:templates/admin/settings/settings.mako',
756 data = render('rhodecode:templates/admin/settings/settings.mako',
757 self._get_template_context(c), self.request)
757 self._get_template_context(c), self.request)
758 html = formencode.htmlfill.render(
758 html = formencode.htmlfill.render(
759 data,
759 data,
760 defaults=self._form_defaults(),
760 defaults=self._form_defaults(),
761 encoding="UTF-8",
761 encoding="UTF-8",
762 force_defaults=False
762 force_defaults=False
763 )
763 )
764 return Response(html)
764 return Response(html)
765
765
766
766
767 # :param key: name of the setting including the 'rhodecode_' prefix
767 # :param key: name of the setting including the 'rhodecode_' prefix
768 # :param type: the RhodeCodeSetting type to use.
768 # :param type: the RhodeCodeSetting type to use.
769 # :param group: the i18ned group in which we should dispaly this setting
769 # :param group: the i18ned group in which we should dispaly this setting
770 # :param label: the i18ned label we should display for this setting
770 # :param label: the i18ned label we should display for this setting
771 # :param help: the i18ned help we should dispaly for this setting
771 # :param help: the i18ned help we should dispaly for this setting
772 LabSetting = collections.namedtuple(
772 LabSetting = collections.namedtuple(
773 'LabSetting', ('key', 'type', 'group', 'label', 'help'))
773 'LabSetting', ('key', 'type', 'group', 'label', 'help'))
774
774
775
775
776 # This list has to be kept in sync with the form
776 # This list has to be kept in sync with the form
777 # rhodecode.model.forms.LabsSettingsForm.
777 # rhodecode.model.forms.LabsSettingsForm.
778 _LAB_SETTINGS = [
778 _LAB_SETTINGS = [
779
779
780 ]
780 ]
@@ -1,592 +1,592 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 The base Controller API
22 The base Controller API
23 Provides the BaseController class for subclassing. And usage in different
23 Provides the BaseController class for subclassing. And usage in different
24 controllers
24 controllers
25 """
25 """
26
26
27 import logging
27 import logging
28 import socket
28 import socket
29
29
30 import markupsafe
30 import markupsafe
31 import ipaddress
31 import ipaddress
32
32
33 from paste.auth.basic import AuthBasicAuthenticator
33 from paste.auth.basic import AuthBasicAuthenticator
34 from paste.httpexceptions import HTTPUnauthorized, HTTPForbidden, get_exception
34 from paste.httpexceptions import HTTPUnauthorized, HTTPForbidden, get_exception
35 from paste.httpheaders import WWW_AUTHENTICATE, AUTHORIZATION
35 from paste.httpheaders import WWW_AUTHENTICATE, AUTHORIZATION
36
36
37 import rhodecode
37 import rhodecode
38 from rhodecode.apps._base import TemplateArgs
38 from rhodecode.apps._base import TemplateArgs
39 from rhodecode.authentication.base import VCS_TYPE
39 from rhodecode.authentication.base import VCS_TYPE
40 from rhodecode.lib import auth, utils2
40 from rhodecode.lib import auth, utils2
41 from rhodecode.lib import helpers as h
41 from rhodecode.lib import helpers as h
42 from rhodecode.lib.auth import AuthUser, CookieStoreWrapper
42 from rhodecode.lib.auth import AuthUser, CookieStoreWrapper
43 from rhodecode.lib.exceptions import UserCreationError
43 from rhodecode.lib.exceptions import UserCreationError
44 from rhodecode.lib.utils import (password_changed, get_enabled_hook_classes)
44 from rhodecode.lib.utils import (password_changed, get_enabled_hook_classes)
45 from rhodecode.lib.utils2 import (
45 from rhodecode.lib.utils2 import (
46 str2bool, safe_unicode, AttributeDict, safe_int, sha1, aslist, safe_str)
46 str2bool, safe_unicode, AttributeDict, safe_int, sha1, aslist, safe_str)
47 from rhodecode.model.db import Repository, User, ChangesetComment, UserBookmark
47 from rhodecode.model.db import Repository, User, ChangesetComment, UserBookmark
48 from rhodecode.model.notification import NotificationModel
48 from rhodecode.model.notification import NotificationModel
49 from rhodecode.model.settings import VcsSettingsModel, SettingsModel
49 from rhodecode.model.settings import VcsSettingsModel, SettingsModel
50
50
51 log = logging.getLogger(__name__)
51 log = logging.getLogger(__name__)
52
52
53
53
54 def _filter_proxy(ip):
54 def _filter_proxy(ip):
55 """
55 """
56 Passed in IP addresses in HEADERS can be in a special format of multiple
56 Passed in IP addresses in HEADERS can be in a special format of multiple
57 ips. Those comma separated IPs are passed from various proxies in the
57 ips. Those comma separated IPs are passed from various proxies in the
58 chain of request processing. The left-most being the original client.
58 chain of request processing. The left-most being the original client.
59 We only care about the first IP which came from the org. client.
59 We only care about the first IP which came from the org. client.
60
60
61 :param ip: ip string from headers
61 :param ip: ip string from headers
62 """
62 """
63 if ',' in ip:
63 if ',' in ip:
64 _ips = ip.split(',')
64 _ips = ip.split(',')
65 _first_ip = _ips[0].strip()
65 _first_ip = _ips[0].strip()
66 log.debug('Got multiple IPs %s, using %s', ','.join(_ips), _first_ip)
66 log.debug('Got multiple IPs %s, using %s', ','.join(_ips), _first_ip)
67 return _first_ip
67 return _first_ip
68 return ip
68 return ip
69
69
70
70
71 def _filter_port(ip):
71 def _filter_port(ip):
72 """
72 """
73 Removes a port from ip, there are 4 main cases to handle here.
73 Removes a port from ip, there are 4 main cases to handle here.
74 - ipv4 eg. 127.0.0.1
74 - ipv4 eg. 127.0.0.1
75 - ipv6 eg. ::1
75 - ipv6 eg. ::1
76 - ipv4+port eg. 127.0.0.1:8080
76 - ipv4+port eg. 127.0.0.1:8080
77 - ipv6+port eg. [::1]:8080
77 - ipv6+port eg. [::1]:8080
78
78
79 :param ip:
79 :param ip:
80 """
80 """
81 def is_ipv6(ip_addr):
81 def is_ipv6(ip_addr):
82 if hasattr(socket, 'inet_pton'):
82 if hasattr(socket, 'inet_pton'):
83 try:
83 try:
84 socket.inet_pton(socket.AF_INET6, ip_addr)
84 socket.inet_pton(socket.AF_INET6, ip_addr)
85 except socket.error:
85 except socket.error:
86 return False
86 return False
87 else:
87 else:
88 # fallback to ipaddress
88 # fallback to ipaddress
89 try:
89 try:
90 ipaddress.IPv6Address(safe_unicode(ip_addr))
90 ipaddress.IPv6Address(safe_unicode(ip_addr))
91 except Exception:
91 except Exception:
92 return False
92 return False
93 return True
93 return True
94
94
95 if ':' not in ip: # must be ipv4 pure ip
95 if ':' not in ip: # must be ipv4 pure ip
96 return ip
96 return ip
97
97
98 if '[' in ip and ']' in ip: # ipv6 with port
98 if '[' in ip and ']' in ip: # ipv6 with port
99 return ip.split(']')[0][1:].lower()
99 return ip.split(']')[0][1:].lower()
100
100
101 # must be ipv6 or ipv4 with port
101 # must be ipv6 or ipv4 with port
102 if is_ipv6(ip):
102 if is_ipv6(ip):
103 return ip
103 return ip
104 else:
104 else:
105 ip, _port = ip.split(':')[:2] # means ipv4+port
105 ip, _port = ip.split(':')[:2] # means ipv4+port
106 return ip
106 return ip
107
107
108
108
109 def get_ip_addr(environ):
109 def get_ip_addr(environ):
110 proxy_key = 'HTTP_X_REAL_IP'
110 proxy_key = 'HTTP_X_REAL_IP'
111 proxy_key2 = 'HTTP_X_FORWARDED_FOR'
111 proxy_key2 = 'HTTP_X_FORWARDED_FOR'
112 def_key = 'REMOTE_ADDR'
112 def_key = 'REMOTE_ADDR'
113 _filters = lambda x: _filter_port(_filter_proxy(x))
113 _filters = lambda x: _filter_port(_filter_proxy(x))
114
114
115 ip = environ.get(proxy_key)
115 ip = environ.get(proxy_key)
116 if ip:
116 if ip:
117 return _filters(ip)
117 return _filters(ip)
118
118
119 ip = environ.get(proxy_key2)
119 ip = environ.get(proxy_key2)
120 if ip:
120 if ip:
121 return _filters(ip)
121 return _filters(ip)
122
122
123 ip = environ.get(def_key, '0.0.0.0')
123 ip = environ.get(def_key, '0.0.0.0')
124 return _filters(ip)
124 return _filters(ip)
125
125
126
126
127 def get_server_ip_addr(environ, log_errors=True):
127 def get_server_ip_addr(environ, log_errors=True):
128 hostname = environ.get('SERVER_NAME')
128 hostname = environ.get('SERVER_NAME')
129 try:
129 try:
130 return socket.gethostbyname(hostname)
130 return socket.gethostbyname(hostname)
131 except Exception as e:
131 except Exception as e:
132 if log_errors:
132 if log_errors:
133 # in some cases this lookup is not possible, and we don't want to
133 # in some cases this lookup is not possible, and we don't want to
134 # make it an exception in logs
134 # make it an exception in logs
135 log.exception('Could not retrieve server ip address: %s', e)
135 log.exception('Could not retrieve server ip address: %s', e)
136 return hostname
136 return hostname
137
137
138
138
139 def get_server_port(environ):
139 def get_server_port(environ):
140 return environ.get('SERVER_PORT')
140 return environ.get('SERVER_PORT')
141
141
142
142
143 def get_access_path(environ):
143 def get_access_path(environ):
144 path = environ.get('PATH_INFO')
144 path = environ.get('PATH_INFO')
145 org_req = environ.get('pylons.original_request')
145 org_req = environ.get('pylons.original_request')
146 if org_req:
146 if org_req:
147 path = org_req.environ.get('PATH_INFO')
147 path = org_req.environ.get('PATH_INFO')
148 return path
148 return path
149
149
150
150
151 def get_user_agent(environ):
151 def get_user_agent(environ):
152 return environ.get('HTTP_USER_AGENT')
152 return environ.get('HTTP_USER_AGENT')
153
153
154
154
155 def vcs_operation_context(
155 def vcs_operation_context(
156 environ, repo_name, username, action, scm, check_locking=True,
156 environ, repo_name, username, action, scm, check_locking=True,
157 is_shadow_repo=False, check_branch_perms=False, detect_force_push=False):
157 is_shadow_repo=False, check_branch_perms=False, detect_force_push=False):
158 """
158 """
159 Generate the context for a vcs operation, e.g. push or pull.
159 Generate the context for a vcs operation, e.g. push or pull.
160
160
161 This context is passed over the layers so that hooks triggered by the
161 This context is passed over the layers so that hooks triggered by the
162 vcs operation know details like the user, the user's IP address etc.
162 vcs operation know details like the user, the user's IP address etc.
163
163
164 :param check_locking: Allows to switch of the computation of the locking
164 :param check_locking: Allows to switch of the computation of the locking
165 data. This serves mainly the need of the simplevcs middleware to be
165 data. This serves mainly the need of the simplevcs middleware to be
166 able to disable this for certain operations.
166 able to disable this for certain operations.
167
167
168 """
168 """
169 # Tri-state value: False: unlock, None: nothing, True: lock
169 # Tri-state value: False: unlock, None: nothing, True: lock
170 make_lock = None
170 make_lock = None
171 locked_by = [None, None, None]
171 locked_by = [None, None, None]
172 is_anonymous = username == User.DEFAULT_USER
172 is_anonymous = username == User.DEFAULT_USER
173 user = User.get_by_username(username)
173 user = User.get_by_username(username)
174 if not is_anonymous and check_locking:
174 if not is_anonymous and check_locking:
175 log.debug('Checking locking on repository "%s"', repo_name)
175 log.debug('Checking locking on repository "%s"', repo_name)
176 repo = Repository.get_by_repo_name(repo_name)
176 repo = Repository.get_by_repo_name(repo_name)
177 make_lock, __, locked_by = repo.get_locking_state(
177 make_lock, __, locked_by = repo.get_locking_state(
178 action, user.user_id)
178 action, user.user_id)
179 user_id = user.user_id
179 user_id = user.user_id
180 settings_model = VcsSettingsModel(repo=repo_name)
180 settings_model = VcsSettingsModel(repo=repo_name)
181 ui_settings = settings_model.get_ui_settings()
181 ui_settings = settings_model.get_ui_settings()
182
182
183 # NOTE(marcink): This should be also in sync with
183 # NOTE(marcink): This should be also in sync with
184 # rhodecode/apps/ssh_support/lib/backends/base.py:update_environment scm_data
184 # rhodecode/apps/ssh_support/lib/backends/base.py:update_environment scm_data
185 store = [x for x in ui_settings if x.key == '/']
185 store = [x for x in ui_settings if x.key == '/']
186 repo_store = ''
186 repo_store = ''
187 if store:
187 if store:
188 repo_store = store[0].value
188 repo_store = store[0].value
189
189
190 scm_data = {
190 scm_data = {
191 'ip': get_ip_addr(environ),
191 'ip': get_ip_addr(environ),
192 'username': username,
192 'username': username,
193 'user_id': user_id,
193 'user_id': user_id,
194 'action': action,
194 'action': action,
195 'repository': repo_name,
195 'repository': repo_name,
196 'scm': scm,
196 'scm': scm,
197 'config': rhodecode.CONFIG['__file__'],
197 'config': rhodecode.CONFIG['__file__'],
198 'repo_store': repo_store,
198 'repo_store': repo_store,
199 'make_lock': make_lock,
199 'make_lock': make_lock,
200 'locked_by': locked_by,
200 'locked_by': locked_by,
201 'server_url': utils2.get_server_url(environ),
201 'server_url': utils2.get_server_url(environ),
202 'user_agent': get_user_agent(environ),
202 'user_agent': get_user_agent(environ),
203 'hooks': get_enabled_hook_classes(ui_settings),
203 'hooks': get_enabled_hook_classes(ui_settings),
204 'is_shadow_repo': is_shadow_repo,
204 'is_shadow_repo': is_shadow_repo,
205 'detect_force_push': detect_force_push,
205 'detect_force_push': detect_force_push,
206 'check_branch_perms': check_branch_perms,
206 'check_branch_perms': check_branch_perms,
207 }
207 }
208 return scm_data
208 return scm_data
209
209
210
210
211 class BasicAuth(AuthBasicAuthenticator):
211 class BasicAuth(AuthBasicAuthenticator):
212
212
213 def __init__(self, realm, authfunc, registry, auth_http_code=None,
213 def __init__(self, realm, authfunc, registry, auth_http_code=None,
214 initial_call_detection=False, acl_repo_name=None):
214 initial_call_detection=False, acl_repo_name=None):
215 self.realm = realm
215 self.realm = realm
216 self.initial_call = initial_call_detection
216 self.initial_call = initial_call_detection
217 self.authfunc = authfunc
217 self.authfunc = authfunc
218 self.registry = registry
218 self.registry = registry
219 self.acl_repo_name = acl_repo_name
219 self.acl_repo_name = acl_repo_name
220 self._rc_auth_http_code = auth_http_code
220 self._rc_auth_http_code = auth_http_code
221
221
222 def _get_response_from_code(self, http_code):
222 def _get_response_from_code(self, http_code):
223 try:
223 try:
224 return get_exception(safe_int(http_code))
224 return get_exception(safe_int(http_code))
225 except Exception:
225 except Exception:
226 log.exception('Failed to fetch response for code %s', http_code)
226 log.exception('Failed to fetch response for code %s', http_code)
227 return HTTPForbidden
227 return HTTPForbidden
228
228
229 def get_rc_realm(self):
229 def get_rc_realm(self):
230 return safe_str(self.registry.rhodecode_settings.get('rhodecode_realm'))
230 return safe_str(self.registry.rhodecode_settings.get('rhodecode_realm'))
231
231
232 def build_authentication(self):
232 def build_authentication(self):
233 head = WWW_AUTHENTICATE.tuples('Basic realm="%s"' % self.realm)
233 head = WWW_AUTHENTICATE.tuples('Basic realm="%s"' % self.realm)
234 if self._rc_auth_http_code and not self.initial_call:
234 if self._rc_auth_http_code and not self.initial_call:
235 # return alternative HTTP code if alternative http return code
235 # return alternative HTTP code if alternative http return code
236 # is specified in RhodeCode config, but ONLY if it's not the
236 # is specified in RhodeCode config, but ONLY if it's not the
237 # FIRST call
237 # FIRST call
238 custom_response_klass = self._get_response_from_code(
238 custom_response_klass = self._get_response_from_code(
239 self._rc_auth_http_code)
239 self._rc_auth_http_code)
240 return custom_response_klass(headers=head)
240 return custom_response_klass(headers=head)
241 return HTTPUnauthorized(headers=head)
241 return HTTPUnauthorized(headers=head)
242
242
243 def authenticate(self, environ):
243 def authenticate(self, environ):
244 authorization = AUTHORIZATION(environ)
244 authorization = AUTHORIZATION(environ)
245 if not authorization:
245 if not authorization:
246 return self.build_authentication()
246 return self.build_authentication()
247 (authmeth, auth) = authorization.split(' ', 1)
247 (authmeth, auth) = authorization.split(' ', 1)
248 if 'basic' != authmeth.lower():
248 if 'basic' != authmeth.lower():
249 return self.build_authentication()
249 return self.build_authentication()
250 auth = auth.strip().decode('base64')
250 auth = auth.strip().decode('base64')
251 _parts = auth.split(':', 1)
251 _parts = auth.split(':', 1)
252 if len(_parts) == 2:
252 if len(_parts) == 2:
253 username, password = _parts
253 username, password = _parts
254 auth_data = self.authfunc(
254 auth_data = self.authfunc(
255 username, password, environ, VCS_TYPE,
255 username, password, environ, VCS_TYPE,
256 registry=self.registry, acl_repo_name=self.acl_repo_name)
256 registry=self.registry, acl_repo_name=self.acl_repo_name)
257 if auth_data:
257 if auth_data:
258 return {'username': username, 'auth_data': auth_data}
258 return {'username': username, 'auth_data': auth_data}
259 if username and password:
259 if username and password:
260 # we mark that we actually executed authentication once, at
260 # we mark that we actually executed authentication once, at
261 # that point we can use the alternative auth code
261 # that point we can use the alternative auth code
262 self.initial_call = False
262 self.initial_call = False
263
263
264 return self.build_authentication()
264 return self.build_authentication()
265
265
266 __call__ = authenticate
266 __call__ = authenticate
267
267
268
268
269 def calculate_version_hash(config):
269 def calculate_version_hash(config):
270 return sha1(
270 return sha1(
271 config.get('beaker.session.secret', '') +
271 config.get('beaker.session.secret', '') +
272 rhodecode.__version__)[:8]
272 rhodecode.__version__)[:8]
273
273
274
274
275 def get_current_lang(request):
275 def get_current_lang(request):
276 # NOTE(marcink): remove after pyramid move
276 # NOTE(marcink): remove after pyramid move
277 try:
277 try:
278 return translation.get_lang()[0]
278 return translation.get_lang()[0]
279 except:
279 except:
280 pass
280 pass
281
281
282 return getattr(request, '_LOCALE_', request.locale_name)
282 return getattr(request, '_LOCALE_', request.locale_name)
283
283
284
284
285 def attach_context_attributes(context, request, user_id=None):
285 def attach_context_attributes(context, request, user_id=None):
286 """
286 """
287 Attach variables into template context called `c`.
287 Attach variables into template context called `c`.
288 """
288 """
289 config = request.registry.settings
289 config = request.registry.settings
290
290
291 rc_config = SettingsModel().get_all_settings(cache=True)
291 rc_config = SettingsModel().get_all_settings(cache=True)
292
292 context.rc_config = rc_config
293 context.rhodecode_version = rhodecode.__version__
293 context.rhodecode_version = rhodecode.__version__
294 context.rhodecode_edition = config.get('rhodecode.edition')
294 context.rhodecode_edition = config.get('rhodecode.edition')
295 # unique secret + version does not leak the version but keep consistency
295 # unique secret + version does not leak the version but keep consistency
296 context.rhodecode_version_hash = calculate_version_hash(config)
296 context.rhodecode_version_hash = calculate_version_hash(config)
297
297
298 # Default language set for the incoming request
298 # Default language set for the incoming request
299 context.language = get_current_lang(request)
299 context.language = get_current_lang(request)
300
300
301 # Visual options
301 # Visual options
302 context.visual = AttributeDict({})
302 context.visual = AttributeDict({})
303
303
304 # DB stored Visual Items
304 # DB stored Visual Items
305 context.visual.show_public_icon = str2bool(
305 context.visual.show_public_icon = str2bool(
306 rc_config.get('rhodecode_show_public_icon'))
306 rc_config.get('rhodecode_show_public_icon'))
307 context.visual.show_private_icon = str2bool(
307 context.visual.show_private_icon = str2bool(
308 rc_config.get('rhodecode_show_private_icon'))
308 rc_config.get('rhodecode_show_private_icon'))
309 context.visual.stylify_metatags = str2bool(
309 context.visual.stylify_metatags = str2bool(
310 rc_config.get('rhodecode_stylify_metatags'))
310 rc_config.get('rhodecode_stylify_metatags'))
311 context.visual.dashboard_items = safe_int(
311 context.visual.dashboard_items = safe_int(
312 rc_config.get('rhodecode_dashboard_items', 100))
312 rc_config.get('rhodecode_dashboard_items', 100))
313 context.visual.admin_grid_items = safe_int(
313 context.visual.admin_grid_items = safe_int(
314 rc_config.get('rhodecode_admin_grid_items', 100))
314 rc_config.get('rhodecode_admin_grid_items', 100))
315 context.visual.show_revision_number = str2bool(
315 context.visual.show_revision_number = str2bool(
316 rc_config.get('rhodecode_show_revision_number', True))
316 rc_config.get('rhodecode_show_revision_number', True))
317 context.visual.show_sha_length = safe_int(
317 context.visual.show_sha_length = safe_int(
318 rc_config.get('rhodecode_show_sha_length', 100))
318 rc_config.get('rhodecode_show_sha_length', 100))
319 context.visual.repository_fields = str2bool(
319 context.visual.repository_fields = str2bool(
320 rc_config.get('rhodecode_repository_fields'))
320 rc_config.get('rhodecode_repository_fields'))
321 context.visual.show_version = str2bool(
321 context.visual.show_version = str2bool(
322 rc_config.get('rhodecode_show_version'))
322 rc_config.get('rhodecode_show_version'))
323 context.visual.use_gravatar = str2bool(
323 context.visual.use_gravatar = str2bool(
324 rc_config.get('rhodecode_use_gravatar'))
324 rc_config.get('rhodecode_use_gravatar'))
325 context.visual.gravatar_url = rc_config.get('rhodecode_gravatar_url')
325 context.visual.gravatar_url = rc_config.get('rhodecode_gravatar_url')
326 context.visual.default_renderer = rc_config.get(
326 context.visual.default_renderer = rc_config.get(
327 'rhodecode_markup_renderer', 'rst')
327 'rhodecode_markup_renderer', 'rst')
328 context.visual.comment_types = ChangesetComment.COMMENT_TYPES
328 context.visual.comment_types = ChangesetComment.COMMENT_TYPES
329 context.visual.rhodecode_support_url = \
329 context.visual.rhodecode_support_url = \
330 rc_config.get('rhodecode_support_url') or h.route_url('rhodecode_support')
330 rc_config.get('rhodecode_support_url') or h.route_url('rhodecode_support')
331
331
332 context.visual.affected_files_cut_off = 60
332 context.visual.affected_files_cut_off = 60
333
333
334 context.pre_code = rc_config.get('rhodecode_pre_code')
334 context.pre_code = rc_config.get('rhodecode_pre_code')
335 context.post_code = rc_config.get('rhodecode_post_code')
335 context.post_code = rc_config.get('rhodecode_post_code')
336 context.rhodecode_name = rc_config.get('rhodecode_title')
336 context.rhodecode_name = rc_config.get('rhodecode_title')
337 context.default_encodings = aslist(config.get('default_encoding'), sep=',')
337 context.default_encodings = aslist(config.get('default_encoding'), sep=',')
338 # if we have specified default_encoding in the request, it has more
338 # if we have specified default_encoding in the request, it has more
339 # priority
339 # priority
340 if request.GET.get('default_encoding'):
340 if request.GET.get('default_encoding'):
341 context.default_encodings.insert(0, request.GET.get('default_encoding'))
341 context.default_encodings.insert(0, request.GET.get('default_encoding'))
342 context.clone_uri_tmpl = rc_config.get('rhodecode_clone_uri_tmpl')
342 context.clone_uri_tmpl = rc_config.get('rhodecode_clone_uri_tmpl')
343 context.clone_uri_ssh_tmpl = rc_config.get('rhodecode_clone_uri_ssh_tmpl')
343 context.clone_uri_ssh_tmpl = rc_config.get('rhodecode_clone_uri_ssh_tmpl')
344
344
345 # INI stored
345 # INI stored
346 context.labs_active = str2bool(
346 context.labs_active = str2bool(
347 config.get('labs_settings_active', 'false'))
347 config.get('labs_settings_active', 'false'))
348 context.ssh_enabled = str2bool(
348 context.ssh_enabled = str2bool(
349 config.get('ssh.generate_authorized_keyfile', 'false'))
349 config.get('ssh.generate_authorized_keyfile', 'false'))
350 context.ssh_key_generator_enabled = str2bool(
350 context.ssh_key_generator_enabled = str2bool(
351 config.get('ssh.enable_ui_key_generator', 'true'))
351 config.get('ssh.enable_ui_key_generator', 'true'))
352
352
353 context.visual.allow_repo_location_change = str2bool(
353 context.visual.allow_repo_location_change = str2bool(
354 config.get('allow_repo_location_change', True))
354 config.get('allow_repo_location_change', True))
355 context.visual.allow_custom_hooks_settings = str2bool(
355 context.visual.allow_custom_hooks_settings = str2bool(
356 config.get('allow_custom_hooks_settings', True))
356 config.get('allow_custom_hooks_settings', True))
357 context.debug_style = str2bool(config.get('debug_style', False))
357 context.debug_style = str2bool(config.get('debug_style', False))
358
358
359 context.rhodecode_instanceid = config.get('instance_id')
359 context.rhodecode_instanceid = config.get('instance_id')
360
360
361 context.visual.cut_off_limit_diff = safe_int(
361 context.visual.cut_off_limit_diff = safe_int(
362 config.get('cut_off_limit_diff'))
362 config.get('cut_off_limit_diff'))
363 context.visual.cut_off_limit_file = safe_int(
363 context.visual.cut_off_limit_file = safe_int(
364 config.get('cut_off_limit_file'))
364 config.get('cut_off_limit_file'))
365
365
366 # AppEnlight
366 # AppEnlight
367 context.appenlight_enabled = str2bool(config.get('appenlight', 'false'))
367 context.appenlight_enabled = str2bool(config.get('appenlight', 'false'))
368 context.appenlight_api_public_key = config.get(
368 context.appenlight_api_public_key = config.get(
369 'appenlight.api_public_key', '')
369 'appenlight.api_public_key', '')
370 context.appenlight_server_url = config.get('appenlight.server_url', '')
370 context.appenlight_server_url = config.get('appenlight.server_url', '')
371
371
372 diffmode = {
372 diffmode = {
373 "unified": "unified",
373 "unified": "unified",
374 "sideside": "sideside"
374 "sideside": "sideside"
375 }.get(request.GET.get('diffmode'))
375 }.get(request.GET.get('diffmode'))
376
376
377 is_api = hasattr(request, 'rpc_user')
377 is_api = hasattr(request, 'rpc_user')
378 session_attrs = {
378 session_attrs = {
379 # defaults
379 # defaults
380 "clone_url_format": "http",
380 "clone_url_format": "http",
381 "diffmode": "sideside"
381 "diffmode": "sideside"
382 }
382 }
383
383
384 if not is_api:
384 if not is_api:
385 # don't access pyramid session for API calls
385 # don't access pyramid session for API calls
386 if diffmode and diffmode != request.session.get('rc_user_session_attr.diffmode'):
386 if diffmode and diffmode != request.session.get('rc_user_session_attr.diffmode'):
387 request.session['rc_user_session_attr.diffmode'] = diffmode
387 request.session['rc_user_session_attr.diffmode'] = diffmode
388
388
389 # session settings per user
389 # session settings per user
390
390
391 for k, v in request.session.items():
391 for k, v in request.session.items():
392 pref = 'rc_user_session_attr.'
392 pref = 'rc_user_session_attr.'
393 if k and k.startswith(pref):
393 if k and k.startswith(pref):
394 k = k[len(pref):]
394 k = k[len(pref):]
395 session_attrs[k] = v
395 session_attrs[k] = v
396
396
397 context.user_session_attrs = session_attrs
397 context.user_session_attrs = session_attrs
398
398
399 # JS template context
399 # JS template context
400 context.template_context = {
400 context.template_context = {
401 'repo_name': None,
401 'repo_name': None,
402 'repo_type': None,
402 'repo_type': None,
403 'repo_landing_commit': None,
403 'repo_landing_commit': None,
404 'rhodecode_user': {
404 'rhodecode_user': {
405 'username': None,
405 'username': None,
406 'email': None,
406 'email': None,
407 'notification_status': False
407 'notification_status': False
408 },
408 },
409 'session_attrs': session_attrs,
409 'session_attrs': session_attrs,
410 'visual': {
410 'visual': {
411 'default_renderer': None
411 'default_renderer': None
412 },
412 },
413 'commit_data': {
413 'commit_data': {
414 'commit_id': None
414 'commit_id': None
415 },
415 },
416 'pull_request_data': {'pull_request_id': None},
416 'pull_request_data': {'pull_request_id': None},
417 'timeago': {
417 'timeago': {
418 'refresh_time': 120 * 1000,
418 'refresh_time': 120 * 1000,
419 'cutoff_limit': 1000 * 60 * 60 * 24 * 7
419 'cutoff_limit': 1000 * 60 * 60 * 24 * 7
420 },
420 },
421 'pyramid_dispatch': {
421 'pyramid_dispatch': {
422
422
423 },
423 },
424 'extra': {'plugins': {}}
424 'extra': {'plugins': {}}
425 }
425 }
426 # END CONFIG VARS
426 # END CONFIG VARS
427 if is_api:
427 if is_api:
428 csrf_token = None
428 csrf_token = None
429 else:
429 else:
430 csrf_token = auth.get_csrf_token(session=request.session)
430 csrf_token = auth.get_csrf_token(session=request.session)
431
431
432 context.csrf_token = csrf_token
432 context.csrf_token = csrf_token
433 context.backends = rhodecode.BACKENDS.keys()
433 context.backends = rhodecode.BACKENDS.keys()
434 context.backends.sort()
434 context.backends.sort()
435 unread_count = 0
435 unread_count = 0
436 user_bookmark_list = []
436 user_bookmark_list = []
437 if user_id:
437 if user_id:
438 unread_count = NotificationModel().get_unread_cnt_for_user(user_id)
438 unread_count = NotificationModel().get_unread_cnt_for_user(user_id)
439 user_bookmark_list = UserBookmark.get_bookmarks_for_user(user_id)
439 user_bookmark_list = UserBookmark.get_bookmarks_for_user(user_id)
440 context.unread_notifications = unread_count
440 context.unread_notifications = unread_count
441 context.bookmark_items = user_bookmark_list
441 context.bookmark_items = user_bookmark_list
442
442
443 # web case
443 # web case
444 if hasattr(request, 'user'):
444 if hasattr(request, 'user'):
445 context.auth_user = request.user
445 context.auth_user = request.user
446 context.rhodecode_user = request.user
446 context.rhodecode_user = request.user
447
447
448 # api case
448 # api case
449 if hasattr(request, 'rpc_user'):
449 if hasattr(request, 'rpc_user'):
450 context.auth_user = request.rpc_user
450 context.auth_user = request.rpc_user
451 context.rhodecode_user = request.rpc_user
451 context.rhodecode_user = request.rpc_user
452
452
453 # attach the whole call context to the request
453 # attach the whole call context to the request
454 request.call_context = context
454 request.call_context = context
455
455
456
456
457 def get_auth_user(request):
457 def get_auth_user(request):
458 environ = request.environ
458 environ = request.environ
459 session = request.session
459 session = request.session
460
460
461 ip_addr = get_ip_addr(environ)
461 ip_addr = get_ip_addr(environ)
462 # make sure that we update permissions each time we call controller
462 # make sure that we update permissions each time we call controller
463 _auth_token = (request.GET.get('auth_token', '') or
463 _auth_token = (request.GET.get('auth_token', '') or
464 request.GET.get('api_key', ''))
464 request.GET.get('api_key', ''))
465
465
466 if _auth_token:
466 if _auth_token:
467 # when using API_KEY we assume user exists, and
467 # when using API_KEY we assume user exists, and
468 # doesn't need auth based on cookies.
468 # doesn't need auth based on cookies.
469 auth_user = AuthUser(api_key=_auth_token, ip_addr=ip_addr)
469 auth_user = AuthUser(api_key=_auth_token, ip_addr=ip_addr)
470 authenticated = False
470 authenticated = False
471 else:
471 else:
472 cookie_store = CookieStoreWrapper(session.get('rhodecode_user'))
472 cookie_store = CookieStoreWrapper(session.get('rhodecode_user'))
473 try:
473 try:
474 auth_user = AuthUser(user_id=cookie_store.get('user_id', None),
474 auth_user = AuthUser(user_id=cookie_store.get('user_id', None),
475 ip_addr=ip_addr)
475 ip_addr=ip_addr)
476 except UserCreationError as e:
476 except UserCreationError as e:
477 h.flash(e, 'error')
477 h.flash(e, 'error')
478 # container auth or other auth functions that create users
478 # container auth or other auth functions that create users
479 # on the fly can throw this exception signaling that there's
479 # on the fly can throw this exception signaling that there's
480 # issue with user creation, explanation should be provided
480 # issue with user creation, explanation should be provided
481 # in Exception itself. We then create a simple blank
481 # in Exception itself. We then create a simple blank
482 # AuthUser
482 # AuthUser
483 auth_user = AuthUser(ip_addr=ip_addr)
483 auth_user = AuthUser(ip_addr=ip_addr)
484
484
485 # in case someone changes a password for user it triggers session
485 # in case someone changes a password for user it triggers session
486 # flush and forces a re-login
486 # flush and forces a re-login
487 if password_changed(auth_user, session):
487 if password_changed(auth_user, session):
488 session.invalidate()
488 session.invalidate()
489 cookie_store = CookieStoreWrapper(session.get('rhodecode_user'))
489 cookie_store = CookieStoreWrapper(session.get('rhodecode_user'))
490 auth_user = AuthUser(ip_addr=ip_addr)
490 auth_user = AuthUser(ip_addr=ip_addr)
491
491
492 authenticated = cookie_store.get('is_authenticated')
492 authenticated = cookie_store.get('is_authenticated')
493
493
494 if not auth_user.is_authenticated and auth_user.is_user_object:
494 if not auth_user.is_authenticated and auth_user.is_user_object:
495 # user is not authenticated and not empty
495 # user is not authenticated and not empty
496 auth_user.set_authenticated(authenticated)
496 auth_user.set_authenticated(authenticated)
497
497
498 return auth_user
498 return auth_user
499
499
500
500
501 def h_filter(s):
501 def h_filter(s):
502 """
502 """
503 Custom filter for Mako templates. Mako by standard uses `markupsafe.escape`
503 Custom filter for Mako templates. Mako by standard uses `markupsafe.escape`
504 we wrap this with additional functionality that converts None to empty
504 we wrap this with additional functionality that converts None to empty
505 strings
505 strings
506 """
506 """
507 if s is None:
507 if s is None:
508 return markupsafe.Markup()
508 return markupsafe.Markup()
509 return markupsafe.escape(s)
509 return markupsafe.escape(s)
510
510
511
511
512 def add_events_routes(config):
512 def add_events_routes(config):
513 """
513 """
514 Adds routing that can be used in events. Because some events are triggered
514 Adds routing that can be used in events. Because some events are triggered
515 outside of pyramid context, we need to bootstrap request with some
515 outside of pyramid context, we need to bootstrap request with some
516 routing registered
516 routing registered
517 """
517 """
518
518
519 from rhodecode.apps._base import ADMIN_PREFIX
519 from rhodecode.apps._base import ADMIN_PREFIX
520
520
521 config.add_route(name='home', pattern='/')
521 config.add_route(name='home', pattern='/')
522
522
523 config.add_route(name='login', pattern=ADMIN_PREFIX + '/login')
523 config.add_route(name='login', pattern=ADMIN_PREFIX + '/login')
524 config.add_route(name='logout', pattern=ADMIN_PREFIX + '/logout')
524 config.add_route(name='logout', pattern=ADMIN_PREFIX + '/logout')
525 config.add_route(name='repo_summary', pattern='/{repo_name}')
525 config.add_route(name='repo_summary', pattern='/{repo_name}')
526 config.add_route(name='repo_summary_explicit', pattern='/{repo_name}/summary')
526 config.add_route(name='repo_summary_explicit', pattern='/{repo_name}/summary')
527 config.add_route(name='repo_group_home', pattern='/{repo_group_name}')
527 config.add_route(name='repo_group_home', pattern='/{repo_group_name}')
528
528
529 config.add_route(name='pullrequest_show',
529 config.add_route(name='pullrequest_show',
530 pattern='/{repo_name}/pull-request/{pull_request_id}')
530 pattern='/{repo_name}/pull-request/{pull_request_id}')
531 config.add_route(name='pull_requests_global',
531 config.add_route(name='pull_requests_global',
532 pattern='/pull-request/{pull_request_id}')
532 pattern='/pull-request/{pull_request_id}')
533 config.add_route(name='repo_commit',
533 config.add_route(name='repo_commit',
534 pattern='/{repo_name}/changeset/{commit_id}')
534 pattern='/{repo_name}/changeset/{commit_id}')
535
535
536 config.add_route(name='repo_files',
536 config.add_route(name='repo_files',
537 pattern='/{repo_name}/files/{commit_id}/{f_path}')
537 pattern='/{repo_name}/files/{commit_id}/{f_path}')
538
538
539
539
540 def bootstrap_config(request):
540 def bootstrap_config(request):
541 import pyramid.testing
541 import pyramid.testing
542 registry = pyramid.testing.Registry('RcTestRegistry')
542 registry = pyramid.testing.Registry('RcTestRegistry')
543
543
544 config = pyramid.testing.setUp(registry=registry, request=request)
544 config = pyramid.testing.setUp(registry=registry, request=request)
545
545
546 # allow pyramid lookup in testing
546 # allow pyramid lookup in testing
547 config.include('pyramid_mako')
547 config.include('pyramid_mako')
548 config.include('rhodecode.lib.rc_beaker')
548 config.include('rhodecode.lib.rc_beaker')
549 config.include('rhodecode.lib.rc_cache')
549 config.include('rhodecode.lib.rc_cache')
550
550
551 add_events_routes(config)
551 add_events_routes(config)
552
552
553 return config
553 return config
554
554
555
555
556 def bootstrap_request(**kwargs):
556 def bootstrap_request(**kwargs):
557 import pyramid.testing
557 import pyramid.testing
558
558
559 class TestRequest(pyramid.testing.DummyRequest):
559 class TestRequest(pyramid.testing.DummyRequest):
560 application_url = kwargs.pop('application_url', 'http://example.com')
560 application_url = kwargs.pop('application_url', 'http://example.com')
561 host = kwargs.pop('host', 'example.com:80')
561 host = kwargs.pop('host', 'example.com:80')
562 domain = kwargs.pop('domain', 'example.com')
562 domain = kwargs.pop('domain', 'example.com')
563
563
564 def translate(self, msg):
564 def translate(self, msg):
565 return msg
565 return msg
566
566
567 def plularize(self, singular, plural, n):
567 def plularize(self, singular, plural, n):
568 return singular
568 return singular
569
569
570 def get_partial_renderer(self, tmpl_name):
570 def get_partial_renderer(self, tmpl_name):
571
571
572 from rhodecode.lib.partial_renderer import get_partial_renderer
572 from rhodecode.lib.partial_renderer import get_partial_renderer
573 return get_partial_renderer(request=self, tmpl_name=tmpl_name)
573 return get_partial_renderer(request=self, tmpl_name=tmpl_name)
574
574
575 _call_context = TemplateArgs()
575 _call_context = TemplateArgs()
576 _call_context.visual = TemplateArgs()
576 _call_context.visual = TemplateArgs()
577 _call_context.visual.show_sha_length = 12
577 _call_context.visual.show_sha_length = 12
578 _call_context.visual.show_revision_number = True
578 _call_context.visual.show_revision_number = True
579
579
580 @property
580 @property
581 def call_context(self):
581 def call_context(self):
582 return self._call_context
582 return self._call_context
583
583
584 class TestDummySession(pyramid.testing.DummySession):
584 class TestDummySession(pyramid.testing.DummySession):
585 def save(*arg, **kw):
585 def save(*arg, **kw):
586 pass
586 pass
587
587
588 request = TestRequest(**kwargs)
588 request = TestRequest(**kwargs)
589 request.session = TestDummySession()
589 request.session = TestDummySession()
590
590
591 return request
591 return request
592
592
@@ -1,5185 +1,5188 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)
43 Text, Float, PickleType)
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.model.meta import Base, Session
70 from rhodecode.model.meta import Base, Session
71
71
72 URL_SEP = '/'
72 URL_SEP = '/'
73 log = logging.getLogger(__name__)
73 log = logging.getLogger(__name__)
74
74
75 # =============================================================================
75 # =============================================================================
76 # BASE CLASSES
76 # BASE CLASSES
77 # =============================================================================
77 # =============================================================================
78
78
79 # this is propagated from .ini file rhodecode.encrypted_values.secret or
79 # this is propagated from .ini file rhodecode.encrypted_values.secret or
80 # beaker.session.secret if first is not set.
80 # beaker.session.secret if first is not set.
81 # and initialized at environment.py
81 # and initialized at environment.py
82 ENCRYPTION_KEY = None
82 ENCRYPTION_KEY = None
83
83
84 # used to sort permissions by types, '#' used here is not allowed to be in
84 # used to sort permissions by types, '#' used here is not allowed to be in
85 # usernames, and it's very early in sorted string.printable table.
85 # usernames, and it's very early in sorted string.printable table.
86 PERMISSION_TYPE_SORT = {
86 PERMISSION_TYPE_SORT = {
87 'admin': '####',
87 'admin': '####',
88 'write': '###',
88 'write': '###',
89 'read': '##',
89 'read': '##',
90 'none': '#',
90 'none': '#',
91 }
91 }
92
92
93
93
94 def display_user_sort(obj):
94 def display_user_sort(obj):
95 """
95 """
96 Sort function used to sort permissions in .permissions() function of
96 Sort function used to sort permissions in .permissions() function of
97 Repository, RepoGroup, UserGroup. Also it put the default user in front
97 Repository, RepoGroup, UserGroup. Also it put the default user in front
98 of all other resources
98 of all other resources
99 """
99 """
100
100
101 if obj.username == User.DEFAULT_USER:
101 if obj.username == User.DEFAULT_USER:
102 return '#####'
102 return '#####'
103 prefix = PERMISSION_TYPE_SORT.get(obj.permission.split('.')[-1], '')
103 prefix = PERMISSION_TYPE_SORT.get(obj.permission.split('.')[-1], '')
104 return prefix + obj.username
104 return prefix + obj.username
105
105
106
106
107 def display_user_group_sort(obj):
107 def display_user_group_sort(obj):
108 """
108 """
109 Sort function used to sort permissions in .permissions() function of
109 Sort function used to sort permissions in .permissions() function of
110 Repository, RepoGroup, UserGroup. Also it put the default user in front
110 Repository, RepoGroup, UserGroup. Also it put the default user in front
111 of all other resources
111 of all other resources
112 """
112 """
113
113
114 prefix = PERMISSION_TYPE_SORT.get(obj.permission.split('.')[-1], '')
114 prefix = PERMISSION_TYPE_SORT.get(obj.permission.split('.')[-1], '')
115 return prefix + obj.users_group_name
115 return prefix + obj.users_group_name
116
116
117
117
118 def _hash_key(k):
118 def _hash_key(k):
119 return sha1_safe(k)
119 return sha1_safe(k)
120
120
121
121
122 def in_filter_generator(qry, items, limit=500):
122 def in_filter_generator(qry, items, limit=500):
123 """
123 """
124 Splits IN() into multiple with OR
124 Splits IN() into multiple with OR
125 e.g.::
125 e.g.::
126 cnt = Repository.query().filter(
126 cnt = Repository.query().filter(
127 or_(
127 or_(
128 *in_filter_generator(Repository.repo_id, range(100000))
128 *in_filter_generator(Repository.repo_id, range(100000))
129 )).count()
129 )).count()
130 """
130 """
131 if not items:
131 if not items:
132 # empty list will cause empty query which might cause security issues
132 # empty list will cause empty query which might cause security issues
133 # this can lead to hidden unpleasant results
133 # this can lead to hidden unpleasant results
134 items = [-1]
134 items = [-1]
135
135
136 parts = []
136 parts = []
137 for chunk in xrange(0, len(items), limit):
137 for chunk in xrange(0, len(items), limit):
138 parts.append(
138 parts.append(
139 qry.in_(items[chunk: chunk + limit])
139 qry.in_(items[chunk: chunk + limit])
140 )
140 )
141
141
142 return parts
142 return parts
143
143
144
144
145 base_table_args = {
145 base_table_args = {
146 'extend_existing': True,
146 'extend_existing': True,
147 'mysql_engine': 'InnoDB',
147 'mysql_engine': 'InnoDB',
148 'mysql_charset': 'utf8',
148 'mysql_charset': 'utf8',
149 'sqlite_autoincrement': True
149 'sqlite_autoincrement': True
150 }
150 }
151
151
152
152
153 class EncryptedTextValue(TypeDecorator):
153 class EncryptedTextValue(TypeDecorator):
154 """
154 """
155 Special column for encrypted long text data, use like::
155 Special column for encrypted long text data, use like::
156
156
157 value = Column("encrypted_value", EncryptedValue(), nullable=False)
157 value = Column("encrypted_value", EncryptedValue(), nullable=False)
158
158
159 This column is intelligent so if value is in unencrypted form it return
159 This column is intelligent so if value is in unencrypted form it return
160 unencrypted form, but on save it always encrypts
160 unencrypted form, but on save it always encrypts
161 """
161 """
162 impl = Text
162 impl = Text
163
163
164 def process_bind_param(self, value, dialect):
164 def process_bind_param(self, value, dialect):
165 """
165 """
166 Setter for storing value
166 Setter for storing value
167 """
167 """
168 import rhodecode
168 import rhodecode
169 if not value:
169 if not value:
170 return value
170 return value
171
171
172 # protect against double encrypting if values is already encrypted
172 # protect against double encrypting if values is already encrypted
173 if value.startswith('enc$aes$') \
173 if value.startswith('enc$aes$') \
174 or value.startswith('enc$aes_hmac$') \
174 or value.startswith('enc$aes_hmac$') \
175 or value.startswith('enc2$'):
175 or value.startswith('enc2$'):
176 raise ValueError('value needs to be in unencrypted format, '
176 raise ValueError('value needs to be in unencrypted format, '
177 'ie. not starting with enc$ or enc2$')
177 'ie. not starting with enc$ or enc2$')
178
178
179 algo = rhodecode.CONFIG.get('rhodecode.encrypted_values.algorithm') or 'aes'
179 algo = rhodecode.CONFIG.get('rhodecode.encrypted_values.algorithm') or 'aes'
180 if algo == 'aes':
180 if algo == 'aes':
181 return 'enc$aes_hmac$%s' % AESCipher(ENCRYPTION_KEY, hmac=True).encrypt(value)
181 return 'enc$aes_hmac$%s' % AESCipher(ENCRYPTION_KEY, hmac=True).encrypt(value)
182 elif algo == 'fernet':
182 elif algo == 'fernet':
183 return Encryptor(ENCRYPTION_KEY).encrypt(value)
183 return Encryptor(ENCRYPTION_KEY).encrypt(value)
184 else:
184 else:
185 ValueError('Bad encryption algorithm, should be fernet or aes, got: {}'.format(algo))
185 ValueError('Bad encryption algorithm, should be fernet or aes, got: {}'.format(algo))
186
186
187 def process_result_value(self, value, dialect):
187 def process_result_value(self, value, dialect):
188 """
188 """
189 Getter for retrieving value
189 Getter for retrieving value
190 """
190 """
191
191
192 import rhodecode
192 import rhodecode
193 if not value:
193 if not value:
194 return value
194 return value
195
195
196 algo = rhodecode.CONFIG.get('rhodecode.encrypted_values.algorithm') or 'aes'
196 algo = rhodecode.CONFIG.get('rhodecode.encrypted_values.algorithm') or 'aes'
197 enc_strict_mode = str2bool(rhodecode.CONFIG.get('rhodecode.encrypted_values.strict') or True)
197 enc_strict_mode = str2bool(rhodecode.CONFIG.get('rhodecode.encrypted_values.strict') or True)
198 if algo == 'aes':
198 if algo == 'aes':
199 decrypted_data = validate_and_get_enc_data(value, ENCRYPTION_KEY, enc_strict_mode)
199 decrypted_data = validate_and_get_enc_data(value, ENCRYPTION_KEY, enc_strict_mode)
200 elif algo == 'fernet':
200 elif algo == 'fernet':
201 return Encryptor(ENCRYPTION_KEY).decrypt(value)
201 return Encryptor(ENCRYPTION_KEY).decrypt(value)
202 else:
202 else:
203 ValueError('Bad encryption algorithm, should be fernet or aes, got: {}'.format(algo))
203 ValueError('Bad encryption algorithm, should be fernet or aes, got: {}'.format(algo))
204 return decrypted_data
204 return decrypted_data
205
205
206
206
207 class BaseModel(object):
207 class BaseModel(object):
208 """
208 """
209 Base Model for all classes
209 Base Model for all classes
210 """
210 """
211
211
212 @classmethod
212 @classmethod
213 def _get_keys(cls):
213 def _get_keys(cls):
214 """return column names for this model """
214 """return column names for this model """
215 return class_mapper(cls).c.keys()
215 return class_mapper(cls).c.keys()
216
216
217 def get_dict(self):
217 def get_dict(self):
218 """
218 """
219 return dict with keys and values corresponding
219 return dict with keys and values corresponding
220 to this model data """
220 to this model data """
221
221
222 d = {}
222 d = {}
223 for k in self._get_keys():
223 for k in self._get_keys():
224 d[k] = getattr(self, k)
224 d[k] = getattr(self, k)
225
225
226 # also use __json__() if present to get additional fields
226 # also use __json__() if present to get additional fields
227 _json_attr = getattr(self, '__json__', None)
227 _json_attr = getattr(self, '__json__', None)
228 if _json_attr:
228 if _json_attr:
229 # update with attributes from __json__
229 # update with attributes from __json__
230 if callable(_json_attr):
230 if callable(_json_attr):
231 _json_attr = _json_attr()
231 _json_attr = _json_attr()
232 for k, val in _json_attr.iteritems():
232 for k, val in _json_attr.iteritems():
233 d[k] = val
233 d[k] = val
234 return d
234 return d
235
235
236 def get_appstruct(self):
236 def get_appstruct(self):
237 """return list with keys and values tuples corresponding
237 """return list with keys and values tuples corresponding
238 to this model data """
238 to this model data """
239
239
240 lst = []
240 lst = []
241 for k in self._get_keys():
241 for k in self._get_keys():
242 lst.append((k, getattr(self, k),))
242 lst.append((k, getattr(self, k),))
243 return lst
243 return lst
244
244
245 def populate_obj(self, populate_dict):
245 def populate_obj(self, populate_dict):
246 """populate model with data from given populate_dict"""
246 """populate model with data from given populate_dict"""
247
247
248 for k in self._get_keys():
248 for k in self._get_keys():
249 if k in populate_dict:
249 if k in populate_dict:
250 setattr(self, k, populate_dict[k])
250 setattr(self, k, populate_dict[k])
251
251
252 @classmethod
252 @classmethod
253 def query(cls):
253 def query(cls):
254 return Session().query(cls)
254 return Session().query(cls)
255
255
256 @classmethod
256 @classmethod
257 def get(cls, id_):
257 def get(cls, id_):
258 if id_:
258 if id_:
259 return cls.query().get(id_)
259 return cls.query().get(id_)
260
260
261 @classmethod
261 @classmethod
262 def get_or_404(cls, id_):
262 def get_or_404(cls, id_):
263 from pyramid.httpexceptions import HTTPNotFound
263 from pyramid.httpexceptions import HTTPNotFound
264
264
265 try:
265 try:
266 id_ = int(id_)
266 id_ = int(id_)
267 except (TypeError, ValueError):
267 except (TypeError, ValueError):
268 raise HTTPNotFound()
268 raise HTTPNotFound()
269
269
270 res = cls.query().get(id_)
270 res = cls.query().get(id_)
271 if not res:
271 if not res:
272 raise HTTPNotFound()
272 raise HTTPNotFound()
273 return res
273 return res
274
274
275 @classmethod
275 @classmethod
276 def getAll(cls):
276 def getAll(cls):
277 # deprecated and left for backward compatibility
277 # deprecated and left for backward compatibility
278 return cls.get_all()
278 return cls.get_all()
279
279
280 @classmethod
280 @classmethod
281 def get_all(cls):
281 def get_all(cls):
282 return cls.query().all()
282 return cls.query().all()
283
283
284 @classmethod
284 @classmethod
285 def delete(cls, id_):
285 def delete(cls, id_):
286 obj = cls.query().get(id_)
286 obj = cls.query().get(id_)
287 Session().delete(obj)
287 Session().delete(obj)
288
288
289 @classmethod
289 @classmethod
290 def identity_cache(cls, session, attr_name, value):
290 def identity_cache(cls, session, attr_name, value):
291 exist_in_session = []
291 exist_in_session = []
292 for (item_cls, pkey), instance in session.identity_map.items():
292 for (item_cls, pkey), instance in session.identity_map.items():
293 if cls == item_cls and getattr(instance, attr_name) == value:
293 if cls == item_cls and getattr(instance, attr_name) == value:
294 exist_in_session.append(instance)
294 exist_in_session.append(instance)
295 if exist_in_session:
295 if exist_in_session:
296 if len(exist_in_session) == 1:
296 if len(exist_in_session) == 1:
297 return exist_in_session[0]
297 return exist_in_session[0]
298 log.exception(
298 log.exception(
299 'multiple objects with attr %s and '
299 'multiple objects with attr %s and '
300 'value %s found with same name: %r',
300 'value %s found with same name: %r',
301 attr_name, value, exist_in_session)
301 attr_name, value, exist_in_session)
302
302
303 def __repr__(self):
303 def __repr__(self):
304 if hasattr(self, '__unicode__'):
304 if hasattr(self, '__unicode__'):
305 # python repr needs to return str
305 # python repr needs to return str
306 try:
306 try:
307 return safe_str(self.__unicode__())
307 return safe_str(self.__unicode__())
308 except UnicodeDecodeError:
308 except UnicodeDecodeError:
309 pass
309 pass
310 return '<DB:%s>' % (self.__class__.__name__)
310 return '<DB:%s>' % (self.__class__.__name__)
311
311
312
312
313 class RhodeCodeSetting(Base, BaseModel):
313 class RhodeCodeSetting(Base, BaseModel):
314 __tablename__ = 'rhodecode_settings'
314 __tablename__ = 'rhodecode_settings'
315 __table_args__ = (
315 __table_args__ = (
316 UniqueConstraint('app_settings_name'),
316 UniqueConstraint('app_settings_name'),
317 base_table_args
317 base_table_args
318 )
318 )
319
319
320 SETTINGS_TYPES = {
320 SETTINGS_TYPES = {
321 'str': safe_str,
321 'str': safe_str,
322 'int': safe_int,
322 'int': safe_int,
323 'unicode': safe_unicode,
323 'unicode': safe_unicode,
324 'bool': str2bool,
324 'bool': str2bool,
325 'list': functools.partial(aslist, sep=',')
325 'list': functools.partial(aslist, sep=',')
326 }
326 }
327 DEFAULT_UPDATE_URL = 'https://rhodecode.com/api/v1/info/versions'
327 DEFAULT_UPDATE_URL = 'https://rhodecode.com/api/v1/info/versions'
328 GLOBAL_CONF_KEY = 'app_settings'
328 GLOBAL_CONF_KEY = 'app_settings'
329
329
330 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
330 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
331 app_settings_name = Column("app_settings_name", String(255), nullable=True, unique=None, default=None)
331 app_settings_name = Column("app_settings_name", String(255), nullable=True, unique=None, default=None)
332 _app_settings_value = Column("app_settings_value", String(4096), nullable=True, unique=None, default=None)
332 _app_settings_value = Column("app_settings_value", String(4096), nullable=True, unique=None, default=None)
333 _app_settings_type = Column("app_settings_type", String(255), nullable=True, unique=None, default=None)
333 _app_settings_type = Column("app_settings_type", String(255), nullable=True, unique=None, default=None)
334
334
335 def __init__(self, key='', val='', type='unicode'):
335 def __init__(self, key='', val='', type='unicode'):
336 self.app_settings_name = key
336 self.app_settings_name = key
337 self.app_settings_type = type
337 self.app_settings_type = type
338 self.app_settings_value = val
338 self.app_settings_value = val
339
339
340 @validates('_app_settings_value')
340 @validates('_app_settings_value')
341 def validate_settings_value(self, key, val):
341 def validate_settings_value(self, key, val):
342 assert type(val) == unicode
342 assert type(val) == unicode
343 return val
343 return val
344
344
345 @hybrid_property
345 @hybrid_property
346 def app_settings_value(self):
346 def app_settings_value(self):
347 v = self._app_settings_value
347 v = self._app_settings_value
348 _type = self.app_settings_type
348 _type = self.app_settings_type
349 if _type:
349 if _type:
350 _type = self.app_settings_type.split('.')[0]
350 _type = self.app_settings_type.split('.')[0]
351 # decode the encrypted value
351 # decode the encrypted value
352 if 'encrypted' in self.app_settings_type:
352 if 'encrypted' in self.app_settings_type:
353 cipher = EncryptedTextValue()
353 cipher = EncryptedTextValue()
354 v = safe_unicode(cipher.process_result_value(v, None))
354 v = safe_unicode(cipher.process_result_value(v, None))
355
355
356 converter = self.SETTINGS_TYPES.get(_type) or \
356 converter = self.SETTINGS_TYPES.get(_type) or \
357 self.SETTINGS_TYPES['unicode']
357 self.SETTINGS_TYPES['unicode']
358 return converter(v)
358 return converter(v)
359
359
360 @app_settings_value.setter
360 @app_settings_value.setter
361 def app_settings_value(self, val):
361 def app_settings_value(self, val):
362 """
362 """
363 Setter that will always make sure we use unicode in app_settings_value
363 Setter that will always make sure we use unicode in app_settings_value
364
364
365 :param val:
365 :param val:
366 """
366 """
367 val = safe_unicode(val)
367 val = safe_unicode(val)
368 # encode the encrypted value
368 # encode the encrypted value
369 if 'encrypted' in self.app_settings_type:
369 if 'encrypted' in self.app_settings_type:
370 cipher = EncryptedTextValue()
370 cipher = EncryptedTextValue()
371 val = safe_unicode(cipher.process_bind_param(val, None))
371 val = safe_unicode(cipher.process_bind_param(val, None))
372 self._app_settings_value = val
372 self._app_settings_value = val
373
373
374 @hybrid_property
374 @hybrid_property
375 def app_settings_type(self):
375 def app_settings_type(self):
376 return self._app_settings_type
376 return self._app_settings_type
377
377
378 @app_settings_type.setter
378 @app_settings_type.setter
379 def app_settings_type(self, val):
379 def app_settings_type(self, val):
380 if val.split('.')[0] not in self.SETTINGS_TYPES:
380 if val.split('.')[0] not in self.SETTINGS_TYPES:
381 raise Exception('type must be one of %s got %s'
381 raise Exception('type must be one of %s got %s'
382 % (self.SETTINGS_TYPES.keys(), val))
382 % (self.SETTINGS_TYPES.keys(), val))
383 self._app_settings_type = val
383 self._app_settings_type = val
384
384
385 @classmethod
385 @classmethod
386 def get_by_prefix(cls, prefix):
386 def get_by_prefix(cls, prefix):
387 return RhodeCodeSetting.query()\
387 return RhodeCodeSetting.query()\
388 .filter(RhodeCodeSetting.app_settings_name.startswith(prefix))\
388 .filter(RhodeCodeSetting.app_settings_name.startswith(prefix))\
389 .all()
389 .all()
390
390
391 def __unicode__(self):
391 def __unicode__(self):
392 return u"<%s('%s:%s[%s]')>" % (
392 return u"<%s('%s:%s[%s]')>" % (
393 self.__class__.__name__,
393 self.__class__.__name__,
394 self.app_settings_name, self.app_settings_value,
394 self.app_settings_name, self.app_settings_value,
395 self.app_settings_type
395 self.app_settings_type
396 )
396 )
397
397
398
398
399 class RhodeCodeUi(Base, BaseModel):
399 class RhodeCodeUi(Base, BaseModel):
400 __tablename__ = 'rhodecode_ui'
400 __tablename__ = 'rhodecode_ui'
401 __table_args__ = (
401 __table_args__ = (
402 UniqueConstraint('ui_key'),
402 UniqueConstraint('ui_key'),
403 base_table_args
403 base_table_args
404 )
404 )
405
405
406 HOOK_REPO_SIZE = 'changegroup.repo_size'
406 HOOK_REPO_SIZE = 'changegroup.repo_size'
407 # HG
407 # HG
408 HOOK_PRE_PULL = 'preoutgoing.pre_pull'
408 HOOK_PRE_PULL = 'preoutgoing.pre_pull'
409 HOOK_PULL = 'outgoing.pull_logger'
409 HOOK_PULL = 'outgoing.pull_logger'
410 HOOK_PRE_PUSH = 'prechangegroup.pre_push'
410 HOOK_PRE_PUSH = 'prechangegroup.pre_push'
411 HOOK_PRETX_PUSH = 'pretxnchangegroup.pre_push'
411 HOOK_PRETX_PUSH = 'pretxnchangegroup.pre_push'
412 HOOK_PUSH = 'changegroup.push_logger'
412 HOOK_PUSH = 'changegroup.push_logger'
413 HOOK_PUSH_KEY = 'pushkey.key_push'
413 HOOK_PUSH_KEY = 'pushkey.key_push'
414
414
415 HOOKS_BUILTIN = [
415 HOOKS_BUILTIN = [
416 HOOK_PRE_PULL,
416 HOOK_PRE_PULL,
417 HOOK_PULL,
417 HOOK_PULL,
418 HOOK_PRE_PUSH,
418 HOOK_PRE_PUSH,
419 HOOK_PRETX_PUSH,
419 HOOK_PRETX_PUSH,
420 HOOK_PUSH,
420 HOOK_PUSH,
421 HOOK_PUSH_KEY,
421 HOOK_PUSH_KEY,
422 ]
422 ]
423
423
424 # TODO: johbo: Unify way how hooks are configured for git and hg,
424 # TODO: johbo: Unify way how hooks are configured for git and hg,
425 # git part is currently hardcoded.
425 # git part is currently hardcoded.
426
426
427 # SVN PATTERNS
427 # SVN PATTERNS
428 SVN_BRANCH_ID = 'vcs_svn_branch'
428 SVN_BRANCH_ID = 'vcs_svn_branch'
429 SVN_TAG_ID = 'vcs_svn_tag'
429 SVN_TAG_ID = 'vcs_svn_tag'
430
430
431 ui_id = Column(
431 ui_id = Column(
432 "ui_id", Integer(), nullable=False, unique=True, default=None,
432 "ui_id", Integer(), nullable=False, unique=True, default=None,
433 primary_key=True)
433 primary_key=True)
434 ui_section = Column(
434 ui_section = Column(
435 "ui_section", String(255), nullable=True, unique=None, default=None)
435 "ui_section", String(255), nullable=True, unique=None, default=None)
436 ui_key = Column(
436 ui_key = Column(
437 "ui_key", String(255), nullable=True, unique=None, default=None)
437 "ui_key", String(255), nullable=True, unique=None, default=None)
438 ui_value = Column(
438 ui_value = Column(
439 "ui_value", String(255), nullable=True, unique=None, default=None)
439 "ui_value", String(255), nullable=True, unique=None, default=None)
440 ui_active = Column(
440 ui_active = Column(
441 "ui_active", Boolean(), nullable=True, unique=None, default=True)
441 "ui_active", Boolean(), nullable=True, unique=None, default=True)
442
442
443 def __repr__(self):
443 def __repr__(self):
444 return '<%s[%s]%s=>%s]>' % (self.__class__.__name__, self.ui_section,
444 return '<%s[%s]%s=>%s]>' % (self.__class__.__name__, self.ui_section,
445 self.ui_key, self.ui_value)
445 self.ui_key, self.ui_value)
446
446
447
447
448 class RepoRhodeCodeSetting(Base, BaseModel):
448 class RepoRhodeCodeSetting(Base, BaseModel):
449 __tablename__ = 'repo_rhodecode_settings'
449 __tablename__ = 'repo_rhodecode_settings'
450 __table_args__ = (
450 __table_args__ = (
451 UniqueConstraint(
451 UniqueConstraint(
452 'app_settings_name', 'repository_id',
452 'app_settings_name', 'repository_id',
453 name='uq_repo_rhodecode_setting_name_repo_id'),
453 name='uq_repo_rhodecode_setting_name_repo_id'),
454 base_table_args
454 base_table_args
455 )
455 )
456
456
457 repository_id = Column(
457 repository_id = Column(
458 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
458 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
459 nullable=False)
459 nullable=False)
460 app_settings_id = Column(
460 app_settings_id = Column(
461 "app_settings_id", Integer(), nullable=False, unique=True,
461 "app_settings_id", Integer(), nullable=False, unique=True,
462 default=None, primary_key=True)
462 default=None, primary_key=True)
463 app_settings_name = Column(
463 app_settings_name = Column(
464 "app_settings_name", String(255), nullable=True, unique=None,
464 "app_settings_name", String(255), nullable=True, unique=None,
465 default=None)
465 default=None)
466 _app_settings_value = Column(
466 _app_settings_value = Column(
467 "app_settings_value", String(4096), nullable=True, unique=None,
467 "app_settings_value", String(4096), nullable=True, unique=None,
468 default=None)
468 default=None)
469 _app_settings_type = Column(
469 _app_settings_type = Column(
470 "app_settings_type", String(255), nullable=True, unique=None,
470 "app_settings_type", String(255), nullable=True, unique=None,
471 default=None)
471 default=None)
472
472
473 repository = relationship('Repository')
473 repository = relationship('Repository')
474
474
475 def __init__(self, repository_id, key='', val='', type='unicode'):
475 def __init__(self, repository_id, key='', val='', type='unicode'):
476 self.repository_id = repository_id
476 self.repository_id = repository_id
477 self.app_settings_name = key
477 self.app_settings_name = key
478 self.app_settings_type = type
478 self.app_settings_type = type
479 self.app_settings_value = val
479 self.app_settings_value = val
480
480
481 @validates('_app_settings_value')
481 @validates('_app_settings_value')
482 def validate_settings_value(self, key, val):
482 def validate_settings_value(self, key, val):
483 assert type(val) == unicode
483 assert type(val) == unicode
484 return val
484 return val
485
485
486 @hybrid_property
486 @hybrid_property
487 def app_settings_value(self):
487 def app_settings_value(self):
488 v = self._app_settings_value
488 v = self._app_settings_value
489 type_ = self.app_settings_type
489 type_ = self.app_settings_type
490 SETTINGS_TYPES = RhodeCodeSetting.SETTINGS_TYPES
490 SETTINGS_TYPES = RhodeCodeSetting.SETTINGS_TYPES
491 converter = SETTINGS_TYPES.get(type_) or SETTINGS_TYPES['unicode']
491 converter = SETTINGS_TYPES.get(type_) or SETTINGS_TYPES['unicode']
492 return converter(v)
492 return converter(v)
493
493
494 @app_settings_value.setter
494 @app_settings_value.setter
495 def app_settings_value(self, val):
495 def app_settings_value(self, val):
496 """
496 """
497 Setter that will always make sure we use unicode in app_settings_value
497 Setter that will always make sure we use unicode in app_settings_value
498
498
499 :param val:
499 :param val:
500 """
500 """
501 self._app_settings_value = safe_unicode(val)
501 self._app_settings_value = safe_unicode(val)
502
502
503 @hybrid_property
503 @hybrid_property
504 def app_settings_type(self):
504 def app_settings_type(self):
505 return self._app_settings_type
505 return self._app_settings_type
506
506
507 @app_settings_type.setter
507 @app_settings_type.setter
508 def app_settings_type(self, val):
508 def app_settings_type(self, val):
509 SETTINGS_TYPES = RhodeCodeSetting.SETTINGS_TYPES
509 SETTINGS_TYPES = RhodeCodeSetting.SETTINGS_TYPES
510 if val not in SETTINGS_TYPES:
510 if val not in SETTINGS_TYPES:
511 raise Exception('type must be one of %s got %s'
511 raise Exception('type must be one of %s got %s'
512 % (SETTINGS_TYPES.keys(), val))
512 % (SETTINGS_TYPES.keys(), val))
513 self._app_settings_type = val
513 self._app_settings_type = val
514
514
515 def __unicode__(self):
515 def __unicode__(self):
516 return u"<%s('%s:%s:%s[%s]')>" % (
516 return u"<%s('%s:%s:%s[%s]')>" % (
517 self.__class__.__name__, self.repository.repo_name,
517 self.__class__.__name__, self.repository.repo_name,
518 self.app_settings_name, self.app_settings_value,
518 self.app_settings_name, self.app_settings_value,
519 self.app_settings_type
519 self.app_settings_type
520 )
520 )
521
521
522
522
523 class RepoRhodeCodeUi(Base, BaseModel):
523 class RepoRhodeCodeUi(Base, BaseModel):
524 __tablename__ = 'repo_rhodecode_ui'
524 __tablename__ = 'repo_rhodecode_ui'
525 __table_args__ = (
525 __table_args__ = (
526 UniqueConstraint(
526 UniqueConstraint(
527 'repository_id', 'ui_section', 'ui_key',
527 'repository_id', 'ui_section', 'ui_key',
528 name='uq_repo_rhodecode_ui_repository_id_section_key'),
528 name='uq_repo_rhodecode_ui_repository_id_section_key'),
529 base_table_args
529 base_table_args
530 )
530 )
531
531
532 repository_id = Column(
532 repository_id = Column(
533 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
533 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
534 nullable=False)
534 nullable=False)
535 ui_id = Column(
535 ui_id = Column(
536 "ui_id", Integer(), nullable=False, unique=True, default=None,
536 "ui_id", Integer(), nullable=False, unique=True, default=None,
537 primary_key=True)
537 primary_key=True)
538 ui_section = Column(
538 ui_section = Column(
539 "ui_section", String(255), nullable=True, unique=None, default=None)
539 "ui_section", String(255), nullable=True, unique=None, default=None)
540 ui_key = Column(
540 ui_key = Column(
541 "ui_key", String(255), nullable=True, unique=None, default=None)
541 "ui_key", String(255), nullable=True, unique=None, default=None)
542 ui_value = Column(
542 ui_value = Column(
543 "ui_value", String(255), nullable=True, unique=None, default=None)
543 "ui_value", String(255), nullable=True, unique=None, default=None)
544 ui_active = Column(
544 ui_active = Column(
545 "ui_active", Boolean(), nullable=True, unique=None, default=True)
545 "ui_active", Boolean(), nullable=True, unique=None, default=True)
546
546
547 repository = relationship('Repository')
547 repository = relationship('Repository')
548
548
549 def __repr__(self):
549 def __repr__(self):
550 return '<%s[%s:%s]%s=>%s]>' % (
550 return '<%s[%s:%s]%s=>%s]>' % (
551 self.__class__.__name__, self.repository.repo_name,
551 self.__class__.__name__, self.repository.repo_name,
552 self.ui_section, self.ui_key, self.ui_value)
552 self.ui_section, self.ui_key, self.ui_value)
553
553
554
554
555 class User(Base, BaseModel):
555 class User(Base, BaseModel):
556 __tablename__ = 'users'
556 __tablename__ = 'users'
557 __table_args__ = (
557 __table_args__ = (
558 UniqueConstraint('username'), UniqueConstraint('email'),
558 UniqueConstraint('username'), UniqueConstraint('email'),
559 Index('u_username_idx', 'username'),
559 Index('u_username_idx', 'username'),
560 Index('u_email_idx', 'email'),
560 Index('u_email_idx', 'email'),
561 base_table_args
561 base_table_args
562 )
562 )
563
563
564 DEFAULT_USER = 'default'
564 DEFAULT_USER = 'default'
565 DEFAULT_USER_EMAIL = 'anonymous@rhodecode.org'
565 DEFAULT_USER_EMAIL = 'anonymous@rhodecode.org'
566 DEFAULT_GRAVATAR_URL = 'https://secure.gravatar.com/avatar/{md5email}?d=identicon&s={size}'
566 DEFAULT_GRAVATAR_URL = 'https://secure.gravatar.com/avatar/{md5email}?d=identicon&s={size}'
567
567
568 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
568 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
569 username = Column("username", String(255), nullable=True, unique=None, default=None)
569 username = Column("username", String(255), nullable=True, unique=None, default=None)
570 password = Column("password", String(255), nullable=True, unique=None, default=None)
570 password = Column("password", String(255), nullable=True, unique=None, default=None)
571 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
571 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
572 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
572 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
573 name = Column("firstname", String(255), nullable=True, unique=None, default=None)
573 name = Column("firstname", String(255), nullable=True, unique=None, default=None)
574 lastname = Column("lastname", String(255), nullable=True, unique=None, default=None)
574 lastname = Column("lastname", String(255), nullable=True, unique=None, default=None)
575 _email = Column("email", String(255), nullable=True, unique=None, default=None)
575 _email = Column("email", String(255), nullable=True, unique=None, default=None)
576 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
576 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
577 last_activity = Column('last_activity', DateTime(timezone=False), nullable=True, unique=None, default=None)
577 last_activity = Column('last_activity', DateTime(timezone=False), nullable=True, unique=None, default=None)
578
578
579 extern_type = Column("extern_type", String(255), nullable=True, unique=None, default=None)
579 extern_type = Column("extern_type", String(255), nullable=True, unique=None, default=None)
580 extern_name = Column("extern_name", String(255), nullable=True, unique=None, default=None)
580 extern_name = Column("extern_name", String(255), nullable=True, unique=None, default=None)
581 _api_key = Column("api_key", String(255), nullable=True, unique=None, default=None)
581 _api_key = Column("api_key", String(255), nullable=True, unique=None, default=None)
582 inherit_default_permissions = Column("inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
582 inherit_default_permissions = Column("inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
583 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
583 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
584 _user_data = Column("user_data", LargeBinary(), nullable=True) # JSON data
584 _user_data = Column("user_data", LargeBinary(), nullable=True) # JSON data
585
585
586 user_log = relationship('UserLog')
586 user_log = relationship('UserLog')
587 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
587 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
588
588
589 repositories = relationship('Repository')
589 repositories = relationship('Repository')
590 repository_groups = relationship('RepoGroup')
590 repository_groups = relationship('RepoGroup')
591 user_groups = relationship('UserGroup')
591 user_groups = relationship('UserGroup')
592
592
593 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
593 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
594 followings = relationship('UserFollowing', primaryjoin='UserFollowing.user_id==User.user_id', cascade='all')
594 followings = relationship('UserFollowing', primaryjoin='UserFollowing.user_id==User.user_id', cascade='all')
595
595
596 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
596 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
597 repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all')
597 repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all')
598 user_group_to_perm = relationship('UserUserGroupToPerm', primaryjoin='UserUserGroupToPerm.user_id==User.user_id', cascade='all')
598 user_group_to_perm = relationship('UserUserGroupToPerm', primaryjoin='UserUserGroupToPerm.user_id==User.user_id', cascade='all')
599
599
600 group_member = relationship('UserGroupMember', cascade='all')
600 group_member = relationship('UserGroupMember', cascade='all')
601
601
602 notifications = relationship('UserNotification', cascade='all')
602 notifications = relationship('UserNotification', cascade='all')
603 # notifications assigned to this user
603 # notifications assigned to this user
604 user_created_notifications = relationship('Notification', cascade='all')
604 user_created_notifications = relationship('Notification', cascade='all')
605 # comments created by this user
605 # comments created by this user
606 user_comments = relationship('ChangesetComment', cascade='all')
606 user_comments = relationship('ChangesetComment', cascade='all')
607 # user profile extra info
607 # user profile extra info
608 user_emails = relationship('UserEmailMap', cascade='all')
608 user_emails = relationship('UserEmailMap', cascade='all')
609 user_ip_map = relationship('UserIpMap', cascade='all')
609 user_ip_map = relationship('UserIpMap', cascade='all')
610 user_auth_tokens = relationship('UserApiKeys', cascade='all')
610 user_auth_tokens = relationship('UserApiKeys', cascade='all')
611 user_ssh_keys = relationship('UserSshKeys', cascade='all')
611 user_ssh_keys = relationship('UserSshKeys', cascade='all')
612
612
613 # gists
613 # gists
614 user_gists = relationship('Gist', cascade='all')
614 user_gists = relationship('Gist', cascade='all')
615 # user pull requests
615 # user pull requests
616 user_pull_requests = relationship('PullRequest', cascade='all')
616 user_pull_requests = relationship('PullRequest', cascade='all')
617 # external identities
617 # external identities
618 extenal_identities = relationship(
618 extenal_identities = relationship(
619 'ExternalIdentity',
619 'ExternalIdentity',
620 primaryjoin="User.user_id==ExternalIdentity.local_user_id",
620 primaryjoin="User.user_id==ExternalIdentity.local_user_id",
621 cascade='all')
621 cascade='all')
622 # review rules
622 # review rules
623 user_review_rules = relationship('RepoReviewRuleUser', cascade='all')
623 user_review_rules = relationship('RepoReviewRuleUser', cascade='all')
624
624
625 def __unicode__(self):
625 def __unicode__(self):
626 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
626 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
627 self.user_id, self.username)
627 self.user_id, self.username)
628
628
629 @hybrid_property
629 @hybrid_property
630 def email(self):
630 def email(self):
631 return self._email
631 return self._email
632
632
633 @email.setter
633 @email.setter
634 def email(self, val):
634 def email(self, val):
635 self._email = val.lower() if val else None
635 self._email = val.lower() if val else None
636
636
637 @hybrid_property
637 @hybrid_property
638 def first_name(self):
638 def first_name(self):
639 from rhodecode.lib import helpers as h
639 from rhodecode.lib import helpers as h
640 if self.name:
640 if self.name:
641 return h.escape(self.name)
641 return h.escape(self.name)
642 return self.name
642 return self.name
643
643
644 @hybrid_property
644 @hybrid_property
645 def last_name(self):
645 def last_name(self):
646 from rhodecode.lib import helpers as h
646 from rhodecode.lib import helpers as h
647 if self.lastname:
647 if self.lastname:
648 return h.escape(self.lastname)
648 return h.escape(self.lastname)
649 return self.lastname
649 return self.lastname
650
650
651 @hybrid_property
651 @hybrid_property
652 def api_key(self):
652 def api_key(self):
653 """
653 """
654 Fetch if exist an auth-token with role ALL connected to this user
654 Fetch if exist an auth-token with role ALL connected to this user
655 """
655 """
656 user_auth_token = UserApiKeys.query()\
656 user_auth_token = UserApiKeys.query()\
657 .filter(UserApiKeys.user_id == self.user_id)\
657 .filter(UserApiKeys.user_id == self.user_id)\
658 .filter(or_(UserApiKeys.expires == -1,
658 .filter(or_(UserApiKeys.expires == -1,
659 UserApiKeys.expires >= time.time()))\
659 UserApiKeys.expires >= time.time()))\
660 .filter(UserApiKeys.role == UserApiKeys.ROLE_ALL).first()
660 .filter(UserApiKeys.role == UserApiKeys.ROLE_ALL).first()
661 if user_auth_token:
661 if user_auth_token:
662 user_auth_token = user_auth_token.api_key
662 user_auth_token = user_auth_token.api_key
663
663
664 return user_auth_token
664 return user_auth_token
665
665
666 @api_key.setter
666 @api_key.setter
667 def api_key(self, val):
667 def api_key(self, val):
668 # don't allow to set API key this is deprecated for now
668 # don't allow to set API key this is deprecated for now
669 self._api_key = None
669 self._api_key = None
670
670
671 @property
671 @property
672 def reviewer_pull_requests(self):
672 def reviewer_pull_requests(self):
673 return PullRequestReviewers.query() \
673 return PullRequestReviewers.query() \
674 .options(joinedload(PullRequestReviewers.pull_request)) \
674 .options(joinedload(PullRequestReviewers.pull_request)) \
675 .filter(PullRequestReviewers.user_id == self.user_id) \
675 .filter(PullRequestReviewers.user_id == self.user_id) \
676 .all()
676 .all()
677
677
678 @property
678 @property
679 def firstname(self):
679 def firstname(self):
680 # alias for future
680 # alias for future
681 return self.name
681 return self.name
682
682
683 @property
683 @property
684 def emails(self):
684 def emails(self):
685 other = UserEmailMap.query()\
685 other = UserEmailMap.query()\
686 .filter(UserEmailMap.user == self) \
686 .filter(UserEmailMap.user == self) \
687 .order_by(UserEmailMap.email_id.asc()) \
687 .order_by(UserEmailMap.email_id.asc()) \
688 .all()
688 .all()
689 return [self.email] + [x.email for x in other]
689 return [self.email] + [x.email for x in other]
690
690
691 @property
691 @property
692 def auth_tokens(self):
692 def auth_tokens(self):
693 auth_tokens = self.get_auth_tokens()
693 auth_tokens = self.get_auth_tokens()
694 return [x.api_key for x in auth_tokens]
694 return [x.api_key for x in auth_tokens]
695
695
696 def get_auth_tokens(self):
696 def get_auth_tokens(self):
697 return UserApiKeys.query()\
697 return UserApiKeys.query()\
698 .filter(UserApiKeys.user == self)\
698 .filter(UserApiKeys.user == self)\
699 .order_by(UserApiKeys.user_api_key_id.asc())\
699 .order_by(UserApiKeys.user_api_key_id.asc())\
700 .all()
700 .all()
701
701
702 @LazyProperty
702 @LazyProperty
703 def feed_token(self):
703 def feed_token(self):
704 return self.get_feed_token()
704 return self.get_feed_token()
705
705
706 def get_feed_token(self, cache=True):
706 def get_feed_token(self, cache=True):
707 feed_tokens = UserApiKeys.query()\
707 feed_tokens = UserApiKeys.query()\
708 .filter(UserApiKeys.user == self)\
708 .filter(UserApiKeys.user == self)\
709 .filter(UserApiKeys.role == UserApiKeys.ROLE_FEED)
709 .filter(UserApiKeys.role == UserApiKeys.ROLE_FEED)
710 if cache:
710 if cache:
711 feed_tokens = feed_tokens.options(
711 feed_tokens = feed_tokens.options(
712 FromCache("sql_cache_short", "get_user_feed_token_%s" % self.user_id))
712 FromCache("sql_cache_short", "get_user_feed_token_%s" % self.user_id))
713
713
714 feed_tokens = feed_tokens.all()
714 feed_tokens = feed_tokens.all()
715 if feed_tokens:
715 if feed_tokens:
716 return feed_tokens[0].api_key
716 return feed_tokens[0].api_key
717 return 'NO_FEED_TOKEN_AVAILABLE'
717 return 'NO_FEED_TOKEN_AVAILABLE'
718
718
719 @classmethod
719 @classmethod
720 def get(cls, user_id, cache=False):
720 def get(cls, user_id, cache=False):
721 if not user_id:
721 if not user_id:
722 return
722 return
723
723
724 user = cls.query()
724 user = cls.query()
725 if cache:
725 if cache:
726 user = user.options(
726 user = user.options(
727 FromCache("sql_cache_short", "get_users_%s" % user_id))
727 FromCache("sql_cache_short", "get_users_%s" % user_id))
728 return user.get(user_id)
728 return user.get(user_id)
729
729
730 @classmethod
730 @classmethod
731 def extra_valid_auth_tokens(cls, user, role=None):
731 def extra_valid_auth_tokens(cls, user, role=None):
732 tokens = UserApiKeys.query().filter(UserApiKeys.user == user)\
732 tokens = UserApiKeys.query().filter(UserApiKeys.user == user)\
733 .filter(or_(UserApiKeys.expires == -1,
733 .filter(or_(UserApiKeys.expires == -1,
734 UserApiKeys.expires >= time.time()))
734 UserApiKeys.expires >= time.time()))
735 if role:
735 if role:
736 tokens = tokens.filter(or_(UserApiKeys.role == role,
736 tokens = tokens.filter(or_(UserApiKeys.role == role,
737 UserApiKeys.role == UserApiKeys.ROLE_ALL))
737 UserApiKeys.role == UserApiKeys.ROLE_ALL))
738 return tokens.all()
738 return tokens.all()
739
739
740 def authenticate_by_token(self, auth_token, roles=None, scope_repo_id=None):
740 def authenticate_by_token(self, auth_token, roles=None, scope_repo_id=None):
741 from rhodecode.lib import auth
741 from rhodecode.lib import auth
742
742
743 log.debug('Trying to authenticate user: %s via auth-token, '
743 log.debug('Trying to authenticate user: %s via auth-token, '
744 'and roles: %s', self, roles)
744 'and roles: %s', self, roles)
745
745
746 if not auth_token:
746 if not auth_token:
747 return False
747 return False
748
748
749 roles = (roles or []) + [UserApiKeys.ROLE_ALL]
749 roles = (roles or []) + [UserApiKeys.ROLE_ALL]
750 tokens_q = UserApiKeys.query()\
750 tokens_q = UserApiKeys.query()\
751 .filter(UserApiKeys.user_id == self.user_id)\
751 .filter(UserApiKeys.user_id == self.user_id)\
752 .filter(or_(UserApiKeys.expires == -1,
752 .filter(or_(UserApiKeys.expires == -1,
753 UserApiKeys.expires >= time.time()))
753 UserApiKeys.expires >= time.time()))
754
754
755 tokens_q = tokens_q.filter(UserApiKeys.role.in_(roles))
755 tokens_q = tokens_q.filter(UserApiKeys.role.in_(roles))
756
756
757 crypto_backend = auth.crypto_backend()
757 crypto_backend = auth.crypto_backend()
758 enc_token_map = {}
758 enc_token_map = {}
759 plain_token_map = {}
759 plain_token_map = {}
760 for token in tokens_q:
760 for token in tokens_q:
761 if token.api_key.startswith(crypto_backend.ENC_PREF):
761 if token.api_key.startswith(crypto_backend.ENC_PREF):
762 enc_token_map[token.api_key] = token
762 enc_token_map[token.api_key] = token
763 else:
763 else:
764 plain_token_map[token.api_key] = token
764 plain_token_map[token.api_key] = token
765 log.debug(
765 log.debug(
766 'Found %s plain and %s encrypted user tokens to check for authentication',
766 'Found %s plain and %s encrypted user tokens to check for authentication',
767 len(plain_token_map), len(enc_token_map))
767 len(plain_token_map), len(enc_token_map))
768
768
769 # plain token match comes first
769 # plain token match comes first
770 match = plain_token_map.get(auth_token)
770 match = plain_token_map.get(auth_token)
771
771
772 # check encrypted tokens now
772 # check encrypted tokens now
773 if not match:
773 if not match:
774 for token_hash, token in enc_token_map.items():
774 for token_hash, token in enc_token_map.items():
775 # NOTE(marcink): this is expensive to calculate, but most secure
775 # NOTE(marcink): this is expensive to calculate, but most secure
776 if crypto_backend.hash_check(auth_token, token_hash):
776 if crypto_backend.hash_check(auth_token, token_hash):
777 match = token
777 match = token
778 break
778 break
779
779
780 if match:
780 if match:
781 log.debug('Found matching token %s', match)
781 log.debug('Found matching token %s', match)
782 if match.repo_id:
782 if match.repo_id:
783 log.debug('Found scope, checking for scope match of token %s', match)
783 log.debug('Found scope, checking for scope match of token %s', match)
784 if match.repo_id == scope_repo_id:
784 if match.repo_id == scope_repo_id:
785 return True
785 return True
786 else:
786 else:
787 log.debug(
787 log.debug(
788 'AUTH_TOKEN: scope mismatch, token has a set repo scope: %s, '
788 'AUTH_TOKEN: scope mismatch, token has a set repo scope: %s, '
789 'and calling scope is:%s, skipping further checks',
789 'and calling scope is:%s, skipping further checks',
790 match.repo, scope_repo_id)
790 match.repo, scope_repo_id)
791 return False
791 return False
792 else:
792 else:
793 return True
793 return True
794
794
795 return False
795 return False
796
796
797 @property
797 @property
798 def ip_addresses(self):
798 def ip_addresses(self):
799 ret = UserIpMap.query().filter(UserIpMap.user == self).all()
799 ret = UserIpMap.query().filter(UserIpMap.user == self).all()
800 return [x.ip_addr for x in ret]
800 return [x.ip_addr for x in ret]
801
801
802 @property
802 @property
803 def username_and_name(self):
803 def username_and_name(self):
804 return '%s (%s %s)' % (self.username, self.first_name, self.last_name)
804 return '%s (%s %s)' % (self.username, self.first_name, self.last_name)
805
805
806 @property
806 @property
807 def username_or_name_or_email(self):
807 def username_or_name_or_email(self):
808 full_name = self.full_name if self.full_name is not ' ' else None
808 full_name = self.full_name if self.full_name is not ' ' else None
809 return self.username or full_name or self.email
809 return self.username or full_name or self.email
810
810
811 @property
811 @property
812 def full_name(self):
812 def full_name(self):
813 return '%s %s' % (self.first_name, self.last_name)
813 return '%s %s' % (self.first_name, self.last_name)
814
814
815 @property
815 @property
816 def full_name_or_username(self):
816 def full_name_or_username(self):
817 return ('%s %s' % (self.first_name, self.last_name)
817 return ('%s %s' % (self.first_name, self.last_name)
818 if (self.first_name and self.last_name) else self.username)
818 if (self.first_name and self.last_name) else self.username)
819
819
820 @property
820 @property
821 def full_contact(self):
821 def full_contact(self):
822 return '%s %s <%s>' % (self.first_name, self.last_name, self.email)
822 return '%s %s <%s>' % (self.first_name, self.last_name, self.email)
823
823
824 @property
824 @property
825 def short_contact(self):
825 def short_contact(self):
826 return '%s %s' % (self.first_name, self.last_name)
826 return '%s %s' % (self.first_name, self.last_name)
827
827
828 @property
828 @property
829 def is_admin(self):
829 def is_admin(self):
830 return self.admin
830 return self.admin
831
831
832 def AuthUser(self, **kwargs):
832 def AuthUser(self, **kwargs):
833 """
833 """
834 Returns instance of AuthUser for this user
834 Returns instance of AuthUser for this user
835 """
835 """
836 from rhodecode.lib.auth import AuthUser
836 from rhodecode.lib.auth import AuthUser
837 return AuthUser(user_id=self.user_id, username=self.username, **kwargs)
837 return AuthUser(user_id=self.user_id, username=self.username, **kwargs)
838
838
839 @hybrid_property
839 @hybrid_property
840 def user_data(self):
840 def user_data(self):
841 if not self._user_data:
841 if not self._user_data:
842 return {}
842 return {}
843
843
844 try:
844 try:
845 return json.loads(self._user_data)
845 return json.loads(self._user_data)
846 except TypeError:
846 except TypeError:
847 return {}
847 return {}
848
848
849 @user_data.setter
849 @user_data.setter
850 def user_data(self, val):
850 def user_data(self, val):
851 if not isinstance(val, dict):
851 if not isinstance(val, dict):
852 raise Exception('user_data must be dict, got %s' % type(val))
852 raise Exception('user_data must be dict, got %s' % type(val))
853 try:
853 try:
854 self._user_data = json.dumps(val)
854 self._user_data = json.dumps(val)
855 except Exception:
855 except Exception:
856 log.error(traceback.format_exc())
856 log.error(traceback.format_exc())
857
857
858 @classmethod
858 @classmethod
859 def get_by_username(cls, username, case_insensitive=False,
859 def get_by_username(cls, username, case_insensitive=False,
860 cache=False, identity_cache=False):
860 cache=False, identity_cache=False):
861 session = Session()
861 session = Session()
862
862
863 if case_insensitive:
863 if case_insensitive:
864 q = cls.query().filter(
864 q = cls.query().filter(
865 func.lower(cls.username) == func.lower(username))
865 func.lower(cls.username) == func.lower(username))
866 else:
866 else:
867 q = cls.query().filter(cls.username == username)
867 q = cls.query().filter(cls.username == username)
868
868
869 if cache:
869 if cache:
870 if identity_cache:
870 if identity_cache:
871 val = cls.identity_cache(session, 'username', username)
871 val = cls.identity_cache(session, 'username', username)
872 if val:
872 if val:
873 return val
873 return val
874 else:
874 else:
875 cache_key = "get_user_by_name_%s" % _hash_key(username)
875 cache_key = "get_user_by_name_%s" % _hash_key(username)
876 q = q.options(
876 q = q.options(
877 FromCache("sql_cache_short", cache_key))
877 FromCache("sql_cache_short", cache_key))
878
878
879 return q.scalar()
879 return q.scalar()
880
880
881 @classmethod
881 @classmethod
882 def get_by_auth_token(cls, auth_token, cache=False):
882 def get_by_auth_token(cls, auth_token, cache=False):
883 q = UserApiKeys.query()\
883 q = UserApiKeys.query()\
884 .filter(UserApiKeys.api_key == auth_token)\
884 .filter(UserApiKeys.api_key == auth_token)\
885 .filter(or_(UserApiKeys.expires == -1,
885 .filter(or_(UserApiKeys.expires == -1,
886 UserApiKeys.expires >= time.time()))
886 UserApiKeys.expires >= time.time()))
887 if cache:
887 if cache:
888 q = q.options(
888 q = q.options(
889 FromCache("sql_cache_short", "get_auth_token_%s" % auth_token))
889 FromCache("sql_cache_short", "get_auth_token_%s" % auth_token))
890
890
891 match = q.first()
891 match = q.first()
892 if match:
892 if match:
893 return match.user
893 return match.user
894
894
895 @classmethod
895 @classmethod
896 def get_by_email(cls, email, case_insensitive=False, cache=False):
896 def get_by_email(cls, email, case_insensitive=False, cache=False):
897
897
898 if case_insensitive:
898 if case_insensitive:
899 q = cls.query().filter(func.lower(cls.email) == func.lower(email))
899 q = cls.query().filter(func.lower(cls.email) == func.lower(email))
900
900
901 else:
901 else:
902 q = cls.query().filter(cls.email == email)
902 q = cls.query().filter(cls.email == email)
903
903
904 email_key = _hash_key(email)
904 email_key = _hash_key(email)
905 if cache:
905 if cache:
906 q = q.options(
906 q = q.options(
907 FromCache("sql_cache_short", "get_email_key_%s" % email_key))
907 FromCache("sql_cache_short", "get_email_key_%s" % email_key))
908
908
909 ret = q.scalar()
909 ret = q.scalar()
910 if ret is None:
910 if ret is None:
911 q = UserEmailMap.query()
911 q = UserEmailMap.query()
912 # try fetching in alternate email map
912 # try fetching in alternate email map
913 if case_insensitive:
913 if case_insensitive:
914 q = q.filter(func.lower(UserEmailMap.email) == func.lower(email))
914 q = q.filter(func.lower(UserEmailMap.email) == func.lower(email))
915 else:
915 else:
916 q = q.filter(UserEmailMap.email == email)
916 q = q.filter(UserEmailMap.email == email)
917 q = q.options(joinedload(UserEmailMap.user))
917 q = q.options(joinedload(UserEmailMap.user))
918 if cache:
918 if cache:
919 q = q.options(
919 q = q.options(
920 FromCache("sql_cache_short", "get_email_map_key_%s" % email_key))
920 FromCache("sql_cache_short", "get_email_map_key_%s" % email_key))
921 ret = getattr(q.scalar(), 'user', None)
921 ret = getattr(q.scalar(), 'user', None)
922
922
923 return ret
923 return ret
924
924
925 @classmethod
925 @classmethod
926 def get_from_cs_author(cls, author):
926 def get_from_cs_author(cls, author):
927 """
927 """
928 Tries to get User objects out of commit author string
928 Tries to get User objects out of commit author string
929
929
930 :param author:
930 :param author:
931 """
931 """
932 from rhodecode.lib.helpers import email, author_name
932 from rhodecode.lib.helpers import email, author_name
933 # Valid email in the attribute passed, see if they're in the system
933 # Valid email in the attribute passed, see if they're in the system
934 _email = email(author)
934 _email = email(author)
935 if _email:
935 if _email:
936 user = cls.get_by_email(_email, case_insensitive=True)
936 user = cls.get_by_email(_email, case_insensitive=True)
937 if user:
937 if user:
938 return user
938 return user
939 # Maybe we can match by username?
939 # Maybe we can match by username?
940 _author = author_name(author)
940 _author = author_name(author)
941 user = cls.get_by_username(_author, case_insensitive=True)
941 user = cls.get_by_username(_author, case_insensitive=True)
942 if user:
942 if user:
943 return user
943 return user
944
944
945 def update_userdata(self, **kwargs):
945 def update_userdata(self, **kwargs):
946 usr = self
946 usr = self
947 old = usr.user_data
947 old = usr.user_data
948 old.update(**kwargs)
948 old.update(**kwargs)
949 usr.user_data = old
949 usr.user_data = old
950 Session().add(usr)
950 Session().add(usr)
951 log.debug('updated userdata with ', kwargs)
951 log.debug('updated userdata with ', kwargs)
952
952
953 def update_lastlogin(self):
953 def update_lastlogin(self):
954 """Update user lastlogin"""
954 """Update user lastlogin"""
955 self.last_login = datetime.datetime.now()
955 self.last_login = datetime.datetime.now()
956 Session().add(self)
956 Session().add(self)
957 log.debug('updated user %s lastlogin', self.username)
957 log.debug('updated user %s lastlogin', self.username)
958
958
959 def update_password(self, new_password):
959 def update_password(self, new_password):
960 from rhodecode.lib.auth import get_crypt_password
960 from rhodecode.lib.auth import get_crypt_password
961
961
962 self.password = get_crypt_password(new_password)
962 self.password = get_crypt_password(new_password)
963 Session().add(self)
963 Session().add(self)
964
964
965 @classmethod
965 @classmethod
966 def get_first_super_admin(cls):
966 def get_first_super_admin(cls):
967 user = User.query()\
967 user = User.query()\
968 .filter(User.admin == true()) \
968 .filter(User.admin == true()) \
969 .order_by(User.user_id.asc()) \
969 .order_by(User.user_id.asc()) \
970 .first()
970 .first()
971
971
972 if user is None:
972 if user is None:
973 raise Exception('FATAL: Missing administrative account!')
973 raise Exception('FATAL: Missing administrative account!')
974 return user
974 return user
975
975
976 @classmethod
976 @classmethod
977 def get_all_super_admins(cls, only_active=False):
977 def get_all_super_admins(cls, only_active=False):
978 """
978 """
979 Returns all admin accounts sorted by username
979 Returns all admin accounts sorted by username
980 """
980 """
981 qry = User.query().filter(User.admin == true()).order_by(User.username.asc())
981 qry = User.query().filter(User.admin == true()).order_by(User.username.asc())
982 if only_active:
982 if only_active:
983 qry = qry.filter(User.active == true())
983 qry = qry.filter(User.active == true())
984 return qry.all()
984 return qry.all()
985
985
986 @classmethod
986 @classmethod
987 def get_default_user(cls, cache=False, refresh=False):
987 def get_default_user(cls, cache=False, refresh=False):
988 user = User.get_by_username(User.DEFAULT_USER, cache=cache)
988 user = User.get_by_username(User.DEFAULT_USER, cache=cache)
989 if user is None:
989 if user is None:
990 raise Exception('FATAL: Missing default account!')
990 raise Exception('FATAL: Missing default account!')
991 if refresh:
991 if refresh:
992 # The default user might be based on outdated state which
992 # The default user might be based on outdated state which
993 # has been loaded from the cache.
993 # has been loaded from the cache.
994 # A call to refresh() ensures that the
994 # A call to refresh() ensures that the
995 # latest state from the database is used.
995 # latest state from the database is used.
996 Session().refresh(user)
996 Session().refresh(user)
997 return user
997 return user
998
998
999 def _get_default_perms(self, user, suffix=''):
999 def _get_default_perms(self, user, suffix=''):
1000 from rhodecode.model.permission import PermissionModel
1000 from rhodecode.model.permission import PermissionModel
1001 return PermissionModel().get_default_perms(user.user_perms, suffix)
1001 return PermissionModel().get_default_perms(user.user_perms, suffix)
1002
1002
1003 def get_default_perms(self, suffix=''):
1003 def get_default_perms(self, suffix=''):
1004 return self._get_default_perms(self, suffix)
1004 return self._get_default_perms(self, suffix)
1005
1005
1006 def get_api_data(self, include_secrets=False, details='full'):
1006 def get_api_data(self, include_secrets=False, details='full'):
1007 """
1007 """
1008 Common function for generating user related data for API
1008 Common function for generating user related data for API
1009
1009
1010 :param include_secrets: By default secrets in the API data will be replaced
1010 :param include_secrets: By default secrets in the API data will be replaced
1011 by a placeholder value to prevent exposing this data by accident. In case
1011 by a placeholder value to prevent exposing this data by accident. In case
1012 this data shall be exposed, set this flag to ``True``.
1012 this data shall be exposed, set this flag to ``True``.
1013
1013
1014 :param details: details can be 'basic|full' basic gives only a subset of
1014 :param details: details can be 'basic|full' basic gives only a subset of
1015 the available user information that includes user_id, name and emails.
1015 the available user information that includes user_id, name and emails.
1016 """
1016 """
1017 user = self
1017 user = self
1018 user_data = self.user_data
1018 user_data = self.user_data
1019 data = {
1019 data = {
1020 'user_id': user.user_id,
1020 'user_id': user.user_id,
1021 'username': user.username,
1021 'username': user.username,
1022 'firstname': user.name,
1022 'firstname': user.name,
1023 'lastname': user.lastname,
1023 'lastname': user.lastname,
1024 'email': user.email,
1024 'email': user.email,
1025 'emails': user.emails,
1025 'emails': user.emails,
1026 }
1026 }
1027 if details == 'basic':
1027 if details == 'basic':
1028 return data
1028 return data
1029
1029
1030 auth_token_length = 40
1030 auth_token_length = 40
1031 auth_token_replacement = '*' * auth_token_length
1031 auth_token_replacement = '*' * auth_token_length
1032
1032
1033 extras = {
1033 extras = {
1034 'auth_tokens': [auth_token_replacement],
1034 'auth_tokens': [auth_token_replacement],
1035 'active': user.active,
1035 'active': user.active,
1036 'admin': user.admin,
1036 'admin': user.admin,
1037 'extern_type': user.extern_type,
1037 'extern_type': user.extern_type,
1038 'extern_name': user.extern_name,
1038 'extern_name': user.extern_name,
1039 'last_login': user.last_login,
1039 'last_login': user.last_login,
1040 'last_activity': user.last_activity,
1040 'last_activity': user.last_activity,
1041 'ip_addresses': user.ip_addresses,
1041 'ip_addresses': user.ip_addresses,
1042 'language': user_data.get('language')
1042 'language': user_data.get('language')
1043 }
1043 }
1044 data.update(extras)
1044 data.update(extras)
1045
1045
1046 if include_secrets:
1046 if include_secrets:
1047 data['auth_tokens'] = user.auth_tokens
1047 data['auth_tokens'] = user.auth_tokens
1048 return data
1048 return data
1049
1049
1050 def __json__(self):
1050 def __json__(self):
1051 data = {
1051 data = {
1052 'full_name': self.full_name,
1052 'full_name': self.full_name,
1053 'full_name_or_username': self.full_name_or_username,
1053 'full_name_or_username': self.full_name_or_username,
1054 'short_contact': self.short_contact,
1054 'short_contact': self.short_contact,
1055 'full_contact': self.full_contact,
1055 'full_contact': self.full_contact,
1056 }
1056 }
1057 data.update(self.get_api_data())
1057 data.update(self.get_api_data())
1058 return data
1058 return data
1059
1059
1060
1060
1061 class UserApiKeys(Base, BaseModel):
1061 class UserApiKeys(Base, BaseModel):
1062 __tablename__ = 'user_api_keys'
1062 __tablename__ = 'user_api_keys'
1063 __table_args__ = (
1063 __table_args__ = (
1064 Index('uak_api_key_idx', 'api_key', unique=True),
1064 Index('uak_api_key_idx', 'api_key', unique=True),
1065 Index('uak_api_key_expires_idx', 'api_key', 'expires'),
1065 Index('uak_api_key_expires_idx', 'api_key', 'expires'),
1066 base_table_args
1066 base_table_args
1067 )
1067 )
1068 __mapper_args__ = {}
1068 __mapper_args__ = {}
1069
1069
1070 # ApiKey role
1070 # ApiKey role
1071 ROLE_ALL = 'token_role_all'
1071 ROLE_ALL = 'token_role_all'
1072 ROLE_HTTP = 'token_role_http'
1072 ROLE_HTTP = 'token_role_http'
1073 ROLE_VCS = 'token_role_vcs'
1073 ROLE_VCS = 'token_role_vcs'
1074 ROLE_API = 'token_role_api'
1074 ROLE_API = 'token_role_api'
1075 ROLE_FEED = 'token_role_feed'
1075 ROLE_FEED = 'token_role_feed'
1076 ROLE_PASSWORD_RESET = 'token_password_reset'
1076 ROLE_PASSWORD_RESET = 'token_password_reset'
1077
1077
1078 ROLES = [ROLE_ALL, ROLE_HTTP, ROLE_VCS, ROLE_API, ROLE_FEED]
1078 ROLES = [ROLE_ALL, ROLE_HTTP, ROLE_VCS, ROLE_API, ROLE_FEED]
1079
1079
1080 user_api_key_id = Column("user_api_key_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1080 user_api_key_id = Column("user_api_key_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1081 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1081 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1082 api_key = Column("api_key", String(255), nullable=False, unique=True)
1082 api_key = Column("api_key", String(255), nullable=False, unique=True)
1083 description = Column('description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
1083 description = Column('description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
1084 expires = Column('expires', Float(53), nullable=False)
1084 expires = Column('expires', Float(53), nullable=False)
1085 role = Column('role', String(255), nullable=True)
1085 role = Column('role', String(255), nullable=True)
1086 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1086 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1087
1087
1088 # scope columns
1088 # scope columns
1089 repo_id = Column(
1089 repo_id = Column(
1090 'repo_id', Integer(), ForeignKey('repositories.repo_id'),
1090 'repo_id', Integer(), ForeignKey('repositories.repo_id'),
1091 nullable=True, unique=None, default=None)
1091 nullable=True, unique=None, default=None)
1092 repo = relationship('Repository', lazy='joined')
1092 repo = relationship('Repository', lazy='joined')
1093
1093
1094 repo_group_id = Column(
1094 repo_group_id = Column(
1095 'repo_group_id', Integer(), ForeignKey('groups.group_id'),
1095 'repo_group_id', Integer(), ForeignKey('groups.group_id'),
1096 nullable=True, unique=None, default=None)
1096 nullable=True, unique=None, default=None)
1097 repo_group = relationship('RepoGroup', lazy='joined')
1097 repo_group = relationship('RepoGroup', lazy='joined')
1098
1098
1099 user = relationship('User', lazy='joined')
1099 user = relationship('User', lazy='joined')
1100
1100
1101 def __unicode__(self):
1101 def __unicode__(self):
1102 return u"<%s('%s')>" % (self.__class__.__name__, self.role)
1102 return u"<%s('%s')>" % (self.__class__.__name__, self.role)
1103
1103
1104 def __json__(self):
1104 def __json__(self):
1105 data = {
1105 data = {
1106 'auth_token': self.api_key,
1106 'auth_token': self.api_key,
1107 'role': self.role,
1107 'role': self.role,
1108 'scope': self.scope_humanized,
1108 'scope': self.scope_humanized,
1109 'expired': self.expired
1109 'expired': self.expired
1110 }
1110 }
1111 return data
1111 return data
1112
1112
1113 def get_api_data(self, include_secrets=False):
1113 def get_api_data(self, include_secrets=False):
1114 data = self.__json__()
1114 data = self.__json__()
1115 if include_secrets:
1115 if include_secrets:
1116 return data
1116 return data
1117 else:
1117 else:
1118 data['auth_token'] = self.token_obfuscated
1118 data['auth_token'] = self.token_obfuscated
1119 return data
1119 return data
1120
1120
1121 @hybrid_property
1121 @hybrid_property
1122 def description_safe(self):
1122 def description_safe(self):
1123 from rhodecode.lib import helpers as h
1123 from rhodecode.lib import helpers as h
1124 return h.escape(self.description)
1124 return h.escape(self.description)
1125
1125
1126 @property
1126 @property
1127 def expired(self):
1127 def expired(self):
1128 if self.expires == -1:
1128 if self.expires == -1:
1129 return False
1129 return False
1130 return time.time() > self.expires
1130 return time.time() > self.expires
1131
1131
1132 @classmethod
1132 @classmethod
1133 def _get_role_name(cls, role):
1133 def _get_role_name(cls, role):
1134 return {
1134 return {
1135 cls.ROLE_ALL: _('all'),
1135 cls.ROLE_ALL: _('all'),
1136 cls.ROLE_HTTP: _('http/web interface'),
1136 cls.ROLE_HTTP: _('http/web interface'),
1137 cls.ROLE_VCS: _('vcs (git/hg/svn protocol)'),
1137 cls.ROLE_VCS: _('vcs (git/hg/svn protocol)'),
1138 cls.ROLE_API: _('api calls'),
1138 cls.ROLE_API: _('api calls'),
1139 cls.ROLE_FEED: _('feed access'),
1139 cls.ROLE_FEED: _('feed access'),
1140 }.get(role, role)
1140 }.get(role, role)
1141
1141
1142 @property
1142 @property
1143 def role_humanized(self):
1143 def role_humanized(self):
1144 return self._get_role_name(self.role)
1144 return self._get_role_name(self.role)
1145
1145
1146 def _get_scope(self):
1146 def _get_scope(self):
1147 if self.repo:
1147 if self.repo:
1148 return 'Repository: {}'.format(self.repo.repo_name)
1148 return 'Repository: {}'.format(self.repo.repo_name)
1149 if self.repo_group:
1149 if self.repo_group:
1150 return 'RepositoryGroup: {} (recursive)'.format(self.repo_group.group_name)
1150 return 'RepositoryGroup: {} (recursive)'.format(self.repo_group.group_name)
1151 return 'Global'
1151 return 'Global'
1152
1152
1153 @property
1153 @property
1154 def scope_humanized(self):
1154 def scope_humanized(self):
1155 return self._get_scope()
1155 return self._get_scope()
1156
1156
1157 @property
1157 @property
1158 def token_obfuscated(self):
1158 def token_obfuscated(self):
1159 if self.api_key:
1159 if self.api_key:
1160 return self.api_key[:4] + "****"
1160 return self.api_key[:4] + "****"
1161
1161
1162
1162
1163 class UserEmailMap(Base, BaseModel):
1163 class UserEmailMap(Base, BaseModel):
1164 __tablename__ = 'user_email_map'
1164 __tablename__ = 'user_email_map'
1165 __table_args__ = (
1165 __table_args__ = (
1166 Index('uem_email_idx', 'email'),
1166 Index('uem_email_idx', 'email'),
1167 UniqueConstraint('email'),
1167 UniqueConstraint('email'),
1168 base_table_args
1168 base_table_args
1169 )
1169 )
1170 __mapper_args__ = {}
1170 __mapper_args__ = {}
1171
1171
1172 email_id = Column("email_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1172 email_id = Column("email_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1173 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1173 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1174 _email = Column("email", String(255), nullable=True, unique=False, default=None)
1174 _email = Column("email", String(255), nullable=True, unique=False, default=None)
1175 user = relationship('User', lazy='joined')
1175 user = relationship('User', lazy='joined')
1176
1176
1177 @validates('_email')
1177 @validates('_email')
1178 def validate_email(self, key, email):
1178 def validate_email(self, key, email):
1179 # check if this email is not main one
1179 # check if this email is not main one
1180 main_email = Session().query(User).filter(User.email == email).scalar()
1180 main_email = Session().query(User).filter(User.email == email).scalar()
1181 if main_email is not None:
1181 if main_email is not None:
1182 raise AttributeError('email %s is present is user table' % email)
1182 raise AttributeError('email %s is present is user table' % email)
1183 return email
1183 return email
1184
1184
1185 @hybrid_property
1185 @hybrid_property
1186 def email(self):
1186 def email(self):
1187 return self._email
1187 return self._email
1188
1188
1189 @email.setter
1189 @email.setter
1190 def email(self, val):
1190 def email(self, val):
1191 self._email = val.lower() if val else None
1191 self._email = val.lower() if val else None
1192
1192
1193
1193
1194 class UserIpMap(Base, BaseModel):
1194 class UserIpMap(Base, BaseModel):
1195 __tablename__ = 'user_ip_map'
1195 __tablename__ = 'user_ip_map'
1196 __table_args__ = (
1196 __table_args__ = (
1197 UniqueConstraint('user_id', 'ip_addr'),
1197 UniqueConstraint('user_id', 'ip_addr'),
1198 base_table_args
1198 base_table_args
1199 )
1199 )
1200 __mapper_args__ = {}
1200 __mapper_args__ = {}
1201
1201
1202 ip_id = Column("ip_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1202 ip_id = Column("ip_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1203 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1203 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1204 ip_addr = Column("ip_addr", String(255), nullable=True, unique=False, default=None)
1204 ip_addr = Column("ip_addr", String(255), nullable=True, unique=False, default=None)
1205 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
1205 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
1206 description = Column("description", String(10000), nullable=True, unique=None, default=None)
1206 description = Column("description", String(10000), nullable=True, unique=None, default=None)
1207 user = relationship('User', lazy='joined')
1207 user = relationship('User', lazy='joined')
1208
1208
1209 @hybrid_property
1209 @hybrid_property
1210 def description_safe(self):
1210 def description_safe(self):
1211 from rhodecode.lib import helpers as h
1211 from rhodecode.lib import helpers as h
1212 return h.escape(self.description)
1212 return h.escape(self.description)
1213
1213
1214 @classmethod
1214 @classmethod
1215 def _get_ip_range(cls, ip_addr):
1215 def _get_ip_range(cls, ip_addr):
1216 net = ipaddress.ip_network(safe_unicode(ip_addr), strict=False)
1216 net = ipaddress.ip_network(safe_unicode(ip_addr), strict=False)
1217 return [str(net.network_address), str(net.broadcast_address)]
1217 return [str(net.network_address), str(net.broadcast_address)]
1218
1218
1219 def __json__(self):
1219 def __json__(self):
1220 return {
1220 return {
1221 'ip_addr': self.ip_addr,
1221 'ip_addr': self.ip_addr,
1222 'ip_range': self._get_ip_range(self.ip_addr),
1222 'ip_range': self._get_ip_range(self.ip_addr),
1223 }
1223 }
1224
1224
1225 def __unicode__(self):
1225 def __unicode__(self):
1226 return u"<%s('user_id:%s=>%s')>" % (self.__class__.__name__,
1226 return u"<%s('user_id:%s=>%s')>" % (self.__class__.__name__,
1227 self.user_id, self.ip_addr)
1227 self.user_id, self.ip_addr)
1228
1228
1229
1229
1230 class UserSshKeys(Base, BaseModel):
1230 class UserSshKeys(Base, BaseModel):
1231 __tablename__ = 'user_ssh_keys'
1231 __tablename__ = 'user_ssh_keys'
1232 __table_args__ = (
1232 __table_args__ = (
1233 Index('usk_ssh_key_fingerprint_idx', 'ssh_key_fingerprint'),
1233 Index('usk_ssh_key_fingerprint_idx', 'ssh_key_fingerprint'),
1234
1234
1235 UniqueConstraint('ssh_key_fingerprint'),
1235 UniqueConstraint('ssh_key_fingerprint'),
1236
1236
1237 base_table_args
1237 base_table_args
1238 )
1238 )
1239 __mapper_args__ = {}
1239 __mapper_args__ = {}
1240
1240
1241 ssh_key_id = Column('ssh_key_id', Integer(), nullable=False, unique=True, default=None, primary_key=True)
1241 ssh_key_id = Column('ssh_key_id', Integer(), nullable=False, unique=True, default=None, primary_key=True)
1242 ssh_key_data = Column('ssh_key_data', String(10240), nullable=False, unique=None, default=None)
1242 ssh_key_data = Column('ssh_key_data', String(10240), nullable=False, unique=None, default=None)
1243 ssh_key_fingerprint = Column('ssh_key_fingerprint', String(255), nullable=False, unique=None, default=None)
1243 ssh_key_fingerprint = Column('ssh_key_fingerprint', String(255), nullable=False, unique=None, default=None)
1244
1244
1245 description = Column('description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
1245 description = Column('description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
1246
1246
1247 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1247 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1248 accessed_on = Column('accessed_on', DateTime(timezone=False), nullable=True, default=None)
1248 accessed_on = Column('accessed_on', DateTime(timezone=False), nullable=True, default=None)
1249 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1249 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1250
1250
1251 user = relationship('User', lazy='joined')
1251 user = relationship('User', lazy='joined')
1252
1252
1253 def __json__(self):
1253 def __json__(self):
1254 data = {
1254 data = {
1255 'ssh_fingerprint': self.ssh_key_fingerprint,
1255 'ssh_fingerprint': self.ssh_key_fingerprint,
1256 'description': self.description,
1256 'description': self.description,
1257 'created_on': self.created_on
1257 'created_on': self.created_on
1258 }
1258 }
1259 return data
1259 return data
1260
1260
1261 def get_api_data(self):
1261 def get_api_data(self):
1262 data = self.__json__()
1262 data = self.__json__()
1263 return data
1263 return data
1264
1264
1265
1265
1266 class UserLog(Base, BaseModel):
1266 class UserLog(Base, BaseModel):
1267 __tablename__ = 'user_logs'
1267 __tablename__ = 'user_logs'
1268 __table_args__ = (
1268 __table_args__ = (
1269 base_table_args,
1269 base_table_args,
1270 )
1270 )
1271
1271
1272 VERSION_1 = 'v1'
1272 VERSION_1 = 'v1'
1273 VERSION_2 = 'v2'
1273 VERSION_2 = 'v2'
1274 VERSIONS = [VERSION_1, VERSION_2]
1274 VERSIONS = [VERSION_1, VERSION_2]
1275
1275
1276 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1276 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1277 user_id = Column("user_id", Integer(), ForeignKey('users.user_id',ondelete='SET NULL'), nullable=True, unique=None, default=None)
1277 user_id = Column("user_id", Integer(), ForeignKey('users.user_id',ondelete='SET NULL'), nullable=True, unique=None, default=None)
1278 username = Column("username", String(255), nullable=True, unique=None, default=None)
1278 username = Column("username", String(255), nullable=True, unique=None, default=None)
1279 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id', ondelete='SET NULL'), nullable=True, unique=None, default=None)
1279 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id', ondelete='SET NULL'), nullable=True, unique=None, default=None)
1280 repository_name = Column("repository_name", String(255), nullable=True, unique=None, default=None)
1280 repository_name = Column("repository_name", String(255), nullable=True, unique=None, default=None)
1281 user_ip = Column("user_ip", String(255), nullable=True, unique=None, default=None)
1281 user_ip = Column("user_ip", String(255), nullable=True, unique=None, default=None)
1282 action = Column("action", Text().with_variant(Text(1200000), 'mysql'), nullable=True, unique=None, default=None)
1282 action = Column("action", Text().with_variant(Text(1200000), 'mysql'), nullable=True, unique=None, default=None)
1283 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
1283 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
1284
1284
1285 version = Column("version", String(255), nullable=True, default=VERSION_1)
1285 version = Column("version", String(255), nullable=True, default=VERSION_1)
1286 user_data = Column('user_data_json', MutationObj.as_mutable(JsonType(dialect_map=dict(mysql=LONGTEXT()))))
1286 user_data = Column('user_data_json', MutationObj.as_mutable(JsonType(dialect_map=dict(mysql=LONGTEXT()))))
1287 action_data = Column('action_data_json', MutationObj.as_mutable(JsonType(dialect_map=dict(mysql=LONGTEXT()))))
1287 action_data = Column('action_data_json', MutationObj.as_mutable(JsonType(dialect_map=dict(mysql=LONGTEXT()))))
1288
1288
1289 def __unicode__(self):
1289 def __unicode__(self):
1290 return u"<%s('id:%s:%s')>" % (
1290 return u"<%s('id:%s:%s')>" % (
1291 self.__class__.__name__, self.repository_name, self.action)
1291 self.__class__.__name__, self.repository_name, self.action)
1292
1292
1293 def __json__(self):
1293 def __json__(self):
1294 return {
1294 return {
1295 'user_id': self.user_id,
1295 'user_id': self.user_id,
1296 'username': self.username,
1296 'username': self.username,
1297 'repository_id': self.repository_id,
1297 'repository_id': self.repository_id,
1298 'repository_name': self.repository_name,
1298 'repository_name': self.repository_name,
1299 'user_ip': self.user_ip,
1299 'user_ip': self.user_ip,
1300 'action_date': self.action_date,
1300 'action_date': self.action_date,
1301 'action': self.action,
1301 'action': self.action,
1302 }
1302 }
1303
1303
1304 @hybrid_property
1304 @hybrid_property
1305 def entry_id(self):
1305 def entry_id(self):
1306 return self.user_log_id
1306 return self.user_log_id
1307
1307
1308 @property
1308 @property
1309 def action_as_day(self):
1309 def action_as_day(self):
1310 return datetime.date(*self.action_date.timetuple()[:3])
1310 return datetime.date(*self.action_date.timetuple()[:3])
1311
1311
1312 user = relationship('User')
1312 user = relationship('User')
1313 repository = relationship('Repository', cascade='')
1313 repository = relationship('Repository', cascade='')
1314
1314
1315
1315
1316 class UserGroup(Base, BaseModel):
1316 class UserGroup(Base, BaseModel):
1317 __tablename__ = 'users_groups'
1317 __tablename__ = 'users_groups'
1318 __table_args__ = (
1318 __table_args__ = (
1319 base_table_args,
1319 base_table_args,
1320 )
1320 )
1321
1321
1322 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1322 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1323 users_group_name = Column("users_group_name", String(255), nullable=False, unique=True, default=None)
1323 users_group_name = Column("users_group_name", String(255), nullable=False, unique=True, default=None)
1324 user_group_description = Column("user_group_description", String(10000), nullable=True, unique=None, default=None)
1324 user_group_description = Column("user_group_description", String(10000), nullable=True, unique=None, default=None)
1325 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
1325 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
1326 inherit_default_permissions = Column("users_group_inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
1326 inherit_default_permissions = Column("users_group_inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
1327 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
1327 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
1328 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1328 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1329 _group_data = Column("group_data", LargeBinary(), nullable=True) # JSON data
1329 _group_data = Column("group_data", LargeBinary(), nullable=True) # JSON data
1330
1330
1331 members = relationship('UserGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
1331 members = relationship('UserGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
1332 users_group_to_perm = relationship('UserGroupToPerm', cascade='all')
1332 users_group_to_perm = relationship('UserGroupToPerm', cascade='all')
1333 users_group_repo_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1333 users_group_repo_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1334 users_group_repo_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
1334 users_group_repo_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
1335 user_user_group_to_perm = relationship('UserUserGroupToPerm', cascade='all')
1335 user_user_group_to_perm = relationship('UserUserGroupToPerm', cascade='all')
1336 user_group_user_group_to_perm = relationship('UserGroupUserGroupToPerm ', primaryjoin="UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id", cascade='all')
1336 user_group_user_group_to_perm = relationship('UserGroupUserGroupToPerm ', primaryjoin="UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id", cascade='all')
1337
1337
1338 user_group_review_rules = relationship('RepoReviewRuleUserGroup', cascade='all')
1338 user_group_review_rules = relationship('RepoReviewRuleUserGroup', cascade='all')
1339 user = relationship('User', primaryjoin="User.user_id==UserGroup.user_id")
1339 user = relationship('User', primaryjoin="User.user_id==UserGroup.user_id")
1340
1340
1341 @classmethod
1341 @classmethod
1342 def _load_group_data(cls, column):
1342 def _load_group_data(cls, column):
1343 if not column:
1343 if not column:
1344 return {}
1344 return {}
1345
1345
1346 try:
1346 try:
1347 return json.loads(column) or {}
1347 return json.loads(column) or {}
1348 except TypeError:
1348 except TypeError:
1349 return {}
1349 return {}
1350
1350
1351 @hybrid_property
1351 @hybrid_property
1352 def description_safe(self):
1352 def description_safe(self):
1353 from rhodecode.lib import helpers as h
1353 from rhodecode.lib import helpers as h
1354 return h.escape(self.user_group_description)
1354 return h.escape(self.user_group_description)
1355
1355
1356 @hybrid_property
1356 @hybrid_property
1357 def group_data(self):
1357 def group_data(self):
1358 return self._load_group_data(self._group_data)
1358 return self._load_group_data(self._group_data)
1359
1359
1360 @group_data.expression
1360 @group_data.expression
1361 def group_data(self, **kwargs):
1361 def group_data(self, **kwargs):
1362 return self._group_data
1362 return self._group_data
1363
1363
1364 @group_data.setter
1364 @group_data.setter
1365 def group_data(self, val):
1365 def group_data(self, val):
1366 try:
1366 try:
1367 self._group_data = json.dumps(val)
1367 self._group_data = json.dumps(val)
1368 except Exception:
1368 except Exception:
1369 log.error(traceback.format_exc())
1369 log.error(traceback.format_exc())
1370
1370
1371 @classmethod
1371 @classmethod
1372 def _load_sync(cls, group_data):
1372 def _load_sync(cls, group_data):
1373 if group_data:
1373 if group_data:
1374 return group_data.get('extern_type')
1374 return group_data.get('extern_type')
1375
1375
1376 @property
1376 @property
1377 def sync(self):
1377 def sync(self):
1378 return self._load_sync(self.group_data)
1378 return self._load_sync(self.group_data)
1379
1379
1380 def __unicode__(self):
1380 def __unicode__(self):
1381 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
1381 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
1382 self.users_group_id,
1382 self.users_group_id,
1383 self.users_group_name)
1383 self.users_group_name)
1384
1384
1385 @classmethod
1385 @classmethod
1386 def get_by_group_name(cls, group_name, cache=False,
1386 def get_by_group_name(cls, group_name, cache=False,
1387 case_insensitive=False):
1387 case_insensitive=False):
1388 if case_insensitive:
1388 if case_insensitive:
1389 q = cls.query().filter(func.lower(cls.users_group_name) ==
1389 q = cls.query().filter(func.lower(cls.users_group_name) ==
1390 func.lower(group_name))
1390 func.lower(group_name))
1391
1391
1392 else:
1392 else:
1393 q = cls.query().filter(cls.users_group_name == group_name)
1393 q = cls.query().filter(cls.users_group_name == group_name)
1394 if cache:
1394 if cache:
1395 q = q.options(
1395 q = q.options(
1396 FromCache("sql_cache_short", "get_group_%s" % _hash_key(group_name)))
1396 FromCache("sql_cache_short", "get_group_%s" % _hash_key(group_name)))
1397 return q.scalar()
1397 return q.scalar()
1398
1398
1399 @classmethod
1399 @classmethod
1400 def get(cls, user_group_id, cache=False):
1400 def get(cls, user_group_id, cache=False):
1401 if not user_group_id:
1401 if not user_group_id:
1402 return
1402 return
1403
1403
1404 user_group = cls.query()
1404 user_group = cls.query()
1405 if cache:
1405 if cache:
1406 user_group = user_group.options(
1406 user_group = user_group.options(
1407 FromCache("sql_cache_short", "get_users_group_%s" % user_group_id))
1407 FromCache("sql_cache_short", "get_users_group_%s" % user_group_id))
1408 return user_group.get(user_group_id)
1408 return user_group.get(user_group_id)
1409
1409
1410 def permissions(self, with_admins=True, with_owner=True,
1410 def permissions(self, with_admins=True, with_owner=True,
1411 expand_from_user_groups=False):
1411 expand_from_user_groups=False):
1412 """
1412 """
1413 Permissions for user groups
1413 Permissions for user groups
1414 """
1414 """
1415 _admin_perm = 'usergroup.admin'
1415 _admin_perm = 'usergroup.admin'
1416
1416
1417 owner_row = []
1417 owner_row = []
1418 if with_owner:
1418 if with_owner:
1419 usr = AttributeDict(self.user.get_dict())
1419 usr = AttributeDict(self.user.get_dict())
1420 usr.owner_row = True
1420 usr.owner_row = True
1421 usr.permission = _admin_perm
1421 usr.permission = _admin_perm
1422 owner_row.append(usr)
1422 owner_row.append(usr)
1423
1423
1424 super_admin_ids = []
1424 super_admin_ids = []
1425 super_admin_rows = []
1425 super_admin_rows = []
1426 if with_admins:
1426 if with_admins:
1427 for usr in User.get_all_super_admins():
1427 for usr in User.get_all_super_admins():
1428 super_admin_ids.append(usr.user_id)
1428 super_admin_ids.append(usr.user_id)
1429 # if this admin is also owner, don't double the record
1429 # if this admin is also owner, don't double the record
1430 if usr.user_id == owner_row[0].user_id:
1430 if usr.user_id == owner_row[0].user_id:
1431 owner_row[0].admin_row = True
1431 owner_row[0].admin_row = True
1432 else:
1432 else:
1433 usr = AttributeDict(usr.get_dict())
1433 usr = AttributeDict(usr.get_dict())
1434 usr.admin_row = True
1434 usr.admin_row = True
1435 usr.permission = _admin_perm
1435 usr.permission = _admin_perm
1436 super_admin_rows.append(usr)
1436 super_admin_rows.append(usr)
1437
1437
1438 q = UserUserGroupToPerm.query().filter(UserUserGroupToPerm.user_group == self)
1438 q = UserUserGroupToPerm.query().filter(UserUserGroupToPerm.user_group == self)
1439 q = q.options(joinedload(UserUserGroupToPerm.user_group),
1439 q = q.options(joinedload(UserUserGroupToPerm.user_group),
1440 joinedload(UserUserGroupToPerm.user),
1440 joinedload(UserUserGroupToPerm.user),
1441 joinedload(UserUserGroupToPerm.permission),)
1441 joinedload(UserUserGroupToPerm.permission),)
1442
1442
1443 # get owners and admins and permissions. We do a trick of re-writing
1443 # get owners and admins and permissions. We do a trick of re-writing
1444 # objects from sqlalchemy to named-tuples due to sqlalchemy session
1444 # objects from sqlalchemy to named-tuples due to sqlalchemy session
1445 # has a global reference and changing one object propagates to all
1445 # has a global reference and changing one object propagates to all
1446 # others. This means if admin is also an owner admin_row that change
1446 # others. This means if admin is also an owner admin_row that change
1447 # would propagate to both objects
1447 # would propagate to both objects
1448 perm_rows = []
1448 perm_rows = []
1449 for _usr in q.all():
1449 for _usr in q.all():
1450 usr = AttributeDict(_usr.user.get_dict())
1450 usr = AttributeDict(_usr.user.get_dict())
1451 # if this user is also owner/admin, mark as duplicate record
1451 # if this user is also owner/admin, mark as duplicate record
1452 if usr.user_id == owner_row[0].user_id or usr.user_id in super_admin_ids:
1452 if usr.user_id == owner_row[0].user_id or usr.user_id in super_admin_ids:
1453 usr.duplicate_perm = True
1453 usr.duplicate_perm = True
1454 usr.permission = _usr.permission.permission_name
1454 usr.permission = _usr.permission.permission_name
1455 perm_rows.append(usr)
1455 perm_rows.append(usr)
1456
1456
1457 # filter the perm rows by 'default' first and then sort them by
1457 # filter the perm rows by 'default' first and then sort them by
1458 # admin,write,read,none permissions sorted again alphabetically in
1458 # admin,write,read,none permissions sorted again alphabetically in
1459 # each group
1459 # each group
1460 perm_rows = sorted(perm_rows, key=display_user_sort)
1460 perm_rows = sorted(perm_rows, key=display_user_sort)
1461
1461
1462 user_groups_rows = []
1462 user_groups_rows = []
1463 if expand_from_user_groups:
1463 if expand_from_user_groups:
1464 for ug in self.permission_user_groups(with_members=True):
1464 for ug in self.permission_user_groups(with_members=True):
1465 for user_data in ug.members:
1465 for user_data in ug.members:
1466 user_groups_rows.append(user_data)
1466 user_groups_rows.append(user_data)
1467
1467
1468 return super_admin_rows + owner_row + perm_rows + user_groups_rows
1468 return super_admin_rows + owner_row + perm_rows + user_groups_rows
1469
1469
1470 def permission_user_groups(self, with_members=False):
1470 def permission_user_groups(self, with_members=False):
1471 q = UserGroupUserGroupToPerm.query()\
1471 q = UserGroupUserGroupToPerm.query()\
1472 .filter(UserGroupUserGroupToPerm.target_user_group == self)
1472 .filter(UserGroupUserGroupToPerm.target_user_group == self)
1473 q = q.options(joinedload(UserGroupUserGroupToPerm.user_group),
1473 q = q.options(joinedload(UserGroupUserGroupToPerm.user_group),
1474 joinedload(UserGroupUserGroupToPerm.target_user_group),
1474 joinedload(UserGroupUserGroupToPerm.target_user_group),
1475 joinedload(UserGroupUserGroupToPerm.permission),)
1475 joinedload(UserGroupUserGroupToPerm.permission),)
1476
1476
1477 perm_rows = []
1477 perm_rows = []
1478 for _user_group in q.all():
1478 for _user_group in q.all():
1479 entry = AttributeDict(_user_group.user_group.get_dict())
1479 entry = AttributeDict(_user_group.user_group.get_dict())
1480 entry.permission = _user_group.permission.permission_name
1480 entry.permission = _user_group.permission.permission_name
1481 if with_members:
1481 if with_members:
1482 entry.members = [x.user.get_dict()
1482 entry.members = [x.user.get_dict()
1483 for x in _user_group.user_group.members]
1483 for x in _user_group.user_group.members]
1484 perm_rows.append(entry)
1484 perm_rows.append(entry)
1485
1485
1486 perm_rows = sorted(perm_rows, key=display_user_group_sort)
1486 perm_rows = sorted(perm_rows, key=display_user_group_sort)
1487 return perm_rows
1487 return perm_rows
1488
1488
1489 def _get_default_perms(self, user_group, suffix=''):
1489 def _get_default_perms(self, user_group, suffix=''):
1490 from rhodecode.model.permission import PermissionModel
1490 from rhodecode.model.permission import PermissionModel
1491 return PermissionModel().get_default_perms(user_group.users_group_to_perm, suffix)
1491 return PermissionModel().get_default_perms(user_group.users_group_to_perm, suffix)
1492
1492
1493 def get_default_perms(self, suffix=''):
1493 def get_default_perms(self, suffix=''):
1494 return self._get_default_perms(self, suffix)
1494 return self._get_default_perms(self, suffix)
1495
1495
1496 def get_api_data(self, with_group_members=True, include_secrets=False):
1496 def get_api_data(self, with_group_members=True, include_secrets=False):
1497 """
1497 """
1498 :param include_secrets: See :meth:`User.get_api_data`, this parameter is
1498 :param include_secrets: See :meth:`User.get_api_data`, this parameter is
1499 basically forwarded.
1499 basically forwarded.
1500
1500
1501 """
1501 """
1502 user_group = self
1502 user_group = self
1503 data = {
1503 data = {
1504 'users_group_id': user_group.users_group_id,
1504 'users_group_id': user_group.users_group_id,
1505 'group_name': user_group.users_group_name,
1505 'group_name': user_group.users_group_name,
1506 'group_description': user_group.user_group_description,
1506 'group_description': user_group.user_group_description,
1507 'active': user_group.users_group_active,
1507 'active': user_group.users_group_active,
1508 'owner': user_group.user.username,
1508 'owner': user_group.user.username,
1509 'sync': user_group.sync,
1509 'sync': user_group.sync,
1510 'owner_email': user_group.user.email,
1510 'owner_email': user_group.user.email,
1511 }
1511 }
1512
1512
1513 if with_group_members:
1513 if with_group_members:
1514 users = []
1514 users = []
1515 for user in user_group.members:
1515 for user in user_group.members:
1516 user = user.user
1516 user = user.user
1517 users.append(user.get_api_data(include_secrets=include_secrets))
1517 users.append(user.get_api_data(include_secrets=include_secrets))
1518 data['users'] = users
1518 data['users'] = users
1519
1519
1520 return data
1520 return data
1521
1521
1522
1522
1523 class UserGroupMember(Base, BaseModel):
1523 class UserGroupMember(Base, BaseModel):
1524 __tablename__ = 'users_groups_members'
1524 __tablename__ = 'users_groups_members'
1525 __table_args__ = (
1525 __table_args__ = (
1526 base_table_args,
1526 base_table_args,
1527 )
1527 )
1528
1528
1529 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1529 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1530 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1530 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1531 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1531 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1532
1532
1533 user = relationship('User', lazy='joined')
1533 user = relationship('User', lazy='joined')
1534 users_group = relationship('UserGroup')
1534 users_group = relationship('UserGroup')
1535
1535
1536 def __init__(self, gr_id='', u_id=''):
1536 def __init__(self, gr_id='', u_id=''):
1537 self.users_group_id = gr_id
1537 self.users_group_id = gr_id
1538 self.user_id = u_id
1538 self.user_id = u_id
1539
1539
1540
1540
1541 class RepositoryField(Base, BaseModel):
1541 class RepositoryField(Base, BaseModel):
1542 __tablename__ = 'repositories_fields'
1542 __tablename__ = 'repositories_fields'
1543 __table_args__ = (
1543 __table_args__ = (
1544 UniqueConstraint('repository_id', 'field_key'), # no-multi field
1544 UniqueConstraint('repository_id', 'field_key'), # no-multi field
1545 base_table_args,
1545 base_table_args,
1546 )
1546 )
1547
1547
1548 PREFIX = 'ex_' # prefix used in form to not conflict with already existing fields
1548 PREFIX = 'ex_' # prefix used in form to not conflict with already existing fields
1549
1549
1550 repo_field_id = Column("repo_field_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1550 repo_field_id = Column("repo_field_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1551 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1551 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1552 field_key = Column("field_key", String(250))
1552 field_key = Column("field_key", String(250))
1553 field_label = Column("field_label", String(1024), nullable=False)
1553 field_label = Column("field_label", String(1024), nullable=False)
1554 field_value = Column("field_value", String(10000), nullable=False)
1554 field_value = Column("field_value", String(10000), nullable=False)
1555 field_desc = Column("field_desc", String(1024), nullable=False)
1555 field_desc = Column("field_desc", String(1024), nullable=False)
1556 field_type = Column("field_type", String(255), nullable=False, unique=None)
1556 field_type = Column("field_type", String(255), nullable=False, unique=None)
1557 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1557 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1558
1558
1559 repository = relationship('Repository')
1559 repository = relationship('Repository')
1560
1560
1561 @property
1561 @property
1562 def field_key_prefixed(self):
1562 def field_key_prefixed(self):
1563 return 'ex_%s' % self.field_key
1563 return 'ex_%s' % self.field_key
1564
1564
1565 @classmethod
1565 @classmethod
1566 def un_prefix_key(cls, key):
1566 def un_prefix_key(cls, key):
1567 if key.startswith(cls.PREFIX):
1567 if key.startswith(cls.PREFIX):
1568 return key[len(cls.PREFIX):]
1568 return key[len(cls.PREFIX):]
1569 return key
1569 return key
1570
1570
1571 @classmethod
1571 @classmethod
1572 def get_by_key_name(cls, key, repo):
1572 def get_by_key_name(cls, key, repo):
1573 row = cls.query()\
1573 row = cls.query()\
1574 .filter(cls.repository == repo)\
1574 .filter(cls.repository == repo)\
1575 .filter(cls.field_key == key).scalar()
1575 .filter(cls.field_key == key).scalar()
1576 return row
1576 return row
1577
1577
1578
1578
1579 class Repository(Base, BaseModel):
1579 class Repository(Base, BaseModel):
1580 __tablename__ = 'repositories'
1580 __tablename__ = 'repositories'
1581 __table_args__ = (
1581 __table_args__ = (
1582 Index('r_repo_name_idx', 'repo_name', mysql_length=255),
1582 Index('r_repo_name_idx', 'repo_name', mysql_length=255),
1583 base_table_args,
1583 base_table_args,
1584 )
1584 )
1585 DEFAULT_CLONE_URI = '{scheme}://{user}@{netloc}/{repo}'
1585 DEFAULT_CLONE_URI = '{scheme}://{user}@{netloc}/{repo}'
1586 DEFAULT_CLONE_URI_ID = '{scheme}://{user}@{netloc}/_{repoid}'
1586 DEFAULT_CLONE_URI_ID = '{scheme}://{user}@{netloc}/_{repoid}'
1587 DEFAULT_CLONE_URI_SSH = 'ssh://{sys_user}@{hostname}/{repo}'
1587 DEFAULT_CLONE_URI_SSH = 'ssh://{sys_user}@{hostname}/{repo}'
1588
1588
1589 STATE_CREATED = 'repo_state_created'
1589 STATE_CREATED = 'repo_state_created'
1590 STATE_PENDING = 'repo_state_pending'
1590 STATE_PENDING = 'repo_state_pending'
1591 STATE_ERROR = 'repo_state_error'
1591 STATE_ERROR = 'repo_state_error'
1592
1592
1593 LOCK_AUTOMATIC = 'lock_auto'
1593 LOCK_AUTOMATIC = 'lock_auto'
1594 LOCK_API = 'lock_api'
1594 LOCK_API = 'lock_api'
1595 LOCK_WEB = 'lock_web'
1595 LOCK_WEB = 'lock_web'
1596 LOCK_PULL = 'lock_pull'
1596 LOCK_PULL = 'lock_pull'
1597
1597
1598 NAME_SEP = URL_SEP
1598 NAME_SEP = URL_SEP
1599
1599
1600 repo_id = Column(
1600 repo_id = Column(
1601 "repo_id", Integer(), nullable=False, unique=True, default=None,
1601 "repo_id", Integer(), nullable=False, unique=True, default=None,
1602 primary_key=True)
1602 primary_key=True)
1603 _repo_name = Column(
1603 _repo_name = Column(
1604 "repo_name", Text(), nullable=False, default=None)
1604 "repo_name", Text(), nullable=False, default=None)
1605 _repo_name_hash = Column(
1605 _repo_name_hash = Column(
1606 "repo_name_hash", String(255), nullable=False, unique=True)
1606 "repo_name_hash", String(255), nullable=False, unique=True)
1607 repo_state = Column("repo_state", String(255), nullable=True)
1607 repo_state = Column("repo_state", String(255), nullable=True)
1608
1608
1609 clone_uri = Column(
1609 clone_uri = Column(
1610 "clone_uri", EncryptedTextValue(), nullable=True, unique=False,
1610 "clone_uri", EncryptedTextValue(), nullable=True, unique=False,
1611 default=None)
1611 default=None)
1612 push_uri = Column(
1612 push_uri = Column(
1613 "push_uri", EncryptedTextValue(), nullable=True, unique=False,
1613 "push_uri", EncryptedTextValue(), nullable=True, unique=False,
1614 default=None)
1614 default=None)
1615 repo_type = Column(
1615 repo_type = Column(
1616 "repo_type", String(255), nullable=False, unique=False, default=None)
1616 "repo_type", String(255), nullable=False, unique=False, default=None)
1617 user_id = Column(
1617 user_id = Column(
1618 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
1618 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
1619 unique=False, default=None)
1619 unique=False, default=None)
1620 private = Column(
1620 private = Column(
1621 "private", Boolean(), nullable=True, unique=None, default=None)
1621 "private", Boolean(), nullable=True, unique=None, default=None)
1622 archived = Column(
1622 archived = Column(
1623 "archived", Boolean(), nullable=True, unique=None, default=None)
1623 "archived", Boolean(), nullable=True, unique=None, default=None)
1624 enable_statistics = Column(
1624 enable_statistics = Column(
1625 "statistics", Boolean(), nullable=True, unique=None, default=True)
1625 "statistics", Boolean(), nullable=True, unique=None, default=True)
1626 enable_downloads = Column(
1626 enable_downloads = Column(
1627 "downloads", Boolean(), nullable=True, unique=None, default=True)
1627 "downloads", Boolean(), nullable=True, unique=None, default=True)
1628 description = Column(
1628 description = Column(
1629 "description", String(10000), nullable=True, unique=None, default=None)
1629 "description", String(10000), nullable=True, unique=None, default=None)
1630 created_on = Column(
1630 created_on = Column(
1631 'created_on', DateTime(timezone=False), nullable=True, unique=None,
1631 'created_on', DateTime(timezone=False), nullable=True, unique=None,
1632 default=datetime.datetime.now)
1632 default=datetime.datetime.now)
1633 updated_on = Column(
1633 updated_on = Column(
1634 'updated_on', DateTime(timezone=False), nullable=True, unique=None,
1634 'updated_on', DateTime(timezone=False), nullable=True, unique=None,
1635 default=datetime.datetime.now)
1635 default=datetime.datetime.now)
1636 _landing_revision = Column(
1636 _landing_revision = Column(
1637 "landing_revision", String(255), nullable=False, unique=False,
1637 "landing_revision", String(255), nullable=False, unique=False,
1638 default=None)
1638 default=None)
1639 enable_locking = Column(
1639 enable_locking = Column(
1640 "enable_locking", Boolean(), nullable=False, unique=None,
1640 "enable_locking", Boolean(), nullable=False, unique=None,
1641 default=False)
1641 default=False)
1642 _locked = Column(
1642 _locked = Column(
1643 "locked", String(255), nullable=True, unique=False, default=None)
1643 "locked", String(255), nullable=True, unique=False, default=None)
1644 _changeset_cache = Column(
1644 _changeset_cache = Column(
1645 "changeset_cache", LargeBinary(), nullable=True) # JSON data
1645 "changeset_cache", LargeBinary(), nullable=True) # JSON data
1646
1646
1647 fork_id = Column(
1647 fork_id = Column(
1648 "fork_id", Integer(), ForeignKey('repositories.repo_id'),
1648 "fork_id", Integer(), ForeignKey('repositories.repo_id'),
1649 nullable=True, unique=False, default=None)
1649 nullable=True, unique=False, default=None)
1650 group_id = Column(
1650 group_id = Column(
1651 "group_id", Integer(), ForeignKey('groups.group_id'), nullable=True,
1651 "group_id", Integer(), ForeignKey('groups.group_id'), nullable=True,
1652 unique=False, default=None)
1652 unique=False, default=None)
1653
1653
1654 user = relationship('User', lazy='joined')
1654 user = relationship('User', lazy='joined')
1655 fork = relationship('Repository', remote_side=repo_id, lazy='joined')
1655 fork = relationship('Repository', remote_side=repo_id, lazy='joined')
1656 group = relationship('RepoGroup', lazy='joined')
1656 group = relationship('RepoGroup', lazy='joined')
1657 repo_to_perm = relationship(
1657 repo_to_perm = relationship(
1658 'UserRepoToPerm', cascade='all',
1658 'UserRepoToPerm', cascade='all',
1659 order_by='UserRepoToPerm.repo_to_perm_id')
1659 order_by='UserRepoToPerm.repo_to_perm_id')
1660 users_group_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1660 users_group_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1661 stats = relationship('Statistics', cascade='all', uselist=False)
1661 stats = relationship('Statistics', cascade='all', uselist=False)
1662
1662
1663 followers = relationship(
1663 followers = relationship(
1664 'UserFollowing',
1664 'UserFollowing',
1665 primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id',
1665 primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id',
1666 cascade='all')
1666 cascade='all')
1667 extra_fields = relationship(
1667 extra_fields = relationship(
1668 'RepositoryField', cascade="all, delete, delete-orphan")
1668 'RepositoryField', cascade="all, delete, delete-orphan")
1669 logs = relationship('UserLog')
1669 logs = relationship('UserLog')
1670 comments = relationship(
1670 comments = relationship(
1671 'ChangesetComment', cascade="all, delete, delete-orphan")
1671 'ChangesetComment', cascade="all, delete, delete-orphan")
1672 pull_requests_source = relationship(
1672 pull_requests_source = relationship(
1673 'PullRequest',
1673 'PullRequest',
1674 primaryjoin='PullRequest.source_repo_id==Repository.repo_id',
1674 primaryjoin='PullRequest.source_repo_id==Repository.repo_id',
1675 cascade="all, delete, delete-orphan")
1675 cascade="all, delete, delete-orphan")
1676 pull_requests_target = relationship(
1676 pull_requests_target = relationship(
1677 'PullRequest',
1677 'PullRequest',
1678 primaryjoin='PullRequest.target_repo_id==Repository.repo_id',
1678 primaryjoin='PullRequest.target_repo_id==Repository.repo_id',
1679 cascade="all, delete, delete-orphan")
1679 cascade="all, delete, delete-orphan")
1680 ui = relationship('RepoRhodeCodeUi', cascade="all")
1680 ui = relationship('RepoRhodeCodeUi', cascade="all")
1681 settings = relationship('RepoRhodeCodeSetting', cascade="all")
1681 settings = relationship('RepoRhodeCodeSetting', cascade="all")
1682 integrations = relationship('Integration', cascade="all, delete, delete-orphan")
1682 integrations = relationship('Integration', cascade="all, delete, delete-orphan")
1683
1683
1684 scoped_tokens = relationship('UserApiKeys', cascade="all")
1684 scoped_tokens = relationship('UserApiKeys', cascade="all")
1685
1685
1686 artifacts = relationship('FileStore', cascade="all")
1686 artifacts = relationship('FileStore', cascade="all")
1687
1687
1688 def __unicode__(self):
1688 def __unicode__(self):
1689 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id,
1689 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id,
1690 safe_unicode(self.repo_name))
1690 safe_unicode(self.repo_name))
1691
1691
1692 @hybrid_property
1692 @hybrid_property
1693 def description_safe(self):
1693 def description_safe(self):
1694 from rhodecode.lib import helpers as h
1694 from rhodecode.lib import helpers as h
1695 return h.escape(self.description)
1695 return h.escape(self.description)
1696
1696
1697 @hybrid_property
1697 @hybrid_property
1698 def landing_rev(self):
1698 def landing_rev(self):
1699 # always should return [rev_type, rev]
1699 # always should return [rev_type, rev]
1700 if self._landing_revision:
1700 if self._landing_revision:
1701 _rev_info = self._landing_revision.split(':')
1701 _rev_info = self._landing_revision.split(':')
1702 if len(_rev_info) < 2:
1702 if len(_rev_info) < 2:
1703 _rev_info.insert(0, 'rev')
1703 _rev_info.insert(0, 'rev')
1704 return [_rev_info[0], _rev_info[1]]
1704 return [_rev_info[0], _rev_info[1]]
1705 return [None, None]
1705 return [None, None]
1706
1706
1707 @landing_rev.setter
1707 @landing_rev.setter
1708 def landing_rev(self, val):
1708 def landing_rev(self, val):
1709 if ':' not in val:
1709 if ':' not in val:
1710 raise ValueError('value must be delimited with `:` and consist '
1710 raise ValueError('value must be delimited with `:` and consist '
1711 'of <rev_type>:<rev>, got %s instead' % val)
1711 'of <rev_type>:<rev>, got %s instead' % val)
1712 self._landing_revision = val
1712 self._landing_revision = val
1713
1713
1714 @hybrid_property
1714 @hybrid_property
1715 def locked(self):
1715 def locked(self):
1716 if self._locked:
1716 if self._locked:
1717 user_id, timelocked, reason = self._locked.split(':')
1717 user_id, timelocked, reason = self._locked.split(':')
1718 lock_values = int(user_id), timelocked, reason
1718 lock_values = int(user_id), timelocked, reason
1719 else:
1719 else:
1720 lock_values = [None, None, None]
1720 lock_values = [None, None, None]
1721 return lock_values
1721 return lock_values
1722
1722
1723 @locked.setter
1723 @locked.setter
1724 def locked(self, val):
1724 def locked(self, val):
1725 if val and isinstance(val, (list, tuple)):
1725 if val and isinstance(val, (list, tuple)):
1726 self._locked = ':'.join(map(str, val))
1726 self._locked = ':'.join(map(str, val))
1727 else:
1727 else:
1728 self._locked = None
1728 self._locked = None
1729
1729
1730 @hybrid_property
1730 @hybrid_property
1731 def changeset_cache(self):
1731 def changeset_cache(self):
1732 from rhodecode.lib.vcs.backends.base import EmptyCommit
1732 from rhodecode.lib.vcs.backends.base import EmptyCommit
1733 dummy = EmptyCommit().__json__()
1733 dummy = EmptyCommit().__json__()
1734 if not self._changeset_cache:
1734 if not self._changeset_cache:
1735 dummy['source_repo_id'] = self.repo_id
1735 dummy['source_repo_id'] = self.repo_id
1736 return json.loads(json.dumps(dummy))
1736 return json.loads(json.dumps(dummy))
1737
1737
1738 try:
1738 try:
1739 return json.loads(self._changeset_cache)
1739 return json.loads(self._changeset_cache)
1740 except TypeError:
1740 except TypeError:
1741 return dummy
1741 return dummy
1742 except Exception:
1742 except Exception:
1743 log.error(traceback.format_exc())
1743 log.error(traceback.format_exc())
1744 return dummy
1744 return dummy
1745
1745
1746 @changeset_cache.setter
1746 @changeset_cache.setter
1747 def changeset_cache(self, val):
1747 def changeset_cache(self, val):
1748 try:
1748 try:
1749 self._changeset_cache = json.dumps(val)
1749 self._changeset_cache = json.dumps(val)
1750 except Exception:
1750 except Exception:
1751 log.error(traceback.format_exc())
1751 log.error(traceback.format_exc())
1752
1752
1753 @hybrid_property
1753 @hybrid_property
1754 def repo_name(self):
1754 def repo_name(self):
1755 return self._repo_name
1755 return self._repo_name
1756
1756
1757 @repo_name.setter
1757 @repo_name.setter
1758 def repo_name(self, value):
1758 def repo_name(self, value):
1759 self._repo_name = value
1759 self._repo_name = value
1760 self._repo_name_hash = hashlib.sha1(safe_str(value)).hexdigest()
1760 self._repo_name_hash = hashlib.sha1(safe_str(value)).hexdigest()
1761
1761
1762 @classmethod
1762 @classmethod
1763 def normalize_repo_name(cls, repo_name):
1763 def normalize_repo_name(cls, repo_name):
1764 """
1764 """
1765 Normalizes os specific repo_name to the format internally stored inside
1765 Normalizes os specific repo_name to the format internally stored inside
1766 database using URL_SEP
1766 database using URL_SEP
1767
1767
1768 :param cls:
1768 :param cls:
1769 :param repo_name:
1769 :param repo_name:
1770 """
1770 """
1771 return cls.NAME_SEP.join(repo_name.split(os.sep))
1771 return cls.NAME_SEP.join(repo_name.split(os.sep))
1772
1772
1773 @classmethod
1773 @classmethod
1774 def get_by_repo_name(cls, repo_name, cache=False, identity_cache=False):
1774 def get_by_repo_name(cls, repo_name, cache=False, identity_cache=False):
1775 session = Session()
1775 session = Session()
1776 q = session.query(cls).filter(cls.repo_name == repo_name)
1776 q = session.query(cls).filter(cls.repo_name == repo_name)
1777
1777
1778 if cache:
1778 if cache:
1779 if identity_cache:
1779 if identity_cache:
1780 val = cls.identity_cache(session, 'repo_name', repo_name)
1780 val = cls.identity_cache(session, 'repo_name', repo_name)
1781 if val:
1781 if val:
1782 return val
1782 return val
1783 else:
1783 else:
1784 cache_key = "get_repo_by_name_%s" % _hash_key(repo_name)
1784 cache_key = "get_repo_by_name_%s" % _hash_key(repo_name)
1785 q = q.options(
1785 q = q.options(
1786 FromCache("sql_cache_short", cache_key))
1786 FromCache("sql_cache_short", cache_key))
1787
1787
1788 return q.scalar()
1788 return q.scalar()
1789
1789
1790 @classmethod
1790 @classmethod
1791 def get_by_id_or_repo_name(cls, repoid):
1791 def get_by_id_or_repo_name(cls, repoid):
1792 if isinstance(repoid, (int, long)):
1792 if isinstance(repoid, (int, long)):
1793 try:
1793 try:
1794 repo = cls.get(repoid)
1794 repo = cls.get(repoid)
1795 except ValueError:
1795 except ValueError:
1796 repo = None
1796 repo = None
1797 else:
1797 else:
1798 repo = cls.get_by_repo_name(repoid)
1798 repo = cls.get_by_repo_name(repoid)
1799 return repo
1799 return repo
1800
1800
1801 @classmethod
1801 @classmethod
1802 def get_by_full_path(cls, repo_full_path):
1802 def get_by_full_path(cls, repo_full_path):
1803 repo_name = repo_full_path.split(cls.base_path(), 1)[-1]
1803 repo_name = repo_full_path.split(cls.base_path(), 1)[-1]
1804 repo_name = cls.normalize_repo_name(repo_name)
1804 repo_name = cls.normalize_repo_name(repo_name)
1805 return cls.get_by_repo_name(repo_name.strip(URL_SEP))
1805 return cls.get_by_repo_name(repo_name.strip(URL_SEP))
1806
1806
1807 @classmethod
1807 @classmethod
1808 def get_repo_forks(cls, repo_id):
1808 def get_repo_forks(cls, repo_id):
1809 return cls.query().filter(Repository.fork_id == repo_id)
1809 return cls.query().filter(Repository.fork_id == repo_id)
1810
1810
1811 @classmethod
1811 @classmethod
1812 def base_path(cls):
1812 def base_path(cls):
1813 """
1813 """
1814 Returns base path when all repos are stored
1814 Returns base path when all repos are stored
1815
1815
1816 :param cls:
1816 :param cls:
1817 """
1817 """
1818 q = Session().query(RhodeCodeUi)\
1818 q = Session().query(RhodeCodeUi)\
1819 .filter(RhodeCodeUi.ui_key == cls.NAME_SEP)
1819 .filter(RhodeCodeUi.ui_key == cls.NAME_SEP)
1820 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1820 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1821 return q.one().ui_value
1821 return q.one().ui_value
1822
1822
1823 @classmethod
1823 @classmethod
1824 def get_all_repos(cls, user_id=Optional(None), group_id=Optional(None),
1824 def get_all_repos(cls, user_id=Optional(None), group_id=Optional(None),
1825 case_insensitive=True, archived=False):
1825 case_insensitive=True, archived=False):
1826 q = Repository.query()
1826 q = Repository.query()
1827
1827
1828 if not archived:
1828 if not archived:
1829 q = q.filter(Repository.archived.isnot(true()))
1829 q = q.filter(Repository.archived.isnot(true()))
1830
1830
1831 if not isinstance(user_id, Optional):
1831 if not isinstance(user_id, Optional):
1832 q = q.filter(Repository.user_id == user_id)
1832 q = q.filter(Repository.user_id == user_id)
1833
1833
1834 if not isinstance(group_id, Optional):
1834 if not isinstance(group_id, Optional):
1835 q = q.filter(Repository.group_id == group_id)
1835 q = q.filter(Repository.group_id == group_id)
1836
1836
1837 if case_insensitive:
1837 if case_insensitive:
1838 q = q.order_by(func.lower(Repository.repo_name))
1838 q = q.order_by(func.lower(Repository.repo_name))
1839 else:
1839 else:
1840 q = q.order_by(Repository.repo_name)
1840 q = q.order_by(Repository.repo_name)
1841
1841
1842 return q.all()
1842 return q.all()
1843
1843
1844 @property
1844 @property
1845 def repo_uid(self):
1845 def repo_uid(self):
1846 return '_{}'.format(self.repo_id)
1846 return '_{}'.format(self.repo_id)
1847
1847
1848 @property
1848 @property
1849 def forks(self):
1849 def forks(self):
1850 """
1850 """
1851 Return forks of this repo
1851 Return forks of this repo
1852 """
1852 """
1853 return Repository.get_repo_forks(self.repo_id)
1853 return Repository.get_repo_forks(self.repo_id)
1854
1854
1855 @property
1855 @property
1856 def parent(self):
1856 def parent(self):
1857 """
1857 """
1858 Returns fork parent
1858 Returns fork parent
1859 """
1859 """
1860 return self.fork
1860 return self.fork
1861
1861
1862 @property
1862 @property
1863 def just_name(self):
1863 def just_name(self):
1864 return self.repo_name.split(self.NAME_SEP)[-1]
1864 return self.repo_name.split(self.NAME_SEP)[-1]
1865
1865
1866 @property
1866 @property
1867 def groups_with_parents(self):
1867 def groups_with_parents(self):
1868 groups = []
1868 groups = []
1869 if self.group is None:
1869 if self.group is None:
1870 return groups
1870 return groups
1871
1871
1872 cur_gr = self.group
1872 cur_gr = self.group
1873 groups.insert(0, cur_gr)
1873 groups.insert(0, cur_gr)
1874 while 1:
1874 while 1:
1875 gr = getattr(cur_gr, 'parent_group', None)
1875 gr = getattr(cur_gr, 'parent_group', None)
1876 cur_gr = cur_gr.parent_group
1876 cur_gr = cur_gr.parent_group
1877 if gr is None:
1877 if gr is None:
1878 break
1878 break
1879 groups.insert(0, gr)
1879 groups.insert(0, gr)
1880
1880
1881 return groups
1881 return groups
1882
1882
1883 @property
1883 @property
1884 def groups_and_repo(self):
1884 def groups_and_repo(self):
1885 return self.groups_with_parents, self
1885 return self.groups_with_parents, self
1886
1886
1887 @LazyProperty
1887 @LazyProperty
1888 def repo_path(self):
1888 def repo_path(self):
1889 """
1889 """
1890 Returns base full path for that repository means where it actually
1890 Returns base full path for that repository means where it actually
1891 exists on a filesystem
1891 exists on a filesystem
1892 """
1892 """
1893 q = Session().query(RhodeCodeUi).filter(
1893 q = Session().query(RhodeCodeUi).filter(
1894 RhodeCodeUi.ui_key == self.NAME_SEP)
1894 RhodeCodeUi.ui_key == self.NAME_SEP)
1895 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1895 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1896 return q.one().ui_value
1896 return q.one().ui_value
1897
1897
1898 @property
1898 @property
1899 def repo_full_path(self):
1899 def repo_full_path(self):
1900 p = [self.repo_path]
1900 p = [self.repo_path]
1901 # we need to split the name by / since this is how we store the
1901 # we need to split the name by / since this is how we store the
1902 # names in the database, but that eventually needs to be converted
1902 # names in the database, but that eventually needs to be converted
1903 # into a valid system path
1903 # into a valid system path
1904 p += self.repo_name.split(self.NAME_SEP)
1904 p += self.repo_name.split(self.NAME_SEP)
1905 return os.path.join(*map(safe_unicode, p))
1905 return os.path.join(*map(safe_unicode, p))
1906
1906
1907 @property
1907 @property
1908 def cache_keys(self):
1908 def cache_keys(self):
1909 """
1909 """
1910 Returns associated cache keys for that repo
1910 Returns associated cache keys for that repo
1911 """
1911 """
1912 invalidation_namespace = CacheKey.REPO_INVALIDATION_NAMESPACE.format(
1912 invalidation_namespace = CacheKey.REPO_INVALIDATION_NAMESPACE.format(
1913 repo_id=self.repo_id)
1913 repo_id=self.repo_id)
1914 return CacheKey.query()\
1914 return CacheKey.query()\
1915 .filter(CacheKey.cache_args == invalidation_namespace)\
1915 .filter(CacheKey.cache_args == invalidation_namespace)\
1916 .order_by(CacheKey.cache_key)\
1916 .order_by(CacheKey.cache_key)\
1917 .all()
1917 .all()
1918
1918
1919 @property
1919 @property
1920 def cached_diffs_relative_dir(self):
1920 def cached_diffs_relative_dir(self):
1921 """
1921 """
1922 Return a relative to the repository store path of cached diffs
1922 Return a relative to the repository store path of cached diffs
1923 used for safe display for users, who shouldn't know the absolute store
1923 used for safe display for users, who shouldn't know the absolute store
1924 path
1924 path
1925 """
1925 """
1926 return os.path.join(
1926 return os.path.join(
1927 os.path.dirname(self.repo_name),
1927 os.path.dirname(self.repo_name),
1928 self.cached_diffs_dir.split(os.path.sep)[-1])
1928 self.cached_diffs_dir.split(os.path.sep)[-1])
1929
1929
1930 @property
1930 @property
1931 def cached_diffs_dir(self):
1931 def cached_diffs_dir(self):
1932 path = self.repo_full_path
1932 path = self.repo_full_path
1933 return os.path.join(
1933 return os.path.join(
1934 os.path.dirname(path),
1934 os.path.dirname(path),
1935 '.__shadow_diff_cache_repo_{}'.format(self.repo_id))
1935 '.__shadow_diff_cache_repo_{}'.format(self.repo_id))
1936
1936
1937 def cached_diffs(self):
1937 def cached_diffs(self):
1938 diff_cache_dir = self.cached_diffs_dir
1938 diff_cache_dir = self.cached_diffs_dir
1939 if os.path.isdir(diff_cache_dir):
1939 if os.path.isdir(diff_cache_dir):
1940 return os.listdir(diff_cache_dir)
1940 return os.listdir(diff_cache_dir)
1941 return []
1941 return []
1942
1942
1943 def shadow_repos(self):
1943 def shadow_repos(self):
1944 shadow_repos_pattern = '.__shadow_repo_{}'.format(self.repo_id)
1944 shadow_repos_pattern = '.__shadow_repo_{}'.format(self.repo_id)
1945 return [
1945 return [
1946 x for x in os.listdir(os.path.dirname(self.repo_full_path))
1946 x for x in os.listdir(os.path.dirname(self.repo_full_path))
1947 if x.startswith(shadow_repos_pattern)]
1947 if x.startswith(shadow_repos_pattern)]
1948
1948
1949 def get_new_name(self, repo_name):
1949 def get_new_name(self, repo_name):
1950 """
1950 """
1951 returns new full repository name based on assigned group and new new
1951 returns new full repository name based on assigned group and new new
1952
1952
1953 :param group_name:
1953 :param group_name:
1954 """
1954 """
1955 path_prefix = self.group.full_path_splitted if self.group else []
1955 path_prefix = self.group.full_path_splitted if self.group else []
1956 return self.NAME_SEP.join(path_prefix + [repo_name])
1956 return self.NAME_SEP.join(path_prefix + [repo_name])
1957
1957
1958 @property
1958 @property
1959 def _config(self):
1959 def _config(self):
1960 """
1960 """
1961 Returns db based config object.
1961 Returns db based config object.
1962 """
1962 """
1963 from rhodecode.lib.utils import make_db_config
1963 from rhodecode.lib.utils import make_db_config
1964 return make_db_config(clear_session=False, repo=self)
1964 return make_db_config(clear_session=False, repo=self)
1965
1965
1966 def permissions(self, with_admins=True, with_owner=True,
1966 def permissions(self, with_admins=True, with_owner=True,
1967 expand_from_user_groups=False):
1967 expand_from_user_groups=False):
1968 """
1968 """
1969 Permissions for repositories
1969 Permissions for repositories
1970 """
1970 """
1971 _admin_perm = 'repository.admin'
1971 _admin_perm = 'repository.admin'
1972
1972
1973 owner_row = []
1973 owner_row = []
1974 if with_owner:
1974 if with_owner:
1975 usr = AttributeDict(self.user.get_dict())
1975 usr = AttributeDict(self.user.get_dict())
1976 usr.owner_row = True
1976 usr.owner_row = True
1977 usr.permission = _admin_perm
1977 usr.permission = _admin_perm
1978 usr.permission_id = None
1978 usr.permission_id = None
1979 owner_row.append(usr)
1979 owner_row.append(usr)
1980
1980
1981 super_admin_ids = []
1981 super_admin_ids = []
1982 super_admin_rows = []
1982 super_admin_rows = []
1983 if with_admins:
1983 if with_admins:
1984 for usr in User.get_all_super_admins():
1984 for usr in User.get_all_super_admins():
1985 super_admin_ids.append(usr.user_id)
1985 super_admin_ids.append(usr.user_id)
1986 # if this admin is also owner, don't double the record
1986 # if this admin is also owner, don't double the record
1987 if usr.user_id == owner_row[0].user_id:
1987 if usr.user_id == owner_row[0].user_id:
1988 owner_row[0].admin_row = True
1988 owner_row[0].admin_row = True
1989 else:
1989 else:
1990 usr = AttributeDict(usr.get_dict())
1990 usr = AttributeDict(usr.get_dict())
1991 usr.admin_row = True
1991 usr.admin_row = True
1992 usr.permission = _admin_perm
1992 usr.permission = _admin_perm
1993 usr.permission_id = None
1993 usr.permission_id = None
1994 super_admin_rows.append(usr)
1994 super_admin_rows.append(usr)
1995
1995
1996 q = UserRepoToPerm.query().filter(UserRepoToPerm.repository == self)
1996 q = UserRepoToPerm.query().filter(UserRepoToPerm.repository == self)
1997 q = q.options(joinedload(UserRepoToPerm.repository),
1997 q = q.options(joinedload(UserRepoToPerm.repository),
1998 joinedload(UserRepoToPerm.user),
1998 joinedload(UserRepoToPerm.user),
1999 joinedload(UserRepoToPerm.permission),)
1999 joinedload(UserRepoToPerm.permission),)
2000
2000
2001 # get owners and admins and permissions. We do a trick of re-writing
2001 # get owners and admins and permissions. We do a trick of re-writing
2002 # objects from sqlalchemy to named-tuples due to sqlalchemy session
2002 # objects from sqlalchemy to named-tuples due to sqlalchemy session
2003 # has a global reference and changing one object propagates to all
2003 # has a global reference and changing one object propagates to all
2004 # others. This means if admin is also an owner admin_row that change
2004 # others. This means if admin is also an owner admin_row that change
2005 # would propagate to both objects
2005 # would propagate to both objects
2006 perm_rows = []
2006 perm_rows = []
2007 for _usr in q.all():
2007 for _usr in q.all():
2008 usr = AttributeDict(_usr.user.get_dict())
2008 usr = AttributeDict(_usr.user.get_dict())
2009 # if this user is also owner/admin, mark as duplicate record
2009 # if this user is also owner/admin, mark as duplicate record
2010 if usr.user_id == owner_row[0].user_id or usr.user_id in super_admin_ids:
2010 if usr.user_id == owner_row[0].user_id or usr.user_id in super_admin_ids:
2011 usr.duplicate_perm = True
2011 usr.duplicate_perm = True
2012 # also check if this permission is maybe used by branch_permissions
2012 # also check if this permission is maybe used by branch_permissions
2013 if _usr.branch_perm_entry:
2013 if _usr.branch_perm_entry:
2014 usr.branch_rules = [x.branch_rule_id for x in _usr.branch_perm_entry]
2014 usr.branch_rules = [x.branch_rule_id for x in _usr.branch_perm_entry]
2015
2015
2016 usr.permission = _usr.permission.permission_name
2016 usr.permission = _usr.permission.permission_name
2017 usr.permission_id = _usr.repo_to_perm_id
2017 usr.permission_id = _usr.repo_to_perm_id
2018 perm_rows.append(usr)
2018 perm_rows.append(usr)
2019
2019
2020 # filter the perm rows by 'default' first and then sort them by
2020 # filter the perm rows by 'default' first and then sort them by
2021 # admin,write,read,none permissions sorted again alphabetically in
2021 # admin,write,read,none permissions sorted again alphabetically in
2022 # each group
2022 # each group
2023 perm_rows = sorted(perm_rows, key=display_user_sort)
2023 perm_rows = sorted(perm_rows, key=display_user_sort)
2024
2024
2025 user_groups_rows = []
2025 user_groups_rows = []
2026 if expand_from_user_groups:
2026 if expand_from_user_groups:
2027 for ug in self.permission_user_groups(with_members=True):
2027 for ug in self.permission_user_groups(with_members=True):
2028 for user_data in ug.members:
2028 for user_data in ug.members:
2029 user_groups_rows.append(user_data)
2029 user_groups_rows.append(user_data)
2030
2030
2031 return super_admin_rows + owner_row + perm_rows + user_groups_rows
2031 return super_admin_rows + owner_row + perm_rows + user_groups_rows
2032
2032
2033 def permission_user_groups(self, with_members=True):
2033 def permission_user_groups(self, with_members=True):
2034 q = UserGroupRepoToPerm.query()\
2034 q = UserGroupRepoToPerm.query()\
2035 .filter(UserGroupRepoToPerm.repository == self)
2035 .filter(UserGroupRepoToPerm.repository == self)
2036 q = q.options(joinedload(UserGroupRepoToPerm.repository),
2036 q = q.options(joinedload(UserGroupRepoToPerm.repository),
2037 joinedload(UserGroupRepoToPerm.users_group),
2037 joinedload(UserGroupRepoToPerm.users_group),
2038 joinedload(UserGroupRepoToPerm.permission),)
2038 joinedload(UserGroupRepoToPerm.permission),)
2039
2039
2040 perm_rows = []
2040 perm_rows = []
2041 for _user_group in q.all():
2041 for _user_group in q.all():
2042 entry = AttributeDict(_user_group.users_group.get_dict())
2042 entry = AttributeDict(_user_group.users_group.get_dict())
2043 entry.permission = _user_group.permission.permission_name
2043 entry.permission = _user_group.permission.permission_name
2044 if with_members:
2044 if with_members:
2045 entry.members = [x.user.get_dict()
2045 entry.members = [x.user.get_dict()
2046 for x in _user_group.users_group.members]
2046 for x in _user_group.users_group.members]
2047 perm_rows.append(entry)
2047 perm_rows.append(entry)
2048
2048
2049 perm_rows = sorted(perm_rows, key=display_user_group_sort)
2049 perm_rows = sorted(perm_rows, key=display_user_group_sort)
2050 return perm_rows
2050 return perm_rows
2051
2051
2052 def get_api_data(self, include_secrets=False):
2052 def get_api_data(self, include_secrets=False):
2053 """
2053 """
2054 Common function for generating repo api data
2054 Common function for generating repo api data
2055
2055
2056 :param include_secrets: See :meth:`User.get_api_data`.
2056 :param include_secrets: See :meth:`User.get_api_data`.
2057
2057
2058 """
2058 """
2059 # TODO: mikhail: Here there is an anti-pattern, we probably need to
2059 # TODO: mikhail: Here there is an anti-pattern, we probably need to
2060 # move this methods on models level.
2060 # move this methods on models level.
2061 from rhodecode.model.settings import SettingsModel
2061 from rhodecode.model.settings import SettingsModel
2062 from rhodecode.model.repo import RepoModel
2062 from rhodecode.model.repo import RepoModel
2063
2063
2064 repo = self
2064 repo = self
2065 _user_id, _time, _reason = self.locked
2065 _user_id, _time, _reason = self.locked
2066
2066
2067 data = {
2067 data = {
2068 'repo_id': repo.repo_id,
2068 'repo_id': repo.repo_id,
2069 'repo_name': repo.repo_name,
2069 'repo_name': repo.repo_name,
2070 'repo_type': repo.repo_type,
2070 'repo_type': repo.repo_type,
2071 'clone_uri': repo.clone_uri or '',
2071 'clone_uri': repo.clone_uri or '',
2072 'push_uri': repo.push_uri or '',
2072 'push_uri': repo.push_uri or '',
2073 'url': RepoModel().get_url(self),
2073 'url': RepoModel().get_url(self),
2074 'private': repo.private,
2074 'private': repo.private,
2075 'created_on': repo.created_on,
2075 'created_on': repo.created_on,
2076 'description': repo.description_safe,
2076 'description': repo.description_safe,
2077 'landing_rev': repo.landing_rev,
2077 'landing_rev': repo.landing_rev,
2078 'owner': repo.user.username,
2078 'owner': repo.user.username,
2079 'fork_of': repo.fork.repo_name if repo.fork else None,
2079 'fork_of': repo.fork.repo_name if repo.fork else None,
2080 'fork_of_id': repo.fork.repo_id if repo.fork else None,
2080 'fork_of_id': repo.fork.repo_id if repo.fork else None,
2081 'enable_statistics': repo.enable_statistics,
2081 'enable_statistics': repo.enable_statistics,
2082 'enable_locking': repo.enable_locking,
2082 'enable_locking': repo.enable_locking,
2083 'enable_downloads': repo.enable_downloads,
2083 'enable_downloads': repo.enable_downloads,
2084 'last_changeset': repo.changeset_cache,
2084 'last_changeset': repo.changeset_cache,
2085 'locked_by': User.get(_user_id).get_api_data(
2085 'locked_by': User.get(_user_id).get_api_data(
2086 include_secrets=include_secrets) if _user_id else None,
2086 include_secrets=include_secrets) if _user_id else None,
2087 'locked_date': time_to_datetime(_time) if _time else None,
2087 'locked_date': time_to_datetime(_time) if _time else None,
2088 'lock_reason': _reason if _reason else None,
2088 'lock_reason': _reason if _reason else None,
2089 }
2089 }
2090
2090
2091 # TODO: mikhail: should be per-repo settings here
2091 # TODO: mikhail: should be per-repo settings here
2092 rc_config = SettingsModel().get_all_settings()
2092 rc_config = SettingsModel().get_all_settings()
2093 repository_fields = str2bool(
2093 repository_fields = str2bool(
2094 rc_config.get('rhodecode_repository_fields'))
2094 rc_config.get('rhodecode_repository_fields'))
2095 if repository_fields:
2095 if repository_fields:
2096 for f in self.extra_fields:
2096 for f in self.extra_fields:
2097 data[f.field_key_prefixed] = f.field_value
2097 data[f.field_key_prefixed] = f.field_value
2098
2098
2099 return data
2099 return data
2100
2100
2101 @classmethod
2101 @classmethod
2102 def lock(cls, repo, user_id, lock_time=None, lock_reason=None):
2102 def lock(cls, repo, user_id, lock_time=None, lock_reason=None):
2103 if not lock_time:
2103 if not lock_time:
2104 lock_time = time.time()
2104 lock_time = time.time()
2105 if not lock_reason:
2105 if not lock_reason:
2106 lock_reason = cls.LOCK_AUTOMATIC
2106 lock_reason = cls.LOCK_AUTOMATIC
2107 repo.locked = [user_id, lock_time, lock_reason]
2107 repo.locked = [user_id, lock_time, lock_reason]
2108 Session().add(repo)
2108 Session().add(repo)
2109 Session().commit()
2109 Session().commit()
2110
2110
2111 @classmethod
2111 @classmethod
2112 def unlock(cls, repo):
2112 def unlock(cls, repo):
2113 repo.locked = None
2113 repo.locked = None
2114 Session().add(repo)
2114 Session().add(repo)
2115 Session().commit()
2115 Session().commit()
2116
2116
2117 @classmethod
2117 @classmethod
2118 def getlock(cls, repo):
2118 def getlock(cls, repo):
2119 return repo.locked
2119 return repo.locked
2120
2120
2121 def is_user_lock(self, user_id):
2121 def is_user_lock(self, user_id):
2122 if self.lock[0]:
2122 if self.lock[0]:
2123 lock_user_id = safe_int(self.lock[0])
2123 lock_user_id = safe_int(self.lock[0])
2124 user_id = safe_int(user_id)
2124 user_id = safe_int(user_id)
2125 # both are ints, and they are equal
2125 # both are ints, and they are equal
2126 return all([lock_user_id, user_id]) and lock_user_id == user_id
2126 return all([lock_user_id, user_id]) and lock_user_id == user_id
2127
2127
2128 return False
2128 return False
2129
2129
2130 def get_locking_state(self, action, user_id, only_when_enabled=True):
2130 def get_locking_state(self, action, user_id, only_when_enabled=True):
2131 """
2131 """
2132 Checks locking on this repository, if locking is enabled and lock is
2132 Checks locking on this repository, if locking is enabled and lock is
2133 present returns a tuple of make_lock, locked, locked_by.
2133 present returns a tuple of make_lock, locked, locked_by.
2134 make_lock can have 3 states None (do nothing) True, make lock
2134 make_lock can have 3 states None (do nothing) True, make lock
2135 False release lock, This value is later propagated to hooks, which
2135 False release lock, This value is later propagated to hooks, which
2136 do the locking. Think about this as signals passed to hooks what to do.
2136 do the locking. Think about this as signals passed to hooks what to do.
2137
2137
2138 """
2138 """
2139 # TODO: johbo: This is part of the business logic and should be moved
2139 # TODO: johbo: This is part of the business logic and should be moved
2140 # into the RepositoryModel.
2140 # into the RepositoryModel.
2141
2141
2142 if action not in ('push', 'pull'):
2142 if action not in ('push', 'pull'):
2143 raise ValueError("Invalid action value: %s" % repr(action))
2143 raise ValueError("Invalid action value: %s" % repr(action))
2144
2144
2145 # defines if locked error should be thrown to user
2145 # defines if locked error should be thrown to user
2146 currently_locked = False
2146 currently_locked = False
2147 # defines if new lock should be made, tri-state
2147 # defines if new lock should be made, tri-state
2148 make_lock = None
2148 make_lock = None
2149 repo = self
2149 repo = self
2150 user = User.get(user_id)
2150 user = User.get(user_id)
2151
2151
2152 lock_info = repo.locked
2152 lock_info = repo.locked
2153
2153
2154 if repo and (repo.enable_locking or not only_when_enabled):
2154 if repo and (repo.enable_locking or not only_when_enabled):
2155 if action == 'push':
2155 if action == 'push':
2156 # check if it's already locked !, if it is compare users
2156 # check if it's already locked !, if it is compare users
2157 locked_by_user_id = lock_info[0]
2157 locked_by_user_id = lock_info[0]
2158 if user.user_id == locked_by_user_id:
2158 if user.user_id == locked_by_user_id:
2159 log.debug(
2159 log.debug(
2160 'Got `push` action from user %s, now unlocking', user)
2160 'Got `push` action from user %s, now unlocking', user)
2161 # unlock if we have push from user who locked
2161 # unlock if we have push from user who locked
2162 make_lock = False
2162 make_lock = False
2163 else:
2163 else:
2164 # we're not the same user who locked, ban with
2164 # we're not the same user who locked, ban with
2165 # code defined in settings (default is 423 HTTP Locked) !
2165 # code defined in settings (default is 423 HTTP Locked) !
2166 log.debug('Repo %s is currently locked by %s', repo, user)
2166 log.debug('Repo %s is currently locked by %s', repo, user)
2167 currently_locked = True
2167 currently_locked = True
2168 elif action == 'pull':
2168 elif action == 'pull':
2169 # [0] user [1] date
2169 # [0] user [1] date
2170 if lock_info[0] and lock_info[1]:
2170 if lock_info[0] and lock_info[1]:
2171 log.debug('Repo %s is currently locked by %s', repo, user)
2171 log.debug('Repo %s is currently locked by %s', repo, user)
2172 currently_locked = True
2172 currently_locked = True
2173 else:
2173 else:
2174 log.debug('Setting lock on repo %s by %s', repo, user)
2174 log.debug('Setting lock on repo %s by %s', repo, user)
2175 make_lock = True
2175 make_lock = True
2176
2176
2177 else:
2177 else:
2178 log.debug('Repository %s do not have locking enabled', repo)
2178 log.debug('Repository %s do not have locking enabled', repo)
2179
2179
2180 log.debug('FINAL locking values make_lock:%s,locked:%s,locked_by:%s',
2180 log.debug('FINAL locking values make_lock:%s,locked:%s,locked_by:%s',
2181 make_lock, currently_locked, lock_info)
2181 make_lock, currently_locked, lock_info)
2182
2182
2183 from rhodecode.lib.auth import HasRepoPermissionAny
2183 from rhodecode.lib.auth import HasRepoPermissionAny
2184 perm_check = HasRepoPermissionAny('repository.write', 'repository.admin')
2184 perm_check = HasRepoPermissionAny('repository.write', 'repository.admin')
2185 if make_lock and not perm_check(repo_name=repo.repo_name, user=user):
2185 if make_lock and not perm_check(repo_name=repo.repo_name, user=user):
2186 # if we don't have at least write permission we cannot make a lock
2186 # if we don't have at least write permission we cannot make a lock
2187 log.debug('lock state reset back to FALSE due to lack '
2187 log.debug('lock state reset back to FALSE due to lack '
2188 'of at least read permission')
2188 'of at least read permission')
2189 make_lock = False
2189 make_lock = False
2190
2190
2191 return make_lock, currently_locked, lock_info
2191 return make_lock, currently_locked, lock_info
2192
2192
2193 @property
2193 @property
2194 def last_commit_cache_update_diff(self):
2194 def last_commit_cache_update_diff(self):
2195 return time.time() - (safe_int(self.changeset_cache.get('updated_on')) or 0)
2195 return time.time() - (safe_int(self.changeset_cache.get('updated_on')) or 0)
2196
2196
2197 @property
2197 @property
2198 def last_commit_change(self):
2198 def last_commit_change(self):
2199 from rhodecode.lib.vcs.utils.helpers import parse_datetime
2199 from rhodecode.lib.vcs.utils.helpers import parse_datetime
2200 empty_date = datetime.datetime.fromtimestamp(0)
2200 empty_date = datetime.datetime.fromtimestamp(0)
2201 date_latest = self.changeset_cache.get('date', empty_date)
2201 date_latest = self.changeset_cache.get('date', empty_date)
2202 try:
2202 try:
2203 return parse_datetime(date_latest)
2203 return parse_datetime(date_latest)
2204 except Exception:
2204 except Exception:
2205 return empty_date
2205 return empty_date
2206
2206
2207 @property
2207 @property
2208 def last_db_change(self):
2208 def last_db_change(self):
2209 return self.updated_on
2209 return self.updated_on
2210
2210
2211 @property
2211 @property
2212 def clone_uri_hidden(self):
2212 def clone_uri_hidden(self):
2213 clone_uri = self.clone_uri
2213 clone_uri = self.clone_uri
2214 if clone_uri:
2214 if clone_uri:
2215 import urlobject
2215 import urlobject
2216 url_obj = urlobject.URLObject(cleaned_uri(clone_uri))
2216 url_obj = urlobject.URLObject(cleaned_uri(clone_uri))
2217 if url_obj.password:
2217 if url_obj.password:
2218 clone_uri = url_obj.with_password('*****')
2218 clone_uri = url_obj.with_password('*****')
2219 return clone_uri
2219 return clone_uri
2220
2220
2221 @property
2221 @property
2222 def push_uri_hidden(self):
2222 def push_uri_hidden(self):
2223 push_uri = self.push_uri
2223 push_uri = self.push_uri
2224 if push_uri:
2224 if push_uri:
2225 import urlobject
2225 import urlobject
2226 url_obj = urlobject.URLObject(cleaned_uri(push_uri))
2226 url_obj = urlobject.URLObject(cleaned_uri(push_uri))
2227 if url_obj.password:
2227 if url_obj.password:
2228 push_uri = url_obj.with_password('*****')
2228 push_uri = url_obj.with_password('*****')
2229 return push_uri
2229 return push_uri
2230
2230
2231 def clone_url(self, **override):
2231 def clone_url(self, **override):
2232 from rhodecode.model.settings import SettingsModel
2232 from rhodecode.model.settings import SettingsModel
2233
2233
2234 uri_tmpl = None
2234 uri_tmpl = None
2235 if 'with_id' in override:
2235 if 'with_id' in override:
2236 uri_tmpl = self.DEFAULT_CLONE_URI_ID
2236 uri_tmpl = self.DEFAULT_CLONE_URI_ID
2237 del override['with_id']
2237 del override['with_id']
2238
2238
2239 if 'uri_tmpl' in override:
2239 if 'uri_tmpl' in override:
2240 uri_tmpl = override['uri_tmpl']
2240 uri_tmpl = override['uri_tmpl']
2241 del override['uri_tmpl']
2241 del override['uri_tmpl']
2242
2242
2243 ssh = False
2243 ssh = False
2244 if 'ssh' in override:
2244 if 'ssh' in override:
2245 ssh = True
2245 ssh = True
2246 del override['ssh']
2246 del override['ssh']
2247
2247
2248 # we didn't override our tmpl from **overrides
2248 # we didn't override our tmpl from **overrides
2249 request = get_current_request()
2249 if not uri_tmpl:
2250 if not uri_tmpl:
2250 rc_config = SettingsModel().get_all_settings(cache=True)
2251 if hasattr(request, 'call_context') and hasattr(request.call_context, 'rc_config'):
2252 rc_config = request.call_context.rc_config
2253 else:
2254 rc_config = SettingsModel().get_all_settings(cache=True)
2251 if ssh:
2255 if ssh:
2252 uri_tmpl = rc_config.get(
2256 uri_tmpl = rc_config.get(
2253 'rhodecode_clone_uri_ssh_tmpl') or self.DEFAULT_CLONE_URI_SSH
2257 'rhodecode_clone_uri_ssh_tmpl') or self.DEFAULT_CLONE_URI_SSH
2254 else:
2258 else:
2255 uri_tmpl = rc_config.get(
2259 uri_tmpl = rc_config.get(
2256 'rhodecode_clone_uri_tmpl') or self.DEFAULT_CLONE_URI
2260 'rhodecode_clone_uri_tmpl') or self.DEFAULT_CLONE_URI
2257
2261
2258 request = get_current_request()
2259 return get_clone_url(request=request,
2262 return get_clone_url(request=request,
2260 uri_tmpl=uri_tmpl,
2263 uri_tmpl=uri_tmpl,
2261 repo_name=self.repo_name,
2264 repo_name=self.repo_name,
2262 repo_id=self.repo_id, **override)
2265 repo_id=self.repo_id, **override)
2263
2266
2264 def set_state(self, state):
2267 def set_state(self, state):
2265 self.repo_state = state
2268 self.repo_state = state
2266 Session().add(self)
2269 Session().add(self)
2267 #==========================================================================
2270 #==========================================================================
2268 # SCM PROPERTIES
2271 # SCM PROPERTIES
2269 #==========================================================================
2272 #==========================================================================
2270
2273
2271 def get_commit(self, commit_id=None, commit_idx=None, pre_load=None):
2274 def get_commit(self, commit_id=None, commit_idx=None, pre_load=None):
2272 return get_commit_safe(
2275 return get_commit_safe(
2273 self.scm_instance(), commit_id, commit_idx, pre_load=pre_load)
2276 self.scm_instance(), commit_id, commit_idx, pre_load=pre_load)
2274
2277
2275 def get_changeset(self, rev=None, pre_load=None):
2278 def get_changeset(self, rev=None, pre_load=None):
2276 warnings.warn("Use get_commit", DeprecationWarning)
2279 warnings.warn("Use get_commit", DeprecationWarning)
2277 commit_id = None
2280 commit_id = None
2278 commit_idx = None
2281 commit_idx = None
2279 if isinstance(rev, compat.string_types):
2282 if isinstance(rev, compat.string_types):
2280 commit_id = rev
2283 commit_id = rev
2281 else:
2284 else:
2282 commit_idx = rev
2285 commit_idx = rev
2283 return self.get_commit(commit_id=commit_id, commit_idx=commit_idx,
2286 return self.get_commit(commit_id=commit_id, commit_idx=commit_idx,
2284 pre_load=pre_load)
2287 pre_load=pre_load)
2285
2288
2286 def get_landing_commit(self):
2289 def get_landing_commit(self):
2287 """
2290 """
2288 Returns landing commit, or if that doesn't exist returns the tip
2291 Returns landing commit, or if that doesn't exist returns the tip
2289 """
2292 """
2290 _rev_type, _rev = self.landing_rev
2293 _rev_type, _rev = self.landing_rev
2291 commit = self.get_commit(_rev)
2294 commit = self.get_commit(_rev)
2292 if isinstance(commit, EmptyCommit):
2295 if isinstance(commit, EmptyCommit):
2293 return self.get_commit()
2296 return self.get_commit()
2294 return commit
2297 return commit
2295
2298
2296 def update_commit_cache(self, cs_cache=None, config=None):
2299 def update_commit_cache(self, cs_cache=None, config=None):
2297 """
2300 """
2298 Update cache of last changeset for repository, keys should be::
2301 Update cache of last changeset for repository, keys should be::
2299
2302
2300 source_repo_id
2303 source_repo_id
2301 short_id
2304 short_id
2302 raw_id
2305 raw_id
2303 revision
2306 revision
2304 parents
2307 parents
2305 message
2308 message
2306 date
2309 date
2307 author
2310 author
2308 updated_on
2311 updated_on
2309
2312
2310 """
2313 """
2311 from rhodecode.lib.vcs.backends.base import BaseChangeset
2314 from rhodecode.lib.vcs.backends.base import BaseChangeset
2312 if cs_cache is None:
2315 if cs_cache is None:
2313 # use no-cache version here
2316 # use no-cache version here
2314 scm_repo = self.scm_instance(cache=False, config=config)
2317 scm_repo = self.scm_instance(cache=False, config=config)
2315
2318
2316 empty = scm_repo is None or scm_repo.is_empty()
2319 empty = scm_repo is None or scm_repo.is_empty()
2317 if not empty:
2320 if not empty:
2318 cs_cache = scm_repo.get_commit(
2321 cs_cache = scm_repo.get_commit(
2319 pre_load=["author", "date", "message", "parents"])
2322 pre_load=["author", "date", "message", "parents"])
2320 else:
2323 else:
2321 cs_cache = EmptyCommit()
2324 cs_cache = EmptyCommit()
2322
2325
2323 if isinstance(cs_cache, BaseChangeset):
2326 if isinstance(cs_cache, BaseChangeset):
2324 cs_cache = cs_cache.__json__()
2327 cs_cache = cs_cache.__json__()
2325
2328
2326 def is_outdated(new_cs_cache):
2329 def is_outdated(new_cs_cache):
2327 if (new_cs_cache['raw_id'] != self.changeset_cache['raw_id'] or
2330 if (new_cs_cache['raw_id'] != self.changeset_cache['raw_id'] or
2328 new_cs_cache['revision'] != self.changeset_cache['revision']):
2331 new_cs_cache['revision'] != self.changeset_cache['revision']):
2329 return True
2332 return True
2330 return False
2333 return False
2331
2334
2332 # check if we have maybe already latest cached revision
2335 # check if we have maybe already latest cached revision
2333 if is_outdated(cs_cache) or not self.changeset_cache:
2336 if is_outdated(cs_cache) or not self.changeset_cache:
2334 _default = datetime.datetime.utcnow()
2337 _default = datetime.datetime.utcnow()
2335 last_change = cs_cache.get('date') or _default
2338 last_change = cs_cache.get('date') or _default
2336 # we check if last update is newer than the new value
2339 # we check if last update is newer than the new value
2337 # if yes, we use the current timestamp instead. Imagine you get
2340 # if yes, we use the current timestamp instead. Imagine you get
2338 # old commit pushed 1y ago, we'd set last update 1y to ago.
2341 # old commit pushed 1y ago, we'd set last update 1y to ago.
2339 last_change_timestamp = datetime_to_time(last_change)
2342 last_change_timestamp = datetime_to_time(last_change)
2340 current_timestamp = datetime_to_time(last_change)
2343 current_timestamp = datetime_to_time(last_change)
2341 if last_change_timestamp > current_timestamp:
2344 if last_change_timestamp > current_timestamp:
2342 cs_cache['date'] = _default
2345 cs_cache['date'] = _default
2343
2346
2344 cs_cache['updated_on'] = time.time()
2347 cs_cache['updated_on'] = time.time()
2345 self.changeset_cache = cs_cache
2348 self.changeset_cache = cs_cache
2346 Session().add(self)
2349 Session().add(self)
2347 Session().commit()
2350 Session().commit()
2348
2351
2349 log.debug('updated repo %s with new commit cache %s',
2352 log.debug('updated repo %s with new commit cache %s',
2350 self.repo_name, cs_cache)
2353 self.repo_name, cs_cache)
2351 else:
2354 else:
2352 cs_cache = self.changeset_cache
2355 cs_cache = self.changeset_cache
2353 cs_cache['updated_on'] = time.time()
2356 cs_cache['updated_on'] = time.time()
2354 self.changeset_cache = cs_cache
2357 self.changeset_cache = cs_cache
2355 Session().add(self)
2358 Session().add(self)
2356 Session().commit()
2359 Session().commit()
2357
2360
2358 log.debug('Skipping update_commit_cache for repo:`%s` '
2361 log.debug('Skipping update_commit_cache for repo:`%s` '
2359 'commit already with latest changes', self.repo_name)
2362 'commit already with latest changes', self.repo_name)
2360
2363
2361 @property
2364 @property
2362 def tip(self):
2365 def tip(self):
2363 return self.get_commit('tip')
2366 return self.get_commit('tip')
2364
2367
2365 @property
2368 @property
2366 def author(self):
2369 def author(self):
2367 return self.tip.author
2370 return self.tip.author
2368
2371
2369 @property
2372 @property
2370 def last_change(self):
2373 def last_change(self):
2371 return self.scm_instance().last_change
2374 return self.scm_instance().last_change
2372
2375
2373 def get_comments(self, revisions=None):
2376 def get_comments(self, revisions=None):
2374 """
2377 """
2375 Returns comments for this repository grouped by revisions
2378 Returns comments for this repository grouped by revisions
2376
2379
2377 :param revisions: filter query by revisions only
2380 :param revisions: filter query by revisions only
2378 """
2381 """
2379 cmts = ChangesetComment.query()\
2382 cmts = ChangesetComment.query()\
2380 .filter(ChangesetComment.repo == self)
2383 .filter(ChangesetComment.repo == self)
2381 if revisions:
2384 if revisions:
2382 cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
2385 cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
2383 grouped = collections.defaultdict(list)
2386 grouped = collections.defaultdict(list)
2384 for cmt in cmts.all():
2387 for cmt in cmts.all():
2385 grouped[cmt.revision].append(cmt)
2388 grouped[cmt.revision].append(cmt)
2386 return grouped
2389 return grouped
2387
2390
2388 def statuses(self, revisions=None):
2391 def statuses(self, revisions=None):
2389 """
2392 """
2390 Returns statuses for this repository
2393 Returns statuses for this repository
2391
2394
2392 :param revisions: list of revisions to get statuses for
2395 :param revisions: list of revisions to get statuses for
2393 """
2396 """
2394 statuses = ChangesetStatus.query()\
2397 statuses = ChangesetStatus.query()\
2395 .filter(ChangesetStatus.repo == self)\
2398 .filter(ChangesetStatus.repo == self)\
2396 .filter(ChangesetStatus.version == 0)
2399 .filter(ChangesetStatus.version == 0)
2397
2400
2398 if revisions:
2401 if revisions:
2399 # Try doing the filtering in chunks to avoid hitting limits
2402 # Try doing the filtering in chunks to avoid hitting limits
2400 size = 500
2403 size = 500
2401 status_results = []
2404 status_results = []
2402 for chunk in xrange(0, len(revisions), size):
2405 for chunk in xrange(0, len(revisions), size):
2403 status_results += statuses.filter(
2406 status_results += statuses.filter(
2404 ChangesetStatus.revision.in_(
2407 ChangesetStatus.revision.in_(
2405 revisions[chunk: chunk+size])
2408 revisions[chunk: chunk+size])
2406 ).all()
2409 ).all()
2407 else:
2410 else:
2408 status_results = statuses.all()
2411 status_results = statuses.all()
2409
2412
2410 grouped = {}
2413 grouped = {}
2411
2414
2412 # maybe we have open new pullrequest without a status?
2415 # maybe we have open new pullrequest without a status?
2413 stat = ChangesetStatus.STATUS_UNDER_REVIEW
2416 stat = ChangesetStatus.STATUS_UNDER_REVIEW
2414 status_lbl = ChangesetStatus.get_status_lbl(stat)
2417 status_lbl = ChangesetStatus.get_status_lbl(stat)
2415 for pr in PullRequest.query().filter(PullRequest.source_repo == self).all():
2418 for pr in PullRequest.query().filter(PullRequest.source_repo == self).all():
2416 for rev in pr.revisions:
2419 for rev in pr.revisions:
2417 pr_id = pr.pull_request_id
2420 pr_id = pr.pull_request_id
2418 pr_repo = pr.target_repo.repo_name
2421 pr_repo = pr.target_repo.repo_name
2419 grouped[rev] = [stat, status_lbl, pr_id, pr_repo]
2422 grouped[rev] = [stat, status_lbl, pr_id, pr_repo]
2420
2423
2421 for stat in status_results:
2424 for stat in status_results:
2422 pr_id = pr_repo = None
2425 pr_id = pr_repo = None
2423 if stat.pull_request:
2426 if stat.pull_request:
2424 pr_id = stat.pull_request.pull_request_id
2427 pr_id = stat.pull_request.pull_request_id
2425 pr_repo = stat.pull_request.target_repo.repo_name
2428 pr_repo = stat.pull_request.target_repo.repo_name
2426 grouped[stat.revision] = [str(stat.status), stat.status_lbl,
2429 grouped[stat.revision] = [str(stat.status), stat.status_lbl,
2427 pr_id, pr_repo]
2430 pr_id, pr_repo]
2428 return grouped
2431 return grouped
2429
2432
2430 # ==========================================================================
2433 # ==========================================================================
2431 # SCM CACHE INSTANCE
2434 # SCM CACHE INSTANCE
2432 # ==========================================================================
2435 # ==========================================================================
2433
2436
2434 def scm_instance(self, **kwargs):
2437 def scm_instance(self, **kwargs):
2435 import rhodecode
2438 import rhodecode
2436
2439
2437 # Passing a config will not hit the cache currently only used
2440 # Passing a config will not hit the cache currently only used
2438 # for repo2dbmapper
2441 # for repo2dbmapper
2439 config = kwargs.pop('config', None)
2442 config = kwargs.pop('config', None)
2440 cache = kwargs.pop('cache', None)
2443 cache = kwargs.pop('cache', None)
2441 vcs_full_cache = kwargs.pop('vcs_full_cache', None)
2444 vcs_full_cache = kwargs.pop('vcs_full_cache', None)
2442 if vcs_full_cache is not None:
2445 if vcs_full_cache is not None:
2443 # allows override global config
2446 # allows override global config
2444 full_cache = vcs_full_cache
2447 full_cache = vcs_full_cache
2445 else:
2448 else:
2446 full_cache = str2bool(rhodecode.CONFIG.get('vcs_full_cache'))
2449 full_cache = str2bool(rhodecode.CONFIG.get('vcs_full_cache'))
2447 # if cache is NOT defined use default global, else we have a full
2450 # if cache is NOT defined use default global, else we have a full
2448 # control over cache behaviour
2451 # control over cache behaviour
2449 if cache is None and full_cache and not config:
2452 if cache is None and full_cache and not config:
2450 log.debug('Initializing pure cached instance for %s', self.repo_path)
2453 log.debug('Initializing pure cached instance for %s', self.repo_path)
2451 return self._get_instance_cached()
2454 return self._get_instance_cached()
2452
2455
2453 # cache here is sent to the "vcs server"
2456 # cache here is sent to the "vcs server"
2454 return self._get_instance(cache=bool(cache), config=config)
2457 return self._get_instance(cache=bool(cache), config=config)
2455
2458
2456 def _get_instance_cached(self):
2459 def _get_instance_cached(self):
2457 from rhodecode.lib import rc_cache
2460 from rhodecode.lib import rc_cache
2458
2461
2459 cache_namespace_uid = 'cache_repo_instance.{}'.format(self.repo_id)
2462 cache_namespace_uid = 'cache_repo_instance.{}'.format(self.repo_id)
2460 invalidation_namespace = CacheKey.REPO_INVALIDATION_NAMESPACE.format(
2463 invalidation_namespace = CacheKey.REPO_INVALIDATION_NAMESPACE.format(
2461 repo_id=self.repo_id)
2464 repo_id=self.repo_id)
2462 region = rc_cache.get_or_create_region('cache_repo_longterm', cache_namespace_uid)
2465 region = rc_cache.get_or_create_region('cache_repo_longterm', cache_namespace_uid)
2463
2466
2464 @region.conditional_cache_on_arguments(namespace=cache_namespace_uid)
2467 @region.conditional_cache_on_arguments(namespace=cache_namespace_uid)
2465 def get_instance_cached(repo_id, context_id, _cache_state_uid):
2468 def get_instance_cached(repo_id, context_id, _cache_state_uid):
2466 return self._get_instance(repo_state_uid=_cache_state_uid)
2469 return self._get_instance(repo_state_uid=_cache_state_uid)
2467
2470
2468 # we must use thread scoped cache here,
2471 # we must use thread scoped cache here,
2469 # because each thread of gevent needs it's own not shared connection and cache
2472 # because each thread of gevent needs it's own not shared connection and cache
2470 # we also alter `args` so the cache key is individual for every green thread.
2473 # we also alter `args` so the cache key is individual for every green thread.
2471 inv_context_manager = rc_cache.InvalidationContext(
2474 inv_context_manager = rc_cache.InvalidationContext(
2472 uid=cache_namespace_uid, invalidation_namespace=invalidation_namespace,
2475 uid=cache_namespace_uid, invalidation_namespace=invalidation_namespace,
2473 thread_scoped=True)
2476 thread_scoped=True)
2474 with inv_context_manager as invalidation_context:
2477 with inv_context_manager as invalidation_context:
2475 cache_state_uid = invalidation_context.cache_data['cache_state_uid']
2478 cache_state_uid = invalidation_context.cache_data['cache_state_uid']
2476 args = (self.repo_id, inv_context_manager.cache_key, cache_state_uid)
2479 args = (self.repo_id, inv_context_manager.cache_key, cache_state_uid)
2477
2480
2478 # re-compute and store cache if we get invalidate signal
2481 # re-compute and store cache if we get invalidate signal
2479 if invalidation_context.should_invalidate():
2482 if invalidation_context.should_invalidate():
2480 instance = get_instance_cached.refresh(*args)
2483 instance = get_instance_cached.refresh(*args)
2481 else:
2484 else:
2482 instance = get_instance_cached(*args)
2485 instance = get_instance_cached(*args)
2483
2486
2484 log.debug('Repo instance fetched in %.4fs', inv_context_manager.compute_time)
2487 log.debug('Repo instance fetched in %.4fs', inv_context_manager.compute_time)
2485 return instance
2488 return instance
2486
2489
2487 def _get_instance(self, cache=True, config=None, repo_state_uid=None):
2490 def _get_instance(self, cache=True, config=None, repo_state_uid=None):
2488 log.debug('Initializing %s instance `%s` with cache flag set to: %s',
2491 log.debug('Initializing %s instance `%s` with cache flag set to: %s',
2489 self.repo_type, self.repo_path, cache)
2492 self.repo_type, self.repo_path, cache)
2490 config = config or self._config
2493 config = config or self._config
2491 custom_wire = {
2494 custom_wire = {
2492 'cache': cache, # controls the vcs.remote cache
2495 'cache': cache, # controls the vcs.remote cache
2493 'repo_state_uid': repo_state_uid
2496 'repo_state_uid': repo_state_uid
2494 }
2497 }
2495 repo = get_vcs_instance(
2498 repo = get_vcs_instance(
2496 repo_path=safe_str(self.repo_full_path),
2499 repo_path=safe_str(self.repo_full_path),
2497 config=config,
2500 config=config,
2498 with_wire=custom_wire,
2501 with_wire=custom_wire,
2499 create=False,
2502 create=False,
2500 _vcs_alias=self.repo_type)
2503 _vcs_alias=self.repo_type)
2501 if repo is not None:
2504 if repo is not None:
2502 repo.count() # cache rebuild
2505 repo.count() # cache rebuild
2503 return repo
2506 return repo
2504
2507
2505 def __json__(self):
2508 def __json__(self):
2506 return {'landing_rev': self.landing_rev}
2509 return {'landing_rev': self.landing_rev}
2507
2510
2508 def get_dict(self):
2511 def get_dict(self):
2509
2512
2510 # Since we transformed `repo_name` to a hybrid property, we need to
2513 # Since we transformed `repo_name` to a hybrid property, we need to
2511 # keep compatibility with the code which uses `repo_name` field.
2514 # keep compatibility with the code which uses `repo_name` field.
2512
2515
2513 result = super(Repository, self).get_dict()
2516 result = super(Repository, self).get_dict()
2514 result['repo_name'] = result.pop('_repo_name', None)
2517 result['repo_name'] = result.pop('_repo_name', None)
2515 return result
2518 return result
2516
2519
2517
2520
2518 class RepoGroup(Base, BaseModel):
2521 class RepoGroup(Base, BaseModel):
2519 __tablename__ = 'groups'
2522 __tablename__ = 'groups'
2520 __table_args__ = (
2523 __table_args__ = (
2521 UniqueConstraint('group_name', 'group_parent_id'),
2524 UniqueConstraint('group_name', 'group_parent_id'),
2522 base_table_args,
2525 base_table_args,
2523 )
2526 )
2524 __mapper_args__ = {'order_by': 'group_name'}
2527 __mapper_args__ = {'order_by': 'group_name'}
2525
2528
2526 CHOICES_SEPARATOR = '/' # used to generate select2 choices for nested groups
2529 CHOICES_SEPARATOR = '/' # used to generate select2 choices for nested groups
2527
2530
2528 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2531 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2529 _group_name = Column("group_name", String(255), nullable=False, unique=True, default=None)
2532 _group_name = Column("group_name", String(255), nullable=False, unique=True, default=None)
2530 group_name_hash = Column("repo_group_name_hash", String(1024), nullable=False, unique=False)
2533 group_name_hash = Column("repo_group_name_hash", String(1024), nullable=False, unique=False)
2531 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
2534 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
2532 group_description = Column("group_description", String(10000), nullable=True, unique=None, default=None)
2535 group_description = Column("group_description", String(10000), nullable=True, unique=None, default=None)
2533 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
2536 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
2534 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
2537 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
2535 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2538 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2536 updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
2539 updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
2537 personal = Column('personal', Boolean(), nullable=True, unique=None, default=None)
2540 personal = Column('personal', Boolean(), nullable=True, unique=None, default=None)
2538 _changeset_cache = Column(
2541 _changeset_cache = Column(
2539 "changeset_cache", LargeBinary(), nullable=True) # JSON data
2542 "changeset_cache", LargeBinary(), nullable=True) # JSON data
2540
2543
2541 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
2544 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
2542 users_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
2545 users_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
2543 parent_group = relationship('RepoGroup', remote_side=group_id)
2546 parent_group = relationship('RepoGroup', remote_side=group_id)
2544 user = relationship('User')
2547 user = relationship('User')
2545 integrations = relationship('Integration', cascade="all, delete, delete-orphan")
2548 integrations = relationship('Integration', cascade="all, delete, delete-orphan")
2546
2549
2547 def __init__(self, group_name='', parent_group=None):
2550 def __init__(self, group_name='', parent_group=None):
2548 self.group_name = group_name
2551 self.group_name = group_name
2549 self.parent_group = parent_group
2552 self.parent_group = parent_group
2550
2553
2551 def __unicode__(self):
2554 def __unicode__(self):
2552 return u"<%s('id:%s:%s')>" % (
2555 return u"<%s('id:%s:%s')>" % (
2553 self.__class__.__name__, self.group_id, self.group_name)
2556 self.__class__.__name__, self.group_id, self.group_name)
2554
2557
2555 @hybrid_property
2558 @hybrid_property
2556 def group_name(self):
2559 def group_name(self):
2557 return self._group_name
2560 return self._group_name
2558
2561
2559 @group_name.setter
2562 @group_name.setter
2560 def group_name(self, value):
2563 def group_name(self, value):
2561 self._group_name = value
2564 self._group_name = value
2562 self.group_name_hash = self.hash_repo_group_name(value)
2565 self.group_name_hash = self.hash_repo_group_name(value)
2563
2566
2564 @hybrid_property
2567 @hybrid_property
2565 def changeset_cache(self):
2568 def changeset_cache(self):
2566 from rhodecode.lib.vcs.backends.base import EmptyCommit
2569 from rhodecode.lib.vcs.backends.base import EmptyCommit
2567 dummy = EmptyCommit().__json__()
2570 dummy = EmptyCommit().__json__()
2568 if not self._changeset_cache:
2571 if not self._changeset_cache:
2569 dummy['source_repo_id'] = ''
2572 dummy['source_repo_id'] = ''
2570 return json.loads(json.dumps(dummy))
2573 return json.loads(json.dumps(dummy))
2571
2574
2572 try:
2575 try:
2573 return json.loads(self._changeset_cache)
2576 return json.loads(self._changeset_cache)
2574 except TypeError:
2577 except TypeError:
2575 return dummy
2578 return dummy
2576 except Exception:
2579 except Exception:
2577 log.error(traceback.format_exc())
2580 log.error(traceback.format_exc())
2578 return dummy
2581 return dummy
2579
2582
2580 @changeset_cache.setter
2583 @changeset_cache.setter
2581 def changeset_cache(self, val):
2584 def changeset_cache(self, val):
2582 try:
2585 try:
2583 self._changeset_cache = json.dumps(val)
2586 self._changeset_cache = json.dumps(val)
2584 except Exception:
2587 except Exception:
2585 log.error(traceback.format_exc())
2588 log.error(traceback.format_exc())
2586
2589
2587 @validates('group_parent_id')
2590 @validates('group_parent_id')
2588 def validate_group_parent_id(self, key, val):
2591 def validate_group_parent_id(self, key, val):
2589 """
2592 """
2590 Check cycle references for a parent group to self
2593 Check cycle references for a parent group to self
2591 """
2594 """
2592 if self.group_id and val:
2595 if self.group_id and val:
2593 assert val != self.group_id
2596 assert val != self.group_id
2594
2597
2595 return val
2598 return val
2596
2599
2597 @hybrid_property
2600 @hybrid_property
2598 def description_safe(self):
2601 def description_safe(self):
2599 from rhodecode.lib import helpers as h
2602 from rhodecode.lib import helpers as h
2600 return h.escape(self.group_description)
2603 return h.escape(self.group_description)
2601
2604
2602 @classmethod
2605 @classmethod
2603 def hash_repo_group_name(cls, repo_group_name):
2606 def hash_repo_group_name(cls, repo_group_name):
2604 val = remove_formatting(repo_group_name)
2607 val = remove_formatting(repo_group_name)
2605 val = safe_str(val).lower()
2608 val = safe_str(val).lower()
2606 chars = []
2609 chars = []
2607 for c in val:
2610 for c in val:
2608 if c not in string.ascii_letters:
2611 if c not in string.ascii_letters:
2609 c = str(ord(c))
2612 c = str(ord(c))
2610 chars.append(c)
2613 chars.append(c)
2611
2614
2612 return ''.join(chars)
2615 return ''.join(chars)
2613
2616
2614 @classmethod
2617 @classmethod
2615 def _generate_choice(cls, repo_group):
2618 def _generate_choice(cls, repo_group):
2616 from webhelpers.html import literal as _literal
2619 from webhelpers.html import literal as _literal
2617 _name = lambda k: _literal(cls.CHOICES_SEPARATOR.join(k))
2620 _name = lambda k: _literal(cls.CHOICES_SEPARATOR.join(k))
2618 return repo_group.group_id, _name(repo_group.full_path_splitted)
2621 return repo_group.group_id, _name(repo_group.full_path_splitted)
2619
2622
2620 @classmethod
2623 @classmethod
2621 def groups_choices(cls, groups=None, show_empty_group=True):
2624 def groups_choices(cls, groups=None, show_empty_group=True):
2622 if not groups:
2625 if not groups:
2623 groups = cls.query().all()
2626 groups = cls.query().all()
2624
2627
2625 repo_groups = []
2628 repo_groups = []
2626 if show_empty_group:
2629 if show_empty_group:
2627 repo_groups = [(-1, u'-- %s --' % _('No parent'))]
2630 repo_groups = [(-1, u'-- %s --' % _('No parent'))]
2628
2631
2629 repo_groups.extend([cls._generate_choice(x) for x in groups])
2632 repo_groups.extend([cls._generate_choice(x) for x in groups])
2630
2633
2631 repo_groups = sorted(
2634 repo_groups = sorted(
2632 repo_groups, key=lambda t: t[1].split(cls.CHOICES_SEPARATOR)[0])
2635 repo_groups, key=lambda t: t[1].split(cls.CHOICES_SEPARATOR)[0])
2633 return repo_groups
2636 return repo_groups
2634
2637
2635 @classmethod
2638 @classmethod
2636 def url_sep(cls):
2639 def url_sep(cls):
2637 return URL_SEP
2640 return URL_SEP
2638
2641
2639 @classmethod
2642 @classmethod
2640 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
2643 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
2641 if case_insensitive:
2644 if case_insensitive:
2642 gr = cls.query().filter(func.lower(cls.group_name)
2645 gr = cls.query().filter(func.lower(cls.group_name)
2643 == func.lower(group_name))
2646 == func.lower(group_name))
2644 else:
2647 else:
2645 gr = cls.query().filter(cls.group_name == group_name)
2648 gr = cls.query().filter(cls.group_name == group_name)
2646 if cache:
2649 if cache:
2647 name_key = _hash_key(group_name)
2650 name_key = _hash_key(group_name)
2648 gr = gr.options(
2651 gr = gr.options(
2649 FromCache("sql_cache_short", "get_group_%s" % name_key))
2652 FromCache("sql_cache_short", "get_group_%s" % name_key))
2650 return gr.scalar()
2653 return gr.scalar()
2651
2654
2652 @classmethod
2655 @classmethod
2653 def get_user_personal_repo_group(cls, user_id):
2656 def get_user_personal_repo_group(cls, user_id):
2654 user = User.get(user_id)
2657 user = User.get(user_id)
2655 if user.username == User.DEFAULT_USER:
2658 if user.username == User.DEFAULT_USER:
2656 return None
2659 return None
2657
2660
2658 return cls.query()\
2661 return cls.query()\
2659 .filter(cls.personal == true()) \
2662 .filter(cls.personal == true()) \
2660 .filter(cls.user == user) \
2663 .filter(cls.user == user) \
2661 .order_by(cls.group_id.asc()) \
2664 .order_by(cls.group_id.asc()) \
2662 .first()
2665 .first()
2663
2666
2664 @classmethod
2667 @classmethod
2665 def get_all_repo_groups(cls, user_id=Optional(None), group_id=Optional(None),
2668 def get_all_repo_groups(cls, user_id=Optional(None), group_id=Optional(None),
2666 case_insensitive=True):
2669 case_insensitive=True):
2667 q = RepoGroup.query()
2670 q = RepoGroup.query()
2668
2671
2669 if not isinstance(user_id, Optional):
2672 if not isinstance(user_id, Optional):
2670 q = q.filter(RepoGroup.user_id == user_id)
2673 q = q.filter(RepoGroup.user_id == user_id)
2671
2674
2672 if not isinstance(group_id, Optional):
2675 if not isinstance(group_id, Optional):
2673 q = q.filter(RepoGroup.group_parent_id == group_id)
2676 q = q.filter(RepoGroup.group_parent_id == group_id)
2674
2677
2675 if case_insensitive:
2678 if case_insensitive:
2676 q = q.order_by(func.lower(RepoGroup.group_name))
2679 q = q.order_by(func.lower(RepoGroup.group_name))
2677 else:
2680 else:
2678 q = q.order_by(RepoGroup.group_name)
2681 q = q.order_by(RepoGroup.group_name)
2679 return q.all()
2682 return q.all()
2680
2683
2681 @property
2684 @property
2682 def parents(self, parents_recursion_limit = 10):
2685 def parents(self, parents_recursion_limit = 10):
2683 groups = []
2686 groups = []
2684 if self.parent_group is None:
2687 if self.parent_group is None:
2685 return groups
2688 return groups
2686 cur_gr = self.parent_group
2689 cur_gr = self.parent_group
2687 groups.insert(0, cur_gr)
2690 groups.insert(0, cur_gr)
2688 cnt = 0
2691 cnt = 0
2689 while 1:
2692 while 1:
2690 cnt += 1
2693 cnt += 1
2691 gr = getattr(cur_gr, 'parent_group', None)
2694 gr = getattr(cur_gr, 'parent_group', None)
2692 cur_gr = cur_gr.parent_group
2695 cur_gr = cur_gr.parent_group
2693 if gr is None:
2696 if gr is None:
2694 break
2697 break
2695 if cnt == parents_recursion_limit:
2698 if cnt == parents_recursion_limit:
2696 # this will prevent accidental infinit loops
2699 # this will prevent accidental infinit loops
2697 log.error('more than %s parents found for group %s, stopping '
2700 log.error('more than %s parents found for group %s, stopping '
2698 'recursive parent fetching', parents_recursion_limit, self)
2701 'recursive parent fetching', parents_recursion_limit, self)
2699 break
2702 break
2700
2703
2701 groups.insert(0, gr)
2704 groups.insert(0, gr)
2702 return groups
2705 return groups
2703
2706
2704 @property
2707 @property
2705 def last_commit_cache_update_diff(self):
2708 def last_commit_cache_update_diff(self):
2706 return time.time() - (safe_int(self.changeset_cache.get('updated_on')) or 0)
2709 return time.time() - (safe_int(self.changeset_cache.get('updated_on')) or 0)
2707
2710
2708 @property
2711 @property
2709 def last_commit_change(self):
2712 def last_commit_change(self):
2710 from rhodecode.lib.vcs.utils.helpers import parse_datetime
2713 from rhodecode.lib.vcs.utils.helpers import parse_datetime
2711 empty_date = datetime.datetime.fromtimestamp(0)
2714 empty_date = datetime.datetime.fromtimestamp(0)
2712 date_latest = self.changeset_cache.get('date', empty_date)
2715 date_latest = self.changeset_cache.get('date', empty_date)
2713 try:
2716 try:
2714 return parse_datetime(date_latest)
2717 return parse_datetime(date_latest)
2715 except Exception:
2718 except Exception:
2716 return empty_date
2719 return empty_date
2717
2720
2718 @property
2721 @property
2719 def last_db_change(self):
2722 def last_db_change(self):
2720 return self.updated_on
2723 return self.updated_on
2721
2724
2722 @property
2725 @property
2723 def children(self):
2726 def children(self):
2724 return RepoGroup.query().filter(RepoGroup.parent_group == self)
2727 return RepoGroup.query().filter(RepoGroup.parent_group == self)
2725
2728
2726 @property
2729 @property
2727 def name(self):
2730 def name(self):
2728 return self.group_name.split(RepoGroup.url_sep())[-1]
2731 return self.group_name.split(RepoGroup.url_sep())[-1]
2729
2732
2730 @property
2733 @property
2731 def full_path(self):
2734 def full_path(self):
2732 return self.group_name
2735 return self.group_name
2733
2736
2734 @property
2737 @property
2735 def full_path_splitted(self):
2738 def full_path_splitted(self):
2736 return self.group_name.split(RepoGroup.url_sep())
2739 return self.group_name.split(RepoGroup.url_sep())
2737
2740
2738 @property
2741 @property
2739 def repositories(self):
2742 def repositories(self):
2740 return Repository.query()\
2743 return Repository.query()\
2741 .filter(Repository.group == self)\
2744 .filter(Repository.group == self)\
2742 .order_by(Repository.repo_name)
2745 .order_by(Repository.repo_name)
2743
2746
2744 @property
2747 @property
2745 def repositories_recursive_count(self):
2748 def repositories_recursive_count(self):
2746 cnt = self.repositories.count()
2749 cnt = self.repositories.count()
2747
2750
2748 def children_count(group):
2751 def children_count(group):
2749 cnt = 0
2752 cnt = 0
2750 for child in group.children:
2753 for child in group.children:
2751 cnt += child.repositories.count()
2754 cnt += child.repositories.count()
2752 cnt += children_count(child)
2755 cnt += children_count(child)
2753 return cnt
2756 return cnt
2754
2757
2755 return cnt + children_count(self)
2758 return cnt + children_count(self)
2756
2759
2757 def _recursive_objects(self, include_repos=True, include_groups=True):
2760 def _recursive_objects(self, include_repos=True, include_groups=True):
2758 all_ = []
2761 all_ = []
2759
2762
2760 def _get_members(root_gr):
2763 def _get_members(root_gr):
2761 if include_repos:
2764 if include_repos:
2762 for r in root_gr.repositories:
2765 for r in root_gr.repositories:
2763 all_.append(r)
2766 all_.append(r)
2764 childs = root_gr.children.all()
2767 childs = root_gr.children.all()
2765 if childs:
2768 if childs:
2766 for gr in childs:
2769 for gr in childs:
2767 if include_groups:
2770 if include_groups:
2768 all_.append(gr)
2771 all_.append(gr)
2769 _get_members(gr)
2772 _get_members(gr)
2770
2773
2771 root_group = []
2774 root_group = []
2772 if include_groups:
2775 if include_groups:
2773 root_group = [self]
2776 root_group = [self]
2774
2777
2775 _get_members(self)
2778 _get_members(self)
2776 return root_group + all_
2779 return root_group + all_
2777
2780
2778 def recursive_groups_and_repos(self):
2781 def recursive_groups_and_repos(self):
2779 """
2782 """
2780 Recursive return all groups, with repositories in those groups
2783 Recursive return all groups, with repositories in those groups
2781 """
2784 """
2782 return self._recursive_objects()
2785 return self._recursive_objects()
2783
2786
2784 def recursive_groups(self):
2787 def recursive_groups(self):
2785 """
2788 """
2786 Returns all children groups for this group including children of children
2789 Returns all children groups for this group including children of children
2787 """
2790 """
2788 return self._recursive_objects(include_repos=False)
2791 return self._recursive_objects(include_repos=False)
2789
2792
2790 def recursive_repos(self):
2793 def recursive_repos(self):
2791 """
2794 """
2792 Returns all children repositories for this group
2795 Returns all children repositories for this group
2793 """
2796 """
2794 return self._recursive_objects(include_groups=False)
2797 return self._recursive_objects(include_groups=False)
2795
2798
2796 def get_new_name(self, group_name):
2799 def get_new_name(self, group_name):
2797 """
2800 """
2798 returns new full group name based on parent and new name
2801 returns new full group name based on parent and new name
2799
2802
2800 :param group_name:
2803 :param group_name:
2801 """
2804 """
2802 path_prefix = (self.parent_group.full_path_splitted if
2805 path_prefix = (self.parent_group.full_path_splitted if
2803 self.parent_group else [])
2806 self.parent_group else [])
2804 return RepoGroup.url_sep().join(path_prefix + [group_name])
2807 return RepoGroup.url_sep().join(path_prefix + [group_name])
2805
2808
2806 def update_commit_cache(self, config=None):
2809 def update_commit_cache(self, config=None):
2807 """
2810 """
2808 Update cache of last changeset for newest repository inside this group, keys should be::
2811 Update cache of last changeset for newest repository inside this group, keys should be::
2809
2812
2810 source_repo_id
2813 source_repo_id
2811 short_id
2814 short_id
2812 raw_id
2815 raw_id
2813 revision
2816 revision
2814 parents
2817 parents
2815 message
2818 message
2816 date
2819 date
2817 author
2820 author
2818
2821
2819 """
2822 """
2820 from rhodecode.lib.vcs.utils.helpers import parse_datetime
2823 from rhodecode.lib.vcs.utils.helpers import parse_datetime
2821
2824
2822 def repo_groups_and_repos():
2825 def repo_groups_and_repos():
2823 all_entries = OrderedDefaultDict(list)
2826 all_entries = OrderedDefaultDict(list)
2824
2827
2825 def _get_members(root_gr, pos=0):
2828 def _get_members(root_gr, pos=0):
2826
2829
2827 for repo in root_gr.repositories:
2830 for repo in root_gr.repositories:
2828 all_entries[root_gr].append(repo)
2831 all_entries[root_gr].append(repo)
2829
2832
2830 # fill in all parent positions
2833 # fill in all parent positions
2831 for parent_group in root_gr.parents:
2834 for parent_group in root_gr.parents:
2832 all_entries[parent_group].extend(all_entries[root_gr])
2835 all_entries[parent_group].extend(all_entries[root_gr])
2833
2836
2834 children_groups = root_gr.children.all()
2837 children_groups = root_gr.children.all()
2835 if children_groups:
2838 if children_groups:
2836 for cnt, gr in enumerate(children_groups, 1):
2839 for cnt, gr in enumerate(children_groups, 1):
2837 _get_members(gr, pos=pos+cnt)
2840 _get_members(gr, pos=pos+cnt)
2838
2841
2839 _get_members(root_gr=self)
2842 _get_members(root_gr=self)
2840 return all_entries
2843 return all_entries
2841
2844
2842 empty_date = datetime.datetime.fromtimestamp(0)
2845 empty_date = datetime.datetime.fromtimestamp(0)
2843 for repo_group, repos in repo_groups_and_repos().items():
2846 for repo_group, repos in repo_groups_and_repos().items():
2844
2847
2845 latest_repo_cs_cache = {}
2848 latest_repo_cs_cache = {}
2846 for repo in repos:
2849 for repo in repos:
2847 repo_cs_cache = repo.changeset_cache
2850 repo_cs_cache = repo.changeset_cache
2848 date_latest = latest_repo_cs_cache.get('date', empty_date)
2851 date_latest = latest_repo_cs_cache.get('date', empty_date)
2849 date_current = repo_cs_cache.get('date', empty_date)
2852 date_current = repo_cs_cache.get('date', empty_date)
2850 current_timestamp = datetime_to_time(parse_datetime(date_latest))
2853 current_timestamp = datetime_to_time(parse_datetime(date_latest))
2851 if current_timestamp < datetime_to_time(parse_datetime(date_current)):
2854 if current_timestamp < datetime_to_time(parse_datetime(date_current)):
2852 latest_repo_cs_cache = repo_cs_cache
2855 latest_repo_cs_cache = repo_cs_cache
2853 latest_repo_cs_cache['source_repo_id'] = repo.repo_id
2856 latest_repo_cs_cache['source_repo_id'] = repo.repo_id
2854
2857
2855 latest_repo_cs_cache['updated_on'] = time.time()
2858 latest_repo_cs_cache['updated_on'] = time.time()
2856 repo_group.changeset_cache = latest_repo_cs_cache
2859 repo_group.changeset_cache = latest_repo_cs_cache
2857 Session().add(repo_group)
2860 Session().add(repo_group)
2858 Session().commit()
2861 Session().commit()
2859
2862
2860 log.debug('updated repo group %s with new commit cache %s',
2863 log.debug('updated repo group %s with new commit cache %s',
2861 repo_group.group_name, latest_repo_cs_cache)
2864 repo_group.group_name, latest_repo_cs_cache)
2862
2865
2863 def permissions(self, with_admins=True, with_owner=True,
2866 def permissions(self, with_admins=True, with_owner=True,
2864 expand_from_user_groups=False):
2867 expand_from_user_groups=False):
2865 """
2868 """
2866 Permissions for repository groups
2869 Permissions for repository groups
2867 """
2870 """
2868 _admin_perm = 'group.admin'
2871 _admin_perm = 'group.admin'
2869
2872
2870 owner_row = []
2873 owner_row = []
2871 if with_owner:
2874 if with_owner:
2872 usr = AttributeDict(self.user.get_dict())
2875 usr = AttributeDict(self.user.get_dict())
2873 usr.owner_row = True
2876 usr.owner_row = True
2874 usr.permission = _admin_perm
2877 usr.permission = _admin_perm
2875 owner_row.append(usr)
2878 owner_row.append(usr)
2876
2879
2877 super_admin_ids = []
2880 super_admin_ids = []
2878 super_admin_rows = []
2881 super_admin_rows = []
2879 if with_admins:
2882 if with_admins:
2880 for usr in User.get_all_super_admins():
2883 for usr in User.get_all_super_admins():
2881 super_admin_ids.append(usr.user_id)
2884 super_admin_ids.append(usr.user_id)
2882 # if this admin is also owner, don't double the record
2885 # if this admin is also owner, don't double the record
2883 if usr.user_id == owner_row[0].user_id:
2886 if usr.user_id == owner_row[0].user_id:
2884 owner_row[0].admin_row = True
2887 owner_row[0].admin_row = True
2885 else:
2888 else:
2886 usr = AttributeDict(usr.get_dict())
2889 usr = AttributeDict(usr.get_dict())
2887 usr.admin_row = True
2890 usr.admin_row = True
2888 usr.permission = _admin_perm
2891 usr.permission = _admin_perm
2889 super_admin_rows.append(usr)
2892 super_admin_rows.append(usr)
2890
2893
2891 q = UserRepoGroupToPerm.query().filter(UserRepoGroupToPerm.group == self)
2894 q = UserRepoGroupToPerm.query().filter(UserRepoGroupToPerm.group == self)
2892 q = q.options(joinedload(UserRepoGroupToPerm.group),
2895 q = q.options(joinedload(UserRepoGroupToPerm.group),
2893 joinedload(UserRepoGroupToPerm.user),
2896 joinedload(UserRepoGroupToPerm.user),
2894 joinedload(UserRepoGroupToPerm.permission),)
2897 joinedload(UserRepoGroupToPerm.permission),)
2895
2898
2896 # get owners and admins and permissions. We do a trick of re-writing
2899 # get owners and admins and permissions. We do a trick of re-writing
2897 # objects from sqlalchemy to named-tuples due to sqlalchemy session
2900 # objects from sqlalchemy to named-tuples due to sqlalchemy session
2898 # has a global reference and changing one object propagates to all
2901 # has a global reference and changing one object propagates to all
2899 # others. This means if admin is also an owner admin_row that change
2902 # others. This means if admin is also an owner admin_row that change
2900 # would propagate to both objects
2903 # would propagate to both objects
2901 perm_rows = []
2904 perm_rows = []
2902 for _usr in q.all():
2905 for _usr in q.all():
2903 usr = AttributeDict(_usr.user.get_dict())
2906 usr = AttributeDict(_usr.user.get_dict())
2904 # if this user is also owner/admin, mark as duplicate record
2907 # if this user is also owner/admin, mark as duplicate record
2905 if usr.user_id == owner_row[0].user_id or usr.user_id in super_admin_ids:
2908 if usr.user_id == owner_row[0].user_id or usr.user_id in super_admin_ids:
2906 usr.duplicate_perm = True
2909 usr.duplicate_perm = True
2907 usr.permission = _usr.permission.permission_name
2910 usr.permission = _usr.permission.permission_name
2908 perm_rows.append(usr)
2911 perm_rows.append(usr)
2909
2912
2910 # filter the perm rows by 'default' first and then sort them by
2913 # filter the perm rows by 'default' first and then sort them by
2911 # admin,write,read,none permissions sorted again alphabetically in
2914 # admin,write,read,none permissions sorted again alphabetically in
2912 # each group
2915 # each group
2913 perm_rows = sorted(perm_rows, key=display_user_sort)
2916 perm_rows = sorted(perm_rows, key=display_user_sort)
2914
2917
2915 user_groups_rows = []
2918 user_groups_rows = []
2916 if expand_from_user_groups:
2919 if expand_from_user_groups:
2917 for ug in self.permission_user_groups(with_members=True):
2920 for ug in self.permission_user_groups(with_members=True):
2918 for user_data in ug.members:
2921 for user_data in ug.members:
2919 user_groups_rows.append(user_data)
2922 user_groups_rows.append(user_data)
2920
2923
2921 return super_admin_rows + owner_row + perm_rows + user_groups_rows
2924 return super_admin_rows + owner_row + perm_rows + user_groups_rows
2922
2925
2923 def permission_user_groups(self, with_members=False):
2926 def permission_user_groups(self, with_members=False):
2924 q = UserGroupRepoGroupToPerm.query()\
2927 q = UserGroupRepoGroupToPerm.query()\
2925 .filter(UserGroupRepoGroupToPerm.group == self)
2928 .filter(UserGroupRepoGroupToPerm.group == self)
2926 q = q.options(joinedload(UserGroupRepoGroupToPerm.group),
2929 q = q.options(joinedload(UserGroupRepoGroupToPerm.group),
2927 joinedload(UserGroupRepoGroupToPerm.users_group),
2930 joinedload(UserGroupRepoGroupToPerm.users_group),
2928 joinedload(UserGroupRepoGroupToPerm.permission),)
2931 joinedload(UserGroupRepoGroupToPerm.permission),)
2929
2932
2930 perm_rows = []
2933 perm_rows = []
2931 for _user_group in q.all():
2934 for _user_group in q.all():
2932 entry = AttributeDict(_user_group.users_group.get_dict())
2935 entry = AttributeDict(_user_group.users_group.get_dict())
2933 entry.permission = _user_group.permission.permission_name
2936 entry.permission = _user_group.permission.permission_name
2934 if with_members:
2937 if with_members:
2935 entry.members = [x.user.get_dict()
2938 entry.members = [x.user.get_dict()
2936 for x in _user_group.users_group.members]
2939 for x in _user_group.users_group.members]
2937 perm_rows.append(entry)
2940 perm_rows.append(entry)
2938
2941
2939 perm_rows = sorted(perm_rows, key=display_user_group_sort)
2942 perm_rows = sorted(perm_rows, key=display_user_group_sort)
2940 return perm_rows
2943 return perm_rows
2941
2944
2942 def get_api_data(self):
2945 def get_api_data(self):
2943 """
2946 """
2944 Common function for generating api data
2947 Common function for generating api data
2945
2948
2946 """
2949 """
2947 group = self
2950 group = self
2948 data = {
2951 data = {
2949 'group_id': group.group_id,
2952 'group_id': group.group_id,
2950 'group_name': group.group_name,
2953 'group_name': group.group_name,
2951 'group_description': group.description_safe,
2954 'group_description': group.description_safe,
2952 'parent_group': group.parent_group.group_name if group.parent_group else None,
2955 'parent_group': group.parent_group.group_name if group.parent_group else None,
2953 'repositories': [x.repo_name for x in group.repositories],
2956 'repositories': [x.repo_name for x in group.repositories],
2954 'owner': group.user.username,
2957 'owner': group.user.username,
2955 }
2958 }
2956 return data
2959 return data
2957
2960
2958 def get_dict(self):
2961 def get_dict(self):
2959 # Since we transformed `group_name` to a hybrid property, we need to
2962 # Since we transformed `group_name` to a hybrid property, we need to
2960 # keep compatibility with the code which uses `group_name` field.
2963 # keep compatibility with the code which uses `group_name` field.
2961 result = super(RepoGroup, self).get_dict()
2964 result = super(RepoGroup, self).get_dict()
2962 result['group_name'] = result.pop('_group_name', None)
2965 result['group_name'] = result.pop('_group_name', None)
2963 return result
2966 return result
2964
2967
2965
2968
2966 class Permission(Base, BaseModel):
2969 class Permission(Base, BaseModel):
2967 __tablename__ = 'permissions'
2970 __tablename__ = 'permissions'
2968 __table_args__ = (
2971 __table_args__ = (
2969 Index('p_perm_name_idx', 'permission_name'),
2972 Index('p_perm_name_idx', 'permission_name'),
2970 base_table_args,
2973 base_table_args,
2971 )
2974 )
2972
2975
2973 PERMS = [
2976 PERMS = [
2974 ('hg.admin', _('RhodeCode Super Administrator')),
2977 ('hg.admin', _('RhodeCode Super Administrator')),
2975
2978
2976 ('repository.none', _('Repository no access')),
2979 ('repository.none', _('Repository no access')),
2977 ('repository.read', _('Repository read access')),
2980 ('repository.read', _('Repository read access')),
2978 ('repository.write', _('Repository write access')),
2981 ('repository.write', _('Repository write access')),
2979 ('repository.admin', _('Repository admin access')),
2982 ('repository.admin', _('Repository admin access')),
2980
2983
2981 ('group.none', _('Repository group no access')),
2984 ('group.none', _('Repository group no access')),
2982 ('group.read', _('Repository group read access')),
2985 ('group.read', _('Repository group read access')),
2983 ('group.write', _('Repository group write access')),
2986 ('group.write', _('Repository group write access')),
2984 ('group.admin', _('Repository group admin access')),
2987 ('group.admin', _('Repository group admin access')),
2985
2988
2986 ('usergroup.none', _('User group no access')),
2989 ('usergroup.none', _('User group no access')),
2987 ('usergroup.read', _('User group read access')),
2990 ('usergroup.read', _('User group read access')),
2988 ('usergroup.write', _('User group write access')),
2991 ('usergroup.write', _('User group write access')),
2989 ('usergroup.admin', _('User group admin access')),
2992 ('usergroup.admin', _('User group admin access')),
2990
2993
2991 ('branch.none', _('Branch no permissions')),
2994 ('branch.none', _('Branch no permissions')),
2992 ('branch.merge', _('Branch access by web merge')),
2995 ('branch.merge', _('Branch access by web merge')),
2993 ('branch.push', _('Branch access by push')),
2996 ('branch.push', _('Branch access by push')),
2994 ('branch.push_force', _('Branch access by push with force')),
2997 ('branch.push_force', _('Branch access by push with force')),
2995
2998
2996 ('hg.repogroup.create.false', _('Repository Group creation disabled')),
2999 ('hg.repogroup.create.false', _('Repository Group creation disabled')),
2997 ('hg.repogroup.create.true', _('Repository Group creation enabled')),
3000 ('hg.repogroup.create.true', _('Repository Group creation enabled')),
2998
3001
2999 ('hg.usergroup.create.false', _('User Group creation disabled')),
3002 ('hg.usergroup.create.false', _('User Group creation disabled')),
3000 ('hg.usergroup.create.true', _('User Group creation enabled')),
3003 ('hg.usergroup.create.true', _('User Group creation enabled')),
3001
3004
3002 ('hg.create.none', _('Repository creation disabled')),
3005 ('hg.create.none', _('Repository creation disabled')),
3003 ('hg.create.repository', _('Repository creation enabled')),
3006 ('hg.create.repository', _('Repository creation enabled')),
3004 ('hg.create.write_on_repogroup.true', _('Repository creation enabled with write permission to a repository group')),
3007 ('hg.create.write_on_repogroup.true', _('Repository creation enabled with write permission to a repository group')),
3005 ('hg.create.write_on_repogroup.false', _('Repository creation disabled with write permission to a repository group')),
3008 ('hg.create.write_on_repogroup.false', _('Repository creation disabled with write permission to a repository group')),
3006
3009
3007 ('hg.fork.none', _('Repository forking disabled')),
3010 ('hg.fork.none', _('Repository forking disabled')),
3008 ('hg.fork.repository', _('Repository forking enabled')),
3011 ('hg.fork.repository', _('Repository forking enabled')),
3009
3012
3010 ('hg.register.none', _('Registration disabled')),
3013 ('hg.register.none', _('Registration disabled')),
3011 ('hg.register.manual_activate', _('User Registration with manual account activation')),
3014 ('hg.register.manual_activate', _('User Registration with manual account activation')),
3012 ('hg.register.auto_activate', _('User Registration with automatic account activation')),
3015 ('hg.register.auto_activate', _('User Registration with automatic account activation')),
3013
3016
3014 ('hg.password_reset.enabled', _('Password reset enabled')),
3017 ('hg.password_reset.enabled', _('Password reset enabled')),
3015 ('hg.password_reset.hidden', _('Password reset hidden')),
3018 ('hg.password_reset.hidden', _('Password reset hidden')),
3016 ('hg.password_reset.disabled', _('Password reset disabled')),
3019 ('hg.password_reset.disabled', _('Password reset disabled')),
3017
3020
3018 ('hg.extern_activate.manual', _('Manual activation of external account')),
3021 ('hg.extern_activate.manual', _('Manual activation of external account')),
3019 ('hg.extern_activate.auto', _('Automatic activation of external account')),
3022 ('hg.extern_activate.auto', _('Automatic activation of external account')),
3020
3023
3021 ('hg.inherit_default_perms.false', _('Inherit object permissions from default user disabled')),
3024 ('hg.inherit_default_perms.false', _('Inherit object permissions from default user disabled')),
3022 ('hg.inherit_default_perms.true', _('Inherit object permissions from default user enabled')),
3025 ('hg.inherit_default_perms.true', _('Inherit object permissions from default user enabled')),
3023 ]
3026 ]
3024
3027
3025 # definition of system default permissions for DEFAULT user, created on
3028 # definition of system default permissions for DEFAULT user, created on
3026 # system setup
3029 # system setup
3027 DEFAULT_USER_PERMISSIONS = [
3030 DEFAULT_USER_PERMISSIONS = [
3028 # object perms
3031 # object perms
3029 'repository.read',
3032 'repository.read',
3030 'group.read',
3033 'group.read',
3031 'usergroup.read',
3034 'usergroup.read',
3032 # branch, for backward compat we need same value as before so forced pushed
3035 # branch, for backward compat we need same value as before so forced pushed
3033 'branch.push_force',
3036 'branch.push_force',
3034 # global
3037 # global
3035 'hg.create.repository',
3038 'hg.create.repository',
3036 'hg.repogroup.create.false',
3039 'hg.repogroup.create.false',
3037 'hg.usergroup.create.false',
3040 'hg.usergroup.create.false',
3038 'hg.create.write_on_repogroup.true',
3041 'hg.create.write_on_repogroup.true',
3039 'hg.fork.repository',
3042 'hg.fork.repository',
3040 'hg.register.manual_activate',
3043 'hg.register.manual_activate',
3041 'hg.password_reset.enabled',
3044 'hg.password_reset.enabled',
3042 'hg.extern_activate.auto',
3045 'hg.extern_activate.auto',
3043 'hg.inherit_default_perms.true',
3046 'hg.inherit_default_perms.true',
3044 ]
3047 ]
3045
3048
3046 # defines which permissions are more important higher the more important
3049 # defines which permissions are more important higher the more important
3047 # Weight defines which permissions are more important.
3050 # Weight defines which permissions are more important.
3048 # The higher number the more important.
3051 # The higher number the more important.
3049 PERM_WEIGHTS = {
3052 PERM_WEIGHTS = {
3050 'repository.none': 0,
3053 'repository.none': 0,
3051 'repository.read': 1,
3054 'repository.read': 1,
3052 'repository.write': 3,
3055 'repository.write': 3,
3053 'repository.admin': 4,
3056 'repository.admin': 4,
3054
3057
3055 'group.none': 0,
3058 'group.none': 0,
3056 'group.read': 1,
3059 'group.read': 1,
3057 'group.write': 3,
3060 'group.write': 3,
3058 'group.admin': 4,
3061 'group.admin': 4,
3059
3062
3060 'usergroup.none': 0,
3063 'usergroup.none': 0,
3061 'usergroup.read': 1,
3064 'usergroup.read': 1,
3062 'usergroup.write': 3,
3065 'usergroup.write': 3,
3063 'usergroup.admin': 4,
3066 'usergroup.admin': 4,
3064
3067
3065 'branch.none': 0,
3068 'branch.none': 0,
3066 'branch.merge': 1,
3069 'branch.merge': 1,
3067 'branch.push': 3,
3070 'branch.push': 3,
3068 'branch.push_force': 4,
3071 'branch.push_force': 4,
3069
3072
3070 'hg.repogroup.create.false': 0,
3073 'hg.repogroup.create.false': 0,
3071 'hg.repogroup.create.true': 1,
3074 'hg.repogroup.create.true': 1,
3072
3075
3073 'hg.usergroup.create.false': 0,
3076 'hg.usergroup.create.false': 0,
3074 'hg.usergroup.create.true': 1,
3077 'hg.usergroup.create.true': 1,
3075
3078
3076 'hg.fork.none': 0,
3079 'hg.fork.none': 0,
3077 'hg.fork.repository': 1,
3080 'hg.fork.repository': 1,
3078 'hg.create.none': 0,
3081 'hg.create.none': 0,
3079 'hg.create.repository': 1
3082 'hg.create.repository': 1
3080 }
3083 }
3081
3084
3082 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3085 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3083 permission_name = Column("permission_name", String(255), nullable=True, unique=None, default=None)
3086 permission_name = Column("permission_name", String(255), nullable=True, unique=None, default=None)
3084 permission_longname = Column("permission_longname", String(255), nullable=True, unique=None, default=None)
3087 permission_longname = Column("permission_longname", String(255), nullable=True, unique=None, default=None)
3085
3088
3086 def __unicode__(self):
3089 def __unicode__(self):
3087 return u"<%s('%s:%s')>" % (
3090 return u"<%s('%s:%s')>" % (
3088 self.__class__.__name__, self.permission_id, self.permission_name
3091 self.__class__.__name__, self.permission_id, self.permission_name
3089 )
3092 )
3090
3093
3091 @classmethod
3094 @classmethod
3092 def get_by_key(cls, key):
3095 def get_by_key(cls, key):
3093 return cls.query().filter(cls.permission_name == key).scalar()
3096 return cls.query().filter(cls.permission_name == key).scalar()
3094
3097
3095 @classmethod
3098 @classmethod
3096 def get_default_repo_perms(cls, user_id, repo_id=None):
3099 def get_default_repo_perms(cls, user_id, repo_id=None):
3097 q = Session().query(UserRepoToPerm, Repository, Permission)\
3100 q = Session().query(UserRepoToPerm, Repository, Permission)\
3098 .join((Permission, UserRepoToPerm.permission_id == Permission.permission_id))\
3101 .join((Permission, UserRepoToPerm.permission_id == Permission.permission_id))\
3099 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
3102 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
3100 .filter(UserRepoToPerm.user_id == user_id)
3103 .filter(UserRepoToPerm.user_id == user_id)
3101 if repo_id:
3104 if repo_id:
3102 q = q.filter(UserRepoToPerm.repository_id == repo_id)
3105 q = q.filter(UserRepoToPerm.repository_id == repo_id)
3103 return q.all()
3106 return q.all()
3104
3107
3105 @classmethod
3108 @classmethod
3106 def get_default_repo_branch_perms(cls, user_id, repo_id=None):
3109 def get_default_repo_branch_perms(cls, user_id, repo_id=None):
3107 q = Session().query(UserToRepoBranchPermission, UserRepoToPerm, Permission) \
3110 q = Session().query(UserToRepoBranchPermission, UserRepoToPerm, Permission) \
3108 .join(
3111 .join(
3109 Permission,
3112 Permission,
3110 UserToRepoBranchPermission.permission_id == Permission.permission_id) \
3113 UserToRepoBranchPermission.permission_id == Permission.permission_id) \
3111 .join(
3114 .join(
3112 UserRepoToPerm,
3115 UserRepoToPerm,
3113 UserToRepoBranchPermission.rule_to_perm_id == UserRepoToPerm.repo_to_perm_id) \
3116 UserToRepoBranchPermission.rule_to_perm_id == UserRepoToPerm.repo_to_perm_id) \
3114 .filter(UserRepoToPerm.user_id == user_id)
3117 .filter(UserRepoToPerm.user_id == user_id)
3115
3118
3116 if repo_id:
3119 if repo_id:
3117 q = q.filter(UserToRepoBranchPermission.repository_id == repo_id)
3120 q = q.filter(UserToRepoBranchPermission.repository_id == repo_id)
3118 return q.order_by(UserToRepoBranchPermission.rule_order).all()
3121 return q.order_by(UserToRepoBranchPermission.rule_order).all()
3119
3122
3120 @classmethod
3123 @classmethod
3121 def get_default_repo_perms_from_user_group(cls, user_id, repo_id=None):
3124 def get_default_repo_perms_from_user_group(cls, user_id, repo_id=None):
3122 q = Session().query(UserGroupRepoToPerm, Repository, Permission)\
3125 q = Session().query(UserGroupRepoToPerm, Repository, Permission)\
3123 .join(
3126 .join(
3124 Permission,
3127 Permission,
3125 UserGroupRepoToPerm.permission_id == Permission.permission_id)\
3128 UserGroupRepoToPerm.permission_id == Permission.permission_id)\
3126 .join(
3129 .join(
3127 Repository,
3130 Repository,
3128 UserGroupRepoToPerm.repository_id == Repository.repo_id)\
3131 UserGroupRepoToPerm.repository_id == Repository.repo_id)\
3129 .join(
3132 .join(
3130 UserGroup,
3133 UserGroup,
3131 UserGroupRepoToPerm.users_group_id ==
3134 UserGroupRepoToPerm.users_group_id ==
3132 UserGroup.users_group_id)\
3135 UserGroup.users_group_id)\
3133 .join(
3136 .join(
3134 UserGroupMember,
3137 UserGroupMember,
3135 UserGroupRepoToPerm.users_group_id ==
3138 UserGroupRepoToPerm.users_group_id ==
3136 UserGroupMember.users_group_id)\
3139 UserGroupMember.users_group_id)\
3137 .filter(
3140 .filter(
3138 UserGroupMember.user_id == user_id,
3141 UserGroupMember.user_id == user_id,
3139 UserGroup.users_group_active == true())
3142 UserGroup.users_group_active == true())
3140 if repo_id:
3143 if repo_id:
3141 q = q.filter(UserGroupRepoToPerm.repository_id == repo_id)
3144 q = q.filter(UserGroupRepoToPerm.repository_id == repo_id)
3142 return q.all()
3145 return q.all()
3143
3146
3144 @classmethod
3147 @classmethod
3145 def get_default_repo_branch_perms_from_user_group(cls, user_id, repo_id=None):
3148 def get_default_repo_branch_perms_from_user_group(cls, user_id, repo_id=None):
3146 q = Session().query(UserGroupToRepoBranchPermission, UserGroupRepoToPerm, Permission) \
3149 q = Session().query(UserGroupToRepoBranchPermission, UserGroupRepoToPerm, Permission) \
3147 .join(
3150 .join(
3148 Permission,
3151 Permission,
3149 UserGroupToRepoBranchPermission.permission_id == Permission.permission_id) \
3152 UserGroupToRepoBranchPermission.permission_id == Permission.permission_id) \
3150 .join(
3153 .join(
3151 UserGroupRepoToPerm,
3154 UserGroupRepoToPerm,
3152 UserGroupToRepoBranchPermission.rule_to_perm_id == UserGroupRepoToPerm.users_group_to_perm_id) \
3155 UserGroupToRepoBranchPermission.rule_to_perm_id == UserGroupRepoToPerm.users_group_to_perm_id) \
3153 .join(
3156 .join(
3154 UserGroup,
3157 UserGroup,
3155 UserGroupRepoToPerm.users_group_id == UserGroup.users_group_id) \
3158 UserGroupRepoToPerm.users_group_id == UserGroup.users_group_id) \
3156 .join(
3159 .join(
3157 UserGroupMember,
3160 UserGroupMember,
3158 UserGroupRepoToPerm.users_group_id == UserGroupMember.users_group_id) \
3161 UserGroupRepoToPerm.users_group_id == UserGroupMember.users_group_id) \
3159 .filter(
3162 .filter(
3160 UserGroupMember.user_id == user_id,
3163 UserGroupMember.user_id == user_id,
3161 UserGroup.users_group_active == true())
3164 UserGroup.users_group_active == true())
3162
3165
3163 if repo_id:
3166 if repo_id:
3164 q = q.filter(UserGroupToRepoBranchPermission.repository_id == repo_id)
3167 q = q.filter(UserGroupToRepoBranchPermission.repository_id == repo_id)
3165 return q.order_by(UserGroupToRepoBranchPermission.rule_order).all()
3168 return q.order_by(UserGroupToRepoBranchPermission.rule_order).all()
3166
3169
3167 @classmethod
3170 @classmethod
3168 def get_default_group_perms(cls, user_id, repo_group_id=None):
3171 def get_default_group_perms(cls, user_id, repo_group_id=None):
3169 q = Session().query(UserRepoGroupToPerm, RepoGroup, Permission)\
3172 q = Session().query(UserRepoGroupToPerm, RepoGroup, Permission)\
3170 .join(
3173 .join(
3171 Permission,
3174 Permission,
3172 UserRepoGroupToPerm.permission_id == Permission.permission_id)\
3175 UserRepoGroupToPerm.permission_id == Permission.permission_id)\
3173 .join(
3176 .join(
3174 RepoGroup,
3177 RepoGroup,
3175 UserRepoGroupToPerm.group_id == RepoGroup.group_id)\
3178 UserRepoGroupToPerm.group_id == RepoGroup.group_id)\
3176 .filter(UserRepoGroupToPerm.user_id == user_id)
3179 .filter(UserRepoGroupToPerm.user_id == user_id)
3177 if repo_group_id:
3180 if repo_group_id:
3178 q = q.filter(UserRepoGroupToPerm.group_id == repo_group_id)
3181 q = q.filter(UserRepoGroupToPerm.group_id == repo_group_id)
3179 return q.all()
3182 return q.all()
3180
3183
3181 @classmethod
3184 @classmethod
3182 def get_default_group_perms_from_user_group(
3185 def get_default_group_perms_from_user_group(
3183 cls, user_id, repo_group_id=None):
3186 cls, user_id, repo_group_id=None):
3184 q = Session().query(UserGroupRepoGroupToPerm, RepoGroup, Permission)\
3187 q = Session().query(UserGroupRepoGroupToPerm, RepoGroup, Permission)\
3185 .join(
3188 .join(
3186 Permission,
3189 Permission,
3187 UserGroupRepoGroupToPerm.permission_id ==
3190 UserGroupRepoGroupToPerm.permission_id ==
3188 Permission.permission_id)\
3191 Permission.permission_id)\
3189 .join(
3192 .join(
3190 RepoGroup,
3193 RepoGroup,
3191 UserGroupRepoGroupToPerm.group_id == RepoGroup.group_id)\
3194 UserGroupRepoGroupToPerm.group_id == RepoGroup.group_id)\
3192 .join(
3195 .join(
3193 UserGroup,
3196 UserGroup,
3194 UserGroupRepoGroupToPerm.users_group_id ==
3197 UserGroupRepoGroupToPerm.users_group_id ==
3195 UserGroup.users_group_id)\
3198 UserGroup.users_group_id)\
3196 .join(
3199 .join(
3197 UserGroupMember,
3200 UserGroupMember,
3198 UserGroupRepoGroupToPerm.users_group_id ==
3201 UserGroupRepoGroupToPerm.users_group_id ==
3199 UserGroupMember.users_group_id)\
3202 UserGroupMember.users_group_id)\
3200 .filter(
3203 .filter(
3201 UserGroupMember.user_id == user_id,
3204 UserGroupMember.user_id == user_id,
3202 UserGroup.users_group_active == true())
3205 UserGroup.users_group_active == true())
3203 if repo_group_id:
3206 if repo_group_id:
3204 q = q.filter(UserGroupRepoGroupToPerm.group_id == repo_group_id)
3207 q = q.filter(UserGroupRepoGroupToPerm.group_id == repo_group_id)
3205 return q.all()
3208 return q.all()
3206
3209
3207 @classmethod
3210 @classmethod
3208 def get_default_user_group_perms(cls, user_id, user_group_id=None):
3211 def get_default_user_group_perms(cls, user_id, user_group_id=None):
3209 q = Session().query(UserUserGroupToPerm, UserGroup, Permission)\
3212 q = Session().query(UserUserGroupToPerm, UserGroup, Permission)\
3210 .join((Permission, UserUserGroupToPerm.permission_id == Permission.permission_id))\
3213 .join((Permission, UserUserGroupToPerm.permission_id == Permission.permission_id))\
3211 .join((UserGroup, UserUserGroupToPerm.user_group_id == UserGroup.users_group_id))\
3214 .join((UserGroup, UserUserGroupToPerm.user_group_id == UserGroup.users_group_id))\
3212 .filter(UserUserGroupToPerm.user_id == user_id)
3215 .filter(UserUserGroupToPerm.user_id == user_id)
3213 if user_group_id:
3216 if user_group_id:
3214 q = q.filter(UserUserGroupToPerm.user_group_id == user_group_id)
3217 q = q.filter(UserUserGroupToPerm.user_group_id == user_group_id)
3215 return q.all()
3218 return q.all()
3216
3219
3217 @classmethod
3220 @classmethod
3218 def get_default_user_group_perms_from_user_group(
3221 def get_default_user_group_perms_from_user_group(
3219 cls, user_id, user_group_id=None):
3222 cls, user_id, user_group_id=None):
3220 TargetUserGroup = aliased(UserGroup, name='target_user_group')
3223 TargetUserGroup = aliased(UserGroup, name='target_user_group')
3221 q = Session().query(UserGroupUserGroupToPerm, UserGroup, Permission)\
3224 q = Session().query(UserGroupUserGroupToPerm, UserGroup, Permission)\
3222 .join(
3225 .join(
3223 Permission,
3226 Permission,
3224 UserGroupUserGroupToPerm.permission_id ==
3227 UserGroupUserGroupToPerm.permission_id ==
3225 Permission.permission_id)\
3228 Permission.permission_id)\
3226 .join(
3229 .join(
3227 TargetUserGroup,
3230 TargetUserGroup,
3228 UserGroupUserGroupToPerm.target_user_group_id ==
3231 UserGroupUserGroupToPerm.target_user_group_id ==
3229 TargetUserGroup.users_group_id)\
3232 TargetUserGroup.users_group_id)\
3230 .join(
3233 .join(
3231 UserGroup,
3234 UserGroup,
3232 UserGroupUserGroupToPerm.user_group_id ==
3235 UserGroupUserGroupToPerm.user_group_id ==
3233 UserGroup.users_group_id)\
3236 UserGroup.users_group_id)\
3234 .join(
3237 .join(
3235 UserGroupMember,
3238 UserGroupMember,
3236 UserGroupUserGroupToPerm.user_group_id ==
3239 UserGroupUserGroupToPerm.user_group_id ==
3237 UserGroupMember.users_group_id)\
3240 UserGroupMember.users_group_id)\
3238 .filter(
3241 .filter(
3239 UserGroupMember.user_id == user_id,
3242 UserGroupMember.user_id == user_id,
3240 UserGroup.users_group_active == true())
3243 UserGroup.users_group_active == true())
3241 if user_group_id:
3244 if user_group_id:
3242 q = q.filter(
3245 q = q.filter(
3243 UserGroupUserGroupToPerm.user_group_id == user_group_id)
3246 UserGroupUserGroupToPerm.user_group_id == user_group_id)
3244
3247
3245 return q.all()
3248 return q.all()
3246
3249
3247
3250
3248 class UserRepoToPerm(Base, BaseModel):
3251 class UserRepoToPerm(Base, BaseModel):
3249 __tablename__ = 'repo_to_perm'
3252 __tablename__ = 'repo_to_perm'
3250 __table_args__ = (
3253 __table_args__ = (
3251 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
3254 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
3252 base_table_args
3255 base_table_args
3253 )
3256 )
3254
3257
3255 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3258 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3256 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3259 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3257 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3260 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3258 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
3261 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
3259
3262
3260 user = relationship('User')
3263 user = relationship('User')
3261 repository = relationship('Repository')
3264 repository = relationship('Repository')
3262 permission = relationship('Permission')
3265 permission = relationship('Permission')
3263
3266
3264 branch_perm_entry = relationship('UserToRepoBranchPermission', cascade="all, delete, delete-orphan", lazy='joined')
3267 branch_perm_entry = relationship('UserToRepoBranchPermission', cascade="all, delete, delete-orphan", lazy='joined')
3265
3268
3266 @classmethod
3269 @classmethod
3267 def create(cls, user, repository, permission):
3270 def create(cls, user, repository, permission):
3268 n = cls()
3271 n = cls()
3269 n.user = user
3272 n.user = user
3270 n.repository = repository
3273 n.repository = repository
3271 n.permission = permission
3274 n.permission = permission
3272 Session().add(n)
3275 Session().add(n)
3273 return n
3276 return n
3274
3277
3275 def __unicode__(self):
3278 def __unicode__(self):
3276 return u'<%s => %s >' % (self.user, self.repository)
3279 return u'<%s => %s >' % (self.user, self.repository)
3277
3280
3278
3281
3279 class UserUserGroupToPerm(Base, BaseModel):
3282 class UserUserGroupToPerm(Base, BaseModel):
3280 __tablename__ = 'user_user_group_to_perm'
3283 __tablename__ = 'user_user_group_to_perm'
3281 __table_args__ = (
3284 __table_args__ = (
3282 UniqueConstraint('user_id', 'user_group_id', 'permission_id'),
3285 UniqueConstraint('user_id', 'user_group_id', 'permission_id'),
3283 base_table_args
3286 base_table_args
3284 )
3287 )
3285
3288
3286 user_user_group_to_perm_id = Column("user_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3289 user_user_group_to_perm_id = Column("user_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3287 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3290 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3288 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3291 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3289 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3292 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3290
3293
3291 user = relationship('User')
3294 user = relationship('User')
3292 user_group = relationship('UserGroup')
3295 user_group = relationship('UserGroup')
3293 permission = relationship('Permission')
3296 permission = relationship('Permission')
3294
3297
3295 @classmethod
3298 @classmethod
3296 def create(cls, user, user_group, permission):
3299 def create(cls, user, user_group, permission):
3297 n = cls()
3300 n = cls()
3298 n.user = user
3301 n.user = user
3299 n.user_group = user_group
3302 n.user_group = user_group
3300 n.permission = permission
3303 n.permission = permission
3301 Session().add(n)
3304 Session().add(n)
3302 return n
3305 return n
3303
3306
3304 def __unicode__(self):
3307 def __unicode__(self):
3305 return u'<%s => %s >' % (self.user, self.user_group)
3308 return u'<%s => %s >' % (self.user, self.user_group)
3306
3309
3307
3310
3308 class UserToPerm(Base, BaseModel):
3311 class UserToPerm(Base, BaseModel):
3309 __tablename__ = 'user_to_perm'
3312 __tablename__ = 'user_to_perm'
3310 __table_args__ = (
3313 __table_args__ = (
3311 UniqueConstraint('user_id', 'permission_id'),
3314 UniqueConstraint('user_id', 'permission_id'),
3312 base_table_args
3315 base_table_args
3313 )
3316 )
3314
3317
3315 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3318 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3316 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3319 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3317 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3320 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3318
3321
3319 user = relationship('User')
3322 user = relationship('User')
3320 permission = relationship('Permission', lazy='joined')
3323 permission = relationship('Permission', lazy='joined')
3321
3324
3322 def __unicode__(self):
3325 def __unicode__(self):
3323 return u'<%s => %s >' % (self.user, self.permission)
3326 return u'<%s => %s >' % (self.user, self.permission)
3324
3327
3325
3328
3326 class UserGroupRepoToPerm(Base, BaseModel):
3329 class UserGroupRepoToPerm(Base, BaseModel):
3327 __tablename__ = 'users_group_repo_to_perm'
3330 __tablename__ = 'users_group_repo_to_perm'
3328 __table_args__ = (
3331 __table_args__ = (
3329 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
3332 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
3330 base_table_args
3333 base_table_args
3331 )
3334 )
3332
3335
3333 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3336 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3334 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3337 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3335 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3338 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3336 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
3339 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
3337
3340
3338 users_group = relationship('UserGroup')
3341 users_group = relationship('UserGroup')
3339 permission = relationship('Permission')
3342 permission = relationship('Permission')
3340 repository = relationship('Repository')
3343 repository = relationship('Repository')
3341 user_group_branch_perms = relationship('UserGroupToRepoBranchPermission', cascade='all')
3344 user_group_branch_perms = relationship('UserGroupToRepoBranchPermission', cascade='all')
3342
3345
3343 @classmethod
3346 @classmethod
3344 def create(cls, users_group, repository, permission):
3347 def create(cls, users_group, repository, permission):
3345 n = cls()
3348 n = cls()
3346 n.users_group = users_group
3349 n.users_group = users_group
3347 n.repository = repository
3350 n.repository = repository
3348 n.permission = permission
3351 n.permission = permission
3349 Session().add(n)
3352 Session().add(n)
3350 return n
3353 return n
3351
3354
3352 def __unicode__(self):
3355 def __unicode__(self):
3353 return u'<UserGroupRepoToPerm:%s => %s >' % (self.users_group, self.repository)
3356 return u'<UserGroupRepoToPerm:%s => %s >' % (self.users_group, self.repository)
3354
3357
3355
3358
3356 class UserGroupUserGroupToPerm(Base, BaseModel):
3359 class UserGroupUserGroupToPerm(Base, BaseModel):
3357 __tablename__ = 'user_group_user_group_to_perm'
3360 __tablename__ = 'user_group_user_group_to_perm'
3358 __table_args__ = (
3361 __table_args__ = (
3359 UniqueConstraint('target_user_group_id', 'user_group_id', 'permission_id'),
3362 UniqueConstraint('target_user_group_id', 'user_group_id', 'permission_id'),
3360 CheckConstraint('target_user_group_id != user_group_id'),
3363 CheckConstraint('target_user_group_id != user_group_id'),
3361 base_table_args
3364 base_table_args
3362 )
3365 )
3363
3366
3364 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)
3367 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)
3365 target_user_group_id = Column("target_user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3368 target_user_group_id = Column("target_user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3366 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3369 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3367 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3370 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3368
3371
3369 target_user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id')
3372 target_user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id')
3370 user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.user_group_id==UserGroup.users_group_id')
3373 user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.user_group_id==UserGroup.users_group_id')
3371 permission = relationship('Permission')
3374 permission = relationship('Permission')
3372
3375
3373 @classmethod
3376 @classmethod
3374 def create(cls, target_user_group, user_group, permission):
3377 def create(cls, target_user_group, user_group, permission):
3375 n = cls()
3378 n = cls()
3376 n.target_user_group = target_user_group
3379 n.target_user_group = target_user_group
3377 n.user_group = user_group
3380 n.user_group = user_group
3378 n.permission = permission
3381 n.permission = permission
3379 Session().add(n)
3382 Session().add(n)
3380 return n
3383 return n
3381
3384
3382 def __unicode__(self):
3385 def __unicode__(self):
3383 return u'<UserGroupUserGroup:%s => %s >' % (self.target_user_group, self.user_group)
3386 return u'<UserGroupUserGroup:%s => %s >' % (self.target_user_group, self.user_group)
3384
3387
3385
3388
3386 class UserGroupToPerm(Base, BaseModel):
3389 class UserGroupToPerm(Base, BaseModel):
3387 __tablename__ = 'users_group_to_perm'
3390 __tablename__ = 'users_group_to_perm'
3388 __table_args__ = (
3391 __table_args__ = (
3389 UniqueConstraint('users_group_id', 'permission_id',),
3392 UniqueConstraint('users_group_id', 'permission_id',),
3390 base_table_args
3393 base_table_args
3391 )
3394 )
3392
3395
3393 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3396 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3394 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3397 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3395 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3398 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3396
3399
3397 users_group = relationship('UserGroup')
3400 users_group = relationship('UserGroup')
3398 permission = relationship('Permission')
3401 permission = relationship('Permission')
3399
3402
3400
3403
3401 class UserRepoGroupToPerm(Base, BaseModel):
3404 class UserRepoGroupToPerm(Base, BaseModel):
3402 __tablename__ = 'user_repo_group_to_perm'
3405 __tablename__ = 'user_repo_group_to_perm'
3403 __table_args__ = (
3406 __table_args__ = (
3404 UniqueConstraint('user_id', 'group_id', 'permission_id'),
3407 UniqueConstraint('user_id', 'group_id', 'permission_id'),
3405 base_table_args
3408 base_table_args
3406 )
3409 )
3407
3410
3408 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3411 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3409 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3412 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3410 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
3413 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
3411 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)
3412
3415
3413 user = relationship('User')
3416 user = relationship('User')
3414 group = relationship('RepoGroup')
3417 group = relationship('RepoGroup')
3415 permission = relationship('Permission')
3418 permission = relationship('Permission')
3416
3419
3417 @classmethod
3420 @classmethod
3418 def create(cls, user, repository_group, permission):
3421 def create(cls, user, repository_group, permission):
3419 n = cls()
3422 n = cls()
3420 n.user = user
3423 n.user = user
3421 n.group = repository_group
3424 n.group = repository_group
3422 n.permission = permission
3425 n.permission = permission
3423 Session().add(n)
3426 Session().add(n)
3424 return n
3427 return n
3425
3428
3426
3429
3427 class UserGroupRepoGroupToPerm(Base, BaseModel):
3430 class UserGroupRepoGroupToPerm(Base, BaseModel):
3428 __tablename__ = 'users_group_repo_group_to_perm'
3431 __tablename__ = 'users_group_repo_group_to_perm'
3429 __table_args__ = (
3432 __table_args__ = (
3430 UniqueConstraint('users_group_id', 'group_id'),
3433 UniqueConstraint('users_group_id', 'group_id'),
3431 base_table_args
3434 base_table_args
3432 )
3435 )
3433
3436
3434 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)
3437 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)
3435 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3438 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3436 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
3439 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
3437 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3440 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3438
3441
3439 users_group = relationship('UserGroup')
3442 users_group = relationship('UserGroup')
3440 permission = relationship('Permission')
3443 permission = relationship('Permission')
3441 group = relationship('RepoGroup')
3444 group = relationship('RepoGroup')
3442
3445
3443 @classmethod
3446 @classmethod
3444 def create(cls, user_group, repository_group, permission):
3447 def create(cls, user_group, repository_group, permission):
3445 n = cls()
3448 n = cls()
3446 n.users_group = user_group
3449 n.users_group = user_group
3447 n.group = repository_group
3450 n.group = repository_group
3448 n.permission = permission
3451 n.permission = permission
3449 Session().add(n)
3452 Session().add(n)
3450 return n
3453 return n
3451
3454
3452 def __unicode__(self):
3455 def __unicode__(self):
3453 return u'<UserGroupRepoGroupToPerm:%s => %s >' % (self.users_group, self.group)
3456 return u'<UserGroupRepoGroupToPerm:%s => %s >' % (self.users_group, self.group)
3454
3457
3455
3458
3456 class Statistics(Base, BaseModel):
3459 class Statistics(Base, BaseModel):
3457 __tablename__ = 'statistics'
3460 __tablename__ = 'statistics'
3458 __table_args__ = (
3461 __table_args__ = (
3459 base_table_args
3462 base_table_args
3460 )
3463 )
3461
3464
3462 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3465 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3463 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
3466 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
3464 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
3467 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
3465 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
3468 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
3466 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
3469 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
3467 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
3470 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
3468
3471
3469 repository = relationship('Repository', single_parent=True)
3472 repository = relationship('Repository', single_parent=True)
3470
3473
3471
3474
3472 class UserFollowing(Base, BaseModel):
3475 class UserFollowing(Base, BaseModel):
3473 __tablename__ = 'user_followings'
3476 __tablename__ = 'user_followings'
3474 __table_args__ = (
3477 __table_args__ = (
3475 UniqueConstraint('user_id', 'follows_repository_id'),
3478 UniqueConstraint('user_id', 'follows_repository_id'),
3476 UniqueConstraint('user_id', 'follows_user_id'),
3479 UniqueConstraint('user_id', 'follows_user_id'),
3477 base_table_args
3480 base_table_args
3478 )
3481 )
3479
3482
3480 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3483 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3481 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3484 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3482 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
3485 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
3483 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
3486 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
3484 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
3487 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
3485
3488
3486 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
3489 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
3487
3490
3488 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
3491 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
3489 follows_repository = relationship('Repository', order_by='Repository.repo_name')
3492 follows_repository = relationship('Repository', order_by='Repository.repo_name')
3490
3493
3491 @classmethod
3494 @classmethod
3492 def get_repo_followers(cls, repo_id):
3495 def get_repo_followers(cls, repo_id):
3493 return cls.query().filter(cls.follows_repo_id == repo_id)
3496 return cls.query().filter(cls.follows_repo_id == repo_id)
3494
3497
3495
3498
3496 class CacheKey(Base, BaseModel):
3499 class CacheKey(Base, BaseModel):
3497 __tablename__ = 'cache_invalidation'
3500 __tablename__ = 'cache_invalidation'
3498 __table_args__ = (
3501 __table_args__ = (
3499 UniqueConstraint('cache_key'),
3502 UniqueConstraint('cache_key'),
3500 Index('key_idx', 'cache_key'),
3503 Index('key_idx', 'cache_key'),
3501 base_table_args,
3504 base_table_args,
3502 )
3505 )
3503
3506
3504 CACHE_TYPE_FEED = 'FEED'
3507 CACHE_TYPE_FEED = 'FEED'
3505 CACHE_TYPE_README = 'README'
3508 CACHE_TYPE_README = 'README'
3506 # namespaces used to register process/thread aware caches
3509 # namespaces used to register process/thread aware caches
3507 REPO_INVALIDATION_NAMESPACE = 'repo_cache:{repo_id}'
3510 REPO_INVALIDATION_NAMESPACE = 'repo_cache:{repo_id}'
3508 SETTINGS_INVALIDATION_NAMESPACE = 'system_settings'
3511 SETTINGS_INVALIDATION_NAMESPACE = 'system_settings'
3509
3512
3510 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3513 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3511 cache_key = Column("cache_key", String(255), nullable=True, unique=None, default=None)
3514 cache_key = Column("cache_key", String(255), nullable=True, unique=None, default=None)
3512 cache_args = Column("cache_args", String(255), nullable=True, unique=None, default=None)
3515 cache_args = Column("cache_args", String(255), nullable=True, unique=None, default=None)
3513 cache_state_uid = Column("cache_state_uid", String(255), nullable=True, unique=None, default=None)
3516 cache_state_uid = Column("cache_state_uid", String(255), nullable=True, unique=None, default=None)
3514 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
3517 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
3515
3518
3516 def __init__(self, cache_key, cache_args='', cache_state_uid=None):
3519 def __init__(self, cache_key, cache_args='', cache_state_uid=None):
3517 self.cache_key = cache_key
3520 self.cache_key = cache_key
3518 self.cache_args = cache_args
3521 self.cache_args = cache_args
3519 self.cache_active = False
3522 self.cache_active = False
3520 # first key should be same for all entries, since all workers should share it
3523 # first key should be same for all entries, since all workers should share it
3521 self.cache_state_uid = cache_state_uid or self.generate_new_state_uid(based_on=cache_args)
3524 self.cache_state_uid = cache_state_uid or self.generate_new_state_uid(based_on=cache_args)
3522
3525
3523 def __unicode__(self):
3526 def __unicode__(self):
3524 return u"<%s('%s:%s[%s]')>" % (
3527 return u"<%s('%s:%s[%s]')>" % (
3525 self.__class__.__name__,
3528 self.__class__.__name__,
3526 self.cache_id, self.cache_key, self.cache_active)
3529 self.cache_id, self.cache_key, self.cache_active)
3527
3530
3528 def _cache_key_partition(self):
3531 def _cache_key_partition(self):
3529 prefix, repo_name, suffix = self.cache_key.partition(self.cache_args)
3532 prefix, repo_name, suffix = self.cache_key.partition(self.cache_args)
3530 return prefix, repo_name, suffix
3533 return prefix, repo_name, suffix
3531
3534
3532 def get_prefix(self):
3535 def get_prefix(self):
3533 """
3536 """
3534 Try to extract prefix from existing cache key. The key could consist
3537 Try to extract prefix from existing cache key. The key could consist
3535 of prefix, repo_name, suffix
3538 of prefix, repo_name, suffix
3536 """
3539 """
3537 # this returns prefix, repo_name, suffix
3540 # this returns prefix, repo_name, suffix
3538 return self._cache_key_partition()[0]
3541 return self._cache_key_partition()[0]
3539
3542
3540 def get_suffix(self):
3543 def get_suffix(self):
3541 """
3544 """
3542 get suffix that might have been used in _get_cache_key to
3545 get suffix that might have been used in _get_cache_key to
3543 generate self.cache_key. Only used for informational purposes
3546 generate self.cache_key. Only used for informational purposes
3544 in repo_edit.mako.
3547 in repo_edit.mako.
3545 """
3548 """
3546 # prefix, repo_name, suffix
3549 # prefix, repo_name, suffix
3547 return self._cache_key_partition()[2]
3550 return self._cache_key_partition()[2]
3548
3551
3549 @classmethod
3552 @classmethod
3550 def generate_new_state_uid(cls, based_on=None):
3553 def generate_new_state_uid(cls, based_on=None):
3551 if based_on:
3554 if based_on:
3552 return str(uuid.uuid5(uuid.NAMESPACE_URL, safe_str(based_on)))
3555 return str(uuid.uuid5(uuid.NAMESPACE_URL, safe_str(based_on)))
3553 else:
3556 else:
3554 return str(uuid.uuid4())
3557 return str(uuid.uuid4())
3555
3558
3556 @classmethod
3559 @classmethod
3557 def delete_all_cache(cls):
3560 def delete_all_cache(cls):
3558 """
3561 """
3559 Delete all cache keys from database.
3562 Delete all cache keys from database.
3560 Should only be run when all instances are down and all entries
3563 Should only be run when all instances are down and all entries
3561 thus stale.
3564 thus stale.
3562 """
3565 """
3563 cls.query().delete()
3566 cls.query().delete()
3564 Session().commit()
3567 Session().commit()
3565
3568
3566 @classmethod
3569 @classmethod
3567 def set_invalidate(cls, cache_uid, delete=False):
3570 def set_invalidate(cls, cache_uid, delete=False):
3568 """
3571 """
3569 Mark all caches of a repo as invalid in the database.
3572 Mark all caches of a repo as invalid in the database.
3570 """
3573 """
3571
3574
3572 try:
3575 try:
3573 qry = Session().query(cls).filter(cls.cache_args == cache_uid)
3576 qry = Session().query(cls).filter(cls.cache_args == cache_uid)
3574 if delete:
3577 if delete:
3575 qry.delete()
3578 qry.delete()
3576 log.debug('cache objects deleted for cache args %s',
3579 log.debug('cache objects deleted for cache args %s',
3577 safe_str(cache_uid))
3580 safe_str(cache_uid))
3578 else:
3581 else:
3579 qry.update({"cache_active": False,
3582 qry.update({"cache_active": False,
3580 "cache_state_uid": cls.generate_new_state_uid()})
3583 "cache_state_uid": cls.generate_new_state_uid()})
3581 log.debug('cache objects marked as invalid for cache args %s',
3584 log.debug('cache objects marked as invalid for cache args %s',
3582 safe_str(cache_uid))
3585 safe_str(cache_uid))
3583
3586
3584 Session().commit()
3587 Session().commit()
3585 except Exception:
3588 except Exception:
3586 log.exception(
3589 log.exception(
3587 'Cache key invalidation failed for cache args %s',
3590 'Cache key invalidation failed for cache args %s',
3588 safe_str(cache_uid))
3591 safe_str(cache_uid))
3589 Session().rollback()
3592 Session().rollback()
3590
3593
3591 @classmethod
3594 @classmethod
3592 def get_active_cache(cls, cache_key):
3595 def get_active_cache(cls, cache_key):
3593 inv_obj = cls.query().filter(cls.cache_key == cache_key).scalar()
3596 inv_obj = cls.query().filter(cls.cache_key == cache_key).scalar()
3594 if inv_obj:
3597 if inv_obj:
3595 return inv_obj
3598 return inv_obj
3596 return None
3599 return None
3597
3600
3598
3601
3599 class ChangesetComment(Base, BaseModel):
3602 class ChangesetComment(Base, BaseModel):
3600 __tablename__ = 'changeset_comments'
3603 __tablename__ = 'changeset_comments'
3601 __table_args__ = (
3604 __table_args__ = (
3602 Index('cc_revision_idx', 'revision'),
3605 Index('cc_revision_idx', 'revision'),
3603 base_table_args,
3606 base_table_args,
3604 )
3607 )
3605
3608
3606 COMMENT_OUTDATED = u'comment_outdated'
3609 COMMENT_OUTDATED = u'comment_outdated'
3607 COMMENT_TYPE_NOTE = u'note'
3610 COMMENT_TYPE_NOTE = u'note'
3608 COMMENT_TYPE_TODO = u'todo'
3611 COMMENT_TYPE_TODO = u'todo'
3609 COMMENT_TYPES = [COMMENT_TYPE_NOTE, COMMENT_TYPE_TODO]
3612 COMMENT_TYPES = [COMMENT_TYPE_NOTE, COMMENT_TYPE_TODO]
3610
3613
3611 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
3614 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
3612 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
3615 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
3613 revision = Column('revision', String(40), nullable=True)
3616 revision = Column('revision', String(40), nullable=True)
3614 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
3617 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
3615 pull_request_version_id = Column("pull_request_version_id", Integer(), ForeignKey('pull_request_versions.pull_request_version_id'), nullable=True)
3618 pull_request_version_id = Column("pull_request_version_id", Integer(), ForeignKey('pull_request_versions.pull_request_version_id'), nullable=True)
3616 line_no = Column('line_no', Unicode(10), nullable=True)
3619 line_no = Column('line_no', Unicode(10), nullable=True)
3617 hl_lines = Column('hl_lines', Unicode(512), nullable=True)
3620 hl_lines = Column('hl_lines', Unicode(512), nullable=True)
3618 f_path = Column('f_path', Unicode(1000), nullable=True)
3621 f_path = Column('f_path', Unicode(1000), nullable=True)
3619 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
3622 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
3620 text = Column('text', UnicodeText().with_variant(UnicodeText(25000), 'mysql'), nullable=False)
3623 text = Column('text', UnicodeText().with_variant(UnicodeText(25000), 'mysql'), nullable=False)
3621 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3624 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3622 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3625 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3623 renderer = Column('renderer', Unicode(64), nullable=True)
3626 renderer = Column('renderer', Unicode(64), nullable=True)
3624 display_state = Column('display_state', Unicode(128), nullable=True)
3627 display_state = Column('display_state', Unicode(128), nullable=True)
3625
3628
3626 comment_type = Column('comment_type', Unicode(128), nullable=True, default=COMMENT_TYPE_NOTE)
3629 comment_type = Column('comment_type', Unicode(128), nullable=True, default=COMMENT_TYPE_NOTE)
3627 resolved_comment_id = Column('resolved_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'), nullable=True)
3630 resolved_comment_id = Column('resolved_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'), nullable=True)
3628
3631
3629 resolved_comment = relationship('ChangesetComment', remote_side=comment_id, back_populates='resolved_by')
3632 resolved_comment = relationship('ChangesetComment', remote_side=comment_id, back_populates='resolved_by')
3630 resolved_by = relationship('ChangesetComment', back_populates='resolved_comment')
3633 resolved_by = relationship('ChangesetComment', back_populates='resolved_comment')
3631
3634
3632 author = relationship('User', lazy='joined')
3635 author = relationship('User', lazy='joined')
3633 repo = relationship('Repository')
3636 repo = relationship('Repository')
3634 status_change = relationship('ChangesetStatus', cascade="all, delete, delete-orphan", lazy='joined')
3637 status_change = relationship('ChangesetStatus', cascade="all, delete, delete-orphan", lazy='joined')
3635 pull_request = relationship('PullRequest', lazy='joined')
3638 pull_request = relationship('PullRequest', lazy='joined')
3636 pull_request_version = relationship('PullRequestVersion')
3639 pull_request_version = relationship('PullRequestVersion')
3637
3640
3638 @classmethod
3641 @classmethod
3639 def get_users(cls, revision=None, pull_request_id=None):
3642 def get_users(cls, revision=None, pull_request_id=None):
3640 """
3643 """
3641 Returns user associated with this ChangesetComment. ie those
3644 Returns user associated with this ChangesetComment. ie those
3642 who actually commented
3645 who actually commented
3643
3646
3644 :param cls:
3647 :param cls:
3645 :param revision:
3648 :param revision:
3646 """
3649 """
3647 q = Session().query(User)\
3650 q = Session().query(User)\
3648 .join(ChangesetComment.author)
3651 .join(ChangesetComment.author)
3649 if revision:
3652 if revision:
3650 q = q.filter(cls.revision == revision)
3653 q = q.filter(cls.revision == revision)
3651 elif pull_request_id:
3654 elif pull_request_id:
3652 q = q.filter(cls.pull_request_id == pull_request_id)
3655 q = q.filter(cls.pull_request_id == pull_request_id)
3653 return q.all()
3656 return q.all()
3654
3657
3655 @classmethod
3658 @classmethod
3656 def get_index_from_version(cls, pr_version, versions):
3659 def get_index_from_version(cls, pr_version, versions):
3657 num_versions = [x.pull_request_version_id for x in versions]
3660 num_versions = [x.pull_request_version_id for x in versions]
3658 try:
3661 try:
3659 return num_versions.index(pr_version) +1
3662 return num_versions.index(pr_version) +1
3660 except (IndexError, ValueError):
3663 except (IndexError, ValueError):
3661 return
3664 return
3662
3665
3663 @property
3666 @property
3664 def outdated(self):
3667 def outdated(self):
3665 return self.display_state == self.COMMENT_OUTDATED
3668 return self.display_state == self.COMMENT_OUTDATED
3666
3669
3667 def outdated_at_version(self, version):
3670 def outdated_at_version(self, version):
3668 """
3671 """
3669 Checks if comment is outdated for given pull request version
3672 Checks if comment is outdated for given pull request version
3670 """
3673 """
3671 return self.outdated and self.pull_request_version_id != version
3674 return self.outdated and self.pull_request_version_id != version
3672
3675
3673 def older_than_version(self, version):
3676 def older_than_version(self, version):
3674 """
3677 """
3675 Checks if comment is made from previous version than given
3678 Checks if comment is made from previous version than given
3676 """
3679 """
3677 if version is None:
3680 if version is None:
3678 return self.pull_request_version_id is not None
3681 return self.pull_request_version_id is not None
3679
3682
3680 return self.pull_request_version_id < version
3683 return self.pull_request_version_id < version
3681
3684
3682 @property
3685 @property
3683 def resolved(self):
3686 def resolved(self):
3684 return self.resolved_by[0] if self.resolved_by else None
3687 return self.resolved_by[0] if self.resolved_by else None
3685
3688
3686 @property
3689 @property
3687 def is_todo(self):
3690 def is_todo(self):
3688 return self.comment_type == self.COMMENT_TYPE_TODO
3691 return self.comment_type == self.COMMENT_TYPE_TODO
3689
3692
3690 @property
3693 @property
3691 def is_inline(self):
3694 def is_inline(self):
3692 return self.line_no and self.f_path
3695 return self.line_no and self.f_path
3693
3696
3694 def get_index_version(self, versions):
3697 def get_index_version(self, versions):
3695 return self.get_index_from_version(
3698 return self.get_index_from_version(
3696 self.pull_request_version_id, versions)
3699 self.pull_request_version_id, versions)
3697
3700
3698 def __repr__(self):
3701 def __repr__(self):
3699 if self.comment_id:
3702 if self.comment_id:
3700 return '<DB:Comment #%s>' % self.comment_id
3703 return '<DB:Comment #%s>' % self.comment_id
3701 else:
3704 else:
3702 return '<DB:Comment at %#x>' % id(self)
3705 return '<DB:Comment at %#x>' % id(self)
3703
3706
3704 def get_api_data(self):
3707 def get_api_data(self):
3705 comment = self
3708 comment = self
3706 data = {
3709 data = {
3707 'comment_id': comment.comment_id,
3710 'comment_id': comment.comment_id,
3708 'comment_type': comment.comment_type,
3711 'comment_type': comment.comment_type,
3709 'comment_text': comment.text,
3712 'comment_text': comment.text,
3710 'comment_status': comment.status_change,
3713 'comment_status': comment.status_change,
3711 'comment_f_path': comment.f_path,
3714 'comment_f_path': comment.f_path,
3712 'comment_lineno': comment.line_no,
3715 'comment_lineno': comment.line_no,
3713 'comment_author': comment.author,
3716 'comment_author': comment.author,
3714 'comment_created_on': comment.created_on,
3717 'comment_created_on': comment.created_on,
3715 'comment_resolved_by': self.resolved
3718 'comment_resolved_by': self.resolved
3716 }
3719 }
3717 return data
3720 return data
3718
3721
3719 def __json__(self):
3722 def __json__(self):
3720 data = dict()
3723 data = dict()
3721 data.update(self.get_api_data())
3724 data.update(self.get_api_data())
3722 return data
3725 return data
3723
3726
3724
3727
3725 class ChangesetStatus(Base, BaseModel):
3728 class ChangesetStatus(Base, BaseModel):
3726 __tablename__ = 'changeset_statuses'
3729 __tablename__ = 'changeset_statuses'
3727 __table_args__ = (
3730 __table_args__ = (
3728 Index('cs_revision_idx', 'revision'),
3731 Index('cs_revision_idx', 'revision'),
3729 Index('cs_version_idx', 'version'),
3732 Index('cs_version_idx', 'version'),
3730 UniqueConstraint('repo_id', 'revision', 'version'),
3733 UniqueConstraint('repo_id', 'revision', 'version'),
3731 base_table_args
3734 base_table_args
3732 )
3735 )
3733
3736
3734 STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed'
3737 STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed'
3735 STATUS_APPROVED = 'approved'
3738 STATUS_APPROVED = 'approved'
3736 STATUS_REJECTED = 'rejected'
3739 STATUS_REJECTED = 'rejected'
3737 STATUS_UNDER_REVIEW = 'under_review'
3740 STATUS_UNDER_REVIEW = 'under_review'
3738
3741
3739 STATUSES = [
3742 STATUSES = [
3740 (STATUS_NOT_REVIEWED, _("Not Reviewed")), # (no icon) and default
3743 (STATUS_NOT_REVIEWED, _("Not Reviewed")), # (no icon) and default
3741 (STATUS_APPROVED, _("Approved")),
3744 (STATUS_APPROVED, _("Approved")),
3742 (STATUS_REJECTED, _("Rejected")),
3745 (STATUS_REJECTED, _("Rejected")),
3743 (STATUS_UNDER_REVIEW, _("Under Review")),
3746 (STATUS_UNDER_REVIEW, _("Under Review")),
3744 ]
3747 ]
3745
3748
3746 changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True)
3749 changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True)
3747 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
3750 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
3748 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
3751 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
3749 revision = Column('revision', String(40), nullable=False)
3752 revision = Column('revision', String(40), nullable=False)
3750 status = Column('status', String(128), nullable=False, default=DEFAULT)
3753 status = Column('status', String(128), nullable=False, default=DEFAULT)
3751 changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'))
3754 changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'))
3752 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
3755 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
3753 version = Column('version', Integer(), nullable=False, default=0)
3756 version = Column('version', Integer(), nullable=False, default=0)
3754 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
3757 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
3755
3758
3756 author = relationship('User', lazy='joined')
3759 author = relationship('User', lazy='joined')
3757 repo = relationship('Repository')
3760 repo = relationship('Repository')
3758 comment = relationship('ChangesetComment', lazy='joined')
3761 comment = relationship('ChangesetComment', lazy='joined')
3759 pull_request = relationship('PullRequest', lazy='joined')
3762 pull_request = relationship('PullRequest', lazy='joined')
3760
3763
3761 def __unicode__(self):
3764 def __unicode__(self):
3762 return u"<%s('%s[v%s]:%s')>" % (
3765 return u"<%s('%s[v%s]:%s')>" % (
3763 self.__class__.__name__,
3766 self.__class__.__name__,
3764 self.status, self.version, self.author
3767 self.status, self.version, self.author
3765 )
3768 )
3766
3769
3767 @classmethod
3770 @classmethod
3768 def get_status_lbl(cls, value):
3771 def get_status_lbl(cls, value):
3769 return dict(cls.STATUSES).get(value)
3772 return dict(cls.STATUSES).get(value)
3770
3773
3771 @property
3774 @property
3772 def status_lbl(self):
3775 def status_lbl(self):
3773 return ChangesetStatus.get_status_lbl(self.status)
3776 return ChangesetStatus.get_status_lbl(self.status)
3774
3777
3775 def get_api_data(self):
3778 def get_api_data(self):
3776 status = self
3779 status = self
3777 data = {
3780 data = {
3778 'status_id': status.changeset_status_id,
3781 'status_id': status.changeset_status_id,
3779 'status': status.status,
3782 'status': status.status,
3780 }
3783 }
3781 return data
3784 return data
3782
3785
3783 def __json__(self):
3786 def __json__(self):
3784 data = dict()
3787 data = dict()
3785 data.update(self.get_api_data())
3788 data.update(self.get_api_data())
3786 return data
3789 return data
3787
3790
3788
3791
3789 class _SetState(object):
3792 class _SetState(object):
3790 """
3793 """
3791 Context processor allowing changing state for sensitive operation such as
3794 Context processor allowing changing state for sensitive operation such as
3792 pull request update or merge
3795 pull request update or merge
3793 """
3796 """
3794
3797
3795 def __init__(self, pull_request, pr_state, back_state=None):
3798 def __init__(self, pull_request, pr_state, back_state=None):
3796 self._pr = pull_request
3799 self._pr = pull_request
3797 self._org_state = back_state or pull_request.pull_request_state
3800 self._org_state = back_state or pull_request.pull_request_state
3798 self._pr_state = pr_state
3801 self._pr_state = pr_state
3799
3802
3800 def __enter__(self):
3803 def __enter__(self):
3801 log.debug('StateLock: entering set state context, setting state to: `%s`',
3804 log.debug('StateLock: entering set state context, setting state to: `%s`',
3802 self._pr_state)
3805 self._pr_state)
3803 self._pr.pull_request_state = self._pr_state
3806 self._pr.pull_request_state = self._pr_state
3804 Session().add(self._pr)
3807 Session().add(self._pr)
3805 Session().commit()
3808 Session().commit()
3806
3809
3807 def __exit__(self, exc_type, exc_val, exc_tb):
3810 def __exit__(self, exc_type, exc_val, exc_tb):
3808 log.debug('StateLock: exiting set state context, setting state to: `%s`',
3811 log.debug('StateLock: exiting set state context, setting state to: `%s`',
3809 self._org_state)
3812 self._org_state)
3810 self._pr.pull_request_state = self._org_state
3813 self._pr.pull_request_state = self._org_state
3811 Session().add(self._pr)
3814 Session().add(self._pr)
3812 Session().commit()
3815 Session().commit()
3813
3816
3814
3817
3815 class _PullRequestBase(BaseModel):
3818 class _PullRequestBase(BaseModel):
3816 """
3819 """
3817 Common attributes of pull request and version entries.
3820 Common attributes of pull request and version entries.
3818 """
3821 """
3819
3822
3820 # .status values
3823 # .status values
3821 STATUS_NEW = u'new'
3824 STATUS_NEW = u'new'
3822 STATUS_OPEN = u'open'
3825 STATUS_OPEN = u'open'
3823 STATUS_CLOSED = u'closed'
3826 STATUS_CLOSED = u'closed'
3824
3827
3825 # available states
3828 # available states
3826 STATE_CREATING = u'creating'
3829 STATE_CREATING = u'creating'
3827 STATE_UPDATING = u'updating'
3830 STATE_UPDATING = u'updating'
3828 STATE_MERGING = u'merging'
3831 STATE_MERGING = u'merging'
3829 STATE_CREATED = u'created'
3832 STATE_CREATED = u'created'
3830
3833
3831 title = Column('title', Unicode(255), nullable=True)
3834 title = Column('title', Unicode(255), nullable=True)
3832 description = Column(
3835 description = Column(
3833 'description', UnicodeText().with_variant(UnicodeText(10240), 'mysql'),
3836 'description', UnicodeText().with_variant(UnicodeText(10240), 'mysql'),
3834 nullable=True)
3837 nullable=True)
3835 description_renderer = Column('description_renderer', Unicode(64), nullable=True)
3838 description_renderer = Column('description_renderer', Unicode(64), nullable=True)
3836
3839
3837 # new/open/closed status of pull request (not approve/reject/etc)
3840 # new/open/closed status of pull request (not approve/reject/etc)
3838 status = Column('status', Unicode(255), nullable=False, default=STATUS_NEW)
3841 status = Column('status', Unicode(255), nullable=False, default=STATUS_NEW)
3839 created_on = Column(
3842 created_on = Column(
3840 'created_on', DateTime(timezone=False), nullable=False,
3843 'created_on', DateTime(timezone=False), nullable=False,
3841 default=datetime.datetime.now)
3844 default=datetime.datetime.now)
3842 updated_on = Column(
3845 updated_on = Column(
3843 'updated_on', DateTime(timezone=False), nullable=False,
3846 'updated_on', DateTime(timezone=False), nullable=False,
3844 default=datetime.datetime.now)
3847 default=datetime.datetime.now)
3845
3848
3846 pull_request_state = Column("pull_request_state", String(255), nullable=True)
3849 pull_request_state = Column("pull_request_state", String(255), nullable=True)
3847
3850
3848 @declared_attr
3851 @declared_attr
3849 def user_id(cls):
3852 def user_id(cls):
3850 return Column(
3853 return Column(
3851 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
3854 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
3852 unique=None)
3855 unique=None)
3853
3856
3854 # 500 revisions max
3857 # 500 revisions max
3855 _revisions = Column(
3858 _revisions = Column(
3856 'revisions', UnicodeText().with_variant(UnicodeText(20500), 'mysql'))
3859 'revisions', UnicodeText().with_variant(UnicodeText(20500), 'mysql'))
3857
3860
3858 @declared_attr
3861 @declared_attr
3859 def source_repo_id(cls):
3862 def source_repo_id(cls):
3860 # TODO: dan: rename column to source_repo_id
3863 # TODO: dan: rename column to source_repo_id
3861 return Column(
3864 return Column(
3862 'org_repo_id', Integer(), ForeignKey('repositories.repo_id'),
3865 'org_repo_id', Integer(), ForeignKey('repositories.repo_id'),
3863 nullable=False)
3866 nullable=False)
3864
3867
3865 _source_ref = Column('org_ref', Unicode(255), nullable=False)
3868 _source_ref = Column('org_ref', Unicode(255), nullable=False)
3866
3869
3867 @hybrid_property
3870 @hybrid_property
3868 def source_ref(self):
3871 def source_ref(self):
3869 return self._source_ref
3872 return self._source_ref
3870
3873
3871 @source_ref.setter
3874 @source_ref.setter
3872 def source_ref(self, val):
3875 def source_ref(self, val):
3873 parts = (val or '').split(':')
3876 parts = (val or '').split(':')
3874 if len(parts) != 3:
3877 if len(parts) != 3:
3875 raise ValueError(
3878 raise ValueError(
3876 'Invalid reference format given: {}, expected X:Y:Z'.format(val))
3879 'Invalid reference format given: {}, expected X:Y:Z'.format(val))
3877 self._source_ref = safe_unicode(val)
3880 self._source_ref = safe_unicode(val)
3878
3881
3879 _target_ref = Column('other_ref', Unicode(255), nullable=False)
3882 _target_ref = Column('other_ref', Unicode(255), nullable=False)
3880
3883
3881 @hybrid_property
3884 @hybrid_property
3882 def target_ref(self):
3885 def target_ref(self):
3883 return self._target_ref
3886 return self._target_ref
3884
3887
3885 @target_ref.setter
3888 @target_ref.setter
3886 def target_ref(self, val):
3889 def target_ref(self, val):
3887 parts = (val or '').split(':')
3890 parts = (val or '').split(':')
3888 if len(parts) != 3:
3891 if len(parts) != 3:
3889 raise ValueError(
3892 raise ValueError(
3890 'Invalid reference format given: {}, expected X:Y:Z'.format(val))
3893 'Invalid reference format given: {}, expected X:Y:Z'.format(val))
3891 self._target_ref = safe_unicode(val)
3894 self._target_ref = safe_unicode(val)
3892
3895
3893 @declared_attr
3896 @declared_attr
3894 def target_repo_id(cls):
3897 def target_repo_id(cls):
3895 # TODO: dan: rename column to target_repo_id
3898 # TODO: dan: rename column to target_repo_id
3896 return Column(
3899 return Column(
3897 'other_repo_id', Integer(), ForeignKey('repositories.repo_id'),
3900 'other_repo_id', Integer(), ForeignKey('repositories.repo_id'),
3898 nullable=False)
3901 nullable=False)
3899
3902
3900 _shadow_merge_ref = Column('shadow_merge_ref', Unicode(255), nullable=True)
3903 _shadow_merge_ref = Column('shadow_merge_ref', Unicode(255), nullable=True)
3901
3904
3902 # TODO: dan: rename column to last_merge_source_rev
3905 # TODO: dan: rename column to last_merge_source_rev
3903 _last_merge_source_rev = Column(
3906 _last_merge_source_rev = Column(
3904 'last_merge_org_rev', String(40), nullable=True)
3907 'last_merge_org_rev', String(40), nullable=True)
3905 # TODO: dan: rename column to last_merge_target_rev
3908 # TODO: dan: rename column to last_merge_target_rev
3906 _last_merge_target_rev = Column(
3909 _last_merge_target_rev = Column(
3907 'last_merge_other_rev', String(40), nullable=True)
3910 'last_merge_other_rev', String(40), nullable=True)
3908 _last_merge_status = Column('merge_status', Integer(), nullable=True)
3911 _last_merge_status = Column('merge_status', Integer(), nullable=True)
3909 merge_rev = Column('merge_rev', String(40), nullable=True)
3912 merge_rev = Column('merge_rev', String(40), nullable=True)
3910
3913
3911 reviewer_data = Column(
3914 reviewer_data = Column(
3912 'reviewer_data_json', MutationObj.as_mutable(
3915 'reviewer_data_json', MutationObj.as_mutable(
3913 JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
3916 JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
3914
3917
3915 @property
3918 @property
3916 def reviewer_data_json(self):
3919 def reviewer_data_json(self):
3917 return json.dumps(self.reviewer_data)
3920 return json.dumps(self.reviewer_data)
3918
3921
3919 @hybrid_property
3922 @hybrid_property
3920 def description_safe(self):
3923 def description_safe(self):
3921 from rhodecode.lib import helpers as h
3924 from rhodecode.lib import helpers as h
3922 return h.escape(self.description)
3925 return h.escape(self.description)
3923
3926
3924 @hybrid_property
3927 @hybrid_property
3925 def revisions(self):
3928 def revisions(self):
3926 return self._revisions.split(':') if self._revisions else []
3929 return self._revisions.split(':') if self._revisions else []
3927
3930
3928 @revisions.setter
3931 @revisions.setter
3929 def revisions(self, val):
3932 def revisions(self, val):
3930 self._revisions = ':'.join(val)
3933 self._revisions = ':'.join(val)
3931
3934
3932 @hybrid_property
3935 @hybrid_property
3933 def last_merge_status(self):
3936 def last_merge_status(self):
3934 return safe_int(self._last_merge_status)
3937 return safe_int(self._last_merge_status)
3935
3938
3936 @last_merge_status.setter
3939 @last_merge_status.setter
3937 def last_merge_status(self, val):
3940 def last_merge_status(self, val):
3938 self._last_merge_status = val
3941 self._last_merge_status = val
3939
3942
3940 @declared_attr
3943 @declared_attr
3941 def author(cls):
3944 def author(cls):
3942 return relationship('User', lazy='joined')
3945 return relationship('User', lazy='joined')
3943
3946
3944 @declared_attr
3947 @declared_attr
3945 def source_repo(cls):
3948 def source_repo(cls):
3946 return relationship(
3949 return relationship(
3947 'Repository',
3950 'Repository',
3948 primaryjoin='%s.source_repo_id==Repository.repo_id' % cls.__name__)
3951 primaryjoin='%s.source_repo_id==Repository.repo_id' % cls.__name__)
3949
3952
3950 @property
3953 @property
3951 def source_ref_parts(self):
3954 def source_ref_parts(self):
3952 return self.unicode_to_reference(self.source_ref)
3955 return self.unicode_to_reference(self.source_ref)
3953
3956
3954 @declared_attr
3957 @declared_attr
3955 def target_repo(cls):
3958 def target_repo(cls):
3956 return relationship(
3959 return relationship(
3957 'Repository',
3960 'Repository',
3958 primaryjoin='%s.target_repo_id==Repository.repo_id' % cls.__name__)
3961 primaryjoin='%s.target_repo_id==Repository.repo_id' % cls.__name__)
3959
3962
3960 @property
3963 @property
3961 def target_ref_parts(self):
3964 def target_ref_parts(self):
3962 return self.unicode_to_reference(self.target_ref)
3965 return self.unicode_to_reference(self.target_ref)
3963
3966
3964 @property
3967 @property
3965 def shadow_merge_ref(self):
3968 def shadow_merge_ref(self):
3966 return self.unicode_to_reference(self._shadow_merge_ref)
3969 return self.unicode_to_reference(self._shadow_merge_ref)
3967
3970
3968 @shadow_merge_ref.setter
3971 @shadow_merge_ref.setter
3969 def shadow_merge_ref(self, ref):
3972 def shadow_merge_ref(self, ref):
3970 self._shadow_merge_ref = self.reference_to_unicode(ref)
3973 self._shadow_merge_ref = self.reference_to_unicode(ref)
3971
3974
3972 @staticmethod
3975 @staticmethod
3973 def unicode_to_reference(raw):
3976 def unicode_to_reference(raw):
3974 """
3977 """
3975 Convert a unicode (or string) to a reference object.
3978 Convert a unicode (or string) to a reference object.
3976 If unicode evaluates to False it returns None.
3979 If unicode evaluates to False it returns None.
3977 """
3980 """
3978 if raw:
3981 if raw:
3979 refs = raw.split(':')
3982 refs = raw.split(':')
3980 return Reference(*refs)
3983 return Reference(*refs)
3981 else:
3984 else:
3982 return None
3985 return None
3983
3986
3984 @staticmethod
3987 @staticmethod
3985 def reference_to_unicode(ref):
3988 def reference_to_unicode(ref):
3986 """
3989 """
3987 Convert a reference object to unicode.
3990 Convert a reference object to unicode.
3988 If reference is None it returns None.
3991 If reference is None it returns None.
3989 """
3992 """
3990 if ref:
3993 if ref:
3991 return u':'.join(ref)
3994 return u':'.join(ref)
3992 else:
3995 else:
3993 return None
3996 return None
3994
3997
3995 def get_api_data(self, with_merge_state=True):
3998 def get_api_data(self, with_merge_state=True):
3996 from rhodecode.model.pull_request import PullRequestModel
3999 from rhodecode.model.pull_request import PullRequestModel
3997
4000
3998 pull_request = self
4001 pull_request = self
3999 if with_merge_state:
4002 if with_merge_state:
4000 merge_status = PullRequestModel().merge_status(pull_request)
4003 merge_status = PullRequestModel().merge_status(pull_request)
4001 merge_state = {
4004 merge_state = {
4002 'status': merge_status[0],
4005 'status': merge_status[0],
4003 'message': safe_unicode(merge_status[1]),
4006 'message': safe_unicode(merge_status[1]),
4004 }
4007 }
4005 else:
4008 else:
4006 merge_state = {'status': 'not_available',
4009 merge_state = {'status': 'not_available',
4007 'message': 'not_available'}
4010 'message': 'not_available'}
4008
4011
4009 merge_data = {
4012 merge_data = {
4010 'clone_url': PullRequestModel().get_shadow_clone_url(pull_request),
4013 'clone_url': PullRequestModel().get_shadow_clone_url(pull_request),
4011 'reference': (
4014 'reference': (
4012 pull_request.shadow_merge_ref._asdict()
4015 pull_request.shadow_merge_ref._asdict()
4013 if pull_request.shadow_merge_ref else None),
4016 if pull_request.shadow_merge_ref else None),
4014 }
4017 }
4015
4018
4016 data = {
4019 data = {
4017 'pull_request_id': pull_request.pull_request_id,
4020 'pull_request_id': pull_request.pull_request_id,
4018 'url': PullRequestModel().get_url(pull_request),
4021 'url': PullRequestModel().get_url(pull_request),
4019 'title': pull_request.title,
4022 'title': pull_request.title,
4020 'description': pull_request.description,
4023 'description': pull_request.description,
4021 'status': pull_request.status,
4024 'status': pull_request.status,
4022 'state': pull_request.pull_request_state,
4025 'state': pull_request.pull_request_state,
4023 'created_on': pull_request.created_on,
4026 'created_on': pull_request.created_on,
4024 'updated_on': pull_request.updated_on,
4027 'updated_on': pull_request.updated_on,
4025 'commit_ids': pull_request.revisions,
4028 'commit_ids': pull_request.revisions,
4026 'review_status': pull_request.calculated_review_status(),
4029 'review_status': pull_request.calculated_review_status(),
4027 'mergeable': merge_state,
4030 'mergeable': merge_state,
4028 'source': {
4031 'source': {
4029 'clone_url': pull_request.source_repo.clone_url(),
4032 'clone_url': pull_request.source_repo.clone_url(),
4030 'repository': pull_request.source_repo.repo_name,
4033 'repository': pull_request.source_repo.repo_name,
4031 'reference': {
4034 'reference': {
4032 'name': pull_request.source_ref_parts.name,
4035 'name': pull_request.source_ref_parts.name,
4033 'type': pull_request.source_ref_parts.type,
4036 'type': pull_request.source_ref_parts.type,
4034 'commit_id': pull_request.source_ref_parts.commit_id,
4037 'commit_id': pull_request.source_ref_parts.commit_id,
4035 },
4038 },
4036 },
4039 },
4037 'target': {
4040 'target': {
4038 'clone_url': pull_request.target_repo.clone_url(),
4041 'clone_url': pull_request.target_repo.clone_url(),
4039 'repository': pull_request.target_repo.repo_name,
4042 'repository': pull_request.target_repo.repo_name,
4040 'reference': {
4043 'reference': {
4041 'name': pull_request.target_ref_parts.name,
4044 'name': pull_request.target_ref_parts.name,
4042 'type': pull_request.target_ref_parts.type,
4045 'type': pull_request.target_ref_parts.type,
4043 'commit_id': pull_request.target_ref_parts.commit_id,
4046 'commit_id': pull_request.target_ref_parts.commit_id,
4044 },
4047 },
4045 },
4048 },
4046 'merge': merge_data,
4049 'merge': merge_data,
4047 'author': pull_request.author.get_api_data(include_secrets=False,
4050 'author': pull_request.author.get_api_data(include_secrets=False,
4048 details='basic'),
4051 details='basic'),
4049 'reviewers': [
4052 'reviewers': [
4050 {
4053 {
4051 'user': reviewer.get_api_data(include_secrets=False,
4054 'user': reviewer.get_api_data(include_secrets=False,
4052 details='basic'),
4055 details='basic'),
4053 'reasons': reasons,
4056 'reasons': reasons,
4054 'review_status': st[0][1].status if st else 'not_reviewed',
4057 'review_status': st[0][1].status if st else 'not_reviewed',
4055 }
4058 }
4056 for obj, reviewer, reasons, mandatory, st in
4059 for obj, reviewer, reasons, mandatory, st in
4057 pull_request.reviewers_statuses()
4060 pull_request.reviewers_statuses()
4058 ]
4061 ]
4059 }
4062 }
4060
4063
4061 return data
4064 return data
4062
4065
4063 def set_state(self, pull_request_state, final_state=None):
4066 def set_state(self, pull_request_state, final_state=None):
4064 """
4067 """
4065 # goes from initial state to updating to initial state.
4068 # goes from initial state to updating to initial state.
4066 # initial state can be changed by specifying back_state=
4069 # initial state can be changed by specifying back_state=
4067 with pull_request_obj.set_state(PullRequest.STATE_UPDATING):
4070 with pull_request_obj.set_state(PullRequest.STATE_UPDATING):
4068 pull_request.merge()
4071 pull_request.merge()
4069
4072
4070 :param pull_request_state:
4073 :param pull_request_state:
4071 :param final_state:
4074 :param final_state:
4072
4075
4073 """
4076 """
4074
4077
4075 return _SetState(self, pull_request_state, back_state=final_state)
4078 return _SetState(self, pull_request_state, back_state=final_state)
4076
4079
4077
4080
4078 class PullRequest(Base, _PullRequestBase):
4081 class PullRequest(Base, _PullRequestBase):
4079 __tablename__ = 'pull_requests'
4082 __tablename__ = 'pull_requests'
4080 __table_args__ = (
4083 __table_args__ = (
4081 base_table_args,
4084 base_table_args,
4082 )
4085 )
4083
4086
4084 pull_request_id = Column(
4087 pull_request_id = Column(
4085 'pull_request_id', Integer(), nullable=False, primary_key=True)
4088 'pull_request_id', Integer(), nullable=False, primary_key=True)
4086
4089
4087 def __repr__(self):
4090 def __repr__(self):
4088 if self.pull_request_id:
4091 if self.pull_request_id:
4089 return '<DB:PullRequest #%s>' % self.pull_request_id
4092 return '<DB:PullRequest #%s>' % self.pull_request_id
4090 else:
4093 else:
4091 return '<DB:PullRequest at %#x>' % id(self)
4094 return '<DB:PullRequest at %#x>' % id(self)
4092
4095
4093 reviewers = relationship('PullRequestReviewers',
4096 reviewers = relationship('PullRequestReviewers',
4094 cascade="all, delete, delete-orphan")
4097 cascade="all, delete, delete-orphan")
4095 statuses = relationship('ChangesetStatus',
4098 statuses = relationship('ChangesetStatus',
4096 cascade="all, delete, delete-orphan")
4099 cascade="all, delete, delete-orphan")
4097 comments = relationship('ChangesetComment',
4100 comments = relationship('ChangesetComment',
4098 cascade="all, delete, delete-orphan")
4101 cascade="all, delete, delete-orphan")
4099 versions = relationship('PullRequestVersion',
4102 versions = relationship('PullRequestVersion',
4100 cascade="all, delete, delete-orphan",
4103 cascade="all, delete, delete-orphan",
4101 lazy='dynamic')
4104 lazy='dynamic')
4102
4105
4103 @classmethod
4106 @classmethod
4104 def get_pr_display_object(cls, pull_request_obj, org_pull_request_obj,
4107 def get_pr_display_object(cls, pull_request_obj, org_pull_request_obj,
4105 internal_methods=None):
4108 internal_methods=None):
4106
4109
4107 class PullRequestDisplay(object):
4110 class PullRequestDisplay(object):
4108 """
4111 """
4109 Special object wrapper for showing PullRequest data via Versions
4112 Special object wrapper for showing PullRequest data via Versions
4110 It mimics PR object as close as possible. This is read only object
4113 It mimics PR object as close as possible. This is read only object
4111 just for display
4114 just for display
4112 """
4115 """
4113
4116
4114 def __init__(self, attrs, internal=None):
4117 def __init__(self, attrs, internal=None):
4115 self.attrs = attrs
4118 self.attrs = attrs
4116 # internal have priority over the given ones via attrs
4119 # internal have priority over the given ones via attrs
4117 self.internal = internal or ['versions']
4120 self.internal = internal or ['versions']
4118
4121
4119 def __getattr__(self, item):
4122 def __getattr__(self, item):
4120 if item in self.internal:
4123 if item in self.internal:
4121 return getattr(self, item)
4124 return getattr(self, item)
4122 try:
4125 try:
4123 return self.attrs[item]
4126 return self.attrs[item]
4124 except KeyError:
4127 except KeyError:
4125 raise AttributeError(
4128 raise AttributeError(
4126 '%s object has no attribute %s' % (self, item))
4129 '%s object has no attribute %s' % (self, item))
4127
4130
4128 def __repr__(self):
4131 def __repr__(self):
4129 return '<DB:PullRequestDisplay #%s>' % self.attrs.get('pull_request_id')
4132 return '<DB:PullRequestDisplay #%s>' % self.attrs.get('pull_request_id')
4130
4133
4131 def versions(self):
4134 def versions(self):
4132 return pull_request_obj.versions.order_by(
4135 return pull_request_obj.versions.order_by(
4133 PullRequestVersion.pull_request_version_id).all()
4136 PullRequestVersion.pull_request_version_id).all()
4134
4137
4135 def is_closed(self):
4138 def is_closed(self):
4136 return pull_request_obj.is_closed()
4139 return pull_request_obj.is_closed()
4137
4140
4138 @property
4141 @property
4139 def pull_request_version_id(self):
4142 def pull_request_version_id(self):
4140 return getattr(pull_request_obj, 'pull_request_version_id', None)
4143 return getattr(pull_request_obj, 'pull_request_version_id', None)
4141
4144
4142 attrs = StrictAttributeDict(pull_request_obj.get_api_data(with_merge_state=False))
4145 attrs = StrictAttributeDict(pull_request_obj.get_api_data(with_merge_state=False))
4143
4146
4144 attrs.author = StrictAttributeDict(
4147 attrs.author = StrictAttributeDict(
4145 pull_request_obj.author.get_api_data())
4148 pull_request_obj.author.get_api_data())
4146 if pull_request_obj.target_repo:
4149 if pull_request_obj.target_repo:
4147 attrs.target_repo = StrictAttributeDict(
4150 attrs.target_repo = StrictAttributeDict(
4148 pull_request_obj.target_repo.get_api_data())
4151 pull_request_obj.target_repo.get_api_data())
4149 attrs.target_repo.clone_url = pull_request_obj.target_repo.clone_url
4152 attrs.target_repo.clone_url = pull_request_obj.target_repo.clone_url
4150
4153
4151 if pull_request_obj.source_repo:
4154 if pull_request_obj.source_repo:
4152 attrs.source_repo = StrictAttributeDict(
4155 attrs.source_repo = StrictAttributeDict(
4153 pull_request_obj.source_repo.get_api_data())
4156 pull_request_obj.source_repo.get_api_data())
4154 attrs.source_repo.clone_url = pull_request_obj.source_repo.clone_url
4157 attrs.source_repo.clone_url = pull_request_obj.source_repo.clone_url
4155
4158
4156 attrs.source_ref_parts = pull_request_obj.source_ref_parts
4159 attrs.source_ref_parts = pull_request_obj.source_ref_parts
4157 attrs.target_ref_parts = pull_request_obj.target_ref_parts
4160 attrs.target_ref_parts = pull_request_obj.target_ref_parts
4158 attrs.revisions = pull_request_obj.revisions
4161 attrs.revisions = pull_request_obj.revisions
4159
4162
4160 attrs.shadow_merge_ref = org_pull_request_obj.shadow_merge_ref
4163 attrs.shadow_merge_ref = org_pull_request_obj.shadow_merge_ref
4161 attrs.reviewer_data = org_pull_request_obj.reviewer_data
4164 attrs.reviewer_data = org_pull_request_obj.reviewer_data
4162 attrs.reviewer_data_json = org_pull_request_obj.reviewer_data_json
4165 attrs.reviewer_data_json = org_pull_request_obj.reviewer_data_json
4163
4166
4164 return PullRequestDisplay(attrs, internal=internal_methods)
4167 return PullRequestDisplay(attrs, internal=internal_methods)
4165
4168
4166 def is_closed(self):
4169 def is_closed(self):
4167 return self.status == self.STATUS_CLOSED
4170 return self.status == self.STATUS_CLOSED
4168
4171
4169 def __json__(self):
4172 def __json__(self):
4170 return {
4173 return {
4171 'revisions': self.revisions,
4174 'revisions': self.revisions,
4172 }
4175 }
4173
4176
4174 def calculated_review_status(self):
4177 def calculated_review_status(self):
4175 from rhodecode.model.changeset_status import ChangesetStatusModel
4178 from rhodecode.model.changeset_status import ChangesetStatusModel
4176 return ChangesetStatusModel().calculated_review_status(self)
4179 return ChangesetStatusModel().calculated_review_status(self)
4177
4180
4178 def reviewers_statuses(self):
4181 def reviewers_statuses(self):
4179 from rhodecode.model.changeset_status import ChangesetStatusModel
4182 from rhodecode.model.changeset_status import ChangesetStatusModel
4180 return ChangesetStatusModel().reviewers_statuses(self)
4183 return ChangesetStatusModel().reviewers_statuses(self)
4181
4184
4182 @property
4185 @property
4183 def workspace_id(self):
4186 def workspace_id(self):
4184 from rhodecode.model.pull_request import PullRequestModel
4187 from rhodecode.model.pull_request import PullRequestModel
4185 return PullRequestModel()._workspace_id(self)
4188 return PullRequestModel()._workspace_id(self)
4186
4189
4187 def get_shadow_repo(self):
4190 def get_shadow_repo(self):
4188 workspace_id = self.workspace_id
4191 workspace_id = self.workspace_id
4189 vcs_obj = self.target_repo.scm_instance()
4192 vcs_obj = self.target_repo.scm_instance()
4190 shadow_repository_path = vcs_obj._get_shadow_repository_path(
4193 shadow_repository_path = vcs_obj._get_shadow_repository_path(
4191 self.target_repo.repo_id, workspace_id)
4194 self.target_repo.repo_id, workspace_id)
4192 if os.path.isdir(shadow_repository_path):
4195 if os.path.isdir(shadow_repository_path):
4193 return vcs_obj.get_shadow_instance(shadow_repository_path)
4196 return vcs_obj.get_shadow_instance(shadow_repository_path)
4194
4197
4195
4198
4196 class PullRequestVersion(Base, _PullRequestBase):
4199 class PullRequestVersion(Base, _PullRequestBase):
4197 __tablename__ = 'pull_request_versions'
4200 __tablename__ = 'pull_request_versions'
4198 __table_args__ = (
4201 __table_args__ = (
4199 base_table_args,
4202 base_table_args,
4200 )
4203 )
4201
4204
4202 pull_request_version_id = Column(
4205 pull_request_version_id = Column(
4203 'pull_request_version_id', Integer(), nullable=False, primary_key=True)
4206 'pull_request_version_id', Integer(), nullable=False, primary_key=True)
4204 pull_request_id = Column(
4207 pull_request_id = Column(
4205 'pull_request_id', Integer(),
4208 'pull_request_id', Integer(),
4206 ForeignKey('pull_requests.pull_request_id'), nullable=False)
4209 ForeignKey('pull_requests.pull_request_id'), nullable=False)
4207 pull_request = relationship('PullRequest')
4210 pull_request = relationship('PullRequest')
4208
4211
4209 def __repr__(self):
4212 def __repr__(self):
4210 if self.pull_request_version_id:
4213 if self.pull_request_version_id:
4211 return '<DB:PullRequestVersion #%s>' % self.pull_request_version_id
4214 return '<DB:PullRequestVersion #%s>' % self.pull_request_version_id
4212 else:
4215 else:
4213 return '<DB:PullRequestVersion at %#x>' % id(self)
4216 return '<DB:PullRequestVersion at %#x>' % id(self)
4214
4217
4215 @property
4218 @property
4216 def reviewers(self):
4219 def reviewers(self):
4217 return self.pull_request.reviewers
4220 return self.pull_request.reviewers
4218
4221
4219 @property
4222 @property
4220 def versions(self):
4223 def versions(self):
4221 return self.pull_request.versions
4224 return self.pull_request.versions
4222
4225
4223 def is_closed(self):
4226 def is_closed(self):
4224 # calculate from original
4227 # calculate from original
4225 return self.pull_request.status == self.STATUS_CLOSED
4228 return self.pull_request.status == self.STATUS_CLOSED
4226
4229
4227 def calculated_review_status(self):
4230 def calculated_review_status(self):
4228 return self.pull_request.calculated_review_status()
4231 return self.pull_request.calculated_review_status()
4229
4232
4230 def reviewers_statuses(self):
4233 def reviewers_statuses(self):
4231 return self.pull_request.reviewers_statuses()
4234 return self.pull_request.reviewers_statuses()
4232
4235
4233
4236
4234 class PullRequestReviewers(Base, BaseModel):
4237 class PullRequestReviewers(Base, BaseModel):
4235 __tablename__ = 'pull_request_reviewers'
4238 __tablename__ = 'pull_request_reviewers'
4236 __table_args__ = (
4239 __table_args__ = (
4237 base_table_args,
4240 base_table_args,
4238 )
4241 )
4239
4242
4240 @hybrid_property
4243 @hybrid_property
4241 def reasons(self):
4244 def reasons(self):
4242 if not self._reasons:
4245 if not self._reasons:
4243 return []
4246 return []
4244 return self._reasons
4247 return self._reasons
4245
4248
4246 @reasons.setter
4249 @reasons.setter
4247 def reasons(self, val):
4250 def reasons(self, val):
4248 val = val or []
4251 val = val or []
4249 if any(not isinstance(x, compat.string_types) for x in val):
4252 if any(not isinstance(x, compat.string_types) for x in val):
4250 raise Exception('invalid reasons type, must be list of strings')
4253 raise Exception('invalid reasons type, must be list of strings')
4251 self._reasons = val
4254 self._reasons = val
4252
4255
4253 pull_requests_reviewers_id = Column(
4256 pull_requests_reviewers_id = Column(
4254 'pull_requests_reviewers_id', Integer(), nullable=False,
4257 'pull_requests_reviewers_id', Integer(), nullable=False,
4255 primary_key=True)
4258 primary_key=True)
4256 pull_request_id = Column(
4259 pull_request_id = Column(
4257 "pull_request_id", Integer(),
4260 "pull_request_id", Integer(),
4258 ForeignKey('pull_requests.pull_request_id'), nullable=False)
4261 ForeignKey('pull_requests.pull_request_id'), nullable=False)
4259 user_id = Column(
4262 user_id = Column(
4260 "user_id", Integer(), ForeignKey('users.user_id'), nullable=True)
4263 "user_id", Integer(), ForeignKey('users.user_id'), nullable=True)
4261 _reasons = Column(
4264 _reasons = Column(
4262 'reason', MutationList.as_mutable(
4265 'reason', MutationList.as_mutable(
4263 JsonType('list', dialect_map=dict(mysql=UnicodeText(16384)))))
4266 JsonType('list', dialect_map=dict(mysql=UnicodeText(16384)))))
4264
4267
4265 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
4268 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
4266 user = relationship('User')
4269 user = relationship('User')
4267 pull_request = relationship('PullRequest')
4270 pull_request = relationship('PullRequest')
4268
4271
4269 rule_data = Column(
4272 rule_data = Column(
4270 'rule_data_json',
4273 'rule_data_json',
4271 JsonType(dialect_map=dict(mysql=UnicodeText(16384))))
4274 JsonType(dialect_map=dict(mysql=UnicodeText(16384))))
4272
4275
4273 def rule_user_group_data(self):
4276 def rule_user_group_data(self):
4274 """
4277 """
4275 Returns the voting user group rule data for this reviewer
4278 Returns the voting user group rule data for this reviewer
4276 """
4279 """
4277
4280
4278 if self.rule_data and 'vote_rule' in self.rule_data:
4281 if self.rule_data and 'vote_rule' in self.rule_data:
4279 user_group_data = {}
4282 user_group_data = {}
4280 if 'rule_user_group_entry_id' in self.rule_data:
4283 if 'rule_user_group_entry_id' in self.rule_data:
4281 # means a group with voting rules !
4284 # means a group with voting rules !
4282 user_group_data['id'] = self.rule_data['rule_user_group_entry_id']
4285 user_group_data['id'] = self.rule_data['rule_user_group_entry_id']
4283 user_group_data['name'] = self.rule_data['rule_name']
4286 user_group_data['name'] = self.rule_data['rule_name']
4284 user_group_data['vote_rule'] = self.rule_data['vote_rule']
4287 user_group_data['vote_rule'] = self.rule_data['vote_rule']
4285
4288
4286 return user_group_data
4289 return user_group_data
4287
4290
4288 def __unicode__(self):
4291 def __unicode__(self):
4289 return u"<%s('id:%s')>" % (self.__class__.__name__,
4292 return u"<%s('id:%s')>" % (self.__class__.__name__,
4290 self.pull_requests_reviewers_id)
4293 self.pull_requests_reviewers_id)
4291
4294
4292
4295
4293 class Notification(Base, BaseModel):
4296 class Notification(Base, BaseModel):
4294 __tablename__ = 'notifications'
4297 __tablename__ = 'notifications'
4295 __table_args__ = (
4298 __table_args__ = (
4296 Index('notification_type_idx', 'type'),
4299 Index('notification_type_idx', 'type'),
4297 base_table_args,
4300 base_table_args,
4298 )
4301 )
4299
4302
4300 TYPE_CHANGESET_COMMENT = u'cs_comment'
4303 TYPE_CHANGESET_COMMENT = u'cs_comment'
4301 TYPE_MESSAGE = u'message'
4304 TYPE_MESSAGE = u'message'
4302 TYPE_MENTION = u'mention'
4305 TYPE_MENTION = u'mention'
4303 TYPE_REGISTRATION = u'registration'
4306 TYPE_REGISTRATION = u'registration'
4304 TYPE_PULL_REQUEST = u'pull_request'
4307 TYPE_PULL_REQUEST = u'pull_request'
4305 TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment'
4308 TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment'
4306
4309
4307 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
4310 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
4308 subject = Column('subject', Unicode(512), nullable=True)
4311 subject = Column('subject', Unicode(512), nullable=True)
4309 body = Column('body', UnicodeText().with_variant(UnicodeText(50000), 'mysql'), nullable=True)
4312 body = Column('body', UnicodeText().with_variant(UnicodeText(50000), 'mysql'), nullable=True)
4310 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
4313 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
4311 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4314 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4312 type_ = Column('type', Unicode(255))
4315 type_ = Column('type', Unicode(255))
4313
4316
4314 created_by_user = relationship('User')
4317 created_by_user = relationship('User')
4315 notifications_to_users = relationship('UserNotification', lazy='joined',
4318 notifications_to_users = relationship('UserNotification', lazy='joined',
4316 cascade="all, delete, delete-orphan")
4319 cascade="all, delete, delete-orphan")
4317
4320
4318 @property
4321 @property
4319 def recipients(self):
4322 def recipients(self):
4320 return [x.user for x in UserNotification.query()\
4323 return [x.user for x in UserNotification.query()\
4321 .filter(UserNotification.notification == self)\
4324 .filter(UserNotification.notification == self)\
4322 .order_by(UserNotification.user_id.asc()).all()]
4325 .order_by(UserNotification.user_id.asc()).all()]
4323
4326
4324 @classmethod
4327 @classmethod
4325 def create(cls, created_by, subject, body, recipients, type_=None):
4328 def create(cls, created_by, subject, body, recipients, type_=None):
4326 if type_ is None:
4329 if type_ is None:
4327 type_ = Notification.TYPE_MESSAGE
4330 type_ = Notification.TYPE_MESSAGE
4328
4331
4329 notification = cls()
4332 notification = cls()
4330 notification.created_by_user = created_by
4333 notification.created_by_user = created_by
4331 notification.subject = subject
4334 notification.subject = subject
4332 notification.body = body
4335 notification.body = body
4333 notification.type_ = type_
4336 notification.type_ = type_
4334 notification.created_on = datetime.datetime.now()
4337 notification.created_on = datetime.datetime.now()
4335
4338
4336 # For each recipient link the created notification to his account
4339 # For each recipient link the created notification to his account
4337 for u in recipients:
4340 for u in recipients:
4338 assoc = UserNotification()
4341 assoc = UserNotification()
4339 assoc.user_id = u.user_id
4342 assoc.user_id = u.user_id
4340 assoc.notification = notification
4343 assoc.notification = notification
4341
4344
4342 # if created_by is inside recipients mark his notification
4345 # if created_by is inside recipients mark his notification
4343 # as read
4346 # as read
4344 if u.user_id == created_by.user_id:
4347 if u.user_id == created_by.user_id:
4345 assoc.read = True
4348 assoc.read = True
4346 Session().add(assoc)
4349 Session().add(assoc)
4347
4350
4348 Session().add(notification)
4351 Session().add(notification)
4349
4352
4350 return notification
4353 return notification
4351
4354
4352
4355
4353 class UserNotification(Base, BaseModel):
4356 class UserNotification(Base, BaseModel):
4354 __tablename__ = 'user_to_notification'
4357 __tablename__ = 'user_to_notification'
4355 __table_args__ = (
4358 __table_args__ = (
4356 UniqueConstraint('user_id', 'notification_id'),
4359 UniqueConstraint('user_id', 'notification_id'),
4357 base_table_args
4360 base_table_args
4358 )
4361 )
4359
4362
4360 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
4363 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
4361 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
4364 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
4362 read = Column('read', Boolean, default=False)
4365 read = Column('read', Boolean, default=False)
4363 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
4366 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
4364
4367
4365 user = relationship('User', lazy="joined")
4368 user = relationship('User', lazy="joined")
4366 notification = relationship('Notification', lazy="joined",
4369 notification = relationship('Notification', lazy="joined",
4367 order_by=lambda: Notification.created_on.desc(),)
4370 order_by=lambda: Notification.created_on.desc(),)
4368
4371
4369 def mark_as_read(self):
4372 def mark_as_read(self):
4370 self.read = True
4373 self.read = True
4371 Session().add(self)
4374 Session().add(self)
4372
4375
4373
4376
4374 class Gist(Base, BaseModel):
4377 class Gist(Base, BaseModel):
4375 __tablename__ = 'gists'
4378 __tablename__ = 'gists'
4376 __table_args__ = (
4379 __table_args__ = (
4377 Index('g_gist_access_id_idx', 'gist_access_id'),
4380 Index('g_gist_access_id_idx', 'gist_access_id'),
4378 Index('g_created_on_idx', 'created_on'),
4381 Index('g_created_on_idx', 'created_on'),
4379 base_table_args
4382 base_table_args
4380 )
4383 )
4381
4384
4382 GIST_PUBLIC = u'public'
4385 GIST_PUBLIC = u'public'
4383 GIST_PRIVATE = u'private'
4386 GIST_PRIVATE = u'private'
4384 DEFAULT_FILENAME = u'gistfile1.txt'
4387 DEFAULT_FILENAME = u'gistfile1.txt'
4385
4388
4386 ACL_LEVEL_PUBLIC = u'acl_public'
4389 ACL_LEVEL_PUBLIC = u'acl_public'
4387 ACL_LEVEL_PRIVATE = u'acl_private'
4390 ACL_LEVEL_PRIVATE = u'acl_private'
4388
4391
4389 gist_id = Column('gist_id', Integer(), primary_key=True)
4392 gist_id = Column('gist_id', Integer(), primary_key=True)
4390 gist_access_id = Column('gist_access_id', Unicode(250))
4393 gist_access_id = Column('gist_access_id', Unicode(250))
4391 gist_description = Column('gist_description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
4394 gist_description = Column('gist_description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
4392 gist_owner = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True)
4395 gist_owner = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True)
4393 gist_expires = Column('gist_expires', Float(53), nullable=False)
4396 gist_expires = Column('gist_expires', Float(53), nullable=False)
4394 gist_type = Column('gist_type', Unicode(128), nullable=False)
4397 gist_type = Column('gist_type', Unicode(128), nullable=False)
4395 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4398 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4396 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4399 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4397 acl_level = Column('acl_level', Unicode(128), nullable=True)
4400 acl_level = Column('acl_level', Unicode(128), nullable=True)
4398
4401
4399 owner = relationship('User')
4402 owner = relationship('User')
4400
4403
4401 def __repr__(self):
4404 def __repr__(self):
4402 return '<Gist:[%s]%s>' % (self.gist_type, self.gist_access_id)
4405 return '<Gist:[%s]%s>' % (self.gist_type, self.gist_access_id)
4403
4406
4404 @hybrid_property
4407 @hybrid_property
4405 def description_safe(self):
4408 def description_safe(self):
4406 from rhodecode.lib import helpers as h
4409 from rhodecode.lib import helpers as h
4407 return h.escape(self.gist_description)
4410 return h.escape(self.gist_description)
4408
4411
4409 @classmethod
4412 @classmethod
4410 def get_or_404(cls, id_):
4413 def get_or_404(cls, id_):
4411 from pyramid.httpexceptions import HTTPNotFound
4414 from pyramid.httpexceptions import HTTPNotFound
4412
4415
4413 res = cls.query().filter(cls.gist_access_id == id_).scalar()
4416 res = cls.query().filter(cls.gist_access_id == id_).scalar()
4414 if not res:
4417 if not res:
4415 raise HTTPNotFound()
4418 raise HTTPNotFound()
4416 return res
4419 return res
4417
4420
4418 @classmethod
4421 @classmethod
4419 def get_by_access_id(cls, gist_access_id):
4422 def get_by_access_id(cls, gist_access_id):
4420 return cls.query().filter(cls.gist_access_id == gist_access_id).scalar()
4423 return cls.query().filter(cls.gist_access_id == gist_access_id).scalar()
4421
4424
4422 def gist_url(self):
4425 def gist_url(self):
4423 from rhodecode.model.gist import GistModel
4426 from rhodecode.model.gist import GistModel
4424 return GistModel().get_url(self)
4427 return GistModel().get_url(self)
4425
4428
4426 @classmethod
4429 @classmethod
4427 def base_path(cls):
4430 def base_path(cls):
4428 """
4431 """
4429 Returns base path when all gists are stored
4432 Returns base path when all gists are stored
4430
4433
4431 :param cls:
4434 :param cls:
4432 """
4435 """
4433 from rhodecode.model.gist import GIST_STORE_LOC
4436 from rhodecode.model.gist import GIST_STORE_LOC
4434 q = Session().query(RhodeCodeUi)\
4437 q = Session().query(RhodeCodeUi)\
4435 .filter(RhodeCodeUi.ui_key == URL_SEP)
4438 .filter(RhodeCodeUi.ui_key == URL_SEP)
4436 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
4439 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
4437 return os.path.join(q.one().ui_value, GIST_STORE_LOC)
4440 return os.path.join(q.one().ui_value, GIST_STORE_LOC)
4438
4441
4439 def get_api_data(self):
4442 def get_api_data(self):
4440 """
4443 """
4441 Common function for generating gist related data for API
4444 Common function for generating gist related data for API
4442 """
4445 """
4443 gist = self
4446 gist = self
4444 data = {
4447 data = {
4445 'gist_id': gist.gist_id,
4448 'gist_id': gist.gist_id,
4446 'type': gist.gist_type,
4449 'type': gist.gist_type,
4447 'access_id': gist.gist_access_id,
4450 'access_id': gist.gist_access_id,
4448 'description': gist.gist_description,
4451 'description': gist.gist_description,
4449 'url': gist.gist_url(),
4452 'url': gist.gist_url(),
4450 'expires': gist.gist_expires,
4453 'expires': gist.gist_expires,
4451 'created_on': gist.created_on,
4454 'created_on': gist.created_on,
4452 'modified_at': gist.modified_at,
4455 'modified_at': gist.modified_at,
4453 'content': None,
4456 'content': None,
4454 'acl_level': gist.acl_level,
4457 'acl_level': gist.acl_level,
4455 }
4458 }
4456 return data
4459 return data
4457
4460
4458 def __json__(self):
4461 def __json__(self):
4459 data = dict(
4462 data = dict(
4460 )
4463 )
4461 data.update(self.get_api_data())
4464 data.update(self.get_api_data())
4462 return data
4465 return data
4463 # SCM functions
4466 # SCM functions
4464
4467
4465 def scm_instance(self, **kwargs):
4468 def scm_instance(self, **kwargs):
4466 """
4469 """
4467 Get an instance of VCS Repository
4470 Get an instance of VCS Repository
4468
4471
4469 :param kwargs:
4472 :param kwargs:
4470 """
4473 """
4471 from rhodecode.model.gist import GistModel
4474 from rhodecode.model.gist import GistModel
4472 full_repo_path = os.path.join(self.base_path(), self.gist_access_id)
4475 full_repo_path = os.path.join(self.base_path(), self.gist_access_id)
4473 return get_vcs_instance(
4476 return get_vcs_instance(
4474 repo_path=safe_str(full_repo_path), create=False,
4477 repo_path=safe_str(full_repo_path), create=False,
4475 _vcs_alias=GistModel.vcs_backend)
4478 _vcs_alias=GistModel.vcs_backend)
4476
4479
4477
4480
4478 class ExternalIdentity(Base, BaseModel):
4481 class ExternalIdentity(Base, BaseModel):
4479 __tablename__ = 'external_identities'
4482 __tablename__ = 'external_identities'
4480 __table_args__ = (
4483 __table_args__ = (
4481 Index('local_user_id_idx', 'local_user_id'),
4484 Index('local_user_id_idx', 'local_user_id'),
4482 Index('external_id_idx', 'external_id'),
4485 Index('external_id_idx', 'external_id'),
4483 base_table_args
4486 base_table_args
4484 )
4487 )
4485
4488
4486 external_id = Column('external_id', Unicode(255), default=u'', primary_key=True)
4489 external_id = Column('external_id', Unicode(255), default=u'', primary_key=True)
4487 external_username = Column('external_username', Unicode(1024), default=u'')
4490 external_username = Column('external_username', Unicode(1024), default=u'')
4488 local_user_id = Column('local_user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
4491 local_user_id = Column('local_user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
4489 provider_name = Column('provider_name', Unicode(255), default=u'', primary_key=True)
4492 provider_name = Column('provider_name', Unicode(255), default=u'', primary_key=True)
4490 access_token = Column('access_token', String(1024), default=u'')
4493 access_token = Column('access_token', String(1024), default=u'')
4491 alt_token = Column('alt_token', String(1024), default=u'')
4494 alt_token = Column('alt_token', String(1024), default=u'')
4492 token_secret = Column('token_secret', String(1024), default=u'')
4495 token_secret = Column('token_secret', String(1024), default=u'')
4493
4496
4494 @classmethod
4497 @classmethod
4495 def by_external_id_and_provider(cls, external_id, provider_name, local_user_id=None):
4498 def by_external_id_and_provider(cls, external_id, provider_name, local_user_id=None):
4496 """
4499 """
4497 Returns ExternalIdentity instance based on search params
4500 Returns ExternalIdentity instance based on search params
4498
4501
4499 :param external_id:
4502 :param external_id:
4500 :param provider_name:
4503 :param provider_name:
4501 :return: ExternalIdentity
4504 :return: ExternalIdentity
4502 """
4505 """
4503 query = cls.query()
4506 query = cls.query()
4504 query = query.filter(cls.external_id == external_id)
4507 query = query.filter(cls.external_id == external_id)
4505 query = query.filter(cls.provider_name == provider_name)
4508 query = query.filter(cls.provider_name == provider_name)
4506 if local_user_id:
4509 if local_user_id:
4507 query = query.filter(cls.local_user_id == local_user_id)
4510 query = query.filter(cls.local_user_id == local_user_id)
4508 return query.first()
4511 return query.first()
4509
4512
4510 @classmethod
4513 @classmethod
4511 def user_by_external_id_and_provider(cls, external_id, provider_name):
4514 def user_by_external_id_and_provider(cls, external_id, provider_name):
4512 """
4515 """
4513 Returns User instance based on search params
4516 Returns User instance based on search params
4514
4517
4515 :param external_id:
4518 :param external_id:
4516 :param provider_name:
4519 :param provider_name:
4517 :return: User
4520 :return: User
4518 """
4521 """
4519 query = User.query()
4522 query = User.query()
4520 query = query.filter(cls.external_id == external_id)
4523 query = query.filter(cls.external_id == external_id)
4521 query = query.filter(cls.provider_name == provider_name)
4524 query = query.filter(cls.provider_name == provider_name)
4522 query = query.filter(User.user_id == cls.local_user_id)
4525 query = query.filter(User.user_id == cls.local_user_id)
4523 return query.first()
4526 return query.first()
4524
4527
4525 @classmethod
4528 @classmethod
4526 def by_local_user_id(cls, local_user_id):
4529 def by_local_user_id(cls, local_user_id):
4527 """
4530 """
4528 Returns all tokens for user
4531 Returns all tokens for user
4529
4532
4530 :param local_user_id:
4533 :param local_user_id:
4531 :return: ExternalIdentity
4534 :return: ExternalIdentity
4532 """
4535 """
4533 query = cls.query()
4536 query = cls.query()
4534 query = query.filter(cls.local_user_id == local_user_id)
4537 query = query.filter(cls.local_user_id == local_user_id)
4535 return query
4538 return query
4536
4539
4537 @classmethod
4540 @classmethod
4538 def load_provider_plugin(cls, plugin_id):
4541 def load_provider_plugin(cls, plugin_id):
4539 from rhodecode.authentication.base import loadplugin
4542 from rhodecode.authentication.base import loadplugin
4540 _plugin_id = 'egg:rhodecode-enterprise-ee#{}'.format(plugin_id)
4543 _plugin_id = 'egg:rhodecode-enterprise-ee#{}'.format(plugin_id)
4541 auth_plugin = loadplugin(_plugin_id)
4544 auth_plugin = loadplugin(_plugin_id)
4542 return auth_plugin
4545 return auth_plugin
4543
4546
4544
4547
4545 class Integration(Base, BaseModel):
4548 class Integration(Base, BaseModel):
4546 __tablename__ = 'integrations'
4549 __tablename__ = 'integrations'
4547 __table_args__ = (
4550 __table_args__ = (
4548 base_table_args
4551 base_table_args
4549 )
4552 )
4550
4553
4551 integration_id = Column('integration_id', Integer(), primary_key=True)
4554 integration_id = Column('integration_id', Integer(), primary_key=True)
4552 integration_type = Column('integration_type', String(255))
4555 integration_type = Column('integration_type', String(255))
4553 enabled = Column('enabled', Boolean(), nullable=False)
4556 enabled = Column('enabled', Boolean(), nullable=False)
4554 name = Column('name', String(255), nullable=False)
4557 name = Column('name', String(255), nullable=False)
4555 child_repos_only = Column('child_repos_only', Boolean(), nullable=False,
4558 child_repos_only = Column('child_repos_only', Boolean(), nullable=False,
4556 default=False)
4559 default=False)
4557
4560
4558 settings = Column(
4561 settings = Column(
4559 'settings_json', MutationObj.as_mutable(
4562 'settings_json', MutationObj.as_mutable(
4560 JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
4563 JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
4561 repo_id = Column(
4564 repo_id = Column(
4562 'repo_id', Integer(), ForeignKey('repositories.repo_id'),
4565 'repo_id', Integer(), ForeignKey('repositories.repo_id'),
4563 nullable=True, unique=None, default=None)
4566 nullable=True, unique=None, default=None)
4564 repo = relationship('Repository', lazy='joined')
4567 repo = relationship('Repository', lazy='joined')
4565
4568
4566 repo_group_id = Column(
4569 repo_group_id = Column(
4567 'repo_group_id', Integer(), ForeignKey('groups.group_id'),
4570 'repo_group_id', Integer(), ForeignKey('groups.group_id'),
4568 nullable=True, unique=None, default=None)
4571 nullable=True, unique=None, default=None)
4569 repo_group = relationship('RepoGroup', lazy='joined')
4572 repo_group = relationship('RepoGroup', lazy='joined')
4570
4573
4571 @property
4574 @property
4572 def scope(self):
4575 def scope(self):
4573 if self.repo:
4576 if self.repo:
4574 return repr(self.repo)
4577 return repr(self.repo)
4575 if self.repo_group:
4578 if self.repo_group:
4576 if self.child_repos_only:
4579 if self.child_repos_only:
4577 return repr(self.repo_group) + ' (child repos only)'
4580 return repr(self.repo_group) + ' (child repos only)'
4578 else:
4581 else:
4579 return repr(self.repo_group) + ' (recursive)'
4582 return repr(self.repo_group) + ' (recursive)'
4580 if self.child_repos_only:
4583 if self.child_repos_only:
4581 return 'root_repos'
4584 return 'root_repos'
4582 return 'global'
4585 return 'global'
4583
4586
4584 def __repr__(self):
4587 def __repr__(self):
4585 return '<Integration(%r, %r)>' % (self.integration_type, self.scope)
4588 return '<Integration(%r, %r)>' % (self.integration_type, self.scope)
4586
4589
4587
4590
4588 class RepoReviewRuleUser(Base, BaseModel):
4591 class RepoReviewRuleUser(Base, BaseModel):
4589 __tablename__ = 'repo_review_rules_users'
4592 __tablename__ = 'repo_review_rules_users'
4590 __table_args__ = (
4593 __table_args__ = (
4591 base_table_args
4594 base_table_args
4592 )
4595 )
4593
4596
4594 repo_review_rule_user_id = Column('repo_review_rule_user_id', Integer(), primary_key=True)
4597 repo_review_rule_user_id = Column('repo_review_rule_user_id', Integer(), primary_key=True)
4595 repo_review_rule_id = Column("repo_review_rule_id", Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
4598 repo_review_rule_id = Column("repo_review_rule_id", Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
4596 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False)
4599 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False)
4597 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
4600 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
4598 user = relationship('User')
4601 user = relationship('User')
4599
4602
4600 def rule_data(self):
4603 def rule_data(self):
4601 return {
4604 return {
4602 'mandatory': self.mandatory
4605 'mandatory': self.mandatory
4603 }
4606 }
4604
4607
4605
4608
4606 class RepoReviewRuleUserGroup(Base, BaseModel):
4609 class RepoReviewRuleUserGroup(Base, BaseModel):
4607 __tablename__ = 'repo_review_rules_users_groups'
4610 __tablename__ = 'repo_review_rules_users_groups'
4608 __table_args__ = (
4611 __table_args__ = (
4609 base_table_args
4612 base_table_args
4610 )
4613 )
4611
4614
4612 VOTE_RULE_ALL = -1
4615 VOTE_RULE_ALL = -1
4613
4616
4614 repo_review_rule_users_group_id = Column('repo_review_rule_users_group_id', Integer(), primary_key=True)
4617 repo_review_rule_users_group_id = Column('repo_review_rule_users_group_id', Integer(), primary_key=True)
4615 repo_review_rule_id = Column("repo_review_rule_id", Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
4618 repo_review_rule_id = Column("repo_review_rule_id", Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
4616 users_group_id = Column("users_group_id", Integer(),ForeignKey('users_groups.users_group_id'), nullable=False)
4619 users_group_id = Column("users_group_id", Integer(),ForeignKey('users_groups.users_group_id'), nullable=False)
4617 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
4620 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
4618 vote_rule = Column("vote_rule", Integer(), nullable=True, default=VOTE_RULE_ALL)
4621 vote_rule = Column("vote_rule", Integer(), nullable=True, default=VOTE_RULE_ALL)
4619 users_group = relationship('UserGroup')
4622 users_group = relationship('UserGroup')
4620
4623
4621 def rule_data(self):
4624 def rule_data(self):
4622 return {
4625 return {
4623 'mandatory': self.mandatory,
4626 'mandatory': self.mandatory,
4624 'vote_rule': self.vote_rule
4627 'vote_rule': self.vote_rule
4625 }
4628 }
4626
4629
4627 @property
4630 @property
4628 def vote_rule_label(self):
4631 def vote_rule_label(self):
4629 if not self.vote_rule or self.vote_rule == self.VOTE_RULE_ALL:
4632 if not self.vote_rule or self.vote_rule == self.VOTE_RULE_ALL:
4630 return 'all must vote'
4633 return 'all must vote'
4631 else:
4634 else:
4632 return 'min. vote {}'.format(self.vote_rule)
4635 return 'min. vote {}'.format(self.vote_rule)
4633
4636
4634
4637
4635 class RepoReviewRule(Base, BaseModel):
4638 class RepoReviewRule(Base, BaseModel):
4636 __tablename__ = 'repo_review_rules'
4639 __tablename__ = 'repo_review_rules'
4637 __table_args__ = (
4640 __table_args__ = (
4638 base_table_args
4641 base_table_args
4639 )
4642 )
4640
4643
4641 repo_review_rule_id = Column(
4644 repo_review_rule_id = Column(
4642 'repo_review_rule_id', Integer(), primary_key=True)
4645 'repo_review_rule_id', Integer(), primary_key=True)
4643 repo_id = Column(
4646 repo_id = Column(
4644 "repo_id", Integer(), ForeignKey('repositories.repo_id'))
4647 "repo_id", Integer(), ForeignKey('repositories.repo_id'))
4645 repo = relationship('Repository', backref='review_rules')
4648 repo = relationship('Repository', backref='review_rules')
4646
4649
4647 review_rule_name = Column('review_rule_name', String(255))
4650 review_rule_name = Column('review_rule_name', String(255))
4648 _branch_pattern = Column("branch_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default=u'*') # glob
4651 _branch_pattern = Column("branch_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default=u'*') # glob
4649 _target_branch_pattern = Column("target_branch_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default=u'*') # glob
4652 _target_branch_pattern = Column("target_branch_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default=u'*') # glob
4650 _file_pattern = Column("file_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default=u'*') # glob
4653 _file_pattern = Column("file_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default=u'*') # glob
4651
4654
4652 use_authors_for_review = Column("use_authors_for_review", Boolean(), nullable=False, default=False)
4655 use_authors_for_review = Column("use_authors_for_review", Boolean(), nullable=False, default=False)
4653 forbid_author_to_review = Column("forbid_author_to_review", Boolean(), nullable=False, default=False)
4656 forbid_author_to_review = Column("forbid_author_to_review", Boolean(), nullable=False, default=False)
4654 forbid_commit_author_to_review = Column("forbid_commit_author_to_review", Boolean(), nullable=False, default=False)
4657 forbid_commit_author_to_review = Column("forbid_commit_author_to_review", Boolean(), nullable=False, default=False)
4655 forbid_adding_reviewers = Column("forbid_adding_reviewers", Boolean(), nullable=False, default=False)
4658 forbid_adding_reviewers = Column("forbid_adding_reviewers", Boolean(), nullable=False, default=False)
4656
4659
4657 rule_users = relationship('RepoReviewRuleUser')
4660 rule_users = relationship('RepoReviewRuleUser')
4658 rule_user_groups = relationship('RepoReviewRuleUserGroup')
4661 rule_user_groups = relationship('RepoReviewRuleUserGroup')
4659
4662
4660 def _validate_pattern(self, value):
4663 def _validate_pattern(self, value):
4661 re.compile('^' + glob2re(value) + '$')
4664 re.compile('^' + glob2re(value) + '$')
4662
4665
4663 @hybrid_property
4666 @hybrid_property
4664 def source_branch_pattern(self):
4667 def source_branch_pattern(self):
4665 return self._branch_pattern or '*'
4668 return self._branch_pattern or '*'
4666
4669
4667 @source_branch_pattern.setter
4670 @source_branch_pattern.setter
4668 def source_branch_pattern(self, value):
4671 def source_branch_pattern(self, value):
4669 self._validate_pattern(value)
4672 self._validate_pattern(value)
4670 self._branch_pattern = value or '*'
4673 self._branch_pattern = value or '*'
4671
4674
4672 @hybrid_property
4675 @hybrid_property
4673 def target_branch_pattern(self):
4676 def target_branch_pattern(self):
4674 return self._target_branch_pattern or '*'
4677 return self._target_branch_pattern or '*'
4675
4678
4676 @target_branch_pattern.setter
4679 @target_branch_pattern.setter
4677 def target_branch_pattern(self, value):
4680 def target_branch_pattern(self, value):
4678 self._validate_pattern(value)
4681 self._validate_pattern(value)
4679 self._target_branch_pattern = value or '*'
4682 self._target_branch_pattern = value or '*'
4680
4683
4681 @hybrid_property
4684 @hybrid_property
4682 def file_pattern(self):
4685 def file_pattern(self):
4683 return self._file_pattern or '*'
4686 return self._file_pattern or '*'
4684
4687
4685 @file_pattern.setter
4688 @file_pattern.setter
4686 def file_pattern(self, value):
4689 def file_pattern(self, value):
4687 self._validate_pattern(value)
4690 self._validate_pattern(value)
4688 self._file_pattern = value or '*'
4691 self._file_pattern = value or '*'
4689
4692
4690 def matches(self, source_branch, target_branch, files_changed):
4693 def matches(self, source_branch, target_branch, files_changed):
4691 """
4694 """
4692 Check if this review rule matches a branch/files in a pull request
4695 Check if this review rule matches a branch/files in a pull request
4693
4696
4694 :param source_branch: source branch name for the commit
4697 :param source_branch: source branch name for the commit
4695 :param target_branch: target branch name for the commit
4698 :param target_branch: target branch name for the commit
4696 :param files_changed: list of file paths changed in the pull request
4699 :param files_changed: list of file paths changed in the pull request
4697 """
4700 """
4698
4701
4699 source_branch = source_branch or ''
4702 source_branch = source_branch or ''
4700 target_branch = target_branch or ''
4703 target_branch = target_branch or ''
4701 files_changed = files_changed or []
4704 files_changed = files_changed or []
4702
4705
4703 branch_matches = True
4706 branch_matches = True
4704 if source_branch or target_branch:
4707 if source_branch or target_branch:
4705 if self.source_branch_pattern == '*':
4708 if self.source_branch_pattern == '*':
4706 source_branch_match = True
4709 source_branch_match = True
4707 else:
4710 else:
4708 if self.source_branch_pattern.startswith('re:'):
4711 if self.source_branch_pattern.startswith('re:'):
4709 source_pattern = self.source_branch_pattern[3:]
4712 source_pattern = self.source_branch_pattern[3:]
4710 else:
4713 else:
4711 source_pattern = '^' + glob2re(self.source_branch_pattern) + '$'
4714 source_pattern = '^' + glob2re(self.source_branch_pattern) + '$'
4712 source_branch_regex = re.compile(source_pattern)
4715 source_branch_regex = re.compile(source_pattern)
4713 source_branch_match = bool(source_branch_regex.search(source_branch))
4716 source_branch_match = bool(source_branch_regex.search(source_branch))
4714 if self.target_branch_pattern == '*':
4717 if self.target_branch_pattern == '*':
4715 target_branch_match = True
4718 target_branch_match = True
4716 else:
4719 else:
4717 if self.target_branch_pattern.startswith('re:'):
4720 if self.target_branch_pattern.startswith('re:'):
4718 target_pattern = self.target_branch_pattern[3:]
4721 target_pattern = self.target_branch_pattern[3:]
4719 else:
4722 else:
4720 target_pattern = '^' + glob2re(self.target_branch_pattern) + '$'
4723 target_pattern = '^' + glob2re(self.target_branch_pattern) + '$'
4721 target_branch_regex = re.compile(target_pattern)
4724 target_branch_regex = re.compile(target_pattern)
4722 target_branch_match = bool(target_branch_regex.search(target_branch))
4725 target_branch_match = bool(target_branch_regex.search(target_branch))
4723
4726
4724 branch_matches = source_branch_match and target_branch_match
4727 branch_matches = source_branch_match and target_branch_match
4725
4728
4726 files_matches = True
4729 files_matches = True
4727 if self.file_pattern != '*':
4730 if self.file_pattern != '*':
4728 files_matches = False
4731 files_matches = False
4729 if self.file_pattern.startswith('re:'):
4732 if self.file_pattern.startswith('re:'):
4730 file_pattern = self.file_pattern[3:]
4733 file_pattern = self.file_pattern[3:]
4731 else:
4734 else:
4732 file_pattern = glob2re(self.file_pattern)
4735 file_pattern = glob2re(self.file_pattern)
4733 file_regex = re.compile(file_pattern)
4736 file_regex = re.compile(file_pattern)
4734 for filename in files_changed:
4737 for filename in files_changed:
4735 if file_regex.search(filename):
4738 if file_regex.search(filename):
4736 files_matches = True
4739 files_matches = True
4737 break
4740 break
4738
4741
4739 return branch_matches and files_matches
4742 return branch_matches and files_matches
4740
4743
4741 @property
4744 @property
4742 def review_users(self):
4745 def review_users(self):
4743 """ Returns the users which this rule applies to """
4746 """ Returns the users which this rule applies to """
4744
4747
4745 users = collections.OrderedDict()
4748 users = collections.OrderedDict()
4746
4749
4747 for rule_user in self.rule_users:
4750 for rule_user in self.rule_users:
4748 if rule_user.user.active:
4751 if rule_user.user.active:
4749 if rule_user.user not in users:
4752 if rule_user.user not in users:
4750 users[rule_user.user.username] = {
4753 users[rule_user.user.username] = {
4751 'user': rule_user.user,
4754 'user': rule_user.user,
4752 'source': 'user',
4755 'source': 'user',
4753 'source_data': {},
4756 'source_data': {},
4754 'data': rule_user.rule_data()
4757 'data': rule_user.rule_data()
4755 }
4758 }
4756
4759
4757 for rule_user_group in self.rule_user_groups:
4760 for rule_user_group in self.rule_user_groups:
4758 source_data = {
4761 source_data = {
4759 'user_group_id': rule_user_group.users_group.users_group_id,
4762 'user_group_id': rule_user_group.users_group.users_group_id,
4760 'name': rule_user_group.users_group.users_group_name,
4763 'name': rule_user_group.users_group.users_group_name,
4761 'members': len(rule_user_group.users_group.members)
4764 'members': len(rule_user_group.users_group.members)
4762 }
4765 }
4763 for member in rule_user_group.users_group.members:
4766 for member in rule_user_group.users_group.members:
4764 if member.user.active:
4767 if member.user.active:
4765 key = member.user.username
4768 key = member.user.username
4766 if key in users:
4769 if key in users:
4767 # skip this member as we have him already
4770 # skip this member as we have him already
4768 # this prevents from override the "first" matched
4771 # this prevents from override the "first" matched
4769 # users with duplicates in multiple groups
4772 # users with duplicates in multiple groups
4770 continue
4773 continue
4771
4774
4772 users[key] = {
4775 users[key] = {
4773 'user': member.user,
4776 'user': member.user,
4774 'source': 'user_group',
4777 'source': 'user_group',
4775 'source_data': source_data,
4778 'source_data': source_data,
4776 'data': rule_user_group.rule_data()
4779 'data': rule_user_group.rule_data()
4777 }
4780 }
4778
4781
4779 return users
4782 return users
4780
4783
4781 def user_group_vote_rule(self, user_id):
4784 def user_group_vote_rule(self, user_id):
4782
4785
4783 rules = []
4786 rules = []
4784 if not self.rule_user_groups:
4787 if not self.rule_user_groups:
4785 return rules
4788 return rules
4786
4789
4787 for user_group in self.rule_user_groups:
4790 for user_group in self.rule_user_groups:
4788 user_group_members = [x.user_id for x in user_group.users_group.members]
4791 user_group_members = [x.user_id for x in user_group.users_group.members]
4789 if user_id in user_group_members:
4792 if user_id in user_group_members:
4790 rules.append(user_group)
4793 rules.append(user_group)
4791 return rules
4794 return rules
4792
4795
4793 def __repr__(self):
4796 def __repr__(self):
4794 return '<RepoReviewerRule(id=%r, repo=%r)>' % (
4797 return '<RepoReviewerRule(id=%r, repo=%r)>' % (
4795 self.repo_review_rule_id, self.repo)
4798 self.repo_review_rule_id, self.repo)
4796
4799
4797
4800
4798 class ScheduleEntry(Base, BaseModel):
4801 class ScheduleEntry(Base, BaseModel):
4799 __tablename__ = 'schedule_entries'
4802 __tablename__ = 'schedule_entries'
4800 __table_args__ = (
4803 __table_args__ = (
4801 UniqueConstraint('schedule_name', name='s_schedule_name_idx'),
4804 UniqueConstraint('schedule_name', name='s_schedule_name_idx'),
4802 UniqueConstraint('task_uid', name='s_task_uid_idx'),
4805 UniqueConstraint('task_uid', name='s_task_uid_idx'),
4803 base_table_args,
4806 base_table_args,
4804 )
4807 )
4805
4808
4806 schedule_types = ['crontab', 'timedelta', 'integer']
4809 schedule_types = ['crontab', 'timedelta', 'integer']
4807 schedule_entry_id = Column('schedule_entry_id', Integer(), primary_key=True)
4810 schedule_entry_id = Column('schedule_entry_id', Integer(), primary_key=True)
4808
4811
4809 schedule_name = Column("schedule_name", String(255), nullable=False, unique=None, default=None)
4812 schedule_name = Column("schedule_name", String(255), nullable=False, unique=None, default=None)
4810 schedule_description = Column("schedule_description", String(10000), nullable=True, unique=None, default=None)
4813 schedule_description = Column("schedule_description", String(10000), nullable=True, unique=None, default=None)
4811 schedule_enabled = Column("schedule_enabled", Boolean(), nullable=False, unique=None, default=True)
4814 schedule_enabled = Column("schedule_enabled", Boolean(), nullable=False, unique=None, default=True)
4812
4815
4813 _schedule_type = Column("schedule_type", String(255), nullable=False, unique=None, default=None)
4816 _schedule_type = Column("schedule_type", String(255), nullable=False, unique=None, default=None)
4814 schedule_definition = Column('schedule_definition_json', MutationObj.as_mutable(JsonType(default=lambda: "", dialect_map=dict(mysql=LONGTEXT()))))
4817 schedule_definition = Column('schedule_definition_json', MutationObj.as_mutable(JsonType(default=lambda: "", dialect_map=dict(mysql=LONGTEXT()))))
4815
4818
4816 schedule_last_run = Column('schedule_last_run', DateTime(timezone=False), nullable=True, unique=None, default=None)
4819 schedule_last_run = Column('schedule_last_run', DateTime(timezone=False), nullable=True, unique=None, default=None)
4817 schedule_total_run_count = Column('schedule_total_run_count', Integer(), nullable=True, unique=None, default=0)
4820 schedule_total_run_count = Column('schedule_total_run_count', Integer(), nullable=True, unique=None, default=0)
4818
4821
4819 # task
4822 # task
4820 task_uid = Column("task_uid", String(255), nullable=False, unique=None, default=None)
4823 task_uid = Column("task_uid", String(255), nullable=False, unique=None, default=None)
4821 task_dot_notation = Column("task_dot_notation", String(4096), nullable=False, unique=None, default=None)
4824 task_dot_notation = Column("task_dot_notation", String(4096), nullable=False, unique=None, default=None)
4822 task_args = Column('task_args_json', MutationObj.as_mutable(JsonType(default=list, dialect_map=dict(mysql=LONGTEXT()))))
4825 task_args = Column('task_args_json', MutationObj.as_mutable(JsonType(default=list, dialect_map=dict(mysql=LONGTEXT()))))
4823 task_kwargs = Column('task_kwargs_json', MutationObj.as_mutable(JsonType(default=dict, dialect_map=dict(mysql=LONGTEXT()))))
4826 task_kwargs = Column('task_kwargs_json', MutationObj.as_mutable(JsonType(default=dict, dialect_map=dict(mysql=LONGTEXT()))))
4824
4827
4825 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4828 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4826 updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=None)
4829 updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=None)
4827
4830
4828 @hybrid_property
4831 @hybrid_property
4829 def schedule_type(self):
4832 def schedule_type(self):
4830 return self._schedule_type
4833 return self._schedule_type
4831
4834
4832 @schedule_type.setter
4835 @schedule_type.setter
4833 def schedule_type(self, val):
4836 def schedule_type(self, val):
4834 if val not in self.schedule_types:
4837 if val not in self.schedule_types:
4835 raise ValueError('Value must be on of `{}` and got `{}`'.format(
4838 raise ValueError('Value must be on of `{}` and got `{}`'.format(
4836 val, self.schedule_type))
4839 val, self.schedule_type))
4837
4840
4838 self._schedule_type = val
4841 self._schedule_type = val
4839
4842
4840 @classmethod
4843 @classmethod
4841 def get_uid(cls, obj):
4844 def get_uid(cls, obj):
4842 args = obj.task_args
4845 args = obj.task_args
4843 kwargs = obj.task_kwargs
4846 kwargs = obj.task_kwargs
4844 if isinstance(args, JsonRaw):
4847 if isinstance(args, JsonRaw):
4845 try:
4848 try:
4846 args = json.loads(args)
4849 args = json.loads(args)
4847 except ValueError:
4850 except ValueError:
4848 args = tuple()
4851 args = tuple()
4849
4852
4850 if isinstance(kwargs, JsonRaw):
4853 if isinstance(kwargs, JsonRaw):
4851 try:
4854 try:
4852 kwargs = json.loads(kwargs)
4855 kwargs = json.loads(kwargs)
4853 except ValueError:
4856 except ValueError:
4854 kwargs = dict()
4857 kwargs = dict()
4855
4858
4856 dot_notation = obj.task_dot_notation
4859 dot_notation = obj.task_dot_notation
4857 val = '.'.join(map(safe_str, [
4860 val = '.'.join(map(safe_str, [
4858 sorted(dot_notation), args, sorted(kwargs.items())]))
4861 sorted(dot_notation), args, sorted(kwargs.items())]))
4859 return hashlib.sha1(val).hexdigest()
4862 return hashlib.sha1(val).hexdigest()
4860
4863
4861 @classmethod
4864 @classmethod
4862 def get_by_schedule_name(cls, schedule_name):
4865 def get_by_schedule_name(cls, schedule_name):
4863 return cls.query().filter(cls.schedule_name == schedule_name).scalar()
4866 return cls.query().filter(cls.schedule_name == schedule_name).scalar()
4864
4867
4865 @classmethod
4868 @classmethod
4866 def get_by_schedule_id(cls, schedule_id):
4869 def get_by_schedule_id(cls, schedule_id):
4867 return cls.query().filter(cls.schedule_entry_id == schedule_id).scalar()
4870 return cls.query().filter(cls.schedule_entry_id == schedule_id).scalar()
4868
4871
4869 @property
4872 @property
4870 def task(self):
4873 def task(self):
4871 return self.task_dot_notation
4874 return self.task_dot_notation
4872
4875
4873 @property
4876 @property
4874 def schedule(self):
4877 def schedule(self):
4875 from rhodecode.lib.celerylib.utils import raw_2_schedule
4878 from rhodecode.lib.celerylib.utils import raw_2_schedule
4876 schedule = raw_2_schedule(self.schedule_definition, self.schedule_type)
4879 schedule = raw_2_schedule(self.schedule_definition, self.schedule_type)
4877 return schedule
4880 return schedule
4878
4881
4879 @property
4882 @property
4880 def args(self):
4883 def args(self):
4881 try:
4884 try:
4882 return list(self.task_args or [])
4885 return list(self.task_args or [])
4883 except ValueError:
4886 except ValueError:
4884 return list()
4887 return list()
4885
4888
4886 @property
4889 @property
4887 def kwargs(self):
4890 def kwargs(self):
4888 try:
4891 try:
4889 return dict(self.task_kwargs or {})
4892 return dict(self.task_kwargs or {})
4890 except ValueError:
4893 except ValueError:
4891 return dict()
4894 return dict()
4892
4895
4893 def _as_raw(self, val):
4896 def _as_raw(self, val):
4894 if hasattr(val, 'de_coerce'):
4897 if hasattr(val, 'de_coerce'):
4895 val = val.de_coerce()
4898 val = val.de_coerce()
4896 if val:
4899 if val:
4897 val = json.dumps(val)
4900 val = json.dumps(val)
4898
4901
4899 return val
4902 return val
4900
4903
4901 @property
4904 @property
4902 def schedule_definition_raw(self):
4905 def schedule_definition_raw(self):
4903 return self._as_raw(self.schedule_definition)
4906 return self._as_raw(self.schedule_definition)
4904
4907
4905 @property
4908 @property
4906 def args_raw(self):
4909 def args_raw(self):
4907 return self._as_raw(self.task_args)
4910 return self._as_raw(self.task_args)
4908
4911
4909 @property
4912 @property
4910 def kwargs_raw(self):
4913 def kwargs_raw(self):
4911 return self._as_raw(self.task_kwargs)
4914 return self._as_raw(self.task_kwargs)
4912
4915
4913 def __repr__(self):
4916 def __repr__(self):
4914 return '<DB:ScheduleEntry({}:{})>'.format(
4917 return '<DB:ScheduleEntry({}:{})>'.format(
4915 self.schedule_entry_id, self.schedule_name)
4918 self.schedule_entry_id, self.schedule_name)
4916
4919
4917
4920
4918 @event.listens_for(ScheduleEntry, 'before_update')
4921 @event.listens_for(ScheduleEntry, 'before_update')
4919 def update_task_uid(mapper, connection, target):
4922 def update_task_uid(mapper, connection, target):
4920 target.task_uid = ScheduleEntry.get_uid(target)
4923 target.task_uid = ScheduleEntry.get_uid(target)
4921
4924
4922
4925
4923 @event.listens_for(ScheduleEntry, 'before_insert')
4926 @event.listens_for(ScheduleEntry, 'before_insert')
4924 def set_task_uid(mapper, connection, target):
4927 def set_task_uid(mapper, connection, target):
4925 target.task_uid = ScheduleEntry.get_uid(target)
4928 target.task_uid = ScheduleEntry.get_uid(target)
4926
4929
4927
4930
4928 class _BaseBranchPerms(BaseModel):
4931 class _BaseBranchPerms(BaseModel):
4929 @classmethod
4932 @classmethod
4930 def compute_hash(cls, value):
4933 def compute_hash(cls, value):
4931 return sha1_safe(value)
4934 return sha1_safe(value)
4932
4935
4933 @hybrid_property
4936 @hybrid_property
4934 def branch_pattern(self):
4937 def branch_pattern(self):
4935 return self._branch_pattern or '*'
4938 return self._branch_pattern or '*'
4936
4939
4937 @hybrid_property
4940 @hybrid_property
4938 def branch_hash(self):
4941 def branch_hash(self):
4939 return self._branch_hash
4942 return self._branch_hash
4940
4943
4941 def _validate_glob(self, value):
4944 def _validate_glob(self, value):
4942 re.compile('^' + glob2re(value) + '$')
4945 re.compile('^' + glob2re(value) + '$')
4943
4946
4944 @branch_pattern.setter
4947 @branch_pattern.setter
4945 def branch_pattern(self, value):
4948 def branch_pattern(self, value):
4946 self._validate_glob(value)
4949 self._validate_glob(value)
4947 self._branch_pattern = value or '*'
4950 self._branch_pattern = value or '*'
4948 # set the Hash when setting the branch pattern
4951 # set the Hash when setting the branch pattern
4949 self._branch_hash = self.compute_hash(self._branch_pattern)
4952 self._branch_hash = self.compute_hash(self._branch_pattern)
4950
4953
4951 def matches(self, branch):
4954 def matches(self, branch):
4952 """
4955 """
4953 Check if this the branch matches entry
4956 Check if this the branch matches entry
4954
4957
4955 :param branch: branch name for the commit
4958 :param branch: branch name for the commit
4956 """
4959 """
4957
4960
4958 branch = branch or ''
4961 branch = branch or ''
4959
4962
4960 branch_matches = True
4963 branch_matches = True
4961 if branch:
4964 if branch:
4962 branch_regex = re.compile('^' + glob2re(self.branch_pattern) + '$')
4965 branch_regex = re.compile('^' + glob2re(self.branch_pattern) + '$')
4963 branch_matches = bool(branch_regex.search(branch))
4966 branch_matches = bool(branch_regex.search(branch))
4964
4967
4965 return branch_matches
4968 return branch_matches
4966
4969
4967
4970
4968 class UserToRepoBranchPermission(Base, _BaseBranchPerms):
4971 class UserToRepoBranchPermission(Base, _BaseBranchPerms):
4969 __tablename__ = 'user_to_repo_branch_permissions'
4972 __tablename__ = 'user_to_repo_branch_permissions'
4970 __table_args__ = (
4973 __table_args__ = (
4971 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4974 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4972 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
4975 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
4973 )
4976 )
4974
4977
4975 branch_rule_id = Column('branch_rule_id', Integer(), primary_key=True)
4978 branch_rule_id = Column('branch_rule_id', Integer(), primary_key=True)
4976
4979
4977 repository_id = Column('repository_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
4980 repository_id = Column('repository_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
4978 repo = relationship('Repository', backref='user_branch_perms')
4981 repo = relationship('Repository', backref='user_branch_perms')
4979
4982
4980 permission_id = Column('permission_id', Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
4983 permission_id = Column('permission_id', Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
4981 permission = relationship('Permission')
4984 permission = relationship('Permission')
4982
4985
4983 rule_to_perm_id = Column('rule_to_perm_id', Integer(), ForeignKey('repo_to_perm.repo_to_perm_id'), nullable=False, unique=None, default=None)
4986 rule_to_perm_id = Column('rule_to_perm_id', Integer(), ForeignKey('repo_to_perm.repo_to_perm_id'), nullable=False, unique=None, default=None)
4984 user_repo_to_perm = relationship('UserRepoToPerm')
4987 user_repo_to_perm = relationship('UserRepoToPerm')
4985
4988
4986 rule_order = Column('rule_order', Integer(), nullable=False)
4989 rule_order = Column('rule_order', Integer(), nullable=False)
4987 _branch_pattern = Column('branch_pattern', UnicodeText().with_variant(UnicodeText(2048), 'mysql'), default=u'*') # glob
4990 _branch_pattern = Column('branch_pattern', UnicodeText().with_variant(UnicodeText(2048), 'mysql'), default=u'*') # glob
4988 _branch_hash = Column('branch_hash', UnicodeText().with_variant(UnicodeText(2048), 'mysql'))
4991 _branch_hash = Column('branch_hash', UnicodeText().with_variant(UnicodeText(2048), 'mysql'))
4989
4992
4990 def __unicode__(self):
4993 def __unicode__(self):
4991 return u'<UserBranchPermission(%s => %r)>' % (
4994 return u'<UserBranchPermission(%s => %r)>' % (
4992 self.user_repo_to_perm, self.branch_pattern)
4995 self.user_repo_to_perm, self.branch_pattern)
4993
4996
4994
4997
4995 class UserGroupToRepoBranchPermission(Base, _BaseBranchPerms):
4998 class UserGroupToRepoBranchPermission(Base, _BaseBranchPerms):
4996 __tablename__ = 'user_group_to_repo_branch_permissions'
4999 __tablename__ = 'user_group_to_repo_branch_permissions'
4997 __table_args__ = (
5000 __table_args__ = (
4998 {'extend_existing': True, 'mysql_engine': 'InnoDB',
5001 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4999 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
5002 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
5000 )
5003 )
5001
5004
5002 branch_rule_id = Column('branch_rule_id', Integer(), primary_key=True)
5005 branch_rule_id = Column('branch_rule_id', Integer(), primary_key=True)
5003
5006
5004 repository_id = Column('repository_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
5007 repository_id = Column('repository_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
5005 repo = relationship('Repository', backref='user_group_branch_perms')
5008 repo = relationship('Repository', backref='user_group_branch_perms')
5006
5009
5007 permission_id = Column('permission_id', Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
5010 permission_id = Column('permission_id', Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
5008 permission = relationship('Permission')
5011 permission = relationship('Permission')
5009
5012
5010 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)
5013 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)
5011 user_group_repo_to_perm = relationship('UserGroupRepoToPerm')
5014 user_group_repo_to_perm = relationship('UserGroupRepoToPerm')
5012
5015
5013 rule_order = Column('rule_order', Integer(), nullable=False)
5016 rule_order = Column('rule_order', Integer(), nullable=False)
5014 _branch_pattern = Column('branch_pattern', UnicodeText().with_variant(UnicodeText(2048), 'mysql'), default=u'*') # glob
5017 _branch_pattern = Column('branch_pattern', UnicodeText().with_variant(UnicodeText(2048), 'mysql'), default=u'*') # glob
5015 _branch_hash = Column('branch_hash', UnicodeText().with_variant(UnicodeText(2048), 'mysql'))
5018 _branch_hash = Column('branch_hash', UnicodeText().with_variant(UnicodeText(2048), 'mysql'))
5016
5019
5017 def __unicode__(self):
5020 def __unicode__(self):
5018 return u'<UserBranchPermission(%s => %r)>' % (
5021 return u'<UserBranchPermission(%s => %r)>' % (
5019 self.user_group_repo_to_perm, self.branch_pattern)
5022 self.user_group_repo_to_perm, self.branch_pattern)
5020
5023
5021
5024
5022 class UserBookmark(Base, BaseModel):
5025 class UserBookmark(Base, BaseModel):
5023 __tablename__ = 'user_bookmarks'
5026 __tablename__ = 'user_bookmarks'
5024 __table_args__ = (
5027 __table_args__ = (
5025 UniqueConstraint('user_id', 'bookmark_repo_id'),
5028 UniqueConstraint('user_id', 'bookmark_repo_id'),
5026 UniqueConstraint('user_id', 'bookmark_repo_group_id'),
5029 UniqueConstraint('user_id', 'bookmark_repo_group_id'),
5027 UniqueConstraint('user_id', 'bookmark_position'),
5030 UniqueConstraint('user_id', 'bookmark_position'),
5028 base_table_args
5031 base_table_args
5029 )
5032 )
5030
5033
5031 user_bookmark_id = Column("user_bookmark_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
5034 user_bookmark_id = Column("user_bookmark_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
5032 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
5035 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
5033 position = Column("bookmark_position", Integer(), nullable=False)
5036 position = Column("bookmark_position", Integer(), nullable=False)
5034 title = Column("bookmark_title", String(255), nullable=True, unique=None, default=None)
5037 title = Column("bookmark_title", String(255), nullable=True, unique=None, default=None)
5035 redirect_url = Column("bookmark_redirect_url", String(10240), nullable=True, unique=None, default=None)
5038 redirect_url = Column("bookmark_redirect_url", String(10240), nullable=True, unique=None, default=None)
5036 created_on = Column("created_on", DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
5039 created_on = Column("created_on", DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
5037
5040
5038 bookmark_repo_id = Column("bookmark_repo_id", Integer(), ForeignKey("repositories.repo_id"), nullable=True, unique=None, default=None)
5041 bookmark_repo_id = Column("bookmark_repo_id", Integer(), ForeignKey("repositories.repo_id"), nullable=True, unique=None, default=None)
5039 bookmark_repo_group_id = Column("bookmark_repo_group_id", Integer(), ForeignKey("groups.group_id"), nullable=True, unique=None, default=None)
5042 bookmark_repo_group_id = Column("bookmark_repo_group_id", Integer(), ForeignKey("groups.group_id"), nullable=True, unique=None, default=None)
5040
5043
5041 user = relationship("User")
5044 user = relationship("User")
5042
5045
5043 repository = relationship("Repository")
5046 repository = relationship("Repository")
5044 repository_group = relationship("RepoGroup")
5047 repository_group = relationship("RepoGroup")
5045
5048
5046 @classmethod
5049 @classmethod
5047 def get_by_position_for_user(cls, position, user_id):
5050 def get_by_position_for_user(cls, position, user_id):
5048 return cls.query() \
5051 return cls.query() \
5049 .filter(UserBookmark.user_id == user_id) \
5052 .filter(UserBookmark.user_id == user_id) \
5050 .filter(UserBookmark.position == position).scalar()
5053 .filter(UserBookmark.position == position).scalar()
5051
5054
5052 @classmethod
5055 @classmethod
5053 def get_bookmarks_for_user(cls, user_id):
5056 def get_bookmarks_for_user(cls, user_id):
5054 return cls.query() \
5057 return cls.query() \
5055 .filter(UserBookmark.user_id == user_id) \
5058 .filter(UserBookmark.user_id == user_id) \
5056 .options(joinedload(UserBookmark.repository)) \
5059 .options(joinedload(UserBookmark.repository)) \
5057 .options(joinedload(UserBookmark.repository_group)) \
5060 .options(joinedload(UserBookmark.repository_group)) \
5058 .order_by(UserBookmark.position.asc()) \
5061 .order_by(UserBookmark.position.asc()) \
5059 .all()
5062 .all()
5060
5063
5061 def __unicode__(self):
5064 def __unicode__(self):
5062 return u'<UserBookmark(%d @ %r)>' % (self.position, self.redirect_url)
5065 return u'<UserBookmark(%d @ %r)>' % (self.position, self.redirect_url)
5063
5066
5064
5067
5065 class FileStore(Base, BaseModel):
5068 class FileStore(Base, BaseModel):
5066 __tablename__ = 'file_store'
5069 __tablename__ = 'file_store'
5067 __table_args__ = (
5070 __table_args__ = (
5068 base_table_args
5071 base_table_args
5069 )
5072 )
5070
5073
5071 file_store_id = Column('file_store_id', Integer(), primary_key=True)
5074 file_store_id = Column('file_store_id', Integer(), primary_key=True)
5072 file_uid = Column('file_uid', String(1024), nullable=False)
5075 file_uid = Column('file_uid', String(1024), nullable=False)
5073 file_display_name = Column('file_display_name', UnicodeText().with_variant(UnicodeText(2048), 'mysql'), nullable=True)
5076 file_display_name = Column('file_display_name', UnicodeText().with_variant(UnicodeText(2048), 'mysql'), nullable=True)
5074 file_description = Column('file_description', UnicodeText().with_variant(UnicodeText(10240), 'mysql'), nullable=True)
5077 file_description = Column('file_description', UnicodeText().with_variant(UnicodeText(10240), 'mysql'), nullable=True)
5075 file_org_name = Column('file_org_name', UnicodeText().with_variant(UnicodeText(10240), 'mysql'), nullable=False)
5078 file_org_name = Column('file_org_name', UnicodeText().with_variant(UnicodeText(10240), 'mysql'), nullable=False)
5076
5079
5077 # sha256 hash
5080 # sha256 hash
5078 file_hash = Column('file_hash', String(512), nullable=False)
5081 file_hash = Column('file_hash', String(512), nullable=False)
5079 file_size = Column('file_size', Integer(), nullable=False)
5082 file_size = Column('file_size', Integer(), nullable=False)
5080
5083
5081 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
5084 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
5082 accessed_on = Column('accessed_on', DateTime(timezone=False), nullable=True)
5085 accessed_on = Column('accessed_on', DateTime(timezone=False), nullable=True)
5083 accessed_count = Column('accessed_count', Integer(), default=0)
5086 accessed_count = Column('accessed_count', Integer(), default=0)
5084
5087
5085 enabled = Column('enabled', Boolean(), nullable=False, default=True)
5088 enabled = Column('enabled', Boolean(), nullable=False, default=True)
5086
5089
5087 # if repo/repo_group reference is set, check for permissions
5090 # if repo/repo_group reference is set, check for permissions
5088 check_acl = Column('check_acl', Boolean(), nullable=False, default=True)
5091 check_acl = Column('check_acl', Boolean(), nullable=False, default=True)
5089
5092
5090 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
5093 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
5091 upload_user = relationship('User', lazy='joined', primaryjoin='User.user_id==FileStore.user_id')
5094 upload_user = relationship('User', lazy='joined', primaryjoin='User.user_id==FileStore.user_id')
5092
5095
5093 # scope limited to user, which requester have access to
5096 # scope limited to user, which requester have access to
5094 scope_user_id = Column(
5097 scope_user_id = Column(
5095 'scope_user_id', Integer(), ForeignKey('users.user_id'),
5098 'scope_user_id', Integer(), ForeignKey('users.user_id'),
5096 nullable=True, unique=None, default=None)
5099 nullable=True, unique=None, default=None)
5097 user = relationship('User', lazy='joined', primaryjoin='User.user_id==FileStore.scope_user_id')
5100 user = relationship('User', lazy='joined', primaryjoin='User.user_id==FileStore.scope_user_id')
5098
5101
5099 # scope limited to user group, which requester have access to
5102 # scope limited to user group, which requester have access to
5100 scope_user_group_id = Column(
5103 scope_user_group_id = Column(
5101 'scope_user_group_id', Integer(), ForeignKey('users_groups.users_group_id'),
5104 'scope_user_group_id', Integer(), ForeignKey('users_groups.users_group_id'),
5102 nullable=True, unique=None, default=None)
5105 nullable=True, unique=None, default=None)
5103 user_group = relationship('UserGroup', lazy='joined')
5106 user_group = relationship('UserGroup', lazy='joined')
5104
5107
5105 # scope limited to repo, which requester have access to
5108 # scope limited to repo, which requester have access to
5106 scope_repo_id = Column(
5109 scope_repo_id = Column(
5107 'scope_repo_id', Integer(), ForeignKey('repositories.repo_id'),
5110 'scope_repo_id', Integer(), ForeignKey('repositories.repo_id'),
5108 nullable=True, unique=None, default=None)
5111 nullable=True, unique=None, default=None)
5109 repo = relationship('Repository', lazy='joined')
5112 repo = relationship('Repository', lazy='joined')
5110
5113
5111 # scope limited to repo group, which requester have access to
5114 # scope limited to repo group, which requester have access to
5112 scope_repo_group_id = Column(
5115 scope_repo_group_id = Column(
5113 'scope_repo_group_id', Integer(), ForeignKey('groups.group_id'),
5116 'scope_repo_group_id', Integer(), ForeignKey('groups.group_id'),
5114 nullable=True, unique=None, default=None)
5117 nullable=True, unique=None, default=None)
5115 repo_group = relationship('RepoGroup', lazy='joined')
5118 repo_group = relationship('RepoGroup', lazy='joined')
5116
5119
5117 @classmethod
5120 @classmethod
5118 def create(cls, file_uid, filename, file_hash, file_size, file_display_name='',
5121 def create(cls, file_uid, filename, file_hash, file_size, file_display_name='',
5119 file_description='', enabled=True, check_acl=True, user_id=None,
5122 file_description='', enabled=True, check_acl=True, user_id=None,
5120 scope_user_id=None, scope_repo_id=None, scope_repo_group_id=None):
5123 scope_user_id=None, scope_repo_id=None, scope_repo_group_id=None):
5121
5124
5122 store_entry = FileStore()
5125 store_entry = FileStore()
5123 store_entry.file_uid = file_uid
5126 store_entry.file_uid = file_uid
5124 store_entry.file_display_name = file_display_name
5127 store_entry.file_display_name = file_display_name
5125 store_entry.file_org_name = filename
5128 store_entry.file_org_name = filename
5126 store_entry.file_size = file_size
5129 store_entry.file_size = file_size
5127 store_entry.file_hash = file_hash
5130 store_entry.file_hash = file_hash
5128 store_entry.file_description = file_description
5131 store_entry.file_description = file_description
5129
5132
5130 store_entry.check_acl = check_acl
5133 store_entry.check_acl = check_acl
5131 store_entry.enabled = enabled
5134 store_entry.enabled = enabled
5132
5135
5133 store_entry.user_id = user_id
5136 store_entry.user_id = user_id
5134 store_entry.scope_user_id = scope_user_id
5137 store_entry.scope_user_id = scope_user_id
5135 store_entry.scope_repo_id = scope_repo_id
5138 store_entry.scope_repo_id = scope_repo_id
5136 store_entry.scope_repo_group_id = scope_repo_group_id
5139 store_entry.scope_repo_group_id = scope_repo_group_id
5137 return store_entry
5140 return store_entry
5138
5141
5139 @classmethod
5142 @classmethod
5140 def bump_access_counter(cls, file_uid, commit=True):
5143 def bump_access_counter(cls, file_uid, commit=True):
5141 FileStore().query()\
5144 FileStore().query()\
5142 .filter(FileStore.file_uid == file_uid)\
5145 .filter(FileStore.file_uid == file_uid)\
5143 .update({FileStore.accessed_count: (FileStore.accessed_count + 1),
5146 .update({FileStore.accessed_count: (FileStore.accessed_count + 1),
5144 FileStore.accessed_on: datetime.datetime.now()})
5147 FileStore.accessed_on: datetime.datetime.now()})
5145 if commit:
5148 if commit:
5146 Session().commit()
5149 Session().commit()
5147
5150
5148 def __repr__(self):
5151 def __repr__(self):
5149 return '<FileStore({})>'.format(self.file_store_id)
5152 return '<FileStore({})>'.format(self.file_store_id)
5150
5153
5151
5154
5152 class DbMigrateVersion(Base, BaseModel):
5155 class DbMigrateVersion(Base, BaseModel):
5153 __tablename__ = 'db_migrate_version'
5156 __tablename__ = 'db_migrate_version'
5154 __table_args__ = (
5157 __table_args__ = (
5155 base_table_args,
5158 base_table_args,
5156 )
5159 )
5157
5160
5158 repository_id = Column('repository_id', String(250), primary_key=True)
5161 repository_id = Column('repository_id', String(250), primary_key=True)
5159 repository_path = Column('repository_path', Text)
5162 repository_path = Column('repository_path', Text)
5160 version = Column('version', Integer)
5163 version = Column('version', Integer)
5161
5164
5162 @classmethod
5165 @classmethod
5163 def set_version(cls, version):
5166 def set_version(cls, version):
5164 """
5167 """
5165 Helper for forcing a different version, usually for debugging purposes via ishell.
5168 Helper for forcing a different version, usually for debugging purposes via ishell.
5166 """
5169 """
5167 ver = DbMigrateVersion.query().first()
5170 ver = DbMigrateVersion.query().first()
5168 ver.version = version
5171 ver.version = version
5169 Session().commit()
5172 Session().commit()
5170
5173
5171
5174
5172 class DbSession(Base, BaseModel):
5175 class DbSession(Base, BaseModel):
5173 __tablename__ = 'db_session'
5176 __tablename__ = 'db_session'
5174 __table_args__ = (
5177 __table_args__ = (
5175 base_table_args,
5178 base_table_args,
5176 )
5179 )
5177
5180
5178 def __repr__(self):
5181 def __repr__(self):
5179 return '<DB:DbSession({})>'.format(self.id)
5182 return '<DB:DbSession({})>'.format(self.id)
5180
5183
5181 id = Column('id', Integer())
5184 id = Column('id', Integer())
5182 namespace = Column('namespace', String(255), primary_key=True)
5185 namespace = Column('namespace', String(255), primary_key=True)
5183 accessed = Column('accessed', DateTime, nullable=False)
5186 accessed = Column('accessed', DateTime, nullable=False)
5184 created = Column('created', DateTime, nullable=False)
5187 created = Column('created', DateTime, nullable=False)
5185 data = Column('data', PickleType, nullable=False)
5188 data = Column('data', PickleType, nullable=False)
General Comments 0
You need to be logged in to leave comments. Login now