mirror of
https://github.com/nirvana-7777/script.service.ultimate.git
synced 2026-09-29 20:42:40 +02:00
368 lines
15 KiB
Python
368 lines
15 KiB
Python
# streaming_providers/providers/auth_builder.py
|
|
|
|
from typing import Dict, Any, Optional, Tuple
|
|
from .auth_context import AuthContext
|
|
from ..base.models.auth import AuthStatus, TokenInfo, AuthState
|
|
|
|
|
|
class AuthStatusBuilder:
|
|
"""
|
|
Simplified builder for authentication status.
|
|
All logic in one class, no intermediate data dict.
|
|
"""
|
|
|
|
@staticmethod
|
|
def for_provider(provider, context: AuthContext) -> AuthStatus:
|
|
"""Build auth status directly from provider and context"""
|
|
|
|
# Get current auth type
|
|
current_auth_type = provider.get_current_auth_type(context)
|
|
|
|
# Calculate auth state (with provider override support)
|
|
auth_state = AuthStatusBuilder._calculate_auth_state_with_override(
|
|
provider, context
|
|
)
|
|
|
|
# Calculate readiness (with provider override support)
|
|
is_ready, reason = AuthStatusBuilder._calculate_readiness_with_override(
|
|
provider, context
|
|
)
|
|
|
|
# Build token info
|
|
token_scopes = AuthStatusBuilder._build_token_scopes(provider, context)
|
|
|
|
# Get credentials info
|
|
has_credentials, credentials_type = AuthStatusBuilder._get_credentials_info(
|
|
provider, context
|
|
)
|
|
|
|
# Get provider-specific details
|
|
provider_specific = AuthStatusBuilder._get_provider_specific_details(
|
|
provider, context, current_auth_type
|
|
)
|
|
|
|
# Get token expiration info
|
|
(token_expires_at, token_expires_in_seconds,
|
|
refresh_token_expires_at, refresh_token_expires_in_seconds) = \
|
|
AuthStatusBuilder._get_token_expiration_info(provider, context)
|
|
|
|
# Build and return
|
|
return AuthStatus(
|
|
provider_name=provider.provider_name,
|
|
provider_label=provider.provider_label,
|
|
country=provider.country,
|
|
auth_type=current_auth_type,
|
|
auth_state=auth_state,
|
|
is_ready=is_ready,
|
|
readiness_reason=reason,
|
|
requires_stored_credentials=provider.requires_stored_credentials,
|
|
has_credentials=has_credentials,
|
|
credentials_type=credentials_type,
|
|
has_valid_token=AuthStatusBuilder._has_valid_token(provider, context),
|
|
primary_token_scope=provider.primary_token_scope,
|
|
token_scopes=token_scopes,
|
|
last_authentication=AuthStatusBuilder._get_last_auth_time(provider, context),
|
|
provider_specific=provider_specific,
|
|
token_expires_at=token_expires_at,
|
|
token_expires_in_seconds=token_expires_in_seconds,
|
|
refresh_token_expires_at=refresh_token_expires_at,
|
|
refresh_token_expires_in_seconds=refresh_token_expires_in_seconds
|
|
)
|
|
|
|
# ===== Calculation Methods (with provider override support) =====
|
|
|
|
@staticmethod
|
|
def _calculate_auth_state_with_override(provider, context: AuthContext) -> AuthState:
|
|
"""Calculate auth state, allowing provider override"""
|
|
# Check if provider has custom logic
|
|
if hasattr(provider, '_calculate_auth_state'):
|
|
custom_state = provider._calculate_auth_state(context)
|
|
if custom_state:
|
|
return custom_state
|
|
|
|
# Standard calculation
|
|
return AuthStatusBuilder._calculate_auth_state(provider, context)
|
|
|
|
@staticmethod
|
|
def _calculate_readiness_with_override(provider, context: AuthContext) -> Tuple[bool, str]:
|
|
"""Calculate readiness, allowing provider override"""
|
|
# Check if provider has custom logic
|
|
if hasattr(provider, '_calculate_readiness'):
|
|
custom_result = provider._calculate_readiness(context)
|
|
if custom_result:
|
|
return custom_result
|
|
|
|
# Standard calculation
|
|
return AuthStatusBuilder._calculate_readiness(provider, context)
|
|
|
|
# ===== Standard Calculation Methods =====
|
|
|
|
@staticmethod
|
|
def _calculate_auth_state(provider, context: AuthContext) -> AuthState:
|
|
"""Standard auth state calculation"""
|
|
|
|
# Anonymous providers don't need auth
|
|
if 'anonymous' in provider.supported_auth_types:
|
|
return AuthState.NOT_APPLICABLE
|
|
|
|
# Check if we have any valid token
|
|
if AuthStatusBuilder._has_valid_token(provider, context):
|
|
return AuthState.AUTHENTICATED
|
|
|
|
# Check if we have an expired token that can be refreshed
|
|
expired_token = AuthStatusBuilder._get_expired_token_with_refresh(provider, context)
|
|
if expired_token:
|
|
return AuthState.EXPIRED
|
|
|
|
# Not authenticated
|
|
return AuthState.NOT_AUTHENTICATED
|
|
|
|
@staticmethod
|
|
def _calculate_readiness(provider, context: AuthContext) -> Tuple[bool, str]:
|
|
"""
|
|
Standard readiness calculation.
|
|
|
|
Priority order:
|
|
1. Anonymous providers are always ready
|
|
2. Valid token = ready (credentials not needed if already authenticated)
|
|
3. Expired refreshable token = ready (can refresh)
|
|
4. Check for stored credentials
|
|
5. Network-based providers (special case)
|
|
"""
|
|
|
|
# 1. Anonymous providers are always ready
|
|
if 'anonymous' in provider.supported_auth_types:
|
|
return True, "Anonymous provider always ready"
|
|
|
|
# 2. Check if we have a valid token (MOST IMPORTANT)
|
|
if AuthStatusBuilder._has_valid_token(provider, context):
|
|
return True, "Has valid authentication token"
|
|
|
|
# 3. Check if we have an expired token that can be refreshed
|
|
expired_token = AuthStatusBuilder._get_expired_token_with_refresh(provider, context)
|
|
if expired_token:
|
|
return True, "Has expired token with refresh capability"
|
|
|
|
# 4. Check credentials if required (only matters if no valid token)
|
|
if provider.requires_stored_credentials:
|
|
credentials = context.get_credentials(provider.provider_name, provider.country)
|
|
if not credentials:
|
|
return False, "Missing required credentials"
|
|
|
|
# Has credentials but no token yet
|
|
return False, "Has credentials but needs authentication"
|
|
|
|
# 5. Network-based providers might be authenticating
|
|
if 'network_based' in provider.supported_auth_types:
|
|
return False, "Network authentication in progress"
|
|
|
|
# 6. Not ready
|
|
return False, "Not authenticated"
|
|
|
|
# ===== Helper Methods =====
|
|
|
|
@staticmethod
|
|
def _has_valid_token(provider, context: AuthContext) -> bool:
|
|
"""Check if provider has any valid token"""
|
|
# Check primary scope first
|
|
if provider.primary_token_scope:
|
|
token = context.get_token(provider.provider_name, provider.primary_token_scope, provider.country)
|
|
if token and not context.is_token_expired(token):
|
|
return True
|
|
|
|
# Check all scopes
|
|
for scope in provider.token_scopes:
|
|
token = context.get_token(provider.provider_name, scope, provider.country)
|
|
if token and not context.is_token_expired(token):
|
|
return True
|
|
|
|
# Check root-level token
|
|
token = context.get_token(provider.provider_name, None, provider.country)
|
|
if token and not context.is_token_expired(token):
|
|
return True
|
|
|
|
return False
|
|
|
|
@staticmethod
|
|
def _get_expired_token_with_refresh(provider, context: AuthContext) -> Optional[Dict[str, Any]]:
|
|
"""Get an expired token that has refresh capability"""
|
|
# Check primary scope
|
|
if provider.primary_token_scope:
|
|
token = context.get_token(provider.provider_name, provider.primary_token_scope, provider.country)
|
|
if token and context.is_token_expired(token) and token.get('refresh_token'):
|
|
return token
|
|
|
|
# Check all scopes
|
|
for scope in provider.token_scopes:
|
|
token = context.get_token(provider.provider_name, scope, provider.country)
|
|
if token and context.is_token_expired(token) and token.get('refresh_token'):
|
|
return token
|
|
|
|
# Check root-level token
|
|
token = context.get_token(provider.provider_name, None, provider.country)
|
|
if token and context.is_token_expired(token) and token.get('refresh_token'):
|
|
return token
|
|
|
|
return None
|
|
|
|
@staticmethod
|
|
def _build_token_scopes(provider, context: AuthContext) -> Dict[str, TokenInfo]:
|
|
"""Build token scope information"""
|
|
token_scopes = {}
|
|
|
|
for scope in provider.token_scopes:
|
|
token_data = context.get_token(provider.provider_name, scope, provider.country)
|
|
if token_data:
|
|
expires_at = None
|
|
if 'issued_at' in token_data and 'expires_in' in token_data:
|
|
expires_at = token_data['issued_at'] + token_data['expires_in']
|
|
|
|
token_info = TokenInfo(
|
|
scope=scope,
|
|
has_token=True,
|
|
is_valid=not context.is_token_expired(token_data),
|
|
expires_at=expires_at,
|
|
has_refresh_token=bool(token_data.get('refresh_token')),
|
|
auth_level=token_data.get('auth_level')
|
|
)
|
|
else:
|
|
token_info = TokenInfo(
|
|
scope=scope,
|
|
has_token=False,
|
|
is_valid=False
|
|
)
|
|
token_scopes[scope] = token_info
|
|
|
|
return token_scopes
|
|
|
|
@staticmethod
|
|
def _get_credentials_info(provider, context: AuthContext) -> Tuple[bool, Optional[str]]:
|
|
"""Get credentials information"""
|
|
if not provider.requires_stored_credentials:
|
|
return False, None
|
|
|
|
credentials = context.get_credentials(provider.provider_name, provider.country)
|
|
if credentials:
|
|
return True, credentials.credential_type
|
|
|
|
return False, None
|
|
|
|
@staticmethod
|
|
def _get_last_auth_time(provider, context: AuthContext) -> Optional[float]:
|
|
"""Get last authentication time from token"""
|
|
if provider.primary_token_scope:
|
|
token = context.get_token(provider.provider_name, provider.primary_token_scope, provider.country)
|
|
if token and 'issued_at' in token:
|
|
return token['issued_at']
|
|
|
|
# Check root-level token
|
|
token = context.get_token(provider.provider_name, None, provider.country)
|
|
if token and 'issued_at' in token:
|
|
return token['issued_at']
|
|
|
|
return None
|
|
|
|
@staticmethod
|
|
def _get_provider_specific_details(provider, context: AuthContext,
|
|
current_auth_type: str) -> Dict[str, Any]:
|
|
"""Get provider-specific auth details"""
|
|
details = {
|
|
'supported_auth_types': provider.supported_auth_types,
|
|
'preferred_auth_type': provider.preferred_auth_type,
|
|
'current_auth_type': current_auth_type
|
|
}
|
|
|
|
# Add provider's own details if available
|
|
if hasattr(provider, 'get_auth_details'):
|
|
custom_details = provider.get_auth_details(context)
|
|
details.update(custom_details)
|
|
|
|
return details
|
|
|
|
@staticmethod
|
|
def _get_token_expiration_info(provider, context: AuthContext) -> Tuple[
|
|
Optional[float], Optional[int], Optional[float], Optional[int]
|
|
]:
|
|
"""
|
|
Get token expiration information.
|
|
|
|
Returns:
|
|
Tuple of (token_expires_at, token_expires_in_seconds,
|
|
refresh_token_expires_at, refresh_token_expires_in_seconds)
|
|
"""
|
|
import time
|
|
current_time = time.time()
|
|
|
|
token_expires_at: Optional[float] = None
|
|
token_expires_in_seconds: Optional[int] = None
|
|
refresh_token_expires_at: Optional[float] = None
|
|
refresh_token_expires_in_seconds: Optional[int] = None
|
|
|
|
# Try to get the primary token
|
|
token_data: Optional[Dict[str, Any]] = None
|
|
|
|
# Check primary scope first
|
|
if provider.primary_token_scope:
|
|
token_data = context.get_token(
|
|
provider.provider_name,
|
|
provider.primary_token_scope,
|
|
provider.country
|
|
)
|
|
|
|
# If no primary scope or no token, check root-level token
|
|
if not token_data:
|
|
token_data = context.get_token(
|
|
provider.provider_name,
|
|
None,
|
|
provider.country
|
|
)
|
|
|
|
# If still no token, try first available scope
|
|
if not token_data and provider.token_scopes:
|
|
for scope in provider.token_scopes:
|
|
token_data = context.get_token(
|
|
provider.provider_name,
|
|
scope,
|
|
provider.country
|
|
)
|
|
if token_data:
|
|
break
|
|
|
|
if not token_data:
|
|
return None, None, None, None
|
|
|
|
# Calculate access token expiration
|
|
# Standard format: expires_in + issued_at
|
|
if 'expires_in' in token_data and 'issued_at' in token_data:
|
|
expires_in = token_data['expires_in']
|
|
issued_at = token_data['issued_at']
|
|
if isinstance(expires_in, (int, float)) and isinstance(issued_at, (int, float)):
|
|
token_expires_at = float(issued_at) + float(expires_in)
|
|
token_expires_in_seconds = int(token_expires_at - current_time)
|
|
|
|
# yo_digital format: separate access token expiration
|
|
elif 'access_token_expires_in' in token_data and 'access_token_issued_at' in token_data:
|
|
expires_in = token_data['access_token_expires_in']
|
|
issued_at = token_data['access_token_issued_at']
|
|
if isinstance(expires_in, (int, float)) and isinstance(issued_at, (int, float)):
|
|
token_expires_at = float(issued_at) + float(expires_in)
|
|
token_expires_in_seconds = int(token_expires_at - current_time)
|
|
|
|
# Direct expiration timestamp
|
|
elif 'expires_at' in token_data:
|
|
expires_at = token_data['expires_at']
|
|
if isinstance(expires_at, (int, float)):
|
|
token_expires_at = float(expires_at)
|
|
token_expires_in_seconds = int(token_expires_at - current_time)
|
|
|
|
# Calculate refresh token expiration (yo_digital format)
|
|
if ('refresh_token_expires_in' in token_data and
|
|
'refresh_token_issued_at' in token_data):
|
|
refresh_expires_in = token_data['refresh_token_expires_in']
|
|
refresh_issued_at = token_data['refresh_token_issued_at']
|
|
if isinstance(refresh_expires_in, (int, float)) and isinstance(refresh_issued_at, (int, float)):
|
|
refresh_token_expires_at = float(refresh_issued_at) + float(refresh_expires_in)
|
|
refresh_token_expires_in_seconds = int(refresh_token_expires_at - current_time)
|
|
|
|
return (token_expires_at, token_expires_in_seconds,
|
|
refresh_token_expires_at, refresh_token_expires_in_seconds) |