kanidm/server/testkit/tests/scim_test.rs
2024-10-15 04:29:45 +00:00

254 lines
7.3 KiB
Rust

use compact_jwt::{traits::JwsVerifiable, JwsCompact, JwsEs256Verifier, JwsVerifier};
use kanidm_client::KanidmClient;
use kanidm_proto::internal::ScimSyncToken;
use kanidm_proto::scim_v1::ScimEntryGetQuery;
use kanidmd_lib::prelude::{Attribute, BUILTIN_GROUP_IDM_ADMINS_V1};
use kanidmd_testkit::{ADMIN_TEST_PASSWORD, ADMIN_TEST_USER};
use reqwest::header::HeaderValue;
use std::str::FromStr;
use url::Url;
#[kanidmd_testkit::test]
async fn test_sync_account_lifecycle(rsclient: KanidmClient) {
let a_res = rsclient
.auth_simple_password(ADMIN_TEST_USER, ADMIN_TEST_PASSWORD)
.await;
assert!(a_res.is_ok());
let a_list = rsclient.idm_sync_account_list().await.unwrap();
assert!(a_list.is_empty());
rsclient
.idm_sync_account_create("ipa_sync_account", Some("Demo of a sync account"))
.await
.unwrap();
let a_list = rsclient.idm_sync_account_list().await.unwrap();
assert!(!a_list.is_empty());
let a = rsclient
.idm_sync_account_get("ipa_sync_account")
.await
.unwrap();
let sync_entry = a.expect("No sync account was created?!");
// Shouldn't have a cred portal.
assert!(!sync_entry.attrs.contains_key("sync_credential_portal"));
let url = Url::parse("https://sink.ipa.example.com/reset").unwrap();
// Set our credential portal.
rsclient
.idm_sync_account_set_credential_portal("ipa_sync_account", Some(&url))
.await
.unwrap();
let a = rsclient
.idm_sync_account_get("ipa_sync_account")
.await
.unwrap();
let sync_entry = a.expect("No sync account present?");
// Should have a cred portal.
let url_a = sync_entry
.attrs
.get("sync_credential_portal")
.and_then(|x| x.first());
assert_eq!(
url_a.map(|s| s.as_str()),
Some("https://sink.ipa.example.com/reset")
);
// Also check we can get it direct
let url_b = rsclient
.idm_sync_account_get_credential_portal("ipa_sync_account")
.await
.unwrap();
assert_eq!(url_b, Some(url));
// Get a token
let token = rsclient
.idm_sync_account_generate_token("ipa_sync_account", "token_label")
.await
.expect("Failed to generate token");
let token_unverified = JwsCompact::from_str(&token).expect("Failed to parse apitoken");
let key_id = token_unverified
.kid()
.expect("token does not have a key id");
assert!(token_unverified.get_jwk_pubkey().is_none());
let jwk = rsclient
.get_public_jwk(key_id)
.await
.expect("Unable to get jwk");
let jws_verifier = JwsEs256Verifier::try_from(&jwk).expect("Unable to build verifier");
let token = jws_verifier
.verify(&token_unverified)
.map(|jws| jws.from_json::<ScimSyncToken>().expect("Invalid json"))
.expect("Unable verify token");
println!("{:?}", token);
rsclient
.idm_sync_account_destroy_token("ipa_sync_account")
.await
.expect("Failed to destroy token");
}
#[kanidmd_testkit::test]
async fn test_scim_sync_get(rsclient: KanidmClient) {
// We need to do manual reqwests here.
let mut headers = reqwest::header::HeaderMap::new();
headers.insert(
reqwest::header::AUTHORIZATION,
HeaderValue::from_str(&format!("Bearer {:?}", rsclient.get_token().await)).unwrap(),
);
let client = reqwest::Client::builder()
.danger_accept_invalid_certs(true)
.default_headers(headers)
.build()
.unwrap();
// here we test the /ui/ endpoint which should have the headers
let response = match client.get(rsclient.make_url("/scim/v1/Sync")).send().await {
Ok(value) => value,
Err(error) => {
panic!(
"Failed to query {:?} : {:#?}",
rsclient.make_url("/scim/v1/Sync"),
error
);
}
};
eprintln!("response: {:#?}", response);
assert!(response.status().is_client_error());
// check that the CSP headers are coming back
eprintln!(
"csp headers: {:#?}",
response
.headers()
.get(http::header::CONTENT_SECURITY_POLICY)
);
assert_ne!(
response
.headers()
.get(http::header::CONTENT_SECURITY_POLICY),
None
);
// test that the proper content type comes back
let url = rsclient.make_url("/scim/v1/Sink");
let response = match client.get(url.clone()).send().await {
Ok(value) => value,
Err(error) => {
panic!("Failed to query {:?} : {:#?}", url, error);
}
};
assert!(response.status().is_success());
let content_type = response.headers().get(http::header::CONTENT_TYPE).unwrap();
assert!(content_type.to_str().unwrap().contains("text/html"));
assert!(response.text().await.unwrap().contains("Sink"));
}
#[kanidmd_testkit::test]
async fn test_scim_sync_entry_get(rsclient: KanidmClient) {
let res = rsclient
.auth_simple_password("admin", ADMIN_TEST_PASSWORD)
.await;
assert!(res.is_ok());
// All admin to create persons.
rsclient
.idm_group_add_members(BUILTIN_GROUP_IDM_ADMINS_V1.name, &["admin"])
.await
.unwrap();
rsclient
.idm_person_account_create("demo_account", "Deeeeemo")
.await
.unwrap();
// This will be as raw json, not the strongly typed version the server sees
// internally.
let scim_entry = rsclient
.scim_v1_entry_get("demo_account", None)
.await
.unwrap();
tracing::info!("{:#?}", scim_entry);
assert!(scim_entry.attrs.contains_key(&Attribute::Class));
assert!(scim_entry.attrs.contains_key(&Attribute::Name));
assert_eq!(
scim_entry
.attrs
.get(&Attribute::Name)
.and_then(|v| v.as_str())
.unwrap(),
"demo_account".to_string()
);
// Limit the attributes we want.
let query = ScimEntryGetQuery {
attributes: Some(vec![Attribute::Name]),
};
let scim_entry = rsclient
.scim_v1_entry_get("demo_account", Some(query))
.await
.unwrap();
tracing::info!("{:#?}", scim_entry);
// Should not be present now.
assert!(!scim_entry.attrs.contains_key(&Attribute::Class));
assert!(scim_entry.attrs.contains_key(&Attribute::Name));
// ==========================================
// Same, but via the Person API
let scim_entry = rsclient
.scim_v1_person_get("demo_account", None)
.await
.unwrap();
tracing::info!("{:#?}", scim_entry);
assert!(scim_entry.attrs.contains_key(&Attribute::Class));
assert!(scim_entry.attrs.contains_key(&Attribute::Name));
assert_eq!(
scim_entry
.attrs
.get(&Attribute::Name)
.and_then(|v| v.as_str())
.unwrap(),
"demo_account".to_string()
);
// Limit the attributes we want.
let query = ScimEntryGetQuery {
attributes: Some(vec![Attribute::Name]),
};
let scim_entry = rsclient
.scim_v1_person_get("demo_account", Some(query))
.await
.unwrap();
tracing::info!("{:#?}", scim_entry);
// Should not be present now.
assert!(!scim_entry.attrs.contains_key(&Attribute::Class));
assert!(scim_entry.attrs.contains_key(&Attribute::Name));
}