Files
mostlymatter/vendor/github.com/mattermost/gosaml2/build_request.go
Scott Bishel 3bccc03df3 MM-19872: Update dependencies for enterprise implementation of new SAML2 golib. (#13298)
* Update dependencies for enterprise implementation of new SAML2 golib.

* add indirect
2019-12-19 08:27:34 -07:00

227 строки
7.2 KiB
Go

package saml2
import (
"bytes"
"compress/flate"
"encoding/base64"
"fmt"
"net/http"
"net/url"
"github.com/beevik/etree"
"github.com/mattermost/gosaml2/uuid"
)
const issueInstantFormat = "2006-01-02T15:04:05Z"
func (sp *SAMLServiceProvider) buildAuthnRequest(includeSig bool) (*etree.Document, error) {
authnRequest := &etree.Element{
Space: "samlp",
Tag: "AuthnRequest",
}
authnRequest.CreateAttr("xmlns:samlp", "urn:oasis:names:tc:SAML:2.0:protocol")
authnRequest.CreateAttr("xmlns:saml", "urn:oasis:names:tc:SAML:2.0:assertion")
arId := uuid.NewV4()
authnRequest.CreateAttr("ID", "_"+arId.String())
authnRequest.CreateAttr("Version", "2.0")
authnRequest.CreateAttr("ProtocolBinding", "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST")
authnRequest.CreateAttr("AssertionConsumerServiceURL", sp.AssertionConsumerServiceURL)
authnRequest.CreateAttr("IssueInstant", sp.Clock.Now().UTC().Format(issueInstantFormat))
authnRequest.CreateAttr("Destination", sp.IdentityProviderSSOURL)
// NOTE(russell_h): In earlier versions we mistakenly sent the IdentityProviderIssuer
// in the AuthnRequest. For backwards compatibility we will fall back to that
// behavior when ServiceProviderIssuer isn't set.
if sp.ServiceProviderIssuer != "" {
authnRequest.CreateElement("saml:Issuer").SetText(sp.ServiceProviderIssuer)
} else {
authnRequest.CreateElement("saml:Issuer").SetText(sp.IdentityProviderIssuer)
}
nameIdPolicy := authnRequest.CreateElement("samlp:NameIDPolicy")
nameIdPolicy.CreateAttr("AllowCreate", "true")
nameIdPolicy.CreateAttr("Format", sp.NameIdFormat)
if sp.RequestedAuthnContext != nil {
requestedAuthnContext := authnRequest.CreateElement("samlp:RequestedAuthnContext")
requestedAuthnContext.CreateAttr("Comparison", sp.RequestedAuthnContext.Comparison)
for _, context := range sp.RequestedAuthnContext.Contexts {
authnContextClassRef := requestedAuthnContext.CreateElement("saml:AuthnContextClassRef")
authnContextClassRef.SetText(context)
}
}
if sp.ScopingIDPProviderId != "" && sp.ScopingIDPProviderName != "" {
scoping := authnRequest.CreateElement("samlp:Scoping")
idpList := scoping.CreateElement("samlp:IDPList")
idpEntry := idpList.CreateElement("samlp:IDPEntry")
idpEntry.CreateAttr("ProviderID", sp.ScopingIDPProviderId)
idpEntry.CreateAttr("Name", sp.ScopingIDPProviderName)
}
doc := etree.NewDocument()
// Only POST binding includes <Signature> in <AuthnRequest> (includeSig)
if sp.SignAuthnRequests && includeSig {
signed, err := sp.SignAuthnRequest(authnRequest)
if err != nil {
return nil, err
}
doc.SetRoot(signed)
} else {
doc.SetRoot(authnRequest)
}
return doc, nil
}
func (sp *SAMLServiceProvider) BuildAuthRequestDocument() (*etree.Document, error) {
return sp.buildAuthnRequest(true)
}
func (sp *SAMLServiceProvider) BuildAuthRequestDocumentNoSig() (*etree.Document, error) {
return sp.buildAuthnRequest(false)
}
// SignAuthnRequest takes a document, builds a signature, creates another document
// and inserts the signature in it. According to the schema, the position of the
// signature is right after the Issuer [1] then all other children.
//
// [1] https://docs.oasis-open.org/security/saml/v2.0/saml-schema-protocol-2.0.xsd
func (sp *SAMLServiceProvider) SignAuthnRequest(el *etree.Element) (*etree.Element, error) {
ctx := sp.SigningContext()
sig, err := ctx.ConstructSignature(el, true)
if err != nil {
return nil, err
}
ret := el.Copy()
var children []etree.Token
children = append(children, ret.Child[0]) // issuer is always first
children = append(children, sig) // next is the signature
children = append(children, ret.Child[1:]...) // then all other children
ret.Child = children
return ret, nil
}
// BuildAuthRequest builds <AuthnRequest> for identity provider
func (sp *SAMLServiceProvider) BuildAuthRequest() (string, error) {
doc, err := sp.BuildAuthRequestDocument()
if err != nil {
return "", err
}
return doc.WriteToString()
}
func (sp *SAMLServiceProvider) buildAuthURLFromDocument(relayState, binding string, doc *etree.Document) (string, error) {
parsedUrl, err := url.Parse(sp.IdentityProviderSSOURL)
if err != nil {
return "", err
}
authnRequest, err := doc.WriteToString()
if err != nil {
return "", err
}
buf := &bytes.Buffer{}
fw, err := flate.NewWriter(buf, flate.DefaultCompression)
if err != nil {
return "", fmt.Errorf("flate NewWriter error: %v", err)
}
_, err = fw.Write([]byte(authnRequest))
if err != nil {
return "", fmt.Errorf("flate.Writer Write error: %v", err)
}
err = fw.Close()
if err != nil {
return "", fmt.Errorf("flate.Writer Close error: %v", err)
}
qs := parsedUrl.Query()
qs.Add("SAMLRequest", base64.StdEncoding.EncodeToString(buf.Bytes()))
if relayState != "" {
qs.Add("RelayState", relayState)
}
if sp.SignAuthnRequests && binding == BindingHttpRedirect {
// Sign URL encoded query (see Section 3.4.4.1 DEFLATE Encoding of saml-bindings-2.0-os.pdf)
ctx := sp.SigningContext()
qs.Add("SigAlg", ctx.GetSignatureMethodIdentifier())
var rawSignature []byte
if rawSignature, err = ctx.SignString(signatureInputString(qs.Get("SAMLRequest"), qs.Get("RelayState"), qs.Get("SigAlg"))); err != nil {
return "", fmt.Errorf("unable to sign query string of redirect URL: %v", err)
}
// Now add base64 encoded Signature
qs.Add("Signature", base64.StdEncoding.EncodeToString(rawSignature))
}
parsedUrl.RawQuery = qs.Encode()
return parsedUrl.String(), nil
}
func (sp *SAMLServiceProvider) BuildAuthURLFromDocument(relayState string, doc *etree.Document) (string, error) {
return sp.buildAuthURLFromDocument(relayState, BindingHttpPost, doc)
}
func (sp *SAMLServiceProvider) BuildAuthURLRedirect(relayState string, doc *etree.Document) (string, error) {
return sp.buildAuthURLFromDocument(relayState, BindingHttpRedirect, doc)
}
// BuildAuthURL builds redirect URL to be sent to principal
func (sp *SAMLServiceProvider) BuildAuthURL(relayState string) (string, error) {
doc, err := sp.BuildAuthRequestDocument()
if err != nil {
return "", err
}
return sp.BuildAuthURLFromDocument(relayState, doc)
}
// AuthRedirect takes a ResponseWriter and Request from an http interaction and
// redirects to the SAMLServiceProvider's configured IdP, including the
// relayState provided, if any.
func (sp *SAMLServiceProvider) AuthRedirect(w http.ResponseWriter, r *http.Request, relayState string) (err error) {
url, err := sp.BuildAuthURL(relayState)
if err != nil {
return err
}
http.Redirect(w, r, url, http.StatusFound)
return nil
}
// signatureInputString constructs the string to be fed into the signature algorithm, as described
// in section 3.4.4.1 of
// https://www.oasis-open.org/committees/download.php/56779/sstc-saml-bindings-errata-2.0-wd-06.pdf
func signatureInputString(samlRequest, relayState, sigAlg string) string {
var params [][2]string
if relayState == "" {
params = [][2]string{{"SAMLRequest", samlRequest}, {"SigAlg", sigAlg}}
} else {
params = [][2]string{{"SAMLRequest", samlRequest}, {"RelayState", relayState}, {"SigAlg", sigAlg}}
}
var buf bytes.Buffer
for _, kv := range params {
k, v := kv[0], kv[1]
if buf.Len() > 0 {
buf.WriteByte('&')
}
buf.WriteString(url.QueryEscape(k) + "=" + url.QueryEscape(v))
}
return buf.String()
}