systemprompt_security/authz/
types.rs1use std::fmt;
12use std::str::FromStr;
13
14use serde::{Deserialize, Serialize};
15use systemprompt_identifiers::{RuleId, TraceId, UserId};
16
17use super::error::AuthzError;
18
19#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, sqlx::Type)]
20#[sqlx(type_name = "TEXT", rename_all = "lowercase")]
21#[serde(rename_all = "lowercase")]
22pub enum RuleType {
23 User,
24 Role,
25 Department,
26}
27
28impl fmt::Display for RuleType {
29 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
30 f.write_str(match *self {
31 Self::User => "user",
32 Self::Role => "role",
33 Self::Department => "department",
34 })
35 }
36}
37
38impl FromStr for RuleType {
39 type Err = AuthzError;
40
41 fn from_str(s: &str) -> Result<Self, Self::Err> {
42 match s {
43 "user" => Ok(Self::User),
44 "role" => Ok(Self::Role),
45 "department" => Ok(Self::Department),
46 other => Err(AuthzError::InvalidRuleType(other.to_owned())),
47 }
48 }
49}
50
51#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, sqlx::Type)]
52#[sqlx(type_name = "TEXT", rename_all = "lowercase")]
53#[serde(rename_all = "lowercase")]
54pub enum Access {
55 Allow,
56 Deny,
57}
58
59impl fmt::Display for Access {
60 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
61 f.write_str(match *self {
62 Self::Allow => "allow",
63 Self::Deny => "deny",
64 })
65 }
66}
67
68impl FromStr for Access {
69 type Err = AuthzError;
70
71 fn from_str(s: &str) -> Result<Self, Self::Err> {
72 match s {
73 "allow" => Ok(Self::Allow),
74 "deny" => Ok(Self::Deny),
75 other => Err(AuthzError::InvalidAccess(other.to_owned())),
76 }
77 }
78}
79
80#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
81#[serde(rename_all = "snake_case")]
82pub enum EntityKind {
83 GatewayRoute,
84 McpServer,
85 Plugin,
86 Agent,
87 Marketplace,
88}
89
90impl EntityKind {
91 pub const fn as_str(self) -> &'static str {
92 match self {
93 Self::GatewayRoute => "gateway_route",
94 Self::McpServer => "mcp_server",
95 Self::Plugin => "plugin",
96 Self::Agent => "agent",
97 Self::Marketplace => "marketplace",
98 }
99 }
100}
101
102impl FromStr for EntityKind {
103 type Err = AuthzError;
104
105 fn from_str(s: &str) -> Result<Self, Self::Err> {
106 match s {
107 "gateway_route" => Ok(Self::GatewayRoute),
108 "mcp_server" => Ok(Self::McpServer),
109 "plugin" => Ok(Self::Plugin),
110 "agent" => Ok(Self::Agent),
111 "marketplace" => Ok(Self::Marketplace),
112 other => Err(AuthzError::Validation(format!(
113 "unknown entity_type: {other}"
114 ))),
115 }
116 }
117}
118
119impl fmt::Display for EntityKind {
120 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
121 f.write_str(self.as_str())
122 }
123}
124
125#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, sqlx::FromRow)]
126pub struct AccessRule {
127 pub id: RuleId,
128 pub rule_type: RuleType,
129 pub rule_value: String,
130 pub access: Access,
131 pub default_included: bool,
132 #[serde(default, skip_serializing_if = "Option::is_none")]
133 pub justification: Option<String>,
134}
135
136#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
137#[serde(tag = "decision", rename_all = "lowercase")]
138pub enum Decision {
139 Allow,
140 Deny {
141 reason: String,
142 #[serde(default, skip_serializing_if = "Option::is_none")]
143 justification: Option<String>,
144 },
145}
146
147#[derive(Debug, Clone, Serialize, Deserialize)]
148pub struct AuthzRequest {
149 pub entity_type: EntityKind,
150 pub entity_id: String,
151 pub user_id: UserId,
152 #[serde(default)]
153 pub roles: Vec<String>,
154 #[serde(default)]
155 pub department: String,
156 pub trace_id: TraceId,
157 #[serde(default)]
158 pub context: serde_json::Value,
161}
162
163#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
164#[serde(tag = "decision", rename_all = "lowercase")]
165pub enum AuthzDecision {
166 Allow,
167 Deny { reason: String, policy: String },
168}