##// END OF EJS Templates
auth-tokens: abstracted adding token for users into UserModel method for easier usage in scripts, and in future in API.
marcink -
r2951:93db3089 default
parent child Browse files
Show More
@@ -1,1241 +1,1242 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2016-2018 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.view import view_config
28 28 from pyramid.renderers import render
29 29 from pyramid.response import Response
30 30
31 31 from rhodecode.apps._base import BaseAppView, DataGridAppView, UserAppView
32 32 from rhodecode.apps.ssh_support import SshKeyFileChangeEvent
33 33 from rhodecode.authentication.plugins import auth_rhodecode
34 34 from rhodecode.events import trigger
35 35 from rhodecode.model.db import true
36 36
37 37 from rhodecode.lib import audit_logger, rc_cache
38 38 from rhodecode.lib.exceptions import (
39 39 UserCreationError, UserOwnsReposException, UserOwnsRepoGroupsException,
40 40 UserOwnsUserGroupsException, DefaultUserException)
41 41 from rhodecode.lib.ext_json import json
42 42 from rhodecode.lib.auth import (
43 43 LoginRequired, HasPermissionAllDecorator, CSRFRequired)
44 44 from rhodecode.lib import helpers as h
45 45 from rhodecode.lib.utils2 import safe_int, safe_unicode, AttributeDict
46 46 from rhodecode.model.auth_token import AuthTokenModel
47 47 from rhodecode.model.forms import (
48 48 UserForm, UserIndividualPermissionsForm, UserPermissionsForm,
49 49 UserExtraEmailForm, UserExtraIpForm)
50 50 from rhodecode.model.permission import PermissionModel
51 51 from rhodecode.model.repo_group import RepoGroupModel
52 52 from rhodecode.model.ssh_key import SshKeyModel
53 53 from rhodecode.model.user import UserModel
54 54 from rhodecode.model.user_group import UserGroupModel
55 55 from rhodecode.model.db import (
56 56 or_, coalesce,IntegrityError, User, UserGroup, UserIpMap, UserEmailMap,
57 57 UserApiKeys, UserSshKeys, RepoGroup)
58 58 from rhodecode.model.meta import Session
59 59
60 60 log = logging.getLogger(__name__)
61 61
62 62
63 63 class AdminUsersView(BaseAppView, DataGridAppView):
64 64
65 65 def load_default_context(self):
66 66 c = self._get_local_tmpl_context()
67 67 return c
68 68
69 69 @LoginRequired()
70 70 @HasPermissionAllDecorator('hg.admin')
71 71 @view_config(
72 72 route_name='users', request_method='GET',
73 73 renderer='rhodecode:templates/admin/users/users.mako')
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 @view_config(
81 81 # renderer defined below
82 82 route_name='users_data', request_method='GET',
83 83 renderer='json_ext', xhr=True)
84 84 def users_list_data(self):
85 85 self.load_default_context()
86 86 column_map = {
87 87 'first_name': 'name',
88 88 'last_name': 'lastname',
89 89 }
90 90 draw, start, limit = self._extract_chunk(self.request)
91 91 search_q, order_by, order_dir = self._extract_ordering(
92 92 self.request, column_map=column_map)
93 93 _render = self.request.get_partial_renderer(
94 94 'rhodecode:templates/data_table/_dt_elements.mako')
95 95
96 96 def user_actions(user_id, username):
97 97 return _render("user_actions", user_id, username)
98 98
99 99 users_data_total_count = User.query()\
100 100 .filter(User.username != User.DEFAULT_USER) \
101 101 .count()
102 102
103 103 users_data_total_inactive_count = User.query()\
104 104 .filter(User.username != User.DEFAULT_USER) \
105 105 .filter(User.active != true())\
106 106 .count()
107 107
108 108 # json generate
109 109 base_q = User.query().filter(User.username != User.DEFAULT_USER)
110 110 base_inactive_q = base_q.filter(User.active != true())
111 111
112 112 if search_q:
113 113 like_expression = u'%{}%'.format(safe_unicode(search_q))
114 114 base_q = base_q.filter(or_(
115 115 User.username.ilike(like_expression),
116 116 User._email.ilike(like_expression),
117 117 User.name.ilike(like_expression),
118 118 User.lastname.ilike(like_expression),
119 119 ))
120 120 base_inactive_q = base_q.filter(User.active != true())
121 121
122 122 users_data_total_filtered_count = base_q.count()
123 123 users_data_total_filtered_inactive_count = base_inactive_q.count()
124 124
125 125 sort_col = getattr(User, order_by, None)
126 126 if sort_col:
127 127 if order_dir == 'asc':
128 128 # handle null values properly to order by NULL last
129 129 if order_by in ['last_activity']:
130 130 sort_col = coalesce(sort_col, datetime.date.max)
131 131 sort_col = sort_col.asc()
132 132 else:
133 133 # handle null values properly to order by NULL last
134 134 if order_by in ['last_activity']:
135 135 sort_col = coalesce(sort_col, datetime.date.min)
136 136 sort_col = sort_col.desc()
137 137
138 138 base_q = base_q.order_by(sort_col)
139 139 base_q = base_q.offset(start).limit(limit)
140 140
141 141 users_list = base_q.all()
142 142
143 143 users_data = []
144 144 for user in users_list:
145 145 users_data.append({
146 146 "username": h.gravatar_with_user(self.request, user.username),
147 147 "email": user.email,
148 148 "first_name": user.first_name,
149 149 "last_name": user.last_name,
150 150 "last_login": h.format_date(user.last_login),
151 151 "last_activity": h.format_date(user.last_activity),
152 152 "active": h.bool2icon(user.active),
153 153 "active_raw": user.active,
154 154 "admin": h.bool2icon(user.admin),
155 155 "extern_type": user.extern_type,
156 156 "extern_name": user.extern_name,
157 157 "action": user_actions(user.user_id, user.username),
158 158 })
159 159 data = ({
160 160 'draw': draw,
161 161 'data': users_data,
162 162 'recordsTotal': users_data_total_count,
163 163 'recordsFiltered': users_data_total_filtered_count,
164 164 'recordsTotalInactive': users_data_total_inactive_count,
165 165 'recordsFilteredInactive': users_data_total_filtered_inactive_count
166 166 })
167 167
168 168 return data
169 169
170 170 def _set_personal_repo_group_template_vars(self, c_obj):
171 171 DummyUser = AttributeDict({
172 172 'username': '${username}',
173 173 'user_id': '${user_id}',
174 174 })
175 175 c_obj.default_create_repo_group = RepoGroupModel() \
176 176 .get_default_create_personal_repo_group()
177 177 c_obj.personal_repo_group_name = RepoGroupModel() \
178 178 .get_personal_group_name(DummyUser)
179 179
180 180 @LoginRequired()
181 181 @HasPermissionAllDecorator('hg.admin')
182 182 @view_config(
183 183 route_name='users_new', request_method='GET',
184 184 renderer='rhodecode:templates/admin/users/user_add.mako')
185 185 def users_new(self):
186 186 _ = self.request.translate
187 187 c = self.load_default_context()
188 188 c.default_extern_type = auth_rhodecode.RhodeCodeAuthPlugin.name
189 189 self._set_personal_repo_group_template_vars(c)
190 190 return self._get_template_context(c)
191 191
192 192 @LoginRequired()
193 193 @HasPermissionAllDecorator('hg.admin')
194 194 @CSRFRequired()
195 195 @view_config(
196 196 route_name='users_create', request_method='POST',
197 197 renderer='rhodecode:templates/admin/users/user_add.mako')
198 198 def users_create(self):
199 199 _ = self.request.translate
200 200 c = self.load_default_context()
201 201 c.default_extern_type = auth_rhodecode.RhodeCodeAuthPlugin.name
202 202 user_model = UserModel()
203 203 user_form = UserForm(self.request.translate)()
204 204 try:
205 205 form_result = user_form.to_python(dict(self.request.POST))
206 206 user = user_model.create(form_result)
207 207 Session().flush()
208 208 creation_data = user.get_api_data()
209 209 username = form_result['username']
210 210
211 211 audit_logger.store_web(
212 212 'user.create', action_data={'data': creation_data},
213 213 user=c.rhodecode_user)
214 214
215 215 user_link = h.link_to(
216 216 h.escape(username),
217 217 h.route_path('user_edit', user_id=user.user_id))
218 218 h.flash(h.literal(_('Created user %(user_link)s')
219 219 % {'user_link': user_link}), category='success')
220 220 Session().commit()
221 221 except formencode.Invalid as errors:
222 222 self._set_personal_repo_group_template_vars(c)
223 223 data = render(
224 224 'rhodecode:templates/admin/users/user_add.mako',
225 225 self._get_template_context(c), self.request)
226 226 html = formencode.htmlfill.render(
227 227 data,
228 228 defaults=errors.value,
229 229 errors=errors.error_dict or {},
230 230 prefix_error=False,
231 231 encoding="UTF-8",
232 232 force_defaults=False
233 233 )
234 234 return Response(html)
235 235 except UserCreationError as e:
236 236 h.flash(e, 'error')
237 237 except Exception:
238 238 log.exception("Exception creation of user")
239 239 h.flash(_('Error occurred during creation of user %s')
240 240 % self.request.POST.get('username'), category='error')
241 241 raise HTTPFound(h.route_path('users'))
242 242
243 243
244 244 class UsersView(UserAppView):
245 245 ALLOW_SCOPED_TOKENS = False
246 246 """
247 247 This view has alternative version inside EE, if modified please take a look
248 248 in there as well.
249 249 """
250 250
251 251 def load_default_context(self):
252 252 c = self._get_local_tmpl_context()
253 253 c.allow_scoped_tokens = self.ALLOW_SCOPED_TOKENS
254 254 c.allowed_languages = [
255 255 ('en', 'English (en)'),
256 256 ('de', 'German (de)'),
257 257 ('fr', 'French (fr)'),
258 258 ('it', 'Italian (it)'),
259 259 ('ja', 'Japanese (ja)'),
260 260 ('pl', 'Polish (pl)'),
261 261 ('pt', 'Portuguese (pt)'),
262 262 ('ru', 'Russian (ru)'),
263 263 ('zh', 'Chinese (zh)'),
264 264 ]
265 265 req = self.request
266 266
267 267 c.available_permissions = req.registry.settings['available_permissions']
268 268 PermissionModel().set_global_permission_choices(
269 269 c, gettext_translator=req.translate)
270 270
271 271 return c
272 272
273 273 @LoginRequired()
274 274 @HasPermissionAllDecorator('hg.admin')
275 275 @CSRFRequired()
276 276 @view_config(
277 277 route_name='user_update', request_method='POST',
278 278 renderer='rhodecode:templates/admin/users/user_edit.mako')
279 279 def user_update(self):
280 280 _ = self.request.translate
281 281 c = self.load_default_context()
282 282
283 283 user_id = self.db_user_id
284 284 c.user = self.db_user
285 285
286 286 c.active = 'profile'
287 287 c.extern_type = c.user.extern_type
288 288 c.extern_name = c.user.extern_name
289 289 c.perm_user = c.user.AuthUser(ip_addr=self.request.remote_addr)
290 290 available_languages = [x[0] for x in c.allowed_languages]
291 291 _form = UserForm(self.request.translate, edit=True,
292 292 available_languages=available_languages,
293 293 old_data={'user_id': user_id,
294 294 'email': c.user.email})()
295 295 form_result = {}
296 296 old_values = c.user.get_api_data()
297 297 try:
298 298 form_result = _form.to_python(dict(self.request.POST))
299 299 skip_attrs = ['extern_type', 'extern_name']
300 300 # TODO: plugin should define if username can be updated
301 301 if c.extern_type != "rhodecode":
302 302 # forbid updating username for external accounts
303 303 skip_attrs.append('username')
304 304
305 305 UserModel().update_user(
306 306 user_id, skip_attrs=skip_attrs, **form_result)
307 307
308 308 audit_logger.store_web(
309 309 'user.edit', action_data={'old_data': old_values},
310 310 user=c.rhodecode_user)
311 311
312 312 Session().commit()
313 313 h.flash(_('User updated successfully'), category='success')
314 314 except formencode.Invalid as errors:
315 315 data = render(
316 316 'rhodecode:templates/admin/users/user_edit.mako',
317 317 self._get_template_context(c), self.request)
318 318 html = formencode.htmlfill.render(
319 319 data,
320 320 defaults=errors.value,
321 321 errors=errors.error_dict or {},
322 322 prefix_error=False,
323 323 encoding="UTF-8",
324 324 force_defaults=False
325 325 )
326 326 return Response(html)
327 327 except UserCreationError as e:
328 328 h.flash(e, 'error')
329 329 except Exception:
330 330 log.exception("Exception updating user")
331 331 h.flash(_('Error occurred during update of user %s')
332 332 % form_result.get('username'), category='error')
333 333 raise HTTPFound(h.route_path('user_edit', user_id=user_id))
334 334
335 335 @LoginRequired()
336 336 @HasPermissionAllDecorator('hg.admin')
337 337 @CSRFRequired()
338 338 @view_config(
339 339 route_name='user_delete', request_method='POST',
340 340 renderer='rhodecode:templates/admin/users/user_edit.mako')
341 341 def user_delete(self):
342 342 _ = self.request.translate
343 343 c = self.load_default_context()
344 344 c.user = self.db_user
345 345
346 346 _repos = c.user.repositories
347 347 _repo_groups = c.user.repository_groups
348 348 _user_groups = c.user.user_groups
349 349
350 350 handle_repos = None
351 351 handle_repo_groups = None
352 352 handle_user_groups = None
353 353 # dummy call for flash of handle
354 354 set_handle_flash_repos = lambda: None
355 355 set_handle_flash_repo_groups = lambda: None
356 356 set_handle_flash_user_groups = lambda: None
357 357
358 358 if _repos and self.request.POST.get('user_repos'):
359 359 do = self.request.POST['user_repos']
360 360 if do == 'detach':
361 361 handle_repos = 'detach'
362 362 set_handle_flash_repos = lambda: h.flash(
363 363 _('Detached %s repositories') % len(_repos),
364 364 category='success')
365 365 elif do == 'delete':
366 366 handle_repos = 'delete'
367 367 set_handle_flash_repos = lambda: h.flash(
368 368 _('Deleted %s repositories') % len(_repos),
369 369 category='success')
370 370
371 371 if _repo_groups and self.request.POST.get('user_repo_groups'):
372 372 do = self.request.POST['user_repo_groups']
373 373 if do == 'detach':
374 374 handle_repo_groups = 'detach'
375 375 set_handle_flash_repo_groups = lambda: h.flash(
376 376 _('Detached %s repository groups') % len(_repo_groups),
377 377 category='success')
378 378 elif do == 'delete':
379 379 handle_repo_groups = 'delete'
380 380 set_handle_flash_repo_groups = lambda: h.flash(
381 381 _('Deleted %s repository groups') % len(_repo_groups),
382 382 category='success')
383 383
384 384 if _user_groups and self.request.POST.get('user_user_groups'):
385 385 do = self.request.POST['user_user_groups']
386 386 if do == 'detach':
387 387 handle_user_groups = 'detach'
388 388 set_handle_flash_user_groups = lambda: h.flash(
389 389 _('Detached %s user groups') % len(_user_groups),
390 390 category='success')
391 391 elif do == 'delete':
392 392 handle_user_groups = 'delete'
393 393 set_handle_flash_user_groups = lambda: h.flash(
394 394 _('Deleted %s user groups') % len(_user_groups),
395 395 category='success')
396 396
397 397 old_values = c.user.get_api_data()
398 398 try:
399 399 UserModel().delete(c.user, handle_repos=handle_repos,
400 400 handle_repo_groups=handle_repo_groups,
401 401 handle_user_groups=handle_user_groups)
402 402
403 403 audit_logger.store_web(
404 404 'user.delete', action_data={'old_data': old_values},
405 405 user=c.rhodecode_user)
406 406
407 407 Session().commit()
408 408 set_handle_flash_repos()
409 409 set_handle_flash_repo_groups()
410 410 set_handle_flash_user_groups()
411 411 h.flash(_('Successfully deleted user'), category='success')
412 412 except (UserOwnsReposException, UserOwnsRepoGroupsException,
413 413 UserOwnsUserGroupsException, DefaultUserException) as e:
414 414 h.flash(e, category='warning')
415 415 except Exception:
416 416 log.exception("Exception during deletion of user")
417 417 h.flash(_('An error occurred during deletion of user'),
418 418 category='error')
419 419 raise HTTPFound(h.route_path('users'))
420 420
421 421 @LoginRequired()
422 422 @HasPermissionAllDecorator('hg.admin')
423 423 @view_config(
424 424 route_name='user_edit', request_method='GET',
425 425 renderer='rhodecode:templates/admin/users/user_edit.mako')
426 426 def user_edit(self):
427 427 _ = self.request.translate
428 428 c = self.load_default_context()
429 429 c.user = self.db_user
430 430
431 431 c.active = 'profile'
432 432 c.extern_type = c.user.extern_type
433 433 c.extern_name = c.user.extern_name
434 434 c.perm_user = c.user.AuthUser(ip_addr=self.request.remote_addr)
435 435
436 436 defaults = c.user.get_dict()
437 437 defaults.update({'language': c.user.user_data.get('language')})
438 438
439 439 data = render(
440 440 'rhodecode:templates/admin/users/user_edit.mako',
441 441 self._get_template_context(c), self.request)
442 442 html = formencode.htmlfill.render(
443 443 data,
444 444 defaults=defaults,
445 445 encoding="UTF-8",
446 446 force_defaults=False
447 447 )
448 448 return Response(html)
449 449
450 450 @LoginRequired()
451 451 @HasPermissionAllDecorator('hg.admin')
452 452 @view_config(
453 453 route_name='user_edit_advanced', request_method='GET',
454 454 renderer='rhodecode:templates/admin/users/user_edit.mako')
455 455 def user_edit_advanced(self):
456 456 _ = self.request.translate
457 457 c = self.load_default_context()
458 458
459 459 user_id = self.db_user_id
460 460 c.user = self.db_user
461 461
462 462 c.active = 'advanced'
463 463 c.personal_repo_group = RepoGroup.get_user_personal_repo_group(user_id)
464 464 c.personal_repo_group_name = RepoGroupModel()\
465 465 .get_personal_group_name(c.user)
466 466
467 467 c.user_to_review_rules = sorted(
468 468 (x.user for x in c.user.user_review_rules),
469 469 key=lambda u: u.username.lower())
470 470
471 471 c.first_admin = User.get_first_super_admin()
472 472 defaults = c.user.get_dict()
473 473
474 474 # Interim workaround if the user participated on any pull requests as a
475 475 # reviewer.
476 476 has_review = len(c.user.reviewer_pull_requests)
477 477 c.can_delete_user = not has_review
478 478 c.can_delete_user_message = ''
479 479 inactive_link = h.link_to(
480 480 'inactive', h.route_path('user_edit', user_id=user_id, _anchor='active'))
481 481 if has_review == 1:
482 482 c.can_delete_user_message = h.literal(_(
483 483 'The user participates as reviewer in {} pull request and '
484 484 'cannot be deleted. \nYou can set the user to '
485 485 '"{}" instead of deleting it.').format(
486 486 has_review, inactive_link))
487 487 elif has_review:
488 488 c.can_delete_user_message = h.literal(_(
489 489 'The user participates as reviewer in {} pull requests and '
490 490 'cannot be deleted. \nYou can set the user to '
491 491 '"{}" instead of deleting it.').format(
492 492 has_review, inactive_link))
493 493
494 494 data = render(
495 495 'rhodecode:templates/admin/users/user_edit.mako',
496 496 self._get_template_context(c), self.request)
497 497 html = formencode.htmlfill.render(
498 498 data,
499 499 defaults=defaults,
500 500 encoding="UTF-8",
501 501 force_defaults=False
502 502 )
503 503 return Response(html)
504 504
505 505 @LoginRequired()
506 506 @HasPermissionAllDecorator('hg.admin')
507 507 @view_config(
508 508 route_name='user_edit_global_perms', request_method='GET',
509 509 renderer='rhodecode:templates/admin/users/user_edit.mako')
510 510 def user_edit_global_perms(self):
511 511 _ = self.request.translate
512 512 c = self.load_default_context()
513 513 c.user = self.db_user
514 514
515 515 c.active = 'global_perms'
516 516
517 517 c.default_user = User.get_default_user()
518 518 defaults = c.user.get_dict()
519 519 defaults.update(c.default_user.get_default_perms(suffix='_inherited'))
520 520 defaults.update(c.default_user.get_default_perms())
521 521 defaults.update(c.user.get_default_perms())
522 522
523 523 data = render(
524 524 'rhodecode:templates/admin/users/user_edit.mako',
525 525 self._get_template_context(c), self.request)
526 526 html = formencode.htmlfill.render(
527 527 data,
528 528 defaults=defaults,
529 529 encoding="UTF-8",
530 530 force_defaults=False
531 531 )
532 532 return Response(html)
533 533
534 534 @LoginRequired()
535 535 @HasPermissionAllDecorator('hg.admin')
536 536 @CSRFRequired()
537 537 @view_config(
538 538 route_name='user_edit_global_perms_update', request_method='POST',
539 539 renderer='rhodecode:templates/admin/users/user_edit.mako')
540 540 def user_edit_global_perms_update(self):
541 541 _ = self.request.translate
542 542 c = self.load_default_context()
543 543
544 544 user_id = self.db_user_id
545 545 c.user = self.db_user
546 546
547 547 c.active = 'global_perms'
548 548 try:
549 549 # first stage that verifies the checkbox
550 550 _form = UserIndividualPermissionsForm(self.request.translate)
551 551 form_result = _form.to_python(dict(self.request.POST))
552 552 inherit_perms = form_result['inherit_default_permissions']
553 553 c.user.inherit_default_permissions = inherit_perms
554 554 Session().add(c.user)
555 555
556 556 if not inherit_perms:
557 557 # only update the individual ones if we un check the flag
558 558 _form = UserPermissionsForm(
559 559 self.request.translate,
560 560 [x[0] for x in c.repo_create_choices],
561 561 [x[0] for x in c.repo_create_on_write_choices],
562 562 [x[0] for x in c.repo_group_create_choices],
563 563 [x[0] for x in c.user_group_create_choices],
564 564 [x[0] for x in c.fork_choices],
565 565 [x[0] for x in c.inherit_default_permission_choices])()
566 566
567 567 form_result = _form.to_python(dict(self.request.POST))
568 568 form_result.update({'perm_user_id': c.user.user_id})
569 569
570 570 PermissionModel().update_user_permissions(form_result)
571 571
572 572 # TODO(marcink): implement global permissions
573 573 # audit_log.store_web('user.edit.permissions')
574 574
575 575 Session().commit()
576 576 h.flash(_('User global permissions updated successfully'),
577 577 category='success')
578 578
579 579 except formencode.Invalid as errors:
580 580 data = render(
581 581 'rhodecode:templates/admin/users/user_edit.mako',
582 582 self._get_template_context(c), self.request)
583 583 html = formencode.htmlfill.render(
584 584 data,
585 585 defaults=errors.value,
586 586 errors=errors.error_dict or {},
587 587 prefix_error=False,
588 588 encoding="UTF-8",
589 589 force_defaults=False
590 590 )
591 591 return Response(html)
592 592 except Exception:
593 593 log.exception("Exception during permissions saving")
594 594 h.flash(_('An error occurred during permissions saving'),
595 595 category='error')
596 596 raise HTTPFound(h.route_path('user_edit_global_perms', user_id=user_id))
597 597
598 598 @LoginRequired()
599 599 @HasPermissionAllDecorator('hg.admin')
600 600 @CSRFRequired()
601 601 @view_config(
602 602 route_name='user_force_password_reset', request_method='POST',
603 603 renderer='rhodecode:templates/admin/users/user_edit.mako')
604 604 def user_force_password_reset(self):
605 605 """
606 606 toggle reset password flag for this user
607 607 """
608 608 _ = self.request.translate
609 609 c = self.load_default_context()
610 610
611 611 user_id = self.db_user_id
612 612 c.user = self.db_user
613 613
614 614 try:
615 615 old_value = c.user.user_data.get('force_password_change')
616 616 c.user.update_userdata(force_password_change=not old_value)
617 617
618 618 if old_value:
619 619 msg = _('Force password change disabled for user')
620 620 audit_logger.store_web(
621 621 'user.edit.password_reset.disabled',
622 622 user=c.rhodecode_user)
623 623 else:
624 624 msg = _('Force password change enabled for user')
625 625 audit_logger.store_web(
626 626 'user.edit.password_reset.enabled',
627 627 user=c.rhodecode_user)
628 628
629 629 Session().commit()
630 630 h.flash(msg, category='success')
631 631 except Exception:
632 632 log.exception("Exception during password reset for user")
633 633 h.flash(_('An error occurred during password reset for user'),
634 634 category='error')
635 635
636 636 raise HTTPFound(h.route_path('user_edit_advanced', user_id=user_id))
637 637
638 638 @LoginRequired()
639 639 @HasPermissionAllDecorator('hg.admin')
640 640 @CSRFRequired()
641 641 @view_config(
642 642 route_name='user_create_personal_repo_group', request_method='POST',
643 643 renderer='rhodecode:templates/admin/users/user_edit.mako')
644 644 def user_create_personal_repo_group(self):
645 645 """
646 646 Create personal repository group for this user
647 647 """
648 648 from rhodecode.model.repo_group import RepoGroupModel
649 649
650 650 _ = self.request.translate
651 651 c = self.load_default_context()
652 652
653 653 user_id = self.db_user_id
654 654 c.user = self.db_user
655 655
656 656 personal_repo_group = RepoGroup.get_user_personal_repo_group(
657 657 c.user.user_id)
658 658 if personal_repo_group:
659 659 raise HTTPFound(h.route_path('user_edit_advanced', user_id=user_id))
660 660
661 661 personal_repo_group_name = RepoGroupModel().get_personal_group_name(
662 662 c.user)
663 663 named_personal_group = RepoGroup.get_by_group_name(
664 664 personal_repo_group_name)
665 665 try:
666 666
667 667 if named_personal_group and named_personal_group.user_id == c.user.user_id:
668 668 # migrate the same named group, and mark it as personal
669 669 named_personal_group.personal = True
670 670 Session().add(named_personal_group)
671 671 Session().commit()
672 672 msg = _('Linked repository group `%s` as personal' % (
673 673 personal_repo_group_name,))
674 674 h.flash(msg, category='success')
675 675 elif not named_personal_group:
676 676 RepoGroupModel().create_personal_repo_group(c.user)
677 677
678 678 msg = _('Created repository group `%s`' % (
679 679 personal_repo_group_name,))
680 680 h.flash(msg, category='success')
681 681 else:
682 682 msg = _('Repository group `%s` is already taken' % (
683 683 personal_repo_group_name,))
684 684 h.flash(msg, category='warning')
685 685 except Exception:
686 686 log.exception("Exception during repository group creation")
687 687 msg = _(
688 688 'An error occurred during repository group creation for user')
689 689 h.flash(msg, category='error')
690 690 Session().rollback()
691 691
692 692 raise HTTPFound(h.route_path('user_edit_advanced', user_id=user_id))
693 693
694 694 @LoginRequired()
695 695 @HasPermissionAllDecorator('hg.admin')
696 696 @view_config(
697 697 route_name='edit_user_auth_tokens', request_method='GET',
698 698 renderer='rhodecode:templates/admin/users/user_edit.mako')
699 699 def auth_tokens(self):
700 700 _ = self.request.translate
701 701 c = self.load_default_context()
702 702 c.user = self.db_user
703 703
704 704 c.active = 'auth_tokens'
705 705
706 706 c.lifetime_values = AuthTokenModel.get_lifetime_values(translator=_)
707 707 c.role_values = [
708 708 (x, AuthTokenModel.cls._get_role_name(x))
709 709 for x in AuthTokenModel.cls.ROLES]
710 710 c.role_options = [(c.role_values, _("Role"))]
711 711 c.user_auth_tokens = AuthTokenModel().get_auth_tokens(
712 712 c.user.user_id, show_expired=True)
713 713 c.role_vcs = AuthTokenModel.cls.ROLE_VCS
714 714 return self._get_template_context(c)
715 715
716 716 def maybe_attach_token_scope(self, token):
717 717 # implemented in EE edition
718 718 pass
719 719
720 720 @LoginRequired()
721 721 @HasPermissionAllDecorator('hg.admin')
722 722 @CSRFRequired()
723 723 @view_config(
724 724 route_name='edit_user_auth_tokens_add', request_method='POST')
725 725 def auth_tokens_add(self):
726 726 _ = self.request.translate
727 727 c = self.load_default_context()
728 728
729 729 user_id = self.db_user_id
730 730 c.user = self.db_user
731 731
732 732 user_data = c.user.get_api_data()
733 733 lifetime = safe_int(self.request.POST.get('lifetime'), -1)
734 734 description = self.request.POST.get('description')
735 735 role = self.request.POST.get('role')
736 736
737 token = AuthTokenModel().create(
738 c.user.user_id, description, lifetime, role)
737 token = UserModel().add_auth_token(
738 user=c.user.user_id,
739 lifetime_minutes=lifetime, role=role, description=description,
740 scope_callback=self.maybe_attach_token_scope)
739 741 token_data = token.get_api_data()
740 742
741 self.maybe_attach_token_scope(token)
742 743 audit_logger.store_web(
743 744 'user.edit.token.add', action_data={
744 745 'data': {'token': token_data, 'user': user_data}},
745 746 user=self._rhodecode_user, )
746 747 Session().commit()
747 748
748 749 h.flash(_("Auth token successfully created"), category='success')
749 750 return HTTPFound(h.route_path('edit_user_auth_tokens', user_id=user_id))
750 751
751 752 @LoginRequired()
752 753 @HasPermissionAllDecorator('hg.admin')
753 754 @CSRFRequired()
754 755 @view_config(
755 756 route_name='edit_user_auth_tokens_delete', request_method='POST')
756 757 def auth_tokens_delete(self):
757 758 _ = self.request.translate
758 759 c = self.load_default_context()
759 760
760 761 user_id = self.db_user_id
761 762 c.user = self.db_user
762 763
763 764 user_data = c.user.get_api_data()
764 765
765 766 del_auth_token = self.request.POST.get('del_auth_token')
766 767
767 768 if del_auth_token:
768 769 token = UserApiKeys.get_or_404(del_auth_token)
769 770 token_data = token.get_api_data()
770 771
771 772 AuthTokenModel().delete(del_auth_token, c.user.user_id)
772 773 audit_logger.store_web(
773 774 'user.edit.token.delete', action_data={
774 775 'data': {'token': token_data, 'user': user_data}},
775 776 user=self._rhodecode_user,)
776 777 Session().commit()
777 778 h.flash(_("Auth token successfully deleted"), category='success')
778 779
779 780 return HTTPFound(h.route_path('edit_user_auth_tokens', user_id=user_id))
780 781
781 782 @LoginRequired()
782 783 @HasPermissionAllDecorator('hg.admin')
783 784 @view_config(
784 785 route_name='edit_user_ssh_keys', request_method='GET',
785 786 renderer='rhodecode:templates/admin/users/user_edit.mako')
786 787 def ssh_keys(self):
787 788 _ = self.request.translate
788 789 c = self.load_default_context()
789 790 c.user = self.db_user
790 791
791 792 c.active = 'ssh_keys'
792 793 c.default_key = self.request.GET.get('default_key')
793 794 c.user_ssh_keys = SshKeyModel().get_ssh_keys(c.user.user_id)
794 795 return self._get_template_context(c)
795 796
796 797 @LoginRequired()
797 798 @HasPermissionAllDecorator('hg.admin')
798 799 @view_config(
799 800 route_name='edit_user_ssh_keys_generate_keypair', request_method='GET',
800 801 renderer='rhodecode:templates/admin/users/user_edit.mako')
801 802 def ssh_keys_generate_keypair(self):
802 803 _ = self.request.translate
803 804 c = self.load_default_context()
804 805
805 806 c.user = self.db_user
806 807
807 808 c.active = 'ssh_keys_generate'
808 809 comment = 'RhodeCode-SSH {}'.format(c.user.email or '')
809 810 c.private, c.public = SshKeyModel().generate_keypair(comment=comment)
810 811
811 812 return self._get_template_context(c)
812 813
813 814 @LoginRequired()
814 815 @HasPermissionAllDecorator('hg.admin')
815 816 @CSRFRequired()
816 817 @view_config(
817 818 route_name='edit_user_ssh_keys_add', request_method='POST')
818 819 def ssh_keys_add(self):
819 820 _ = self.request.translate
820 821 c = self.load_default_context()
821 822
822 823 user_id = self.db_user_id
823 824 c.user = self.db_user
824 825
825 826 user_data = c.user.get_api_data()
826 827 key_data = self.request.POST.get('key_data')
827 828 description = self.request.POST.get('description')
828 829
829 830 fingerprint = 'unknown'
830 831 try:
831 832 if not key_data:
832 833 raise ValueError('Please add a valid public key')
833 834
834 835 key = SshKeyModel().parse_key(key_data.strip())
835 836 fingerprint = key.hash_md5()
836 837
837 838 ssh_key = SshKeyModel().create(
838 839 c.user.user_id, fingerprint, key.keydata, description)
839 840 ssh_key_data = ssh_key.get_api_data()
840 841
841 842 audit_logger.store_web(
842 843 'user.edit.ssh_key.add', action_data={
843 844 'data': {'ssh_key': ssh_key_data, 'user': user_data}},
844 845 user=self._rhodecode_user, )
845 846 Session().commit()
846 847
847 848 # Trigger an event on change of keys.
848 849 trigger(SshKeyFileChangeEvent(), self.request.registry)
849 850
850 851 h.flash(_("Ssh Key successfully created"), category='success')
851 852
852 853 except IntegrityError:
853 854 log.exception("Exception during ssh key saving")
854 855 err = 'Such key with fingerprint `{}` already exists, ' \
855 856 'please use a different one'.format(fingerprint)
856 857 h.flash(_('An error occurred during ssh key saving: {}').format(err),
857 858 category='error')
858 859 except Exception as e:
859 860 log.exception("Exception during ssh key saving")
860 861 h.flash(_('An error occurred during ssh key saving: {}').format(e),
861 862 category='error')
862 863
863 864 return HTTPFound(
864 865 h.route_path('edit_user_ssh_keys', user_id=user_id))
865 866
866 867 @LoginRequired()
867 868 @HasPermissionAllDecorator('hg.admin')
868 869 @CSRFRequired()
869 870 @view_config(
870 871 route_name='edit_user_ssh_keys_delete', request_method='POST')
871 872 def ssh_keys_delete(self):
872 873 _ = self.request.translate
873 874 c = self.load_default_context()
874 875
875 876 user_id = self.db_user_id
876 877 c.user = self.db_user
877 878
878 879 user_data = c.user.get_api_data()
879 880
880 881 del_ssh_key = self.request.POST.get('del_ssh_key')
881 882
882 883 if del_ssh_key:
883 884 ssh_key = UserSshKeys.get_or_404(del_ssh_key)
884 885 ssh_key_data = ssh_key.get_api_data()
885 886
886 887 SshKeyModel().delete(del_ssh_key, c.user.user_id)
887 888 audit_logger.store_web(
888 889 'user.edit.ssh_key.delete', action_data={
889 890 'data': {'ssh_key': ssh_key_data, 'user': user_data}},
890 891 user=self._rhodecode_user,)
891 892 Session().commit()
892 893 # Trigger an event on change of keys.
893 894 trigger(SshKeyFileChangeEvent(), self.request.registry)
894 895 h.flash(_("Ssh key successfully deleted"), category='success')
895 896
896 897 return HTTPFound(h.route_path('edit_user_ssh_keys', user_id=user_id))
897 898
898 899 @LoginRequired()
899 900 @HasPermissionAllDecorator('hg.admin')
900 901 @view_config(
901 902 route_name='edit_user_emails', request_method='GET',
902 903 renderer='rhodecode:templates/admin/users/user_edit.mako')
903 904 def emails(self):
904 905 _ = self.request.translate
905 906 c = self.load_default_context()
906 907 c.user = self.db_user
907 908
908 909 c.active = 'emails'
909 910 c.user_email_map = UserEmailMap.query() \
910 911 .filter(UserEmailMap.user == c.user).all()
911 912
912 913 return self._get_template_context(c)
913 914
914 915 @LoginRequired()
915 916 @HasPermissionAllDecorator('hg.admin')
916 917 @CSRFRequired()
917 918 @view_config(
918 919 route_name='edit_user_emails_add', request_method='POST')
919 920 def emails_add(self):
920 921 _ = self.request.translate
921 922 c = self.load_default_context()
922 923
923 924 user_id = self.db_user_id
924 925 c.user = self.db_user
925 926
926 927 email = self.request.POST.get('new_email')
927 928 user_data = c.user.get_api_data()
928 929 try:
929 930
930 931 form = UserExtraEmailForm(self.request.translate)()
931 932 data = form.to_python({'email': email})
932 933 email = data['email']
933 934
934 935 UserModel().add_extra_email(c.user.user_id, email)
935 936 audit_logger.store_web(
936 937 'user.edit.email.add',
937 938 action_data={'email': email, 'user': user_data},
938 939 user=self._rhodecode_user)
939 940 Session().commit()
940 941 h.flash(_("Added new email address `%s` for user account") % email,
941 942 category='success')
942 943 except formencode.Invalid as error:
943 944 h.flash(h.escape(error.error_dict['email']), category='error')
944 945 except IntegrityError:
945 946 log.warning("Email %s already exists", email)
946 947 h.flash(_('Email `{}` is already registered for another user.').format(email),
947 948 category='error')
948 949 except Exception:
949 950 log.exception("Exception during email saving")
950 951 h.flash(_('An error occurred during email saving'),
951 952 category='error')
952 953 raise HTTPFound(h.route_path('edit_user_emails', user_id=user_id))
953 954
954 955 @LoginRequired()
955 956 @HasPermissionAllDecorator('hg.admin')
956 957 @CSRFRequired()
957 958 @view_config(
958 959 route_name='edit_user_emails_delete', request_method='POST')
959 960 def emails_delete(self):
960 961 _ = self.request.translate
961 962 c = self.load_default_context()
962 963
963 964 user_id = self.db_user_id
964 965 c.user = self.db_user
965 966
966 967 email_id = self.request.POST.get('del_email_id')
967 968 user_model = UserModel()
968 969
969 970 email = UserEmailMap.query().get(email_id).email
970 971 user_data = c.user.get_api_data()
971 972 user_model.delete_extra_email(c.user.user_id, email_id)
972 973 audit_logger.store_web(
973 974 'user.edit.email.delete',
974 975 action_data={'email': email, 'user': user_data},
975 976 user=self._rhodecode_user)
976 977 Session().commit()
977 978 h.flash(_("Removed email address from user account"),
978 979 category='success')
979 980 raise HTTPFound(h.route_path('edit_user_emails', user_id=user_id))
980 981
981 982 @LoginRequired()
982 983 @HasPermissionAllDecorator('hg.admin')
983 984 @view_config(
984 985 route_name='edit_user_ips', request_method='GET',
985 986 renderer='rhodecode:templates/admin/users/user_edit.mako')
986 987 def ips(self):
987 988 _ = self.request.translate
988 989 c = self.load_default_context()
989 990 c.user = self.db_user
990 991
991 992 c.active = 'ips'
992 993 c.user_ip_map = UserIpMap.query() \
993 994 .filter(UserIpMap.user == c.user).all()
994 995
995 996 c.inherit_default_ips = c.user.inherit_default_permissions
996 997 c.default_user_ip_map = UserIpMap.query() \
997 998 .filter(UserIpMap.user == User.get_default_user()).all()
998 999
999 1000 return self._get_template_context(c)
1000 1001
1001 1002 @LoginRequired()
1002 1003 @HasPermissionAllDecorator('hg.admin')
1003 1004 @CSRFRequired()
1004 1005 @view_config(
1005 1006 route_name='edit_user_ips_add', request_method='POST')
1006 1007 # NOTE(marcink): this view is allowed for default users, as we can
1007 1008 # edit their IP white list
1008 1009 def ips_add(self):
1009 1010 _ = self.request.translate
1010 1011 c = self.load_default_context()
1011 1012
1012 1013 user_id = self.db_user_id
1013 1014 c.user = self.db_user
1014 1015
1015 1016 user_model = UserModel()
1016 1017 desc = self.request.POST.get('description')
1017 1018 try:
1018 1019 ip_list = user_model.parse_ip_range(
1019 1020 self.request.POST.get('new_ip'))
1020 1021 except Exception as e:
1021 1022 ip_list = []
1022 1023 log.exception("Exception during ip saving")
1023 1024 h.flash(_('An error occurred during ip saving:%s' % (e,)),
1024 1025 category='error')
1025 1026 added = []
1026 1027 user_data = c.user.get_api_data()
1027 1028 for ip in ip_list:
1028 1029 try:
1029 1030 form = UserExtraIpForm(self.request.translate)()
1030 1031 data = form.to_python({'ip': ip})
1031 1032 ip = data['ip']
1032 1033
1033 1034 user_model.add_extra_ip(c.user.user_id, ip, desc)
1034 1035 audit_logger.store_web(
1035 1036 'user.edit.ip.add',
1036 1037 action_data={'ip': ip, 'user': user_data},
1037 1038 user=self._rhodecode_user)
1038 1039 Session().commit()
1039 1040 added.append(ip)
1040 1041 except formencode.Invalid as error:
1041 1042 msg = error.error_dict['ip']
1042 1043 h.flash(msg, category='error')
1043 1044 except Exception:
1044 1045 log.exception("Exception during ip saving")
1045 1046 h.flash(_('An error occurred during ip saving'),
1046 1047 category='error')
1047 1048 if added:
1048 1049 h.flash(
1049 1050 _("Added ips %s to user whitelist") % (', '.join(ip_list), ),
1050 1051 category='success')
1051 1052 if 'default_user' in self.request.POST:
1052 1053 # case for editing global IP list we do it for 'DEFAULT' user
1053 1054 raise HTTPFound(h.route_path('admin_permissions_ips'))
1054 1055 raise HTTPFound(h.route_path('edit_user_ips', user_id=user_id))
1055 1056
1056 1057 @LoginRequired()
1057 1058 @HasPermissionAllDecorator('hg.admin')
1058 1059 @CSRFRequired()
1059 1060 @view_config(
1060 1061 route_name='edit_user_ips_delete', request_method='POST')
1061 1062 # NOTE(marcink): this view is allowed for default users, as we can
1062 1063 # edit their IP white list
1063 1064 def ips_delete(self):
1064 1065 _ = self.request.translate
1065 1066 c = self.load_default_context()
1066 1067
1067 1068 user_id = self.db_user_id
1068 1069 c.user = self.db_user
1069 1070
1070 1071 ip_id = self.request.POST.get('del_ip_id')
1071 1072 user_model = UserModel()
1072 1073 user_data = c.user.get_api_data()
1073 1074 ip = UserIpMap.query().get(ip_id).ip_addr
1074 1075 user_model.delete_extra_ip(c.user.user_id, ip_id)
1075 1076 audit_logger.store_web(
1076 1077 'user.edit.ip.delete', action_data={'ip': ip, 'user': user_data},
1077 1078 user=self._rhodecode_user)
1078 1079 Session().commit()
1079 1080 h.flash(_("Removed ip address from user whitelist"), category='success')
1080 1081
1081 1082 if 'default_user' in self.request.POST:
1082 1083 # case for editing global IP list we do it for 'DEFAULT' user
1083 1084 raise HTTPFound(h.route_path('admin_permissions_ips'))
1084 1085 raise HTTPFound(h.route_path('edit_user_ips', user_id=user_id))
1085 1086
1086 1087 @LoginRequired()
1087 1088 @HasPermissionAllDecorator('hg.admin')
1088 1089 @view_config(
1089 1090 route_name='edit_user_groups_management', request_method='GET',
1090 1091 renderer='rhodecode:templates/admin/users/user_edit.mako')
1091 1092 def groups_management(self):
1092 1093 c = self.load_default_context()
1093 1094 c.user = self.db_user
1094 1095 c.data = c.user.group_member
1095 1096
1096 1097 groups = [UserGroupModel.get_user_groups_as_dict(group.users_group)
1097 1098 for group in c.user.group_member]
1098 1099 c.groups = json.dumps(groups)
1099 1100 c.active = 'groups'
1100 1101
1101 1102 return self._get_template_context(c)
1102 1103
1103 1104 @LoginRequired()
1104 1105 @HasPermissionAllDecorator('hg.admin')
1105 1106 @CSRFRequired()
1106 1107 @view_config(
1107 1108 route_name='edit_user_groups_management_updates', request_method='POST')
1108 1109 def groups_management_updates(self):
1109 1110 _ = self.request.translate
1110 1111 c = self.load_default_context()
1111 1112
1112 1113 user_id = self.db_user_id
1113 1114 c.user = self.db_user
1114 1115
1115 1116 user_groups = set(self.request.POST.getall('users_group_id'))
1116 1117 user_groups_objects = []
1117 1118
1118 1119 for ugid in user_groups:
1119 1120 user_groups_objects.append(
1120 1121 UserGroupModel().get_group(safe_int(ugid)))
1121 1122 user_group_model = UserGroupModel()
1122 1123 added_to_groups, removed_from_groups = \
1123 1124 user_group_model.change_groups(c.user, user_groups_objects)
1124 1125
1125 1126 user_data = c.user.get_api_data()
1126 1127 for user_group_id in added_to_groups:
1127 1128 user_group = UserGroup.get(user_group_id)
1128 1129 old_values = user_group.get_api_data()
1129 1130 audit_logger.store_web(
1130 1131 'user_group.edit.member.add',
1131 1132 action_data={'user': user_data, 'old_data': old_values},
1132 1133 user=self._rhodecode_user)
1133 1134
1134 1135 for user_group_id in removed_from_groups:
1135 1136 user_group = UserGroup.get(user_group_id)
1136 1137 old_values = user_group.get_api_data()
1137 1138 audit_logger.store_web(
1138 1139 'user_group.edit.member.delete',
1139 1140 action_data={'user': user_data, 'old_data': old_values},
1140 1141 user=self._rhodecode_user)
1141 1142
1142 1143 Session().commit()
1143 1144 c.active = 'user_groups_management'
1144 1145 h.flash(_("Groups successfully changed"), category='success')
1145 1146
1146 1147 return HTTPFound(h.route_path(
1147 1148 'edit_user_groups_management', user_id=user_id))
1148 1149
1149 1150 @LoginRequired()
1150 1151 @HasPermissionAllDecorator('hg.admin')
1151 1152 @view_config(
1152 1153 route_name='edit_user_audit_logs', request_method='GET',
1153 1154 renderer='rhodecode:templates/admin/users/user_edit.mako')
1154 1155 def user_audit_logs(self):
1155 1156 _ = self.request.translate
1156 1157 c = self.load_default_context()
1157 1158 c.user = self.db_user
1158 1159
1159 1160 c.active = 'audit'
1160 1161
1161 1162 p = safe_int(self.request.GET.get('page', 1), 1)
1162 1163
1163 1164 filter_term = self.request.GET.get('filter')
1164 1165 user_log = UserModel().get_user_log(c.user, filter_term)
1165 1166
1166 1167 def url_generator(**kw):
1167 1168 if filter_term:
1168 1169 kw['filter'] = filter_term
1169 1170 return self.request.current_route_path(_query=kw)
1170 1171
1171 1172 c.audit_logs = h.Page(
1172 1173 user_log, page=p, items_per_page=10, url=url_generator)
1173 1174 c.filter_term = filter_term
1174 1175 return self._get_template_context(c)
1175 1176
1176 1177 @LoginRequired()
1177 1178 @HasPermissionAllDecorator('hg.admin')
1178 1179 @view_config(
1179 1180 route_name='edit_user_perms_summary', request_method='GET',
1180 1181 renderer='rhodecode:templates/admin/users/user_edit.mako')
1181 1182 def user_perms_summary(self):
1182 1183 _ = self.request.translate
1183 1184 c = self.load_default_context()
1184 1185 c.user = self.db_user
1185 1186
1186 1187 c.active = 'perms_summary'
1187 1188 c.perm_user = c.user.AuthUser(ip_addr=self.request.remote_addr)
1188 1189
1189 1190 return self._get_template_context(c)
1190 1191
1191 1192 @LoginRequired()
1192 1193 @HasPermissionAllDecorator('hg.admin')
1193 1194 @view_config(
1194 1195 route_name='edit_user_perms_summary_json', request_method='GET',
1195 1196 renderer='json_ext')
1196 1197 def user_perms_summary_json(self):
1197 1198 self.load_default_context()
1198 1199 perm_user = self.db_user.AuthUser(ip_addr=self.request.remote_addr)
1199 1200
1200 1201 return perm_user.permissions
1201 1202
1202 1203 @LoginRequired()
1203 1204 @HasPermissionAllDecorator('hg.admin')
1204 1205 @view_config(
1205 1206 route_name='edit_user_caches', request_method='GET',
1206 1207 renderer='rhodecode:templates/admin/users/user_edit.mako')
1207 1208 def user_caches(self):
1208 1209 _ = self.request.translate
1209 1210 c = self.load_default_context()
1210 1211 c.user = self.db_user
1211 1212
1212 1213 c.active = 'caches'
1213 1214 c.perm_user = c.user.AuthUser(ip_addr=self.request.remote_addr)
1214 1215
1215 1216 cache_namespace_uid = 'cache_user_auth.{}'.format(self.db_user.user_id)
1216 1217 c.region = rc_cache.get_or_create_region('cache_perms', cache_namespace_uid)
1217 1218 c.backend = c.region.backend
1218 1219 c.user_keys = sorted(c.region.backend.list_keys(prefix=cache_namespace_uid))
1219 1220
1220 1221 return self._get_template_context(c)
1221 1222
1222 1223 @LoginRequired()
1223 1224 @HasPermissionAllDecorator('hg.admin')
1224 1225 @CSRFRequired()
1225 1226 @view_config(
1226 1227 route_name='edit_user_caches_update', request_method='POST')
1227 1228 def user_caches_update(self):
1228 1229 _ = self.request.translate
1229 1230 c = self.load_default_context()
1230 1231 c.user = self.db_user
1231 1232
1232 1233 c.active = 'caches'
1233 1234 c.perm_user = c.user.AuthUser(ip_addr=self.request.remote_addr)
1234 1235
1235 1236 cache_namespace_uid = 'cache_user_auth.{}'.format(self.db_user.user_id)
1236 1237 del_keys = rc_cache.clear_cache_namespace('cache_perms', cache_namespace_uid)
1237 1238
1238 1239 h.flash(_("Deleted {} cache keys").format(del_keys), category='success')
1239 1240
1240 1241 return HTTPFound(h.route_path(
1241 1242 'edit_user_caches', user_id=c.user.user_id))
@@ -1,460 +1,461 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2016-2018 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 time
22 22 import collections
23 23 import datetime
24 24 import formencode
25 25 import formencode.htmlfill
26 26 import logging
27 27 import urlparse
28 28 import requests
29 29
30 30 from pyramid.httpexceptions import HTTPFound
31 31 from pyramid.view import view_config
32 32
33 33 from rhodecode.apps._base import BaseAppView
34 34 from rhodecode.authentication.base import authenticate, HTTP_TYPE
35 35 from rhodecode.events import UserRegistered, trigger
36 36 from rhodecode.lib import helpers as h
37 37 from rhodecode.lib import audit_logger
38 38 from rhodecode.lib.auth import (
39 39 AuthUser, HasPermissionAnyDecorator, CSRFRequired)
40 40 from rhodecode.lib.base import get_ip_addr
41 41 from rhodecode.lib.exceptions import UserCreationError
42 42 from rhodecode.lib.utils2 import safe_str
43 43 from rhodecode.model.db import User, UserApiKeys
44 44 from rhodecode.model.forms import LoginForm, RegisterForm, PasswordResetForm
45 45 from rhodecode.model.meta import Session
46 46 from rhodecode.model.auth_token import AuthTokenModel
47 47 from rhodecode.model.settings import SettingsModel
48 48 from rhodecode.model.user import UserModel
49 49 from rhodecode.translation import _
50 50
51 51
52 52 log = logging.getLogger(__name__)
53 53
54 54 CaptchaData = collections.namedtuple(
55 55 'CaptchaData', 'active, private_key, public_key')
56 56
57 57
58 58 def _store_user_in_session(session, username, remember=False):
59 59 user = User.get_by_username(username, case_insensitive=True)
60 60 auth_user = AuthUser(user.user_id)
61 61 auth_user.set_authenticated()
62 62 cs = auth_user.get_cookie_store()
63 63 session['rhodecode_user'] = cs
64 64 user.update_lastlogin()
65 65 Session().commit()
66 66
67 67 # If they want to be remembered, update the cookie
68 68 if remember:
69 69 _year = (datetime.datetime.now() +
70 70 datetime.timedelta(seconds=60 * 60 * 24 * 365))
71 71 session._set_cookie_expires(_year)
72 72
73 73 session.save()
74 74
75 75 safe_cs = cs.copy()
76 76 safe_cs['password'] = '****'
77 77 log.info('user %s is now authenticated and stored in '
78 78 'session, session attrs %s', username, safe_cs)
79 79
80 80 # dumps session attrs back to cookie
81 81 session._update_cookie_out()
82 82 # we set new cookie
83 83 headers = None
84 84 if session.request['set_cookie']:
85 85 # send set-cookie headers back to response to update cookie
86 86 headers = [('Set-Cookie', session.request['cookie_out'])]
87 87 return headers
88 88
89 89
90 90 def get_came_from(request):
91 91 came_from = safe_str(request.GET.get('came_from', ''))
92 92 parsed = urlparse.urlparse(came_from)
93 93 allowed_schemes = ['http', 'https']
94 94 default_came_from = h.route_path('home')
95 95 if parsed.scheme and parsed.scheme not in allowed_schemes:
96 96 log.error('Suspicious URL scheme detected %s for url %s' %
97 97 (parsed.scheme, parsed))
98 98 came_from = default_came_from
99 99 elif parsed.netloc and request.host != parsed.netloc:
100 100 log.error('Suspicious NETLOC detected %s for url %s server url '
101 101 'is: %s' % (parsed.netloc, parsed, request.host))
102 102 came_from = default_came_from
103 103 elif any(bad_str in parsed.path for bad_str in ('\r', '\n')):
104 104 log.error('Header injection detected `%s` for url %s server url ' %
105 105 (parsed.path, parsed))
106 106 came_from = default_came_from
107 107
108 108 return came_from or default_came_from
109 109
110 110
111 111 class LoginView(BaseAppView):
112 112
113 113 def load_default_context(self):
114 114 c = self._get_local_tmpl_context()
115 115 c.came_from = get_came_from(self.request)
116 116
117 117 return c
118 118
119 119 def _get_captcha_data(self):
120 120 settings = SettingsModel().get_all_settings()
121 121 private_key = settings.get('rhodecode_captcha_private_key')
122 122 public_key = settings.get('rhodecode_captcha_public_key')
123 123 active = bool(private_key)
124 124 return CaptchaData(
125 125 active=active, private_key=private_key, public_key=public_key)
126 126
127 127 def validate_captcha(self, private_key):
128 128
129 129 captcha_rs = self.request.POST.get('g-recaptcha-response')
130 130 url = "https://www.google.com/recaptcha/api/siteverify"
131 131 params = {
132 132 'secret': private_key,
133 133 'response': captcha_rs,
134 134 'remoteip': get_ip_addr(self.request.environ)
135 135 }
136 136 verify_rs = requests.get(url, params=params, verify=True, timeout=60)
137 137 verify_rs = verify_rs.json()
138 138 captcha_status = verify_rs.get('success', False)
139 139 captcha_errors = verify_rs.get('error-codes', [])
140 140 if not isinstance(captcha_errors, list):
141 141 captcha_errors = [captcha_errors]
142 142 captcha_errors = ', '.join(captcha_errors)
143 143 captcha_message = ''
144 144 if captcha_status is False:
145 145 captcha_message = "Bad captcha. Errors: {}".format(
146 146 captcha_errors)
147 147
148 148 return captcha_status, captcha_message
149 149
150 150 @view_config(
151 151 route_name='login', request_method='GET',
152 152 renderer='rhodecode:templates/login.mako')
153 153 def login(self):
154 154 c = self.load_default_context()
155 155 auth_user = self._rhodecode_user
156 156
157 157 # redirect if already logged in
158 158 if (auth_user.is_authenticated and
159 159 not auth_user.is_default and auth_user.ip_allowed):
160 160 raise HTTPFound(c.came_from)
161 161
162 162 # check if we use headers plugin, and try to login using it.
163 163 try:
164 164 log.debug('Running PRE-AUTH for headers based authentication')
165 165 auth_info = authenticate(
166 166 '', '', self.request.environ, HTTP_TYPE, skip_missing=True)
167 167 if auth_info:
168 168 headers = _store_user_in_session(
169 169 self.session, auth_info.get('username'))
170 170 raise HTTPFound(c.came_from, headers=headers)
171 171 except UserCreationError as e:
172 172 log.error(e)
173 173 h.flash(e, category='error')
174 174
175 175 return self._get_template_context(c)
176 176
177 177 @view_config(
178 178 route_name='login', request_method='POST',
179 179 renderer='rhodecode:templates/login.mako')
180 180 def login_post(self):
181 181 c = self.load_default_context()
182 182
183 183 login_form = LoginForm(self.request.translate)()
184 184
185 185 try:
186 186 self.session.invalidate()
187 187 form_result = login_form.to_python(self.request.POST)
188 188 # form checks for username/password, now we're authenticated
189 189 headers = _store_user_in_session(
190 190 self.session,
191 191 username=form_result['username'],
192 192 remember=form_result['remember'])
193 193 log.debug('Redirecting to "%s" after login.', c.came_from)
194 194
195 195 audit_user = audit_logger.UserWrap(
196 196 username=self.request.POST.get('username'),
197 197 ip_addr=self.request.remote_addr)
198 198 action_data = {'user_agent': self.request.user_agent}
199 199 audit_logger.store_web(
200 200 'user.login.success', action_data=action_data,
201 201 user=audit_user, commit=True)
202 202
203 203 raise HTTPFound(c.came_from, headers=headers)
204 204 except formencode.Invalid as errors:
205 205 defaults = errors.value
206 206 # remove password from filling in form again
207 207 defaults.pop('password', None)
208 208 render_ctx = {
209 209 'errors': errors.error_dict,
210 210 'defaults': defaults,
211 211 }
212 212
213 213 audit_user = audit_logger.UserWrap(
214 214 username=self.request.POST.get('username'),
215 215 ip_addr=self.request.remote_addr)
216 216 action_data = {'user_agent': self.request.user_agent}
217 217 audit_logger.store_web(
218 218 'user.login.failure', action_data=action_data,
219 219 user=audit_user, commit=True)
220 220 return self._get_template_context(c, **render_ctx)
221 221
222 222 except UserCreationError as e:
223 223 # headers auth or other auth functions that create users on
224 224 # the fly can throw this exception signaling that there's issue
225 225 # with user creation, explanation should be provided in
226 226 # Exception itself
227 227 h.flash(e, category='error')
228 228 return self._get_template_context(c)
229 229
230 230 @CSRFRequired()
231 231 @view_config(route_name='logout', request_method='POST')
232 232 def logout(self):
233 233 auth_user = self._rhodecode_user
234 234 log.info('Deleting session for user: `%s`', auth_user)
235 235
236 236 action_data = {'user_agent': self.request.user_agent}
237 237 audit_logger.store_web(
238 238 'user.logout', action_data=action_data,
239 239 user=auth_user, commit=True)
240 240 self.session.delete()
241 241 return HTTPFound(h.route_path('home'))
242 242
243 243 @HasPermissionAnyDecorator(
244 244 'hg.admin', 'hg.register.auto_activate', 'hg.register.manual_activate')
245 245 @view_config(
246 246 route_name='register', request_method='GET',
247 247 renderer='rhodecode:templates/register.mako',)
248 248 def register(self, defaults=None, errors=None):
249 249 c = self.load_default_context()
250 250 defaults = defaults or {}
251 251 errors = errors or {}
252 252
253 253 settings = SettingsModel().get_all_settings()
254 254 register_message = settings.get('rhodecode_register_message') or ''
255 255 captcha = self._get_captcha_data()
256 256 auto_active = 'hg.register.auto_activate' in User.get_default_user()\
257 257 .AuthUser().permissions['global']
258 258
259 259 render_ctx = self._get_template_context(c)
260 260 render_ctx.update({
261 261 'defaults': defaults,
262 262 'errors': errors,
263 263 'auto_active': auto_active,
264 264 'captcha_active': captcha.active,
265 265 'captcha_public_key': captcha.public_key,
266 266 'register_message': register_message,
267 267 })
268 268 return render_ctx
269 269
270 270 @HasPermissionAnyDecorator(
271 271 'hg.admin', 'hg.register.auto_activate', 'hg.register.manual_activate')
272 272 @view_config(
273 273 route_name='register', request_method='POST',
274 274 renderer='rhodecode:templates/register.mako')
275 275 def register_post(self):
276 276 self.load_default_context()
277 277 captcha = self._get_captcha_data()
278 278 auto_active = 'hg.register.auto_activate' in User.get_default_user()\
279 279 .AuthUser().permissions['global']
280 280
281 281 register_form = RegisterForm(self.request.translate)()
282 282 try:
283 283
284 284 form_result = register_form.to_python(self.request.POST)
285 285 form_result['active'] = auto_active
286 286
287 287 if captcha.active:
288 288 captcha_status, captcha_message = self.validate_captcha(
289 289 captcha.private_key)
290 290
291 291 if not captcha_status:
292 292 _value = form_result
293 293 _msg = _('Bad captcha')
294 294 error_dict = {'recaptcha_field': captcha_message}
295 295 raise formencode.Invalid(
296 296 _msg, _value, None, error_dict=error_dict)
297 297
298 298 new_user = UserModel().create_registration(form_result)
299 299
300 300 action_data = {'data': new_user.get_api_data(),
301 301 'user_agent': self.request.user_agent}
302 302
303 303 audit_user = audit_logger.UserWrap(
304 304 username=new_user.username,
305 305 user_id=new_user.user_id,
306 306 ip_addr=self.request.remote_addr)
307 307
308 308 audit_logger.store_web(
309 309 'user.register', action_data=action_data,
310 310 user=audit_user)
311 311
312 312 event = UserRegistered(user=new_user, session=self.session)
313 313 trigger(event)
314 314 h.flash(
315 315 _('You have successfully registered with RhodeCode'),
316 316 category='success')
317 317 Session().commit()
318 318
319 319 redirect_ro = self.request.route_path('login')
320 320 raise HTTPFound(redirect_ro)
321 321
322 322 except formencode.Invalid as errors:
323 323 errors.value.pop('password', None)
324 324 errors.value.pop('password_confirmation', None)
325 325 return self.register(
326 326 defaults=errors.value, errors=errors.error_dict)
327 327
328 328 except UserCreationError as e:
329 329 # container auth or other auth functions that create users on
330 330 # the fly can throw this exception signaling that there's issue
331 331 # with user creation, explanation should be provided in
332 332 # Exception itself
333 333 h.flash(e, category='error')
334 334 return self.register()
335 335
336 336 @view_config(
337 337 route_name='reset_password', request_method=('GET', 'POST'),
338 338 renderer='rhodecode:templates/password_reset.mako')
339 339 def password_reset(self):
340 340 c = self.load_default_context()
341 341 captcha = self._get_captcha_data()
342 342
343 343 template_context = {
344 344 'captcha_active': captcha.active,
345 345 'captcha_public_key': captcha.public_key,
346 346 'defaults': {},
347 347 'errors': {},
348 348 }
349 349
350 350 # always send implicit message to prevent from discovery of
351 351 # matching emails
352 352 msg = _('If such email exists, a password reset link was sent to it.')
353 353
354 354 if self.request.POST:
355 355 if h.HasPermissionAny('hg.password_reset.disabled')():
356 356 _email = self.request.POST.get('email', '')
357 357 log.error('Failed attempt to reset password for `%s`.', _email)
358 358 h.flash(_('Password reset has been disabled.'),
359 359 category='error')
360 360 return HTTPFound(self.request.route_path('reset_password'))
361 361
362 362 password_reset_form = PasswordResetForm(self.request.translate)()
363 363 try:
364 364 form_result = password_reset_form.to_python(
365 365 self.request.POST)
366 366 user_email = form_result['email']
367 367
368 368 if captcha.active:
369 369 captcha_status, captcha_message = self.validate_captcha(
370 370 captcha.private_key)
371 371
372 372 if not captcha_status:
373 373 _value = form_result
374 374 _msg = _('Bad captcha')
375 375 error_dict = {'recaptcha_field': captcha_message}
376 376 raise formencode.Invalid(
377 377 _msg, _value, None, error_dict=error_dict)
378 378
379 379 # Generate reset URL and send mail.
380 380 user = User.get_by_email(user_email)
381 381
382 # generate password reset token that expires in 10minutes
383 desc = 'Generated token for password reset from {}'.format(
382 # generate password reset token that expires in 10 minutes
383 description = u'Generated token for password reset from {}'.format(
384 384 datetime.datetime.now().isoformat())
385 reset_token = AuthTokenModel().create(
386 user, lifetime=10,
387 description=desc,
388 role=UserApiKeys.ROLE_PASSWORD_RESET)
385
386 reset_token = UserModel().add_auth_token(
387 user=user, lifetime_minutes=10,
388 role=UserModel.auth_token_role.ROLE_PASSWORD_RESET,
389 description=description)
389 390 Session().commit()
390 391
391 392 log.debug('Successfully created password recovery token')
392 393 password_reset_url = self.request.route_url(
393 394 'reset_password_confirmation',
394 395 _query={'key': reset_token.api_key})
395 396 UserModel().reset_password_link(
396 397 form_result, password_reset_url)
397 398 # Display success message and redirect.
398 399 h.flash(msg, category='success')
399 400
400 401 action_data = {'email': user_email,
401 402 'user_agent': self.request.user_agent}
402 403 audit_logger.store_web(
403 404 'user.password.reset_request', action_data=action_data,
404 405 user=self._rhodecode_user, commit=True)
405 406 return HTTPFound(self.request.route_path('reset_password'))
406 407
407 408 except formencode.Invalid as errors:
408 409 template_context.update({
409 410 'defaults': errors.value,
410 411 'errors': errors.error_dict,
411 412 })
412 413 if not self.request.POST.get('email'):
413 414 # case of empty email, we want to report that
414 415 return self._get_template_context(c, **template_context)
415 416
416 417 if 'recaptcha_field' in errors.error_dict:
417 418 # case of failed captcha
418 419 return self._get_template_context(c, **template_context)
419 420
420 421 log.debug('faking response on invalid password reset')
421 422 # make this take 2s, to prevent brute forcing.
422 423 time.sleep(2)
423 424 h.flash(msg, category='success')
424 425 return HTTPFound(self.request.route_path('reset_password'))
425 426
426 427 return self._get_template_context(c, **template_context)
427 428
428 429 @view_config(route_name='reset_password_confirmation',
429 430 request_method='GET')
430 431 def password_reset_confirmation(self):
431 432 self.load_default_context()
432 433 if self.request.GET and self.request.GET.get('key'):
433 434 # make this take 2s, to prevent brute forcing.
434 435 time.sleep(2)
435 436
436 437 token = AuthTokenModel().get_auth_token(
437 438 self.request.GET.get('key'))
438 439
439 440 # verify token is the correct role
440 441 if token is None or token.role != UserApiKeys.ROLE_PASSWORD_RESET:
441 442 log.debug('Got token with role:%s expected is %s',
442 443 getattr(token, 'role', 'EMPTY_TOKEN'),
443 444 UserApiKeys.ROLE_PASSWORD_RESET)
444 445 h.flash(
445 446 _('Given reset token is invalid'), category='error')
446 447 return HTTPFound(self.request.route_path('reset_password'))
447 448
448 449 try:
449 450 owner = token.user
450 451 data = {'email': owner.email, 'token': token.api_key}
451 452 UserModel().reset_password(data)
452 453 h.flash(
453 454 _('Your password reset was successful, '
454 455 'a new password has been sent to your email'),
455 456 category='success')
456 457 except Exception as e:
457 458 log.error(e)
458 459 return HTTPFound(self.request.route_path('reset_password'))
459 460
460 461 return HTTPFound(self.request.route_path('login'))
@@ -1,604 +1,605 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2016-2018 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
24 24 import formencode
25 25 import formencode.htmlfill
26 26 from pyramid.httpexceptions import HTTPFound
27 27 from pyramid.view import view_config
28 28 from pyramid.renderers import render
29 29 from pyramid.response import Response
30 30
31 31 from rhodecode.apps._base import BaseAppView, DataGridAppView
32 32 from rhodecode import forms
33 33 from rhodecode.lib import helpers as h
34 34 from rhodecode.lib import audit_logger
35 35 from rhodecode.lib.ext_json import json
36 36 from rhodecode.lib.auth import LoginRequired, NotAnonymous, CSRFRequired
37 37 from rhodecode.lib.channelstream import (
38 38 channelstream_request, ChannelstreamException)
39 39 from rhodecode.lib.utils2 import safe_int, md5, str2bool
40 40 from rhodecode.model.auth_token import AuthTokenModel
41 41 from rhodecode.model.comment import CommentsModel
42 42 from rhodecode.model.db import (
43 43 Repository, UserEmailMap, UserApiKeys, UserFollowing, joinedload,
44 44 PullRequest)
45 45 from rhodecode.model.forms import UserForm, UserExtraEmailForm
46 46 from rhodecode.model.meta import Session
47 47 from rhodecode.model.pull_request import PullRequestModel
48 48 from rhodecode.model.scm import RepoList
49 49 from rhodecode.model.user import UserModel
50 50 from rhodecode.model.repo import RepoModel
51 51 from rhodecode.model.user_group import UserGroupModel
52 52 from rhodecode.model.validation_schema.schemas import user_schema
53 53
54 54 log = logging.getLogger(__name__)
55 55
56 56
57 57 class MyAccountView(BaseAppView, DataGridAppView):
58 58 ALLOW_SCOPED_TOKENS = False
59 59 """
60 60 This view has alternative version inside EE, if modified please take a look
61 61 in there as well.
62 62 """
63 63
64 64 def load_default_context(self):
65 65 c = self._get_local_tmpl_context()
66 66 c.user = c.auth_user.get_instance()
67 67 c.allow_scoped_tokens = self.ALLOW_SCOPED_TOKENS
68 68
69 69 return c
70 70
71 71 @LoginRequired()
72 72 @NotAnonymous()
73 73 @view_config(
74 74 route_name='my_account_profile', request_method='GET',
75 75 renderer='rhodecode:templates/admin/my_account/my_account.mako')
76 76 def my_account_profile(self):
77 77 c = self.load_default_context()
78 78 c.active = 'profile'
79 79 return self._get_template_context(c)
80 80
81 81 @LoginRequired()
82 82 @NotAnonymous()
83 83 @view_config(
84 84 route_name='my_account_password', request_method='GET',
85 85 renderer='rhodecode:templates/admin/my_account/my_account.mako')
86 86 def my_account_password(self):
87 87 c = self.load_default_context()
88 88 c.active = 'password'
89 89 c.extern_type = c.user.extern_type
90 90
91 91 schema = user_schema.ChangePasswordSchema().bind(
92 92 username=c.user.username)
93 93
94 94 form = forms.Form(
95 95 schema,
96 96 action=h.route_path('my_account_password_update'),
97 97 buttons=(forms.buttons.save, forms.buttons.reset))
98 98
99 99 c.form = form
100 100 return self._get_template_context(c)
101 101
102 102 @LoginRequired()
103 103 @NotAnonymous()
104 104 @CSRFRequired()
105 105 @view_config(
106 106 route_name='my_account_password_update', request_method='POST',
107 107 renderer='rhodecode:templates/admin/my_account/my_account.mako')
108 108 def my_account_password_update(self):
109 109 _ = self.request.translate
110 110 c = self.load_default_context()
111 111 c.active = 'password'
112 112 c.extern_type = c.user.extern_type
113 113
114 114 schema = user_schema.ChangePasswordSchema().bind(
115 115 username=c.user.username)
116 116
117 117 form = forms.Form(
118 118 schema, buttons=(forms.buttons.save, forms.buttons.reset))
119 119
120 120 if c.extern_type != 'rhodecode':
121 121 raise HTTPFound(self.request.route_path('my_account_password'))
122 122
123 123 controls = self.request.POST.items()
124 124 try:
125 125 valid_data = form.validate(controls)
126 126 UserModel().update_user(c.user.user_id, **valid_data)
127 127 c.user.update_userdata(force_password_change=False)
128 128 Session().commit()
129 129 except forms.ValidationFailure as e:
130 130 c.form = e
131 131 return self._get_template_context(c)
132 132
133 133 except Exception:
134 134 log.exception("Exception updating password")
135 135 h.flash(_('Error occurred during update of user password'),
136 136 category='error')
137 137 else:
138 138 instance = c.auth_user.get_instance()
139 139 self.session.setdefault('rhodecode_user', {}).update(
140 140 {'password': md5(instance.password)})
141 141 self.session.save()
142 142 h.flash(_("Successfully updated password"), category='success')
143 143
144 144 raise HTTPFound(self.request.route_path('my_account_password'))
145 145
146 146 @LoginRequired()
147 147 @NotAnonymous()
148 148 @view_config(
149 149 route_name='my_account_auth_tokens', request_method='GET',
150 150 renderer='rhodecode:templates/admin/my_account/my_account.mako')
151 151 def my_account_auth_tokens(self):
152 152 _ = self.request.translate
153 153
154 154 c = self.load_default_context()
155 155 c.active = 'auth_tokens'
156 156 c.lifetime_values = AuthTokenModel.get_lifetime_values(translator=_)
157 157 c.role_values = [
158 158 (x, AuthTokenModel.cls._get_role_name(x))
159 159 for x in AuthTokenModel.cls.ROLES]
160 160 c.role_options = [(c.role_values, _("Role"))]
161 161 c.user_auth_tokens = AuthTokenModel().get_auth_tokens(
162 162 c.user.user_id, show_expired=True)
163 163 c.role_vcs = AuthTokenModel.cls.ROLE_VCS
164 164 return self._get_template_context(c)
165 165
166 166 def maybe_attach_token_scope(self, token):
167 167 # implemented in EE edition
168 168 pass
169 169
170 170 @LoginRequired()
171 171 @NotAnonymous()
172 172 @CSRFRequired()
173 173 @view_config(
174 174 route_name='my_account_auth_tokens_add', request_method='POST',)
175 175 def my_account_auth_tokens_add(self):
176 176 _ = self.request.translate
177 177 c = self.load_default_context()
178 178
179 179 lifetime = safe_int(self.request.POST.get('lifetime'), -1)
180 180 description = self.request.POST.get('description')
181 181 role = self.request.POST.get('role')
182 182
183 token = AuthTokenModel().create(
184 c.user.user_id, description, lifetime, role)
183 token = UserModel().add_auth_token(
184 user=c.user.user_id,
185 lifetime_minutes=lifetime, role=role, description=description,
186 scope_callback=self.maybe_attach_token_scope)
185 187 token_data = token.get_api_data()
186 188
187 self.maybe_attach_token_scope(token)
188 189 audit_logger.store_web(
189 190 'user.edit.token.add', action_data={
190 191 'data': {'token': token_data, 'user': 'self'}},
191 192 user=self._rhodecode_user, )
192 193 Session().commit()
193 194
194 195 h.flash(_("Auth token successfully created"), category='success')
195 196 return HTTPFound(h.route_path('my_account_auth_tokens'))
196 197
197 198 @LoginRequired()
198 199 @NotAnonymous()
199 200 @CSRFRequired()
200 201 @view_config(
201 202 route_name='my_account_auth_tokens_delete', request_method='POST')
202 203 def my_account_auth_tokens_delete(self):
203 204 _ = self.request.translate
204 205 c = self.load_default_context()
205 206
206 207 del_auth_token = self.request.POST.get('del_auth_token')
207 208
208 209 if del_auth_token:
209 210 token = UserApiKeys.get_or_404(del_auth_token)
210 211 token_data = token.get_api_data()
211 212
212 213 AuthTokenModel().delete(del_auth_token, c.user.user_id)
213 214 audit_logger.store_web(
214 215 'user.edit.token.delete', action_data={
215 216 'data': {'token': token_data, 'user': 'self'}},
216 217 user=self._rhodecode_user,)
217 218 Session().commit()
218 219 h.flash(_("Auth token successfully deleted"), category='success')
219 220
220 221 return HTTPFound(h.route_path('my_account_auth_tokens'))
221 222
222 223 @LoginRequired()
223 224 @NotAnonymous()
224 225 @view_config(
225 226 route_name='my_account_emails', request_method='GET',
226 227 renderer='rhodecode:templates/admin/my_account/my_account.mako')
227 228 def my_account_emails(self):
228 229 _ = self.request.translate
229 230
230 231 c = self.load_default_context()
231 232 c.active = 'emails'
232 233
233 234 c.user_email_map = UserEmailMap.query()\
234 235 .filter(UserEmailMap.user == c.user).all()
235 236
236 237 schema = user_schema.AddEmailSchema().bind(
237 238 username=c.user.username, user_emails=c.user.emails)
238 239
239 240 form = forms.RcForm(schema,
240 241 action=h.route_path('my_account_emails_add'),
241 242 buttons=(forms.buttons.save, forms.buttons.reset))
242 243
243 244 c.form = form
244 245 return self._get_template_context(c)
245 246
246 247 @LoginRequired()
247 248 @NotAnonymous()
248 249 @CSRFRequired()
249 250 @view_config(
250 251 route_name='my_account_emails_add', request_method='POST',
251 252 renderer='rhodecode:templates/admin/my_account/my_account.mako')
252 253 def my_account_emails_add(self):
253 254 _ = self.request.translate
254 255 c = self.load_default_context()
255 256 c.active = 'emails'
256 257
257 258 schema = user_schema.AddEmailSchema().bind(
258 259 username=c.user.username, user_emails=c.user.emails)
259 260
260 261 form = forms.RcForm(
261 262 schema, action=h.route_path('my_account_emails_add'),
262 263 buttons=(forms.buttons.save, forms.buttons.reset))
263 264
264 265 controls = self.request.POST.items()
265 266 try:
266 267 valid_data = form.validate(controls)
267 268 UserModel().add_extra_email(c.user.user_id, valid_data['email'])
268 269 audit_logger.store_web(
269 270 'user.edit.email.add', action_data={
270 271 'data': {'email': valid_data['email'], 'user': 'self'}},
271 272 user=self._rhodecode_user,)
272 273 Session().commit()
273 274 except formencode.Invalid as error:
274 275 h.flash(h.escape(error.error_dict['email']), category='error')
275 276 except forms.ValidationFailure as e:
276 277 c.user_email_map = UserEmailMap.query() \
277 278 .filter(UserEmailMap.user == c.user).all()
278 279 c.form = e
279 280 return self._get_template_context(c)
280 281 except Exception:
281 282 log.exception("Exception adding email")
282 283 h.flash(_('Error occurred during adding email'),
283 284 category='error')
284 285 else:
285 286 h.flash(_("Successfully added email"), category='success')
286 287
287 288 raise HTTPFound(self.request.route_path('my_account_emails'))
288 289
289 290 @LoginRequired()
290 291 @NotAnonymous()
291 292 @CSRFRequired()
292 293 @view_config(
293 294 route_name='my_account_emails_delete', request_method='POST')
294 295 def my_account_emails_delete(self):
295 296 _ = self.request.translate
296 297 c = self.load_default_context()
297 298
298 299 del_email_id = self.request.POST.get('del_email_id')
299 300 if del_email_id:
300 301 email = UserEmailMap.get_or_404(del_email_id).email
301 302 UserModel().delete_extra_email(c.user.user_id, del_email_id)
302 303 audit_logger.store_web(
303 304 'user.edit.email.delete', action_data={
304 305 'data': {'email': email, 'user': 'self'}},
305 306 user=self._rhodecode_user,)
306 307 Session().commit()
307 308 h.flash(_("Email successfully deleted"),
308 309 category='success')
309 310 return HTTPFound(h.route_path('my_account_emails'))
310 311
311 312 @LoginRequired()
312 313 @NotAnonymous()
313 314 @CSRFRequired()
314 315 @view_config(
315 316 route_name='my_account_notifications_test_channelstream',
316 317 request_method='POST', renderer='json_ext')
317 318 def my_account_notifications_test_channelstream(self):
318 319 message = 'Test message sent via Channelstream by user: {}, on {}'.format(
319 320 self._rhodecode_user.username, datetime.datetime.now())
320 321 payload = {
321 322 # 'channel': 'broadcast',
322 323 'type': 'message',
323 324 'timestamp': datetime.datetime.utcnow(),
324 325 'user': 'system',
325 326 'pm_users': [self._rhodecode_user.username],
326 327 'message': {
327 328 'message': message,
328 329 'level': 'info',
329 330 'topic': '/notifications'
330 331 }
331 332 }
332 333
333 334 registry = self.request.registry
334 335 rhodecode_plugins = getattr(registry, 'rhodecode_plugins', {})
335 336 channelstream_config = rhodecode_plugins.get('channelstream', {})
336 337
337 338 try:
338 339 channelstream_request(channelstream_config, [payload], '/message')
339 340 except ChannelstreamException as e:
340 341 log.exception('Failed to send channelstream data')
341 342 return {"response": 'ERROR: {}'.format(e.__class__.__name__)}
342 343 return {"response": 'Channelstream data sent. '
343 344 'You should see a new live message now.'}
344 345
345 346 def _load_my_repos_data(self, watched=False):
346 347 if watched:
347 348 admin = False
348 349 follows_repos = Session().query(UserFollowing)\
349 350 .filter(UserFollowing.user_id == self._rhodecode_user.user_id)\
350 351 .options(joinedload(UserFollowing.follows_repository))\
351 352 .all()
352 353 repo_list = [x.follows_repository for x in follows_repos]
353 354 else:
354 355 admin = True
355 356 repo_list = Repository.get_all_repos(
356 357 user_id=self._rhodecode_user.user_id)
357 358 repo_list = RepoList(repo_list, perm_set=[
358 359 'repository.read', 'repository.write', 'repository.admin'])
359 360
360 361 repos_data = RepoModel().get_repos_as_dict(
361 362 repo_list=repo_list, admin=admin)
362 363 # json used to render the grid
363 364 return json.dumps(repos_data)
364 365
365 366 @LoginRequired()
366 367 @NotAnonymous()
367 368 @view_config(
368 369 route_name='my_account_repos', request_method='GET',
369 370 renderer='rhodecode:templates/admin/my_account/my_account.mako')
370 371 def my_account_repos(self):
371 372 c = self.load_default_context()
372 373 c.active = 'repos'
373 374
374 375 # json used to render the grid
375 376 c.data = self._load_my_repos_data()
376 377 return self._get_template_context(c)
377 378
378 379 @LoginRequired()
379 380 @NotAnonymous()
380 381 @view_config(
381 382 route_name='my_account_watched', request_method='GET',
382 383 renderer='rhodecode:templates/admin/my_account/my_account.mako')
383 384 def my_account_watched(self):
384 385 c = self.load_default_context()
385 386 c.active = 'watched'
386 387
387 388 # json used to render the grid
388 389 c.data = self._load_my_repos_data(watched=True)
389 390 return self._get_template_context(c)
390 391
391 392 @LoginRequired()
392 393 @NotAnonymous()
393 394 @view_config(
394 395 route_name='my_account_perms', request_method='GET',
395 396 renderer='rhodecode:templates/admin/my_account/my_account.mako')
396 397 def my_account_perms(self):
397 398 c = self.load_default_context()
398 399 c.active = 'perms'
399 400
400 401 c.perm_user = c.auth_user
401 402 return self._get_template_context(c)
402 403
403 404 @LoginRequired()
404 405 @NotAnonymous()
405 406 @view_config(
406 407 route_name='my_account_notifications', request_method='GET',
407 408 renderer='rhodecode:templates/admin/my_account/my_account.mako')
408 409 def my_notifications(self):
409 410 c = self.load_default_context()
410 411 c.active = 'notifications'
411 412
412 413 return self._get_template_context(c)
413 414
414 415 @LoginRequired()
415 416 @NotAnonymous()
416 417 @CSRFRequired()
417 418 @view_config(
418 419 route_name='my_account_notifications_toggle_visibility',
419 420 request_method='POST', renderer='json_ext')
420 421 def my_notifications_toggle_visibility(self):
421 422 user = self._rhodecode_db_user
422 423 new_status = not user.user_data.get('notification_status', True)
423 424 user.update_userdata(notification_status=new_status)
424 425 Session().commit()
425 426 return user.user_data['notification_status']
426 427
427 428 @LoginRequired()
428 429 @NotAnonymous()
429 430 @view_config(
430 431 route_name='my_account_edit',
431 432 request_method='GET',
432 433 renderer='rhodecode:templates/admin/my_account/my_account.mako')
433 434 def my_account_edit(self):
434 435 c = self.load_default_context()
435 436 c.active = 'profile_edit'
436 437 c.extern_type = c.user.extern_type
437 438 c.extern_name = c.user.extern_name
438 439
439 440 schema = user_schema.UserProfileSchema().bind(
440 441 username=c.user.username, user_emails=c.user.emails)
441 442 appstruct = {
442 443 'username': c.user.username,
443 444 'email': c.user.email,
444 445 'firstname': c.user.firstname,
445 446 'lastname': c.user.lastname,
446 447 }
447 448 c.form = forms.RcForm(
448 449 schema, appstruct=appstruct,
449 450 action=h.route_path('my_account_update'),
450 451 buttons=(forms.buttons.save, forms.buttons.reset))
451 452
452 453 return self._get_template_context(c)
453 454
454 455 @LoginRequired()
455 456 @NotAnonymous()
456 457 @CSRFRequired()
457 458 @view_config(
458 459 route_name='my_account_update',
459 460 request_method='POST',
460 461 renderer='rhodecode:templates/admin/my_account/my_account.mako')
461 462 def my_account_update(self):
462 463 _ = self.request.translate
463 464 c = self.load_default_context()
464 465 c.active = 'profile_edit'
465 466 c.perm_user = c.auth_user
466 467 c.extern_type = c.user.extern_type
467 468 c.extern_name = c.user.extern_name
468 469
469 470 schema = user_schema.UserProfileSchema().bind(
470 471 username=c.user.username, user_emails=c.user.emails)
471 472 form = forms.RcForm(
472 473 schema, buttons=(forms.buttons.save, forms.buttons.reset))
473 474
474 475 controls = self.request.POST.items()
475 476 try:
476 477 valid_data = form.validate(controls)
477 478 skip_attrs = ['admin', 'active', 'extern_type', 'extern_name',
478 479 'new_password', 'password_confirmation']
479 480 if c.extern_type != "rhodecode":
480 481 # forbid updating username for external accounts
481 482 skip_attrs.append('username')
482 483 old_email = c.user.email
483 484 UserModel().update_user(
484 485 self._rhodecode_user.user_id, skip_attrs=skip_attrs,
485 486 **valid_data)
486 487 if old_email != valid_data['email']:
487 488 old = UserEmailMap.query() \
488 489 .filter(UserEmailMap.user == c.user).filter(UserEmailMap.email == valid_data['email']).first()
489 490 old.email = old_email
490 491 h.flash(_('Your account was updated successfully'), category='success')
491 492 Session().commit()
492 493 except forms.ValidationFailure as e:
493 494 c.form = e
494 495 return self._get_template_context(c)
495 496 except Exception:
496 497 log.exception("Exception updating user")
497 498 h.flash(_('Error occurred during update of user'),
498 499 category='error')
499 500 raise HTTPFound(h.route_path('my_account_profile'))
500 501
501 502 def _get_pull_requests_list(self, statuses):
502 503 draw, start, limit = self._extract_chunk(self.request)
503 504 search_q, order_by, order_dir = self._extract_ordering(self.request)
504 505 _render = self.request.get_partial_renderer(
505 506 'rhodecode:templates/data_table/_dt_elements.mako')
506 507
507 508 pull_requests = PullRequestModel().get_im_participating_in(
508 509 user_id=self._rhodecode_user.user_id,
509 510 statuses=statuses,
510 511 offset=start, length=limit, order_by=order_by,
511 512 order_dir=order_dir)
512 513
513 514 pull_requests_total_count = PullRequestModel().count_im_participating_in(
514 515 user_id=self._rhodecode_user.user_id, statuses=statuses)
515 516
516 517 data = []
517 518 comments_model = CommentsModel()
518 519 for pr in pull_requests:
519 520 repo_id = pr.target_repo_id
520 521 comments = comments_model.get_all_comments(
521 522 repo_id, pull_request=pr)
522 523 owned = pr.user_id == self._rhodecode_user.user_id
523 524
524 525 data.append({
525 526 'target_repo': _render('pullrequest_target_repo',
526 527 pr.target_repo.repo_name),
527 528 'name': _render('pullrequest_name',
528 529 pr.pull_request_id, pr.target_repo.repo_name,
529 530 short=True),
530 531 'name_raw': pr.pull_request_id,
531 532 'status': _render('pullrequest_status',
532 533 pr.calculated_review_status()),
533 534 'title': _render(
534 535 'pullrequest_title', pr.title, pr.description),
535 536 'description': h.escape(pr.description),
536 537 'updated_on': _render('pullrequest_updated_on',
537 538 h.datetime_to_time(pr.updated_on)),
538 539 'updated_on_raw': h.datetime_to_time(pr.updated_on),
539 540 'created_on': _render('pullrequest_updated_on',
540 541 h.datetime_to_time(pr.created_on)),
541 542 'created_on_raw': h.datetime_to_time(pr.created_on),
542 543 'author': _render('pullrequest_author',
543 544 pr.author.full_contact, ),
544 545 'author_raw': pr.author.full_name,
545 546 'comments': _render('pullrequest_comments', len(comments)),
546 547 'comments_raw': len(comments),
547 548 'closed': pr.is_closed(),
548 549 'owned': owned
549 550 })
550 551
551 552 # json used to render the grid
552 553 data = ({
553 554 'draw': draw,
554 555 'data': data,
555 556 'recordsTotal': pull_requests_total_count,
556 557 'recordsFiltered': pull_requests_total_count,
557 558 })
558 559 return data
559 560
560 561 @LoginRequired()
561 562 @NotAnonymous()
562 563 @view_config(
563 564 route_name='my_account_pullrequests',
564 565 request_method='GET',
565 566 renderer='rhodecode:templates/admin/my_account/my_account.mako')
566 567 def my_account_pullrequests(self):
567 568 c = self.load_default_context()
568 569 c.active = 'pullrequests'
569 570 req_get = self.request.GET
570 571
571 572 c.closed = str2bool(req_get.get('pr_show_closed'))
572 573
573 574 return self._get_template_context(c)
574 575
575 576 @LoginRequired()
576 577 @NotAnonymous()
577 578 @view_config(
578 579 route_name='my_account_pullrequests_data',
579 580 request_method='GET', renderer='json_ext')
580 581 def my_account_pullrequests_data(self):
581 582 self.load_default_context()
582 583 req_get = self.request.GET
583 584 closed = str2bool(req_get.get('closed'))
584 585
585 586 statuses = [PullRequest.STATUS_NEW, PullRequest.STATUS_OPEN]
586 587 if closed:
587 588 statuses += [PullRequest.STATUS_CLOSED]
588 589
589 590 data = self._get_pull_requests_list(statuses=statuses)
590 591 return data
591 592
592 593 @LoginRequired()
593 594 @NotAnonymous()
594 595 @view_config(
595 596 route_name='my_account_user_group_membership',
596 597 request_method='GET',
597 598 renderer='rhodecode:templates/admin/my_account/my_account.mako')
598 599 def my_account_user_group_membership(self):
599 600 c = self.load_default_context()
600 601 c.active = 'user_group_membership'
601 602 groups = [UserGroupModel.get_user_groups_as_dict(group.users_group)
602 603 for group in self._rhodecode_db_user.group_member]
603 604 c.user_groups = json.dumps(groups)
604 605 return self._get_template_context(c)
@@ -1,620 +1,621 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2018 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 Database creation, and setup module for RhodeCode Enterprise. Used for creation
23 23 of database as well as for migration operations
24 24 """
25 25
26 26 import os
27 27 import sys
28 28 import time
29 29 import uuid
30 30 import logging
31 31 import getpass
32 32 from os.path import dirname as dn, join as jn
33 33
34 34 from sqlalchemy.engine import create_engine
35 35
36 36 from rhodecode import __dbversion__
37 37 from rhodecode.model import init_model
38 38 from rhodecode.model.user import UserModel
39 39 from rhodecode.model.db import (
40 40 User, Permission, RhodeCodeUi, RhodeCodeSetting, UserToPerm,
41 41 DbMigrateVersion, RepoGroup, UserRepoGroupToPerm, CacheKey, Repository)
42 42 from rhodecode.model.meta import Session, Base
43 43 from rhodecode.model.permission import PermissionModel
44 44 from rhodecode.model.repo import RepoModel
45 45 from rhodecode.model.repo_group import RepoGroupModel
46 46 from rhodecode.model.settings import SettingsModel
47 47
48 48
49 49 log = logging.getLogger(__name__)
50 50
51 51
52 52 def notify(msg):
53 53 """
54 54 Notification for migrations messages
55 55 """
56 56 ml = len(msg) + (4 * 2)
57 57 print(('\n%s\n*** %s ***\n%s' % ('*' * ml, msg, '*' * ml)).upper())
58 58
59 59
60 60 class DbManage(object):
61 61
62 62 def __init__(self, log_sql, dbconf, root, tests=False,
63 63 SESSION=None, cli_args=None):
64 64 self.dbname = dbconf.split('/')[-1]
65 65 self.tests = tests
66 66 self.root = root
67 67 self.dburi = dbconf
68 68 self.log_sql = log_sql
69 69 self.db_exists = False
70 70 self.cli_args = cli_args or {}
71 71 self.init_db(SESSION=SESSION)
72 72 self.ask_ok = self.get_ask_ok_func(self.cli_args.get('force_ask'))
73 73
74 74 def get_ask_ok_func(self, param):
75 75 if param not in [None]:
76 76 # return a function lambda that has a default set to param
77 77 return lambda *args, **kwargs: param
78 78 else:
79 79 from rhodecode.lib.utils import ask_ok
80 80 return ask_ok
81 81
82 82 def init_db(self, SESSION=None):
83 83 if SESSION:
84 84 self.sa = SESSION
85 85 else:
86 86 # init new sessions
87 87 engine = create_engine(self.dburi, echo=self.log_sql)
88 88 init_model(engine)
89 89 self.sa = Session()
90 90
91 91 def create_tables(self, override=False):
92 92 """
93 93 Create a auth database
94 94 """
95 95
96 96 log.info("Existing database with the same name is going to be destroyed.")
97 97 log.info("Setup command will run DROP ALL command on that database.")
98 98 if self.tests:
99 99 destroy = True
100 100 else:
101 101 destroy = self.ask_ok('Are you sure that you want to destroy the old database? [y/n]')
102 102 if not destroy:
103 103 log.info('Nothing done.')
104 104 sys.exit(0)
105 105 if destroy:
106 106 Base.metadata.drop_all()
107 107
108 108 checkfirst = not override
109 109 Base.metadata.create_all(checkfirst=checkfirst)
110 110 log.info('Created tables for %s' % self.dbname)
111 111
112 112 def set_db_version(self):
113 113 ver = DbMigrateVersion()
114 114 ver.version = __dbversion__
115 115 ver.repository_id = 'rhodecode_db_migrations'
116 116 ver.repository_path = 'versions'
117 117 self.sa.add(ver)
118 118 log.info('db version set to: %s' % __dbversion__)
119 119
120 120 def run_pre_migration_tasks(self):
121 121 """
122 122 Run various tasks before actually doing migrations
123 123 """
124 124 # delete cache keys on each upgrade
125 125 total = CacheKey.query().count()
126 126 log.info("Deleting (%s) cache keys now...", total)
127 127 CacheKey.delete_all_cache()
128 128
129 129 def upgrade(self, version=None):
130 130 """
131 131 Upgrades given database schema to given revision following
132 132 all needed steps, to perform the upgrade
133 133
134 134 """
135 135
136 136 from rhodecode.lib.dbmigrate.migrate.versioning import api
137 137 from rhodecode.lib.dbmigrate.migrate.exceptions import \
138 138 DatabaseNotControlledError
139 139
140 140 if 'sqlite' in self.dburi:
141 141 print (
142 142 '********************** WARNING **********************\n'
143 143 'Make sure your version of sqlite is at least 3.7.X. \n'
144 144 'Earlier versions are known to fail on some migrations\n'
145 145 '*****************************************************\n')
146 146
147 147 upgrade = self.ask_ok(
148 148 'You are about to perform a database upgrade. Make '
149 149 'sure you have backed up your database. '
150 150 'Continue ? [y/n]')
151 151 if not upgrade:
152 152 log.info('No upgrade performed')
153 153 sys.exit(0)
154 154
155 155 repository_path = jn(dn(dn(dn(os.path.realpath(__file__)))),
156 156 'rhodecode/lib/dbmigrate')
157 157 db_uri = self.dburi
158 158
159 159 try:
160 160 curr_version = version or api.db_version(db_uri, repository_path)
161 161 msg = ('Found current database db_uri under version '
162 162 'control with version {}'.format(curr_version))
163 163
164 164 except (RuntimeError, DatabaseNotControlledError):
165 165 curr_version = 1
166 166 msg = ('Current database is not under version control. Setting '
167 167 'as version %s' % curr_version)
168 168 api.version_control(db_uri, repository_path, curr_version)
169 169
170 170 notify(msg)
171 171
172 172 self.run_pre_migration_tasks()
173 173
174 174 if curr_version == __dbversion__:
175 175 log.info('This database is already at the newest version')
176 176 sys.exit(0)
177 177
178 178 upgrade_steps = range(curr_version + 1, __dbversion__ + 1)
179 179 notify('attempting to upgrade database from '
180 180 'version %s to version %s' % (curr_version, __dbversion__))
181 181
182 182 # CALL THE PROPER ORDER OF STEPS TO PERFORM FULL UPGRADE
183 183 _step = None
184 184 for step in upgrade_steps:
185 185 notify('performing upgrade step %s' % step)
186 186 time.sleep(0.5)
187 187
188 188 api.upgrade(db_uri, repository_path, step)
189 189 self.sa.rollback()
190 190 notify('schema upgrade for step %s completed' % (step,))
191 191
192 192 _step = step
193 193
194 194 notify('upgrade to version %s successful' % _step)
195 195
196 196 def fix_repo_paths(self):
197 197 """
198 198 Fixes an old RhodeCode version path into new one without a '*'
199 199 """
200 200
201 201 paths = self.sa.query(RhodeCodeUi)\
202 202 .filter(RhodeCodeUi.ui_key == '/')\
203 203 .scalar()
204 204
205 205 paths.ui_value = paths.ui_value.replace('*', '')
206 206
207 207 try:
208 208 self.sa.add(paths)
209 209 self.sa.commit()
210 210 except Exception:
211 211 self.sa.rollback()
212 212 raise
213 213
214 214 def fix_default_user(self):
215 215 """
216 216 Fixes an old default user with some 'nicer' default values,
217 217 used mostly for anonymous access
218 218 """
219 219 def_user = self.sa.query(User)\
220 220 .filter(User.username == User.DEFAULT_USER)\
221 221 .one()
222 222
223 223 def_user.name = 'Anonymous'
224 224 def_user.lastname = 'User'
225 225 def_user.email = User.DEFAULT_USER_EMAIL
226 226
227 227 try:
228 228 self.sa.add(def_user)
229 229 self.sa.commit()
230 230 except Exception:
231 231 self.sa.rollback()
232 232 raise
233 233
234 234 def fix_settings(self):
235 235 """
236 236 Fixes rhodecode settings and adds ga_code key for google analytics
237 237 """
238 238
239 239 hgsettings3 = RhodeCodeSetting('ga_code', '')
240 240
241 241 try:
242 242 self.sa.add(hgsettings3)
243 243 self.sa.commit()
244 244 except Exception:
245 245 self.sa.rollback()
246 246 raise
247 247
248 248 def create_admin_and_prompt(self):
249 249
250 250 # defaults
251 251 defaults = self.cli_args
252 252 username = defaults.get('username')
253 253 password = defaults.get('password')
254 254 email = defaults.get('email')
255 255
256 256 if username is None:
257 257 username = raw_input('Specify admin username:')
258 258 if password is None:
259 259 password = self._get_admin_password()
260 260 if not password:
261 261 # second try
262 262 password = self._get_admin_password()
263 263 if not password:
264 264 sys.exit()
265 265 if email is None:
266 266 email = raw_input('Specify admin email:')
267 267 api_key = self.cli_args.get('api_key')
268 268 self.create_user(username, password, email, True,
269 269 strict_creation_check=False,
270 270 api_key=api_key)
271 271
272 272 def _get_admin_password(self):
273 273 password = getpass.getpass('Specify admin password '
274 274 '(min 6 chars):')
275 275 confirm = getpass.getpass('Confirm password:')
276 276
277 277 if password != confirm:
278 278 log.error('passwords mismatch')
279 279 return False
280 280 if len(password) < 6:
281 281 log.error('password is too short - use at least 6 characters')
282 282 return False
283 283
284 284 return password
285 285
286 286 def create_test_admin_and_users(self):
287 287 log.info('creating admin and regular test users')
288 288 from rhodecode.tests import TEST_USER_ADMIN_LOGIN, \
289 289 TEST_USER_ADMIN_PASS, TEST_USER_ADMIN_EMAIL, \
290 290 TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS, \
291 291 TEST_USER_REGULAR_EMAIL, TEST_USER_REGULAR2_LOGIN, \
292 292 TEST_USER_REGULAR2_PASS, TEST_USER_REGULAR2_EMAIL
293 293
294 294 self.create_user(TEST_USER_ADMIN_LOGIN, TEST_USER_ADMIN_PASS,
295 295 TEST_USER_ADMIN_EMAIL, True, api_key=True)
296 296
297 297 self.create_user(TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS,
298 298 TEST_USER_REGULAR_EMAIL, False, api_key=True)
299 299
300 300 self.create_user(TEST_USER_REGULAR2_LOGIN, TEST_USER_REGULAR2_PASS,
301 301 TEST_USER_REGULAR2_EMAIL, False, api_key=True)
302 302
303 303 def create_ui_settings(self, repo_store_path):
304 304 """
305 305 Creates ui settings, fills out hooks
306 306 and disables dotencode
307 307 """
308 308 settings_model = SettingsModel(sa=self.sa)
309 309 from rhodecode.lib.vcs.backends.hg import largefiles_store
310 310 from rhodecode.lib.vcs.backends.git import lfs_store
311 311
312 312 # Build HOOKS
313 313 hooks = [
314 314 (RhodeCodeUi.HOOK_REPO_SIZE, 'python:vcsserver.hooks.repo_size'),
315 315
316 316 # HG
317 317 (RhodeCodeUi.HOOK_PRE_PULL, 'python:vcsserver.hooks.pre_pull'),
318 318 (RhodeCodeUi.HOOK_PULL, 'python:vcsserver.hooks.log_pull_action'),
319 319 (RhodeCodeUi.HOOK_PRE_PUSH, 'python:vcsserver.hooks.pre_push'),
320 320 (RhodeCodeUi.HOOK_PRETX_PUSH, 'python:vcsserver.hooks.pre_push'),
321 321 (RhodeCodeUi.HOOK_PUSH, 'python:vcsserver.hooks.log_push_action'),
322 322 (RhodeCodeUi.HOOK_PUSH_KEY, 'python:vcsserver.hooks.key_push'),
323 323
324 324 ]
325 325
326 326 for key, value in hooks:
327 327 hook_obj = settings_model.get_ui_by_key(key)
328 328 hooks2 = hook_obj if hook_obj else RhodeCodeUi()
329 329 hooks2.ui_section = 'hooks'
330 330 hooks2.ui_key = key
331 331 hooks2.ui_value = value
332 332 self.sa.add(hooks2)
333 333
334 334 # enable largefiles
335 335 largefiles = RhodeCodeUi()
336 336 largefiles.ui_section = 'extensions'
337 337 largefiles.ui_key = 'largefiles'
338 338 largefiles.ui_value = ''
339 339 self.sa.add(largefiles)
340 340
341 341 # set default largefiles cache dir, defaults to
342 342 # /repo_store_location/.cache/largefiles
343 343 largefiles = RhodeCodeUi()
344 344 largefiles.ui_section = 'largefiles'
345 345 largefiles.ui_key = 'usercache'
346 346 largefiles.ui_value = largefiles_store(repo_store_path)
347 347
348 348 self.sa.add(largefiles)
349 349
350 350 # set default lfs cache dir, defaults to
351 351 # /repo_store_location/.cache/lfs_store
352 352 lfsstore = RhodeCodeUi()
353 353 lfsstore.ui_section = 'vcs_git_lfs'
354 354 lfsstore.ui_key = 'store_location'
355 355 lfsstore.ui_value = lfs_store(repo_store_path)
356 356
357 357 self.sa.add(lfsstore)
358 358
359 359 # enable hgsubversion disabled by default
360 360 hgsubversion = RhodeCodeUi()
361 361 hgsubversion.ui_section = 'extensions'
362 362 hgsubversion.ui_key = 'hgsubversion'
363 363 hgsubversion.ui_value = ''
364 364 hgsubversion.ui_active = False
365 365 self.sa.add(hgsubversion)
366 366
367 367 # enable hgevolve disabled by default
368 368 hgevolve = RhodeCodeUi()
369 369 hgevolve.ui_section = 'extensions'
370 370 hgevolve.ui_key = 'evolve'
371 371 hgevolve.ui_value = ''
372 372 hgevolve.ui_active = False
373 373 self.sa.add(hgevolve)
374 374
375 375 # enable hggit disabled by default
376 376 hggit = RhodeCodeUi()
377 377 hggit.ui_section = 'extensions'
378 378 hggit.ui_key = 'hggit'
379 379 hggit.ui_value = ''
380 380 hggit.ui_active = False
381 381 self.sa.add(hggit)
382 382
383 383 # set svn branch defaults
384 384 branches = ["/branches/*", "/trunk"]
385 385 tags = ["/tags/*"]
386 386
387 387 for branch in branches:
388 388 settings_model.create_ui_section_value(
389 389 RhodeCodeUi.SVN_BRANCH_ID, branch)
390 390
391 391 for tag in tags:
392 392 settings_model.create_ui_section_value(RhodeCodeUi.SVN_TAG_ID, tag)
393 393
394 394 def create_auth_plugin_options(self, skip_existing=False):
395 395 """
396 396 Create default auth plugin settings, and make it active
397 397
398 398 :param skip_existing:
399 399 """
400 400
401 401 for k, v, t in [('auth_plugins', 'egg:rhodecode-enterprise-ce#rhodecode', 'list'),
402 402 ('auth_rhodecode_enabled', 'True', 'bool')]:
403 403 if (skip_existing and
404 404 SettingsModel().get_setting_by_name(k) is not None):
405 405 log.debug('Skipping option %s' % k)
406 406 continue
407 407 setting = RhodeCodeSetting(k, v, t)
408 408 self.sa.add(setting)
409 409
410 410 def create_default_options(self, skip_existing=False):
411 411 """Creates default settings"""
412 412
413 413 for k, v, t in [
414 414 ('default_repo_enable_locking', False, 'bool'),
415 415 ('default_repo_enable_downloads', False, 'bool'),
416 416 ('default_repo_enable_statistics', False, 'bool'),
417 417 ('default_repo_private', False, 'bool'),
418 418 ('default_repo_type', 'hg', 'unicode')]:
419 419
420 420 if (skip_existing and
421 421 SettingsModel().get_setting_by_name(k) is not None):
422 422 log.debug('Skipping option %s' % k)
423 423 continue
424 424 setting = RhodeCodeSetting(k, v, t)
425 425 self.sa.add(setting)
426 426
427 427 def fixup_groups(self):
428 428 def_usr = User.get_default_user()
429 429 for g in RepoGroup.query().all():
430 430 g.group_name = g.get_new_name(g.name)
431 431 self.sa.add(g)
432 432 # get default perm
433 433 default = UserRepoGroupToPerm.query()\
434 434 .filter(UserRepoGroupToPerm.group == g)\
435 435 .filter(UserRepoGroupToPerm.user == def_usr)\
436 436 .scalar()
437 437
438 438 if default is None:
439 439 log.debug('missing default permission for group %s adding' % g)
440 440 perm_obj = RepoGroupModel()._create_default_perms(g)
441 441 self.sa.add(perm_obj)
442 442
443 443 def reset_permissions(self, username):
444 444 """
445 445 Resets permissions to default state, useful when old systems had
446 446 bad permissions, we must clean them up
447 447
448 448 :param username:
449 449 """
450 450 default_user = User.get_by_username(username)
451 451 if not default_user:
452 452 return
453 453
454 454 u2p = UserToPerm.query()\
455 455 .filter(UserToPerm.user == default_user).all()
456 456 fixed = False
457 457 if len(u2p) != len(Permission.DEFAULT_USER_PERMISSIONS):
458 458 for p in u2p:
459 459 Session().delete(p)
460 460 fixed = True
461 461 self.populate_default_permissions()
462 462 return fixed
463 463
464 464 def update_repo_info(self):
465 465 RepoModel.update_repoinfo()
466 466
467 467 def config_prompt(self, test_repo_path='', retries=3):
468 468 defaults = self.cli_args
469 469 _path = defaults.get('repos_location')
470 470 if retries == 3:
471 471 log.info('Setting up repositories config')
472 472
473 473 if _path is not None:
474 474 path = _path
475 475 elif not self.tests and not test_repo_path:
476 476 path = raw_input(
477 477 'Enter a valid absolute path to store repositories. '
478 478 'All repositories in that path will be added automatically:'
479 479 )
480 480 else:
481 481 path = test_repo_path
482 482 path_ok = True
483 483
484 484 # check proper dir
485 485 if not os.path.isdir(path):
486 486 path_ok = False
487 487 log.error('Given path %s is not a valid directory' % (path,))
488 488
489 489 elif not os.path.isabs(path):
490 490 path_ok = False
491 491 log.error('Given path %s is not an absolute path' % (path,))
492 492
493 493 # check if path is at least readable.
494 494 if not os.access(path, os.R_OK):
495 495 path_ok = False
496 496 log.error('Given path %s is not readable' % (path,))
497 497
498 498 # check write access, warn user about non writeable paths
499 499 elif not os.access(path, os.W_OK) and path_ok:
500 500 log.warning('No write permission to given path %s' % (path,))
501 501
502 502 q = ('Given path %s is not writeable, do you want to '
503 503 'continue with read only mode ? [y/n]' % (path,))
504 504 if not self.ask_ok(q):
505 505 log.error('Canceled by user')
506 506 sys.exit(-1)
507 507
508 508 if retries == 0:
509 509 sys.exit('max retries reached')
510 510 if not path_ok:
511 511 retries -= 1
512 512 return self.config_prompt(test_repo_path, retries)
513 513
514 514 real_path = os.path.normpath(os.path.realpath(path))
515 515
516 516 if real_path != os.path.normpath(path):
517 517 q = ('Path looks like a symlink, RhodeCode Enterprise will store '
518 518 'given path as %s ? [y/n]') % (real_path,)
519 519 if not self.ask_ok(q):
520 520 log.error('Canceled by user')
521 521 sys.exit(-1)
522 522
523 523 return real_path
524 524
525 525 def create_settings(self, path):
526 526
527 527 self.create_ui_settings(path)
528 528
529 529 ui_config = [
530 530 ('web', 'push_ssl', 'False'),
531 531 ('web', 'allow_archive', 'gz zip bz2'),
532 532 ('web', 'allow_push', '*'),
533 533 ('web', 'baseurl', '/'),
534 534 ('paths', '/', path),
535 535 ('phases', 'publish', 'True')
536 536 ]
537 537 for section, key, value in ui_config:
538 538 ui_conf = RhodeCodeUi()
539 539 setattr(ui_conf, 'ui_section', section)
540 540 setattr(ui_conf, 'ui_key', key)
541 541 setattr(ui_conf, 'ui_value', value)
542 542 self.sa.add(ui_conf)
543 543
544 544 # rhodecode app settings
545 545 settings = [
546 546 ('realm', 'RhodeCode', 'unicode'),
547 547 ('title', '', 'unicode'),
548 548 ('pre_code', '', 'unicode'),
549 549 ('post_code', '', 'unicode'),
550 550 ('show_public_icon', True, 'bool'),
551 551 ('show_private_icon', True, 'bool'),
552 552 ('stylify_metatags', False, 'bool'),
553 553 ('dashboard_items', 100, 'int'),
554 554 ('admin_grid_items', 25, 'int'),
555 555 ('show_version', True, 'bool'),
556 556 ('use_gravatar', False, 'bool'),
557 557 ('gravatar_url', User.DEFAULT_GRAVATAR_URL, 'unicode'),
558 558 ('clone_uri_tmpl', Repository.DEFAULT_CLONE_URI, 'unicode'),
559 559 ('support_url', '', 'unicode'),
560 560 ('update_url', RhodeCodeSetting.DEFAULT_UPDATE_URL, 'unicode'),
561 561 ('show_revision_number', True, 'bool'),
562 562 ('show_sha_length', 12, 'int'),
563 563 ]
564 564
565 565 for key, val, type_ in settings:
566 566 sett = RhodeCodeSetting(key, val, type_)
567 567 self.sa.add(sett)
568 568
569 569 self.create_auth_plugin_options()
570 570 self.create_default_options()
571 571
572 572 log.info('created ui config')
573 573
574 574 def create_user(self, username, password, email='', admin=False,
575 575 strict_creation_check=True, api_key=None):
576 log.info('creating user %s' % username)
576 log.info('creating user `%s`' % username)
577 577 user = UserModel().create_or_update(
578 578 username, password, email, firstname=u'RhodeCode', lastname=u'Admin',
579 579 active=True, admin=admin, extern_type="rhodecode",
580 580 strict_creation_check=strict_creation_check)
581 581
582 582 if api_key:
583 log.info('setting a provided api key for the user %s', username)
584 from rhodecode.model.auth_token import AuthTokenModel
585 AuthTokenModel().create(
586 user=user, description=u'BUILTIN TOKEN')
583 log.info('setting a new default auth token for user `%s`', username)
584 UserModel().add_auth_token(
585 user=user, lifetime_minutes=-1,
586 role=UserModel.auth_token_role.ROLE_ALL,
587 description=u'BUILTIN TOKEN')
587 588
588 589 def create_default_user(self):
589 590 log.info('creating default user')
590 591 # create default user for handling default permissions.
591 592 user = UserModel().create_or_update(username=User.DEFAULT_USER,
592 593 password=str(uuid.uuid1())[:20],
593 594 email=User.DEFAULT_USER_EMAIL,
594 595 firstname=u'Anonymous',
595 596 lastname=u'User',
596 597 strict_creation_check=False)
597 # based on configuration options activate/deactive this user which
598 # based on configuration options activate/de-activate this user which
598 599 # controlls anonymous access
599 600 if self.cli_args.get('public_access') is False:
600 601 log.info('Public access disabled')
601 602 user.active = False
602 603 Session().add(user)
603 604 Session().commit()
604 605
605 606 def create_permissions(self):
606 607 """
607 608 Creates all permissions defined in the system
608 609 """
609 610 # module.(access|create|change|delete)_[name]
610 611 # module.(none|read|write|admin)
611 612 log.info('creating permissions')
612 613 PermissionModel(self.sa).create_permissions()
613 614
614 615 def populate_default_permissions(self):
615 616 """
616 617 Populate default permissions. It will create only the default
617 618 permissions that are missing, and not alter already defined ones
618 619 """
619 620 log.info('creating default user permissions')
620 621 PermissionModel(self.sa).create_default_user_permissions(user=User.DEFAULT_USER)
@@ -1,918 +1,940 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2018 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 users model for RhodeCode
23 23 """
24 24
25 25 import logging
26 26 import traceback
27 27 import datetime
28 28 import ipaddress
29 29
30 30 from pyramid.threadlocal import get_current_request
31 31 from sqlalchemy.exc import DatabaseError
32 32
33 33 from rhodecode import events
34 34 from rhodecode.lib.user_log_filter import user_log_filter
35 35 from rhodecode.lib.utils2 import (
36 36 safe_unicode, get_current_rhodecode_user, action_logger_generic,
37 37 AttributeDict, str2bool)
38 38 from rhodecode.lib.exceptions import (
39 39 DefaultUserException, UserOwnsReposException, UserOwnsRepoGroupsException,
40 40 UserOwnsUserGroupsException, NotAllowedToCreateUserError)
41 41 from rhodecode.lib.caching_query import FromCache
42 42 from rhodecode.model import BaseModel
43 43 from rhodecode.model.auth_token import AuthTokenModel
44 44 from rhodecode.model.db import (
45 45 _hash_key, true, false, or_, joinedload, User, UserToPerm,
46 46 UserEmailMap, UserIpMap, UserLog)
47 47 from rhodecode.model.meta import Session
48 48 from rhodecode.model.repo_group import RepoGroupModel
49 49
50 50
51 51 log = logging.getLogger(__name__)
52 52
53 53
54 54 class UserModel(BaseModel):
55 55 cls = User
56 56
57 57 def get(self, user_id, cache=False):
58 58 user = self.sa.query(User)
59 59 if cache:
60 60 user = user.options(
61 61 FromCache("sql_cache_short", "get_user_%s" % user_id))
62 62 return user.get(user_id)
63 63
64 64 def get_user(self, user):
65 65 return self._get_user(user)
66 66
67 67 def _serialize_user(self, user):
68 68 import rhodecode.lib.helpers as h
69 69
70 70 return {
71 71 'id': user.user_id,
72 72 'first_name': user.first_name,
73 73 'last_name': user.last_name,
74 74 'username': user.username,
75 75 'email': user.email,
76 76 'icon_link': h.gravatar_url(user.email, 30),
77 77 'profile_link': h.link_to_user(user),
78 78 'value_display': h.escape(h.person(user)),
79 79 'value': user.username,
80 80 'value_type': 'user',
81 81 'active': user.active,
82 82 }
83 83
84 84 def get_users(self, name_contains=None, limit=20, only_active=True):
85 85
86 86 query = self.sa.query(User)
87 87 if only_active:
88 88 query = query.filter(User.active == true())
89 89
90 90 if name_contains:
91 91 ilike_expression = u'%{}%'.format(safe_unicode(name_contains))
92 92 query = query.filter(
93 93 or_(
94 94 User.name.ilike(ilike_expression),
95 95 User.lastname.ilike(ilike_expression),
96 96 User.username.ilike(ilike_expression)
97 97 )
98 98 )
99 99 query = query.limit(limit)
100 100 users = query.all()
101 101
102 102 _users = [
103 103 self._serialize_user(user) for user in users
104 104 ]
105 105 return _users
106 106
107 107 def get_by_username(self, username, cache=False, case_insensitive=False):
108 108
109 109 if case_insensitive:
110 110 user = self.sa.query(User).filter(User.username.ilike(username))
111 111 else:
112 112 user = self.sa.query(User)\
113 113 .filter(User.username == username)
114 114 if cache:
115 115 name_key = _hash_key(username)
116 116 user = user.options(
117 117 FromCache("sql_cache_short", "get_user_%s" % name_key))
118 118 return user.scalar()
119 119
120 120 def get_by_email(self, email, cache=False, case_insensitive=False):
121 121 return User.get_by_email(email, case_insensitive, cache)
122 122
123 123 def get_by_auth_token(self, auth_token, cache=False):
124 124 return User.get_by_auth_token(auth_token, cache)
125 125
126 126 def get_active_user_count(self, cache=False):
127 127 qry = User.query().filter(
128 128 User.active == true()).filter(
129 129 User.username != User.DEFAULT_USER)
130 130 if cache:
131 131 qry = qry.options(
132 132 FromCache("sql_cache_short", "get_active_users"))
133 133 return qry.count()
134 134
135 135 def create(self, form_data, cur_user=None):
136 136 if not cur_user:
137 137 cur_user = getattr(get_current_rhodecode_user(), 'username', None)
138 138
139 139 user_data = {
140 140 'username': form_data['username'],
141 141 'password': form_data['password'],
142 142 'email': form_data['email'],
143 143 'firstname': form_data['firstname'],
144 144 'lastname': form_data['lastname'],
145 145 'active': form_data['active'],
146 146 'extern_type': form_data['extern_type'],
147 147 'extern_name': form_data['extern_name'],
148 148 'admin': False,
149 149 'cur_user': cur_user
150 150 }
151 151
152 152 if 'create_repo_group' in form_data:
153 153 user_data['create_repo_group'] = str2bool(
154 154 form_data.get('create_repo_group'))
155 155
156 156 try:
157 157 if form_data.get('password_change'):
158 158 user_data['force_password_change'] = True
159 159 return UserModel().create_or_update(**user_data)
160 160 except Exception:
161 161 log.error(traceback.format_exc())
162 162 raise
163 163
164 164 def update_user(self, user, skip_attrs=None, **kwargs):
165 165 from rhodecode.lib.auth import get_crypt_password
166 166
167 167 user = self._get_user(user)
168 168 if user.username == User.DEFAULT_USER:
169 169 raise DefaultUserException(
170 170 "You can't edit this user (`%(username)s`) since it's "
171 171 "crucial for entire application" % {
172 172 'username': user.username})
173 173
174 174 # first store only defaults
175 175 user_attrs = {
176 176 'updating_user_id': user.user_id,
177 177 'username': user.username,
178 178 'password': user.password,
179 179 'email': user.email,
180 180 'firstname': user.name,
181 181 'lastname': user.lastname,
182 182 'active': user.active,
183 183 'admin': user.admin,
184 184 'extern_name': user.extern_name,
185 185 'extern_type': user.extern_type,
186 186 'language': user.user_data.get('language')
187 187 }
188 188
189 189 # in case there's new_password, that comes from form, use it to
190 190 # store password
191 191 if kwargs.get('new_password'):
192 192 kwargs['password'] = kwargs['new_password']
193 193
194 194 # cleanups, my_account password change form
195 195 kwargs.pop('current_password', None)
196 196 kwargs.pop('new_password', None)
197 197
198 198 # cleanups, user edit password change form
199 199 kwargs.pop('password_confirmation', None)
200 200 kwargs.pop('password_change', None)
201 201
202 202 # create repo group on user creation
203 203 kwargs.pop('create_repo_group', None)
204 204
205 205 # legacy forms send name, which is the firstname
206 206 firstname = kwargs.pop('name', None)
207 207 if firstname:
208 208 kwargs['firstname'] = firstname
209 209
210 210 for k, v in kwargs.items():
211 211 # skip if we don't want to update this
212 212 if skip_attrs and k in skip_attrs:
213 213 continue
214 214
215 215 user_attrs[k] = v
216 216
217 217 try:
218 218 return self.create_or_update(**user_attrs)
219 219 except Exception:
220 220 log.error(traceback.format_exc())
221 221 raise
222 222
223 223 def create_or_update(
224 224 self, username, password, email, firstname='', lastname='',
225 225 active=True, admin=False, extern_type=None, extern_name=None,
226 226 cur_user=None, plugin=None, force_password_change=False,
227 227 allow_to_create_user=True, create_repo_group=None,
228 228 updating_user_id=None, language=None, strict_creation_check=True):
229 229 """
230 230 Creates a new instance if not found, or updates current one
231 231
232 232 :param username:
233 233 :param password:
234 234 :param email:
235 235 :param firstname:
236 236 :param lastname:
237 237 :param active:
238 238 :param admin:
239 239 :param extern_type:
240 240 :param extern_name:
241 241 :param cur_user:
242 242 :param plugin: optional plugin this method was called from
243 243 :param force_password_change: toggles new or existing user flag
244 244 for password change
245 245 :param allow_to_create_user: Defines if the method can actually create
246 246 new users
247 247 :param create_repo_group: Defines if the method should also
248 248 create an repo group with user name, and owner
249 249 :param updating_user_id: if we set it up this is the user we want to
250 250 update this allows to editing username.
251 251 :param language: language of user from interface.
252 252
253 253 :returns: new User object with injected `is_new_user` attribute.
254 254 """
255 255
256 256 if not cur_user:
257 257 cur_user = getattr(get_current_rhodecode_user(), 'username', None)
258 258
259 259 from rhodecode.lib.auth import (
260 260 get_crypt_password, check_password, generate_auth_token)
261 261 from rhodecode.lib.hooks_base import (
262 262 log_create_user, check_allowed_create_user)
263 263
264 264 def _password_change(new_user, password):
265 265 old_password = new_user.password or ''
266 266 # empty password
267 267 if not old_password:
268 268 return False
269 269
270 270 # password check is only needed for RhodeCode internal auth calls
271 271 # in case it's a plugin we don't care
272 272 if not plugin:
273 273
274 274 # first check if we gave crypted password back, and if it
275 275 # matches it's not password change
276 276 if new_user.password == password:
277 277 return False
278 278
279 279 password_match = check_password(password, old_password)
280 280 if not password_match:
281 281 return True
282 282
283 283 return False
284 284
285 285 # read settings on default personal repo group creation
286 286 if create_repo_group is None:
287 287 default_create_repo_group = RepoGroupModel()\
288 288 .get_default_create_personal_repo_group()
289 289 create_repo_group = default_create_repo_group
290 290
291 291 user_data = {
292 292 'username': username,
293 293 'password': password,
294 294 'email': email,
295 295 'firstname': firstname,
296 296 'lastname': lastname,
297 297 'active': active,
298 298 'admin': admin
299 299 }
300 300
301 301 if updating_user_id:
302 302 log.debug('Checking for existing account in RhodeCode '
303 303 'database with user_id `%s` ' % (updating_user_id,))
304 304 user = User.get(updating_user_id)
305 305 else:
306 306 log.debug('Checking for existing account in RhodeCode '
307 307 'database with username `%s` ' % (username,))
308 308 user = User.get_by_username(username, case_insensitive=True)
309 309
310 310 if user is None:
311 311 # we check internal flag if this method is actually allowed to
312 312 # create new user
313 313 if not allow_to_create_user:
314 314 msg = ('Method wants to create new user, but it is not '
315 315 'allowed to do so')
316 316 log.warning(msg)
317 317 raise NotAllowedToCreateUserError(msg)
318 318
319 319 log.debug('Creating new user %s', username)
320 320
321 321 # only if we create user that is active
322 322 new_active_user = active
323 323 if new_active_user and strict_creation_check:
324 324 # raises UserCreationError if it's not allowed for any reason to
325 325 # create new active user, this also executes pre-create hooks
326 326 check_allowed_create_user(user_data, cur_user, strict_check=True)
327 327 events.trigger(events.UserPreCreate(user_data))
328 328 new_user = User()
329 329 edit = False
330 330 else:
331 331 log.debug('updating user %s', username)
332 332 events.trigger(events.UserPreUpdate(user, user_data))
333 333 new_user = user
334 334 edit = True
335 335
336 336 # we're not allowed to edit default user
337 337 if user.username == User.DEFAULT_USER:
338 338 raise DefaultUserException(
339 339 "You can't edit this user (`%(username)s`) since it's "
340 340 "crucial for entire application"
341 341 % {'username': user.username})
342 342
343 343 # inject special attribute that will tell us if User is new or old
344 344 new_user.is_new_user = not edit
345 345 # for users that didn's specify auth type, we use RhodeCode built in
346 346 from rhodecode.authentication.plugins import auth_rhodecode
347 347 extern_name = extern_name or auth_rhodecode.RhodeCodeAuthPlugin.name
348 348 extern_type = extern_type or auth_rhodecode.RhodeCodeAuthPlugin.name
349 349
350 350 try:
351 351 new_user.username = username
352 352 new_user.admin = admin
353 353 new_user.email = email
354 354 new_user.active = active
355 355 new_user.extern_name = safe_unicode(extern_name)
356 356 new_user.extern_type = safe_unicode(extern_type)
357 357 new_user.name = firstname
358 358 new_user.lastname = lastname
359 359
360 360 # set password only if creating an user or password is changed
361 361 if not edit or _password_change(new_user, password):
362 362 reason = 'new password' if edit else 'new user'
363 363 log.debug('Updating password reason=>%s', reason)
364 364 new_user.password = get_crypt_password(password) if password else None
365 365
366 366 if force_password_change:
367 367 new_user.update_userdata(force_password_change=True)
368 368 if language:
369 369 new_user.update_userdata(language=language)
370 370 new_user.update_userdata(notification_status=True)
371 371
372 372 self.sa.add(new_user)
373 373
374 374 if not edit and create_repo_group:
375 375 RepoGroupModel().create_personal_repo_group(
376 376 new_user, commit_early=False)
377 377
378 378 if not edit:
379 379 # add the RSS token
380 AuthTokenModel().create(username,
381 description=u'Generated feed token',
382 role=AuthTokenModel.cls.ROLE_FEED)
380 self.add_auth_token(
381 user=username, lifetime_minutes=-1,
382 role=self.auth_token_role.ROLE_FEED,
383 description=u'Generated feed token')
384
383 385 kwargs = new_user.get_dict()
384 386 # backward compat, require api_keys present
385 387 kwargs['api_keys'] = kwargs['auth_tokens']
386 388 log_create_user(created_by=cur_user, **kwargs)
387 389 events.trigger(events.UserPostCreate(user_data))
388 390 return new_user
389 391 except (DatabaseError,):
390 392 log.error(traceback.format_exc())
391 393 raise
392 394
393 395 def create_registration(self, form_data):
394 396 from rhodecode.model.notification import NotificationModel
395 397 from rhodecode.model.notification import EmailNotificationModel
396 398
397 399 try:
398 400 form_data['admin'] = False
399 401 form_data['extern_name'] = 'rhodecode'
400 402 form_data['extern_type'] = 'rhodecode'
401 403 new_user = self.create(form_data)
402 404
403 405 self.sa.add(new_user)
404 406 self.sa.flush()
405 407
406 408 user_data = new_user.get_dict()
407 409 kwargs = {
408 410 # use SQLALCHEMY safe dump of user data
409 411 'user': AttributeDict(user_data),
410 412 'date': datetime.datetime.now()
411 413 }
412 414 notification_type = EmailNotificationModel.TYPE_REGISTRATION
413 415 # pre-generate the subject for notification itself
414 416 (subject,
415 417 _h, _e, # we don't care about those
416 418 body_plaintext) = EmailNotificationModel().render_email(
417 419 notification_type, **kwargs)
418 420
419 421 # create notification objects, and emails
420 422 NotificationModel().create(
421 423 created_by=new_user,
422 424 notification_subject=subject,
423 425 notification_body=body_plaintext,
424 426 notification_type=notification_type,
425 427 recipients=None, # all admins
426 428 email_kwargs=kwargs,
427 429 )
428 430
429 431 return new_user
430 432 except Exception:
431 433 log.error(traceback.format_exc())
432 434 raise
433 435
434 436 def _handle_user_repos(self, username, repositories, handle_mode=None):
435 437 _superadmin = self.cls.get_first_super_admin()
436 438 left_overs = True
437 439
438 440 from rhodecode.model.repo import RepoModel
439 441
440 442 if handle_mode == 'detach':
441 443 for obj in repositories:
442 444 obj.user = _superadmin
443 445 # set description we know why we super admin now owns
444 446 # additional repositories that were orphaned !
445 447 obj.description += ' \n::detached repository from deleted user: %s' % (username,)
446 448 self.sa.add(obj)
447 449 left_overs = False
448 450 elif handle_mode == 'delete':
449 451 for obj in repositories:
450 452 RepoModel().delete(obj, forks='detach')
451 453 left_overs = False
452 454
453 455 # if nothing is done we have left overs left
454 456 return left_overs
455 457
456 458 def _handle_user_repo_groups(self, username, repository_groups,
457 459 handle_mode=None):
458 460 _superadmin = self.cls.get_first_super_admin()
459 461 left_overs = True
460 462
461 463 from rhodecode.model.repo_group import RepoGroupModel
462 464
463 465 if handle_mode == 'detach':
464 466 for r in repository_groups:
465 467 r.user = _superadmin
466 468 # set description we know why we super admin now owns
467 469 # additional repositories that were orphaned !
468 470 r.group_description += ' \n::detached repository group from deleted user: %s' % (username,)
469 471 self.sa.add(r)
470 472 left_overs = False
471 473 elif handle_mode == 'delete':
472 474 for r in repository_groups:
473 475 RepoGroupModel().delete(r)
474 476 left_overs = False
475 477
476 478 # if nothing is done we have left overs left
477 479 return left_overs
478 480
479 481 def _handle_user_user_groups(self, username, user_groups, handle_mode=None):
480 482 _superadmin = self.cls.get_first_super_admin()
481 483 left_overs = True
482 484
483 485 from rhodecode.model.user_group import UserGroupModel
484 486
485 487 if handle_mode == 'detach':
486 488 for r in user_groups:
487 489 for user_user_group_to_perm in r.user_user_group_to_perm:
488 490 if user_user_group_to_perm.user.username == username:
489 491 user_user_group_to_perm.user = _superadmin
490 492 r.user = _superadmin
491 493 # set description we know why we super admin now owns
492 494 # additional repositories that were orphaned !
493 495 r.user_group_description += ' \n::detached user group from deleted user: %s' % (username,)
494 496 self.sa.add(r)
495 497 left_overs = False
496 498 elif handle_mode == 'delete':
497 499 for r in user_groups:
498 500 UserGroupModel().delete(r)
499 501 left_overs = False
500 502
501 503 # if nothing is done we have left overs left
502 504 return left_overs
503 505
504 506 def delete(self, user, cur_user=None, handle_repos=None,
505 507 handle_repo_groups=None, handle_user_groups=None):
506 508 if not cur_user:
507 509 cur_user = getattr(
508 510 get_current_rhodecode_user(), 'username', None)
509 511 user = self._get_user(user)
510 512
511 513 try:
512 514 if user.username == User.DEFAULT_USER:
513 515 raise DefaultUserException(
514 516 u"You can't remove this user since it's"
515 517 u" crucial for entire application")
516 518
517 519 left_overs = self._handle_user_repos(
518 520 user.username, user.repositories, handle_repos)
519 521 if left_overs and user.repositories:
520 522 repos = [x.repo_name for x in user.repositories]
521 523 raise UserOwnsReposException(
522 524 u'user "%(username)s" still owns %(len_repos)s repositories and cannot be '
523 525 u'removed. Switch owners or remove those repositories:%(list_repos)s'
524 526 % {'username': user.username, 'len_repos': len(repos),
525 527 'list_repos': ', '.join(repos)})
526 528
527 529 left_overs = self._handle_user_repo_groups(
528 530 user.username, user.repository_groups, handle_repo_groups)
529 531 if left_overs and user.repository_groups:
530 532 repo_groups = [x.group_name for x in user.repository_groups]
531 533 raise UserOwnsRepoGroupsException(
532 534 u'user "%(username)s" still owns %(len_repo_groups)s repository groups and cannot be '
533 535 u'removed. Switch owners or remove those repository groups:%(list_repo_groups)s'
534 536 % {'username': user.username, 'len_repo_groups': len(repo_groups),
535 537 'list_repo_groups': ', '.join(repo_groups)})
536 538
537 539 left_overs = self._handle_user_user_groups(
538 540 user.username, user.user_groups, handle_user_groups)
539 541 if left_overs and user.user_groups:
540 542 user_groups = [x.users_group_name for x in user.user_groups]
541 543 raise UserOwnsUserGroupsException(
542 544 u'user "%s" still owns %s user groups and cannot be '
543 545 u'removed. Switch owners or remove those user groups:%s'
544 546 % (user.username, len(user_groups), ', '.join(user_groups)))
545 547
546 548 # we might change the user data with detach/delete, make sure
547 549 # the object is marked as expired before actually deleting !
548 550 self.sa.expire(user)
549 551 self.sa.delete(user)
550 552 from rhodecode.lib.hooks_base import log_delete_user
551 553 log_delete_user(deleted_by=cur_user, **user.get_dict())
552 554 except Exception:
553 555 log.error(traceback.format_exc())
554 556 raise
555 557
556 558 def reset_password_link(self, data, pwd_reset_url):
557 559 from rhodecode.lib.celerylib import tasks, run_task
558 560 from rhodecode.model.notification import EmailNotificationModel
559 561 user_email = data['email']
560 562 try:
561 563 user = User.get_by_email(user_email)
562 564 if user:
563 565 log.debug('password reset user found %s', user)
564 566
565 567 email_kwargs = {
566 568 'password_reset_url': pwd_reset_url,
567 569 'user': user,
568 570 'email': user_email,
569 571 'date': datetime.datetime.now()
570 572 }
571 573
572 574 (subject, headers, email_body,
573 575 email_body_plaintext) = EmailNotificationModel().render_email(
574 576 EmailNotificationModel.TYPE_PASSWORD_RESET, **email_kwargs)
575 577
576 578 recipients = [user_email]
577 579
578 580 action_logger_generic(
579 581 'sending password reset email to user: {}'.format(
580 582 user), namespace='security.password_reset')
581 583
582 584 run_task(tasks.send_email, recipients, subject,
583 585 email_body_plaintext, email_body)
584 586
585 587 else:
586 588 log.debug("password reset email %s not found", user_email)
587 589 except Exception:
588 590 log.error(traceback.format_exc())
589 591 return False
590 592
591 593 return True
592 594
593 595 def reset_password(self, data):
594 596 from rhodecode.lib.celerylib import tasks, run_task
595 597 from rhodecode.model.notification import EmailNotificationModel
596 598 from rhodecode.lib import auth
597 599 user_email = data['email']
598 600 pre_db = True
599 601 try:
600 602 user = User.get_by_email(user_email)
601 603 new_passwd = auth.PasswordGenerator().gen_password(
602 604 12, auth.PasswordGenerator.ALPHABETS_BIG_SMALL)
603 605 if user:
604 606 user.password = auth.get_crypt_password(new_passwd)
605 607 # also force this user to reset his password !
606 608 user.update_userdata(force_password_change=True)
607 609
608 610 Session().add(user)
609 611
610 612 # now delete the token in question
611 613 UserApiKeys = AuthTokenModel.cls
612 614 UserApiKeys().query().filter(
613 615 UserApiKeys.api_key == data['token']).delete()
614 616
615 617 Session().commit()
616 618 log.info('successfully reset password for `%s`', user_email)
617 619
618 620 if new_passwd is None:
619 621 raise Exception('unable to generate new password')
620 622
621 623 pre_db = False
622 624
623 625 email_kwargs = {
624 626 'new_password': new_passwd,
625 627 'user': user,
626 628 'email': user_email,
627 629 'date': datetime.datetime.now()
628 630 }
629 631
630 632 (subject, headers, email_body,
631 633 email_body_plaintext) = EmailNotificationModel().render_email(
632 634 EmailNotificationModel.TYPE_PASSWORD_RESET_CONFIRMATION,
633 635 **email_kwargs)
634 636
635 637 recipients = [user_email]
636 638
637 639 action_logger_generic(
638 640 'sent new password to user: {} with email: {}'.format(
639 641 user, user_email), namespace='security.password_reset')
640 642
641 643 run_task(tasks.send_email, recipients, subject,
642 644 email_body_plaintext, email_body)
643 645
644 646 except Exception:
645 647 log.error('Failed to update user password')
646 648 log.error(traceback.format_exc())
647 649 if pre_db:
648 650 # we rollback only if local db stuff fails. If it goes into
649 651 # run_task, we're pass rollback state this wouldn't work then
650 652 Session().rollback()
651 653
652 654 return True
653 655
654 656 def fill_data(self, auth_user, user_id=None, api_key=None, username=None):
655 657 """
656 658 Fetches auth_user by user_id,or api_key if present.
657 659 Fills auth_user attributes with those taken from database.
658 660 Additionally set's is_authenitated if lookup fails
659 661 present in database
660 662
661 663 :param auth_user: instance of user to set attributes
662 664 :param user_id: user id to fetch by
663 665 :param api_key: api key to fetch by
664 666 :param username: username to fetch by
665 667 """
666 668 def token_obfuscate(token):
667 669 if token:
668 670 return token[:4] + "****"
669 671
670 672 if user_id is None and api_key is None and username is None:
671 673 raise Exception('You need to pass user_id, api_key or username')
672 674
673 675 log.debug(
674 676 'AuthUser: fill data execution based on: '
675 677 'user_id:%s api_key:%s username:%s', user_id, api_key, username)
676 678 try:
677 679 dbuser = None
678 680 if user_id:
679 681 dbuser = self.get(user_id)
680 682 elif api_key:
681 683 dbuser = self.get_by_auth_token(api_key)
682 684 elif username:
683 685 dbuser = self.get_by_username(username)
684 686
685 687 if not dbuser:
686 688 log.warning(
687 689 'Unable to lookup user by id:%s api_key:%s username:%s',
688 690 user_id, token_obfuscate(api_key), username)
689 691 return False
690 692 if not dbuser.active:
691 693 log.debug('User `%s:%s` is inactive, skipping fill data',
692 694 username, user_id)
693 695 return False
694 696
695 697 log.debug('AuthUser: filling found user:%s data', dbuser)
696 698 user_data = dbuser.get_dict()
697 699
698 700 user_data.update({
699 701 # set explicit the safe escaped values
700 702 'first_name': dbuser.first_name,
701 703 'last_name': dbuser.last_name,
702 704 })
703 705
704 706 for k, v in user_data.items():
705 707 # properties of auth user we dont update
706 708 if k not in ['auth_tokens', 'permissions']:
707 709 setattr(auth_user, k, v)
708 710
709 711 except Exception:
710 712 log.error(traceback.format_exc())
711 713 auth_user.is_authenticated = False
712 714 return False
713 715
714 716 return True
715 717
716 718 def has_perm(self, user, perm):
717 719 perm = self._get_perm(perm)
718 720 user = self._get_user(user)
719 721
720 722 return UserToPerm.query().filter(UserToPerm.user == user)\
721 723 .filter(UserToPerm.permission == perm).scalar() is not None
722 724
723 725 def grant_perm(self, user, perm):
724 726 """
725 727 Grant user global permissions
726 728
727 729 :param user:
728 730 :param perm:
729 731 """
730 732 user = self._get_user(user)
731 733 perm = self._get_perm(perm)
732 734 # if this permission is already granted skip it
733 735 _perm = UserToPerm.query()\
734 736 .filter(UserToPerm.user == user)\
735 737 .filter(UserToPerm.permission == perm)\
736 738 .scalar()
737 739 if _perm:
738 740 return
739 741 new = UserToPerm()
740 742 new.user = user
741 743 new.permission = perm
742 744 self.sa.add(new)
743 745 return new
744 746
745 747 def revoke_perm(self, user, perm):
746 748 """
747 749 Revoke users global permissions
748 750
749 751 :param user:
750 752 :param perm:
751 753 """
752 754 user = self._get_user(user)
753 755 perm = self._get_perm(perm)
754 756
755 757 obj = UserToPerm.query()\
756 758 .filter(UserToPerm.user == user)\
757 759 .filter(UserToPerm.permission == perm)\
758 760 .scalar()
759 761 if obj:
760 762 self.sa.delete(obj)
761 763
762 764 def add_extra_email(self, user, email):
763 765 """
764 766 Adds email address to UserEmailMap
765 767
766 768 :param user:
767 769 :param email:
768 770 """
769 771
770 772 user = self._get_user(user)
771 773
772 774 obj = UserEmailMap()
773 775 obj.user = user
774 776 obj.email = email
775 777 self.sa.add(obj)
776 778 return obj
777 779
778 780 def delete_extra_email(self, user, email_id):
779 781 """
780 782 Removes email address from UserEmailMap
781 783
782 784 :param user:
783 785 :param email_id:
784 786 """
785 787 user = self._get_user(user)
786 788 obj = UserEmailMap.query().get(email_id)
787 789 if obj and obj.user_id == user.user_id:
788 790 self.sa.delete(obj)
789 791
790 792 def parse_ip_range(self, ip_range):
791 793 ip_list = []
792 794
793 795 def make_unique(value):
794 796 seen = []
795 797 return [c for c in value if not (c in seen or seen.append(c))]
796 798
797 799 # firsts split by commas
798 800 for ip_range in ip_range.split(','):
799 801 if not ip_range:
800 802 continue
801 803 ip_range = ip_range.strip()
802 804 if '-' in ip_range:
803 805 start_ip, end_ip = ip_range.split('-', 1)
804 806 start_ip = ipaddress.ip_address(safe_unicode(start_ip.strip()))
805 807 end_ip = ipaddress.ip_address(safe_unicode(end_ip.strip()))
806 808 parsed_ip_range = []
807 809
808 810 for index in xrange(int(start_ip), int(end_ip) + 1):
809 811 new_ip = ipaddress.ip_address(index)
810 812 parsed_ip_range.append(str(new_ip))
811 813 ip_list.extend(parsed_ip_range)
812 814 else:
813 815 ip_list.append(ip_range)
814 816
815 817 return make_unique(ip_list)
816 818
817 819 def add_extra_ip(self, user, ip, description=None):
818 820 """
819 821 Adds ip address to UserIpMap
820 822
821 823 :param user:
822 824 :param ip:
823 825 """
824 826
825 827 user = self._get_user(user)
826 828 obj = UserIpMap()
827 829 obj.user = user
828 830 obj.ip_addr = ip
829 831 obj.description = description
830 832 self.sa.add(obj)
831 833 return obj
832 834
835 auth_token_role = AuthTokenModel.cls
836
837 def add_auth_token(self, user, lifetime_minutes, role, description=u'',
838 scope_callback=None):
839 """
840 Add AuthToken for user.
841
842 :param user: username/user_id
843 :param lifetime_minutes: in minutes the lifetime for token, -1 equals no limit
844 :param role: one of AuthTokenModel.cls.ROLE_*
845 :param description: optional string description
846 """
847
848 token = AuthTokenModel().create(
849 user, description, lifetime_minutes, role)
850 if scope_callback and callable(scope_callback):
851 # call the callback if we provide, used to attach scope for EE edition
852 scope_callback(token)
853 return token
854
833 855 def delete_extra_ip(self, user, ip_id):
834 856 """
835 857 Removes ip address from UserIpMap
836 858
837 859 :param user:
838 860 :param ip_id:
839 861 """
840 862 user = self._get_user(user)
841 863 obj = UserIpMap.query().get(ip_id)
842 864 if obj and obj.user_id == user.user_id:
843 865 self.sa.delete(obj)
844 866
845 867 def get_accounts_in_creation_order(self, current_user=None):
846 868 """
847 869 Get accounts in order of creation for deactivation for license limits
848 870
849 871 pick currently logged in user, and append to the list in position 0
850 872 pick all super-admins in order of creation date and add it to the list
851 873 pick all other accounts in order of creation and add it to the list.
852 874
853 875 Based on that list, the last accounts can be disabled as they are
854 876 created at the end and don't include any of the super admins as well
855 877 as the current user.
856 878
857 879 :param current_user: optionally current user running this operation
858 880 """
859 881
860 882 if not current_user:
861 883 current_user = get_current_rhodecode_user()
862 884 active_super_admins = [
863 885 x.user_id for x in User.query()
864 886 .filter(User.user_id != current_user.user_id)
865 887 .filter(User.active == true())
866 888 .filter(User.admin == true())
867 889 .order_by(User.created_on.asc())]
868 890
869 891 active_regular_users = [
870 892 x.user_id for x in User.query()
871 893 .filter(User.user_id != current_user.user_id)
872 894 .filter(User.active == true())
873 895 .filter(User.admin == false())
874 896 .order_by(User.created_on.asc())]
875 897
876 898 list_of_accounts = [current_user.user_id]
877 899 list_of_accounts += active_super_admins
878 900 list_of_accounts += active_regular_users
879 901
880 902 return list_of_accounts
881 903
882 904 def deactivate_last_users(self, expected_users, current_user=None):
883 905 """
884 906 Deactivate accounts that are over the license limits.
885 907 Algorithm of which accounts to disabled is based on the formula:
886 908
887 909 Get current user, then super admins in creation order, then regular
888 910 active users in creation order.
889 911
890 912 Using that list we mark all accounts from the end of it as inactive.
891 913 This way we block only latest created accounts.
892 914
893 915 :param expected_users: list of users in special order, we deactivate
894 916 the end N ammoun of users from that list
895 917 """
896 918
897 919 list_of_accounts = self.get_accounts_in_creation_order(
898 920 current_user=current_user)
899 921
900 922 for acc_id in list_of_accounts[expected_users + 1:]:
901 923 user = User.get(acc_id)
902 924 log.info('Deactivating account %s for license unlock', user)
903 925 user.active = False
904 926 Session().add(user)
905 927 Session().commit()
906 928
907 929 return
908 930
909 931 def get_user_log(self, user, filter_term):
910 932 user_log = UserLog.query()\
911 933 .filter(or_(UserLog.user_id == user.user_id,
912 934 UserLog.username == user.username))\
913 935 .options(joinedload(UserLog.user))\
914 936 .options(joinedload(UserLog.repository))\
915 937 .order_by(UserLog.action_date.desc())
916 938
917 939 user_log = user_log_filter(user_log, filter_term)
918 940 return user_log
General Comments 0
You need to be logged in to leave comments. Login now