[ Web Proxy ]
URL:
Viewing: https://raw.githubusercontent.com/feast-dev/feast/master/sdk/python/feast/permissions/auth_model.py [Back]  [Original]

from __future__ import annotations

from typing import Literal, Optional, Tuple

from pydantic import ConfigDict, Field, model_validator

from feast.repo_config import FeastConfigBaseModel


def _check_mutually_exclusive(**groups: Tuple[object, ...]) -> None:
    """Validate that at most one named group is configured, and completely.

    Each *group* is a tuple of field values.
    A group is **active** only when *all* its values are truthy.
    A group is **partial** (error) when *any* but not *all* values are truthy.
    At most one active group may exist.
    """
    partial = [name for name, vals in groups.items() if any(vals) and not all(vals)]
    if partial:
        raise ValueError(
            f"Incomplete configuration for '{partial[0]}': "
            f"configure all of these fields together, or none at all. "
            f"Check the documentation for valid credential combinations."
        )
    active = [name for name, vals in groups.items() if all(vals)]
    if len(active) > 1:
        raise ValueError(
            f"Only one of [{', '.join(groups)}] may be set, "
            f"but got: {', '.join(active)}"
        )


class AuthConfig(FeastConfigBaseModel):
    type: Literal["oidc", "kubernetes", "no_auth"] = "no_auth"


class OidcAuthConfig(AuthConfig):
    auth_discovery_url: str
    client_id: Optional[str] = None
    ui_client_id: Optional[str] = None
    verify_ssl: bool = True
    ca_cert_path: str = ""
    # When set, incoming tokens must carry a matching `aud` / `iss` claim;
    # when left unset (the default), the corresponding claim is not verified.
    # Set these to the values your IdP puts in the token itself, which may
    # differ from the discovery document (e.g. Entra ID v1.0 tokens validated
    # against a v2.0 discovery URL).
    audience: Optional[str] = None
    issuer: Optional[str] = None
    # How long the fetched JWK set is reused before the server refetches it.
    # This also bounds how long a key the IdP has revoked keeps validating
    # tokens, so lower it if your provider rotates or revokes aggressively;
    # every reduction costs a corresponding increase in JWKS fetches.
    jwks_cache_lifespan_seconds: int = Field(default=300, gt=0)
    # Network timeout for the JWKS fetch. This fetch happens inline on the
    # request path, so an unresponsive IdP blocks serving for at most this
    # long.
    jwks_request_timeout_seconds: float = Field(default=10, gt=0)


class OidcClientAuthConfig(OidcAuthConfig):
    auth_discovery_url: Optional[str] = None  # type: ignore[assignment]
    client_id: Optional[str] = None

    username: Optional[str] = None
    password: Optional[str] = None
    client_secret: Optional[str] = None
    token: Optional[str] = None
    token_env_var: Optional[str] = None
    # Stop reusing an IdP-issued token this many seconds before it expires,
    # so a reused token still has life left when the server validates it.
    # Raise it if clients see sporadic 401s from clock skew or slow calls;
    # lower it to squeeze more reuse out of short-lived tokens.
    token_refresh_margin_seconds: float = Field(default=30, gt=0)

    @model_validator(mode="after")
    def _validate_credentials(self):
        network = (self.client_secret, self.auth_discovery_url, self.client_id)
        if self.username or self.password:
            network += (self.username, self.password)

        _check_mutually_exclusive(
            token=(self.token,),
            token_env_var=(self.token_env_var,),
            client_credentials=network,
        )
        return self


class NoAuthConfig(AuthConfig):
    pass


class KubernetesAuthConfig(AuthConfig):
    user_token: Optional[str] = None

    model_config = ConfigDict(arbitrary_types_allowed=True, extra="allow")

Web Proxy Viewer  |  New URL  |  Original Page