Skip to content

Anatomy of a response micro service

Ivan Kanakarakis edited this page Mar 5, 2020 · 11 revisions

SATOSA micro-services

Anatomy of a response micro_service

A micro-service has configuration and code. The configuration is given to the micro-service on initialization. Any validation on the configuration should be done at this point. The micro-service is loaded and awaits execution.

When the right time comes, the micro-service is invoked through the process() function. The process() function receives two parameters: the context of the current request, and data which reflects the internal data representation of the protocol information, as transformed by the corresponding frontend or backend.

The micro-service (most of the time) will add/remove/change data.attributes and in the end it will return the result for the next micro-service to work upon.

Configuration example

module: example.satosa.micro_services.micro_service_module.NameOfMicroservice
name: An example micro-service skeleton
config:
  capitalize_name: True

Code example

"""A micro-service example."""

import logging

import satosa
import satosa.logging_util as lu
from satosa.micro_services.base import ResponseMicroService


logger = logging.getLogger(__name__)


class NameOfMicroservice(ResponseMicroService):
    """
    A micro-service to show an example skeleton.

    Example configuration:

    ```yaml
    module: python.path.to.the.class.NameOfMicroservice
    name: An example micro-service skeleton
    config:
        capitalize_name: True
    ```
    """

    def __init__(self, config, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.capitalize_name = config.get("capitalize_name", False)
        log_debug("initialized the micro-service")

    def process(
        self, context: satosa.context.Context, data: satosa.internal.InternalData
    ):
        log_debug("running the micro-service", context)

        # do things with data.attributes
        if self.capitalize_name:
            data.attributes["name"] = [
                value.upper() for value in data.attributes.get("name", [])
            ]

            # log a message
            message = "name has been capitilized"
            log_info(message, context)

        log_debug("end of the micro-service", context)
        # return updated context and data
        return super().process(context, data)


# some logging helpers to avoid duplication


def log_debug(msg, context=None):
    _log(logging.DEBUG, msg, context)


def log_info(msg, context=None):
    _log(logging.INFO, msg, context)


def _log(loglevel, msg, context):
    state = context.state if context else None
    session_id = lu.get_session_id(state)
    line = lu.LOG_FMT.format(id=session_id, message=msg)
    logger.log(loglevel, line)

Example content of data param

# type: satosa.internal.InternalData
data = {
    # type: satosa.internal.AuthenticationInformation
    # This part holds the authentication information
    # namely, the authentication context classes (ie, the LoA reference)
    # and the issuer of the given identity (ie, the IdP entity-id)
    "auth_info": {
        "auth_class_ref": "urn:oasis:names:tc:SAML:2.0:ac:classes:Password",
        "timestamp": "2020-02-22T19:30:04Z",
        "issuer": "https://www.rediris.es/sir/umaidp",
    },

    # the requester of the authentication; this is typically the SP (or RP) entity-id
    "requester": "https://example.org/saml-sp/metadata.xml",
    "requester_name": [{"text": None, "lang": "en"}],

    # the subject identifier as expressed in different protocols, along with its type or format.
    # this will match `NameID` for SAML2 and `sub` for OIDC.
    "subject_id": "69e83a116ed953279999d4463541c2799795c816",
    "subject_type": "urn:oasis:names:tc:SAML:2.0:nameid-format:persistent",

    # the attributes of the subject as expressed in different protocols.
    # this will be a representation of claims for OIDC,
    # or attribute statements of an assertion for SAML2.
    "attributes": {
        "edupersontargetedid": ["04e8192d48a040f3ef495999958908f8aa10b4a5"],
        "displayname": ["Somename Somesurname"],
        "givenname": ["Somename"],
        "mail": ["[email protected]"],
        "name": ["Somename"],
        "surname": ["Somesurname"],
        "epsa": ["[email protected]", "[email protected]"],
        "eppn": ["[email protected]"],
        "spuc": [
            "urn:schac:personalUniqueCode:es:rediris:sir:mbid:{sha1}0c938d124632017100980299997b1ab174789657",
            "urn:schac:personalUniqueCode:es:uma:CAU:id:822",
            "urn:schac:personalUniqueCode:es:uma:ESC:code:a98b1a8c-9215-11e9-8545-000077349997",
            "urn:schac:personalUniqueCode:es:uma:codUni:06100004X",
        ],
    },

    # this has been added by another micro-service; namely metainfo
    # the role of that micro-service is to collect metadata information about entities
    # and present it in a unified way.
    # other micro-services can lookup the metadata of an entity using this structure.
    "metadata": {
        "https://www.rediris.es/sir/umaidp": {
            "display_name": "University of Malaga",
            "privacy_statement": None,
            "contacts": [
                {
                    "contact_type": "technical",
                    "given_name": "SIR helpdesk",
                    "email_address": ["mailto:[email protected]"],
                },
                {
                    "contact_type": "other",
                    "given_name": "RedIRIS SIRTFI-CSIRT Team",
                    "email_address": ["mailto:[email protected]"],
                },
            ],
            "entity_categories": [],
            "supported_entity_categories": [
                "http://refeds.org/category/research-and-scholarship"
            ],
            "assurance_certifications": ["https://refeds.org/sirtfi"],
        },
        "https://example.org/saml-sp/metadata.xml": {
            "display_name": None,
            "privacy_statement": None,
            "contacts": [],
            "entity_categories": [],
            "supported_entity_categories": [],
            "assurance_certifications": [],
        },
    }
}

Example content of context param

context = {
    "cookie": 'SATOSA_PROXY_STATE="..."',
    "internal_data": {
        "metadata_store": <saml2.mdstore.MetadataStore object at 0x7fc60e32cb70>
    },
    "request": None,
    "request_authorization": "",
    "state": {
        "ROUTER": "Saml2IDP",
        "SATOSA_BASE": {"requester": "..."},
        "Saml2IDP": {
            "relay_state": "https://example.org/authenticate?as=some-sp",
            "resp_args": {
                "binding": "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST",
                "destination": "https://example.org/saml-sp/saml2-acs",
                "in_response_to": "_e923792fde97aa0b6bc82999cba5274a61eae4b96c",
                "name_id_policy": """<ns0:NameIDPolicy xmlns:ns0="urn:oasis:names:tc:SAML:2.0:protocol" AllowCreate="true" Format="urn:oasis:names:tc:SAML:2.0:nameid-format:transient"/>""",
                "sp_entity_id": "...",
            },
        },
        "memorized_idp": "...",
    },
    "target_backend": "saml2sp",
    "target_frontend": None,
    "target_micro_service": None,
}