diff --git a/.github/workflows/kanidm_book.yml b/.github/workflows/kanidm_book.yml index 8af17d2d4..021af1017 100644 --- a/.github/workflows/kanidm_book.yml +++ b/.github/workflows/kanidm_book.yml @@ -7,6 +7,7 @@ name: GitHub Pages - "master" pull_request: +# yamllint disable-line rule:line-length # permissions list: https://docs.github.com/en/actions/security-guides/automatic-token-authentication#modifying-the-permissions-for-the-github_token permissions: id-token: write @@ -36,7 +37,11 @@ jobs: fetch-depth: 0 - name: Latest branch run: | - echo "latest=$(git branch -a | awk '{print $1}' | sort -t. -k3n,3 -k4n,4 | awk -F'/' '{print $NF}' | tail -n1)" >> $GITHUB_OUTPUT + echo "latest=$(git branch -a \ + | awk '{print $1}' \ + | sort -t. -k3n,3 -k4n,4 \ + | awk -F'/' '{print $NF}' \ + | tail -n1)" >> $GITHUB_OUTPUT id: branchname - name: Move redirector page run: | @@ -53,6 +58,7 @@ jobs: fanout: uses: './.github/workflows/kanidm_individual_book.yml' needs: pre_deploy + # yamllint disable-line rule:line-length if: ${{ github.action_ref == 'refs/heads/master' && github.repository == 'kanidm/kanidm' && github.event == 'merge' }} strategy: @@ -78,6 +84,7 @@ jobs: - fanout - docs_master runs-on: ubuntu-latest + # yamllint disable-line rule:line-length if: ${{ github.action_ref == 'refs/heads/master' && github.repository == 'kanidm/kanidm' && github.event == 'merge' }} steps: - name: Setup Pages @@ -110,7 +117,10 @@ jobs: find $(pwd) -name '*.tar.gz' -ls -exec tar zxvf "{}" \; echo "Carrying on..." mkdir -p docs - cd docs && cp -R "$(git branch -a | awk '{print $1}' | sort -t. -k3n,3 -k4n,4 | awk -F'/' '{print $NF}' | tail -n1)/" stable && cd .. + cd docs && cp -R "$(git branch -a \ + | awk '{print $1}' \ + | sort -t. -k3n,3 -k4n,4 \ + | awk -F'/' '{print $NF}' | tail -n1)/" stable && cd .. ls -la docs/ echo "Cleaning up docs archives" rm docs/*.tar.gz diff --git a/.github/workflows/wasm_test.yml b/.github/workflows/wasm_test.yml index bbf7b5588..5bb345218 100644 --- a/.github/workflows/wasm_test.yml +++ b/.github/workflows/wasm_test.yml @@ -41,9 +41,15 @@ jobs: # Optional: do not specify to match Chrome's version # chromedriver-version: '88.0.4324.96' - # docs here: - # https://rustwasm.github.io/docs/wasm-bindgen/wasm-bindgen-test/browsers.html - - run: make webui + # - run: make webui - name: "Run wasm-pack test" + # https://rustwasm.github.io/docs/wasm-bindgen/wasm-bindgen-test/browsers.html run: make webui/test continue-on-error: true + + - name: "Run webdriver tests" + run: | + chromedriver & + cargo test -p kanidmd_testkit --features webdriver + env: + DISPLAY: ":99" diff --git a/.yamllint b/.yamllint new file mode 100644 index 000000000..b79c7d733 --- /dev/null +++ b/.yamllint @@ -0,0 +1,7 @@ +extends: default + +rules: + line-length: + max: 120 + level: warning + diff --git a/Cargo.lock b/Cargo.lock index 657b88eaf..824da73c7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1510,6 +1510,28 @@ dependencies = [ "regex", ] +[[package]] +name = "fantoccini" +version = "0.19.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65f0fbe245d714b596ba5802b46f937f5ce68dcae0f32f9a70b5c3b04d3c6f64" +dependencies = [ + "base64 0.13.1", + "cookie 0.16.2", + "futures-core", + "futures-util", + "http", + "hyper", + "hyper-tls", + "mime", + "serde", + "serde_json", + "time 0.3.22", + "tokio", + "url", + "webdriver", +] + [[package]] name = "fastrand" version = "1.9.0" @@ -2704,7 +2726,9 @@ name = "kanidmd_testkit" version = "1.1.0-beta.13-dev" dependencies = [ "compact_jwt", + "fantoccini", "futures", + "hyper-tls", "kanidm_client", "kanidm_proto", "kanidmd_core", @@ -5273,6 +5297,12 @@ dependencies = [ "tinyvec", ] +[[package]] +name = "unicode-segmentation" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36" + [[package]] name = "unicode-width" version = "0.1.10" @@ -5577,6 +5607,25 @@ dependencies = [ "web-sys", ] +[[package]] +name = "webdriver" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9973cb72c8587d5ad5efdb91e663d36177dc37725e6c90ca86c626b0cc45c93f" +dependencies = [ + "base64 0.13.1", + "bytes", + "cookie 0.16.2", + "http", + "log", + "serde", + "serde_derive", + "serde_json", + "time 0.3.22", + "unicode-segmentation", + "url", +] + [[package]] name = "which" version = "4.4.0" diff --git a/server/core/src/https/oauth2.rs b/server/core/src/https/oauth2.rs index b2b430f2f..745ae41da 100644 --- a/server/core/src/https/oauth2.rs +++ b/server/core/src/https/oauth2.rs @@ -241,6 +241,7 @@ pub async fn oauth2_id_delete( // valid Kanidm instance in the topology can handle these request. // +#[instrument(level = "debug", skip(state, kopid))] pub async fn oauth2_authorise_post( State(state): State, Extension(kopid): Extension, @@ -256,6 +257,7 @@ pub async fn oauth2_authorise_post( res } +#[instrument(level = "debug", skip(state, kopid))] pub async fn oauth2_authorise_get( State(state): State, Extension(kopid): Extension, diff --git a/server/lib/src/idm/oauth2.rs b/server/lib/src/idm/oauth2.rs index d3c35973e..cf7cc0ffc 100644 --- a/server/lib/src/idm/oauth2.rs +++ b/server/lib/src/idm/oauth2.rs @@ -1408,9 +1408,10 @@ impl<'a> IdmServerProxyReadTransaction<'a> { }; if consent_previously_granted { + let pretty_scopes: Vec = granted_scopes.iter().map(|s| s.to_owned()).collect(); admin_info!( - "User has previously consented, permitting. {:?}", - granted_scopes + "User has previously consented, permitting with scopes: {}", + pretty_scopes.join(",") ); // Setup for the permit success diff --git a/server/testkit/Cargo.toml b/server/testkit/Cargo.toml index 7c392b859..d319d3152 100644 --- a/server/testkit/Cargo.toml +++ b/server/testkit/Cargo.toml @@ -15,11 +15,21 @@ repository = { workspace = true } name = "kanidmd_testkit" path = "src/lib.rs" +[features] +default = [] +# Enables webdriver tests, you need to be running a webdriver server +webdriver = ["fantoccini"] + [dependencies] kanidm_client = { workspace = true } kanidm_proto = { workspace = true } kanidmd_core = { workspace = true } kanidmd_lib = { workspace = true } +# used for webdriver testing +hyper-tls = { workspace = true } +# used for webdriver testing +fantoccini = { version="0.19.3", optional=true} + url = { workspace = true, features = ["serde"] } diff --git a/server/testkit/src/lib.rs b/server/testkit/src/lib.rs index c6f8f7ea6..a48904684 100644 --- a/server/testkit/src/lib.rs +++ b/server/testkit/src/lib.rs @@ -14,6 +14,7 @@ use std::net::TcpStream; use std::sync::atomic::{AtomicU16, Ordering}; use kanidm_client::{KanidmClient, KanidmClientBuilder}; +use kanidm_proto::v1::{Filter, Modify, ModifyList}; use kanidmd_core::config::{Configuration, IntegrationTestConfig, ServerRole}; use kanidmd_core::{create_server_core, CoreHandle}; use tokio::task; @@ -92,3 +93,289 @@ pub async fn setup_async_test() -> (KanidmClient, CoreHandle) { (rsclient, core_handle) } + +/// creates a user (username: `id`) and puts them into a group, creating it if need be. +pub async fn create_user(rsclient: &KanidmClient, id: &str, group_name: &str) { + #[allow(clippy::expect_used)] + rsclient + .idm_person_account_create(id, id) + .await + .expect("Failed to create the user"); + + // Create group and add to user to test read attr: member_of + #[allow(clippy::expect_used)] + if rsclient + .idm_group_get(group_name) + .await + .expect("Failed to get group") + .is_none() + { + #[allow(clippy::expect_used)] + rsclient + .idm_group_create(group_name) + .await + .expect("Failed to create group"); + } + + #[allow(clippy::expect_used)] + rsclient + .idm_group_add_members(group_name, &[id]) + .await + .expect("Failed to set group membership for user"); +} + +pub async fn create_user_with_all_attrs( + rsclient: &KanidmClient, + id: &str, + optional_group: Option<&str>, +) { + let group_format = format!("{}_group", id); + let group_name = optional_group.unwrap_or(&group_format); + + create_user(rsclient, id, group_name).await; + add_all_attrs(rsclient, id, group_name, Some(id)).await; +} + +pub async fn add_all_attrs( + rsclient: &KanidmClient, + id: &str, + group_name: &str, + legalname: Option<&str>, +) { + // Extend with posix attrs to test read attr: gidnumber and loginshell + #[allow(clippy::expect_used)] + rsclient + .idm_person_account_unix_extend(id, None, Some("/bin/sh")) + .await + .expect("Failed to set shell to /bin/sh for user"); + #[allow(clippy::expect_used)] + rsclient + .idm_group_unix_extend(group_name, None) + .await + .expect("Failed to extend user group"); + + for attr in ["ssh_publickey", "mail"].iter() { + println!("Checking writable for {}", attr); + #[allow(clippy::expect_used)] + let res = is_attr_writable(rsclient, id, attr) + .await + .expect("Failed to get wriable status for attribute"); + assert!(res); + } + + if let Some(legalname) = legalname { + #[allow(clippy::expect_used)] + let res = is_attr_writable(rsclient, legalname, "legalname") + .await + .expect("Failed to get writable status for legalname field"); + assert!(res); + } + + // Write radius credentials + if id != "anonymous" { + login_account(rsclient, id).await; + #[allow(clippy::expect_used)] + let _ = rsclient + .idm_account_radius_credential_regenerate(id) + .await + .expect("Failed to regen password for user"); + + #[allow(clippy::expect_used)] + rsclient + .auth_simple_password(ADMIN_TEST_USER, ADMIN_TEST_PASSWORD) + .await + .expect("Failed to auth with password as admin!"); + } +} + +pub async fn is_attr_writable(rsclient: &KanidmClient, id: &str, attr: &str) -> Option { + println!("writing to attribute: {}", attr); + match attr { + "radius_secret" => Some( + rsclient + .idm_account_radius_credential_regenerate(id) + .await + .is_ok(), + ), + "primary_credential" => Some( + rsclient + .idm_person_account_primary_credential_set_password(id, "dsadjasiodqwjk12asdl") + .await + .is_ok(), + ), + "ssh_publickey" => Some( + rsclient + .idm_person_account_post_ssh_pubkey( + id, + "k1", + "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAeGW1P6Pc2rPq0XqbRaDKBcXZUPRklo0\ + L1EyR30CwoP william@amethyst", + ) + .await + .is_ok(), + ), + "unix_password" => Some( + rsclient + .idm_person_account_unix_cred_put(id, "dsadjasiodqwjk12asdl") + .await + .is_ok(), + ), + "legalname" => Some( + rsclient + .idm_person_account_set_attr(id, "legalname", &["test legal name"]) + .await + .is_ok(), + ), + "mail" => Some( + rsclient + .idm_person_account_set_attr(id, "mail", &[&format!("{}@example.com", id)]) + .await + .is_ok(), + ), + entry => { + let new_value = match entry { + "acp_receiver_group" => "00000000-0000-0000-0000-000000000011".to_string(), + "acp_targetscope" => "{\"and\": [{\"eq\": [\"class\",\"access_control_profile\"]}, {\"andnot\": {\"or\": [{\"eq\": [\"class\", \"tombstone\"]}, {\"eq\": [\"class\", \"recycled\"]}]}}]}".to_string(), + _ => id.to_string(), + }; + let m = ModifyList::new_list(vec![ + Modify::Purged(attr.to_string()), + Modify::Present(attr.to_string(), new_value), + ]); + let f = Filter::Eq("name".to_string(), id.to_string()); + Some(rsclient.modify(f.clone(), m.clone()).await.is_ok()) + } + } +} + +pub async fn login_account(rsclient: &KanidmClient, id: &str) { + #[allow(clippy::expect_used)] + rsclient + .idm_group_add_members( + "idm_people_account_password_import_priv", + &[ADMIN_TEST_USER], + ) + .await + .expect("Failed to add user to idm_people_account_password_import_priv"); + + #[allow(clippy::expect_used)] + rsclient + .idm_group_add_members("idm_people_extend_priv", &[ADMIN_TEST_USER]) + .await + .expect("Failed to add user to idm_people_extend_priv"); + + #[allow(clippy::expect_used)] + rsclient + .idm_person_account_primary_credential_set_password(id, NOT_ADMIN_TEST_PASSWORD) + .await + .expect("Failed to set password for user"); + + let _ = rsclient.logout().await; + let res = rsclient + .auth_simple_password(id, NOT_ADMIN_TEST_PASSWORD) + .await; + + // Setup privs + println!("{} logged in", id); + assert!(res.is_ok()); + + let res = rsclient + .reauth_simple_password(NOT_ADMIN_TEST_PASSWORD) + .await; + println!("{} priv granted for", id); + assert!(res.is_ok()); +} + +// Login to the given account, but first login with default admin credentials. +// This is necessary when switching between unprivileged accounts, but adds extra calls which +// create extra debugging noise, so should be avoided when unnecessary. +pub async fn login_account_via_admin(rsclient: &KanidmClient, id: &str) { + let _ = rsclient.logout().await; + + #[allow(clippy::expect_used)] + rsclient + .auth_simple_password(ADMIN_TEST_USER, ADMIN_TEST_PASSWORD) + .await + .expect("Failed to login as admin!"); + login_account(rsclient, id).await +} + +pub async fn test_read_attrs(rsclient: &KanidmClient, id: &str, attrs: &[&str], is_readable: bool) { + println!("Test read to {}, is readable: {}", id, is_readable); + #[allow(clippy::expect_used)] + let rset = rsclient + .search(Filter::Eq("name".to_string(), id.to_string())) + .await + .expect("Can't get user from search"); + + #[allow(clippy::expect_used)] + let e = rset.first().expect("Failed to get first user from set"); + + for attr in attrs.iter() { + println!("Reading {}", attr); + #[allow(clippy::unwrap_used)] + let is_ok = match *attr { + "radius_secret" => rsclient + .idm_account_radius_credential_get(id) + .await + .unwrap() + .is_some(), + _ => e.attrs.get(*attr).is_some(), + }; + + assert!(is_ok == is_readable) + } +} + +pub async fn test_write_attrs( + rsclient: &KanidmClient, + id: &str, + attrs: &[&str], + is_writeable: bool, +) { + println!("Test write to {}, is writeable: {}", id, is_writeable); + for attr in attrs.iter() { + println!("Writing to {} - ex {}", attr, is_writeable); + #[allow(clippy::unwrap_used)] + let is_ok = is_attr_writable(rsclient, id, attr).await.unwrap(); + assert!(is_ok == is_writeable) + } +} + +pub async fn test_modify_group( + rsclient: &KanidmClient, + group_names: &[&str], + can_be_modified: bool, +) { + // need user test created to be added as test part + for group in group_names.iter() { + println!("Testing group: {}", group); + for attr in ["description", "name"].iter() { + #[allow(clippy::unwrap_used)] + let is_writable = is_attr_writable(rsclient, group, attr).await.unwrap(); + assert!(is_writable == can_be_modified) + } + assert!( + rsclient + .idm_group_add_members(group, &[NOT_ADMIN_TEST_USERNAME]) + .await + .is_ok() + == can_be_modified + ); + } +} + +/// Logs in with the admin user and puts them in idm_admins so they can do admin things +pub async fn login_put_admin_idm_admins(rsclient: &KanidmClient) { + #[allow(clippy::expect_used)] + rsclient + .auth_simple_password(ADMIN_TEST_USER, ADMIN_TEST_PASSWORD) + .await + .expect("Failed to authenticate as admin!"); + + #[allow(clippy::expect_used)] + rsclient + .idm_group_add_members("idm_admins", &[ADMIN_TEST_USER]) + .await + .expect("Failed to add admin user to idm_admins") +} diff --git a/server/testkit/tests/default_entries.rs b/server/testkit/tests/default_entries.rs index 3159dd003..69ffa773d 100644 --- a/server/testkit/tests/default_entries.rs +++ b/server/testkit/tests/default_entries.rs @@ -2,9 +2,8 @@ use std::collections::HashSet; use kanidm_client::KanidmClient; -use kanidm_proto::v1::{Filter, Modify, ModifyList}; -use kanidmd_testkit::{ADMIN_TEST_PASSWORD, ADMIN_TEST_USER, NOT_ADMIN_TEST_PASSWORD}; +use kanidmd_testkit::*; static USER_READABLE_ATTRS: [&str; 9] = [ "name", @@ -55,240 +54,13 @@ static DEFAULT_HP_GROUP_NAMES: [&str; 24] = [ static DEFAULT_NOT_HP_GROUP_NAMES: [&str; 2] = ["idm_account_unix_extend_priv", "idm_group_unix_extend_priv"]; -async fn create_user(rsclient: &KanidmClient, id: &str, group_name: &str) { - rsclient.idm_person_account_create(id, id).await.unwrap(); - - // Create group and add to user to test read attr: member_of - if rsclient.idm_group_get(group_name).await.unwrap().is_none() { - rsclient.idm_group_create(group_name).await.unwrap(); - } - - rsclient - .idm_group_add_members(group_name, &[id]) - .await - .unwrap(); -} - -async fn is_attr_writable(rsclient: &KanidmClient, id: &str, attr: &str) -> Option { - println!("writing to attribute: {}", attr); - match attr { - "radius_secret" => Some( - rsclient - .idm_account_radius_credential_regenerate(id) - .await - .is_ok(), - ), - "primary_credential" => Some( - rsclient - .idm_person_account_primary_credential_set_password(id, "dsadjasiodqwjk12asdl") - .await - .is_ok(), - ), - "ssh_publickey" => Some( - rsclient - .idm_person_account_post_ssh_pubkey( - id, - "k1", - "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAeGW1P6Pc2rPq0XqbRaDKBcXZUPRklo0\ - L1EyR30CwoP william@amethyst", - ) - .await - .is_ok(), - ), - "unix_password" => Some( - rsclient - .idm_person_account_unix_cred_put(id, "dsadjasiodqwjk12asdl") - .await - .is_ok(), - ), - "legalname" => Some( - rsclient - .idm_person_account_set_attr(id, "legalname", &["test legal name"]) - .await - .is_ok(), - ), - "mail" => Some( - rsclient - .idm_person_account_set_attr(id, "mail", &[&format!("{}@example.com", id)]) - .await - .is_ok(), - ), - entry => { - let new_value = match entry { - "acp_receiver_group" => "00000000-0000-0000-0000-000000000011".to_string(), - "acp_targetscope" => "{\"and\": [{\"eq\": [\"class\",\"access_control_profile\"]}, {\"andnot\": {\"or\": [{\"eq\": [\"class\", \"tombstone\"]}, {\"eq\": [\"class\", \"recycled\"]}]}}]}".to_string(), - _ => id.to_string(), - }; - let m = ModifyList::new_list(vec![ - Modify::Purged(attr.to_string()), - Modify::Present(attr.to_string(), new_value), - ]); - let f = Filter::Eq("name".to_string(), id.to_string()); - Some(rsclient.modify(f.clone(), m.clone()).await.is_ok()) - } - } -} - -async fn add_all_attrs( - rsclient: &KanidmClient, - id: &str, - group_name: &str, - legalname: Option<&str>, -) { - // Extend with posix attrs to test read attr: gidnumber and loginshell - rsclient - .idm_person_account_unix_extend(id, None, Some("/bin/sh")) - .await - .unwrap(); - rsclient - .idm_group_unix_extend(group_name, None) - .await - .unwrap(); - - for attr in ["ssh_publickey", "mail"].iter() { - assert!(is_attr_writable(rsclient, id, attr).await.unwrap()); - } - - if let Some(legalname) = legalname { - assert!(is_attr_writable(rsclient, legalname, "legalname") - .await - .unwrap()); - } - - // Write radius credentials - if id != "anonymous" { - login_account(rsclient, id).await; - let _ = rsclient - .idm_account_radius_credential_regenerate(id) - .await - .unwrap(); - rsclient - .auth_simple_password(ADMIN_TEST_USER, ADMIN_TEST_PASSWORD) - .await - .unwrap(); - } -} - -async fn create_user_with_all_attrs( - rsclient: &KanidmClient, - id: &str, - optional_group: Option<&str>, -) { - let group_format = format!("{}_group", id); - let group_name = optional_group.unwrap_or(&group_format); - - create_user(rsclient, id, group_name).await; - add_all_attrs(rsclient, id, group_name, Some(id)).await; -} - -async fn login_account(rsclient: &KanidmClient, id: &str) { - rsclient - .idm_group_add_members( - "idm_people_account_password_import_priv", - &[ADMIN_TEST_USER], - ) - .await - .unwrap(); - rsclient - .idm_group_add_members("idm_people_extend_priv", &[ADMIN_TEST_USER]) - .await - .unwrap(); - - rsclient - .idm_person_account_primary_credential_set_password(id, NOT_ADMIN_TEST_PASSWORD) - .await - .unwrap(); - - let _ = rsclient.logout(); - let res = rsclient - .auth_simple_password(id, NOT_ADMIN_TEST_PASSWORD) - .await; - - // Setup privs - println!("{} logged in", id); - assert!(res.is_ok()); - - let res = rsclient - .reauth_simple_password(NOT_ADMIN_TEST_PASSWORD) - .await; - println!("{} priv granted for", id); - assert!(res.is_ok()); -} - -// Login to the given account, but first login with default admin credentials. -// This is necessary when switching between unprivileged accounts, but adds extra calls which -// create extra debugging noise, so should be avoided when unnecessary. -async fn login_account_via_admin(rsclient: &KanidmClient, id: &str) { - let _ = rsclient.logout(); - rsclient - .auth_simple_password(ADMIN_TEST_USER, ADMIN_TEST_PASSWORD) - .await - .unwrap(); - login_account(rsclient, id).await -} - -async fn test_read_attrs(rsclient: &KanidmClient, id: &str, attrs: &[&str], is_readable: bool) { - println!("Test read to {}, is readable: {}", id, is_readable); - let rset = rsclient - .search(Filter::Eq("name".to_string(), id.to_string())) - .await - .unwrap(); - let e = rset.first().unwrap(); - for attr in attrs.iter() { - println!("Reading {}", attr); - let is_ok = match *attr { - "radius_secret" => rsclient - .idm_account_radius_credential_get(id) - .await - .unwrap() - .is_some(), - _ => e.attrs.get(*attr).is_some(), - }; - - assert!(is_ok == is_readable) - } -} - -async fn test_write_attrs(rsclient: &KanidmClient, id: &str, attrs: &[&str], is_writeable: bool) { - println!("Test write to {}, is writeable: {}", id, is_writeable); - for attr in attrs.iter() { - println!("Writing to {} - ex {}", attr, is_writeable); - let is_ok = is_attr_writable(rsclient, id, attr).await.unwrap(); - assert!(is_ok == is_writeable) - } -} - -async fn test_modify_group(rsclient: &KanidmClient, group_names: &[&str], is_modificable: bool) { - // need user test created to be added as test part - for group in group_names.iter() { - println!("Testing group: {}", group); - for attr in ["description", "name"].iter() { - assert!(is_attr_writable(rsclient, group, attr).await.unwrap() == is_modificable) - } - assert!( - rsclient - .idm_group_add_members(group, &["test"]) - .await - .is_ok() - == is_modificable - ); - } -} - // Users // - Read to all self attributes (within security constraints). // - Write to a limited set of self attributes, such as: // name, displayname, legalname, ssh-keys, credentials etc. #[kanidmd_testkit::test] async fn test_default_entries_rbac_users(rsclient: KanidmClient) { - rsclient - .auth_simple_password(ADMIN_TEST_USER, ADMIN_TEST_PASSWORD) - .await - .unwrap(); - rsclient - .idm_group_add_members("idm_admins", &[ADMIN_TEST_USER]) - .await - .unwrap(); + login_put_admin_idm_admins(&rsclient).await; create_user_with_all_attrs(&rsclient, "self_account", Some("self_group")).await; create_user_with_all_attrs(&rsclient, "other_account", Some("other_group")).await; @@ -321,21 +93,20 @@ async fn test_default_entries_rbac_users(rsclient: KanidmClient) { // ability to lock and unlock accounts, excluding high access members. #[kanidmd_testkit::test] async fn test_default_entries_rbac_account_managers(rsclient: KanidmClient) { - rsclient - .auth_simple_password(ADMIN_TEST_USER, ADMIN_TEST_PASSWORD) - .await - .unwrap(); - rsclient - .idm_group_add_members("idm_admins", &[ADMIN_TEST_USER]) - .await - .unwrap(); + login_put_admin_idm_admins(&rsclient).await; create_user(&rsclient, "account_manager", "idm_account_manage_priv").await; - create_user_with_all_attrs(&rsclient, "test", Some("test_group")).await; + create_user_with_all_attrs(&rsclient, NOT_ADMIN_TEST_USERNAME, Some("test_group")).await; login_account(&rsclient, "account_manager").await; - test_read_attrs(&rsclient, "test", &USER_READABLE_ATTRS, true).await; + test_read_attrs( + &rsclient, + NOT_ADMIN_TEST_USERNAME, + &USER_READABLE_ATTRS, + true, + ) + .await; static ACCOUNT_MANAGER_ATTRS: [&str; 5] = [ "name", "displayname", @@ -343,11 +114,29 @@ async fn test_default_entries_rbac_account_managers(rsclient: KanidmClient) { "ssh_publickey", "mail", ]; - test_write_attrs(&rsclient, "test", &ACCOUNT_MANAGER_ATTRS, true).await; + test_write_attrs( + &rsclient, + NOT_ADMIN_TEST_USERNAME, + &ACCOUNT_MANAGER_ATTRS, + true, + ) + .await; static PRIVATE_DATA_ATTRS: [&str; 1] = ["legalname"]; - test_read_attrs(&rsclient, "test", &PRIVATE_DATA_ATTRS, false).await; - test_write_attrs(&rsclient, "test", &PRIVATE_DATA_ATTRS, false).await; + test_read_attrs( + &rsclient, + NOT_ADMIN_TEST_USERNAME, + &PRIVATE_DATA_ATTRS, + false, + ) + .await; + test_write_attrs( + &rsclient, + NOT_ADMIN_TEST_USERNAME, + &PRIVATE_DATA_ATTRS, + false, + ) + .await; // TODO #59: lock and _unlock, except high access members } @@ -356,18 +145,11 @@ async fn test_default_entries_rbac_account_managers(rsclient: KanidmClient) { // write group but not high access #[kanidmd_testkit::test] async fn test_default_entries_rbac_group_managers(rsclient: KanidmClient) { - rsclient - .auth_simple_password(ADMIN_TEST_USER, ADMIN_TEST_PASSWORD) - .await - .unwrap(); - rsclient - .idm_group_add_members("idm_admins", &[ADMIN_TEST_USER]) - .await - .unwrap(); + login_put_admin_idm_admins(&rsclient).await; create_user(&rsclient, "group_manager", "idm_group_manage_priv").await; // create test user without creating new groups - create_user(&rsclient, "test", "idm_admins").await; + create_user(&rsclient, NOT_ADMIN_TEST_USERNAME, "idm_admins").await; login_account(&rsclient, "group_manager").await; @@ -391,7 +173,7 @@ async fn test_default_entries_rbac_group_managers(rsclient: KanidmClient) { rsclient.idm_group_create("test_group").await.unwrap(); rsclient - .idm_group_add_members("test_group", &["test"]) + .idm_group_add_members("test_group", &[NOT_ADMIN_TEST_USERNAME]) .await .unwrap(); assert!(is_attr_writable(&rsclient, "test_group", "description") @@ -403,14 +185,7 @@ async fn test_default_entries_rbac_group_managers(rsclient: KanidmClient) { // read and write access control entries. #[kanidmd_testkit::test] async fn test_default_entries_rbac_admins_access_control_entries(rsclient: KanidmClient) { - rsclient - .auth_simple_password(ADMIN_TEST_USER, ADMIN_TEST_PASSWORD) - .await - .unwrap(); - rsclient - .idm_group_add_members("idm_admins", &[ADMIN_TEST_USER]) - .await - .unwrap(); + login_put_admin_idm_admins(&rsclient).await; static ACP_COMMON_ATTRS: [&str; 4] = [ "name", @@ -459,14 +234,7 @@ async fn test_default_entries_rbac_admins_access_control_entries(rsclient: Kanid // TODO #252: write schema entries #[kanidmd_testkit::test] async fn test_default_entries_rbac_admins_schema_entries(rsclient: KanidmClient) { - rsclient - .auth_simple_password(ADMIN_TEST_USER, ADMIN_TEST_PASSWORD) - .await - .unwrap(); - rsclient - .idm_group_add_members("idm_admins", &[ADMIN_TEST_USER]) - .await - .unwrap(); + login_put_admin_idm_admins(&rsclient).await; let default_classnames: HashSet = [ "access_control_create", @@ -574,16 +342,9 @@ async fn test_default_entries_rbac_admins_schema_entries(rsclient: KanidmClient) // create new accounts (to bootstrap the system). #[kanidmd_testkit::test] async fn test_default_entries_rbac_admins_group_entries(rsclient: KanidmClient) { - rsclient - .auth_simple_password(ADMIN_TEST_USER, ADMIN_TEST_PASSWORD) - .await - .unwrap(); - rsclient - .idm_group_add_members("idm_admins", &[ADMIN_TEST_USER]) - .await - .unwrap(); + login_put_admin_idm_admins(&rsclient).await; - create_user(&rsclient, "test", "test_group").await; + create_user(&rsclient, NOT_ADMIN_TEST_USERNAME, "test_group").await; let default_group_names = [&DEFAULT_HP_GROUP_NAMES[..], &DEFAULT_NOT_HP_GROUP_NAMES[..]].concat(); @@ -594,14 +355,7 @@ async fn test_default_entries_rbac_admins_group_entries(rsclient: KanidmClient) // modify high access accounts as an escalation for security sensitive accounts. #[kanidmd_testkit::test] async fn test_default_entries_rbac_admins_ha_accounts(rsclient: KanidmClient) { - rsclient - .auth_simple_password(ADMIN_TEST_USER, ADMIN_TEST_PASSWORD) - .await - .unwrap(); - rsclient - .idm_group_add_members("idm_admins", &[ADMIN_TEST_USER]) - .await - .unwrap(); + login_put_admin_idm_admins(&rsclient).await; static MAIN_ATTRS: [&str; 3] = ["name", "displayname", "primary_credential"]; test_write_attrs(&rsclient, "idm_admin", &MAIN_ATTRS, true).await; @@ -610,21 +364,23 @@ async fn test_default_entries_rbac_admins_ha_accounts(rsclient: KanidmClient) { // recover from the recycle bin #[kanidmd_testkit::test] async fn test_default_entries_rbac_admins_recycle_accounts(rsclient: KanidmClient) { + login_put_admin_idm_admins(&rsclient).await; + + create_user(&rsclient, NOT_ADMIN_TEST_USERNAME, "test_group").await; + rsclient - .auth_simple_password(ADMIN_TEST_USER, ADMIN_TEST_PASSWORD) + .idm_person_account_delete(NOT_ADMIN_TEST_USERNAME) .await .unwrap(); rsclient - .idm_group_add_members("idm_admins", &[ADMIN_TEST_USER]) + .recycle_bin_revive(NOT_ADMIN_TEST_USERNAME) .await .unwrap(); - create_user(&rsclient, "test", "test_group").await; - - rsclient.idm_person_account_delete("test").await.unwrap(); - rsclient.recycle_bin_revive("test").await.unwrap(); - - let acc = rsclient.idm_person_account_get("test").await.unwrap(); + let acc = rsclient + .idm_person_account_get(NOT_ADMIN_TEST_USERNAME) + .await + .unwrap(); assert!(acc.is_some()); } @@ -633,29 +389,40 @@ async fn test_default_entries_rbac_admins_recycle_accounts(rsclient: KanidmClien // write private or sensitive data of persons, IE legalName #[kanidmd_testkit::test] async fn test_default_entries_rbac_people_managers(rsclient: KanidmClient) { - rsclient - .auth_simple_password(ADMIN_TEST_USER, ADMIN_TEST_PASSWORD) - .await - .unwrap(); - rsclient - .idm_group_add_members("idm_admins", &[ADMIN_TEST_USER]) - .await - .unwrap(); + login_put_admin_idm_admins(&rsclient).await; create_user(&rsclient, "read_people_manager", "idm_people_read_priv").await; - create_user_with_all_attrs(&rsclient, "test", Some("test_group")).await; + create_user_with_all_attrs(&rsclient, NOT_ADMIN_TEST_USERNAME, Some("test_group")).await; static PEOPLE_MANAGER_ATTRS: [&str; 2] = ["legalname", "mail"]; static TECHNICAL_ATTRS: [&str; 3] = ["primary_credential", "radius_secret", "unix_password"]; - test_read_attrs(&rsclient, "test", &PEOPLE_MANAGER_ATTRS, true).await; + test_read_attrs( + &rsclient, + NOT_ADMIN_TEST_USERNAME, + &PEOPLE_MANAGER_ATTRS, + true, + ) + .await; login_account(&rsclient, "read_people_manager").await; - test_read_attrs(&rsclient, "test", &PEOPLE_MANAGER_ATTRS, true).await; - test_read_attrs(&rsclient, "test", &TECHNICAL_ATTRS, false).await; - test_write_attrs(&rsclient, "test", &PEOPLE_MANAGER_ATTRS, false).await; - test_write_attrs(&rsclient, "test", &TECHNICAL_ATTRS, false).await; + test_read_attrs( + &rsclient, + NOT_ADMIN_TEST_USERNAME, + &PEOPLE_MANAGER_ATTRS, + true, + ) + .await; + test_read_attrs(&rsclient, NOT_ADMIN_TEST_USERNAME, &TECHNICAL_ATTRS, false).await; + test_write_attrs( + &rsclient, + NOT_ADMIN_TEST_USERNAME, + &PEOPLE_MANAGER_ATTRS, + false, + ) + .await; + test_write_attrs(&rsclient, NOT_ADMIN_TEST_USERNAME, &TECHNICAL_ATTRS, false).await; let _ = rsclient.logout(); rsclient @@ -665,26 +432,31 @@ async fn test_default_entries_rbac_people_managers(rsclient: KanidmClient) { create_user(&rsclient, "write_people_manager", "idm_people_write_priv").await; login_account(&rsclient, "write_people_manager").await; - test_read_attrs(&rsclient, "test", &PEOPLE_MANAGER_ATTRS, true).await; - test_read_attrs(&rsclient, "test", &TECHNICAL_ATTRS, false).await; - test_write_attrs(&rsclient, "test", &PEOPLE_MANAGER_ATTRS, true).await; - test_write_attrs(&rsclient, "test", &TECHNICAL_ATTRS, false).await; + test_read_attrs( + &rsclient, + NOT_ADMIN_TEST_USERNAME, + &PEOPLE_MANAGER_ATTRS, + true, + ) + .await; + test_read_attrs(&rsclient, NOT_ADMIN_TEST_USERNAME, &TECHNICAL_ATTRS, false).await; + test_write_attrs( + &rsclient, + NOT_ADMIN_TEST_USERNAME, + &PEOPLE_MANAGER_ATTRS, + true, + ) + .await; + test_write_attrs(&rsclient, NOT_ADMIN_TEST_USERNAME, &TECHNICAL_ATTRS, false).await; } // Anonymous Clients + Everyone Else // read memberof, unix attrs, name, displayname, class #[kanidmd_testkit::test] async fn test_default_entries_rbac_anonymous_entry(rsclient: KanidmClient) { - rsclient - .auth_simple_password(ADMIN_TEST_USER, ADMIN_TEST_PASSWORD) - .await - .unwrap(); - rsclient - .idm_group_add_members("idm_admins", &[ADMIN_TEST_USER]) - .await - .unwrap(); + login_put_admin_idm_admins(&rsclient).await; - create_user_with_all_attrs(&rsclient, "test", Some("test_group")).await; + create_user_with_all_attrs(&rsclient, NOT_ADMIN_TEST_USERNAME, Some("test_group")).await; rsclient .idm_group_add_members("test_group", &["anonymous"]) .await @@ -694,9 +466,21 @@ async fn test_default_entries_rbac_anonymous_entry(rsclient: KanidmClient) { let _ = rsclient.logout(); rsclient.auth_anonymous().await.unwrap(); - test_read_attrs(&rsclient, "test", &USER_READABLE_ATTRS, true).await; + test_read_attrs( + &rsclient, + NOT_ADMIN_TEST_USERNAME, + &USER_READABLE_ATTRS, + true, + ) + .await; test_read_attrs(&rsclient, "anonymous", &USER_READABLE_ATTRS, true).await; - test_write_attrs(&rsclient, "test", &SELF_WRITEABLE_ATTRS, false).await; + test_write_attrs( + &rsclient, + NOT_ADMIN_TEST_USERNAME, + &SELF_WRITEABLE_ATTRS, + false, + ) + .await; test_write_attrs(&rsclient, "anonymous", &SELF_WRITEABLE_ATTRS, false).await; } @@ -705,50 +489,57 @@ async fn test_default_entries_rbac_anonymous_entry(rsclient: KanidmClient) { // Read other needed attributes to fulfil radius functions. #[kanidmd_testkit::test] async fn test_default_entries_rbac_radius_servers(rsclient: KanidmClient) { - rsclient - .auth_simple_password(ADMIN_TEST_USER, ADMIN_TEST_PASSWORD) - .await - .unwrap(); - rsclient - .idm_group_add_members("idm_admins", &[ADMIN_TEST_USER]) - .await - .unwrap(); + login_put_admin_idm_admins(&rsclient).await; create_user(&rsclient, "radius_server", "idm_radius_servers").await; - create_user_with_all_attrs(&rsclient, "test", Some("test_group")).await; + create_user_with_all_attrs(&rsclient, NOT_ADMIN_TEST_USERNAME, Some("test_group")).await; login_account(&rsclient, "radius_server").await; static RADIUS_NECESSARY_ATTRS: [&str; 4] = ["name", "spn", "uuid", "radius_secret"]; - test_read_attrs(&rsclient, "test", &USER_READABLE_ATTRS, true).await; - test_read_attrs(&rsclient, "test", &RADIUS_NECESSARY_ATTRS, true).await; - test_write_attrs(&rsclient, "test", &RADIUS_NECESSARY_ATTRS, false).await; + test_read_attrs( + &rsclient, + NOT_ADMIN_TEST_USERNAME, + &USER_READABLE_ATTRS, + true, + ) + .await; + test_read_attrs( + &rsclient, + NOT_ADMIN_TEST_USERNAME, + &RADIUS_NECESSARY_ATTRS, + true, + ) + .await; + test_write_attrs( + &rsclient, + NOT_ADMIN_TEST_USERNAME, + &RADIUS_NECESSARY_ATTRS, + false, + ) + .await; } #[kanidmd_testkit::test] async fn test_self_write_mail_priv_people(rsclient: KanidmClient) { - rsclient - .auth_simple_password(ADMIN_TEST_USER, ADMIN_TEST_PASSWORD) - .await - .unwrap(); - rsclient - .idm_group_add_members("idm_admins", &[ADMIN_TEST_USER]) - .await - .unwrap(); + login_put_admin_idm_admins(&rsclient).await; // test and other, each can write to themselves, but not each other - create_user_with_all_attrs(&rsclient, "test", None).await; + create_user_with_all_attrs(&rsclient, NOT_ADMIN_TEST_USERNAME, None).await; create_user_with_all_attrs(&rsclient, "other", None).await; rsclient - .idm_group_add_members("idm_people_self_write_mail_priv", &["other", "test"]) + .idm_group_add_members( + "idm_people_self_write_mail_priv", + &["other", NOT_ADMIN_TEST_USERNAME], + ) .await .unwrap(); // a non-person, they can't write to themselves even with the priv create_user(&rsclient, "nonperson", "nonperson_group").await; - login_account(&rsclient, "test").await; + login_account(&rsclient, NOT_ADMIN_TEST_USERNAME).await; // can write to own mail - test_write_attrs(&rsclient, "test", &["mail"], true).await; + test_write_attrs(&rsclient, NOT_ADMIN_TEST_USERNAME, &["mail"], true).await; // not someone elses test_write_attrs(&rsclient, "other", &["mail"], false).await; diff --git a/server/testkit/tests/integration.rs b/server/testkit/tests/integration.rs new file mode 100644 index 000000000..c4ff9a823 --- /dev/null +++ b/server/testkit/tests/integration.rs @@ -0,0 +1,191 @@ +//! Integration tests using browser automation + +/// Tries to handle closing the webdriver session if there's an error +#[allow(unused_macros)] +macro_rules! handle_error { + ($client:ident, $e:expr, $msg:expr) => { + match $e { + Ok(e) => e, + Err(e) => { + $client.close().await.unwrap(); + panic!("{:?}: {:?}", $msg, e); + } + } + }; +} + +/// Tries to get the webdriver client, trying the default chromedriver port if the default selenium port doesn't work +#[allow(dead_code)] +#[cfg(feature = "webdriver")] +async fn get_webdriver_client() -> fantoccini::Client { + use fantoccini::wd::Capabilities; + use serde_json::json; + + // check if the env var "CI" is set + let in_ci = match std::env::var("CI") { + Ok(_) => true, + Err(_) => false, + }; + if !in_ci { + match fantoccini::ClientBuilder::native() + .connect("http://localhost:4444") + .await + { + Ok(val) => val, + Err(_) => { + // trying the default chromedriver port + eprintln!("Couldn't connect on 4444, trying 9515"); + fantoccini::ClientBuilder::new(hyper_tls::HttpsConnector::new()).connect("http://localhost:9515") + .await + .unwrap() + } + } + } else { + println!("In CI setting headless and assuming Chrome"); + let cap = json!({ + "goog:chromeOptions" : { + "args" : ["--headless", "--no-sandbox", "--disable-gpu", "--disable-dev-shm-usage", "--window-size=1280,1024"] + } + }); + let cap: Capabilities = serde_json::from_value(cap).unwrap(); + fantoccini::ClientBuilder::new(hyper_tls::HttpsConnector::new()).capabilities(cap).connect("http://localhost:9515").await.unwrap() + + } +} + +#[kanidmd_testkit::test] +#[cfg(feature = "webdriver")] +async fn test_webdriver_user_login(rsclient: kanidm_client::KanidmClient) { + if !cfg!(feature = "webdriver") { + println!("Skipping test as webdriver feature is not enabled!"); + return; + } + + use fantoccini::elements::Element; + use fantoccini::Locator; + use kanidmd_testkit::*; + use std::time::Duration; + login_put_admin_idm_admins(&rsclient).await; + + create_user_with_all_attrs( + &rsclient, + NOT_ADMIN_TEST_USERNAME, + Some(NOT_ADMIN_TEST_PASSWORD), + ) + .await; + + let c = get_webdriver_client().await; + + handle_error!(c, c.goto(rsclient.get_url()).await, "Couldn't get URL"); + + println!("Waiting for page to load"); + let mut wait_attempts = 0; + while wait_attempts < 10 { + tokio::time::sleep(tokio::time::Duration::from_micros(200)).await; + c.wait(); + + if c.find(Locator::Id("username")).await.is_ok() { + break; + } + wait_attempts += 1; + if wait_attempts > 10 { + panic!("Couldn't find username field after 10 attempts!"); + } + } + + let id = handle_error!( + c, + c.find(Locator::Id("username")).await, + "Couldn't find input id=username" + ); + handle_error!(c, id.click().await, "Couldn't click the username input?"); + + handle_error!( + c, + id.send_keys(NOT_ADMIN_TEST_USERNAME).await, + "Couldn't type the password?" + ); + + let username_form = handle_error!( + c, + c.form(Locator::Id("login")).await, + "Coudln't find login form" + ); + handle_error!( + c, + username_form.submit().await, + "Couldn't submit username-login form" + ); + c.wait(); + tokio::time::sleep(Duration::from_millis(300)).await; + + let password_form = handle_error!( + c, + c.form(Locator::Id("login")).await, + "Coudln't find login form" + ); + let id = handle_error!( + c, + c.find(Locator::Id("password")).await, + "Couldn't find input id=password" + ); + handle_error!(c, id.click().await, "Couldn't click the username input?"); + + handle_error!( + c, + id.send_keys(NOT_ADMIN_TEST_PASSWORD).await, + "Couldn't type the password?" + ); + handle_error!( + c, + password_form.submit().await, + "Couldn't submit password-login form" + ); + c.wait(); + + // try clicking the nav links + let mut navlinks: Vec = vec![]; + let mut navlinks_attempts = 0; + while navlinks.is_empty() { + navlinks = handle_error!( + c, + c.find_all(Locator::Css(".nav-link")).await, + "Couldn't find nav-link CSS items" + ); + navlinks_attempts += 1; + tokio::time::sleep(Duration::from_millis(200)).await; + if navlinks_attempts > 10 { + panic!("Couldn't find navlinks after 2 seconds!"); + } + } + println!("Found navlinks: {:?}", navlinks); + + for link in navlinks { + println!("Clicking {:?}", link.text().await); + handle_error!(c, link.click().await, &format!("Couldn't click {:?}", link)); + if let Ok(text) = link.text().await { + if text.to_lowercase() == "sign out" { + println!("looking for the sign out modal to click the cancel button..."); + tokio::time::sleep(Duration::from_secs(1)).await; + println!("Found the sign out modal, clicking the cancel button"); + // find the cancel button and click it + let buttons = handle_error!( + c, + c.find_all(Locator::Css(".btn")).await, + "Couldn't find CSS 'btn' items" + ); + println!("Found the following buttons: {:?}", buttons); + for button in buttons { + if let Ok(text) = button.text().await { + if text == "Cancel" { + println!("Found the sign out cancel button, clicking it"); + handle_error!(c, button.click().await, "Couldn't click cancel button"); + break; + } + } + } + } + } + } + // tokio::time::sleep(Duration::from_millis(3000)).await; +} diff --git a/server/web_ui/Cargo.toml b/server/web_ui/Cargo.toml index c47f5c562..d07144f40 100644 --- a/server/web_ui/Cargo.toml +++ b/server/web_ui/Cargo.toml @@ -14,21 +14,11 @@ license = "MPL-2.0" homepage = "https://github.com/kanidm/kanidm/" repository = "https://github.com/kanidm/kanidm/" -# version = { workspace = true } -# authors = { workspace = true } -# rust-version = { workspace = true } -# edition = { workspace = true } -# license = { workspace = true } -# homepage = { workspace = true } -# repository = { workspace = true } - - [lib] crate-type = ["cdylib", "rlib"] [dependencies] compact_jwt = { workspace = true } -# gloo = "^0.8.0" gloo = { workspace = true } js-sys = { workspace = true } kanidm_proto = { workspace = true, features = ["wasm"] } @@ -45,7 +35,6 @@ yew = { workspace = true, features = ["csr"] } yew-router = { workspace = true } time = { workspace = true } - [dependencies.web-sys] workspace = true features = [ @@ -76,4 +65,3 @@ features = [ "Response", "Window", ] - diff --git a/server/web_ui/build_wasm.sh b/server/web_ui/build_wasm.sh index de411cee3..5683f6b6e 100755 --- a/server/web_ui/build_wasm.sh +++ b/server/web_ui/build_wasm.sh @@ -1,5 +1,6 @@ #!/bin/sh +set -e # This builds the assets for the Web UI, defaulting to a release build. if [ ! -f build_wasm.sh ]; then @@ -22,7 +23,7 @@ if [ -z "$(which wasm-pack)" ]; then fi if [ "$(find ./pkg/ -name 'kanidmd*' | wc -l)" -gt 0 ]; then - echo "Cleaning up" + echo "Cleaning up WASM files before build..." rm pkg/kanidmd* fi @@ -35,3 +36,7 @@ touch ./pkg/ANYTHING_HERE_WILL_BE_DELETED_ADD_TO_SRC && \ cp ../../README.md ./pkg/ cp ../../LICENSE.md ./pkg/ rm ./pkg/.gitignore + +# updates the brotli-compressed files +echo "brotli-compressing the WASM file..." +find ./pkg -name '*.wasm' -exec ./find_best_brotli.sh "{}" \; || exit 1 diff --git a/server/web_ui/find_best_brotli.sh b/server/web_ui/find_best_brotli.sh new file mode 100755 index 000000000..884c21d89 --- /dev/null +++ b/server/web_ui/find_best_brotli.sh @@ -0,0 +1,63 @@ +#!/bin/bash + +# check brotli's installed +if ! command -v brotli &> /dev/null +then + echo "brotli tool could not be found, please make sure you have it in your path!" + exit 1 +fi + +# Exit if no argument is provided +if [ $# -eq 0 ]; then + echo "No filename provided" + exit 1 +fi + +filename=$1 +echo "Compressing $1" + +# Exit if the file doesn't exist +if [ ! -f "${filename}" ]; then + echo "File ${filename} not found" + exit 1 +fi + +original_size_kb=$(du -k "${filename}" | cut -f1) + +tmpfile=$(mktemp) + +for num in {10..24} +do + { + size=$(brotli --lgwin="${num}" -c "${filename}" | wc -c) + echo "${num} ${size}" >> "${tmpfile}" + } & +done + +# Wait for all background jobs to finish +wait + +# Process results +{ + read -r min_num min_size + while read -r num size; do + if (( size < min_size )); then + min_size=$size + min_num=$num + fi + done + + echo "Original size was ${original_size_kb}KB" + echo "Smallest compressed size was ${min_size} bytes for NUM=${min_num}" +} < "${tmpfile}" + +# Clean up +rm "${tmpfile}" + +# Use the smallest NUM value found in the test command +brotli --force --lgwin="${min_num}" "${filename}" -o "${filename}.br" + +# find the difference in the file sizes +compressed_size_kb=$(du -k "${filename}.br" | cut -f1) +size_difference=$(bc <<< "${original_size_kb} - ${compressed_size_kb}") +echo "The compressed file is ${size_difference}KB smaller than the original file" diff --git a/server/web_ui/pkg/kanidmd_web_ui.js b/server/web_ui/pkg/kanidmd_web_ui.js index 0cdb26c05..ec8c4ca16 100644 --- a/server/web_ui/pkg/kanidmd_web_ui.js +++ b/server/web_ui/pkg/kanidmd_web_ui.js @@ -234,7 +234,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__h6181404b47c1b27d(arg0, arg1, addBorrowedObject(arg2)); + wasm._dyn_core__ops__function__FnMut___A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__h91fc06edd1d61942(arg0, arg1, addBorrowedObject(arg2)); } finally { heap[stack_pointer++] = undefined; } @@ -242,14 +242,14 @@ function __wbg_adapter_48(arg0, arg1, arg2) { function __wbg_adapter_51(arg0, arg1, arg2) { try { - wasm._dyn_core__ops__function__FnMut___A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__h0469109c0dc279df(arg0, arg1, addBorrowedObject(arg2)); + wasm._dyn_core__ops__function__FnMut___A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__h69b5a7e71157cf78(arg0, arg1, addBorrowedObject(arg2)); } finally { heap[stack_pointer++] = undefined; } } function __wbg_adapter_54(arg0, arg1, arg2) { - wasm._dyn_core__ops__function__FnMut__A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__h23ae592972fec7fc(arg0, arg1, addHeapObject(arg2)); + wasm._dyn_core__ops__function__FnMut__A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__h2d5c8ecfb4968ae0(arg0, arg1, addHeapObject(arg2)); } /** @@ -463,10 +463,6 @@ function __wbg_get_imports() { const ret = getObject(arg0) == getObject(arg1); return ret; }; - imports.wbg.__wbg_getwithrefkey_15c62c2b8546208d = function(arg0, arg1) { - const ret = getObject(arg0)[getObject(arg1)]; - return addHeapObject(ret); - }; imports.wbg.__wbg_set_20cbc34131e76824 = function(arg0, arg1, arg2) { getObject(arg0)[takeObject(arg1)] = takeObject(arg2); }; @@ -1125,16 +1121,16 @@ function __wbg_get_imports() { const ret = wasm.memory; return addHeapObject(ret); }; - imports.wbg.__wbindgen_closure_wrapper2575 = function(arg0, arg1, arg2) { + imports.wbg.__wbindgen_closure_wrapper2571 = function(arg0, arg1, arg2) { const ret = makeMutClosure(arg0, arg1, 1196, __wbg_adapter_48); return addHeapObject(ret); }; - imports.wbg.__wbindgen_closure_wrapper3416 = function(arg0, arg1, arg2) { - const ret = makeMutClosure(arg0, arg1, 1503, __wbg_adapter_51); + imports.wbg.__wbindgen_closure_wrapper3389 = function(arg0, arg1, arg2) { + const ret = makeMutClosure(arg0, arg1, 1499, __wbg_adapter_51); return addHeapObject(ret); }; - imports.wbg.__wbindgen_closure_wrapper4520 = function(arg0, arg1, arg2) { - const ret = makeMutClosure(arg0, arg1, 1577, __wbg_adapter_54); + imports.wbg.__wbindgen_closure_wrapper4500 = function(arg0, arg1, arg2) { + const ret = makeMutClosure(arg0, arg1, 1574, __wbg_adapter_54); return addHeapObject(ret); }; diff --git a/server/web_ui/pkg/kanidmd_web_ui_bg.wasm b/server/web_ui/pkg/kanidmd_web_ui_bg.wasm index 175596e58..bf8be9ef8 100644 Binary files a/server/web_ui/pkg/kanidmd_web_ui_bg.wasm and b/server/web_ui/pkg/kanidmd_web_ui_bg.wasm differ diff --git a/server/web_ui/pkg/kanidmd_web_ui_bg.wasm.br b/server/web_ui/pkg/kanidmd_web_ui_bg.wasm.br new file mode 100644 index 000000000..59b407c02 Binary files /dev/null and b/server/web_ui/pkg/kanidmd_web_ui_bg.wasm.br differ diff --git a/server/web_ui/src/login/mod.rs b/server/web_ui/src/login/mod.rs index 61ac758f4..002181aaf 100644 --- a/server/web_ui/src/login/mod.rs +++ b/server/web_ui/src/login/mod.rs @@ -244,7 +244,7 @@ impl LoginApp { <>
-
Init - prevent_default()".to_string()); @@ -295,7 +295,7 @@ impl LoginApp { <>

