- Added configuration options for Stalwart hosted mail in .env.example. - Updated Docker Compose to include Stalwart service with health checks. - Introduced new API endpoints for managing mail domains and migration projects. - Enhanced Authentik blueprints for user enrollment and post-migration security. - Updated OAuth handling for Google and Microsoft migration processes. - Improved error handling and response structures in the mail API. - Added integration tests for email claiming and migration workflows.
144 lines
3.9 KiB
Go
144 lines
3.9 KiB
Go
package migration
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"strings"
|
|
)
|
|
|
|
const adminConsentStatePrefix = "project:"
|
|
|
|
// MicrosoftAdminConsentRecord stores the outcome of a Microsoft admin consent redirect.
|
|
type MicrosoftAdminConsentRecord struct {
|
|
TenantID string
|
|
ClientID string
|
|
ProjectID string
|
|
Granted bool
|
|
ErrorCode string
|
|
ErrorDescription string
|
|
}
|
|
|
|
// MicrosoftAdminConsent is a persisted tenant-level admin consent row.
|
|
type MicrosoftAdminConsent struct {
|
|
TenantID string `json:"tenant_id"`
|
|
ClientID string `json:"client_id"`
|
|
ProjectID string `json:"project_id,omitempty"`
|
|
Granted bool `json:"granted"`
|
|
ErrorCode string `json:"error_code,omitempty"`
|
|
ErrorDescription string `json:"error_description,omitempty"`
|
|
ConsentedAt string `json:"consented_at"`
|
|
UpdatedAt string `json:"updated_at"`
|
|
}
|
|
|
|
func EncodeAdminConsentState(projectID string) string {
|
|
projectID = strings.TrimSpace(projectID)
|
|
if projectID == "" {
|
|
return ""
|
|
}
|
|
return adminConsentStatePrefix + projectID
|
|
}
|
|
|
|
func ParseAdminConsentProjectID(state string) string {
|
|
state = strings.TrimSpace(state)
|
|
if !strings.HasPrefix(state, adminConsentStatePrefix) {
|
|
return ""
|
|
}
|
|
return strings.TrimSpace(strings.TrimPrefix(state, adminConsentStatePrefix))
|
|
}
|
|
|
|
func (s *Service) RecordMicrosoftAdminConsent(ctx context.Context, in MicrosoftAdminConsentRecord) error {
|
|
if s.db == nil {
|
|
return fmt.Errorf("database not configured")
|
|
}
|
|
tenantID := strings.TrimSpace(in.TenantID)
|
|
clientID := strings.TrimSpace(in.ClientID)
|
|
projectID := strings.TrimSpace(in.ProjectID)
|
|
if tenantID == "" {
|
|
return fmt.Errorf("tenant id required")
|
|
}
|
|
if clientID == "" {
|
|
return fmt.Errorf("client id required")
|
|
}
|
|
|
|
tx, err := s.db.Begin(ctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer tx.Rollback(ctx)
|
|
|
|
_, err = tx.Exec(ctx, `
|
|
INSERT INTO migration_microsoft_admin_consents (
|
|
tenant_id, client_id, project_id, granted, error_code, error_description
|
|
) VALUES ($1, $2, NULLIF($3, '')::uuid, $4, $5, $6)
|
|
ON CONFLICT (tenant_id, client_id) DO UPDATE SET
|
|
project_id = COALESCE(EXCLUDED.project_id, migration_microsoft_admin_consents.project_id),
|
|
granted = EXCLUDED.granted,
|
|
error_code = EXCLUDED.error_code,
|
|
error_description = EXCLUDED.error_description,
|
|
consented_at = NOW(),
|
|
updated_at = NOW()
|
|
`, tenantID, clientID, projectID, in.Granted, in.ErrorCode, in.ErrorDescription)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if projectID != "" {
|
|
if in.Granted {
|
|
_, err = tx.Exec(ctx, `
|
|
UPDATE migration_projects SET
|
|
microsoft_tenant_id = $2,
|
|
microsoft_admin_consent_at = NOW(),
|
|
microsoft_admin_consent_error = '',
|
|
updated_at = NOW()
|
|
WHERE id = $1::uuid
|
|
`, projectID, tenantID)
|
|
} else {
|
|
errMsg := strings.TrimSpace(in.ErrorDescription)
|
|
if errMsg == "" {
|
|
errMsg = strings.TrimSpace(in.ErrorCode)
|
|
}
|
|
_, err = tx.Exec(ctx, `
|
|
UPDATE migration_projects SET
|
|
microsoft_tenant_id = CASE
|
|
WHEN microsoft_tenant_id = '' THEN $2
|
|
ELSE microsoft_tenant_id
|
|
END,
|
|
microsoft_admin_consent_error = $3,
|
|
updated_at = NOW()
|
|
WHERE id = $1::uuid
|
|
`, projectID, tenantID, errMsg)
|
|
}
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return tx.Commit(ctx)
|
|
}
|
|
|
|
func (s *Service) ListMicrosoftAdminConsents(ctx context.Context) ([]MicrosoftAdminConsent, error) {
|
|
rows, err := s.db.Query(ctx, `
|
|
SELECT tenant_id, client_id, COALESCE(project_id::text, ''), granted,
|
|
error_code, error_description, consented_at::text, updated_at::text
|
|
FROM migration_microsoft_admin_consents
|
|
ORDER BY updated_at DESC
|
|
`)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
|
|
var out []MicrosoftAdminConsent
|
|
for rows.Next() {
|
|
var row MicrosoftAdminConsent
|
|
if err := rows.Scan(
|
|
&row.TenantID, &row.ClientID, &row.ProjectID, &row.Granted,
|
|
&row.ErrorCode, &row.ErrorDescription, &row.ConsentedAt, &row.UpdatedAt,
|
|
); err != nil {
|
|
return nil, err
|
|
}
|
|
out = append(out, row)
|
|
}
|
|
return out, rows.Err()
|
|
}
|