implement basicauth and jwt token

This commit is contained in:
Matthieu Lamalle 2020-01-24 13:43:14 +01:00
parent 52c878b0ab
commit d78d581c65
9 changed files with 116 additions and 15 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:

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

@ -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,12 @@ 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
import os
from pathlib import PurePosixPath
@ -19,16 +24,20 @@ 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}
}

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,6 +18,16 @@ from .services import load_services
def create_context(request):
risotto_context = Context()
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,73 @@ 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']
payload = {
'user': auth.login,
'exp': datetime.datetime.utcnow() + datetime.timedelta(seconds=expire),
'iss': issuer
}
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 refreshed')
return True
def verify_token(token):
secret = get_config()['jwt']['secret']
issuer = get_config()['jwt']['issuer']
try:
decoded = jwt.decode(token, secret, issuer=issuer, algorithms=['HS256'])
except jwt.ExpiredSignatureError:
raise HTTPUnauthorized(reason='Token Expired')
except jwt.InvalidIssuerError:
raise HTTPUnauthorized(reason='Token could not be verified')
return decoded
tiramisu = None

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,6 +23,7 @@ class Risotto(Controller):
user_login) is None:
await self._user_create(risotto_context,
user_login,
user_password,
user_login,
user_login)
await self._user_role_create(risotto_context,
@ -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)