[Web][Postfix][Dovecot] Mail policies: compare addresses like Postfix, close bypasses via alias forms, temporary aliases, Sieve and cross-domain groups

This commit is contained in:
FreddleSpl0it committed 2026-11-07 17:51:27 +01:00
1 parent 4484cfcd42
commit 46c9ec90b0
22 files changed
+614 -141

No files matched your search

@@ -23,6 +23,7 @@ class PolicyServiceTest extends TestCase
private FakeMailPolicyLookup $lookup;
/** @var Policy[] saved policies */
private array $saved = [];
private int $groupLookups = 0;
protected function setUp(): void
{
@@ -30,7 +31,11 @@ class PolicyServiceTest extends TestCase
$this->repo->method('isDomain')->willReturnCallback(fn(string $domain): bool => in_array($domain, ['firma.de', 'tochter.de'], true));
$this->repo->method('mailboxDomain')->willReturnCallback(fn(string $username): ?string =>
['azubi@firma.de' => 'firma.de', 'chef@firma.de' => 'firma.de', 'andere@tochter.de' => 'tochter.de'][$username] ?? null);
$this->repo->method('groupDomain')->willReturnCallback(fn(int $id): ?string => [7 => 'firma.de', 9 => 'tochter.de'][$id] ?? null);
$this->repo->method('groupDomain')->willReturnCallback(function (int $id): ?string {
$this->groupLookups++;
return [7 => 'firma.de', 9 => 'tochter.de'][$id] ?? null;
});
$this->repo->method('groupsIn')->willReturn([['id' => 7, 'name' => 'Azubis', 'domain' => 'firma.de', 'address' => null]]);
$this->repo->method('save')->willReturnCallback(function (Policy $policy): int {
$policy->id ??= 1;
$this->saved[] = clone $policy;
@@ -133,7 +138,7 @@ class PolicyServiceTest extends TestCase
public function testPermissions(): void
{
$this->repo->method('find')->willReturn($this->stored());
$da = FakeContext::domainAdmin([], ['firma.de'], ['mail_policies' => '1']);
$da = FakeContext::domainAdmin([], ['firma.de'], ['mail_policies' => '1', 'mailbox_groups' => '1']);
$this->assertSame('Test', $this->service($da)->create(['domain' => 'firma.de', 'name' => 'Test'])->name);
$this->assertNotNull($this->service($da)->get(3));
@@ -149,6 +154,58 @@ class PolicyServiceTest extends TestCase
$this->expectUserError('access_denied', fn() => $this->service($readOnly)->delete(3));
}
public function testGroupsNeedTheGroupAcl(): void
{
$this->repo->method('find')->willReturn($this->stored(['groups' => [7]]));
$da = FakeContext::domainAdmin([], ['firma.de'], ['mail_policies' => '1']);
$this->assertSame([], $this->service($da)->groupChoices(['firma.de']));
// the form sends empty group fields: the group entries and assignments stay
$policy = $this->service($da)->update(3, ['send_allow' => 'kunde.example', 'send_allow_groups' => '', 'groups' => '']);
$this->assertSame([['kunde.example', 'group:7'], [7]], [$policy->sendAllow, $policy->groups]);
// the stored values sent back unchanged, as returned by the API
$policy = $this->service($da)->update(3, ['send_allow' => ['partner.de', 'group:7'], 'groups' => ['7']]);
$this->assertSame([['partner.de', 'group:7'], [7]], [$policy->sendAllow, $policy->groups]);
$this->expectUserError('access_denied', fn() => $this->service($da)->update(3, ['groups' => ['7', '8']]));
$this->expectUserError('access_denied', fn() => $this->service($da)->create(['domain' => 'firma.de', 'name' => 'Test', 'groups' => ['7']]));
$this->expectUserError(['mail_policy_entry_invalid', 'group:7'],
fn() => $this->service($da)->create(['domain' => 'firma.de', 'name' => 'Test', 'send_block' => 'group:7']));
$this->assertCount(1, $this->service(FakeContext::domainAdmin([], ['firma.de'], ['mail_policies' => '1', 'mailbox_groups' => '1']))->groupChoices(['firma.de']));
}
public function testGroupEntriesBelongToThePolicyDomain(): void
{
// also for admins: the policy of firma.de cannot name a group of tochter.de
$this->expectUserError(['mail_policy_entry_invalid', 'group:9'],
fn() => $this->service()->create(['domain' => 'firma.de', 'name' => 'Test', 'receive_block' => 'group:9']));
}
public function testListsAreLimitedBeforeAnythingIsLookedUp(): void
{
$groups = array_map(fn(int $i): string => 'group:' . $i, range(1, 501));
$this->expectUserError(['mail_policy_entries_exceeded', 500], fn() => $this->service()->create(['domain' => 'firma.de', 'name' => 'Test', 'send_block' => $groups]));
$this->assertSame(0, $this->groupLookups);
// repeats count once
$this->assertSame(['group:7'], $this->service()->create(['domain' => 'firma.de', 'name' => 'Test', 'send_block' => array_fill(0, 600, 'group:7')])->sendBlock);
$this->expectUserError(['mail_policy_entries_exceeded', 500],
fn() => $this->service()->create(['domain' => 'firma.de', 'name' => 'Test', 'mailboxes' => array_map(fn(int $i): string => "m$i@firma.de", range(1, 501))]));
}
public function testRuleTestShowsNothingOfOtherDomains(): void
{
$this->lookup->mailboxes['andere@tochter.de'] = 'tochter.de';
$this->lookup->mailboxPolicies['andere@tochter.de'] = [$this->stored(['id' => 5, 'domain' => 'tochter.de', 'name' => 'Geheim',
'sendAllow' => [], 'receiveMode' => 'internal', 'rejectText' => 'Interner Text'])];
$this->repo->method('mailboxDomain')->willReturnCallback(fn(string $username): ?string => $this->lookup->mailboxes[$username] ?? null);
$da = FakeContext::domainAdmin([], ['firma.de'], ['mail_policies' => '1']);
// a policy of another domain: neither name nor text
$this->assertSame(['allowed' => 0, 'side' => 'receive', 'policy' => null, 'internal_alias' => 0, 'logged_in' => 1,
'text' => 'This recipient does not accept mail from this sender'], $this->service($da)->test('azubi@firma.de', 'andere@tochter.de', true));
// a sender of another domain is not tested as logged in, so its mailbox and its rules stay hidden
$this->assertSame(0, $this->service($da)->test('andere@tochter.de', 'chef@firma.de', true)['logged_in']);
}
public function testRuleTest(): void
{
$policy = $this->stored(['sendMode' => 'internal', 'sendAllow' => []]);