##// END OF EJS Templates
login: Remove social auth code from login controller....
johbo -
r23:dd346b2b default
parent child Browse files
Show More
@@ -1,409 +1,290 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2016 RhodeCode GmbH
3 # Copyright (C) 2010-2016 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 """
21 """
22 Login controller for rhodeocode
22 Login controller for rhodeocode
23 """
23 """
24
24
25 import datetime
25 import datetime
26 import formencode
26 import formencode
27 import logging
27 import logging
28 import urlparse
28 import urlparse
29 import uuid
29 import uuid
30
30
31 from formencode import htmlfill
31 from formencode import htmlfill
32 from webob.exc import HTTPFound
32 from webob.exc import HTTPFound
33 from pylons.i18n.translation import _
33 from pylons.i18n.translation import _
34 from pylons.controllers.util import redirect
34 from pylons.controllers.util import redirect
35 from pylons import request, session, tmpl_context as c, url
35 from pylons import request, session, tmpl_context as c, url
36 from recaptcha.client.captcha import submit
36 from recaptcha.client.captcha import submit
37
37
38 import rhodecode.lib.helpers as h
38 import rhodecode.lib.helpers as h
39 from rhodecode.lib.auth import (
39 from rhodecode.lib.auth import (
40 AuthUser, HasPermissionAnyDecorator, CSRFRequired)
40 AuthUser, HasPermissionAnyDecorator, CSRFRequired)
41 from rhodecode.authentication.base import loadplugin
41 from rhodecode.authentication.base import loadplugin
42 from rhodecode.lib.base import BaseController, render
42 from rhodecode.lib.base import BaseController, render
43 from rhodecode.lib.exceptions import UserCreationError
43 from rhodecode.lib.exceptions import UserCreationError
44 from rhodecode.lib.utils2 import safe_str
44 from rhodecode.lib.utils2 import safe_str
45 from rhodecode.model.db import User, ExternalIdentity
45 from rhodecode.model.db import User
46 from rhodecode.model.forms import LoginForm, RegisterForm, PasswordResetForm
46 from rhodecode.model.forms import LoginForm, RegisterForm, PasswordResetForm
47 from rhodecode.model.login_session import LoginSession
47 from rhodecode.model.login_session import LoginSession
48 from rhodecode.model.meta import Session
48 from rhodecode.model.meta import Session
49 from rhodecode.model.settings import SettingsModel
49 from rhodecode.model.settings import SettingsModel
50 from rhodecode.model.user import UserModel
50 from rhodecode.model.user import UserModel
51
51
52 log = logging.getLogger(__name__)
52 log = logging.getLogger(__name__)
53
53
54
54
55 class LoginController(BaseController):
55 class LoginController(BaseController):
56
56
57 def __before__(self):
57 def __before__(self):
58 super(LoginController, self).__before__()
58 super(LoginController, self).__before__()
59
59
60 def _store_user_in_session(self, username, remember=False):
60 def _store_user_in_session(self, username, remember=False):
61 user = User.get_by_username(username, case_insensitive=True)
61 user = User.get_by_username(username, case_insensitive=True)
62 auth_user = AuthUser(user.user_id)
62 auth_user = AuthUser(user.user_id)
63 auth_user.set_authenticated()
63 auth_user.set_authenticated()
64 cs = auth_user.get_cookie_store()
64 cs = auth_user.get_cookie_store()
65 session['rhodecode_user'] = cs
65 session['rhodecode_user'] = cs
66 user.update_lastlogin()
66 user.update_lastlogin()
67 Session().commit()
67 Session().commit()
68
68
69 # If they want to be remembered, update the cookie
69 # If they want to be remembered, update the cookie
70 if remember:
70 if remember:
71 _year = (datetime.datetime.now() +
71 _year = (datetime.datetime.now() +
72 datetime.timedelta(seconds=60 * 60 * 24 * 365))
72 datetime.timedelta(seconds=60 * 60 * 24 * 365))
73 session._set_cookie_expires(_year)
73 session._set_cookie_expires(_year)
74
74
75 session.save()
75 session.save()
76
76
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, cs)
78 'session, session attrs %s', username, 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 def _validate_came_from(self, came_from):
89 def _validate_came_from(self, came_from):
90 if not came_from:
90 if not came_from:
91 return came_from
91 return came_from
92
92
93 parsed = urlparse.urlparse(came_from)
93 parsed = urlparse.urlparse(came_from)
94 server_parsed = urlparse.urlparse(url.current())
94 server_parsed = urlparse.urlparse(url.current())
95 allowed_schemes = ['http', 'https']
95 allowed_schemes = ['http', 'https']
96 if parsed.scheme and parsed.scheme not in allowed_schemes:
96 if parsed.scheme and parsed.scheme not in allowed_schemes:
97 log.error('Suspicious URL scheme detected %s for url %s' %
97 log.error('Suspicious URL scheme detected %s for url %s' %
98 (parsed.scheme, parsed))
98 (parsed.scheme, parsed))
99 came_from = url('home')
99 came_from = url('home')
100 elif server_parsed.netloc != parsed.netloc:
100 elif server_parsed.netloc != parsed.netloc:
101 log.error('Suspicious NETLOC detected %s for url %s server url '
101 log.error('Suspicious NETLOC detected %s for url %s server url '
102 'is: %s' % (parsed.netloc, parsed, server_parsed))
102 'is: %s' % (parsed.netloc, parsed, server_parsed))
103 came_from = url('home')
103 came_from = url('home')
104 if any(bad_str in parsed.path for bad_str in ('\r', '\n')):
104 if any(bad_str in parsed.path for bad_str in ('\r', '\n')):
105 log.error('Header injection detected `%s` for url %s server url ' %
105 log.error('Header injection detected `%s` for url %s server url ' %
106 (parsed.path, parsed))
106 (parsed.path, parsed))
107 came_from = url('home')
107 came_from = url('home')
108 return came_from
108 return came_from
109
109
110 def _redirect_to_origin(self, location, headers=None):
110 def _redirect_to_origin(self, location, headers=None):
111 request.GET.pop('came_from', None)
111 request.GET.pop('came_from', None)
112 raise HTTPFound(location=location, headers=headers)
112 raise HTTPFound(location=location, headers=headers)
113
113
114 def _set_came_from(self):
114 def _set_came_from(self):
115 _default_came_from = url('home')
115 _default_came_from = url('home')
116 came_from = self._validate_came_from(
116 came_from = self._validate_came_from(
117 safe_str(request.GET.get('came_from', '')))
117 safe_str(request.GET.get('came_from', '')))
118 c.came_from = came_from or _default_came_from
118 c.came_from = came_from or _default_came_from
119
119
120 def index(self):
120 def index(self):
121 self._set_came_from()
121 self._set_came_from()
122
122
123 not_default = c.rhodecode_user.username != User.DEFAULT_USER
123 not_default = c.rhodecode_user.username != User.DEFAULT_USER
124 ip_allowed = c.rhodecode_user.ip_allowed
124 ip_allowed = c.rhodecode_user.ip_allowed
125 c.social_plugins = self._get_active_social_plugins()
126
125
127 # redirect if already logged in
126 # redirect if already logged in
128 if c.rhodecode_user.is_authenticated and not_default and ip_allowed:
127 if c.rhodecode_user.is_authenticated and not_default and ip_allowed:
129 raise self._redirect_to_origin(location=c.came_from)
128 raise self._redirect_to_origin(location=c.came_from)
130
129
131 if request.POST:
130 if request.POST:
132 # import Login Form validator class
131 # import Login Form validator class
133 login_form = LoginForm()()
132 login_form = LoginForm()()
134 try:
133 try:
135 session.invalidate()
134 session.invalidate()
136 c.form_result = login_form.to_python(dict(request.POST))
135 c.form_result = login_form.to_python(dict(request.POST))
137 # form checks for username/password, now we're authenticated
136 # form checks for username/password, now we're authenticated
138 headers = self._store_user_in_session(
137 headers = self._store_user_in_session(
139 username=c.form_result['username'],
138 username=c.form_result['username'],
140 remember=c.form_result['remember'])
139 remember=c.form_result['remember'])
141 raise self._redirect_to_origin(
140 raise self._redirect_to_origin(
142 location=c.came_from, headers=headers)
141 location=c.came_from, headers=headers)
143 except formencode.Invalid as errors:
142 except formencode.Invalid as errors:
144 defaults = errors.value
143 defaults = errors.value
145 # remove password from filling in form again
144 # remove password from filling in form again
146 del defaults['password']
145 del defaults['password']
147 return htmlfill.render(
146 return htmlfill.render(
148 render('/login.html'),
147 render('/login.html'),
149 defaults=errors.value,
148 defaults=errors.value,
150 errors=errors.error_dict or {},
149 errors=errors.error_dict or {},
151 prefix_error=False,
150 prefix_error=False,
152 encoding="UTF-8",
151 encoding="UTF-8",
153 force_defaults=False)
152 force_defaults=False)
154 except UserCreationError as e:
153 except UserCreationError as e:
155 # container auth or other auth functions that create users on
154 # container auth or other auth functions that create users on
156 # the fly can throw this exception signaling that there's issue
155 # the fly can throw this exception signaling that there's issue
157 # with user creation, explanation should be provided in
156 # with user creation, explanation should be provided in
158 # Exception itself
157 # Exception itself
159 h.flash(e, 'error')
158 h.flash(e, 'error')
160
159
161 # check if we use container plugin, and try to login using it.
160 # check if we use container plugin, and try to login using it.
162 from rhodecode.authentication.base import authenticate, HTTP_TYPE
161 from rhodecode.authentication.base import authenticate, HTTP_TYPE
163 try:
162 try:
164 log.debug('Running PRE-AUTH for container based authentication')
163 log.debug('Running PRE-AUTH for container based authentication')
165 auth_info = authenticate(
164 auth_info = authenticate(
166 '', '', request.environ, HTTP_TYPE, skip_missing=True)
165 '', '', request.environ, HTTP_TYPE, skip_missing=True)
167 except UserCreationError as e:
166 except UserCreationError as e:
168 log.error(e)
167 log.error(e)
169 h.flash(e, 'error')
168 h.flash(e, 'error')
170 # render login, with flash message about limit
169 # render login, with flash message about limit
171 return render('/login.html')
170 return render('/login.html')
172
171
173 if auth_info:
172 if auth_info:
174 headers = self._store_user_in_session(auth_info.get('username'))
173 headers = self._store_user_in_session(auth_info.get('username'))
175 raise self._redirect_to_origin(
174 raise self._redirect_to_origin(
176 location=c.came_from, headers=headers)
175 location=c.came_from, headers=headers)
177 return render('/login.html')
176 return render('/login.html')
178
177
179 # TODO: Move this to a better place.
180 def _get_active_social_plugins(self):
181 from rhodecode.authentication.base import AuthomaticBase
182 activated_plugins = SettingsModel().get_auth_plugins()
183 social_plugins = []
184 for plugin_id in activated_plugins:
185 plugin = loadplugin(plugin_id)
186 if isinstance(plugin, AuthomaticBase) and plugin.is_active():
187 social_plugins.append(plugin)
188 return social_plugins
189
190 @HasPermissionAnyDecorator('hg.admin', 'hg.register.auto_activate',
178 @HasPermissionAnyDecorator('hg.admin', 'hg.register.auto_activate',
191 'hg.register.manual_activate')
179 'hg.register.manual_activate')
192 def register(self):
180 def register(self):
193 c.auto_active = 'hg.register.auto_activate' in User.get_default_user()\
181 c.auto_active = 'hg.register.auto_activate' in User.get_default_user()\
194 .AuthUser.permissions['global']
182 .AuthUser.permissions['global']
195
183
196 settings = SettingsModel().get_all_settings()
184 settings = SettingsModel().get_all_settings()
197 captcha_private_key = settings.get('rhodecode_captcha_private_key')
185 captcha_private_key = settings.get('rhodecode_captcha_private_key')
198 c.captcha_active = bool(captcha_private_key)
186 c.captcha_active = bool(captcha_private_key)
199 c.captcha_public_key = settings.get('rhodecode_captcha_public_key')
187 c.captcha_public_key = settings.get('rhodecode_captcha_public_key')
200 c.register_message = settings.get('rhodecode_register_message') or ''
188 c.register_message = settings.get('rhodecode_register_message') or ''
201
202 c.social_plugins = self._get_active_social_plugins()
203
204 social_data = session.get('rhodecode.social_auth')
205 c.form_data = {}
189 c.form_data = {}
206 if social_data:
207 c.form_data = {'username': social_data['user'].get('user_name'),
208 'password': str(uuid.uuid4()),
209 'email': social_data['user'].get('email')
210 }
211
190
212 if request.POST:
191 if request.POST:
213 register_form = RegisterForm()()
192 register_form = RegisterForm()()
214 try:
193 try:
215 form_result = register_form.to_python(dict(request.POST))
194 form_result = register_form.to_python(dict(request.POST))
216 form_result['active'] = c.auto_active
195 form_result['active'] = c.auto_active
217
196
218 if c.captcha_active:
197 if c.captcha_active:
219 response = submit(
198 response = submit(
220 request.POST.get('recaptcha_challenge_field'),
199 request.POST.get('recaptcha_challenge_field'),
221 request.POST.get('recaptcha_response_field'),
200 request.POST.get('recaptcha_response_field'),
222 private_key=captcha_private_key,
201 private_key=captcha_private_key,
223 remoteip=self.ip_addr)
202 remoteip=self.ip_addr)
224 if c.captcha_active and not response.is_valid:
203 if c.captcha_active and not response.is_valid:
225 _value = form_result
204 _value = form_result
226 _msg = _('bad captcha')
205 _msg = _('bad captcha')
227 error_dict = {'recaptcha_field': _msg}
206 error_dict = {'recaptcha_field': _msg}
228 raise formencode.Invalid(_msg, _value, None,
207 raise formencode.Invalid(_msg, _value, None,
229 error_dict=error_dict)
208 error_dict=error_dict)
230
209
231 new_user = UserModel().create_registration(form_result)
210 UserModel().create_registration(form_result)
232 if social_data:
233 plugin_name = 'egg:rhodecode-enterprise-ee#{}'.format(
234 social_data['credentials.provider']
235 )
236 auth_plugin = loadplugin(plugin_name)
237 if auth_plugin:
238 auth_plugin.handle_social_data(
239 session, new_user.user_id, social_data)
240 h.flash(_('You have successfully registered with RhodeCode'),
211 h.flash(_('You have successfully registered with RhodeCode'),
241 category='success')
212 category='success')
242 Session().commit()
213 Session().commit()
243 return redirect(url('login_home'))
214 return redirect(url('login_home'))
244
215
245 except formencode.Invalid as errors:
216 except formencode.Invalid as errors:
246 return htmlfill.render(
217 return htmlfill.render(
247 render('/register.html'),
218 render('/register.html'),
248 defaults=errors.value,
219 defaults=errors.value,
249 errors=errors.error_dict or {},
220 errors=errors.error_dict or {},
250 prefix_error=False,
221 prefix_error=False,
251 encoding="UTF-8",
222 encoding="UTF-8",
252 force_defaults=False)
223 force_defaults=False)
253 except UserCreationError as e:
224 except UserCreationError as e:
254 # container auth or other auth functions that create users on
225 # container auth or other auth functions that create users on
255 # the fly can throw this exception signaling that there's issue
226 # the fly can throw this exception signaling that there's issue
256 # with user creation, explanation should be provided in
227 # with user creation, explanation should be provided in
257 # Exception itself
228 # Exception itself
258 h.flash(e, 'error')
229 h.flash(e, 'error')
259
230
260 return render('/register.html')
231 return render('/register.html')
261
232
262 def password_reset(self):
233 def password_reset(self):
263 settings = SettingsModel().get_all_settings()
234 settings = SettingsModel().get_all_settings()
264 captcha_private_key = settings.get('rhodecode_captcha_private_key')
235 captcha_private_key = settings.get('rhodecode_captcha_private_key')
265 c.captcha_active = bool(captcha_private_key)
236 c.captcha_active = bool(captcha_private_key)
266 c.captcha_public_key = settings.get('rhodecode_captcha_public_key')
237 c.captcha_public_key = settings.get('rhodecode_captcha_public_key')
267
238
268 if request.POST:
239 if request.POST:
269 password_reset_form = PasswordResetForm()()
240 password_reset_form = PasswordResetForm()()
270 try:
241 try:
271 form_result = password_reset_form.to_python(dict(request.POST))
242 form_result = password_reset_form.to_python(dict(request.POST))
272 if c.captcha_active:
243 if c.captcha_active:
273 response = submit(
244 response = submit(
274 request.POST.get('recaptcha_challenge_field'),
245 request.POST.get('recaptcha_challenge_field'),
275 request.POST.get('recaptcha_response_field'),
246 request.POST.get('recaptcha_response_field'),
276 private_key=captcha_private_key,
247 private_key=captcha_private_key,
277 remoteip=self.ip_addr)
248 remoteip=self.ip_addr)
278 if c.captcha_active and not response.is_valid:
249 if c.captcha_active and not response.is_valid:
279 _value = form_result
250 _value = form_result
280 _msg = _('bad captcha')
251 _msg = _('bad captcha')
281 error_dict = {'recaptcha_field': _msg}
252 error_dict = {'recaptcha_field': _msg}
282 raise formencode.Invalid(_msg, _value, None,
253 raise formencode.Invalid(_msg, _value, None,
283 error_dict=error_dict)
254 error_dict=error_dict)
284 UserModel().reset_password_link(form_result)
255 UserModel().reset_password_link(form_result)
285 h.flash(_('Your password reset link was sent'),
256 h.flash(_('Your password reset link was sent'),
286 category='success')
257 category='success')
287 return redirect(url('login_home'))
258 return redirect(url('login_home'))
288
259
289 except formencode.Invalid as errors:
260 except formencode.Invalid as errors:
290 return htmlfill.render(
261 return htmlfill.render(
291 render('/password_reset.html'),
262 render('/password_reset.html'),
292 defaults=errors.value,
263 defaults=errors.value,
293 errors=errors.error_dict or {},
264 errors=errors.error_dict or {},
294 prefix_error=False,
265 prefix_error=False,
295 encoding="UTF-8",
266 encoding="UTF-8",
296 force_defaults=False)
267 force_defaults=False)
297
268
298 return render('/password_reset.html')
269 return render('/password_reset.html')
299
270
300 def password_reset_confirmation(self):
271 def password_reset_confirmation(self):
301 if request.GET and request.GET.get('key'):
272 if request.GET and request.GET.get('key'):
302 try:
273 try:
303 user = User.get_by_auth_token(request.GET.get('key'))
274 user = User.get_by_auth_token(request.GET.get('key'))
304 data = {'email': user.email}
275 data = {'email': user.email}
305 UserModel().reset_password(data)
276 UserModel().reset_password(data)
306 h.flash(_(
277 h.flash(_(
307 'Your password reset was successful, '
278 'Your password reset was successful, '
308 'a new password has been sent to your email'),
279 'a new password has been sent to your email'),
309 category='success')
280 category='success')
310 except Exception as e:
281 except Exception as e:
311 log.error(e)
282 log.error(e)
312 return redirect(url('reset_password'))
283 return redirect(url('reset_password'))
313
284
314 return redirect(url('login_home'))
285 return redirect(url('login_home'))
315
286
316 @CSRFRequired()
287 @CSRFRequired()
317 def logout(self):
288 def logout(self):
318 LoginSession().destroy_user_session()
289 LoginSession().destroy_user_session()
319 return redirect(url('home'))
290 return redirect(url('home'))
320
321 def social_auth(self, provider_name):
322 plugin_name = 'egg:rhodecode-enterprise-ee#{}'.format(
323 provider_name
324 )
325 auth_plugin = loadplugin(plugin_name)
326 if not auth_plugin:
327 return self._handle_social_auth_error(request, 'No auth plugin')
328
329 result, response = auth_plugin.get_provider_result(request)
330 if result:
331 if result.error:
332 return self._handle_social_auth_error(request, result.error)
333 elif result.user:
334 return self._handle_social_auth_success(request, result)
335 return response
336
337 def _handle_social_auth_error(self, request, result):
338 log.error(result)
339 h.flash(_('There was an error during OAuth processing.'),
340 category='error')
341 return redirect(url('home'))
342
343 def _normalize_social_data(self, result):
344 social_data = {
345 'user': {'data': result.user.data},
346 'credentials.provider': result.user.credentials.provider_name,
347 'credentials.token': result.user.credentials.token,
348 'credentials.token_secret': result.user.credentials.token_secret,
349 'credentials.refresh_token': result.user.credentials.refresh_token
350 }
351 # normalize data
352 social_data['user']['id'] = result.user.id
353 user_name = result.user.username or ''
354 # use email name as username for google
355 if (social_data['credentials.provider'] == 'google' and
356 result.user.email):
357 user_name = result.user.email
358
359 social_data['user']['user_name'] = user_name
360 social_data['user']['email'] = result.user.email or ''
361 return social_data
362
363 def _handle_social_auth_success(self, request, result):
364 self._set_came_from()
365
366 # Hooray, we have the user!
367 # OAuth 2.0 and OAuth 1.0a provide only limited user data on login,
368 # We need to update the user to get more info.
369 if result.user:
370 result.user.update()
371
372 social_data = self._normalize_social_data(result)
373
374 session['rhodecode.social_auth'] = social_data
375
376 plugin_name = 'egg:rhodecode-enterprise-ee#{}'.format(
377 social_data['credentials.provider']
378 )
379 auth_plugin = loadplugin(plugin_name)
380
381 # user is logged so bind his external identity with account
382 if request.user and request.user.username != User.DEFAULT_USER:
383 if auth_plugin:
384 auth_plugin.handle_social_data(
385 session, request.user.user_id, social_data)
386 session.pop('rhodecode.social_auth', None)
387 Session().commit()
388 return redirect(url('my_account_oauth'))
389 else:
390 user = ExternalIdentity.user_by_external_id_and_provider(
391 social_data['user']['id'],
392 social_data['credentials.provider']
393 )
394
395 # user tokens are already found in our db
396 if user:
397 if auth_plugin:
398 auth_plugin.handle_social_data(
399 session, user.user_id, social_data)
400 session.pop('rhodecode.social_auth', None)
401 headers = self._store_user_in_session(user.username)
402 raise self._redirect_to_origin(
403 location=c.came_from, headers=headers)
404 else:
405 msg = _('You need to finish registration '
406 'process to bind your external identity to your '
407 'account or sign in to existing account')
408 h.flash(msg, category='success')
409 return redirect(url('register'))
General Comments 0
You need to be logged in to leave comments. Login now