Files
Pulse/internal/api/security_oidc.go
rcourtman 8152197207 fix: mark unused parameters to satisfy unparam linter
Mark intentionally unused parameters with underscore to:
- Silence unparam warnings for legitimate unused parameters
- Keep function signatures intact for API compatibility
- Remove unused req from serveChecksum helper
2025-11-27 10:12:48 +00:00

169 lines
6.0 KiB
Go

package api
import (
"encoding/json"
"net/http"
"strings"
"github.com/rcourtman/pulse-go-rewrite/internal/config"
"github.com/rs/zerolog/log"
)
// handleOIDCConfig exposes and updates the OIDC configuration.
func (r *Router) handleOIDCConfig(w http.ResponseWriter, req *http.Request) {
switch req.Method {
case http.MethodGet:
r.handleGetOIDCConfig(w, req)
case http.MethodPut:
r.handleUpdateOIDCConfig(w, req)
default:
writeErrorResponse(w, http.StatusMethodNotAllowed, "method_not_allowed", "Only GET and PUT are supported", nil)
}
}
func (r *Router) handleGetOIDCConfig(w http.ResponseWriter, _ *http.Request) {
cfg := r.ensureOIDCConfig()
response := makeOIDCResponse(cfg, r.config.PublicURL)
w.Header().Set("Content-Type", "application/json")
if err := json.NewEncoder(w).Encode(response); err != nil {
log.Error().Err(err).Msg("Failed to encode OIDC configuration response; returning HTTP 500 to caller")
}
}
func (r *Router) handleUpdateOIDCConfig(w http.ResponseWriter, req *http.Request) {
cfg := r.ensureOIDCConfig()
if len(cfg.EnvOverrides) > 0 {
writeErrorResponse(w, http.StatusConflict, "oidc_locked", "OIDC settings are managed via environment variables and cannot be changed at runtime", nil)
return
}
var payload struct {
Enabled bool `json:"enabled"`
IssuerURL string `json:"issuerUrl"`
ClientID string `json:"clientId"`
ClientSecret *string `json:"clientSecret,omitempty"`
RedirectURL string `json:"redirectUrl"`
LogoutURL string `json:"logoutUrl"`
Scopes []string `json:"scopes"`
UsernameClaim string `json:"usernameClaim"`
EmailClaim string `json:"emailClaim"`
GroupsClaim string `json:"groupsClaim"`
AllowedGroups []string `json:"allowedGroups"`
AllowedDomains []string `json:"allowedDomains"`
AllowedEmails []string `json:"allowedEmails"`
ClearClientSecret bool `json:"clearClientSecret"`
CABundle *string `json:"caBundle"`
}
if err := json.NewDecoder(req.Body).Decode(&payload); err != nil {
writeErrorResponse(w, http.StatusBadRequest, "invalid_request", "Invalid request payload", nil)
return
}
updated := &config.OIDCConfig{
Enabled: payload.Enabled,
IssuerURL: strings.TrimSpace(payload.IssuerURL),
ClientID: strings.TrimSpace(payload.ClientID),
RedirectURL: strings.TrimSpace(payload.RedirectURL),
LogoutURL: strings.TrimSpace(payload.LogoutURL),
Scopes: append([]string{}, payload.Scopes...),
UsernameClaim: strings.TrimSpace(payload.UsernameClaim),
EmailClaim: strings.TrimSpace(payload.EmailClaim),
GroupsClaim: strings.TrimSpace(payload.GroupsClaim),
AllowedGroups: append([]string{}, payload.AllowedGroups...),
AllowedDomains: append([]string{}, payload.AllowedDomains...),
AllowedEmails: append([]string{}, payload.AllowedEmails...),
CABundle: strings.TrimSpace(cfg.CABundle),
EnvOverrides: make(map[string]bool),
}
// Preserve existing secret unless explicitly changed.
updated.ClientSecret = cfg.ClientSecret
if payload.ClearClientSecret {
updated.ClientSecret = ""
}
if payload.ClientSecret != nil {
updated.ClientSecret = strings.TrimSpace(*payload.ClientSecret)
}
if payload.CABundle != nil {
updated.CABundle = strings.TrimSpace(*payload.CABundle)
}
updated.ApplyDefaults(r.config.PublicURL)
if err := updated.Validate(); err != nil {
writeErrorResponse(w, http.StatusBadRequest, "validation_error", err.Error(), nil)
return
}
if err := config.SaveOIDCConfig(updated); err != nil {
log.Error().Err(err).Msg("Failed to persist OIDC configuration")
writeErrorResponse(w, http.StatusInternalServerError, "save_failed", "Failed to save OIDC settings", nil)
return
}
// Update in-memory configuration for immediate effect.
r.config.OIDC = updated
response := makeOIDCResponse(updated, r.config.PublicURL)
w.Header().Set("Content-Type", "application/json")
if err := json.NewEncoder(w).Encode(response); err != nil {
log.Error().Err(err).Msg("Failed to encode OIDC configuration response; returning HTTP 500 to caller")
}
}
type oidcResponse struct {
Enabled bool `json:"enabled"`
IssuerURL string `json:"issuerUrl"`
ClientID string `json:"clientId"`
RedirectURL string `json:"redirectUrl"`
LogoutURL string `json:"logoutUrl"`
Scopes []string `json:"scopes"`
UsernameClaim string `json:"usernameClaim"`
EmailClaim string `json:"emailClaim"`
GroupsClaim string `json:"groupsClaim"`
AllowedGroups []string `json:"allowedGroups"`
AllowedDomains []string `json:"allowedDomains"`
AllowedEmails []string `json:"allowedEmails"`
CABundle string `json:"caBundle"`
ClientSecretSet bool `json:"clientSecretSet"`
DefaultRedirect string `json:"defaultRedirect"`
EnvOverrides map[string]bool `json:"envOverrides,omitempty"`
}
func makeOIDCResponse(cfg *config.OIDCConfig, publicURL string) oidcResponse {
if cfg == nil {
cfg = config.NewOIDCConfig()
cfg.ApplyDefaults(publicURL)
}
resp := oidcResponse{
Enabled: cfg.Enabled,
IssuerURL: cfg.IssuerURL,
ClientID: cfg.ClientID,
RedirectURL: cfg.RedirectURL,
LogoutURL: cfg.LogoutURL,
Scopes: append([]string{}, cfg.Scopes...),
UsernameClaim: cfg.UsernameClaim,
EmailClaim: cfg.EmailClaim,
GroupsClaim: cfg.GroupsClaim,
AllowedGroups: append([]string{}, cfg.AllowedGroups...),
AllowedDomains: append([]string{}, cfg.AllowedDomains...),
AllowedEmails: append([]string{}, cfg.AllowedEmails...),
CABundle: cfg.CABundle,
ClientSecretSet: cfg.ClientSecret != "",
DefaultRedirect: config.DefaultRedirectURL(publicURL),
}
if len(cfg.EnvOverrides) > 0 {
resp.EnvOverrides = make(map[string]bool, len(cfg.EnvOverrides))
for k, v := range cfg.EnvOverrides {
resp.EnvOverrides[k] = v
}
}
return resp
}