Allow opt-in of easter eggs (#3308)

So that we can start to add some more easter eggs to the server,
we also need to respect user preferences that may not want them.

This adds a configuration setting to the domain allowing a release
build to opt-in to easter eggs, and development builds to opt-out
of them.
This commit is contained in:
Firstyear 2024-12-19 13:30:35 +10:00 committed by GitHub
parent 1fbbf323fa
commit 50a7d9d700
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 143 additions and 0 deletions

View file

@ -1,4 +1,5 @@
use crate::{ClientError, KanidmClient}; use crate::{ClientError, KanidmClient};
use kanidm_proto::constants::ATTR_DOMAIN_ALLOW_EASTER_EGGS;
use kanidm_proto::internal::ImageValue; use kanidm_proto::internal::ImageValue;
use reqwest::multipart; use reqwest::multipart;
@ -8,6 +9,14 @@ impl KanidmClient {
self.perform_delete_request("/v1/domain/_image").await 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 /// Add or update the domain logo/image
pub async fn idm_domain_update_image(&self, image: ImageValue) -> Result<(), ClientError> { pub async fn idm_domain_update_image(&self, image: ImageValue) -> Result<(), ClientError> {
let file_content_type = image.filetype.as_content_type_str(); let file_content_type = image.filetype.as_content_type_str();

View file

@ -53,6 +53,7 @@ pub enum Attribute {
DisplayName, DisplayName,
Dn, Dn,
Domain, Domain,
DomainAllowEasterEggs,
DomainDevelopmentTaint, DomainDevelopmentTaint,
DomainDisplayName, DomainDisplayName,
DomainLdapBasedn, DomainLdapBasedn,
@ -282,6 +283,7 @@ impl Attribute {
Attribute::DisplayName => ATTR_DISPLAYNAME, Attribute::DisplayName => ATTR_DISPLAYNAME,
Attribute::Dn => ATTR_DN, Attribute::Dn => ATTR_DN,
Attribute::Domain => ATTR_DOMAIN, Attribute::Domain => ATTR_DOMAIN,
Attribute::DomainAllowEasterEggs => ATTR_DOMAIN_ALLOW_EASTER_EGGS,
Attribute::DomainDevelopmentTaint => ATTR_DOMAIN_DEVELOPMENT_TAINT, Attribute::DomainDevelopmentTaint => ATTR_DOMAIN_DEVELOPMENT_TAINT,
Attribute::DomainDisplayName => ATTR_DOMAIN_DISPLAY_NAME, Attribute::DomainDisplayName => ATTR_DOMAIN_DISPLAY_NAME,
Attribute::DomainLdapBasedn => ATTR_DOMAIN_LDAP_BASEDN, Attribute::DomainLdapBasedn => ATTR_DOMAIN_LDAP_BASEDN,
@ -464,6 +466,7 @@ impl Attribute {
ATTR_DISPLAYNAME => Attribute::DisplayName, ATTR_DISPLAYNAME => Attribute::DisplayName,
ATTR_DN => Attribute::Dn, ATTR_DN => Attribute::Dn,
ATTR_DOMAIN => Attribute::Domain, ATTR_DOMAIN => Attribute::Domain,
ATTR_DOMAIN_ALLOW_EASTER_EGGS => Attribute::DomainAllowEasterEggs,
ATTR_DOMAIN_DISPLAY_NAME => Attribute::DomainDisplayName, ATTR_DOMAIN_DISPLAY_NAME => Attribute::DomainDisplayName,
ATTR_DOMAIN_DEVELOPMENT_TAINT => Attribute::DomainDevelopmentTaint, ATTR_DOMAIN_DEVELOPMENT_TAINT => Attribute::DomainDevelopmentTaint,
ATTR_DOMAIN_LDAP_BASEDN => Attribute::DomainLdapBasedn, ATTR_DOMAIN_LDAP_BASEDN => Attribute::DomainLdapBasedn,

View file

@ -89,6 +89,7 @@ pub const ATTR_DESCRIPTION: &str = "description";
pub const ATTR_DIRECTMEMBEROF: &str = "directmemberof"; pub const ATTR_DIRECTMEMBEROF: &str = "directmemberof";
pub const ATTR_DISPLAYNAME: &str = "displayname"; pub const ATTR_DISPLAYNAME: &str = "displayname";
pub const ATTR_DN: &str = "dn"; 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_DEVELOPMENT_TAINT: &str = "domain_development_taint";
pub const ATTR_DOMAIN_DISPLAY_NAME: &str = "domain_display_name"; pub const ATTR_DOMAIN_DISPLAY_NAME: &str = "domain_display_name";
pub const ATTR_DOMAIN_LDAP_BASEDN: &str = "domain_ldap_basedn"; pub const ATTR_DOMAIN_LDAP_BASEDN: &str = "domain_ldap_basedn";

View file

@ -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! { lazy_static! {
pub static ref IDM_ACP_SYNC_ACCOUNT_MANAGE_V1: BuiltinAcp = BuiltinAcp { pub static ref IDM_ACP_SYNC_ACCOUNT_MANAGE_V1: BuiltinAcp = BuiltinAcp {
classes: vec![ classes: vec![

View file

@ -770,6 +770,14 @@ pub static ref SCHEMA_ATTR_DOMAIN_DEVELOPMENT_TAINT_DL7: SchemaAttribute = Schem
..Default::default() ..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 { pub static ref SCHEMA_ATTR_REFERS_DL7: SchemaAttribute = SchemaAttribute {
uuid: UUID_SCHEMA_ATTR_REFERS, uuid: UUID_SCHEMA_ATTR_REFERS,
name: Attribute::Refers, name: Attribute::Refers,
@ -1177,6 +1185,30 @@ pub static ref SCHEMA_CLASS_DOMAIN_INFO_DL8: SchemaClass = SchemaClass {
..Default::default() ..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 { pub static ref SCHEMA_CLASS_POSIXGROUP: SchemaClass = SchemaClass {
uuid: UUID_SCHEMA_CLASS_POSIXGROUP, uuid: UUID_SCHEMA_CLASS_POSIXGROUP,
name: EntryClass::PosixGroup.into(), name: EntryClass::PosixGroup.into(),

View file

@ -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_CREATED_AT_CID: Uuid = uuid!("00000000-0000-0000-0000-ffff00000184");
pub const UUID_SCHEMA_ATTR_ALLOW_PRIMARY_CRED_FALLBACK: Uuid = pub const UUID_SCHEMA_ATTR_ALLOW_PRIMARY_CRED_FALLBACK: Uuid =
uuid!("00000000-0000-0000-0000-ffff00000185"); 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 // System and domain infos
// I'd like to strongly criticise william of the past for making poor choices about these allocations. // I'd like to strongly criticise william of the past for making poor choices about these allocations.

View file

@ -647,7 +647,9 @@ impl QueryServerWriteTransaction<'_> {
// Now update schema // Now update schema
let idm_schema_changes = [ let idm_schema_changes = [
SCHEMA_ATTR_OAUTH2_DEVICE_FLOW_ENABLE_DL9.clone().into(), 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_OAUTH2_RS_DL9.clone().into(),
SCHEMA_CLASS_DOMAIN_INFO_DL9.clone().into(),
]; ];
idm_schema_changes idm_schema_changes
@ -663,6 +665,7 @@ impl QueryServerWriteTransaction<'_> {
let idm_data = [ let idm_data = [
IDM_ACP_OAUTH2_MANAGE_DL9.clone().into(), IDM_ACP_OAUTH2_MANAGE_DL9.clone().into(),
IDM_ACP_GROUP_MANAGE_DL9.clone().into(), IDM_ACP_GROUP_MANAGE_DL9.clone().into(),
IDM_ACP_DOMAIN_ADMIN_DL9.clone().into(),
]; ];
idm_data idm_data

View file

@ -79,6 +79,7 @@ pub struct DomainInfo {
pub(crate) d_patch_level: u32, pub(crate) d_patch_level: u32,
pub(crate) d_devel_taint: bool, pub(crate) d_devel_taint: bool,
pub(crate) d_ldap_allow_unix_pw_bind: 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. // In future this should be image reference instead of the image itself.
d_image: Option<ImageValue>, d_image: Option<ImageValue>,
} }
@ -103,6 +104,10 @@ impl DomainInfo {
pub fn has_custom_image(&self) -> bool { pub fn has_custom_image(&self) -> bool {
self.d_image.is_some() self.d_image.is_some()
} }
pub fn allow_easter_eggs(&self) -> bool {
self.d_allow_easter_eggs
}
} }
#[derive(Debug, Clone, PartialEq, Eq, Default)] #[derive(Debug, Clone, PartialEq, Eq, Default)]
@ -1657,6 +1662,7 @@ impl QueryServer {
// Automatically derive our current taint mode based on the PRERELEASE setting. // Automatically derive our current taint mode based on the PRERELEASE setting.
d_devel_taint: option_env!("KANIDM_PRE_RELEASE").is_some(), d_devel_taint: option_env!("KANIDM_PRE_RELEASE").is_some(),
d_ldap_allow_unix_pw_bind: false, d_ldap_allow_unix_pw_bind: false,
d_allow_easter_eggs: false,
d_image: None, d_image: None,
})); }));
@ -2284,6 +2290,11 @@ impl<'a> QueryServerWriteTransaction<'a> {
.get_ava_single_bool(Attribute::DomainDevelopmentTaint) .get_ava_single_bool(Attribute::DomainDevelopmentTaint)
.unwrap_or_default(); .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 // 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 // will now see it's been increased. This also prevents recursion during reloads
// inside of a domain migration. // 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_vers = domain_info_version;
mut_d_info.d_patch_level = domain_info_patch_level; mut_d_info.d_patch_level = domain_info_patch_level;
mut_d_info.d_devel_taint = domain_info_devel_taint; 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 // 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. // not, then we only proceed to migrate *if* our server boot phase is correct.

View file

@ -12,6 +12,7 @@ impl DomainOpt {
| DomainOpt::SetImage { copt, .. } | DomainOpt::SetImage { copt, .. }
| DomainOpt::RemoveImage { copt } | DomainOpt::RemoveImage { copt }
| DomainOpt::SetLdapAllowUnixPasswordBind { copt, .. } | DomainOpt::SetLdapAllowUnixPasswordBind { copt, .. }
| DomainOpt::SetAllowEasterEggs { copt, .. }
| DomainOpt::RevokeKey { copt, .. } | DomainOpt::RevokeKey { copt, .. }
| DomainOpt::Show(copt) => copt.debug, | DomainOpt::Show(copt) => copt.debug,
} }
@ -51,6 +52,19 @@ impl DomainOpt {
Err(e) => handle_client_error(e, copt.output_mode), 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) => { DomainOpt::Show(copt) => {
let client = copt.to_client(OpType::Read).await; let client = copt.to_client(OpType::Read).await;
match client.idm_domain_get().await { match client.idm_domain_get().await {

View file

@ -1332,6 +1332,15 @@ pub enum DomainOpt {
#[clap(name = "allow", action = clap::ArgAction::Set)] #[clap(name = "allow", action = clap::ArgAction::Set)]
enable: bool, 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")] #[clap(name = "show")]
/// Show information about this system's domain /// Show information about this system's domain
Show(CommonOpt), Show(CommonOpt),