diff --git a/Cargo.lock b/Cargo.lock index 683054330..1fa20eee8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -166,7 +166,7 @@ version = "1.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e14485364214912d3b19cc3435dde4df66065127f05fa0d75c712f36f12c2f28" dependencies = [ - "concurrent-queue", + "concurrent-queue 1.2.4", "event-listener", "futures-core", ] @@ -196,15 +196,15 @@ dependencies = [ [[package]] name = "async-executor" -version = "1.4.1" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "871f9bb5e0a22eeb7e8cf16641feb87c9dc67032ccf8ff49e772eb9941d3a965" +checksum = "17adb73da160dfb475c183343c8cccd80721ea5a605d3eb57125f0a7b7a92d0b" dependencies = [ + "async-lock", "async-task", - "concurrent-queue", + "concurrent-queue 2.0.0", "fastrand", "futures-lite", - "once_cell", "slab", ] @@ -248,7 +248,7 @@ checksum = "e8121296a9f05be7f34aa4196b1747243b3b62e048bb7906f644f3fbfc490cf7" dependencies = [ "async-lock", "autocfg", - "concurrent-queue", + "concurrent-queue 1.2.4", "futures-lite", "libc", "log", @@ -626,9 +626,9 @@ checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" [[package]] name = "cc" -version = "1.0.74" +version = "1.0.76" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "581f5dba903aac52ea3feb5ec4810848460ee833876f1f9b0fdeab1f19091574" +checksum = "76a284da2e6fe2092f2353e51713435363112dfd60030e22add80be333fb928f" dependencies = [ "jobserver", ] @@ -809,6 +809,15 @@ dependencies = [ "cache-padded", ] +[[package]] +name = "concurrent-queue" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd7bef69dc86e3c610e4e7aed41035e2a7ed12e72dd7530f61327a6579a4390b" +dependencies = [ + "crossbeam-utils", +] + [[package]] name = "console" version = "0.15.2" @@ -1105,9 +1114,9 @@ dependencies = [ [[package]] name = "cxx" -version = "1.0.80" +version = "1.0.81" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b7d4e43b25d3c994662706a1d4fcfc32aaa6afd287502c111b237093bb23f3a" +checksum = "97abf9f0eca9e52b7f81b945524e76710e6cb2366aead23b7d4fbf72e281f888" dependencies = [ "cc", "cxxbridge-flags", @@ -1117,9 +1126,9 @@ dependencies = [ [[package]] name = "cxx-build" -version = "1.0.80" +version = "1.0.81" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84f8829ddc213e2c1368e51a2564c552b65a8cb6a28f31e576270ac81d5e5827" +checksum = "7cc32cc5fea1d894b77d269ddb9f192110069a8a9c1f1d441195fba90553dea3" dependencies = [ "cc", "codespan-reporting", @@ -1132,15 +1141,15 @@ dependencies = [ [[package]] name = "cxxbridge-flags" -version = "1.0.80" +version = "1.0.81" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e72537424b474af1460806647c41d4b6d35d09ef7fe031c5c2fa5766047cc56a" +checksum = "8ca220e4794c934dc6b1207c3b42856ad4c302f2df1712e9f8d2eec5afaacf1f" [[package]] name = "cxxbridge-macro" -version = "1.0.80" +version = "1.0.81" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "309e4fb93eed90e1e14bea0da16b209f81813ba9fc7830c20ed151dd7bc0a4d7" +checksum = "b846f081361125bfc8dc9d3940c84e1fd83ba54bbca7b17cd29483c828be0704" dependencies = [ "proc-macro2", "quote", @@ -2036,9 +2045,9 @@ checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" [[package]] name = "hyper" -version = "0.14.22" +version = "0.14.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "abfba89e19b959ca163c7752ba59d737c1ceea53a5d31a149c805446fc958064" +checksum = "034711faac9d2166cb1baf1a2fb0b60b1f277f8492fd72176c17f3515e1abd3c" dependencies = [ "bytes", "futures-channel", @@ -2060,9 +2069,9 @@ dependencies = [ [[package]] name = "hyper-rustls" -version = "0.23.0" +version = "0.23.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d87c48c02e0dc5e3b849a2041db3029fd066650f8f717c07bf8ed78ccb895cac" +checksum = "59df7c4e19c950e6e0e868dcc0a300b09a9b88e9ec55bd879ca819087a77355d" dependencies = [ "http", "hyper", @@ -2290,6 +2299,7 @@ dependencies = [ "base32", "base64urlsafedata", "last-git-commit", + "num_enum", "scim_proto", "serde", "serde_json", @@ -2543,7 +2553,7 @@ dependencies = [ [[package]] name = "ldap3_client" version = "0.3.0" -source = "git+https://github.com/kanidm/ldap3.git#6b0d146d3f85a32add3bdc3639ba4146822eb861" +source = "git+https://github.com/kanidm/ldap3.git#1adbd017d02a60e09967572706cb22b1394d927b" dependencies = [ "base64 0.13.1", "base64urlsafedata", @@ -2562,7 +2572,7 @@ dependencies = [ [[package]] name = "ldap3_proto" version = "0.3.0" -source = "git+https://github.com/kanidm/ldap3.git#6b0d146d3f85a32add3bdc3639ba4146822eb861" +source = "git+https://github.com/kanidm/ldap3.git#1adbd017d02a60e09967572706cb22b1394d927b" dependencies = [ "bytes", "lber", @@ -2959,9 +2969,9 @@ dependencies = [ [[package]] name = "oauth2" -version = "4.2.3" +version = "4.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d62c436394991641b970a92e23e8eeb4eb9bca74af4f5badc53bcd568daadbd" +checksum = "eeaf26a72311c087f8c5ba617c96fac67a5c04f430e716ac8d8ab2de62e23368" dependencies = [ "base64 0.13.1", "chrono", @@ -3091,9 +3101,9 @@ dependencies = [ [[package]] name = "os_str_bytes" -version = "6.3.1" +version = "6.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3baf96e39c5359d2eb0dd6ccb42c62b91d9678aa68160d261b9e0ccbf9e9dea9" +checksum = "7b5bf27447411e9ee3ff51186bf7a08e16c341efdde93f4d823e8844429bed7e" [[package]] name = "overload" @@ -4799,9 +4809,9 @@ dependencies = [ [[package]] name = "uuid" -version = "1.2.1" +version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "feb41e78f93363bb2df8b0e86a2ca30eed7806ea16ea0c790d757cf93f79be83" +checksum = "422ee0de9031b5b948b97a8fc04e3aa35230001a722ddd27943e0be31564ce4c" dependencies = [ "getrandom 0.2.8", "serde", @@ -5031,9 +5041,9 @@ dependencies = [ [[package]] name = "webauthn-authenticator-rs" -version = "0.4.7" +version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d30dcdffd0c5dfa110246701399efcc09962c1bb565f61a5d7fe995645ff6f21" +checksum = "dbc33594bd26aabc90ce6d081d98617f5d16803660187f88912dead3ac2a9784" dependencies = [ "authenticator-ctap2-2021", "base64urlsafedata", @@ -5045,14 +5055,15 @@ dependencies = [ "serde_json", "tracing", "url", + "uuid", "webauthn-rs-proto", ] [[package]] name = "webauthn-rs" -version = "0.4.7" +version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d5984278a28dc397c565fd79ee1aba67b74fa365c4eea489f7258b3f902422f" +checksum = "2db00711c712414e93b019c4596315085792215bc2ac2d5872f9e8913b0a6316" dependencies = [ "base64urlsafedata", "serde", @@ -5064,9 +5075,9 @@ dependencies = [ [[package]] name = "webauthn-rs-core" -version = "0.4.7" +version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6528b4769d8fbe020e0e8c2e66bab6035982a2e9c3f8dac384120718f4763f4" +checksum = "8ef9a989b5cd2c39c52850c4bc36ebc88907a06ceacf7f80e45d78a308944b9d" dependencies = [ "base64 0.13.1", "base64urlsafedata", @@ -5088,9 +5099,9 @@ dependencies = [ [[package]] name = "webauthn-rs-proto" -version = "0.4.7" +version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09e9a265574f8d7b8f8c94c4488bb491a82d7b8e183003a4512d3dceb88efd98" +checksum = "585c7662de492733e6ea11e2726270bf34f5d7f8dbd08cd089830fbb3639a229" dependencies = [ "base64urlsafedata", "js-sys", diff --git a/Cargo.toml b/Cargo.toml index 570027f0a..e994d9bf3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -158,10 +158,10 @@ wasm-bindgen = "^0.2.81" wasm-bindgen-futures = "^0.4.30" wasm-bindgen-test = "0.3.33" -webauthn-authenticator-rs = "0.4.7" -webauthn-rs = "0.4.7" -webauthn-rs-core = "0.4.7" -webauthn-rs-proto = "0.4.7" +webauthn-authenticator-rs = "0.4.8" +webauthn-rs = "0.4.8" +webauthn-rs-core = "0.4.8" +webauthn-rs-proto = "0.4.8" # webauthn-authenticator-rs = { path = "../webauthn-rs/webauthn-authenticator-rs" } # webauthn-rs = { path = "../webauthn-rs/webauthn-rs" } # webauthn-rs-core = { path = "../webauthn-rs/webauthn-rs-core" } diff --git a/kanidm_proto/Cargo.toml b/kanidm_proto/Cargo.toml index d5465a405..b3e3e6874 100644 --- a/kanidm_proto/Cargo.toml +++ b/kanidm_proto/Cargo.toml @@ -18,6 +18,7 @@ wasm = ["webauthn-rs-proto/wasm"] [dependencies] base32.workspace = true base64urlsafedata.workspace = true +num_enum.workspace = true scim_proto.workspace = true serde = { workspace = true, features = ["derive"] } serde_json.workspace = true diff --git a/kanidm_proto/src/v1.rs b/kanidm_proto/src/v1.rs index deec5d3ae..91f291a60 100644 --- a/kanidm_proto/src/v1.rs +++ b/kanidm_proto/src/v1.rs @@ -1,7 +1,9 @@ use std::cmp::Ordering; use std::collections::{BTreeMap, BTreeSet}; use std::fmt; +use std::str::FromStr; +use num_enum::TryFromPrimitive; use serde::{Deserialize, Serialize}; use uuid::Uuid; use webauthn_rs_proto::{ @@ -315,10 +317,34 @@ impl fmt::Display for AuthType { } } -#[derive(Debug, Serialize, Deserialize, Clone, Ord, PartialOrd, Eq, PartialEq)] +#[derive(Debug, Serialize, Deserialize, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash)] #[serde(rename_all = "lowercase")] +#[derive(TryFromPrimitive)] +#[repr(u16)] pub enum UiHint { - PosixAccount, + ExperimentalFeatures = 0, + PosixAccount = 1, +} + +impl fmt::Display for UiHint { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + UiHint::PosixAccount => write!(f, "PosixAccount"), + UiHint::ExperimentalFeatures => write!(f, "ExperimentalFeatures"), + } + } +} + +impl FromStr for UiHint { + type Err = (); + + fn from_str(s: &str) -> Result { + match s { + "PosixAccount" => Ok(UiHint::PosixAccount), + "ExperimentalFeatures" => Ok(UiHint::ExperimentalFeatures), + _ => Err(()), + } + } } #[derive(Debug, Serialize, Deserialize, Clone)] diff --git a/kanidmd/lib/src/be/dbentry.rs b/kanidmd/lib/src/be/dbentry.rs index f29c3440c..955b05621 100644 --- a/kanidmd/lib/src/be/dbentry.rs +++ b/kanidmd/lib/src/be/dbentry.rs @@ -139,7 +139,7 @@ fn from_vec_dbval1(attr_val: Vec) -> Result, _> = viter .map(|dbv| { if let DbValueV1::IndexType(s) = dbv { - Ok(s) + u16::try_from(s).map_err(|_| OperationError::InvalidValueState) } else { Err(OperationError::InvalidValueState) } diff --git a/kanidmd/lib/src/be/dbvalue.rs b/kanidmd/lib/src/be/dbvalue.rs index 6183e8d5d..78b8245ef 100644 --- a/kanidmd/lib/src/be/dbvalue.rs +++ b/kanidmd/lib/src/be/dbvalue.rs @@ -495,7 +495,7 @@ pub enum DbValueSetV2 { #[serde(rename = "SY")] SyntaxType(Vec), #[serde(rename = "IN")] - IndexType(Vec), + IndexType(Vec), #[serde(rename = "RF")] Reference(Vec), #[serde(rename = "JF")] @@ -550,11 +550,13 @@ pub enum DbValueSetV2 { JwsKeyRs256(Vec>), #[serde(rename = "AS")] Oauth2Session(Vec), + #[serde(rename = "UH")] + UiHint(Vec), } impl DbValueSetV2 { pub fn len(&self) -> usize { - match &self { + match self { DbValueSetV2::Utf8(set) => set.len(), DbValueSetV2::Iutf8(set) => set.len(), DbValueSetV2::Iname(set) => set.len(), @@ -589,6 +591,7 @@ impl DbValueSetV2 { DbValueSetV2::Oauth2Session(set) => set.len(), DbValueSetV2::JwsKeyEs256(set) => set.len(), DbValueSetV2::JwsKeyRs256(set) => set.len(), + DbValueSetV2::UiHint(set) => set.len(), } } diff --git a/kanidmd/lib/src/constants/entries.rs b/kanidmd/lib/src/constants/entries.rs index d237b7ff0..b8e6b2a08 100644 --- a/kanidmd/lib/src/constants/entries.rs +++ b/kanidmd/lib/src/constants/entries.rs @@ -3,6 +3,7 @@ use crate::constants::uuids::*; use crate::constants::values::*; use crate::entry::{Entry, EntryInit, EntryInitNew, EntryNew}; use crate::value::Value; +use kanidm_proto::v1::UiHint; #[cfg(test)] use uuid::{uuid, Uuid}; @@ -477,6 +478,28 @@ pub const JSON_IDM_ALL_ACCOUNTS: &str = r#"{ } }"#; +lazy_static! { + pub static ref E_IDM_UI_ENABLE_EXPERIMENTAL_FEATURES: EntryInitNew = entry_init!( + ("class", CLASS_OBJECT.clone()), + ("class", CLASS_GROUP.clone()), + ( + "name", + Value::new_iname("idm_ui_enable_experimental_features") + ), + ( + "uuid", + Value::new_uuid(UUID_IDM_UI_ENABLE_EXPERIMENTAL_FEATURES) + ), + ( + "description", + Value::new_utf8s( + "Members of this group will have access to experimental web UI features." + ) + ), + ("grant_ui_hint", Value::UiHint(UiHint::ExperimentalFeatures)) + ); +} + /// This must be the last group to init to include the UUID of the other high priv groups. pub const JSON_IDM_HIGH_PRIVILEGE_V1: &str = r#"{ "attrs": { diff --git a/kanidmd/lib/src/constants/schema.rs b/kanidmd/lib/src/constants/schema.rs index acb1871d2..c50b2f4b7 100644 --- a/kanidmd/lib/src/constants/schema.rs +++ b/kanidmd/lib/src/constants/schema.rs @@ -1262,6 +1262,37 @@ pub const JSON_SCHEMA_ATTR_SYNC_COOKIE: &str = r#"{ } }"#; +pub const JSON_SCHEMA_ATTR_GRANT_UI_HINT: &str = r#"{ + "attrs": { + "class": [ + "object", + "system", + "attributetype" + ], + "description": [ + "A ui hint that is granted via membership to a group" + ], + "index": [ + "EQUALITY" + ], + "unique": [ + "false" + ], + "multivalue": [ + "true" + ], + "attributename": [ + "grant_ui_hint" + ], + "syntax": [ + "UIHINT" + ], + "uuid": [ + "00000000-0000-0000-0000-ffff00000119" + ] + } +}"#; + // === classes === pub const JSON_SCHEMA_CLASS_PERSON: &str = r#" @@ -1337,7 +1368,8 @@ pub const JSON_SCHEMA_CLASS_GROUP: &str = r#" "group" ], "systemmay": [ - "member" + "member", + "grant_ui_hint" ], "systemmust": [ "name", diff --git a/kanidmd/lib/src/constants/uuids.rs b/kanidmd/lib/src/constants/uuids.rs index 4a7663b88..f2829deda 100644 --- a/kanidmd/lib/src/constants/uuids.rs +++ b/kanidmd/lib/src/constants/uuids.rs @@ -55,6 +55,9 @@ pub const UUID_IDM_ALL_ACCOUNTS: Uuid = uuid!("00000000-0000-0000-0000-000000000 pub const _UUID_IDM_HP_SYNC_ACCOUNT_MANAGE_PRIV: Uuid = uuid!("00000000-0000-0000-0000-000000000037"); +pub const UUID_IDM_UI_ENABLE_EXPERIMENTAL_FEATURES: Uuid = + uuid!("00000000-0000-0000-0000-000000000038"); + // pub const _UUID_IDM_HIGH_PRIVILEGE: Uuid = uuid!("00000000-0000-0000-0000-000000001000"); @@ -206,6 +209,7 @@ pub const _UUID_SCHEMA_ATTR_SYNC_TOKEN_SESSION: Uuid = pub const _UUID_SCHEMA_ATTR_SYNC_COOKIE: Uuid = uuid!("00000000-0000-0000-0000-ffff00000116"); pub const _UUID_SCHEMA_ATTR_OAUTH2_SESSION: Uuid = uuid!("00000000-0000-0000-0000-ffff00000117"); pub const UUID_SCHEMA_ATTR_ACP_RECEIVER_GROUP: Uuid = uuid!("00000000-0000-0000-0000-ffff00000118"); +pub const _UUID_SCHEMA_ATTR_GRANT_UI_HINT: Uuid = uuid!("00000000-0000-0000-0000-ffff00000119"); // System and domain infos // I'd like to strongly criticise william of the past for making poor choices about these allocations. diff --git a/kanidmd/lib/src/constants/values.rs b/kanidmd/lib/src/constants/values.rs index 4e2a73d5b..86a758fcc 100644 --- a/kanidmd/lib/src/constants/values.rs +++ b/kanidmd/lib/src/constants/values.rs @@ -39,6 +39,7 @@ lazy_static! { pub static ref CLASS_ACCOUNT: Value = Value::new_class("account"); pub static ref CLASS_DOMAIN_INFO: Value = Value::new_class("domain_info"); pub static ref CLASS_DYNGROUP: Value = Value::new_class("dyngroup"); + pub static ref CLASS_GROUP: Value = Value::new_class("group"); pub static ref CLASS_MEMBEROF: Value = Value::new_class("memberof"); pub static ref CLASS_OBJECT: Value = Value::new_class("object"); pub static ref CLASS_RECYCLED: Value = Value::new_class("recycled"); diff --git a/kanidmd/lib/src/entry.rs b/kanidmd/lib/src/entry.rs index 4373182d3..b4c6a60a3 100644 --- a/kanidmd/lib/src/entry.rs +++ b/kanidmd/lib/src/entry.rs @@ -33,6 +33,7 @@ use compact_jwt::JwsSigner; use hashbrown::HashMap; use kanidm_proto::v1::{ ConsistencyError, Entry as ProtoEntry, Filter as ProtoFilter, OperationError, SchemaError, + UiHint, }; use ldap3_proto::simple::{LdapPartialAttribute, LdapSearchResultEntry}; use smartstring::alias::String as AttrString; @@ -1973,6 +1974,12 @@ impl Entry { self.attrs.get(attr).and_then(|vs| vs.as_devicekey_map()) } + #[inline(always)] + /// Get the set of passkeys on this account, if any are present. + pub fn get_ava_uihint(&self, attr: &str) -> Option<&BTreeSet> { + self.attrs.get(attr).and_then(|vs| vs.as_uihint_set()) + } + #[inline(always)] /// Return a single secret value, if valid to transform this value. pub fn get_ava_single_secret(&self, attr: &str) -> Option<&str> { diff --git a/kanidmd/lib/src/idm/account.rs b/kanidmd/lib/src/idm/account.rs index 94a7f5604..58ba035b7 100644 --- a/kanidmd/lib/src/idm/account.rs +++ b/kanidmd/lib/src/idm/account.rs @@ -91,7 +91,13 @@ macro_rules! try_from_entry { .cloned() .unwrap_or_default(); - let mut ui_hints = BTreeSet::default(); + // Provide hints from groups. + let mut ui_hints: BTreeSet<_> = groups + .iter() + .map(|group: &Group| group.ui_hints.iter()) + .flatten() + .copied() + .collect(); if $value.attribute_equality("class", &PVCLASS_POSIXACCOUNT) { ui_hints.insert(UiHint::PosixAccount); @@ -670,9 +676,10 @@ impl<'a> IdmServerProxyReadTransaction<'a> { #[cfg(test)] mod tests { - use crate::constants::JSON_ANONYMOUS_V1; - // use crate::entry::{Entry, EntryNew, EntrySealed}; - // use crate::idm::account::Account; + use crate::event::{CreateEvent, ModifyEvent}; + use crate::prelude::*; + use async_std::task; + use kanidm_proto::v1::{AuthType, UiHint}; #[test] fn test_idm_account_from_anonymous() { @@ -682,14 +689,94 @@ mod tests { } #[test] - fn test_idm_account_from_real() { - // For now, nothing, but later, we'll test different types of cred - // passing. - } + fn test_idm_account_ui_hints() { + run_idm_test!(|_qs: &QueryServer, + idms: &IdmServer, + _idms_delayed: &mut IdmServerDelayed| { + let ct = duration_from_epoch_now(); + let mut idms_prox_write = task::block_on(idms.proxy_write(ct.clone())); - #[test] - fn test_idm_account_set_credential() { - // Using a real entry, set a credential back to it's entry. - // In the end, this boils down to a modify operation on the Value + let target_uuid = Uuid::new_v4(); + + // Create a user. So far no ui hints. + // Create a service account + let e = entry_init!( + ("class", Value::new_class("object")), + ("class", Value::new_class("account")), + ("class", Value::new_class("person")), + ("name", Value::new_iname("testaccount")), + ("uuid", Value::new_uuid(target_uuid)), + ("description", Value::new_utf8s("testaccount")), + ("displayname", Value::new_utf8s("Test Account")) + ); + + let ce = CreateEvent::new_internal(vec![e]); + assert!(idms_prox_write.qs_write.create(&ce).is_ok()); + + let account = idms_prox_write + .target_to_account(&target_uuid) + .expect("account must exist"); + let session_id = uuid::Uuid::new_v4(); + let uat = account + .to_userauthtoken(session_id, ct, AuthType::Passkey, None) + .expect("Unable to create uat"); + + // Check the ui hints are as expected. + assert!(uat.ui_hints.is_empty()); + + // Modify the user to be a posix account, ensure they get the hint. + let me_posix = unsafe { + ModifyEvent::new_internal_invalid( + filter!(f_eq("name", PartialValue::new_iname("testaccount"))), + ModifyList::new_list(vec![ + Modify::Present( + AttrString::from("class"), + Value::new_class("posixaccount"), + ), + Modify::Present(AttrString::from("gidnumber"), Value::new_uint32(2001)), + ]), + ) + }; + assert!(idms_prox_write.qs_write.modify(&me_posix).is_ok()); + + // Check the ui hints are as expected. + let account = idms_prox_write + .target_to_account(&target_uuid) + .expect("account must exist"); + let session_id = uuid::Uuid::new_v4(); + let uat = account + .to_userauthtoken(session_id, ct, AuthType::Passkey, None) + .expect("Unable to create uat"); + + assert!(uat.ui_hints.len() == 1); + assert!(uat.ui_hints.contains(&UiHint::PosixAccount)); + + // Add a group with a ui hint, and then check they get the hint. + let e = entry_init!( + ("class", Value::new_class("object")), + ("class", Value::new_class("group")), + ("name", Value::new_iname("test_uihint_group")), + ("member", Value::Refer(target_uuid)), + ("grant_ui_hint", Value::UiHint(UiHint::ExperimentalFeatures)) + ); + + let ce = CreateEvent::new_internal(vec![e]); + assert!(idms_prox_write.qs_write.create(&ce).is_ok()); + + // Check the ui hints are as expected. + let account = idms_prox_write + .target_to_account(&target_uuid) + .expect("account must exist"); + let session_id = uuid::Uuid::new_v4(); + let uat = account + .to_userauthtoken(session_id, ct, AuthType::Passkey, None) + .expect("Unable to create uat"); + + assert!(uat.ui_hints.len() == 2); + assert!(uat.ui_hints.contains(&UiHint::PosixAccount)); + assert!(uat.ui_hints.contains(&UiHint::ExperimentalFeatures)); + + assert!(idms_prox_write.commit().is_ok()); + }) } } diff --git a/kanidmd/lib/src/idm/group.rs b/kanidmd/lib/src/idm/group.rs index 40f23cb9c..b7232d1ba 100644 --- a/kanidmd/lib/src/idm/group.rs +++ b/kanidmd/lib/src/idm/group.rs @@ -1,3 +1,6 @@ +use std::collections::BTreeSet; + +use kanidm_proto::v1::UiHint; use kanidm_proto::v1::{Group as ProtoGroup, OperationError}; use uuid::Uuid; @@ -10,6 +13,7 @@ pub struct Group { spn: String, uuid: Uuid, // We'll probably add policy and claims later to this + pub ui_hints: BTreeSet, } macro_rules! try_from_account_e { @@ -23,13 +27,21 @@ macro_rules! try_from_account_e { })?; */ + // Setup the user private group let spn = $value.get_ava_single_proto_string("spn").ok_or( OperationError::InvalidAccountState("Missing attribute: spn".to_string()), )?; let uuid = $value.get_uuid(); - let upg = Group { spn, uuid }; + // We could allow ui hints on the user direct in the future? + let ui_hints = BTreeSet::default(); + + let upg = Group { + spn, + uuid, + ui_hints, + }; let mut groups: Vec = match $value.get_ava_as_refuuid("memberof") { Some(riter) => { @@ -111,7 +123,16 @@ impl Group { let uuid = value.get_uuid(); - Ok(Group { spn, uuid }) + let ui_hints = value + .get_ava_uihint("grant_ui_hint") + .cloned() + .unwrap_or_default(); + + Ok(Group { + spn, + uuid, + ui_hints, + }) } pub fn to_proto(&self) -> ProtoGroup { diff --git a/kanidmd/lib/src/schema.rs b/kanidmd/lib/src/schema.rs index f10d189c0..565cd2446 100644 --- a/kanidmd/lib/src/schema.rs +++ b/kanidmd/lib/src/schema.rs @@ -197,6 +197,7 @@ impl SchemaAttribute { // These are just insensitive string lookups on the hex-ified kid. SyntaxType::JwsKeyEs256 => matches!(v, PartialValue::Iutf8(_)), SyntaxType::JwsKeyRs256 => matches!(v, PartialValue::Iutf8(_)), + SyntaxType::UiHint => matches!(v, PartialValue::UiHint(_)), }; if r { Ok(()) @@ -243,6 +244,7 @@ impl SchemaAttribute { SyntaxType::Oauth2Session => matches!(v, Value::Oauth2Session(_, _)), SyntaxType::JwsKeyEs256 => matches!(v, Value::JwsKeyEs256(_)), SyntaxType::JwsKeyRs256 => matches!(v, Value::JwsKeyRs256(_)), + SyntaxType::UiHint => matches!(v, Value::UiHint(_)), }; if r { Ok(()) diff --git a/kanidmd/lib/src/server.rs b/kanidmd/lib/src/server.rs index 32d5ad7d8..d4ff1931d 100644 --- a/kanidmd/lib/src/server.rs +++ b/kanidmd/lib/src/server.rs @@ -4,13 +4,14 @@ // This is really only used for long lived, high level types that need clone // that otherwise can't be cloned. Think Mutex. use std::cell::Cell; +use std::str::FromStr; use std::sync::Arc; use std::time::Duration; use concread::arcache::{ARCache, ARCacheBuilder, ARCacheReadTxn}; use concread::cowcell::*; use hashbrown::{HashMap, HashSet}; -use kanidm_proto::v1::{ConsistencyError, SchemaError}; +use kanidm_proto::v1::{ConsistencyError, SchemaError, UiHint}; use tokio::sync::{Semaphore, SemaphorePermit}; use tracing::trace; @@ -517,6 +518,9 @@ pub trait QueryServerTransaction<'a> { SyntaxType::JwsKeyEs256 => Err(OperationError::InvalidAttribute("JwsKeyEs256 Values can not be supplied through modification".to_string())), SyntaxType::JwsKeyRs256 => Err(OperationError::InvalidAttribute("JwsKeyRs256 Values can not be supplied through modification".to_string())), SyntaxType::Oauth2Session => Err(OperationError::InvalidAttribute("Oauth2Session Values can not be supplied through modification".to_string())), + SyntaxType::UiHint => UiHint::from_str(value) + .map(Value::UiHint) + .map_err(|()| OperationError::InvalidAttribute("Invalid uihint syntax".to_string())), } } None => { @@ -645,6 +649,11 @@ pub trait QueryServerTransaction<'a> { ) }) } + SyntaxType::UiHint => UiHint::from_str(value) + .map(PartialValue::UiHint) + .map_err(|()| { + OperationError::InvalidAttribute("Invalid uihint syntax".to_string()) + }), } } None => { @@ -2682,6 +2691,7 @@ impl<'a> QueryServerWriteTransaction<'a> { JSON_SCHEMA_ATTR_OAUTH2_PREFER_SHORT_USERNAME, JSON_SCHEMA_ATTR_SYNC_TOKEN_SESSION, JSON_SCHEMA_ATTR_SYNC_COOKIE, + JSON_SCHEMA_ATTR_GRANT_UI_HINT, JSON_SCHEMA_CLASS_PERSON, JSON_SCHEMA_CLASS_ORGPERSON, JSON_SCHEMA_CLASS_GROUP, @@ -2848,13 +2858,26 @@ impl<'a> QueryServerWriteTransaction<'a> { debug_assert!(res.is_ok()); res?; + let idm_entries = [E_IDM_UI_ENABLE_EXPERIMENTAL_FEATURES.clone()]; + + let res: Result<(), _> = idm_entries + .into_iter() + .try_for_each(|entry| self.internal_migrate_or_create(entry)); + if res.is_ok() { + admin_debug!("initialise_idm -> result Ok!"); + } else { + admin_error!(?res, "initialise_idm p3 -> result"); + } + debug_assert!(res.is_ok()); + res?; + self.changed_schema.set(true); self.changed_acp.set(true); Ok(()) } - #[instrument(level = "info", name = "reload_schema", skip(self))] + #[instrument(level = "debug", name = "reload_schema", skip(self))] fn reload_schema(&mut self) -> Result<(), OperationError> { // supply entries to the writable schema to reload from. // find all attributes. diff --git a/kanidmd/lib/src/value.rs b/kanidmd/lib/src/value.rs index 888399c5c..9e738532d 100644 --- a/kanidmd/lib/src/value.rs +++ b/kanidmd/lib/src/value.rs @@ -12,6 +12,8 @@ use std::time::Duration; use compact_jwt::JwsSigner; use hashbrown::HashSet; use kanidm_proto::v1::Filter as ProtoFilter; +use kanidm_proto::v1::UiHint; +use num_enum::TryFromPrimitive; use regex::Regex; use serde::{Deserialize, Serialize}; use sshkeys::PublicKey as SshPublicKey; @@ -72,14 +74,6 @@ pub struct Address { pub country: String, } -#[allow(non_camel_case_types)] -#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Deserialize, Serialize, Hash)] -pub enum IndexType { - Equality, - Presence, - SubString, -} - #[derive(Debug, Clone, PartialEq, Eq)] pub enum IntentTokenState { Valid { @@ -95,6 +89,27 @@ pub enum IntentTokenState { }, } +#[allow(non_camel_case_types)] +#[derive( + Debug, + Clone, + Copy, + PartialEq, + Eq, + PartialOrd, + Ord, + Deserialize, + Serialize, + Hash, + TryFromPrimitive, +)] +#[repr(u16)] +pub enum IndexType { + Equality, + Presence, + SubString, +} + impl TryFrom<&str> for IndexType { type Error = (); @@ -111,19 +126,6 @@ impl TryFrom<&str> for IndexType { } } -impl TryFrom for IndexType { - type Error = (); - - fn try_from(value: usize) -> Result { - match value { - 0 => Ok(IndexType::Equality), - 1 => Ok(IndexType::Presence), - 2 => Ok(IndexType::SubString), - _ => Err(()), - } - } -} - impl IndexType { pub fn as_idx_str(&self) -> &str { match self { @@ -132,14 +134,6 @@ impl IndexType { IndexType::SubString => "sub", } } - - pub fn to_usize(&self) -> usize { - match self { - IndexType::Equality => 0, - IndexType::Presence => 1, - IndexType::SubString => 2, - } - } } impl fmt::Display for IndexType { @@ -157,7 +151,19 @@ impl fmt::Display for IndexType { } #[allow(non_camel_case_types)] -#[derive(Hash, Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Deserialize, Serialize)] +#[derive( + Hash, + Debug, + Clone, + Copy, + PartialEq, + Eq, + PartialOrd, + Ord, + Deserialize, + Serialize, + TryFromPrimitive, +)] #[repr(u16)] pub enum SyntaxType { Utf8String = 0, @@ -189,6 +195,7 @@ pub enum SyntaxType { JwsKeyEs256 = 26, JwsKeyRs256 = 27, Oauth2Session = 28, + UiHint = 29, } impl TryFrom<&str> for SyntaxType { @@ -227,45 +234,7 @@ impl TryFrom<&str> for SyntaxType { "JWS_KEY_ES256" => Ok(SyntaxType::JwsKeyEs256), "JWS_KEY_RS256" => Ok(SyntaxType::JwsKeyRs256), "OAUTH2SESSION" => Ok(SyntaxType::Oauth2Session), - _ => Err(()), - } - } -} - -impl TryFrom for SyntaxType { - type Error = (); - - fn try_from(value: u16) -> Result { - match value { - 0 => Ok(SyntaxType::Utf8String), - 1 => Ok(SyntaxType::Utf8StringInsensitive), - 2 => Ok(SyntaxType::Uuid), - 3 => Ok(SyntaxType::Boolean), - 4 => Ok(SyntaxType::SyntaxId), - 5 => Ok(SyntaxType::IndexId), - 6 => Ok(SyntaxType::ReferenceUuid), - 7 => Ok(SyntaxType::JsonFilter), - 8 => Ok(SyntaxType::Credential), - 9 => Ok(SyntaxType::SecretUtf8String), - 10 => Ok(SyntaxType::SshKey), - 11 => Ok(SyntaxType::SecurityPrincipalName), - 12 => Ok(SyntaxType::Uint32), - 13 => Ok(SyntaxType::Cid), - 14 => Ok(SyntaxType::Utf8StringIname), - 15 => Ok(SyntaxType::NsUniqueId), - 16 => Ok(SyntaxType::DateTime), - 17 => Ok(SyntaxType::EmailAddress), - 18 => Ok(SyntaxType::Url), - 19 => Ok(SyntaxType::OauthScope), - 20 => Ok(SyntaxType::OauthScopeMap), - 21 => Ok(SyntaxType::PrivateBinary), - 22 => Ok(SyntaxType::IntentToken), - 23 => Ok(SyntaxType::Passkey), - 24 => Ok(SyntaxType::DeviceKey), - 25 => Ok(SyntaxType::Session), - 26 => Ok(SyntaxType::JwsKeyEs256), - 27 => Ok(SyntaxType::JwsKeyRs256), - 28 => Ok(SyntaxType::Oauth2Session), + "UIHINT" => Ok(SyntaxType::UiHint), _ => Err(()), } } @@ -303,6 +272,7 @@ impl fmt::Display for SyntaxType { SyntaxType::JwsKeyEs256 => "JWS_KEY_ES256", SyntaxType::JwsKeyRs256 => "JWS_KEY_RS256", SyntaxType::Oauth2Session => "OAUTH2SESSION", + SyntaxType::UiHint => "UIHINT", }) } } @@ -348,6 +318,7 @@ pub enum PartialValue { // Float64(f64), RestrictedString(String), IntentToken(String), + UiHint(UiHint), Passkey(Uuid), DeviceKey(Uuid), @@ -697,7 +668,7 @@ impl PartialValue { } pub fn get_idx_eq_key(&self) -> String { - match &self { + match self { PartialValue::Utf8(s) | PartialValue::Iutf8(s) | PartialValue::Iname(s) @@ -736,6 +707,7 @@ impl PartialValue { PartialValue::IntentToken(u) => u.clone(), PartialValue::TrustedDeviceEnrollment(u) => u.as_hyphenated().to_string(), PartialValue::Session(u) => u.as_hyphenated().to_string(), + PartialValue::UiHint(u) => (*u as u16).to_string(), } } @@ -810,6 +782,7 @@ pub enum Value { JwsKeyEs256(JwsSigner), JwsKeyRs256(JwsSigner), + UiHint(UiHint), } impl PartialEq for Value { diff --git a/kanidmd/lib/src/valueset/index.rs b/kanidmd/lib/src/valueset/index.rs index 7aebcb66a..25b57e911 100644 --- a/kanidmd/lib/src/valueset/index.rs +++ b/kanidmd/lib/src/valueset/index.rs @@ -20,9 +20,9 @@ impl ValueSetIndex { self.set.insert(s) } - pub fn from_dbvs2(data: Vec) -> Result { + pub fn from_dbvs2(data: Vec) -> Result { let set: Result<_, _> = data.into_iter().map(IndexType::try_from).collect(); - let set = set.map_err(|()| OperationError::InvalidValueState)?; + let set = set.map_err(|_| OperationError::InvalidValueState)?; Ok(Box::new(ValueSetIndex { set })) } @@ -98,7 +98,7 @@ impl ValueSetT for ValueSetIndex { } fn to_db_valueset_v2(&self) -> DbValueSetV2 { - DbValueSetV2::IndexType(self.set.iter().map(|s| s.to_usize()).collect()) + DbValueSetV2::IndexType(self.set.iter().map(|s| *s as u16).collect()) } fn to_partialvalue_iter(&self) -> Box + '_> { diff --git a/kanidmd/lib/src/valueset/mod.rs b/kanidmd/lib/src/valueset/mod.rs index d97f17b14..b1472eb30 100644 --- a/kanidmd/lib/src/valueset/mod.rs +++ b/kanidmd/lib/src/valueset/mod.rs @@ -4,6 +4,7 @@ use compact_jwt::JwsSigner; use dyn_clone::DynClone; use hashbrown::HashSet; use kanidm_proto::v1::Filter as ProtoFilter; +use kanidm_proto::v1::UiHint; use smolset::SmolSet; use time::OffsetDateTime; // use std::fmt::Debug; @@ -36,6 +37,7 @@ mod session; mod spn; mod ssh; mod syntax; +mod uihint; mod uint32; mod url; mod utf8; @@ -60,6 +62,7 @@ pub use self::session::{ValueSetOauth2Session, ValueSetSession}; pub use self::spn::ValueSetSpn; pub use self::ssh::ValueSetSshKey; pub use self::syntax::ValueSetSyntax; +pub use self::uihint::ValueSetUiHint; pub use self::uint32::ValueSetUint32; pub use self::url::ValueSetUrl; pub use self::utf8::ValueSetUtf8; @@ -495,6 +498,16 @@ pub trait ValueSetT: std::fmt::Debug + DynClone { debug_assert!(false); None } + + fn as_uihint_set(&self) -> Option<&BTreeSet> { + debug_assert!(false); + None + } + + fn as_uihint_iter(&self) -> Option + '_>> { + debug_assert!(false); + None + } } impl PartialEq for ValueSet { @@ -546,6 +559,7 @@ pub fn from_result_value_iter( Value::PublicBinary(t, b) => ValueSetPublicBinary::new(t, b), Value::IntentToken(u, s) => ValueSetIntentToken::new(u, s), Value::EmailAddress(a, _) => ValueSetEmailAddress::new(a), + Value::UiHint(u) => ValueSetUiHint::new(u), Value::PhoneNumber(_, _) | Value::Passkey(_, _, _) | Value::DeviceKey(_, _, _) @@ -608,6 +622,7 @@ pub fn from_value_iter(mut iter: impl Iterator) -> Result ValueSetJwsKeyRs256::new(k), Value::Session(u, m) => ValueSetSession::new(u, m), Value::Oauth2Session(u, m) => ValueSetOauth2Session::new(u, m), + Value::UiHint(u) => ValueSetUiHint::new(u), Value::PhoneNumber(_, _) | Value::TrustedDeviceEnrollment(_) => { debug_assert!(false); return Err(OperationError::InvalidValueState); @@ -654,6 +669,7 @@ pub fn from_db_valueset_v2(dbvs: DbValueSetV2) -> Result ValueSetOauth2Session::from_dbvs2(set), DbValueSetV2::JwsKeyEs256(set) => ValueSetJwsKeyEs256::from_dbvs2(&set), DbValueSetV2::JwsKeyRs256(set) => ValueSetJwsKeyEs256::from_dbvs2(&set), + DbValueSetV2::UiHint(set) => ValueSetUiHint::from_dbvs2(set), DbValueSetV2::PhoneNumber(_, _) | DbValueSetV2::TrustedDeviceEnrollment(_) => { todo!() } diff --git a/kanidmd/lib/src/valueset/syntax.rs b/kanidmd/lib/src/valueset/syntax.rs index c094db468..8d3ea061d 100644 --- a/kanidmd/lib/src/valueset/syntax.rs +++ b/kanidmd/lib/src/valueset/syntax.rs @@ -22,7 +22,7 @@ impl ValueSetSyntax { pub fn from_dbvs2(data: Vec) -> Result { let set: Result<_, _> = data.into_iter().map(SyntaxType::try_from).collect(); - let set = set.map_err(|()| OperationError::InvalidValueState)?; + let set = set.map_err(|_| OperationError::InvalidValueState)?; Ok(Box::new(ValueSetSyntax { set })) } } diff --git a/kanidmd/lib/src/valueset/uihint.rs b/kanidmd/lib/src/valueset/uihint.rs new file mode 100644 index 000000000..4888dacd8 --- /dev/null +++ b/kanidmd/lib/src/valueset/uihint.rs @@ -0,0 +1,126 @@ +use std::collections::BTreeSet; + +use crate::prelude::*; +use crate::schema::SchemaAttribute; +use crate::valueset::{DbValueSetV2, ValueSet}; + +use kanidm_proto::v1::UiHint; + +#[derive(Debug, Clone)] +pub struct ValueSetUiHint { + set: BTreeSet, +} + +impl ValueSetUiHint { + pub fn new(s: UiHint) -> Box { + let mut set = BTreeSet::new(); + set.insert(s); + Box::new(ValueSetUiHint { set }) + } + + pub fn push(&mut self, s: UiHint) -> bool { + self.set.insert(s) + } + + pub fn from_dbvs2(data: Vec) -> Result { + let set: Result<_, _> = data.into_iter().map(UiHint::try_from).collect(); + let set = set.map_err(|_| OperationError::InvalidValueState)?; + Ok(Box::new(ValueSetUiHint { set })) + } +} + +impl ValueSetT for ValueSetUiHint { + fn insert_checked(&mut self, value: Value) -> Result { + match value { + Value::UiHint(s) => Ok(self.set.insert(s)), + _ => Err(OperationError::InvalidValueState), + } + } + + fn clear(&mut self) { + self.set.clear(); + } + + fn remove(&mut self, pv: &PartialValue) -> bool { + match pv { + PartialValue::UiHint(s) => self.set.remove(s), + _ => { + debug_assert!(false); + true + } + } + } + + fn contains(&self, pv: &PartialValue) -> bool { + match pv { + PartialValue::UiHint(s) => self.set.contains(&s), + _ => false, + } + } + + fn substring(&self, _pv: &PartialValue) -> bool { + false + } + + fn lessthan(&self, _pv: &PartialValue) -> bool { + false + } + + fn len(&self) -> usize { + self.set.len() + } + + fn generate_idx_eq_keys(&self) -> Vec { + self.set.iter().map(|u| (*u as u16).to_string()).collect() + } + + fn syntax(&self) -> SyntaxType { + SyntaxType::UiHint + } + + fn validate(&self, _schema_attr: &SchemaAttribute) -> bool { + true + } + + fn to_proto_string_clone_iter(&self) -> Box + '_> { + Box::new(self.set.iter().map(|u| u.to_string())) + } + + fn to_db_valueset_v2(&self) -> DbValueSetV2 { + DbValueSetV2::UiHint(self.set.iter().map(|u| *u as u16).collect()) + } + + fn to_partialvalue_iter(&self) -> Box + '_> { + Box::new(self.set.iter().copied().map(|i| PartialValue::UiHint(i))) + } + + fn to_value_iter(&self) -> Box + '_> { + Box::new(self.set.iter().copied().map(|i| Value::UiHint(i))) + } + + fn equal(&self, other: &ValueSet) -> bool { + if let Some(other) = other.as_uihint_set() { + &self.set == other + } else { + debug_assert!(false); + false + } + } + + fn merge(&mut self, other: &ValueSet) -> Result<(), OperationError> { + if let Some(b) = other.as_uihint_set() { + mergesets!(self.set, b) + } else { + debug_assert!(false); + Err(OperationError::InvalidValueState) + } + } + + fn as_uihint_set(&self) -> Option<&BTreeSet> { + Some(&self.set) + } + + fn as_uihint_iter(&self) -> Option + '_>> { + Some(Box::new(self.set.iter().copied())) + } +} diff --git a/kanidmd_web_ui/pkg/kanidmd_web_ui.js b/kanidmd_web_ui/pkg/kanidmd_web_ui.js index d59557960..01b13f1e7 100644 --- a/kanidmd_web_ui/pkg/kanidmd_web_ui.js +++ b/kanidmd_web_ui/pkg/kanidmd_web_ui.js @@ -233,7 +233,7 @@ function addBorrowedObject(obj) { } function __wbg_adapter_48(arg0, arg1, arg2) { try { - wasm._dyn_core__ops__function__FnMut___A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__ha86bc8783d36be0a(arg0, arg1, addBorrowedObject(arg2)); + wasm._dyn_core__ops__function__FnMut___A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__h2f44da981a3bd620(arg0, arg1, addBorrowedObject(arg2)); } finally { heap[stack_pointer++] = undefined; } @@ -261,11 +261,11 @@ function makeClosure(arg0, arg1, dtor, f) { return real; } function __wbg_adapter_51(arg0, arg1, arg2) { - wasm._dyn_core__ops__function__Fn__A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__h36bbc8108d49feb4(arg0, arg1, addHeapObject(arg2)); + wasm._dyn_core__ops__function__Fn__A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__h10a3687bc3b2a4bb(arg0, arg1, addHeapObject(arg2)); } function __wbg_adapter_54(arg0, arg1, arg2) { - wasm._dyn_core__ops__function__FnMut__A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__hc4ba360e62a5fa4a(arg0, arg1, addHeapObject(arg2)); + wasm._dyn_core__ops__function__FnMut__A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__h6d8a3152557e4ad6(arg0, arg1, addHeapObject(arg2)); } /** @@ -345,25 +345,12 @@ async function load(module, imports) { function getImports() { const imports = {}; imports.wbg = {}; - imports.wbg.__wbindgen_is_undefined = function(arg0) { - const ret = getObject(arg0) === undefined; - return ret; - }; - imports.wbg.__wbindgen_in = function(arg0, arg1) { - const ret = getObject(arg0) in getObject(arg1); - return ret; - }; imports.wbg.__wbindgen_number_get = function(arg0, arg1) { const obj = getObject(arg1); const ret = typeof(obj) === 'number' ? obj : undefined; getFloat64Memory0()[arg0 / 8 + 1] = isLikeNone(ret) ? 0 : ret; getInt32Memory0()[arg0 / 4 + 0] = !isLikeNone(ret); }; - imports.wbg.__wbindgen_boolean_get = function(arg0) { - const v = getObject(arg0); - const ret = typeof(v) === 'boolean' ? (v ? 1 : 0) : 2; - return ret; - }; imports.wbg.__wbindgen_string_get = function(arg0, arg1) { const obj = getObject(arg1); const ret = typeof(obj) === 'string' ? obj : undefined; @@ -372,23 +359,14 @@ function getImports() { getInt32Memory0()[arg0 / 4 + 1] = len0; getInt32Memory0()[arg0 / 4 + 0] = ptr0; }; - imports.wbg.__wbindgen_is_bigint = function(arg0) { - const ret = typeof(getObject(arg0)) === 'bigint'; - return ret; - }; - imports.wbg.__wbindgen_is_object = function(arg0) { - const val = getObject(arg0); - const ret = typeof(val) === 'object' && val !== null; - return ret; - }; - imports.wbg.__wbindgen_is_string = function(arg0) { - const ret = typeof(getObject(arg0)) === 'string'; - return ret; - }; imports.wbg.__wbindgen_bigint_from_i64 = function(arg0) { const ret = arg0; return addHeapObject(ret); }; + imports.wbg.__wbindgen_jsval_eq = function(arg0, arg1) { + const ret = getObject(arg0) === getObject(arg1); + return ret; + }; imports.wbg.__wbindgen_bigint_from_u64 = function(arg0) { const ret = BigInt.asUintN(64, arg0); return addHeapObject(ret); @@ -405,13 +383,35 @@ function getImports() { const ret = getStringFromWasm0(arg0, arg1); return addHeapObject(ret); }; - imports.wbg.__wbindgen_jsval_eq = function(arg0, arg1) { - const ret = getObject(arg0) === getObject(arg1); - return ret; - }; imports.wbg.__wbindgen_object_drop_ref = function(arg0) { takeObject(arg0); }; + imports.wbg.__wbindgen_is_undefined = function(arg0) { + const ret = getObject(arg0) === undefined; + return ret; + }; + imports.wbg.__wbindgen_in = function(arg0, arg1) { + const ret = getObject(arg0) in getObject(arg1); + return ret; + }; + imports.wbg.__wbindgen_boolean_get = function(arg0) { + const v = getObject(arg0); + const ret = typeof(v) === 'boolean' ? (v ? 1 : 0) : 2; + return ret; + }; + imports.wbg.__wbindgen_is_bigint = function(arg0) { + const ret = typeof(getObject(arg0)) === 'bigint'; + return ret; + }; + imports.wbg.__wbindgen_is_object = function(arg0) { + const val = getObject(arg0); + const ret = typeof(val) === 'object' && val !== null; + return ret; + }; + imports.wbg.__wbindgen_is_string = function(arg0) { + const ret = typeof(getObject(arg0)) === 'string'; + return ret; + }; imports.wbg.__wbindgen_cb_drop = function(arg0) { const obj = takeObject(arg0).original; if (obj.cnt-- == 1) { @@ -421,7 +421,7 @@ function getImports() { const ret = false; return ret; }; - imports.wbg.__wbg_modalhidebyid_4a1a18ce4b8f3393 = function(arg0, arg1) { + imports.wbg.__wbg_modalhidebyid_130cc6453fa7b55b = function(arg0, arg1) { modal_hide_by_id(getStringFromWasm0(arg0, arg1)); }; imports.wbg.__wbindgen_number_new = function(arg0) { @@ -539,6 +539,43 @@ function getImports() { const ret = getObject(arg0).querySelector(getStringFromWasm0(arg1, arg2)); return isLikeNone(ret) ? 0 : addHeapObject(ret); }, arguments) }; + imports.wbg.__wbg_log_4b5638ad60bdc54a = function(arg0) { + console.log(getObject(arg0)); + }; + imports.wbg.__wbg_value_ccb32485ee1b3928 = function(arg0, arg1) { + const ret = getObject(arg1).value; + const ptr0 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); + const len0 = WASM_VECTOR_LEN; + getInt32Memory0()[arg0 / 4 + 1] = len0; + getInt32Memory0()[arg0 / 4 + 0] = ptr0; + }; + imports.wbg.__wbg_setvalue_df64bc6794c098f2 = function(arg0, arg1, arg2) { + getObject(arg0).value = getStringFromWasm0(arg1, arg2); + }; + imports.wbg.__wbg_new_2d0053ee81e4dd2a = function() { return handleError(function () { + const ret = new Headers(); + return addHeapObject(ret); + }, arguments) }; + imports.wbg.__wbg_get_31b57952dfc2c6cc = function() { return handleError(function (arg0, arg1, arg2, arg3) { + const ret = getObject(arg1).get(getStringFromWasm0(arg2, arg3)); + var ptr0 = isLikeNone(ret) ? 0 : passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); + var len0 = WASM_VECTOR_LEN; + getInt32Memory0()[arg0 / 4 + 1] = len0; + getInt32Memory0()[arg0 / 4 + 0] = ptr0; + }, arguments) }; + imports.wbg.__wbg_set_992c1d31586b2957 = function() { return handleError(function (arg0, arg1, arg2, arg3, arg4) { + getObject(arg0).set(getStringFromWasm0(arg1, arg2), getStringFromWasm0(arg3, arg4)); + }, arguments) }; + imports.wbg.__wbg_instanceof_HtmlFormElement_1c489ff7e99e43d3 = function(arg0) { + let result; + try { + result = getObject(arg0) instanceof HTMLFormElement; + } catch { + result = false; + } + const ret = result; + return ret; + }; imports.wbg.__wbg_getItem_845e475f85f593e4 = function() { return handleError(function (arg0, arg1, arg2, arg3) { const ret = getObject(arg1).getItem(getStringFromWasm0(arg2, arg3)); var ptr0 = isLikeNone(ret) ? 0 : passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); @@ -552,6 +589,16 @@ function getImports() { imports.wbg.__wbg_setItem_9c469d634d0c321c = function() { return handleError(function (arg0, arg1, arg2, arg3, arg4) { getObject(arg0).setItem(getStringFromWasm0(arg1, arg2), getStringFromWasm0(arg3, arg4)); }, arguments) }; + imports.wbg.__wbg_new_ca4d3a3eca340210 = function() { return handleError(function () { + const ret = new URLSearchParams(); + return addHeapObject(ret); + }, arguments) }; + imports.wbg.__wbg_add_89a4f3b0846cf0aa = function() { return handleError(function (arg0, arg1, arg2) { + getObject(arg0).add(getStringFromWasm0(arg1, arg2)); + }, arguments) }; + imports.wbg.__wbg_remove_1a26eb5d822902ed = function() { return handleError(function (arg0, arg1, arg2) { + getObject(arg0).remove(getStringFromWasm0(arg1, arg2)); + }, arguments) }; imports.wbg.__wbg_pathname_78a642e573bf8169 = function(arg0, arg1) { const ret = getObject(arg1).pathname; const ptr0 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); @@ -573,69 +620,6 @@ function getImports() { const ret = new URL(getStringFromWasm0(arg0, arg1)); return addHeapObject(ret); }, arguments) }; - imports.wbg.__wbg_new_ca4d3a3eca340210 = function() { return handleError(function () { - const ret = new URLSearchParams(); - return addHeapObject(ret); - }, arguments) }; - imports.wbg.__wbg_new_2d0053ee81e4dd2a = function() { return handleError(function () { - const ret = new Headers(); - return addHeapObject(ret); - }, arguments) }; - imports.wbg.__wbg_get_31b57952dfc2c6cc = function() { return handleError(function (arg0, arg1, arg2, arg3) { - const ret = getObject(arg1).get(getStringFromWasm0(arg2, arg3)); - var ptr0 = isLikeNone(ret) ? 0 : passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); - var len0 = WASM_VECTOR_LEN; - getInt32Memory0()[arg0 / 4 + 1] = len0; - getInt32Memory0()[arg0 / 4 + 0] = ptr0; - }, arguments) }; - imports.wbg.__wbg_set_992c1d31586b2957 = function() { return handleError(function (arg0, arg1, arg2, arg3, arg4) { - getObject(arg0).set(getStringFromWasm0(arg1, arg2), getStringFromWasm0(arg3, arg4)); - }, arguments) }; - imports.wbg.__wbg_value_ccb32485ee1b3928 = function(arg0, arg1) { - const ret = getObject(arg1).value; - const ptr0 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); - const len0 = WASM_VECTOR_LEN; - getInt32Memory0()[arg0 / 4 + 1] = len0; - getInt32Memory0()[arg0 / 4 + 0] = ptr0; - }; - imports.wbg.__wbg_setvalue_df64bc6794c098f2 = function(arg0, arg1, arg2) { - getObject(arg0).value = getStringFromWasm0(arg1, arg2); - }; - imports.wbg.__wbg_url_1c013f0875e97715 = function(arg0, arg1) { - const ret = getObject(arg1).url; - const ptr0 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); - const len0 = WASM_VECTOR_LEN; - getInt32Memory0()[arg0 / 4 + 1] = len0; - getInt32Memory0()[arg0 / 4 + 0] = ptr0; - }; - imports.wbg.__wbg_headers_85824e993aa739bf = function(arg0) { - const ret = getObject(arg0).headers; - return addHeapObject(ret); - }; - imports.wbg.__wbg_newwithstr_fdce36db91ec5f92 = function() { return handleError(function (arg0, arg1) { - const ret = new Request(getStringFromWasm0(arg0, arg1)); - return addHeapObject(ret); - }, arguments) }; - imports.wbg.__wbg_newwithstrandinit_05d7180788420c40 = function() { return handleError(function (arg0, arg1, arg2) { - const ret = new Request(getStringFromWasm0(arg0, arg1), getObject(arg2)); - return addHeapObject(ret); - }, arguments) }; - imports.wbg.__wbg_add_89a4f3b0846cf0aa = function() { return handleError(function (arg0, arg1, arg2) { - getObject(arg0).add(getStringFromWasm0(arg1, arg2)); - }, arguments) }; - imports.wbg.__wbg_remove_1a26eb5d822902ed = function() { return handleError(function (arg0, arg1, arg2) { - getObject(arg0).remove(getStringFromWasm0(arg1, arg2)); - }, arguments) }; - imports.wbg.__wbg_instanceof_HtmlFormElement_1c489ff7e99e43d3 = function(arg0) { - let result; - try { - result = getObject(arg0) instanceof HTMLFormElement; - } catch { - result = false; - } - const ret = result; - return ret; - }; imports.wbg.__wbg_instanceof_HtmlInputElement_970e4026de0fccff = function(arg0) { let result; try { @@ -659,6 +643,40 @@ function getImports() { imports.wbg.__wbg_setvalue_e5b519cca37d82a7 = function(arg0, arg1, arg2) { getObject(arg0).value = getStringFromWasm0(arg1, arg2); }; + imports.wbg.__wbg_create_53c6ddb068a22172 = function() { return handleError(function (arg0, arg1) { + const ret = getObject(arg0).create(getObject(arg1)); + return addHeapObject(ret); + }, arguments) }; + imports.wbg.__wbg_get_da97585bbb5a63bb = function() { return handleError(function (arg0, arg1) { + const ret = getObject(arg0).get(getObject(arg1)); + return addHeapObject(ret); + }, arguments) }; + imports.wbg.__wbg_href_90ff36b5040e3b76 = function(arg0, arg1) { + const ret = getObject(arg1).href; + const ptr0 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); + const len0 = WASM_VECTOR_LEN; + getInt32Memory0()[arg0 / 4 + 1] = len0; + getInt32Memory0()[arg0 / 4 + 0] = ptr0; + }; + imports.wbg.__wbg_url_1c013f0875e97715 = function(arg0, arg1) { + const ret = getObject(arg1).url; + const ptr0 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); + const len0 = WASM_VECTOR_LEN; + getInt32Memory0()[arg0 / 4 + 1] = len0; + getInt32Memory0()[arg0 / 4 + 0] = ptr0; + }; + imports.wbg.__wbg_headers_85824e993aa739bf = function(arg0) { + const ret = getObject(arg0).headers; + return addHeapObject(ret); + }; + imports.wbg.__wbg_newwithstr_fdce36db91ec5f92 = function() { return handleError(function (arg0, arg1) { + const ret = new Request(getStringFromWasm0(arg0, arg1)); + return addHeapObject(ret); + }, arguments) }; + imports.wbg.__wbg_newwithstrandinit_05d7180788420c40 = function() { return handleError(function (arg0, arg1, arg2) { + const ret = new Request(getStringFromWasm0(arg0, arg1), getObject(arg2)); + return addHeapObject(ret); + }, arguments) }; imports.wbg.__wbg_instanceof_Element_33bd126d58f2021b = function(arg0) { let result; try { @@ -702,21 +720,6 @@ function getImports() { imports.wbg.__wbg_focus_adfe4cc61e2c09bc = function() { return handleError(function (arg0) { getObject(arg0).focus(); }, arguments) }; - imports.wbg.__wbg_create_53c6ddb068a22172 = function() { return handleError(function (arg0, arg1) { - const ret = getObject(arg0).create(getObject(arg1)); - return addHeapObject(ret); - }, arguments) }; - imports.wbg.__wbg_get_da97585bbb5a63bb = function() { return handleError(function (arg0, arg1) { - const ret = getObject(arg0).get(getObject(arg1)); - return addHeapObject(ret); - }, arguments) }; - imports.wbg.__wbg_href_90ff36b5040e3b76 = function(arg0, arg1) { - const ret = getObject(arg1).href; - const ptr0 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); - const len0 = WASM_VECTOR_LEN; - getInt32Memory0()[arg0 / 4 + 1] = len0; - getInt32Memory0()[arg0 / 4 + 0] = ptr0; - }; imports.wbg.__wbg_credentials_eab5c0bffc3e9cc5 = function(arg0) { const ret = getObject(arg0).credentials; return addHeapObject(ret); @@ -746,12 +749,6 @@ function getImports() { imports.wbg.__wbg_preventDefault_3209279b490de583 = function(arg0) { getObject(arg0).preventDefault(); }; - imports.wbg.__wbg_addEventListener_1fc744729ac6dc27 = function() { return handleError(function (arg0, arg1, arg2, arg3, arg4) { - getObject(arg0).addEventListener(getStringFromWasm0(arg1, arg2), getObject(arg3), getObject(arg4)); - }, arguments) }; - imports.wbg.__wbg_removeEventListener_b10f1a66647f3aa0 = function() { return handleError(function (arg0, arg1, arg2, arg3, arg4) { - getObject(arg0).removeEventListener(getStringFromWasm0(arg1, arg2), getObject(arg3), arg4 !== 0); - }, arguments) }; imports.wbg.__wbg_newwithform_6b545e9ddaccc455 = function() { return handleError(function (arg0) { const ret = new FormData(getObject(arg0)); return addHeapObject(ret); @@ -760,6 +757,12 @@ function getImports() { const ret = getObject(arg0).get(getStringFromWasm0(arg1, arg2)); return addHeapObject(ret); }; + imports.wbg.__wbg_addEventListener_1fc744729ac6dc27 = function() { return handleError(function (arg0, arg1, arg2, arg3, arg4) { + getObject(arg0).addEventListener(getStringFromWasm0(arg1, arg2), getObject(arg3), getObject(arg4)); + }, arguments) }; + imports.wbg.__wbg_removeEventListener_b10f1a66647f3aa0 = function() { return handleError(function (arg0, arg1, arg2, arg3, arg4) { + getObject(arg0).removeEventListener(getStringFromWasm0(arg1, arg2), getObject(arg3), arg4 !== 0); + }, arguments) }; imports.wbg.__wbg_parentElement_0cffb3ceb0f107bd = function(arg0) { const ret = getObject(arg0).parentElement; return isLikeNone(ret) ? 0 : addHeapObject(ret); @@ -1037,16 +1040,16 @@ function getImports() { const ret = wasm.memory; return addHeapObject(ret); }; - imports.wbg.__wbindgen_closure_wrapper5651 = function(arg0, arg1, arg2) { - const ret = makeMutClosure(arg0, arg1, 1343, __wbg_adapter_48); + imports.wbg.__wbindgen_closure_wrapper4757 = function(arg0, arg1, arg2) { + const ret = makeMutClosure(arg0, arg1, 1013, __wbg_adapter_48); return addHeapObject(ret); }; - imports.wbg.__wbindgen_closure_wrapper5814 = function(arg0, arg1, arg2) { - const ret = makeClosure(arg0, arg1, 1378, __wbg_adapter_51); + imports.wbg.__wbindgen_closure_wrapper4941 = function(arg0, arg1, arg2) { + const ret = makeClosure(arg0, arg1, 1037, __wbg_adapter_51); return addHeapObject(ret); }; - imports.wbg.__wbindgen_closure_wrapper6542 = function(arg0, arg1, arg2) { - const ret = makeMutClosure(arg0, arg1, 1635, __wbg_adapter_54); + imports.wbg.__wbindgen_closure_wrapper5597 = function(arg0, arg1, arg2) { + const ret = makeMutClosure(arg0, arg1, 1287, __wbg_adapter_54); return addHeapObject(ret); }; diff --git a/kanidmd_web_ui/pkg/kanidmd_web_ui_bg.wasm b/kanidmd_web_ui/pkg/kanidmd_web_ui_bg.wasm index cde163003..53186f834 100644 Binary files a/kanidmd_web_ui/pkg/kanidmd_web_ui_bg.wasm and b/kanidmd_web_ui/pkg/kanidmd_web_ui_bg.wasm differ diff --git a/kanidmd_web_ui/pkg/package.json b/kanidmd_web_ui/pkg/package.json index 88cf1daa9..831d0038d 100644 --- a/kanidmd_web_ui/pkg/package.json +++ b/kanidmd_web_ui/pkg/package.json @@ -5,7 +5,7 @@ "James Hodgkinson " ], "description": "Kanidm Server Web User Interface", - "version": "1.1.0-alpha.10", + "version": "1.1.0-alpha.11-dev", "license": "MPL-2.0", "repository": { "type": "git", diff --git a/kanidmd_web_ui/src/views/mod.rs b/kanidmd_web_ui/src/views/mod.rs index 0e3661334..9e165f4c0 100644 --- a/kanidmd_web_ui/src/views/mod.rs +++ b/kanidmd_web_ui/src/views/mod.rs @@ -1,5 +1,5 @@ use gloo::console; -use kanidm_proto::v1::UserAuthToken; +use kanidm_proto::v1::{UserAuthToken, UiHint}; use serde::{Deserialize, Serialize}; use wasm_bindgen::{JsCast, UnwrapThrowExt}; use wasm_bindgen_futures::JsFuture; @@ -236,6 +236,8 @@ impl ViewsApp { fn view_authenticated(&self, ctx: &Context, uat: &UserAuthToken) -> Html { let current_user_uat = uat.clone(); + let ui_hint_experimental = uat.ui_hints.contains(&UiHint::ExperimentalFeatures); + // WARN set dash-body against body here? html! { <> @@ -247,41 +249,48 @@ impl ViewsApp { +