##// END OF EJS Templates
fix(caching): fixed problems with Cache query for users....
fix(caching): fixed problems with Cache query for users. The old way of querying caused the user get query to be always cached, and returning old results even in 2fa forms. The new limited query doesn't cache the user object resolving issues

File last commit:

r5360:4cbf1ad2 default
r5365:ae8a165b default
Show More
auth_rhodecode.py
228 lines | 8.9 KiB | text/x-python | PythonLexer
# Copyright (C) 2012-2023 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
# (only), as published by the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
# This program is dual-licensed. If you wish to learn more about the
# RhodeCode Enterprise Edition, including its added features, Support services,
# and proprietary license terms, please see https://rhodecode.com/licenses/
"""
RhodeCode authentication plugin for built in internal auth
"""
import logging
import colander
from rhodecode.translation import _
from rhodecode.lib.utils2 import safe_bytes
from rhodecode.model.db import User
from rhodecode.authentication.schema import AuthnPluginSettingsSchemaBase
from rhodecode.authentication.base import (
RhodeCodeAuthPluginBase, hybrid_property, HTTP_TYPE, VCS_TYPE)
from rhodecode.authentication.routes import AuthnPluginResourceBase
log = logging.getLogger(__name__)
def plugin_factory(plugin_id, *args, **kwargs):
plugin = RhodeCodeAuthPlugin(plugin_id)
return plugin
class RhodecodeAuthnResource(AuthnPluginResourceBase):
pass
class RhodeCodeAuthPlugin(RhodeCodeAuthPluginBase):
uid = 'rhodecode'
AUTH_RESTRICTION_NONE = 'user_all'
AUTH_RESTRICTION_SUPER_ADMIN = 'user_super_admin'
AUTH_RESTRICTION_SCOPE_ALL = 'scope_all'
AUTH_RESTRICTION_SCOPE_HTTP = 'scope_http'
AUTH_RESTRICTION_SCOPE_VCS = 'scope_vcs'
def includeme(self, config):
config.add_authn_plugin(self)
config.add_authn_resource(self.get_id(), RhodecodeAuthnResource(self))
config.add_view(
'rhodecode.authentication.views.AuthnPluginViewBase',
attr='settings_get',
renderer='rhodecode:templates/admin/auth/plugin_settings.mako',
request_method='GET',
route_name='auth_home',
context=RhodecodeAuthnResource)
config.add_view(
'rhodecode.authentication.views.AuthnPluginViewBase',
attr='settings_post',
renderer='rhodecode:templates/admin/auth/plugin_settings.mako',
request_method='POST',
route_name='auth_home',
context=RhodecodeAuthnResource)
def get_settings_schema(self):
return RhodeCodeSettingsSchema()
def get_display_name(self, load_from_settings=False):
return _('RhodeCode Internal')
@classmethod
def docs(cls):
return "https://docs.rhodecode.com/RhodeCode-Enterprise/auth/auth.html"
@hybrid_property
def name(self):
return "rhodecode"
def user_activation_state(self):
def_user_perms = User.get_default_user().AuthUser().permissions['global']
return 'hg.register.auto_activate' in def_user_perms
def allows_authentication_from(
self, user, allows_non_existing_user=True,
allowed_auth_plugins=None, allowed_auth_sources=None):
"""
Custom method for this auth that doesn't accept non existing users.
We know that user exists in our database.
"""
allows_non_existing_user = False
return super().allows_authentication_from(
user, allows_non_existing_user=allows_non_existing_user)
def auth(self, userobj, username, password, settings, **kwargs):
if not userobj:
log.debug('userobj was:%s skipping', userobj)
return None
if userobj.extern_type != self.name:
log.warning("userobj:%s extern_type mismatch got:`%s` expected:`%s`",
userobj, userobj.extern_type, self.name)
return None
# check scope of auth
scope_restriction = settings.get('scope_restriction', '')
if scope_restriction == self.AUTH_RESTRICTION_SCOPE_HTTP \
and self.auth_type != HTTP_TYPE:
log.warning("userobj:%s tried scope type %s and scope restriction is set to %s",
userobj, self.auth_type, scope_restriction)
return None
if scope_restriction == self.AUTH_RESTRICTION_SCOPE_VCS \
and self.auth_type != VCS_TYPE:
log.warning("userobj:%s tried scope type %s and scope restriction is set to %s",
userobj, self.auth_type, scope_restriction)
return None
# check super-admin restriction
auth_restriction = settings.get('auth_restriction', '')
if auth_restriction == self.AUTH_RESTRICTION_SUPER_ADMIN \
and userobj.admin is False:
log.warning("userobj:%s is not super-admin and auth restriction is set to %s",
userobj, auth_restriction)
return None
user_attrs = {
"username": userobj.username,
"firstname": userobj.firstname,
"lastname": userobj.lastname,
"groups": [],
'user_group_sync': False,
"email": userobj.email,
"admin": userobj.admin,
"active": userobj.active,
"active_from_extern": userobj.active,
"extern_name": userobj.user_id,
"extern_type": userobj.extern_type,
}
log.debug("User attributes:%s", user_attrs)
if userobj.active:
from rhodecode.lib import auth
crypto_backend = auth.crypto_backend()
password_encoded = safe_bytes(password)
password_match, new_hash = crypto_backend.hash_check_with_upgrade(
password_encoded, userobj.password or '')
if password_match and new_hash:
log.debug('user %s properly authenticated, but '
'requires hash change to bcrypt', userobj)
# if password match, and we use OLD deprecated hash,
# we should migrate this user hash password to the new hash
# we store the new returned by hash_check_with_upgrade function
user_attrs['_hash_migrate'] = new_hash
if userobj.username == User.DEFAULT_USER and userobj.active:
log.info('user `%s` authenticated correctly as anonymous user',
userobj.username,
extra={"action": "user_auth_ok", "auth_module": "auth_rhodecode_anon", "username": userobj.username})
return user_attrs
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
log.warning("user `%s` used a wrong password when "
"authenticating on this plugin", userobj.username)
return None
else:
log.warning('user `%s` failed to authenticate via %s, reason: account not '
'active.', username, self.name)
return None
class RhodeCodeSettingsSchema(AuthnPluginSettingsSchemaBase):
global_2fa = colander.SchemaNode(
colander.Bool(),
default=False,
description=_('Force all users to use two factor authentication by enabling this.'),
missing=False,
title=_('Global 2FA'),
widget='bool',
)
auth_restriction_choices = [
(RhodeCodeAuthPlugin.AUTH_RESTRICTION_NONE, 'All users'),
(RhodeCodeAuthPlugin.AUTH_RESTRICTION_SUPER_ADMIN, 'Super admins only'),
]
auth_scope_choices = [
(RhodeCodeAuthPlugin.AUTH_RESTRICTION_SCOPE_ALL, 'HTTP and VCS'),
(RhodeCodeAuthPlugin.AUTH_RESTRICTION_SCOPE_HTTP, 'HTTP only'),
]
auth_restriction = colander.SchemaNode(
colander.String(),
default=auth_restriction_choices[0],
description=_('Allowed user types for authentication using this plugin.'),
title=_('User restriction'),
validator=colander.OneOf([x[0] for x in auth_restriction_choices]),
widget='select_with_labels',
choices=auth_restriction_choices
)
scope_restriction = colander.SchemaNode(
colander.String(),
default=auth_scope_choices[0],
description=_('Allowed protocols for authentication using this plugin. '
'VCS means GIT/HG/SVN. HTTP is web based login.'),
title=_('Scope restriction'),
validator=colander.OneOf([x[0] for x in auth_scope_choices]),
widget='select_with_labels',
choices=auth_scope_choices
)
def includeme(config):
plugin_id = f'egg:rhodecode-enterprise-ce#{RhodeCodeAuthPlugin.uid}'
plugin_factory(plugin_id).includeme(config)