oxide_auth_async/endpoint/
authorization.rs

1use std::{borrow::Cow, marker::PhantomData};
2
3use oxide_auth::{
4    endpoint::{WebResponse, QueryParameter, NormalizedParameter},
5    code_grant::authorization::{Error as AuthorizationError, Request as AuthorizationRequest},
6};
7
8use crate::code_grant::authorization::{
9    authorization_code, Endpoint as AuthorizationEndpoint, Extension, Pending,
10};
11
12use super::*;
13use url::Url;
14
15/// All relevant methods for handling authorization code requests.
16pub struct AuthorizationFlow<E, R>
17where
18    E: Endpoint<R>,
19    R: WebRequest,
20{
21    endpoint: WrappedAuthorization<E, R>,
22}
23
24struct WrappedAuthorization<E: Endpoint<R>, R>
25where
26    E: Endpoint<R>,
27    R: WebRequest,
28{
29    inner: E,
30    extension_fallback: (),
31    r_type: PhantomData<R>,
32}
33
34#[derive(Clone)]
35pub struct WrappedRequest<R>
36where
37    R: WebRequest,
38{
39    /// The query in the url.
40    query: NormalizedParameter,
41
42    /// An error if one occurred.
43    error: Option<R::Error>,
44}
45
46struct AuthorizationPending<'a, E: 'a, R: 'a>
47where
48    E: Endpoint<R> + Send,
49    R: WebRequest,
50{
51    endpoint: &'a mut WrappedAuthorization<E, R>,
52    pending: Pending,
53    request: R,
54}
55
56/// A processed authentication request that may be waiting for authorization by the resource owner.
57///
58/// Note that this borrows from the `AuthorizationFlow` used to create it. You can `finish` the
59/// authorization flow for this request to produce a response or an error.
60struct AuthorizationPartial<'a, E: 'a, R: 'a>
61where
62    E: Endpoint<R> + Send,
63    R: WebRequest,
64{
65    inner: AuthorizationPartialInner<'a, E, R>,
66
67    /// TODO: offer this in the public api instead of dropping the request.
68    _with_request: Option<Box<dyn FnOnce(R) + Send>>,
69}
70
71/// Result type from processing an authentication request.
72enum AuthorizationPartialInner<'a, E: 'a, R: 'a>
73where
74    E: Endpoint<R> + Send,
75    R: WebRequest,
76{
77    /// No error happened during processing and the resource owner can decide over the grant.
78    Pending {
79        /// A utility struct with which the request can be decided.
80        pending: AuthorizationPending<'a, E, R>,
81    },
82
83    /// The request was faulty, e.g. wrong client data, but there is a well defined response.
84    Failed {
85        /// The request passed in.
86        request: R,
87
88        /// Final response to the client.
89        ///
90        /// This should be forwarded unaltered to the client. Modifications and amendments risk
91        /// deviating from the OAuth2 rfc, enabling DoS, or even leaking secrets. Modify with care.
92        response: R::Response,
93    },
94
95    /// An internal error happened during the request.
96    Error {
97        /// The request passed in.
98        request: R,
99
100        /// Error that happened while handling the request.
101        error: E::Error,
102    },
103}
104
105impl<E, R> AuthorizationFlow<E, R>
106where
107    E: Endpoint<R> + Send + Sync,
108    R: WebRequest + Send + Sync,
109    <R as WebRequest>::Error: Send + Sync,
110{
111    /// Check that the endpoint supports the necessary operations for handling requests.
112    ///
113    /// Binds the endpoint to a particular type of request that it supports, for many
114    /// implementations this is probably single type anyways.
115    ///
116    /// ## Panics
117    ///
118    /// Indirectly `execute` may panic when this flow is instantiated with an inconsistent
119    /// endpoint, for details see the documentation of `Endpoint`. For consistent endpoints,
120    /// the panic is instead caught as an error here.
121    pub fn prepare(mut endpoint: E) -> Result<Self, E::Error> {
122        if endpoint.registrar().is_none() {
123            return Err(endpoint.error(OAuthError::PrimitiveError));
124        }
125
126        if endpoint.authorizer_mut().is_none() {
127            return Err(endpoint.error(OAuthError::PrimitiveError));
128        }
129
130        Ok(AuthorizationFlow {
131            endpoint: WrappedAuthorization {
132                inner: endpoint,
133                extension_fallback: (),
134                r_type: PhantomData,
135            },
136        })
137    }
138
139    /// Use the checked endpoint to execute the authorization flow for a request.
140    ///
141    /// In almost all cases this is followed by executing `finish` on the result but some users may
142    /// instead want to inspect the partial result.
143    ///
144    /// ## Panics
145    ///
146    /// When the registrar or the authorizer returned by the endpoint is suddenly `None` when
147    /// previously it was `Some(_)`.
148    pub async fn execute(&mut self, mut request: R) -> Result<R::Response, E::Error> {
149        let negotiated =
150            authorization_code(&mut self.endpoint, &WrappedRequest::new(&mut request)).await;
151
152        let inner = match negotiated {
153            Err(err) => match authorization_error(&mut self.endpoint.inner, &mut request, err) {
154                Ok(response) => AuthorizationPartialInner::Failed { request, response },
155                Err(error) => AuthorizationPartialInner::Error { request, error },
156            },
157            Ok(negotiated) => AuthorizationPartialInner::Pending {
158                pending: AuthorizationPending {
159                    endpoint: &mut self.endpoint,
160                    pending: negotiated,
161                    request,
162                },
163            },
164        };
165
166        let partial = AuthorizationPartial {
167            inner,
168            _with_request: None,
169        };
170
171        partial.finish().await
172    }
173}
174
175impl<'a, E, R> AuthorizationPartial<'a, E, R>
176where
177    E: Endpoint<R> + Send,
178    R: WebRequest + Send,
179{
180    /// Finish the authentication step.
181    ///
182    /// If authorization has not yet produced a hard error or an explicit response, executes the
183    /// owner solicitor of the endpoint to determine owner consent.
184    pub async fn finish(self) -> Result<R::Response, E::Error> {
185        let (_request, result) = match self.inner {
186            AuthorizationPartialInner::Pending { pending } => pending.finish().await,
187            AuthorizationPartialInner::Failed { request, response } => (request, Ok(response)),
188            AuthorizationPartialInner::Error { request, error } => (request, Err(error)),
189        };
190
191        result
192    }
193}
194
195fn authorization_error<E, R>(
196    endpoint: &mut E, request: &mut R, error: AuthorizationError,
197) -> Result<R::Response, E::Error>
198where
199    E: Endpoint<R>,
200    R: WebRequest,
201{
202    match error {
203        AuthorizationError::Ignore => Err(endpoint.error(OAuthError::DenySilently)),
204        AuthorizationError::Redirect(mut target) => {
205            let mut response =
206                endpoint.response(request, Template::new_redirect(Some(target.description())))?;
207            response
208                .redirect(target.into())
209                .map_err(|err| endpoint.web_error(err))?;
210            Ok(response)
211        }
212        AuthorizationError::PrimitiveError => Err(endpoint.error(OAuthError::PrimitiveError)),
213    }
214}
215
216impl<'a, E, R> AuthorizationPending<'a, E, R>
217where
218    E: Endpoint<R> + Send,
219    R: WebRequest + Send,
220{
221    /// Resolve the pending status using the endpoint to query owner consent.
222    async fn finish(mut self) -> (R, Result<R::Response, E::Error>) {
223        let checked = self
224            .endpoint
225            .owner_solicitor()
226            .check_consent(&mut self.request, self.pending.as_solicitation())
227            .await;
228
229        match checked {
230            OwnerConsent::Denied => self.deny(),
231            OwnerConsent::InProgress(resp) => self.in_progress(resp),
232            OwnerConsent::Authorized(who) => self.authorize(who).await,
233            OwnerConsent::Error(err) => (self.request, Err(self.endpoint.inner.web_error(err))),
234        }
235    }
236
237    /// Postpones the decision over the request, to display data to the resource owner.
238    ///
239    /// This should happen at least once for each request unless the resource owner has already
240    /// acknowledged and pre-approved a specific grant.  The response can also be used to determine
241    /// the resource owner, if no login has been detected or if multiple accounts are allowed to be
242    /// logged in at the same time.
243    fn in_progress(self, response: R::Response) -> (R, Result<R::Response, E::Error>) {
244        (self.request, Ok(response))
245    }
246
247    /// Denies the request, the client is not allowed access.
248    fn deny(mut self) -> (R, Result<R::Response, E::Error>) {
249        let result = self.pending.deny();
250        let result = Self::convert_result(result, &mut self.endpoint.inner, &mut self.request);
251
252        (self.request, result)
253    }
254
255    /// Tells the system that the resource owner with the given id has approved the grant.
256    async fn authorize(mut self, who: String) -> (R, Result<R::Response, E::Error>) {
257        let result = self.pending.authorize(self.endpoint, who.into()).await;
258        let result = Self::convert_result(result, &mut self.endpoint.inner, &mut self.request);
259
260        (self.request, result)
261    }
262
263    fn convert_result(
264        result: Result<Url, AuthorizationError>, endpoint: &mut E, request: &mut R,
265    ) -> Result<R::Response, E::Error> {
266        match result {
267            Ok(url) => {
268                let mut response = endpoint.response(request, Template::new_redirect(None))?;
269                response.redirect(url).map_err(|err| endpoint.web_error(err))?;
270                Ok(response)
271            }
272            Err(err) => authorization_error(endpoint, request, err),
273        }
274    }
275}
276
277impl<E, R> WrappedAuthorization<E, R>
278where
279    E: Endpoint<R>,
280    R: WebRequest,
281{
282    fn owner_solicitor(&mut self) -> &mut (dyn OwnerSolicitor<R> + Send) {
283        self.inner.owner_solicitor().unwrap()
284    }
285}
286
287impl<E, R> AuthorizationEndpoint for WrappedAuthorization<E, R>
288where
289    E: Endpoint<R>,
290    R: WebRequest,
291{
292    fn registrar(&self) -> &(dyn Registrar + Sync) {
293        self.inner.registrar().unwrap()
294    }
295
296    fn authorizer(&mut self) -> &mut (dyn Authorizer + Send) {
297        self.inner.authorizer_mut().unwrap()
298    }
299
300    fn extension(&mut self) -> &mut (dyn Extension + Send) {
301        self.inner
302            .extension()
303            .and_then(super::Extension::authorization)
304            .unwrap_or(&mut self.extension_fallback)
305    }
306}
307
308impl<'a, R> WrappedRequest<R>
309where
310    R: WebRequest + 'a,
311{
312    pub fn new(request: &'a mut R) -> Self {
313        Self::new_or_fail(request).unwrap_or_else(Self::from_err)
314    }
315
316    fn new_or_fail(request: &'a mut R) -> Result<Self, R::Error> {
317        Ok(WrappedRequest {
318            query: request.query()?.into_owned(),
319            error: None,
320        })
321    }
322
323    fn from_err(err: R::Error) -> Self {
324        WrappedRequest {
325            query: Default::default(),
326            error: Some(err),
327        }
328    }
329}
330
331impl<R> AuthorizationRequest for WrappedRequest<R>
332where
333    R: WebRequest,
334{
335    fn valid(&self) -> bool {
336        self.error.is_none()
337    }
338
339    fn client_id(&self) -> Option<Cow<str>> {
340        self.query.unique_value("client_id")
341    }
342
343    fn scope(&self) -> Option<Cow<str>> {
344        self.query.unique_value("scope")
345    }
346
347    fn redirect_uri(&self) -> Option<Cow<str>> {
348        self.query.unique_value("redirect_uri")
349    }
350
351    fn state(&self) -> Option<Cow<str>> {
352        self.query.unique_value("state")
353    }
354
355    fn response_type(&self) -> Option<Cow<str>> {
356        self.query.unique_value("response_type")
357    }
358
359    fn extension(&self, key: &str) -> Option<Cow<str>> {
360        self.query.unique_value(key)
361    }
362}