diff --git a/crates/tranquil-pds/src/delegation/scopes.rs b/crates/tranquil-pds/src/delegation/scopes.rs index 1075502..a72e061 100644 --- a/crates/tranquil-pds/src/delegation/scopes.rs +++ b/crates/tranquil-pds/src/delegation/scopes.rs @@ -14,8 +14,10 @@ pub struct ScopePreset { pub scopes: &'static str, } -pub const OWNER_FULL_SCOPES: &str = - "atproto repo:* blob:*/* rpc:* identity:* account:*?action=manage"; +pub const OWNER_FULL_SCOPES: &str = concat!( + "atproto repo:* blob:*/* rpc:* identity:* account:*?action=manage ", + "transition:generic transition:chat.bsky transition:email" +); pub const ADMIN_FULL_SCOPES: &str = "atproto repo:* blob:*/* rpc:* account:*?action=manage"; @@ -341,6 +343,9 @@ mod tests { ("rpc", "rpc:app.bsky.actor.getProfile?aud=*"), ("account", "account:email?action=manage"), ("identity", "identity:handle"), + ("transition:generic", "transition:generic"), + ("transition:chat.bsky", "transition:chat.bsky"), + ("transition:email", "transition:email"), ]; /// The taxonomy label a scope type must be represented by, or `None` for scope types @@ -352,10 +357,10 @@ mod tests { ParsedScope::Rpc(_) => Some("rpc"), ParsedScope::Account(_) => Some("account"), ParsedScope::Identity(_) => Some("identity"), + ParsedScope::TransitionGeneric => Some("transition:generic"), + ParsedScope::TransitionChat => Some("transition:chat.bsky"), + ParsedScope::TransitionEmail => Some("transition:email"), ParsedScope::Atproto => None, - ParsedScope::TransitionGeneric - | ParsedScope::TransitionChat - | ParsedScope::TransitionEmail => None, ParsedScope::Include(_) => None, ParsedScope::Unknown(_) => None, } @@ -437,3 +442,27 @@ mod tests { ); } } + +#[cfg(test)] +mod scratch_transition_probe { + use super::*; + #[test] + fn probe() { + let g = "atproto repo:* blob:*/* rpc:* identity:* account:*?action=manage transition:generic transition:chat transition:email"; + println!( + "validate mixed grant: {:?}", + ValidatedDelegationScope::new(g).is_ok() + ); + for s in ["transition:generic", "transition:chat", "transition:email"] { + println!(" {s} -> {:?}", grant_coverage(g, s)); + println!( + " {s} under CURRENT owner -> {:?}", + grant_coverage(OWNER_FULL_SCOPES, s) + ); + } + println!( + "intersect(transition:generic, mixed grant) = {:?}", + intersect_scopes("atproto transition:generic", g) + ); + } +} diff --git a/crates/tranquil-pds/src/state.rs b/crates/tranquil-pds/src/state.rs index 6aaefd7..40f5b46 100644 --- a/crates/tranquil-pds/src/state.rs +++ b/crates/tranquil-pds/src/state.rs @@ -534,7 +534,13 @@ fn migrate_delegation_preset_scopes(metastore: &tranquil_store::metastore::Metas const V2_LEGACY_EDITOR: &str = "atproto repo:*?action=create repo:*?action=update repo:*?action=delete blob:*/*"; - let passes: [(&str, &[(&str, &str)]); 2] = [ + // v3 adds the `transition:` scopes to the owner preset + // Without them an owner-level delegation withholds all transition scopes + const V3_MARKER: &str = "migration:delegation_preset_scopes_v3"; + const V3_LEGACY_OWNER: &str = + "atproto repo:* blob:*/* rpc:* identity:* account:*?action=manage"; + + let passes: [(&str, &[(&str, &str)]); 3] = [ ( V1_MARKER, &[ @@ -550,6 +556,10 @@ fn migrate_delegation_preset_scopes(metastore: &tranquil_store::metastore::Metas (V2_LEGACY_EDITOR, crate::delegation::EDITOR_FULL_SCOPES), ], ), + ( + V3_MARKER, + &[(V3_LEGACY_OWNER, crate::delegation::OWNER_FULL_SCOPES)], + ), ]; let infra = metastore.infra_ops();