add helpers to retrieve request data

This commit is contained in:
ducklet 2021-08-02 18:33:48 +02:00
parent c1b41d3882
commit 9bb433eb0c

View file

@ -2,7 +2,7 @@ import asyncio
import logging import logging
import secrets import secrets
from json.decoder import JSONDecodeError from json.decoder import JSONDecodeError
from typing import Literal, Optional from typing import Literal, Optional, overload
from starlette.applications import Starlette from starlette.applications import Starlette
from starlette.authentication import ( 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: def as_ulid(s: str) -> ULID:
try: try:
if not s:
raise ValueError("Invalid ULID.")
return ULID(s) return ULID(s)
except ValueError: except ValueError:
raise HTTPException(422, "Not a valid ULID.") 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 = [] _routes = []
@ -125,6 +164,7 @@ route.registered = _routes
@route("/groups/{group_id}/ratings") @route("/groups/{group_id}/ratings")
async def get_ratings_for_group(request): async def get_ratings_for_group(request):
group_id = as_ulid(request.path_params["group_id"]) group_id = as_ulid(request.path_params["group_id"])
group = await db.get(Group, id=str(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) return JSONResponse({"error": reason}, status_code=404)
def not_implemented():
raise HTTPException(404, "Not yet implemented.")
@route("/movies") @route("/movies")
@requires(["private"]) @requires(["private"])
async def get_movies(request): async def get_movies(request):
@ -194,7 +238,8 @@ async def get_movies(request):
@route("/movies", methods=["POST"]) @route("/movies", methods=["POST"])
@requires(["authenticated", "admin"]) @requires(["authenticated", "admin"])
async def add_movie(request): async def add_movie(request):
pass
not_implemented()
import_lock = asyncio.Lock() import_lock = asyncio.Lock()
@ -233,6 +278,7 @@ async def progress_for_load_imdb_movies(request):
@route("/movies/_reload_imdb", methods=["POST"]) @route("/movies/_reload_imdb", methods=["POST"])
@requires(["authenticated", "admin"]) @requires(["authenticated", "admin"])
async def load_imdb_movies(request): async def load_imdb_movies(request):
async with import_lock: async with import_lock:
progress = await db.get_import_progress() progress = await db.get_import_progress()
if progress and not progress.stopped: if progress and not progress.stopped:
@ -252,26 +298,31 @@ async def load_imdb_movies(request):
@route("/users") @route("/users")
@requires(["authenticated", "admin"]) @requires(["authenticated", "admin"])
async def list_users(request): async def list_users(request):
users = await db.get_all(User) users = await db.get_all(User)
return JSONResponse([asplain(u) for u in users]) return JSONResponse([asplain(u) for u in users])
@route("/users", methods=["POST"]) @route("/users", methods=["POST"])
@requires(["authenticated", "admin"]) @requires(["authenticated", "admin"])
async def add_user(request): async def add_user(request):
pass
not_implemented()
@route("/users/{user_id}/ratings") @route("/users/{user_id}/ratings")
@requires(["private"]) @requires(["private"])
async def ratings_for_user(request): async def ratings_for_user(request):
request.path_params["user_id"]
not_implemented()
@route("/users/{user_id}/ratings", methods=["PUT"]) @route("/users/{user_id}/ratings", methods=["PUT"])
@requires("authenticated") @requires("authenticated")
async def set_rating_for_user(request): async def set_rating_for_user(request):
request.path_params["user_id"]
not_implemented()
@route("/users/_reload_ratings", methods=["POST"]) @route("/users/_reload_ratings", methods=["POST"])
@ -286,25 +337,17 @@ async def load_imdb_user_ratings(request):
@route("/groups") @route("/groups")
@requires(["authenticated", "admin"]) @requires(["authenticated", "admin"])
async def list_groups(request): async def list_groups(request):
groups = await db.get_all(Group) groups = await db.get_all(Group)
return JSONResponse([asplain(g) for g in groups]) return JSONResponse([asplain(g) for g in groups])
@route("/groups", methods=["POST"]) @route("/groups", methods=["POST"])
@requires(["authenticated", "admin"]) @requires(["authenticated", "admin"])
async def add_group(request): 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,) = await json_from_body(request, ["name"])
name = data["name"]
except KeyError as err:
raise HTTPException(422, f"Missing data for key: {err.args[0]}")
# XXX restrict name # XXX restrict name
@ -330,25 +373,11 @@ async def add_user_to_group(request):
if not group: if not group:
return not_found() return not_found()
is_allowed = "admin" in request.auth.scopes or phc_compare( is_allowed = any(auth(request, group.secret))
secret=request.user.token, phc_string=group.secret
)
if not is_allowed: if not is_allowed:
return forbidden() return forbidden()
if not await request.body(): name, user_id = await json_from_body(request, ["name", "id"])
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]}")
# XXX check if user exists # XXX check if user exists
# XXX restrict name # XXX restrict name