mirror of
https://github.com/ModernRelay/omnigraph.git
synced 2026-06-12 01:45:14 +02:00
Add InvokeQuery Cedar action (coarse, graph-scoped)
A per-graph, branch-scoped action that gates invoking a server-side stored query by name. Coarse for now: an `invoke_query` allow rule permits any stored query on the graph; a future, additive refinement adds an optional per-query-name scope without changing rules written against the coarse action. Enforcement is at the HTTP boundary; the engine `_as` writers still enforce read/change per the query body, so a stored mutation is double-gated (invoke_query to reach the tool, change for the write). No call site yet — the invocation handler wires it in a later change (same pattern as Admin/GraphList added ahead of consumers). - variant + as_str/resource_kind(Graph)/FromStr/uses_branch_scope - Cedar schema: invoke_query appliesTo Graph - tests: per-graph allow/deny, branch-scope accepted Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
3419512846
commit
6a16b3c6ac
1 changed files with 103 additions and 2 deletions
|
|
@ -56,6 +56,18 @@ pub enum PolicyAction {
|
|||
/// from v0.6.0; operators add and remove graphs by editing
|
||||
/// `omnigraph.yaml` and restarting.
|
||||
GraphList,
|
||||
/// Gates invoking a server-side stored query by name. Per-graph and
|
||||
/// branch-scoped, like `Read`/`Change`. In this release it is
|
||||
/// **coarse**: an `invoke_query` allow rule permits *any* stored
|
||||
/// query on the graph (there is no per-query dimension yet). A
|
||||
/// future, additive refinement adds an optional query-name scope to
|
||||
/// rules without changing rules written against the coarse action.
|
||||
///
|
||||
/// This gate sits at the HTTP boundary. The underlying engine `_as`
|
||||
/// writers still enforce `Read`/`Change` per the stored query's body,
|
||||
/// so a stored *mutation* is double-gated: `invoke_query` to reach
|
||||
/// the tool, plus `change` for the write itself.
|
||||
InvokeQuery,
|
||||
}
|
||||
|
||||
impl PolicyAction {
|
||||
|
|
@ -70,11 +82,15 @@ impl PolicyAction {
|
|||
Self::BranchMerge => "branch_merge",
|
||||
Self::Admin => "admin",
|
||||
Self::GraphList => "graph_list",
|
||||
Self::InvokeQuery => "invoke_query",
|
||||
}
|
||||
}
|
||||
|
||||
fn uses_branch_scope(self) -> bool {
|
||||
matches!(self, Self::Read | Self::Export | Self::Change)
|
||||
matches!(
|
||||
self,
|
||||
Self::Read | Self::Export | Self::Change | Self::InvokeQuery
|
||||
)
|
||||
}
|
||||
|
||||
fn uses_target_branch_scope(self) -> bool {
|
||||
|
|
@ -99,7 +115,8 @@ impl PolicyAction {
|
|||
| Self::BranchCreate
|
||||
| Self::BranchDelete
|
||||
| Self::BranchMerge
|
||||
| Self::Admin => PolicyResourceKind::Graph,
|
||||
| Self::Admin
|
||||
| Self::InvokeQuery => PolicyResourceKind::Graph,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -155,6 +172,7 @@ impl FromStr for PolicyAction {
|
|||
"branch_merge" => Ok(Self::BranchMerge),
|
||||
"admin" => Ok(Self::Admin),
|
||||
"graph_list" => Ok(Self::GraphList),
|
||||
"invoke_query" => Ok(Self::InvokeQuery),
|
||||
other => bail!("unknown policy action '{other}'"),
|
||||
}
|
||||
}
|
||||
|
|
@ -806,6 +824,7 @@ namespace Omnigraph {
|
|||
action "branch_delete" appliesTo { principal: Actor, resource: Graph, context: RequestContext };
|
||||
action "branch_merge" appliesTo { principal: Actor, resource: Graph, context: RequestContext };
|
||||
action "admin" appliesTo { principal: Actor, resource: Graph, context: RequestContext };
|
||||
action "invoke_query" appliesTo { principal: Actor, resource: Graph, context: RequestContext };
|
||||
|
||||
action "graph_list" appliesTo { principal: Actor, resource: Server, context: RequestContext };
|
||||
}
|
||||
|
|
@ -1264,6 +1283,88 @@ rules:
|
|||
assert!(!deny.allowed);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn invoke_query_authorizes_per_graph() {
|
||||
let policy: PolicyConfig = serde_yaml::from_str(
|
||||
r#"
|
||||
version: 1
|
||||
groups:
|
||||
team: [act-alice]
|
||||
others: [act-bruno]
|
||||
rules:
|
||||
- id: team-invoke-queries
|
||||
allow:
|
||||
actors: { group: team }
|
||||
actions: [invoke_query]
|
||||
"#,
|
||||
)
|
||||
.unwrap();
|
||||
let engine = PolicyCompiler::compile(&policy, "graph").unwrap();
|
||||
|
||||
let allow = engine
|
||||
.authorize(
|
||||
"act-alice",
|
||||
&PolicyRequest {
|
||||
action: PolicyAction::InvokeQuery,
|
||||
branch: Some("main".to_string()),
|
||||
target_branch: None,
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
assert!(allow.allowed);
|
||||
assert_eq!(
|
||||
allow.matched_rule_id.as_deref(),
|
||||
Some("team-invoke-queries")
|
||||
);
|
||||
|
||||
// Actor outside the group → deny.
|
||||
let deny = engine
|
||||
.authorize(
|
||||
"act-bruno",
|
||||
&PolicyRequest {
|
||||
action: PolicyAction::InvokeQuery,
|
||||
branch: Some("main".to_string()),
|
||||
target_branch: None,
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
assert!(!deny.allowed);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn invoke_query_is_branch_scoped() {
|
||||
// Unlike server-scoped actions, invoke_query accepts a
|
||||
// `branch_scope` qualifier — it runs against a branch like
|
||||
// read/change — so validation passes and the rule authorizes.
|
||||
let policy: PolicyConfig = serde_yaml::from_str(
|
||||
r#"
|
||||
version: 1
|
||||
groups:
|
||||
team: [act-alice]
|
||||
rules:
|
||||
- id: team-invoke-any-branch
|
||||
allow:
|
||||
actors: { group: team }
|
||||
actions: [invoke_query]
|
||||
branch_scope: any
|
||||
"#,
|
||||
)
|
||||
.unwrap();
|
||||
policy.validate().unwrap();
|
||||
let engine = PolicyCompiler::compile(&policy, "graph").unwrap();
|
||||
let allow = engine
|
||||
.authorize(
|
||||
"act-alice",
|
||||
&PolicyRequest {
|
||||
action: PolicyAction::InvokeQuery,
|
||||
branch: Some("review".to_string()),
|
||||
target_branch: None,
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
assert!(allow.allowed);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn server_scoped_rule_cannot_use_branch_scope() {
|
||||
let policy: PolicyConfig = serde_yaml::from_str(
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue