dcaf/endpoints/token_req/
mod.rs

1/*
2 * Copyright (c) 2022 The NAMIB Project Developers.
3 * Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
4 * https://www.apache.org/licenses/LICENSE-2.0> or the MIT license
5 * <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your
6 * option. This file may not be copied, modified, or distributed
7 * except according to those terms.
8 *
9 * SPDX-License-Identifier: MIT OR Apache-2.0
10 */
11
12//! Contains the data models for structures related to access token requests and responses,
13//! as described in [RFC 9200, section 5.8](https://www.rfc-editor.org/rfc/rfc9200#section-5.8).
14//!
15//! The most important members of this module are [`AccessTokenRequest`], [`AccessTokenResponse`],
16//! and [`ErrorResponse`]. Look at their documentation for usage examples.
17//! Other members are mainly used as part of the aforementioned structures.
18
19use coset::AsCborValue;
20
21use crate::common::cbor_values::{ByteString, ProofOfPossessionKey};
22use crate::Scope;
23
24#[cfg(not(feature = "std"))]
25use {alloc::boxed::Box, alloc::string::String, alloc::vec::Vec};
26
27#[cfg(test)]
28mod tests;
29
30/// Type of the resource owner's authorization used by the client to obtain an access token.
31/// For more information, see [section 1.3 of RFC 6749](https://www.rfc-editor.org/rfc/rfc6749).
32///
33/// Grant types are used in the [`AccessTokenRequest`].
34///
35/// # Example
36/// For example, if you wish to indicate in your request that the resource owner's authorization
37/// works via client credentials:
38/// ```
39/// # use dcaf::{AccessTokenRequest, GrantType};
40/// # use dcaf::endpoints::token_req::AccessTokenRequestBuilderError;
41/// let request = AccessTokenRequest::builder()
42///     .client_id("test_client")
43///     .grant_type(GrantType::ClientCredentials)
44///     .build()?;
45/// # Ok::<(), AccessTokenRequestBuilderError>(())
46/// ```
47/// It's also possible to use your own value for a custom grant type, as defined in
48/// [section 8.5 of RFC 9200](https://www.rfc-editor.org/rfc/rfc9200#section-8.5):
49/// ```
50/// # use dcaf::{AccessTokenRequest, GrantType};
51/// # use dcaf::endpoints::token_req::AccessTokenRequestBuilderError;
52/// let request = AccessTokenRequest::builder()
53///     .client_id("test_client")
54///     // values below -65536 marked for private use.
55///     .grant_type(GrantType::Other(-99999))
56///     .build()?;
57/// # Ok::<(), AccessTokenRequestBuilderError>(())
58/// ```
59#[derive(Debug, PartialEq, Eq, Copy, Clone, Hash)]
60#[non_exhaustive]
61pub enum GrantType {
62    /// Grant type intended for clients capable of obtaining the
63    /// resource owner's credentials.
64    ///
65    /// Note that the authorization server should take special care when
66    /// enabling this grant type and only allow it when other flows are not viable.
67    ///
68    /// See [section 4.3 of RFC 6749](https://www.rfc-editor.org/rfc/rfc6749#section-4.3)
69    /// for details.
70    Password,
71
72    /// Redirection-based flow optimized for confidential clients.
73    ///
74    /// See [section 4.1 of RFC 6749](https://www.rfc-editor.org/rfc/rfc6749#section-4.1)
75    /// for details.
76    AuthorizationCode,
77
78    /// Used when the client authenticates with the authorization server in an unspecified way.
79    ///
80    /// Must only be used for confidential clients.
81    ///
82    /// See [section 4.4 of RFC 6749](https://www.rfc-editor.org/rfc/rfc6749#section-4.4)
83    /// for details.
84    ClientCredentials,
85
86    /// Used for refreshing an existing access token.
87    ///
88    /// When using this, it's necessary that [`refresh_token`](AccessTokenResponse::refresh_token)
89    /// is specified in the [`AccessTokenResponse`].
90    ///
91    /// See [section 6 of RFC 6749](https://www.rfc-editor.org/rfc/rfc6749#section-6)
92    /// for details.
93    RefreshToken,
94
95    /// Another authorization grant not listed here.
96    ///
97    /// See [section 8.5 of RFC 9200](https://www.rfc-editor.org/rfc/rfc9200#section-8.5)
98    /// for corresponding IANA registries.
99    Other(i32),
100}
101
102/// Request for an access token, sent from the client, as defined in
103/// [section 5.8.1 of RFC 9200](https://www.rfc-editor.org/rfc/rfc9200#section-5.8.1).
104///
105/// Use the [`AccessTokenRequestBuilder`] (which you can access using the
106/// [`AccessTokenRequest::builder()`] method) to create an instance of this struct.
107///
108/// # Example
109/// Figure 5 of [RFC 9200](https://www.rfc-editor.org/rfc/rfc9200#figure-4)
110/// gives us an example of an access token request, given in CBOR diagnostic notation[^cbor]:
111/// ```text
112/// {
113///     "client_id" : "myclient",
114///     "audience" : "tempSensor4711"
115/// }
116/// ```
117///
118/// This could be built and serialized as an [`AccessTokenRequest`] like so:
119/// ```
120/// # use std::error::Error;
121/// # use ciborium_io::{Read, Write};
122/// # use dcaf::{ToCborMap, AccessTokenRequest, Scope};
123/// # use dcaf::endpoints::token_req::AccessTokenRequestBuilderError;
124/// # use dcaf::error::InvalidTextEncodedScopeError;
125/// # #[cfg(feature = "std")] {
126/// let request: AccessTokenRequest = AccessTokenRequest::builder()
127///    .client_id("myclient")
128///    .audience("tempSensor4711")
129///    .build()?;
130/// let mut serialized = Vec::new();
131/// request.clone().serialize_into(&mut serialized)?;
132/// assert_eq!(AccessTokenRequest::deserialize_from(serialized.as_slice())?, request);
133/// # }
134/// # Ok::<(), Box<dyn Error>>(())
135/// ```
136///
137/// [^cbor]: Note that abbreviations aren't used here, so keep in mind that the labels are really
138/// integers instead of strings.
139#[derive(Debug, Default, PartialEq, Clone, Builder)]
140#[builder(
141    no_std,
142    setter(into, strip_option),
143    derive(Debug, PartialEq),
144    build_fn(validate = "Self::validate")
145)]
146pub struct AccessTokenRequest {
147    // TODO: Certain grant types have certain required fields. These should be verified in the
148    //       builder's `validate` method (only if the grant type is given! Otherwise, check spec.)
149    /// The client identifier as described in section 2.2 of
150    /// [RFC 6749](https://www.rfc-editor.org/rfc/rfc6749).
151    #[builder(default)]
152    pub client_id: Option<String>,
153
154    /// Grant type used for this request.
155    ///
156    /// Defaults to [`GrantType::ClientCredentials`].
157    ///
158    /// See also the documentation of [`GrantType`] for details.
159    #[builder(default)]
160    pub grant_type: Option<GrantType>,
161
162    /// The logical name of the target service where the client intends to use the requested security token.
163    #[builder(default)]
164    pub audience: Option<String>,
165
166    /// URI to redirect the client to after authorization is complete.
167    #[builder(default)]
168    pub redirect_uri: Option<String>,
169
170    /// Client nonce to ensure the token is still fresh.
171    #[builder(default)]
172    pub client_nonce: Option<ByteString>,
173
174    /// Scope of the access request as described by section 3.3 of
175    /// [RFC 6749](https://www.rfc-editor.org/rfc/rfc6749).
176    ///
177    /// See also the documentation of [`Scope`] for details.
178    #[builder(default)]
179    pub scope: Option<Scope>,
180
181    /// Included in the request if the AS shall include the `ace_profile` parameter in its
182    /// response.
183    #[builder(setter(custom, strip_option), default = "None")]
184    pub ace_profile: Option<()>,
185
186    /// Contains information about the key the client would like to bind to the
187    /// access token for proof-of-possession.
188    ///
189    /// See also the documentation of [`ProofOfPossessionKey`] for details.
190    #[builder(default)]
191    pub req_cnf: Option<ProofOfPossessionKey>,
192
193    /// Issuer of the token.
194    /// Note that this is only used by libdcaf and not present in the ACE-OAuth specification
195    /// for access token requests.
196    /// Instead, it is usually encoded as a claim in the access token itself.
197    ///
198    /// Defined in [section 3.1.1 of RFC 8392](https://www.rfc-editor.org/rfc/rfc8392#section-3.1.1)
199    /// and [Table 6 of RFC 9200](https://www.rfc-editor.org/rfc/rfc9200#table-6).
200    #[builder(default)]
201    pub issuer: Option<String>,
202}
203
204/// The type of the token issued as described in section 7.1 of
205/// [RFC 6749](https://www.rfc-editor.org/rfc/rfc6749#section-7.1).
206///
207/// Token types are used in the [`AccessTokenResponse`].
208///
209/// # Example
210/// For example, if you wish to indicate in your response that the token is of the
211/// proof-of-possession type:
212/// ```
213/// # use dcaf::{AccessTokenResponse, GrantType, TokenType};
214/// # use dcaf::common::cbor_values::{ByteString, ProofOfPossessionKey};
215/// # use dcaf::endpoints::token_req::AccessTokenResponseBuilderError;
216/// # use dcaf::TokenType::ProofOfPossession;
217/// let request = AccessTokenResponse::builder()
218///     .access_token(vec![1,2,3,4])
219///     .token_type(TokenType::ProofOfPossession)
220///     .cnf(ProofOfPossessionKey::KeyId(vec![0xDC, 0xAF]))
221///     .build()?;
222/// # Ok::<(), AccessTokenResponseBuilderError>(())
223/// ```
224/// It's also possible to use your own value for a custom token type, as defined in
225/// [section 8.7 of RFC 9200](https://www.rfc-editor.org/rfc/rfc9200#section-8.7):
226/// ```
227/// # use dcaf::{AccessTokenResponse, GrantType, TokenType};
228/// # use dcaf::endpoints::token_req::AccessTokenResponseBuilderError;
229/// let request = AccessTokenResponse::builder()
230///     .access_token(vec![1,2,3,4])
231///     // values below -65536 marked for private use.
232///     .token_type(TokenType::Other(-99999))
233///     .build()?;
234/// # Ok::<(), AccessTokenResponseBuilderError>(())
235/// ```
236#[derive(Debug, PartialEq, Eq, Copy, Clone, Hash)]
237#[non_exhaustive]
238pub enum TokenType {
239    /// Bearer token type as defined in [RFC 6750](https://www.rfc-editor.org/rfc/rfc6750).
240    Bearer,
241
242    /// Proof-of-possession token type, as specified in
243    /// [RFC 9201](https://www.rfc-editor.org/rfc/rfc9201).
244    ProofOfPossession,
245
246    /// An unspecified token type along with its representation in CBOR.
247    ///
248    /// See [section 8.7 of RFC 9200](https://www.rfc-editor.org/rfc/rfc9200#section-8.7)
249    /// for details.
250    Other(i32),
251}
252
253/// Profiles for ACE-OAuth as specified in [section 5.8.4.3 of RFC 9200](https://www.rfc-editor.org/rfc/rfc9200#section-5.8.4.3).
254///
255/// ACE-OAuth profiles are used in the [`AccessTokenResponse`] if the client previously sent
256/// an [`AccessTokenRequest`] with the `ace_profile` field set.
257///
258/// There are at the moment two profiles for ACE-OAuth which are not drafts:
259/// - The DTLS profile, specified in [RFC 9202](https://www.rfc-editor.org/rfc/rfc9202).
260/// - The OSCORE profile, defined in [RFC 9203](https://www.rfc-editor.org/rfc/rfc9203).
261///
262/// If you wish to use a different profile, you need to specify a user-defined CBOR integer for it
263/// using the [`Other`](AceProfile::Other) variant.
264///
265/// # Example
266/// For example, if you wish to indicate in your response that the DTLS profile is used:
267/// ```
268/// # use dcaf::{AccessTokenResponse, AceProfile};
269/// # use dcaf::common::cbor_values::{ByteString, ProofOfPossessionKey};
270/// # use dcaf::endpoints::token_req::AccessTokenResponseBuilderError;
271/// # use dcaf::TokenType::ProofOfPossession;
272/// let request = AccessTokenResponse::builder()
273///     .access_token(vec![1,2,3,4])
274///     .ace_profile(AceProfile::CoapDtls)
275///     .build()?;
276/// # Ok::<(), AccessTokenResponseBuilderError>(())
277/// ```
278/// It's also possible to use your own value for a custom profile, as defined in
279/// [section 8.8 of RFC 9200](https://www.rfc-editor.org/rfc/rfc9200#section-8.8):
280/// ```
281/// # use dcaf::{AccessTokenResponse, AceProfile};
282/// # use dcaf::endpoints::token_req::AccessTokenResponseBuilderError;
283/// let request = AccessTokenResponse::builder()
284///     .access_token(vec![1,2,3,4])
285///     // values below -65536 marked for private use.
286///     .ace_profile(AceProfile::Other(-99999))
287///     .build()?;
288/// # Ok::<(), AccessTokenResponseBuilderError>(())
289/// ```
290#[derive(Debug, PartialEq, Eq, Copy, Clone, Hash)]
291#[non_exhaustive]
292pub enum AceProfile {
293    /// Profile for ACE-OAuth using Datagram Transport Layer Security, specified in
294    /// [RFC 9202](https://www.rfc-editor.org/rfc/rfc9202).
295    CoapDtls,
296
297    /// Profile for ACE-OAuth using OSCORE, specified in
298    /// [RFC 9203](https://www.rfc-editor.org/rfc/rfc9203).
299    CoapOscore,
300
301    /// An unspecified ACE-OAuth profile along with its representation in CBOR.
302    ///
303    /// See [section 8.8 of RFC 9200](https://www.rfc-editor.org/rfc/rfc9200#section-8.8)
304    /// for details.
305    Other(i32),
306}
307
308/// Response to an [`AccessTokenRequest`] containing the Access Token among additional information,
309/// as defined in [section 5.8.2 of RFC 9200](https://www.rfc-editor.org/rfc/rfc9200#section-5.8.2).
310///
311/// Use the [`AccessTokenResponseBuilder`] (which you can access using the
312/// [`AccessTokenResponse::builder()`] method) to create an instance of this struct.
313///
314/// # Example
315/// Figure 7 of [RFC 9200](https://www.rfc-editor.org/rfc/rfc9200#figure-7)
316/// gives us an example of an access token response, given in CBOR diagnostic notation[^cbor]:
317/// ```text
318/// {
319///   "access_token" : b64'SlAV32hkKG ...
320///    (remainder of CWT omitted for brevity;
321///    CWT contains COSE_Key in the "cnf" claim)',
322///   "ace_profile" : "coap_dtls",
323///   "expires_in" : "3600",
324///   "cnf" : {
325///     "COSE_Key" : {
326///       "kty" : "Symmetric",
327///       "kid" : b64'39Gqlw',
328///       "k" : b64'hJtXhkV8FJG+Onbc6mxCcQh'
329///     }
330///   }
331/// }
332/// ```
333///
334/// This could be built and serialized as an [`AccessTokenResponse`] like so:
335/// ```
336/// # use std::error::Error;
337/// # use ciborium_io::{Read, Write};
338/// # use coset::CoseKeyBuilder;
339/// # use dcaf::{ToCborMap, AccessTokenResponse, AceProfile};
340/// # use dcaf::endpoints::token_req::AccessTokenResponseBuilderError;
341/// let key = CoseKeyBuilder::new_symmetric_key(
342///    // Omitted for brevity.
343/// #   vec![ 0x84, 0x9b, 0x57, 0x86, 0x45, 0x7c, 0x14, 0x91, 0xbe, 0x3a, 0x76, 0xdc, 0xea, 0x6c,
344/// #         0x42, 0x71, 0x08]
345/// ).key_id(vec![0xDF, 0xD1, 0xAA, 0x97]).build();
346/// let expires_in: u32 = 3600;  // this needs to be done so Rust doesn't think of it as an i32
347/// # #[cfg(feature = "std")] {
348/// let response: AccessTokenResponse = AccessTokenResponse::builder()
349///    .access_token(
350///       // Omitted for brevity, this is a CWT whose `cnf` claim contains
351///       // the COSE_Key used in the `cnf` field from this `AccessTokenResponse`.
352/// # // TODO: Actually have it be that.
353/// # vec![0xDC, 0xAF]
354///    )
355///    .ace_profile(AceProfile::CoapDtls)
356///    .expires_in(expires_in)
357///    .cnf(key)
358///    .build()?;
359/// let mut serialized = Vec::new();
360/// response.clone().serialize_into(&mut serialized)?;
361/// assert_eq!(AccessTokenResponse::deserialize_from(serialized.as_slice())?, response);
362/// # }
363/// # Ok::<(), Box<dyn Error>>(())
364/// ```
365///
366/// [^cbor]: Note that abbreviations aren't used here, so keep in mind that the labels are really
367/// integers instead of strings.
368///
369#[derive(Debug, PartialEq, Default, Clone, Builder)]
370#[builder(
371    no_std,
372    setter(into, strip_option),
373    derive(Debug, PartialEq),
374    build_fn(validate = "Self::validate")
375)]
376pub struct AccessTokenResponse {
377    /// The access token issued by the authorization server.
378    ///
379    /// Must be included.
380    pub access_token: ByteString,
381
382    /// The lifetime in seconds of the access token.
383    #[builder(default)]
384    pub expires_in: Option<u32>,
385
386    /// The scope of the access token as described by
387    /// section 3.3 of [RFC 6749](https://www.rfc-editor.org/rfc/rfc6749#section-3.3).
388    ///
389    /// See the documentation of [`Scope`] for details.
390    #[builder(default)]
391    pub scope: Option<Scope>,
392
393    /// The type of the token issued as described in [section 7.1 of
394    /// RFC 6749](https://www.rfc-editor.org/rfc/rfc6749#section-7.1) and [section 5.8.4.2
395    /// of RFC 9200](https://www.rfc-editor.org/rfc/rfc9200#section-5.8.4.2).
396    ///
397    /// See the documentation of [`TokenType`] for details.
398    #[builder(default)]
399    pub token_type: Option<TokenType>,
400
401    /// The refresh token, which can be used to obtain new access tokens using the same
402    /// authorization grant as described in [section 6 of
403    /// RFC 6749](https://www.rfc-editor.org/rfc/rfc6749#section-6).
404    #[builder(default)]
405    pub refresh_token: Option<ByteString>,
406
407    /// This indicates the profile that the client must use towards the RS.
408    ///
409    /// See the documentation of [`AceProfile`] for details.
410    #[builder(default)]
411    pub ace_profile: Option<AceProfile>,
412
413    /// The proof-of-possession key that the AS selected for the token.
414    ///
415    /// See the documentation of [`ProofOfPossessionKey`] for details.
416    #[builder(default)]
417    pub cnf: Option<ProofOfPossessionKey>,
418
419    /// Information about the public key used by the RS to authenticate.
420    ///
421    /// See the documentation of [`ProofOfPossessionKey`] for details.
422    #[builder(default)]
423    pub rs_cnf: Option<ProofOfPossessionKey>,
424
425    /// Timestamp when the token was issued.
426    /// Note that this is only used by libdcaf and not present in the ACE-OAuth specification
427    /// for access token responses.
428    /// It is instead usually encoded as a claim in the access token itself.
429    ///
430    /// Defined in [section 3.1.6 of RFC 8392](https://www.rfc-editor.org/rfc/rfc8392#section-3.1.6)
431    /// and [table 6 of RFC 9200](https://www.rfc-editor.org/rfc/rfc9200#table-6).
432    #[builder(default)]
433    pub issued_at: Option<coset::cwt::Timestamp>,
434}
435
436/// Error code specifying what went wrong for a token request, as specified in
437/// [section 5.2 of RFC 6749](https://www.rfc-editor.org/rfc/rfc6749#section-5.2) and
438/// [section 5.8.3 of RFC 9200](https://www.rfc-editor.org/rfc/rfc9200#section-5.8.3).
439///
440/// An error code is used in the [`ErrorResponse`].
441///
442/// # Example
443/// For example, if you wish to indicate in your error response that the client is not authorized:
444/// ```
445/// # use dcaf::{ErrorResponse, AceProfile, ErrorCode};
446/// # use dcaf::endpoints::token_req::ErrorResponseBuilderError;
447/// let request = ErrorResponse::builder()
448///     .error(ErrorCode::UnauthorizedClient)
449///     .build()?;
450/// # Ok::<(), ErrorResponseBuilderError>(())
451/// ```
452/// It's also possible to use your own value for a custom error code, as defined in
453/// [section 8.4 of RFC 9200](https://www.rfc-editor.org/rfc/rfc9200#section-8.4):
454/// ```
455/// # use dcaf::{ErrorResponse, AceProfile, ErrorCode};
456/// # use dcaf::endpoints::token_req::ErrorResponseBuilderError;
457/// let request = ErrorResponse::builder()
458///     // Values less than 65536 marked as private use.
459///     .error(ErrorCode::Other(-99999))
460///     .build()?;
461/// # Ok::<(), ErrorResponseBuilderError>(())
462/// ```
463#[derive(Debug, PartialEq, Eq, Copy, Clone, Hash)]
464#[non_exhaustive]
465pub enum ErrorCode {
466    /// The request is missing a required parameter, includes an unsupported parameter value (other
467    /// than grant type), repeats a parameter, includes multiple credentials, utilizes
468    /// more than one mechanism for authenticating the client, or is otherwise malformed.
469    InvalidRequest,
470
471    /// Client authentication failed (e.g., unknown client, no client authentication included, or
472    /// unsupported authentication method)
473    InvalidClient,
474
475    /// The provided authorization grant (e.g., authorization code, resource owner credentials) or
476    /// refresh token is invalid, expired, revoked, does not match the redirection URI used in the
477    /// authorization request, or was issued to another client.
478    InvalidGrant,
479
480    /// The authenticated client is not authorized to use this authorization grant type.
481    UnauthorizedClient,
482
483    /// The authorization grant type is not supported by the authorization server.
484    UnsupportedGrantType,
485
486    /// The authorization grant type is not supported by the authorization server.
487    InvalidScope,
488
489    /// The client submitted an asymmetric key in the token request that the RS cannot process.
490    UnsupportedPopKey,
491
492    /// The client and the RS it has requested an access token for do not share a common profile.
493    IncompatibleAceProfiles,
494
495    /// An unspecified error code along with its representation in CBOR.
496    ///
497    /// See [section 8.4 of RFC 9200](https://www.rfc-editor.org/rfc/rfc9200#section-8.4)
498    /// for details.
499    Other(i32),
500}
501
502/// Details about an error which occurred for an access token request.
503///
504/// For more information, see [section 5.8.3 of RFC 9200](https://www.rfc-editor.org/rfc/rfc9200#section-5.8.3).
505///
506/// Use the [`ErrorResponseBuilder`] (which you can access using the
507/// [`ErrorResponse::builder()`] method) to create an instance of this struct.
508///
509/// # Example
510/// For example, let us use the example from [section 5.2 of RFC 6749](https://www.rfc-editor.org/rfc/rfc6749#section-5.2):
511/// ```text
512/// {
513///       "error":"invalid_request"
514/// }
515///
516/// ```
517/// Creating and serializing a simple error response telling the client their request was invalid
518/// would look like the following:
519/// ```
520/// # use std::error::Error;
521/// # use ciborium_io::{Read, Write};
522/// # use dcaf::{ToCborMap, ErrorCode, ErrorResponse};
523/// # use dcaf::endpoints::token_req::ErrorResponseBuilderError;
524/// # #[cfg(feature = "std")] {
525/// let error: ErrorResponse = ErrorResponse::builder()
526///     .error(ErrorCode::InvalidRequest)
527///     .build()?;
528/// let mut serialized = Vec::new();
529/// error.clone().serialize_into(&mut serialized)?;
530/// assert_eq!(ErrorResponse::deserialize_from(serialized.as_slice())?, error);
531/// # }
532/// # Ok::<(), Box<dyn Error>>(())
533/// ```
534///
535/// [^cbor]: Note that abbreviations aren't used here, so keep in mind that the labels are really
536/// integers instead of strings.
537#[derive(Debug, PartialEq, Eq, Hash, Clone, Builder)]
538#[builder(
539    no_std,
540    setter(into, strip_option),
541    derive(Debug, PartialEq),
542    build_fn(validate = "Self::validate")
543)]
544pub struct ErrorResponse {
545    /// Error code for this error.
546    ///
547    /// Must be included.
548    ///
549    /// See the documentation of [`ErrorCode`] for details.
550    pub error: ErrorCode,
551
552    /// Human-readable ASCII text providing additional information, used to assist the
553    /// client developer in understanding the error that occurred.
554    #[builder(default)]
555    pub description: Option<String>,
556
557    /// A URI identifying a human-readable web page with information about the error, used to
558    /// provide the client developer with additional information about the error.
559    #[builder(default)]
560    pub uri: Option<String>,
561}
562
563impl AccessTokenRequest {
564    /// Initializes and returns a new [`AccessTokenRequestBuilder`].
565    #[must_use]
566    pub fn builder() -> AccessTokenRequestBuilder {
567        AccessTokenRequestBuilder::default()
568    }
569}
570
571#[allow(clippy::unused_self, clippy::unnecessary_wraps)]
572mod builder {
573    use super::*;
574
575    impl AccessTokenRequestBuilder {
576        pub(crate) fn validate(&self) -> Result<(), AccessTokenRequestBuilderError> {
577            // TODO: Check whether there are invariants to validate
578            Ok(())
579        }
580
581        /// Sets the [`ace_profile`](AccessTokenRequest::ace_profile) field to an empty value,
582        /// which indicates a request for the Authorization Server to respond with the
583        /// `ace_profile` field in the response.
584        pub fn ace_profile(&mut self) -> &mut Self {
585            self.ace_profile = Some(Some(()));
586            self
587        }
588    }
589
590    impl AccessTokenResponse {
591        /// Initializes and returns a new [`AccessTokenResponseBuilder`].
592        #[must_use]
593        pub fn builder() -> AccessTokenResponseBuilder {
594            AccessTokenResponseBuilder::default()
595        }
596    }
597
598    impl AccessTokenResponseBuilder {
599        pub(crate) fn validate(&self) -> Result<(), AccessTokenResponseBuilderError> {
600            // TODO: Check whether there are invariants to validate
601            Ok(())
602        }
603    }
604
605    impl ErrorResponse {
606        /// Initializes and returns a new [`ErrorResponseBuilder`].
607        #[must_use]
608        pub fn builder() -> ErrorResponseBuilder {
609            ErrorResponseBuilder::default()
610        }
611    }
612
613    impl ErrorResponseBuilder {
614        pub(crate) fn validate(&self) -> Result<(), ErrorResponseBuilderError> {
615            // TODO: Check whether there are invariants to validate
616            Ok(())
617        }
618    }
619}
620
621mod conversion {
622    use ciborium::value::Value;
623    use coset::cwt::Timestamp;
624    use erased_serde::Serialize as ErasedSerialize;
625
626    use crate::common::cbor_map::{
627        cbor_map_vec, decode_int_map, decode_number, decode_scope, ToCborMap,
628    };
629    use crate::common::cbor_values::{CborMapValue, ProofOfPossessionKey};
630    use crate::constants::cbor_abbreviations::{
631        ace_profile, error, grant_types, introspection, token, token_types,
632    };
633
634    #[cfg(not(feature = "std"))]
635    use {alloc::borrow::ToOwned, alloc::string::ToString};
636
637    use crate::endpoints::token_req::AceProfile::{CoapDtls, CoapOscore};
638    use crate::error::TryFromCborMapError;
639
640    use super::*;
641
642    impl From<i32> for GrantType {
643        fn from(value: i32) -> Self {
644            match value {
645                grant_types::PASSWORD => GrantType::Password,
646                grant_types::AUTHORIZATION_CODE => GrantType::AuthorizationCode,
647                grant_types::CLIENT_CREDENTIALS => GrantType::ClientCredentials,
648                grant_types::REFRESH_TOKEN => GrantType::RefreshToken,
649                x => GrantType::Other(x),
650            }
651        }
652    }
653
654    impl From<GrantType> for i32 {
655        fn from(grant: GrantType) -> Self {
656            match grant {
657                GrantType::Password => grant_types::PASSWORD,
658                GrantType::AuthorizationCode => grant_types::AUTHORIZATION_CODE,
659                GrantType::ClientCredentials => grant_types::CLIENT_CREDENTIALS,
660                GrantType::RefreshToken => grant_types::REFRESH_TOKEN,
661                GrantType::Other(x) => x.to_owned(),
662            }
663        }
664    }
665
666    impl From<i32> for TokenType {
667        fn from(value: i32) -> Self {
668            match value {
669                token_types::BEARER => TokenType::Bearer,
670                token_types::POP => TokenType::ProofOfPossession,
671                x => TokenType::Other(x),
672            }
673        }
674    }
675
676    impl From<TokenType> for i32 {
677        fn from(token: TokenType) -> Self {
678            match token {
679                TokenType::Bearer => token_types::BEARER,
680                TokenType::ProofOfPossession => token_types::POP,
681                TokenType::Other(x) => x,
682            }
683        }
684    }
685
686    impl From<i32> for AceProfile {
687        fn from(value: i32) -> Self {
688            match value {
689                ace_profile::COAP_DTLS => CoapDtls,
690                ace_profile::COAP_OSCORE => CoapOscore,
691                x => AceProfile::Other(x),
692            }
693        }
694    }
695
696    impl From<AceProfile> for i32 {
697        fn from(profile: AceProfile) -> Self {
698            match profile {
699                CoapDtls => ace_profile::COAP_DTLS,
700                CoapOscore => ace_profile::COAP_OSCORE,
701                AceProfile::Other(x) => x,
702            }
703        }
704    }
705
706    impl From<i32> for ErrorCode {
707        fn from(value: i32) -> Self {
708            match value {
709                error::INVALID_REQUEST => ErrorCode::InvalidRequest,
710                error::INVALID_CLIENT => ErrorCode::InvalidClient,
711                error::INVALID_GRANT => ErrorCode::InvalidGrant,
712                error::UNAUTHORIZED_CLIENT => ErrorCode::UnauthorizedClient,
713                error::UNSUPPORTED_GRANT_TYPE => ErrorCode::UnsupportedGrantType,
714                error::INVALID_SCOPE => ErrorCode::InvalidScope,
715                error::UNSUPPORTED_POP_KEY => ErrorCode::UnsupportedPopKey,
716                error::INCOMPATIBLE_ACE_PROFILES => ErrorCode::IncompatibleAceProfiles,
717                x => ErrorCode::Other(x),
718            }
719        }
720    }
721
722    impl From<ErrorCode> for i32 {
723        fn from(code: ErrorCode) -> Self {
724            match code {
725                ErrorCode::InvalidRequest => error::INVALID_REQUEST,
726                ErrorCode::InvalidClient => error::INVALID_CLIENT,
727                ErrorCode::InvalidGrant => error::INVALID_GRANT,
728                ErrorCode::UnauthorizedClient => error::UNAUTHORIZED_CLIENT,
729                ErrorCode::UnsupportedGrantType => error::UNSUPPORTED_GRANT_TYPE,
730                ErrorCode::InvalidScope => error::INVALID_SCOPE,
731                ErrorCode::UnsupportedPopKey => error::UNSUPPORTED_POP_KEY,
732                ErrorCode::IncompatibleAceProfiles => error::INCOMPATIBLE_ACE_PROFILES,
733                ErrorCode::Other(x) => x,
734            }
735        }
736    }
737
738    impl ToCborMap for AccessTokenRequest {
739        fn to_cbor_map(&self) -> Vec<(i128, Option<Box<dyn ErasedSerialize + '_>>)> {
740            let grant_type: Option<CborMapValue<GrantType>> = self.grant_type.map(CborMapValue);
741            cbor_map_vec! {
742                introspection::ISSUER => self.issuer.as_ref(),
743                token::REQ_CNF => self.req_cnf.as_ref().map(ToCborMap::to_ciborium_value),
744                token::AUDIENCE => self.audience.as_ref(),
745                token::SCOPE => self.scope.as_ref(),
746                token::CLIENT_ID => self.client_id.as_ref(),
747                token::REDIRECT_URI => self.redirect_uri.as_ref(),
748                token::GRANT_TYPE => grant_type,
749                token::ACE_PROFILE => self.ace_profile.as_ref(),
750                token::CNONCE => self.client_nonce.as_ref().map(|v| Value::Bytes(v.clone()))
751            }
752        }
753
754        fn try_from_cbor_map(map: Vec<(i128, Value)>) -> Result<Self, TryFromCborMapError>
755        where
756            Self: Sized + ToCborMap,
757        {
758            let mut request = AccessTokenRequest::builder();
759            for entry in map {
760                match (u8::try_from(entry.0)?, entry.1) {
761                    (token::REQ_CNF, Value::Map(x)) => {
762                        request.req_cnf(ProofOfPossessionKey::try_from_cbor_map(decode_int_map::<
763                            Self,
764                        >(
765                            x, "req_cnf"
766                        )?)?)
767                    }
768                    (token::AUDIENCE, Value::Text(x)) => request.audience(x),
769                    (token::SCOPE, v) => request.scope(decode_scope(v)?),
770                    (token::CLIENT_ID, Value::Text(x)) => request.client_id(x),
771                    (token::REDIRECT_URI, Value::Text(x)) => request.redirect_uri(x),
772                    (token::GRANT_TYPE, Value::Integer(x)) => {
773                        request.grant_type(GrantType::from(decode_number::<i32>(x, "grant_type")?))
774                    }
775                    (token::ACE_PROFILE, Value::Null) => request.ace_profile(),
776                    (token::CNONCE, Value::Bytes(x)) => request.client_nonce(x),
777                    (introspection::ISSUER, Value::Text(x)) => request.issuer(x),
778                    (key, _) => return Err(TryFromCborMapError::unknown_field(key)),
779                };
780            }
781            request
782                .build()
783                .map_err(|x| TryFromCborMapError::build_failed("AccessTokenRequest", x))
784        }
785    }
786
787    impl ToCborMap for AccessTokenResponse {
788        fn to_cbor_map(&self) -> Vec<(i128, Option<Box<dyn ErasedSerialize + '_>>)> {
789            let token_type: Option<CborMapValue<TokenType>> = self.token_type.map(CborMapValue);
790            let ace_profile: Option<CborMapValue<AceProfile>> = self.ace_profile.map(CborMapValue);
791            cbor_map_vec! {
792                token::ACCESS_TOKEN => Some(Value::Bytes(self.access_token.clone())),
793                token::EXPIRES_IN => self.expires_in,
794                introspection::ISSUED_AT => self.issued_at.as_ref().map(|x| x.clone().to_cbor_value().expect("serialization of issued_at failed")),
795                token::CNF => self.cnf.as_ref().map(ToCborMap::to_ciborium_value),
796                token::SCOPE => self.scope.as_ref(),
797                token::TOKEN_TYPE => token_type,
798                token::REFRESH_TOKEN => self.refresh_token.as_ref().map(|v| Value::Bytes(v.clone())),
799                token::ACE_PROFILE => ace_profile,
800                token::RS_CNF => self.rs_cnf.as_ref().map(ToCborMap::to_ciborium_value)
801            }
802        }
803
804        fn try_from_cbor_map(map: Vec<(i128, Value)>) -> Result<Self, TryFromCborMapError>
805        where
806            Self: Sized + ToCborMap,
807        {
808            let mut response = AccessTokenResponse::builder();
809            for entry in map {
810                match (u8::try_from(entry.0)?, entry.1) {
811                    (token::ACCESS_TOKEN, Value::Bytes(x)) => response.access_token(x),
812                    (token::EXPIRES_IN, Value::Integer(x)) => {
813                        response.expires_in(decode_number::<u32>(x, "expires_in")?)
814                    }
815                    (introspection::ISSUED_AT, v) => response.issued_at(
816                        Timestamp::from_cbor_value(v)
817                            .map_err(|x| TryFromCborMapError::from_message(x.to_string()))?,
818                    ),
819                    (token::CNF, Value::Map(x)) => {
820                        response.cnf(ProofOfPossessionKey::try_from_cbor_map(decode_int_map::<
821                            Self,
822                        >(
823                            x, "cnf"
824                        )?)?)
825                    }
826                    (token::SCOPE, v) => response.scope(decode_scope(v)?),
827                    (token::TOKEN_TYPE, Value::Integer(x)) => {
828                        response.token_type(TokenType::from(decode_number::<i32>(x, "token_type")?))
829                    }
830                    (token::REFRESH_TOKEN, Value::Bytes(x)) => response.refresh_token(x),
831                    (token::ACE_PROFILE, Value::Integer(x)) => response
832                        .ace_profile(AceProfile::from(decode_number::<i32>(x, "ace_profile")?)),
833                    (token::RS_CNF, Value::Map(x)) => {
834                        response.rs_cnf(ProofOfPossessionKey::try_from_cbor_map(decode_int_map::<
835                            Self,
836                        >(
837                            x, "rs_cnf"
838                        )?)?)
839                    }
840                    (key, _) => return Err(TryFromCborMapError::unknown_field(key)),
841                };
842            }
843            response
844                .build()
845                .map_err(|x| TryFromCborMapError::build_failed("AccessTokenResponse", x))
846        }
847    }
848
849    impl ToCborMap for ErrorResponse {
850        fn to_cbor_map(&self) -> Vec<(i128, Option<Box<dyn ErasedSerialize + '_>>)> {
851            let error = CborMapValue(self.error);
852            cbor_map_vec! {
853                token::ERROR => Some(error),
854                token::ERROR_DESCRIPTION => self.description.as_ref(),
855                token::ERROR_URI => self.uri.as_ref()
856            }
857        }
858
859        fn try_from_cbor_map(map: Vec<(i128, Value)>) -> Result<Self, TryFromCborMapError>
860        where
861            Self: Sized + ToCborMap,
862        {
863            let mut error = ErrorResponse::builder();
864            for entry in map {
865                match (u8::try_from(entry.0)?, entry.1) {
866                    (token::ERROR, Value::Integer(x)) => {
867                        error.error(ErrorCode::from(decode_number::<i32>(x, "error")?))
868                    }
869                    (token::ERROR_URI, Value::Text(x)) => error.uri(x),
870                    (token::ERROR_DESCRIPTION, Value::Text(x)) => error.description(x),
871                    (key, _) => return Err(TryFromCborMapError::unknown_field(key)),
872                };
873            }
874            error
875                .build()
876                .map_err(|x| TryFromCborMapError::build_failed("ErrorResponse", x))
877        }
878    }
879}