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