Import upstream v0.16.22, stripped

Upstream commit: 474dd0229cb20cf513036619781ed97bd8073c3f
Enterprise-only files removed or emptied: 63
Enterprise-only snippets removed: 117 in 50 files
Dangling module declarations removed: 5
Cargo edits turning enterprise off: 14
Verification: clean
Enterprise feature gates left for rebuilt features: 19 in 18 files

Produced by tools/fork/strip.py. The full report is in docs/fork/strip-reports/ on main.
This commit is contained in:
2026-09-18 10:21:56 -07:00
commit 7dae9b29fd
1650 changed files with 485521 additions and 0 deletions
+362
View File
@@ -0,0 +1,362 @@
/*
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/
use crate::{
scim::{SCIM_DOMAIN, ScimClient, ScimTest, api_key, group_body, patch_body, query, user_body},
utils::{account::Account, server::TestServer},
};
use registry::{
schema::{
enums::{Permission, StorageQuota},
prelude::{ObjectType, Property},
structs::{
self, Action, CertificateManagement, DkimManagement, DnsManagement, Domain,
PasswordCredential, Permissions, PermissionsList, Tenant, UserAccount,
},
},
types::{EnumImpl, list::List, map::Map},
};
use scim_proto::{MESSAGE_BULK_REQUEST, SCHEMA_USER};
use serde_json::json;
use types::id::Id;
const TENANT_DOMAIN: &str = "acme.example.com";
const TENANT_PRINCIPAL: &str = "[email protected]";
const TENANT_SECRET: &str = "these_pretzels_are_making_me_thirsty";
pub async fn test(test: &TestServer, scim: &ScimTest) {
println!("Running SCIM tenant isolation tests...");
let fixture = TenantFixture::new(test).await;
a_tenant_client_only_sees_its_own_tenant(&fixture, scim).await;
a_tenant_client_cannot_reach_another_tenant(&fixture, scim).await;
a_tenant_client_cannot_provision_outside_its_domains(&fixture).await;
a_tenant_client_provisions_inside_its_own_domain(&fixture).await;
fixture.cleanup(test).await;
}
struct TenantFixture {
client: ScimClient,
tenant_id: Id,
domain_id: Id,
principal_id: Id,
resident_id: Id,
}
impl TenantFixture {
async fn new(test: &TestServer) -> Self {
let admin = test.account("admin");
let tenant_id = admin
.registry_create_object(Tenant {
name: "acme".to_string(),
permissions: Permissions::Merge(PermissionsList {
disabled_permissions: Default::default(),
enabled_permissions: Map::new(vec![
Permission::ScimAccess,
Permission::UnlimitedRequests,
]),
}),
..Default::default()
})
.await;
let domain_id = admin
.registry_create_object(Domain {
is_enabled: true,
name: TENANT_DOMAIN.to_string(),
certificate_management: CertificateManagement::Manual,
dns_management: DnsManagement::Manual,
dkim_management: DkimManagement::Manual,
member_tenant_id: Some(tenant_id),
allow_scim_provisioning: true,
..Default::default()
})
.await;
let principal_id = create_account(
admin,
"scim-svc",
domain_id,
tenant_id,
"Tenant SCIM Service Principal",
vec![
Permission::ScimAccess,
Permission::SysAccountGet,
Permission::SysAccountCreate,
Permission::SysAccountUpdate,
Permission::SysAccountDestroy,
Permission::UnlimitedRequests,
],
)
.await;
let resident_id = create_account(
admin,
"resident",
domain_id,
tenant_id,
"Tenant Resident",
vec![],
)
.await;
admin
.registry_update_object(
ObjectType::Account,
principal_id,
json!({ Property::Quotas: { StorageQuota::MaxApiKeys.as_str(): 20 } }),
)
.await;
admin.registry_create_object(Action::InvalidateCaches).await;
let mut principal = Account::new(TENANT_PRINCIPAL, TENANT_SECRET, &[], "", principal_id);
principal.http_listener_port = crate::scim::HTTP_PORT;
let token = api_key(admin, &principal, json!({ "@type": "Inherit" })).await;
TenantFixture {
client: ScimClient::bearer(&token),
tenant_id,
domain_id,
principal_id,
resident_id,
}
}
async fn cleanup(&self, test: &TestServer) {
let admin = test.account("admin");
for id in [self.resident_id, self.principal_id] {
admin.registry_destroy(ObjectType::Account, [id]).await;
}
admin
.registry_destroy(ObjectType::Domain, [self.domain_id])
.await;
admin
.registry_destroy(ObjectType::Tenant, [self.tenant_id])
.await;
admin.registry_create_object(Action::InvalidateCaches).await;
}
}
async fn a_tenant_client_only_sees_its_own_tenant(fixture: &TenantFixture, scim: &ScimTest) {
let outsider = scim.create_user("[email protected]").await;
let listing = fixture.client.get("/Users?count=200").await;
listing.assert_status(200);
listing.assert_contains_id(&fixture.resident_id.to_string());
listing.assert_contains_id(&fixture.principal_id.to_string());
listing.assert_lacks_id(&outsider);
assert_eq!(
listing.total_results(),
2,
"A tenant client must see only its own accounts: {}",
listing.body
);
for filter in [
"userName eq \"[email protected]\"",
&format!("id eq \"{outsider}\""),
"emails eq \"[email protected]\"",
] {
let response = fixture.client.get(&query("/Users", filter)).await;
response.assert_status(200);
assert_eq!(response.total_results(), 0, "{filter}: {}", response.body);
}
let searched = fixture
.client
.post(
"/.search",
json!({
"schemas": ["urn:ietf:params:scim:api:messages:2.0:SearchRequest"],
"count": 200,
}),
)
.await;
searched.assert_status(200);
searched.assert_lacks_id(&outsider);
scim.destroy(&format!("/Users/{outsider}")).await;
}
async fn a_tenant_client_cannot_reach_another_tenant(fixture: &TenantFixture, scim: &ScimTest) {
let outsider = scim.create_user("[email protected]").await;
let outsider_group = scim.create_group("Untenanted Team").await;
let path = format!("/Users/{outsider}");
fixture.client.get(&path).await.assert_error(404, None);
fixture
.client
.patch(
&path,
patch_body(json!([{"op": "replace", "path": "displayName", "value": "Crossed"}])),
)
.await
.assert_error(404, None);
fixture
.client
.put(&path, user_body("[email protected]"))
.await
.assert_error(404, None);
fixture.client.delete(&path).await.assert_error(404, None);
fixture
.client
.get(&format!("/Groups/{outsider_group}"))
.await
.assert_error(404, None);
fixture
.client
.delete(&format!("/Groups/{outsider_group}"))
.await
.assert_error(404, None);
let bulk = fixture
.client
.post(
"/Bulk",
json!({
"schemas": [MESSAGE_BULK_REQUEST],
"Operations": [
{"method": "DELETE", "path": path},
{"method": "PATCH", "path": path, "data": patch_body(
json!([{"op": "replace", "path": "active", "value": false}])
)},
],
}),
)
.await;
bulk.assert_status(200);
for operation in bulk.json["Operations"].as_array().unwrap() {
assert_eq!(operation["status"], json!("404"), "{operation}");
}
let group = fixture
.client
.post("/Groups", group_body("Tenant Team"))
.await
.assert_status(201)
.id();
fixture
.client
.patch(
&format!("/Groups/{group}"),
patch_body(json!([{"op": "add", "path": "members", "value": [{"value": outsider}]}])),
)
.await
.assert_error(400, Some("invalidValue"));
fixture.client.delete(&format!("/Groups/{group}")).await;
let survivor = scim.client.get(&path).await;
survivor.assert_status(200);
assert!(
survivor.json.get("displayName").is_none(),
"The cross tenant patch was applied: {}",
survivor.body
);
scim.destroy(&format!("/Groups/{outsider_group}")).await;
scim.destroy(&path).await;
}
async fn a_tenant_client_cannot_provision_outside_its_domains(fixture: &TenantFixture) {
let response = fixture
.client
.post("/Users", user_body(&format!("intruder@{SCIM_DOMAIN}")))
.await;
response.assert_error(404, None);
response.assert_detail_contains(SCIM_DOMAIN);
let response = fixture
.client
.post(
"/Users",
json!({
"schemas": [SCHEMA_USER],
"userName": format!("aliased@{TENANT_DOMAIN}"),
"emails": [{"value": format!("intruder@{SCIM_DOMAIN}")}],
}),
)
.await;
response.assert_error(404, None);
response.assert_detail_contains(SCIM_DOMAIN);
}
async fn a_tenant_client_provisions_inside_its_own_domain(fixture: &TenantFixture) {
let response = fixture
.client
.post("/Users", user_body(&format!("newcomer@{TENANT_DOMAIN}")))
.await;
response.assert_status(201);
let id = response.id();
fixture
.client
.get(&format!("/Users/{id}"))
.await
.assert_status(200);
let group = fixture
.client
.post("/Groups", group_body("Acme Team"))
.await
.assert_status(201)
.id();
fixture
.client
.patch(
&format!("/Groups/{group}"),
patch_body(json!([{"op": "add", "path": "members", "value": [{"value": id}]}])),
)
.await
.assert_status(200);
fixture
.client
.patch(
&format!("/Groups/{group}"),
patch_body(json!([{"op": "remove", "path": "members"}])),
)
.await
.assert_status(200);
fixture
.client
.delete(&format!("/Groups/{group}"))
.await
.assert_status(204);
fixture
.client
.delete(&format!("/Users/{id}"))
.await
.assert_status(204);
}
async fn create_account(
admin: &Account,
name: &str,
domain_id: Id,
tenant_id: Id,
description: &str,
permissions: Vec<Permission>,
) -> Id {
admin
.registry_create_object(structs::Account::User(UserAccount {
name: name.to_string(),
domain_id,
member_tenant_id: Some(tenant_id),
description: Some(description.to_string()),
credentials: List::from_iter([structs::Credential::Password(PasswordCredential {
secret: TENANT_SECRET.to_string(),
..Default::default()
})]),
permissions: Permissions::Merge(PermissionsList {
disabled_permissions: Default::default(),
enabled_permissions: Map::new(permissions),
}),
..Default::default()
}))
.await
}