diff --git a/Cargo.lock b/Cargo.lock index f66e14eb3..8a5db068f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2344,6 +2344,7 @@ dependencies = [ "regex", "serde", "serde_json", + "serde_with", "sketching", "time 0.3.24", "tokio", @@ -2392,6 +2393,7 @@ dependencies = [ "serde", "serde_cbor_2", "serde_json", + "serde_with", "sketching", "smartstring", "smolset", diff --git a/Cargo.toml b/Cargo.toml index febf6b1d5..7bcd99fd8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -51,6 +51,7 @@ kanidm_proto = { path = "./proto", version = "1.1.0-rc.14-dev" } kanidm_unix_int = { path = "./unix_integration" } kanidm_utils_users = { path = "./libs/users" } +serde_with = "3.1.0" argon2 = { version = "0.5.1", features = ["alloc"] } async-recursion = "1.0.4" async-trait = "^0.1.72" diff --git a/proto/Cargo.toml b/proto/Cargo.toml index c63b9fcca..01849231d 100644 --- a/proto/Cargo.toml +++ b/proto/Cargo.toml @@ -22,7 +22,7 @@ num_enum = { workspace = true } scim_proto = { workspace = true } serde = { workspace = true, features = ["derive"] } serde_json = { workspace = true } -serde_with = "3.1.0" +serde_with = { workspace = true } time = { workspace = true, features = ["serde", "std"] } tracing = { workspace = true } url = { workspace = true, features = ["serde"] } diff --git a/proto/src/oauth2.rs b/proto/src/oauth2.rs index ceaf4137c..5410ef333 100644 --- a/proto/src/oauth2.rs +++ b/proto/src/oauth2.rs @@ -20,18 +20,18 @@ pub struct PkceRequest { pub code_challenge_method: CodeChallengeMethod, } +#[skip_serializing_none] #[derive(Serialize, Deserialize, Debug, Clone)] pub struct AuthorisationRequest { // Must be "code". (or token, see 4.2.1) pub response_type: String, pub client_id: String, pub state: String, - #[serde(flatten, skip_serializing_if = "Option::is_none")] + #[serde(flatten)] pub pkce_request: Option, pub redirect_uri: Url, pub scope: String, // OIDC adds a nonce parameter that is optional. - #[serde(skip_serializing_if = "Option::is_none")] pub nonce: Option, // OIDC also allows other optional params #[serde(flatten)] @@ -40,23 +40,16 @@ pub struct AuthorisationRequest { pub unknown_keys: BTreeMap, } +#[skip_serializing_none] #[derive(Serialize, Deserialize, Debug, Clone, Default)] pub struct AuthorisationRequestOidc { - #[serde(skip_serializing_if = "Option::is_none")] pub display: Option, - #[serde(skip_serializing_if = "Option::is_none")] pub prompt: Option, - #[serde(skip_serializing_if = "Option::is_none")] pub max_age: Option, - #[serde(skip_serializing_if = "Option::is_none")] pub ui_locales: Option<()>, - #[serde(skip_serializing_if = "Option::is_none")] pub claims_locales: Option<()>, - #[serde(skip_serializing_if = "Option::is_none")] pub id_token_hint: Option, - #[serde(skip_serializing_if = "Option::is_none")] pub login_hint: Option, - #[serde(skip_serializing_if = "Option::is_none")] pub acr: Option, } @@ -99,15 +92,14 @@ pub enum GrantTypeReq { }, } +#[skip_serializing_none] #[derive(Serialize, Deserialize, Debug)] pub struct AccessTokenRequest { #[serde(flatten)] pub grant_type: GrantTypeReq, // REQUIRED, if the client is not authenticating with the // authorization server as described in Section 3.2.1. - #[serde(skip_serializing_if = "Option::is_none")] pub client_id: Option, - #[serde(skip_serializing_if = "Option::is_none")] pub client_secret: Option, } @@ -121,17 +113,18 @@ impl From for AccessTokenRequest { } } +#[skip_serializing_none] #[derive(Serialize, Deserialize, Debug)] pub struct TokenRevokeRequest { pub token: String, /// Generally not needed. See: /// - #[serde(skip_serializing_if = "Option::is_none")] pub token_type_hint: Option, } // The corresponding Response to a revoke request is empty body with 200. +#[skip_serializing_none] #[derive(Serialize, Deserialize, Debug)] pub struct AccessTokenResponse { // Could be Base64UrlSafeData @@ -140,50 +133,37 @@ pub struct AccessTokenResponse { pub token_type: String, // seconds. pub expires_in: u32, - #[serde(skip_serializing_if = "Option::is_none")] pub refresh_token: Option, - #[serde(skip_serializing_if = "Option::is_none")] /// Space separated list of scopes that were approved, if this differs from the /// original request. pub scope: Option, - #[serde(skip_serializing_if = "Option::is_none")] /// Oidc puts the token here. pub id_token: Option, } +#[skip_serializing_none] #[derive(Serialize, Deserialize, Debug)] pub struct AccessTokenIntrospectRequest { pub token: String, /// Generally not needed. See: /// - #[serde(skip_serializing_if = "Option::is_none")] pub token_type_hint: Option, } +#[skip_serializing_none] #[derive(Serialize, Deserialize, Debug)] pub struct AccessTokenIntrospectResponse { pub active: bool, - #[serde(skip_serializing_if = "Option::is_none")] pub scope: Option, - #[serde(skip_serializing_if = "Option::is_none")] pub client_id: Option, - #[serde(skip_serializing_if = "Option::is_none")] pub username: Option, - #[serde(skip_serializing_if = "Option::is_none")] pub token_type: Option, - #[serde(skip_serializing_if = "Option::is_none")] pub exp: Option, - #[serde(skip_serializing_if = "Option::is_none")] pub iat: Option, - #[serde(skip_serializing_if = "Option::is_none")] pub nbf: Option, - #[serde(skip_serializing_if = "Option::is_none")] pub sub: Option, - #[serde(skip_serializing_if = "Option::is_none")] pub aud: Option, - #[serde(skip_serializing_if = "Option::is_none")] pub iss: Option, - #[serde(skip_serializing_if = "Option::is_none")] pub jti: Option, } @@ -303,18 +283,16 @@ fn require_request_uri_parameter_supported_default() -> bool { false } +#[skip_serializing_none] #[derive(Serialize, Deserialize, Debug)] // https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderMetadata pub struct OidcDiscoveryResponse { pub issuer: Url, pub authorization_endpoint: Url, pub token_endpoint: Url, - #[serde(skip_serializing_if = "Option::is_none")] pub userinfo_endpoint: Option, pub jwks_uri: Url, - #[serde(skip_serializing_if = "Option::is_none")] pub registration_endpoint: Option, - #[serde(skip_serializing_if = "Option::is_none")] pub scopes_supported: Option>, // https://datatracker.ietf.org/doc/html/rfc6749#section-3.1.1 pub response_types_supported: Vec, @@ -324,45 +302,30 @@ pub struct OidcDiscoveryResponse { // Need to fill in as authorization_code only else a default is assumed. #[serde(default = "grant_types_supported_default")] pub grant_types_supported: Vec, - #[serde(skip_serializing_if = "Option::is_none")] pub acr_values_supported: Option>, // https://openid.net/specs/openid-connect-core-1_0.html#PairwiseAlg pub subject_types_supported: Vec, pub id_token_signing_alg_values_supported: Vec, - #[serde(skip_serializing_if = "Option::is_none")] pub id_token_encryption_alg_values_supported: Option>, - #[serde(skip_serializing_if = "Option::is_none")] pub id_token_encryption_enc_values_supported: Option>, - #[serde(skip_serializing_if = "Option::is_none")] pub userinfo_signing_alg_values_supported: Option>, - #[serde(skip_serializing_if = "Option::is_none")] pub userinfo_encryption_alg_values_supported: Option>, - #[serde(skip_serializing_if = "Option::is_none")] pub userinfo_encryption_enc_values_supported: Option>, - #[serde(skip_serializing_if = "Option::is_none")] pub request_object_signing_alg_values_supported: Option>, - #[serde(skip_serializing_if = "Option::is_none")] pub request_object_encryption_alg_values_supported: Option>, - #[serde(skip_serializing_if = "Option::is_none")] pub request_object_encryption_enc_values_supported: Option>, // Defaults to client_secret_basic #[serde(default = "token_endpoint_auth_methods_supported_default")] pub token_endpoint_auth_methods_supported: Vec, - #[serde(skip_serializing_if = "Option::is_none")] pub token_endpoint_auth_signing_alg_values_supported: Option>, // https://openid.net/specs/openid-connect-core-1_0.html#AuthRequest - #[serde(skip_serializing_if = "Option::is_none")] pub display_values_supported: Option>, // Default to normal. #[serde(default = "claim_types_supported_default")] pub claim_types_supported: Vec, - #[serde(skip_serializing_if = "Option::is_none")] pub claims_supported: Option>, - #[serde(skip_serializing_if = "Option::is_none")] pub service_documentation: Option, - #[serde(skip_serializing_if = "Option::is_none")] pub claims_locales_supported: Option>, - #[serde(skip_serializing_if = "Option::is_none")] pub ui_locales_supported: Option>, // Default false. #[serde(default = "claims_parameter_supported_default")] @@ -373,18 +336,15 @@ pub struct OidcDiscoveryResponse { pub request_uri_parameter_supported: bool, #[serde(default = "require_request_uri_parameter_supported_default")] pub require_request_uri_registration: bool, - #[serde(skip_serializing_if = "Option::is_none")] pub op_policy_uri: Option, - #[serde(skip_serializing_if = "Option::is_none")] pub op_tos_uri: Option, } +#[skip_serializing_none] #[derive(Serialize, Deserialize, Debug)] pub struct ErrorResponse { pub error: String, - #[serde(skip_serializing_if = "Option::is_none")] pub error_description: Option, - #[serde(skip_serializing_if = "Option::is_none")] pub error_uri: Option, } diff --git a/proto/src/v1.rs b/proto/src/v1.rs index 95ca14ff3..f99318c96 100644 --- a/proto/src/v1.rs +++ b/proto/src/v1.rs @@ -1,12 +1,12 @@ +use num_enum::TryFromPrimitive; +use serde::{Deserialize, Serialize}; +use serde_with::skip_serializing_none; use std::cmp::Ordering; use std::collections::{BTreeMap, BTreeSet}; use std::fmt; use std::str::FromStr; -use url::Url; - -use num_enum::TryFromPrimitive; -use serde::{Deserialize, Serialize}; use time::OffsetDateTime; +use url::Url; use uuid::Uuid; use webauthn_rs_proto::{ CreationChallengeResponse, PublicKeyCredential, RegisterPublicKeyCredential, @@ -574,6 +574,7 @@ pub struct GroupUnixExtend { pub gidnumber: Option, } +#[skip_serializing_none] #[derive(Debug, Serialize, Deserialize, Clone)] pub struct UnixUserToken { pub name: String, @@ -581,7 +582,6 @@ pub struct UnixUserToken { pub displayname: String, pub gidnumber: u32, pub uuid: Uuid, - #[serde(skip_serializing_if = "Option::is_none")] pub shell: Option, pub groups: Vec, pub sshkeys: Vec, diff --git a/server/core/Cargo.toml b/server/core/Cargo.toml index 48970cc78..c18cdb19f 100644 --- a/server/core/Cargo.toml +++ b/server/core/Cargo.toml @@ -49,6 +49,7 @@ tracing-subscriber = { workspace = true, features = ["time", "json"] } urlencoding = { workspace = true } kanidm_utils_users = { workspace = true } uuid = { workspace = true, features = ["serde", "v4" ] } +serde_with = { workspace = true } [build-dependencies] kanidm_build_profiles = { workspace = true } diff --git a/server/core/src/https/manifest.rs b/server/core/src/https/manifest.rs index 8dca2a20c..ee01f7555 100644 --- a/server/core/src/https/manifest.rs +++ b/server/core/src/https/manifest.rs @@ -5,6 +5,7 @@ use axum::response::{IntoResponse, Response}; use axum::Extension; use http::HeaderValue; use serde::{Deserialize, Serialize}; +use serde_with::skip_serializing_none; use super::middleware::KOpId; // Thanks to the webmanifest crate for a lot of this code @@ -14,6 +15,8 @@ use super::ServerState; const MIME_TYPE_MANIFEST: &str = "application/manifest+json;charset=utf-8"; /// Create a new manifest builder. + +#[skip_serializing_none] #[derive(Debug, Clone, Serialize, Deserialize)] pub struct Manifest { name: String, @@ -22,28 +25,22 @@ pub struct Manifest { #[serde(rename = "display")] display_mode: DisplayMode, background_color: String, - #[serde(skip_serializing_if = "Option::is_none")] description: Option, #[serde(rename = "dir")] direction: Direction, // direction: Option, - #[serde(skip_serializing_if = "Option::is_none")] orientation: Option, // orientation: Option, - #[serde(skip_serializing_if = "Option::is_none")] lang: Option, - #[serde(skip_serializing_if = "Option::is_none")] scope: Option, - // #[serde(skip_serializing_if = "Option::is_none")] + // theme_color: String, - #[serde(skip_serializing_if = "Option::is_none")] prefer_related_applications: Option, // #[serde(borrow)] - // #[serde(skip_serializing_if = "Option::is_none")] + // icons: Vec, // icons: Vec>, // #[serde(borrow)] - #[serde(skip_serializing_if = "Option::is_none")] related_applications: Option>, // related_applications: Vec>, } diff --git a/server/lib/Cargo.toml b/server/lib/Cargo.toml index 30ce85ab1..99acdf1d2 100644 --- a/server/lib/Cargo.toml +++ b/server/lib/Cargo.toml @@ -65,6 +65,7 @@ uuid = { workspace = true, features = ["serde", "v4" ] } webauthn-rs = { workspace = true, features = ["resident-key-support", "preview-features", "danger-credential-internals"] } webauthn-rs-core = { workspace = true } zxcvbn = { workspace = true } +serde_with = { workspace = true } # because windows really can't build without the bundled one [target.'cfg(target_family = "windows")'.dependencies] diff --git a/server/lib/src/be/dbvalue.rs b/server/lib/src/be/dbvalue.rs index 8d848fb8f..1995723ba 100644 --- a/server/lib/src/be/dbvalue.rs +++ b/server/lib/src/be/dbvalue.rs @@ -3,6 +3,7 @@ use std::time::Duration; use hashbrown::HashSet; use serde::{Deserialize, Serialize}; +use serde_with::skip_serializing_none; use url::Url; use uuid::Uuid; use webauthn_rs::prelude::{ @@ -114,54 +115,39 @@ fn is_false(b: &bool) -> bool { !b } +#[skip_serializing_none] #[derive(Serialize, Deserialize, Debug)] #[serde(tag = "type_")] pub enum DbCred { // These are the old v1 versions. Pw { - #[serde(skip_serializing_if = "Option::is_none")] password: Option, - #[serde(skip_serializing_if = "Option::is_none")] webauthn: Option>, - #[serde(skip_serializing_if = "Option::is_none")] totp: Option, - #[serde(skip_serializing_if = "Option::is_none")] backup_code: Option, claims: Vec, uuid: Uuid, }, GPw { - #[serde(skip_serializing_if = "Option::is_none")] password: Option, - #[serde(skip_serializing_if = "Option::is_none")] webauthn: Option>, - #[serde(skip_serializing_if = "Option::is_none")] totp: Option, - #[serde(skip_serializing_if = "Option::is_none")] backup_code: Option, claims: Vec, uuid: Uuid, }, PwMfa { - #[serde(skip_serializing_if = "Option::is_none")] password: Option, - #[serde(skip_serializing_if = "Option::is_none")] webauthn: Option>, - #[serde(skip_serializing_if = "Option::is_none")] totp: Option, - #[serde(skip_serializing_if = "Option::is_none")] backup_code: Option, claims: Vec, uuid: Uuid, }, Wn { - #[serde(skip_serializing_if = "Option::is_none")] password: Option, - #[serde(skip_serializing_if = "Option::is_none")] webauthn: Option>, - #[serde(skip_serializing_if = "Option::is_none")] totp: Option, - #[serde(skip_serializing_if = "Option::is_none")] backup_code: Option, claims: Vec, uuid: Uuid, @@ -680,6 +666,7 @@ impl DbValueSetV2 { mod tests { use base64::{engine::general_purpose, Engine as _}; use serde::{Deserialize, Serialize}; + use serde_with::skip_serializing_none; use uuid::Uuid; use super::{DbBackupCodeV1, DbCred, DbPasswordV1, DbTotpV1, DbWebauthnV1}; @@ -699,17 +686,14 @@ mod tests { // PwWnVer, } + #[skip_serializing_none] #[derive(Serialize, Deserialize, Debug)] pub struct DbCredV1 { #[serde(default = "dbcred_type_default_pw")] pub type_: DbCredTypeV1, - #[serde(skip_serializing_if = "Option::is_none")] pub password: Option, - #[serde(skip_serializing_if = "Option::is_none")] pub webauthn: Option>, - #[serde(skip_serializing_if = "Option::is_none")] pub totp: Option, - #[serde(skip_serializing_if = "Option::is_none")] pub backup_code: Option, pub claims: Vec, pub uuid: Uuid,