mirror of
https://github.com/kanidm/kanidm.git
synced 2025-02-23 04:27:02 +01:00
headless webdriver testing, starting on brotli feature (#1844)
* headless chromedriver testing * updating build scripts
This commit is contained in:
parent
43d5577895
commit
749522418c
14
.github/workflows/kanidm_book.yml
vendored
14
.github/workflows/kanidm_book.yml
vendored
|
@ -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
|
||||
|
|
12
.github/workflows/wasm_test.yml
vendored
12
.github/workflows/wasm_test.yml
vendored
|
@ -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"
|
||||
|
|
7
.yamllint
Normal file
7
.yamllint
Normal file
|
@ -0,0 +1,7 @@
|
|||
extends: default
|
||||
|
||||
rules:
|
||||
line-length:
|
||||
max: 120
|
||||
level: warning
|
||||
|
49
Cargo.lock
generated
49
Cargo.lock
generated
|
@ -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"
|
||||
|
|
|
@ -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<ServerState>,
|
||||
Extension(kopid): Extension<KOpId>,
|
||||
|
@ -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<ServerState>,
|
||||
Extension(kopid): Extension<KOpId>,
|
||||
|
|
|
@ -1408,9 +1408,10 @@ impl<'a> IdmServerProxyReadTransaction<'a> {
|
|||
};
|
||||
|
||||
if consent_previously_granted {
|
||||
let pretty_scopes: Vec<String> = 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
|
||||
|
|
|
@ -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"] }
|
||||
|
|
|
@ -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<bool> {
|
||||
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")
|
||||
}
|
||||
|
|
|
@ -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<bool> {
|
||||
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<String> = [
|
||||
"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;
|
||||
|
||||
|
|
191
server/testkit/tests/integration.rs
Normal file
191
server/testkit/tests/integration.rs
Normal file
|
@ -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<Element> = 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;
|
||||
}
|
|
@ -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",
|
||||
]
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
63
server/web_ui/find_best_brotli.sh
Executable file
63
server/web_ui/find_best_brotli.sh
Executable file
|
@ -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"
|
|
@ -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);
|
||||
};
|
||||
|
||||
|
|
Binary file not shown.
BIN
server/web_ui/pkg/kanidmd_web_ui_bg.wasm.br
Normal file
BIN
server/web_ui/pkg/kanidmd_web_ui_bg.wasm.br
Normal file
Binary file not shown.
|
@ -244,7 +244,7 @@ impl LoginApp {
|
|||
<>
|
||||
<div class="container">
|
||||
<label for="username" class="form-label">{ "Username" }</label>
|
||||
<form
|
||||
<form id="login"
|
||||
onsubmit={ ctx.link().callback(|e: SubmitEvent| {
|
||||
#[cfg(debug_assertions)]
|
||||
console::debug!("login::view_state -> Init - prevent_default()".to_string());
|
||||
|
@ -295,7 +295,7 @@ impl LoginApp {
|
|||
<>
|
||||
<div class="container">
|
||||
<p>{ msg }</p>
|
||||
<form
|
||||
<form id="login"
|
||||
onsubmit={ ctx.link().callback(|e: SubmitEvent| {
|
||||
#[cfg(debug_assertions)]
|
||||
console::debug!("login::view_state -> Init - prevent_default()".to_string());
|
||||
|
@ -359,7 +359,7 @@ impl LoginApp {
|
|||
<>
|
||||
<div class="container">
|
||||
<label for="password" class="form-label">{ "Password" }</label>
|
||||
<form
|
||||
<form id="login"
|
||||
onsubmit={ ctx.link().callback(|e: SubmitEvent| {
|
||||
console::debug!("login::view_state -> Password - prevent_default()".to_string());
|
||||
e.prevent_default();
|
||||
|
@ -397,7 +397,7 @@ impl LoginApp {
|
|||
<label for="backup_code" class="form-label">
|
||||
{"Backup Code"}
|
||||
</label>
|
||||
<form
|
||||
<form id="login"
|
||||
onsubmit={ ctx.link().callback(|e: SubmitEvent| {
|
||||
console::debug!("login::view_state -> BackupCode - prevent_default()".to_string());
|
||||
e.prevent_default();
|
||||
|
@ -429,7 +429,7 @@ impl LoginApp {
|
|||
<>
|
||||
<div class="container">
|
||||
<label for="totp" class="form-label">{"TOTP"}</label>
|
||||
<form
|
||||
<form id="login"
|
||||
onsubmit={ ctx.link().callback(|e: SubmitEvent| {
|
||||
console::debug!("login::view_state -> Totp - prevent_default()".to_string());
|
||||
e.prevent_default();
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue