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}