# HG changeset patch # User Serhii Ilin # Date 2024-03-14 09:18:07 # Node ID 2095c653fabd4fe6a5b78a30fa21a60643e61c17 # Parent 947712ef09ae51f3f7a119265d63bf60e9261cf7 feat(login by email option): added ability to log in with user primary email. Fixes: RCCE-63 diff --git a/rhodecode/apps/login/tests/test_login.py b/rhodecode/apps/login/tests/test_login.py --- a/rhodecode/apps/login/tests/test_login.py +++ b/rhodecode/apps/login/tests/test_login.py @@ -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, diff --git a/rhodecode/apps/login/views.py b/rhodecode/apps/login/views.py --- a/rhodecode/apps/login/views.py +++ b/rhodecode/apps/login/views.py @@ -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, usern 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) diff --git a/rhodecode/authentication/base.py b/rhodecode/authentication/base.py --- a/rhodecode/authentication/base.py +++ b/rhodecode/authentication/base.py @@ -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: diff --git a/rhodecode/authentication/plugins/auth_rhodecode.py b/rhodecode/authentication/plugins/auth_rhodecode.py --- a/rhodecode/authentication/plugins/auth_rhodecode.py +++ b/rhodecode/authentication/plugins/auth_rhodecode.py @@ -169,7 +169,7 @@ class RhodeCodeAuthPlugin(RhodeCodeAuthP 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 diff --git a/rhodecode/model/db.py b/rhodecode/model/db.py --- a/rhodecode/model/db.py +++ b/rhodecode/model/db.py @@ -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) @@ -954,6 +954,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): q = cls.select(User)\ diff --git a/rhodecode/model/validators.py b/rhodecode/model/validators.py --- a/rhodecode/model/validators.py +++ b/rhodecode/model/validators.py @@ -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) diff --git a/rhodecode/templates/login.mako b/rhodecode/templates/login.mako --- a/rhodecode/templates/login.mako +++ b/rhodecode/templates/login.mako @@ -35,12 +35,12 @@ <%block name="above_login_button" />
-

${_('Sign In using username/password')}

+

${_('Sign In using credentials')}

${h.form(request.route_path('login', _query={'came_from': c.came_from}), needs_csrf_token=False)} - + ${h.text('username', class_='focus', value=defaults.get('username'))} %if 'username' in errors: ${errors.get('username')}