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