Registry testing - part 4

This commit is contained in:
mdecimus
2026-03-14 20:39:25 +01:00
parent 65a5900e7b
commit 4b5688fd57
28 changed files with 1448 additions and 922 deletions

View File

@@ -437,19 +437,6 @@ pub(crate) async fn account_set(
}
}
if credential.secret != old_credential.secret {
credential.secret = hash_secret(
set.server
.core
.network
.security
.password_hash_algorithm,
std::mem::take(&mut credential.secret),
)
.await
.caused_by(trc::location!())?;
}
if credential.otp_auth != old_credential.otp_auth
&& !verify_otp_auth(
credential.otp_auth.as_deref(),
@@ -463,6 +450,30 @@ pub(crate) async fn account_set(
);
continue 'outer;
}
if credential.secret != old_credential.secret {
if let Err(err) =
set.server.is_secure_password(&credential.secret, &[])
{
set.response.not_updated.append(
id,
SetError::invalid_properties()
.with_property(Property::Secret)
.with_description(err),
);
continue 'outer;
}
credential.secret = hash_secret(
set.server
.core
.network
.security
.password_hash_algorithm,
std::mem::take(&mut credential.secret),
)
.await
.caused_by(trc::location!())?;
}
} else {
set.response.not_updated.append(
id,

View File

@@ -104,22 +104,20 @@ pub(crate) async fn validate_account(
}
if credential.secret != old_credential.secret {
if !credential.secret.is_empty() {
credential.secret = hash_secret(
set.server
.core
.network
.security
.password_hash_algorithm,
std::mem::take(&mut credential.secret),
)
.await
.caused_by(trc::location!())?;
} else {
if let Err(err) =
set.server.is_secure_password(&credential.secret, &[])
{
return Ok(Err(SetError::invalid_properties()
.with_property(Property::Secret)
.with_description("Password cannot be empty.")));
.with_description(err)));
}
credential.secret = hash_secret(
set.server.core.network.security.password_hash_algorithm,
std::mem::take(&mut credential.secret),
)
.await
.caused_by(trc::location!())?;
}
}
(
@@ -240,7 +238,11 @@ async fn validate_credential_creation(
.with_description("Only one password credential is allowed.")));
}
if credential.secret.is_empty() {
if let Err(err) = server.is_secure_password(&credential.secret, &[]) {
Ok(Err(SetError::invalid_properties()
.with_property(Property::Secret)
.with_description(err)))
} else {
credential.secret = hash_secret(
server.core.network.security.password_hash_algorithm,
std::mem::take(&mut credential.secret),
@@ -248,10 +250,6 @@ async fn validate_credential_creation(
.await
.caused_by(trc::location!())?;
Ok(Ok(()))
} else {
Ok(Err(SetError::invalid_properties()
.with_property(Property::Secret)
.with_description("Password cannot be empty.")))
}
}
Credential::AppPassword(_) | Credential::ApiKey(_) => {

View File

@@ -90,8 +90,9 @@ impl RegistrySet for Server {
let has_account_id = (object_flags & OBJ_FILTER_ACCOUNT) != 0;
let is_tenant_filtered =
(object_flags & OBJ_FILTER_TENANT) != 0 && access_token.tenant_id().is_some();
let is_account_filtered =
has_account_id && !access_token.has_permission(Permission::Impersonate);
let can_set_tenant = access_token.tenant_id().is_none();
let can_set_account = access_token.has_permission(Permission::Impersonate);
let is_account_filtered = has_account_id && !can_set_account;
// Build response
let mut response = SetResponse::from_request(&request, self.core.jmap.set_max_objects)?;
@@ -316,77 +317,28 @@ impl RegistrySet for Server {
let is_create = matches!(modification, Modification::Create { .. });
let mut unpatched_properties = VecMap::new();
for (key, value) in value.into_expanded_object() {
let ptr = match (key, &modification) {
(Key::Property(prop), _) => {
JsonPointer::new(vec![JsonPointerItem::Key(Key::Property(prop))])
}
(Key::Borrowed(other), Modification::Update { .. }) => {
JsonPointer::parse(other)
}
(Key::Owned(other), Modification::Update { .. }) => {
JsonPointer::parse(&other)
}
(key, Modification::Create { .. }) => {
set.failed(
modification,
SetError::invalid_properties().with_property(key.into_owned()),
);
continue 'outer;
}
};
if is_tenant_filtered || is_account_filtered {
match ptr.last().and_then(|p| p.as_property_key()) {
Some(Property::MemberTenantId) => {
// SPDX-SnippetBegin
// SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <hello@stalw.art>
// SPDX-License-Identifier: LicenseRef-SEL
#[cfg(feature = "enterprise")]
if access_token.tenant_id().is_some() {
continue;
}
// SPDX-SnippetEnd
#[cfg(not(feature = "enterprise"))]
continue;
}
Some(Property::AccountId) => {
set.failed(
modification,
SetError::forbidden()
.with_property(Property::AccountId)
.with_description("Cannot change server-set property"),
);
continue 'outer;
}
_ => {}
}
}
if is_create {
// Patch object
match new_object
.patch(JsonPointerPatch::new(&ptr).with_create(is_create), value)
{
match new_object.patch(
JsonPointerPatch::new(&JsonPointer::new(vec![]))
.with_create(true)
.with_can_set_tenant(can_set_tenant)
.with_can_set_account(can_set_account),
value,
) {
Ok(MaybeUnpatched::Patched) => {}
Ok(MaybeUnpatched::Unpatched { property, value }) => {
unpatched_properties.append(property, value);
}
Ok(MaybeUnpatched::UnpatchedMany { properties }) => {
if unpatched_properties.is_empty() {
unpatched_properties = properties;
} else {
unpatched_properties.extend(properties);
}
unpatched_properties = properties;
}
Err(err) => {
set.failed(modification, err.into());
continue 'outer;
}
}
}
if is_create {
// Add tenantId for tenant filtered objects
if is_tenant_filtered && let Some(tenant_id) = set.access_token.tenant_id()
{
@@ -397,6 +349,43 @@ impl RegistrySet for Server {
if has_account_id {
new_object.inner.set_account_id(set.account_id.into());
}
} else {
for (key, value) in value.into_expanded_object() {
let ptr = match key {
Key::Property(prop) => {
JsonPointer::new(vec![JsonPointerItem::Key(Key::Property(
prop,
))])
}
Key::Borrowed(other) => JsonPointer::parse(other),
Key::Owned(other) => JsonPointer::parse(&other),
};
// Patch object
match new_object.patch(
JsonPointerPatch::new(&ptr)
.with_create(false)
.with_can_set_tenant(can_set_tenant)
.with_can_set_account(can_set_account),
value,
) {
Ok(MaybeUnpatched::Patched) => {}
Ok(MaybeUnpatched::Unpatched { property, value }) => {
unpatched_properties.append(property, value);
}
Ok(MaybeUnpatched::UnpatchedMany { properties }) => {
if unpatched_properties.is_empty() {
unpatched_properties = properties;
} else {
unpatched_properties.extend(properties);
}
}
Err(err) => {
set.failed(modification, err.into());
continue 'outer;
}
}
}
}
// Validate objects