//! The granular atproto OAuth scope grammar, parsed into a typed //! representation with intersection and containment operations. //! //! `plan/oauth.md` calls this "the single most important thing" this epic //! produces, because `plan/scope-policy.md` has to intersect a request //! against a per-agent ceiling and report exactly which scope was refused — //! impossible to do correctly over bare strings, where `repo:app.bsky.feed.*` //! and `repo:app.bsky.feed.post` are unrelated text but one contains the //! other. Everything here is inert with respect to policy: this module //! parses the wire grammar, tests whether one [`Scope`] contains another, and //! intersects a requested [`ScopeSet`] against a ceiling `ScopeSet`. It does //! not decide what a ceiling *is* — that is `plan/scope-policy.md`, blocked //! on this and on `policy-store`, and it is deliberately not built here. //! //! # The grammar //! //! Space-separated scope strings, one atom each: //! //! - `atproto` — the base scope every atproto OAuth grant carries. //! - `transition:generic`, `transition:chat.bsky`, `transition:email` — the //! legacy, ungranular scopes the spec keeps for compatibility. Each is //! modelled as its own variant rather than desugared into the granular //! scopes it stands in for, because `plan/scope-policy.md` is explicit that //! `transition:*` is the one exception every granular rule has to reckon //! with by name, not the one this module should quietly launder away. //! - `repo:[?action=create&action=update…]` — write access to //! one collection. Several actions repeat the parameter, as the permission //! spec writes every list parameter; no `action` query means all three. //! - `rpc:?aud=` — permission to call one XRPC method at another //! service: the audience is a DID and a service fragment, with its `#` //! written `%23`, or `*` for every audience. //! - `blob:`, or `blob?accept=[&accept=…]` — permission to //! upload a blob of one MIME type or type prefix. The two spellings are //! the same scope: the positional one names a single type, the `accept` //! one names as many as it repeats. How large a blob may be is not in this //! grammar; it is the operator's, in policy and in the server's blob //! limit. //! - `identity:` — control of the account's identity: //! [`IdentityAttr`] names the handle, or `*` for the whole DID document //! and handle. `plan/scope-policy.md` never grants one. //! - `account:[?action=]` — the account's hosting: //! [`AccountAttr`] names what, and [`AccountAction`] how much, `read` when //! the atom names none. Never granted either. //! - `include:[?aud=%23]` — a permission set, published //! as a lexicon. As an atom it admits only itself. [`Include`] reads it, //! and [`Include::grants`] turns the set's published permissions into the //! `repo:` and `rpc:` atoms it grants. //! //! Every value may be percent-encoded, and every positional value may be //! written as a parameter instead: `repo?collection=a&collection=b`, //! `rpc?lxm=*&aud=…`, `identity?attr=handle`, `include?nsid=…`. A list //! parameter names one scope per value, so `repo?collection=a&collection=b` //! reads as `repo:a repo:b`, and that is how it prints. //! //! `mime` patterns may wildcard either half of `type/subtype` (`image/*` //! contains `image/png`). //! //! # Forms the spec does not define //! //! [`ScopeSet::read`] reads an atom written in a form the permission spec //! does not define as well as it can, and says how in a [`ScopeWarning`]. //! One such form is a partial wildcard, `some.prefix.*`, which this grammar //! reads as the prefix and every NSID beneath it at any depth: an operator's //! ceiling uses it without a warning. //! //! An atom this grammar cannot read at all — an unknown kind, or a value its //! kind does not define — is kept as [`Scope::Unknown`], which admits only //! itself, rather than failing the whole string. A request carrying one can //! then be judged on the rest. `didbot-serve` never grants one. #![forbid(unsafe_code)] mod account; mod action; mod error; mod identity; mod include; mod parse; mod pattern; mod read; mod scope; mod set; mod transition; mod warning; #[cfg(test)] mod tests; pub use account::{AccountAction, AccountAttr}; pub use action::{Action, ActionSet}; pub use error::{ ScopeParseError, MAX_ATOM_BYTES, MAX_GRANT_ATOMS, MAX_GRANT_BYTES, MAX_SCOPE_ATOMS, MAX_SCOPE_BYTES, }; pub use identity::IdentityAttr; pub use include::{Include, IncludeError}; pub use pattern::{MimePattern, NsidPattern}; pub use scope::Scope; pub use set::{ScopeRefused, ScopeSet}; pub use transition::Transition; pub use warning::ScopeWarning;