e223f7d327ae6271abf15552cbb8df72a407a925
6
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
6c6fe91d0c |
A deleted tenant's legacy protocols switch goes with it
CI / build (pull_request) Canceled after 0s
Deleting a tenant now also removes its stored inbuxa:TenantProtocolPolicy,
in the same place the registry's other per-type clean-ups run. Without it
the row outlived the tenant, and a tenant that later came to have the same
id would have started with legacy protocols off.
The e2e deletes a tenant whose switch a server administrator had turned
off, and would check that a new tenant with the same id starts with them
on. On this build the registry hands out a fresh id instead ("d" after
"c"), so the reuse -- and with it the removal -- isn't observable over
JMAP; the test says so rather than passing silently. The risk it guards
was therefore smaller than feared, and the change is mostly about not
leaving an orphaned row behind. All 72 checks pass.
|
||
|
|
3f40b36032 |
The switch knows who still uses legacy mail apps (LP-15, server)
The impact panel's data. Every successful sign-in over IMAP, POP3,
ManageSieve or SMTP AUTH records, per account and per protocol, one
timestamp -- nothing else: no address, no IP, no client. It is written at
most once an hour per account and protocol, so a mail app polling every
minute costs a read per sign-in and a write an hour. A record that can't be
written is logged and the sign-in goes ahead.
Both switches serve it as a read-only property, recentLegacyUse, as
wouldClose serves the confirmation: a list of {accountId, name, protocol,
lastUsedAt} for sign-ins in the last 30 days, most recent first.
inbuxa:ProtocolPolicy lists every account; inbuxa:TenantProtocolPolicy
lists only its tenant's own (MT-1). Accounts since deleted are left out. It
is computed only when the property is asked for.
The recording sits where the tenant check already runs once the account is
known, which becomes admit_legacy_session: refuse if the account's tenant
has legacy protocols off, otherwise record. A refused sign-in is never
recorded.
The spec leaves the interface to the implementation; a property on each
switch keeps the panel's data behind the same permission as the switch
itself, with no new object.
Unit tests hold the 30-day window to acceptance test 11 (three days ago
listed, forty not), the hourly throttle and the keys. The e2e proves on a
running server that the admin's IMAP and submission sign-ins are listed
with their time, that a second sign-in within the hour isn't written again,
and that a tenant's list holds its own user and nobody outside the tenant.
All 70 checks pass.
|
||
|
|
b65afb66f9 |
A tenant can turn legacy protocols off for itself (LP-9 to LP-14a)
The tenant switch. A tenant's administrator turns legacy mail protocols off for its own tenant, and from then on sign-in over IMAP, POP3, ManageSieve and SMTP AUTH is refused for every address on the tenant's domains, while every other domain on the server carries on. No port closes, since other tenants share them (LP-13): it is one stored fact per tenant, read at sign-in and when client configuration is answered. inbuxa:TenantProtocolPolicy/get and /set, one per tenant, id the tenant's: - Inside a tenant, a principal reaches only its own tenant's switch (MT-1): /get with no ids answers with it, another tenant's is notFound and can't be changed. At server level /get with no ids lists every tenant's. - Turning it off is always allowed. Turning it back on is refused with forbidden, naming inbuxa:ProtocolPolicy, while the server has legacy protocols off (LP-9). - A change raises security.legacy-protocols-changed with policy = tenant, the tenant's id, the new value and who made it (LP-14). - It takes sysDomainGet and sysDomainUpdate, not the two new permissions the spec names. The switch governs sign-in on the tenant's domains, so whoever manages those domains may turn it -- and the default Tenant Administrator role already holds both, where new permissions would reach no role already stored on a server (MT-12's note), leaving today's tenant administrators without the switch until someone edited their role by hand. The same trade inbuxa:AiLimits and inbuxa:ProtocolPolicy made. /query is not built yet; /get with no ids covers listing. Sign-in (LP-10 to LP-12). Before the credentials are looked at, the name given is resolved to its domain and the domain to its tenant, so a real account and a made-up address on the domain get the same refusal, with a right password or a wrong one, counted as no failed sign-in (LP-11). The words are the spec's: "Your organization allows only INBUXA webmail and JMAP apps...", in each protocol's form. A bearer token needn't name an account, so after authentication the account's own tenant is checked too; a token that named nobody can't slip past. The refusal carries policy = tenant and the domain, not the tenant's id: IMAP answers a command's tag from the Id key, so an error holding one was sent under the wrong tag and the mail app hung waiting for its reply. The first live run found that; a unit test now holds the refusal to it. Client configuration (LP-14a). Autoconfig, autodiscover, PACC and the suggested DNS records now ask whether legacy services are off for the domain being answered for -- the server's switch, or the domain's tenant's -- so a tenant's domains stop offering IMAP, POP3 and submission while others still do. tests/e2e/legacy_protocols.py builds a tenant with its own domain, a user and a tenant administrator, and a second tenant, and proves on a running server: the admin sees and changes only its own tenant's switch (test 10); turning it off is an event (test 14); the tenant's user is refused over IMAP with the right password and a wrong one, a made-up address on the domain the same (tests 6, 7); POP3 and submission refuse in their own forms and JMAP still works (test 8); an account on another domain signs in normally (test 6); autoconfig drops IMAP for the tenant's domain only; with the server off, the tenant can't turn it back on (test 9); and once back on, the user signs in again. All 62 checks pass. |
||
|
|
08f12fa158 |
SMTP and JMAP are locked open, and the selector will show them so
John, 2026-09-20: "SMTP and JMAP should be shown with the selector locked, we want to prevent those two protocols from being shutdown for now." The selector lists every mail protocol the server speaks, so the operator sees the whole surface at once; SMTP and JMAP sit in it named and visibly not switchable. JMAP was never closeable -- closing it locks everyone out of their mail and the operator out of INBUXA Admin, with no way back but the host -- and is now visibly so. SMTP is locked whole. LP-3 already spared inbound on 25; this extends that to submission on 465 and 587, which LP-1 would otherwise have closed by default. So closeSubmission has no effect while the lock stands, and is forced to false. A client that asks for true is not refused: the value is recorded, overruled, and the overrule reported, because the field is specified and the lock is meant to be temporary. is_locked() is consulted before anything else in closes(), so no phrasing of a request reaches past it. This costs the feature nothing. Submission's ports stay open and sign-in over them is still refused once LP-6 lands, which is the case acceptance test 2 already described: a mail app reaching 465 is told it cannot sign in rather than finding nothing listening. The operator also keeps a port they may well be forwarding, which is the LP-20 problem in miniature. The locked set is a server constant the front ends read, not a list they carry, so unlocking later is a server change and no admin release. The LP-3 tests stay as they are, to keep it covered if the lock is lifted. Recorded as LP-21, with acceptance tests 17 and 18. |
||
|
|
f04dbc3417 |
Taking the legacy listeners away, and putting them back
LP-1 and LP-5, the registry half. close() removes every listener object the policy closes, saving each one whole first; reopen() puts them back. The switch removes the listener objects, not just their sockets. A stopped socket returns on the next restart, which would reopen every port the operator had just closed, and the operator would have no way to know. A removed object stays removed, and a server that boots with the switch on never spawns those listeners at all -- so there is no boot-time special case to write or to forget. LP-3 is decided here, on the object rather than the running socket, and sees every address a listener binds: a submission listener that also binds 25 is inbound and stays. lmtp and http are never candidates. A listener that cannot be put back does not stop the others; it comes back with its reason and stays saved for another try (LP-5). A delete the registry declines is reported as not removed, so the policy never claims a port is closed while it is still accepting. Stopping the running socket is still a separate step in common, which owns the listener registry. Nothing calls any of this yet. |
||
|
|
c35b24b123 |
inbuxa:ProtocolPolicy, the switch itself
The server-wide legacy-protocols policy: the switch, whether submission closes with it, the listeners taken away to honour it, and who last changed it. Stored like inbuxa:AiLimits, as JSON in the fork's subspace, so an unset field reads as its default and an old record still loads. closes() is where LP-3 lives. imap, pop3 and manageSieve are named outright; smtp is not, because an SMTP listener is inbound or submission depending on its port and nothing else can tell them apart. A listener bound to 25 is inbound whatever it is called, including one that also binds 465, so it stays. http and lmtp are never candidates at all. savedListeners keeps each listener's registry object whole rather than a few fields of it. LP-5 promises the listeners come back exactly as they were, and a listener carries proxy networks, TLS timeouts and socket options that no one should have to re-derive -- a field this code has never heard of has to survive the round trip too, and a test holds that. The module is under security/ rather than beside the rebuilt features, because this one is not a rebuild: upstream has nothing like it. Still only a fact. Nothing reads this policy yet, so no port closes and no sign-in is refused; the acting code needs the listener registry and the config store, which live above this crate. |