implement basicauth and jwt token
This commit is contained in:
parent
52c878b0ab
commit
d78d581c65
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -6,6 +6,7 @@ services:
|
|||
dockerfile: docker/Dockerfile
|
||||
volumes:
|
||||
- ../.:/srv/src/risotto
|
||||
- ../messages:/usr/local/lib/messages
|
||||
ports:
|
||||
- "8080:8080"
|
||||
depends_on:
|
||||
|
|
|
@ -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
|
||||
);
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
);
|
||||
|
|
|
@ -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}
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
Loading…
Reference in New Issue