modkit_auth/
authorizer.rs1use crate::{claims::Claims, errors::AuthError, traits::PrimaryAuthorizer, types::SecRequirement};
2use async_trait::async_trait;
3
4#[derive(Debug, Clone, Default)]
6pub struct RoleAuthorizer;
7
8impl RoleAuthorizer {
9 fn check_permission(claims: &Claims, requirement: &SecRequirement) -> bool {
11 claims.permissions.iter().any(|perm| {
12 let resource = perm.resource_pattern();
13 let action = perm.action();
14
15 if resource == requirement.resource && action == requirement.action {
17 return true;
18 }
19
20 if resource == requirement.resource && action == "*" {
22 return true;
23 }
24
25 if resource == "*" && action == requirement.action {
27 return true;
28 }
29
30 if resource == "*" && action == "*" {
32 return true;
33 }
34
35 false
36 })
37 }
38}
39
40#[async_trait]
41impl PrimaryAuthorizer for RoleAuthorizer {
42 async fn check(&self, claims: &Claims, requirement: &SecRequirement) -> Result<(), AuthError> {
43 if Self::check_permission(claims, requirement) {
44 Ok(())
45 } else {
46 Err(AuthError::Forbidden)
47 }
48 }
49}
50
51#[cfg(test)]
52#[cfg_attr(coverage_nightly, coverage(off))]
53mod tests {
54 use super::*;
55 use crate::claims::Permission;
56 use uuid::Uuid;
57
58 fn mock_claims(permissions: Vec<Permission>) -> Claims {
59 Claims {
60 issuer: "test".to_owned(),
61 subject: Uuid::new_v4(),
62 audiences: vec![],
63 expires_at: None,
64 not_before: None,
65 issued_at: None,
66 jwt_id: None,
67 tenant_id: Uuid::new_v4(),
68 permissions,
69 extras: serde_json::Map::new(),
70 }
71 }
72
73 #[tokio::test]
74 async fn test_exact_role_match() {
75 let auth = RoleAuthorizer;
76 let perm = Permission::builder()
77 .resource_pattern("users")
78 .action("read")
79 .build()
80 .unwrap();
81 let claims = mock_claims(vec![perm]);
82 let req = SecRequirement::new("users", "read");
83
84 assert!(auth.check(&claims, &req).await.is_ok());
85 }
86
87 #[tokio::test]
88 async fn test_resource_wildcard() {
89 let auth = RoleAuthorizer;
90 let perm = Permission::builder()
91 .resource_pattern("users")
92 .action("*")
93 .build()
94 .unwrap();
95 let claims = mock_claims(vec![perm]);
96 let req = SecRequirement::new("users", "write");
97
98 assert!(auth.check(&claims, &req).await.is_ok());
99 }
100
101 #[tokio::test]
102 async fn test_action_wildcard() {
103 let auth = RoleAuthorizer;
104 let perm = Permission::builder()
105 .resource_pattern("*")
106 .action("read")
107 .build()
108 .unwrap();
109 let claims = mock_claims(vec![perm]);
110 let req = SecRequirement::new("posts", "read");
111
112 assert!(auth.check(&claims, &req).await.is_ok());
113 }
114
115 #[tokio::test]
116 async fn test_full_wildcard() {
117 let auth = RoleAuthorizer;
118 let perm = Permission::builder()
119 .resource_pattern("*")
120 .action("*")
121 .build()
122 .unwrap();
123 let claims = mock_claims(vec![perm]);
124 let req = SecRequirement::new("anything", "everything");
125
126 assert!(auth.check(&claims, &req).await.is_ok());
127 }
128
129 #[tokio::test]
130 async fn test_no_matching_role() {
131 let auth = RoleAuthorizer;
132 let perm = Permission::builder()
133 .resource_pattern("posts")
134 .action("read")
135 .build()
136 .unwrap();
137 let claims = mock_claims(vec![perm]);
138 let req = SecRequirement::new("users", "read");
139
140 assert!(matches!(
141 auth.check(&claims, &req).await,
142 Err(AuthError::Forbidden)
143 ));
144 }
145}