1use std::{future::Future, pin::Pin, sync::Arc};
4
5use http::{Request as HttpRequest, Response, StatusCode};
6use tower::{Layer, Service};
7
8use crate::{Checker, Request};
9
10pub trait RequestAuthorizer<B>: Send + Sync {
12 fn authorization_request(&self, request: &HttpRequest<B>) -> Option<Request>;
14}
15
16impl<B, F> RequestAuthorizer<B> for F
17where
18 F: Fn(&HttpRequest<B>) -> Option<Request> + Send + Sync,
19{
20 fn authorization_request(&self, request: &HttpRequest<B>) -> Option<Request> {
21 self(request)
22 }
23}
24
25#[derive(Clone)]
27pub struct AuthorizationLayer<C, A> {
28 checker: Arc<C>,
29 authorizer: Arc<A>,
30}
31
32impl<C, A> AuthorizationLayer<C, A> {
33 #[must_use]
35 pub fn new(checker: C, authorizer: A) -> Self {
36 Self {
37 checker: Arc::new(checker),
38 authorizer: Arc::new(authorizer),
39 }
40 }
41}
42
43impl<S, C, A> Layer<S> for AuthorizationLayer<C, A> {
44 type Service = AuthorizationService<S, C, A>;
45
46 fn layer(&self, inner: S) -> Self::Service {
47 AuthorizationService {
48 inner,
49 checker: Arc::clone(&self.checker),
50 authorizer: Arc::clone(&self.authorizer),
51 }
52 }
53}
54
55#[derive(Clone)]
57pub struct AuthorizationService<S, C, A> {
58 inner: S,
59 checker: Arc<C>,
60 authorizer: Arc<A>,
61}
62
63impl<S, C, A, ReqBody, ResBody> Service<HttpRequest<ReqBody>> for AuthorizationService<S, C, A>
64where
65 S: Service<HttpRequest<ReqBody>, Response = Response<ResBody>> + Clone + Send + 'static,
66 S::Future: Send + 'static,
67 C: Checker + 'static,
68 A: RequestAuthorizer<ReqBody> + 'static,
69 ReqBody: Send + 'static,
70 ResBody: Default + Send + 'static,
71{
72 type Response = S::Response;
73 type Error = S::Error;
74 type Future = Pin<Box<dyn Future<Output = Result<Self::Response, Self::Error>> + Send>>;
75
76 fn poll_ready(
77 &mut self,
78 cx: &mut std::task::Context<'_>,
79 ) -> std::task::Poll<Result<(), Self::Error>> {
80 self.inner.poll_ready(cx)
81 }
82
83 fn call(&mut self, req: HttpRequest<ReqBody>) -> Self::Future {
84 let clone = self.inner.clone();
85 let mut inner = std::mem::replace(&mut self.inner, clone);
86 let checker = Arc::clone(&self.checker);
87 let authorizer = Arc::clone(&self.authorizer);
88
89 Box::pin(async move {
90 let Some(authz_request) = authorizer.authorization_request(&req) else {
91 return Ok(forbidden_response());
92 };
93 if checker.check(&authz_request) {
94 inner.call(req).await
95 } else {
96 Ok(forbidden_response())
97 }
98 })
99 }
100}
101
102fn forbidden_response<ResBody: Default>() -> Response<ResBody> {
103 let mut response = Response::new(ResBody::default());
104 *response.status_mut() = StatusCode::FORBIDDEN;
105 response
106}
107
108#[cfg(test)]
109mod tests {
110 use std::{collections::HashMap, convert::Infallible, future::Ready};
111
112 use http::{Request as HttpRequest, Response, StatusCode};
113 use tower::{Layer, Service, ServiceExt};
114
115 use super::{AuthorizationLayer, RequestAuthorizer};
116 use crate::{Checker, Decision, Request, Resource, Subject};
117
118 #[derive(Clone)]
119 struct StaticChecker(bool);
120
121 impl Checker for StaticChecker {
122 fn authorize(&self, _request: &Request) -> Decision {
123 Decision {
124 allowed: self.0,
125 reason: "test".into(),
126 }
127 }
128 }
129
130 #[derive(Clone)]
131 struct OkService;
132
133 impl Service<HttpRequest<()>> for OkService {
134 type Response = Response<()>;
135 type Error = Infallible;
136 type Future = Ready<Result<Self::Response, Self::Error>>;
137
138 fn poll_ready(
139 &mut self,
140 _cx: &mut std::task::Context<'_>,
141 ) -> std::task::Poll<Result<(), Self::Error>> {
142 std::task::Poll::Ready(Ok(()))
143 }
144
145 fn call(&mut self, _request: HttpRequest<()>) -> Self::Future {
146 std::future::ready(Ok(Response::builder()
147 .status(StatusCode::OK)
148 .body(())
149 .unwrap()))
150 }
151 }
152
153 fn authz_request() -> Request {
154 Request {
155 subject: Subject {
156 id: "user-1".into(),
157 roles: Vec::new(),
158 attributes: HashMap::new(),
159 },
160 resource: Resource {
161 resource_type: "article".into(),
162 id: "article-1".into(),
163 attributes: HashMap::new(),
164 },
165 action: "read".into(),
166 context: HashMap::new(),
167 }
168 }
169
170 #[derive(Clone)]
171 struct StaticAuthorizer(bool);
172
173 impl RequestAuthorizer<()> for StaticAuthorizer {
174 fn authorization_request(&self, _request: &HttpRequest<()>) -> Option<Request> {
175 self.0.then(authz_request)
176 }
177 }
178
179 #[tokio::test]
180 async fn authorization_layer_allows_only_explicit_allow_decisions() {
181 let mut service =
182 AuthorizationLayer::new(StaticChecker(true), StaticAuthorizer(true)).layer(OkService);
183 let response = service
184 .ready()
185 .await
186 .unwrap()
187 .call(HttpRequest::new(()))
188 .await
189 .unwrap();
190 assert_eq!(response.status(), StatusCode::OK);
191
192 let mut service =
193 AuthorizationLayer::new(StaticChecker(false), StaticAuthorizer(true)).layer(OkService);
194 let response = service
195 .ready()
196 .await
197 .unwrap()
198 .call(HttpRequest::new(()))
199 .await
200 .unwrap();
201 assert_eq!(response.status(), StatusCode::FORBIDDEN);
202 }
203
204 #[tokio::test]
205 async fn authorization_layer_denies_missing_authorization_context() {
206 let mut service =
207 AuthorizationLayer::new(StaticChecker(true), StaticAuthorizer(false)).layer(OkService);
208 let response = service
209 .ready()
210 .await
211 .unwrap()
212 .call(HttpRequest::new(()))
213 .await
214 .unwrap();
215 assert_eq!(response.status(), StatusCode::FORBIDDEN);
216 }
217}