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 Admin,
27}
28
29impl ApiKeyScope {
30 pub fn permits(&self, required: &ApiKeyScope) -> bool {
32 match self {
33 ApiKeyScope::Admin => true,
34 other => other == required,
35 }
36 }
37
38 pub fn has_permission(scopes: &[ApiKeyScope], required: &ApiKeyScope) -> bool {
40 scopes.iter().any(|s| s.permits(required))
41 }
42
43 pub fn all_non_admin() -> Vec<ApiKeyScope> {
45 vec![
46 ApiKeyScope::WorkflowsRead,
47 ApiKeyScope::RunsRead,
48 ApiKeyScope::RunsWrite,
49 ApiKeyScope::RunsManage,
50 ApiKeyScope::StatsRead,
51 ]
52 }
53
54 pub fn member_allowed() -> &'static [ApiKeyScope] {
59 &[
60 ApiKeyScope::WorkflowsRead,
61 ApiKeyScope::RunsRead,
62 ApiKeyScope::StatsRead,
63 ]
64 }
65
66 pub fn all_allowed_for_member(scopes: &[ApiKeyScope]) -> bool {
68 let allowed = Self::member_allowed();
69 scopes.iter().all(|s| allowed.contains(s))
70 }
71}
72
73#[cfg(test)]
74mod tests {
75 use super::*;
76
77 #[test]
78 fn admin_permits_everything() {
79 let admin = ApiKeyScope::Admin;
80 assert!(admin.permits(&ApiKeyScope::RunsRead));
81 assert!(admin.permits(&ApiKeyScope::RunsWrite));
82 assert!(admin.permits(&ApiKeyScope::RunsManage));
83 assert!(admin.permits(&ApiKeyScope::WorkflowsRead));
84 assert!(admin.permits(&ApiKeyScope::StatsRead));
85 assert!(admin.permits(&ApiKeyScope::Admin));
86 }
87
88 #[test]
89 fn regular_scope_only_permits_itself() {
90 let scope = ApiKeyScope::RunsRead;
91 assert!(scope.permits(&ApiKeyScope::RunsRead));
92 assert!(!scope.permits(&ApiKeyScope::RunsWrite));
93 assert!(!scope.permits(&ApiKeyScope::Admin));
94 }
95
96 #[test]
97 fn has_permission_with_multiple_scopes() {
98 let scopes = vec![ApiKeyScope::RunsRead, ApiKeyScope::WorkflowsRead];
99 assert!(ApiKeyScope::has_permission(&scopes, &ApiKeyScope::RunsRead));
100 assert!(ApiKeyScope::has_permission(
101 &scopes,
102 &ApiKeyScope::WorkflowsRead
103 ));
104 assert!(!ApiKeyScope::has_permission(
105 &scopes,
106 &ApiKeyScope::RunsWrite
107 ));
108 }
109
110 #[test]
111 fn roundtrip_display_parse() {
112 let scopes = vec![
113 ApiKeyScope::WorkflowsRead,
114 ApiKeyScope::RunsRead,
115 ApiKeyScope::RunsWrite,
116 ApiKeyScope::RunsManage,
117 ApiKeyScope::StatsRead,
118 ApiKeyScope::Admin,
119 ];
120 for scope in scopes {
121 let s = scope.to_string();
122 let parsed: ApiKeyScope = s.parse().expect("should parse");
123 assert_eq!(parsed, scope);
124 }
125 }
126
127 #[test]
128 fn parse_invalid_scope() {
129 let result = "invalid".parse::<ApiKeyScope>();
130 assert!(result.is_err());
131 }
132
133 #[test]
134 fn serde_roundtrip() {
135 let scope = ApiKeyScope::RunsWrite;
136 let json = serde_json::to_string(&scope).expect("serialize");
137 assert_eq!(json, "\"runs_write\"");
138 let parsed: ApiKeyScope = serde_json::from_str(&json).expect("deserialize");
139 assert_eq!(parsed, scope);
140 }
141
142 #[test]
143 fn all_non_admin_excludes_admin() {
144 let scopes = ApiKeyScope::all_non_admin();
145 assert!(!scopes.contains(&ApiKeyScope::Admin));
146 assert_eq!(scopes.len(), 5);
147 }
148
149 #[test]
150 fn member_allowed_is_read_only() {
151 let allowed = ApiKeyScope::member_allowed();
152 assert!(allowed.contains(&ApiKeyScope::WorkflowsRead));
153 assert!(allowed.contains(&ApiKeyScope::RunsRead));
154 assert!(allowed.contains(&ApiKeyScope::StatsRead));
155 assert!(!allowed.contains(&ApiKeyScope::RunsWrite));
156 assert!(!allowed.contains(&ApiKeyScope::RunsManage));
157 assert!(!allowed.contains(&ApiKeyScope::Admin));
158 }
159
160 #[test]
161 fn all_allowed_for_member_accepts_read_scopes() {
162 let scopes = vec![ApiKeyScope::WorkflowsRead, ApiKeyScope::RunsRead];
163 assert!(ApiKeyScope::all_allowed_for_member(&scopes));
164 }
165
166 #[test]
167 fn all_allowed_for_member_rejects_write_scopes() {
168 let scopes = vec![ApiKeyScope::RunsRead, ApiKeyScope::RunsWrite];
169 assert!(!ApiKeyScope::all_allowed_for_member(&scopes));
170 }
171
172 #[test]
173 fn all_allowed_for_member_rejects_admin() {
174 let scopes = vec![ApiKeyScope::Admin];
175 assert!(!ApiKeyScope::all_allowed_for_member(&scopes));
176 }
177}