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) }