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