create risotto admin and set rights

This commit is contained in:
Emmanuel Garette 2020-01-14 14:11:41 +01:00
parent 722d4894a1
commit cb0e4b5d5d
11 changed files with 132 additions and 33 deletions

View File

@ -8,6 +8,7 @@ pattern: rpc
parameters: parameters:
server_name: server_name:
type: String type: String
shortarg: s
ref: Server.ServerName ref: Server.ServerName
description: Nom du serveur. description: Nom du serveur.

View File

@ -8,7 +8,7 @@ pattern: rpc
parameters: parameters:
user_login: user_login:
type: String type: String
shortarg: l shortarg: u
description: Login de l'utilisateur. description: Login de l'utilisateur.
ref: User.UserLogin ref: User.UserLogin
role_name: role_name:

View File

@ -1,4 +1,3 @@
HTTP_PORT = 8080
MESSAGE_ROOT_PATH = 'messages' MESSAGE_ROOT_PATH = 'messages'
DEBUG = True DEBUG = True
DATABASE_DIR = 'database' DATABASE_DIR = 'database'
@ -9,6 +8,7 @@ TMP_DIR = 'tmp'
ROUGAIL_DTD_PATH = '../rougail/data/creole.dtd' ROUGAIL_DTD_PATH = '../rougail/data/creole.dtd'
POSTGRESQL_ADDRESS = '192.168.56.106' POSTGRESQL_ADDRESS = '192.168.56.106'
POSTGRESQL_PORT = 5432 POSTGRESQL_PORT = 5432
DEFAULT_USER = 'Anonymous'
import os import os
from pathlib import PurePosixPath from pathlib import PurePosixPath
@ -21,12 +21,14 @@ def get_config():
'user': 'risotto', 'user': 'risotto',
'password': 'risotto', 'password': 'risotto',
}, },
'http_server': {'port': 8080}, 'http_server': {'port': 8080,
'default_user': DEFAULT_USER},
'global': {'message_root_path': CURRENT_PATH.parents[2] / 'messages', 'global': {'message_root_path': CURRENT_PATH.parents[2] / 'messages',
'debug': DEBUG, 'debug': DEBUG,
'internal_user': 'internal', 'internal_user': 'internal',
'check_role': False, 'check_role': True,
'rougail_dtd_path': '../rougail/data/creole.dtd'}, 'rougail_dtd_path': '../rougail/data/creole.dtd',
'admin_user': DEFAULT_USER},
'source': {'root_path': '/srv/seed'}, 'source': {'root_path': '/srv/seed'},
'cache': {'root_path': '/var/cache/risotto'} 'cache': {'root_path': '/var/cache/risotto'}
} }

View File

@ -275,10 +275,12 @@ class Dispatcher(register.RegisterDispatcher, CallDispatcher, PublishDispatcher)
kwargs, kwargs,
check_role) check_role)
except Exception as err: except Exception as err:
# if there is a problem with arguments, just send an error et do nothing # if there is a problem with arguments, just send an error and do nothing
if DEBUG: if DEBUG:
print_exc() print_exc()
await log.error_msg(risotto_context, kwargs, err) await log.error_msg(risotto_context, kwargs, err)
if risotto_context.type == 'rpc':
raise err
return return
# config is ok, so send the message # config is ok, so send the message
for function_obj in function_objs: for function_obj in function_objs:

View File

