diff --git a/Cargo.lock b/Cargo.lock index 917de7f6..80ec74f1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1184,37 +1184,14 @@ dependencies = [ "cipher 0.3.0", ] -[[package]] -name = "darling" -version = "0.13.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a01d95850c592940db9b8194bc39f4bc0e89dee5c4265e4b1807c34a9aba453c" -dependencies = [ - "darling_core 0.13.4", - "darling_macro 0.13.4", -] - [[package]] name = "darling" version = "0.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4529658bdda7fd6769b8614be250cdcfc3aeb0ee72fe66f9e41e5e5eb73eac02" dependencies = [ - "darling_core 0.14.1", - "darling_macro 0.14.1", -] - -[[package]] -name = "darling_core" -version = "0.13.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "859d65a907b6852c9361e3185c862aae7fafd2887876799fa55f5f99dc40d610" -dependencies = [ - "fnv", - "ident_case", - "proc-macro2", - "quote", - "syn", + "darling_core", + "darling_macro", ] [[package]] @@ -1231,24 +1208,13 @@ dependencies = [ "syn", ] -[[package]] -name = "darling_macro" -version = "0.13.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c972679f83bdf9c42bd905396b6c3588a843a17f0f16dfcfa3e2c5d57441835" -dependencies = [ - "darling_core 0.13.4", - "quote", - "syn", -] - [[package]] name = "darling_macro" version = "0.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ddfc69c5bfcbd2fc09a0f38451d2daf0e372e367986a83906d1b0dbc88134fb5" dependencies = [ - "darling_core 0.14.1", + "darling_core", "quote", "syn", ] @@ -2912,7 +2878,6 @@ dependencies = [ "mas-jose", "parse-display", "serde", - "serde-enum-str", "serde_json", "serde_with", "sha2 0.10.5", @@ -4062,36 +4027,6 @@ dependencies = [ "serde_derive", ] -[[package]] -name = "serde-attributes" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3aba2af3c3b9cd6f3a919056dac6005b71fceecc1cdfa65c4df3912f64e07e60" -dependencies = [ - "darling_core 0.13.4", - "serde-rename-rule", - "syn", -] - -[[package]] -name = "serde-enum-str" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2a41bf2fc78a58589b9a6948bfc918c9b2dc918732f2ac14eed982ffb876b39" -dependencies = [ - "darling 0.13.4", - "proc-macro2", - "quote", - "serde-attributes", - "syn", -] - -[[package]] -name = "serde-rename-rule" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd2930103714ccef4f1fe5b6a5f2b6fdcfe462a6c802464714bd41e5b5097c33" - [[package]] name = "serde_bser" version = "0.3.1" @@ -4172,7 +4107,7 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "de337f322382fcdfbb21a014f7c224ee041a23785651db67b9827403178f698f" dependencies = [ - "darling 0.14.1", + "darling", "proc-macro2", "quote", "syn", diff --git a/crates/oauth2-types/Cargo.toml b/crates/oauth2-types/Cargo.toml index 40a10bec..4f1dcf73 100644 --- a/crates/oauth2-types/Cargo.toml +++ b/crates/oauth2-types/Cargo.toml @@ -19,7 +19,6 @@ sha2 = "0.10.5" data-encoding = "2.3.2" thiserror = "1.0.34" itertools = "0.10.3" -serde-enum-str = "0.2.5" mas-iana = { path = "../iana" } mas-jose = { path = "../jose" } diff --git a/crates/oauth2-types/src/errors.rs b/crates/oauth2-types/src/errors.rs index 0d39daa7..c14179c4 100644 --- a/crates/oauth2-types/src/errors.rs +++ b/crates/oauth2-types/src/errors.rs @@ -14,8 +14,9 @@ use std::borrow::Cow; +use parse_display::{Display, FromStr}; use serde::{Deserialize, Serialize}; -use serde_enum_str::{Deserialize_enum_str, Serialize_enum_str}; +use serde_with::{DeserializeFromStr, SerializeDisplay}; /// A client error returned by an authorization server. /// @@ -55,8 +56,8 @@ impl From for ClientError { } } -#[derive(Debug, Clone, PartialEq, Eq, Serialize_enum_str, Deserialize_enum_str)] -#[serde(rename_all = "snake_case")] +#[derive(Debug, Clone, PartialEq, Eq, Display, FromStr, SerializeDisplay, DeserializeFromStr)] +#[display(style = "snake_case")] pub enum ClientErrorCode { /// `invalid_request` /// @@ -225,7 +226,7 @@ pub enum ClientErrorCode { InvalidClientMetadata, /// Another error code. - #[serde(other)] + #[display("{0}")] Unknown(String), } @@ -307,3 +308,195 @@ impl ClientErrorCode { } } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn serialize_client_error_code() { + assert_eq!( + serde_json::to_string(&ClientErrorCode::InvalidRequest).unwrap(), + "\"invalid_request\"" + ); + assert_eq!( + serde_json::to_string(&ClientErrorCode::InvalidClient).unwrap(), + "\"invalid_client\"" + ); + assert_eq!( + serde_json::to_string(&ClientErrorCode::InvalidGrant).unwrap(), + "\"invalid_grant\"" + ); + assert_eq!( + serde_json::to_string(&ClientErrorCode::UnauthorizedClient).unwrap(), + "\"unauthorized_client\"" + ); + assert_eq!( + serde_json::to_string(&ClientErrorCode::UnsupportedGrantType).unwrap(), + "\"unsupported_grant_type\"" + ); + assert_eq!( + serde_json::to_string(&ClientErrorCode::AccessDenied).unwrap(), + "\"access_denied\"" + ); + assert_eq!( + serde_json::to_string(&ClientErrorCode::UnsupportedResponseType).unwrap(), + "\"unsupported_response_type\"" + ); + assert_eq!( + serde_json::to_string(&ClientErrorCode::InvalidScope).unwrap(), + "\"invalid_scope\"" + ); + assert_eq!( + serde_json::to_string(&ClientErrorCode::ServerError).unwrap(), + "\"server_error\"" + ); + assert_eq!( + serde_json::to_string(&ClientErrorCode::TemporarilyUnavailable).unwrap(), + "\"temporarily_unavailable\"" + ); + assert_eq!( + serde_json::to_string(&ClientErrorCode::InteractionRequired).unwrap(), + "\"interaction_required\"" + ); + assert_eq!( + serde_json::to_string(&ClientErrorCode::LoginRequired).unwrap(), + "\"login_required\"" + ); + assert_eq!( + serde_json::to_string(&ClientErrorCode::AccountSelectionRequired).unwrap(), + "\"account_selection_required\"" + ); + assert_eq!( + serde_json::to_string(&ClientErrorCode::ConsentRequired).unwrap(), + "\"consent_required\"" + ); + assert_eq!( + serde_json::to_string(&ClientErrorCode::InvalidRequestUri).unwrap(), + "\"invalid_request_uri\"" + ); + assert_eq!( + serde_json::to_string(&ClientErrorCode::InvalidRequestObject).unwrap(), + "\"invalid_request_object\"" + ); + assert_eq!( + serde_json::to_string(&ClientErrorCode::RequestNotSupported).unwrap(), + "\"request_not_supported\"" + ); + assert_eq!( + serde_json::to_string(&ClientErrorCode::RequestUriNotSupported).unwrap(), + "\"request_uri_not_supported\"" + ); + assert_eq!( + serde_json::to_string(&ClientErrorCode::RegistrationNotSupported).unwrap(), + "\"registration_not_supported\"" + ); + assert_eq!( + serde_json::to_string(&ClientErrorCode::InvalidRedirectUri).unwrap(), + "\"invalid_redirect_uri\"" + ); + assert_eq!( + serde_json::to_string(&ClientErrorCode::InvalidClientMetadata).unwrap(), + "\"invalid_client_metadata\"" + ); + + assert_eq!( + serde_json::to_string(&ClientErrorCode::Unknown("unknown_error_code".to_owned())) + .unwrap(), + "\"unknown_error_code\"" + ); + } + + #[test] + fn deserialize_client_error_code() { + assert_eq!( + serde_json::from_str::("\"invalid_request\"").unwrap(), + ClientErrorCode::InvalidRequest + ); + assert_eq!( + serde_json::from_str::("\"invalid_client\"").unwrap(), + ClientErrorCode::InvalidClient + ); + assert_eq!( + serde_json::from_str::("\"invalid_grant\"").unwrap(), + ClientErrorCode::InvalidGrant + ); + assert_eq!( + serde_json::from_str::("\"unauthorized_client\"").unwrap(), + ClientErrorCode::UnauthorizedClient + ); + assert_eq!( + serde_json::from_str::("\"unsupported_grant_type\"").unwrap(), + ClientErrorCode::UnsupportedGrantType + ); + assert_eq!( + serde_json::from_str::("\"access_denied\"").unwrap(), + ClientErrorCode::AccessDenied + ); + assert_eq!( + serde_json::from_str::("\"unsupported_response_type\"").unwrap(), + ClientErrorCode::UnsupportedResponseType + ); + assert_eq!( + serde_json::from_str::("\"invalid_scope\"").unwrap(), + ClientErrorCode::InvalidScope + ); + assert_eq!( + serde_json::from_str::("\"server_error\"").unwrap(), + ClientErrorCode::ServerError + ); + assert_eq!( + serde_json::from_str::("\"temporarily_unavailable\"").unwrap(), + ClientErrorCode::TemporarilyUnavailable + ); + assert_eq!( + serde_json::from_str::("\"interaction_required\"").unwrap(), + ClientErrorCode::InteractionRequired + ); + assert_eq!( + serde_json::from_str::("\"login_required\"").unwrap(), + ClientErrorCode::LoginRequired + ); + assert_eq!( + serde_json::from_str::("\"account_selection_required\"").unwrap(), + ClientErrorCode::AccountSelectionRequired + ); + assert_eq!( + serde_json::from_str::("\"consent_required\"").unwrap(), + ClientErrorCode::ConsentRequired + ); + assert_eq!( + serde_json::from_str::("\"invalid_request_uri\"").unwrap(), + ClientErrorCode::InvalidRequestUri + ); + assert_eq!( + serde_json::from_str::("\"invalid_request_object\"").unwrap(), + ClientErrorCode::InvalidRequestObject + ); + assert_eq!( + serde_json::from_str::("\"request_not_supported\"").unwrap(), + ClientErrorCode::RequestNotSupported + ); + assert_eq!( + serde_json::from_str::("\"request_uri_not_supported\"").unwrap(), + ClientErrorCode::RequestUriNotSupported + ); + assert_eq!( + serde_json::from_str::("\"registration_not_supported\"").unwrap(), + ClientErrorCode::RegistrationNotSupported + ); + assert_eq!( + serde_json::from_str::("\"invalid_redirect_uri\"").unwrap(), + ClientErrorCode::InvalidRedirectUri + ); + assert_eq!( + serde_json::from_str::("\"invalid_client_metadata\"").unwrap(), + ClientErrorCode::InvalidClientMetadata + ); + + assert_eq!( + serde_json::from_str::("\"unknown_error_code\"").unwrap(), + ClientErrorCode::Unknown("unknown_error_code".to_owned()) + ); + } +} diff --git a/crates/oauth2-types/src/oidc.rs b/crates/oauth2-types/src/oidc.rs index 89704ff5..d74181f3 100644 --- a/crates/oauth2-types/src/oidc.rs +++ b/crates/oauth2-types/src/oidc.rs @@ -22,29 +22,36 @@ use mas_iana::{ PkceCodeChallengeMethod, }, }; +use parse_display::{Display, FromStr}; use serde::{Deserialize, Serialize}; -use serde_with::skip_serializing_none; +use serde_with::{skip_serializing_none, DeserializeFromStr, SerializeDisplay}; use thiserror::Error; use url::Url; use crate::requests::{Display, GrantType, Prompt, ResponseMode}; -#[derive(Serialize, Deserialize, Clone, Copy, PartialEq, Eq, Hash, Debug)] -#[serde(rename_all = "lowercase")] +#[derive( + SerializeDisplay, DeserializeFromStr, Clone, Copy, PartialEq, Eq, Hash, Debug, Display, FromStr, +)] +#[display(style = "lowercase")] pub enum ApplicationType { Web, Native, } -#[derive(Serialize, Deserialize, Clone, Copy, PartialEq, Eq, Hash, Debug)] -#[serde(rename_all = "lowercase")] +#[derive( + SerializeDisplay, DeserializeFromStr, Clone, Copy, PartialEq, Eq, Hash, Debug, Display, FromStr, +)] +#[display(style = "lowercase")] pub enum SubjectType { Public, Pairwise, } -#[derive(Serialize, Deserialize, Clone, Copy, PartialEq, Eq, Hash, Debug)] -#[serde(rename_all = "lowercase")] +#[derive( + SerializeDisplay, DeserializeFromStr, Clone, Copy, PartialEq, Eq, Hash, Debug, Display, FromStr, +)] +#[display(style = "lowercase")] pub enum ClaimType { Normal, Aggregated, @@ -918,7 +925,7 @@ mod tests { }; use url::Url; - use super::{ProviderMetadata, ProviderMetadataVerificationError, SubjectType}; + use super::*; fn valid_provider_metadata() -> (ProviderMetadata, Url) { let issuer = Url::parse("https://localhost").unwrap(); @@ -1407,4 +1414,84 @@ mod tests { Some(Url::parse("https://localhost/par?query#fragment").unwrap()); metadata.validate(&issuer).unwrap(); } + + #[test] + fn serialize_application_type() { + assert_eq!( + serde_json::to_string(&ApplicationType::Web).unwrap(), + "\"web\"" + ); + assert_eq!( + serde_json::to_string(&ApplicationType::Native).unwrap(), + "\"native\"" + ); + } + + #[test] + fn deserialize_application_type() { + assert_eq!( + serde_json::from_str::("\"web\"").unwrap(), + ApplicationType::Web + ); + assert_eq!( + serde_json::from_str::("\"native\"").unwrap(), + ApplicationType::Native + ); + } + + #[test] + fn serialize_subject_type() { + assert_eq!( + serde_json::to_string(&SubjectType::Public).unwrap(), + "\"public\"" + ); + assert_eq!( + serde_json::to_string(&SubjectType::Pairwise).unwrap(), + "\"pairwise\"" + ); + } + + #[test] + fn deserialize_subject_type() { + assert_eq!( + serde_json::from_str::("\"public\"").unwrap(), + SubjectType::Public + ); + assert_eq!( + serde_json::from_str::("\"pairwise\"").unwrap(), + SubjectType::Pairwise + ); + } + + #[test] + fn serialize_claim_type() { + assert_eq!( + serde_json::to_string(&ClaimType::Normal).unwrap(), + "\"normal\"" + ); + assert_eq!( + serde_json::to_string(&ClaimType::Aggregated).unwrap(), + "\"aggregated\"" + ); + assert_eq!( + serde_json::to_string(&ClaimType::Distributed).unwrap(), + "\"distributed\"" + ); + } + + #[test] + fn deserialize_claim_type() { + assert_eq!( + serde_json::from_str::("\"normal\"").unwrap(), + ClaimType::Normal + ); + assert_eq!( + serde_json::from_str::("\"aggregated\"").unwrap(), + ClaimType::Aggregated + ); + assert_eq!( + serde_json::from_str::("\"distributed\"").unwrap(), + ClaimType::Distributed + ); + } } diff --git a/crates/oauth2-types/src/requests.rs b/crates/oauth2-types/src/requests.rs index e17ebaa9..3e2e8f4f 100644 --- a/crates/oauth2-types/src/requests.rs +++ b/crates/oauth2-types/src/requests.rs @@ -22,8 +22,8 @@ use mas_iana::oauth::{ use parse_display::{Display, FromStr}; use serde::{Deserialize, Serialize}; use serde_with::{ - formats::SpaceSeparator, serde_as, skip_serializing_none, DisplayFromStr, DurationSeconds, - StringWithSeparator, TimestampSeconds, + formats::SpaceSeparator, serde_as, skip_serializing_none, DeserializeFromStr, DisplayFromStr, + DurationSeconds, SerializeDisplay, StringWithSeparator, TimestampSeconds, }; use url::Url; @@ -46,10 +46,10 @@ use crate::scope::Scope; Copy, Display, FromStr, - Serialize, - Deserialize, + SerializeDisplay, + DeserializeFromStr, )] -#[serde(rename_all = "snake_case")] +#[display(style = "snake_case")] pub enum ResponseMode { /// Authorization Response parameters are encoded in the query string added /// to the `redirect_uri`. @@ -84,10 +84,10 @@ pub enum ResponseMode { Copy, Display, FromStr, - Serialize, - Deserialize, + SerializeDisplay, + DeserializeFromStr, )] -#[serde(rename_all = "snake_case")] +#[display(style = "snake_case")] pub enum Display { /// The Authorization Server should display the authentication and consent /// UI consistent with a full User Agent page view. @@ -129,11 +129,10 @@ impl Default for Display { Copy, Display, FromStr, - Serialize, - Deserialize, + SerializeDisplay, + DeserializeFromStr, )] #[display(style = "snake_case")] -#[serde(rename_all = "snake_case")] pub enum Prompt { /// The Authorization Server must not display any authentication or consent /// user interface pages. @@ -366,10 +365,10 @@ pub struct ClientCredentialsGrant { Copy, Display, FromStr, - Serialize, - Deserialize, + SerializeDisplay, + DeserializeFromStr, )] -#[serde(rename_all = "snake_case")] +#[display(style = "snake_case")] pub enum GrantType { /// [`authorization_code`](https://www.rfc-editor.org/rfc/rfc6749#section-4.1) AuthorizationCode, @@ -387,11 +386,11 @@ pub enum GrantType { Password, /// [`urn:ietf:params:oauth:grant-type:device_code`](https://www.rfc-editor.org/rfc/rfc8628) - #[serde(rename = "urn:ietf:params:oauth:grant-type:device_code")] + #[display("urn:ietf:params:oauth:grant-type:device_code")] DeviceCode, /// [`urn:openid:params:grant-type:ciba`](https://openid.net/specs/openid-client-initiated-backchannel-authentication-core-1_0.html) - #[serde(rename = "urn:openid:params:grant-type:ciba")] + #[display("urn:openid:params:grant-type:ciba")] ClientInitiatedBackchannelAuthentication, } @@ -667,4 +666,106 @@ mod tests { GrantType::ClientInitiatedBackchannelAuthentication ); } + + #[test] + fn serialize_response_mode() { + assert_eq!( + serde_json::to_string(&ResponseMode::Query).unwrap(), + "\"query\"" + ); + assert_eq!( + serde_json::to_string(&ResponseMode::Fragment).unwrap(), + "\"fragment\"" + ); + assert_eq!( + serde_json::to_string(&ResponseMode::FormPost).unwrap(), + "\"form_post\"" + ); + } + + #[test] + fn deserialize_response_mode() { + assert_eq!( + serde_json::from_str::("\"query\"").unwrap(), + ResponseMode::Query + ); + assert_eq!( + serde_json::from_str::("\"fragment\"").unwrap(), + ResponseMode::Fragment + ); + assert_eq!( + serde_json::from_str::("\"form_post\"").unwrap(), + ResponseMode::FormPost + ); + } + + #[test] + fn serialize_display() { + assert_eq!(serde_json::to_string(&Display::Page).unwrap(), "\"page\""); + assert_eq!(serde_json::to_string(&Display::Popup).unwrap(), "\"popup\""); + assert_eq!(serde_json::to_string(&Display::Touch).unwrap(), "\"touch\""); + assert_eq!(serde_json::to_string(&Display::Wap).unwrap(), "\"wap\""); + } + + #[test] + fn deserialize_display() { + assert_eq!( + serde_json::from_str::("\"page\"").unwrap(), + Display::Page + ); + assert_eq!( + serde_json::from_str::("\"popup\"").unwrap(), + Display::Popup + ); + assert_eq!( + serde_json::from_str::("\"touch\"").unwrap(), + Display::Touch + ); + assert_eq!( + serde_json::from_str::("\"wap\"").unwrap(), + Display::Wap + ); + } + + #[test] + fn serialize_prompt() { + assert_eq!(serde_json::to_string(&Prompt::None).unwrap(), "\"none\""); + assert_eq!(serde_json::to_string(&Prompt::Login).unwrap(), "\"login\""); + assert_eq!( + serde_json::to_string(&Prompt::Consent).unwrap(), + "\"consent\"" + ); + assert_eq!( + serde_json::to_string(&Prompt::SelectAccount).unwrap(), + "\"select_account\"" + ); + assert_eq!( + serde_json::to_string(&Prompt::Create).unwrap(), + "\"create\"" + ); + } + + #[test] + fn deserialize_prompt() { + assert_eq!( + serde_json::from_str::("\"none\"").unwrap(), + Prompt::None + ); + assert_eq!( + serde_json::from_str::("\"login\"").unwrap(), + Prompt::Login + ); + assert_eq!( + serde_json::from_str::("\"consent\"").unwrap(), + Prompt::Consent + ); + assert_eq!( + serde_json::from_str::("\"select_account\"").unwrap(), + Prompt::SelectAccount + ); + assert_eq!( + serde_json::from_str::("\"create\"").unwrap(), + Prompt::Create + ); + } }