refactor(drm): drop the hardcoded service branch in the license flow

One service needed session_id in its license call, which a near-duplicate of get_content_keys supplied behind a service-tag test. service_licence now passes each service only the arguments its own license function declares, so the duplicate and the branch are gone. The REST API license paths and the base PlayReady delegate get the same filtering, replacing their fixed argument set.

Two behaviour changes ride along: an empty license challenge now raises EmptyLicense for every service, not just the one, and a service that declares only challenge now receives a working call instead of a TypeError.
This commit is contained in:
imSp4rky
2026-09-05 22:14:47 -06:00
parent e845e9ba28
commit 0f9997be4c
8 changed files with 564 additions and 61 deletions
+34
View File
@@ -0,0 +1,34 @@
from __future__ import annotations
from unshackle.core.utilities import declared_kwargs
SENT = {"challenge": b"c", "title": "t", "track": "tr", "pssh": "P", "session_id": "S"}
def test_keeps_only_declared_arguments() -> None:
def licence(*, challenge: bytes, title: str, track: str) -> None: ...
assert sorted(declared_kwargs(licence, SENT)) == ["challenge", "title", "track"]
def test_passes_session_id_to_a_function_that_asks_for_it() -> None:
def licence(*, challenge: bytes, title: str, track: str, session_id: str | None = None) -> None: ...
assert sorted(declared_kwargs(licence, SENT)) == ["challenge", "session_id", "title", "track"]
def test_a_var_keyword_function_gets_everything() -> None:
def licence(*, challenge: bytes, **kwargs: object) -> None: ...
assert declared_kwargs(licence, SENT) == SENT
def test_an_unreadable_signature_gets_everything() -> None:
# min() has no readable signature, and a compiled service may not either
assert declared_kwargs(min, SENT) == SENT
def test_a_signature_we_cannot_call_by_keyword_gets_everything() -> None:
def licence(challenge, /, **kwargs: object) -> None: ...
assert declared_kwargs(licence, SENT) == SENT
@@ -0,0 +1,95 @@
from __future__ import annotations
from typing import Any
from uuid import UUID
import pytest
from pywidevine.pssh import PSSH
from unshackle.core.drm import Widevine
from unshackle.core.utilities import declared_kwargs
KID = UUID("00112233-4455-6677-8899-aabbccddeeff")
KEY = "00000000000000000000000000000001"
class FakeKey:
def __init__(self, kid: UUID, key: str) -> None:
self.kid = kid
self.key = bytes.fromhex(key)
self.type = "CONTENT"
class FakeCdm:
"""The smallest CDM the licensing flow accepts; records what it was given."""
service_certificate_challenge = b"cert-challenge"
def __init__(self, challenge: bytes = b"challenge") -> None:
self._challenge = challenge
self.parsed: list[Any] = []
def open(self) -> str:
return "session-1"
def close(self, session_id: str) -> None:
pass
def get_license_challenge(self, session_id: str, pssh: PSSH) -> bytes:
return self._challenge
def parse_license(self, session_id: str, response: Any) -> None:
self.parsed.append(response)
def get_keys(self, session_id: str, type_: str) -> list[FakeKey]:
return [FakeKey(KID, KEY)]
def _drm() -> Widevine:
return Widevine(pssh=PSSH.new(system_id=PSSH.SystemId.Widevine, key_ids=[KID]), kid=KID)
def _licence_for(fn: Any) -> Any:
"""Build the callable dl.py gives the DRM object, filtered to what the service declares."""
return lambda **kw: fn(**declared_kwargs(fn, {**kw, "title": "t", "track": "tr"}))
def test_a_session_aware_service_gets_the_open_session_id() -> None:
seen: dict[str, Any] = {}
def get_widevine_license(*, challenge: bytes, title: str, track: str, session_id: Any = None) -> bytes:
seen.update(challenge=challenge, title=title, track=track, session_id=session_id)
return b"license"
drm = _drm()
cdm = FakeCdm()
drm.get_content_keys(cdm=cdm, certificate=lambda **_: None, licence=_licence_for(get_widevine_license))
assert seen["session_id"] == "session-1"
assert "pssh" not in seen
assert drm.content_keys == {KID: KEY}
def test_a_plain_service_does_not_get_pssh_or_session_id() -> None:
seen: dict[str, Any] = {}
def get_widevine_license(*, challenge: bytes, title: str, track: str) -> bytes:
seen.update(challenge=challenge, title=title, track=track)
return b"license"
drm = _drm()
drm.get_content_keys(cdm=FakeCdm(), certificate=lambda **_: None, licence=_licence_for(get_widevine_license))
assert set(seen) == {"challenge", "title", "track"}
assert drm.content_keys == {KID: KEY}
def test_an_empty_challenge_raises_instead_of_licensing() -> None:
def get_widevine_license(*, challenge: bytes, title: str, track: str) -> bytes:
raise AssertionError("the licence request must not go out for an empty challenge")
drm = _drm()
with pytest.raises(Widevine.Exceptions.EmptyLicense):
drm.get_content_keys(
cdm=FakeCdm(challenge=b""), certificate=lambda **_: None, licence=_licence_for(get_widevine_license)
)