##// END OF EJS Templates
login: Moved method out of login view.
johbo -
r57:a49c279c default
parent child Browse files
Show More
@@ -1,342 +1,336 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2016-2016 RhodeCode GmbH
3 # Copyright (C) 2016-2016 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import datetime
21 import datetime
22 import formencode
22 import formencode
23 import logging
23 import logging
24 import urlparse
24 import urlparse
25
25
26 from pylons import url
26 from pylons import url
27 from pyramid.httpexceptions import HTTPFound
27 from pyramid.httpexceptions import HTTPFound
28 from pyramid.view import view_config
28 from pyramid.view import view_config
29 from recaptcha.client.captcha import submit
29 from recaptcha.client.captcha import submit
30
30
31 from rhodecode.events import UserRegistered
31 from rhodecode.events import UserRegistered
32 from rhodecode.lib.auth import (
32 from rhodecode.lib.auth import (
33 AuthUser, HasPermissionAnyDecorator, CSRFRequired)
33 AuthUser, HasPermissionAnyDecorator, CSRFRequired)
34 from rhodecode.lib.base import get_ip_addr
34 from rhodecode.lib.base import get_ip_addr
35 from rhodecode.lib.exceptions import UserCreationError
35 from rhodecode.lib.exceptions import UserCreationError
36 from rhodecode.lib.utils2 import safe_str
36 from rhodecode.lib.utils2 import safe_str
37 from rhodecode.model.db import User
37 from rhodecode.model.db import User
38 from rhodecode.model.forms import LoginForm, RegisterForm, PasswordResetForm
38 from rhodecode.model.forms import LoginForm, RegisterForm, PasswordResetForm
39 from rhodecode.model.login_session import LoginSession
39 from rhodecode.model.login_session import LoginSession
40 from rhodecode.model.meta import Session
40 from rhodecode.model.meta import Session
41 from rhodecode.model.settings import SettingsModel
41 from rhodecode.model.settings import SettingsModel
42 from rhodecode.model.user import UserModel
42 from rhodecode.model.user import UserModel
43 from rhodecode.translation import _
43 from rhodecode.translation import _
44
44
45
45
46 log = logging.getLogger(__name__)
46 log = logging.getLogger(__name__)
47
47
48
48
49 def _store_user_in_session(session, username, remember=False):
49 def _store_user_in_session(session, username, remember=False):
50 user = User.get_by_username(username, case_insensitive=True)
50 user = User.get_by_username(username, case_insensitive=True)
51 auth_user = AuthUser(user.user_id)
51 auth_user = AuthUser(user.user_id)
52 auth_user.set_authenticated()
52 auth_user.set_authenticated()
53 cs = auth_user.get_cookie_store()
53 cs = auth_user.get_cookie_store()
54 session['rhodecode_user'] = cs
54 session['rhodecode_user'] = cs
55 user.update_lastlogin()
55 user.update_lastlogin()
56 Session().commit()
56 Session().commit()
57
57
58 # If they want to be remembered, update the cookie
58 # If they want to be remembered, update the cookie
59 if remember:
59 if remember:
60 _year = (datetime.datetime.now() +
60 _year = (datetime.datetime.now() +
61 datetime.timedelta(seconds=60 * 60 * 24 * 365))
61 datetime.timedelta(seconds=60 * 60 * 24 * 365))
62 session._set_cookie_expires(_year)
62 session._set_cookie_expires(_year)
63
63
64 session.save()
64 session.save()
65
65
66 log.info('user %s is now authenticated and stored in '
66 log.info('user %s is now authenticated and stored in '
67 'session, session attrs %s', username, cs)
67 'session, session attrs %s', username, cs)
68
68
69 # dumps session attrs back to cookie
69 # dumps session attrs back to cookie
70 session._update_cookie_out()
70 session._update_cookie_out()
71 # we set new cookie
71 # we set new cookie
72 headers = None
72 headers = None
73 if session.request['set_cookie']:
73 if session.request['set_cookie']:
74 # send set-cookie headers back to response to update cookie
74 # send set-cookie headers back to response to update cookie
75 headers = [('Set-Cookie', session.request['cookie_out'])]
75 headers = [('Set-Cookie', session.request['cookie_out'])]
76 return headers
76 return headers
77
77
78
78
79 def get_came_from(request):
80 came_from = safe_str(request.GET.get('came_from', ''))
81 parsed = urlparse.urlparse(came_from)
82 allowed_schemes = ['http', 'https']
83 if parsed.scheme and parsed.scheme not in allowed_schemes:
84 log.error('Suspicious URL scheme detected %s for url %s' %
85 (parsed.scheme, parsed))
86 came_from = url('home')
87 elif parsed.netloc and request.host != parsed.netloc:
88 log.error('Suspicious NETLOC detected %s for url %s server url '
89 'is: %s' % (parsed.netloc, parsed, request.host))
90 came_from = url('home')
91 elif any(bad_str in parsed.path for bad_str in ('\r', '\n')):
92 log.error('Header injection detected `%s` for url %s server url ' %
93 (parsed.path, parsed))
94 came_from = url('home')
95
96 return came_from
97
98
79 class LoginView(object):
99 class LoginView(object):
80
100
81 def __init__(self, context, request):
101 def __init__(self, context, request):
82 self.request = request
102 self.request = request
83 self.context = context
103 self.context = context
84 self.session = request.session
104 self.session = request.session
85 self._rhodecode_user = request.user
105 self._rhodecode_user = request.user
86
106
87 def _validate_came_from(self, came_from):
88 if not came_from:
89 return came_from
90
91 parsed = urlparse.urlparse(came_from)
92 allowed_schemes = ['http', 'https']
93 if parsed.scheme and parsed.scheme not in allowed_schemes:
94 log.error('Suspicious URL scheme detected %s for url %s' %
95 (parsed.scheme, parsed))
96 came_from = url('home')
97 elif parsed.netloc and self.request.host != parsed.netloc:
98 log.error('Suspicious NETLOC detected %s for url %s server url '
99 'is: %s' % (parsed.netloc, parsed, self.request.host))
100 came_from = url('home')
101 if any(bad_str in parsed.path for bad_str in ('\r', '\n')):
102 log.error('Header injection detected `%s` for url %s server url ' %
103 (parsed.path, parsed))
104 came_from = url('home')
105 return came_from
106
107 def _get_came_from(self):
108 _default_came_from = url('home')
109 came_from = self._validate_came_from(
110 safe_str(self.request.GET.get('came_from', '')))
111 return came_from or _default_came_from
112
113 def _get_template_context(self):
107 def _get_template_context(self):
114 return {
108 return {
115 'came_from': self._get_came_from(),
109 'came_from': get_came_from(self.request),
116 'defaults': {},
110 'defaults': {},
117 'errors': {},
111 'errors': {},
118 }
112 }
119
113
120 @view_config(
114 @view_config(
121 route_name='login', request_method='GET',
115 route_name='login', request_method='GET',
122 renderer='rhodecode:templates/login.html')
116 renderer='rhodecode:templates/login.html')
123 def login(self):
117 def login(self):
124 user = self.request.user
118 user = self.request.user
125
119
126 # redirect if already logged in
120 # redirect if already logged in
127 if user.is_authenticated and not user.is_default and user.ip_allowed:
121 if user.is_authenticated and not user.is_default and user.ip_allowed:
128 raise HTTPFound(self._get_came_from())
122 raise HTTPFound(get_came_from(self.request))
129
123
130 return self._get_template_context()
124 return self._get_template_context()
131
125
132 @view_config(
126 @view_config(
133 route_name='login', request_method='POST',
127 route_name='login', request_method='POST',
134 renderer='rhodecode:templates/login.html')
128 renderer='rhodecode:templates/login.html')
135 def login_post(self):
129 def login_post(self):
136 came_from = self._get_came_from()
130 came_from = get_came_from(self.request)
137 session = self.request.session
131 session = self.request.session
138 login_form = LoginForm()()
132 login_form = LoginForm()()
139
133
140 try:
134 try:
141 session.invalidate()
135 session.invalidate()
142 form_result = login_form.to_python(self.request.params)
136 form_result = login_form.to_python(self.request.params)
143 # form checks for username/password, now we're authenticated
137 # form checks for username/password, now we're authenticated
144 headers = _store_user_in_session(
138 headers = _store_user_in_session(
145 self.session,
139 self.session,
146 username=form_result['username'],
140 username=form_result['username'],
147 remember=form_result['remember'])
141 remember=form_result['remember'])
148 raise HTTPFound(came_from, headers=headers)
142 raise HTTPFound(came_from, headers=headers)
149 except formencode.Invalid as errors:
143 except formencode.Invalid as errors:
150 defaults = errors.value
144 defaults = errors.value
151 # remove password from filling in form again
145 # remove password from filling in form again
152 del defaults['password']
146 del defaults['password']
153 render_ctx = self._get_template_context()
147 render_ctx = self._get_template_context()
154 render_ctx.update({
148 render_ctx.update({
155 'errors': errors.error_dict,
149 'errors': errors.error_dict,
156 'defaults': defaults,
150 'defaults': defaults,
157 })
151 })
158 return render_ctx
152 return render_ctx
159
153
160 except UserCreationError as e:
154 except UserCreationError as e:
161 # container auth or other auth functions that create users on
155 # container auth or other auth functions that create users on
162 # the fly can throw this exception signaling that there's issue
156 # the fly can throw this exception signaling that there's issue
163 # with user creation, explanation should be provided in
157 # with user creation, explanation should be provided in
164 # Exception itself
158 # Exception itself
165 session.flash(e, queue='error')
159 session.flash(e, queue='error')
166
160
167 # check if we use container plugin, and try to login using it.
161 # check if we use container plugin, and try to login using it.
168 from rhodecode.authentication.base import authenticate, HTTP_TYPE
162 from rhodecode.authentication.base import authenticate, HTTP_TYPE
169 try:
163 try:
170 log.debug('Running PRE-AUTH for container based authentication')
164 log.debug('Running PRE-AUTH for container based authentication')
171 auth_info = authenticate(
165 auth_info = authenticate(
172 '', '', self.request.environ, HTTP_TYPE, skip_missing=True)
166 '', '', self.request.environ, HTTP_TYPE, skip_missing=True)
173 except UserCreationError as e:
167 except UserCreationError as e:
174 log.error(e)
168 log.error(e)
175 session.flash(e, queue='error')
169 session.flash(e, queue='error')
176 # render login, with flash message about limit
170 # render login, with flash message about limit
177 return self._get_template_context()
171 return self._get_template_context()
178
172
179 if auth_info:
173 if auth_info:
180 headers = _store_user_in_session(auth_info.get('username'))
174 headers = _store_user_in_session(auth_info.get('username'))
181 raise HTTPFound(came_from, headers=headers)
175 raise HTTPFound(came_from, headers=headers)
182
176
183 return self._get_template_context()
177 return self._get_template_context()
184
178
185 @CSRFRequired()
179 @CSRFRequired()
186 @view_config(route_name='logout', request_method='POST')
180 @view_config(route_name='logout', request_method='POST')
187 def logout(self):
181 def logout(self):
188 LoginSession().destroy_user_session()
182 LoginSession().destroy_user_session()
189 return HTTPFound(url('home'))
183 return HTTPFound(url('home'))
190
184
191 @HasPermissionAnyDecorator(
185 @HasPermissionAnyDecorator(
192 'hg.admin', 'hg.register.auto_activate', 'hg.register.manual_activate')
186 'hg.admin', 'hg.register.auto_activate', 'hg.register.manual_activate')
193 @view_config(
187 @view_config(
194 route_name='register', request_method='GET',
188 route_name='register', request_method='GET',
195 renderer='rhodecode:templates/register.html',)
189 renderer='rhodecode:templates/register.html',)
196 def register(self, defaults={}, errors={}):
190 def register(self, defaults={}, errors={}):
197 settings = SettingsModel().get_all_settings()
191 settings = SettingsModel().get_all_settings()
198 captcha_public_key = settings.get('rhodecode_captcha_public_key')
192 captcha_public_key = settings.get('rhodecode_captcha_public_key')
199 captcha_private_key = settings.get('rhodecode_captcha_private_key')
193 captcha_private_key = settings.get('rhodecode_captcha_private_key')
200 captcha_active = bool(captcha_private_key)
194 captcha_active = bool(captcha_private_key)
201 register_message = settings.get('rhodecode_register_message') or ''
195 register_message = settings.get('rhodecode_register_message') or ''
202 auto_active = 'hg.register.auto_activate' in User.get_default_user()\
196 auto_active = 'hg.register.auto_activate' in User.get_default_user()\
203 .AuthUser.permissions['global']
197 .AuthUser.permissions['global']
204
198
205 render_ctx = self._get_template_context()
199 render_ctx = self._get_template_context()
206 render_ctx.update({
200 render_ctx.update({
207 'defaults': defaults,
201 'defaults': defaults,
208 'errors': errors,
202 'errors': errors,
209 'auto_active': auto_active,
203 'auto_active': auto_active,
210 'captcha_active': captcha_active,
204 'captcha_active': captcha_active,
211 'captcha_public_key': captcha_public_key,
205 'captcha_public_key': captcha_public_key,
212 'register_message': register_message,
206 'register_message': register_message,
213 })
207 })
214 return render_ctx
208 return render_ctx
215
209
216 @view_config(
210 @view_config(
217 route_name='register', request_method='POST',
211 route_name='register', request_method='POST',
218 renderer='rhodecode:templates/register.html')
212 renderer='rhodecode:templates/register.html')
219 def register_post(self):
213 def register_post(self):
220 captcha_private_key = SettingsModel().get_setting_by_name(
214 captcha_private_key = SettingsModel().get_setting_by_name(
221 'rhodecode_captcha_private_key')
215 'rhodecode_captcha_private_key')
222 captcha_active = bool(captcha_private_key)
216 captcha_active = bool(captcha_private_key)
223 auto_active = 'hg.register.auto_activate' in User.get_default_user()\
217 auto_active = 'hg.register.auto_activate' in User.get_default_user()\
224 .AuthUser.permissions['global']
218 .AuthUser.permissions['global']
225
219
226 register_form = RegisterForm()()
220 register_form = RegisterForm()()
227 try:
221 try:
228 form_result = register_form.to_python(self.request.params)
222 form_result = register_form.to_python(self.request.params)
229 form_result['active'] = auto_active
223 form_result['active'] = auto_active
230
224
231 if captcha_active:
225 if captcha_active:
232 response = submit(
226 response = submit(
233 self.request.params.get('recaptcha_challenge_field'),
227 self.request.params.get('recaptcha_challenge_field'),
234 self.request.params.get('recaptcha_response_field'),
228 self.request.params.get('recaptcha_response_field'),
235 private_key=captcha_private_key,
229 private_key=captcha_private_key,
236 remoteip=get_ip_addr(self.request.environ))
230 remoteip=get_ip_addr(self.request.environ))
237 if captcha_active and not response.is_valid:
231 if captcha_active and not response.is_valid:
238 _value = form_result
232 _value = form_result
239 _msg = _('bad captcha')
233 _msg = _('bad captcha')
240 error_dict = {'recaptcha_field': _msg}
234 error_dict = {'recaptcha_field': _msg}
241 raise formencode.Invalid(_msg, _value, None,
235 raise formencode.Invalid(_msg, _value, None,
242 error_dict=error_dict)
236 error_dict=error_dict)
243
237
244 new_user = UserModel().create_registration(form_result)
238 new_user = UserModel().create_registration(form_result)
245 event = UserRegistered(user=new_user, session=self.session)
239 event = UserRegistered(user=new_user, session=self.session)
246 self.request.registry.notify(event)
240 self.request.registry.notify(event)
247 self.session.flash(
241 self.session.flash(
248 _('You have successfully registered with RhodeCode'),
242 _('You have successfully registered with RhodeCode'),
249 queue='success')
243 queue='success')
250 Session().commit()
244 Session().commit()
251
245
252 redirect_ro = self.request.route_path('login')
246 redirect_ro = self.request.route_path('login')
253 raise HTTPFound(redirect_ro)
247 raise HTTPFound(redirect_ro)
254
248
255 except formencode.Invalid as errors:
249 except formencode.Invalid as errors:
256 del errors.value['password']
250 del errors.value['password']
257 del errors.value['password_confirmation']
251 del errors.value['password_confirmation']
258 return self.register(
252 return self.register(
259 defaults=errors.value, errors=errors.error_dict)
253 defaults=errors.value, errors=errors.error_dict)
260
254
261 except UserCreationError as e:
255 except UserCreationError as e:
262 # container auth or other auth functions that create users on
256 # container auth or other auth functions that create users on
263 # the fly can throw this exception signaling that there's issue
257 # the fly can throw this exception signaling that there's issue
264 # with user creation, explanation should be provided in
258 # with user creation, explanation should be provided in
265 # Exception itself
259 # Exception itself
266 self.session.flash(e, queue='error')
260 self.session.flash(e, queue='error')
267 return self.register()
261 return self.register()
268
262
269 @view_config(
263 @view_config(
270 route_name='reset_password', request_method=('GET', 'POST'),
264 route_name='reset_password', request_method=('GET', 'POST'),
271 renderer='rhodecode:templates/password_reset.html')
265 renderer='rhodecode:templates/password_reset.html')
272 def password_reset(self):
266 def password_reset(self):
273 settings = SettingsModel().get_all_settings()
267 settings = SettingsModel().get_all_settings()
274 captcha_private_key = settings.get('rhodecode_captcha_private_key')
268 captcha_private_key = settings.get('rhodecode_captcha_private_key')
275 captcha_active = bool(captcha_private_key)
269 captcha_active = bool(captcha_private_key)
276 captcha_public_key = settings.get('rhodecode_captcha_public_key')
270 captcha_public_key = settings.get('rhodecode_captcha_public_key')
277
271
278 render_ctx = {
272 render_ctx = {
279 'captcha_active': captcha_active,
273 'captcha_active': captcha_active,
280 'captcha_public_key': captcha_public_key,
274 'captcha_public_key': captcha_public_key,
281 'defaults': {},
275 'defaults': {},
282 'errors': {},
276 'errors': {},
283 }
277 }
284
278
285 if self.request.POST:
279 if self.request.POST:
286 password_reset_form = PasswordResetForm()()
280 password_reset_form = PasswordResetForm()()
287 try:
281 try:
288 form_result = password_reset_form.to_python(
282 form_result = password_reset_form.to_python(
289 self.request.params)
283 self.request.params)
290 if captcha_active:
284 if captcha_active:
291 response = submit(
285 response = submit(
292 self.request.params.get('recaptcha_challenge_field'),
286 self.request.params.get('recaptcha_challenge_field'),
293 self.request.params.get('recaptcha_response_field'),
287 self.request.params.get('recaptcha_response_field'),
294 private_key=captcha_private_key,
288 private_key=captcha_private_key,
295 remoteip=get_ip_addr(self.request.environ))
289 remoteip=get_ip_addr(self.request.environ))
296 if captcha_active and not response.is_valid:
290 if captcha_active and not response.is_valid:
297 _value = form_result
291 _value = form_result
298 _msg = _('bad captcha')
292 _msg = _('bad captcha')
299 error_dict = {'recaptcha_field': _msg}
293 error_dict = {'recaptcha_field': _msg}
300 raise formencode.Invalid(_msg, _value, None,
294 raise formencode.Invalid(_msg, _value, None,
301 error_dict=error_dict)
295 error_dict=error_dict)
302
296
303 # Generate reset URL and send mail.
297 # Generate reset URL and send mail.
304 user_email = form_result['email']
298 user_email = form_result['email']
305 user = User.get_by_email(user_email)
299 user = User.get_by_email(user_email)
306 password_reset_url = self.request.route_url(
300 password_reset_url = self.request.route_url(
307 'reset_password_confirmation',
301 'reset_password_confirmation',
308 _query={'key': user.api_key})
302 _query={'key': user.api_key})
309 UserModel().reset_password_link(
303 UserModel().reset_password_link(
310 form_result, password_reset_url)
304 form_result, password_reset_url)
311
305
312 # Display success message and redirect.
306 # Display success message and redirect.
313 self.session.flash(
307 self.session.flash(
314 _('Your password reset link was sent'),
308 _('Your password reset link was sent'),
315 queue='success')
309 queue='success')
316 return HTTPFound(self.request.route_path('login'))
310 return HTTPFound(self.request.route_path('login'))
317
311
318 except formencode.Invalid as errors:
312 except formencode.Invalid as errors:
319 render_ctx.update({
313 render_ctx.update({
320 'defaults': errors.value,
314 'defaults': errors.value,
321 'errors': errors.error_dict,
315 'errors': errors.error_dict,
322 })
316 })
323
317
324 return render_ctx
318 return render_ctx
325
319
326 @view_config(route_name='reset_password_confirmation',
320 @view_config(route_name='reset_password_confirmation',
327 request_method='GET')
321 request_method='GET')
328 def password_reset_confirmation(self):
322 def password_reset_confirmation(self):
329 if self.request.GET and self.request.GET.get('key'):
323 if self.request.GET and self.request.GET.get('key'):
330 try:
324 try:
331 user = User.get_by_auth_token(self.request.GET.get('key'))
325 user = User.get_by_auth_token(self.request.GET.get('key'))
332 data = {'email': user.email}
326 data = {'email': user.email}
333 UserModel().reset_password(data)
327 UserModel().reset_password(data)
334 self.session.flash(
328 self.session.flash(
335 _('Your password reset was successful, '
329 _('Your password reset was successful, '
336 'a new password has been sent to your email'),
330 'a new password has been sent to your email'),
337 queue='success')
331 queue='success')
338 except Exception as e:
332 except Exception as e:
339 log.error(e)
333 log.error(e)
340 return HTTPFound(self.request.route_path('reset_password'))
334 return HTTPFound(self.request.route_path('reset_password'))
341
335
342 return HTTPFound(self.request.route_path('login'))
336 return HTTPFound(self.request.route_path('login'))
General Comments 0
You need to be logged in to leave comments. Login now