2017-05-09 00:43:48 +00:00
|
|
|
package sso
|
|
|
|
|
|
|
|
import (
|
|
|
|
"bytes"
|
|
|
|
"compress/flate"
|
|
|
|
"encoding/base64"
|
|
|
|
"encoding/xml"
|
2021-11-22 14:13:26 +00:00
|
|
|
"errors"
|
|
|
|
"fmt"
|
2021-11-23 13:25:43 +00:00
|
|
|
"io"
|
2017-05-09 00:43:48 +00:00
|
|
|
"net/url"
|
|
|
|
"time"
|
|
|
|
)
|
|
|
|
|
|
|
|
const (
|
|
|
|
samlVersion = "2.0"
|
|
|
|
cacheLifetime = 300 // five minutes
|
|
|
|
)
|
|
|
|
|
|
|
|
// RelayState sets optional relay state
|
|
|
|
func RelayState(v string) func(*opts) {
|
|
|
|
return func(o *opts) {
|
|
|
|
o.relayState = v
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
type opts struct {
|
|
|
|
relayState string
|
|
|
|
}
|
|
|
|
|
|
|
|
// CreateAuthorizationRequest creates a url suitable for use to satisfy the SAML
|
2021-11-23 13:25:43 +00:00
|
|
|
// redirect binding. It returns the URL of the identity provider, configured to
|
|
|
|
// initiate the authentication request.
|
2017-05-09 00:43:48 +00:00
|
|
|
// See http://docs.oasis-open.org/security/saml/v2.0/saml-bindings-2.0-os.pdf Section 3.4
|
|
|
|
func CreateAuthorizationRequest(settings *Settings, issuer string, options ...func(o *opts)) (string, error) {
|
|
|
|
var optionalParams opts
|
|
|
|
for _, opt := range options {
|
|
|
|
opt(&optionalParams)
|
|
|
|
}
|
|
|
|
if settings.Metadata == nil {
|
|
|
|
return "", errors.New("missing settings metadata")
|
|
|
|
}
|
2017-08-01 02:48:42 +00:00
|
|
|
requestID, err := generateSAMLValidID()
|
2017-05-09 00:43:48 +00:00
|
|
|
if err != nil {
|
2021-11-22 14:13:26 +00:00
|
|
|
return "", fmt.Errorf("creating auth request id: %w", err)
|
2017-05-09 00:43:48 +00:00
|
|
|
}
|
|
|
|
destinationURL, err := getDestinationURL(settings)
|
|
|
|
if err != nil {
|
2021-11-22 14:13:26 +00:00
|
|
|
return "", fmt.Errorf("creating auth request: %w", err)
|
2017-05-09 00:43:48 +00:00
|
|
|
}
|
|
|
|
request := AuthnRequest{
|
|
|
|
XMLName: xml.Name{
|
|
|
|
Local: "samlp:AuthnRequest",
|
|
|
|
},
|
2020-08-19 21:56:44 +00:00
|
|
|
ID: requestID,
|
|
|
|
SAMLP: "urn:oasis:names:tc:SAML:2.0:protocol",
|
|
|
|
SAML: "urn:oasis:names:tc:SAML:2.0:assertion",
|
2017-05-09 00:43:48 +00:00
|
|
|
AssertionConsumerServiceURL: settings.AssertionConsumerServiceURL,
|
|
|
|
Destination: destinationURL,
|
|
|
|
IssueInstant: time.Now().UTC().Format("2006-01-02T15:04:05Z"),
|
|
|
|
Version: samlVersion,
|
2021-06-06 23:58:23 +00:00
|
|
|
ProviderName: "Fleet",
|
2017-05-09 00:43:48 +00:00
|
|
|
Issuer: Issuer{
|
|
|
|
XMLName: xml.Name{
|
|
|
|
Local: "saml:Issuer",
|
|
|
|
},
|
|
|
|
Url: issuer,
|
|
|
|
},
|
|
|
|
}
|
2021-11-23 13:25:43 +00:00
|
|
|
|
2023-03-01 23:18:40 +00:00
|
|
|
var metadataWriter bytes.Buffer
|
|
|
|
err = xml.NewEncoder(&metadataWriter).Encode(settings.Metadata)
|
2017-05-09 00:43:48 +00:00
|
|
|
if err != nil {
|
2021-11-22 14:13:26 +00:00
|
|
|
return "", fmt.Errorf("encoding metadata creating auth request: %w", err)
|
2017-05-09 00:43:48 +00:00
|
|
|
}
|
2021-11-23 13:25:43 +00:00
|
|
|
|
2017-05-09 00:43:48 +00:00
|
|
|
// cache metadata so we can check the signatures on the response we get from the IDP
|
|
|
|
err = settings.SessionStore.create(requestID,
|
|
|
|
settings.OriginalURL,
|
2023-03-01 23:18:40 +00:00
|
|
|
metadataWriter.String(),
|
2017-05-09 00:43:48 +00:00
|
|
|
cacheLifetime,
|
|
|
|
)
|
|
|
|
if err != nil {
|
2021-11-23 13:25:43 +00:00
|
|
|
return "", fmt.Errorf("caching metadata while creating auth request: %w", err)
|
2017-05-09 00:43:48 +00:00
|
|
|
}
|
2021-11-23 13:25:43 +00:00
|
|
|
|
2017-05-09 00:43:48 +00:00
|
|
|
u, err := url.Parse(destinationURL)
|
|
|
|
if err != nil {
|
2021-11-22 14:13:26 +00:00
|
|
|
return "", fmt.Errorf("parsing destination url: %w", err)
|
2017-05-09 00:43:48 +00:00
|
|
|
}
|
|
|
|
qry := u.Query()
|
|
|
|
|
|
|
|
var writer bytes.Buffer
|
|
|
|
err = xml.NewEncoder(&writer).Encode(request)
|
|
|
|
if err != nil {
|
2021-11-22 14:13:26 +00:00
|
|
|
return "", fmt.Errorf("encoding auth request xml: %w", err)
|
2017-05-09 00:43:48 +00:00
|
|
|
}
|
2021-11-23 13:25:43 +00:00
|
|
|
|
2017-05-09 00:43:48 +00:00
|
|
|
authQueryVal, err := deflate(&writer)
|
|
|
|
if err != nil {
|
2021-11-22 14:13:26 +00:00
|
|
|
return "", fmt.Errorf("unable to compress auth info: %w", err)
|
2017-05-09 00:43:48 +00:00
|
|
|
}
|
2021-11-23 13:25:43 +00:00
|
|
|
|
2017-05-09 00:43:48 +00:00
|
|
|
qry.Set("SAMLRequest", authQueryVal)
|
|
|
|
if optionalParams.relayState != "" {
|
|
|
|
qry.Set("RelayState", optionalParams.relayState)
|
|
|
|
}
|
2021-11-23 13:25:43 +00:00
|
|
|
|
2017-05-09 00:43:48 +00:00
|
|
|
u.RawQuery = qry.Encode()
|
|
|
|
return u.String(), nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func getDestinationURL(settings *Settings) (string, error) {
|
|
|
|
for _, sso := range settings.Metadata.IDPSSODescriptor.SingleSignOnService {
|
|
|
|
if sso.Binding == RedirectBinding {
|
|
|
|
return sso.Location, nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return "", errors.New("IDP does not support redirect binding")
|
|
|
|
}
|
|
|
|
|
|
|
|
// See SAML Bindings http://docs.oasis-open.org/security/saml/v2.0/saml-bindings-2.0-os.pdf
|
|
|
|
// Section 3.4.4.1
|
|
|
|
func deflate(xmlBuffer *bytes.Buffer) (string, error) {
|
2019-04-09 16:23:22 +00:00
|
|
|
// Gzip
|
2017-05-09 00:43:48 +00:00
|
|
|
var deflated bytes.Buffer
|
|
|
|
writer, err := flate.NewWriter(&deflated, flate.DefaultCompression)
|
|
|
|
if err != nil {
|
2021-11-22 14:13:26 +00:00
|
|
|
return "", fmt.Errorf("create flate writer: %w", err)
|
2017-05-09 00:43:48 +00:00
|
|
|
}
|
2021-11-23 13:25:43 +00:00
|
|
|
|
|
|
|
count := xmlBuffer.Len()
|
|
|
|
n, err := io.Copy(writer, xmlBuffer)
|
2017-05-09 00:43:48 +00:00
|
|
|
if err != nil {
|
2019-04-09 16:23:22 +00:00
|
|
|
_ = writer.Close()
|
2021-11-22 14:13:26 +00:00
|
|
|
return "", fmt.Errorf("compressing auth request: %w", err)
|
2017-05-09 00:43:48 +00:00
|
|
|
}
|
2021-11-23 13:25:43 +00:00
|
|
|
|
|
|
|
if int(n) != count {
|
|
|
|
_ = writer.Close()
|
|
|
|
return "", errors.New("incomplete write during compression")
|
|
|
|
}
|
|
|
|
|
2019-04-09 16:23:22 +00:00
|
|
|
if err := writer.Close(); err != nil {
|
2021-11-22 14:13:26 +00:00
|
|
|
return "", fmt.Errorf("close flate writer: %w", err)
|
2019-04-09 16:23:22 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Base64
|
2017-05-09 00:43:48 +00:00
|
|
|
encbuff := deflated.Bytes()
|
|
|
|
encoded := base64.StdEncoding.EncodeToString(encbuff)
|
|
|
|
return encoded, nil
|
|
|
|
}
|