Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ For instance, New Relic allows you to configure a saml provider (https://newreli

Ping Identity has a nice video for SAML here: https://www.pingidentity.com/resource-center/Introduction-to-SAML-Video.cfm

Shibboleth provides a test SAML IdP and SP endpoint at https://www.testshib.org/

Installation
------------

Expand Down
136 changes: 76 additions & 60 deletions authnrequest.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,11 @@
package saml

import (
"bytes"
"compress/flate"
"encoding/base64"
"encoding/xml"
"errors"
"fmt"
"github.com/nu7hatch/gouuid"
"io/ioutil"
Expand All @@ -27,6 +30,11 @@ import (
"time"
)

const (
BindingPOST = iota
BindingRedirect
)

func NewAuthorizationRequest(appSettings AppSettings, accountSettings AccountSettings) *AuthorizationRequest {
myIdUUID, err := uuid.NewV4()
if err != nil {
Expand All @@ -39,17 +47,25 @@ func NewAuthorizationRequest(appSettings AppSettings, accountSettings AccountSet
return &AuthorizationRequest{AccountSettings: accountSettings, AppSettings: appSettings, Id: "_" + myIdUUID.String(), IssueInstant: t}
}

// GetRequest returns a string formatted XML document that represents the SAML document
// TODO: parameterize more parts of the request
func (ar AuthorizationRequest) GetRequest(base64Encode bool) (string, error) {
func (ar AuthorizationRequest) BuildRequest(binding int) (AuthnRequest, error) {
bindings := map[int]string{
BindingPOST: "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST",
BindingRedirect: "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect",
}

bind, ok := bindings[binding]
if !ok {
return AuthnRequest{}, errors.New("invalid binding type requested")
}

d := AuthnRequest{
XMLName: xml.Name{
Local: "samlp:AuthnRequest",
},
SAMLP: "urn:oasis:names:tc:SAML:2.0:protocol",
SAML: "urn:oasis:names:tc:SAML:2.0:assertion",
ID: ar.Id,
ProtocolBinding: "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST",
ProtocolBinding: bind,
Version: "2.0",
AssertionConsumerServiceURL: ar.AppSettings.AssertionConsumerServiceURL,
Issuer: Issuer{
Expand Down Expand Up @@ -82,68 +98,43 @@ func (ar AuthorizationRequest) GetRequest(base64Encode bool) (string, error) {
},
},
}

return d, nil
}

// GetRequest returns a string formatted XML document that represents the SAML document
// TODO: parameterize more parts of the request
func (ar AuthorizationRequest) GetRequest(binding int) (string, error) {
d, err := ar.BuildRequest(binding)
if err != nil {
return "", err
}

b, err := xml.MarshalIndent(d, "", " ")
if err != nil {
return "", err
}

xmlAuthnRequest := fmt.Sprintf("<?xml version='1.0' encoding='UTF-8'?>\n%s", b)

if base64Encode {
data := []byte(xmlAuthnRequest)
return base64.StdEncoding.EncodeToString(data), nil
} else {
return string(xmlAuthnRequest), nil
}
return string(xmlAuthnRequest), nil
}

// GetSignedRequest returns a string formatted XML document that represents the SAML document
// TODO: parameterize more parts of the request
func (ar AuthorizationRequest) GetSignedRequest(base64Encode bool, publicCert string, privateCert string) (string, error) {
func (ar AuthorizationRequest) GetSignedRequest(binding int, publicCert string, privateCert string) (string, error) {
cert, err := LoadCertificate(publicCert)
if err != nil {
return "", err
}

base, err := ar.BuildRequest(binding)
if err != nil {
return "", err
}

d := AuthnSignedRequest{
XMLName: xml.Name{
Local: "samlp:AuthnRequest",
},
SAMLP: "urn:oasis:names:tc:SAML:2.0:protocol",
SAML: "urn:oasis:names:tc:SAML:2.0:assertion",
SAMLSIG: "http://www.w3.org/2000/09/xmldsig#",
ID: ar.Id,
ProtocolBinding: "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST",
Version: "2.0",
AssertionConsumerServiceURL: ar.AppSettings.AssertionConsumerServiceURL,
Issuer: Issuer{
XMLName: xml.Name{
Local: "saml:Issuer",
},
Url: ar.AppSettings.Issuer,
},
IssueInstant: ar.IssueInstant,
NameIDPolicy: NameIDPolicy{
XMLName: xml.Name{
Local: "samlp:NameIDPolicy",
},
AllowCreate: true,
Format: "urn:oasis:names:tc:SAML:2.0:nameid-format:transient",
},
RequestedAuthnContext: RequestedAuthnContext{
XMLName: xml.Name{
Local: "samlp:RequestedAuthnContext",
},
SAMLP: "urn:oasis:names:tc:SAML:2.0:protocol",
Comparison: "exact",
},
AuthnContextClassRef: AuthnContextClassRef{
XMLName: xml.Name{
Local: "saml:AuthnContextClassRef",
},
SAML: "urn:oasis:names:tc:SAML:2.0:assertion",
Transport: "urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport",
},
AuthnRequest: base,
SAMLSIG: "http://www.w3.org/2000/09/xmldsig#",
Signature: Signature{
XMLName: xml.Name{
Local: "samlsig:Signature",
Expand Down Expand Up @@ -216,7 +207,15 @@ func (ar AuthorizationRequest) GetSignedRequest(base64Encode bool, publicCert st
},
},
},
AuthnContextClassRef: AuthnContextClassRef{
XMLName: xml.Name{
Local: "saml:AuthnContextClassRef",
},
SAML: "urn:oasis:names:tc:SAML:2.0:assertion",
Transport: "urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport",
},
}

b, err := xml.MarshalIndent(d, "", " ")
if err != nil {
return "", err
Expand Down Expand Up @@ -252,29 +251,46 @@ func (ar AuthorizationRequest) GetSignedRequest(base64Encode bool, publicCert st
return "", err
}
samlSignedRequestXml := strings.Trim(string(samlSignedRequest), "\n")

if base64Encode {
data := []byte(samlSignedRequestXml)
return base64.StdEncoding.EncodeToString(data), nil
} else {
return string(samlSignedRequestXml), nil
}
return string(samlSignedRequestXml), nil
}

// String reqString = accSettings.getIdp_sso_target_url()+"?SAMLRequest=" +
// AuthRequest.getRidOfCRLF(URLEncoder.encode(authReq.getRequest(AuthRequest.base64),"UTF-8"));
func (ar AuthorizationRequest) GetRequestUrl() (string, error) {
func (ar AuthorizationRequest) GetRequestUrl(binding int) (string, error) {
u, err := url.Parse(ar.AccountSettings.IDP_SSO_Target_URL)
if err != nil {
return "", err
}
base64EncodedUTF8SamlRequest, err := ar.GetRequest(true)

request, err := ar.GetRequest(binding)
if err != nil {
return "", err
}

// Deflate
var deflated bytes.Buffer

fw, err := flate.NewWriter(&deflated, -1)
if err != nil {
return "", err
}

_, err = fw.Write([]byte(request))
if err != nil {
return "", err
}

err = fw.Close()
if err != nil {
return "", err
}

// base64
b64str := base64.StdEncoding.EncodeToString(deflated.Bytes())

// urlencode
q := u.Query()
q.Add("SAMLRequest", base64EncodedUTF8SamlRequest)
q.Add("SAMLRequest", b64str)

u.RawQuery = q.Encode()
return u.String(), nil
Expand Down
22 changes: 5 additions & 17 deletions authnrequestInterface.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,30 +35,18 @@ type AuthnRequest struct {
ProtocolBinding string `xml:"ProtocolBinding,attr"`
AssertionConsumerServiceURL string `xml:"AssertionConsumerServiceURL,attr,omitempty"`
IssueInstant string `xml:"IssueInstant,attr"`
AttributeConsumingServiceIndex int `xml:"AttributeConsumingServiceIndex,attr"`
AttributeConsumingServiceIndex int `xml:"AttributeConsumingServiceIndex,attr,omitemtpy"`
AssertionConsumerServiceIndex int `xml:"AssertionConsumerServiceIndex,attr,omitempty"`
Issuer Issuer `xml:"Issuer"`
NameIDPolicy NameIDPolicy `xml:"NameIDPolicy"`
RequestedAuthnContext RequestedAuthnContext `xml:"RequestedAuthnContext"`
}

type AuthnSignedRequest struct {
XMLName xml.Name
SAMLP string `xml:"xmlns:samlp,attr"`
SAML string `xml:"xmlns:saml,attr"`
SAMLSIG string `xml:"xmlns:samlsig,attr"`
ID string `xml:"ID,attr"`
Version string `xml:"Version,attr"`
ProtocolBinding string `xml:"ProtocolBinding,attr"`
AssertionConsumerServiceURL string `xml:"AssertionConsumerServiceURL,attr"`
IssueInstant string `xml:"IssueInstant,attr"`
AssertionConsumerServiceIndex int `xml:"AssertionConsumerServiceIndex,attr"`
AttributeConsumingServiceIndex int `xml:"AttributeConsumingServiceIndex,attr"`
Issuer Issuer `xml:"Issuer"`
NameIDPolicy NameIDPolicy `xml:"NameIDPolicy"`
RequestedAuthnContext RequestedAuthnContext `xml:"RequestedAuthnContext"`
AuthnContextClassRef AuthnContextClassRef `xml:"AuthnContextClassRef"`
Signature Signature `xml:"Signature"`
AuthnRequest
AuthnContextClassRef AuthnContextClassRef `xml:"AuthnContextClassRef"`
Signature Signature `xml:"Signature"`
SAMLSIG string `xml:"xmlns:samlsig,attr"`
}

type Issuer struct {
Expand Down