##// END OF EJS Templates
login: Fix password reset mail link....
johbo -
r37:0ef0b0cc default
parent child Browse files
Show More
@@ -1,359 +1,369 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2016-2016 RhodeCode GmbH
3 # Copyright (C) 2016-2016 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 datetime
21 import datetime
22 import formencode
22 import formencode
23 import logging
23 import logging
24 import urlparse
24 import urlparse
25 import uuid
25 import uuid
26
26
27 from pylons import url
27 from pylons import url
28 from pyramid.httpexceptions import HTTPFound
28 from pyramid.httpexceptions import HTTPFound
29 from pyramid.i18n import TranslationStringFactory
29 from pyramid.i18n import TranslationStringFactory
30 from pyramid.view import view_config
30 from pyramid.view import view_config
31 from recaptcha.client.captcha import submit
31 from recaptcha.client.captcha import submit
32
32
33 from rhodecode.authentication.base import loadplugin
33 from rhodecode.authentication.base import loadplugin
34 from rhodecode.lib.auth import (
34 from rhodecode.lib.auth import (
35 AuthUser, HasPermissionAnyDecorator, CSRFRequired)
35 AuthUser, HasPermissionAnyDecorator, CSRFRequired)
36 from rhodecode.lib.base import get_ip_addr
36 from rhodecode.lib.base import get_ip_addr
37 from rhodecode.lib.exceptions import UserCreationError
37 from rhodecode.lib.exceptions import UserCreationError
38 from rhodecode.lib.utils2 import safe_str
38 from rhodecode.lib.utils2 import safe_str
39 from rhodecode.model.db import User
39 from rhodecode.model.db import User
40 from rhodecode.model.forms import LoginForm, RegisterForm, PasswordResetForm
40 from rhodecode.model.forms import LoginForm, RegisterForm, PasswordResetForm
41 from rhodecode.model.login_session import LoginSession
41 from rhodecode.model.login_session import LoginSession
42 from rhodecode.model.meta import Session
42 from rhodecode.model.meta import Session
43 from rhodecode.model.settings import SettingsModel
43 from rhodecode.model.settings import SettingsModel
44 from rhodecode.model.user import UserModel
44 from rhodecode.model.user import UserModel
45
45
46
46
47 _ = TranslationStringFactory('rhodecode-enterprise')
47 _ = TranslationStringFactory('rhodecode-enterprise')
48
48
49 log = logging.getLogger(__name__)
49 log = logging.getLogger(__name__)
50
50
51
51
52 def _store_user_in_session(session, username, remember=False):
52 def _store_user_in_session(session, username, remember=False):
53 user = User.get_by_username(username, case_insensitive=True)
53 user = User.get_by_username(username, case_insensitive=True)
54 auth_user = AuthUser(user.user_id)
54 auth_user = AuthUser(user.user_id)
55 auth_user.set_authenticated()
55 auth_user.set_authenticated()
56 cs = auth_user.get_cookie_store()
56 cs = auth_user.get_cookie_store()
57 session['rhodecode_user'] = cs
57 session['rhodecode_user'] = cs
58 user.update_lastlogin()
58 user.update_lastlogin()
59 Session().commit()
59 Session().commit()
60
60
61 # If they want to be remembered, update the cookie
61 # If they want to be remembered, update the cookie
62 if remember:
62 if remember:
63 _year = (datetime.datetime.now() +
63 _year = (datetime.datetime.now() +
64 datetime.timedelta(seconds=60 * 60 * 24 * 365))
64 datetime.timedelta(seconds=60 * 60 * 24 * 365))
65 session._set_cookie_expires(_year)
65 session._set_cookie_expires(_year)
66
66
67 session.save()
67 session.save()
68
68
69 log.info('user %s is now authenticated and stored in '
69 log.info('user %s is now authenticated and stored in '
70 'session, session attrs %s', username, cs)
70 'session, session attrs %s', username, cs)
71
71
72 # dumps session attrs back to cookie
72 # dumps session attrs back to cookie
73 session._update_cookie_out()
73 session._update_cookie_out()
74 # we set new cookie
74 # we set new cookie
75 headers = None
75 headers = None
76 if session.request['set_cookie']:
76 if session.request['set_cookie']:
77 # send set-cookie headers back to response to update cookie
77 # send set-cookie headers back to response to update cookie
78 headers = [('Set-Cookie', session.request['cookie_out'])]
78 headers = [('Set-Cookie', session.request['cookie_out'])]
79 return headers
79 return headers
80
80
81
81
82 class LoginView(object):
82 class LoginView(object):
83
83
84 def __init__(self, context, request):
84 def __init__(self, context, request):
85 self.request = request
85 self.request = request
86 self.context = context
86 self.context = context
87 self.session = request.session
87 self.session = request.session
88 self._rhodecode_user = request.user
88 self._rhodecode_user = request.user
89
89
90 def _validate_came_from(self, came_from):
90 def _validate_came_from(self, came_from):
91 if not came_from:
91 if not came_from:
92 return came_from
92 return came_from
93
93
94 parsed = urlparse.urlparse(came_from)
94 parsed = urlparse.urlparse(came_from)
95 allowed_schemes = ['http', 'https']
95 allowed_schemes = ['http', 'https']
96 if parsed.scheme and parsed.scheme not in allowed_schemes:
96 if parsed.scheme and parsed.scheme not in allowed_schemes:
97 log.error('Suspicious URL scheme detected %s for url %s' %
97 log.error('Suspicious URL scheme detected %s for url %s' %
98 (parsed.scheme, parsed))
98 (parsed.scheme, parsed))
99 came_from = url('home')
99 came_from = url('home')
100 elif parsed.netloc and self.request.host != parsed.netloc:
100 elif parsed.netloc and self.request.host != parsed.netloc:
101 log.error('Suspicious NETLOC detected %s for url %s server url '
101 log.error('Suspicious NETLOC detected %s for url %s server url '
102 'is: %s' % (parsed.netloc, parsed, self.request.host))
102 'is: %s' % (parsed.netloc, parsed, self.request.host))
103 came_from = url('home')
103 came_from = url('home')
104 if any(bad_str in parsed.path for bad_str in ('\r', '\n')):
104 if any(bad_str in parsed.path for bad_str in ('\r', '\n')):
105 log.error('Header injection detected `%s` for url %s server url ' %
105 log.error('Header injection detected `%s` for url %s server url ' %
106 (parsed.path, parsed))
106 (parsed.path, parsed))
107 came_from = url('home')
107 came_from = url('home')
108 return came_from
108 return came_from
109
109
110 def _get_came_from(self):
110 def _get_came_from(self):
111 _default_came_from = url('home')
111 _default_came_from = url('home')
112 came_from = self._validate_came_from(
112 came_from = self._validate_came_from(
113 safe_str(self.request.GET.get('came_from', '')))
113 safe_str(self.request.GET.get('came_from', '')))
114 return came_from or _default_came_from
114 return came_from or _default_came_from
115
115
116 def _get_template_context(self):
116 def _get_template_context(self):
117 return {
117 return {
118 'came_from': self._get_came_from(),
118 'came_from': self._get_came_from(),
119 'defaults': {},
119 'defaults': {},
120 'errors': {},
120 'errors': {},
121 }
121 }
122
122
123 @view_config(
123 @view_config(
124 route_name='login', request_method='GET',
124 route_name='login', request_method='GET',
125 renderer='rhodecode:templates/login.html')
125 renderer='rhodecode:templates/login.html')
126 def login(self):
126 def login(self):
127 user = self.request.user
127 user = self.request.user
128
128
129 # redirect if already logged in
129 # redirect if already logged in
130 if user.is_authenticated and not user.is_default and user.ip_allowed:
130 if user.is_authenticated and not user.is_default and user.ip_allowed:
131 raise HTTPFound(self._get_came_from())
131 raise HTTPFound(self._get_came_from())
132
132
133 return self._get_template_context()
133 return self._get_template_context()
134
134
135 @view_config(
135 @view_config(
136 route_name='login', request_method='POST',
136 route_name='login', request_method='POST',
137 renderer='rhodecode:templates/login.html')
137 renderer='rhodecode:templates/login.html')
138 def login_post(self):
138 def login_post(self):
139 came_from = self._get_came_from()
139 came_from = self._get_came_from()
140 session = self.request.session
140 session = self.request.session
141 login_form = LoginForm()()
141 login_form = LoginForm()()
142
142
143 try:
143 try:
144 session.invalidate()
144 session.invalidate()
145 form_result = login_form.to_python(self.request.params)
145 form_result = login_form.to_python(self.request.params)
146 # form checks for username/password, now we're authenticated
146 # form checks for username/password, now we're authenticated
147 headers = _store_user_in_session(
147 headers = _store_user_in_session(
148 self.session,
148 self.session,
149 username=form_result['username'],
149 username=form_result['username'],
150 remember=form_result['remember'])
150 remember=form_result['remember'])
151 raise HTTPFound(came_from, headers=headers)
151 raise HTTPFound(came_from, headers=headers)
152 except formencode.Invalid as errors:
152 except formencode.Invalid as errors:
153 defaults = errors.value
153 defaults = errors.value
154 # remove password from filling in form again
154 # remove password from filling in form again
155 del defaults['password']
155 del defaults['password']
156 render_ctx = self._get_template_context()
156 render_ctx = self._get_template_context()
157 render_ctx.update({
157 render_ctx.update({
158 'errors': errors.error_dict,
158 'errors': errors.error_dict,
159 'defaults': defaults,
159 'defaults': defaults,
160 })
160 })
161 return render_ctx
161 return render_ctx
162
162
163 except UserCreationError as e:
163 except UserCreationError as e:
164 # container auth or other auth functions that create users on
164 # container auth or other auth functions that create users on
165 # the fly can throw this exception signaling that there's issue
165 # the fly can throw this exception signaling that there's issue
166 # with user creation, explanation should be provided in
166 # with user creation, explanation should be provided in
167 # Exception itself
167 # Exception itself
168 session.flash(e, queue='error')
168 session.flash(e, queue='error')
169
169
170 # check if we use container plugin, and try to login using it.
170 # check if we use container plugin, and try to login using it.
171 from rhodecode.authentication.base import authenticate, HTTP_TYPE
171 from rhodecode.authentication.base import authenticate, HTTP_TYPE
172 try:
172 try:
173 log.debug('Running PRE-AUTH for container based authentication')
173 log.debug('Running PRE-AUTH for container based authentication')
174 auth_info = authenticate(
174 auth_info = authenticate(
175 '', '', self.request.environ, HTTP_TYPE, skip_missing=True)
175 '', '', self.request.environ, HTTP_TYPE, skip_missing=True)
176 except UserCreationError as e:
176 except UserCreationError as e:
177 log.error(e)
177 log.error(e)
178 session.flash(e, queue='error')
178 session.flash(e, queue='error')
179 # render login, with flash message about limit
179 # render login, with flash message about limit
180 return self._get_template_context()
180 return self._get_template_context()
181
181
182 if auth_info:
182 if auth_info:
183 headers = _store_user_in_session(auth_info.get('username'))
183 headers = _store_user_in_session(auth_info.get('username'))
184 raise HTTPFound(came_from, headers=headers)
184 raise HTTPFound(came_from, headers=headers)
185
185
186 return self._get_template_context()
186 return self._get_template_context()
187
187
188 @CSRFRequired()
188 @CSRFRequired()
189 @view_config(route_name='logout', request_method='POST')
189 @view_config(route_name='logout', request_method='POST')
190 def logout(self):
190 def logout(self):
191 LoginSession().destroy_user_session()
191 LoginSession().destroy_user_session()
192 return HTTPFound(url('home'))
192 return HTTPFound(url('home'))
193
193
194 @HasPermissionAnyDecorator(
194 @HasPermissionAnyDecorator(
195 'hg.admin', 'hg.register.auto_activate', 'hg.register.manual_activate')
195 'hg.admin', 'hg.register.auto_activate', 'hg.register.manual_activate')
196 @view_config(
196 @view_config(
197 route_name='register', request_method='GET',
197 route_name='register', request_method='GET',
198 renderer='rhodecode:templates/register.html',)
198 renderer='rhodecode:templates/register.html',)
199 def register(self):
199 def register(self):
200 settings = SettingsModel().get_all_settings()
200 settings = SettingsModel().get_all_settings()
201
201
202 social_data = self.session.get('rhodecode.social_auth')
202 social_data = self.session.get('rhodecode.social_auth')
203 form_defaults = {}
203 form_defaults = {}
204 if social_data:
204 if social_data:
205 password = str(uuid.uuid4())
205 password = str(uuid.uuid4())
206 form_defaults = {
206 form_defaults = {
207 'username': social_data['user'].get('user_name'),
207 'username': social_data['user'].get('user_name'),
208 'password': password,
208 'password': password,
209 'password_confirmation': password,
209 'password_confirmation': password,
210 'email': social_data['user'].get('email'),
210 'email': social_data['user'].get('email'),
211 }
211 }
212
212
213 auto_active = 'hg.register.auto_activate' in User.get_default_user()\
213 auto_active = 'hg.register.auto_activate' in User.get_default_user()\
214 .AuthUser.permissions['global']
214 .AuthUser.permissions['global']
215 captcha_private_key = settings.get('rhodecode_captcha_private_key')
215 captcha_private_key = settings.get('rhodecode_captcha_private_key')
216 render_ctx = self._get_template_context()
216 render_ctx = self._get_template_context()
217 render_ctx.update({
217 render_ctx.update({
218 'auto_active': auto_active,
218 'auto_active': auto_active,
219 'captcha_active': bool(captcha_private_key),
219 'captcha_active': bool(captcha_private_key),
220 'captcha_public_key': settings.get('rhodecode_captcha_public_key'),
220 'captcha_public_key': settings.get('rhodecode_captcha_public_key'),
221 'defaults': form_defaults,
221 'defaults': form_defaults,
222 'register_message': settings.get('rhodecode_register_message') or '',
222 'register_message': settings.get('rhodecode_register_message') or '',
223 })
223 })
224 return render_ctx
224 return render_ctx
225
225
226 @view_config(
226 @view_config(
227 route_name='register', request_method='POST',
227 route_name='register', request_method='POST',
228 renderer='rhodecode:templates/register.html')
228 renderer='rhodecode:templates/register.html')
229 def register_post(self):
229 def register_post(self):
230 social_data = self.session.get('rhodecode.social_auth')
230 social_data = self.session.get('rhodecode.social_auth')
231 settings = SettingsModel().get_all_settings()
231 settings = SettingsModel().get_all_settings()
232 captcha_private_key = settings.get('rhodecode_captcha_private_key')
232 captcha_private_key = settings.get('rhodecode_captcha_private_key')
233 captcha_active = bool(captcha_private_key)
233 captcha_active = bool(captcha_private_key)
234 auto_active = 'hg.register.auto_activate' in User.get_default_user()\
234 auto_active = 'hg.register.auto_activate' in User.get_default_user()\
235 .AuthUser.permissions['global']
235 .AuthUser.permissions['global']
236 register_message = settings.get('rhodecode_register_message') or ''
236 register_message = settings.get('rhodecode_register_message') or ''
237
237
238 register_form = RegisterForm()()
238 register_form = RegisterForm()()
239 try:
239 try:
240 form_result = register_form.to_python(self.request.params)
240 form_result = register_form.to_python(self.request.params)
241 form_result['active'] = auto_active
241 form_result['active'] = auto_active
242
242
243 if captcha_active:
243 if captcha_active:
244 response = submit(
244 response = submit(
245 self.request.params.get('recaptcha_challenge_field'),
245 self.request.params.get('recaptcha_challenge_field'),
246 self.request.params.get('recaptcha_response_field'),
246 self.request.params.get('recaptcha_response_field'),
247 private_key=captcha_private_key,
247 private_key=captcha_private_key,
248 remoteip=self.ip_addr)
248 remoteip=self.ip_addr)
249 if captcha_active and not response.is_valid:
249 if captcha_active and not response.is_valid:
250 _value = form_result
250 _value = form_result
251 _msg = _('bad captcha')
251 _msg = _('bad captcha')
252 error_dict = {'recaptcha_field': _msg}
252 error_dict = {'recaptcha_field': _msg}
253 raise formencode.Invalid(_msg, _value, None,
253 raise formencode.Invalid(_msg, _value, None,
254 error_dict=error_dict)
254 error_dict=error_dict)
255
255
256 new_user = UserModel().create_registration(form_result)
256 new_user = UserModel().create_registration(form_result)
257 if social_data:
257 if social_data:
258 plugin_name = 'egg:rhodecode-enterprise-ee#{}'.format(
258 plugin_name = 'egg:rhodecode-enterprise-ee#{}'.format(
259 social_data['credentials.provider']
259 social_data['credentials.provider']
260 )
260 )
261 auth_plugin = loadplugin(plugin_name)
261 auth_plugin = loadplugin(plugin_name)
262 if auth_plugin:
262 if auth_plugin:
263 auth_plugin.handle_social_data(
263 auth_plugin.handle_social_data(
264 self.session, new_user.user_id, social_data)
264 self.session, new_user.user_id, social_data)
265 self.session.flash(
265 self.session.flash(
266 _('You have successfully registered with RhodeCode'),
266 _('You have successfully registered with RhodeCode'),
267 queue='success')
267 queue='success')
268 Session().commit()
268 Session().commit()
269
269
270 redirect_ro = self.request.route_path('login')
270 redirect_ro = self.request.route_path('login')
271 raise HTTPFound(redirect_ro)
271 raise HTTPFound(redirect_ro)
272
272
273 except formencode.Invalid as errors:
273 except formencode.Invalid as errors:
274 del errors.value['password']
274 del errors.value['password']
275 del errors.value['password_confirmation']
275 del errors.value['password_confirmation']
276 render_ctx = self._get_template_context()
276 render_ctx = self._get_template_context()
277 render_ctx.update({
277 render_ctx.update({
278 'errors': errors.error_dict,
278 'errors': errors.error_dict,
279 'defaults': errors.value,
279 'defaults': errors.value,
280 'register_message': register_message,
280 'register_message': register_message,
281 })
281 })
282 return render_ctx
282 return render_ctx
283
283
284 except UserCreationError as e:
284 except UserCreationError as e:
285 # container auth or other auth functions that create users on
285 # container auth or other auth functions that create users on
286 # the fly can throw this exception signaling that there's issue
286 # the fly can throw this exception signaling that there's issue
287 # with user creation, explanation should be provided in
287 # with user creation, explanation should be provided in
288 # Exception itself
288 # Exception itself
289 self.session.flash(e, queue='error')
289 self.session.flash(e, queue='error')
290 render_ctx = self._get_template_context()
290 render_ctx = self._get_template_context()
291 render_ctx.update({
291 render_ctx.update({
292 'register_message': register_message,
292 'register_message': register_message,
293 })
293 })
294 return render_ctx
294 return render_ctx
295
295
296 @view_config(
296 @view_config(
297 route_name='reset_password', request_method=('GET', 'POST'),
297 route_name='reset_password', request_method=('GET', 'POST'),
298 renderer='rhodecode:templates/password_reset.html')
298 renderer='rhodecode:templates/password_reset.html')
299 def password_reset(self):
299 def password_reset(self):
300 settings = SettingsModel().get_all_settings()
300 settings = SettingsModel().get_all_settings()
301 captcha_private_key = settings.get('rhodecode_captcha_private_key')
301 captcha_private_key = settings.get('rhodecode_captcha_private_key')
302 captcha_active = bool(captcha_private_key)
302 captcha_active = bool(captcha_private_key)
303 captcha_public_key = settings.get('rhodecode_captcha_public_key')
303 captcha_public_key = settings.get('rhodecode_captcha_public_key')
304
304
305 render_ctx = {
305 render_ctx = {
306 'captcha_active': captcha_active,
306 'captcha_active': captcha_active,
307 'captcha_public_key': captcha_public_key,
307 'captcha_public_key': captcha_public_key,
308 'defaults': {},
308 'defaults': {},
309 'errors': {},
309 'errors': {},
310 }
310 }
311
311
312 if self.request.POST:
312 if self.request.POST:
313 password_reset_form = PasswordResetForm()()
313 password_reset_form = PasswordResetForm()()
314 try:
314 try:
315 form_result = password_reset_form.to_python(
315 form_result = password_reset_form.to_python(
316 self.request.params)
316 self.request.params)
317 if captcha_active:
317 if captcha_active:
318 response = submit(
318 response = submit(
319 self.request.params.get('recaptcha_challenge_field'),
319 self.request.params.get('recaptcha_challenge_field'),
320 self.request.params.get('recaptcha_response_field'),
320 self.request.params.get('recaptcha_response_field'),
321 private_key=captcha_private_key,
321 private_key=captcha_private_key,
322 remoteip=get_ip_addr(self.request.environ))
322 remoteip=get_ip_addr(self.request.environ))
323 if captcha_active and not response.is_valid:
323 if captcha_active and not response.is_valid:
324 _value = form_result
324 _value = form_result
325 _msg = _('bad captcha')
325 _msg = _('bad captcha')
326 error_dict = {'recaptcha_field': _msg}
326 error_dict = {'recaptcha_field': _msg}
327 raise formencode.Invalid(_msg, _value, None,
327 raise formencode.Invalid(_msg, _value, None,
328 error_dict=error_dict)
328 error_dict=error_dict)
329 UserModel().reset_password_link(form_result)
329
330 # Generate reset URL and send mail.
331 user_email = form_result['email']
332 user = User.get_by_email(user_email)
333 password_reset_url = self.request.route_url(
334 'reset_password_confirmation',
335 _query={'key': user.api_key})
336 UserModel().reset_password_link(
337 form_result, password_reset_url)
338
339 # Display success message and redirect.
330 self.session.flash(
340 self.session.flash(
331 _('Your password reset link was sent'),
341 _('Your password reset link was sent'),
332 queue='success')
342 queue='success')
333 return HTTPFound(self.request.route_path('login'))
343 return HTTPFound(self.request.route_path('login'))
334
344
335 except formencode.Invalid as errors:
345 except formencode.Invalid as errors:
336 render_ctx.update({
346 render_ctx.update({
337 'defaults': errors.value,
347 'defaults': errors.value,
338 'errors': errors.error_dict,
348 'errors': errors.error_dict,
339 })
349 })
340
350
341 return render_ctx
351 return render_ctx
342
352
343 @view_config(route_name='reset_password_confirmation',
353 @view_config(route_name='reset_password_confirmation',
344 request_method='GET')
354 request_method='GET')
345 def password_reset_confirmation(self):
355 def password_reset_confirmation(self):
346 if self.request.GET and self.request.GET.get('key'):
356 if self.request.GET and self.request.GET.get('key'):
347 try:
357 try:
348 user = User.get_by_auth_token(self.request.GET.get('key'))
358 user = User.get_by_auth_token(self.request.GET.get('key'))
349 data = {'email': user.email}
359 data = {'email': user.email}
350 UserModel().reset_password(data)
360 UserModel().reset_password(data)
351 self.session.flash(
361 self.session.flash(
352 _('Your password reset was successful, '
362 _('Your password reset was successful, '
353 'a new password has been sent to your email'),
363 'a new password has been sent to your email'),
354 queue='success')
364 queue='success')
355 except Exception as e:
365 except Exception as e:
356 log.error(e)
366 log.error(e)
357 return HTTPFound(self.request.route_path('reset_password'))
367 return HTTPFound(self.request.route_path('reset_password'))
358
368
359 return HTTPFound(self.request.route_path('login'))
369 return HTTPFound(self.request.route_path('login'))
@@ -1,840 +1,836 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2016 RhodeCode GmbH
3 # Copyright (C) 2010-2016 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
27
28 import datetime
28 import datetime
29 from pylons import url
29 from pylons import url
30 from pylons.i18n.translation import _
30 from pylons.i18n.translation import _
31
31
32 import ipaddress
32 import ipaddress
33 from sqlalchemy.exc import DatabaseError
33 from sqlalchemy.exc import DatabaseError
34 from sqlalchemy.sql.expression import true, false
34 from sqlalchemy.sql.expression import true, false
35
35
36 from rhodecode.lib.utils2 import (
36 from rhodecode.lib.utils2 import (
37 safe_unicode, get_current_rhodecode_user, action_logger_generic,
37 safe_unicode, get_current_rhodecode_user, action_logger_generic,
38 AttributeDict)
38 AttributeDict)
39 from rhodecode.lib.caching_query import FromCache
39 from rhodecode.lib.caching_query import FromCache
40 from rhodecode.model import BaseModel
40 from rhodecode.model import BaseModel
41 from rhodecode.model.auth_token import AuthTokenModel
41 from rhodecode.model.auth_token import AuthTokenModel
42 from rhodecode.model.db import (
42 from rhodecode.model.db import (
43 User, UserToPerm, UserEmailMap, UserIpMap)
43 User, UserToPerm, UserEmailMap, UserIpMap)
44 from rhodecode.lib.exceptions import (
44 from rhodecode.lib.exceptions import (
45 DefaultUserException, UserOwnsReposException, UserOwnsRepoGroupsException,
45 DefaultUserException, UserOwnsReposException, UserOwnsRepoGroupsException,
46 UserOwnsUserGroupsException, NotAllowedToCreateUserError)
46 UserOwnsUserGroupsException, NotAllowedToCreateUserError)
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(FromCache("sql_cache_short",
60 user = user.options(FromCache("sql_cache_short",
61 "get_user_%s" % user_id))
61 "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 get_by_username(self, username, cache=False, case_insensitive=False):
67 def get_by_username(self, username, cache=False, case_insensitive=False):
68
68
69 if case_insensitive:
69 if case_insensitive:
70 user = self.sa.query(User).filter(User.username.ilike(username))
70 user = self.sa.query(User).filter(User.username.ilike(username))
71 else:
71 else:
72 user = self.sa.query(User)\
72 user = self.sa.query(User)\
73 .filter(User.username == username)
73 .filter(User.username == username)
74 if cache:
74 if cache:
75 user = user.options(FromCache("sql_cache_short",
75 user = user.options(FromCache("sql_cache_short",
76 "get_user_%s" % username))
76 "get_user_%s" % username))
77 return user.scalar()
77 return user.scalar()
78
78
79 def get_by_email(self, email, cache=False, case_insensitive=False):
79 def get_by_email(self, email, cache=False, case_insensitive=False):
80 return User.get_by_email(email, case_insensitive, cache)
80 return User.get_by_email(email, case_insensitive, cache)
81
81
82 def get_by_auth_token(self, auth_token, cache=False):
82 def get_by_auth_token(self, auth_token, cache=False):
83 return User.get_by_auth_token(auth_token, cache)
83 return User.get_by_auth_token(auth_token, cache)
84
84
85 def get_active_user_count(self, cache=False):
85 def get_active_user_count(self, cache=False):
86 return User.query().filter(
86 return User.query().filter(
87 User.active == True).filter(
87 User.active == True).filter(
88 User.username != User.DEFAULT_USER).count()
88 User.username != User.DEFAULT_USER).count()
89
89
90 def create(self, form_data, cur_user=None):
90 def create(self, form_data, cur_user=None):
91 if not cur_user:
91 if not cur_user:
92 cur_user = getattr(get_current_rhodecode_user(), 'username', None)
92 cur_user = getattr(get_current_rhodecode_user(), 'username', None)
93
93
94 user_data = {
94 user_data = {
95 'username': form_data['username'],
95 'username': form_data['username'],
96 'password': form_data['password'],
96 'password': form_data['password'],
97 'email': form_data['email'],
97 'email': form_data['email'],
98 'firstname': form_data['firstname'],
98 'firstname': form_data['firstname'],
99 'lastname': form_data['lastname'],
99 'lastname': form_data['lastname'],
100 'active': form_data['active'],
100 'active': form_data['active'],
101 'extern_type': form_data['extern_type'],
101 'extern_type': form_data['extern_type'],
102 'extern_name': form_data['extern_name'],
102 'extern_name': form_data['extern_name'],
103 'admin': False,
103 'admin': False,
104 'cur_user': cur_user
104 'cur_user': cur_user
105 }
105 }
106
106
107 try:
107 try:
108 if form_data.get('create_repo_group'):
108 if form_data.get('create_repo_group'):
109 user_data['create_repo_group'] = True
109 user_data['create_repo_group'] = True
110 if form_data.get('password_change'):
110 if form_data.get('password_change'):
111 user_data['force_password_change'] = True
111 user_data['force_password_change'] = True
112
112
113 return UserModel().create_or_update(**user_data)
113 return UserModel().create_or_update(**user_data)
114 except Exception:
114 except Exception:
115 log.error(traceback.format_exc())
115 log.error(traceback.format_exc())
116 raise
116 raise
117
117
118 def update_user(self, user, skip_attrs=None, **kwargs):
118 def update_user(self, user, skip_attrs=None, **kwargs):
119 from rhodecode.lib.auth import get_crypt_password
119 from rhodecode.lib.auth import get_crypt_password
120
120
121 user = self._get_user(user)
121 user = self._get_user(user)
122 if user.username == User.DEFAULT_USER:
122 if user.username == User.DEFAULT_USER:
123 raise DefaultUserException(
123 raise DefaultUserException(
124 _("You can't Edit this user since it's"
124 _("You can't Edit this user since it's"
125 " crucial for entire application"))
125 " crucial for entire application"))
126
126
127 # first store only defaults
127 # first store only defaults
128 user_attrs = {
128 user_attrs = {
129 'updating_user_id': user.user_id,
129 'updating_user_id': user.user_id,
130 'username': user.username,
130 'username': user.username,
131 'password': user.password,
131 'password': user.password,
132 'email': user.email,
132 'email': user.email,
133 'firstname': user.name,
133 'firstname': user.name,
134 'lastname': user.lastname,
134 'lastname': user.lastname,
135 'active': user.active,
135 'active': user.active,
136 'admin': user.admin,
136 'admin': user.admin,
137 'extern_name': user.extern_name,
137 'extern_name': user.extern_name,
138 'extern_type': user.extern_type,
138 'extern_type': user.extern_type,
139 'language': user.user_data.get('language')
139 'language': user.user_data.get('language')
140 }
140 }
141
141
142 # in case there's new_password, that comes from form, use it to
142 # in case there's new_password, that comes from form, use it to
143 # store password
143 # store password
144 if kwargs.get('new_password'):
144 if kwargs.get('new_password'):
145 kwargs['password'] = kwargs['new_password']
145 kwargs['password'] = kwargs['new_password']
146
146
147 # cleanups, my_account password change form
147 # cleanups, my_account password change form
148 kwargs.pop('current_password', None)
148 kwargs.pop('current_password', None)
149 kwargs.pop('new_password', None)
149 kwargs.pop('new_password', None)
150 kwargs.pop('new_password_confirmation', None)
150 kwargs.pop('new_password_confirmation', None)
151
151
152 # cleanups, user edit password change form
152 # cleanups, user edit password change form
153 kwargs.pop('password_confirmation', None)
153 kwargs.pop('password_confirmation', None)
154 kwargs.pop('password_change', None)
154 kwargs.pop('password_change', None)
155
155
156 # create repo group on user creation
156 # create repo group on user creation
157 kwargs.pop('create_repo_group', None)
157 kwargs.pop('create_repo_group', None)
158
158
159 # legacy forms send name, which is the firstname
159 # legacy forms send name, which is the firstname
160 firstname = kwargs.pop('name', None)
160 firstname = kwargs.pop('name', None)
161 if firstname:
161 if firstname:
162 kwargs['firstname'] = firstname
162 kwargs['firstname'] = firstname
163
163
164 for k, v in kwargs.items():
164 for k, v in kwargs.items():
165 # skip if we don't want to update this
165 # skip if we don't want to update this
166 if skip_attrs and k in skip_attrs:
166 if skip_attrs and k in skip_attrs:
167 continue
167 continue
168
168
169 user_attrs[k] = v
169 user_attrs[k] = v
170
170
171 try:
171 try:
172 return self.create_or_update(**user_attrs)
172 return self.create_or_update(**user_attrs)
173 except Exception:
173 except Exception:
174 log.error(traceback.format_exc())
174 log.error(traceback.format_exc())
175 raise
175 raise
176
176
177 def create_or_update(
177 def create_or_update(
178 self, username, password, email, firstname='', lastname='',
178 self, username, password, email, firstname='', lastname='',
179 active=True, admin=False, extern_type=None, extern_name=None,
179 active=True, admin=False, extern_type=None, extern_name=None,
180 cur_user=None, plugin=None, force_password_change=False,
180 cur_user=None, plugin=None, force_password_change=False,
181 allow_to_create_user=True, create_repo_group=False,
181 allow_to_create_user=True, create_repo_group=False,
182 updating_user_id=None, language=None, strict_creation_check=True):
182 updating_user_id=None, language=None, strict_creation_check=True):
183 """
183 """
184 Creates a new instance if not found, or updates current one
184 Creates a new instance if not found, or updates current one
185
185
186 :param username:
186 :param username:
187 :param password:
187 :param password:
188 :param email:
188 :param email:
189 :param firstname:
189 :param firstname:
190 :param lastname:
190 :param lastname:
191 :param active:
191 :param active:
192 :param admin:
192 :param admin:
193 :param extern_type:
193 :param extern_type:
194 :param extern_name:
194 :param extern_name:
195 :param cur_user:
195 :param cur_user:
196 :param plugin: optional plugin this method was called from
196 :param plugin: optional plugin this method was called from
197 :param force_password_change: toggles new or existing user flag
197 :param force_password_change: toggles new or existing user flag
198 for password change
198 for password change
199 :param allow_to_create_user: Defines if the method can actually create
199 :param allow_to_create_user: Defines if the method can actually create
200 new users
200 new users
201 :param create_repo_group: Defines if the method should also
201 :param create_repo_group: Defines if the method should also
202 create an repo group with user name, and owner
202 create an repo group with user name, and owner
203 :param updating_user_id: if we set it up this is the user we want to
203 :param updating_user_id: if we set it up this is the user we want to
204 update this allows to editing username.
204 update this allows to editing username.
205 :param language: language of user from interface.
205 :param language: language of user from interface.
206
206
207 :returns: new User object with injected `is_new_user` attribute.
207 :returns: new User object with injected `is_new_user` attribute.
208 """
208 """
209 if not cur_user:
209 if not cur_user:
210 cur_user = getattr(get_current_rhodecode_user(), 'username', None)
210 cur_user = getattr(get_current_rhodecode_user(), 'username', None)
211
211
212 from rhodecode.lib.auth import (
212 from rhodecode.lib.auth import (
213 get_crypt_password, check_password, generate_auth_token)
213 get_crypt_password, check_password, generate_auth_token)
214 from rhodecode.lib.hooks_base import (
214 from rhodecode.lib.hooks_base import (
215 log_create_user, check_allowed_create_user)
215 log_create_user, check_allowed_create_user)
216
216
217 def _password_change(new_user, password):
217 def _password_change(new_user, password):
218 # empty password
218 # empty password
219 if not new_user.password:
219 if not new_user.password:
220 return False
220 return False
221
221
222 # password check is only needed for RhodeCode internal auth calls
222 # password check is only needed for RhodeCode internal auth calls
223 # in case it's a plugin we don't care
223 # in case it's a plugin we don't care
224 if not plugin:
224 if not plugin:
225
225
226 # first check if we gave crypted password back, and if it matches
226 # first check if we gave crypted password back, and if it matches
227 # it's not password change
227 # it's not password change
228 if new_user.password == password:
228 if new_user.password == password:
229 return False
229 return False
230
230
231 password_match = check_password(password, new_user.password)
231 password_match = check_password(password, new_user.password)
232 if not password_match:
232 if not password_match:
233 return True
233 return True
234
234
235 return False
235 return False
236
236
237 user_data = {
237 user_data = {
238 'username': username,
238 'username': username,
239 'password': password,
239 'password': password,
240 'email': email,
240 'email': email,
241 'firstname': firstname,
241 'firstname': firstname,
242 'lastname': lastname,
242 'lastname': lastname,
243 'active': active,
243 'active': active,
244 'admin': admin
244 'admin': admin
245 }
245 }
246
246
247 if updating_user_id:
247 if updating_user_id:
248 log.debug('Checking for existing account in RhodeCode '
248 log.debug('Checking for existing account in RhodeCode '
249 'database with user_id `%s` ' % (updating_user_id,))
249 'database with user_id `%s` ' % (updating_user_id,))
250 user = User.get(updating_user_id)
250 user = User.get(updating_user_id)
251 else:
251 else:
252 log.debug('Checking for existing account in RhodeCode '
252 log.debug('Checking for existing account in RhodeCode '
253 'database with username `%s` ' % (username,))
253 'database with username `%s` ' % (username,))
254 user = User.get_by_username(username, case_insensitive=True)
254 user = User.get_by_username(username, case_insensitive=True)
255
255
256 if user is None:
256 if user is None:
257 # we check internal flag if this method is actually allowed to
257 # we check internal flag if this method is actually allowed to
258 # create new user
258 # create new user
259 if not allow_to_create_user:
259 if not allow_to_create_user:
260 msg = ('Method wants to create new user, but it is not '
260 msg = ('Method wants to create new user, but it is not '
261 'allowed to do so')
261 'allowed to do so')
262 log.warning(msg)
262 log.warning(msg)
263 raise NotAllowedToCreateUserError(msg)
263 raise NotAllowedToCreateUserError(msg)
264
264
265 log.debug('Creating new user %s', username)
265 log.debug('Creating new user %s', username)
266
266
267 # only if we create user that is active
267 # only if we create user that is active
268 new_active_user = active
268 new_active_user = active
269 if new_active_user and strict_creation_check:
269 if new_active_user and strict_creation_check:
270 # raises UserCreationError if it's not allowed for any reason to
270 # raises UserCreationError if it's not allowed for any reason to
271 # create new active user, this also executes pre-create hooks
271 # create new active user, this also executes pre-create hooks
272 check_allowed_create_user(user_data, cur_user, strict_check=True)
272 check_allowed_create_user(user_data, cur_user, strict_check=True)
273 new_user = User()
273 new_user = User()
274 edit = False
274 edit = False
275 else:
275 else:
276 log.debug('updating user %s', username)
276 log.debug('updating user %s', username)
277 new_user = user
277 new_user = user
278 edit = True
278 edit = True
279
279
280 # we're not allowed to edit default user
280 # we're not allowed to edit default user
281 if user.username == User.DEFAULT_USER:
281 if user.username == User.DEFAULT_USER:
282 raise DefaultUserException(
282 raise DefaultUserException(
283 _("You can't edit this user (`%(username)s`) since it's "
283 _("You can't edit this user (`%(username)s`) since it's "
284 "crucial for entire application") % {'username': user.username})
284 "crucial for entire application") % {'username': user.username})
285
285
286 # inject special attribute that will tell us if User is new or old
286 # inject special attribute that will tell us if User is new or old
287 new_user.is_new_user = not edit
287 new_user.is_new_user = not edit
288 # for users that didn's specify auth type, we use RhodeCode built in
288 # for users that didn's specify auth type, we use RhodeCode built in
289 from rhodecode.authentication.plugins import auth_rhodecode
289 from rhodecode.authentication.plugins import auth_rhodecode
290 extern_name = extern_name or auth_rhodecode.RhodeCodeAuthPlugin.name
290 extern_name = extern_name or auth_rhodecode.RhodeCodeAuthPlugin.name
291 extern_type = extern_type or auth_rhodecode.RhodeCodeAuthPlugin.name
291 extern_type = extern_type or auth_rhodecode.RhodeCodeAuthPlugin.name
292
292
293 try:
293 try:
294 new_user.username = username
294 new_user.username = username
295 new_user.admin = admin
295 new_user.admin = admin
296 new_user.email = email
296 new_user.email = email
297 new_user.active = active
297 new_user.active = active
298 new_user.extern_name = safe_unicode(extern_name)
298 new_user.extern_name = safe_unicode(extern_name)
299 new_user.extern_type = safe_unicode(extern_type)
299 new_user.extern_type = safe_unicode(extern_type)
300 new_user.name = firstname
300 new_user.name = firstname
301 new_user.lastname = lastname
301 new_user.lastname = lastname
302
302
303 if not edit:
303 if not edit:
304 new_user.api_key = generate_auth_token(username)
304 new_user.api_key = generate_auth_token(username)
305
305
306 # set password only if creating an user or password is changed
306 # set password only if creating an user or password is changed
307 if not edit or _password_change(new_user, password):
307 if not edit or _password_change(new_user, password):
308 reason = 'new password' if edit else 'new user'
308 reason = 'new password' if edit else 'new user'
309 log.debug('Updating password reason=>%s', reason)
309 log.debug('Updating password reason=>%s', reason)
310 new_user.password = get_crypt_password(password) if password else None
310 new_user.password = get_crypt_password(password) if password else None
311
311
312 if force_password_change:
312 if force_password_change:
313 new_user.update_userdata(force_password_change=True)
313 new_user.update_userdata(force_password_change=True)
314 if language:
314 if language:
315 new_user.update_userdata(language=language)
315 new_user.update_userdata(language=language)
316
316
317 self.sa.add(new_user)
317 self.sa.add(new_user)
318
318
319 if not edit and create_repo_group:
319 if not edit and create_repo_group:
320 # create new group same as username, and make this user an owner
320 # create new group same as username, and make this user an owner
321 desc = RepoGroupModel.PERSONAL_GROUP_DESC % {'username': username}
321 desc = RepoGroupModel.PERSONAL_GROUP_DESC % {'username': username}
322 RepoGroupModel().create(group_name=username,
322 RepoGroupModel().create(group_name=username,
323 group_description=desc,
323 group_description=desc,
324 owner=username, commit_early=False)
324 owner=username, commit_early=False)
325 if not edit:
325 if not edit:
326 # add the RSS token
326 # add the RSS token
327 AuthTokenModel().create(username,
327 AuthTokenModel().create(username,
328 description='Generated feed token',
328 description='Generated feed token',
329 role=AuthTokenModel.cls.ROLE_FEED)
329 role=AuthTokenModel.cls.ROLE_FEED)
330 log_create_user(created_by=cur_user, **new_user.get_dict())
330 log_create_user(created_by=cur_user, **new_user.get_dict())
331 return new_user
331 return new_user
332 except (DatabaseError,):
332 except (DatabaseError,):
333 log.error(traceback.format_exc())
333 log.error(traceback.format_exc())
334 raise
334 raise
335
335
336 def create_registration(self, form_data):
336 def create_registration(self, form_data):
337 from rhodecode.model.notification import NotificationModel
337 from rhodecode.model.notification import NotificationModel
338 from rhodecode.model.notification import EmailNotificationModel
338 from rhodecode.model.notification import EmailNotificationModel
339
339
340 try:
340 try:
341 form_data['admin'] = False
341 form_data['admin'] = False
342 form_data['extern_name'] = 'rhodecode'
342 form_data['extern_name'] = 'rhodecode'
343 form_data['extern_type'] = 'rhodecode'
343 form_data['extern_type'] = 'rhodecode'
344 new_user = self.create(form_data)
344 new_user = self.create(form_data)
345
345
346 self.sa.add(new_user)
346 self.sa.add(new_user)
347 self.sa.flush()
347 self.sa.flush()
348
348
349 user_data = new_user.get_dict()
349 user_data = new_user.get_dict()
350 kwargs = {
350 kwargs = {
351 # use SQLALCHEMY safe dump of user data
351 # use SQLALCHEMY safe dump of user data
352 'user': AttributeDict(user_data),
352 'user': AttributeDict(user_data),
353 'date': datetime.datetime.now()
353 'date': datetime.datetime.now()
354 }
354 }
355 notification_type = EmailNotificationModel.TYPE_REGISTRATION
355 notification_type = EmailNotificationModel.TYPE_REGISTRATION
356 # pre-generate the subject for notification itself
356 # pre-generate the subject for notification itself
357 (subject,
357 (subject,
358 _h, _e, # we don't care about those
358 _h, _e, # we don't care about those
359 body_plaintext) = EmailNotificationModel().render_email(
359 body_plaintext) = EmailNotificationModel().render_email(
360 notification_type, **kwargs)
360 notification_type, **kwargs)
361
361
362 # create notification objects, and emails
362 # create notification objects, and emails
363 NotificationModel().create(
363 NotificationModel().create(
364 created_by=new_user,
364 created_by=new_user,
365 notification_subject=subject,
365 notification_subject=subject,
366 notification_body=body_plaintext,
366 notification_body=body_plaintext,
367 notification_type=notification_type,
367 notification_type=notification_type,
368 recipients=None, # all admins
368 recipients=None, # all admins
369 email_kwargs=kwargs,
369 email_kwargs=kwargs,
370 )
370 )
371
371
372 return new_user
372 return new_user
373 except Exception:
373 except Exception:
374 log.error(traceback.format_exc())
374 log.error(traceback.format_exc())
375 raise
375 raise
376
376
377 def _handle_user_repos(self, username, repositories, handle_mode=None):
377 def _handle_user_repos(self, username, repositories, handle_mode=None):
378 _superadmin = self.cls.get_first_admin()
378 _superadmin = self.cls.get_first_admin()
379 left_overs = True
379 left_overs = True
380
380
381 from rhodecode.model.repo import RepoModel
381 from rhodecode.model.repo import RepoModel
382
382
383 if handle_mode == 'detach':
383 if handle_mode == 'detach':
384 for obj in repositories:
384 for obj in repositories:
385 obj.user = _superadmin
385 obj.user = _superadmin
386 # set description we know why we super admin now owns
386 # set description we know why we super admin now owns
387 # additional repositories that were orphaned !
387 # additional repositories that were orphaned !
388 obj.description += ' \n::detached repository from deleted user: %s' % (username,)
388 obj.description += ' \n::detached repository from deleted user: %s' % (username,)
389 self.sa.add(obj)
389 self.sa.add(obj)
390 left_overs = False
390 left_overs = False
391 elif handle_mode == 'delete':
391 elif handle_mode == 'delete':
392 for obj in repositories:
392 for obj in repositories:
393 RepoModel().delete(obj, forks='detach')
393 RepoModel().delete(obj, forks='detach')
394 left_overs = False
394 left_overs = False
395
395
396 # if nothing is done we have left overs left
396 # if nothing is done we have left overs left
397 return left_overs
397 return left_overs
398
398
399 def _handle_user_repo_groups(self, username, repository_groups,
399 def _handle_user_repo_groups(self, username, repository_groups,
400 handle_mode=None):
400 handle_mode=None):
401 _superadmin = self.cls.get_first_admin()
401 _superadmin = self.cls.get_first_admin()
402 left_overs = True
402 left_overs = True
403
403
404 from rhodecode.model.repo_group import RepoGroupModel
404 from rhodecode.model.repo_group import RepoGroupModel
405
405
406 if handle_mode == 'detach':
406 if handle_mode == 'detach':
407 for r in repository_groups:
407 for r in repository_groups:
408 r.user = _superadmin
408 r.user = _superadmin
409 # set description we know why we super admin now owns
409 # set description we know why we super admin now owns
410 # additional repositories that were orphaned !
410 # additional repositories that were orphaned !
411 r.group_description += ' \n::detached repository group from deleted user: %s' % (username,)
411 r.group_description += ' \n::detached repository group from deleted user: %s' % (username,)
412 self.sa.add(r)
412 self.sa.add(r)
413 left_overs = False
413 left_overs = False
414 elif handle_mode == 'delete':
414 elif handle_mode == 'delete':
415 for r in repository_groups:
415 for r in repository_groups:
416 RepoGroupModel().delete(r)
416 RepoGroupModel().delete(r)
417 left_overs = False
417 left_overs = False
418
418
419 # if nothing is done we have left overs left
419 # if nothing is done we have left overs left
420 return left_overs
420 return left_overs
421
421
422 def _handle_user_user_groups(self, username, user_groups, handle_mode=None):
422 def _handle_user_user_groups(self, username, user_groups, handle_mode=None):
423 _superadmin = self.cls.get_first_admin()
423 _superadmin = self.cls.get_first_admin()
424 left_overs = True
424 left_overs = True
425
425
426 from rhodecode.model.user_group import UserGroupModel
426 from rhodecode.model.user_group import UserGroupModel
427
427
428 if handle_mode == 'detach':
428 if handle_mode == 'detach':
429 for r in user_groups:
429 for r in user_groups:
430 for user_user_group_to_perm in r.user_user_group_to_perm:
430 for user_user_group_to_perm in r.user_user_group_to_perm:
431 if user_user_group_to_perm.user.username == username:
431 if user_user_group_to_perm.user.username == username:
432 user_user_group_to_perm.user = _superadmin
432 user_user_group_to_perm.user = _superadmin
433 r.user = _superadmin
433 r.user = _superadmin
434 # set description we know why we super admin now owns
434 # set description we know why we super admin now owns
435 # additional repositories that were orphaned !
435 # additional repositories that were orphaned !
436 r.user_group_description += ' \n::detached user group from deleted user: %s' % (username,)
436 r.user_group_description += ' \n::detached user group from deleted user: %s' % (username,)
437 self.sa.add(r)
437 self.sa.add(r)
438 left_overs = False
438 left_overs = False
439 elif handle_mode == 'delete':
439 elif handle_mode == 'delete':
440 for r in user_groups:
440 for r in user_groups:
441 UserGroupModel().delete(r)
441 UserGroupModel().delete(r)
442 left_overs = False
442 left_overs = False
443
443
444 # if nothing is done we have left overs left
444 # if nothing is done we have left overs left
445 return left_overs
445 return left_overs
446
446
447 def delete(self, user, cur_user=None, handle_repos=None,
447 def delete(self, user, cur_user=None, handle_repos=None,
448 handle_repo_groups=None, handle_user_groups=None):
448 handle_repo_groups=None, handle_user_groups=None):
449 if not cur_user:
449 if not cur_user:
450 cur_user = getattr(get_current_rhodecode_user(), 'username', None)
450 cur_user = getattr(get_current_rhodecode_user(), 'username', None)
451 user = self._get_user(user)
451 user = self._get_user(user)
452
452
453 try:
453 try:
454 if user.username == User.DEFAULT_USER:
454 if user.username == User.DEFAULT_USER:
455 raise DefaultUserException(
455 raise DefaultUserException(
456 _(u"You can't remove this user since it's"
456 _(u"You can't remove this user since it's"
457 u" crucial for entire application"))
457 u" crucial for entire application"))
458
458
459 left_overs = self._handle_user_repos(
459 left_overs = self._handle_user_repos(
460 user.username, user.repositories, handle_repos)
460 user.username, user.repositories, handle_repos)
461 if left_overs and user.repositories:
461 if left_overs and user.repositories:
462 repos = [x.repo_name for x in user.repositories]
462 repos = [x.repo_name for x in user.repositories]
463 raise UserOwnsReposException(
463 raise UserOwnsReposException(
464 _(u'user "%s" still owns %s repositories and cannot be '
464 _(u'user "%s" still owns %s repositories and cannot be '
465 u'removed. Switch owners or remove those repositories:%s')
465 u'removed. Switch owners or remove those repositories:%s')
466 % (user.username, len(repos), ', '.join(repos)))
466 % (user.username, len(repos), ', '.join(repos)))
467
467
468 left_overs = self._handle_user_repo_groups(
468 left_overs = self._handle_user_repo_groups(
469 user.username, user.repository_groups, handle_repo_groups)
469 user.username, user.repository_groups, handle_repo_groups)
470 if left_overs and user.repository_groups:
470 if left_overs and user.repository_groups:
471 repo_groups = [x.group_name for x in user.repository_groups]
471 repo_groups = [x.group_name for x in user.repository_groups]
472 raise UserOwnsRepoGroupsException(
472 raise UserOwnsRepoGroupsException(
473 _(u'user "%s" still owns %s repository groups and cannot be '
473 _(u'user "%s" still owns %s repository groups and cannot be '
474 u'removed. Switch owners or remove those repository groups:%s')
474 u'removed. Switch owners or remove those repository groups:%s')
475 % (user.username, len(repo_groups), ', '.join(repo_groups)))
475 % (user.username, len(repo_groups), ', '.join(repo_groups)))
476
476
477 left_overs = self._handle_user_user_groups(
477 left_overs = self._handle_user_user_groups(
478 user.username, user.user_groups, handle_user_groups)
478 user.username, user.user_groups, handle_user_groups)
479 if left_overs and user.user_groups:
479 if left_overs and user.user_groups:
480 user_groups = [x.users_group_name for x in user.user_groups]
480 user_groups = [x.users_group_name for x in user.user_groups]
481 raise UserOwnsUserGroupsException(
481 raise UserOwnsUserGroupsException(
482 _(u'user "%s" still owns %s user groups and cannot be '
482 _(u'user "%s" still owns %s user groups and cannot be '
483 u'removed. Switch owners or remove those user groups:%s')
483 u'removed. Switch owners or remove those user groups:%s')
484 % (user.username, len(user_groups), ', '.join(user_groups)))
484 % (user.username, len(user_groups), ', '.join(user_groups)))
485
485
486 # we might change the user data with detach/delete, make sure
486 # we might change the user data with detach/delete, make sure
487 # the object is marked as expired before actually deleting !
487 # the object is marked as expired before actually deleting !
488 self.sa.expire(user)
488 self.sa.expire(user)
489 self.sa.delete(user)
489 self.sa.delete(user)
490 from rhodecode.lib.hooks_base import log_delete_user
490 from rhodecode.lib.hooks_base import log_delete_user
491 log_delete_user(deleted_by=cur_user, **user.get_dict())
491 log_delete_user(deleted_by=cur_user, **user.get_dict())
492 except Exception:
492 except Exception:
493 log.error(traceback.format_exc())
493 log.error(traceback.format_exc())
494 raise
494 raise
495
495
496 def reset_password_link(self, data):
496 def reset_password_link(self, data, pwd_reset_url):
497 from rhodecode.lib.celerylib import tasks, run_task
497 from rhodecode.lib.celerylib import tasks, run_task
498 from rhodecode.model.notification import EmailNotificationModel
498 from rhodecode.model.notification import EmailNotificationModel
499 user_email = data['email']
499 user_email = data['email']
500 try:
500 try:
501 user = User.get_by_email(user_email)
501 user = User.get_by_email(user_email)
502 if user:
502 if user:
503 log.debug('password reset user found %s', user)
503 log.debug('password reset user found %s', user)
504
504
505 password_reset_url = url(
506 'reset_password_confirmation', key=user.api_key,
507 qualified=True)
508
509 email_kwargs = {
505 email_kwargs = {
510 'password_reset_url': password_reset_url,
506 'password_reset_url': pwd_reset_url,
511 'user': user,
507 'user': user,
512 'email': user_email,
508 'email': user_email,
513 'date': datetime.datetime.now()
509 'date': datetime.datetime.now()
514 }
510 }
515
511
516 (subject, headers, email_body,
512 (subject, headers, email_body,
517 email_body_plaintext) = EmailNotificationModel().render_email(
513 email_body_plaintext) = EmailNotificationModel().render_email(
518 EmailNotificationModel.TYPE_PASSWORD_RESET, **email_kwargs)
514 EmailNotificationModel.TYPE_PASSWORD_RESET, **email_kwargs)
519
515
520 recipients = [user_email]
516 recipients = [user_email]
521
517
522 action_logger_generic(
518 action_logger_generic(
523 'sending password reset email to user: {}'.format(
519 'sending password reset email to user: {}'.format(
524 user), namespace='security.password_reset')
520 user), namespace='security.password_reset')
525
521
526 run_task(tasks.send_email, recipients, subject,
522 run_task(tasks.send_email, recipients, subject,
527 email_body_plaintext, email_body)
523 email_body_plaintext, email_body)
528
524
529 else:
525 else:
530 log.debug("password reset email %s not found", user_email)
526 log.debug("password reset email %s not found", user_email)
531 except Exception:
527 except Exception:
532 log.error(traceback.format_exc())
528 log.error(traceback.format_exc())
533 return False
529 return False
534
530
535 return True
531 return True
536
532
537 def reset_password(self, data):
533 def reset_password(self, data):
538 from rhodecode.lib.celerylib import tasks, run_task
534 from rhodecode.lib.celerylib import tasks, run_task
539 from rhodecode.model.notification import EmailNotificationModel
535 from rhodecode.model.notification import EmailNotificationModel
540 from rhodecode.lib import auth
536 from rhodecode.lib import auth
541 user_email = data['email']
537 user_email = data['email']
542 pre_db = True
538 pre_db = True
543 try:
539 try:
544 user = User.get_by_email(user_email)
540 user = User.get_by_email(user_email)
545 new_passwd = auth.PasswordGenerator().gen_password(
541 new_passwd = auth.PasswordGenerator().gen_password(
546 12, auth.PasswordGenerator.ALPHABETS_BIG_SMALL)
542 12, auth.PasswordGenerator.ALPHABETS_BIG_SMALL)
547 if user:
543 if user:
548 user.password = auth.get_crypt_password(new_passwd)
544 user.password = auth.get_crypt_password(new_passwd)
549 # also force this user to reset his password !
545 # also force this user to reset his password !
550 user.update_userdata(force_password_change=True)
546 user.update_userdata(force_password_change=True)
551
547
552 Session().add(user)
548 Session().add(user)
553 Session().commit()
549 Session().commit()
554 log.info('change password for %s', user_email)
550 log.info('change password for %s', user_email)
555 if new_passwd is None:
551 if new_passwd is None:
556 raise Exception('unable to generate new password')
552 raise Exception('unable to generate new password')
557
553
558 pre_db = False
554 pre_db = False
559
555
560 email_kwargs = {
556 email_kwargs = {
561 'new_password': new_passwd,
557 'new_password': new_passwd,
562 'user': user,
558 'user': user,
563 'email': user_email,
559 'email': user_email,
564 'date': datetime.datetime.now()
560 'date': datetime.datetime.now()
565 }
561 }
566
562
567 (subject, headers, email_body,
563 (subject, headers, email_body,
568 email_body_plaintext) = EmailNotificationModel().render_email(
564 email_body_plaintext) = EmailNotificationModel().render_email(
569 EmailNotificationModel.TYPE_PASSWORD_RESET_CONFIRMATION, **email_kwargs)
565 EmailNotificationModel.TYPE_PASSWORD_RESET_CONFIRMATION, **email_kwargs)
570
566
571 recipients = [user_email]
567 recipients = [user_email]
572
568
573 action_logger_generic(
569 action_logger_generic(
574 'sent new password to user: {} with email: {}'.format(
570 'sent new password to user: {} with email: {}'.format(
575 user, user_email), namespace='security.password_reset')
571 user, user_email), namespace='security.password_reset')
576
572
577 run_task(tasks.send_email, recipients, subject,
573 run_task(tasks.send_email, recipients, subject,
578 email_body_plaintext, email_body)
574 email_body_plaintext, email_body)
579
575
580 except Exception:
576 except Exception:
581 log.error('Failed to update user password')
577 log.error('Failed to update user password')
582 log.error(traceback.format_exc())
578 log.error(traceback.format_exc())
583 if pre_db:
579 if pre_db:
584 # we rollback only if local db stuff fails. If it goes into
580 # we rollback only if local db stuff fails. If it goes into
585 # run_task, we're pass rollback state this wouldn't work then
581 # run_task, we're pass rollback state this wouldn't work then
586 Session().rollback()
582 Session().rollback()
587
583
588 return True
584 return True
589
585
590 def fill_data(self, auth_user, user_id=None, api_key=None, username=None):
586 def fill_data(self, auth_user, user_id=None, api_key=None, username=None):
591 """
587 """
592 Fetches auth_user by user_id,or api_key if present.
588 Fetches auth_user by user_id,or api_key if present.
593 Fills auth_user attributes with those taken from database.
589 Fills auth_user attributes with those taken from database.
594 Additionally set's is_authenitated if lookup fails
590 Additionally set's is_authenitated if lookup fails
595 present in database
591 present in database
596
592
597 :param auth_user: instance of user to set attributes
593 :param auth_user: instance of user to set attributes
598 :param user_id: user id to fetch by
594 :param user_id: user id to fetch by
599 :param api_key: api key to fetch by
595 :param api_key: api key to fetch by
600 :param username: username to fetch by
596 :param username: username to fetch by
601 """
597 """
602 if user_id is None and api_key is None and username is None:
598 if user_id is None and api_key is None and username is None:
603 raise Exception('You need to pass user_id, api_key or username')
599 raise Exception('You need to pass user_id, api_key or username')
604
600
605 log.debug(
601 log.debug(
606 'doing fill data based on: user_id:%s api_key:%s username:%s',
602 'doing fill data based on: user_id:%s api_key:%s username:%s',
607 user_id, api_key, username)
603 user_id, api_key, username)
608 try:
604 try:
609 dbuser = None
605 dbuser = None
610 if user_id:
606 if user_id:
611 dbuser = self.get(user_id)
607 dbuser = self.get(user_id)
612 elif api_key:
608 elif api_key:
613 dbuser = self.get_by_auth_token(api_key)
609 dbuser = self.get_by_auth_token(api_key)
614 elif username:
610 elif username:
615 dbuser = self.get_by_username(username)
611 dbuser = self.get_by_username(username)
616
612
617 if not dbuser:
613 if not dbuser:
618 log.warning(
614 log.warning(
619 'Unable to lookup user by id:%s api_key:%s username:%s',
615 'Unable to lookup user by id:%s api_key:%s username:%s',
620 user_id, api_key, username)
616 user_id, api_key, username)
621 return False
617 return False
622 if not dbuser.active:
618 if not dbuser.active:
623 log.debug('User `%s` is inactive, skipping fill data', username)
619 log.debug('User `%s` is inactive, skipping fill data', username)
624 return False
620 return False
625
621
626 log.debug('filling user:%s data', dbuser)
622 log.debug('filling user:%s data', dbuser)
627
623
628 # TODO: johbo: Think about this and find a clean solution
624 # TODO: johbo: Think about this and find a clean solution
629 user_data = dbuser.get_dict()
625 user_data = dbuser.get_dict()
630 user_data.update(dbuser.get_api_data(include_secrets=True))
626 user_data.update(dbuser.get_api_data(include_secrets=True))
631
627
632 for k, v in user_data.iteritems():
628 for k, v in user_data.iteritems():
633 # properties of auth user we dont update
629 # properties of auth user we dont update
634 if k not in ['auth_tokens', 'permissions']:
630 if k not in ['auth_tokens', 'permissions']:
635 setattr(auth_user, k, v)
631 setattr(auth_user, k, v)
636
632
637 # few extras
633 # few extras
638 setattr(auth_user, 'feed_token', dbuser.feed_token)
634 setattr(auth_user, 'feed_token', dbuser.feed_token)
639 except Exception:
635 except Exception:
640 log.error(traceback.format_exc())
636 log.error(traceback.format_exc())
641 auth_user.is_authenticated = False
637 auth_user.is_authenticated = False
642 return False
638 return False
643
639
644 return True
640 return True
645
641
646 def has_perm(self, user, perm):
642 def has_perm(self, user, perm):
647 perm = self._get_perm(perm)
643 perm = self._get_perm(perm)
648 user = self._get_user(user)
644 user = self._get_user(user)
649
645
650 return UserToPerm.query().filter(UserToPerm.user == user)\
646 return UserToPerm.query().filter(UserToPerm.user == user)\
651 .filter(UserToPerm.permission == perm).scalar() is not None
647 .filter(UserToPerm.permission == perm).scalar() is not None
652
648
653 def grant_perm(self, user, perm):
649 def grant_perm(self, user, perm):
654 """
650 """
655 Grant user global permissions
651 Grant user global permissions
656
652
657 :param user:
653 :param user:
658 :param perm:
654 :param perm:
659 """
655 """
660 user = self._get_user(user)
656 user = self._get_user(user)
661 perm = self._get_perm(perm)
657 perm = self._get_perm(perm)
662 # if this permission is already granted skip it
658 # if this permission is already granted skip it
663 _perm = UserToPerm.query()\
659 _perm = UserToPerm.query()\
664 .filter(UserToPerm.user == user)\
660 .filter(UserToPerm.user == user)\
665 .filter(UserToPerm.permission == perm)\
661 .filter(UserToPerm.permission == perm)\
666 .scalar()
662 .scalar()
667 if _perm:
663 if _perm:
668 return
664 return
669 new = UserToPerm()
665 new = UserToPerm()
670 new.user = user
666 new.user = user
671 new.permission = perm
667 new.permission = perm
672 self.sa.add(new)
668 self.sa.add(new)
673 return new
669 return new
674
670
675 def revoke_perm(self, user, perm):
671 def revoke_perm(self, user, perm):
676 """
672 """
677 Revoke users global permissions
673 Revoke users global permissions
678
674
679 :param user:
675 :param user:
680 :param perm:
676 :param perm:
681 """
677 """
682 user = self._get_user(user)
678 user = self._get_user(user)
683 perm = self._get_perm(perm)
679 perm = self._get_perm(perm)
684
680
685 obj = UserToPerm.query()\
681 obj = UserToPerm.query()\
686 .filter(UserToPerm.user == user)\
682 .filter(UserToPerm.user == user)\
687 .filter(UserToPerm.permission == perm)\
683 .filter(UserToPerm.permission == perm)\
688 .scalar()
684 .scalar()
689 if obj:
685 if obj:
690 self.sa.delete(obj)
686 self.sa.delete(obj)
691
687
692 def add_extra_email(self, user, email):
688 def add_extra_email(self, user, email):
693 """
689 """
694 Adds email address to UserEmailMap
690 Adds email address to UserEmailMap
695
691
696 :param user:
692 :param user:
697 :param email:
693 :param email:
698 """
694 """
699 from rhodecode.model import forms
695 from rhodecode.model import forms
700 form = forms.UserExtraEmailForm()()
696 form = forms.UserExtraEmailForm()()
701 data = form.to_python({'email': email})
697 data = form.to_python({'email': email})
702 user = self._get_user(user)
698 user = self._get_user(user)
703
699
704 obj = UserEmailMap()
700 obj = UserEmailMap()
705 obj.user = user
701 obj.user = user
706 obj.email = data['email']
702 obj.email = data['email']
707 self.sa.add(obj)
703 self.sa.add(obj)
708 return obj
704 return obj
709
705
710 def delete_extra_email(self, user, email_id):
706 def delete_extra_email(self, user, email_id):
711 """
707 """
712 Removes email address from UserEmailMap
708 Removes email address from UserEmailMap
713
709
714 :param user:
710 :param user:
715 :param email_id:
711 :param email_id:
716 """
712 """
717 user = self._get_user(user)
713 user = self._get_user(user)
718 obj = UserEmailMap.query().get(email_id)
714 obj = UserEmailMap.query().get(email_id)
719 if obj:
715 if obj:
720 self.sa.delete(obj)
716 self.sa.delete(obj)
721
717
722 def parse_ip_range(self, ip_range):
718 def parse_ip_range(self, ip_range):
723 ip_list = []
719 ip_list = []
724 def make_unique(value):
720 def make_unique(value):
725 seen = []
721 seen = []
726 return [c for c in value if not (c in seen or seen.append(c))]
722 return [c for c in value if not (c in seen or seen.append(c))]
727
723
728 # firsts split by commas
724 # firsts split by commas
729 for ip_range in ip_range.split(','):
725 for ip_range in ip_range.split(','):
730 if not ip_range:
726 if not ip_range:
731 continue
727 continue
732 ip_range = ip_range.strip()
728 ip_range = ip_range.strip()
733 if '-' in ip_range:
729 if '-' in ip_range:
734 start_ip, end_ip = ip_range.split('-', 1)
730 start_ip, end_ip = ip_range.split('-', 1)
735 start_ip = ipaddress.ip_address(start_ip.strip())
731 start_ip = ipaddress.ip_address(start_ip.strip())
736 end_ip = ipaddress.ip_address(end_ip.strip())
732 end_ip = ipaddress.ip_address(end_ip.strip())
737 parsed_ip_range = []
733 parsed_ip_range = []
738
734
739 for index in xrange(int(start_ip), int(end_ip) + 1):
735 for index in xrange(int(start_ip), int(end_ip) + 1):
740 new_ip = ipaddress.ip_address(index)
736 new_ip = ipaddress.ip_address(index)
741 parsed_ip_range.append(str(new_ip))
737 parsed_ip_range.append(str(new_ip))
742 ip_list.extend(parsed_ip_range)
738 ip_list.extend(parsed_ip_range)
743 else:
739 else:
744 ip_list.append(ip_range)
740 ip_list.append(ip_range)
745
741
746 return make_unique(ip_list)
742 return make_unique(ip_list)
747
743
748 def add_extra_ip(self, user, ip, description=None):
744 def add_extra_ip(self, user, ip, description=None):
749 """
745 """
750 Adds ip address to UserIpMap
746 Adds ip address to UserIpMap
751
747
752 :param user:
748 :param user:
753 :param ip:
749 :param ip:
754 """
750 """
755 from rhodecode.model import forms
751 from rhodecode.model import forms
756 form = forms.UserExtraIpForm()()
752 form = forms.UserExtraIpForm()()
757 data = form.to_python({'ip': ip})
753 data = form.to_python({'ip': ip})
758 user = self._get_user(user)
754 user = self._get_user(user)
759
755
760 obj = UserIpMap()
756 obj = UserIpMap()
761 obj.user = user
757 obj.user = user
762 obj.ip_addr = data['ip']
758 obj.ip_addr = data['ip']
763 obj.description = description
759 obj.description = description
764 self.sa.add(obj)
760 self.sa.add(obj)
765 return obj
761 return obj
766
762
767 def delete_extra_ip(self, user, ip_id):
763 def delete_extra_ip(self, user, ip_id):
768 """
764 """
769 Removes ip address from UserIpMap
765 Removes ip address from UserIpMap
770
766
771 :param user:
767 :param user:
772 :param ip_id:
768 :param ip_id:
773 """
769 """
774 user = self._get_user(user)
770 user = self._get_user(user)
775 obj = UserIpMap.query().get(ip_id)
771 obj = UserIpMap.query().get(ip_id)
776 if obj:
772 if obj:
777 self.sa.delete(obj)
773 self.sa.delete(obj)
778
774
779 def get_accounts_in_creation_order(self, current_user=None):
775 def get_accounts_in_creation_order(self, current_user=None):
780 """
776 """
781 Get accounts in order of creation for deactivation for license limits
777 Get accounts in order of creation for deactivation for license limits
782
778
783 pick currently logged in user, and append to the list in position 0
779 pick currently logged in user, and append to the list in position 0
784 pick all super-admins in order of creation date and add it to the list
780 pick all super-admins in order of creation date and add it to the list
785 pick all other accounts in order of creation and add it to the list.
781 pick all other accounts in order of creation and add it to the list.
786
782
787 Based on that list, the last accounts can be disabled as they are
783 Based on that list, the last accounts can be disabled as they are
788 created at the end and don't include any of the super admins as well
784 created at the end and don't include any of the super admins as well
789 as the current user.
785 as the current user.
790
786
791 :param current_user: optionally current user running this operation
787 :param current_user: optionally current user running this operation
792 """
788 """
793
789
794 if not current_user:
790 if not current_user:
795 current_user = get_current_rhodecode_user()
791 current_user = get_current_rhodecode_user()
796 active_super_admins = [
792 active_super_admins = [
797 x.user_id for x in User.query()
793 x.user_id for x in User.query()
798 .filter(User.user_id != current_user.user_id)
794 .filter(User.user_id != current_user.user_id)
799 .filter(User.active == true())
795 .filter(User.active == true())
800 .filter(User.admin == true())
796 .filter(User.admin == true())
801 .order_by(User.created_on.asc())]
797 .order_by(User.created_on.asc())]
802
798
803 active_regular_users = [
799 active_regular_users = [
804 x.user_id for x in User.query()
800 x.user_id for x in User.query()
805 .filter(User.user_id != current_user.user_id)
801 .filter(User.user_id != current_user.user_id)
806 .filter(User.active == true())
802 .filter(User.active == true())
807 .filter(User.admin == false())
803 .filter(User.admin == false())
808 .order_by(User.created_on.asc())]
804 .order_by(User.created_on.asc())]
809
805
810 list_of_accounts = [current_user.user_id]
806 list_of_accounts = [current_user.user_id]
811 list_of_accounts += active_super_admins
807 list_of_accounts += active_super_admins
812 list_of_accounts += active_regular_users
808 list_of_accounts += active_regular_users
813
809
814 return list_of_accounts
810 return list_of_accounts
815
811
816 def deactivate_last_users(self, expected_users):
812 def deactivate_last_users(self, expected_users):
817 """
813 """
818 Deactivate accounts that are over the license limits.
814 Deactivate accounts that are over the license limits.
819 Algorithm of which accounts to disabled is based on the formula:
815 Algorithm of which accounts to disabled is based on the formula:
820
816
821 Get current user, then super admins in creation order, then regular
817 Get current user, then super admins in creation order, then regular
822 active users in creation order.
818 active users in creation order.
823
819
824 Using that list we mark all accounts from the end of it as inactive.
820 Using that list we mark all accounts from the end of it as inactive.
825 This way we block only latest created accounts.
821 This way we block only latest created accounts.
826
822
827 :param expected_users: list of users in special order, we deactivate
823 :param expected_users: list of users in special order, we deactivate
828 the end N ammoun of users from that list
824 the end N ammoun of users from that list
829 """
825 """
830
826
831 list_of_accounts = self.get_accounts_in_creation_order()
827 list_of_accounts = self.get_accounts_in_creation_order()
832
828
833 for acc_id in list_of_accounts[expected_users + 1:]:
829 for acc_id in list_of_accounts[expected_users + 1:]:
834 user = User.get(acc_id)
830 user = User.get(acc_id)
835 log.info('Deactivating account %s for license unlock', user)
831 log.info('Deactivating account %s for license unlock', user)
836 user.active = False
832 user.active = False
837 Session().add(user)
833 Session().add(user)
838 Session().commit()
834 Session().commit()
839
835
840 return
836 return
General Comments 0
You need to be logged in to leave comments. Login now