##// END OF EJS Templates
users: autocomplete now sorts by matched username to show best matches first.
marcink -
r4518:0cddd671 stable
parent child Browse files
Show More
@@ -1,1046 +1,1050 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2020 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 """
22 22 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 UserOwnsPullRequestsException, UserOwnsArtifactsException)
42 42 from rhodecode.lib.caching_query import FromCache
43 43 from rhodecode.model import BaseModel
44 44 from rhodecode.model.db import (
45 _hash_key, true, false, or_, joinedload, User, UserToPerm,
45 _hash_key, func, true, false, or_, joinedload, User, UserToPerm,
46 46 UserEmailMap, UserIpMap, UserLog)
47 47 from rhodecode.model.meta import Session
48 48 from rhodecode.model.auth_token import AuthTokenModel
49 49 from rhodecode.model.repo_group import RepoGroupModel
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 # sort by len to have top most matches first
100 query = query.order_by(func.length(User.username))\
101 .order_by(User.username)
99 102 query = query.limit(limit)
103
100 104 users = query.all()
101 105
102 106 _users = [
103 107 self._serialize_user(user) for user in users
104 108 ]
105 109 return _users
106 110
107 111 def get_by_username(self, username, cache=False, case_insensitive=False):
108 112
109 113 if case_insensitive:
110 114 user = self.sa.query(User).filter(User.username.ilike(username))
111 115 else:
112 116 user = self.sa.query(User)\
113 117 .filter(User.username == username)
114 118 if cache:
115 119 name_key = _hash_key(username)
116 120 user = user.options(
117 121 FromCache("sql_cache_short", "get_user_%s" % name_key))
118 122 return user.scalar()
119 123
120 124 def get_by_email(self, email, cache=False, case_insensitive=False):
121 125 return User.get_by_email(email, case_insensitive, cache)
122 126
123 127 def get_by_auth_token(self, auth_token, cache=False):
124 128 return User.get_by_auth_token(auth_token, cache)
125 129
126 130 def get_active_user_count(self, cache=False):
127 131 qry = User.query().filter(
128 132 User.active == true()).filter(
129 133 User.username != User.DEFAULT_USER)
130 134 if cache:
131 135 qry = qry.options(
132 136 FromCache("sql_cache_short", "get_active_users"))
133 137 return qry.count()
134 138
135 139 def create(self, form_data, cur_user=None):
136 140 if not cur_user:
137 141 cur_user = getattr(get_current_rhodecode_user(), 'username', None)
138 142
139 143 user_data = {
140 144 'username': form_data['username'],
141 145 'password': form_data['password'],
142 146 'email': form_data['email'],
143 147 'firstname': form_data['firstname'],
144 148 'lastname': form_data['lastname'],
145 149 'active': form_data['active'],
146 150 'extern_type': form_data['extern_type'],
147 151 'extern_name': form_data['extern_name'],
148 152 'admin': False,
149 153 'cur_user': cur_user
150 154 }
151 155
152 156 if 'create_repo_group' in form_data:
153 157 user_data['create_repo_group'] = str2bool(
154 158 form_data.get('create_repo_group'))
155 159
156 160 try:
157 161 if form_data.get('password_change'):
158 162 user_data['force_password_change'] = True
159 163 return UserModel().create_or_update(**user_data)
160 164 except Exception:
161 165 log.error(traceback.format_exc())
162 166 raise
163 167
164 168 def update_user(self, user, skip_attrs=None, **kwargs):
165 169 from rhodecode.lib.auth import get_crypt_password
166 170
167 171 user = self._get_user(user)
168 172 if user.username == User.DEFAULT_USER:
169 173 raise DefaultUserException(
170 174 "You can't edit this user (`%(username)s`) since it's "
171 175 "crucial for entire application" % {
172 176 'username': user.username})
173 177
174 178 # first store only defaults
175 179 user_attrs = {
176 180 'updating_user_id': user.user_id,
177 181 'username': user.username,
178 182 'password': user.password,
179 183 'email': user.email,
180 184 'firstname': user.name,
181 185 'lastname': user.lastname,
182 186 'description': user.description,
183 187 'active': user.active,
184 188 'admin': user.admin,
185 189 'extern_name': user.extern_name,
186 190 'extern_type': user.extern_type,
187 191 'language': user.user_data.get('language')
188 192 }
189 193
190 194 # in case there's new_password, that comes from form, use it to
191 195 # store password
192 196 if kwargs.get('new_password'):
193 197 kwargs['password'] = kwargs['new_password']
194 198
195 199 # cleanups, my_account password change form
196 200 kwargs.pop('current_password', None)
197 201 kwargs.pop('new_password', None)
198 202
199 203 # cleanups, user edit password change form
200 204 kwargs.pop('password_confirmation', None)
201 205 kwargs.pop('password_change', None)
202 206
203 207 # create repo group on user creation
204 208 kwargs.pop('create_repo_group', None)
205 209
206 210 # legacy forms send name, which is the firstname
207 211 firstname = kwargs.pop('name', None)
208 212 if firstname:
209 213 kwargs['firstname'] = firstname
210 214
211 215 for k, v in kwargs.items():
212 216 # skip if we don't want to update this
213 217 if skip_attrs and k in skip_attrs:
214 218 continue
215 219
216 220 user_attrs[k] = v
217 221
218 222 try:
219 223 return self.create_or_update(**user_attrs)
220 224 except Exception:
221 225 log.error(traceback.format_exc())
222 226 raise
223 227
224 228 def create_or_update(
225 229 self, username, password, email, firstname='', lastname='',
226 230 active=True, admin=False, extern_type=None, extern_name=None,
227 231 cur_user=None, plugin=None, force_password_change=False,
228 232 allow_to_create_user=True, create_repo_group=None,
229 233 updating_user_id=None, language=None, description='',
230 234 strict_creation_check=True):
231 235 """
232 236 Creates a new instance if not found, or updates current one
233 237
234 238 :param username:
235 239 :param password:
236 240 :param email:
237 241 :param firstname:
238 242 :param lastname:
239 243 :param active:
240 244 :param admin:
241 245 :param extern_type:
242 246 :param extern_name:
243 247 :param cur_user:
244 248 :param plugin: optional plugin this method was called from
245 249 :param force_password_change: toggles new or existing user flag
246 250 for password change
247 251 :param allow_to_create_user: Defines if the method can actually create
248 252 new users
249 253 :param create_repo_group: Defines if the method should also
250 254 create an repo group with user name, and owner
251 255 :param updating_user_id: if we set it up this is the user we want to
252 256 update this allows to editing username.
253 257 :param language: language of user from interface.
254 258 :param description: user description
255 259 :param strict_creation_check: checks for allowed creation license wise etc.
256 260
257 261 :returns: new User object with injected `is_new_user` attribute.
258 262 """
259 263
260 264 if not cur_user:
261 265 cur_user = getattr(get_current_rhodecode_user(), 'username', None)
262 266
263 267 from rhodecode.lib.auth import (
264 268 get_crypt_password, check_password)
265 269 from rhodecode.lib import hooks_base
266 270
267 271 def _password_change(new_user, password):
268 272 old_password = new_user.password or ''
269 273 # empty password
270 274 if not old_password:
271 275 return False
272 276
273 277 # password check is only needed for RhodeCode internal auth calls
274 278 # in case it's a plugin we don't care
275 279 if not plugin:
276 280
277 281 # first check if we gave crypted password back, and if it
278 282 # matches it's not password change
279 283 if new_user.password == password:
280 284 return False
281 285
282 286 password_match = check_password(password, old_password)
283 287 if not password_match:
284 288 return True
285 289
286 290 return False
287 291
288 292 # read settings on default personal repo group creation
289 293 if create_repo_group is None:
290 294 default_create_repo_group = RepoGroupModel()\
291 295 .get_default_create_personal_repo_group()
292 296 create_repo_group = default_create_repo_group
293 297
294 298 user_data = {
295 299 'username': username,
296 300 'password': password,
297 301 'email': email,
298 302 'firstname': firstname,
299 303 'lastname': lastname,
300 304 'active': active,
301 305 'admin': admin
302 306 }
303 307
304 308 if updating_user_id:
305 309 log.debug('Checking for existing account in RhodeCode '
306 310 'database with user_id `%s` ', updating_user_id)
307 311 user = User.get(updating_user_id)
308 312 else:
309 313 log.debug('Checking for existing account in RhodeCode '
310 314 'database with username `%s` ', username)
311 315 user = User.get_by_username(username, case_insensitive=True)
312 316
313 317 if user is None:
314 318 # we check internal flag if this method is actually allowed to
315 319 # create new user
316 320 if not allow_to_create_user:
317 321 msg = ('Method wants to create new user, but it is not '
318 322 'allowed to do so')
319 323 log.warning(msg)
320 324 raise NotAllowedToCreateUserError(msg)
321 325
322 326 log.debug('Creating new user %s', username)
323 327
324 328 # only if we create user that is active
325 329 new_active_user = active
326 330 if new_active_user and strict_creation_check:
327 331 # raises UserCreationError if it's not allowed for any reason to
328 332 # create new active user, this also executes pre-create hooks
329 333 hooks_base.check_allowed_create_user(user_data, cur_user, strict_check=True)
330 334 events.trigger(events.UserPreCreate(user_data))
331 335 new_user = User()
332 336 edit = False
333 337 else:
334 338 log.debug('updating user `%s`', username)
335 339 events.trigger(events.UserPreUpdate(user, user_data))
336 340 new_user = user
337 341 edit = True
338 342
339 343 # we're not allowed to edit default user
340 344 if user.username == User.DEFAULT_USER:
341 345 raise DefaultUserException(
342 346 "You can't edit this user (`%(username)s`) since it's "
343 347 "crucial for entire application"
344 348 % {'username': user.username})
345 349
346 350 # inject special attribute that will tell us if User is new or old
347 351 new_user.is_new_user = not edit
348 352 # for users that didn's specify auth type, we use RhodeCode built in
349 353 from rhodecode.authentication.plugins import auth_rhodecode
350 354 extern_name = extern_name or auth_rhodecode.RhodeCodeAuthPlugin.uid
351 355 extern_type = extern_type or auth_rhodecode.RhodeCodeAuthPlugin.uid
352 356
353 357 try:
354 358 new_user.username = username
355 359 new_user.admin = admin
356 360 new_user.email = email
357 361 new_user.active = active
358 362 new_user.extern_name = safe_unicode(extern_name)
359 363 new_user.extern_type = safe_unicode(extern_type)
360 364 new_user.name = firstname
361 365 new_user.lastname = lastname
362 366 new_user.description = description
363 367
364 368 # set password only if creating an user or password is changed
365 369 if not edit or _password_change(new_user, password):
366 370 reason = 'new password' if edit else 'new user'
367 371 log.debug('Updating password reason=>%s', reason)
368 372 new_user.password = get_crypt_password(password) if password else None
369 373
370 374 if force_password_change:
371 375 new_user.update_userdata(force_password_change=True)
372 376 if language:
373 377 new_user.update_userdata(language=language)
374 378 new_user.update_userdata(notification_status=True)
375 379
376 380 self.sa.add(new_user)
377 381
378 382 if not edit and create_repo_group:
379 383 RepoGroupModel().create_personal_repo_group(
380 384 new_user, commit_early=False)
381 385
382 386 if not edit:
383 387 # add the RSS token
384 388 self.add_auth_token(
385 389 user=username, lifetime_minutes=-1,
386 390 role=self.auth_token_role.ROLE_FEED,
387 391 description=u'Generated feed token')
388 392
389 393 kwargs = new_user.get_dict()
390 394 # backward compat, require api_keys present
391 395 kwargs['api_keys'] = kwargs['auth_tokens']
392 396 hooks_base.create_user(created_by=cur_user, **kwargs)
393 397 events.trigger(events.UserPostCreate(user_data))
394 398 return new_user
395 399 except (DatabaseError,):
396 400 log.error(traceback.format_exc())
397 401 raise
398 402
399 403 def create_registration(self, form_data,
400 404 extern_name='rhodecode', extern_type='rhodecode'):
401 405 from rhodecode.model.notification import NotificationModel
402 406 from rhodecode.model.notification import EmailNotificationModel
403 407
404 408 try:
405 409 form_data['admin'] = False
406 410 form_data['extern_name'] = extern_name
407 411 form_data['extern_type'] = extern_type
408 412 new_user = self.create(form_data)
409 413
410 414 self.sa.add(new_user)
411 415 self.sa.flush()
412 416
413 417 user_data = new_user.get_dict()
414 418 user_data.update({
415 419 'first_name': user_data.get('firstname'),
416 420 'last_name': user_data.get('lastname'),
417 421 })
418 422 kwargs = {
419 423 # use SQLALCHEMY safe dump of user data
420 424 'user': AttributeDict(user_data),
421 425 'date': datetime.datetime.now()
422 426 }
423 427 notification_type = EmailNotificationModel.TYPE_REGISTRATION
424 428 # pre-generate the subject for notification itself
425 429 (subject, _e, body_plaintext) = EmailNotificationModel().render_email(
426 430 notification_type, **kwargs)
427 431
428 432 # create notification objects, and emails
429 433 NotificationModel().create(
430 434 created_by=new_user,
431 435 notification_subject=subject,
432 436 notification_body=body_plaintext,
433 437 notification_type=notification_type,
434 438 recipients=None, # all admins
435 439 email_kwargs=kwargs,
436 440 )
437 441
438 442 return new_user
439 443 except Exception:
440 444 log.error(traceback.format_exc())
441 445 raise
442 446
443 447 def _handle_user_repos(self, username, repositories, handle_user,
444 448 handle_mode=None):
445 449
446 450 left_overs = True
447 451
448 452 from rhodecode.model.repo import RepoModel
449 453
450 454 if handle_mode == 'detach':
451 455 for obj in repositories:
452 456 obj.user = handle_user
453 457 # set description we know why we super admin now owns
454 458 # additional repositories that were orphaned !
455 459 obj.description += ' \n::detached repository from deleted user: %s' % (username,)
456 460 self.sa.add(obj)
457 461 left_overs = False
458 462 elif handle_mode == 'delete':
459 463 for obj in repositories:
460 464 RepoModel().delete(obj, forks='detach')
461 465 left_overs = False
462 466
463 467 # if nothing is done we have left overs left
464 468 return left_overs
465 469
466 470 def _handle_user_repo_groups(self, username, repository_groups, handle_user,
467 471 handle_mode=None):
468 472
469 473 left_overs = True
470 474
471 475 from rhodecode.model.repo_group import RepoGroupModel
472 476
473 477 if handle_mode == 'detach':
474 478 for r in repository_groups:
475 479 r.user = handle_user
476 480 # set description we know why we super admin now owns
477 481 # additional repositories that were orphaned !
478 482 r.group_description += ' \n::detached repository group from deleted user: %s' % (username,)
479 483 r.personal = False
480 484 self.sa.add(r)
481 485 left_overs = False
482 486 elif handle_mode == 'delete':
483 487 for r in repository_groups:
484 488 RepoGroupModel().delete(r)
485 489 left_overs = False
486 490
487 491 # if nothing is done we have left overs left
488 492 return left_overs
489 493
490 494 def _handle_user_user_groups(self, username, user_groups, handle_user,
491 495 handle_mode=None):
492 496
493 497 left_overs = True
494 498
495 499 from rhodecode.model.user_group import UserGroupModel
496 500
497 501 if handle_mode == 'detach':
498 502 for r in user_groups:
499 503 for user_user_group_to_perm in r.user_user_group_to_perm:
500 504 if user_user_group_to_perm.user.username == username:
501 505 user_user_group_to_perm.user = handle_user
502 506 r.user = handle_user
503 507 # set description we know why we super admin now owns
504 508 # additional repositories that were orphaned !
505 509 r.user_group_description += ' \n::detached user group from deleted user: %s' % (username,)
506 510 self.sa.add(r)
507 511 left_overs = False
508 512 elif handle_mode == 'delete':
509 513 for r in user_groups:
510 514 UserGroupModel().delete(r)
511 515 left_overs = False
512 516
513 517 # if nothing is done we have left overs left
514 518 return left_overs
515 519
516 520 def _handle_user_pull_requests(self, username, pull_requests, handle_user,
517 521 handle_mode=None):
518 522 left_overs = True
519 523
520 524 from rhodecode.model.pull_request import PullRequestModel
521 525
522 526 if handle_mode == 'detach':
523 527 for pr in pull_requests:
524 528 pr.user_id = handle_user.user_id
525 529 # set description we know why we super admin now owns
526 530 # additional repositories that were orphaned !
527 531 pr.description += ' \n::detached pull requests from deleted user: %s' % (username,)
528 532 self.sa.add(pr)
529 533 left_overs = False
530 534 elif handle_mode == 'delete':
531 535 for pr in pull_requests:
532 536 PullRequestModel().delete(pr)
533 537
534 538 left_overs = False
535 539
536 540 # if nothing is done we have left overs left
537 541 return left_overs
538 542
539 543 def _handle_user_artifacts(self, username, artifacts, handle_user,
540 544 handle_mode=None):
541 545
542 546 left_overs = True
543 547
544 548 if handle_mode == 'detach':
545 549 for a in artifacts:
546 550 a.upload_user = handle_user
547 551 # set description we know why we super admin now owns
548 552 # additional artifacts that were orphaned !
549 553 a.file_description += ' \n::detached artifact from deleted user: %s' % (username,)
550 554 self.sa.add(a)
551 555 left_overs = False
552 556 elif handle_mode == 'delete':
553 557 from rhodecode.apps.file_store import utils as store_utils
554 558 request = get_current_request()
555 559 storage = store_utils.get_file_storage(request.registry.settings)
556 560 for a in artifacts:
557 561 file_uid = a.file_uid
558 562 storage.delete(file_uid)
559 563 self.sa.delete(a)
560 564
561 565 left_overs = False
562 566
563 567 # if nothing is done we have left overs left
564 568 return left_overs
565 569
566 570 def delete(self, user, cur_user=None, handle_repos=None,
567 571 handle_repo_groups=None, handle_user_groups=None,
568 572 handle_pull_requests=None, handle_artifacts=None, handle_new_owner=None):
569 573 from rhodecode.lib import hooks_base
570 574
571 575 if not cur_user:
572 576 cur_user = getattr(get_current_rhodecode_user(), 'username', None)
573 577
574 578 user = self._get_user(user)
575 579
576 580 try:
577 581 if user.username == User.DEFAULT_USER:
578 582 raise DefaultUserException(
579 583 u"You can't remove this user since it's"
580 584 u" crucial for entire application")
581 585 handle_user = handle_new_owner or self.cls.get_first_super_admin()
582 586 log.debug('New detached objects owner %s', handle_user)
583 587
584 588 left_overs = self._handle_user_repos(
585 589 user.username, user.repositories, handle_user, handle_repos)
586 590 if left_overs and user.repositories:
587 591 repos = [x.repo_name for x in user.repositories]
588 592 raise UserOwnsReposException(
589 593 u'user "%(username)s" still owns %(len_repos)s repositories and cannot be '
590 594 u'removed. Switch owners or remove those repositories:%(list_repos)s'
591 595 % {'username': user.username, 'len_repos': len(repos),
592 596 'list_repos': ', '.join(repos)})
593 597
594 598 left_overs = self._handle_user_repo_groups(
595 599 user.username, user.repository_groups, handle_user, handle_repo_groups)
596 600 if left_overs and user.repository_groups:
597 601 repo_groups = [x.group_name for x in user.repository_groups]
598 602 raise UserOwnsRepoGroupsException(
599 603 u'user "%(username)s" still owns %(len_repo_groups)s repository groups and cannot be '
600 604 u'removed. Switch owners or remove those repository groups:%(list_repo_groups)s'
601 605 % {'username': user.username, 'len_repo_groups': len(repo_groups),
602 606 'list_repo_groups': ', '.join(repo_groups)})
603 607
604 608 left_overs = self._handle_user_user_groups(
605 609 user.username, user.user_groups, handle_user, handle_user_groups)
606 610 if left_overs and user.user_groups:
607 611 user_groups = [x.users_group_name for x in user.user_groups]
608 612 raise UserOwnsUserGroupsException(
609 613 u'user "%s" still owns %s user groups and cannot be '
610 614 u'removed. Switch owners or remove those user groups:%s'
611 615 % (user.username, len(user_groups), ', '.join(user_groups)))
612 616
613 617 left_overs = self._handle_user_pull_requests(
614 618 user.username, user.user_pull_requests, handle_user, handle_pull_requests)
615 619 if left_overs and user.user_pull_requests:
616 620 pull_requests = ['!{}'.format(x.pull_request_id) for x in user.user_pull_requests]
617 621 raise UserOwnsPullRequestsException(
618 622 u'user "%s" still owns %s pull requests and cannot be '
619 623 u'removed. Switch owners or remove those pull requests:%s'
620 624 % (user.username, len(pull_requests), ', '.join(pull_requests)))
621 625
622 626 left_overs = self._handle_user_artifacts(
623 627 user.username, user.artifacts, handle_user, handle_artifacts)
624 628 if left_overs and user.artifacts:
625 629 artifacts = [x.file_uid for x in user.artifacts]
626 630 raise UserOwnsArtifactsException(
627 631 u'user "%s" still owns %s artifacts and cannot be '
628 632 u'removed. Switch owners or remove those artifacts:%s'
629 633 % (user.username, len(artifacts), ', '.join(artifacts)))
630 634
631 635 user_data = user.get_dict() # fetch user data before expire
632 636
633 637 # we might change the user data with detach/delete, make sure
634 638 # the object is marked as expired before actually deleting !
635 639 self.sa.expire(user)
636 640 self.sa.delete(user)
637 641
638 642 hooks_base.delete_user(deleted_by=cur_user, **user_data)
639 643 except Exception:
640 644 log.error(traceback.format_exc())
641 645 raise
642 646
643 647 def reset_password_link(self, data, pwd_reset_url):
644 648 from rhodecode.lib.celerylib import tasks, run_task
645 649 from rhodecode.model.notification import EmailNotificationModel
646 650 user_email = data['email']
647 651 try:
648 652 user = User.get_by_email(user_email)
649 653 if user:
650 654 log.debug('password reset user found %s', user)
651 655
652 656 email_kwargs = {
653 657 'password_reset_url': pwd_reset_url,
654 658 'user': user,
655 659 'email': user_email,
656 660 'date': datetime.datetime.now(),
657 661 'first_admin_email': User.get_first_super_admin().email
658 662 }
659 663
660 664 (subject, email_body, email_body_plaintext) = EmailNotificationModel().render_email(
661 665 EmailNotificationModel.TYPE_PASSWORD_RESET, **email_kwargs)
662 666
663 667 recipients = [user_email]
664 668
665 669 action_logger_generic(
666 670 'sending password reset email to user: {}'.format(
667 671 user), namespace='security.password_reset')
668 672
669 673 run_task(tasks.send_email, recipients, subject,
670 674 email_body_plaintext, email_body)
671 675
672 676 else:
673 677 log.debug("password reset email %s not found", user_email)
674 678 except Exception:
675 679 log.error(traceback.format_exc())
676 680 return False
677 681
678 682 return True
679 683
680 684 def reset_password(self, data):
681 685 from rhodecode.lib.celerylib import tasks, run_task
682 686 from rhodecode.model.notification import EmailNotificationModel
683 687 from rhodecode.lib import auth
684 688 user_email = data['email']
685 689 pre_db = True
686 690 try:
687 691 user = User.get_by_email(user_email)
688 692 new_passwd = auth.PasswordGenerator().gen_password(
689 693 12, auth.PasswordGenerator.ALPHABETS_BIG_SMALL)
690 694 if user:
691 695 user.password = auth.get_crypt_password(new_passwd)
692 696 # also force this user to reset his password !
693 697 user.update_userdata(force_password_change=True)
694 698
695 699 Session().add(user)
696 700
697 701 # now delete the token in question
698 702 UserApiKeys = AuthTokenModel.cls
699 703 UserApiKeys().query().filter(
700 704 UserApiKeys.api_key == data['token']).delete()
701 705
702 706 Session().commit()
703 707 log.info('successfully reset password for `%s`', user_email)
704 708
705 709 if new_passwd is None:
706 710 raise Exception('unable to generate new password')
707 711
708 712 pre_db = False
709 713
710 714 email_kwargs = {
711 715 'new_password': new_passwd,
712 716 'user': user,
713 717 'email': user_email,
714 718 'date': datetime.datetime.now(),
715 719 'first_admin_email': User.get_first_super_admin().email
716 720 }
717 721
718 722 (subject, email_body, email_body_plaintext) = EmailNotificationModel().render_email(
719 723 EmailNotificationModel.TYPE_PASSWORD_RESET_CONFIRMATION,
720 724 **email_kwargs)
721 725
722 726 recipients = [user_email]
723 727
724 728 action_logger_generic(
725 729 'sent new password to user: {} with email: {}'.format(
726 730 user, user_email), namespace='security.password_reset')
727 731
728 732 run_task(tasks.send_email, recipients, subject,
729 733 email_body_plaintext, email_body)
730 734
731 735 except Exception:
732 736 log.error('Failed to update user password')
733 737 log.error(traceback.format_exc())
734 738 if pre_db:
735 739 # we rollback only if local db stuff fails. If it goes into
736 740 # run_task, we're pass rollback state this wouldn't work then
737 741 Session().rollback()
738 742
739 743 return True
740 744
741 745 def fill_data(self, auth_user, user_id=None, api_key=None, username=None):
742 746 """
743 747 Fetches auth_user by user_id,or api_key if present.
744 748 Fills auth_user attributes with those taken from database.
745 749 Additionally set's is_authenitated if lookup fails
746 750 present in database
747 751
748 752 :param auth_user: instance of user to set attributes
749 753 :param user_id: user id to fetch by
750 754 :param api_key: api key to fetch by
751 755 :param username: username to fetch by
752 756 """
753 757 def token_obfuscate(token):
754 758 if token:
755 759 return token[:4] + "****"
756 760
757 761 if user_id is None and api_key is None and username is None:
758 762 raise Exception('You need to pass user_id, api_key or username')
759 763
760 764 log.debug(
761 765 'AuthUser: fill data execution based on: '
762 766 'user_id:%s api_key:%s username:%s', user_id, api_key, username)
763 767 try:
764 768 dbuser = None
765 769 if user_id:
766 770 dbuser = self.get(user_id)
767 771 elif api_key:
768 772 dbuser = self.get_by_auth_token(api_key)
769 773 elif username:
770 774 dbuser = self.get_by_username(username)
771 775
772 776 if not dbuser:
773 777 log.warning(
774 778 'Unable to lookup user by id:%s api_key:%s username:%s',
775 779 user_id, token_obfuscate(api_key), username)
776 780 return False
777 781 if not dbuser.active:
778 782 log.debug('User `%s:%s` is inactive, skipping fill data',
779 783 username, user_id)
780 784 return False
781 785
782 786 log.debug('AuthUser: filling found user:%s data', dbuser)
783 787
784 788 attrs = {
785 789 'user_id': dbuser.user_id,
786 790 'username': dbuser.username,
787 791 'name': dbuser.name,
788 792 'first_name': dbuser.first_name,
789 793 'firstname': dbuser.firstname,
790 794 'last_name': dbuser.last_name,
791 795 'lastname': dbuser.lastname,
792 796 'admin': dbuser.admin,
793 797 'active': dbuser.active,
794 798
795 799 'email': dbuser.email,
796 800 'emails': dbuser.emails_cached(),
797 801 'short_contact': dbuser.short_contact,
798 802 'full_contact': dbuser.full_contact,
799 803 'full_name': dbuser.full_name,
800 804 'full_name_or_username': dbuser.full_name_or_username,
801 805
802 806 '_api_key': dbuser._api_key,
803 807 '_user_data': dbuser._user_data,
804 808
805 809 'created_on': dbuser.created_on,
806 810 'extern_name': dbuser.extern_name,
807 811 'extern_type': dbuser.extern_type,
808 812
809 813 'inherit_default_permissions': dbuser.inherit_default_permissions,
810 814
811 815 'language': dbuser.language,
812 816 'last_activity': dbuser.last_activity,
813 817 'last_login': dbuser.last_login,
814 818 'password': dbuser.password,
815 819 }
816 820 auth_user.__dict__.update(attrs)
817 821 except Exception:
818 822 log.error(traceback.format_exc())
819 823 auth_user.is_authenticated = False
820 824 return False
821 825
822 826 return True
823 827
824 828 def has_perm(self, user, perm):
825 829 perm = self._get_perm(perm)
826 830 user = self._get_user(user)
827 831
828 832 return UserToPerm.query().filter(UserToPerm.user == user)\
829 833 .filter(UserToPerm.permission == perm).scalar() is not None
830 834
831 835 def grant_perm(self, user, perm):
832 836 """
833 837 Grant user global permissions
834 838
835 839 :param user:
836 840 :param perm:
837 841 """
838 842 user = self._get_user(user)
839 843 perm = self._get_perm(perm)
840 844 # if this permission is already granted skip it
841 845 _perm = UserToPerm.query()\
842 846 .filter(UserToPerm.user == user)\
843 847 .filter(UserToPerm.permission == perm)\
844 848 .scalar()
845 849 if _perm:
846 850 return
847 851 new = UserToPerm()
848 852 new.user = user
849 853 new.permission = perm
850 854 self.sa.add(new)
851 855 return new
852 856
853 857 def revoke_perm(self, user, perm):
854 858 """
855 859 Revoke users global permissions
856 860
857 861 :param user:
858 862 :param perm:
859 863 """
860 864 user = self._get_user(user)
861 865 perm = self._get_perm(perm)
862 866
863 867 obj = UserToPerm.query()\
864 868 .filter(UserToPerm.user == user)\
865 869 .filter(UserToPerm.permission == perm)\
866 870 .scalar()
867 871 if obj:
868 872 self.sa.delete(obj)
869 873
870 874 def add_extra_email(self, user, email):
871 875 """
872 876 Adds email address to UserEmailMap
873 877
874 878 :param user:
875 879 :param email:
876 880 """
877 881
878 882 user = self._get_user(user)
879 883
880 884 obj = UserEmailMap()
881 885 obj.user = user
882 886 obj.email = email
883 887 self.sa.add(obj)
884 888 return obj
885 889
886 890 def delete_extra_email(self, user, email_id):
887 891 """
888 892 Removes email address from UserEmailMap
889 893
890 894 :param user:
891 895 :param email_id:
892 896 """
893 897 user = self._get_user(user)
894 898 obj = UserEmailMap.query().get(email_id)
895 899 if obj and obj.user_id == user.user_id:
896 900 self.sa.delete(obj)
897 901
898 902 def parse_ip_range(self, ip_range):
899 903 ip_list = []
900 904
901 905 def make_unique(value):
902 906 seen = []
903 907 return [c for c in value if not (c in seen or seen.append(c))]
904 908
905 909 # firsts split by commas
906 910 for ip_range in ip_range.split(','):
907 911 if not ip_range:
908 912 continue
909 913 ip_range = ip_range.strip()
910 914 if '-' in ip_range:
911 915 start_ip, end_ip = ip_range.split('-', 1)
912 916 start_ip = ipaddress.ip_address(safe_unicode(start_ip.strip()))
913 917 end_ip = ipaddress.ip_address(safe_unicode(end_ip.strip()))
914 918 parsed_ip_range = []
915 919
916 920 for index in range(int(start_ip), int(end_ip) + 1):
917 921 new_ip = ipaddress.ip_address(index)
918 922 parsed_ip_range.append(str(new_ip))
919 923 ip_list.extend(parsed_ip_range)
920 924 else:
921 925 ip_list.append(ip_range)
922 926
923 927 return make_unique(ip_list)
924 928
925 929 def add_extra_ip(self, user, ip, description=None):
926 930 """
927 931 Adds ip address to UserIpMap
928 932
929 933 :param user:
930 934 :param ip:
931 935 """
932 936
933 937 user = self._get_user(user)
934 938 obj = UserIpMap()
935 939 obj.user = user
936 940 obj.ip_addr = ip
937 941 obj.description = description
938 942 self.sa.add(obj)
939 943 return obj
940 944
941 945 auth_token_role = AuthTokenModel.cls
942 946
943 947 def add_auth_token(self, user, lifetime_minutes, role, description=u'',
944 948 scope_callback=None):
945 949 """
946 950 Add AuthToken for user.
947 951
948 952 :param user: username/user_id
949 953 :param lifetime_minutes: in minutes the lifetime for token, -1 equals no limit
950 954 :param role: one of AuthTokenModel.cls.ROLE_*
951 955 :param description: optional string description
952 956 """
953 957
954 958 token = AuthTokenModel().create(
955 959 user, description, lifetime_minutes, role)
956 960 if scope_callback and callable(scope_callback):
957 961 # call the callback if we provide, used to attach scope for EE edition
958 962 scope_callback(token)
959 963 return token
960 964
961 965 def delete_extra_ip(self, user, ip_id):
962 966 """
963 967 Removes ip address from UserIpMap
964 968
965 969 :param user:
966 970 :param ip_id:
967 971 """
968 972 user = self._get_user(user)
969 973 obj = UserIpMap.query().get(ip_id)
970 974 if obj and obj.user_id == user.user_id:
971 975 self.sa.delete(obj)
972 976
973 977 def get_accounts_in_creation_order(self, current_user=None):
974 978 """
975 979 Get accounts in order of creation for deactivation for license limits
976 980
977 981 pick currently logged in user, and append to the list in position 0
978 982 pick all super-admins in order of creation date and add it to the list
979 983 pick all other accounts in order of creation and add it to the list.
980 984
981 985 Based on that list, the last accounts can be disabled as they are
982 986 created at the end and don't include any of the super admins as well
983 987 as the current user.
984 988
985 989 :param current_user: optionally current user running this operation
986 990 """
987 991
988 992 if not current_user:
989 993 current_user = get_current_rhodecode_user()
990 994 active_super_admins = [
991 995 x.user_id for x in User.query()
992 996 .filter(User.user_id != current_user.user_id)
993 997 .filter(User.active == true())
994 998 .filter(User.admin == true())
995 999 .order_by(User.created_on.asc())]
996 1000
997 1001 active_regular_users = [
998 1002 x.user_id for x in User.query()
999 1003 .filter(User.user_id != current_user.user_id)
1000 1004 .filter(User.active == true())
1001 1005 .filter(User.admin == false())
1002 1006 .order_by(User.created_on.asc())]
1003 1007
1004 1008 list_of_accounts = [current_user.user_id]
1005 1009 list_of_accounts += active_super_admins
1006 1010 list_of_accounts += active_regular_users
1007 1011
1008 1012 return list_of_accounts
1009 1013
1010 1014 def deactivate_last_users(self, expected_users, current_user=None):
1011 1015 """
1012 1016 Deactivate accounts that are over the license limits.
1013 1017 Algorithm of which accounts to disabled is based on the formula:
1014 1018
1015 1019 Get current user, then super admins in creation order, then regular
1016 1020 active users in creation order.
1017 1021
1018 1022 Using that list we mark all accounts from the end of it as inactive.
1019 1023 This way we block only latest created accounts.
1020 1024
1021 1025 :param expected_users: list of users in special order, we deactivate
1022 1026 the end N amount of users from that list
1023 1027 """
1024 1028
1025 1029 list_of_accounts = self.get_accounts_in_creation_order(
1026 1030 current_user=current_user)
1027 1031
1028 1032 for acc_id in list_of_accounts[expected_users + 1:]:
1029 1033 user = User.get(acc_id)
1030 1034 log.info('Deactivating account %s for license unlock', user)
1031 1035 user.active = False
1032 1036 Session().add(user)
1033 1037 Session().commit()
1034 1038
1035 1039 return
1036 1040
1037 1041 def get_user_log(self, user, filter_term):
1038 1042 user_log = UserLog.query()\
1039 1043 .filter(or_(UserLog.user_id == user.user_id,
1040 1044 UserLog.username == user.username))\
1041 1045 .options(joinedload(UserLog.user))\
1042 1046 .options(joinedload(UserLog.repository))\
1043 1047 .order_by(UserLog.action_date.desc())
1044 1048
1045 1049 user_log = user_log_filter(user_log, filter_term)
1046 1050 return user_log
General Comments 0
You need to be logged in to leave comments. Login now