Commit 54ed96ec authored by David Foucher's avatar David Foucher

Add authorisation api endpoint

/authorisation endpoint in POST with
email, password, and file or regexp to file authorized to modified

authorisation will check data in the csv file
trefle/config/authorisations.csv (with 3 columns: email, password, file)

if the authorisations.csf does not exists it will be created empty
waiting to be populated otherwise no authorisations to modify files will
be available
parent 93cf81cd
Pipeline #2281 passed with stage
in 1 minute and 16 seconds
......@@ -13,3 +13,4 @@ backoffice/node_modules/
backoffice/dist/
*.log
.DS_Store
authorisations.csv
......@@ -5,6 +5,7 @@ from pathlib import Path
import pytest
from roll.extensions import traceback
from trefle.config import AUTHORIZED
from trefle.rules import LABELS, SCHEMA
......@@ -35,6 +36,19 @@ def patch_schema():
LABELS.update(before_labels)
@pytest.fixture
def patch_authorisations():
before = AUTHORIZED.copy()
def patch(new):
AUTHORIZED.clear()
AUTHORIZED.extend(new)
yield patch
AUTHORIZED.clear()
AUTHORIZED.extend(before)
@pytest.fixture
def mock_get(monkeypatch):
class Response:
......
import datetime
import json
import os
import urllib.parse
......@@ -515,6 +516,58 @@ async def test_remuneration_parsing_should_be_liberal(client):
assert resp.status == HTTPStatus.OK
async def test_authentification(patch_authorisations, client, mock_get):
body = {
"email": "test@test.fr",
"password": "test",
"file": "/règles nationales/CPF.rules"
}
patch_authorisations([body])
mock_get(status_code=200)
date = datetime.datetime.today().strftime('%y%m%d')
token = hash(f"{body['email']}.{body['password']}.{date}")
resp = await client.post("/authentification", body=body)
assert resp.status == HTTPStatus.OK
assert json.loads(resp.body) == {
"token": token
}
async def test_authentification_with_bad_email(patch_authorisations, client, mock_get):
body = {
"email": "bademail@test.com",
"password": "test",
"file": "/règles nationales/CPF.rules"
}
auth = body.copy()
auth['email'] = 'test@test.fr'
patch_authorisations([auth])
mock_get(status_code=401)
resp = await client.post("/authentification", body=body)
assert resp.status == HTTPStatus.UNAUTHORIZED
async def test_authentification_with_token(patch_authorisations, client, mock_get):
auth = {
"email": "test@test.fr",
"password": "test",
"file": "/règles nationales/CPF.rules"
}
date = datetime.datetime.today().strftime('%y%m%d')
token = hash(f"{auth['email']}.{auth['password']}.{date}")
body = {
"token": token
}
patch_authorisations([auth])
mock_get(status_code=200)
resp = await client.post("/authentification", body=body)
assert resp.status == HTTPStatus.OK
assert json.loads(resp.body) == body
async def test_glossary(client):
resp = await client.get("/explore/glossary")
assert resp.status == HTTPStatus.OK
......
from trefle.config import Financement, Organisme, load_naf, load_schema
from trefle.config import Financement, Organisme, load_naf, load_schema, load_authorisations
def test_load_schema():
......@@ -37,6 +37,14 @@ SECTION A;AGRICULTURE, SYLVICULTURE ET PÊCHE
}
def test_load_authorisations():
data = """email,password,file
test@test.fr,test,.*"""
assert load_authorisations(data) == [
{'email': 'test@test.fr', 'password': 'test', 'file': '.*'}
]
def test_financement_format():
financement = Financement(
......
import datetime
import re
from http import HTTPStatus
import gitlab
......@@ -11,7 +12,7 @@ import ujson as json
from . import VERSION, get_financements, get_remunerations, simulate
from . import routine
from .config import FINANCEMENTS, COMMIT_AUTHORIZED, GLOSSARY, IDCC, NAF, CERTIFINFO, RAW_RULES, SCHEMA, GITLAB_TOKEN
from .config import AUTHORIZED, FINANCEMENTS, COMMIT_AUTHORIZED, GLOSSARY, IDCC, NAF, CERTIFINFO, RAW_RULES, SCHEMA, GITLAB_TOKEN
from .context import Context
from .debugging import SCENARIOS, data_from_lbf_url, make_scenario
from .exceptions import DataError, UnauthorizedAccess, NotModifiedError
......@@ -210,6 +211,31 @@ async def decode_lbf_url(request, response):
response.json = data_from_lbf_url(request.query.get("url"))
@app.route("/authentification", ['POST'])
async def authent(request, response):
data = request.json
date = datetime.datetime.today().strftime('%y%m%d')
for authorized in AUTHORIZED:
aemail = authorized['email']
apassword = authorized['password']
afile = authorized['file']
atoken = hash(f"{aemail}.{apassword}.{date}")
if data.get('email') == aemail and data.get('password') == apassword:
if re.match(afile, data.get('file')):
response.json = {'token': atoken}
else:
raise HttpError(HTTPStatus.UNAUTHORIZED,
'Non authorisé à modifier le fichier.')
elif data.get('token', '') == '':
raise HttpError(HTTPStatus.UNAUTHORIZED,
'Email ou mot de passe non reconnu.')
elif data.get('token') != atoken:
raise HttpError(HTTPStatus.UNAUTHORIZED,
'Le token n\'est pas reconnu.')
else:
response.json = {'token': atoken}
@app.route("/source/modified")
async def source_modified(request, response):
gl = gitlab.Gitlab('https://git.beta.pole-emploi.fr', private_token=GITLAB_TOKEN)
......@@ -225,7 +251,7 @@ async def source_modified(request, response):
'title': branch.attributes.get('commit').get('title'),
'message': branch.attributes.get('commit').get('message'),
'author_name': branch.attributes.get('commit').get('author_name'),
'date': branch.attributes.get('commit').get('author_date'),
'date': branch.attributes.get('commit').get('authored_date'),
'file': commit[0].get('new_path'), # NOTE: only one commit per branch
'diff': commit[0].get('diff')
}
......@@ -234,7 +260,7 @@ async def source_modified(request, response):
@app.route("/source/save", ['POST'])
async def source_save(request, response):
# TODO los error
# TODO log errors
try:
response.json = await submit_modification(request.json)
except UnauthorizedAccess:
......
......@@ -32,7 +32,11 @@ CATALOG_URL = os.environ.get(
GITLAB_URL = os.environ.get("GITLAB_URL", "https://git.beta.pole-emploi.fr")
GITLAB_PROJECT = os.environ.get("GITLAB_PROJECT", "open-source/trefle")
GITLAB_TOKEN = os.environ.get("GITLAB_TOKEN", "need-private-access-token")
COMMIT_AUTHORIZED = os.environ.get("COMMIT_AUTHORIZED", "contributeur@trefle.beta.pole-emploi.fr").split(', ')
COMMIT_AUTHORIZED = set(os.environ.get(
"COMMIT_AUTHORIZED",
"contributeur@trefle.beta.pole-emploi.fr"
).split(', '))
AUTHORIZED = []
def load_schema(data, output=None, namespace=None):
......@@ -97,6 +101,17 @@ def load_naf(data):
return out
def load_authorisations(data):
out = []
reader = csv.DictReader(data.split("\n"), delimiter=",", skipinitialspace=True)
for line in reader:
email = line["email"]
password = line["password"]
_file = line["file"]
out.append({'email': email, 'password': password, 'file': _file})
return out
def load_rules(path):
with path.open() as rules_file:
data = rules_file.read()
......@@ -185,5 +200,11 @@ def init():
NAF.update(load_naf(f.read()))
with (ROOT / "certifinfo.json").open() as f:
CERTIFINFO.update(json.loads(f.read()))
with (ROOT / "authorisations.csv").open('a+') as f:
f.seek(0)
AUTHORIZED.extend(load_authorisations(f.read()))
for auth in AUTHORIZED:
COMMIT_AUTHORIZED.add(auth.get('email', ''))
load_features()
print("Done initializing config")
......@@ -32,7 +32,7 @@ async def submit_modification(data):
raise NotModifiedError(f"Content not modified in rule {data.get('branch')}")
elif data.get("author_email") not in COMMIT_AUTHORIZED:
raise UnauthorizedAccess(
f"Your emai is not authorized to modify the rule {data.get('branch')}"
f"Your email is not authorized to modify the rule {data.get('branch')}"
)
else:
commit = await create_commit(project, data)
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment