cloudillo_core/
create_perm.rs1use 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 !auth_ctx.roles.iter().any(|r| r.as_ref() == "contributor") {
51 warn!(
52 subject = %auth_ctx.id_tag,
53 resource_type = resource_type,
54 action = action,
55 roles = ?auth_ctx.roles,
56 "CREATE permission denied: requires at least 'contributor' role"
57 );
58 return Err(Error::PermissionDenied);
59 }
60
61 let subject_attrs = load_subject_attrs(&app, &auth_ctx).await?;
63
64 let environment = Environment::new();
66 let checker = app.permission_checker.read().await;
67
68 if !checker.has_collection_permission(
70 &auth_ctx,
71 &subject_attrs,
72 resource_type,
73 action,
74 &environment,
75 ) {
76 warn!(
77 subject = %auth_ctx.id_tag,
78 resource_type = resource_type,
79 action = action,
80 tier = %subject_attrs.tier,
81 quota_remaining_bytes = %subject_attrs.quota_remaining_bytes,
82 roles = ?subject_attrs.roles,
83 banned = subject_attrs.banned,
84 email_verified = subject_attrs.email_verified,
85 "CREATE permission denied"
86 );
87 return Err(Error::PermissionDenied);
88 }
89
90 Ok(next.run(req).await)
91}
92
93async fn load_subject_attrs(
98 app: &App,
99 auth_ctx: &cloudillo_types::auth_adapter::AuthCtx,
100) -> ClResult<SubjectAttrs> {
101 let banned = match app.meta_adapter.get_profile_info(auth_ctx.tn_id, &auth_ctx.id_tag).await {
104 Ok(_profile_data) => {
105 false
110 }
111 Err(_) => {
112 false
115 }
116 };
117
118 let email_verified = match app.auth_adapter.read_tenant(&auth_ctx.id_tag).await {
121 Ok(_) => {
122 true
126 }
127 Err(_) => {
128 false
130 }
131 };
132
133 let tier: Box<str> = if auth_ctx.roles.iter().any(|r| r.as_ref() == "leader") {
135 "premium".into()
136 } else if auth_ctx.roles.iter().any(|r| r.as_ref() == "creator") {
137 "standard".into()
138 } else {
139 "free".into()
140 };
141
142 let quota_bytes = match tier.as_ref() {
145 "premium" => 1024 * 1024 * 1024, "standard" => 100 * 1024 * 1024, _ => 10 * 1024 * 1024, };
149
150 let rate_limit_remaining_val = 100u32; Ok(SubjectAttrs {
155 id_tag: auth_ctx.id_tag.clone(),
156 roles: auth_ctx.roles.to_vec(),
157 tier,
158 quota_remaining_bytes: quota_bytes.to_string().into(),
159 rate_limit_remaining: rate_limit_remaining_val.to_string().into(),
160 banned,
161 email_verified,
162 })
163}
164
165#[cfg(test)]
166mod tests {
167 use super::*;
168
169 #[test]
170 fn test_subject_attrs_creation() {
171 let attrs = SubjectAttrs {
172 id_tag: "alice".into(),
173 roles: vec!["creator".into()],
174 tier: "standard".into(),
175 quota_remaining_bytes: "100000000".into(),
176 rate_limit_remaining: "50".into(),
177 banned: false,
178 email_verified: true,
179 };
180
181 assert_eq!(attrs.id_tag.as_ref(), "alice");
182 assert_eq!(attrs.tier.as_ref(), "standard");
183 assert!(!attrs.banned);
184 assert!(attrs.email_verified);
185 }
186
187 #[test]
188 fn test_subject_attrs_implements_attr_set() {
189 use crate::abac::AttrSet;
190
191 let attrs = SubjectAttrs {
192 id_tag: "bob".into(),
193 roles: vec!["member".into(), "creator".into()],
194 tier: "premium".into(),
195 quota_remaining_bytes: "500000000".into(),
196 rate_limit_remaining: "95".into(),
197 banned: false,
198 email_verified: true,
199 };
200
201 assert_eq!(attrs.get("id_tag"), Some("bob"));
203 assert_eq!(attrs.get("tier"), Some("premium"));
204 assert_eq!(attrs.get("banned"), Some("false"));
205
206 let roles = attrs.get_list("roles");
208 assert!(roles.is_some());
209 assert_eq!(roles.unwrap().len(), 2);
210
211 assert!(attrs.has("tier", "premium"));
213 assert!(!attrs.has("tier", "free"));
214
215 assert!(attrs.contains("roles", "creator"));
217 assert!(!attrs.contains("roles", "admin"));
218 }
219}
220
221