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/gorilla/handlers"
|
||||||
_ "github.com/hako/durafmt"
|
_ "github.com/hako/durafmt"
|
||||||
_ "github.com/hashicorp/memberlist"
|
_ "github.com/hashicorp/memberlist"
|
||||||
|
_ "github.com/mattermost/gosaml2"
|
||||||
_ "github.com/mattermost/ldap"
|
_ "github.com/mattermost/ldap"
|
||||||
_ "github.com/mattermost/rsc/qr"
|
_ "github.com/mattermost/rsc/qr"
|
||||||
_ "github.com/prometheus/client_golang/prometheus"
|
_ "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/NYTimes/gziphandler v1.1.1
|
||||||
github.com/armon/go-metrics v0.0.0-20190430140413-ec5e00d3c878 // indirect
|
github.com/armon/go-metrics v0.0.0-20190430140413-ec5e00d3c878 // indirect
|
||||||
github.com/avct/uasurfer v0.0.0-20190821150637-906cc7dc6197
|
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/blang/semver v3.5.1+incompatible
|
||||||
github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 // indirect
|
github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 // indirect
|
||||||
github.com/corpix/uarand v0.1.1 // 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/icrowley/fake v0.0.0-20180203215853-4178557ae428
|
||||||
github.com/jaytaylor/html2text v0.0.0-20190408195923-01ec452cbe43
|
github.com/jaytaylor/html2text v0.0.0-20190408195923-01ec452cbe43
|
||||||
github.com/jmoiron/sqlx v1.2.0
|
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/konsorten/go-windows-terminal-sequences v1.0.2 // indirect
|
||||||
github.com/lib/pq v1.2.0
|
github.com/lib/pq v1.2.0
|
||||||
github.com/magiconair/properties v1.8.1 // indirect
|
github.com/magiconair/properties v1.8.1 // indirect
|
||||||
github.com/mailru/easyjson v0.7.0 // indirect
|
github.com/mailru/easyjson v0.7.0 // indirect
|
||||||
github.com/mattermost/go-i18n v1.11.0
|
github.com/mattermost/go-i18n v1.11.0
|
||||||
github.com/mattermost/gorp v2.0.1-0.20190301154413-3b31e9a39d05+incompatible
|
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/ldap v0.0.0-20191128190019-9f62ba4b8d4d
|
||||||
github.com/mattermost/rsc v0.0.0-20160330161541-bbaefb05eaa0
|
github.com/mattermost/rsc v0.0.0-20160330161541-bbaefb05eaa0
|
||||||
github.com/mattermost/viper v1.0.4
|
github.com/mattermost/viper v1.0.4
|
||||||
@@ -65,6 +68,7 @@ require (
|
|||||||
github.com/prometheus/common v0.7.0 // indirect
|
github.com/prometheus/common v0.7.0 // indirect
|
||||||
github.com/prometheus/procfs v0.0.5 // indirect
|
github.com/prometheus/procfs v0.0.5 // indirect
|
||||||
github.com/rs/cors v1.7.0
|
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/rwcarlsen/goexif v0.0.0-20190401172101-9e8deecbddbd
|
||||||
github.com/segmentio/analytics-go v3.1.0+incompatible
|
github.com/segmentio/analytics-go v3.1.0+incompatible
|
||||||
github.com/segmentio/backo-go v0.0.0-20160424052352-204274ad699c // indirect
|
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 h1:E7XoJNlFlrtC6dlRG9SoGBwJpX8vD7fNTKRmgSwAj5I=
|
||||||
github.com/avct/uasurfer v0.0.0-20190821150637-906cc7dc6197/go.mod h1:noBAuukeYOXa0aXGqxr24tADqkwDO2KRD15FsuaZ5a8=
|
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/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 h1:xJ4a3vCFaGF/jqvzLMYoU8P317H5OQ+Via4RmuPwCS0=
|
||||||
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
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=
|
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/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 h1:41Ip0zITnmWNR/vHV+S4m+VoUivnWY5E4OJfLZjCJMA=
|
||||||
github.com/jmoiron/sqlx v1.2.0/go.mod h1:1FEQNm3xlJgrMD+FBdI9+xvCksHtbpVBBw5dYhBSsks=
|
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.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
||||||
github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
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=
|
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/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 h1:FN4zK2wNig7MVVsOsGEZ+LeIq0gUcudn3LEGgbodMq8=
|
||||||
github.com/mattermost/gorp v2.0.1-0.20190301154413-3b31e9a39d05+incompatible/go.mod h1:0kX1qa3DOpaPJyOdMLeo7TcBN0QmUszj9a/VygOhDe0=
|
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 h1:2DV7VIlEv6J5R5o6tUcb3ZMKJYeeZuWZL7Rv1m23TgQ=
|
||||||
github.com/mattermost/ldap v0.0.0-20191128190019-9f62ba4b8d4d/go.mod h1:HLbgMEI5K131jpxGazJ97AxfPDt31osq36YS1oxFQPQ=
|
github.com/mattermost/ldap v0.0.0-20191128190019-9f62ba4b8d4d/go.mod h1:HLbgMEI5K131jpxGazJ97AxfPDt31osq36YS1oxFQPQ=
|
||||||
github.com/mattermost/rsc v0.0.0-20160330161541-bbaefb05eaa0 h1:G9tL6JXRBMzjuD1kkBtcnd42kUiT6QDwxfFYu7adM6o=
|
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/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 h1:+88SsELBHx5r+hZ8TCkggzSstaWNbDvThkVK8H6f9ik=
|
||||||
github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU=
|
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/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 h1:unvLzUrgn54Nhn5yJB8qUBr/a7wiZHQaJ/8Y4yTGAjE=
|
||||||
github.com/rwcarlsen/goexif v0.0.0-20190318171057-76e3344f7516/go.mod h1:hPqNNc0+uJM6H+SuU8sEs5K5IQeKccPqeSjfgcKGgPk=
|
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/armon/go-metrics
|
||||||
# github.com/avct/uasurfer v0.0.0-20190821150637-906cc7dc6197
|
# github.com/avct/uasurfer v0.0.0-20190821150637-906cc7dc6197
|
||||||
github.com/avct/uasurfer
|
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 v1.0.1
|
||||||
github.com/beorn7/perks/quantile
|
github.com/beorn7/perks/quantile
|
||||||
# github.com/blang/semver v3.5.1+incompatible
|
# 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 v1.2.0
|
||||||
github.com/jmoiron/sqlx
|
github.com/jmoiron/sqlx
|
||||||
github.com/jmoiron/sqlx/reflectx
|
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 v1.0.2
|
||||||
github.com/konsorten/go-windows-terminal-sequences
|
github.com/konsorten/go-windows-terminal-sequences
|
||||||
# github.com/lann/builder v0.0.0-20180802200727-47ae307949d0
|
# 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/go-i18n/i18n/translation
|
||||||
# github.com/mattermost/gorp v2.0.1-0.20190301154413-3b31e9a39d05+incompatible
|
# github.com/mattermost/gorp v2.0.1-0.20190301154413-3b31e9a39d05+incompatible
|
||||||
github.com/mattermost/gorp
|
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 v0.0.0-20191128190019-9f62ba4b8d4d
|
||||||
github.com/mattermost/ldap
|
github.com/mattermost/ldap
|
||||||
# github.com/mattermost/rsc v0.0.0-20160330161541-bbaefb05eaa0
|
# 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/prometheus/procfs/internal/util
|
||||||
# github.com/rs/cors v1.7.0
|
# github.com/rs/cors v1.7.0
|
||||||
github.com/rs/cors
|
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 v0.0.0-20190401172101-9e8deecbddbd
|
||||||
github.com/rwcarlsen/goexif/exif
|
github.com/rwcarlsen/goexif/exif
|
||||||
github.com/rwcarlsen/goexif/tiff
|
github.com/rwcarlsen/goexif/tiff
|
||||||
|
|||||||
Ссылка в новой задаче
Block a user