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:
parent
8c7ec57205
commit
6a6d8fa92c
4 changed files with 137 additions and 41 deletions
|
|
@ -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'})
|
||||
|
|
|
|||
|
|
@ -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
|
||||
)
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue