oxide_auth/code_grant/
authorization.rs

1//! Provides the handling for Authorization Code Requests
2use std::borrow::Cow;
3use std::result::Result as StdResult;
4
5use url::Url;
6use chrono::{Duration, Utc};
7
8use crate::code_grant::error::{AuthorizationError, AuthorizationErrorType};
9use crate::primitives::authorizer::Authorizer;
10use crate::primitives::registrar::{ClientUrl, ExactUrl, Registrar, RegistrarError, PreGrant};
11use crate::primitives::grant::{Extensions, Grant};
12use crate::{endpoint::Scope, endpoint::Solicitation, primitives::registrar::BoundClient};
13
14/// Interface required from a request to determine the handling in the backend.
15pub trait Request {
16    /// Received request might not be encoded correctly. This method gives implementors the chance
17    /// to signal that a request was received but its encoding was generally malformed. If this is
18    /// the case, then no other attribute will be queried. This method exists mainly to make
19    /// frontends straightforward by not having them handle special cases for malformed requests.
20    fn valid(&self) -> bool;
21
22    /// Identity of the client trying to gain an oauth token.
23    fn client_id(&self) -> Option<Cow<str>>;
24
25    /// Optionally specifies the requested scope
26    fn scope(&self) -> Option<Cow<str>>;
27
28    /// Valid request have (one of) the registered redirect urls for this client.
29    fn redirect_uri(&self) -> Option<Cow<str>>;
30
31    /// Optional parameter the client can use to identify the redirected user-agent.
32    fn state(&self) -> Option<Cow<str>>;
33
34    /// The method requested, valid requests MUST return `code`
35    fn response_type(&self) -> Option<Cow<str>>;
36
37    /// Retrieve an additional parameter used in an extension
38    fn extension(&self, key: &str) -> Option<Cow<str>>;
39}
40
41/// A system of addons provided additional data.
42///
43/// An endpoint not having any extension may use `&mut ()` as the result of system.
44pub trait Extension {
45    /// Inspect the request to produce extension data.
46    fn extend(&mut self, request: &dyn Request) -> std::result::Result<Extensions, ()>;
47}
48
49impl Extension for () {
50    fn extend(&mut self, _: &dyn Request) -> std::result::Result<Extensions, ()> {
51        Ok(Extensions::new())
52    }
53}
54
55/// Required functionality to respond to authorization code requests.
56///
57/// Each method will only be invoked exactly once when processing a correct and authorized request,
58/// and potentially less than once when the request is faulty.  These methods should be implemented
59/// by internally using `primitives`, as it is implemented in the `frontend` module.
60pub trait Endpoint {
61    /// 'Bind' a client and redirect uri from a request to internally approved parameters.
62    fn registrar(&self) -> &dyn Registrar;
63
64    /// Generate an authorization code for a given grant.
65    fn authorizer(&mut self) -> &mut dyn Authorizer;
66
67    /// An extension implementation of this endpoint.
68    ///
69    /// It is possible to use `&mut ()`.
70    fn extension(&mut self) -> &mut dyn Extension;
71}
72
73/// The result will indicate wether the authorization succeed or not.
74pub struct Authorization {
75    state: AuthorizationState,
76    extensions: Option<Extensions>,
77    scope: Option<Scope>,
78}
79
80enum AuthorizationState {
81    /// State after request is validated
82    Binding {
83        client_id: String,
84        redirect_uri: Option<ExactUrl>,
85    },
86    Extending {
87        bound_client: BoundClient<'static>,
88    },
89    Negotiating {
90        bound_client: BoundClient<'static>,
91    },
92    Pending {
93        pre_grant: PreGrant,
94        state: Option<String>,
95        extensions: Extensions,
96    },
97    Err(Error),
98}
99
100/// Input injected by the executor into the state machine.
101pub enum Input<'machine> {
102    /// Binding of the client succeeded
103    Bound {
104        /// Request is given again to make some additional check that need bound client to run
105        request: &'machine dyn Request,
106        /// The bound client
107        bound_client: BoundClient<'static>,
108    },
109    /// Extension succeeded
110    Extended(Extensions),
111    /// Negotiation done
112    Negotiated {
113        /// The pre grant from the negotiation
114        pre_grant: PreGrant,
115        /// State from the request
116        state: Option<String>,
117    },
118    /// We're done
119    Finished,
120    /// Advance without input as far as possible, or just retrieve the output again.
121    None,
122}
123
124/// A request by the statemachine to the executor.
125///
126/// Each variant is fulfilled by certain variants of the next inputs as an argument to
127/// `Authorization::advance`. The output of most states is simply repeated if `Input::None` is
128/// provided instead.
129pub enum Output<'machine> {
130    /// Ask registrar to bind the client and checks its redirect_uri
131    Bind {
132        /// The to-be-bound client.
133        client_id: String,
134        /// The redirect_uri to check if any
135        redirect_uri: Option<ExactUrl>,
136    },
137    /// Ask for extensions if any
138    Extend,
139    /// Ask registrar to negotiate
140    Negotiate {
141        /// The current bound client
142        bound_client: &'machine BoundClient<'static>,
143        /// The scope, if any
144        scope: Option<Scope>,
145    },
146    /// State machine is finished, provides parameters to construct a `Pending` (sync or async
147    /// version)
148    Ok {
149        /// The grant
150        pre_grant: PreGrant,
151        /// The state
152        state: Option<String>,
153        /// The extensions
154        extensions: Extensions,
155    },
156    /// The state machine finished in an error.
157    ///
158    /// The error will be repeated on *any* following input.
159    Err(Error),
160}
161
162impl Authorization {
163    /// Create state machine and validate request
164    pub fn new(request: &dyn Request) -> Self {
165        Authorization {
166            state: Self::validate(request).unwrap_or_else(AuthorizationState::Err),
167            extensions: None,
168            scope: None,
169        }
170    }
171
172    /// Go to next state
173    pub fn advance<'req>(&mut self, input: Input<'req>) -> Output<'_> {
174        self.state = match (self.take(), input) {
175            (current, Input::None) => current,
176            (
177                AuthorizationState::Binding { .. },
178                Input::Bound {
179                    request,
180                    bound_client,
181                },
182            ) => self
183                .bound(request, bound_client)
184                .unwrap_or_else(AuthorizationState::Err),
185            (AuthorizationState::Extending { bound_client }, Input::Extended(grant_extension)) => {
186                self.extended(grant_extension, bound_client)
187            }
188            (AuthorizationState::Negotiating { .. }, Input::Negotiated { pre_grant, state }) => {
189                self.negotiated(state, pre_grant)
190            }
191            (AuthorizationState::Err(err), _) => AuthorizationState::Err(err),
192            (_, _) => AuthorizationState::Err(Error::PrimitiveError),
193        };
194
195        self.output()
196    }
197
198    fn output(&self) -> Output<'_> {
199        match &self.state {
200            AuthorizationState::Err(err) => Output::Err(err.clone()),
201            AuthorizationState::Binding {
202                client_id,
203                redirect_uri,
204            } => Output::Bind {
205                client_id: client_id.to_string(),
206                redirect_uri: (*redirect_uri).clone(),
207            },
208            AuthorizationState::Extending { .. } => Output::Extend,
209            AuthorizationState::Negotiating { bound_client } => Output::Negotiate {
210                bound_client: &bound_client,
211                scope: self.scope.clone(),
212            },
213            AuthorizationState::Pending {
214                pre_grant,
215                state,
216                extensions,
217            } => Output::Ok {
218                pre_grant: pre_grant.clone(),
219                state: state.clone(),
220                extensions: extensions.clone(),
221            },
222        }
223    }
224
225    fn bound(
226        &mut self, request: &dyn Request, bound_client: BoundClient<'static>,
227    ) -> Result<AuthorizationState> {
228        // It's done here rather than in `validate` because we need bound_client to be sure
229        // `redirect_uri` has a value
230        match request.response_type() {
231            Some(ref method) if method.as_ref() == "code" => (),
232            _ => {
233                let prepared_error = ErrorUrl::with_request(
234                    request,
235                    (*bound_client.redirect_uri).to_url(),
236                    AuthorizationErrorType::UnsupportedResponseType,
237                );
238                return Err(Error::Redirect(prepared_error));
239            }
240        }
241
242        // Extract additional parameters from request to be used in negotiating
243        // It's done here rather than in `validate` because we need bound_client to be sure
244        // `redirect_uri` has a value
245        let scope = request.scope();
246        self.scope = match scope.map(|scope| scope.as_ref().parse()) {
247            None => None,
248            Some(Err(_)) => {
249                let prepared_error = ErrorUrl::with_request(
250                    request,
251                    (*bound_client.redirect_uri).to_url(),
252                    AuthorizationErrorType::InvalidScope,
253                );
254                return Err(Error::Redirect(prepared_error));
255            }
256            Some(Ok(scope)) => Some(scope),
257        };
258
259        Ok(AuthorizationState::Extending { bound_client })
260    }
261
262    fn extended(
263        &mut self, grant_extension: Extensions, bound_client: BoundClient<'static>,
264    ) -> AuthorizationState {
265        self.extensions = Some(grant_extension);
266        AuthorizationState::Negotiating { bound_client }
267    }
268
269    fn negotiated(&mut self, state: Option<String>, pre_grant: PreGrant) -> AuthorizationState {
270        AuthorizationState::Pending {
271            pre_grant,
272            state,
273            extensions: self.extensions.clone().expect("Should have extensions by now"),
274        }
275    }
276
277    fn take(&mut self) -> AuthorizationState {
278        std::mem::replace(&mut self.state, AuthorizationState::Err(Error::PrimitiveError))
279    }
280
281    fn validate(request: &dyn Request) -> Result<AuthorizationState> {
282        if !request.valid() {
283            return Err(Error::Ignore);
284        };
285
286        // Check preconditions
287        let client_id = request.client_id().ok_or(Error::Ignore)?;
288        let redirect_uri: Option<Cow<ExactUrl>> = match request.redirect_uri() {
289            None => None,
290            Some(ref uri) => {
291                let parsed = uri.parse().map_err(|_| Error::Ignore)?;
292                Some(Cow::Owned(parsed))
293            }
294        };
295
296        Ok(AuthorizationState::Binding {
297            client_id: client_id.into_owned(),
298            redirect_uri: redirect_uri.map(|uri| uri.into_owned()),
299        })
300    }
301}
302
303/// Retrieve allowed scope and redirect url from the registrar.
304///
305/// Checks the validity of any given input as the registrar instance communicates the registrated
306/// parameters. The registrar can also set or override the requested (default) scope of the client.
307/// This will result in a tuple of negotiated parameters which can be used further to authorize
308/// the client by the owner or, in case of errors, in an action to be taken.
309/// If the client is not registered, the request will otherwise be ignored, if the request has
310/// some other syntactical error, the client is contacted at its redirect url with an error
311/// response.
312pub fn authorization_code(handler: &mut dyn Endpoint, request: &dyn Request) -> self::Result<Pending> {
313    enum Requested {
314        None,
315        Bind {
316            client_id: String,
317            redirect_uri: Option<ExactUrl>,
318        },
319        Extend,
320        Negotiate {
321            client_id: String,
322            redirect_uri: Url,
323            scope: Option<Scope>,
324        },
325    }
326
327    let mut authorization = Authorization::new(request);
328    let mut requested = Requested::None;
329    let mut the_redirect_uri = None;
330
331    loop {
332        let input = match requested {
333            Requested::None => Input::None,
334            Requested::Bind {
335                client_id,
336                redirect_uri,
337            } => {
338                let client_url = ClientUrl {
339                    client_id: Cow::Owned(client_id),
340                    redirect_uri: redirect_uri.map(Cow::Owned),
341                };
342                let bound_client = match handler.registrar().bound_redirect(client_url) {
343                    Err(RegistrarError::Unspecified) => return Err(Error::Ignore),
344                    Err(RegistrarError::PrimitiveError) => return Err(Error::PrimitiveError),
345                    Ok(pre_grant) => pre_grant,
346                };
347                the_redirect_uri = Some(bound_client.redirect_uri.clone().into_owned());
348                Input::Bound {
349                    request,
350                    bound_client,
351                }
352            }
353            Requested::Extend => {
354                let grant_extension = match handler.extension().extend(request) {
355                    Ok(extension_data) => extension_data,
356                    Err(()) => {
357                        let prepared_error = ErrorUrl::with_request(
358                            request,
359                            the_redirect_uri.unwrap().into(),
360                            AuthorizationErrorType::InvalidRequest,
361                        );
362                        return Err(Error::Redirect(prepared_error));
363                    }
364                };
365                Input::Extended(grant_extension)
366            }
367            Requested::Negotiate {
368                client_id,
369                redirect_uri,
370                scope,
371            } => {
372                let bound_client = BoundClient {
373                    client_id: Cow::Owned(client_id),
374                    redirect_uri: Cow::Owned(redirect_uri.clone().into()),
375                };
376                let pre_grant = handler
377                    .registrar()
378                    .negotiate(bound_client, scope)
379                    .map_err(|err| match err {
380                        RegistrarError::PrimitiveError => Error::PrimitiveError,
381                        RegistrarError::Unspecified => {
382                            let prepared_error = ErrorUrl::with_request(
383                                request,
384                                redirect_uri,
385                                AuthorizationErrorType::InvalidScope,
386                            );
387                            Error::Redirect(prepared_error)
388                        }
389                    })?;
390                Input::Negotiated {
391                    pre_grant,
392                    state: request.state().map(|s| s.into_owned()),
393                }
394            }
395        };
396
397        requested = match authorization.advance(input) {
398            Output::Bind {
399                client_id,
400                redirect_uri,
401            } => Requested::Bind {
402                client_id,
403                redirect_uri,
404            },
405            Output::Extend => Requested::Extend,
406            Output::Negotiate { bound_client, scope } => Requested::Negotiate {
407                client_id: bound_client.client_id.clone().into_owned(),
408                redirect_uri: bound_client.redirect_uri.to_url(),
409                scope,
410            },
411            Output::Ok {
412                pre_grant,
413                state,
414                extensions,
415            } => {
416                return Ok(Pending {
417                    pre_grant,
418                    state,
419                    extensions,
420                })
421            }
422            Output::Err(e) => return Err(e),
423        };
424    }
425}
426
427/// Represents a valid, currently pending authorization request not bound to an owner. The frontend
428/// can signal a reponse using this object.
429// Don't ever implement `Clone` here. It's to make it very
430// hard for the user toaccidentally respond to a request in two conflicting ways. This has
431// potential security impact if it could be both denied and authorized.
432pub struct Pending {
433    pre_grant: PreGrant,
434    state: Option<String>,
435    extensions: Extensions,
436}
437
438impl Pending {
439    /// Reference this pending state as a solicitation.
440    pub fn as_solicitation(&self) -> Solicitation<'_> {
441        Solicitation {
442            grant: Cow::Borrowed(&self.pre_grant),
443            state: self.state.as_ref().map(|s| Cow::Borrowed(&**s)),
444        }
445    }
446
447    /// Denies the request, which redirects to the client for which the request originated.
448    pub fn deny(self) -> Result<Url> {
449        let url = self.pre_grant.redirect_uri;
450        let mut error = AuthorizationError::default();
451        error.set_type(AuthorizationErrorType::AccessDenied);
452        let error = ErrorUrl::new_generic(url.into_url(), self.state, error);
453        Err(Error::Redirect(error))
454    }
455
456    /// Inform the backend about consent from a resource owner.
457    ///
458    /// Use negotiated parameters to authorize a client for an owner. The endpoint SHOULD be the
459    /// same endpoint as was used to create the pending request.
460    pub fn authorize(self, handler: &mut dyn Endpoint, owner_id: Cow<str>) -> Result<Url> {
461        let mut url = self.pre_grant.redirect_uri.to_url();
462
463        let grant = handler
464            .authorizer()
465            .authorize(Grant {
466                owner_id: owner_id.into_owned(),
467                client_id: self.pre_grant.client_id,
468                redirect_uri: self.pre_grant.redirect_uri.into_url(),
469                scope: self.pre_grant.scope,
470                until: Utc::now() + Duration::minutes(10),
471                extensions: self.extensions,
472            })
473            .map_err(|()| Error::PrimitiveError)?;
474
475        url.query_pairs_mut()
476            .append_pair("code", grant.as_str())
477            .extend_pairs(self.state.map(|v| ("state", v)))
478            .finish();
479        Ok(url)
480    }
481
482    /// Retrieve a reference to the negotiated parameters (e.g. scope). These should be displayed
483    /// to the resource owner when asking for his authorization.
484    pub fn pre_grant(&self) -> &PreGrant {
485        &self.pre_grant
486    }
487}
488
489/// Defines the correct treatment of the error.
490/// Not all errors are signalled to the requesting party, especially when impersonation is possible
491/// it is integral for security to resolve the error internally instead of redirecting the user
492/// agent to a possibly crafted and malicious target.
493#[derive(Clone)]
494pub enum Error {
495    /// Ignore the request entirely
496    Ignore,
497
498    /// Redirect to the given url
499    Redirect(ErrorUrl),
500
501    /// Something happened in one of the primitives.
502    ///
503    /// The endpoint should decide how to handle this and if this is temporary.
504    PrimitiveError,
505}
506
507/// Encapsulates a redirect to a valid redirect_uri with an error response. The implementation
508/// makes it possible to alter the contained error, for example to provide additional optional
509/// information. The error type should not be altered by the frontend but the specificalities
510/// of this should be enforced by the frontend instead.
511#[derive(Clone)]
512pub struct ErrorUrl {
513    base_uri: Url,
514    error: AuthorizationError,
515}
516
517type Result<T> = StdResult<T, Error>;
518
519impl ErrorUrl {
520    /// Construct a new error, already fixing the state parameter if it exists.
521    fn new_generic<S>(mut url: Url, state: Option<S>, error: AuthorizationError) -> ErrorUrl
522    where
523        S: AsRef<str>,
524    {
525        url.query_pairs_mut()
526            .extend_pairs(state.as_ref().map(|st| ("state", st.as_ref())));
527        ErrorUrl { base_uri: url, error }
528    }
529
530    /// Construct a new error, already fixing the state parameter if it exists.
531    pub fn new(url: Url, state: Option<&str>, error: AuthorizationError) -> ErrorUrl {
532        ErrorUrl::new_generic(url, state, error)
533    }
534
535    /// Construct a new error with a request to provide `state` and an error type
536    pub fn with_request(
537        request: &dyn Request, redirect_uri: Url, err_type: AuthorizationErrorType,
538    ) -> ErrorUrl {
539        let mut err = ErrorUrl::new(
540            redirect_uri,
541            request.state().as_deref(),
542            AuthorizationError::default(),
543        );
544        err.description().set_type(err_type);
545        err
546    }
547
548    /// Get a handle to the description the client will receive.
549    pub fn description(&mut self) -> &mut AuthorizationError {
550        &mut self.error
551    }
552}
553
554impl Error {
555    /// Get a handle to the description the client will receive.
556    ///
557    /// Some types of this error don't return any description which is represented by a `None`
558    /// result.
559    pub fn description(&mut self) -> Option<&mut AuthorizationError> {
560        match self {
561            Error::Ignore => None,
562            Error::Redirect(inner) => Some(inner.description()),
563            Error::PrimitiveError => None,
564        }
565    }
566}
567
568impl Into<Url> for ErrorUrl {
569    /// Finalize the error url by saving its parameters in the query part of the redirect_uri
570    fn into(self) -> Url {
571        let mut url = self.base_uri;
572        url.query_pairs_mut().extend_pairs(self.error.into_iter());
573        url
574    }
575}