##// END OF EJS Templates
authentication: allow setting extern type with registration....
marcink -
r3255:f80876b0 default
parent child Browse files
Show More
@@ -1,461 +1,477 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2016-2018 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 import time
22 22 import collections
23 23 import datetime
24 24 import formencode
25 25 import formencode.htmlfill
26 26 import logging
27 27 import urlparse
28 28 import requests
29 29
30 30 from pyramid.httpexceptions import HTTPFound
31 31 from pyramid.view import view_config
32 32
33 33 from rhodecode.apps._base import BaseAppView
34 34 from rhodecode.authentication.base import authenticate, HTTP_TYPE
35 35 from rhodecode.events import UserRegistered, trigger
36 36 from rhodecode.lib import helpers as h
37 37 from rhodecode.lib import audit_logger
38 38 from rhodecode.lib.auth import (
39 39 AuthUser, HasPermissionAnyDecorator, CSRFRequired)
40 40 from rhodecode.lib.base import get_ip_addr
41 41 from rhodecode.lib.exceptions import UserCreationError
42 42 from rhodecode.lib.utils2 import safe_str
43 43 from rhodecode.model.db import User, UserApiKeys
44 44 from rhodecode.model.forms import LoginForm, RegisterForm, PasswordResetForm
45 45 from rhodecode.model.meta import Session
46 46 from rhodecode.model.auth_token import AuthTokenModel
47 47 from rhodecode.model.settings import SettingsModel
48 48 from rhodecode.model.user import UserModel
49 49 from rhodecode.translation import _
50 50
51 51
52 52 log = logging.getLogger(__name__)
53 53
54 54 CaptchaData = collections.namedtuple(
55 55 'CaptchaData', 'active, private_key, public_key')
56 56
57 57
58 58 def store_user_in_session(session, username, remember=False):
59 59 user = User.get_by_username(username, case_insensitive=True)
60 60 auth_user = AuthUser(user.user_id)
61 61 auth_user.set_authenticated()
62 62 cs = auth_user.get_cookie_store()
63 63 session['rhodecode_user'] = cs
64 64 user.update_lastlogin()
65 65 Session().commit()
66 66
67 67 # If they want to be remembered, update the cookie
68 68 if remember:
69 69 _year = (datetime.datetime.now() +
70 70 datetime.timedelta(seconds=60 * 60 * 24 * 365))
71 71 session._set_cookie_expires(_year)
72 72
73 73 session.save()
74 74
75 75 safe_cs = cs.copy()
76 76 safe_cs['password'] = '****'
77 77 log.info('user %s is now authenticated and stored in '
78 78 'session, session attrs %s', username, safe_cs)
79 79
80 80 # dumps session attrs back to cookie
81 81 session._update_cookie_out()
82 82 # we set new cookie
83 83 headers = None
84 84 if session.request['set_cookie']:
85 85 # send set-cookie headers back to response to update cookie
86 86 headers = [('Set-Cookie', session.request['cookie_out'])]
87 87 return headers
88 88
89 89
90 90 def get_came_from(request):
91 91 came_from = safe_str(request.GET.get('came_from', ''))
92 92 parsed = urlparse.urlparse(came_from)
93 93 allowed_schemes = ['http', 'https']
94 94 default_came_from = h.route_path('home')
95 95 if parsed.scheme and parsed.scheme not in allowed_schemes:
96 96 log.error('Suspicious URL scheme detected %s for url %s',
97 97 parsed.scheme, parsed)
98 98 came_from = default_came_from
99 99 elif parsed.netloc and request.host != parsed.netloc:
100 100 log.error('Suspicious NETLOC detected %s for url %s server url '
101 101 'is: %s', parsed.netloc, parsed, request.host)
102 102 came_from = default_came_from
103 103 elif any(bad_str in parsed.path for bad_str in ('\r', '\n')):
104 104 log.error('Header injection detected `%s` for url %s server url ',
105 105 parsed.path, parsed)
106 106 came_from = default_came_from
107 107
108 108 return came_from or default_came_from
109 109
110 110
111 111 class LoginView(BaseAppView):
112 112
113 113 def load_default_context(self):
114 114 c = self._get_local_tmpl_context()
115 115 c.came_from = get_came_from(self.request)
116 116
117 117 return c
118 118
119 119 def _get_captcha_data(self):
120 120 settings = SettingsModel().get_all_settings()
121 121 private_key = settings.get('rhodecode_captcha_private_key')
122 122 public_key = settings.get('rhodecode_captcha_public_key')
123 123 active = bool(private_key)
124 124 return CaptchaData(
125 125 active=active, private_key=private_key, public_key=public_key)
126 126
127 127 def validate_captcha(self, private_key):
128 128
129 129 captcha_rs = self.request.POST.get('g-recaptcha-response')
130 130 url = "https://www.google.com/recaptcha/api/siteverify"
131 131 params = {
132 132 'secret': private_key,
133 133 'response': captcha_rs,
134 134 'remoteip': get_ip_addr(self.request.environ)
135 135 }
136 136 verify_rs = requests.get(url, params=params, verify=True, timeout=60)
137 137 verify_rs = verify_rs.json()
138 138 captcha_status = verify_rs.get('success', False)
139 139 captcha_errors = verify_rs.get('error-codes', [])
140 140 if not isinstance(captcha_errors, list):
141 141 captcha_errors = [captcha_errors]
142 142 captcha_errors = ', '.join(captcha_errors)
143 143 captcha_message = ''
144 144 if captcha_status is False:
145 145 captcha_message = "Bad captcha. Errors: {}".format(
146 146 captcha_errors)
147 147
148 148 return captcha_status, captcha_message
149 149
150 150 @view_config(
151 151 route_name='login', request_method='GET',
152 152 renderer='rhodecode:templates/login.mako')
153 153 def login(self):
154 154 c = self.load_default_context()
155 155 auth_user = self._rhodecode_user
156 156
157 157 # redirect if already logged in
158 158 if (auth_user.is_authenticated and
159 159 not auth_user.is_default and auth_user.ip_allowed):
160 160 raise HTTPFound(c.came_from)
161 161
162 162 # check if we use headers plugin, and try to login using it.
163 163 try:
164 164 log.debug('Running PRE-AUTH for headers based authentication')
165 165 auth_info = authenticate(
166 166 '', '', self.request.environ, HTTP_TYPE, skip_missing=True)
167 167 if auth_info:
168 168 headers = store_user_in_session(
169 169 self.session, auth_info.get('username'))
170 170 raise HTTPFound(c.came_from, headers=headers)
171 171 except UserCreationError as e:
172 172 log.error(e)
173 173 h.flash(e, category='error')
174 174
175 175 return self._get_template_context(c)
176 176
177 177 @view_config(
178 178 route_name='login', request_method='POST',
179 179 renderer='rhodecode:templates/login.mako')
180 180 def login_post(self):
181 181 c = self.load_default_context()
182 182
183 183 login_form = LoginForm(self.request.translate)()
184 184
185 185 try:
186 186 self.session.invalidate()
187 187 form_result = login_form.to_python(self.request.POST)
188 188 # form checks for username/password, now we're authenticated
189 189 headers = store_user_in_session(
190 190 self.session,
191 191 username=form_result['username'],
192 192 remember=form_result['remember'])
193 193 log.debug('Redirecting to "%s" after login.', c.came_from)
194 194
195 195 audit_user = audit_logger.UserWrap(
196 196 username=self.request.POST.get('username'),
197 197 ip_addr=self.request.remote_addr)
198 198 action_data = {'user_agent': self.request.user_agent}
199 199 audit_logger.store_web(
200 200 'user.login.success', action_data=action_data,
201 201 user=audit_user, commit=True)
202 202
203 203 raise HTTPFound(c.came_from, headers=headers)
204 204 except formencode.Invalid as errors:
205 205 defaults = errors.value
206 206 # remove password from filling in form again
207 207 defaults.pop('password', None)
208 208 render_ctx = {
209 209 'errors': errors.error_dict,
210 210 'defaults': defaults,
211 211 }
212 212
213 213 audit_user = audit_logger.UserWrap(
214 214 username=self.request.POST.get('username'),
215 215 ip_addr=self.request.remote_addr)
216 216 action_data = {'user_agent': self.request.user_agent}
217 217 audit_logger.store_web(
218 218 'user.login.failure', action_data=action_data,
219 219 user=audit_user, commit=True)
220 220 return self._get_template_context(c, **render_ctx)
221 221
222 222 except UserCreationError as e:
223 223 # headers auth or other auth functions that create users on
224 224 # the fly can throw this exception signaling that there's issue
225 225 # with user creation, explanation should be provided in
226 226 # Exception itself
227 227 h.flash(e, category='error')
228 228 return self._get_template_context(c)
229 229
230 230 @CSRFRequired()
231 231 @view_config(route_name='logout', request_method='POST')
232 232 def logout(self):
233 233 auth_user = self._rhodecode_user
234 234 log.info('Deleting session for user: `%s`', auth_user)
235 235
236 236 action_data = {'user_agent': self.request.user_agent}
237 237 audit_logger.store_web(
238 238 'user.logout', action_data=action_data,
239 239 user=auth_user, commit=True)
240 240 self.session.delete()
241 241 return HTTPFound(h.route_path('home'))
242 242
243 243 @HasPermissionAnyDecorator(
244 244 'hg.admin', 'hg.register.auto_activate', 'hg.register.manual_activate')
245 245 @view_config(
246 246 route_name='register', request_method='GET',
247 247 renderer='rhodecode:templates/register.mako',)
248 248 def register(self, defaults=None, errors=None):
249 249 c = self.load_default_context()
250 250 defaults = defaults or {}
251 251 errors = errors or {}
252 252
253 253 settings = SettingsModel().get_all_settings()
254 254 register_message = settings.get('rhodecode_register_message') or ''
255 255 captcha = self._get_captcha_data()
256 256 auto_active = 'hg.register.auto_activate' in User.get_default_user()\
257 257 .AuthUser().permissions['global']
258 258
259 259 render_ctx = self._get_template_context(c)
260 260 render_ctx.update({
261 261 'defaults': defaults,
262 262 'errors': errors,
263 263 'auto_active': auto_active,
264 264 'captcha_active': captcha.active,
265 265 'captcha_public_key': captcha.public_key,
266 266 'register_message': register_message,
267 267 })
268 268 return render_ctx
269 269
270 270 @HasPermissionAnyDecorator(
271 271 'hg.admin', 'hg.register.auto_activate', 'hg.register.manual_activate')
272 272 @view_config(
273 273 route_name='register', request_method='POST',
274 274 renderer='rhodecode:templates/register.mako')
275 275 def register_post(self):
276 from rhodecode.authentication.plugins import auth_rhodecode
277
276 278 self.load_default_context()
277 279 captcha = self._get_captcha_data()
278 280 auto_active = 'hg.register.auto_activate' in User.get_default_user()\
279 281 .AuthUser().permissions['global']
280 282
283 extern_name = auth_rhodecode.RhodeCodeAuthPlugin.uid
284 extern_type = auth_rhodecode.RhodeCodeAuthPlugin.uid
285
281 286 register_form = RegisterForm(self.request.translate)()
282 287 try:
283 288
284 289 form_result = register_form.to_python(self.request.POST)
285 290 form_result['active'] = auto_active
291 external_identity = self.request.POST.get('external_identity')
292
293 if external_identity:
294 extern_name = external_identity
295 extern_type = external_identity
286 296
287 297 if captcha.active:
288 298 captcha_status, captcha_message = self.validate_captcha(
289 299 captcha.private_key)
290 300
291 301 if not captcha_status:
292 302 _value = form_result
293 303 _msg = _('Bad captcha')
294 304 error_dict = {'recaptcha_field': captcha_message}
295 305 raise formencode.Invalid(
296 306 _msg, _value, None, error_dict=error_dict)
297 307
298 new_user = UserModel().create_registration(form_result)
308 new_user = UserModel().create_registration(
309 form_result, extern_name=extern_name, extern_type=extern_type)
299 310
300 311 action_data = {'data': new_user.get_api_data(),
301 312 'user_agent': self.request.user_agent}
302 313
314
315
316 if external_identity:
317 action_data['external_identity'] = external_identity
318
303 319 audit_user = audit_logger.UserWrap(
304 320 username=new_user.username,
305 321 user_id=new_user.user_id,
306 322 ip_addr=self.request.remote_addr)
307 323
308 324 audit_logger.store_web(
309 325 'user.register', action_data=action_data,
310 326 user=audit_user)
311 327
312 328 event = UserRegistered(user=new_user, session=self.session)
313 329 trigger(event)
314 330 h.flash(
315 331 _('You have successfully registered with RhodeCode'),
316 332 category='success')
317 333 Session().commit()
318 334
319 335 redirect_ro = self.request.route_path('login')
320 336 raise HTTPFound(redirect_ro)
321 337
322 338 except formencode.Invalid as errors:
323 339 errors.value.pop('password', None)
324 340 errors.value.pop('password_confirmation', None)
325 341 return self.register(
326 342 defaults=errors.value, errors=errors.error_dict)
327 343
328 344 except UserCreationError as e:
329 345 # container auth or other auth functions that create users on
330 346 # the fly can throw this exception signaling that there's issue
331 347 # with user creation, explanation should be provided in
332 348 # Exception itself
333 349 h.flash(e, category='error')
334 350 return self.register()
335 351
336 352 @view_config(
337 353 route_name='reset_password', request_method=('GET', 'POST'),
338 354 renderer='rhodecode:templates/password_reset.mako')
339 355 def password_reset(self):
340 356 c = self.load_default_context()
341 357 captcha = self._get_captcha_data()
342 358
343 359 template_context = {
344 360 'captcha_active': captcha.active,
345 361 'captcha_public_key': captcha.public_key,
346 362 'defaults': {},
347 363 'errors': {},
348 364 }
349 365
350 366 # always send implicit message to prevent from discovery of
351 367 # matching emails
352 368 msg = _('If such email exists, a password reset link was sent to it.')
353 369
354 370 if self.request.POST:
355 371 if h.HasPermissionAny('hg.password_reset.disabled')():
356 372 _email = self.request.POST.get('email', '')
357 373 log.error('Failed attempt to reset password for `%s`.', _email)
358 374 h.flash(_('Password reset has been disabled.'),
359 375 category='error')
360 376 return HTTPFound(self.request.route_path('reset_password'))
361 377
362 378 password_reset_form = PasswordResetForm(self.request.translate)()
363 379 try:
364 380 form_result = password_reset_form.to_python(
365 381 self.request.POST)
366 382 user_email = form_result['email']
367 383
368 384 if captcha.active:
369 385 captcha_status, captcha_message = self.validate_captcha(
370 386 captcha.private_key)
371 387
372 388 if not captcha_status:
373 389 _value = form_result
374 390 _msg = _('Bad captcha')
375 391 error_dict = {'recaptcha_field': captcha_message}
376 392 raise formencode.Invalid(
377 393 _msg, _value, None, error_dict=error_dict)
378 394
379 395 # Generate reset URL and send mail.
380 396 user = User.get_by_email(user_email)
381 397
382 398 # generate password reset token that expires in 10 minutes
383 399 description = u'Generated token for password reset from {}'.format(
384 400 datetime.datetime.now().isoformat())
385 401
386 402 reset_token = UserModel().add_auth_token(
387 403 user=user, lifetime_minutes=10,
388 404 role=UserModel.auth_token_role.ROLE_PASSWORD_RESET,
389 405 description=description)
390 406 Session().commit()
391 407
392 408 log.debug('Successfully created password recovery token')
393 409 password_reset_url = self.request.route_url(
394 410 'reset_password_confirmation',
395 411 _query={'key': reset_token.api_key})
396 412 UserModel().reset_password_link(
397 413 form_result, password_reset_url)
398 414 # Display success message and redirect.
399 415 h.flash(msg, category='success')
400 416
401 417 action_data = {'email': user_email,
402 418 'user_agent': self.request.user_agent}
403 419 audit_logger.store_web(
404 420 'user.password.reset_request', action_data=action_data,
405 421 user=self._rhodecode_user, commit=True)
406 422 return HTTPFound(self.request.route_path('reset_password'))
407 423
408 424 except formencode.Invalid as errors:
409 425 template_context.update({
410 426 'defaults': errors.value,
411 427 'errors': errors.error_dict,
412 428 })
413 429 if not self.request.POST.get('email'):
414 430 # case of empty email, we want to report that
415 431 return self._get_template_context(c, **template_context)
416 432
417 433 if 'recaptcha_field' in errors.error_dict:
418 434 # case of failed captcha
419 435 return self._get_template_context(c, **template_context)
420 436
421 437 log.debug('faking response on invalid password reset')
422 438 # make this take 2s, to prevent brute forcing.
423 439 time.sleep(2)
424 440 h.flash(msg, category='success')
425 441 return HTTPFound(self.request.route_path('reset_password'))
426 442
427 443 return self._get_template_context(c, **template_context)
428 444
429 445 @view_config(route_name='reset_password_confirmation',
430 446 request_method='GET')
431 447 def password_reset_confirmation(self):
432 448 self.load_default_context()
433 449 if self.request.GET and self.request.GET.get('key'):
434 450 # make this take 2s, to prevent brute forcing.
435 451 time.sleep(2)
436 452
437 453 token = AuthTokenModel().get_auth_token(
438 454 self.request.GET.get('key'))
439 455
440 456 # verify token is the correct role
441 457 if token is None or token.role != UserApiKeys.ROLE_PASSWORD_RESET:
442 458 log.debug('Got token with role:%s expected is %s',
443 459 getattr(token, 'role', 'EMPTY_TOKEN'),
444 460 UserApiKeys.ROLE_PASSWORD_RESET)
445 461 h.flash(
446 462 _('Given reset token is invalid'), category='error')
447 463 return HTTPFound(self.request.route_path('reset_password'))
448 464
449 465 try:
450 466 owner = token.user
451 467 data = {'email': owner.email, 'token': token.api_key}
452 468 UserModel().reset_password(data)
453 469 h.flash(
454 470 _('Your password reset was successful, '
455 471 'a new password has been sent to your email'),
456 472 category='success')
457 473 except Exception as e:
458 474 log.error(e)
459 475 return HTTPFound(self.request.route_path('reset_password'))
460 476
461 477 return HTTPFound(self.request.route_path('login'))
@@ -1,941 +1,942 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2018 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 """
22 22 users model for RhodeCode
23 23 """
24 24
25 25 import logging
26 26 import traceback
27 27 import datetime
28 28 import ipaddress
29 29
30 30 from pyramid.threadlocal import get_current_request
31 31 from sqlalchemy.exc import DatabaseError
32 32
33 33 from rhodecode import events
34 34 from rhodecode.lib.user_log_filter import user_log_filter
35 35 from rhodecode.lib.utils2 import (
36 36 safe_unicode, get_current_rhodecode_user, action_logger_generic,
37 37 AttributeDict, str2bool)
38 38 from rhodecode.lib.exceptions import (
39 39 DefaultUserException, UserOwnsReposException, UserOwnsRepoGroupsException,
40 40 UserOwnsUserGroupsException, NotAllowedToCreateUserError)
41 41 from rhodecode.lib.caching_query import FromCache
42 42 from rhodecode.model import BaseModel
43 43 from rhodecode.model.auth_token import AuthTokenModel
44 44 from rhodecode.model.db import (
45 45 _hash_key, true, false, or_, joinedload, User, UserToPerm,
46 46 UserEmailMap, UserIpMap, UserLog)
47 47 from rhodecode.model.meta import Session
48 48 from rhodecode.model.repo_group import RepoGroupModel
49 49
50 50
51 51 log = logging.getLogger(__name__)
52 52
53 53
54 54 class UserModel(BaseModel):
55 55 cls = User
56 56
57 57 def get(self, user_id, cache=False):
58 58 user = self.sa.query(User)
59 59 if cache:
60 60 user = user.options(
61 61 FromCache("sql_cache_short", "get_user_%s" % user_id))
62 62 return user.get(user_id)
63 63
64 64 def get_user(self, user):
65 65 return self._get_user(user)
66 66
67 67 def _serialize_user(self, user):
68 68 import rhodecode.lib.helpers as h
69 69
70 70 return {
71 71 'id': user.user_id,
72 72 'first_name': user.first_name,
73 73 'last_name': user.last_name,
74 74 'username': user.username,
75 75 'email': user.email,
76 76 'icon_link': h.gravatar_url(user.email, 30),
77 77 'profile_link': h.link_to_user(user),
78 78 'value_display': h.escape(h.person(user)),
79 79 'value': user.username,
80 80 'value_type': 'user',
81 81 'active': user.active,
82 82 }
83 83
84 84 def get_users(self, name_contains=None, limit=20, only_active=True):
85 85
86 86 query = self.sa.query(User)
87 87 if only_active:
88 88 query = query.filter(User.active == true())
89 89
90 90 if name_contains:
91 91 ilike_expression = u'%{}%'.format(safe_unicode(name_contains))
92 92 query = query.filter(
93 93 or_(
94 94 User.name.ilike(ilike_expression),
95 95 User.lastname.ilike(ilike_expression),
96 96 User.username.ilike(ilike_expression)
97 97 )
98 98 )
99 99 query = query.limit(limit)
100 100 users = query.all()
101 101
102 102 _users = [
103 103 self._serialize_user(user) for user in users
104 104 ]
105 105 return _users
106 106
107 107 def get_by_username(self, username, cache=False, case_insensitive=False):
108 108
109 109 if case_insensitive:
110 110 user = self.sa.query(User).filter(User.username.ilike(username))
111 111 else:
112 112 user = self.sa.query(User)\
113 113 .filter(User.username == username)
114 114 if cache:
115 115 name_key = _hash_key(username)
116 116 user = user.options(
117 117 FromCache("sql_cache_short", "get_user_%s" % name_key))
118 118 return user.scalar()
119 119
120 120 def get_by_email(self, email, cache=False, case_insensitive=False):
121 121 return User.get_by_email(email, case_insensitive, cache)
122 122
123 123 def get_by_auth_token(self, auth_token, cache=False):
124 124 return User.get_by_auth_token(auth_token, cache)
125 125
126 126 def get_active_user_count(self, cache=False):
127 127 qry = User.query().filter(
128 128 User.active == true()).filter(
129 129 User.username != User.DEFAULT_USER)
130 130 if cache:
131 131 qry = qry.options(
132 132 FromCache("sql_cache_short", "get_active_users"))
133 133 return qry.count()
134 134
135 135 def create(self, form_data, cur_user=None):
136 136 if not cur_user:
137 137 cur_user = getattr(get_current_rhodecode_user(), 'username', None)
138 138
139 139 user_data = {
140 140 'username': form_data['username'],
141 141 'password': form_data['password'],
142 142 'email': form_data['email'],
143 143 'firstname': form_data['firstname'],
144 144 'lastname': form_data['lastname'],
145 145 'active': form_data['active'],
146 146 'extern_type': form_data['extern_type'],
147 147 'extern_name': form_data['extern_name'],
148 148 'admin': False,
149 149 'cur_user': cur_user
150 150 }
151 151
152 152 if 'create_repo_group' in form_data:
153 153 user_data['create_repo_group'] = str2bool(
154 154 form_data.get('create_repo_group'))
155 155
156 156 try:
157 157 if form_data.get('password_change'):
158 158 user_data['force_password_change'] = True
159 159 return UserModel().create_or_update(**user_data)
160 160 except Exception:
161 161 log.error(traceback.format_exc())
162 162 raise
163 163
164 164 def update_user(self, user, skip_attrs=None, **kwargs):
165 165 from rhodecode.lib.auth import get_crypt_password
166 166
167 167 user = self._get_user(user)
168 168 if user.username == User.DEFAULT_USER:
169 169 raise DefaultUserException(
170 170 "You can't edit this user (`%(username)s`) since it's "
171 171 "crucial for entire application" % {
172 172 'username': user.username})
173 173
174 174 # first store only defaults
175 175 user_attrs = {
176 176 'updating_user_id': user.user_id,
177 177 'username': user.username,
178 178 'password': user.password,
179 179 'email': user.email,
180 180 'firstname': user.name,
181 181 'lastname': user.lastname,
182 182 'active': user.active,
183 183 'admin': user.admin,
184 184 'extern_name': user.extern_name,
185 185 'extern_type': user.extern_type,
186 186 'language': user.user_data.get('language')
187 187 }
188 188
189 189 # in case there's new_password, that comes from form, use it to
190 190 # store password
191 191 if kwargs.get('new_password'):
192 192 kwargs['password'] = kwargs['new_password']
193 193
194 194 # cleanups, my_account password change form
195 195 kwargs.pop('current_password', None)
196 196 kwargs.pop('new_password', None)
197 197
198 198 # cleanups, user edit password change form
199 199 kwargs.pop('password_confirmation', None)
200 200 kwargs.pop('password_change', None)
201 201
202 202 # create repo group on user creation
203 203 kwargs.pop('create_repo_group', None)
204 204
205 205 # legacy forms send name, which is the firstname
206 206 firstname = kwargs.pop('name', None)
207 207 if firstname:
208 208 kwargs['firstname'] = firstname
209 209
210 210 for k, v in kwargs.items():
211 211 # skip if we don't want to update this
212 212 if skip_attrs and k in skip_attrs:
213 213 continue
214 214
215 215 user_attrs[k] = v
216 216
217 217 try:
218 218 return self.create_or_update(**user_attrs)
219 219 except Exception:
220 220 log.error(traceback.format_exc())
221 221 raise
222 222
223 223 def create_or_update(
224 224 self, username, password, email, firstname='', lastname='',
225 225 active=True, admin=False, extern_type=None, extern_name=None,
226 226 cur_user=None, plugin=None, force_password_change=False,
227 227 allow_to_create_user=True, create_repo_group=None,
228 228 updating_user_id=None, language=None, strict_creation_check=True):
229 229 """
230 230 Creates a new instance if not found, or updates current one
231 231
232 232 :param username:
233 233 :param password:
234 234 :param email:
235 235 :param firstname:
236 236 :param lastname:
237 237 :param active:
238 238 :param admin:
239 239 :param extern_type:
240 240 :param extern_name:
241 241 :param cur_user:
242 242 :param plugin: optional plugin this method was called from
243 243 :param force_password_change: toggles new or existing user flag
244 244 for password change
245 245 :param allow_to_create_user: Defines if the method can actually create
246 246 new users
247 247 :param create_repo_group: Defines if the method should also
248 248 create an repo group with user name, and owner
249 249 :param updating_user_id: if we set it up this is the user we want to
250 250 update this allows to editing username.
251 251 :param language: language of user from interface.
252 252
253 253 :returns: new User object with injected `is_new_user` attribute.
254 254 """
255 255
256 256 if not cur_user:
257 257 cur_user = getattr(get_current_rhodecode_user(), 'username', None)
258 258
259 259 from rhodecode.lib.auth import (
260 260 get_crypt_password, check_password, generate_auth_token)
261 261 from rhodecode.lib.hooks_base import (
262 262 log_create_user, check_allowed_create_user)
263 263
264 264 def _password_change(new_user, password):
265 265 old_password = new_user.password or ''
266 266 # empty password
267 267 if not old_password:
268 268 return False
269 269
270 270 # password check is only needed for RhodeCode internal auth calls
271 271 # in case it's a plugin we don't care
272 272 if not plugin:
273 273
274 274 # first check if we gave crypted password back, and if it
275 275 # matches it's not password change
276 276 if new_user.password == password:
277 277 return False
278 278
279 279 password_match = check_password(password, old_password)
280 280 if not password_match:
281 281 return True
282 282
283 283 return False
284 284
285 285 # read settings on default personal repo group creation
286 286 if create_repo_group is None:
287 287 default_create_repo_group = RepoGroupModel()\
288 288 .get_default_create_personal_repo_group()
289 289 create_repo_group = default_create_repo_group
290 290
291 291 user_data = {
292 292 'username': username,
293 293 'password': password,
294 294 'email': email,
295 295 'firstname': firstname,
296 296 'lastname': lastname,
297 297 'active': active,
298 298 'admin': admin
299 299 }
300 300
301 301 if updating_user_id:
302 302 log.debug('Checking for existing account in RhodeCode '
303 303 'database with user_id `%s` ', updating_user_id)
304 304 user = User.get(updating_user_id)
305 305 else:
306 306 log.debug('Checking for existing account in RhodeCode '
307 307 'database with username `%s` ', username)
308 308 user = User.get_by_username(username, case_insensitive=True)
309 309
310 310 if user is None:
311 311 # we check internal flag if this method is actually allowed to
312 312 # create new user
313 313 if not allow_to_create_user:
314 314 msg = ('Method wants to create new user, but it is not '
315 315 'allowed to do so')
316 316 log.warning(msg)
317 317 raise NotAllowedToCreateUserError(msg)
318 318
319 319 log.debug('Creating new user %s', username)
320 320
321 321 # only if we create user that is active
322 322 new_active_user = active
323 323 if new_active_user and strict_creation_check:
324 324 # raises UserCreationError if it's not allowed for any reason to
325 325 # create new active user, this also executes pre-create hooks
326 326 check_allowed_create_user(user_data, cur_user, strict_check=True)
327 327 events.trigger(events.UserPreCreate(user_data))
328 328 new_user = User()
329 329 edit = False
330 330 else:
331 331 log.debug('updating user %s', username)
332 332 events.trigger(events.UserPreUpdate(user, user_data))
333 333 new_user = user
334 334 edit = True
335 335
336 336 # we're not allowed to edit default user
337 337 if user.username == User.DEFAULT_USER:
338 338 raise DefaultUserException(
339 339 "You can't edit this user (`%(username)s`) since it's "
340 340 "crucial for entire application"
341 341 % {'username': user.username})
342 342
343 343 # inject special attribute that will tell us if User is new or old
344 344 new_user.is_new_user = not edit
345 345 # for users that didn's specify auth type, we use RhodeCode built in
346 346 from rhodecode.authentication.plugins import auth_rhodecode
347 extern_name = extern_name or auth_rhodecode.RhodeCodeAuthPlugin.name
348 extern_type = extern_type or auth_rhodecode.RhodeCodeAuthPlugin.name
347 extern_name = extern_name or auth_rhodecode.RhodeCodeAuthPlugin.uid
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 def create_registration(self, form_data):
395 def create_registration(self, form_data,
396 extern_name='rhodecode', extern_type='rhodecode'):
396 397 from rhodecode.model.notification import NotificationModel
397 398 from rhodecode.model.notification import EmailNotificationModel
398 399
399 400 try:
400 401 form_data['admin'] = False
401 form_data['extern_name'] = 'rhodecode'
402 form_data['extern_type'] = 'rhodecode'
402 form_data['extern_name'] = extern_name
403 form_data['extern_type'] = extern_type
403 404 new_user = self.create(form_data)
404 405
405 406 self.sa.add(new_user)
406 407 self.sa.flush()
407 408
408 409 user_data = new_user.get_dict()
409 410 kwargs = {
410 411 # use SQLALCHEMY safe dump of user data
411 412 'user': AttributeDict(user_data),
412 413 'date': datetime.datetime.now()
413 414 }
414 415 notification_type = EmailNotificationModel.TYPE_REGISTRATION
415 416 # pre-generate the subject for notification itself
416 417 (subject,
417 418 _h, _e, # we don't care about those
418 419 body_plaintext) = EmailNotificationModel().render_email(
419 420 notification_type, **kwargs)
420 421
421 422 # create notification objects, and emails
422 423 NotificationModel().create(
423 424 created_by=new_user,
424 425 notification_subject=subject,
425 426 notification_body=body_plaintext,
426 427 notification_type=notification_type,
427 428 recipients=None, # all admins
428 429 email_kwargs=kwargs,
429 430 )
430 431
431 432 return new_user
432 433 except Exception:
433 434 log.error(traceback.format_exc())
434 435 raise
435 436
436 437 def _handle_user_repos(self, username, repositories, handle_mode=None):
437 438 _superadmin = self.cls.get_first_super_admin()
438 439 left_overs = True
439 440
440 441 from rhodecode.model.repo import RepoModel
441 442
442 443 if handle_mode == 'detach':
443 444 for obj in repositories:
444 445 obj.user = _superadmin
445 446 # set description we know why we super admin now owns
446 447 # additional repositories that were orphaned !
447 448 obj.description += ' \n::detached repository from deleted user: %s' % (username,)
448 449 self.sa.add(obj)
449 450 left_overs = False
450 451 elif handle_mode == 'delete':
451 452 for obj in repositories:
452 453 RepoModel().delete(obj, forks='detach')
453 454 left_overs = False
454 455
455 456 # if nothing is done we have left overs left
456 457 return left_overs
457 458
458 459 def _handle_user_repo_groups(self, username, repository_groups,
459 460 handle_mode=None):
460 461 _superadmin = self.cls.get_first_super_admin()
461 462 left_overs = True
462 463
463 464 from rhodecode.model.repo_group import RepoGroupModel
464 465
465 466 if handle_mode == 'detach':
466 467 for r in repository_groups:
467 468 r.user = _superadmin
468 469 # set description we know why we super admin now owns
469 470 # additional repositories that were orphaned !
470 471 r.group_description += ' \n::detached repository group from deleted user: %s' % (username,)
471 472 r.personal = False
472 473 self.sa.add(r)
473 474 left_overs = False
474 475 elif handle_mode == 'delete':
475 476 for r in repository_groups:
476 477 RepoGroupModel().delete(r)
477 478 left_overs = False
478 479
479 480 # if nothing is done we have left overs left
480 481 return left_overs
481 482
482 483 def _handle_user_user_groups(self, username, user_groups, handle_mode=None):
483 484 _superadmin = self.cls.get_first_super_admin()
484 485 left_overs = True
485 486
486 487 from rhodecode.model.user_group import UserGroupModel
487 488
488 489 if handle_mode == 'detach':
489 490 for r in user_groups:
490 491 for user_user_group_to_perm in r.user_user_group_to_perm:
491 492 if user_user_group_to_perm.user.username == username:
492 493 user_user_group_to_perm.user = _superadmin
493 494 r.user = _superadmin
494 495 # set description we know why we super admin now owns
495 496 # additional repositories that were orphaned !
496 497 r.user_group_description += ' \n::detached user group from deleted user: %s' % (username,)
497 498 self.sa.add(r)
498 499 left_overs = False
499 500 elif handle_mode == 'delete':
500 501 for r in user_groups:
501 502 UserGroupModel().delete(r)
502 503 left_overs = False
503 504
504 505 # if nothing is done we have left overs left
505 506 return left_overs
506 507
507 508 def delete(self, user, cur_user=None, handle_repos=None,
508 509 handle_repo_groups=None, handle_user_groups=None):
509 510 if not cur_user:
510 511 cur_user = getattr(
511 512 get_current_rhodecode_user(), 'username', None)
512 513 user = self._get_user(user)
513 514
514 515 try:
515 516 if user.username == User.DEFAULT_USER:
516 517 raise DefaultUserException(
517 518 u"You can't remove this user since it's"
518 519 u" crucial for entire application")
519 520
520 521 left_overs = self._handle_user_repos(
521 522 user.username, user.repositories, handle_repos)
522 523 if left_overs and user.repositories:
523 524 repos = [x.repo_name for x in user.repositories]
524 525 raise UserOwnsReposException(
525 526 u'user "%(username)s" still owns %(len_repos)s repositories and cannot be '
526 527 u'removed. Switch owners or remove those repositories:%(list_repos)s'
527 528 % {'username': user.username, 'len_repos': len(repos),
528 529 'list_repos': ', '.join(repos)})
529 530
530 531 left_overs = self._handle_user_repo_groups(
531 532 user.username, user.repository_groups, handle_repo_groups)
532 533 if left_overs and user.repository_groups:
533 534 repo_groups = [x.group_name for x in user.repository_groups]
534 535 raise UserOwnsRepoGroupsException(
535 536 u'user "%(username)s" still owns %(len_repo_groups)s repository groups and cannot be '
536 537 u'removed. Switch owners or remove those repository groups:%(list_repo_groups)s'
537 538 % {'username': user.username, 'len_repo_groups': len(repo_groups),
538 539 'list_repo_groups': ', '.join(repo_groups)})
539 540
540 541 left_overs = self._handle_user_user_groups(
541 542 user.username, user.user_groups, handle_user_groups)
542 543 if left_overs and user.user_groups:
543 544 user_groups = [x.users_group_name for x in user.user_groups]
544 545 raise UserOwnsUserGroupsException(
545 546 u'user "%s" still owns %s user groups and cannot be '
546 547 u'removed. Switch owners or remove those user groups:%s'
547 548 % (user.username, len(user_groups), ', '.join(user_groups)))
548 549
549 550 # we might change the user data with detach/delete, make sure
550 551 # the object is marked as expired before actually deleting !
551 552 self.sa.expire(user)
552 553 self.sa.delete(user)
553 554 from rhodecode.lib.hooks_base import log_delete_user
554 555 log_delete_user(deleted_by=cur_user, **user.get_dict())
555 556 except Exception:
556 557 log.error(traceback.format_exc())
557 558 raise
558 559
559 560 def reset_password_link(self, data, pwd_reset_url):
560 561 from rhodecode.lib.celerylib import tasks, run_task
561 562 from rhodecode.model.notification import EmailNotificationModel
562 563 user_email = data['email']
563 564 try:
564 565 user = User.get_by_email(user_email)
565 566 if user:
566 567 log.debug('password reset user found %s', user)
567 568
568 569 email_kwargs = {
569 570 'password_reset_url': pwd_reset_url,
570 571 'user': user,
571 572 'email': user_email,
572 573 'date': datetime.datetime.now()
573 574 }
574 575
575 576 (subject, headers, email_body,
576 577 email_body_plaintext) = EmailNotificationModel().render_email(
577 578 EmailNotificationModel.TYPE_PASSWORD_RESET, **email_kwargs)
578 579
579 580 recipients = [user_email]
580 581
581 582 action_logger_generic(
582 583 'sending password reset email to user: {}'.format(
583 584 user), namespace='security.password_reset')
584 585
585 586 run_task(tasks.send_email, recipients, subject,
586 587 email_body_plaintext, email_body)
587 588
588 589 else:
589 590 log.debug("password reset email %s not found", user_email)
590 591 except Exception:
591 592 log.error(traceback.format_exc())
592 593 return False
593 594
594 595 return True
595 596
596 597 def reset_password(self, data):
597 598 from rhodecode.lib.celerylib import tasks, run_task
598 599 from rhodecode.model.notification import EmailNotificationModel
599 600 from rhodecode.lib import auth
600 601 user_email = data['email']
601 602 pre_db = True
602 603 try:
603 604 user = User.get_by_email(user_email)
604 605 new_passwd = auth.PasswordGenerator().gen_password(
605 606 12, auth.PasswordGenerator.ALPHABETS_BIG_SMALL)
606 607 if user:
607 608 user.password = auth.get_crypt_password(new_passwd)
608 609 # also force this user to reset his password !
609 610 user.update_userdata(force_password_change=True)
610 611
611 612 Session().add(user)
612 613
613 614 # now delete the token in question
614 615 UserApiKeys = AuthTokenModel.cls
615 616 UserApiKeys().query().filter(
616 617 UserApiKeys.api_key == data['token']).delete()
617 618
618 619 Session().commit()
619 620 log.info('successfully reset password for `%s`', user_email)
620 621
621 622 if new_passwd is None:
622 623 raise Exception('unable to generate new password')
623 624
624 625 pre_db = False
625 626
626 627 email_kwargs = {
627 628 'new_password': new_passwd,
628 629 'user': user,
629 630 'email': user_email,
630 631 'date': datetime.datetime.now()
631 632 }
632 633
633 634 (subject, headers, email_body,
634 635 email_body_plaintext) = EmailNotificationModel().render_email(
635 636 EmailNotificationModel.TYPE_PASSWORD_RESET_CONFIRMATION,
636 637 **email_kwargs)
637 638
638 639 recipients = [user_email]
639 640
640 641 action_logger_generic(
641 642 'sent new password to user: {} with email: {}'.format(
642 643 user, user_email), namespace='security.password_reset')
643 644
644 645 run_task(tasks.send_email, recipients, subject,
645 646 email_body_plaintext, email_body)
646 647
647 648 except Exception:
648 649 log.error('Failed to update user password')
649 650 log.error(traceback.format_exc())
650 651 if pre_db:
651 652 # we rollback only if local db stuff fails. If it goes into
652 653 # run_task, we're pass rollback state this wouldn't work then
653 654 Session().rollback()
654 655
655 656 return True
656 657
657 658 def fill_data(self, auth_user, user_id=None, api_key=None, username=None):
658 659 """
659 660 Fetches auth_user by user_id,or api_key if present.
660 661 Fills auth_user attributes with those taken from database.
661 662 Additionally set's is_authenitated if lookup fails
662 663 present in database
663 664
664 665 :param auth_user: instance of user to set attributes
665 666 :param user_id: user id to fetch by
666 667 :param api_key: api key to fetch by
667 668 :param username: username to fetch by
668 669 """
669 670 def token_obfuscate(token):
670 671 if token:
671 672 return token[:4] + "****"
672 673
673 674 if user_id is None and api_key is None and username is None:
674 675 raise Exception('You need to pass user_id, api_key or username')
675 676
676 677 log.debug(
677 678 'AuthUser: fill data execution based on: '
678 679 'user_id:%s api_key:%s username:%s', user_id, api_key, username)
679 680 try:
680 681 dbuser = None
681 682 if user_id:
682 683 dbuser = self.get(user_id)
683 684 elif api_key:
684 685 dbuser = self.get_by_auth_token(api_key)
685 686 elif username:
686 687 dbuser = self.get_by_username(username)
687 688
688 689 if not dbuser:
689 690 log.warning(
690 691 'Unable to lookup user by id:%s api_key:%s username:%s',
691 692 user_id, token_obfuscate(api_key), username)
692 693 return False
693 694 if not dbuser.active:
694 695 log.debug('User `%s:%s` is inactive, skipping fill data',
695 696 username, user_id)
696 697 return False
697 698
698 699 log.debug('AuthUser: filling found user:%s data', dbuser)
699 700 user_data = dbuser.get_dict()
700 701
701 702 user_data.update({
702 703 # set explicit the safe escaped values
703 704 'first_name': dbuser.first_name,
704 705 'last_name': dbuser.last_name,
705 706 })
706 707
707 708 for k, v in user_data.items():
708 709 # properties of auth user we dont update
709 710 if k not in ['auth_tokens', 'permissions']:
710 711 setattr(auth_user, k, v)
711 712
712 713 except Exception:
713 714 log.error(traceback.format_exc())
714 715 auth_user.is_authenticated = False
715 716 return False
716 717
717 718 return True
718 719
719 720 def has_perm(self, user, perm):
720 721 perm = self._get_perm(perm)
721 722 user = self._get_user(user)
722 723
723 724 return UserToPerm.query().filter(UserToPerm.user == user)\
724 725 .filter(UserToPerm.permission == perm).scalar() is not None
725 726
726 727 def grant_perm(self, user, perm):
727 728 """
728 729 Grant user global permissions
729 730
730 731 :param user:
731 732 :param perm:
732 733 """
733 734 user = self._get_user(user)
734 735 perm = self._get_perm(perm)
735 736 # if this permission is already granted skip it
736 737 _perm = UserToPerm.query()\
737 738 .filter(UserToPerm.user == user)\
738 739 .filter(UserToPerm.permission == perm)\
739 740 .scalar()
740 741 if _perm:
741 742 return
742 743 new = UserToPerm()
743 744 new.user = user
744 745 new.permission = perm
745 746 self.sa.add(new)
746 747 return new
747 748
748 749 def revoke_perm(self, user, perm):
749 750 """
750 751 Revoke users global permissions
751 752
752 753 :param user:
753 754 :param perm:
754 755 """
755 756 user = self._get_user(user)
756 757 perm = self._get_perm(perm)
757 758
758 759 obj = UserToPerm.query()\
759 760 .filter(UserToPerm.user == user)\
760 761 .filter(UserToPerm.permission == perm)\
761 762 .scalar()
762 763 if obj:
763 764 self.sa.delete(obj)
764 765
765 766 def add_extra_email(self, user, email):
766 767 """
767 768 Adds email address to UserEmailMap
768 769
769 770 :param user:
770 771 :param email:
771 772 """
772 773
773 774 user = self._get_user(user)
774 775
775 776 obj = UserEmailMap()
776 777 obj.user = user
777 778 obj.email = email
778 779 self.sa.add(obj)
779 780 return obj
780 781
781 782 def delete_extra_email(self, user, email_id):
782 783 """
783 784 Removes email address from UserEmailMap
784 785
785 786 :param user:
786 787 :param email_id:
787 788 """
788 789 user = self._get_user(user)
789 790 obj = UserEmailMap.query().get(email_id)
790 791 if obj and obj.user_id == user.user_id:
791 792 self.sa.delete(obj)
792 793
793 794 def parse_ip_range(self, ip_range):
794 795 ip_list = []
795 796
796 797 def make_unique(value):
797 798 seen = []
798 799 return [c for c in value if not (c in seen or seen.append(c))]
799 800
800 801 # firsts split by commas
801 802 for ip_range in ip_range.split(','):
802 803 if not ip_range:
803 804 continue
804 805 ip_range = ip_range.strip()
805 806 if '-' in ip_range:
806 807 start_ip, end_ip = ip_range.split('-', 1)
807 808 start_ip = ipaddress.ip_address(safe_unicode(start_ip.strip()))
808 809 end_ip = ipaddress.ip_address(safe_unicode(end_ip.strip()))
809 810 parsed_ip_range = []
810 811
811 812 for index in xrange(int(start_ip), int(end_ip) + 1):
812 813 new_ip = ipaddress.ip_address(index)
813 814 parsed_ip_range.append(str(new_ip))
814 815 ip_list.extend(parsed_ip_range)
815 816 else:
816 817 ip_list.append(ip_range)
817 818
818 819 return make_unique(ip_list)
819 820
820 821 def add_extra_ip(self, user, ip, description=None):
821 822 """
822 823 Adds ip address to UserIpMap
823 824
824 825 :param user:
825 826 :param ip:
826 827 """
827 828
828 829 user = self._get_user(user)
829 830 obj = UserIpMap()
830 831 obj.user = user
831 832 obj.ip_addr = ip
832 833 obj.description = description
833 834 self.sa.add(obj)
834 835 return obj
835 836
836 837 auth_token_role = AuthTokenModel.cls
837 838
838 839 def add_auth_token(self, user, lifetime_minutes, role, description=u'',
839 840 scope_callback=None):
840 841 """
841 842 Add AuthToken for user.
842 843
843 844 :param user: username/user_id
844 845 :param lifetime_minutes: in minutes the lifetime for token, -1 equals no limit
845 846 :param role: one of AuthTokenModel.cls.ROLE_*
846 847 :param description: optional string description
847 848 """
848 849
849 850 token = AuthTokenModel().create(
850 851 user, description, lifetime_minutes, role)
851 852 if scope_callback and callable(scope_callback):
852 853 # call the callback if we provide, used to attach scope for EE edition
853 854 scope_callback(token)
854 855 return token
855 856
856 857 def delete_extra_ip(self, user, ip_id):
857 858 """
858 859 Removes ip address from UserIpMap
859 860
860 861 :param user:
861 862 :param ip_id:
862 863 """
863 864 user = self._get_user(user)
864 865 obj = UserIpMap.query().get(ip_id)
865 866 if obj and obj.user_id == user.user_id:
866 867 self.sa.delete(obj)
867 868
868 869 def get_accounts_in_creation_order(self, current_user=None):
869 870 """
870 871 Get accounts in order of creation for deactivation for license limits
871 872
872 873 pick currently logged in user, and append to the list in position 0
873 874 pick all super-admins in order of creation date and add it to the list
874 875 pick all other accounts in order of creation and add it to the list.
875 876
876 877 Based on that list, the last accounts can be disabled as they are
877 878 created at the end and don't include any of the super admins as well
878 879 as the current user.
879 880
880 881 :param current_user: optionally current user running this operation
881 882 """
882 883
883 884 if not current_user:
884 885 current_user = get_current_rhodecode_user()
885 886 active_super_admins = [
886 887 x.user_id for x in User.query()
887 888 .filter(User.user_id != current_user.user_id)
888 889 .filter(User.active == true())
889 890 .filter(User.admin == true())
890 891 .order_by(User.created_on.asc())]
891 892
892 893 active_regular_users = [
893 894 x.user_id for x in User.query()
894 895 .filter(User.user_id != current_user.user_id)
895 896 .filter(User.active == true())
896 897 .filter(User.admin == false())
897 898 .order_by(User.created_on.asc())]
898 899
899 900 list_of_accounts = [current_user.user_id]
900 901 list_of_accounts += active_super_admins
901 902 list_of_accounts += active_regular_users
902 903
903 904 return list_of_accounts
904 905
905 906 def deactivate_last_users(self, expected_users, current_user=None):
906 907 """
907 908 Deactivate accounts that are over the license limits.
908 909 Algorithm of which accounts to disabled is based on the formula:
909 910
910 911 Get current user, then super admins in creation order, then regular
911 912 active users in creation order.
912 913
913 914 Using that list we mark all accounts from the end of it as inactive.
914 915 This way we block only latest created accounts.
915 916
916 917 :param expected_users: list of users in special order, we deactivate
917 918 the end N amount of users from that list
918 919 """
919 920
920 921 list_of_accounts = self.get_accounts_in_creation_order(
921 922 current_user=current_user)
922 923
923 924 for acc_id in list_of_accounts[expected_users + 1:]:
924 925 user = User.get(acc_id)
925 926 log.info('Deactivating account %s for license unlock', user)
926 927 user.active = False
927 928 Session().add(user)
928 929 Session().commit()
929 930
930 931 return
931 932
932 933 def get_user_log(self, user, filter_term):
933 934 user_log = UserLog.query()\
934 935 .filter(or_(UserLog.user_id == user.user_id,
935 936 UserLog.username == user.username))\
936 937 .options(joinedload(UserLog.user))\
937 938 .options(joinedload(UserLog.repository))\
938 939 .order_by(UserLog.action_date.desc())
939 940
940 941 user_log = user_log_filter(user_log, filter_term)
941 942 return user_log
General Comments 0
You need to be logged in to leave comments. Login now