mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-10-20 10:52:08 +00:00
2c96242487
Derive OIDC and SAML browser-session principals from provider-scoped subjects instead of mutable username or email claims. Preserve compatibility by migrating legacy username/email RBAC assignments to the stable SSO principal when no authoritative group mapping is present, and pin the invariant in API/security contracts.
86 lines
2.3 KiB
Go
86 lines
2.3 KiB
Go
package api
|
|
|
|
import (
|
|
"crypto/sha256"
|
|
"encoding/base64"
|
|
"fmt"
|
|
"strings"
|
|
|
|
"github.com/rcourtman/pulse-go-rewrite/internal/config"
|
|
internalauth "github.com/rcourtman/pulse-go-rewrite/pkg/auth"
|
|
)
|
|
|
|
func stableSSOPrincipal(providerType config.SSOProviderType, providerID string, subject string) (string, error) {
|
|
typ := strings.TrimSpace(strings.ToLower(string(providerType)))
|
|
id := strings.TrimSpace(providerID)
|
|
sub := strings.TrimSpace(subject)
|
|
if typ == "" {
|
|
return "", fmt.Errorf("sso provider type is required")
|
|
}
|
|
if id == "" {
|
|
return "", fmt.Errorf("sso provider id is required")
|
|
}
|
|
if sub == "" {
|
|
return "", fmt.Errorf("sso subject is required")
|
|
}
|
|
|
|
sum := sha256.Sum256([]byte(typ + "\x00" + id + "\x00" + sub))
|
|
return "sso:" + typ + ":" + id + ":" + base64.RawURLEncoding.EncodeToString(sum[:]), nil
|
|
}
|
|
|
|
func ssoLegacyPrincipalCandidates(values ...string) []string {
|
|
candidates := make([]string, 0, len(values)+2)
|
|
seen := make(map[string]struct{}, len(values)+2)
|
|
for _, value := range values {
|
|
trimmed := strings.TrimSpace(value)
|
|
if trimmed == "" {
|
|
continue
|
|
}
|
|
if _, ok := seen[trimmed]; !ok {
|
|
candidates = append(candidates, trimmed)
|
|
seen[trimmed] = struct{}{}
|
|
}
|
|
|
|
if strings.Contains(trimmed, "@") {
|
|
lower := strings.ToLower(trimmed)
|
|
if _, ok := seen[lower]; !ok {
|
|
candidates = append(candidates, lower)
|
|
seen[lower] = struct{}{}
|
|
}
|
|
}
|
|
}
|
|
return candidates
|
|
}
|
|
|
|
func applySSORoleAssignments(manager internalauth.Manager, principal string, legacyCandidates []string, mappedRoles []string, mappingAuthoritative bool, ensureListed bool) error {
|
|
principal = strings.TrimSpace(principal)
|
|
if manager == nil || principal == "" {
|
|
return nil
|
|
}
|
|
|
|
if mappingAuthoritative || len(mappedRoles) > 0 {
|
|
return manager.UpdateUserRoles(principal, mappedRoles)
|
|
}
|
|
|
|
if assignment, ok := manager.GetUserAssignment(principal); ok && len(assignment.RoleIDs) > 0 {
|
|
return nil
|
|
}
|
|
|
|
for _, candidate := range legacyCandidates {
|
|
candidate = strings.TrimSpace(candidate)
|
|
if candidate == "" || candidate == principal {
|
|
continue
|
|
}
|
|
assignment, ok := manager.GetUserAssignment(candidate)
|
|
if !ok || len(assignment.RoleIDs) == 0 {
|
|
continue
|
|
}
|
|
return manager.UpdateUserRoles(principal, assignment.RoleIDs)
|
|
}
|
|
|
|
if ensureListed {
|
|
return manager.UpdateUserRoles(principal, nil)
|
|
}
|
|
return nil
|
|
}
|