##// END OF EJS Templates
authentication: allow setting extern type with registration....
marcink -
r3255:f80876b0 default
parent child Browse files
Show More
@@ -1,461 +1,477 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 from rhodecode.authentication.plugins import auth_rhodecode
277
276 self.load_default_context()
278 self.load_default_context()
277 captcha = self._get_captcha_data()
279 captcha = self._get_captcha_data()
278 auto_active = 'hg.register.auto_activate' in User.get_default_user()\
280 auto_active = 'hg.register.auto_activate' in User.get_default_user()\
279 .AuthUser().permissions['global']
281 .AuthUser().permissions['global']
280
282
283 extern_name = auth_rhodecode.RhodeCodeAuthPlugin.uid
284 extern_type = auth_rhodecode.RhodeCodeAuthPlugin.uid
285
281 register_form = RegisterForm(self.request.translate)()
286 register_form = RegisterForm(self.request.translate)()
282 try:
287 try:
283
288
284 form_result = register_form.to_python(self.request.POST)
289 form_result = register_form.to_python(self.request.POST)
285 form_result['active'] = auto_active
290 form_result['active'] = auto_active
291 external_identity = self.request.POST.get('external_identity')
292
293 if external_identity:
294 extern_name = external_identity
295 extern_type = external_identity
286
296
287 if captcha.active:
297 if captcha.active:
288 captcha_status, captcha_message = self.validate_captcha(
298 captcha_status, captcha_message = self.validate_captcha(
289 captcha.private_key)
299 captcha.private_key)
290
300
291 if not captcha_status:
301 if not captcha_status:
292 _value = form_result
302 _value = form_result
293 _msg = _('Bad captcha')
303 _msg = _('Bad captcha')
294 error_dict = {'recaptcha_field': captcha_message}
304 error_dict = {'recaptcha_field': captcha_message}
295 raise formencode.Invalid(
305 raise formencode.Invalid(
296 _msg, _value, None, error_dict=error_dict)
306 _msg, _value, None, error_dict=error_dict)
297
307
298 new_user = UserModel().create_registration(form_result)
308 new_user = UserModel().create_registration(
309 form_result, extern_name=extern_name, extern_type=extern_type)
299
310
300 action_data = {'data': new_user.get_api_data(),
311 action_data = {'data': new_user.get_api_data(),
301 'user_agent': self.request.user_agent}
312 'user_agent': self.request.user_agent}
302
313
314
315
316 if external_identity:
317 action_data['external_identity'] = external_identity
318
303 audit_user = audit_logger.UserWrap(
319 audit_user = audit_logger.UserWrap(
304 username=new_user.username,
320 username=new_user.username,
305 user_id=new_user.user_id,
321 user_id=new_user.user_id,
306 ip_addr=self.request.remote_addr)
322 ip_addr=self.request.remote_addr)
307
323
308 audit_logger.store_web(
324 audit_logger.store_web(
309 'user.register', action_data=action_data,
325 'user.register', action_data=action_data,
310 user=audit_user)
326 user=audit_user)
311
327
312 event = UserRegistered(user=new_user, session=self.session)
328 event = UserRegistered(user=new_user, session=self.session)
313 trigger(event)
329 trigger(event)
314 h.flash(
330 h.flash(
315 _('You have successfully registered with RhodeCode'),
331 _('You have successfully registered with RhodeCode'),
316 category='success')
332 category='success')
317 Session().commit()
333 Session().commit()
318
334
319 redirect_ro = self.request.route_path('login')
335 redirect_ro = self.request.route_path('login')
320 raise HTTPFound(redirect_ro)
336 raise HTTPFound(redirect_ro)
321
337
322 except formencode.Invalid as errors:
338 except formencode.Invalid as errors:
323 errors.value.pop('password', None)
339 errors.value.pop('password', None)
324 errors.value.pop('password_confirmation', None)
340 errors.value.pop('password_confirmation', None)
325 return self.register(
341 return self.register(
326 defaults=errors.value, errors=errors.error_dict)
342 defaults=errors.value, errors=errors.error_dict)
327
343
328 except UserCreationError as e:
344 except UserCreationError as e:
329 # container auth or other auth functions that create users on
345 # container auth or other auth functions that create users on
330 # the fly can throw this exception signaling that there's issue
346 # the fly can throw this exception signaling that there's issue
331 # with user creation, explanation should be provided in
347 # with user creation, explanation should be provided in
332 # Exception itself
348 # Exception itself
333 h.flash(e, category='error')
349 h.flash(e, category='error')
334 return self.register()
350 return self.register()
335
351
336 @view_config(
352 @view_config(
337 route_name='reset_password', request_method=('GET', 'POST'),
353 route_name='reset_password', request_method=('GET', 'POST'),
338 renderer='rhodecode:templates/password_reset.mako')
354 renderer='rhodecode:templates/password_reset.mako')
339 def password_reset(self):
355 def password_reset(self):
340 c = self.load_default_context()
356 c = self.load_default_context()
341 captcha = self._get_captcha_data()
357 captcha = self._get_captcha_data()
342
358
343 template_context = {
359 template_context = {
344 'captcha_active': captcha.active,
360 'captcha_active': captcha.active,
345 'captcha_public_key': captcha.public_key,
361 'captcha_public_key': captcha.public_key,
346 'defaults': {},
362 'defaults': {},
347 'errors': {},
363 'errors': {},
348 }
364 }
349
365
350 # always send implicit message to prevent from discovery of
366 # always send implicit message to prevent from discovery of
351 # matching emails
367 # matching emails
352 msg = _('If such email exists, a password reset link was sent to it.')
368 msg = _('If such email exists, a password reset link was sent to it.')
353
369
354 if self.request.POST:
370 if self.request.POST:
355 if h.HasPermissionAny('hg.password_reset.disabled')():
371 if h.HasPermissionAny('hg.password_reset.disabled')():
356 _email = self.request.POST.get('email', '')
372 _email = self.request.POST.get('email', '')
357 log.error('Failed attempt to reset password for `%s`.', _email)
373 log.error('Failed attempt to reset password for `%s`.', _email)
358 h.flash(_('Password reset has been disabled.'),
374 h.flash(_('Password reset has been disabled.'),
359 category='error')
375 category='error')
360 return HTTPFound(self.request.route_path('reset_password'))
376 return HTTPFound(self.request.route_path('reset_password'))
361
377
362 password_reset_form = PasswordResetForm(self.request.translate)()
378 password_reset_form = PasswordResetForm(self.request.translate)()
363 try:
379 try:
364 form_result = password_reset_form.to_python(
380 form_result = password_reset_form.to_python(
365 self.request.POST)
381 self.request.POST)
366 user_email = form_result['email']
382 user_email = form_result['email']
367
383
368 if captcha.active:
384 if captcha.active:
369 captcha_status, captcha_message = self.validate_captcha(
385 captcha_status, captcha_message = self.validate_captcha(
370 captcha.private_key)
386 captcha.private_key)
371
387
372 if not captcha_status:
388 if not captcha_status:
373 _value = form_result
389 _value = form_result
374 _msg = _('Bad captcha')
390 _msg = _('Bad captcha')
375 error_dict = {'recaptcha_field': captcha_message}
391 error_dict = {'recaptcha_field': captcha_message}
376 raise formencode.Invalid(
392 raise formencode.Invalid(
377 _msg, _value, None, error_dict=error_dict)
393 _msg, _value, None, error_dict=error_dict)
378
394
379 # Generate reset URL and send mail.
395 # Generate reset URL and send mail.
380 user = User.get_by_email(user_email)
396 user = User.get_by_email(user_email)
381
397
382 # generate password reset token that expires in 10 minutes
398 # generate password reset token that expires in 10 minutes
383 description = u'Generated token for password reset from {}'.format(
399 description = u'Generated token for password reset from {}'.format(
384 datetime.datetime.now().isoformat())
400 datetime.datetime.now().isoformat())
385
401
386 reset_token = UserModel().add_auth_token(
402 reset_token = UserModel().add_auth_token(
387 user=user, lifetime_minutes=10,
403 user=user, lifetime_minutes=10,
388 role=UserModel.auth_token_role.ROLE_PASSWORD_RESET,
404 role=UserModel.auth_token_role.ROLE_PASSWORD_RESET,
389 description=description)
405 description=description)
390 Session().commit()
406 Session().commit()
391
407
392 log.debug('Successfully created password recovery token')
408 log.debug('Successfully created password recovery token')
393 password_reset_url = self.request.route_url(
409 password_reset_url = self.request.route_url(
394 'reset_password_confirmation',
410 'reset_password_confirmation',
395 _query={'key': reset_token.api_key})
411 _query={'key': reset_token.api_key})
396 UserModel().reset_password_link(
412 UserModel().reset_password_link(
397 form_result, password_reset_url)
413 form_result, password_reset_url)
398 # Display success message and redirect.
414 # Display success message and redirect.
399 h.flash(msg, category='success')
415 h.flash(msg, category='success')
400
416
401 action_data = {'email': user_email,
417 action_data = {'email': user_email,
402 'user_agent': self.request.user_agent}
418 'user_agent': self.request.user_agent}
403 audit_logger.store_web(
419 audit_logger.store_web(
404 'user.password.reset_request', action_data=action_data,
420 'user.password.reset_request', action_data=action_data,
405 user=self._rhodecode_user, commit=True)
421 user=self._rhodecode_user, commit=True)
406 return HTTPFound(self.request.route_path('reset_password'))
422 return HTTPFound(self.request.route_path('reset_password'))
407
423
408 except formencode.Invalid as errors:
424 except formencode.Invalid as errors:
409 template_context.update({
425 template_context.update({
410 'defaults': errors.value,
426 'defaults': errors.value,
411 'errors': errors.error_dict,
427 'errors': errors.error_dict,
412 })
428 })
413 if not self.request.POST.get('email'):
429 if not self.request.POST.get('email'):
414 # case of empty email, we want to report that
430 # case of empty email, we want to report that
415 return self._get_template_context(c, **template_context)
431 return self._get_template_context(c, **template_context)
416
432
417 if 'recaptcha_field' in errors.error_dict:
433 if 'recaptcha_field' in errors.error_dict:
418 # case of failed captcha
434 # case of failed captcha
419 return self._get_template_context(c, **template_context)
435 return self._get_template_context(c, **template_context)
420
436
421 log.debug('faking response on invalid password reset')
437 log.debug('faking response on invalid password reset')
422 # make this take 2s, to prevent brute forcing.
438 # make this take 2s, to prevent brute forcing.
423 time.sleep(2)
439 time.sleep(2)
424 h.flash(msg, category='success')
440 h.flash(msg, category='success')
425 return HTTPFound(self.request.route_path('reset_password'))
441 return HTTPFound(self.request.route_path('reset_password'))
426
442
427 return self._get_template_context(c, **template_context)
443 return self._get_template_context(c, **template_context)
428
444
429 @view_config(route_name='reset_password_confirmation',
445 @view_config(route_name='reset_password_confirmation',
430 request_method='GET')
446 request_method='GET')
431 def password_reset_confirmation(self):
447 def password_reset_confirmation(self):
432 self.load_default_context()
448 self.load_default_context()
433 if self.request.GET and self.request.GET.get('key'):
449 if self.request.GET and self.request.GET.get('key'):
434 # make this take 2s, to prevent brute forcing.
450 # make this take 2s, to prevent brute forcing.
435 time.sleep(2)
451 time.sleep(2)
436
452
437 token = AuthTokenModel().get_auth_token(
453 token = AuthTokenModel().get_auth_token(
438 self.request.GET.get('key'))
454 self.request.GET.get('key'))
439
455
440 # verify token is the correct role
456 # verify token is the correct role
441 if token is None or token.role != UserApiKeys.ROLE_PASSWORD_RESET:
457 if token is None or token.role != UserApiKeys.ROLE_PASSWORD_RESET:
442 log.debug('Got token with role:%s expected is %s',
458 log.debug('Got token with role:%s expected is %s',
443 getattr(token, 'role', 'EMPTY_TOKEN'),
459 getattr(token, 'role', 'EMPTY_TOKEN'),
444 UserApiKeys.ROLE_PASSWORD_RESET)
460 UserApiKeys.ROLE_PASSWORD_RESET)
445 h.flash(
461 h.flash(
446 _('Given reset token is invalid'), category='error')
462 _('Given reset token is invalid'), category='error')
447 return HTTPFound(self.request.route_path('reset_password'))
463 return HTTPFound(self.request.route_path('reset_password'))
448
464
449 try:
465 try:
450 owner = token.user
466 owner = token.user
451 data = {'email': owner.email, 'token': token.api_key}
467 data = {'email': owner.email, 'token': token.api_key}
452 UserModel().reset_password(data)
468 UserModel().reset_password(data)
453 h.flash(
469 h.flash(
454 _('Your password reset was successful, '
470 _('Your password reset was successful, '
455 'a new password has been sent to your email'),
471 'a new password has been sent to your email'),
456 category='success')
472 category='success')
457 except Exception as e:
473 except Exception as e:
458 log.error(e)
474 log.error(e)
459 return HTTPFound(self.request.route_path('reset_password'))
475 return HTTPFound(self.request.route_path('reset_password'))
460
476
461 return HTTPFound(self.request.route_path('login'))
477 return HTTPFound(self.request.route_path('login'))
@@ -1,941 +1,942 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.uid
348 extern_type = extern_type or auth_rhodecode.RhodeCodeAuthPlugin.name
348 extern_type = extern_type or auth_rhodecode.RhodeCodeAuthPlugin.uid
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 self.add_auth_token(
380 self.add_auth_token(
381 user=username, lifetime_minutes=-1,
381 user=username, lifetime_minutes=-1,
382 role=self.auth_token_role.ROLE_FEED,
382 role=self.auth_token_role.ROLE_FEED,
383 description=u'Generated feed token')
383 description=u'Generated feed token')
384
384
385 kwargs = new_user.get_dict()
385 kwargs = new_user.get_dict()
386 # backward compat, require api_keys present
386 # backward compat, require api_keys present
387 kwargs['api_keys'] = kwargs['auth_tokens']
387 kwargs['api_keys'] = kwargs['auth_tokens']
388 log_create_user(created_by=cur_user, **kwargs)
388 log_create_user(created_by=cur_user, **kwargs)
389 events.trigger(events.UserPostCreate(user_data))
389 events.trigger(events.UserPostCreate(user_data))
390 return new_user
390 return new_user
391 except (DatabaseError,):
391 except (DatabaseError,):
392 log.error(traceback.format_exc())
392 log.error(traceback.format_exc())
393 raise
393 raise
394
394
395 def create_registration(self, form_data):
395 def create_registration(self, form_data,
396 extern_name='rhodecode', extern_type='rhodecode'):
396 from rhodecode.model.notification import NotificationModel
397 from rhodecode.model.notification import NotificationModel
397 from rhodecode.model.notification import EmailNotificationModel
398 from rhodecode.model.notification import EmailNotificationModel
398
399
399 try:
400 try:
400 form_data['admin'] = False
401 form_data['admin'] = False
401 form_data['extern_name'] = 'rhodecode'
402 form_data['extern_name'] = extern_name
402 form_data['extern_type'] = 'rhodecode'
403 form_data['extern_type'] = extern_type
403 new_user = self.create(form_data)
404 new_user = self.create(form_data)
404
405
405 self.sa.add(new_user)
406 self.sa.add(new_user)
406 self.sa.flush()
407 self.sa.flush()
407
408
408 user_data = new_user.get_dict()
409 user_data = new_user.get_dict()
409 kwargs = {
410 kwargs = {
410 # use SQLALCHEMY safe dump of user data
411 # use SQLALCHEMY safe dump of user data
411 'user': AttributeDict(user_data),
412 'user': AttributeDict(user_data),
412 'date': datetime.datetime.now()
413 'date': datetime.datetime.now()
413 }
414 }
414 notification_type = EmailNotificationModel.TYPE_REGISTRATION
415 notification_type = EmailNotificationModel.TYPE_REGISTRATION
415 # pre-generate the subject for notification itself
416 # pre-generate the subject for notification itself
416 (subject,
417 (subject,
417 _h, _e, # we don't care about those
418 _h, _e, # we don't care about those
418 body_plaintext) = EmailNotificationModel().render_email(
419 body_plaintext) = EmailNotificationModel().render_email(
419 notification_type, **kwargs)
420 notification_type, **kwargs)
420
421
421 # create notification objects, and emails
422 # create notification objects, and emails
422 NotificationModel().create(
423 NotificationModel().create(
423 created_by=new_user,
424 created_by=new_user,
424 notification_subject=subject,
425 notification_subject=subject,
425 notification_body=body_plaintext,
426 notification_body=body_plaintext,
426 notification_type=notification_type,
427 notification_type=notification_type,
427 recipients=None, # all admins
428 recipients=None, # all admins
428 email_kwargs=kwargs,
429 email_kwargs=kwargs,
429 )
430 )
430
431
431 return new_user
432 return new_user
432 except Exception:
433 except Exception:
433 log.error(traceback.format_exc())
434 log.error(traceback.format_exc())
434 raise
435 raise
435
436
436 def _handle_user_repos(self, username, repositories, handle_mode=None):
437 def _handle_user_repos(self, username, repositories, handle_mode=None):
437 _superadmin = self.cls.get_first_super_admin()
438 _superadmin = self.cls.get_first_super_admin()
438 left_overs = True
439 left_overs = True
439
440
440 from rhodecode.model.repo import RepoModel
441 from rhodecode.model.repo import RepoModel
441
442
442 if handle_mode == 'detach':
443 if handle_mode == 'detach':
443 for obj in repositories:
444 for obj in repositories:
444 obj.user = _superadmin
445 obj.user = _superadmin
445 # set description we know why we super admin now owns
446 # set description we know why we super admin now owns
446 # additional repositories that were orphaned !
447 # additional repositories that were orphaned !
447 obj.description += ' \n::detached repository from deleted user: %s' % (username,)
448 obj.description += ' \n::detached repository from deleted user: %s' % (username,)
448 self.sa.add(obj)
449 self.sa.add(obj)
449 left_overs = False
450 left_overs = False
450 elif handle_mode == 'delete':
451 elif handle_mode == 'delete':
451 for obj in repositories:
452 for obj in repositories:
452 RepoModel().delete(obj, forks='detach')
453 RepoModel().delete(obj, forks='detach')
453 left_overs = False
454 left_overs = False
454
455
455 # if nothing is done we have left overs left
456 # if nothing is done we have left overs left
456 return left_overs
457 return left_overs
457
458
458 def _handle_user_repo_groups(self, username, repository_groups,
459 def _handle_user_repo_groups(self, username, repository_groups,
459 handle_mode=None):
460 handle_mode=None):
460 _superadmin = self.cls.get_first_super_admin()
461 _superadmin = self.cls.get_first_super_admin()
461 left_overs = True
462 left_overs = True
462
463
463 from rhodecode.model.repo_group import RepoGroupModel
464 from rhodecode.model.repo_group import RepoGroupModel
464
465
465 if handle_mode == 'detach':
466 if handle_mode == 'detach':
466 for r in repository_groups:
467 for r in repository_groups:
467 r.user = _superadmin
468 r.user = _superadmin
468 # set description we know why we super admin now owns
469 # set description we know why we super admin now owns
469 # additional repositories that were orphaned !
470 # additional repositories that were orphaned !
470 r.group_description += ' \n::detached repository group from deleted user: %s' % (username,)
471 r.group_description += ' \n::detached repository group from deleted user: %s' % (username,)
471 r.personal = False
472 r.personal = False
472 self.sa.add(r)
473 self.sa.add(r)
473 left_overs = False
474 left_overs = False
474 elif handle_mode == 'delete':
475 elif handle_mode == 'delete':
475 for r in repository_groups:
476 for r in repository_groups:
476 RepoGroupModel().delete(r)
477 RepoGroupModel().delete(r)
477 left_overs = False
478 left_overs = False
478
479
479 # if nothing is done we have left overs left
480 # if nothing is done we have left overs left
480 return left_overs
481 return left_overs
481
482
482 def _handle_user_user_groups(self, username, user_groups, handle_mode=None):
483 def _handle_user_user_groups(self, username, user_groups, handle_mode=None):
483 _superadmin = self.cls.get_first_super_admin()
484 _superadmin = self.cls.get_first_super_admin()
484 left_overs = True
485 left_overs = True
485
486
486 from rhodecode.model.user_group import UserGroupModel
487 from rhodecode.model.user_group import UserGroupModel
487
488
488 if handle_mode == 'detach':
489 if handle_mode == 'detach':
489 for r in user_groups:
490 for r in user_groups:
490 for user_user_group_to_perm in r.user_user_group_to_perm:
491 for user_user_group_to_perm in r.user_user_group_to_perm:
491 if user_user_group_to_perm.user.username == username:
492 if user_user_group_to_perm.user.username == username:
492 user_user_group_to_perm.user = _superadmin
493 user_user_group_to_perm.user = _superadmin
493 r.user = _superadmin
494 r.user = _superadmin
494 # set description we know why we super admin now owns
495 # set description we know why we super admin now owns
495 # additional repositories that were orphaned !
496 # additional repositories that were orphaned !
496 r.user_group_description += ' \n::detached user group from deleted user: %s' % (username,)
497 r.user_group_description += ' \n::detached user group from deleted user: %s' % (username,)
497 self.sa.add(r)
498 self.sa.add(r)
498 left_overs = False
499 left_overs = False
499 elif handle_mode == 'delete':
500 elif handle_mode == 'delete':
500 for r in user_groups:
501 for r in user_groups:
501 UserGroupModel().delete(r)
502 UserGroupModel().delete(r)
502 left_overs = False
503 left_overs = False
503
504
504 # if nothing is done we have left overs left
505 # if nothing is done we have left overs left
505 return left_overs
506 return left_overs
506
507
507 def delete(self, user, cur_user=None, handle_repos=None,
508 def delete(self, user, cur_user=None, handle_repos=None,
508 handle_repo_groups=None, handle_user_groups=None):
509 handle_repo_groups=None, handle_user_groups=None):
509 if not cur_user:
510 if not cur_user:
510 cur_user = getattr(
511 cur_user = getattr(
511 get_current_rhodecode_user(), 'username', None)
512 get_current_rhodecode_user(), 'username', None)
512 user = self._get_user(user)
513 user = self._get_user(user)
513
514
514 try:
515 try:
515 if user.username == User.DEFAULT_USER:
516 if user.username == User.DEFAULT_USER:
516 raise DefaultUserException(
517 raise DefaultUserException(
517 u"You can't remove this user since it's"
518 u"You can't remove this user since it's"
518 u" crucial for entire application")
519 u" crucial for entire application")
519
520
520 left_overs = self._handle_user_repos(
521 left_overs = self._handle_user_repos(
521 user.username, user.repositories, handle_repos)
522 user.username, user.repositories, handle_repos)
522 if left_overs and user.repositories:
523 if left_overs and user.repositories:
523 repos = [x.repo_name for x in user.repositories]
524 repos = [x.repo_name for x in user.repositories]
524 raise UserOwnsReposException(
525 raise UserOwnsReposException(
525 u'user "%(username)s" still owns %(len_repos)s repositories and cannot be '
526 u'user "%(username)s" still owns %(len_repos)s repositories and cannot be '
526 u'removed. Switch owners or remove those repositories:%(list_repos)s'
527 u'removed. Switch owners or remove those repositories:%(list_repos)s'
527 % {'username': user.username, 'len_repos': len(repos),
528 % {'username': user.username, 'len_repos': len(repos),
528 'list_repos': ', '.join(repos)})
529 'list_repos': ', '.join(repos)})
529
530
530 left_overs = self._handle_user_repo_groups(
531 left_overs = self._handle_user_repo_groups(
531 user.username, user.repository_groups, handle_repo_groups)
532 user.username, user.repository_groups, handle_repo_groups)
532 if left_overs and user.repository_groups:
533 if left_overs and user.repository_groups:
533 repo_groups = [x.group_name for x in user.repository_groups]
534 repo_groups = [x.group_name for x in user.repository_groups]
534 raise UserOwnsRepoGroupsException(
535 raise UserOwnsRepoGroupsException(
535 u'user "%(username)s" still owns %(len_repo_groups)s repository groups and cannot be '
536 u'user "%(username)s" still owns %(len_repo_groups)s repository groups and cannot be '
536 u'removed. Switch owners or remove those repository groups:%(list_repo_groups)s'
537 u'removed. Switch owners or remove those repository groups:%(list_repo_groups)s'
537 % {'username': user.username, 'len_repo_groups': len(repo_groups),
538 % {'username': user.username, 'len_repo_groups': len(repo_groups),
538 'list_repo_groups': ', '.join(repo_groups)})
539 'list_repo_groups': ', '.join(repo_groups)})
539
540
540 left_overs = self._handle_user_user_groups(
541 left_overs = self._handle_user_user_groups(
541 user.username, user.user_groups, handle_user_groups)
542 user.username, user.user_groups, handle_user_groups)
542 if left_overs and user.user_groups:
543 if left_overs and user.user_groups:
543 user_groups = [x.users_group_name for x in user.user_groups]
544 user_groups = [x.users_group_name for x in user.user_groups]
544 raise UserOwnsUserGroupsException(
545 raise UserOwnsUserGroupsException(
545 u'user "%s" still owns %s user groups and cannot be '
546 u'user "%s" still owns %s user groups and cannot be '
546 u'removed. Switch owners or remove those user groups:%s'
547 u'removed. Switch owners or remove those user groups:%s'
547 % (user.username, len(user_groups), ', '.join(user_groups)))
548 % (user.username, len(user_groups), ', '.join(user_groups)))
548
549
549 # we might change the user data with detach/delete, make sure
550 # we might change the user data with detach/delete, make sure
550 # the object is marked as expired before actually deleting !
551 # the object is marked as expired before actually deleting !
551 self.sa.expire(user)
552 self.sa.expire(user)
552 self.sa.delete(user)
553 self.sa.delete(user)
553 from rhodecode.lib.hooks_base import log_delete_user
554 from rhodecode.lib.hooks_base import log_delete_user
554 log_delete_user(deleted_by=cur_user, **user.get_dict())
555 log_delete_user(deleted_by=cur_user, **user.get_dict())
555 except Exception:
556 except Exception:
556 log.error(traceback.format_exc())
557 log.error(traceback.format_exc())
557 raise
558 raise
558
559
559 def reset_password_link(self, data, pwd_reset_url):
560 def reset_password_link(self, data, pwd_reset_url):
560 from rhodecode.lib.celerylib import tasks, run_task
561 from rhodecode.lib.celerylib import tasks, run_task
561 from rhodecode.model.notification import EmailNotificationModel
562 from rhodecode.model.notification import EmailNotificationModel
562 user_email = data['email']
563 user_email = data['email']
563 try:
564 try:
564 user = User.get_by_email(user_email)
565 user = User.get_by_email(user_email)
565 if user:
566 if user:
566 log.debug('password reset user found %s', user)
567 log.debug('password reset user found %s', user)
567
568
568 email_kwargs = {
569 email_kwargs = {
569 'password_reset_url': pwd_reset_url,
570 'password_reset_url': pwd_reset_url,
570 'user': user,
571 'user': user,
571 'email': user_email,
572 'email': user_email,
572 'date': datetime.datetime.now()
573 'date': datetime.datetime.now()
573 }
574 }
574
575
575 (subject, headers, email_body,
576 (subject, headers, email_body,
576 email_body_plaintext) = EmailNotificationModel().render_email(
577 email_body_plaintext) = EmailNotificationModel().render_email(
577 EmailNotificationModel.TYPE_PASSWORD_RESET, **email_kwargs)
578 EmailNotificationModel.TYPE_PASSWORD_RESET, **email_kwargs)
578
579
579 recipients = [user_email]
580 recipients = [user_email]
580
581
581 action_logger_generic(
582 action_logger_generic(
582 'sending password reset email to user: {}'.format(
583 'sending password reset email to user: {}'.format(
583 user), namespace='security.password_reset')
584 user), namespace='security.password_reset')
584
585
585 run_task(tasks.send_email, recipients, subject,
586 run_task(tasks.send_email, recipients, subject,
586 email_body_plaintext, email_body)
587 email_body_plaintext, email_body)
587
588
588 else:
589 else:
589 log.debug("password reset email %s not found", user_email)
590 log.debug("password reset email %s not found", user_email)
590 except Exception:
591 except Exception:
591 log.error(traceback.format_exc())
592 log.error(traceback.format_exc())
592 return False
593 return False
593
594
594 return True
595 return True
595
596
596 def reset_password(self, data):
597 def reset_password(self, data):
597 from rhodecode.lib.celerylib import tasks, run_task
598 from rhodecode.lib.celerylib import tasks, run_task
598 from rhodecode.model.notification import EmailNotificationModel
599 from rhodecode.model.notification import EmailNotificationModel
599 from rhodecode.lib import auth
600 from rhodecode.lib import auth
600 user_email = data['email']
601 user_email = data['email']
601 pre_db = True
602 pre_db = True
602 try:
603 try:
603 user = User.get_by_email(user_email)
604 user = User.get_by_email(user_email)
604 new_passwd = auth.PasswordGenerator().gen_password(
605 new_passwd = auth.PasswordGenerator().gen_password(
605 12, auth.PasswordGenerator.ALPHABETS_BIG_SMALL)
606 12, auth.PasswordGenerator.ALPHABETS_BIG_SMALL)
606 if user:
607 if user:
607 user.password = auth.get_crypt_password(new_passwd)
608 user.password = auth.get_crypt_password(new_passwd)
608 # also force this user to reset his password !
609 # also force this user to reset his password !
609 user.update_userdata(force_password_change=True)
610 user.update_userdata(force_password_change=True)
610
611
611 Session().add(user)
612 Session().add(user)
612
613
613 # now delete the token in question
614 # now delete the token in question
614 UserApiKeys = AuthTokenModel.cls
615 UserApiKeys = AuthTokenModel.cls
615 UserApiKeys().query().filter(
616 UserApiKeys().query().filter(
616 UserApiKeys.api_key == data['token']).delete()
617 UserApiKeys.api_key == data['token']).delete()
617
618
618 Session().commit()
619 Session().commit()
619 log.info('successfully reset password for `%s`', user_email)
620 log.info('successfully reset password for `%s`', user_email)
620
621
621 if new_passwd is None:
622 if new_passwd is None:
622 raise Exception('unable to generate new password')
623 raise Exception('unable to generate new password')
623
624
624 pre_db = False
625 pre_db = False
625
626
626 email_kwargs = {
627 email_kwargs = {
627 'new_password': new_passwd,
628 'new_password': new_passwd,
628 'user': user,
629 'user': user,
629 'email': user_email,
630 'email': user_email,
630 'date': datetime.datetime.now()
631 'date': datetime.datetime.now()
631 }
632 }
632
633
633 (subject, headers, email_body,
634 (subject, headers, email_body,
634 email_body_plaintext) = EmailNotificationModel().render_email(
635 email_body_plaintext) = EmailNotificationModel().render_email(
635 EmailNotificationModel.TYPE_PASSWORD_RESET_CONFIRMATION,
636 EmailNotificationModel.TYPE_PASSWORD_RESET_CONFIRMATION,
636 **email_kwargs)
637 **email_kwargs)
637
638
638 recipients = [user_email]
639 recipients = [user_email]
639
640
640 action_logger_generic(
641 action_logger_generic(
641 'sent new password to user: {} with email: {}'.format(
642 'sent new password to user: {} with email: {}'.format(
642 user, user_email), namespace='security.password_reset')
643 user, user_email), namespace='security.password_reset')
643
644
644 run_task(tasks.send_email, recipients, subject,
645 run_task(tasks.send_email, recipients, subject,
645 email_body_plaintext, email_body)
646 email_body_plaintext, email_body)
646
647
647 except Exception:
648 except Exception:
648 log.error('Failed to update user password')
649 log.error('Failed to update user password')
649 log.error(traceback.format_exc())
650 log.error(traceback.format_exc())
650 if pre_db:
651 if pre_db:
651 # we rollback only if local db stuff fails. If it goes into
652 # we rollback only if local db stuff fails. If it goes into
652 # run_task, we're pass rollback state this wouldn't work then
653 # run_task, we're pass rollback state this wouldn't work then
653 Session().rollback()
654 Session().rollback()
654
655
655 return True
656 return True
656
657
657 def fill_data(self, auth_user, user_id=None, api_key=None, username=None):
658 def fill_data(self, auth_user, user_id=None, api_key=None, username=None):
658 """
659 """
659 Fetches auth_user by user_id,or api_key if present.
660 Fetches auth_user by user_id,or api_key if present.
660 Fills auth_user attributes with those taken from database.
661 Fills auth_user attributes with those taken from database.
661 Additionally set's is_authenitated if lookup fails
662 Additionally set's is_authenitated if lookup fails
662 present in database
663 present in database
663
664
664 :param auth_user: instance of user to set attributes
665 :param auth_user: instance of user to set attributes
665 :param user_id: user id to fetch by
666 :param user_id: user id to fetch by
666 :param api_key: api key to fetch by
667 :param api_key: api key to fetch by
667 :param username: username to fetch by
668 :param username: username to fetch by
668 """
669 """
669 def token_obfuscate(token):
670 def token_obfuscate(token):
670 if token:
671 if token:
671 return token[:4] + "****"
672 return token[:4] + "****"
672
673
673 if user_id is None and api_key is None and username is None:
674 if user_id is None and api_key is None and username is None:
674 raise Exception('You need to pass user_id, api_key or username')
675 raise Exception('You need to pass user_id, api_key or username')
675
676
676 log.debug(
677 log.debug(
677 'AuthUser: fill data execution based on: '
678 'AuthUser: fill data execution based on: '
678 'user_id:%s api_key:%s username:%s', user_id, api_key, username)
679 'user_id:%s api_key:%s username:%s', user_id, api_key, username)
679 try:
680 try:
680 dbuser = None
681 dbuser = None
681 if user_id:
682 if user_id:
682 dbuser = self.get(user_id)
683 dbuser = self.get(user_id)
683 elif api_key:
684 elif api_key:
684 dbuser = self.get_by_auth_token(api_key)
685 dbuser = self.get_by_auth_token(api_key)
685 elif username:
686 elif username:
686 dbuser = self.get_by_username(username)
687 dbuser = self.get_by_username(username)
687
688
688 if not dbuser:
689 if not dbuser:
689 log.warning(
690 log.warning(
690 'Unable to lookup user by id:%s api_key:%s username:%s',
691 'Unable to lookup user by id:%s api_key:%s username:%s',
691 user_id, token_obfuscate(api_key), username)
692 user_id, token_obfuscate(api_key), username)
692 return False
693 return False
693 if not dbuser.active:
694 if not dbuser.active:
694 log.debug('User `%s:%s` is inactive, skipping fill data',
695 log.debug('User `%s:%s` is inactive, skipping fill data',
695 username, user_id)
696 username, user_id)
696 return False
697 return False
697
698
698 log.debug('AuthUser: filling found user:%s data', dbuser)
699 log.debug('AuthUser: filling found user:%s data', dbuser)
699 user_data = dbuser.get_dict()
700 user_data = dbuser.get_dict()
700
701
701 user_data.update({
702 user_data.update({
702 # set explicit the safe escaped values
703 # set explicit the safe escaped values
703 'first_name': dbuser.first_name,
704 'first_name': dbuser.first_name,
704 'last_name': dbuser.last_name,
705 'last_name': dbuser.last_name,
705 })
706 })
706
707
707 for k, v in user_data.items():
708 for k, v in user_data.items():
708 # properties of auth user we dont update
709 # properties of auth user we dont update
709 if k not in ['auth_tokens', 'permissions']:
710 if k not in ['auth_tokens', 'permissions']:
710 setattr(auth_user, k, v)
711 setattr(auth_user, k, v)
711
712
712 except Exception:
713 except Exception:
713 log.error(traceback.format_exc())
714 log.error(traceback.format_exc())
714 auth_user.is_authenticated = False
715 auth_user.is_authenticated = False
715 return False
716 return False
716
717
717 return True
718 return True
718
719
719 def has_perm(self, user, perm):
720 def has_perm(self, user, perm):
720 perm = self._get_perm(perm)
721 perm = self._get_perm(perm)
721 user = self._get_user(user)
722 user = self._get_user(user)
722
723
723 return UserToPerm.query().filter(UserToPerm.user == user)\
724 return UserToPerm.query().filter(UserToPerm.user == user)\
724 .filter(UserToPerm.permission == perm).scalar() is not None
725 .filter(UserToPerm.permission == perm).scalar() is not None
725
726
726 def grant_perm(self, user, perm):
727 def grant_perm(self, user, perm):
727 """
728 """
728 Grant user global permissions
729 Grant user global permissions
729
730
730 :param user:
731 :param user:
731 :param perm:
732 :param perm:
732 """
733 """
733 user = self._get_user(user)
734 user = self._get_user(user)
734 perm = self._get_perm(perm)
735 perm = self._get_perm(perm)
735 # if this permission is already granted skip it
736 # if this permission is already granted skip it
736 _perm = UserToPerm.query()\
737 _perm = UserToPerm.query()\
737 .filter(UserToPerm.user == user)\
738 .filter(UserToPerm.user == user)\
738 .filter(UserToPerm.permission == perm)\
739 .filter(UserToPerm.permission == perm)\
739 .scalar()
740 .scalar()
740 if _perm:
741 if _perm:
741 return
742 return
742 new = UserToPerm()
743 new = UserToPerm()
743 new.user = user
744 new.user = user
744 new.permission = perm
745 new.permission = perm
745 self.sa.add(new)
746 self.sa.add(new)
746 return new
747 return new
747
748
748 def revoke_perm(self, user, perm):
749 def revoke_perm(self, user, perm):
749 """
750 """
750 Revoke users global permissions
751 Revoke users global permissions
751
752
752 :param user:
753 :param user:
753 :param perm:
754 :param perm:
754 """
755 """
755 user = self._get_user(user)
756 user = self._get_user(user)
756 perm = self._get_perm(perm)
757 perm = self._get_perm(perm)
757
758
758 obj = UserToPerm.query()\
759 obj = UserToPerm.query()\
759 .filter(UserToPerm.user == user)\
760 .filter(UserToPerm.user == user)\
760 .filter(UserToPerm.permission == perm)\
761 .filter(UserToPerm.permission == perm)\
761 .scalar()
762 .scalar()
762 if obj:
763 if obj:
763 self.sa.delete(obj)
764 self.sa.delete(obj)
764
765
765 def add_extra_email(self, user, email):
766 def add_extra_email(self, user, email):
766 """
767 """
767 Adds email address to UserEmailMap
768 Adds email address to UserEmailMap
768
769
769 :param user:
770 :param user:
770 :param email:
771 :param email:
771 """
772 """
772
773
773 user = self._get_user(user)
774 user = self._get_user(user)
774
775
775 obj = UserEmailMap()
776 obj = UserEmailMap()
776 obj.user = user
777 obj.user = user
777 obj.email = email
778 obj.email = email
778 self.sa.add(obj)
779 self.sa.add(obj)
779 return obj
780 return obj
780
781
781 def delete_extra_email(self, user, email_id):
782 def delete_extra_email(self, user, email_id):
782 """
783 """
783 Removes email address from UserEmailMap
784 Removes email address from UserEmailMap
784
785
785 :param user:
786 :param user:
786 :param email_id:
787 :param email_id:
787 """
788 """
788 user = self._get_user(user)
789 user = self._get_user(user)
789 obj = UserEmailMap.query().get(email_id)
790 obj = UserEmailMap.query().get(email_id)
790 if obj and obj.user_id == user.user_id:
791 if obj and obj.user_id == user.user_id:
791 self.sa.delete(obj)
792 self.sa.delete(obj)
792
793
793 def parse_ip_range(self, ip_range):
794 def parse_ip_range(self, ip_range):
794 ip_list = []
795 ip_list = []
795
796
796 def make_unique(value):
797 def make_unique(value):
797 seen = []
798 seen = []
798 return [c for c in value if not (c in seen or seen.append(c))]
799 return [c for c in value if not (c in seen or seen.append(c))]
799
800
800 # firsts split by commas
801 # firsts split by commas
801 for ip_range in ip_range.split(','):
802 for ip_range in ip_range.split(','):
802 if not ip_range:
803 if not ip_range:
803 continue
804 continue
804 ip_range = ip_range.strip()
805 ip_range = ip_range.strip()
805 if '-' in ip_range:
806 if '-' in ip_range:
806 start_ip, end_ip = ip_range.split('-', 1)
807 start_ip, end_ip = ip_range.split('-', 1)
807 start_ip = ipaddress.ip_address(safe_unicode(start_ip.strip()))
808 start_ip = ipaddress.ip_address(safe_unicode(start_ip.strip()))
808 end_ip = ipaddress.ip_address(safe_unicode(end_ip.strip()))
809 end_ip = ipaddress.ip_address(safe_unicode(end_ip.strip()))
809 parsed_ip_range = []
810 parsed_ip_range = []
810
811
811 for index in xrange(int(start_ip), int(end_ip) + 1):
812 for index in xrange(int(start_ip), int(end_ip) + 1):
812 new_ip = ipaddress.ip_address(index)
813 new_ip = ipaddress.ip_address(index)
813 parsed_ip_range.append(str(new_ip))
814 parsed_ip_range.append(str(new_ip))
814 ip_list.extend(parsed_ip_range)
815 ip_list.extend(parsed_ip_range)
815 else:
816 else:
816 ip_list.append(ip_range)
817 ip_list.append(ip_range)
817
818
818 return make_unique(ip_list)
819 return make_unique(ip_list)
819
820
820 def add_extra_ip(self, user, ip, description=None):
821 def add_extra_ip(self, user, ip, description=None):
821 """
822 """
822 Adds ip address to UserIpMap
823 Adds ip address to UserIpMap
823
824
824 :param user:
825 :param user:
825 :param ip:
826 :param ip:
826 """
827 """
827
828
828 user = self._get_user(user)
829 user = self._get_user(user)
829 obj = UserIpMap()
830 obj = UserIpMap()
830 obj.user = user
831 obj.user = user
831 obj.ip_addr = ip
832 obj.ip_addr = ip
832 obj.description = description
833 obj.description = description
833 self.sa.add(obj)
834 self.sa.add(obj)
834 return obj
835 return obj
835
836
836 auth_token_role = AuthTokenModel.cls
837 auth_token_role = AuthTokenModel.cls
837
838
838 def add_auth_token(self, user, lifetime_minutes, role, description=u'',
839 def add_auth_token(self, user, lifetime_minutes, role, description=u'',
839 scope_callback=None):
840 scope_callback=None):
840 """
841 """
841 Add AuthToken for user.
842 Add AuthToken for user.
842
843
843 :param user: username/user_id
844 :param user: username/user_id
844 :param lifetime_minutes: in minutes the lifetime for token, -1 equals no limit
845 :param lifetime_minutes: in minutes the lifetime for token, -1 equals no limit
845 :param role: one of AuthTokenModel.cls.ROLE_*
846 :param role: one of AuthTokenModel.cls.ROLE_*
846 :param description: optional string description
847 :param description: optional string description
847 """
848 """
848
849
849 token = AuthTokenModel().create(
850 token = AuthTokenModel().create(
850 user, description, lifetime_minutes, role)
851 user, description, lifetime_minutes, role)
851 if scope_callback and callable(scope_callback):
852 if scope_callback and callable(scope_callback):
852 # call the callback if we provide, used to attach scope for EE edition
853 # call the callback if we provide, used to attach scope for EE edition
853 scope_callback(token)
854 scope_callback(token)
854 return token
855 return token
855
856
856 def delete_extra_ip(self, user, ip_id):
857 def delete_extra_ip(self, user, ip_id):
857 """
858 """
858 Removes ip address from UserIpMap
859 Removes ip address from UserIpMap
859
860
860 :param user:
861 :param user:
861 :param ip_id:
862 :param ip_id:
862 """
863 """
863 user = self._get_user(user)
864 user = self._get_user(user)
864 obj = UserIpMap.query().get(ip_id)
865 obj = UserIpMap.query().get(ip_id)
865 if obj and obj.user_id == user.user_id:
866 if obj and obj.user_id == user.user_id:
866 self.sa.delete(obj)
867 self.sa.delete(obj)
867
868
868 def get_accounts_in_creation_order(self, current_user=None):
869 def get_accounts_in_creation_order(self, current_user=None):
869 """
870 """
870 Get accounts in order of creation for deactivation for license limits
871 Get accounts in order of creation for deactivation for license limits
871
872
872 pick currently logged in user, and append to the list in position 0
873 pick currently logged in user, and append to the list in position 0
873 pick all super-admins in order of creation date and add it to the list
874 pick all super-admins in order of creation date and add it to the list
874 pick all other accounts in order of creation and add it to the list.
875 pick all other accounts in order of creation and add it to the list.
875
876
876 Based on that list, the last accounts can be disabled as they are
877 Based on that list, the last accounts can be disabled as they are
877 created at the end and don't include any of the super admins as well
878 created at the end and don't include any of the super admins as well
878 as the current user.
879 as the current user.
879
880
880 :param current_user: optionally current user running this operation
881 :param current_user: optionally current user running this operation
881 """
882 """
882
883
883 if not current_user:
884 if not current_user:
884 current_user = get_current_rhodecode_user()
885 current_user = get_current_rhodecode_user()
885 active_super_admins = [
886 active_super_admins = [
886 x.user_id for x in User.query()
887 x.user_id for x in User.query()
887 .filter(User.user_id != current_user.user_id)
888 .filter(User.user_id != current_user.user_id)
888 .filter(User.active == true())
889 .filter(User.active == true())
889 .filter(User.admin == true())
890 .filter(User.admin == true())
890 .order_by(User.created_on.asc())]
891 .order_by(User.created_on.asc())]
891
892
892 active_regular_users = [
893 active_regular_users = [
893 x.user_id for x in User.query()
894 x.user_id for x in User.query()
894 .filter(User.user_id != current_user.user_id)
895 .filter(User.user_id != current_user.user_id)
895 .filter(User.active == true())
896 .filter(User.active == true())
896 .filter(User.admin == false())
897 .filter(User.admin == false())
897 .order_by(User.created_on.asc())]
898 .order_by(User.created_on.asc())]
898
899
899 list_of_accounts = [current_user.user_id]
900 list_of_accounts = [current_user.user_id]
900 list_of_accounts += active_super_admins
901 list_of_accounts += active_super_admins
901 list_of_accounts += active_regular_users
902 list_of_accounts += active_regular_users
902
903
903 return list_of_accounts
904 return list_of_accounts
904
905
905 def deactivate_last_users(self, expected_users, current_user=None):
906 def deactivate_last_users(self, expected_users, current_user=None):
906 """
907 """
907 Deactivate accounts that are over the license limits.
908 Deactivate accounts that are over the license limits.
908 Algorithm of which accounts to disabled is based on the formula:
909 Algorithm of which accounts to disabled is based on the formula:
909
910
910 Get current user, then super admins in creation order, then regular
911 Get current user, then super admins in creation order, then regular
911 active users in creation order.
912 active users in creation order.
912
913
913 Using that list we mark all accounts from the end of it as inactive.
914 Using that list we mark all accounts from the end of it as inactive.
914 This way we block only latest created accounts.
915 This way we block only latest created accounts.
915
916
916 :param expected_users: list of users in special order, we deactivate
917 :param expected_users: list of users in special order, we deactivate
917 the end N amount of users from that list
918 the end N amount of users from that list
918 """
919 """
919
920
920 list_of_accounts = self.get_accounts_in_creation_order(
921 list_of_accounts = self.get_accounts_in_creation_order(
921 current_user=current_user)
922 current_user=current_user)
922
923
923 for acc_id in list_of_accounts[expected_users + 1:]:
924 for acc_id in list_of_accounts[expected_users + 1:]:
924 user = User.get(acc_id)
925 user = User.get(acc_id)
925 log.info('Deactivating account %s for license unlock', user)
926 log.info('Deactivating account %s for license unlock', user)
926 user.active = False
927 user.active = False
927 Session().add(user)
928 Session().add(user)
928 Session().commit()
929 Session().commit()
929
930
930 return
931 return
931
932
932 def get_user_log(self, user, filter_term):
933 def get_user_log(self, user, filter_term):
933 user_log = UserLog.query()\
934 user_log = UserLog.query()\
934 .filter(or_(UserLog.user_id == user.user_id,
935 .filter(or_(UserLog.user_id == user.user_id,
935 UserLog.username == user.username))\
936 UserLog.username == user.username))\
936 .options(joinedload(UserLog.user))\
937 .options(joinedload(UserLog.user))\
937 .options(joinedload(UserLog.repository))\
938 .options(joinedload(UserLog.repository))\
938 .order_by(UserLog.action_date.desc())
939 .order_by(UserLog.action_date.desc())
939
940
940 user_log = user_log_filter(user_log, filter_term)
941 user_log = user_log_filter(user_log, filter_term)
941 return user_log
942 return user_log
General Comments 0
You need to be logged in to leave comments. Login now