feat(login by email option): added ability to log in with user primary email. Fixes: RCCE-63
This commit is contained in:
parent
5f36eb1b22
commit
d8281bb5c0
7 changed files with 28 additions and 14 deletions
|
|
@ -80,6 +80,18 @@ class TestLoginController(object):
|
|||
assert username == 'test_regular'
|
||||
response.mustcontain('logout')
|
||||
|
||||
def test_login_with_primary_email(self):
|
||||
user_email = 'test_regular@mail.com'
|
||||
response = self.app.post(route_path('login'),
|
||||
{'username': user_email,
|
||||
'password': 'test12'}, status=302)
|
||||
response = response.follow()
|
||||
session = response.get_session_from_response()
|
||||
user = session['rhodecode_user']
|
||||
assert user['username'] == user_email.split('@')[0]
|
||||
assert user['is_authenticated']
|
||||
response.mustcontain('logout')
|
||||
|
||||
def test_login_regular_forbidden_when_super_admin_restriction(self):
|
||||
from rhodecode.authentication.plugins.auth_rhodecode import RhodeCodeAuthPlugin
|
||||
with fixture.auth_restriction(self.app._pyramid_registry,
|
||||
|
|
|
|||
|
|
@ -54,8 +54,8 @@ CaptchaData = collections.namedtuple(
|
|||
'CaptchaData', 'active, private_key, public_key')
|
||||
|
||||
|
||||
def store_user_in_session(session, username, remember=False):
|
||||
user = User.get_by_username(username, case_insensitive=True)
|
||||
def store_user_in_session(session, user_identifier, remember=False):
|
||||
user = User.get_by_username_or_primary_email(user_identifier)
|
||||
auth_user = AuthUser(user.user_id)
|
||||
auth_user.set_authenticated()
|
||||
cs = auth_user.get_cookie_store()
|
||||
|
|
@ -74,7 +74,7 @@ def store_user_in_session(session, username, remember=False):
|
|||
safe_cs = cs.copy()
|
||||
safe_cs['password'] = '****'
|
||||
log.info('user %s is now authenticated and stored in '
|
||||
'session, session attrs %s', username, safe_cs)
|
||||
'session, session attrs %s', user_identifier, safe_cs)
|
||||
|
||||
# dumps session attrs back to cookie
|
||||
session._update_cookie_out()
|
||||
|
|
@ -181,7 +181,7 @@ class LoginView(BaseAppView):
|
|||
# form checks for username/password, now we're authenticated
|
||||
headers = store_user_in_session(
|
||||
self.session,
|
||||
username=form_result['username'],
|
||||
user_identifier=form_result['username'],
|
||||
remember=form_result['remember'])
|
||||
log.debug('Redirecting to "%s" after login.', c.came_from)
|
||||
|
||||
|
|
|
|||
|
|
@ -389,11 +389,7 @@ class RhodeCodeAuthPluginBase(object):
|
|||
log.debug(
|
||||
'Trying to fetch user `%s` from RhodeCode database', username)
|
||||
if username:
|
||||
user = User.get_by_username(username)
|
||||
if not user:
|
||||
log.debug('User not found, fallback to fetch user in '
|
||||
'case insensitive mode')
|
||||
user = User.get_by_username(username, case_insensitive=True)
|
||||
user = User.get_by_username_or_primary_email(username)
|
||||
else:
|
||||
log.debug('provided username:`%s` is empty skipping...', username)
|
||||
if not user:
|
||||
|
|
|
|||
|
|
@ -169,7 +169,7 @@ class RhodeCodeAuthPlugin(RhodeCodeAuthPluginBase):
|
|||
extra={"action": "user_auth_ok", "auth_module": "auth_rhodecode_anon", "username": userobj.username})
|
||||
return user_attrs
|
||||
|
||||
elif userobj.username == username and password_match:
|
||||
elif (userobj.username == username or userobj.email == username) and password_match:
|
||||
log.info('user `%s` authenticated correctly', userobj.username,
|
||||
extra={"action": "user_auth_ok", "auth_module": "auth_rhodecode", "username": userobj.username})
|
||||
return user_attrs
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ import collections
|
|||
|
||||
from sqlalchemy import (
|
||||
or_, and_, not_, func, cast, TypeDecorator, event, select,
|
||||
true, false, null,
|
||||
true, false, null, union_all,
|
||||
Index, Sequence, UniqueConstraint, ForeignKey, CheckConstraint, Column,
|
||||
Boolean, String, Unicode, UnicodeText, DateTime, Integer, LargeBinary,
|
||||
Text, Float, PickleType, BigInteger)
|
||||
|
|
@ -953,6 +953,12 @@ class User(Base, BaseModel):
|
|||
|
||||
return cls.execute(q).scalar_one_or_none()
|
||||
|
||||
@classmethod
|
||||
def get_by_username_or_primary_email(cls, user_identifier):
|
||||
qs = union_all(cls.select().where(func.lower(cls.username) == func.lower(user_identifier)),
|
||||
cls.select().where(func.lower(cls.email) == func.lower(user_identifier)))
|
||||
return cls.execute(cls.select(User).from_statement(qs)).scalar_one_or_none()
|
||||
|
||||
@classmethod
|
||||
def get_by_auth_token(cls, auth_token, cache=False):
|
||||
|
||||
|
|
|
|||
|
|
@ -432,7 +432,7 @@ def ValidAuth(localizer):
|
|||
|
||||
if not authenticate(username, password, '', HTTP_TYPE,
|
||||
skip_missing=True):
|
||||
user = User.get_by_username(username)
|
||||
user = User.get_by_username_or_primary_email(username)
|
||||
if user and not user.active:
|
||||
log.warning('user %s is disabled', username)
|
||||
msg = M(self, 'disabled_account', state)
|
||||
|
|
|
|||
|
|
@ -35,12 +35,12 @@
|
|||
<%block name="above_login_button" />
|
||||
<!-- login -->
|
||||
<div class="sign-in-title">
|
||||
<h1>${_('Sign In using username/password')}</h1>
|
||||
<h1>${_('Sign In using credentials')}</h1>
|
||||
</div>
|
||||
<div class="inner form">
|
||||
${h.form(request.route_path('login', _query={'came_from': c.came_from}), needs_csrf_token=False)}
|
||||
|
||||
<label for="username">${_('Username')}:</label>
|
||||
<label for="username">${_('Username or email address')}:</label>
|
||||
${h.text('username', class_='focus', value=defaults.get('username'))}
|
||||
%if 'username' in errors:
|
||||
<span class="error-message">${errors.get('username')}</span>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue