systemprompt_security/authz/types/
request.rs1use std::borrow::Cow;
7use std::collections::BTreeMap;
8
9use serde::{Deserialize, Serialize};
10use systemprompt_identifiers::{
11 Actor, ContextId, McpToolName, ModelId, SessionId, TaskId, TraceId, UserId,
12};
13
14use super::decision::DenyReason;
15use super::entity_ref::EntityRef;
16
17#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
32pub struct AuthzContext {
33 pub kind: Cow<'static, str>,
34 #[serde(default, skip_serializing_if = "serde_json::Value::is_null")]
35 pub payload: serde_json::Value,
36}
37
38impl Default for AuthzContext {
39 fn default() -> Self {
40 Self::none()
41 }
42}
43
44impl AuthzContext {
45 pub const NONE_KIND: &'static str = "none";
46 pub const GATEWAY_INVOCATION_KIND: &'static str = "gateway.invocation";
47 pub const MCP_TOOL_CALL_KIND: &'static str = "mcp.tool_call";
48
49 #[must_use]
50 pub const fn none() -> Self {
51 Self {
52 kind: Cow::Borrowed(Self::NONE_KIND),
53 payload: serde_json::Value::Null,
54 }
55 }
56
57 #[must_use]
58 pub fn gateway_invocation(model: &ModelId) -> Self {
59 Self {
60 kind: Cow::Borrowed(Self::GATEWAY_INVOCATION_KIND),
61 payload: serde_json::json!({ "model": model.as_str() }),
62 }
63 }
64
65 #[must_use]
66 pub fn mcp_tool_call(tool: &McpToolName) -> Self {
67 Self {
68 kind: Cow::Borrowed(Self::MCP_TOOL_CALL_KIND),
69 payload: serde_json::json!({ "tool": tool.as_str() }),
70 }
71 }
72
73 #[must_use]
74 pub fn extension(kind: impl Into<Cow<'static, str>>, payload: serde_json::Value) -> Self {
75 Self {
76 kind: kind.into(),
77 payload,
78 }
79 }
80
81 #[must_use]
82 pub fn gateway_invocation_model(&self) -> Option<ModelId> {
83 if self.kind != Self::GATEWAY_INVOCATION_KIND {
84 return None;
85 }
86 self.payload
87 .get("model")
88 .and_then(|v| v.as_str())
89 .map(ModelId::new)
90 }
91
92 #[must_use]
93 pub fn mcp_tool_call_tool(&self) -> Option<McpToolName> {
94 if self.kind != Self::MCP_TOOL_CALL_KIND {
95 return None;
96 }
97 self.payload
98 .get("tool")
99 .and_then(|v| v.as_str())
100 .map(McpToolName::new)
101 }
102
103 #[must_use]
104 pub fn is_none(&self) -> bool {
105 self.kind == Self::NONE_KIND
106 }
107
108 pub const MARKETPLACE_FLOOR_KEY: &'static str = "marketplace.attribute_floor";
109
110 #[must_use]
111 pub fn with_marketplace_floor(&self, floor: &BTreeMap<String, serde_json::Value>) -> Self {
112 let mut payload = match self.payload.clone() {
113 serde_json::Value::Object(map) => map,
114 _ => serde_json::Map::new(),
115 };
116 let floor_value = floor
117 .iter()
118 .map(|(k, v)| (k.clone(), v.clone()))
119 .collect::<serde_json::Map<String, serde_json::Value>>();
120 payload.insert(
121 Self::MARKETPLACE_FLOOR_KEY.to_owned(),
122 serde_json::Value::Object(floor_value),
123 );
124 Self {
125 kind: self.kind.clone(),
126 payload: serde_json::Value::Object(payload),
127 }
128 }
129
130 #[must_use]
131 pub fn marketplace_floor(&self) -> Option<BTreeMap<String, serde_json::Value>> {
132 let obj = self.payload.get(Self::MARKETPLACE_FLOOR_KEY)?.as_object()?;
133 Some(
134 obj.iter()
135 .map(|(k, v)| (k.clone(), v.clone()))
136 .collect::<BTreeMap<String, serde_json::Value>>(),
137 )
138 }
139}
140
141#[derive(Debug, Clone, Serialize, Deserialize)]
142pub struct AuthzRequest {
143 pub entity: EntityRef,
144 pub user_id: UserId,
145 #[serde(default)]
146 pub roles: Vec<String>,
147 #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
148 pub attributes: BTreeMap<String, serde_json::Value>,
149 pub trace_id: TraceId,
150 #[serde(default, skip_serializing_if = "Option::is_none")]
151 pub session_id: Option<SessionId>,
152 #[serde(default)]
153 pub context: AuthzContext,
154 #[serde(default, skip_serializing_if = "Option::is_none")]
155 pub context_id: Option<ContextId>,
156 #[serde(default, skip_serializing_if = "Option::is_none")]
157 pub task_id: Option<TaskId>,
158 #[serde(default, skip_serializing_if = "Vec::is_empty")]
159 pub act_chain: Vec<Actor>,
160}
161
162#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
163#[serde(tag = "decision", rename_all = "lowercase")]
164pub enum AuthzDecision {
165 Allow,
166 Deny { reason: DenyReason, policy: String },
167}