oxide_auth/code_grant/
client_credentials.rs

1//! Provides the handling for Access Token Requests
2use std::mem;
3use std::borrow::Cow;
4
5use chrono::{Utc, Duration};
6
7use crate::code_grant::accesstoken::BearerToken;
8use crate::code_grant::error::{AccessTokenError, AccessTokenErrorType};
9use crate::endpoint::{Scope, Solicitation};
10use crate::primitives::issuer::Issuer;
11use crate::primitives::grant::{Extensions, Grant};
12use crate::primitives::registrar::{Registrar, RegistrarError, BoundClient, PreGrant, ClientUrl};
13
14use super::accesstoken::{ErrorDescription, PrimitiveError};
15
16/// Required content of a client credentials request.
17pub trait Request {
18    /// Received request might not be encoded correctly. This method gives implementors the chance
19    /// to signal that a request was received but its encoding was generally malformed. If this is
20    /// the case, then no other attribute will be queried. This method exists mainly to make
21    /// frontends straightforward by not having them handle special cases for malformed requests.
22    fn valid(&self) -> bool;
23
24    /// User:password of a basic authorization header.
25    fn authorization(&self) -> Option<(Cow<str>, Cow<[u8]>)>;
26
27    /// Optionally specifies the requested scope
28    fn scope(&self) -> Option<Cow<str>>;
29
30    /// Valid requests have this set to "client_credentials"
31    fn grant_type(&self) -> Option<Cow<str>>;
32
33    /// Retrieve an additional parameter used in an extension
34    fn extension(&self, key: &str) -> Option<Cow<str>>;
35
36    /// Credentials in body should only be enabled if use of HTTP Basic is not possible.
37    ///
38    /// Allows the request body to contain the `client_secret` as a form parameter. This is NOT
39    /// RECOMMENDED and need not be supported. The parameters MUST NOT appear in the request URI
40    /// itself.
41    ///
42    /// Under these considerations, support must be explicitely enabled.
43    fn allow_credentials_in_body(&self) -> bool {
44        false
45    }
46
47    /// Allow the refresh token to be included in the response.
48    ///
49    /// According to [RFC-6749 Section 4.4.3][4.4.3] "A refresh token SHOULD NOT be included" in
50    /// the response for the client credentials grant. Following that recommendation, the default
51    /// behaviour of this flow is to discard any refresh token that is returned from the issuer.
52    ///
53    /// If this behaviour is not what you want (it is possible that your particular application
54    /// does have a use for a client credentials refresh token), you may enable this feature.
55    ///
56    /// [4.4.3]: https://www.rfc-editor.org/rfc/rfc6749#section-4.4.3
57    fn allow_refresh_token(&self) -> bool {
58        false
59    }
60}
61
62/// A system of addons provided additional data.
63///
64/// An endpoint not having any extension may use `&mut ()` as the result of system.
65pub trait Extension {
66    /// Inspect the request to produce extension data.
67    fn extend(&mut self, request: &dyn Request) -> std::result::Result<Extensions, ()>;
68}
69
70impl Extension for () {
71    fn extend(&mut self, _: &dyn Request) -> std::result::Result<Extensions, ()> {
72        Ok(Extensions::new())
73    }
74}
75
76/// Required functionality to respond to client credentials requests.
77///
78/// Each method will only be invoked exactly once when processing a correct and authorized request,
79/// and potentially less than once when the request is faulty.  These methods should be implemented
80/// by internally using `primitives`, as it is implemented in the `frontend` module.
81pub trait Endpoint {
82    /// Get the client corresponding to some id.
83    fn registrar(&self) -> &dyn Registrar;
84
85    /// Return the issuer instance to create the client credentials.
86    fn issuer(&mut self) -> &mut dyn Issuer;
87
88    /// The system of used extension, extending responses.
89    ///
90    /// It is possible to use `&mut ()`.
91    fn extension(&mut self) -> &mut dyn Extension;
92}
93
94enum Credentials<'a> {
95    /// No credentials were offered.
96    None,
97    /// One set of credentials was offered.
98    Authenticated {
99        client_id: &'a str,
100        passphrase: &'a [u8],
101    },
102    /// No password but name was offered.
103    ///
104    /// As the client credentials may not be used for public clients, this is
105    /// actually an error.
106    Unauthenticated,
107    /// Multiple possible credentials were offered.
108    ///
109    /// This is a security issue, only one attempt must be made per request.
110    Duplicate,
111}
112
113/// Client credentials token issuing process
114///
115/// This state machine will go through four phases. On creation, the request will be validated and
116/// parameters for the first step will be extracted from it. It will pose some requests in the form
117/// of [`Output`] which should be satisfied with the next [`Input`] data. This will eventually
118/// produce a [`BearerToken`] or an [`Error`]. Note that the executing environment will need to use
119/// a [`Registrar`], an optional [`Extension`] and an [`Issuer`] to which some requests should be forwarded.
120///
121/// [`Input`]: struct.Input.html
122/// [`Output`]: struct.Output.html
123/// [`BearerToken`]: struct.BearerToken.html
124/// [`Error`]: struct.Error.html
125/// [`Issuer`] ../primitives/issuer/trait.Issuer.html
126/// [`Registrar`] ../primitives/registrar/trait.Registrar.html
127/// [`Extension`] trait.Extension.html
128///
129/// A rough sketch of the operational phases:
130///
131/// 1. Ensure the request is valid based on the basic requirements (includes required parameters)
132/// 2. Try to produce a new token
133///     2.1. Authenticate the client
134///     2.2. Construct a grant based on the request
135///     2.3. Check the intrinsic validity (scope)
136/// 3. Query the backend for a new (bearer) token
137pub struct ClientCredentials {
138    state: ClientCredentialsState,
139    scope: Option<Scope>,
140}
141
142/// Inner state machine for client credentials
143enum ClientCredentialsState {
144    Authenticate {
145        client: String,
146        passdata: Vec<u8>,
147    },
148    Binding {
149        client_id: String,
150    },
151    Extend {
152        bound_client: BoundClient<'static>,
153    },
154    Negotiating {
155        bound_client: BoundClient<'static>,
156        extensions: Extensions,
157    },
158    Issue {
159        pre_grant: PreGrant,
160        extensions: Extensions,
161    },
162    Err(Error),
163}
164
165/// Input injected by the executor into the state machine.
166pub enum Input {
167    /// Positively answer an authentication query.
168    Authenticated,
169    /// Binding of the client succeeded
170    Bound {
171        /// The bound client
172        bound_client: BoundClient<'static>,
173    },
174    /// Provide extensions
175    Extended {
176        /// The grant extension
177        extensions: Extensions,
178    },
179    /// Negotiation done
180    Negotiated {
181        /// The pre grant from the negotiation
182        pre_grant: PreGrant,
183    },
184    /// Advance without input as far as possible, or just retrieve the output again.
185    None,
186}
187
188/// A request by the statemachine to the executor.
189///
190/// Each variant is fulfilled by certain variants of the next inputs as an argument to
191/// `AccessToken::advance`. The output of most states is simply repeated if `Input::None` is
192/// provided instead but note that the successful bearer token response is **not** repeated.
193pub enum Output<'machine> {
194    /// The registrar should authenticate a client.
195    ///
196    /// Fulfilled by `Input::Authenticated`. In an unsuccessful case, the executor should not
197    /// continue and discard the flow.
198    Authenticate {
199        /// The to-be-authenticated client.
200        client: &'machine str,
201        /// The supplied passdata/password.
202        passdata: &'machine [u8],
203    },
204    /// Ask registrar to bind the client. There is no redirect URI provided from the request,
205    /// so the registrar will have to pick one arbitrarily (or return an invalid one). Ths
206    /// redirect URL will not be followed.
207    ///
208    /// Fulfilled by `Input::Bound`
209    Binding {
210        /// The client to bind
211        client_id: &'machine str,
212    },
213    /// The extension (if any) should provide the extensions
214    ///
215    /// Fullfilled by `Input::Extended`
216    Extend,
217    /// Ask registrar to negotiate.
218    ///
219    /// Fulfilled by `Input::Negotiated`
220    Negotiate {
221        /// The current bound client
222        bound_client: &'machine BoundClient<'static>,
223        /// The scope, if any
224        scope: Option<Scope>,
225    },
226    /// The state machine finished and the frontend should assign an owner ID and then
227    /// issue the token.
228    ///
229    /// Fullfilled by `Input::Issued`
230    Ok {
231        /// The grant to be used in the token generation
232        pre_grant: &'machine PreGrant,
233        /// The extensions to include
234        extensions: &'machine Extensions,
235    },
236    /// The state machine finished in an error.
237    ///
238    /// The error will be repeated on *any* following input.
239    Err(Box<Error>),
240}
241
242impl ClientCredentials {
243    /// Create the state machine. validating the request in the process
244    pub fn new(request: &dyn Request) -> Self {
245        let (state, scope) =
246            Self::validate(request).unwrap_or_else(|err| (ClientCredentialsState::Err(err), None));
247        ClientCredentials { state, scope }
248    }
249
250    /// Go to next state
251    pub fn advance(&mut self, input: Input) -> Output<'_> {
252        self.state = match (self.take(), input) {
253            (current, Input::None) => current,
254            (ClientCredentialsState::Authenticate { client, .. }, Input::Authenticated) => {
255                Self::authenticated(client)
256            }
257            (ClientCredentialsState::Binding { .. }, Input::Bound { bound_client }) => {
258                Self::bound(bound_client)
259            }
260            (ClientCredentialsState::Extend { bound_client }, Input::Extended { extensions }) => {
261                Self::extended(bound_client, extensions)
262            }
263            (
264                ClientCredentialsState::Negotiating { extensions, .. },
265                Input::Negotiated { pre_grant },
266            ) => Self::negotiated(pre_grant, extensions),
267            (ClientCredentialsState::Err(err), _) => ClientCredentialsState::Err(err),
268            (_, _) => ClientCredentialsState::Err(Error::Primitive(Box::new(PrimitiveError::empty()))),
269        };
270
271        self.output()
272    }
273
274    fn output(&self) -> Output<'_> {
275        match &self.state {
276            ClientCredentialsState::Err(err) => Output::Err(Box::new(err.clone())),
277            ClientCredentialsState::Authenticate { client, passdata, .. } => Output::Authenticate {
278                client,
279                passdata: passdata.as_slice(),
280            },
281            ClientCredentialsState::Binding { client_id } => Output::Binding { client_id },
282            ClientCredentialsState::Extend { .. } => Output::Extend,
283            ClientCredentialsState::Negotiating { bound_client, .. } => Output::Negotiate {
284                bound_client,
285                scope: self.scope.clone(),
286            },
287            ClientCredentialsState::Issue {
288                pre_grant,
289                extensions,
290            } => Output::Ok {
291                pre_grant,
292                extensions,
293            },
294        }
295    }
296
297    fn take(&mut self) -> ClientCredentialsState {
298        mem::replace(
299            &mut self.state,
300            ClientCredentialsState::Err(Error::Primitive(Box::new(PrimitiveError::empty()))),
301        )
302    }
303
304    fn validate(request: &dyn Request) -> Result<(ClientCredentialsState, Option<Scope>)> {
305        if !request.valid() {
306            return Err(Error::invalid());
307        }
308
309        let authorization = request.authorization();
310        let client_id = request.extension("client_id");
311        let client_secret = request.extension("client_secret");
312
313        let mut credentials = Credentials::None;
314        if let Some((client_id, auth)) = &authorization {
315            credentials.authenticate(client_id.as_ref(), auth.as_ref());
316        }
317
318        match (&client_id, &client_secret) {
319            (Some(client_id), Some(client_secret)) if request.allow_credentials_in_body() => {
320                credentials.authenticate(client_id.as_ref(), client_secret.as_ref().as_bytes())
321            }
322            (None, None) => {}
323            _ => credentials.unauthenticated(),
324        }
325
326        let scope = match request.scope().map(|scope| scope.as_ref().parse()) {
327            None => None,
328            Some(Err(_)) => return Err(Error::invalid()),
329            Some(Ok(scope)) => Some(scope),
330        };
331
332        match request.grant_type() {
333            Some(ref cow) if cow == "client_credentials" => (),
334            None => return Err(Error::invalid()),
335            Some(_) => return Err(Error::invalid_with(AccessTokenErrorType::UnsupportedGrantType)),
336        };
337
338        let (client_id, passdata) = credentials.into_client().ok_or_else(Error::invalid)?;
339
340        Ok((
341            ClientCredentialsState::Authenticate {
342                client: client_id.to_string(),
343                passdata: Vec::from(passdata),
344            },
345            scope,
346        ))
347    }
348
349    fn authenticated(client_id: String) -> ClientCredentialsState {
350        ClientCredentialsState::Binding { client_id }
351    }
352
353    fn bound(bound_client: BoundClient<'static>) -> ClientCredentialsState {
354        ClientCredentialsState::Extend { bound_client }
355    }
356
357    fn extended(bound_client: BoundClient<'static>, extensions: Extensions) -> ClientCredentialsState {
358        ClientCredentialsState::Negotiating {
359            bound_client,
360            extensions,
361        }
362    }
363
364    fn negotiated(pre_grant: PreGrant, extensions: Extensions) -> ClientCredentialsState {
365        ClientCredentialsState::Issue {
366            pre_grant,
367            extensions,
368        }
369    }
370}
371
372/// Represents a valid, currently pending client credentials not bound to an owner.
373///
374/// This will be passed along to the solicitor to obtain the owner ID, and then
375/// a token will be issued. Since this is the client credentials flow, a pending
376/// response is considered an internal error.
377// Don't ever implement `Clone` here. It's to make it very
378// hard for the user to accidentally respond to a request in two conflicting ways. This has
379// potential security impact if it could be both denied and authorized.
380pub struct Pending {
381    pre_grant: PreGrant,
382    extensions: Extensions,
383}
384
385impl Pending {
386    /// Reference this pending state as a solicitation.
387    pub fn as_solicitation(&self) -> Solicitation<'_> {
388        Solicitation {
389            grant: Cow::Borrowed(&self.pre_grant),
390            state: None,
391        }
392    }
393
394    /// Inform the backend about consent from a resource owner.
395    ///
396    /// Use negotiated parameters to authorize a client for an owner. The endpoint SHOULD be the
397    /// same endpoint as was used to create the pending request.
398    pub fn issue(
399        self, handler: &mut dyn Endpoint, owner_id: String, allow_refresh_token: bool,
400    ) -> Result<BearerToken> {
401        let mut token = handler
402            .issuer()
403            .issue(Grant {
404                owner_id,
405                client_id: self.pre_grant.client_id,
406                redirect_uri: self.pre_grant.redirect_uri.into_url(),
407                scope: self.pre_grant.scope.clone(),
408                until: Utc::now() + Duration::minutes(10),
409                extensions: self.extensions,
410            })
411            .map_err(|()| Error::Primitive(Box::new(PrimitiveError::empty())))?;
412
413        if !allow_refresh_token {
414            token.refresh = None;
415        }
416
417        Ok(BearerToken(token, self.pre_grant.scope.clone()))
418    }
419}
420
421// FiXME: use state machine instead
422/// Try to get client credentials.
423pub fn client_credentials(handler: &mut dyn Endpoint, request: &dyn Request) -> Result<Pending> {
424    enum Requested {
425        None,
426        Authenticate {
427            client: String,
428            passdata: Vec<u8>,
429        },
430        Bind {
431            client_id: String,
432        },
433        Extend,
434        Negotiate {
435            bound_client: BoundClient<'static>,
436            scope: Option<Scope>,
437        },
438    }
439
440    let mut client_credentials = ClientCredentials::new(request);
441    let mut requested = Requested::None;
442
443    loop {
444        let input = match requested {
445            Requested::None => Input::None,
446            Requested::Authenticate { client, passdata } => {
447                handler
448                    .registrar()
449                    .check(&client, Some(passdata.as_slice()))
450                    .map_err(|err| match err {
451                        RegistrarError::Unspecified => Error::unauthorized("basic"),
452                        RegistrarError::PrimitiveError => Error::Primitive(Box::new(PrimitiveError {
453                            grant: None,
454                            extensions: None,
455                        })),
456                    })?;
457                Input::Authenticated
458            }
459            Requested::Bind { client_id } => {
460                let client_url = ClientUrl {
461                    client_id: Cow::Owned(client_id),
462                    redirect_uri: None,
463                };
464                let bound_client = match handler.registrar().bound_redirect(client_url) {
465                    Err(RegistrarError::Unspecified) => return Err(Error::Ignore),
466                    Err(RegistrarError::PrimitiveError) => {
467                        return Err(Error::Primitive(Box::new(PrimitiveError {
468                            grant: None,
469                            extensions: None,
470                        })));
471                    }
472                    Ok(pre_grant) => pre_grant,
473                };
474                Input::Bound { bound_client }
475            }
476            Requested::Extend => {
477                let extensions = handler
478                    .extension()
479                    .extend(request)
480                    .map_err(|_| Error::invalid())?;
481                Input::Extended { extensions }
482            }
483            Requested::Negotiate { bound_client, scope } => {
484                let pre_grant = handler
485                    .registrar()
486                    .negotiate(bound_client.clone(), scope.clone())
487                    .map_err(|err| match err {
488                        RegistrarError::PrimitiveError => Error::Primitive(Box::new(PrimitiveError {
489                            grant: None,
490                            extensions: None,
491                        })),
492                        RegistrarError::Unspecified => Error::Ignore,
493                    })?;
494                Input::Negotiated { pre_grant }
495            }
496        };
497
498        requested = match client_credentials.advance(input) {
499            Output::Authenticate { client, passdata } => Requested::Authenticate {
500                client: client.to_owned(),
501                passdata: passdata.to_vec(),
502            },
503            Output::Binding { client_id } => Requested::Bind {
504                client_id: client_id.to_owned(),
505            },
506            Output::Extend => Requested::Extend,
507            Output::Negotiate { bound_client, scope } => Requested::Negotiate {
508                bound_client: bound_client.clone(),
509                scope,
510            },
511            Output::Ok {
512                pre_grant,
513                extensions,
514            } => {
515                return Ok(Pending {
516                    pre_grant: pre_grant.clone(),
517                    extensions: extensions.clone(),
518                })
519            }
520            Output::Err(e) => return Err(*e),
521        };
522    }
523}
524
525impl<'a> Credentials<'a> {
526    pub fn authenticate(&mut self, client_id: &'a str, passphrase: &'a [u8]) {
527        self.add(Credentials::Authenticated {
528            client_id,
529            passphrase,
530        })
531    }
532
533    pub fn unauthenticated(&mut self) {
534        self.add(Credentials::Unauthenticated)
535    }
536
537    pub fn into_client(self) -> Option<(&'a str, &'a [u8])> {
538        match self {
539            Credentials::Authenticated {
540                client_id,
541                passphrase,
542            } => Some((client_id, passphrase)),
543            Credentials::Unauthenticated { .. } => None,
544            _ => None,
545        }
546    }
547
548    fn add(&mut self, new: Self) {
549        *self = match self {
550            Credentials::None => new,
551            _ => Credentials::Duplicate,
552        };
553    }
554}
555
556/// Defines actions for the response to a client credentials request.
557#[derive(Clone)]
558pub enum Error {
559    /// Ignore the request entirely
560    Ignore,
561
562    /// The token did not represent a valid token.
563    Invalid(ErrorDescription),
564
565    /// The client did not properly authorize itself.
566    Unauthorized(ErrorDescription, String),
567
568    /// An underlying primitive operation did not complete successfully.
569    ///
570    /// This is expected to occur with some endpoints. See `PrimitiveError` for
571    /// more details on when this is returned.
572    Primitive(Box<PrimitiveError>),
573}
574
575type Result<T> = std::result::Result<T, Error>;
576
577impl Error {
578    /// Create invalid error type
579    pub fn invalid() -> Self {
580        Error::Invalid(ErrorDescription {
581            error: AccessTokenError::default(),
582        })
583    }
584
585    fn invalid_with(with_type: AccessTokenErrorType) -> Self {
586        Error::Invalid(ErrorDescription {
587            error: {
588                let mut error = AccessTokenError::default();
589                error.set_type(with_type);
590                error
591            },
592        })
593    }
594
595    /// Create unauthorized error type
596    pub fn unauthorized(authtype: &str) -> Error {
597        Error::Unauthorized(
598            ErrorDescription {
599                error: {
600                    let mut error = AccessTokenError::default();
601                    error.set_type(AccessTokenErrorType::InvalidClient);
602                    error
603                },
604            },
605            authtype.to_string(),
606        )
607    }
608
609    /// Get a handle to the description the client will receive.
610    ///
611    /// Some types of this error don't return any description which is represented by a `None`
612    /// result.
613    pub fn description(&mut self) -> Option<&mut AccessTokenError> {
614        match self {
615            Error::Ignore => None,
616            Error::Invalid(description) => Some(description.description()),
617            Error::Unauthorized(description, _) => Some(description.description()),
618            Error::Primitive(_) => None,
619        }
620    }
621}