Compare commits

..

5 Commits
docker ... jwt

Author SHA1 Message Date
424273360d verify audience in jwt 2020-01-30 15:46:18 +01:00
ae6dfb2644 add audience claim data to jwt 2020-01-24 15:07:30 +01:00
d78d581c65 implement basicauth and jwt token 2020-01-24 13:43:14 +01:00
52c878b0ab replace all zephir occurence 2020-01-23 14:05:07 +01:00
5666c01bdc update docker 2020-01-23 11:01:38 +01:00
41 changed files with 157 additions and 51 deletions

View File

@ -26,7 +26,8 @@ docker exec -ti postgres bash
psql -U postgres -h localhost -c "CREATE ROLE risotto WITH LOGIN PASSWORD 'risotto';"
psql -U postgres -h localhost -c "CREATE DATABASE risotto;"
psql -U postgres -h localhost -c "GRANT ALL ON DATABASE risotto TO risotto;"
psql -U postgres -h localhost -c "CREATE EXTENSION hstore;" risotto
psql -U postgres -h localhost -c "CREATE EXTENSION hstore;"
psql -U postgres -h localhost -c "CREATE EXTENSION pgcrypto;"
```
Gestion de la base de données avec Sqitch

View File

@ -20,7 +20,7 @@ RUN ln -s /srv/src/tiramisu/tiramisu /usr/local/lib/python3.7
RUN ln -s /srv/src/rougail/src/rougail /usr/local/lib/python3.7
RUN ln -s /srv/src/risotto/src/risotto /usr/local/lib/python3.7
RUN pip install Cheetah3
RUN pip install Cheetah3 PyJWT
RUN cd /srv/src/risotto && pip install -r requirements.txt
# Installation

View File

@ -6,6 +6,7 @@ services:
dockerfile: docker/Dockerfile
volumes:
- ../.:/srv/src/risotto
- ../messages:/usr/local/lib/messages
ports:
- "8080:8080"
depends_on:
@ -13,8 +14,8 @@ services:
links:
- postgres
#command: tail -F /var/log
command: python /srv/src/risotto/script/server.py
restart: unless-stopped
command: python -u /srv/src/risotto/script/server.py
restart: on-failure
postgres:
image: postgres:11-alpine
environment:

View File

@ -7,6 +7,7 @@ psql --username "$POSTGRES_USER" <<-EOSQL
GRANT ALL ON DATABASE risotto TO risotto;
\c risotto
CREATE EXTENSION hstore;
CREATE EXTENSION pgcrypto;
EOSQL
psql --username "risotto" --password "risotto" <<-EOSQL
@ -64,6 +65,7 @@ psql --username "risotto" --password "risotto" <<-EOSQL
CREATE TABLE RisottoUser (
UserId SERIAL PRIMARY KEY,
UserLogin VARCHAR(100) NOT NULL UNIQUE,
UserPassword TEXT NOT NULL,
UserName VARCHAR(100) NOT NULL,
UserSurname VARCHAR(100) NOT NULL
);

View File

@ -5,7 +5,7 @@ description: |
Retourne des informations sur la session HTTP courante de l'utilisateur.
sampleuse: |
zephir-client identity.session-user.get
cucchiaiata identity.session-user.get
pattern: rpc

View File

@ -5,7 +5,7 @@ description: |
Retourne les préférences de l'utilisateur spécifié.
sampleuse: |
zephir-client identity.settings.get -u yo
cucchiaiata identity.settings.get -u yo
pattern: rpc

View File

@ -5,7 +5,7 @@ description: |
Enregistre des préférences pour l'utilisateur spécifié.
sampleuse: |
zephir-client identity.settings.set -u yo
cucchiaiata identity.settings.set -u yo
pattern: rpc

View File

@ -11,7 +11,7 @@ pattern: rpc
domain: server-domain
sampleuse: |
zephir-client server.delete -s 1
cucchiaiata server.delete -s 1
parameters:
serverid:

View File

@ -5,7 +5,7 @@ description: |
Transmet une commande à exécuter sur un serveur donné.
sampleuse: |
zephir-client server.exec.command -s 1 -c reconfigure
cucchiaiata server.exec.command -s 1 -c reconfigure
domain: server-domain

View File

@ -5,7 +5,7 @@ description: |
Déploie la configuration sur un serveur donné.
sampleuse: |
zephir-client server.exec.deploy -s 1
cucchiaiata server.exec.deploy -s 1
domain: server-domain

View File

@ -5,7 +5,7 @@ description: |
Liste les commandes exécuté pour un identifiant de tâche.
sampleuse: |
zephir-client server.exec.list -j 1
cucchiaiata server.exec.list -j 1
domain: execution-domain

View File

@ -5,7 +5,7 @@ description: |
Liste les commandes exécutées sur un serveur donné.
sampleuse: |
zephir-client server.exec.list -s 1
cucchiaiata server.exec.list -s 1
domain: execution-domain

View File

@ -5,7 +5,7 @@ description: |
Retourne la liste des sélections de serveurs d'un serveur
sampleuse: |
zephir-client server.serverselection.list
cucchiaiata server.serverselection.list
pattern: rpc

View File

@ -7,7 +7,7 @@ description: |
public: true
sampleuse: |
zephir-client server.update -s 1 -n toto -d "server description"
cucchiaiata server.update -s 1 -n toto -d "server description"
pattern: rpc

View File

@ -5,7 +5,7 @@ description: |
Associe un service applicatif à un modèle de serveur.
sampleuse: |
zephir-client servermodel.applicationservice.join -m 1 -s 1
cucchiaiata servermodel.applicationservice.join -m 1 -s 1
pattern: rpc

View File

@ -5,7 +5,7 @@ description: |
Retourne la liste des applications service.
sampleuse: |
zephir-client servermodel.applicationservice.list -s 6
cucchiaiata servermodel.applicationservice.list -s 6
pattern: rpc

View File

@ -5,7 +5,7 @@ description: |
Crée un modèle de serveur.
sampleuse: |
zephir-client servermodel.create -p 1 -n "MonServeurModele" -d "Ma description" -s 1
cucchiaiata servermodel.create -p 1 -n "MonServeurModele" -d "Ma description" -s 1
pattern: rpc

View File

@ -5,7 +5,7 @@ description: |
Retourne la liste des subreleases.
sampleuse: |
zephir-client servermodel.subrelease.list
cucchiaiata servermodel.subrelease.list
pattern: rpc

View File

@ -5,7 +5,7 @@ description: |
Crée un sélection de serveurs.
sampleuse: |
zephir-client serverselection.create -n Select1 -d "Ma description"
cucchiaiata serverselection.create -n Select1 -d "Ma description"
pattern: rpc

View File

@ -5,7 +5,7 @@ description: |
Supprime une sélection de serveurs.
sampleuse: |
zephir-client serverselection.delete -s 1
cucchiaiata serverselection.delete -s 1
pattern: rpc

View File

@ -6,7 +6,7 @@ description: |
sampleuse: |
zephir-client serverselection.describe -s 1
cucchiaiata serverselection.describe -s 1
pattern: rpc

View File

@ -5,7 +5,7 @@ description: |
Transmet une commande à exécuter sur une sélection de serveurs.
sampleuse: |
zephir-client serverselection.exec.command -s 1 -c reconfigure
cucchiaiata serverselection.exec.command -s 1 -c reconfigure
domain: server-domain

View File

@ -5,7 +5,7 @@ description: |
Déploie la configuration sur les serveurs d'une sélection de serveurs.
sampleuse: |
zephir-client serverselection.exec.deploy -s 1
cucchiaiata serverselection.exec.deploy -s 1
domain: server-domain

View File

@ -5,7 +5,7 @@ description: |
Retourne la liste des sélections de serveurs.
sampleuse: |
zephir-client serverselection.list
cucchiaiata serverselection.list
pattern: rpc

View File

@ -5,7 +5,7 @@ description: |
Ajoute un serveur à une sélection de serveurs.
sampleuse: |
zephir-client serverselection.add.server -s 1 -i 1
cucchiaiata serverselection.add.server -s 1 -i 1
pattern: rpc

View File

@ -5,7 +5,7 @@ description: |
Supprime un serveur d'une sélection de serveurs.
sampleuse: |
zephir-client serverselection.remove.server -s 1 -i 1
cucchiaiata serverselection.remove.server -s 1 -i 1
pattern: rpc

View File

@ -5,7 +5,7 @@ description: |
Renseigne une liste de serveur dans une sélection de serveurs.
sampleuse: |
zephir-client serverselection.server.set -s 1 -i 1
cucchiaiata serverselection.server.set -s 1 -i 1
pattern: rpc

View File

@ -5,7 +5,7 @@ description: |
Modifie une sélection de serveur.
sampleuse: |
zephir-client serverselection.update -s 1 -n Select1 -d "Ma description"
cucchiaiata serverselection.update -s 1 -n Select1 -d "Ma description"
pattern: rpc

View File

@ -5,7 +5,7 @@ description: |
Associe un utilisateur à une sélection de serveurs.
sampleuse: |
zephir-client serverselection.add.user -s 1 -u yo -r admin
cucchiaiata serverselection.add.user -s 1 -u yo -r admin
pattern: rpc

View File

@ -5,7 +5,7 @@ description: |
Retourne la sélection de serveurs par défaut de l'utilisateur.
sampleuse: |
zephir-client serverselection.user.default
cucchiaiata serverselection.user.default
pattern: rpc

View File

@ -5,7 +5,7 @@ description: |
Retourne les sélections de serveurs dont l'utilisateur fait parti.
sampleuse: |
zephir-client serverselection.user.list
cucchiaiata serverselection.user.list
pattern: rpc

View File

@ -5,7 +5,7 @@ description: |
Dissocie un utilisateur d'une sélection de serveurs.
sampleuse: |
zephir-client serverselection.remove.user -s 1 -u yo
cucchiaiata serverselection.remove.user -s 1 -u yo
pattern: rpc

View File

@ -5,7 +5,7 @@ description: |
Retourne le rôle d'utilisateur sur une selection de serveurs.
sampleuse: |
zephir-client serverselection.user.role.get -d '{}'
cucchiaiata serverselection.user.role.get -d '{}'
pattern: rpc

View File

@ -5,7 +5,7 @@ description: |
Retourne le rôle d'un utlisateur sur un serveur.
sampleuse: |
zephir-client serverselection.user.role.server.get -d '{}'
cucchiaiata serverselection.user.role.server.get -d '{}'
pattern: rpc

View File

@ -5,7 +5,7 @@ description: |
Modifie le rôle d'un utilisateur pour une sélection de serveurs.
sampleuse: |
zephir-client serverselection.update.user -s 1 -u yo -r admin
cucchiaiata serverselection.update.user -s 1 -u yo -r admin
pattern: rpc

View File

@ -11,6 +11,10 @@ parameters:
shortarg: l
description: Login de l'utilisateur.
ref: User.Login
user_password:
type: String
shortarg: p
description: Password de l'utilisateur.
user_name:
type: String
shortarg: n

View File

@ -57,6 +57,7 @@ CREATE TABLE Server (
CREATE TABLE RisottoUser (
UserId SERIAL PRIMARY KEY,
UserLogin VARCHAR(100) NOT NULL UNIQUE,
UserPassword TEXT NOT NULL,
UserName VARCHAR(100) NOT NULL,
UserSurname VARCHAR(100) NOT NULL
);

View File

@ -5,7 +5,13 @@ CONFIGURATION_DIR = 'configurations'
TEMPLATE_DIR = 'templates'
TMP_DIR = 'tmp'
ROUGAIL_DTD_PATH = '../rougail/data/creole.dtd'
DEFAULT_USER = 'Anonymous'
DEFAULT_USER = 'gnunux'
DEFAULT_USER_PASSWORD = 'gnunux'
URI = 'http://localhost'
PORT = 8080
JWT_SECRET = 'MY_SUPER_SECRET'
JWT_TOKEN_EXPIRE = 3600
JWT_TOKEN_AUDIENCE = "Risotto"
import os
from pathlib import PurePosixPath
@ -19,16 +25,21 @@ def get_config():
'user': 'risotto',
'password': 'risotto',
},
'http_server': {'port': 8080,
#'default_user': "gnunux"},
'http_server': {'port': PORT,
'default_user': DEFAULT_USER},
'global': {'message_root_path': CURRENT_PATH.parents[2] / 'messages',
'debug': True,
'internal_user': 'internal',
'check_role': True,
'rougail_dtd_path': '../rougail/data/creole.dtd',
'admin_user': DEFAULT_USER},
'admin_user': DEFAULT_USER,
'admin_user_password': DEFAULT_USER_PASSWORD},
'source': {'root_path': '/srv/seed'},
'cache': {'root_path': '/var/cache/risotto'}
'cache': {'root_path': '/var/cache/risotto'},
'jwt': {
'secret': JWT_SECRET,
'token_expire': JWT_TOKEN_EXPIRE,
'issuer': URI,
'audience': JWT_TOKEN_AUDIENCE}
}

View File

@ -1,8 +1,10 @@
from aiohttp.web import Application, Response, get, post, HTTPBadRequest, HTTPInternalServerError, HTTPNotFound
from aiohttp.web import Application, Response, get, post, HTTPBadRequest, HTTPInternalServerError, HTTPNotFound, HTTPUnauthorized
from aiohttp import BasicAuth, RequestInfo
from json import dumps
from traceback import print_exc
from tiramisu import Config
import datetime
import jwt
from .dispatcher import dispatcher
from .utils import _
@ -16,7 +18,17 @@ from .services import load_services
def create_context(request):
risotto_context = Context()
risotto_context.username = request.match_info.get('username',
if 'Authorization' in request.headers:
token = request.headers['Authorization']
if not token.startswith("Bearer "):
raise HTTPBadRequest(reason='Unexpected bearer format')
token = token[7:]
decoded = verify_token(token)
if 'user' in decoded:
risotto_context.username = decoded['user']
return risotto_context
else:
risotto_context.username = request.match_info.get('username',
get_config()['http_server']['default_user'])
return risotto_context
@ -49,7 +61,7 @@ class extra_route_handler:
try:
returns = await cls.function(**kwargs)
except NotAllowedError as err:
raise HTTPNotFound(reason=str(err))
raise HTTPUnauthorized(reason=str(err))
except CallError as err:
raise HTTPBadRequest(reason=str(err))
except Exception as err:
@ -77,7 +89,7 @@ async def handle(request):
check_role=True,
**kwargs)
except NotAllowedError as err:
raise HTTPNotFound(reason=str(err))
raise HTTPUnauthorized(reason=str(err))
except CallError as err:
raise HTTPBadRequest(reason=str(err).replace('\n', ' '))
except Exception as err:
@ -141,8 +153,76 @@ async def get_app(loop):
print()
del extra_routes
app.add_routes(routes)
app.router.add_post('/auth', auth)
app.router.add_post('/access_token', access_token)
await dispatcher.on_join()
return await loop.create_server(app.make_handler(), '*', get_config()['http_server']['port'])
async def auth(request):
auth_code = request.headers['Authorization']
if not auth_code.startswith("Basic "):
raise HTTPBadRequest(reason='Unexpected bearer format')
auth = BasicAuth.decode(auth_code)
async with dispatcher.pool.acquire() as connection:
async with connection.transaction():
# Check role with ACL
sql = '''
SELECT UserName
FROM RisottoUser
WHERE UserLogin = $1
AND UserPassword = crypt($2, UserPassword);
'''
res = await connection.fetch(sql, auth.login, auth.password)
if res:
res = gen_token(auth)
if verify_token(res):
return Response(text=str(res.decode('utf-8')))
else:
return HTTPInternalServerError(reason='Token could not be verified just after creation')
else:
raise HTTPUnauthorized(reason='Unauthorized')
def gen_token(auth):
secret = get_config()['jwt']['secret']
expire = get_config()['jwt']['token_expire']
issuer = get_config()['jwt']['issuer']
audience = get_config()['jwt']['audience']
payload = {
'user': auth.login,
'exp': datetime.datetime.utcnow() + datetime.timedelta(seconds=expire),
'iss': issuer,
'aud': audience
}
token = jwt.encode(payload, secret, algorithm='HS256')
return token
def access_token(request):
expire = get_config()['jwt']['token_expire']
secret = get_config()['jwt']['secret']
token = request.headers['Authorization']
if not token.startswith("Bearer "):
raise HTTPBadRequest(reason='Unexpected bearer format')
token = token[7:]
decoded = verify_token(token)
if decoded:
decoded['exp'] = datetime.datetime.utcnow() + datetime.timedelta(seconds=expire)
token = jwt.encode(decoded, secret, algorithm='HS256')
return Response(text=str(token.decode('utf-8')))
else:
return HTTPUnauthorized(reason='Token could not be verified')
def verify_token(token):
secret = get_config()['jwt']['secret']
issuer = get_config()['jwt']['issuer']
audience = get_config()['jwt']['audience']
try:
decoded = jwt.decode(token, secret, issuer=issuer, audience=audience, algorithms=['HS256'])
except jwt.ExpiredSignatureError:
raise HTTPUnauthorized(reason='Token Expired')
except jwt.InvalidIssuerError:
raise HTTPUnauthorized(reason='Token could not be verified')
except jwt.InvalidAudienceError:
raise HTTPUnauthorized(reason='Token audience not match')
return decoded
tiramisu = None

View File

@ -38,7 +38,7 @@ class AnyOption(Option):
class MessageDefinition:
"""
A MessageDefinition is a representation of a message in the Zephir application messaging context
A MessageDefinition is a representation of a message in the Risotto application messaging context
"""
__slots__ = ('version',
'uri',
@ -135,7 +135,7 @@ class ParameterDefinition:
class ResponseDefinition:
"""
An ResponseDefinition is a representation of a response in the Zephir application messaging context
An ResponseDefinition is a representation of a response in the Risotto application messaging context
"""
__slots__ = ('description',
'type',
@ -184,7 +184,7 @@ class ResponseDefinition:
class ErrorDefinition:
"""
An ErrorDefinition is a representation of an error in the Zephir application messaging context
An ErrorDefinition is a representation of an error in the Risotto application messaging context
"""
__slots__ = ('uri',)

View File

@ -13,6 +13,7 @@ class Risotto(Controller):
""" pre-load servermodel and server
"""
user_login = get_config()['global']['admin_user']
user_password = get_config()['global']['admin_user_password']
sql = '''
SELECT UserId
FROM RisottoUser
@ -22,7 +23,8 @@ class Risotto(Controller):
user_login) is None:
await self._user_create(risotto_context,
user_login,
user_login,
user_password,
user_login,
user_login)
await self._user_role_create(risotto_context,
user_login,
@ -33,14 +35,16 @@ class Risotto(Controller):
async def _user_create(self,
risotto_context: Context,
user_login: str,
user_password: str,
user_name: str,
user_surname: str) -> Dict:
user_insert = """INSERT INTO RisottoUser(UserLogin, UserName, UserSurname)
VALUES ($1,$2,$3)
user_insert = """INSERT INTO RisottoUser(UserLogin, UserPassword, UserName, UserSurname)
VALUES ($1,crypt($2, gen_salt('bf')),$3,$4)
RETURNING UserId
"""
user_id = await risotto_context.connection.fetchval(user_insert,
user_login,
user_password,
user_name,
user_surname)
await self.call('v1.user.role.create',
@ -56,10 +60,12 @@ class Risotto(Controller):
async def user_create(self,
risotto_context: Context,
user_login: str,
user_password: str,
user_name: str,
user_surname: str) -> Dict:
return await self._user_create(risotto_context,
user_login,
user_password,
user_name,
user_surname)