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.
This commit is contained in:
Marcin Kuzminski 2019-01-16 11:03:28 +01:00
parent 8c7ec57205
commit 6a6d8fa92c
4 changed files with 137 additions and 41 deletions

View file

@ -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'})

View file

@ -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(AuthnPluginResourceBase):
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(RhodeCodeAuthPluginBase):
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
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)
# 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 = {
@ -154,30 +173,45 @@ class RhodeCodeAuthPlugin(RhodeCodeAuthPluginBase):
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'),
]
login_restriction = colander.SchemaNode(
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=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
)

View file

@ -23,7 +23,9 @@ RhodeCode authentication token plugin for built in internal auth
"""
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(RhodeCodeAuthPluginBase):
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(RhodeCodeAuthPluginBase):
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(RhodeCodeAuthPluginBase):
'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
)

View file

@ -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()