Merge/upstream v0.16.23 #18
@@ -38,7 +38,6 @@ use store::{
|
|||||||
write::{AlignedBytes, Archive, QueueClass, ValueClass},
|
write::{AlignedBytes, Archive, QueueClass, ValueClass},
|
||||||
};
|
};
|
||||||
use trc::{AddContext, SpamEvent};
|
use trc::{AddContext, SpamEvent};
|
||||||
use types::id::Id;
|
|
||||||
use utils::DomainPart;
|
use utils::DomainPart;
|
||||||
|
|
||||||
impl Server {
|
impl Server {
|
||||||
|
|||||||
@@ -1,115 +0,0 @@
|
|||||||
/*
|
|
||||||
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
|
|
||||||
*/
|
|
||||||
|
|
||||||
use crate::{
|
|
||||||
directory::oidc::get_token_for_client,
|
|
||||||
utils::{containers, server::TestServerBuilder},
|
|
||||||
};
|
|
||||||
use base64::{Engine, engine::general_purpose};
|
|
||||||
use registry::{
|
|
||||||
schema::{
|
|
||||||
prelude::{ObjectType, Property},
|
|
||||||
structs::{Action, Directory, OidcDirectory},
|
|
||||||
},
|
|
||||||
types::map::Map,
|
|
||||||
};
|
|
||||||
use serde_json::json;
|
|
||||||
use std::time::Duration;
|
|
||||||
|
|
||||||
const ISSUER: &str = "http://localhost:9080/realms/stalwart";
|
|
||||||
const DOMAIN: &str = "example.org";
|
|
||||||
const ACCOUNT: &str = "[email protected]";
|
|
||||||
const PASSWORD: &str = "this is an OIDC password";
|
|
||||||
|
|
||||||
pub async fn test() {
|
|
||||||
println!("Running OIDC issuer routing tests...");
|
|
||||||
containers::ensure_keycloak().await;
|
|
||||||
let test = TestServerBuilder::new("directory_issuer_test")
|
|
||||||
.await
|
|
||||||
.with_default_listeners()
|
|
||||||
.await
|
|
||||||
.disable_services()
|
|
||||||
.build()
|
|
||||||
.await;
|
|
||||||
|
|
||||||
let admin = test.account("admin");
|
|
||||||
let directory_id = admin
|
|
||||||
.registry_create_object(Directory::Oidc(OidcDirectory {
|
|
||||||
description: "Issuer routing test OIDC directory".to_string(),
|
|
||||||
issuer_url: ISSUER.to_string(),
|
|
||||||
claim_username: "email".to_string(),
|
|
||||||
claim_name: Some("name".to_string()),
|
|
||||||
claim_groups: Some("groups".to_string()),
|
|
||||||
require_audience: Some("stalwart".to_string()),
|
|
||||||
require_scopes: Map::new(vec!["openid".to_string()]),
|
|
||||||
..Default::default()
|
|
||||||
}))
|
|
||||||
.await;
|
|
||||||
let domain_id = admin.find_or_create_domain(DOMAIN).await;
|
|
||||||
admin
|
|
||||||
.registry_update_object(
|
|
||||||
ObjectType::Domain,
|
|
||||||
domain_id,
|
|
||||||
json!({ Property::DirectoryId: directory_id.to_string() }),
|
|
||||||
)
|
|
||||||
.await;
|
|
||||||
admin.reload_settings().await;
|
|
||||||
admin.registry_create_object(Action::InvalidateCaches).await;
|
|
||||||
|
|
||||||
assert!(
|
|
||||||
test.server.get_default_directory().is_none(),
|
|
||||||
"The OIDC directory must be reachable only through the domain for this test to mean anything"
|
|
||||||
);
|
|
||||||
|
|
||||||
let token = get_token_for_client(
|
|
||||||
"stalwart-fallback",
|
|
||||||
"stalwart-fallback-secret",
|
|
||||||
ACCOUNT,
|
|
||||||
PASSWORD,
|
|
||||||
"openid",
|
|
||||||
)
|
|
||||||
.await;
|
|
||||||
let claims = access_token_claims(&token);
|
|
||||||
for claim in ["email", "preferred_username", "upn"] {
|
|
||||||
assert!(
|
|
||||||
claims.get(claim).is_none(),
|
|
||||||
"The access token carries a {claim} claim, so it no longer covers issuer based routing: {claims}"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
assert_eq!(claims["iss"], json!(ISSUER));
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
session_status(&token).await,
|
|
||||||
200,
|
|
||||||
"A bearer token without a username claim did not reach the domain's OIDC directory"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn access_token_claims(token: &str) -> serde_json::Value {
|
|
||||||
let payload = token.split('.').nth(1).expect("The token is not a JWT");
|
|
||||||
|
|
||||||
serde_json::from_slice(
|
|
||||||
&general_purpose::URL_SAFE_NO_PAD
|
|
||||||
.decode(payload)
|
|
||||||
.expect("Failed to decode the token payload"),
|
|
||||||
)
|
|
||||||
.expect("Failed to parse the token claims")
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn session_status(token: &str) -> u16 {
|
|
||||||
reqwest::Client::builder()
|
|
||||||
.timeout(Duration::from_secs(30))
|
|
||||||
.danger_accept_invalid_certs(true)
|
|
||||||
.build()
|
|
||||||
.unwrap()
|
|
||||||
.get("https://127.0.0.1:8899/jmap/session")
|
|
||||||
.bearer_auth(token)
|
|
||||||
.send()
|
|
||||||
.await
|
|
||||||
.expect("Failed to send session request")
|
|
||||||
.status()
|
|
||||||
.as_u16()
|
|
||||||
}
|
|
||||||
@@ -8,7 +8,6 @@
|
|||||||
|
|
||||||
pub mod discovery;
|
pub mod discovery;
|
||||||
pub mod integration;
|
pub mod integration;
|
||||||
pub mod issuer;
|
|
||||||
pub mod ldap;
|
pub mod ldap;
|
||||||
pub mod oidc; // inbuxa: rebuilt from the per-domain directories spec
|
pub mod oidc; // inbuxa: rebuilt from the per-domain directories spec
|
||||||
#[cfg(feature = "sqlite")]
|
#[cfg(feature = "sqlite")]
|
||||||
@@ -24,7 +23,6 @@ pub async fn directory_tests() {
|
|||||||
oidc::test().await;
|
oidc::test().await;
|
||||||
unavailable::test().await;
|
unavailable::test().await;
|
||||||
discovery::test().await;
|
discovery::test().await;
|
||||||
issuer::test().await;
|
|
||||||
#[cfg(feature = "sqlite")]
|
#[cfg(feature = "sqlite")]
|
||||||
sql::test().await;
|
sql::test().await;
|
||||||
synchronization::test().await;
|
synchronization::test().await;
|
||||||
|
|||||||
Reference in New Issue
Block a user