package core // Allowed decides whether caller may perform op on repo, given the caller's // resolved per-repo ACL grant (aclMode, nil if the caller has no ACL entry). // It implements the dolt.sr.ht access matrix exactly: // // Caller \ Visibility | PUBLIC | UNLISTED | PRIVATE // --------------------|---------------|---------------|---------------------- // anon / any user | browse, clone | browse, clone | — // ACL RO | browse, clone | browse, clone | browse, clone // ACL RW | + push | + push | browse, clone, push // owner | all | all | all // // Rules: // - The owner may do anything (subject to the suspension rule below). // - An ACL RO grant permits browse and clone on any visibility, including // PRIVATE. // - An ACL RW grant additionally permits push (but never admin). // - With no ACL and non-owner: PUBLIC and UNLISTED permit browse and clone // for everyone (including anonymous); PRIVATE permits nothing. // - A suspended caller may read (browse, clone) but never push or admin, // regardless of ownership or ACL. // - Any operation outside the four known Ops is denied. // // Access control fails closed: a nil repo denies everything. func Allowed(caller *Caller, repo *Repo, aclMode *AccessMode, op Op) bool { switch op { case OpBrowse, OpCloneRead, OpPush, OpAdmin: // known op default: return false } if repo == nil { return false } suspended := caller != nil && caller.Suspended isWrite := op == OpPush || op == OpAdmin if suspended && isWrite { return false } // Owner may do anything (write already gated by the suspension check). if caller != nil && caller.UserID == repo.OwnerID { return true } // Explicit ACL grants (only meaningful for authenticated callers). if caller != nil && aclMode != nil { switch *aclMode { case AccessRO: return op == OpBrowse || op == OpCloneRead case AccessRW: // RO reads plus push; never admin. return op == OpBrowse || op == OpCloneRead || op == OpPush } } // No ownership, no ACL: fall back to visibility. Only reads are ever // granted this way. switch repo.Visibility { case VisibilityPublic, VisibilityUnlisted: return op == OpBrowse || op == OpCloneRead default: // VisibilityPrivate and anything unrecognized return false } } // NotFoundForPrivate reports whether a denied request for repo should be // surfaced as "not found" (404) rather than "forbidden" (403), so that the // existence of PRIVATE repositories is not leaked. Consult it only after // Allowed has already returned false for the request. // // A PRIVATE repo that the caller cannot even browse is reported as not found; // PUBLIC/UNLISTED repos, and PRIVATE repos the caller may browse, reveal their // existence normally (a plain 403). A nil repo is always "not found". func NotFoundForPrivate(caller *Caller, repo *Repo, aclMode *AccessMode) bool { if repo == nil { return true } if repo.Visibility != VisibilityPrivate { return false } return !Allowed(caller, repo, aclMode, OpBrowse) }