Source code for core.metadata
""" Metadata about the instance, available through HTTP. """
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)