cloudillo_core/
create_perm.rs1use axum::{
7 extract::{Request, State},
8 middleware::Next,
9 response::Response,
10};
11
12use crate::{abac::Environment, extract::Auth, middleware::PermissionCheckOutput, prelude::*};
13use cloudillo_types::types::SubjectAttrs;
14
15pub fn check_perm_create(
27 resource_type: &'static str,
28 action: &'static str,
29) -> impl Fn(State<App>, Auth, Request, Next) -> PermissionCheckOutput + Clone {
30 move |state, auth, req, next| {
31 Box::pin(check_create_permission(state, auth, req, next, resource_type, action))
32 }
33}
34
35async fn check_create_permission(
36 State(app): State<App>,
37 Auth(auth_ctx): Auth,
38 req: Request,
39 next: Next,
40 resource_type: &str,
41 action: &str,
42) -> Result<Response, Error> {
43 use tracing::warn;
44
45 if !auth_ctx.roles.iter().any(|r| r.as_ref() == "contributor") {
48 warn!(
49 subject = %auth_ctx.id_tag,
50 resource_type = resource_type,
51 action = action,
52 roles = ?auth_ctx.roles,
53 "CREATE permission denied: requires at least 'contributor' role"
54 );
55 return Err(Error::PermissionDenied);
56 }
57
58 let subject_attrs = load_subject_attrs(&app, &auth_ctx).await?;
60
61 let environment = Environment::new();
63 let checker = app.permission_checker.read().await;
64
65 if !checker.has_collection_permission(
67 &auth_ctx,
68 &subject_attrs,
69 resource_type,
70 action,
71 &environment,
72 ) {
73 warn!(
74 subject = %auth_ctx.id_tag,
75 resource_type = resource_type,
76 action = action,
77 tier = %subject_attrs.tier,
78 quota_remaining_bytes = %subject_attrs.quota_remaining_bytes,
79 roles = ?subject_attrs.roles,
80 banned = subject_attrs.banned,
81 email_verified = subject_attrs.email_verified,
82 "CREATE permission denied"
83 );
84 return Err(Error::PermissionDenied);
85 }
86
87 Ok(next.run(req).await)
88}
89
90async fn load_subject_attrs(
95 app: &App,
96 auth_ctx: &cloudillo_types::auth_adapter::AuthCtx,
97) -> ClResult<SubjectAttrs> {
98 let banned = match app.meta_adapter.get_profile_info(auth_ctx.tn_id, &auth_ctx.id_tag).await {
101 Ok(_profile_data) => {
102 false
107 }
108 Err(_) => {
109 false
112 }
113 };
114
115 let email_verified = match app.auth_adapter.read_tenant(&auth_ctx.id_tag).await {
118 Ok(_) => {
119 true
123 }
124 Err(_) => {
125 false
127 }
128 };
129
130 let tier: Box<str> = if auth_ctx.roles.iter().any(|r| r.as_ref() == "leader") {
132 "premium".into()
133 } else if auth_ctx.roles.iter().any(|r| r.as_ref() == "creator") {
134 "standard".into()
135 } else {
136 "free".into()
137 };
138
139 let quota_bytes = match tier.as_ref() {
142 "premium" => 1024 * 1024 * 1024, "standard" => 100 * 1024 * 1024, _ => 10 * 1024 * 1024, };
146
147 let rate_limit_remaining_val = 100u32; Ok(SubjectAttrs {
152 id_tag: auth_ctx.id_tag.clone(),
153 roles: auth_ctx.roles.to_vec(),
154 tier,
155 quota_remaining_bytes: quota_bytes.to_string().into(),
156 rate_limit_remaining: rate_limit_remaining_val.to_string().into(),
157 banned,
158 email_verified,
159 })
160}
161
162#[cfg(test)]
163mod tests {
164 use super::*;
165
166 #[test]
167 fn test_subject_attrs_creation() {
168 let attrs = SubjectAttrs {
169 id_tag: "alice".into(),
170 roles: vec!["creator".into()],
171 tier: "standard".into(),
172 quota_remaining_bytes: "100000000".into(),
173 rate_limit_remaining: "50".into(),
174 banned: false,
175 email_verified: true,
176 };
177
178 assert_eq!(attrs.id_tag.as_ref(), "alice");
179 assert_eq!(attrs.tier.as_ref(), "standard");
180 assert!(!attrs.banned);
181 assert!(attrs.email_verified);
182 }
183
184 #[test]
185 fn test_subject_attrs_implements_attr_set() {
186 use crate::abac::AttrSet;
187
188 let attrs = SubjectAttrs {
189 id_tag: "bob".into(),
190 roles: vec!["member".into(), "creator".into()],
191 tier: "premium".into(),
192 quota_remaining_bytes: "500000000".into(),
193 rate_limit_remaining: "95".into(),
194 banned: false,
195 email_verified: true,
196 };
197
198 assert_eq!(attrs.get("id_tag"), Some("bob"));
200 assert_eq!(attrs.get("tier"), Some("premium"));
201 assert_eq!(attrs.get("banned"), Some("false"));
202
203 let roles = attrs.get_list("roles");
205 assert!(roles.is_some());
206 assert_eq!(roles.unwrap().len(), 2);
207
208 assert!(attrs.has("tier", "premium"));
210 assert!(!attrs.has("tier", "free"));
211
212 assert!(attrs.contains("roles", "creator"));
214 assert!(!attrs.contains("roles", "admin"));
215 }
216}
217
218