Voice et bot modif
This commit is contained in:
parent
189d56026b
commit
7333a22bcd
10774 changed files with 634644 additions and 933308 deletions
163
venv/lib/python3.12/site-packages/huggingface_hub/_oidc.py
Normal file
163
venv/lib/python3.12/site-packages/huggingface_hub/_oidc.py
Normal file
|
|
@ -0,0 +1,163 @@
|
|||
# Copyright 2026 The HuggingFace Team. All rights reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
"""Keyless CI/CD authentication via OIDC token exchange ("Trusted Publishers").
|
||||
|
||||
A CI job proves its identity to the Hub with a short-lived OIDC id token minted by its CI
|
||||
provider (e.g. GitHub Actions), then exchanges it at ``POST {ENDPOINT}/oauth/token`` (RFC 8693)
|
||||
for a short-lived Hugging Face token — no long-lived ``HF_TOKEN`` secret to store.
|
||||
|
||||
This module is self-contained: it only handles minting the provider id token and the exchange.
|
||||
It deliberately does not register a public API or a CLI verb; the integration point is the token
|
||||
resolution in ``utils/_auth.py`` (see ``_get_token_from_oidc``).
|
||||
|
||||
Docs: https://huggingface.co/docs/hub/trusted-publishers
|
||||
"""
|
||||
|
||||
import os
|
||||
from enum import Enum
|
||||
|
||||
from . import constants
|
||||
from .errors import OIDCError
|
||||
from .utils import get_session, hf_raise_for_status
|
||||
|
||||
|
||||
# RFC 8693 token-exchange grant + id-token subject type (see trusted-publishers docs).
|
||||
_TOKEN_EXCHANGE_GRANT_TYPE = "urn:ietf:params:oauth:grant-type:token-exchange"
|
||||
_ID_TOKEN_TYPE = "urn:ietf:params:oauth:token-type:id_token"
|
||||
|
||||
|
||||
class Provider(str, Enum):
|
||||
"""CI providers that can mint an OIDC id token natively. GitHub Actions only for now."""
|
||||
|
||||
GITHUB = "github"
|
||||
|
||||
|
||||
def detect_provider() -> Provider | None:
|
||||
"""Detect the CI provider able to mint an OIDC id token, or `None` if not in a supported CI."""
|
||||
if os.environ.get("GITHUB_ACTIONS") == "true":
|
||||
return Provider.GITHUB
|
||||
return None
|
||||
|
||||
|
||||
def _get_github_oidc_token(audience: str) -> str:
|
||||
"""Mint an OIDC id token from the GitHub Actions runtime.
|
||||
|
||||
Relies on the `ACTIONS_ID_TOKEN_REQUEST_URL` / `ACTIONS_ID_TOKEN_REQUEST_TOKEN` env vars,
|
||||
which GitHub only injects when the job declares `permissions: id-token: write`.
|
||||
"""
|
||||
request_url = os.environ.get("ACTIONS_ID_TOKEN_REQUEST_URL")
|
||||
request_token = os.environ.get("ACTIONS_ID_TOKEN_REQUEST_TOKEN")
|
||||
if not request_url or not request_token:
|
||||
raise OIDCError(
|
||||
"Cannot request an OIDC id token from GitHub Actions. Make sure the workflow job sets "
|
||||
"`permissions: id-token: write`. See "
|
||||
"https://docs.github.com/en/actions/deployment/security-hardening-your-deployments/about-security-hardening-with-openid-connect"
|
||||
)
|
||||
response = get_session().get(
|
||||
request_url,
|
||||
params={"audience": audience},
|
||||
headers={"Authorization": f"Bearer {request_token}"},
|
||||
)
|
||||
hf_raise_for_status(response)
|
||||
return response.json()["value"]
|
||||
|
||||
|
||||
def get_oidc_token(*, provider: Provider | str | None = None, audience: str | None = None) -> str:
|
||||
"""Mint a raw OIDC id token (JWT) from the current CI provider.
|
||||
|
||||
Args:
|
||||
provider (`str`, *optional*):
|
||||
CI provider to use. Auto-detected from the environment when omitted.
|
||||
audience (`str`, *optional*):
|
||||
The `aud` claim to request. Defaults to `constants.ENDPOINT` so it matches the endpoint
|
||||
that validates it (respects `HF_ENDPOINT`/staging).
|
||||
|
||||
Returns:
|
||||
`str`: The raw id token (JWT) to pass to [`exchange_oidc_token`].
|
||||
"""
|
||||
audience = audience or constants.ENDPOINT
|
||||
provider = provider or detect_provider()
|
||||
supported = ", ".join(p.value for p in Provider)
|
||||
if provider is None:
|
||||
raise OIDCError(f"No supported CI OIDC provider detected. Trusted Publishers currently supports: {supported}.")
|
||||
if provider == Provider.GITHUB:
|
||||
return _get_github_oidc_token(audience)
|
||||
raise NotImplementedError(f"OIDC provider '{provider}' is not supported yet. Supported: {supported}.")
|
||||
|
||||
|
||||
def exchange_oidc_token(*, subject_token: str, resource: str, endpoint: str | None = None) -> dict:
|
||||
"""Exchange a CI OIDC id token for a short-lived Hugging Face token (RFC 8693).
|
||||
|
||||
Args:
|
||||
subject_token (`str`):
|
||||
The raw OIDC id token (JWT) from the CI provider. Its `aud` claim must be the Hub URL.
|
||||
resource (`str`):
|
||||
What to scope the token to: a Hub repo (`namespace/name`, `datasets/namespace/name`,
|
||||
`spaces/namespace/name`, `kernels/namespace/name`) for a write token, or a bare Hub
|
||||
username for a read-only `gated-repos` token.
|
||||
endpoint (`str`, *optional*):
|
||||
Hub endpoint. Defaults to `constants.ENDPOINT` (respects `HF_ENDPOINT`/staging).
|
||||
|
||||
Returns:
|
||||
`dict`: The token-exchange response, e.g.
|
||||
`{"access_token": "hf_jwt_…", "token_type": "bearer", "expires_in": 3600, ...}`.
|
||||
"""
|
||||
response = get_session().post(
|
||||
f"{endpoint or constants.ENDPOINT}/oauth/token",
|
||||
json={
|
||||
"grant_type": _TOKEN_EXCHANGE_GRANT_TYPE,
|
||||
"subject_token_type": _ID_TOKEN_TYPE,
|
||||
"subject_token": subject_token,
|
||||
"resource": resource,
|
||||
},
|
||||
)
|
||||
hf_raise_for_status(response)
|
||||
return response.json()
|
||||
|
||||
|
||||
def oidc_login(
|
||||
*,
|
||||
resource: str,
|
||||
subject_token: str | None = None,
|
||||
provider: Provider | str | None = None,
|
||||
audience: str | None = None,
|
||||
endpoint: str | None = None,
|
||||
) -> dict:
|
||||
"""Mint a CI OIDC id token and exchange it for a Hugging Face token.
|
||||
|
||||
Convenience wrapper around [`get_oidc_token`] + [`exchange_oidc_token`]. Returns the raw
|
||||
exchange response (it does not persist anything — the caller decides what to do with the token).
|
||||
|
||||
Args:
|
||||
resource (`str`):
|
||||
Repo or username to scope the token to. See [`exchange_oidc_token`].
|
||||
subject_token (`str`, *optional*):
|
||||
A pre-minted OIDC id token to exchange directly. Use this for CI providers not yet
|
||||
supported natively (e.g. GitLab): mint the id token in your job and pass it here. When
|
||||
omitted, the token is minted from the detected `provider`.
|
||||
provider (`str`, *optional*):
|
||||
CI provider. Auto-detected when omitted. Ignored when `subject_token` is provided.
|
||||
audience (`str`, *optional*):
|
||||
The `aud` claim to request. Defaults to the resolved `endpoint`, so it matches the
|
||||
endpoint that validates it.
|
||||
endpoint (`str`, *optional*):
|
||||
Hub endpoint. Defaults to `constants.ENDPOINT`.
|
||||
|
||||
Returns:
|
||||
`dict`: The token-exchange response (`access_token`, `token_type`, `expires_in`, ...).
|
||||
"""
|
||||
endpoint = endpoint or constants.ENDPOINT
|
||||
if subject_token is None:
|
||||
subject_token = get_oidc_token(provider=provider, audience=audience or endpoint)
|
||||
return exchange_oidc_token(subject_token=subject_token, resource=resource, endpoint=endpoint)
|
||||
Loading…
Add table
Add a link
Reference in a new issue