Администраторы почты из Jambotron: каждый ADMIN управляет ящиками
continuous-integration/drone/pr Build is passing

users.is_admin (V8) — ставит только служебное API (PUT /api/service/accounts
с "admin"), ящик сам себя администратором не сделает. MailAdmins: список из
конфигурации + выданные Jambotron (загружаются при старте, меняются сразу).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-11-07 19:44:32 +01:00
co-authored by Claude Opus 5.5
parent 01c1e5504a
commit 7e09282a88
7 changed files with 107 additions and 14 deletions
@@ -10,13 +10,15 @@ import java.util.stream.Collectors;
/**
* Who manages mailboxes (Settings → Mailboxes): {@code mailserver.admins} (comma separated), by default the first
* mailbox — {@code mailserver.admin.email}, or admin@&lt;first domain&gt;. No role in the database: the list lives in
* the configuration, so a stolen mailbox can not make itself an administrator.
* mailbox — {@code mailserver.admin.email}, or admin@&lt;first domain&gt;; plus mailboxes Jambotron marks as admin
* through the service API ({@code users.is_admin}, V8 — every Jambotron ADMIN). A mailbox can not make itself an
* administrator: only the configuration or the service token.
*/
@Component
public class MailAdmins {
private final Set<String> admins;
private final Set<String> granted = java.util.concurrent.ConcurrentHashMap.newKeySet();
public MailAdmins(@Value("${mailserver.admins:}") String admins,
@Value("${mailserver.admin.email:}") String firstMailbox,
@@ -31,6 +33,20 @@ public class MailAdmins {
}
public boolean isAdmin(String email) {
return email != null && admins.contains(email.toLowerCase(Locale.ROOT));
if (email == null) {
return false;
}
String key = email.toLowerCase(Locale.ROOT);
return admins.contains(key) || granted.contains(key);
}
/** Given / taken by Jambotron (users.is_admin) — loaded at start and kept here on every change. */
public void grant(String email, boolean admin) {
String key = email.toLowerCase(Locale.ROOT);
if (admin) {
granted.add(key);
} else {
granted.remove(key);
}
}
}
@@ -0,0 +1,32 @@
package com.mailserver.config;
import com.mailserver.repository.UserRepository;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;
/** At start: mailboxes Jambotron made administrators (users.is_admin) → {@link MailAdmins}. */
@Component
public class MailAdminsLoader {
private static final Logger log = LoggerFactory.getLogger(MailAdminsLoader.class);
private final UserRepository users;
private final MailAdmins admins;
public MailAdminsLoader(UserRepository users, MailAdmins admins) {
this.users = users;
this.admins = admins;
}
@EventListener(ApplicationReadyEvent.class)
public void load() {
users.findAll().filter(user -> user.isAdmin())
.doOnNext(user -> admins.grant(user.getEmail(), true))
.count()
.subscribe(n -> log.info("Mail administrators from Jambotron: {}", n),
e -> log.error("Mail administrators from Jambotron not loaded: {}", e.getMessage()));
}
}