use coset::AsCborValue;
use crate::common::cbor_values::{ByteString, ProofOfPossessionKey};
use crate::Scope;
#[cfg(not(feature = "std"))]
use {alloc::boxed::Box, alloc::string::String, alloc::vec::Vec};
#[cfg(test)]
mod tests;
#[derive(Debug, PartialEq, Eq, Copy, Clone, Hash)]
#[non_exhaustive]
pub enum GrantType {
Password,
AuthorizationCode,
ClientCredentials,
RefreshToken,
Other(i32),
}
#[derive(Debug, Default, PartialEq, Clone, Builder)]
#[builder(
no_std,
setter(into, strip_option),
derive(Debug, PartialEq),
build_fn(validate = "Self::validate")
)]
pub struct AccessTokenRequest {
#[builder(default)]
pub client_id: Option<String>,
#[builder(default)]
pub grant_type: Option<GrantType>,
#[builder(default)]
pub audience: Option<String>,
#[builder(default)]
pub redirect_uri: Option<String>,
#[builder(default)]
pub client_nonce: Option<ByteString>,
#[builder(default)]
pub scope: Option<Scope>,
#[builder(setter(custom, strip_option), default = "None")]
pub ace_profile: Option<()>,
#[builder(default)]
pub req_cnf: Option<ProofOfPossessionKey>,
#[builder(default)]
pub issuer: Option<String>,
}
#[derive(Debug, PartialEq, Eq, Copy, Clone, Hash)]
#[non_exhaustive]
pub enum TokenType {
Bearer,
ProofOfPossession,
Other(i32),
}
#[derive(Debug, PartialEq, Eq, Copy, Clone, Hash)]
#[non_exhaustive]
pub enum AceProfile {
CoapDtls,
CoapOscore,
Other(i32),
}
#[derive(Debug, PartialEq, Default, Clone, Builder)]
#[builder(
no_std,
setter(into, strip_option),
derive(Debug, PartialEq),
build_fn(validate = "Self::validate")
)]
pub struct AccessTokenResponse {
pub access_token: ByteString,
#[builder(default)]
pub expires_in: Option<u32>,
#[builder(default)]
pub scope: Option<Scope>,
#[builder(default)]
pub token_type: Option<TokenType>,
#[builder(default)]
pub refresh_token: Option<ByteString>,
#[builder(default)]
pub ace_profile: Option<AceProfile>,
#[builder(default)]
pub cnf: Option<ProofOfPossessionKey>,
#[builder(default)]
pub rs_cnf: Option<ProofOfPossessionKey>,
#[builder(default)]
pub issued_at: Option<coset::cwt::Timestamp>,
}
#[derive(Debug, PartialEq, Eq, Copy, Clone, Hash)]
#[non_exhaustive]
pub enum ErrorCode {
InvalidRequest,
InvalidClient,
InvalidGrant,
UnauthorizedClient,
UnsupportedGrantType,
InvalidScope,
UnsupportedPopKey,
IncompatibleAceProfiles,
Other(i32),
}
#[derive(Debug, PartialEq, Eq, Hash, Clone, Builder)]
#[builder(
no_std,
setter(into, strip_option),
derive(Debug, PartialEq),
build_fn(validate = "Self::validate")
)]
pub struct ErrorResponse {
pub error: ErrorCode,
#[builder(default)]
pub description: Option<String>,
#[builder(default)]
pub uri: Option<String>,
}
impl AccessTokenRequest {
#[must_use]
pub fn builder() -> AccessTokenRequestBuilder {
AccessTokenRequestBuilder::default()
}
}
#[allow(clippy::unused_self, clippy::unnecessary_wraps)]
mod builder {
use super::*;
impl AccessTokenRequestBuilder {
pub(crate) fn validate(&self) -> Result<(), AccessTokenRequestBuilderError> {
Ok(())
}
pub fn ace_profile(&mut self) -> &mut Self {
self.ace_profile = Some(Some(()));
self
}
}
impl AccessTokenResponse {
#[must_use]
pub fn builder() -> AccessTokenResponseBuilder {
AccessTokenResponseBuilder::default()
}
}
impl AccessTokenResponseBuilder {
pub(crate) fn validate(&self) -> Result<(), AccessTokenResponseBuilderError> {
Ok(())
}
}
impl ErrorResponse {
#[must_use]
pub fn builder() -> ErrorResponseBuilder {
ErrorResponseBuilder::default()
}
}
impl ErrorResponseBuilder {
pub(crate) fn validate(&self) -> Result<(), ErrorResponseBuilderError> {
Ok(())
}
}
}
mod conversion {
use ciborium::value::Value;
use coset::cwt::Timestamp;
use erased_serde::Serialize as ErasedSerialize;
use crate::common::cbor_map::{
cbor_map_vec, decode_int_map, decode_number, decode_scope, ToCborMap,
};
use crate::common::cbor_values::{CborMapValue, ProofOfPossessionKey};
use crate::constants::cbor_abbreviations::{
ace_profile, error, grant_types, introspection, token, token_types,
};
#[cfg(not(feature = "std"))]
use {alloc::borrow::ToOwned, alloc::string::ToString};
use crate::endpoints::token_req::AceProfile::{CoapDtls, CoapOscore};
use crate::error::TryFromCborMapError;
use super::*;
impl From<i32> for GrantType {
fn from(value: i32) -> Self {
match value {
grant_types::PASSWORD => GrantType::Password,
grant_types::AUTHORIZATION_CODE => GrantType::AuthorizationCode,
grant_types::CLIENT_CREDENTIALS => GrantType::ClientCredentials,
grant_types::REFRESH_TOKEN => GrantType::RefreshToken,
x => GrantType::Other(x),
}
}
}
impl From<GrantType> for i32 {
fn from(grant: GrantType) -> Self {
match grant {
GrantType::Password => grant_types::PASSWORD,
GrantType::AuthorizationCode => grant_types::AUTHORIZATION_CODE,
GrantType::ClientCredentials => grant_types::CLIENT_CREDENTIALS,
GrantType::RefreshToken => grant_types::REFRESH_TOKEN,
GrantType::Other(x) => x.to_owned(),
}
}
}
impl From<i32> for TokenType {
fn from(value: i32) -> Self {
match value {
token_types::BEARER => TokenType::Bearer,
token_types::POP => TokenType::ProofOfPossession,
x => TokenType::Other(x),
}
}
}
impl From<TokenType> for i32 {
fn from(token: TokenType) -> Self {
match token {
TokenType::Bearer => token_types::BEARER,
TokenType::ProofOfPossession => token_types::POP,
TokenType::Other(x) => x,
}
}
}
impl From<i32> for AceProfile {
fn from(value: i32) -> Self {
match value {
ace_profile::COAP_DTLS => CoapDtls,
ace_profile::COAP_OSCORE => CoapOscore,
x => AceProfile::Other(x),
}
}
}
impl From<AceProfile> for i32 {
fn from(profile: AceProfile) -> Self {
match profile {
CoapDtls => ace_profile::COAP_DTLS,
CoapOscore => ace_profile::COAP_OSCORE,
AceProfile::Other(x) => x,
}
}
}
impl From<i32> for ErrorCode {
fn from(value: i32) -> Self {
match value {
error::INVALID_REQUEST => ErrorCode::InvalidRequest,
error::INVALID_CLIENT => ErrorCode::InvalidClient,
error::INVALID_GRANT => ErrorCode::InvalidGrant,
error::UNAUTHORIZED_CLIENT => ErrorCode::UnauthorizedClient,
error::UNSUPPORTED_GRANT_TYPE => ErrorCode::UnsupportedGrantType,
error::INVALID_SCOPE => ErrorCode::InvalidScope,
error::UNSUPPORTED_POP_KEY => ErrorCode::UnsupportedPopKey,
error::INCOMPATIBLE_ACE_PROFILES => ErrorCode::IncompatibleAceProfiles,
x => ErrorCode::Other(x),
}
}
}
impl From<ErrorCode> for i32 {
fn from(code: ErrorCode) -> Self {
match code {
ErrorCode::InvalidRequest => error::INVALID_REQUEST,
ErrorCode::InvalidClient => error::INVALID_CLIENT,
ErrorCode::InvalidGrant => error::INVALID_GRANT,
ErrorCode::UnauthorizedClient => error::UNAUTHORIZED_CLIENT,
ErrorCode::UnsupportedGrantType => error::UNSUPPORTED_GRANT_TYPE,
ErrorCode::InvalidScope => error::INVALID_SCOPE,
ErrorCode::UnsupportedPopKey => error::UNSUPPORTED_POP_KEY,
ErrorCode::IncompatibleAceProfiles => error::INCOMPATIBLE_ACE_PROFILES,
ErrorCode::Other(x) => x,
}
}
}
impl ToCborMap for AccessTokenRequest {
fn to_cbor_map(&self) -> Vec<(i128, Option<Box<dyn ErasedSerialize + '_>>)> {
let grant_type: Option<CborMapValue<GrantType>> = self.grant_type.map(CborMapValue);
cbor_map_vec! {
introspection::ISSUER => self.issuer.as_ref(),
token::REQ_CNF => self.req_cnf.as_ref().map(ToCborMap::to_ciborium_value),
token::AUDIENCE => self.audience.as_ref(),
token::SCOPE => self.scope.as_ref(),
token::CLIENT_ID => self.client_id.as_ref(),
token::REDIRECT_URI => self.redirect_uri.as_ref(),
token::GRANT_TYPE => grant_type,
token::ACE_PROFILE => self.ace_profile.as_ref(),
token::CNONCE => self.client_nonce.as_ref().map(|v| Value::Bytes(v.clone()))
}
}
fn try_from_cbor_map(map: Vec<(i128, Value)>) -> Result<Self, TryFromCborMapError>
where
Self: Sized + ToCborMap,
{
let mut request = AccessTokenRequest::builder();
for entry in map {
match (u8::try_from(entry.0)?, entry.1) {
(token::REQ_CNF, Value::Map(x)) => {
request.req_cnf(ProofOfPossessionKey::try_from_cbor_map(decode_int_map::<
Self,
>(
x, "req_cnf"
)?)?)
}
(token::AUDIENCE, Value::Text(x)) => request.audience(x),
(token::SCOPE, v) => request.scope(decode_scope(v)?),
(token::CLIENT_ID, Value::Text(x)) => request.client_id(x),
(token::REDIRECT_URI, Value::Text(x)) => request.redirect_uri(x),
(token::GRANT_TYPE, Value::Integer(x)) => {
request.grant_type(GrantType::from(decode_number::<i32>(x, "grant_type")?))
}
(token::ACE_PROFILE, Value::Null) => request.ace_profile(),
(token::CNONCE, Value::Bytes(x)) => request.client_nonce(x),
(introspection::ISSUER, Value::Text(x)) => request.issuer(x),
(key, _) => return Err(TryFromCborMapError::unknown_field(key)),
};
}
request
.build()
.map_err(|x| TryFromCborMapError::build_failed("AccessTokenRequest", x))
}
}
impl ToCborMap for AccessTokenResponse {
fn to_cbor_map(&self) -> Vec<(i128, Option<Box<dyn ErasedSerialize + '_>>)> {
let token_type: Option<CborMapValue<TokenType>> = self.token_type.map(CborMapValue);
let ace_profile: Option<CborMapValue<AceProfile>> = self.ace_profile.map(CborMapValue);
cbor_map_vec! {
token::ACCESS_TOKEN => Some(Value::Bytes(self.access_token.clone())),
token::EXPIRES_IN => self.expires_in,
introspection::ISSUED_AT => self.issued_at.as_ref().map(|x| x.clone().to_cbor_value().expect("serialization of issued_at failed")),
token::CNF => self.cnf.as_ref().map(ToCborMap::to_ciborium_value),
token::SCOPE => self.scope.as_ref(),
token::TOKEN_TYPE => token_type,
token::REFRESH_TOKEN => self.refresh_token.as_ref().map(|v| Value::Bytes(v.clone())),
token::ACE_PROFILE => ace_profile,
token::RS_CNF => self.rs_cnf.as_ref().map(ToCborMap::to_ciborium_value)
}
}
fn try_from_cbor_map(map: Vec<(i128, Value)>) -> Result<Self, TryFromCborMapError>
where
Self: Sized + ToCborMap,
{
let mut response = AccessTokenResponse::builder();
for entry in map {
match (u8::try_from(entry.0)?, entry.1) {
(token::ACCESS_TOKEN, Value::Bytes(x)) => response.access_token(x),
(token::EXPIRES_IN, Value::Integer(x)) => {
response.expires_in(decode_number::<u32>(x, "expires_in")?)
}
(introspection::ISSUED_AT, v) => response.issued_at(
Timestamp::from_cbor_value(v)
.map_err(|x| TryFromCborMapError::from_message(x.to_string()))?,
),
(token::CNF, Value::Map(x)) => {
response.cnf(ProofOfPossessionKey::try_from_cbor_map(decode_int_map::<
Self,
>(
x, "cnf"
)?)?)
}
(token::SCOPE, v) => response.scope(decode_scope(v)?),
(token::TOKEN_TYPE, Value::Integer(x)) => {
response.token_type(TokenType::from(decode_number::<i32>(x, "token_type")?))
}
(token::REFRESH_TOKEN, Value::Bytes(x)) => response.refresh_token(x),
(token::ACE_PROFILE, Value::Integer(x)) => response
.ace_profile(AceProfile::from(decode_number::<i32>(x, "ace_profile")?)),
(token::RS_CNF, Value::Map(x)) => {
response.rs_cnf(ProofOfPossessionKey::try_from_cbor_map(decode_int_map::<
Self,
>(
x, "rs_cnf"
)?)?)
}
(key, _) => return Err(TryFromCborMapError::unknown_field(key)),
};
}
response
.build()
.map_err(|x| TryFromCborMapError::build_failed("AccessTokenResponse", x))
}
}
impl ToCborMap for ErrorResponse {
fn to_cbor_map(&self) -> Vec<(i128, Option<Box<dyn ErasedSerialize + '_>>)> {
let error = CborMapValue(self.error);
cbor_map_vec! {
token::ERROR => Some(error),
token::ERROR_DESCRIPTION => self.description.as_ref(),
token::ERROR_URI => self.uri.as_ref()
}
}
fn try_from_cbor_map(map: Vec<(i128, Value)>) -> Result<Self, TryFromCborMapError>
where
Self: Sized + ToCborMap,
{
let mut error = ErrorResponse::builder();
for entry in map {
match (u8::try_from(entry.0)?, entry.1) {
(token::ERROR, Value::Integer(x)) => {
error.error(ErrorCode::from(decode_number::<i32>(x, "error")?))
}
(token::ERROR_URI, Value::Text(x)) => error.uri(x),
(token::ERROR_DESCRIPTION, Value::Text(x)) => error.description(x),
(key, _) => return Err(TryFromCborMapError::unknown_field(key)),
};
}
error
.build()
.map_err(|x| TryFromCborMapError::build_failed("ErrorResponse", x))
}
}
}