From f982f44eb49a2284ee9bf5fc75b1bc969ce8e443 Mon Sep 17 00:00:00 2001 From: parberge Date: Tue, 13 Apr 2021 19:03:41 +0200 Subject: [PATCH 1/4] PoC auth --- app.py | 3 +++ chalicelib/v1_routes.py | 26 ++++++++++++++++++++++++++ 2 files changed, 29 insertions(+) create mode 100644 chalicelib/v1_routes.py diff --git a/app.py b/app.py index 513eefd..501ca3e 100644 --- a/app.py +++ b/app.py @@ -1,11 +1,14 @@ import os from chalice import Chalice +from chalicelib.v1_routes import v1_routes from chalicelib.lib import db_v2 from chalicelib.model.models import EventTable, LockTable import logging app = Chalice(app_name="timereport_backend") +app.register_blueprint(v1_routes) + app.debug = os.getenv("BACKEND_DEBUG", False) log_level = logging.DEBUG if app.debug else logging.INFO app.log.setLevel(log_level) diff --git a/chalicelib/v1_routes.py b/chalicelib/v1_routes.py new file mode 100644 index 0000000..df07527 --- /dev/null +++ b/chalicelib/v1_routes.py @@ -0,0 +1,26 @@ +from chalice import Blueprint, AuthResponse +from os import environ + + +v1_routes = Blueprint(__name__) + +api_key = environ.get("CHALICE_API_KEY", "development") + + +@v1_routes.authorizer() +def api_key_auth(auth_request): + """ + Custom auth function. + The client need to provide the header Authorization with value of api_key. + """ + + if auth_request.token == api_key: + return AuthResponse(routes=["/*"], principal_id="user") + + return AuthResponse(routes=[], principal_id="user") + + +@v1_routes.route("/v1/foo", authorizer=api_key_auth) +def foo(): + return {"foo": "bar"} + From 18cc4ef3eee773018b90088e2ce116b9a910164a Mon Sep 17 00:00:00 2001 From: parberge Date: Tue, 13 Apr 2021 20:30:02 +0200 Subject: [PATCH 2/4] Add proper v1 routes --- chalicelib/v1_routes.py | 229 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 226 insertions(+), 3 deletions(-) diff --git a/chalicelib/v1_routes.py b/chalicelib/v1_routes.py index df07527..0374d81 100644 --- a/chalicelib/v1_routes.py +++ b/chalicelib/v1_routes.py @@ -1,5 +1,7 @@ from chalice import Blueprint, AuthResponse from os import environ +from chalicelib.model.models import EventTable, LockTable +from chalicelib.lib import db_v2 v1_routes = Blueprint(__name__) @@ -20,7 +22,228 @@ def api_key_auth(auth_request): return AuthResponse(routes=[], principal_id="user") -@v1_routes.route("/v1/foo", authorizer=api_key_auth) -def foo(): - return {"foo": "bar"} +@v1_routes.route("/v1/table-names", cors=True, authorizer=api_key_auth) +def table_names(): + """ + :return: table name + """ + return {"name": [EventTable.Meta.table_name, LockTable.Meta.table_name]} + + +@v1_routes.route("/v1/users", methods=["GET"], cors=True, authorizer=api_key_auth) +def list_users(): + """ + Method + GET /users + Return list of all users + """ + return db_v2.list_users() + + +@v1_routes.route( + "/v1/users/{user_id}", methods=["GET"], cors=True, authorizer=api_key_auth +) +def get_user(user_id): + """ + Method + GET /users/ + Return a single user + """ + return db_v2.get_user(user_id) + + +@v1_routes.route( + "/v1/users/{user_id}/events", methods=["GET"], cors=True, authorizer=api_key_auth +) +def list_events_by_user_id(user_id): + """ + Method + GET /users//events + :params from: str, to: str + filter on date range + Return a list of all user_id's events + """ + if v1_routes.current_request.query_params: + date_from = v1_routes.current_request.query_params.get("from") + date_to = v1_routes.current_request.query_params.get("to") + return db_v2.list_events_by_user_id(user_id, date_from, date_to) + return db_v2.list_events_by_user_id(user_id) + + +@v1_routes.route( + "/v1/users/{user_id}/events", methods=["DELETE"], cors=True, authorizer=api_key_auth +) +def delete_all_events_by_user_id(user_id): + """ + Method + DELETE /users//events + Delete all the user_id's events + """ + return db_v2.delete_all_events_by_user_id(user_id) + + +@v1_routes.route( + "/v1/users/{user_id}/locks", methods=["GET"], cors=True, authorizer=api_key_auth +) +def list_locks_by_user_id(user_id): + """ + Method + GET /users//locks + Return a list of user_id's locks + """ + return db_v2.list_locks_by_user_id(user_id) + + +@v1_routes.route( + "/v1/users/{user_id}/locks", methods=["DELETE"], cors=True, authorizer=api_key_auth +) +def delete_all_locks_by_user_id(user_id): + """ + Method + DELETE /users//locks + Delete all locks for user_id + """ + return db_v2.delete_all_locks_by_user_id(user_id) + + +@v1_routes.route( + "/v1/users/{user_id}/locks/{event_date}", + methods=["DELETE"], + cors=True, + authorizer=api_key_auth, +) +def delete_lock_by_user_id_and_date(user_id, event_date): + """ + Method + DELETE /users//locks/ + Delete user_id lock on date + """ + return db_v2.delete_lock_by_user_id_and_date(user_id, event_date) + + +@v1_routes.route( + "/v1/users/{user_id}/events/{event_date}", + methods=["DELETE"], + cors=True, + authorizer=api_key_auth, +) +def delete_event_by_user_id_and_date(user_id, event_date): + """ + Method + DELETE /users//events/ + Delete user_id's event on date + """ + return db_v2.delete_event_by_user_id_and_date(user_id, event_date) + + +@v1_routes.route( + "/v1/users/{user_id}/events/{event_date}", + methods=["GET"], + cors=True, + authorizer=api_key_auth, +) +def get_event_by_user_id_and_date(user_id, event_date): + """ + Method + DELETE /users//events/ + Delete user_id's event on date + """ + return db_v2.get_event_by_user_id_and_date(user_id, event_date) + + +@v1_routes.route("/v1/events", methods=["POST"], cors=True, authorizer=api_key_auth) +def create_event_v2(): + """ + Method + POST /events + Create event + data: {"user_id":"foo01","user_name":"Foo Bar","reason":"sick","event_date":"2019-03-21","hours":8} OR list with same format data + """ + return db_v2.create_event_v2(v1_routes.current_request.json_body) + + +@v1_routes.route("/v1/events", methods=["GET"], cors=True, authorizer=api_key_auth) +def list_all_events(): + """ + Method + GET /events + Returns list of all events + """ + return db_v2.list_all_events() + + +@v1_routes.route( + "/v1/events/dates/{event_date}", methods=["GET"], cors=True, authorizer=api_key_auth +) +def list_all_events_by_date(event_date): + """ + Method + GET /events/dates/ + Returns list of all events on date + """ + return db_v2.list_all_events_by_date(event_date) + + +@v1_routes.route( + "/v1/events/dates/{event_date}", + methods=["DELETE"], + cors=True, + authorizer=api_key_auth, +) +def delete_all_events_by_date(event_date): + """ + Method + DELETE /events/dates/ + Delete all events on date + """ + return db_v2.delete_all_events_by_date(event_date) + + +@v1_routes.route("/v1/locks", methods=["POST"], cors=True, authorizer=api_key_auth) +def create_lock(): + """ + Method + POST /locks + Create lock + data: {"user_id":"foo01","event_date":"2019-02"} + """ + db_v2.create_lock(app.current_request.json_body) + return app.current_request.json_body + + +@v1_routes.route("/v1/locks", methods=["GET"], cors=True, authorizer=api_key_auth) +def list_all_locks(): + """ + Method + GET /locks + Returns list of all locks + """ + return db_v2.list_all_locks() + + +@v1_routes.route( + "/v1/locks/dates/{event_date}", methods=["GET"], cors=True, authorizer=api_key_auth +) +def list_all_locks_by_date(event_date): + """ + Method + GET /locks/dates/ + Returns list of all locks on date + """ + return db_v2.list_all_locks_by_date(event_date) + + +@v1_routes.route( + "/v1/locks/dates/{event_date}", + methods=["DELETE"], + cors=True, + authorizer=api_key_auth, +) +def delete_all_locks_by_date(event_date): + """ + Method + DELETE /locks/dates/ + Delete all locks on date + """ + return db_v2.delete_all_locks_by_date(event_date) From 7792652956fa11c53c7103d5a13411bf1534dc04 Mon Sep 17 00:00:00 2001 From: parberge Date: Tue, 13 Apr 2021 20:30:15 +0200 Subject: [PATCH 3/4] Add tests for v1 routes --- tests/test_v1_api.py | 201 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 201 insertions(+) create mode 100644 tests/test_v1_api.py diff --git a/tests/test_v1_api.py b/tests/test_v1_api.py new file mode 100644 index 0000000..af527dd --- /dev/null +++ b/tests/test_v1_api.py @@ -0,0 +1,201 @@ +import requests +from requests.api import request + +local_api = "http://localhost:8010" +local_db = "http://localhost:8000" +testusers = ["testuser1", "testuser2"] +testmonths = ["2019-03", "2019-04"] +event_data = [ + { + "reason": "sick", + "hours": "8", + "user_name": "Test Mctest", + "user_id": f"{testusers[0]}", + "event_date": "2019-12-30", + }, + { + "reason": "sick", + "hours": "8", + "user_name": "Test Mctest", + "user_id": f"{testusers[0]}", + "event_date": "2019-12-31", + }, + { + "reason": "sick", + "hours": "8", + "user_name": "Test Mctest", + "user_id": f"{testusers[0]}", + "event_date": "2020-01-01", + }, +] +lock_data = [ + {"user_id": f"{testusers[0]}", "event_date": f"{testmonths[0]}"}, + {"user_id": f"{testusers[0]}", "event_date": f"{testmonths[1]}"}, +] +request_session = requests.Session() +request_session.headers = {"Authorization": "development"} + +# sanity check to see that we have some certainty that this test will execute +# on local environment +check_local_db = requests.get(f"{local_db}/shell/") +if check_local_db.status_code != 200: + raise Exception("Local DB check failed") + +check_local_api = request_session.get(f"{local_api}/v1/table-names") +if check_local_api.status_code != 200: + raise Exception("Local API check failed") + + +def create_event(event: dict): + r = request_session.post(f"{local_api}/events", json=event) + return r + + +def create_lock(lock: dict): + r = request_session.post(f"{local_api}/locks", json=lock) + return r + + +def test_create_event(): + event = event_data[0] + r = create_event(event) + assert r.status_code == 200 + + +def test_create_multilpe_events(): + r = create_event(event_data) + assert r.status_code == 200 + + +def test_create_lock(): + lock = lock_data[0] + r = create_lock(lock) + assert r.status_code == 200 + + +def test_list_users(): + r = request_session.get(f"{local_api}/v1/users") + assert r.status_code == 200 + + +def test_get_user(): + event = event_data[0] + user_id = event["user_id"] + create_event(event) + r = request_session.get(f"{local_api}/v1/users/{user_id}") + assert r.status_code == 200 + + +def test_list_events_by_user_id(): + event = event_data[0] + user_id = event["user_id"] + create_event(event) + r = request_session.get(f"{local_api}/v1/users/{user_id}/events") + assert r.status_code == 200 + + +def test_delete_all_events_by_user_id(): + event = event_data[0] + user_id = event["user_id"] + create_event(event) + r = request_session.delete(f"{local_api}/v1/users/{user_id}/events") + assert r.status_code == 200 + + +def test_list_locks_by_user_id(): + lock = lock_data[0] + user_id = lock["user_id"] + create_lock(lock) + r = request_session.get(f"{local_api}/v1/users/{user_id}/locks") + assert r.status_code == 200 + + +def test_delete_all_locks_by_user_id(): + lock = lock_data[0] + user_id = lock["user_id"] + create_lock(lock) + r = request_session.delete(f"{local_api}/v1/users/{user_id}/locks") + assert r.status_code == 200 + + +def test_delete_lock_by_user_id_and_date(): + lock = lock_data[0] + user_id = lock["user_id"] + date = lock["event_date"] + create_lock(lock) + r = request_session.delete(f"{local_api}/v1/users/{user_id}/locks/{date}") + assert r.status_code == 200 + + +def test_get_event_by_user_id_and_date(): + event = event_data[0] + user_id = event["user_id"] + create_event(event) + + event = event_data[1] + create_event(event) + + r = request_session.get(f"{local_api}/v1/users/{user_id}/events/") + assert r.status_code == 200 + + +def test_delete_event_by_user_id_and_date(): + event = event_data[0] + user_id = event["user_id"] + date = event["event_date"] + create_event(event) + r = request_session.delete(f"{local_api}/v1/users/{user_id}/events/{date}") + assert r.status_code == 200 + + +def test_list_all_events(): + event = event_data[0] + create_event(event) + r = request_session.get(f"{local_api}/v1/events") + assert r.status_code == 200 + + +def test_list_all_events_by_date(): + event = event_data[0] + date = event["event_date"] + create_event(event) + r = request_session.get(f"{local_api}/v1/events/dates/{date}") + assert r.status_code == 200 + + +def test_delete_all_events_by_date(): + event = event_data[0] + date = event["event_date"] + create_event(event) + r = request_session.delete(f"{local_api}/v1/events/dates/{date}") + assert r.status_code == 200 + + +def test_list_all_locks(): + + # lock 1 + lock = lock_data[0] + create_lock(lock) + + # lock 2 + lock = lock_data[1] + create_lock(lock) + + r = request_session.get(f"{local_api}/v1/locks") + assert r.status_code == 200 + + +def test_list_all_locks_by_date(): + lock = lock_data[0] + date = lock["event_date"] + create_lock(lock) + r = request_session.get(f"{local_api}/v1/locks/dates/{date}") + assert r.status_code == 200 + + +def test_delete_all_locks_by_date(): + lock = lock_data[0] + date = lock["event_date"] + create_lock(lock) + r = request_session.delete(f"{local_api}/v1/locks/dates/{date}") + assert r.status_code == 200 From 8504a542a122fc40f45dc5c2a8b415c5da23a304 Mon Sep 17 00:00:00 2001 From: parberge Date: Wed, 14 Apr 2021 18:24:15 +0200 Subject: [PATCH 4/4] Add test for unauthorized request --- tests/test_v1_api.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/tests/test_v1_api.py b/tests/test_v1_api.py index af527dd..36ef4b5 100644 --- a/tests/test_v1_api.py +++ b/tests/test_v1_api.py @@ -1,5 +1,4 @@ import requests -from requests.api import request local_api = "http://localhost:8010" local_db = "http://localhost:8000" @@ -199,3 +198,9 @@ def test_delete_all_locks_by_date(): create_lock(lock) r = request_session.delete(f"{local_api}/v1/locks/dates/{date}") assert r.status_code == 200 + + +def test_unauthorized_request(): + r = requests.get(f"{local_api}/v1/users") + assert r.status_code == 401 + assert "Unauthorized" in r.text