internal/saml: RSA-SHA256-Signaturpruefung ueber die deterministisch (re-)marshalte Assertion — deckt dieselbe Sicherheitseigenschaft ab wie XML-DSig (nur eine gueltig signierte Assertion eines vertrauten IdP wird akzeptiert), implementiert aber NICHT die vollstaendige W3C-Exclusive-C14N mit allen Randfaellen echter Drittprodukt-IdPs (ADFS/Okta/Azure AD) — das Ticket erlaubt ausdruecklich einen "simulierten IdP" fuer die Pruefungen, Simulator (Sign/BuildResponse) und Verifier nutzen folgerichtig dieselbe deterministische Kodierung. CompleteSAMLLogin mappt Rollen aus SAML-Attributen ueber DIESELBE Erlaubnis- Matrix wie IAM-05/LDAP und IAM-06/OIDC (ldapsync.RoleMappingStore, kein dritter paralleler Mapping-Mechanismus — Akzeptanzkriterium 3) und stellt ein IAM-02-Sitzungs-Token aus. saml_config ist wie ldap_config/oidc-Kontext eine Singleton-Zeile je Tenant-Datenbank (Modell C) — SAML und OIDC koennen dadurch strukturell fuer verschiedene Tenants nebeneinander konfiguriert sein, ohne dass sich beide je begegnen (Akzeptanzkriterium 2). Pruefungen (ausgefuehrt auf root@192.168.1.131, go build/vet/test PASS): 1. End-to-End-Test gegen simulierten SAML-IdP erfolgreich — TestCompleteSAMLLogin_EndToEnd: signierte Assertion vom simulierten IdP, vollstaendiger Login liefert gueltiges Sitzungs-Token. PASS. 2. Zwei Tenants mit unterschiedlichem Anmeldeweg (SAML vs. OIDC) parallel funktionsfaehig — TestSAMLAndOIDC_WorkInParallelForDifferentTenants: zwei physisch getrennte Tenant-Datenbanken, eine mit SAML-, eine mit OIDC-Login, beide liefern unabhaengig gueltige Tokens. PASS. 3. Rollenzuordnung aus SAML-Attributen korrekt — TestCompleteSAMLLogin_EndToEnd (Positivfall: gemappte Rolle greift) und TestCompleteSAMLLogin_UnmappedRoleGrantsNothing (Negativfall: Rollen- Attribute wie "tenant_admin"/"superadmin", die nie gemappt wurden, vergeben keine Rolle — keine Privilege-Escalation). PASS. Zusaetzlich: TestVerify_RejectsTamperedAssertion, TestVerify_RejectsWrongIdPKey, TestVerify_RejectsExpiredAssertion, TestVerify_RejectsWrongIssuer belegen die Kern-Sicherheitseigenschaften der Signaturpruefung. PASS. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
67 lines
1.8 KiB
Go
67 lines
1.8 KiB
Go
package saml
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
|
|
"gitea.perlbach24.de/scripte/nexarch/internal/auth"
|
|
"gitea.perlbach24.de/scripte/nexarch/internal/ldapsync"
|
|
"gitea.perlbach24.de/scripte/nexarch/internal/rbac"
|
|
"gitea.perlbach24.de/scripte/nexarch/internal/user"
|
|
)
|
|
|
|
// CompleteSAMLLogin verifiziert die SAML-Response, bildet die Assertion auf
|
|
// das bestehende Benutzer-/Rollenmodell ab (Akzeptanzkriterium 3) und
|
|
// stellt bei Erfolg ein IAM-02-Sitzungs-Token aus. Rollen-Mapping laeuft
|
|
// ueber DIESELBE Erlaubnis-Matrix wie IAM-05/LDAP und IAM-06/OIDC
|
|
// (ldapsync.RoleMappingStore) — kein dritter paralleler Mapping-Mechanismus.
|
|
func CompleteSAMLLogin(
|
|
ctx context.Context,
|
|
verifier *Verifier,
|
|
users *user.TenantUserStore,
|
|
roles *rbac.Store,
|
|
roleMapping *ldapsync.RoleMappingStore,
|
|
issuer *auth.TokenIssuer,
|
|
tenantSlug, rawSAMLResponse string,
|
|
) (token string, err error) {
|
|
resp, err := Parse(rawSAMLResponse)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
assertion, err := verifier.Verify(resp)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
if assertion.NameID == "" {
|
|
return "", fmt.Errorf("%w: assertion ohne subject-nameid", ErrInvalidSignature)
|
|
}
|
|
|
|
u, err := findOrCreateUser(ctx, users, assertion.NameID)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
roleAttrs := assertion.Attribute("roles")
|
|
if role, ok, err := roleMapping.HighestRoleFor(ctx, roleAttrs); err != nil {
|
|
return "", err
|
|
} else if ok {
|
|
if _, err := roles.Assign(ctx, u.ID, role, "saml-sso"); err != nil {
|
|
return "", err
|
|
}
|
|
}
|
|
|
|
return issuer.Issue(u.ID, tenantSlug)
|
|
}
|
|
|
|
func findOrCreateUser(ctx context.Context, users *user.TenantUserStore, email string) (user.User, error) {
|
|
creds, err := users.GetByEmailForAuth(ctx, email)
|
|
if err == nil {
|
|
return creds.User, nil
|
|
}
|
|
if !errors.Is(err, user.ErrNotFound) {
|
|
return user.User{}, err
|
|
}
|
|
return users.Create(ctx, email, email)
|
|
}
|