@ -10,13 +10,14 @@ from .context import Context
from .error import CallError, NotAllowedError, RegistrationError from .error import CallError, NotAllowedError, RegistrationError
from .message import get_messages from .message import get_messages
from .logger import log from .logger import log
from .config import DEBUG, HTTP_PORT from .config import get_config
from .services import load_services from .services import load_services
def create_context(request): def create_context(request):
risotto_context = Context() risotto_context = Context()
risotto_context.username = request.match_info.get('username', "Anonymous") risotto_context.username = request.match_info.get('username',
get_config()['http_server']['default_user'])
return risotto_context return risotto_context
@ -52,7 +53,7 @@ class extra_route_handler:
except CallError as err: except CallError as err:
raise HTTPBadRequest(reason=str(err)) raise HTTPBadRequest(reason=str(err))
except Exception as err: except Exception as err:
if DEBUG: if get_config()['global']['debug']:
print_exc() print_exc()
raise HTTPInternalServerError(reason=str(err)) raise HTTPInternalServerError(reason=str(err))
# await log.info_msg(kwargs['risotto_context'], # await log.info_msg(kwargs['risotto_context'],
@ -80,7 +81,7 @@ async def handle(request):
except CallError as err: except CallError as err:
raise HTTPBadRequest(reason=str(err).replace('\n', ' ')) raise HTTPBadRequest(reason=str(err).replace('\n', ' '))
except Exception as err: except Exception as err:
if DEBUG: if get_config()['global']['debug']:
print_exc() print_exc()
raise HTTPInternalServerError(reason=str(err)) raise HTTPInternalServerError(reason=str(err))
return Response(text=dumps({'response': text})) return Response(text=dumps({'response': text}))
@ -89,7 +90,19 @@ async def handle(request):
async def api(request, risotto_context): async def api(request, risotto_context):
global tiramisu global tiramisu
if not tiramisu: if not tiramisu:
config = await Config(get_messages(load_shortarg=True)[1]) # check all URI that have an associated role
# all URI without role is concidered has a private URI
uris = []
async with dispatcher.pool.acquire() as connection:
async with connection.transaction():
# Check role with ACL
sql = '''
SELECT URI.URIName
FROM URI, RoleURI
WHERE RoleURI.URIId = URI.URIId
'''
uris = [uri['uriname'] for uri in await connection.fetch(sql)]
config = await Config(get_messages(load_shortarg=True, uris=uris)[1])
await config.property.read_write() await config.property.read_write()
tiramisu = await config.option.dict(remotable='none') tiramisu = await config.option.dict(remotable='none')
return tiramisu return tiramisu
@ -129,7 +142,7 @@ async def get_app(loop):
del extra_routes del extra_routes
app.add_routes(routes) app.add_routes(routes)
await dispatcher.on_join() await dispatcher.on_join()
return await loop.create_server(app.make_handler(), '*', HTTP_PORT) return await loop.create_server(app.make_handler(), '*', get_config()['http_server']['port'])
tiramisu = None tiramisu = None

View File

