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 ROLE risotto WITH LOGIN PASSWORD 'risotto';"
|
||||||
psql -U postgres -h localhost -c "CREATE DATABASE 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 "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
|
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/rougail/src/rougail /usr/local/lib/python3.7
|
||||||
RUN ln -s /srv/src/risotto/src/risotto /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
|
RUN cd /srv/src/risotto && pip install -r requirements.txt
|
||||||
|
|
||||||
# Installation
|
# Installation
|
||||||
|
|
|
@ -6,6 +6,7 @@ services:
|
||||||
dockerfile: docker/Dockerfile
|
dockerfile: docker/Dockerfile
|
||||||
volumes:
|
volumes:
|
||||||
- ../.:/srv/src/risotto
|
- ../.:/srv/src/risotto
|
||||||
|
- ../messages:/usr/local/lib/messages
|
||||||
ports:
|
ports:
|
||||||
- "8080:8080"
|
- "8080:8080"
|
||||||
depends_on:
|
depends_on:
|
||||||
|
|
|
@ -7,6 +7,7 @@ psql --username "$POSTGRES_USER" <<-EOSQL
|
||||||
GRANT ALL ON DATABASE risotto TO risotto;
|
GRANT ALL ON DATABASE risotto TO risotto;
|
||||||
\c risotto
|
\c risotto
|
||||||
CREATE EXTENSION hstore;
|
CREATE EXTENSION hstore;
|
||||||
|
CREATE EXTENSION pgcrypto;
|
||||||
EOSQL
|
EOSQL
|
||||||
|
|
||||||
psql --username "risotto" --password "risotto" <<-EOSQL
|
psql --username "risotto" --password "risotto" <<-EOSQL
|
||||||
|
@ -64,6 +65,7 @@ psql --username "risotto" --password "risotto" <<-EOSQL
|
||||||
CREATE TABLE RisottoUser (
|
CREATE TABLE RisottoUser (
|
||||||
UserId SERIAL PRIMARY KEY,
|
UserId SERIAL PRIMARY KEY,
|
||||||
UserLogin VARCHAR(100) NOT NULL UNIQUE,
|
UserLogin VARCHAR(100) NOT NULL UNIQUE,
|
||||||
|
UserPassword TEXT NOT NULL,
|
||||||
UserName VARCHAR(100) NOT NULL,
|
UserName VARCHAR(100) NOT NULL,
|
||||||
UserSurname VARCHAR(100) NOT NULL
|
UserSurname VARCHAR(100) NOT NULL
|
||||||
);
|
);
|
||||||
|
|
|
@ -11,6 +11,10 @@ parameters:
|
||||||
shortarg: l
|
shortarg: l
|
||||||
description: Login de l'utilisateur.
|
description: Login de l'utilisateur.
|
||||||
ref: User.Login
|
ref: User.Login
|
||||||
|
user_password:
|
||||||
|
type: String
|
||||||
|
shortarg: p
|
||||||
|
description: Password de l'utilisateur.
|
||||||
user_name:
|
user_name:
|
||||||
type: String
|
type: String
|
||||||
shortarg: n
|
shortarg: n
|
||||||
|
|
|
@ -57,6 +57,7 @@ CREATE TABLE Server (
|
||||||
CREATE TABLE RisottoUser (
|
CREATE TABLE RisottoUser (
|
||||||
UserId SERIAL PRIMARY KEY,
|
UserId SERIAL PRIMARY KEY,
|
||||||
UserLogin VARCHAR(100) NOT NULL UNIQUE,
|
UserLogin VARCHAR(100) NOT NULL UNIQUE,
|
||||||
|
UserPassword TEXT NOT NULL,
|
||||||
UserName VARCHAR(100) NOT NULL,
|
UserName VARCHAR(100) NOT NULL,
|
||||||
UserSurname VARCHAR(100) NOT NULL
|
UserSurname VARCHAR(100) NOT NULL
|
||||||
);
|
);
|
||||||
|
|
|
@ -5,7 +5,12 @@ CONFIGURATION_DIR = 'configurations'
|
||||||
TEMPLATE_DIR = 'templates'
|
TEMPLATE_DIR = 'templates'
|
||||||
TMP_DIR = 'tmp'
|
TMP_DIR = 'tmp'
|
||||||
ROUGAIL_DTD_PATH = '../rougail/data/creole.dtd'
|
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
|
import os
|
||||||
from pathlib import PurePosixPath
|
from pathlib import PurePosixPath
|
||||||
|
@ -19,16 +24,20 @@ def get_config():
|
||||||
'user': 'risotto',
|
'user': 'risotto',
|
||||||
'password': 'risotto',
|
'password': 'risotto',
|
||||||
},
|
},
|
||||||
'http_server': {'port': 8080,
|
'http_server': {'port': PORT,
|
||||||
#'default_user': "gnunux"},
|
|
||||||
'default_user': DEFAULT_USER},
|
'default_user': DEFAULT_USER},
|
||||||
'global': {'message_root_path': CURRENT_PATH.parents[2] / 'messages',
|
'global': {'message_root_path': CURRENT_PATH.parents[2] / 'messages',
|
||||||
'debug': True,
|
'debug': True,
|
||||||
'internal_user': 'internal',
|
'internal_user': 'internal',
|
||||||
'check_role': True,
|
'check_role': True,
|
||||||
'rougail_dtd_path': '../rougail/data/creole.dtd',
|
'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'},
|
'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 json import dumps
|
||||||
from traceback import print_exc
|
from traceback import print_exc
|
||||||
from tiramisu import Config
|
from tiramisu import Config
|
||||||
|
import datetime
|
||||||
|
import jwt
|
||||||
|
|
||||||
from .dispatcher import dispatcher
|
from .dispatcher import dispatcher
|
||||||
from .utils import _
|
from .utils import _
|
||||||
|
@ -16,6 +18,16 @@ from .services import load_services
|
||||||
|
|
||||||
def create_context(request):
|
def create_context(request):
|
||||||
risotto_context = Context()
|
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',
|
risotto_context.username = request.match_info.get('username',
|
||||||
get_config()['http_server']['default_user'])
|
get_config()['http_server']['default_user'])
|
||||||
return risotto_context
|
return risotto_context
|
||||||
|
@ -49,7 +61,7 @@ class extra_route_handler:
|
||||||
try:
|
try:
|
||||||
returns = await cls.function(**kwargs)
|
returns = await cls.function(**kwargs)
|
||||||
except NotAllowedError as err:
|
except NotAllowedError as err:
|
||||||
raise HTTPNotFound(reason=str(err))
|
raise HTTPUnauthorized(reason=str(err))
|
||||||
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:
|
||||||
|
@ -77,7 +89,7 @@ async def handle(request):
|
||||||
check_role=True,
|
check_role=True,
|
||||||
**kwargs)
|
**kwargs)
|
||||||
except NotAllowedError as err:
|
except NotAllowedError as err:
|
||||||
raise HTTPNotFound(reason=str(err))
|
raise HTTPUnauthorized(reason=str(err))
|
||||||
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:
|
||||||
|
@ -141,8 +153,73 @@ async def get_app(loop):
|
||||||
print()
|
print()
|
||||||
del extra_routes
|
del extra_routes
|
||||||
app.add_routes(routes)
|
app.add_routes(routes)
|
||||||
|
app.router.add_post('/auth', auth)
|
||||||
|
app.router.add_post('/access_token', access_token)
|
||||||
await dispatcher.on_join()
|
await dispatcher.on_join()
|
||||||
return await loop.create_server(app.make_handler(), '*', get_config()['http_server']['port'])
|
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
|
tiramisu = None
|
||||||
|
|
|
@ -13,6 +13,7 @@ class Risotto(Controller):
|
||||||
""" pre-load servermodel and server
|
""" pre-load servermodel and server
|
||||||
"""
|
"""
|
||||||
user_login = get_config()['global']['admin_user']
|
user_login = get_config()['global']['admin_user']
|
||||||
|
user_password = get_config()['global']['admin_user_password']
|
||||||
sql = '''
|
sql = '''
|
||||||
SELECT UserId
|
SELECT UserId
|
||||||
FROM RisottoUser
|
FROM RisottoUser
|
||||||
|
@ -22,6 +23,7 @@ class Risotto(Controller):
|
||||||
user_login) is None:
|
user_login) is None:
|
||||||
await self._user_create(risotto_context,
|
await self._user_create(risotto_context,
|
||||||
user_login,
|
user_login,
|
||||||
|
user_password,
|
||||||
user_login,
|
user_login,
|
||||||
user_login)
|
user_login)
|
||||||
await self._user_role_create(risotto_context,
|
await self._user_role_create(risotto_context,
|
||||||
|
@ -33,14 +35,16 @@ class Risotto(Controller):
|
||||||
async def _user_create(self,
|
async def _user_create(self,
|
||||||
risotto_context: Context,
|
risotto_context: Context,
|
||||||
user_login: str,
|
user_login: str,
|
||||||
|
user_password: str,
|
||||||
user_name: str,
|
user_name: str,
|
||||||
user_surname: str) -> Dict:
|
user_surname: str) -> Dict:
|
||||||
user_insert = """INSERT INTO RisottoUser(UserLogin, UserName, UserSurname)
|
user_insert = """INSERT INTO RisottoUser(UserLogin, UserPassword, UserName, UserSurname)
|
||||||
VALUES ($1,$2,$3)
|
VALUES ($1,crypt($2, gen_salt('bf')),$3,$4)
|
||||||
RETURNING UserId
|
RETURNING UserId
|
||||||
"""
|
"""
|
||||||
user_id = await risotto_context.connection.fetchval(user_insert,
|
user_id = await risotto_context.connection.fetchval(user_insert,
|
||||||
user_login,
|
user_login,
|
||||||
|
user_password,
|
||||||
user_name,
|
user_name,
|
||||||
user_surname)
|
user_surname)
|
||||||
await self.call('v1.user.role.create',
|
await self.call('v1.user.role.create',
|
||||||
|
@ -56,10 +60,12 @@ class Risotto(Controller):
|
||||||
async def user_create(self,
|
async def user_create(self,
|
||||||
risotto_context: Context,
|
risotto_context: Context,
|
||||||
user_login: str,
|
user_login: str,
|
||||||
|
user_password: str,
|
||||||
user_name: str,
|
user_name: str,
|
||||||
user_surname: str) -> Dict:
|
user_surname: str) -> Dict:
|
||||||
return await self._user_create(risotto_context,
|
return await self._user_create(risotto_context,
|
||||||
user_login,
|
user_login,
|
||||||
|
user_password,
|
||||||
user_name,
|
user_name,
|
||||||
user_surname)
|
user_surname)
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue