##// END OF EJS Templates
users: fetch user data for user removal hook, *before* actually marking object for deletion.
marcink -
r3979:a1866b8a default
parent child Browse files
Show More
@@ -1,942 +1,946 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2019 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.uid
348 348 extern_type = extern_type or auth_rhodecode.RhodeCodeAuthPlugin.uid
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 380 self.add_auth_token(
381 381 user=username, lifetime_minutes=-1,
382 382 role=self.auth_token_role.ROLE_FEED,
383 383 description=u'Generated feed token')
384 384
385 385 kwargs = new_user.get_dict()
386 386 # backward compat, require api_keys present
387 387 kwargs['api_keys'] = kwargs['auth_tokens']
388 388 log_create_user(created_by=cur_user, **kwargs)
389 389 events.trigger(events.UserPostCreate(user_data))
390 390 return new_user
391 391 except (DatabaseError,):
392 392 log.error(traceback.format_exc())
393 393 raise
394 394
395 395 def create_registration(self, form_data,
396 396 extern_name='rhodecode', extern_type='rhodecode'):
397 397 from rhodecode.model.notification import NotificationModel
398 398 from rhodecode.model.notification import EmailNotificationModel
399 399
400 400 try:
401 401 form_data['admin'] = False
402 402 form_data['extern_name'] = extern_name
403 403 form_data['extern_type'] = extern_type
404 404 new_user = self.create(form_data)
405 405
406 406 self.sa.add(new_user)
407 407 self.sa.flush()
408 408
409 409 user_data = new_user.get_dict()
410 410 kwargs = {
411 411 # use SQLALCHEMY safe dump of user data
412 412 'user': AttributeDict(user_data),
413 413 'date': datetime.datetime.now()
414 414 }
415 415 notification_type = EmailNotificationModel.TYPE_REGISTRATION
416 416 # pre-generate the subject for notification itself
417 417 (subject,
418 418 _h, _e, # we don't care about those
419 419 body_plaintext) = EmailNotificationModel().render_email(
420 420 notification_type, **kwargs)
421 421
422 422 # create notification objects, and emails
423 423 NotificationModel().create(
424 424 created_by=new_user,
425 425 notification_subject=subject,
426 426 notification_body=body_plaintext,
427 427 notification_type=notification_type,
428 428 recipients=None, # all admins
429 429 email_kwargs=kwargs,
430 430 )
431 431
432 432 return new_user
433 433 except Exception:
434 434 log.error(traceback.format_exc())
435 435 raise
436 436
437 437 def _handle_user_repos(self, username, repositories, handle_mode=None):
438 438 _superadmin = self.cls.get_first_super_admin()
439 439 left_overs = True
440 440
441 441 from rhodecode.model.repo import RepoModel
442 442
443 443 if handle_mode == 'detach':
444 444 for obj in repositories:
445 445 obj.user = _superadmin
446 446 # set description we know why we super admin now owns
447 447 # additional repositories that were orphaned !
448 448 obj.description += ' \n::detached repository from deleted user: %s' % (username,)
449 449 self.sa.add(obj)
450 450 left_overs = False
451 451 elif handle_mode == 'delete':
452 452 for obj in repositories:
453 453 RepoModel().delete(obj, forks='detach')
454 454 left_overs = False
455 455
456 456 # if nothing is done we have left overs left
457 457 return left_overs
458 458
459 459 def _handle_user_repo_groups(self, username, repository_groups,
460 460 handle_mode=None):
461 461 _superadmin = self.cls.get_first_super_admin()
462 462 left_overs = True
463 463
464 464 from rhodecode.model.repo_group import RepoGroupModel
465 465
466 466 if handle_mode == 'detach':
467 467 for r in repository_groups:
468 468 r.user = _superadmin
469 469 # set description we know why we super admin now owns
470 470 # additional repositories that were orphaned !
471 471 r.group_description += ' \n::detached repository group from deleted user: %s' % (username,)
472 472 r.personal = False
473 473 self.sa.add(r)
474 474 left_overs = False
475 475 elif handle_mode == 'delete':
476 476 for r in repository_groups:
477 477 RepoGroupModel().delete(r)
478 478 left_overs = False
479 479
480 480 # if nothing is done we have left overs left
481 481 return left_overs
482 482
483 483 def _handle_user_user_groups(self, username, user_groups, handle_mode=None):
484 484 _superadmin = self.cls.get_first_super_admin()
485 485 left_overs = True
486 486
487 487 from rhodecode.model.user_group import UserGroupModel
488 488
489 489 if handle_mode == 'detach':
490 490 for r in user_groups:
491 491 for user_user_group_to_perm in r.user_user_group_to_perm:
492 492 if user_user_group_to_perm.user.username == username:
493 493 user_user_group_to_perm.user = _superadmin
494 494 r.user = _superadmin
495 495 # set description we know why we super admin now owns
496 496 # additional repositories that were orphaned !
497 497 r.user_group_description += ' \n::detached user group from deleted user: %s' % (username,)
498 498 self.sa.add(r)
499 499 left_overs = False
500 500 elif handle_mode == 'delete':
501 501 for r in user_groups:
502 502 UserGroupModel().delete(r)
503 503 left_overs = False
504 504
505 505 # if nothing is done we have left overs left
506 506 return left_overs
507 507
508 508 def delete(self, user, cur_user=None, handle_repos=None,
509 509 handle_repo_groups=None, handle_user_groups=None):
510 from rhodecode.lib.hooks_base import log_delete_user
511
510 512 if not cur_user:
511 513 cur_user = getattr(
512 514 get_current_rhodecode_user(), 'username', None)
513 515 user = self._get_user(user)
514 516
515 517 try:
516 518 if user.username == User.DEFAULT_USER:
517 519 raise DefaultUserException(
518 520 u"You can't remove this user since it's"
519 521 u" crucial for entire application")
520 522
521 523 left_overs = self._handle_user_repos(
522 524 user.username, user.repositories, handle_repos)
523 525 if left_overs and user.repositories:
524 526 repos = [x.repo_name for x in user.repositories]
525 527 raise UserOwnsReposException(
526 528 u'user "%(username)s" still owns %(len_repos)s repositories and cannot be '
527 529 u'removed. Switch owners or remove those repositories:%(list_repos)s'
528 530 % {'username': user.username, 'len_repos': len(repos),
529 531 'list_repos': ', '.join(repos)})
530 532
531 533 left_overs = self._handle_user_repo_groups(
532 534 user.username, user.repository_groups, handle_repo_groups)
533 535 if left_overs and user.repository_groups:
534 536 repo_groups = [x.group_name for x in user.repository_groups]
535 537 raise UserOwnsRepoGroupsException(
536 538 u'user "%(username)s" still owns %(len_repo_groups)s repository groups and cannot be '
537 539 u'removed. Switch owners or remove those repository groups:%(list_repo_groups)s'
538 540 % {'username': user.username, 'len_repo_groups': len(repo_groups),
539 541 'list_repo_groups': ', '.join(repo_groups)})
540 542
541 543 left_overs = self._handle_user_user_groups(
542 544 user.username, user.user_groups, handle_user_groups)
543 545 if left_overs and user.user_groups:
544 546 user_groups = [x.users_group_name for x in user.user_groups]
545 547 raise UserOwnsUserGroupsException(
546 548 u'user "%s" still owns %s user groups and cannot be '
547 549 u'removed. Switch owners or remove those user groups:%s'
548 550 % (user.username, len(user_groups), ', '.join(user_groups)))
549 551
552 user_data = user.get_dict() # fetch user data before expire
553
550 554 # we might change the user data with detach/delete, make sure
551 555 # the object is marked as expired before actually deleting !
552 556 self.sa.expire(user)
553 557 self.sa.delete(user)
554 from rhodecode.lib.hooks_base import log_delete_user
555 log_delete_user(deleted_by=cur_user, **user.get_dict())
558
559 log_delete_user(deleted_by=cur_user, **user_data)
556 560 except Exception:
557 561 log.error(traceback.format_exc())
558 562 raise
559 563
560 564 def reset_password_link(self, data, pwd_reset_url):
561 565 from rhodecode.lib.celerylib import tasks, run_task
562 566 from rhodecode.model.notification import EmailNotificationModel
563 567 user_email = data['email']
564 568 try:
565 569 user = User.get_by_email(user_email)
566 570 if user:
567 571 log.debug('password reset user found %s', user)
568 572
569 573 email_kwargs = {
570 574 'password_reset_url': pwd_reset_url,
571 575 'user': user,
572 576 'email': user_email,
573 577 'date': datetime.datetime.now()
574 578 }
575 579
576 580 (subject, headers, email_body,
577 581 email_body_plaintext) = EmailNotificationModel().render_email(
578 582 EmailNotificationModel.TYPE_PASSWORD_RESET, **email_kwargs)
579 583
580 584 recipients = [user_email]
581 585
582 586 action_logger_generic(
583 587 'sending password reset email to user: {}'.format(
584 588 user), namespace='security.password_reset')
585 589
586 590 run_task(tasks.send_email, recipients, subject,
587 591 email_body_plaintext, email_body)
588 592
589 593 else:
590 594 log.debug("password reset email %s not found", user_email)
591 595 except Exception:
592 596 log.error(traceback.format_exc())
593 597 return False
594 598
595 599 return True
596 600
597 601 def reset_password(self, data):
598 602 from rhodecode.lib.celerylib import tasks, run_task
599 603 from rhodecode.model.notification import EmailNotificationModel
600 604 from rhodecode.lib import auth
601 605 user_email = data['email']
602 606 pre_db = True
603 607 try:
604 608 user = User.get_by_email(user_email)
605 609 new_passwd = auth.PasswordGenerator().gen_password(
606 610 12, auth.PasswordGenerator.ALPHABETS_BIG_SMALL)
607 611 if user:
608 612 user.password = auth.get_crypt_password(new_passwd)
609 613 # also force this user to reset his password !
610 614 user.update_userdata(force_password_change=True)
611 615
612 616 Session().add(user)
613 617
614 618 # now delete the token in question
615 619 UserApiKeys = AuthTokenModel.cls
616 620 UserApiKeys().query().filter(
617 621 UserApiKeys.api_key == data['token']).delete()
618 622
619 623 Session().commit()
620 624 log.info('successfully reset password for `%s`', user_email)
621 625
622 626 if new_passwd is None:
623 627 raise Exception('unable to generate new password')
624 628
625 629 pre_db = False
626 630
627 631 email_kwargs = {
628 632 'new_password': new_passwd,
629 633 'user': user,
630 634 'email': user_email,
631 635 'date': datetime.datetime.now()
632 636 }
633 637
634 638 (subject, headers, email_body,
635 639 email_body_plaintext) = EmailNotificationModel().render_email(
636 640 EmailNotificationModel.TYPE_PASSWORD_RESET_CONFIRMATION,
637 641 **email_kwargs)
638 642
639 643 recipients = [user_email]
640 644
641 645 action_logger_generic(
642 646 'sent new password to user: {} with email: {}'.format(
643 647 user, user_email), namespace='security.password_reset')
644 648
645 649 run_task(tasks.send_email, recipients, subject,
646 650 email_body_plaintext, email_body)
647 651
648 652 except Exception:
649 653 log.error('Failed to update user password')
650 654 log.error(traceback.format_exc())
651 655 if pre_db:
652 656 # we rollback only if local db stuff fails. If it goes into
653 657 # run_task, we're pass rollback state this wouldn't work then
654 658 Session().rollback()
655 659
656 660 return True
657 661
658 662 def fill_data(self, auth_user, user_id=None, api_key=None, username=None):
659 663 """
660 664 Fetches auth_user by user_id,or api_key if present.
661 665 Fills auth_user attributes with those taken from database.
662 666 Additionally set's is_authenitated if lookup fails
663 667 present in database
664 668
665 669 :param auth_user: instance of user to set attributes
666 670 :param user_id: user id to fetch by
667 671 :param api_key: api key to fetch by
668 672 :param username: username to fetch by
669 673 """
670 674 def token_obfuscate(token):
671 675 if token:
672 676 return token[:4] + "****"
673 677
674 678 if user_id is None and api_key is None and username is None:
675 679 raise Exception('You need to pass user_id, api_key or username')
676 680
677 681 log.debug(
678 682 'AuthUser: fill data execution based on: '
679 683 'user_id:%s api_key:%s username:%s', user_id, api_key, username)
680 684 try:
681 685 dbuser = None
682 686 if user_id:
683 687 dbuser = self.get(user_id)
684 688 elif api_key:
685 689 dbuser = self.get_by_auth_token(api_key)
686 690 elif username:
687 691 dbuser = self.get_by_username(username)
688 692
689 693 if not dbuser:
690 694 log.warning(
691 695 'Unable to lookup user by id:%s api_key:%s username:%s',
692 696 user_id, token_obfuscate(api_key), username)
693 697 return False
694 698 if not dbuser.active:
695 699 log.debug('User `%s:%s` is inactive, skipping fill data',
696 700 username, user_id)
697 701 return False
698 702
699 703 log.debug('AuthUser: filling found user:%s data', dbuser)
700 704 user_data = dbuser.get_dict()
701 705
702 706 user_data.update({
703 707 # set explicit the safe escaped values
704 708 'first_name': dbuser.first_name,
705 709 'last_name': dbuser.last_name,
706 710 })
707 711
708 712 for k, v in user_data.items():
709 713 # properties of auth user we dont update
710 714 if k not in ['auth_tokens', 'permissions']:
711 715 setattr(auth_user, k, v)
712 716
713 717 except Exception:
714 718 log.error(traceback.format_exc())
715 719 auth_user.is_authenticated = False
716 720 return False
717 721
718 722 return True
719 723
720 724 def has_perm(self, user, perm):
721 725 perm = self._get_perm(perm)
722 726 user = self._get_user(user)
723 727
724 728 return UserToPerm.query().filter(UserToPerm.user == user)\
725 729 .filter(UserToPerm.permission == perm).scalar() is not None
726 730
727 731 def grant_perm(self, user, perm):
728 732 """
729 733 Grant user global permissions
730 734
731 735 :param user:
732 736 :param perm:
733 737 """
734 738 user = self._get_user(user)
735 739 perm = self._get_perm(perm)
736 740 # if this permission is already granted skip it
737 741 _perm = UserToPerm.query()\
738 742 .filter(UserToPerm.user == user)\
739 743 .filter(UserToPerm.permission == perm)\
740 744 .scalar()
741 745 if _perm:
742 746 return
743 747 new = UserToPerm()
744 748 new.user = user
745 749 new.permission = perm
746 750 self.sa.add(new)
747 751 return new
748 752
749 753 def revoke_perm(self, user, perm):
750 754 """
751 755 Revoke users global permissions
752 756
753 757 :param user:
754 758 :param perm:
755 759 """
756 760 user = self._get_user(user)
757 761 perm = self._get_perm(perm)
758 762
759 763 obj = UserToPerm.query()\
760 764 .filter(UserToPerm.user == user)\
761 765 .filter(UserToPerm.permission == perm)\
762 766 .scalar()
763 767 if obj:
764 768 self.sa.delete(obj)
765 769
766 770 def add_extra_email(self, user, email):
767 771 """
768 772 Adds email address to UserEmailMap
769 773
770 774 :param user:
771 775 :param email:
772 776 """
773 777
774 778 user = self._get_user(user)
775 779
776 780 obj = UserEmailMap()
777 781 obj.user = user
778 782 obj.email = email
779 783 self.sa.add(obj)
780 784 return obj
781 785
782 786 def delete_extra_email(self, user, email_id):
783 787 """
784 788 Removes email address from UserEmailMap
785 789
786 790 :param user:
787 791 :param email_id:
788 792 """
789 793 user = self._get_user(user)
790 794 obj = UserEmailMap.query().get(email_id)
791 795 if obj and obj.user_id == user.user_id:
792 796 self.sa.delete(obj)
793 797
794 798 def parse_ip_range(self, ip_range):
795 799 ip_list = []
796 800
797 801 def make_unique(value):
798 802 seen = []
799 803 return [c for c in value if not (c in seen or seen.append(c))]
800 804
801 805 # firsts split by commas
802 806 for ip_range in ip_range.split(','):
803 807 if not ip_range:
804 808 continue
805 809 ip_range = ip_range.strip()
806 810 if '-' in ip_range:
807 811 start_ip, end_ip = ip_range.split('-', 1)
808 812 start_ip = ipaddress.ip_address(safe_unicode(start_ip.strip()))
809 813 end_ip = ipaddress.ip_address(safe_unicode(end_ip.strip()))
810 814 parsed_ip_range = []
811 815
812 816 for index in xrange(int(start_ip), int(end_ip) + 1):
813 817 new_ip = ipaddress.ip_address(index)
814 818 parsed_ip_range.append(str(new_ip))
815 819 ip_list.extend(parsed_ip_range)
816 820 else:
817 821 ip_list.append(ip_range)
818 822
819 823 return make_unique(ip_list)
820 824
821 825 def add_extra_ip(self, user, ip, description=None):
822 826 """
823 827 Adds ip address to UserIpMap
824 828
825 829 :param user:
826 830 :param ip:
827 831 """
828 832
829 833 user = self._get_user(user)
830 834 obj = UserIpMap()
831 835 obj.user = user
832 836 obj.ip_addr = ip
833 837 obj.description = description
834 838 self.sa.add(obj)
835 839 return obj
836 840
837 841 auth_token_role = AuthTokenModel.cls
838 842
839 843 def add_auth_token(self, user, lifetime_minutes, role, description=u'',
840 844 scope_callback=None):
841 845 """
842 846 Add AuthToken for user.
843 847
844 848 :param user: username/user_id
845 849 :param lifetime_minutes: in minutes the lifetime for token, -1 equals no limit
846 850 :param role: one of AuthTokenModel.cls.ROLE_*
847 851 :param description: optional string description
848 852 """
849 853
850 854 token = AuthTokenModel().create(
851 855 user, description, lifetime_minutes, role)
852 856 if scope_callback and callable(scope_callback):
853 857 # call the callback if we provide, used to attach scope for EE edition
854 858 scope_callback(token)
855 859 return token
856 860
857 861 def delete_extra_ip(self, user, ip_id):
858 862 """
859 863 Removes ip address from UserIpMap
860 864
861 865 :param user:
862 866 :param ip_id:
863 867 """
864 868 user = self._get_user(user)
865 869 obj = UserIpMap.query().get(ip_id)
866 870 if obj and obj.user_id == user.user_id:
867 871 self.sa.delete(obj)
868 872
869 873 def get_accounts_in_creation_order(self, current_user=None):
870 874 """
871 875 Get accounts in order of creation for deactivation for license limits
872 876
873 877 pick currently logged in user, and append to the list in position 0
874 878 pick all super-admins in order of creation date and add it to the list
875 879 pick all other accounts in order of creation and add it to the list.
876 880
877 881 Based on that list, the last accounts can be disabled as they are
878 882 created at the end and don't include any of the super admins as well
879 883 as the current user.
880 884
881 885 :param current_user: optionally current user running this operation
882 886 """
883 887
884 888 if not current_user:
885 889 current_user = get_current_rhodecode_user()
886 890 active_super_admins = [
887 891 x.user_id for x in User.query()
888 892 .filter(User.user_id != current_user.user_id)
889 893 .filter(User.active == true())
890 894 .filter(User.admin == true())
891 895 .order_by(User.created_on.asc())]
892 896
893 897 active_regular_users = [
894 898 x.user_id for x in User.query()
895 899 .filter(User.user_id != current_user.user_id)
896 900 .filter(User.active == true())
897 901 .filter(User.admin == false())
898 902 .order_by(User.created_on.asc())]
899 903
900 904 list_of_accounts = [current_user.user_id]
901 905 list_of_accounts += active_super_admins
902 906 list_of_accounts += active_regular_users
903 907
904 908 return list_of_accounts
905 909
906 910 def deactivate_last_users(self, expected_users, current_user=None):
907 911 """
908 912 Deactivate accounts that are over the license limits.
909 913 Algorithm of which accounts to disabled is based on the formula:
910 914
911 915 Get current user, then super admins in creation order, then regular
912 916 active users in creation order.
913 917
914 918 Using that list we mark all accounts from the end of it as inactive.
915 919 This way we block only latest created accounts.
916 920
917 921 :param expected_users: list of users in special order, we deactivate
918 922 the end N amount of users from that list
919 923 """
920 924
921 925 list_of_accounts = self.get_accounts_in_creation_order(
922 926 current_user=current_user)
923 927
924 928 for acc_id in list_of_accounts[expected_users + 1:]:
925 929 user = User.get(acc_id)
926 930 log.info('Deactivating account %s for license unlock', user)
927 931 user.active = False
928 932 Session().add(user)
929 933 Session().commit()
930 934
931 935 return
932 936
933 937 def get_user_log(self, user, filter_term):
934 938 user_log = UserLog.query()\
935 939 .filter(or_(UserLog.user_id == user.user_id,
936 940 UserLog.username == user.username))\
937 941 .options(joinedload(UserLog.user))\
938 942 .options(joinedload(UserLog.repository))\
939 943 .order_by(UserLog.action_date.desc())
940 944
941 945 user_log = user_log_filter(user_log, filter_term)
942 946 return user_log
General Comments 0
You need to be logged in to leave comments. Login now