##// END OF EJS Templates
security: fix self-xss inside the email add functionality.
ergo -
r1828:20cd932d default
parent child Browse files
Show More
@@ -1,510 +1,509 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2016-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 import formencode
24 24
25 25 from pyramid.httpexceptions import HTTPFound
26 26 from pyramid.view import view_config
27 27 from sqlalchemy.sql.functions import coalesce
28 28
29 29 from rhodecode.apps._base import BaseAppView, DataGridAppView
30 30
31 31 from rhodecode.lib import audit_logger
32 32 from rhodecode.lib.ext_json import json
33 33 from rhodecode.lib.auth import (
34 34 LoginRequired, HasPermissionAllDecorator, CSRFRequired)
35 35 from rhodecode.lib import helpers as h
36 36 from rhodecode.lib.utils import PartialRenderer
37 37 from rhodecode.lib.utils2 import safe_int, safe_unicode
38 38 from rhodecode.model.auth_token import AuthTokenModel
39 39 from rhodecode.model.user import UserModel
40 40 from rhodecode.model.user_group import UserGroupModel
41 41 from rhodecode.model.db import User, or_, UserIpMap, UserEmailMap, UserApiKeys
42 42 from rhodecode.model.meta import Session
43 43
44 44 log = logging.getLogger(__name__)
45 45
46 46
47 47 class AdminUsersView(BaseAppView, DataGridAppView):
48 48 ALLOW_SCOPED_TOKENS = False
49 49 """
50 50 This view has alternative version inside EE, if modified please take a look
51 51 in there as well.
52 52 """
53 53
54 54 def load_default_context(self):
55 55 c = self._get_local_tmpl_context()
56 56 c.allow_scoped_tokens = self.ALLOW_SCOPED_TOKENS
57 57 self._register_global_c(c)
58 58 return c
59 59
60 60 def _redirect_for_default_user(self, username):
61 61 _ = self.request.translate
62 62 if username == User.DEFAULT_USER:
63 63 h.flash(_("You can't edit this user"), category='warning')
64 64 # TODO(marcink): redirect to 'users' admin panel once this
65 65 # is a pyramid view
66 66 raise HTTPFound('/')
67 67
68 68 @HasPermissionAllDecorator('hg.admin')
69 69 @view_config(
70 70 route_name='users', request_method='GET',
71 71 renderer='rhodecode:templates/admin/users/users.mako')
72 72 def users_list(self):
73 73 c = self.load_default_context()
74 74 return self._get_template_context(c)
75 75
76 76 @HasPermissionAllDecorator('hg.admin')
77 77 @view_config(
78 78 # renderer defined below
79 79 route_name='users_data', request_method='GET',
80 80 renderer='json_ext', xhr=True)
81 81 def users_list_data(self):
82 82 draw, start, limit = self._extract_chunk(self.request)
83 83 search_q, order_by, order_dir = self._extract_ordering(self.request)
84 84
85 85 _render = PartialRenderer('data_table/_dt_elements.mako')
86 86
87 87 def user_actions(user_id, username):
88 88 return _render("user_actions", user_id, username)
89 89
90 90 users_data_total_count = User.query()\
91 91 .filter(User.username != User.DEFAULT_USER) \
92 92 .count()
93 93
94 94 # json generate
95 95 base_q = User.query().filter(User.username != User.DEFAULT_USER)
96 96
97 97 if search_q:
98 98 like_expression = u'%{}%'.format(safe_unicode(search_q))
99 99 base_q = base_q.filter(or_(
100 100 User.username.ilike(like_expression),
101 101 User._email.ilike(like_expression),
102 102 User.name.ilike(like_expression),
103 103 User.lastname.ilike(like_expression),
104 104 ))
105 105
106 106 users_data_total_filtered_count = base_q.count()
107 107
108 108 sort_col = getattr(User, order_by, None)
109 109 if sort_col:
110 110 if order_dir == 'asc':
111 111 # handle null values properly to order by NULL last
112 112 if order_by in ['last_activity']:
113 113 sort_col = coalesce(sort_col, datetime.date.max)
114 114 sort_col = sort_col.asc()
115 115 else:
116 116 # handle null values properly to order by NULL last
117 117 if order_by in ['last_activity']:
118 118 sort_col = coalesce(sort_col, datetime.date.min)
119 119 sort_col = sort_col.desc()
120 120
121 121 base_q = base_q.order_by(sort_col)
122 122 base_q = base_q.offset(start).limit(limit)
123 123
124 124 users_list = base_q.all()
125 125
126 126 users_data = []
127 127 for user in users_list:
128 128 users_data.append({
129 129 "username": h.gravatar_with_user(user.username),
130 130 "email": user.email,
131 131 "first_name": user.first_name,
132 132 "last_name": user.last_name,
133 133 "last_login": h.format_date(user.last_login),
134 134 "last_activity": h.format_date(user.last_activity),
135 135 "active": h.bool2icon(user.active),
136 136 "active_raw": user.active,
137 137 "admin": h.bool2icon(user.admin),
138 138 "extern_type": user.extern_type,
139 139 "extern_name": user.extern_name,
140 140 "action": user_actions(user.user_id, user.username),
141 141 })
142 142
143 143 data = ({
144 144 'draw': draw,
145 145 'data': users_data,
146 146 'recordsTotal': users_data_total_count,
147 147 'recordsFiltered': users_data_total_filtered_count,
148 148 })
149 149
150 150 return data
151 151
152 152 @LoginRequired()
153 153 @HasPermissionAllDecorator('hg.admin')
154 154 @view_config(
155 155 route_name='edit_user_auth_tokens', request_method='GET',
156 156 renderer='rhodecode:templates/admin/users/user_edit.mako')
157 157 def auth_tokens(self):
158 158 _ = self.request.translate
159 159 c = self.load_default_context()
160 160
161 161 user_id = self.request.matchdict.get('user_id')
162 162 c.user = User.get_or_404(user_id, pyramid_exc=True)
163 163 self._redirect_for_default_user(c.user.username)
164 164
165 165 c.active = 'auth_tokens'
166 166
167 167 c.lifetime_values = [
168 168 (str(-1), _('forever')),
169 169 (str(5), _('5 minutes')),
170 170 (str(60), _('1 hour')),
171 171 (str(60 * 24), _('1 day')),
172 172 (str(60 * 24 * 30), _('1 month')),
173 173 ]
174 174 c.lifetime_options = [(c.lifetime_values, _("Lifetime"))]
175 175 c.role_values = [
176 176 (x, AuthTokenModel.cls._get_role_name(x))
177 177 for x in AuthTokenModel.cls.ROLES]
178 178 c.role_options = [(c.role_values, _("Role"))]
179 179 c.user_auth_tokens = AuthTokenModel().get_auth_tokens(
180 180 c.user.user_id, show_expired=True)
181 181 return self._get_template_context(c)
182 182
183 183 def maybe_attach_token_scope(self, token):
184 184 # implemented in EE edition
185 185 pass
186 186
187 187 @LoginRequired()
188 188 @HasPermissionAllDecorator('hg.admin')
189 189 @CSRFRequired()
190 190 @view_config(
191 191 route_name='edit_user_auth_tokens_add', request_method='POST')
192 192 def auth_tokens_add(self):
193 193 _ = self.request.translate
194 194 c = self.load_default_context()
195 195
196 196 user_id = self.request.matchdict.get('user_id')
197 197 c.user = User.get_or_404(user_id, pyramid_exc=True)
198 198
199 199 self._redirect_for_default_user(c.user.username)
200 200
201 201 user_data = c.user.get_api_data()
202 202 lifetime = safe_int(self.request.POST.get('lifetime'), -1)
203 203 description = self.request.POST.get('description')
204 204 role = self.request.POST.get('role')
205 205
206 206 token = AuthTokenModel().create(
207 207 c.user.user_id, description, lifetime, role)
208 208 token_data = token.get_api_data()
209 209
210 210 self.maybe_attach_token_scope(token)
211 211 audit_logger.store_web(
212 212 action='user.edit.token.add',
213 213 action_data={'data': {'token': token_data, 'user': user_data}},
214 214 user=self._rhodecode_user, )
215 215 Session().commit()
216 216
217 217 h.flash(_("Auth token successfully created"), category='success')
218 218 return HTTPFound(h.route_path('edit_user_auth_tokens', user_id=user_id))
219 219
220 220 @LoginRequired()
221 221 @HasPermissionAllDecorator('hg.admin')
222 222 @CSRFRequired()
223 223 @view_config(
224 224 route_name='edit_user_auth_tokens_delete', request_method='POST')
225 225 def auth_tokens_delete(self):
226 226 _ = self.request.translate
227 227 c = self.load_default_context()
228 228
229 229 user_id = self.request.matchdict.get('user_id')
230 230 c.user = User.get_or_404(user_id, pyramid_exc=True)
231 231 self._redirect_for_default_user(c.user.username)
232 232 user_data = c.user.get_api_data()
233 233
234 234 del_auth_token = self.request.POST.get('del_auth_token')
235 235
236 236 if del_auth_token:
237 237 token = UserApiKeys.get_or_404(del_auth_token, pyramid_exc=True)
238 238 token_data = token.get_api_data()
239 239
240 240 AuthTokenModel().delete(del_auth_token, c.user.user_id)
241 241 audit_logger.store_web(
242 242 action='user.edit.token.delete',
243 243 action_data={'data': {'token': token_data, 'user': user_data}},
244 244 user=self._rhodecode_user,)
245 245 Session().commit()
246 246 h.flash(_("Auth token successfully deleted"), category='success')
247 247
248 248 return HTTPFound(h.route_path('edit_user_auth_tokens', user_id=user_id))
249 249
250 250 @LoginRequired()
251 251 @HasPermissionAllDecorator('hg.admin')
252 252 @view_config(
253 253 route_name='edit_user_emails', request_method='GET',
254 254 renderer='rhodecode:templates/admin/users/user_edit.mako')
255 255 def emails(self):
256 256 _ = self.request.translate
257 257 c = self.load_default_context()
258 258
259 259 user_id = self.request.matchdict.get('user_id')
260 260 c.user = User.get_or_404(user_id, pyramid_exc=True)
261 261 self._redirect_for_default_user(c.user.username)
262 262
263 263 c.active = 'emails'
264 264 c.user_email_map = UserEmailMap.query() \
265 265 .filter(UserEmailMap.user == c.user).all()
266 266
267 267 return self._get_template_context(c)
268 268
269 269 @LoginRequired()
270 270 @HasPermissionAllDecorator('hg.admin')
271 271 @CSRFRequired()
272 272 @view_config(
273 273 route_name='edit_user_emails_add', request_method='POST')
274 274 def emails_add(self):
275 275 _ = self.request.translate
276 276 c = self.load_default_context()
277 277
278 278 user_id = self.request.matchdict.get('user_id')
279 279 c.user = User.get_or_404(user_id, pyramid_exc=True)
280 280 self._redirect_for_default_user(c.user.username)
281 281
282 282 email = self.request.POST.get('new_email')
283 283 user_data = c.user.get_api_data()
284 284 try:
285 285 UserModel().add_extra_email(c.user.user_id, email)
286 286 audit_logger.store_web(
287 287 'user.edit.email.add',
288 288 action_data={'email': email, 'user': user_data},
289 289 user=self._rhodecode_user)
290 290 Session().commit()
291 291 h.flash(_("Added new email address `%s` for user account") % email,
292 292 category='success')
293 293 except formencode.Invalid as error:
294 msg = error.error_dict['email']
295 h.flash(msg, category='error')
294 h.flash(h.escape(error.error_dict['email']), category='error')
296 295 except Exception:
297 296 log.exception("Exception during email saving")
298 297 h.flash(_('An error occurred during email saving'),
299 298 category='error')
300 299 raise HTTPFound(h.route_path('edit_user_emails', user_id=user_id))
301 300
302 301 @LoginRequired()
303 302 @HasPermissionAllDecorator('hg.admin')
304 303 @CSRFRequired()
305 304 @view_config(
306 305 route_name='edit_user_emails_delete', request_method='POST')
307 306 def emails_delete(self):
308 307 _ = self.request.translate
309 308 c = self.load_default_context()
310 309
311 310 user_id = self.request.matchdict.get('user_id')
312 311 c.user = User.get_or_404(user_id, pyramid_exc=True)
313 312 self._redirect_for_default_user(c.user.username)
314 313
315 314 email_id = self.request.POST.get('del_email_id')
316 315 user_model = UserModel()
317 316
318 317 email = UserEmailMap.query().get(email_id).email
319 318 user_data = c.user.get_api_data()
320 319 user_model.delete_extra_email(c.user.user_id, email_id)
321 320 audit_logger.store_web(
322 321 'user.edit.email.delete',
323 322 action_data={'email': email, 'user': user_data},
324 323 user=self._rhodecode_user)
325 324 Session().commit()
326 325 h.flash(_("Removed email address from user account"),
327 326 category='success')
328 327 raise HTTPFound(h.route_path('edit_user_emails', user_id=user_id))
329 328
330 329 @LoginRequired()
331 330 @HasPermissionAllDecorator('hg.admin')
332 331 @view_config(
333 332 route_name='edit_user_ips', request_method='GET',
334 333 renderer='rhodecode:templates/admin/users/user_edit.mako')
335 334 def ips(self):
336 335 _ = self.request.translate
337 336 c = self.load_default_context()
338 337
339 338 user_id = self.request.matchdict.get('user_id')
340 339 c.user = User.get_or_404(user_id, pyramid_exc=True)
341 340 self._redirect_for_default_user(c.user.username)
342 341
343 342 c.active = 'ips'
344 343 c.user_ip_map = UserIpMap.query() \
345 344 .filter(UserIpMap.user == c.user).all()
346 345
347 346 c.inherit_default_ips = c.user.inherit_default_permissions
348 347 c.default_user_ip_map = UserIpMap.query() \
349 348 .filter(UserIpMap.user == User.get_default_user()).all()
350 349
351 350 return self._get_template_context(c)
352 351
353 352 @LoginRequired()
354 353 @HasPermissionAllDecorator('hg.admin')
355 354 @CSRFRequired()
356 355 @view_config(
357 356 route_name='edit_user_ips_add', request_method='POST')
358 357 def ips_add(self):
359 358 _ = self.request.translate
360 359 c = self.load_default_context()
361 360
362 361 user_id = self.request.matchdict.get('user_id')
363 362 c.user = User.get_or_404(user_id, pyramid_exc=True)
364 363 # NOTE(marcink): this view is allowed for default users, as we can
365 364 # edit their IP white list
366 365
367 366 user_model = UserModel()
368 367 desc = self.request.POST.get('description')
369 368 try:
370 369 ip_list = user_model.parse_ip_range(
371 370 self.request.POST.get('new_ip'))
372 371 except Exception as e:
373 372 ip_list = []
374 373 log.exception("Exception during ip saving")
375 374 h.flash(_('An error occurred during ip saving:%s' % (e,)),
376 375 category='error')
377 376 added = []
378 377 user_data = c.user.get_api_data()
379 378 for ip in ip_list:
380 379 try:
381 380 user_model.add_extra_ip(c.user.user_id, ip, desc)
382 381 audit_logger.store_web(
383 382 'user.edit.ip.add',
384 383 action_data={'ip': ip, 'user': user_data},
385 384 user=self._rhodecode_user)
386 385 Session().commit()
387 386 added.append(ip)
388 387 except formencode.Invalid as error:
389 388 msg = error.error_dict['ip']
390 389 h.flash(msg, category='error')
391 390 except Exception:
392 391 log.exception("Exception during ip saving")
393 392 h.flash(_('An error occurred during ip saving'),
394 393 category='error')
395 394 if added:
396 395 h.flash(
397 396 _("Added ips %s to user whitelist") % (', '.join(ip_list), ),
398 397 category='success')
399 398 if 'default_user' in self.request.POST:
400 399 # case for editing global IP list we do it for 'DEFAULT' user
401 400 raise HTTPFound(h.route_path('admin_permissions_ips'))
402 401 raise HTTPFound(h.route_path('edit_user_ips', user_id=user_id))
403 402
404 403 @LoginRequired()
405 404 @HasPermissionAllDecorator('hg.admin')
406 405 @CSRFRequired()
407 406 @view_config(
408 407 route_name='edit_user_ips_delete', request_method='POST')
409 408 def ips_delete(self):
410 409 _ = self.request.translate
411 410 c = self.load_default_context()
412 411
413 412 user_id = self.request.matchdict.get('user_id')
414 413 c.user = User.get_or_404(user_id, pyramid_exc=True)
415 414 # NOTE(marcink): this view is allowed for default users, as we can
416 415 # edit their IP white list
417 416
418 417 ip_id = self.request.POST.get('del_ip_id')
419 418 user_model = UserModel()
420 419 user_data = c.user.get_api_data()
421 420 ip = UserIpMap.query().get(ip_id).ip_addr
422 421 user_model.delete_extra_ip(c.user.user_id, ip_id)
423 422 audit_logger.store_web(
424 423 'user.edit.ip.delete',
425 424 action_data={'ip': ip, 'user': user_data},
426 425 user=self._rhodecode_user)
427 426 Session().commit()
428 427 h.flash(_("Removed ip address from user whitelist"), category='success')
429 428
430 429 if 'default_user' in self.request.POST:
431 430 # case for editing global IP list we do it for 'DEFAULT' user
432 431 raise HTTPFound(h.route_path('admin_permissions_ips'))
433 432 raise HTTPFound(h.route_path('edit_user_ips', user_id=user_id))
434 433
435 434 @LoginRequired()
436 435 @HasPermissionAllDecorator('hg.admin')
437 436 @view_config(
438 437 route_name='edit_user_groups_management', request_method='GET',
439 438 renderer='rhodecode:templates/admin/users/user_edit.mako')
440 439 def groups_management(self):
441 440 c = self.load_default_context()
442 441
443 442 user_id = self.request.matchdict.get('user_id')
444 443 c.user = User.get_or_404(user_id, pyramid_exc=True)
445 444 c.data = c.user.group_member
446 445 self._redirect_for_default_user(c.user.username)
447 446 groups = [UserGroupModel.get_user_groups_as_dict(group.users_group)
448 447 for group in c.user.group_member]
449 448 c.groups = json.dumps(groups)
450 449 c.active = 'groups'
451 450
452 451 return self._get_template_context(c)
453 452
454 453 @LoginRequired()
455 454 @HasPermissionAllDecorator('hg.admin')
456 455 @CSRFRequired()
457 456 @view_config(
458 457 route_name='edit_user_groups_management_updates', request_method='POST')
459 458 def groups_management_updates(self):
460 459 _ = self.request.translate
461 460 c = self.load_default_context()
462 461
463 462 user_id = self.request.matchdict.get('user_id')
464 463 c.user = User.get_or_404(user_id, pyramid_exc=True)
465 464 self._redirect_for_default_user(c.user.username)
466 465
467 466 users_groups = set(self.request.POST.getall('users_group_id'))
468 467 users_groups_model = []
469 468
470 469 for ugid in users_groups:
471 470 users_groups_model.append(UserGroupModel().get_group(safe_int(ugid)))
472 471 user_group_model = UserGroupModel()
473 472 user_group_model.change_groups(c.user, users_groups_model)
474 473
475 474 Session().commit()
476 475 c.active = 'user_groups_management'
477 476 h.flash(_("Groups successfully changed"), category='success')
478 477
479 478 return HTTPFound(h.route_path(
480 479 'edit_user_groups_management', user_id=user_id))
481 480
482 481 @LoginRequired()
483 482 @HasPermissionAllDecorator('hg.admin')
484 483 @view_config(
485 484 route_name='edit_user_audit_logs', request_method='GET',
486 485 renderer='rhodecode:templates/admin/users/user_edit.mako')
487 486 def user_audit_logs(self):
488 487 _ = self.request.translate
489 488 c = self.load_default_context()
490 489
491 490 user_id = self.request.matchdict.get('user_id')
492 491 c.user = User.get_or_404(user_id, pyramid_exc=True)
493 492 self._redirect_for_default_user(c.user.username)
494 493 c.active = 'audit'
495 494
496 495 p = safe_int(self.request.GET.get('page', 1), 1)
497 496
498 497 filter_term = self.request.GET.get('filter')
499 498 user_log = UserModel().get_user_log(c.user, filter_term)
500 499
501 500 def url_generator(**kw):
502 501 if filter_term:
503 502 kw['filter'] = filter_term
504 503 return self.request.current_route_path(_query=kw)
505 504
506 505 c.audit_logs = h.Page(
507 506 user_log, page=p, items_per_page=10, url=url_generator)
508 507 c.filter_term = filter_term
509 508 return self._get_template_context(c)
510 509
@@ -1,400 +1,399 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2016-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 import formencode
25 25 from pyramid.httpexceptions import HTTPFound
26 26 from pyramid.view import view_config
27 27
28 28 from rhodecode.apps._base import BaseAppView
29 29 from rhodecode import forms
30 30 from rhodecode.lib import helpers as h
31 31 from rhodecode.lib import audit_logger
32 32 from rhodecode.lib.ext_json import json
33 33 from rhodecode.lib.auth import LoginRequired, NotAnonymous, CSRFRequired
34 34 from rhodecode.lib.channelstream import channelstream_request, \
35 35 ChannelstreamException
36 36 from rhodecode.lib.utils2 import safe_int, md5
37 37 from rhodecode.model.auth_token import AuthTokenModel
38 38 from rhodecode.model.db import (
39 39 Repository, UserEmailMap, UserApiKeys, UserFollowing, joinedload)
40 40 from rhodecode.model.meta import Session
41 41 from rhodecode.model.scm import RepoList
42 42 from rhodecode.model.user import UserModel
43 43 from rhodecode.model.repo import RepoModel
44 44 from rhodecode.model.validation_schema.schemas import user_schema
45 45
46 46 log = logging.getLogger(__name__)
47 47
48 48
49 49 class MyAccountView(BaseAppView):
50 50 ALLOW_SCOPED_TOKENS = False
51 51 """
52 52 This view has alternative version inside EE, if modified please take a look
53 53 in there as well.
54 54 """
55 55
56 56 def load_default_context(self):
57 57 c = self._get_local_tmpl_context()
58 58 c.user = c.auth_user.get_instance()
59 59 c.allow_scoped_tokens = self.ALLOW_SCOPED_TOKENS
60 60 self._register_global_c(c)
61 61 return c
62 62
63 63 @LoginRequired()
64 64 @NotAnonymous()
65 65 @view_config(
66 66 route_name='my_account_profile', request_method='GET',
67 67 renderer='rhodecode:templates/admin/my_account/my_account.mako')
68 68 def my_account_profile(self):
69 69 c = self.load_default_context()
70 70 c.active = 'profile'
71 71 return self._get_template_context(c)
72 72
73 73 @LoginRequired()
74 74 @NotAnonymous()
75 75 @view_config(
76 76 route_name='my_account_password', request_method='GET',
77 77 renderer='rhodecode:templates/admin/my_account/my_account.mako')
78 78 def my_account_password(self):
79 79 c = self.load_default_context()
80 80 c.active = 'password'
81 81 c.extern_type = c.user.extern_type
82 82
83 83 schema = user_schema.ChangePasswordSchema().bind(
84 84 username=c.user.username)
85 85
86 86 form = forms.Form(
87 87 schema, buttons=(forms.buttons.save, forms.buttons.reset))
88 88
89 89 c.form = form
90 90 return self._get_template_context(c)
91 91
92 92 @LoginRequired()
93 93 @NotAnonymous()
94 94 @CSRFRequired()
95 95 @view_config(
96 96 route_name='my_account_password', request_method='POST',
97 97 renderer='rhodecode:templates/admin/my_account/my_account.mako')
98 98 def my_account_password_update(self):
99 99 _ = self.request.translate
100 100 c = self.load_default_context()
101 101 c.active = 'password'
102 102 c.extern_type = c.user.extern_type
103 103
104 104 schema = user_schema.ChangePasswordSchema().bind(
105 105 username=c.user.username)
106 106
107 107 form = forms.Form(
108 108 schema, buttons=(forms.buttons.save, forms.buttons.reset))
109 109
110 110 if c.extern_type != 'rhodecode':
111 111 raise HTTPFound(self.request.route_path('my_account_password'))
112 112
113 113 controls = self.request.POST.items()
114 114 try:
115 115 valid_data = form.validate(controls)
116 116 UserModel().update_user(c.user.user_id, **valid_data)
117 117 c.user.update_userdata(force_password_change=False)
118 118 Session().commit()
119 119 except forms.ValidationFailure as e:
120 120 c.form = e
121 121 return self._get_template_context(c)
122 122
123 123 except Exception:
124 124 log.exception("Exception updating password")
125 125 h.flash(_('Error occurred during update of user password'),
126 126 category='error')
127 127 else:
128 128 instance = c.auth_user.get_instance()
129 129 self.session.setdefault('rhodecode_user', {}).update(
130 130 {'password': md5(instance.password)})
131 131 self.session.save()
132 132 h.flash(_("Successfully updated password"), category='success')
133 133
134 134 raise HTTPFound(self.request.route_path('my_account_password'))
135 135
136 136 @LoginRequired()
137 137 @NotAnonymous()
138 138 @view_config(
139 139 route_name='my_account_auth_tokens', request_method='GET',
140 140 renderer='rhodecode:templates/admin/my_account/my_account.mako')
141 141 def my_account_auth_tokens(self):
142 142 _ = self.request.translate
143 143
144 144 c = self.load_default_context()
145 145 c.active = 'auth_tokens'
146 146
147 147 c.lifetime_values = [
148 148 (str(-1), _('forever')),
149 149 (str(5), _('5 minutes')),
150 150 (str(60), _('1 hour')),
151 151 (str(60 * 24), _('1 day')),
152 152 (str(60 * 24 * 30), _('1 month')),
153 153 ]
154 154 c.lifetime_options = [(c.lifetime_values, _("Lifetime"))]
155 155 c.role_values = [
156 156 (x, AuthTokenModel.cls._get_role_name(x))
157 157 for x in AuthTokenModel.cls.ROLES]
158 158 c.role_options = [(c.role_values, _("Role"))]
159 159 c.user_auth_tokens = AuthTokenModel().get_auth_tokens(
160 160 c.user.user_id, show_expired=True)
161 161 return self._get_template_context(c)
162 162
163 163 def maybe_attach_token_scope(self, token):
164 164 # implemented in EE edition
165 165 pass
166 166
167 167 @LoginRequired()
168 168 @NotAnonymous()
169 169 @CSRFRequired()
170 170 @view_config(
171 171 route_name='my_account_auth_tokens_add', request_method='POST',)
172 172 def my_account_auth_tokens_add(self):
173 173 _ = self.request.translate
174 174 c = self.load_default_context()
175 175
176 176 lifetime = safe_int(self.request.POST.get('lifetime'), -1)
177 177 description = self.request.POST.get('description')
178 178 role = self.request.POST.get('role')
179 179
180 180 token = AuthTokenModel().create(
181 181 c.user.user_id, description, lifetime, role)
182 182 token_data = token.get_api_data()
183 183
184 184 self.maybe_attach_token_scope(token)
185 185 audit_logger.store_web(
186 186 action='user.edit.token.add',
187 187 action_data={'data': {'token': token_data, 'user': 'self'}},
188 188 user=self._rhodecode_user, )
189 189 Session().commit()
190 190
191 191 h.flash(_("Auth token successfully created"), category='success')
192 192 return HTTPFound(h.route_path('my_account_auth_tokens'))
193 193
194 194 @LoginRequired()
195 195 @NotAnonymous()
196 196 @CSRFRequired()
197 197 @view_config(
198 198 route_name='my_account_auth_tokens_delete', request_method='POST')
199 199 def my_account_auth_tokens_delete(self):
200 200 _ = self.request.translate
201 201 c = self.load_default_context()
202 202
203 203 del_auth_token = self.request.POST.get('del_auth_token')
204 204
205 205 if del_auth_token:
206 206 token = UserApiKeys.get_or_404(del_auth_token, pyramid_exc=True)
207 207 token_data = token.get_api_data()
208 208
209 209 AuthTokenModel().delete(del_auth_token, c.user.user_id)
210 210 audit_logger.store_web(
211 211 action='user.edit.token.delete',
212 212 action_data={'data': {'token': token_data, 'user': 'self'}},
213 213 user=self._rhodecode_user,)
214 214 Session().commit()
215 215 h.flash(_("Auth token successfully deleted"), category='success')
216 216
217 217 return HTTPFound(h.route_path('my_account_auth_tokens'))
218 218
219 219 @LoginRequired()
220 220 @NotAnonymous()
221 221 @view_config(
222 222 route_name='my_account_emails', request_method='GET',
223 223 renderer='rhodecode:templates/admin/my_account/my_account.mako')
224 224 def my_account_emails(self):
225 225 _ = self.request.translate
226 226
227 227 c = self.load_default_context()
228 228 c.active = 'emails'
229 229
230 230 c.user_email_map = UserEmailMap.query()\
231 231 .filter(UserEmailMap.user == c.user).all()
232 232 return self._get_template_context(c)
233 233
234 234 @LoginRequired()
235 235 @NotAnonymous()
236 236 @CSRFRequired()
237 237 @view_config(
238 238 route_name='my_account_emails_add', request_method='POST')
239 239 def my_account_emails_add(self):
240 240 _ = self.request.translate
241 241 c = self.load_default_context()
242 242
243 243 email = self.request.POST.get('new_email')
244 244
245 245 try:
246 246 UserModel().add_extra_email(c.user.user_id, email)
247 247 audit_logger.store_web(
248 248 action='user.edit.email.add',
249 249 action_data={'data': {'email': email, 'user': 'self'}},
250 250 user=self._rhodecode_user,)
251 251
252 252 Session().commit()
253 253 h.flash(_("Added new email address `%s` for user account") % email,
254 254 category='success')
255 255 except formencode.Invalid as error:
256 msg = error.error_dict['email']
257 h.flash(msg, category='error')
256 h.flash(h.escape(error.error_dict['email']), category='error')
258 257 except Exception:
259 258 log.exception("Exception in my_account_emails")
260 259 h.flash(_('An error occurred during email saving'),
261 260 category='error')
262 261 return HTTPFound(h.route_path('my_account_emails'))
263 262
264 263 @LoginRequired()
265 264 @NotAnonymous()
266 265 @CSRFRequired()
267 266 @view_config(
268 267 route_name='my_account_emails_delete', request_method='POST')
269 268 def my_account_emails_delete(self):
270 269 _ = self.request.translate
271 270 c = self.load_default_context()
272 271
273 272 del_email_id = self.request.POST.get('del_email_id')
274 273 if del_email_id:
275 274 email = UserEmailMap.get_or_404(del_email_id, pyramid_exc=True).email
276 275 UserModel().delete_extra_email(c.user.user_id, del_email_id)
277 276 audit_logger.store_web(
278 277 action='user.edit.email.delete',
279 278 action_data={'data': {'email': email, 'user': 'self'}},
280 279 user=self._rhodecode_user,)
281 280 Session().commit()
282 281 h.flash(_("Email successfully deleted"),
283 282 category='success')
284 283 return HTTPFound(h.route_path('my_account_emails'))
285 284
286 285 @LoginRequired()
287 286 @NotAnonymous()
288 287 @CSRFRequired()
289 288 @view_config(
290 289 route_name='my_account_notifications_test_channelstream',
291 290 request_method='POST', renderer='json_ext')
292 291 def my_account_notifications_test_channelstream(self):
293 292 message = 'Test message sent via Channelstream by user: {}, on {}'.format(
294 293 self._rhodecode_user.username, datetime.datetime.now())
295 294 payload = {
296 295 # 'channel': 'broadcast',
297 296 'type': 'message',
298 297 'timestamp': datetime.datetime.utcnow(),
299 298 'user': 'system',
300 299 'pm_users': [self._rhodecode_user.username],
301 300 'message': {
302 301 'message': message,
303 302 'level': 'info',
304 303 'topic': '/notifications'
305 304 }
306 305 }
307 306
308 307 registry = self.request.registry
309 308 rhodecode_plugins = getattr(registry, 'rhodecode_plugins', {})
310 309 channelstream_config = rhodecode_plugins.get('channelstream', {})
311 310
312 311 try:
313 312 channelstream_request(channelstream_config, [payload], '/message')
314 313 except ChannelstreamException as e:
315 314 log.exception('Failed to send channelstream data')
316 315 return {"response": 'ERROR: {}'.format(e.__class__.__name__)}
317 316 return {"response": 'Channelstream data sent. '
318 317 'You should see a new live message now.'}
319 318
320 319 def _load_my_repos_data(self, watched=False):
321 320 if watched:
322 321 admin = False
323 322 follows_repos = Session().query(UserFollowing)\
324 323 .filter(UserFollowing.user_id == self._rhodecode_user.user_id)\
325 324 .options(joinedload(UserFollowing.follows_repository))\
326 325 .all()
327 326 repo_list = [x.follows_repository for x in follows_repos]
328 327 else:
329 328 admin = True
330 329 repo_list = Repository.get_all_repos(
331 330 user_id=self._rhodecode_user.user_id)
332 331 repo_list = RepoList(repo_list, perm_set=[
333 332 'repository.read', 'repository.write', 'repository.admin'])
334 333
335 334 repos_data = RepoModel().get_repos_as_dict(
336 335 repo_list=repo_list, admin=admin)
337 336 # json used to render the grid
338 337 return json.dumps(repos_data)
339 338
340 339 @LoginRequired()
341 340 @NotAnonymous()
342 341 @view_config(
343 342 route_name='my_account_repos', request_method='GET',
344 343 renderer='rhodecode:templates/admin/my_account/my_account.mako')
345 344 def my_account_repos(self):
346 345 c = self.load_default_context()
347 346 c.active = 'repos'
348 347
349 348 # json used to render the grid
350 349 c.data = self._load_my_repos_data()
351 350 return self._get_template_context(c)
352 351
353 352 @LoginRequired()
354 353 @NotAnonymous()
355 354 @view_config(
356 355 route_name='my_account_watched', request_method='GET',
357 356 renderer='rhodecode:templates/admin/my_account/my_account.mako')
358 357 def my_account_watched(self):
359 358 c = self.load_default_context()
360 359 c.active = 'watched'
361 360
362 361 # json used to render the grid
363 362 c.data = self._load_my_repos_data(watched=True)
364 363 return self._get_template_context(c)
365 364
366 365 @LoginRequired()
367 366 @NotAnonymous()
368 367 @view_config(
369 368 route_name='my_account_perms', request_method='GET',
370 369 renderer='rhodecode:templates/admin/my_account/my_account.mako')
371 370 def my_account_perms(self):
372 371 c = self.load_default_context()
373 372 c.active = 'perms'
374 373
375 374 c.perm_user = c.auth_user
376 375 return self._get_template_context(c)
377 376
378 377 @LoginRequired()
379 378 @NotAnonymous()
380 379 @view_config(
381 380 route_name='my_account_notifications', request_method='GET',
382 381 renderer='rhodecode:templates/admin/my_account/my_account.mako')
383 382 def my_notifications(self):
384 383 c = self.load_default_context()
385 384 c.active = 'notifications'
386 385
387 386 return self._get_template_context(c)
388 387
389 388 @LoginRequired()
390 389 @NotAnonymous()
391 390 @CSRFRequired()
392 391 @view_config(
393 392 route_name='my_account_notifications_toggle_visibility',
394 393 request_method='POST', renderer='json_ext')
395 394 def my_notifications_toggle_visibility(self):
396 395 user = self._rhodecode_db_user
397 396 new_status = not user.user_data.get('notification_status', True)
398 397 user.update_userdata(notification_status=new_status)
399 398 Session().commit()
400 399 return user.user_data['notification_status'] No newline at end of file
General Comments 0
You need to be logged in to leave comments. Login now