Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 10 additions & 3 deletions src/sap_cloud_sdk/dms/_auth.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import logging
import time
import requests
from collections import OrderedDict
from requests.exceptions import RequestException
from typing import Optional, TypedDict
from sap_cloud_sdk.dms.exceptions import (
Expand All @@ -18,7 +19,6 @@ class _TokenResponse(TypedDict):
expires_in: int


# TODO: limit number of access tokens in cache to 10
class _CachedToken:
def __init__(self, token: str, expires_at: float) -> None:
self.token = token
Expand All @@ -28,26 +28,33 @@ def is_valid(self) -> bool:
return time.monotonic() < self.expires_at - 30


# TODO: limit number of access tokens in cache to 10
_MAX_CACHE_SIZE = 10


class Auth:
"""Fetches and caches OAuth2 access tokens for DMS service requests."""

def __init__(self, credentials: DMSCredentials) -> None:
self._credentials = credentials
self._cache: dict[str, _CachedToken] = {}
self._cache: OrderedDict[str, _CachedToken] = OrderedDict()

def get_token(self, tenant_subdomain: Optional[str] = None) -> str:
cache_key = tenant_subdomain or "technical"

cached = self._cache.get(cache_key)
if cached and cached.is_valid():
self._cache.move_to_end(cache_key) # Mark as recently used by moving to end
logger.debug("Using cached token for key '%s'", cache_key)
return cached.token

logger.debug("Fetching new token for key '%s'", cache_key)
token_url = self._resolve_token_url(tenant_subdomain)
token = self._fetch_token(token_url)

if len(self._cache) >= _MAX_CACHE_SIZE:
evicted, _ = self._cache.popitem(last=False)
logger.debug("Cache full — evicted token for key '%s'", evicted)

self._cache[cache_key] = _CachedToken(
token=token["access_token"],
expires_at=time.monotonic() + token.get("expires_in", 3600),
Expand Down
2 changes: 1 addition & 1 deletion src/sap_cloud_sdk/dms/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ def load_sdm_config_from_env_or_mount(instance: Optional[str] = None) -> DMSCred
read_from_mount_and_fallback_to_env_var(
base_volume_mount="/etc/secrets/appfnd",
base_var_name="CLOUD_SDK_CFG",
module="sdm", # TODO check if this should be "dms" or "sdm"
module="sdm",
instance=inst,
target=binding,
)
Expand Down
8 changes: 7 additions & 1 deletion src/sap_cloud_sdk/dms/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,12 @@ class ConfigName(str, Enum):
IS_CROSS_DOMAIN_MAPPING_ALLOWED = "isCrossDomainMappingAllowed"


class HashAlgorithm(str, Enum):
MD5 = "MD5"
SHA1 = "SHA-1"
SHA256 = "SHA-256"


@dataclass
class UserClaim:
"""User identity claims forwarded to the DMS service.
Expand Down Expand Up @@ -117,7 +123,7 @@ class InternalRepoRequest:
isVersionEnabled: Optional[bool] = None
isVirusScanEnabled: Optional[bool] = None
skipVirusScanForLargeFile: Optional[bool] = None
hashAlgorithms: Optional[str] = None # TODO provide enum
hashAlgorithms: Optional[HashAlgorithm] = None
isThumbnailEnabled: Optional[bool] = None
isEncryptionEnabled: Optional[bool] = None
externalId: Optional[str] = None
Expand Down
Loading