Improve SAML Signature and Response Validation
* Improve Order of Namespace Declarations and Attributes in Canonical XML. This is related to an issue in goxmldsig for which I created an [pull request](https://github.com/russellhaering/goxmldsig/pull/17). * Do not compress the AuthnRequest if `HTTP-POST` binding is used. * SAML Response is valid if the Message and/or the Assertion is signed. * Add `AssertionConsumerServiceURL` to `AuthnRequest` * Validate Status on the Response * Validate Conditions on the Assertion * Validation SubjectConfirmation on the Subject
This commit is contained in:
parent
a3ef8d26bc
commit
e46f2ebe40
@ -2,8 +2,6 @@
|
|||||||
package saml
|
package saml
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"compress/flate"
|
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
@ -36,6 +34,15 @@ const (
|
|||||||
nameIDFormatKerberos = "urn:oasis:names:tc:SAML:2.0:nameid-format:kerberos"
|
nameIDFormatKerberos = "urn:oasis:names:tc:SAML:2.0:nameid-format:kerberos"
|
||||||
nameIDFormatPersistent = "urn:oasis:names:tc:SAML:2.0:nameid-format:persistent"
|
nameIDFormatPersistent = "urn:oasis:names:tc:SAML:2.0:nameid-format:persistent"
|
||||||
nameIDformatTransient = "urn:oasis:names:tc:SAML:2.0:nameid-format:transient"
|
nameIDformatTransient = "urn:oasis:names:tc:SAML:2.0:nameid-format:transient"
|
||||||
|
|
||||||
|
// top level status codes
|
||||||
|
statusCodeSuccess = "urn:oasis:names:tc:SAML:2.0:status:Success"
|
||||||
|
|
||||||
|
// subject confirmation methods
|
||||||
|
subjectConfirmationMethodBearer = "urn:oasis:names:tc:SAML:2.0:cm:bearer"
|
||||||
|
|
||||||
|
// allowed clock drift for timestamp validation
|
||||||
|
allowedClockDrift = time.Duration(30) * time.Second
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@ -253,6 +260,7 @@ func (p *provider) POSTData(s connector.Scopes) (action, value string, err error
|
|||||||
AllowCreate: true,
|
AllowCreate: true,
|
||||||
Format: p.nameIDPolicyFormat,
|
Format: p.nameIDPolicyFormat,
|
||||||
},
|
},
|
||||||
|
AssertionConsumerServiceURL: p.redirectURI,
|
||||||
}
|
}
|
||||||
|
|
||||||
data, err := xml.MarshalIndent(r, "", " ")
|
data, err := xml.MarshalIndent(r, "", " ")
|
||||||
@ -260,19 +268,7 @@ func (p *provider) POSTData(s connector.Scopes) (action, value string, err error
|
|||||||
return "", "", fmt.Errorf("marshal authn request: %v", err)
|
return "", "", fmt.Errorf("marshal authn request: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
buff := new(bytes.Buffer)
|
return p.ssoURL, base64.StdEncoding.EncodeToString(data), nil
|
||||||
fw, err := flate.NewWriter(buff, flate.DefaultCompression)
|
|
||||||
if err != nil {
|
|
||||||
return "", "", fmt.Errorf("new flate writer: %v", err)
|
|
||||||
}
|
|
||||||
if _, err := fw.Write(data); err != nil {
|
|
||||||
return "", "", fmt.Errorf("compress message: %v", err)
|
|
||||||
}
|
|
||||||
if err := fw.Close(); err != nil {
|
|
||||||
return "", "", fmt.Errorf("flush message: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return p.ssoURL, base64.StdEncoding.EncodeToString(buff.Bytes()), nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *provider) HandlePOST(s connector.Scopes, samlResponse string) (ident connector.Identity, err error) {
|
func (p *provider) HandlePOST(s connector.Scopes, samlResponse string) (ident connector.Identity, err error) {
|
||||||
@ -296,6 +292,10 @@ func (p *provider) HandlePOST(s connector.Scopes, samlResponse string) (ident co
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err = p.validateStatus(&resp); err != nil {
|
||||||
|
return ident, err
|
||||||
|
}
|
||||||
|
|
||||||
assertion := resp.Assertion
|
assertion := resp.Assertion
|
||||||
if assertion == nil {
|
if assertion == nil {
|
||||||
return ident, fmt.Errorf("response did not contain an assertion")
|
return ident, fmt.Errorf("response did not contain an assertion")
|
||||||
@ -305,6 +305,13 @@ func (p *provider) HandlePOST(s connector.Scopes, samlResponse string) (ident co
|
|||||||
return ident, fmt.Errorf("response did not contain a subject")
|
return ident, fmt.Errorf("response did not contain a subject")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err = p.validateConditions(assertion); err != nil {
|
||||||
|
return ident, err
|
||||||
|
}
|
||||||
|
if err = p.validateSubjectConfirmation(subject); err != nil {
|
||||||
|
return ident, err
|
||||||
|
}
|
||||||
|
|
||||||
switch {
|
switch {
|
||||||
case subject.NameID != nil:
|
case subject.NameID != nil:
|
||||||
if ident.UserID = subject.NameID.Value; ident.UserID == "" {
|
if ident.UserID = subject.NameID.Value; ident.UserID == "" {
|
||||||
@ -348,19 +355,151 @@ func (p *provider) HandlePOST(s connector.Scopes, samlResponse string) (ident co
|
|||||||
return ident, nil
|
return ident, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Validate that the StatusCode of the Response is success.
|
||||||
|
// Otherwise return a human readable message to the end user
|
||||||
|
func (p *provider) validateStatus(resp *response) error {
|
||||||
|
// Status is mandatory in the Response type
|
||||||
|
status := resp.Status
|
||||||
|
if status == nil {
|
||||||
|
return fmt.Errorf("response did not contain a Status")
|
||||||
|
}
|
||||||
|
// StatusCode is mandatory in the Status type
|
||||||
|
statusCode := status.StatusCode
|
||||||
|
if statusCode == nil {
|
||||||
|
return fmt.Errorf("response did not contain a StatusCode")
|
||||||
|
}
|
||||||
|
if statusCode.Value != statusCodeSuccess {
|
||||||
|
parts := strings.Split(statusCode.Value, ":")
|
||||||
|
lastPart := parts[len(parts)-1]
|
||||||
|
errorMessage := fmt.Sprintf("status code of the Response was not Success, was %q", lastPart)
|
||||||
|
statusMessage := status.StatusMessage
|
||||||
|
if statusMessage != nil && statusMessage.Value != "" {
|
||||||
|
errorMessage += " -> " + statusMessage.Value
|
||||||
|
}
|
||||||
|
return fmt.Errorf(errorMessage)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Multiple subject SubjectConfirmation can be in the assertion
|
||||||
|
// and at least one SubjectConfirmation must be valid.
|
||||||
|
// This is described in the spec "Profiles for the OASIS Security
|
||||||
|
// Assertion Markup Language" in section 3.3 Bearer.
|
||||||
|
// see https://www.oasis-open.org/committees/download.php/35389/sstc-saml-profiles-errata-2.0-wd-06-diff.pdf
|
||||||
|
func (p *provider) validateSubjectConfirmation(subject *subject) error {
|
||||||
|
validSubjectConfirmation := false
|
||||||
|
subjectConfirmations := subject.SubjectConfirmations
|
||||||
|
if subjectConfirmations != nil && len(subjectConfirmations) > 0 {
|
||||||
|
for _, subjectConfirmation := range subjectConfirmations {
|
||||||
|
// skip if method is wrong
|
||||||
|
method := subjectConfirmation.Method
|
||||||
|
if method != "" && method != subjectConfirmationMethodBearer {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
subjectConfirmationData := subjectConfirmation.SubjectConfirmationData
|
||||||
|
if subjectConfirmationData == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
inResponseTo := subjectConfirmationData.InResponseTo
|
||||||
|
if inResponseTo != "" {
|
||||||
|
// TODO also validate InResponseTo if present
|
||||||
|
}
|
||||||
|
// only validate that subjectConfirmationData is not expired
|
||||||
|
now := p.now()
|
||||||
|
notOnOrAfter := time.Time(subjectConfirmationData.NotOnOrAfter)
|
||||||
|
if !notOnOrAfter.IsZero() {
|
||||||
|
if now.After(notOnOrAfter) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// validate recipient if present
|
||||||
|
recipient := subjectConfirmationData.Recipient
|
||||||
|
if recipient != "" && recipient != p.redirectURI {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
validSubjectConfirmation = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !validSubjectConfirmation {
|
||||||
|
return fmt.Errorf("no valid SubjectConfirmation was found on this Response")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validates the Conditions element and all of it's content
|
||||||
|
func (p *provider) validateConditions(assertion *assertion) error {
|
||||||
|
// Checks if a Conditions element exists
|
||||||
|
conditions := assertion.Conditions
|
||||||
|
if conditions == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
// Validates Assertion timestamps
|
||||||
|
now := p.now()
|
||||||
|
notBefore := time.Time(conditions.NotBefore)
|
||||||
|
if !notBefore.IsZero() {
|
||||||
|
if now.Add(allowedClockDrift).Before(notBefore) {
|
||||||
|
return fmt.Errorf("at %s got response that cannot be processed before %s", now, notBefore)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
notOnOrAfter := time.Time(conditions.NotOnOrAfter)
|
||||||
|
if !notOnOrAfter.IsZero() {
|
||||||
|
if now.After(notOnOrAfter.Add(allowedClockDrift)) {
|
||||||
|
return fmt.Errorf("at %s got response that cannot be processed because it expired at %s", now, notOnOrAfter)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Validates audience
|
||||||
|
audienceRestriction := conditions.AudienceRestriction
|
||||||
|
if audienceRestriction != nil {
|
||||||
|
audiences := audienceRestriction.Audiences
|
||||||
|
if audiences != nil && len(audiences) > 0 {
|
||||||
|
issuerInAudiences := false
|
||||||
|
for _, audience := range audiences {
|
||||||
|
if audience.Value == p.issuer {
|
||||||
|
issuerInAudiences = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !issuerInAudiences {
|
||||||
|
return fmt.Errorf("required audience %s was not in Response audiences %s", p.issuer, audiences)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// verify checks the signature info of a XML document and returns
|
// verify checks the signature info of a XML document and returns
|
||||||
// the signed elements.
|
// the signed elements.
|
||||||
|
// The Validate function of the goxmldsig library only looks for
|
||||||
|
// signatures on the root element level. But a saml Response is valid
|
||||||
|
// if the complete message is signed, or only the Assertion is signed,
|
||||||
|
// or but elements are signed. Therefore we first check a possible
|
||||||
|
// signature of the Response than of the Assertion. If one of these
|
||||||
|
// is successful the Response is considered as valid.
|
||||||
func verify(validator *dsig.ValidationContext, data []byte) (signed []byte, err error) {
|
func verify(validator *dsig.ValidationContext, data []byte) (signed []byte, err error) {
|
||||||
doc := etree.NewDocument()
|
doc := etree.NewDocument()
|
||||||
if err := doc.ReadFromBytes(data); err != nil {
|
if err = doc.ReadFromBytes(data); err != nil {
|
||||||
return nil, fmt.Errorf("parse document: %v", err)
|
return nil, fmt.Errorf("parse document: %v", err)
|
||||||
}
|
}
|
||||||
|
verified := false
|
||||||
result, err := validator.Validate(doc.Root())
|
response := doc.Root()
|
||||||
if err != nil {
|
transformedResponse, err := validator.Validate(response)
|
||||||
return nil, err
|
if err == nil {
|
||||||
|
verified = true
|
||||||
|
doc.SetRoot(transformedResponse)
|
||||||
|
}
|
||||||
|
assertion := response.SelectElement("Assertion")
|
||||||
|
if assertion == nil {
|
||||||
|
return nil, fmt.Errorf("response does not contain an Assertion element")
|
||||||
|
}
|
||||||
|
transformedAssertion, err := validator.Validate(assertion)
|
||||||
|
if err == nil {
|
||||||
|
verified = true
|
||||||
|
response.RemoveChild(assertion)
|
||||||
|
response.AddChild(transformedAssertion)
|
||||||
|
}
|
||||||
|
if verified != true {
|
||||||
|
return nil, fmt.Errorf("response does not contain a valid Signature element")
|
||||||
}
|
}
|
||||||
doc.SetRoot(result)
|
|
||||||
return doc.WriteToBytes()
|
return doc.WriteToBytes()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,12 +2,24 @@ package saml
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
|
"encoding/base64"
|
||||||
"encoding/pem"
|
"encoding/pem"
|
||||||
"errors"
|
"errors"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
sdig "github.com/russellhaering/goxmldsig"
|
"github.com/Sirupsen/logrus"
|
||||||
|
|
||||||
|
dsig "github.com/russellhaering/goxmldsig"
|
||||||
|
|
||||||
|
"github.com/coreos/dex/connector"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
defaultIssuer = "http://localhost:5556/dex/callback"
|
||||||
|
defaultRedirectURI = "http://localhost:5556/dex/callback"
|
||||||
)
|
)
|
||||||
|
|
||||||
func loadCert(ca string) (*x509.Certificate, error) {
|
func loadCert(ca string) (*x509.Certificate, error) {
|
||||||
@ -22,21 +34,238 @@ func loadCert(ca string) (*x509.Certificate, error) {
|
|||||||
return x509.ParseCertificate(block.Bytes)
|
return x509.ParseCertificate(block.Bytes)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestVerify(t *testing.T) {
|
func runVerify(t *testing.T, ca string, resp string, shouldSucceed bool) {
|
||||||
cert, err := loadCert("testdata/okta-ca.pem")
|
cert, err := loadCert(ca)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
s := certStore{[]*x509.Certificate{cert}}
|
s := certStore{[]*x509.Certificate{cert}}
|
||||||
|
|
||||||
validator := sdig.NewDefaultValidationContext(s)
|
validator := dsig.NewDefaultValidationContext(s)
|
||||||
|
|
||||||
data, err := ioutil.ReadFile("testdata/okta-resp.xml")
|
data, err := ioutil.ReadFile(resp)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err := verify(validator, data); err != nil {
|
if _, err := verify(validator, data); err != nil {
|
||||||
|
if shouldSucceed {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
if !shouldSucceed {
|
||||||
|
t.Fatalf("expected an invalid signatrue but verification has been successful")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func newProvider(issuer string, redirectURI string) *provider {
|
||||||
|
if issuer == "" {
|
||||||
|
issuer = defaultIssuer
|
||||||
|
}
|
||||||
|
if redirectURI == "" {
|
||||||
|
redirectURI = defaultRedirectURI
|
||||||
|
}
|
||||||
|
now, _ := time.Parse(time.RFC3339, "2017-01-24T20:48:41Z")
|
||||||
|
timeFunc := func() time.Time { return now }
|
||||||
|
return &provider{
|
||||||
|
issuer: issuer,
|
||||||
|
ssoURL: "http://idp.org/saml/sso",
|
||||||
|
now: timeFunc,
|
||||||
|
usernameAttr: "user",
|
||||||
|
emailAttr: "email",
|
||||||
|
redirectURI: redirectURI,
|
||||||
|
logger: logrus.New(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestVerify(t *testing.T) {
|
||||||
|
runVerify(t, "testdata/okta-ca.pem", "testdata/okta-resp.xml", true)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestVerifySignedMessageAndUnsignedAssertion(t *testing.T) {
|
||||||
|
runVerify(t, "testdata/idp-cert.pem", "testdata/idp-resp-signed-message.xml", true)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestVerifyUnsignedMessageAndSignedAssertion(t *testing.T) {
|
||||||
|
runVerify(t, "testdata/idp-cert.pem", "testdata/idp-resp-signed-assertion.xml", true)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestVerifySignedMessageAndSignedAssertion(t *testing.T) {
|
||||||
|
runVerify(t, "testdata/idp-cert.pem", "testdata/idp-resp-signed-message-and-assertion.xml", true)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestVerifyUnsignedMessageAndUnsignedAssertion(t *testing.T) {
|
||||||
|
runVerify(t, "testdata/idp-cert.pem", "testdata/idp-resp.xml", false)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHandlePOST(t *testing.T) {
|
||||||
|
p := newProvider("", "")
|
||||||
|
scopes := connector.Scopes{
|
||||||
|
OfflineAccess: false,
|
||||||
|
Groups: true,
|
||||||
|
}
|
||||||
|
data, err := ioutil.ReadFile("testdata/idp-resp.xml")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
ident, err := p.HandlePOST(scopes, base64.StdEncoding.EncodeToString(data))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if ident.UserID != "eric.chiang+okta@coreos.com" {
|
||||||
|
t.Fatalf("unexpected UserID %q", ident.UserID)
|
||||||
|
}
|
||||||
|
if ident.Username != "admin" {
|
||||||
|
t.Fatalf("unexpected Username: %q", ident.UserID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestValidateStatus(t *testing.T) {
|
||||||
|
p := newProvider("", "")
|
||||||
|
var err error
|
||||||
|
resp := response{}
|
||||||
|
// Test missing Status element
|
||||||
|
err = p.validateStatus(&resp)
|
||||||
|
if err == nil || !strings.HasSuffix(err.Error(), `Status`) {
|
||||||
|
t.Fatalf("validation should fail with missing Status")
|
||||||
|
}
|
||||||
|
// Test missing StatusCode element
|
||||||
|
resp.Status = &status{}
|
||||||
|
err = p.validateStatus(&resp)
|
||||||
|
if err == nil || !strings.HasSuffix(err.Error(), `StatusCode`) {
|
||||||
|
t.Fatalf("validation should fail with missing StatusCode")
|
||||||
|
}
|
||||||
|
// Test failed request without StatusMessage
|
||||||
|
resp.Status.StatusCode = &statusCode{
|
||||||
|
Value: ":Requester",
|
||||||
|
}
|
||||||
|
err = p.validateStatus(&resp)
|
||||||
|
if err == nil || !strings.HasSuffix(err.Error(), `"Requester"`) {
|
||||||
|
t.Fatalf("validation should fail with code %q", "Requester")
|
||||||
|
}
|
||||||
|
// Test failed request with StatusMessage
|
||||||
|
resp.Status.StatusMessage = &statusMessage{
|
||||||
|
Value: "Failed",
|
||||||
|
}
|
||||||
|
err = p.validateStatus(&resp)
|
||||||
|
if err == nil || !strings.HasSuffix(err.Error(), `"Requester" -> Failed`) {
|
||||||
|
t.Fatalf("validation should fail with code %q and message %q", "Requester", "Failed")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestValidateSubjectConfirmation(t *testing.T) {
|
||||||
|
p := newProvider("", "")
|
||||||
|
var err error
|
||||||
|
var notAfter time.Time
|
||||||
|
subj := &subject{}
|
||||||
|
// Subject without any SubjectConfirmation
|
||||||
|
err = p.validateSubjectConfirmation(subj)
|
||||||
|
if err == nil {
|
||||||
|
t.Fatalf("validation of %q should fail", "Subject without any SubjectConfirmation")
|
||||||
|
}
|
||||||
|
// SubjectConfirmation without Method and SubjectConfirmationData
|
||||||
|
subj.SubjectConfirmations = []subjectConfirmation{subjectConfirmation{}}
|
||||||
|
err = p.validateSubjectConfirmation(subj)
|
||||||
|
if err == nil {
|
||||||
|
t.Fatalf("validation of %q should fail", "SubjectConfirmation without Method and SubjectConfirmationData")
|
||||||
|
}
|
||||||
|
// SubjectConfirmation with invalid Method and no SubjectConfirmationData
|
||||||
|
subj.SubjectConfirmations = []subjectConfirmation{subjectConfirmation{
|
||||||
|
Method: "invalid",
|
||||||
|
}}
|
||||||
|
err = p.validateSubjectConfirmation(subj)
|
||||||
|
if err == nil {
|
||||||
|
t.Fatalf("validation of %q should fail", "SubjectConfirmation with invalid Method and no SubjectConfirmationData")
|
||||||
|
}
|
||||||
|
// SubjectConfirmation with valid Method and empty SubjectConfirmationData
|
||||||
|
subjConfirmationData := subjectConfirmationData{}
|
||||||
|
subj.SubjectConfirmations = []subjectConfirmation{subjectConfirmation{
|
||||||
|
Method: "urn:oasis:names:tc:SAML:2.0:cm:bearer",
|
||||||
|
SubjectConfirmationData: &subjConfirmationData,
|
||||||
|
}}
|
||||||
|
err = p.validateSubjectConfirmation(subj)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("validation of %q should succeed", "SubjectConfirmation with valid Method and empty SubjectConfirmationData")
|
||||||
|
}
|
||||||
|
// SubjectConfirmationData with invalid Recipient
|
||||||
|
subjConfirmationData.Recipient = "invalid"
|
||||||
|
err = p.validateSubjectConfirmation(subj)
|
||||||
|
if err == nil {
|
||||||
|
t.Fatalf("validation of %q should fail", "SubjectConfirmationData with invalid Recipient")
|
||||||
|
}
|
||||||
|
// expired SubjectConfirmationData
|
||||||
|
notAfter = p.now().Add(-time.Duration(60) * time.Second)
|
||||||
|
subjConfirmationData.NotOnOrAfter = xmlTime(notAfter)
|
||||||
|
subjConfirmationData.Recipient = defaultRedirectURI
|
||||||
|
err = p.validateSubjectConfirmation(subj)
|
||||||
|
if err == nil {
|
||||||
|
t.Fatalf("validation of %q should fail", " expired SubjectConfirmationData")
|
||||||
|
}
|
||||||
|
// valid SubjectConfirmationData
|
||||||
|
notAfter = p.now().Add(+time.Duration(60) * time.Second)
|
||||||
|
subjConfirmationData.NotOnOrAfter = xmlTime(notAfter)
|
||||||
|
subjConfirmationData.Recipient = defaultRedirectURI
|
||||||
|
err = p.validateSubjectConfirmation(subj)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("validation of %q should succed", "valid SubjectConfirmationData")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestValidateConditions(t *testing.T) {
|
||||||
|
p := newProvider("", "")
|
||||||
|
var err error
|
||||||
|
var notAfter, notBefore time.Time
|
||||||
|
cond := conditions{
|
||||||
|
AudienceRestriction: &audienceRestriction{},
|
||||||
|
}
|
||||||
|
assert := &assertion{}
|
||||||
|
// Assertion without Conditions
|
||||||
|
err = p.validateConditions(assert)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("validation of %q should succeed", "Assertion without Conditions")
|
||||||
|
}
|
||||||
|
// Assertion with empty Conditions
|
||||||
|
assert.Conditions = &cond
|
||||||
|
err = p.validateConditions(assert)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("validation of %q should succeed", "Assertion with empty Conditions")
|
||||||
|
}
|
||||||
|
// Conditions with valid timestamps
|
||||||
|
notBefore = p.now().Add(-time.Duration(60) * time.Second)
|
||||||
|
notAfter = p.now().Add(+time.Duration(60) * time.Second)
|
||||||
|
cond.NotBefore = xmlTime(notBefore)
|
||||||
|
cond.NotOnOrAfter = xmlTime(notAfter)
|
||||||
|
err = p.validateConditions(assert)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("validation of %q should succeed", "Conditions with valid timestamps")
|
||||||
|
}
|
||||||
|
// Conditions where notBefore is 45 seconds after now
|
||||||
|
notBefore = p.now().Add(+time.Duration(45) * time.Second)
|
||||||
|
cond.NotBefore = xmlTime(notBefore)
|
||||||
|
err = p.validateConditions(assert)
|
||||||
|
if err == nil {
|
||||||
|
t.Fatalf("validation of %q should fail", "Conditions where notBefore is 45 seconds after now")
|
||||||
|
}
|
||||||
|
// Conditions where notBefore is 15 seconds after now
|
||||||
|
notBefore = p.now().Add(+time.Duration(15) * time.Second)
|
||||||
|
cond.NotBefore = xmlTime(notBefore)
|
||||||
|
err = p.validateConditions(assert)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("validation of %q should succeed", "Conditions where notBefore is 15 seconds after now")
|
||||||
|
}
|
||||||
|
// Audiences contains the issuer
|
||||||
|
validAudience := audience{Value: p.issuer}
|
||||||
|
cond.AudienceRestriction.Audiences = []audience{validAudience}
|
||||||
|
err = p.validateConditions(assert)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("validation of %q should succeed", "Audiences contains the issuer")
|
||||||
|
}
|
||||||
|
// Audiences is not empty and not contains the issuer
|
||||||
|
invalidAudience := audience{Value: "invalid"}
|
||||||
|
cond.AudienceRestriction.Audiences = []audience{invalidAudience}
|
||||||
|
err = p.validateConditions(assert)
|
||||||
|
if err == nil {
|
||||||
|
t.Fatalf("validation of %q should succeed", "Audiences is not empty and not contains the issuer")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
26
connector/saml/testdata/idp-cert.pem
vendored
Normal file
26
connector/saml/testdata/idp-cert.pem
vendored
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIIEUTCCAzmgAwIBAgIJAJdmunb39nFKMA0GCSqGSIb3DQEBCwUAMHgxCzAJBgNV
|
||||||
|
BAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMQwwCgYDVQQKEwNJRFAxFDASBgNV
|
||||||
|
BAsTC1NTT1Byb3ZpZGVyMRMwEQYDVQQDEwpkZXYtOTY5MjQ0MRswGQYJKoZIhvcN
|
||||||
|
AQkBFgxpbmZvQGlkcC5vcmcwHhcNMTcwMTI0MTczMTI3WhcNMjcwMTIyMTczMTI3
|
||||||
|
WjB4MQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEMMAoGA1UEChMD
|
||||||
|
SURQMRQwEgYDVQQLEwtTU09Qcm92aWRlcjETMBEGA1UEAxMKZGV2LTk2OTI0NDEb
|
||||||
|
MBkGCSqGSIb3DQEJARYMaW5mb0BpZHAub3JnMIIBIjANBgkqhkiG9w0BAQEFAAOC
|
||||||
|
AQ8AMIIBCgKCAQEA0X/AE1tmDmhGRROAWaJ82XSORivRfgNt9Fb4rLrf6nIJsQN3
|
||||||
|
vNb1Nk4DSUEDdQuvHNaEemSVkSPgfq5qnhh37bJaghr0728J8dOyYzV5eArPvsby
|
||||||
|
CRcnhXQzpCK2zvHwjgxNJMsNJLbnYpG/U+dCdCtcOOn9JEhKO8wKn06y2tcrvC1u
|
||||||
|
uVs7bodukPUNq82KJTyvCQP8jh1hEZXeR2siJFDeJj1n2FNTMeCKIqOb42J/i+sB
|
||||||
|
TlyK3mV5Ni++hI/ssIYVbPwrMIBd6sKLVAgInshBHOj/7XcXW/rMf468YtBKs4Xn
|
||||||
|
XsE3hLoU02aWCRDlVHa4hm3jfIAqEADOUumklQIDAQABo4HdMIHaMB0GA1UdDgQW
|
||||||
|
BBRjN/dQSvhZxIsHTXmDKQJkPrjp0TCBqgYDVR0jBIGiMIGfgBRjN/dQSvhZxIsH
|
||||||
|
TXmDKQJkPrjp0aF8pHoweDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCkNhbGlmb3Ju
|
||||||
|
aWExDDAKBgNVBAoTA0lEUDEUMBIGA1UECxMLU1NPUHJvdmlkZXIxEzARBgNVBAMT
|
||||||
|
CmRldi05NjkyNDQxGzAZBgkqhkiG9w0BCQEWDGluZm9AaWRwLm9yZ4IJAJdmunb3
|
||||||
|
9nFKMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBAIqHUglIUAA+BKMW
|
||||||
|
6B0Q+cqIgDr9fWlsvDwIVK7/cvUeGIH3icSsje9AVZ4nQOJpxmC/E06HfuDXmbT1
|
||||||
|
wG16jNo01mPW9qaOGRJuQqlZdegCSF385o/OHcbaEKBRwyYuvLfu80EREj8wcMUK
|
||||||
|
FpExoaxK7K8DS7hh3w7exLB80jyhIaDEYc1hdyAl+206XpOXSYBetsg7I622R2+a
|
||||||
|
jSL7ygUxQjmKQ5DyInPdXzCFCL6Ew/BN0dwzfnBEEK223ruOWBLpj13zMC077dor
|
||||||
|
/NgYyHZU6iqiDS2eYO5jhVMve/mP9734+6N34seQRmekfmsf2dJcEQhPVYr/j0De
|
||||||
|
Jc3men4=
|
||||||
|
-----END CERTIFICATE-----
|
29
connector/saml/testdata/idp-resp-signed-assertion.xml
vendored
Normal file
29
connector/saml/testdata/idp-resp-signed-assertion.xml
vendored
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
<Response xmlns="urn:oasis:names:tc:SAML:2.0:protocol" Destination="http://localhost:5556/dex/callback" ID="id108965453120986171998428970" InResponseTo="_fd1b3ef9-ec09-44a7-a66b-0d39c250f6a0" IssueInstant="2016-12-20T22:18:23.771Z" Version="2.0">
|
||||||
|
<Issuer xmlns="urn:oasis:names:tc:SAML:2.0:assertion" Format="urn:oasis:names:tc:SAML:2.0:nameid-format:entity">http://www.okta.com/exk91cb99lKkKSYoy0h7</Issuer>
|
||||||
|
<Status>
|
||||||
|
<StatusCode Value="urn:oasis:names:tc:SAML:2.0:status:Success"/>
|
||||||
|
</Status>
|
||||||
|
<Assertion xmlns="urn:oasis:names:tc:SAML:2.0:assertion" ID="pfxe4534a5f-0f40-2f3a-599d-4dfd123f7d0a" IssueInstant="2016-12-20T22:18:23.771Z" Version="2.0">
|
||||||
|
<Issuer Format="urn:oasis:names:tc:SAML:2.0:nameid-format:entity">http://www.okta.com/exk91cb99lKkKSYoy0h7</Issuer><ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
|
||||||
|
<ds:SignedInfo><ds:CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
|
||||||
|
<ds:SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1"/>
|
||||||
|
<ds:Reference URI="#pfxe4534a5f-0f40-2f3a-599d-4dfd123f7d0a"><ds:Transforms><ds:Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"/><ds:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/></ds:Transforms><ds:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/><ds:DigestValue>HFNooGfpAONF7T96W3bFsXkH51k=</ds:DigestValue></ds:Reference></ds:SignedInfo><ds:SignatureValue>dI0QBihhNT5rtRYE9iB0lEKXkE7Yr4+QueOItRH2RcKwAXJ6DA/m3D/S7qwXk00Hn8ZpHu48ZO+HJpyweEEh2UuUWJCCTwwggagKybbSoRx3UTnSuNAFTdoDWTGt89z8j4+gRMC0sepYwppF3u87vJKRVBh8HjFfrHmWsZKwNtfoeXOOFCeatwxcI1sKCoBs2fTn78683ThoAJe3pygipSHY5WPt4dfT/yAY5Ars+OPY/N02M80OfIygZXdJwND0tVPJIF3M9DaehSkvCBHs7QA7DARsRXcuXdsYY7R8wHzqDVJZ4OvcsprONamm5AgUIpql1CjT94rFwWOFyxF2tg==</ds:SignatureValue>
|
||||||
|
<ds:KeyInfo><ds:X509Data><ds:X509Certificate>MIIEUTCCAzmgAwIBAgIJAJdmunb39nFKMA0GCSqGSIb3DQEBCwUAMHgxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMQwwCgYDVQQKEwNJRFAxFDASBgNVBAsTC1NTT1Byb3ZpZGVyMRMwEQYDVQQDEwpkZXYtOTY5MjQ0MRswGQYJKoZIhvcNAQkBFgxpbmZvQGlkcC5vcmcwHhcNMTcwMTI0MTczMTI3WhcNMjcwMTIyMTczMTI3WjB4MQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEMMAoGA1UEChMDSURQMRQwEgYDVQQLEwtTU09Qcm92aWRlcjETMBEGA1UEAxMKZGV2LTk2OTI0NDEbMBkGCSqGSIb3DQEJARYMaW5mb0BpZHAub3JnMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0X/AE1tmDmhGRROAWaJ82XSORivRfgNt9Fb4rLrf6nIJsQN3vNb1Nk4DSUEDdQuvHNaEemSVkSPgfq5qnhh37bJaghr0728J8dOyYzV5eArPvsbyCRcnhXQzpCK2zvHwjgxNJMsNJLbnYpG/U+dCdCtcOOn9JEhKO8wKn06y2tcrvC1uuVs7bodukPUNq82KJTyvCQP8jh1hEZXeR2siJFDeJj1n2FNTMeCKIqOb42J/i+sBTlyK3mV5Ni++hI/ssIYVbPwrMIBd6sKLVAgInshBHOj/7XcXW/rMf468YtBKs4XnXsE3hLoU02aWCRDlVHa4hm3jfIAqEADOUumklQIDAQABo4HdMIHaMB0GA1UdDgQWBBRjN/dQSvhZxIsHTXmDKQJkPrjp0TCBqgYDVR0jBIGiMIGfgBRjN/dQSvhZxIsHTXmDKQJkPrjp0aF8pHoweDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWExDDAKBgNVBAoTA0lEUDEUMBIGA1UECxMLU1NPUHJvdmlkZXIxEzARBgNVBAMTCmRldi05NjkyNDQxGzAZBgkqhkiG9w0BCQEWDGluZm9AaWRwLm9yZ4IJAJdmunb39nFKMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBAIqHUglIUAA+BKMW6B0Q+cqIgDr9fWlsvDwIVK7/cvUeGIH3icSsje9AVZ4nQOJpxmC/E06HfuDXmbT1wG16jNo01mPW9qaOGRJuQqlZdegCSF385o/OHcbaEKBRwyYuvLfu80EREj8wcMUKFpExoaxK7K8DS7hh3w7exLB80jyhIaDEYc1hdyAl+206XpOXSYBetsg7I622R2+ajSL7ygUxQjmKQ5DyInPdXzCFCL6Ew/BN0dwzfnBEEK223ruOWBLpj13zMC077dor/NgYyHZU6iqiDS2eYO5jhVMve/mP9734+6N34seQRmekfmsf2dJcEQhPVYr/j0DeJc3men4=</ds:X509Certificate></ds:X509Data></ds:KeyInfo></ds:Signature>
|
||||||
|
<Subject>
|
||||||
|
<NameID Format="urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified">eric.chiang+okta@coreos.com</NameID>
|
||||||
|
<SubjectConfirmation Method="urn:oasis:names:tc:SAML:2.0:cm:bearer">
|
||||||
|
<SubjectConfirmationData InResponseTo="_fd1b3ef9-ec09-44a7-a66b-0d39c250f6a0" NotOnOrAfter="2116-12-20T22:23:23.772Z" Recipient="http://localhost:5556/dex/callback"/>
|
||||||
|
</SubjectConfirmation>
|
||||||
|
</Subject>
|
||||||
|
<Conditions NotBefore="2016-12-20T22:13:23.772Z" NotOnOrAfter="2116-12-20T22:23:23.772Z">
|
||||||
|
<AudienceRestriction>
|
||||||
|
<Audience>http://localhost:5556/dex/callback</Audience>
|
||||||
|
</AudienceRestriction>
|
||||||
|
</Conditions>
|
||||||
|
<AuthnStatement AuthnInstant="2016-12-20T22:18:23.771Z" SessionIndex="_fd1b3ef9-ec09-44a7-a66b-0d39c250f6a0">
|
||||||
|
<AuthnContext>
|
||||||
|
<AuthnContextClassRef>urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport</AuthnContextClassRef>
|
||||||
|
</AuthnContext>
|
||||||
|
</AuthnStatement>
|
||||||
|
</Assertion>
|
||||||
|
</Response>
|
34
connector/saml/testdata/idp-resp-signed-message-and-assertion.xml
vendored
Normal file
34
connector/saml/testdata/idp-resp-signed-message-and-assertion.xml
vendored
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<Response xmlns="urn:oasis:names:tc:SAML:2.0:protocol" Destination="http://localhost:5556/dex/callback" ID="pfxb2d65771-7c81-6391-4e1f-79211ae3a3fd" InResponseTo="_fd1b3ef9-ec09-44a7-a66b-0d39c250f6a0" IssueInstant="2016-12-20T22:18:23.771Z" Version="2.0"><ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
|
||||||
|
<ds:SignedInfo><ds:CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
|
||||||
|
<ds:SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1"/>
|
||||||
|
<ds:Reference URI="#pfxb2d65771-7c81-6391-4e1f-79211ae3a3fd"><ds:Transforms><ds:Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"/><ds:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/></ds:Transforms><ds:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/><ds:DigestValue>P2k0nQ19ZcowlcaOz6do6Tyu8WI=</ds:DigestValue></ds:Reference></ds:SignedInfo><ds:SignatureValue>ytdy1qRPdMeIGnlkkaeLdblzTPtIFc0EJNm8WktsNU1Mn6G/6AmNaXEUik2BkTpk8zKabHdSf6+le8hwRiyfNWPTF84lzVdMjQ/+I8pnX/srpG534zoSAsP6ZFQvHp46AHPx31KP75H/ymqx2DNppqxh8JjUeMKQkPUEqduWUZ4kFjcsrz9H3MNVsHfxntnswibiknU/wAthtBuY2I6yOIF55RprUgYb5j2TqDd3IArF6LkxWRvHvhaw66MdhY1iiit7AFOcuHJVyPe8Attra94jwM+O1Ch+HQgoI43nX91d/jkP0vyWzWD8Xkcwb+KuRPsQflxjV22UU0+JbwrBYA==</ds:SignatureValue>
|
||||||
|
<ds:KeyInfo><ds:X509Data><ds:X509Certificate>MIIEUTCCAzmgAwIBAgIJAJdmunb39nFKMA0GCSqGSIb3DQEBCwUAMHgxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMQwwCgYDVQQKEwNJRFAxFDASBgNVBAsTC1NTT1Byb3ZpZGVyMRMwEQYDVQQDEwpkZXYtOTY5MjQ0MRswGQYJKoZIhvcNAQkBFgxpbmZvQGlkcC5vcmcwHhcNMTcwMTI0MTczMTI3WhcNMjcwMTIyMTczMTI3WjB4MQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEMMAoGA1UEChMDSURQMRQwEgYDVQQLEwtTU09Qcm92aWRlcjETMBEGA1UEAxMKZGV2LTk2OTI0NDEbMBkGCSqGSIb3DQEJARYMaW5mb0BpZHAub3JnMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0X/AE1tmDmhGRROAWaJ82XSORivRfgNt9Fb4rLrf6nIJsQN3vNb1Nk4DSUEDdQuvHNaEemSVkSPgfq5qnhh37bJaghr0728J8dOyYzV5eArPvsbyCRcnhXQzpCK2zvHwjgxNJMsNJLbnYpG/U+dCdCtcOOn9JEhKO8wKn06y2tcrvC1uuVs7bodukPUNq82KJTyvCQP8jh1hEZXeR2siJFDeJj1n2FNTMeCKIqOb42J/i+sBTlyK3mV5Ni++hI/ssIYVbPwrMIBd6sKLVAgInshBHOj/7XcXW/rMf468YtBKs4XnXsE3hLoU02aWCRDlVHa4hm3jfIAqEADOUumklQIDAQABo4HdMIHaMB0GA1UdDgQWBBRjN/dQSvhZxIsHTXmDKQJkPrjp0TCBqgYDVR0jBIGiMIGfgBRjN/dQSvhZxIsHTXmDKQJkPrjp0aF8pHoweDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWExDDAKBgNVBAoTA0lEUDEUMBIGA1UECxMLU1NPUHJvdmlkZXIxEzARBgNVBAMTCmRldi05NjkyNDQxGzAZBgkqhkiG9w0BCQEWDGluZm9AaWRwLm9yZ4IJAJdmunb39nFKMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBAIqHUglIUAA+BKMW6B0Q+cqIgDr9fWlsvDwIVK7/cvUeGIH3icSsje9AVZ4nQOJpxmC/E06HfuDXmbT1wG16jNo01mPW9qaOGRJuQqlZdegCSF385o/OHcbaEKBRwyYuvLfu80EREj8wcMUKFpExoaxK7K8DS7hh3w7exLB80jyhIaDEYc1hdyAl+206XpOXSYBetsg7I622R2+ajSL7ygUxQjmKQ5DyInPdXzCFCL6Ew/BN0dwzfnBEEK223ruOWBLpj13zMC077dor/NgYyHZU6iqiDS2eYO5jhVMve/mP9734+6N34seQRmekfmsf2dJcEQhPVYr/j0DeJc3men4=</ds:X509Certificate></ds:X509Data></ds:KeyInfo></ds:Signature>
|
||||||
|
<Issuer xmlns="urn:oasis:names:tc:SAML:2.0:assertion" Format="urn:oasis:names:tc:SAML:2.0:nameid-format:entity">http://www.okta.com/exk91cb99lKkKSYoy0h7</Issuer>
|
||||||
|
<Status>
|
||||||
|
<StatusCode Value="urn:oasis:names:tc:SAML:2.0:status:Success"/>
|
||||||
|
</Status>
|
||||||
|
<Assertion xmlns="urn:oasis:names:tc:SAML:2.0:assertion" ID="pfxa75683e6-25ff-5639-6806-08d2a132ad4e" IssueInstant="2016-12-20T22:18:23.771Z" Version="2.0">
|
||||||
|
<Issuer Format="urn:oasis:names:tc:SAML:2.0:nameid-format:entity">http://www.okta.com/exk91cb99lKkKSYoy0h7</Issuer><ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
|
||||||
|
<ds:SignedInfo><ds:CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
|
||||||
|
<ds:SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1"/>
|
||||||
|
<ds:Reference URI="#pfxa75683e6-25ff-5639-6806-08d2a132ad4e"><ds:Transforms><ds:Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"/><ds:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/></ds:Transforms><ds:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/><ds:DigestValue>4jNCSI3tTnbpozZ8qT4FZe+EWV8=</ds:DigestValue></ds:Reference></ds:SignedInfo><ds:SignatureValue>xtTnl92ArZyxskD3b34cIjo5LIpeE+3RjW+jgtXMXhUIZp3uGJ2RC6n1CbJ6IWuo4KmezpnVUnSWNz/fgOTZCN/1VlqsfLDpoTf790GrP+q6rKyw8CW7nd0uVS5FRYe05HTO6C5RqnaE9PmZ/YYbiWtLIDx0+kqvu/jFr+D144G/mukaVG4ydnDQ/tl21N6hWIOpi1tWaNPv50OEEgY//9VPql9Us3YuhfrxNggVugauArwY9RL4nVFVjALP1wpkZn1JzpgNMFgvXfY3MxnI1OnWg6ypJESugIKroKqj5RyqMIaLICsUOBwIKk8R4zAATrB+D+kuFV9Ec837duW/Eg==</ds:SignatureValue>
|
||||||
|
<ds:KeyInfo><ds:X509Data><ds:X509Certificate>MIIEUTCCAzmgAwIBAgIJAJdmunb39nFKMA0GCSqGSIb3DQEBCwUAMHgxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMQwwCgYDVQQKEwNJRFAxFDASBgNVBAsTC1NTT1Byb3ZpZGVyMRMwEQYDVQQDEwpkZXYtOTY5MjQ0MRswGQYJKoZIhvcNAQkBFgxpbmZvQGlkcC5vcmcwHhcNMTcwMTI0MTczMTI3WhcNMjcwMTIyMTczMTI3WjB4MQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEMMAoGA1UEChMDSURQMRQwEgYDVQQLEwtTU09Qcm92aWRlcjETMBEGA1UEAxMKZGV2LTk2OTI0NDEbMBkGCSqGSIb3DQEJARYMaW5mb0BpZHAub3JnMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0X/AE1tmDmhGRROAWaJ82XSORivRfgNt9Fb4rLrf6nIJsQN3vNb1Nk4DSUEDdQuvHNaEemSVkSPgfq5qnhh37bJaghr0728J8dOyYzV5eArPvsbyCRcnhXQzpCK2zvHwjgxNJMsNJLbnYpG/U+dCdCtcOOn9JEhKO8wKn06y2tcrvC1uuVs7bodukPUNq82KJTyvCQP8jh1hEZXeR2siJFDeJj1n2FNTMeCKIqOb42J/i+sBTlyK3mV5Ni++hI/ssIYVbPwrMIBd6sKLVAgInshBHOj/7XcXW/rMf468YtBKs4XnXsE3hLoU02aWCRDlVHa4hm3jfIAqEADOUumklQIDAQABo4HdMIHaMB0GA1UdDgQWBBRjN/dQSvhZxIsHTXmDKQJkPrjp0TCBqgYDVR0jBIGiMIGfgBRjN/dQSvhZxIsHTXmDKQJkPrjp0aF8pHoweDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWExDDAKBgNVBAoTA0lEUDEUMBIGA1UECxMLU1NPUHJvdmlkZXIxEzARBgNVBAMTCmRldi05NjkyNDQxGzAZBgkqhkiG9w0BCQEWDGluZm9AaWRwLm9yZ4IJAJdmunb39nFKMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBAIqHUglIUAA+BKMW6B0Q+cqIgDr9fWlsvDwIVK7/cvUeGIH3icSsje9AVZ4nQOJpxmC/E06HfuDXmbT1wG16jNo01mPW9qaOGRJuQqlZdegCSF385o/OHcbaEKBRwyYuvLfu80EREj8wcMUKFpExoaxK7K8DS7hh3w7exLB80jyhIaDEYc1hdyAl+206XpOXSYBetsg7I622R2+ajSL7ygUxQjmKQ5DyInPdXzCFCL6Ew/BN0dwzfnBEEK223ruOWBLpj13zMC077dor/NgYyHZU6iqiDS2eYO5jhVMve/mP9734+6N34seQRmekfmsf2dJcEQhPVYr/j0DeJc3men4=</ds:X509Certificate></ds:X509Data></ds:KeyInfo></ds:Signature>
|
||||||
|
<Subject>
|
||||||
|
<NameID Format="urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified">eric.chiang+okta@coreos.com</NameID>
|
||||||
|
<SubjectConfirmation Method="urn:oasis:names:tc:SAML:2.0:cm:bearer">
|
||||||
|
<SubjectConfirmationData InResponseTo="_fd1b3ef9-ec09-44a7-a66b-0d39c250f6a0" NotOnOrAfter="2116-12-20T22:23:23.772Z" Recipient="http://localhost:5556/dex/callback"/>
|
||||||
|
</SubjectConfirmation>
|
||||||
|
</Subject>
|
||||||
|
<Conditions NotBefore="2016-12-20T22:13:23.772Z" NotOnOrAfter="2116-12-20T22:23:23.772Z">
|
||||||
|
<AudienceRestriction>
|
||||||
|
<Audience>http://localhost:5556/dex/callback</Audience>
|
||||||
|
</AudienceRestriction>
|
||||||
|
</Conditions>
|
||||||
|
<AuthnStatement AuthnInstant="2016-12-20T22:18:23.771Z" SessionIndex="_fd1b3ef9-ec09-44a7-a66b-0d39c250f6a0">
|
||||||
|
<AuthnContext>
|
||||||
|
<AuthnContextClassRef>urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport</AuthnContextClassRef>
|
||||||
|
</AuthnContext>
|
||||||
|
</AuthnStatement>
|
||||||
|
</Assertion>
|
||||||
|
</Response>
|
30
connector/saml/testdata/idp-resp-signed-message.xml
vendored
Normal file
30
connector/saml/testdata/idp-resp-signed-message.xml
vendored
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<Response xmlns="urn:oasis:names:tc:SAML:2.0:protocol" Destination="http://localhost:5556/dex/callback" ID="pfx2801a166-e451-31a9-87a4-4cd777c16182" InResponseTo="_fd1b3ef9-ec09-44a7-a66b-0d39c250f6a0" IssueInstant="2016-12-20T22:18:23.771Z" Version="2.0"><ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
|
||||||
|
<ds:SignedInfo><ds:CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
|
||||||
|
<ds:SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1"/>
|
||||||
|
<ds:Reference URI="#pfx2801a166-e451-31a9-87a4-4cd777c16182"><ds:Transforms><ds:Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"/><ds:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/></ds:Transforms><ds:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/><ds:DigestValue>Oy9CTB/hzFWyr6QF99EZ4ymIEfY=</ds:DigestValue></ds:Reference></ds:SignedInfo><ds:SignatureValue>COKw1DUpDzNVulVNFlcrPwaalCwr8BfTye92GN5snTmx3IDKLudr1PT+WPt5i2N4xaoFcq/X1p/yEVmWtC1O+YYXNNIouFwps9Gyw/iDEMs1TtVKfikbloKWkDdYgfqgcon+mOq/lHagLVAcgz5QRBHVTIrFWcFYbnemsj1hy8q7ToIeoyHX9f5TAZBfZEEbdZcsD581xKPafNGowgfWxkgEwLBzFsJVYg/QfeoNnORTsKlsQBuswiXrsWatZNOOjWpdF9qqYn/3f9axqx2CJPD2HB38Vl0g4dTFnpmMAe45ndJq0IpXr9YJNCDJjUIvR7srdV1AW7qe2Mp6LxBBNw==</ds:SignatureValue>
|
||||||
|
<ds:KeyInfo><ds:X509Data><ds:X509Certificate>MIIEUTCCAzmgAwIBAgIJAJdmunb39nFKMA0GCSqGSIb3DQEBCwUAMHgxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMQwwCgYDVQQKEwNJRFAxFDASBgNVBAsTC1NTT1Byb3ZpZGVyMRMwEQYDVQQDEwpkZXYtOTY5MjQ0MRswGQYJKoZIhvcNAQkBFgxpbmZvQGlkcC5vcmcwHhcNMTcwMTI0MTczMTI3WhcNMjcwMTIyMTczMTI3WjB4MQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEMMAoGA1UEChMDSURQMRQwEgYDVQQLEwtTU09Qcm92aWRlcjETMBEGA1UEAxMKZGV2LTk2OTI0NDEbMBkGCSqGSIb3DQEJARYMaW5mb0BpZHAub3JnMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0X/AE1tmDmhGRROAWaJ82XSORivRfgNt9Fb4rLrf6nIJsQN3vNb1Nk4DSUEDdQuvHNaEemSVkSPgfq5qnhh37bJaghr0728J8dOyYzV5eArPvsbyCRcnhXQzpCK2zvHwjgxNJMsNJLbnYpG/U+dCdCtcOOn9JEhKO8wKn06y2tcrvC1uuVs7bodukPUNq82KJTyvCQP8jh1hEZXeR2siJFDeJj1n2FNTMeCKIqOb42J/i+sBTlyK3mV5Ni++hI/ssIYVbPwrMIBd6sKLVAgInshBHOj/7XcXW/rMf468YtBKs4XnXsE3hLoU02aWCRDlVHa4hm3jfIAqEADOUumklQIDAQABo4HdMIHaMB0GA1UdDgQWBBRjN/dQSvhZxIsHTXmDKQJkPrjp0TCBqgYDVR0jBIGiMIGfgBRjN/dQSvhZxIsHTXmDKQJkPrjp0aF8pHoweDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWExDDAKBgNVBAoTA0lEUDEUMBIGA1UECxMLU1NPUHJvdmlkZXIxEzARBgNVBAMTCmRldi05NjkyNDQxGzAZBgkqhkiG9w0BCQEWDGluZm9AaWRwLm9yZ4IJAJdmunb39nFKMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBAIqHUglIUAA+BKMW6B0Q+cqIgDr9fWlsvDwIVK7/cvUeGIH3icSsje9AVZ4nQOJpxmC/E06HfuDXmbT1wG16jNo01mPW9qaOGRJuQqlZdegCSF385o/OHcbaEKBRwyYuvLfu80EREj8wcMUKFpExoaxK7K8DS7hh3w7exLB80jyhIaDEYc1hdyAl+206XpOXSYBetsg7I622R2+ajSL7ygUxQjmKQ5DyInPdXzCFCL6Ew/BN0dwzfnBEEK223ruOWBLpj13zMC077dor/NgYyHZU6iqiDS2eYO5jhVMve/mP9734+6N34seQRmekfmsf2dJcEQhPVYr/j0DeJc3men4=</ds:X509Certificate></ds:X509Data></ds:KeyInfo></ds:Signature>
|
||||||
|
<Issuer xmlns="urn:oasis:names:tc:SAML:2.0:assertion" Format="urn:oasis:names:tc:SAML:2.0:nameid-format:entity">http://www.okta.com/exk91cb99lKkKSYoy0h7</Issuer>
|
||||||
|
<Status>
|
||||||
|
<StatusCode Value="urn:oasis:names:tc:SAML:2.0:status:Success"/>
|
||||||
|
</Status>
|
||||||
|
<Assertion xmlns="urn:oasis:names:tc:SAML:2.0:assertion" ID="id10896545312129779529177535" IssueInstant="2016-12-20T22:18:23.771Z" Version="2.0">
|
||||||
|
<Issuer Format="urn:oasis:names:tc:SAML:2.0:nameid-format:entity">http://www.okta.com/exk91cb99lKkKSYoy0h7</Issuer>
|
||||||
|
<Subject>
|
||||||
|
<NameID Format="urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified">eric.chiang+okta@coreos.com</NameID>
|
||||||
|
<SubjectConfirmation Method="urn:oasis:names:tc:SAML:2.0:cm:bearer">
|
||||||
|
<SubjectConfirmationData InResponseTo="_fd1b3ef9-ec09-44a7-a66b-0d39c250f6a0" NotOnOrAfter="2116-12-20T22:23:23.772Z" Recipient="http://localhost:5556/dex/callback"/>
|
||||||
|
</SubjectConfirmation>
|
||||||
|
</Subject>
|
||||||
|
<Conditions NotBefore="2016-12-20T22:13:23.772Z" NotOnOrAfter="2116-12-20T22:23:23.772Z">
|
||||||
|
<AudienceRestriction>
|
||||||
|
<Audience>http://localhost:5556/dex/callback</Audience>
|
||||||
|
</AudienceRestriction>
|
||||||
|
</Conditions>
|
||||||
|
<AuthnStatement AuthnInstant="2016-12-20T22:18:23.771Z" SessionIndex="_fd1b3ef9-ec09-44a7-a66b-0d39c250f6a0">
|
||||||
|
<AuthnContext>
|
||||||
|
<AuthnContextClassRef>urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport</AuthnContextClassRef>
|
||||||
|
</AuthnContext>
|
||||||
|
</AuthnStatement>
|
||||||
|
</Assertion>
|
||||||
|
</Response>
|
34
connector/saml/testdata/idp-resp.xml
vendored
Normal file
34
connector/saml/testdata/idp-resp.xml
vendored
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<Response xmlns="urn:oasis:names:tc:SAML:2.0:protocol" Destination="http://localhost:5556/dex/callback" ID="id108965453120986171998428970" InResponseTo="_fd1b3ef9-ec09-44a7-a66b-0d39c250f6a0" IssueInstant="2016-12-20T22:18:23.771Z" Version="2.0">
|
||||||
|
<Issuer xmlns="urn:oasis:names:tc:SAML:2.0:assertion" Format="urn:oasis:names:tc:SAML:2.0:nameid-format:entity">http://www.okta.com/exk91cb99lKkKSYoy0h7</Issuer>
|
||||||
|
<Status>
|
||||||
|
<StatusCode Value="urn:oasis:names:tc:SAML:2.0:status:Success"/>
|
||||||
|
</Status>
|
||||||
|
<Assertion xmlns="urn:oasis:names:tc:SAML:2.0:assertion" ID="id10896545312129779529177535" IssueInstant="2016-12-20T22:18:23.771Z" Version="2.0">
|
||||||
|
<Issuer Format="urn:oasis:names:tc:SAML:2.0:nameid-format:entity">http://www.okta.com/exk91cb99lKkKSYoy0h7</Issuer>
|
||||||
|
<Subject>
|
||||||
|
<NameID Format="urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified">eric.chiang+okta@coreos.com</NameID>
|
||||||
|
<SubjectConfirmation Method="urn:oasis:names:tc:SAML:2.0:cm:bearer">
|
||||||
|
<SubjectConfirmationData InResponseTo="_fd1b3ef9-ec09-44a7-a66b-0d39c250f6a0" NotOnOrAfter="2116-12-20T22:23:23.772Z" Recipient="http://localhost:5556/dex/callback"/>
|
||||||
|
</SubjectConfirmation>
|
||||||
|
</Subject>
|
||||||
|
<Conditions NotBefore="2016-12-20T22:13:23.772Z" NotOnOrAfter="2116-12-20T22:23:23.772Z">
|
||||||
|
<AudienceRestriction>
|
||||||
|
<Audience>http://localhost:5556/dex/callback</Audience>
|
||||||
|
</AudienceRestriction>
|
||||||
|
</Conditions>
|
||||||
|
<AuthnStatement AuthnInstant="2016-12-20T22:18:23.771Z" SessionIndex="_fd1b3ef9-ec09-44a7-a66b-0d39c250f6a0">
|
||||||
|
<AuthnContext>
|
||||||
|
<AuthnContextClassRef>urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport</AuthnContextClassRef>
|
||||||
|
</AuthnContext>
|
||||||
|
</AuthnStatement>
|
||||||
|
<AttributeStatement>
|
||||||
|
<Attribute Name="user" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:basic">
|
||||||
|
<AttributeValue xsi:type="xs:string">admin</AttributeValue>
|
||||||
|
</Attribute>
|
||||||
|
<Attribute Name="email" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:basic">
|
||||||
|
<AttributeValue xsi:type="xs:string">eric.chiang+okta@coreos.com</AttributeValue>
|
||||||
|
</Attribute>
|
||||||
|
</AttributeStatement>
|
||||||
|
</Assertion>
|
||||||
|
</Response>
|
@ -57,6 +57,8 @@ type authnRequest struct {
|
|||||||
IsPassive bool `xml:"IsPassive,attr,omitempty"`
|
IsPassive bool `xml:"IsPassive,attr,omitempty"`
|
||||||
ProtocolBinding string `xml:"ProtocolBinding,attr,omitempty"`
|
ProtocolBinding string `xml:"ProtocolBinding,attr,omitempty"`
|
||||||
|
|
||||||
|
AssertionConsumerServiceURL string `xml:"AssertionConsumerServiceURL,attr,omitempty"`
|
||||||
|
|
||||||
Subject *subject `xml:"Subject,omitempty"`
|
Subject *subject `xml:"Subject,omitempty"`
|
||||||
Issuer *issuer `xml:"Issuer,omitempty"`
|
Issuer *issuer `xml:"Issuer,omitempty"`
|
||||||
NameIDPolicy *nameIDPolicy `xml:"NameIDPolicy,omitempty"`
|
NameIDPolicy *nameIDPolicy `xml:"NameIDPolicy,omitempty"`
|
||||||
@ -69,6 +71,7 @@ type subject struct {
|
|||||||
XMLName xml.Name `xml:"urn:oasis:names:tc:SAML:2.0:assertion Subject"`
|
XMLName xml.Name `xml:"urn:oasis:names:tc:SAML:2.0:assertion Subject"`
|
||||||
|
|
||||||
NameID *nameID `xml:"NameID,omitempty"`
|
NameID *nameID `xml:"NameID,omitempty"`
|
||||||
|
SubjectConfirmations []subjectConfirmation `xml:"SubjectConfirmation"`
|
||||||
|
|
||||||
// TODO(ericchiang): Do we need to deal with baseID?
|
// TODO(ericchiang): Do we need to deal with baseID?
|
||||||
}
|
}
|
||||||
@ -80,6 +83,60 @@ type nameID struct {
|
|||||||
Value string `xml:",chardata"`
|
Value string `xml:",chardata"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type subjectConfirmationData struct {
|
||||||
|
XMLName xml.Name `xml:"urn:oasis:names:tc:SAML:2.0:assertion SubjectConfirmationData"`
|
||||||
|
|
||||||
|
NotOnOrAfter xmlTime `xml:"NotOnOrAfter,attr,omitempty"`
|
||||||
|
Recipient string `xml:"Recipient,attr,omitempty"`
|
||||||
|
InResponseTo string `xml:"InResponseTo,attr,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type subjectConfirmation struct {
|
||||||
|
XMLName xml.Name `xml:"urn:oasis:names:tc:SAML:2.0:assertion SubjectConfirmation"`
|
||||||
|
|
||||||
|
Method string `xml:"Method,attr,omitempty"`
|
||||||
|
SubjectConfirmationData *subjectConfirmationData `xml:"SubjectConfirmationData,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type audience struct {
|
||||||
|
XMLName xml.Name `xml:"urn:oasis:names:tc:SAML:2.0:assertion Audience"`
|
||||||
|
Value string `xml:",chardata"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type audienceRestriction struct {
|
||||||
|
XMLName xml.Name `xml:"urn:oasis:names:tc:SAML:2.0:assertion AudienceRestriction"`
|
||||||
|
|
||||||
|
Audiences []audience `xml:"Audience"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type conditions struct {
|
||||||
|
XMLName xml.Name `xml:"urn:oasis:names:tc:SAML:2.0:assertion Conditions"`
|
||||||
|
|
||||||
|
NotBefore xmlTime `xml:"NotBefore,attr,omitempty"`
|
||||||
|
NotOnOrAfter xmlTime `xml:"NotOnOrAfter,attr,omitempty"`
|
||||||
|
|
||||||
|
AudienceRestriction *audienceRestriction `xml:"AudienceRestriction,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type statusCode struct {
|
||||||
|
XMLName xml.Name `xml:"urn:oasis:names:tc:SAML:2.0:protocol StatusCode"`
|
||||||
|
|
||||||
|
Value string `xml:"Value,attr,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type statusMessage struct {
|
||||||
|
XMLName xml.Name `xml:"urn:oasis:names:tc:SAML:2.0:protocol StatusMessage"`
|
||||||
|
|
||||||
|
Value string `xml:",chardata"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type status struct {
|
||||||
|
XMLName xml.Name `xml:"urn:oasis:names:tc:SAML:2.0:protocol Status"`
|
||||||
|
|
||||||
|
StatusCode *statusCode `xml:"StatusCode"`
|
||||||
|
StatusMessage *statusMessage `xml:"StatusMessage,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
type issuer struct {
|
type issuer struct {
|
||||||
XMLName xml.Name `xml:"urn:oasis:names:tc:SAML:2.0:assertion Issuer"`
|
XMLName xml.Name `xml:"urn:oasis:names:tc:SAML:2.0:assertion Issuer"`
|
||||||
Issuer string `xml:",chardata"`
|
Issuer string `xml:",chardata"`
|
||||||
@ -112,6 +169,8 @@ type response struct {
|
|||||||
|
|
||||||
Issuer *issuer `xml:"Issuer,omitempty"`
|
Issuer *issuer `xml:"Issuer,omitempty"`
|
||||||
|
|
||||||
|
Status *status `xml:"Status"`
|
||||||
|
|
||||||
// TODO(ericchiang): How do deal with multiple assertions?
|
// TODO(ericchiang): How do deal with multiple assertions?
|
||||||
Assertion *assertion `xml:"Assertion,omitempty"`
|
Assertion *assertion `xml:"Assertion,omitempty"`
|
||||||
}
|
}
|
||||||
@ -127,6 +186,8 @@ type assertion struct {
|
|||||||
|
|
||||||
Subject *subject `xml:"Subject,omitempty"`
|
Subject *subject `xml:"Subject,omitempty"`
|
||||||
|
|
||||||
|
Conditions *conditions `xml:"Conditions"`
|
||||||
|
|
||||||
AttributeStatement *attributeStatement `xml:"AttributeStatement,omitempty"`
|
AttributeStatement *attributeStatement `xml:"AttributeStatement,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -134,7 +134,7 @@ import:
|
|||||||
|
|
||||||
# XML signature validation for SAML connector
|
# XML signature validation for SAML connector
|
||||||
- package: github.com/russellhaering/goxmldsig
|
- package: github.com/russellhaering/goxmldsig
|
||||||
version: d9f653eb27ee8b145f7d5a45172e81a93def0860
|
version: e2990269f42f6ddfea940870a0800a14acdb8c21
|
||||||
- package: github.com/beevik/etree
|
- package: github.com/beevik/etree
|
||||||
version: 4cd0dd976db869f817248477718071a28e978df0
|
version: 4cd0dd976db869f817248477718071a28e978df0
|
||||||
- package: github.com/jonboulle/clockwork
|
- package: github.com/jonboulle/clockwork
|
||||||
|
Reference in New Issue
Block a user