{ msg }

- Init - prevent_default()".to_string()); @@ -359,7 +359,7 @@ impl LoginApp { <>
- Password - prevent_default()".to_string()); e.prevent_default(); @@ -397,7 +397,7 @@ impl LoginApp { - BackupCode - prevent_default()".to_string()); e.prevent_default(); @@ -429,7 +429,7 @@ impl LoginApp { <>
- Totp - prevent_default()".to_string()); e.prevent_default(); diff --git a/server/web_ui/tests/test.rs b/server/web_ui/tests/test.rs index a3f971125..ee35605f8 100644 --- a/server/web_ui/tests/test.rs +++ b/server/web_ui/tests/test.rs @@ -1,17 +1,19 @@ -/// Test harnesses for WASM things. -/// -/// Here be crabs with troubling pasts. -/// -/// Run this on a mac with Safari using the following command: -/// -/// ```shell -/// wasm-pack test --safari -/// ``` +//! Test harnesses for WASM things. +//! +//! Here be crabs with troubling pasts. +//! +//! Run this on a mac with Safari using the following command: +//! +//! ```shell +//! wasm-pack test --chrome --headless +//!``` +//! + use wasm_bindgen_test::*; wasm_bindgen_test_configure!(run_in_browser); #[wasm_bindgen_test] -fn pass() { +fn if_this_fails_then_oh_no() { assert_eq!(1, 1); }