feat(PROJ-21/23): Pro-Tenant Xapian-Index + Tenant-LDAP Backend
PROJ-21 Phase 4:
- internal/index/tenant_manager.go: TenantIndexManager mit lazy-loading Pool
- internal/index/tenant_worker.go: TenantIndexWorker leitet Submit an richtigen Index
- Jeder Mandant bekommt eigenes Xapian-Verzeichnis (tenant-<id>/)
- handleSearch nutzt direkt Tenant-Index statt nachgelagertem Post-Filter
- runBackfill re-indexiert pro Mandant beim Start
PROJ-23 / PROJ-16 Phase B:
- internal/ldapconfig/tenant_store.go: TenantStore mit AES-256-GCM für tenant_ldap
- internal/api/ldap_tenants.go: 8 neue Handler (GET/PUT/DELETE/test für
/api/tenant/ldap und /api/admin/tenants/{id}/ldap)
- internal/auth/auth.go: Login-Fallback prüft tenant_ldap nach globalem LDAP
(Domain-Extraktion → tenant_ldap config → UpsertLDAPUser mit tenant_id)
- internal/api/server.go: SetTenantLDAP(), neue Routen registriert
- internal/tenantstore/store.go: GetByDomain() Interface für auth-Package
- cmd/archivmail/main.go: TenantLDAPStore + TenantIndexManager verdrahtet
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
+24
-6
@@ -78,8 +78,10 @@ type Server struct {
|
||||
pop3Store *pop3store.Store
|
||||
pop3Importer *pop3store.Importer
|
||||
uploadJobs sync.Map // jobID → *UploadJob
|
||||
ldapStore *ldapcfg.Store
|
||||
tenantStore *tenantstore.Store
|
||||
ldapStore *ldapcfg.Store
|
||||
tenantStore *tenantstore.Store
|
||||
tenantLdapStore *ldapcfg.TenantStore // PROJ-23: per-tenant LDAP config
|
||||
idxMgr *index.TenantIndexManager // PROJ-21 Phase 4: per-tenant Xapian index
|
||||
}
|
||||
|
||||
// SetSMTPDaemon wires the SMTP daemon into the API server after construction.
|
||||
@@ -100,6 +102,11 @@ func (s *Server) SetPop3(store *pop3store.Store, importer *pop3store.Importer) {
|
||||
s.pop3Importer = importer
|
||||
}
|
||||
|
||||
// SetIndexManager wires the per-tenant index manager into the API server (PROJ-21 Phase 4).
|
||||
func (s *Server) SetIndexManager(mgr *index.TenantIndexManager) {
|
||||
s.idxMgr = mgr
|
||||
}
|
||||
|
||||
// New creates and wires up a new API server.
|
||||
func New(
|
||||
cfg config.APIConfig,
|
||||
@@ -615,15 +622,26 @@ func (s *Server) handleSearch(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
}
|
||||
|
||||
result, err := s.idx.Search(req)
|
||||
// PROJ-21 Phase 4: Use per-tenant index when available; fall back to
|
||||
// global index + post-filter when the tenant index manager is not wired.
|
||||
tenantID := tenantFromCtx(r.Context())
|
||||
searchIdx := s.idx
|
||||
usedTenantIndex := false
|
||||
if s.idxMgr != nil && tenantID != nil {
|
||||
searchIdx = s.idxMgr.ForTenant(tenantID)
|
||||
usedTenantIndex = true
|
||||
}
|
||||
|
||||
result, err := searchIdx.Search(req)
|
||||
if err != nil {
|
||||
writeError(w, http.StatusInternalServerError, "search failed")
|
||||
return
|
||||
}
|
||||
|
||||
// Tenant isolation: filter results to only this tenant's emails.
|
||||
tenantID := tenantFromCtx(r.Context())
|
||||
if tenantID != nil && len(result.Hits) > 0 {
|
||||
// Fallback tenant isolation: post-filter when we used the global index
|
||||
// but the user belongs to a tenant. This is the legacy path; the per-tenant
|
||||
// index path above makes this unnecessary.
|
||||
if tenantID != nil && !usedTenantIndex && len(result.Hits) > 0 {
|
||||
allowedIDs, idErr := s.store.GetAllIDsByTenant(r.Context(), tenantID)
|
||||
if idErr == nil {
|
||||
allowed := make(map[string]struct{}, len(allowedIDs))
|
||||
|
||||
Reference in New Issue
Block a user