diff --git a/Cargo.lock b/Cargo.lock index 65aa0961a..45ab1f866 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -221,8 +221,6 @@ dependencies = [ "memchr", "pin-project-lite", "tokio", - "zstd", - "zstd-safe", ] [[package]] @@ -667,7 +665,6 @@ version = "1.0.83" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" dependencies = [ - "jobserver", "libc", ] @@ -2885,15 +2882,6 @@ version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" -[[package]] -name = "jobserver" -version = "0.1.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab46a6e9526ddef3ae7f787c06f0f2600639ba80ea3eade3d8e670a2230f51d6" -dependencies = [ - "libc", -] - [[package]] name = "jpeg-decoder" version = "0.3.1" @@ -6892,34 +6880,6 @@ dependencies = [ "flate2", ] -[[package]] -name = "zstd" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bffb3309596d527cfcba7dfc6ed6052f1d39dfbd7c867aa2e865e4a449c10110" -dependencies = [ - "zstd-safe", -] - -[[package]] -name = "zstd-safe" -version = "7.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43747c7422e2924c11144d5229878b98180ef8b06cca4ab5af37afc8a8d8ea3e" -dependencies = [ - "zstd-sys", -] - -[[package]] -name = "zstd-sys" -version = "2.0.9+zstd.1.5.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e16efa8a874a0481a574084d34cc26fdb3b99627480f785888deb6386506656" -dependencies = [ - "cc", - "pkg-config", -] - [[package]] name = "zxcvbn" version = "2.2.2" diff --git a/libs/client/src/lib.rs b/libs/client/src/lib.rs index 86e897a9e..30b25db54 100644 --- a/libs/client/src/lib.rs +++ b/libs/client/src/lib.rs @@ -29,6 +29,7 @@ use kanidm_proto::constants::{ APPLICATION_JSON, ATTR_ENTRY_MANAGED_BY, ATTR_NAME, CLIENT_TOKEN_CACHE, KOPID, KSESSIONID, KVERSION, }; +use kanidm_proto::internal::*; use kanidm_proto::v1::*; use reqwest::header::CONTENT_TYPE; use reqwest::Response; diff --git a/libs/client/src/person.rs b/libs/client/src/person.rs index 18991ff21..18878df60 100644 --- a/libs/client/src/person.rs +++ b/libs/client/src/person.rs @@ -1,10 +1,8 @@ use std::collections::BTreeMap; use kanidm_proto::constants::*; -use kanidm_proto::internal::{IdentifyUserRequest, IdentifyUserResponse}; -use kanidm_proto::v1::{ - AccountUnixExtend, CredentialStatus, Entry, SingleStringRequest, UatStatus, -}; +use kanidm_proto::internal::{CredentialStatus, IdentifyUserRequest, IdentifyUserResponse}; +use kanidm_proto::v1::{AccountUnixExtend, Entry, SingleStringRequest, UatStatus}; use uuid::Uuid; use crate::{ClientError, KanidmClient}; diff --git a/libs/client/src/service_account.rs b/libs/client/src/service_account.rs index 1f69675f0..68e743673 100644 --- a/libs/client/src/service_account.rs +++ b/libs/client/src/service_account.rs @@ -1,7 +1,8 @@ use std::collections::BTreeMap; use kanidm_proto::constants::{ATTR_DISPLAYNAME, ATTR_ENTRY_MANAGED_BY, ATTR_MAIL, ATTR_NAME}; -use kanidm_proto::v1::{AccountUnixExtend, ApiToken, ApiTokenGenerate, CredentialStatus, Entry}; +use kanidm_proto::internal::{ApiToken, CredentialStatus}; +use kanidm_proto::v1::{AccountUnixExtend, ApiTokenGenerate, Entry}; use time::OffsetDateTime; use uuid::Uuid; diff --git a/libs/crypto/build.rs b/libs/crypto/build.rs index ad5561d4c..173ee7e97 100644 --- a/libs/crypto/build.rs +++ b/libs/crypto/build.rs @@ -1,8 +1,8 @@ -// include!("src/lib/audit_loglevel.rs"); - use std::env; fn main() { + println!("cargo:rerun-if-changed=build.rs"); + println!("cargo:rerun-if-env-changed=DEP_OPENSSL_VERSION_NUMBER"); if let Ok(v) = env::var("DEP_OPENSSL_VERSION_NUMBER") { let version = u64::from_str_radix(&v, 16).unwrap(); diff --git a/libs/crypto/src/lib.rs b/libs/crypto/src/lib.rs index 7a933f7b9..56666356a 100644 --- a/libs/crypto/src/lib.rs +++ b/libs/crypto/src/lib.rs @@ -22,7 +22,7 @@ use serde::{Deserialize, Serialize}; use std::fmt; use std::time::{Duration, Instant}; -use kanidm_proto::v1::OperationError; +use kanidm_proto::internal::OperationError; use openssl::error::ErrorStack as OpenSSLErrorStack; use openssl::hash::{self, MessageDigest}; use openssl::nid::Nid; diff --git a/libs/profiles/build.rs b/libs/profiles/build.rs index 88855a7c4..07dba3e22 100644 --- a/libs/profiles/build.rs +++ b/libs/profiles/build.rs @@ -33,6 +33,14 @@ fn determine_git_rev() -> Option { } fn main() { + /* + * https://doc.rust-lang.org/cargo/reference/build-scripts.html#change-detection + * If the build script inherently does not need to re-run under any circumstance, then emitting + * cargo:rerun-if-changed=build.rs is a simple way to prevent it from being re-run (otherwise, + * the default if no rerun-if instructions are emitted is to scan the entire package directory + * for changes). + */ + println!("cargo:rerun-if-changed=build.rs"); println!("cargo:rerun-if-env-changed=KANIDM_BUILD_PROFILE"); let profile = env::var("KANIDM_BUILD_PROFILE").unwrap_or_else(|_| "developer".to_string()); diff --git a/libs/profiles/src/lib.rs b/libs/profiles/src/lib.rs index ac3206867..d45d167ab 100644 --- a/libs/profiles/src/lib.rs +++ b/libs/profiles/src/lib.rs @@ -2,6 +2,9 @@ use base64::{engine::general_purpose, Engine as _}; use serde::Deserialize; use std::env; +// To debug why a rebuild is requested. +// CARGO_LOG=cargo::core::compiler::fingerprint=info cargo ... + #[derive(Debug, Deserialize)] #[allow(non_camel_case_types)] enum CpuOptLevel { @@ -76,6 +79,9 @@ pub fn apply_profile() { // We have to setup for our pkg version to be passed into things correctly // now. This relies on the profile build.rs to get the commit rev if present, but // we combine it with the local package version + println!("cargo:rerun-if-env-changed=CARGO_PKG_VERSION"); + println!("cargo:rerun-if-env-changed=KANIDM_PKG_COMMIT_REV"); + let version = env!("CARGO_PKG_VERSION"); if let Some(commit_rev) = option_env!("KANIDM_PKG_COMMIT_REV") { println!( diff --git a/project_docs/RELEASE_AND_SUPPORT.md b/project_docs/RELEASE_AND_SUPPORT.md index 39a032ca4..a6ff92bc5 100644 --- a/project_docs/RELEASE_AND_SUPPORT.md +++ b/project_docs/RELEASE_AND_SUPPORT.md @@ -84,9 +84,9 @@ Stable APIs are: - LDAP protocol operations - JSON HTTP end points which use elements from - [`proto/src/v1.rs`](https://github.com/kanidm/kanidm/blob/master/proto/src/v1.rs) + [`proto/src/v1`](https://github.com/kanidm/kanidm/blob/master/proto/src/v1) - SCIM operations from - [`proto/src/scim_v1.rs`](https://github.com/kanidm/kanidm/blob/master/proto/src/scim_v1.rs) + [`proto/src/scim_v1`](https://github.com/kanidm/kanidm/blob/master/proto/src/scim_v1) All other APIs and interactions are not considered stable. Changes will be minimised if possible. This includes but is not limited to: diff --git a/proto/src/constants.rs b/proto/src/constants.rs index 29adff79d..d382996ff 100644 --- a/proto/src/constants.rs +++ b/proto/src/constants.rs @@ -5,10 +5,15 @@ pub mod uri; /// The default location for the `kanidm` CLI tool's token cache. pub const CLIENT_TOKEN_CACHE: &str = "~/.cache/kanidm_tokens"; +/// Content type string for jpeg pub const CONTENT_TYPE_JPG: &str = "image/jpeg"; +/// Content type string for png pub const CONTENT_TYPE_PNG: &str = "image/png"; +/// Content type string for gif pub const CONTENT_TYPE_GIF: &str = "image/gif"; +/// Content type string for svg pub const CONTENT_TYPE_SVG: &str = "image/svg+xml"; +/// Content type string for webp pub const CONTENT_TYPE_WEBP: &str = "image/webp"; // For when the user uploads things to the various image endpoints, these are the valid content-types. diff --git a/proto/src/internal/credupdate.rs b/proto/src/internal/credupdate.rs new file mode 100644 index 000000000..a2660e1ee --- /dev/null +++ b/proto/src/internal/credupdate.rs @@ -0,0 +1,437 @@ +use serde::{Deserialize, Serialize}; +use std::fmt; +use url::Url; +use utoipa::ToSchema; +use uuid::Uuid; + +use webauthn_rs_proto::CreationChallengeResponse; +use webauthn_rs_proto::RegisterPublicKeyCredential; + +#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)] +#[serde(rename_all = "lowercase")] +pub enum TotpAlgo { + Sha1, + Sha256, + Sha512, +} + +impl fmt::Display for TotpAlgo { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + TotpAlgo::Sha1 => write!(f, "SHA1"), + TotpAlgo::Sha256 => write!(f, "SHA256"), + TotpAlgo::Sha512 => write!(f, "SHA512"), + } + } +} + +#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)] +pub struct TotpSecret { + pub accountname: String, + /// User-facing name of the system, issuer of the TOTP + pub issuer: String, + pub secret: Vec, + pub algo: TotpAlgo, + pub step: u64, + pub digits: u8, +} + +impl TotpSecret { + /// + pub fn to_uri(&self) -> String { + let accountname = urlencoding::Encoded(&self.accountname); + let issuer = urlencoding::Encoded(&self.issuer); + let label = format!("{}:{}", issuer, accountname); + let algo = self.algo.to_string(); + let secret = self.get_secret(); + let period = self.step; + let digits = self.digits; + + format!( + "otpauth://totp/{}?secret={}&issuer={}&algorithm={}&digits={}&period={}", + label, secret, issuer, algo, digits, period + ) + } + + pub fn get_secret(&self) -> String { + base32::encode(base32::Alphabet::RFC4648 { padding: false }, &self.secret) + } +} + +#[derive(Debug, Serialize, Deserialize, ToSchema)] +pub struct CUIntentToken { + pub token: String, +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, ToSchema)] +pub struct CUSessionToken { + pub token: String, +} + +#[derive(Clone, Serialize, Deserialize)] +#[serde(rename_all = "lowercase")] +pub enum CURequest { + PrimaryRemove, + Password(String), + CancelMFAReg, + TotpGenerate, + TotpVerify(u32, String), + TotpAcceptSha1, + TotpRemove(String), + BackupCodeGenerate, + BackupCodeRemove, + PasskeyInit, + PasskeyFinish(String, RegisterPublicKeyCredential), + PasskeyRemove(Uuid), + AttestedPasskeyInit, + AttestedPasskeyFinish(String, RegisterPublicKeyCredential), + AttestedPasskeyRemove(Uuid), +} + +impl fmt::Debug for CURequest { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let t = match self { + CURequest::PrimaryRemove => "CURequest::PrimaryRemove", + CURequest::Password(_) => "CURequest::Password", + CURequest::CancelMFAReg => "CURequest::CancelMFAReg", + CURequest::TotpGenerate => "CURequest::TotpGenerate", + CURequest::TotpVerify(_, _) => "CURequest::TotpVerify", + CURequest::TotpAcceptSha1 => "CURequest::TotpAcceptSha1", + CURequest::TotpRemove(_) => "CURequest::TotpRemove", + CURequest::BackupCodeGenerate => "CURequest::BackupCodeGenerate", + CURequest::BackupCodeRemove => "CURequest::BackupCodeRemove", + CURequest::PasskeyInit => "CURequest::PasskeyInit", + CURequest::PasskeyFinish(_, _) => "CURequest::PasskeyFinish", + CURequest::PasskeyRemove(_) => "CURequest::PasskeyRemove", + CURequest::AttestedPasskeyInit => "CURequest::AttestedPasskeyInit", + CURequest::AttestedPasskeyFinish(_, _) => "CURequest::AttestedPasskeyFinish", + CURequest::AttestedPasskeyRemove(_) => "CURequest::AttestedPasskeyRemove", + }; + writeln!(f, "{}", t) + } +} + +#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)] +pub enum CURegState { + // Nothing in progress. + None, + TotpCheck(TotpSecret), + TotpTryAgain, + TotpInvalidSha1, + BackupCodes(Vec), + Passkey(CreationChallengeResponse), + AttestedPasskey(CreationChallengeResponse), +} + +#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)] +pub enum CUExtPortal { + None, + Hidden, + Some(Url), +} + +#[derive(Debug, Clone, Copy, Serialize, Deserialize, ToSchema, PartialEq)] +pub enum CUCredState { + Modifiable, + DeleteOnly, + AccessDeny, + PolicyDeny, + // Disabled, +} + +#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)] +pub enum CURegWarning { + MfaRequired, + PasskeyRequired, + AttestedPasskeyRequired, + AttestedResidentKeyRequired, + Unsatisfiable, + WebauthnAttestationUnsatisfiable, +} + +#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)] +pub struct CUStatus { + // Display values + pub spn: String, + pub displayname: String, + pub ext_cred_portal: CUExtPortal, + // Internal State Tracking + pub mfaregstate: CURegState, + // Display hints + The credential details. + pub can_commit: bool, + pub warnings: Vec, + pub primary: Option, + pub primary_state: CUCredState, + pub passkeys: Vec, + pub passkeys_state: CUCredState, + pub attested_passkeys: Vec, + pub attested_passkeys_state: CUCredState, + pub attested_passkeys_allowed_devices: Vec, +} + +#[derive(Debug, Serialize, Deserialize, Clone, ToSchema)] +pub struct CredentialStatus { + pub creds: Vec, +} + +impl fmt::Display for CredentialStatus { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + for cred in &self.creds { + writeln!(f, "---")?; + cred.fmt(f)?; + } + writeln!(f, "---") + } +} + +#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq, ToSchema)] +pub enum CredentialDetailType { + Password, + GeneratedPassword, + Passkey(Vec), + /// totp, webauthn + PasswordMfa(Vec, Vec, usize), +} + +#[derive(Debug, Serialize, Deserialize, Clone, ToSchema)] +pub struct CredentialDetail { + pub uuid: Uuid, + pub type_: CredentialDetailType, +} + +impl fmt::Display for CredentialDetail { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + writeln!(f, "uuid: {}", self.uuid)?; + /* + writeln!(f, "claims:")?; + for claim in &self.claims { + writeln!(f, " * {}", claim)?; + } + */ + match &self.type_ { + CredentialDetailType::Password => writeln!(f, "password: set"), + CredentialDetailType::GeneratedPassword => writeln!(f, "generated password: set"), + CredentialDetailType::Passkey(labels) => { + if labels.is_empty() { + writeln!(f, "passkeys: none registered") + } else { + writeln!(f, "passkeys:")?; + for label in labels { + writeln!(f, " * {}", label)?; + } + write!(f, "") + } + } + CredentialDetailType::PasswordMfa(totp_labels, wan_labels, backup_code) => { + writeln!(f, "password: set")?; + + if !totp_labels.is_empty() { + writeln!(f, "totp:")?; + for label in totp_labels { + writeln!(f, " * {}", label)?; + } + } else { + writeln!(f, "totp: disabled")?; + } + + if *backup_code > 0 { + writeln!(f, "backup_code: enabled")?; + } else { + writeln!(f, "backup_code: disabled")?; + } + + if !wan_labels.is_empty() { + // We no longer show the deprecated security key case by default. + writeln!(f, " ⚠️ warning - security keys are deprecated.")?; + writeln!(f, " ⚠️ you should re-enroll these to passkeys.")?; + writeln!(f, "security keys:")?; + for label in wan_labels { + writeln!(f, " * {}", label)?; + } + write!(f, "") + } else { + write!(f, "") + } + } + } + } +} + +#[derive(Debug, Serialize, Deserialize, Clone, ToSchema)] +pub struct PasskeyDetail { + pub uuid: Uuid, + pub tag: String, +} + +#[derive(Debug, Serialize, Deserialize, Clone, ToSchema)] +pub struct BackupCodesView { + pub backup_codes: Vec, +} + +#[derive(Serialize, Deserialize, Debug, ToSchema, PartialEq, Eq, PartialOrd, Ord)] +#[serde(rename_all = "lowercase")] +pub enum PasswordFeedback { + // https://docs.rs/zxcvbn/latest/zxcvbn/feedback/enum.Suggestion.html + UseAFewWordsAvoidCommonPhrases, + NoNeedForSymbolsDigitsOrUppercaseLetters, + AddAnotherWordOrTwo, + CapitalizationDoesntHelpVeryMuch, + AllUppercaseIsAlmostAsEasyToGuessAsAllLowercase, + ReversedWordsArentMuchHarderToGuess, + PredictableSubstitutionsDontHelpVeryMuch, + UseALongerKeyboardPatternWithMoreTurns, + AvoidRepeatedWordsAndCharacters, + AvoidSequences, + AvoidRecentYears, + AvoidYearsThatAreAssociatedWithYou, + AvoidDatesAndYearsThatAreAssociatedWithYou, + // https://docs.rs/zxcvbn/latest/zxcvbn/feedback/enum.Warning.html + StraightRowsOfKeysAreEasyToGuess, + ShortKeyboardPatternsAreEasyToGuess, + RepeatsLikeAaaAreEasyToGuess, + RepeatsLikeAbcAbcAreOnlySlightlyHarderToGuess, + ThisIsATop10Password, + ThisIsATop100Password, + ThisIsACommonPassword, + ThisIsSimilarToACommonlyUsedPassword, + SequencesLikeAbcAreEasyToGuess, + RecentYearsAreEasyToGuess, + AWordByItselfIsEasyToGuess, + DatesAreOftenEasyToGuess, + NamesAndSurnamesByThemselvesAreEasyToGuess, + CommonNamesAndSurnamesAreEasyToGuess, + // Custom + TooShort(u32), + BadListed, + DontReusePasswords, +} + +/// Human-readable PasswordFeedback result. +impl fmt::Display for PasswordFeedback { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + PasswordFeedback::AddAnotherWordOrTwo => write!(f, "Add another word or two."), + PasswordFeedback::AllUppercaseIsAlmostAsEasyToGuessAsAllLowercase => write!( + f, + "All uppercase is almost as easy to guess as all lowercase." + ), + PasswordFeedback::AvoidDatesAndYearsThatAreAssociatedWithYou => write!( + f, + "Avoid dates and years that are associated with you or your account." + ), + PasswordFeedback::AvoidRecentYears => write!(f, "Avoid recent years."), + PasswordFeedback::AvoidRepeatedWordsAndCharacters => { + write!(f, "Avoid repeated words and characters.") + } + PasswordFeedback::AvoidSequences => write!(f, "Avoid sequences of characters."), + PasswordFeedback::AvoidYearsThatAreAssociatedWithYou => { + write!(f, "Avoid years that are associated with you.") + } + PasswordFeedback::AWordByItselfIsEasyToGuess => { + write!(f, "A word by itself is easy to guess.") + } + PasswordFeedback::BadListed => write!( + f, + "This password has been compromised or otherwise blocked and can not be used." + ), + PasswordFeedback::CapitalizationDoesntHelpVeryMuch => { + write!(f, "Capitalization doesn't help very much.") + } + PasswordFeedback::CommonNamesAndSurnamesAreEasyToGuess => { + write!(f, "Common names and surnames are easy to guess.") + } + PasswordFeedback::DatesAreOftenEasyToGuess => { + write!(f, "Dates are often easy to guess.") + } + PasswordFeedback::DontReusePasswords => { + write!( + f, + "Don't reuse passwords that already exist on your account" + ) + } + PasswordFeedback::NamesAndSurnamesByThemselvesAreEasyToGuess => { + write!(f, "Names and surnames by themselves are easy to guess.") + } + PasswordFeedback::NoNeedForSymbolsDigitsOrUppercaseLetters => { + write!(f, "No need for symbols, digits or upper-case letters.") + } + PasswordFeedback::PredictableSubstitutionsDontHelpVeryMuch => { + write!(f, "Predictable substitutions don't help very much.") + } + PasswordFeedback::RecentYearsAreEasyToGuess => { + write!(f, "Recent years are easy to guess.") + } + PasswordFeedback::RepeatsLikeAaaAreEasyToGuess => { + write!(f, "Repeats like 'aaa' are easy to guess.") + } + PasswordFeedback::RepeatsLikeAbcAbcAreOnlySlightlyHarderToGuess => write!( + f, + "Repeats like abcabcabc are only slightly harder to guess." + ), + PasswordFeedback::ReversedWordsArentMuchHarderToGuess => { + write!(f, "Reversed words aren't much harder to guess.") + } + PasswordFeedback::SequencesLikeAbcAreEasyToGuess => { + write!(f, "Sequences like 'abc' are easy to guess.") + } + PasswordFeedback::ShortKeyboardPatternsAreEasyToGuess => { + write!(f, "Short keyboard patterns are easy to guess.") + } + PasswordFeedback::StraightRowsOfKeysAreEasyToGuess => { + write!(f, "Straight rows of keys are easy to guess.") + } + PasswordFeedback::ThisIsACommonPassword => write!(f, "This is a common password."), + PasswordFeedback::ThisIsATop100Password => write!(f, "This is a top 100 password."), + PasswordFeedback::ThisIsATop10Password => write!(f, "This is a top 10 password."), + PasswordFeedback::ThisIsSimilarToACommonlyUsedPassword => { + write!(f, "This is similar to a commonly used password.") + } + PasswordFeedback::TooShort(minlength) => write!( + f, + "Password too was short, needs to be at least {} characters long.", + minlength + ), + PasswordFeedback::UseAFewWordsAvoidCommonPhrases => { + write!(f, "Use a few words and avoid common phrases.") + } + PasswordFeedback::UseALongerKeyboardPatternWithMoreTurns => { + write!( + f, + "The password included keyboard patterns across too much of a single row." + ) + } + } + } +} + +#[cfg(test)] +mod tests { + use super::{TotpAlgo, TotpSecret}; + + #[test] + fn totp_to_string() { + let totp = TotpSecret { + accountname: "william".to_string(), + issuer: "blackhats".to_string(), + secret: vec![0xaa, 0xbb, 0xcc, 0xdd], + step: 30, + algo: TotpAlgo::Sha256, + digits: 6, + }; + let s = totp.to_uri(); + assert!(s == "otpauth://totp/blackhats:william?secret=VK54ZXI&issuer=blackhats&algorithm=SHA256&digits=6&period=30"); + + // check that invalid issuer/accounts are cleaned up. + let totp = TotpSecret { + accountname: "william:%3A".to_string(), + issuer: "blackhats australia".to_string(), + secret: vec![0xaa, 0xbb, 0xcc, 0xdd], + step: 30, + algo: TotpAlgo::Sha256, + digits: 6, + }; + let s = totp.to_uri(); + println!("{}", s); + assert!(s == "otpauth://totp/blackhats%20australia:william%3A%253A?secret=VK54ZXI&issuer=blackhats%20australia&algorithm=SHA256&digits=6&period=30"); + } +} diff --git a/proto/src/internal/error.rs b/proto/src/internal/error.rs new file mode 100644 index 000000000..b79a9d257 --- /dev/null +++ b/proto/src/internal/error.rs @@ -0,0 +1,147 @@ +use serde::{Deserialize, Serialize}; +use utoipa::ToSchema; +use uuid::Uuid; + +use super::credupdate::PasswordFeedback; + +/* ===== errors ===== */ +#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, ToSchema)] +#[serde(rename_all = "lowercase")] +pub enum SchemaError { + NotImplemented, + NoClassFound, + InvalidClass(Vec), + MissingMustAttribute(Vec), + InvalidAttribute(String), + InvalidAttributeSyntax(String), + AttributeNotValidForClass(String), + SupplementsNotSatisfied(Vec), + ExcludesNotSatisfied(Vec), + EmptyFilter, + Corrupted, + PhantomAttribute(String), +} + +#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, ToSchema)] +#[serde(rename_all = "lowercase")] +pub enum PluginError { + AttrUnique(String), + Base(String), + ReferentialIntegrity(String), + CredImport(String), + Oauth2Secrets, +} + +#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, ToSchema)] +#[serde(rename_all = "lowercase")] +pub enum ConsistencyError { + Unknown, + // Class, Attribute + SchemaClassMissingAttribute(String, String), + SchemaClassPhantomAttribute(String, String), + SchemaUuidNotUnique(Uuid), + QueryServerSearchFailure, + EntryUuidCorrupt(u64), + UuidIndexCorrupt(String), + UuidNotUnique(String), + RefintNotUpheld(u64), + MemberOfInvalid(u64), + InvalidAttributeType(String), + DuplicateUniqueAttribute, + InvalidSpn(u64), + SqliteIntegrityFailure, + BackendAllIdsSync, + BackendIndexSync, + ChangelogDesynchronised(u64), + ChangeStateDesynchronised(u64), + RuvInconsistent(String), + DeniedName(Uuid), +} + +#[derive(Serialize, Deserialize, Debug, ToSchema)] +#[serde(rename_all = "lowercase")] +pub enum OperationError { + SessionExpired, + EmptyRequest, + Backend, + NoMatchingEntries, + NoMatchingAttributes, + CorruptedEntry(u64), + CorruptedIndex(String), + // TODO: this should just be a vec of the ConsistencyErrors, surely? + ConsistencyError(Vec>), + SchemaViolation(SchemaError), + Plugin(PluginError), + FilterGeneration, + FilterUuidResolution, + InvalidAttributeName(String), + InvalidAttribute(String), + InvalidDbState, + InvalidCacheState, + InvalidValueState, + InvalidEntryId, + InvalidRequestState, + InvalidSyncState, + InvalidState, + InvalidEntryState, + InvalidUuid, + InvalidReplChangeId, + InvalidAcpState(String), + InvalidSchemaState(String), + InvalidAccountState(String), + MissingEntries, + ModifyAssertionFailed, + BackendEngine, + SqliteError, //(RusqliteError) + FsError, + SerdeJsonError, + SerdeCborError, + AccessDenied, + NotAuthenticated, + NotAuthorised, + InvalidAuthState(String), + InvalidSessionState, + SystemProtectedObject, + SystemProtectedAttribute, + PasswordQuality(Vec), + CryptographyError, + ResourceLimit, + QueueDisconnected, + Webauthn, + #[serde(with = "time::serde::timestamp")] + Wait(time::OffsetDateTime), + ReplReplayFailure, + ReplEntryNotChanged, + ReplInvalidRUVState, + ReplDomainLevelUnsatisfiable, + ReplDomainUuidMismatch, + ReplServerUuidSplitDataState, + TransactionAlreadyCommitted, + /// when you ask for a gid that's lower than a safe minimum + GidOverlapsSystemMin(u32), + /// When a name is denied by the system config + ValueDenyName, + // What about something like this for unique errors? + // Credential Update Errors + CU0001WebauthnAttestationNotTrusted, + CU0002WebauthnRegistrationError, + // ValueSet errors + VS0001IncomingReplSshPublicKey, + // Value Errors + VL0001ValueSshPublicKeyString, + // SCIM + SC0001IncomingSshPublicKey, + // Migration + MG0001InvalidReMigrationLevel, + MG0002RaiseDomainLevelExceedsMaximum, + MG0003ServerPhaseInvalidForMigration, +} + +impl PartialEq for OperationError { + fn eq(&self, other: &Self) -> bool { + // We do this to avoid InvalidPassword being checked as it's not + // derive PartialEq. Generally we only use the PartialEq for TESTING + // anyway. + std::mem::discriminant(self) == std::mem::discriminant(other) + } +} diff --git a/proto/src/internal.rs b/proto/src/internal/mod.rs similarity index 77% rename from proto/src/internal.rs rename to proto/src/internal/mod.rs index 1bdd958dc..3dc314cbd 100644 --- a/proto/src/internal.rs +++ b/proto/src/internal/mod.rs @@ -1,12 +1,29 @@ +//! Kanidm internal elements +//! +//! Items defined in this module *may* change between releases without notice. + use crate::constants::{ CONTENT_TYPE_GIF, CONTENT_TYPE_JPG, CONTENT_TYPE_PNG, CONTENT_TYPE_SVG, CONTENT_TYPE_WEBP, }; -use crate::v1::ApiTokenPurpose; use serde::{Deserialize, Serialize}; +use std::fmt; +use std::str::FromStr; use url::Url; use utoipa::ToSchema; use uuid::Uuid; +use num_enum::TryFromPrimitive; + +mod credupdate; +mod error; +mod raw; +mod token; + +pub use self::credupdate::*; +pub use self::error::*; +pub use self::raw::*; +pub use self::token::*; + #[derive(Debug, Serialize, Deserialize, Clone)] /// This is a description of a linked or connected application for a user. This is /// used in the UI to render applications on the dashboard for a user to access. @@ -20,15 +37,40 @@ pub enum AppLink { }, } -#[derive(Debug, Serialize, Deserialize, Clone)] +#[derive(Debug, Serialize, Deserialize, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash)] #[serde(rename_all = "lowercase")] -pub struct ScimSyncToken { - // uuid of the token? - pub token_id: Uuid, - #[serde(with = "time::serde::timestamp")] - pub issued_at: time::OffsetDateTime, - #[serde(default)] - pub purpose: ApiTokenPurpose, +#[derive(TryFromPrimitive)] +#[repr(u16)] +pub enum UiHint { + ExperimentalFeatures = 0, + PosixAccount = 1, + CredentialUpdate = 2, + SynchronisedAccount = 3, +} + +impl fmt::Display for UiHint { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + UiHint::PosixAccount => write!(f, "PosixAccount"), + UiHint::CredentialUpdate => write!(f, "CredentialUpdate"), + UiHint::ExperimentalFeatures => write!(f, "ExperimentalFeatures"), + UiHint::SynchronisedAccount => write!(f, "SynchronisedAccount"), + } + } +} + +impl FromStr for UiHint { + type Err = (); + + fn from_str(s: &str) -> Result { + match s { + "CredentialUpdate" => Ok(UiHint::CredentialUpdate), + "PosixAccount" => Ok(UiHint::PosixAccount), + "ExperimentalFeatures" => Ok(UiHint::ExperimentalFeatures), + "SynchronisedAccount" => Ok(UiHint::SynchronisedAccount), + _ => Err(()), + } + } } // State machine states and transitions for the identity verification system feature! diff --git a/proto/src/internal/raw.rs b/proto/src/internal/raw.rs new file mode 100644 index 000000000..b0d538d60 --- /dev/null +++ b/proto/src/internal/raw.rs @@ -0,0 +1,113 @@ +use serde::{Deserialize, Serialize}; +use utoipa::ToSchema; + +use crate::v1::Entry; + +#[derive(Debug, Serialize, Deserialize, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, ToSchema)] +#[serde(rename_all = "lowercase")] +pub enum Filter { + // This is attr - value + #[serde(alias = "Eq")] + Eq(String, String), + #[serde(alias = "Cnt")] + Cnt(String, String), + #[serde(alias = "Pres")] + Pres(String), + #[serde(alias = "Or")] + Or(Vec), + #[serde(alias = "And")] + And(Vec), + #[serde(alias = "AndNot")] + AndNot(Box), + #[serde(rename = "self", alias = "Self")] + SelfUuid, +} + +#[derive(Serialize, Deserialize, Debug, Clone, ToSchema)] +#[serde(rename_all = "lowercase")] +pub enum Modify { + Present(String, String), + Removed(String, String), + Purged(String), +} + +#[derive(Serialize, Deserialize, Debug, Clone, ToSchema)] +pub struct ModifyList { + pub mods: Vec, +} + +impl ModifyList { + pub fn new_list(mods: Vec) -> Self { + ModifyList { mods } + } +} + +#[derive(Debug, Serialize, Deserialize, ToSchema)] +pub struct SearchRequest { + pub filter: Filter, +} + +impl SearchRequest { + pub fn new(filter: Filter) -> Self { + SearchRequest { filter } + } +} + +#[derive(Debug, Serialize, Deserialize, ToSchema)] +pub struct SearchResponse { + pub entries: Vec, +} + +impl SearchResponse { + pub fn new(entries: Vec) -> Self { + SearchResponse { entries } + } +} + +#[derive(Debug, Serialize, Deserialize, ToSchema)] +pub struct CreateRequest { + pub entries: Vec, +} + +impl CreateRequest { + pub fn new(entries: Vec) -> Self { + CreateRequest { entries } + } +} + +#[derive(Debug, Serialize, Deserialize, ToSchema)] +pub struct DeleteRequest { + pub filter: Filter, +} + +impl DeleteRequest { + pub fn new(filter: Filter) -> Self { + DeleteRequest { filter } + } +} + +#[derive(Debug, Serialize, Deserialize, ToSchema)] +pub struct ModifyRequest { + // Probably needs a modlist? + pub filter: Filter, + pub modlist: ModifyList, +} + +impl ModifyRequest { + pub fn new(filter: Filter, modlist: ModifyList) -> Self { + ModifyRequest { filter, modlist } + } +} + +#[cfg(test)] +mod tests { + use super::Filter as ProtoFilter; + use crate::constants::ATTR_CLASS; + + #[test] + fn test_protofilter_simple() { + let pf: ProtoFilter = ProtoFilter::Pres(ATTR_CLASS.to_string()); + + println!("{:?}", serde_json::to_string(&pf).expect("JSON failure")); + } +} diff --git a/proto/src/internal/token.rs b/proto/src/internal/token.rs new file mode 100644 index 000000000..7af9212e5 --- /dev/null +++ b/proto/src/internal/token.rs @@ -0,0 +1,200 @@ +use super::UiHint; +use serde::{Deserialize, Serialize}; +use std::collections::BTreeSet; +use std::fmt; +use time::OffsetDateTime; +use utoipa::ToSchema; +use uuid::Uuid; + +use serde_with::skip_serializing_none; + +#[derive(Debug, Serialize, Deserialize, Clone, ToSchema)] +#[serde(rename_all = "lowercase")] +pub enum UatPurpose { + ReadOnly, + ReadWrite { + /// If none, there is no expiry, and this is always rw. If there is + /// an expiry, check that the current time < expiry. + #[serde(with = "time::serde::timestamp::option")] + expiry: Option, + }, +} + +/// The currently authenticated user, and any required metadata for them +/// to properly authorise them. This is similar in nature to oauth and the krb +/// PAC/PAD structures. This information is transparent to clients and CAN +/// be parsed by them! +/// +/// This structure and how it works will *very much* change over time from this +/// point onward! This means on updates, that sessions will invalidate in many +/// cases. +#[derive(Debug, Serialize, Deserialize, Clone, ToSchema)] +#[skip_serializing_none] +#[serde(rename_all = "lowercase")] +pub struct UserAuthToken { + pub session_id: Uuid, + #[serde(with = "time::serde::timestamp")] + pub issued_at: time::OffsetDateTime, + /// If none, there is no expiry, and this is always valid. If there is + /// an expiry, check that the current time < expiry. + #[serde(with = "time::serde::timestamp::option")] + pub expiry: Option, + pub purpose: UatPurpose, + pub uuid: Uuid, + pub displayname: String, + pub spn: String, + pub mail_primary: Option, + pub ui_hints: BTreeSet, + + pub limit_search_max_results: Option, + pub limit_search_max_filter_test: Option, +} + +impl fmt::Display for UserAuthToken { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + writeln!(f, "spn: {}", self.spn)?; + writeln!(f, "uuid: {}", self.uuid)?; + writeln!(f, "display: {}", self.displayname)?; + if let Some(exp) = self.expiry { + writeln!(f, "expiry: {}", exp)?; + } else { + writeln!(f, "expiry: -")?; + } + match &self.purpose { + UatPurpose::ReadOnly => writeln!(f, "purpose: read only")?, + UatPurpose::ReadWrite { + expiry: Some(expiry), + } => writeln!(f, "purpose: read write (expiry: {})", expiry)?, + UatPurpose::ReadWrite { expiry: None } => { + writeln!(f, "purpose: read write (expiry: none)")? + } + } + Ok(()) + } +} + +impl PartialEq for UserAuthToken { + fn eq(&self, other: &Self) -> bool { + self.session_id == other.session_id + } +} + +impl Eq for UserAuthToken {} + +impl UserAuthToken { + pub fn name(&self) -> &str { + self.spn.split_once('@').map(|x| x.0).unwrap_or(&self.spn) + } + + /// Show if the uat at a current point in time has active read-write + /// capabilities. + pub fn purpose_readwrite_active(&self, ct: time::OffsetDateTime) -> bool { + match self.purpose { + UatPurpose::ReadWrite { expiry: Some(exp) } => ct < exp, + _ => false, + } + } +} + +#[derive(Debug, Serialize, Deserialize, Clone, Default, ToSchema)] +#[serde(rename_all = "lowercase")] +pub enum ApiTokenPurpose { + #[default] + ReadOnly, + ReadWrite, + Synchronise, +} + +#[derive(Debug, Serialize, Deserialize, Clone, ToSchema)] +#[serde(rename_all = "lowercase")] +pub struct ApiToken { + // The account this is associated with. + pub account_id: Uuid, + pub token_id: Uuid, + pub label: String, + #[serde(with = "time::serde::timestamp::option")] + pub expiry: Option, + #[serde(with = "time::serde::timestamp")] + pub issued_at: time::OffsetDateTime, + // Defaults to ReadOnly if not present + #[serde(default)] + pub purpose: ApiTokenPurpose, +} + +impl fmt::Display for ApiToken { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + writeln!(f, "account_id: {}", self.account_id)?; + writeln!(f, "token_id: {}", self.token_id)?; + writeln!(f, "label: {}", self.label)?; + writeln!(f, "issued at: {}", self.issued_at)?; + if let Some(expiry) = self.expiry { + // if this fails we're in trouble! + #[allow(clippy::expect_used)] + let expiry_str = expiry + .to_offset( + time::UtcOffset::local_offset_at(OffsetDateTime::UNIX_EPOCH) + .unwrap_or(time::UtcOffset::UTC), + ) + .format(&time::format_description::well_known::Rfc3339) + .expect("Failed to format timestamp to RFC3339"); + writeln!(f, "token expiry: {}", expiry_str) + } else { + writeln!(f, "token expiry: never") + } + } +} + +impl PartialEq for ApiToken { + fn eq(&self, other: &Self) -> bool { + self.token_id == other.token_id + } +} + +impl Eq for ApiToken {} + +// This is similar to uat, but omits claims (they have no role in radius), and adds +// the radius secret field. +#[derive(Debug, Serialize, Deserialize, Clone, ToSchema)] +pub struct RadiusAuthToken { + pub name: String, + pub displayname: String, + pub uuid: String, + pub secret: String, + pub groups: Vec, +} + +impl fmt::Display for RadiusAuthToken { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + writeln!(f, "name: {}", self.name)?; + writeln!(f, "displayname: {}", self.displayname)?; + writeln!(f, "uuid: {}", self.uuid)?; + writeln!(f, "secret: {}", self.secret)?; + self.groups + .iter() + .try_for_each(|g| writeln!(f, "group: {}", g)) + } +} + +#[derive(Debug, Serialize, Deserialize, Clone)] +#[serde(rename_all = "lowercase")] +pub struct ScimSyncToken { + // uuid of the token? + pub token_id: Uuid, + #[serde(with = "time::serde::timestamp")] + pub issued_at: time::OffsetDateTime, + #[serde(default)] + pub purpose: ApiTokenPurpose, +} + +#[derive(Debug, Serialize, Deserialize, Clone, ToSchema)] +pub struct Group { + pub spn: String, + pub uuid: String, +} + +impl fmt::Display for Group { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "[ spn: {}, ", self.spn)?; + write!(f, "uuid: {} ]", self.uuid) + } +} diff --git a/proto/src/lib.rs b/proto/src/lib.rs index e4a0a294b..d18eee232 100644 --- a/proto/src/lib.rs +++ b/proto/src/lib.rs @@ -1,5 +1,14 @@ +//! Kanidm JSON protocol definitions +//! +//! This library defines the elements that are used by Kanidm's http APIs. +//! Each module has different support levels which define the projects policy +//! on change for the module. + #![deny(warnings)] #![warn(unused_extern_crates)] +// #![warn(missing_docs)] +#![deny(clippy::todo)] +#![deny(clippy::unimplemented)] #![deny(clippy::unwrap_used)] #![deny(clippy::expect_used)] #![deny(clippy::panic)] diff --git a/proto/src/oauth2.rs b/proto/src/oauth2.rs index 738a9873f..c0f615966 100644 --- a/proto/src/oauth2.rs +++ b/proto/src/oauth2.rs @@ -1,3 +1,5 @@ +//! Oauth2 RFC protocol definitions. + use std::collections::{BTreeMap, BTreeSet}; use base64urlsafedata::Base64UrlSafeData; @@ -20,6 +22,8 @@ pub struct PkceRequest { pub code_challenge_method: CodeChallengeMethod, } +/// An OAuth2 client redirects to the authorisation server with Authorisation Request +/// parameters. #[skip_serializing_none] #[derive(Serialize, Deserialize, Debug, Clone)] pub struct AuthorisationRequest { @@ -40,6 +44,8 @@ pub struct AuthorisationRequest { pub unknown_keys: BTreeMap, } +/// An OIDC client redirects to the authorisation server with Authorisation Request +/// parameters. #[skip_serializing_none] #[derive(Serialize, Deserialize, Debug, Clone, Default)] pub struct AuthorisationRequestOidc { @@ -53,8 +59,9 @@ pub struct AuthorisationRequestOidc { pub acr: Option, } -/// When we request to authorise, it can either prompt us for consent, -/// or it can immediately be granted due the past grant. +/// In response to an Authorisation request, the user may be prompted to consent to the +/// scopes requested by the OAuth2 client. If they have previously consented, they will +/// immediately proceed. #[derive(Serialize, Deserialize, Debug, Clone)] pub enum AuthorisationResponse { ConsentRequested { @@ -74,7 +81,6 @@ pub enum AuthorisationResponse { #[serde_as] #[skip_serializing_none] -// this is the equivalent of serde(skip_serializing_if = "Option::is_none") applied to ALL the options #[derive(Serialize, Deserialize, Debug)] #[serde(tag = "grant_type", rename_all = "snake_case")] pub enum GrantTypeReq { @@ -96,6 +102,7 @@ pub enum GrantTypeReq { }, } +/// An Access Token request. This requires a set of grant-type parameters to satisfy the request. #[skip_serializing_none] #[derive(Serialize, Deserialize, Debug)] pub struct AccessTokenRequest { @@ -117,43 +124,45 @@ impl From for AccessTokenRequest { } } -#[skip_serializing_none] -#[derive(Serialize, Deserialize, Debug)] -pub struct TokenRevokeRequest { - pub token: String, - /// Generally not needed. See: - /// - pub token_type_hint: Option, -} - -// The corresponding Response to a revoke request is empty body with 200. - +/// The #[skip_serializing_none] #[derive(Serialize, Deserialize, Debug)] pub struct AccessTokenResponse { - // Could be Base64UrlSafeData pub access_token: String, - // Enum? pub token_type: String, - // seconds. + /// Expiration relative to `now` in seconds. pub expires_in: u32, pub refresh_token: Option, /// Space separated list of scopes that were approved, if this differs from the /// original request. pub scope: Option, - /// Oidc puts the token here. + /// If the `openid` scope was requested, an `id_token` may be present in the response. pub id_token: Option, } +/// Request revocation of an Access or Refresh token. On success the response is OK 200 +/// with no body. #[skip_serializing_none] #[derive(Serialize, Deserialize, Debug)] -pub struct AccessTokenIntrospectRequest { +pub struct TokenRevokeRequest { pub token: String, - /// Generally not needed. See: + /// Not required for Kanidm. /// pub token_type_hint: Option, } +/// Request to introspect the identity of the account associated to a token. +#[skip_serializing_none] +#[derive(Serialize, Deserialize, Debug)] +pub struct AccessTokenIntrospectRequest { + pub token: String, + /// Not required for Kanidm. + /// + pub token_type_hint: Option, +} + +/// Response to an introspection request. If the token is inactive or revoked, only +/// `active` will be set to the value of `false`. #[skip_serializing_none] #[derive(Serialize, Deserialize, Debug)] pub struct AccessTokenIntrospectResponse { @@ -235,8 +244,9 @@ pub enum PkceAlg { #[derive(Serialize, Deserialize, Debug, PartialEq, Eq)] #[serde(rename_all = "UPPERCASE")] -// WE REFUSE TO SUPPORT NONE. DONT EVEN ASK. IT WON'T HAPPEN. +/// Algorithms supported for token signatures. Prefers `ES256` pub enum IdTokenSignAlg { + // WE REFUSE TO SUPPORT NONE. DONT EVEN ASK. IT WON'T HAPPEN. ES256, RS256, } @@ -292,9 +302,10 @@ fn require_request_uri_parameter_supported_default() -> bool { false } +/// The response to an OpenID connect discovery request +/// #[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, @@ -354,9 +365,9 @@ pub struct OidcDiscoveryResponse { pub code_challenge_methods_supported: Vec, } +/// The response to an OAuth2 rfc8414 metadata request #[skip_serializing_none] #[derive(Serialize, Deserialize, Debug)] -// https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderMetadata pub struct Oauth2Rfc8414MetadataResponse { pub issuer: Url, pub authorization_endpoint: Url, diff --git a/proto/src/scim_v1/mod.rs b/proto/src/scim_v1/mod.rs new file mode 100644 index 000000000..99a9b9970 --- /dev/null +++ b/proto/src/scim_v1/mod.rs @@ -0,0 +1,3 @@ +mod synch; + +pub use self::synch::*; diff --git a/proto/src/scim_v1.rs b/proto/src/scim_v1/synch.rs similarity index 96% rename from proto/src/scim_v1.rs rename to proto/src/scim_v1/synch.rs index c10113083..44547028f 100644 --- a/proto/src/scim_v1.rs +++ b/proto/src/scim_v1/synch.rs @@ -60,11 +60,14 @@ pub const SCIM_DIGITS: &str = "digits"; pub const SCIM_SECRET: &str = "secret"; pub const SCIM_STEP: &str = "step"; -pub const SCIM_SCHEMA_SYNC: &str = "urn:ietf:params:scim:schemas:kanidm:1.0:"; -pub const SCIM_SCHEMA_SYNC_PERSON: &str = "urn:ietf:params:scim:schemas:kanidm:1.0:person"; -pub const SCIM_SCHEMA_SYNC_ACCOUNT: &str = "urn:ietf:params:scim:schemas:kanidm:1.0:account"; +pub const SCIM_SCHEMA_SYNC_1: &str = "urn:ietf:params:scim:schemas:kanidm:sync:1:"; +pub const SCIM_SCHEMA_SYNC_ACCOUNT: &str = "urn:ietf:params:scim:schemas:kanidm:sync:1:account"; +pub const SCIM_SCHEMA_SYNC_GROUP: &str = "urn:ietf:params:scim:schemas:kanidm:sync:1:group"; +pub const SCIM_SCHEMA_SYNC_PERSON: &str = "urn:ietf:params:scim:schemas:kanidm:sync:1:person"; pub const SCIM_SCHEMA_SYNC_POSIXACCOUNT: &str = - "urn:ietf:params:scim:schemas:kanidm:1.0:posixaccount"; + "urn:ietf:params:scim:schemas:kanidm:sync:1:posixaccount"; +pub const SCIM_SCHEMA_SYNC_POSIXGROUP: &str = + "urn:ietf:params:scim:schemas:kanidm:sync:1:posixgroup"; #[derive(Serialize, Debug, Clone)] pub struct ScimTotp { @@ -206,9 +209,6 @@ impl Into for ScimSyncPerson { } } -pub const SCIM_SCHEMA_SYNC_GROUP: &str = "urn:ietf:params:scim:schemas:kanidm:1.0:group"; -pub const SCIM_SCHEMA_SYNC_POSIXGROUP: &str = "urn:ietf:params:scim:schemas:kanidm:1.0:posixgroup"; - #[derive(Serialize, Debug, Clone)] pub struct ScimExternalMember { pub external_id: String, diff --git a/proto/src/v1.rs b/proto/src/v1.rs deleted file mode 100644 index 94f61cb22..000000000 --- a/proto/src/v1.rs +++ /dev/null @@ -1,1321 +0,0 @@ -#![allow(non_upper_case_globals)] - -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 time::OffsetDateTime; -use url::Url; -use utoipa::ToSchema; -use uuid::Uuid; -use webauthn_rs_proto::{ - CreationChallengeResponse, PublicKeyCredential, RegisterPublicKeyCredential, - RequestChallengeResponse, -}; - -use crate::constants::{ATTR_GROUP, ATTR_LDAP_SSHPUBLICKEY}; - -// These proto implementations are here because they have public definitions - -#[derive(Clone, Copy, Debug, ToSchema)] -pub enum AccountType { - Person, - ServiceAccount, -} - -impl ToString for AccountType { - fn to_string(&self) -> String { - match self { - AccountType::Person => "person".to_string(), - AccountType::ServiceAccount => "service_account".to_string(), - } - } -} - -/* ===== errors ===== */ -#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, ToSchema)] -#[serde(rename_all = "lowercase")] -pub enum SchemaError { - NotImplemented, - NoClassFound, - InvalidClass(Vec), - MissingMustAttribute(Vec), - InvalidAttribute(String), - InvalidAttributeSyntax(String), - AttributeNotValidForClass(String), - SupplementsNotSatisfied(Vec), - ExcludesNotSatisfied(Vec), - EmptyFilter, - Corrupted, - PhantomAttribute(String), -} - -#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, ToSchema)] -#[serde(rename_all = "lowercase")] -pub enum PluginError { - AttrUnique(String), - Base(String), - ReferentialIntegrity(String), - CredImport(String), - Oauth2Secrets, -} - -#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, ToSchema)] -#[serde(rename_all = "lowercase")] -pub enum ConsistencyError { - Unknown, - // Class, Attribute - SchemaClassMissingAttribute(String, String), - SchemaClassPhantomAttribute(String, String), - SchemaUuidNotUnique(Uuid), - QueryServerSearchFailure, - EntryUuidCorrupt(u64), - UuidIndexCorrupt(String), - UuidNotUnique(String), - RefintNotUpheld(u64), - MemberOfInvalid(u64), - InvalidAttributeType(String), - DuplicateUniqueAttribute, - InvalidSpn(u64), - SqliteIntegrityFailure, - BackendAllIdsSync, - BackendIndexSync, - ChangelogDesynchronised(u64), - ChangeStateDesynchronised(u64), - RuvInconsistent(String), - DeniedName(Uuid), -} - -#[derive(Serialize, Deserialize, Debug, ToSchema, PartialEq, Eq, PartialOrd, Ord)] -#[serde(rename_all = "lowercase")] -pub enum PasswordFeedback { - // https://docs.rs/zxcvbn/latest/zxcvbn/feedback/enum.Suggestion.html - UseAFewWordsAvoidCommonPhrases, - NoNeedForSymbolsDigitsOrUppercaseLetters, - AddAnotherWordOrTwo, - CapitalizationDoesntHelpVeryMuch, - AllUppercaseIsAlmostAsEasyToGuessAsAllLowercase, - ReversedWordsArentMuchHarderToGuess, - PredictableSubstitutionsDontHelpVeryMuch, - UseALongerKeyboardPatternWithMoreTurns, - AvoidRepeatedWordsAndCharacters, - AvoidSequences, - AvoidRecentYears, - AvoidYearsThatAreAssociatedWithYou, - AvoidDatesAndYearsThatAreAssociatedWithYou, - // https://docs.rs/zxcvbn/latest/zxcvbn/feedback/enum.Warning.html - StraightRowsOfKeysAreEasyToGuess, - ShortKeyboardPatternsAreEasyToGuess, - RepeatsLikeAaaAreEasyToGuess, - RepeatsLikeAbcAbcAreOnlySlightlyHarderToGuess, - ThisIsATop10Password, - ThisIsATop100Password, - ThisIsACommonPassword, - ThisIsSimilarToACommonlyUsedPassword, - SequencesLikeAbcAreEasyToGuess, - RecentYearsAreEasyToGuess, - AWordByItselfIsEasyToGuess, - DatesAreOftenEasyToGuess, - NamesAndSurnamesByThemselvesAreEasyToGuess, - CommonNamesAndSurnamesAreEasyToGuess, - // Custom - TooShort(u32), - BadListed, - DontReusePasswords, -} - -/// Human-readable PasswordFeedback result. -impl fmt::Display for PasswordFeedback { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - PasswordFeedback::AddAnotherWordOrTwo => write!(f, "Add another word or two."), - PasswordFeedback::AllUppercaseIsAlmostAsEasyToGuessAsAllLowercase => write!( - f, - "All uppercase is almost as easy to guess as all lowercase." - ), - PasswordFeedback::AvoidDatesAndYearsThatAreAssociatedWithYou => write!( - f, - "Avoid dates and years that are associated with you or your account." - ), - PasswordFeedback::AvoidRecentYears => write!(f, "Avoid recent years."), - PasswordFeedback::AvoidRepeatedWordsAndCharacters => { - write!(f, "Avoid repeated words and characters.") - } - PasswordFeedback::AvoidSequences => write!(f, "Avoid sequences of characters."), - PasswordFeedback::AvoidYearsThatAreAssociatedWithYou => { - write!(f, "Avoid years that are associated with you.") - } - PasswordFeedback::AWordByItselfIsEasyToGuess => { - write!(f, "A word by itself is easy to guess.") - } - PasswordFeedback::BadListed => write!( - f, - "This password has been compromised or otherwise blocked and can not be used." - ), - PasswordFeedback::CapitalizationDoesntHelpVeryMuch => { - write!(f, "Capitalization doesn't help very much.") - } - PasswordFeedback::CommonNamesAndSurnamesAreEasyToGuess => { - write!(f, "Common names and surnames are easy to guess.") - } - PasswordFeedback::DatesAreOftenEasyToGuess => { - write!(f, "Dates are often easy to guess.") - } - PasswordFeedback::DontReusePasswords => { - write!( - f, - "Don't reuse passwords that already exist on your account" - ) - } - PasswordFeedback::NamesAndSurnamesByThemselvesAreEasyToGuess => { - write!(f, "Names and surnames by themselves are easy to guess.") - } - PasswordFeedback::NoNeedForSymbolsDigitsOrUppercaseLetters => { - write!(f, "No need for symbols, digits or upper-case letters.") - } - PasswordFeedback::PredictableSubstitutionsDontHelpVeryMuch => { - write!(f, "Predictable substitutions don't help very much.") - } - PasswordFeedback::RecentYearsAreEasyToGuess => { - write!(f, "Recent years are easy to guess.") - } - PasswordFeedback::RepeatsLikeAaaAreEasyToGuess => { - write!(f, "Repeats like 'aaa' are easy to guess.") - } - PasswordFeedback::RepeatsLikeAbcAbcAreOnlySlightlyHarderToGuess => write!( - f, - "Repeats like abcabcabc are only slightly harder to guess." - ), - PasswordFeedback::ReversedWordsArentMuchHarderToGuess => { - write!(f, "Reversed words aren't much harder to guess.") - } - PasswordFeedback::SequencesLikeAbcAreEasyToGuess => { - write!(f, "Sequences like 'abc' are easy to guess.") - } - PasswordFeedback::ShortKeyboardPatternsAreEasyToGuess => { - write!(f, "Short keyboard patterns are easy to guess.") - } - PasswordFeedback::StraightRowsOfKeysAreEasyToGuess => { - write!(f, "Straight rows of keys are easy to guess.") - } - PasswordFeedback::ThisIsACommonPassword => write!(f, "This is a common password."), - PasswordFeedback::ThisIsATop100Password => write!(f, "This is a top 100 password."), - PasswordFeedback::ThisIsATop10Password => write!(f, "This is a top 10 password."), - PasswordFeedback::ThisIsSimilarToACommonlyUsedPassword => { - write!(f, "This is similar to a commonly used password.") - } - PasswordFeedback::TooShort(minlength) => write!( - f, - "Password too was short, needs to be at least {} characters long.", - minlength - ), - PasswordFeedback::UseAFewWordsAvoidCommonPhrases => { - write!(f, "Use a few words and avoid common phrases.") - } - PasswordFeedback::UseALongerKeyboardPatternWithMoreTurns => { - write!( - f, - "The password included keyboard patterns across too much of a single row." - ) - } - } - } -} - -#[derive(Serialize, Deserialize, Debug, ToSchema)] -#[serde(rename_all = "lowercase")] -pub enum OperationError { - SessionExpired, - EmptyRequest, - Backend, - NoMatchingEntries, - NoMatchingAttributes, - CorruptedEntry(u64), - CorruptedIndex(String), - // TODO: this should just be a vec of the ConsistencyErrors, surely? - ConsistencyError(Vec>), - SchemaViolation(SchemaError), - Plugin(PluginError), - FilterGeneration, - FilterUuidResolution, - InvalidAttributeName(String), - InvalidAttribute(String), - InvalidDbState, - InvalidCacheState, - InvalidValueState, - InvalidEntryId, - InvalidRequestState, - InvalidSyncState, - InvalidState, - InvalidEntryState, - InvalidUuid, - InvalidReplChangeId, - InvalidAcpState(String), - InvalidSchemaState(String), - InvalidAccountState(String), - MissingEntries, - ModifyAssertionFailed, - BackendEngine, - SqliteError, //(RusqliteError) - FsError, - SerdeJsonError, - SerdeCborError, - AccessDenied, - NotAuthenticated, - NotAuthorised, - InvalidAuthState(String), - InvalidSessionState, - SystemProtectedObject, - SystemProtectedAttribute, - PasswordQuality(Vec), - CryptographyError, - ResourceLimit, - QueueDisconnected, - Webauthn, - #[serde(with = "time::serde::timestamp")] - Wait(time::OffsetDateTime), - ReplReplayFailure, - ReplEntryNotChanged, - ReplInvalidRUVState, - ReplDomainLevelUnsatisfiable, - ReplDomainUuidMismatch, - ReplServerUuidSplitDataState, - TransactionAlreadyCommitted, - /// when you ask for a gid that's lower than a safe minimum - GidOverlapsSystemMin(u32), - /// When a name is denied by the system config - ValueDenyName, - // What about something like this for unique errors? - // Credential Update Errors - CU0001WebauthnAttestationNotTrusted, - CU0002WebauthnRegistrationError, - // ValueSet errors - VS0001IncomingReplSshPublicKey, - // Value Errors - VL0001ValueSshPublicKeyString, - // SCIM - SC0001IncomingSshPublicKey, - // Migration - MG0001InvalidReMigrationLevel, - MG0002RaiseDomainLevelExceedsMaximum, - MG0003ServerPhaseInvalidForMigration, -} - -impl PartialEq for OperationError { - fn eq(&self, other: &Self) -> bool { - // We do this to avoid InvalidPassword being checked as it's not - // derive PartialEq. Generally we only use the PartialEq for TESTING - // anyway. - std::mem::discriminant(self) == std::mem::discriminant(other) - } -} - -/* ===== higher level types ===== */ -// These are all types that are conceptually layers on top of entry and -// friends. They allow us to process more complex requests and provide -// domain specific fields for the purposes of IDM, over the normal -// entry/ava/filter types. These related deeply to schema. - -#[derive(Debug, Serialize, Deserialize, Clone, ToSchema)] -pub struct Group { - pub spn: String, - pub uuid: String, -} - -impl fmt::Display for Group { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "[ spn: {}, ", self.spn)?; - write!(f, "uuid: {} ]", self.uuid) - } -} - -#[derive(Debug, Serialize, Deserialize, Clone, ToSchema)] -pub struct Claim { - pub name: String, - pub uuid: String, - // These can be ephemeral, or shortlived in a session. - // some may even need requesting. - // pub expiry: DateTime -} - -#[derive(Debug, Serialize, Deserialize, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash)] -#[serde(rename_all = "lowercase")] -#[derive(TryFromPrimitive)] -#[repr(u16)] -pub enum UiHint { - ExperimentalFeatures = 0, - PosixAccount = 1, - CredentialUpdate = 2, - SynchronisedAccount = 3, -} - -impl fmt::Display for UiHint { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - UiHint::PosixAccount => write!(f, "PosixAccount"), - UiHint::CredentialUpdate => write!(f, "CredentialUpdate"), - UiHint::ExperimentalFeatures => write!(f, "ExperimentalFeatures"), - UiHint::SynchronisedAccount => write!(f, "SynchronisedAccount"), - } - } -} - -impl FromStr for UiHint { - type Err = (); - - fn from_str(s: &str) -> Result { - match s { - "CredentialUpdate" => Ok(UiHint::CredentialUpdate), - "PosixAccount" => Ok(UiHint::PosixAccount), - "ExperimentalFeatures" => Ok(UiHint::ExperimentalFeatures), - "SynchronisedAccount" => Ok(UiHint::SynchronisedAccount), - _ => Err(()), - } - } -} - -#[derive(Debug, Serialize, Deserialize, Clone, ToSchema)] -#[serde(rename_all = "lowercase")] -pub enum UatPurposeStatus { - ReadOnly, - ReadWrite, - PrivilegeCapable, -} - -#[derive(Debug, Serialize, Deserialize, Clone, ToSchema)] -#[serde(rename_all = "lowercase")] -pub enum UatStatusState { - #[serde(with = "time::serde::timestamp")] - ExpiresAt(time::OffsetDateTime), - NeverExpires, - Revoked, -} - -impl fmt::Display for UatStatusState { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - UatStatusState::ExpiresAt(odt) => write!(f, "expires at {}", odt), - UatStatusState::NeverExpires => write!(f, "never expires"), - UatStatusState::Revoked => write!(f, "revoked"), - } - } -} - -#[derive(Debug, Serialize, Deserialize, Clone, ToSchema)] -#[serde(rename_all = "lowercase")] -pub struct UatStatus { - pub account_id: Uuid, - pub session_id: Uuid, - pub state: UatStatusState, - #[serde(with = "time::serde::timestamp")] - pub issued_at: time::OffsetDateTime, - pub purpose: UatPurposeStatus, -} - -impl fmt::Display for UatStatus { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - writeln!(f, "account_id: {}", self.account_id)?; - writeln!(f, "session_id: {}", self.session_id)?; - writeln!(f, "state: {}", self.state)?; - writeln!(f, "issued_at: {}", self.issued_at)?; - match &self.purpose { - UatPurposeStatus::ReadOnly => writeln!(f, "purpose: read only")?, - UatPurposeStatus::ReadWrite => writeln!(f, "purpose: read write")?, - UatPurposeStatus::PrivilegeCapable => writeln!(f, "purpose: privilege capable")?, - } - Ok(()) - } -} - -#[derive(Debug, Serialize, Deserialize, Clone, ToSchema)] -#[serde(rename_all = "lowercase")] -pub enum UatPurpose { - ReadOnly, - ReadWrite { - /// If none, there is no expiry, and this is always rw. If there is - /// an expiry, check that the current time < expiry. - #[serde(with = "time::serde::timestamp::option")] - expiry: Option, - }, -} - -/// The currently authenticated user, and any required metadata for them -/// to properly authorise them. This is similar in nature to oauth and the krb -/// PAC/PAD structures. This information is transparent to clients and CAN -/// be parsed by them! -/// -/// This structure and how it works will *very much* change over time from this -/// point onward! This means on updates, that sessions will invalidate in many -/// cases. -#[derive(Debug, Serialize, Deserialize, Clone, ToSchema)] -#[skip_serializing_none] -#[serde(rename_all = "lowercase")] -pub struct UserAuthToken { - pub session_id: Uuid, - #[serde(with = "time::serde::timestamp")] - pub issued_at: time::OffsetDateTime, - /// If none, there is no expiry, and this is always valid. If there is - /// an expiry, check that the current time < expiry. - #[serde(with = "time::serde::timestamp::option")] - pub expiry: Option, - pub purpose: UatPurpose, - pub uuid: Uuid, - pub displayname: String, - pub spn: String, - pub mail_primary: Option, - pub ui_hints: BTreeSet, - - pub limit_search_max_results: Option, - pub limit_search_max_filter_test: Option, -} - -impl fmt::Display for UserAuthToken { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - writeln!(f, "spn: {}", self.spn)?; - writeln!(f, "uuid: {}", self.uuid)?; - writeln!(f, "display: {}", self.displayname)?; - if let Some(exp) = self.expiry { - writeln!(f, "expiry: {}", exp)?; - } else { - writeln!(f, "expiry: -")?; - } - match &self.purpose { - UatPurpose::ReadOnly => writeln!(f, "purpose: read only")?, - UatPurpose::ReadWrite { - expiry: Some(expiry), - } => writeln!(f, "purpose: read write (expiry: {})", expiry)?, - UatPurpose::ReadWrite { expiry: None } => { - writeln!(f, "purpose: read write (expiry: none)")? - } - } - Ok(()) - } -} - -impl PartialEq for UserAuthToken { - fn eq(&self, other: &Self) -> bool { - self.session_id == other.session_id - } -} - -impl Eq for UserAuthToken {} - -impl UserAuthToken { - pub fn name(&self) -> &str { - self.spn.split_once('@').map(|x| x.0).unwrap_or(&self.spn) - } - - /// Show if the uat at a current point in time has active read-write - /// capabilities. - pub fn purpose_readwrite_active(&self, ct: time::OffsetDateTime) -> bool { - match self.purpose { - UatPurpose::ReadWrite { expiry: Some(exp) } => ct < exp, - _ => false, - } - } -} - -#[derive(Debug, Serialize, Deserialize, Clone, Default, ToSchema)] -#[serde(rename_all = "lowercase")] -pub enum ApiTokenPurpose { - #[default] - ReadOnly, - ReadWrite, - Synchronise, -} - -#[derive(Debug, Serialize, Deserialize, Clone, ToSchema)] -#[serde(rename_all = "lowercase")] -pub struct ApiToken { - // The account this is associated with. - pub account_id: Uuid, - pub token_id: Uuid, - pub label: String, - #[serde(with = "time::serde::timestamp::option")] - pub expiry: Option, - #[serde(with = "time::serde::timestamp")] - pub issued_at: time::OffsetDateTime, - // Defaults to ReadOnly if not present - #[serde(default)] - pub purpose: ApiTokenPurpose, -} - -impl fmt::Display for ApiToken { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - writeln!(f, "account_id: {}", self.account_id)?; - writeln!(f, "token_id: {}", self.token_id)?; - writeln!(f, "label: {}", self.label)?; - writeln!(f, "issued at: {}", self.issued_at)?; - if let Some(expiry) = self.expiry { - // if this fails we're in trouble! - #[allow(clippy::expect_used)] - let expiry_str = expiry - .to_offset( - time::UtcOffset::local_offset_at(OffsetDateTime::UNIX_EPOCH) - .unwrap_or(time::UtcOffset::UTC), - ) - .format(&time::format_description::well_known::Rfc3339) - .expect("Failed to format timestamp to RFC3339"); - writeln!(f, "token expiry: {}", expiry_str) - } else { - writeln!(f, "token expiry: never") - } - } -} - -impl PartialEq for ApiToken { - fn eq(&self, other: &Self) -> bool { - self.token_id == other.token_id - } -} - -impl Eq for ApiToken {} - -#[derive(Debug, Serialize, Deserialize, Clone, ToSchema)] -#[serde(rename_all = "lowercase")] -pub struct ApiTokenGenerate { - pub label: String, - #[serde(with = "time::serde::timestamp::option")] - pub expiry: Option, - pub read_write: bool, -} - -// UAT will need a downcast to Entry, which adds in the claims to the entry -// for the purpose of filtering. - -// This is similar to uat, but omits claims (they have no role in radius), and adds -// the radius secret field. -#[derive(Debug, Serialize, Deserialize, Clone, ToSchema)] -pub struct RadiusAuthToken { - pub name: String, - pub displayname: String, - pub uuid: String, - pub secret: String, - pub groups: Vec, -} - -impl fmt::Display for RadiusAuthToken { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - writeln!(f, "name: {}", self.name)?; - writeln!(f, "displayname: {}", self.displayname)?; - writeln!(f, "uuid: {}", self.uuid)?; - writeln!(f, "secret: {}", self.secret)?; - self.groups - .iter() - .try_for_each(|g| writeln!(f, "group: {}", g)) - } -} - -#[derive(Debug, Serialize, Deserialize, Clone, ToSchema)] -pub struct UnixGroupToken { - pub name: String, - pub spn: String, - pub uuid: Uuid, - pub gidnumber: u32, -} - -impl fmt::Display for UnixGroupToken { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "[ spn: {}, ", self.spn)?; - write!(f, "gidnumber: {} ", self.gidnumber)?; - write!(f, "name: {}, ", self.name)?; - write!(f, "uuid: {} ]", self.uuid) - } -} - -#[derive(Debug, Serialize, Deserialize, Clone, ToSchema)] -pub struct GroupUnixExtend { - pub gidnumber: Option, -} - -#[skip_serializing_none] -#[derive(Debug, Serialize, Deserialize, Clone, ToSchema)] -pub struct UnixUserToken { - pub name: String, - pub spn: String, - pub displayname: String, - pub gidnumber: u32, - pub uuid: Uuid, - pub shell: Option, - pub groups: Vec, - pub sshkeys: Vec, - // The default value of bool is false. - #[serde(default)] - pub valid: bool, -} - -impl fmt::Display for UnixUserToken { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - writeln!(f, "---")?; - writeln!(f, "spn: {}", self.spn)?; - writeln!(f, "name: {}", self.name)?; - writeln!(f, "displayname: {}", self.displayname)?; - writeln!(f, "uuid: {}", self.uuid)?; - match &self.shell { - Some(s) => writeln!(f, "shell: {}", s)?, - None => writeln!(f, "shell: ")?, - } - self.sshkeys - .iter() - .try_for_each(|s| writeln!(f, "{}: {}", ATTR_LDAP_SSHPUBLICKEY, s))?; - self.groups - .iter() - .try_for_each(|g| writeln!(f, "{}: {}", ATTR_GROUP, g)) - } -} - -#[derive(Debug, Serialize, Deserialize, Clone, ToSchema)] -#[serde(deny_unknown_fields)] -pub struct AccountUnixExtend { - pub gidnumber: Option, - // TODO: rename shell to loginshell everywhere we can find - /// The internal attribute is "loginshell" but we use shell in the API currently - #[serde(alias = "loginshell")] - pub shell: Option, -} - -/* -#[derive(Debug, Serialize, Deserialize, Clone)] -pub struct AccountOrgPersonExtend { - pub mail: String, -} -*/ - -#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq, ToSchema)] -pub enum CredentialDetailType { - Password, - GeneratedPassword, - Passkey(Vec), - /// totp, webauthn - PasswordMfa(Vec, Vec, usize), -} - -#[derive(Debug, Serialize, Deserialize, Clone, ToSchema)] -pub struct CredentialDetail { - pub uuid: Uuid, - pub type_: CredentialDetailType, -} - -impl fmt::Display for CredentialDetail { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - writeln!(f, "uuid: {}", self.uuid)?; - /* - writeln!(f, "claims:")?; - for claim in &self.claims { - writeln!(f, " * {}", claim)?; - } - */ - match &self.type_ { - CredentialDetailType::Password => writeln!(f, "password: set"), - CredentialDetailType::GeneratedPassword => writeln!(f, "generated password: set"), - CredentialDetailType::Passkey(labels) => { - if labels.is_empty() { - writeln!(f, "passkeys: none registered") - } else { - writeln!(f, "passkeys:")?; - for label in labels { - writeln!(f, " * {}", label)?; - } - write!(f, "") - } - } - CredentialDetailType::PasswordMfa(totp_labels, wan_labels, backup_code) => { - writeln!(f, "password: set")?; - - if !totp_labels.is_empty() { - writeln!(f, "totp:")?; - for label in totp_labels { - writeln!(f, " * {}", label)?; - } - } else { - writeln!(f, "totp: disabled")?; - } - - if *backup_code > 0 { - writeln!(f, "backup_code: enabled")?; - } else { - writeln!(f, "backup_code: disabled")?; - } - - if !wan_labels.is_empty() { - // We no longer show the deprecated security key case by default. - writeln!(f, " ⚠️ warning - security keys are deprecated.")?; - writeln!(f, " ⚠️ you should re-enroll these to passkeys.")?; - writeln!(f, "security keys:")?; - for label in wan_labels { - writeln!(f, " * {}", label)?; - } - write!(f, "") - } else { - write!(f, "") - } - } - } - } -} - -#[derive(Debug, Serialize, Deserialize, Clone, ToSchema)] -pub struct PasskeyDetail { - pub uuid: Uuid, - pub tag: String, -} - -#[derive(Debug, Serialize, Deserialize, Clone, ToSchema)] -pub struct CredentialStatus { - pub creds: Vec, -} - -impl fmt::Display for CredentialStatus { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - for cred in &self.creds { - writeln!(f, "---")?; - cred.fmt(f)?; - } - writeln!(f, "---") - } -} - -#[derive(Debug, Serialize, Deserialize, Clone, ToSchema)] -pub struct BackupCodesView { - pub backup_codes: Vec, -} - -/* ===== low level proto types ===== */ - -// ProtoEntry vs Entry -// There is a good future reason for this separation. It allows changing -// the in memory server core entry type, without affecting the protoEntry type -// - -#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq, Default, ToSchema)] -pub struct Entry { - pub attrs: BTreeMap>, -} - -impl fmt::Display for Entry { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - writeln!(f, "---")?; - self.attrs - .iter() - .try_for_each(|(k, vs)| vs.iter().try_for_each(|v| writeln!(f, "{}: {}", k, v))) - } -} - -#[derive(Debug, Serialize, Deserialize, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, ToSchema)] -#[serde(rename_all = "lowercase")] -pub enum Filter { - // This is attr - value - #[serde(alias = "Eq")] - Eq(String, String), - #[serde(alias = "Cnt")] - Cnt(String, String), - #[serde(alias = "Pres")] - Pres(String), - #[serde(alias = "Or")] - Or(Vec), - #[serde(alias = "And")] - And(Vec), - #[serde(alias = "AndNot")] - AndNot(Box), - #[serde(rename = "self", alias = "Self")] - SelfUuid, -} - -#[derive(Serialize, Deserialize, Debug, Clone, ToSchema)] -#[serde(rename_all = "lowercase")] -pub enum Modify { - Present(String, String), - Removed(String, String), - Purged(String), -} - -#[derive(Serialize, Deserialize, Debug, Clone, ToSchema)] -pub struct ModifyList { - pub mods: Vec, -} - -impl ModifyList { - pub fn new_list(mods: Vec) -> Self { - ModifyList { mods } - } -} - -#[derive(Debug, Serialize, Deserialize, ToSchema)] -pub struct SearchRequest { - pub filter: Filter, -} - -impl SearchRequest { - pub fn new(filter: Filter) -> Self { - SearchRequest { filter } - } -} - -#[derive(Debug, Serialize, Deserialize, ToSchema)] -pub struct SearchResponse { - pub entries: Vec, -} - -impl SearchResponse { - pub fn new(entries: Vec) -> Self { - SearchResponse { entries } - } -} - -#[derive(Debug, Serialize, Deserialize, ToSchema)] -pub struct CreateRequest { - pub entries: Vec, -} - -impl CreateRequest { - pub fn new(entries: Vec) -> Self { - CreateRequest { entries } - } -} - -#[derive(Debug, Serialize, Deserialize, ToSchema)] -pub struct DeleteRequest { - pub filter: Filter, -} - -impl DeleteRequest { - pub fn new(filter: Filter) -> Self { - DeleteRequest { filter } - } -} - -#[derive(Debug, Serialize, Deserialize, ToSchema)] -pub struct ModifyRequest { - // Probably needs a modlist? - pub filter: Filter, - pub modlist: ModifyList, -} - -impl ModifyRequest { - pub fn new(filter: Filter, modlist: ModifyList) -> Self { - ModifyRequest { filter, modlist } - } -} - -// Login is a multi-step process potentially. First the client says who they -// want to request -// -// we respond with a set of possible authentications that can proceed, and perhaps -// we indicate which options must/may? -// -// The client can then step and negotiate each. -// -// This continues until a LoginSuccess, or LoginFailure is returned. -// -// On loginSuccess, we send a cookie, and that allows the token to be -// generated. The cookie can be shared between servers. -#[derive(Serialize, Deserialize, ToSchema)] -#[serde(rename_all = "lowercase")] -pub enum AuthCredential { - Anonymous, - Password(String), - Totp(u32), - SecurityKey(Box), - BackupCode(String), - // Should this just be discoverable? - Passkey(Box), -} - -impl fmt::Debug for AuthCredential { - fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { - match self { - AuthCredential::Anonymous => write!(fmt, "Anonymous"), - AuthCredential::Password(_) => write!(fmt, "Password(_)"), - AuthCredential::Totp(_) => write!(fmt, "TOTP(_)"), - AuthCredential::SecurityKey(_) => write!(fmt, "SecurityKey(_)"), - AuthCredential::BackupCode(_) => write!(fmt, "BackupCode(_)"), - AuthCredential::Passkey(_) => write!(fmt, "Passkey(_)"), - } - } -} - -#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialOrd, Ord, ToSchema)] -#[serde(rename_all = "lowercase")] -pub enum AuthMech { - Anonymous, - Password, - PasswordMfa, - Passkey, -} - -impl PartialEq for AuthMech { - fn eq(&self, other: &Self) -> bool { - std::mem::discriminant(self) == std::mem::discriminant(other) - } -} - -impl fmt::Display for AuthMech { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - AuthMech::Anonymous => write!(f, "Anonymous (no credentials)"), - AuthMech::Password => write!(f, "Password"), - AuthMech::PasswordMfa => write!(f, "TOTP/Backup Code and Password"), - AuthMech::Passkey => write!(f, "Passkey"), - } - } -} - -#[derive(Debug, Serialize, Deserialize, Copy, Clone, ToSchema)] -#[serde(rename_all = "lowercase")] -// TODO: what is this actually used for? -pub enum AuthIssueSession { - Token, -} - -#[derive(Debug, Serialize, Deserialize, ToSchema)] -#[serde(rename_all = "lowercase")] -pub enum AuthStep { - /// "I want to authenticate with this username" - Init(String), - /// A new way to issue sessions. Doing this as a new init type - /// to prevent breaking existing clients. Allows requesting of the type - /// of session that will be issued at the end if successful. - Init2 { - username: String, - issue: AuthIssueSession, - #[serde(default)] - /// If true, the session will have r/w access. - privileged: bool, - }, - /// We want to talk to you like this. - Begin(AuthMech), - /// Provide a response to a challenge. - Cred(AuthCredential), -} - -// Request auth for identity X with roles Y? -#[derive(Debug, Serialize, Deserialize, ToSchema)] -pub struct AuthRequest { - pub step: AuthStep, -} - -// Respond with the list of auth types and nonce, etc. -// It can also contain a denied, or success. -#[derive(Debug, Serialize, Deserialize, Clone, ToSchema)] -#[serde(rename_all = "lowercase")] -pub enum AuthAllowed { - Anonymous, - BackupCode, - Password, - Totp, - SecurityKey(RequestChallengeResponse), - Passkey(RequestChallengeResponse), -} - -impl PartialEq for AuthAllowed { - fn eq(&self, other: &Self) -> bool { - std::mem::discriminant(self) == std::mem::discriminant(other) - } -} - -impl From<&AuthAllowed> for u8 { - fn from(a: &AuthAllowed) -> u8 { - match a { - AuthAllowed::Anonymous => 0, - AuthAllowed::Password => 1, - AuthAllowed::BackupCode => 2, - AuthAllowed::Totp => 3, - AuthAllowed::Passkey(_) => 4, - AuthAllowed::SecurityKey(_) => 5, - } - } -} - -impl Eq for AuthAllowed {} - -impl Ord for AuthAllowed { - fn cmp(&self, other: &Self) -> Ordering { - let self_ord: u8 = self.into(); - let other_ord: u8 = other.into(); - self_ord.cmp(&other_ord) - } -} - -impl PartialOrd for AuthAllowed { - fn partial_cmp(&self, other: &Self) -> Option { - Some(self.cmp(other)) - } -} - -impl fmt::Display for AuthAllowed { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - AuthAllowed::Anonymous => write!(f, "Anonymous (no credentials)"), - AuthAllowed::Password => write!(f, "Password"), - AuthAllowed::BackupCode => write!(f, "Backup Code"), - AuthAllowed::Totp => write!(f, "TOTP"), - AuthAllowed::SecurityKey(_) => write!(f, "Security Token"), - AuthAllowed::Passkey(_) => write!(f, "Passkey"), - } - } -} - -#[derive(Debug, Serialize, Deserialize, ToSchema)] -#[serde(rename_all = "lowercase")] -pub enum AuthState { - /// You need to select how you want to talk to me. - Choose(Vec), - /// Continue to auth, allowed mechanisms/challenges listed. - Continue(Vec), - /// Something was bad, your session is terminated and no cookie. - Denied(String), - /// Everything is good, your bearer token has been issued and is within the result. - Success(String), -} - -#[derive(Debug, Serialize, Deserialize, ToSchema)] -pub struct AuthResponse { - pub sessionid: Uuid, - pub state: AuthState, -} - -// Types needed for setting credentials -/* -#[derive(Debug, Serialize, Deserialize)] -#[serde(rename_all = "lowercase")] -pub enum SetCredentialRequest { - Password(String), - GeneratePassword, - TotpGenerate, - TotpVerify(Uuid, u32), - TotpAcceptSha1(Uuid), - TotpRemove, - BackupCodeGenerate, - BackupCodeRemove, -} -*/ - -#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)] -#[serde(rename_all = "lowercase")] -pub enum TotpAlgo { - Sha1, - Sha256, - Sha512, -} - -impl fmt::Display for TotpAlgo { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match self { - TotpAlgo::Sha1 => write!(f, "SHA1"), - TotpAlgo::Sha256 => write!(f, "SHA256"), - TotpAlgo::Sha512 => write!(f, "SHA512"), - } - } -} - -#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)] -pub struct TotpSecret { - pub accountname: String, - /// User-facing name of the system, issuer of the TOTP - pub issuer: String, - pub secret: Vec, - pub algo: TotpAlgo, - pub step: u64, - pub digits: u8, -} - -impl TotpSecret { - /// - pub fn to_uri(&self) -> String { - let accountname = urlencoding::Encoded(&self.accountname); - let issuer = urlencoding::Encoded(&self.issuer); - let label = format!("{}:{}", issuer, accountname); - let algo = self.algo.to_string(); - let secret = self.get_secret(); - let period = self.step; - let digits = self.digits; - - format!( - "otpauth://totp/{}?secret={}&issuer={}&algorithm={}&digits={}&period={}", - label, secret, issuer, algo, digits, period - ) - } - - pub fn get_secret(&self) -> String { - base32::encode(base32::Alphabet::RFC4648 { padding: false }, &self.secret) - } -} - -#[derive(Debug, Serialize, Deserialize, ToSchema)] -pub struct CUIntentToken { - pub token: String, -} - -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, ToSchema)] -pub struct CUSessionToken { - pub token: String, -} - -#[derive(Clone, Serialize, Deserialize)] -#[serde(rename_all = "lowercase")] -pub enum CURequest { - PrimaryRemove, - Password(String), - CancelMFAReg, - TotpGenerate, - TotpVerify(u32, String), - TotpAcceptSha1, - TotpRemove(String), - BackupCodeGenerate, - BackupCodeRemove, - PasskeyInit, - PasskeyFinish(String, RegisterPublicKeyCredential), - PasskeyRemove(Uuid), - AttestedPasskeyInit, - AttestedPasskeyFinish(String, RegisterPublicKeyCredential), - AttestedPasskeyRemove(Uuid), -} - -impl fmt::Debug for CURequest { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let t = match self { - CURequest::PrimaryRemove => "CURequest::PrimaryRemove", - CURequest::Password(_) => "CURequest::Password", - CURequest::CancelMFAReg => "CURequest::CancelMFAReg", - CURequest::TotpGenerate => "CURequest::TotpGenerate", - CURequest::TotpVerify(_, _) => "CURequest::TotpVerify", - CURequest::TotpAcceptSha1 => "CURequest::TotpAcceptSha1", - CURequest::TotpRemove(_) => "CURequest::TotpRemove", - CURequest::BackupCodeGenerate => "CURequest::BackupCodeGenerate", - CURequest::BackupCodeRemove => "CURequest::BackupCodeRemove", - CURequest::PasskeyInit => "CURequest::PasskeyInit", - CURequest::PasskeyFinish(_, _) => "CURequest::PasskeyFinish", - CURequest::PasskeyRemove(_) => "CURequest::PasskeyRemove", - CURequest::AttestedPasskeyInit => "CURequest::AttestedPasskeyInit", - CURequest::AttestedPasskeyFinish(_, _) => "CURequest::AttestedPasskeyFinish", - CURequest::AttestedPasskeyRemove(_) => "CURequest::AttestedPasskeyRemove", - }; - writeln!(f, "{}", t) - } -} - -#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)] -pub enum CURegState { - // Nothing in progress. - None, - TotpCheck(TotpSecret), - TotpTryAgain, - TotpInvalidSha1, - BackupCodes(Vec), - Passkey(CreationChallengeResponse), - AttestedPasskey(CreationChallengeResponse), -} - -#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)] -pub enum CUExtPortal { - None, - Hidden, - Some(Url), -} - -#[derive(Debug, Clone, Copy, Serialize, Deserialize, ToSchema, PartialEq)] -pub enum CUCredState { - Modifiable, - DeleteOnly, - AccessDeny, - PolicyDeny, - // Disabled, -} - -#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)] -pub enum CURegWarning { - MfaRequired, - PasskeyRequired, - AttestedPasskeyRequired, - AttestedResidentKeyRequired, - Unsatisfiable, - WebauthnAttestationUnsatisfiable, -} - -#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)] -pub struct CUStatus { - // Display values - pub spn: String, - pub displayname: String, - pub ext_cred_portal: CUExtPortal, - // Internal State Tracking - pub mfaregstate: CURegState, - // Display hints + The credential details. - pub can_commit: bool, - pub warnings: Vec, - pub primary: Option, - pub primary_state: CUCredState, - pub passkeys: Vec, - pub passkeys_state: CUCredState, - pub attested_passkeys: Vec, - pub attested_passkeys_state: CUCredState, - pub attested_passkeys_allowed_devices: Vec, -} - -#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq, ToSchema)] -pub struct WhoamiResponse { - // Should we just embed the entry? Or destructure it? - pub youare: Entry, -} - -impl WhoamiResponse { - pub fn new(youare: Entry) -> Self { - WhoamiResponse { youare } - } -} - -// Simple string value provision. -#[derive(Debug, Serialize, Deserialize, ToSchema)] -pub struct SingleStringRequest { - pub value: String, -} - -impl SingleStringRequest { - pub fn new(s: String) -> Self { - SingleStringRequest { value: s } - } -} -// Use OperationResponse here ... - -#[cfg(test)] -mod tests { - use crate::constants::ATTR_CLASS; - use crate::v1::{Filter as ProtoFilter, TotpAlgo, TotpSecret}; - - #[test] - fn test_protofilter_simple() { - let pf: ProtoFilter = ProtoFilter::Pres(ATTR_CLASS.to_string()); - - println!("{:?}", serde_json::to_string(&pf).expect("JSON failure")); - } - - #[test] - fn totp_to_string() { - let totp = TotpSecret { - accountname: "william".to_string(), - issuer: "blackhats".to_string(), - secret: vec![0xaa, 0xbb, 0xcc, 0xdd], - step: 30, - algo: TotpAlgo::Sha256, - digits: 6, - }; - let s = totp.to_uri(); - assert!(s == "otpauth://totp/blackhats:william?secret=VK54ZXI&issuer=blackhats&algorithm=SHA256&digits=6&period=30"); - - // check that invalid issuer/accounts are cleaned up. - let totp = TotpSecret { - accountname: "william:%3A".to_string(), - issuer: "blackhats australia".to_string(), - secret: vec![0xaa, 0xbb, 0xcc, 0xdd], - step: 30, - algo: TotpAlgo::Sha256, - digits: 6, - }; - let s = totp.to_uri(); - println!("{}", s); - assert!(s == "otpauth://totp/blackhats%20australia:william%3A%253A?secret=VK54ZXI&issuer=blackhats%20australia&algorithm=SHA256&digits=6&period=30"); - } -} diff --git a/proto/src/v1/auth.rs b/proto/src/v1/auth.rs new file mode 100644 index 000000000..c20545da2 --- /dev/null +++ b/proto/src/v1/auth.rs @@ -0,0 +1,189 @@ +use serde::{Deserialize, Serialize}; +use std::cmp::Ordering; +use std::fmt; +use utoipa::ToSchema; +use uuid::Uuid; + +use webauthn_rs_proto::PublicKeyCredential; +use webauthn_rs_proto::RequestChallengeResponse; + +/// Authentication to Kanidm is a stepped process. +/// +/// The session is fist initialised with the requested username. +/// +/// In response the list of supported authentication mechanisms is provided. +/// +/// The user chooses the authentication mechanism to proceed with. +/// +/// The server responds with a challenge that the user provides a credential +/// to satisfy. This challenge and response process continues until a credential +/// fails to validate, an error occurs, or successful authentication is complete. +#[derive(Debug, Serialize, Deserialize, ToSchema)] +#[serde(rename_all = "lowercase")] +pub enum AuthStep { + /// Initialise a new authentication session + Init(String), + /// Initialise a new authentication session with extra flags + /// for requesting different types of session tokens or + /// immediate access to privileges. + Init2 { + username: String, + issue: AuthIssueSession, + #[serde(default)] + /// If true, the session will have r/w access. + privileged: bool, + }, + /// Request the named authentication mechanism to proceed + Begin(AuthMech), + /// Provide a credential in response to a challenge + Cred(AuthCredential), +} + +/// The response to an AuthStep request. +#[derive(Debug, Serialize, Deserialize, ToSchema)] +#[serde(rename_all = "lowercase")] +pub enum AuthState { + /// You need to select how you want to proceed. + Choose(Vec), + /// Continue to auth, allowed mechanisms/challenges listed. + Continue(Vec), + /// Something was bad, your session is terminated and no cookie. + Denied(String), + /// Everything is good, your bearer token has been issued and is within. + Success(String), +} + +/// The credential challenge provided by a user. +#[derive(Serialize, Deserialize, ToSchema)] +#[serde(rename_all = "lowercase")] +pub enum AuthCredential { + Anonymous, + Password(String), + Totp(u32), + SecurityKey(Box), + BackupCode(String), + // Should this just be discoverable? + Passkey(Box), +} + +impl fmt::Debug for AuthCredential { + fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { + match self { + AuthCredential::Anonymous => write!(fmt, "Anonymous"), + AuthCredential::Password(_) => write!(fmt, "Password(_)"), + AuthCredential::Totp(_) => write!(fmt, "TOTP(_)"), + AuthCredential::SecurityKey(_) => write!(fmt, "SecurityKey(_)"), + AuthCredential::BackupCode(_) => write!(fmt, "BackupCode(_)"), + AuthCredential::Passkey(_) => write!(fmt, "Passkey(_)"), + } + } +} + +/// The mechanisms that may proceed in this authentication +#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialOrd, Ord, ToSchema)] +#[serde(rename_all = "lowercase")] +pub enum AuthMech { + Anonymous, + Password, + PasswordMfa, + Passkey, +} + +impl PartialEq for AuthMech { + fn eq(&self, other: &Self) -> bool { + std::mem::discriminant(self) == std::mem::discriminant(other) + } +} + +impl fmt::Display for AuthMech { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + AuthMech::Anonymous => write!(f, "Anonymous (no credentials)"), + AuthMech::Password => write!(f, "Password"), + AuthMech::PasswordMfa => write!(f, "TOTP/Backup Code and Password"), + AuthMech::Passkey => write!(f, "Passkey"), + } + } +} + +/// The type of session that should be issued to the client. +#[derive(Debug, Serialize, Deserialize, Copy, Clone, ToSchema)] +#[serde(rename_all = "lowercase")] +pub enum AuthIssueSession { + // Previously supported other types beside token. + Token, +} + +/// A request for the next step of an authentication. +#[derive(Debug, Serialize, Deserialize, ToSchema)] +pub struct AuthRequest { + pub step: AuthStep, +} + +/// A challenge containing the list of allowed authentication types +/// that can satisfy the next step. These may have inner types with +/// required context. +#[derive(Debug, Serialize, Deserialize, Clone, ToSchema)] +#[serde(rename_all = "lowercase")] +pub enum AuthAllowed { + Anonymous, + BackupCode, + Password, + Totp, + SecurityKey(RequestChallengeResponse), + Passkey(RequestChallengeResponse), +} + +impl PartialEq for AuthAllowed { + fn eq(&self, other: &Self) -> bool { + std::mem::discriminant(self) == std::mem::discriminant(other) + } +} + +impl From<&AuthAllowed> for u8 { + fn from(a: &AuthAllowed) -> u8 { + match a { + AuthAllowed::Anonymous => 0, + AuthAllowed::Password => 1, + AuthAllowed::BackupCode => 2, + AuthAllowed::Totp => 3, + AuthAllowed::Passkey(_) => 4, + AuthAllowed::SecurityKey(_) => 5, + } + } +} + +impl Eq for AuthAllowed {} + +impl Ord for AuthAllowed { + fn cmp(&self, other: &Self) -> Ordering { + let self_ord: u8 = self.into(); + let other_ord: u8 = other.into(); + self_ord.cmp(&other_ord) + } +} + +impl PartialOrd for AuthAllowed { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl fmt::Display for AuthAllowed { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + AuthAllowed::Anonymous => write!(f, "Anonymous (no credentials)"), + AuthAllowed::Password => write!(f, "Password"), + AuthAllowed::BackupCode => write!(f, "Backup Code"), + AuthAllowed::Totp => write!(f, "TOTP"), + AuthAllowed::SecurityKey(_) => write!(f, "Security Token"), + AuthAllowed::Passkey(_) => write!(f, "Passkey"), + } + } +} + +#[derive(Debug, Serialize, Deserialize, ToSchema)] +pub struct AuthResponse { + pub sessionid: Uuid, + pub state: AuthState, +} diff --git a/proto/src/v1/mod.rs b/proto/src/v1/mod.rs new file mode 100644 index 000000000..96368904a --- /dev/null +++ b/proto/src/v1/mod.rs @@ -0,0 +1,149 @@ +//! Kanidm Version 1 +//! +//! Items defined in this module will remain stable, or change in ways that are forward +//! compatible with newer releases. + +#![allow(non_upper_case_globals)] + +use serde::{Deserialize, Serialize}; +use std::collections::BTreeMap; +use std::fmt; +use utoipa::ToSchema; +use uuid::Uuid; + +mod auth; +mod unix; + +pub use self::auth::*; +pub use self::unix::*; + +/// The type of Account in use. +#[derive(Clone, Copy, Debug, ToSchema)] +pub enum AccountType { + Person, + ServiceAccount, +} + +impl ToString for AccountType { + fn to_string(&self) -> String { + match self { + AccountType::Person => "person".to_string(), + AccountType::ServiceAccount => "service_account".to_string(), + } + } +} + +/* ===== higher level types ===== */ +// These are all types that are conceptually layers on top of entry and +// friends. They allow us to process more complex requests and provide +// domain specific fields for the purposes of IDM, over the normal +// entry/ava/filter types. These related deeply to schema. + +/// The current purpose of a User Auth Token. It may be read-only, read-write +/// or privilige capable (able to step up to read-write after re-authentication). +#[derive(Debug, Serialize, Deserialize, Clone, ToSchema)] +#[serde(rename_all = "lowercase")] +pub enum UatPurposeStatus { + ReadOnly, + ReadWrite, + PrivilegeCapable, +} + +/// The expiry of the User Auth Token. +#[derive(Debug, Serialize, Deserialize, Clone, ToSchema)] +#[serde(rename_all = "lowercase")] +pub enum UatStatusState { + #[serde(with = "time::serde::timestamp")] + ExpiresAt(time::OffsetDateTime), + NeverExpires, + Revoked, +} + +impl fmt::Display for UatStatusState { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + UatStatusState::ExpiresAt(odt) => write!(f, "expires at {}", odt), + UatStatusState::NeverExpires => write!(f, "never expires"), + UatStatusState::Revoked => write!(f, "revoked"), + } + } +} + +/// The status of a User Auth Token +#[derive(Debug, Serialize, Deserialize, Clone, ToSchema)] +#[serde(rename_all = "lowercase")] +pub struct UatStatus { + pub account_id: Uuid, + pub session_id: Uuid, + pub state: UatStatusState, + #[serde(with = "time::serde::timestamp")] + pub issued_at: time::OffsetDateTime, + pub purpose: UatPurposeStatus, +} + +impl fmt::Display for UatStatus { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + writeln!(f, "account_id: {}", self.account_id)?; + writeln!(f, "session_id: {}", self.session_id)?; + writeln!(f, "state: {}", self.state)?; + writeln!(f, "issued_at: {}", self.issued_at)?; + match &self.purpose { + UatPurposeStatus::ReadOnly => writeln!(f, "purpose: read only")?, + UatPurposeStatus::ReadWrite => writeln!(f, "purpose: read write")?, + UatPurposeStatus::PrivilegeCapable => writeln!(f, "purpose: privilege capable")?, + } + Ok(()) + } +} + +/// A request to generate a new API token for a service account +#[derive(Debug, Serialize, Deserialize, Clone, ToSchema)] +#[serde(rename_all = "lowercase")] +pub struct ApiTokenGenerate { + pub label: String, + #[serde(with = "time::serde::timestamp::option")] + pub expiry: Option, + pub read_write: bool, +} + +/* ===== low level proto types ===== */ + +/// A limited view of an entry in Kanidm. +#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq, Default, ToSchema)] +pub struct Entry { + pub attrs: BTreeMap>, +} + +impl fmt::Display for Entry { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + writeln!(f, "---")?; + self.attrs + .iter() + .try_for_each(|(k, vs)| vs.iter().try_for_each(|v| writeln!(f, "{}: {}", k, v))) + } +} + +/// A response to a whoami request +#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq, ToSchema)] +pub struct WhoamiResponse { + // Should we just embed the entry? Or destructure it? + pub youare: Entry, +} + +impl WhoamiResponse { + pub fn new(youare: Entry) -> Self { + WhoamiResponse { youare } + } +} + +// Simple string value provision. +#[derive(Debug, Serialize, Deserialize, ToSchema)] +pub struct SingleStringRequest { + pub value: String, +} + +impl SingleStringRequest { + pub fn new(s: String) -> Self { + SingleStringRequest { value: s } + } +} diff --git a/proto/src/v1/unix.rs b/proto/src/v1/unix.rs new file mode 100644 index 000000000..a8721c84e --- /dev/null +++ b/proto/src/v1/unix.rs @@ -0,0 +1,80 @@ +use serde::{Deserialize, Serialize}; +use std::fmt; +use utoipa::ToSchema; +use uuid::Uuid; + +use serde_with::skip_serializing_none; + +use crate::constants::{ATTR_GROUP, ATTR_LDAP_SSHPUBLICKEY}; + +/// A token representing the details of a unix group +#[derive(Debug, Serialize, Deserialize, Clone, ToSchema)] +pub struct UnixGroupToken { + pub name: String, + pub spn: String, + pub uuid: Uuid, + pub gidnumber: u32, +} + +impl fmt::Display for UnixGroupToken { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "[ spn: {}, ", self.spn)?; + write!(f, "gidnumber: {} ", self.gidnumber)?; + write!(f, "name: {}, ", self.name)?; + write!(f, "uuid: {} ]", self.uuid) + } +} + +/// Request addition of unix attributes to a group. +#[derive(Debug, Serialize, Deserialize, Clone, ToSchema)] +pub struct GroupUnixExtend { + pub gidnumber: Option, +} + +/// A token representing the details of a unix user +#[skip_serializing_none] +#[derive(Debug, Serialize, Deserialize, Clone, ToSchema)] +pub struct UnixUserToken { + pub name: String, + pub spn: String, + pub displayname: String, + pub gidnumber: u32, + pub uuid: Uuid, + pub shell: Option, + pub groups: Vec, + pub sshkeys: Vec, + // The default value of bool is false. + #[serde(default)] + pub valid: bool, +} + +impl fmt::Display for UnixUserToken { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + writeln!(f, "---")?; + writeln!(f, "spn: {}", self.spn)?; + writeln!(f, "name: {}", self.name)?; + writeln!(f, "displayname: {}", self.displayname)?; + writeln!(f, "uuid: {}", self.uuid)?; + match &self.shell { + Some(s) => writeln!(f, "shell: {}", s)?, + None => writeln!(f, "shell: ")?, + } + self.sshkeys + .iter() + .try_for_each(|s| writeln!(f, "{}: {}", ATTR_LDAP_SSHPUBLICKEY, s))?; + self.groups + .iter() + .try_for_each(|g| writeln!(f, "{}: {}", ATTR_GROUP, g)) + } +} + +/// Request addition of unix attributes to an account +#[derive(Debug, Serialize, Deserialize, Clone, ToSchema)] +#[serde(deny_unknown_fields)] +pub struct AccountUnixExtend { + pub gidnumber: Option, + // TODO: rename shell to loginshell everywhere we can find + /// The internal attribute is "loginshell" but we use shell in the API currently + #[serde(alias = "loginshell")] + pub shell: Option, +} diff --git a/server/core/Cargo.toml b/server/core/Cargo.toml index 4dfbe41a4..dc1fdfb5b 100644 --- a/server/core/Cargo.toml +++ b/server/core/Cargo.toml @@ -54,7 +54,6 @@ toml = { workspace = true } tower = { version = "0.4.13", features = ["tokio-stream", "tracing"] } tower-http = { version = "0.4.4", features = [ "compression-gzip", - "compression-zstd", "fs", "tokio", "trace", diff --git a/server/core/build.rs b/server/core/build.rs index 5a19158c1..9fadb5cd4 100644 --- a/server/core/build.rs +++ b/server/core/build.rs @@ -1,3 +1,4 @@ fn main() { profiles::apply_profile(); + println!("cargo:rerun-if-changed=build.rs"); } diff --git a/server/core/src/actors/v1_read.rs b/server/core/src/actors/v1_read.rs index e72951994..e7eb5a481 100644 --- a/server/core/src/actors/v1_read.rs +++ b/server/core/src/actors/v1_read.rs @@ -4,11 +4,14 @@ use std::net::IpAddr; use std::path::{Path, PathBuf}; use std::sync::Arc; -use kanidm_proto::internal::{AppLink, IdentifyUserRequest, IdentifyUserResponse, ImageValue}; +use kanidm_proto::internal::{ + ApiToken, AppLink, BackupCodesView, CURequest, CUSessionToken, CUStatus, CredentialStatus, + IdentifyUserRequest, IdentifyUserResponse, ImageValue, OperationError, RadiusAuthToken, + SearchRequest, SearchResponse, UserAuthToken, +}; use kanidm_proto::v1::{ - ApiToken, AuthIssueSession, AuthRequest, BackupCodesView, CURequest, CUSessionToken, CUStatus, - CredentialStatus, Entry as ProtoEntry, OperationError, RadiusAuthToken, SearchRequest, - SearchResponse, UatStatus, UnixGroupToken, UnixUserToken, UserAuthToken, WhoamiResponse, + AuthIssueSession, AuthRequest, Entry as ProtoEntry, UatStatus, UnixGroupToken, UnixUserToken, + WhoamiResponse, }; use kanidmd_lib::idm::identityverification::{ IdentifyUserDisplayCodeEvent, IdentifyUserStartEvent, IdentifyUserSubmitCodeEvent, diff --git a/server/core/src/actors/v1_write.rs b/server/core/src/actors/v1_write.rs index 6ae107f16..7587e588e 100644 --- a/server/core/src/actors/v1_write.rs +++ b/server/core/src/actors/v1_write.rs @@ -1,13 +1,12 @@ use std::{iter, sync::Arc}; -use kanidm_proto::internal::DomainInfo as ProtoDomainInfo; -use kanidm_proto::internal::ImageValue; -use kanidm_proto::internal::Oauth2ClaimMapJoin as ProtoOauth2ClaimMapJoin; -use kanidm_proto::v1::{ - AccountUnixExtend, CUIntentToken, CUSessionToken, CUStatus, CreateRequest, DeleteRequest, - Entry as ProtoEntry, GroupUnixExtend, Modify as ProtoModify, ModifyList as ProtoModifyList, - ModifyRequest, OperationError, +use kanidm_proto::internal::{ + CUIntentToken, CUSessionToken, CUStatus, CreateRequest, DeleteRequest, + DomainInfo as ProtoDomainInfo, ImageValue, Modify as ProtoModify, + ModifyList as ProtoModifyList, ModifyRequest, Oauth2ClaimMapJoin as ProtoOauth2ClaimMapJoin, + OperationError, }; +use kanidm_proto::v1::{AccountUnixExtend, Entry as ProtoEntry, GroupUnixExtend}; use time::OffsetDateTime; use tracing::{info, instrument, span, trace, Instrument, Level}; use uuid::Uuid; diff --git a/server/core/src/https/apidocs/mod.rs b/server/core/src/https/apidocs/mod.rs index 4e5679ecc..11b6c860f 100644 --- a/server/core/src/https/apidocs/mod.rs +++ b/server/core/src/https/apidocs/mod.rs @@ -1,5 +1,5 @@ use axum::{middleware::from_fn, response::Redirect, routing::get, Router}; -use kanidm_proto::{scim_v1, v1}; +use kanidm_proto::{internal, scim_v1, v1}; use utoipa::{ openapi::security::{HttpAuthScheme, HttpBuilder, SecurityScheme}, Modify, OpenApi, @@ -189,57 +189,56 @@ impl Modify for SecurityAddon { // TODO: can't add Entry/ProtoEntry to schema as this was only recently supported utoipa v3.5.0 doesn't support it - ref // v1::Entry, v1::AccountUnixExtend, - v1::ApiToken, + internal::ApiToken, v1::ApiTokenGenerate, v1::AuthRequest, v1::AuthResponse, v1::AuthState, - v1::BackupCodesView, - v1::Claim, - v1::CreateRequest, - v1::CredentialDetail, - v1::CredentialStatus, - v1::CUIntentToken, - v1::CUSessionToken, - v1::CUStatus, - v1::DeleteRequest, - v1::Group, + internal::BackupCodesView, + internal::CreateRequest, + internal::CredentialDetail, + internal::CredentialStatus, + internal::CUIntentToken, + internal::CUSessionToken, + internal::CUStatus, + internal::DeleteRequest, + internal::Group, v1::GroupUnixExtend, - v1::ModifyList, - v1::ModifyRequest, - v1::PasskeyDetail, - v1::RadiusAuthToken, - v1::SearchRequest, - v1::SearchResponse, + internal::ModifyList, + internal::ModifyRequest, + internal::PasskeyDetail, + internal::RadiusAuthToken, + internal::SearchRequest, + internal::SearchResponse, v1::SingleStringRequest, - v1::TotpSecret, - v1::TotpAlgo, + internal::TotpSecret, + internal::TotpAlgo, v1::UatStatus, v1::UnixGroupToken, v1::UnixUserToken, - v1::UserAuthToken, + internal::UserAuthToken, v1::WhoamiResponse, - v1::ApiTokenPurpose, + internal::ApiTokenPurpose, v1::AuthStep, v1::AuthIssueSession, v1::AuthMech, v1::AuthCredential, v1::AuthAllowed, - v1::CUExtPortal, - v1::CURegState, - v1::CredentialDetailType, + internal::CUExtPortal, + internal::CURegState, + internal::CredentialDetailType, v1::Entry, - v1::Filter, - v1::Modify, + internal::Filter, + internal::Modify, v1::UatStatusState, v1::UatPurposeStatus, - v1::UatPurpose, - v1::OperationError, - v1::SchemaError, - v1::PluginError, - v1::PasswordFeedback, + internal::UatPurpose, + internal::OperationError, + internal::SchemaError, + internal::PluginError, + internal::PasswordFeedback, - kanidm_proto::internal::IdentifyUserRequest, + internal::IdentifyUserRequest, // terrible workaround for other things response_schema::CreationChallengeResponse, // terrible workaround for other things diff --git a/server/core/src/https/errors.rs b/server/core/src/https/errors.rs index 10ce8f228..6f4edadd2 100644 --- a/server/core/src/https/errors.rs +++ b/server/core/src/https/errors.rs @@ -4,7 +4,7 @@ use axum::http::header::ACCESS_CONTROL_ALLOW_ORIGIN; use axum::http::{HeaderValue, StatusCode}; use axum::response::{IntoResponse, Response}; -use kanidm_proto::v1::OperationError; +use kanidm_proto::internal::OperationError; use utoipa::ToSchema; /// The web app's top level error type, this takes an `OperationError` and converts it into a HTTP response. diff --git a/server/core/src/https/v1.rs b/server/core/src/https/v1.rs index 08f5791aa..b35d0a2db 100644 --- a/server/core/src/https/v1.rs +++ b/server/core/src/https/v1.rs @@ -12,13 +12,15 @@ use serde::{Deserialize, Serialize}; use std::net::IpAddr; use uuid::Uuid; -use kanidm_proto::internal::{AppLink, IdentifyUserRequest, IdentifyUserResponse}; +use kanidm_proto::internal::{ + ApiToken, AppLink, CUIntentToken, CURequest, CUSessionToken, CUStatus, CreateRequest, + CredentialStatus, DeleteRequest, IdentifyUserRequest, IdentifyUserResponse, ModifyRequest, + RadiusAuthToken, SearchRequest, SearchResponse, UserAuthToken, +}; use kanidm_proto::v1::{ - AccountUnixExtend, ApiToken, ApiTokenGenerate, AuthIssueSession, AuthRequest, AuthResponse, - AuthState as ProtoAuthState, CUIntentToken, CURequest, CUSessionToken, CUStatus, CreateRequest, - CredentialStatus, DeleteRequest, Entry as ProtoEntry, GroupUnixExtend, ModifyRequest, - RadiusAuthToken, SearchRequest, SearchResponse, SingleStringRequest, UatStatus, UnixGroupToken, - UnixUserToken, UserAuthToken, WhoamiResponse, + AccountUnixExtend, ApiTokenGenerate, AuthIssueSession, AuthRequest, AuthResponse, + AuthState as ProtoAuthState, Entry as ProtoEntry, GroupUnixExtend, SingleStringRequest, + UatStatus, UnixGroupToken, UnixUserToken, WhoamiResponse, }; use kanidmd_lib::idm::event::AuthResult; use kanidmd_lib::idm::AuthState; diff --git a/server/core/src/lib.rs b/server/core/src/lib.rs index a0a56f6ec..c501b77b0 100644 --- a/server/core/src/lib.rs +++ b/server/core/src/lib.rs @@ -40,7 +40,7 @@ use std::sync::Arc; use crate::utils::touch_file_or_quit; use compact_jwt::JwsHs256Signer; -use kanidm_proto::v1::OperationError; +use kanidm_proto::internal::OperationError; use kanidmd_lib::be::{Backend, BackendConfig, BackendTransaction}; use kanidmd_lib::idm::ldap::LdapServer; use kanidmd_lib::prelude::*; diff --git a/server/daemon/build.rs b/server/daemon/build.rs index 4a3550f66..5688d44f1 100644 --- a/server/daemon/build.rs +++ b/server/daemon/build.rs @@ -11,6 +11,9 @@ include!("src/opt.rs"); fn main() { profiles::apply_profile(); + println!("cargo:rerun-if-changed=build.rs"); + println!("cargo:rerun-if-env-changed=OUT_DIR"); + println!("cargo:rerun-if-changed=src/opt.rs"); let outdir = match env::var_os("OUT_DIR") { None => return, Some(outdir) => outdir, diff --git a/server/lib/build.rs b/server/lib/build.rs index 41421eaea..f1ad7fbb9 100644 --- a/server/lib/build.rs +++ b/server/lib/build.rs @@ -1,10 +1,10 @@ -// include!("src/lib/audit_loglevel.rs"); - use hashbrown::HashMap; use std::env; fn main() { + println!("cargo:rerun-if-changed=build.rs"); + println!("cargo:rerun-if-env-changed=DEP_OPENSSL_VERSION_NUMBER"); if let Ok(v) = env::var("DEP_OPENSSL_VERSION_NUMBER") { let version = u64::from_str_radix(&v, 16).unwrap(); @@ -16,6 +16,7 @@ fn main() { profiles::apply_profile(); // check we don't have duplicate UUIDs + println!("cargo:rerun-if-changed=src/constants/uuids.rs"); let uuid_filename = format!( "{}/{}", env!("CARGO_MANIFEST_DIR"), diff --git a/server/lib/src/be/idl_arc_sqlite.rs b/server/lib/src/be/idl_arc_sqlite.rs index b5305f1f8..db1b3ac22 100644 --- a/server/lib/src/be/idl_arc_sqlite.rs +++ b/server/lib/src/be/idl_arc_sqlite.rs @@ -10,7 +10,7 @@ use concread::cowcell::*; use hashbrown::HashMap; use idlset::v2::IDLBitRange; use idlset::AndNot; -use kanidm_proto::v1::{ConsistencyError, OperationError}; +use kanidm_proto::internal::{ConsistencyError, OperationError}; use tracing::trace; use uuid::Uuid; diff --git a/server/lib/src/be/idl_sqlite.rs b/server/lib/src/be/idl_sqlite.rs index cde1a5110..5440841d4 100644 --- a/server/lib/src/be/idl_sqlite.rs +++ b/server/lib/src/be/idl_sqlite.rs @@ -9,7 +9,7 @@ use super::keystorage::{KeyHandle, KeyHandleId}; // use crate::valueset; use hashbrown::HashMap; use idlset::v2::IDLBitRange; -use kanidm_proto::v1::{ConsistencyError, OperationError}; +use kanidm_proto::internal::{ConsistencyError, OperationError}; use rusqlite::vtab::array::Array; use rusqlite::{Connection, OpenFlags, OptionalExtension}; use uuid::Uuid; diff --git a/server/lib/src/be/mod.rs b/server/lib/src/be/mod.rs index b39311929..4a1eb5afa 100644 --- a/server/lib/src/be/mod.rs +++ b/server/lib/src/be/mod.rs @@ -14,7 +14,7 @@ use concread::cowcell::*; use hashbrown::{HashMap as Map, HashSet}; use idlset::v2::IDLBitRange; use idlset::AndNot; -use kanidm_proto::v1::{ConsistencyError, OperationError}; +use kanidm_proto::internal::{ConsistencyError, OperationError}; use smartstring::alias::String as AttrString; use tracing::{trace, trace_span}; use uuid::Uuid; diff --git a/server/lib/src/constants/acp.rs b/server/lib/src/constants/acp.rs index aa431ef16..153a06e57 100644 --- a/server/lib/src/constants/acp.rs +++ b/server/lib/src/constants/acp.rs @@ -5,7 +5,7 @@ use crate::constants::uuids::*; use crate::entry::EntryInitNew; use crate::prelude::*; use crate::value::Value; -use kanidm_proto::v1::Filter as ProtoFilter; +use kanidm_proto::internal::Filter as ProtoFilter; lazy_static! { /// either recycled or tombstone diff --git a/server/lib/src/constants/entries.rs b/server/lib/src/constants/entries.rs index 5f404ca44..019d678ba 100644 --- a/server/lib/src/constants/entries.rs +++ b/server/lib/src/constants/entries.rs @@ -10,7 +10,8 @@ use crate::idm::account::Account; use crate::value::PartialValue; use crate::value::Value; use kanidm_proto::constants::*; -use kanidm_proto::v1::{AccountType, OperationError}; +use kanidm_proto::internal::OperationError; +use kanidm_proto::v1::AccountType; #[cfg(test)] use uuid::uuid; diff --git a/server/lib/src/constants/groups.rs b/server/lib/src/constants/groups.rs index 1b539a0a7..218a8baef 100644 --- a/server/lib/src/constants/groups.rs +++ b/server/lib/src/constants/groups.rs @@ -1,7 +1,7 @@ use crate::entry::EntryInitNew; use crate::prelude::*; -use kanidm_proto::v1::{Filter, OperationError, UiHint}; +use kanidm_proto::internal::{Filter, OperationError, UiHint}; #[derive(Clone, Debug, Default)] /// Built-in group definitions diff --git a/server/lib/src/credential/mod.rs b/server/lib/src/credential/mod.rs index a417004a6..e6816740b 100644 --- a/server/lib/src/credential/mod.rs +++ b/server/lib/src/credential/mod.rs @@ -1,7 +1,9 @@ use std::convert::TryFrom; use hashbrown::{HashMap as Map, HashSet}; -use kanidm_proto::v1::{BackupCodesView, CredentialDetail, CredentialDetailType, OperationError}; +use kanidm_proto::internal::{ + BackupCodesView, CredentialDetail, CredentialDetailType, OperationError, +}; use uuid::Uuid; use webauthn_rs::prelude::{AuthenticationResult, Passkey, SecurityKey}; use webauthn_rs_core::proto::{Credential as WebauthnCredential, CredentialV3}; diff --git a/server/lib/src/credential/totp.rs b/server/lib/src/credential/totp.rs index dae13ca0d..f30491898 100644 --- a/server/lib/src/credential/totp.rs +++ b/server/lib/src/credential/totp.rs @@ -1,7 +1,7 @@ use std::convert::{TryFrom, TryInto}; use std::time::{Duration, SystemTime}; -use kanidm_proto::v1::{TotpAlgo as ProtoTotpAlgo, TotpSecret as ProtoTotp}; +use kanidm_proto::internal::{TotpAlgo as ProtoTotpAlgo, TotpSecret as ProtoTotp}; use openssl::hash::MessageDigest; use openssl::pkey::PKey; use openssl::sign::Signer; diff --git a/server/lib/src/entry.rs b/server/lib/src/entry.rs index bef1cac68..e1e6b7b9c 100644 --- a/server/lib/src/entry.rs +++ b/server/lib/src/entry.rs @@ -32,10 +32,10 @@ use std::sync::Arc; use compact_jwt::JwsEs256Signer; use hashbrown::{HashMap, HashSet}; use kanidm_proto::internal::ImageValue; -use kanidm_proto::v1::{ - ConsistencyError, Entry as ProtoEntry, Filter as ProtoFilter, OperationError, SchemaError, - UiHint, +use kanidm_proto::internal::{ + ConsistencyError, Filter as ProtoFilter, OperationError, SchemaError, UiHint, }; +use kanidm_proto::v1::Entry as ProtoEntry; use ldap3_proto::simple::{LdapPartialAttribute, LdapSearchResultEntry}; use openssl::ec::EcKey; use openssl::pkey::{Private, Public}; diff --git a/server/lib/src/event.rs b/server/lib/src/event.rs index 4f51632b1..cf6ea8449 100644 --- a/server/lib/src/event.rs +++ b/server/lib/src/event.rs @@ -19,10 +19,11 @@ use std::collections::BTreeSet; #[cfg(test)] use std::sync::Arc; -use kanidm_proto::v1::{ - CreateRequest, DeleteRequest, Entry as ProtoEntry, ModifyList as ProtoModifyList, - ModifyRequest, OperationError, SearchRequest, SearchResponse, WhoamiResponse, +use kanidm_proto::internal::{ + CreateRequest, DeleteRequest, ModifyList as ProtoModifyList, ModifyRequest, OperationError, + SearchRequest, SearchResponse, }; +use kanidm_proto::v1::{Entry as ProtoEntry, WhoamiResponse}; use ldap3_proto::simple::LdapFilter; use uuid::Uuid; diff --git a/server/lib/src/filter.rs b/server/lib/src/filter.rs index c7944ce78..cbfbca214 100644 --- a/server/lib/src/filter.rs +++ b/server/lib/src/filter.rs @@ -20,7 +20,7 @@ use hashbrown::HashMap; #[cfg(test)] use hashbrown::HashSet; use kanidm_proto::constants::ATTR_UUID; -use kanidm_proto::v1::{Filter as ProtoFilter, OperationError, SchemaError}; +use kanidm_proto::internal::{Filter as ProtoFilter, OperationError, SchemaError}; use ldap3_proto::proto::{LdapFilter, LdapSubstringFilter}; // use smartstring::alias::String as AttrString; use serde::Deserialize; @@ -1574,7 +1574,7 @@ mod tests { use std::collections::BTreeSet; use std::time::Duration; - use kanidm_proto::v1::Filter as ProtoFilter; + use kanidm_proto::internal::Filter as ProtoFilter; use ldap3_proto::simple::LdapFilter; use crate::event::{CreateEvent, DeleteEvent}; diff --git a/server/lib/src/idm/account.rs b/server/lib/src/idm/account.rs index bc605915a..ae9b8e031 100644 --- a/server/lib/src/idm/account.rs +++ b/server/lib/src/idm/account.rs @@ -1,10 +1,10 @@ use std::collections::{BTreeMap, BTreeSet}; use std::time::Duration; -use kanidm_proto::v1::{ - BackupCodesView, CredentialStatus, OperationError, UatPurpose, UatStatus, UatStatusState, - UiHint, UserAuthToken, +use kanidm_proto::internal::{ + BackupCodesView, CredentialStatus, UatPurpose, UiHint, UserAuthToken, }; +use kanidm_proto::v1::{UatStatus, UatStatusState}; use time::OffsetDateTime; use uuid::Uuid; use webauthn_rs::prelude::{ @@ -925,7 +925,7 @@ mod tests { use crate::idm::account::Account; use crate::idm::accountpolicy::ResolvedAccountPolicy; use crate::prelude::*; - use kanidm_proto::v1::UiHint; + use kanidm_proto::internal::UiHint; #[test] fn test_idm_account_from_anonymous() { diff --git a/server/lib/src/idm/authsession.rs b/server/lib/src/idm/authsession.rs index c0f4e890f..eeaade467 100644 --- a/server/lib/src/idm/authsession.rs +++ b/server/lib/src/idm/authsession.rs @@ -9,9 +9,8 @@ use std::time::Duration; use compact_jwt::{Jws, JwsEs256Signer, JwsSigner}; use hashbrown::HashSet; -use kanidm_proto::v1::{ - AuthAllowed, AuthCredential, AuthIssueSession, AuthMech, OperationError, UserAuthToken, -}; +use kanidm_proto::internal::UserAuthToken; +use kanidm_proto::v1::{AuthAllowed, AuthCredential, AuthIssueSession, AuthMech}; use nonempty::{nonempty, NonEmpty}; use tokio::sync::mpsc::UnboundedSender as Sender; use uuid::Uuid; @@ -1482,9 +1481,8 @@ mod tests { use compact_jwt::{JwsCompact, JwsEs256Signer, JwsEs256Verifier, JwsVerifier}; use hashbrown::HashSet; - use kanidm_proto::v1::{ - AuthAllowed, AuthCredential, AuthIssueSession, AuthMech, UatPurpose, UserAuthToken, - }; + use kanidm_proto::internal::{UatPurpose, UserAuthToken}; + use kanidm_proto::v1::{AuthAllowed, AuthCredential, AuthIssueSession, AuthMech}; use tokio::sync::mpsc::unbounded_channel as unbounded; use webauthn_authenticator_rs::softpasskey::SoftPasskey; use webauthn_authenticator_rs::WebauthnAuthenticator; diff --git a/server/lib/src/idm/credupdatesession.rs b/server/lib/src/idm/credupdatesession.rs index c613b5656..d0ba1d6e9 100644 --- a/server/lib/src/idm/credupdatesession.rs +++ b/server/lib/src/idm/credupdatesession.rs @@ -7,7 +7,7 @@ use std::time::Duration; use sshkey_attest::proto::PublicKey as SshPublicKey; use hashbrown::HashSet; -use kanidm_proto::v1::{ +use kanidm_proto::internal::{ CUCredState, CUExtPortal, CURegState, CURegWarning, CUStatus, CredentialDetail, PasskeyDetail, PasswordFeedback, TotpSecret, }; @@ -2312,10 +2312,8 @@ impl<'a> IdmServerCredUpdateTransaction<'a> { mod tests { use std::time::Duration; - use kanidm_proto::v1::{ - AuthAllowed, AuthIssueSession, AuthMech, CUExtPortal, CredentialDetailType, - PasswordFeedback, - }; + use kanidm_proto::internal::{CUExtPortal, CredentialDetailType, PasswordFeedback}; + use kanidm_proto::v1::{AuthAllowed, AuthIssueSession, AuthMech}; use uuid::uuid; use webauthn_authenticator_rs::softpasskey::SoftPasskey; use webauthn_authenticator_rs::softtoken::{self, SoftToken}; diff --git a/server/lib/src/idm/event.rs b/server/lib/src/idm/event.rs index 5b9bd5dec..addcdcc88 100644 --- a/server/lib/src/idm/event.rs +++ b/server/lib/src/idm/event.rs @@ -1,6 +1,5 @@ use crate::idm::AuthState; use crate::prelude::*; -use kanidm_proto::v1::OperationError; use kanidm_proto::v1::{AuthCredential, AuthIssueSession, AuthMech, AuthRequest, AuthStep}; #[cfg(test)] diff --git a/server/lib/src/idm/group.rs b/server/lib/src/idm/group.rs index df84b8be4..0a2149dce 100644 --- a/server/lib/src/idm/group.rs +++ b/server/lib/src/idm/group.rs @@ -1,7 +1,6 @@ use std::collections::BTreeSet; -use kanidm_proto::v1::UiHint; -use kanidm_proto::v1::{Group as ProtoGroup, OperationError}; +use kanidm_proto::internal::{Group as ProtoGroup, UiHint}; use uuid::Uuid; use super::accountpolicy::{AccountPolicy, ResolvedAccountPolicy}; diff --git a/server/lib/src/idm/identityverification.rs b/server/lib/src/idm/identityverification.rs index 6bbaac8c6..05b16f836 100644 --- a/server/lib/src/idm/identityverification.rs +++ b/server/lib/src/idm/identityverification.rs @@ -1,6 +1,7 @@ +use crate::prelude::*; use std::time::SystemTime; -use kanidm_proto::{internal::IdentifyUserResponse, v1::OperationError}; +use kanidm_proto::internal::IdentifyUserResponse; use openssl::ec::EcKey; use openssl::pkey::{PKey, Private, Public}; use openssl::pkey_ctx::PkeyCtx; @@ -8,7 +9,6 @@ use sketching::admin_error; use uuid::Uuid; use crate::credential::totp::{Totp, TotpAlgo, TotpDigits}; -use crate::prelude::{tagged_event, Attribute, EventTag}; use crate::server::QueryServerTransaction; use crate::{event::SearchEvent, server::identity::Identity}; diff --git a/server/lib/src/idm/ldap.rs b/server/lib/src/idm/ldap.rs index b5c5ddf62..bf1c1a53e 100644 --- a/server/lib/src/idm/ldap.rs +++ b/server/lib/src/idm/ldap.rs @@ -5,7 +5,7 @@ use std::collections::BTreeSet; use std::iter; use kanidm_proto::constants::*; -use kanidm_proto::v1::{ApiToken, OperationError, UserAuthToken}; +use kanidm_proto::internal::{ApiToken, UserAuthToken}; use ldap3_proto::simple::*; use regex::Regex; use std::net::IpAddr; @@ -621,7 +621,7 @@ mod tests { use compact_jwt::{JwsCompact, JwsEs256Verifier, JwsVerifier}; use hashbrown::HashSet; - use kanidm_proto::v1::ApiToken; + use kanidm_proto::internal::ApiToken; use ldap3_proto::proto::{LdapFilter, LdapOp, LdapSearchScope, LdapSubstringFilter}; use ldap3_proto::simple::*; diff --git a/server/lib/src/idm/oauth2.rs b/server/lib/src/idm/oauth2.rs index 3e818343f..3df05002a 100644 --- a/server/lib/src/idm/oauth2.rs +++ b/server/lib/src/idm/oauth2.rs @@ -2551,8 +2551,8 @@ mod tests { OidcSubject, OidcUnverified, }; use kanidm_proto::constants::*; + use kanidm_proto::internal::UserAuthToken; use kanidm_proto::oauth2::*; - use kanidm_proto::v1::UserAuthToken; use openssl::sha; use crate::idm::accountpolicy::ResolvedAccountPolicy; diff --git a/server/lib/src/idm/radius.rs b/server/lib/src/idm/radius.rs index edf983d8e..c56f981f6 100644 --- a/server/lib/src/idm/radius.rs +++ b/server/lib/src/idm/radius.rs @@ -1,6 +1,6 @@ use std::time::Duration; -use kanidm_proto::v1::{OperationError, RadiusAuthToken}; +use kanidm_proto::internal::RadiusAuthToken; use time::OffsetDateTime; use uuid::Uuid; diff --git a/server/lib/src/idm/scim.rs b/server/lib/src/idm/scim.rs index e54d96f19..681e37b57 100644 --- a/server/lib/src/idm/scim.rs +++ b/server/lib/src/idm/scim.rs @@ -3,9 +3,8 @@ use std::time::Duration; use base64urlsafedata::Base64UrlSafeData; use compact_jwt::{Jws, JwsEs256Signer, JwsSigner}; -use kanidm_proto::internal::ScimSyncToken; +use kanidm_proto::internal::{ApiTokenPurpose, ScimSyncToken}; use kanidm_proto::scim_v1::*; -use kanidm_proto::v1::ApiTokenPurpose; use std::collections::{BTreeMap, BTreeSet}; use crate::credential::totp::{Totp, TotpAlgo, TotpDigits}; @@ -1150,7 +1149,7 @@ impl<'a> IdmServerProxyWriteTransaction<'a> { // What classes did they request for this entry to sync? let requested_classes = scim_ent.schemas.iter() .map(|schema| { - schema.as_str().strip_prefix(SCIM_SCHEMA_SYNC) + schema.as_str().strip_prefix(SCIM_SCHEMA_SYNC_1) .ok_or_else(|| { error!(?schema, "Invalid requested schema - Not a kanidm sync schema."); OperationError::InvalidEntryState @@ -1537,8 +1536,8 @@ mod tests { use crate::prelude::*; use base64urlsafedata::Base64UrlSafeData; use compact_jwt::{Jws, JwsSigner}; + use kanidm_proto::internal::ApiTokenPurpose; use kanidm_proto::scim_v1::*; - use kanidm_proto::v1::ApiTokenPurpose; use std::sync::Arc; use std::time::Duration; @@ -2094,7 +2093,7 @@ mod tests { assert!(apply_phase_3_test( idms, vec![ScimEntry { - schemas: vec![format!("{SCIM_SCHEMA_SYNC}system")], + schemas: vec![format!("{SCIM_SCHEMA_SYNC_1}system")], id: user_sync_uuid, external_id: Some("cn=testgroup,ou=people,dc=test".to_string()), meta: None, @@ -3168,9 +3167,9 @@ mod tests { "entries": [ { "schemas": [ - "urn:ietf:params:scim:schemas:kanidm:1.0:person", - "urn:ietf:params:scim:schemas:kanidm:1.0:account", - "urn:ietf:params:scim:schemas:kanidm:1.0:posixaccount" + "urn:ietf:params:scim:schemas:kanidm:sync:1:person", + "urn:ietf:params:scim:schemas:kanidm:sync:1:account", + "urn:ietf:params:scim:schemas:kanidm:sync:1:posixaccount" ], "id": "babb8302-43a1-11ed-a50d-919b4b1a5ec0", "externalId": "uid=testuser,cn=users,cn=accounts,dc=dev,dc=blackhats,dc=net,dc=au", @@ -3194,7 +3193,7 @@ mod tests { }, { "schemas": [ - "urn:ietf:params:scim:schemas:kanidm:1.0:group" + "urn:ietf:params:scim:schemas:kanidm:sync:1:group" ], "id": "d547c581-5f26-11ed-a50d-919b4b1a5ec0", "externalId": "cn=testgroup,cn=groups,cn=accounts,dc=dev,dc=blackhats,dc=net,dc=au", @@ -3208,7 +3207,7 @@ mod tests { }, { "schemas": [ - "urn:ietf:params:scim:schemas:kanidm:1.0:group" + "urn:ietf:params:scim:schemas:kanidm:sync:1:group" ], "id": "d547c583-5f26-11ed-a50d-919b4b1a5ec0", "externalId": "cn=testexternal,cn=groups,cn=accounts,dc=dev,dc=blackhats,dc=net,dc=au", @@ -3216,8 +3215,8 @@ mod tests { }, { "schemas": [ - "urn:ietf:params:scim:schemas:kanidm:1.0:group", - "urn:ietf:params:scim:schemas:kanidm:1.0:posixgroup" + "urn:ietf:params:scim:schemas:kanidm:sync:1:group", + "urn:ietf:params:scim:schemas:kanidm:sync:1:posixgroup" ], "id": "f90b0b81-5f26-11ed-a50d-919b4b1a5ec0", "externalId": "cn=testposix,cn=groups,cn=accounts,dc=dev,dc=blackhats,dc=net,dc=au", @@ -3249,7 +3248,7 @@ mod tests { "entries": [ { "schemas": [ - "urn:ietf:params:scim:schemas:kanidm:1.0:group" + "urn:ietf:params:scim:schemas:kanidm:sync:1:group" ], "id": "d547c583-5f26-11ed-a50d-919b4b1a5ec0", "externalId": "cn=testexternal2,cn=groups,cn=accounts,dc=dev,dc=blackhats,dc=net,dc=au", @@ -3262,8 +3261,8 @@ mod tests { }, { "schemas": [ - "urn:ietf:params:scim:schemas:kanidm:1.0:group", - "urn:ietf:params:scim:schemas:kanidm:1.0:posixgroup" + "urn:ietf:params:scim:schemas:kanidm:sync:1:group", + "urn:ietf:params:scim:schemas:kanidm:sync:1:posixgroup" ], "id": "f90b0b81-5f26-11ed-a50d-919b4b1a5ec0", "externalId": "cn=testposix,cn=groups,cn=accounts,dc=dev,dc=blackhats,dc=net,dc=au", @@ -3290,9 +3289,9 @@ mod tests { "entries": [ { "schemas": [ - "urn:ietf:params:scim:schemas:kanidm:1.0:person", - "urn:ietf:params:scim:schemas:kanidm:1.0:account", - "urn:ietf:params:scim:schemas:kanidm:1.0:posixaccount" + "urn:ietf:params:scim:schemas:kanidm:sync:1:person", + "urn:ietf:params:scim:schemas:kanidm:sync:1:account", + "urn:ietf:params:scim:schemas:kanidm:sync:1:posixaccount" ], "id": "babb8302-43a1-11ed-a50d-919b4b1a5ec0", "externalId": "uid=testuser,cn=users,cn=accounts,dc=dev,dc=blackhats,dc=net,dc=au", @@ -3307,7 +3306,7 @@ mod tests { }, { "schemas": [ - "urn:ietf:params:scim:schemas:kanidm:1.0:group" + "urn:ietf:params:scim:schemas:kanidm:sync:1:group" ], "id": "d547c581-5f26-11ed-a50d-919b4b1a5ec0", "externalId": "cn=testgroup,cn=groups,cn=accounts,dc=dev,dc=blackhats,dc=net,dc=au", diff --git a/server/lib/src/idm/server.rs b/server/lib/src/idm/server.rs index de2bfe86b..4ee5141e7 100644 --- a/server/lib/src/idm/server.rs +++ b/server/lib/src/idm/server.rs @@ -11,11 +11,11 @@ use concread::cowcell::{CowCellReadTxn, CowCellWriteTxn}; use concread::hashmap::HashMap; use concread::CowCell; use fernet::Fernet; -use kanidm_proto::internal::ScimSyncToken; -use kanidm_proto::v1::{ - ApiToken, BackupCodesView, CredentialStatus, PasswordFeedback, RadiusAuthToken, UatPurpose, - UnixGroupToken, UnixUserToken, UserAuthToken, +use kanidm_proto::internal::{ + ApiToken, BackupCodesView, CredentialStatus, PasswordFeedback, RadiusAuthToken, ScimSyncToken, + UatPurpose, UserAuthToken, }; +use kanidm_proto::v1::{UnixGroupToken, UnixUserToken}; use rand::prelude::*; use tokio::sync::mpsc::{ unbounded_channel as unbounded, UnboundedReceiver as Receiver, UnboundedSender as Sender, @@ -2165,7 +2165,7 @@ mod tests { use std::convert::TryFrom; use std::time::Duration; - use kanidm_proto::v1::{AuthAllowed, AuthIssueSession, AuthMech, OperationError}; + use kanidm_proto::v1::{AuthAllowed, AuthIssueSession, AuthMech}; use time::OffsetDateTime; use uuid::Uuid; @@ -3592,7 +3592,7 @@ mod tests { idms: &IdmServer, idms_delayed: &mut IdmServerDelayed, ) { - use kanidm_proto::v1::UserAuthToken; + use kanidm_proto::internal::UserAuthToken; use std::str::FromStr; let ct = Duration::from_secs(TEST_CURRENT_TIME); diff --git a/server/lib/src/idm/serviceaccount.rs b/server/lib/src/idm/serviceaccount.rs index 05822a5ed..b24ec2b6b 100644 --- a/server/lib/src/idm/serviceaccount.rs +++ b/server/lib/src/idm/serviceaccount.rs @@ -2,7 +2,7 @@ use std::collections::BTreeMap; use std::time::Duration; use compact_jwt::{Jws, JwsEs256Signer, JwsSigner}; -use kanidm_proto::v1::ApiToken as ProtoApiToken; +use kanidm_proto::internal::ApiToken as ProtoApiToken; use time::OffsetDateTime; use crate::credential::Credential; @@ -14,21 +14,6 @@ use crate::prelude::*; use crate::utils::password_from_random; use crate::value::ApiToken; -// Need to add KID to es256 der for lookups ✅ - -// Need to generate the es256 on the account on modifies ✅ - -// Add migration to generate the es256 on startup at least once. ✅ - -// Create new valueset type to store sessions w_ labels ✅ - -// Able to lookup from KID to get service account - -// Able to take token -> ident -// -- check still valid - -// revoke - macro_rules! try_from_entry { ($value:expr) => {{ // Check the classes @@ -432,7 +417,7 @@ mod tests { use std::time::Duration; use compact_jwt::{JwsCompact, JwsEs256Verifier, JwsVerifier}; - use kanidm_proto::v1::ApiToken; + use kanidm_proto::internal::ApiToken; use super::{DestroyApiTokenEvent, GenerateApiTokenEvent}; use crate::event::CreateEvent; diff --git a/server/lib/src/idm/unix.rs b/server/lib/src/idm/unix.rs index a6ed9e4f0..1b9462d2c 100644 --- a/server/lib/src/idm/unix.rs +++ b/server/lib/src/idm/unix.rs @@ -2,7 +2,7 @@ use std::iter; // use crossbeam::channel::Sender; use std::time::Duration; -use kanidm_proto::v1::{OperationError, UnixGroupToken, UnixUserToken}; +use kanidm_proto::v1::{UnixGroupToken, UnixUserToken}; use time::OffsetDateTime; use tokio::sync::mpsc::UnboundedSender as Sender; use uuid::Uuid; diff --git a/server/lib/src/lib.rs b/server/lib/src/lib.rs index b439fd98a..cdf6da60b 100644 --- a/server/lib/src/lib.rs +++ b/server/lib/src/lib.rs @@ -63,7 +63,7 @@ pub mod testkit; /// help make imports cleaner. pub mod prelude { pub use kanidm_proto::constants::*; - pub use kanidm_proto::v1::{ConsistencyError, OperationError, SchemaError}; + pub use kanidm_proto::internal::{ConsistencyError, OperationError, PluginError, SchemaError}; pub use sketching::{ admin_debug, admin_error, admin_info, admin_warn, filter_error, filter_info, filter_trace, filter_warn, perf_trace, request_error, request_info, request_trace, request_warn, diff --git a/server/lib/src/modify.rs b/server/lib/src/modify.rs index 42141ee6a..0ef0f738a 100644 --- a/server/lib/src/modify.rs +++ b/server/lib/src/modify.rs @@ -4,10 +4,10 @@ use std::slice; -use kanidm_proto::v1::{ - Entry as ProtoEntry, Modify as ProtoModify, ModifyList as ProtoModifyList, OperationError, - SchemaError, +use kanidm_proto::internal::{ + Modify as ProtoModify, ModifyList as ProtoModifyList, OperationError, SchemaError, }; +use kanidm_proto::v1::Entry as ProtoEntry; // Should this be std? use serde::{Deserialize, Serialize}; use smartstring::alias::String as AttrString; diff --git a/server/lib/src/plugins/attrunique.rs b/server/lib/src/plugins/attrunique.rs index 0b6893188..64d64adcd 100644 --- a/server/lib/src/plugins/attrunique.rs +++ b/server/lib/src/plugins/attrunique.rs @@ -8,7 +8,6 @@ use std::collections::VecDeque; use std::collections::{BTreeMap, BTreeSet}; use std::sync::Arc; -use kanidm_proto::v1::{ConsistencyError, PluginError}; use tracing::trace; use crate::event::{CreateEvent, ModifyEvent}; @@ -491,8 +490,6 @@ impl Plugin for AttrUnique { #[cfg(test)] mod tests { - use kanidm_proto::v1::PluginError; - use crate::prelude::*; // Test entry in db, and same name, reject. diff --git a/server/lib/src/plugins/base.rs b/server/lib/src/plugins/base.rs index 2de84247e..9ca3faa11 100644 --- a/server/lib/src/plugins/base.rs +++ b/server/lib/src/plugins/base.rs @@ -3,7 +3,6 @@ use std::iter::once; use std::sync::Arc; use hashbrown::HashSet; -use kanidm_proto::v1::{ConsistencyError, PluginError}; use crate::event::{CreateEvent, ModifyEvent}; use crate::modify::Modify; @@ -241,7 +240,6 @@ impl Plugin for Base { #[cfg(test)] mod tests { use crate::prelude::*; - use kanidm_proto::v1::PluginError; use std::sync::Arc; const UUID_TEST_ACCOUNT: Uuid = uuid::uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930"); diff --git a/server/lib/src/plugins/cred_import.rs b/server/lib/src/plugins/cred_import.rs index d0bcd3a9a..6c0746039 100644 --- a/server/lib/src/plugins/cred_import.rs +++ b/server/lib/src/plugins/cred_import.rs @@ -3,8 +3,6 @@ use std::convert::TryFrom; use std::iter::once; use std::sync::Arc; -use kanidm_proto::v1::PluginError; - use crate::credential::{Credential, Password}; use crate::event::{CreateEvent, ModifyEvent}; use crate::plugins::Plugin; @@ -171,7 +169,6 @@ mod tests { use crate::credential::{Credential, CredentialType}; use crate::prelude::*; use kanidm_lib_crypto::CryptoPolicy; - use kanidm_proto::v1::PluginError; const IMPORT_HASH: &str = "pbkdf2_sha256$36000$xIEozuZVAoYm$uW1b35DUKyhvQAf1mBqMvoBDcqSD06juzyO/nmyV0+w="; diff --git a/server/lib/src/plugins/domain.rs b/server/lib/src/plugins/domain.rs index ab7d832ab..94744287b 100644 --- a/server/lib/src/plugins/domain.rs +++ b/server/lib/src/plugins/domain.rs @@ -8,7 +8,6 @@ use std::iter::once; use std::sync::Arc; use compact_jwt::JwsEs256Signer; -use kanidm_proto::v1::OperationError; use rand::prelude::*; use regex::Regex; use tracing::trace; diff --git a/server/lib/src/plugins/dyngroup.rs b/server/lib/src/plugins/dyngroup.rs index 458796b1e..184c82442 100644 --- a/server/lib/src/plugins/dyngroup.rs +++ b/server/lib/src/plugins/dyngroup.rs @@ -1,7 +1,7 @@ use std::collections::BTreeMap; use std::sync::Arc; -use kanidm_proto::v1::Filter as ProtoFilter; +use kanidm_proto::internal::Filter as ProtoFilter; use crate::filter::FilterInvalid; use crate::prelude::*; @@ -378,7 +378,7 @@ impl DynGroup { #[cfg(test)] mod tests { - use kanidm_proto::v1::Filter as ProtoFilter; + use kanidm_proto::internal::Filter as ProtoFilter; use crate::prelude::*; diff --git a/server/lib/src/plugins/eckeygen.rs b/server/lib/src/plugins/eckeygen.rs index 67ac15c38..99644f09d 100644 --- a/server/lib/src/plugins/eckeygen.rs +++ b/server/lib/src/plugins/eckeygen.rs @@ -2,6 +2,7 @@ use openssl::ec::{EcGroup, EcKey}; use openssl::nid::Nid; use crate::prelude::*; +use std::sync::Arc; use super::Plugin; @@ -62,21 +63,21 @@ impl Plugin for EcdhKeyGen { #[instrument(level = "debug", name = "ecdhkeygen::pre_modify", skip_all)] fn pre_modify( - _qs: &mut crate::server::QueryServerWriteTransaction, - _pre_cand: &[std::sync::Arc], - cand: &mut Vec, - _me: &crate::event::ModifyEvent, - ) -> Result<(), kanidm_proto::v1::OperationError> { + _qs: &mut QueryServerWriteTransaction, + _pre_cand: &[Arc], + cand: &mut Vec, + _me: &ModifyEvent, + ) -> Result<(), OperationError> { Self::generate_key(cand) } #[instrument(level = "debug", name = "ecdhkeygen::pre_batch_modify", skip_all)] fn pre_batch_modify( - _qs: &mut crate::server::QueryServerWriteTransaction, - _pre_cand: &[std::sync::Arc], - cand: &mut Vec, - _me: &crate::server::batch_modify::BatchModifyEvent, - ) -> Result<(), kanidm_proto::v1::OperationError> { + _qs: &mut QueryServerWriteTransaction, + _pre_cand: &[Arc], + cand: &mut Vec, + _me: &BatchModifyEvent, + ) -> Result<(), OperationError> { Self::generate_key(cand) } } @@ -86,7 +87,7 @@ mod tests { use openssl::ec::EcKey; use uuid::Uuid; - use crate::plugins::eckeygen::DEFAULT_KEY_GROUP; + use super::DEFAULT_KEY_GROUP; use crate::prelude::*; use crate::value::Value; use crate::valueset; diff --git a/server/lib/src/plugins/memberof.rs b/server/lib/src/plugins/memberof.rs index 67f416131..6fc3ffdb7 100644 --- a/server/lib/src/plugins/memberof.rs +++ b/server/lib/src/plugins/memberof.rs @@ -14,7 +14,6 @@ use std::collections::BTreeSet; use std::sync::Arc; use hashbrown::HashMap; -use kanidm_proto::v1::{ConsistencyError, OperationError}; use crate::entry::{Entry, EntryCommitted, EntrySealed, EntryTuple}; use crate::event::{CreateEvent, DeleteEvent, ModifyEvent}; diff --git a/server/lib/src/plugins/mod.rs b/server/lib/src/plugins/mod.rs index 15e565ed7..926077aec 100644 --- a/server/lib/src/plugins/mod.rs +++ b/server/lib/src/plugins/mod.rs @@ -6,8 +6,6 @@ use std::collections::BTreeSet; use std::sync::Arc; -use kanidm_proto::v1::{ConsistencyError, OperationError}; - use crate::entry::{Entry, EntryCommitted, EntryInvalid, EntryNew, EntrySealed}; use crate::event::{CreateEvent, DeleteEvent, ModifyEvent}; use crate::prelude::*; diff --git a/server/lib/src/plugins/namehistory.rs b/server/lib/src/plugins/namehistory.rs index 674eb33ec..e7d545eb4 100644 --- a/server/lib/src/plugins/namehistory.rs +++ b/server/lib/src/plugins/namehistory.rs @@ -1,7 +1,5 @@ use std::sync::Arc; -use kanidm_proto::v1::OperationError; - use crate::entry::{EntryInvalidCommitted, EntrySealedCommitted}; use crate::event::ModifyEvent; use crate::plugins::Plugin; diff --git a/server/lib/src/plugins/refint.rs b/server/lib/src/plugins/refint.rs index 93eca58c3..a2fb32252 100644 --- a/server/lib/src/plugins/refint.rs +++ b/server/lib/src/plugins/refint.rs @@ -13,7 +13,6 @@ use std::collections::BTreeSet; use std::sync::Arc; use hashbrown::HashSet; -use kanidm_proto::v1::ConsistencyError; use crate::event::{CreateEvent, DeleteEvent, ModifyEvent}; use crate::filter::{f_eq, FC}; @@ -439,18 +438,15 @@ impl ReferentialIntegrity { error!(?missing); } - Err(OperationError::Plugin( - kanidm_proto::v1::PluginError::ReferentialIntegrity( - "Uuid referenced not found in database".to_string(), - ), - )) + Err(OperationError::Plugin(PluginError::ReferentialIntegrity( + "Uuid referenced not found in database".to_string(), + ))) } } #[cfg(test)] mod tests { - use kanidm_proto::v1::Filter as ProtoFilter; - use kanidm_proto::v1::PluginError; + use kanidm_proto::internal::Filter as ProtoFilter; use crate::event::CreateEvent; use crate::prelude::*; diff --git a/server/lib/src/plugins/spn.rs b/server/lib/src/plugins/spn.rs index 0aff41e28..2eae129d6 100644 --- a/server/lib/src/plugins/spn.rs +++ b/server/lib/src/plugins/spn.rs @@ -4,9 +4,6 @@ use std::collections::BTreeSet; use std::iter::once; use std::sync::Arc; -// use crate::value::{PartialValue, Value}; -use kanidm_proto::v1::{ConsistencyError, OperationError}; - use crate::entry::{Entry, EntryCommitted, EntryInvalid, EntryNew, EntrySealed}; use crate::event::{CreateEvent, ModifyEvent}; use crate::plugins::Plugin; diff --git a/server/lib/src/repl/cid.rs b/server/lib/src/repl/cid.rs index f198e179a..bc186085c 100644 --- a/server/lib/src/repl/cid.rs +++ b/server/lib/src/repl/cid.rs @@ -3,7 +3,6 @@ use std::time::Duration; use crate::be::dbvalue::DbCidV1; use crate::prelude::*; -use kanidm_proto::v1::OperationError; use serde::{Deserialize, Serialize}; #[derive(Serialize, Deserialize, PartialEq, Clone, Eq, PartialOrd, Ord, Hash)] diff --git a/server/lib/src/repl/ruv.rs b/server/lib/src/repl/ruv.rs index b07ffde9a..21b71f388 100644 --- a/server/lib/src/repl/ruv.rs +++ b/server/lib/src/repl/ruv.rs @@ -8,7 +8,6 @@ use std::time::Duration; use concread::bptree::{BptreeMap, BptreeMapReadSnapshot, BptreeMapReadTxn, BptreeMapWriteTxn}; use idlset::v2::IDLBitRange; -use kanidm_proto::v1::ConsistencyError; use crate::prelude::*; use crate::repl::cid::Cid; diff --git a/server/lib/src/schema.rs b/server/lib/src/schema.rs index 739bea560..4a971a473 100644 --- a/server/lib/src/schema.rs +++ b/server/lib/src/schema.rs @@ -20,7 +20,6 @@ use std::collections::BTreeSet; use concread::cowcell::*; use hashbrown::{HashMap, HashSet}; -use kanidm_proto::v1::{ConsistencyError, OperationError, SchemaError}; use tracing::trace; use uuid::Uuid; @@ -2268,13 +2267,11 @@ impl Schema { #[cfg(test)] mod tests { - use kanidm_proto::v1::{ConsistencyError, SchemaError}; - use uuid::Uuid; - use crate::prelude::*; use crate::schema::{ IndexType, Schema, SchemaAttribute, SchemaClass, SchemaTransaction, SyntaxType, }; + use uuid::Uuid; // use crate::proto_v1::Filter as ProtoFilter; diff --git a/server/lib/src/server/access/mod.rs b/server/lib/src/server/access/mod.rs index 70ae52fdd..9275cb3e1 100644 --- a/server/lib/src/server/access/mod.rs +++ b/server/lib/src/server/access/mod.rs @@ -21,7 +21,6 @@ use std::sync::Arc; use concread::arcache::{ARCache, ARCacheBuilder, ARCacheReadTxn}; use concread::cowcell::*; -use kanidm_proto::v1::OperationError; use tracing::trace; use uuid::Uuid; diff --git a/server/lib/src/server/access/profiles.rs b/server/lib/src/server/access/profiles.rs index 8377bb8e9..cec08d48a 100644 --- a/server/lib/src/server/access/profiles.rs +++ b/server/lib/src/server/access/profiles.rs @@ -3,7 +3,7 @@ use std::collections::BTreeSet; use crate::filter::{Filter, FilterValid, FilterValidResolved}; -use kanidm_proto::v1::Filter as ProtoFilter; +use kanidm_proto::internal::Filter as ProtoFilter; // ========================================================================= // PARSE ENTRY TO ACP, AND ACP MANAGEMENT diff --git a/server/lib/src/server/identity.rs b/server/lib/src/server/identity.rs index a4d388a81..e0e98aaa7 100644 --- a/server/lib/src/server/identity.rs +++ b/server/lib/src/server/identity.rs @@ -10,7 +10,7 @@ use std::net::IpAddr; use std::sync::Arc; use uuid::uuid; -use kanidm_proto::v1::{ApiTokenPurpose, UatPurpose}; +use kanidm_proto::internal::{ApiTokenPurpose, UatPurpose}; use serde::{Deserialize, Serialize}; diff --git a/server/lib/src/server/migrations.rs b/server/lib/src/server/migrations.rs index 8d05a16ec..39dc1cdc5 100644 --- a/server/lib/src/server/migrations.rs +++ b/server/lib/src/server/migrations.rs @@ -1,5 +1,4 @@ use crate::value::CredentialType; -use kanidm_proto::v1::SchemaError; use std::time::Duration; use crate::prelude::*; diff --git a/server/lib/src/server/mod.rs b/server/lib/src/server/mod.rs index d2acdecf0..53067b381 100644 --- a/server/lib/src/server/mod.rs +++ b/server/lib/src/server/mod.rs @@ -11,8 +11,7 @@ use std::collections::BTreeSet; use tokio::sync::{Semaphore, SemaphorePermit}; use tracing::trace; -use kanidm_proto::internal::DomainInfo as ProtoDomainInfo; -use kanidm_proto::v1::{ConsistencyError, UiHint}; +use kanidm_proto::internal::{DomainInfo as ProtoDomainInfo, UiHint}; use crate::be::{Backend, BackendReadTransaction, BackendTransaction, BackendWriteTransaction}; // We use so many, we just import them all ... diff --git a/server/lib/src/value.rs b/server/lib/src/value.rs index 8a0e88fe9..735bf1ab9 100644 --- a/server/lib/src/value.rs +++ b/server/lib/src/value.rs @@ -39,10 +39,8 @@ use crate::repl::cid::Cid; use crate::server::identity::IdentityId; use crate::valueset::image::ImageValueThings; use crate::valueset::uuid_to_proto_string; -use kanidm_proto::v1::ApiTokenPurpose; -use kanidm_proto::v1::Filter as ProtoFilter; +use kanidm_proto::internal::{ApiTokenPurpose, Filter as ProtoFilter, UiHint}; use kanidm_proto::v1::UatPurposeStatus; -use kanidm_proto::v1::UiHint; use std::hash::Hash; lazy_static! { diff --git a/server/lib/src/valueset/eckey.rs b/server/lib/src/valueset/eckey.rs index cc7702848..c9679d3e4 100644 --- a/server/lib/src/valueset/eckey.rs +++ b/server/lib/src/valueset/eckey.rs @@ -4,7 +4,6 @@ use crate::be::dbvalue::DbValueSetV2; use crate::prelude::*; use crate::repl::proto::ReplAttrV1; use crate::value::{PartialValue, SyntaxType, Value}; -use kanidm_proto::v1::OperationError; use openssl::ec::EcKey; use openssl::pkey::{Private, Public}; @@ -73,10 +72,7 @@ impl ValueSetEcKeyPrivate { } impl ValueSetT for ValueSetEcKeyPrivate { - fn insert_checked( - &mut self, - value: crate::value::Value, - ) -> Result { + fn insert_checked(&mut self, value: crate::value::Value) -> Result { match value { Value::EcKeyPrivate(k) => Ok(self.push(&k)), _ => { @@ -190,7 +186,7 @@ impl ValueSetT for ValueSetEcKeyPrivate { }) } - fn merge(&mut self, other: &super::ValueSet) -> Result<(), kanidm_proto::v1::OperationError> { + fn merge(&mut self, other: &super::ValueSet) -> Result<(), OperationError> { if let Some(other_key) = other.as_ec_key_private() { let priv_key = other_key.clone(); let pub_key = Self::private_key_to_public_key(&priv_key) diff --git a/server/lib/src/valueset/json.rs b/server/lib/src/valueset/json.rs index c0829830c..c81149985 100644 --- a/server/lib/src/valueset/json.rs +++ b/server/lib/src/valueset/json.rs @@ -1,4 +1,4 @@ -use kanidm_proto::v1::Filter as ProtoFilter; +use kanidm_proto::internal::Filter as ProtoFilter; use smolset::SmolSet; use crate::prelude::*; diff --git a/server/lib/src/valueset/mod.rs b/server/lib/src/valueset/mod.rs index c79f791ea..7ca454f23 100644 --- a/server/lib/src/valueset/mod.rs +++ b/server/lib/src/valueset/mod.rs @@ -14,8 +14,7 @@ use webauthn_rs::prelude::AttestationCaList; use webauthn_rs::prelude::AttestedPasskey as AttestedPasskeyV4; use webauthn_rs::prelude::Passkey as PasskeyV4; -use kanidm_proto::v1::Filter as ProtoFilter; -use kanidm_proto::v1::UiHint; +use kanidm_proto::internal::{Filter as ProtoFilter, UiHint}; use crate::be::dbvalue::DbValueSetV2; use crate::credential::{totp::Totp, Credential}; diff --git a/server/lib/src/valueset/uihint.rs b/server/lib/src/valueset/uihint.rs index 2f88ca859..446eb1597 100644 --- a/server/lib/src/valueset/uihint.rs +++ b/server/lib/src/valueset/uihint.rs @@ -5,7 +5,7 @@ use crate::repl::proto::ReplAttrV1; use crate::schema::SchemaAttribute; use crate::valueset::{DbValueSetV2, ValueSet}; -use kanidm_proto::v1::UiHint; +use kanidm_proto::internal::UiHint; #[derive(Debug, Clone)] pub struct ValueSetUiHint { diff --git a/server/testkit/build.rs b/server/testkit/build.rs index 5a19158c1..9fadb5cd4 100644 --- a/server/testkit/build.rs +++ b/server/testkit/build.rs @@ -1,3 +1,4 @@ fn main() { profiles::apply_profile(); + println!("cargo:rerun-if-changed=build.rs"); } diff --git a/server/testkit/src/lib.rs b/server/testkit/src/lib.rs index 781cbd731..9d475a048 100644 --- a/server/testkit/src/lib.rs +++ b/server/testkit/src/lib.rs @@ -15,7 +15,7 @@ use std::sync::atomic::{AtomicU16, Ordering}; use kanidm_client::{KanidmClient, KanidmClientBuilder}; -use kanidm_proto::v1::{Filter, Modify, ModifyList}; +use kanidm_proto::internal::{Filter, Modify, ModifyList}; use kanidmd_core::config::{Configuration, IntegrationTestConfig}; use kanidmd_core::{create_server_core, CoreHandle}; use kanidmd_lib::prelude::{Attribute, BUILTIN_GROUP_IDM_ADMINS_V1}; diff --git a/server/testkit/tests/identity_verification_tests.rs b/server/testkit/tests/identity_verification_tests.rs index b7bb8eb2a..bc60a8a2f 100644 --- a/server/testkit/tests/identity_verification_tests.rs +++ b/server/testkit/tests/identity_verification_tests.rs @@ -1,5 +1,6 @@ use core::result::Result::Err; use kanidm_client::KanidmClient; +use kanidm_proto::internal::OperationError; use kanidm_proto::internal::{IdentifyUserRequest, IdentifyUserResponse}; use kanidmd_lib::prelude::Attribute; @@ -57,7 +58,7 @@ async fn test_non_existing_user_id(rsclient: KanidmClient) { .idm_person_identify_user(non_existing_user, IdentifyUserRequest::Start) .await; assert!( - matches!(dbg!(res), Err(err) if matches!(err, kanidm_client::ClientError::Http(StatusCode::NOT_FOUND, Some(kanidm_proto::v1::OperationError::NoMatchingEntries), .. ))) + matches!(dbg!(res), Err(err) if matches!(err, kanidm_client::ClientError::Http(StatusCode::NOT_FOUND, Some(OperationError::NoMatchingEntries), .. ))) ); let res = rsclient @@ -65,7 +66,7 @@ async fn test_non_existing_user_id(rsclient: KanidmClient) { .await; assert!( - matches!(dbg!(res), Err(err) if matches!(err, kanidm_client::ClientError::Http(StatusCode::NOT_FOUND, Some(kanidm_proto::v1::OperationError::NoMatchingEntries), .. ))) + matches!(dbg!(res), Err(err) if matches!(err, kanidm_client::ClientError::Http(StatusCode::NOT_FOUND, Some(OperationError::NoMatchingEntries), .. ))) ); let res = rsclient @@ -76,7 +77,7 @@ async fn test_non_existing_user_id(rsclient: KanidmClient) { .await; assert!( - matches!(dbg!(res), Err(err) if matches!(err, kanidm_client::ClientError::Http(StatusCode::NOT_FOUND, Some(kanidm_proto::v1::OperationError::NoMatchingEntries), .. ))) + matches!(dbg!(res), Err(err) if matches!(err, kanidm_client::ClientError::Http(StatusCode::NOT_FOUND, Some(OperationError::NoMatchingEntries), .. ))) ); } diff --git a/server/testkit/tests/proto_v1_test.rs b/server/testkit/tests/proto_v1_test.rs index 6e22daadb..2c0db7885 100644 --- a/server/testkit/tests/proto_v1_test.rs +++ b/server/testkit/tests/proto_v1_test.rs @@ -3,10 +3,12 @@ use std::path::Path; use std::time::SystemTime; use kanidm_proto::constants::KSESSIONID; -use kanidm_proto::internal::ImageValue; +use kanidm_proto::internal::{ + ApiToken, CURegState, Filter, ImageValue, Modify, ModifyList, UatPurpose, UserAuthToken, +}; use kanidm_proto::v1::{ - ApiToken, AuthCredential, AuthIssueSession, AuthMech, AuthRequest, AuthResponse, AuthState, - AuthStep, CURegState, Entry, Filter, Modify, ModifyList, UatPurpose, UserAuthToken, + AuthCredential, AuthIssueSession, AuthMech, AuthRequest, AuthResponse, AuthState, AuthStep, + Entry, }; use kanidmd_lib::credential::totp::Totp; use kanidmd_lib::prelude::{ diff --git a/server/web_ui/shared/src/models.rs b/server/web_ui/shared/src/models.rs index 6908177e8..3d86d3ac0 100644 --- a/server/web_ui/shared/src/models.rs +++ b/server/web_ui/shared/src/models.rs @@ -1,8 +1,8 @@ #[cfg(debug_assertions)] use gloo::console; use gloo::storage::{LocalStorage, SessionStorage as TemporaryStorage, Storage}; +use kanidm_proto::internal::{CUSessionToken, CUStatus}; use kanidm_proto::oauth2::AuthorisationRequest; -use kanidm_proto::v1::{CUSessionToken, CUStatus}; use wasm_bindgen::UnwrapThrowExt; use crate::constants::URL_USER_HOME; diff --git a/server/web_ui/user/src/components/change_unix_password.rs b/server/web_ui/user/src/components/change_unix_password.rs index 2870014ed..769c1c84d 100644 --- a/server/web_ui/user/src/components/change_unix_password.rs +++ b/server/web_ui/user/src/components/change_unix_password.rs @@ -1,4 +1,5 @@ -use kanidm_proto::v1::{SingleStringRequest, UserAuthToken}; +use kanidm_proto::internal::UserAuthToken; +use kanidm_proto::v1::SingleStringRequest; use kanidmd_web_ui_shared::constants::ID_UNIX_PASSWORDCHANGE; use kanidmd_web_ui_shared::do_request; use kanidmd_web_ui_shared::error::FetchError; diff --git a/server/web_ui/user/src/components/create_reset_code.rs b/server/web_ui/user/src/components/create_reset_code.rs index 7d0a4bbd6..599b42e73 100644 --- a/server/web_ui/user/src/components/create_reset_code.rs +++ b/server/web_ui/user/src/components/create_reset_code.rs @@ -1,6 +1,6 @@ #[cfg(debug_assertions)] use gloo::console; -use kanidm_proto::v1::{CUIntentToken, UserAuthToken}; +use kanidm_proto::internal::{CUIntentToken, UserAuthToken}; use kanidmd_web_ui_shared::constants::{ID_CRED_RESET_CODE, URL_RESET}; use kanidmd_web_ui_shared::error::FetchError; use kanidmd_web_ui_shared::utils::{document, modal_hide_by_id, origin}; diff --git a/server/web_ui/user/src/components/profile.rs b/server/web_ui/user/src/components/profile.rs index ce364cef7..c1a84dbd4 100644 --- a/server/web_ui/user/src/components/profile.rs +++ b/server/web_ui/user/src/components/profile.rs @@ -1,6 +1,6 @@ #[cfg(debug_assertions)] use gloo::console; -use kanidm_proto::v1::{CUSessionToken, CUStatus, UiHint, UserAuthToken}; +use kanidm_proto::internal::{CUSessionToken, CUStatus, UiHint, UserAuthToken}; use kanidmd_web_ui_shared::models::{ push_cred_update_session, push_login_hint, push_return_location, }; diff --git a/server/web_ui/user/src/credential/delete.rs b/server/web_ui/user/src/credential/delete.rs index f97fada3b..0dae71291 100644 --- a/server/web_ui/user/src/credential/delete.rs +++ b/server/web_ui/user/src/credential/delete.rs @@ -1,6 +1,6 @@ #[cfg(debug_assertions)] use gloo::console; -use kanidm_proto::v1::{CURequest, CUSessionToken, CUStatus}; +use kanidm_proto::internal::{CURequest, CUSessionToken, CUStatus}; use kanidmd_web_ui_shared::error::FetchError; use kanidmd_web_ui_shared::utils::modal_hide_by_id; use serde::Serialize; diff --git a/server/web_ui/user/src/credential/passkey.rs b/server/web_ui/user/src/credential/passkey.rs index 1f1c65f97..400758f1f 100644 --- a/server/web_ui/user/src/credential/passkey.rs +++ b/server/web_ui/user/src/credential/passkey.rs @@ -1,5 +1,5 @@ use gloo::console; -use kanidm_proto::v1::{CURegState, CURequest, CUSessionToken, CUStatus}; +use kanidm_proto::internal::{CURegState, CURequest, CUSessionToken, CUStatus}; use kanidm_proto::webauthn::{CreationChallengeResponse, RegisterPublicKeyCredential}; use kanidmd_web_ui_shared::constants::CLASS_BUTTON_SUCCESS; use kanidmd_web_ui_shared::error::FetchError; diff --git a/server/web_ui/user/src/credential/passkeyremove.rs b/server/web_ui/user/src/credential/passkeyremove.rs index d486c64ec..fa870b728 100644 --- a/server/web_ui/user/src/credential/passkeyremove.rs +++ b/server/web_ui/user/src/credential/passkeyremove.rs @@ -1,6 +1,6 @@ #[cfg(debug_assertions)] use gloo::console; -use kanidm_proto::v1::{CURegState, CURequest, CUSessionToken, CUStatus}; +use kanidm_proto::internal::{CURegState, CURequest, CUSessionToken, CUStatus}; use serde::Serialize; use uuid::Uuid; use wasm_bindgen::UnwrapThrowExt; diff --git a/server/web_ui/user/src/credential/pwmodal.rs b/server/web_ui/user/src/credential/pwmodal.rs index 655652f7c..f2978da4d 100644 --- a/server/web_ui/user/src/credential/pwmodal.rs +++ b/server/web_ui/user/src/credential/pwmodal.rs @@ -1,5 +1,7 @@ use gloo::console; -use kanidm_proto::v1::{CURequest, CUSessionToken, CUStatus, OperationError, PasswordFeedback}; +use kanidm_proto::internal::{ + CURequest, CUSessionToken, CUStatus, OperationError, PasswordFeedback, +}; use wasm_bindgen::UnwrapThrowExt; use serde::Serialize; diff --git a/server/web_ui/user/src/credential/reset.rs b/server/web_ui/user/src/credential/reset.rs index 9a69c40cc..0249b0301 100644 --- a/server/web_ui/user/src/credential/reset.rs +++ b/server/web_ui/user/src/credential/reset.rs @@ -1,5 +1,5 @@ use gloo::console; -use kanidm_proto::v1::{ +use kanidm_proto::internal::{ CUCredState, CUExtPortal, CUIntentToken, CURegWarning, CUSessionToken, CUStatus, CredentialDetail, CredentialDetailType, PasskeyDetail, }; diff --git a/server/web_ui/user/src/credential/totpmodal.rs b/server/web_ui/user/src/credential/totpmodal.rs index 039261a24..56197be31 100644 --- a/server/web_ui/user/src/credential/totpmodal.rs +++ b/server/web_ui/user/src/credential/totpmodal.rs @@ -1,6 +1,6 @@ #[cfg(debug_assertions)] use gloo::console; -use kanidm_proto::v1::{CURegState, CURequest, CUSessionToken, CUStatus, TotpSecret}; +use kanidm_proto::internal::{CURegState, CURequest, CUSessionToken, CUStatus, TotpSecret}; use qrcode::render::svg; use qrcode::QrCode; use wasm_bindgen::UnwrapThrowExt; diff --git a/server/web_ui/user/src/credential/totpremove.rs b/server/web_ui/user/src/credential/totpremove.rs index 10528c7eb..b46249cad 100644 --- a/server/web_ui/user/src/credential/totpremove.rs +++ b/server/web_ui/user/src/credential/totpremove.rs @@ -1,7 +1,7 @@ use super::reset::{EventBusMsg, TotpRemoveProps}; #[cfg(debug_assertions)] use gloo::console; -use kanidm_proto::v1::{CURequest, CUSessionToken, CUStatus}; +use kanidm_proto::internal::{CURequest, CUSessionToken, CUStatus}; use kanidmd_web_ui_shared::RequestMethod; use kanidmd_web_ui_shared::{do_request, error::FetchError}; use serde::Serialize; diff --git a/server/web_ui/user/src/views/mod.rs b/server/web_ui/user/src/views/mod.rs index e3e00a8ba..dd334d466 100644 --- a/server/web_ui/user/src/views/mod.rs +++ b/server/web_ui/user/src/views/mod.rs @@ -2,7 +2,7 @@ #![allow(non_camel_case_types)] use gloo::console; use kanidm_proto::constants::uri::V1_AUTH_VALID; -use kanidm_proto::v1::{UiHint, UserAuthToken}; +use kanidm_proto::internal::{UiHint, UserAuthToken}; use kanidmd_web_ui_shared::constants::{ CSS_ALERT_DANGER, CSS_NAVBAR_BRAND, CSS_NAVBAR_LINKS_UL, CSS_NAVBAR_NAV, CSS_NAV_LINK, ID_NAVBAR_COLLAPSE, IMG_LOGO_SQUARE, URL_ADMIN, URL_LOGIN, diff --git a/tools/cli/build.rs b/tools/cli/build.rs index 0f8197eed..8be557e17 100644 --- a/tools/cli/build.rs +++ b/tools/cli/build.rs @@ -15,6 +15,10 @@ include!("src/opt/kanidm.rs"); fn main() -> Result<(), Error> { profiles::apply_profile(); + println!("cargo:rerun-if-changed=build.rs"); + println!("cargo:rerun-if-env-changed=OUT_DIR"); + println!("cargo:rerun-if-changed=src/opt/kanidm.rs"); + println!("cargo:rerun-if-changed=src/opt/ssh_authorizedkeys.rs"); let outdir = match env::var_os("OUT_DIR") { None => return Ok(()), Some(outdir) => outdir, diff --git a/tools/cli/src/cli/common.rs b/tools/cli/src/cli/common.rs index a26ddd9fe..3bc82c995 100644 --- a/tools/cli/src/cli/common.rs +++ b/tools/cli/src/cli/common.rs @@ -7,7 +7,7 @@ use dialoguer::theme::ColorfulTheme; use dialoguer::{Confirm, Select}; use kanidm_client::{KanidmClient, KanidmClientBuilder}; use kanidm_proto::constants::{DEFAULT_CLIENT_CONFIG_PATH, DEFAULT_CLIENT_CONFIG_PATH_HOME}; -use kanidm_proto::v1::UserAuthToken; +use kanidm_proto::internal::UserAuthToken; use time::format_description::well_known::Rfc3339; use time::OffsetDateTime; diff --git a/tools/cli/src/cli/person.rs b/tools/cli/src/cli/person.rs index b623cab8b..4b7d4caa2 100644 --- a/tools/cli/src/cli/person.rs +++ b/tools/cli/src/cli/person.rs @@ -7,13 +7,13 @@ use dialoguer::{Confirm, Input, Password, Select}; use kanidm_client::ClientError::Http as ClientErrorHttp; use kanidm_client::KanidmClient; use kanidm_proto::constants::{ATTR_ACCOUNT_EXPIRE, ATTR_ACCOUNT_VALID_FROM, ATTR_SSH_PUBLICKEY}; -use kanidm_proto::messages::{AccountChangeMessage, ConsoleOutputMode, MessageStatus}; -use kanidm_proto::v1::OperationError::PasswordQuality; -use kanidm_proto::v1::{ +use kanidm_proto::internal::OperationError::PasswordQuality; +use kanidm_proto::internal::{ CUCredState, CUExtPortal, CUIntentToken, CURegState, CURegWarning, CUSessionToken, CUStatus, TotpSecret, }; -use kanidm_proto::v1::{CredentialDetail, CredentialDetailType}; +use kanidm_proto::internal::{CredentialDetail, CredentialDetailType}; +use kanidm_proto::messages::{AccountChangeMessage, ConsoleOutputMode, MessageStatus}; use qrcode::render::unicode; use qrcode::QrCode; use time::format_description::well_known::Rfc3339; diff --git a/tools/cli/src/cli/raw.rs b/tools/cli/src/cli/raw.rs index 087fc777b..c53f211da 100644 --- a/tools/cli/src/cli/raw.rs +++ b/tools/cli/src/cli/raw.rs @@ -5,7 +5,8 @@ use std::fs::File; use std::io::BufReader; use std::path::Path; -use kanidm_proto::v1::{Entry, Filter, Modify, ModifyList}; +use kanidm_proto::internal::{Filter, Modify, ModifyList}; +use kanidm_proto::v1::Entry; use serde::de::DeserializeOwned; use crate::RawOpt; diff --git a/tools/cli/src/cli/session.rs b/tools/cli/src/cli/session.rs index 19351eafd..ac547aca2 100644 --- a/tools/cli/src/cli/session.rs +++ b/tools/cli/src/cli/session.rs @@ -11,7 +11,8 @@ use dialoguer::theme::ColorfulTheme; use dialoguer::Select; use kanidm_client::{ClientError, KanidmClient}; use kanidm_proto::constants::CLIENT_TOKEN_CACHE; -use kanidm_proto::v1::{AuthAllowed, AuthResponse, AuthState, UserAuthToken}; +use kanidm_proto::internal::UserAuthToken; +use kanidm_proto::v1::{AuthAllowed, AuthResponse, AuthState}; #[cfg(target_family = "unix")] use libc::umask; use webauthn_authenticator_rs::prelude::RequestChallengeResponse; diff --git a/tools/orca/build.rs b/tools/orca/build.rs index 5a19158c1..9fadb5cd4 100644 --- a/tools/orca/build.rs +++ b/tools/orca/build.rs @@ -1,3 +1,4 @@ fn main() { profiles::apply_profile(); + println!("cargo:rerun-if-changed=build.rs"); } diff --git a/tools/orca/src/kani.rs b/tools/orca/src/kani.rs index de34d9cd6..330899d63 100644 --- a/tools/orca/src/kani.rs +++ b/tools/orca/src/kani.rs @@ -2,7 +2,7 @@ use hashbrown::{HashMap, HashSet}; use std::time::{Duration, Instant}; use kanidm_client::{ClientError, KanidmClient, KanidmClientBuilder, StatusCode}; -use kanidm_proto::v1::*; +use kanidm_proto::internal::*; use uuid::Uuid; use crate::data::*; diff --git a/unix_integration/build.rs b/unix_integration/build.rs index c5b1b9164..08d8aae13 100644 --- a/unix_integration/build.rs +++ b/unix_integration/build.rs @@ -11,6 +11,10 @@ include!("src/opt/tool.rs"); fn main() { profiles::apply_profile(); + println!("cargo:rerun-if-changed=build.rs"); + println!("cargo:rerun-if-env-changed=OUT_DIR"); + println!("cargo:rerun-if-changed=src/opt/tool.rs"); + println!("cargo:rerun-if-changed=src/opt/ssh_authorizedkeys.rs"); let outdir = match env::var_os("OUT_DIR") { None => return, Some(outdir) => outdir, diff --git a/unix_integration/pam_kanidm/build.rs b/unix_integration/pam_kanidm/build.rs index f43d80630..3c23ee3f1 100644 --- a/unix_integration/pam_kanidm/build.rs +++ b/unix_integration/pam_kanidm/build.rs @@ -1,4 +1,5 @@ fn main() { + println!("cargo:rerun-if-changed=build.rs"); // ignore errors here since older versions of pam do not ship the pkg-config `pam.pc` file. // Not setting anything here will fall back on just blindly linking with `-lpam`, // which will work on environments with libpam.so, but no pkg-config file. diff --git a/unix_integration/src/idprovider/kanidm.rs b/unix_integration/src/idprovider/kanidm.rs index 2b4708da9..a23ea7694 100644 --- a/unix_integration/src/idprovider/kanidm.rs +++ b/unix_integration/src/idprovider/kanidm.rs @@ -1,7 +1,8 @@ use crate::db::KeyStoreTxn; use async_trait::async_trait; use kanidm_client::{ClientError, KanidmClient, StatusCode}; -use kanidm_proto::v1::{OperationError, UnixGroupToken, UnixUserToken}; +use kanidm_proto::internal::OperationError; +use kanidm_proto::v1::{UnixGroupToken, UnixUserToken}; use tokio::sync::RwLock; use super::interface::{