##// END OF EJS Templates
audit-logs: added audit-logs on user actions....
marcink -
r1801:c1a16410 default
parent child Browse files
Show More
@@ -1,629 +1,643 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 crud controller for pylons
23 23 """
24 24
25 25 import logging
26 26 import formencode
27 27
28 28 from formencode import htmlfill
29 29 from pylons import request, tmpl_context as c, url, config
30 30 from pylons.controllers.util import redirect
31 31 from pylons.i18n.translation import _
32 32
33 33 from rhodecode.authentication.plugins import auth_rhodecode
34
35 from rhodecode.lib import helpers as h
36 from rhodecode.lib import auth
37 from rhodecode.lib import audit_logger
38 from rhodecode.lib.auth import (
39 LoginRequired, HasPermissionAllDecorator, AuthUser)
40 from rhodecode.lib.base import BaseController, render
34 41 from rhodecode.lib.exceptions import (
35 42 DefaultUserException, UserOwnsReposException, UserOwnsRepoGroupsException,
36 43 UserOwnsUserGroupsException, UserCreationError)
37 from rhodecode.lib import helpers as h
38 from rhodecode.lib import auth
39 from rhodecode.lib.auth import (
40 LoginRequired, HasPermissionAllDecorator, AuthUser, generate_auth_token)
41 from rhodecode.lib.base import BaseController, render
42 from rhodecode.model.auth_token import AuthTokenModel
44 from rhodecode.lib.utils2 import safe_int, AttributeDict
43 45
44 46 from rhodecode.model.db import (
45 47 PullRequestReviewers, User, UserEmailMap, UserIpMap, RepoGroup)
46 48 from rhodecode.model.forms import (
47 49 UserForm, UserPermissionsForm, UserIndividualPermissionsForm)
48 50 from rhodecode.model.repo_group import RepoGroupModel
49 51 from rhodecode.model.user import UserModel
50 52 from rhodecode.model.meta import Session
51 53 from rhodecode.model.permission import PermissionModel
52 from rhodecode.lib.utils import action_logger
53 from rhodecode.lib.utils2 import datetime_to_time, safe_int, AttributeDict
54 54
55 55 log = logging.getLogger(__name__)
56 56
57 57
58 58 class UsersController(BaseController):
59 59 """REST Controller styled on the Atom Publishing Protocol"""
60 60
61 61 @LoginRequired()
62 62 def __before__(self):
63 63 super(UsersController, self).__before__()
64 64 c.available_permissions = config['available_permissions']
65 65 c.allowed_languages = [
66 66 ('en', 'English (en)'),
67 67 ('de', 'German (de)'),
68 68 ('fr', 'French (fr)'),
69 69 ('it', 'Italian (it)'),
70 70 ('ja', 'Japanese (ja)'),
71 71 ('pl', 'Polish (pl)'),
72 72 ('pt', 'Portuguese (pt)'),
73 73 ('ru', 'Russian (ru)'),
74 74 ('zh', 'Chinese (zh)'),
75 75 ]
76 76 PermissionModel().set_global_permission_choices(c, gettext_translator=_)
77 77
78 78 def _get_personal_repo_group_template_vars(self):
79 79 DummyUser = AttributeDict({
80 80 'username': '${username}',
81 81 'user_id': '${user_id}',
82 82 })
83 83 c.default_create_repo_group = RepoGroupModel() \
84 84 .get_default_create_personal_repo_group()
85 85 c.personal_repo_group_name = RepoGroupModel() \
86 86 .get_personal_group_name(DummyUser)
87 87
88 88 @HasPermissionAllDecorator('hg.admin')
89 89 @auth.CSRFRequired()
90 90 def create(self):
91 """POST /users: Create a new item"""
92 91 c.default_extern_type = auth_rhodecode.RhodeCodeAuthPlugin.name
93 92 user_model = UserModel()
94 93 user_form = UserForm()()
95 94 try:
96 95 form_result = user_form.to_python(dict(request.POST))
97 96 user = user_model.create(form_result)
98 97 Session().flush()
98 creation_data = user.get_api_data()
99 99 username = form_result['username']
100 action_logger(c.rhodecode_user, 'admin_created_user:%s' % username,
101 None, self.ip_addr, self.sa)
100
101 audit_logger.store_web(
102 'user.create', action_data={'data': creation_data},
103 user=c.rhodecode_user)
102 104
103 105 user_link = h.link_to(h.escape(username),
104 106 url('edit_user',
105 107 user_id=user.user_id))
106 108 h.flash(h.literal(_('Created user %(user_link)s')
107 109 % {'user_link': user_link}), category='success')
108 110 Session().commit()
109 111 except formencode.Invalid as errors:
110 112 self._get_personal_repo_group_template_vars()
111 113 return htmlfill.render(
112 114 render('admin/users/user_add.mako'),
113 115 defaults=errors.value,
114 116 errors=errors.error_dict or {},
115 117 prefix_error=False,
116 118 encoding="UTF-8",
117 119 force_defaults=False)
118 120 except UserCreationError as e:
119 121 h.flash(e, 'error')
120 122 except Exception:
121 123 log.exception("Exception creation of user")
122 124 h.flash(_('Error occurred during creation of user %s')
123 125 % request.POST.get('username'), category='error')
124 126 return redirect(h.route_path('users'))
125 127
126 128 @HasPermissionAllDecorator('hg.admin')
127 129 def new(self):
128 """GET /users/new: Form to create a new item"""
129 # url('new_user')
130 130 c.default_extern_type = auth_rhodecode.RhodeCodeAuthPlugin.name
131 131 self._get_personal_repo_group_template_vars()
132 132 return render('admin/users/user_add.mako')
133 133
134 134 @HasPermissionAllDecorator('hg.admin')
135 135 @auth.CSRFRequired()
136 136 def update(self, user_id):
137 """PUT /users/user_id: Update an existing item"""
138 # Forms posted to this method should contain a hidden field:
139 # <input type="hidden" name="_method" value="PUT" />
140 # Or using helpers:
141 # h.form(url('update_user', user_id=ID),
142 # method='put')
143 # url('user', user_id=ID)
137
144 138 user_id = safe_int(user_id)
145 139 c.user = User.get_or_404(user_id)
146 140 c.active = 'profile'
147 141 c.extern_type = c.user.extern_type
148 142 c.extern_name = c.user.extern_name
149 143 c.perm_user = AuthUser(user_id=user_id, ip_addr=self.ip_addr)
150 144 available_languages = [x[0] for x in c.allowed_languages]
151 145 _form = UserForm(edit=True, available_languages=available_languages,
152 146 old_data={'user_id': user_id,
153 147 'email': c.user.email})()
154 148 form_result = {}
149 old_values = c.user.get_api_data()
155 150 try:
156 151 form_result = _form.to_python(dict(request.POST))
157 152 skip_attrs = ['extern_type', 'extern_name']
158 153 # TODO: plugin should define if username can be updated
159 154 if c.extern_type != "rhodecode":
160 155 # forbid updating username for external accounts
161 156 skip_attrs.append('username')
162 157
163 UserModel().update_user(user_id, skip_attrs=skip_attrs, **form_result)
164 usr = form_result['username']
165 action_logger(c.rhodecode_user, 'admin_updated_user:%s' % usr,
166 None, self.ip_addr, self.sa)
158 UserModel().update_user(
159 user_id, skip_attrs=skip_attrs, **form_result)
160
161 audit_logger.store_web(
162 'user.edit', action_data={'old_data': old_values},
163 user=c.rhodecode_user)
164
165 Session().commit()
167 166 h.flash(_('User updated successfully'), category='success')
168 Session().commit()
169 167 except formencode.Invalid as errors:
170 168 defaults = errors.value
171 169 e = errors.error_dict or {}
172 170
173 171 return htmlfill.render(
174 172 render('admin/users/user_edit.mako'),
175 173 defaults=defaults,
176 174 errors=e,
177 175 prefix_error=False,
178 176 encoding="UTF-8",
179 177 force_defaults=False)
180 178 except UserCreationError as e:
181 179 h.flash(e, 'error')
182 180 except Exception:
183 181 log.exception("Exception updating user")
184 182 h.flash(_('Error occurred during update of user %s')
185 183 % form_result.get('username'), category='error')
186 184 return redirect(url('edit_user', user_id=user_id))
187 185
188 186 @HasPermissionAllDecorator('hg.admin')
189 187 @auth.CSRFRequired()
190 188 def delete(self, user_id):
191 """DELETE /users/user_id: Delete an existing item"""
192 # Forms posted to this method should contain a hidden field:
193 # <input type="hidden" name="_method" value="DELETE" />
194 # Or using helpers:
195 # h.form(url('delete_user', user_id=ID),
196 # method='delete')
197 # url('user', user_id=ID)
198 189 user_id = safe_int(user_id)
199 190 c.user = User.get_or_404(user_id)
200 191
201 192 _repos = c.user.repositories
202 193 _repo_groups = c.user.repository_groups
203 194 _user_groups = c.user.user_groups
204 195
205 196 handle_repos = None
206 197 handle_repo_groups = None
207 198 handle_user_groups = None
208 199 # dummy call for flash of handle
209 200 set_handle_flash_repos = lambda: None
210 201 set_handle_flash_repo_groups = lambda: None
211 202 set_handle_flash_user_groups = lambda: None
212 203
213 204 if _repos and request.POST.get('user_repos'):
214 205 do = request.POST['user_repos']
215 206 if do == 'detach':
216 207 handle_repos = 'detach'
217 208 set_handle_flash_repos = lambda: h.flash(
218 209 _('Detached %s repositories') % len(_repos),
219 210 category='success')
220 211 elif do == 'delete':
221 212 handle_repos = 'delete'
222 213 set_handle_flash_repos = lambda: h.flash(
223 214 _('Deleted %s repositories') % len(_repos),
224 215 category='success')
225 216
226 217 if _repo_groups and request.POST.get('user_repo_groups'):
227 218 do = request.POST['user_repo_groups']
228 219 if do == 'detach':
229 220 handle_repo_groups = 'detach'
230 221 set_handle_flash_repo_groups = lambda: h.flash(
231 222 _('Detached %s repository groups') % len(_repo_groups),
232 223 category='success')
233 224 elif do == 'delete':
234 225 handle_repo_groups = 'delete'
235 226 set_handle_flash_repo_groups = lambda: h.flash(
236 227 _('Deleted %s repository groups') % len(_repo_groups),
237 228 category='success')
238 229
239 230 if _user_groups and request.POST.get('user_user_groups'):
240 231 do = request.POST['user_user_groups']
241 232 if do == 'detach':
242 233 handle_user_groups = 'detach'
243 234 set_handle_flash_user_groups = lambda: h.flash(
244 235 _('Detached %s user groups') % len(_user_groups),
245 236 category='success')
246 237 elif do == 'delete':
247 238 handle_user_groups = 'delete'
248 239 set_handle_flash_user_groups = lambda: h.flash(
249 240 _('Deleted %s user groups') % len(_user_groups),
250 241 category='success')
251 242
243 old_values = c.user.get_api_data()
252 244 try:
253 245 UserModel().delete(c.user, handle_repos=handle_repos,
254 246 handle_repo_groups=handle_repo_groups,
255 247 handle_user_groups=handle_user_groups)
248
249 audit_logger.store_web(
250 'user.delete', action_data={'old_data': old_values},
251 user=c.rhodecode_user)
252
256 253 Session().commit()
257 254 set_handle_flash_repos()
258 255 set_handle_flash_repo_groups()
259 256 set_handle_flash_user_groups()
260 257 h.flash(_('Successfully deleted user'), category='success')
261 258 except (UserOwnsReposException, UserOwnsRepoGroupsException,
262 259 UserOwnsUserGroupsException, DefaultUserException) as e:
263 260 h.flash(e, category='warning')
264 261 except Exception:
265 262 log.exception("Exception during deletion of user")
266 263 h.flash(_('An error occurred during deletion of user'),
267 264 category='error')
268 265 return redirect(h.route_path('users'))
269 266
270 267 @HasPermissionAllDecorator('hg.admin')
271 268 @auth.CSRFRequired()
272 269 def reset_password(self, user_id):
273 270 """
274 271 toggle reset password flag for this user
275
276 :param user_id:
277 272 """
278 273 user_id = safe_int(user_id)
279 274 c.user = User.get_or_404(user_id)
280 275 try:
281 276 old_value = c.user.user_data.get('force_password_change')
282 277 c.user.update_userdata(force_password_change=not old_value)
283 Session().commit()
278
284 279 if old_value:
285 280 msg = _('Force password change disabled for user')
281 audit_logger.store_web(
282 'user.edit.password_reset.disabled',
283 user=c.rhodecode_user)
286 284 else:
287 285 msg = _('Force password change enabled for user')
286 audit_logger.store_web(
287 'user.edit.password_reset.enabled',
288 user=c.rhodecode_user)
289
290 Session().commit()
288 291 h.flash(msg, category='success')
289 292 except Exception:
290 293 log.exception("Exception during password reset for user")
291 294 h.flash(_('An error occurred during password reset for user'),
292 295 category='error')
293 296
294 297 return redirect(url('edit_user_advanced', user_id=user_id))
295 298
296 299 @HasPermissionAllDecorator('hg.admin')
297 300 @auth.CSRFRequired()
298 301 def create_personal_repo_group(self, user_id):
299 302 """
300 303 Create personal repository group for this user
301
302 :param user_id:
303 304 """
304 305 from rhodecode.model.repo_group import RepoGroupModel
305 306
306 307 user_id = safe_int(user_id)
307 308 c.user = User.get_or_404(user_id)
308 309 personal_repo_group = RepoGroup.get_user_personal_repo_group(
309 310 c.user.user_id)
310 311 if personal_repo_group:
311 312 return redirect(url('edit_user_advanced', user_id=user_id))
312 313
313 314 personal_repo_group_name = RepoGroupModel().get_personal_group_name(
314 315 c.user)
315 316 named_personal_group = RepoGroup.get_by_group_name(
316 317 personal_repo_group_name)
317 318 try:
318 319
319 320 if named_personal_group and named_personal_group.user_id == c.user.user_id:
320 321 # migrate the same named group, and mark it as personal
321 322 named_personal_group.personal = True
322 323 Session().add(named_personal_group)
323 324 Session().commit()
324 325 msg = _('Linked repository group `%s` as personal' % (
325 326 personal_repo_group_name,))
326 327 h.flash(msg, category='success')
327 328 elif not named_personal_group:
328 329 RepoGroupModel().create_personal_repo_group(c.user)
329 330
330 331 msg = _('Created repository group `%s`' % (
331 332 personal_repo_group_name,))
332 333 h.flash(msg, category='success')
333 334 else:
334 335 msg = _('Repository group `%s` is already taken' % (
335 336 personal_repo_group_name,))
336 337 h.flash(msg, category='warning')
337 338 except Exception:
338 339 log.exception("Exception during repository group creation")
339 340 msg = _(
340 341 'An error occurred during repository group creation for user')
341 342 h.flash(msg, category='error')
342 343 Session().rollback()
343 344
344 345 return redirect(url('edit_user_advanced', user_id=user_id))
345 346
346 347 @HasPermissionAllDecorator('hg.admin')
347 348 def show(self, user_id):
348 349 """GET /users/user_id: Show a specific item"""
349 350 # url('user', user_id=ID)
350 351 User.get_or_404(-1)
351 352
352 353 @HasPermissionAllDecorator('hg.admin')
353 354 def edit(self, user_id):
354 355 """GET /users/user_id/edit: Form to edit an existing item"""
355 356 # url('edit_user', user_id=ID)
356 357 user_id = safe_int(user_id)
357 358 c.user = User.get_or_404(user_id)
358 359 if c.user.username == User.DEFAULT_USER:
359 360 h.flash(_("You can't edit this user"), category='warning')
360 361 return redirect(h.route_path('users'))
361 362
362 363 c.active = 'profile'
363 364 c.extern_type = c.user.extern_type
364 365 c.extern_name = c.user.extern_name
365 366 c.perm_user = AuthUser(user_id=user_id, ip_addr=self.ip_addr)
366 367
367 368 defaults = c.user.get_dict()
368 369 defaults.update({'language': c.user.user_data.get('language')})
369 370 return htmlfill.render(
370 371 render('admin/users/user_edit.mako'),
371 372 defaults=defaults,
372 373 encoding="UTF-8",
373 374 force_defaults=False)
374 375
375 376 @HasPermissionAllDecorator('hg.admin')
376 377 def edit_advanced(self, user_id):
377 378 user_id = safe_int(user_id)
378 379 user = c.user = User.get_or_404(user_id)
379 380 if user.username == User.DEFAULT_USER:
380 381 h.flash(_("You can't edit this user"), category='warning')
381 382 return redirect(h.route_path('users'))
382 383
383 384 c.active = 'advanced'
384 385 c.personal_repo_group = RepoGroup.get_user_personal_repo_group(user_id)
385 386 c.personal_repo_group_name = RepoGroupModel()\
386 387 .get_personal_group_name(user)
387 388 c.first_admin = User.get_first_super_admin()
388 389 defaults = user.get_dict()
389 390
390 391 # Interim workaround if the user participated on any pull requests as a
391 392 # reviewer.
392 393 has_review = bool(PullRequestReviewers.query().filter(
393 394 PullRequestReviewers.user_id == user_id).first())
394 395 c.can_delete_user = not has_review
395 396 c.can_delete_user_message = _(
396 397 'The user participates as reviewer in pull requests and '
397 398 'cannot be deleted. You can set the user to '
398 399 '"inactive" instead of deleting it.') if has_review else ''
399 400
400 401 return htmlfill.render(
401 402 render('admin/users/user_edit.mako'),
402 403 defaults=defaults,
403 404 encoding="UTF-8",
404 405 force_defaults=False)
405 406
406 407 @HasPermissionAllDecorator('hg.admin')
407 408 def edit_global_perms(self, user_id):
408 409 user_id = safe_int(user_id)
409 410 c.user = User.get_or_404(user_id)
410 411 if c.user.username == User.DEFAULT_USER:
411 412 h.flash(_("You can't edit this user"), category='warning')
412 413 return redirect(h.route_path('users'))
413 414
414 415 c.active = 'global_perms'
415 416
416 417 c.default_user = User.get_default_user()
417 418 defaults = c.user.get_dict()
418 419 defaults.update(c.default_user.get_default_perms(suffix='_inherited'))
419 420 defaults.update(c.default_user.get_default_perms())
420 421 defaults.update(c.user.get_default_perms())
421 422
422 423 return htmlfill.render(
423 424 render('admin/users/user_edit.mako'),
424 425 defaults=defaults,
425 426 encoding="UTF-8",
426 427 force_defaults=False)
427 428
428 429 @HasPermissionAllDecorator('hg.admin')
429 430 @auth.CSRFRequired()
430 431 def update_global_perms(self, user_id):
431 """PUT /users_perm/user_id: Update an existing item"""
432 # url('user_perm', user_id=ID, method='put')
433 432 user_id = safe_int(user_id)
434 433 user = User.get_or_404(user_id)
435 434 c.active = 'global_perms'
436 435 try:
437 436 # first stage that verifies the checkbox
438 437 _form = UserIndividualPermissionsForm()
439 438 form_result = _form.to_python(dict(request.POST))
440 439 inherit_perms = form_result['inherit_default_permissions']
441 440 user.inherit_default_permissions = inherit_perms
442 441 Session().add(user)
443 442
444 443 if not inherit_perms:
445 444 # only update the individual ones if we un check the flag
446 445 _form = UserPermissionsForm(
447 446 [x[0] for x in c.repo_create_choices],
448 447 [x[0] for x in c.repo_create_on_write_choices],
449 448 [x[0] for x in c.repo_group_create_choices],
450 449 [x[0] for x in c.user_group_create_choices],
451 450 [x[0] for x in c.fork_choices],
452 451 [x[0] for x in c.inherit_default_permission_choices])()
453 452
454 453 form_result = _form.to_python(dict(request.POST))
455 454 form_result.update({'perm_user_id': user.user_id})
456 455
457 456 PermissionModel().update_user_permissions(form_result)
458 457
458 # TODO(marcink): implement global permissions
459 # audit_log.store_web('user.edit.permissions')
460
459 461 Session().commit()
460 462 h.flash(_('User global permissions updated successfully'),
461 463 category='success')
462 464
463 Session().commit()
464 465 except formencode.Invalid as errors:
465 466 defaults = errors.value
466 467 c.user = user
467 468 return htmlfill.render(
468 469 render('admin/users/user_edit.mako'),
469 470 defaults=defaults,
470 471 errors=errors.error_dict or {},
471 472 prefix_error=False,
472 473 encoding="UTF-8",
473 474 force_defaults=False)
474 475 except Exception:
475 476 log.exception("Exception during permissions saving")
476 477 h.flash(_('An error occurred during permissions saving'),
477 478 category='error')
478 479 return redirect(url('edit_user_global_perms', user_id=user_id))
479 480
480 481 @HasPermissionAllDecorator('hg.admin')
481 482 def edit_perms_summary(self, user_id):
482 483 user_id = safe_int(user_id)
483 484 c.user = User.get_or_404(user_id)
484 485 if c.user.username == User.DEFAULT_USER:
485 486 h.flash(_("You can't edit this user"), category='warning')
486 487 return redirect(h.route_path('users'))
487 488
488 489 c.active = 'perms_summary'
489 490 c.perm_user = AuthUser(user_id=user_id, ip_addr=self.ip_addr)
490 491
491 492 return render('admin/users/user_edit.mako')
492 493
493 494 @HasPermissionAllDecorator('hg.admin')
494 495 def edit_emails(self, user_id):
495 496 user_id = safe_int(user_id)
496 497 c.user = User.get_or_404(user_id)
497 498 if c.user.username == User.DEFAULT_USER:
498 499 h.flash(_("You can't edit this user"), category='warning')
499 500 return redirect(h.route_path('users'))
500 501
501 502 c.active = 'emails'
502 503 c.user_email_map = UserEmailMap.query() \
503 504 .filter(UserEmailMap.user == c.user).all()
504 505
505 506 defaults = c.user.get_dict()
506 507 return htmlfill.render(
507 508 render('admin/users/user_edit.mako'),
508 509 defaults=defaults,
509 510 encoding="UTF-8",
510 511 force_defaults=False)
511 512
512 513 @HasPermissionAllDecorator('hg.admin')
513 514 @auth.CSRFRequired()
514 515 def add_email(self, user_id):
515 """POST /user_emails:Add an existing item"""
516 # url('user_emails', user_id=ID, method='put')
517 516 user_id = safe_int(user_id)
518 517 c.user = User.get_or_404(user_id)
519 518
520 519 email = request.POST.get('new_email')
521 520 user_model = UserModel()
522
521 user_data = c.user.get_api_data()
523 522 try:
524 523 user_model.add_extra_email(user_id, email)
524 audit_logger.store_web(
525 'user.edit.email.add',
526 action_data={'email': email, 'user': user_data},
527 user=c.rhodecode_user)
525 528 Session().commit()
526 529 h.flash(_("Added new email address `%s` for user account") % email,
527 530 category='success')
528 531 except formencode.Invalid as error:
529 532 msg = error.error_dict['email']
530 533 h.flash(msg, category='error')
531 534 except Exception:
532 535 log.exception("Exception during email saving")
533 536 h.flash(_('An error occurred during email saving'),
534 537 category='error')
535 538 return redirect(url('edit_user_emails', user_id=user_id))
536 539
537 540 @HasPermissionAllDecorator('hg.admin')
538 541 @auth.CSRFRequired()
539 542 def delete_email(self, user_id):
540 """DELETE /user_emails_delete/user_id: Delete an existing item"""
541 # url('user_emails_delete', user_id=ID, method='delete')
542 543 user_id = safe_int(user_id)
543 544 c.user = User.get_or_404(user_id)
544 545 email_id = request.POST.get('del_email_id')
545 546 user_model = UserModel()
547
548 email = UserEmailMap.query().get(email_id).email
549 user_data = c.user.get_api_data()
546 550 user_model.delete_extra_email(user_id, email_id)
551 audit_logger.store_web(
552 'user.edit.email.delete',
553 action_data={'email': email, 'user': user_data},
554 user=c.rhodecode_user)
547 555 Session().commit()
548 556 h.flash(_("Removed email address from user account"), category='success')
549 557 return redirect(url('edit_user_emails', user_id=user_id))
550 558
551 559 @HasPermissionAllDecorator('hg.admin')
552 560 def edit_ips(self, user_id):
553 561 user_id = safe_int(user_id)
554 562 c.user = User.get_or_404(user_id)
555 563 if c.user.username == User.DEFAULT_USER:
556 564 h.flash(_("You can't edit this user"), category='warning')
557 565 return redirect(h.route_path('users'))
558 566
559 567 c.active = 'ips'
560 568 c.user_ip_map = UserIpMap.query() \
561 569 .filter(UserIpMap.user == c.user).all()
562 570
563 571 c.inherit_default_ips = c.user.inherit_default_permissions
564 572 c.default_user_ip_map = UserIpMap.query() \
565 573 .filter(UserIpMap.user == User.get_default_user()).all()
566 574
567 575 defaults = c.user.get_dict()
568 576 return htmlfill.render(
569 577 render('admin/users/user_edit.mako'),
570 578 defaults=defaults,
571 579 encoding="UTF-8",
572 580 force_defaults=False)
573 581
574 582 @HasPermissionAllDecorator('hg.admin')
575 583 @auth.CSRFRequired()
576 584 def add_ip(self, user_id):
577 """POST /user_ips:Add an existing item"""
578 # url('user_ips', user_id=ID, method='put')
579
580 585 user_id = safe_int(user_id)
581 586 c.user = User.get_or_404(user_id)
582 587 user_model = UserModel()
583 588 try:
584 589 ip_list = user_model.parse_ip_range(request.POST.get('new_ip'))
585 590 except Exception as e:
586 591 ip_list = []
587 592 log.exception("Exception during ip saving")
588 593 h.flash(_('An error occurred during ip saving:%s' % (e,)),
589 594 category='error')
590 595
591 596 desc = request.POST.get('description')
592 597 added = []
598 user_data = c.user.get_api_data()
593 599 for ip in ip_list:
594 600 try:
595 601 user_model.add_extra_ip(user_id, ip, desc)
602 audit_logger.store_web(
603 'user.edit.ip.add',
604 action_data={'ip': ip, 'user': user_data},
605 user=c.rhodecode_user)
596 606 Session().commit()
597 607 added.append(ip)
598 608 except formencode.Invalid as error:
599 609 msg = error.error_dict['ip']
600 610 h.flash(msg, category='error')
601 611 except Exception:
602 612 log.exception("Exception during ip saving")
603 613 h.flash(_('An error occurred during ip saving'),
604 614 category='error')
605 615 if added:
606 616 h.flash(
607 617 _("Added ips %s to user whitelist") % (', '.join(ip_list), ),
608 618 category='success')
609 619 if 'default_user' in request.POST:
610 620 return redirect(url('admin_permissions_ips'))
611 621 return redirect(url('edit_user_ips', user_id=user_id))
612 622
613 623 @HasPermissionAllDecorator('hg.admin')
614 624 @auth.CSRFRequired()
615 625 def delete_ip(self, user_id):
616 """DELETE /user_ips_delete/user_id: Delete an existing item"""
617 # url('user_ips_delete', user_id=ID, method='delete')
618 626 user_id = safe_int(user_id)
619 627 c.user = User.get_or_404(user_id)
620 628
621 629 ip_id = request.POST.get('del_ip_id')
622 630 user_model = UserModel()
631 ip = UserIpMap.query().get(ip_id).ip_addr
632 user_data = c.user.get_api_data()
623 633 user_model.delete_extra_ip(user_id, ip_id)
634 audit_logger.store_web(
635 'user.edit.ip.delete',
636 action_data={'ip': ip, 'user': user_data},
637 user=c.rhodecode_user)
624 638 Session().commit()
625 639 h.flash(_("Removed ip address from user whitelist"), category='success')
626 640
627 641 if 'default_user' in request.POST:
628 642 return redirect(url('admin_permissions_ips'))
629 643 return redirect(url('edit_user_ips', user_id=user_id))
@@ -1,219 +1,231 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2017-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 import logging
22 22 import datetime
23 23
24 24 from rhodecode.model import meta
25 25 from rhodecode.model.db import User, UserLog, Repository
26 26
27 27
28 28 log = logging.getLogger(__name__)
29 29
30
30 # action as key, and expected action_data as value
31 31 ACTIONS = {
32 32 'user.login.success': {},
33 33 'user.login.failure': {},
34 34 'user.logout': {},
35 35 'user.password.reset_request': {},
36 36 'user.push': {},
37 37 'user.pull': {},
38 38
39 39 'repo.create': {},
40 40 'repo.edit': {},
41 'user.create': {'data': {}},
42 'user.delete': {'old_data': {}},
43 'user.edit': {'old_data': {}},
44 'user.edit.permissions': {},
45 'user.edit.ip.add': {},
46 'user.edit.ip.delete': {},
47 'user.edit.token.add': {},
48 'user.edit.token.delete': {},
49 'user.edit.email.add': {},
50 'user.edit.email.delete': {},
51 'user.edit.password_reset.enabled': {},
52 'user.edit.password_reset.disabled': {},
53
41 54 'repo.edit.permissions': {},
42 55 'repo.delete': {},
43 56 'repo.commit.strip': {},
44 57 'repo.archive.download': {},
45 58
46 59 'repo_group.create': {},
47 60 'repo_group.edit': {},
48 61 'repo_group.edit.permissions': {},
49 62 'repo_group.delete': {},
50 63 }
51 64
52 65 SOURCE_WEB = 'source_web'
53 66 SOURCE_API = 'source_api'
54 67
55 68
56 69 class UserWrap(object):
57 70 """
58 71 Fake object used to imitate AuthUser
59 72 """
60 73
61 74 def __init__(self, user_id=None, username=None, ip_addr=None):
62 75 self.user_id = user_id
63 76 self.username = username
64 77 self.ip_addr = ip_addr
65 78
66 79
67 80 class RepoWrap(object):
68 81 """
69 82 Fake object used to imitate RepoObject that audit logger requires
70 83 """
71 84
72 85 def __init__(self, repo_id=None, repo_name=None):
73 86 self.repo_id = repo_id
74 87 self.repo_name = repo_name
75 88
76 89
77 90 def _store_log(action_name, action_data, user_id, username, user_data,
78 91 ip_address, repository_id, repository_name):
79 92 user_log = UserLog()
80 93 user_log.version = UserLog.VERSION_2
81 94
82 95 user_log.action = action_name
83 96 user_log.action_data = action_data
84 97
85 98 user_log.user_ip = ip_address
86 99
87 100 user_log.user_id = user_id
88 101 user_log.username = username
89 102 user_log.user_data = user_data
90 103
91 104 user_log.repository_id = repository_id
92 105 user_log.repository_name = repository_name
93 106
94 107 user_log.action_date = datetime.datetime.now()
95 108
96 109 log.info('AUDIT: Logging action: `%s` by user:id:%s[%s] ip:%s',
97 110 action_name, user_id, username, ip_address)
98 111
99 112 return user_log
100 113
101 114
102 115 def store_web(*args, **kwargs):
103 116 if 'action_data' not in kwargs:
104 117 kwargs['action_data'] = {}
105 118 kwargs['action_data'].update({
106 119 'source': SOURCE_WEB
107 120 })
108 121 return store(*args, **kwargs)
109 122
110 123
111 124 def store_api(*args, **kwargs):
112 125 if 'action_data' not in kwargs:
113 126 kwargs['action_data'] = {}
114 127 kwargs['action_data'].update({
115 128 'source': SOURCE_API
116 129 })
117 130 return store(*args, **kwargs)
118 131
119 132
120 def store(
121 action, user, action_data=None, user_data=None, ip_addr=None,
122 repo=None, sa_session=None, commit=False):
133 def store(action, user, action_data=None, user_data=None, ip_addr=None,
134 repo=None, sa_session=None, commit=False):
123 135 """
124 136 Audit logger for various actions made by users, typically this
125 137 results in a call such::
126 138
127 139 from rhodecode.lib import audit_logger
128 140
129 141 audit_logger.store(
130 142 action='repo.edit', user=self._rhodecode_user)
131 143 audit_logger.store(
132 144 action='repo.delete', action_data={'repo_data': repo_data},
133 145 user=audit_logger.UserWrap(username='itried-login', ip_addr='8.8.8.8'))
134 146
135 147 # repo action
136 148 audit_logger.store(
137 149 action='repo.delete',
138 150 user=audit_logger.UserWrap(username='itried-login', ip_addr='8.8.8.8'),
139 151 repo=audit_logger.RepoWrap(repo_name='some-repo'))
140 152
141 153 # repo action, when we know and have the repository object already
142 154 audit_logger.store(
143 155 action='repo.delete',
144 156 action_data={'source': audit_logger.SOURCE_WEB, },
145 157 user=self._rhodecode_user,
146 158 repo=repo_object)
147 159
148 160 # alternative wrapper to the above
149 161 audit_logger.store_web(
150 162 action='repo.delete',
151 163 action_data={},
152 164 user=self._rhodecode_user,
153 165 repo=repo_object)
154 166
155 167 # without an user ?
156 168 audit_logger.store(
157 169 action='user.login.failure',
158 170 user=audit_logger.UserWrap(
159 171 username=self.request.params.get('username'),
160 172 ip_addr=self.request.remote_addr))
161 173
162 174 """
163 175 from rhodecode.lib.utils2 import safe_unicode
164 176 from rhodecode.lib.auth import AuthUser
165 177
166 178 if action not in ACTIONS:
167 179 raise ValueError('Action `{}` not in valid actions'.format(action))
168 180
169 181 if not sa_session:
170 182 sa_session = meta.Session()
171 183
172 184 try:
173 185 username = getattr(user, 'username', None)
174 186 if not username:
175 187 pass
176 188
177 189 user_id = getattr(user, 'user_id', None)
178 190 if not user_id:
179 191 # maybe we have username ? Try to figure user_id from username
180 192 if username:
181 193 user_id = getattr(
182 194 User.get_by_username(username), 'user_id', None)
183 195
184 196 ip_addr = ip_addr or getattr(user, 'ip_addr', None)
185 197 if not ip_addr:
186 198 pass
187 199
188 200 if not user_data:
189 201 # try to get this from the auth user
190 202 if isinstance(user, AuthUser):
191 203 user_data = {
192 204 'username': user.username,
193 205 'email': user.email,
194 206 }
195 207
196 208 repository_name = getattr(repo, 'repo_name', None)
197 209 repository_id = getattr(repo, 'repo_id', None)
198 210 if not repository_id:
199 211 # maybe we have repo_name ? Try to figure repo_id from repo_name
200 212 if repository_name:
201 213 repository_id = getattr(
202 214 Repository.get_by_repo_name(repository_name), 'repo_id', None)
203 215
204 216 user_log = _store_log(
205 217 action_name=safe_unicode(action),
206 218 action_data=action_data or {},
207 219 user_id=user_id,
208 220 username=username,
209 221 user_data=user_data or {},
210 222 ip_address=safe_unicode(ip_addr),
211 223 repository_id=repository_id,
212 224 repository_name=repository_name
213 225 )
214 226 sa_session.add(user_log)
215 227 if commit:
216 228 sa_session.commit()
217 229
218 230 except Exception:
219 231 log.exception('AUDIT: failed to store audit log')
@@ -1,902 +1,902 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': h.escape(user.name),
74 74 'last_name': h.escape(user.lastname),
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 259 # empty password
260 260 if not new_user.password:
261 261 return False
262 262
263 263 # password check is only needed for RhodeCode internal auth calls
264 264 # in case it's a plugin we don't care
265 265 if not plugin:
266 266
267 267 # first check if we gave crypted password back, and if it
268 268 # matches it's not password change
269 269 if new_user.password == password:
270 270 return False
271 271
272 272 password_match = check_password(password, new_user.password)
273 273 if not password_match:
274 274 return True
275 275
276 276 return False
277 277
278 278 # read settings on default personal repo group creation
279 279 if create_repo_group is None:
280 280 default_create_repo_group = RepoGroupModel()\
281 281 .get_default_create_personal_repo_group()
282 282 create_repo_group = default_create_repo_group
283 283
284 284 user_data = {
285 285 'username': username,
286 286 'password': password,
287 287 'email': email,
288 288 'firstname': firstname,
289 289 'lastname': lastname,
290 290 'active': active,
291 291 'admin': admin
292 292 }
293 293
294 294 if updating_user_id:
295 295 log.debug('Checking for existing account in RhodeCode '
296 296 'database with user_id `%s` ' % (updating_user_id,))
297 297 user = User.get(updating_user_id)
298 298 else:
299 299 log.debug('Checking for existing account in RhodeCode '
300 300 'database with username `%s` ' % (username,))
301 301 user = User.get_by_username(username, case_insensitive=True)
302 302
303 303 if user is None:
304 304 # we check internal flag if this method is actually allowed to
305 305 # create new user
306 306 if not allow_to_create_user:
307 307 msg = ('Method wants to create new user, but it is not '
308 308 'allowed to do so')
309 309 log.warning(msg)
310 310 raise NotAllowedToCreateUserError(msg)
311 311
312 312 log.debug('Creating new user %s', username)
313 313
314 314 # only if we create user that is active
315 315 new_active_user = active
316 316 if new_active_user and strict_creation_check:
317 317 # raises UserCreationError if it's not allowed for any reason to
318 318 # create new active user, this also executes pre-create hooks
319 319 check_allowed_create_user(user_data, cur_user, strict_check=True)
320 320 events.trigger(events.UserPreCreate(user_data))
321 321 new_user = User()
322 322 edit = False
323 323 else:
324 324 log.debug('updating user %s', username)
325 325 events.trigger(events.UserPreUpdate(user, user_data))
326 326 new_user = user
327 327 edit = True
328 328
329 329 # we're not allowed to edit default user
330 330 if user.username == User.DEFAULT_USER:
331 331 raise DefaultUserException(
332 332 _("You can't edit this user (`%(username)s`) since it's "
333 333 "crucial for entire application") % {'username': user.username})
334 334
335 335 # inject special attribute that will tell us if User is new or old
336 336 new_user.is_new_user = not edit
337 337 # for users that didn's specify auth type, we use RhodeCode built in
338 338 from rhodecode.authentication.plugins import auth_rhodecode
339 339 extern_name = extern_name or auth_rhodecode.RhodeCodeAuthPlugin.name
340 340 extern_type = extern_type or auth_rhodecode.RhodeCodeAuthPlugin.name
341 341
342 342 try:
343 343 new_user.username = username
344 344 new_user.admin = admin
345 345 new_user.email = email
346 346 new_user.active = active
347 347 new_user.extern_name = safe_unicode(extern_name)
348 348 new_user.extern_type = safe_unicode(extern_type)
349 349 new_user.name = firstname
350 350 new_user.lastname = lastname
351 351
352 352 # set password only if creating an user or password is changed
353 353 if not edit or _password_change(new_user, password):
354 354 reason = 'new password' if edit else 'new user'
355 355 log.debug('Updating password reason=>%s', reason)
356 356 new_user.password = get_crypt_password(password) if password else None
357 357
358 358 if force_password_change:
359 359 new_user.update_userdata(force_password_change=True)
360 360 if language:
361 361 new_user.update_userdata(language=language)
362 362 new_user.update_userdata(notification_status=True)
363 363
364 364 self.sa.add(new_user)
365 365
366 366 if not edit and create_repo_group:
367 367 RepoGroupModel().create_personal_repo_group(
368 368 new_user, commit_early=False)
369 369
370 370 if not edit:
371 371 # add the RSS token
372 372 AuthTokenModel().create(username,
373 373 description='Generated feed token',
374 374 role=AuthTokenModel.cls.ROLE_FEED)
375 375 log_create_user(created_by=cur_user, **new_user.get_dict())
376 376 events.trigger(events.UserPostCreate(user_data))
377 377 return new_user
378 378 except (DatabaseError,):
379 379 log.error(traceback.format_exc())
380 380 raise
381 381
382 382 def create_registration(self, form_data):
383 383 from rhodecode.model.notification import NotificationModel
384 384 from rhodecode.model.notification import EmailNotificationModel
385 385
386 386 try:
387 387 form_data['admin'] = False
388 388 form_data['extern_name'] = 'rhodecode'
389 389 form_data['extern_type'] = 'rhodecode'
390 390 new_user = self.create(form_data)
391 391
392 392 self.sa.add(new_user)
393 393 self.sa.flush()
394 394
395 395 user_data = new_user.get_dict()
396 396 kwargs = {
397 397 # use SQLALCHEMY safe dump of user data
398 398 'user': AttributeDict(user_data),
399 399 'date': datetime.datetime.now()
400 400 }
401 401 notification_type = EmailNotificationModel.TYPE_REGISTRATION
402 402 # pre-generate the subject for notification itself
403 403 (subject,
404 404 _h, _e, # we don't care about those
405 405 body_plaintext) = EmailNotificationModel().render_email(
406 406 notification_type, **kwargs)
407 407
408 408 # create notification objects, and emails
409 409 NotificationModel().create(
410 410 created_by=new_user,
411 411 notification_subject=subject,
412 412 notification_body=body_plaintext,
413 413 notification_type=notification_type,
414 414 recipients=None, # all admins
415 415 email_kwargs=kwargs,
416 416 )
417 417
418 418 return new_user
419 419 except Exception:
420 420 log.error(traceback.format_exc())
421 421 raise
422 422
423 423 def _handle_user_repos(self, username, repositories, handle_mode=None):
424 424 _superadmin = self.cls.get_first_super_admin()
425 425 left_overs = True
426 426
427 427 from rhodecode.model.repo import RepoModel
428 428
429 429 if handle_mode == 'detach':
430 430 for obj in repositories:
431 431 obj.user = _superadmin
432 432 # set description we know why we super admin now owns
433 433 # additional repositories that were orphaned !
434 434 obj.description += ' \n::detached repository from deleted user: %s' % (username,)
435 435 self.sa.add(obj)
436 436 left_overs = False
437 437 elif handle_mode == 'delete':
438 438 for obj in repositories:
439 439 RepoModel().delete(obj, forks='detach')
440 440 left_overs = False
441 441
442 442 # if nothing is done we have left overs left
443 443 return left_overs
444 444
445 445 def _handle_user_repo_groups(self, username, repository_groups,
446 446 handle_mode=None):
447 447 _superadmin = self.cls.get_first_super_admin()
448 448 left_overs = True
449 449
450 450 from rhodecode.model.repo_group import RepoGroupModel
451 451
452 452 if handle_mode == 'detach':
453 453 for r in repository_groups:
454 454 r.user = _superadmin
455 455 # set description we know why we super admin now owns
456 456 # additional repositories that were orphaned !
457 457 r.group_description += ' \n::detached repository group from deleted user: %s' % (username,)
458 458 self.sa.add(r)
459 459 left_overs = False
460 460 elif handle_mode == 'delete':
461 461 for r in repository_groups:
462 462 RepoGroupModel().delete(r)
463 463 left_overs = False
464 464
465 465 # if nothing is done we have left overs left
466 466 return left_overs
467 467
468 468 def _handle_user_user_groups(self, username, user_groups, handle_mode=None):
469 469 _superadmin = self.cls.get_first_super_admin()
470 470 left_overs = True
471 471
472 472 from rhodecode.model.user_group import UserGroupModel
473 473
474 474 if handle_mode == 'detach':
475 475 for r in user_groups:
476 476 for user_user_group_to_perm in r.user_user_group_to_perm:
477 477 if user_user_group_to_perm.user.username == username:
478 478 user_user_group_to_perm.user = _superadmin
479 479 r.user = _superadmin
480 480 # set description we know why we super admin now owns
481 481 # additional repositories that were orphaned !
482 482 r.user_group_description += ' \n::detached user group from deleted user: %s' % (username,)
483 483 self.sa.add(r)
484 484 left_overs = False
485 485 elif handle_mode == 'delete':
486 486 for r in user_groups:
487 487 UserGroupModel().delete(r)
488 488 left_overs = False
489 489
490 490 # if nothing is done we have left overs left
491 491 return left_overs
492 492
493 493 def delete(self, user, cur_user=None, handle_repos=None,
494 494 handle_repo_groups=None, handle_user_groups=None):
495 495 if not cur_user:
496 496 cur_user = getattr(get_current_rhodecode_user(), 'username', None)
497 497 user = self._get_user(user)
498 498
499 499 try:
500 500 if user.username == User.DEFAULT_USER:
501 501 raise DefaultUserException(
502 502 _(u"You can't remove this user since it's"
503 503 u" crucial for entire application"))
504 504
505 505 left_overs = self._handle_user_repos(
506 506 user.username, user.repositories, handle_repos)
507 507 if left_overs and user.repositories:
508 508 repos = [x.repo_name for x in user.repositories]
509 509 raise UserOwnsReposException(
510 510 _(u'user "%s" still owns %s repositories and cannot be '
511 511 u'removed. Switch owners or remove those repositories:%s')
512 512 % (user.username, len(repos), ', '.join(repos)))
513 513
514 514 left_overs = self._handle_user_repo_groups(
515 515 user.username, user.repository_groups, handle_repo_groups)
516 516 if left_overs and user.repository_groups:
517 517 repo_groups = [x.group_name for x in user.repository_groups]
518 518 raise UserOwnsRepoGroupsException(
519 519 _(u'user "%s" still owns %s repository groups and cannot be '
520 520 u'removed. Switch owners or remove those repository groups:%s')
521 521 % (user.username, len(repo_groups), ', '.join(repo_groups)))
522 522
523 523 left_overs = self._handle_user_user_groups(
524 524 user.username, user.user_groups, handle_user_groups)
525 525 if left_overs and user.user_groups:
526 526 user_groups = [x.users_group_name for x in user.user_groups]
527 527 raise UserOwnsUserGroupsException(
528 528 _(u'user "%s" still owns %s user groups and cannot be '
529 529 u'removed. Switch owners or remove those user groups:%s')
530 530 % (user.username, len(user_groups), ', '.join(user_groups)))
531 531
532 532 # we might change the user data with detach/delete, make sure
533 533 # the object is marked as expired before actually deleting !
534 534 self.sa.expire(user)
535 535 self.sa.delete(user)
536 536 from rhodecode.lib.hooks_base import log_delete_user
537 537 log_delete_user(deleted_by=cur_user, **user.get_dict())
538 538 except Exception:
539 539 log.error(traceback.format_exc())
540 540 raise
541 541
542 542 def reset_password_link(self, data, pwd_reset_url):
543 543 from rhodecode.lib.celerylib import tasks, run_task
544 544 from rhodecode.model.notification import EmailNotificationModel
545 545 user_email = data['email']
546 546 try:
547 547 user = User.get_by_email(user_email)
548 548 if user:
549 549 log.debug('password reset user found %s', user)
550 550
551 551 email_kwargs = {
552 552 'password_reset_url': pwd_reset_url,
553 553 'user': user,
554 554 'email': user_email,
555 555 'date': datetime.datetime.now()
556 556 }
557 557
558 558 (subject, headers, email_body,
559 559 email_body_plaintext) = EmailNotificationModel().render_email(
560 560 EmailNotificationModel.TYPE_PASSWORD_RESET, **email_kwargs)
561 561
562 562 recipients = [user_email]
563 563
564 564 action_logger_generic(
565 565 'sending password reset email to user: {}'.format(
566 566 user), namespace='security.password_reset')
567 567
568 568 run_task(tasks.send_email, recipients, subject,
569 569 email_body_plaintext, email_body)
570 570
571 571 else:
572 572 log.debug("password reset email %s not found", user_email)
573 573 except Exception:
574 574 log.error(traceback.format_exc())
575 575 return False
576 576
577 577 return True
578 578
579 579 def reset_password(self, data):
580 580 from rhodecode.lib.celerylib import tasks, run_task
581 581 from rhodecode.model.notification import EmailNotificationModel
582 582 from rhodecode.lib import auth
583 583 user_email = data['email']
584 584 pre_db = True
585 585 try:
586 586 user = User.get_by_email(user_email)
587 587 new_passwd = auth.PasswordGenerator().gen_password(
588 588 12, auth.PasswordGenerator.ALPHABETS_BIG_SMALL)
589 589 if user:
590 590 user.password = auth.get_crypt_password(new_passwd)
591 591 # also force this user to reset his password !
592 592 user.update_userdata(force_password_change=True)
593 593
594 594 Session().add(user)
595 595
596 596 # now delete the token in question
597 597 UserApiKeys = AuthTokenModel.cls
598 598 UserApiKeys().query().filter(
599 599 UserApiKeys.api_key == data['token']).delete()
600 600
601 601 Session().commit()
602 602 log.info('successfully reset password for `%s`', user_email)
603 603
604 604 if new_passwd is None:
605 605 raise Exception('unable to generate new password')
606 606
607 607 pre_db = False
608 608
609 609 email_kwargs = {
610 610 'new_password': new_passwd,
611 611 'user': user,
612 612 'email': user_email,
613 613 'date': datetime.datetime.now()
614 614 }
615 615
616 616 (subject, headers, email_body,
617 617 email_body_plaintext) = EmailNotificationModel().render_email(
618 618 EmailNotificationModel.TYPE_PASSWORD_RESET_CONFIRMATION,
619 619 **email_kwargs)
620 620
621 621 recipients = [user_email]
622 622
623 623 action_logger_generic(
624 624 'sent new password to user: {} with email: {}'.format(
625 625 user, user_email), namespace='security.password_reset')
626 626
627 627 run_task(tasks.send_email, recipients, subject,
628 628 email_body_plaintext, email_body)
629 629
630 630 except Exception:
631 631 log.error('Failed to update user password')
632 632 log.error(traceback.format_exc())
633 633 if pre_db:
634 634 # we rollback only if local db stuff fails. If it goes into
635 635 # run_task, we're pass rollback state this wouldn't work then
636 636 Session().rollback()
637 637
638 638 return True
639 639
640 640 def fill_data(self, auth_user, user_id=None, api_key=None, username=None):
641 641 """
642 642 Fetches auth_user by user_id,or api_key if present.
643 643 Fills auth_user attributes with those taken from database.
644 644 Additionally set's is_authenitated if lookup fails
645 645 present in database
646 646
647 647 :param auth_user: instance of user to set attributes
648 648 :param user_id: user id to fetch by
649 649 :param api_key: api key to fetch by
650 650 :param username: username to fetch by
651 651 """
652 652 if user_id is None and api_key is None and username is None:
653 653 raise Exception('You need to pass user_id, api_key or username')
654 654
655 655 log.debug(
656 656 'doing fill data based on: user_id:%s api_key:%s username:%s',
657 657 user_id, api_key, username)
658 658 try:
659 659 dbuser = None
660 660 if user_id:
661 661 dbuser = self.get(user_id)
662 662 elif api_key:
663 663 dbuser = self.get_by_auth_token(api_key)
664 664 elif username:
665 665 dbuser = self.get_by_username(username)
666 666
667 667 if not dbuser:
668 668 log.warning(
669 669 'Unable to lookup user by id:%s api_key:%s username:%s',
670 670 user_id, api_key, username)
671 671 return False
672 672 if not dbuser.active:
673 673 log.debug('User `%s:%s` is inactive, skipping fill data',
674 674 username, user_id)
675 675 return False
676 676
677 677 log.debug('filling user:%s data', dbuser)
678 678
679 679 # TODO: johbo: Think about this and find a clean solution
680 680 user_data = dbuser.get_dict()
681 681 user_data.update(dbuser.get_api_data(include_secrets=True))
682 682
683 683 for k, v in user_data.iteritems():
684 684 # properties of auth user we dont update
685 685 if k not in ['auth_tokens', 'permissions']:
686 686 setattr(auth_user, k, v)
687 687
688 688 # few extras
689 689 setattr(auth_user, 'feed_token', dbuser.feed_token)
690 690 except Exception:
691 691 log.error(traceback.format_exc())
692 692 auth_user.is_authenticated = False
693 693 return False
694 694
695 695 return True
696 696
697 697 def has_perm(self, user, perm):
698 698 perm = self._get_perm(perm)
699 699 user = self._get_user(user)
700 700
701 701 return UserToPerm.query().filter(UserToPerm.user == user)\
702 702 .filter(UserToPerm.permission == perm).scalar() is not None
703 703
704 704 def grant_perm(self, user, perm):
705 705 """
706 706 Grant user global permissions
707 707
708 708 :param user:
709 709 :param perm:
710 710 """
711 711 user = self._get_user(user)
712 712 perm = self._get_perm(perm)
713 713 # if this permission is already granted skip it
714 714 _perm = UserToPerm.query()\
715 715 .filter(UserToPerm.user == user)\
716 716 .filter(UserToPerm.permission == perm)\
717 717 .scalar()
718 718 if _perm:
719 719 return
720 720 new = UserToPerm()
721 721 new.user = user
722 722 new.permission = perm
723 723 self.sa.add(new)
724 724 return new
725 725
726 726 def revoke_perm(self, user, perm):
727 727 """
728 728 Revoke users global permissions
729 729
730 730 :param user:
731 731 :param perm:
732 732 """
733 733 user = self._get_user(user)
734 734 perm = self._get_perm(perm)
735 735
736 736 obj = UserToPerm.query()\
737 737 .filter(UserToPerm.user == user)\
738 738 .filter(UserToPerm.permission == perm)\
739 739 .scalar()
740 740 if obj:
741 741 self.sa.delete(obj)
742 742
743 743 def add_extra_email(self, user, email):
744 744 """
745 745 Adds email address to UserEmailMap
746 746
747 747 :param user:
748 748 :param email:
749 749 """
750 750 from rhodecode.model import forms
751 751 form = forms.UserExtraEmailForm()()
752 752 data = form.to_python({'email': email})
753 753 user = self._get_user(user)
754 754
755 755 obj = UserEmailMap()
756 756 obj.user = user
757 757 obj.email = data['email']
758 758 self.sa.add(obj)
759 759 return obj
760 760
761 761 def delete_extra_email(self, user, email_id):
762 762 """
763 763 Removes email address from UserEmailMap
764 764
765 765 :param user:
766 766 :param email_id:
767 767 """
768 768 user = self._get_user(user)
769 769 obj = UserEmailMap.query().get(email_id)
770 if obj:
770 if obj and obj.user_id == user.user_id:
771 771 self.sa.delete(obj)
772 772
773 773 def parse_ip_range(self, ip_range):
774 774 ip_list = []
775 775 def make_unique(value):
776 776 seen = []
777 777 return [c for c in value if not (c in seen or seen.append(c))]
778 778
779 779 # firsts split by commas
780 780 for ip_range in ip_range.split(','):
781 781 if not ip_range:
782 782 continue
783 783 ip_range = ip_range.strip()
784 784 if '-' in ip_range:
785 785 start_ip, end_ip = ip_range.split('-', 1)
786 786 start_ip = ipaddress.ip_address(start_ip.strip())
787 787 end_ip = ipaddress.ip_address(end_ip.strip())
788 788 parsed_ip_range = []
789 789
790 790 for index in xrange(int(start_ip), int(end_ip) + 1):
791 791 new_ip = ipaddress.ip_address(index)
792 792 parsed_ip_range.append(str(new_ip))
793 793 ip_list.extend(parsed_ip_range)
794 794 else:
795 795 ip_list.append(ip_range)
796 796
797 797 return make_unique(ip_list)
798 798
799 799 def add_extra_ip(self, user, ip, description=None):
800 800 """
801 801 Adds ip address to UserIpMap
802 802
803 803 :param user:
804 804 :param ip:
805 805 """
806 806 from rhodecode.model import forms
807 807 form = forms.UserExtraIpForm()()
808 808 data = form.to_python({'ip': ip})
809 809 user = self._get_user(user)
810 810
811 811 obj = UserIpMap()
812 812 obj.user = user
813 813 obj.ip_addr = data['ip']
814 814 obj.description = description
815 815 self.sa.add(obj)
816 816 return obj
817 817
818 818 def delete_extra_ip(self, user, ip_id):
819 819 """
820 820 Removes ip address from UserIpMap
821 821
822 822 :param user:
823 823 :param ip_id:
824 824 """
825 825 user = self._get_user(user)
826 826 obj = UserIpMap.query().get(ip_id)
827 if obj:
827 if obj and obj.user_id == user.user_id:
828 828 self.sa.delete(obj)
829 829
830 830 def get_accounts_in_creation_order(self, current_user=None):
831 831 """
832 832 Get accounts in order of creation for deactivation for license limits
833 833
834 834 pick currently logged in user, and append to the list in position 0
835 835 pick all super-admins in order of creation date and add it to the list
836 836 pick all other accounts in order of creation and add it to the list.
837 837
838 838 Based on that list, the last accounts can be disabled as they are
839 839 created at the end and don't include any of the super admins as well
840 840 as the current user.
841 841
842 842 :param current_user: optionally current user running this operation
843 843 """
844 844
845 845 if not current_user:
846 846 current_user = get_current_rhodecode_user()
847 847 active_super_admins = [
848 848 x.user_id for x in User.query()
849 849 .filter(User.user_id != current_user.user_id)
850 850 .filter(User.active == true())
851 851 .filter(User.admin == true())
852 852 .order_by(User.created_on.asc())]
853 853
854 854 active_regular_users = [
855 855 x.user_id for x in User.query()
856 856 .filter(User.user_id != current_user.user_id)
857 857 .filter(User.active == true())
858 858 .filter(User.admin == false())
859 859 .order_by(User.created_on.asc())]
860 860
861 861 list_of_accounts = [current_user.user_id]
862 862 list_of_accounts += active_super_admins
863 863 list_of_accounts += active_regular_users
864 864
865 865 return list_of_accounts
866 866
867 867 def deactivate_last_users(self, expected_users):
868 868 """
869 869 Deactivate accounts that are over the license limits.
870 870 Algorithm of which accounts to disabled is based on the formula:
871 871
872 872 Get current user, then super admins in creation order, then regular
873 873 active users in creation order.
874 874
875 875 Using that list we mark all accounts from the end of it as inactive.
876 876 This way we block only latest created accounts.
877 877
878 878 :param expected_users: list of users in special order, we deactivate
879 879 the end N ammoun of users from that list
880 880 """
881 881
882 882 list_of_accounts = self.get_accounts_in_creation_order()
883 883
884 884 for acc_id in list_of_accounts[expected_users + 1:]:
885 885 user = User.get(acc_id)
886 886 log.info('Deactivating account %s for license unlock', user)
887 887 user.active = False
888 888 Session().add(user)
889 889 Session().commit()
890 890
891 891 return
892 892
893 893 def get_user_log(self, user, filter_term):
894 894 user_log = UserLog.query()\
895 895 .filter(or_(UserLog.user_id == user.user_id,
896 896 UserLog.username == user.username))\
897 897 .options(joinedload(UserLog.user))\
898 898 .options(joinedload(UserLog.repository))\
899 899 .order_by(UserLog.action_date.desc())
900 900
901 901 user_log = user_log_filter(user_log, filter_term)
902 902 return user_log
General Comments 0
You need to be logged in to leave comments. Login now