token variant
This commit is contained in:
+60
-6
@@ -14,7 +14,11 @@ service account); they differ only in the OAuth *scope* and the header prefix:
|
|||||||
* Google Ads -> ``X-GAds-*`` (scope adwords) + developer token
|
* Google Ads -> ``X-GAds-*`` (scope adwords) + developer token
|
||||||
|
|
||||||
Each provides ``*-Access-Token`` (ready Bearer token, takes precedence) and
|
Each provides ``*-Access-Token`` (ready Bearer token, takes precedence) and
|
||||||
``*-Credentials`` (base64 service-account JSON; the proxy mints a token).
|
``*-Credentials`` (base64 service-account JSON; the proxy mints a token). As an
|
||||||
|
alternative, the ready access token may also be supplied via the standard
|
||||||
|
``Authorization: Bearer <token>`` header (the service-specific ``*-Access-Token``
|
||||||
|
wins if both are present); this keeps the original header path intact while
|
||||||
|
offering the conventional bearer-token path.
|
||||||
|
|
||||||
Sklik uses ``X-Sklik-Token`` (the proxy calls client.loginByToken).
|
Sklik uses ``X-Sklik-Token`` (the proxy calls client.loginByToken).
|
||||||
"""
|
"""
|
||||||
@@ -47,6 +51,29 @@ class GoogleAdsCredentials:
|
|||||||
login_customer_id: str | None
|
login_customer_id: str | None
|
||||||
|
|
||||||
|
|
||||||
|
def _bearer_from_authorization(authorization: str | None) -> str | None:
|
||||||
|
"""Extract the token from a standard ``Authorization: Bearer <token>`` header.
|
||||||
|
|
||||||
|
Only the ``Bearer`` scheme is accepted; any other scheme (e.g. ``Basic``) is
|
||||||
|
ignored so the caller falls through to the other credential sources and gets
|
||||||
|
a clear "no credentials" error rather than a token that cannot work.
|
||||||
|
"""
|
||||||
|
if not authorization:
|
||||||
|
return None
|
||||||
|
parts = authorization.strip().split(None, 1)
|
||||||
|
if len(parts) == 2 and parts[0].lower() == "bearer":
|
||||||
|
return parts[1].strip() or None
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def _authorization_description(token_header: str) -> str:
|
||||||
|
return (
|
||||||
|
"Standard OAuth2 bearer token, sent as 'Authorization: Bearer <token>'. "
|
||||||
|
f"Alternative to {token_header} (which wins if both are present) and to "
|
||||||
|
"the service-account credentials header. Must carry the service's scope."
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def _build_google_credentials(
|
def _build_google_credentials(
|
||||||
access_token: str | None,
|
access_token: str | None,
|
||||||
raw_credentials: str | None,
|
raw_credentials: str | None,
|
||||||
@@ -55,9 +82,17 @@ def _build_google_credentials(
|
|||||||
*,
|
*,
|
||||||
token_header: str,
|
token_header: str,
|
||||||
creds_header: str,
|
creds_header: str,
|
||||||
|
authorization: str | None = None,
|
||||||
) -> GoogleCredentials:
|
) -> GoogleCredentials:
|
||||||
"""Parse a Google access token / base64 service-account JSON from headers."""
|
"""Parse a Google access token / base64 service-account JSON from headers.
|
||||||
|
|
||||||
|
The ready access token may arrive either in the service-specific
|
||||||
|
``*-Access-Token`` header (takes precedence, kept for backward
|
||||||
|
compatibility) or in a standard ``Authorization: Bearer <token>`` header.
|
||||||
|
"""
|
||||||
token = (access_token or "").strip() or None
|
token = (access_token or "").strip() or None
|
||||||
|
if token is None:
|
||||||
|
token = _bearer_from_authorization(authorization)
|
||||||
|
|
||||||
service_account_info: dict | None = None
|
service_account_info: dict | None = None
|
||||||
raw = (raw_credentials or "").strip()
|
raw = (raw_credentials or "").strip()
|
||||||
@@ -81,7 +116,8 @@ def _build_google_credentials(
|
|||||||
|
|
||||||
if not token and service_account_info is None:
|
if not token and service_account_info is None:
|
||||||
raise MissingCredentialsError(
|
raise MissingCredentialsError(
|
||||||
f"Provide either {token_header} or {creds_header}."
|
f"Provide either {token_header}, an 'Authorization: Bearer <token>' "
|
||||||
|
f"header, or {creds_header}."
|
||||||
)
|
)
|
||||||
|
|
||||||
return GoogleCredentials(
|
return GoogleCredentials(
|
||||||
@@ -98,7 +134,12 @@ def get_ga_credentials(
|
|||||||
default=None,
|
default=None,
|
||||||
alias="X-GA-Access-Token",
|
alias="X-GA-Access-Token",
|
||||||
description="Ready OAuth2 access token used directly as a Bearer token. "
|
description="Ready OAuth2 access token used directly as a Bearer token. "
|
||||||
"Takes precedence over X-GA-Credentials.",
|
"Takes precedence over Authorization and X-GA-Credentials.",
|
||||||
|
),
|
||||||
|
authorization: str | None = Header(
|
||||||
|
default=None,
|
||||||
|
alias="Authorization",
|
||||||
|
description=_authorization_description("X-GA-Access-Token"),
|
||||||
),
|
),
|
||||||
x_ga_credentials: str | None = Header(
|
x_ga_credentials: str | None = Header(
|
||||||
default=None,
|
default=None,
|
||||||
@@ -121,6 +162,7 @@ def get_ga_credentials(
|
|||||||
config.GA_SCOPE,
|
config.GA_SCOPE,
|
||||||
token_header="X-GA-Access-Token",
|
token_header="X-GA-Access-Token",
|
||||||
creds_header="X-GA-Credentials",
|
creds_header="X-GA-Credentials",
|
||||||
|
authorization=authorization,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -130,7 +172,12 @@ def get_gsc_credentials(
|
|||||||
default=None,
|
default=None,
|
||||||
alias="X-GSC-Access-Token",
|
alias="X-GSC-Access-Token",
|
||||||
description="Ready OAuth2 access token used directly as a Bearer token. "
|
description="Ready OAuth2 access token used directly as a Bearer token. "
|
||||||
"Takes precedence over X-GSC-Credentials.",
|
"Takes precedence over Authorization and X-GSC-Credentials.",
|
||||||
|
),
|
||||||
|
authorization: str | None = Header(
|
||||||
|
default=None,
|
||||||
|
alias="Authorization",
|
||||||
|
description=_authorization_description("X-GSC-Access-Token"),
|
||||||
),
|
),
|
||||||
x_gsc_credentials: str | None = Header(
|
x_gsc_credentials: str | None = Header(
|
||||||
default=None,
|
default=None,
|
||||||
@@ -152,6 +199,7 @@ def get_gsc_credentials(
|
|||||||
config.GSC_SCOPE,
|
config.GSC_SCOPE,
|
||||||
token_header="X-GSC-Access-Token",
|
token_header="X-GSC-Access-Token",
|
||||||
creds_header="X-GSC-Credentials",
|
creds_header="X-GSC-Credentials",
|
||||||
|
authorization=authorization,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -167,7 +215,12 @@ def get_google_ads_credentials(
|
|||||||
default=None,
|
default=None,
|
||||||
alias="X-GAds-Access-Token",
|
alias="X-GAds-Access-Token",
|
||||||
description="Ready OAuth2 access token used directly as a Bearer token. "
|
description="Ready OAuth2 access token used directly as a Bearer token. "
|
||||||
"Takes precedence over X-GAds-Credentials.",
|
"Takes precedence over Authorization and X-GAds-Credentials.",
|
||||||
|
),
|
||||||
|
authorization: str | None = Header(
|
||||||
|
default=None,
|
||||||
|
alias="Authorization",
|
||||||
|
description=_authorization_description("X-GAds-Access-Token"),
|
||||||
),
|
),
|
||||||
x_gads_credentials: str | None = Header(
|
x_gads_credentials: str | None = Header(
|
||||||
default=None,
|
default=None,
|
||||||
@@ -199,6 +252,7 @@ def get_google_ads_credentials(
|
|||||||
config.GOOGLE_ADS_SCOPE,
|
config.GOOGLE_ADS_SCOPE,
|
||||||
token_header="X-GAds-Access-Token",
|
token_header="X-GAds-Access-Token",
|
||||||
creds_header="X-GAds-Credentials",
|
creds_header="X-GAds-Credentials",
|
||||||
|
authorization=authorization,
|
||||||
)
|
)
|
||||||
login_customer_id = (x_gads_login_customer_id or "").strip().replace("-", "") or None
|
login_customer_id = (x_gads_login_customer_id or "").strip().replace("-", "") or None
|
||||||
return GoogleAdsCredentials(
|
return GoogleAdsCredentials(
|
||||||
|
|||||||
+9
-2
@@ -45,6 +45,12 @@ jen prefixem hlavičky a oprávněním (scope). **Jeden service account lze pou
|
|||||||
pro všechny tři** (stačí mu udělit přístup v dané službě a povolit příslušné
|
pro všechny tři** (stačí mu udělit přístup v dané službě a povolit příslušné
|
||||||
API). U Google Ads navíc vždy potřebujete *developer token*.
|
API). U Google Ads navíc vždy potřebujete *developer token*.
|
||||||
|
|
||||||
|
> 💡 Hotový OAuth2 *access token* lze u všech tří Google služeb poslat dvěma
|
||||||
|
> způsoby: buď v původní hlavičce `X-…-Access-Token`, **nebo** ve standardní
|
||||||
|
> hlavičce `Authorization: Bearer <token>`. Obě cesty jsou rovnocenné; pokud
|
||||||
|
> pošlete obě, vyhrává `X-…-Access-Token`. Pořadí priority je
|
||||||
|
> `X-…-Access-Token` → `Authorization: Bearer` → `X-…-Credentials`.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Kde vzít přihlašovací údaje
|
## Kde vzít přihlašovací údaje
|
||||||
@@ -65,9 +71,10 @@ API). U Google Ads navíc vždy potřebujete *developer token*.
|
|||||||
|
|
||||||
Service account (jeho `client_email` z JSON) pak musíte **přidat jako uživatele
|
Service account (jeho `client_email` z JSON) pak musíte **přidat jako uživatele
|
||||||
v dané službě** – viz níže. Místo service accountu lze vždy poslat i hotový
|
v dané službě** – viz níže. Místo service accountu lze vždy poslat i hotový
|
||||||
OAuth2 *access token* v `*-Access-Token` (např. z
|
OAuth2 *access token* (např. z
|
||||||
[OAuth Playground](https://developers.google.com/oauthplayground/) se správným
|
[OAuth Playground](https://developers.google.com/oauthplayground/) se správným
|
||||||
scope); platí ~1 hodinu.
|
scope); platí ~1 hodinu. Token pošlete buď v `*-Access-Token`, nebo ve
|
||||||
|
standardní hlavičce `Authorization: Bearer <token>`.
|
||||||
|
|
||||||
### 🔹 Google Analytics 4 (`X-GA-*`)
|
### 🔹 Google Analytics 4 (`X-GA-*`)
|
||||||
|
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ Google Ads needs more than a Bearer token:
|
|||||||
| --- | --- | --- |
|
| --- | --- | --- |
|
||||||
| `X-GAds-Developer-Token` | **yes** | Developer token from a Google Ads manager account → `developer-token`. |
|
| `X-GAds-Developer-Token` | **yes** | Developer token from a Google Ads manager account → `developer-token`. |
|
||||||
| `X-GAds-Access-Token` | one of these | Ready OAuth2 access token (Bearer). |
|
| `X-GAds-Access-Token` | one of these | Ready OAuth2 access token (Bearer). |
|
||||||
|
| `Authorization` | one of these | Standard `Authorization: Bearer <token>` header — equivalent alternative to `X-GAds-Access-Token` (the X- header wins if both are sent). The developer token is still required separately. |
|
||||||
| `X-GAds-Credentials` | one of these | Base64 service-account JSON (scope `adwords`); needs domain-wide delegation. |
|
| `X-GAds-Credentials` | one of these | Base64 service-account JSON (scope `adwords`); needs domain-wide delegation. |
|
||||||
| `X-GAds-Login-Customer-Id` | no | Manager (MCC) id → `login-customer-id`. Digits only. |
|
| `X-GAds-Login-Customer-Id` | no | Manager (MCC) id → `login-customer-id`. Digits only. |
|
||||||
| `X-GAds-Quota-Project` | no | GCP project id → `x-goog-user-project`. |
|
| `X-GAds-Quota-Project` | no | GCP project id → `x-goog-user-project`. |
|
||||||
|
|||||||
@@ -10,11 +10,15 @@ Token has precedence over the service account:
|
|||||||
| Header | Meaning |
|
| Header | Meaning |
|
||||||
| --- | --- |
|
| --- | --- |
|
||||||
| `X-GA-Access-Token` | Ready OAuth2 access token, used directly as `Authorization: Bearer`. |
|
| `X-GA-Access-Token` | Ready OAuth2 access token, used directly as `Authorization: Bearer`. |
|
||||||
|
| `Authorization` | Standard `Authorization: Bearer <token>` header — equivalent alternative to `X-GA-Access-Token`. |
|
||||||
| `X-GA-Credentials` | **Base64** of a Google service-account JSON key. The proxy mints a short-lived token (scope `https://www.googleapis.com/auth/analytics.readonly`) via `google-auth` and caches it in memory until ~60 s before expiry. |
|
| `X-GA-Credentials` | **Base64** of a Google service-account JSON key. The proxy mints a short-lived token (scope `https://www.googleapis.com/auth/analytics.readonly`) via `google-auth` and caches it in memory until ~60 s before expiry. |
|
||||||
| `X-GA-Quota-Project` | Optional GCP project id → upstream `x-goog-user-project`. |
|
| `X-GA-Quota-Project` | Optional GCP project id → upstream `x-goog-user-project`. |
|
||||||
|
|
||||||
At least one of `X-GA-Access-Token` / `X-GA-Credentials` is required (otherwise
|
A ready access token can be supplied either in `X-GA-Access-Token` or in the
|
||||||
`401 missing_credentials`).
|
standard `Authorization: Bearer <token>` header. At least one credential source
|
||||||
|
(token header, `Authorization: Bearer`, or `X-GA-Credentials`) is required,
|
||||||
|
otherwise `401 missing_credentials`. Priority when several are present:
|
||||||
|
`X-GA-Access-Token` → `Authorization: Bearer` → `X-GA-Credentials`.
|
||||||
|
|
||||||
The service account (or token) must have access to the GA4 property — add its
|
The service account (or token) must have access to the GA4 property — add its
|
||||||
`client_email` as a viewer in GA Admin → Property Access Management.
|
`client_email` as a viewer in GA Admin → Property Access Management.
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ Same Google OAuth model as Analytics, token wins over service account:
|
|||||||
| Header | Meaning |
|
| Header | Meaning |
|
||||||
| --- | --- |
|
| --- | --- |
|
||||||
| `X-GSC-Access-Token` | Ready OAuth2 access token (Bearer). |
|
| `X-GSC-Access-Token` | Ready OAuth2 access token (Bearer). |
|
||||||
|
| `Authorization` | Standard `Authorization: Bearer <token>` header — equivalent alternative to `X-GSC-Access-Token` (the X- header wins if both are sent). |
|
||||||
| `X-GSC-Credentials` | Base64 service-account JSON; token minted with scope `https://www.googleapis.com/auth/webmasters.readonly`. |
|
| `X-GSC-Credentials` | Base64 service-account JSON; token minted with scope `https://www.googleapis.com/auth/webmasters.readonly`. |
|
||||||
| `X-GSC-Quota-Project` | Optional GCP project id → `x-goog-user-project`. |
|
| `X-GSC-Quota-Project` | Optional GCP project id → `x-goog-user-project`. |
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user