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