flip_api.auth.access_manager ============================ .. py:module:: flip_api.auth.access_manager Attributes ---------- .. autoapisummary:: flip_api.auth.access_manager.API_KEY_HEADER_NAME flip_api.auth.access_manager.api_key_header_scheme flip_api.auth.access_manager._trust_api_key_hashes_cache flip_api.auth.access_manager.INTERNAL_SERVICE_KEY_HEADER_NAME flip_api.auth.access_manager.internal_key_header_scheme flip_api.auth.access_manager._internal_service_key_hash_cache Functions --------- .. autoapisummary:: flip_api.auth.access_manager.can_access_project flip_api.auth.access_manager.can_modify_project flip_api.auth.access_manager.can_contribute_to_project flip_api.auth.access_manager.can_modify_model flip_api.auth.access_manager.can_access_model flip_api.auth.access_manager.can_access_cohort_query flip_api.auth.access_manager.verify_trust_identity flip_api.auth.access_manager._get_trust_api_key_hashes flip_api.auth.access_manager._get_internal_service_key_hash flip_api.auth.access_manager.authenticate_internal_service flip_api.auth.access_manager.authenticate_trust Module Contents --------------- .. py:function:: can_access_project(user_id: uuid.UUID, project_id: uuid.UUID, db: sqlmodel.Session) -> bool Check if a user has access to a specific project. :param user_id: ID of the user :type user_id: UUID :param project_id: ID of the project :type project_id: UUID :param db: Database session :type db: Session :returns: True if the user has access to the project, False otherwise :rtype: bool .. py:function:: can_modify_project(user_id: uuid.UUID, project_id: uuid.UUID, db: sqlmodel.Session) -> bool Check if a user can perform project-level write operations (edit / stage / delete the project itself). Returns True for Admins (CAN_MANAGE_PROJECTS) and the project owner. Project membership alone does NOT unlock project-level writes — see :func:`can_contribute_to_project` for the looser check used by model-write endpoints. :param user_id: ID of the user :type user_id: UUID :param project_id: ID of the project :type project_id: UUID :param db: Database session :type db: Session :returns: True if the user can modify the project, False otherwise :rtype: bool .. py:function:: can_contribute_to_project(user_id: uuid.UUID, project_id: uuid.UUID, db: sqlmodel.Session) -> bool Check if a user can contribute artefacts (e.g. models) to a project. Looser than :func:`can_modify_project`: a Researcher who has been added to a project via ``ProjectUserAccess`` can contribute their own models even though they cannot edit the project itself. Observers — who hold no permissions — are excluded by the ``CAN_CREATE_PROJECTS`` clause, so membership alone does not unlock writes for them. Returns True when any of the following holds: - The caller has ``CAN_MANAGE_PROJECTS`` (Admin), or - The caller is the project owner, or - The caller has a ``ProjectUserAccess`` row for the project AND holds ``CAN_CREATE_PROJECTS`` (Researcher member). :param user_id: ID of the user :type user_id: UUID :param project_id: ID of the project :type project_id: UUID :param db: Database session :type db: Session :returns: True if the user can contribute to the project, False otherwise. :rtype: bool .. py:function:: can_modify_model(user_id: uuid.UUID, model_id: uuid.UUID, db: sqlmodel.Session) -> bool Check if a user can perform write operations on a model. Allows: - Admins (``CAN_MANAGE_PROJECTS``). - The project owner (unrestricted across all models on the project). - The model's own ``owner_id``, but only if they could still contribute to the project (per :func:`can_contribute_to_project`). The contribution check is defence-in-depth: it locks an Observer out even if they somehow ended up as ``Model.owner_id``. :param user_id: ID of the user :type user_id: UUID :param model_id: ID of the model :type model_id: UUID :param db: Database session :type db: Session :returns: True if the user can modify the model, False otherwise :rtype: bool .. py:function:: can_access_model(user_id: uuid.UUID, model_id: uuid.UUID, db: sqlmodel.Session) -> bool Check if a user has access to a specific model. :param user_id: ID of the user :type user_id: UUID :param model_id: ID of the model :type model_id: UUID :param db: Database session :type db: Session :returns: True if the user has access to the model, False otherwise :rtype: bool :raises HTTPException: If there is an error during the access check .. py:function:: can_access_cohort_query(user_id: uuid.UUID, query_id: uuid.UUID, db: sqlmodel.Session) -> bool Check if the user has access to the specified cohort query. :param user_id: ID of the user :type user_id: UUID :param query_id: ID of the cohort query :type query_id: UUID :param db: Database session :type db: Session :returns: True if the user has access to the cohort query, False otherwise :rtype: bool :raises HTTPException: If there is an error during the access check .. py:data:: API_KEY_HEADER_NAME .. py:data:: api_key_header_scheme .. py:function:: verify_trust_identity(trust_name: str, authenticated_trust: str) -> None Verify the authenticated trust matches the expected trust name. :param trust_name: The trust name from the URL path. :type trust_name: str :param authenticated_trust: The trust name from API key authentication. :type authenticated_trust: str :raises HTTPException: 403 if the names do not match. .. py:data:: _trust_api_key_hashes_cache :type: dict[str, str] | None :value: None .. py:function:: _get_trust_api_key_hashes() -> dict[str, str] Get trust API key hashes from env var (dev) or AWS Secrets Manager (prod). Cached after first call — the hashes do not change during the lifetime of a process. :returns: Mapping of trust names to SHA-256 hex digests of their API keys. :rtype: dict[str, str] .. py:data:: INTERNAL_SERVICE_KEY_HEADER_NAME .. py:data:: internal_key_header_scheme .. py:data:: _internal_service_key_hash_cache :type: str | None :value: None .. py:function:: _get_internal_service_key_hash() -> str Get internal service key hash from env var (dev) or AWS Secrets Manager (prod). Cached after first call — the hash does not change during the lifetime of a process. :returns: SHA-256 hex digest of the internal service key, or empty string if not configured. :rtype: str .. py:function:: authenticate_internal_service(api_key: str = Security(internal_key_header_scheme)) -> None Authenticate an internal service (e.g., fl-server on the Central Hub). The fl-server sends an internal service key in the X-Internal-Service-Key header. This dependency hashes the provided key and compares it against the stored hash using constant-time comparison. :param api_key: The internal service key from the request header. :type api_key: str :raises HTTPException: 401 if the key is missing, unconfigured, or invalid. .. py:function:: authenticate_trust(api_key: str = Security(api_key_header_scheme)) -> str Authenticate a trust by its per-trust API key and return the trust name. Each trust has a unique API key. The hub stores SHA-256 hashes of these keys in TRUST_API_KEY_HASHES (env var in dev, AWS Secrets Manager in prod). This dependency hashes the provided key, looks it up in the mapping, and returns the authenticated trust name. Uses hmac.compare_digest for constant-time comparison to prevent timing attacks. :param api_key: The API key extracted from the request header. :type api_key: str :raises HTTPException: 401 if the key is missing or does not match any trust. :returns: The name of the authenticated trust (e.g. "Trust_1"). :rtype: str