#![cfg(feature = "scim")]
use std::sync::Arc;
use arcly_http_identity::federation::scim::{ScimProvisioner, ScimUser};
use arcly_http_identity::model::AccountStatus;
use arcly_http_identity::store::{MemoryStore, UserStore};
fn scim_user(email: &str, active: bool) -> ScimUser {
ScimUser {
schemas: vec!["urn:ietf:params:scim:schemas:core:2.0:User".into()],
id: None,
user_name: email.into(),
name: None,
emails: vec![arcly_http_identity::federation::scim::ScimEmail {
value: email.into(),
primary: true,
}],
active,
external_id: Some("okta-123".into()),
}
}
#[tokio::test]
async fn scim_provision_then_deprovision() {
let store = Arc::new(MemoryStore::new());
let scim = ScimProvisioner::new(store.clone(), Some("acme".into()));
let created = scim.create(&scim_user("sso@acme.com", true)).await.unwrap();
let id = created.id.clone().unwrap();
let user = store.find_by_id(&id).await.unwrap().unwrap();
assert_eq!(user.status, AccountStatus::Active);
assert!(user.email_verified); assert_eq!(user.tenant.as_deref(), Some("acme"));
assert!(scim.create(&scim_user("sso@acme.com", true)).await.is_err());
let patched = scim.set_active(&id, false).await.unwrap();
assert!(!patched.active);
assert_eq!(
store.find_by_id(&id).await.unwrap().unwrap().status,
AccountStatus::Suspended
);
scim.delete(&id).await.unwrap();
assert_eq!(
store.find_by_id(&id).await.unwrap().unwrap().status,
AccountStatus::Deleted
);
}
#[tokio::test]
async fn back_channel_logout_revokes_refresh_tokens() {
use arcly_http_identity::error::Result as IdResult;
use arcly_http_identity::federation::{BackChannelLogout, IdTokenVerifier, VerifiedIdToken};
use arcly_http_identity::session::{MemoryRefreshStore, RefreshStore};
struct StubVerifier;
#[async_trait::async_trait]
impl IdTokenVerifier for StubVerifier {
async fn verify(&self, token: &str, _nonce: Option<&str>) -> IdResult<VerifiedIdToken> {
let mut extra = serde_json::Map::new();
extra.insert(
"events".into(),
serde_json::json!({
"http://schemas.openid.net/event/backchannel-logout": {}
}),
);
Ok(VerifiedIdToken {
iss: "idp".into(),
sub: token.to_owned(),
aud: "app".into(),
email: None,
email_verified: false,
name: None,
nonce: None,
extra,
})
}
}
let refresh = Arc::new(MemoryRefreshStore::new());
refresh.save("jti-1", "user-42", 3600).await.unwrap();
refresh.save("jti-2", "user-42", 3600).await.unwrap();
let slo = BackChannelLogout::new(Arc::new(StubVerifier), refresh.clone());
let sub = slo.handle("user-42").await.unwrap();
assert_eq!(sub, "user-42");
assert!(refresh.take("jti-1").await.unwrap().is_none());
assert!(refresh.take("jti-2").await.unwrap().is_none());
}