# streaming_providers/providers/rtlplus/auth.py import base64 import json import time import hashlib from typing import Any, Dict, Optional, List from ...base.auth.base_auth import BaseAuthToken, TokenAuthLevel from ...base.auth.base_oauth2_auth import BaseOAuth2Authenticator from ...base.models.proxy_models import ProxyConfig from ...base.utils.logger import logger from .constants import RTLPlusConfig, RTLPlusDefaults from .models import RTLPlusAuthToken, RTLPlusClientCredentials, RTLPlusUserCredentials class RTLPlusAuthenticator(BaseOAuth2Authenticator): def __init__( self, credentials=None, config_dir=None, client_version=None, device_id=None, proxy_config: Optional[ProxyConfig] = None, http_manager=None, ): config_dict = {} if client_version: config_dict["client_version"] = client_version if device_id: config_dict["device_id"] = device_id self._config = RTLPlusConfig(config_dict) self._client_id = None self._bedrock_token: Optional[str] = None self._bedrock_token_expiry: float = 0 self._cached_user_id: Optional[str] = None self._selected_profile_id: Optional[str] = None if proxy_config is None: from ...base.network import ProxyConfigManager proxy_mgr = ProxyConfigManager(config_dir) proxy_config = proxy_mgr.get_proxy_config("rtlplus") super().__init__( provider_name="rtlplus", credentials=credentials, config_dir=config_dir, proxy_config=proxy_config, http_manager=http_manager, ) if self.credentials is None: self.credentials = self._get_default_credentials() @property def auth_endpoint(self) -> str: return self.config.auth_endpoint @property def oauth_client_id(self) -> str: return RTLPlusDefaults.BEDROCK_CLIENT_ID # "bedrock-m6group_web" @property def oauth_scope(self) -> str: return "openid email profile" @property def oauth_redirect_uri(self) -> str: # Used in authorize step return "https://plus.rtl.de/tv-programm" @property def oauth_token_redirect_uri(self) -> str: # Used in the token exchange step return f"{self.config.base_website}silent-sso-iframe.html" @property def config(self) -> RTLPlusConfig: return self._config def _get_auth_headers(self) -> Dict[str, str]: return self.config.get_auth_headers() def _build_auth_payload(self) -> Dict[str, Any]: return self.credentials.to_auth_payload() def _get_default_credentials(self): try: config_creds = self._get_anonymous_credentials_from_config() if config_creds: return RTLPlusClientCredentials( client_id=config_creds.get("client_id", RTLPlusDefaults.ANONYMOUS_CLIENT_ID), client_secret=config_creds.get("client_secret", RTLPlusDefaults.ANONYMOUS_CLIENT_SECRET), ) except Exception as e: logger.warning(f"Could not get dynamic credentials: {e}") return RTLPlusClientCredentials() def _create_token_from_response(self, response_data: Dict[str, Any]) -> RTLPlusAuthToken: return RTLPlusAuthToken( access_token=response_data["access_token"], token_type=response_data.get("token_type", "Bearer"), expires_in=response_data.get("expires_in", 86400), issued_at=response_data.get("issued_at", time.time()), refresh_token=response_data.get("refresh_token"), refresh_expires_in=response_data.get("refresh_expires_in", 0), not_before_policy=response_data.get("not-before-policy"), scope=response_data.get("scope", ""), ) def get_current_token_level(self) -> TokenAuthLevel: for attr in ("token", "_token", "current_token", "_current_token", "_access_token"): tok = getattr(self, attr, None) if tok is not None: return self._classify_token(tok) return TokenAuthLevel.UNKNOWN def get_fallback_credentials(self): return self._get_default_credentials() def _classify_token(self, token: BaseAuthToken) -> TokenAuthLevel: if not token or not token.access_token: return TokenAuthLevel.UNKNOWN try: parts = token.access_token.split(".") if len(parts) < 2: return TokenAuthLevel.UNKNOWN payload_segment = parts[1] padding = 4 - len(payload_segment) % 4 if padding != 4: payload_segment += "=" * padding payload_json = base64.b64decode(payload_segment) payload = json.loads(payload_json) client_id = payload.get("clientId") is_guest = payload.get("isGuest", False) preferred_username = payload.get("preferred_username") email = payload.get("email") if preferred_username or email: return TokenAuthLevel.USER_AUTHENTICATED if is_guest and client_id == "anonymous-user": return TokenAuthLevel.CLIENT_CREDENTIALS return TokenAuthLevel.UNKNOWN except Exception as e: logger.warning(f"RTL+ Error classifying token: {e}") return TokenAuthLevel.UNKNOWN def _perform_oauth_authorization_code_flow(self, username: str, password: str) -> Dict[str, Any]: import uuid return self._perform_generic_form_login( username=username, password=password, form_selector_pattern=r'