// Copyright (c) 2025 Probo Inc . // // Permission to use, copy, modify, and/or distribute this software for any // purpose with or without fee is hereby granted, provided that the above // copyright notice and this permission notice appear in all copies. // // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH // REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY // AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, // INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM // LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR // OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR // PERFORMANCE OF THIS SOFTWARE. package auth import ( "crypto/x509" "encoding/pem" "fmt" "net/url" ) type ValidationError struct { Field string Message string } func (e ValidationError) Error() string { return fmt.Sprintf("%s: %s", e.Field, e.Message) } // ValidateIdPConfiguration validates only the IdP (Identity Provider) configuration. // This validates user-provided data from the IdP. // SP (Service Provider) configuration is generated by the application and doesn't need validation. func ValidateIdPConfiguration( idpEntityID string, idpSsoURL string, idpCertificate string, ) []ValidationError { var errors []ValidationError // Validate IdP Entity ID if idpEntityID == "" { errors = append(errors, ValidationError{ Field: "idp_entity_id", Message: "IdP Entity ID cannot be empty", }) } // Validate IdP SSO URL - accept both HTTP and HTTPS if err := validateURL(idpSsoURL, "idp_sso_url"); err != nil { errors = append(errors, *err) } // Validate IdP certificate if err := validateCertificate(idpCertificate); err != nil { errors = append(errors, ValidationError{ Field: "idp_certificate", Message: err.Error(), }) } return errors } func validateURL(urlStr string, fieldName string) *ValidationError { if urlStr == "" { return &ValidationError{ Field: fieldName, Message: "URL cannot be empty", } } parsedURL, err := url.Parse(urlStr) if err != nil { return &ValidationError{ Field: fieldName, Message: fmt.Sprintf("invalid URL format: %v", err), } } if parsedURL.Scheme == "" { return &ValidationError{ Field: fieldName, Message: "URL must have a scheme (http or https)", } } if parsedURL.Scheme != "http" && parsedURL.Scheme != "https" { return &ValidationError{ Field: fieldName, Message: "URL scheme must be http or https (found: " + parsedURL.Scheme + ")", } } if parsedURL.Host == "" { return &ValidationError{ Field: fieldName, Message: "URL must have a host", } } return nil } func validateCertificate(certPEM string) error { if certPEM == "" { return fmt.Errorf("certificate cannot be empty") } block, _ := pem.Decode([]byte(certPEM)) if block == nil { return fmt.Errorf("cannot parse certificate PEM") } if block.Type != "CERTIFICATE" { return fmt.Errorf("PEM block type must be CERTIFICATE (found: %s)", block.Type) } _, err := x509.ParseCertificate(block.Bytes) if err != nil { return fmt.Errorf("cannot parse X.509 certificate: %w", err) } return nil }