diff --git a/libs/client/src/domain.rs b/libs/client/src/domain.rs index 50457e647..f0c766658 100644 --- a/libs/client/src/domain.rs +++ b/libs/client/src/domain.rs @@ -1,4 +1,5 @@ use crate::{ClientError, KanidmClient}; +use kanidm_proto::constants::ATTR_DOMAIN_ALLOW_EASTER_EGGS; use kanidm_proto::internal::ImageValue; use reqwest::multipart; @@ -8,6 +9,14 @@ impl KanidmClient { self.perform_delete_request("/v1/domain/_image").await } + pub async fn idm_set_domain_allow_easter_eggs(&self, enable: bool) -> Result<(), ClientError> { + self.perform_put_request( + &format!("{}{}", "/v1/domain/_attr/", ATTR_DOMAIN_ALLOW_EASTER_EGGS), + vec![enable.to_string()], + ) + .await + } + /// Add or update the domain logo/image pub async fn idm_domain_update_image(&self, image: ImageValue) -> Result<(), ClientError> { let file_content_type = image.filetype.as_content_type_str(); diff --git a/proto/src/attribute.rs b/proto/src/attribute.rs index 9e25c103b..f0c00e907 100644 --- a/proto/src/attribute.rs +++ b/proto/src/attribute.rs @@ -53,6 +53,7 @@ pub enum Attribute { DisplayName, Dn, Domain, + DomainAllowEasterEggs, DomainDevelopmentTaint, DomainDisplayName, DomainLdapBasedn, @@ -282,6 +283,7 @@ impl Attribute { Attribute::DisplayName => ATTR_DISPLAYNAME, Attribute::Dn => ATTR_DN, Attribute::Domain => ATTR_DOMAIN, + Attribute::DomainAllowEasterEggs => ATTR_DOMAIN_ALLOW_EASTER_EGGS, Attribute::DomainDevelopmentTaint => ATTR_DOMAIN_DEVELOPMENT_TAINT, Attribute::DomainDisplayName => ATTR_DOMAIN_DISPLAY_NAME, Attribute::DomainLdapBasedn => ATTR_DOMAIN_LDAP_BASEDN, @@ -464,6 +466,7 @@ impl Attribute { ATTR_DISPLAYNAME => Attribute::DisplayName, ATTR_DN => Attribute::Dn, ATTR_DOMAIN => Attribute::Domain, + ATTR_DOMAIN_ALLOW_EASTER_EGGS => Attribute::DomainAllowEasterEggs, ATTR_DOMAIN_DISPLAY_NAME => Attribute::DomainDisplayName, ATTR_DOMAIN_DEVELOPMENT_TAINT => Attribute::DomainDevelopmentTaint, ATTR_DOMAIN_LDAP_BASEDN => Attribute::DomainLdapBasedn, diff --git a/proto/src/constants.rs b/proto/src/constants.rs index b1ed216a9..c6d8b3b0a 100644 --- a/proto/src/constants.rs +++ b/proto/src/constants.rs @@ -89,6 +89,7 @@ pub const ATTR_DESCRIPTION: &str = "description"; pub const ATTR_DIRECTMEMBEROF: &str = "directmemberof"; pub const ATTR_DISPLAYNAME: &str = "displayname"; pub const ATTR_DN: &str = "dn"; +pub const ATTR_DOMAIN_ALLOW_EASTER_EGGS: &str = "domain_allow_easter_eggs"; pub const ATTR_DOMAIN_DEVELOPMENT_TAINT: &str = "domain_development_taint"; pub const ATTR_DOMAIN_DISPLAY_NAME: &str = "domain_display_name"; pub const ATTR_DOMAIN_LDAP_BASEDN: &str = "domain_ldap_basedn"; diff --git a/server/lib/src/constants/acp.rs b/server/lib/src/constants/acp.rs index 5868832e5..7c0487745 100644 --- a/server/lib/src/constants/acp.rs +++ b/server/lib/src/constants/acp.rs @@ -1129,6 +1129,64 @@ lazy_static! { }; } +lazy_static! { + pub static ref IDM_ACP_DOMAIN_ADMIN_DL9: BuiltinAcp = BuiltinAcp { + classes: vec![ + EntryClass::Object, + EntryClass::AccessControlProfile, + EntryClass::AccessControlModify, + EntryClass::AccessControlSearch + ], + name: "idm_acp_domain_admin", + uuid: UUID_IDM_ACP_DOMAIN_ADMIN_V1, + description: "Builtin IDM Control for granting domain info administration locally", + receiver: BuiltinAcpReceiver::Group(vec![UUID_DOMAIN_ADMINS]), + target: BuiltinAcpTarget::Filter(ProtoFilter::And(vec![ + ProtoFilter::Eq( + Attribute::Uuid.to_string(), + STR_UUID_DOMAIN_INFO.to_string() + ), + FILTER_ANDNOT_TOMBSTONE_OR_RECYCLED.clone() + ])), + search_attrs: vec![ + Attribute::Class, + Attribute::Name, + Attribute::Uuid, + Attribute::DomainAllowEasterEggs, + Attribute::DomainDisplayName, + Attribute::DomainName, + Attribute::DomainLdapBasedn, + Attribute::DomainSsid, + Attribute::DomainUuid, + Attribute::KeyInternalData, + Attribute::LdapAllowUnixPwBind, + Attribute::Version, + Attribute::Image, + ], + modify_removed_attrs: vec![ + Attribute::DomainDisplayName, + Attribute::DomainSsid, + Attribute::DomainLdapBasedn, + Attribute::DomainAllowEasterEggs, + Attribute::LdapAllowUnixPwBind, + Attribute::KeyActionRevoke, + Attribute::KeyActionRotate, + Attribute::Image, + ], + modify_present_attrs: vec![ + Attribute::DomainDisplayName, + Attribute::DomainLdapBasedn, + Attribute::DomainSsid, + Attribute::DomainAllowEasterEggs, + Attribute::LdapAllowUnixPwBind, + Attribute::KeyActionRevoke, + Attribute::KeyActionRotate, + Attribute::Image, + ], + ..Default::default() + }; +} + lazy_static! { pub static ref IDM_ACP_SYNC_ACCOUNT_MANAGE_V1: BuiltinAcp = BuiltinAcp { classes: vec![ diff --git a/server/lib/src/constants/schema.rs b/server/lib/src/constants/schema.rs index 19a9d9907..a5d943e34 100644 --- a/server/lib/src/constants/schema.rs +++ b/server/lib/src/constants/schema.rs @@ -770,6 +770,14 @@ pub static ref SCHEMA_ATTR_DOMAIN_DEVELOPMENT_TAINT_DL7: SchemaAttribute = Schem ..Default::default() }; +pub static ref SCHEMA_ATTR_DOMAIN_ALLOW_EASTER_EGGS_DL9: SchemaAttribute = SchemaAttribute { + uuid: UUID_SCHEMA_ATTR_DOMAIN_ALLOW_EASTER_EGGS, + name: Attribute::DomainAllowEasterEggs, + description: "A flag to enable easter eggs in the server that may not always be wanted by all users/deployments.".to_string(), + syntax: SyntaxType::Boolean, + ..Default::default() +}; + pub static ref SCHEMA_ATTR_REFERS_DL7: SchemaAttribute = SchemaAttribute { uuid: UUID_SCHEMA_ATTR_REFERS, name: Attribute::Refers, @@ -1177,6 +1185,30 @@ pub static ref SCHEMA_CLASS_DOMAIN_INFO_DL8: SchemaClass = SchemaClass { ..Default::default() }; +pub static ref SCHEMA_CLASS_DOMAIN_INFO_DL9: SchemaClass = SchemaClass { + uuid: UUID_SCHEMA_CLASS_DOMAIN_INFO, + name: EntryClass::DomainInfo.into(), + description: "Local domain information and configuration".to_string(), + + systemmay: vec![ + Attribute::DomainSsid, + Attribute::DomainLdapBasedn, + Attribute::LdapAllowUnixPwBind, + Attribute::Image, + Attribute::PatchLevel, + Attribute::DomainDevelopmentTaint, + Attribute::DomainAllowEasterEggs, + ], + systemmust: vec![ + Attribute::Name, + Attribute::DomainUuid, + Attribute::DomainName, + Attribute::DomainDisplayName, + Attribute::Version, + ], + ..Default::default() +}; + pub static ref SCHEMA_CLASS_POSIXGROUP: SchemaClass = SchemaClass { uuid: UUID_SCHEMA_CLASS_POSIXGROUP, name: EntryClass::PosixGroup.into(), diff --git a/server/lib/src/constants/uuids.rs b/server/lib/src/constants/uuids.rs index f92e39389..708cd218b 100644 --- a/server/lib/src/constants/uuids.rs +++ b/server/lib/src/constants/uuids.rs @@ -321,6 +321,8 @@ pub const UUID_SCHEMA_ATTR_APPLICATION_PASSWORD: Uuid = pub const UUID_SCHEMA_ATTR_CREATED_AT_CID: Uuid = uuid!("00000000-0000-0000-0000-ffff00000184"); pub const UUID_SCHEMA_ATTR_ALLOW_PRIMARY_CRED_FALLBACK: Uuid = uuid!("00000000-0000-0000-0000-ffff00000185"); +pub const UUID_SCHEMA_ATTR_DOMAIN_ALLOW_EASTER_EGGS: Uuid = + uuid!("00000000-0000-0000-0000-ffff00000186"); // System and domain infos // I'd like to strongly criticise william of the past for making poor choices about these allocations. diff --git a/server/lib/src/server/migrations.rs b/server/lib/src/server/migrations.rs index e9fb0668b..b402df520 100644 --- a/server/lib/src/server/migrations.rs +++ b/server/lib/src/server/migrations.rs @@ -647,7 +647,9 @@ impl QueryServerWriteTransaction<'_> { // Now update schema let idm_schema_changes = [ SCHEMA_ATTR_OAUTH2_DEVICE_FLOW_ENABLE_DL9.clone().into(), + SCHEMA_ATTR_DOMAIN_ALLOW_EASTER_EGGS_DL9.clone().into(), SCHEMA_CLASS_OAUTH2_RS_DL9.clone().into(), + SCHEMA_CLASS_DOMAIN_INFO_DL9.clone().into(), ]; idm_schema_changes @@ -663,6 +665,7 @@ impl QueryServerWriteTransaction<'_> { let idm_data = [ IDM_ACP_OAUTH2_MANAGE_DL9.clone().into(), IDM_ACP_GROUP_MANAGE_DL9.clone().into(), + IDM_ACP_DOMAIN_ADMIN_DL9.clone().into(), ]; idm_data diff --git a/server/lib/src/server/mod.rs b/server/lib/src/server/mod.rs index a4bfec94f..f8ab0fc81 100644 --- a/server/lib/src/server/mod.rs +++ b/server/lib/src/server/mod.rs @@ -79,6 +79,7 @@ pub struct DomainInfo { pub(crate) d_patch_level: u32, pub(crate) d_devel_taint: bool, pub(crate) d_ldap_allow_unix_pw_bind: bool, + pub(crate) d_allow_easter_eggs: bool, // In future this should be image reference instead of the image itself. d_image: Option, } @@ -103,6 +104,10 @@ impl DomainInfo { pub fn has_custom_image(&self) -> bool { self.d_image.is_some() } + + pub fn allow_easter_eggs(&self) -> bool { + self.d_allow_easter_eggs + } } #[derive(Debug, Clone, PartialEq, Eq, Default)] @@ -1657,6 +1662,7 @@ impl QueryServer { // Automatically derive our current taint mode based on the PRERELEASE setting. d_devel_taint: option_env!("KANIDM_PRE_RELEASE").is_some(), d_ldap_allow_unix_pw_bind: false, + d_allow_easter_eggs: false, d_image: None, })); @@ -2284,6 +2290,11 @@ impl<'a> QueryServerWriteTransaction<'a> { .get_ava_single_bool(Attribute::DomainDevelopmentTaint) .unwrap_or_default(); + let domain_allow_easter_eggs = domain_info + .get_ava_single_bool(Attribute::DomainAllowEasterEggs) + // This defaults to false for release versions, and true in development + .unwrap_or(option_env!("KANIDM_PRE_RELEASE").is_some()); + // We have to set the domain version here so that features which check for it // will now see it's been increased. This also prevents recursion during reloads // inside of a domain migration. @@ -2293,6 +2304,7 @@ impl<'a> QueryServerWriteTransaction<'a> { mut_d_info.d_vers = domain_info_version; mut_d_info.d_patch_level = domain_info_patch_level; mut_d_info.d_devel_taint = domain_info_devel_taint; + mut_d_info.d_allow_easter_eggs = domain_allow_easter_eggs; // We must both be at the correct domain version *and* the correct patch level. If we are // not, then we only proceed to migrate *if* our server boot phase is correct. diff --git a/tools/cli/src/cli/domain/mod.rs b/tools/cli/src/cli/domain/mod.rs index 9ad264d0a..70067a50d 100644 --- a/tools/cli/src/cli/domain/mod.rs +++ b/tools/cli/src/cli/domain/mod.rs @@ -12,6 +12,7 @@ impl DomainOpt { | DomainOpt::SetImage { copt, .. } | DomainOpt::RemoveImage { copt } | DomainOpt::SetLdapAllowUnixPasswordBind { copt, .. } + | DomainOpt::SetAllowEasterEggs { copt, .. } | DomainOpt::RevokeKey { copt, .. } | DomainOpt::Show(copt) => copt.debug, } @@ -51,6 +52,19 @@ impl DomainOpt { Err(e) => handle_client_error(e, copt.output_mode), } } + DomainOpt::SetAllowEasterEggs { copt, enable } => { + let client = copt.to_client(OpType::Write).await; + match client.idm_set_domain_allow_easter_eggs(*enable).await { + Ok(_) => { + if *enable { + println!("Success 🎉 🥚 🎉") + } else { + println!("Success") + } + } + Err(e) => handle_client_error(e, copt.output_mode), + } + } DomainOpt::Show(copt) => { let client = copt.to_client(OpType::Read).await; match client.idm_domain_get().await { diff --git a/tools/cli/src/opt/kanidm.rs b/tools/cli/src/opt/kanidm.rs index 7515205d0..83a61fe4c 100644 --- a/tools/cli/src/opt/kanidm.rs +++ b/tools/cli/src/opt/kanidm.rs @@ -1332,6 +1332,15 @@ pub enum DomainOpt { #[clap(name = "allow", action = clap::ArgAction::Set)] enable: bool, }, + /// Enable or disable easter eggs in the server. This includes seasonal icons, kanidm + /// birthday surprises and other fun components. Defaults to false for production releases + /// and true in development builds. + SetAllowEasterEggs { + #[clap(flatten)] + copt: CommonOpt, + #[clap(name = "allow", action = clap::ArgAction::Set)] + enable: bool, + }, #[clap(name = "show")] /// Show information about this system's domain Show(CommonOpt),