##// END OF EJS Templates
forms: fixed error handling in forms
super-admin -
r5018:b9966616 default
parent child Browse files
Show More
@@ -1,103 +1,103 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2016-2020 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 import logging
22 22
23 23 import formencode
24 24 import formencode.htmlfill
25 25
26 26 from pyramid.httpexceptions import HTTPFound
27 27 from pyramid.renderers import render
28 28 from pyramid.response import Response
29 29
30 30 from rhodecode.apps._base import BaseAppView
31 31 from rhodecode.lib.auth import (
32 32 LoginRequired, HasPermissionAllDecorator, CSRFRequired)
33 33 from rhodecode.lib import helpers as h
34 34 from rhodecode.model.forms import DefaultsForm
35 35 from rhodecode.model.meta import Session
36 36 from rhodecode import BACKENDS
37 37 from rhodecode.model.settings import SettingsModel
38 38
39 39 log = logging.getLogger(__name__)
40 40
41 41
42 42 class AdminDefaultSettingsView(BaseAppView):
43 43
44 44 def load_default_context(self):
45 45 c = self._get_local_tmpl_context()
46 46 return c
47 47
48 48 @LoginRequired()
49 49 @HasPermissionAllDecorator('hg.admin')
50 50 def defaults_repository_show(self):
51 51 c = self.load_default_context()
52 52 c.backends = BACKENDS.keys()
53 53 c.active = 'repositories'
54 54 defaults = SettingsModel().get_default_repo_settings()
55 55
56 56 data = render(
57 57 'rhodecode:templates/admin/defaults/defaults.mako',
58 58 self._get_template_context(c), self.request)
59 59 html = formencode.htmlfill.render(
60 60 data,
61 61 defaults=defaults,
62 62 encoding="UTF-8",
63 63 force_defaults=False
64 64 )
65 65 return Response(html)
66 66
67 67 @LoginRequired()
68 68 @HasPermissionAllDecorator('hg.admin')
69 69 @CSRFRequired()
70 70 def defaults_repository_update(self):
71 71 _ = self.request.translate
72 72 c = self.load_default_context()
73 73 c.active = 'repositories'
74 74 form = DefaultsForm(self.request.translate)()
75 75
76 76 try:
77 77 form_result = form.to_python(dict(self.request.POST))
78 78 for k, v in form_result.items():
79 79 setting = SettingsModel().create_or_update_setting(k, v)
80 80 Session().add(setting)
81 81 Session().commit()
82 82 h.flash(_('Default settings updated successfully'),
83 83 category='success')
84 84
85 85 except formencode.Invalid as errors:
86 86 data = render(
87 87 'rhodecode:templates/admin/defaults/defaults.mako',
88 88 self._get_template_context(c), self.request)
89 89 html = formencode.htmlfill.render(
90 90 data,
91 91 defaults=errors.value,
92 errors=errors.error_dict or {},
92 errors=errors.unpack_errors() or {},
93 93 prefix_error=False,
94 94 encoding="UTF-8",
95 95 force_defaults=False
96 96 )
97 97 return Response(html)
98 98 except Exception:
99 99 log.exception('Exception in update action')
100 100 h.flash(_('Error occurred during update of default values'),
101 101 category='error')
102 102
103 103 raise HTTPFound(h.route_path('admin_defaults_repositories'))
@@ -1,479 +1,479 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2016-2020 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 import re
22 22 import logging
23 23 import formencode
24 24 import formencode.htmlfill
25 25 import datetime
26 26 from pyramid.interfaces import IRoutesMapper
27 27
28 28 from pyramid.httpexceptions import HTTPFound
29 29 from pyramid.renderers import render
30 30 from pyramid.response import Response
31 31
32 32 from rhodecode.apps._base import BaseAppView, DataGridAppView
33 33 from rhodecode.apps.ssh_support import SshKeyFileChangeEvent
34 34 from rhodecode import events
35 35
36 36 from rhodecode.lib import helpers as h
37 37 from rhodecode.lib.auth import (
38 38 LoginRequired, HasPermissionAllDecorator, CSRFRequired)
39 39 from rhodecode.lib.utils2 import aslist, safe_unicode
40 40 from rhodecode.model.db import (
41 41 or_, coalesce, User, UserIpMap, UserSshKeys)
42 42 from rhodecode.model.forms import (
43 43 ApplicationPermissionsForm, ObjectPermissionsForm, UserPermissionsForm)
44 44 from rhodecode.model.meta import Session
45 45 from rhodecode.model.permission import PermissionModel
46 46 from rhodecode.model.settings import SettingsModel
47 47
48 48
49 49 log = logging.getLogger(__name__)
50 50
51 51
52 52 class AdminPermissionsView(BaseAppView, DataGridAppView):
53 53 def load_default_context(self):
54 54 c = self._get_local_tmpl_context()
55 55 PermissionModel().set_global_permission_choices(
56 56 c, gettext_translator=self.request.translate)
57 57 return c
58 58
59 59 @LoginRequired()
60 60 @HasPermissionAllDecorator('hg.admin')
61 61 def permissions_application(self):
62 62 c = self.load_default_context()
63 63 c.active = 'application'
64 64
65 65 c.user = User.get_default_user(refresh=True)
66 66
67 67 app_settings = c.rc_config
68 68
69 69 defaults = {
70 70 'anonymous': c.user.active,
71 71 'default_register_message': app_settings.get(
72 72 'rhodecode_register_message')
73 73 }
74 74 defaults.update(c.user.get_default_perms())
75 75
76 76 data = render('rhodecode:templates/admin/permissions/permissions.mako',
77 77 self._get_template_context(c), self.request)
78 78 html = formencode.htmlfill.render(
79 79 data,
80 80 defaults=defaults,
81 81 encoding="UTF-8",
82 82 force_defaults=False
83 83 )
84 84 return Response(html)
85 85
86 86 @LoginRequired()
87 87 @HasPermissionAllDecorator('hg.admin')
88 88 @CSRFRequired()
89 89 def permissions_application_update(self):
90 90 _ = self.request.translate
91 91 c = self.load_default_context()
92 92 c.active = 'application'
93 93
94 94 _form = ApplicationPermissionsForm(
95 95 self.request.translate,
96 96 [x[0] for x in c.register_choices],
97 97 [x[0] for x in c.password_reset_choices],
98 98 [x[0] for x in c.extern_activate_choices])()
99 99
100 100 try:
101 101 form_result = _form.to_python(dict(self.request.POST))
102 102 form_result.update({'perm_user_name': User.DEFAULT_USER})
103 103 PermissionModel().update_application_permissions(form_result)
104 104
105 105 settings = [
106 106 ('register_message', 'default_register_message'),
107 107 ]
108 108 for setting, form_key in settings:
109 109 sett = SettingsModel().create_or_update_setting(
110 110 setting, form_result[form_key])
111 111 Session().add(sett)
112 112
113 113 Session().commit()
114 114 h.flash(_('Application permissions updated successfully'),
115 115 category='success')
116 116
117 117 except formencode.Invalid as errors:
118 118 defaults = errors.value
119 119
120 120 data = render(
121 121 'rhodecode:templates/admin/permissions/permissions.mako',
122 122 self._get_template_context(c), self.request)
123 123 html = formencode.htmlfill.render(
124 124 data,
125 125 defaults=defaults,
126 errors=errors.error_dict or {},
126 errors=errors.unpack_errors() or {},
127 127 prefix_error=False,
128 128 encoding="UTF-8",
129 129 force_defaults=False
130 130 )
131 131 return Response(html)
132 132
133 133 except Exception:
134 134 log.exception("Exception during update of permissions")
135 135 h.flash(_('Error occurred during update of permissions'),
136 136 category='error')
137 137
138 138 affected_user_ids = [User.get_default_user_id()]
139 139 PermissionModel().trigger_permission_flush(affected_user_ids)
140 140
141 141 raise HTTPFound(h.route_path('admin_permissions_application'))
142 142
143 143 @LoginRequired()
144 144 @HasPermissionAllDecorator('hg.admin')
145 145 def permissions_objects(self):
146 146 c = self.load_default_context()
147 147 c.active = 'objects'
148 148
149 149 c.user = User.get_default_user(refresh=True)
150 150 defaults = {}
151 151 defaults.update(c.user.get_default_perms())
152 152
153 153 data = render(
154 154 'rhodecode:templates/admin/permissions/permissions.mako',
155 155 self._get_template_context(c), self.request)
156 156 html = formencode.htmlfill.render(
157 157 data,
158 158 defaults=defaults,
159 159 encoding="UTF-8",
160 160 force_defaults=False
161 161 )
162 162 return Response(html)
163 163
164 164 @LoginRequired()
165 165 @HasPermissionAllDecorator('hg.admin')
166 166 @CSRFRequired()
167 167 def permissions_objects_update(self):
168 168 _ = self.request.translate
169 169 c = self.load_default_context()
170 170 c.active = 'objects'
171 171
172 172 _form = ObjectPermissionsForm(
173 173 self.request.translate,
174 174 [x[0] for x in c.repo_perms_choices],
175 175 [x[0] for x in c.group_perms_choices],
176 176 [x[0] for x in c.user_group_perms_choices],
177 177 )()
178 178
179 179 try:
180 180 form_result = _form.to_python(dict(self.request.POST))
181 181 form_result.update({'perm_user_name': User.DEFAULT_USER})
182 182 PermissionModel().update_object_permissions(form_result)
183 183
184 184 Session().commit()
185 185 h.flash(_('Object permissions updated successfully'),
186 186 category='success')
187 187
188 188 except formencode.Invalid as errors:
189 189 defaults = errors.value
190 190
191 191 data = render(
192 192 'rhodecode:templates/admin/permissions/permissions.mako',
193 193 self._get_template_context(c), self.request)
194 194 html = formencode.htmlfill.render(
195 195 data,
196 196 defaults=defaults,
197 errors=errors.error_dict or {},
197 errors=errors.unpack_errors() or {},
198 198 prefix_error=False,
199 199 encoding="UTF-8",
200 200 force_defaults=False
201 201 )
202 202 return Response(html)
203 203 except Exception:
204 204 log.exception("Exception during update of permissions")
205 205 h.flash(_('Error occurred during update of permissions'),
206 206 category='error')
207 207
208 208 affected_user_ids = [User.get_default_user_id()]
209 209 PermissionModel().trigger_permission_flush(affected_user_ids)
210 210
211 211 raise HTTPFound(h.route_path('admin_permissions_object'))
212 212
213 213 @LoginRequired()
214 214 @HasPermissionAllDecorator('hg.admin')
215 215 def permissions_branch(self):
216 216 c = self.load_default_context()
217 217 c.active = 'branch'
218 218
219 219 c.user = User.get_default_user(refresh=True)
220 220 defaults = {}
221 221 defaults.update(c.user.get_default_perms())
222 222
223 223 data = render(
224 224 'rhodecode:templates/admin/permissions/permissions.mako',
225 225 self._get_template_context(c), self.request)
226 226 html = formencode.htmlfill.render(
227 227 data,
228 228 defaults=defaults,
229 229 encoding="UTF-8",
230 230 force_defaults=False
231 231 )
232 232 return Response(html)
233 233
234 234 @LoginRequired()
235 235 @HasPermissionAllDecorator('hg.admin')
236 236 def permissions_global(self):
237 237 c = self.load_default_context()
238 238 c.active = 'global'
239 239
240 240 c.user = User.get_default_user(refresh=True)
241 241 defaults = {}
242 242 defaults.update(c.user.get_default_perms())
243 243
244 244 data = render(
245 245 'rhodecode:templates/admin/permissions/permissions.mako',
246 246 self._get_template_context(c), self.request)
247 247 html = formencode.htmlfill.render(
248 248 data,
249 249 defaults=defaults,
250 250 encoding="UTF-8",
251 251 force_defaults=False
252 252 )
253 253 return Response(html)
254 254
255 255 @LoginRequired()
256 256 @HasPermissionAllDecorator('hg.admin')
257 257 @CSRFRequired()
258 258 def permissions_global_update(self):
259 259 _ = self.request.translate
260 260 c = self.load_default_context()
261 261 c.active = 'global'
262 262
263 263 _form = UserPermissionsForm(
264 264 self.request.translate,
265 265 [x[0] for x in c.repo_create_choices],
266 266 [x[0] for x in c.repo_create_on_write_choices],
267 267 [x[0] for x in c.repo_group_create_choices],
268 268 [x[0] for x in c.user_group_create_choices],
269 269 [x[0] for x in c.fork_choices],
270 270 [x[0] for x in c.inherit_default_permission_choices])()
271 271
272 272 try:
273 273 form_result = _form.to_python(dict(self.request.POST))
274 274 form_result.update({'perm_user_name': User.DEFAULT_USER})
275 275 PermissionModel().update_user_permissions(form_result)
276 276
277 277 Session().commit()
278 278 h.flash(_('Global permissions updated successfully'),
279 279 category='success')
280 280
281 281 except formencode.Invalid as errors:
282 282 defaults = errors.value
283 283
284 284 data = render(
285 285 'rhodecode:templates/admin/permissions/permissions.mako',
286 286 self._get_template_context(c), self.request)
287 287 html = formencode.htmlfill.render(
288 288 data,
289 289 defaults=defaults,
290 errors=errors.error_dict or {},
290 errors=errors.unpack_errors() or {},
291 291 prefix_error=False,
292 292 encoding="UTF-8",
293 293 force_defaults=False
294 294 )
295 295 return Response(html)
296 296 except Exception:
297 297 log.exception("Exception during update of permissions")
298 298 h.flash(_('Error occurred during update of permissions'),
299 299 category='error')
300 300
301 301 affected_user_ids = [User.get_default_user_id()]
302 302 PermissionModel().trigger_permission_flush(affected_user_ids)
303 303
304 304 raise HTTPFound(h.route_path('admin_permissions_global'))
305 305
306 306 @LoginRequired()
307 307 @HasPermissionAllDecorator('hg.admin')
308 308 def permissions_ips(self):
309 309 c = self.load_default_context()
310 310 c.active = 'ips'
311 311
312 312 c.user = User.get_default_user(refresh=True)
313 313 c.user_ip_map = (
314 314 UserIpMap.query().filter(UserIpMap.user == c.user).all())
315 315
316 316 return self._get_template_context(c)
317 317
318 318 @LoginRequired()
319 319 @HasPermissionAllDecorator('hg.admin')
320 320 def permissions_overview(self):
321 321 c = self.load_default_context()
322 322 c.active = 'perms'
323 323
324 324 c.user = User.get_default_user(refresh=True)
325 325 c.perm_user = c.user.AuthUser()
326 326 return self._get_template_context(c)
327 327
328 328 @LoginRequired()
329 329 @HasPermissionAllDecorator('hg.admin')
330 330 def auth_token_access(self):
331 331 from rhodecode import CONFIG
332 332
333 333 c = self.load_default_context()
334 334 c.active = 'auth_token_access'
335 335
336 336 c.user = User.get_default_user(refresh=True)
337 337 c.perm_user = c.user.AuthUser()
338 338
339 339 mapper = self.request.registry.queryUtility(IRoutesMapper)
340 340 c.view_data = []
341 341
342 342 _argument_prog = re.compile(r'\{(.*?)\}|:\((.*)\)')
343 343 introspector = self.request.registry.introspector
344 344
345 345 view_intr = {}
346 346 for view_data in introspector.get_category('views'):
347 347 intr = view_data['introspectable']
348 348
349 349 if 'route_name' in intr and intr['attr']:
350 350 view_intr[intr['route_name']] = '{}:{}'.format(
351 351 str(intr['derived_callable'].__name__), intr['attr']
352 352 )
353 353
354 354 c.whitelist_key = 'api_access_controllers_whitelist'
355 355 c.whitelist_file = CONFIG.get('__file__')
356 356 whitelist_views = aslist(
357 357 CONFIG.get(c.whitelist_key), sep=',')
358 358
359 359 for route_info in mapper.get_routes():
360 360 if not route_info.name.startswith('__'):
361 361 routepath = route_info.pattern
362 362
363 363 def replace(matchobj):
364 364 if matchobj.group(1):
365 365 return "{%s}" % matchobj.group(1).split(':')[0]
366 366 else:
367 367 return "{%s}" % matchobj.group(2)
368 368
369 369 routepath = _argument_prog.sub(replace, routepath)
370 370
371 371 if not routepath.startswith('/'):
372 372 routepath = '/' + routepath
373 373
374 374 view_fqn = view_intr.get(route_info.name, 'NOT AVAILABLE')
375 375 active = view_fqn in whitelist_views
376 376 c.view_data.append((route_info.name, view_fqn, routepath, active))
377 377
378 378 c.whitelist_views = whitelist_views
379 379 return self._get_template_context(c)
380 380
381 381 def ssh_enabled(self):
382 382 return self.request.registry.settings.get(
383 383 'ssh.generate_authorized_keyfile')
384 384
385 385 @LoginRequired()
386 386 @HasPermissionAllDecorator('hg.admin')
387 387 def ssh_keys(self):
388 388 c = self.load_default_context()
389 389 c.active = 'ssh_keys'
390 390 c.ssh_enabled = self.ssh_enabled()
391 391 return self._get_template_context(c)
392 392
393 393 @LoginRequired()
394 394 @HasPermissionAllDecorator('hg.admin')
395 395 def ssh_keys_data(self):
396 396 _ = self.request.translate
397 397 self.load_default_context()
398 398 column_map = {
399 399 'fingerprint': 'ssh_key_fingerprint',
400 400 'username': User.username
401 401 }
402 402 draw, start, limit = self._extract_chunk(self.request)
403 403 search_q, order_by, order_dir = self._extract_ordering(
404 404 self.request, column_map=column_map)
405 405
406 406 ssh_keys_data_total_count = UserSshKeys.query()\
407 407 .count()
408 408
409 409 # json generate
410 410 base_q = UserSshKeys.query().join(UserSshKeys.user)
411 411
412 412 if search_q:
413 413 like_expression = u'%{}%'.format(safe_unicode(search_q))
414 414 base_q = base_q.filter(or_(
415 415 User.username.ilike(like_expression),
416 416 UserSshKeys.ssh_key_fingerprint.ilike(like_expression),
417 417 ))
418 418
419 419 users_data_total_filtered_count = base_q.count()
420 420
421 421 sort_col = self._get_order_col(order_by, UserSshKeys)
422 422 if sort_col:
423 423 if order_dir == 'asc':
424 424 # handle null values properly to order by NULL last
425 425 if order_by in ['created_on']:
426 426 sort_col = coalesce(sort_col, datetime.date.max)
427 427 sort_col = sort_col.asc()
428 428 else:
429 429 # handle null values properly to order by NULL last
430 430 if order_by in ['created_on']:
431 431 sort_col = coalesce(sort_col, datetime.date.min)
432 432 sort_col = sort_col.desc()
433 433
434 434 base_q = base_q.order_by(sort_col)
435 435 base_q = base_q.offset(start).limit(limit)
436 436
437 437 ssh_keys = base_q.all()
438 438
439 439 ssh_keys_data = []
440 440 for ssh_key in ssh_keys:
441 441 ssh_keys_data.append({
442 442 "username": h.gravatar_with_user(self.request, ssh_key.user.username),
443 443 "fingerprint": ssh_key.ssh_key_fingerprint,
444 444 "description": ssh_key.description,
445 445 "created_on": h.format_date(ssh_key.created_on),
446 446 "accessed_on": h.format_date(ssh_key.accessed_on),
447 447 "action": h.link_to(
448 448 _('Edit'), h.route_path('edit_user_ssh_keys',
449 449 user_id=ssh_key.user.user_id))
450 450 })
451 451
452 452 data = ({
453 453 'draw': draw,
454 454 'data': ssh_keys_data,
455 455 'recordsTotal': ssh_keys_data_total_count,
456 456 'recordsFiltered': users_data_total_filtered_count,
457 457 })
458 458
459 459 return data
460 460
461 461 @LoginRequired()
462 462 @HasPermissionAllDecorator('hg.admin')
463 463 @CSRFRequired()
464 464 def ssh_keys_update(self):
465 465 _ = self.request.translate
466 466 self.load_default_context()
467 467
468 468 ssh_enabled = self.ssh_enabled()
469 469 key_file = self.request.registry.settings.get(
470 470 'ssh.authorized_keys_file_path')
471 471 if ssh_enabled:
472 472 events.trigger(SshKeyFileChangeEvent(), self.request.registry)
473 473 h.flash(_('Updated SSH keys file: {}').format(key_file),
474 474 category='success')
475 475 else:
476 476 h.flash(_('SSH key support is disabled in .ini file'),
477 477 category='warning')
478 478
479 479 raise HTTPFound(h.route_path('admin_permissions_ssh_keys'))
@@ -1,356 +1,356 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2016-2020 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20 import datetime
21 21 import logging
22 22 import time
23 23
24 24 import formencode
25 25 import formencode.htmlfill
26 26
27 27 from pyramid.httpexceptions import HTTPFound, HTTPForbidden
28 28
29 29 from pyramid.renderers import render
30 30 from pyramid.response import Response
31 31
32 32 from rhodecode import events
33 33 from rhodecode.apps._base import BaseAppView, DataGridAppView
34 34
35 35 from rhodecode.lib.auth import (
36 36 LoginRequired, CSRFRequired, NotAnonymous,
37 37 HasPermissionAny, HasRepoGroupPermissionAny)
38 38 from rhodecode.lib import helpers as h, audit_logger
39 39 from rhodecode.lib.utils2 import safe_int, safe_unicode, datetime_to_time
40 40 from rhodecode.model.forms import RepoGroupForm
41 41 from rhodecode.model.permission import PermissionModel
42 42 from rhodecode.model.repo_group import RepoGroupModel
43 43 from rhodecode.model.scm import RepoGroupList
44 44 from rhodecode.model.db import (
45 45 or_, count, func, in_filter_generator, Session, RepoGroup, User, Repository)
46 46
47 47 log = logging.getLogger(__name__)
48 48
49 49
50 50 class AdminRepoGroupsView(BaseAppView, DataGridAppView):
51 51
52 52 def load_default_context(self):
53 53 c = self._get_local_tmpl_context()
54 54
55 55 return c
56 56
57 57 def _load_form_data(self, c):
58 58 allow_empty_group = False
59 59
60 60 if self._can_create_repo_group():
61 61 # we're global admin, we're ok and we can create TOP level groups
62 62 allow_empty_group = True
63 63
64 64 # override the choices for this form, we need to filter choices
65 65 # and display only those we have ADMIN right
66 66 groups_with_admin_rights = RepoGroupList(
67 67 RepoGroup.query().all(),
68 68 perm_set=['group.admin'], extra_kwargs=dict(user=self._rhodecode_user))
69 69 c.repo_groups = RepoGroup.groups_choices(
70 70 groups=groups_with_admin_rights,
71 71 show_empty_group=allow_empty_group)
72 72 c.personal_repo_group = self._rhodecode_user.personal_repo_group
73 73
74 74 def _can_create_repo_group(self, parent_group_id=None):
75 75 is_admin = HasPermissionAny('hg.admin')('group create controller')
76 76 create_repo_group = HasPermissionAny(
77 77 'hg.repogroup.create.true')('group create controller')
78 78 if is_admin or (create_repo_group and not parent_group_id):
79 79 # we're global admin, or we have global repo group create
80 80 # permission
81 81 # we're ok and we can create TOP level groups
82 82 return True
83 83 elif parent_group_id:
84 84 # we check the permission if we can write to parent group
85 85 group = RepoGroup.get(parent_group_id)
86 86 group_name = group.group_name if group else None
87 87 if HasRepoGroupPermissionAny('group.admin')(
88 88 group_name, 'check if user is an admin of group'):
89 89 # we're an admin of passed in group, we're ok.
90 90 return True
91 91 else:
92 92 return False
93 93 return False
94 94
95 95 # permission check in data loading of
96 96 # `repo_group_list_data` via RepoGroupList
97 97 @LoginRequired()
98 98 @NotAnonymous()
99 99 def repo_group_list(self):
100 100 c = self.load_default_context()
101 101 return self._get_template_context(c)
102 102
103 103 # permission check inside
104 104 @LoginRequired()
105 105 @NotAnonymous()
106 106 def repo_group_list_data(self):
107 107 self.load_default_context()
108 108 column_map = {
109 109 'name': 'group_name_hash',
110 110 'desc': 'group_description',
111 111 'last_change': 'updated_on',
112 112 'top_level_repos': 'repos_total',
113 113 'owner': 'user_username',
114 114 }
115 115 draw, start, limit = self._extract_chunk(self.request)
116 116 search_q, order_by, order_dir = self._extract_ordering(
117 117 self.request, column_map=column_map)
118 118
119 119 _render = self.request.get_partial_renderer(
120 120 'rhodecode:templates/data_table/_dt_elements.mako')
121 121 c = _render.get_call_context()
122 122
123 123 def quick_menu(repo_group_name):
124 124 return _render('quick_repo_group_menu', repo_group_name)
125 125
126 126 def repo_group_lnk(repo_group_name):
127 127 return _render('repo_group_name', repo_group_name)
128 128
129 129 def last_change(last_change):
130 130 if isinstance(last_change, datetime.datetime) and not last_change.tzinfo:
131 131 ts = time.time()
132 132 utc_offset = (datetime.datetime.fromtimestamp(ts)
133 133 - datetime.datetime.utcfromtimestamp(ts)).total_seconds()
134 134 last_change = last_change + datetime.timedelta(seconds=utc_offset)
135 135 return _render("last_change", last_change)
136 136
137 137 def desc(desc, personal):
138 138 return _render(
139 139 'repo_group_desc', desc, personal, c.visual.stylify_metatags)
140 140
141 141 def repo_group_actions(repo_group_id, repo_group_name, gr_count):
142 142 return _render(
143 143 'repo_group_actions', repo_group_id, repo_group_name, gr_count)
144 144
145 145 def user_profile(username):
146 146 return _render('user_profile', username)
147 147
148 148 _perms = ['group.admin']
149 149 allowed_ids = [-1] + self._rhodecode_user.repo_group_acl_ids_from_stack(_perms)
150 150
151 151 repo_groups_data_total_count = RepoGroup.query()\
152 152 .filter(or_(
153 153 # generate multiple IN to fix limitation problems
154 154 *in_filter_generator(RepoGroup.group_id, allowed_ids)
155 155 )) \
156 156 .count()
157 157
158 158 repo_groups_data_total_inactive_count = RepoGroup.query()\
159 159 .filter(RepoGroup.group_id.in_(allowed_ids))\
160 160 .count()
161 161
162 162 repo_count = count(Repository.repo_id)
163 163 base_q = Session.query(
164 164 RepoGroup.group_name,
165 165 RepoGroup.group_name_hash,
166 166 RepoGroup.group_description,
167 167 RepoGroup.group_id,
168 168 RepoGroup.personal,
169 169 RepoGroup.updated_on,
170 170 User,
171 171 repo_count.label('repos_count')
172 172 ) \
173 173 .filter(or_(
174 174 # generate multiple IN to fix limitation problems
175 175 *in_filter_generator(RepoGroup.group_id, allowed_ids)
176 176 )) \
177 177 .outerjoin(Repository, Repository.group_id == RepoGroup.group_id) \
178 178 .join(User, User.user_id == RepoGroup.user_id) \
179 179 .group_by(RepoGroup, User)
180 180
181 181 if search_q:
182 182 like_expression = u'%{}%'.format(safe_unicode(search_q))
183 183 base_q = base_q.filter(or_(
184 184 RepoGroup.group_name.ilike(like_expression),
185 185 ))
186 186
187 187 repo_groups_data_total_filtered_count = base_q.count()
188 188 # the inactive isn't really used, but we still make it same as other data grids
189 189 # which use inactive (users,user groups)
190 190 repo_groups_data_total_filtered_inactive_count = repo_groups_data_total_filtered_count
191 191
192 192 sort_defined = False
193 193 if order_by == 'group_name':
194 194 sort_col = func.lower(RepoGroup.group_name)
195 195 sort_defined = True
196 196 elif order_by == 'repos_total':
197 197 sort_col = repo_count
198 198 sort_defined = True
199 199 elif order_by == 'user_username':
200 200 sort_col = User.username
201 201 else:
202 202 sort_col = getattr(RepoGroup, order_by, None)
203 203
204 204 if sort_defined or sort_col:
205 205 if order_dir == 'asc':
206 206 sort_col = sort_col.asc()
207 207 else:
208 208 sort_col = sort_col.desc()
209 209
210 210 base_q = base_q.order_by(sort_col)
211 211 base_q = base_q.offset(start).limit(limit)
212 212
213 213 # authenticated access to user groups
214 214 auth_repo_group_list = base_q.all()
215 215
216 216 repo_groups_data = []
217 217 for repo_gr in auth_repo_group_list:
218 218 row = {
219 219 "menu": quick_menu(repo_gr.group_name),
220 220 "name": repo_group_lnk(repo_gr.group_name),
221 221
222 222 "last_change": last_change(repo_gr.updated_on),
223 223
224 224 "last_changeset": "",
225 225 "last_changeset_raw": "",
226 226
227 227 "desc": desc(repo_gr.group_description, repo_gr.personal),
228 228 "owner": user_profile(repo_gr.User.username),
229 229 "top_level_repos": repo_gr.repos_count,
230 230 "action": repo_group_actions(
231 231 repo_gr.group_id, repo_gr.group_name, repo_gr.repos_count),
232 232
233 233 }
234 234
235 235 repo_groups_data.append(row)
236 236
237 237 data = ({
238 238 'draw': draw,
239 239 'data': repo_groups_data,
240 240 'recordsTotal': repo_groups_data_total_count,
241 241 'recordsTotalInactive': repo_groups_data_total_inactive_count,
242 242 'recordsFiltered': repo_groups_data_total_filtered_count,
243 243 'recordsFilteredInactive': repo_groups_data_total_filtered_inactive_count,
244 244 })
245 245
246 246 return data
247 247
248 248 @LoginRequired()
249 249 @NotAnonymous()
250 250 # perm checks inside
251 251 def repo_group_new(self):
252 252 c = self.load_default_context()
253 253
254 254 # perm check for admin, create_group perm or admin of parent_group
255 255 parent_group_id = safe_int(self.request.GET.get('parent_group'))
256 256 _gr = RepoGroup.get(parent_group_id)
257 257 if not self._can_create_repo_group(parent_group_id):
258 258 raise HTTPForbidden()
259 259
260 260 self._load_form_data(c)
261 261
262 262 defaults = {} # Future proof for default of repo group
263 263
264 264 parent_group_choice = '-1'
265 265 if not self._rhodecode_user.is_admin and self._rhodecode_user.personal_repo_group:
266 266 parent_group_choice = self._rhodecode_user.personal_repo_group
267 267
268 268 if parent_group_id and _gr:
269 269 if parent_group_id in [x[0] for x in c.repo_groups]:
270 270 parent_group_choice = safe_unicode(parent_group_id)
271 271
272 272 defaults.update({'group_parent_id': parent_group_choice})
273 273
274 274 data = render(
275 275 'rhodecode:templates/admin/repo_groups/repo_group_add.mako',
276 276 self._get_template_context(c), self.request)
277 277
278 278 html = formencode.htmlfill.render(
279 279 data,
280 280 defaults=defaults,
281 281 encoding="UTF-8",
282 282 force_defaults=False
283 283 )
284 284 return Response(html)
285 285
286 286 @LoginRequired()
287 287 @NotAnonymous()
288 288 @CSRFRequired()
289 289 # perm checks inside
290 290 def repo_group_create(self):
291 291 c = self.load_default_context()
292 292 _ = self.request.translate
293 293
294 294 parent_group_id = safe_int(self.request.POST.get('group_parent_id'))
295 295 can_create = self._can_create_repo_group(parent_group_id)
296 296
297 297 self._load_form_data(c)
298 298 # permissions for can create group based on parent_id are checked
299 299 # here in the Form
300 300 available_groups = map(lambda k: safe_unicode(k[0]), c.repo_groups)
301 301 repo_group_form = RepoGroupForm(
302 302 self.request.translate, available_groups=available_groups,
303 303 can_create_in_root=can_create)()
304 304
305 305 repo_group_name = self.request.POST.get('group_name')
306 306 try:
307 307 owner = self._rhodecode_user
308 308 form_result = repo_group_form.to_python(dict(self.request.POST))
309 309 copy_permissions = form_result.get('group_copy_permissions')
310 310 repo_group = RepoGroupModel().create(
311 311 group_name=form_result['group_name_full'],
312 312 group_description=form_result['group_description'],
313 313 owner=owner.user_id,
314 314 copy_permissions=form_result['group_copy_permissions']
315 315 )
316 316 Session().flush()
317 317
318 318 repo_group_data = repo_group.get_api_data()
319 319 audit_logger.store_web(
320 320 'repo_group.create', action_data={'data': repo_group_data},
321 321 user=self._rhodecode_user)
322 322
323 323 Session().commit()
324 324
325 325 _new_group_name = form_result['group_name_full']
326 326
327 327 repo_group_url = h.link_to(
328 328 _new_group_name,
329 329 h.route_path('repo_group_home', repo_group_name=_new_group_name))
330 330 h.flash(h.literal(_('Created repository group %s')
331 331 % repo_group_url), category='success')
332 332
333 333 except formencode.Invalid as errors:
334 334 data = render(
335 335 'rhodecode:templates/admin/repo_groups/repo_group_add.mako',
336 336 self._get_template_context(c), self.request)
337 337 html = formencode.htmlfill.render(
338 338 data,
339 339 defaults=errors.value,
340 errors=errors.error_dict or {},
340 errors=errors.unpack_errors() or {},
341 341 prefix_error=False,
342 342 encoding="UTF-8",
343 343 force_defaults=False
344 344 )
345 345 return Response(html)
346 346 except Exception:
347 347 log.exception("Exception during creation of repository group")
348 348 h.flash(_('Error occurred during creation of repository group %s')
349 349 % repo_group_name, category='error')
350 350 raise HTTPFound(h.route_path('home'))
351 351
352 352 PermissionModel().trigger_permission_flush()
353 353
354 354 raise HTTPFound(
355 355 h.route_path('repo_group_home',
356 356 repo_group_name=form_result['group_name_full']))
@@ -1,250 +1,250 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2016-2020 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 import logging
22 22 import formencode
23 23 import formencode.htmlfill
24 24
25 25 from pyramid.httpexceptions import HTTPFound, HTTPForbidden
26 26
27 27 from pyramid.renderers import render
28 28 from pyramid.response import Response
29 29
30 30 from rhodecode import events
31 31 from rhodecode.apps._base import BaseAppView, DataGridAppView
32 32 from rhodecode.lib.celerylib.utils import get_task_id
33 33
34 34 from rhodecode.lib.auth import (
35 35 LoginRequired, CSRFRequired, NotAnonymous,
36 36 HasPermissionAny, HasRepoGroupPermissionAny)
37 37 from rhodecode.lib import helpers as h
38 38 from rhodecode.lib.utils import repo_name_slug
39 39 from rhodecode.lib.utils2 import safe_int, safe_unicode
40 40 from rhodecode.model.forms import RepoForm
41 41 from rhodecode.model.permission import PermissionModel
42 42 from rhodecode.model.repo import RepoModel
43 43 from rhodecode.model.scm import RepoList, RepoGroupList, ScmModel
44 44 from rhodecode.model.settings import SettingsModel
45 45 from rhodecode.model.db import (
46 46 in_filter_generator, or_, func, Session, Repository, RepoGroup, User)
47 47
48 48 log = logging.getLogger(__name__)
49 49
50 50
51 51 class AdminReposView(BaseAppView, DataGridAppView):
52 52
53 53 def load_default_context(self):
54 54 c = self._get_local_tmpl_context()
55 55 return c
56 56
57 57 def _load_form_data(self, c):
58 58 acl_groups = RepoGroupList(RepoGroup.query().all(),
59 59 perm_set=['group.write', 'group.admin'])
60 60 c.repo_groups = RepoGroup.groups_choices(groups=acl_groups)
61 61 c.repo_groups_choices = map(lambda k: safe_unicode(k[0]), c.repo_groups)
62 62 c.personal_repo_group = self._rhodecode_user.personal_repo_group
63 63
64 64 @LoginRequired()
65 65 @NotAnonymous()
66 66 # perms check inside
67 67 def repository_list(self):
68 68 c = self.load_default_context()
69 69 return self._get_template_context(c)
70 70
71 71 @LoginRequired()
72 72 @NotAnonymous()
73 73 # perms check inside
74 74 def repository_list_data(self):
75 75 self.load_default_context()
76 76 column_map = {
77 77 'name': 'repo_name',
78 78 'desc': 'description',
79 79 'last_change': 'updated_on',
80 80 'owner': 'user_username',
81 81 }
82 82 draw, start, limit = self._extract_chunk(self.request)
83 83 search_q, order_by, order_dir = self._extract_ordering(
84 84 self.request, column_map=column_map)
85 85
86 86 _perms = ['repository.admin']
87 87 allowed_ids = [-1] + self._rhodecode_user.repo_acl_ids_from_stack(_perms)
88 88
89 89 repos_data_total_count = Repository.query() \
90 90 .filter(or_(
91 91 # generate multiple IN to fix limitation problems
92 92 *in_filter_generator(Repository.repo_id, allowed_ids))
93 93 ) \
94 94 .count()
95 95
96 96 base_q = Session.query(
97 97 Repository.repo_id,
98 98 Repository.repo_name,
99 99 Repository.description,
100 100 Repository.repo_type,
101 101 Repository.repo_state,
102 102 Repository.private,
103 103 Repository.archived,
104 104 Repository.fork,
105 105 Repository.updated_on,
106 106 Repository._changeset_cache,
107 107 User,
108 108 ) \
109 109 .filter(or_(
110 110 # generate multiple IN to fix limitation problems
111 111 *in_filter_generator(Repository.repo_id, allowed_ids))
112 112 ) \
113 113 .join(User, User.user_id == Repository.user_id) \
114 114 .group_by(Repository, User)
115 115
116 116 if search_q:
117 117 like_expression = u'%{}%'.format(safe_unicode(search_q))
118 118 base_q = base_q.filter(or_(
119 119 Repository.repo_name.ilike(like_expression),
120 120 ))
121 121
122 122 repos_data_total_filtered_count = base_q.count()
123 123
124 124 sort_defined = False
125 125 if order_by == 'repo_name':
126 126 sort_col = func.lower(Repository.repo_name)
127 127 sort_defined = True
128 128 elif order_by == 'user_username':
129 129 sort_col = User.username
130 130 else:
131 131 sort_col = getattr(Repository, order_by, None)
132 132
133 133 if sort_defined or sort_col:
134 134 if order_dir == 'asc':
135 135 sort_col = sort_col.asc()
136 136 else:
137 137 sort_col = sort_col.desc()
138 138
139 139 base_q = base_q.order_by(sort_col)
140 140 base_q = base_q.offset(start).limit(limit)
141 141
142 142 repos_list = base_q.all()
143 143
144 144 repos_data = RepoModel().get_repos_as_dict(
145 145 repo_list=repos_list, admin=True, super_user_actions=True)
146 146
147 147 data = ({
148 148 'draw': draw,
149 149 'data': repos_data,
150 150 'recordsTotal': repos_data_total_count,
151 151 'recordsFiltered': repos_data_total_filtered_count,
152 152 })
153 153 return data
154 154
155 155 @LoginRequired()
156 156 @NotAnonymous()
157 157 # perms check inside
158 158 def repository_new(self):
159 159 c = self.load_default_context()
160 160
161 161 new_repo = self.request.GET.get('repo', '')
162 162 parent_group_id = safe_int(self.request.GET.get('parent_group'))
163 163 _gr = RepoGroup.get(parent_group_id)
164 164
165 165 if not HasPermissionAny('hg.admin', 'hg.create.repository')():
166 166 # you're not super admin nor have global create permissions,
167 167 # but maybe you have at least write permission to a parent group ?
168 168
169 169 gr_name = _gr.group_name if _gr else None
170 170 # create repositories with write permission on group is set to true
171 171 create_on_write = HasPermissionAny('hg.create.write_on_repogroup.true')()
172 172 group_admin = HasRepoGroupPermissionAny('group.admin')(group_name=gr_name)
173 173 group_write = HasRepoGroupPermissionAny('group.write')(group_name=gr_name)
174 174 if not (group_admin or (group_write and create_on_write)):
175 175 raise HTTPForbidden()
176 176
177 177 self._load_form_data(c)
178 178 c.new_repo = repo_name_slug(new_repo)
179 179
180 180 # apply the defaults from defaults page
181 181 defaults = SettingsModel().get_default_repo_settings(strip_prefix=True)
182 182 # set checkbox to autochecked
183 183 defaults['repo_copy_permissions'] = True
184 184
185 185 parent_group_choice = '-1'
186 186 if not self._rhodecode_user.is_admin and self._rhodecode_user.personal_repo_group:
187 187 parent_group_choice = self._rhodecode_user.personal_repo_group
188 188
189 189 if parent_group_id and _gr:
190 190 if parent_group_id in [x[0] for x in c.repo_groups]:
191 191 parent_group_choice = safe_unicode(parent_group_id)
192 192
193 193 defaults.update({'repo_group': parent_group_choice})
194 194
195 195 data = render('rhodecode:templates/admin/repos/repo_add.mako',
196 196 self._get_template_context(c), self.request)
197 197 html = formencode.htmlfill.render(
198 198 data,
199 199 defaults=defaults,
200 200 encoding="UTF-8",
201 201 force_defaults=False
202 202 )
203 203 return Response(html)
204 204
205 205 @LoginRequired()
206 206 @NotAnonymous()
207 207 @CSRFRequired()
208 208 # perms check inside
209 209 def repository_create(self):
210 210 c = self.load_default_context()
211 211
212 212 form_result = {}
213 213 self._load_form_data(c)
214 214
215 215 try:
216 216 # CanWriteToGroup validators checks permissions of this POST
217 217 form = RepoForm(
218 218 self.request.translate, repo_groups=c.repo_groups_choices)()
219 219 form_result = form.to_python(dict(self.request.POST))
220 220 copy_permissions = form_result.get('repo_copy_permissions')
221 221 # create is done sometimes async on celery, db transaction
222 222 # management is handled there.
223 223 task = RepoModel().create(form_result, self._rhodecode_user.user_id)
224 224 task_id = get_task_id(task)
225 225 except formencode.Invalid as errors:
226 226 data = render('rhodecode:templates/admin/repos/repo_add.mako',
227 227 self._get_template_context(c), self.request)
228 228 html = formencode.htmlfill.render(
229 229 data,
230 230 defaults=errors.value,
231 errors=errors.error_dict or {},
231 errors=errors.unpack_errors() or {},
232 232 prefix_error=False,
233 233 encoding="UTF-8",
234 234 force_defaults=False
235 235 )
236 236 return Response(html)
237 237
238 238 except Exception as e:
239 239 msg = self._log_creation_exception(e, form_result.get('repo_name'))
240 240 h.flash(msg, category='error')
241 241 raise HTTPFound(h.route_path('home'))
242 242
243 243 repo_name = form_result.get('repo_name_full')
244 244
245 245 affected_user_ids = [self._rhodecode_user.user_id]
246 246 PermissionModel().trigger_permission_flush(affected_user_ids)
247 247
248 248 raise HTTPFound(
249 249 h.route_path('repo_creating', repo_name=repo_name,
250 250 _query=dict(task_id=task_id)))
@@ -1,720 +1,720 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2020 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21
22 22 import logging
23 23 import collections
24 24
25 25 import datetime
26 26 import formencode
27 27 import formencode.htmlfill
28 28
29 29 import rhodecode
30 30
31 31 from pyramid.httpexceptions import HTTPFound, HTTPNotFound
32 32 from pyramid.renderers import render
33 33 from pyramid.response import Response
34 34
35 35 from rhodecode.apps._base import BaseAppView
36 36 from rhodecode.apps._base.navigation import navigation_list
37 37 from rhodecode.apps.svn_support.config_keys import generate_config
38 38 from rhodecode.lib import helpers as h
39 39 from rhodecode.lib.auth import (
40 40 LoginRequired, HasPermissionAllDecorator, CSRFRequired)
41 41 from rhodecode.lib.celerylib import tasks, run_task
42 42 from rhodecode.lib.utils import repo2db_mapper
43 43 from rhodecode.lib.utils2 import str2bool, safe_unicode, AttributeDict
44 44 from rhodecode.lib.index import searcher_from_config
45 45
46 46 from rhodecode.model.db import RhodeCodeUi, Repository
47 47 from rhodecode.model.forms import (ApplicationSettingsForm,
48 48 ApplicationUiSettingsForm, ApplicationVisualisationForm,
49 49 LabsSettingsForm, IssueTrackerPatternsForm)
50 50 from rhodecode.model.permission import PermissionModel
51 51 from rhodecode.model.repo_group import RepoGroupModel
52 52
53 53 from rhodecode.model.scm import ScmModel
54 54 from rhodecode.model.notification import EmailNotificationModel
55 55 from rhodecode.model.meta import Session
56 56 from rhodecode.model.settings import (
57 57 IssueTrackerSettingsModel, VcsSettingsModel, SettingNotFound,
58 58 SettingsModel)
59 59
60 60
61 61 log = logging.getLogger(__name__)
62 62
63 63
64 64 class AdminSettingsView(BaseAppView):
65 65
66 66 def load_default_context(self):
67 67 c = self._get_local_tmpl_context()
68 68 c.labs_active = str2bool(
69 69 rhodecode.CONFIG.get('labs_settings_active', 'true'))
70 70 c.navlist = navigation_list(self.request)
71 71 return c
72 72
73 73 @classmethod
74 74 def _get_ui_settings(cls):
75 75 ret = RhodeCodeUi.query().all()
76 76
77 77 if not ret:
78 78 raise Exception('Could not get application ui settings !')
79 79 settings = {}
80 80 for each in ret:
81 81 k = each.ui_key
82 82 v = each.ui_value
83 83 if k == '/':
84 84 k = 'root_path'
85 85
86 86 if k in ['push_ssl', 'publish', 'enabled']:
87 87 v = str2bool(v)
88 88
89 89 if k.find('.') != -1:
90 90 k = k.replace('.', '_')
91 91
92 92 if each.ui_section in ['hooks', 'extensions']:
93 93 v = each.ui_active
94 94
95 95 settings[each.ui_section + '_' + k] = v
96 96 return settings
97 97
98 98 @classmethod
99 99 def _form_defaults(cls):
100 100 defaults = SettingsModel().get_all_settings()
101 101 defaults.update(cls._get_ui_settings())
102 102
103 103 defaults.update({
104 104 'new_svn_branch': '',
105 105 'new_svn_tag': '',
106 106 })
107 107 return defaults
108 108
109 109 @LoginRequired()
110 110 @HasPermissionAllDecorator('hg.admin')
111 111 def settings_vcs(self):
112 112 c = self.load_default_context()
113 113 c.active = 'vcs'
114 114 model = VcsSettingsModel()
115 115 c.svn_branch_patterns = model.get_global_svn_branch_patterns()
116 116 c.svn_tag_patterns = model.get_global_svn_tag_patterns()
117 117
118 118 settings = self.request.registry.settings
119 119 c.svn_proxy_generate_config = settings[generate_config]
120 120
121 121 defaults = self._form_defaults()
122 122
123 123 model.create_largeobjects_dirs_if_needed(defaults['paths_root_path'])
124 124
125 125 data = render('rhodecode:templates/admin/settings/settings.mako',
126 126 self._get_template_context(c), self.request)
127 127 html = formencode.htmlfill.render(
128 128 data,
129 129 defaults=defaults,
130 130 encoding="UTF-8",
131 131 force_defaults=False
132 132 )
133 133 return Response(html)
134 134
135 135 @LoginRequired()
136 136 @HasPermissionAllDecorator('hg.admin')
137 137 @CSRFRequired()
138 138 def settings_vcs_update(self):
139 139 _ = self.request.translate
140 140 c = self.load_default_context()
141 141 c.active = 'vcs'
142 142
143 143 model = VcsSettingsModel()
144 144 c.svn_branch_patterns = model.get_global_svn_branch_patterns()
145 145 c.svn_tag_patterns = model.get_global_svn_tag_patterns()
146 146
147 147 settings = self.request.registry.settings
148 148 c.svn_proxy_generate_config = settings[generate_config]
149 149
150 150 application_form = ApplicationUiSettingsForm(self.request.translate)()
151 151
152 152 try:
153 153 form_result = application_form.to_python(dict(self.request.POST))
154 154 except formencode.Invalid as errors:
155 155 h.flash(
156 156 _("Some form inputs contain invalid data."),
157 157 category='error')
158 158 data = render('rhodecode:templates/admin/settings/settings.mako',
159 159 self._get_template_context(c), self.request)
160 160 html = formencode.htmlfill.render(
161 161 data,
162 162 defaults=errors.value,
163 errors=errors.error_dict or {},
163 errors=errors.unpack_errors() or {},
164 164 prefix_error=False,
165 165 encoding="UTF-8",
166 166 force_defaults=False
167 167 )
168 168 return Response(html)
169 169
170 170 try:
171 171 if c.visual.allow_repo_location_change:
172 172 model.update_global_path_setting(form_result['paths_root_path'])
173 173
174 174 model.update_global_ssl_setting(form_result['web_push_ssl'])
175 175 model.update_global_hook_settings(form_result)
176 176
177 177 model.create_or_update_global_svn_settings(form_result)
178 178 model.create_or_update_global_hg_settings(form_result)
179 179 model.create_or_update_global_git_settings(form_result)
180 180 model.create_or_update_global_pr_settings(form_result)
181 181 except Exception:
182 182 log.exception("Exception while updating settings")
183 183 h.flash(_('Error occurred during updating '
184 184 'application settings'), category='error')
185 185 else:
186 186 Session().commit()
187 187 h.flash(_('Updated VCS settings'), category='success')
188 188 raise HTTPFound(h.route_path('admin_settings_vcs'))
189 189
190 190 data = render('rhodecode:templates/admin/settings/settings.mako',
191 191 self._get_template_context(c), self.request)
192 192 html = formencode.htmlfill.render(
193 193 data,
194 194 defaults=self._form_defaults(),
195 195 encoding="UTF-8",
196 196 force_defaults=False
197 197 )
198 198 return Response(html)
199 199
200 200 @LoginRequired()
201 201 @HasPermissionAllDecorator('hg.admin')
202 202 @CSRFRequired()
203 203 def settings_vcs_delete_svn_pattern(self):
204 204 delete_pattern_id = self.request.POST.get('delete_svn_pattern')
205 205 model = VcsSettingsModel()
206 206 try:
207 207 model.delete_global_svn_pattern(delete_pattern_id)
208 208 except SettingNotFound:
209 209 log.exception(
210 210 'Failed to delete svn_pattern with id %s', delete_pattern_id)
211 211 raise HTTPNotFound()
212 212
213 213 Session().commit()
214 214 return True
215 215
216 216 @LoginRequired()
217 217 @HasPermissionAllDecorator('hg.admin')
218 218 def settings_mapping(self):
219 219 c = self.load_default_context()
220 220 c.active = 'mapping'
221 221
222 222 data = render('rhodecode:templates/admin/settings/settings.mako',
223 223 self._get_template_context(c), self.request)
224 224 html = formencode.htmlfill.render(
225 225 data,
226 226 defaults=self._form_defaults(),
227 227 encoding="UTF-8",
228 228 force_defaults=False
229 229 )
230 230 return Response(html)
231 231
232 232 @LoginRequired()
233 233 @HasPermissionAllDecorator('hg.admin')
234 234 @CSRFRequired()
235 235 def settings_mapping_update(self):
236 236 _ = self.request.translate
237 237 c = self.load_default_context()
238 238 c.active = 'mapping'
239 239 rm_obsolete = self.request.POST.get('destroy', False)
240 240 invalidate_cache = self.request.POST.get('invalidate', False)
241 241 log.debug('rescanning repo location with destroy obsolete=%s', rm_obsolete)
242 242
243 243 if invalidate_cache:
244 244 log.debug('invalidating all repositories cache')
245 245 for repo in Repository.get_all():
246 246 ScmModel().mark_for_invalidation(repo.repo_name, delete=True)
247 247
248 248 filesystem_repos = ScmModel().repo_scan()
249 249 added, removed = repo2db_mapper(filesystem_repos, rm_obsolete)
250 250 PermissionModel().trigger_permission_flush()
251 251
252 252 _repr = lambda l: ', '.join(map(safe_unicode, l)) or '-'
253 253 h.flash(_('Repositories successfully '
254 254 'rescanned added: %s ; removed: %s') %
255 255 (_repr(added), _repr(removed)),
256 256 category='success')
257 257 raise HTTPFound(h.route_path('admin_settings_mapping'))
258 258
259 259 @LoginRequired()
260 260 @HasPermissionAllDecorator('hg.admin')
261 261 def settings_global(self):
262 262 c = self.load_default_context()
263 263 c.active = 'global'
264 264 c.personal_repo_group_default_pattern = RepoGroupModel()\
265 265 .get_personal_group_name_pattern()
266 266
267 267 data = render('rhodecode:templates/admin/settings/settings.mako',
268 268 self._get_template_context(c), self.request)
269 269 html = formencode.htmlfill.render(
270 270 data,
271 271 defaults=self._form_defaults(),
272 272 encoding="UTF-8",
273 273 force_defaults=False
274 274 )
275 275 return Response(html)
276 276
277 277 @LoginRequired()
278 278 @HasPermissionAllDecorator('hg.admin')
279 279 @CSRFRequired()
280 280 def settings_global_update(self):
281 281 _ = self.request.translate
282 282 c = self.load_default_context()
283 283 c.active = 'global'
284 284 c.personal_repo_group_default_pattern = RepoGroupModel()\
285 285 .get_personal_group_name_pattern()
286 286 application_form = ApplicationSettingsForm(self.request.translate)()
287 287 try:
288 288 form_result = application_form.to_python(dict(self.request.POST))
289 289 except formencode.Invalid as errors:
290 290 h.flash(
291 291 _("Some form inputs contain invalid data."),
292 292 category='error')
293 293 data = render('rhodecode:templates/admin/settings/settings.mako',
294 294 self._get_template_context(c), self.request)
295 295 html = formencode.htmlfill.render(
296 296 data,
297 297 defaults=errors.value,
298 errors=errors.error_dict or {},
298 errors=errors.unpack_errors() or {},
299 299 prefix_error=False,
300 300 encoding="UTF-8",
301 301 force_defaults=False
302 302 )
303 303 return Response(html)
304 304
305 305 settings = [
306 306 ('title', 'rhodecode_title', 'unicode'),
307 307 ('realm', 'rhodecode_realm', 'unicode'),
308 308 ('pre_code', 'rhodecode_pre_code', 'unicode'),
309 309 ('post_code', 'rhodecode_post_code', 'unicode'),
310 310 ('captcha_public_key', 'rhodecode_captcha_public_key', 'unicode'),
311 311 ('captcha_private_key', 'rhodecode_captcha_private_key', 'unicode'),
312 312 ('create_personal_repo_group', 'rhodecode_create_personal_repo_group', 'bool'),
313 313 ('personal_repo_group_pattern', 'rhodecode_personal_repo_group_pattern', 'unicode'),
314 314 ]
315 315 try:
316 316 for setting, form_key, type_ in settings:
317 317 sett = SettingsModel().create_or_update_setting(
318 318 setting, form_result[form_key], type_)
319 319 Session().add(sett)
320 320
321 321 Session().commit()
322 322 SettingsModel().invalidate_settings_cache()
323 323 h.flash(_('Updated application settings'), category='success')
324 324 except Exception:
325 325 log.exception("Exception while updating application settings")
326 326 h.flash(
327 327 _('Error occurred during updating application settings'),
328 328 category='error')
329 329
330 330 raise HTTPFound(h.route_path('admin_settings_global'))
331 331
332 332 @LoginRequired()
333 333 @HasPermissionAllDecorator('hg.admin')
334 334 def settings_visual(self):
335 335 c = self.load_default_context()
336 336 c.active = 'visual'
337 337
338 338 data = render('rhodecode:templates/admin/settings/settings.mako',
339 339 self._get_template_context(c), self.request)
340 340 html = formencode.htmlfill.render(
341 341 data,
342 342 defaults=self._form_defaults(),
343 343 encoding="UTF-8",
344 344 force_defaults=False
345 345 )
346 346 return Response(html)
347 347
348 348 @LoginRequired()
349 349 @HasPermissionAllDecorator('hg.admin')
350 350 @CSRFRequired()
351 351 def settings_visual_update(self):
352 352 _ = self.request.translate
353 353 c = self.load_default_context()
354 354 c.active = 'visual'
355 355 application_form = ApplicationVisualisationForm(self.request.translate)()
356 356 try:
357 357 form_result = application_form.to_python(dict(self.request.POST))
358 358 except formencode.Invalid as errors:
359 359 h.flash(
360 360 _("Some form inputs contain invalid data."),
361 361 category='error')
362 362 data = render('rhodecode:templates/admin/settings/settings.mako',
363 363 self._get_template_context(c), self.request)
364 364 html = formencode.htmlfill.render(
365 365 data,
366 366 defaults=errors.value,
367 errors=errors.error_dict or {},
367 errors=errors.unpack_errors() or {},
368 368 prefix_error=False,
369 369 encoding="UTF-8",
370 370 force_defaults=False
371 371 )
372 372 return Response(html)
373 373
374 374 try:
375 375 settings = [
376 376 ('show_public_icon', 'rhodecode_show_public_icon', 'bool'),
377 377 ('show_private_icon', 'rhodecode_show_private_icon', 'bool'),
378 378 ('stylify_metatags', 'rhodecode_stylify_metatags', 'bool'),
379 379 ('repository_fields', 'rhodecode_repository_fields', 'bool'),
380 380 ('dashboard_items', 'rhodecode_dashboard_items', 'int'),
381 381 ('admin_grid_items', 'rhodecode_admin_grid_items', 'int'),
382 382 ('show_version', 'rhodecode_show_version', 'bool'),
383 383 ('use_gravatar', 'rhodecode_use_gravatar', 'bool'),
384 384 ('markup_renderer', 'rhodecode_markup_renderer', 'unicode'),
385 385 ('gravatar_url', 'rhodecode_gravatar_url', 'unicode'),
386 386 ('clone_uri_tmpl', 'rhodecode_clone_uri_tmpl', 'unicode'),
387 387 ('clone_uri_id_tmpl', 'rhodecode_clone_uri_id_tmpl', 'unicode'),
388 388 ('clone_uri_ssh_tmpl', 'rhodecode_clone_uri_ssh_tmpl', 'unicode'),
389 389 ('support_url', 'rhodecode_support_url', 'unicode'),
390 390 ('show_revision_number', 'rhodecode_show_revision_number', 'bool'),
391 391 ('show_sha_length', 'rhodecode_show_sha_length', 'int'),
392 392 ]
393 393 for setting, form_key, type_ in settings:
394 394 sett = SettingsModel().create_or_update_setting(
395 395 setting, form_result[form_key], type_)
396 396 Session().add(sett)
397 397
398 398 Session().commit()
399 399 SettingsModel().invalidate_settings_cache()
400 400 h.flash(_('Updated visualisation settings'), category='success')
401 401 except Exception:
402 402 log.exception("Exception updating visualization settings")
403 403 h.flash(_('Error occurred during updating '
404 404 'visualisation settings'),
405 405 category='error')
406 406
407 407 raise HTTPFound(h.route_path('admin_settings_visual'))
408 408
409 409 @LoginRequired()
410 410 @HasPermissionAllDecorator('hg.admin')
411 411 def settings_issuetracker(self):
412 412 c = self.load_default_context()
413 413 c.active = 'issuetracker'
414 414 defaults = c.rc_config
415 415
416 416 entry_key = 'rhodecode_issuetracker_pat_'
417 417
418 418 c.issuetracker_entries = {}
419 419 for k, v in defaults.items():
420 420 if k.startswith(entry_key):
421 421 uid = k[len(entry_key):]
422 422 c.issuetracker_entries[uid] = None
423 423
424 424 for uid in c.issuetracker_entries:
425 425 c.issuetracker_entries[uid] = AttributeDict({
426 426 'pat': defaults.get('rhodecode_issuetracker_pat_' + uid),
427 427 'url': defaults.get('rhodecode_issuetracker_url_' + uid),
428 428 'pref': defaults.get('rhodecode_issuetracker_pref_' + uid),
429 429 'desc': defaults.get('rhodecode_issuetracker_desc_' + uid),
430 430 })
431 431
432 432 return self._get_template_context(c)
433 433
434 434 @LoginRequired()
435 435 @HasPermissionAllDecorator('hg.admin')
436 436 @CSRFRequired()
437 437 def settings_issuetracker_test(self):
438 438 error_container = []
439 439
440 440 urlified_commit = h.urlify_commit_message(
441 441 self.request.POST.get('test_text', ''),
442 442 'repo_group/test_repo1', error_container=error_container)
443 443 if error_container:
444 444 def converter(inp):
445 445 return h.html_escape(inp)
446 446
447 447 return 'ERRORS: ' + '\n'.join(map(converter, error_container))
448 448
449 449 return urlified_commit
450 450
451 451 @LoginRequired()
452 452 @HasPermissionAllDecorator('hg.admin')
453 453 @CSRFRequired()
454 454 def settings_issuetracker_update(self):
455 455 _ = self.request.translate
456 456 self.load_default_context()
457 457 settings_model = IssueTrackerSettingsModel()
458 458
459 459 try:
460 460 form = IssueTrackerPatternsForm(self.request.translate)()
461 461 data = form.to_python(self.request.POST)
462 462 except formencode.Invalid as errors:
463 463 log.exception('Failed to add new pattern')
464 464 error = errors
465 465 h.flash(_('Invalid issue tracker pattern: {}'.format(error)),
466 466 category='error')
467 467 raise HTTPFound(h.route_path('admin_settings_issuetracker'))
468 468
469 469 if data:
470 470 for uid in data.get('delete_patterns', []):
471 471 settings_model.delete_entries(uid)
472 472
473 473 for pattern in data.get('patterns', []):
474 474 for setting, value, type_ in pattern:
475 475 sett = settings_model.create_or_update_setting(
476 476 setting, value, type_)
477 477 Session().add(sett)
478 478
479 479 Session().commit()
480 480
481 481 SettingsModel().invalidate_settings_cache()
482 482 h.flash(_('Updated issue tracker entries'), category='success')
483 483 raise HTTPFound(h.route_path('admin_settings_issuetracker'))
484 484
485 485 @LoginRequired()
486 486 @HasPermissionAllDecorator('hg.admin')
487 487 @CSRFRequired()
488 488 def settings_issuetracker_delete(self):
489 489 _ = self.request.translate
490 490 self.load_default_context()
491 491 uid = self.request.POST.get('uid')
492 492 try:
493 493 IssueTrackerSettingsModel().delete_entries(uid)
494 494 except Exception:
495 495 log.exception('Failed to delete issue tracker setting %s', uid)
496 496 raise HTTPNotFound()
497 497
498 498 SettingsModel().invalidate_settings_cache()
499 499 h.flash(_('Removed issue tracker entry.'), category='success')
500 500
501 501 return {'deleted': uid}
502 502
503 503 @LoginRequired()
504 504 @HasPermissionAllDecorator('hg.admin')
505 505 def settings_email(self):
506 506 c = self.load_default_context()
507 507 c.active = 'email'
508 508 c.rhodecode_ini = rhodecode.CONFIG
509 509
510 510 data = render('rhodecode:templates/admin/settings/settings.mako',
511 511 self._get_template_context(c), self.request)
512 512 html = formencode.htmlfill.render(
513 513 data,
514 514 defaults=self._form_defaults(),
515 515 encoding="UTF-8",
516 516 force_defaults=False
517 517 )
518 518 return Response(html)
519 519
520 520 @LoginRequired()
521 521 @HasPermissionAllDecorator('hg.admin')
522 522 @CSRFRequired()
523 523 def settings_email_update(self):
524 524 _ = self.request.translate
525 525 c = self.load_default_context()
526 526 c.active = 'email'
527 527
528 528 test_email = self.request.POST.get('test_email')
529 529
530 530 if not test_email:
531 531 h.flash(_('Please enter email address'), category='error')
532 532 raise HTTPFound(h.route_path('admin_settings_email'))
533 533
534 534 email_kwargs = {
535 535 'date': datetime.datetime.now(),
536 536 'user': self._rhodecode_db_user
537 537 }
538 538
539 539 (subject, email_body, email_body_plaintext) = EmailNotificationModel().render_email(
540 540 EmailNotificationModel.TYPE_EMAIL_TEST, **email_kwargs)
541 541
542 542 recipients = [test_email] if test_email else None
543 543
544 544 run_task(tasks.send_email, recipients, subject,
545 545 email_body_plaintext, email_body)
546 546
547 547 h.flash(_('Send email task created'), category='success')
548 548 raise HTTPFound(h.route_path('admin_settings_email'))
549 549
550 550 @LoginRequired()
551 551 @HasPermissionAllDecorator('hg.admin')
552 552 def settings_hooks(self):
553 553 c = self.load_default_context()
554 554 c.active = 'hooks'
555 555
556 556 model = SettingsModel()
557 557 c.hooks = model.get_builtin_hooks()
558 558 c.custom_hooks = model.get_custom_hooks()
559 559
560 560 data = render('rhodecode:templates/admin/settings/settings.mako',
561 561 self._get_template_context(c), self.request)
562 562 html = formencode.htmlfill.render(
563 563 data,
564 564 defaults=self._form_defaults(),
565 565 encoding="UTF-8",
566 566 force_defaults=False
567 567 )
568 568 return Response(html)
569 569
570 570 @LoginRequired()
571 571 @HasPermissionAllDecorator('hg.admin')
572 572 @CSRFRequired()
573 573 def settings_hooks_update(self):
574 574 _ = self.request.translate
575 575 c = self.load_default_context()
576 576 c.active = 'hooks'
577 577 if c.visual.allow_custom_hooks_settings:
578 578 ui_key = self.request.POST.get('new_hook_ui_key')
579 579 ui_value = self.request.POST.get('new_hook_ui_value')
580 580
581 581 hook_id = self.request.POST.get('hook_id')
582 582 new_hook = False
583 583
584 584 model = SettingsModel()
585 585 try:
586 586 if ui_value and ui_key:
587 587 model.create_or_update_hook(ui_key, ui_value)
588 588 h.flash(_('Added new hook'), category='success')
589 589 new_hook = True
590 590 elif hook_id:
591 591 RhodeCodeUi.delete(hook_id)
592 592 Session().commit()
593 593
594 594 # check for edits
595 595 update = False
596 596 _d = self.request.POST.dict_of_lists()
597 597 for k, v in zip(_d.get('hook_ui_key', []),
598 598 _d.get('hook_ui_value_new', [])):
599 599 model.create_or_update_hook(k, v)
600 600 update = True
601 601
602 602 if update and not new_hook:
603 603 h.flash(_('Updated hooks'), category='success')
604 604 Session().commit()
605 605 except Exception:
606 606 log.exception("Exception during hook creation")
607 607 h.flash(_('Error occurred during hook creation'),
608 608 category='error')
609 609
610 610 raise HTTPFound(h.route_path('admin_settings_hooks'))
611 611
612 612 @LoginRequired()
613 613 @HasPermissionAllDecorator('hg.admin')
614 614 def settings_search(self):
615 615 c = self.load_default_context()
616 616 c.active = 'search'
617 617
618 618 c.searcher = searcher_from_config(self.request.registry.settings)
619 619 c.statistics = c.searcher.statistics(self.request.translate)
620 620
621 621 return self._get_template_context(c)
622 622
623 623 @LoginRequired()
624 624 @HasPermissionAllDecorator('hg.admin')
625 625 def settings_automation(self):
626 626 c = self.load_default_context()
627 627 c.active = 'automation'
628 628
629 629 return self._get_template_context(c)
630 630
631 631 @LoginRequired()
632 632 @HasPermissionAllDecorator('hg.admin')
633 633 def settings_labs(self):
634 634 c = self.load_default_context()
635 635 if not c.labs_active:
636 636 raise HTTPFound(h.route_path('admin_settings'))
637 637
638 638 c.active = 'labs'
639 639 c.lab_settings = _LAB_SETTINGS
640 640
641 641 data = render('rhodecode:templates/admin/settings/settings.mako',
642 642 self._get_template_context(c), self.request)
643 643 html = formencode.htmlfill.render(
644 644 data,
645 645 defaults=self._form_defaults(),
646 646 encoding="UTF-8",
647 647 force_defaults=False
648 648 )
649 649 return Response(html)
650 650
651 651 @LoginRequired()
652 652 @HasPermissionAllDecorator('hg.admin')
653 653 @CSRFRequired()
654 654 def settings_labs_update(self):
655 655 _ = self.request.translate
656 656 c = self.load_default_context()
657 657 c.active = 'labs'
658 658
659 659 application_form = LabsSettingsForm(self.request.translate)()
660 660 try:
661 661 form_result = application_form.to_python(dict(self.request.POST))
662 662 except formencode.Invalid as errors:
663 663 h.flash(
664 664 _("Some form inputs contain invalid data."),
665 665 category='error')
666 666 data = render('rhodecode:templates/admin/settings/settings.mako',
667 667 self._get_template_context(c), self.request)
668 668 html = formencode.htmlfill.render(
669 669 data,
670 670 defaults=errors.value,
671 errors=errors.error_dict or {},
671 errors=errors.unpack_errors() or {},
672 672 prefix_error=False,
673 673 encoding="UTF-8",
674 674 force_defaults=False
675 675 )
676 676 return Response(html)
677 677
678 678 try:
679 679 session = Session()
680 680 for setting in _LAB_SETTINGS:
681 681 setting_name = setting.key[len('rhodecode_'):]
682 682 sett = SettingsModel().create_or_update_setting(
683 683 setting_name, form_result[setting.key], setting.type)
684 684 session.add(sett)
685 685
686 686 except Exception:
687 687 log.exception('Exception while updating lab settings')
688 688 h.flash(_('Error occurred during updating labs settings'),
689 689 category='error')
690 690 else:
691 691 Session().commit()
692 692 SettingsModel().invalidate_settings_cache()
693 693 h.flash(_('Updated Labs settings'), category='success')
694 694 raise HTTPFound(h.route_path('admin_settings_labs'))
695 695
696 696 data = render('rhodecode:templates/admin/settings/settings.mako',
697 697 self._get_template_context(c), self.request)
698 698 html = formencode.htmlfill.render(
699 699 data,
700 700 defaults=self._form_defaults(),
701 701 encoding="UTF-8",
702 702 force_defaults=False
703 703 )
704 704 return Response(html)
705 705
706 706
707 707 # :param key: name of the setting including the 'rhodecode_' prefix
708 708 # :param type: the RhodeCodeSetting type to use.
709 709 # :param group: the i18ned group in which we should dispaly this setting
710 710 # :param label: the i18ned label we should display for this setting
711 711 # :param help: the i18ned help we should dispaly for this setting
712 712 LabSetting = collections.namedtuple(
713 713 'LabSetting', ('key', 'type', 'group', 'label', 'help'))
714 714
715 715
716 716 # This list has to be kept in sync with the form
717 717 # rhodecode.model.forms.LabsSettingsForm.
718 718 _LAB_SETTINGS = [
719 719
720 720 ]
@@ -1,253 +1,253 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2016-2020 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 import logging
22 22
23 23 import formencode
24 24 import formencode.htmlfill
25 25
26 26 from pyramid.httpexceptions import HTTPFound
27 27
28 28 from pyramid.response import Response
29 29 from pyramid.renderers import render
30 30
31 31 from rhodecode import events
32 32 from rhodecode.apps._base import BaseAppView, DataGridAppView
33 33 from rhodecode.lib.auth import (
34 34 LoginRequired, NotAnonymous, CSRFRequired, HasPermissionAnyDecorator)
35 35 from rhodecode.lib import helpers as h, audit_logger
36 36 from rhodecode.lib.utils2 import safe_unicode
37 37
38 38 from rhodecode.model.forms import UserGroupForm
39 39 from rhodecode.model.permission import PermissionModel
40 40 from rhodecode.model.scm import UserGroupList
41 41 from rhodecode.model.db import (
42 42 or_, count, User, UserGroup, UserGroupMember, in_filter_generator)
43 43 from rhodecode.model.meta import Session
44 44 from rhodecode.model.user_group import UserGroupModel
45 45 from rhodecode.model.db import true
46 46
47 47 log = logging.getLogger(__name__)
48 48
49 49
50 50 class AdminUserGroupsView(BaseAppView, DataGridAppView):
51 51
52 52 def load_default_context(self):
53 53 c = self._get_local_tmpl_context()
54 54 PermissionModel().set_global_permission_choices(
55 55 c, gettext_translator=self.request.translate)
56 56 return c
57 57
58 58 # permission check in data loading of
59 59 # `user_groups_list_data` via UserGroupList
60 60 @LoginRequired()
61 61 @NotAnonymous()
62 62 def user_groups_list(self):
63 63 c = self.load_default_context()
64 64 return self._get_template_context(c)
65 65
66 66 # permission check inside
67 67 @LoginRequired()
68 68 @NotAnonymous()
69 69 def user_groups_list_data(self):
70 70 self.load_default_context()
71 71 column_map = {
72 72 'active': 'users_group_active',
73 73 'description': 'user_group_description',
74 74 'members': 'members_total',
75 75 'owner': 'user_username',
76 76 'sync': 'group_data'
77 77 }
78 78 draw, start, limit = self._extract_chunk(self.request)
79 79 search_q, order_by, order_dir = self._extract_ordering(
80 80 self.request, column_map=column_map)
81 81
82 82 _render = self.request.get_partial_renderer(
83 83 'rhodecode:templates/data_table/_dt_elements.mako')
84 84
85 85 def user_group_name(user_group_name):
86 86 return _render("user_group_name", user_group_name)
87 87
88 88 def user_group_actions(user_group_id, user_group_name):
89 89 return _render("user_group_actions", user_group_id, user_group_name)
90 90
91 91 def user_profile(username):
92 92 return _render('user_profile', username)
93 93
94 94 _perms = ['usergroup.admin']
95 95 allowed_ids = [-1] + self._rhodecode_user.user_group_acl_ids_from_stack(_perms)
96 96
97 97 user_groups_data_total_count = UserGroup.query()\
98 98 .filter(or_(
99 99 # generate multiple IN to fix limitation problems
100 100 *in_filter_generator(UserGroup.users_group_id, allowed_ids)
101 101 ))\
102 102 .count()
103 103
104 104 user_groups_data_total_inactive_count = UserGroup.query()\
105 105 .filter(or_(
106 106 # generate multiple IN to fix limitation problems
107 107 *in_filter_generator(UserGroup.users_group_id, allowed_ids)
108 108 ))\
109 109 .filter(UserGroup.users_group_active != true()).count()
110 110
111 111 member_count = count(UserGroupMember.user_id)
112 112 base_q = Session.query(
113 113 UserGroup.users_group_name,
114 114 UserGroup.user_group_description,
115 115 UserGroup.users_group_active,
116 116 UserGroup.users_group_id,
117 117 UserGroup.group_data,
118 118 User,
119 119 member_count.label('member_count')
120 120 ) \
121 121 .filter(or_(
122 122 # generate multiple IN to fix limitation problems
123 123 *in_filter_generator(UserGroup.users_group_id, allowed_ids)
124 124 )) \
125 125 .outerjoin(UserGroupMember, UserGroupMember.users_group_id == UserGroup.users_group_id) \
126 126 .join(User, User.user_id == UserGroup.user_id) \
127 127 .group_by(UserGroup, User)
128 128
129 129 base_q_inactive = base_q.filter(UserGroup.users_group_active != true())
130 130
131 131 if search_q:
132 132 like_expression = u'%{}%'.format(safe_unicode(search_q))
133 133 base_q = base_q.filter(or_(
134 134 UserGroup.users_group_name.ilike(like_expression),
135 135 ))
136 136 base_q_inactive = base_q.filter(UserGroup.users_group_active != true())
137 137
138 138 user_groups_data_total_filtered_count = base_q.count()
139 139 user_groups_data_total_filtered_inactive_count = base_q_inactive.count()
140 140
141 141 sort_defined = False
142 142 if order_by == 'members_total':
143 143 sort_col = member_count
144 144 sort_defined = True
145 145 elif order_by == 'user_username':
146 146 sort_col = User.username
147 147 else:
148 148 sort_col = getattr(UserGroup, order_by, None)
149 149
150 150 if sort_defined or sort_col:
151 151 if order_dir == 'asc':
152 152 sort_col = sort_col.asc()
153 153 else:
154 154 sort_col = sort_col.desc()
155 155
156 156 base_q = base_q.order_by(sort_col)
157 157 base_q = base_q.offset(start).limit(limit)
158 158
159 159 # authenticated access to user groups
160 160 auth_user_group_list = base_q.all()
161 161
162 162 user_groups_data = []
163 163 for user_gr in auth_user_group_list:
164 164 row = {
165 165 "users_group_name": user_group_name(user_gr.users_group_name),
166 166 "description": h.escape(user_gr.user_group_description),
167 167 "members": user_gr.member_count,
168 168 # NOTE(marcink): because of advanced query we
169 169 # need to load it like that
170 170 "sync": UserGroup._load_sync(
171 171 UserGroup._load_group_data(user_gr.group_data)),
172 172 "active": h.bool2icon(user_gr.users_group_active),
173 173 "owner": user_profile(user_gr.User.username),
174 174 "action": user_group_actions(
175 175 user_gr.users_group_id, user_gr.users_group_name)
176 176 }
177 177 user_groups_data.append(row)
178 178
179 179 data = ({
180 180 'draw': draw,
181 181 'data': user_groups_data,
182 182 'recordsTotal': user_groups_data_total_count,
183 183 'recordsTotalInactive': user_groups_data_total_inactive_count,
184 184 'recordsFiltered': user_groups_data_total_filtered_count,
185 185 'recordsFilteredInactive': user_groups_data_total_filtered_inactive_count,
186 186 })
187 187
188 188 return data
189 189
190 190 @LoginRequired()
191 191 @HasPermissionAnyDecorator('hg.admin', 'hg.usergroup.create.true')
192 192 def user_groups_new(self):
193 193 c = self.load_default_context()
194 194 return self._get_template_context(c)
195 195
196 196 @LoginRequired()
197 197 @HasPermissionAnyDecorator('hg.admin', 'hg.usergroup.create.true')
198 198 @CSRFRequired()
199 199 def user_groups_create(self):
200 200 _ = self.request.translate
201 201 c = self.load_default_context()
202 202 users_group_form = UserGroupForm(self.request.translate)()
203 203
204 204 user_group_name = self.request.POST.get('users_group_name')
205 205 try:
206 206 form_result = users_group_form.to_python(dict(self.request.POST))
207 207 user_group = UserGroupModel().create(
208 208 name=form_result['users_group_name'],
209 209 description=form_result['user_group_description'],
210 210 owner=self._rhodecode_user.user_id,
211 211 active=form_result['users_group_active'])
212 212 Session().flush()
213 213 creation_data = user_group.get_api_data()
214 214 user_group_name = form_result['users_group_name']
215 215
216 216 audit_logger.store_web(
217 217 'user_group.create', action_data={'data': creation_data},
218 218 user=self._rhodecode_user)
219 219
220 220 user_group_link = h.link_to(
221 221 h.escape(user_group_name),
222 222 h.route_path(
223 223 'edit_user_group', user_group_id=user_group.users_group_id))
224 224 h.flash(h.literal(_('Created user group %(user_group_link)s')
225 225 % {'user_group_link': user_group_link}),
226 226 category='success')
227 227 Session().commit()
228 228 user_group_id = user_group.users_group_id
229 229 except formencode.Invalid as errors:
230 230
231 231 data = render(
232 232 'rhodecode:templates/admin/user_groups/user_group_add.mako',
233 233 self._get_template_context(c), self.request)
234 234 html = formencode.htmlfill.render(
235 235 data,
236 236 defaults=errors.value,
237 errors=errors.error_dict or {},
237 errors=errors.unpack_errors() or {},
238 238 prefix_error=False,
239 239 encoding="UTF-8",
240 240 force_defaults=False
241 241 )
242 242 return Response(html)
243 243
244 244 except Exception:
245 245 log.exception("Exception creating user group")
246 246 h.flash(_('Error occurred during creation of user group %s') \
247 247 % user_group_name, category='error')
248 248 raise HTTPFound(h.route_path('user_groups_new'))
249 249
250 250 PermissionModel().trigger_permission_flush()
251 251
252 252 raise HTTPFound(
253 253 h.route_path('edit_user_group', user_group_id=user_group_id))
@@ -1,1321 +1,1322 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2016-2020 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 import logging
22 22 import datetime
23 23 import formencode
24 24 import formencode.htmlfill
25 25
26 26 from pyramid.httpexceptions import HTTPFound
27 27 from pyramid.renderers import render
28 28 from pyramid.response import Response
29 29
30 30 from rhodecode import events
31 31 from rhodecode.apps._base import BaseAppView, DataGridAppView, UserAppView
32 32 from rhodecode.apps.ssh_support import SshKeyFileChangeEvent
33 33 from rhodecode.authentication.base import get_authn_registry, RhodeCodeExternalAuthPlugin
34 34 from rhodecode.authentication.plugins import auth_rhodecode
35 35 from rhodecode.events import trigger
36 36 from rhodecode.model.db import true, UserNotice
37 37
38 38 from rhodecode.lib import audit_logger, rc_cache, auth
39 39 from rhodecode.lib.exceptions import (
40 40 UserCreationError, UserOwnsReposException, UserOwnsRepoGroupsException,
41 41 UserOwnsUserGroupsException, UserOwnsPullRequestsException,
42 42 UserOwnsArtifactsException, DefaultUserException)
43 43 from rhodecode.lib import ext_json
44 44 from rhodecode.lib.auth import (
45 45 LoginRequired, HasPermissionAllDecorator, CSRFRequired)
46 46 from rhodecode.lib import helpers as h
47 47 from rhodecode.lib.helpers import SqlPage
48 48 from rhodecode.lib.utils2 import safe_int, safe_unicode, AttributeDict
49 49 from rhodecode.model.auth_token import AuthTokenModel
50 50 from rhodecode.model.forms import (
51 51 UserForm, UserIndividualPermissionsForm, UserPermissionsForm,
52 52 UserExtraEmailForm, UserExtraIpForm)
53 53 from rhodecode.model.permission import PermissionModel
54 54 from rhodecode.model.repo_group import RepoGroupModel
55 55 from rhodecode.model.ssh_key import SshKeyModel
56 56 from rhodecode.model.user import UserModel
57 57 from rhodecode.model.user_group import UserGroupModel
58 58 from rhodecode.model.db import (
59 59 or_, coalesce,IntegrityError, User, UserGroup, UserIpMap, UserEmailMap,
60 60 UserApiKeys, UserSshKeys, RepoGroup)
61 61 from rhodecode.model.meta import Session
62 62
63 63 log = logging.getLogger(__name__)
64 64
65 65
66 66 class AdminUsersView(BaseAppView, DataGridAppView):
67 67
68 68 def load_default_context(self):
69 69 c = self._get_local_tmpl_context()
70 70 return c
71 71
72 72 @LoginRequired()
73 73 @HasPermissionAllDecorator('hg.admin')
74 74 def users_list(self):
75 75 c = self.load_default_context()
76 76 return self._get_template_context(c)
77 77
78 78 @LoginRequired()
79 79 @HasPermissionAllDecorator('hg.admin')
80 80 def users_list_data(self):
81 81 self.load_default_context()
82 82 column_map = {
83 83 'first_name': 'name',
84 84 'last_name': 'lastname',
85 85 }
86 86 draw, start, limit = self._extract_chunk(self.request)
87 87 search_q, order_by, order_dir = self._extract_ordering(
88 88 self.request, column_map=column_map)
89 89 _render = self.request.get_partial_renderer(
90 90 'rhodecode:templates/data_table/_dt_elements.mako')
91 91
92 92 def user_actions(user_id, username):
93 93 return _render("user_actions", user_id, username)
94 94
95 95 users_data_total_count = User.query()\
96 96 .filter(User.username != User.DEFAULT_USER) \
97 97 .count()
98 98
99 99 users_data_total_inactive_count = User.query()\
100 100 .filter(User.username != User.DEFAULT_USER) \
101 101 .filter(User.active != true())\
102 102 .count()
103 103
104 104 # json generate
105 105 base_q = User.query().filter(User.username != User.DEFAULT_USER)
106 106 base_inactive_q = base_q.filter(User.active != true())
107 107
108 108 if search_q:
109 like_expression = u'%{}%'.format(safe_unicode(search_q))
109 like_expression = '%{}%'.format(safe_unicode(search_q))
110 110 base_q = base_q.filter(or_(
111 111 User.username.ilike(like_expression),
112 112 User._email.ilike(like_expression),
113 113 User.name.ilike(like_expression),
114 114 User.lastname.ilike(like_expression),
115 115 ))
116 116 base_inactive_q = base_q.filter(User.active != true())
117 117
118 118 users_data_total_filtered_count = base_q.count()
119 119 users_data_total_filtered_inactive_count = base_inactive_q.count()
120 120
121 121 sort_col = getattr(User, order_by, None)
122 122 if sort_col:
123 123 if order_dir == 'asc':
124 124 # handle null values properly to order by NULL last
125 125 if order_by in ['last_activity']:
126 126 sort_col = coalesce(sort_col, datetime.date.max)
127 127 sort_col = sort_col.asc()
128 128 else:
129 129 # handle null values properly to order by NULL last
130 130 if order_by in ['last_activity']:
131 131 sort_col = coalesce(sort_col, datetime.date.min)
132 132 sort_col = sort_col.desc()
133 133
134 134 base_q = base_q.order_by(sort_col)
135 135 base_q = base_q.offset(start).limit(limit)
136 136
137 137 users_list = base_q.all()
138 138
139 139 users_data = []
140 140 for user in users_list:
141 141 users_data.append({
142 142 "username": h.gravatar_with_user(self.request, user.username),
143 143 "email": user.email,
144 144 "first_name": user.first_name,
145 145 "last_name": user.last_name,
146 146 "last_login": h.format_date(user.last_login),
147 147 "last_activity": h.format_date(user.last_activity),
148 148 "active": h.bool2icon(user.active),
149 149 "active_raw": user.active,
150 150 "admin": h.bool2icon(user.admin),
151 151 "extern_type": user.extern_type,
152 152 "extern_name": user.extern_name,
153 153 "action": user_actions(user.user_id, user.username),
154 154 })
155 155 data = ({
156 156 'draw': draw,
157 157 'data': users_data,
158 158 'recordsTotal': users_data_total_count,
159 159 'recordsFiltered': users_data_total_filtered_count,
160 160 'recordsTotalInactive': users_data_total_inactive_count,
161 161 'recordsFilteredInactive': users_data_total_filtered_inactive_count
162 162 })
163 163
164 164 return data
165 165
166 166 def _set_personal_repo_group_template_vars(self, c_obj):
167 167 DummyUser = AttributeDict({
168 168 'username': '${username}',
169 169 'user_id': '${user_id}',
170 170 })
171 171 c_obj.default_create_repo_group = RepoGroupModel() \
172 172 .get_default_create_personal_repo_group()
173 173 c_obj.personal_repo_group_name = RepoGroupModel() \
174 174 .get_personal_group_name(DummyUser)
175 175
176 176 @LoginRequired()
177 177 @HasPermissionAllDecorator('hg.admin')
178 178 def users_new(self):
179 179 _ = self.request.translate
180 180 c = self.load_default_context()
181 181 c.default_extern_type = auth_rhodecode.RhodeCodeAuthPlugin.uid
182 182 self._set_personal_repo_group_template_vars(c)
183 183 return self._get_template_context(c)
184 184
185 185 @LoginRequired()
186 186 @HasPermissionAllDecorator('hg.admin')
187 187 @CSRFRequired()
188 188 def users_create(self):
189 189 _ = self.request.translate
190 190 c = self.load_default_context()
191 191 c.default_extern_type = auth_rhodecode.RhodeCodeAuthPlugin.uid
192 192 user_model = UserModel()
193 193 user_form = UserForm(self.request.translate)()
194 194 try:
195 195 form_result = user_form.to_python(dict(self.request.POST))
196 196 user = user_model.create(form_result)
197 197 Session().flush()
198 198 creation_data = user.get_api_data()
199 199 username = form_result['username']
200 200
201 201 audit_logger.store_web(
202 202 'user.create', action_data={'data': creation_data},
203 203 user=c.rhodecode_user)
204 204
205 205 user_link = h.link_to(
206 206 h.escape(username),
207 207 h.route_path('user_edit', user_id=user.user_id))
208 208 h.flash(h.literal(_('Created user %(user_link)s')
209 209 % {'user_link': user_link}), category='success')
210 210 Session().commit()
211 211 except formencode.Invalid as errors:
212 212 self._set_personal_repo_group_template_vars(c)
213 213 data = render(
214 214 'rhodecode:templates/admin/users/user_add.mako',
215 215 self._get_template_context(c), self.request)
216 216 html = formencode.htmlfill.render(
217 217 data,
218 218 defaults=errors.value,
219 errors=errors.error_dict or {},
219 errors=errors.unpack_errors() or {},
220 220 prefix_error=False,
221 221 encoding="UTF-8",
222 222 force_defaults=False
223 223 )
224 224 return Response(html)
225 225 except UserCreationError as e:
226 h.flash(e, 'error')
226 h.flash(safe_unicode(e), 'error')
227 227 except Exception:
228 228 log.exception("Exception creation of user")
229 229 h.flash(_('Error occurred during creation of user %s')
230 230 % self.request.POST.get('username'), category='error')
231 231 raise HTTPFound(h.route_path('users'))
232 232
233 233
234 234 class UsersView(UserAppView):
235 235 ALLOW_SCOPED_TOKENS = False
236 236 """
237 237 This view has alternative version inside EE, if modified please take a look
238 238 in there as well.
239 239 """
240 240
241 241 def get_auth_plugins(self):
242 242 valid_plugins = []
243 243 authn_registry = get_authn_registry(self.request.registry)
244 244 for plugin in authn_registry.get_plugins_for_authentication():
245 245 if isinstance(plugin, RhodeCodeExternalAuthPlugin):
246 246 valid_plugins.append(plugin)
247 247 elif plugin.name == 'rhodecode':
248 248 valid_plugins.append(plugin)
249 249
250 250 # extend our choices if user has set a bound plugin which isn't enabled at the
251 251 # moment
252 252 extern_type = self.db_user.extern_type
253 253 if extern_type not in [x.uid for x in valid_plugins]:
254 254 try:
255 255 plugin = authn_registry.get_plugin_by_uid(extern_type)
256 256 if plugin:
257 257 valid_plugins.append(plugin)
258 258
259 259 except Exception:
260 260 log.exception(
261 261 'Could not extend user plugins with `{}`'.format(extern_type))
262 262 return valid_plugins
263 263
264 264 def load_default_context(self):
265 265 req = self.request
266 266
267 267 c = self._get_local_tmpl_context()
268 268 c.allow_scoped_tokens = self.ALLOW_SCOPED_TOKENS
269 269 c.allowed_languages = [
270 270 ('en', 'English (en)'),
271 271 ('de', 'German (de)'),
272 272 ('fr', 'French (fr)'),
273 273 ('it', 'Italian (it)'),
274 274 ('ja', 'Japanese (ja)'),
275 275 ('pl', 'Polish (pl)'),
276 276 ('pt', 'Portuguese (pt)'),
277 277 ('ru', 'Russian (ru)'),
278 278 ('zh', 'Chinese (zh)'),
279 279 ]
280 280
281 281 c.allowed_extern_types = [
282 282 (x.uid, x.get_display_name()) for x in self.get_auth_plugins()
283 283 ]
284 284 perms = req.registry.settings.get('available_permissions')
285 285 if not perms:
286 286 # inject info about available permissions
287 287 auth.set_available_permissions(req.registry.settings)
288 288
289 289 c.available_permissions = req.registry.settings['available_permissions']
290 290 PermissionModel().set_global_permission_choices(
291 291 c, gettext_translator=req.translate)
292 292
293 293 return c
294 294
295 295 @LoginRequired()
296 296 @HasPermissionAllDecorator('hg.admin')
297 297 @CSRFRequired()
298 298 def user_update(self):
299 299 _ = self.request.translate
300 300 c = self.load_default_context()
301 301
302 302 user_id = self.db_user_id
303 303 c.user = self.db_user
304 304
305 305 c.active = 'profile'
306 306 c.extern_type = c.user.extern_type
307 307 c.extern_name = c.user.extern_name
308 308 c.perm_user = c.user.AuthUser(ip_addr=self.request.remote_addr)
309 309 available_languages = [x[0] for x in c.allowed_languages]
310 310 _form = UserForm(self.request.translate, edit=True,
311 311 available_languages=available_languages,
312 312 old_data={'user_id': user_id,
313 313 'email': c.user.email})()
314 314
315 315 c.edit_mode = self.request.POST.get('edit') == '1'
316 316 form_result = {}
317 317 old_values = c.user.get_api_data()
318 318 try:
319 319 form_result = _form.to_python(dict(self.request.POST))
320 320 skip_attrs = ['extern_name']
321 321 # TODO: plugin should define if username can be updated
322 322
323 323 if c.extern_type != "rhodecode" and not c.edit_mode:
324 324 # forbid updating username for external accounts
325 325 skip_attrs.append('username')
326 326
327 327 UserModel().update_user(
328 328 user_id, skip_attrs=skip_attrs, **form_result)
329 329
330 330 audit_logger.store_web(
331 331 'user.edit', action_data={'old_data': old_values},
332 332 user=c.rhodecode_user)
333 333
334 334 Session().commit()
335 335 h.flash(_('User updated successfully'), category='success')
336 336 except formencode.Invalid as errors:
337 337 data = render(
338 338 'rhodecode:templates/admin/users/user_edit.mako',
339 339 self._get_template_context(c), self.request)
340 340 html = formencode.htmlfill.render(
341 341 data,
342 342 defaults=errors.value,
343 errors=errors.error_dict or {},
343 errors=errors.unpack_errors() or {},
344 344 prefix_error=False,
345 345 encoding="UTF-8",
346 346 force_defaults=False
347 347 )
348 348 return Response(html)
349 349 except UserCreationError as e:
350 h.flash(e, 'error')
350 h.flash(safe_unicode(e), 'error')
351 351 except Exception:
352 352 log.exception("Exception updating user")
353 353 h.flash(_('Error occurred during update of user %s')
354 354 % form_result.get('username'), category='error')
355 355 raise HTTPFound(h.route_path('user_edit', user_id=user_id))
356 356
357 357 @LoginRequired()
358 358 @HasPermissionAllDecorator('hg.admin')
359 359 @CSRFRequired()
360 360 def user_delete(self):
361 361 _ = self.request.translate
362 362 c = self.load_default_context()
363 363 c.user = self.db_user
364 364
365 365 _repos = c.user.repositories
366 366 _repo_groups = c.user.repository_groups
367 367 _user_groups = c.user.user_groups
368 368 _pull_requests = c.user.user_pull_requests
369 369 _artifacts = c.user.artifacts
370 370
371 371 handle_repos = None
372 372 handle_repo_groups = None
373 373 handle_user_groups = None
374 374 handle_pull_requests = None
375 375 handle_artifacts = None
376 376
377 377 # calls for flash of handle based on handle case detach or delete
378 378 def set_handle_flash_repos():
379 379 handle = handle_repos
380 380 if handle == 'detach':
381 381 h.flash(_('Detached %s repositories') % len(_repos),
382 382 category='success')
383 383 elif handle == 'delete':
384 384 h.flash(_('Deleted %s repositories') % len(_repos),
385 385 category='success')
386 386
387 387 def set_handle_flash_repo_groups():
388 388 handle = handle_repo_groups
389 389 if handle == 'detach':
390 390 h.flash(_('Detached %s repository groups') % len(_repo_groups),
391 391 category='success')
392 392 elif handle == 'delete':
393 393 h.flash(_('Deleted %s repository groups') % len(_repo_groups),
394 394 category='success')
395 395
396 396 def set_handle_flash_user_groups():
397 397 handle = handle_user_groups
398 398 if handle == 'detach':
399 399 h.flash(_('Detached %s user groups') % len(_user_groups),
400 400 category='success')
401 401 elif handle == 'delete':
402 402 h.flash(_('Deleted %s user groups') % len(_user_groups),
403 403 category='success')
404 404
405 405 def set_handle_flash_pull_requests():
406 406 handle = handle_pull_requests
407 407 if handle == 'detach':
408 408 h.flash(_('Detached %s pull requests') % len(_pull_requests),
409 409 category='success')
410 410 elif handle == 'delete':
411 411 h.flash(_('Deleted %s pull requests') % len(_pull_requests),
412 412 category='success')
413 413
414 414 def set_handle_flash_artifacts():
415 415 handle = handle_artifacts
416 416 if handle == 'detach':
417 417 h.flash(_('Detached %s artifacts') % len(_artifacts),
418 418 category='success')
419 419 elif handle == 'delete':
420 420 h.flash(_('Deleted %s artifacts') % len(_artifacts),
421 421 category='success')
422 422
423 423 handle_user = User.get_first_super_admin()
424 424 handle_user_id = safe_int(self.request.POST.get('detach_user_id'))
425 425 if handle_user_id:
426 426 # NOTE(marcink): we get new owner for objects...
427 427 handle_user = User.get_or_404(handle_user_id)
428 428
429 429 if _repos and self.request.POST.get('user_repos'):
430 430 handle_repos = self.request.POST['user_repos']
431 431
432 432 if _repo_groups and self.request.POST.get('user_repo_groups'):
433 433 handle_repo_groups = self.request.POST['user_repo_groups']
434 434
435 435 if _user_groups and self.request.POST.get('user_user_groups'):
436 436 handle_user_groups = self.request.POST['user_user_groups']
437 437
438 438 if _pull_requests and self.request.POST.get('user_pull_requests'):
439 439 handle_pull_requests = self.request.POST['user_pull_requests']
440 440
441 441 if _artifacts and self.request.POST.get('user_artifacts'):
442 442 handle_artifacts = self.request.POST['user_artifacts']
443 443
444 444 old_values = c.user.get_api_data()
445 445
446 446 try:
447 447
448 448 UserModel().delete(
449 449 c.user,
450 450 handle_repos=handle_repos,
451 451 handle_repo_groups=handle_repo_groups,
452 452 handle_user_groups=handle_user_groups,
453 453 handle_pull_requests=handle_pull_requests,
454 454 handle_artifacts=handle_artifacts,
455 455 handle_new_owner=handle_user
456 456 )
457 457
458 458 audit_logger.store_web(
459 459 'user.delete', action_data={'old_data': old_values},
460 460 user=c.rhodecode_user)
461 461
462 462 Session().commit()
463 463 set_handle_flash_repos()
464 464 set_handle_flash_repo_groups()
465 465 set_handle_flash_user_groups()
466 466 set_handle_flash_pull_requests()
467 467 set_handle_flash_artifacts()
468 468 username = h.escape(old_values['username'])
469 469 h.flash(_('Successfully deleted user `{}`').format(username), category='success')
470 470 except (UserOwnsReposException, UserOwnsRepoGroupsException,
471 471 UserOwnsUserGroupsException, UserOwnsPullRequestsException,
472 472 UserOwnsArtifactsException, DefaultUserException) as e:
473 473 h.flash(e, category='warning')
474 474 except Exception:
475 475 log.exception("Exception during deletion of user")
476 476 h.flash(_('An error occurred during deletion of user'),
477 477 category='error')
478 478 raise HTTPFound(h.route_path('users'))
479 479
480 480 @LoginRequired()
481 481 @HasPermissionAllDecorator('hg.admin')
482 482 def user_edit(self):
483 483 _ = self.request.translate
484 484 c = self.load_default_context()
485 485 c.user = self.db_user
486 486
487 487 c.active = 'profile'
488 488 c.extern_type = c.user.extern_type
489 489 c.extern_name = c.user.extern_name
490 490 c.perm_user = c.user.AuthUser(ip_addr=self.request.remote_addr)
491 491 c.edit_mode = self.request.GET.get('edit') == '1'
492 492
493 493 defaults = c.user.get_dict()
494 494 defaults.update({'language': c.user.user_data.get('language')})
495 495
496 496 data = render(
497 497 'rhodecode:templates/admin/users/user_edit.mako',
498 498 self._get_template_context(c), self.request)
499 499 html = formencode.htmlfill.render(
500 500 data,
501 501 defaults=defaults,
502 502 encoding="UTF-8",
503 503 force_defaults=False
504 504 )
505 505 return Response(html)
506 506
507 507 @LoginRequired()
508 508 @HasPermissionAllDecorator('hg.admin')
509 509 def user_edit_advanced(self):
510 510 _ = self.request.translate
511 511 c = self.load_default_context()
512 512
513 513 user_id = self.db_user_id
514 514 c.user = self.db_user
515 515
516 516 c.detach_user = User.get_first_super_admin()
517 517 detach_user_id = safe_int(self.request.GET.get('detach_user_id'))
518 518 if detach_user_id:
519 519 c.detach_user = User.get_or_404(detach_user_id)
520 520
521 521 c.active = 'advanced'
522 522 c.personal_repo_group = RepoGroup.get_user_personal_repo_group(user_id)
523 523 c.personal_repo_group_name = RepoGroupModel()\
524 524 .get_personal_group_name(c.user)
525 525
526 526 c.user_to_review_rules = sorted(
527 527 (x.user for x in c.user.user_review_rules),
528 528 key=lambda u: u.username.lower())
529 529
530 530 defaults = c.user.get_dict()
531 531
532 532 # Interim workaround if the user participated on any pull requests as a
533 533 # reviewer.
534 534 has_review = len(c.user.reviewer_pull_requests)
535 535 c.can_delete_user = not has_review
536 536 c.can_delete_user_message = ''
537 537 inactive_link = h.link_to(
538 538 'inactive', h.route_path('user_edit', user_id=user_id, _anchor='active'))
539 539 if has_review == 1:
540 540 c.can_delete_user_message = h.literal(_(
541 541 'The user participates as reviewer in {} pull request and '
542 542 'cannot be deleted. \nYou can set the user to '
543 543 '"{}" instead of deleting it.').format(
544 544 has_review, inactive_link))
545 545 elif has_review:
546 546 c.can_delete_user_message = h.literal(_(
547 547 'The user participates as reviewer in {} pull requests and '
548 548 'cannot be deleted. \nYou can set the user to '
549 549 '"{}" instead of deleting it.').format(
550 550 has_review, inactive_link))
551 551
552 552 data = render(
553 553 'rhodecode:templates/admin/users/user_edit.mako',
554 554 self._get_template_context(c), self.request)
555 555 html = formencode.htmlfill.render(
556 556 data,
557 557 defaults=defaults,
558 558 encoding="UTF-8",
559 559 force_defaults=False
560 560 )
561 561 return Response(html)
562 562
563 563 @LoginRequired()
564 564 @HasPermissionAllDecorator('hg.admin')
565 565 def user_edit_global_perms(self):
566 566 _ = self.request.translate
567 567 c = self.load_default_context()
568 568 c.user = self.db_user
569 569
570 570 c.active = 'global_perms'
571 571
572 572 c.default_user = User.get_default_user()
573 573 defaults = c.user.get_dict()
574 574 defaults.update(c.default_user.get_default_perms(suffix='_inherited'))
575 575 defaults.update(c.default_user.get_default_perms())
576 576 defaults.update(c.user.get_default_perms())
577 577
578 578 data = render(
579 579 'rhodecode:templates/admin/users/user_edit.mako',
580 580 self._get_template_context(c), self.request)
581 581 html = formencode.htmlfill.render(
582 582 data,
583 583 defaults=defaults,
584 584 encoding="UTF-8",
585 585 force_defaults=False
586 586 )
587 587 return Response(html)
588 588
589 589 @LoginRequired()
590 590 @HasPermissionAllDecorator('hg.admin')
591 591 @CSRFRequired()
592 592 def user_edit_global_perms_update(self):
593 593 _ = self.request.translate
594 594 c = self.load_default_context()
595 595
596 596 user_id = self.db_user_id
597 597 c.user = self.db_user
598 598
599 599 c.active = 'global_perms'
600 600 try:
601 601 # first stage that verifies the checkbox
602 602 _form = UserIndividualPermissionsForm(self.request.translate)
603 603 form_result = _form.to_python(dict(self.request.POST))
604 604 inherit_perms = form_result['inherit_default_permissions']
605 605 c.user.inherit_default_permissions = inherit_perms
606 606 Session().add(c.user)
607 607
608 608 if not inherit_perms:
609 609 # only update the individual ones if we un check the flag
610 610 _form = UserPermissionsForm(
611 611 self.request.translate,
612 612 [x[0] for x in c.repo_create_choices],
613 613 [x[0] for x in c.repo_create_on_write_choices],
614 614 [x[0] for x in c.repo_group_create_choices],
615 615 [x[0] for x in c.user_group_create_choices],
616 616 [x[0] for x in c.fork_choices],
617 617 [x[0] for x in c.inherit_default_permission_choices])()
618 618
619 619 form_result = _form.to_python(dict(self.request.POST))
620 620 form_result.update({'perm_user_id': c.user.user_id})
621 621
622 622 PermissionModel().update_user_permissions(form_result)
623 623
624 624 # TODO(marcink): implement global permissions
625 625 # audit_log.store_web('user.edit.permissions')
626 626
627 627 Session().commit()
628 628
629 629 h.flash(_('User global permissions updated successfully'),
630 630 category='success')
631 631
632 632 except formencode.Invalid as errors:
633 633 data = render(
634 634 'rhodecode:templates/admin/users/user_edit.mako',
635 635 self._get_template_context(c), self.request)
636 636 html = formencode.htmlfill.render(
637 637 data,
638 638 defaults=errors.value,
639 errors=errors.error_dict or {},
639 errors=errors.unpack_errors() or {},
640 640 prefix_error=False,
641 641 encoding="UTF-8",
642 642 force_defaults=False
643 643 )
644 644 return Response(html)
645 645 except Exception:
646 646 log.exception("Exception during permissions saving")
647 647 h.flash(_('An error occurred during permissions saving'),
648 648 category='error')
649 649
650 650 affected_user_ids = [user_id]
651 651 PermissionModel().trigger_permission_flush(affected_user_ids)
652 652 raise HTTPFound(h.route_path('user_edit_global_perms', user_id=user_id))
653 653
654 654 @LoginRequired()
655 655 @HasPermissionAllDecorator('hg.admin')
656 656 @CSRFRequired()
657 657 def user_enable_force_password_reset(self):
658 658 _ = self.request.translate
659 659 c = self.load_default_context()
660 660
661 661 user_id = self.db_user_id
662 662 c.user = self.db_user
663 663
664 664 try:
665 665 c.user.update_userdata(force_password_change=True)
666 666
667 667 msg = _('Force password change enabled for user')
668 668 audit_logger.store_web('user.edit.password_reset.enabled',
669 669 user=c.rhodecode_user)
670 670
671 671 Session().commit()
672 672 h.flash(msg, category='success')
673 673 except Exception:
674 674 log.exception("Exception during password reset for user")
675 675 h.flash(_('An error occurred during password reset for user'),
676 676 category='error')
677 677
678 678 raise HTTPFound(h.route_path('user_edit_advanced', user_id=user_id))
679 679
680 680 @LoginRequired()
681 681 @HasPermissionAllDecorator('hg.admin')
682 682 @CSRFRequired()
683 683 def user_disable_force_password_reset(self):
684 684 _ = self.request.translate
685 685 c = self.load_default_context()
686 686
687 687 user_id = self.db_user_id
688 688 c.user = self.db_user
689 689
690 690 try:
691 691 c.user.update_userdata(force_password_change=False)
692 692
693 693 msg = _('Force password change disabled for user')
694 694 audit_logger.store_web(
695 695 'user.edit.password_reset.disabled',
696 696 user=c.rhodecode_user)
697 697
698 698 Session().commit()
699 699 h.flash(msg, category='success')
700 700 except Exception:
701 701 log.exception("Exception during password reset for user")
702 702 h.flash(_('An error occurred during password reset for user'),
703 703 category='error')
704 704
705 705 raise HTTPFound(h.route_path('user_edit_advanced', user_id=user_id))
706 706
707 707 @LoginRequired()
708 708 @HasPermissionAllDecorator('hg.admin')
709 709 @CSRFRequired()
710 710 def user_notice_dismiss(self):
711 711 _ = self.request.translate
712 712 c = self.load_default_context()
713 713
714 714 user_id = self.db_user_id
715 715 c.user = self.db_user
716 716 user_notice_id = safe_int(self.request.POST.get('notice_id'))
717 717 notice = UserNotice().query()\
718 718 .filter(UserNotice.user_id == user_id)\
719 719 .filter(UserNotice.user_notice_id == user_notice_id)\
720 720 .scalar()
721 721 read = False
722 722 if notice:
723 723 notice.notice_read = True
724 724 Session().add(notice)
725 725 Session().commit()
726 726 read = True
727 727
728 728 return {'notice': user_notice_id, 'read': read}
729 729
730 730 @LoginRequired()
731 731 @HasPermissionAllDecorator('hg.admin')
732 732 @CSRFRequired()
733 733 def user_create_personal_repo_group(self):
734 734 """
735 735 Create personal repository group for this user
736 736 """
737 737 from rhodecode.model.repo_group import RepoGroupModel
738 738
739 739 _ = self.request.translate
740 740 c = self.load_default_context()
741 741
742 742 user_id = self.db_user_id
743 743 c.user = self.db_user
744 744
745 745 personal_repo_group = RepoGroup.get_user_personal_repo_group(
746 746 c.user.user_id)
747 747 if personal_repo_group:
748 748 raise HTTPFound(h.route_path('user_edit_advanced', user_id=user_id))
749 749
750 750 personal_repo_group_name = RepoGroupModel().get_personal_group_name(c.user)
751 751 named_personal_group = RepoGroup.get_by_group_name(
752 752 personal_repo_group_name)
753 753 try:
754 754
755 755 if named_personal_group and named_personal_group.user_id == c.user.user_id:
756 756 # migrate the same named group, and mark it as personal
757 757 named_personal_group.personal = True
758 758 Session().add(named_personal_group)
759 759 Session().commit()
760 760 msg = _('Linked repository group `%s` as personal' % (
761 761 personal_repo_group_name,))
762 762 h.flash(msg, category='success')
763 763 elif not named_personal_group:
764 764 RepoGroupModel().create_personal_repo_group(c.user)
765 765
766 766 msg = _('Created repository group `%s`' % (
767 767 personal_repo_group_name,))
768 768 h.flash(msg, category='success')
769 769 else:
770 770 msg = _('Repository group `%s` is already taken' % (
771 771 personal_repo_group_name,))
772 772 h.flash(msg, category='warning')
773 773 except Exception:
774 774 log.exception("Exception during repository group creation")
775 775 msg = _(
776 776 'An error occurred during repository group creation for user')
777 777 h.flash(msg, category='error')
778 778 Session().rollback()
779 779
780 780 raise HTTPFound(h.route_path('user_edit_advanced', user_id=user_id))
781 781
782 782 @LoginRequired()
783 783 @HasPermissionAllDecorator('hg.admin')
784 784 def auth_tokens(self):
785 785 _ = self.request.translate
786 786 c = self.load_default_context()
787 787 c.user = self.db_user
788 788
789 789 c.active = 'auth_tokens'
790 790
791 791 c.lifetime_values = AuthTokenModel.get_lifetime_values(translator=_)
792 792 c.role_values = [
793 793 (x, AuthTokenModel.cls._get_role_name(x))
794 794 for x in AuthTokenModel.cls.ROLES]
795 795 c.role_options = [(c.role_values, _("Role"))]
796 796 c.user_auth_tokens = AuthTokenModel().get_auth_tokens(
797 797 c.user.user_id, show_expired=True)
798 798 c.role_vcs = AuthTokenModel.cls.ROLE_VCS
799 799 return self._get_template_context(c)
800 800
801 801 @LoginRequired()
802 802 @HasPermissionAllDecorator('hg.admin')
803 803 def auth_tokens_view(self):
804 804 _ = self.request.translate
805 805 c = self.load_default_context()
806 806 c.user = self.db_user
807 807
808 808 auth_token_id = self.request.POST.get('auth_token_id')
809 809
810 810 if auth_token_id:
811 811 token = UserApiKeys.get_or_404(auth_token_id)
812 812
813 813 return {
814 814 'auth_token': token.api_key
815 815 }
816 816
817 817 def maybe_attach_token_scope(self, token):
818 818 # implemented in EE edition
819 819 pass
820 820
821 821 @LoginRequired()
822 822 @HasPermissionAllDecorator('hg.admin')
823 823 @CSRFRequired()
824 824 def auth_tokens_add(self):
825 825 _ = self.request.translate
826 826 c = self.load_default_context()
827 827
828 828 user_id = self.db_user_id
829 829 c.user = self.db_user
830 830
831 831 user_data = c.user.get_api_data()
832 832 lifetime = safe_int(self.request.POST.get('lifetime'), -1)
833 833 description = self.request.POST.get('description')
834 834 role = self.request.POST.get('role')
835 835
836 836 token = UserModel().add_auth_token(
837 837 user=c.user.user_id,
838 838 lifetime_minutes=lifetime, role=role, description=description,
839 839 scope_callback=self.maybe_attach_token_scope)
840 840 token_data = token.get_api_data()
841 841
842 842 audit_logger.store_web(
843 843 'user.edit.token.add', action_data={
844 844 'data': {'token': token_data, 'user': user_data}},
845 845 user=self._rhodecode_user, )
846 846 Session().commit()
847 847
848 848 h.flash(_("Auth token successfully created"), category='success')
849 849 return HTTPFound(h.route_path('edit_user_auth_tokens', user_id=user_id))
850 850
851 851 @LoginRequired()
852 852 @HasPermissionAllDecorator('hg.admin')
853 853 @CSRFRequired()
854 854 def auth_tokens_delete(self):
855 855 _ = self.request.translate
856 856 c = self.load_default_context()
857 857
858 858 user_id = self.db_user_id
859 859 c.user = self.db_user
860 860
861 861 user_data = c.user.get_api_data()
862 862
863 863 del_auth_token = self.request.POST.get('del_auth_token')
864 864
865 865 if del_auth_token:
866 866 token = UserApiKeys.get_or_404(del_auth_token)
867 867 token_data = token.get_api_data()
868 868
869 869 AuthTokenModel().delete(del_auth_token, c.user.user_id)
870 870 audit_logger.store_web(
871 871 'user.edit.token.delete', action_data={
872 872 'data': {'token': token_data, 'user': user_data}},
873 873 user=self._rhodecode_user,)
874 874 Session().commit()
875 875 h.flash(_("Auth token successfully deleted"), category='success')
876 876
877 877 return HTTPFound(h.route_path('edit_user_auth_tokens', user_id=user_id))
878 878
879 879 @LoginRequired()
880 880 @HasPermissionAllDecorator('hg.admin')
881 881 def ssh_keys(self):
882 882 _ = self.request.translate
883 883 c = self.load_default_context()
884 884 c.user = self.db_user
885 885
886 886 c.active = 'ssh_keys'
887 887 c.default_key = self.request.GET.get('default_key')
888 888 c.user_ssh_keys = SshKeyModel().get_ssh_keys(c.user.user_id)
889 889 return self._get_template_context(c)
890 890
891 891 @LoginRequired()
892 892 @HasPermissionAllDecorator('hg.admin')
893 893 def ssh_keys_generate_keypair(self):
894 894 _ = self.request.translate
895 895 c = self.load_default_context()
896 896
897 897 c.user = self.db_user
898 898
899 899 c.active = 'ssh_keys_generate'
900 900 comment = 'RhodeCode-SSH {}'.format(c.user.email or '')
901 901 private_format = self.request.GET.get('private_format') \
902 902 or SshKeyModel.DEFAULT_PRIVATE_KEY_FORMAT
903 903 c.private, c.public = SshKeyModel().generate_keypair(
904 904 comment=comment, private_format=private_format)
905 905
906 906 return self._get_template_context(c)
907 907
908 908 @LoginRequired()
909 909 @HasPermissionAllDecorator('hg.admin')
910 910 @CSRFRequired()
911 911 def ssh_keys_add(self):
912 912 _ = self.request.translate
913 913 c = self.load_default_context()
914 914
915 915 user_id = self.db_user_id
916 916 c.user = self.db_user
917 917
918 918 user_data = c.user.get_api_data()
919 919 key_data = self.request.POST.get('key_data')
920 920 description = self.request.POST.get('description')
921 921
922 922 fingerprint = 'unknown'
923 923 try:
924 924 if not key_data:
925 925 raise ValueError('Please add a valid public key')
926 926
927 927 key = SshKeyModel().parse_key(key_data.strip())
928 928 fingerprint = key.hash_md5()
929 929
930 930 ssh_key = SshKeyModel().create(
931 931 c.user.user_id, fingerprint, key.keydata, description)
932 932 ssh_key_data = ssh_key.get_api_data()
933 933
934 934 audit_logger.store_web(
935 935 'user.edit.ssh_key.add', action_data={
936 936 'data': {'ssh_key': ssh_key_data, 'user': user_data}},
937 937 user=self._rhodecode_user, )
938 938 Session().commit()
939 939
940 940 # Trigger an event on change of keys.
941 941 trigger(SshKeyFileChangeEvent(), self.request.registry)
942 942
943 943 h.flash(_("Ssh Key successfully created"), category='success')
944 944
945 945 except IntegrityError:
946 946 log.exception("Exception during ssh key saving")
947 947 err = 'Such key with fingerprint `{}` already exists, ' \
948 948 'please use a different one'.format(fingerprint)
949 949 h.flash(_('An error occurred during ssh key saving: {}').format(err),
950 950 category='error')
951 951 except Exception as e:
952 952 log.exception("Exception during ssh key saving")
953 953 h.flash(_('An error occurred during ssh key saving: {}').format(e),
954 954 category='error')
955 955
956 956 return HTTPFound(
957 957 h.route_path('edit_user_ssh_keys', user_id=user_id))
958 958
959 959 @LoginRequired()
960 960 @HasPermissionAllDecorator('hg.admin')
961 961 @CSRFRequired()
962 962 def ssh_keys_delete(self):
963 963 _ = self.request.translate
964 964 c = self.load_default_context()
965 965
966 966 user_id = self.db_user_id
967 967 c.user = self.db_user
968 968
969 969 user_data = c.user.get_api_data()
970 970
971 971 del_ssh_key = self.request.POST.get('del_ssh_key')
972 972
973 973 if del_ssh_key:
974 974 ssh_key = UserSshKeys.get_or_404(del_ssh_key)
975 975 ssh_key_data = ssh_key.get_api_data()
976 976
977 977 SshKeyModel().delete(del_ssh_key, c.user.user_id)
978 978 audit_logger.store_web(
979 979 'user.edit.ssh_key.delete', action_data={
980 980 'data': {'ssh_key': ssh_key_data, 'user': user_data}},
981 981 user=self._rhodecode_user,)
982 982 Session().commit()
983 983 # Trigger an event on change of keys.
984 984 trigger(SshKeyFileChangeEvent(), self.request.registry)
985 985 h.flash(_("Ssh key successfully deleted"), category='success')
986 986
987 987 return HTTPFound(h.route_path('edit_user_ssh_keys', user_id=user_id))
988 988
989 989 @LoginRequired()
990 990 @HasPermissionAllDecorator('hg.admin')
991 991 def emails(self):
992 992 _ = self.request.translate
993 993 c = self.load_default_context()
994 994 c.user = self.db_user
995 995
996 996 c.active = 'emails'
997 997 c.user_email_map = UserEmailMap.query() \
998 998 .filter(UserEmailMap.user == c.user).all()
999 999
1000 1000 return self._get_template_context(c)
1001 1001
1002 1002 @LoginRequired()
1003 1003 @HasPermissionAllDecorator('hg.admin')
1004 1004 @CSRFRequired()
1005 1005 def emails_add(self):
1006 1006 _ = self.request.translate
1007 1007 c = self.load_default_context()
1008 1008
1009 1009 user_id = self.db_user_id
1010 1010 c.user = self.db_user
1011 1011
1012 1012 email = self.request.POST.get('new_email')
1013 1013 user_data = c.user.get_api_data()
1014 1014 try:
1015 1015
1016 1016 form = UserExtraEmailForm(self.request.translate)()
1017 1017 data = form.to_python({'email': email})
1018 1018 email = data['email']
1019 1019
1020 1020 UserModel().add_extra_email(c.user.user_id, email)
1021 1021 audit_logger.store_web(
1022 1022 'user.edit.email.add',
1023 1023 action_data={'email': email, 'user': user_data},
1024 1024 user=self._rhodecode_user)
1025 1025 Session().commit()
1026 1026 h.flash(_("Added new email address `%s` for user account") % email,
1027 1027 category='success')
1028 1028 except formencode.Invalid as error:
1029 h.flash(h.escape(error.error_dict['email']), category='error')
1029 msg = error.unpack_errors()['email']
1030 h.flash(h.escape(msg), category='error')
1030 1031 except IntegrityError:
1031 1032 log.warning("Email %s already exists", email)
1032 1033 h.flash(_('Email `{}` is already registered for another user.').format(email),
1033 1034 category='error')
1034 1035 except Exception:
1035 1036 log.exception("Exception during email saving")
1036 1037 h.flash(_('An error occurred during email saving'),
1037 1038 category='error')
1038 1039 raise HTTPFound(h.route_path('edit_user_emails', user_id=user_id))
1039 1040
1040 1041 @LoginRequired()
1041 1042 @HasPermissionAllDecorator('hg.admin')
1042 1043 @CSRFRequired()
1043 1044 def emails_delete(self):
1044 1045 _ = self.request.translate
1045 1046 c = self.load_default_context()
1046 1047
1047 1048 user_id = self.db_user_id
1048 1049 c.user = self.db_user
1049 1050
1050 1051 email_id = self.request.POST.get('del_email_id')
1051 1052 user_model = UserModel()
1052 1053
1053 1054 email = UserEmailMap.query().get(email_id).email
1054 1055 user_data = c.user.get_api_data()
1055 1056 user_model.delete_extra_email(c.user.user_id, email_id)
1056 1057 audit_logger.store_web(
1057 1058 'user.edit.email.delete',
1058 1059 action_data={'email': email, 'user': user_data},
1059 1060 user=self._rhodecode_user)
1060 1061 Session().commit()
1061 1062 h.flash(_("Removed email address from user account"),
1062 1063 category='success')
1063 1064 raise HTTPFound(h.route_path('edit_user_emails', user_id=user_id))
1064 1065
1065 1066 @LoginRequired()
1066 1067 @HasPermissionAllDecorator('hg.admin')
1067 1068 def ips(self):
1068 1069 _ = self.request.translate
1069 1070 c = self.load_default_context()
1070 1071 c.user = self.db_user
1071 1072
1072 1073 c.active = 'ips'
1073 1074 c.user_ip_map = UserIpMap.query() \
1074 1075 .filter(UserIpMap.user == c.user).all()
1075 1076
1076 1077 c.inherit_default_ips = c.user.inherit_default_permissions
1077 1078 c.default_user_ip_map = UserIpMap.query() \
1078 1079 .filter(UserIpMap.user == User.get_default_user()).all()
1079 1080
1080 1081 return self._get_template_context(c)
1081 1082
1082 1083 @LoginRequired()
1083 1084 @HasPermissionAllDecorator('hg.admin')
1084 1085 @CSRFRequired()
1085 1086 # NOTE(marcink): this view is allowed for default users, as we can
1086 1087 # edit their IP white list
1087 1088 def ips_add(self):
1088 1089 _ = self.request.translate
1089 1090 c = self.load_default_context()
1090 1091
1091 1092 user_id = self.db_user_id
1092 1093 c.user = self.db_user
1093 1094
1094 1095 user_model = UserModel()
1095 1096 desc = self.request.POST.get('description')
1096 1097 try:
1097 1098 ip_list = user_model.parse_ip_range(
1098 1099 self.request.POST.get('new_ip'))
1099 1100 except Exception as e:
1100 1101 ip_list = []
1101 1102 log.exception("Exception during ip saving")
1102 1103 h.flash(_('An error occurred during ip saving:%s' % (e,)),
1103 1104 category='error')
1104 1105 added = []
1105 1106 user_data = c.user.get_api_data()
1106 1107 for ip in ip_list:
1107 1108 try:
1108 1109 form = UserExtraIpForm(self.request.translate)()
1109 1110 data = form.to_python({'ip': ip})
1110 1111 ip = data['ip']
1111 1112
1112 1113 user_model.add_extra_ip(c.user.user_id, ip, desc)
1113 1114 audit_logger.store_web(
1114 1115 'user.edit.ip.add',
1115 1116 action_data={'ip': ip, 'user': user_data},
1116 1117 user=self._rhodecode_user)
1117 1118 Session().commit()
1118 1119 added.append(ip)
1119 1120 except formencode.Invalid as error:
1120 msg = error.error_dict['ip']
1121 msg = error.unpack_errors()['ip']
1121 1122 h.flash(msg, category='error')
1122 1123 except Exception:
1123 1124 log.exception("Exception during ip saving")
1124 1125 h.flash(_('An error occurred during ip saving'),
1125 1126 category='error')
1126 1127 if added:
1127 1128 h.flash(
1128 1129 _("Added ips %s to user whitelist") % (', '.join(ip_list), ),
1129 1130 category='success')
1130 1131 if 'default_user' in self.request.POST:
1131 1132 # case for editing global IP list we do it for 'DEFAULT' user
1132 1133 raise HTTPFound(h.route_path('admin_permissions_ips'))
1133 1134 raise HTTPFound(h.route_path('edit_user_ips', user_id=user_id))
1134 1135
1135 1136 @LoginRequired()
1136 1137 @HasPermissionAllDecorator('hg.admin')
1137 1138 @CSRFRequired()
1138 1139 # NOTE(marcink): this view is allowed for default users, as we can
1139 1140 # edit their IP white list
1140 1141 def ips_delete(self):
1141 1142 _ = self.request.translate
1142 1143 c = self.load_default_context()
1143 1144
1144 1145 user_id = self.db_user_id
1145 1146 c.user = self.db_user
1146 1147
1147 1148 ip_id = self.request.POST.get('del_ip_id')
1148 1149 user_model = UserModel()
1149 1150 user_data = c.user.get_api_data()
1150 1151 ip = UserIpMap.query().get(ip_id).ip_addr
1151 1152 user_model.delete_extra_ip(c.user.user_id, ip_id)
1152 1153 audit_logger.store_web(
1153 1154 'user.edit.ip.delete', action_data={'ip': ip, 'user': user_data},
1154 1155 user=self._rhodecode_user)
1155 1156 Session().commit()
1156 1157 h.flash(_("Removed ip address from user whitelist"), category='success')
1157 1158
1158 1159 if 'default_user' in self.request.POST:
1159 1160 # case for editing global IP list we do it for 'DEFAULT' user
1160 1161 raise HTTPFound(h.route_path('admin_permissions_ips'))
1161 1162 raise HTTPFound(h.route_path('edit_user_ips', user_id=user_id))
1162 1163
1163 1164 @LoginRequired()
1164 1165 @HasPermissionAllDecorator('hg.admin')
1165 1166 def groups_management(self):
1166 1167 c = self.load_default_context()
1167 1168 c.user = self.db_user
1168 1169 c.data = c.user.group_member
1169 1170
1170 1171 groups = [UserGroupModel.get_user_groups_as_dict(group.users_group)
1171 1172 for group in c.user.group_member]
1172 1173 c.groups = ext_json.str_json(groups)
1173 1174 c.active = 'groups'
1174 1175
1175 1176 return self._get_template_context(c)
1176 1177
1177 1178 @LoginRequired()
1178 1179 @HasPermissionAllDecorator('hg.admin')
1179 1180 @CSRFRequired()
1180 1181 def groups_management_updates(self):
1181 1182 _ = self.request.translate
1182 1183 c = self.load_default_context()
1183 1184
1184 1185 user_id = self.db_user_id
1185 1186 c.user = self.db_user
1186 1187
1187 1188 user_groups = set(self.request.POST.getall('users_group_id'))
1188 1189 user_groups_objects = []
1189 1190
1190 1191 for ugid in user_groups:
1191 1192 user_groups_objects.append(
1192 1193 UserGroupModel().get_group(safe_int(ugid)))
1193 1194 user_group_model = UserGroupModel()
1194 1195 added_to_groups, removed_from_groups = \
1195 1196 user_group_model.change_groups(c.user, user_groups_objects)
1196 1197
1197 1198 user_data = c.user.get_api_data()
1198 1199 for user_group_id in added_to_groups:
1199 1200 user_group = UserGroup.get(user_group_id)
1200 1201 old_values = user_group.get_api_data()
1201 1202 audit_logger.store_web(
1202 1203 'user_group.edit.member.add',
1203 1204 action_data={'user': user_data, 'old_data': old_values},
1204 1205 user=self._rhodecode_user)
1205 1206
1206 1207 for user_group_id in removed_from_groups:
1207 1208 user_group = UserGroup.get(user_group_id)
1208 1209 old_values = user_group.get_api_data()
1209 1210 audit_logger.store_web(
1210 1211 'user_group.edit.member.delete',
1211 1212 action_data={'user': user_data, 'old_data': old_values},
1212 1213 user=self._rhodecode_user)
1213 1214
1214 1215 Session().commit()
1215 1216 c.active = 'user_groups_management'
1216 1217 h.flash(_("Groups successfully changed"), category='success')
1217 1218
1218 1219 return HTTPFound(h.route_path(
1219 1220 'edit_user_groups_management', user_id=user_id))
1220 1221
1221 1222 @LoginRequired()
1222 1223 @HasPermissionAllDecorator('hg.admin')
1223 1224 def user_audit_logs(self):
1224 1225 _ = self.request.translate
1225 1226 c = self.load_default_context()
1226 1227 c.user = self.db_user
1227 1228
1228 1229 c.active = 'audit'
1229 1230
1230 1231 p = safe_int(self.request.GET.get('page', 1), 1)
1231 1232
1232 1233 filter_term = self.request.GET.get('filter')
1233 1234 user_log = UserModel().get_user_log(c.user, filter_term)
1234 1235
1235 1236 def url_generator(page_num):
1236 1237 query_params = {
1237 1238 'page': page_num
1238 1239 }
1239 1240 if filter_term:
1240 1241 query_params['filter'] = filter_term
1241 1242 return self.request.current_route_path(_query=query_params)
1242 1243
1243 1244 c.audit_logs = SqlPage(
1244 1245 user_log, page=p, items_per_page=10, url_maker=url_generator)
1245 1246 c.filter_term = filter_term
1246 1247 return self._get_template_context(c)
1247 1248
1248 1249 @LoginRequired()
1249 1250 @HasPermissionAllDecorator('hg.admin')
1250 1251 def user_audit_logs_download(self):
1251 1252 _ = self.request.translate
1252 1253 c = self.load_default_context()
1253 1254 c.user = self.db_user
1254 1255
1255 1256 user_log = UserModel().get_user_log(c.user, filter_term=None)
1256 1257
1257 1258 audit_log_data = {}
1258 1259 for entry in user_log:
1259 1260 audit_log_data[entry.user_log_id] = entry.get_dict()
1260 1261
1261 1262 response = Response(ext_json.formatted_str_json(audit_log_data))
1262 1263 response.content_disposition = f'attachment; filename=user_{c.user.user_id}_audit_logs.json'
1263 1264 response.content_type = 'application/json'
1264 1265
1265 1266 return response
1266 1267
1267 1268 @LoginRequired()
1268 1269 @HasPermissionAllDecorator('hg.admin')
1269 1270 def user_perms_summary(self):
1270 1271 _ = self.request.translate
1271 1272 c = self.load_default_context()
1272 1273 c.user = self.db_user
1273 1274
1274 1275 c.active = 'perms_summary'
1275 1276 c.perm_user = c.user.AuthUser(ip_addr=self.request.remote_addr)
1276 1277
1277 1278 return self._get_template_context(c)
1278 1279
1279 1280 @LoginRequired()
1280 1281 @HasPermissionAllDecorator('hg.admin')
1281 1282 def user_perms_summary_json(self):
1282 1283 self.load_default_context()
1283 1284 perm_user = self.db_user.AuthUser(ip_addr=self.request.remote_addr)
1284 1285
1285 1286 return perm_user.permissions
1286 1287
1287 1288 @LoginRequired()
1288 1289 @HasPermissionAllDecorator('hg.admin')
1289 1290 def user_caches(self):
1290 1291 _ = self.request.translate
1291 1292 c = self.load_default_context()
1292 1293 c.user = self.db_user
1293 1294
1294 1295 c.active = 'caches'
1295 1296 c.perm_user = c.user.AuthUser(ip_addr=self.request.remote_addr)
1296 1297
1297 1298 cache_namespace_uid = 'cache_user_auth.{}'.format(self.db_user.user_id)
1298 1299 c.region = rc_cache.get_or_create_region('cache_perms', cache_namespace_uid)
1299 1300 c.backend = c.region.backend
1300 1301 c.user_keys = sorted(c.region.backend.list_keys(prefix=cache_namespace_uid))
1301 1302
1302 1303 return self._get_template_context(c)
1303 1304
1304 1305 @LoginRequired()
1305 1306 @HasPermissionAllDecorator('hg.admin')
1306 1307 @CSRFRequired()
1307 1308 def user_caches_update(self):
1308 1309 _ = self.request.translate
1309 1310 c = self.load_default_context()
1310 1311 c.user = self.db_user
1311 1312
1312 1313 c.active = 'caches'
1313 1314 c.perm_user = c.user.AuthUser(ip_addr=self.request.remote_addr)
1314 1315
1315 1316 cache_namespace_uid = 'cache_user_auth.{}'.format(self.db_user.user_id)
1316 1317 del_keys = rc_cache.clear_cache_namespace('cache_perms', cache_namespace_uid)
1317 1318
1318 1319 h.flash(_("Deleted {} cache keys").format(del_keys), category='success')
1319 1320
1320 1321 return HTTPFound(h.route_path(
1321 1322 'edit_user_caches', user_id=c.user.user_id))
General Comments 0
You need to be logged in to leave comments. Login now