mirror of
https://github.com/nirvana-7777/script.service.ultimate.git
synced 2026-10-20 23:02:44 +02:00
fix provider eon login
This commit is contained in:
@@ -4,8 +4,9 @@ Eon authentication -- the auth class itself satisfies AuthProtocol
|
||||
(get_access_token / build_headers / invalidate); managers get it directly.
|
||||
|
||||
Three identities are involved (see PVREon / HttpClient):
|
||||
* client token -- OAuth client_credentials at the broker; only used for
|
||||
device registration. Re-fetched on 401.
|
||||
* client token -- OAuth client_credentials at the broker; shared with
|
||||
EonEndpoints (brand / CDN discovery) and used for device
|
||||
registration. Owned by EonClientToken, re-fetched on 401.
|
||||
* device -- serial (client-generated), deviceId / deviceNumber
|
||||
(server-minted). CLIENT IDENTITY: persisted, never
|
||||
cleared with the session.
|
||||
@@ -59,7 +60,7 @@ from ...base.errors import AuthError, ProviderError, ServerError, error_from_exc
|
||||
from ...base.utils.logger import logger
|
||||
from ...base.utils.transport import transport_errors
|
||||
from .constants import EonConfig, EonDefaults
|
||||
from .bootstrap import EonEndpoints
|
||||
from .bootstrap import EonClientToken, EonEndpoints
|
||||
from .errors import EonLoginRejected, EonOtpRequiredError
|
||||
from .models import EonDeviceRegistration, EonTokenResponse
|
||||
|
||||
@@ -118,6 +119,7 @@ class EonAuth:
|
||||
http_manager: Any,
|
||||
config: EonConfig,
|
||||
endpoints: EonEndpoints,
|
||||
client_token: EonClientToken,
|
||||
store: Optional[EonStore] = None,
|
||||
credentials: Any = None,
|
||||
credentials_loader: Optional[Callable[[], Any]] = None,
|
||||
@@ -126,6 +128,7 @@ class EonAuth:
|
||||
self._http = http_manager
|
||||
self._config = config
|
||||
self._endpoints = endpoints
|
||||
self._client_token = client_token
|
||||
self._store: EonStore = store if store is not None else MemoryStore()
|
||||
self._credentials = credentials # object with .username / .password
|
||||
self._credentials_loader = credentials_loader # lazy: no disk/network in __init__
|
||||
@@ -133,10 +136,8 @@ class EonAuth:
|
||||
self._stream_user: Optional[str] = None
|
||||
self._access_token: Optional[str] = None
|
||||
self._expires_at: Optional[float] = None
|
||||
self._generic_token: Optional[str] = None
|
||||
self._pending_otp: Optional[str] = None
|
||||
self._lock = threading.RLock()
|
||||
self._generic_lock = threading.RLock()
|
||||
self._clock = clock
|
||||
self.last_error: Optional[Exception] = None
|
||||
self._permanent: Optional[Exception] = None
|
||||
@@ -385,29 +386,8 @@ class EonAuth:
|
||||
raise AuthError(f"{_PROVIDER}: the one-time code was not confirmed or has expired") from exc
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Client token + device registration
|
||||
# Generic client-token calls + device registration
|
||||
# ------------------------------------------------------------------
|
||||
def _client_token(self, *, force: bool = False) -> str:
|
||||
with self._generic_lock:
|
||||
if self._generic_token and not force:
|
||||
return self._generic_token
|
||||
with transport_errors("client token", _PROVIDER):
|
||||
try:
|
||||
resp = self._http.post(
|
||||
EonDefaults.BROKER_URL + EonDefaults.PATH_OAUTH_TOKEN,
|
||||
params={"grant_type": "client_credentials"},
|
||||
json={},
|
||||
headers=self._config.basic_headers(),
|
||||
operation="auth",
|
||||
)
|
||||
token = resp.json().get("access_token")
|
||||
except HTTPError as exc:
|
||||
raise error_from_exception(exc) from exc
|
||||
if not token:
|
||||
raise ServerError(f"{_PROVIDER}: client token response has no access_token")
|
||||
self._generic_token = token
|
||||
return token
|
||||
|
||||
def _generic_call(self, method: str, path: str, *, params: Optional[Dict[str, Any]] = None,
|
||||
body: Optional[Dict[str, Any]] = None) -> Any:
|
||||
"""Call with the client token; one retry after refreshing it on 401.
|
||||
@@ -417,7 +397,7 @@ class EonAuth:
|
||||
raise NotImplementedError(f"http_manager has no {method}()")
|
||||
url = self._endpoints.url(path)
|
||||
for attempt in (1, 2):
|
||||
headers = self._config.bearer_headers(self._client_token(force=attempt == 2))
|
||||
headers = self._config.bearer_headers(self._client_token.get(force=attempt == 2))
|
||||
kwargs: Dict[str, Any] = {"headers": headers, "operation": "api", "raise_for_status": False}
|
||||
if params:
|
||||
kwargs["params"] = params
|
||||
|
||||
@@ -2,12 +2,15 @@
|
||||
"""
|
||||
Lazy bootstrap data. Nothing here runs in __init__ (no network there).
|
||||
|
||||
EonEndpoints -- resolves the brand's API host via the broker (v2/brands ->
|
||||
cdnIdentifier, v1/cdninfo -> domains.baseApi[selector]).
|
||||
Needs NO login: broker calls carry the client credentials.
|
||||
EonCatalog -- rendering profiles, streaming servers and the service-provider
|
||||
id. These need a logged-in session, so they sit behind EonApi
|
||||
and are cached with a TTL.
|
||||
EonClientToken -- OAuth client_credentials token at the broker. Needed by the
|
||||
broker calls (brand / CDN discovery) and by the device
|
||||
registration; no user, no device involved.
|
||||
EonEndpoints -- resolves the brand's API host via the broker (v2/brands ->
|
||||
cdnIdentifier, v1/cdninfo -> domains.baseApi[selector]).
|
||||
Needs the CLIENT TOKEN, no user login.
|
||||
EonCatalog -- rendering profiles, streaming servers and the service-provider
|
||||
id. These need a logged-in session, so they sit behind EonApi
|
||||
and are cached with a TTL.
|
||||
|
||||
Brand resolution is by IDENTIFIER, never by list position: the broker's list
|
||||
has changed before. An unknown brand falls back to the CDN the broker marks
|
||||
@@ -18,7 +21,9 @@ import threading
|
||||
import time
|
||||
from typing import Any, Dict, List, Optional, Tuple
|
||||
|
||||
from ...base.errors import ConfigurationError, ServerError
|
||||
from requests.exceptions import HTTPError
|
||||
|
||||
from ...base.errors import ConfigurationError, ServerError, error_from_exception
|
||||
from ...base.utils.logger import logger
|
||||
from ...base.utils.transport import transport_errors
|
||||
from .constants import EonConfig, EonDefaults
|
||||
@@ -27,11 +32,43 @@ from .models import EonRenderingProfile, EonServer
|
||||
_PROVIDER = EonDefaults.PROVIDER_LABEL
|
||||
|
||||
|
||||
class EonEndpoints:
|
||||
class EonClientToken:
|
||||
"""OAuth client_credentials token at the broker. No user, no device."""
|
||||
|
||||
def __init__(self, *, http_manager: Any, config: EonConfig):
|
||||
self._http = http_manager
|
||||
self._config = config
|
||||
self._lock = threading.RLock()
|
||||
self._token: Optional[str] = None
|
||||
|
||||
def get(self, *, force: bool = False) -> str:
|
||||
with self._lock:
|
||||
if self._token and not force:
|
||||
return self._token
|
||||
with transport_errors("client token", _PROVIDER):
|
||||
try:
|
||||
resp = self._http.post(
|
||||
EonDefaults.BROKER_URL + EonDefaults.PATH_OAUTH_TOKEN,
|
||||
params={"grant_type": "client_credentials"},
|
||||
json={},
|
||||
headers=self._config.basic_headers(),
|
||||
operation="auth",
|
||||
)
|
||||
token = resp.json().get("access_token")
|
||||
except HTTPError as exc:
|
||||
raise error_from_exception(exc) from exc
|
||||
if not token:
|
||||
raise ServerError(f"{_PROVIDER}: client token response has no access_token")
|
||||
self._token = token
|
||||
return token
|
||||
|
||||
|
||||
class EonEndpoints:
|
||||
def __init__(self, *, http_manager: Any, config: EonConfig, client_token: EonClientToken):
|
||||
self._http = http_manager
|
||||
self._config = config
|
||||
self._client_token = client_token
|
||||
self._lock = threading.RLock()
|
||||
self._api_base: Optional[str] = None
|
||||
self._images_base: Optional[str] = None
|
||||
|
||||
@@ -87,12 +124,18 @@ class EonEndpoints:
|
||||
|
||||
def _broker_get(self, path: str, what: str) -> Any:
|
||||
with transport_errors(what, _PROVIDER):
|
||||
resp = self._http.get(
|
||||
EonDefaults.BROKER_URL + path,
|
||||
headers=self._config.basic_headers(),
|
||||
operation="api",
|
||||
)
|
||||
return resp.json()
|
||||
for attempt in (1, 2):
|
||||
token = self._client_token.get(force=attempt == 2)
|
||||
resp = self._http.get(
|
||||
EonDefaults.BROKER_URL + path,
|
||||
headers=self._config.bearer_headers(token),
|
||||
operation="api",
|
||||
raise_for_status=False,
|
||||
)
|
||||
if resp.status_code != 401 or attempt == 2:
|
||||
resp.raise_for_status()
|
||||
return resp.json()
|
||||
raise ServerError(f"{_PROVIDER}: unreachable") # pragma: no cover
|
||||
|
||||
def _brand_cdn_identifier(self) -> str:
|
||||
brands = self._broker_get(EonDefaults.PATH_BRANDS, "brands")
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
# streaming_providers/providers/eon/provider.py
|
||||
# streaming_providers/providers/eon/provider.py
|
||||
"""
|
||||
Eon provider -- orchestrator only.
|
||||
|
||||
The provider owns the SHARED resources (config, http_manager, endpoints,
|
||||
auth, api, catalog, caches) and BUILDS the managers. Everything identical
|
||||
across providers is inherited from ManagedProvider.
|
||||
The provider owns the SHARED resources (config, http_manager, client_token,
|
||||
endpoints, auth, api, catalog, caches) and BUILDS the managers. Everything
|
||||
identical across providers is inherited from ManagedProvider.
|
||||
|
||||
Eon is a white-label platform: `config["brand"]` (vivacom, nova, sbb,
|
||||
telemach, telemach-hr, telemach-si) picks the operator, `config["platform"]`
|
||||
@@ -19,7 +20,7 @@ from ...base.managed_provider import ManagedProvider
|
||||
from ...base.models.proxy_models import ProxyConfig
|
||||
from .api import EonApi
|
||||
from .auth import EonAuth, EonStore
|
||||
from .bootstrap import EonCatalog, EonEndpoints
|
||||
from .bootstrap import EonCatalog, EonClientToken, EonEndpoints
|
||||
from .catchup_manager import EonCatchupManager
|
||||
from .channel_manager import EonChannelManager
|
||||
from .constants import EonConfig, EonDefaults
|
||||
@@ -80,12 +81,17 @@ class EonProvider(ManagedProvider):
|
||||
)
|
||||
|
||||
# 4. Lazy collaborators: no network in __init__.
|
||||
self.endpoints = EonEndpoints(
|
||||
self.client_token = EonClientToken(
|
||||
http_manager=self.http_manager, config=self.provider_config
|
||||
)
|
||||
self.endpoints = EonEndpoints(
|
||||
http_manager=self.http_manager, config=self.provider_config,
|
||||
client_token=self.client_token,
|
||||
)
|
||||
self.auth = EonAuth(
|
||||
http_manager=self.http_manager, config=self.provider_config,
|
||||
endpoints=self.endpoints,
|
||||
client_token=self.client_token,
|
||||
store=store if store is not None else default_store,
|
||||
credentials_loader=credentials_loader,
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user