mirror of
https://github.com/YunoHost/moulinette.git
synced 2024-09-03 20:06:31 +02:00
Some refactoring (again) in authenticators and exceptions
* Move authenticators classes into distinct modules * Standardize MoulinetteError which is now a child class of IOError * Add methods from helpers.py to LDAP authenticator class * Review authenticator and action configuration storage * Small changes in error printing on MoulinetteError raising
This commit is contained in:
parent
c95469073a
commit
ecd88ce853
9 changed files with 477 additions and 318 deletions
|
@ -35,11 +35,8 @@ if __name__ == '__main__':
|
||||||
raise YunoHostError(17, _("YunoHost is not correctly installed, please execute 'yunohost tools postinstall'"))
|
raise YunoHostError(17, _("YunoHost is not correctly installed, please execute 'yunohost tools postinstall'"))
|
||||||
|
|
||||||
# Execute the action
|
# Execute the action
|
||||||
cli(['yunohost', 'test'], args, use_cache)
|
ret = cli(['yunohost', 'test'], args, use_cache)
|
||||||
except MoulinetteError as e:
|
|
||||||
print(e.colorize())
|
|
||||||
sys.exit(e.code)
|
|
||||||
except YunoHostError as e:
|
except YunoHostError as e:
|
||||||
print(colorize(_("Error: "), 'red') + e.message)
|
print(colorize(_("Error: "), 'red') + e.message)
|
||||||
sys.exit(e.code)
|
sys.exit(e.code)
|
||||||
sys.exit(0)
|
sys.exit(ret)
|
||||||
|
|
|
@ -9,7 +9,7 @@ basedir = os.path.abspath(os.path.dirname(__file__) +'/../')
|
||||||
if os.path.isdir(basedir +'/src'):
|
if os.path.isdir(basedir +'/src'):
|
||||||
sys.path.append(basedir +'/src')
|
sys.path.append(basedir +'/src')
|
||||||
|
|
||||||
from moulinette import init, api
|
from moulinette import init, api, MoulinetteError
|
||||||
|
|
||||||
|
|
||||||
## Callbacks for additional routes
|
## Callbacks for additional routes
|
||||||
|
@ -40,6 +40,12 @@ if __name__ == '__main__':
|
||||||
sys.argv.remove('--debug')
|
sys.argv.remove('--debug')
|
||||||
# TODO: Add log argument
|
# TODO: Add log argument
|
||||||
|
|
||||||
# Rune the server
|
try:
|
||||||
api(['yunohost', 'test'], 6787, {('GET', '/installed'): is_installed}, use_cache)
|
# Run the server
|
||||||
|
api(['yunohost', 'test'], 6787,
|
||||||
|
{('GET', '/installed'): is_installed}, use_cache)
|
||||||
|
except MoulinetteError as e:
|
||||||
|
from moulinette.interface.cli import colorize
|
||||||
|
print(_('%s: %s' % (colorize(_('Error'), 'red'), e.strerror)))
|
||||||
|
sys.exit(e.code)
|
||||||
sys.exit(0)
|
sys.exit(0)
|
||||||
|
|
|
@ -29,7 +29,7 @@ __all__ = [
|
||||||
'MoulinetteError',
|
'MoulinetteError',
|
||||||
]
|
]
|
||||||
|
|
||||||
from .core import MoulinetteError
|
from moulinette.core import MoulinetteError
|
||||||
|
|
||||||
|
|
||||||
## Package functions
|
## Package functions
|
||||||
|
@ -50,7 +50,7 @@ def init(**kwargs):
|
||||||
"""
|
"""
|
||||||
import sys
|
import sys
|
||||||
import __builtin__
|
import __builtin__
|
||||||
from .core import Package, install_i18n
|
from moulinette.core import Package, install_i18n
|
||||||
__builtin__.__dict__['pkg'] = Package(**kwargs)
|
__builtin__.__dict__['pkg'] = Package(**kwargs)
|
||||||
|
|
||||||
# Initialize internationalization
|
# Initialize internationalization
|
||||||
|
@ -76,8 +76,8 @@ def api(namespaces, port, routes={}, use_cache=True):
|
||||||
instead of using the cached one
|
instead of using the cached one
|
||||||
|
|
||||||
"""
|
"""
|
||||||
from .actionsmap import ActionsMap
|
from moulinette.actionsmap import ActionsMap
|
||||||
from .interface.api import MoulinetteAPI
|
from moulinette.interface.api import MoulinetteAPI
|
||||||
|
|
||||||
amap = ActionsMap('api', namespaces, use_cache)
|
amap = ActionsMap('api', namespaces, use_cache)
|
||||||
moulinette = MoulinetteAPI(amap, routes)
|
moulinette = MoulinetteAPI(amap, routes)
|
||||||
|
@ -97,10 +97,15 @@ def cli(namespaces, args, use_cache=True):
|
||||||
instead of using the cached one
|
instead of using the cached one
|
||||||
|
|
||||||
"""
|
"""
|
||||||
from .actionsmap import ActionsMap
|
from moulinette.actionsmap import ActionsMap
|
||||||
from .interface.cli import MoulinetteCLI
|
from moulinette.interface.cli import MoulinetteCLI, colorize
|
||||||
|
|
||||||
amap = ActionsMap('cli', namespaces, use_cache)
|
try:
|
||||||
moulinette = MoulinetteCLI(amap)
|
amap = ActionsMap('cli', namespaces, use_cache)
|
||||||
|
moulinette = MoulinetteCLI(amap)
|
||||||
|
|
||||||
moulinette.run(args)
|
moulinette.run(args)
|
||||||
|
except MoulinetteError as e:
|
||||||
|
print(_('%s: %s' % (colorize(_('Error'), 'red'), e.strerror)))
|
||||||
|
return e.errno
|
||||||
|
return 0
|
||||||
|
|
|
@ -1,16 +1,17 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
import argparse
|
|
||||||
import yaml
|
|
||||||
import re
|
|
||||||
import os
|
import os
|
||||||
|
import re
|
||||||
|
import errno
|
||||||
|
import yaml
|
||||||
|
import argparse
|
||||||
import cPickle as pickle
|
import cPickle as pickle
|
||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from . import __version__
|
from moulinette.core import (MoulinetteError, MoulinetteLock,
|
||||||
from .core import MoulinetteError, MoulinetteLock, init_authenticator
|
init_authenticator)
|
||||||
|
|
||||||
## Actions map Signals -------------------------------------------------
|
## Actions map Signals -------------------------------------------------
|
||||||
|
|
||||||
|
@ -48,7 +49,7 @@ class _AMapSignals(object):
|
||||||
"""The list of available signals"""
|
"""The list of available signals"""
|
||||||
signals = { 'authenticate', 'prompt' }
|
signals = { 'authenticate', 'prompt' }
|
||||||
|
|
||||||
def authenticate(self, authenticator, name, help, vendor=None):
|
def authenticate(self, authenticator, help):
|
||||||
"""Process the authentication
|
"""Process the authentication
|
||||||
|
|
||||||
Attempt to authenticate to the given authenticator and return
|
Attempt to authenticate to the given authenticator and return
|
||||||
|
@ -57,10 +58,8 @@ class _AMapSignals(object):
|
||||||
action).
|
action).
|
||||||
|
|
||||||
Keyword arguments:
|
Keyword arguments:
|
||||||
- authenticator -- The authenticator to use
|
- authenticator -- The authenticator object to use
|
||||||
- name -- The authenticator name in the actions map
|
|
||||||
- help -- A help message for the authenticator
|
- help -- A help message for the authenticator
|
||||||
- vendor -- Not expected (TODO: Remove it)
|
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
The authenticator object
|
The authenticator object
|
||||||
|
@ -68,7 +67,7 @@ class _AMapSignals(object):
|
||||||
"""
|
"""
|
||||||
if authenticator.is_authenticated:
|
if authenticator.is_authenticated:
|
||||||
return authenticator
|
return authenticator
|
||||||
return self._authenticate(authenticator, name, help)
|
return self._authenticate(authenticator, help)
|
||||||
|
|
||||||
def prompt(self, message, is_password=False, confirm=False):
|
def prompt(self, message, is_password=False, confirm=False):
|
||||||
"""Prompt for a value
|
"""Prompt for a value
|
||||||
|
@ -172,18 +171,14 @@ class _AMapParser(object):
|
||||||
raise NotImplementedError("derived class '%s' must override this method" % \
|
raise NotImplementedError("derived class '%s' must override this method" % \
|
||||||
self.__class__.__name__)
|
self.__class__.__name__)
|
||||||
|
|
||||||
def add_action_parser(self, name, tid, conf=None, **kwargs):
|
def add_action_parser(self, name, tid, **kwargs):
|
||||||
"""Add a parser for an action
|
"""Add a parser for an action
|
||||||
|
|
||||||
Create a new action and return an argument parser for it. It
|
Create a new action and return an argument parser for it.
|
||||||
should set the configuration 'conf' for the action which can be
|
|
||||||
identified by the tuple identifier 'tid' - it is usually in the
|
|
||||||
form of (namespace, category, action).
|
|
||||||
|
|
||||||
Keyword arguments:
|
Keyword arguments:
|
||||||
- name -- The action name
|
- name -- The action name
|
||||||
- tid -- The tuple identifier of the action
|
- tid -- The tuple identifier of the action
|
||||||
- conf -- A dict of configuration for the action
|
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
An ArgumentParser based object
|
An ArgumentParser based object
|
||||||
|
@ -288,6 +283,7 @@ class _AMapParser(object):
|
||||||
- configuration -- The configuration to pre-format
|
- configuration -- The configuration to pre-format
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
# TODO: Create a class with a validator method for each configuration
|
||||||
conf = {}
|
conf = {}
|
||||||
|
|
||||||
# -- 'authenficate'
|
# -- 'authenficate'
|
||||||
|
@ -305,7 +301,7 @@ class _AMapParser(object):
|
||||||
conf['authenticate'] = True if self.name in ifaces else False
|
conf['authenticate'] = True if self.name in ifaces else False
|
||||||
else:
|
else:
|
||||||
# TODO: Log error instead and tell valid values
|
# TODO: Log error instead and tell valid values
|
||||||
raise MoulinetteError(22, "Invalid value '%r' for configuration 'authenticate'" % ifaces)
|
raise MoulinetteError(errno.EINVAL, "Invalid value '%r' for configuration 'authenticate'" % ifaces)
|
||||||
|
|
||||||
# -- 'authenticator'
|
# -- 'authenticator'
|
||||||
try:
|
try:
|
||||||
|
@ -315,26 +311,31 @@ class _AMapParser(object):
|
||||||
else:
|
else:
|
||||||
if not is_global and isinstance(auth, str):
|
if not is_global and isinstance(auth, str):
|
||||||
try:
|
try:
|
||||||
# Store parameters of the required authenticator
|
# Store needed authenticator profile
|
||||||
conf['authenticator'] = self.global_conf['authenticator'][auth]
|
conf['authenticator'] = self.global_conf['authenticator'][auth]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
raise MoulinetteError(22, "Authenticator '%s' is not defined in global configuration" % auth)
|
raise MoulinetteError(errno.EINVAL, "Undefined authenticator '%s' in global configuration" % auth)
|
||||||
elif is_global and isinstance(auth, dict):
|
elif is_global and isinstance(auth, dict):
|
||||||
if len(auth) == 0:
|
if len(auth) == 0:
|
||||||
logging.warning('no authenticator defined in global configuration')
|
logging.warning('no authenticator defined in global configuration')
|
||||||
else:
|
else:
|
||||||
auths = {}
|
auths = {}
|
||||||
for auth_name, auth_conf in auth.items():
|
for auth_name, auth_conf in auth.items():
|
||||||
# Add authenticator name
|
# Add authenticator profile as a 3-tuple
|
||||||
auths[auth_name] = ({ 'name': auth_name,
|
# (identifier, configuration, parameters) with
|
||||||
'vendor': auth_conf.get('vendor'),
|
# - identifier: the authenticator vendor and its
|
||||||
'help': auth_conf.get('help', None)
|
# profile name as a 2-tuple
|
||||||
},
|
# - configuration: a dict of additional global
|
||||||
|
# configuration (i.e. 'help')
|
||||||
|
# - parameters: a dict of arguments for the
|
||||||
|
# authenticator profile
|
||||||
|
auths[auth_name] = ((auth_conf.get('vendor'), auth_name),
|
||||||
|
{ 'help': auth_conf.get('help', None) },
|
||||||
auth_conf.get('parameters', {}))
|
auth_conf.get('parameters', {}))
|
||||||
conf['authenticator'] = auths
|
conf['authenticator'] = auths
|
||||||
else:
|
else:
|
||||||
# TODO: Log error instead and tell valid values
|
# TODO: Log error instead and tell valid values
|
||||||
raise MoulinetteError(22, "Invalid value '%r' for configuration 'authenticator'" % auth)
|
raise MoulinetteError(errno.EINVAL, "Invalid value '%r' for configuration 'authenticator'" % auth)
|
||||||
|
|
||||||
# -- 'argument_auth'
|
# -- 'argument_auth'
|
||||||
try:
|
try:
|
||||||
|
@ -346,7 +347,7 @@ class _AMapParser(object):
|
||||||
conf['argument_auth'] = arg_auth
|
conf['argument_auth'] = arg_auth
|
||||||
else:
|
else:
|
||||||
# TODO: Log error instead and tell valid values
|
# TODO: Log error instead and tell valid values
|
||||||
raise MoulinetteError(22, "Invalid value '%r' for configuration 'argument_auth'" % arg_auth)
|
raise MoulinetteError(errno.EINVAL, "Invalid value '%r' for configuration 'argument_auth'" % arg_auth)
|
||||||
|
|
||||||
return conf
|
return conf
|
||||||
|
|
||||||
|
@ -362,14 +363,12 @@ class _AMapParser(object):
|
||||||
|
|
||||||
"""
|
"""
|
||||||
if name == 'authenticator' and value:
|
if name == 'authenticator' and value:
|
||||||
auth_conf, auth_params = value
|
(identifier, configuration, parameters) = value
|
||||||
|
|
||||||
# Return authenticator configuration and an instanciator for
|
# Return global configuration and an authenticator
|
||||||
# it as a 2-tuple
|
# instanciator as a 2-tuple
|
||||||
return (auth_conf,
|
return (configuration,
|
||||||
lambda: init_authenticator(auth_conf['name'],
|
lambda: init_authenticator(identifier, parameters))
|
||||||
auth_conf['vendor'],
|
|
||||||
**auth_params))
|
|
||||||
|
|
||||||
return value
|
return value
|
||||||
|
|
||||||
|
@ -415,7 +414,7 @@ class CLIAMapParser(_AMapParser):
|
||||||
parser = self._subparsers.add_parser(name, help=category_help)
|
parser = self._subparsers.add_parser(name, help=category_help)
|
||||||
return self.__class__(self, parser)
|
return self.__class__(self, parser)
|
||||||
|
|
||||||
def add_action_parser(self, name, tid, conf=None, action_help=None, **kwargs):
|
def add_action_parser(self, name, tid, action_help=None, **kwargs):
|
||||||
"""Add a parser for an action
|
"""Add a parser for an action
|
||||||
|
|
||||||
Keyword arguments:
|
Keyword arguments:
|
||||||
|
@ -425,8 +424,6 @@ class CLIAMapParser(_AMapParser):
|
||||||
A new argparse.ArgumentParser object for the action
|
A new argparse.ArgumentParser object for the action
|
||||||
|
|
||||||
"""
|
"""
|
||||||
if conf:
|
|
||||||
self.set_conf(tid, conf)
|
|
||||||
return self._subparsers.add_parser(name, help=action_help)
|
return self._subparsers.add_parser(name, help=action_help)
|
||||||
|
|
||||||
def parse_args(self, args, **kwargs):
|
def parse_args(self, args, **kwargs):
|
||||||
|
@ -440,7 +437,7 @@ class CLIAMapParser(_AMapParser):
|
||||||
auth = shandler.authenticate(klass(), **auth_conf)
|
auth = shandler.authenticate(klass(), **auth_conf)
|
||||||
if not auth.is_authenticated:
|
if not auth.is_authenticated:
|
||||||
# TODO: Set proper error code
|
# TODO: Set proper error code
|
||||||
raise MoulinetteError(1, _("This action need authentication"))
|
raise MoulinetteError(errno.EACCES, _("This action need authentication"))
|
||||||
if self.get_conf(ret._tid, 'argument_auth') and \
|
if self.get_conf(ret._tid, 'argument_auth') and \
|
||||||
self.get_conf(ret._tid, 'authenticate') == 'all':
|
self.get_conf(ret._tid, 'authenticate') == 'all':
|
||||||
ret.auth = auth
|
ret.auth = auth
|
||||||
|
@ -521,7 +518,6 @@ class APIAMapParser(_AMapParser):
|
||||||
"""Actions map's API Parser
|
"""Actions map's API Parser
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
super(APIAMapParser, self).__init__()
|
super(APIAMapParser, self).__init__()
|
||||||
|
|
||||||
|
@ -556,7 +552,7 @@ class APIAMapParser(_AMapParser):
|
||||||
def add_category_parser(self, name, **kwargs):
|
def add_category_parser(self, name, **kwargs):
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def add_action_parser(self, name, tid, conf=None, api=None, **kwargs):
|
def add_action_parser(self, name, tid, api=None, **kwargs):
|
||||||
"""Add a parser for an action
|
"""Add a parser for an action
|
||||||
|
|
||||||
Keyword arguments:
|
Keyword arguments:
|
||||||
|
@ -582,9 +578,7 @@ class APIAMapParser(_AMapParser):
|
||||||
|
|
||||||
# Create and append parser
|
# Create and append parser
|
||||||
parser = _HTTPArgumentParser()
|
parser = _HTTPArgumentParser()
|
||||||
self._parsers[key] = parser
|
self._parsers[key] = (tid, parser)
|
||||||
if conf:
|
|
||||||
self.set_conf(key, conf)
|
|
||||||
|
|
||||||
# Return the created parser
|
# Return the created parser
|
||||||
return parser
|
return parser
|
||||||
|
@ -596,25 +590,27 @@ class APIAMapParser(_AMapParser):
|
||||||
- route -- The action route as a 2-tuple (method, path)
|
- route -- The action route as a 2-tuple (method, path)
|
||||||
|
|
||||||
"""
|
"""
|
||||||
# Retrieve the parser for the route
|
try:
|
||||||
if route not in self.routes:
|
# Retrieve the tid and the parser for the route
|
||||||
raise MoulinetteError(22, "No parser for '%s %s' found" % key)
|
tid, parser = self._parsers[route]
|
||||||
|
except KeyError:
|
||||||
|
raise MoulinetteError(errno.EINVAL, "No parser found for route '%s'" % route)
|
||||||
ret = argparse.Namespace()
|
ret = argparse.Namespace()
|
||||||
|
|
||||||
# Perform authentication if needed
|
# Perform authentication if needed
|
||||||
if self.get_conf(route, 'authenticate'):
|
if self.get_conf(tid, 'authenticate'):
|
||||||
auth_conf, klass = self.get_conf(route, 'authenticator')
|
auth_conf, klass = self.get_conf(tid, 'authenticator')
|
||||||
|
|
||||||
# TODO: Catch errors
|
# TODO: Catch errors
|
||||||
auth = shandler.authenticate(klass(), **auth_conf)
|
auth = shandler.authenticate(klass(), **auth_conf)
|
||||||
if not auth.is_authenticated:
|
if not auth.is_authenticated:
|
||||||
# TODO: Set proper error code
|
# TODO: Set proper error code
|
||||||
raise MoulinetteError(1, _("This action need authentication"))
|
raise MoulinetteError(errno.EACCES, _("This action need authentication"))
|
||||||
if self.get_conf(route, 'argument_auth') and \
|
if self.get_conf(tid, 'argument_auth') and \
|
||||||
self.get_conf(route, 'authenticate') == 'all':
|
self.get_conf(tid, 'authenticate') == 'all':
|
||||||
ret.auth = auth
|
ret.auth = auth
|
||||||
|
|
||||||
return self._parsers[route].parse_args(args, ret)
|
return parser.parse_args(args, ret)
|
||||||
|
|
||||||
"""
|
"""
|
||||||
The dict of interfaces names and their associated parser class.
|
The dict of interfaces names and their associated parser class.
|
||||||
|
@ -758,7 +754,7 @@ class PatternParameter(_ExtraParameter):
|
||||||
pattern, message = (arguments[0], arguments[1])
|
pattern, message = (arguments[0], arguments[1])
|
||||||
|
|
||||||
if not re.match(pattern, arg_value or ''):
|
if not re.match(pattern, arg_value or ''):
|
||||||
raise MoulinetteError(22, message)
|
raise MoulinetteError(errno.EINVAL, message)
|
||||||
return arg_value
|
return arg_value
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
@ -776,7 +772,7 @@ The list of available extra parameters classes. It will keep to this list
|
||||||
order on argument parsing.
|
order on argument parsing.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
extraparameters_list = {AskParameter, PasswordParameter, PatternParameter}
|
extraparameters_list = [AskParameter, PasswordParameter, PatternParameter]
|
||||||
|
|
||||||
# Extra parameters argument Parser
|
# Extra parameters argument Parser
|
||||||
|
|
||||||
|
@ -880,7 +876,7 @@ class ActionsMap(object):
|
||||||
# Retrieve the interface parser
|
# Retrieve the interface parser
|
||||||
self._parser_class = actionsmap_parsers[interface]
|
self._parser_class = actionsmap_parsers[interface]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
raise MoulinetteError(22, _("Invalid interface '%s'" % interface))
|
raise MoulinetteError(errno.EINVAL, _("Unknown interface '%s'" % interface))
|
||||||
|
|
||||||
logging.debug("initializing ActionsMap for the '%s' interface" % interface)
|
logging.debug("initializing ActionsMap for the '%s' interface" % interface)
|
||||||
|
|
||||||
|
@ -931,7 +927,7 @@ class ActionsMap(object):
|
||||||
try:
|
try:
|
||||||
auth = self.parser.get_global_conf('authenticator', profile)[1]
|
auth = self.parser.get_global_conf('authenticator', profile)[1]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
raise MoulinetteError(167, _("Unknown authenticator profile '%s'") % profile)
|
raise MoulinetteError(errno.EINVAL, _("Unknown authenticator profile '%s'") % profile)
|
||||||
else:
|
else:
|
||||||
return auth()
|
return auth()
|
||||||
|
|
||||||
|
@ -977,7 +973,7 @@ class ActionsMap(object):
|
||||||
fromlist=[func_name])
|
fromlist=[func_name])
|
||||||
func = getattr(mod, func_name)
|
func = getattr(mod, func_name)
|
||||||
except (AttributeError, ImportError):
|
except (AttributeError, ImportError):
|
||||||
raise MoulinetteError(168, _('Function is not defined'))
|
raise MoulinetteError(errno.ENOSYS, _('Function is not defined'))
|
||||||
else:
|
else:
|
||||||
# Process the action
|
# Process the action
|
||||||
return func(**arguments)
|
return func(**arguments)
|
||||||
|
@ -1056,11 +1052,13 @@ class ActionsMap(object):
|
||||||
for argn, argp in arguments.items():
|
for argn, argp in arguments.items():
|
||||||
names = top_parser.format_arg_names(argn,
|
names = top_parser.format_arg_names(argn,
|
||||||
argp.pop('full', None))
|
argp.pop('full', None))
|
||||||
extra = argp.pop('extra', None)
|
try:
|
||||||
|
extra = argp.pop('extra')
|
||||||
arg = parser.add_argument(*names, **argp)
|
arg_dest = (parser.add_argument(*names, **argp)).dest
|
||||||
if extra:
|
extras[arg_dest] = _get_extra(arg_dest, extra)
|
||||||
extras[arg.dest] = _get_extra(arg.dest, extra)
|
except KeyError:
|
||||||
|
# No extra parameters
|
||||||
|
parser.add_argument(*names, **argp)
|
||||||
if extras:
|
if extras:
|
||||||
parser.set_defaults(_extra=extras)
|
parser.set_defaults(_extra=extras)
|
||||||
|
|
||||||
|
@ -1095,6 +1093,7 @@ class ActionsMap(object):
|
||||||
actions = cp.pop('actions')
|
actions = cp.pop('actions')
|
||||||
except KeyError:
|
except KeyError:
|
||||||
# Invalid category without actions
|
# Invalid category without actions
|
||||||
|
logging.warning("no actions found in category '%s'" % cn)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# Get category parser
|
# Get category parser
|
||||||
|
@ -1102,13 +1101,18 @@ class ActionsMap(object):
|
||||||
|
|
||||||
# -- Parse actions
|
# -- Parse actions
|
||||||
for an, ap in actions.items():
|
for an, ap in actions.items():
|
||||||
conf = ap.pop('configuration', None)
|
|
||||||
args = ap.pop('arguments', {})
|
args = ap.pop('arguments', {})
|
||||||
tid = (n, cn, an)
|
tid = (n, cn, an)
|
||||||
|
try:
|
||||||
|
conf = ap.pop('configuration')
|
||||||
|
_set_conf = lambda p: p.set_conf(tid, conf)
|
||||||
|
except KeyError:
|
||||||
|
# No action configuration
|
||||||
|
_set_conf = lambda p: False
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Get action parser
|
# Get action parser
|
||||||
parser = cat_parser.add_action_parser(an, tid, conf, **ap)
|
parser = cat_parser.add_action_parser(an, tid, **ap)
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
# No parser for the action
|
# No parser for the action
|
||||||
continue
|
continue
|
||||||
|
@ -1119,5 +1123,6 @@ class ActionsMap(object):
|
||||||
# Store action identifier and add arguments
|
# Store action identifier and add arguments
|
||||||
parser.set_defaults(_tid=tid)
|
parser.set_defaults(_tid=tid)
|
||||||
_add_arguments(parser, args)
|
_add_arguments(parser, args)
|
||||||
|
_set_conf(cat_parser)
|
||||||
|
|
||||||
return top_parser
|
return top_parser
|
||||||
|
|
146
src/moulinette/authenticators/__init__.py
Normal file
146
src/moulinette/authenticators/__init__.py
Normal file
|
@ -0,0 +1,146 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
import errno
|
||||||
|
import gnupg
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from moulinette.core import MoulinetteError
|
||||||
|
|
||||||
|
# Base Class -----------------------------------------------------------
|
||||||
|
|
||||||
|
class BaseAuthenticator(object):
|
||||||
|
"""Authenticator base representation
|
||||||
|
|
||||||
|
Each authenticators must implement an Authenticator class derived
|
||||||
|
from this class. It implements base methods to authenticate with a
|
||||||
|
password or a session token.
|
||||||
|
Authenticators configurations are identified by a profile name which
|
||||||
|
must be given on instantiation - with the corresponding vendor
|
||||||
|
configuration of the authenticator.
|
||||||
|
|
||||||
|
Keyword arguments:
|
||||||
|
- name -- The authenticator profile name
|
||||||
|
|
||||||
|
"""
|
||||||
|
def __init__(self, name):
|
||||||
|
self._name = name
|
||||||
|
|
||||||
|
@property
|
||||||
|
def name(self):
|
||||||
|
"""Return the name of the authenticator instance"""
|
||||||
|
return self._name
|
||||||
|
|
||||||
|
|
||||||
|
## Virtual properties
|
||||||
|
# Each authenticator classes must implement these properties.
|
||||||
|
|
||||||
|
"""The vendor name of the authenticator"""
|
||||||
|
vendor = None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_authenticated(self):
|
||||||
|
"""Either the instance is authenticated or not"""
|
||||||
|
raise NotImplementedError("derived class '%s' must override this property" % \
|
||||||
|
self.__class__.__name__)
|
||||||
|
|
||||||
|
|
||||||
|
## Virtual methods
|
||||||
|
# Each authenticator classes must implement these methods.
|
||||||
|
|
||||||
|
def authenticate(password=None):
|
||||||
|
"""Attempt to authenticate
|
||||||
|
|
||||||
|
Attempt to authenticate with given password. It should raise an
|
||||||
|
AuthenticationError exception if authentication fails.
|
||||||
|
|
||||||
|
Keyword arguments:
|
||||||
|
- password -- A clear text password
|
||||||
|
|
||||||
|
"""
|
||||||
|
raise NotImplementedError("derived class '%s' must override this method" % \
|
||||||
|
self.__class__.__name__)
|
||||||
|
|
||||||
|
|
||||||
|
## Authentication methods
|
||||||
|
|
||||||
|
def __call__(self, password=None, token=None):
|
||||||
|
"""Attempt to authenticate
|
||||||
|
|
||||||
|
Attempt to authenticate either with password or with session
|
||||||
|
token if 'password' is None. If the authentication succeed, the
|
||||||
|
instance is returned and the session is registered for the token
|
||||||
|
if 'token' and 'password' are given.
|
||||||
|
The token is composed by the session identifier and a session
|
||||||
|
hash - to use for encryption - as a 2-tuple.
|
||||||
|
|
||||||
|
Keyword arguments:
|
||||||
|
- password -- A clear text password
|
||||||
|
- token -- The session token in the form of (id, hash)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
The authenticated instance
|
||||||
|
|
||||||
|
"""
|
||||||
|
if self.is_authenticated:
|
||||||
|
return self
|
||||||
|
store_session = True if password and token else False
|
||||||
|
|
||||||
|
if token:
|
||||||
|
try:
|
||||||
|
# Extract id and hash from token
|
||||||
|
s_id, s_hash = token
|
||||||
|
except TypeError:
|
||||||
|
if not password:
|
||||||
|
raise MoulinetteError(errno.EINVAL, _("Invalid format for token"))
|
||||||
|
else:
|
||||||
|
# TODO: Log error
|
||||||
|
store_session = False
|
||||||
|
else:
|
||||||
|
if password is None:
|
||||||
|
# Retrieve session
|
||||||
|
password = self._retrieve_session(s_id, s_hash)
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Attempt to authenticate
|
||||||
|
self.authenticate(password)
|
||||||
|
except MoulinetteError:
|
||||||
|
raise
|
||||||
|
except Exception as e:
|
||||||
|
logging.error("authentication (name: '%s', type: '%s') fails: %s" % \
|
||||||
|
(self.name, self.vendor, e))
|
||||||
|
raise MoulinetteError(errno.EACCES, _("Unable to authenticate"))
|
||||||
|
|
||||||
|
# Store session
|
||||||
|
if store_session:
|
||||||
|
self._store_session(s_id, s_hash, password)
|
||||||
|
|
||||||
|
return self
|
||||||
|
|
||||||
|
|
||||||
|
## Private methods
|
||||||
|
|
||||||
|
def _open_sessionfile(self, session_id, mode='r'):
|
||||||
|
"""Open a session file for this instance in given mode"""
|
||||||
|
return pkg.open_cachefile('%s.asc' % session_id, mode,
|
||||||
|
subdir='session/%s' % self.name)
|
||||||
|
|
||||||
|
def _store_session(self, session_id, session_hash, password):
|
||||||
|
"""Store a session and its associated password"""
|
||||||
|
gpg = gnupg.GPG()
|
||||||
|
gpg.encoding = 'utf-8'
|
||||||
|
with self._open_sessionfile(session_id, 'w') as f:
|
||||||
|
f.write(str(gpg.encrypt(password, None, symmetric=True,
|
||||||
|
passphrase=session_hash)))
|
||||||
|
|
||||||
|
def _retrieve_session(self, session_id, session_hash):
|
||||||
|
"""Retrieve a session and return its associated password"""
|
||||||
|
try:
|
||||||
|
with self._open_sessionfile(session_id, 'r') as f:
|
||||||
|
enc_pwd = f.read()
|
||||||
|
except IOError:
|
||||||
|
# TODO: Set proper error code
|
||||||
|
raise MoulinetteError(167, _("Unable to retrieve session"))
|
||||||
|
else:
|
||||||
|
gpg = gnupg.GPG()
|
||||||
|
gpg.encoding = 'utf-8'
|
||||||
|
return str(gpg.decrypt(enc_pwd, passphrase=session_hash))
|
192
src/moulinette/authenticators/ldap.py
Normal file
192
src/moulinette/authenticators/ldap.py
Normal file
|
@ -0,0 +1,192 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
# TODO: Use Python3 to remove this fix!
|
||||||
|
from __future__ import absolute_import
|
||||||
|
import ldap
|
||||||
|
import ldap.modlist as modlist
|
||||||
|
|
||||||
|
from moulinette.core import MoulinetteError
|
||||||
|
from moulinette.authenticators import BaseAuthenticator
|
||||||
|
|
||||||
|
# LDAP Class Implementation --------------------------------------------
|
||||||
|
|
||||||
|
class Authenticator(BaseAuthenticator):
|
||||||
|
"""LDAP Authenticator
|
||||||
|
|
||||||
|
Initialize a LDAP connexion for the given arguments. It attempts to
|
||||||
|
authenticate a user if 'user_rdn' is given - by associating user_rdn
|
||||||
|
and base_dn - and provides extra methods to manage opened connexion.
|
||||||
|
|
||||||
|
Keyword arguments:
|
||||||
|
- uri -- The LDAP server URI
|
||||||
|
- base_dn -- The base dn
|
||||||
|
- user_rdn -- The user rdn to authenticate
|
||||||
|
|
||||||
|
"""
|
||||||
|
def __init__(self, name, uri, base_dn, user_rdn=None):
|
||||||
|
super(Authenticator, self).__init__(name)
|
||||||
|
|
||||||
|
self.uri = uri
|
||||||
|
self.basedn = base_dn
|
||||||
|
if user_rdn:
|
||||||
|
self.userdn = '%s,%s' % (user_rdn, base_dn)
|
||||||
|
self.con = None
|
||||||
|
else:
|
||||||
|
# Initialize anonymous usage
|
||||||
|
self.userdn = ''
|
||||||
|
self.authenticate(None)
|
||||||
|
|
||||||
|
|
||||||
|
## Implement virtual properties
|
||||||
|
|
||||||
|
vendor = 'ldap'
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_authenticated(self):
|
||||||
|
try:
|
||||||
|
# Retrieve identity
|
||||||
|
who = self.con.whoami_s()
|
||||||
|
except:
|
||||||
|
return False
|
||||||
|
else:
|
||||||
|
if who[3:] == self.userdn:
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
## Implement virtual methods
|
||||||
|
|
||||||
|
def authenticate(self, password):
|
||||||
|
try:
|
||||||
|
con = ldap.initialize(self.uri)
|
||||||
|
if self.userdn:
|
||||||
|
con.simple_bind_s(self.userdn, password)
|
||||||
|
else:
|
||||||
|
con.simple_bind_s()
|
||||||
|
except ldap.INVALID_CREDENTIALS:
|
||||||
|
raise MoulinetteError(errno.EACCES, _("Invalid password"))
|
||||||
|
else:
|
||||||
|
self.con = con
|
||||||
|
|
||||||
|
|
||||||
|
## Additional LDAP methods
|
||||||
|
# TODO: Review these methods
|
||||||
|
|
||||||
|
def search(self, base=None, filter='(objectClass=*)', attrs=['dn']):
|
||||||
|
"""
|
||||||
|
Search in LDAP base
|
||||||
|
|
||||||
|
Keyword arguments:
|
||||||
|
base -- Base to search into
|
||||||
|
filter -- LDAP filter
|
||||||
|
attrs -- Array of attributes to fetch
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Boolean | Dict
|
||||||
|
|
||||||
|
"""
|
||||||
|
if not base:
|
||||||
|
base = self.basedn
|
||||||
|
|
||||||
|
try:
|
||||||
|
result = self.con.search_s(base, ldap.SCOPE_SUBTREE, filter, attrs)
|
||||||
|
except:
|
||||||
|
raise MoulinetteError(169, _('An error occured during LDAP search'))
|
||||||
|
|
||||||
|
if result:
|
||||||
|
result_list = []
|
||||||
|
for dn, entry in result:
|
||||||
|
if attrs != None:
|
||||||
|
if 'dn' in attrs:
|
||||||
|
entry['dn'] = [dn]
|
||||||
|
result_list.append(entry)
|
||||||
|
return result_list
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
|
||||||
|
def add(self, rdn, attr_dict):
|
||||||
|
"""
|
||||||
|
Add LDAP entry
|
||||||
|
|
||||||
|
Keyword arguments:
|
||||||
|
rdn -- DN without domain
|
||||||
|
attr_dict -- Dictionnary of attributes/values to add
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Boolean | MoulinetteError
|
||||||
|
|
||||||
|
"""
|
||||||
|
dn = rdn + ',' + self.basedn
|
||||||
|
ldif = modlist.addModlist(attr_dict)
|
||||||
|
|
||||||
|
try:
|
||||||
|
self.con.add_s(dn, ldif)
|
||||||
|
except:
|
||||||
|
raise MoulinetteError(169, _('An error occured during LDAP entry creation'))
|
||||||
|
else:
|
||||||
|
return True
|
||||||
|
|
||||||
|
def remove(self, rdn):
|
||||||
|
"""
|
||||||
|
Remove LDAP entry
|
||||||
|
|
||||||
|
Keyword arguments:
|
||||||
|
rdn -- DN without domain
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Boolean | MoulinetteError
|
||||||
|
|
||||||
|
"""
|
||||||
|
dn = rdn + ',' + self.basedn
|
||||||
|
try:
|
||||||
|
self.con.delete_s(dn)
|
||||||
|
except:
|
||||||
|
raise MoulinetteError(169, _('An error occured during LDAP entry deletion'))
|
||||||
|
else:
|
||||||
|
return True
|
||||||
|
|
||||||
|
def update(self, rdn, attr_dict, new_rdn=False):
|
||||||
|
"""
|
||||||
|
Modify LDAP entry
|
||||||
|
|
||||||
|
Keyword arguments:
|
||||||
|
rdn -- DN without domain
|
||||||
|
attr_dict -- Dictionnary of attributes/values to add
|
||||||
|
new_rdn -- New RDN for modification
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Boolean | MoulinetteError
|
||||||
|
|
||||||
|
"""
|
||||||
|
dn = rdn + ',' + self.basedn
|
||||||
|
actual_entry = self.search(base=dn, attrs=None)
|
||||||
|
ldif = modlist.modifyModlist(actual_entry[0], attr_dict, ignore_oldexistent=1)
|
||||||
|
|
||||||
|
try:
|
||||||
|
if new_rdn:
|
||||||
|
self.con.rename_s(dn, new_rdn)
|
||||||
|
dn = new_rdn + ',' + self.basedn
|
||||||
|
|
||||||
|
self.con.modify_ext_s(dn, ldif)
|
||||||
|
except:
|
||||||
|
raise MoulinetteError(169, _('An error occured during LDAP entry update'))
|
||||||
|
else:
|
||||||
|
return True
|
||||||
|
|
||||||
|
def validate_uniqueness(self, value_dict):
|
||||||
|
"""
|
||||||
|
Check uniqueness of values
|
||||||
|
|
||||||
|
Keyword arguments:
|
||||||
|
value_dict -- Dictionnary of attributes/values to check
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Boolean | MoulinetteError
|
||||||
|
|
||||||
|
"""
|
||||||
|
for attr, value in value_dict.items():
|
||||||
|
if not self.search(filter=attr + '=' + value):
|
||||||
|
continue
|
||||||
|
else:
|
||||||
|
raise MoulinetteError(17, _('Attribute already exists') + ' "' + attr + '=' + value + '"')
|
||||||
|
return True
|
|
@ -3,10 +3,11 @@
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
import time
|
import time
|
||||||
|
import errno
|
||||||
import gettext
|
import gettext
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from .helpers import colorize
|
from importlib import import_module
|
||||||
|
|
||||||
# Package manipulation -------------------------------------------------
|
# Package manipulation -------------------------------------------------
|
||||||
|
|
||||||
|
@ -132,189 +133,25 @@ class Package(object):
|
||||||
|
|
||||||
# Authenticators -------------------------------------------------------
|
# Authenticators -------------------------------------------------------
|
||||||
|
|
||||||
import ldap
|
def init_authenticator((vendor, name), kwargs={}):
|
||||||
import gnupg
|
"""Return a new authenticator instance
|
||||||
|
|
||||||
class AuthenticationError(Exception):
|
Retrieve the given authenticator vendor and return a new instance of
|
||||||
pass
|
its Authenticator class for the given profile.
|
||||||
|
|
||||||
class _BaseAuthenticator(object):
|
Keyword arguments:
|
||||||
|
- vendor -- The authenticator vendor name
|
||||||
|
- name -- The authenticator profile name
|
||||||
|
- kwargs -- A dict of arguments for the authenticator profile
|
||||||
|
|
||||||
def __init__(self, name):
|
"""
|
||||||
self._name = name
|
try:
|
||||||
|
mod = import_module('moulinette.authenticators.%s' % vendor)
|
||||||
@property
|
except ImportError:
|
||||||
def name(self):
|
# TODO: List available authenticator vendors
|
||||||
"""Return the name of the authenticator instance"""
|
raise MoulinetteError(errno.EINVAL, _("Unknown authenticator vendor '%s'" % vendor))
|
||||||
return self._name
|
else:
|
||||||
|
return mod.Authenticator(name, **kwargs)
|
||||||
|
|
||||||
## Virtual properties
|
|
||||||
# Each authenticator classes must implement these properties.
|
|
||||||
|
|
||||||
"""The vendor of the authenticator"""
|
|
||||||
vendor = None
|
|
||||||
|
|
||||||
@property
|
|
||||||
def is_authenticated(self):
|
|
||||||
"""Either the instance is authenticated or not"""
|
|
||||||
raise NotImplementedError("derived class '%s' must override this property" % \
|
|
||||||
self.__class__.__name__)
|
|
||||||
|
|
||||||
|
|
||||||
## Virtual methods
|
|
||||||
# Each authenticator classes must implement these methods.
|
|
||||||
|
|
||||||
def authenticate(password=None):
|
|
||||||
"""Attempt to authenticate
|
|
||||||
|
|
||||||
Attempt to authenticate with given password. It should raise an
|
|
||||||
AuthenticationError exception if authentication fails.
|
|
||||||
|
|
||||||
Keyword arguments:
|
|
||||||
- password -- A clear text password
|
|
||||||
|
|
||||||
"""
|
|
||||||
raise NotImplementedError("derived class '%s' must override this method" % \
|
|
||||||
self.__class__.__name__)
|
|
||||||
|
|
||||||
|
|
||||||
## Authentication methods
|
|
||||||
|
|
||||||
def __call__(self, password=None, token=None):
|
|
||||||
"""Attempt to authenticate
|
|
||||||
|
|
||||||
Attempt to authenticate either with password or with session
|
|
||||||
token if 'password' is None. If the authentication succeed, the
|
|
||||||
instance is returned and the session is registered for the token
|
|
||||||
if 'token' and 'password' are given.
|
|
||||||
The token is composed by the session identifier and a session
|
|
||||||
hash - to use for encryption - as a 2-tuple.
|
|
||||||
|
|
||||||
Keyword arguments:
|
|
||||||
- password -- A clear text password
|
|
||||||
- token -- The session token in the form of (id, hash)
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
The authenticated instance
|
|
||||||
|
|
||||||
"""
|
|
||||||
if self.is_authenticated:
|
|
||||||
return self
|
|
||||||
store_session = True if password and token else False
|
|
||||||
|
|
||||||
if token:
|
|
||||||
try:
|
|
||||||
# Extract id and hash from token
|
|
||||||
s_id, s_hash = token
|
|
||||||
except TypeError:
|
|
||||||
if not password:
|
|
||||||
raise MoulinetteError(22, _("Invalid format for token"))
|
|
||||||
else:
|
|
||||||
# TODO: Log error
|
|
||||||
store_session = False
|
|
||||||
else:
|
|
||||||
if password is None:
|
|
||||||
# Retrieve session
|
|
||||||
password = self._retrieve_session(s_id, s_hash)
|
|
||||||
|
|
||||||
try:
|
|
||||||
# Attempt to authenticate
|
|
||||||
self.authenticate(password)
|
|
||||||
except AuthenticationError as e:
|
|
||||||
raise MoulinetteError(13, str(e))
|
|
||||||
except Exception as e:
|
|
||||||
logging.error("authentication (name: '%s', type: '%s') fails: %s" % \
|
|
||||||
(self.name, self.vendor, e))
|
|
||||||
raise MoulinetteError(13, _("Unable to authenticate"))
|
|
||||||
|
|
||||||
# Store session
|
|
||||||
if store_session:
|
|
||||||
self._store_session(s_id, s_hash, password)
|
|
||||||
|
|
||||||
return self
|
|
||||||
|
|
||||||
|
|
||||||
## Private methods
|
|
||||||
|
|
||||||
def _open_sessionfile(self, session_id, mode='r'):
|
|
||||||
"""Open a session file for this instance in given mode"""
|
|
||||||
return pkg.open_cachefile('%s.asc' % session_id, mode,
|
|
||||||
subdir='session/%s' % self.name)
|
|
||||||
|
|
||||||
def _store_session(self, session_id, session_hash, password):
|
|
||||||
"""Store a session and its associated password"""
|
|
||||||
gpg = gnupg.GPG()
|
|
||||||
gpg.encoding = 'utf-8'
|
|
||||||
with self._open_sessionfile(session_id, 'w') as f:
|
|
||||||
f.write(str(gpg.encrypt(password, None, symmetric=True,
|
|
||||||
passphrase=session_hash)))
|
|
||||||
|
|
||||||
def _retrieve_session(self, session_id, session_hash):
|
|
||||||
"""Retrieve a session and return its associated password"""
|
|
||||||
try:
|
|
||||||
with self._open_sessionfile(session_id, 'r') as f:
|
|
||||||
enc_pwd = f.read()
|
|
||||||
except IOError:
|
|
||||||
# TODO: Set proper error code
|
|
||||||
raise MoulinetteError(167, _("Unable to retrieve session"))
|
|
||||||
else:
|
|
||||||
gpg = gnupg.GPG()
|
|
||||||
gpg.encoding = 'utf-8'
|
|
||||||
return str(gpg.decrypt(enc_pwd, passphrase=session_hash))
|
|
||||||
|
|
||||||
|
|
||||||
class LDAPAuthenticator(_BaseAuthenticator):
|
|
||||||
|
|
||||||
def __init__(self, uri, base_dn, user_rdn=None, **kwargs):
|
|
||||||
super(LDAPAuthenticator, self).__init__(**kwargs)
|
|
||||||
|
|
||||||
self.uri = uri
|
|
||||||
self.basedn = base_dn
|
|
||||||
if user_rdn:
|
|
||||||
self.userdn = '%s,%s' % (user_rdn, base_dn)
|
|
||||||
self.con = None
|
|
||||||
else:
|
|
||||||
# Initialize anonymous usage
|
|
||||||
self.userdn = ''
|
|
||||||
self.authenticate(None)
|
|
||||||
|
|
||||||
|
|
||||||
## Implement virtual properties
|
|
||||||
|
|
||||||
vendor = 'ldap'
|
|
||||||
|
|
||||||
@property
|
|
||||||
def is_authenticated(self):
|
|
||||||
try:
|
|
||||||
# Retrieve identity
|
|
||||||
who = self.con.whoami_s()
|
|
||||||
except:
|
|
||||||
return False
|
|
||||||
else:
|
|
||||||
if who[3:] == self.userdn:
|
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
## Implement virtual methods
|
|
||||||
|
|
||||||
def authenticate(self, password):
|
|
||||||
try:
|
|
||||||
con = ldap.initialize(self.uri)
|
|
||||||
if self.userdn:
|
|
||||||
con.simple_bind_s(self.userdn, password)
|
|
||||||
else:
|
|
||||||
con.simple_bind_s()
|
|
||||||
except ldap.INVALID_CREDENTIALS:
|
|
||||||
raise AuthenticationError(_("Invalid password"))
|
|
||||||
else:
|
|
||||||
self.con = con
|
|
||||||
|
|
||||||
|
|
||||||
def init_authenticator(_name, _vendor, **kwargs):
|
|
||||||
if _vendor == 'ldap':
|
|
||||||
return LDAPAuthenticator(name=_name, **kwargs)
|
|
||||||
|
|
||||||
def clean_session(session_id, profiles=[]):
|
def clean_session(session_id, profiles=[]):
|
||||||
sessiondir = pkg.get_cachedir('session')
|
sessiondir = pkg.get_cachedir('session')
|
||||||
|
@ -330,44 +167,9 @@ def clean_session(session_id, profiles=[]):
|
||||||
|
|
||||||
# Moulinette core classes ----------------------------------------------
|
# Moulinette core classes ----------------------------------------------
|
||||||
|
|
||||||
class MoulinetteError(Exception):
|
class MoulinetteError(OSError):
|
||||||
"""Moulinette base exception
|
"""Moulinette base exception"""
|
||||||
|
pass
|
||||||
Keyword arguments:
|
|
||||||
- code -- Integer error code
|
|
||||||
- message -- Error message to display
|
|
||||||
|
|
||||||
"""
|
|
||||||
def __init__(self, code, message):
|
|
||||||
self.code = code
|
|
||||||
self.message = message
|
|
||||||
|
|
||||||
errorcode_desc = {
|
|
||||||
1 : _('Fail'),
|
|
||||||
13 : _('Permission denied'),
|
|
||||||
17 : _('Already exists'),
|
|
||||||
22 : _('Invalid arguments'),
|
|
||||||
87 : _('Too many users'),
|
|
||||||
111 : _('Connection refused'),
|
|
||||||
122 : _('Quota exceeded'),
|
|
||||||
125 : _('Operation canceled'),
|
|
||||||
167 : _('Not found'),
|
|
||||||
168 : _('Undefined'),
|
|
||||||
169 : _('LDAP operation error')
|
|
||||||
}
|
|
||||||
if code in errorcode_desc:
|
|
||||||
self.desc = errorcode_desc[code]
|
|
||||||
else:
|
|
||||||
self.desc = _('Error %s' % code)
|
|
||||||
|
|
||||||
def __str__(self, colorized=False):
|
|
||||||
desc = self.desc
|
|
||||||
if colorized:
|
|
||||||
desc = colorize(self.desc, 'red')
|
|
||||||
return _('%s: %s' % (desc, self.message))
|
|
||||||
|
|
||||||
def colorize(self):
|
|
||||||
return self.__str__(colorized=True)
|
|
||||||
|
|
||||||
|
|
||||||
class MoulinetteLock(object):
|
class MoulinetteLock(object):
|
||||||
|
@ -409,8 +211,8 @@ class MoulinetteLock(object):
|
||||||
break
|
break
|
||||||
|
|
||||||
if (time.time() - start_time) > self.timeout:
|
if (time.time() - start_time) > self.timeout:
|
||||||
raise MoulinetteError(1, _("An instance is already running for '%s'") \
|
raise MoulinetteError(errno.EBUSY, _("An instance is already running for '%s'") \
|
||||||
% self.namespace)
|
% self.namespace)
|
||||||
# Wait before checking again
|
# Wait before checking again
|
||||||
time.sleep(self.interval)
|
time.sleep(self.interval)
|
||||||
self._locked = True
|
self._locked = True
|
||||||
|
|
|
@ -1,10 +1,11 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
import errno
|
||||||
from bottle import run, request, response, Bottle, HTTPResponse
|
from bottle import run, request, response, Bottle, HTTPResponse
|
||||||
from json import dumps as json_encode
|
from json import dumps as json_encode
|
||||||
|
|
||||||
from ..core import MoulinetteError, clean_session
|
from moulinette.core import MoulinetteError, clean_session
|
||||||
from ..helpers import YunoHostError, YunoHostLDAP
|
from moulinette.helpers import YunoHostError, YunoHostLDAP
|
||||||
|
|
||||||
# API helpers ----------------------------------------------------------
|
# API helpers ----------------------------------------------------------
|
||||||
|
|
||||||
|
@ -159,10 +160,9 @@ class _ActionsMapPlugin(object):
|
||||||
if len(s_hashes) > 0:
|
if len(s_hashes) > 0:
|
||||||
try: self.logout(profile)
|
try: self.logout(profile)
|
||||||
except: pass
|
except: pass
|
||||||
# TODO: Replace by proper exception
|
if e.errno == errno.EACCES:
|
||||||
if e.code == 13:
|
raise HTTPUnauthorizedResponse(e.strerror)
|
||||||
raise HTTPUnauthorizedResponse(e.message)
|
raise HTTPErrorResponse(e.strerror)
|
||||||
raise HTTPErrorResponse(e.message)
|
|
||||||
else:
|
else:
|
||||||
# Update dicts with new values
|
# Update dicts with new values
|
||||||
s_hashes[profile] = s_hash
|
s_hashes[profile] = s_hash
|
||||||
|
@ -210,14 +210,14 @@ class _ActionsMapPlugin(object):
|
||||||
try:
|
try:
|
||||||
ret = self.actionsmap.process(arguments, route=_route)
|
ret = self.actionsmap.process(arguments, route=_route)
|
||||||
except MoulinetteError as e:
|
except MoulinetteError as e:
|
||||||
raise HTTPErrorResponse(e.message)
|
raise HTTPErrorResponse(e.strerror)
|
||||||
else:
|
else:
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
|
|
||||||
## Signals handlers
|
## Signals handlers
|
||||||
|
|
||||||
def _do_authenticate(self, authenticator, name, help):
|
def _do_authenticate(self, authenticator, help):
|
||||||
"""Process the authentication
|
"""Process the authentication
|
||||||
|
|
||||||
Handle the actionsmap._AMapSignals.authenticate signal.
|
Handle the actionsmap._AMapSignals.authenticate signal.
|
||||||
|
@ -227,12 +227,12 @@ class _ActionsMapPlugin(object):
|
||||||
try:
|
try:
|
||||||
s_secret = self.secrets[s_id]
|
s_secret = self.secrets[s_id]
|
||||||
s_hash = request.get_cookie('session.hashes',
|
s_hash = request.get_cookie('session.hashes',
|
||||||
secret=s_secret)[name]
|
secret=s_secret)[authenticator.name]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
if name == 'default':
|
if authenticator.name == 'default':
|
||||||
msg = _("Needing authentication")
|
msg = _("Needing authentication")
|
||||||
else:
|
else:
|
||||||
msg = _("Needing authentication to profile '%s'") % name
|
msg = _("Needing authentication to profile '%s'") % authenticator.name
|
||||||
raise HTTPUnauthorizedResponse(msg)
|
raise HTTPUnauthorizedResponse(msg)
|
||||||
else:
|
else:
|
||||||
return authenticator(token=(s_id, s_hash))
|
return authenticator(token=(s_id, s_hash))
|
||||||
|
@ -287,7 +287,12 @@ class MoulinetteAPI(object):
|
||||||
- _port -- Port number to run on
|
- _port -- Port number to run on
|
||||||
|
|
||||||
"""
|
"""
|
||||||
run(self._app, port=_port)
|
try:
|
||||||
|
run(self._app, port=_port)
|
||||||
|
except IOError as e:
|
||||||
|
if e.args[0] == errno.EADDRINUSE:
|
||||||
|
raise MoulinetteError(errno.EADDRINUSE, _("A server is already running"))
|
||||||
|
raise
|
||||||
|
|
||||||
|
|
||||||
## Routes handlers
|
## Routes handlers
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
import errno
|
||||||
import getpass
|
import getpass
|
||||||
|
|
||||||
from ..core import MoulinetteError
|
from moulinette.core import MoulinetteError
|
||||||
|
|
||||||
# CLI helpers ----------------------------------------------------------
|
# CLI helpers ----------------------------------------------------------
|
||||||
|
|
||||||
|
@ -64,7 +65,7 @@ class MoulinetteCLI(object):
|
||||||
"""Moulinette command-line Interface
|
"""Moulinette command-line Interface
|
||||||
|
|
||||||
Initialize an interface connected to the standard input and output
|
Initialize an interface connected to the standard input and output
|
||||||
stream which allows to process moulinette action.
|
stream which allows to process moulinette actions.
|
||||||
|
|
||||||
Keyword arguments:
|
Keyword arguments:
|
||||||
- actionsmap -- The interface relevant ActionsMap instance
|
- actionsmap -- The interface relevant ActionsMap instance
|
||||||
|
@ -90,7 +91,7 @@ class MoulinetteCLI(object):
|
||||||
try:
|
try:
|
||||||
ret = self.actionsmap.process(args, timeout=5)
|
ret = self.actionsmap.process(args, timeout=5)
|
||||||
except KeyboardInterrupt, EOFError:
|
except KeyboardInterrupt, EOFError:
|
||||||
raise MoulinetteError(125, _("Interrupted"))
|
raise MoulinetteError(errno.EINTR, _("Interrupted"))
|
||||||
|
|
||||||
if isinstance(ret, dict):
|
if isinstance(ret, dict):
|
||||||
pretty_print_dict(ret)
|
pretty_print_dict(ret)
|
||||||
|
@ -100,7 +101,7 @@ class MoulinetteCLI(object):
|
||||||
|
|
||||||
## Signals handlers
|
## Signals handlers
|
||||||
|
|
||||||
def _do_authenticate(self, authenticator, name, help):
|
def _do_authenticate(self, authenticator, help):
|
||||||
"""Process the authentication
|
"""Process the authentication
|
||||||
|
|
||||||
Handle the actionsmap._AMapSignals.authenticate signal.
|
Handle the actionsmap._AMapSignals.authenticate signal.
|
||||||
|
@ -124,6 +125,6 @@ class MoulinetteCLI(object):
|
||||||
|
|
||||||
if confirm:
|
if confirm:
|
||||||
if prompt(_('Retype %s: ') % message) != value:
|
if prompt(_('Retype %s: ') % message) != value:
|
||||||
raise MoulinetteError(22, _("Values don't match"))
|
raise MoulinetteError(errno.EINVAL, _("Values don't match"))
|
||||||
|
|
||||||
return value
|
return value
|
||||||
|
|
Loading…
Reference in a new issue