oxide_auth_async/endpoint/
resource.rs

1use std::{marker::PhantomData, borrow::Cow};
2
3use oxide_auth::code_grant::resource::{Error as ResourceError, Request as ResourceRequest};
4use oxide_auth::{
5    endpoint::{Scope, WebResponse},
6    primitives::grant::Grant,
7};
8
9use crate::code_grant::resource::{protect, Endpoint as ResourceEndpoint};
10
11use super::*;
12
13/// Guards resources by requiring OAuth authorization.
14pub struct ResourceFlow<E, R>
15where
16    E: Endpoint<R>,
17    R: WebRequest,
18{
19    endpoint: WrappedResource<E, R>,
20}
21
22struct WrappedResource<E, R>(E, PhantomData<R>)
23where
24    E: Endpoint<R>,
25    R: WebRequest;
26
27struct WrappedRequest<R: WebRequest> {
28    /// Original request.
29    request: PhantomData<R>,
30
31    /// The authorization token.
32    authorization: Option<String>,
33
34    /// An error if one occurred.
35    ///
36    /// Actual parsing of the authorization header is done in the lower level.
37    error: Option<R::Error>,
38}
39
40struct Scoped<'a, E: 'a, R: 'a> {
41    request: &'a mut R,
42    endpoint: &'a mut E,
43}
44
45impl<E, R> ResourceFlow<E, R>
46where
47    E: Endpoint<R> + Send + Sync,
48    R: WebRequest + Send + Sync,
49    <R as WebRequest>::Error: Send + Sync,
50{
51    /// Check that the endpoint supports the necessary operations for handling requests.
52    ///
53    /// Binds the endpoint to a particular type of request that it supports, for many
54    /// implementations this is probably single type anyways.
55    ///
56    /// ## Panics
57    ///
58    /// Indirectly `execute` may panic when this flow is instantiated with an inconsistent
59    /// endpoint, for details see the documentation of `Endpoint` and `execute`. For
60    /// consistent endpoints, the panic is instead caught as an error here.
61    pub fn prepare(mut endpoint: E) -> Result<Self, E::Error> {
62        if endpoint.issuer_mut().is_none() {
63            return Err(endpoint.error(OAuthError::PrimitiveError));
64        }
65
66        if endpoint.scopes().is_none() {
67            return Err(endpoint.error(OAuthError::PrimitiveError));
68        }
69
70        Ok(ResourceFlow {
71            endpoint: WrappedResource(endpoint, PhantomData),
72        })
73    }
74
75    /// Use the checked endpoint to check for authorization for a resource.
76    ///
77    /// ## Panics
78    ///
79    /// When the issuer returned by the endpoint is suddenly `None` when previously it
80    /// was `Some(_)`.
81    pub async fn execute(&mut self, mut request: R) -> Result<Grant, Result<R::Response, E::Error>> {
82        let protected = {
83            let wrapped = WrappedRequest::new(&mut request);
84
85            let mut scoped = Scoped {
86                request: &mut request,
87                endpoint: &mut self.endpoint.0,
88            };
89
90            protect(&mut scoped, &wrapped).await
91        };
92
93        protected.map_err(|err| self.denied(&mut request, err))
94    }
95
96    fn denied(&mut self, request: &mut R, error: ResourceError) -> Result<R::Response, E::Error> {
97        let template = match &error {
98            ResourceError::AccessDenied { .. } => Template::new_unauthorized(None, None),
99            ResourceError::NoAuthentication { .. } => Template::new_unauthorized(None, None),
100            ResourceError::InvalidRequest { .. } => Template::new_bad(None),
101            ResourceError::PrimitiveError => {
102                return Err(self.endpoint.0.error(OAuthError::PrimitiveError))
103            }
104        };
105
106        let mut response = self.endpoint.0.response(request, template)?;
107        response
108            .unauthorized(&error.www_authenticate())
109            .map_err(|err| self.endpoint.0.web_error(err))?;
110
111        Ok(response)
112    }
113}
114
115impl<R: WebRequest> WrappedRequest<R> {
116    fn new(request: &mut R) -> Self {
117        let token = match request.authheader() {
118            // TODO: this is unecessarily wasteful, we always clone.
119            Ok(Some(token)) => Some(token.into_owned()),
120            Ok(None) => None,
121            Err(error) => return Self::from_error(error),
122        };
123
124        WrappedRequest {
125            request: PhantomData,
126            authorization: token,
127            error: None,
128        }
129    }
130
131    fn from_error(error: R::Error) -> Self {
132        WrappedRequest {
133            request: PhantomData,
134            authorization: None,
135            error: Some(error),
136        }
137    }
138}
139
140impl<'a, E: 'a, R: 'a> ResourceEndpoint for Scoped<'a, E, R>
141where
142    E: Endpoint<R>,
143    R: WebRequest,
144{
145    fn scopes(&mut self) -> &[Scope] {
146        self.endpoint.scopes().unwrap().scopes(self.request)
147    }
148
149    fn issuer(&mut self) -> &mut (dyn Issuer + Send) {
150        self.endpoint.issuer_mut().unwrap()
151    }
152}
153
154impl<R: WebRequest> ResourceRequest for WrappedRequest<R> {
155    fn valid(&self) -> bool {
156        self.error.is_none()
157    }
158
159    fn token(&self) -> Option<Cow<str>> {
160        self.authorization.as_deref().map(Cow::Borrowed)
161    }
162}