mirror of
https://github.com/turnstonelabs/turnstone.git
synced 2026-08-12 23:12:23 -06:00
fix(models): review feedback — separator vocabulary, constraints stub, import style
The scopes sanitize now shares the registry guard's separator vocabulary: tab/newline/CR read as spaces, and every other C0 byte — including the U+001C–U+001F block str.split() would silently promote to separators — strips like the control it is, so a control byte inside a token can never split it into two valid-looking scopes (pinned alongside the registry's refusal). The livepass auth-constraints stub serves the new app_identity_auth_modes field so the pass exercises the served-data path for the model list's auth badge, and the session-module import in the mint tests drops to the string-path monkeypatch spelling (single-style imports).
This commit is contained in:
@@ -501,6 +501,7 @@ CONSOLE_TEMPLATE = """<!doctype html>
|
||||
auth_grant_profile: "entra",
|
||||
dynamic_auth_modes: ["entra_app", "entra_obo", "rfc8693_obo"],
|
||||
scopes_auth_modes: ["rfc8693_obo"],
|
||||
app_identity_auth_modes: ["entra_app"],
|
||||
auth_mode_profiles: {
|
||||
entra_app: "entra", entra_obo: "entra",
|
||||
rfc8693_obo: "rfc8693",
|
||||
|
||||
@@ -1640,9 +1640,7 @@ class TestModelOboToken:
|
||||
"""A delegated mode with no registered grant-profile pairing cannot
|
||||
pin a leg, so the dispatch refuses loudly before the mint bridge —
|
||||
minting with leg=None would run the pre-dedicated-mode overload."""
|
||||
import turnstone.core.session as session_module
|
||||
|
||||
monkeypatch.setattr(session_module, "MODEL_AUTH_MODE_PROFILES", {})
|
||||
monkeypatch.setattr("turnstone.core.session.MODEL_AUTH_MODE_PROFILES", {})
|
||||
reg = _registry_with(self._obo_cfg())
|
||||
sess = _fake_session(registry=reg, user_id=USER, mint_token="never")
|
||||
with pytest.raises(BackendAuthUnavailableError, match="grant-profile pairing"):
|
||||
|
||||
@@ -2982,14 +2982,17 @@ def test_obo_scopes_normalizers_agree_across_modules() -> None:
|
||||
assert mr_module.sanitize_backend_auth_scopes(dirty) == "aud-gwopenid"
|
||||
# The C0 separator block counts as Python whitespace, so a bare
|
||||
# str.split() would swallow it before the guard could refuse; the
|
||||
# registry must refuse it like every other control byte, while the
|
||||
# sanctioned separators (tab/newline/CR, blessed in the corpus above)
|
||||
# keep collapsing.
|
||||
# registry must refuse it like every other control byte, and the
|
||||
# SANITIZE must strip it like every other control — never promote it to
|
||||
# a separator that splits one token into two valid-looking scopes —
|
||||
# while the sanctioned separators (tab/newline/CR, blessed in the
|
||||
# corpus above) keep collapsing.
|
||||
for sep_byte in (chr(0x1C), chr(0x1D), chr(0x1E), chr(0x1F)):
|
||||
with pytest.raises(mr_module.ModelAuthConfigError):
|
||||
mr_module._normalize_auth_mode(
|
||||
"gw", "rfc8693_obo", "api://gw", f"aud-gw{sep_byte}openid"
|
||||
)
|
||||
assert mr_module.sanitize_backend_auth_scopes(f"aud-gw{sep_byte}openid") == "aud-gwopenid"
|
||||
|
||||
|
||||
def test_control_bearing_alias_refuses_to_load() -> None:
|
||||
|
||||
@@ -269,18 +269,22 @@ def strip_control_characters(value: str) -> str:
|
||||
def sanitize_backend_auth_scopes(value: Any) -> str:
|
||||
"""The ONE spelling of the backend-auth scopes sanitize.
|
||||
|
||||
Collapse whitespace runs to single spaces, strip the remaining C0/DEL
|
||||
control characters, then re-collapse the runs the stripping can reopen
|
||||
(``"a \\x01 b"`` becomes ``"a b"`` becomes ``"a b"``). Collapse-first
|
||||
ordering is load-bearing: stripping a tab-separated list first would
|
||||
CONCATENATE the scopes the tab separates. No length cap and no refusal —
|
||||
policy (caps, and refuse-vs-strip on garbage) stays with each consuming
|
||||
The sanctioned separators — tab, newline, CR — read as spaces FIRST
|
||||
(stripping a tab-separated list outright would CONCATENATE the scopes
|
||||
the tab separates), then every remaining C0/DEL control character is
|
||||
stripped, and finally whitespace runs collapse to single spaces. The
|
||||
separator vocabulary deliberately matches the registry guard's: the C0
|
||||
separator block (U+001C–U+001F) is a CONTROL here, never a separator —
|
||||
a bare ``str.split()`` would silently promote it to one — so a control
|
||||
byte inside a token strips-and-joins rather than splitting the token
|
||||
into two valid-looking scopes. No length cap and no refusal — policy
|
||||
(caps, and refuse-vs-strip on garbage) stays with each consuming
|
||||
layer; this function only fixes the shared spelling those policies
|
||||
measure, so the console store, the registry load, and the mint request
|
||||
can never disagree on what a scopes value *is*.
|
||||
"""
|
||||
collapsed = " ".join(str(value or "").split())
|
||||
return " ".join(strip_control_characters(collapsed).split())
|
||||
blessed = re.sub(r"[\t\n\r]", " ", str(value or ""))
|
||||
return " ".join(strip_control_characters(blessed).split())
|
||||
|
||||
|
||||
def _check_auth_text(alias: str, field: str, value: str) -> None:
|
||||
|
||||
Reference in New Issue
Block a user