diff --git a/rhodecode/authentication/__init__.py b/rhodecode/authentication/__init__.py --- a/rhodecode/authentication/__init__.py +++ b/rhodecode/authentication/__init__.py @@ -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, pref 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()) diff --git a/rhodecode/authentication/base.py b/rhodecode/authentication/base.py --- a/rhodecode/authentication/base.py +++ b/rhodecode/authentication/base.py @@ -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, env :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) diff --git a/rhodecode/authentication/plugins/auth_crowd.py b/rhodecode/authentication/plugins/auth_crowd.py --- a/rhodecode/authentication/plugins/auth_crowd.py +++ b/rhodecode/authentication/plugins/auth_crowd.py @@ -26,7 +26,9 @@ RhodeCode authentication plugin for Atla 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 ( diff --git a/rhodecode/authentication/plugins/auth_headers.py b/rhodecode/authentication/plugins/auth_headers.py --- a/rhodecode/authentication/plugins/auth_headers.py +++ b/rhodecode/authentication/plugins/auth_headers.py @@ -27,7 +27,8 @@ from rhodecode.authentication.base impor 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(RhodeCodeExter 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(RhodeCodeExter 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(RhodeCodeExter 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 '', diff --git a/rhodecode/authentication/plugins/auth_jasig_cas.py b/rhodecode/authentication/plugins/auth_jasig_cas.py --- a/rhodecode/authentication/plugins/auth_jasig_cas.py +++ b/rhodecode/authentication/plugins/auth_jasig_cas.py @@ -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 impor 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(RhodeCodeExter {"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(RhodeCodeExter 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 '', diff --git a/rhodecode/authentication/plugins/auth_ldap.py b/rhodecode/authentication/plugins/auth_ldap.py --- a/rhodecode/authentication/plugins/auth_ldap.py +++ b/rhodecode/authentication/plugins/auth_ldap.py @@ -34,7 +34,7 @@ from rhodecode.lib.colander_utils import 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(RhodeCodeExter 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, 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 @@ -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(RhodeCodeAuthP 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 '') diff --git a/rhodecode/authentication/registry.py b/rhodecode/authentication/registry.py --- a/rhodecode/authentication/registry.py +++ b/rhodecode/authentication/registry.py @@ -66,25 +66,19 @@ class AuthenticationPluginRegistry(objec 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(objec # 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(objec '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(objec 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)