cts_common/auth/claims/
role.rs1use super::Scope;
2use crate::{
3 auth::claims::{WORKSPACE_ADMIN_SCOPE, WORKSPACE_CONTROL_SCOPE, WORKSPACE_MEMBER_SCOPE},
4 claims::common::ArrayOrValue,
5};
6use serde::{Deserialize, Serialize};
7use thiserror::Error;
8use utoipa::ToSchema;
9
10#[derive(Error, Debug, PartialEq)]
11#[error("Role error: {0}")]
12pub struct RoleError(String);
13
14#[derive(Debug, Copy, Clone, Deserialize, PartialEq, Eq, Hash, Serialize, ToSchema)]
16#[serde(rename_all = "lowercase")]
17#[derive(Default)]
18pub enum Role {
19 #[serde(alias = "Admin", alias = "ADMIN")]
22 Admin,
23
24 #[serde(alias = "Control", alias = "CONTROL")]
26 Control,
27
28 #[serde(alias = "Member", alias = "MEMBER")]
30 #[default]
31 Member,
32}
33
34#[derive(Debug, Clone, Deserialize, PartialEq, Serialize, Default)]
37#[serde(rename_all = "lowercase")]
38pub struct RoleSet(ArrayOrValue<Role>);
39
40impl Role {
41 pub fn scope(&self) -> Scope {
43 match self {
44 Role::Admin => WORKSPACE_ADMIN_SCOPE,
45 Role::Control => WORKSPACE_CONTROL_SCOPE,
46 Role::Member => WORKSPACE_MEMBER_SCOPE,
47 }
48 }
49
50 pub fn is_superset_of(&self, other: Role) -> bool {
54 self.scope().has_all_permissions(other.scope())
55 }
56}
57
58impl RoleSet {
59 pub fn single(role: Role) -> Self {
60 Self(ArrayOrValue::single(role))
61 }
62
63 pub fn scope(&self) -> Scope {
65 self.0
66 .iter()
67 .map(|role| role.scope())
68 .fold(Scope::with_no_permissions(), |acc, scope| acc.merge(scope))
69 }
70
71 pub fn has_role(&self, role: Role) -> bool {
72 self.0.iter().any(|check| check == role)
73 }
74
75 pub fn is_empty(&self) -> bool {
76 self.len() == 0
77 }
78
79 pub fn len(&self) -> usize {
80 self.0.len()
81 }
82
83 pub fn add_role(self, role: Role) -> Self {
84 Self(self.0.add(role))
85 }
86}
87
88impl PartialEq<String> for Role {
89 fn eq(&self, other: &String) -> bool {
90 Into::<&str>::into(*self) == other.as_str()
91 }
92}
93
94impl PartialEq<&str> for Role {
95 fn eq(&self, other: &&str) -> bool {
96 Into::<&str>::into(*self) == *other
97 }
98}
99
100impl TryFrom<String> for Role {
101 type Error = RoleError;
102
103 fn try_from(s: String) -> Result<Self, Self::Error> {
104 s.as_str().try_into()
105 }
106}
107
108impl TryFrom<&str> for Role {
109 type Error = RoleError;
110
111 fn try_from(s: &str) -> Result<Self, Self::Error> {
112 match s.to_lowercase().as_str() {
113 "admin" => Ok(Role::Admin),
114 "control" => Ok(Role::Control),
115 "member" => Ok(Role::Member),
116 _ => Err(RoleError(format!("Invalid role: {s}"))),
117 }
118 }
119}
120
121impl From<Role> for &str {
122 fn from(role: Role) -> Self {
123 match role {
124 Role::Admin => "admin",
125 Role::Control => "control",
126 Role::Member => "member",
127 }
128 }
129}
130
131impl From<Role> for String {
132 fn from(role: Role) -> Self {
133 Into::<&str>::into(role).to_string()
134 }
135}
136
137#[cfg(feature = "test_utils")]
138impl fake::Dummy<fake::Faker> for Role {
139 fn dummy_with_rng<R: rand::Rng + ?Sized>(_: &fake::Faker, _: &mut R) -> Self {
140 Role::Admin
141 }
142}
143
144#[cfg(test)]
145mod test {
146 use super::*;
147 use serde_json::json;
148
149 mod role_set {
150 use super::*;
151
152 #[test]
153 fn single() {
154 let role_set = RoleSet::single(Role::Admin);
155 assert_eq!(role_set.len(), 1);
156 assert!(role_set.has_role(Role::Admin));
157
158 assert!(!role_set.has_role(Role::Member));
160 }
161
162 #[test]
163 fn empty() {
164 let role_set: RoleSet = Default::default();
165 assert!(role_set.is_empty());
166 assert_eq!(role_set.len(), 0);
167 }
168
169 #[test]
170 fn multiple_roles() {
171 let role_set = RoleSet::default()
172 .add_role(Role::Admin)
173 .add_role(Role::Member);
174 assert_eq!(role_set.len(), 2);
175 assert!(role_set.has_role(Role::Admin));
176 assert!(role_set.has_role(Role::Member));
177 }
178
179 #[test]
180 fn scope_single() {
181 let role_set = RoleSet::single(Role::Admin);
182 assert_eq!(role_set.scope(), WORKSPACE_ADMIN_SCOPE);
183 }
184
185 #[test]
186 fn scope_multiple() {
187 let role_set = RoleSet::default()
188 .add_role(Role::Admin)
189 .add_role(Role::Member);
190
191 assert_eq!(
192 role_set.scope(),
193 WORKSPACE_ADMIN_SCOPE.merge(WORKSPACE_MEMBER_SCOPE)
194 );
195 }
196 }
197
198 mod scope {
199 use super::*;
200
201 #[test]
202 fn admin() {
203 assert_eq!(Role::Admin.scope(), WORKSPACE_ADMIN_SCOPE);
204 }
205
206 #[test]
207 fn control() {
208 assert_eq!(Role::Control.scope(), WORKSPACE_CONTROL_SCOPE);
209 }
210
211 #[test]
212 fn member() {
213 assert_eq!(Role::Member.scope(), WORKSPACE_MEMBER_SCOPE);
214 }
215 }
216
217 mod equality {
218 use super::*;
219
220 #[test]
221 fn with_string() {
222 assert_eq!(Role::Admin, "admin".to_string());
223 assert_eq!(Role::Control, "control".to_string());
224 assert_eq!(Role::Member, "member".to_string());
225 }
226
227 #[test]
228 fn with_str() {
229 assert_eq!(Role::Admin, "admin");
230 assert_eq!(Role::Control, "control");
231 assert_eq!(Role::Member, "member");
232 }
233 }
234
235 mod conversion {
236 use super::*;
237
238 #[test]
239 fn from_string() -> anyhow::Result<()> {
240 assert_eq!(Role::Admin, Role::try_from("admin".to_string())?);
241 assert_eq!(Role::Control, Role::try_from("control".to_string())?);
242 assert_eq!(Role::Member, Role::try_from("member".to_string())?);
243 assert!(Role::try_from("notarole".to_string()).is_err());
244
245 Ok(())
246 }
247
248 #[test]
249 fn from_str() -> anyhow::Result<()> {
250 assert_eq!(Role::Admin, Role::try_from("admin")?);
251 assert_eq!(Role::Control, Role::try_from("control")?);
252 assert_eq!(Role::Member, Role::try_from("member")?);
253 assert!(Role::try_from("notarole").is_err());
254
255 Ok(())
256 }
257 }
258
259 mod deserialize {
260 use super::*;
261
262 #[test]
263 fn lowercase() -> anyhow::Result<()> {
264 assert_eq!(Role::Admin, serde_json::from_value::<Role>(json!("admin"))?);
265 assert_eq!(
266 Role::Control,
267 serde_json::from_value::<Role>(json!("control"))?
268 );
269 assert_eq!(
270 Role::Member,
271 serde_json::from_value::<Role>(json!("member"))?
272 );
273 assert!(serde_json::from_value::<Role>(json!("notarole")).is_err());
274
275 Ok(())
276 }
277
278 #[test]
279 fn capitalized() -> anyhow::Result<()> {
280 assert_eq!(Role::Admin, serde_json::from_value::<Role>(json!("Admin"))?);
281 assert_eq!(
282 Role::Control,
283 serde_json::from_value::<Role>(json!("Control"))?
284 );
285 assert_eq!(
286 Role::Member,
287 serde_json::from_value::<Role>(json!("Member"))?
288 );
289 assert!(serde_json::from_value::<Role>(json!("Notarole")).is_err());
290
291 Ok(())
292 }
293
294 #[test]
295 fn allcaps() -> anyhow::Result<()> {
296 assert_eq!(Role::Admin, serde_json::from_value::<Role>(json!("ADMIN"))?);
297 assert_eq!(
298 Role::Control,
299 serde_json::from_value::<Role>(json!("CONTROL"))?
300 );
301 assert_eq!(
302 Role::Member,
303 serde_json::from_value::<Role>(json!("MEMBER"))?
304 );
305 assert!(serde_json::from_value::<Role>(json!("NOTAROLE")).is_err());
306
307 Ok(())
308 }
309 }
310
311 mod serialize {
312 use super::*;
313
314 #[test]
315 fn capitalisation() {
316 assert_eq!(serde_json::to_value(Role::Admin).unwrap(), json!("admin"));
317 assert_eq!(
318 serde_json::to_value(Role::Control).unwrap(),
319 json!("control")
320 );
321 assert_eq!(serde_json::to_value(Role::Member).unwrap(), json!("member"));
322 }
323 }
324
325 mod privilege_escalation {
326 use super::*;
327
328 #[test]
329 fn admin_is_superset() {
330 assert!(Role::Admin.is_superset_of(Role::Admin));
331 assert!(Role::Admin.is_superset_of(Role::Control));
332 assert!(Role::Admin.is_superset_of(Role::Member));
333 }
334
335 #[test]
336 fn control_is_not_superset_of_admin_or_member() {
337 assert!(!Role::Control.is_superset_of(Role::Admin));
338 assert!(!Role::Control.is_superset_of(Role::Member));
339 }
340
341 #[test]
342 fn member_is_not_superset() {
343 assert!(!Role::Member.is_superset_of(Role::Admin));
344 assert!(!Role::Member.is_superset_of(Role::Control));
345 assert!(Role::Member.is_superset_of(Role::Member));
346 }
347 }
348}