mirror of
https://github.com/unshackle-dl/unshackle.git
synced 2026-11-14 13:05:09 +00:00
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:
@@ -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)
|
||||
)
|
||||
Reference in New Issue
Block a user