Source code for core.metadata
""" Metadata about the instance, available through HTTP. """
from __future__ import annotations
import hashlib
import inspect
import morepath
import socket
from onegov.core.framework import Framework
from onegov.core.security import Public, Secret
from onegov.core.utils import dict_path
from webob import exc
# The metadata responses contain either public or public+secret responses,
# depending on the user's authorization. This simply controls the output
# of in the view though. Public properties may still load secret properties
# if they desire to do so. So this is not as much a security feature as it
# is a handy way to control the output.
from typing import Any, Literal, TYPE_CHECKING
if TYPE_CHECKING:
from collections.abc import Callable, Iterator
from .request import CoreRequest
# NOTE: For the purposes of type checking we treat them like
# regular properties, that way we get support for all
# the property features without having to implement a
# complete Protocol that implements all the features
#
# Technically it isn't quite correct, since we can't
# use the full property constructor with these, but
# it's good enough for now...
public_property = secret_property = property # noqa: TC009
else:
[docs]
def public_property(fn: Callable[[Any], Any]) -> property:
fn.audience = 'public'
return property(fn)
def secret_property(fn: Callable[[Any], Any]) -> property:
fn.audience = 'secret'
return property(fn)
[docs]
class Metadata:
def __init__(self, app: Framework, absorb: str | None = None):
[docs]
def for_audiences(
self,
*audiences: Literal['public', 'secret']
) -> dict[str, Any]:
""" Returns a dict with the metadata for the given audience(s). """
def pick(
properties: list[tuple[str, Any]]
) -> Iterator[tuple[str, Any]]:
for name, prop in properties:
if not hasattr(prop, 'fget'):
continue
audience = getattr(prop.fget, 'audience', None)
if audience is None or audience not in audiences:
continue
assert prop.fget is not None
yield name, prop.fget(self)
props = inspect.getmembers(self.__class__, inspect.isdatadescriptor)
return dict(pick(props))
@secret_property
[docs]
def fqdn(self) -> str:
""" Returns the fqdn of the host running the site. """
return socket.getfqdn()
@secret_property
@public_property
[docs]
def identity(self) -> str:
""" Each instance has a unqiue identity formed out of the hostname and
the application id.
"""
digest = hashlib.sha256()
digest.update(self.fqdn.encode('utf-8'))
digest.update(self.application_id.encode('utf-8'))
return digest.hexdigest()
@Framework.path(model=PublicMetadata, path='/metadata/public', absorb=True)
[docs]
def get_public_metadata(app: Framework, absorb: str) -> PublicMetadata:
return PublicMetadata(app, absorb)
@Framework.path(model=SecretMetadata, path='/metadata/secret', absorb=True)
[docs]
def get_private_metadata(app: Framework, absorb: str) -> SecretMetadata:
return SecretMetadata(app, absorb)
@Framework.json(model=PublicMetadata, permission=Public)
[docs]
def view_public_metadata(
self: PublicMetadata,
request: CoreRequest
) -> morepath.Response:
return render_metadata(self, request)
@Framework.json(model=SecretMetadata, permission=Secret)
[docs]
def view_secret_metadata(
self: PublicMetadata,
request: CoreRequest
) -> morepath.Response:
return render_metadata(self, request)
[docs]
def render_metadata(
self: PublicMetadata | SecretMetadata,
request: CoreRequest
) -> morepath.Response:
data = self.as_dict()
if self.path:
try:
response = morepath.Response(dict_path(data, self.path))
response.content_type = 'text/plain'
return response
except KeyError as exception:
raise exc.HTTPNotFound() from exception
return morepath.render_json(data, request)