ironflow_store/entities/
api_key_scope.rs1use serde::{Deserialize, Serialize};
4use strum::{Display, EnumString};
5
6#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
11#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, Display, EnumString)]
12#[serde(rename_all = "snake_case")]
13#[strum(serialize_all = "snake_case")]
14pub enum ApiKeyScope {
15 WorkflowsRead,
17 RunsRead,
19 RunsWrite,
21 RunsManage,
23 StatsRead,
25 AccountsRead,
27 AccountsManage,
29 SignalsSend,
31 Admin,
33}
34
35impl ApiKeyScope {
36 pub fn permits(&self, required: &ApiKeyScope) -> bool {
38 match self {
39 ApiKeyScope::Admin => true,
40 other => other == required,
41 }
42 }
43
44 pub fn has_permission(scopes: &[ApiKeyScope], required: &ApiKeyScope) -> bool {
46 scopes.iter().any(|s| s.permits(required))
47 }
48
49 pub fn all_non_admin() -> Vec<ApiKeyScope> {
51 vec![
52 ApiKeyScope::WorkflowsRead,
53 ApiKeyScope::RunsRead,
54 ApiKeyScope::RunsWrite,
55 ApiKeyScope::RunsManage,
56 ApiKeyScope::StatsRead,
57 ApiKeyScope::AccountsRead,
58 ApiKeyScope::AccountsManage,
59 ApiKeyScope::SignalsSend,
60 ]
61 }
62
63 pub fn member_allowed() -> &'static [ApiKeyScope] {
68 &[
69 ApiKeyScope::WorkflowsRead,
70 ApiKeyScope::RunsRead,
71 ApiKeyScope::StatsRead,
72 ]
73 }
74
75 pub fn all_allowed_for_member(scopes: &[ApiKeyScope]) -> bool {
77 let allowed = Self::member_allowed();
78 scopes.iter().all(|s| allowed.contains(s))
79 }
80}
81
82#[cfg(test)]
83mod tests {
84 use super::*;
85
86 #[test]
87 fn admin_permits_everything() {
88 let admin = ApiKeyScope::Admin;
89 assert!(admin.permits(&ApiKeyScope::RunsRead));
90 assert!(admin.permits(&ApiKeyScope::RunsWrite));
91 assert!(admin.permits(&ApiKeyScope::RunsManage));
92 assert!(admin.permits(&ApiKeyScope::WorkflowsRead));
93 assert!(admin.permits(&ApiKeyScope::StatsRead));
94 assert!(admin.permits(&ApiKeyScope::Admin));
95 }
96
97 #[test]
98 fn regular_scope_only_permits_itself() {
99 let scope = ApiKeyScope::RunsRead;
100 assert!(scope.permits(&ApiKeyScope::RunsRead));
101 assert!(!scope.permits(&ApiKeyScope::RunsWrite));
102 assert!(!scope.permits(&ApiKeyScope::Admin));
103 }
104
105 #[test]
106 fn has_permission_with_multiple_scopes() {
107 let scopes = vec![ApiKeyScope::RunsRead, ApiKeyScope::WorkflowsRead];
108 assert!(ApiKeyScope::has_permission(&scopes, &ApiKeyScope::RunsRead));
109 assert!(ApiKeyScope::has_permission(
110 &scopes,
111 &ApiKeyScope::WorkflowsRead
112 ));
113 assert!(!ApiKeyScope::has_permission(
114 &scopes,
115 &ApiKeyScope::RunsWrite
116 ));
117 }
118
119 #[test]
120 fn roundtrip_display_parse() {
121 let scopes = vec![
122 ApiKeyScope::WorkflowsRead,
123 ApiKeyScope::RunsRead,
124 ApiKeyScope::RunsWrite,
125 ApiKeyScope::RunsManage,
126 ApiKeyScope::StatsRead,
127 ApiKeyScope::AccountsRead,
128 ApiKeyScope::AccountsManage,
129 ApiKeyScope::SignalsSend,
130 ApiKeyScope::Admin,
131 ];
132 for scope in scopes {
133 let s = scope.to_string();
134 let parsed: ApiKeyScope = s.parse().expect("should parse");
135 assert_eq!(parsed, scope);
136 }
137 }
138
139 #[test]
140 fn parse_invalid_scope() {
141 let result = "invalid".parse::<ApiKeyScope>();
142 assert!(result.is_err());
143 }
144
145 #[test]
146 fn serde_roundtrip() {
147 let scope = ApiKeyScope::RunsWrite;
148 let json = serde_json::to_string(&scope).expect("serialize");
149 assert_eq!(json, "\"runs_write\"");
150 let parsed: ApiKeyScope = serde_json::from_str(&json).expect("deserialize");
151 assert_eq!(parsed, scope);
152 }
153
154 #[test]
155 fn all_non_admin_excludes_admin() {
156 let scopes = ApiKeyScope::all_non_admin();
157 assert!(!scopes.contains(&ApiKeyScope::Admin));
158 assert_eq!(scopes.len(), 8);
159 }
160
161 #[test]
162 fn member_allowed_is_read_only() {
163 let allowed = ApiKeyScope::member_allowed();
164 assert!(allowed.contains(&ApiKeyScope::WorkflowsRead));
165 assert!(allowed.contains(&ApiKeyScope::RunsRead));
166 assert!(allowed.contains(&ApiKeyScope::StatsRead));
167 assert!(!allowed.contains(&ApiKeyScope::RunsWrite));
168 assert!(!allowed.contains(&ApiKeyScope::RunsManage));
169 assert!(!allowed.contains(&ApiKeyScope::Admin));
170 assert!(!allowed.contains(&ApiKeyScope::AccountsRead));
171 assert!(!allowed.contains(&ApiKeyScope::AccountsManage));
172 assert!(!allowed.contains(&ApiKeyScope::SignalsSend));
173 }
174
175 #[test]
176 fn all_allowed_for_member_accepts_read_scopes() {
177 let scopes = vec![ApiKeyScope::WorkflowsRead, ApiKeyScope::RunsRead];
178 assert!(ApiKeyScope::all_allowed_for_member(&scopes));
179 }
180
181 #[test]
182 fn all_allowed_for_member_rejects_write_scopes() {
183 let scopes = vec![ApiKeyScope::RunsRead, ApiKeyScope::RunsWrite];
184 assert!(!ApiKeyScope::all_allowed_for_member(&scopes));
185 }
186
187 #[test]
188 fn all_allowed_for_member_rejects_admin() {
189 let scopes = vec![ApiKeyScope::Admin];
190 assert!(!ApiKeyScope::all_allowed_for_member(&scopes));
191 }
192}