package discourse import ( "crypto/hmac" "crypto/sha256" "encoding/base64" "encoding/hex" "fmt" "net/url" "strconv" "strings" "github.com/google/uuid" ) // SsoConfig - Configuration for an SSO Request type SsoConfig struct { Endpoint string ReturnEndpoint string Secret string UUID uuid.UUID } // GetSSORequestURL - gets a url for an sso request func GetSSORequestURL(config SsoConfig) string { url := generateSSORequestURL(config) return url } // ValidateSsoResponse - Validate the signature on the sso response func ValidateSsoResponse(sso, sig, secret string) bool { ssoDataSig := computeHmac256(sso, secret) return sig == ssoDataSig } func generateSSORequestURL(config SsoConfig) string { // Build the query string payload payload := fmt.Sprintf("nonce=%s&return_sso_url=%s", config.UUID, config.ReturnEndpoint) // Base64 encode the payload base64Payload := base64.StdEncoding.EncodeToString([]byte(payload)) // Urlencode the URLEncodedPayload := url.QueryEscape(base64Payload) // Get a hex signature for this payload with the sso secret hexSignature := computeHmac256(base64Payload, config.Secret) ssoURL := fmt.Sprintf("%s?sso=%s&sig=%s", config.Endpoint, URLEncodedPayload, hexSignature) return ssoURL } // GenerateSSO - Generate the sso and sig for an sso request func GenerateSSO(nonce string, returnEndpoint, secret string) (string, string) { // Build the query string payload payload := fmt.Sprintf("nonce=%s&return_sso_url=%s", nonce, returnEndpoint) // Base64 encode the payload base64Payload := base64.StdEncoding.EncodeToString([]byte(payload)) // Urlencode the URLEncodedPayload := url.QueryEscape(base64Payload) // Get a hex signature for this payload with the sso secret hexSignature := computeHmac256(base64Payload, secret) return URLEncodedPayload, hexSignature } // GetSSOUrl - Generate the sso enpodint url give the domain, sso and sig func GetSSOUrl(endpoint, sso, sig string) string { return fmt.Sprintf("%s/session/sso_provider?sso=%s&sig=%s", endpoint, sso, sig) } // Create a hex signature from a message and secret func computeHmac256(message string, secret string) string { key := []byte(secret) h := hmac.New(sha256.New, key) h.Write([]byte(message)) return hex.EncodeToString(h.Sum(nil)) } func ParseSSOResponse(ssoData string) (SSOResponse, error) { decodedSsoData, err := base64.StdEncoding.DecodeString(ssoData) if err != nil { return SSOResponse{}, err } queryData, err := url.ParseQuery(string(decodedSsoData)) if err != nil { return SSOResponse{}, err } id, _ := strconv.Atoi(queryData.Get("external_id")) response := SSOResponse{ ID: id, Admin: (queryData.Get("admin") == "true"), Moderator: (queryData.Get("moderator") == "true"), Groups: strings.Split(queryData.Get("groups"), ","), Username: queryData.Get("username"), Nonce: queryData.Get("nonce"), } return response, nil }