@ -237,13 +237,16 @@ def split_message_uri(uri):
def get_message_file_path(version, message): def get_message_file_path(version, message):
return join(MESSAGE_ROOT_PATH, version, 'messages', message + '.yml') return join(MESSAGE_ROOT_PATH, version, 'messages', message + '.yml')
def list_messages(): def list_messages(uris):
messages = listdir(MESSAGE_ROOT_PATH) messages = listdir(MESSAGE_ROOT_PATH)
messages.sort() messages.sort()
for version in messages: for version in messages:
for message in listdir(join(MESSAGE_ROOT_PATH, version, 'messages')): for message in listdir(join(MESSAGE_ROOT_PATH, version, 'messages')):
if message.endswith('.yml'): if message.endswith('.yml'):
yield version + '.' + message.rsplit('.', 1)[0] uri = version + '.' + message.rsplit('.', 1)[0]
if uris is not None and uri not in uris:
continue
yield uri
class CustomParam: class CustomParam:
__slots__ = ('name', __slots__ = ('name',
@ -573,14 +576,15 @@ def _get_root_option(select_option, optiondescriptions):
return OptionDescription('root', 'root', options_obj) return OptionDescription('root', 'root', options_obj)
def get_messages(load_shortarg=False): def get_messages(load_shortarg=False,
uris=None):
"""generate description from yml files """generate description from yml files
""" """
optiondescriptions = OrderedDict() optiondescriptions = OrderedDict()
optiondescriptions_name = [] optiondescriptions_name = []
optiondescriptions_info = {} optiondescriptions_info = {}
needs = OrderedDict() needs = OrderedDict()
messages = list(list_messages()) messages = list(list_messages(uris))
messages.sort() messages.sort()
for message_name in messages: for message_name in messages:
message_def = get_message(message_name) message_def = get_message(message_name)

View File

@ -2,6 +2,7 @@ from tiramisu import Config
from inspect import signature from inspect import signature
from typing import Callable, Optional from typing import Callable, Optional
import asyncpg import asyncpg
from json import dumps, loads
from .utils import _ from .utils import _
from .error import RegistrationError from .error import RegistrationError
@ -219,12 +220,21 @@ class RegisterDispatcher:
raise RegistrationError(_(f'missing uri {missing_messages}')) raise RegistrationError(_(f'missing uri {missing_messages}'))
async def on_join(self): async def on_join(self):
for module_name, module in self.injected_self.items(): async with self.pool.acquire() as connection:
risotto_context = Context() await connection.set_type_codec(
risotto_context.username = INTERNAL_USER 'json',
risotto_context.paths.append(f'{module_name}.on_join') encoder=dumps,
risotto_context.type = None decoder=loads,
await module.on_join(risotto_context) schema='pg_catalog'
)
async with connection.transaction():
for module_name, module in self.injected_self.items():
risotto_context = Context()
risotto_context.username = INTERNAL_USER
risotto_context.paths.append(f'{module_name}.on_join')
risotto_context.type = None
risotto_context.connection = connection
await module.on_join(risotto_context)
async def insert_message(self, async def insert_message(self,
connection, connection,

View File

@ -369,7 +369,7 @@ class Risotto(Controller):
# await child.information.get('servermodel_id'), # await child.information.get('servermodel_id'),
# servermodel_id) # servermodel_id)
@register('v1.config.configuration.server.get', None) @register('v1.config.configuration.server.get')
async def get_configuration(self, async def get_configuration(self,
risotto_context: Context, risotto_context: Context,
server_name: str, server_name: str,
@ -412,6 +412,7 @@ class Risotto(Controller):
server_id: int) -> Dict: server_id: int) -> Dict:
"""Copy values, permissions, permissives from config 'to deploy' to active config """Copy values, permissions, permissives from config 'to deploy' to active config
""" """
# FIXME ?
config = self.server[server_id]['server'] config = self.server[server_id]['server']
config_std = self.server[server_id]['server_to_deploy'] config_std = self.server[server_id]['server_to_deploy']

View File

@ -25,7 +25,6 @@ class Risotto(Controller):
servermodel_name: str, servermodel_name: str,
source_name: str, source_name: str,
release_distribution: str) -> Dict: release_distribution: str) -> Dict:
servermodel = await self.call('v1.servermodel.describe', servermodel = await self.call('v1.servermodel.describe',
risotto_context, risotto_context,
servermodel_name=servermodel_name, servermodel_name=servermodel_name,
@ -39,6 +38,10 @@ class Risotto(Controller):
server_name, server_name,
server_description, server_description,
servermodel['servermodel_id']) servermodel['servermodel_id'])
await self.call('v1.user.role.server.create',
risotto_context,
user_login=risotto_context.username,
server_name=server_name)
return {'server_id': server_id, return {'server_id': server_id,
'server_name': server_name, 'server_name': server_name,
'server_description': server_description, 'server_description': server_description,

View File

@ -24,6 +24,7 @@ class Risotto(Controller):
'v1.user.delete', 'v1.user.delete',
'v1.user.list', 'v1.user.list',
'v1.user.role.create', 'v1.user.role.create',
'v1.user.role.server.create',
'v1.config.configuration.server.get', 'v1.config.configuration.server.get',
'v1.user.role.list']: 'v1.user.role.list']:
try: try:
@ -63,7 +64,7 @@ class Risotto(Controller):
pass pass
@register('v1.uri.role.join') @register('v1.uri.role.join')
async def _uri_role_join(self, async def uri_role_join(self,
risotto_context: Context, risotto_context: Context,
role_name: str, role_name: str,
uri_name: str) -> Dict: uri_name: str) -> Dict:

View File

@ -4,15 +4,37 @@ from ...controller import Controller
from ...register import register from ...register import register
from ...context import Context from ...context import Context
from ...utils import _ from ...utils import _
from ...config import get_config
class Risotto(Controller): class Risotto(Controller):
@register('v1.user.create') async def on_join(self,
async def user_create(self, risotto_context: Context) -> None:
risotto_context: Context, """ pre-load servermodel and server
user_login: str, """
user_name: str, user_login = get_config()['global']['admin_user']
user_surname: str) -> Dict: sql = '''
SELECT UserId
FROM RisottoUser
WHERE UserLogin = $1
'''
if await risotto_context.connection.fetchval(sql,
user_login) is None:
await self._user_create(risotto_context,
user_login,
user_login,
user_login)
await self._user_role_create(risotto_context,
user_login,
'administrator',
None,
None)
async def _user_create(self,
risotto_context: Context,
user_login: str,
user_name: str,
user_surname: str) -> Dict:
user_insert = """INSERT INTO RisottoUser(UserLogin, UserName, UserSurname) user_insert = """INSERT INTO RisottoUser(UserLogin, UserName, UserSurname)
VALUES ($1,$2,$3) VALUES ($1,$2,$3)
RETURNING UserId RETURNING UserId
@ -30,6 +52,17 @@ class Risotto(Controller):
'user_name': user_name, 'user_name': user_name,
'user_surname': user_surname} 'user_surname': user_surname}
@register('v1.user.create')
async def user_create(self,
risotto_context: Context,
user_login: str,
user_name: str,
user_surname: str) -> Dict:
return await self._user_create(risotto_context,
user_login,
user_name,
user_surname)
@register('v1.user.list') @register('v1.user.list')
async def user_list(self, async def user_list(self,
risotto_context: Context) -> Dict: risotto_context: Context) -> Dict:
@ -55,8 +88,7 @@ class Risotto(Controller):
raise Exception(_(f'unable to find user {user_login}')) raise Exception(_(f'unable to find user {user_login}'))
return dict(user) return dict(user)
@register('v1.user.role.create') async def _user_role_create(self,
async def user_role_create(self,
risotto_context: Context, risotto_context: Context,
user_login: str, user_login: str,
role_name: str, role_name: str,
@ -87,6 +119,19 @@ class Risotto(Controller):
'role_attribute': role_attribute, 'role_attribute': role_attribute,
'role_attribute_value': role_attribute_value} 'role_attribute_value': role_attribute_value}
@register('v1.user.role.create')
async def user_role_create(self,
risotto_context: Context,
user_login: str,
role_name: str,
role_attribute: str,
role_attribute_value: str) -> Dict:
return await self._user_role_create(risotto_context,
user_login,
role_name,
role_attribute,
role_attribute_value)
@register('v1.user.role.list') @register('v1.user.role.list')
async def user_role_list(self, async def user_role_list(self,
risotto_context: Context, risotto_context: Context,
@ -145,3 +190,20 @@ class Risotto(Controller):
# if role is None: # if role is None:
# raise Exception(_(f'unable to find role {role_name}')) # raise Exception(_(f'unable to find role {role_name}'))
# return dict(role) # return dict(role)
@register('v1.user.role.server.create')
async def user_role_server_create(self,
risotto_context: Context,
user_login: str,
server_name: str) -> Dict:
ret = []
for uri in ['v1.server.describe',
'v1.config.configuration.server.get',
'v1.config.configuration.server.deploy',
'v1.session.server.start',
'v1.template.generate']:
ret.append(await self.call('v1.user.role.create',
risotto_context,
user_login=user_login,
role_name='server_rw'))
return ret