MM-19872: Update dependencies for enterprise implementation of new SAML2 golib. (#13298)
* Update dependencies for enterprise implementation of new SAML2 golib. * add indirect
Этот коммит содержится в:
коммит произвёл
GitHub
родитель
155ada09fb
Коммит
3bccc03df3
1
vendor/github.com/mattermost/gosaml2/.gitignore
сгенерированный
поставляемый
Обычный файл
1
vendor/github.com/mattermost/gosaml2/.gitignore
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1 @@
|
||||
*.test
|
||||
15
vendor/github.com/mattermost/gosaml2/.travis.yml
сгенерированный
поставляемый
Обычный файл
15
vendor/github.com/mattermost/gosaml2/.travis.yml
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,15 @@
|
||||
language: go
|
||||
|
||||
go:
|
||||
- 1.5.x
|
||||
- 1.6.x
|
||||
- 1.7.x
|
||||
- 1.8.x
|
||||
- 1.9.x
|
||||
- 1.10.x
|
||||
- 1.11.x
|
||||
- tip
|
||||
|
||||
matrix:
|
||||
allow_failures:
|
||||
- go: tip
|
||||
175
vendor/github.com/mattermost/gosaml2/LICENSE
сгенерированный
поставляемый
Обычный файл
175
vendor/github.com/mattermost/gosaml2/LICENSE
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,175 @@
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
34
vendor/github.com/mattermost/gosaml2/README.md
сгенерированный
поставляемый
Обычный файл
34
vendor/github.com/mattermost/gosaml2/README.md
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,34 @@
|
||||
# gosaml2
|
||||
|
||||
[](https://travis-ci.org/russellhaering/gosaml2)
|
||||
[](https://godoc.org/github.com/russellhaering/gosaml2)
|
||||
|
||||
SAML 2.0 implemementation for Service Providers based on [etree](https://github.com/beevik/etree)
|
||||
and [goxmldsig](https://github.com/russellhaering/goxmldsig), a pure Go
|
||||
implementation of XML digital signatures.
|
||||
|
||||
## Installation
|
||||
|
||||
Install `gosaml2` into your `$GOPATH` using `go get`:
|
||||
|
||||
```
|
||||
go get github.com/russellhaering/gosaml2
|
||||
```
|
||||
|
||||
## Example
|
||||
|
||||
See [demo.go](s2example/demo.go).
|
||||
|
||||
## Supported Identity Providers
|
||||
|
||||
This library is meant to be a generic SAML implementation. If you find a
|
||||
standards compliant identity provider that it doesn't work with please
|
||||
submit a bug or pull request.
|
||||
|
||||
The following identity providers have been tested:
|
||||
|
||||
* Okta
|
||||
* Auth0
|
||||
* Shibboleth
|
||||
* Ipsilon
|
||||
* OneLogin
|
||||
19
vendor/github.com/mattermost/gosaml2/attribute.go
сгенерированный
поставляемый
Обычный файл
19
vendor/github.com/mattermost/gosaml2/attribute.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,19 @@
|
||||
package saml2
|
||||
|
||||
import "github.com/mattermost/gosaml2/types"
|
||||
|
||||
// Values is a convenience wrapper for a map of strings to Attributes, which
|
||||
// can be used for easy access to the string values of Attribute lists.
|
||||
type Values map[string]types.Attribute
|
||||
|
||||
// Get is a safe method (nil maps will not panic) for returning the first value
|
||||
// for an attribute at a key, or the empty string if none exists.
|
||||
func (vals Values) Get(k string) string {
|
||||
if vals == nil {
|
||||
return ""
|
||||
}
|
||||
if v, ok := vals[k]; ok && len(v.Values) > 0 {
|
||||
return string(v.Values[0].Value)
|
||||
}
|
||||
return ""
|
||||
}
|
||||
16
vendor/github.com/mattermost/gosaml2/authn_request.go
сгенерированный
поставляемый
Обычный файл
16
vendor/github.com/mattermost/gosaml2/authn_request.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,16 @@
|
||||
package saml2
|
||||
|
||||
import "time"
|
||||
|
||||
// AuthNRequest is the go struct representation of an authentication request
|
||||
type AuthNRequest struct {
|
||||
ID string `xml:",attr"`
|
||||
Version string `xml:",attr"`
|
||||
ProtocolBinding string `xml:",attr"`
|
||||
AssertionConsumerServiceURL string `xml:",attr"`
|
||||
|
||||
IssueInstant time.Time `xml:",attr"`
|
||||
|
||||
Destination string `xml:",attr"`
|
||||
Issuer string
|
||||
}
|
||||
226
vendor/github.com/mattermost/gosaml2/build_request.go
сгенерированный
поставляемый
Обычный файл
226
vendor/github.com/mattermost/gosaml2/build_request.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,226 @@
|
||||
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()
|
||||
}
|
||||
337
vendor/github.com/mattermost/gosaml2/decode_response.go
сгенерированный
поставляемый
Обычный файл
337
vendor/github.com/mattermost/gosaml2/decode_response.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,337 @@
|
||||
package saml2
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"compress/flate"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
|
||||
"encoding/xml"
|
||||
|
||||
"github.com/beevik/etree"
|
||||
"github.com/mattermost/gosaml2/types"
|
||||
dsig "github.com/russellhaering/goxmldsig"
|
||||
"github.com/russellhaering/goxmldsig/etreeutils"
|
||||
)
|
||||
|
||||
func (sp *SAMLServiceProvider) validationContext() *dsig.ValidationContext {
|
||||
ctx := dsig.NewDefaultValidationContext(sp.IDPCertificateStore)
|
||||
ctx.Clock = sp.Clock
|
||||
return ctx
|
||||
}
|
||||
|
||||
// validateResponseAttributes validates a SAML Response's tag and attributes. It does
|
||||
// not inspect child elements of the Response at all.
|
||||
func (sp *SAMLServiceProvider) validateResponseAttributes(response *types.Response) error {
|
||||
if response.Destination != "" && response.Destination != sp.AssertionConsumerServiceURL {
|
||||
return ErrInvalidValue{
|
||||
Key: DestinationAttr,
|
||||
Expected: sp.AssertionConsumerServiceURL,
|
||||
Actual: response.Destination,
|
||||
}
|
||||
}
|
||||
|
||||
if response.Version != "2.0" {
|
||||
return ErrInvalidValue{
|
||||
Reason: ReasonUnsupported,
|
||||
Key: "SAML version",
|
||||
Expected: "2.0",
|
||||
Actual: response.Version,
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func xmlUnmarshalElement(el *etree.Element, obj interface{}) error {
|
||||
doc := etree.NewDocument()
|
||||
doc.SetRoot(el)
|
||||
data, err := doc.WriteToBytes()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = xml.Unmarshal(data, obj)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (sp *SAMLServiceProvider) getDecryptCert() (*tls.Certificate, error) {
|
||||
if sp.SPKeyStore == nil {
|
||||
return nil, fmt.Errorf("no decryption certs available")
|
||||
}
|
||||
|
||||
//This is the tls.Certificate we'll use to decrypt any encrypted assertions
|
||||
var decryptCert tls.Certificate
|
||||
|
||||
switch crt := sp.SPKeyStore.(type) {
|
||||
case dsig.TLSCertKeyStore:
|
||||
// Get the tls.Certificate directly if possible
|
||||
decryptCert = tls.Certificate(crt)
|
||||
|
||||
default:
|
||||
|
||||
//Otherwise, construct one from the results of GetKeyPair
|
||||
pk, cert, err := sp.SPKeyStore.GetKeyPair()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error getting keypair: %v", err)
|
||||
}
|
||||
|
||||
decryptCert = tls.Certificate{
|
||||
Certificate: [][]byte{cert},
|
||||
PrivateKey: pk,
|
||||
}
|
||||
}
|
||||
|
||||
if sp.ValidateEncryptionCert {
|
||||
// Check Validity period of certificate
|
||||
if len(decryptCert.Certificate) < 1 || len(decryptCert.Certificate[0]) < 1 {
|
||||
return nil, fmt.Errorf("empty decryption cert")
|
||||
} else if cert, err := x509.ParseCertificate(decryptCert.Certificate[0]); err != nil {
|
||||
return nil, fmt.Errorf("invalid x509 decryption cert: %v", err)
|
||||
} else {
|
||||
now := sp.Clock.Now()
|
||||
if now.Before(cert.NotBefore) || now.After(cert.NotAfter) {
|
||||
return nil, fmt.Errorf("decryption cert is not valid at this time")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return &decryptCert, nil
|
||||
}
|
||||
|
||||
func (sp *SAMLServiceProvider) decryptAssertions(el *etree.Element) error {
|
||||
var decryptCert *tls.Certificate
|
||||
|
||||
decryptAssertion := func(ctx etreeutils.NSContext, encryptedElement *etree.Element) error {
|
||||
if encryptedElement.Parent() != el {
|
||||
return fmt.Errorf("found encrypted assertion with unexpected parent element: %s", encryptedElement.Parent().Tag)
|
||||
}
|
||||
|
||||
detached, err := etreeutils.NSDetatch(ctx, encryptedElement) // make a detached copy
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to detach encrypted assertion: %v", err)
|
||||
}
|
||||
|
||||
encryptedAssertion := &types.EncryptedAssertion{}
|
||||
err = xmlUnmarshalElement(detached, encryptedAssertion)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to unmarshal encrypted assertion: %v", err)
|
||||
}
|
||||
|
||||
if decryptCert == nil {
|
||||
decryptCert, err = sp.getDecryptCert()
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to get decryption certificate: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
raw, derr := encryptedAssertion.DecryptBytes(decryptCert)
|
||||
if derr != nil {
|
||||
return fmt.Errorf("unable to decrypt encrypted assertion: %v", derr)
|
||||
}
|
||||
|
||||
doc, _, err := parseResponse(raw)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to create element from decrypted assertion bytes: %v", derr)
|
||||
}
|
||||
|
||||
// Replace the original encrypted assertion with the decrypted one.
|
||||
if el.RemoveChild(encryptedElement) == nil {
|
||||
// Out of an abundance of caution, make sure removed worked
|
||||
panic("unable to remove encrypted assertion")
|
||||
}
|
||||
|
||||
el.AddChild(doc.Root())
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := etreeutils.NSFindIterate(el, SAMLAssertionNamespace, EncryptedAssertionTag, decryptAssertion); err != nil {
|
||||
return err
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func (sp *SAMLServiceProvider) validateElementSignature(el *etree.Element) (*etree.Element, error) {
|
||||
return sp.validationContext().Validate(el)
|
||||
}
|
||||
|
||||
func (sp *SAMLServiceProvider) validateAssertionSignatures(el *etree.Element) error {
|
||||
signedAssertions := 0
|
||||
unsignedAssertions := 0
|
||||
validateAssertion := func(ctx etreeutils.NSContext, unverifiedAssertion *etree.Element) error {
|
||||
if unverifiedAssertion.Parent() != el {
|
||||
return fmt.Errorf("found assertion with unexpected parent element: %s", unverifiedAssertion.Parent().Tag)
|
||||
}
|
||||
|
||||
detached, err := etreeutils.NSDetatch(ctx, unverifiedAssertion) // make a detached copy
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to detach unverified assertion: %v", err)
|
||||
}
|
||||
|
||||
assertion, err := sp.validationContext().Validate(detached)
|
||||
if err == dsig.ErrMissingSignature {
|
||||
unsignedAssertions++
|
||||
return nil
|
||||
} else if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Replace the original unverified Assertion with the verified one. Note that
|
||||
// if the Response is not signed, only signed Assertions (and not the parent Response) can be trusted.
|
||||
if el.RemoveChild(unverifiedAssertion) == nil {
|
||||
// Out of an abundance of caution, check to make sure an Assertion was actually
|
||||
// removed. If it wasn't a programming error has occurred.
|
||||
panic("unable to remove assertion")
|
||||
}
|
||||
|
||||
el.AddChild(assertion)
|
||||
signedAssertions++
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := etreeutils.NSFindIterate(el, SAMLAssertionNamespace, AssertionTag, validateAssertion); err != nil {
|
||||
return err
|
||||
} else if signedAssertions > 0 && unsignedAssertions > 0 {
|
||||
return fmt.Errorf("invalid to have both signed and unsigned assertions")
|
||||
} else if signedAssertions < 1 {
|
||||
return dsig.ErrMissingSignature
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
//ValidateEncodedResponse both decodes and validates, based on SP
|
||||
//configuration, an encoded, signed response. It will also appropriately
|
||||
//decrypt a response if the assertion was encrypted
|
||||
func (sp *SAMLServiceProvider) ValidateEncodedResponse(encodedResponse string) (*types.Response, error) {
|
||||
raw, err := base64.StdEncoding.DecodeString(encodedResponse)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Parse the raw response
|
||||
doc, el, err := parseResponse(raw)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var responseSignatureValidated bool
|
||||
if !sp.SkipSignatureValidation {
|
||||
el, err = sp.validateElementSignature(el)
|
||||
if err == dsig.ErrMissingSignature {
|
||||
// Unfortunately we just blew away our Response
|
||||
el = doc.Root()
|
||||
} else if err != nil {
|
||||
return nil, err
|
||||
} else if el == nil {
|
||||
return nil, fmt.Errorf("missing transformed response")
|
||||
} else {
|
||||
responseSignatureValidated = true
|
||||
}
|
||||
}
|
||||
|
||||
err = sp.decryptAssertions(el)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var assertionSignaturesValidated bool
|
||||
if !sp.SkipSignatureValidation {
|
||||
err = sp.validateAssertionSignatures(el)
|
||||
if err == dsig.ErrMissingSignature {
|
||||
if !responseSignatureValidated {
|
||||
return nil, fmt.Errorf("response and/or assertions must be signed")
|
||||
}
|
||||
} else if err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
assertionSignaturesValidated = true
|
||||
}
|
||||
}
|
||||
|
||||
decodedResponse := &types.Response{}
|
||||
err = xmlUnmarshalElement(el, decodedResponse)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to unmarshal response: %v", err)
|
||||
}
|
||||
decodedResponse.SignatureValidated = responseSignatureValidated
|
||||
if assertionSignaturesValidated {
|
||||
for idx := 0; idx < len(decodedResponse.Assertions); idx++ {
|
||||
decodedResponse.Assertions[idx].SignatureValidated = true
|
||||
}
|
||||
}
|
||||
|
||||
err = sp.Validate(decodedResponse)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return decodedResponse, nil
|
||||
}
|
||||
|
||||
// DecodeUnverifiedBaseResponse decodes several attributes from a SAML response for the purpose
|
||||
// of determining how to validate the response. This is useful for Service Providers which
|
||||
// expose a single Assertion Consumer Service URL but consume Responses from many IdPs.
|
||||
func DecodeUnverifiedBaseResponse(encodedResponse string) (*types.UnverifiedBaseResponse, error) {
|
||||
raw, err := base64.StdEncoding.DecodeString(encodedResponse)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var response *types.UnverifiedBaseResponse
|
||||
|
||||
err = maybeDeflate(raw, func(maybeXML []byte) error {
|
||||
response = &types.UnverifiedBaseResponse{}
|
||||
return xml.Unmarshal(maybeXML, response)
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return response, nil
|
||||
}
|
||||
|
||||
// maybeDeflate invokes the passed decoder over the passed data. If an error is
|
||||
// returned, it then attempts to deflate the passed data before re-invoking
|
||||
// the decoder over the deflated data.
|
||||
func maybeDeflate(data []byte, decoder func([]byte) error) error {
|
||||
err := decoder(data)
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
deflated, err := ioutil.ReadAll(flate.NewReader(bytes.NewReader(data)))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return decoder(deflated)
|
||||
}
|
||||
|
||||
// parseResponse is a helper function that was refactored out so that the XML parsing behavior can be isolated and unit tested
|
||||
func parseResponse(xml []byte) (*etree.Document, *etree.Element, error) {
|
||||
var doc *etree.Document
|
||||
|
||||
err := maybeDeflate(xml, func(xml []byte) error {
|
||||
doc = etree.NewDocument()
|
||||
return doc.ReadFromBytes(xml)
|
||||
})
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
el := doc.Root()
|
||||
if el == nil {
|
||||
return nil, nil, fmt.Errorf("unable to parse response")
|
||||
}
|
||||
|
||||
return doc, el, nil
|
||||
}
|
||||
95
vendor/github.com/mattermost/gosaml2/retrieve_assertion.go
сгенерированный
поставляемый
Обычный файл
95
vendor/github.com/mattermost/gosaml2/retrieve_assertion.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,95 @@
|
||||
package saml2
|
||||
|
||||
import "fmt"
|
||||
|
||||
//ErrMissingElement is the error type that indicates an element and/or attribute is
|
||||
//missing. It provides a structured error that can be more appropriately acted
|
||||
//upon.
|
||||
type ErrMissingElement struct {
|
||||
Tag, Attribute string
|
||||
}
|
||||
|
||||
type ErrVerification struct {
|
||||
Cause error
|
||||
}
|
||||
|
||||
func (e ErrVerification) Error() string {
|
||||
return fmt.Sprintf("error validating response: %s", e.Cause.Error())
|
||||
}
|
||||
|
||||
//ErrMissingAssertion indicates that an appropriate assertion element could not
|
||||
//be found in the SAML Response
|
||||
var (
|
||||
ErrMissingAssertion = ErrMissingElement{Tag: AssertionTag}
|
||||
)
|
||||
|
||||
func (e ErrMissingElement) Error() string {
|
||||
if e.Attribute != "" {
|
||||
return fmt.Sprintf("missing %s attribute on %s element", e.Attribute, e.Tag)
|
||||
}
|
||||
return fmt.Sprintf("missing %s element", e.Tag)
|
||||
}
|
||||
|
||||
//RetrieveAssertionInfo takes an encoded response and returns the AssertionInfo
|
||||
//contained, or an error message if an error has been encountered.
|
||||
func (sp *SAMLServiceProvider) RetrieveAssertionInfo(encodedResponse string) (*AssertionInfo, error) {
|
||||
assertionInfo := &AssertionInfo{
|
||||
Values: make(Values),
|
||||
}
|
||||
|
||||
response, err := sp.ValidateEncodedResponse(encodedResponse)
|
||||
if err != nil {
|
||||
return nil, ErrVerification{Cause: err}
|
||||
}
|
||||
|
||||
// TODO: Support multiple assertions
|
||||
if len(response.Assertions) == 0 {
|
||||
return nil, ErrMissingAssertion
|
||||
}
|
||||
|
||||
assertion := response.Assertions[0]
|
||||
assertionInfo.Assertions = response.Assertions
|
||||
assertionInfo.ResponseSignatureValidated = response.SignatureValidated
|
||||
|
||||
warningInfo, err := sp.VerifyAssertionConditions(&assertion)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
//Get the NameID
|
||||
subject := assertion.Subject
|
||||
if subject == nil {
|
||||
return nil, ErrMissingElement{Tag: SubjectTag}
|
||||
}
|
||||
|
||||
nameID := subject.NameID
|
||||
if nameID == nil {
|
||||
return nil, ErrMissingElement{Tag: NameIdTag}
|
||||
}
|
||||
|
||||
assertionInfo.NameID = nameID.Value
|
||||
|
||||
//Get the actual assertion attributes
|
||||
attributeStatement := assertion.AttributeStatement
|
||||
if attributeStatement == nil && !sp.AllowMissingAttributes {
|
||||
return nil, ErrMissingElement{Tag: AttributeStatementTag}
|
||||
}
|
||||
|
||||
if attributeStatement != nil {
|
||||
for _, attribute := range attributeStatement.Attributes {
|
||||
assertionInfo.Values[attribute.Name] = attribute
|
||||
}
|
||||
}
|
||||
|
||||
if assertion.AuthnStatement != nil {
|
||||
if assertion.AuthnStatement.AuthnInstant != nil {
|
||||
assertionInfo.AuthnInstant = assertion.AuthnStatement.AuthnInstant
|
||||
}
|
||||
if assertion.AuthnStatement.SessionNotOnOrAfter != nil {
|
||||
assertionInfo.SessionNotOnOrAfter = assertion.AuthnStatement.SessionNotOnOrAfter
|
||||
}
|
||||
}
|
||||
|
||||
assertionInfo.WarningInfo = warningInfo
|
||||
return assertionInfo, nil
|
||||
}
|
||||
12
vendor/github.com/mattermost/gosaml2/run_test.sh
сгенерированный
поставляемый
Обычный файл
12
vendor/github.com/mattermost/gosaml2/run_test.sh
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,12 @@
|
||||
#!/bin/bash
|
||||
cd `dirname $0`
|
||||
DIRS=`git grep -l 'func Test' | xargs dirname | sort -u`
|
||||
for DIR in $DIRS
|
||||
do
|
||||
echo
|
||||
echo "dir: $DIR"
|
||||
echo "======================================"
|
||||
pushd $DIR >/dev/null
|
||||
go test -v || exit 1
|
||||
popd >/dev/null
|
||||
done
|
||||
196
vendor/github.com/mattermost/gosaml2/saml.go
сгенерированный
поставляемый
Обычный файл
196
vendor/github.com/mattermost/gosaml2/saml.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,196 @@
|
||||
package saml2
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/mattermost/gosaml2/types"
|
||||
dsig "github.com/russellhaering/goxmldsig"
|
||||
dsigtypes "github.com/russellhaering/goxmldsig/types"
|
||||
)
|
||||
|
||||
type ErrSaml struct {
|
||||
Message string
|
||||
System error
|
||||
}
|
||||
|
||||
func (serr ErrSaml) Error() string {
|
||||
if serr.Message != "" {
|
||||
return serr.Message
|
||||
}
|
||||
return "SAML error"
|
||||
}
|
||||
|
||||
type SAMLServiceProvider struct {
|
||||
IdentityProviderSSOURL string
|
||||
IdentityProviderIssuer string
|
||||
|
||||
AssertionConsumerServiceURL string
|
||||
ServiceProviderIssuer string
|
||||
|
||||
SignAuthnRequests bool
|
||||
SignAuthnRequestsAlgorithm string
|
||||
SignAuthnRequestsCanonicalizer dsig.Canonicalizer
|
||||
|
||||
// RequestedAuthnContext allows service providers to require that the identity
|
||||
// provider use specific authentication mechanisms. Leaving this unset will
|
||||
// permit the identity provider to choose the auth method. To maximize compatibility
|
||||
// with identity providers it is recommended to leave this unset.
|
||||
RequestedAuthnContext *RequestedAuthnContext
|
||||
AudienceURI string
|
||||
IDPCertificateStore dsig.X509CertificateStore
|
||||
SPKeyStore dsig.X509KeyStore // Required encryption key, default signing key
|
||||
SPSigningKeyStore dsig.X509KeyStore // Optional signing key
|
||||
NameIdFormat string
|
||||
ValidateEncryptionCert bool
|
||||
SkipSignatureValidation bool
|
||||
AllowMissingAttributes bool
|
||||
ScopingIDPProviderId string
|
||||
ScopingIDPProviderName string
|
||||
Clock *dsig.Clock
|
||||
signingContextMu sync.RWMutex
|
||||
signingContext *dsig.SigningContext
|
||||
}
|
||||
|
||||
// RequestedAuthnContext controls which authentication mechanisms are requested of
|
||||
// the identity provider. It is generally sufficient to omit this and let the
|
||||
// identity provider select an authentication mechansim.
|
||||
type RequestedAuthnContext struct {
|
||||
// The RequestedAuthnContext comparison policy to use. See the section 3.3.2.2.1
|
||||
// of the SAML 2.0 specification for details. Constants named AuthnPolicyMatch*
|
||||
// contain standardized values.
|
||||
Comparison string
|
||||
|
||||
// Contexts will be passed as AuthnContextClassRefs. For example, to force password
|
||||
// authentication on some identity providers, Contexts should have a value of
|
||||
// []string{AuthnContextPasswordProtectedTransport}, and Comparison should have a
|
||||
// value of AuthnPolicyMatchExact.
|
||||
Contexts []string
|
||||
}
|
||||
|
||||
func (sp *SAMLServiceProvider) Metadata() (*types.EntityDescriptor, error) {
|
||||
signingCertBytes, err := sp.GetSigningCertBytes()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
encryptionCertBytes, err := sp.GetEncryptionCertBytes()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &types.EntityDescriptor{
|
||||
ValidUntil: time.Now().UTC().Add(time.Hour * 24 * 7), // 7 days
|
||||
EntityID: sp.ServiceProviderIssuer,
|
||||
SPSSODescriptor: &types.SPSSODescriptor{
|
||||
AuthnRequestsSigned: sp.SignAuthnRequests,
|
||||
WantAssertionsSigned: !sp.SkipSignatureValidation,
|
||||
ProtocolSupportEnumeration: SAMLProtocolNamespace,
|
||||
KeyDescriptors: []types.KeyDescriptor{
|
||||
{
|
||||
Use: "signing",
|
||||
KeyInfo: dsigtypes.KeyInfo{
|
||||
X509Data: dsigtypes.X509Data{
|
||||
X509Certificates: []dsigtypes.X509Certificate{dsigtypes.X509Certificate{
|
||||
Data: base64.StdEncoding.EncodeToString(signingCertBytes),
|
||||
}},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Use: "encryption",
|
||||
KeyInfo: dsigtypes.KeyInfo{
|
||||
X509Data: dsigtypes.X509Data{
|
||||
X509Certificates: []dsigtypes.X509Certificate{dsigtypes.X509Certificate{
|
||||
Data: base64.StdEncoding.EncodeToString(encryptionCertBytes),
|
||||
}},
|
||||
},
|
||||
},
|
||||
EncryptionMethods: []types.EncryptionMethod{
|
||||
{Algorithm: types.MethodAES128GCM},
|
||||
{Algorithm: types.MethodAES128CBC},
|
||||
{Algorithm: types.MethodAES256CBC},
|
||||
},
|
||||
},
|
||||
},
|
||||
AssertionConsumerServices: []types.IndexedEndpoint{{
|
||||
Binding: BindingHttpPost,
|
||||
Location: sp.AssertionConsumerServiceURL,
|
||||
Index: 1,
|
||||
}},
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (sp *SAMLServiceProvider) GetEncryptionKey() dsig.X509KeyStore {
|
||||
return sp.SPKeyStore
|
||||
}
|
||||
|
||||
func (sp *SAMLServiceProvider) GetSigningKey() dsig.X509KeyStore {
|
||||
if sp.SPSigningKeyStore == nil {
|
||||
return sp.GetEncryptionKey() // Default is signing key is same as encryption key
|
||||
}
|
||||
return sp.SPSigningKeyStore
|
||||
}
|
||||
|
||||
func (sp *SAMLServiceProvider) GetEncryptionCertBytes() ([]byte, error) {
|
||||
if _, encryptionCert, err := sp.GetEncryptionKey().GetKeyPair(); err != nil {
|
||||
return nil, ErrSaml{Message: "no SP encryption certificate", System: err}
|
||||
} else if len(encryptionCert) < 1 {
|
||||
return nil, ErrSaml{Message: "empty SP encryption certificate"}
|
||||
} else {
|
||||
return encryptionCert, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (sp *SAMLServiceProvider) GetSigningCertBytes() ([]byte, error) {
|
||||
if _, signingCert, err := sp.GetSigningKey().GetKeyPair(); err != nil {
|
||||
return nil, ErrSaml{Message: "no SP signing certificate", System: err}
|
||||
} else if len(signingCert) < 1 {
|
||||
return nil, ErrSaml{Message: "empty SP signing certificate"}
|
||||
} else {
|
||||
return signingCert, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (sp *SAMLServiceProvider) SigningContext() *dsig.SigningContext {
|
||||
sp.signingContextMu.RLock()
|
||||
signingContext := sp.signingContext
|
||||
sp.signingContextMu.RUnlock()
|
||||
|
||||
if signingContext != nil {
|
||||
return signingContext
|
||||
}
|
||||
|
||||
sp.signingContextMu.Lock()
|
||||
defer sp.signingContextMu.Unlock()
|
||||
|
||||
sp.signingContext = dsig.NewDefaultSigningContext(sp.GetSigningKey())
|
||||
sp.signingContext.SetSignatureMethod(sp.SignAuthnRequestsAlgorithm)
|
||||
if sp.SignAuthnRequestsCanonicalizer != nil {
|
||||
sp.signingContext.Canonicalizer = sp.SignAuthnRequestsCanonicalizer
|
||||
}
|
||||
|
||||
return sp.signingContext
|
||||
}
|
||||
|
||||
type ProxyRestriction struct {
|
||||
Count int
|
||||
Audience []string
|
||||
}
|
||||
|
||||
type WarningInfo struct {
|
||||
OneTimeUse bool
|
||||
ProxyRestriction *ProxyRestriction
|
||||
NotInAudience bool
|
||||
InvalidTime bool
|
||||
}
|
||||
|
||||
type AssertionInfo struct {
|
||||
NameID string
|
||||
Values Values
|
||||
WarningInfo *WarningInfo
|
||||
AuthnInstant *time.Time
|
||||
SessionNotOnOrAfter *time.Time
|
||||
Assertions []types.Assertion
|
||||
ResponseSignatureValidated bool
|
||||
}
|
||||
395
vendor/github.com/mattermost/gosaml2/test_constants.go
сгенерированный
поставляемый
Обычный файл
395
vendor/github.com/mattermost/gosaml2/test_constants.go
сгенерированный
поставляемый
Обычный файл
Различия файлов скрыты, потому что одна или несколько строк слишком длинны
83
vendor/github.com/mattermost/gosaml2/types/encrypted_assertion.go
сгенерированный
поставляемый
Обычный файл
83
vendor/github.com/mattermost/gosaml2/types/encrypted_assertion.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,83 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/cipher"
|
||||
"crypto/tls"
|
||||
"encoding/base64"
|
||||
"encoding/xml"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
type EncryptedAssertion struct {
|
||||
XMLName xml.Name `xml:"urn:oasis:names:tc:SAML:2.0:assertion EncryptedAssertion"`
|
||||
EncryptionMethod EncryptionMethod `xml:"EncryptedData>EncryptionMethod"`
|
||||
EncryptedKey EncryptedKey `xml:"EncryptedData>KeyInfo>EncryptedKey"`
|
||||
DetEncryptedKey EncryptedKey `xml:"EncryptedKey"` // detached EncryptedKey element
|
||||
CipherValue string `xml:"EncryptedData>CipherData>CipherValue"`
|
||||
}
|
||||
|
||||
func (ea *EncryptedAssertion) DecryptBytes(cert *tls.Certificate) ([]byte, error) {
|
||||
data, err := base64.StdEncoding.DecodeString(ea.CipherValue)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// EncryptedKey must include CipherValue. EncryptedKey may be part of EncryptedData.
|
||||
ek := &ea.EncryptedKey
|
||||
if ek.CipherValue == "" {
|
||||
// Use detached EncryptedKey element (sibling of EncryptedData). See:
|
||||
// https://www.w3.org/TR/2002/REC-xmlenc-core-20021210/Overview.html#sec-Extensions-to-KeyInfo
|
||||
ek = &ea.DetEncryptedKey
|
||||
}
|
||||
k, err := ek.DecryptSymmetricKey(cert)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot decrypt, error retrieving private key: %s", err)
|
||||
}
|
||||
|
||||
switch ea.EncryptionMethod.Algorithm {
|
||||
case MethodAES128GCM:
|
||||
c, err := cipher.NewGCM(k)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot create AES-GCM: %s", err)
|
||||
}
|
||||
|
||||
nonce, data := data[:c.NonceSize()], data[c.NonceSize():]
|
||||
plainText, err := c.Open(nil, nonce, data, nil)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot open AES-GCM: %s", err)
|
||||
}
|
||||
return plainText, nil
|
||||
case MethodAES128CBC, MethodAES256CBC:
|
||||
nonce, data := data[:k.BlockSize()], data[k.BlockSize():]
|
||||
c := cipher.NewCBCDecrypter(k, nonce)
|
||||
c.CryptBlocks(data, data)
|
||||
|
||||
// Remove zero bytes
|
||||
data = bytes.TrimRight(data, "\x00")
|
||||
|
||||
// Calculate index to remove based on padding
|
||||
padLength := data[len(data)-1]
|
||||
lastGoodIndex := len(data) - int(padLength)
|
||||
return data[:lastGoodIndex], nil
|
||||
default:
|
||||
return nil, fmt.Errorf("unknown symmetric encryption method %#v", ea.EncryptionMethod.Algorithm)
|
||||
}
|
||||
}
|
||||
|
||||
// Decrypt decrypts and unmarshals the EncryptedAssertion.
|
||||
func (ea *EncryptedAssertion) Decrypt(cert *tls.Certificate) (*Assertion, error) {
|
||||
plaintext, err := ea.DecryptBytes(cert)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Error decrypting assertion: %v", err)
|
||||
}
|
||||
|
||||
assertion := &Assertion{}
|
||||
|
||||
err = xml.Unmarshal(plaintext, assertion)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Error unmarshaling assertion: %v", err)
|
||||
}
|
||||
|
||||
return assertion, nil
|
||||
}
|
||||
140
vendor/github.com/mattermost/gosaml2/types/encrypted_key.go
сгенерированный
поставляемый
Обычный файл
140
vendor/github.com/mattermost/gosaml2/types/encrypted_key.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,140 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/sha1"
|
||||
"crypto/sha256"
|
||||
"crypto/sha512"
|
||||
"crypto/tls"
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"hash"
|
||||
"strings"
|
||||
)
|
||||
|
||||
//EncryptedKey contains the decryption key data from the saml2 core and xmlenc
|
||||
//standards.
|
||||
type EncryptedKey struct {
|
||||
// EncryptionMethod string `xml:"EncryptionMethod>Algorithm"`
|
||||
X509Data string `xml:"KeyInfo>X509Data>X509Certificate"`
|
||||
CipherValue string `xml:"CipherData>CipherValue"`
|
||||
EncryptionMethod EncryptionMethod
|
||||
}
|
||||
|
||||
//EncryptionMethod specifies the type of encryption that was used.
|
||||
type EncryptionMethod struct {
|
||||
Algorithm string `xml:",attr,omitempty"`
|
||||
DigestMethod DigestMethod `xml:",omitempty"`
|
||||
}
|
||||
|
||||
//DigestMethod is a digest type specification
|
||||
type DigestMethod struct {
|
||||
Algorithm string `xml:",attr,omitempty"`
|
||||
}
|
||||
|
||||
//Well-known public-key encryption methods
|
||||
const (
|
||||
MethodRSAOAEP = "http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p"
|
||||
MethodRSAOAEP2 = "http://www.w3.org/2009/xmlenc11#rsa-oaep"
|
||||
)
|
||||
|
||||
//Well-known private key encryption methods
|
||||
const (
|
||||
MethodAES128GCM = "http://www.w3.org/2009/xmlenc11#aes128-gcm"
|
||||
MethodAES128CBC = "http://www.w3.org/2001/04/xmlenc#aes128-cbc"
|
||||
MethodAES256CBC = "http://www.w3.org/2001/04/xmlenc#aes256-cbc"
|
||||
)
|
||||
|
||||
//Well-known hash methods
|
||||
const (
|
||||
MethodSHA1 = "http://www.w3.org/2000/09/xmldsig#sha1"
|
||||
MethodSHA256 = "http://www.w3.org/2000/09/xmldsig#sha256"
|
||||
MethodSHA512 = "http://www.w3.org/2000/09/xmldsig#sha512"
|
||||
)
|
||||
|
||||
//SHA-1 is commonly used for certificate fingerprints (openssl -fingerprint and ADFS thumbprint).
|
||||
//SHA-1 is sufficient for our purposes here (error message).
|
||||
func debugKeyFp(keyBytes []byte) string {
|
||||
if len(keyBytes) < 1 {
|
||||
return ""
|
||||
}
|
||||
hashFunc := sha1.New()
|
||||
hashFunc.Write(keyBytes)
|
||||
sum := strings.ToLower(hex.EncodeToString(hashFunc.Sum(nil)))
|
||||
var ret string
|
||||
for idx := 0; idx+1 < len(sum); idx += 2 {
|
||||
if idx == 0 {
|
||||
ret += sum[idx : idx+2]
|
||||
} else {
|
||||
ret += ":" + sum[idx:idx+2]
|
||||
}
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
//DecryptSymmetricKey returns the private key contained in the EncryptedKey document
|
||||
func (ek *EncryptedKey) DecryptSymmetricKey(cert *tls.Certificate) (cipher.Block, error) {
|
||||
if len(cert.Certificate) < 1 {
|
||||
return nil, fmt.Errorf("decryption tls.Certificate has no public certs attached")
|
||||
}
|
||||
|
||||
// The EncryptedKey may or may not include X509Data (certificate).
|
||||
// If included, the EncryptedKey certificate:
|
||||
// - is FYI only (fail if it does not match the SP certificate)
|
||||
// - is NOT used to decrypt CipherData
|
||||
if ek.X509Data != "" {
|
||||
if encCert, err := base64.StdEncoding.DecodeString(ek.X509Data); err != nil {
|
||||
return nil, fmt.Errorf("error decoding EncryptedKey certificate: %v", err)
|
||||
} else if !bytes.Equal(cert.Certificate[0], encCert) {
|
||||
return nil, fmt.Errorf("key decryption attempted with mismatched cert, SP cert(%.11s), assertion cert(%.11s)",
|
||||
debugKeyFp(cert.Certificate[0]), debugKeyFp(encCert))
|
||||
}
|
||||
}
|
||||
|
||||
cipherText, err := base64.StdEncoding.DecodeString(ek.CipherValue)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
switch pk := cert.PrivateKey.(type) {
|
||||
case *rsa.PrivateKey:
|
||||
var h hash.Hash
|
||||
|
||||
switch ek.EncryptionMethod.DigestMethod.Algorithm {
|
||||
case "", MethodSHA1:
|
||||
h = sha1.New() // default
|
||||
case MethodSHA256:
|
||||
h = sha256.New()
|
||||
case MethodSHA512:
|
||||
h = sha512.New()
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported digest algorithm: %v",
|
||||
ek.EncryptionMethod.DigestMethod.Algorithm)
|
||||
}
|
||||
|
||||
switch ek.EncryptionMethod.Algorithm {
|
||||
case "":
|
||||
return nil, fmt.Errorf("missing encryption algorithm")
|
||||
case MethodRSAOAEP, MethodRSAOAEP2:
|
||||
pt, err := rsa.DecryptOAEP(h, rand.Reader, pk, cipherText, nil)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("rsa internal error: %v", err)
|
||||
}
|
||||
|
||||
b, err := aes.NewCipher(pt)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return b, nil
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported encryption algorithm: %s", ek.EncryptionMethod.Algorithm)
|
||||
}
|
||||
}
|
||||
return nil, fmt.Errorf("no cipher for decoding symmetric key")
|
||||
}
|
||||
67
vendor/github.com/mattermost/gosaml2/types/metadata.go
сгенерированный
поставляемый
Обычный файл
67
vendor/github.com/mattermost/gosaml2/types/metadata.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,67 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"encoding/xml"
|
||||
"time"
|
||||
|
||||
dsigtypes "github.com/russellhaering/goxmldsig/types"
|
||||
)
|
||||
|
||||
type EntityDescriptor struct {
|
||||
XMLName xml.Name `xml:"urn:oasis:names:tc:SAML:2.0:metadata EntityDescriptor"`
|
||||
ValidUntil time.Time `xml:"validUntil,attr"`
|
||||
// SAML 2.0 8.3.6 Entity Identifier could be used to represent issuer
|
||||
EntityID string `xml:"entityID,attr"`
|
||||
SPSSODescriptor *SPSSODescriptor `xml:"SPSSODescriptor,omitempty"`
|
||||
IDPSSODescriptor *IDPSSODescriptor `xml:"IDPSSODescriptor,omitempty"`
|
||||
}
|
||||
|
||||
type Endpoint struct {
|
||||
Binding string `xml:"Binding,attr"`
|
||||
Location string `xml:"Location,attr"`
|
||||
ResponseLocation string `xml:"ResponseLocation,attr,omitempty"`
|
||||
}
|
||||
|
||||
type IndexedEndpoint struct {
|
||||
Binding string `xml:"Binding,attr"`
|
||||
Location string `xml:"Location,attr"`
|
||||
Index int `xml:"index,attr"`
|
||||
}
|
||||
|
||||
type SPSSODescriptor struct {
|
||||
XMLName xml.Name `xml:"urn:oasis:names:tc:SAML:2.0:metadata SPSSODescriptor"`
|
||||
AuthnRequestsSigned bool `xml:"AuthnRequestsSigned,attr"`
|
||||
WantAssertionsSigned bool `xml:"WantAssertionsSigned,attr"`
|
||||
ProtocolSupportEnumeration string `xml:"protocolSupportEnumeration,attr"`
|
||||
KeyDescriptors []KeyDescriptor `xml:"KeyDescriptor"`
|
||||
SingleLogoutServices []Endpoint `xml:"SingleLogoutService"`
|
||||
NameIDFormats []string `xml:"NameIDFormat"`
|
||||
AssertionConsumerServices []IndexedEndpoint `xml:"AssertionConsumerService"`
|
||||
}
|
||||
|
||||
type IDPSSODescriptor struct {
|
||||
XMLName xml.Name `xml:"urn:oasis:names:tc:SAML:2.0:metadata IDPSSODescriptor"`
|
||||
WantAuthnRequestsSigned bool `xml:"WantAuthnRequestsSigned,attr"`
|
||||
KeyDescriptors []KeyDescriptor `xml:"KeyDescriptor"`
|
||||
NameIDFormats []NameIDFormat `xml:"NameIDFormat"`
|
||||
SingleSignOnServices []SingleSignOnService `xml:"SingleSignOnService"`
|
||||
Attributes []Attribute `xml:"Attribute"`
|
||||
}
|
||||
|
||||
type KeyDescriptor struct {
|
||||
XMLName xml.Name `xml:"urn:oasis:names:tc:SAML:2.0:metadata KeyDescriptor"`
|
||||
Use string `xml:"use,attr"`
|
||||
KeyInfo dsigtypes.KeyInfo `xml:"KeyInfo"`
|
||||
EncryptionMethods []EncryptionMethod `xml:"EncryptionMethod"`
|
||||
}
|
||||
|
||||
type NameIDFormat struct {
|
||||
XMLName xml.Name `xml:"urn:oasis:names:tc:SAML:2.0:metadata NameIDFormat"`
|
||||
Value string `xml:",chardata"`
|
||||
}
|
||||
|
||||
type SingleSignOnService struct {
|
||||
XMLName xml.Name `xml:"urn:oasis:names:tc:SAML:2.0:metadata SingleSignOnService"`
|
||||
Binding string `xml:"Binding,attr"`
|
||||
Location string `xml:"Location,attr"`
|
||||
}
|
||||
156
vendor/github.com/mattermost/gosaml2/types/response.go
сгенерированный
поставляемый
Обычный файл
156
vendor/github.com/mattermost/gosaml2/types/response.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,156 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"encoding/xml"
|
||||
"time"
|
||||
)
|
||||
|
||||
// UnverifiedBaseResponse extracts several basic attributes of a SAML Response
|
||||
// which may be useful in deciding how to validate the Response. An UnverifiedBaseResponse
|
||||
// is parsed by this library prior to any validation of the Response, so the
|
||||
// values it contains may have been supplied by an attacker and should not be
|
||||
// trusted as authoritative from the IdP.
|
||||
type UnverifiedBaseResponse struct {
|
||||
XMLName xml.Name `xml:"urn:oasis:names:tc:SAML:2.0:protocol Response"`
|
||||
ID string `xml:"ID,attr"`
|
||||
InResponseTo string `xml:"InResponseTo,attr"`
|
||||
Destination string `xml:"Destination,attr"`
|
||||
Version string `xml:"Version,attr"`
|
||||
Issuer *Issuer `xml:"Issuer"`
|
||||
}
|
||||
|
||||
type Response struct {
|
||||
XMLName xml.Name `xml:"urn:oasis:names:tc:SAML:2.0:protocol Response"`
|
||||
ID string `xml:"ID,attr"`
|
||||
InResponseTo string `xml:"InResponseTo,attr"`
|
||||
Destination string `xml:"Destination,attr"`
|
||||
Version string `xml:"Version,attr"`
|
||||
IssueInstant time.Time `xml:"IssueInstant,attr"`
|
||||
Status *Status `xml:"Status"`
|
||||
Issuer *Issuer `xml:"Issuer"`
|
||||
Assertions []Assertion `xml:"Assertion"`
|
||||
EncryptedAssertions []EncryptedAssertion `xml:"EncryptedAssertion"`
|
||||
SignatureValidated bool `xml:"-"` // not read, not dumped
|
||||
}
|
||||
|
||||
type Status struct {
|
||||
XMLName xml.Name `xml:"urn:oasis:names:tc:SAML:2.0:protocol Status"`
|
||||
StatusCode *StatusCode `xml:"StatusCode"`
|
||||
}
|
||||
|
||||
type StatusCode struct {
|
||||
XMLName xml.Name `xml:"urn:oasis:names:tc:SAML:2.0:protocol StatusCode"`
|
||||
Value string `xml:"Value,attr"`
|
||||
}
|
||||
|
||||
type Issuer struct {
|
||||
XMLName xml.Name `xml:"urn:oasis:names:tc:SAML:2.0:assertion Issuer"`
|
||||
Value string `xml:",chardata"`
|
||||
}
|
||||
|
||||
type Signature struct {
|
||||
SignatureDocument []byte `xml:",innerxml"`
|
||||
}
|
||||
|
||||
type Assertion struct {
|
||||
XMLName xml.Name `xml:"urn:oasis:names:tc:SAML:2.0:assertion Assertion"`
|
||||
Version string `xml:"Version,attr"`
|
||||
ID string `xml:"ID,attr"`
|
||||
IssueInstant time.Time `xml:"IssueInstant,attr"`
|
||||
Issuer *Issuer `xml:"Issuer"`
|
||||
Signature *Signature `xml:"Signature"`
|
||||
Subject *Subject `xml:"Subject"`
|
||||
Conditions *Conditions `xml:"Conditions"`
|
||||
AttributeStatement *AttributeStatement `xml:"AttributeStatement"`
|
||||
AuthnStatement *AuthnStatement `xml:"AuthnStatement"`
|
||||
SignatureValidated bool `xml:"-"` // not read, not dumped
|
||||
}
|
||||
|
||||
type Subject struct {
|
||||
XMLName xml.Name `xml:"urn:oasis:names:tc:SAML:2.0:assertion Subject"`
|
||||
NameID *NameID `xml:"NameID"`
|
||||
SubjectConfirmation *SubjectConfirmation `xml:"SubjectConfirmation"`
|
||||
}
|
||||
|
||||
type AuthnContext struct {
|
||||
XMLName xml.Name `xml:urn:oasis:names:tc:SAML:2.0:assertion AuthnContext"`
|
||||
AuthnContextClassRef *AuthnContextClassRef `xml:"AuthnContextClassRef"`
|
||||
}
|
||||
|
||||
type AuthnContextClassRef struct {
|
||||
XMLName xml.Name `xml:urn:oasis:names:tc:SAML:2.0:assertion AuthnContextClassRef"`
|
||||
Value string `xml:",chardata"`
|
||||
}
|
||||
|
||||
type NameID struct {
|
||||
XMLName xml.Name `xml:"urn:oasis:names:tc:SAML:2.0:assertion NameID"`
|
||||
Value string `xml:",chardata"`
|
||||
}
|
||||
|
||||
type SubjectConfirmation struct {
|
||||
XMLName xml.Name `xml:"urn:oasis:names:tc:SAML:2.0:assertion SubjectConfirmation"`
|
||||
Method string `xml:"Method,attr"`
|
||||
SubjectConfirmationData *SubjectConfirmationData `xml:"SubjectConfirmationData"`
|
||||
}
|
||||
|
||||
type SubjectConfirmationData struct {
|
||||
XMLName xml.Name `xml:"urn:oasis:names:tc:SAML:2.0:assertion SubjectConfirmationData"`
|
||||
NotOnOrAfter string `xml:"NotOnOrAfter,attr"`
|
||||
Recipient string `xml:"Recipient,attr"`
|
||||
InResponseTo string `xml:"InResponseTo,attr"`
|
||||
}
|
||||
|
||||
type Conditions struct {
|
||||
XMLName xml.Name `xml:"urn:oasis:names:tc:SAML:2.0:assertion Conditions"`
|
||||
NotBefore string `xml:"NotBefore,attr"`
|
||||
NotOnOrAfter string `xml:"NotOnOrAfter,attr"`
|
||||
AudienceRestrictions []AudienceRestriction `xml:"AudienceRestriction"`
|
||||
OneTimeUse *OneTimeUse `xml:"OneTimeUse"`
|
||||
ProxyRestriction *ProxyRestriction `xml:"ProxyRestriction"`
|
||||
}
|
||||
|
||||
type AudienceRestriction struct {
|
||||
XMLName xml.Name `xml:"urn:oasis:names:tc:SAML:2.0:assertion AudienceRestriction"`
|
||||
Audiences []Audience `xml:"Audience"`
|
||||
}
|
||||
|
||||
type Audience struct {
|
||||
XMLName xml.Name `xml:"urn:oasis:names:tc:SAML:2.0:assertion Audience"`
|
||||
Value string `xml:",chardata"`
|
||||
}
|
||||
|
||||
type OneTimeUse struct {
|
||||
XMLName xml.Name `xml:"urn:oasis:names:tc:SAML:2.0:assertion OneTimeUse"`
|
||||
}
|
||||
|
||||
type ProxyRestriction struct {
|
||||
XMLName xml.Name `xml:"urn:oasis:names:tc:SAML:2.0:assertion ProxyRestriction"`
|
||||
Count int `xml:"Count,attr"`
|
||||
Audience []Audience `xml:"Audience"`
|
||||
}
|
||||
|
||||
type AttributeStatement struct {
|
||||
XMLName xml.Name `xml:"urn:oasis:names:tc:SAML:2.0:assertion AttributeStatement"`
|
||||
Attributes []Attribute `xml:"Attribute"`
|
||||
}
|
||||
|
||||
type Attribute struct {
|
||||
XMLName xml.Name `xml:"urn:oasis:names:tc:SAML:2.0:assertion Attribute"`
|
||||
FriendlyName string `xml:"FriendlyName,attr"`
|
||||
Name string `xml:"Name,attr"`
|
||||
NameFormat string `xml:"NameFormat,attr"`
|
||||
Values []AttributeValue `xml:"AttributeValue"`
|
||||
}
|
||||
|
||||
type AttributeValue struct {
|
||||
XMLName xml.Name `xml:"urn:oasis:names:tc:SAML:2.0:assertion AttributeValue"`
|
||||
Type string `xml:"xsi:type,attr"`
|
||||
Value string `xml:",chardata"`
|
||||
}
|
||||
|
||||
type AuthnStatement struct {
|
||||
XMLName xml.Name `xml:"urn:oasis:names:tc:SAML:2.0:assertion AuthnStatement"`
|
||||
AuthnInstant *time.Time `xml:"AuthnInstant,attr,omitempty"`
|
||||
SessionNotOnOrAfter *time.Time `xml:"SessionNotOnOrAfter,attr,omitempty"`
|
||||
AuthnContext *AuthnContext `xml:"AuthnContext"`
|
||||
}
|
||||
27
vendor/github.com/mattermost/gosaml2/uuid/uuid.go
сгенерированный
поставляемый
Обычный файл
27
vendor/github.com/mattermost/gosaml2/uuid/uuid.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,27 @@
|
||||
package uuid
|
||||
|
||||
// relevant bits from https://github.com/abneptis/GoUUID/blob/master/uuid.go
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
type UUID [16]byte
|
||||
|
||||
// NewV4 returns random generated UUID.
|
||||
func NewV4() *UUID {
|
||||
u := &UUID{}
|
||||
_, err := rand.Read(u[:16])
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
u[8] = (u[8] | 0x80) & 0xBf
|
||||
u[6] = (u[6] | 0x40) & 0x4f
|
||||
return u
|
||||
}
|
||||
|
||||
func (u *UUID) String() string {
|
||||
return fmt.Sprintf("%x-%x-%x-%x-%x", u[:4], u[4:6], u[6:8], u[8:10], u[10:])
|
||||
}
|
||||
231
vendor/github.com/mattermost/gosaml2/validate.go
сгенерированный
поставляемый
Обычный файл
231
vendor/github.com/mattermost/gosaml2/validate.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,231 @@
|
||||
package saml2
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/mattermost/gosaml2/types"
|
||||
)
|
||||
|
||||
//ErrParsing indicates that the value present in an assertion could not be
|
||||
//parsed. It can be inspected for the specific tag name, the contents, and the
|
||||
//intended type.
|
||||
type ErrParsing struct {
|
||||
Tag, Value, Type string
|
||||
}
|
||||
|
||||
func (ep ErrParsing) Error() string {
|
||||
return fmt.Sprintf("Error parsing %s tag value as type %s", ep.Tag, ep.Value)
|
||||
}
|
||||
|
||||
//Oft-used messages
|
||||
const (
|
||||
ReasonUnsupported = "Unsupported"
|
||||
ReasonExpired = "Expired"
|
||||
)
|
||||
|
||||
//ErrInvalidValue indicates that the expected value did not match the received
|
||||
//value.
|
||||
type ErrInvalidValue struct {
|
||||
Key, Expected, Actual string
|
||||
Reason string
|
||||
}
|
||||
|
||||
func (e ErrInvalidValue) Error() string {
|
||||
if e.Reason == "" {
|
||||
e.Reason = "Unrecognized"
|
||||
}
|
||||
return fmt.Sprintf("%s %s value, Expected: %s, Actual: %s", e.Reason, e.Key, e.Expected, e.Actual)
|
||||
}
|
||||
|
||||
//Well-known methods of subject confirmation
|
||||
const (
|
||||
SubjMethodBearer = "urn:oasis:names:tc:SAML:2.0:cm:bearer"
|
||||
)
|
||||
|
||||
//VerifyAssertionConditions inspects an assertion element and makes sure that
|
||||
//all SAML2 contracts are upheld.
|
||||
func (sp *SAMLServiceProvider) VerifyAssertionConditions(assertion *types.Assertion) (*WarningInfo, error) {
|
||||
warningInfo := &WarningInfo{}
|
||||
now := sp.Clock.Now()
|
||||
|
||||
conditions := assertion.Conditions
|
||||
if conditions == nil {
|
||||
return nil, ErrMissingElement{Tag: ConditionsTag}
|
||||
}
|
||||
|
||||
if conditions.NotBefore == "" {
|
||||
return nil, ErrMissingElement{Tag: ConditionsTag, Attribute: NotBeforeAttr}
|
||||
}
|
||||
|
||||
notBefore, err := time.Parse(time.RFC3339, conditions.NotBefore)
|
||||
if err != nil {
|
||||
return nil, ErrParsing{Tag: NotBeforeAttr, Value: conditions.NotBefore, Type: "time.RFC3339"}
|
||||
}
|
||||
|
||||
if now.Before(notBefore) {
|
||||
warningInfo.InvalidTime = true
|
||||
}
|
||||
|
||||
if conditions.NotOnOrAfter == "" {
|
||||
return nil, ErrMissingElement{Tag: ConditionsTag, Attribute: NotOnOrAfterAttr}
|
||||
}
|
||||
|
||||
notOnOrAfter, err := time.Parse(time.RFC3339, conditions.NotOnOrAfter)
|
||||
if err != nil {
|
||||
return nil, ErrParsing{Tag: NotOnOrAfterAttr, Value: conditions.NotOnOrAfter, Type: "time.RFC3339"}
|
||||
}
|
||||
|
||||
if now.After(notOnOrAfter) {
|
||||
warningInfo.InvalidTime = true
|
||||
}
|
||||
|
||||
for _, audienceRestriction := range conditions.AudienceRestrictions {
|
||||
matched := false
|
||||
|
||||
for _, audience := range audienceRestriction.Audiences {
|
||||
if audience.Value == sp.AudienceURI {
|
||||
matched = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !matched {
|
||||
warningInfo.NotInAudience = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if conditions.OneTimeUse != nil {
|
||||
warningInfo.OneTimeUse = true
|
||||
}
|
||||
|
||||
proxyRestriction := conditions.ProxyRestriction
|
||||
if proxyRestriction != nil {
|
||||
proxyRestrictionInfo := &ProxyRestriction{
|
||||
Count: proxyRestriction.Count,
|
||||
Audience: []string{},
|
||||
}
|
||||
|
||||
for _, audience := range proxyRestriction.Audience {
|
||||
proxyRestrictionInfo.Audience = append(proxyRestrictionInfo.Audience, audience.Value)
|
||||
}
|
||||
|
||||
warningInfo.ProxyRestriction = proxyRestrictionInfo
|
||||
}
|
||||
|
||||
return warningInfo, nil
|
||||
}
|
||||
|
||||
//Validate ensures that the assertion passed is valid for the current Service
|
||||
//Provider.
|
||||
func (sp *SAMLServiceProvider) Validate(response *types.Response) error {
|
||||
err := sp.validateResponseAttributes(response)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(response.Assertions) == 0 {
|
||||
return ErrMissingAssertion
|
||||
}
|
||||
|
||||
issuer := response.Issuer
|
||||
if issuer == nil {
|
||||
// FIXME?: SAML Core 2.0 Section 3.2.2 has Response.Issuer as [Optional]
|
||||
return ErrMissingElement{Tag: IssuerTag}
|
||||
}
|
||||
|
||||
if sp.IdentityProviderIssuer != "" && response.Issuer.Value != sp.IdentityProviderIssuer {
|
||||
return ErrInvalidValue{
|
||||
Key: IssuerTag,
|
||||
Expected: sp.IdentityProviderIssuer,
|
||||
Actual: response.Issuer.Value,
|
||||
}
|
||||
}
|
||||
|
||||
status := response.Status
|
||||
if status == nil {
|
||||
return ErrMissingElement{Tag: StatusTag}
|
||||
}
|
||||
|
||||
statusCode := status.StatusCode
|
||||
if statusCode == nil {
|
||||
return ErrMissingElement{Tag: StatusCodeTag}
|
||||
}
|
||||
|
||||
if statusCode.Value != StatusCodeSuccess {
|
||||
return ErrInvalidValue{
|
||||
Key: StatusCodeTag,
|
||||
Expected: StatusCodeSuccess,
|
||||
Actual: statusCode.Value,
|
||||
}
|
||||
}
|
||||
|
||||
for _, assertion := range response.Assertions {
|
||||
issuer = assertion.Issuer
|
||||
if issuer == nil {
|
||||
return ErrMissingElement{Tag: IssuerTag}
|
||||
}
|
||||
if sp.IdentityProviderIssuer != "" && assertion.Issuer.Value != sp.IdentityProviderIssuer {
|
||||
return ErrInvalidValue{
|
||||
Key: IssuerTag,
|
||||
Expected: sp.IdentityProviderIssuer,
|
||||
Actual: issuer.Value,
|
||||
}
|
||||
}
|
||||
|
||||
subject := assertion.Subject
|
||||
if subject == nil {
|
||||
return ErrMissingElement{Tag: SubjectTag}
|
||||
}
|
||||
|
||||
subjectConfirmation := subject.SubjectConfirmation
|
||||
if subjectConfirmation == nil {
|
||||
return ErrMissingElement{Tag: SubjectConfirmationTag}
|
||||
}
|
||||
|
||||
if subjectConfirmation.Method != SubjMethodBearer {
|
||||
return ErrInvalidValue{
|
||||
Reason: ReasonUnsupported,
|
||||
Key: SubjectConfirmationTag,
|
||||
Expected: SubjMethodBearer,
|
||||
Actual: subjectConfirmation.Method,
|
||||
}
|
||||
}
|
||||
|
||||
subjectConfirmationData := subjectConfirmation.SubjectConfirmationData
|
||||
if subjectConfirmationData == nil {
|
||||
return ErrMissingElement{Tag: SubjectConfirmationDataTag}
|
||||
}
|
||||
|
||||
if subjectConfirmationData.Recipient != sp.AssertionConsumerServiceURL {
|
||||
return ErrInvalidValue{
|
||||
Key: RecipientAttr,
|
||||
Expected: sp.AssertionConsumerServiceURL,
|
||||
Actual: subjectConfirmationData.Recipient,
|
||||
}
|
||||
}
|
||||
|
||||
if subjectConfirmationData.NotOnOrAfter == "" {
|
||||
return ErrMissingElement{Tag: SubjectConfirmationDataTag, Attribute: NotOnOrAfterAttr}
|
||||
}
|
||||
|
||||
notOnOrAfter, err := time.Parse(time.RFC3339, subjectConfirmationData.NotOnOrAfter)
|
||||
if err != nil {
|
||||
return ErrParsing{Tag: NotOnOrAfterAttr, Value: subjectConfirmationData.NotOnOrAfter, Type: "time.RFC3339"}
|
||||
}
|
||||
|
||||
now := sp.Clock.Now()
|
||||
if now.After(notOnOrAfter) {
|
||||
return ErrInvalidValue{
|
||||
Reason: ReasonExpired,
|
||||
Key: NotOnOrAfterAttr,
|
||||
Expected: now.Format(time.RFC3339),
|
||||
Actual: subjectConfirmationData.NotOnOrAfter,
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
58
vendor/github.com/mattermost/gosaml2/xml_constants.go
сгенерированный
поставляемый
Обычный файл
58
vendor/github.com/mattermost/gosaml2/xml_constants.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,58 @@
|
||||
package saml2
|
||||
|
||||
const (
|
||||
ResponseTag = "Response"
|
||||
AssertionTag = "Assertion"
|
||||
EncryptedAssertionTag = "EncryptedAssertion"
|
||||
SubjectTag = "Subject"
|
||||
NameIdTag = "NameID"
|
||||
SubjectConfirmationTag = "SubjectConfirmation"
|
||||
SubjectConfirmationDataTag = "SubjectConfirmationData"
|
||||
AttributeStatementTag = "AttributeStatement"
|
||||
AttributeValueTag = "AttributeValue"
|
||||
ConditionsTag = "Conditions"
|
||||
AudienceRestrictionTag = "AudienceRestriction"
|
||||
AudienceTag = "Audience"
|
||||
OneTimeUseTag = "OneTimeUse"
|
||||
ProxyRestrictionTag = "ProxyRestriction"
|
||||
IssuerTag = "Issuer"
|
||||
StatusTag = "Status"
|
||||
StatusCodeTag = "StatusCode"
|
||||
)
|
||||
|
||||
const (
|
||||
DestinationAttr = "Destination"
|
||||
VersionAttr = "Version"
|
||||
IdAttr = "ID"
|
||||
MethodAttr = "Method"
|
||||
RecipientAttr = "Recipient"
|
||||
NameAttr = "Name"
|
||||
NotBeforeAttr = "NotBefore"
|
||||
NotOnOrAfterAttr = "NotOnOrAfter"
|
||||
CountAttr = "Count"
|
||||
)
|
||||
|
||||
const (
|
||||
NameIdFormatPersistent = "urn:oasis:names:tc:SAML:2.0:nameid-format:persistent"
|
||||
NameIdFormatTransient = "urn:oasis:names:tc:SAML:2.0:nameid-format:transient"
|
||||
NameIdFormatEmailAddress = "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress"
|
||||
NameIdFormatUnspecified = "urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified"
|
||||
NameIdFormatX509SubjectName = "urn:oasis:names:tc:SAML:1.1:nameid-format:x509SubjectName"
|
||||
|
||||
AuthnContextPasswordProtectedTransport = "urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport"
|
||||
|
||||
AuthnPolicyMatchExact = "exact"
|
||||
AuthnPolicyMatchMinimum = "minimum"
|
||||
AuthnPolicyMatchMaximum = "maximum"
|
||||
AuthnPolicyMatchBetter = "better"
|
||||
|
||||
StatusCodeSuccess = "urn:oasis:names:tc:SAML:2.0:status:Success"
|
||||
|
||||
BindingHttpPost = "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"
|
||||
BindingHttpRedirect = "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect"
|
||||
)
|
||||
|
||||
const (
|
||||
SAMLAssertionNamespace = "urn:oasis:names:tc:SAML:2.0:assertion"
|
||||
SAMLProtocolNamespace = "urn:oasis:names:tc:SAML:2.0:protocol"
|
||||
)
|
||||
Ссылка в новой задаче
Block a user