##// END OF EJS Templates
auth/security: enforce that external users cannot reset their password.
marcink -
r3258:e5497c9f default
parent child Browse files
Show More
@@ -1,1242 +1,1242 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2016-2018 RhodeCode GmbH
3 # Copyright (C) 2016-2018 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 import formencode.htmlfill
24 import formencode.htmlfill
25
25
26 from pyramid.httpexceptions import HTTPFound
26 from pyramid.httpexceptions import HTTPFound
27 from pyramid.view import view_config
27 from pyramid.view import view_config
28 from pyramid.renderers import render
28 from pyramid.renderers import render
29 from pyramid.response import Response
29 from pyramid.response import Response
30
30
31 from rhodecode.apps._base import BaseAppView, DataGridAppView, UserAppView
31 from rhodecode.apps._base import BaseAppView, DataGridAppView, UserAppView
32 from rhodecode.apps.ssh_support import SshKeyFileChangeEvent
32 from rhodecode.apps.ssh_support import SshKeyFileChangeEvent
33 from rhodecode.authentication.plugins import auth_rhodecode
33 from rhodecode.authentication.plugins import auth_rhodecode
34 from rhodecode.events import trigger
34 from rhodecode.events import trigger
35 from rhodecode.model.db import true
35 from rhodecode.model.db import true
36
36
37 from rhodecode.lib import audit_logger, rc_cache
37 from rhodecode.lib import audit_logger, rc_cache
38 from rhodecode.lib.exceptions import (
38 from rhodecode.lib.exceptions import (
39 UserCreationError, UserOwnsReposException, UserOwnsRepoGroupsException,
39 UserCreationError, UserOwnsReposException, UserOwnsRepoGroupsException,
40 UserOwnsUserGroupsException, DefaultUserException)
40 UserOwnsUserGroupsException, DefaultUserException)
41 from rhodecode.lib.ext_json import json
41 from rhodecode.lib.ext_json import json
42 from rhodecode.lib.auth import (
42 from rhodecode.lib.auth import (
43 LoginRequired, HasPermissionAllDecorator, CSRFRequired)
43 LoginRequired, HasPermissionAllDecorator, CSRFRequired)
44 from rhodecode.lib import helpers as h
44 from rhodecode.lib import helpers as h
45 from rhodecode.lib.utils2 import safe_int, safe_unicode, AttributeDict
45 from rhodecode.lib.utils2 import safe_int, safe_unicode, AttributeDict
46 from rhodecode.model.auth_token import AuthTokenModel
46 from rhodecode.model.auth_token import AuthTokenModel
47 from rhodecode.model.forms import (
47 from rhodecode.model.forms import (
48 UserForm, UserIndividualPermissionsForm, UserPermissionsForm,
48 UserForm, UserIndividualPermissionsForm, UserPermissionsForm,
49 UserExtraEmailForm, UserExtraIpForm)
49 UserExtraEmailForm, UserExtraIpForm)
50 from rhodecode.model.permission import PermissionModel
50 from rhodecode.model.permission import PermissionModel
51 from rhodecode.model.repo_group import RepoGroupModel
51 from rhodecode.model.repo_group import RepoGroupModel
52 from rhodecode.model.ssh_key import SshKeyModel
52 from rhodecode.model.ssh_key import SshKeyModel
53 from rhodecode.model.user import UserModel
53 from rhodecode.model.user import UserModel
54 from rhodecode.model.user_group import UserGroupModel
54 from rhodecode.model.user_group import UserGroupModel
55 from rhodecode.model.db import (
55 from rhodecode.model.db import (
56 or_, coalesce,IntegrityError, User, UserGroup, UserIpMap, UserEmailMap,
56 or_, coalesce,IntegrityError, User, UserGroup, UserIpMap, UserEmailMap,
57 UserApiKeys, UserSshKeys, RepoGroup)
57 UserApiKeys, UserSshKeys, RepoGroup)
58 from rhodecode.model.meta import Session
58 from rhodecode.model.meta import Session
59
59
60 log = logging.getLogger(__name__)
60 log = logging.getLogger(__name__)
61
61
62
62
63 class AdminUsersView(BaseAppView, DataGridAppView):
63 class AdminUsersView(BaseAppView, DataGridAppView):
64
64
65 def load_default_context(self):
65 def load_default_context(self):
66 c = self._get_local_tmpl_context()
66 c = self._get_local_tmpl_context()
67 return c
67 return c
68
68
69 @LoginRequired()
69 @LoginRequired()
70 @HasPermissionAllDecorator('hg.admin')
70 @HasPermissionAllDecorator('hg.admin')
71 @view_config(
71 @view_config(
72 route_name='users', request_method='GET',
72 route_name='users', request_method='GET',
73 renderer='rhodecode:templates/admin/users/users.mako')
73 renderer='rhodecode:templates/admin/users/users.mako')
74 def users_list(self):
74 def users_list(self):
75 c = self.load_default_context()
75 c = self.load_default_context()
76 return self._get_template_context(c)
76 return self._get_template_context(c)
77
77
78 @LoginRequired()
78 @LoginRequired()
79 @HasPermissionAllDecorator('hg.admin')
79 @HasPermissionAllDecorator('hg.admin')
80 @view_config(
80 @view_config(
81 # renderer defined below
81 # renderer defined below
82 route_name='users_data', request_method='GET',
82 route_name='users_data', request_method='GET',
83 renderer='json_ext', xhr=True)
83 renderer='json_ext', xhr=True)
84 def users_list_data(self):
84 def users_list_data(self):
85 self.load_default_context()
85 self.load_default_context()
86 column_map = {
86 column_map = {
87 'first_name': 'name',
87 'first_name': 'name',
88 'last_name': 'lastname',
88 'last_name': 'lastname',
89 }
89 }
90 draw, start, limit = self._extract_chunk(self.request)
90 draw, start, limit = self._extract_chunk(self.request)
91 search_q, order_by, order_dir = self._extract_ordering(
91 search_q, order_by, order_dir = self._extract_ordering(
92 self.request, column_map=column_map)
92 self.request, column_map=column_map)
93 _render = self.request.get_partial_renderer(
93 _render = self.request.get_partial_renderer(
94 'rhodecode:templates/data_table/_dt_elements.mako')
94 'rhodecode:templates/data_table/_dt_elements.mako')
95
95
96 def user_actions(user_id, username):
96 def user_actions(user_id, username):
97 return _render("user_actions", user_id, username)
97 return _render("user_actions", user_id, username)
98
98
99 users_data_total_count = User.query()\
99 users_data_total_count = User.query()\
100 .filter(User.username != User.DEFAULT_USER) \
100 .filter(User.username != User.DEFAULT_USER) \
101 .count()
101 .count()
102
102
103 users_data_total_inactive_count = User.query()\
103 users_data_total_inactive_count = User.query()\
104 .filter(User.username != User.DEFAULT_USER) \
104 .filter(User.username != User.DEFAULT_USER) \
105 .filter(User.active != true())\
105 .filter(User.active != true())\
106 .count()
106 .count()
107
107
108 # json generate
108 # json generate
109 base_q = User.query().filter(User.username != User.DEFAULT_USER)
109 base_q = User.query().filter(User.username != User.DEFAULT_USER)
110 base_inactive_q = base_q.filter(User.active != true())
110 base_inactive_q = base_q.filter(User.active != true())
111
111
112 if search_q:
112 if search_q:
113 like_expression = u'%{}%'.format(safe_unicode(search_q))
113 like_expression = u'%{}%'.format(safe_unicode(search_q))
114 base_q = base_q.filter(or_(
114 base_q = base_q.filter(or_(
115 User.username.ilike(like_expression),
115 User.username.ilike(like_expression),
116 User._email.ilike(like_expression),
116 User._email.ilike(like_expression),
117 User.name.ilike(like_expression),
117 User.name.ilike(like_expression),
118 User.lastname.ilike(like_expression),
118 User.lastname.ilike(like_expression),
119 ))
119 ))
120 base_inactive_q = base_q.filter(User.active != true())
120 base_inactive_q = base_q.filter(User.active != true())
121
121
122 users_data_total_filtered_count = base_q.count()
122 users_data_total_filtered_count = base_q.count()
123 users_data_total_filtered_inactive_count = base_inactive_q.count()
123 users_data_total_filtered_inactive_count = base_inactive_q.count()
124
124
125 sort_col = getattr(User, order_by, None)
125 sort_col = getattr(User, order_by, None)
126 if sort_col:
126 if sort_col:
127 if order_dir == 'asc':
127 if order_dir == 'asc':
128 # handle null values properly to order by NULL last
128 # handle null values properly to order by NULL last
129 if order_by in ['last_activity']:
129 if order_by in ['last_activity']:
130 sort_col = coalesce(sort_col, datetime.date.max)
130 sort_col = coalesce(sort_col, datetime.date.max)
131 sort_col = sort_col.asc()
131 sort_col = sort_col.asc()
132 else:
132 else:
133 # handle null values properly to order by NULL last
133 # handle null values properly to order by NULL last
134 if order_by in ['last_activity']:
134 if order_by in ['last_activity']:
135 sort_col = coalesce(sort_col, datetime.date.min)
135 sort_col = coalesce(sort_col, datetime.date.min)
136 sort_col = sort_col.desc()
136 sort_col = sort_col.desc()
137
137
138 base_q = base_q.order_by(sort_col)
138 base_q = base_q.order_by(sort_col)
139 base_q = base_q.offset(start).limit(limit)
139 base_q = base_q.offset(start).limit(limit)
140
140
141 users_list = base_q.all()
141 users_list = base_q.all()
142
142
143 users_data = []
143 users_data = []
144 for user in users_list:
144 for user in users_list:
145 users_data.append({
145 users_data.append({
146 "username": h.gravatar_with_user(self.request, user.username),
146 "username": h.gravatar_with_user(self.request, user.username),
147 "email": user.email,
147 "email": user.email,
148 "first_name": user.first_name,
148 "first_name": user.first_name,
149 "last_name": user.last_name,
149 "last_name": user.last_name,
150 "last_login": h.format_date(user.last_login),
150 "last_login": h.format_date(user.last_login),
151 "last_activity": h.format_date(user.last_activity),
151 "last_activity": h.format_date(user.last_activity),
152 "active": h.bool2icon(user.active),
152 "active": h.bool2icon(user.active),
153 "active_raw": user.active,
153 "active_raw": user.active,
154 "admin": h.bool2icon(user.admin),
154 "admin": h.bool2icon(user.admin),
155 "extern_type": user.extern_type,
155 "extern_type": user.extern_type,
156 "extern_name": user.extern_name,
156 "extern_name": user.extern_name,
157 "action": user_actions(user.user_id, user.username),
157 "action": user_actions(user.user_id, user.username),
158 })
158 })
159 data = ({
159 data = ({
160 'draw': draw,
160 'draw': draw,
161 'data': users_data,
161 'data': users_data,
162 'recordsTotal': users_data_total_count,
162 'recordsTotal': users_data_total_count,
163 'recordsFiltered': users_data_total_filtered_count,
163 'recordsFiltered': users_data_total_filtered_count,
164 'recordsTotalInactive': users_data_total_inactive_count,
164 'recordsTotalInactive': users_data_total_inactive_count,
165 'recordsFilteredInactive': users_data_total_filtered_inactive_count
165 'recordsFilteredInactive': users_data_total_filtered_inactive_count
166 })
166 })
167
167
168 return data
168 return data
169
169
170 def _set_personal_repo_group_template_vars(self, c_obj):
170 def _set_personal_repo_group_template_vars(self, c_obj):
171 DummyUser = AttributeDict({
171 DummyUser = AttributeDict({
172 'username': '${username}',
172 'username': '${username}',
173 'user_id': '${user_id}',
173 'user_id': '${user_id}',
174 })
174 })
175 c_obj.default_create_repo_group = RepoGroupModel() \
175 c_obj.default_create_repo_group = RepoGroupModel() \
176 .get_default_create_personal_repo_group()
176 .get_default_create_personal_repo_group()
177 c_obj.personal_repo_group_name = RepoGroupModel() \
177 c_obj.personal_repo_group_name = RepoGroupModel() \
178 .get_personal_group_name(DummyUser)
178 .get_personal_group_name(DummyUser)
179
179
180 @LoginRequired()
180 @LoginRequired()
181 @HasPermissionAllDecorator('hg.admin')
181 @HasPermissionAllDecorator('hg.admin')
182 @view_config(
182 @view_config(
183 route_name='users_new', request_method='GET',
183 route_name='users_new', request_method='GET',
184 renderer='rhodecode:templates/admin/users/user_add.mako')
184 renderer='rhodecode:templates/admin/users/user_add.mako')
185 def users_new(self):
185 def users_new(self):
186 _ = self.request.translate
186 _ = self.request.translate
187 c = self.load_default_context()
187 c = self.load_default_context()
188 c.default_extern_type = auth_rhodecode.RhodeCodeAuthPlugin.name
188 c.default_extern_type = auth_rhodecode.RhodeCodeAuthPlugin.uid
189 self._set_personal_repo_group_template_vars(c)
189 self._set_personal_repo_group_template_vars(c)
190 return self._get_template_context(c)
190 return self._get_template_context(c)
191
191
192 @LoginRequired()
192 @LoginRequired()
193 @HasPermissionAllDecorator('hg.admin')
193 @HasPermissionAllDecorator('hg.admin')
194 @CSRFRequired()
194 @CSRFRequired()
195 @view_config(
195 @view_config(
196 route_name='users_create', request_method='POST',
196 route_name='users_create', request_method='POST',
197 renderer='rhodecode:templates/admin/users/user_add.mako')
197 renderer='rhodecode:templates/admin/users/user_add.mako')
198 def users_create(self):
198 def users_create(self):
199 _ = self.request.translate
199 _ = self.request.translate
200 c = self.load_default_context()
200 c = self.load_default_context()
201 c.default_extern_type = auth_rhodecode.RhodeCodeAuthPlugin.name
201 c.default_extern_type = auth_rhodecode.RhodeCodeAuthPlugin.uid
202 user_model = UserModel()
202 user_model = UserModel()
203 user_form = UserForm(self.request.translate)()
203 user_form = UserForm(self.request.translate)()
204 try:
204 try:
205 form_result = user_form.to_python(dict(self.request.POST))
205 form_result = user_form.to_python(dict(self.request.POST))
206 user = user_model.create(form_result)
206 user = user_model.create(form_result)
207 Session().flush()
207 Session().flush()
208 creation_data = user.get_api_data()
208 creation_data = user.get_api_data()
209 username = form_result['username']
209 username = form_result['username']
210
210
211 audit_logger.store_web(
211 audit_logger.store_web(
212 'user.create', action_data={'data': creation_data},
212 'user.create', action_data={'data': creation_data},
213 user=c.rhodecode_user)
213 user=c.rhodecode_user)
214
214
215 user_link = h.link_to(
215 user_link = h.link_to(
216 h.escape(username),
216 h.escape(username),
217 h.route_path('user_edit', user_id=user.user_id))
217 h.route_path('user_edit', user_id=user.user_id))
218 h.flash(h.literal(_('Created user %(user_link)s')
218 h.flash(h.literal(_('Created user %(user_link)s')
219 % {'user_link': user_link}), category='success')
219 % {'user_link': user_link}), category='success')
220 Session().commit()
220 Session().commit()
221 except formencode.Invalid as errors:
221 except formencode.Invalid as errors:
222 self._set_personal_repo_group_template_vars(c)
222 self._set_personal_repo_group_template_vars(c)
223 data = render(
223 data = render(
224 'rhodecode:templates/admin/users/user_add.mako',
224 'rhodecode:templates/admin/users/user_add.mako',
225 self._get_template_context(c), self.request)
225 self._get_template_context(c), self.request)
226 html = formencode.htmlfill.render(
226 html = formencode.htmlfill.render(
227 data,
227 data,
228 defaults=errors.value,
228 defaults=errors.value,
229 errors=errors.error_dict or {},
229 errors=errors.error_dict or {},
230 prefix_error=False,
230 prefix_error=False,
231 encoding="UTF-8",
231 encoding="UTF-8",
232 force_defaults=False
232 force_defaults=False
233 )
233 )
234 return Response(html)
234 return Response(html)
235 except UserCreationError as e:
235 except UserCreationError as e:
236 h.flash(e, 'error')
236 h.flash(e, 'error')
237 except Exception:
237 except Exception:
238 log.exception("Exception creation of user")
238 log.exception("Exception creation of user")
239 h.flash(_('Error occurred during creation of user %s')
239 h.flash(_('Error occurred during creation of user %s')
240 % self.request.POST.get('username'), category='error')
240 % self.request.POST.get('username'), category='error')
241 raise HTTPFound(h.route_path('users'))
241 raise HTTPFound(h.route_path('users'))
242
242
243
243
244 class UsersView(UserAppView):
244 class UsersView(UserAppView):
245 ALLOW_SCOPED_TOKENS = False
245 ALLOW_SCOPED_TOKENS = False
246 """
246 """
247 This view has alternative version inside EE, if modified please take a look
247 This view has alternative version inside EE, if modified please take a look
248 in there as well.
248 in there as well.
249 """
249 """
250
250
251 def load_default_context(self):
251 def load_default_context(self):
252 c = self._get_local_tmpl_context()
252 c = self._get_local_tmpl_context()
253 c.allow_scoped_tokens = self.ALLOW_SCOPED_TOKENS
253 c.allow_scoped_tokens = self.ALLOW_SCOPED_TOKENS
254 c.allowed_languages = [
254 c.allowed_languages = [
255 ('en', 'English (en)'),
255 ('en', 'English (en)'),
256 ('de', 'German (de)'),
256 ('de', 'German (de)'),
257 ('fr', 'French (fr)'),
257 ('fr', 'French (fr)'),
258 ('it', 'Italian (it)'),
258 ('it', 'Italian (it)'),
259 ('ja', 'Japanese (ja)'),
259 ('ja', 'Japanese (ja)'),
260 ('pl', 'Polish (pl)'),
260 ('pl', 'Polish (pl)'),
261 ('pt', 'Portuguese (pt)'),
261 ('pt', 'Portuguese (pt)'),
262 ('ru', 'Russian (ru)'),
262 ('ru', 'Russian (ru)'),
263 ('zh', 'Chinese (zh)'),
263 ('zh', 'Chinese (zh)'),
264 ]
264 ]
265 req = self.request
265 req = self.request
266
266
267 c.available_permissions = req.registry.settings['available_permissions']
267 c.available_permissions = req.registry.settings['available_permissions']
268 PermissionModel().set_global_permission_choices(
268 PermissionModel().set_global_permission_choices(
269 c, gettext_translator=req.translate)
269 c, gettext_translator=req.translate)
270
270
271 return c
271 return c
272
272
273 @LoginRequired()
273 @LoginRequired()
274 @HasPermissionAllDecorator('hg.admin')
274 @HasPermissionAllDecorator('hg.admin')
275 @CSRFRequired()
275 @CSRFRequired()
276 @view_config(
276 @view_config(
277 route_name='user_update', request_method='POST',
277 route_name='user_update', request_method='POST',
278 renderer='rhodecode:templates/admin/users/user_edit.mako')
278 renderer='rhodecode:templates/admin/users/user_edit.mako')
279 def user_update(self):
279 def user_update(self):
280 _ = self.request.translate
280 _ = self.request.translate
281 c = self.load_default_context()
281 c = self.load_default_context()
282
282
283 user_id = self.db_user_id
283 user_id = self.db_user_id
284 c.user = self.db_user
284 c.user = self.db_user
285
285
286 c.active = 'profile'
286 c.active = 'profile'
287 c.extern_type = c.user.extern_type
287 c.extern_type = c.user.extern_type
288 c.extern_name = c.user.extern_name
288 c.extern_name = c.user.extern_name
289 c.perm_user = c.user.AuthUser(ip_addr=self.request.remote_addr)
289 c.perm_user = c.user.AuthUser(ip_addr=self.request.remote_addr)
290 available_languages = [x[0] for x in c.allowed_languages]
290 available_languages = [x[0] for x in c.allowed_languages]
291 _form = UserForm(self.request.translate, edit=True,
291 _form = UserForm(self.request.translate, edit=True,
292 available_languages=available_languages,
292 available_languages=available_languages,
293 old_data={'user_id': user_id,
293 old_data={'user_id': user_id,
294 'email': c.user.email})()
294 'email': c.user.email})()
295 form_result = {}
295 form_result = {}
296 old_values = c.user.get_api_data()
296 old_values = c.user.get_api_data()
297 try:
297 try:
298 form_result = _form.to_python(dict(self.request.POST))
298 form_result = _form.to_python(dict(self.request.POST))
299 skip_attrs = ['extern_type', 'extern_name']
299 skip_attrs = ['extern_type', 'extern_name']
300 # TODO: plugin should define if username can be updated
300 # TODO: plugin should define if username can be updated
301 if c.extern_type != "rhodecode":
301 if c.extern_type != "rhodecode":
302 # forbid updating username for external accounts
302 # forbid updating username for external accounts
303 skip_attrs.append('username')
303 skip_attrs.append('username')
304
304
305 UserModel().update_user(
305 UserModel().update_user(
306 user_id, skip_attrs=skip_attrs, **form_result)
306 user_id, skip_attrs=skip_attrs, **form_result)
307
307
308 audit_logger.store_web(
308 audit_logger.store_web(
309 'user.edit', action_data={'old_data': old_values},
309 'user.edit', action_data={'old_data': old_values},
310 user=c.rhodecode_user)
310 user=c.rhodecode_user)
311
311
312 Session().commit()
312 Session().commit()
313 h.flash(_('User updated successfully'), category='success')
313 h.flash(_('User updated successfully'), category='success')
314 except formencode.Invalid as errors:
314 except formencode.Invalid as errors:
315 data = render(
315 data = render(
316 'rhodecode:templates/admin/users/user_edit.mako',
316 'rhodecode:templates/admin/users/user_edit.mako',
317 self._get_template_context(c), self.request)
317 self._get_template_context(c), self.request)
318 html = formencode.htmlfill.render(
318 html = formencode.htmlfill.render(
319 data,
319 data,
320 defaults=errors.value,
320 defaults=errors.value,
321 errors=errors.error_dict or {},
321 errors=errors.error_dict or {},
322 prefix_error=False,
322 prefix_error=False,
323 encoding="UTF-8",
323 encoding="UTF-8",
324 force_defaults=False
324 force_defaults=False
325 )
325 )
326 return Response(html)
326 return Response(html)
327 except UserCreationError as e:
327 except UserCreationError as e:
328 h.flash(e, 'error')
328 h.flash(e, 'error')
329 except Exception:
329 except Exception:
330 log.exception("Exception updating user")
330 log.exception("Exception updating user")
331 h.flash(_('Error occurred during update of user %s')
331 h.flash(_('Error occurred during update of user %s')
332 % form_result.get('username'), category='error')
332 % form_result.get('username'), category='error')
333 raise HTTPFound(h.route_path('user_edit', user_id=user_id))
333 raise HTTPFound(h.route_path('user_edit', user_id=user_id))
334
334
335 @LoginRequired()
335 @LoginRequired()
336 @HasPermissionAllDecorator('hg.admin')
336 @HasPermissionAllDecorator('hg.admin')
337 @CSRFRequired()
337 @CSRFRequired()
338 @view_config(
338 @view_config(
339 route_name='user_delete', request_method='POST',
339 route_name='user_delete', request_method='POST',
340 renderer='rhodecode:templates/admin/users/user_edit.mako')
340 renderer='rhodecode:templates/admin/users/user_edit.mako')
341 def user_delete(self):
341 def user_delete(self):
342 _ = self.request.translate
342 _ = self.request.translate
343 c = self.load_default_context()
343 c = self.load_default_context()
344 c.user = self.db_user
344 c.user = self.db_user
345
345
346 _repos = c.user.repositories
346 _repos = c.user.repositories
347 _repo_groups = c.user.repository_groups
347 _repo_groups = c.user.repository_groups
348 _user_groups = c.user.user_groups
348 _user_groups = c.user.user_groups
349
349
350 handle_repos = None
350 handle_repos = None
351 handle_repo_groups = None
351 handle_repo_groups = None
352 handle_user_groups = None
352 handle_user_groups = None
353 # dummy call for flash of handle
353 # dummy call for flash of handle
354 set_handle_flash_repos = lambda: None
354 set_handle_flash_repos = lambda: None
355 set_handle_flash_repo_groups = lambda: None
355 set_handle_flash_repo_groups = lambda: None
356 set_handle_flash_user_groups = lambda: None
356 set_handle_flash_user_groups = lambda: None
357
357
358 if _repos and self.request.POST.get('user_repos'):
358 if _repos and self.request.POST.get('user_repos'):
359 do = self.request.POST['user_repos']
359 do = self.request.POST['user_repos']
360 if do == 'detach':
360 if do == 'detach':
361 handle_repos = 'detach'
361 handle_repos = 'detach'
362 set_handle_flash_repos = lambda: h.flash(
362 set_handle_flash_repos = lambda: h.flash(
363 _('Detached %s repositories') % len(_repos),
363 _('Detached %s repositories') % len(_repos),
364 category='success')
364 category='success')
365 elif do == 'delete':
365 elif do == 'delete':
366 handle_repos = 'delete'
366 handle_repos = 'delete'
367 set_handle_flash_repos = lambda: h.flash(
367 set_handle_flash_repos = lambda: h.flash(
368 _('Deleted %s repositories') % len(_repos),
368 _('Deleted %s repositories') % len(_repos),
369 category='success')
369 category='success')
370
370
371 if _repo_groups and self.request.POST.get('user_repo_groups'):
371 if _repo_groups and self.request.POST.get('user_repo_groups'):
372 do = self.request.POST['user_repo_groups']
372 do = self.request.POST['user_repo_groups']
373 if do == 'detach':
373 if do == 'detach':
374 handle_repo_groups = 'detach'
374 handle_repo_groups = 'detach'
375 set_handle_flash_repo_groups = lambda: h.flash(
375 set_handle_flash_repo_groups = lambda: h.flash(
376 _('Detached %s repository groups') % len(_repo_groups),
376 _('Detached %s repository groups') % len(_repo_groups),
377 category='success')
377 category='success')
378 elif do == 'delete':
378 elif do == 'delete':
379 handle_repo_groups = 'delete'
379 handle_repo_groups = 'delete'
380 set_handle_flash_repo_groups = lambda: h.flash(
380 set_handle_flash_repo_groups = lambda: h.flash(
381 _('Deleted %s repository groups') % len(_repo_groups),
381 _('Deleted %s repository groups') % len(_repo_groups),
382 category='success')
382 category='success')
383
383
384 if _user_groups and self.request.POST.get('user_user_groups'):
384 if _user_groups and self.request.POST.get('user_user_groups'):
385 do = self.request.POST['user_user_groups']
385 do = self.request.POST['user_user_groups']
386 if do == 'detach':
386 if do == 'detach':
387 handle_user_groups = 'detach'
387 handle_user_groups = 'detach'
388 set_handle_flash_user_groups = lambda: h.flash(
388 set_handle_flash_user_groups = lambda: h.flash(
389 _('Detached %s user groups') % len(_user_groups),
389 _('Detached %s user groups') % len(_user_groups),
390 category='success')
390 category='success')
391 elif do == 'delete':
391 elif do == 'delete':
392 handle_user_groups = 'delete'
392 handle_user_groups = 'delete'
393 set_handle_flash_user_groups = lambda: h.flash(
393 set_handle_flash_user_groups = lambda: h.flash(
394 _('Deleted %s user groups') % len(_user_groups),
394 _('Deleted %s user groups') % len(_user_groups),
395 category='success')
395 category='success')
396
396
397 old_values = c.user.get_api_data()
397 old_values = c.user.get_api_data()
398 try:
398 try:
399 UserModel().delete(c.user, handle_repos=handle_repos,
399 UserModel().delete(c.user, handle_repos=handle_repos,
400 handle_repo_groups=handle_repo_groups,
400 handle_repo_groups=handle_repo_groups,
401 handle_user_groups=handle_user_groups)
401 handle_user_groups=handle_user_groups)
402
402
403 audit_logger.store_web(
403 audit_logger.store_web(
404 'user.delete', action_data={'old_data': old_values},
404 'user.delete', action_data={'old_data': old_values},
405 user=c.rhodecode_user)
405 user=c.rhodecode_user)
406
406
407 Session().commit()
407 Session().commit()
408 set_handle_flash_repos()
408 set_handle_flash_repos()
409 set_handle_flash_repo_groups()
409 set_handle_flash_repo_groups()
410 set_handle_flash_user_groups()
410 set_handle_flash_user_groups()
411 h.flash(_('Successfully deleted user'), category='success')
411 h.flash(_('Successfully deleted user'), category='success')
412 except (UserOwnsReposException, UserOwnsRepoGroupsException,
412 except (UserOwnsReposException, UserOwnsRepoGroupsException,
413 UserOwnsUserGroupsException, DefaultUserException) as e:
413 UserOwnsUserGroupsException, DefaultUserException) as e:
414 h.flash(e, category='warning')
414 h.flash(e, category='warning')
415 except Exception:
415 except Exception:
416 log.exception("Exception during deletion of user")
416 log.exception("Exception during deletion of user")
417 h.flash(_('An error occurred during deletion of user'),
417 h.flash(_('An error occurred during deletion of user'),
418 category='error')
418 category='error')
419 raise HTTPFound(h.route_path('users'))
419 raise HTTPFound(h.route_path('users'))
420
420
421 @LoginRequired()
421 @LoginRequired()
422 @HasPermissionAllDecorator('hg.admin')
422 @HasPermissionAllDecorator('hg.admin')
423 @view_config(
423 @view_config(
424 route_name='user_edit', request_method='GET',
424 route_name='user_edit', request_method='GET',
425 renderer='rhodecode:templates/admin/users/user_edit.mako')
425 renderer='rhodecode:templates/admin/users/user_edit.mako')
426 def user_edit(self):
426 def user_edit(self):
427 _ = self.request.translate
427 _ = self.request.translate
428 c = self.load_default_context()
428 c = self.load_default_context()
429 c.user = self.db_user
429 c.user = self.db_user
430
430
431 c.active = 'profile'
431 c.active = 'profile'
432 c.extern_type = c.user.extern_type
432 c.extern_type = c.user.extern_type
433 c.extern_name = c.user.extern_name
433 c.extern_name = c.user.extern_name
434 c.perm_user = c.user.AuthUser(ip_addr=self.request.remote_addr)
434 c.perm_user = c.user.AuthUser(ip_addr=self.request.remote_addr)
435
435
436 defaults = c.user.get_dict()
436 defaults = c.user.get_dict()
437 defaults.update({'language': c.user.user_data.get('language')})
437 defaults.update({'language': c.user.user_data.get('language')})
438
438
439 data = render(
439 data = render(
440 'rhodecode:templates/admin/users/user_edit.mako',
440 'rhodecode:templates/admin/users/user_edit.mako',
441 self._get_template_context(c), self.request)
441 self._get_template_context(c), self.request)
442 html = formencode.htmlfill.render(
442 html = formencode.htmlfill.render(
443 data,
443 data,
444 defaults=defaults,
444 defaults=defaults,
445 encoding="UTF-8",
445 encoding="UTF-8",
446 force_defaults=False
446 force_defaults=False
447 )
447 )
448 return Response(html)
448 return Response(html)
449
449
450 @LoginRequired()
450 @LoginRequired()
451 @HasPermissionAllDecorator('hg.admin')
451 @HasPermissionAllDecorator('hg.admin')
452 @view_config(
452 @view_config(
453 route_name='user_edit_advanced', request_method='GET',
453 route_name='user_edit_advanced', request_method='GET',
454 renderer='rhodecode:templates/admin/users/user_edit.mako')
454 renderer='rhodecode:templates/admin/users/user_edit.mako')
455 def user_edit_advanced(self):
455 def user_edit_advanced(self):
456 _ = self.request.translate
456 _ = self.request.translate
457 c = self.load_default_context()
457 c = self.load_default_context()
458
458
459 user_id = self.db_user_id
459 user_id = self.db_user_id
460 c.user = self.db_user
460 c.user = self.db_user
461
461
462 c.active = 'advanced'
462 c.active = 'advanced'
463 c.personal_repo_group = RepoGroup.get_user_personal_repo_group(user_id)
463 c.personal_repo_group = RepoGroup.get_user_personal_repo_group(user_id)
464 c.personal_repo_group_name = RepoGroupModel()\
464 c.personal_repo_group_name = RepoGroupModel()\
465 .get_personal_group_name(c.user)
465 .get_personal_group_name(c.user)
466
466
467 c.user_to_review_rules = sorted(
467 c.user_to_review_rules = sorted(
468 (x.user for x in c.user.user_review_rules),
468 (x.user for x in c.user.user_review_rules),
469 key=lambda u: u.username.lower())
469 key=lambda u: u.username.lower())
470
470
471 c.first_admin = User.get_first_super_admin()
471 c.first_admin = User.get_first_super_admin()
472 defaults = c.user.get_dict()
472 defaults = c.user.get_dict()
473
473
474 # Interim workaround if the user participated on any pull requests as a
474 # Interim workaround if the user participated on any pull requests as a
475 # reviewer.
475 # reviewer.
476 has_review = len(c.user.reviewer_pull_requests)
476 has_review = len(c.user.reviewer_pull_requests)
477 c.can_delete_user = not has_review
477 c.can_delete_user = not has_review
478 c.can_delete_user_message = ''
478 c.can_delete_user_message = ''
479 inactive_link = h.link_to(
479 inactive_link = h.link_to(
480 'inactive', h.route_path('user_edit', user_id=user_id, _anchor='active'))
480 'inactive', h.route_path('user_edit', user_id=user_id, _anchor='active'))
481 if has_review == 1:
481 if has_review == 1:
482 c.can_delete_user_message = h.literal(_(
482 c.can_delete_user_message = h.literal(_(
483 'The user participates as reviewer in {} pull request and '
483 'The user participates as reviewer in {} pull request and '
484 'cannot be deleted. \nYou can set the user to '
484 'cannot be deleted. \nYou can set the user to '
485 '"{}" instead of deleting it.').format(
485 '"{}" instead of deleting it.').format(
486 has_review, inactive_link))
486 has_review, inactive_link))
487 elif has_review:
487 elif has_review:
488 c.can_delete_user_message = h.literal(_(
488 c.can_delete_user_message = h.literal(_(
489 'The user participates as reviewer in {} pull requests and '
489 'The user participates as reviewer in {} pull requests and '
490 'cannot be deleted. \nYou can set the user to '
490 'cannot be deleted. \nYou can set the user to '
491 '"{}" instead of deleting it.').format(
491 '"{}" instead of deleting it.').format(
492 has_review, inactive_link))
492 has_review, inactive_link))
493
493
494 data = render(
494 data = render(
495 'rhodecode:templates/admin/users/user_edit.mako',
495 'rhodecode:templates/admin/users/user_edit.mako',
496 self._get_template_context(c), self.request)
496 self._get_template_context(c), self.request)
497 html = formencode.htmlfill.render(
497 html = formencode.htmlfill.render(
498 data,
498 data,
499 defaults=defaults,
499 defaults=defaults,
500 encoding="UTF-8",
500 encoding="UTF-8",
501 force_defaults=False
501 force_defaults=False
502 )
502 )
503 return Response(html)
503 return Response(html)
504
504
505 @LoginRequired()
505 @LoginRequired()
506 @HasPermissionAllDecorator('hg.admin')
506 @HasPermissionAllDecorator('hg.admin')
507 @view_config(
507 @view_config(
508 route_name='user_edit_global_perms', request_method='GET',
508 route_name='user_edit_global_perms', request_method='GET',
509 renderer='rhodecode:templates/admin/users/user_edit.mako')
509 renderer='rhodecode:templates/admin/users/user_edit.mako')
510 def user_edit_global_perms(self):
510 def user_edit_global_perms(self):
511 _ = self.request.translate
511 _ = self.request.translate
512 c = self.load_default_context()
512 c = self.load_default_context()
513 c.user = self.db_user
513 c.user = self.db_user
514
514
515 c.active = 'global_perms'
515 c.active = 'global_perms'
516
516
517 c.default_user = User.get_default_user()
517 c.default_user = User.get_default_user()
518 defaults = c.user.get_dict()
518 defaults = c.user.get_dict()
519 defaults.update(c.default_user.get_default_perms(suffix='_inherited'))
519 defaults.update(c.default_user.get_default_perms(suffix='_inherited'))
520 defaults.update(c.default_user.get_default_perms())
520 defaults.update(c.default_user.get_default_perms())
521 defaults.update(c.user.get_default_perms())
521 defaults.update(c.user.get_default_perms())
522
522
523 data = render(
523 data = render(
524 'rhodecode:templates/admin/users/user_edit.mako',
524 'rhodecode:templates/admin/users/user_edit.mako',
525 self._get_template_context(c), self.request)
525 self._get_template_context(c), self.request)
526 html = formencode.htmlfill.render(
526 html = formencode.htmlfill.render(
527 data,
527 data,
528 defaults=defaults,
528 defaults=defaults,
529 encoding="UTF-8",
529 encoding="UTF-8",
530 force_defaults=False
530 force_defaults=False
531 )
531 )
532 return Response(html)
532 return Response(html)
533
533
534 @LoginRequired()
534 @LoginRequired()
535 @HasPermissionAllDecorator('hg.admin')
535 @HasPermissionAllDecorator('hg.admin')
536 @CSRFRequired()
536 @CSRFRequired()
537 @view_config(
537 @view_config(
538 route_name='user_edit_global_perms_update', request_method='POST',
538 route_name='user_edit_global_perms_update', request_method='POST',
539 renderer='rhodecode:templates/admin/users/user_edit.mako')
539 renderer='rhodecode:templates/admin/users/user_edit.mako')
540 def user_edit_global_perms_update(self):
540 def user_edit_global_perms_update(self):
541 _ = self.request.translate
541 _ = self.request.translate
542 c = self.load_default_context()
542 c = self.load_default_context()
543
543
544 user_id = self.db_user_id
544 user_id = self.db_user_id
545 c.user = self.db_user
545 c.user = self.db_user
546
546
547 c.active = 'global_perms'
547 c.active = 'global_perms'
548 try:
548 try:
549 # first stage that verifies the checkbox
549 # first stage that verifies the checkbox
550 _form = UserIndividualPermissionsForm(self.request.translate)
550 _form = UserIndividualPermissionsForm(self.request.translate)
551 form_result = _form.to_python(dict(self.request.POST))
551 form_result = _form.to_python(dict(self.request.POST))
552 inherit_perms = form_result['inherit_default_permissions']
552 inherit_perms = form_result['inherit_default_permissions']
553 c.user.inherit_default_permissions = inherit_perms
553 c.user.inherit_default_permissions = inherit_perms
554 Session().add(c.user)
554 Session().add(c.user)
555
555
556 if not inherit_perms:
556 if not inherit_perms:
557 # only update the individual ones if we un check the flag
557 # only update the individual ones if we un check the flag
558 _form = UserPermissionsForm(
558 _form = UserPermissionsForm(
559 self.request.translate,
559 self.request.translate,
560 [x[0] for x in c.repo_create_choices],
560 [x[0] for x in c.repo_create_choices],
561 [x[0] for x in c.repo_create_on_write_choices],
561 [x[0] for x in c.repo_create_on_write_choices],
562 [x[0] for x in c.repo_group_create_choices],
562 [x[0] for x in c.repo_group_create_choices],
563 [x[0] for x in c.user_group_create_choices],
563 [x[0] for x in c.user_group_create_choices],
564 [x[0] for x in c.fork_choices],
564 [x[0] for x in c.fork_choices],
565 [x[0] for x in c.inherit_default_permission_choices])()
565 [x[0] for x in c.inherit_default_permission_choices])()
566
566
567 form_result = _form.to_python(dict(self.request.POST))
567 form_result = _form.to_python(dict(self.request.POST))
568 form_result.update({'perm_user_id': c.user.user_id})
568 form_result.update({'perm_user_id': c.user.user_id})
569
569
570 PermissionModel().update_user_permissions(form_result)
570 PermissionModel().update_user_permissions(form_result)
571
571
572 # TODO(marcink): implement global permissions
572 # TODO(marcink): implement global permissions
573 # audit_log.store_web('user.edit.permissions')
573 # audit_log.store_web('user.edit.permissions')
574
574
575 Session().commit()
575 Session().commit()
576 h.flash(_('User global permissions updated successfully'),
576 h.flash(_('User global permissions updated successfully'),
577 category='success')
577 category='success')
578
578
579 except formencode.Invalid as errors:
579 except formencode.Invalid as errors:
580 data = render(
580 data = render(
581 'rhodecode:templates/admin/users/user_edit.mako',
581 'rhodecode:templates/admin/users/user_edit.mako',
582 self._get_template_context(c), self.request)
582 self._get_template_context(c), self.request)
583 html = formencode.htmlfill.render(
583 html = formencode.htmlfill.render(
584 data,
584 data,
585 defaults=errors.value,
585 defaults=errors.value,
586 errors=errors.error_dict or {},
586 errors=errors.error_dict or {},
587 prefix_error=False,
587 prefix_error=False,
588 encoding="UTF-8",
588 encoding="UTF-8",
589 force_defaults=False
589 force_defaults=False
590 )
590 )
591 return Response(html)
591 return Response(html)
592 except Exception:
592 except Exception:
593 log.exception("Exception during permissions saving")
593 log.exception("Exception during permissions saving")
594 h.flash(_('An error occurred during permissions saving'),
594 h.flash(_('An error occurred during permissions saving'),
595 category='error')
595 category='error')
596 raise HTTPFound(h.route_path('user_edit_global_perms', user_id=user_id))
596 raise HTTPFound(h.route_path('user_edit_global_perms', user_id=user_id))
597
597
598 @LoginRequired()
598 @LoginRequired()
599 @HasPermissionAllDecorator('hg.admin')
599 @HasPermissionAllDecorator('hg.admin')
600 @CSRFRequired()
600 @CSRFRequired()
601 @view_config(
601 @view_config(
602 route_name='user_force_password_reset', request_method='POST',
602 route_name='user_force_password_reset', request_method='POST',
603 renderer='rhodecode:templates/admin/users/user_edit.mako')
603 renderer='rhodecode:templates/admin/users/user_edit.mako')
604 def user_force_password_reset(self):
604 def user_force_password_reset(self):
605 """
605 """
606 toggle reset password flag for this user
606 toggle reset password flag for this user
607 """
607 """
608 _ = self.request.translate
608 _ = self.request.translate
609 c = self.load_default_context()
609 c = self.load_default_context()
610
610
611 user_id = self.db_user_id
611 user_id = self.db_user_id
612 c.user = self.db_user
612 c.user = self.db_user
613
613
614 try:
614 try:
615 old_value = c.user.user_data.get('force_password_change')
615 old_value = c.user.user_data.get('force_password_change')
616 c.user.update_userdata(force_password_change=not old_value)
616 c.user.update_userdata(force_password_change=not old_value)
617
617
618 if old_value:
618 if old_value:
619 msg = _('Force password change disabled for user')
619 msg = _('Force password change disabled for user')
620 audit_logger.store_web(
620 audit_logger.store_web(
621 'user.edit.password_reset.disabled',
621 'user.edit.password_reset.disabled',
622 user=c.rhodecode_user)
622 user=c.rhodecode_user)
623 else:
623 else:
624 msg = _('Force password change enabled for user')
624 msg = _('Force password change enabled for user')
625 audit_logger.store_web(
625 audit_logger.store_web(
626 'user.edit.password_reset.enabled',
626 'user.edit.password_reset.enabled',
627 user=c.rhodecode_user)
627 user=c.rhodecode_user)
628
628
629 Session().commit()
629 Session().commit()
630 h.flash(msg, category='success')
630 h.flash(msg, category='success')
631 except Exception:
631 except Exception:
632 log.exception("Exception during password reset for user")
632 log.exception("Exception during password reset for user")
633 h.flash(_('An error occurred during password reset for user'),
633 h.flash(_('An error occurred during password reset for user'),
634 category='error')
634 category='error')
635
635
636 raise HTTPFound(h.route_path('user_edit_advanced', user_id=user_id))
636 raise HTTPFound(h.route_path('user_edit_advanced', user_id=user_id))
637
637
638 @LoginRequired()
638 @LoginRequired()
639 @HasPermissionAllDecorator('hg.admin')
639 @HasPermissionAllDecorator('hg.admin')
640 @CSRFRequired()
640 @CSRFRequired()
641 @view_config(
641 @view_config(
642 route_name='user_create_personal_repo_group', request_method='POST',
642 route_name='user_create_personal_repo_group', request_method='POST',
643 renderer='rhodecode:templates/admin/users/user_edit.mako')
643 renderer='rhodecode:templates/admin/users/user_edit.mako')
644 def user_create_personal_repo_group(self):
644 def user_create_personal_repo_group(self):
645 """
645 """
646 Create personal repository group for this user
646 Create personal repository group for this user
647 """
647 """
648 from rhodecode.model.repo_group import RepoGroupModel
648 from rhodecode.model.repo_group import RepoGroupModel
649
649
650 _ = self.request.translate
650 _ = self.request.translate
651 c = self.load_default_context()
651 c = self.load_default_context()
652
652
653 user_id = self.db_user_id
653 user_id = self.db_user_id
654 c.user = self.db_user
654 c.user = self.db_user
655
655
656 personal_repo_group = RepoGroup.get_user_personal_repo_group(
656 personal_repo_group = RepoGroup.get_user_personal_repo_group(
657 c.user.user_id)
657 c.user.user_id)
658 if personal_repo_group:
658 if personal_repo_group:
659 raise HTTPFound(h.route_path('user_edit_advanced', user_id=user_id))
659 raise HTTPFound(h.route_path('user_edit_advanced', user_id=user_id))
660
660
661 personal_repo_group_name = RepoGroupModel().get_personal_group_name(
661 personal_repo_group_name = RepoGroupModel().get_personal_group_name(
662 c.user)
662 c.user)
663 named_personal_group = RepoGroup.get_by_group_name(
663 named_personal_group = RepoGroup.get_by_group_name(
664 personal_repo_group_name)
664 personal_repo_group_name)
665 try:
665 try:
666
666
667 if named_personal_group and named_personal_group.user_id == c.user.user_id:
667 if named_personal_group and named_personal_group.user_id == c.user.user_id:
668 # migrate the same named group, and mark it as personal
668 # migrate the same named group, and mark it as personal
669 named_personal_group.personal = True
669 named_personal_group.personal = True
670 Session().add(named_personal_group)
670 Session().add(named_personal_group)
671 Session().commit()
671 Session().commit()
672 msg = _('Linked repository group `%s` as personal' % (
672 msg = _('Linked repository group `%s` as personal' % (
673 personal_repo_group_name,))
673 personal_repo_group_name,))
674 h.flash(msg, category='success')
674 h.flash(msg, category='success')
675 elif not named_personal_group:
675 elif not named_personal_group:
676 RepoGroupModel().create_personal_repo_group(c.user)
676 RepoGroupModel().create_personal_repo_group(c.user)
677
677
678 msg = _('Created repository group `%s`' % (
678 msg = _('Created repository group `%s`' % (
679 personal_repo_group_name,))
679 personal_repo_group_name,))
680 h.flash(msg, category='success')
680 h.flash(msg, category='success')
681 else:
681 else:
682 msg = _('Repository group `%s` is already taken' % (
682 msg = _('Repository group `%s` is already taken' % (
683 personal_repo_group_name,))
683 personal_repo_group_name,))
684 h.flash(msg, category='warning')
684 h.flash(msg, category='warning')
685 except Exception:
685 except Exception:
686 log.exception("Exception during repository group creation")
686 log.exception("Exception during repository group creation")
687 msg = _(
687 msg = _(
688 'An error occurred during repository group creation for user')
688 'An error occurred during repository group creation for user')
689 h.flash(msg, category='error')
689 h.flash(msg, category='error')
690 Session().rollback()
690 Session().rollback()
691
691
692 raise HTTPFound(h.route_path('user_edit_advanced', user_id=user_id))
692 raise HTTPFound(h.route_path('user_edit_advanced', user_id=user_id))
693
693
694 @LoginRequired()
694 @LoginRequired()
695 @HasPermissionAllDecorator('hg.admin')
695 @HasPermissionAllDecorator('hg.admin')
696 @view_config(
696 @view_config(
697 route_name='edit_user_auth_tokens', request_method='GET',
697 route_name='edit_user_auth_tokens', request_method='GET',
698 renderer='rhodecode:templates/admin/users/user_edit.mako')
698 renderer='rhodecode:templates/admin/users/user_edit.mako')
699 def auth_tokens(self):
699 def auth_tokens(self):
700 _ = self.request.translate
700 _ = self.request.translate
701 c = self.load_default_context()
701 c = self.load_default_context()
702 c.user = self.db_user
702 c.user = self.db_user
703
703
704 c.active = 'auth_tokens'
704 c.active = 'auth_tokens'
705
705
706 c.lifetime_values = AuthTokenModel.get_lifetime_values(translator=_)
706 c.lifetime_values = AuthTokenModel.get_lifetime_values(translator=_)
707 c.role_values = [
707 c.role_values = [
708 (x, AuthTokenModel.cls._get_role_name(x))
708 (x, AuthTokenModel.cls._get_role_name(x))
709 for x in AuthTokenModel.cls.ROLES]
709 for x in AuthTokenModel.cls.ROLES]
710 c.role_options = [(c.role_values, _("Role"))]
710 c.role_options = [(c.role_values, _("Role"))]
711 c.user_auth_tokens = AuthTokenModel().get_auth_tokens(
711 c.user_auth_tokens = AuthTokenModel().get_auth_tokens(
712 c.user.user_id, show_expired=True)
712 c.user.user_id, show_expired=True)
713 c.role_vcs = AuthTokenModel.cls.ROLE_VCS
713 c.role_vcs = AuthTokenModel.cls.ROLE_VCS
714 return self._get_template_context(c)
714 return self._get_template_context(c)
715
715
716 def maybe_attach_token_scope(self, token):
716 def maybe_attach_token_scope(self, token):
717 # implemented in EE edition
717 # implemented in EE edition
718 pass
718 pass
719
719
720 @LoginRequired()
720 @LoginRequired()
721 @HasPermissionAllDecorator('hg.admin')
721 @HasPermissionAllDecorator('hg.admin')
722 @CSRFRequired()
722 @CSRFRequired()
723 @view_config(
723 @view_config(
724 route_name='edit_user_auth_tokens_add', request_method='POST')
724 route_name='edit_user_auth_tokens_add', request_method='POST')
725 def auth_tokens_add(self):
725 def auth_tokens_add(self):
726 _ = self.request.translate
726 _ = self.request.translate
727 c = self.load_default_context()
727 c = self.load_default_context()
728
728
729 user_id = self.db_user_id
729 user_id = self.db_user_id
730 c.user = self.db_user
730 c.user = self.db_user
731
731
732 user_data = c.user.get_api_data()
732 user_data = c.user.get_api_data()
733 lifetime = safe_int(self.request.POST.get('lifetime'), -1)
733 lifetime = safe_int(self.request.POST.get('lifetime'), -1)
734 description = self.request.POST.get('description')
734 description = self.request.POST.get('description')
735 role = self.request.POST.get('role')
735 role = self.request.POST.get('role')
736
736
737 token = UserModel().add_auth_token(
737 token = UserModel().add_auth_token(
738 user=c.user.user_id,
738 user=c.user.user_id,
739 lifetime_minutes=lifetime, role=role, description=description,
739 lifetime_minutes=lifetime, role=role, description=description,
740 scope_callback=self.maybe_attach_token_scope)
740 scope_callback=self.maybe_attach_token_scope)
741 token_data = token.get_api_data()
741 token_data = token.get_api_data()
742
742
743 audit_logger.store_web(
743 audit_logger.store_web(
744 'user.edit.token.add', action_data={
744 'user.edit.token.add', action_data={
745 'data': {'token': token_data, 'user': user_data}},
745 'data': {'token': token_data, 'user': user_data}},
746 user=self._rhodecode_user, )
746 user=self._rhodecode_user, )
747 Session().commit()
747 Session().commit()
748
748
749 h.flash(_("Auth token successfully created"), category='success')
749 h.flash(_("Auth token successfully created"), category='success')
750 return HTTPFound(h.route_path('edit_user_auth_tokens', user_id=user_id))
750 return HTTPFound(h.route_path('edit_user_auth_tokens', user_id=user_id))
751
751
752 @LoginRequired()
752 @LoginRequired()
753 @HasPermissionAllDecorator('hg.admin')
753 @HasPermissionAllDecorator('hg.admin')
754 @CSRFRequired()
754 @CSRFRequired()
755 @view_config(
755 @view_config(
756 route_name='edit_user_auth_tokens_delete', request_method='POST')
756 route_name='edit_user_auth_tokens_delete', request_method='POST')
757 def auth_tokens_delete(self):
757 def auth_tokens_delete(self):
758 _ = self.request.translate
758 _ = self.request.translate
759 c = self.load_default_context()
759 c = self.load_default_context()
760
760
761 user_id = self.db_user_id
761 user_id = self.db_user_id
762 c.user = self.db_user
762 c.user = self.db_user
763
763
764 user_data = c.user.get_api_data()
764 user_data = c.user.get_api_data()
765
765
766 del_auth_token = self.request.POST.get('del_auth_token')
766 del_auth_token = self.request.POST.get('del_auth_token')
767
767
768 if del_auth_token:
768 if del_auth_token:
769 token = UserApiKeys.get_or_404(del_auth_token)
769 token = UserApiKeys.get_or_404(del_auth_token)
770 token_data = token.get_api_data()
770 token_data = token.get_api_data()
771
771
772 AuthTokenModel().delete(del_auth_token, c.user.user_id)
772 AuthTokenModel().delete(del_auth_token, c.user.user_id)
773 audit_logger.store_web(
773 audit_logger.store_web(
774 'user.edit.token.delete', action_data={
774 'user.edit.token.delete', action_data={
775 'data': {'token': token_data, 'user': user_data}},
775 'data': {'token': token_data, 'user': user_data}},
776 user=self._rhodecode_user,)
776 user=self._rhodecode_user,)
777 Session().commit()
777 Session().commit()
778 h.flash(_("Auth token successfully deleted"), category='success')
778 h.flash(_("Auth token successfully deleted"), category='success')
779
779
780 return HTTPFound(h.route_path('edit_user_auth_tokens', user_id=user_id))
780 return HTTPFound(h.route_path('edit_user_auth_tokens', user_id=user_id))
781
781
782 @LoginRequired()
782 @LoginRequired()
783 @HasPermissionAllDecorator('hg.admin')
783 @HasPermissionAllDecorator('hg.admin')
784 @view_config(
784 @view_config(
785 route_name='edit_user_ssh_keys', request_method='GET',
785 route_name='edit_user_ssh_keys', request_method='GET',
786 renderer='rhodecode:templates/admin/users/user_edit.mako')
786 renderer='rhodecode:templates/admin/users/user_edit.mako')
787 def ssh_keys(self):
787 def ssh_keys(self):
788 _ = self.request.translate
788 _ = self.request.translate
789 c = self.load_default_context()
789 c = self.load_default_context()
790 c.user = self.db_user
790 c.user = self.db_user
791
791
792 c.active = 'ssh_keys'
792 c.active = 'ssh_keys'
793 c.default_key = self.request.GET.get('default_key')
793 c.default_key = self.request.GET.get('default_key')
794 c.user_ssh_keys = SshKeyModel().get_ssh_keys(c.user.user_id)
794 c.user_ssh_keys = SshKeyModel().get_ssh_keys(c.user.user_id)
795 return self._get_template_context(c)
795 return self._get_template_context(c)
796
796
797 @LoginRequired()
797 @LoginRequired()
798 @HasPermissionAllDecorator('hg.admin')
798 @HasPermissionAllDecorator('hg.admin')
799 @view_config(
799 @view_config(
800 route_name='edit_user_ssh_keys_generate_keypair', request_method='GET',
800 route_name='edit_user_ssh_keys_generate_keypair', request_method='GET',
801 renderer='rhodecode:templates/admin/users/user_edit.mako')
801 renderer='rhodecode:templates/admin/users/user_edit.mako')
802 def ssh_keys_generate_keypair(self):
802 def ssh_keys_generate_keypair(self):
803 _ = self.request.translate
803 _ = self.request.translate
804 c = self.load_default_context()
804 c = self.load_default_context()
805
805
806 c.user = self.db_user
806 c.user = self.db_user
807
807
808 c.active = 'ssh_keys_generate'
808 c.active = 'ssh_keys_generate'
809 comment = 'RhodeCode-SSH {}'.format(c.user.email or '')
809 comment = 'RhodeCode-SSH {}'.format(c.user.email or '')
810 c.private, c.public = SshKeyModel().generate_keypair(comment=comment)
810 c.private, c.public = SshKeyModel().generate_keypair(comment=comment)
811
811
812 return self._get_template_context(c)
812 return self._get_template_context(c)
813
813
814 @LoginRequired()
814 @LoginRequired()
815 @HasPermissionAllDecorator('hg.admin')
815 @HasPermissionAllDecorator('hg.admin')
816 @CSRFRequired()
816 @CSRFRequired()
817 @view_config(
817 @view_config(
818 route_name='edit_user_ssh_keys_add', request_method='POST')
818 route_name='edit_user_ssh_keys_add', request_method='POST')
819 def ssh_keys_add(self):
819 def ssh_keys_add(self):
820 _ = self.request.translate
820 _ = self.request.translate
821 c = self.load_default_context()
821 c = self.load_default_context()
822
822
823 user_id = self.db_user_id
823 user_id = self.db_user_id
824 c.user = self.db_user
824 c.user = self.db_user
825
825
826 user_data = c.user.get_api_data()
826 user_data = c.user.get_api_data()
827 key_data = self.request.POST.get('key_data')
827 key_data = self.request.POST.get('key_data')
828 description = self.request.POST.get('description')
828 description = self.request.POST.get('description')
829
829
830 fingerprint = 'unknown'
830 fingerprint = 'unknown'
831 try:
831 try:
832 if not key_data:
832 if not key_data:
833 raise ValueError('Please add a valid public key')
833 raise ValueError('Please add a valid public key')
834
834
835 key = SshKeyModel().parse_key(key_data.strip())
835 key = SshKeyModel().parse_key(key_data.strip())
836 fingerprint = key.hash_md5()
836 fingerprint = key.hash_md5()
837
837
838 ssh_key = SshKeyModel().create(
838 ssh_key = SshKeyModel().create(
839 c.user.user_id, fingerprint, key.keydata, description)
839 c.user.user_id, fingerprint, key.keydata, description)
840 ssh_key_data = ssh_key.get_api_data()
840 ssh_key_data = ssh_key.get_api_data()
841
841
842 audit_logger.store_web(
842 audit_logger.store_web(
843 'user.edit.ssh_key.add', action_data={
843 'user.edit.ssh_key.add', action_data={
844 'data': {'ssh_key': ssh_key_data, 'user': user_data}},
844 'data': {'ssh_key': ssh_key_data, 'user': user_data}},
845 user=self._rhodecode_user, )
845 user=self._rhodecode_user, )
846 Session().commit()
846 Session().commit()
847
847
848 # Trigger an event on change of keys.
848 # Trigger an event on change of keys.
849 trigger(SshKeyFileChangeEvent(), self.request.registry)
849 trigger(SshKeyFileChangeEvent(), self.request.registry)
850
850
851 h.flash(_("Ssh Key successfully created"), category='success')
851 h.flash(_("Ssh Key successfully created"), category='success')
852
852
853 except IntegrityError:
853 except IntegrityError:
854 log.exception("Exception during ssh key saving")
854 log.exception("Exception during ssh key saving")
855 err = 'Such key with fingerprint `{}` already exists, ' \
855 err = 'Such key with fingerprint `{}` already exists, ' \
856 'please use a different one'.format(fingerprint)
856 'please use a different one'.format(fingerprint)
857 h.flash(_('An error occurred during ssh key saving: {}').format(err),
857 h.flash(_('An error occurred during ssh key saving: {}').format(err),
858 category='error')
858 category='error')
859 except Exception as e:
859 except Exception as e:
860 log.exception("Exception during ssh key saving")
860 log.exception("Exception during ssh key saving")
861 h.flash(_('An error occurred during ssh key saving: {}').format(e),
861 h.flash(_('An error occurred during ssh key saving: {}').format(e),
862 category='error')
862 category='error')
863
863
864 return HTTPFound(
864 return HTTPFound(
865 h.route_path('edit_user_ssh_keys', user_id=user_id))
865 h.route_path('edit_user_ssh_keys', user_id=user_id))
866
866
867 @LoginRequired()
867 @LoginRequired()
868 @HasPermissionAllDecorator('hg.admin')
868 @HasPermissionAllDecorator('hg.admin')
869 @CSRFRequired()
869 @CSRFRequired()
870 @view_config(
870 @view_config(
871 route_name='edit_user_ssh_keys_delete', request_method='POST')
871 route_name='edit_user_ssh_keys_delete', request_method='POST')
872 def ssh_keys_delete(self):
872 def ssh_keys_delete(self):
873 _ = self.request.translate
873 _ = self.request.translate
874 c = self.load_default_context()
874 c = self.load_default_context()
875
875
876 user_id = self.db_user_id
876 user_id = self.db_user_id
877 c.user = self.db_user
877 c.user = self.db_user
878
878
879 user_data = c.user.get_api_data()
879 user_data = c.user.get_api_data()
880
880
881 del_ssh_key = self.request.POST.get('del_ssh_key')
881 del_ssh_key = self.request.POST.get('del_ssh_key')
882
882
883 if del_ssh_key:
883 if del_ssh_key:
884 ssh_key = UserSshKeys.get_or_404(del_ssh_key)
884 ssh_key = UserSshKeys.get_or_404(del_ssh_key)
885 ssh_key_data = ssh_key.get_api_data()
885 ssh_key_data = ssh_key.get_api_data()
886
886
887 SshKeyModel().delete(del_ssh_key, c.user.user_id)
887 SshKeyModel().delete(del_ssh_key, c.user.user_id)
888 audit_logger.store_web(
888 audit_logger.store_web(
889 'user.edit.ssh_key.delete', action_data={
889 'user.edit.ssh_key.delete', action_data={
890 'data': {'ssh_key': ssh_key_data, 'user': user_data}},
890 'data': {'ssh_key': ssh_key_data, 'user': user_data}},
891 user=self._rhodecode_user,)
891 user=self._rhodecode_user,)
892 Session().commit()
892 Session().commit()
893 # Trigger an event on change of keys.
893 # Trigger an event on change of keys.
894 trigger(SshKeyFileChangeEvent(), self.request.registry)
894 trigger(SshKeyFileChangeEvent(), self.request.registry)
895 h.flash(_("Ssh key successfully deleted"), category='success')
895 h.flash(_("Ssh key successfully deleted"), category='success')
896
896
897 return HTTPFound(h.route_path('edit_user_ssh_keys', user_id=user_id))
897 return HTTPFound(h.route_path('edit_user_ssh_keys', user_id=user_id))
898
898
899 @LoginRequired()
899 @LoginRequired()
900 @HasPermissionAllDecorator('hg.admin')
900 @HasPermissionAllDecorator('hg.admin')
901 @view_config(
901 @view_config(
902 route_name='edit_user_emails', request_method='GET',
902 route_name='edit_user_emails', request_method='GET',
903 renderer='rhodecode:templates/admin/users/user_edit.mako')
903 renderer='rhodecode:templates/admin/users/user_edit.mako')
904 def emails(self):
904 def emails(self):
905 _ = self.request.translate
905 _ = self.request.translate
906 c = self.load_default_context()
906 c = self.load_default_context()
907 c.user = self.db_user
907 c.user = self.db_user
908
908
909 c.active = 'emails'
909 c.active = 'emails'
910 c.user_email_map = UserEmailMap.query() \
910 c.user_email_map = UserEmailMap.query() \
911 .filter(UserEmailMap.user == c.user).all()
911 .filter(UserEmailMap.user == c.user).all()
912
912
913 return self._get_template_context(c)
913 return self._get_template_context(c)
914
914
915 @LoginRequired()
915 @LoginRequired()
916 @HasPermissionAllDecorator('hg.admin')
916 @HasPermissionAllDecorator('hg.admin')
917 @CSRFRequired()
917 @CSRFRequired()
918 @view_config(
918 @view_config(
919 route_name='edit_user_emails_add', request_method='POST')
919 route_name='edit_user_emails_add', request_method='POST')
920 def emails_add(self):
920 def emails_add(self):
921 _ = self.request.translate
921 _ = self.request.translate
922 c = self.load_default_context()
922 c = self.load_default_context()
923
923
924 user_id = self.db_user_id
924 user_id = self.db_user_id
925 c.user = self.db_user
925 c.user = self.db_user
926
926
927 email = self.request.POST.get('new_email')
927 email = self.request.POST.get('new_email')
928 user_data = c.user.get_api_data()
928 user_data = c.user.get_api_data()
929 try:
929 try:
930
930
931 form = UserExtraEmailForm(self.request.translate)()
931 form = UserExtraEmailForm(self.request.translate)()
932 data = form.to_python({'email': email})
932 data = form.to_python({'email': email})
933 email = data['email']
933 email = data['email']
934
934
935 UserModel().add_extra_email(c.user.user_id, email)
935 UserModel().add_extra_email(c.user.user_id, email)
936 audit_logger.store_web(
936 audit_logger.store_web(
937 'user.edit.email.add',
937 'user.edit.email.add',
938 action_data={'email': email, 'user': user_data},
938 action_data={'email': email, 'user': user_data},
939 user=self._rhodecode_user)
939 user=self._rhodecode_user)
940 Session().commit()
940 Session().commit()
941 h.flash(_("Added new email address `%s` for user account") % email,
941 h.flash(_("Added new email address `%s` for user account") % email,
942 category='success')
942 category='success')
943 except formencode.Invalid as error:
943 except formencode.Invalid as error:
944 h.flash(h.escape(error.error_dict['email']), category='error')
944 h.flash(h.escape(error.error_dict['email']), category='error')
945 except IntegrityError:
945 except IntegrityError:
946 log.warning("Email %s already exists", email)
946 log.warning("Email %s already exists", email)
947 h.flash(_('Email `{}` is already registered for another user.').format(email),
947 h.flash(_('Email `{}` is already registered for another user.').format(email),
948 category='error')
948 category='error')
949 except Exception:
949 except Exception:
950 log.exception("Exception during email saving")
950 log.exception("Exception during email saving")
951 h.flash(_('An error occurred during email saving'),
951 h.flash(_('An error occurred during email saving'),
952 category='error')
952 category='error')
953 raise HTTPFound(h.route_path('edit_user_emails', user_id=user_id))
953 raise HTTPFound(h.route_path('edit_user_emails', user_id=user_id))
954
954
955 @LoginRequired()
955 @LoginRequired()
956 @HasPermissionAllDecorator('hg.admin')
956 @HasPermissionAllDecorator('hg.admin')
957 @CSRFRequired()
957 @CSRFRequired()
958 @view_config(
958 @view_config(
959 route_name='edit_user_emails_delete', request_method='POST')
959 route_name='edit_user_emails_delete', request_method='POST')
960 def emails_delete(self):
960 def emails_delete(self):
961 _ = self.request.translate
961 _ = self.request.translate
962 c = self.load_default_context()
962 c = self.load_default_context()
963
963
964 user_id = self.db_user_id
964 user_id = self.db_user_id
965 c.user = self.db_user
965 c.user = self.db_user
966
966
967 email_id = self.request.POST.get('del_email_id')
967 email_id = self.request.POST.get('del_email_id')
968 user_model = UserModel()
968 user_model = UserModel()
969
969
970 email = UserEmailMap.query().get(email_id).email
970 email = UserEmailMap.query().get(email_id).email
971 user_data = c.user.get_api_data()
971 user_data = c.user.get_api_data()
972 user_model.delete_extra_email(c.user.user_id, email_id)
972 user_model.delete_extra_email(c.user.user_id, email_id)
973 audit_logger.store_web(
973 audit_logger.store_web(
974 'user.edit.email.delete',
974 'user.edit.email.delete',
975 action_data={'email': email, 'user': user_data},
975 action_data={'email': email, 'user': user_data},
976 user=self._rhodecode_user)
976 user=self._rhodecode_user)
977 Session().commit()
977 Session().commit()
978 h.flash(_("Removed email address from user account"),
978 h.flash(_("Removed email address from user account"),
979 category='success')
979 category='success')
980 raise HTTPFound(h.route_path('edit_user_emails', user_id=user_id))
980 raise HTTPFound(h.route_path('edit_user_emails', user_id=user_id))
981
981
982 @LoginRequired()
982 @LoginRequired()
983 @HasPermissionAllDecorator('hg.admin')
983 @HasPermissionAllDecorator('hg.admin')
984 @view_config(
984 @view_config(
985 route_name='edit_user_ips', request_method='GET',
985 route_name='edit_user_ips', request_method='GET',
986 renderer='rhodecode:templates/admin/users/user_edit.mako')
986 renderer='rhodecode:templates/admin/users/user_edit.mako')
987 def ips(self):
987 def ips(self):
988 _ = self.request.translate
988 _ = self.request.translate
989 c = self.load_default_context()
989 c = self.load_default_context()
990 c.user = self.db_user
990 c.user = self.db_user
991
991
992 c.active = 'ips'
992 c.active = 'ips'
993 c.user_ip_map = UserIpMap.query() \
993 c.user_ip_map = UserIpMap.query() \
994 .filter(UserIpMap.user == c.user).all()
994 .filter(UserIpMap.user == c.user).all()
995
995
996 c.inherit_default_ips = c.user.inherit_default_permissions
996 c.inherit_default_ips = c.user.inherit_default_permissions
997 c.default_user_ip_map = UserIpMap.query() \
997 c.default_user_ip_map = UserIpMap.query() \
998 .filter(UserIpMap.user == User.get_default_user()).all()
998 .filter(UserIpMap.user == User.get_default_user()).all()
999
999
1000 return self._get_template_context(c)
1000 return self._get_template_context(c)
1001
1001
1002 @LoginRequired()
1002 @LoginRequired()
1003 @HasPermissionAllDecorator('hg.admin')
1003 @HasPermissionAllDecorator('hg.admin')
1004 @CSRFRequired()
1004 @CSRFRequired()
1005 @view_config(
1005 @view_config(
1006 route_name='edit_user_ips_add', request_method='POST')
1006 route_name='edit_user_ips_add', request_method='POST')
1007 # NOTE(marcink): this view is allowed for default users, as we can
1007 # NOTE(marcink): this view is allowed for default users, as we can
1008 # edit their IP white list
1008 # edit their IP white list
1009 def ips_add(self):
1009 def ips_add(self):
1010 _ = self.request.translate
1010 _ = self.request.translate
1011 c = self.load_default_context()
1011 c = self.load_default_context()
1012
1012
1013 user_id = self.db_user_id
1013 user_id = self.db_user_id
1014 c.user = self.db_user
1014 c.user = self.db_user
1015
1015
1016 user_model = UserModel()
1016 user_model = UserModel()
1017 desc = self.request.POST.get('description')
1017 desc = self.request.POST.get('description')
1018 try:
1018 try:
1019 ip_list = user_model.parse_ip_range(
1019 ip_list = user_model.parse_ip_range(
1020 self.request.POST.get('new_ip'))
1020 self.request.POST.get('new_ip'))
1021 except Exception as e:
1021 except Exception as e:
1022 ip_list = []
1022 ip_list = []
1023 log.exception("Exception during ip saving")
1023 log.exception("Exception during ip saving")
1024 h.flash(_('An error occurred during ip saving:%s' % (e,)),
1024 h.flash(_('An error occurred during ip saving:%s' % (e,)),
1025 category='error')
1025 category='error')
1026 added = []
1026 added = []
1027 user_data = c.user.get_api_data()
1027 user_data = c.user.get_api_data()
1028 for ip in ip_list:
1028 for ip in ip_list:
1029 try:
1029 try:
1030 form = UserExtraIpForm(self.request.translate)()
1030 form = UserExtraIpForm(self.request.translate)()
1031 data = form.to_python({'ip': ip})
1031 data = form.to_python({'ip': ip})
1032 ip = data['ip']
1032 ip = data['ip']
1033
1033
1034 user_model.add_extra_ip(c.user.user_id, ip, desc)
1034 user_model.add_extra_ip(c.user.user_id, ip, desc)
1035 audit_logger.store_web(
1035 audit_logger.store_web(
1036 'user.edit.ip.add',
1036 'user.edit.ip.add',
1037 action_data={'ip': ip, 'user': user_data},
1037 action_data={'ip': ip, 'user': user_data},
1038 user=self._rhodecode_user)
1038 user=self._rhodecode_user)
1039 Session().commit()
1039 Session().commit()
1040 added.append(ip)
1040 added.append(ip)
1041 except formencode.Invalid as error:
1041 except formencode.Invalid as error:
1042 msg = error.error_dict['ip']
1042 msg = error.error_dict['ip']
1043 h.flash(msg, category='error')
1043 h.flash(msg, category='error')
1044 except Exception:
1044 except Exception:
1045 log.exception("Exception during ip saving")
1045 log.exception("Exception during ip saving")
1046 h.flash(_('An error occurred during ip saving'),
1046 h.flash(_('An error occurred during ip saving'),
1047 category='error')
1047 category='error')
1048 if added:
1048 if added:
1049 h.flash(
1049 h.flash(
1050 _("Added ips %s to user whitelist") % (', '.join(ip_list), ),
1050 _("Added ips %s to user whitelist") % (', '.join(ip_list), ),
1051 category='success')
1051 category='success')
1052 if 'default_user' in self.request.POST:
1052 if 'default_user' in self.request.POST:
1053 # case for editing global IP list we do it for 'DEFAULT' user
1053 # case for editing global IP list we do it for 'DEFAULT' user
1054 raise HTTPFound(h.route_path('admin_permissions_ips'))
1054 raise HTTPFound(h.route_path('admin_permissions_ips'))
1055 raise HTTPFound(h.route_path('edit_user_ips', user_id=user_id))
1055 raise HTTPFound(h.route_path('edit_user_ips', user_id=user_id))
1056
1056
1057 @LoginRequired()
1057 @LoginRequired()
1058 @HasPermissionAllDecorator('hg.admin')
1058 @HasPermissionAllDecorator('hg.admin')
1059 @CSRFRequired()
1059 @CSRFRequired()
1060 @view_config(
1060 @view_config(
1061 route_name='edit_user_ips_delete', request_method='POST')
1061 route_name='edit_user_ips_delete', request_method='POST')
1062 # NOTE(marcink): this view is allowed for default users, as we can
1062 # NOTE(marcink): this view is allowed for default users, as we can
1063 # edit their IP white list
1063 # edit their IP white list
1064 def ips_delete(self):
1064 def ips_delete(self):
1065 _ = self.request.translate
1065 _ = self.request.translate
1066 c = self.load_default_context()
1066 c = self.load_default_context()
1067
1067
1068 user_id = self.db_user_id
1068 user_id = self.db_user_id
1069 c.user = self.db_user
1069 c.user = self.db_user
1070
1070
1071 ip_id = self.request.POST.get('del_ip_id')
1071 ip_id = self.request.POST.get('del_ip_id')
1072 user_model = UserModel()
1072 user_model = UserModel()
1073 user_data = c.user.get_api_data()
1073 user_data = c.user.get_api_data()
1074 ip = UserIpMap.query().get(ip_id).ip_addr
1074 ip = UserIpMap.query().get(ip_id).ip_addr
1075 user_model.delete_extra_ip(c.user.user_id, ip_id)
1075 user_model.delete_extra_ip(c.user.user_id, ip_id)
1076 audit_logger.store_web(
1076 audit_logger.store_web(
1077 'user.edit.ip.delete', action_data={'ip': ip, 'user': user_data},
1077 'user.edit.ip.delete', action_data={'ip': ip, 'user': user_data},
1078 user=self._rhodecode_user)
1078 user=self._rhodecode_user)
1079 Session().commit()
1079 Session().commit()
1080 h.flash(_("Removed ip address from user whitelist"), category='success')
1080 h.flash(_("Removed ip address from user whitelist"), category='success')
1081
1081
1082 if 'default_user' in self.request.POST:
1082 if 'default_user' in self.request.POST:
1083 # case for editing global IP list we do it for 'DEFAULT' user
1083 # case for editing global IP list we do it for 'DEFAULT' user
1084 raise HTTPFound(h.route_path('admin_permissions_ips'))
1084 raise HTTPFound(h.route_path('admin_permissions_ips'))
1085 raise HTTPFound(h.route_path('edit_user_ips', user_id=user_id))
1085 raise HTTPFound(h.route_path('edit_user_ips', user_id=user_id))
1086
1086
1087 @LoginRequired()
1087 @LoginRequired()
1088 @HasPermissionAllDecorator('hg.admin')
1088 @HasPermissionAllDecorator('hg.admin')
1089 @view_config(
1089 @view_config(
1090 route_name='edit_user_groups_management', request_method='GET',
1090 route_name='edit_user_groups_management', request_method='GET',
1091 renderer='rhodecode:templates/admin/users/user_edit.mako')
1091 renderer='rhodecode:templates/admin/users/user_edit.mako')
1092 def groups_management(self):
1092 def groups_management(self):
1093 c = self.load_default_context()
1093 c = self.load_default_context()
1094 c.user = self.db_user
1094 c.user = self.db_user
1095 c.data = c.user.group_member
1095 c.data = c.user.group_member
1096
1096
1097 groups = [UserGroupModel.get_user_groups_as_dict(group.users_group)
1097 groups = [UserGroupModel.get_user_groups_as_dict(group.users_group)
1098 for group in c.user.group_member]
1098 for group in c.user.group_member]
1099 c.groups = json.dumps(groups)
1099 c.groups = json.dumps(groups)
1100 c.active = 'groups'
1100 c.active = 'groups'
1101
1101
1102 return self._get_template_context(c)
1102 return self._get_template_context(c)
1103
1103
1104 @LoginRequired()
1104 @LoginRequired()
1105 @HasPermissionAllDecorator('hg.admin')
1105 @HasPermissionAllDecorator('hg.admin')
1106 @CSRFRequired()
1106 @CSRFRequired()
1107 @view_config(
1107 @view_config(
1108 route_name='edit_user_groups_management_updates', request_method='POST')
1108 route_name='edit_user_groups_management_updates', request_method='POST')
1109 def groups_management_updates(self):
1109 def groups_management_updates(self):
1110 _ = self.request.translate
1110 _ = self.request.translate
1111 c = self.load_default_context()
1111 c = self.load_default_context()
1112
1112
1113 user_id = self.db_user_id
1113 user_id = self.db_user_id
1114 c.user = self.db_user
1114 c.user = self.db_user
1115
1115
1116 user_groups = set(self.request.POST.getall('users_group_id'))
1116 user_groups = set(self.request.POST.getall('users_group_id'))
1117 user_groups_objects = []
1117 user_groups_objects = []
1118
1118
1119 for ugid in user_groups:
1119 for ugid in user_groups:
1120 user_groups_objects.append(
1120 user_groups_objects.append(
1121 UserGroupModel().get_group(safe_int(ugid)))
1121 UserGroupModel().get_group(safe_int(ugid)))
1122 user_group_model = UserGroupModel()
1122 user_group_model = UserGroupModel()
1123 added_to_groups, removed_from_groups = \
1123 added_to_groups, removed_from_groups = \
1124 user_group_model.change_groups(c.user, user_groups_objects)
1124 user_group_model.change_groups(c.user, user_groups_objects)
1125
1125
1126 user_data = c.user.get_api_data()
1126 user_data = c.user.get_api_data()
1127 for user_group_id in added_to_groups:
1127 for user_group_id in added_to_groups:
1128 user_group = UserGroup.get(user_group_id)
1128 user_group = UserGroup.get(user_group_id)
1129 old_values = user_group.get_api_data()
1129 old_values = user_group.get_api_data()
1130 audit_logger.store_web(
1130 audit_logger.store_web(
1131 'user_group.edit.member.add',
1131 'user_group.edit.member.add',
1132 action_data={'user': user_data, 'old_data': old_values},
1132 action_data={'user': user_data, 'old_data': old_values},
1133 user=self._rhodecode_user)
1133 user=self._rhodecode_user)
1134
1134
1135 for user_group_id in removed_from_groups:
1135 for user_group_id in removed_from_groups:
1136 user_group = UserGroup.get(user_group_id)
1136 user_group = UserGroup.get(user_group_id)
1137 old_values = user_group.get_api_data()
1137 old_values = user_group.get_api_data()
1138 audit_logger.store_web(
1138 audit_logger.store_web(
1139 'user_group.edit.member.delete',
1139 'user_group.edit.member.delete',
1140 action_data={'user': user_data, 'old_data': old_values},
1140 action_data={'user': user_data, 'old_data': old_values},
1141 user=self._rhodecode_user)
1141 user=self._rhodecode_user)
1142
1142
1143 Session().commit()
1143 Session().commit()
1144 c.active = 'user_groups_management'
1144 c.active = 'user_groups_management'
1145 h.flash(_("Groups successfully changed"), category='success')
1145 h.flash(_("Groups successfully changed"), category='success')
1146
1146
1147 return HTTPFound(h.route_path(
1147 return HTTPFound(h.route_path(
1148 'edit_user_groups_management', user_id=user_id))
1148 'edit_user_groups_management', user_id=user_id))
1149
1149
1150 @LoginRequired()
1150 @LoginRequired()
1151 @HasPermissionAllDecorator('hg.admin')
1151 @HasPermissionAllDecorator('hg.admin')
1152 @view_config(
1152 @view_config(
1153 route_name='edit_user_audit_logs', request_method='GET',
1153 route_name='edit_user_audit_logs', request_method='GET',
1154 renderer='rhodecode:templates/admin/users/user_edit.mako')
1154 renderer='rhodecode:templates/admin/users/user_edit.mako')
1155 def user_audit_logs(self):
1155 def user_audit_logs(self):
1156 _ = self.request.translate
1156 _ = self.request.translate
1157 c = self.load_default_context()
1157 c = self.load_default_context()
1158 c.user = self.db_user
1158 c.user = self.db_user
1159
1159
1160 c.active = 'audit'
1160 c.active = 'audit'
1161
1161
1162 p = safe_int(self.request.GET.get('page', 1), 1)
1162 p = safe_int(self.request.GET.get('page', 1), 1)
1163
1163
1164 filter_term = self.request.GET.get('filter')
1164 filter_term = self.request.GET.get('filter')
1165 user_log = UserModel().get_user_log(c.user, filter_term)
1165 user_log = UserModel().get_user_log(c.user, filter_term)
1166
1166
1167 def url_generator(**kw):
1167 def url_generator(**kw):
1168 if filter_term:
1168 if filter_term:
1169 kw['filter'] = filter_term
1169 kw['filter'] = filter_term
1170 return self.request.current_route_path(_query=kw)
1170 return self.request.current_route_path(_query=kw)
1171
1171
1172 c.audit_logs = h.Page(
1172 c.audit_logs = h.Page(
1173 user_log, page=p, items_per_page=10, url=url_generator)
1173 user_log, page=p, items_per_page=10, url=url_generator)
1174 c.filter_term = filter_term
1174 c.filter_term = filter_term
1175 return self._get_template_context(c)
1175 return self._get_template_context(c)
1176
1176
1177 @LoginRequired()
1177 @LoginRequired()
1178 @HasPermissionAllDecorator('hg.admin')
1178 @HasPermissionAllDecorator('hg.admin')
1179 @view_config(
1179 @view_config(
1180 route_name='edit_user_perms_summary', request_method='GET',
1180 route_name='edit_user_perms_summary', request_method='GET',
1181 renderer='rhodecode:templates/admin/users/user_edit.mako')
1181 renderer='rhodecode:templates/admin/users/user_edit.mako')
1182 def user_perms_summary(self):
1182 def user_perms_summary(self):
1183 _ = self.request.translate
1183 _ = self.request.translate
1184 c = self.load_default_context()
1184 c = self.load_default_context()
1185 c.user = self.db_user
1185 c.user = self.db_user
1186
1186
1187 c.active = 'perms_summary'
1187 c.active = 'perms_summary'
1188 c.perm_user = c.user.AuthUser(ip_addr=self.request.remote_addr)
1188 c.perm_user = c.user.AuthUser(ip_addr=self.request.remote_addr)
1189
1189
1190 return self._get_template_context(c)
1190 return self._get_template_context(c)
1191
1191
1192 @LoginRequired()
1192 @LoginRequired()
1193 @HasPermissionAllDecorator('hg.admin')
1193 @HasPermissionAllDecorator('hg.admin')
1194 @view_config(
1194 @view_config(
1195 route_name='edit_user_perms_summary_json', request_method='GET',
1195 route_name='edit_user_perms_summary_json', request_method='GET',
1196 renderer='json_ext')
1196 renderer='json_ext')
1197 def user_perms_summary_json(self):
1197 def user_perms_summary_json(self):
1198 self.load_default_context()
1198 self.load_default_context()
1199 perm_user = self.db_user.AuthUser(ip_addr=self.request.remote_addr)
1199 perm_user = self.db_user.AuthUser(ip_addr=self.request.remote_addr)
1200
1200
1201 return perm_user.permissions
1201 return perm_user.permissions
1202
1202
1203 @LoginRequired()
1203 @LoginRequired()
1204 @HasPermissionAllDecorator('hg.admin')
1204 @HasPermissionAllDecorator('hg.admin')
1205 @view_config(
1205 @view_config(
1206 route_name='edit_user_caches', request_method='GET',
1206 route_name='edit_user_caches', request_method='GET',
1207 renderer='rhodecode:templates/admin/users/user_edit.mako')
1207 renderer='rhodecode:templates/admin/users/user_edit.mako')
1208 def user_caches(self):
1208 def user_caches(self):
1209 _ = self.request.translate
1209 _ = self.request.translate
1210 c = self.load_default_context()
1210 c = self.load_default_context()
1211 c.user = self.db_user
1211 c.user = self.db_user
1212
1212
1213 c.active = 'caches'
1213 c.active = 'caches'
1214 c.perm_user = c.user.AuthUser(ip_addr=self.request.remote_addr)
1214 c.perm_user = c.user.AuthUser(ip_addr=self.request.remote_addr)
1215
1215
1216 cache_namespace_uid = 'cache_user_auth.{}'.format(self.db_user.user_id)
1216 cache_namespace_uid = 'cache_user_auth.{}'.format(self.db_user.user_id)
1217 c.region = rc_cache.get_or_create_region('cache_perms', cache_namespace_uid)
1217 c.region = rc_cache.get_or_create_region('cache_perms', cache_namespace_uid)
1218 c.backend = c.region.backend
1218 c.backend = c.region.backend
1219 c.user_keys = sorted(c.region.backend.list_keys(prefix=cache_namespace_uid))
1219 c.user_keys = sorted(c.region.backend.list_keys(prefix=cache_namespace_uid))
1220
1220
1221 return self._get_template_context(c)
1221 return self._get_template_context(c)
1222
1222
1223 @LoginRequired()
1223 @LoginRequired()
1224 @HasPermissionAllDecorator('hg.admin')
1224 @HasPermissionAllDecorator('hg.admin')
1225 @CSRFRequired()
1225 @CSRFRequired()
1226 @view_config(
1226 @view_config(
1227 route_name='edit_user_caches_update', request_method='POST')
1227 route_name='edit_user_caches_update', request_method='POST')
1228 def user_caches_update(self):
1228 def user_caches_update(self):
1229 _ = self.request.translate
1229 _ = self.request.translate
1230 c = self.load_default_context()
1230 c = self.load_default_context()
1231 c.user = self.db_user
1231 c.user = self.db_user
1232
1232
1233 c.active = 'caches'
1233 c.active = 'caches'
1234 c.perm_user = c.user.AuthUser(ip_addr=self.request.remote_addr)
1234 c.perm_user = c.user.AuthUser(ip_addr=self.request.remote_addr)
1235
1235
1236 cache_namespace_uid = 'cache_user_auth.{}'.format(self.db_user.user_id)
1236 cache_namespace_uid = 'cache_user_auth.{}'.format(self.db_user.user_id)
1237 del_keys = rc_cache.clear_cache_namespace('cache_perms', cache_namespace_uid)
1237 del_keys = rc_cache.clear_cache_namespace('cache_perms', cache_namespace_uid)
1238
1238
1239 h.flash(_("Deleted {} cache keys").format(del_keys), category='success')
1239 h.flash(_("Deleted {} cache keys").format(del_keys), category='success')
1240
1240
1241 return HTTPFound(h.route_path(
1241 return HTTPFound(h.route_path(
1242 'edit_user_caches', user_id=c.user.user_id))
1242 'edit_user_caches', user_id=c.user.user_id))
@@ -1,477 +1,486 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2016-2018 RhodeCode GmbH
3 # Copyright (C) 2016-2018 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 time
21 import time
22 import collections
22 import collections
23 import datetime
23 import datetime
24 import formencode
24 import formencode
25 import formencode.htmlfill
25 import formencode.htmlfill
26 import logging
26 import logging
27 import urlparse
27 import urlparse
28 import requests
28 import requests
29
29
30 from pyramid.httpexceptions import HTTPFound
30 from pyramid.httpexceptions import HTTPFound
31 from pyramid.view import view_config
31 from pyramid.view import view_config
32
32
33 from rhodecode.apps._base import BaseAppView
33 from rhodecode.apps._base import BaseAppView
34 from rhodecode.authentication.base import authenticate, HTTP_TYPE
34 from rhodecode.authentication.base import authenticate, HTTP_TYPE
35 from rhodecode.authentication.plugins import auth_rhodecode
35 from rhodecode.events import UserRegistered, trigger
36 from rhodecode.events import UserRegistered, trigger
36 from rhodecode.lib import helpers as h
37 from rhodecode.lib import helpers as h
37 from rhodecode.lib import audit_logger
38 from rhodecode.lib import audit_logger
38 from rhodecode.lib.auth import (
39 from rhodecode.lib.auth import (
39 AuthUser, HasPermissionAnyDecorator, CSRFRequired)
40 AuthUser, HasPermissionAnyDecorator, CSRFRequired)
40 from rhodecode.lib.base import get_ip_addr
41 from rhodecode.lib.base import get_ip_addr
41 from rhodecode.lib.exceptions import UserCreationError
42 from rhodecode.lib.exceptions import UserCreationError
42 from rhodecode.lib.utils2 import safe_str
43 from rhodecode.lib.utils2 import safe_str
43 from rhodecode.model.db import User, UserApiKeys
44 from rhodecode.model.db import User, UserApiKeys
44 from rhodecode.model.forms import LoginForm, RegisterForm, PasswordResetForm
45 from rhodecode.model.forms import LoginForm, RegisterForm, PasswordResetForm
45 from rhodecode.model.meta import Session
46 from rhodecode.model.meta import Session
46 from rhodecode.model.auth_token import AuthTokenModel
47 from rhodecode.model.auth_token import AuthTokenModel
47 from rhodecode.model.settings import SettingsModel
48 from rhodecode.model.settings import SettingsModel
48 from rhodecode.model.user import UserModel
49 from rhodecode.model.user import UserModel
49 from rhodecode.translation import _
50 from rhodecode.translation import _
50
51
51
52
52 log = logging.getLogger(__name__)
53 log = logging.getLogger(__name__)
53
54
54 CaptchaData = collections.namedtuple(
55 CaptchaData = collections.namedtuple(
55 'CaptchaData', 'active, private_key, public_key')
56 'CaptchaData', 'active, private_key, public_key')
56
57
57
58
58 def store_user_in_session(session, username, remember=False):
59 def store_user_in_session(session, username, remember=False):
59 user = User.get_by_username(username, case_insensitive=True)
60 user = User.get_by_username(username, case_insensitive=True)
60 auth_user = AuthUser(user.user_id)
61 auth_user = AuthUser(user.user_id)
61 auth_user.set_authenticated()
62 auth_user.set_authenticated()
62 cs = auth_user.get_cookie_store()
63 cs = auth_user.get_cookie_store()
63 session['rhodecode_user'] = cs
64 session['rhodecode_user'] = cs
64 user.update_lastlogin()
65 user.update_lastlogin()
65 Session().commit()
66 Session().commit()
66
67
67 # If they want to be remembered, update the cookie
68 # If they want to be remembered, update the cookie
68 if remember:
69 if remember:
69 _year = (datetime.datetime.now() +
70 _year = (datetime.datetime.now() +
70 datetime.timedelta(seconds=60 * 60 * 24 * 365))
71 datetime.timedelta(seconds=60 * 60 * 24 * 365))
71 session._set_cookie_expires(_year)
72 session._set_cookie_expires(_year)
72
73
73 session.save()
74 session.save()
74
75
75 safe_cs = cs.copy()
76 safe_cs = cs.copy()
76 safe_cs['password'] = '****'
77 safe_cs['password'] = '****'
77 log.info('user %s is now authenticated and stored in '
78 log.info('user %s is now authenticated and stored in '
78 'session, session attrs %s', username, safe_cs)
79 'session, session attrs %s', username, safe_cs)
79
80
80 # dumps session attrs back to cookie
81 # dumps session attrs back to cookie
81 session._update_cookie_out()
82 session._update_cookie_out()
82 # we set new cookie
83 # we set new cookie
83 headers = None
84 headers = None
84 if session.request['set_cookie']:
85 if session.request['set_cookie']:
85 # send set-cookie headers back to response to update cookie
86 # send set-cookie headers back to response to update cookie
86 headers = [('Set-Cookie', session.request['cookie_out'])]
87 headers = [('Set-Cookie', session.request['cookie_out'])]
87 return headers
88 return headers
88
89
89
90
90 def get_came_from(request):
91 def get_came_from(request):
91 came_from = safe_str(request.GET.get('came_from', ''))
92 came_from = safe_str(request.GET.get('came_from', ''))
92 parsed = urlparse.urlparse(came_from)
93 parsed = urlparse.urlparse(came_from)
93 allowed_schemes = ['http', 'https']
94 allowed_schemes = ['http', 'https']
94 default_came_from = h.route_path('home')
95 default_came_from = h.route_path('home')
95 if parsed.scheme and parsed.scheme not in allowed_schemes:
96 if parsed.scheme and parsed.scheme not in allowed_schemes:
96 log.error('Suspicious URL scheme detected %s for url %s',
97 log.error('Suspicious URL scheme detected %s for url %s',
97 parsed.scheme, parsed)
98 parsed.scheme, parsed)
98 came_from = default_came_from
99 came_from = default_came_from
99 elif parsed.netloc and request.host != parsed.netloc:
100 elif parsed.netloc and request.host != parsed.netloc:
100 log.error('Suspicious NETLOC detected %s for url %s server url '
101 log.error('Suspicious NETLOC detected %s for url %s server url '
101 'is: %s', parsed.netloc, parsed, request.host)
102 'is: %s', parsed.netloc, parsed, request.host)
102 came_from = default_came_from
103 came_from = default_came_from
103 elif any(bad_str in parsed.path for bad_str in ('\r', '\n')):
104 elif any(bad_str in parsed.path for bad_str in ('\r', '\n')):
104 log.error('Header injection detected `%s` for url %s server url ',
105 log.error('Header injection detected `%s` for url %s server url ',
105 parsed.path, parsed)
106 parsed.path, parsed)
106 came_from = default_came_from
107 came_from = default_came_from
107
108
108 return came_from or default_came_from
109 return came_from or default_came_from
109
110
110
111
111 class LoginView(BaseAppView):
112 class LoginView(BaseAppView):
112
113
113 def load_default_context(self):
114 def load_default_context(self):
114 c = self._get_local_tmpl_context()
115 c = self._get_local_tmpl_context()
115 c.came_from = get_came_from(self.request)
116 c.came_from = get_came_from(self.request)
116
117
117 return c
118 return c
118
119
119 def _get_captcha_data(self):
120 def _get_captcha_data(self):
120 settings = SettingsModel().get_all_settings()
121 settings = SettingsModel().get_all_settings()
121 private_key = settings.get('rhodecode_captcha_private_key')
122 private_key = settings.get('rhodecode_captcha_private_key')
122 public_key = settings.get('rhodecode_captcha_public_key')
123 public_key = settings.get('rhodecode_captcha_public_key')
123 active = bool(private_key)
124 active = bool(private_key)
124 return CaptchaData(
125 return CaptchaData(
125 active=active, private_key=private_key, public_key=public_key)
126 active=active, private_key=private_key, public_key=public_key)
126
127
127 def validate_captcha(self, private_key):
128 def validate_captcha(self, private_key):
128
129
129 captcha_rs = self.request.POST.get('g-recaptcha-response')
130 captcha_rs = self.request.POST.get('g-recaptcha-response')
130 url = "https://www.google.com/recaptcha/api/siteverify"
131 url = "https://www.google.com/recaptcha/api/siteverify"
131 params = {
132 params = {
132 'secret': private_key,
133 'secret': private_key,
133 'response': captcha_rs,
134 'response': captcha_rs,
134 'remoteip': get_ip_addr(self.request.environ)
135 'remoteip': get_ip_addr(self.request.environ)
135 }
136 }
136 verify_rs = requests.get(url, params=params, verify=True, timeout=60)
137 verify_rs = requests.get(url, params=params, verify=True, timeout=60)
137 verify_rs = verify_rs.json()
138 verify_rs = verify_rs.json()
138 captcha_status = verify_rs.get('success', False)
139 captcha_status = verify_rs.get('success', False)
139 captcha_errors = verify_rs.get('error-codes', [])
140 captcha_errors = verify_rs.get('error-codes', [])
140 if not isinstance(captcha_errors, list):
141 if not isinstance(captcha_errors, list):
141 captcha_errors = [captcha_errors]
142 captcha_errors = [captcha_errors]
142 captcha_errors = ', '.join(captcha_errors)
143 captcha_errors = ', '.join(captcha_errors)
143 captcha_message = ''
144 captcha_message = ''
144 if captcha_status is False:
145 if captcha_status is False:
145 captcha_message = "Bad captcha. Errors: {}".format(
146 captcha_message = "Bad captcha. Errors: {}".format(
146 captcha_errors)
147 captcha_errors)
147
148
148 return captcha_status, captcha_message
149 return captcha_status, captcha_message
149
150
150 @view_config(
151 @view_config(
151 route_name='login', request_method='GET',
152 route_name='login', request_method='GET',
152 renderer='rhodecode:templates/login.mako')
153 renderer='rhodecode:templates/login.mako')
153 def login(self):
154 def login(self):
154 c = self.load_default_context()
155 c = self.load_default_context()
155 auth_user = self._rhodecode_user
156 auth_user = self._rhodecode_user
156
157
157 # redirect if already logged in
158 # redirect if already logged in
158 if (auth_user.is_authenticated and
159 if (auth_user.is_authenticated and
159 not auth_user.is_default and auth_user.ip_allowed):
160 not auth_user.is_default and auth_user.ip_allowed):
160 raise HTTPFound(c.came_from)
161 raise HTTPFound(c.came_from)
161
162
162 # check if we use headers plugin, and try to login using it.
163 # check if we use headers plugin, and try to login using it.
163 try:
164 try:
164 log.debug('Running PRE-AUTH for headers based authentication')
165 log.debug('Running PRE-AUTH for headers based authentication')
165 auth_info = authenticate(
166 auth_info = authenticate(
166 '', '', self.request.environ, HTTP_TYPE, skip_missing=True)
167 '', '', self.request.environ, HTTP_TYPE, skip_missing=True)
167 if auth_info:
168 if auth_info:
168 headers = store_user_in_session(
169 headers = store_user_in_session(
169 self.session, auth_info.get('username'))
170 self.session, auth_info.get('username'))
170 raise HTTPFound(c.came_from, headers=headers)
171 raise HTTPFound(c.came_from, headers=headers)
171 except UserCreationError as e:
172 except UserCreationError as e:
172 log.error(e)
173 log.error(e)
173 h.flash(e, category='error')
174 h.flash(e, category='error')
174
175
175 return self._get_template_context(c)
176 return self._get_template_context(c)
176
177
177 @view_config(
178 @view_config(
178 route_name='login', request_method='POST',
179 route_name='login', request_method='POST',
179 renderer='rhodecode:templates/login.mako')
180 renderer='rhodecode:templates/login.mako')
180 def login_post(self):
181 def login_post(self):
181 c = self.load_default_context()
182 c = self.load_default_context()
182
183
183 login_form = LoginForm(self.request.translate)()
184 login_form = LoginForm(self.request.translate)()
184
185
185 try:
186 try:
186 self.session.invalidate()
187 self.session.invalidate()
187 form_result = login_form.to_python(self.request.POST)
188 form_result = login_form.to_python(self.request.POST)
188 # form checks for username/password, now we're authenticated
189 # form checks for username/password, now we're authenticated
189 headers = store_user_in_session(
190 headers = store_user_in_session(
190 self.session,
191 self.session,
191 username=form_result['username'],
192 username=form_result['username'],
192 remember=form_result['remember'])
193 remember=form_result['remember'])
193 log.debug('Redirecting to "%s" after login.', c.came_from)
194 log.debug('Redirecting to "%s" after login.', c.came_from)
194
195
195 audit_user = audit_logger.UserWrap(
196 audit_user = audit_logger.UserWrap(
196 username=self.request.POST.get('username'),
197 username=self.request.POST.get('username'),
197 ip_addr=self.request.remote_addr)
198 ip_addr=self.request.remote_addr)
198 action_data = {'user_agent': self.request.user_agent}
199 action_data = {'user_agent': self.request.user_agent}
199 audit_logger.store_web(
200 audit_logger.store_web(
200 'user.login.success', action_data=action_data,
201 'user.login.success', action_data=action_data,
201 user=audit_user, commit=True)
202 user=audit_user, commit=True)
202
203
203 raise HTTPFound(c.came_from, headers=headers)
204 raise HTTPFound(c.came_from, headers=headers)
204 except formencode.Invalid as errors:
205 except formencode.Invalid as errors:
205 defaults = errors.value
206 defaults = errors.value
206 # remove password from filling in form again
207 # remove password from filling in form again
207 defaults.pop('password', None)
208 defaults.pop('password', None)
208 render_ctx = {
209 render_ctx = {
209 'errors': errors.error_dict,
210 'errors': errors.error_dict,
210 'defaults': defaults,
211 'defaults': defaults,
211 }
212 }
212
213
213 audit_user = audit_logger.UserWrap(
214 audit_user = audit_logger.UserWrap(
214 username=self.request.POST.get('username'),
215 username=self.request.POST.get('username'),
215 ip_addr=self.request.remote_addr)
216 ip_addr=self.request.remote_addr)
216 action_data = {'user_agent': self.request.user_agent}
217 action_data = {'user_agent': self.request.user_agent}
217 audit_logger.store_web(
218 audit_logger.store_web(
218 'user.login.failure', action_data=action_data,
219 'user.login.failure', action_data=action_data,
219 user=audit_user, commit=True)
220 user=audit_user, commit=True)
220 return self._get_template_context(c, **render_ctx)
221 return self._get_template_context(c, **render_ctx)
221
222
222 except UserCreationError as e:
223 except UserCreationError as e:
223 # headers auth or other auth functions that create users on
224 # headers auth or other auth functions that create users on
224 # the fly can throw this exception signaling that there's issue
225 # the fly can throw this exception signaling that there's issue
225 # with user creation, explanation should be provided in
226 # with user creation, explanation should be provided in
226 # Exception itself
227 # Exception itself
227 h.flash(e, category='error')
228 h.flash(e, category='error')
228 return self._get_template_context(c)
229 return self._get_template_context(c)
229
230
230 @CSRFRequired()
231 @CSRFRequired()
231 @view_config(route_name='logout', request_method='POST')
232 @view_config(route_name='logout', request_method='POST')
232 def logout(self):
233 def logout(self):
233 auth_user = self._rhodecode_user
234 auth_user = self._rhodecode_user
234 log.info('Deleting session for user: `%s`', auth_user)
235 log.info('Deleting session for user: `%s`', auth_user)
235
236
236 action_data = {'user_agent': self.request.user_agent}
237 action_data = {'user_agent': self.request.user_agent}
237 audit_logger.store_web(
238 audit_logger.store_web(
238 'user.logout', action_data=action_data,
239 'user.logout', action_data=action_data,
239 user=auth_user, commit=True)
240 user=auth_user, commit=True)
240 self.session.delete()
241 self.session.delete()
241 return HTTPFound(h.route_path('home'))
242 return HTTPFound(h.route_path('home'))
242
243
243 @HasPermissionAnyDecorator(
244 @HasPermissionAnyDecorator(
244 'hg.admin', 'hg.register.auto_activate', 'hg.register.manual_activate')
245 'hg.admin', 'hg.register.auto_activate', 'hg.register.manual_activate')
245 @view_config(
246 @view_config(
246 route_name='register', request_method='GET',
247 route_name='register', request_method='GET',
247 renderer='rhodecode:templates/register.mako',)
248 renderer='rhodecode:templates/register.mako',)
248 def register(self, defaults=None, errors=None):
249 def register(self, defaults=None, errors=None):
249 c = self.load_default_context()
250 c = self.load_default_context()
250 defaults = defaults or {}
251 defaults = defaults or {}
251 errors = errors or {}
252 errors = errors or {}
252
253
253 settings = SettingsModel().get_all_settings()
254 settings = SettingsModel().get_all_settings()
254 register_message = settings.get('rhodecode_register_message') or ''
255 register_message = settings.get('rhodecode_register_message') or ''
255 captcha = self._get_captcha_data()
256 captcha = self._get_captcha_data()
256 auto_active = 'hg.register.auto_activate' in User.get_default_user()\
257 auto_active = 'hg.register.auto_activate' in User.get_default_user()\
257 .AuthUser().permissions['global']
258 .AuthUser().permissions['global']
258
259
259 render_ctx = self._get_template_context(c)
260 render_ctx = self._get_template_context(c)
260 render_ctx.update({
261 render_ctx.update({
261 'defaults': defaults,
262 'defaults': defaults,
262 'errors': errors,
263 'errors': errors,
263 'auto_active': auto_active,
264 'auto_active': auto_active,
264 'captcha_active': captcha.active,
265 'captcha_active': captcha.active,
265 'captcha_public_key': captcha.public_key,
266 'captcha_public_key': captcha.public_key,
266 'register_message': register_message,
267 'register_message': register_message,
267 })
268 })
268 return render_ctx
269 return render_ctx
269
270
270 @HasPermissionAnyDecorator(
271 @HasPermissionAnyDecorator(
271 'hg.admin', 'hg.register.auto_activate', 'hg.register.manual_activate')
272 'hg.admin', 'hg.register.auto_activate', 'hg.register.manual_activate')
272 @view_config(
273 @view_config(
273 route_name='register', request_method='POST',
274 route_name='register', request_method='POST',
274 renderer='rhodecode:templates/register.mako')
275 renderer='rhodecode:templates/register.mako')
275 def register_post(self):
276 def register_post(self):
276 from rhodecode.authentication.plugins import auth_rhodecode
277 from rhodecode.authentication.plugins import auth_rhodecode
277
278
278 self.load_default_context()
279 self.load_default_context()
279 captcha = self._get_captcha_data()
280 captcha = self._get_captcha_data()
280 auto_active = 'hg.register.auto_activate' in User.get_default_user()\
281 auto_active = 'hg.register.auto_activate' in User.get_default_user()\
281 .AuthUser().permissions['global']
282 .AuthUser().permissions['global']
282
283
283 extern_name = auth_rhodecode.RhodeCodeAuthPlugin.uid
284 extern_name = auth_rhodecode.RhodeCodeAuthPlugin.uid
284 extern_type = auth_rhodecode.RhodeCodeAuthPlugin.uid
285 extern_type = auth_rhodecode.RhodeCodeAuthPlugin.uid
285
286
286 register_form = RegisterForm(self.request.translate)()
287 register_form = RegisterForm(self.request.translate)()
287 try:
288 try:
288
289
289 form_result = register_form.to_python(self.request.POST)
290 form_result = register_form.to_python(self.request.POST)
290 form_result['active'] = auto_active
291 form_result['active'] = auto_active
291 external_identity = self.request.POST.get('external_identity')
292 external_identity = self.request.POST.get('external_identity')
292
293
293 if external_identity:
294 if external_identity:
294 extern_name = external_identity
295 extern_name = external_identity
295 extern_type = external_identity
296 extern_type = external_identity
296
297
297 if captcha.active:
298 if captcha.active:
298 captcha_status, captcha_message = self.validate_captcha(
299 captcha_status, captcha_message = self.validate_captcha(
299 captcha.private_key)
300 captcha.private_key)
300
301
301 if not captcha_status:
302 if not captcha_status:
302 _value = form_result
303 _value = form_result
303 _msg = _('Bad captcha')
304 _msg = _('Bad captcha')
304 error_dict = {'recaptcha_field': captcha_message}
305 error_dict = {'recaptcha_field': captcha_message}
305 raise formencode.Invalid(
306 raise formencode.Invalid(
306 _msg, _value, None, error_dict=error_dict)
307 _msg, _value, None, error_dict=error_dict)
307
308
308 new_user = UserModel().create_registration(
309 new_user = UserModel().create_registration(
309 form_result, extern_name=extern_name, extern_type=extern_type)
310 form_result, extern_name=extern_name, extern_type=extern_type)
310
311
311 action_data = {'data': new_user.get_api_data(),
312 action_data = {'data': new_user.get_api_data(),
312 'user_agent': self.request.user_agent}
313 'user_agent': self.request.user_agent}
313
314
314
315
315
316
316 if external_identity:
317 if external_identity:
317 action_data['external_identity'] = external_identity
318 action_data['external_identity'] = external_identity
318
319
319 audit_user = audit_logger.UserWrap(
320 audit_user = audit_logger.UserWrap(
320 username=new_user.username,
321 username=new_user.username,
321 user_id=new_user.user_id,
322 user_id=new_user.user_id,
322 ip_addr=self.request.remote_addr)
323 ip_addr=self.request.remote_addr)
323
324
324 audit_logger.store_web(
325 audit_logger.store_web(
325 'user.register', action_data=action_data,
326 'user.register', action_data=action_data,
326 user=audit_user)
327 user=audit_user)
327
328
328 event = UserRegistered(user=new_user, session=self.session)
329 event = UserRegistered(user=new_user, session=self.session)
329 trigger(event)
330 trigger(event)
330 h.flash(
331 h.flash(
331 _('You have successfully registered with RhodeCode'),
332 _('You have successfully registered with RhodeCode'),
332 category='success')
333 category='success')
333 Session().commit()
334 Session().commit()
334
335
335 redirect_ro = self.request.route_path('login')
336 redirect_ro = self.request.route_path('login')
336 raise HTTPFound(redirect_ro)
337 raise HTTPFound(redirect_ro)
337
338
338 except formencode.Invalid as errors:
339 except formencode.Invalid as errors:
339 errors.value.pop('password', None)
340 errors.value.pop('password', None)
340 errors.value.pop('password_confirmation', None)
341 errors.value.pop('password_confirmation', None)
341 return self.register(
342 return self.register(
342 defaults=errors.value, errors=errors.error_dict)
343 defaults=errors.value, errors=errors.error_dict)
343
344
344 except UserCreationError as e:
345 except UserCreationError as e:
345 # container auth or other auth functions that create users on
346 # container auth or other auth functions that create users on
346 # the fly can throw this exception signaling that there's issue
347 # the fly can throw this exception signaling that there's issue
347 # with user creation, explanation should be provided in
348 # with user creation, explanation should be provided in
348 # Exception itself
349 # Exception itself
349 h.flash(e, category='error')
350 h.flash(e, category='error')
350 return self.register()
351 return self.register()
351
352
352 @view_config(
353 @view_config(
353 route_name='reset_password', request_method=('GET', 'POST'),
354 route_name='reset_password', request_method=('GET', 'POST'),
354 renderer='rhodecode:templates/password_reset.mako')
355 renderer='rhodecode:templates/password_reset.mako')
355 def password_reset(self):
356 def password_reset(self):
356 c = self.load_default_context()
357 c = self.load_default_context()
357 captcha = self._get_captcha_data()
358 captcha = self._get_captcha_data()
358
359
359 template_context = {
360 template_context = {
360 'captcha_active': captcha.active,
361 'captcha_active': captcha.active,
361 'captcha_public_key': captcha.public_key,
362 'captcha_public_key': captcha.public_key,
362 'defaults': {},
363 'defaults': {},
363 'errors': {},
364 'errors': {},
364 }
365 }
365
366
366 # always send implicit message to prevent from discovery of
367 # always send implicit message to prevent from discovery of
367 # matching emails
368 # matching emails
368 msg = _('If such email exists, a password reset link was sent to it.')
369 msg = _('If such email exists, a password reset link was sent to it.')
369
370
371 def default_response():
372 log.debug('faking response on invalid password reset')
373 # make this take 2s, to prevent brute forcing.
374 time.sleep(2)
375 h.flash(msg, category='success')
376 return HTTPFound(self.request.route_path('reset_password'))
377
370 if self.request.POST:
378 if self.request.POST:
371 if h.HasPermissionAny('hg.password_reset.disabled')():
379 if h.HasPermissionAny('hg.password_reset.disabled')():
372 _email = self.request.POST.get('email', '')
380 _email = self.request.POST.get('email', '')
373 log.error('Failed attempt to reset password for `%s`.', _email)
381 log.error('Failed attempt to reset password for `%s`.', _email)
374 h.flash(_('Password reset has been disabled.'),
382 h.flash(_('Password reset has been disabled.'), category='error')
375 category='error')
376 return HTTPFound(self.request.route_path('reset_password'))
383 return HTTPFound(self.request.route_path('reset_password'))
377
384
378 password_reset_form = PasswordResetForm(self.request.translate)()
385 password_reset_form = PasswordResetForm(self.request.translate)()
386 description = u'Generated token for password reset from {}'.format(
387 datetime.datetime.now().isoformat())
388
379 try:
389 try:
380 form_result = password_reset_form.to_python(
390 form_result = password_reset_form.to_python(
381 self.request.POST)
391 self.request.POST)
382 user_email = form_result['email']
392 user_email = form_result['email']
383
393
384 if captcha.active:
394 if captcha.active:
385 captcha_status, captcha_message = self.validate_captcha(
395 captcha_status, captcha_message = self.validate_captcha(
386 captcha.private_key)
396 captcha.private_key)
387
397
388 if not captcha_status:
398 if not captcha_status:
389 _value = form_result
399 _value = form_result
390 _msg = _('Bad captcha')
400 _msg = _('Bad captcha')
391 error_dict = {'recaptcha_field': captcha_message}
401 error_dict = {'recaptcha_field': captcha_message}
392 raise formencode.Invalid(
402 raise formencode.Invalid(
393 _msg, _value, None, error_dict=error_dict)
403 _msg, _value, None, error_dict=error_dict)
394
404
395 # Generate reset URL and send mail.
405 # Generate reset URL and send mail.
396 user = User.get_by_email(user_email)
406 user = User.get_by_email(user_email)
397
407
408 # only allow rhodecode based users to reset their password
409 # external auth shouldn't allow password reset
410 if user and user.extern_type != auth_rhodecode.RhodeCodeAuthPlugin.uid:
411 log.warning('User %s with external type `%s` tried a password reset. '
412 'This try was rejected', user, user.extern_type)
413 return default_response()
414
398 # generate password reset token that expires in 10 minutes
415 # generate password reset token that expires in 10 minutes
399 description = u'Generated token for password reset from {}'.format(
400 datetime.datetime.now().isoformat())
401
402 reset_token = UserModel().add_auth_token(
416 reset_token = UserModel().add_auth_token(
403 user=user, lifetime_minutes=10,
417 user=user, lifetime_minutes=10,
404 role=UserModel.auth_token_role.ROLE_PASSWORD_RESET,
418 role=UserModel.auth_token_role.ROLE_PASSWORD_RESET,
405 description=description)
419 description=description)
406 Session().commit()
420 Session().commit()
407
421
408 log.debug('Successfully created password recovery token')
422 log.debug('Successfully created password recovery token')
409 password_reset_url = self.request.route_url(
423 password_reset_url = self.request.route_url(
410 'reset_password_confirmation',
424 'reset_password_confirmation',
411 _query={'key': reset_token.api_key})
425 _query={'key': reset_token.api_key})
412 UserModel().reset_password_link(
426 UserModel().reset_password_link(
413 form_result, password_reset_url)
427 form_result, password_reset_url)
414 # Display success message and redirect.
415 h.flash(msg, category='success')
416
428
417 action_data = {'email': user_email,
429 action_data = {'email': user_email,
418 'user_agent': self.request.user_agent}
430 'user_agent': self.request.user_agent}
419 audit_logger.store_web(
431 audit_logger.store_web(
420 'user.password.reset_request', action_data=action_data,
432 'user.password.reset_request', action_data=action_data,
421 user=self._rhodecode_user, commit=True)
433 user=self._rhodecode_user, commit=True)
422 return HTTPFound(self.request.route_path('reset_password'))
434
435 return default_response()
423
436
424 except formencode.Invalid as errors:
437 except formencode.Invalid as errors:
425 template_context.update({
438 template_context.update({
426 'defaults': errors.value,
439 'defaults': errors.value,
427 'errors': errors.error_dict,
440 'errors': errors.error_dict,
428 })
441 })
429 if not self.request.POST.get('email'):
442 if not self.request.POST.get('email'):
430 # case of empty email, we want to report that
443 # case of empty email, we want to report that
431 return self._get_template_context(c, **template_context)
444 return self._get_template_context(c, **template_context)
432
445
433 if 'recaptcha_field' in errors.error_dict:
446 if 'recaptcha_field' in errors.error_dict:
434 # case of failed captcha
447 # case of failed captcha
435 return self._get_template_context(c, **template_context)
448 return self._get_template_context(c, **template_context)
436
449
437 log.debug('faking response on invalid password reset')
450 return default_response()
438 # make this take 2s, to prevent brute forcing.
439 time.sleep(2)
440 h.flash(msg, category='success')
441 return HTTPFound(self.request.route_path('reset_password'))
442
451
443 return self._get_template_context(c, **template_context)
452 return self._get_template_context(c, **template_context)
444
453
445 @view_config(route_name='reset_password_confirmation',
454 @view_config(route_name='reset_password_confirmation',
446 request_method='GET')
455 request_method='GET')
447 def password_reset_confirmation(self):
456 def password_reset_confirmation(self):
448 self.load_default_context()
457 self.load_default_context()
449 if self.request.GET and self.request.GET.get('key'):
458 if self.request.GET and self.request.GET.get('key'):
450 # make this take 2s, to prevent brute forcing.
459 # make this take 2s, to prevent brute forcing.
451 time.sleep(2)
460 time.sleep(2)
452
461
453 token = AuthTokenModel().get_auth_token(
462 token = AuthTokenModel().get_auth_token(
454 self.request.GET.get('key'))
463 self.request.GET.get('key'))
455
464
456 # verify token is the correct role
465 # verify token is the correct role
457 if token is None or token.role != UserApiKeys.ROLE_PASSWORD_RESET:
466 if token is None or token.role != UserApiKeys.ROLE_PASSWORD_RESET:
458 log.debug('Got token with role:%s expected is %s',
467 log.debug('Got token with role:%s expected is %s',
459 getattr(token, 'role', 'EMPTY_TOKEN'),
468 getattr(token, 'role', 'EMPTY_TOKEN'),
460 UserApiKeys.ROLE_PASSWORD_RESET)
469 UserApiKeys.ROLE_PASSWORD_RESET)
461 h.flash(
470 h.flash(
462 _('Given reset token is invalid'), category='error')
471 _('Given reset token is invalid'), category='error')
463 return HTTPFound(self.request.route_path('reset_password'))
472 return HTTPFound(self.request.route_path('reset_password'))
464
473
465 try:
474 try:
466 owner = token.user
475 owner = token.user
467 data = {'email': owner.email, 'token': token.api_key}
476 data = {'email': owner.email, 'token': token.api_key}
468 UserModel().reset_password(data)
477 UserModel().reset_password(data)
469 h.flash(
478 h.flash(
470 _('Your password reset was successful, '
479 _('Your password reset was successful, '
471 'a new password has been sent to your email'),
480 'a new password has been sent to your email'),
472 category='success')
481 category='success')
473 except Exception as e:
482 except Exception as e:
474 log.error(e)
483 log.error(e)
475 return HTTPFound(self.request.route_path('reset_password'))
484 return HTTPFound(self.request.route_path('reset_password'))
476
485
477 return HTTPFound(self.request.route_path('login'))
486 return HTTPFound(self.request.route_path('login'))
@@ -1,150 +1,151 b''
1 <%namespace name="base" file="/base/base.mako"/>
1 <%namespace name="base" file="/base/base.mako"/>
2
2
3 <div class="panel panel-default user-profile">
3 <div class="panel panel-default user-profile">
4 <div class="panel-heading">
4 <div class="panel-heading">
5 <h3 class="panel-title">${_('User Profile')}</h3>
5 <h3 class="panel-title">${_('User Profile')}</h3>
6 </div>
6 </div>
7 <div class="panel-body">
7 <div class="panel-body">
8 <div class="user-profile-content">
8 <div class="user-profile-content">
9 ${h.secure_form(h.route_path('user_update', user_id=c.user.user_id), class_='form', request=request)}
9 ${h.secure_form(h.route_path('user_update', user_id=c.user.user_id), class_='form', request=request)}
10 <% readonly = None %>
10 <% readonly = None %>
11 <% disabled = "" %>
11 <% disabled = "" %>
12 %if c.extern_type != 'rhodecode':
12 %if c.extern_type != 'rhodecode':
13 <% readonly = "readonly" %>
13 <% readonly = "readonly" %>
14 <% disabled = " disabled" %>
14 <% disabled = " disabled" %>
15 <div class="infoform">
15 <div class="infoform">
16 <div class="fields">
16 <div class="fields">
17 <p>${_('This user was created from external source (%s). Editing some of the settings is limited.' % c.extern_type)}</p>
17 <p>${_('This user was created from external source (%s). Editing some of the settings is limited.' % c.extern_type)}</p>
18 </div>
18 </div>
19 </div>
19 </div>
20 %endif
20 %endif
21 <div class="form">
21 <div class="form">
22 <div class="fields">
22 <div class="fields">
23 <div class="field">
23 <div class="field">
24 <div class="label photo">
24 <div class="label photo">
25 ${_('Photo')}:
25 ${_('Photo')}:
26 </div>
26 </div>
27 <div class="input profile">
27 <div class="input profile">
28 %if c.visual.use_gravatar:
28 %if c.visual.use_gravatar:
29 ${base.gravatar(c.user.email, 100)}
29 ${base.gravatar(c.user.email, 100)}
30 <p class="help-block">${_('Change the avatar at')} <a href="http://gravatar.com">gravatar.com</a>.</p>
30 <p class="help-block">${_('Change the avatar at')} <a href="http://gravatar.com">gravatar.com</a>.</p>
31 %else:
31 %else:
32 ${base.gravatar(c.user.email, 20)}
32 ${base.gravatar(c.user.email, 20)}
33 ${_('Avatars are disabled')}
33 ${_('Avatars are disabled')}
34 %endif
34 %endif
35 </div>
35 </div>
36 </div>
36 </div>
37 <div class="field">
37 <div class="field">
38 <div class="label">
38 <div class="label">
39 ${_('Username')}:
39 ${_('Username')}:
40 </div>
40 </div>
41 <div class="input">
41 <div class="input">
42 ${h.text('username', class_='%s medium' % disabled, readonly=readonly)}
42 ${h.text('username', class_='%s medium' % disabled, readonly=readonly)}
43 </div>
43 </div>
44 </div>
44 </div>
45 <div class="field">
45 <div class="field">
46 <div class="label">
46 <div class="label">
47 <label for="name">${_('First Name')}:</label>
47 <label for="name">${_('First Name')}:</label>
48 </div>
48 </div>
49 <div class="input">
49 <div class="input">
50 ${h.text('firstname', class_="medium")}
50 ${h.text('firstname', class_="medium")}
51 </div>
51 </div>
52 </div>
52 </div>
53
53
54 <div class="field">
54 <div class="field">
55 <div class="label">
55 <div class="label">
56 <label for="lastname">${_('Last Name')}:</label>
56 <label for="lastname">${_('Last Name')}:</label>
57 </div>
57 </div>
58 <div class="input">
58 <div class="input">
59 ${h.text('lastname', class_="medium")}
59 ${h.text('lastname', class_="medium")}
60 </div>
60 </div>
61 </div>
61 </div>
62
62
63 <div class="field">
63 <div class="field">
64 <div class="label">
64 <div class="label">
65 <label for="email">${_('Email')}:</label>
65 <label for="email">${_('Email')}:</label>
66 </div>
66 </div>
67 <div class="input">
67 <div class="input">
68 ## we should be able to edit email !
68 ## we should be able to edit email !
69 ${h.text('email', class_="medium")}
69 ${h.text('email', class_="medium")}
70 </div>
70 </div>
71 </div>
71 </div>
72 <div class="field">
72 <div class="field">
73 <div class="label">
73 <div class="label">
74 ${_('New Password')}:
74 ${_('New Password')}:
75 </div>
75 </div>
76 <div class="input">
76 <div class="input">
77 ${h.password('new_password',class_='%s medium' % disabled,autocomplete="off",readonly=readonly)}
77 ${h.password('new_password',class_='%s medium' % disabled,autocomplete="off",readonly=readonly)}
78 </div>
78 </div>
79 </div>
79 </div>
80 <div class="field">
80 <div class="field">
81 <div class="label">
81 <div class="label">
82 ${_('New Password Confirmation')}:
82 ${_('New Password Confirmation')}:
83 </div>
83 </div>
84 <div class="input">
84 <div class="input">
85 ${h.password('password_confirmation',class_="%s medium" % disabled,autocomplete="off",readonly=readonly)}
85 ${h.password('password_confirmation',class_="%s medium" % disabled,autocomplete="off",readonly=readonly)}
86 </div>
86 </div>
87 </div>
87 </div>
88 <div class="field">
88 <div class="field">
89 <div class="label-text">
89 <div class="label-text">
90 ${_('Active')}:
90 ${_('Active')}:
91 </div>
91 </div>
92 <div class="input user-checkbox">
92 <div class="input user-checkbox">
93 ${h.checkbox('active',value=True)}
93 ${h.checkbox('active',value=True)}
94 </div>
94 </div>
95 </div>
95 </div>
96 <div class="field">
96 <div class="field">
97 <div class="label-text">
97 <div class="label-text">
98 ${_('Super Admin')}:
98 ${_('Super Admin')}:
99 </div>
99 </div>
100 <div class="input user-checkbox">
100 <div class="input user-checkbox">
101 ${h.checkbox('admin',value=True)}
101 ${h.checkbox('admin',value=True)}
102 </div>
102 </div>
103 </div>
103 </div>
104 <div class="field">
104 <div class="field">
105 <div class="label-text">
105 <div class="label-text">
106 ${_('Source of Record')}:
106 ${_('Authentication type')}:
107 </div>
107 </div>
108 <div class="input">
108 <div class="input">
109 <p>${c.extern_type}</p>
109 <p>${c.extern_type}</p>
110 ${h.hidden('extern_type', readonly="readonly")}
110 ${h.hidden('extern_type', readonly="readonly")}
111 <p class="help-block">${_('User was created using an external source. He is bound to authentication using this method.')}</p>
111 </div>
112 </div>
112 </div>
113 </div>
113 <div class="field">
114 <div class="field">
114 <div class="label-text">
115 <div class="label-text">
115 ${_('Name in Source of Record')}:
116 ${_('Name in Source of Record')}:
116 </div>
117 </div>
117 <div class="input">
118 <div class="input">
118 <p>${c.extern_name}</p>
119 <p>${c.extern_name}</p>
119 ${h.hidden('extern_name', readonly="readonly")}
120 ${h.hidden('extern_name', readonly="readonly")}
120 </div>
121 </div>
121 </div>
122 </div>
122 <div class="field">
123 <div class="field">
123 <div class="label">
124 <div class="label">
124 ${_('Language')}:
125 ${_('Language')}:
125 </div>
126 </div>
126 <div class="input">
127 <div class="input">
127 ## allowed_languages is defined in the users.py
128 ## allowed_languages is defined in the users.py
128 ## c.language comes from base.py as a default language
129 ## c.language comes from base.py as a default language
129 ${h.select('language', c.language, c.allowed_languages)}
130 ${h.select('language', c.language, c.allowed_languages)}
130 <p class="help-block">${h.literal(_('Help translate %(rc_link)s into your language.') % {'rc_link': h.link_to('RhodeCode Enterprise', h.route_url('rhodecode_translations'))})}</p>
131 <p class="help-block">${h.literal(_('User interface language. Help translate %(rc_link)s into your language.') % {'rc_link': h.link_to('RhodeCode Enterprise', h.route_url('rhodecode_translations'))})}</p>
131 </div>
132 </div>
132 </div>
133 </div>
133 <div class="buttons">
134 <div class="buttons">
134 ${h.submit('save', _('Save'), class_="btn")}
135 ${h.submit('save', _('Save'), class_="btn")}
135 ${h.reset('reset', _('Reset'), class_="btn")}
136 ${h.reset('reset', _('Reset'), class_="btn")}
136 </div>
137 </div>
137 </div>
138 </div>
138 </div>
139 </div>
139 ${h.end_form()}
140 ${h.end_form()}
140 </div>
141 </div>
141 </div>
142 </div>
142 </div>
143 </div>
143
144
144 <script>
145 <script>
145 $('#language').select2({
146 $('#language').select2({
146 'containerCssClass': "drop-menu",
147 'containerCssClass': "drop-menu",
147 'dropdownCssClass': "drop-menu-dropdown",
148 'dropdownCssClass': "drop-menu-dropdown",
148 'dropdownAutoWidth': true
149 'dropdownAutoWidth': true
149 });
150 });
150 </script>
151 </script>
General Comments 0
You need to be logged in to leave comments. Login now