diff --git a/yunohost b/yunohost index 3244e31e..cc86f78f 100755 --- a/yunohost +++ b/yunohost @@ -38,121 +38,12 @@ if not __debug__: gettext.install('YunoHost') try: - from yunohost import YunoHostError, YunoHostLDAP, str_to_func, colorize, pretty_print_dict, display_error, validate, win + from yunohost import YunoHostError, YunoHostLDAP, str_to_func, colorize, pretty_print_dict, display_error, validate, win, parse_dict except ImportError: sys.stderr.write('Error: Yunohost CLI Require YunoHost lib\n') sys.exit(1) -def parse_dict(action_map): - """ - Turn action dictionnary to parser, subparsers and arguments - - Keyword arguments: - action_map -- Multi-level dictionnary of categories/actions/arguments list - - Returns: - Namespace of args - - """ - # Intialize parsers - parsers = subparsers_category = subparsers_action = {} - parsers['general'] = argparse.ArgumentParser() - subparsers = parsers['general'].add_subparsers() - new_args = [] - patterns = {} - - # Add general arguments - for arg_name, arg_params in action_map['general_arguments'].items(): - if 'full' in arg_params: - arg_names = [arg_name, arg_params['full']] - arg_fullname = arg_params['full'] - del arg_params['full'] - else: arg_names = [arg_name] - parsers['general'].add_argument(*arg_names, **arg_params) - - del action_map['general_arguments'] - - # Split categories into subparsers - for category, category_params in action_map.items(): - if 'category_help' not in category_params: category_params['category_help'] = '' - subparsers_category[category] = subparsers.add_parser(category, help=category_params['category_help']) - subparsers_action[category] = subparsers_category[category].add_subparsers() - # Split actions - if 'actions' in category_params: - for action, action_params in category_params['actions'].items(): - if 'action_help' not in action_params: action_params['action_help'] = '' - parsers[category + '_' + action] = subparsers_action[category].add_parser(action, help=action_params['action_help']) - # Set the action s related function - parsers[category + '_' + action].set_defaults( - func=str_to_func('yunohost_' + category + '.' - + category + '_' + action)) - # Add arguments - if 'arguments' in action_params: - for arg_name, arg_params in action_params['arguments'].items(): - arg_fullname = False - - if 'password' in arg_params: - if arg_params['password']: is_password = True - del arg_params['password'] - else: is_password = False - - if 'full' in arg_params: - arg_names = [arg_name, arg_params['full']] - arg_fullname = arg_params['full'] - del arg_params['full'] - else: arg_names = [arg_name] - - if 'ask' in arg_params: - require_input = True - if '-h' in sys.argv or '--help' in sys.argv: - require_input = False - if (category != sys.argv[1]) or (action != sys.argv[2]): - require_input = False - for name in arg_names: - if name in sys.argv[2:]: require_input = False - - if require_input: - if is_password: - if os.isatty(1): - pwd1 = getpass.getpass(colorize(arg_params['ask'] + ': ', 'cyan')) - pwd2 = getpass.getpass(colorize('Retype ' + arg_params['ask'][0].lower() + arg_params['ask'][1:] + ': ', 'cyan')) - if pwd1 != pwd2: - raise YunoHostError(22, _("Passwords don't match")) - sys.exit(1) - else: - raise YunoHostError(22, _("Missing arguments") + ': ' + arg_name) - if arg_name[0] == '-': arg_extend = [arg_name, pwd1] - else: arg_extend = [pwd1] - else: - if os.isatty(1): - arg_value = raw_input(colorize(arg_params['ask'] + ': ', 'cyan')) - else: - raise YunoHostError(22, _("Missing arguments") + ': ' + arg_name) - if arg_name[0] == '-': arg_extend = [arg_name, arg_value] - else: arg_extend = [arg_value] - new_args.extend(arg_extend) - del arg_params['ask'] - - if 'pattern' in arg_params: - if (category == sys.argv[1]) and (action == sys.argv[2]): - if 'dest' in arg_params: name = arg_params['dest'] - elif arg_fullname: name = arg_fullname[2:] - else: name = arg_name - name = name.replace('-', '_') - patterns[name] = arg_params['pattern'] - del arg_params['pattern'] - - parsers[category + '_' + action].add_argument(*arg_names, **arg_params) - - args = parsers['general'].parse_args(sys.argv.extend(new_args)) - args_dict = vars(args) - for key, value in patterns.items(): - validate(value, args_dict[key]) - - return args - - def main(): """ Main instructions diff --git a/yunohost.py b/yunohost.py index 2fc0a4b9..fbaf00ec 100644 --- a/yunohost.py +++ b/yunohost.py @@ -9,11 +9,15 @@ except ImportError: sys.stderr.write('apt-get install python-ldap\n') sys.exit(1) import ldap.modlist as modlist +import yaml import json import re import getpass import random import string +import argparse +import gettext +import getpass if not __debug__: import traceback @@ -93,8 +97,8 @@ def win_msg(astr): global win if os.isatty(1): print('\n' + colorize(_("Success: "), 'green') + astr + '\n') - else: - win.append(astr) + + win.append(astr) @@ -444,3 +448,112 @@ class YunoHostLDAP(Singleton): else: raise YunoHostError(17, _('Attribute already exists') + ' "' + attr + '=' + value + '"') return True + + +def parse_dict(action_map): + """ + Turn action dictionnary to parser, subparsers and arguments + + Keyword arguments: + action_map -- Multi-level dictionnary of categories/actions/arguments list + + Returns: + Namespace of args + + """ + # Intialize parsers + parsers = subparsers_category = subparsers_action = {} + parsers['general'] = argparse.ArgumentParser() + subparsers = parsers['general'].add_subparsers() + new_args = [] + patterns = {} + + # Add general arguments + for arg_name, arg_params in action_map['general_arguments'].items(): + if 'full' in arg_params: + arg_names = [arg_name, arg_params['full']] + arg_fullname = arg_params['full'] + del arg_params['full'] + else: arg_names = [arg_name] + parsers['general'].add_argument(*arg_names, **arg_params) + + del action_map['general_arguments'] + + # Split categories into subparsers + for category, category_params in action_map.items(): + if 'category_help' not in category_params: category_params['category_help'] = '' + subparsers_category[category] = subparsers.add_parser(category, help=category_params['category_help']) + subparsers_action[category] = subparsers_category[category].add_subparsers() + # Split actions + if 'actions' in category_params: + for action, action_params in category_params['actions'].items(): + if 'action_help' not in action_params: action_params['action_help'] = '' + parsers[category + '_' + action] = subparsers_action[category].add_parser(action, help=action_params['action_help']) + # Set the action s related function + parsers[category + '_' + action].set_defaults( + func=str_to_func('yunohost_' + category + '.' + + category + '_' + action)) + # Add arguments + if 'arguments' in action_params: + for arg_name, arg_params in action_params['arguments'].items(): + arg_fullname = False + + if 'password' in arg_params: + if arg_params['password']: is_password = True + del arg_params['password'] + else: is_password = False + + if 'full' in arg_params: + arg_names = [arg_name, arg_params['full']] + arg_fullname = arg_params['full'] + del arg_params['full'] + else: arg_names = [arg_name] + + if 'ask' in arg_params: + require_input = True + if '-h' in sys.argv or '--help' in sys.argv: + require_input = False + if (category != sys.argv[1]) or (action != sys.argv[2]): + require_input = False + for name in arg_names: + if name in sys.argv[2:]: require_input = False + + if require_input: + if is_password: + if os.isatty(1): + pwd1 = getpass.getpass(colorize(arg_params['ask'] + ': ', 'cyan')) + pwd2 = getpass.getpass(colorize('Retype ' + arg_params['ask'][0].lower() + arg_params['ask'][1:] + ': ', 'cyan')) + if pwd1 != pwd2: + raise YunoHostError(22, _("Passwords don't match")) + sys.exit(1) + else: + raise YunoHostError(22, _("Missing arguments") + ': ' + arg_name) + if arg_name[0] == '-': arg_extend = [arg_name, pwd1] + else: arg_extend = [pwd1] + else: + if os.isatty(1): + arg_value = raw_input(colorize(arg_params['ask'] + ': ', 'cyan')) + else: + raise YunoHostError(22, _("Missing arguments") + ': ' + arg_name) + if arg_name[0] == '-': arg_extend = [arg_name, arg_value] + else: arg_extend = [arg_value] + new_args.extend(arg_extend) + del arg_params['ask'] + + if 'pattern' in arg_params: + if (category == sys.argv[1]) and (action == sys.argv[2]): + if 'dest' in arg_params: name = arg_params['dest'] + elif arg_fullname: name = arg_fullname[2:] + else: name = arg_name + name = name.replace('-', '_') + patterns[name] = arg_params['pattern'] + del arg_params['pattern'] + + parsers[category + '_' + action].add_argument(*arg_names, **arg_params) + + args = parsers['general'].parse_args(sys.argv.extend(new_args)) + args_dict = vars(args) + for key, value in patterns.items(): + validate(value, args_dict[key]) + + return args diff --git a/yunohost.tac b/yunohost.tac new file mode 100755 index 00000000..bd3ecb09 --- /dev/null +++ b/yunohost.tac @@ -0,0 +1,141 @@ +# -*- mode: python -*- +import os +import sys +import gettext +import ldap +import yaml +import json +from twisted.python import log +from twisted.web.server import Site +from twisted.web.resource import IResource +from twisted.web.guard import HTTPAuthSessionWrapper, BasicCredentialFactory +from twisted.internet import reactor, defer +from twisted.cred.portal import IRealm, Portal +from twisted.cred.checkers import ICredentialsChecker +from twisted.cred.credentials import IUsernamePassword +from twisted.cred.error import UnauthorizedLogin +from zope.interface import implements +from txrestapi.resource import APIResource +from yunohost import YunoHostError, YunoHostLDAP, str_to_func, colorize, pretty_print_dict, display_error, validate, win, parse_dict + +if not __debug__: + import traceback + +gettext.install('YunoHost') + +class LDAPHTTPAuth(): + implements (ICredentialsChecker) + + credentialInterfaces = IUsernamePassword, + + def requestAvatarId(self, credentials): + try: + if credentials.username != "admin": + raise YunoHostError(22, _("Invalid username") + ': ' + credentials.username) + YunoHostLDAP(password=credentials.password) + return credentials.username + + except Exception as e: + raise defer.fail(UnauthorizedLogin("Unable to verify password for "+ credentials.username)) #TODO: Reask for password + + +class SimpleRealm(object): + implements(IRealm) + + _api = None + + def __init__(self, api): + self._api = api + + def requestAvatar(self, avatarId, mind, *interfaces): + if IResource in interfaces: + return IResource, self._api, lambda: None + raise NotImplementedError() + + +action_dict = {} + +def http_exec(request): + global win + dict = action_dict[request.method+' '+request.path] + args = dict['arguments'] + for arg, params in args.items(): + sanitized_key = arg.replace('-', '_') + if sanitized_key is not arg: + args[sanitized_key] = args[arg] + del args[arg] + arg = sanitized_key + if arg[0] == '_': + if 'nargs' not in params: + args[arg]['nargs'] = '*' + if 'full' in params: + new_key = params['full'][2:] + else: + new_key = arg[2:] + args[new_key] = args[arg] + del args[arg] + + try: + validated_args = {} + for key, value in request.args.items(): + if key in args: + # Validate args + if 'pattern' in args[key]: validate(args[key]['pattern'], value) + if 'nargs' not in args[key] or ('nargs' != '*' and 'nargs' != '+'): value = value[0] + if 'action' in args[key] and args[key]['action'] == 'store_true': + yes = ['true', 'True', 'yes', 'Yes'] + value = value in yes + validated_args[key] = value + + func = str_to_func(dict['function']) + with YunoHostLDAP(password=request.getPassword()): + result = func(**validated_args) + if result is None: + result = {} + if win: + result['win'] = win + win = [] + except YunoHostError, error: + result = { "error" : str(error.code) +' : '+ error.message } + + return json.dumps(result) + + +def main(): + global action_dict + log.startLogging(sys.stdout) + api = APIResource() + + with open('action_map.yml') as f: + action_map = yaml.load(f) + + del action_map['general_arguments'] + for category, category_params in action_map.items(): + for action, action_params in category_params['actions'].items(): + if 'help' not in action_params: + action_params['help'] = '' + if 'api' not in action_params: + action_params['api'] = 'GET /'+ category +'/'+ action + method, path = action_params['api'].split(' ') + api.register(method, path, http_exec) + action_dict[action_params['api']] = { + 'function': 'yunohost_'+ category +'.'+ category +'_'+ action, + 'help' : action_params['help'] + } + if 'arguments' in action_params: + action_dict[action_params['api']]['arguments'] = action_params['arguments'] + + ldap_auth = LDAPHTTPAuth() + credentialFactory = BasicCredentialFactory("Restricted Area") + resource = HTTPAuthSessionWrapper(Portal(SimpleRealm(api), [ldap_auth]), [credentialFactory]) + try: + with open('/etc/yunohost/installed') as f: pass + except IOError: + resource = APIResource() + resource.register('POST', '/postinstall', http_exec) + reactor.listenTCP(6767, Site(resource, timeout=None)) + reactor.run() + + +if __name__ == '__main__': + main()