ferro_rs/authorization/
policy.rs1use super::response::AuthResponse;
6use crate::auth::Authenticatable;
7
8pub trait Policy<M>: Send + Sync {
50 type User: Authenticatable;
52
53 fn before(&self, _user: &Self::User, _ability: &str) -> Option<bool> {
60 None
61 }
62
63 fn view_any(&self, _user: &Self::User) -> AuthResponse {
65 AuthResponse::deny_silent()
66 }
67
68 fn view(&self, _user: &Self::User, _model: &M) -> AuthResponse {
70 AuthResponse::deny_silent()
71 }
72
73 fn create(&self, _user: &Self::User) -> AuthResponse {
75 AuthResponse::deny_silent()
76 }
77
78 fn update(&self, _user: &Self::User, _model: &M) -> AuthResponse {
80 AuthResponse::deny_silent()
81 }
82
83 fn delete(&self, _user: &Self::User, _model: &M) -> AuthResponse {
85 AuthResponse::deny_silent()
86 }
87
88 fn restore(&self, _user: &Self::User, _model: &M) -> AuthResponse {
90 AuthResponse::deny_silent()
91 }
92
93 fn force_delete(&self, _user: &Self::User, _model: &M) -> AuthResponse {
95 AuthResponse::deny_silent()
96 }
97
98 fn check(&self, user: &Self::User, ability: &str, model: Option<&M>) -> AuthResponse {
102 if let Some(result) = self.before(user, ability) {
104 return result.into();
105 }
106
107 match ability {
109 "viewAny" | "view_any" => self.view_any(user),
110 "view" => model
111 .map(|m| self.view(user, m))
112 .unwrap_or_else(AuthResponse::deny_silent),
113 "create" => self.create(user),
114 "update" => model
115 .map(|m| self.update(user, m))
116 .unwrap_or_else(AuthResponse::deny_silent),
117 "delete" => model
118 .map(|m| self.delete(user, m))
119 .unwrap_or_else(AuthResponse::deny_silent),
120 "restore" => model
121 .map(|m| self.restore(user, m))
122 .unwrap_or_else(AuthResponse::deny_silent),
123 "forceDelete" | "force_delete" => model
124 .map(|m| self.force_delete(user, m))
125 .unwrap_or_else(AuthResponse::deny_silent),
126 _ => AuthResponse::deny_silent(),
127 }
128 }
129}
130
131#[cfg(test)]
132mod tests {
133 use super::*;
134 use std::any::Any;
135
136 #[derive(Debug, Clone)]
138 struct TestUser {
139 id: i64,
140 is_admin: bool,
141 }
142
143 impl Authenticatable for TestUser {
144 fn auth_identifier(&self) -> i64 {
145 self.id
146 }
147
148 fn as_any(&self) -> &dyn Any {
149 self
150 }
151 }
152
153 #[derive(Debug)]
155 struct TestPost {
156 #[allow(dead_code)]
157 id: i64,
158 user_id: i64,
159 }
160
161 struct TestPostPolicy;
163
164 impl Policy<TestPost> for TestPostPolicy {
165 type User = TestUser;
166
167 fn before(&self, user: &Self::User, _ability: &str) -> Option<bool> {
168 if user.is_admin {
169 return Some(true);
170 }
171 None
172 }
173
174 fn view(&self, _user: &Self::User, _model: &TestPost) -> AuthResponse {
175 AuthResponse::allow()
176 }
177
178 fn update(&self, user: &Self::User, post: &TestPost) -> AuthResponse {
179 (user.id == post.user_id).into()
180 }
181
182 fn delete(&self, user: &Self::User, post: &TestPost) -> AuthResponse {
183 self.update(user, post)
184 }
185 }
186
187 #[test]
188 fn test_policy_allows_view() {
189 let policy = TestPostPolicy;
190 let user = TestUser {
191 id: 1,
192 is_admin: false,
193 };
194 let post = TestPost { id: 1, user_id: 2 };
195
196 let response = policy.view(&user, &post);
197 assert!(response.allowed());
198 }
199
200 #[test]
201 fn test_policy_owner_can_update() {
202 let policy = TestPostPolicy;
203 let user = TestUser {
204 id: 1,
205 is_admin: false,
206 };
207 let post = TestPost { id: 1, user_id: 1 };
208
209 let response = policy.update(&user, &post);
210 assert!(response.allowed());
211 }
212
213 #[test]
214 fn test_policy_non_owner_cannot_update() {
215 let policy = TestPostPolicy;
216 let user = TestUser {
217 id: 1,
218 is_admin: false,
219 };
220 let post = TestPost { id: 1, user_id: 2 };
221
222 let response = policy.update(&user, &post);
223 assert!(response.denied());
224 }
225
226 #[test]
227 fn test_policy_admin_bypass() {
228 let policy = TestPostPolicy;
229 let admin = TestUser {
230 id: 1,
231 is_admin: true,
232 };
233 let post = TestPost {
234 id: 1,
235 user_id: 999,
236 };
237
238 let response = policy.check(&admin, "update", Some(&post));
240 assert!(response.allowed());
241 }
242
243 #[test]
244 fn test_policy_check_method() {
245 let policy = TestPostPolicy;
246 let user = TestUser {
247 id: 1,
248 is_admin: false,
249 };
250 let post = TestPost { id: 1, user_id: 1 };
251
252 let response = policy.check(&user, "update", Some(&post));
253 assert!(response.allowed());
254
255 let other_post = TestPost { id: 2, user_id: 2 };
256 let response = policy.check(&user, "update", Some(&other_post));
257 assert!(response.denied());
258 }
259}