mirror of
https://github.com/CyberMind-FR/secubox-deb.git
synced 2026-07-29 18:36:55 +00:00
fix(annuaire): standing-mode assist request now requires an active assist grant
can_open() accepted mode=="standing" unconditionally, letting a center open a session on the strength of the request alone. Per spec, standing mode is a persistent authorization and must be backed by a real, self-issued delegation: can_open() now additionally requires an active, SELF-issued (issued_by == self_did) capability="assist" Grant naming that request's center_did, via grants.active_grants() — the sovereignty filter is already enforced there. Missing grant -> (False, "no-standing-grant"). Per-incident mode is unchanged: the box-authored request remains its own authority. ref sous-projet 2 Co-Authored-By: Gerald KERMA <devel@cybermind.fr>
This commit is contained in:
parent
d08820d675
commit
6dfc5a5ff0
|
|
@ -14,7 +14,7 @@ from __future__ import annotations
|
|||
|
||||
from typing import Any, List, Mapping, Optional
|
||||
|
||||
from .grants import _op, _payload # dict/LogEntry-tolerant accessors
|
||||
from .grants import _op, _payload, active_grants # dict/LogEntry-tolerant accessors
|
||||
from .model import Op
|
||||
|
||||
|
||||
|
|
@ -90,8 +90,16 @@ def pending_requests(entries: List[Mapping[str, Any]], self_did: str) -> List[di
|
|||
def can_open(entries: List[Mapping[str, Any]], req_id: str,
|
||||
self_did: str, now_ts: str) -> tuple[bool, str]:
|
||||
"""Whether the box may open a session for req_id: request exists, was
|
||||
accepted, is authorized for this box (self-authored, or standing mode),
|
||||
and NO session is currently active (single-session invariant)."""
|
||||
accepted, is authorized for this box (self-authored, or standing mode
|
||||
BACKED BY an active capability="assist" Grant), and NO session is
|
||||
currently active (single-session invariant).
|
||||
|
||||
Per-incident mode: the box-authored ASSIST_REQUEST IS the authority —
|
||||
no grant needed. Standing mode: the request alone is NOT enough; the
|
||||
center must additionally hold an active, SELF-issued (issued_by ==
|
||||
self_did — see active_grants()'s sovereignty filter) capability="assist"
|
||||
grant naming that same center_did, or the open is refused.
|
||||
"""
|
||||
reqs = {p.get("req_id"): p for p in _by(entries, Op.ASSIST_REQUEST)}
|
||||
if req_id not in reqs:
|
||||
return False, "no-such-request"
|
||||
|
|
@ -101,6 +109,14 @@ def can_open(entries: List[Mapping[str, Any]], req_id: str,
|
|||
req = reqs[req_id]
|
||||
if req.get("issued_by") != self_did and req.get("mode") != "standing":
|
||||
return False, "not-authorized"
|
||||
if req.get("mode") == "standing":
|
||||
grants = active_grants(entries, self_did)
|
||||
has_assist_grant = any(
|
||||
g.get("capability") == "assist" and g.get("center_did") == req.get("center_did")
|
||||
for g in grants.values()
|
||||
)
|
||||
if not has_assist_grant:
|
||||
return False, "no-standing-grant"
|
||||
if active_session(entries, self_did, now_ts) is not None:
|
||||
return False, "session-already-active"
|
||||
return True, "ok"
|
||||
|
|
|
|||
|
|
@ -104,3 +104,63 @@ def test_can_open_allows_self_authored_accepted_request():
|
|||
]
|
||||
ok, reason = assist.can_open(entries, "r-self", BOX, now_ts="2026-07-25T12:00:00Z")
|
||||
assert (ok, reason) == (True, "ok")
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Standing mode REQUIRES an active capability="assist" Grant for the center
|
||||
# (spec: per-incident's own box-authored request IS the authority; standing
|
||||
# is NOT — it must be backed by a real, self-issued delegation).
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def _grant_issue(gid, center_did, capability, scope, issued_by):
|
||||
return {"op": Op.GRANT_ISSUE.value, "payload": {
|
||||
"grant_id": gid, "center_did": center_did, "capability": capability,
|
||||
"scope": scope, "layer": "baseline", "issued_by": issued_by}}
|
||||
|
||||
|
||||
def test_can_open_standing_without_grant_is_refused():
|
||||
entries = [
|
||||
e(Op.ASSIST_REQUEST, req_id="r-standing", center_did=CENTER,
|
||||
mode="standing", scope="dns", issued_by=CENTER),
|
||||
e(Op.ASSIST_ACCEPT, req_id="r-standing", issued_by=CENTER),
|
||||
]
|
||||
ok, reason = assist.can_open(entries, "r-standing", BOX, now_ts="2026-07-25T12:00:00Z")
|
||||
assert (ok, reason) == (False, "no-standing-grant")
|
||||
|
||||
|
||||
def test_can_open_standing_with_active_assist_grant_is_allowed():
|
||||
entries = [
|
||||
e(Op.ASSIST_REQUEST, req_id="r-standing", center_did=CENTER,
|
||||
mode="standing", scope="dns", issued_by=CENTER),
|
||||
e(Op.ASSIST_ACCEPT, req_id="r-standing", issued_by=CENTER),
|
||||
_grant_issue("g1", CENTER, "assist", "dns", BOX),
|
||||
]
|
||||
ok, reason = assist.can_open(entries, "r-standing", BOX, now_ts="2026-07-25T12:00:00Z")
|
||||
assert (ok, reason) == (True, "ok")
|
||||
|
||||
|
||||
def test_can_open_standing_ignores_non_assist_capability_grant():
|
||||
# A "config" capability grant for the same center must not satisfy the
|
||||
# standing assist-grant requirement — capability must be exactly "assist".
|
||||
entries = [
|
||||
e(Op.ASSIST_REQUEST, req_id="r-standing", center_did=CENTER,
|
||||
mode="standing", scope="dns", issued_by=CENTER),
|
||||
e(Op.ASSIST_ACCEPT, req_id="r-standing", issued_by=CENTER),
|
||||
_grant_issue("g1", CENTER, "config", "dns", BOX),
|
||||
]
|
||||
ok, reason = assist.can_open(entries, "r-standing", BOX, now_ts="2026-07-25T12:00:00Z")
|
||||
assert (ok, reason) == (False, "no-standing-grant")
|
||||
|
||||
|
||||
def test_can_open_standing_ignores_foreign_issued_grant():
|
||||
# A capability="assist" grant that THIS box never issued (e.g. a mesh
|
||||
# peer's federated grant naming itself) must not count — active_grants'
|
||||
# sovereignty filter already scopes this to issued_by == self_did.
|
||||
entries = [
|
||||
e(Op.ASSIST_REQUEST, req_id="r-standing", center_did=CENTER,
|
||||
mode="standing", scope="dns", issued_by=CENTER),
|
||||
e(Op.ASSIST_ACCEPT, req_id="r-standing", issued_by=CENTER),
|
||||
_grant_issue("g1", CENTER, "assist", "dns", OTHER),
|
||||
]
|
||||
ok, reason = assist.can_open(entries, "r-standing", BOX, now_ts="2026-07-25T12:00:00Z")
|
||||
assert (ok, reason) == (False, "no-standing-grant")
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user