168 lines
3.8 KiB
Python
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,
|
|
) |