From 9bb433eb0c1575219f23ce52a68d54e108dc20a4 Mon Sep 17 00:00:00 2001 From: ducklet Date: Mon, 2 Aug 2021 18:33:48 +0200 Subject: [PATCH] add helpers to retrieve request data --- unwind/web.py | 93 +++++++++++++++++++++++++++++++++------------------ 1 file changed, 61 insertions(+), 32 deletions(-) diff --git a/unwind/web.py b/unwind/web.py index c5edbd4..08b0ab6 100644 --- a/unwind/web.py +++ b/unwind/web.py @@ -2,7 +2,7 @@ import asyncio import logging import secrets from json.decoder import JSONDecodeError -from typing import Literal, Optional +from typing import Literal, Optional, overload from starlette.applications import Starlette from starlette.authentication import ( @@ -103,11 +103,50 @@ def as_int(x, *, max: int = None, min: Optional[int] = 1, default: int = None): def as_ulid(s: str) -> ULID: try: + if not s: + raise ValueError("Invalid ULID.") + return ULID(s) + except ValueError: raise HTTPException(422, "Not a valid ULID.") +@overload +async def json_from_body(request) -> dict: + ... + + +@overload +async def json_from_body(request, keys: list[str]) -> list: + ... + + +async def json_from_body(request, keys: list[str] = None): + if not await request.body(): + data = {} + + else: + try: + data = await request.json() + except JSONDecodeError: + raise HTTPException(422, "Invalid JSON content.") + + if not keys: + return data + + try: + return [data[k] for k in keys] + except KeyError as err: + raise HTTPException(422, f"Missing data for key: {err.args[0]}") + + +def auth(request, secret: str = None): + is_admin = "admin" in request.auth.scopes + is_owner = secret and phc_compare(secret=request.user.token, phc_string=secret) + return is_admin, bool(is_owner) + + _routes = [] @@ -125,6 +164,7 @@ route.registered = _routes @route("/groups/{group_id}/ratings") async def get_ratings_for_group(request): + group_id = as_ulid(request.path_params["group_id"]) group = await db.get(Group, id=str(group_id)) @@ -180,6 +220,10 @@ def not_found(reason: str = "Not Found"): return JSONResponse({"error": reason}, status_code=404) +def not_implemented(): + raise HTTPException(404, "Not yet implemented.") + + @route("/movies") @requires(["private"]) async def get_movies(request): @@ -194,7 +238,8 @@ async def get_movies(request): @route("/movies", methods=["POST"]) @requires(["authenticated", "admin"]) async def add_movie(request): - pass + + not_implemented() import_lock = asyncio.Lock() @@ -233,6 +278,7 @@ async def progress_for_load_imdb_movies(request): @route("/movies/_reload_imdb", methods=["POST"]) @requires(["authenticated", "admin"]) async def load_imdb_movies(request): + async with import_lock: progress = await db.get_import_progress() if progress and not progress.stopped: @@ -252,26 +298,31 @@ async def load_imdb_movies(request): @route("/users") @requires(["authenticated", "admin"]) async def list_users(request): + users = await db.get_all(User) + return JSONResponse([asplain(u) for u in users]) @route("/users", methods=["POST"]) @requires(["authenticated", "admin"]) async def add_user(request): - pass + + not_implemented() @route("/users/{user_id}/ratings") @requires(["private"]) async def ratings_for_user(request): - request.path_params["user_id"] + + not_implemented() @route("/users/{user_id}/ratings", methods=["PUT"]) @requires("authenticated") async def set_rating_for_user(request): - request.path_params["user_id"] + + not_implemented() @route("/users/_reload_ratings", methods=["POST"]) @@ -286,25 +337,17 @@ async def load_imdb_user_ratings(request): @route("/groups") @requires(["authenticated", "admin"]) async def list_groups(request): + groups = await db.get_all(Group) + return JSONResponse([asplain(g) for g in groups]) @route("/groups", methods=["POST"]) @requires(["authenticated", "admin"]) async def add_group(request): - if not await request.body(): - data = {} - else: - try: - data = await request.json() - except JSONDecodeError: - raise HTTPException(422, "Invalid JSON content.") - try: - name = data["name"] - except KeyError as err: - raise HTTPException(422, f"Missing data for key: {err.args[0]}") + (name,) = await json_from_body(request, ["name"]) # XXX restrict name @@ -330,25 +373,11 @@ async def add_user_to_group(request): if not group: return not_found() - is_allowed = "admin" in request.auth.scopes or phc_compare( - secret=request.user.token, phc_string=group.secret - ) + is_allowed = any(auth(request, group.secret)) if not is_allowed: return forbidden() - if not await request.body(): - data = {} - else: - try: - data = await request.json() - except JSONDecodeError: - raise HTTPException(422, "Invalid JSON content.") - - try: - name = data["name"] - user_id = data["id"] - except KeyError as err: - raise HTTPException(422, f"Missing data for key: {err.args[0]}") + name, user_id = await json_from_body(request, ["name", "id"]) # XXX check if user exists # XXX restrict name