# HG changeset patch # User Marcin Kuzminski # Date 2019-01-16 10:03:28 # Node ID 5cc5c872749117171fc496359272f824e8d72874 # Parent d889da9ed9be179c23a004c5593813e6dc63f254 auth: add scope and login restrictions to rhodecode plugin, and scope restriction to token plugin. - allows limiting the usage of builtin auth to HTTP only (so force usage of tokens) - allows migration to something like saml keeping only super-admin for login. 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 @@ -109,7 +109,17 @@ class TestLoginController(object): def test_login_regular_forbidden_when_super_admin_restriction(self): from rhodecode.authentication.plugins.auth_rhodecode import RhodeCodeAuthPlugin - with fixture.login_restriction(RhodeCodeAuthPlugin.LOGIN_RESTRICTION_SUPER_ADMIN): + with fixture.auth_restriction(RhodeCodeAuthPlugin.AUTH_RESTRICTION_SUPER_ADMIN): + response = self.app.post(route_path('login'), + {'username': 'test_regular', + 'password': 'test12'}) + + response.mustcontain('invalid user name') + response.mustcontain('invalid password') + + def test_login_regular_forbidden_when_scope_restriction(self): + from rhodecode.authentication.plugins.auth_rhodecode import RhodeCodeAuthPlugin + with fixture.scope_restriction(RhodeCodeAuthPlugin.AUTH_RESTRICTION_SCOPE_VCS): response = self.app.post(route_path('login'), {'username': 'test_regular', 'password': 'test12'}) 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 @@ -26,13 +26,13 @@ import logging import colander -from rhodecode.authentication.schema import AuthnPluginSettingsSchemaBase from rhodecode.translation import _ - -from rhodecode.authentication.base import RhodeCodeAuthPluginBase, hybrid_property -from rhodecode.authentication.routes import AuthnPluginResourceBase from rhodecode.lib.utils2 import safe_str 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__) @@ -48,8 +48,11 @@ class RhodecodeAuthnResource(AuthnPlugin class RhodeCodeAuthPlugin(RhodeCodeAuthPluginBase): uid = 'rhodecode' - LOGIN_RESTRICTION_NONE = 'none' - LOGIN_RESTRICTION_SUPER_ADMIN = 'super_admin' + 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) @@ -104,16 +107,32 @@ class RhodeCodeAuthPlugin(RhodeCodeAuthP 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) + 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 - login_restriction = settings.get('login_restriction', '') - if login_restriction == self.LOGIN_RESTRICTION_SUPER_ADMIN and userobj.admin is False: - log.info( - "userobj:%s is not super-admin and login restriction is set to %s", - userobj, login_restriction) + 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 = { @@ -154,30 +173,45 @@ class RhodeCodeAuthPlugin(RhodeCodeAuthP elif userobj.username == username and password_match: log.info('user `%s` authenticated correctly', userobj.username) return user_attrs - log.warn("user `%s` used a wrong password when " - "authenticating on this plugin", userobj.username) + 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) + log.warning('user `%s` failed to authenticate via %s, reason: account not ' + 'active.', username, self.name) return None class RhodeCodeSettingsSchema(AuthnPluginSettingsSchemaBase): - login_restriction_choices = [ - (RhodeCodeAuthPlugin.LOGIN_RESTRICTION_NONE, 'All users'), - (RhodeCodeAuthPlugin.LOGIN_RESTRICTION_SUPER_ADMIN, 'Super admins only') + + 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'), ] - login_restriction = colander.SchemaNode( + auth_restriction = colander.SchemaNode( colander.String(), - default=login_restriction_choices[0], - description=_('Choose login restrition for users.'), - title=_('Login restriction'), - validator=colander.OneOf([x[0] for x in login_restriction_choices]), + 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=login_restriction_choices + 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 ) diff --git a/rhodecode/authentication/plugins/auth_token.py b/rhodecode/authentication/plugins/auth_token.py --- a/rhodecode/authentication/plugins/auth_token.py +++ b/rhodecode/authentication/plugins/auth_token.py @@ -23,7 +23,9 @@ RhodeCode authentication token plugin fo """ import logging +import colander +from rhodecode.authentication.schema import AuthnPluginSettingsSchemaBase from rhodecode.translation import _ from rhodecode.authentication.base import ( RhodeCodeAuthPluginBase, VCS_TYPE, hybrid_property) @@ -48,6 +50,7 @@ class RhodeCodeAuthPlugin(RhodeCodeAuthP Enables usage of authentication tokens for vcs operations. """ uid = 'token' + AUTH_RESTRICTION_SCOPE_VCS = 'scope_vcs' def includeme(self, config): config.add_authn_plugin(self) @@ -67,6 +70,9 @@ class RhodeCodeAuthPlugin(RhodeCodeAuthP route_name='auth_home', context=RhodecodeAuthnResource) + def get_settings_schema(self): + return RhodeCodeSettingsSchema() + def get_display_name(self): return _('Rhodecode Token') @@ -142,16 +148,30 @@ class RhodeCodeAuthPlugin(RhodeCodeAuthP 'user `%s` successfully authenticated via %s', user_attrs['username'], self.name) return user_attrs - log.warn( - 'user `%s` failed to authenticate via %s, reason: bad or ' - 'inactive token.', username, self.name) + log.warning('user `%s` failed to authenticate via %s, reason: bad or ' + 'inactive token.', username, self.name) else: - log.warning( - 'user `%s` failed to authenticate via %s, reason: account not ' - 'active.', username, self.name) + log.warning('user `%s` failed to authenticate via %s, reason: account not ' + 'active.', username, self.name) return None def includeme(config): plugin_id = 'egg:rhodecode-enterprise-ce#{}'.format(RhodeCodeAuthPlugin.uid) plugin_factory(plugin_id).includeme(config) + + +class RhodeCodeSettingsSchema(AuthnPluginSettingsSchemaBase): + auth_scope_choices = [ + (RhodeCodeAuthPlugin.AUTH_RESTRICTION_SCOPE_VCS, 'VCS only'), + ] + + scope_restriction = colander.SchemaNode( + colander.String(), + default=auth_scope_choices[0], + description=_('Choose operation scope restriction when authenticating.'), + title=_('Scope restriction'), + validator=colander.OneOf([x[0] for x in auth_scope_choices]), + widget='select_with_labels', + choices=auth_scope_choices + ) diff --git a/rhodecode/tests/fixture.py b/rhodecode/tests/fixture.py --- a/rhodecode/tests/fixture.py +++ b/rhodecode/tests/fixture.py @@ -122,15 +122,15 @@ class Fixture(object): return context() - def login_restriction(self, login_restriction): + def auth_restriction(self, auth_restriction): """ - Context process for changing the builtin rhodecode plugin login restrictions. + Context process for changing the builtin rhodecode plugin auth restrictions. Use like: fixture = Fixture() - with fixture.login_restriction('super_admin'): + with fixture.auth_restriction('super_admin'): #tests - after this block login restriction will be taken off + after this block auth restriction will be taken off """ class context(object): @@ -143,13 +143,45 @@ class Fixture(object): def __enter__(self): plugin = self._get_pluing() plugin.create_or_update_setting( - 'login_restriction', login_restriction) + 'auth_restriction', auth_restriction) Session().commit() def __exit__(self, exc_type, exc_val, exc_tb): plugin = self._get_pluing() plugin.create_or_update_setting( - 'login_restriction', RhodeCodeAuthPlugin.LOGIN_RESTRICTION_NONE) + 'auth_restriction', RhodeCodeAuthPlugin.AUTH_RESTRICTION_NONE) + Session().commit() + + return context() + + def scope_restriction(self, scope_restriction): + """ + Context process for changing the builtin rhodecode plugin scope restrictions. + Use like: + fixture = Fixture() + with fixture.scope_restriction('scope_http'): + #tests + + after this block scope restriction will be taken off + """ + + class context(object): + def _get_pluing(self): + plugin_id = 'egg:rhodecode-enterprise-ce#{}'.format( + RhodeCodeAuthPlugin.uid) + plugin = RhodeCodeAuthPlugin(plugin_id) + return plugin + + def __enter__(self): + plugin = self._get_pluing() + plugin.create_or_update_setting( + 'scope_restriction', scope_restriction) + Session().commit() + + def __exit__(self, exc_type, exc_val, exc_tb): + plugin = self._get_pluing() + plugin.create_or_update_setting( + 'scope_restriction', RhodeCodeAuthPlugin.AUTH_RESTRICTION_SCOPE_ALL) Session().commit() return context()