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