authentication: fixed for python3 migrations

This commit is contained in:
RhodeCode Admin 2023-07-18 11:00:47 +02:00
parent d985e2f96c
commit 2ff61d5c42
8 changed files with 78 additions and 46 deletions

View file

@ -21,7 +21,7 @@
import logging
import importlib
from pyramid.authentication import SessionAuthenticationPolicy
from pyramid.authentication import SessionAuthenticationHelper
from rhodecode.authentication.registry import AuthenticationPluginRegistry
from rhodecode.authentication.routes import root_factory
@ -70,9 +70,7 @@ def discover_legacy_plugins(config, prefix=legacy_plugin_prefix):
def includeme(config):
# Set authentication policy.
authn_policy = SessionAuthenticationPolicy()
config.set_authentication_policy(authn_policy)
config.set_security_policy(SessionAuthenticationHelper())
# Create authentication plugin registry and add it to the pyramid registry.
authn_registry = AuthenticationPluginRegistry(config.get_settings())

View file

@ -32,11 +32,13 @@ import functools
from pyramid.threadlocal import get_current_registry
from rhodecode.authentication import AuthenticationPluginRegistry
from rhodecode.authentication.interface import IAuthnPluginRegistry
from rhodecode.authentication.schema import AuthnPluginSettingsSchemaBase
from rhodecode.lib import rc_cache
from rhodecode.lib.statsd_client import StatsdClient
from rhodecode.lib.auth import PasswordGenerator, _RhodeCodeCryptoBCrypt
from rhodecode.lib.str_utils import safe_bytes
from rhodecode.lib.utils2 import safe_int, safe_str
from rhodecode.lib.exceptions import (LdapConnectionError, LdapUsernameError, LdapPasswordError)
from rhodecode.model.db import User
@ -377,12 +379,13 @@ class RhodeCodeAuthPluginBase(object):
def get_user(self, username=None, **kwargs):
"""
Helper method for user fetching in plugins, by default it's using
simple fetch by username, but this method can be custimized in plugins
simple fetch by username, but this method can be customized in plugins
eg. headers auth plugin to fetch user by environ params
:param username: username if given to fetch from database
:param kwargs: extra arguments needed for user fetching.
"""
user = None
log.debug(
'Trying to fetch user `%s` from RhodeCode database', username)
@ -437,6 +440,7 @@ class RhodeCodeAuthPluginBase(object):
# check if hash should be migrated ?
new_hash = auth.get('_hash_migrate')
if new_hash:
# new_hash is a newly encrypted destination hash
self._migrate_hash_to_bcrypt(username, passwd, new_hash)
if 'user_group_sync' not in auth:
auth['user_group_sync'] = False
@ -446,9 +450,9 @@ class RhodeCodeAuthPluginBase(object):
def _migrate_hash_to_bcrypt(self, username, password, new_hash):
new_hash_cypher = _RhodeCodeCryptoBCrypt()
# extra checks, so make sure new hash is correct.
password_encoded = safe_str(password)
if new_hash and new_hash_cypher.hash_check(
password_encoded, new_hash):
password_as_bytes = safe_bytes(password)
if new_hash and new_hash_cypher.hash_check(password_as_bytes, new_hash):
cur_user = User.get_by_username(username)
cur_user.password = new_hash
Session().add(cur_user)
@ -671,7 +675,7 @@ def loadplugin(plugin_id):
return plugin
def get_authn_registry(registry=None):
def get_authn_registry(registry=None) -> AuthenticationPluginRegistry:
registry = registry or get_current_registry()
authn_registry = registry.queryUtility(IAuthnPluginRegistry)
return authn_registry
@ -688,18 +692,23 @@ def authenticate(username, password, environ=None, auth_type=None,
:param environ: environ headers passed for headers auth
:param auth_type: type of authentication, either `HTTP_TYPE` or `VCS_TYPE`
:param skip_missing: ignores plugins that are in db but not in environment
:param registry: pyramid registry
:param acl_repo_name: name of repo for ACL checks
:returns: None if auth failed, plugin_user dict if auth is correct
"""
if not auth_type or auth_type not in [HTTP_TYPE, VCS_TYPE]:
raise ValueError('auth type must be on of http, vcs got "%s" instead'
% auth_type)
headers_only = environ and not (username and password)
raise ValueError(f'auth type must be on of http, vcs got "{auth_type}" instead')
auth_credentials = (username and password)
headers_only = environ and not auth_credentials
authn_registry = get_authn_registry(registry)
plugins_to_check = authn_registry.get_plugins_for_authentication()
log.debug('authentication: headers=%s, username_and_passwd=%s', headers_only, bool(auth_credentials))
log.debug('Starting ordered authentication chain using %s plugins',
[x.name for x in plugins_to_check])
for plugin in plugins_to_check:
plugin.set_auth_type(auth_type)
plugin.set_calling_scope_repo(acl_repo_name)

View file

@ -26,7 +26,9 @@ RhodeCode authentication plugin for Atlassian CROWD
import colander
import base64
import logging
import urllib.request, urllib.error, urllib.parse
import urllib.request
import urllib.error
import urllib.parse
from rhodecode.translation import _
from rhodecode.authentication.base import (

View file

@ -27,7 +27,8 @@ from rhodecode.authentication.base import (
from rhodecode.authentication.schema import AuthnPluginSettingsSchemaBase
from rhodecode.authentication.routes import AuthnPluginResourceBase
from rhodecode.lib.colander_utils import strip_whitespace
from rhodecode.lib.utils2 import str2bool, safe_unicode
from rhodecode.lib.str_utils import safe_str
from rhodecode.lib.utils2 import str2bool
from rhodecode.model.db import User
@ -149,7 +150,7 @@ class RhodeCodeAuthPlugin(RhodeCodeExternalAuthPlugin):
def get_user(self, username=None, **kwargs):
"""
Helper method for user fetching in plugins, by default it's using
simple fetch by username, but this method can be custimized in plugins
simple fetch by username, but this method can be customized in plugins
eg. headers auth plugin to fetch user by environ params
:param username: username if given to fetch
:param kwargs: extra arguments needed for user fetching.
@ -162,7 +163,7 @@ class RhodeCodeAuthPlugin(RhodeCodeExternalAuthPlugin):
def auth(self, userobj, username, password, settings, **kwargs):
"""
Get's the headers_auth username (or email). It tries to get username
Gets the headers_auth username (or email). It tries to get username
from REMOTE_USER if this plugin is enabled, if that fails
it tries to get username from HTTP_X_FORWARDED_USER if fallback header
is set. clean_username extracts the username from this data if it's
@ -210,8 +211,8 @@ class RhodeCodeAuthPlugin(RhodeCodeExternalAuthPlugin):
user_attrs = {
'username': username,
'firstname': safe_unicode(firstname or username),
'lastname': safe_unicode(lastname or ''),
'firstname': safe_str(firstname or username),
'lastname': safe_str(lastname or ''),
'groups': [],
'user_group_sync': False,
'email': email or '',

View file

@ -27,8 +27,10 @@ http://www.jasig.org/cas
import colander
import logging
import rhodecode
import urllib.request, urllib.parse, urllib.error
import urllib.request, urllib.error, urllib.parse
import urllib.request
import urllib.parse
import urllib.error
from rhodecode.translation import _
from rhodecode.authentication.base import (
@ -36,8 +38,8 @@ from rhodecode.authentication.base import (
from rhodecode.authentication.schema import AuthnPluginSettingsSchemaBase
from rhodecode.authentication.routes import AuthnPluginResourceBase
from rhodecode.lib.colander_utils import strip_whitespace
from rhodecode.lib.utils2 import safe_unicode
from rhodecode.model.db import User
from rhodecode.lib.str_utils import safe_str
log = logging.getLogger(__name__)
@ -133,12 +135,12 @@ class RhodeCodeAuthPlugin(RhodeCodeExternalAuthPlugin):
{"url": url, "body": params, "headers": headers})
request = urllib.request.Request(url, params, headers)
try:
response = urllib.request.urlopen(request)
urllib.request.urlopen(request)
except urllib.error.HTTPError as e:
log.debug("HTTPError when requesting Jasig CAS (status code: %d)", e.code)
return None
except urllib.error.URLError as e:
log.debug("URLError when requesting Jasig CAS url: %s ", url)
log.debug("URLError when requesting Jasig CAS url: %s %s", url, e)
return None
# old attrs fetched from RhodeCode database
@ -152,8 +154,8 @@ class RhodeCodeAuthPlugin(RhodeCodeExternalAuthPlugin):
user_attrs = {
'username': username,
'firstname': safe_unicode(firstname or username),
'lastname': safe_unicode(lastname or ''),
'firstname': safe_str(firstname or username),
'lastname': safe_str(lastname or ''),
'groups': [],
'user_group_sync': False,
'email': email or '',

View file

@ -34,7 +34,7 @@ from rhodecode.lib.colander_utils import strip_whitespace
from rhodecode.lib.exceptions import (
LdapConnectionError, LdapUsernameError, LdapPasswordError, LdapImportError
)
from rhodecode.lib.utils2 import safe_unicode, safe_str
from rhodecode.lib.str_utils import safe_str
from rhodecode.model.db import User
from rhodecode.model.validators import Missing
@ -520,8 +520,8 @@ class RhodeCodeAuthPlugin(RhodeCodeExternalAuthPlugin):
user_attrs = {
'username': username,
'firstname': safe_unicode(get_ldap_attr('attr_firstname') or firstname),
'lastname': safe_unicode(get_ldap_attr('attr_lastname') or lastname),
'firstname': safe_str(get_ldap_attr('attr_firstname') or firstname),
'lastname': safe_str(get_ldap_attr('attr_lastname') or lastname),
'groups': groups,
'user_group_sync': False,
'email': get_ldap_attr('attr_email') or email,

View file

@ -27,7 +27,7 @@ import logging
import colander
from rhodecode.translation import _
from rhodecode.lib.utils2 import safe_str
from rhodecode.lib.utils2 import safe_bytes
from rhodecode.model.db import User
from rhodecode.authentication.schema import AuthnPluginSettingsSchemaBase
from rhodecode.authentication.base import (
@ -153,7 +153,7 @@ class RhodeCodeAuthPlugin(RhodeCodeAuthPluginBase):
if userobj.active:
from rhodecode.lib import auth
crypto_backend = auth.crypto_backend()
password_encoded = safe_str(password)
password_encoded = safe_bytes(password)
password_match, new_hash = crypto_backend.hash_check_with_upgrade(
password_encoded, userobj.password or '')

View file

@ -66,25 +66,19 @@ class AuthenticationPluginRegistry(object):
if plugin.uid == plugin_uid:
return plugin
def get_plugins_for_authentication(self, cache=True):
"""
Returns a list of plugins which should be consulted when authenticating
a user. It only returns plugins which are enabled and active.
Additionally it includes the fallback plugin from the INI file, if
`rhodecode.auth_plugin_fallback` is set to a plugin ID.
"""
cache_namespace_uid = 'cache_auth_plugins'
region = rc_cache.get_or_create_region('cache_general', cache_namespace_uid)
def get_cache_call_method(self, cache=True):
region, _ns = self.get_cache_region()
@region.conditional_cache_on_arguments(condition=cache)
def _get_auth_plugins(name, key, fallback_plugin):
plugins = []
def _get_auth_plugins(name: str, key: str, fallback_plugin):
log.debug('auth-plugins: calculating plugins available for authentication')
_plugins = []
# Add all enabled and active plugins to the list. We iterate over the
# auth_plugins setting from DB because it also represents the ordering.
enabled_plugins = SettingsModel().get_auth_plugins()
raw_settings = SettingsModel().get_all_settings(cache=False)
for plugin_id in enabled_plugins:
plugin = self.get_plugin(plugin_id)
if plugin is not None and plugin.is_active(
@ -92,7 +86,7 @@ class AuthenticationPluginRegistry(object):
# inject settings into plugin, we can re-use the DB fetched settings here
plugin._settings = plugin._propagate_settings(raw_settings)
plugins.append(plugin)
_plugins.append(plugin)
# Add the fallback plugin from ini file.
if fallback_plugin:
@ -100,10 +94,22 @@ class AuthenticationPluginRegistry(object):
'Using fallback authentication plugin from INI file: "%s"',
fallback_plugin)
plugin = self.get_plugin(fallback_plugin)
if plugin is not None and plugin not in plugins:
if plugin is not None and plugin not in _plugins:
plugin._settings = plugin._propagate_settings(raw_settings)
plugins.append(plugin)
return plugins
_plugins.append(plugin)
return _plugins
return _get_auth_plugins
def get_plugins_for_authentication(self, cache=True):
"""
Returns a list of plugins which should be consulted when authenticating
a user. It only returns plugins which are enabled and active.
Additionally, it includes the fallback plugin from the INI file, if
`rhodecode.auth_plugin_fallback` is set to a plugin ID.
"""
_get_auth_plugins = self.get_cache_call_method(cache=cache)
start = time.time()
plugins = _get_auth_plugins('rhodecode_auth_plugins', 'v1', self._fallback_plugin)
@ -119,3 +125,17 @@ class AuthenticationPluginRegistry(object):
return plugins
@classmethod
def get_cache_region(cls):
cache_namespace_uid = 'auth_plugins'
region = rc_cache.get_or_create_region('cache_general', cache_namespace_uid)
return region, cache_namespace_uid
@classmethod
def invalidate_auth_plugins_cache(cls, hard=True):
region, namespace_key = cls.get_cache_region()
log.debug('Invalidation cache [%s] region %s for cache_key: %s',
'invalidate_auth_plugins_cache', region, namespace_key)
# we use hard cleanup if invalidation is sent
rc_cache.clear_cache_namespace(region, namespace_key, method=rc_cache.CLEAR_DELETE)