##// END OF EJS Templates
auth-rhodecode: don't fail on bcrypt if user password is set to None....
marcink -
r2153:6de97439 default
parent child Browse files
Show More
@@ -1,142 +1,142 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2012-2017 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 RhodeCode authentication plugin for built in internal auth
23 23 """
24 24
25 25 import logging
26 26
27 27 from rhodecode.translation import _
28 28
29 29 from rhodecode.authentication.base import RhodeCodeAuthPluginBase, hybrid_property
30 30 from rhodecode.authentication.routes import AuthnPluginResourceBase
31 31 from rhodecode.lib.utils2 import safe_str
32 32 from rhodecode.model.db import User
33 33
34 34 log = logging.getLogger(__name__)
35 35
36 36
37 37 def plugin_factory(plugin_id, *args, **kwds):
38 38 plugin = RhodeCodeAuthPlugin(plugin_id)
39 39 return plugin
40 40
41 41
42 42 class RhodecodeAuthnResource(AuthnPluginResourceBase):
43 43 pass
44 44
45 45
46 46 class RhodeCodeAuthPlugin(RhodeCodeAuthPluginBase):
47 47
48 48 def includeme(self, config):
49 49 config.add_authn_plugin(self)
50 50 config.add_authn_resource(self.get_id(), RhodecodeAuthnResource(self))
51 51 config.add_view(
52 52 'rhodecode.authentication.views.AuthnPluginViewBase',
53 53 attr='settings_get',
54 54 renderer='rhodecode:templates/admin/auth/plugin_settings.mako',
55 55 request_method='GET',
56 56 route_name='auth_home',
57 57 context=RhodecodeAuthnResource)
58 58 config.add_view(
59 59 'rhodecode.authentication.views.AuthnPluginViewBase',
60 60 attr='settings_post',
61 61 renderer='rhodecode:templates/admin/auth/plugin_settings.mako',
62 62 request_method='POST',
63 63 route_name='auth_home',
64 64 context=RhodecodeAuthnResource)
65 65
66 66 def get_display_name(self):
67 67 return _('Rhodecode')
68 68
69 69 @hybrid_property
70 70 def name(self):
71 71 return "rhodecode"
72 72
73 73 def user_activation_state(self):
74 74 def_user_perms = User.get_default_user().AuthUser().permissions['global']
75 75 return 'hg.register.auto_activate' in def_user_perms
76 76
77 77 def allows_authentication_from(
78 78 self, user, allows_non_existing_user=True,
79 79 allowed_auth_plugins=None, allowed_auth_sources=None):
80 80 """
81 81 Custom method for this auth that doesn't accept non existing users.
82 82 We know that user exists in our database.
83 83 """
84 84 allows_non_existing_user = False
85 85 return super(RhodeCodeAuthPlugin, self).allows_authentication_from(
86 86 user, allows_non_existing_user=allows_non_existing_user)
87 87
88 88 def auth(self, userobj, username, password, settings, **kwargs):
89 89 if not userobj:
90 90 log.debug('userobj was:%s skipping' % (userobj, ))
91 91 return None
92 92 if userobj.extern_type != self.name:
93 93 log.warning(
94 94 "userobj:%s extern_type mismatch got:`%s` expected:`%s`" %
95 95 (userobj, userobj.extern_type, self.name))
96 96 return None
97 97
98 98 user_attrs = {
99 99 "username": userobj.username,
100 100 "firstname": userobj.firstname,
101 101 "lastname": userobj.lastname,
102 102 "groups": [],
103 103 "email": userobj.email,
104 104 "admin": userobj.admin,
105 105 "active": userobj.active,
106 106 "active_from_extern": userobj.active,
107 107 "extern_name": userobj.user_id,
108 108 "extern_type": userobj.extern_type,
109 109 }
110 110
111 111 log.debug("User attributes:%s" % (user_attrs, ))
112 112 if userobj.active:
113 113 from rhodecode.lib import auth
114 114 crypto_backend = auth.crypto_backend()
115 115 password_encoded = safe_str(password)
116 116 password_match, new_hash = crypto_backend.hash_check_with_upgrade(
117 password_encoded, userobj.password)
117 password_encoded, userobj.password or '')
118 118
119 119 if password_match and new_hash:
120 120 log.debug('user %s properly authenticated, but '
121 121 'requires hash change to bcrypt', userobj)
122 122 # if password match, and we use OLD deprecated hash,
123 123 # we should migrate this user hash password to the new hash
124 124 # we store the new returned by hash_check_with_upgrade function
125 125 user_attrs['_hash_migrate'] = new_hash
126 126
127 127 if userobj.username == User.DEFAULT_USER and userobj.active:
128 128 log.info(
129 129 'user %s authenticated correctly as anonymous user', userobj)
130 130 return user_attrs
131 131
132 132 elif userobj.username == username and password_match:
133 133 log.info('user %s authenticated correctly', userobj)
134 134 return user_attrs
135 135 log.info("user %s had a bad password when "
136 136 "authenticating on this plugin", userobj)
137 137 return None
138 138 else:
139 139 log.warning(
140 140 'user `%s` failed to authenticate via %s, reason: account not '
141 141 'active.', username, self.name)
142 142 return None
@@ -1,910 +1,911 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2017 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
28 28 import datetime
29 29 from pylons.i18n.translation import _
30 30
31 31 import ipaddress
32 32 from sqlalchemy.exc import DatabaseError
33 33
34 34 from rhodecode import events
35 35 from rhodecode.lib.user_log_filter import user_log_filter
36 36 from rhodecode.lib.utils2 import (
37 37 safe_unicode, get_current_rhodecode_user, action_logger_generic,
38 38 AttributeDict, str2bool)
39 39 from rhodecode.lib.exceptions import (
40 40 DefaultUserException, UserOwnsReposException, UserOwnsRepoGroupsException,
41 41 UserOwnsUserGroupsException, NotAllowedToCreateUserError)
42 42 from rhodecode.lib.caching_query import FromCache
43 43 from rhodecode.model import BaseModel
44 44 from rhodecode.model.auth_token import AuthTokenModel
45 45 from rhodecode.model.db import (
46 46 _hash_key, true, false, or_, joinedload, User, UserToPerm,
47 47 UserEmailMap, UserIpMap, UserLog)
48 48 from rhodecode.model.meta import Session
49 49 from rhodecode.model.repo_group import RepoGroupModel
50 50
51 51
52 52 log = logging.getLogger(__name__)
53 53
54 54
55 55 class UserModel(BaseModel):
56 56 cls = User
57 57
58 58 def get(self, user_id, cache=False):
59 59 user = self.sa.query(User)
60 60 if cache:
61 61 user = user.options(
62 62 FromCache("sql_cache_short", "get_user_%s" % user_id))
63 63 return user.get(user_id)
64 64
65 65 def get_user(self, user):
66 66 return self._get_user(user)
67 67
68 68 def _serialize_user(self, user):
69 69 import rhodecode.lib.helpers as h
70 70
71 71 return {
72 72 'id': user.user_id,
73 73 'first_name': user.first_name,
74 74 'last_name': user.last_name,
75 75 'username': user.username,
76 76 'email': user.email,
77 77 'icon_link': h.gravatar_url(user.email, 30),
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 return User.query().filter(
128 128 User.active == True).filter(
129 129 User.username != User.DEFAULT_USER).count()
130 130
131 131 def create(self, form_data, cur_user=None):
132 132 if not cur_user:
133 133 cur_user = getattr(get_current_rhodecode_user(), 'username', None)
134 134
135 135 user_data = {
136 136 'username': form_data['username'],
137 137 'password': form_data['password'],
138 138 'email': form_data['email'],
139 139 'firstname': form_data['firstname'],
140 140 'lastname': form_data['lastname'],
141 141 'active': form_data['active'],
142 142 'extern_type': form_data['extern_type'],
143 143 'extern_name': form_data['extern_name'],
144 144 'admin': False,
145 145 'cur_user': cur_user
146 146 }
147 147
148 148 if 'create_repo_group' in form_data:
149 149 user_data['create_repo_group'] = str2bool(
150 150 form_data.get('create_repo_group'))
151 151
152 152 try:
153 153 if form_data.get('password_change'):
154 154 user_data['force_password_change'] = True
155 155 return UserModel().create_or_update(**user_data)
156 156 except Exception:
157 157 log.error(traceback.format_exc())
158 158 raise
159 159
160 160 def update_user(self, user, skip_attrs=None, **kwargs):
161 161 from rhodecode.lib.auth import get_crypt_password
162 162
163 163 user = self._get_user(user)
164 164 if user.username == User.DEFAULT_USER:
165 165 raise DefaultUserException(
166 166 _("You can't Edit this user since it's"
167 167 " crucial for entire application"))
168 168
169 169 # first store only defaults
170 170 user_attrs = {
171 171 'updating_user_id': user.user_id,
172 172 'username': user.username,
173 173 'password': user.password,
174 174 'email': user.email,
175 175 'firstname': user.name,
176 176 'lastname': user.lastname,
177 177 'active': user.active,
178 178 'admin': user.admin,
179 179 'extern_name': user.extern_name,
180 180 'extern_type': user.extern_type,
181 181 'language': user.user_data.get('language')
182 182 }
183 183
184 184 # in case there's new_password, that comes from form, use it to
185 185 # store password
186 186 if kwargs.get('new_password'):
187 187 kwargs['password'] = kwargs['new_password']
188 188
189 189 # cleanups, my_account password change form
190 190 kwargs.pop('current_password', None)
191 191 kwargs.pop('new_password', None)
192 192
193 193 # cleanups, user edit password change form
194 194 kwargs.pop('password_confirmation', None)
195 195 kwargs.pop('password_change', None)
196 196
197 197 # create repo group on user creation
198 198 kwargs.pop('create_repo_group', None)
199 199
200 200 # legacy forms send name, which is the firstname
201 201 firstname = kwargs.pop('name', None)
202 202 if firstname:
203 203 kwargs['firstname'] = firstname
204 204
205 205 for k, v in kwargs.items():
206 206 # skip if we don't want to update this
207 207 if skip_attrs and k in skip_attrs:
208 208 continue
209 209
210 210 user_attrs[k] = v
211 211
212 212 try:
213 213 return self.create_or_update(**user_attrs)
214 214 except Exception:
215 215 log.error(traceback.format_exc())
216 216 raise
217 217
218 218 def create_or_update(
219 219 self, username, password, email, firstname='', lastname='',
220 220 active=True, admin=False, extern_type=None, extern_name=None,
221 221 cur_user=None, plugin=None, force_password_change=False,
222 222 allow_to_create_user=True, create_repo_group=None,
223 223 updating_user_id=None, language=None, strict_creation_check=True):
224 224 """
225 225 Creates a new instance if not found, or updates current one
226 226
227 227 :param username:
228 228 :param password:
229 229 :param email:
230 230 :param firstname:
231 231 :param lastname:
232 232 :param active:
233 233 :param admin:
234 234 :param extern_type:
235 235 :param extern_name:
236 236 :param cur_user:
237 237 :param plugin: optional plugin this method was called from
238 238 :param force_password_change: toggles new or existing user flag
239 239 for password change
240 240 :param allow_to_create_user: Defines if the method can actually create
241 241 new users
242 242 :param create_repo_group: Defines if the method should also
243 243 create an repo group with user name, and owner
244 244 :param updating_user_id: if we set it up this is the user we want to
245 245 update this allows to editing username.
246 246 :param language: language of user from interface.
247 247
248 248 :returns: new User object with injected `is_new_user` attribute.
249 249 """
250 250 if not cur_user:
251 251 cur_user = getattr(get_current_rhodecode_user(), 'username', None)
252 252
253 253 from rhodecode.lib.auth import (
254 254 get_crypt_password, check_password, generate_auth_token)
255 255 from rhodecode.lib.hooks_base import (
256 256 log_create_user, check_allowed_create_user)
257 257
258 258 def _password_change(new_user, password):
259 old_password = new_user.password or ''
259 260 # empty password
260 if not new_user.password:
261 if not old_password:
261 262 return False
262 263
263 264 # password check is only needed for RhodeCode internal auth calls
264 265 # in case it's a plugin we don't care
265 266 if not plugin:
266 267
267 268 # first check if we gave crypted password back, and if it
268 269 # matches it's not password change
269 270 if new_user.password == password:
270 271 return False
271 272
272 password_match = check_password(password, new_user.password)
273 password_match = check_password(password, old_password)
273 274 if not password_match:
274 275 return True
275 276
276 277 return False
277 278
278 279 # read settings on default personal repo group creation
279 280 if create_repo_group is None:
280 281 default_create_repo_group = RepoGroupModel()\
281 282 .get_default_create_personal_repo_group()
282 283 create_repo_group = default_create_repo_group
283 284
284 285 user_data = {
285 286 'username': username,
286 287 'password': password,
287 288 'email': email,
288 289 'firstname': firstname,
289 290 'lastname': lastname,
290 291 'active': active,
291 292 'admin': admin
292 293 }
293 294
294 295 if updating_user_id:
295 296 log.debug('Checking for existing account in RhodeCode '
296 297 'database with user_id `%s` ' % (updating_user_id,))
297 298 user = User.get(updating_user_id)
298 299 else:
299 300 log.debug('Checking for existing account in RhodeCode '
300 301 'database with username `%s` ' % (username,))
301 302 user = User.get_by_username(username, case_insensitive=True)
302 303
303 304 if user is None:
304 305 # we check internal flag if this method is actually allowed to
305 306 # create new user
306 307 if not allow_to_create_user:
307 308 msg = ('Method wants to create new user, but it is not '
308 309 'allowed to do so')
309 310 log.warning(msg)
310 311 raise NotAllowedToCreateUserError(msg)
311 312
312 313 log.debug('Creating new user %s', username)
313 314
314 315 # only if we create user that is active
315 316 new_active_user = active
316 317 if new_active_user and strict_creation_check:
317 318 # raises UserCreationError if it's not allowed for any reason to
318 319 # create new active user, this also executes pre-create hooks
319 320 check_allowed_create_user(user_data, cur_user, strict_check=True)
320 321 events.trigger(events.UserPreCreate(user_data))
321 322 new_user = User()
322 323 edit = False
323 324 else:
324 325 log.debug('updating user %s', username)
325 326 events.trigger(events.UserPreUpdate(user, user_data))
326 327 new_user = user
327 328 edit = True
328 329
329 330 # we're not allowed to edit default user
330 331 if user.username == User.DEFAULT_USER:
331 332 raise DefaultUserException(
332 333 _("You can't edit this user (`%(username)s`) since it's "
333 334 "crucial for entire application") % {'username': user.username})
334 335
335 336 # inject special attribute that will tell us if User is new or old
336 337 new_user.is_new_user = not edit
337 338 # for users that didn's specify auth type, we use RhodeCode built in
338 339 from rhodecode.authentication.plugins import auth_rhodecode
339 340 extern_name = extern_name or auth_rhodecode.RhodeCodeAuthPlugin.name
340 341 extern_type = extern_type or auth_rhodecode.RhodeCodeAuthPlugin.name
341 342
342 343 try:
343 344 new_user.username = username
344 345 new_user.admin = admin
345 346 new_user.email = email
346 347 new_user.active = active
347 348 new_user.extern_name = safe_unicode(extern_name)
348 349 new_user.extern_type = safe_unicode(extern_type)
349 350 new_user.name = firstname
350 351 new_user.lastname = lastname
351 352
352 353 # set password only if creating an user or password is changed
353 354 if not edit or _password_change(new_user, password):
354 355 reason = 'new password' if edit else 'new user'
355 356 log.debug('Updating password reason=>%s', reason)
356 357 new_user.password = get_crypt_password(password) if password else None
357 358
358 359 if force_password_change:
359 360 new_user.update_userdata(force_password_change=True)
360 361 if language:
361 362 new_user.update_userdata(language=language)
362 363 new_user.update_userdata(notification_status=True)
363 364
364 365 self.sa.add(new_user)
365 366
366 367 if not edit and create_repo_group:
367 368 RepoGroupModel().create_personal_repo_group(
368 369 new_user, commit_early=False)
369 370
370 371 if not edit:
371 372 # add the RSS token
372 373 AuthTokenModel().create(username,
373 374 description=u'Generated feed token',
374 375 role=AuthTokenModel.cls.ROLE_FEED)
375 376 kwargs = new_user.get_dict()
376 377 # backward compat, require api_keys present
377 378 kwargs['api_keys'] = kwargs['auth_tokens']
378 379 log_create_user(created_by=cur_user, **kwargs)
379 380 events.trigger(events.UserPostCreate(user_data))
380 381 return new_user
381 382 except (DatabaseError,):
382 383 log.error(traceback.format_exc())
383 384 raise
384 385
385 386 def create_registration(self, form_data):
386 387 from rhodecode.model.notification import NotificationModel
387 388 from rhodecode.model.notification import EmailNotificationModel
388 389
389 390 try:
390 391 form_data['admin'] = False
391 392 form_data['extern_name'] = 'rhodecode'
392 393 form_data['extern_type'] = 'rhodecode'
393 394 new_user = self.create(form_data)
394 395
395 396 self.sa.add(new_user)
396 397 self.sa.flush()
397 398
398 399 user_data = new_user.get_dict()
399 400 kwargs = {
400 401 # use SQLALCHEMY safe dump of user data
401 402 'user': AttributeDict(user_data),
402 403 'date': datetime.datetime.now()
403 404 }
404 405 notification_type = EmailNotificationModel.TYPE_REGISTRATION
405 406 # pre-generate the subject for notification itself
406 407 (subject,
407 408 _h, _e, # we don't care about those
408 409 body_plaintext) = EmailNotificationModel().render_email(
409 410 notification_type, **kwargs)
410 411
411 412 # create notification objects, and emails
412 413 NotificationModel().create(
413 414 created_by=new_user,
414 415 notification_subject=subject,
415 416 notification_body=body_plaintext,
416 417 notification_type=notification_type,
417 418 recipients=None, # all admins
418 419 email_kwargs=kwargs,
419 420 )
420 421
421 422 return new_user
422 423 except Exception:
423 424 log.error(traceback.format_exc())
424 425 raise
425 426
426 427 def _handle_user_repos(self, username, repositories, handle_mode=None):
427 428 _superadmin = self.cls.get_first_super_admin()
428 429 left_overs = True
429 430
430 431 from rhodecode.model.repo import RepoModel
431 432
432 433 if handle_mode == 'detach':
433 434 for obj in repositories:
434 435 obj.user = _superadmin
435 436 # set description we know why we super admin now owns
436 437 # additional repositories that were orphaned !
437 438 obj.description += ' \n::detached repository from deleted user: %s' % (username,)
438 439 self.sa.add(obj)
439 440 left_overs = False
440 441 elif handle_mode == 'delete':
441 442 for obj in repositories:
442 443 RepoModel().delete(obj, forks='detach')
443 444 left_overs = False
444 445
445 446 # if nothing is done we have left overs left
446 447 return left_overs
447 448
448 449 def _handle_user_repo_groups(self, username, repository_groups,
449 450 handle_mode=None):
450 451 _superadmin = self.cls.get_first_super_admin()
451 452 left_overs = True
452 453
453 454 from rhodecode.model.repo_group import RepoGroupModel
454 455
455 456 if handle_mode == 'detach':
456 457 for r in repository_groups:
457 458 r.user = _superadmin
458 459 # set description we know why we super admin now owns
459 460 # additional repositories that were orphaned !
460 461 r.group_description += ' \n::detached repository group from deleted user: %s' % (username,)
461 462 self.sa.add(r)
462 463 left_overs = False
463 464 elif handle_mode == 'delete':
464 465 for r in repository_groups:
465 466 RepoGroupModel().delete(r)
466 467 left_overs = False
467 468
468 469 # if nothing is done we have left overs left
469 470 return left_overs
470 471
471 472 def _handle_user_user_groups(self, username, user_groups, handle_mode=None):
472 473 _superadmin = self.cls.get_first_super_admin()
473 474 left_overs = True
474 475
475 476 from rhodecode.model.user_group import UserGroupModel
476 477
477 478 if handle_mode == 'detach':
478 479 for r in user_groups:
479 480 for user_user_group_to_perm in r.user_user_group_to_perm:
480 481 if user_user_group_to_perm.user.username == username:
481 482 user_user_group_to_perm.user = _superadmin
482 483 r.user = _superadmin
483 484 # set description we know why we super admin now owns
484 485 # additional repositories that were orphaned !
485 486 r.user_group_description += ' \n::detached user group from deleted user: %s' % (username,)
486 487 self.sa.add(r)
487 488 left_overs = False
488 489 elif handle_mode == 'delete':
489 490 for r in user_groups:
490 491 UserGroupModel().delete(r)
491 492 left_overs = False
492 493
493 494 # if nothing is done we have left overs left
494 495 return left_overs
495 496
496 497 def delete(self, user, cur_user=None, handle_repos=None,
497 498 handle_repo_groups=None, handle_user_groups=None):
498 499 if not cur_user:
499 500 cur_user = getattr(get_current_rhodecode_user(), 'username', None)
500 501 user = self._get_user(user)
501 502
502 503 try:
503 504 if user.username == User.DEFAULT_USER:
504 505 raise DefaultUserException(
505 506 _(u"You can't remove this user since it's"
506 507 u" crucial for entire application"))
507 508
508 509 left_overs = self._handle_user_repos(
509 510 user.username, user.repositories, handle_repos)
510 511 if left_overs and user.repositories:
511 512 repos = [x.repo_name for x in user.repositories]
512 513 raise UserOwnsReposException(
513 514 _(u'user "%s" still owns %s repositories and cannot be '
514 515 u'removed. Switch owners or remove those repositories:%s')
515 516 % (user.username, len(repos), ', '.join(repos)))
516 517
517 518 left_overs = self._handle_user_repo_groups(
518 519 user.username, user.repository_groups, handle_repo_groups)
519 520 if left_overs and user.repository_groups:
520 521 repo_groups = [x.group_name for x in user.repository_groups]
521 522 raise UserOwnsRepoGroupsException(
522 523 _(u'user "%s" still owns %s repository groups and cannot be '
523 524 u'removed. Switch owners or remove those repository groups:%s')
524 525 % (user.username, len(repo_groups), ', '.join(repo_groups)))
525 526
526 527 left_overs = self._handle_user_user_groups(
527 528 user.username, user.user_groups, handle_user_groups)
528 529 if left_overs and user.user_groups:
529 530 user_groups = [x.users_group_name for x in user.user_groups]
530 531 raise UserOwnsUserGroupsException(
531 532 _(u'user "%s" still owns %s user groups and cannot be '
532 533 u'removed. Switch owners or remove those user groups:%s')
533 534 % (user.username, len(user_groups), ', '.join(user_groups)))
534 535
535 536 # we might change the user data with detach/delete, make sure
536 537 # the object is marked as expired before actually deleting !
537 538 self.sa.expire(user)
538 539 self.sa.delete(user)
539 540 from rhodecode.lib.hooks_base import log_delete_user
540 541 log_delete_user(deleted_by=cur_user, **user.get_dict())
541 542 except Exception:
542 543 log.error(traceback.format_exc())
543 544 raise
544 545
545 546 def reset_password_link(self, data, pwd_reset_url):
546 547 from rhodecode.lib.celerylib import tasks, run_task
547 548 from rhodecode.model.notification import EmailNotificationModel
548 549 user_email = data['email']
549 550 try:
550 551 user = User.get_by_email(user_email)
551 552 if user:
552 553 log.debug('password reset user found %s', user)
553 554
554 555 email_kwargs = {
555 556 'password_reset_url': pwd_reset_url,
556 557 'user': user,
557 558 'email': user_email,
558 559 'date': datetime.datetime.now()
559 560 }
560 561
561 562 (subject, headers, email_body,
562 563 email_body_plaintext) = EmailNotificationModel().render_email(
563 564 EmailNotificationModel.TYPE_PASSWORD_RESET, **email_kwargs)
564 565
565 566 recipients = [user_email]
566 567
567 568 action_logger_generic(
568 569 'sending password reset email to user: {}'.format(
569 570 user), namespace='security.password_reset')
570 571
571 572 run_task(tasks.send_email, recipients, subject,
572 573 email_body_plaintext, email_body)
573 574
574 575 else:
575 576 log.debug("password reset email %s not found", user_email)
576 577 except Exception:
577 578 log.error(traceback.format_exc())
578 579 return False
579 580
580 581 return True
581 582
582 583 def reset_password(self, data):
583 584 from rhodecode.lib.celerylib import tasks, run_task
584 585 from rhodecode.model.notification import EmailNotificationModel
585 586 from rhodecode.lib import auth
586 587 user_email = data['email']
587 588 pre_db = True
588 589 try:
589 590 user = User.get_by_email(user_email)
590 591 new_passwd = auth.PasswordGenerator().gen_password(
591 592 12, auth.PasswordGenerator.ALPHABETS_BIG_SMALL)
592 593 if user:
593 594 user.password = auth.get_crypt_password(new_passwd)
594 595 # also force this user to reset his password !
595 596 user.update_userdata(force_password_change=True)
596 597
597 598 Session().add(user)
598 599
599 600 # now delete the token in question
600 601 UserApiKeys = AuthTokenModel.cls
601 602 UserApiKeys().query().filter(
602 603 UserApiKeys.api_key == data['token']).delete()
603 604
604 605 Session().commit()
605 606 log.info('successfully reset password for `%s`', user_email)
606 607
607 608 if new_passwd is None:
608 609 raise Exception('unable to generate new password')
609 610
610 611 pre_db = False
611 612
612 613 email_kwargs = {
613 614 'new_password': new_passwd,
614 615 'user': user,
615 616 'email': user_email,
616 617 'date': datetime.datetime.now()
617 618 }
618 619
619 620 (subject, headers, email_body,
620 621 email_body_plaintext) = EmailNotificationModel().render_email(
621 622 EmailNotificationModel.TYPE_PASSWORD_RESET_CONFIRMATION,
622 623 **email_kwargs)
623 624
624 625 recipients = [user_email]
625 626
626 627 action_logger_generic(
627 628 'sent new password to user: {} with email: {}'.format(
628 629 user, user_email), namespace='security.password_reset')
629 630
630 631 run_task(tasks.send_email, recipients, subject,
631 632 email_body_plaintext, email_body)
632 633
633 634 except Exception:
634 635 log.error('Failed to update user password')
635 636 log.error(traceback.format_exc())
636 637 if pre_db:
637 638 # we rollback only if local db stuff fails. If it goes into
638 639 # run_task, we're pass rollback state this wouldn't work then
639 640 Session().rollback()
640 641
641 642 return True
642 643
643 644 def fill_data(self, auth_user, user_id=None, api_key=None, username=None):
644 645 """
645 646 Fetches auth_user by user_id,or api_key if present.
646 647 Fills auth_user attributes with those taken from database.
647 648 Additionally set's is_authenitated if lookup fails
648 649 present in database
649 650
650 651 :param auth_user: instance of user to set attributes
651 652 :param user_id: user id to fetch by
652 653 :param api_key: api key to fetch by
653 654 :param username: username to fetch by
654 655 """
655 656 if user_id is None and api_key is None and username is None:
656 657 raise Exception('You need to pass user_id, api_key or username')
657 658
658 659 log.debug(
659 660 'AuthUser: fill data execution based on: '
660 661 'user_id:%s api_key:%s username:%s', user_id, api_key, username)
661 662 try:
662 663 dbuser = None
663 664 if user_id:
664 665 dbuser = self.get(user_id)
665 666 elif api_key:
666 667 dbuser = self.get_by_auth_token(api_key)
667 668 elif username:
668 669 dbuser = self.get_by_username(username)
669 670
670 671 if not dbuser:
671 672 log.warning(
672 673 'Unable to lookup user by id:%s api_key:%s username:%s',
673 674 user_id, api_key, username)
674 675 return False
675 676 if not dbuser.active:
676 677 log.debug('User `%s:%s` is inactive, skipping fill data',
677 678 username, user_id)
678 679 return False
679 680
680 681 log.debug('AuthUser: filling found user:%s data', dbuser)
681 682 user_data = dbuser.get_dict()
682 683
683 684 user_data.update({
684 685 # set explicit the safe escaped values
685 686 'first_name': dbuser.first_name,
686 687 'last_name': dbuser.last_name,
687 688 })
688 689
689 690 for k, v in user_data.items():
690 691 # properties of auth user we dont update
691 692 if k not in ['auth_tokens', 'permissions']:
692 693 setattr(auth_user, k, v)
693 694
694 695 # few extras
695 696 setattr(auth_user, 'feed_token', dbuser.feed_token)
696 697 except Exception:
697 698 log.error(traceback.format_exc())
698 699 auth_user.is_authenticated = False
699 700 return False
700 701
701 702 return True
702 703
703 704 def has_perm(self, user, perm):
704 705 perm = self._get_perm(perm)
705 706 user = self._get_user(user)
706 707
707 708 return UserToPerm.query().filter(UserToPerm.user == user)\
708 709 .filter(UserToPerm.permission == perm).scalar() is not None
709 710
710 711 def grant_perm(self, user, perm):
711 712 """
712 713 Grant user global permissions
713 714
714 715 :param user:
715 716 :param perm:
716 717 """
717 718 user = self._get_user(user)
718 719 perm = self._get_perm(perm)
719 720 # if this permission is already granted skip it
720 721 _perm = UserToPerm.query()\
721 722 .filter(UserToPerm.user == user)\
722 723 .filter(UserToPerm.permission == perm)\
723 724 .scalar()
724 725 if _perm:
725 726 return
726 727 new = UserToPerm()
727 728 new.user = user
728 729 new.permission = perm
729 730 self.sa.add(new)
730 731 return new
731 732
732 733 def revoke_perm(self, user, perm):
733 734 """
734 735 Revoke users global permissions
735 736
736 737 :param user:
737 738 :param perm:
738 739 """
739 740 user = self._get_user(user)
740 741 perm = self._get_perm(perm)
741 742
742 743 obj = UserToPerm.query()\
743 744 .filter(UserToPerm.user == user)\
744 745 .filter(UserToPerm.permission == perm)\
745 746 .scalar()
746 747 if obj:
747 748 self.sa.delete(obj)
748 749
749 750 def add_extra_email(self, user, email):
750 751 """
751 752 Adds email address to UserEmailMap
752 753
753 754 :param user:
754 755 :param email:
755 756 """
756 757 from rhodecode.model import forms
757 758 form = forms.UserExtraEmailForm()()
758 759 data = form.to_python({'email': email})
759 760 user = self._get_user(user)
760 761
761 762 obj = UserEmailMap()
762 763 obj.user = user
763 764 obj.email = data['email']
764 765 self.sa.add(obj)
765 766 return obj
766 767
767 768 def delete_extra_email(self, user, email_id):
768 769 """
769 770 Removes email address from UserEmailMap
770 771
771 772 :param user:
772 773 :param email_id:
773 774 """
774 775 user = self._get_user(user)
775 776 obj = UserEmailMap.query().get(email_id)
776 777 if obj and obj.user_id == user.user_id:
777 778 self.sa.delete(obj)
778 779
779 780 def parse_ip_range(self, ip_range):
780 781 ip_list = []
781 782
782 783 def make_unique(value):
783 784 seen = []
784 785 return [c for c in value if not (c in seen or seen.append(c))]
785 786
786 787 # firsts split by commas
787 788 for ip_range in ip_range.split(','):
788 789 if not ip_range:
789 790 continue
790 791 ip_range = ip_range.strip()
791 792 if '-' in ip_range:
792 793 start_ip, end_ip = ip_range.split('-', 1)
793 794 start_ip = ipaddress.ip_address(safe_unicode(start_ip.strip()))
794 795 end_ip = ipaddress.ip_address(safe_unicode(end_ip.strip()))
795 796 parsed_ip_range = []
796 797
797 798 for index in xrange(int(start_ip), int(end_ip) + 1):
798 799 new_ip = ipaddress.ip_address(index)
799 800 parsed_ip_range.append(str(new_ip))
800 801 ip_list.extend(parsed_ip_range)
801 802 else:
802 803 ip_list.append(ip_range)
803 804
804 805 return make_unique(ip_list)
805 806
806 807 def add_extra_ip(self, user, ip, description=None):
807 808 """
808 809 Adds ip address to UserIpMap
809 810
810 811 :param user:
811 812 :param ip:
812 813 """
813 814 from rhodecode.model import forms
814 815 form = forms.UserExtraIpForm()()
815 816 data = form.to_python({'ip': ip})
816 817 user = self._get_user(user)
817 818
818 819 obj = UserIpMap()
819 820 obj.user = user
820 821 obj.ip_addr = data['ip']
821 822 obj.description = description
822 823 self.sa.add(obj)
823 824 return obj
824 825
825 826 def delete_extra_ip(self, user, ip_id):
826 827 """
827 828 Removes ip address from UserIpMap
828 829
829 830 :param user:
830 831 :param ip_id:
831 832 """
832 833 user = self._get_user(user)
833 834 obj = UserIpMap.query().get(ip_id)
834 835 if obj and obj.user_id == user.user_id:
835 836 self.sa.delete(obj)
836 837
837 838 def get_accounts_in_creation_order(self, current_user=None):
838 839 """
839 840 Get accounts in order of creation for deactivation for license limits
840 841
841 842 pick currently logged in user, and append to the list in position 0
842 843 pick all super-admins in order of creation date and add it to the list
843 844 pick all other accounts in order of creation and add it to the list.
844 845
845 846 Based on that list, the last accounts can be disabled as they are
846 847 created at the end and don't include any of the super admins as well
847 848 as the current user.
848 849
849 850 :param current_user: optionally current user running this operation
850 851 """
851 852
852 853 if not current_user:
853 854 current_user = get_current_rhodecode_user()
854 855 active_super_admins = [
855 856 x.user_id for x in User.query()
856 857 .filter(User.user_id != current_user.user_id)
857 858 .filter(User.active == true())
858 859 .filter(User.admin == true())
859 860 .order_by(User.created_on.asc())]
860 861
861 862 active_regular_users = [
862 863 x.user_id for x in User.query()
863 864 .filter(User.user_id != current_user.user_id)
864 865 .filter(User.active == true())
865 866 .filter(User.admin == false())
866 867 .order_by(User.created_on.asc())]
867 868
868 869 list_of_accounts = [current_user.user_id]
869 870 list_of_accounts += active_super_admins
870 871 list_of_accounts += active_regular_users
871 872
872 873 return list_of_accounts
873 874
874 875 def deactivate_last_users(self, expected_users, current_user=None):
875 876 """
876 877 Deactivate accounts that are over the license limits.
877 878 Algorithm of which accounts to disabled is based on the formula:
878 879
879 880 Get current user, then super admins in creation order, then regular
880 881 active users in creation order.
881 882
882 883 Using that list we mark all accounts from the end of it as inactive.
883 884 This way we block only latest created accounts.
884 885
885 886 :param expected_users: list of users in special order, we deactivate
886 887 the end N ammoun of users from that list
887 888 """
888 889
889 890 list_of_accounts = self.get_accounts_in_creation_order(
890 891 current_user=current_user)
891 892
892 893 for acc_id in list_of_accounts[expected_users + 1:]:
893 894 user = User.get(acc_id)
894 895 log.info('Deactivating account %s for license unlock', user)
895 896 user.active = False
896 897 Session().add(user)
897 898 Session().commit()
898 899
899 900 return
900 901
901 902 def get_user_log(self, user, filter_term):
902 903 user_log = UserLog.query()\
903 904 .filter(or_(UserLog.user_id == user.user_id,
904 905 UserLog.username == user.username))\
905 906 .options(joinedload(UserLog.user))\
906 907 .options(joinedload(UserLog.repository))\
907 908 .order_by(UserLog.action_date.desc())
908 909
909 910 user_log = user_log_filter(user_log, filter_term)
910 911 return user_log
General Comments 0
You need to be logged in to leave comments. Login now