1use axum::{
10 extract::{Request, State},
11 middleware::Next,
12 response::Response,
13};
14
15use crate::{abac::Environment, extract::Auth, middleware::PermissionCheckOutput, prelude::*};
16use cloudillo_types::types::SubjectAttrs;
17
18pub fn check_perm_create(
30 resource_type: &'static str,
31 action: &'static str,
32) -> impl Fn(State<App>, Auth, Request, Next) -> PermissionCheckOutput + Clone {
33 move |state, auth, req, next| {
34 Box::pin(check_create_permission(state, auth, req, next, resource_type, action))
35 }
36}
37
38async fn check_create_permission(
39 State(app): State<App>,
40 Auth(auth_ctx): Auth,
41 req: Request,
42 next: Next,
43 resource_type: &str,
44 action: &str,
45) -> Result<Response, Error> {
46 use tracing::warn;
47
48 if crate::file_access::scope_grants_collection_op(
54 auth_ctx.scope.as_deref(),
55 resource_type,
56 action,
57 ) {
58 return Ok(next.run(req).await);
59 }
60
61 if !auth_ctx.roles.iter().any(|r| r.as_ref() == "contributor") {
64 warn!(
65 subject = %auth_ctx.id_tag,
66 resource_type = resource_type,
67 action = action,
68 roles = ?auth_ctx.roles,
69 "CREATE permission denied: requires at least 'contributor' role"
70 );
71 return Err(Error::PermissionDenied);
72 }
73
74 let subject_attrs = load_subject_attrs(&app, &auth_ctx).await?;
76
77 let environment = Environment::new();
79 let checker = app.permission_checker.read().await;
80
81 if !checker.has_collection_permission(
83 &auth_ctx,
84 &subject_attrs,
85 resource_type,
86 action,
87 &environment,
88 ) {
89 warn!(
90 subject = %auth_ctx.id_tag,
91 resource_type = resource_type,
92 action = action,
93 tier = %subject_attrs.tier,
94 quota_remaining_bytes = %subject_attrs.quota_remaining_bytes,
95 roles = ?subject_attrs.roles,
96 banned = subject_attrs.banned,
97 email_verified = subject_attrs.email_verified,
98 "CREATE permission denied"
99 );
100 return Err(Error::PermissionDenied);
101 }
102
103 Ok(next.run(req).await)
104}
105
106async fn load_subject_attrs(
111 app: &App,
112 auth_ctx: &cloudillo_types::auth_adapter::AuthCtx,
113) -> ClResult<SubjectAttrs> {
114 let banned = match app.meta_adapter.get_profile_info(auth_ctx.tn_id, &auth_ctx.id_tag).await {
117 Ok(_profile_data) => {
118 false
123 }
124 Err(_) => {
125 false
128 }
129 };
130
131 let email_verified = match app.auth_adapter.read_tenant(&auth_ctx.id_tag).await {
134 Ok(_) => {
135 true
139 }
140 Err(_) => {
141 false
143 }
144 };
145
146 let tier: Box<str> = if auth_ctx.roles.iter().any(|r| r.as_ref() == "leader") {
148 "premium".into()
149 } else if auth_ctx.roles.iter().any(|r| r.as_ref() == "creator") {
150 "standard".into()
151 } else {
152 "free".into()
153 };
154
155 let quota_bytes = match tier.as_ref() {
158 "premium" => 1024 * 1024 * 1024, "standard" => 100 * 1024 * 1024, _ => 10 * 1024 * 1024, };
162
163 let rate_limit_remaining_val = 100u32; Ok(SubjectAttrs {
168 id_tag: auth_ctx.id_tag.clone(),
169 roles: auth_ctx.roles.to_vec(),
170 tier,
171 quota_remaining_bytes: quota_bytes.to_string().into(),
172 rate_limit_remaining: rate_limit_remaining_val.to_string().into(),
173 banned,
174 email_verified,
175 })
176}
177
178#[cfg(test)]
179mod tests {
180 use super::*;
181
182 #[test]
183 fn test_subject_attrs_creation() {
184 let attrs = SubjectAttrs {
185 id_tag: "alice".into(),
186 roles: vec!["creator".into()],
187 tier: "standard".into(),
188 quota_remaining_bytes: "100000000".into(),
189 rate_limit_remaining: "50".into(),
190 banned: false,
191 email_verified: true,
192 };
193
194 assert_eq!(attrs.id_tag.as_ref(), "alice");
195 assert_eq!(attrs.tier.as_ref(), "standard");
196 assert!(!attrs.banned);
197 assert!(attrs.email_verified);
198 }
199
200 #[test]
201 fn test_subject_attrs_implements_attr_set() {
202 use crate::abac::AttrSet;
203
204 let attrs = SubjectAttrs {
205 id_tag: "bob".into(),
206 roles: vec!["member".into(), "creator".into()],
207 tier: "premium".into(),
208 quota_remaining_bytes: "500000000".into(),
209 rate_limit_remaining: "95".into(),
210 banned: false,
211 email_verified: true,
212 };
213
214 assert_eq!(attrs.get("id_tag"), Some("bob"));
216 assert_eq!(attrs.get("tier"), Some("premium"));
217 assert_eq!(attrs.get("banned"), Some("false"));
218
219 let roles = attrs.get_list("roles");
221 assert!(roles.is_some());
222 assert_eq!(roles.unwrap().len(), 2);
223
224 assert!(attrs.has("tier", "premium"));
226 assert!(!attrs.has("tier", "free"));
227
228 assert!(attrs.contains("roles", "creator"));
230 assert!(!attrs.contains("roles", "admin"));
231 }
232}
233
234