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