webfinger/webfinger/lookup.py

168 lines
3.8 KiB
Python
Raw Permalink Normal View History

2022-12-26 00:32:57 +00:00
"""
Perform WebFinger lookups according to RFC7033
"""
import json
from fastapi import APIRouter
2022-12-10 10:34:05 +00:00
from fastapi.responses import PlainTextResponse, JSONResponse
from fastapi.params import Query
2022-12-26 00:32:57 +00:00
from pydantic import ValidationError
2022-12-10 10:34:05 +00:00
from webfinger.io import get_jrd
2022-12-26 00:32:57 +00:00
from webfinger.models import JRD, JRDResponse
2022-12-10 10:34:05 +00:00
router = APIRouter(
prefix="/.well-known/webfinger",
)
2022-12-10 10:34:05 +00:00
# Define required CORS header per RFC7033
2022-12-10 10:34:05 +00:00
HEADERS = {'Access-Control-Allow-Origin': '*'}
# if env.get("CORS_ALLOW_ORIGIN"):
# allowed_origins = env.get("CORS_ALLOW_ORIGIN")
# headers.update({'Access-Control-Allow-Origin': allowed_origins})
2022-12-10 10:34:05 +00:00
## Example responses for OpenAPI schema
2022-12-26 00:32:57 +00:00
RESOURCE_NOT_PROVIDED = \
"Query ?resource={resource} to obtain information about that resource."
RESOURCE_NOT_FOUND = \
"Resource not found"
RESOURCE_NOT_PARSED = \
"Resource contains invalid JSON or was unreadable"
RESOURCE_NOT_VALID = \
"Some errors occurred while validating the JRD"
2022-12-10 10:34:05 +00:00
RESPONSES = {
200: {
2022-12-26 00:32:57 +00:00
"description": (
"OK:"
" The resource you requested was found,"
" and has the returned resource document."),
"model": JRD,
"content": {
"application/jrd+json": {
"example": json.loads(JRD.Config.schema_extra['example']),
},
},
},
2022-12-10 10:34:05 +00:00
400: {
2022-12-26 00:32:57 +00:00
"description": (
"Bad Request:"
" No resource was provided."),
2022-12-10 10:34:05 +00:00
"content": {
"text/plain": {
"example": RESOURCE_NOT_PROVIDED
},
2022-12-10 10:34:05 +00:00
},
},
404: {
2022-12-26 00:32:57 +00:00
"description": (
"Not Found:"
" Could not find a document for the provided resource."),
2022-12-10 10:34:05 +00:00
"content": {
"text/plain": {
"example": RESOURCE_NOT_FOUND
},
2022-12-10 10:34:05 +00:00
},
},
500: {
2022-12-26 00:32:57 +00:00
"description": (
"Server Error:"
" The resource exists,"
" but contains invalid JSON or was unreadable."),
2022-12-10 10:34:05 +00:00
"content": {
"text/plain": {
"example": RESOURCE_NOT_PARSED
},
},
2022-12-10 10:34:05 +00:00
},
}
## The lookup method as described in RFC7033
@router.get(
"",
summary = "Lookup a Webfinger resource",
2022-12-26 00:32:57 +00:00
description = (
"Query the Webfinger service for a resource URI"
" and obtain its associated resource document"),
2022-12-10 10:34:05 +00:00
tags = ["RFC7033"],
response_model = JRD,
response_class = JRDResponse,
2022-12-10 10:34:05 +00:00
responses = {**RESPONSES},
)
2022-12-10 10:34:05 +00:00
async def lookup(
resource: str = Query(
None,
title = "Resource URI",
2022-12-26 00:32:57 +00:00
description = (
"The subject whose document you are looking up."
" If not provided, you will get a 400 Bad Request."
),
2022-12-10 10:34:05 +00:00
),
rel: list[str] = Query(
None,
title = "Link relation",
2022-12-26 00:32:57 +00:00
description = (
"If provided, filter the links array for this rel-value only."
" This parameter may be included multiple times."
),
2022-12-10 10:34:05 +00:00
)
):
"""
Respond to a WebFinger query.
"""
# If no resource is given, then show a basic hint.
if not resource:
return PlainTextResponse(
content = RESOURCE_NOT_PROVIDED,
2022-12-10 10:34:05 +00:00
status_code=400,
headers = HEADERS,
)
2022-12-26 00:32:57 +00:00
2022-12-10 10:34:05 +00:00
# Otherwise, try to read the resource document.
try:
jrd = get_jrd(resource)
2022-12-26 00:32:57 +00:00
except FileNotFoundError:
# JRD file does not exist
2022-12-10 10:34:05 +00:00
return PlainTextResponse(
content = RESOURCE_NOT_FOUND,
status_code = 404,
headers = HEADERS,
)
2022-12-26 00:32:57 +00:00
except (OSError, json.JSONDecodeError):
# JRD file could not be read or parsed
return PlainTextResponse(
content = RESOURCE_NOT_PARSED,
status_code = 500,
headers = HEADERS,
)
2022-12-26 00:32:57 +00:00
except ValidationError as exception:
# JRD was parsed but contained type or value errors
errors = [RESOURCE_NOT_VALID]
for error in exception.errors():
print(error)
errors.append(f"{error['msg']} for {error['loc'][0]}")
return PlainTextResponse(
content = '\n- '.join(errors),
status_code=500,
headers = HEADERS,
)
2022-12-10 10:34:05 +00:00
# If optional rel is provided, then filter only for links with that rel-type.
if rel:
2022-12-26 00:32:57 +00:00
jrd.links = [link for link in jrd.links if link.rel in rel]
2022-12-10 10:34:05 +00:00
# Construct JSON response and return it.
return JSONResponse(
2022-12-26 00:32:57 +00:00
content = jrd.dict(exclude_none=True),
media_type = "application/jrd+json",
headers = HEADERS,
)