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] self.app = app
[docs] self.absorb = absorb
[docs] self.path = self.absorb and self.absorb.replace('/', '.')
[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
[docs] def application_id(self) -> str: return self.app.application_id
@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()
[docs] class PublicMetadata(Metadata):
[docs] def as_dict(self) -> dict[str, Any]: return super().for_audiences('public')
[docs] class SecretMetadata(Metadata):
[docs] def as_dict(self) -> dict[str, Any]: return super().for_audiences('public', 'secret')
@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)