webfinger/webfinger/lookup.py

168 lines
3.8 KiB
Python

"""
Perform WebFinger lookups according to RFC7033
"""
import json
from fastapi import APIRouter
from fastapi.responses import PlainTextResponse, JSONResponse
from fastapi.params import Query
from pydantic import ValidationError
from webfinger.io import get_jrd
from webfinger.models import JRD, JRDResponse
router = APIRouter(
prefix="/.well-known/webfinger",
)
# Define required CORS header per RFC7033
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})
## Example responses for OpenAPI schema
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"
RESPONSES = {
200: {
"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']),
},
},
},
400: {
"description": (
"Bad Request:"
" No resource was provided."),
"content": {
"text/plain": {
"example": RESOURCE_NOT_PROVIDED
},
},
},
404: {
"description": (
"Not Found:"
" Could not find a document for the provided resource."),
"content": {
"text/plain": {
"example": RESOURCE_NOT_FOUND
},
},
},
500: {
"description": (
"Server Error:"
" The resource exists,"
" but contains invalid JSON or was unreadable."),
"content": {
"text/plain": {
"example": RESOURCE_NOT_PARSED
},
},
},
}
## The lookup method as described in RFC7033
@router.get(
"",
summary = "Lookup a Webfinger resource",
description = (
"Query the Webfinger service for a resource URI"
" and obtain its associated resource document"),
tags = ["RFC7033"],
response_model = JRD,
response_class = JRDResponse,
responses = {**RESPONSES},
)
async def lookup(
resource: str = Query(
None,
title = "Resource URI",
description = (
"The subject whose document you are looking up."
" If not provided, you will get a 400 Bad Request."
),
),
rel: list[str] = Query(
None,
title = "Link relation",
description = (
"If provided, filter the links array for this rel-value only."
" This parameter may be included multiple times."
),
)
):
"""
Respond to a WebFinger query.
"""
# If no resource is given, then show a basic hint.
if not resource:
return PlainTextResponse(
content = RESOURCE_NOT_PROVIDED,
status_code=400,
headers = HEADERS,
)
# Otherwise, try to read the resource document.
try:
jrd = get_jrd(resource)
except FileNotFoundError:
# JRD file does not exist
return PlainTextResponse(
content = RESOURCE_NOT_FOUND,
status_code = 404,
headers = HEADERS,
)
except (OSError, json.JSONDecodeError):
# JRD file could not be read or parsed
return PlainTextResponse(
content = RESOURCE_NOT_PARSED,
status_code = 500,
headers = HEADERS,
)
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,
)
# If optional rel is provided, then filter only for links with that rel-type.
if rel:
jrd.links = [link for link in jrd.links if link.rel in rel]
# Construct JSON response and return it.
return JSONResponse(
content = jrd.dict(exclude_none=True),
media_type = "application/jrd+json",
headers = HEADERS,
)