##// END OF EJS Templates
auth-tokens: abstracted adding token for users into UserModel method for easier usage in scripts, and in future in API.
marcink -
r2951:93db3089 default
parent child Browse files
Show More
@@ -1,1241 +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.name
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.name
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 = AuthTokenModel().create(
737 token = UserModel().add_auth_token(
738 c.user.user_id, description, lifetime, role)
738 user=c.user.user_id,
739 lifetime_minutes=lifetime, role=role, description=description,
740 scope_callback=self.maybe_attach_token_scope)
739 token_data = token.get_api_data()
741 token_data = token.get_api_data()
740
742
741 self.maybe_attach_token_scope(token)
742 audit_logger.store_web(
743 audit_logger.store_web(
743 'user.edit.token.add', action_data={
744 'user.edit.token.add', action_data={
744 'data': {'token': token_data, 'user': user_data}},
745 'data': {'token': token_data, 'user': user_data}},
745 user=self._rhodecode_user, )
746 user=self._rhodecode_user, )
746 Session().commit()
747 Session().commit()
747
748
748 h.flash(_("Auth token successfully created"), category='success')
749 h.flash(_("Auth token successfully created"), category='success')
749 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))
750
751
751 @LoginRequired()
752 @LoginRequired()
752 @HasPermissionAllDecorator('hg.admin')
753 @HasPermissionAllDecorator('hg.admin')
753 @CSRFRequired()
754 @CSRFRequired()
754 @view_config(
755 @view_config(
755 route_name='edit_user_auth_tokens_delete', request_method='POST')
756 route_name='edit_user_auth_tokens_delete', request_method='POST')
756 def auth_tokens_delete(self):
757 def auth_tokens_delete(self):
757 _ = self.request.translate
758 _ = self.request.translate
758 c = self.load_default_context()
759 c = self.load_default_context()
759
760
760 user_id = self.db_user_id
761 user_id = self.db_user_id
761 c.user = self.db_user
762 c.user = self.db_user
762
763
763 user_data = c.user.get_api_data()
764 user_data = c.user.get_api_data()
764
765
765 del_auth_token = self.request.POST.get('del_auth_token')
766 del_auth_token = self.request.POST.get('del_auth_token')
766
767
767 if del_auth_token:
768 if del_auth_token:
768 token = UserApiKeys.get_or_404(del_auth_token)
769 token = UserApiKeys.get_or_404(del_auth_token)
769 token_data = token.get_api_data()
770 token_data = token.get_api_data()
770
771
771 AuthTokenModel().delete(del_auth_token, c.user.user_id)
772 AuthTokenModel().delete(del_auth_token, c.user.user_id)
772 audit_logger.store_web(
773 audit_logger.store_web(
773 'user.edit.token.delete', action_data={
774 'user.edit.token.delete', action_data={
774 'data': {'token': token_data, 'user': user_data}},
775 'data': {'token': token_data, 'user': user_data}},
775 user=self._rhodecode_user,)
776 user=self._rhodecode_user,)
776 Session().commit()
777 Session().commit()
777 h.flash(_("Auth token successfully deleted"), category='success')
778 h.flash(_("Auth token successfully deleted"), category='success')
778
779
779 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))
780
781
781 @LoginRequired()
782 @LoginRequired()
782 @HasPermissionAllDecorator('hg.admin')
783 @HasPermissionAllDecorator('hg.admin')
783 @view_config(
784 @view_config(
784 route_name='edit_user_ssh_keys', request_method='GET',
785 route_name='edit_user_ssh_keys', request_method='GET',
785 renderer='rhodecode:templates/admin/users/user_edit.mako')
786 renderer='rhodecode:templates/admin/users/user_edit.mako')
786 def ssh_keys(self):
787 def ssh_keys(self):
787 _ = self.request.translate
788 _ = self.request.translate
788 c = self.load_default_context()
789 c = self.load_default_context()
789 c.user = self.db_user
790 c.user = self.db_user
790
791
791 c.active = 'ssh_keys'
792 c.active = 'ssh_keys'
792 c.default_key = self.request.GET.get('default_key')
793 c.default_key = self.request.GET.get('default_key')
793 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)
794 return self._get_template_context(c)
795 return self._get_template_context(c)
795
796
796 @LoginRequired()
797 @LoginRequired()
797 @HasPermissionAllDecorator('hg.admin')
798 @HasPermissionAllDecorator('hg.admin')
798 @view_config(
799 @view_config(
799 route_name='edit_user_ssh_keys_generate_keypair', request_method='GET',
800 route_name='edit_user_ssh_keys_generate_keypair', request_method='GET',
800 renderer='rhodecode:templates/admin/users/user_edit.mako')
801 renderer='rhodecode:templates/admin/users/user_edit.mako')
801 def ssh_keys_generate_keypair(self):
802 def ssh_keys_generate_keypair(self):
802 _ = self.request.translate
803 _ = self.request.translate
803 c = self.load_default_context()
804 c = self.load_default_context()
804
805
805 c.user = self.db_user
806 c.user = self.db_user
806
807
807 c.active = 'ssh_keys_generate'
808 c.active = 'ssh_keys_generate'
808 comment = 'RhodeCode-SSH {}'.format(c.user.email or '')
809 comment = 'RhodeCode-SSH {}'.format(c.user.email or '')
809 c.private, c.public = SshKeyModel().generate_keypair(comment=comment)
810 c.private, c.public = SshKeyModel().generate_keypair(comment=comment)
810
811
811 return self._get_template_context(c)
812 return self._get_template_context(c)
812
813
813 @LoginRequired()
814 @LoginRequired()
814 @HasPermissionAllDecorator('hg.admin')
815 @HasPermissionAllDecorator('hg.admin')
815 @CSRFRequired()
816 @CSRFRequired()
816 @view_config(
817 @view_config(
817 route_name='edit_user_ssh_keys_add', request_method='POST')
818 route_name='edit_user_ssh_keys_add', request_method='POST')
818 def ssh_keys_add(self):
819 def ssh_keys_add(self):
819 _ = self.request.translate
820 _ = self.request.translate
820 c = self.load_default_context()
821 c = self.load_default_context()
821
822
822 user_id = self.db_user_id
823 user_id = self.db_user_id
823 c.user = self.db_user
824 c.user = self.db_user
824
825
825 user_data = c.user.get_api_data()
826 user_data = c.user.get_api_data()
826 key_data = self.request.POST.get('key_data')
827 key_data = self.request.POST.get('key_data')
827 description = self.request.POST.get('description')
828 description = self.request.POST.get('description')
828
829
829 fingerprint = 'unknown'
830 fingerprint = 'unknown'
830 try:
831 try:
831 if not key_data:
832 if not key_data:
832 raise ValueError('Please add a valid public key')
833 raise ValueError('Please add a valid public key')
833
834
834 key = SshKeyModel().parse_key(key_data.strip())
835 key = SshKeyModel().parse_key(key_data.strip())
835 fingerprint = key.hash_md5()
836 fingerprint = key.hash_md5()
836
837
837 ssh_key = SshKeyModel().create(
838 ssh_key = SshKeyModel().create(
838 c.user.user_id, fingerprint, key.keydata, description)
839 c.user.user_id, fingerprint, key.keydata, description)
839 ssh_key_data = ssh_key.get_api_data()
840 ssh_key_data = ssh_key.get_api_data()
840
841
841 audit_logger.store_web(
842 audit_logger.store_web(
842 'user.edit.ssh_key.add', action_data={
843 'user.edit.ssh_key.add', action_data={
843 'data': {'ssh_key': ssh_key_data, 'user': user_data}},
844 'data': {'ssh_key': ssh_key_data, 'user': user_data}},
844 user=self._rhodecode_user, )
845 user=self._rhodecode_user, )
845 Session().commit()
846 Session().commit()
846
847
847 # Trigger an event on change of keys.
848 # Trigger an event on change of keys.
848 trigger(SshKeyFileChangeEvent(), self.request.registry)
849 trigger(SshKeyFileChangeEvent(), self.request.registry)
849
850
850 h.flash(_("Ssh Key successfully created"), category='success')
851 h.flash(_("Ssh Key successfully created"), category='success')
851
852
852 except IntegrityError:
853 except IntegrityError:
853 log.exception("Exception during ssh key saving")
854 log.exception("Exception during ssh key saving")
854 err = 'Such key with fingerprint `{}` already exists, ' \
855 err = 'Such key with fingerprint `{}` already exists, ' \
855 'please use a different one'.format(fingerprint)
856 'please use a different one'.format(fingerprint)
856 h.flash(_('An error occurred during ssh key saving: {}').format(err),
857 h.flash(_('An error occurred during ssh key saving: {}').format(err),
857 category='error')
858 category='error')
858 except Exception as e:
859 except Exception as e:
859 log.exception("Exception during ssh key saving")
860 log.exception("Exception during ssh key saving")
860 h.flash(_('An error occurred during ssh key saving: {}').format(e),
861 h.flash(_('An error occurred during ssh key saving: {}').format(e),
861 category='error')
862 category='error')
862
863
863 return HTTPFound(
864 return HTTPFound(
864 h.route_path('edit_user_ssh_keys', user_id=user_id))
865 h.route_path('edit_user_ssh_keys', user_id=user_id))
865
866
866 @LoginRequired()
867 @LoginRequired()
867 @HasPermissionAllDecorator('hg.admin')
868 @HasPermissionAllDecorator('hg.admin')
868 @CSRFRequired()
869 @CSRFRequired()
869 @view_config(
870 @view_config(
870 route_name='edit_user_ssh_keys_delete', request_method='POST')
871 route_name='edit_user_ssh_keys_delete', request_method='POST')
871 def ssh_keys_delete(self):
872 def ssh_keys_delete(self):
872 _ = self.request.translate
873 _ = self.request.translate
873 c = self.load_default_context()
874 c = self.load_default_context()
874
875
875 user_id = self.db_user_id
876 user_id = self.db_user_id
876 c.user = self.db_user
877 c.user = self.db_user
877
878
878 user_data = c.user.get_api_data()
879 user_data = c.user.get_api_data()
879
880
880 del_ssh_key = self.request.POST.get('del_ssh_key')
881 del_ssh_key = self.request.POST.get('del_ssh_key')
881
882
882 if del_ssh_key:
883 if del_ssh_key:
883 ssh_key = UserSshKeys.get_or_404(del_ssh_key)
884 ssh_key = UserSshKeys.get_or_404(del_ssh_key)
884 ssh_key_data = ssh_key.get_api_data()
885 ssh_key_data = ssh_key.get_api_data()
885
886
886 SshKeyModel().delete(del_ssh_key, c.user.user_id)
887 SshKeyModel().delete(del_ssh_key, c.user.user_id)
887 audit_logger.store_web(
888 audit_logger.store_web(
888 'user.edit.ssh_key.delete', action_data={
889 'user.edit.ssh_key.delete', action_data={
889 'data': {'ssh_key': ssh_key_data, 'user': user_data}},
890 'data': {'ssh_key': ssh_key_data, 'user': user_data}},
890 user=self._rhodecode_user,)
891 user=self._rhodecode_user,)
891 Session().commit()
892 Session().commit()
892 # Trigger an event on change of keys.
893 # Trigger an event on change of keys.
893 trigger(SshKeyFileChangeEvent(), self.request.registry)
894 trigger(SshKeyFileChangeEvent(), self.request.registry)
894 h.flash(_("Ssh key successfully deleted"), category='success')
895 h.flash(_("Ssh key successfully deleted"), category='success')
895
896
896 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))
897
898
898 @LoginRequired()
899 @LoginRequired()
899 @HasPermissionAllDecorator('hg.admin')
900 @HasPermissionAllDecorator('hg.admin')
900 @view_config(
901 @view_config(
901 route_name='edit_user_emails', request_method='GET',
902 route_name='edit_user_emails', request_method='GET',
902 renderer='rhodecode:templates/admin/users/user_edit.mako')
903 renderer='rhodecode:templates/admin/users/user_edit.mako')
903 def emails(self):
904 def emails(self):
904 _ = self.request.translate
905 _ = self.request.translate
905 c = self.load_default_context()
906 c = self.load_default_context()
906 c.user = self.db_user
907 c.user = self.db_user
907
908
908 c.active = 'emails'
909 c.active = 'emails'
909 c.user_email_map = UserEmailMap.query() \
910 c.user_email_map = UserEmailMap.query() \
910 .filter(UserEmailMap.user == c.user).all()
911 .filter(UserEmailMap.user == c.user).all()
911
912
912 return self._get_template_context(c)
913 return self._get_template_context(c)
913
914
914 @LoginRequired()
915 @LoginRequired()
915 @HasPermissionAllDecorator('hg.admin')
916 @HasPermissionAllDecorator('hg.admin')
916 @CSRFRequired()
917 @CSRFRequired()
917 @view_config(
918 @view_config(
918 route_name='edit_user_emails_add', request_method='POST')
919 route_name='edit_user_emails_add', request_method='POST')
919 def emails_add(self):
920 def emails_add(self):
920 _ = self.request.translate
921 _ = self.request.translate
921 c = self.load_default_context()
922 c = self.load_default_context()
922
923
923 user_id = self.db_user_id
924 user_id = self.db_user_id
924 c.user = self.db_user
925 c.user = self.db_user
925
926
926 email = self.request.POST.get('new_email')
927 email = self.request.POST.get('new_email')
927 user_data = c.user.get_api_data()
928 user_data = c.user.get_api_data()
928 try:
929 try:
929
930
930 form = UserExtraEmailForm(self.request.translate)()
931 form = UserExtraEmailForm(self.request.translate)()
931 data = form.to_python({'email': email})
932 data = form.to_python({'email': email})
932 email = data['email']
933 email = data['email']
933
934
934 UserModel().add_extra_email(c.user.user_id, email)
935 UserModel().add_extra_email(c.user.user_id, email)
935 audit_logger.store_web(
936 audit_logger.store_web(
936 'user.edit.email.add',
937 'user.edit.email.add',
937 action_data={'email': email, 'user': user_data},
938 action_data={'email': email, 'user': user_data},
938 user=self._rhodecode_user)
939 user=self._rhodecode_user)
939 Session().commit()
940 Session().commit()
940 h.flash(_("Added new email address `%s` for user account") % email,
941 h.flash(_("Added new email address `%s` for user account") % email,
941 category='success')
942 category='success')
942 except formencode.Invalid as error:
943 except formencode.Invalid as error:
943 h.flash(h.escape(error.error_dict['email']), category='error')
944 h.flash(h.escape(error.error_dict['email']), category='error')
944 except IntegrityError:
945 except IntegrityError:
945 log.warning("Email %s already exists", email)
946 log.warning("Email %s already exists", email)
946 h.flash(_('Email `{}` is already registered for another user.').format(email),
947 h.flash(_('Email `{}` is already registered for another user.').format(email),
947 category='error')
948 category='error')
948 except Exception:
949 except Exception:
949 log.exception("Exception during email saving")
950 log.exception("Exception during email saving")
950 h.flash(_('An error occurred during email saving'),
951 h.flash(_('An error occurred during email saving'),
951 category='error')
952 category='error')
952 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))
953
954
954 @LoginRequired()
955 @LoginRequired()
955 @HasPermissionAllDecorator('hg.admin')
956 @HasPermissionAllDecorator('hg.admin')
956 @CSRFRequired()
957 @CSRFRequired()
957 @view_config(
958 @view_config(
958 route_name='edit_user_emails_delete', request_method='POST')
959 route_name='edit_user_emails_delete', request_method='POST')
959 def emails_delete(self):
960 def emails_delete(self):
960 _ = self.request.translate
961 _ = self.request.translate
961 c = self.load_default_context()
962 c = self.load_default_context()
962
963
963 user_id = self.db_user_id
964 user_id = self.db_user_id
964 c.user = self.db_user
965 c.user = self.db_user
965
966
966 email_id = self.request.POST.get('del_email_id')
967 email_id = self.request.POST.get('del_email_id')
967 user_model = UserModel()
968 user_model = UserModel()
968
969
969 email = UserEmailMap.query().get(email_id).email
970 email = UserEmailMap.query().get(email_id).email
970 user_data = c.user.get_api_data()
971 user_data = c.user.get_api_data()
971 user_model.delete_extra_email(c.user.user_id, email_id)
972 user_model.delete_extra_email(c.user.user_id, email_id)
972 audit_logger.store_web(
973 audit_logger.store_web(
973 'user.edit.email.delete',
974 'user.edit.email.delete',
974 action_data={'email': email, 'user': user_data},
975 action_data={'email': email, 'user': user_data},
975 user=self._rhodecode_user)
976 user=self._rhodecode_user)
976 Session().commit()
977 Session().commit()
977 h.flash(_("Removed email address from user account"),
978 h.flash(_("Removed email address from user account"),
978 category='success')
979 category='success')
979 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))
980
981
981 @LoginRequired()
982 @LoginRequired()
982 @HasPermissionAllDecorator('hg.admin')
983 @HasPermissionAllDecorator('hg.admin')
983 @view_config(
984 @view_config(
984 route_name='edit_user_ips', request_method='GET',
985 route_name='edit_user_ips', request_method='GET',
985 renderer='rhodecode:templates/admin/users/user_edit.mako')
986 renderer='rhodecode:templates/admin/users/user_edit.mako')
986 def ips(self):
987 def ips(self):
987 _ = self.request.translate
988 _ = self.request.translate
988 c = self.load_default_context()
989 c = self.load_default_context()
989 c.user = self.db_user
990 c.user = self.db_user
990
991
991 c.active = 'ips'
992 c.active = 'ips'
992 c.user_ip_map = UserIpMap.query() \
993 c.user_ip_map = UserIpMap.query() \
993 .filter(UserIpMap.user == c.user).all()
994 .filter(UserIpMap.user == c.user).all()
994
995
995 c.inherit_default_ips = c.user.inherit_default_permissions
996 c.inherit_default_ips = c.user.inherit_default_permissions
996 c.default_user_ip_map = UserIpMap.query() \
997 c.default_user_ip_map = UserIpMap.query() \
997 .filter(UserIpMap.user == User.get_default_user()).all()
998 .filter(UserIpMap.user == User.get_default_user()).all()
998
999
999 return self._get_template_context(c)
1000 return self._get_template_context(c)
1000
1001
1001 @LoginRequired()
1002 @LoginRequired()
1002 @HasPermissionAllDecorator('hg.admin')
1003 @HasPermissionAllDecorator('hg.admin')
1003 @CSRFRequired()
1004 @CSRFRequired()
1004 @view_config(
1005 @view_config(
1005 route_name='edit_user_ips_add', request_method='POST')
1006 route_name='edit_user_ips_add', request_method='POST')
1006 # 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
1007 # edit their IP white list
1008 # edit their IP white list
1008 def ips_add(self):
1009 def ips_add(self):
1009 _ = self.request.translate
1010 _ = self.request.translate
1010 c = self.load_default_context()
1011 c = self.load_default_context()
1011
1012
1012 user_id = self.db_user_id
1013 user_id = self.db_user_id
1013 c.user = self.db_user
1014 c.user = self.db_user
1014
1015
1015 user_model = UserModel()
1016 user_model = UserModel()
1016 desc = self.request.POST.get('description')
1017 desc = self.request.POST.get('description')
1017 try:
1018 try:
1018 ip_list = user_model.parse_ip_range(
1019 ip_list = user_model.parse_ip_range(
1019 self.request.POST.get('new_ip'))
1020 self.request.POST.get('new_ip'))
1020 except Exception as e:
1021 except Exception as e:
1021 ip_list = []
1022 ip_list = []
1022 log.exception("Exception during ip saving")
1023 log.exception("Exception during ip saving")
1023 h.flash(_('An error occurred during ip saving:%s' % (e,)),
1024 h.flash(_('An error occurred during ip saving:%s' % (e,)),
1024 category='error')
1025 category='error')
1025 added = []
1026 added = []
1026 user_data = c.user.get_api_data()
1027 user_data = c.user.get_api_data()
1027 for ip in ip_list:
1028 for ip in ip_list:
1028 try:
1029 try:
1029 form = UserExtraIpForm(self.request.translate)()
1030 form = UserExtraIpForm(self.request.translate)()
1030 data = form.to_python({'ip': ip})
1031 data = form.to_python({'ip': ip})
1031 ip = data['ip']
1032 ip = data['ip']
1032
1033
1033 user_model.add_extra_ip(c.user.user_id, ip, desc)
1034 user_model.add_extra_ip(c.user.user_id, ip, desc)
1034 audit_logger.store_web(
1035 audit_logger.store_web(
1035 'user.edit.ip.add',
1036 'user.edit.ip.add',
1036 action_data={'ip': ip, 'user': user_data},
1037 action_data={'ip': ip, 'user': user_data},
1037 user=self._rhodecode_user)
1038 user=self._rhodecode_user)
1038 Session().commit()
1039 Session().commit()
1039 added.append(ip)
1040 added.append(ip)
1040 except formencode.Invalid as error:
1041 except formencode.Invalid as error:
1041 msg = error.error_dict['ip']
1042 msg = error.error_dict['ip']
1042 h.flash(msg, category='error')
1043 h.flash(msg, category='error')
1043 except Exception:
1044 except Exception:
1044 log.exception("Exception during ip saving")
1045 log.exception("Exception during ip saving")
1045 h.flash(_('An error occurred during ip saving'),
1046 h.flash(_('An error occurred during ip saving'),
1046 category='error')
1047 category='error')
1047 if added:
1048 if added:
1048 h.flash(
1049 h.flash(
1049 _("Added ips %s to user whitelist") % (', '.join(ip_list), ),
1050 _("Added ips %s to user whitelist") % (', '.join(ip_list), ),
1050 category='success')
1051 category='success')
1051 if 'default_user' in self.request.POST:
1052 if 'default_user' in self.request.POST:
1052 # 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
1053 raise HTTPFound(h.route_path('admin_permissions_ips'))
1054 raise HTTPFound(h.route_path('admin_permissions_ips'))
1054 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))
1055
1056
1056 @LoginRequired()
1057 @LoginRequired()
1057 @HasPermissionAllDecorator('hg.admin')
1058 @HasPermissionAllDecorator('hg.admin')
1058 @CSRFRequired()
1059 @CSRFRequired()
1059 @view_config(
1060 @view_config(
1060 route_name='edit_user_ips_delete', request_method='POST')
1061 route_name='edit_user_ips_delete', request_method='POST')
1061 # 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
1062 # edit their IP white list
1063 # edit their IP white list
1063 def ips_delete(self):
1064 def ips_delete(self):
1064 _ = self.request.translate
1065 _ = self.request.translate
1065 c = self.load_default_context()
1066 c = self.load_default_context()
1066
1067
1067 user_id = self.db_user_id
1068 user_id = self.db_user_id
1068 c.user = self.db_user
1069 c.user = self.db_user
1069
1070
1070 ip_id = self.request.POST.get('del_ip_id')
1071 ip_id = self.request.POST.get('del_ip_id')
1071 user_model = UserModel()
1072 user_model = UserModel()
1072 user_data = c.user.get_api_data()
1073 user_data = c.user.get_api_data()
1073 ip = UserIpMap.query().get(ip_id).ip_addr
1074 ip = UserIpMap.query().get(ip_id).ip_addr
1074 user_model.delete_extra_ip(c.user.user_id, ip_id)
1075 user_model.delete_extra_ip(c.user.user_id, ip_id)
1075 audit_logger.store_web(
1076 audit_logger.store_web(
1076 'user.edit.ip.delete', action_data={'ip': ip, 'user': user_data},
1077 'user.edit.ip.delete', action_data={'ip': ip, 'user': user_data},
1077 user=self._rhodecode_user)
1078 user=self._rhodecode_user)
1078 Session().commit()
1079 Session().commit()
1079 h.flash(_("Removed ip address from user whitelist"), category='success')
1080 h.flash(_("Removed ip address from user whitelist"), category='success')
1080
1081
1081 if 'default_user' in self.request.POST:
1082 if 'default_user' in self.request.POST:
1082 # 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
1083 raise HTTPFound(h.route_path('admin_permissions_ips'))
1084 raise HTTPFound(h.route_path('admin_permissions_ips'))
1084 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))
1085
1086
1086 @LoginRequired()
1087 @LoginRequired()
1087 @HasPermissionAllDecorator('hg.admin')
1088 @HasPermissionAllDecorator('hg.admin')
1088 @view_config(
1089 @view_config(
1089 route_name='edit_user_groups_management', request_method='GET',
1090 route_name='edit_user_groups_management', request_method='GET',
1090 renderer='rhodecode:templates/admin/users/user_edit.mako')
1091 renderer='rhodecode:templates/admin/users/user_edit.mako')
1091 def groups_management(self):
1092 def groups_management(self):
1092 c = self.load_default_context()
1093 c = self.load_default_context()
1093 c.user = self.db_user
1094 c.user = self.db_user
1094 c.data = c.user.group_member
1095 c.data = c.user.group_member
1095
1096
1096 groups = [UserGroupModel.get_user_groups_as_dict(group.users_group)
1097 groups = [UserGroupModel.get_user_groups_as_dict(group.users_group)
1097 for group in c.user.group_member]
1098 for group in c.user.group_member]
1098 c.groups = json.dumps(groups)
1099 c.groups = json.dumps(groups)
1099 c.active = 'groups'
1100 c.active = 'groups'
1100
1101
1101 return self._get_template_context(c)
1102 return self._get_template_context(c)
1102
1103
1103 @LoginRequired()
1104 @LoginRequired()
1104 @HasPermissionAllDecorator('hg.admin')
1105 @HasPermissionAllDecorator('hg.admin')
1105 @CSRFRequired()
1106 @CSRFRequired()
1106 @view_config(
1107 @view_config(
1107 route_name='edit_user_groups_management_updates', request_method='POST')
1108 route_name='edit_user_groups_management_updates', request_method='POST')
1108 def groups_management_updates(self):
1109 def groups_management_updates(self):
1109 _ = self.request.translate
1110 _ = self.request.translate
1110 c = self.load_default_context()
1111 c = self.load_default_context()
1111
1112
1112 user_id = self.db_user_id
1113 user_id = self.db_user_id
1113 c.user = self.db_user
1114 c.user = self.db_user
1114
1115
1115 user_groups = set(self.request.POST.getall('users_group_id'))
1116 user_groups = set(self.request.POST.getall('users_group_id'))
1116 user_groups_objects = []
1117 user_groups_objects = []
1117
1118
1118 for ugid in user_groups:
1119 for ugid in user_groups:
1119 user_groups_objects.append(
1120 user_groups_objects.append(
1120 UserGroupModel().get_group(safe_int(ugid)))
1121 UserGroupModel().get_group(safe_int(ugid)))
1121 user_group_model = UserGroupModel()
1122 user_group_model = UserGroupModel()
1122 added_to_groups, removed_from_groups = \
1123 added_to_groups, removed_from_groups = \
1123 user_group_model.change_groups(c.user, user_groups_objects)
1124 user_group_model.change_groups(c.user, user_groups_objects)
1124
1125
1125 user_data = c.user.get_api_data()
1126 user_data = c.user.get_api_data()
1126 for user_group_id in added_to_groups:
1127 for user_group_id in added_to_groups:
1127 user_group = UserGroup.get(user_group_id)
1128 user_group = UserGroup.get(user_group_id)
1128 old_values = user_group.get_api_data()
1129 old_values = user_group.get_api_data()
1129 audit_logger.store_web(
1130 audit_logger.store_web(
1130 'user_group.edit.member.add',
1131 'user_group.edit.member.add',
1131 action_data={'user': user_data, 'old_data': old_values},
1132 action_data={'user': user_data, 'old_data': old_values},
1132 user=self._rhodecode_user)
1133 user=self._rhodecode_user)
1133
1134
1134 for user_group_id in removed_from_groups:
1135 for user_group_id in removed_from_groups:
1135 user_group = UserGroup.get(user_group_id)
1136 user_group = UserGroup.get(user_group_id)
1136 old_values = user_group.get_api_data()
1137 old_values = user_group.get_api_data()
1137 audit_logger.store_web(
1138 audit_logger.store_web(
1138 'user_group.edit.member.delete',
1139 'user_group.edit.member.delete',
1139 action_data={'user': user_data, 'old_data': old_values},
1140 action_data={'user': user_data, 'old_data': old_values},
1140 user=self._rhodecode_user)
1141 user=self._rhodecode_user)
1141
1142
1142 Session().commit()
1143 Session().commit()
1143 c.active = 'user_groups_management'
1144 c.active = 'user_groups_management'
1144 h.flash(_("Groups successfully changed"), category='success')
1145 h.flash(_("Groups successfully changed"), category='success')
1145
1146
1146 return HTTPFound(h.route_path(
1147 return HTTPFound(h.route_path(
1147 'edit_user_groups_management', user_id=user_id))
1148 'edit_user_groups_management', user_id=user_id))
1148
1149
1149 @LoginRequired()
1150 @LoginRequired()
1150 @HasPermissionAllDecorator('hg.admin')
1151 @HasPermissionAllDecorator('hg.admin')
1151 @view_config(
1152 @view_config(
1152 route_name='edit_user_audit_logs', request_method='GET',
1153 route_name='edit_user_audit_logs', request_method='GET',
1153 renderer='rhodecode:templates/admin/users/user_edit.mako')
1154 renderer='rhodecode:templates/admin/users/user_edit.mako')
1154 def user_audit_logs(self):
1155 def user_audit_logs(self):
1155 _ = self.request.translate
1156 _ = self.request.translate
1156 c = self.load_default_context()
1157 c = self.load_default_context()
1157 c.user = self.db_user
1158 c.user = self.db_user
1158
1159
1159 c.active = 'audit'
1160 c.active = 'audit'
1160
1161
1161 p = safe_int(self.request.GET.get('page', 1), 1)
1162 p = safe_int(self.request.GET.get('page', 1), 1)
1162
1163
1163 filter_term = self.request.GET.get('filter')
1164 filter_term = self.request.GET.get('filter')
1164 user_log = UserModel().get_user_log(c.user, filter_term)
1165 user_log = UserModel().get_user_log(c.user, filter_term)
1165
1166
1166 def url_generator(**kw):
1167 def url_generator(**kw):
1167 if filter_term:
1168 if filter_term:
1168 kw['filter'] = filter_term
1169 kw['filter'] = filter_term
1169 return self.request.current_route_path(_query=kw)
1170 return self.request.current_route_path(_query=kw)
1170
1171
1171 c.audit_logs = h.Page(
1172 c.audit_logs = h.Page(
1172 user_log, page=p, items_per_page=10, url=url_generator)
1173 user_log, page=p, items_per_page=10, url=url_generator)
1173 c.filter_term = filter_term
1174 c.filter_term = filter_term
1174 return self._get_template_context(c)
1175 return self._get_template_context(c)
1175
1176
1176 @LoginRequired()
1177 @LoginRequired()
1177 @HasPermissionAllDecorator('hg.admin')
1178 @HasPermissionAllDecorator('hg.admin')
1178 @view_config(
1179 @view_config(
1179 route_name='edit_user_perms_summary', request_method='GET',
1180 route_name='edit_user_perms_summary', request_method='GET',
1180 renderer='rhodecode:templates/admin/users/user_edit.mako')
1181 renderer='rhodecode:templates/admin/users/user_edit.mako')
1181 def user_perms_summary(self):
1182 def user_perms_summary(self):
1182 _ = self.request.translate
1183 _ = self.request.translate
1183 c = self.load_default_context()
1184 c = self.load_default_context()
1184 c.user = self.db_user
1185 c.user = self.db_user
1185
1186
1186 c.active = 'perms_summary'
1187 c.active = 'perms_summary'
1187 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)
1188
1189
1189 return self._get_template_context(c)
1190 return self._get_template_context(c)
1190
1191
1191 @LoginRequired()
1192 @LoginRequired()
1192 @HasPermissionAllDecorator('hg.admin')
1193 @HasPermissionAllDecorator('hg.admin')
1193 @view_config(
1194 @view_config(
1194 route_name='edit_user_perms_summary_json', request_method='GET',
1195 route_name='edit_user_perms_summary_json', request_method='GET',
1195 renderer='json_ext')
1196 renderer='json_ext')
1196 def user_perms_summary_json(self):
1197 def user_perms_summary_json(self):
1197 self.load_default_context()
1198 self.load_default_context()
1198 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)
1199
1200
1200 return perm_user.permissions
1201 return perm_user.permissions
1201
1202
1202 @LoginRequired()
1203 @LoginRequired()
1203 @HasPermissionAllDecorator('hg.admin')
1204 @HasPermissionAllDecorator('hg.admin')
1204 @view_config(
1205 @view_config(
1205 route_name='edit_user_caches', request_method='GET',
1206 route_name='edit_user_caches', request_method='GET',
1206 renderer='rhodecode:templates/admin/users/user_edit.mako')
1207 renderer='rhodecode:templates/admin/users/user_edit.mako')
1207 def user_caches(self):
1208 def user_caches(self):
1208 _ = self.request.translate
1209 _ = self.request.translate
1209 c = self.load_default_context()
1210 c = self.load_default_context()
1210 c.user = self.db_user
1211 c.user = self.db_user
1211
1212
1212 c.active = 'caches'
1213 c.active = 'caches'
1213 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)
1214
1215
1215 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)
1216 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)
1217 c.backend = c.region.backend
1218 c.backend = c.region.backend
1218 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))
1219
1220
1220 return self._get_template_context(c)
1221 return self._get_template_context(c)
1221
1222
1222 @LoginRequired()
1223 @LoginRequired()
1223 @HasPermissionAllDecorator('hg.admin')
1224 @HasPermissionAllDecorator('hg.admin')
1224 @CSRFRequired()
1225 @CSRFRequired()
1225 @view_config(
1226 @view_config(
1226 route_name='edit_user_caches_update', request_method='POST')
1227 route_name='edit_user_caches_update', request_method='POST')
1227 def user_caches_update(self):
1228 def user_caches_update(self):
1228 _ = self.request.translate
1229 _ = self.request.translate
1229 c = self.load_default_context()
1230 c = self.load_default_context()
1230 c.user = self.db_user
1231 c.user = self.db_user
1231
1232
1232 c.active = 'caches'
1233 c.active = 'caches'
1233 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)
1234
1235
1235 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)
1236 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)
1237
1238
1238 h.flash(_("Deleted {} cache keys").format(del_keys), category='success')
1239 h.flash(_("Deleted {} cache keys").format(del_keys), category='success')
1239
1240
1240 return HTTPFound(h.route_path(
1241 return HTTPFound(h.route_path(
1241 'edit_user_caches', user_id=c.user.user_id))
1242 'edit_user_caches', user_id=c.user.user_id))
@@ -1,460 +1,461 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.events import UserRegistered, trigger
35 from rhodecode.events import UserRegistered, trigger
36 from rhodecode.lib import helpers as h
36 from rhodecode.lib import helpers as h
37 from rhodecode.lib import audit_logger
37 from rhodecode.lib import audit_logger
38 from rhodecode.lib.auth import (
38 from rhodecode.lib.auth import (
39 AuthUser, HasPermissionAnyDecorator, CSRFRequired)
39 AuthUser, HasPermissionAnyDecorator, CSRFRequired)
40 from rhodecode.lib.base import get_ip_addr
40 from rhodecode.lib.base import get_ip_addr
41 from rhodecode.lib.exceptions import UserCreationError
41 from rhodecode.lib.exceptions import UserCreationError
42 from rhodecode.lib.utils2 import safe_str
42 from rhodecode.lib.utils2 import safe_str
43 from rhodecode.model.db import User, UserApiKeys
43 from rhodecode.model.db import User, UserApiKeys
44 from rhodecode.model.forms import LoginForm, RegisterForm, PasswordResetForm
44 from rhodecode.model.forms import LoginForm, RegisterForm, PasswordResetForm
45 from rhodecode.model.meta import Session
45 from rhodecode.model.meta import Session
46 from rhodecode.model.auth_token import AuthTokenModel
46 from rhodecode.model.auth_token import AuthTokenModel
47 from rhodecode.model.settings import SettingsModel
47 from rhodecode.model.settings import SettingsModel
48 from rhodecode.model.user import UserModel
48 from rhodecode.model.user import UserModel
49 from rhodecode.translation import _
49 from rhodecode.translation import _
50
50
51
51
52 log = logging.getLogger(__name__)
52 log = logging.getLogger(__name__)
53
53
54 CaptchaData = collections.namedtuple(
54 CaptchaData = collections.namedtuple(
55 'CaptchaData', 'active, private_key, public_key')
55 'CaptchaData', 'active, private_key, public_key')
56
56
57
57
58 def _store_user_in_session(session, username, remember=False):
58 def _store_user_in_session(session, username, remember=False):
59 user = User.get_by_username(username, case_insensitive=True)
59 user = User.get_by_username(username, case_insensitive=True)
60 auth_user = AuthUser(user.user_id)
60 auth_user = AuthUser(user.user_id)
61 auth_user.set_authenticated()
61 auth_user.set_authenticated()
62 cs = auth_user.get_cookie_store()
62 cs = auth_user.get_cookie_store()
63 session['rhodecode_user'] = cs
63 session['rhodecode_user'] = cs
64 user.update_lastlogin()
64 user.update_lastlogin()
65 Session().commit()
65 Session().commit()
66
66
67 # If they want to be remembered, update the cookie
67 # If they want to be remembered, update the cookie
68 if remember:
68 if remember:
69 _year = (datetime.datetime.now() +
69 _year = (datetime.datetime.now() +
70 datetime.timedelta(seconds=60 * 60 * 24 * 365))
70 datetime.timedelta(seconds=60 * 60 * 24 * 365))
71 session._set_cookie_expires(_year)
71 session._set_cookie_expires(_year)
72
72
73 session.save()
73 session.save()
74
74
75 safe_cs = cs.copy()
75 safe_cs = cs.copy()
76 safe_cs['password'] = '****'
76 safe_cs['password'] = '****'
77 log.info('user %s is now authenticated and stored in '
77 log.info('user %s is now authenticated and stored in '
78 'session, session attrs %s', username, safe_cs)
78 'session, session attrs %s', username, safe_cs)
79
79
80 # dumps session attrs back to cookie
80 # dumps session attrs back to cookie
81 session._update_cookie_out()
81 session._update_cookie_out()
82 # we set new cookie
82 # we set new cookie
83 headers = None
83 headers = None
84 if session.request['set_cookie']:
84 if session.request['set_cookie']:
85 # send set-cookie headers back to response to update cookie
85 # send set-cookie headers back to response to update cookie
86 headers = [('Set-Cookie', session.request['cookie_out'])]
86 headers = [('Set-Cookie', session.request['cookie_out'])]
87 return headers
87 return headers
88
88
89
89
90 def get_came_from(request):
90 def get_came_from(request):
91 came_from = safe_str(request.GET.get('came_from', ''))
91 came_from = safe_str(request.GET.get('came_from', ''))
92 parsed = urlparse.urlparse(came_from)
92 parsed = urlparse.urlparse(came_from)
93 allowed_schemes = ['http', 'https']
93 allowed_schemes = ['http', 'https']
94 default_came_from = h.route_path('home')
94 default_came_from = h.route_path('home')
95 if parsed.scheme and parsed.scheme not in allowed_schemes:
95 if parsed.scheme and parsed.scheme not in allowed_schemes:
96 log.error('Suspicious URL scheme detected %s for url %s' %
96 log.error('Suspicious URL scheme detected %s for url %s' %
97 (parsed.scheme, parsed))
97 (parsed.scheme, parsed))
98 came_from = default_came_from
98 came_from = default_came_from
99 elif parsed.netloc and request.host != parsed.netloc:
99 elif parsed.netloc and request.host != parsed.netloc:
100 log.error('Suspicious NETLOC detected %s for url %s server url '
100 log.error('Suspicious NETLOC detected %s for url %s server url '
101 'is: %s' % (parsed.netloc, parsed, request.host))
101 'is: %s' % (parsed.netloc, parsed, request.host))
102 came_from = default_came_from
102 came_from = default_came_from
103 elif any(bad_str in parsed.path for bad_str in ('\r', '\n')):
103 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 ' %
104 log.error('Header injection detected `%s` for url %s server url ' %
105 (parsed.path, parsed))
105 (parsed.path, parsed))
106 came_from = default_came_from
106 came_from = default_came_from
107
107
108 return came_from or default_came_from
108 return came_from or default_came_from
109
109
110
110
111 class LoginView(BaseAppView):
111 class LoginView(BaseAppView):
112
112
113 def load_default_context(self):
113 def load_default_context(self):
114 c = self._get_local_tmpl_context()
114 c = self._get_local_tmpl_context()
115 c.came_from = get_came_from(self.request)
115 c.came_from = get_came_from(self.request)
116
116
117 return c
117 return c
118
118
119 def _get_captcha_data(self):
119 def _get_captcha_data(self):
120 settings = SettingsModel().get_all_settings()
120 settings = SettingsModel().get_all_settings()
121 private_key = settings.get('rhodecode_captcha_private_key')
121 private_key = settings.get('rhodecode_captcha_private_key')
122 public_key = settings.get('rhodecode_captcha_public_key')
122 public_key = settings.get('rhodecode_captcha_public_key')
123 active = bool(private_key)
123 active = bool(private_key)
124 return CaptchaData(
124 return CaptchaData(
125 active=active, private_key=private_key, public_key=public_key)
125 active=active, private_key=private_key, public_key=public_key)
126
126
127 def validate_captcha(self, private_key):
127 def validate_captcha(self, private_key):
128
128
129 captcha_rs = self.request.POST.get('g-recaptcha-response')
129 captcha_rs = self.request.POST.get('g-recaptcha-response')
130 url = "https://www.google.com/recaptcha/api/siteverify"
130 url = "https://www.google.com/recaptcha/api/siteverify"
131 params = {
131 params = {
132 'secret': private_key,
132 'secret': private_key,
133 'response': captcha_rs,
133 'response': captcha_rs,
134 'remoteip': get_ip_addr(self.request.environ)
134 'remoteip': get_ip_addr(self.request.environ)
135 }
135 }
136 verify_rs = requests.get(url, params=params, verify=True, timeout=60)
136 verify_rs = requests.get(url, params=params, verify=True, timeout=60)
137 verify_rs = verify_rs.json()
137 verify_rs = verify_rs.json()
138 captcha_status = verify_rs.get('success', False)
138 captcha_status = verify_rs.get('success', False)
139 captcha_errors = verify_rs.get('error-codes', [])
139 captcha_errors = verify_rs.get('error-codes', [])
140 if not isinstance(captcha_errors, list):
140 if not isinstance(captcha_errors, list):
141 captcha_errors = [captcha_errors]
141 captcha_errors = [captcha_errors]
142 captcha_errors = ', '.join(captcha_errors)
142 captcha_errors = ', '.join(captcha_errors)
143 captcha_message = ''
143 captcha_message = ''
144 if captcha_status is False:
144 if captcha_status is False:
145 captcha_message = "Bad captcha. Errors: {}".format(
145 captcha_message = "Bad captcha. Errors: {}".format(
146 captcha_errors)
146 captcha_errors)
147
147
148 return captcha_status, captcha_message
148 return captcha_status, captcha_message
149
149
150 @view_config(
150 @view_config(
151 route_name='login', request_method='GET',
151 route_name='login', request_method='GET',
152 renderer='rhodecode:templates/login.mako')
152 renderer='rhodecode:templates/login.mako')
153 def login(self):
153 def login(self):
154 c = self.load_default_context()
154 c = self.load_default_context()
155 auth_user = self._rhodecode_user
155 auth_user = self._rhodecode_user
156
156
157 # redirect if already logged in
157 # redirect if already logged in
158 if (auth_user.is_authenticated and
158 if (auth_user.is_authenticated and
159 not auth_user.is_default and auth_user.ip_allowed):
159 not auth_user.is_default and auth_user.ip_allowed):
160 raise HTTPFound(c.came_from)
160 raise HTTPFound(c.came_from)
161
161
162 # check if we use headers plugin, and try to login using it.
162 # check if we use headers plugin, and try to login using it.
163 try:
163 try:
164 log.debug('Running PRE-AUTH for headers based authentication')
164 log.debug('Running PRE-AUTH for headers based authentication')
165 auth_info = authenticate(
165 auth_info = authenticate(
166 '', '', self.request.environ, HTTP_TYPE, skip_missing=True)
166 '', '', self.request.environ, HTTP_TYPE, skip_missing=True)
167 if auth_info:
167 if auth_info:
168 headers = _store_user_in_session(
168 headers = _store_user_in_session(
169 self.session, auth_info.get('username'))
169 self.session, auth_info.get('username'))
170 raise HTTPFound(c.came_from, headers=headers)
170 raise HTTPFound(c.came_from, headers=headers)
171 except UserCreationError as e:
171 except UserCreationError as e:
172 log.error(e)
172 log.error(e)
173 h.flash(e, category='error')
173 h.flash(e, category='error')
174
174
175 return self._get_template_context(c)
175 return self._get_template_context(c)
176
176
177 @view_config(
177 @view_config(
178 route_name='login', request_method='POST',
178 route_name='login', request_method='POST',
179 renderer='rhodecode:templates/login.mako')
179 renderer='rhodecode:templates/login.mako')
180 def login_post(self):
180 def login_post(self):
181 c = self.load_default_context()
181 c = self.load_default_context()
182
182
183 login_form = LoginForm(self.request.translate)()
183 login_form = LoginForm(self.request.translate)()
184
184
185 try:
185 try:
186 self.session.invalidate()
186 self.session.invalidate()
187 form_result = login_form.to_python(self.request.POST)
187 form_result = login_form.to_python(self.request.POST)
188 # form checks for username/password, now we're authenticated
188 # form checks for username/password, now we're authenticated
189 headers = _store_user_in_session(
189 headers = _store_user_in_session(
190 self.session,
190 self.session,
191 username=form_result['username'],
191 username=form_result['username'],
192 remember=form_result['remember'])
192 remember=form_result['remember'])
193 log.debug('Redirecting to "%s" after login.', c.came_from)
193 log.debug('Redirecting to "%s" after login.', c.came_from)
194
194
195 audit_user = audit_logger.UserWrap(
195 audit_user = audit_logger.UserWrap(
196 username=self.request.POST.get('username'),
196 username=self.request.POST.get('username'),
197 ip_addr=self.request.remote_addr)
197 ip_addr=self.request.remote_addr)
198 action_data = {'user_agent': self.request.user_agent}
198 action_data = {'user_agent': self.request.user_agent}
199 audit_logger.store_web(
199 audit_logger.store_web(
200 'user.login.success', action_data=action_data,
200 'user.login.success', action_data=action_data,
201 user=audit_user, commit=True)
201 user=audit_user, commit=True)
202
202
203 raise HTTPFound(c.came_from, headers=headers)
203 raise HTTPFound(c.came_from, headers=headers)
204 except formencode.Invalid as errors:
204 except formencode.Invalid as errors:
205 defaults = errors.value
205 defaults = errors.value
206 # remove password from filling in form again
206 # remove password from filling in form again
207 defaults.pop('password', None)
207 defaults.pop('password', None)
208 render_ctx = {
208 render_ctx = {
209 'errors': errors.error_dict,
209 'errors': errors.error_dict,
210 'defaults': defaults,
210 'defaults': defaults,
211 }
211 }
212
212
213 audit_user = audit_logger.UserWrap(
213 audit_user = audit_logger.UserWrap(
214 username=self.request.POST.get('username'),
214 username=self.request.POST.get('username'),
215 ip_addr=self.request.remote_addr)
215 ip_addr=self.request.remote_addr)
216 action_data = {'user_agent': self.request.user_agent}
216 action_data = {'user_agent': self.request.user_agent}
217 audit_logger.store_web(
217 audit_logger.store_web(
218 'user.login.failure', action_data=action_data,
218 'user.login.failure', action_data=action_data,
219 user=audit_user, commit=True)
219 user=audit_user, commit=True)
220 return self._get_template_context(c, **render_ctx)
220 return self._get_template_context(c, **render_ctx)
221
221
222 except UserCreationError as e:
222 except UserCreationError as e:
223 # headers auth or other auth functions that create users on
223 # headers auth or other auth functions that create users on
224 # the fly can throw this exception signaling that there's issue
224 # the fly can throw this exception signaling that there's issue
225 # with user creation, explanation should be provided in
225 # with user creation, explanation should be provided in
226 # Exception itself
226 # Exception itself
227 h.flash(e, category='error')
227 h.flash(e, category='error')
228 return self._get_template_context(c)
228 return self._get_template_context(c)
229
229
230 @CSRFRequired()
230 @CSRFRequired()
231 @view_config(route_name='logout', request_method='POST')
231 @view_config(route_name='logout', request_method='POST')
232 def logout(self):
232 def logout(self):
233 auth_user = self._rhodecode_user
233 auth_user = self._rhodecode_user
234 log.info('Deleting session for user: `%s`', auth_user)
234 log.info('Deleting session for user: `%s`', auth_user)
235
235
236 action_data = {'user_agent': self.request.user_agent}
236 action_data = {'user_agent': self.request.user_agent}
237 audit_logger.store_web(
237 audit_logger.store_web(
238 'user.logout', action_data=action_data,
238 'user.logout', action_data=action_data,
239 user=auth_user, commit=True)
239 user=auth_user, commit=True)
240 self.session.delete()
240 self.session.delete()
241 return HTTPFound(h.route_path('home'))
241 return HTTPFound(h.route_path('home'))
242
242
243 @HasPermissionAnyDecorator(
243 @HasPermissionAnyDecorator(
244 'hg.admin', 'hg.register.auto_activate', 'hg.register.manual_activate')
244 'hg.admin', 'hg.register.auto_activate', 'hg.register.manual_activate')
245 @view_config(
245 @view_config(
246 route_name='register', request_method='GET',
246 route_name='register', request_method='GET',
247 renderer='rhodecode:templates/register.mako',)
247 renderer='rhodecode:templates/register.mako',)
248 def register(self, defaults=None, errors=None):
248 def register(self, defaults=None, errors=None):
249 c = self.load_default_context()
249 c = self.load_default_context()
250 defaults = defaults or {}
250 defaults = defaults or {}
251 errors = errors or {}
251 errors = errors or {}
252
252
253 settings = SettingsModel().get_all_settings()
253 settings = SettingsModel().get_all_settings()
254 register_message = settings.get('rhodecode_register_message') or ''
254 register_message = settings.get('rhodecode_register_message') or ''
255 captcha = self._get_captcha_data()
255 captcha = self._get_captcha_data()
256 auto_active = 'hg.register.auto_activate' in User.get_default_user()\
256 auto_active = 'hg.register.auto_activate' in User.get_default_user()\
257 .AuthUser().permissions['global']
257 .AuthUser().permissions['global']
258
258
259 render_ctx = self._get_template_context(c)
259 render_ctx = self._get_template_context(c)
260 render_ctx.update({
260 render_ctx.update({
261 'defaults': defaults,
261 'defaults': defaults,
262 'errors': errors,
262 'errors': errors,
263 'auto_active': auto_active,
263 'auto_active': auto_active,
264 'captcha_active': captcha.active,
264 'captcha_active': captcha.active,
265 'captcha_public_key': captcha.public_key,
265 'captcha_public_key': captcha.public_key,
266 'register_message': register_message,
266 'register_message': register_message,
267 })
267 })
268 return render_ctx
268 return render_ctx
269
269
270 @HasPermissionAnyDecorator(
270 @HasPermissionAnyDecorator(
271 'hg.admin', 'hg.register.auto_activate', 'hg.register.manual_activate')
271 'hg.admin', 'hg.register.auto_activate', 'hg.register.manual_activate')
272 @view_config(
272 @view_config(
273 route_name='register', request_method='POST',
273 route_name='register', request_method='POST',
274 renderer='rhodecode:templates/register.mako')
274 renderer='rhodecode:templates/register.mako')
275 def register_post(self):
275 def register_post(self):
276 self.load_default_context()
276 self.load_default_context()
277 captcha = self._get_captcha_data()
277 captcha = self._get_captcha_data()
278 auto_active = 'hg.register.auto_activate' in User.get_default_user()\
278 auto_active = 'hg.register.auto_activate' in User.get_default_user()\
279 .AuthUser().permissions['global']
279 .AuthUser().permissions['global']
280
280
281 register_form = RegisterForm(self.request.translate)()
281 register_form = RegisterForm(self.request.translate)()
282 try:
282 try:
283
283
284 form_result = register_form.to_python(self.request.POST)
284 form_result = register_form.to_python(self.request.POST)
285 form_result['active'] = auto_active
285 form_result['active'] = auto_active
286
286
287 if captcha.active:
287 if captcha.active:
288 captcha_status, captcha_message = self.validate_captcha(
288 captcha_status, captcha_message = self.validate_captcha(
289 captcha.private_key)
289 captcha.private_key)
290
290
291 if not captcha_status:
291 if not captcha_status:
292 _value = form_result
292 _value = form_result
293 _msg = _('Bad captcha')
293 _msg = _('Bad captcha')
294 error_dict = {'recaptcha_field': captcha_message}
294 error_dict = {'recaptcha_field': captcha_message}
295 raise formencode.Invalid(
295 raise formencode.Invalid(
296 _msg, _value, None, error_dict=error_dict)
296 _msg, _value, None, error_dict=error_dict)
297
297
298 new_user = UserModel().create_registration(form_result)
298 new_user = UserModel().create_registration(form_result)
299
299
300 action_data = {'data': new_user.get_api_data(),
300 action_data = {'data': new_user.get_api_data(),
301 'user_agent': self.request.user_agent}
301 'user_agent': self.request.user_agent}
302
302
303 audit_user = audit_logger.UserWrap(
303 audit_user = audit_logger.UserWrap(
304 username=new_user.username,
304 username=new_user.username,
305 user_id=new_user.user_id,
305 user_id=new_user.user_id,
306 ip_addr=self.request.remote_addr)
306 ip_addr=self.request.remote_addr)
307
307
308 audit_logger.store_web(
308 audit_logger.store_web(
309 'user.register', action_data=action_data,
309 'user.register', action_data=action_data,
310 user=audit_user)
310 user=audit_user)
311
311
312 event = UserRegistered(user=new_user, session=self.session)
312 event = UserRegistered(user=new_user, session=self.session)
313 trigger(event)
313 trigger(event)
314 h.flash(
314 h.flash(
315 _('You have successfully registered with RhodeCode'),
315 _('You have successfully registered with RhodeCode'),
316 category='success')
316 category='success')
317 Session().commit()
317 Session().commit()
318
318
319 redirect_ro = self.request.route_path('login')
319 redirect_ro = self.request.route_path('login')
320 raise HTTPFound(redirect_ro)
320 raise HTTPFound(redirect_ro)
321
321
322 except formencode.Invalid as errors:
322 except formencode.Invalid as errors:
323 errors.value.pop('password', None)
323 errors.value.pop('password', None)
324 errors.value.pop('password_confirmation', None)
324 errors.value.pop('password_confirmation', None)
325 return self.register(
325 return self.register(
326 defaults=errors.value, errors=errors.error_dict)
326 defaults=errors.value, errors=errors.error_dict)
327
327
328 except UserCreationError as e:
328 except UserCreationError as e:
329 # container auth or other auth functions that create users on
329 # container auth or other auth functions that create users on
330 # the fly can throw this exception signaling that there's issue
330 # the fly can throw this exception signaling that there's issue
331 # with user creation, explanation should be provided in
331 # with user creation, explanation should be provided in
332 # Exception itself
332 # Exception itself
333 h.flash(e, category='error')
333 h.flash(e, category='error')
334 return self.register()
334 return self.register()
335
335
336 @view_config(
336 @view_config(
337 route_name='reset_password', request_method=('GET', 'POST'),
337 route_name='reset_password', request_method=('GET', 'POST'),
338 renderer='rhodecode:templates/password_reset.mako')
338 renderer='rhodecode:templates/password_reset.mako')
339 def password_reset(self):
339 def password_reset(self):
340 c = self.load_default_context()
340 c = self.load_default_context()
341 captcha = self._get_captcha_data()
341 captcha = self._get_captcha_data()
342
342
343 template_context = {
343 template_context = {
344 'captcha_active': captcha.active,
344 'captcha_active': captcha.active,
345 'captcha_public_key': captcha.public_key,
345 'captcha_public_key': captcha.public_key,
346 'defaults': {},
346 'defaults': {},
347 'errors': {},
347 'errors': {},
348 }
348 }
349
349
350 # always send implicit message to prevent from discovery of
350 # always send implicit message to prevent from discovery of
351 # matching emails
351 # matching emails
352 msg = _('If such email exists, a password reset link was sent to it.')
352 msg = _('If such email exists, a password reset link was sent to it.')
353
353
354 if self.request.POST:
354 if self.request.POST:
355 if h.HasPermissionAny('hg.password_reset.disabled')():
355 if h.HasPermissionAny('hg.password_reset.disabled')():
356 _email = self.request.POST.get('email', '')
356 _email = self.request.POST.get('email', '')
357 log.error('Failed attempt to reset password for `%s`.', _email)
357 log.error('Failed attempt to reset password for `%s`.', _email)
358 h.flash(_('Password reset has been disabled.'),
358 h.flash(_('Password reset has been disabled.'),
359 category='error')
359 category='error')
360 return HTTPFound(self.request.route_path('reset_password'))
360 return HTTPFound(self.request.route_path('reset_password'))
361
361
362 password_reset_form = PasswordResetForm(self.request.translate)()
362 password_reset_form = PasswordResetForm(self.request.translate)()
363 try:
363 try:
364 form_result = password_reset_form.to_python(
364 form_result = password_reset_form.to_python(
365 self.request.POST)
365 self.request.POST)
366 user_email = form_result['email']
366 user_email = form_result['email']
367
367
368 if captcha.active:
368 if captcha.active:
369 captcha_status, captcha_message = self.validate_captcha(
369 captcha_status, captcha_message = self.validate_captcha(
370 captcha.private_key)
370 captcha.private_key)
371
371
372 if not captcha_status:
372 if not captcha_status:
373 _value = form_result
373 _value = form_result
374 _msg = _('Bad captcha')
374 _msg = _('Bad captcha')
375 error_dict = {'recaptcha_field': captcha_message}
375 error_dict = {'recaptcha_field': captcha_message}
376 raise formencode.Invalid(
376 raise formencode.Invalid(
377 _msg, _value, None, error_dict=error_dict)
377 _msg, _value, None, error_dict=error_dict)
378
378
379 # Generate reset URL and send mail.
379 # Generate reset URL and send mail.
380 user = User.get_by_email(user_email)
380 user = User.get_by_email(user_email)
381
381
382 # generate password reset token that expires in 10minutes
382 # generate password reset token that expires in 10 minutes
383 desc = 'Generated token for password reset from {}'.format(
383 description = u'Generated token for password reset from {}'.format(
384 datetime.datetime.now().isoformat())
384 datetime.datetime.now().isoformat())
385 reset_token = AuthTokenModel().create(
385
386 user, lifetime=10,
386 reset_token = UserModel().add_auth_token(
387 description=desc,
387 user=user, lifetime_minutes=10,
388 role=UserApiKeys.ROLE_PASSWORD_RESET)
388 role=UserModel.auth_token_role.ROLE_PASSWORD_RESET,
389 description=description)
389 Session().commit()
390 Session().commit()
390
391
391 log.debug('Successfully created password recovery token')
392 log.debug('Successfully created password recovery token')
392 password_reset_url = self.request.route_url(
393 password_reset_url = self.request.route_url(
393 'reset_password_confirmation',
394 'reset_password_confirmation',
394 _query={'key': reset_token.api_key})
395 _query={'key': reset_token.api_key})
395 UserModel().reset_password_link(
396 UserModel().reset_password_link(
396 form_result, password_reset_url)
397 form_result, password_reset_url)
397 # Display success message and redirect.
398 # Display success message and redirect.
398 h.flash(msg, category='success')
399 h.flash(msg, category='success')
399
400
400 action_data = {'email': user_email,
401 action_data = {'email': user_email,
401 'user_agent': self.request.user_agent}
402 'user_agent': self.request.user_agent}
402 audit_logger.store_web(
403 audit_logger.store_web(
403 'user.password.reset_request', action_data=action_data,
404 'user.password.reset_request', action_data=action_data,
404 user=self._rhodecode_user, commit=True)
405 user=self._rhodecode_user, commit=True)
405 return HTTPFound(self.request.route_path('reset_password'))
406 return HTTPFound(self.request.route_path('reset_password'))
406
407
407 except formencode.Invalid as errors:
408 except formencode.Invalid as errors:
408 template_context.update({
409 template_context.update({
409 'defaults': errors.value,
410 'defaults': errors.value,
410 'errors': errors.error_dict,
411 'errors': errors.error_dict,
411 })
412 })
412 if not self.request.POST.get('email'):
413 if not self.request.POST.get('email'):
413 # case of empty email, we want to report that
414 # case of empty email, we want to report that
414 return self._get_template_context(c, **template_context)
415 return self._get_template_context(c, **template_context)
415
416
416 if 'recaptcha_field' in errors.error_dict:
417 if 'recaptcha_field' in errors.error_dict:
417 # case of failed captcha
418 # case of failed captcha
418 return self._get_template_context(c, **template_context)
419 return self._get_template_context(c, **template_context)
419
420
420 log.debug('faking response on invalid password reset')
421 log.debug('faking response on invalid password reset')
421 # make this take 2s, to prevent brute forcing.
422 # make this take 2s, to prevent brute forcing.
422 time.sleep(2)
423 time.sleep(2)
423 h.flash(msg, category='success')
424 h.flash(msg, category='success')
424 return HTTPFound(self.request.route_path('reset_password'))
425 return HTTPFound(self.request.route_path('reset_password'))
425
426
426 return self._get_template_context(c, **template_context)
427 return self._get_template_context(c, **template_context)
427
428
428 @view_config(route_name='reset_password_confirmation',
429 @view_config(route_name='reset_password_confirmation',
429 request_method='GET')
430 request_method='GET')
430 def password_reset_confirmation(self):
431 def password_reset_confirmation(self):
431 self.load_default_context()
432 self.load_default_context()
432 if self.request.GET and self.request.GET.get('key'):
433 if self.request.GET and self.request.GET.get('key'):
433 # make this take 2s, to prevent brute forcing.
434 # make this take 2s, to prevent brute forcing.
434 time.sleep(2)
435 time.sleep(2)
435
436
436 token = AuthTokenModel().get_auth_token(
437 token = AuthTokenModel().get_auth_token(
437 self.request.GET.get('key'))
438 self.request.GET.get('key'))
438
439
439 # verify token is the correct role
440 # verify token is the correct role
440 if token is None or token.role != UserApiKeys.ROLE_PASSWORD_RESET:
441 if token is None or token.role != UserApiKeys.ROLE_PASSWORD_RESET:
441 log.debug('Got token with role:%s expected is %s',
442 log.debug('Got token with role:%s expected is %s',
442 getattr(token, 'role', 'EMPTY_TOKEN'),
443 getattr(token, 'role', 'EMPTY_TOKEN'),
443 UserApiKeys.ROLE_PASSWORD_RESET)
444 UserApiKeys.ROLE_PASSWORD_RESET)
444 h.flash(
445 h.flash(
445 _('Given reset token is invalid'), category='error')
446 _('Given reset token is invalid'), category='error')
446 return HTTPFound(self.request.route_path('reset_password'))
447 return HTTPFound(self.request.route_path('reset_password'))
447
448
448 try:
449 try:
449 owner = token.user
450 owner = token.user
450 data = {'email': owner.email, 'token': token.api_key}
451 data = {'email': owner.email, 'token': token.api_key}
451 UserModel().reset_password(data)
452 UserModel().reset_password(data)
452 h.flash(
453 h.flash(
453 _('Your password reset was successful, '
454 _('Your password reset was successful, '
454 'a new password has been sent to your email'),
455 'a new password has been sent to your email'),
455 category='success')
456 category='success')
456 except Exception as e:
457 except Exception as e:
457 log.error(e)
458 log.error(e)
458 return HTTPFound(self.request.route_path('reset_password'))
459 return HTTPFound(self.request.route_path('reset_password'))
459
460
460 return HTTPFound(self.request.route_path('login'))
461 return HTTPFound(self.request.route_path('login'))
@@ -1,604 +1,605 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
23
24 import formencode
24 import formencode
25 import formencode.htmlfill
25 import formencode.htmlfill
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
31 from rhodecode.apps._base import BaseAppView, DataGridAppView
32 from rhodecode import forms
32 from rhodecode import forms
33 from rhodecode.lib import helpers as h
33 from rhodecode.lib import helpers as h
34 from rhodecode.lib import audit_logger
34 from rhodecode.lib import audit_logger
35 from rhodecode.lib.ext_json import json
35 from rhodecode.lib.ext_json import json
36 from rhodecode.lib.auth import LoginRequired, NotAnonymous, CSRFRequired
36 from rhodecode.lib.auth import LoginRequired, NotAnonymous, CSRFRequired
37 from rhodecode.lib.channelstream import (
37 from rhodecode.lib.channelstream import (
38 channelstream_request, ChannelstreamException)
38 channelstream_request, ChannelstreamException)
39 from rhodecode.lib.utils2 import safe_int, md5, str2bool
39 from rhodecode.lib.utils2 import safe_int, md5, str2bool
40 from rhodecode.model.auth_token import AuthTokenModel
40 from rhodecode.model.auth_token import AuthTokenModel
41 from rhodecode.model.comment import CommentsModel
41 from rhodecode.model.comment import CommentsModel
42 from rhodecode.model.db import (
42 from rhodecode.model.db import (
43 Repository, UserEmailMap, UserApiKeys, UserFollowing, joinedload,
43 Repository, UserEmailMap, UserApiKeys, UserFollowing, joinedload,
44 PullRequest)
44 PullRequest)
45 from rhodecode.model.forms import UserForm, UserExtraEmailForm
45 from rhodecode.model.forms import UserForm, UserExtraEmailForm
46 from rhodecode.model.meta import Session
46 from rhodecode.model.meta import Session
47 from rhodecode.model.pull_request import PullRequestModel
47 from rhodecode.model.pull_request import PullRequestModel
48 from rhodecode.model.scm import RepoList
48 from rhodecode.model.scm import RepoList
49 from rhodecode.model.user import UserModel
49 from rhodecode.model.user import UserModel
50 from rhodecode.model.repo import RepoModel
50 from rhodecode.model.repo import RepoModel
51 from rhodecode.model.user_group import UserGroupModel
51 from rhodecode.model.user_group import UserGroupModel
52 from rhodecode.model.validation_schema.schemas import user_schema
52 from rhodecode.model.validation_schema.schemas import user_schema
53
53
54 log = logging.getLogger(__name__)
54 log = logging.getLogger(__name__)
55
55
56
56
57 class MyAccountView(BaseAppView, DataGridAppView):
57 class MyAccountView(BaseAppView, DataGridAppView):
58 ALLOW_SCOPED_TOKENS = False
58 ALLOW_SCOPED_TOKENS = False
59 """
59 """
60 This view has alternative version inside EE, if modified please take a look
60 This view has alternative version inside EE, if modified please take a look
61 in there as well.
61 in there as well.
62 """
62 """
63
63
64 def load_default_context(self):
64 def load_default_context(self):
65 c = self._get_local_tmpl_context()
65 c = self._get_local_tmpl_context()
66 c.user = c.auth_user.get_instance()
66 c.user = c.auth_user.get_instance()
67 c.allow_scoped_tokens = self.ALLOW_SCOPED_TOKENS
67 c.allow_scoped_tokens = self.ALLOW_SCOPED_TOKENS
68
68
69 return c
69 return c
70
70
71 @LoginRequired()
71 @LoginRequired()
72 @NotAnonymous()
72 @NotAnonymous()
73 @view_config(
73 @view_config(
74 route_name='my_account_profile', request_method='GET',
74 route_name='my_account_profile', request_method='GET',
75 renderer='rhodecode:templates/admin/my_account/my_account.mako')
75 renderer='rhodecode:templates/admin/my_account/my_account.mako')
76 def my_account_profile(self):
76 def my_account_profile(self):
77 c = self.load_default_context()
77 c = self.load_default_context()
78 c.active = 'profile'
78 c.active = 'profile'
79 return self._get_template_context(c)
79 return self._get_template_context(c)
80
80
81 @LoginRequired()
81 @LoginRequired()
82 @NotAnonymous()
82 @NotAnonymous()
83 @view_config(
83 @view_config(
84 route_name='my_account_password', request_method='GET',
84 route_name='my_account_password', request_method='GET',
85 renderer='rhodecode:templates/admin/my_account/my_account.mako')
85 renderer='rhodecode:templates/admin/my_account/my_account.mako')
86 def my_account_password(self):
86 def my_account_password(self):
87 c = self.load_default_context()
87 c = self.load_default_context()
88 c.active = 'password'
88 c.active = 'password'
89 c.extern_type = c.user.extern_type
89 c.extern_type = c.user.extern_type
90
90
91 schema = user_schema.ChangePasswordSchema().bind(
91 schema = user_schema.ChangePasswordSchema().bind(
92 username=c.user.username)
92 username=c.user.username)
93
93
94 form = forms.Form(
94 form = forms.Form(
95 schema,
95 schema,
96 action=h.route_path('my_account_password_update'),
96 action=h.route_path('my_account_password_update'),
97 buttons=(forms.buttons.save, forms.buttons.reset))
97 buttons=(forms.buttons.save, forms.buttons.reset))
98
98
99 c.form = form
99 c.form = form
100 return self._get_template_context(c)
100 return self._get_template_context(c)
101
101
102 @LoginRequired()
102 @LoginRequired()
103 @NotAnonymous()
103 @NotAnonymous()
104 @CSRFRequired()
104 @CSRFRequired()
105 @view_config(
105 @view_config(
106 route_name='my_account_password_update', request_method='POST',
106 route_name='my_account_password_update', request_method='POST',
107 renderer='rhodecode:templates/admin/my_account/my_account.mako')
107 renderer='rhodecode:templates/admin/my_account/my_account.mako')
108 def my_account_password_update(self):
108 def my_account_password_update(self):
109 _ = self.request.translate
109 _ = self.request.translate
110 c = self.load_default_context()
110 c = self.load_default_context()
111 c.active = 'password'
111 c.active = 'password'
112 c.extern_type = c.user.extern_type
112 c.extern_type = c.user.extern_type
113
113
114 schema = user_schema.ChangePasswordSchema().bind(
114 schema = user_schema.ChangePasswordSchema().bind(
115 username=c.user.username)
115 username=c.user.username)
116
116
117 form = forms.Form(
117 form = forms.Form(
118 schema, buttons=(forms.buttons.save, forms.buttons.reset))
118 schema, buttons=(forms.buttons.save, forms.buttons.reset))
119
119
120 if c.extern_type != 'rhodecode':
120 if c.extern_type != 'rhodecode':
121 raise HTTPFound(self.request.route_path('my_account_password'))
121 raise HTTPFound(self.request.route_path('my_account_password'))
122
122
123 controls = self.request.POST.items()
123 controls = self.request.POST.items()
124 try:
124 try:
125 valid_data = form.validate(controls)
125 valid_data = form.validate(controls)
126 UserModel().update_user(c.user.user_id, **valid_data)
126 UserModel().update_user(c.user.user_id, **valid_data)
127 c.user.update_userdata(force_password_change=False)
127 c.user.update_userdata(force_password_change=False)
128 Session().commit()
128 Session().commit()
129 except forms.ValidationFailure as e:
129 except forms.ValidationFailure as e:
130 c.form = e
130 c.form = e
131 return self._get_template_context(c)
131 return self._get_template_context(c)
132
132
133 except Exception:
133 except Exception:
134 log.exception("Exception updating password")
134 log.exception("Exception updating password")
135 h.flash(_('Error occurred during update of user password'),
135 h.flash(_('Error occurred during update of user password'),
136 category='error')
136 category='error')
137 else:
137 else:
138 instance = c.auth_user.get_instance()
138 instance = c.auth_user.get_instance()
139 self.session.setdefault('rhodecode_user', {}).update(
139 self.session.setdefault('rhodecode_user', {}).update(
140 {'password': md5(instance.password)})
140 {'password': md5(instance.password)})
141 self.session.save()
141 self.session.save()
142 h.flash(_("Successfully updated password"), category='success')
142 h.flash(_("Successfully updated password"), category='success')
143
143
144 raise HTTPFound(self.request.route_path('my_account_password'))
144 raise HTTPFound(self.request.route_path('my_account_password'))
145
145
146 @LoginRequired()
146 @LoginRequired()
147 @NotAnonymous()
147 @NotAnonymous()
148 @view_config(
148 @view_config(
149 route_name='my_account_auth_tokens', request_method='GET',
149 route_name='my_account_auth_tokens', request_method='GET',
150 renderer='rhodecode:templates/admin/my_account/my_account.mako')
150 renderer='rhodecode:templates/admin/my_account/my_account.mako')
151 def my_account_auth_tokens(self):
151 def my_account_auth_tokens(self):
152 _ = self.request.translate
152 _ = self.request.translate
153
153
154 c = self.load_default_context()
154 c = self.load_default_context()
155 c.active = 'auth_tokens'
155 c.active = 'auth_tokens'
156 c.lifetime_values = AuthTokenModel.get_lifetime_values(translator=_)
156 c.lifetime_values = AuthTokenModel.get_lifetime_values(translator=_)
157 c.role_values = [
157 c.role_values = [
158 (x, AuthTokenModel.cls._get_role_name(x))
158 (x, AuthTokenModel.cls._get_role_name(x))
159 for x in AuthTokenModel.cls.ROLES]
159 for x in AuthTokenModel.cls.ROLES]
160 c.role_options = [(c.role_values, _("Role"))]
160 c.role_options = [(c.role_values, _("Role"))]
161 c.user_auth_tokens = AuthTokenModel().get_auth_tokens(
161 c.user_auth_tokens = AuthTokenModel().get_auth_tokens(
162 c.user.user_id, show_expired=True)
162 c.user.user_id, show_expired=True)
163 c.role_vcs = AuthTokenModel.cls.ROLE_VCS
163 c.role_vcs = AuthTokenModel.cls.ROLE_VCS
164 return self._get_template_context(c)
164 return self._get_template_context(c)
165
165
166 def maybe_attach_token_scope(self, token):
166 def maybe_attach_token_scope(self, token):
167 # implemented in EE edition
167 # implemented in EE edition
168 pass
168 pass
169
169
170 @LoginRequired()
170 @LoginRequired()
171 @NotAnonymous()
171 @NotAnonymous()
172 @CSRFRequired()
172 @CSRFRequired()
173 @view_config(
173 @view_config(
174 route_name='my_account_auth_tokens_add', request_method='POST',)
174 route_name='my_account_auth_tokens_add', request_method='POST',)
175 def my_account_auth_tokens_add(self):
175 def my_account_auth_tokens_add(self):
176 _ = self.request.translate
176 _ = self.request.translate
177 c = self.load_default_context()
177 c = self.load_default_context()
178
178
179 lifetime = safe_int(self.request.POST.get('lifetime'), -1)
179 lifetime = safe_int(self.request.POST.get('lifetime'), -1)
180 description = self.request.POST.get('description')
180 description = self.request.POST.get('description')
181 role = self.request.POST.get('role')
181 role = self.request.POST.get('role')
182
182
183 token = AuthTokenModel().create(
183 token = UserModel().add_auth_token(
184 c.user.user_id, description, lifetime, role)
184 user=c.user.user_id,
185 lifetime_minutes=lifetime, role=role, description=description,
186 scope_callback=self.maybe_attach_token_scope)
185 token_data = token.get_api_data()
187 token_data = token.get_api_data()
186
188
187 self.maybe_attach_token_scope(token)
188 audit_logger.store_web(
189 audit_logger.store_web(
189 'user.edit.token.add', action_data={
190 'user.edit.token.add', action_data={
190 'data': {'token': token_data, 'user': 'self'}},
191 'data': {'token': token_data, 'user': 'self'}},
191 user=self._rhodecode_user, )
192 user=self._rhodecode_user, )
192 Session().commit()
193 Session().commit()
193
194
194 h.flash(_("Auth token successfully created"), category='success')
195 h.flash(_("Auth token successfully created"), category='success')
195 return HTTPFound(h.route_path('my_account_auth_tokens'))
196 return HTTPFound(h.route_path('my_account_auth_tokens'))
196
197
197 @LoginRequired()
198 @LoginRequired()
198 @NotAnonymous()
199 @NotAnonymous()
199 @CSRFRequired()
200 @CSRFRequired()
200 @view_config(
201 @view_config(
201 route_name='my_account_auth_tokens_delete', request_method='POST')
202 route_name='my_account_auth_tokens_delete', request_method='POST')
202 def my_account_auth_tokens_delete(self):
203 def my_account_auth_tokens_delete(self):
203 _ = self.request.translate
204 _ = self.request.translate
204 c = self.load_default_context()
205 c = self.load_default_context()
205
206
206 del_auth_token = self.request.POST.get('del_auth_token')
207 del_auth_token = self.request.POST.get('del_auth_token')
207
208
208 if del_auth_token:
209 if del_auth_token:
209 token = UserApiKeys.get_or_404(del_auth_token)
210 token = UserApiKeys.get_or_404(del_auth_token)
210 token_data = token.get_api_data()
211 token_data = token.get_api_data()
211
212
212 AuthTokenModel().delete(del_auth_token, c.user.user_id)
213 AuthTokenModel().delete(del_auth_token, c.user.user_id)
213 audit_logger.store_web(
214 audit_logger.store_web(
214 'user.edit.token.delete', action_data={
215 'user.edit.token.delete', action_data={
215 'data': {'token': token_data, 'user': 'self'}},
216 'data': {'token': token_data, 'user': 'self'}},
216 user=self._rhodecode_user,)
217 user=self._rhodecode_user,)
217 Session().commit()
218 Session().commit()
218 h.flash(_("Auth token successfully deleted"), category='success')
219 h.flash(_("Auth token successfully deleted"), category='success')
219
220
220 return HTTPFound(h.route_path('my_account_auth_tokens'))
221 return HTTPFound(h.route_path('my_account_auth_tokens'))
221
222
222 @LoginRequired()
223 @LoginRequired()
223 @NotAnonymous()
224 @NotAnonymous()
224 @view_config(
225 @view_config(
225 route_name='my_account_emails', request_method='GET',
226 route_name='my_account_emails', request_method='GET',
226 renderer='rhodecode:templates/admin/my_account/my_account.mako')
227 renderer='rhodecode:templates/admin/my_account/my_account.mako')
227 def my_account_emails(self):
228 def my_account_emails(self):
228 _ = self.request.translate
229 _ = self.request.translate
229
230
230 c = self.load_default_context()
231 c = self.load_default_context()
231 c.active = 'emails'
232 c.active = 'emails'
232
233
233 c.user_email_map = UserEmailMap.query()\
234 c.user_email_map = UserEmailMap.query()\
234 .filter(UserEmailMap.user == c.user).all()
235 .filter(UserEmailMap.user == c.user).all()
235
236
236 schema = user_schema.AddEmailSchema().bind(
237 schema = user_schema.AddEmailSchema().bind(
237 username=c.user.username, user_emails=c.user.emails)
238 username=c.user.username, user_emails=c.user.emails)
238
239
239 form = forms.RcForm(schema,
240 form = forms.RcForm(schema,
240 action=h.route_path('my_account_emails_add'),
241 action=h.route_path('my_account_emails_add'),
241 buttons=(forms.buttons.save, forms.buttons.reset))
242 buttons=(forms.buttons.save, forms.buttons.reset))
242
243
243 c.form = form
244 c.form = form
244 return self._get_template_context(c)
245 return self._get_template_context(c)
245
246
246 @LoginRequired()
247 @LoginRequired()
247 @NotAnonymous()
248 @NotAnonymous()
248 @CSRFRequired()
249 @CSRFRequired()
249 @view_config(
250 @view_config(
250 route_name='my_account_emails_add', request_method='POST',
251 route_name='my_account_emails_add', request_method='POST',
251 renderer='rhodecode:templates/admin/my_account/my_account.mako')
252 renderer='rhodecode:templates/admin/my_account/my_account.mako')
252 def my_account_emails_add(self):
253 def my_account_emails_add(self):
253 _ = self.request.translate
254 _ = self.request.translate
254 c = self.load_default_context()
255 c = self.load_default_context()
255 c.active = 'emails'
256 c.active = 'emails'
256
257
257 schema = user_schema.AddEmailSchema().bind(
258 schema = user_schema.AddEmailSchema().bind(
258 username=c.user.username, user_emails=c.user.emails)
259 username=c.user.username, user_emails=c.user.emails)
259
260
260 form = forms.RcForm(
261 form = forms.RcForm(
261 schema, action=h.route_path('my_account_emails_add'),
262 schema, action=h.route_path('my_account_emails_add'),
262 buttons=(forms.buttons.save, forms.buttons.reset))
263 buttons=(forms.buttons.save, forms.buttons.reset))
263
264
264 controls = self.request.POST.items()
265 controls = self.request.POST.items()
265 try:
266 try:
266 valid_data = form.validate(controls)
267 valid_data = form.validate(controls)
267 UserModel().add_extra_email(c.user.user_id, valid_data['email'])
268 UserModel().add_extra_email(c.user.user_id, valid_data['email'])
268 audit_logger.store_web(
269 audit_logger.store_web(
269 'user.edit.email.add', action_data={
270 'user.edit.email.add', action_data={
270 'data': {'email': valid_data['email'], 'user': 'self'}},
271 'data': {'email': valid_data['email'], 'user': 'self'}},
271 user=self._rhodecode_user,)
272 user=self._rhodecode_user,)
272 Session().commit()
273 Session().commit()
273 except formencode.Invalid as error:
274 except formencode.Invalid as error:
274 h.flash(h.escape(error.error_dict['email']), category='error')
275 h.flash(h.escape(error.error_dict['email']), category='error')
275 except forms.ValidationFailure as e:
276 except forms.ValidationFailure as e:
276 c.user_email_map = UserEmailMap.query() \
277 c.user_email_map = UserEmailMap.query() \
277 .filter(UserEmailMap.user == c.user).all()
278 .filter(UserEmailMap.user == c.user).all()
278 c.form = e
279 c.form = e
279 return self._get_template_context(c)
280 return self._get_template_context(c)
280 except Exception:
281 except Exception:
281 log.exception("Exception adding email")
282 log.exception("Exception adding email")
282 h.flash(_('Error occurred during adding email'),
283 h.flash(_('Error occurred during adding email'),
283 category='error')
284 category='error')
284 else:
285 else:
285 h.flash(_("Successfully added email"), category='success')
286 h.flash(_("Successfully added email"), category='success')
286
287
287 raise HTTPFound(self.request.route_path('my_account_emails'))
288 raise HTTPFound(self.request.route_path('my_account_emails'))
288
289
289 @LoginRequired()
290 @LoginRequired()
290 @NotAnonymous()
291 @NotAnonymous()
291 @CSRFRequired()
292 @CSRFRequired()
292 @view_config(
293 @view_config(
293 route_name='my_account_emails_delete', request_method='POST')
294 route_name='my_account_emails_delete', request_method='POST')
294 def my_account_emails_delete(self):
295 def my_account_emails_delete(self):
295 _ = self.request.translate
296 _ = self.request.translate
296 c = self.load_default_context()
297 c = self.load_default_context()
297
298
298 del_email_id = self.request.POST.get('del_email_id')
299 del_email_id = self.request.POST.get('del_email_id')
299 if del_email_id:
300 if del_email_id:
300 email = UserEmailMap.get_or_404(del_email_id).email
301 email = UserEmailMap.get_or_404(del_email_id).email
301 UserModel().delete_extra_email(c.user.user_id, del_email_id)
302 UserModel().delete_extra_email(c.user.user_id, del_email_id)
302 audit_logger.store_web(
303 audit_logger.store_web(
303 'user.edit.email.delete', action_data={
304 'user.edit.email.delete', action_data={
304 'data': {'email': email, 'user': 'self'}},
305 'data': {'email': email, 'user': 'self'}},
305 user=self._rhodecode_user,)
306 user=self._rhodecode_user,)
306 Session().commit()
307 Session().commit()
307 h.flash(_("Email successfully deleted"),
308 h.flash(_("Email successfully deleted"),
308 category='success')
309 category='success')
309 return HTTPFound(h.route_path('my_account_emails'))
310 return HTTPFound(h.route_path('my_account_emails'))
310
311
311 @LoginRequired()
312 @LoginRequired()
312 @NotAnonymous()
313 @NotAnonymous()
313 @CSRFRequired()
314 @CSRFRequired()
314 @view_config(
315 @view_config(
315 route_name='my_account_notifications_test_channelstream',
316 route_name='my_account_notifications_test_channelstream',
316 request_method='POST', renderer='json_ext')
317 request_method='POST', renderer='json_ext')
317 def my_account_notifications_test_channelstream(self):
318 def my_account_notifications_test_channelstream(self):
318 message = 'Test message sent via Channelstream by user: {}, on {}'.format(
319 message = 'Test message sent via Channelstream by user: {}, on {}'.format(
319 self._rhodecode_user.username, datetime.datetime.now())
320 self._rhodecode_user.username, datetime.datetime.now())
320 payload = {
321 payload = {
321 # 'channel': 'broadcast',
322 # 'channel': 'broadcast',
322 'type': 'message',
323 'type': 'message',
323 'timestamp': datetime.datetime.utcnow(),
324 'timestamp': datetime.datetime.utcnow(),
324 'user': 'system',
325 'user': 'system',
325 'pm_users': [self._rhodecode_user.username],
326 'pm_users': [self._rhodecode_user.username],
326 'message': {
327 'message': {
327 'message': message,
328 'message': message,
328 'level': 'info',
329 'level': 'info',
329 'topic': '/notifications'
330 'topic': '/notifications'
330 }
331 }
331 }
332 }
332
333
333 registry = self.request.registry
334 registry = self.request.registry
334 rhodecode_plugins = getattr(registry, 'rhodecode_plugins', {})
335 rhodecode_plugins = getattr(registry, 'rhodecode_plugins', {})
335 channelstream_config = rhodecode_plugins.get('channelstream', {})
336 channelstream_config = rhodecode_plugins.get('channelstream', {})
336
337
337 try:
338 try:
338 channelstream_request(channelstream_config, [payload], '/message')
339 channelstream_request(channelstream_config, [payload], '/message')
339 except ChannelstreamException as e:
340 except ChannelstreamException as e:
340 log.exception('Failed to send channelstream data')
341 log.exception('Failed to send channelstream data')
341 return {"response": 'ERROR: {}'.format(e.__class__.__name__)}
342 return {"response": 'ERROR: {}'.format(e.__class__.__name__)}
342 return {"response": 'Channelstream data sent. '
343 return {"response": 'Channelstream data sent. '
343 'You should see a new live message now.'}
344 'You should see a new live message now.'}
344
345
345 def _load_my_repos_data(self, watched=False):
346 def _load_my_repos_data(self, watched=False):
346 if watched:
347 if watched:
347 admin = False
348 admin = False
348 follows_repos = Session().query(UserFollowing)\
349 follows_repos = Session().query(UserFollowing)\
349 .filter(UserFollowing.user_id == self._rhodecode_user.user_id)\
350 .filter(UserFollowing.user_id == self._rhodecode_user.user_id)\
350 .options(joinedload(UserFollowing.follows_repository))\
351 .options(joinedload(UserFollowing.follows_repository))\
351 .all()
352 .all()
352 repo_list = [x.follows_repository for x in follows_repos]
353 repo_list = [x.follows_repository for x in follows_repos]
353 else:
354 else:
354 admin = True
355 admin = True
355 repo_list = Repository.get_all_repos(
356 repo_list = Repository.get_all_repos(
356 user_id=self._rhodecode_user.user_id)
357 user_id=self._rhodecode_user.user_id)
357 repo_list = RepoList(repo_list, perm_set=[
358 repo_list = RepoList(repo_list, perm_set=[
358 'repository.read', 'repository.write', 'repository.admin'])
359 'repository.read', 'repository.write', 'repository.admin'])
359
360
360 repos_data = RepoModel().get_repos_as_dict(
361 repos_data = RepoModel().get_repos_as_dict(
361 repo_list=repo_list, admin=admin)
362 repo_list=repo_list, admin=admin)
362 # json used to render the grid
363 # json used to render the grid
363 return json.dumps(repos_data)
364 return json.dumps(repos_data)
364
365
365 @LoginRequired()
366 @LoginRequired()
366 @NotAnonymous()
367 @NotAnonymous()
367 @view_config(
368 @view_config(
368 route_name='my_account_repos', request_method='GET',
369 route_name='my_account_repos', request_method='GET',
369 renderer='rhodecode:templates/admin/my_account/my_account.mako')
370 renderer='rhodecode:templates/admin/my_account/my_account.mako')
370 def my_account_repos(self):
371 def my_account_repos(self):
371 c = self.load_default_context()
372 c = self.load_default_context()
372 c.active = 'repos'
373 c.active = 'repos'
373
374
374 # json used to render the grid
375 # json used to render the grid
375 c.data = self._load_my_repos_data()
376 c.data = self._load_my_repos_data()
376 return self._get_template_context(c)
377 return self._get_template_context(c)
377
378
378 @LoginRequired()
379 @LoginRequired()
379 @NotAnonymous()
380 @NotAnonymous()
380 @view_config(
381 @view_config(
381 route_name='my_account_watched', request_method='GET',
382 route_name='my_account_watched', request_method='GET',
382 renderer='rhodecode:templates/admin/my_account/my_account.mako')
383 renderer='rhodecode:templates/admin/my_account/my_account.mako')
383 def my_account_watched(self):
384 def my_account_watched(self):
384 c = self.load_default_context()
385 c = self.load_default_context()
385 c.active = 'watched'
386 c.active = 'watched'
386
387
387 # json used to render the grid
388 # json used to render the grid
388 c.data = self._load_my_repos_data(watched=True)
389 c.data = self._load_my_repos_data(watched=True)
389 return self._get_template_context(c)
390 return self._get_template_context(c)
390
391
391 @LoginRequired()
392 @LoginRequired()
392 @NotAnonymous()
393 @NotAnonymous()
393 @view_config(
394 @view_config(
394 route_name='my_account_perms', request_method='GET',
395 route_name='my_account_perms', request_method='GET',
395 renderer='rhodecode:templates/admin/my_account/my_account.mako')
396 renderer='rhodecode:templates/admin/my_account/my_account.mako')
396 def my_account_perms(self):
397 def my_account_perms(self):
397 c = self.load_default_context()
398 c = self.load_default_context()
398 c.active = 'perms'
399 c.active = 'perms'
399
400
400 c.perm_user = c.auth_user
401 c.perm_user = c.auth_user
401 return self._get_template_context(c)
402 return self._get_template_context(c)
402
403
403 @LoginRequired()
404 @LoginRequired()
404 @NotAnonymous()
405 @NotAnonymous()
405 @view_config(
406 @view_config(
406 route_name='my_account_notifications', request_method='GET',
407 route_name='my_account_notifications', request_method='GET',
407 renderer='rhodecode:templates/admin/my_account/my_account.mako')
408 renderer='rhodecode:templates/admin/my_account/my_account.mako')
408 def my_notifications(self):
409 def my_notifications(self):
409 c = self.load_default_context()
410 c = self.load_default_context()
410 c.active = 'notifications'
411 c.active = 'notifications'
411
412
412 return self._get_template_context(c)
413 return self._get_template_context(c)
413
414
414 @LoginRequired()
415 @LoginRequired()
415 @NotAnonymous()
416 @NotAnonymous()
416 @CSRFRequired()
417 @CSRFRequired()
417 @view_config(
418 @view_config(
418 route_name='my_account_notifications_toggle_visibility',
419 route_name='my_account_notifications_toggle_visibility',
419 request_method='POST', renderer='json_ext')
420 request_method='POST', renderer='json_ext')
420 def my_notifications_toggle_visibility(self):
421 def my_notifications_toggle_visibility(self):
421 user = self._rhodecode_db_user
422 user = self._rhodecode_db_user
422 new_status = not user.user_data.get('notification_status', True)
423 new_status = not user.user_data.get('notification_status', True)
423 user.update_userdata(notification_status=new_status)
424 user.update_userdata(notification_status=new_status)
424 Session().commit()
425 Session().commit()
425 return user.user_data['notification_status']
426 return user.user_data['notification_status']
426
427
427 @LoginRequired()
428 @LoginRequired()
428 @NotAnonymous()
429 @NotAnonymous()
429 @view_config(
430 @view_config(
430 route_name='my_account_edit',
431 route_name='my_account_edit',
431 request_method='GET',
432 request_method='GET',
432 renderer='rhodecode:templates/admin/my_account/my_account.mako')
433 renderer='rhodecode:templates/admin/my_account/my_account.mako')
433 def my_account_edit(self):
434 def my_account_edit(self):
434 c = self.load_default_context()
435 c = self.load_default_context()
435 c.active = 'profile_edit'
436 c.active = 'profile_edit'
436 c.extern_type = c.user.extern_type
437 c.extern_type = c.user.extern_type
437 c.extern_name = c.user.extern_name
438 c.extern_name = c.user.extern_name
438
439
439 schema = user_schema.UserProfileSchema().bind(
440 schema = user_schema.UserProfileSchema().bind(
440 username=c.user.username, user_emails=c.user.emails)
441 username=c.user.username, user_emails=c.user.emails)
441 appstruct = {
442 appstruct = {
442 'username': c.user.username,
443 'username': c.user.username,
443 'email': c.user.email,
444 'email': c.user.email,
444 'firstname': c.user.firstname,
445 'firstname': c.user.firstname,
445 'lastname': c.user.lastname,
446 'lastname': c.user.lastname,
446 }
447 }
447 c.form = forms.RcForm(
448 c.form = forms.RcForm(
448 schema, appstruct=appstruct,
449 schema, appstruct=appstruct,
449 action=h.route_path('my_account_update'),
450 action=h.route_path('my_account_update'),
450 buttons=(forms.buttons.save, forms.buttons.reset))
451 buttons=(forms.buttons.save, forms.buttons.reset))
451
452
452 return self._get_template_context(c)
453 return self._get_template_context(c)
453
454
454 @LoginRequired()
455 @LoginRequired()
455 @NotAnonymous()
456 @NotAnonymous()
456 @CSRFRequired()
457 @CSRFRequired()
457 @view_config(
458 @view_config(
458 route_name='my_account_update',
459 route_name='my_account_update',
459 request_method='POST',
460 request_method='POST',
460 renderer='rhodecode:templates/admin/my_account/my_account.mako')
461 renderer='rhodecode:templates/admin/my_account/my_account.mako')
461 def my_account_update(self):
462 def my_account_update(self):
462 _ = self.request.translate
463 _ = self.request.translate
463 c = self.load_default_context()
464 c = self.load_default_context()
464 c.active = 'profile_edit'
465 c.active = 'profile_edit'
465 c.perm_user = c.auth_user
466 c.perm_user = c.auth_user
466 c.extern_type = c.user.extern_type
467 c.extern_type = c.user.extern_type
467 c.extern_name = c.user.extern_name
468 c.extern_name = c.user.extern_name
468
469
469 schema = user_schema.UserProfileSchema().bind(
470 schema = user_schema.UserProfileSchema().bind(
470 username=c.user.username, user_emails=c.user.emails)
471 username=c.user.username, user_emails=c.user.emails)
471 form = forms.RcForm(
472 form = forms.RcForm(
472 schema, buttons=(forms.buttons.save, forms.buttons.reset))
473 schema, buttons=(forms.buttons.save, forms.buttons.reset))
473
474
474 controls = self.request.POST.items()
475 controls = self.request.POST.items()
475 try:
476 try:
476 valid_data = form.validate(controls)
477 valid_data = form.validate(controls)
477 skip_attrs = ['admin', 'active', 'extern_type', 'extern_name',
478 skip_attrs = ['admin', 'active', 'extern_type', 'extern_name',
478 'new_password', 'password_confirmation']
479 'new_password', 'password_confirmation']
479 if c.extern_type != "rhodecode":
480 if c.extern_type != "rhodecode":
480 # forbid updating username for external accounts
481 # forbid updating username for external accounts
481 skip_attrs.append('username')
482 skip_attrs.append('username')
482 old_email = c.user.email
483 old_email = c.user.email
483 UserModel().update_user(
484 UserModel().update_user(
484 self._rhodecode_user.user_id, skip_attrs=skip_attrs,
485 self._rhodecode_user.user_id, skip_attrs=skip_attrs,
485 **valid_data)
486 **valid_data)
486 if old_email != valid_data['email']:
487 if old_email != valid_data['email']:
487 old = UserEmailMap.query() \
488 old = UserEmailMap.query() \
488 .filter(UserEmailMap.user == c.user).filter(UserEmailMap.email == valid_data['email']).first()
489 .filter(UserEmailMap.user == c.user).filter(UserEmailMap.email == valid_data['email']).first()
489 old.email = old_email
490 old.email = old_email
490 h.flash(_('Your account was updated successfully'), category='success')
491 h.flash(_('Your account was updated successfully'), category='success')
491 Session().commit()
492 Session().commit()
492 except forms.ValidationFailure as e:
493 except forms.ValidationFailure as e:
493 c.form = e
494 c.form = e
494 return self._get_template_context(c)
495 return self._get_template_context(c)
495 except Exception:
496 except Exception:
496 log.exception("Exception updating user")
497 log.exception("Exception updating user")
497 h.flash(_('Error occurred during update of user'),
498 h.flash(_('Error occurred during update of user'),
498 category='error')
499 category='error')
499 raise HTTPFound(h.route_path('my_account_profile'))
500 raise HTTPFound(h.route_path('my_account_profile'))
500
501
501 def _get_pull_requests_list(self, statuses):
502 def _get_pull_requests_list(self, statuses):
502 draw, start, limit = self._extract_chunk(self.request)
503 draw, start, limit = self._extract_chunk(self.request)
503 search_q, order_by, order_dir = self._extract_ordering(self.request)
504 search_q, order_by, order_dir = self._extract_ordering(self.request)
504 _render = self.request.get_partial_renderer(
505 _render = self.request.get_partial_renderer(
505 'rhodecode:templates/data_table/_dt_elements.mako')
506 'rhodecode:templates/data_table/_dt_elements.mako')
506
507
507 pull_requests = PullRequestModel().get_im_participating_in(
508 pull_requests = PullRequestModel().get_im_participating_in(
508 user_id=self._rhodecode_user.user_id,
509 user_id=self._rhodecode_user.user_id,
509 statuses=statuses,
510 statuses=statuses,
510 offset=start, length=limit, order_by=order_by,
511 offset=start, length=limit, order_by=order_by,
511 order_dir=order_dir)
512 order_dir=order_dir)
512
513
513 pull_requests_total_count = PullRequestModel().count_im_participating_in(
514 pull_requests_total_count = PullRequestModel().count_im_participating_in(
514 user_id=self._rhodecode_user.user_id, statuses=statuses)
515 user_id=self._rhodecode_user.user_id, statuses=statuses)
515
516
516 data = []
517 data = []
517 comments_model = CommentsModel()
518 comments_model = CommentsModel()
518 for pr in pull_requests:
519 for pr in pull_requests:
519 repo_id = pr.target_repo_id
520 repo_id = pr.target_repo_id
520 comments = comments_model.get_all_comments(
521 comments = comments_model.get_all_comments(
521 repo_id, pull_request=pr)
522 repo_id, pull_request=pr)
522 owned = pr.user_id == self._rhodecode_user.user_id
523 owned = pr.user_id == self._rhodecode_user.user_id
523
524
524 data.append({
525 data.append({
525 'target_repo': _render('pullrequest_target_repo',
526 'target_repo': _render('pullrequest_target_repo',
526 pr.target_repo.repo_name),
527 pr.target_repo.repo_name),
527 'name': _render('pullrequest_name',
528 'name': _render('pullrequest_name',
528 pr.pull_request_id, pr.target_repo.repo_name,
529 pr.pull_request_id, pr.target_repo.repo_name,
529 short=True),
530 short=True),
530 'name_raw': pr.pull_request_id,
531 'name_raw': pr.pull_request_id,
531 'status': _render('pullrequest_status',
532 'status': _render('pullrequest_status',
532 pr.calculated_review_status()),
533 pr.calculated_review_status()),
533 'title': _render(
534 'title': _render(
534 'pullrequest_title', pr.title, pr.description),
535 'pullrequest_title', pr.title, pr.description),
535 'description': h.escape(pr.description),
536 'description': h.escape(pr.description),
536 'updated_on': _render('pullrequest_updated_on',
537 'updated_on': _render('pullrequest_updated_on',
537 h.datetime_to_time(pr.updated_on)),
538 h.datetime_to_time(pr.updated_on)),
538 'updated_on_raw': h.datetime_to_time(pr.updated_on),
539 'updated_on_raw': h.datetime_to_time(pr.updated_on),
539 'created_on': _render('pullrequest_updated_on',
540 'created_on': _render('pullrequest_updated_on',
540 h.datetime_to_time(pr.created_on)),
541 h.datetime_to_time(pr.created_on)),
541 'created_on_raw': h.datetime_to_time(pr.created_on),
542 'created_on_raw': h.datetime_to_time(pr.created_on),
542 'author': _render('pullrequest_author',
543 'author': _render('pullrequest_author',
543 pr.author.full_contact, ),
544 pr.author.full_contact, ),
544 'author_raw': pr.author.full_name,
545 'author_raw': pr.author.full_name,
545 'comments': _render('pullrequest_comments', len(comments)),
546 'comments': _render('pullrequest_comments', len(comments)),
546 'comments_raw': len(comments),
547 'comments_raw': len(comments),
547 'closed': pr.is_closed(),
548 'closed': pr.is_closed(),
548 'owned': owned
549 'owned': owned
549 })
550 })
550
551
551 # json used to render the grid
552 # json used to render the grid
552 data = ({
553 data = ({
553 'draw': draw,
554 'draw': draw,
554 'data': data,
555 'data': data,
555 'recordsTotal': pull_requests_total_count,
556 'recordsTotal': pull_requests_total_count,
556 'recordsFiltered': pull_requests_total_count,
557 'recordsFiltered': pull_requests_total_count,
557 })
558 })
558 return data
559 return data
559
560
560 @LoginRequired()
561 @LoginRequired()
561 @NotAnonymous()
562 @NotAnonymous()
562 @view_config(
563 @view_config(
563 route_name='my_account_pullrequests',
564 route_name='my_account_pullrequests',
564 request_method='GET',
565 request_method='GET',
565 renderer='rhodecode:templates/admin/my_account/my_account.mako')
566 renderer='rhodecode:templates/admin/my_account/my_account.mako')
566 def my_account_pullrequests(self):
567 def my_account_pullrequests(self):
567 c = self.load_default_context()
568 c = self.load_default_context()
568 c.active = 'pullrequests'
569 c.active = 'pullrequests'
569 req_get = self.request.GET
570 req_get = self.request.GET
570
571
571 c.closed = str2bool(req_get.get('pr_show_closed'))
572 c.closed = str2bool(req_get.get('pr_show_closed'))
572
573
573 return self._get_template_context(c)
574 return self._get_template_context(c)
574
575
575 @LoginRequired()
576 @LoginRequired()
576 @NotAnonymous()
577 @NotAnonymous()
577 @view_config(
578 @view_config(
578 route_name='my_account_pullrequests_data',
579 route_name='my_account_pullrequests_data',
579 request_method='GET', renderer='json_ext')
580 request_method='GET', renderer='json_ext')
580 def my_account_pullrequests_data(self):
581 def my_account_pullrequests_data(self):
581 self.load_default_context()
582 self.load_default_context()
582 req_get = self.request.GET
583 req_get = self.request.GET
583 closed = str2bool(req_get.get('closed'))
584 closed = str2bool(req_get.get('closed'))
584
585
585 statuses = [PullRequest.STATUS_NEW, PullRequest.STATUS_OPEN]
586 statuses = [PullRequest.STATUS_NEW, PullRequest.STATUS_OPEN]
586 if closed:
587 if closed:
587 statuses += [PullRequest.STATUS_CLOSED]
588 statuses += [PullRequest.STATUS_CLOSED]
588
589
589 data = self._get_pull_requests_list(statuses=statuses)
590 data = self._get_pull_requests_list(statuses=statuses)
590 return data
591 return data
591
592
592 @LoginRequired()
593 @LoginRequired()
593 @NotAnonymous()
594 @NotAnonymous()
594 @view_config(
595 @view_config(
595 route_name='my_account_user_group_membership',
596 route_name='my_account_user_group_membership',
596 request_method='GET',
597 request_method='GET',
597 renderer='rhodecode:templates/admin/my_account/my_account.mako')
598 renderer='rhodecode:templates/admin/my_account/my_account.mako')
598 def my_account_user_group_membership(self):
599 def my_account_user_group_membership(self):
599 c = self.load_default_context()
600 c = self.load_default_context()
600 c.active = 'user_group_membership'
601 c.active = 'user_group_membership'
601 groups = [UserGroupModel.get_user_groups_as_dict(group.users_group)
602 groups = [UserGroupModel.get_user_groups_as_dict(group.users_group)
602 for group in self._rhodecode_db_user.group_member]
603 for group in self._rhodecode_db_user.group_member]
603 c.user_groups = json.dumps(groups)
604 c.user_groups = json.dumps(groups)
604 return self._get_template_context(c)
605 return self._get_template_context(c)
@@ -1,620 +1,621 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2018 RhodeCode GmbH
3 # Copyright (C) 2010-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 """
21 """
22 Database creation, and setup module for RhodeCode Enterprise. Used for creation
22 Database creation, and setup module for RhodeCode Enterprise. Used for creation
23 of database as well as for migration operations
23 of database as well as for migration operations
24 """
24 """
25
25
26 import os
26 import os
27 import sys
27 import sys
28 import time
28 import time
29 import uuid
29 import uuid
30 import logging
30 import logging
31 import getpass
31 import getpass
32 from os.path import dirname as dn, join as jn
32 from os.path import dirname as dn, join as jn
33
33
34 from sqlalchemy.engine import create_engine
34 from sqlalchemy.engine import create_engine
35
35
36 from rhodecode import __dbversion__
36 from rhodecode import __dbversion__
37 from rhodecode.model import init_model
37 from rhodecode.model import init_model
38 from rhodecode.model.user import UserModel
38 from rhodecode.model.user import UserModel
39 from rhodecode.model.db import (
39 from rhodecode.model.db import (
40 User, Permission, RhodeCodeUi, RhodeCodeSetting, UserToPerm,
40 User, Permission, RhodeCodeUi, RhodeCodeSetting, UserToPerm,
41 DbMigrateVersion, RepoGroup, UserRepoGroupToPerm, CacheKey, Repository)
41 DbMigrateVersion, RepoGroup, UserRepoGroupToPerm, CacheKey, Repository)
42 from rhodecode.model.meta import Session, Base
42 from rhodecode.model.meta import Session, Base
43 from rhodecode.model.permission import PermissionModel
43 from rhodecode.model.permission import PermissionModel
44 from rhodecode.model.repo import RepoModel
44 from rhodecode.model.repo import RepoModel
45 from rhodecode.model.repo_group import RepoGroupModel
45 from rhodecode.model.repo_group import RepoGroupModel
46 from rhodecode.model.settings import SettingsModel
46 from rhodecode.model.settings import SettingsModel
47
47
48
48
49 log = logging.getLogger(__name__)
49 log = logging.getLogger(__name__)
50
50
51
51
52 def notify(msg):
52 def notify(msg):
53 """
53 """
54 Notification for migrations messages
54 Notification for migrations messages
55 """
55 """
56 ml = len(msg) + (4 * 2)
56 ml = len(msg) + (4 * 2)
57 print(('\n%s\n*** %s ***\n%s' % ('*' * ml, msg, '*' * ml)).upper())
57 print(('\n%s\n*** %s ***\n%s' % ('*' * ml, msg, '*' * ml)).upper())
58
58
59
59
60 class DbManage(object):
60 class DbManage(object):
61
61
62 def __init__(self, log_sql, dbconf, root, tests=False,
62 def __init__(self, log_sql, dbconf, root, tests=False,
63 SESSION=None, cli_args=None):
63 SESSION=None, cli_args=None):
64 self.dbname = dbconf.split('/')[-1]
64 self.dbname = dbconf.split('/')[-1]
65 self.tests = tests
65 self.tests = tests
66 self.root = root
66 self.root = root
67 self.dburi = dbconf
67 self.dburi = dbconf
68 self.log_sql = log_sql
68 self.log_sql = log_sql
69 self.db_exists = False
69 self.db_exists = False
70 self.cli_args = cli_args or {}
70 self.cli_args = cli_args or {}
71 self.init_db(SESSION=SESSION)
71 self.init_db(SESSION=SESSION)
72 self.ask_ok = self.get_ask_ok_func(self.cli_args.get('force_ask'))
72 self.ask_ok = self.get_ask_ok_func(self.cli_args.get('force_ask'))
73
73
74 def get_ask_ok_func(self, param):
74 def get_ask_ok_func(self, param):
75 if param not in [None]:
75 if param not in [None]:
76 # return a function lambda that has a default set to param
76 # return a function lambda that has a default set to param
77 return lambda *args, **kwargs: param
77 return lambda *args, **kwargs: param
78 else:
78 else:
79 from rhodecode.lib.utils import ask_ok
79 from rhodecode.lib.utils import ask_ok
80 return ask_ok
80 return ask_ok
81
81
82 def init_db(self, SESSION=None):
82 def init_db(self, SESSION=None):
83 if SESSION:
83 if SESSION:
84 self.sa = SESSION
84 self.sa = SESSION
85 else:
85 else:
86 # init new sessions
86 # init new sessions
87 engine = create_engine(self.dburi, echo=self.log_sql)
87 engine = create_engine(self.dburi, echo=self.log_sql)
88 init_model(engine)
88 init_model(engine)
89 self.sa = Session()
89 self.sa = Session()
90
90
91 def create_tables(self, override=False):
91 def create_tables(self, override=False):
92 """
92 """
93 Create a auth database
93 Create a auth database
94 """
94 """
95
95
96 log.info("Existing database with the same name is going to be destroyed.")
96 log.info("Existing database with the same name is going to be destroyed.")
97 log.info("Setup command will run DROP ALL command on that database.")
97 log.info("Setup command will run DROP ALL command on that database.")
98 if self.tests:
98 if self.tests:
99 destroy = True
99 destroy = True
100 else:
100 else:
101 destroy = self.ask_ok('Are you sure that you want to destroy the old database? [y/n]')
101 destroy = self.ask_ok('Are you sure that you want to destroy the old database? [y/n]')
102 if not destroy:
102 if not destroy:
103 log.info('Nothing done.')
103 log.info('Nothing done.')
104 sys.exit(0)
104 sys.exit(0)
105 if destroy:
105 if destroy:
106 Base.metadata.drop_all()
106 Base.metadata.drop_all()
107
107
108 checkfirst = not override
108 checkfirst = not override
109 Base.metadata.create_all(checkfirst=checkfirst)
109 Base.metadata.create_all(checkfirst=checkfirst)
110 log.info('Created tables for %s' % self.dbname)
110 log.info('Created tables for %s' % self.dbname)
111
111
112 def set_db_version(self):
112 def set_db_version(self):
113 ver = DbMigrateVersion()
113 ver = DbMigrateVersion()
114 ver.version = __dbversion__
114 ver.version = __dbversion__
115 ver.repository_id = 'rhodecode_db_migrations'
115 ver.repository_id = 'rhodecode_db_migrations'
116 ver.repository_path = 'versions'
116 ver.repository_path = 'versions'
117 self.sa.add(ver)
117 self.sa.add(ver)
118 log.info('db version set to: %s' % __dbversion__)
118 log.info('db version set to: %s' % __dbversion__)
119
119
120 def run_pre_migration_tasks(self):
120 def run_pre_migration_tasks(self):
121 """
121 """
122 Run various tasks before actually doing migrations
122 Run various tasks before actually doing migrations
123 """
123 """
124 # delete cache keys on each upgrade
124 # delete cache keys on each upgrade
125 total = CacheKey.query().count()
125 total = CacheKey.query().count()
126 log.info("Deleting (%s) cache keys now...", total)
126 log.info("Deleting (%s) cache keys now...", total)
127 CacheKey.delete_all_cache()
127 CacheKey.delete_all_cache()
128
128
129 def upgrade(self, version=None):
129 def upgrade(self, version=None):
130 """
130 """
131 Upgrades given database schema to given revision following
131 Upgrades given database schema to given revision following
132 all needed steps, to perform the upgrade
132 all needed steps, to perform the upgrade
133
133
134 """
134 """
135
135
136 from rhodecode.lib.dbmigrate.migrate.versioning import api
136 from rhodecode.lib.dbmigrate.migrate.versioning import api
137 from rhodecode.lib.dbmigrate.migrate.exceptions import \
137 from rhodecode.lib.dbmigrate.migrate.exceptions import \
138 DatabaseNotControlledError
138 DatabaseNotControlledError
139
139
140 if 'sqlite' in self.dburi:
140 if 'sqlite' in self.dburi:
141 print (
141 print (
142 '********************** WARNING **********************\n'
142 '********************** WARNING **********************\n'
143 'Make sure your version of sqlite is at least 3.7.X. \n'
143 'Make sure your version of sqlite is at least 3.7.X. \n'
144 'Earlier versions are known to fail on some migrations\n'
144 'Earlier versions are known to fail on some migrations\n'
145 '*****************************************************\n')
145 '*****************************************************\n')
146
146
147 upgrade = self.ask_ok(
147 upgrade = self.ask_ok(
148 'You are about to perform a database upgrade. Make '
148 'You are about to perform a database upgrade. Make '
149 'sure you have backed up your database. '
149 'sure you have backed up your database. '
150 'Continue ? [y/n]')
150 'Continue ? [y/n]')
151 if not upgrade:
151 if not upgrade:
152 log.info('No upgrade performed')
152 log.info('No upgrade performed')
153 sys.exit(0)
153 sys.exit(0)
154
154
155 repository_path = jn(dn(dn(dn(os.path.realpath(__file__)))),
155 repository_path = jn(dn(dn(dn(os.path.realpath(__file__)))),
156 'rhodecode/lib/dbmigrate')
156 'rhodecode/lib/dbmigrate')
157 db_uri = self.dburi
157 db_uri = self.dburi
158
158
159 try:
159 try:
160 curr_version = version or api.db_version(db_uri, repository_path)
160 curr_version = version or api.db_version(db_uri, repository_path)
161 msg = ('Found current database db_uri under version '
161 msg = ('Found current database db_uri under version '
162 'control with version {}'.format(curr_version))
162 'control with version {}'.format(curr_version))
163
163
164 except (RuntimeError, DatabaseNotControlledError):
164 except (RuntimeError, DatabaseNotControlledError):
165 curr_version = 1
165 curr_version = 1
166 msg = ('Current database is not under version control. Setting '
166 msg = ('Current database is not under version control. Setting '
167 'as version %s' % curr_version)
167 'as version %s' % curr_version)
168 api.version_control(db_uri, repository_path, curr_version)
168 api.version_control(db_uri, repository_path, curr_version)
169
169
170 notify(msg)
170 notify(msg)
171
171
172 self.run_pre_migration_tasks()
172 self.run_pre_migration_tasks()
173
173
174 if curr_version == __dbversion__:
174 if curr_version == __dbversion__:
175 log.info('This database is already at the newest version')
175 log.info('This database is already at the newest version')
176 sys.exit(0)
176 sys.exit(0)
177
177
178 upgrade_steps = range(curr_version + 1, __dbversion__ + 1)
178 upgrade_steps = range(curr_version + 1, __dbversion__ + 1)
179 notify('attempting to upgrade database from '
179 notify('attempting to upgrade database from '
180 'version %s to version %s' % (curr_version, __dbversion__))
180 'version %s to version %s' % (curr_version, __dbversion__))
181
181
182 # CALL THE PROPER ORDER OF STEPS TO PERFORM FULL UPGRADE
182 # CALL THE PROPER ORDER OF STEPS TO PERFORM FULL UPGRADE
183 _step = None
183 _step = None
184 for step in upgrade_steps:
184 for step in upgrade_steps:
185 notify('performing upgrade step %s' % step)
185 notify('performing upgrade step %s' % step)
186 time.sleep(0.5)
186 time.sleep(0.5)
187
187
188 api.upgrade(db_uri, repository_path, step)
188 api.upgrade(db_uri, repository_path, step)
189 self.sa.rollback()
189 self.sa.rollback()
190 notify('schema upgrade for step %s completed' % (step,))
190 notify('schema upgrade for step %s completed' % (step,))
191
191
192 _step = step
192 _step = step
193
193
194 notify('upgrade to version %s successful' % _step)
194 notify('upgrade to version %s successful' % _step)
195
195
196 def fix_repo_paths(self):
196 def fix_repo_paths(self):
197 """
197 """
198 Fixes an old RhodeCode version path into new one without a '*'
198 Fixes an old RhodeCode version path into new one without a '*'
199 """
199 """
200
200
201 paths = self.sa.query(RhodeCodeUi)\
201 paths = self.sa.query(RhodeCodeUi)\
202 .filter(RhodeCodeUi.ui_key == '/')\
202 .filter(RhodeCodeUi.ui_key == '/')\
203 .scalar()
203 .scalar()
204
204
205 paths.ui_value = paths.ui_value.replace('*', '')
205 paths.ui_value = paths.ui_value.replace('*', '')
206
206
207 try:
207 try:
208 self.sa.add(paths)
208 self.sa.add(paths)
209 self.sa.commit()
209 self.sa.commit()
210 except Exception:
210 except Exception:
211 self.sa.rollback()
211 self.sa.rollback()
212 raise
212 raise
213
213
214 def fix_default_user(self):
214 def fix_default_user(self):
215 """
215 """
216 Fixes an old default user with some 'nicer' default values,
216 Fixes an old default user with some 'nicer' default values,
217 used mostly for anonymous access
217 used mostly for anonymous access
218 """
218 """
219 def_user = self.sa.query(User)\
219 def_user = self.sa.query(User)\
220 .filter(User.username == User.DEFAULT_USER)\
220 .filter(User.username == User.DEFAULT_USER)\
221 .one()
221 .one()
222
222
223 def_user.name = 'Anonymous'
223 def_user.name = 'Anonymous'
224 def_user.lastname = 'User'
224 def_user.lastname = 'User'
225 def_user.email = User.DEFAULT_USER_EMAIL
225 def_user.email = User.DEFAULT_USER_EMAIL
226
226
227 try:
227 try:
228 self.sa.add(def_user)
228 self.sa.add(def_user)
229 self.sa.commit()
229 self.sa.commit()
230 except Exception:
230 except Exception:
231 self.sa.rollback()
231 self.sa.rollback()
232 raise
232 raise
233
233
234 def fix_settings(self):
234 def fix_settings(self):
235 """
235 """
236 Fixes rhodecode settings and adds ga_code key for google analytics
236 Fixes rhodecode settings and adds ga_code key for google analytics
237 """
237 """
238
238
239 hgsettings3 = RhodeCodeSetting('ga_code', '')
239 hgsettings3 = RhodeCodeSetting('ga_code', '')
240
240
241 try:
241 try:
242 self.sa.add(hgsettings3)
242 self.sa.add(hgsettings3)
243 self.sa.commit()
243 self.sa.commit()
244 except Exception:
244 except Exception:
245 self.sa.rollback()
245 self.sa.rollback()
246 raise
246 raise
247
247
248 def create_admin_and_prompt(self):
248 def create_admin_and_prompt(self):
249
249
250 # defaults
250 # defaults
251 defaults = self.cli_args
251 defaults = self.cli_args
252 username = defaults.get('username')
252 username = defaults.get('username')
253 password = defaults.get('password')
253 password = defaults.get('password')
254 email = defaults.get('email')
254 email = defaults.get('email')
255
255
256 if username is None:
256 if username is None:
257 username = raw_input('Specify admin username:')
257 username = raw_input('Specify admin username:')
258 if password is None:
258 if password is None:
259 password = self._get_admin_password()
259 password = self._get_admin_password()
260 if not password:
260 if not password:
261 # second try
261 # second try
262 password = self._get_admin_password()
262 password = self._get_admin_password()
263 if not password:
263 if not password:
264 sys.exit()
264 sys.exit()
265 if email is None:
265 if email is None:
266 email = raw_input('Specify admin email:')
266 email = raw_input('Specify admin email:')
267 api_key = self.cli_args.get('api_key')
267 api_key = self.cli_args.get('api_key')
268 self.create_user(username, password, email, True,
268 self.create_user(username, password, email, True,
269 strict_creation_check=False,
269 strict_creation_check=False,
270 api_key=api_key)
270 api_key=api_key)
271
271
272 def _get_admin_password(self):
272 def _get_admin_password(self):
273 password = getpass.getpass('Specify admin password '
273 password = getpass.getpass('Specify admin password '
274 '(min 6 chars):')
274 '(min 6 chars):')
275 confirm = getpass.getpass('Confirm password:')
275 confirm = getpass.getpass('Confirm password:')
276
276
277 if password != confirm:
277 if password != confirm:
278 log.error('passwords mismatch')
278 log.error('passwords mismatch')
279 return False
279 return False
280 if len(password) < 6:
280 if len(password) < 6:
281 log.error('password is too short - use at least 6 characters')
281 log.error('password is too short - use at least 6 characters')
282 return False
282 return False
283
283
284 return password
284 return password
285
285
286 def create_test_admin_and_users(self):
286 def create_test_admin_and_users(self):
287 log.info('creating admin and regular test users')
287 log.info('creating admin and regular test users')
288 from rhodecode.tests import TEST_USER_ADMIN_LOGIN, \
288 from rhodecode.tests import TEST_USER_ADMIN_LOGIN, \
289 TEST_USER_ADMIN_PASS, TEST_USER_ADMIN_EMAIL, \
289 TEST_USER_ADMIN_PASS, TEST_USER_ADMIN_EMAIL, \
290 TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS, \
290 TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS, \
291 TEST_USER_REGULAR_EMAIL, TEST_USER_REGULAR2_LOGIN, \
291 TEST_USER_REGULAR_EMAIL, TEST_USER_REGULAR2_LOGIN, \
292 TEST_USER_REGULAR2_PASS, TEST_USER_REGULAR2_EMAIL
292 TEST_USER_REGULAR2_PASS, TEST_USER_REGULAR2_EMAIL
293
293
294 self.create_user(TEST_USER_ADMIN_LOGIN, TEST_USER_ADMIN_PASS,
294 self.create_user(TEST_USER_ADMIN_LOGIN, TEST_USER_ADMIN_PASS,
295 TEST_USER_ADMIN_EMAIL, True, api_key=True)
295 TEST_USER_ADMIN_EMAIL, True, api_key=True)
296
296
297 self.create_user(TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS,
297 self.create_user(TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS,
298 TEST_USER_REGULAR_EMAIL, False, api_key=True)
298 TEST_USER_REGULAR_EMAIL, False, api_key=True)
299
299
300 self.create_user(TEST_USER_REGULAR2_LOGIN, TEST_USER_REGULAR2_PASS,
300 self.create_user(TEST_USER_REGULAR2_LOGIN, TEST_USER_REGULAR2_PASS,
301 TEST_USER_REGULAR2_EMAIL, False, api_key=True)
301 TEST_USER_REGULAR2_EMAIL, False, api_key=True)
302
302
303 def create_ui_settings(self, repo_store_path):
303 def create_ui_settings(self, repo_store_path):
304 """
304 """
305 Creates ui settings, fills out hooks
305 Creates ui settings, fills out hooks
306 and disables dotencode
306 and disables dotencode
307 """
307 """
308 settings_model = SettingsModel(sa=self.sa)
308 settings_model = SettingsModel(sa=self.sa)
309 from rhodecode.lib.vcs.backends.hg import largefiles_store
309 from rhodecode.lib.vcs.backends.hg import largefiles_store
310 from rhodecode.lib.vcs.backends.git import lfs_store
310 from rhodecode.lib.vcs.backends.git import lfs_store
311
311
312 # Build HOOKS
312 # Build HOOKS
313 hooks = [
313 hooks = [
314 (RhodeCodeUi.HOOK_REPO_SIZE, 'python:vcsserver.hooks.repo_size'),
314 (RhodeCodeUi.HOOK_REPO_SIZE, 'python:vcsserver.hooks.repo_size'),
315
315
316 # HG
316 # HG
317 (RhodeCodeUi.HOOK_PRE_PULL, 'python:vcsserver.hooks.pre_pull'),
317 (RhodeCodeUi.HOOK_PRE_PULL, 'python:vcsserver.hooks.pre_pull'),
318 (RhodeCodeUi.HOOK_PULL, 'python:vcsserver.hooks.log_pull_action'),
318 (RhodeCodeUi.HOOK_PULL, 'python:vcsserver.hooks.log_pull_action'),
319 (RhodeCodeUi.HOOK_PRE_PUSH, 'python:vcsserver.hooks.pre_push'),
319 (RhodeCodeUi.HOOK_PRE_PUSH, 'python:vcsserver.hooks.pre_push'),
320 (RhodeCodeUi.HOOK_PRETX_PUSH, 'python:vcsserver.hooks.pre_push'),
320 (RhodeCodeUi.HOOK_PRETX_PUSH, 'python:vcsserver.hooks.pre_push'),
321 (RhodeCodeUi.HOOK_PUSH, 'python:vcsserver.hooks.log_push_action'),
321 (RhodeCodeUi.HOOK_PUSH, 'python:vcsserver.hooks.log_push_action'),
322 (RhodeCodeUi.HOOK_PUSH_KEY, 'python:vcsserver.hooks.key_push'),
322 (RhodeCodeUi.HOOK_PUSH_KEY, 'python:vcsserver.hooks.key_push'),
323
323
324 ]
324 ]
325
325
326 for key, value in hooks:
326 for key, value in hooks:
327 hook_obj = settings_model.get_ui_by_key(key)
327 hook_obj = settings_model.get_ui_by_key(key)
328 hooks2 = hook_obj if hook_obj else RhodeCodeUi()
328 hooks2 = hook_obj if hook_obj else RhodeCodeUi()
329 hooks2.ui_section = 'hooks'
329 hooks2.ui_section = 'hooks'
330 hooks2.ui_key = key
330 hooks2.ui_key = key
331 hooks2.ui_value = value
331 hooks2.ui_value = value
332 self.sa.add(hooks2)
332 self.sa.add(hooks2)
333
333
334 # enable largefiles
334 # enable largefiles
335 largefiles = RhodeCodeUi()
335 largefiles = RhodeCodeUi()
336 largefiles.ui_section = 'extensions'
336 largefiles.ui_section = 'extensions'
337 largefiles.ui_key = 'largefiles'
337 largefiles.ui_key = 'largefiles'
338 largefiles.ui_value = ''
338 largefiles.ui_value = ''
339 self.sa.add(largefiles)
339 self.sa.add(largefiles)
340
340
341 # set default largefiles cache dir, defaults to
341 # set default largefiles cache dir, defaults to
342 # /repo_store_location/.cache/largefiles
342 # /repo_store_location/.cache/largefiles
343 largefiles = RhodeCodeUi()
343 largefiles = RhodeCodeUi()
344 largefiles.ui_section = 'largefiles'
344 largefiles.ui_section = 'largefiles'
345 largefiles.ui_key = 'usercache'
345 largefiles.ui_key = 'usercache'
346 largefiles.ui_value = largefiles_store(repo_store_path)
346 largefiles.ui_value = largefiles_store(repo_store_path)
347
347
348 self.sa.add(largefiles)
348 self.sa.add(largefiles)
349
349
350 # set default lfs cache dir, defaults to
350 # set default lfs cache dir, defaults to
351 # /repo_store_location/.cache/lfs_store
351 # /repo_store_location/.cache/lfs_store
352 lfsstore = RhodeCodeUi()
352 lfsstore = RhodeCodeUi()
353 lfsstore.ui_section = 'vcs_git_lfs'
353 lfsstore.ui_section = 'vcs_git_lfs'
354 lfsstore.ui_key = 'store_location'
354 lfsstore.ui_key = 'store_location'
355 lfsstore.ui_value = lfs_store(repo_store_path)
355 lfsstore.ui_value = lfs_store(repo_store_path)
356
356
357 self.sa.add(lfsstore)
357 self.sa.add(lfsstore)
358
358
359 # enable hgsubversion disabled by default
359 # enable hgsubversion disabled by default
360 hgsubversion = RhodeCodeUi()
360 hgsubversion = RhodeCodeUi()
361 hgsubversion.ui_section = 'extensions'
361 hgsubversion.ui_section = 'extensions'
362 hgsubversion.ui_key = 'hgsubversion'
362 hgsubversion.ui_key = 'hgsubversion'
363 hgsubversion.ui_value = ''
363 hgsubversion.ui_value = ''
364 hgsubversion.ui_active = False
364 hgsubversion.ui_active = False
365 self.sa.add(hgsubversion)
365 self.sa.add(hgsubversion)
366
366
367 # enable hgevolve disabled by default
367 # enable hgevolve disabled by default
368 hgevolve = RhodeCodeUi()
368 hgevolve = RhodeCodeUi()
369 hgevolve.ui_section = 'extensions'
369 hgevolve.ui_section = 'extensions'
370 hgevolve.ui_key = 'evolve'
370 hgevolve.ui_key = 'evolve'
371 hgevolve.ui_value = ''
371 hgevolve.ui_value = ''
372 hgevolve.ui_active = False
372 hgevolve.ui_active = False
373 self.sa.add(hgevolve)
373 self.sa.add(hgevolve)
374
374
375 # enable hggit disabled by default
375 # enable hggit disabled by default
376 hggit = RhodeCodeUi()
376 hggit = RhodeCodeUi()
377 hggit.ui_section = 'extensions'
377 hggit.ui_section = 'extensions'
378 hggit.ui_key = 'hggit'
378 hggit.ui_key = 'hggit'
379 hggit.ui_value = ''
379 hggit.ui_value = ''
380 hggit.ui_active = False
380 hggit.ui_active = False
381 self.sa.add(hggit)
381 self.sa.add(hggit)
382
382
383 # set svn branch defaults
383 # set svn branch defaults
384 branches = ["/branches/*", "/trunk"]
384 branches = ["/branches/*", "/trunk"]
385 tags = ["/tags/*"]
385 tags = ["/tags/*"]
386
386
387 for branch in branches:
387 for branch in branches:
388 settings_model.create_ui_section_value(
388 settings_model.create_ui_section_value(
389 RhodeCodeUi.SVN_BRANCH_ID, branch)
389 RhodeCodeUi.SVN_BRANCH_ID, branch)
390
390
391 for tag in tags:
391 for tag in tags:
392 settings_model.create_ui_section_value(RhodeCodeUi.SVN_TAG_ID, tag)
392 settings_model.create_ui_section_value(RhodeCodeUi.SVN_TAG_ID, tag)
393
393
394 def create_auth_plugin_options(self, skip_existing=False):
394 def create_auth_plugin_options(self, skip_existing=False):
395 """
395 """
396 Create default auth plugin settings, and make it active
396 Create default auth plugin settings, and make it active
397
397
398 :param skip_existing:
398 :param skip_existing:
399 """
399 """
400
400
401 for k, v, t in [('auth_plugins', 'egg:rhodecode-enterprise-ce#rhodecode', 'list'),
401 for k, v, t in [('auth_plugins', 'egg:rhodecode-enterprise-ce#rhodecode', 'list'),
402 ('auth_rhodecode_enabled', 'True', 'bool')]:
402 ('auth_rhodecode_enabled', 'True', 'bool')]:
403 if (skip_existing and
403 if (skip_existing and
404 SettingsModel().get_setting_by_name(k) is not None):
404 SettingsModel().get_setting_by_name(k) is not None):
405 log.debug('Skipping option %s' % k)
405 log.debug('Skipping option %s' % k)
406 continue
406 continue
407 setting = RhodeCodeSetting(k, v, t)
407 setting = RhodeCodeSetting(k, v, t)
408 self.sa.add(setting)
408 self.sa.add(setting)
409
409
410 def create_default_options(self, skip_existing=False):
410 def create_default_options(self, skip_existing=False):
411 """Creates default settings"""
411 """Creates default settings"""
412
412
413 for k, v, t in [
413 for k, v, t in [
414 ('default_repo_enable_locking', False, 'bool'),
414 ('default_repo_enable_locking', False, 'bool'),
415 ('default_repo_enable_downloads', False, 'bool'),
415 ('default_repo_enable_downloads', False, 'bool'),
416 ('default_repo_enable_statistics', False, 'bool'),
416 ('default_repo_enable_statistics', False, 'bool'),
417 ('default_repo_private', False, 'bool'),
417 ('default_repo_private', False, 'bool'),
418 ('default_repo_type', 'hg', 'unicode')]:
418 ('default_repo_type', 'hg', 'unicode')]:
419
419
420 if (skip_existing and
420 if (skip_existing and
421 SettingsModel().get_setting_by_name(k) is not None):
421 SettingsModel().get_setting_by_name(k) is not None):
422 log.debug('Skipping option %s' % k)
422 log.debug('Skipping option %s' % k)
423 continue
423 continue
424 setting = RhodeCodeSetting(k, v, t)
424 setting = RhodeCodeSetting(k, v, t)
425 self.sa.add(setting)
425 self.sa.add(setting)
426
426
427 def fixup_groups(self):
427 def fixup_groups(self):
428 def_usr = User.get_default_user()
428 def_usr = User.get_default_user()
429 for g in RepoGroup.query().all():
429 for g in RepoGroup.query().all():
430 g.group_name = g.get_new_name(g.name)
430 g.group_name = g.get_new_name(g.name)
431 self.sa.add(g)
431 self.sa.add(g)
432 # get default perm
432 # get default perm
433 default = UserRepoGroupToPerm.query()\
433 default = UserRepoGroupToPerm.query()\
434 .filter(UserRepoGroupToPerm.group == g)\
434 .filter(UserRepoGroupToPerm.group == g)\
435 .filter(UserRepoGroupToPerm.user == def_usr)\
435 .filter(UserRepoGroupToPerm.user == def_usr)\
436 .scalar()
436 .scalar()
437
437
438 if default is None:
438 if default is None:
439 log.debug('missing default permission for group %s adding' % g)
439 log.debug('missing default permission for group %s adding' % g)
440 perm_obj = RepoGroupModel()._create_default_perms(g)
440 perm_obj = RepoGroupModel()._create_default_perms(g)
441 self.sa.add(perm_obj)
441 self.sa.add(perm_obj)
442
442
443 def reset_permissions(self, username):
443 def reset_permissions(self, username):
444 """
444 """
445 Resets permissions to default state, useful when old systems had
445 Resets permissions to default state, useful when old systems had
446 bad permissions, we must clean them up
446 bad permissions, we must clean them up
447
447
448 :param username:
448 :param username:
449 """
449 """
450 default_user = User.get_by_username(username)
450 default_user = User.get_by_username(username)
451 if not default_user:
451 if not default_user:
452 return
452 return
453
453
454 u2p = UserToPerm.query()\
454 u2p = UserToPerm.query()\
455 .filter(UserToPerm.user == default_user).all()
455 .filter(UserToPerm.user == default_user).all()
456 fixed = False
456 fixed = False
457 if len(u2p) != len(Permission.DEFAULT_USER_PERMISSIONS):
457 if len(u2p) != len(Permission.DEFAULT_USER_PERMISSIONS):
458 for p in u2p:
458 for p in u2p:
459 Session().delete(p)
459 Session().delete(p)
460 fixed = True
460 fixed = True
461 self.populate_default_permissions()
461 self.populate_default_permissions()
462 return fixed
462 return fixed
463
463
464 def update_repo_info(self):
464 def update_repo_info(self):
465 RepoModel.update_repoinfo()
465 RepoModel.update_repoinfo()
466
466
467 def config_prompt(self, test_repo_path='', retries=3):
467 def config_prompt(self, test_repo_path='', retries=3):
468 defaults = self.cli_args
468 defaults = self.cli_args
469 _path = defaults.get('repos_location')
469 _path = defaults.get('repos_location')
470 if retries == 3:
470 if retries == 3:
471 log.info('Setting up repositories config')
471 log.info('Setting up repositories config')
472
472
473 if _path is not None:
473 if _path is not None:
474 path = _path
474 path = _path
475 elif not self.tests and not test_repo_path:
475 elif not self.tests and not test_repo_path:
476 path = raw_input(
476 path = raw_input(
477 'Enter a valid absolute path to store repositories. '
477 'Enter a valid absolute path to store repositories. '
478 'All repositories in that path will be added automatically:'
478 'All repositories in that path will be added automatically:'
479 )
479 )
480 else:
480 else:
481 path = test_repo_path
481 path = test_repo_path
482 path_ok = True
482 path_ok = True
483
483
484 # check proper dir
484 # check proper dir
485 if not os.path.isdir(path):
485 if not os.path.isdir(path):
486 path_ok = False
486 path_ok = False
487 log.error('Given path %s is not a valid directory' % (path,))
487 log.error('Given path %s is not a valid directory' % (path,))
488
488
489 elif not os.path.isabs(path):
489 elif not os.path.isabs(path):
490 path_ok = False
490 path_ok = False
491 log.error('Given path %s is not an absolute path' % (path,))
491 log.error('Given path %s is not an absolute path' % (path,))
492
492
493 # check if path is at least readable.
493 # check if path is at least readable.
494 if not os.access(path, os.R_OK):
494 if not os.access(path, os.R_OK):
495 path_ok = False
495 path_ok = False
496 log.error('Given path %s is not readable' % (path,))
496 log.error('Given path %s is not readable' % (path,))
497
497
498 # check write access, warn user about non writeable paths
498 # check write access, warn user about non writeable paths
499 elif not os.access(path, os.W_OK) and path_ok:
499 elif not os.access(path, os.W_OK) and path_ok:
500 log.warning('No write permission to given path %s' % (path,))
500 log.warning('No write permission to given path %s' % (path,))
501
501
502 q = ('Given path %s is not writeable, do you want to '
502 q = ('Given path %s is not writeable, do you want to '
503 'continue with read only mode ? [y/n]' % (path,))
503 'continue with read only mode ? [y/n]' % (path,))
504 if not self.ask_ok(q):
504 if not self.ask_ok(q):
505 log.error('Canceled by user')
505 log.error('Canceled by user')
506 sys.exit(-1)
506 sys.exit(-1)
507
507
508 if retries == 0:
508 if retries == 0:
509 sys.exit('max retries reached')
509 sys.exit('max retries reached')
510 if not path_ok:
510 if not path_ok:
511 retries -= 1
511 retries -= 1
512 return self.config_prompt(test_repo_path, retries)
512 return self.config_prompt(test_repo_path, retries)
513
513
514 real_path = os.path.normpath(os.path.realpath(path))
514 real_path = os.path.normpath(os.path.realpath(path))
515
515
516 if real_path != os.path.normpath(path):
516 if real_path != os.path.normpath(path):
517 q = ('Path looks like a symlink, RhodeCode Enterprise will store '
517 q = ('Path looks like a symlink, RhodeCode Enterprise will store '
518 'given path as %s ? [y/n]') % (real_path,)
518 'given path as %s ? [y/n]') % (real_path,)
519 if not self.ask_ok(q):
519 if not self.ask_ok(q):
520 log.error('Canceled by user')
520 log.error('Canceled by user')
521 sys.exit(-1)
521 sys.exit(-1)
522
522
523 return real_path
523 return real_path
524
524
525 def create_settings(self, path):
525 def create_settings(self, path):
526
526
527 self.create_ui_settings(path)
527 self.create_ui_settings(path)
528
528
529 ui_config = [
529 ui_config = [
530 ('web', 'push_ssl', 'False'),
530 ('web', 'push_ssl', 'False'),
531 ('web', 'allow_archive', 'gz zip bz2'),
531 ('web', 'allow_archive', 'gz zip bz2'),
532 ('web', 'allow_push', '*'),
532 ('web', 'allow_push', '*'),
533 ('web', 'baseurl', '/'),
533 ('web', 'baseurl', '/'),
534 ('paths', '/', path),
534 ('paths', '/', path),
535 ('phases', 'publish', 'True')
535 ('phases', 'publish', 'True')
536 ]
536 ]
537 for section, key, value in ui_config:
537 for section, key, value in ui_config:
538 ui_conf = RhodeCodeUi()
538 ui_conf = RhodeCodeUi()
539 setattr(ui_conf, 'ui_section', section)
539 setattr(ui_conf, 'ui_section', section)
540 setattr(ui_conf, 'ui_key', key)
540 setattr(ui_conf, 'ui_key', key)
541 setattr(ui_conf, 'ui_value', value)
541 setattr(ui_conf, 'ui_value', value)
542 self.sa.add(ui_conf)
542 self.sa.add(ui_conf)
543
543
544 # rhodecode app settings
544 # rhodecode app settings
545 settings = [
545 settings = [
546 ('realm', 'RhodeCode', 'unicode'),
546 ('realm', 'RhodeCode', 'unicode'),
547 ('title', '', 'unicode'),
547 ('title', '', 'unicode'),
548 ('pre_code', '', 'unicode'),
548 ('pre_code', '', 'unicode'),
549 ('post_code', '', 'unicode'),
549 ('post_code', '', 'unicode'),
550 ('show_public_icon', True, 'bool'),
550 ('show_public_icon', True, 'bool'),
551 ('show_private_icon', True, 'bool'),
551 ('show_private_icon', True, 'bool'),
552 ('stylify_metatags', False, 'bool'),
552 ('stylify_metatags', False, 'bool'),
553 ('dashboard_items', 100, 'int'),
553 ('dashboard_items', 100, 'int'),
554 ('admin_grid_items', 25, 'int'),
554 ('admin_grid_items', 25, 'int'),
555 ('show_version', True, 'bool'),
555 ('show_version', True, 'bool'),
556 ('use_gravatar', False, 'bool'),
556 ('use_gravatar', False, 'bool'),
557 ('gravatar_url', User.DEFAULT_GRAVATAR_URL, 'unicode'),
557 ('gravatar_url', User.DEFAULT_GRAVATAR_URL, 'unicode'),
558 ('clone_uri_tmpl', Repository.DEFAULT_CLONE_URI, 'unicode'),
558 ('clone_uri_tmpl', Repository.DEFAULT_CLONE_URI, 'unicode'),
559 ('support_url', '', 'unicode'),
559 ('support_url', '', 'unicode'),
560 ('update_url', RhodeCodeSetting.DEFAULT_UPDATE_URL, 'unicode'),
560 ('update_url', RhodeCodeSetting.DEFAULT_UPDATE_URL, 'unicode'),
561 ('show_revision_number', True, 'bool'),
561 ('show_revision_number', True, 'bool'),
562 ('show_sha_length', 12, 'int'),
562 ('show_sha_length', 12, 'int'),
563 ]
563 ]
564
564
565 for key, val, type_ in settings:
565 for key, val, type_ in settings:
566 sett = RhodeCodeSetting(key, val, type_)
566 sett = RhodeCodeSetting(key, val, type_)
567 self.sa.add(sett)
567 self.sa.add(sett)
568
568
569 self.create_auth_plugin_options()
569 self.create_auth_plugin_options()
570 self.create_default_options()
570 self.create_default_options()
571
571
572 log.info('created ui config')
572 log.info('created ui config')
573
573
574 def create_user(self, username, password, email='', admin=False,
574 def create_user(self, username, password, email='', admin=False,
575 strict_creation_check=True, api_key=None):
575 strict_creation_check=True, api_key=None):
576 log.info('creating user %s' % username)
576 log.info('creating user `%s`' % username)
577 user = UserModel().create_or_update(
577 user = UserModel().create_or_update(
578 username, password, email, firstname=u'RhodeCode', lastname=u'Admin',
578 username, password, email, firstname=u'RhodeCode', lastname=u'Admin',
579 active=True, admin=admin, extern_type="rhodecode",
579 active=True, admin=admin, extern_type="rhodecode",
580 strict_creation_check=strict_creation_check)
580 strict_creation_check=strict_creation_check)
581
581
582 if api_key:
582 if api_key:
583 log.info('setting a provided api key for the user %s', username)
583 log.info('setting a new default auth token for user `%s`', username)
584 from rhodecode.model.auth_token import AuthTokenModel
584 UserModel().add_auth_token(
585 AuthTokenModel().create(
585 user=user, lifetime_minutes=-1,
586 user=user, description=u'BUILTIN TOKEN')
586 role=UserModel.auth_token_role.ROLE_ALL,
587 description=u'BUILTIN TOKEN')
587
588
588 def create_default_user(self):
589 def create_default_user(self):
589 log.info('creating default user')
590 log.info('creating default user')
590 # create default user for handling default permissions.
591 # create default user for handling default permissions.
591 user = UserModel().create_or_update(username=User.DEFAULT_USER,
592 user = UserModel().create_or_update(username=User.DEFAULT_USER,
592 password=str(uuid.uuid1())[:20],
593 password=str(uuid.uuid1())[:20],
593 email=User.DEFAULT_USER_EMAIL,
594 email=User.DEFAULT_USER_EMAIL,
594 firstname=u'Anonymous',
595 firstname=u'Anonymous',
595 lastname=u'User',
596 lastname=u'User',
596 strict_creation_check=False)
597 strict_creation_check=False)
597 # based on configuration options activate/deactive this user which
598 # based on configuration options activate/de-activate this user which
598 # controlls anonymous access
599 # controlls anonymous access
599 if self.cli_args.get('public_access') is False:
600 if self.cli_args.get('public_access') is False:
600 log.info('Public access disabled')
601 log.info('Public access disabled')
601 user.active = False
602 user.active = False
602 Session().add(user)
603 Session().add(user)
603 Session().commit()
604 Session().commit()
604
605
605 def create_permissions(self):
606 def create_permissions(self):
606 """
607 """
607 Creates all permissions defined in the system
608 Creates all permissions defined in the system
608 """
609 """
609 # module.(access|create|change|delete)_[name]
610 # module.(access|create|change|delete)_[name]
610 # module.(none|read|write|admin)
611 # module.(none|read|write|admin)
611 log.info('creating permissions')
612 log.info('creating permissions')
612 PermissionModel(self.sa).create_permissions()
613 PermissionModel(self.sa).create_permissions()
613
614
614 def populate_default_permissions(self):
615 def populate_default_permissions(self):
615 """
616 """
616 Populate default permissions. It will create only the default
617 Populate default permissions. It will create only the default
617 permissions that are missing, and not alter already defined ones
618 permissions that are missing, and not alter already defined ones
618 """
619 """
619 log.info('creating default user permissions')
620 log.info('creating default user permissions')
620 PermissionModel(self.sa).create_default_user_permissions(user=User.DEFAULT_USER)
621 PermissionModel(self.sa).create_default_user_permissions(user=User.DEFAULT_USER)
@@ -1,918 +1,940 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2018 RhodeCode GmbH
3 # Copyright (C) 2010-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 """
21 """
22 users model for RhodeCode
22 users model for RhodeCode
23 """
23 """
24
24
25 import logging
25 import logging
26 import traceback
26 import traceback
27 import datetime
27 import datetime
28 import ipaddress
28 import ipaddress
29
29
30 from pyramid.threadlocal import get_current_request
30 from pyramid.threadlocal import get_current_request
31 from sqlalchemy.exc import DatabaseError
31 from sqlalchemy.exc import DatabaseError
32
32
33 from rhodecode import events
33 from rhodecode import events
34 from rhodecode.lib.user_log_filter import user_log_filter
34 from rhodecode.lib.user_log_filter import user_log_filter
35 from rhodecode.lib.utils2 import (
35 from rhodecode.lib.utils2 import (
36 safe_unicode, get_current_rhodecode_user, action_logger_generic,
36 safe_unicode, get_current_rhodecode_user, action_logger_generic,
37 AttributeDict, str2bool)
37 AttributeDict, str2bool)
38 from rhodecode.lib.exceptions import (
38 from rhodecode.lib.exceptions import (
39 DefaultUserException, UserOwnsReposException, UserOwnsRepoGroupsException,
39 DefaultUserException, UserOwnsReposException, UserOwnsRepoGroupsException,
40 UserOwnsUserGroupsException, NotAllowedToCreateUserError)
40 UserOwnsUserGroupsException, NotAllowedToCreateUserError)
41 from rhodecode.lib.caching_query import FromCache
41 from rhodecode.lib.caching_query import FromCache
42 from rhodecode.model import BaseModel
42 from rhodecode.model import BaseModel
43 from rhodecode.model.auth_token import AuthTokenModel
43 from rhodecode.model.auth_token import AuthTokenModel
44 from rhodecode.model.db import (
44 from rhodecode.model.db import (
45 _hash_key, true, false, or_, joinedload, User, UserToPerm,
45 _hash_key, true, false, or_, joinedload, User, UserToPerm,
46 UserEmailMap, UserIpMap, UserLog)
46 UserEmailMap, UserIpMap, UserLog)
47 from rhodecode.model.meta import Session
47 from rhodecode.model.meta import Session
48 from rhodecode.model.repo_group import RepoGroupModel
48 from rhodecode.model.repo_group import RepoGroupModel
49
49
50
50
51 log = logging.getLogger(__name__)
51 log = logging.getLogger(__name__)
52
52
53
53
54 class UserModel(BaseModel):
54 class UserModel(BaseModel):
55 cls = User
55 cls = User
56
56
57 def get(self, user_id, cache=False):
57 def get(self, user_id, cache=False):
58 user = self.sa.query(User)
58 user = self.sa.query(User)
59 if cache:
59 if cache:
60 user = user.options(
60 user = user.options(
61 FromCache("sql_cache_short", "get_user_%s" % user_id))
61 FromCache("sql_cache_short", "get_user_%s" % user_id))
62 return user.get(user_id)
62 return user.get(user_id)
63
63
64 def get_user(self, user):
64 def get_user(self, user):
65 return self._get_user(user)
65 return self._get_user(user)
66
66
67 def _serialize_user(self, user):
67 def _serialize_user(self, user):
68 import rhodecode.lib.helpers as h
68 import rhodecode.lib.helpers as h
69
69
70 return {
70 return {
71 'id': user.user_id,
71 'id': user.user_id,
72 'first_name': user.first_name,
72 'first_name': user.first_name,
73 'last_name': user.last_name,
73 'last_name': user.last_name,
74 'username': user.username,
74 'username': user.username,
75 'email': user.email,
75 'email': user.email,
76 'icon_link': h.gravatar_url(user.email, 30),
76 'icon_link': h.gravatar_url(user.email, 30),
77 'profile_link': h.link_to_user(user),
77 'profile_link': h.link_to_user(user),
78 'value_display': h.escape(h.person(user)),
78 'value_display': h.escape(h.person(user)),
79 'value': user.username,
79 'value': user.username,
80 'value_type': 'user',
80 'value_type': 'user',
81 'active': user.active,
81 'active': user.active,
82 }
82 }
83
83
84 def get_users(self, name_contains=None, limit=20, only_active=True):
84 def get_users(self, name_contains=None, limit=20, only_active=True):
85
85
86 query = self.sa.query(User)
86 query = self.sa.query(User)
87 if only_active:
87 if only_active:
88 query = query.filter(User.active == true())
88 query = query.filter(User.active == true())
89
89
90 if name_contains:
90 if name_contains:
91 ilike_expression = u'%{}%'.format(safe_unicode(name_contains))
91 ilike_expression = u'%{}%'.format(safe_unicode(name_contains))
92 query = query.filter(
92 query = query.filter(
93 or_(
93 or_(
94 User.name.ilike(ilike_expression),
94 User.name.ilike(ilike_expression),
95 User.lastname.ilike(ilike_expression),
95 User.lastname.ilike(ilike_expression),
96 User.username.ilike(ilike_expression)
96 User.username.ilike(ilike_expression)
97 )
97 )
98 )
98 )
99 query = query.limit(limit)
99 query = query.limit(limit)
100 users = query.all()
100 users = query.all()
101
101
102 _users = [
102 _users = [
103 self._serialize_user(user) for user in users
103 self._serialize_user(user) for user in users
104 ]
104 ]
105 return _users
105 return _users
106
106
107 def get_by_username(self, username, cache=False, case_insensitive=False):
107 def get_by_username(self, username, cache=False, case_insensitive=False):
108
108
109 if case_insensitive:
109 if case_insensitive:
110 user = self.sa.query(User).filter(User.username.ilike(username))
110 user = self.sa.query(User).filter(User.username.ilike(username))
111 else:
111 else:
112 user = self.sa.query(User)\
112 user = self.sa.query(User)\
113 .filter(User.username == username)
113 .filter(User.username == username)
114 if cache:
114 if cache:
115 name_key = _hash_key(username)
115 name_key = _hash_key(username)
116 user = user.options(
116 user = user.options(
117 FromCache("sql_cache_short", "get_user_%s" % name_key))
117 FromCache("sql_cache_short", "get_user_%s" % name_key))
118 return user.scalar()
118 return user.scalar()
119
119
120 def get_by_email(self, email, cache=False, case_insensitive=False):
120 def get_by_email(self, email, cache=False, case_insensitive=False):
121 return User.get_by_email(email, case_insensitive, cache)
121 return User.get_by_email(email, case_insensitive, cache)
122
122
123 def get_by_auth_token(self, auth_token, cache=False):
123 def get_by_auth_token(self, auth_token, cache=False):
124 return User.get_by_auth_token(auth_token, cache)
124 return User.get_by_auth_token(auth_token, cache)
125
125
126 def get_active_user_count(self, cache=False):
126 def get_active_user_count(self, cache=False):
127 qry = User.query().filter(
127 qry = User.query().filter(
128 User.active == true()).filter(
128 User.active == true()).filter(
129 User.username != User.DEFAULT_USER)
129 User.username != User.DEFAULT_USER)
130 if cache:
130 if cache:
131 qry = qry.options(
131 qry = qry.options(
132 FromCache("sql_cache_short", "get_active_users"))
132 FromCache("sql_cache_short", "get_active_users"))
133 return qry.count()
133 return qry.count()
134
134
135 def create(self, form_data, cur_user=None):
135 def create(self, form_data, cur_user=None):
136 if not cur_user:
136 if not cur_user:
137 cur_user = getattr(get_current_rhodecode_user(), 'username', None)
137 cur_user = getattr(get_current_rhodecode_user(), 'username', None)
138
138
139 user_data = {
139 user_data = {
140 'username': form_data['username'],
140 'username': form_data['username'],
141 'password': form_data['password'],
141 'password': form_data['password'],
142 'email': form_data['email'],
142 'email': form_data['email'],
143 'firstname': form_data['firstname'],
143 'firstname': form_data['firstname'],
144 'lastname': form_data['lastname'],
144 'lastname': form_data['lastname'],
145 'active': form_data['active'],
145 'active': form_data['active'],
146 'extern_type': form_data['extern_type'],
146 'extern_type': form_data['extern_type'],
147 'extern_name': form_data['extern_name'],
147 'extern_name': form_data['extern_name'],
148 'admin': False,
148 'admin': False,
149 'cur_user': cur_user
149 'cur_user': cur_user
150 }
150 }
151
151
152 if 'create_repo_group' in form_data:
152 if 'create_repo_group' in form_data:
153 user_data['create_repo_group'] = str2bool(
153 user_data['create_repo_group'] = str2bool(
154 form_data.get('create_repo_group'))
154 form_data.get('create_repo_group'))
155
155
156 try:
156 try:
157 if form_data.get('password_change'):
157 if form_data.get('password_change'):
158 user_data['force_password_change'] = True
158 user_data['force_password_change'] = True
159 return UserModel().create_or_update(**user_data)
159 return UserModel().create_or_update(**user_data)
160 except Exception:
160 except Exception:
161 log.error(traceback.format_exc())
161 log.error(traceback.format_exc())
162 raise
162 raise
163
163
164 def update_user(self, user, skip_attrs=None, **kwargs):
164 def update_user(self, user, skip_attrs=None, **kwargs):
165 from rhodecode.lib.auth import get_crypt_password
165 from rhodecode.lib.auth import get_crypt_password
166
166
167 user = self._get_user(user)
167 user = self._get_user(user)
168 if user.username == User.DEFAULT_USER:
168 if user.username == User.DEFAULT_USER:
169 raise DefaultUserException(
169 raise DefaultUserException(
170 "You can't edit this user (`%(username)s`) since it's "
170 "You can't edit this user (`%(username)s`) since it's "
171 "crucial for entire application" % {
171 "crucial for entire application" % {
172 'username': user.username})
172 'username': user.username})
173
173
174 # first store only defaults
174 # first store only defaults
175 user_attrs = {
175 user_attrs = {
176 'updating_user_id': user.user_id,
176 'updating_user_id': user.user_id,
177 'username': user.username,
177 'username': user.username,
178 'password': user.password,
178 'password': user.password,
179 'email': user.email,
179 'email': user.email,
180 'firstname': user.name,
180 'firstname': user.name,
181 'lastname': user.lastname,
181 'lastname': user.lastname,
182 'active': user.active,
182 'active': user.active,
183 'admin': user.admin,
183 'admin': user.admin,
184 'extern_name': user.extern_name,
184 'extern_name': user.extern_name,
185 'extern_type': user.extern_type,
185 'extern_type': user.extern_type,
186 'language': user.user_data.get('language')
186 'language': user.user_data.get('language')
187 }
187 }
188
188
189 # in case there's new_password, that comes from form, use it to
189 # in case there's new_password, that comes from form, use it to
190 # store password
190 # store password
191 if kwargs.get('new_password'):
191 if kwargs.get('new_password'):
192 kwargs['password'] = kwargs['new_password']
192 kwargs['password'] = kwargs['new_password']
193
193
194 # cleanups, my_account password change form
194 # cleanups, my_account password change form
195 kwargs.pop('current_password', None)
195 kwargs.pop('current_password', None)
196 kwargs.pop('new_password', None)
196 kwargs.pop('new_password', None)
197
197
198 # cleanups, user edit password change form
198 # cleanups, user edit password change form
199 kwargs.pop('password_confirmation', None)
199 kwargs.pop('password_confirmation', None)
200 kwargs.pop('password_change', None)
200 kwargs.pop('password_change', None)
201
201
202 # create repo group on user creation
202 # create repo group on user creation
203 kwargs.pop('create_repo_group', None)
203 kwargs.pop('create_repo_group', None)
204
204
205 # legacy forms send name, which is the firstname
205 # legacy forms send name, which is the firstname
206 firstname = kwargs.pop('name', None)
206 firstname = kwargs.pop('name', None)
207 if firstname:
207 if firstname:
208 kwargs['firstname'] = firstname
208 kwargs['firstname'] = firstname
209
209
210 for k, v in kwargs.items():
210 for k, v in kwargs.items():
211 # skip if we don't want to update this
211 # skip if we don't want to update this
212 if skip_attrs and k in skip_attrs:
212 if skip_attrs and k in skip_attrs:
213 continue
213 continue
214
214
215 user_attrs[k] = v
215 user_attrs[k] = v
216
216
217 try:
217 try:
218 return self.create_or_update(**user_attrs)
218 return self.create_or_update(**user_attrs)
219 except Exception:
219 except Exception:
220 log.error(traceback.format_exc())
220 log.error(traceback.format_exc())
221 raise
221 raise
222
222
223 def create_or_update(
223 def create_or_update(
224 self, username, password, email, firstname='', lastname='',
224 self, username, password, email, firstname='', lastname='',
225 active=True, admin=False, extern_type=None, extern_name=None,
225 active=True, admin=False, extern_type=None, extern_name=None,
226 cur_user=None, plugin=None, force_password_change=False,
226 cur_user=None, plugin=None, force_password_change=False,
227 allow_to_create_user=True, create_repo_group=None,
227 allow_to_create_user=True, create_repo_group=None,
228 updating_user_id=None, language=None, strict_creation_check=True):
228 updating_user_id=None, language=None, strict_creation_check=True):
229 """
229 """
230 Creates a new instance if not found, or updates current one
230 Creates a new instance if not found, or updates current one
231
231
232 :param username:
232 :param username:
233 :param password:
233 :param password:
234 :param email:
234 :param email:
235 :param firstname:
235 :param firstname:
236 :param lastname:
236 :param lastname:
237 :param active:
237 :param active:
238 :param admin:
238 :param admin:
239 :param extern_type:
239 :param extern_type:
240 :param extern_name:
240 :param extern_name:
241 :param cur_user:
241 :param cur_user:
242 :param plugin: optional plugin this method was called from
242 :param plugin: optional plugin this method was called from
243 :param force_password_change: toggles new or existing user flag
243 :param force_password_change: toggles new or existing user flag
244 for password change
244 for password change
245 :param allow_to_create_user: Defines if the method can actually create
245 :param allow_to_create_user: Defines if the method can actually create
246 new users
246 new users
247 :param create_repo_group: Defines if the method should also
247 :param create_repo_group: Defines if the method should also
248 create an repo group with user name, and owner
248 create an repo group with user name, and owner
249 :param updating_user_id: if we set it up this is the user we want to
249 :param updating_user_id: if we set it up this is the user we want to
250 update this allows to editing username.
250 update this allows to editing username.
251 :param language: language of user from interface.
251 :param language: language of user from interface.
252
252
253 :returns: new User object with injected `is_new_user` attribute.
253 :returns: new User object with injected `is_new_user` attribute.
254 """
254 """
255
255
256 if not cur_user:
256 if not cur_user:
257 cur_user = getattr(get_current_rhodecode_user(), 'username', None)
257 cur_user = getattr(get_current_rhodecode_user(), 'username', None)
258
258
259 from rhodecode.lib.auth import (
259 from rhodecode.lib.auth import (
260 get_crypt_password, check_password, generate_auth_token)
260 get_crypt_password, check_password, generate_auth_token)
261 from rhodecode.lib.hooks_base import (
261 from rhodecode.lib.hooks_base import (
262 log_create_user, check_allowed_create_user)
262 log_create_user, check_allowed_create_user)
263
263
264 def _password_change(new_user, password):
264 def _password_change(new_user, password):
265 old_password = new_user.password or ''
265 old_password = new_user.password or ''
266 # empty password
266 # empty password
267 if not old_password:
267 if not old_password:
268 return False
268 return False
269
269
270 # password check is only needed for RhodeCode internal auth calls
270 # password check is only needed for RhodeCode internal auth calls
271 # in case it's a plugin we don't care
271 # in case it's a plugin we don't care
272 if not plugin:
272 if not plugin:
273
273
274 # first check if we gave crypted password back, and if it
274 # first check if we gave crypted password back, and if it
275 # matches it's not password change
275 # matches it's not password change
276 if new_user.password == password:
276 if new_user.password == password:
277 return False
277 return False
278
278
279 password_match = check_password(password, old_password)
279 password_match = check_password(password, old_password)
280 if not password_match:
280 if not password_match:
281 return True
281 return True
282
282
283 return False
283 return False
284
284
285 # read settings on default personal repo group creation
285 # read settings on default personal repo group creation
286 if create_repo_group is None:
286 if create_repo_group is None:
287 default_create_repo_group = RepoGroupModel()\
287 default_create_repo_group = RepoGroupModel()\
288 .get_default_create_personal_repo_group()
288 .get_default_create_personal_repo_group()
289 create_repo_group = default_create_repo_group
289 create_repo_group = default_create_repo_group
290
290
291 user_data = {
291 user_data = {
292 'username': username,
292 'username': username,
293 'password': password,
293 'password': password,
294 'email': email,
294 'email': email,
295 'firstname': firstname,
295 'firstname': firstname,
296 'lastname': lastname,
296 'lastname': lastname,
297 'active': active,
297 'active': active,
298 'admin': admin
298 'admin': admin
299 }
299 }
300
300
301 if updating_user_id:
301 if updating_user_id:
302 log.debug('Checking for existing account in RhodeCode '
302 log.debug('Checking for existing account in RhodeCode '
303 'database with user_id `%s` ' % (updating_user_id,))
303 'database with user_id `%s` ' % (updating_user_id,))
304 user = User.get(updating_user_id)
304 user = User.get(updating_user_id)
305 else:
305 else:
306 log.debug('Checking for existing account in RhodeCode '
306 log.debug('Checking for existing account in RhodeCode '
307 'database with username `%s` ' % (username,))
307 'database with username `%s` ' % (username,))
308 user = User.get_by_username(username, case_insensitive=True)
308 user = User.get_by_username(username, case_insensitive=True)
309
309
310 if user is None:
310 if user is None:
311 # we check internal flag if this method is actually allowed to
311 # we check internal flag if this method is actually allowed to
312 # create new user
312 # create new user
313 if not allow_to_create_user:
313 if not allow_to_create_user:
314 msg = ('Method wants to create new user, but it is not '
314 msg = ('Method wants to create new user, but it is not '
315 'allowed to do so')
315 'allowed to do so')
316 log.warning(msg)
316 log.warning(msg)
317 raise NotAllowedToCreateUserError(msg)
317 raise NotAllowedToCreateUserError(msg)
318
318
319 log.debug('Creating new user %s', username)
319 log.debug('Creating new user %s', username)
320
320
321 # only if we create user that is active
321 # only if we create user that is active
322 new_active_user = active
322 new_active_user = active
323 if new_active_user and strict_creation_check:
323 if new_active_user and strict_creation_check:
324 # raises UserCreationError if it's not allowed for any reason to
324 # raises UserCreationError if it's not allowed for any reason to
325 # create new active user, this also executes pre-create hooks
325 # create new active user, this also executes pre-create hooks
326 check_allowed_create_user(user_data, cur_user, strict_check=True)
326 check_allowed_create_user(user_data, cur_user, strict_check=True)
327 events.trigger(events.UserPreCreate(user_data))
327 events.trigger(events.UserPreCreate(user_data))
328 new_user = User()
328 new_user = User()
329 edit = False
329 edit = False
330 else:
330 else:
331 log.debug('updating user %s', username)
331 log.debug('updating user %s', username)
332 events.trigger(events.UserPreUpdate(user, user_data))
332 events.trigger(events.UserPreUpdate(user, user_data))
333 new_user = user
333 new_user = user
334 edit = True
334 edit = True
335
335
336 # we're not allowed to edit default user
336 # we're not allowed to edit default user
337 if user.username == User.DEFAULT_USER:
337 if user.username == User.DEFAULT_USER:
338 raise DefaultUserException(
338 raise DefaultUserException(
339 "You can't edit this user (`%(username)s`) since it's "
339 "You can't edit this user (`%(username)s`) since it's "
340 "crucial for entire application"
340 "crucial for entire application"
341 % {'username': user.username})
341 % {'username': user.username})
342
342
343 # inject special attribute that will tell us if User is new or old
343 # inject special attribute that will tell us if User is new or old
344 new_user.is_new_user = not edit
344 new_user.is_new_user = not edit
345 # for users that didn's specify auth type, we use RhodeCode built in
345 # for users that didn's specify auth type, we use RhodeCode built in
346 from rhodecode.authentication.plugins import auth_rhodecode
346 from rhodecode.authentication.plugins import auth_rhodecode
347 extern_name = extern_name or auth_rhodecode.RhodeCodeAuthPlugin.name
347 extern_name = extern_name or auth_rhodecode.RhodeCodeAuthPlugin.name
348 extern_type = extern_type or auth_rhodecode.RhodeCodeAuthPlugin.name
348 extern_type = extern_type or auth_rhodecode.RhodeCodeAuthPlugin.name
349
349
350 try:
350 try:
351 new_user.username = username
351 new_user.username = username
352 new_user.admin = admin
352 new_user.admin = admin
353 new_user.email = email
353 new_user.email = email
354 new_user.active = active
354 new_user.active = active
355 new_user.extern_name = safe_unicode(extern_name)
355 new_user.extern_name = safe_unicode(extern_name)
356 new_user.extern_type = safe_unicode(extern_type)
356 new_user.extern_type = safe_unicode(extern_type)
357 new_user.name = firstname
357 new_user.name = firstname
358 new_user.lastname = lastname
358 new_user.lastname = lastname
359
359
360 # set password only if creating an user or password is changed
360 # set password only if creating an user or password is changed
361 if not edit or _password_change(new_user, password):
361 if not edit or _password_change(new_user, password):
362 reason = 'new password' if edit else 'new user'
362 reason = 'new password' if edit else 'new user'
363 log.debug('Updating password reason=>%s', reason)
363 log.debug('Updating password reason=>%s', reason)
364 new_user.password = get_crypt_password(password) if password else None
364 new_user.password = get_crypt_password(password) if password else None
365
365
366 if force_password_change:
366 if force_password_change:
367 new_user.update_userdata(force_password_change=True)
367 new_user.update_userdata(force_password_change=True)
368 if language:
368 if language:
369 new_user.update_userdata(language=language)
369 new_user.update_userdata(language=language)
370 new_user.update_userdata(notification_status=True)
370 new_user.update_userdata(notification_status=True)
371
371
372 self.sa.add(new_user)
372 self.sa.add(new_user)
373
373
374 if not edit and create_repo_group:
374 if not edit and create_repo_group:
375 RepoGroupModel().create_personal_repo_group(
375 RepoGroupModel().create_personal_repo_group(
376 new_user, commit_early=False)
376 new_user, commit_early=False)
377
377
378 if not edit:
378 if not edit:
379 # add the RSS token
379 # add the RSS token
380 AuthTokenModel().create(username,
380 self.add_auth_token(
381 description=u'Generated feed token',
381 user=username, lifetime_minutes=-1,
382 role=AuthTokenModel.cls.ROLE_FEED)
382 role=self.auth_token_role.ROLE_FEED,
383 description=u'Generated feed token')
384
383 kwargs = new_user.get_dict()
385 kwargs = new_user.get_dict()
384 # backward compat, require api_keys present
386 # backward compat, require api_keys present
385 kwargs['api_keys'] = kwargs['auth_tokens']
387 kwargs['api_keys'] = kwargs['auth_tokens']
386 log_create_user(created_by=cur_user, **kwargs)
388 log_create_user(created_by=cur_user, **kwargs)
387 events.trigger(events.UserPostCreate(user_data))
389 events.trigger(events.UserPostCreate(user_data))
388 return new_user
390 return new_user
389 except (DatabaseError,):
391 except (DatabaseError,):
390 log.error(traceback.format_exc())
392 log.error(traceback.format_exc())
391 raise
393 raise
392
394
393 def create_registration(self, form_data):
395 def create_registration(self, form_data):
394 from rhodecode.model.notification import NotificationModel
396 from rhodecode.model.notification import NotificationModel
395 from rhodecode.model.notification import EmailNotificationModel
397 from rhodecode.model.notification import EmailNotificationModel
396
398
397 try:
399 try:
398 form_data['admin'] = False
400 form_data['admin'] = False
399 form_data['extern_name'] = 'rhodecode'
401 form_data['extern_name'] = 'rhodecode'
400 form_data['extern_type'] = 'rhodecode'
402 form_data['extern_type'] = 'rhodecode'
401 new_user = self.create(form_data)
403 new_user = self.create(form_data)
402
404
403 self.sa.add(new_user)
405 self.sa.add(new_user)
404 self.sa.flush()
406 self.sa.flush()
405
407
406 user_data = new_user.get_dict()
408 user_data = new_user.get_dict()
407 kwargs = {
409 kwargs = {
408 # use SQLALCHEMY safe dump of user data
410 # use SQLALCHEMY safe dump of user data
409 'user': AttributeDict(user_data),
411 'user': AttributeDict(user_data),
410 'date': datetime.datetime.now()
412 'date': datetime.datetime.now()
411 }
413 }
412 notification_type = EmailNotificationModel.TYPE_REGISTRATION
414 notification_type = EmailNotificationModel.TYPE_REGISTRATION
413 # pre-generate the subject for notification itself
415 # pre-generate the subject for notification itself
414 (subject,
416 (subject,
415 _h, _e, # we don't care about those
417 _h, _e, # we don't care about those
416 body_plaintext) = EmailNotificationModel().render_email(
418 body_plaintext) = EmailNotificationModel().render_email(
417 notification_type, **kwargs)
419 notification_type, **kwargs)
418
420
419 # create notification objects, and emails
421 # create notification objects, and emails
420 NotificationModel().create(
422 NotificationModel().create(
421 created_by=new_user,
423 created_by=new_user,
422 notification_subject=subject,
424 notification_subject=subject,
423 notification_body=body_plaintext,
425 notification_body=body_plaintext,
424 notification_type=notification_type,
426 notification_type=notification_type,
425 recipients=None, # all admins
427 recipients=None, # all admins
426 email_kwargs=kwargs,
428 email_kwargs=kwargs,
427 )
429 )
428
430
429 return new_user
431 return new_user
430 except Exception:
432 except Exception:
431 log.error(traceback.format_exc())
433 log.error(traceback.format_exc())
432 raise
434 raise
433
435
434 def _handle_user_repos(self, username, repositories, handle_mode=None):
436 def _handle_user_repos(self, username, repositories, handle_mode=None):
435 _superadmin = self.cls.get_first_super_admin()
437 _superadmin = self.cls.get_first_super_admin()
436 left_overs = True
438 left_overs = True
437
439
438 from rhodecode.model.repo import RepoModel
440 from rhodecode.model.repo import RepoModel
439
441
440 if handle_mode == 'detach':
442 if handle_mode == 'detach':
441 for obj in repositories:
443 for obj in repositories:
442 obj.user = _superadmin
444 obj.user = _superadmin
443 # set description we know why we super admin now owns
445 # set description we know why we super admin now owns
444 # additional repositories that were orphaned !
446 # additional repositories that were orphaned !
445 obj.description += ' \n::detached repository from deleted user: %s' % (username,)
447 obj.description += ' \n::detached repository from deleted user: %s' % (username,)
446 self.sa.add(obj)
448 self.sa.add(obj)
447 left_overs = False
449 left_overs = False
448 elif handle_mode == 'delete':
450 elif handle_mode == 'delete':
449 for obj in repositories:
451 for obj in repositories:
450 RepoModel().delete(obj, forks='detach')
452 RepoModel().delete(obj, forks='detach')
451 left_overs = False
453 left_overs = False
452
454
453 # if nothing is done we have left overs left
455 # if nothing is done we have left overs left
454 return left_overs
456 return left_overs
455
457
456 def _handle_user_repo_groups(self, username, repository_groups,
458 def _handle_user_repo_groups(self, username, repository_groups,
457 handle_mode=None):
459 handle_mode=None):
458 _superadmin = self.cls.get_first_super_admin()
460 _superadmin = self.cls.get_first_super_admin()
459 left_overs = True
461 left_overs = True
460
462
461 from rhodecode.model.repo_group import RepoGroupModel
463 from rhodecode.model.repo_group import RepoGroupModel
462
464
463 if handle_mode == 'detach':
465 if handle_mode == 'detach':
464 for r in repository_groups:
466 for r in repository_groups:
465 r.user = _superadmin
467 r.user = _superadmin
466 # set description we know why we super admin now owns
468 # set description we know why we super admin now owns
467 # additional repositories that were orphaned !
469 # additional repositories that were orphaned !
468 r.group_description += ' \n::detached repository group from deleted user: %s' % (username,)
470 r.group_description += ' \n::detached repository group from deleted user: %s' % (username,)
469 self.sa.add(r)
471 self.sa.add(r)
470 left_overs = False
472 left_overs = False
471 elif handle_mode == 'delete':
473 elif handle_mode == 'delete':
472 for r in repository_groups:
474 for r in repository_groups:
473 RepoGroupModel().delete(r)
475 RepoGroupModel().delete(r)
474 left_overs = False
476 left_overs = False
475
477
476 # if nothing is done we have left overs left
478 # if nothing is done we have left overs left
477 return left_overs
479 return left_overs
478
480
479 def _handle_user_user_groups(self, username, user_groups, handle_mode=None):
481 def _handle_user_user_groups(self, username, user_groups, handle_mode=None):
480 _superadmin = self.cls.get_first_super_admin()
482 _superadmin = self.cls.get_first_super_admin()
481 left_overs = True
483 left_overs = True
482
484
483 from rhodecode.model.user_group import UserGroupModel
485 from rhodecode.model.user_group import UserGroupModel
484
486
485 if handle_mode == 'detach':
487 if handle_mode == 'detach':
486 for r in user_groups:
488 for r in user_groups:
487 for user_user_group_to_perm in r.user_user_group_to_perm:
489 for user_user_group_to_perm in r.user_user_group_to_perm:
488 if user_user_group_to_perm.user.username == username:
490 if user_user_group_to_perm.user.username == username:
489 user_user_group_to_perm.user = _superadmin
491 user_user_group_to_perm.user = _superadmin
490 r.user = _superadmin
492 r.user = _superadmin
491 # set description we know why we super admin now owns
493 # set description we know why we super admin now owns
492 # additional repositories that were orphaned !
494 # additional repositories that were orphaned !
493 r.user_group_description += ' \n::detached user group from deleted user: %s' % (username,)
495 r.user_group_description += ' \n::detached user group from deleted user: %s' % (username,)
494 self.sa.add(r)
496 self.sa.add(r)
495 left_overs = False
497 left_overs = False
496 elif handle_mode == 'delete':
498 elif handle_mode == 'delete':
497 for r in user_groups:
499 for r in user_groups:
498 UserGroupModel().delete(r)
500 UserGroupModel().delete(r)
499 left_overs = False
501 left_overs = False
500
502
501 # if nothing is done we have left overs left
503 # if nothing is done we have left overs left
502 return left_overs
504 return left_overs
503
505
504 def delete(self, user, cur_user=None, handle_repos=None,
506 def delete(self, user, cur_user=None, handle_repos=None,
505 handle_repo_groups=None, handle_user_groups=None):
507 handle_repo_groups=None, handle_user_groups=None):
506 if not cur_user:
508 if not cur_user:
507 cur_user = getattr(
509 cur_user = getattr(
508 get_current_rhodecode_user(), 'username', None)
510 get_current_rhodecode_user(), 'username', None)
509 user = self._get_user(user)
511 user = self._get_user(user)
510
512
511 try:
513 try:
512 if user.username == User.DEFAULT_USER:
514 if user.username == User.DEFAULT_USER:
513 raise DefaultUserException(
515 raise DefaultUserException(
514 u"You can't remove this user since it's"
516 u"You can't remove this user since it's"
515 u" crucial for entire application")
517 u" crucial for entire application")
516
518
517 left_overs = self._handle_user_repos(
519 left_overs = self._handle_user_repos(
518 user.username, user.repositories, handle_repos)
520 user.username, user.repositories, handle_repos)
519 if left_overs and user.repositories:
521 if left_overs and user.repositories:
520 repos = [x.repo_name for x in user.repositories]
522 repos = [x.repo_name for x in user.repositories]
521 raise UserOwnsReposException(
523 raise UserOwnsReposException(
522 u'user "%(username)s" still owns %(len_repos)s repositories and cannot be '
524 u'user "%(username)s" still owns %(len_repos)s repositories and cannot be '
523 u'removed. Switch owners or remove those repositories:%(list_repos)s'
525 u'removed. Switch owners or remove those repositories:%(list_repos)s'
524 % {'username': user.username, 'len_repos': len(repos),
526 % {'username': user.username, 'len_repos': len(repos),
525 'list_repos': ', '.join(repos)})
527 'list_repos': ', '.join(repos)})
526
528
527 left_overs = self._handle_user_repo_groups(
529 left_overs = self._handle_user_repo_groups(
528 user.username, user.repository_groups, handle_repo_groups)
530 user.username, user.repository_groups, handle_repo_groups)
529 if left_overs and user.repository_groups:
531 if left_overs and user.repository_groups:
530 repo_groups = [x.group_name for x in user.repository_groups]
532 repo_groups = [x.group_name for x in user.repository_groups]
531 raise UserOwnsRepoGroupsException(
533 raise UserOwnsRepoGroupsException(
532 u'user "%(username)s" still owns %(len_repo_groups)s repository groups and cannot be '
534 u'user "%(username)s" still owns %(len_repo_groups)s repository groups and cannot be '
533 u'removed. Switch owners or remove those repository groups:%(list_repo_groups)s'
535 u'removed. Switch owners or remove those repository groups:%(list_repo_groups)s'
534 % {'username': user.username, 'len_repo_groups': len(repo_groups),
536 % {'username': user.username, 'len_repo_groups': len(repo_groups),
535 'list_repo_groups': ', '.join(repo_groups)})
537 'list_repo_groups': ', '.join(repo_groups)})
536
538
537 left_overs = self._handle_user_user_groups(
539 left_overs = self._handle_user_user_groups(
538 user.username, user.user_groups, handle_user_groups)
540 user.username, user.user_groups, handle_user_groups)
539 if left_overs and user.user_groups:
541 if left_overs and user.user_groups:
540 user_groups = [x.users_group_name for x in user.user_groups]
542 user_groups = [x.users_group_name for x in user.user_groups]
541 raise UserOwnsUserGroupsException(
543 raise UserOwnsUserGroupsException(
542 u'user "%s" still owns %s user groups and cannot be '
544 u'user "%s" still owns %s user groups and cannot be '
543 u'removed. Switch owners or remove those user groups:%s'
545 u'removed. Switch owners or remove those user groups:%s'
544 % (user.username, len(user_groups), ', '.join(user_groups)))
546 % (user.username, len(user_groups), ', '.join(user_groups)))
545
547
546 # we might change the user data with detach/delete, make sure
548 # we might change the user data with detach/delete, make sure
547 # the object is marked as expired before actually deleting !
549 # the object is marked as expired before actually deleting !
548 self.sa.expire(user)
550 self.sa.expire(user)
549 self.sa.delete(user)
551 self.sa.delete(user)
550 from rhodecode.lib.hooks_base import log_delete_user
552 from rhodecode.lib.hooks_base import log_delete_user
551 log_delete_user(deleted_by=cur_user, **user.get_dict())
553 log_delete_user(deleted_by=cur_user, **user.get_dict())
552 except Exception:
554 except Exception:
553 log.error(traceback.format_exc())
555 log.error(traceback.format_exc())
554 raise
556 raise
555
557
556 def reset_password_link(self, data, pwd_reset_url):
558 def reset_password_link(self, data, pwd_reset_url):
557 from rhodecode.lib.celerylib import tasks, run_task
559 from rhodecode.lib.celerylib import tasks, run_task
558 from rhodecode.model.notification import EmailNotificationModel
560 from rhodecode.model.notification import EmailNotificationModel
559 user_email = data['email']
561 user_email = data['email']
560 try:
562 try:
561 user = User.get_by_email(user_email)
563 user = User.get_by_email(user_email)
562 if user:
564 if user:
563 log.debug('password reset user found %s', user)
565 log.debug('password reset user found %s', user)
564
566
565 email_kwargs = {
567 email_kwargs = {
566 'password_reset_url': pwd_reset_url,
568 'password_reset_url': pwd_reset_url,
567 'user': user,
569 'user': user,
568 'email': user_email,
570 'email': user_email,
569 'date': datetime.datetime.now()
571 'date': datetime.datetime.now()
570 }
572 }
571
573
572 (subject, headers, email_body,
574 (subject, headers, email_body,
573 email_body_plaintext) = EmailNotificationModel().render_email(
575 email_body_plaintext) = EmailNotificationModel().render_email(
574 EmailNotificationModel.TYPE_PASSWORD_RESET, **email_kwargs)
576 EmailNotificationModel.TYPE_PASSWORD_RESET, **email_kwargs)
575
577
576 recipients = [user_email]
578 recipients = [user_email]
577
579
578 action_logger_generic(
580 action_logger_generic(
579 'sending password reset email to user: {}'.format(
581 'sending password reset email to user: {}'.format(
580 user), namespace='security.password_reset')
582 user), namespace='security.password_reset')
581
583
582 run_task(tasks.send_email, recipients, subject,
584 run_task(tasks.send_email, recipients, subject,
583 email_body_plaintext, email_body)
585 email_body_plaintext, email_body)
584
586
585 else:
587 else:
586 log.debug("password reset email %s not found", user_email)
588 log.debug("password reset email %s not found", user_email)
587 except Exception:
589 except Exception:
588 log.error(traceback.format_exc())
590 log.error(traceback.format_exc())
589 return False
591 return False
590
592
591 return True
593 return True
592
594
593 def reset_password(self, data):
595 def reset_password(self, data):
594 from rhodecode.lib.celerylib import tasks, run_task
596 from rhodecode.lib.celerylib import tasks, run_task
595 from rhodecode.model.notification import EmailNotificationModel
597 from rhodecode.model.notification import EmailNotificationModel
596 from rhodecode.lib import auth
598 from rhodecode.lib import auth
597 user_email = data['email']
599 user_email = data['email']
598 pre_db = True
600 pre_db = True
599 try:
601 try:
600 user = User.get_by_email(user_email)
602 user = User.get_by_email(user_email)
601 new_passwd = auth.PasswordGenerator().gen_password(
603 new_passwd = auth.PasswordGenerator().gen_password(
602 12, auth.PasswordGenerator.ALPHABETS_BIG_SMALL)
604 12, auth.PasswordGenerator.ALPHABETS_BIG_SMALL)
603 if user:
605 if user:
604 user.password = auth.get_crypt_password(new_passwd)
606 user.password = auth.get_crypt_password(new_passwd)
605 # also force this user to reset his password !
607 # also force this user to reset his password !
606 user.update_userdata(force_password_change=True)
608 user.update_userdata(force_password_change=True)
607
609
608 Session().add(user)
610 Session().add(user)
609
611
610 # now delete the token in question
612 # now delete the token in question
611 UserApiKeys = AuthTokenModel.cls
613 UserApiKeys = AuthTokenModel.cls
612 UserApiKeys().query().filter(
614 UserApiKeys().query().filter(
613 UserApiKeys.api_key == data['token']).delete()
615 UserApiKeys.api_key == data['token']).delete()
614
616
615 Session().commit()
617 Session().commit()
616 log.info('successfully reset password for `%s`', user_email)
618 log.info('successfully reset password for `%s`', user_email)
617
619
618 if new_passwd is None:
620 if new_passwd is None:
619 raise Exception('unable to generate new password')
621 raise Exception('unable to generate new password')
620
622
621 pre_db = False
623 pre_db = False
622
624
623 email_kwargs = {
625 email_kwargs = {
624 'new_password': new_passwd,
626 'new_password': new_passwd,
625 'user': user,
627 'user': user,
626 'email': user_email,
628 'email': user_email,
627 'date': datetime.datetime.now()
629 'date': datetime.datetime.now()
628 }
630 }
629
631
630 (subject, headers, email_body,
632 (subject, headers, email_body,
631 email_body_plaintext) = EmailNotificationModel().render_email(
633 email_body_plaintext) = EmailNotificationModel().render_email(
632 EmailNotificationModel.TYPE_PASSWORD_RESET_CONFIRMATION,
634 EmailNotificationModel.TYPE_PASSWORD_RESET_CONFIRMATION,
633 **email_kwargs)
635 **email_kwargs)
634
636
635 recipients = [user_email]
637 recipients = [user_email]
636
638
637 action_logger_generic(
639 action_logger_generic(
638 'sent new password to user: {} with email: {}'.format(
640 'sent new password to user: {} with email: {}'.format(
639 user, user_email), namespace='security.password_reset')
641 user, user_email), namespace='security.password_reset')
640
642
641 run_task(tasks.send_email, recipients, subject,
643 run_task(tasks.send_email, recipients, subject,
642 email_body_plaintext, email_body)
644 email_body_plaintext, email_body)
643
645
644 except Exception:
646 except Exception:
645 log.error('Failed to update user password')
647 log.error('Failed to update user password')
646 log.error(traceback.format_exc())
648 log.error(traceback.format_exc())
647 if pre_db:
649 if pre_db:
648 # we rollback only if local db stuff fails. If it goes into
650 # we rollback only if local db stuff fails. If it goes into
649 # run_task, we're pass rollback state this wouldn't work then
651 # run_task, we're pass rollback state this wouldn't work then
650 Session().rollback()
652 Session().rollback()
651
653
652 return True
654 return True
653
655
654 def fill_data(self, auth_user, user_id=None, api_key=None, username=None):
656 def fill_data(self, auth_user, user_id=None, api_key=None, username=None):
655 """
657 """
656 Fetches auth_user by user_id,or api_key if present.
658 Fetches auth_user by user_id,or api_key if present.
657 Fills auth_user attributes with those taken from database.
659 Fills auth_user attributes with those taken from database.
658 Additionally set's is_authenitated if lookup fails
660 Additionally set's is_authenitated if lookup fails
659 present in database
661 present in database
660
662
661 :param auth_user: instance of user to set attributes
663 :param auth_user: instance of user to set attributes
662 :param user_id: user id to fetch by
664 :param user_id: user id to fetch by
663 :param api_key: api key to fetch by
665 :param api_key: api key to fetch by
664 :param username: username to fetch by
666 :param username: username to fetch by
665 """
667 """
666 def token_obfuscate(token):
668 def token_obfuscate(token):
667 if token:
669 if token:
668 return token[:4] + "****"
670 return token[:4] + "****"
669
671
670 if user_id is None and api_key is None and username is None:
672 if user_id is None and api_key is None and username is None:
671 raise Exception('You need to pass user_id, api_key or username')
673 raise Exception('You need to pass user_id, api_key or username')
672
674
673 log.debug(
675 log.debug(
674 'AuthUser: fill data execution based on: '
676 'AuthUser: fill data execution based on: '
675 'user_id:%s api_key:%s username:%s', user_id, api_key, username)
677 'user_id:%s api_key:%s username:%s', user_id, api_key, username)
676 try:
678 try:
677 dbuser = None
679 dbuser = None
678 if user_id:
680 if user_id:
679 dbuser = self.get(user_id)
681 dbuser = self.get(user_id)
680 elif api_key:
682 elif api_key:
681 dbuser = self.get_by_auth_token(api_key)
683 dbuser = self.get_by_auth_token(api_key)
682 elif username:
684 elif username:
683 dbuser = self.get_by_username(username)
685 dbuser = self.get_by_username(username)
684
686
685 if not dbuser:
687 if not dbuser:
686 log.warning(
688 log.warning(
687 'Unable to lookup user by id:%s api_key:%s username:%s',
689 'Unable to lookup user by id:%s api_key:%s username:%s',
688 user_id, token_obfuscate(api_key), username)
690 user_id, token_obfuscate(api_key), username)
689 return False
691 return False
690 if not dbuser.active:
692 if not dbuser.active:
691 log.debug('User `%s:%s` is inactive, skipping fill data',
693 log.debug('User `%s:%s` is inactive, skipping fill data',
692 username, user_id)
694 username, user_id)
693 return False
695 return False
694
696
695 log.debug('AuthUser: filling found user:%s data', dbuser)
697 log.debug('AuthUser: filling found user:%s data', dbuser)
696 user_data = dbuser.get_dict()
698 user_data = dbuser.get_dict()
697
699
698 user_data.update({
700 user_data.update({
699 # set explicit the safe escaped values
701 # set explicit the safe escaped values
700 'first_name': dbuser.first_name,
702 'first_name': dbuser.first_name,
701 'last_name': dbuser.last_name,
703 'last_name': dbuser.last_name,
702 })
704 })
703
705
704 for k, v in user_data.items():
706 for k, v in user_data.items():
705 # properties of auth user we dont update
707 # properties of auth user we dont update
706 if k not in ['auth_tokens', 'permissions']:
708 if k not in ['auth_tokens', 'permissions']:
707 setattr(auth_user, k, v)
709 setattr(auth_user, k, v)
708
710
709 except Exception:
711 except Exception:
710 log.error(traceback.format_exc())
712 log.error(traceback.format_exc())
711 auth_user.is_authenticated = False
713 auth_user.is_authenticated = False
712 return False
714 return False
713
715
714 return True
716 return True
715
717
716 def has_perm(self, user, perm):
718 def has_perm(self, user, perm):
717 perm = self._get_perm(perm)
719 perm = self._get_perm(perm)
718 user = self._get_user(user)
720 user = self._get_user(user)
719
721
720 return UserToPerm.query().filter(UserToPerm.user == user)\
722 return UserToPerm.query().filter(UserToPerm.user == user)\
721 .filter(UserToPerm.permission == perm).scalar() is not None
723 .filter(UserToPerm.permission == perm).scalar() is not None
722
724
723 def grant_perm(self, user, perm):
725 def grant_perm(self, user, perm):
724 """
726 """
725 Grant user global permissions
727 Grant user global permissions
726
728
727 :param user:
729 :param user:
728 :param perm:
730 :param perm:
729 """
731 """
730 user = self._get_user(user)
732 user = self._get_user(user)
731 perm = self._get_perm(perm)
733 perm = self._get_perm(perm)
732 # if this permission is already granted skip it
734 # if this permission is already granted skip it
733 _perm = UserToPerm.query()\
735 _perm = UserToPerm.query()\
734 .filter(UserToPerm.user == user)\
736 .filter(UserToPerm.user == user)\
735 .filter(UserToPerm.permission == perm)\
737 .filter(UserToPerm.permission == perm)\
736 .scalar()
738 .scalar()
737 if _perm:
739 if _perm:
738 return
740 return
739 new = UserToPerm()
741 new = UserToPerm()
740 new.user = user
742 new.user = user
741 new.permission = perm
743 new.permission = perm
742 self.sa.add(new)
744 self.sa.add(new)
743 return new
745 return new
744
746
745 def revoke_perm(self, user, perm):
747 def revoke_perm(self, user, perm):
746 """
748 """
747 Revoke users global permissions
749 Revoke users global permissions
748
750
749 :param user:
751 :param user:
750 :param perm:
752 :param perm:
751 """
753 """
752 user = self._get_user(user)
754 user = self._get_user(user)
753 perm = self._get_perm(perm)
755 perm = self._get_perm(perm)
754
756
755 obj = UserToPerm.query()\
757 obj = UserToPerm.query()\
756 .filter(UserToPerm.user == user)\
758 .filter(UserToPerm.user == user)\
757 .filter(UserToPerm.permission == perm)\
759 .filter(UserToPerm.permission == perm)\
758 .scalar()
760 .scalar()
759 if obj:
761 if obj:
760 self.sa.delete(obj)
762 self.sa.delete(obj)
761
763
762 def add_extra_email(self, user, email):
764 def add_extra_email(self, user, email):
763 """
765 """
764 Adds email address to UserEmailMap
766 Adds email address to UserEmailMap
765
767
766 :param user:
768 :param user:
767 :param email:
769 :param email:
768 """
770 """
769
771
770 user = self._get_user(user)
772 user = self._get_user(user)
771
773
772 obj = UserEmailMap()
774 obj = UserEmailMap()
773 obj.user = user
775 obj.user = user
774 obj.email = email
776 obj.email = email
775 self.sa.add(obj)
777 self.sa.add(obj)
776 return obj
778 return obj
777
779
778 def delete_extra_email(self, user, email_id):
780 def delete_extra_email(self, user, email_id):
779 """
781 """
780 Removes email address from UserEmailMap
782 Removes email address from UserEmailMap
781
783
782 :param user:
784 :param user:
783 :param email_id:
785 :param email_id:
784 """
786 """
785 user = self._get_user(user)
787 user = self._get_user(user)
786 obj = UserEmailMap.query().get(email_id)
788 obj = UserEmailMap.query().get(email_id)
787 if obj and obj.user_id == user.user_id:
789 if obj and obj.user_id == user.user_id:
788 self.sa.delete(obj)
790 self.sa.delete(obj)
789
791
790 def parse_ip_range(self, ip_range):
792 def parse_ip_range(self, ip_range):
791 ip_list = []
793 ip_list = []
792
794
793 def make_unique(value):
795 def make_unique(value):
794 seen = []
796 seen = []
795 return [c for c in value if not (c in seen or seen.append(c))]
797 return [c for c in value if not (c in seen or seen.append(c))]
796
798
797 # firsts split by commas
799 # firsts split by commas
798 for ip_range in ip_range.split(','):
800 for ip_range in ip_range.split(','):
799 if not ip_range:
801 if not ip_range:
800 continue
802 continue
801 ip_range = ip_range.strip()
803 ip_range = ip_range.strip()
802 if '-' in ip_range:
804 if '-' in ip_range:
803 start_ip, end_ip = ip_range.split('-', 1)
805 start_ip, end_ip = ip_range.split('-', 1)
804 start_ip = ipaddress.ip_address(safe_unicode(start_ip.strip()))
806 start_ip = ipaddress.ip_address(safe_unicode(start_ip.strip()))
805 end_ip = ipaddress.ip_address(safe_unicode(end_ip.strip()))
807 end_ip = ipaddress.ip_address(safe_unicode(end_ip.strip()))
806 parsed_ip_range = []
808 parsed_ip_range = []
807
809
808 for index in xrange(int(start_ip), int(end_ip) + 1):
810 for index in xrange(int(start_ip), int(end_ip) + 1):
809 new_ip = ipaddress.ip_address(index)
811 new_ip = ipaddress.ip_address(index)
810 parsed_ip_range.append(str(new_ip))
812 parsed_ip_range.append(str(new_ip))
811 ip_list.extend(parsed_ip_range)
813 ip_list.extend(parsed_ip_range)
812 else:
814 else:
813 ip_list.append(ip_range)
815 ip_list.append(ip_range)
814
816
815 return make_unique(ip_list)
817 return make_unique(ip_list)
816
818
817 def add_extra_ip(self, user, ip, description=None):
819 def add_extra_ip(self, user, ip, description=None):
818 """
820 """
819 Adds ip address to UserIpMap
821 Adds ip address to UserIpMap
820
822
821 :param user:
823 :param user:
822 :param ip:
824 :param ip:
823 """
825 """
824
826
825 user = self._get_user(user)
827 user = self._get_user(user)
826 obj = UserIpMap()
828 obj = UserIpMap()
827 obj.user = user
829 obj.user = user
828 obj.ip_addr = ip
830 obj.ip_addr = ip
829 obj.description = description
831 obj.description = description
830 self.sa.add(obj)
832 self.sa.add(obj)
831 return obj
833 return obj
832
834
835 auth_token_role = AuthTokenModel.cls
836
837 def add_auth_token(self, user, lifetime_minutes, role, description=u'',
838 scope_callback=None):
839 """
840 Add AuthToken for user.
841
842 :param user: username/user_id
843 :param lifetime_minutes: in minutes the lifetime for token, -1 equals no limit
844 :param role: one of AuthTokenModel.cls.ROLE_*
845 :param description: optional string description
846 """
847
848 token = AuthTokenModel().create(
849 user, description, lifetime_minutes, role)
850 if scope_callback and callable(scope_callback):
851 # call the callback if we provide, used to attach scope for EE edition
852 scope_callback(token)
853 return token
854
833 def delete_extra_ip(self, user, ip_id):
855 def delete_extra_ip(self, user, ip_id):
834 """
856 """
835 Removes ip address from UserIpMap
857 Removes ip address from UserIpMap
836
858
837 :param user:
859 :param user:
838 :param ip_id:
860 :param ip_id:
839 """
861 """
840 user = self._get_user(user)
862 user = self._get_user(user)
841 obj = UserIpMap.query().get(ip_id)
863 obj = UserIpMap.query().get(ip_id)
842 if obj and obj.user_id == user.user_id:
864 if obj and obj.user_id == user.user_id:
843 self.sa.delete(obj)
865 self.sa.delete(obj)
844
866
845 def get_accounts_in_creation_order(self, current_user=None):
867 def get_accounts_in_creation_order(self, current_user=None):
846 """
868 """
847 Get accounts in order of creation for deactivation for license limits
869 Get accounts in order of creation for deactivation for license limits
848
870
849 pick currently logged in user, and append to the list in position 0
871 pick currently logged in user, and append to the list in position 0
850 pick all super-admins in order of creation date and add it to the list
872 pick all super-admins in order of creation date and add it to the list
851 pick all other accounts in order of creation and add it to the list.
873 pick all other accounts in order of creation and add it to the list.
852
874
853 Based on that list, the last accounts can be disabled as they are
875 Based on that list, the last accounts can be disabled as they are
854 created at the end and don't include any of the super admins as well
876 created at the end and don't include any of the super admins as well
855 as the current user.
877 as the current user.
856
878
857 :param current_user: optionally current user running this operation
879 :param current_user: optionally current user running this operation
858 """
880 """
859
881
860 if not current_user:
882 if not current_user:
861 current_user = get_current_rhodecode_user()
883 current_user = get_current_rhodecode_user()
862 active_super_admins = [
884 active_super_admins = [
863 x.user_id for x in User.query()
885 x.user_id for x in User.query()
864 .filter(User.user_id != current_user.user_id)
886 .filter(User.user_id != current_user.user_id)
865 .filter(User.active == true())
887 .filter(User.active == true())
866 .filter(User.admin == true())
888 .filter(User.admin == true())
867 .order_by(User.created_on.asc())]
889 .order_by(User.created_on.asc())]
868
890
869 active_regular_users = [
891 active_regular_users = [
870 x.user_id for x in User.query()
892 x.user_id for x in User.query()
871 .filter(User.user_id != current_user.user_id)
893 .filter(User.user_id != current_user.user_id)
872 .filter(User.active == true())
894 .filter(User.active == true())
873 .filter(User.admin == false())
895 .filter(User.admin == false())
874 .order_by(User.created_on.asc())]
896 .order_by(User.created_on.asc())]
875
897
876 list_of_accounts = [current_user.user_id]
898 list_of_accounts = [current_user.user_id]
877 list_of_accounts += active_super_admins
899 list_of_accounts += active_super_admins
878 list_of_accounts += active_regular_users
900 list_of_accounts += active_regular_users
879
901
880 return list_of_accounts
902 return list_of_accounts
881
903
882 def deactivate_last_users(self, expected_users, current_user=None):
904 def deactivate_last_users(self, expected_users, current_user=None):
883 """
905 """
884 Deactivate accounts that are over the license limits.
906 Deactivate accounts that are over the license limits.
885 Algorithm of which accounts to disabled is based on the formula:
907 Algorithm of which accounts to disabled is based on the formula:
886
908
887 Get current user, then super admins in creation order, then regular
909 Get current user, then super admins in creation order, then regular
888 active users in creation order.
910 active users in creation order.
889
911
890 Using that list we mark all accounts from the end of it as inactive.
912 Using that list we mark all accounts from the end of it as inactive.
891 This way we block only latest created accounts.
913 This way we block only latest created accounts.
892
914
893 :param expected_users: list of users in special order, we deactivate
915 :param expected_users: list of users in special order, we deactivate
894 the end N ammoun of users from that list
916 the end N ammoun of users from that list
895 """
917 """
896
918
897 list_of_accounts = self.get_accounts_in_creation_order(
919 list_of_accounts = self.get_accounts_in_creation_order(
898 current_user=current_user)
920 current_user=current_user)
899
921
900 for acc_id in list_of_accounts[expected_users + 1:]:
922 for acc_id in list_of_accounts[expected_users + 1:]:
901 user = User.get(acc_id)
923 user = User.get(acc_id)
902 log.info('Deactivating account %s for license unlock', user)
924 log.info('Deactivating account %s for license unlock', user)
903 user.active = False
925 user.active = False
904 Session().add(user)
926 Session().add(user)
905 Session().commit()
927 Session().commit()
906
928
907 return
929 return
908
930
909 def get_user_log(self, user, filter_term):
931 def get_user_log(self, user, filter_term):
910 user_log = UserLog.query()\
932 user_log = UserLog.query()\
911 .filter(or_(UserLog.user_id == user.user_id,
933 .filter(or_(UserLog.user_id == user.user_id,
912 UserLog.username == user.username))\
934 UserLog.username == user.username))\
913 .options(joinedload(UserLog.user))\
935 .options(joinedload(UserLog.user))\
914 .options(joinedload(UserLog.repository))\
936 .options(joinedload(UserLog.repository))\
915 .order_by(UserLog.action_date.desc())
937 .order_by(UserLog.action_date.desc())
916
938
917 user_log = user_log_filter(user_log, filter_term)
939 user_log = user_log_filter(user_log, filter_term)
918 return user_log
940 return user_log
General Comments 0
You need to be logged in to leave comments. Login now