sso.go 2.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109
  1. package discourse
  2. import (
  3. "crypto/hmac"
  4. "crypto/sha256"
  5. "encoding/base64"
  6. "encoding/hex"
  7. "fmt"
  8. "net/url"
  9. "strconv"
  10. "strings"
  11. "github.com/google/uuid"
  12. )
  13. // SsoConfig - Configuration for an SSO Request
  14. type SsoConfig struct {
  15. Endpoint string
  16. ReturnEndpoint string
  17. Secret string
  18. UUID uuid.UUID
  19. }
  20. // GetSSORequestURL - gets a url for an sso request
  21. func GetSSORequestURL(config SsoConfig) string {
  22. url := generateSSORequestURL(config)
  23. return url
  24. }
  25. // ValidateSsoResponse - Validate the signature on the sso response
  26. func ValidateSsoResponse(sso, sig, secret string) bool {
  27. ssoDataSig := computeHmac256(sso, secret)
  28. return sig == ssoDataSig
  29. }
  30. func generateSSORequestURL(config SsoConfig) string {
  31. // Build the query string payload
  32. payload := fmt.Sprintf("nonce=%s&return_sso_url=%s", config.UUID, config.ReturnEndpoint)
  33. // Base64 encode the payload
  34. base64Payload := base64.StdEncoding.EncodeToString([]byte(payload))
  35. // Urlencode the
  36. URLEncodedPayload := url.QueryEscape(base64Payload)
  37. // Get a hex signature for this payload with the sso secret
  38. hexSignature := computeHmac256(base64Payload, config.Secret)
  39. ssoURL := fmt.Sprintf("%s?sso=%s&sig=%s", config.Endpoint, URLEncodedPayload, hexSignature)
  40. return ssoURL
  41. }
  42. // GenerateSSO - Generate the sso and sig for an sso request
  43. func GenerateSSO(nonce string, returnEndpoint, secret string) (string, string) {
  44. // Build the query string payload
  45. payload := fmt.Sprintf("nonce=%s&return_sso_url=%s", nonce, returnEndpoint)
  46. // Base64 encode the payload
  47. base64Payload := base64.StdEncoding.EncodeToString([]byte(payload))
  48. // Urlencode the
  49. URLEncodedPayload := url.QueryEscape(base64Payload)
  50. // Get a hex signature for this payload with the sso secret
  51. hexSignature := computeHmac256(base64Payload, secret)
  52. return URLEncodedPayload, hexSignature
  53. }
  54. // GetSSOUrl - Generate the sso enpodint url give the domain, sso and sig
  55. func GetSSOUrl(endpoint, sso, sig string) string {
  56. return fmt.Sprintf("%s/session/sso_provider?sso=%s&sig=%s", endpoint, sso, sig)
  57. }
  58. // Create a hex signature from a message and secret
  59. func computeHmac256(message string, secret string) string {
  60. key := []byte(secret)
  61. h := hmac.New(sha256.New, key)
  62. h.Write([]byte(message))
  63. return hex.EncodeToString(h.Sum(nil))
  64. }
  65. func ParseSSOResponse(ssoData string) (SSOResponse, error) {
  66. decodedSsoData, err := base64.StdEncoding.DecodeString(ssoData)
  67. if err != nil {
  68. return SSOResponse{}, err
  69. }
  70. queryData, err := url.ParseQuery(string(decodedSsoData))
  71. if err != nil {
  72. return SSOResponse{}, err
  73. }
  74. id, _ := strconv.Atoi(queryData.Get("external_id"))
  75. response := SSOResponse{
  76. ID: id,
  77. Admin: (queryData.Get("admin") == "true"),
  78. Moderator: (queryData.Get("moderator") == "true"),
  79. Groups: strings.Split(queryData.Get("groups"), ","),
  80. Username: queryData.Get("username"),
  81. Nonce: queryData.Get("nonce"),
  82. }
  83. return response, nil
  84. }