oxide_auth/endpoint/
resource.rs

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