mirror of
https://github.com/YunoHost/moulinette.git
synced 2024-09-03 20:06:31 +02:00
REST API for moulinette :d
This commit is contained in:
parent
aecf43f259
commit
ad7fa4e40b
3 changed files with 257 additions and 112 deletions
111
yunohost
111
yunohost
|
@ -38,121 +38,12 @@ if not __debug__:
|
||||||
gettext.install('YunoHost')
|
gettext.install('YunoHost')
|
||||||
|
|
||||||
try:
|
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:
|
except ImportError:
|
||||||
sys.stderr.write('Error: Yunohost CLI Require YunoHost lib\n')
|
sys.stderr.write('Error: Yunohost CLI Require YunoHost lib\n')
|
||||||
sys.exit(1)
|
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():
|
def main():
|
||||||
"""
|
"""
|
||||||
Main instructions
|
Main instructions
|
||||||
|
|
115
yunohost.py
115
yunohost.py
|
@ -9,11 +9,15 @@ except ImportError:
|
||||||
sys.stderr.write('apt-get install python-ldap\n')
|
sys.stderr.write('apt-get install python-ldap\n')
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
import ldap.modlist as modlist
|
import ldap.modlist as modlist
|
||||||
|
import yaml
|
||||||
import json
|
import json
|
||||||
import re
|
import re
|
||||||
import getpass
|
import getpass
|
||||||
import random
|
import random
|
||||||
import string
|
import string
|
||||||
|
import argparse
|
||||||
|
import gettext
|
||||||
|
import getpass
|
||||||
if not __debug__:
|
if not __debug__:
|
||||||
import traceback
|
import traceback
|
||||||
|
|
||||||
|
@ -93,7 +97,7 @@ def win_msg(astr):
|
||||||
global win
|
global win
|
||||||
if os.isatty(1):
|
if os.isatty(1):
|
||||||
print('\n' + colorize(_("Success: "), 'green') + astr + '\n')
|
print('\n' + colorize(_("Success: "), 'green') + astr + '\n')
|
||||||
else:
|
|
||||||
win.append(astr)
|
win.append(astr)
|
||||||
|
|
||||||
|
|
||||||
|
@ -444,3 +448,112 @@ class YunoHostLDAP(Singleton):
|
||||||
else:
|
else:
|
||||||
raise YunoHostError(17, _('Attribute already exists') + ' "' + attr + '=' + value + '"')
|
raise YunoHostError(17, _('Attribute already exists') + ' "' + attr + '=' + value + '"')
|
||||||
return True
|
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
|
||||||
|
|
141
yunohost.tac
Executable file
141
yunohost.tac
Executable file
|
@ -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()
|
Loading…
Add table
Reference in a new issue