98 lines
3.6 KiB
Python
98 lines
3.6 KiB
Python
import sqlite3
|
|
|
|
import pytest
|
|
|
|
from src.passkey_store import PasskeyStore
|
|
from src.session_store import SessionStore, SessionStoreError
|
|
|
|
|
|
def test_global_access_revocation_rolls_back_every_credential_and_session_change(tmp_path):
|
|
database = tmp_path / "sessions.sqlite3"
|
|
sessions = SessionStore(database, clock=lambda: 1_000.0)
|
|
sessions.activate(
|
|
"phone-session",
|
|
2_000,
|
|
management_id="phone-management-id",
|
|
device_label="Phone",
|
|
)
|
|
sessions.mint_step_up(
|
|
"phone-session",
|
|
action="revoke_all_sessions",
|
|
target="all",
|
|
ttl_seconds=90,
|
|
)
|
|
passkeys = PasskeyStore(database, clock=lambda: 1_000.0)
|
|
passkeys.register(
|
|
credential_id=b"phone-credential",
|
|
public_key=b"phone-public-key",
|
|
sign_count=0,
|
|
device_label="Phone",
|
|
management_id="phone-management-id",
|
|
)
|
|
passkeys.issue_challenge(
|
|
b"pending-challenge",
|
|
session_id="phone-session",
|
|
purpose="authentication",
|
|
action="sign_in",
|
|
target="dashboard",
|
|
)
|
|
with sqlite3.connect(database) as connection:
|
|
connection.execute(
|
|
"CREATE TRIGGER block_session_delete BEFORE DELETE ON active_sessions "
|
|
"BEGIN SELECT RAISE(ABORT, 'injected failure'); END"
|
|
)
|
|
|
|
with pytest.raises(SessionStoreError):
|
|
passkeys.revoke_all_access()
|
|
|
|
with sqlite3.connect(database) as connection:
|
|
assert connection.execute("SELECT COUNT(*) FROM passkey_credentials").fetchone() == (1,)
|
|
assert connection.execute("SELECT COUNT(*) FROM passkey_challenges").fetchone() == (1,)
|
|
assert connection.execute("SELECT COUNT(*) FROM step_up_grants").fetchone() == (1,)
|
|
assert connection.execute("SELECT COUNT(*) FROM active_sessions").fetchone() == (1,)
|
|
|
|
|
|
def test_device_access_revocation_rolls_back_target_and_preserves_other_devices(tmp_path):
|
|
database = tmp_path / "sessions.sqlite3"
|
|
sessions = SessionStore(database, clock=lambda: 1_000.0)
|
|
passkeys = PasskeyStore(database, clock=lambda: 1_000.0)
|
|
for label in ("Phone", "Laptop"):
|
|
slug = label.lower()
|
|
sessions.activate(
|
|
f"{slug}-session",
|
|
2_000,
|
|
management_id=f"{slug}-management-id",
|
|
device_label=label,
|
|
)
|
|
sessions.mint_step_up(
|
|
f"{slug}-session",
|
|
action="close_issue",
|
|
target="stackchain/dashboard#1",
|
|
ttl_seconds=90,
|
|
)
|
|
passkeys.register(
|
|
credential_id=f"{slug}-credential".encode(),
|
|
public_key=f"{slug}-public-key".encode(),
|
|
sign_count=0,
|
|
device_label=label,
|
|
management_id=f"{slug}-management-id",
|
|
)
|
|
with sqlite3.connect(database) as connection:
|
|
connection.execute(
|
|
"CREATE TRIGGER block_phone_delete BEFORE DELETE ON active_sessions "
|
|
"WHEN OLD.management_id = 'phone-management-id' "
|
|
"BEGIN SELECT RAISE(ABORT, 'injected failure'); END"
|
|
)
|
|
|
|
with pytest.raises(SessionStoreError):
|
|
passkeys.revoke_device_access("phone-management-id")
|
|
|
|
with sqlite3.connect(database) as connection:
|
|
assert connection.execute(
|
|
"SELECT management_id FROM active_sessions ORDER BY management_id"
|
|
).fetchall() == [("laptop-management-id",), ("phone-management-id",)]
|
|
assert connection.execute(
|
|
"SELECT management_id FROM passkey_credentials ORDER BY management_id"
|
|
).fetchall() == [("laptop-management-id",), ("phone-management-id",)]
|
|
assert connection.execute("SELECT COUNT(*) FROM step_up_grants").fetchone() == (2,)
|