diff --git a/cmd/mattermost/main.go b/cmd/mattermost/main.go
index 6570482034..493359a1be 100644
--- a/cmd/mattermost/main.go
+++ b/cmd/mattermost/main.go
@@ -18,6 +18,7 @@ import (
_ "github.com/gorilla/handlers"
_ "github.com/hako/durafmt"
_ "github.com/hashicorp/memberlist"
+ _ "github.com/mattermost/gosaml2"
_ "github.com/mattermost/ldap"
_ "github.com/mattermost/rsc/qr"
_ "github.com/prometheus/client_golang/prometheus"
diff --git a/go.mod b/go.mod
index 02d2668a90..06318a418d 100644
--- a/go.mod
+++ b/go.mod
@@ -7,6 +7,7 @@ require (
github.com/NYTimes/gziphandler v1.1.1
github.com/armon/go-metrics v0.0.0-20190430140413-ec5e00d3c878 // indirect
github.com/avct/uasurfer v0.0.0-20190821150637-906cc7dc6197
+ github.com/beevik/etree v1.1.0 // indirect
github.com/blang/semver v3.5.1+incompatible
github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 // indirect
github.com/corpix/uarand v0.1.1 // indirect
@@ -38,12 +39,14 @@ require (
github.com/icrowley/fake v0.0.0-20180203215853-4178557ae428
github.com/jaytaylor/html2text v0.0.0-20190408195923-01ec452cbe43
github.com/jmoiron/sqlx v1.2.0
+ github.com/jonboulle/clockwork v0.1.0 // indirect
github.com/konsorten/go-windows-terminal-sequences v1.0.2 // indirect
github.com/lib/pq v1.2.0
github.com/magiconair/properties v1.8.1 // indirect
github.com/mailru/easyjson v0.7.0 // indirect
github.com/mattermost/go-i18n v1.11.0
github.com/mattermost/gorp v2.0.1-0.20190301154413-3b31e9a39d05+incompatible
+ github.com/mattermost/gosaml2 v0.3.2
github.com/mattermost/ldap v0.0.0-20191128190019-9f62ba4b8d4d
github.com/mattermost/rsc v0.0.0-20160330161541-bbaefb05eaa0
github.com/mattermost/viper v1.0.4
@@ -65,6 +68,7 @@ require (
github.com/prometheus/common v0.7.0 // indirect
github.com/prometheus/procfs v0.0.5 // indirect
github.com/rs/cors v1.7.0
+ github.com/russellhaering/goxmldsig v0.0.0-20180430223755-7acd5e4a6ef7 // indirect
github.com/rwcarlsen/goexif v0.0.0-20190401172101-9e8deecbddbd
github.com/segmentio/analytics-go v3.1.0+incompatible
github.com/segmentio/backo-go v0.0.0-20160424052352-204274ad699c // indirect
diff --git a/go.sum b/go.sum
index f011000e95..2519ee62f5 100644
--- a/go.sum
+++ b/go.sum
@@ -35,6 +35,8 @@ github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj
github.com/avct/uasurfer v0.0.0-20190821150637-906cc7dc6197 h1:E7XoJNlFlrtC6dlRG9SoGBwJpX8vD7fNTKRmgSwAj5I=
github.com/avct/uasurfer v0.0.0-20190821150637-906cc7dc6197/go.mod h1:noBAuukeYOXa0aXGqxr24tADqkwDO2KRD15FsuaZ5a8=
github.com/aws/aws-sdk-go v1.19.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
+github.com/beevik/etree v1.1.0 h1:T0xke/WvNtMoCqgzPhkX2r4rjY3GDZFi+FjpRZY2Jbs=
+github.com/beevik/etree v1.1.0/go.mod h1:r8Aw8JqVegEf0w2fDnATrX9VpkMcyFeM0FhwO62wh+A=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973 h1:xJ4a3vCFaGF/jqvzLMYoU8P317H5OQ+Via4RmuPwCS0=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/beorn7/perks v1.0.0 h1:HWo1m869IqiPhD389kmkxeTalrjNbbJTC8LXupb+sl0=
@@ -207,6 +209,8 @@ github.com/jellevandenhooff/dkim v0.0.0-20150330215556-f50fe3d243e1/go.mod h1:E0
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
github.com/jmoiron/sqlx v1.2.0 h1:41Ip0zITnmWNR/vHV+S4m+VoUivnWY5E4OJfLZjCJMA=
github.com/jmoiron/sqlx v1.2.0/go.mod h1:1FEQNm3xlJgrMD+FBdI9+xvCksHtbpVBBw5dYhBSsks=
+github.com/jonboulle/clockwork v0.1.0 h1:VKV+ZcuP6l3yW9doeqz6ziZGgcynBVQO+obU0+0hcPo=
+github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
@@ -244,6 +248,8 @@ github.com/mattermost/go-i18n v1.11.0 h1:1hLKqn/ZvhZ80OekjVPGYcCrBfMz+YxNNgqS+be
github.com/mattermost/go-i18n v1.11.0/go.mod h1:RyS7FDNQlzF1PsjbJWHRI35exqaKGSO9qD4iv8QjE34=
github.com/mattermost/gorp v2.0.1-0.20190301154413-3b31e9a39d05+incompatible h1:FN4zK2wNig7MVVsOsGEZ+LeIq0gUcudn3LEGgbodMq8=
github.com/mattermost/gorp v2.0.1-0.20190301154413-3b31e9a39d05+incompatible/go.mod h1:0kX1qa3DOpaPJyOdMLeo7TcBN0QmUszj9a/VygOhDe0=
+github.com/mattermost/gosaml2 v0.3.2 h1:kq2dY5qUe6fPPHra171GVlgo+ycBsEog0gZMetxL8ow=
+github.com/mattermost/gosaml2 v0.3.2/go.mod h1:Z429EIOiEi9kbq6yHoApfzlcXpa6dzRDc6pO+Vy2Ksk=
github.com/mattermost/ldap v0.0.0-20191128190019-9f62ba4b8d4d h1:2DV7VIlEv6J5R5o6tUcb3ZMKJYeeZuWZL7Rv1m23TgQ=
github.com/mattermost/ldap v0.0.0-20191128190019-9f62ba4b8d4d/go.mod h1:HLbgMEI5K131jpxGazJ97AxfPDt31osq36YS1oxFQPQ=
github.com/mattermost/rsc v0.0.0-20160330161541-bbaefb05eaa0 h1:G9tL6JXRBMzjuD1kkBtcnd42kUiT6QDwxfFYu7adM6o=
@@ -360,6 +366,8 @@ github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqn
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
github.com/rs/cors v1.7.0 h1:+88SsELBHx5r+hZ8TCkggzSstaWNbDvThkVK8H6f9ik=
github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU=
+github.com/russellhaering/goxmldsig v0.0.0-20180430223755-7acd5e4a6ef7 h1:J4AOUcOh/t1XbQcJfkEqhzgvMJ2tDxdCVvmHxW5QXao=
+github.com/russellhaering/goxmldsig v0.0.0-20180430223755-7acd5e4a6ef7/go.mod h1:Oz4y6ImuOQZxynhbSXk7btjEfNBtGlj2dcaOvXl2FSM=
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
github.com/rwcarlsen/goexif v0.0.0-20190318171057-76e3344f7516 h1:unvLzUrgn54Nhn5yJB8qUBr/a7wiZHQaJ/8Y4yTGAjE=
github.com/rwcarlsen/goexif v0.0.0-20190318171057-76e3344f7516/go.mod h1:hPqNNc0+uJM6H+SuU8sEs5K5IQeKccPqeSjfgcKGgPk=
diff --git a/vendor/github.com/beevik/etree/.travis.yml b/vendor/github.com/beevik/etree/.travis.yml
new file mode 100644
index 0000000000..f4cb25d473
--- /dev/null
+++ b/vendor/github.com/beevik/etree/.travis.yml
@@ -0,0 +1,14 @@
+language: go
+sudo: false
+
+go:
+ - 1.11.x
+ - tip
+
+matrix:
+ allow_failures:
+ - go: tip
+
+script:
+ - go vet ./...
+ - go test -v ./...
diff --git a/vendor/github.com/beevik/etree/CONTRIBUTORS b/vendor/github.com/beevik/etree/CONTRIBUTORS
new file mode 100644
index 0000000000..03211a85eb
--- /dev/null
+++ b/vendor/github.com/beevik/etree/CONTRIBUTORS
@@ -0,0 +1,10 @@
+Brett Vickers (beevik)
+Felix Geisendörfer (felixge)
+Kamil Kisiel (kisielk)
+Graham King (grahamking)
+Matt Smith (ma314smith)
+Michal Jemala (michaljemala)
+Nicolas Piganeau (npiganeau)
+Chris Brown (ccbrown)
+Earncef Sequeira (earncef)
+Gabriel de Labachelerie (wuzuf)
diff --git a/vendor/github.com/beevik/etree/LICENSE b/vendor/github.com/beevik/etree/LICENSE
new file mode 100644
index 0000000000..26f1f7751b
--- /dev/null
+++ b/vendor/github.com/beevik/etree/LICENSE
@@ -0,0 +1,24 @@
+Copyright 2015-2019 Brett Vickers. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions
+are met:
+
+ 1. Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+
+ 2. Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY COPYRIGHT HOLDER ``AS IS'' AND ANY
+EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL COPYRIGHT HOLDER OR
+CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/vendor/github.com/beevik/etree/README.md b/vendor/github.com/beevik/etree/README.md
new file mode 100644
index 0000000000..08ec26b0ac
--- /dev/null
+++ b/vendor/github.com/beevik/etree/README.md
@@ -0,0 +1,205 @@
+[](https://travis-ci.org/beevik/etree)
+[](https://godoc.org/github.com/beevik/etree)
+
+etree
+=====
+
+The etree package is a lightweight, pure go package that expresses XML in
+the form of an element tree. Its design was inspired by the Python
+[ElementTree](http://docs.python.org/2/library/xml.etree.elementtree.html)
+module.
+
+Some of the package's capabilities and features:
+
+* Represents XML documents as trees of elements for easy traversal.
+* Imports, serializes, modifies or creates XML documents from scratch.
+* Writes and reads XML to/from files, byte slices, strings and io interfaces.
+* Performs simple or complex searches with lightweight XPath-like query APIs.
+* Auto-indents XML using spaces or tabs for better readability.
+* Implemented in pure go; depends only on standard go libraries.
+* Built on top of the go [encoding/xml](http://golang.org/pkg/encoding/xml)
+ package.
+
+### Creating an XML document
+
+The following example creates an XML document from scratch using the etree
+package and outputs its indented contents to stdout.
+```go
+doc := etree.NewDocument()
+doc.CreateProcInst("xml", `version="1.0" encoding="UTF-8"`)
+doc.CreateProcInst("xml-stylesheet", `type="text/xsl" href="style.xsl"`)
+
+people := doc.CreateElement("People")
+people.CreateComment("These are all known people")
+
+jon := people.CreateElement("Person")
+jon.CreateAttr("name", "Jon")
+
+sally := people.CreateElement("Person")
+sally.CreateAttr("name", "Sally")
+
+doc.Indent(2)
+doc.WriteTo(os.Stdout)
+```
+
+Output:
+```xml
+
+
+
+
+
+
+
+```
+
+### Reading an XML file
+
+Suppose you have a file on disk called `bookstore.xml` containing the
+following data:
+
+```xml
+
+
+
+ Everyday Italian
+ Giada De Laurentiis
+ 2005
+ 30.00
+
+
+
+ Harry Potter
+ J K. Rowling
+ 2005
+ 29.99
+
+
+
+ XQuery Kick Start
+ James McGovern
+ Per Bothner
+ Kurt Cagle
+ James Linn
+ Vaidyanathan Nagarajan
+ 2003
+ 49.99
+
+
+
+ Learning XML
+ Erik T. Ray
+ 2003
+ 39.95
+
+
+
+```
+
+This code reads the file's contents into an etree document.
+```go
+doc := etree.NewDocument()
+if err := doc.ReadFromFile("bookstore.xml"); err != nil {
+ panic(err)
+}
+```
+
+You can also read XML from a string, a byte slice, or an `io.Reader`.
+
+### Processing elements and attributes
+
+This example illustrates several ways to access elements and attributes using
+etree selection queries.
+```go
+root := doc.SelectElement("bookstore")
+fmt.Println("ROOT element:", root.Tag)
+
+for _, book := range root.SelectElements("book") {
+ fmt.Println("CHILD element:", book.Tag)
+ if title := book.SelectElement("title"); title != nil {
+ lang := title.SelectAttrValue("lang", "unknown")
+ fmt.Printf(" TITLE: %s (%s)\n", title.Text(), lang)
+ }
+ for _, attr := range book.Attr {
+ fmt.Printf(" ATTR: %s=%s\n", attr.Key, attr.Value)
+ }
+}
+```
+Output:
+```
+ROOT element: bookstore
+CHILD element: book
+ TITLE: Everyday Italian (en)
+ ATTR: category=COOKING
+CHILD element: book
+ TITLE: Harry Potter (en)
+ ATTR: category=CHILDREN
+CHILD element: book
+ TITLE: XQuery Kick Start (en)
+ ATTR: category=WEB
+CHILD element: book
+ TITLE: Learning XML (en)
+ ATTR: category=WEB
+```
+
+### Path queries
+
+This example uses etree's path functions to select all book titles that fall
+into the category of 'WEB'. The double-slash prefix in the path causes the
+search for book elements to occur recursively; book elements may appear at any
+level of the XML hierarchy.
+```go
+for _, t := range doc.FindElements("//book[@category='WEB']/title") {
+ fmt.Println("Title:", t.Text())
+}
+```
+
+Output:
+```
+Title: XQuery Kick Start
+Title: Learning XML
+```
+
+This example finds the first book element under the root bookstore element and
+outputs the tag and text of each of its child elements.
+```go
+for _, e := range doc.FindElements("./bookstore/book[1]/*") {
+ fmt.Printf("%s: %s\n", e.Tag, e.Text())
+}
+```
+
+Output:
+```
+title: Everyday Italian
+author: Giada De Laurentiis
+year: 2005
+price: 30.00
+```
+
+This example finds all books with a price of 49.99 and outputs their titles.
+```go
+path := etree.MustCompilePath("./bookstore/book[p:price='49.99']/title")
+for _, e := range doc.FindElementsPath(path) {
+ fmt.Println(e.Text())
+}
+```
+
+Output:
+```
+XQuery Kick Start
+```
+
+Note that this example uses the FindElementsPath function, which takes as an
+argument a pre-compiled path object. Use precompiled paths when you plan to
+search with the same path more than once.
+
+### Other features
+
+These are just a few examples of the things the etree package can do. See the
+[documentation](http://godoc.org/github.com/beevik/etree) for a complete
+description of its capabilities.
+
+### Contributing
+
+This project accepts contributions. Just fork the repo and submit a pull
+request!
diff --git a/vendor/github.com/beevik/etree/RELEASE_NOTES.md b/vendor/github.com/beevik/etree/RELEASE_NOTES.md
new file mode 100644
index 0000000000..ee59d7abf4
--- /dev/null
+++ b/vendor/github.com/beevik/etree/RELEASE_NOTES.md
@@ -0,0 +1,109 @@
+Release v1.1.0
+==============
+
+**New Features**
+
+* New attribute helpers.
+ * Added the `Element.SortAttrs` method, which lexicographically sorts an
+ element's attributes by key.
+* New `ReadSettings` properties.
+ * Added `Entity` for the support of custom entity maps.
+* New `WriteSettings` properties.
+ * Added `UseCRLF` to allow the output of CR-LF newlines instead of the
+ default LF newlines. This is useful on Windows systems.
+* Additional support for text and CDATA sections.
+ * The `Element.Text` method now returns the concatenation of all consecutive
+ character data tokens immediately following an element's opening tag.
+ * Added `Element.SetCData` to replace the character data immediately
+ following an element's opening tag with a CDATA section.
+ * Added `Element.CreateCData` to create and add a CDATA section child
+ `CharData` token to an element.
+ * Added `Element.CreateText` to create and add a child text `CharData` token
+ to an element.
+ * Added `NewCData` to create a parentless CDATA section `CharData` token.
+ * Added `NewText` to create a parentless text `CharData`
+ token.
+ * Added `CharData.IsCData` to detect if the token contains a CDATA section.
+ * Added `CharData.IsWhitespace` to detect if the token contains whitespace
+ inserted by one of the document Indent functions.
+ * Modified `Element.SetText` so that it replaces a run of consecutive
+ character data tokens following the element's opening tag (instead of just
+ the first one).
+* New "tail text" support.
+ * Added the `Element.Tail` method, which returns the text immediately
+ following an element's closing tag.
+ * Added the `Element.SetTail` method, which modifies the text immediately
+ following an element's closing tag.
+* New element child insertion and removal methods.
+ * Added the `Element.InsertChildAt` method, which inserts a new child token
+ before the specified child token index.
+ * Added the `Element.RemoveChildAt` method, which removes the child token at
+ the specified child token index.
+* New element and attribute queries.
+ * Added the `Element.Index` method, which returns the element's index within
+ its parent element's child token list.
+ * Added the `Element.NamespaceURI` method to return the namespace URI
+ associated with an element.
+ * Added the `Attr.NamespaceURI` method to return the namespace URI
+ associated with an element.
+ * Added the `Attr.Element` method to return the element that an attribute
+ belongs to.
+* New Path filter functions.
+ * Added `[local-name()='val']` to keep elements whose unprefixed tag matches
+ the desired value.
+ * Added `[name()='val']` to keep elements whose full tag matches the desired
+ value.
+ * Added `[namespace-prefix()='val']` to keep elements whose namespace prefix
+ matches the desired value.
+ * Added `[namespace-uri()='val']` to keep elements whose namespace URI
+ matches the desired value.
+
+**Bug Fixes**
+
+* A default XML `CharSetReader` is now used to prevent failed parsing of XML
+ documents using certain encodings.
+ ([Issue](https://github.com/beevik/etree/issues/53)).
+* All characters are now properly escaped according to XML parsing rules.
+ ([Issue](https://github.com/beevik/etree/issues/55)).
+* The `Document.Indent` and `Document.IndentTabs` functions no longer insert
+ empty string `CharData` tokens.
+
+**Deprecated**
+
+* `Element`
+ * The `InsertChild` method is deprecated. Use `InsertChildAt` instead.
+ * The `CreateCharData` method is deprecated. Use `CreateText` instead.
+* `CharData`
+ * The `NewCharData` method is deprecated. Use `NewText` instead.
+
+
+Release v1.0.1
+==============
+
+**Changes**
+
+* Added support for absolute etree Path queries. An absolute path begins with
+ `/` or `//` and begins its search from the element's document root.
+* Added [`GetPath`](https://godoc.org/github.com/beevik/etree#Element.GetPath)
+ and [`GetRelativePath`](https://godoc.org/github.com/beevik/etree#Element.GetRelativePath)
+ functions to the [`Element`](https://godoc.org/github.com/beevik/etree#Element)
+ type.
+
+**Breaking changes**
+
+* A path starting with `//` is now interpreted as an absolute path.
+ Previously, it was interpreted as a relative path starting from the element
+ whose
+ [`FindElement`](https://godoc.org/github.com/beevik/etree#Element.FindElement)
+ method was called. To remain compatible with this release, all paths
+ prefixed with `//` should be prefixed with `.//` when called from any
+ element other than the document's root.
+* [**edit 2/1/2019**]: Minor releases should not contain breaking changes.
+ Even though this breaking change was very minor, it was a mistake to include
+ it in this minor release. In the future, all breaking changes will be
+ limited to major releases (e.g., version 2.0.0).
+
+Release v1.0.0
+==============
+
+Initial release.
diff --git a/vendor/github.com/beevik/etree/etree.go b/vendor/github.com/beevik/etree/etree.go
new file mode 100644
index 0000000000..9e24f90126
--- /dev/null
+++ b/vendor/github.com/beevik/etree/etree.go
@@ -0,0 +1,1453 @@
+// Copyright 2015-2019 Brett Vickers.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Package etree provides XML services through an Element Tree
+// abstraction.
+package etree
+
+import (
+ "bufio"
+ "bytes"
+ "encoding/xml"
+ "errors"
+ "io"
+ "os"
+ "sort"
+ "strings"
+)
+
+const (
+ // NoIndent is used with Indent to disable all indenting.
+ NoIndent = -1
+)
+
+// ErrXML is returned when XML parsing fails due to incorrect formatting.
+var ErrXML = errors.New("etree: invalid XML format")
+
+// ReadSettings allow for changing the default behavior of the ReadFrom*
+// methods.
+type ReadSettings struct {
+ // CharsetReader to be passed to standard xml.Decoder. Default: nil.
+ CharsetReader func(charset string, input io.Reader) (io.Reader, error)
+
+ // Permissive allows input containing common mistakes such as missing tags
+ // or attribute values. Default: false.
+ Permissive bool
+
+ // Entity to be passed to standard xml.Decoder. Default: nil.
+ Entity map[string]string
+}
+
+// newReadSettings creates a default ReadSettings record.
+func newReadSettings() ReadSettings {
+ return ReadSettings{
+ CharsetReader: func(label string, input io.Reader) (io.Reader, error) {
+ return input, nil
+ },
+ Permissive: false,
+ }
+}
+
+// WriteSettings allow for changing the serialization behavior of the WriteTo*
+// methods.
+type WriteSettings struct {
+ // CanonicalEndTags forces the production of XML end tags, even for
+ // elements that have no child elements. Default: false.
+ CanonicalEndTags bool
+
+ // CanonicalText forces the production of XML character references for
+ // text data characters &, <, and >. If false, XML character references
+ // are also produced for " and '. Default: false.
+ CanonicalText bool
+
+ // CanonicalAttrVal forces the production of XML character references for
+ // attribute value characters &, < and ". If false, XML character
+ // references are also produced for > and '. Default: false.
+ CanonicalAttrVal bool
+
+ // When outputting indented XML, use a carriage return and linefeed
+ // ("\r\n") as a new-line delimiter instead of just a linefeed ("\n").
+ // This is useful on Windows-based systems.
+ UseCRLF bool
+}
+
+// newWriteSettings creates a default WriteSettings record.
+func newWriteSettings() WriteSettings {
+ return WriteSettings{
+ CanonicalEndTags: false,
+ CanonicalText: false,
+ CanonicalAttrVal: false,
+ UseCRLF: false,
+ }
+}
+
+// A Token is an empty interface that represents an Element, CharData,
+// Comment, Directive, or ProcInst.
+type Token interface {
+ Parent() *Element
+ Index() int
+ dup(parent *Element) Token
+ setParent(parent *Element)
+ setIndex(index int)
+ writeTo(w *bufio.Writer, s *WriteSettings)
+}
+
+// A Document is a container holding a complete XML hierarchy. Its embedded
+// element contains zero or more children, one of which is usually the root
+// element. The embedded element may include other children such as
+// processing instructions or BOM CharData tokens.
+type Document struct {
+ Element
+ ReadSettings ReadSettings
+ WriteSettings WriteSettings
+}
+
+// An Element represents an XML element, its attributes, and its child tokens.
+type Element struct {
+ Space, Tag string // namespace prefix and tag
+ Attr []Attr // key-value attribute pairs
+ Child []Token // child tokens (elements, comments, etc.)
+ parent *Element // parent element
+ index int // token index in parent's children
+}
+
+// An Attr represents a key-value attribute of an XML element.
+type Attr struct {
+ Space, Key string // The attribute's namespace prefix and key
+ Value string // The attribute value string
+ element *Element // element containing the attribute
+}
+
+// charDataFlags are used with CharData tokens to store additional settings.
+type charDataFlags uint8
+
+const (
+ // The CharData was created by an indent function as whitespace.
+ whitespaceFlag charDataFlags = 1 << iota
+
+ // The CharData contains a CDATA section.
+ cdataFlag
+)
+
+// CharData can be used to represent character data or a CDATA section within
+// an XML document.
+type CharData struct {
+ Data string
+ parent *Element
+ index int
+ flags charDataFlags
+}
+
+// A Comment represents an XML comment.
+type Comment struct {
+ Data string
+ parent *Element
+ index int
+}
+
+// A Directive represents an XML directive.
+type Directive struct {
+ Data string
+ parent *Element
+ index int
+}
+
+// A ProcInst represents an XML processing instruction.
+type ProcInst struct {
+ Target string
+ Inst string
+ parent *Element
+ index int
+}
+
+// NewDocument creates an XML document without a root element.
+func NewDocument() *Document {
+ return &Document{
+ Element{Child: make([]Token, 0)},
+ newReadSettings(),
+ newWriteSettings(),
+ }
+}
+
+// Copy returns a recursive, deep copy of the document.
+func (d *Document) Copy() *Document {
+ return &Document{*(d.dup(nil).(*Element)), d.ReadSettings, d.WriteSettings}
+}
+
+// Root returns the root element of the document, or nil if there is no root
+// element.
+func (d *Document) Root() *Element {
+ for _, t := range d.Child {
+ if c, ok := t.(*Element); ok {
+ return c
+ }
+ }
+ return nil
+}
+
+// SetRoot replaces the document's root element with e. If the document
+// already has a root when this function is called, then the document's
+// original root is unbound first. If the element e is bound to another
+// document (or to another element within a document), then it is unbound
+// first.
+func (d *Document) SetRoot(e *Element) {
+ if e.parent != nil {
+ e.parent.RemoveChild(e)
+ }
+
+ p := &d.Element
+ e.setParent(p)
+
+ // If there is already a root element, replace it.
+ for i, t := range p.Child {
+ if _, ok := t.(*Element); ok {
+ t.setParent(nil)
+ t.setIndex(-1)
+ p.Child[i] = e
+ e.setIndex(i)
+ return
+ }
+ }
+
+ // No existing root element, so add it.
+ p.addChild(e)
+}
+
+// ReadFrom reads XML from the reader r into the document d. It returns the
+// number of bytes read and any error encountered.
+func (d *Document) ReadFrom(r io.Reader) (n int64, err error) {
+ return d.Element.readFrom(r, d.ReadSettings)
+}
+
+// ReadFromFile reads XML from the string s into the document d.
+func (d *Document) ReadFromFile(filename string) error {
+ f, err := os.Open(filename)
+ if err != nil {
+ return err
+ }
+ defer f.Close()
+ _, err = d.ReadFrom(f)
+ return err
+}
+
+// ReadFromBytes reads XML from the byte slice b into the document d.
+func (d *Document) ReadFromBytes(b []byte) error {
+ _, err := d.ReadFrom(bytes.NewReader(b))
+ return err
+}
+
+// ReadFromString reads XML from the string s into the document d.
+func (d *Document) ReadFromString(s string) error {
+ _, err := d.ReadFrom(strings.NewReader(s))
+ return err
+}
+
+// WriteTo serializes an XML document into the writer w. It
+// returns the number of bytes written and any error encountered.
+func (d *Document) WriteTo(w io.Writer) (n int64, err error) {
+ cw := newCountWriter(w)
+ b := bufio.NewWriter(cw)
+ for _, c := range d.Child {
+ c.writeTo(b, &d.WriteSettings)
+ }
+ err, n = b.Flush(), cw.bytes
+ return
+}
+
+// WriteToFile serializes an XML document into the file named
+// filename.
+func (d *Document) WriteToFile(filename string) error {
+ f, err := os.Create(filename)
+ if err != nil {
+ return err
+ }
+ defer f.Close()
+ _, err = d.WriteTo(f)
+ return err
+}
+
+// WriteToBytes serializes the XML document into a slice of
+// bytes.
+func (d *Document) WriteToBytes() (b []byte, err error) {
+ var buf bytes.Buffer
+ if _, err = d.WriteTo(&buf); err != nil {
+ return
+ }
+ return buf.Bytes(), nil
+}
+
+// WriteToString serializes the XML document into a string.
+func (d *Document) WriteToString() (s string, err error) {
+ var b []byte
+ if b, err = d.WriteToBytes(); err != nil {
+ return
+ }
+ return string(b), nil
+}
+
+type indentFunc func(depth int) string
+
+// Indent modifies the document's element tree by inserting character data
+// tokens containing newlines and indentation. The amount of indentation per
+// depth level is given as spaces. Pass etree.NoIndent for spaces if you want
+// no indentation at all.
+func (d *Document) Indent(spaces int) {
+ var indent indentFunc
+ switch {
+ case spaces < 0:
+ indent = func(depth int) string { return "" }
+ case d.WriteSettings.UseCRLF == true:
+ indent = func(depth int) string { return indentCRLF(depth*spaces, indentSpaces) }
+ default:
+ indent = func(depth int) string { return indentLF(depth*spaces, indentSpaces) }
+ }
+ d.Element.indent(0, indent)
+}
+
+// IndentTabs modifies the document's element tree by inserting CharData
+// tokens containing newlines and tabs for indentation. One tab is used per
+// indentation level.
+func (d *Document) IndentTabs() {
+ var indent indentFunc
+ switch d.WriteSettings.UseCRLF {
+ case true:
+ indent = func(depth int) string { return indentCRLF(depth, indentTabs) }
+ default:
+ indent = func(depth int) string { return indentLF(depth, indentTabs) }
+ }
+ d.Element.indent(0, indent)
+}
+
+// NewElement creates an unparented element with the specified tag. The tag
+// may be prefixed by a namespace prefix and a colon.
+func NewElement(tag string) *Element {
+ space, stag := spaceDecompose(tag)
+ return newElement(space, stag, nil)
+}
+
+// newElement is a helper function that creates an element and binds it to
+// a parent element if possible.
+func newElement(space, tag string, parent *Element) *Element {
+ e := &Element{
+ Space: space,
+ Tag: tag,
+ Attr: make([]Attr, 0),
+ Child: make([]Token, 0),
+ parent: parent,
+ index: -1,
+ }
+ if parent != nil {
+ parent.addChild(e)
+ }
+ return e
+}
+
+// Copy creates a recursive, deep copy of the element and all its attributes
+// and children. The returned element has no parent but can be parented to a
+// another element using AddElement, or to a document using SetRoot.
+func (e *Element) Copy() *Element {
+ return e.dup(nil).(*Element)
+}
+
+// FullTag returns the element e's complete tag, including namespace prefix if
+// present.
+func (e *Element) FullTag() string {
+ if e.Space == "" {
+ return e.Tag
+ }
+ return e.Space + ":" + e.Tag
+}
+
+// NamespaceURI returns the XML namespace URI associated with the element. If
+// the element is part of the XML default namespace, NamespaceURI returns the
+// empty string.
+func (e *Element) NamespaceURI() string {
+ if e.Space == "" {
+ return e.findDefaultNamespaceURI()
+ }
+ return e.findLocalNamespaceURI(e.Space)
+}
+
+// findLocalNamespaceURI finds the namespace URI corresponding to the
+// requested prefix.
+func (e *Element) findLocalNamespaceURI(prefix string) string {
+ for _, a := range e.Attr {
+ if a.Space == "xmlns" && a.Key == prefix {
+ return a.Value
+ }
+ }
+
+ if e.parent == nil {
+ return ""
+ }
+
+ return e.parent.findLocalNamespaceURI(prefix)
+}
+
+// findDefaultNamespaceURI finds the default namespace URI of the element.
+func (e *Element) findDefaultNamespaceURI() string {
+ for _, a := range e.Attr {
+ if a.Space == "" && a.Key == "xmlns" {
+ return a.Value
+ }
+ }
+
+ if e.parent == nil {
+ return ""
+ }
+
+ return e.parent.findDefaultNamespaceURI()
+}
+
+// hasText returns true if the element has character data immediately
+// folllowing the element's opening tag.
+func (e *Element) hasText() bool {
+ if len(e.Child) == 0 {
+ return false
+ }
+ _, ok := e.Child[0].(*CharData)
+ return ok
+}
+
+// namespacePrefix returns the namespace prefix associated with the element.
+func (e *Element) namespacePrefix() string {
+ return e.Space
+}
+
+// name returns the tag associated with the element.
+func (e *Element) name() string {
+ return e.Tag
+}
+
+// Text returns all character data immediately following the element's opening
+// tag.
+func (e *Element) Text() string {
+ if len(e.Child) == 0 {
+ return ""
+ }
+
+ text := ""
+ for _, ch := range e.Child {
+ if cd, ok := ch.(*CharData); ok {
+ if text == "" {
+ text = cd.Data
+ } else {
+ text = text + cd.Data
+ }
+ } else {
+ break
+ }
+ }
+ return text
+}
+
+// SetText replaces all character data immediately following an element's
+// opening tag with the requested string.
+func (e *Element) SetText(text string) {
+ e.replaceText(0, text, 0)
+}
+
+// SetCData replaces all character data immediately following an element's
+// opening tag with a CDATA section.
+func (e *Element) SetCData(text string) {
+ e.replaceText(0, text, cdataFlag)
+}
+
+// Tail returns all character data immediately following the element's end
+// tag.
+func (e *Element) Tail() string {
+ if e.Parent() == nil {
+ return ""
+ }
+
+ p := e.Parent()
+ i := e.Index()
+
+ text := ""
+ for _, ch := range p.Child[i+1:] {
+ if cd, ok := ch.(*CharData); ok {
+ if text == "" {
+ text = cd.Data
+ } else {
+ text = text + cd.Data
+ }
+ } else {
+ break
+ }
+ }
+ return text
+}
+
+// SetTail replaces all character data immediately following the element's end
+// tag with the requested string.
+func (e *Element) SetTail(text string) {
+ if e.Parent() == nil {
+ return
+ }
+
+ p := e.Parent()
+ p.replaceText(e.Index()+1, text, 0)
+}
+
+// replaceText is a helper function that replaces a series of chardata tokens
+// starting at index i with the requested text.
+func (e *Element) replaceText(i int, text string, flags charDataFlags) {
+ end := e.findTermCharDataIndex(i)
+
+ switch {
+ case end == i:
+ if text != "" {
+ // insert a new chardata token at index i
+ cd := newCharData(text, flags, nil)
+ e.InsertChildAt(i, cd)
+ }
+
+ case end == i+1:
+ if text == "" {
+ // remove the chardata token at index i
+ e.RemoveChildAt(i)
+ } else {
+ // replace the first and only character token at index i
+ cd := e.Child[i].(*CharData)
+ cd.Data, cd.flags = text, flags
+ }
+
+ default:
+ if text == "" {
+ // remove all chardata tokens starting from index i
+ copy(e.Child[i:], e.Child[end:])
+ removed := end - i
+ e.Child = e.Child[:len(e.Child)-removed]
+ for j := i; j < len(e.Child); j++ {
+ e.Child[j].setIndex(j)
+ }
+ } else {
+ // replace the first chardata token at index i and remove all
+ // subsequent chardata tokens
+ cd := e.Child[i].(*CharData)
+ cd.Data, cd.flags = text, flags
+ copy(e.Child[i+1:], e.Child[end:])
+ removed := end - (i + 1)
+ e.Child = e.Child[:len(e.Child)-removed]
+ for j := i + 1; j < len(e.Child); j++ {
+ e.Child[j].setIndex(j)
+ }
+ }
+ }
+}
+
+// findTermCharDataIndex finds the index of the first child token that isn't
+// a CharData token. It starts from the requested start index.
+func (e *Element) findTermCharDataIndex(start int) int {
+ for i := start; i < len(e.Child); i++ {
+ if _, ok := e.Child[i].(*CharData); !ok {
+ return i
+ }
+ }
+ return len(e.Child)
+}
+
+// CreateElement creates an element with the specified tag and adds it as the
+// last child element of the element e. The tag may be prefixed by a namespace
+// prefix and a colon.
+func (e *Element) CreateElement(tag string) *Element {
+ space, stag := spaceDecompose(tag)
+ return newElement(space, stag, e)
+}
+
+// AddChild adds the token t as the last child of element e. If token t was
+// already the child of another element, it is first removed from its current
+// parent element.
+func (e *Element) AddChild(t Token) {
+ if t.Parent() != nil {
+ t.Parent().RemoveChild(t)
+ }
+
+ t.setParent(e)
+ e.addChild(t)
+}
+
+// InsertChild inserts the token t before e's existing child token ex. If ex
+// is nil or ex is not a child of e, then t is added to the end of e's child
+// token list. If token t was already the child of another element, it is
+// first removed from its current parent element.
+//
+// Deprecated: InsertChild is deprecated. Use InsertChildAt instead.
+func (e *Element) InsertChild(ex Token, t Token) {
+ if ex == nil || ex.Parent() != e {
+ e.AddChild(t)
+ return
+ }
+
+ if t.Parent() != nil {
+ t.Parent().RemoveChild(t)
+ }
+
+ t.setParent(e)
+
+ i := ex.Index()
+ e.Child = append(e.Child, nil)
+ copy(e.Child[i+1:], e.Child[i:])
+ e.Child[i] = t
+
+ for j := i; j < len(e.Child); j++ {
+ e.Child[j].setIndex(j)
+ }
+}
+
+// InsertChildAt inserts the token t into the element e's list of child tokens
+// just before the requested index. If the index is greater than or equal to
+// the length of the list of child tokens, the token t is added to the end of
+// the list.
+func (e *Element) InsertChildAt(index int, t Token) {
+ if index >= len(e.Child) {
+ e.AddChild(t)
+ return
+ }
+
+ if t.Parent() != nil {
+ if t.Parent() == e && t.Index() > index {
+ index--
+ }
+ t.Parent().RemoveChild(t)
+ }
+
+ t.setParent(e)
+
+ e.Child = append(e.Child, nil)
+ copy(e.Child[index+1:], e.Child[index:])
+ e.Child[index] = t
+
+ for j := index; j < len(e.Child); j++ {
+ e.Child[j].setIndex(j)
+ }
+}
+
+// RemoveChild attempts to remove the token t from element e's list of
+// children. If the token t is a child of e, then it is returned. Otherwise,
+// nil is returned.
+func (e *Element) RemoveChild(t Token) Token {
+ if t.Parent() != e {
+ return nil
+ }
+ return e.RemoveChildAt(t.Index())
+}
+
+// RemoveChildAt removes the index-th child token from the element e. The
+// removed child token is returned. If the index is out of bounds, no child is
+// removed and nil is returned.
+func (e *Element) RemoveChildAt(index int) Token {
+ if index >= len(e.Child) {
+ return nil
+ }
+
+ t := e.Child[index]
+ for j := index + 1; j < len(e.Child); j++ {
+ e.Child[j].setIndex(j - 1)
+ }
+ e.Child = append(e.Child[:index], e.Child[index+1:]...)
+ t.setIndex(-1)
+ t.setParent(nil)
+ return t
+}
+
+// ReadFrom reads XML from the reader r and stores the result as a new child
+// of element e.
+func (e *Element) readFrom(ri io.Reader, settings ReadSettings) (n int64, err error) {
+ r := newCountReader(ri)
+ dec := xml.NewDecoder(r)
+ dec.CharsetReader = settings.CharsetReader
+ dec.Strict = !settings.Permissive
+ dec.Entity = settings.Entity
+ var stack stack
+ stack.push(e)
+ for {
+ t, err := dec.RawToken()
+ switch {
+ case err == io.EOF:
+ return r.bytes, nil
+ case err != nil:
+ return r.bytes, err
+ case stack.empty():
+ return r.bytes, ErrXML
+ }
+
+ top := stack.peek().(*Element)
+
+ switch t := t.(type) {
+ case xml.StartElement:
+ e := newElement(t.Name.Space, t.Name.Local, top)
+ for _, a := range t.Attr {
+ e.createAttr(a.Name.Space, a.Name.Local, a.Value, e)
+ }
+ stack.push(e)
+ case xml.EndElement:
+ stack.pop()
+ case xml.CharData:
+ data := string(t)
+ var flags charDataFlags
+ if isWhitespace(data) {
+ flags = whitespaceFlag
+ }
+ newCharData(data, flags, top)
+ case xml.Comment:
+ newComment(string(t), top)
+ case xml.Directive:
+ newDirective(string(t), top)
+ case xml.ProcInst:
+ newProcInst(t.Target, string(t.Inst), top)
+ }
+ }
+}
+
+// SelectAttr finds an element attribute matching the requested key and
+// returns it if found. Returns nil if no matching attribute is found. The key
+// may be prefixed by a namespace prefix and a colon.
+func (e *Element) SelectAttr(key string) *Attr {
+ space, skey := spaceDecompose(key)
+ for i, a := range e.Attr {
+ if spaceMatch(space, a.Space) && skey == a.Key {
+ return &e.Attr[i]
+ }
+ }
+ return nil
+}
+
+// SelectAttrValue finds an element attribute matching the requested key and
+// returns its value if found. The key may be prefixed by a namespace prefix
+// and a colon. If the key is not found, the dflt value is returned instead.
+func (e *Element) SelectAttrValue(key, dflt string) string {
+ space, skey := spaceDecompose(key)
+ for _, a := range e.Attr {
+ if spaceMatch(space, a.Space) && skey == a.Key {
+ return a.Value
+ }
+ }
+ return dflt
+}
+
+// ChildElements returns all elements that are children of element e.
+func (e *Element) ChildElements() []*Element {
+ var elements []*Element
+ for _, t := range e.Child {
+ if c, ok := t.(*Element); ok {
+ elements = append(elements, c)
+ }
+ }
+ return elements
+}
+
+// SelectElement returns the first child element with the given tag. The tag
+// may be prefixed by a namespace prefix and a colon. Returns nil if no
+// element with a matching tag was found.
+func (e *Element) SelectElement(tag string) *Element {
+ space, stag := spaceDecompose(tag)
+ for _, t := range e.Child {
+ if c, ok := t.(*Element); ok && spaceMatch(space, c.Space) && stag == c.Tag {
+ return c
+ }
+ }
+ return nil
+}
+
+// SelectElements returns a slice of all child elements with the given tag.
+// The tag may be prefixed by a namespace prefix and a colon.
+func (e *Element) SelectElements(tag string) []*Element {
+ space, stag := spaceDecompose(tag)
+ var elements []*Element
+ for _, t := range e.Child {
+ if c, ok := t.(*Element); ok && spaceMatch(space, c.Space) && stag == c.Tag {
+ elements = append(elements, c)
+ }
+ }
+ return elements
+}
+
+// FindElement returns the first element matched by the XPath-like path
+// string. Returns nil if no element is found using the path. Panics if an
+// invalid path string is supplied.
+func (e *Element) FindElement(path string) *Element {
+ return e.FindElementPath(MustCompilePath(path))
+}
+
+// FindElementPath returns the first element matched by the XPath-like path
+// string. Returns nil if no element is found using the path.
+func (e *Element) FindElementPath(path Path) *Element {
+ p := newPather()
+ elements := p.traverse(e, path)
+ switch {
+ case len(elements) > 0:
+ return elements[0]
+ default:
+ return nil
+ }
+}
+
+// FindElements returns a slice of elements matched by the XPath-like path
+// string. Panics if an invalid path string is supplied.
+func (e *Element) FindElements(path string) []*Element {
+ return e.FindElementsPath(MustCompilePath(path))
+}
+
+// FindElementsPath returns a slice of elements matched by the Path object.
+func (e *Element) FindElementsPath(path Path) []*Element {
+ p := newPather()
+ return p.traverse(e, path)
+}
+
+// GetPath returns the absolute path of the element.
+func (e *Element) GetPath() string {
+ path := []string{}
+ for seg := e; seg != nil; seg = seg.Parent() {
+ if seg.Tag != "" {
+ path = append(path, seg.Tag)
+ }
+ }
+
+ // Reverse the path.
+ for i, j := 0, len(path)-1; i < j; i, j = i+1, j-1 {
+ path[i], path[j] = path[j], path[i]
+ }
+
+ return "/" + strings.Join(path, "/")
+}
+
+// GetRelativePath returns the path of the element relative to the source
+// element. If the two elements are not part of the same element tree, then
+// GetRelativePath returns the empty string.
+func (e *Element) GetRelativePath(source *Element) string {
+ var path []*Element
+
+ if source == nil {
+ return ""
+ }
+
+ // Build a reverse path from the element toward the root. Stop if the
+ // source element is encountered.
+ var seg *Element
+ for seg = e; seg != nil && seg != source; seg = seg.Parent() {
+ path = append(path, seg)
+ }
+
+ // If we found the source element, reverse the path and compose the
+ // string.
+ if seg == source {
+ if len(path) == 0 {
+ return "."
+ }
+ parts := []string{}
+ for i := len(path) - 1; i >= 0; i-- {
+ parts = append(parts, path[i].Tag)
+ }
+ return "./" + strings.Join(parts, "/")
+ }
+
+ // The source wasn't encountered, so climb from the source element toward
+ // the root of the tree until an element in the reversed path is
+ // encountered.
+
+ findPathIndex := func(e *Element, path []*Element) int {
+ for i, ee := range path {
+ if e == ee {
+ return i
+ }
+ }
+ return -1
+ }
+
+ climb := 0
+ for seg = source; seg != nil; seg = seg.Parent() {
+ i := findPathIndex(seg, path)
+ if i >= 0 {
+ path = path[:i] // truncate at found segment
+ break
+ }
+ climb++
+ }
+
+ // No element in the reversed path was encountered, so the two elements
+ // must not be part of the same tree.
+ if seg == nil {
+ return ""
+ }
+
+ // Reverse the (possibly truncated) path and prepend ".." segments to
+ // climb.
+ parts := []string{}
+ for i := 0; i < climb; i++ {
+ parts = append(parts, "..")
+ }
+ for i := len(path) - 1; i >= 0; i-- {
+ parts = append(parts, path[i].Tag)
+ }
+ return strings.Join(parts, "/")
+}
+
+// indent recursively inserts proper indentation between an
+// XML element's child tokens.
+func (e *Element) indent(depth int, indent indentFunc) {
+ e.stripIndent()
+ n := len(e.Child)
+ if n == 0 {
+ return
+ }
+
+ oldChild := e.Child
+ e.Child = make([]Token, 0, n*2+1)
+ isCharData, firstNonCharData := false, true
+ for _, c := range oldChild {
+ // Insert NL+indent before child if it's not character data.
+ // Exceptions: when it's the first non-character-data child, or when
+ // the child is at root depth.
+ _, isCharData = c.(*CharData)
+ if !isCharData {
+ if !firstNonCharData || depth > 0 {
+ s := indent(depth)
+ if s != "" {
+ newCharData(s, whitespaceFlag, e)
+ }
+ }
+ firstNonCharData = false
+ }
+
+ e.addChild(c)
+
+ // Recursively process child elements.
+ if ce, ok := c.(*Element); ok {
+ ce.indent(depth+1, indent)
+ }
+ }
+
+ // Insert NL+indent before the last child.
+ if !isCharData {
+ if !firstNonCharData || depth > 0 {
+ s := indent(depth - 1)
+ if s != "" {
+ newCharData(s, whitespaceFlag, e)
+ }
+ }
+ }
+}
+
+// stripIndent removes any previously inserted indentation.
+func (e *Element) stripIndent() {
+ // Count the number of non-indent child tokens
+ n := len(e.Child)
+ for _, c := range e.Child {
+ if cd, ok := c.(*CharData); ok && cd.IsWhitespace() {
+ n--
+ }
+ }
+ if n == len(e.Child) {
+ return
+ }
+
+ // Strip out indent CharData
+ newChild := make([]Token, n)
+ j := 0
+ for _, c := range e.Child {
+ if cd, ok := c.(*CharData); ok && cd.IsWhitespace() {
+ continue
+ }
+ newChild[j] = c
+ newChild[j].setIndex(j)
+ j++
+ }
+ e.Child = newChild
+}
+
+// dup duplicates the element.
+func (e *Element) dup(parent *Element) Token {
+ ne := &Element{
+ Space: e.Space,
+ Tag: e.Tag,
+ Attr: make([]Attr, len(e.Attr)),
+ Child: make([]Token, len(e.Child)),
+ parent: parent,
+ index: e.index,
+ }
+ for i, t := range e.Child {
+ ne.Child[i] = t.dup(ne)
+ }
+ for i, a := range e.Attr {
+ ne.Attr[i] = a
+ }
+ return ne
+}
+
+// Parent returns the element token's parent element, or nil if it has no
+// parent.
+func (e *Element) Parent() *Element {
+ return e.parent
+}
+
+// Index returns the index of this element within its parent element's
+// list of child tokens. If this element has no parent element, the index
+// is -1.
+func (e *Element) Index() int {
+ return e.index
+}
+
+// setParent replaces the element token's parent.
+func (e *Element) setParent(parent *Element) {
+ e.parent = parent
+}
+
+// setIndex sets the element token's index within its parent's Child slice.
+func (e *Element) setIndex(index int) {
+ e.index = index
+}
+
+// writeTo serializes the element to the writer w.
+func (e *Element) writeTo(w *bufio.Writer, s *WriteSettings) {
+ w.WriteByte('<')
+ w.WriteString(e.FullTag())
+ for _, a := range e.Attr {
+ w.WriteByte(' ')
+ a.writeTo(w, s)
+ }
+ if len(e.Child) > 0 {
+ w.WriteString(">")
+ for _, c := range e.Child {
+ c.writeTo(w, s)
+ }
+ w.Write([]byte{'<', '/'})
+ w.WriteString(e.FullTag())
+ w.WriteByte('>')
+ } else {
+ if s.CanonicalEndTags {
+ w.Write([]byte{'>', '<', '/'})
+ w.WriteString(e.FullTag())
+ w.WriteByte('>')
+ } else {
+ w.Write([]byte{'/', '>'})
+ }
+ }
+}
+
+// addChild adds a child token to the element e.
+func (e *Element) addChild(t Token) {
+ t.setIndex(len(e.Child))
+ e.Child = append(e.Child, t)
+}
+
+// CreateAttr creates an attribute and adds it to element e. The key may be
+// prefixed by a namespace prefix and a colon. If an attribute with the key
+// already exists, its value is replaced.
+func (e *Element) CreateAttr(key, value string) *Attr {
+ space, skey := spaceDecompose(key)
+ return e.createAttr(space, skey, value, e)
+}
+
+// createAttr is a helper function that creates attributes.
+func (e *Element) createAttr(space, key, value string, parent *Element) *Attr {
+ for i, a := range e.Attr {
+ if space == a.Space && key == a.Key {
+ e.Attr[i].Value = value
+ return &e.Attr[i]
+ }
+ }
+ a := Attr{
+ Space: space,
+ Key: key,
+ Value: value,
+ element: parent,
+ }
+ e.Attr = append(e.Attr, a)
+ return &e.Attr[len(e.Attr)-1]
+}
+
+// RemoveAttr removes and returns a copy of the first attribute of the element
+// whose key matches the given key. The key may be prefixed by a namespace
+// prefix and a colon. If a matching attribute does not exist, nil is
+// returned.
+func (e *Element) RemoveAttr(key string) *Attr {
+ space, skey := spaceDecompose(key)
+ for i, a := range e.Attr {
+ if space == a.Space && skey == a.Key {
+ e.Attr = append(e.Attr[0:i], e.Attr[i+1:]...)
+ return &Attr{
+ Space: a.Space,
+ Key: a.Key,
+ Value: a.Value,
+ element: nil,
+ }
+ }
+ }
+ return nil
+}
+
+// SortAttrs sorts the element's attributes lexicographically by key.
+func (e *Element) SortAttrs() {
+ sort.Sort(byAttr(e.Attr))
+}
+
+type byAttr []Attr
+
+func (a byAttr) Len() int {
+ return len(a)
+}
+
+func (a byAttr) Swap(i, j int) {
+ a[i], a[j] = a[j], a[i]
+}
+
+func (a byAttr) Less(i, j int) bool {
+ sp := strings.Compare(a[i].Space, a[j].Space)
+ if sp == 0 {
+ return strings.Compare(a[i].Key, a[j].Key) < 0
+ }
+ return sp < 0
+}
+
+// FullKey returns the attribute a's complete key, including namespace prefix
+// if present.
+func (a *Attr) FullKey() string {
+ if a.Space == "" {
+ return a.Key
+ }
+ return a.Space + ":" + a.Key
+}
+
+// Element returns the element containing the attribute.
+func (a *Attr) Element() *Element {
+ return a.element
+}
+
+// NamespaceURI returns the XML namespace URI associated with the attribute.
+// If the element is part of the XML default namespace, NamespaceURI returns
+// the empty string.
+func (a *Attr) NamespaceURI() string {
+ return a.element.NamespaceURI()
+}
+
+// writeTo serializes the attribute to the writer.
+func (a *Attr) writeTo(w *bufio.Writer, s *WriteSettings) {
+ w.WriteString(a.FullKey())
+ w.WriteString(`="`)
+ var m escapeMode
+ if s.CanonicalAttrVal {
+ m = escapeCanonicalAttr
+ } else {
+ m = escapeNormal
+ }
+ escapeString(w, a.Value, m)
+ w.WriteByte('"')
+}
+
+// NewText creates a parentless CharData token containing character data.
+func NewText(text string) *CharData {
+ return newCharData(text, 0, nil)
+}
+
+// NewCData creates a parentless XML character CDATA section.
+func NewCData(data string) *CharData {
+ return newCharData(data, cdataFlag, nil)
+}
+
+// NewCharData creates a parentless CharData token containing character data.
+//
+// Deprecated: NewCharData is deprecated. Instead, use NewText, which does the
+// same thing.
+func NewCharData(data string) *CharData {
+ return newCharData(data, 0, nil)
+}
+
+// newCharData creates a character data token and binds it to a parent
+// element. If parent is nil, the CharData token remains unbound.
+func newCharData(data string, flags charDataFlags, parent *Element) *CharData {
+ c := &CharData{
+ Data: data,
+ parent: parent,
+ index: -1,
+ flags: flags,
+ }
+ if parent != nil {
+ parent.addChild(c)
+ }
+ return c
+}
+
+// CreateText creates a CharData token containing character data and adds it
+// as a child of element e.
+func (e *Element) CreateText(text string) *CharData {
+ return newCharData(text, 0, e)
+}
+
+// CreateCData creates a CharData token containing a CDATA section and adds it
+// as a child of element e.
+func (e *Element) CreateCData(data string) *CharData {
+ return newCharData(data, cdataFlag, e)
+}
+
+// CreateCharData creates a CharData token containing character data and adds
+// it as a child of element e.
+//
+// Deprecated: CreateCharData is deprecated. Instead, use CreateText, which
+// does the same thing.
+func (e *Element) CreateCharData(data string) *CharData {
+ return newCharData(data, 0, e)
+}
+
+// dup duplicates the character data.
+func (c *CharData) dup(parent *Element) Token {
+ return &CharData{
+ Data: c.Data,
+ flags: c.flags,
+ parent: parent,
+ index: c.index,
+ }
+}
+
+// IsCData returns true if the character data token is to be encoded as a
+// CDATA section.
+func (c *CharData) IsCData() bool {
+ return (c.flags & cdataFlag) != 0
+}
+
+// IsWhitespace returns true if the character data token was created by one of
+// the document Indent methods to contain only whitespace.
+func (c *CharData) IsWhitespace() bool {
+ return (c.flags & whitespaceFlag) != 0
+}
+
+// Parent returns the character data token's parent element, or nil if it has
+// no parent.
+func (c *CharData) Parent() *Element {
+ return c.parent
+}
+
+// Index returns the index of this CharData token within its parent element's
+// list of child tokens. If this CharData token has no parent element, the
+// index is -1.
+func (c *CharData) Index() int {
+ return c.index
+}
+
+// setParent replaces the character data token's parent.
+func (c *CharData) setParent(parent *Element) {
+ c.parent = parent
+}
+
+// setIndex sets the CharData token's index within its parent element's Child
+// slice.
+func (c *CharData) setIndex(index int) {
+ c.index = index
+}
+
+// writeTo serializes character data to the writer.
+func (c *CharData) writeTo(w *bufio.Writer, s *WriteSettings) {
+ if c.IsCData() {
+ w.WriteString(``)
+ } else {
+ var m escapeMode
+ if s.CanonicalText {
+ m = escapeCanonicalText
+ } else {
+ m = escapeNormal
+ }
+ escapeString(w, c.Data, m)
+ }
+}
+
+// NewComment creates a parentless XML comment.
+func NewComment(comment string) *Comment {
+ return newComment(comment, nil)
+}
+
+// NewComment creates an XML comment and binds it to a parent element. If
+// parent is nil, the Comment remains unbound.
+func newComment(comment string, parent *Element) *Comment {
+ c := &Comment{
+ Data: comment,
+ parent: parent,
+ index: -1,
+ }
+ if parent != nil {
+ parent.addChild(c)
+ }
+ return c
+}
+
+// CreateComment creates an XML comment and adds it as a child of element e.
+func (e *Element) CreateComment(comment string) *Comment {
+ return newComment(comment, e)
+}
+
+// dup duplicates the comment.
+func (c *Comment) dup(parent *Element) Token {
+ return &Comment{
+ Data: c.Data,
+ parent: parent,
+ index: c.index,
+ }
+}
+
+// Parent returns comment token's parent element, or nil if it has no parent.
+func (c *Comment) Parent() *Element {
+ return c.parent
+}
+
+// Index returns the index of this Comment token within its parent element's
+// list of child tokens. If this Comment token has no parent element, the
+// index is -1.
+func (c *Comment) Index() int {
+ return c.index
+}
+
+// setParent replaces the comment token's parent.
+func (c *Comment) setParent(parent *Element) {
+ c.parent = parent
+}
+
+// setIndex sets the Comment token's index within its parent element's Child
+// slice.
+func (c *Comment) setIndex(index int) {
+ c.index = index
+}
+
+// writeTo serialies the comment to the writer.
+func (c *Comment) writeTo(w *bufio.Writer, s *WriteSettings) {
+ w.WriteString("")
+}
+
+// NewDirective creates a parentless XML directive.
+func NewDirective(data string) *Directive {
+ return newDirective(data, nil)
+}
+
+// newDirective creates an XML directive and binds it to a parent element. If
+// parent is nil, the Directive remains unbound.
+func newDirective(data string, parent *Element) *Directive {
+ d := &Directive{
+ Data: data,
+ parent: parent,
+ index: -1,
+ }
+ if parent != nil {
+ parent.addChild(d)
+ }
+ return d
+}
+
+// CreateDirective creates an XML directive and adds it as the last child of
+// element e.
+func (e *Element) CreateDirective(data string) *Directive {
+ return newDirective(data, e)
+}
+
+// dup duplicates the directive.
+func (d *Directive) dup(parent *Element) Token {
+ return &Directive{
+ Data: d.Data,
+ parent: parent,
+ index: d.index,
+ }
+}
+
+// Parent returns directive token's parent element, or nil if it has no
+// parent.
+func (d *Directive) Parent() *Element {
+ return d.parent
+}
+
+// Index returns the index of this Directive token within its parent element's
+// list of child tokens. If this Directive token has no parent element, the
+// index is -1.
+func (d *Directive) Index() int {
+ return d.index
+}
+
+// setParent replaces the directive token's parent.
+func (d *Directive) setParent(parent *Element) {
+ d.parent = parent
+}
+
+// setIndex sets the Directive token's index within its parent element's Child
+// slice.
+func (d *Directive) setIndex(index int) {
+ d.index = index
+}
+
+// writeTo serializes the XML directive to the writer.
+func (d *Directive) writeTo(w *bufio.Writer, s *WriteSettings) {
+ w.WriteString("")
+}
+
+// NewProcInst creates a parentless XML processing instruction.
+func NewProcInst(target, inst string) *ProcInst {
+ return newProcInst(target, inst, nil)
+}
+
+// newProcInst creates an XML processing instruction and binds it to a parent
+// element. If parent is nil, the ProcInst remains unbound.
+func newProcInst(target, inst string, parent *Element) *ProcInst {
+ p := &ProcInst{
+ Target: target,
+ Inst: inst,
+ parent: parent,
+ index: -1,
+ }
+ if parent != nil {
+ parent.addChild(p)
+ }
+ return p
+}
+
+// CreateProcInst creates a processing instruction and adds it as a child of
+// element e.
+func (e *Element) CreateProcInst(target, inst string) *ProcInst {
+ return newProcInst(target, inst, e)
+}
+
+// dup duplicates the procinst.
+func (p *ProcInst) dup(parent *Element) Token {
+ return &ProcInst{
+ Target: p.Target,
+ Inst: p.Inst,
+ parent: parent,
+ index: p.index,
+ }
+}
+
+// Parent returns processing instruction token's parent element, or nil if it
+// has no parent.
+func (p *ProcInst) Parent() *Element {
+ return p.parent
+}
+
+// Index returns the index of this ProcInst token within its parent element's
+// list of child tokens. If this ProcInst token has no parent element, the
+// index is -1.
+func (p *ProcInst) Index() int {
+ return p.index
+}
+
+// setParent replaces the processing instruction token's parent.
+func (p *ProcInst) setParent(parent *Element) {
+ p.parent = parent
+}
+
+// setIndex sets the processing instruction token's index within its parent
+// element's Child slice.
+func (p *ProcInst) setIndex(index int) {
+ p.index = index
+}
+
+// writeTo serializes the processing instruction to the writer.
+func (p *ProcInst) writeTo(w *bufio.Writer, s *WriteSettings) {
+ w.WriteString("")
+ w.WriteString(p.Target)
+ if p.Inst != "" {
+ w.WriteByte(' ')
+ w.WriteString(p.Inst)
+ }
+ w.WriteString("?>")
+}
diff --git a/vendor/github.com/beevik/etree/helpers.go b/vendor/github.com/beevik/etree/helpers.go
new file mode 100644
index 0000000000..825e14e914
--- /dev/null
+++ b/vendor/github.com/beevik/etree/helpers.go
@@ -0,0 +1,276 @@
+// Copyright 2015-2019 Brett Vickers.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package etree
+
+import (
+ "bufio"
+ "io"
+ "strings"
+ "unicode/utf8"
+)
+
+// A simple stack
+type stack struct {
+ data []interface{}
+}
+
+func (s *stack) empty() bool {
+ return len(s.data) == 0
+}
+
+func (s *stack) push(value interface{}) {
+ s.data = append(s.data, value)
+}
+
+func (s *stack) pop() interface{} {
+ value := s.data[len(s.data)-1]
+ s.data[len(s.data)-1] = nil
+ s.data = s.data[:len(s.data)-1]
+ return value
+}
+
+func (s *stack) peek() interface{} {
+ return s.data[len(s.data)-1]
+}
+
+// A fifo is a simple first-in-first-out queue.
+type fifo struct {
+ data []interface{}
+ head, tail int
+}
+
+func (f *fifo) add(value interface{}) {
+ if f.len()+1 >= len(f.data) {
+ f.grow()
+ }
+ f.data[f.tail] = value
+ if f.tail++; f.tail == len(f.data) {
+ f.tail = 0
+ }
+}
+
+func (f *fifo) remove() interface{} {
+ value := f.data[f.head]
+ f.data[f.head] = nil
+ if f.head++; f.head == len(f.data) {
+ f.head = 0
+ }
+ return value
+}
+
+func (f *fifo) len() int {
+ if f.tail >= f.head {
+ return f.tail - f.head
+ }
+ return len(f.data) - f.head + f.tail
+}
+
+func (f *fifo) grow() {
+ c := len(f.data) * 2
+ if c == 0 {
+ c = 4
+ }
+ buf, count := make([]interface{}, c), f.len()
+ if f.tail >= f.head {
+ copy(buf[0:count], f.data[f.head:f.tail])
+ } else {
+ hindex := len(f.data) - f.head
+ copy(buf[0:hindex], f.data[f.head:])
+ copy(buf[hindex:count], f.data[:f.tail])
+ }
+ f.data, f.head, f.tail = buf, 0, count
+}
+
+// countReader implements a proxy reader that counts the number of
+// bytes read from its encapsulated reader.
+type countReader struct {
+ r io.Reader
+ bytes int64
+}
+
+func newCountReader(r io.Reader) *countReader {
+ return &countReader{r: r}
+}
+
+func (cr *countReader) Read(p []byte) (n int, err error) {
+ b, err := cr.r.Read(p)
+ cr.bytes += int64(b)
+ return b, err
+}
+
+// countWriter implements a proxy writer that counts the number of
+// bytes written by its encapsulated writer.
+type countWriter struct {
+ w io.Writer
+ bytes int64
+}
+
+func newCountWriter(w io.Writer) *countWriter {
+ return &countWriter{w: w}
+}
+
+func (cw *countWriter) Write(p []byte) (n int, err error) {
+ b, err := cw.w.Write(p)
+ cw.bytes += int64(b)
+ return b, err
+}
+
+// isWhitespace returns true if the byte slice contains only
+// whitespace characters.
+func isWhitespace(s string) bool {
+ for i := 0; i < len(s); i++ {
+ if c := s[i]; c != ' ' && c != '\t' && c != '\n' && c != '\r' {
+ return false
+ }
+ }
+ return true
+}
+
+// spaceMatch returns true if namespace a is the empty string
+// or if namespace a equals namespace b.
+func spaceMatch(a, b string) bool {
+ switch {
+ case a == "":
+ return true
+ default:
+ return a == b
+ }
+}
+
+// spaceDecompose breaks a namespace:tag identifier at the ':'
+// and returns the two parts.
+func spaceDecompose(str string) (space, key string) {
+ colon := strings.IndexByte(str, ':')
+ if colon == -1 {
+ return "", str
+ }
+ return str[:colon], str[colon+1:]
+}
+
+// Strings used by indentCRLF and indentLF
+const (
+ indentSpaces = "\r\n "
+ indentTabs = "\r\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t"
+)
+
+// indentCRLF returns a CRLF newline followed by n copies of the first
+// non-CRLF character in the source string.
+func indentCRLF(n int, source string) string {
+ switch {
+ case n < 0:
+ return source[:2]
+ case n < len(source)-1:
+ return source[:n+2]
+ default:
+ return source + strings.Repeat(source[2:3], n-len(source)+2)
+ }
+}
+
+// indentLF returns a LF newline followed by n copies of the first non-LF
+// character in the source string.
+func indentLF(n int, source string) string {
+ switch {
+ case n < 0:
+ return source[1:2]
+ case n < len(source)-1:
+ return source[1 : n+2]
+ default:
+ return source[1:] + strings.Repeat(source[2:3], n-len(source)+2)
+ }
+}
+
+// nextIndex returns the index of the next occurrence of sep in s,
+// starting from offset. It returns -1 if the sep string is not found.
+func nextIndex(s, sep string, offset int) int {
+ switch i := strings.Index(s[offset:], sep); i {
+ case -1:
+ return -1
+ default:
+ return offset + i
+ }
+}
+
+// isInteger returns true if the string s contains an integer.
+func isInteger(s string) bool {
+ for i := 0; i < len(s); i++ {
+ if (s[i] < '0' || s[i] > '9') && !(i == 0 && s[i] == '-') {
+ return false
+ }
+ }
+ return true
+}
+
+type escapeMode byte
+
+const (
+ escapeNormal escapeMode = iota
+ escapeCanonicalText
+ escapeCanonicalAttr
+)
+
+// escapeString writes an escaped version of a string to the writer.
+func escapeString(w *bufio.Writer, s string, m escapeMode) {
+ var esc []byte
+ last := 0
+ for i := 0; i < len(s); {
+ r, width := utf8.DecodeRuneInString(s[i:])
+ i += width
+ switch r {
+ case '&':
+ esc = []byte("&")
+ case '<':
+ esc = []byte("<")
+ case '>':
+ if m == escapeCanonicalAttr {
+ continue
+ }
+ esc = []byte(">")
+ case '\'':
+ if m != escapeNormal {
+ continue
+ }
+ esc = []byte("'")
+ case '"':
+ if m == escapeCanonicalText {
+ continue
+ }
+ esc = []byte(""")
+ case '\t':
+ if m != escapeCanonicalAttr {
+ continue
+ }
+ esc = []byte(" ")
+ case '\n':
+ if m != escapeCanonicalAttr {
+ continue
+ }
+ esc = []byte("
")
+ case '\r':
+ if m == escapeNormal {
+ continue
+ }
+ esc = []byte("
")
+ default:
+ if !isInCharacterRange(r) || (r == 0xFFFD && width == 1) {
+ esc = []byte("\uFFFD")
+ break
+ }
+ continue
+ }
+ w.WriteString(s[last : i-width])
+ w.Write(esc)
+ last = i
+ }
+ w.WriteString(s[last:])
+}
+
+func isInCharacterRange(r rune) bool {
+ return r == 0x09 ||
+ r == 0x0A ||
+ r == 0x0D ||
+ r >= 0x20 && r <= 0xD7FF ||
+ r >= 0xE000 && r <= 0xFFFD ||
+ r >= 0x10000 && r <= 0x10FFFF
+}
diff --git a/vendor/github.com/beevik/etree/path.go b/vendor/github.com/beevik/etree/path.go
new file mode 100644
index 0000000000..82db0ac556
--- /dev/null
+++ b/vendor/github.com/beevik/etree/path.go
@@ -0,0 +1,582 @@
+// Copyright 2015-2019 Brett Vickers.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package etree
+
+import (
+ "strconv"
+ "strings"
+)
+
+/*
+A Path is a string that represents a search path through an etree starting
+from the document root or an arbitrary element. Paths are used with the
+Element object's Find* methods to locate and return desired elements.
+
+A Path consists of a series of slash-separated "selectors", each of which may
+be modified by one or more bracket-enclosed "filters". Selectors are used to
+traverse the etree from element to element, while filters are used to narrow
+the list of candidate elements at each node.
+
+Although etree Path strings are similar to XPath strings
+(https://www.w3.org/TR/1999/REC-xpath-19991116/), they have a more limited set
+of selectors and filtering options.
+
+The following selectors are supported by etree Path strings:
+
+ . Select the current element.
+ .. Select the parent of the current element.
+ * Select all child elements of the current element.
+ / Select the root element when used at the start of a path.
+ // Select all descendants of the current element.
+ tag Select all child elements with a name matching the tag.
+
+The following basic filters are supported by etree Path strings:
+
+ [@attrib] Keep elements with an attribute named attrib.
+ [@attrib='val'] Keep elements with an attribute named attrib and value matching val.
+ [tag] Keep elements with a child element named tag.
+ [tag='val'] Keep elements with a child element named tag and text matching val.
+ [n] Keep the n-th element, where n is a numeric index starting from 1.
+
+The following function filters are also supported:
+
+ [text()] Keep elements with non-empty text.
+ [text()='val'] Keep elements whose text matches val.
+ [local-name()='val'] Keep elements whose un-prefixed tag matches val.
+ [name()='val'] Keep elements whose full tag exactly matches val.
+ [namespace-prefix()='val'] Keep elements whose namespace prefix matches val.
+ [namespace-uri()='val'] Keep elements whose namespace URI matches val.
+
+Here are some examples of Path strings:
+
+- Select the bookstore child element of the root element:
+ /bookstore
+
+- Beginning from the root element, select the title elements of all
+descendant book elements having a 'category' attribute of 'WEB':
+ //book[@category='WEB']/title
+
+- Beginning from the current element, select the first descendant
+book element with a title child element containing the text 'Great
+Expectations':
+ .//book[title='Great Expectations'][1]
+
+- Beginning from the current element, select all child elements of
+book elements with an attribute 'language' set to 'english':
+ ./book/*[@language='english']
+
+- Beginning from the current element, select all child elements of
+book elements containing the text 'special':
+ ./book/*[text()='special']
+
+- Beginning from the current element, select all descendant book
+elements whose title child element has a 'language' attribute of 'french':
+ .//book/title[@language='french']/..
+
+- Beginning from the current element, select all book elements
+belonging to the http://www.w3.org/TR/html4/ namespace:
+ .//book[namespace-uri()='http://www.w3.org/TR/html4/']
+
+*/
+type Path struct {
+ segments []segment
+}
+
+// ErrPath is returned by path functions when an invalid etree path is provided.
+type ErrPath string
+
+// Error returns the string describing a path error.
+func (err ErrPath) Error() string {
+ return "etree: " + string(err)
+}
+
+// CompilePath creates an optimized version of an XPath-like string that
+// can be used to query elements in an element tree.
+func CompilePath(path string) (Path, error) {
+ var comp compiler
+ segments := comp.parsePath(path)
+ if comp.err != ErrPath("") {
+ return Path{nil}, comp.err
+ }
+ return Path{segments}, nil
+}
+
+// MustCompilePath creates an optimized version of an XPath-like string that
+// can be used to query elements in an element tree. Panics if an error
+// occurs. Use this function to create Paths when you know the path is
+// valid (i.e., if it's hard-coded).
+func MustCompilePath(path string) Path {
+ p, err := CompilePath(path)
+ if err != nil {
+ panic(err)
+ }
+ return p
+}
+
+// A segment is a portion of a path between "/" characters.
+// It contains one selector and zero or more [filters].
+type segment struct {
+ sel selector
+ filters []filter
+}
+
+func (seg *segment) apply(e *Element, p *pather) {
+ seg.sel.apply(e, p)
+ for _, f := range seg.filters {
+ f.apply(p)
+ }
+}
+
+// A selector selects XML elements for consideration by the
+// path traversal.
+type selector interface {
+ apply(e *Element, p *pather)
+}
+
+// A filter pares down a list of candidate XML elements based
+// on a path filter in [brackets].
+type filter interface {
+ apply(p *pather)
+}
+
+// A pather is helper object that traverses an element tree using
+// a Path object. It collects and deduplicates all elements matching
+// the path query.
+type pather struct {
+ queue fifo
+ results []*Element
+ inResults map[*Element]bool
+ candidates []*Element
+ scratch []*Element // used by filters
+}
+
+// A node represents an element and the remaining path segments that
+// should be applied against it by the pather.
+type node struct {
+ e *Element
+ segments []segment
+}
+
+func newPather() *pather {
+ return &pather{
+ results: make([]*Element, 0),
+ inResults: make(map[*Element]bool),
+ candidates: make([]*Element, 0),
+ scratch: make([]*Element, 0),
+ }
+}
+
+// traverse follows the path from the element e, collecting
+// and then returning all elements that match the path's selectors
+// and filters.
+func (p *pather) traverse(e *Element, path Path) []*Element {
+ for p.queue.add(node{e, path.segments}); p.queue.len() > 0; {
+ p.eval(p.queue.remove().(node))
+ }
+ return p.results
+}
+
+// eval evalutes the current path node by applying the remaining
+// path's selector rules against the node's element.
+func (p *pather) eval(n node) {
+ p.candidates = p.candidates[0:0]
+ seg, remain := n.segments[0], n.segments[1:]
+ seg.apply(n.e, p)
+
+ if len(remain) == 0 {
+ for _, c := range p.candidates {
+ if in := p.inResults[c]; !in {
+ p.inResults[c] = true
+ p.results = append(p.results, c)
+ }
+ }
+ } else {
+ for _, c := range p.candidates {
+ p.queue.add(node{c, remain})
+ }
+ }
+}
+
+// A compiler generates a compiled path from a path string.
+type compiler struct {
+ err ErrPath
+}
+
+// parsePath parses an XPath-like string describing a path
+// through an element tree and returns a slice of segment
+// descriptors.
+func (c *compiler) parsePath(path string) []segment {
+ // If path ends with //, fix it
+ if strings.HasSuffix(path, "//") {
+ path = path + "*"
+ }
+
+ var segments []segment
+
+ // Check for an absolute path
+ if strings.HasPrefix(path, "/") {
+ segments = append(segments, segment{new(selectRoot), []filter{}})
+ path = path[1:]
+ }
+
+ // Split path into segments
+ for _, s := range splitPath(path) {
+ segments = append(segments, c.parseSegment(s))
+ if c.err != ErrPath("") {
+ break
+ }
+ }
+ return segments
+}
+
+func splitPath(path string) []string {
+ pieces := make([]string, 0)
+ start := 0
+ inquote := false
+ for i := 0; i+1 <= len(path); i++ {
+ if path[i] == '\'' {
+ inquote = !inquote
+ } else if path[i] == '/' && !inquote {
+ pieces = append(pieces, path[start:i])
+ start = i + 1
+ }
+ }
+ return append(pieces, path[start:])
+}
+
+// parseSegment parses a path segment between / characters.
+func (c *compiler) parseSegment(path string) segment {
+ pieces := strings.Split(path, "[")
+ seg := segment{
+ sel: c.parseSelector(pieces[0]),
+ filters: []filter{},
+ }
+ for i := 1; i < len(pieces); i++ {
+ fpath := pieces[i]
+ if fpath[len(fpath)-1] != ']' {
+ c.err = ErrPath("path has invalid filter [brackets].")
+ break
+ }
+ seg.filters = append(seg.filters, c.parseFilter(fpath[:len(fpath)-1]))
+ }
+ return seg
+}
+
+// parseSelector parses a selector at the start of a path segment.
+func (c *compiler) parseSelector(path string) selector {
+ switch path {
+ case ".":
+ return new(selectSelf)
+ case "..":
+ return new(selectParent)
+ case "*":
+ return new(selectChildren)
+ case "":
+ return new(selectDescendants)
+ default:
+ return newSelectChildrenByTag(path)
+ }
+}
+
+var fnTable = map[string]struct {
+ hasFn func(e *Element) bool
+ getValFn func(e *Element) string
+}{
+ "local-name": {nil, (*Element).name},
+ "name": {nil, (*Element).FullTag},
+ "namespace-prefix": {nil, (*Element).namespacePrefix},
+ "namespace-uri": {nil, (*Element).NamespaceURI},
+ "text": {(*Element).hasText, (*Element).Text},
+}
+
+// parseFilter parses a path filter contained within [brackets].
+func (c *compiler) parseFilter(path string) filter {
+ if len(path) == 0 {
+ c.err = ErrPath("path contains an empty filter expression.")
+ return nil
+ }
+
+ // Filter contains [@attr='val'], [fn()='val'], or [tag='val']?
+ eqindex := strings.Index(path, "='")
+ if eqindex >= 0 {
+ rindex := nextIndex(path, "'", eqindex+2)
+ if rindex != len(path)-1 {
+ c.err = ErrPath("path has mismatched filter quotes.")
+ return nil
+ }
+
+ key := path[:eqindex]
+ value := path[eqindex+2 : rindex]
+
+ switch {
+ case key[0] == '@':
+ return newFilterAttrVal(key[1:], value)
+ case strings.HasSuffix(key, "()"):
+ fn := key[:len(key)-2]
+ if t, ok := fnTable[fn]; ok && t.getValFn != nil {
+ return newFilterFuncVal(t.getValFn, value)
+ }
+ c.err = ErrPath("path has unknown function " + fn)
+ return nil
+ default:
+ return newFilterChildText(key, value)
+ }
+ }
+
+ // Filter contains [@attr], [N], [tag] or [fn()]
+ switch {
+ case path[0] == '@':
+ return newFilterAttr(path[1:])
+ case strings.HasSuffix(path, "()"):
+ fn := path[:len(path)-2]
+ if t, ok := fnTable[fn]; ok && t.hasFn != nil {
+ return newFilterFunc(t.hasFn)
+ }
+ c.err = ErrPath("path has unknown function " + fn)
+ return nil
+ case isInteger(path):
+ pos, _ := strconv.Atoi(path)
+ switch {
+ case pos > 0:
+ return newFilterPos(pos - 1)
+ default:
+ return newFilterPos(pos)
+ }
+ default:
+ return newFilterChild(path)
+ }
+}
+
+// selectSelf selects the current element into the candidate list.
+type selectSelf struct{}
+
+func (s *selectSelf) apply(e *Element, p *pather) {
+ p.candidates = append(p.candidates, e)
+}
+
+// selectRoot selects the element's root node.
+type selectRoot struct{}
+
+func (s *selectRoot) apply(e *Element, p *pather) {
+ root := e
+ for root.parent != nil {
+ root = root.parent
+ }
+ p.candidates = append(p.candidates, root)
+}
+
+// selectParent selects the element's parent into the candidate list.
+type selectParent struct{}
+
+func (s *selectParent) apply(e *Element, p *pather) {
+ if e.parent != nil {
+ p.candidates = append(p.candidates, e.parent)
+ }
+}
+
+// selectChildren selects the element's child elements into the
+// candidate list.
+type selectChildren struct{}
+
+func (s *selectChildren) apply(e *Element, p *pather) {
+ for _, c := range e.Child {
+ if c, ok := c.(*Element); ok {
+ p.candidates = append(p.candidates, c)
+ }
+ }
+}
+
+// selectDescendants selects all descendant child elements
+// of the element into the candidate list.
+type selectDescendants struct{}
+
+func (s *selectDescendants) apply(e *Element, p *pather) {
+ var queue fifo
+ for queue.add(e); queue.len() > 0; {
+ e := queue.remove().(*Element)
+ p.candidates = append(p.candidates, e)
+ for _, c := range e.Child {
+ if c, ok := c.(*Element); ok {
+ queue.add(c)
+ }
+ }
+ }
+}
+
+// selectChildrenByTag selects into the candidate list all child
+// elements of the element having the specified tag.
+type selectChildrenByTag struct {
+ space, tag string
+}
+
+func newSelectChildrenByTag(path string) *selectChildrenByTag {
+ s, l := spaceDecompose(path)
+ return &selectChildrenByTag{s, l}
+}
+
+func (s *selectChildrenByTag) apply(e *Element, p *pather) {
+ for _, c := range e.Child {
+ if c, ok := c.(*Element); ok && spaceMatch(s.space, c.Space) && s.tag == c.Tag {
+ p.candidates = append(p.candidates, c)
+ }
+ }
+}
+
+// filterPos filters the candidate list, keeping only the
+// candidate at the specified index.
+type filterPos struct {
+ index int
+}
+
+func newFilterPos(pos int) *filterPos {
+ return &filterPos{pos}
+}
+
+func (f *filterPos) apply(p *pather) {
+ if f.index >= 0 {
+ if f.index < len(p.candidates) {
+ p.scratch = append(p.scratch, p.candidates[f.index])
+ }
+ } else {
+ if -f.index <= len(p.candidates) {
+ p.scratch = append(p.scratch, p.candidates[len(p.candidates)+f.index])
+ }
+ }
+ p.candidates, p.scratch = p.scratch, p.candidates[0:0]
+}
+
+// filterAttr filters the candidate list for elements having
+// the specified attribute.
+type filterAttr struct {
+ space, key string
+}
+
+func newFilterAttr(str string) *filterAttr {
+ s, l := spaceDecompose(str)
+ return &filterAttr{s, l}
+}
+
+func (f *filterAttr) apply(p *pather) {
+ for _, c := range p.candidates {
+ for _, a := range c.Attr {
+ if spaceMatch(f.space, a.Space) && f.key == a.Key {
+ p.scratch = append(p.scratch, c)
+ break
+ }
+ }
+ }
+ p.candidates, p.scratch = p.scratch, p.candidates[0:0]
+}
+
+// filterAttrVal filters the candidate list for elements having
+// the specified attribute with the specified value.
+type filterAttrVal struct {
+ space, key, val string
+}
+
+func newFilterAttrVal(str, value string) *filterAttrVal {
+ s, l := spaceDecompose(str)
+ return &filterAttrVal{s, l, value}
+}
+
+func (f *filterAttrVal) apply(p *pather) {
+ for _, c := range p.candidates {
+ for _, a := range c.Attr {
+ if spaceMatch(f.space, a.Space) && f.key == a.Key && f.val == a.Value {
+ p.scratch = append(p.scratch, c)
+ break
+ }
+ }
+ }
+ p.candidates, p.scratch = p.scratch, p.candidates[0:0]
+}
+
+// filterFunc filters the candidate list for elements satisfying a custom
+// boolean function.
+type filterFunc struct {
+ fn func(e *Element) bool
+}
+
+func newFilterFunc(fn func(e *Element) bool) *filterFunc {
+ return &filterFunc{fn}
+}
+
+func (f *filterFunc) apply(p *pather) {
+ for _, c := range p.candidates {
+ if f.fn(c) {
+ p.scratch = append(p.scratch, c)
+ }
+ }
+ p.candidates, p.scratch = p.scratch, p.candidates[0:0]
+}
+
+// filterFuncVal filters the candidate list for elements containing a value
+// matching the result of a custom function.
+type filterFuncVal struct {
+ fn func(e *Element) string
+ val string
+}
+
+func newFilterFuncVal(fn func(e *Element) string, value string) *filterFuncVal {
+ return &filterFuncVal{fn, value}
+}
+
+func (f *filterFuncVal) apply(p *pather) {
+ for _, c := range p.candidates {
+ if f.fn(c) == f.val {
+ p.scratch = append(p.scratch, c)
+ }
+ }
+ p.candidates, p.scratch = p.scratch, p.candidates[0:0]
+}
+
+// filterChild filters the candidate list for elements having
+// a child element with the specified tag.
+type filterChild struct {
+ space, tag string
+}
+
+func newFilterChild(str string) *filterChild {
+ s, l := spaceDecompose(str)
+ return &filterChild{s, l}
+}
+
+func (f *filterChild) apply(p *pather) {
+ for _, c := range p.candidates {
+ for _, cc := range c.Child {
+ if cc, ok := cc.(*Element); ok &&
+ spaceMatch(f.space, cc.Space) &&
+ f.tag == cc.Tag {
+ p.scratch = append(p.scratch, c)
+ }
+ }
+ }
+ p.candidates, p.scratch = p.scratch, p.candidates[0:0]
+}
+
+// filterChildText filters the candidate list for elements having
+// a child element with the specified tag and text.
+type filterChildText struct {
+ space, tag, text string
+}
+
+func newFilterChildText(str, text string) *filterChildText {
+ s, l := spaceDecompose(str)
+ return &filterChildText{s, l, text}
+}
+
+func (f *filterChildText) apply(p *pather) {
+ for _, c := range p.candidates {
+ for _, cc := range c.Child {
+ if cc, ok := cc.(*Element); ok &&
+ spaceMatch(f.space, cc.Space) &&
+ f.tag == cc.Tag &&
+ f.text == cc.Text() {
+ p.scratch = append(p.scratch, c)
+ }
+ }
+ }
+ p.candidates, p.scratch = p.scratch, p.candidates[0:0]
+}
diff --git a/vendor/github.com/jonboulle/clockwork/.gitignore b/vendor/github.com/jonboulle/clockwork/.gitignore
new file mode 100644
index 0000000000..010c242bd8
--- /dev/null
+++ b/vendor/github.com/jonboulle/clockwork/.gitignore
@@ -0,0 +1,25 @@
+# Compiled Object files, Static and Dynamic libs (Shared Objects)
+*.o
+*.a
+*.so
+
+# Folders
+_obj
+_test
+
+# Architecture specific extensions/prefixes
+*.[568vq]
+[568vq].out
+
+*.cgo1.go
+*.cgo2.c
+_cgo_defun.c
+_cgo_gotypes.go
+_cgo_export.*
+
+_testmain.go
+
+*.exe
+*.test
+
+*.swp
diff --git a/vendor/github.com/jonboulle/clockwork/.travis.yml b/vendor/github.com/jonboulle/clockwork/.travis.yml
new file mode 100644
index 0000000000..aefda90bfa
--- /dev/null
+++ b/vendor/github.com/jonboulle/clockwork/.travis.yml
@@ -0,0 +1,5 @@
+language: go
+go:
+ - 1.3
+
+sudo: false
diff --git a/vendor/github.com/jonboulle/clockwork/LICENSE b/vendor/github.com/jonboulle/clockwork/LICENSE
new file mode 100644
index 0000000000..5c304d1a4a
--- /dev/null
+++ b/vendor/github.com/jonboulle/clockwork/LICENSE
@@ -0,0 +1,201 @@
+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.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "{}"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright {yyyy} {name of copyright owner}
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
diff --git a/vendor/github.com/jonboulle/clockwork/README.md b/vendor/github.com/jonboulle/clockwork/README.md
new file mode 100644
index 0000000000..d43a6c799a
--- /dev/null
+++ b/vendor/github.com/jonboulle/clockwork/README.md
@@ -0,0 +1,61 @@
+clockwork
+=========
+
+[](https://travis-ci.org/jonboulle/clockwork)
+[](http://godoc.org/github.com/jonboulle/clockwork)
+
+a simple fake clock for golang
+
+# Usage
+
+Replace uses of the `time` package with the `clockwork.Clock` interface instead.
+
+For example, instead of using `time.Sleep` directly:
+
+```
+func my_func() {
+ time.Sleep(3 * time.Second)
+ do_something()
+}
+```
+
+inject a clock and use its `Sleep` method instead:
+
+```
+func my_func(clock clockwork.Clock) {
+ clock.Sleep(3 * time.Second)
+ do_something()
+}
+```
+
+Now you can easily test `my_func` with a `FakeClock`:
+
+```
+func TestMyFunc(t *testing.T) {
+ c := clockwork.NewFakeClock()
+
+ // Start our sleepy function
+ my_func(c)
+
+ // Ensure we wait until my_func is sleeping
+ c.BlockUntil(1)
+
+ assert_state()
+
+ // Advance the FakeClock forward in time
+ c.Advance(3)
+
+ assert_state()
+}
+```
+
+and in production builds, simply inject the real clock instead:
+```
+my_func(clockwork.NewRealClock())
+```
+
+See [example_test.go](example_test.go) for a full example.
+
+# Credits
+
+clockwork is inspired by @wickman's [threaded fake clock](https://gist.github.com/wickman/3840816), and the [Golang playground](http://blog.golang.org/playground#Faking time)
diff --git a/vendor/github.com/jonboulle/clockwork/clockwork.go b/vendor/github.com/jonboulle/clockwork/clockwork.go
new file mode 100644
index 0000000000..9ec96ed296
--- /dev/null
+++ b/vendor/github.com/jonboulle/clockwork/clockwork.go
@@ -0,0 +1,169 @@
+package clockwork
+
+import (
+ "sync"
+ "time"
+)
+
+// Clock provides an interface that packages can use instead of directly
+// using the time module, so that chronology-related behavior can be tested
+type Clock interface {
+ After(d time.Duration) <-chan time.Time
+ Sleep(d time.Duration)
+ Now() time.Time
+}
+
+// FakeClock provides an interface for a clock which can be
+// manually advanced through time
+type FakeClock interface {
+ Clock
+ // Advance advances the FakeClock to a new point in time, ensuring any existing
+ // sleepers are notified appropriately before returning
+ Advance(d time.Duration)
+ // BlockUntil will block until the FakeClock has the given number of
+ // sleepers (callers of Sleep or After)
+ BlockUntil(n int)
+}
+
+// NewRealClock returns a Clock which simply delegates calls to the actual time
+// package; it should be used by packages in production.
+func NewRealClock() Clock {
+ return &realClock{}
+}
+
+// NewFakeClock returns a FakeClock implementation which can be
+// manually advanced through time for testing. The initial time of the
+// FakeClock will be an arbitrary non-zero time.
+func NewFakeClock() FakeClock {
+ // use a fixture that does not fulfill Time.IsZero()
+ return NewFakeClockAt(time.Date(1984, time.April, 4, 0, 0, 0, 0, time.UTC))
+}
+
+// NewFakeClockAt returns a FakeClock initialised at the given time.Time.
+func NewFakeClockAt(t time.Time) FakeClock {
+ return &fakeClock{
+ time: t,
+ }
+}
+
+type realClock struct{}
+
+func (rc *realClock) After(d time.Duration) <-chan time.Time {
+ return time.After(d)
+}
+
+func (rc *realClock) Sleep(d time.Duration) {
+ time.Sleep(d)
+}
+
+func (rc *realClock) Now() time.Time {
+ return time.Now()
+}
+
+type fakeClock struct {
+ sleepers []*sleeper
+ blockers []*blocker
+ time time.Time
+
+ l sync.RWMutex
+}
+
+// sleeper represents a caller of After or Sleep
+type sleeper struct {
+ until time.Time
+ done chan time.Time
+}
+
+// blocker represents a caller of BlockUntil
+type blocker struct {
+ count int
+ ch chan struct{}
+}
+
+// After mimics time.After; it waits for the given duration to elapse on the
+// fakeClock, then sends the current time on the returned channel.
+func (fc *fakeClock) After(d time.Duration) <-chan time.Time {
+ fc.l.Lock()
+ defer fc.l.Unlock()
+ now := fc.time
+ done := make(chan time.Time, 1)
+ if d.Nanoseconds() == 0 {
+ // special case - trigger immediately
+ done <- now
+ } else {
+ // otherwise, add to the set of sleepers
+ s := &sleeper{
+ until: now.Add(d),
+ done: done,
+ }
+ fc.sleepers = append(fc.sleepers, s)
+ // and notify any blockers
+ fc.blockers = notifyBlockers(fc.blockers, len(fc.sleepers))
+ }
+ return done
+}
+
+// notifyBlockers notifies all the blockers waiting until the
+// given number of sleepers are waiting on the fakeClock. It
+// returns an updated slice of blockers (i.e. those still waiting)
+func notifyBlockers(blockers []*blocker, count int) (newBlockers []*blocker) {
+ for _, b := range blockers {
+ if b.count == count {
+ close(b.ch)
+ } else {
+ newBlockers = append(newBlockers, b)
+ }
+ }
+ return
+}
+
+// Sleep blocks until the given duration has passed on the fakeClock
+func (fc *fakeClock) Sleep(d time.Duration) {
+ <-fc.After(d)
+}
+
+// Time returns the current time of the fakeClock
+func (fc *fakeClock) Now() time.Time {
+ fc.l.RLock()
+ t := fc.time
+ fc.l.RUnlock()
+ return t
+}
+
+// Advance advances fakeClock to a new point in time, ensuring channels from any
+// previous invocations of After are notified appropriately before returning
+func (fc *fakeClock) Advance(d time.Duration) {
+ fc.l.Lock()
+ defer fc.l.Unlock()
+ end := fc.time.Add(d)
+ var newSleepers []*sleeper
+ for _, s := range fc.sleepers {
+ if end.Sub(s.until) >= 0 {
+ s.done <- end
+ } else {
+ newSleepers = append(newSleepers, s)
+ }
+ }
+ fc.sleepers = newSleepers
+ fc.blockers = notifyBlockers(fc.blockers, len(fc.sleepers))
+ fc.time = end
+}
+
+// BlockUntil will block until the fakeClock has the given number of sleepers
+// (callers of Sleep or After)
+func (fc *fakeClock) BlockUntil(n int) {
+ fc.l.Lock()
+ // Fast path: current number of sleepers is what we're looking for
+ if len(fc.sleepers) == n {
+ fc.l.Unlock()
+ return
+ }
+ // Otherwise, set up a new blocker
+ b := &blocker{
+ count: n,
+ ch: make(chan struct{}),
+ }
+ fc.blockers = append(fc.blockers, b)
+ fc.l.Unlock()
+ <-b.ch
+}
diff --git a/vendor/github.com/mattermost/gosaml2/.gitignore b/vendor/github.com/mattermost/gosaml2/.gitignore
new file mode 100644
index 0000000000..9ed3b07cef
--- /dev/null
+++ b/vendor/github.com/mattermost/gosaml2/.gitignore
@@ -0,0 +1 @@
+*.test
diff --git a/vendor/github.com/mattermost/gosaml2/.travis.yml b/vendor/github.com/mattermost/gosaml2/.travis.yml
new file mode 100644
index 0000000000..631959450b
--- /dev/null
+++ b/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
diff --git a/vendor/github.com/mattermost/gosaml2/LICENSE b/vendor/github.com/mattermost/gosaml2/LICENSE
new file mode 100644
index 0000000000..67db858821
--- /dev/null
+++ b/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.
diff --git a/vendor/github.com/mattermost/gosaml2/README.md b/vendor/github.com/mattermost/gosaml2/README.md
new file mode 100644
index 0000000000..2cefd9d7e0
--- /dev/null
+++ b/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
diff --git a/vendor/github.com/mattermost/gosaml2/attribute.go b/vendor/github.com/mattermost/gosaml2/attribute.go
new file mode 100644
index 0000000000..1b271cfb8e
--- /dev/null
+++ b/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 ""
+}
diff --git a/vendor/github.com/mattermost/gosaml2/authn_request.go b/vendor/github.com/mattermost/gosaml2/authn_request.go
new file mode 100644
index 0000000000..9f8360adc7
--- /dev/null
+++ b/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
+}
diff --git a/vendor/github.com/mattermost/gosaml2/build_request.go b/vendor/github.com/mattermost/gosaml2/build_request.go
new file mode 100644
index 0000000000..21ec0bc88b
--- /dev/null
+++ b/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 in (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 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()
+}
diff --git a/vendor/github.com/mattermost/gosaml2/decode_response.go b/vendor/github.com/mattermost/gosaml2/decode_response.go
new file mode 100644
index 0000000000..8698056860
--- /dev/null
+++ b/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
+}
diff --git a/vendor/github.com/mattermost/gosaml2/retrieve_assertion.go b/vendor/github.com/mattermost/gosaml2/retrieve_assertion.go
new file mode 100644
index 0000000000..3da3e36e63
--- /dev/null
+++ b/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
+}
diff --git a/vendor/github.com/mattermost/gosaml2/run_test.sh b/vendor/github.com/mattermost/gosaml2/run_test.sh
new file mode 100644
index 0000000000..cfe5b2ea96
--- /dev/null
+++ b/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
diff --git a/vendor/github.com/mattermost/gosaml2/saml.go b/vendor/github.com/mattermost/gosaml2/saml.go
new file mode 100644
index 0000000000..d9ff05fb12
--- /dev/null
+++ b/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
+}
diff --git a/vendor/github.com/mattermost/gosaml2/test_constants.go b/vendor/github.com/mattermost/gosaml2/test_constants.go
new file mode 100644
index 0000000000..cee10012db
--- /dev/null
+++ b/vendor/github.com/mattermost/gosaml2/test_constants.go
@@ -0,0 +1,395 @@
+package saml2
+
+var idpCertificate = `
+-----BEGIN CERTIFICATE-----
+MIIDpDCCAoygAwIBAgIGAVLIBhAwMA0GCSqGSIb3DQEBBQUAMIGSMQswCQYDVQQGEwJVUzETMBEG
+A1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNU2FuIEZyYW5jaXNjbzENMAsGA1UECgwET2t0YTEU
+MBIGA1UECwwLU1NPUHJvdmlkZXIxEzARBgNVBAMMCmRldi0xMTY4MDcxHDAaBgkqhkiG9w0BCQEW
+DWluZm9Ab2t0YS5jb20wHhcNMTYwMjA5MjE1MjA2WhcNMjYwMjA5MjE1MzA2WjCBkjELMAkGA1UE
+BhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWExFjAUBgNVBAcMDVNhbiBGcmFuY2lzY28xDTALBgNV
+BAoMBE9rdGExFDASBgNVBAsMC1NTT1Byb3ZpZGVyMRMwEQYDVQQDDApkZXYtMTE2ODA3MRwwGgYJ
+KoZIhvcNAQkBFg1pbmZvQG9rdGEuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA
+mtjBOZ8MmhUyi8cGk4dUY6Fj1MFDt/q3FFiaQpLzu3/q5lRVUNUBbAtqQWwY10dzfZguHOuvA5p5
+QyiVDvUhe+XkVwN2R2WfArQJRTPnIcOaHrxqQf3o5cCIG21ZtysFHJSo8clPSOe+0VsoRgcJ1aF4
+2rODwgqRRZdO9Wh3502XlJ799DJQ23IC7XasKEsGKzJqhlRrfd/FyIuZT0sFHDKRz5snSJhm9gpN
+uQlCmk7ONZ1sXqtt+nBIfWIqeoYQubPW7pT5GTc7wouWq4TCjHJiK9k2HiyNxW0E3JX08swEZi2+
+LVDjgLzNc4lwjSYIj3AOtPZs8s606oBdIBni4wIDAQABMA0GCSqGSIb3DQEBBQUAA4IBAQBMxSkJ
+TxkXxsoKNW0awJNpWRbU81QpheMFfENIzLam4Itc/5kSZAaSy/9e2QKfo4jBo/MMbCq2vM9TyeJQ
+DJpRaioUTd2lGh4TLUxAxCxtUk/pascL+3Nn936LFmUCLxaxnbeGzPOXAhscCtU1H0nFsXRnKx5a
+cPXYSKFZZZktieSkww2Oi8dg2DYaQhGQMSFMVqgVfwEu4bvCRBvdSiNXdWGCZQmFVzBZZ/9rOLzP
+pvTFTPnpkavJm81FLlUhiE/oFgKlCDLWDknSpXAI0uZGERcwPca6xvIMh86LjQKjbVci9FYDStXC
+qRnqQ+TccSu/B6uONFsDEngGcXSKfB+a
+-----END CERTIFICATE-----
+`
+
+const rawResponse = `
+http://www.okta.com/exk5zt0r12Edi4rD20h7http://www.okta.com/exk5zt0r12Edi4rD20h7FsWGCBC+t/LaVkUKUvRQpzyZTmlxUzw4R9FOzXPPJRw=hS50WgYs/cn3uxmhrza/0/0QW3H7bwdjPZ2hQmG7IeSd7awTOghBqdrjvaPfQ7tRW+UK6ewMgIBVKG6jV3qYAWeW2U70hMb7hE9qJqBKyYyimmhVWULx1HB2YmlU1wmispywoPlXQ6gj0iWaL2RFI83vUp7X50eZ6dELqoJVZpzQI065Tt0TG7UuKUW1flYsbiS9NaXnuw+mcrBW25ZA9F5CLePHki01ZzUw+XtNmKthEb7SR30mzPoj08Dji22daYvGu82IR01wIZPoQJPCGMT6y2xC/pQPqGljAg/vUa+gaYgaMaAVYxhk/hfgMUBlOeKACBaGTmygab1Nz5KvPg==MIIDpDCCAoygAwIBAgIGAVLIBhAwMA0GCSqGSIb3DQEBBQUAMIGSMQswCQYDVQQGEwJVUzETMBEG
+A1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNU2FuIEZyYW5jaXNjbzENMAsGA1UECgwET2t0YTEU
+MBIGA1UECwwLU1NPUHJvdmlkZXIxEzARBgNVBAMMCmRldi0xMTY4MDcxHDAaBgkqhkiG9w0BCQEW
+DWluZm9Ab2t0YS5jb20wHhcNMTYwMjA5MjE1MjA2WhcNMjYwMjA5MjE1MzA2WjCBkjELMAkGA1UE
+BhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWExFjAUBgNVBAcMDVNhbiBGcmFuY2lzY28xDTALBgNV
+BAoMBE9rdGExFDASBgNVBAsMC1NTT1Byb3ZpZGVyMRMwEQYDVQQDDApkZXYtMTE2ODA3MRwwGgYJ
+KoZIhvcNAQkBFg1pbmZvQG9rdGEuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA
+mtjBOZ8MmhUyi8cGk4dUY6Fj1MFDt/q3FFiaQpLzu3/q5lRVUNUBbAtqQWwY10dzfZguHOuvA5p5
+QyiVDvUhe+XkVwN2R2WfArQJRTPnIcOaHrxqQf3o5cCIG21ZtysFHJSo8clPSOe+0VsoRgcJ1aF4
+2rODwgqRRZdO9Wh3502XlJ799DJQ23IC7XasKEsGKzJqhlRrfd/FyIuZT0sFHDKRz5snSJhm9gpN
+uQlCmk7ONZ1sXqtt+nBIfWIqeoYQubPW7pT5GTc7wouWq4TCjHJiK9k2HiyNxW0E3JX08swEZi2+
+LVDjgLzNc4lwjSYIj3AOtPZs8s606oBdIBni4wIDAQABMA0GCSqGSIb3DQEBBQUAA4IBAQBMxSkJ
+TxkXxsoKNW0awJNpWRbU81QpheMFfENIzLam4Itc/5kSZAaSy/9e2QKfo4jBo/MMbCq2vM9TyeJQ
+DJpRaioUTd2lGh4TLUxAxCxtUk/pascL+3Nn936LFmUCLxaxnbeGzPOXAhscCtU1H0nFsXRnKx5a
+cPXYSKFZZZktieSkww2Oi8dg2DYaQhGQMSFMVqgVfwEu4bvCRBvdSiNXdWGCZQmFVzBZZ/9rOLzP
+pvTFTPnpkavJm81FLlUhiE/oFgKlCDLWDknSpXAI0uZGERcwPca6xvIMh86LjQKjbVci9FYDStXC
+qRnqQ+TccSu/B6uONFsDEngGcXSKfB+aphoebe.simon@scaleft.com123urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransportPhoebeSimonphoebe.simon@scaleft.comphoebesimon`
+
+const manInTheMiddledResponse = `
+http://www.okta.com/exk5zt0r12Edi4rD20h7ijTqmVmDy7ssK+rvmJaCQ6AQaFaXz+HIN/r6O37B0eQ=G09fAYXGDLK+/jAekHsNL0RLo40Xm6+VwXmUj0IDIrvIIv/mJU5VD6ylOLnPezLDBVY9BJst1YCz+8krdvmQ8Stkd6qiN2bN/5KpCdika111YGpeNdMmg/E57ZG3S895hTNJQYOfCwhPFUtQuXLkspOaw81pcqOTr+bVSofJ8uQP7cVQa/ANxbjKAj0fhAuxAvZfiqPms5Stv4sNGpzULUDJl87CoEleHExGmpTsI7Qt3EvGToPMZXPHF4MGvuC0Z2ZD4iI6Pr7xk98t54PJtAX2qJu1tZqBJmL0Qcq5spl9W3yC1tAZuDeFLm1C4/T9crO2Q5WILP/tkw/yJ+ZttQ==MIIDpDCCAoygAwIBAgIGAVLIBhAwMA0GCSqGSIb3DQEBBQUAMIGSMQswCQYDVQQGEwJVUzETMBEG
+A1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNU2FuIEZyYW5jaXNjbzENMAsGA1UECgwET2t0YTEU
+MBIGA1UECwwLU1NPUHJvdmlkZXIxEzARBgNVBAMMCmRldi0xMTY4MDcxHDAaBgkqhkiG9w0BCQEW
+DWluZm9Ab2t0YS5jb20wHhcNMTYwMjA5MjE1MjA2WhcNMjYwMjA5MjE1MzA2WjCBkjELMAkGA1UE
+BhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWExFjAUBgNVBAcMDVNhbiBGcmFuY2lzY28xDTALBgNV
+BAoMBE9rdGExFDASBgNVBAsMC1NTT1Byb3ZpZGVyMRMwEQYDVQQDDApkZXYtMTE2ODA3MRwwGgYJ
+KoZIhvcNAQkBFg1pbmZvQG9rdGEuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA
+mtjBOZ8MmhUyi8cGk4dUY6Fj1MFDt/q3FFiaQpLzu3/q5lRVUNUBbAtqQWwY10dzfZguHOuvA5p5
+QyiVDvUhe+XkVwN2R2WfArQJRTPnIcOaHrxqQf3o5cCIG21ZtysFHJSo8clPSOe+0VsoRgcJ1aF4
+2rODwgqRRZdO9Wh3502XlJ799DJQ23IC7XasKEsGKzJqhlRrfd/FyIuZT0sFHDKRz5snSJhm9gpN
+uQlCmk7ONZ1sXqtt+nBIfWIqeoYQubPW7pT5GTc7wouWq4TCjHJiK9k2HiyNxW0E3JX08swEZi2+
+LVDjgLzNc4lwjSYIj3AOtPZs8s606oBdIBni4wIDAQABMA0GCSqGSIb3DQEBBQUAA4IBAQBMxSkJ
+TxkXxsoKNW0awJNpWRbU81QpheMFfENIzLam4Itc/5kSZAaSy/9e2QKfo4jBo/MMbCq2vM9TyeJQ
+DJpRaioUTd2lGh4TLUxAxCxtUk/pascL+3Nn936LFmUCLxaxnbeGzPOXAhscCtU1H0nFsXRnKx5a
+cPXYSKFZZZktieSkww2Oi8dg2DYaQhGQMSFMVqgVfwEu4bvCRBvdSiNXdWGCZQmFVzBZZ/9rOLzP
+pvTFTPnpkavJm81FLlUhiE/oFgKlCDLWDknSpXAI0uZGERcwPca6xvIMh86LjQKjbVci9FYDStXC
+qRnqQ+TccSu/B6uONFsDEngGcXSKfB+ahttp://www.okta.com/exk5zt0r12Edi4rD20h7zln6sheEO2JBdanrT5mZtJZ192tGHavuBpCFHQsJFVg=dHh6TWbnjtImyrfjPTX5QzE/6Vm/HsRWVvWWlvFAddf/CvhO4Kc5j8C7hvQoYMLhYuZMFFSReGysuDy5IscOJwTGhhcvb238qHSGGs6q8OUBCsmLSDAbIaGA++LV/tkUZ2ridGIi0yT81UOl1oT1batlHsK3eMyxkpnFmvBzIm4tGTzRkOPpYRLeiM9bxbKI+DM/623DCXyBCLYBzJo1O6QE02aLajwRMi/vmiV4LSiGlFcY9TtDCafdVJRv0tIQ25BQoT4feuHdr6S8xOSpGgRYH5ECamVOt4e079XdEkVUiSzQokiUkgDlTXEyerPLOVsOk4PW5nRs86sXIiGL5w==MIIDpDCCAoygAwIBAgIGAVLIBhAwMA0GCSqGSIb3DQEBBQUAMIGSMQswCQYDVQQGEwJVUzETMBEG
+A1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNU2FuIEZyYW5jaXNjbzENMAsGA1UECgwET2t0YTEU
+MBIGA1UECwwLU1NPUHJvdmlkZXIxEzARBgNVBAMMCmRldi0xMTY4MDcxHDAaBgkqhkiH9w0BCQEW
+DWluZm9Ab2t0YS5jb20wHhcNMTYwMjA5MjE1MjA2WhcNMjYwMjA5MjE1MzA2WjCBkjELMAkGA1UE
+BhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWExFjAUBgNVBAcMDVNhbiBGcmFuY2lzY28xDTALBgNV
+BAoMBE9rdGExFDASBgNVBAsMC1NTT1Byb3ZpZGVyMRMwEQYDVQQDDApkZXYtMTE2ODA3MRwwGgYJ
+KoZIhvcNAQkBFg1pbmZvQG9rdGEuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA
+mtjBOZ8MmhUyi8cGk4dUY6Fj1MFDt/q3FFiaQpLzu3/q5lRVUNUBbAtqQWwY10dzfZguHOuvA5p5
+QyiVDvUhe+XkVwN2R2WfArQJRTPnIcOaHrxqQf3o5cCIG21ZtysFHJSo8clPSOe+0VsoRgcJ1aF4
+2rODwgqRRZdO9Wh3502XlJ799DJQ23IC7XasKEsGKzJqhlRrfd/FyIuZT0sFHDKRz5snSJhm9gpN
+uQlCmk7ONZ1sXqtt+nBIfWIqeoYQubPW7pT5GTc7wouWq4TCjHJiK9k2HiyNxW0E3JX08swEZi2+
+LVDjgLzNc4lwjSYIj3AOtPZs8s606oBdIBni4wIDAQABMA0GCSqGSIb3DQEBBQUAA4IBAQBMxSkJ
+TxkXxsoKNW0awJNpWRbU81QpheMFfENIzLam4Itc/5kSZAaSy/9e2QKfo4jBo/MMbCq2vM9TyeJQ
+DJpRaioUTd2lGh4TLUxAxCxtUk/pascL+3Nn936LFmUCLxaxnbeGzPOXAhscCtU1H0nFsXRnKx5a
+cPXYSKFZZZktieSkww2Oi8dg2DYaQhGQMSFMVqgVfwEu4bvCRBvdSiNXdWGCZQmFVzBZZ/9rOLzP
+pvTFTPnpkavJm81FLlUhiE/oFgKlCDLWDknSpXAI0uZGERcwPca6xvIMh86LjQKjbVci9FYDStXC
+qRnqQ+TccSu/B6uONFsDEngGcXSKfB+aphoebe.simon@scaleft.com123urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransportPhoebeSimonphoebe.simon@scaleft.com`
+
+const alteredReferenceURIResponse = `
+http://www.okta.com/exk5zt0r12Edi4rD20h7ijTqmVmDy7ssK+rvmJaCQ6AQaFaXz+HIN/r6O37B0eQ=G09fAYXGDLK+/jAekHsNL0RLo40Xm6+VwXmUj0IDIrvIIv/mJU5VD6ylOLnPezLDBVY9BJst1YCz+8krdvmQ8Stkd6qiN2bN/5KpCdika111YGpeNdMmg/E57ZG3S895hTNJQYOfCwhPFUtQuXLkspOaw81pcqOTr+bVSofJ8uQP7cVQa/ANxbjKAj0fhAuxAvZfiqPms5Stv4sNGpzULUDJl87CoEleHExGmpTsI7Qt3EvGToPMZXPHF4MGvuC0Z2ZD4iI6Pr7xk98t54PJtAX2qJu1tZqBJmL0Qcq5spl9W3yC1tAZuDeFLm1C4/T9crO2Q5WILP/tkw/yJ+ZttQ==MIIDpDCCAoygAwIBAgIGAVLIBhAwMA0GCSqGSIb3DQEBBQUAMIGSMQswCQYDVQQGEwJVUzETMBEG
+A1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNU2FuIEZyYW5jaXNjbzENMAsGA1UECgwET2t0YTEU
+MBIGA1UECwwLU1NPUHJvdmlkZXIxEzARBgNVBAMMCmRldi0xMTY4MDcxHDAaBgkqhkiG9w0BCQEW
+DWluZm9Ab2t0YS5jb20wHhcNMTYwMjA5MjE1MjA2WhcNMjYwMjA5MjE1MzA2WjCBkjELMAkGA1UE
+BhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWExFjAUBgNVBAcMDVNhbiBGcmFuY2lzY28xDTALBgNV
+BAoMBE9rdGExFDASBgNVBAsMC1NTT1Byb3ZpZGVyMRMwEQYDVQQDDApkZXYtMTE2ODA3MRwwGgYJ
+KoZIhvcNAQkBFg1pbmZvQG9rdGEuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA
+mtjBOZ8MmhUyi8cGk4dUY6Fj1MFDt/q3FFiaQpLzu3/q5lRVUNUBbAtqQWwY10dzfZguHOuvA5p5
+QyiVDvUhe+XkVwN2R2WfArQJRTPnIcOaHrxqQf3o5cCIG21ZtysFHJSo8clPSOe+0VsoRgcJ1aF4
+2rODwgqRRZdO9Wh3502XlJ799DJQ23IC7XasKEsGKzJqhlRrfd/FyIuZT0sFHDKRz5snSJhm9gpN
+uQlCmk7ONZ1sXqtt+nBIfWIqeoYQubPW7pT5GTc7wouWq4TCjHJiK9k2HiyNxW0E3JX08swEZi2+
+LVDjgLzNc4lwjSYIj3AOtPZs8s606oBdIBni4wIDAQABMA0GCSqGSIb3DQEBBQUAA4IBAQBMxSkJ
+TxkXxsoKNW0awJNpWRbU81QpheMFfENIzLam4Itc/5kSZAaSy/9e2QKfo4jBo/MMbCq2vM9TyeJQ
+DJpRaioUTd2lGh4TLUxAxCxtUk/pascL+3Nn936LFmUCLxaxnbeGzPOXAhscCtU1H0nFsXRnKx5a
+cPXYSKFZZZktieSkww2Oi8dg2DYaQhGQMSFMVqgVfwEu4bvCRBvdSiNXdWGCZQmFVzBZZ/9rOLzP
+pvTFTPnpkavJm81FLlUhiE/oFgKlCDLWDknSpXAI0uZGERcwPca6xvIMh86LjQKjbVci9FYDStXC
+qRnqQ+TccSu/B6uONFsDEngGcXSKfB+ahttp://www.okta.com/exk5zt0r12Edi4rD20h7zln6sheEO2JBdanrT5mZtJZ192tGHavuBpCFHQsJFVg=dHh6TWbnjtImyrfjPTX5QzE/6Vm/HsRWVvWWlvFAddf/CvhO4Kc5j8C7hvQoYMLhYuZMFFSReGysuDy5IscOJwTGhhcvb238qHSGGs6q8OUBCsmLSDAbIaGA++LV/tkUZ2ridGIi0yT81UOl1oT1batlHsK3eMyxkpnFmvBzIm4tGTzRkOPpYRLeiM9bxbKI+DM/623DCXyBCLYBzJo1O6QE02aLajwRMi/vmiV4LSiGlFcY9TtDCafdVJRv0tIQ25BQoT4feuHdr6S8xOSpGgRYH5ECamVOt4e079XdEkVUiSzQokiUkgDlTXEyerPLOVsOk4PW5nRs86sXIiGL5w==MIIDpDCCAoygAwIBAgIGAVLIBhAwMA0GCSqGSIb3DQEBBQUAMIGSMQswCQYDVQQGEwJVUzETMBEG
+A1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNU2FuIEZyYW5jaXNjbzENMAsGA1UECgwET2t0YTEU
+MBIGA1UECwwLU1NPUHJvdmlkZXIxEzARBgNVBAMMCmRldi0xMTY4MDcxHDAaBgkqhkiH9w0BCQEW
+DWluZm9Ab2t0YS5jb20wHhcNMTYwMjA5MjE1MjA2WhcNMjYwMjA5MjE1MzA2WjCBkjELMAkGA1UE
+BhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWExFjAUBgNVBAcMDVNhbiBGcmFuY2lzY28xDTALBgNV
+BAoMBE9rdGExFDASBgNVBAsMC1NTT1Byb3ZpZGVyMRMwEQYDVQQDDApkZXYtMTE2ODA3MRwwGgYJ
+KoZIhvcNAQkBFg1pbmZvQG9rdGEuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA
+mtjBOZ8MmhUyi8cGk4dUY6Fj1MFDt/q3FFiaQpLzu3/q5lRVUNUBbAtqQWwY10dzfZguHOuvA5p5
+QyiVDvUhe+XkVwN2R2WfArQJRTPnIcOaHrxqQf3o5cCIG21ZtysFHJSo8clPSOe+0VsoRgcJ1aF4
+2rODwgqRRZdO9Wh3502XlJ799DJQ23IC7XasKEsGKzJqhlRrfd/FyIuZT0sFHDKRz5snSJhm9gpN
+uQlCmk7ONZ1sXqtt+nBIfWIqeoYQubPW7pT5GTc7wouWq4TCjHJiK9k2HiyNxW0E3JX08swEZi2+
+LVDjgLzNc4lwjSYIj3AOtPZs8s606oBdIBni4wIDAQABMA0GCSqGSIb3DQEBBQUAA4IBAQBMxSkJ
+TxkXxsoKNW0awJNpWRbU81QpheMFfENIzLam4Itc/5kSZAaSy/9e2QKfo4jBo/MMbCq2vM9TyeJQ
+DJpRaioUTd2lGh4TLUxAxCxtUk/pascL+3Nn936LFmUCLxaxnbeGzPOXAhscCtU1H0nFsXRnKx5a
+cPXYSKFZZZktieSkww2Oi8dg2DYaQhGQMSFMVqgVfwEu4bvCRBvdSiNXdWGCZQmFVzBZZ/9rOLzP
+pvTFTPnpkavJm81FLlUhiE/oFgKlCDLWDknSpXAI0uZGERcwPca6xvIMh86LjQKjbVci9FYDStXC
+qRnqQ+TccSu/B6uONFsDEngGcXSKfB+aphoebe.simon@scaleft.com123urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransportPhoebeSimonphoebe.simon@scaleft.com`
+
+const alteredSignedInfoResponse = `
+http://www.okta.com/exk5zt0r12Edi4rD20h7ijTqmVmDy7ssK+rvmJaCQ6AQaFaXz+HIN/r6O37B0eQ=G09fAYXGDLK+/jAekHsNL0RLo40Xm6+VwXmUj0IDIrvIIv/mJU5VD6ylOLnPezLDBVY9BJst1YCz+8krdvmQ8Stkd6qiN2bN/5KpCdika111YGpeNdMmg/E57ZG3S895hTNJQYOfCwhPFUtQuXLkspOaw81pcqOTr+bVSofJ8uQP7cVQa/ANxbjKAj0fhAuxAvZfiqPms5Stv4sNGpzULUDJl87CoEleHExGmpTsI7Qt3EvGToPMZXPHF4MGvuC0Z2ZD4iI6Pr7xk98t54PJtAX2qJu1tZqBJmL0Qcq5spl9W3yC1tAZuDeFLm1C4/T9crO2Q5WILP/tkw/yJ+ZttQ==MIIDpDCCAoygAwIBAgIGAVLIBhAwMA0GCSqGSIb3DQEBBQUAMIGSMQswCQYDVQQGEwJVUzETMBEG
+A1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNU2FuIEZyYW5jaXNjbzENMAsGA1UECgwET2t0YTEV
+MBIGA1UECwwLU1NPUHJvdmlkZXIxEzARBgNVBAMMCmRldi0xMTY4MDcxHDAaBgkqhkiG9w0BCQEW
+DWluZm9Ab2t0YS5jb20wHhcNMTYwMjA5MjE1MjA2WhcNMjYwMjA5MjE1MzA2WjCBkjELMAkGA1UE
+BhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWExFjAUBgNVBAcMDVNhbiBGcmFuY2lzY28xDTALBgNV
+BAoMBE9rdGExFDASBgNVBAsMC1NTT1Byb3ZpZGVyMRMwEQYDVQQDDApkZXYtMTE2ODA3MRwwGgYJ
+KoZIhvcNAQkBFg1pbmZvQG9rdGEuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA
+mtjBOZ8MmhUyi8cGk4dUY6Fj1MFDt/q3FFiaQpLzu3/q5lRVUNUBbAtqQWwY10dzfZguHOuvA5p5
+QyiVDvUhe+XkVwN2R2WfArQJRTPnIcOaHrxqQf3o5cCIG21ZtysFHJSo8clPSOe+0VsoRgcJ1aF4
+2rODwgqRRZdO9Wh3502XlJ799DJQ23IC7XasKEsGKzJqhlRrfd/FyIuZT0sFHDKRz5snSJhm9gpN
+uQlCmk7ONZ1sXqtt+nBIfWIqeoYQubPW7pT5GTc7wouWq4TCjHJiK9k2HiyNxW0E3JX08swEZi2+
+LVDjgLzNc4lwjSYIj3AOtPZs8s606oBdIBni4wIDAQABMA0GCSqGSIb3DQEBBQUAA4IBAQBMxSkJ
+TxkXxsoKNW0awJNpWRbU81QpheMFfENIzLam4Itc/5kSZAaSy/9e2QKfo4jBo/MMbCq2vM9TyeJQ
+DJpRaioUTd2lGh4TLUxAxCxtUk/pascL+3Nn936LFmUCLxaxnbeGzPOXAhscCtU1H0nFsXRnKx5a
+cPXYSKFZZZktieSkww2Oi8dg2DYaQhGQMSFMVqgVfwEu4bvCRBvdSiNXdWGCZQmFVzBZZ/9rOLzP
+pvTFTPnpkavJm81FLlUhiE/oFgKlCDLWDknSpXAI0uZGERcwPca6xvIMh86LjQKjbVci9FYDStXC
+qRnqQ+TccSu/B6uONFsDEngGcXSKfB+ahttp://www.okta.com/exk5zt0r12Edi4rD20h7zln6sheEO2JBdanrT5mZtJZ192tGHavuBpCFHQsJFVg=dHh6TWbnjtImyrfjPTX5QzE/6Vm/HsRWVvWWlvFAddf/CvhO4Kc5j8C7hvQoYMLhYuZMFFSReGysuDy5IscOJwTGhhcvb238qHSGGs6q8OUBCsmLSDAbIaGA++LV/tkUZ2ridGIi0yT81UOl1oT1batlHsK3eMyxkpnFmvBzIm4tGTzRkOPpYRLeiM9bxbKI+DM/623DCXyBCLYBzJo1O6QE02aLajwRMi/vmiV4LSiGlFcY9TtDCafdVJRv0tIQ25BQoT4feuHdr6S8xOSpGgRYH5ECamVOt4e079XdEkVUiSzQokiUkgDlTXEyerPLOVsOk4PW5nRs86sXIiGL5w==MIIDpDCCAoygAwIBAgIGAVLIBhAwMA0GCSqGSIb3DQEBBQUAMIGSMQswCQYDVQQGEwJVUzETMBEG
+A1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNU2FuIEZyYW5jaXNjbzENMAsGA1UECgwET2t0YTEU
+MBIGA1UECwwLU1NPUHJvdmlkZXIxEzARBgNVBAMMCmRldi0xMTY4MDcxHDAaBgkqhkiH9w0BCQEW
+DWluZm9Ab2t0YS5jb20wHhcNMTYwMjA5MjE1MjA2WhcNMjYwMjA5MjE1MzA2WjCBkjELMAkGA1UE
+BhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWExFjAUBgNVBAcMDVNhbiBGcmFuY2lzY28xDTALBgNV
+BAoMBE9rdGExFDASBgNVBAsMC1NTT1Byb3ZpZGVyMRMwEQYDVQQDDApkZXYtMTE2ODA3MRwwGgYJ
+KoZIhvcNAQkBFg1pbmZvQG9rdGEuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA
+mtjBOZ8MmhUyi8cGk4dUY6Fj1MFDt/q3FFiaQpLzu3/q5lRVUNUBbAtqQWwY10dzfZguHOuvA5p5
+QyiVDvUhe+XkVwN2R2WfArQJRTPnIcOaHrxqQf3o5cCIG21ZtysFHJSo8clPSOe+0VsoRgcJ1aF4
+2rODwgqRRZdO9Wh3502XlJ799DJQ23IC7XasKEsGKzJqhlRrfd/FyIuZT0sFHDKRz5snSJhm9gpN
+uQlCmk7ONZ1sXqtt+nBIfWIqeoYQubPW7pT5GTc7wouWq4TCjHJiK9k2HiyNxW0E3JX08swEZi2+
+LVDjgLzNc4lwjSYIj3AOtPZs8s606oBdIBni4wIDAQABMA0GCSqGSIb3DQEBBQUAA4IBAQBMxSkJ
+TxkXxsoKNW0awJNpWRbU81QpheMFfENIzLam4Itc/5kSZAaSy/9e2QKfo4jBo/MMbCq2vM9TyeJQ
+DJpRaioUTd2lGh4TLUxAxCxtUk/pascL+3Nn936LFmUCLxaxnbeGzPOXAhscCtU1H0nFsXRnKx5a
+cPXYSKFZZZktieSkww2Oi8dg2DYaQhGQMSFMVqgVfwEu4bvCRBvdSiNXdWGCZQmFVzBZZ/9rOLzP
+pvTFTPnpkavJm81FLlUhiE/oFgKlCDLWDknSpXAI0uZGERcwPca6xvIMh86LjQKjbVci9FYDStXC
+qRnqQ+TccSu/B6uONFsDEngGcXSKfB+aphoebe.simon@scaleft.com123urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransportPhoebeSimonphoebe.simon@scaleft.com`
+
+const alteredRecipientResponse = `
+http://www.okta.com/exk5zt0r12Edi4rD20h7http://www.okta.com/exk5zt0r12Edi4rD20h7FsWGCBC+t/LaVkUKUvRQpzyZTmlxUzw4R9FOzXPPJRw=hS50WgYs/cn3uxmhrza/0/0QW3H7bwdjPZ2hQmG7IeSd7awTOghBqdrjvaPfQ7tRW+UK6ewMgIBVKG6jV3qYAWeW2U70hMb7hE9qJqBKyYyimmhVWULx1HB2YmlU1wmispywoPlXQ6gj0iWaL2RFI83vUp7X50eZ6dELqoJVZpzQI065Tt0TG7UuKUW1flYsbiS9NaXnuw+mcrBW25ZA9F5CLePHki01ZzUw+XtNmKthEb7SR30mzPoj08Dji22daYvGu82IR01wIZPoQJPCGMT6y2xC/pQPqGljAg/vUa+gaYgaMaAVYxhk/hfgMUBlOeKACBaGTmygab1Nz5KvPg==MIIDpDCCAoygAwIBAgIGAVLIBhAwMA0GCSqGSIb3DQEBBQUAMIGSMQswCQYDVQQGEwJVUzETMBEG
+A1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNU2FuIEZyYW5jaXNjbzENMAsGA1UECgwET2t0YTEU
+MBIGA1UECwwLU1NPUHJvdmlkZXIxEzARBgNVBAMMCmRldi0xMTY4MDcxHDAaBgkqhkiG9w0BCQEW
+DWluZm9Ab2t0YS5jb20wHhcNMTYwMjA5MjE1MjA2WhcNMjYwMjA5MjE1MzA2WjCBkjELMAkGA1UE
+BhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWExFjAUBgNVBAcMDVNhbiBGcmFuY2lzY28xDTALBgNV
+BAoMBE9rdGExFDASBgNVBAsMC1NTT1Byb3ZpZGVyMRMwEQYDVQQDDApkZXYtMTE2ODA3MRwwGgYJ
+KoZIhvcNAQkBFg1pbmZvQG9rdGEuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA
+mtjBOZ8MmhUyi8cGk4dUY6Fj1MFDt/q3FFiaQpLzu3/q5lRVUNUBbAtqQWwY10dzfZguHOuvA5p5
+QyiVDvUhe+XkVwN2R2WfArQJRTPnIcOaHrxqQf3o5cCIG21ZtysFHJSo8clPSOe+0VsoRgcJ1aF4
+2rODwgqRRZdO9Wh3502XlJ799DJQ23IC7XasKEsGKzJqhlRrfd/FyIuZT0sFHDKRz5snSJhm9gpN
+uQlCmk7ONZ1sXqtt+nBIfWIqeoYQubPW7pT5GTc7wouWq4TCjHJiK9k2HiyNxW0E3JX08swEZi2+
+LVDjgLzNc4lwjSYIj3AOtPZs8s606oBdIBni4wIDAQABMA0GCSqGSIb3DQEBBQUAA4IBAQBMxSkJ
+TxkXxsoKNW0awJNpWRbU81QpheMFfENIzLam4Itc/5kSZAaSy/9e2QKfo4jBo/MMbCq2vM9TyeJQ
+DJpRaioUTd2lGh4TLUxAxCxtUk/pascL+3Nn936LFmUCLxaxnbeGzPOXAhscCtU1H0nFsXRnKx5a
+cPXYSKFZZZktieSkww2Oi8dg2DYaQhGQMSFMVqgVfwEu4bvCRBvdSiNXdWGCZQmFVzBZZ/9rOLzP
+pvTFTPnpkavJm81FLlUhiE/oFgKlCDLWDknSpXAI0uZGERcwPca6xvIMh86LjQKjbVci9FYDStXC
+qRnqQ+TccSu/B6uONFsDEngGcXSKfB+aphoebe.simon@scaleft.com123urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransportPhoebeSimonphoebe.simon@scaleft.comphoebe.simon@scaleft.com`
+
+const alteredSubjectConfirmationMethodResponse = `
+http://www.okta.com/exk5zt0r12Edi4rD20h7http://www.okta.com/exk5zt0r12Edi4rD20h7FsWGCBC+t/LaVkUKUvRQpzyZTmlxUzw4R9FOzXPPJRw=hS50WgYs/cn3uxmhrza/0/0QW3H7bwdjPZ2hQmG7IeSd7awTOghBqdrjvaPfQ7tRW+UK6ewMgIBVKG6jV3qYAWeW2U70hMb7hE9qJqBKyYyimmhVWULx1HB2YmlU1wmispywoPlXQ6gj0iWaL2RFI83vUp7X50eZ6dELqoJVZpzQI065Tt0TG7UuKUW1flYsbiS9NaXnuw+mcrBW25ZA9F5CLePHki01ZzUw+XtNmKthEb7SR30mzPoj08Dji22daYvGu82IR01wIZPoQJPCGMT6y2xC/pQPqGljAg/vUa+gaYgaMaAVYxhk/hfgMUBlOeKACBaGTmygab1Nz5KvPg==MIIDpDCCAoygAwIBAgIGAVLIBhAwMA0GCSqGSIb3DQEBBQUAMIGSMQswCQYDVQQGEwJVUzETMBEG
+A1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNU2FuIEZyYW5jaXNjbzENMAsGA1UECgwET2t0YTEU
+MBIGA1UECwwLU1NPUHJvdmlkZXIxEzARBgNVBAMMCmRldi0xMTY4MDcxHDAaBgkqhkiG9w0BCQEW
+DWluZm9Ab2t0YS5jb20wHhcNMTYwMjA5MjE1MjA2WhcNMjYwMjA5MjE1MzA2WjCBkjELMAkGA1UE
+BhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWExFjAUBgNVBAcMDVNhbiBGcmFuY2lzY28xDTALBgNV
+BAoMBE9rdGExFDASBgNVBAsMC1NTT1Byb3ZpZGVyMRMwEQYDVQQDDApkZXYtMTE2ODA3MRwwGgYJ
+KoZIhvcNAQkBFg1pbmZvQG9rdGEuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA
+mtjBOZ8MmhUyi8cGk4dUY6Fj1MFDt/q3FFiaQpLzu3/q5lRVUNUBbAtqQWwY10dzfZguHOuvA5p5
+QyiVDvUhe+XkVwN2R2WfArQJRTPnIcOaHrxqQf3o5cCIG21ZtysFHJSo8clPSOe+0VsoRgcJ1aF4
+2rODwgqRRZdO9Wh3502XlJ799DJQ23IC7XasKEsGKzJqhlRrfd/FyIuZT0sFHDKRz5snSJhm9gpN
+uQlCmk7ONZ1sXqtt+nBIfWIqeoYQubPW7pT5GTc7wouWq4TCjHJiK9k2HiyNxW0E3JX08swEZi2+
+LVDjgLzNc4lwjSYIj3AOtPZs8s606oBdIBni4wIDAQABMA0GCSqGSIb3DQEBBQUAA4IBAQBMxSkJ
+TxkXxsoKNW0awJNpWRbU81QpheMFfENIzLam4Itc/5kSZAaSy/9e2QKfo4jBo/MMbCq2vM9TyeJQ
+DJpRaioUTd2lGh4TLUxAxCxtUk/pascL+3Nn936LFmUCLxaxnbeGzPOXAhscCtU1H0nFsXRnKx5a
+cPXYSKFZZZktieSkww2Oi8dg2DYaQhGQMSFMVqgVfwEu4bvCRBvdSiNXdWGCZQmFVzBZZ/9rOLzP
+pvTFTPnpkavJm81FLlUhiE/oFgKlCDLWDknSpXAI0uZGERcwPca6xvIMh86LjQKjbVci9FYDStXC
+qRnqQ+TccSu/B6uONFsDEngGcXSKfB+aphoebe.simon@scaleft.com123urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransportPhoebeSimonphoebe.simon@scaleft.comphoebe.simon@scaleft.com`
+
+const alteredDestinationResponse = `
+http://www.okta.com/exk5zt0r12Edi4rD20h7http://www.okta.com/exk5zt0r12Edi4rD20h7FsWGCBC+t/LaVkUKUvRQpzyZTmlxUzw4R9FOzXPPJRw=hS50WgYs/cn3uxmhrza/0/0QW3H7bwdjPZ2hQmG7IeSd7awTOghBqdrjvaPfQ7tRW+UK6ewMgIBVKG6jV3qYAWeW2U70hMb7hE9qJqBKyYyimmhVWULx1HB2YmlU1wmispywoPlXQ6gj0iWaL2RFI83vUp7X50eZ6dELqoJVZpzQI065Tt0TG7UuKUW1flYsbiS9NaXnuw+mcrBW25ZA9F5CLePHki01ZzUw+XtNmKthEb7SR30mzPoj08Dji22daYvGu82IR01wIZPoQJPCGMT6y2xC/pQPqGljAg/vUa+gaYgaMaAVYxhk/hfgMUBlOeKACBaGTmygab1Nz5KvPg==MIIDpDCCAoygAwIBAgIGAVLIBhAwMA0GCSqGSIb3DQEBBQUAMIGSMQswCQYDVQQGEwJVUzETMBEG
+A1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNU2FuIEZyYW5jaXNjbzENMAsGA1UECgwET2t0YTEU
+MBIGA1UECwwLU1NPUHJvdmlkZXIxEzARBgNVBAMMCmRldi0xMTY4MDcxHDAaBgkqhkiG9w0BCQEW
+DWluZm9Ab2t0YS5jb20wHhcNMTYwMjA5MjE1MjA2WhcNMjYwMjA5MjE1MzA2WjCBkjELMAkGA1UE
+BhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWExFjAUBgNVBAcMDVNhbiBGcmFuY2lzY28xDTALBgNV
+BAoMBE9rdGExFDASBgNVBAsMC1NTT1Byb3ZpZGVyMRMwEQYDVQQDDApkZXYtMTE2ODA3MRwwGgYJ
+KoZIhvcNAQkBFg1pbmZvQG9rdGEuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA
+mtjBOZ8MmhUyi8cGk4dUY6Fj1MFDt/q3FFiaQpLzu3/q5lRVUNUBbAtqQWwY10dzfZguHOuvA5p5
+QyiVDvUhe+XkVwN2R2WfArQJRTPnIcOaHrxqQf3o5cCIG21ZtysFHJSo8clPSOe+0VsoRgcJ1aF4
+2rODwgqRRZdO9Wh3502XlJ799DJQ23IC7XasKEsGKzJqhlRrfd/FyIuZT0sFHDKRz5snSJhm9gpN
+uQlCmk7ONZ1sXqtt+nBIfWIqeoYQubPW7pT5GTc7wouWq4TCjHJiK9k2HiyNxW0E3JX08swEZi2+
+LVDjgLzNc4lwjSYIj3AOtPZs8s606oBdIBni4wIDAQABMA0GCSqGSIb3DQEBBQUAA4IBAQBMxSkJ
+TxkXxsoKNW0awJNpWRbU81QpheMFfENIzLam4Itc/5kSZAaSy/9e2QKfo4jBo/MMbCq2vM9TyeJQ
+DJpRaioUTd2lGh4TLUxAxCxtUk/pascL+3Nn936LFmUCLxaxnbeGzPOXAhscCtU1H0nFsXRnKx5a
+cPXYSKFZZZktieSkww2Oi8dg2DYaQhGQMSFMVqgVfwEu4bvCRBvdSiNXdWGCZQmFVzBZZ/9rOLzP
+pvTFTPnpkavJm81FLlUhiE/oFgKlCDLWDknSpXAI0uZGERcwPca6xvIMh86LjQKjbVci9FYDStXC
+qRnqQ+TccSu/B6uONFsDEngGcXSKfB+aphoebe.simon@scaleft.com123urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransportPhoebeSimonphoebe.simon@scaleft.comphoebe.simon@scaleft.com`
+
+const alteredVersionResponse = `
+http://www.okta.com/exk5zt0r12Edi4rD20h7http://www.okta.com/exk5zt0r12Edi4rD20h7FsWGCBC+t/LaVkUKUvRQpzyZTmlxUzw4R9FOzXPPJRw=hS50WgYs/cn3uxmhrza/0/0QW3H7bwdjPZ2hQmG7IeSd7awTOghBqdrjvaPfQ7tRW+UK6ewMgIBVKG6jV3qYAWeW2U70hMb7hE9qJqBKyYyimmhVWULx1HB2YmlU1wmispywoPlXQ6gj0iWaL2RFI83vUp7X50eZ6dELqoJVZpzQI065Tt0TG7UuKUW1flYsbiS9NaXnuw+mcrBW25ZA9F5CLePHki01ZzUw+XtNmKthEb7SR30mzPoj08Dji22daYvGu82IR01wIZPoQJPCGMT6y2xC/pQPqGljAg/vUa+gaYgaMaAVYxhk/hfgMUBlOeKACBaGTmygab1Nz5KvPg==MIIDpDCCAoygAwIBAgIGAVLIBhAwMA0GCSqGSIb3DQEBBQUAMIGSMQswCQYDVQQGEwJVUzETMBEG
+A1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNU2FuIEZyYW5jaXNjbzENMAsGA1UECgwET2t0YTEU
+MBIGA1UECwwLU1NPUHJvdmlkZXIxEzARBgNVBAMMCmRldi0xMTY4MDcxHDAaBgkqhkiG9w0BCQEW
+DWluZm9Ab2t0YS5jb20wHhcNMTYwMjA5MjE1MjA2WhcNMjYwMjA5MjE1MzA2WjCBkjELMAkGA1UE
+BhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWExFjAUBgNVBAcMDVNhbiBGcmFuY2lzY28xDTALBgNV
+BAoMBE9rdGExFDASBgNVBAsMC1NTT1Byb3ZpZGVyMRMwEQYDVQQDDApkZXYtMTE2ODA3MRwwGgYJ
+KoZIhvcNAQkBFg1pbmZvQG9rdGEuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA
+mtjBOZ8MmhUyi8cGk4dUY6Fj1MFDt/q3FFiaQpLzu3/q5lRVUNUBbAtqQWwY10dzfZguHOuvA5p5
+QyiVDvUhe+XkVwN2R2WfArQJRTPnIcOaHrxqQf3o5cCIG21ZtysFHJSo8clPSOe+0VsoRgcJ1aF4
+2rODwgqRRZdO9Wh3502XlJ799DJQ23IC7XasKEsGKzJqhlRrfd/FyIuZT0sFHDKRz5snSJhm9gpN
+uQlCmk7ONZ1sXqtt+nBIfWIqeoYQubPW7pT5GTc7wouWq4TCjHJiK9k2HiyNxW0E3JX08swEZi2+
+LVDjgLzNc4lwjSYIj3AOtPZs8s606oBdIBni4wIDAQABMA0GCSqGSIb3DQEBBQUAA4IBAQBMxSkJ
+TxkXxsoKNW0awJNpWRbU81QpheMFfENIzLam4Itc/5kSZAaSy/9e2QKfo4jBo/MMbCq2vM9TyeJQ
+DJpRaioUTd2lGh4TLUxAxCxtUk/pascL+3Nn936LFmUCLxaxnbeGzPOXAhscCtU1H0nFsXRnKx5a
+cPXYSKFZZZktieSkww2Oi8dg2DYaQhGQMSFMVqgVfwEu4bvCRBvdSiNXdWGCZQmFVzBZZ/9rOLzP
+pvTFTPnpkavJm81FLlUhiE/oFgKlCDLWDknSpXAI0uZGERcwPca6xvIMh86LjQKjbVci9FYDStXC
+qRnqQ+TccSu/B6uONFsDEngGcXSKfB+aphoebe.simon@scaleft.com123urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransportPhoebeSimonphoebe.simon@scaleft.comphoebe.simon@scaleft.com`
+
+const missingIDResponse = `
+http://www.okta.com/exk5zt0r12Edi4rD20h7mj+xyS5DtKVNbbFq4caWhGcrirqNzv7mIHNzHQH/f60=GA1URoMOE5EFfkHYimGXm7Ecph/m0s135VyF9Wut6NSpuZdQ2crM1IslvKCRjkE09rZgagQQMAThUcOFuX35dZPz9J4Ihpt1juhfGv1AV8I8jiOKFETj65MiPabDEi8+P6YWf4qNujAJXHKJIa/MFXBqoKR/imLQT8eu1nhVBQGYqWwZePddfXO2JYk2ce7mtnyMT0dUVb+o+tlEDYa7ri9fj4JL/z1XX7yrbVZxn2mdKPJtSSP8uHNOWSM6j1vp4oK+KSDviBfiVLlVA58noz5GyFtp642h+LV2quKbncMFfnfB1kfHLK/xaz9UaDBy+bHK4oGzSpVhZqcOzzliKA==MIIDpDCCAoygAwIBAgIGAVLIBhAwMA0GCSqGSIb3DQEBBQUAMIGSMQswCQYDVQQGEwJVUzETMBEG
+A1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNU2FuIEZyYW5jaXNjbzENMAsGA1UECgwET2t0YTEU
+MBIGA1UECwwLU1NPUHJvdmlkZXIxEzARBgNVBAMMCmRldi0xMTY4MDcxHDAaBgkqhkiG9w0BCQEW
+DWluZm9Ab2t0YS5jb20wHhcNMTYwMjA5MjE1MjA2WhcNMjYwMjA5MjE1MzA2WjCBkjELMAkGA1UE
+BhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWExFjAUBgNVBAcMDVNhbiBGcmFuY2lzY28xDTALBgNV
+BAoMBE9rdGExFDASBgNVBAsMC1NTT1Byb3ZpZGVyMRMwEQYDVQQDDApkZXYtMTE2ODA3MRwwGgYJ
+KoZIhvcNAQkBFg1pbmZvQG9rdGEuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA
+mtjBOZ8MmhUyi8cGk4dUY6Fj1MFDt/q3FFiaQpLzu3/q5lRVUNUBbAtqQWwY10dzfZguHOuvA5p5
+QyiVDvUhe+XkVwN2R2WfArQJRTPnIcOaHrxqQf3o5cCIG21ZtysFHJSo8clPSOe+0VsoRgcJ1aF4
+2rODwgqRRZdO9Wh3502XlJ799DJQ23IC7XasKEsGKzJqhlRrfd/FyIuZT0sFHDKRz5snSJhm9gpN
+uQlCmk7ONZ1sXqtt+nBIfWIqeoYQubPW7pT5GTc7wouWq4TCjHJiK9k2HiyNxW0E3JX08swEZi2+
+LVDjgLzNc4lwjSYIj3AOtPZs8s606oBdIBni4wIDAQABMA0GCSqGSIb3DQEBBQUAA4IBAQBMxSkJ
+TxkXxsoKNW0awJNpWRbU81QpheMFfENIzLam4Itc/5kSZAaSy/9e2QKfo4jBo/MMbCq2vM9TyeJQ
+DJpRaioUTd2lGh4TLUxAxCxtUk/pascL+3Nn936LFmUCLxaxnbeGzPOXAhscCtU1H0nFsXRnKx5a
+cPXYSKFZZZktieSkww2Oi8dg2DYaQhGQMSFMVqgVfwEu4bvCRBvdSiNXdWGCZQmFVzBZZ/9rOLzP
+pvTFTPnpkavJm81FLlUhiE/oFgKlCDLWDknSpXAI0uZGERcwPca6xvIMh86LjQKjbVci9FYDStXC
+qRnqQ+TccSu/B6uONFsDEngGcXSKfB+ahttp://www.okta.com/exk5zt0r12Edi4rD20h7FsWGCBC+t/LaVkUKUvRQpzyZTmlxUzw4R9FOzXPPJRw=hS50WgYs/cn3uxmhrza/0/0QW3H7bwdjPZ2hQmG7IeSd7awTOghBqdrjvaPfQ7tRW+UK6ewMgIBVKG6jV3qYAWeW2U70hMb7hE9qJqBKyYyimmhVWULx1HB2YmlU1wmispywoPlXQ6gj0iWaL2RFI83vUp7X50eZ6dELqoJVZpzQI065Tt0TG7UuKUW1flYsbiS9NaXnuw+mcrBW25ZA9F5CLePHki01ZzUw+XtNmKthEb7SR30mzPoj08Dji22daYvGu82IR01wIZPoQJPCGMT6y2xC/pQPqGljAg/vUa+gaYgaMaAVYxhk/hfgMUBlOeKACBaGTmygab1Nz5KvPg==MIIDpDCCAoygAwIBAgIGAVLIBhAwMA0GCSqGSIb3DQEBBQUAMIGSMQswCQYDVQQGEwJVUzETMBEG
+A1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNU2FuIEZyYW5jaXNjbzENMAsGA1UECgwET2t0YTEU
+MBIGA1UECwwLU1NPUHJvdmlkZXIxEzARBgNVBAMMCmRldi0xMTY4MDcxHDAaBgkqhkiG9w0BCQEW
+DWluZm9Ab2t0YS5jb20wHhcNMTYwMjA5MjE1MjA2WhcNMjYwMjA5MjE1MzA2WjCBkjELMAkGA1UE
+BhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWExFjAUBgNVBAcMDVNhbiBGcmFuY2lzY28xDTALBgNV
+BAoMBE9rdGExFDASBgNVBAsMC1NTT1Byb3ZpZGVyMRMwEQYDVQQDDApkZXYtMTE2ODA3MRwwGgYJ
+KoZIhvcNAQkBFg1pbmZvQG9rdGEuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA
+mtjBOZ8MmhUyi8cGk4dUY6Fj1MFDt/q3FFiaQpLzu3/q5lRVUNUBbAtqQWwY10dzfZguHOuvA5p5
+QyiVDvUhe+XkVwN2R2WfArQJRTPnIcOaHrxqQf3o5cCIG21ZtysFHJSo8clPSOe+0VsoRgcJ1aF4
+2rODwgqRRZdO9Wh3502XlJ799DJQ23IC7XasKEsGKzJqhlRrfd/FyIuZT0sFHDKRz5snSJhm9gpN
+uQlCmk7ONZ1sXqtt+nBIfWIqeoYQubPW7pT5GTc7wouWq4TCjHJiK9k2HiyNxW0E3JX08swEZi2+
+LVDjgLzNc4lwjSYIj3AOtPZs8s606oBdIBni4wIDAQABMA0GCSqGSIb3DQEBBQUAA4IBAQBMxSkJ
+TxkXxsoKNW0awJNpWRbU81QpheMFfENIzLam4Itc/5kSZAaSy/9e2QKfo4jBo/MMbCq2vM9TyeJQ
+DJpRaioUTd2lGh4TLUxAxCxtUk/pascL+3Nn936LFmUCLxaxnbeGzPOXAhscCtU1H0nFsXRnKx5a
+cPXYSKFZZZktieSkww2Oi8dg2DYaQhGQMSFMVqgVfwEu4bvCRBvdSiNXdWGCZQmFVzBZZ/9rOLzP
+pvTFTPnpkavJm81FLlUhiE/oFgKlCDLWDknSpXAI0uZGERcwPca6xvIMh86LjQKjbVci9FYDStXC
+qRnqQ+TccSu/B6uONFsDEngGcXSKfB+aphoebe.simon@scaleft.com123urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransportPhoebeSimonphoebe.simon@scaleft.comphoebe.simon@scaleft.com`
+
+const assertionInfoResponse = `
+http://www.okta.com/exk5zt0r12Edi4rD20h7http://www.okta.com/exk5zt0r12Edi4rD20h7FsWGCBC+t/LaVkUKUvRQpzyZTmlxUzw4R9FOzXPPJRw=hS50WgYs/cn3uxmhrza/0/0QW3H7bwdjPZ2hQmG7IeSd7awTOghBqdrjvaPfQ7tRW+UK6ewMgIBVKG6jV3qYAWeW2U70hMb7hE9qJqBKyYyimmhVWULx1HB2YmlU1wmispywoPlXQ6gj0iWaL2RFI83vUp7X50eZ6dELqoJVZpzQI065Tt0TG7UuKUW1flYsbiS9NaXnuw+mcrBW25ZA9F5CLePHki01ZzUw+XtNmKthEb7SR30mzPoj08Dji22daYvGu82IR01wIZPoQJPCGMT6y2xC/pQPqGljAg/vUa+gaYgaMaAVYxhk/hfgMUBlOeKACBaGTmygab1Nz5KvPg==MIIDpDCCAoygAwIBAgIGAVLIBhAwMA0GCSqGSIb3DQEBBQUAMIGSMQswCQYDVQQGEwJVUzETMBEG
+A1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNU2FuIEZyYW5jaXNjbzENMAsGA1UECgwET2t0YTEU
+MBIGA1UECwwLU1NPUHJvdmlkZXIxEzARBgNVBAMMCmRldi0xMTY4MDcxHDAaBgkqhkiG9w0BCQEW
+DWluZm9Ab2t0YS5jb20wHhcNMTYwMjA5MjE1MjA2WhcNMjYwMjA5MjE1MzA2WjCBkjELMAkGA1UE
+BhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWExFjAUBgNVBAcMDVNhbiBGcmFuY2lzY28xDTALBgNV
+BAoMBE9rdGExFDASBgNVBAsMC1NTT1Byb3ZpZGVyMRMwEQYDVQQDDApkZXYtMTE2ODA3MRwwGgYJ
+KoZIhvcNAQkBFg1pbmZvQG9rdGEuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA
+mtjBOZ8MmhUyi8cGk4dUY6Fj1MFDt/q3FFiaQpLzu3/q5lRVUNUBbAtqQWwY10dzfZguHOuvA5p5
+QyiVDvUhe+XkVwN2R2WfArQJRTPnIcOaHrxqQf3o5cCIG21ZtysFHJSo8clPSOe+0VsoRgcJ1aF4
+2rODwgqRRZdO9Wh3502XlJ799DJQ23IC7XasKEsGKzJqhlRrfd/FyIuZT0sFHDKRz5snSJhm9gpN
+uQlCmk7ONZ1sXqtt+nBIfWIqeoYQubPW7pT5GTc7wouWq4TCjHJiK9k2HiyNxW0E3JX08swEZi2+
+LVDjgLzNc4lwjSYIj3AOtPZs8s606oBdIBni4wIDAQABMA0GCSqGSIb3DQEBBQUAA4IBAQBMxSkJ
+TxkXxsoKNW0awJNpWRbU81QpheMFfENIzLam4Itc/5kSZAaSy/9e2QKfo4jBo/MMbCq2vM9TyeJQ
+DJpRaioUTd2lGh4TLUxAxCxtUk/pascL+3Nn936LFmUCLxaxnbeGzPOXAhscCtU1H0nFsXRnKx5a
+cPXYSKFZZZktieSkww2Oi8dg2DYaQhGQMSFMVqgVfwEu4bvCRBvdSiNXdWGCZQmFVzBZZ/9rOLzP
+pvTFTPnpkavJm81FLlUhiE/oFgKlCDLWDknSpXAI0uZGERcwPca6xvIMh86LjQKjbVci9FYDStXC
+qRnqQ+TccSu/B6uONFsDEngGcXSKfB+aphoebe.simon@scaleft.com123urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransportPhoebeSimonphoebe.simon@scaleft.comphoebe.simon@scaleft.com`
+
+const assertionInfoModifiedAudienceResponse = `
+http://www.okta.com/exk5zt0r12Edi4rD20h7http://www.okta.com/exk5zt0r12Edi4rD20h7FsWGCBC+t/LaVkUKUvRQpzyZTmlxUzw4R9FOzXPPJRw=hS50WgYs/cn3uxmhrza/0/0QW3H7bwdjPZ2hQmG7IeSd7awTOghBqdrjvaPfQ7tRW+UK6ewMgIBVKG6jV3qYAWeW2U70hMb7hE9qJqBKyYyimmhVWULx1HB2YmlU1wmispywoPlXQ6gj0iWaL2RFI83vUp7X50eZ6dELqoJVZpzQI065Tt0TG7UuKUW1flYsbiS9NaXnuw+mcrBW25ZA9F5CLePHki01ZzUw+XtNmKthEb7SR30mzPoj08Dji22daYvGu82IR01wIZPoQJPCGMT6y2xC/pQPqGljAg/vUa+gaYgaMaAVYxhk/hfgMUBlOeKACBaGTmygab1Nz5KvPg==MIIDpDCCAoygAwIBAgIGAVLIBhAwMA0GCSqGSIb3DQEBBQUAMIGSMQswCQYDVQQGEwJVUzETMBEG
+A1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNU2FuIEZyYW5jaXNjbzENMAsGA1UECgwET2t0YTEU
+MBIGA1UECwwLU1NPUHJvdmlkZXIxEzARBgNVBAMMCmRldi0xMTY4MDcxHDAaBgkqhkiG9w0BCQEW
+DWluZm9Ab2t0YS5jb20wHhcNMTYwMjA5MjE1MjA2WhcNMjYwMjA5MjE1MzA2WjCBkjELMAkGA1UE
+BhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWExFjAUBgNVBAcMDVNhbiBGcmFuY2lzY28xDTALBgNV
+BAoMBE9rdGExFDASBgNVBAsMC1NTT1Byb3ZpZGVyMRMwEQYDVQQDDApkZXYtMTE2ODA3MRwwGgYJ
+KoZIhvcNAQkBFg1pbmZvQG9rdGEuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA
+mtjBOZ8MmhUyi8cGk4dUY6Fj1MFDt/q3FFiaQpLzu3/q5lRVUNUBbAtqQWwY10dzfZguHOuvA5p5
+QyiVDvUhe+XkVwN2R2WfArQJRTPnIcOaHrxqQf3o5cCIG21ZtysFHJSo8clPSOe+0VsoRgcJ1aF4
+2rODwgqRRZdO9Wh3502XlJ799DJQ23IC7XasKEsGKzJqhlRrfd/FyIuZT0sFHDKRz5snSJhm9gpN
+uQlCmk7ONZ1sXqtt+nBIfWIqeoYQubPW7pT5GTc7wouWq4TCjHJiK9k2HiyNxW0E3JX08swEZi2+
+LVDjgLzNc4lwjSYIj3AOtPZs8s606oBdIBni4wIDAQABMA0GCSqGSIb3DQEBBQUAA4IBAQBMxSkJ
+TxkXxsoKNW0awJNpWRbU81QpheMFfENIzLam4Itc/5kSZAaSy/9e2QKfo4jBo/MMbCq2vM9TyeJQ
+DJpRaioUTd2lGh4TLUxAxCxtUk/pascL+3Nn936LFmUCLxaxnbeGzPOXAhscCtU1H0nFsXRnKx5a
+cPXYSKFZZZktieSkww2Oi8dg2DYaQhGQMSFMVqgVfwEu4bvCRBvdSiNXdWGCZQmFVzBZZ/9rOLzP
+pvTFTPnpkavJm81FLlUhiE/oFgKlCDLWDknSpXAI0uZGERcwPca6xvIMh86LjQKjbVci9FYDStXC
+qRnqQ+TccSu/B6uONFsDEngGcXSKfB+aphoebe.simon@scaleft.com124urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransportPhoebeSimonphoebe.simon@scaleft.comphoebe.simon@scaleft.com`
+
+const assertionInfoOneTimeUseResponse = `
+http://www.okta.com/exk5zt0r12Edi4rD20h7http://www.okta.com/exk5zt0r12Edi4rD20h7FsWGCBC+t/LaVkUKUvRQpzyZTmlxUzw4R9FOzXPPJRw=hS50WgYs/cn3uxmhrza/0/0QW3H7bwdjPZ2hQmG7IeSd7awTOghBqdrjvaPfQ7tRW+UK6ewMgIBVKG6jV3qYAWeW2U70hMb7hE9qJqBKyYyimmhVWULx1HB2YmlU1wmispywoPlXQ6gj0iWaL2RFI83vUp7X50eZ6dELqoJVZpzQI065Tt0TG7UuKUW1flYsbiS9NaXnuw+mcrBW25ZA9F5CLePHki01ZzUw+XtNmKthEb7SR30mzPoj08Dji22daYvGu82IR01wIZPoQJPCGMT6y2xC/pQPqGljAg/vUa+gaYgaMaAVYxhk/hfgMUBlOeKACBaGTmygab1Nz5KvPg==MIIDpDCCAoygAwIBAgIGAVLIBhAwMA0GCSqGSIb3DQEBBQUAMIGSMQswCQYDVQQGEwJVUzETMBEG
+A1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNU2FuIEZyYW5jaXNjbzENMAsGA1UECgwET2t0YTEU
+MBIGA1UECwwLU1NPUHJvdmlkZXIxEzARBgNVBAMMCmRldi0xMTY4MDcxHDAaBgkqhkiG9w0BCQEW
+DWluZm9Ab2t0YS5jb20wHhcNMTYwMjA5MjE1MjA2WhcNMjYwMjA5MjE1MzA2WjCBkjELMAkGA1UE
+BhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWExFjAUBgNVBAcMDVNhbiBGcmFuY2lzY28xDTALBgNV
+BAoMBE9rdGExFDASBgNVBAsMC1NTT1Byb3ZpZGVyMRMwEQYDVQQDDApkZXYtMTE2ODA3MRwwGgYJ
+KoZIhvcNAQkBFg1pbmZvQG9rdGEuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA
+mtjBOZ8MmhUyi8cGk4dUY6Fj1MFDt/q3FFiaQpLzu3/q5lRVUNUBbAtqQWwY10dzfZguHOuvA5p5
+QyiVDvUhe+XkVwN2R2WfArQJRTPnIcOaHrxqQf3o5cCIG21ZtysFHJSo8clPSOe+0VsoRgcJ1aF4
+2rODwgqRRZdO9Wh3502XlJ799DJQ23IC7XasKEsGKzJqhlRrfd/FyIuZT0sFHDKRz5snSJhm9gpN
+uQlCmk7ONZ1sXqtt+nBIfWIqeoYQubPW7pT5GTc7wouWq4TCjHJiK9k2HiyNxW0E3JX08swEZi2+
+LVDjgLzNc4lwjSYIj3AOtPZs8s606oBdIBni4wIDAQABMA0GCSqGSIb3DQEBBQUAA4IBAQBMxSkJ
+TxkXxsoKNW0awJNpWRbU81QpheMFfENIzLam4Itc/5kSZAaSy/9e2QKfo4jBo/MMbCq2vM9TyeJQ
+DJpRaioUTd2lGh4TLUxAxCxtUk/pascL+3Nn936LFmUCLxaxnbeGzPOXAhscCtU1H0nFsXRnKx5a
+cPXYSKFZZZktieSkww2Oi8dg2DYaQhGQMSFMVqgVfwEu4bvCRBvdSiNXdWGCZQmFVzBZZ/9rOLzP
+pvTFTPnpkavJm81FLlUhiE/oFgKlCDLWDknSpXAI0uZGERcwPca6xvIMh86LjQKjbVci9FYDStXC
+qRnqQ+TccSu/B6uONFsDEngGcXSKfB+aphoebe.simon@scaleft.com123urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransportPhoebeSimonphoebe.simon@scaleft.comphoebe.simon@scaleft.com`
+
+const assertionInfoProxyRestrictionResponse = `
+http://www.okta.com/exk5zt0r12Edi4rD20h7http://www.okta.com/exk5zt0r12Edi4rD20h7FsWGCBC+t/LaVkUKUvRQpzyZTmlxUzw4R9FOzXPPJRw=hS50WgYs/cn3uxmhrza/0/0QW3H7bwdjPZ2hQmG7IeSd7awTOghBqdrjvaPfQ7tRW+UK6ewMgIBVKG6jV3qYAWeW2U70hMb7hE9qJqBKyYyimmhVWULx1HB2YmlU1wmispywoPlXQ6gj0iWaL2RFI83vUp7X50eZ6dELqoJVZpzQI065Tt0TG7UuKUW1flYsbiS9NaXnuw+mcrBW25ZA9F5CLePHki01ZzUw+XtNmKthEb7SR30mzPoj08Dji22daYvGu82IR01wIZPoQJPCGMT6y2xC/pQPqGljAg/vUa+gaYgaMaAVYxhk/hfgMUBlOeKACBaGTmygab1Nz5KvPg==MIIDpDCCAoygAwIBAgIGAVLIBhAwMA0GCSqGSIb3DQEBBQUAMIGSMQswCQYDVQQGEwJVUzETMBEG
+A1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNU2FuIEZyYW5jaXNjbzENMAsGA1UECgwET2t0YTEU
+MBIGA1UECwwLU1NPUHJvdmlkZXIxEzARBgNVBAMMCmRldi0xMTY4MDcxHDAaBgkqhkiG9w0BCQEW
+DWluZm9Ab2t0YS5jb20wHhcNMTYwMjA5MjE1MjA2WhcNMjYwMjA5MjE1MzA2WjCBkjELMAkGA1UE
+BhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWExFjAUBgNVBAcMDVNhbiBGcmFuY2lzY28xDTALBgNV
+BAoMBE9rdGExFDASBgNVBAsMC1NTT1Byb3ZpZGVyMRMwEQYDVQQDDApkZXYtMTE2ODA3MRwwGgYJ
+KoZIhvcNAQkBFg1pbmZvQG9rdGEuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA
+mtjBOZ8MmhUyi8cGk4dUY6Fj1MFDt/q3FFiaQpLzu3/q5lRVUNUBbAtqQWwY10dzfZguHOuvA5p5
+QyiVDvUhe+XkVwN2R2WfArQJRTPnIcOaHrxqQf3o5cCIG21ZtysFHJSo8clPSOe+0VsoRgcJ1aF4
+2rODwgqRRZdO9Wh3502XlJ799DJQ23IC7XasKEsGKzJqhlRrfd/FyIuZT0sFHDKRz5snSJhm9gpN
+uQlCmk7ONZ1sXqtt+nBIfWIqeoYQubPW7pT5GTc7wouWq4TCjHJiK9k2HiyNxW0E3JX08swEZi2+
+LVDjgLzNc4lwjSYIj3AOtPZs8s606oBdIBni4wIDAQABMA0GCSqGSIb3DQEBBQUAA4IBAQBMxSkJ
+TxkXxsoKNW0awJNpWRbU81QpheMFfENIzLam4Itc/5kSZAaSy/9e2QKfo4jBo/MMbCq2vM9TyeJQ
+DJpRaioUTd2lGh4TLUxAxCxtUk/pascL+3Nn936LFmUCLxaxnbeGzPOXAhscCtU1H0nFsXRnKx5a
+cPXYSKFZZZktieSkww2Oi8dg2DYaQhGQMSFMVqgVfwEu4bvCRBvdSiNXdWGCZQmFVzBZZ/9rOLzP
+pvTFTPnpkavJm81FLlUhiE/oFgKlCDLWDknSpXAI0uZGERcwPca6xvIMh86LjQKjbVci9FYDStXC
+qRnqQ+TccSu/B6uONFsDEngGcXSKfB+aphoebe.simon@scaleft.com123123urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransportPhoebeSimonphoebe.simon@scaleft.comphoebe.simon@scaleft.com`
+
+const assertionInfoProxyRestrictionNoCountResponse = `
+http://www.okta.com/exk5zt0r12Edi4rD20h7http://www.okta.com/exk5zt0r12Edi4rD20h7FsWGCBC+t/LaVkUKUvRQpzyZTmlxUzw4R9FOzXPPJRw=hS50WgYs/cn3uxmhrza/0/0QW3H7bwdjPZ2hQmG7IeSd7awTOghBqdrjvaPfQ7tRW+UK6ewMgIBVKG6jV3qYAWeW2U70hMb7hE9qJqBKyYyimmhVWULx1HB2YmlU1wmispywoPlXQ6gj0iWaL2RFI83vUp7X50eZ6dELqoJVZpzQI065Tt0TG7UuKUW1flYsbiS9NaXnuw+mcrBW25ZA9F5CLePHki01ZzUw+XtNmKthEb7SR30mzPoj08Dji22daYvGu82IR01wIZPoQJPCGMT6y2xC/pQPqGljAg/vUa+gaYgaMaAVYxhk/hfgMUBlOeKACBaGTmygab1Nz5KvPg==MIIDpDCCAoygAwIBAgIGAVLIBhAwMA0GCSqGSIb3DQEBBQUAMIGSMQswCQYDVQQGEwJVUzETMBEG
+A1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNU2FuIEZyYW5jaXNjbzENMAsGA1UECgwET2t0YTEU
+MBIGA1UECwwLU1NPUHJvdmlkZXIxEzARBgNVBAMMCmRldi0xMTY4MDcxHDAaBgkqhkiG9w0BCQEW
+DWluZm9Ab2t0YS5jb20wHhcNMTYwMjA5MjE1MjA2WhcNMjYwMjA5MjE1MzA2WjCBkjELMAkGA1UE
+BhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWExFjAUBgNVBAcMDVNhbiBGcmFuY2lzY28xDTALBgNV
+BAoMBE9rdGExFDASBgNVBAsMC1NTT1Byb3ZpZGVyMRMwEQYDVQQDDApkZXYtMTE2ODA3MRwwGgYJ
+KoZIhvcNAQkBFg1pbmZvQG9rdGEuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA
+mtjBOZ8MmhUyi8cGk4dUY6Fj1MFDt/q3FFiaQpLzu3/q5lRVUNUBbAtqQWwY10dzfZguHOuvA5p5
+QyiVDvUhe+XkVwN2R2WfArQJRTPnIcOaHrxqQf3o5cCIG21ZtysFHJSo8clPSOe+0VsoRgcJ1aF4
+2rODwgqRRZdO9Wh3502XlJ799DJQ23IC7XasKEsGKzJqhlRrfd/FyIuZT0sFHDKRz5snSJhm9gpN
+uQlCmk7ONZ1sXqtt+nBIfWIqeoYQubPW7pT5GTc7wouWq4TCjHJiK9k2HiyNxW0E3JX08swEZi2+
+LVDjgLzNc4lwjSYIj3AOtPZs8s606oBdIBni4wIDAQABMA0GCSqGSIb3DQEBBQUAA4IBAQBMxSkJ
+TxkXxsoKNW0awJNpWRbU81QpheMFfENIzLam4Itc/5kSZAaSy/9e2QKfo4jBo/MMbCq2vM9TyeJQ
+DJpRaioUTd2lGh4TLUxAxCxtUk/pascL+3Nn936LFmUCLxaxnbeGzPOXAhscCtU1H0nFsXRnKx5a
+cPXYSKFZZZktieSkww2Oi8dg2DYaQhGQMSFMVqgVfwEu4bvCRBvdSiNXdWGCZQmFVzBZZ/9rOLzP
+pvTFTPnpkavJm81FLlUhiE/oFgKlCDLWDknSpXAI0uZGERcwPca6xvIMh86LjQKjbVci9FYDStXC
+qRnqQ+TccSu/B6uONFsDEngGcXSKfB+aphoebe.simon@scaleft.com123123urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransportPhoebeSimonphoebe.simon@scaleft.comphoebe.simon@scaleft.com`
+
+const assertionInfoProxyRestrictionNoAudienceResponse = `
+http://www.okta.com/exk5zt0r12Edi4rD20h7http://www.okta.com/exk5zt0r12Edi4rD20h7FsWGCBC+t/LaVkUKUvRQpzyZTmlxUzw4R9FOzXPPJRw=hS50WgYs/cn3uxmhrza/0/0QW3H7bwdjPZ2hQmG7IeSd7awTOghBqdrjvaPfQ7tRW+UK6ewMgIBVKG6jV3qYAWeW2U70hMb7hE9qJqBKyYyimmhVWULx1HB2YmlU1wmispywoPlXQ6gj0iWaL2RFI83vUp7X50eZ6dELqoJVZpzQI065Tt0TG7UuKUW1flYsbiS9NaXnuw+mcrBW25ZA9F5CLePHki01ZzUw+XtNmKthEb7SR30mzPoj08Dji22daYvGu82IR01wIZPoQJPCGMT6y2xC/pQPqGljAg/vUa+gaYgaMaAVYxhk/hfgMUBlOeKACBaGTmygab1Nz5KvPg==MIIDpDCCAoygAwIBAgIGAVLIBhAwMA0GCSqGSIb3DQEBBQUAMIGSMQswCQYDVQQGEwJVUzETMBEG
+A1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNU2FuIEZyYW5jaXNjbzENMAsGA1UECgwET2t0YTEU
+MBIGA1UECwwLU1NPUHJvdmlkZXIxEzARBgNVBAMMCmRldi0xMTY4MDcxHDAaBgkqhkiG9w0BCQEW
+DWluZm9Ab2t0YS5jb20wHhcNMTYwMjA5MjE1MjA2WhcNMjYwMjA5MjE1MzA2WjCBkjELMAkGA1UE
+BhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWExFjAUBgNVBAcMDVNhbiBGcmFuY2lzY28xDTALBgNV
+BAoMBE9rdGExFDASBgNVBAsMC1NTT1Byb3ZpZGVyMRMwEQYDVQQDDApkZXYtMTE2ODA3MRwwGgYJ
+KoZIhvcNAQkBFg1pbmZvQG9rdGEuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA
+mtjBOZ8MmhUyi8cGk4dUY6Fj1MFDt/q3FFiaQpLzu3/q5lRVUNUBbAtqQWwY10dzfZguHOuvA5p5
+QyiVDvUhe+XkVwN2R2WfArQJRTPnIcOaHrxqQf3o5cCIG21ZtysFHJSo8clPSOe+0VsoRgcJ1aF4
+2rODwgqRRZdO9Wh3502XlJ799DJQ23IC7XasKEsGKzJqhlRrfd/FyIuZT0sFHDKRz5snSJhm9gpN
+uQlCmk7ONZ1sXqtt+nBIfWIqeoYQubPW7pT5GTc7wouWq4TCjHJiK9k2HiyNxW0E3JX08swEZi2+
+LVDjgLzNc4lwjSYIj3AOtPZs8s606oBdIBni4wIDAQABMA0GCSqGSIb3DQEBBQUAA4IBAQBMxSkJ
+TxkXxsoKNW0awJNpWRbU81QpheMFfENIzLam4Itc/5kSZAaSy/9e2QKfo4jBo/MMbCq2vM9TyeJQ
+DJpRaioUTd2lGh4TLUxAxCxtUk/pascL+3Nn936LFmUCLxaxnbeGzPOXAhscCtU1H0nFsXRnKx5a
+cPXYSKFZZZktieSkww2Oi8dg2DYaQhGQMSFMVqgVfwEu4bvCRBvdSiNXdWGCZQmFVzBZZ/9rOLzP
+pvTFTPnpkavJm81FLlUhiE/oFgKlCDLWDknSpXAI0uZGERcwPca6xvIMh86LjQKjbVci9FYDStXC
+qRnqQ+TccSu/B6uONFsDEngGcXSKfB+aphoebe.simon@scaleft.com123urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransportPhoebeSimonphoebe.simon@scaleft.comphoebe.simon@scaleft.com`
+
+const exampleBase64 = `PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz48c2FtbDJwOlJlc3BvbnNlIHhtbG5zOnNhbWwycD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOnByb3RvY29sIiBEZXN0aW5hdGlvbj0iaHR0cDovL2xvY2FsaG9zdDo4MDgwL3YxL19zYW1sX2NhbGxiYWNrIiBJRD0iaWQxMDM1MzI4MDQ2NDc3ODc5NzUzODEzMjUiIEluUmVzcG9uc2VUbz0iXzg2OTljNjU1LWM0ODItNDUxYS05YjdmLTYxNjY4ZjE0MGI0NyIgSXNzdWVJbnN0YW50PSIyMDE2LTAzLTE2VDAxOjAyOjU3LjY4MloiIFZlcnNpb249IjIuMCI+PHNhbWwyOklzc3VlciB4bWxuczpzYW1sMj0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmFzc2VydGlvbiIgRm9ybWF0PSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6bmFtZWlkLWZvcm1hdDplbnRpdHkiPmh0dHA6Ly93d3cub2t0YS5jb20vZXhrNXp0MHIxMkVkaTRyRDIwaDc8L3NhbWwyOklzc3Vlcj48ZHM6U2lnbmF0dXJlIHhtbG5zOmRzPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwLzA5L3htbGRzaWcjIj48ZHM6U2lnbmVkSW5mbz48ZHM6Q2Fub25pY2FsaXphdGlvbk1ldGhvZCBBbGdvcml0aG09Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvMTAveG1sLWV4Yy1jMTRuIyIvPjxkczpTaWduYXR1cmVNZXRob2QgQWxnb3JpdGhtPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxLzA0L3htbGRzaWctbW9yZSNyc2Etc2hhMjU2Ii8+PGRzOlJlZmVyZW5jZSBVUkk9IiNpZDEwMzUzMjgwNDY0Nzc4Nzk3NTM4MTMyNSI+PGRzOlRyYW5zZm9ybXM+PGRzOlRyYW5zZm9ybSBBbGdvcml0aG09Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvMDkveG1sZHNpZyNlbnZlbG9wZWQtc2lnbmF0dXJlIi8+PGRzOlRyYW5zZm9ybSBBbGdvcml0aG09Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvMTAveG1sLWV4Yy1jMTRuIyIvPjwvZHM6VHJhbnNmb3Jtcz48ZHM6RGlnZXN0TWV0aG9kIEFsZ29yaXRobT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS8wNC94bWxlbmMjc2hhMjU2Ii8+PGRzOkRpZ2VzdFZhbHVlPm5wVEFsNmtyYWtzQmxDUmx1bmJ5RDZuSUNUY2ZzRGFIalBYVnhvRFBydzA9PC9kczpEaWdlc3RWYWx1ZT48L2RzOlJlZmVyZW5jZT48L2RzOlNpZ25lZEluZm8+PGRzOlNpZ25hdHVyZVZhbHVlPlNiQjAzZkkxVFZzdEo3cTFCNlh4OFlSR2tEcE5ROGFyNHpGM3AzYWlra2NxOFRUUzBlUjI4Rm9RdU4xSFg3MlBuMnJjY0U0T05pellOUzYvcnZybHlWL1NsWFhtQzltaFRMUlBlSno1bXJ4anFPNVFZRDFZM0l6bW5rZlE2S3V0dWtrY0dPSkVwYTN2WWVzZjVKS1JTKzBXR1J0ek9TNHdKRjE4b0dJWitiYThQNmd4bU1yeUE4eEIvZUpneHBmcm1VYkJqUEhMU2ZsamViaDg4RWlOSUQwODhYdVNHeWQrM0RtcFc1QjUyRFFCOGNBeXlPQlJrUlJjcUxGSWd4aWJtdnRJaWVxdVUwYTJuY29qcHUwKzRvamwrNHdEQ1dkR09FeXF0Sm9UUVhDNHNLUmFVNzlGSzVJRmZFaVlNcXZpRkQwb2F1NHNQajBnbkZDRUY1Rmw0dz09PC9kczpTaWduYXR1cmVWYWx1ZT48ZHM6S2V5SW5mbz48ZHM6WDUwOURhdGE+PGRzOlg1MDlDZXJ0aWZpY2F0ZT5NSUlEcERDQ0FveWdBd0lCQWdJR0FWTElCaEF3TUEwR0NTcUdTSWIzRFFFQkJRVUFNSUdTTVFzd0NRWURWUVFHRXdKVlV6RVRNQkVHCkExVUVDQXdLUTJGc2FXWnZjbTVwWVRFV01CUUdBMVVFQnd3TlUyRnVJRVp5WVc1amFYTmpiekVOTUFzR0ExVUVDZ3dFVDJ0MFlURVUKTUJJR0ExVUVDd3dMVTFOUFVISnZkbWxrWlhJeEV6QVJCZ05WQkFNTUNtUmxkaTB4TVRZNE1EY3hIREFhQmdrcWhraUc5dzBCQ1FFVwpEV2x1Wm05QWIydDBZUzVqYjIwd0hoY05NVFl3TWpBNU1qRTFNakEyV2hjTk1qWXdNakE1TWpFMU16QTJXakNCa2pFTE1Ba0dBMVVFCkJoTUNWVk14RXpBUkJnTlZCQWdNQ2tOaGJHbG1iM0p1YVdFeEZqQVVCZ05WQkFjTURWTmhiaUJHY21GdVkybHpZMjh4RFRBTEJnTlYKQkFvTUJFOXJkR0V4RkRBU0JnTlZCQXNNQzFOVFQxQnliM1pwWkdWeU1STXdFUVlEVlFRRERBcGtaWFl0TVRFMk9EQTNNUnd3R2dZSgpLb1pJaHZjTkFRa0JGZzFwYm1adlFHOXJkR0V1WTI5dE1JSUJJakFOQmdrcWhraUc5dzBCQVFFRkFBT0NBUThBTUlJQkNnS0NBUUVBCm10akJPWjhNbWhVeWk4Y0drNGRVWTZGajFNRkR0L3EzRkZpYVFwTHp1My9xNWxSVlVOVUJiQXRxUVd3WTEwZHpmWmd1SE91dkE1cDUKUXlpVkR2VWhlK1hrVndOMlIyV2ZBclFKUlRQbkljT2FIcnhxUWYzbzVjQ0lHMjFadHlzRkhKU284Y2xQU09lKzBWc29SZ2NKMWFGNAoyck9Ed2dxUlJaZE85V2gzNTAyWGxKNzk5REpRMjNJQzdYYXNLRXNHS3pKcWhsUnJmZC9GeUl1WlQwc0ZIREtSejVzblNKaG05Z3BOCnVRbENtazdPTloxc1hxdHQrbkJJZldJcWVvWVF1YlBXN3BUNUdUYzd3b3VXcTRUQ2pISmlLOWsySGl5TnhXMEUzSlgwOHN3RVppMisKTFZEamdMek5jNGx3alNZSWozQU90UFpzOHM2MDZvQmRJQm5pNHdJREFRQUJNQTBHQ1NxR1NJYjNEUUVCQlFVQUE0SUJBUUJNeFNrSgpUeGtYeHNvS05XMGF3Sk5wV1JiVTgxUXBoZU1GZkVOSXpMYW00SXRjLzVrU1pBYVN5LzllMlFLZm80akJvL01NYkNxMnZNOVR5ZUpRCkRKcFJhaW9VVGQybEdoNFRMVXhBeEN4dFVrL3Bhc2NMKzNObjkzNkxGbVVDTHhheG5iZUd6UE9YQWhzY0N0VTFIMG5Gc1hSbkt4NWEKY1BYWVNLRlpaWmt0aWVTa3d3Mk9pOGRnMkRZYVFoR1FNU0ZNVnFnVmZ3RXU0YnZDUkJ2ZFNpTlhkV0dDWlFtRlZ6QlpaLzlyT0x6UApwdlRGVFBucGthdkptODFGTGxVaGlFL29GZ0tsQ0RMV0RrblNwWEFJMHVaR0VSY3dQY2E2eHZJTWg4NkxqUUtqYlZjaTlGWURTdFhDCnFSbnFRK1RjY1N1L0I2dU9ORnNERW5nR2NYU0tmQithPC9kczpYNTA5Q2VydGlmaWNhdGU+PC9kczpYNTA5RGF0YT48L2RzOktleUluZm8+PC9kczpTaWduYXR1cmU+PHNhbWwycDpTdGF0dXMgeG1sbnM6c2FtbDJwPSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6cHJvdG9jb2wiPjxzYW1sMnA6U3RhdHVzQ29kZSBWYWx1ZT0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOnN0YXR1czpTdWNjZXNzIi8+PC9zYW1sMnA6U3RhdHVzPjxzYW1sMjpBc3NlcnRpb24geG1sbnM6c2FtbDI9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDphc3NlcnRpb24iIElEPSJpZDEwMzUzMjgwNDY1MjY1ODg5MDAwODk0MjQiIElzc3VlSW5zdGFudD0iMjAxNi0wMy0xNlQwMTowMjo1Ny42ODJaIiBWZXJzaW9uPSIyLjAiPjxzYW1sMjpJc3N1ZXIgRm9ybWF0PSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6bmFtZWlkLWZvcm1hdDplbnRpdHkiIHhtbG5zOnNhbWwyPSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6YXNzZXJ0aW9uIj5odHRwOi8vd3d3Lm9rdGEuY29tL2V4azV6dDByMTJFZGk0ckQyMGg3PC9zYW1sMjpJc3N1ZXI+PGRzOlNpZ25hdHVyZSB4bWxuczpkcz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC8wOS94bWxkc2lnIyI+PGRzOlNpZ25lZEluZm8+PGRzOkNhbm9uaWNhbGl6YXRpb25NZXRob2QgQWxnb3JpdGhtPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxLzEwL3htbC1leGMtYzE0biMiLz48ZHM6U2lnbmF0dXJlTWV0aG9kIEFsZ29yaXRobT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS8wNC94bWxkc2lnLW1vcmUjcnNhLXNoYTI1NiIvPjxkczpSZWZlcmVuY2UgVVJJPSIjaWQxMDM1MzI4MDQ2NTI2NTg4OTAwMDg5NDI0Ij48ZHM6VHJhbnNmb3Jtcz48ZHM6VHJhbnNmb3JtIEFsZ29yaXRobT0iaHR0cDovL3d3dy53My5vcmcvMjAwMC8wOS94bWxkc2lnI2VudmVsb3BlZC1zaWduYXR1cmUiLz48ZHM6VHJhbnNmb3JtIEFsZ29yaXRobT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS8xMC94bWwtZXhjLWMxNG4jIi8+PC9kczpUcmFuc2Zvcm1zPjxkczpEaWdlc3RNZXRob2QgQWxnb3JpdGhtPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxLzA0L3htbGVuYyNzaGEyNTYiLz48ZHM6RGlnZXN0VmFsdWU+Tm8xVnlRbGs4WGlmNEZpSitoYVZpd0VReVNJekJhMTRsR3kwY29DbjBjOD08L2RzOkRpZ2VzdFZhbHVlPjwvZHM6UmVmZXJlbmNlPjwvZHM6U2lnbmVkSW5mbz48ZHM6U2lnbmF0dXJlVmFsdWU+VlNWOFZ3NDdxN24vWFp3YVFPUFdRZUtJNVpBNjlmbkdaeUVGaGV4NHh1YUlmQytMT1luZmQ4cThxY1pzbTFNNmt2NDdIL2RSNllYUklNalBLWFpleVgvTUtjbUdQQ2FkcVdGVDdFV0Z2enVPL3V5L0FCL0NMNVpDUWlZOUgvYU9oRHlzTzhnbHNlMVMrWTJLMEN3dnNvUndNZkZpTzJYT1loVk9zbmdVU2tDQmRMSUI2T3E0Zitac0swcncvRTc5bjlRVWQ4b3dEcTNkVkMxOFNGWVlkY0lWRGhRcHBnbHl1QkVaZnUydEcwNmdEOWpsczdaRTh2amNNZkhtaHVIdHhsSDNvdk5MQjM1TkZPL1ZyQ05kRnFtRDc2R25FQTk4Zm9pSnhDWDh2ek5IRjRyUFVGWEFFZGlTNE9kUUF4YjdqTk5Wb0tWWXVhZHVuTHlneXNaR1NnPT08L2RzOlNpZ25hdHVyZVZhbHVlPjxkczpLZXlJbmZvPjxkczpYNTA5RGF0YT48ZHM6WDUwOUNlcnRpZmljYXRlPk1JSURwRENDQW95Z0F3SUJBZ0lHQVZMSUJoQXdNQTBHQ1NxR1NJYjNEUUVCQlFVQU1JR1NNUXN3Q1FZRFZRUUdFd0pWVXpFVE1CRUcKQTFVRUNBd0tRMkZzYVdadmNtNXBZVEVXTUJRR0ExVUVCd3dOVTJGdUlFWnlZVzVqYVhOamJ6RU5NQXNHQTFVRUNnd0VUMnQwWVRFVQpNQklHQTFVRUN3d0xVMU5QVUhKdmRtbGtaWEl4RXpBUkJnTlZCQU1NQ21SbGRpMHhNVFk0TURjeEhEQWFCZ2txaGtpRzl3MEJDUUVXCkRXbHVabTlBYjJ0MFlTNWpiMjB3SGhjTk1UWXdNakE1TWpFMU1qQTJXaGNOTWpZd01qQTVNakUxTXpBMldqQ0JrakVMTUFrR0ExVUUKQmhNQ1ZWTXhFekFSQmdOVkJBZ01Da05oYkdsbWIzSnVhV0V4RmpBVUJnTlZCQWNNRFZOaGJpQkdjbUZ1WTJselkyOHhEVEFMQmdOVgpCQW9NQkU5cmRHRXhGREFTQmdOVkJBc01DMU5UVDFCeWIzWnBaR1Z5TVJNd0VRWURWUVFEREFwa1pYWXRNVEUyT0RBM01Sd3dHZ1lKCktvWklodmNOQVFrQkZnMXBibVp2UUc5cmRHRXVZMjl0TUlJQklqQU5CZ2txaGtpRzl3MEJBUUVGQUFPQ0FROEFNSUlCQ2dLQ0FRRUEKbXRqQk9aOE1taFV5aThjR2s0ZFVZNkZqMU1GRHQvcTNGRmlhUXBMenUzL3E1bFJWVU5VQmJBdHFRV3dZMTBkemZaZ3VIT3V2QTVwNQpReWlWRHZVaGUrWGtWd04yUjJXZkFyUUpSVFBuSWNPYUhyeHFRZjNvNWNDSUcyMVp0eXNGSEpTbzhjbFBTT2UrMFZzb1JnY0oxYUY0CjJyT0R3Z3FSUlpkTzlXaDM1MDJYbEo3OTlESlEyM0lDN1hhc0tFc0dLekpxaGxScmZkL0Z5SXVaVDBzRkhES1J6NXNuU0pobTlncE4KdVFsQ21rN09OWjFzWHF0dCtuQklmV0lxZW9ZUXViUFc3cFQ1R1RjN3dvdVdxNFRDakhKaUs5azJIaXlOeFcwRTNKWDA4c3dFWmkyKwpMVkRqZ0x6TmM0bHdqU1lJajNBT3RQWnM4czYwNm9CZElCbmk0d0lEQVFBQk1BMEdDU3FHU0liM0RRRUJCUVVBQTRJQkFRQk14U2tKClR4a1h4c29LTlcwYXdKTnBXUmJVODFRcGhlTUZmRU5JekxhbTRJdGMvNWtTWkFhU3kvOWUyUUtmbzRqQm8vTU1iQ3Eydk05VHllSlEKREpwUmFpb1VUZDJsR2g0VExVeEF4Q3h0VWsvcGFzY0wrM05uOTM2TEZtVUNMeGF4bmJlR3pQT1hBaHNjQ3RVMUgwbkZzWFJuS3g1YQpjUFhZU0tGWlpaa3RpZVNrd3cyT2k4ZGcyRFlhUWhHUU1TRk1WcWdWZndFdTRidkNSQnZkU2lOWGRXR0NaUW1GVnpCWlovOXJPTHpQCnB2VEZUUG5wa2F2Sm04MUZMbFVoaUUvb0ZnS2xDRExXRGtuU3BYQUkwdVpHRVJjd1BjYTZ4dklNaDg2TGpRS2piVmNpOUZZRFN0WEMKcVJucVErVGNjU3UvQjZ1T05Gc0RFbmdHY1hTS2ZCK2E8L2RzOlg1MDlDZXJ0aWZpY2F0ZT48L2RzOlg1MDlEYXRhPjwvZHM6S2V5SW5mbz48L2RzOlNpZ25hdHVyZT48c2FtbDI6U3ViamVjdCB4bWxuczpzYW1sMj0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmFzc2VydGlvbiI+PHNhbWwyOk5hbWVJRCBGb3JtYXQ9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjEuMTpuYW1laWQtZm9ybWF0OnVuc3BlY2lmaWVkIj5ydXNzZWxsLmhhZXJpbmdAc2NhbGVmdC5jb208L3NhbWwyOk5hbWVJRD48c2FtbDI6U3ViamVjdENvbmZpcm1hdGlvbiBNZXRob2Q9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDpjbTpiZWFyZXIiPjxzYW1sMjpTdWJqZWN0Q29uZmlybWF0aW9uRGF0YSBJblJlc3BvbnNlVG89Il84Njk5YzY1NS1jNDgyLTQ1MWEtOWI3Zi02MTY2OGYxNDBiNDciIE5vdE9uT3JBZnRlcj0iMjAxNi0wMy0xNlQwMTowNzo1Ny42ODJaIiBSZWNpcGllbnQ9Imh0dHA6Ly9sb2NhbGhvc3Q6ODA4MC92MS9fc2FtbF9jYWxsYmFjayIvPjwvc2FtbDI6U3ViamVjdENvbmZpcm1hdGlvbj48L3NhbWwyOlN1YmplY3Q+PHNhbWwyOkNvbmRpdGlvbnMgTm90QmVmb3JlPSIyMDE2LTAzLTE2VDAwOjU3OjU3LjY4MloiIE5vdE9uT3JBZnRlcj0iMjAxNi0wMy0xNlQwMTowNzo1Ny42ODJaIiB4bWxuczpzYW1sMj0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmFzc2VydGlvbiI+PHNhbWwyOkF1ZGllbmNlUmVzdHJpY3Rpb24+PHNhbWwyOkF1ZGllbmNlPjEyMzwvc2FtbDI6QXVkaWVuY2U+PC9zYW1sMjpBdWRpZW5jZVJlc3RyaWN0aW9uPjwvc2FtbDI6Q29uZGl0aW9ucz48c2FtbDI6QXV0aG5TdGF0ZW1lbnQgQXV0aG5JbnN0YW50PSIyMDE2LTAzLTE2VDAxOjAyOjU3LjY4MloiIFNlc3Npb25JbmRleD0iXzg2OTljNjU1LWM0ODItNDUxYS05YjdmLTYxNjY4ZjE0MGI0NyIgeG1sbnM6c2FtbDI9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDphc3NlcnRpb24iPjxzYW1sMjpBdXRobkNvbnRleHQ+PHNhbWwyOkF1dGhuQ29udGV4dENsYXNzUmVmPnVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDphYzpjbGFzc2VzOlBhc3N3b3JkUHJvdGVjdGVkVHJhbnNwb3J0PC9zYW1sMjpBdXRobkNvbnRleHRDbGFzc1JlZj48L3NhbWwyOkF1dGhuQ29udGV4dD48L3NhbWwyOkF1dGhuU3RhdGVtZW50Pjwvc2FtbDI6QXNzZXJ0aW9uPjwvc2FtbDJwOlJlc3BvbnNlPg==`
+
+const exampleBase64_2 = `PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz48c2FtbDJwOlJlc3BvbnNlIHhtbG5zOnNhbWwycD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOnByb3RvY29sIiBEZXN0aW5hdGlvbj0iaHR0cDovL2xvY2FsaG9zdDo4MDgwL3YxL19zYW1sX2NhbGxiYWNrIiBJRD0iaWQyMTI4MjQ4OTI5NTEwNjcwODM0NTU5MTg1IiBJblJlc3BvbnNlVG89Il9kYTIxM2RmOC1lZjk1LTQxZDAtYjliZi03MWQyNzE3MzVjZDciIElzc3VlSW5zdGFudD0iMjAxNi0wMy0yOFQxNjozODoxOC41NjVaIiBWZXJzaW9uPSIyLjAiIHhtbG5zOnhzPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxL1hNTFNjaGVtYSI+PHNhbWwyOklzc3VlciB4bWxuczpzYW1sMj0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmFzc2VydGlvbiIgRm9ybWF0PSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6bmFtZWlkLWZvcm1hdDplbnRpdHkiPmh0dHA6Ly93d3cub2t0YS5jb20vZXhrNXp0MHIxMkVkaTRyRDIwaDc8L3NhbWwyOklzc3Vlcj48ZHM6U2lnbmF0dXJlIHhtbG5zOmRzPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwLzA5L3htbGRzaWcjIj48ZHM6U2lnbmVkSW5mbz48ZHM6Q2Fub25pY2FsaXphdGlvbk1ldGhvZCBBbGdvcml0aG09Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvMTAveG1sLWV4Yy1jMTRuIyIvPjxkczpTaWduYXR1cmVNZXRob2QgQWxnb3JpdGhtPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxLzA0L3htbGRzaWctbW9yZSNyc2Etc2hhMjU2Ii8+PGRzOlJlZmVyZW5jZSBVUkk9IiNpZDIxMjgyNDg5Mjk1MTA2NzA4MzQ1NTkxODUiPjxkczpUcmFuc2Zvcm1zPjxkczpUcmFuc2Zvcm0gQWxnb3JpdGhtPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwLzA5L3htbGRzaWcjZW52ZWxvcGVkLXNpZ25hdHVyZSIvPjxkczpUcmFuc2Zvcm0gQWxnb3JpdGhtPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxLzEwL3htbC1leGMtYzE0biMiPjxlYzpJbmNsdXNpdmVOYW1lc3BhY2VzIHhtbG5zOmVjPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxLzEwL3htbC1leGMtYzE0biMiIFByZWZpeExpc3Q9InhzIi8+PC9kczpUcmFuc2Zvcm0+PC9kczpUcmFuc2Zvcm1zPjxkczpEaWdlc3RNZXRob2QgQWxnb3JpdGhtPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxLzA0L3htbGVuYyNzaGEyNTYiLz48ZHM6RGlnZXN0VmFsdWU+V3ZnVy9KZlA0bWpVKy8xd3R5WDA2RTlFR3hZTnNvQ1UrcmJTWm5Bdmoycz08L2RzOkRpZ2VzdFZhbHVlPjwvZHM6UmVmZXJlbmNlPjwvZHM6U2lnbmVkSW5mbz48ZHM6U2lnbmF0dXJlVmFsdWU+R0ExVVJvTU9FNUVGZmtIWWltR1htN0VjcGgvbTBzMTM1VnlGOVd1dDZOU3B1WmRRMmNyTTFJc2x2S0NSamtFMDlyWmdhZ1FRTUFUaFVjT0Z1WDM1ZFpQejlKNElocHQxanVoZkd2MUFWOEk4amlPS0ZFVGo2NU1pUGFiREVpOCtQNllXZjRxTnVqQUpYSEtKSWEvTUZYQnFvS1IvaW1MUVQ4ZXUxbmhWQlFHWXFXd1plUGRkZlhPMkpZazJjZTdtdG55TVQwZFVWYitvK3RsRURZYTdyaTlmajRKTC96MVhYN3lyYlZaeG4ybWRLUEp0U1NQOHVITk9XU002ajF2cDRvSytLU0R2aUJmaVZMbFZBNThub3o1R3lGdHA2NDJoK0xWMnF1S2JuY01GZm5mQjFrZkhMSy94YXo5VWFEQnkrYkhLNG9HelNwVmhacWNPenpsaUtBPT08L2RzOlNpZ25hdHVyZVZhbHVlPjxkczpLZXlJbmZvPjxkczpYNTA5RGF0YT48ZHM6WDUwOUNlcnRpZmljYXRlPk1JSURwRENDQW95Z0F3SUJBZ0lHQVZMSUJoQXdNQTBHQ1NxR1NJYjNEUUVCQlFVQU1JR1NNUXN3Q1FZRFZRUUdFd0pWVXpFVE1CRUcKQTFVRUNBd0tRMkZzYVdadmNtNXBZVEVXTUJRR0ExVUVCd3dOVTJGdUlFWnlZVzVqYVhOamJ6RU5NQXNHQTFVRUNnd0VUMnQwWVRFVQpNQklHQTFVRUN3d0xVMU5QVUhKdmRtbGtaWEl4RXpBUkJnTlZCQU1NQ21SbGRpMHhNVFk0TURjeEhEQWFCZ2txaGtpRzl3MEJDUUVXCkRXbHVabTlBYjJ0MFlTNWpiMjB3SGhjTk1UWXdNakE1TWpFMU1qQTJXaGNOTWpZd01qQTVNakUxTXpBMldqQ0JrakVMTUFrR0ExVUUKQmhNQ1ZWTXhFekFSQmdOVkJBZ01Da05oYkdsbWIzSnVhV0V4RmpBVUJnTlZCQWNNRFZOaGJpQkdjbUZ1WTJselkyOHhEVEFMQmdOVgpCQW9NQkU5cmRHRXhGREFTQmdOVkJBc01DMU5UVDFCeWIzWnBaR1Z5TVJNd0VRWURWUVFEREFwa1pYWXRNVEUyT0RBM01Sd3dHZ1lKCktvWklodmNOQVFrQkZnMXBibVp2UUc5cmRHRXVZMjl0TUlJQklqQU5CZ2txaGtpRzl3MEJBUUVGQUFPQ0FROEFNSUlCQ2dLQ0FRRUEKbXRqQk9aOE1taFV5aThjR2s0ZFVZNkZqMU1GRHQvcTNGRmlhUXBMenUzL3E1bFJWVU5VQmJBdHFRV3dZMTBkemZaZ3VIT3V2QTVwNQpReWlWRHZVaGUrWGtWd04yUjJXZkFyUUpSVFBuSWNPYUhyeHFRZjNvNWNDSUcyMVp0eXNGSEpTbzhjbFBTT2UrMFZzb1JnY0oxYUY0CjJyT0R3Z3FSUlpkTzlXaDM1MDJYbEo3OTlESlEyM0lDN1hhc0tFc0dLekpxaGxScmZkL0Z5SXVaVDBzRkhES1J6NXNuU0pobTlncE4KdVFsQ21rN09OWjFzWHF0dCtuQklmV0lxZW9ZUXViUFc3cFQ1R1RjN3dvdVdxNFRDakhKaUs5azJIaXlOeFcwRTNKWDA4c3dFWmkyKwpMVkRqZ0x6TmM0bHdqU1lJajNBT3RQWnM4czYwNm9CZElCbmk0d0lEQVFBQk1BMEdDU3FHU0liM0RRRUJCUVVBQTRJQkFRQk14U2tKClR4a1h4c29LTlcwYXdKTnBXUmJVODFRcGhlTUZmRU5JekxhbTRJdGMvNWtTWkFhU3kvOWUyUUtmbzRqQm8vTU1iQ3Eydk05VHllSlEKREpwUmFpb1VUZDJsR2g0VExVeEF4Q3h0VWsvcGFzY0wrM05uOTM2TEZtVUNMeGF4bmJlR3pQT1hBaHNjQ3RVMUgwbkZzWFJuS3g1YQpjUFhZU0tGWlpaa3RpZVNrd3cyT2k4ZGcyRFlhUWhHUU1TRk1WcWdWZndFdTRidkNSQnZkU2lOWGRXR0NaUW1GVnpCWlovOXJPTHpQCnB2VEZUUG5wa2F2Sm04MUZMbFVoaUUvb0ZnS2xDRExXRGtuU3BYQUkwdVpHRVJjd1BjYTZ4dklNaDg2TGpRS2piVmNpOUZZRFN0WEMKcVJucVErVGNjU3UvQjZ1T05Gc0RFbmdHY1hTS2ZCK2E8L2RzOlg1MDlDZXJ0aWZpY2F0ZT48L2RzOlg1MDlEYXRhPjwvZHM6S2V5SW5mbz48L2RzOlNpZ25hdHVyZT48c2FtbDJwOlN0YXR1cyB4bWxuczpzYW1sMnA9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDpwcm90b2NvbCI+PHNhbWwycDpTdGF0dXNDb2RlIFZhbHVlPSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6c3RhdHVzOlN1Y2Nlc3MiLz48L3NhbWwycDpTdGF0dXM+PHNhbWwyOkFzc2VydGlvbiB4bWxuczpzYW1sMj0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmFzc2VydGlvbiIgSUQ9ImlkMjEyODI0ODkyOTU3NzY3ODIxMjY0NjgzMTkiIElzc3VlSW5zdGFudD0iMjAxNi0wMy0yOFQxNjozODoxOC41NjVaIiBWZXJzaW9uPSIyLjAiIHhtbG5zOnhzPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxL1hNTFNjaGVtYSI+PHNhbWwyOklzc3VlciBGb3JtYXQ9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDpuYW1laWQtZm9ybWF0OmVudGl0eSIgeG1sbnM6c2FtbDI9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDphc3NlcnRpb24iPmh0dHA6Ly93d3cub2t0YS5jb20vZXhrNXp0MHIxMkVkaTRyRDIwaDc8L3NhbWwyOklzc3Vlcj48ZHM6U2lnbmF0dXJlIHhtbG5zOmRzPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwLzA5L3htbGRzaWcjIj48ZHM6U2lnbmVkSW5mbz48ZHM6Q2Fub25pY2FsaXphdGlvbk1ldGhvZCBBbGdvcml0aG09Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvMTAveG1sLWV4Yy1jMTRuIyIvPjxkczpTaWduYXR1cmVNZXRob2QgQWxnb3JpdGhtPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxLzA0L3htbGRzaWctbW9yZSNyc2Etc2hhMjU2Ii8+PGRzOlJlZmVyZW5jZSBVUkk9IiNpZDIxMjgyNDg5Mjk1Nzc2NzgyMTI2NDY4MzE5Ij48ZHM6VHJhbnNmb3Jtcz48ZHM6VHJhbnNmb3JtIEFsZ29yaXRobT0iaHR0cDovL3d3dy53My5vcmcvMjAwMC8wOS94bWxkc2lnI2VudmVsb3BlZC1zaWduYXR1cmUiLz48ZHM6VHJhbnNmb3JtIEFsZ29yaXRobT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS8xMC94bWwtZXhjLWMxNG4jIj48ZWM6SW5jbHVzaXZlTmFtZXNwYWNlcyB4bWxuczplYz0iaHR0cDovL3d3dy53My5vcmcvMjAwMS8xMC94bWwtZXhjLWMxNG4jIiBQcmVmaXhMaXN0PSJ4cyIvPjwvZHM6VHJhbnNmb3JtPjwvZHM6VHJhbnNmb3Jtcz48ZHM6RGlnZXN0TWV0aG9kIEFsZ29yaXRobT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS8wNC94bWxlbmMjc2hhMjU2Ii8+PGRzOkRpZ2VzdFZhbHVlPkZzV0dDQkMrdC9MYVZrVUtVdlJRcHp5WlRtbHhVenc0UjlGT3pYUFBKUnc9PC9kczpEaWdlc3RWYWx1ZT48L2RzOlJlZmVyZW5jZT48L2RzOlNpZ25lZEluZm8+PGRzOlNpZ25hdHVyZVZhbHVlPmhTNTBXZ1lzL2NuM3V4bWhyemEvMC8wUVczSDdid2RqUFoyaFFtRzdJZVNkN2F3VE9naEJxZHJqdmFQZlE3dFJXK1VLNmV3TWdJQlZLRzZqVjNxWUFXZVcyVTcwaE1iN2hFOXFKcUJLeVl5aW1taFZXVUx4MUhCMlltbFUxd21pc3B5d29QbFhRNmdqMGlXYUwyUkZJODN2VXA3WDUwZVo2ZEVMcW9KVlpwelFJMDY1VHQwVEc3VXVLVVcxZmxZc2JpUzlOYVhudXcrbWNyQlcyNVpBOUY1Q0xlUEhraTAxWnpVdytYdE5tS3RoRWI3U1IzMG16UG9qMDhEamkyMmRhWXZHdTgySVIwMXdJWlBvUUpQQ0dNVDZ5MnhDL3BRUHFHbGpBZy92VWErZ2FZZ2FNYUFWWXhoay9oZmdNVUJsT2VLQUNCYUdUbXlnYWIxTno1S3ZQZz09PC9kczpTaWduYXR1cmVWYWx1ZT48ZHM6S2V5SW5mbz48ZHM6WDUwOURhdGE+PGRzOlg1MDlDZXJ0aWZpY2F0ZT5NSUlEcERDQ0FveWdBd0lCQWdJR0FWTElCaEF3TUEwR0NTcUdTSWIzRFFFQkJRVUFNSUdTTVFzd0NRWURWUVFHRXdKVlV6RVRNQkVHCkExVUVDQXdLUTJGc2FXWnZjbTVwWVRFV01CUUdBMVVFQnd3TlUyRnVJRVp5WVc1amFYTmpiekVOTUFzR0ExVUVDZ3dFVDJ0MFlURVUKTUJJR0ExVUVDd3dMVTFOUFVISnZkbWxrWlhJeEV6QVJCZ05WQkFNTUNtUmxkaTB4TVRZNE1EY3hIREFhQmdrcWhraUc5dzBCQ1FFVwpEV2x1Wm05QWIydDBZUzVqYjIwd0hoY05NVFl3TWpBNU1qRTFNakEyV2hjTk1qWXdNakE1TWpFMU16QTJXakNCa2pFTE1Ba0dBMVVFCkJoTUNWVk14RXpBUkJnTlZCQWdNQ2tOaGJHbG1iM0p1YVdFeEZqQVVCZ05WQkFjTURWTmhiaUJHY21GdVkybHpZMjh4RFRBTEJnTlYKQkFvTUJFOXJkR0V4RkRBU0JnTlZCQXNNQzFOVFQxQnliM1pwWkdWeU1STXdFUVlEVlFRRERBcGtaWFl0TVRFMk9EQTNNUnd3R2dZSgpLb1pJaHZjTkFRa0JGZzFwYm1adlFHOXJkR0V1WTI5dE1JSUJJakFOQmdrcWhraUc5dzBCQVFFRkFBT0NBUThBTUlJQkNnS0NBUUVBCm10akJPWjhNbWhVeWk4Y0drNGRVWTZGajFNRkR0L3EzRkZpYVFwTHp1My9xNWxSVlVOVUJiQXRxUVd3WTEwZHpmWmd1SE91dkE1cDUKUXlpVkR2VWhlK1hrVndOMlIyV2ZBclFKUlRQbkljT2FIcnhxUWYzbzVjQ0lHMjFadHlzRkhKU284Y2xQU09lKzBWc29SZ2NKMWFGNAoyck9Ed2dxUlJaZE85V2gzNTAyWGxKNzk5REpRMjNJQzdYYXNLRXNHS3pKcWhsUnJmZC9GeUl1WlQwc0ZIREtSejVzblNKaG05Z3BOCnVRbENtazdPTloxc1hxdHQrbkJJZldJcWVvWVF1YlBXN3BUNUdUYzd3b3VXcTRUQ2pISmlLOWsySGl5TnhXMEUzSlgwOHN3RVppMisKTFZEamdMek5jNGx3alNZSWozQU90UFpzOHM2MDZvQmRJQm5pNHdJREFRQUJNQTBHQ1NxR1NJYjNEUUVCQlFVQUE0SUJBUUJNeFNrSgpUeGtYeHNvS05XMGF3Sk5wV1JiVTgxUXBoZU1GZkVOSXpMYW00SXRjLzVrU1pBYVN5LzllMlFLZm80akJvL01NYkNxMnZNOVR5ZUpRCkRKcFJhaW9VVGQybEdoNFRMVXhBeEN4dFVrL3Bhc2NMKzNObjkzNkxGbVVDTHhheG5iZUd6UE9YQWhzY0N0VTFIMG5Gc1hSbkt4NWEKY1BYWVNLRlpaWmt0aWVTa3d3Mk9pOGRnMkRZYVFoR1FNU0ZNVnFnVmZ3RXU0YnZDUkJ2ZFNpTlhkV0dDWlFtRlZ6QlpaLzlyT0x6UApwdlRGVFBucGthdkptODFGTGxVaGlFL29GZ0tsQ0RMV0RrblNwWEFJMHVaR0VSY3dQY2E2eHZJTWg4NkxqUUtqYlZjaTlGWURTdFhDCnFSbnFRK1RjY1N1L0I2dU9ORnNERW5nR2NYU0tmQithPC9kczpYNTA5Q2VydGlmaWNhdGU+PC9kczpYNTA5RGF0YT48L2RzOktleUluZm8+PC9kczpTaWduYXR1cmU+PHNhbWwyOlN1YmplY3QgeG1sbnM6c2FtbDI9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDphc3NlcnRpb24iPjxzYW1sMjpOYW1lSUQgRm9ybWF0PSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoxLjE6bmFtZWlkLWZvcm1hdDplbWFpbEFkZHJlc3MiPnBob2ViZS5zaW1vbkBzY2FsZWZ0LmNvbTwvc2FtbDI6TmFtZUlEPjxzYW1sMjpTdWJqZWN0Q29uZmlybWF0aW9uIE1ldGhvZD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmNtOmJlYXJlciI+PHNhbWwyOlN1YmplY3RDb25maXJtYXRpb25EYXRhIEluUmVzcG9uc2VUbz0iX2RhMjEzZGY4LWVmOTUtNDFkMC1iOWJmLTcxZDI3MTczNWNkNyIgTm90T25PckFmdGVyPSIyMDE2LTAzLTI4VDE2OjQzOjE4LjU2NVoiIFJlY2lwaWVudD0iaHR0cDovL2xvY2FsaG9zdDo4MDgwL3YxL19zYW1sX2NhbGxiYWNrIi8+PC9zYW1sMjpTdWJqZWN0Q29uZmlybWF0aW9uPjwvc2FtbDI6U3ViamVjdD48c2FtbDI6Q29uZGl0aW9ucyBOb3RCZWZvcmU9IjIwMTYtMDMtMjhUMTY6MzM6MTguNTY1WiIgTm90T25PckFmdGVyPSIyMDE2LTAzLTI4VDE2OjQzOjE4LjU2NVoiIHhtbG5zOnNhbWwyPSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6YXNzZXJ0aW9uIj48c2FtbDI6QXVkaWVuY2VSZXN0cmljdGlvbj48c2FtbDI6QXVkaWVuY2U+MTIzPC9zYW1sMjpBdWRpZW5jZT48L3NhbWwyOkF1ZGllbmNlUmVzdHJpY3Rpb24+PC9zYW1sMjpDb25kaXRpb25zPjxzYW1sMjpBdXRoblN0YXRlbWVudCBBdXRobkluc3RhbnQ9IjIwMTYtMDMtMjhUMTY6Mzg6MTguNTY1WiIgU2Vzc2lvbkluZGV4PSJfZGEyMTNkZjgtZWY5NS00MWQwLWI5YmYtNzFkMjcxNzM1Y2Q3IiB4bWxuczpzYW1sMj0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmFzc2VydGlvbiI+PHNhbWwyOkF1dGhuQ29udGV4dD48c2FtbDI6QXV0aG5Db250ZXh0Q2xhc3NSZWY+dXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmFjOmNsYXNzZXM6UGFzc3dvcmRQcm90ZWN0ZWRUcmFuc3BvcnQ8L3NhbWwyOkF1dGhuQ29udGV4dENsYXNzUmVmPjwvc2FtbDI6QXV0aG5Db250ZXh0Pjwvc2FtbDI6QXV0aG5TdGF0ZW1lbnQ+PHNhbWwyOkF0dHJpYnV0ZVN0YXRlbWVudCB4bWxuczpzYW1sMj0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmFzc2VydGlvbiI+PHNhbWwyOkF0dHJpYnV0ZSBOYW1lPSJGaXJzdE5hbWUiIE5hbWVGb3JtYXQ9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDphdHRybmFtZS1mb3JtYXQ6dW5zcGVjaWZpZWQiPjxzYW1sMjpBdHRyaWJ1dGVWYWx1ZSB4bWxuczp4cz0iaHR0cDovL3d3dy53My5vcmcvMjAwMS9YTUxTY2hlbWEiIHhtbG5zOnhzaT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS9YTUxTY2hlbWEtaW5zdGFuY2UiIHhzaTp0eXBlPSJ4czpzdHJpbmciPlBob2ViZTwvc2FtbDI6QXR0cmlidXRlVmFsdWU+PC9zYW1sMjpBdHRyaWJ1dGU+PHNhbWwyOkF0dHJpYnV0ZSBOYW1lPSJMYXN0TmFtZSIgTmFtZUZvcm1hdD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmF0dHJuYW1lLWZvcm1hdDp1bnNwZWNpZmllZCI+PHNhbWwyOkF0dHJpYnV0ZVZhbHVlIHhtbG5zOnhzPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxL1hNTFNjaGVtYSIgeG1sbnM6eHNpPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxL1hNTFNjaGVtYS1pbnN0YW5jZSIgeHNpOnR5cGU9InhzOnN0cmluZyI+U2ltb248L3NhbWwyOkF0dHJpYnV0ZVZhbHVlPjwvc2FtbDI6QXR0cmlidXRlPjxzYW1sMjpBdHRyaWJ1dGUgTmFtZT0iRW1haWwiIE5hbWVGb3JtYXQ9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDphdHRybmFtZS1mb3JtYXQ6dW5zcGVjaWZpZWQiPjxzYW1sMjpBdHRyaWJ1dGVWYWx1ZSB4bWxuczp4cz0iaHR0cDovL3d3dy53My5vcmcvMjAwMS9YTUxTY2hlbWEiIHhtbG5zOnhzaT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS9YTUxTY2hlbWEtaW5zdGFuY2UiIHhzaTp0eXBlPSJ4czpzdHJpbmciPnBob2ViZS5zaW1vbkBzY2FsZWZ0LmNvbTwvc2FtbDI6QXR0cmlidXRlVmFsdWU+PC9zYW1sMjpBdHRyaWJ1dGU+PHNhbWwyOkF0dHJpYnV0ZSBOYW1lPSJMb2dpbiIgTmFtZUZvcm1hdD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmF0dHJuYW1lLWZvcm1hdDp1bnNwZWNpZmllZCI+PHNhbWwyOkF0dHJpYnV0ZVZhbHVlIHhtbG5zOnhzPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxL1hNTFNjaGVtYSIgeG1sbnM6eHNpPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxL1hNTFNjaGVtYS1pbnN0YW5jZSIgeHNpOnR5cGU9InhzOnN0cmluZyI+cGhvZWJlLnNpbW9uQHNjYWxlZnQuY29tPC9zYW1sMjpBdHRyaWJ1dGVWYWx1ZT48L3NhbWwyOkF0dHJpYnV0ZT48L3NhbWwyOkF0dHJpYnV0ZVN0YXRlbWVudD48L3NhbWwyOkFzc2VydGlvbj48L3NhbWwycDpSZXNwb25zZT4=`
+
+const commentInjectionAttackResponse = `
+http://www.okta.com/exk5zt0r12Edi4rD20h7http://www.okta.com/exk5zt0r12Edi4rD20h7FsWGCBC+t/LaVkUKUvRQpzyZTmlxUzw4R9FOzXPPJRw=hS50WgYs/cn3uxmhrza/0/0QW3H7bwdjPZ2hQmG7IeSd7awTOghBqdrjvaPfQ7tRW+UK6ewMgIBVKG6jV3qYAWeW2U70hMb7hE9qJqBKyYyimmhVWULx1HB2YmlU1wmispywoPlXQ6gj0iWaL2RFI83vUp7X50eZ6dELqoJVZpzQI065Tt0TG7UuKUW1flYsbiS9NaXnuw+mcrBW25ZA9F5CLePHki01ZzUw+XtNmKthEb7SR30mzPoj08Dji22daYvGu82IR01wIZPoQJPCGMT6y2xC/pQPqGljAg/vUa+gaYgaMaAVYxhk/hfgMUBlOeKACBaGTmygab1Nz5KvPg==MIIDpDCCAoygAwIBAgIGAVLIBhAwMA0GCSqGSIb3DQEBBQUAMIGSMQswCQYDVQQGEwJVUzETMBEG
+A1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNU2FuIEZyYW5jaXNjbzENMAsGA1UECgwET2t0YTEU
+MBIGA1UECwwLU1NPUHJvdmlkZXIxEzARBgNVBAMMCmRldi0xMTY4MDcxHDAaBgkqhkiG9w0BCQEW
+DWluZm9Ab2t0YS5jb20wHhcNMTYwMjA5MjE1MjA2WhcNMjYwMjA5MjE1MzA2WjCBkjELMAkGA1UE
+BhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWExFjAUBgNVBAcMDVNhbiBGcmFuY2lzY28xDTALBgNV
+BAoMBE9rdGExFDASBgNVBAsMC1NTT1Byb3ZpZGVyMRMwEQYDVQQDDApkZXYtMTE2ODA3MRwwGgYJ
+KoZIhvcNAQkBFg1pbmZvQG9rdGEuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA
+mtjBOZ8MmhUyi8cGk4dUY6Fj1MFDt/q3FFiaQpLzu3/q5lRVUNUBbAtqQWwY10dzfZguHOuvA5p5
+QyiVDvUhe+XkVwN2R2WfArQJRTPnIcOaHrxqQf3o5cCIG21ZtysFHJSo8clPSOe+0VsoRgcJ1aF4
+2rODwgqRRZdO9Wh3502XlJ799DJQ23IC7XasKEsGKzJqhlRrfd/FyIuZT0sFHDKRz5snSJhm9gpN
+uQlCmk7ONZ1sXqtt+nBIfWIqeoYQubPW7pT5GTc7wouWq4TCjHJiK9k2HiyNxW0E3JX08swEZi2+
+LVDjgLzNc4lwjSYIj3AOtPZs8s606oBdIBni4wIDAQABMA0GCSqGSIb3DQEBBQUAA4IBAQBMxSkJ
+TxkXxsoKNW0awJNpWRbU81QpheMFfENIzLam4Itc/5kSZAaSy/9e2QKfo4jBo/MMbCq2vM9TyeJQ
+DJpRaioUTd2lGh4TLUxAxCxtUk/pascL+3Nn936LFmUCLxaxnbeGzPOXAhscCtU1H0nFsXRnKx5a
+cPXYSKFZZZktieSkww2Oi8dg2DYaQhGQMSFMVqgVfwEu4bvCRBvdSiNXdWGCZQmFVzBZZ/9rOLzP
+pvTFTPnpkavJm81FLlUhiE/oFgKlCDLWDknSpXAI0uZGERcwPca6xvIMh86LjQKjbVci9FYDStXC
+qRnqQ+TccSu/B6uONFsDEngGcXSKfB+aphoebe.simon@scaleft.com.evil.com123urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransportPhoebeSimonphoebe.simon@scaleft.comphoebesimon`
diff --git a/vendor/github.com/mattermost/gosaml2/types/encrypted_assertion.go b/vendor/github.com/mattermost/gosaml2/types/encrypted_assertion.go
new file mode 100644
index 0000000000..9bc056d497
--- /dev/null
+++ b/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
+}
diff --git a/vendor/github.com/mattermost/gosaml2/types/encrypted_key.go b/vendor/github.com/mattermost/gosaml2/types/encrypted_key.go
new file mode 100644
index 0000000000..dde68a8ed0
--- /dev/null
+++ b/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")
+}
diff --git a/vendor/github.com/mattermost/gosaml2/types/metadata.go b/vendor/github.com/mattermost/gosaml2/types/metadata.go
new file mode 100644
index 0000000000..a7099181fd
--- /dev/null
+++ b/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"`
+}
diff --git a/vendor/github.com/mattermost/gosaml2/types/response.go b/vendor/github.com/mattermost/gosaml2/types/response.go
new file mode 100644
index 0000000000..4f338f03d3
--- /dev/null
+++ b/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"`
+}
diff --git a/vendor/github.com/mattermost/gosaml2/uuid/uuid.go b/vendor/github.com/mattermost/gosaml2/uuid/uuid.go
new file mode 100644
index 0000000000..e3765189a3
--- /dev/null
+++ b/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:])
+}
diff --git a/vendor/github.com/mattermost/gosaml2/validate.go b/vendor/github.com/mattermost/gosaml2/validate.go
new file mode 100644
index 0000000000..7c85524f22
--- /dev/null
+++ b/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
+}
diff --git a/vendor/github.com/mattermost/gosaml2/xml_constants.go b/vendor/github.com/mattermost/gosaml2/xml_constants.go
new file mode 100644
index 0000000000..f5062f41aa
--- /dev/null
+++ b/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"
+)
diff --git a/vendor/github.com/russellhaering/goxmldsig/.gitignore b/vendor/github.com/russellhaering/goxmldsig/.gitignore
new file mode 100644
index 0000000000..9ed3b07cef
--- /dev/null
+++ b/vendor/github.com/russellhaering/goxmldsig/.gitignore
@@ -0,0 +1 @@
+*.test
diff --git a/vendor/github.com/russellhaering/goxmldsig/.travis.yml b/vendor/github.com/russellhaering/goxmldsig/.travis.yml
new file mode 100644
index 0000000000..689db99221
--- /dev/null
+++ b/vendor/github.com/russellhaering/goxmldsig/.travis.yml
@@ -0,0 +1,9 @@
+language: go
+
+go:
+ - "1.6.x"
+ - "1.7.x"
+ - "1.8.x"
+ - "1.9.x"
+ - "1.10.x"
+ - master
diff --git a/vendor/github.com/russellhaering/goxmldsig/LICENSE b/vendor/github.com/russellhaering/goxmldsig/LICENSE
new file mode 100644
index 0000000000..67db858821
--- /dev/null
+++ b/vendor/github.com/russellhaering/goxmldsig/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.
diff --git a/vendor/github.com/russellhaering/goxmldsig/README.md b/vendor/github.com/russellhaering/goxmldsig/README.md
new file mode 100644
index 0000000000..5fc3bdbceb
--- /dev/null
+++ b/vendor/github.com/russellhaering/goxmldsig/README.md
@@ -0,0 +1,90 @@
+# goxmldsig
+
+[](https://travis-ci.org/russellhaering/goxmldsig)
+[](https://godoc.org/github.com/russellhaering/goxmldsig)
+
+XML Digital Signatures implemented in pure Go.
+
+## Installation
+
+Install `goxmldsig` into your `$GOPATH` using `go get`:
+
+```
+$ go get github.com/russellhaering/goxmldsig
+```
+
+## Usage
+
+### Signing
+
+```go
+package main
+
+import (
+ "github.com/beevik/etree"
+ "github.com/russellhaering/goxmldsig"
+)
+
+func main() {
+ // Generate a key and self-signed certificate for signing
+ randomKeyStore := dsig.RandomKeyStoreForTest()
+ ctx := dsig.NewDefaultSigningContext(randomKeyStore)
+ elementToSign := &etree.Element{
+ Tag: "ExampleElement",
+ }
+ elementToSign.CreateAttr("ID", "id1234")
+
+ // Sign the element
+ signedElement, err := ctx.SignEnveloped(elementToSign)
+ if err != nil {
+ panic(err)
+ }
+
+ // Serialize the signed element. It is important not to modify the element
+ // after it has been signed - even pretty-printing the XML will invalidate
+ // the signature.
+ doc := etree.NewDocument()
+ doc.SetRoot(signedElement)
+ str, err := doc.WriteToString()
+ if err != nil {
+ panic(err)
+ }
+
+ println(str)
+}
+```
+
+### Signature Validation
+
+```go
+// Validate an element against a root certificate
+func validate(root *x509.Certificate, el *etree.Element) {
+ // Construct a signing context with one or more roots of trust.
+ ctx := dsig.NewDefaultValidationContext(&dsig.MemoryX509CertificateStore{
+ Roots: []*x509.Certificate{root},
+ })
+
+ // It is important to only use the returned validated element.
+ // See: https://www.w3.org/TR/xmldsig-bestpractices/#check-what-is-signed
+ validated, err := ctx.Validate(el)
+ if err != nil {
+ panic(err)
+ }
+
+ doc := etree.NewDocument()
+ doc.SetRoot(validated)
+ str, err := doc.WriteToString()
+ if err != nil {
+ panic(err)
+ }
+
+ println(str)
+}
+```
+
+## Limitations
+
+This library was created in order to [implement SAML 2.0](https://github.com/russellhaering/gosaml2)
+without needing to execute a command line tool to create and validate signatures. It currently
+only implements the subset of relevant standards needed to support that implementation, but
+I hope to make it more complete over time. Contributions are welcome.
diff --git a/vendor/github.com/russellhaering/goxmldsig/canonicalize.go b/vendor/github.com/russellhaering/goxmldsig/canonicalize.go
new file mode 100644
index 0000000000..5510191926
--- /dev/null
+++ b/vendor/github.com/russellhaering/goxmldsig/canonicalize.go
@@ -0,0 +1,162 @@
+package dsig
+
+import (
+ "sort"
+
+ "github.com/beevik/etree"
+ "github.com/russellhaering/goxmldsig/etreeutils"
+)
+
+// Canonicalizer is an implementation of a canonicalization algorithm.
+type Canonicalizer interface {
+ Canonicalize(el *etree.Element) ([]byte, error)
+ Algorithm() AlgorithmID
+}
+
+type c14N10ExclusiveCanonicalizer struct {
+ prefixList string
+}
+
+// MakeC14N10ExclusiveCanonicalizerWithPrefixList constructs an exclusive Canonicalizer
+// from a PrefixList in NMTOKENS format (a white space separated list).
+func MakeC14N10ExclusiveCanonicalizerWithPrefixList(prefixList string) Canonicalizer {
+ return &c14N10ExclusiveCanonicalizer{
+ prefixList: prefixList,
+ }
+}
+
+// Canonicalize transforms the input Element into a serialized XML document in canonical form.
+func (c *c14N10ExclusiveCanonicalizer) Canonicalize(el *etree.Element) ([]byte, error) {
+ err := etreeutils.TransformExcC14n(el, c.prefixList)
+ if err != nil {
+ return nil, err
+ }
+
+ return canonicalSerialize(el)
+}
+
+func (c *c14N10ExclusiveCanonicalizer) Algorithm() AlgorithmID {
+ return CanonicalXML10ExclusiveAlgorithmId
+}
+
+type c14N11Canonicalizer struct{}
+
+// MakeC14N11Canonicalizer constructs an inclusive canonicalizer.
+func MakeC14N11Canonicalizer() Canonicalizer {
+ return &c14N11Canonicalizer{}
+}
+
+// Canonicalize transforms the input Element into a serialized XML document in canonical form.
+func (c *c14N11Canonicalizer) Canonicalize(el *etree.Element) ([]byte, error) {
+ scope := make(map[string]struct{})
+ return canonicalSerialize(canonicalPrep(el, scope))
+}
+
+func (c *c14N11Canonicalizer) Algorithm() AlgorithmID {
+ return CanonicalXML11AlgorithmId
+}
+
+type c14N10RecCanonicalizer struct{}
+
+// MakeC14N10RecCanonicalizer constructs an inclusive canonicalizer.
+func MakeC14N10RecCanonicalizer() Canonicalizer {
+ return &c14N10RecCanonicalizer{}
+}
+
+// Canonicalize transforms the input Element into a serialized XML document in canonical form.
+func (c *c14N10RecCanonicalizer) Canonicalize(el *etree.Element) ([]byte, error) {
+ scope := make(map[string]struct{})
+ return canonicalSerialize(canonicalPrep(el, scope))
+}
+
+func (c *c14N10RecCanonicalizer) Algorithm() AlgorithmID {
+ return CanonicalXML10RecAlgorithmId
+}
+
+type c14N10CommentCanonicalizer struct{}
+
+// MakeC14N10CommentCanonicalizer constructs an inclusive canonicalizer.
+func MakeC14N10CommentCanonicalizer() Canonicalizer {
+ return &c14N10CommentCanonicalizer{}
+}
+
+// Canonicalize transforms the input Element into a serialized XML document in canonical form.
+func (c *c14N10CommentCanonicalizer) Canonicalize(el *etree.Element) ([]byte, error) {
+ scope := make(map[string]struct{})
+ return canonicalSerialize(canonicalPrep(el, scope))
+}
+
+func (c *c14N10CommentCanonicalizer) Algorithm() AlgorithmID {
+ return CanonicalXML10CommentAlgorithmId
+}
+
+func composeAttr(space, key string) string {
+ if space != "" {
+ return space + ":" + key
+ }
+
+ return key
+}
+
+type c14nSpace struct {
+ a etree.Attr
+ used bool
+}
+
+const nsSpace = "xmlns"
+
+// canonicalPrep accepts an *etree.Element and transforms it into one which is ready
+// for serialization into inclusive canonical form. Specifically this
+// entails:
+//
+// 1. Stripping re-declarations of namespaces
+// 2. Sorting attributes into canonical order
+//
+// Inclusive canonicalization does not strip unused namespaces.
+//
+// TODO(russell_h): This is very similar to excCanonicalPrep - perhaps they should
+// be unified into one parameterized function?
+func canonicalPrep(el *etree.Element, seenSoFar map[string]struct{}) *etree.Element {
+ _seenSoFar := make(map[string]struct{})
+ for k, v := range seenSoFar {
+ _seenSoFar[k] = v
+ }
+
+ ne := el.Copy()
+ sort.Sort(etreeutils.SortedAttrs(ne.Attr))
+ if len(ne.Attr) != 0 {
+ for _, attr := range ne.Attr {
+ if attr.Space != nsSpace {
+ continue
+ }
+ key := attr.Space + ":" + attr.Key
+ if _, seen := _seenSoFar[key]; seen {
+ ne.RemoveAttr(attr.Space + ":" + attr.Key)
+ } else {
+ _seenSoFar[key] = struct{}{}
+ }
+ }
+ }
+
+ for i, token := range ne.Child {
+ childElement, ok := token.(*etree.Element)
+ if ok {
+ ne.Child[i] = canonicalPrep(childElement, _seenSoFar)
+ }
+ }
+
+ return ne
+}
+
+func canonicalSerialize(el *etree.Element) ([]byte, error) {
+ doc := etree.NewDocument()
+ doc.SetRoot(el.Copy())
+
+ doc.WriteSettings = etree.WriteSettings{
+ CanonicalAttrVal: true,
+ CanonicalEndTags: true,
+ CanonicalText: true,
+ }
+
+ return doc.WriteToBytes()
+}
diff --git a/vendor/github.com/russellhaering/goxmldsig/clock.go b/vendor/github.com/russellhaering/goxmldsig/clock.go
new file mode 100644
index 0000000000..cceaaa5460
--- /dev/null
+++ b/vendor/github.com/russellhaering/goxmldsig/clock.go
@@ -0,0 +1,55 @@
+package dsig
+
+import (
+ "time"
+
+ "github.com/jonboulle/clockwork"
+)
+
+// Clock wraps a clockwork.Clock (which could be real or fake) in order
+// to default to a real clock when a nil *Clock is used. In other words,
+// if you attempt to use a nil *Clock it will defer to the real system
+// clock. This allows Clock to be easily added to structs with methods
+// that currently reference the time package, without requiring every
+// instantiation of that struct to be updated.
+type Clock struct {
+ wrapped clockwork.Clock
+}
+
+func (c *Clock) getWrapped() clockwork.Clock {
+ if c == nil {
+ return clockwork.NewRealClock()
+ }
+
+ return c.wrapped
+}
+
+func (c *Clock) After(d time.Duration) <-chan time.Time {
+ return c.getWrapped().After(d)
+}
+
+func (c *Clock) Sleep(d time.Duration) {
+ c.getWrapped().Sleep(d)
+}
+
+func (c *Clock) Now() time.Time {
+ return c.getWrapped().Now()
+}
+
+func NewRealClock() *Clock {
+ return &Clock{
+ wrapped: clockwork.NewRealClock(),
+ }
+}
+
+func NewFakeClock(wrapped clockwork.Clock) *Clock {
+ return &Clock{
+ wrapped: wrapped,
+ }
+}
+
+func NewFakeClockAt(t time.Time) *Clock {
+ return &Clock{
+ wrapped: clockwork.NewFakeClockAt(t),
+ }
+}
diff --git a/vendor/github.com/russellhaering/goxmldsig/etreeutils/canonicalize.go b/vendor/github.com/russellhaering/goxmldsig/etreeutils/canonicalize.go
new file mode 100644
index 0000000000..e9f8deb18a
--- /dev/null
+++ b/vendor/github.com/russellhaering/goxmldsig/etreeutils/canonicalize.go
@@ -0,0 +1,98 @@
+package etreeutils
+
+import (
+ "sort"
+ "strings"
+
+ "github.com/beevik/etree"
+)
+
+// TransformExcC14n transforms the passed element into xml-exc-c14n form.
+func TransformExcC14n(el *etree.Element, inclusiveNamespacesPrefixList string) error {
+ prefixes := strings.Fields(inclusiveNamespacesPrefixList)
+ prefixSet := make(map[string]struct{}, len(prefixes))
+
+ for _, prefix := range prefixes {
+ prefixSet[prefix] = struct{}{}
+ }
+
+ err := transformExcC14n(DefaultNSContext, DefaultNSContext, el, prefixSet)
+ if err != nil {
+ return err
+ }
+
+ return nil
+}
+
+func transformExcC14n(ctx, declared NSContext, el *etree.Element, inclusiveNamespaces map[string]struct{}) error {
+ scope, err := ctx.SubContext(el)
+ if err != nil {
+ return err
+ }
+
+ visiblyUtilizedPrefixes := map[string]struct{}{
+ el.Space: struct{}{},
+ }
+
+ filteredAttrs := []etree.Attr{}
+
+ // Filter out all namespace declarations
+ for _, attr := range el.Attr {
+ switch {
+ case attr.Space == xmlnsPrefix:
+ if _, ok := inclusiveNamespaces[attr.Key]; ok {
+ visiblyUtilizedPrefixes[attr.Key] = struct{}{}
+ }
+
+ case attr.Space == defaultPrefix && attr.Key == xmlnsPrefix:
+ if _, ok := inclusiveNamespaces[defaultPrefix]; ok {
+ visiblyUtilizedPrefixes[defaultPrefix] = struct{}{}
+ }
+
+ default:
+ if attr.Space != defaultPrefix {
+ visiblyUtilizedPrefixes[attr.Space] = struct{}{}
+ }
+
+ filteredAttrs = append(filteredAttrs, attr)
+ }
+ }
+
+ el.Attr = filteredAttrs
+
+ declared = declared.Copy()
+
+ // Declare all visibly utilized prefixes that are in-scope but haven't
+ // been declared in the canonicalized form yet. These might have been
+ // declared on this element but then filtered out above, or they might
+ // have been declared on an ancestor (before canonicalization) which
+ // didn't visibly utilize and thus had them removed.
+ for prefix := range visiblyUtilizedPrefixes {
+ // Skip redundant declarations - they have to already have the same
+ // value.
+ if declaredNamespace, ok := declared.prefixes[prefix]; ok {
+ if value, ok := scope.prefixes[prefix]; ok && declaredNamespace == value {
+ continue
+ }
+ }
+
+ namespace, err := scope.LookupPrefix(prefix)
+ if err != nil {
+ return err
+ }
+
+ el.Attr = append(el.Attr, declared.declare(prefix, namespace))
+ }
+
+ sort.Sort(SortedAttrs(el.Attr))
+
+ // Transform child elements
+ for _, child := range el.ChildElements() {
+ err := transformExcC14n(scope, declared, child, inclusiveNamespaces)
+ if err != nil {
+ return err
+ }
+ }
+
+ return nil
+}
diff --git a/vendor/github.com/russellhaering/goxmldsig/etreeutils/namespace.go b/vendor/github.com/russellhaering/goxmldsig/etreeutils/namespace.go
new file mode 100644
index 0000000000..baf1124f66
--- /dev/null
+++ b/vendor/github.com/russellhaering/goxmldsig/etreeutils/namespace.go
@@ -0,0 +1,407 @@
+package etreeutils
+
+import (
+ "errors"
+
+ "fmt"
+
+ "sort"
+
+ "github.com/beevik/etree"
+)
+
+const (
+ defaultPrefix = ""
+ xmlnsPrefix = "xmlns"
+ xmlPrefix = "xml"
+
+ XMLNamespace = "http://www.w3.org/XML/1998/namespace"
+ XMLNSNamespace = "http://www.w3.org/2000/xmlns/"
+)
+
+var (
+ DefaultNSContext = NSContext{
+ prefixes: map[string]string{
+ defaultPrefix: XMLNamespace,
+ xmlPrefix: XMLNamespace,
+ xmlnsPrefix: XMLNSNamespace,
+ },
+ }
+
+ EmptyNSContext = NSContext{}
+
+ ErrReservedNamespace = errors.New("disallowed declaration of reserved namespace")
+ ErrInvalidDefaultNamespace = errors.New("invalid default namespace declaration")
+ ErrTraversalHalted = errors.New("traversal halted")
+)
+
+type ErrUndeclaredNSPrefix struct {
+ Prefix string
+}
+
+func (e ErrUndeclaredNSPrefix) Error() string {
+ return fmt.Sprintf("undeclared namespace prefix: '%s'", e.Prefix)
+}
+
+type NSContext struct {
+ prefixes map[string]string
+}
+
+func (ctx NSContext) Copy() NSContext {
+ prefixes := make(map[string]string, len(ctx.prefixes)+4)
+ for k, v := range ctx.prefixes {
+ prefixes[k] = v
+ }
+
+ return NSContext{prefixes: prefixes}
+}
+
+func (ctx NSContext) declare(prefix, namespace string) etree.Attr {
+ ctx.prefixes[prefix] = namespace
+
+ switch prefix {
+ case defaultPrefix:
+ return etree.Attr{
+ Key: xmlnsPrefix,
+ Value: namespace,
+ }
+
+ default:
+ return etree.Attr{
+ Space: xmlnsPrefix,
+ Key: prefix,
+ Value: namespace,
+ }
+ }
+}
+
+func (ctx NSContext) SubContext(el *etree.Element) (NSContext, error) {
+ // The subcontext should inherit existing declared prefixes
+ newCtx := ctx.Copy()
+
+ // Merge new namespace declarations on top of existing ones.
+ for _, attr := range el.Attr {
+ if attr.Space == xmlnsPrefix {
+ // This attribute is a namespace declaration of the form "xmlns:"
+
+ // The 'xml' namespace may only be re-declared with the name 'http://www.w3.org/XML/1998/namespace'
+ if attr.Key == xmlPrefix && attr.Value != XMLNamespace {
+ return ctx, ErrReservedNamespace
+ }
+
+ // The 'xmlns' namespace may not be re-declared
+ if attr.Key == xmlnsPrefix {
+ return ctx, ErrReservedNamespace
+ }
+
+ newCtx.declare(attr.Key, attr.Value)
+ } else if attr.Space == defaultPrefix && attr.Key == xmlnsPrefix {
+ // This attribute is a default namespace declaration
+
+ // The xmlns namespace value may not be declared as the default namespace
+ if attr.Value == XMLNSNamespace {
+ return ctx, ErrInvalidDefaultNamespace
+ }
+
+ newCtx.declare(defaultPrefix, attr.Value)
+ }
+ }
+
+ return newCtx, nil
+}
+
+// Prefixes returns a copy of this context's prefix map.
+func (ctx NSContext) Prefixes() map[string]string {
+ prefixes := make(map[string]string, len(ctx.prefixes))
+ for k, v := range ctx.prefixes {
+ prefixes[k] = v
+ }
+
+ return prefixes
+}
+
+// LookupPrefix attempts to find a declared namespace for the specified prefix. If the prefix
+// is an empty string this will be the default namespace for this context. If the prefix is
+// undeclared in this context an ErrUndeclaredNSPrefix will be returned.
+func (ctx NSContext) LookupPrefix(prefix string) (string, error) {
+ if namespace, ok := ctx.prefixes[prefix]; ok {
+ return namespace, nil
+ }
+
+ return "", ErrUndeclaredNSPrefix{
+ Prefix: prefix,
+ }
+}
+
+// NSIterHandler is a function which is invoked with a element and its surrounding
+// NSContext during traversals.
+type NSIterHandler func(NSContext, *etree.Element) error
+
+// NSTraverse traverses an element tree, invoking the passed handler for each element
+// in the tree.
+func NSTraverse(ctx NSContext, el *etree.Element, handle NSIterHandler) error {
+ ctx, err := ctx.SubContext(el)
+ if err != nil {
+ return err
+ }
+
+ err = handle(ctx, el)
+ if err != nil {
+ return err
+ }
+
+ // Recursively traverse child elements.
+ for _, child := range el.ChildElements() {
+ err := NSTraverse(ctx, child, handle)
+ if err != nil {
+ return err
+ }
+ }
+
+ return nil
+}
+
+// NSDetatch makes a copy of the passed element, and declares any namespaces in
+// the passed context onto the new element before returning it.
+func NSDetatch(ctx NSContext, el *etree.Element) (*etree.Element, error) {
+ ctx, err := ctx.SubContext(el)
+ if err != nil {
+ return nil, err
+ }
+
+ el = el.Copy()
+
+ // Build a new attribute list
+ attrs := make([]etree.Attr, 0, len(el.Attr))
+
+ // First copy over anything that isn't a namespace declaration
+ for _, attr := range el.Attr {
+ if attr.Space == xmlnsPrefix {
+ continue
+ }
+
+ if attr.Space == defaultPrefix && attr.Key == xmlnsPrefix {
+ continue
+ }
+
+ attrs = append(attrs, attr)
+ }
+
+ // Append all in-context namespace declarations
+ for prefix, namespace := range ctx.prefixes {
+ // Skip the implicit "xml" and "xmlns" prefix declarations
+ if prefix == xmlnsPrefix || prefix == xmlPrefix {
+ continue
+ }
+
+ // Also skip declararing the default namespace as XMLNamespace
+ if prefix == defaultPrefix && namespace == XMLNamespace {
+ continue
+ }
+
+ if prefix != defaultPrefix {
+ attrs = append(attrs, etree.Attr{
+ Space: xmlnsPrefix,
+ Key: prefix,
+ Value: namespace,
+ })
+ } else {
+ attrs = append(attrs, etree.Attr{
+ Key: xmlnsPrefix,
+ Value: namespace,
+ })
+ }
+ }
+
+ sort.Sort(SortedAttrs(attrs))
+
+ el.Attr = attrs
+
+ return el, nil
+}
+
+// NSSelectOne behaves identically to NSSelectOneCtx, but uses DefaultNSContext as the
+// surrounding context.
+func NSSelectOne(el *etree.Element, namespace, tag string) (*etree.Element, error) {
+ return NSSelectOneCtx(DefaultNSContext, el, namespace, tag)
+}
+
+// NSSelectOneCtx conducts a depth-first search for an element with the specified namespace
+// and tag. If such an element is found, a new *etree.Element is returned which is a
+// copy of the found element, but with all in-context namespace declarations attached
+// to the element as attributes.
+func NSSelectOneCtx(ctx NSContext, el *etree.Element, namespace, tag string) (*etree.Element, error) {
+ var found *etree.Element
+
+ err := NSFindIterateCtx(ctx, el, namespace, tag, func(ctx NSContext, el *etree.Element) error {
+ var err error
+
+ found, err = NSDetatch(ctx, el)
+ if err != nil {
+ return err
+ }
+
+ return ErrTraversalHalted
+ })
+
+ if err != nil {
+ return nil, err
+ }
+
+ return found, nil
+}
+
+// NSFindIterate behaves identically to NSFindIterateCtx, but uses DefaultNSContext
+// as the surrounding context.
+func NSFindIterate(el *etree.Element, namespace, tag string, handle NSIterHandler) error {
+ return NSFindIterateCtx(DefaultNSContext, el, namespace, tag, handle)
+}
+
+// NSFindIterateCtx conducts a depth-first traversal searching for elements with the
+// specified tag in the specified namespace. It uses the passed NSContext for prefix
+// lookups. For each such element, the passed handler function is invoked. If the
+// handler function returns an error traversal is immediately halted. If the error
+// returned by the handler is ErrTraversalHalted then nil will be returned by
+// NSFindIterate. If any other error is returned by the handler, that error will be
+// returned by NSFindIterate.
+func NSFindIterateCtx(ctx NSContext, el *etree.Element, namespace, tag string, handle NSIterHandler) error {
+ err := NSTraverse(ctx, el, func(ctx NSContext, el *etree.Element) error {
+ _ctx, err := ctx.SubContext(el)
+ if err != nil {
+ return err
+ }
+
+ currentNS, err := _ctx.LookupPrefix(el.Space)
+ if err != nil {
+ return err
+ }
+
+ // Base case, el is the sought after element.
+ if currentNS == namespace && el.Tag == tag {
+ return handle(ctx, el)
+ }
+
+ return nil
+ })
+
+ if err != nil && err != ErrTraversalHalted {
+ return err
+ }
+
+ return nil
+}
+
+// NSFindOne behaves identically to NSFindOneCtx, but uses DefaultNSContext for
+// context.
+func NSFindOne(el *etree.Element, namespace, tag string) (*etree.Element, error) {
+ return NSFindOneCtx(DefaultNSContext, el, namespace, tag)
+}
+
+// NSFindOneCtx conducts a depth-first search for the specified element. If such an element
+// is found a reference to it is returned.
+func NSFindOneCtx(ctx NSContext, el *etree.Element, namespace, tag string) (*etree.Element, error) {
+ var found *etree.Element
+
+ err := NSFindIterateCtx(ctx, el, namespace, tag, func(ctx NSContext, el *etree.Element) error {
+ found = el
+ return ErrTraversalHalted
+ })
+
+ if err != nil {
+ return nil, err
+ }
+
+ return found, nil
+}
+
+// NSIterateChildren iterates the children of an element, invoking the passed
+// handler with each direct child of the element, and the context surrounding
+// that child.
+func NSIterateChildren(ctx NSContext, el *etree.Element, handle NSIterHandler) error {
+ ctx, err := ctx.SubContext(el)
+ if err != nil {
+ return err
+ }
+
+ // Iterate the child elements.
+ for _, child := range el.ChildElements() {
+ err = handle(ctx, child)
+ if err != nil {
+ return err
+ }
+ }
+
+ return nil
+}
+
+// NSFindIterateChildrenCtx takes an element and its surrounding context, and iterates
+// the children of that element searching for an element matching the passed namespace
+// and tag. For each such element that is found, handle is invoked with the matched
+// element and its own surrounding context.
+func NSFindChildrenIterateCtx(ctx NSContext, el *etree.Element, namespace, tag string, handle NSIterHandler) error {
+ err := NSIterateChildren(ctx, el, func(ctx NSContext, el *etree.Element) error {
+ _ctx, err := ctx.SubContext(el)
+ if err != nil {
+ return err
+ }
+
+ currentNS, err := _ctx.LookupPrefix(el.Space)
+ if err != nil {
+ return err
+ }
+
+ // Base case, el is the sought after element.
+ if currentNS == namespace && el.Tag == tag {
+ return handle(ctx, el)
+ }
+
+ return nil
+ })
+
+ if err != nil && err != ErrTraversalHalted {
+ return err
+ }
+
+ return nil
+}
+
+// NSFindOneChild behaves identically to NSFindOneChildCtx, but uses
+// DefaultNSContext for context.
+func NSFindOneChild(el *etree.Element, namespace, tag string) (*etree.Element, error) {
+ return NSFindOneChildCtx(DefaultNSContext, el, namespace, tag)
+}
+
+// NSFindOneCtx conducts a depth-first search for the specified element. If such an
+// element is found a reference to it is returned.
+func NSFindOneChildCtx(ctx NSContext, el *etree.Element, namespace, tag string) (*etree.Element, error) {
+ var found *etree.Element
+
+ err := NSFindChildrenIterateCtx(ctx, el, namespace, tag, func(ctx NSContext, el *etree.Element) error {
+ found = el
+ return ErrTraversalHalted
+ })
+
+ if err != nil && err != ErrTraversalHalted {
+ return nil, err
+ }
+
+ return found, nil
+}
+
+// NSBuildParentContext recurses upward from an element in order to build an NSContext
+// for its immediate parent. If the element has no parent DefaultNSContext
+// is returned.
+func NSBuildParentContext(el *etree.Element) (NSContext, error) {
+ parent := el.Parent()
+ if parent == nil {
+ return DefaultNSContext, nil
+ }
+
+ ctx, err := NSBuildParentContext(parent)
+
+ if err != nil {
+ return ctx, err
+ }
+
+ return ctx.SubContext(parent)
+}
diff --git a/vendor/github.com/russellhaering/goxmldsig/etreeutils/sort.go b/vendor/github.com/russellhaering/goxmldsig/etreeutils/sort.go
new file mode 100644
index 0000000000..5871a3913d
--- /dev/null
+++ b/vendor/github.com/russellhaering/goxmldsig/etreeutils/sort.go
@@ -0,0 +1,66 @@
+package etreeutils
+
+import "github.com/beevik/etree"
+
+// SortedAttrs provides sorting capabilities, compatible with XML C14N, on top
+// of an []etree.Attr
+type SortedAttrs []etree.Attr
+
+func (a SortedAttrs) Len() int {
+ return len(a)
+}
+
+func (a SortedAttrs) Swap(i, j int) {
+ a[i], a[j] = a[j], a[i]
+}
+
+func (a SortedAttrs) Less(i, j int) bool {
+ // This is the best reference I've found on sort order:
+ // http://dst.lbl.gov/~ksb/Scratch/XMLC14N.html
+
+ // If attr j is a default namespace declaration, attr i may
+ // not be strictly "less" than it.
+ if a[j].Space == defaultPrefix && a[j].Key == xmlnsPrefix {
+ return false
+ }
+
+ // Otherwise, if attr i is a default namespace declaration, it
+ // must be less than anything else.
+ if a[i].Space == defaultPrefix && a[i].Key == xmlnsPrefix {
+ return true
+ }
+
+ // Next, namespace prefix declarations, sorted by prefix, come before
+ // anythign else.
+ if a[i].Space == xmlnsPrefix {
+ if a[j].Space == xmlnsPrefix {
+ return a[i].Key < a[j].Key
+ }
+ return true
+ }
+
+ if a[j].Space == xmlnsPrefix {
+ return false
+ }
+
+ // Then come unprefixed attributes, sorted by key.
+ if a[i].Space == defaultPrefix {
+ if a[j].Space == defaultPrefix {
+ return a[i].Key < a[j].Key
+ }
+ return true
+ }
+
+ if a[j].Space == defaultPrefix {
+ return false
+ }
+
+ // Wow. We're still going. Finally, attributes in the same namespace should be
+ // sorted by key. Attributes in different namespaces should be sorted by the
+ // actual namespace (_not_ the prefix). For now just use the prefix.
+ if a[i].Space == a[j].Space {
+ return a[i].Key < a[j].Key
+ }
+
+ return a[i].Space < a[j].Space
+}
diff --git a/vendor/github.com/russellhaering/goxmldsig/etreeutils/unmarshal.go b/vendor/github.com/russellhaering/goxmldsig/etreeutils/unmarshal.go
new file mode 100644
index 0000000000..b1fecf85a4
--- /dev/null
+++ b/vendor/github.com/russellhaering/goxmldsig/etreeutils/unmarshal.go
@@ -0,0 +1,43 @@
+package etreeutils
+
+import (
+ "encoding/xml"
+
+ "github.com/beevik/etree"
+)
+
+// NSUnmarshalElement unmarshals the passed etree Element into the value pointed to by
+// v using encoding/xml in the context of the passed NSContext. If v implements
+// ElementKeeper, SetUnderlyingElement will be called on v with a reference to el.
+func NSUnmarshalElement(ctx NSContext, el *etree.Element, v interface{}) error {
+ detatched, err := NSDetatch(ctx, el)
+ if err != nil {
+ return err
+ }
+
+ doc := etree.NewDocument()
+ doc.AddChild(detatched)
+ data, err := doc.WriteToBytes()
+ if err != nil {
+ return err
+ }
+
+ err = xml.Unmarshal(data, v)
+ if err != nil {
+ return err
+ }
+
+ switch v := v.(type) {
+ case ElementKeeper:
+ v.SetUnderlyingElement(el)
+ }
+
+ return nil
+}
+
+// ElementKeeper should be implemented by types which will be passed to
+// UnmarshalElement, but wish to keep a reference
+type ElementKeeper interface {
+ SetUnderlyingElement(*etree.Element)
+ UnderlyingElement() *etree.Element
+}
diff --git a/vendor/github.com/russellhaering/goxmldsig/keystore.go b/vendor/github.com/russellhaering/goxmldsig/keystore.go
new file mode 100644
index 0000000000..313d9b6a6e
--- /dev/null
+++ b/vendor/github.com/russellhaering/goxmldsig/keystore.go
@@ -0,0 +1,67 @@
+package dsig
+
+import (
+ "crypto/rand"
+ "crypto/rsa"
+ "crypto/x509"
+ "math/big"
+ "time"
+)
+
+type X509KeyStore interface {
+ GetKeyPair() (privateKey *rsa.PrivateKey, cert []byte, err error)
+}
+
+type X509ChainStore interface {
+ GetChain() (certs [][]byte, err error)
+}
+
+type X509CertificateStore interface {
+ Certificates() (roots []*x509.Certificate, err error)
+}
+
+type MemoryX509CertificateStore struct {
+ Roots []*x509.Certificate
+}
+
+func (mX509cs *MemoryX509CertificateStore) Certificates() ([]*x509.Certificate, error) {
+ return mX509cs.Roots, nil
+}
+
+type MemoryX509KeyStore struct {
+ privateKey *rsa.PrivateKey
+ cert []byte
+}
+
+func (ks *MemoryX509KeyStore) GetKeyPair() (*rsa.PrivateKey, []byte, error) {
+ return ks.privateKey, ks.cert, nil
+}
+
+func RandomKeyStoreForTest() X509KeyStore {
+ key, err := rsa.GenerateKey(rand.Reader, 1024)
+ if err != nil {
+ panic(err)
+ }
+
+ now := time.Now()
+
+ template := &x509.Certificate{
+ SerialNumber: big.NewInt(0),
+ NotBefore: now.Add(-5 * time.Minute),
+ NotAfter: now.Add(365 * 24 * time.Hour),
+
+ KeyUsage: x509.KeyUsageDigitalSignature,
+ ExtKeyUsage: []x509.ExtKeyUsage{},
+ BasicConstraintsValid: true,
+ }
+
+ cert, err := x509.CreateCertificate(rand.Reader, template, template, &key.PublicKey, key)
+ if err != nil {
+ panic(err)
+ }
+
+ return &MemoryX509KeyStore{
+ privateKey: key,
+ cert: cert,
+ }
+}
diff --git a/vendor/github.com/russellhaering/goxmldsig/run_test.sh b/vendor/github.com/russellhaering/goxmldsig/run_test.sh
new file mode 100644
index 0000000000..cfe5b2ea96
--- /dev/null
+++ b/vendor/github.com/russellhaering/goxmldsig/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
diff --git a/vendor/github.com/russellhaering/goxmldsig/sign.go b/vendor/github.com/russellhaering/goxmldsig/sign.go
new file mode 100644
index 0000000000..e2c2852e43
--- /dev/null
+++ b/vendor/github.com/russellhaering/goxmldsig/sign.go
@@ -0,0 +1,256 @@
+package dsig
+
+import (
+ "crypto"
+ "crypto/rand"
+ "crypto/rsa"
+ _ "crypto/sha1"
+ _ "crypto/sha256"
+ "encoding/base64"
+ "errors"
+ "fmt"
+
+ "github.com/beevik/etree"
+ "github.com/russellhaering/goxmldsig/etreeutils"
+)
+
+type SigningContext struct {
+ Hash crypto.Hash
+ KeyStore X509KeyStore
+ IdAttribute string
+ Prefix string
+ Canonicalizer Canonicalizer
+}
+
+func NewDefaultSigningContext(ks X509KeyStore) *SigningContext {
+ return &SigningContext{
+ Hash: crypto.SHA256,
+ KeyStore: ks,
+ IdAttribute: DefaultIdAttr,
+ Prefix: DefaultPrefix,
+ Canonicalizer: MakeC14N11Canonicalizer(),
+ }
+}
+
+func (ctx *SigningContext) SetSignatureMethod(algorithmID string) error {
+ hash, ok := signatureMethodsByIdentifier[algorithmID]
+ if !ok {
+ return fmt.Errorf("Unknown SignatureMethod: %s", algorithmID)
+ }
+
+ ctx.Hash = hash
+
+ return nil
+}
+
+func (ctx *SigningContext) digest(el *etree.Element) ([]byte, error) {
+ canonical, err := ctx.Canonicalizer.Canonicalize(el)
+ if err != nil {
+ return nil, err
+ }
+
+ hash := ctx.Hash.New()
+ _, err = hash.Write(canonical)
+ if err != nil {
+ return nil, err
+ }
+
+ return hash.Sum(nil), nil
+}
+
+func (ctx *SigningContext) constructSignedInfo(el *etree.Element, enveloped bool) (*etree.Element, error) {
+ digestAlgorithmIdentifier := ctx.GetDigestAlgorithmIdentifier()
+ if digestAlgorithmIdentifier == "" {
+ return nil, errors.New("unsupported hash mechanism")
+ }
+
+ signatureMethodIdentifier := ctx.GetSignatureMethodIdentifier()
+ if signatureMethodIdentifier == "" {
+ return nil, errors.New("unsupported signature method")
+ }
+
+ digest, err := ctx.digest(el)
+ if err != nil {
+ return nil, err
+ }
+
+ signedInfo := &etree.Element{
+ Tag: SignedInfoTag,
+ Space: ctx.Prefix,
+ }
+
+ // /SignedInfo/CanonicalizationMethod
+ canonicalizationMethod := ctx.createNamespacedElement(signedInfo, CanonicalizationMethodTag)
+ canonicalizationMethod.CreateAttr(AlgorithmAttr, string(ctx.Canonicalizer.Algorithm()))
+
+ // /SignedInfo/SignatureMethod
+ signatureMethod := ctx.createNamespacedElement(signedInfo, SignatureMethodTag)
+ signatureMethod.CreateAttr(AlgorithmAttr, signatureMethodIdentifier)
+
+ // /SignedInfo/Reference
+ reference := ctx.createNamespacedElement(signedInfo, ReferenceTag)
+
+ dataId := el.SelectAttrValue(ctx.IdAttribute, "")
+ if dataId == "" {
+ return nil, errors.New("Missing data ID")
+ }
+
+ reference.CreateAttr(URIAttr, "#"+dataId)
+
+ // /SignedInfo/Reference/Transforms
+ transforms := ctx.createNamespacedElement(reference, TransformsTag)
+ if enveloped {
+ envelopedTransform := ctx.createNamespacedElement(transforms, TransformTag)
+ envelopedTransform.CreateAttr(AlgorithmAttr, EnvelopedSignatureAltorithmId.String())
+ }
+ canonicalizationAlgorithm := ctx.createNamespacedElement(transforms, TransformTag)
+ canonicalizationAlgorithm.CreateAttr(AlgorithmAttr, string(ctx.Canonicalizer.Algorithm()))
+
+ // /SignedInfo/Reference/DigestMethod
+ digestMethod := ctx.createNamespacedElement(reference, DigestMethodTag)
+ digestMethod.CreateAttr(AlgorithmAttr, digestAlgorithmIdentifier)
+
+ // /SignedInfo/Reference/DigestValue
+ digestValue := ctx.createNamespacedElement(reference, DigestValueTag)
+ digestValue.SetText(base64.StdEncoding.EncodeToString(digest))
+
+ return signedInfo, nil
+}
+
+func (ctx *SigningContext) ConstructSignature(el *etree.Element, enveloped bool) (*etree.Element, error) {
+ signedInfo, err := ctx.constructSignedInfo(el, enveloped)
+ if err != nil {
+ return nil, err
+ }
+
+ sig := &etree.Element{
+ Tag: SignatureTag,
+ Space: ctx.Prefix,
+ }
+
+ xmlns := "xmlns"
+ if ctx.Prefix != "" {
+ xmlns += ":" + ctx.Prefix
+ }
+
+ sig.CreateAttr(xmlns, Namespace)
+ sig.AddChild(signedInfo)
+
+ // When using xml-c14n11 (ie, non-exclusive canonicalization) the canonical form
+ // of the SignedInfo must declare all namespaces that are in scope at it's final
+ // enveloped location in the document. In order to do that, we're going to construct
+ // a series of cascading NSContexts to capture namespace declarations:
+
+ // First get the context surrounding the element we are signing.
+ rootNSCtx, err := etreeutils.NSBuildParentContext(el)
+ if err != nil {
+ return nil, err
+ }
+
+ // Then capture any declarations on the element itself.
+ elNSCtx, err := rootNSCtx.SubContext(el)
+ if err != nil {
+ return nil, err
+ }
+
+ // Followed by declarations on the Signature (which we just added above)
+ sigNSCtx, err := elNSCtx.SubContext(sig)
+ if err != nil {
+ return nil, err
+ }
+
+ // Finally detatch the SignedInfo in order to capture all of the namespace
+ // declarations in the scope we've constructed.
+ detatchedSignedInfo, err := etreeutils.NSDetatch(sigNSCtx, signedInfo)
+ if err != nil {
+ return nil, err
+ }
+
+ digest, err := ctx.digest(detatchedSignedInfo)
+ if err != nil {
+ return nil, err
+ }
+
+ key, cert, err := ctx.KeyStore.GetKeyPair()
+ if err != nil {
+ return nil, err
+ }
+
+ certs := [][]byte{cert}
+ if cs, ok := ctx.KeyStore.(X509ChainStore); ok {
+ certs, err = cs.GetChain()
+ if err != nil {
+ return nil, err
+ }
+ }
+
+ rawSignature, err := rsa.SignPKCS1v15(rand.Reader, key, ctx.Hash, digest)
+ if err != nil {
+ return nil, err
+ }
+
+ signatureValue := ctx.createNamespacedElement(sig, SignatureValueTag)
+ signatureValue.SetText(base64.StdEncoding.EncodeToString(rawSignature))
+
+ keyInfo := ctx.createNamespacedElement(sig, KeyInfoTag)
+ x509Data := ctx.createNamespacedElement(keyInfo, X509DataTag)
+ for _, cert := range certs {
+ x509Certificate := ctx.createNamespacedElement(x509Data, X509CertificateTag)
+ x509Certificate.SetText(base64.StdEncoding.EncodeToString(cert))
+ }
+
+ return sig, nil
+}
+
+func (ctx *SigningContext) createNamespacedElement(el *etree.Element, tag string) *etree.Element {
+ child := el.CreateElement(tag)
+ child.Space = ctx.Prefix
+ return child
+}
+
+func (ctx *SigningContext) SignEnveloped(el *etree.Element) (*etree.Element, error) {
+ sig, err := ctx.ConstructSignature(el, true)
+ if err != nil {
+ return nil, err
+ }
+
+ ret := el.Copy()
+ ret.Child = append(ret.Child, sig)
+
+ return ret, nil
+}
+
+func (ctx *SigningContext) GetSignatureMethodIdentifier() string {
+ if ident, ok := signatureMethodIdentifiers[ctx.Hash]; ok {
+ return ident
+ }
+ return ""
+}
+
+func (ctx *SigningContext) GetDigestAlgorithmIdentifier() string {
+ if ident, ok := digestAlgorithmIdentifiers[ctx.Hash]; ok {
+ return ident
+ }
+ return ""
+}
+
+// Useful for signing query string (including DEFLATED AuthnRequest) when
+// using HTTP-Redirect to make a signed request.
+// See 3.4.4.1 DEFLATE Encoding of https://docs.oasis-open.org/security/saml/v2.0/saml-bindings-2.0-os.pdf
+func (ctx *SigningContext) SignString(content string) ([]byte, error) {
+ hash := ctx.Hash.New()
+ if ln, err := hash.Write([]byte(content)); err != nil {
+ return nil, fmt.Errorf("error calculating hash: %v", err)
+ } else if ln < 1 {
+ return nil, fmt.Errorf("zero length hash")
+ }
+ digest := hash.Sum(nil)
+
+ var signature []byte
+ if key, _, err := ctx.KeyStore.GetKeyPair(); err != nil {
+ return nil, fmt.Errorf("unable to fetch key for signing: %v", err)
+ } else if signature, err = rsa.SignPKCS1v15(rand.Reader, key, ctx.Hash, digest); err != nil {
+ return nil, fmt.Errorf("error signing: %v", err)
+ }
+ return signature, nil
+}
diff --git a/vendor/github.com/russellhaering/goxmldsig/tls_keystore.go b/vendor/github.com/russellhaering/goxmldsig/tls_keystore.go
new file mode 100644
index 0000000000..79a1a4d81a
--- /dev/null
+++ b/vendor/github.com/russellhaering/goxmldsig/tls_keystore.go
@@ -0,0 +1,39 @@
+package dsig
+
+import (
+ "crypto/rsa"
+ "crypto/tls"
+ "fmt"
+)
+
+//Well-known errors
+var (
+ ErrNonRSAKey = fmt.Errorf("Private key was not RSA")
+ ErrMissingCertificates = fmt.Errorf("No public certificates provided")
+)
+
+//TLSCertKeyStore wraps the stdlib tls.Certificate to return its contained key
+//and certs.
+type TLSCertKeyStore tls.Certificate
+
+//GetKeyPair implements X509KeyStore using the underlying tls.Certificate
+func (d TLSCertKeyStore) GetKeyPair() (*rsa.PrivateKey, []byte, error) {
+ pk, ok := d.PrivateKey.(*rsa.PrivateKey)
+
+ if !ok {
+ return nil, nil, ErrNonRSAKey
+ }
+
+ if len(d.Certificate) < 1 {
+ return nil, nil, ErrMissingCertificates
+ }
+
+ crt := d.Certificate[0]
+
+ return pk, crt, nil
+}
+
+//GetChain impliments X509ChainStore using the underlying tls.Certificate
+func (d TLSCertKeyStore) GetChain() ([][]byte, error) {
+ return d.Certificate, nil
+}
diff --git a/vendor/github.com/russellhaering/goxmldsig/types/signature.go b/vendor/github.com/russellhaering/goxmldsig/types/signature.go
new file mode 100644
index 0000000000..17fd3d72bd
--- /dev/null
+++ b/vendor/github.com/russellhaering/goxmldsig/types/signature.go
@@ -0,0 +1,93 @@
+package types
+
+import (
+ "encoding/xml"
+
+ "github.com/beevik/etree"
+)
+
+type InclusiveNamespaces struct {
+ XMLName xml.Name `xml:"http://www.w3.org/2001/10/xml-exc-c14n# InclusiveNamespaces"`
+ PrefixList string `xml:"PrefixList,attr"`
+}
+
+type Transform struct {
+ XMLName xml.Name `xml:"http://www.w3.org/2000/09/xmldsig# Transform"`
+ Algorithm string `xml:"Algorithm,attr"`
+ InclusiveNamespaces *InclusiveNamespaces `xml:"InclusiveNamespaces"`
+}
+
+type Transforms struct {
+ XMLName xml.Name `xml:"http://www.w3.org/2000/09/xmldsig# Transforms"`
+ Transforms []Transform `xml:"Transform"`
+}
+
+type DigestMethod struct {
+ XMLName xml.Name `xml:"http://www.w3.org/2000/09/xmldsig# DigestMethod"`
+ Algorithm string `xml:"Algorithm,attr"`
+}
+
+type Reference struct {
+ XMLName xml.Name `xml:"http://www.w3.org/2000/09/xmldsig# Reference"`
+ URI string `xml:"URI,attr"`
+ DigestValue string `xml:"DigestValue"`
+ DigestAlgo DigestMethod `xml:"DigestMethod"`
+ Transforms Transforms `xml:"Transforms"`
+}
+
+type CanonicalizationMethod struct {
+ XMLName xml.Name `xml:"http://www.w3.org/2000/09/xmldsig# CanonicalizationMethod"`
+ Algorithm string `xml:"Algorithm,attr"`
+}
+
+type SignatureMethod struct {
+ XMLName xml.Name `xml:"http://www.w3.org/2000/09/xmldsig# SignatureMethod"`
+ Algorithm string `xml:"Algorithm,attr"`
+}
+
+type SignedInfo struct {
+ XMLName xml.Name `xml:"http://www.w3.org/2000/09/xmldsig# SignedInfo"`
+ CanonicalizationMethod CanonicalizationMethod `xml:"CanonicalizationMethod"`
+ SignatureMethod SignatureMethod `xml:"SignatureMethod"`
+ References []Reference `xml:"Reference"`
+}
+
+type SignatureValue struct {
+ XMLName xml.Name `xml:"http://www.w3.org/2000/09/xmldsig# SignatureValue"`
+ Data string `xml:",chardata"`
+}
+
+type KeyInfo struct {
+ XMLName xml.Name `xml:"http://www.w3.org/2000/09/xmldsig# KeyInfo"`
+ X509Data X509Data `xml:"X509Data"`
+}
+
+type X509Data struct {
+ XMLName xml.Name `xml:"http://www.w3.org/2000/09/xmldsig# X509Data"`
+ X509Certificates []X509Certificate `xml:"X509Certificate"`
+}
+
+type X509Certificate struct {
+ XMLName xml.Name `xml:"http://www.w3.org/2000/09/xmldsig# X509Certificate"`
+ Data string `xml:",chardata"`
+}
+
+type Signature struct {
+ XMLName xml.Name `xml:"http://www.w3.org/2000/09/xmldsig# Signature"`
+ SignedInfo *SignedInfo `xml:"SignedInfo"`
+ SignatureValue *SignatureValue `xml:"SignatureValue"`
+ KeyInfo *KeyInfo `xml:"KeyInfo"`
+ el *etree.Element
+}
+
+// SetUnderlyingElement will be called with a reference to the Element this Signature
+// was unmarshaled from.
+func (s *Signature) SetUnderlyingElement(el *etree.Element) {
+ s.el = el
+}
+
+// UnderlyingElement returns a reference to the Element this signature was unmarshaled
+// from, where applicable.
+func (s *Signature) UnderlyingElement() *etree.Element {
+ return s.el
+}
diff --git a/vendor/github.com/russellhaering/goxmldsig/validate.go b/vendor/github.com/russellhaering/goxmldsig/validate.go
new file mode 100644
index 0000000000..55feb3960b
--- /dev/null
+++ b/vendor/github.com/russellhaering/goxmldsig/validate.go
@@ -0,0 +1,467 @@
+package dsig
+
+import (
+ "bytes"
+ "crypto/rsa"
+ "crypto/x509"
+ "encoding/base64"
+ "errors"
+ "fmt"
+ "regexp"
+
+ "github.com/beevik/etree"
+ "github.com/russellhaering/goxmldsig/etreeutils"
+ "github.com/russellhaering/goxmldsig/types"
+)
+
+var uriRegexp = regexp.MustCompile("^#[a-zA-Z_][\\w.-]*$")
+var whiteSpace = regexp.MustCompile("\\s+")
+
+var (
+ // ErrMissingSignature indicates that no enveloped signature was found referencing
+ // the top level element passed for signature verification.
+ ErrMissingSignature = errors.New("Missing signature referencing the top-level element")
+)
+
+type ValidationContext struct {
+ CertificateStore X509CertificateStore
+ IdAttribute string
+ Clock *Clock
+}
+
+func NewDefaultValidationContext(certificateStore X509CertificateStore) *ValidationContext {
+ return &ValidationContext{
+ CertificateStore: certificateStore,
+ IdAttribute: DefaultIdAttr,
+ }
+}
+
+// TODO(russell_h): More flexible namespace support. This might barely work.
+func inNamespace(el *etree.Element, ns string) bool {
+ for _, attr := range el.Attr {
+ if attr.Value == ns {
+ if attr.Space == "" && attr.Key == "xmlns" {
+ return el.Space == ""
+ } else if attr.Space == "xmlns" {
+ return el.Space == attr.Key
+ }
+ }
+ }
+
+ return false
+}
+
+func childPath(space, tag string) string {
+ if space == "" {
+ return "./" + tag
+ } else {
+ return "./" + space + ":" + tag
+ }
+}
+
+func mapPathToElement(tree, el *etree.Element) []int {
+ for i, child := range tree.Child {
+ if child == el {
+ return []int{i}
+ }
+ }
+
+ for i, child := range tree.Child {
+ if childElement, ok := child.(*etree.Element); ok {
+ childPath := mapPathToElement(childElement, el)
+ if childElement != nil {
+ return append([]int{i}, childPath...)
+ }
+ }
+ }
+
+ return nil
+}
+
+func removeElementAtPath(el *etree.Element, path []int) bool {
+ if len(path) == 0 {
+ return false
+ }
+
+ if len(el.Child) <= path[0] {
+ return false
+ }
+
+ childElement, ok := el.Child[path[0]].(*etree.Element)
+ if !ok {
+ return false
+ }
+
+ if len(path) == 1 {
+ el.RemoveChild(childElement)
+ return true
+ }
+
+ return removeElementAtPath(childElement, path[1:])
+}
+
+// Transform returns a new element equivalent to the passed root el, but with
+// the set of transformations described by the ref applied.
+//
+// The functionality of transform is currently very limited and purpose-specific.
+func (ctx *ValidationContext) transform(
+ el *etree.Element,
+ sig *types.Signature,
+ ref *types.Reference) (*etree.Element, Canonicalizer, error) {
+ transforms := ref.Transforms.Transforms
+
+ if len(transforms) != 2 {
+ return nil, nil, errors.New("Expected Enveloped and C14N transforms")
+ }
+
+ // map the path to the passed signature relative to the passed root, in
+ // order to enable removal of the signature by an enveloped signature
+ // transform
+ signaturePath := mapPathToElement(el, sig.UnderlyingElement())
+
+ // make a copy of the passed root
+ el = el.Copy()
+
+ var canonicalizer Canonicalizer
+
+ for _, transform := range transforms {
+ algo := transform.Algorithm
+
+ switch AlgorithmID(algo) {
+ case EnvelopedSignatureAltorithmId:
+ if !removeElementAtPath(el, signaturePath) {
+ return nil, nil, errors.New("Error applying canonicalization transform: Signature not found")
+ }
+
+ case CanonicalXML10ExclusiveAlgorithmId:
+ var prefixList string
+ if transform.InclusiveNamespaces != nil {
+ prefixList = transform.InclusiveNamespaces.PrefixList
+ }
+
+ canonicalizer = MakeC14N10ExclusiveCanonicalizerWithPrefixList(prefixList)
+
+ case CanonicalXML11AlgorithmId:
+ canonicalizer = MakeC14N11Canonicalizer()
+
+ case CanonicalXML10RecAlgorithmId:
+ canonicalizer = MakeC14N10RecCanonicalizer()
+
+ case CanonicalXML10CommentAlgorithmId:
+ canonicalizer = MakeC14N10CommentCanonicalizer()
+
+ default:
+ return nil, nil, errors.New("Unknown Transform Algorithm: " + algo)
+ }
+ }
+
+ if canonicalizer == nil {
+ return nil, nil, errors.New("Expected canonicalization transform")
+ }
+
+ return el, canonicalizer, nil
+}
+
+func (ctx *ValidationContext) digest(el *etree.Element, digestAlgorithmId string, canonicalizer Canonicalizer) ([]byte, error) {
+ data, err := canonicalizer.Canonicalize(el)
+ if err != nil {
+ return nil, err
+ }
+
+ digestAlgorithm, ok := digestAlgorithmsByIdentifier[digestAlgorithmId]
+ if !ok {
+ return nil, errors.New("Unknown digest algorithm: " + digestAlgorithmId)
+ }
+
+ hash := digestAlgorithm.New()
+ _, err = hash.Write(data)
+ if err != nil {
+ return nil, err
+ }
+
+ return hash.Sum(nil), nil
+}
+
+func (ctx *ValidationContext) verifySignedInfo(sig *types.Signature, canonicalizer Canonicalizer, signatureMethodId string, cert *x509.Certificate, decodedSignature []byte) error {
+ signatureElement := sig.UnderlyingElement()
+
+ nsCtx, err := etreeutils.NSBuildParentContext(signatureElement)
+ if err != nil {
+ return err
+ }
+
+ signedInfo, err := etreeutils.NSFindOneChildCtx(nsCtx, signatureElement, Namespace, SignedInfoTag)
+ if err != nil {
+ return err
+ }
+
+ if signedInfo == nil {
+ return errors.New("Missing SignedInfo")
+ }
+
+ // Canonicalize the xml
+ canonical, err := canonicalSerialize(signedInfo)
+ if err != nil {
+ return err
+ }
+
+ signatureAlgorithm, ok := signatureMethodsByIdentifier[signatureMethodId]
+ if !ok {
+ return errors.New("Unknown signature method: " + signatureMethodId)
+ }
+
+ hash := signatureAlgorithm.New()
+ _, err = hash.Write(canonical)
+ if err != nil {
+ return err
+ }
+
+ hashed := hash.Sum(nil)
+
+ pubKey, ok := cert.PublicKey.(*rsa.PublicKey)
+ if !ok {
+ return errors.New("Invalid public key")
+ }
+
+ // Verify that the private key matching the public key from the cert was what was used to sign the 'SignedInfo' and produce the 'SignatureValue'
+ err = rsa.VerifyPKCS1v15(pubKey, signatureAlgorithm, hashed[:], decodedSignature)
+ if err != nil {
+ return err
+ }
+
+ return nil
+}
+
+func (ctx *ValidationContext) validateSignature(el *etree.Element, sig *types.Signature, cert *x509.Certificate) (*etree.Element, error) {
+ idAttr := el.SelectAttr(ctx.IdAttribute)
+ if idAttr == nil || idAttr.Value == "" {
+ return nil, errors.New("Missing ID attribute")
+ }
+
+ var ref *types.Reference
+
+ // Find the first reference which references the top-level element
+ for _, _ref := range sig.SignedInfo.References {
+ if _ref.URI == "" || _ref.URI[1:] == idAttr.Value {
+ ref = &_ref
+ }
+ }
+
+ // Perform all transformations listed in the 'SignedInfo'
+ // Basically, this means removing the 'SignedInfo'
+ transformed, canonicalizer, err := ctx.transform(el, sig, ref)
+ if err != nil {
+ return nil, err
+ }
+
+ digestAlgorithm := ref.DigestAlgo.Algorithm
+
+ // Digest the transformed XML and compare it to the 'DigestValue' from the 'SignedInfo'
+ digest, err := ctx.digest(transformed, digestAlgorithm, canonicalizer)
+ if err != nil {
+ return nil, err
+ }
+
+ decodedDigestValue, err := base64.StdEncoding.DecodeString(ref.DigestValue)
+ if err != nil {
+ return nil, err
+ }
+
+ if !bytes.Equal(digest, decodedDigestValue) {
+ return nil, errors.New("Signature could not be verified")
+ }
+
+ // Decode the 'SignatureValue' so we can compare against it
+ decodedSignature, err := base64.StdEncoding.DecodeString(sig.SignatureValue.Data)
+ if err != nil {
+ return nil, errors.New("Could not decode signature")
+ }
+
+ // Actually verify the 'SignedInfo' was signed by a trusted source
+ signatureMethod := sig.SignedInfo.SignatureMethod.Algorithm
+ err = ctx.verifySignedInfo(sig, canonicalizer, signatureMethod, cert, decodedSignature)
+ if err != nil {
+ return nil, err
+ }
+
+ return transformed, nil
+}
+
+func contains(roots []*x509.Certificate, cert *x509.Certificate) bool {
+ for _, root := range roots {
+ if root.Equal(cert) {
+ return true
+ }
+ }
+ return false
+}
+
+// findSignature searches for a Signature element referencing the passed root element.
+func (ctx *ValidationContext) findSignature(el *etree.Element) (*types.Signature, error) {
+ idAttr := el.SelectAttr(ctx.IdAttribute)
+ if idAttr == nil || idAttr.Value == "" {
+ return nil, errors.New("Missing ID attribute")
+ }
+
+ var sig *types.Signature
+
+ // Traverse the tree looking for a Signature element
+ err := etreeutils.NSFindIterate(el, Namespace, SignatureTag, func(ctx etreeutils.NSContext, el *etree.Element) error {
+
+ found := false
+ err := etreeutils.NSFindChildrenIterateCtx(ctx, el, Namespace, SignedInfoTag,
+ func(ctx etreeutils.NSContext, signedInfo *etree.Element) error {
+ detachedSignedInfo, err := etreeutils.NSDetatch(ctx, signedInfo)
+ if err != nil {
+ return err
+ }
+
+ c14NMethod, err := etreeutils.NSFindOneChildCtx(ctx, detachedSignedInfo, Namespace, CanonicalizationMethodTag)
+ if err != nil {
+ return err
+ }
+
+ if c14NMethod == nil {
+ return errors.New("missing CanonicalizationMethod on Signature")
+ }
+
+ c14NAlgorithm := c14NMethod.SelectAttrValue(AlgorithmAttr, "")
+
+ var canonicalSignedInfo *etree.Element
+
+ switch AlgorithmID(c14NAlgorithm) {
+ case CanonicalXML10ExclusiveAlgorithmId:
+ err := etreeutils.TransformExcC14n(detachedSignedInfo, "")
+ if err != nil {
+ return err
+ }
+
+ // NOTE: TransformExcC14n transforms the element in-place,
+ // while canonicalPrep isn't meant to. Once we standardize
+ // this behavior we can drop this, as well as the adding and
+ // removing of elements below.
+ canonicalSignedInfo = detachedSignedInfo
+
+ case CanonicalXML11AlgorithmId:
+ canonicalSignedInfo = canonicalPrep(detachedSignedInfo, map[string]struct{}{})
+
+ case CanonicalXML10RecAlgorithmId:
+ canonicalSignedInfo = canonicalPrep(detachedSignedInfo, map[string]struct{}{})
+
+ case CanonicalXML10CommentAlgorithmId:
+ canonicalSignedInfo = canonicalPrep(detachedSignedInfo, map[string]struct{}{})
+
+ default:
+ return fmt.Errorf("invalid CanonicalizationMethod on Signature: %s", c14NAlgorithm)
+ }
+
+ el.RemoveChild(signedInfo)
+ el.AddChild(canonicalSignedInfo)
+
+ found = true
+
+ return etreeutils.ErrTraversalHalted
+ })
+ if err != nil {
+ return err
+ }
+
+ if !found {
+ return errors.New("Missing SignedInfo")
+ }
+
+ // Unmarshal the signature into a structured Signature type
+ _sig := &types.Signature{}
+ err = etreeutils.NSUnmarshalElement(ctx, el, _sig)
+ if err != nil {
+ return err
+ }
+
+ // Traverse references in the signature to determine whether it has at least
+ // one reference to the top level element. If so, conclude the search.
+ for _, ref := range _sig.SignedInfo.References {
+ if ref.URI == "" || ref.URI[1:] == idAttr.Value {
+ sig = _sig
+ return etreeutils.ErrTraversalHalted
+ }
+ }
+
+ return nil
+ })
+
+ if err != nil {
+ return nil, err
+ }
+
+ if sig == nil {
+ return nil, ErrMissingSignature
+ }
+
+ return sig, nil
+}
+
+func (ctx *ValidationContext) verifyCertificate(sig *types.Signature) (*x509.Certificate, error) {
+ now := ctx.Clock.Now()
+
+ roots, err := ctx.CertificateStore.Certificates()
+ if err != nil {
+ return nil, err
+ }
+
+ var cert *x509.Certificate
+
+ if sig.KeyInfo != nil {
+ // If the Signature includes KeyInfo, extract the certificate from there
+ if len(sig.KeyInfo.X509Data.X509Certificates) == 0 || sig.KeyInfo.X509Data.X509Certificates[0].Data == "" {
+ return nil, errors.New("missing X509Certificate within KeyInfo")
+ }
+
+ certData, err := base64.StdEncoding.DecodeString(
+ whiteSpace.ReplaceAllString(sig.KeyInfo.X509Data.X509Certificates[0].Data, ""))
+ if err != nil {
+ return nil, errors.New("Failed to parse certificate")
+ }
+
+ cert, err = x509.ParseCertificate(certData)
+ if err != nil {
+ return nil, err
+ }
+ } else {
+ // If the Signature doesn't have KeyInfo, Use the root certificate if there is only one
+ if len(roots) == 1 {
+ cert = roots[0]
+ } else {
+ return nil, errors.New("Missing x509 Element")
+ }
+ }
+
+ // Verify that the certificate is one we trust
+ if !contains(roots, cert) {
+ return nil, errors.New("Could not verify certificate against trusted certs")
+ }
+
+ if now.Before(cert.NotBefore) || now.After(cert.NotAfter) {
+ return nil, errors.New("Cert is not valid at this time")
+ }
+
+ return cert, nil
+}
+
+// Validate verifies that the passed element contains a valid enveloped signature
+// matching a currently-valid certificate in the context's CertificateStore.
+func (ctx *ValidationContext) Validate(el *etree.Element) (*etree.Element, error) {
+ // Make a copy of the element to avoid mutating the one we were passed.
+ el = el.Copy()
+
+ sig, err := ctx.findSignature(el)
+ if err != nil {
+ return nil, err
+ }
+
+ cert, err := ctx.verifyCertificate(sig)
+ if err != nil {
+ return nil, err
+ }
+
+ return ctx.validateSignature(el, sig, cert)
+}
diff --git a/vendor/github.com/russellhaering/goxmldsig/xml_constants.go b/vendor/github.com/russellhaering/goxmldsig/xml_constants.go
new file mode 100644
index 0000000000..c4b815b295
--- /dev/null
+++ b/vendor/github.com/russellhaering/goxmldsig/xml_constants.go
@@ -0,0 +1,81 @@
+package dsig
+
+import "crypto"
+
+const (
+ DefaultPrefix = "ds"
+ Namespace = "http://www.w3.org/2000/09/xmldsig#"
+)
+
+// Tags
+const (
+ SignatureTag = "Signature"
+ SignedInfoTag = "SignedInfo"
+ CanonicalizationMethodTag = "CanonicalizationMethod"
+ SignatureMethodTag = "SignatureMethod"
+ ReferenceTag = "Reference"
+ TransformsTag = "Transforms"
+ TransformTag = "Transform"
+ DigestMethodTag = "DigestMethod"
+ DigestValueTag = "DigestValue"
+ SignatureValueTag = "SignatureValue"
+ KeyInfoTag = "KeyInfo"
+ X509DataTag = "X509Data"
+ X509CertificateTag = "X509Certificate"
+ InclusiveNamespacesTag = "InclusiveNamespaces"
+)
+
+const (
+ AlgorithmAttr = "Algorithm"
+ URIAttr = "URI"
+ DefaultIdAttr = "ID"
+ PrefixListAttr = "PrefixList"
+)
+
+type AlgorithmID string
+
+func (id AlgorithmID) String() string {
+ return string(id)
+}
+
+const (
+ RSASHA1SignatureMethod = "http://www.w3.org/2000/09/xmldsig#rsa-sha1"
+ RSASHA256SignatureMethod = "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"
+ RSASHA512SignatureMethod = "http://www.w3.org/2001/04/xmldsig-more#rsa-sha512"
+)
+
+//Well-known signature algorithms
+const (
+ // Supported canonicalization algorithms
+ CanonicalXML10ExclusiveAlgorithmId AlgorithmID = "http://www.w3.org/2001/10/xml-exc-c14n#"
+ CanonicalXML11AlgorithmId AlgorithmID = "http://www.w3.org/2006/12/xml-c14n11"
+
+ CanonicalXML10RecAlgorithmId AlgorithmID = "http://www.w3.org/TR/2001/REC-xml-c14n-20010315"
+ CanonicalXML10CommentAlgorithmId AlgorithmID = "http://www.w3.org/TR/2001/REC-xml-c14n-20010315#WithComments"
+
+ EnvelopedSignatureAltorithmId AlgorithmID = "http://www.w3.org/2000/09/xmldsig#enveloped-signature"
+)
+
+var digestAlgorithmIdentifiers = map[crypto.Hash]string{
+ crypto.SHA1: "http://www.w3.org/2000/09/xmldsig#sha1",
+ crypto.SHA256: "http://www.w3.org/2001/04/xmlenc#sha256",
+ crypto.SHA512: "http://www.w3.org/2001/04/xmlenc#sha512",
+}
+
+var digestAlgorithmsByIdentifier = map[string]crypto.Hash{}
+var signatureMethodsByIdentifier = map[string]crypto.Hash{}
+
+func init() {
+ for hash, id := range digestAlgorithmIdentifiers {
+ digestAlgorithmsByIdentifier[id] = hash
+ }
+ for hash, id := range signatureMethodIdentifiers {
+ signatureMethodsByIdentifier[id] = hash
+ }
+}
+
+var signatureMethodIdentifiers = map[crypto.Hash]string{
+ crypto.SHA1: RSASHA1SignatureMethod,
+ crypto.SHA256: RSASHA256SignatureMethod,
+ crypto.SHA512: RSASHA512SignatureMethod,
+}
diff --git a/vendor/modules.txt b/vendor/modules.txt
index 5a507c359c..7a39eb1b37 100644
--- a/vendor/modules.txt
+++ b/vendor/modules.txt
@@ -6,6 +6,8 @@ github.com/NYTimes/gziphandler
github.com/armon/go-metrics
# github.com/avct/uasurfer v0.0.0-20190821150637-906cc7dc6197
github.com/avct/uasurfer
+# github.com/beevik/etree v1.1.0
+github.com/beevik/etree
# github.com/beorn7/perks v1.0.1
github.com/beorn7/perks/quantile
# github.com/blang/semver v3.5.1+incompatible
@@ -94,6 +96,8 @@ github.com/jaytaylor/html2text
# github.com/jmoiron/sqlx v1.2.0
github.com/jmoiron/sqlx
github.com/jmoiron/sqlx/reflectx
+# github.com/jonboulle/clockwork v0.1.0
+github.com/jonboulle/clockwork
# github.com/konsorten/go-windows-terminal-sequences v1.0.2
github.com/konsorten/go-windows-terminal-sequences
# github.com/lann/builder v0.0.0-20180802200727-47ae307949d0
@@ -118,6 +122,10 @@ github.com/mattermost/go-i18n/i18n/language
github.com/mattermost/go-i18n/i18n/translation
# github.com/mattermost/gorp v2.0.1-0.20190301154413-3b31e9a39d05+incompatible
github.com/mattermost/gorp
+# github.com/mattermost/gosaml2 v0.3.2
+github.com/mattermost/gosaml2
+github.com/mattermost/gosaml2/types
+github.com/mattermost/gosaml2/uuid
# github.com/mattermost/ldap v0.0.0-20191128190019-9f62ba4b8d4d
github.com/mattermost/ldap
# github.com/mattermost/rsc v0.0.0-20160330161541-bbaefb05eaa0
@@ -186,6 +194,10 @@ github.com/prometheus/procfs/internal/fs
github.com/prometheus/procfs/internal/util
# github.com/rs/cors v1.7.0
github.com/rs/cors
+# github.com/russellhaering/goxmldsig v0.0.0-20180430223755-7acd5e4a6ef7
+github.com/russellhaering/goxmldsig
+github.com/russellhaering/goxmldsig/etreeutils
+github.com/russellhaering/goxmldsig/types
# github.com/rwcarlsen/goexif v0.0.0-20190401172101-9e8deecbddbd
github.com/rwcarlsen/goexif/exif
github.com/rwcarlsen/goexif/tiff