diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 32398d8df6..277ebefd83 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -172,6 +172,8 @@ jobs: if: ${{ !cancelled() }} - run: make check-tidy if: ${{ !cancelled() }} + - run: make -C packagetracker check-if-new-version-available + if: ${{ !cancelled() }} authgear-e2e: if: ${{ github.repository != 'oursky/authgear-server' }} diff --git a/custombuild/go.mod b/custombuild/go.mod index 58270666d9..dfd4de76fb 100644 --- a/custombuild/go.mod +++ b/custombuild/go.mod @@ -46,7 +46,6 @@ require ( github.com/cncf/xds/go v0.0.0-20240905190251-b4127c9b8d78 // indirect github.com/cockroachdb/apd/v2 v2.0.2 // indirect github.com/coder/websocket v1.8.12 // indirect - github.com/crewjam/saml v0.4.14 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/davidbyttow/govips/v2 v2.15.0 // indirect github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0 // indirect diff --git a/custombuild/go.sum b/custombuild/go.sum index 76bc4711c8..e43ac91a13 100644 --- a/custombuild/go.sum +++ b/custombuild/go.sum @@ -104,8 +104,6 @@ github.com/coder/websocket v1.8.12 h1:5bUXkEPPIbewrnkU8LTCLVaxi4N4J8ahufH2vlo4NA github.com/coder/websocket v1.8.12/go.mod h1:LNVeNrXQZfe5qhS9ALED3uA+l5pPqvwXg3CKoDBB2gs= github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= -github.com/crewjam/saml v0.4.14 h1:g9FBNx62osKusnFzs3QTN5L9CVA/Egfgm+stJShzw/c= -github.com/crewjam/saml v0.4.14/go.mod h1:UVSZCf18jJkk6GpWNVqcyQJMD5HsRugBPf4I1nl2mME= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= @@ -191,8 +189,6 @@ github.com/goccy/go-json v0.10.3 h1:KZ5WoDbxAIgm2HNbYckL0se1fHD6rz5j4ywS6ebzDqA= github.com/goccy/go-json v0.10.3/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= -github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg= -github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk= github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= @@ -724,8 +720,6 @@ gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo= -gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= k8s.io/api v0.30.6 h1:uqRDLnFkmPLorI9D0x1dGXdYeRQMhQHlrHDgZ3/45RE= diff --git a/e2e/go.mod b/e2e/go.mod index bdb6075122..72bdeab53c 100644 --- a/e2e/go.mod +++ b/e2e/go.mod @@ -62,7 +62,6 @@ require ( github.com/cncf/xds/go v0.0.0-20240905190251-b4127c9b8d78 // indirect github.com/cockroachdb/apd/v2 v2.0.2 // indirect github.com/coder/websocket v1.8.12 // indirect - github.com/crewjam/saml v0.4.14 // indirect github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0 // indirect github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect github.com/dlclark/regexp2 v1.7.0 // indirect @@ -84,7 +83,6 @@ require ( github.com/go-webauthn/webauthn v0.8.6 // indirect github.com/go-webauthn/x v0.1.4 // indirect github.com/goccy/go-json v0.10.3 // indirect - github.com/golang-jwt/jwt/v4 v4.5.0 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/google/go-tpm v0.9.0 // indirect github.com/google/s2a-go v0.1.8 // indirect diff --git a/e2e/go.sum b/e2e/go.sum index 6b7fee558b..957495ad3f 100644 --- a/e2e/go.sum +++ b/e2e/go.sum @@ -109,8 +109,6 @@ github.com/coder/websocket v1.8.12 h1:5bUXkEPPIbewrnkU8LTCLVaxi4N4J8ahufH2vlo4NA github.com/coder/websocket v1.8.12/go.mod h1:LNVeNrXQZfe5qhS9ALED3uA+l5pPqvwXg3CKoDBB2gs= github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= -github.com/crewjam/saml v0.4.14 h1:g9FBNx62osKusnFzs3QTN5L9CVA/Egfgm+stJShzw/c= -github.com/crewjam/saml v0.4.14/go.mod h1:UVSZCf18jJkk6GpWNVqcyQJMD5HsRugBPf4I1nl2mME= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= @@ -182,8 +180,6 @@ github.com/go-webauthn/x v0.1.4 h1:sGmIFhcY70l6k7JIDfnjVBiAAFEssga5lXIUXe0GtAs= github.com/go-webauthn/x v0.1.4/go.mod h1:75Ug0oK6KYpANh5hDOanfDI+dvPWHk788naJVG/37H8= github.com/goccy/go-json v0.10.3 h1:KZ5WoDbxAIgm2HNbYckL0se1fHD6rz5j4ywS6ebzDqA= github.com/goccy/go-json v0.10.3/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= -github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg= -github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk= github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= @@ -711,8 +707,6 @@ gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo= -gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E= diff --git a/go.mod b/go.mod index 2b868025a3..d0311e5823 100644 --- a/go.mod +++ b/go.mod @@ -105,7 +105,6 @@ require ( require ( github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 // indirect github.com/beevik/etree v1.4.1 - github.com/crewjam/saml v0.4.14 github.com/go-asn1-ber/asn1-ber v1.5.6 // indirect github.com/mattermost/xml-roundtrip-validator v0.1.0 ) diff --git a/go.sum b/go.sum index 5df3328b65..03ebfa3031 100644 --- a/go.sum +++ b/go.sum @@ -102,8 +102,6 @@ github.com/coder/websocket v1.8.12 h1:5bUXkEPPIbewrnkU8LTCLVaxi4N4J8ahufH2vlo4NA github.com/coder/websocket v1.8.12/go.mod h1:LNVeNrXQZfe5qhS9ALED3uA+l5pPqvwXg3CKoDBB2gs= github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= -github.com/crewjam/saml v0.4.14 h1:g9FBNx62osKusnFzs3QTN5L9CVA/Egfgm+stJShzw/c= -github.com/crewjam/saml v0.4.14/go.mod h1:UVSZCf18jJkk6GpWNVqcyQJMD5HsRugBPf4I1nl2mME= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= @@ -192,8 +190,6 @@ github.com/goccy/go-json v0.10.3 h1:KZ5WoDbxAIgm2HNbYckL0se1fHD6rz5j4ywS6ebzDqA= github.com/goccy/go-json v0.10.3/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= -github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg= -github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk= github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= @@ -752,8 +748,6 @@ gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo= -gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= k8s.io/api v0.30.6 h1:uqRDLnFkmPLorI9D0x1dGXdYeRQMhQHlrHDgZ3/45RE= diff --git a/packagetracker/.gitignore b/packagetracker/.gitignore new file mode 100644 index 0000000000..6db33941e3 --- /dev/null +++ b/packagetracker/.gitignore @@ -0,0 +1,2 @@ +go.mod +go.sum diff --git a/packagetracker/Makefile b/packagetracker/Makefile new file mode 100644 index 0000000000..0032928413 --- /dev/null +++ b/packagetracker/Makefile @@ -0,0 +1,7 @@ +.PHONY: check-if-new-version-available +check-if-new-version-available: + @cp ./go.mod.tpl ./go.mod + @go mod download + @# If grep matches something, it exits 0, otherwise it exits 1. + @# In our case, we want to invert the exit code. + go list -u -m -f '{{if .Update}}{{if not .Indirect}}{{.}}{{end}}{{end}}' all | grep "."; status_code=$$?; if [ $$status_code -eq 0 ]; then exit 1; else exit 0; fi; diff --git a/packagetracker/README.md b/packagetracker/README.md new file mode 100644 index 0000000000..3e76ebcce4 --- /dev/null +++ b/packagetracker/README.md @@ -0,0 +1,7 @@ +This directory is used to run scripts which check if the listed packages having new versions. + +Usage + +```sh +make check-if-new-version-available +``` diff --git a/packagetracker/go.mod.tpl b/packagetracker/go.mod.tpl new file mode 100644 index 0000000000..c5bb072260 --- /dev/null +++ b/packagetracker/go.mod.tpl @@ -0,0 +1,9 @@ +module github.com/authgear/authgear-server/packagetracker + +// go1.21 supports toolchain +// See https://go.dev/doc/toolchain +go 1.22.7 + +require ( + github.com/crewjam/saml v0.4.14 +) diff --git a/pkg/lib/saml/samlprotocol/const.go b/pkg/lib/saml/samlprotocol/const.go index d7998d2d17..033bab239c 100644 --- a/pkg/lib/saml/samlprotocol/const.go +++ b/pkg/lib/saml/samlprotocol/const.go @@ -60,5 +60,4 @@ var ( SAMLAttrTypeString = fmt.Sprintf("%s:string", xmlSchemaNamespace) ) -const timeFormat = "2006-01-02T15:04:05.999Z07:00" const canonicalizerPrefixList = "" diff --git a/pkg/lib/saml/samlprotocol/duration.go b/pkg/lib/saml/samlprotocol/duration.go new file mode 100644 index 0000000000..5a2296ff5b --- /dev/null +++ b/pkg/lib/saml/samlprotocol/duration.go @@ -0,0 +1,132 @@ +// NOTE: Copied from https://github.com/crewjam/saml/blob/1dfacaea137943953459ad09aedf007c1b92deb2/duration.go + +package samlprotocol + +import ( + "fmt" + "regexp" + "strconv" + "strings" + "time" +) + +// Duration is a time.Duration that uses the xsd:duration format for text +// marshalling and unmarshalling. +type Duration time.Duration + +// MarshalText implements the encoding.TextMarshaler interface. +func (d Duration) MarshalText() ([]byte, error) { + if d == 0 { + return nil, nil + } + + out := "PT" + if d < 0 { + d *= -1 + out = "-" + out + } + + h := time.Duration(d) / time.Hour + m := time.Duration(d) % time.Hour / time.Minute + s := time.Duration(d) % time.Minute / time.Second + ns := time.Duration(d) % time.Second + if h > 0 { + out += fmt.Sprintf("%dH", h) + } + if m > 0 { + out += fmt.Sprintf("%dM", m) + } + if s > 0 || ns > 0 { + out += fmt.Sprintf("%d", s) + if ns > 0 { + out += strings.TrimRight(fmt.Sprintf(".%09d", ns), "0") + } + out += "S" + } + + return []byte(out), nil +} + +const ( + day = 24 * time.Hour + month = 30 * day // Assumed to be 30 days. + year = 365 * day // Assumed to be non-leap year. +) + +var ( + durationRegexp = regexp.MustCompile(`^(-?)P(?:(\d+)Y)?(?:(\d+)M)?(?:(\d+)D)?(?:T(.+))?$`) + durationTimeRegexp = regexp.MustCompile(`^(?:(\d+)H)?(?:(\d+)M)?(?:(\d+(?:\.\d+)?)S)?$`) +) + +// UnmarshalText implements the encoding.TextUnmarshaler interface. +// +//nolint:gocognit +func (d *Duration) UnmarshalText(text []byte) error { + if text == nil { + *d = 0 + return nil + } + + var ( + out time.Duration + sign time.Duration = 1 + ) + match := durationRegexp.FindStringSubmatch(string(text)) + if match == nil || strings.Join(match[2:6], "") == "" { + return fmt.Errorf("invalid duration (%s)", text) + } + if match[1] == "-" { + sign = -1 + } + if match[2] != "" { + y, err := strconv.Atoi(match[2]) + if err != nil { + return fmt.Errorf("invalid duration years (%s): %s", text, err) + } + out += time.Duration(y) * year + } + if match[3] != "" { + m, err := strconv.Atoi(match[3]) + if err != nil { + return fmt.Errorf("invalid duration months (%s): %s", text, err) + } + out += time.Duration(m) * month + } + if match[4] != "" { + d, err := strconv.Atoi(match[4]) + if err != nil { + return fmt.Errorf("invalid duration days (%s): %s", text, err) + } + out += time.Duration(d) * day + } + if match[5] != "" { + match := durationTimeRegexp.FindStringSubmatch(match[5]) + if match == nil { + return fmt.Errorf("invalid duration (%s)", text) + } + if match[1] != "" { + h, err := strconv.Atoi(match[1]) + if err != nil { + return fmt.Errorf("invalid duration hours (%s): %s", text, err) + } + out += time.Duration(h) * time.Hour + } + if match[2] != "" { + m, err := strconv.Atoi(match[2]) + if err != nil { + return fmt.Errorf("invalid duration minutes (%s): %s", text, err) + } + out += time.Duration(m) * time.Minute + } + if match[3] != "" { + s, err := strconv.ParseFloat(match[3], 64) + if err != nil { + return fmt.Errorf("invalid duration seconds (%s): %s", text, err) + } + out += time.Duration(s * float64(time.Second)) + } + } + + *d = Duration(sign * out) + return nil +} diff --git a/pkg/lib/saml/samlprotocol/metadata.go b/pkg/lib/saml/samlprotocol/metadata.go index be26509277..b112d2d051 100644 --- a/pkg/lib/saml/samlprotocol/metadata.go +++ b/pkg/lib/saml/samlprotocol/metadata.go @@ -2,10 +2,11 @@ package samlprotocol import ( "encoding/xml" + "fmt" + "net/url" "time" "github.com/beevik/etree" - crewjamsaml "github.com/crewjam/saml" ) // Copied from https://github.com/crewjam/saml/blob/main/metadata.go#L53 @@ -18,15 +19,15 @@ type EntityDescriptor struct { ValidUntil *time.Time `xml:"validUntil,attr,omitempty"` CacheDuration time.Duration `xml:"cacheDuration,attr,omitempty"` Signature *etree.Element - RoleDescriptors []crewjamsaml.RoleDescriptor `xml:"RoleDescriptor"` - IDPSSODescriptors []crewjamsaml.IDPSSODescriptor `xml:"IDPSSODescriptor"` - SPSSODescriptors []crewjamsaml.SPSSODescriptor `xml:"SPSSODescriptor"` - AuthnAuthorityDescriptors []crewjamsaml.AuthnAuthorityDescriptor `xml:"AuthnAuthorityDescriptor"` - AttributeAuthorityDescriptors []crewjamsaml.AttributeAuthorityDescriptor `xml:"AttributeAuthorityDescriptor"` - PDPDescriptors []crewjamsaml.PDPDescriptor `xml:"PDPDescriptor"` - AffiliationDescriptor *crewjamsaml.AffiliationDescriptor - Organization *crewjamsaml.Organization - ContactPerson *crewjamsaml.ContactPerson + RoleDescriptors []RoleDescriptor `xml:"RoleDescriptor"` + IDPSSODescriptors []IDPSSODescriptor `xml:"IDPSSODescriptor"` + SPSSODescriptors []SPSSODescriptor `xml:"SPSSODescriptor"` + AuthnAuthorityDescriptors []AuthnAuthorityDescriptor `xml:"AuthnAuthorityDescriptor"` + AttributeAuthorityDescriptors []AttributeAuthorityDescriptor `xml:"AttributeAuthorityDescriptor"` + PDPDescriptors []PDPDescriptor `xml:"PDPDescriptor"` + AffiliationDescriptor *AffiliationDescriptor + Organization *Organization + ContactPerson *ContactPerson AdditionalMetadataLocations []string `xml:"AdditionalMetadataLocation"` } @@ -34,16 +35,16 @@ type EntityDescriptor struct { func (m EntityDescriptor) MarshalXML(e *xml.Encoder, _ xml.StartElement) error { type Alias EntityDescriptor aux := &struct { - ValidUntil *crewjamsaml.RelaxedTime `xml:"validUntil,attr,omitempty"` - CacheDuration crewjamsaml.Duration `xml:"cacheDuration,attr,omitempty"` + ValidUntil *RelaxedTime `xml:"validUntil,attr,omitempty"` + CacheDuration Duration `xml:"cacheDuration,attr,omitempty"` *Alias }{ ValidUntil: nil, - CacheDuration: crewjamsaml.Duration(m.CacheDuration), + CacheDuration: Duration(m.CacheDuration), Alias: (*Alias)(&m), } if m.ValidUntil != nil { - validUntil := crewjamsaml.RelaxedTime(*m.ValidUntil) + validUntil := RelaxedTime(*m.ValidUntil) aux.ValidUntil = &validUntil } return e.Encode(aux) @@ -53,8 +54,8 @@ func (m EntityDescriptor) MarshalXML(e *xml.Encoder, _ xml.StartElement) error { func (m *EntityDescriptor) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error { type Alias EntityDescriptor aux := &struct { - ValidUntil *crewjamsaml.RelaxedTime `xml:"validUntil,attr,omitempty"` - CacheDuration crewjamsaml.Duration `xml:"cacheDuration,attr,omitempty"` + ValidUntil *RelaxedTime `xml:"validUntil,attr,omitempty"` + CacheDuration Duration `xml:"cacheDuration,attr,omitempty"` *Alias }{ Alias: (*Alias)(m), @@ -81,3 +82,342 @@ func (m *Metadata) ToXMLBytes() []byte { } return buf } + +// NOTE: The below are copied from https://github.com/crewjam/saml/blob/b07b16cf83c4171d16da4d85608cb827f183cd79/metadata.go +// Some minor changes were made so that the code can live in our codebase + +// HTTPPostBinding is the official URN for the HTTP-POST binding (transport) +const HTTPPostBinding = "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" + +// HTTPRedirectBinding is the official URN for the HTTP-Redirect binding (transport) +const HTTPRedirectBinding = "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" + +// HTTPArtifactBinding is the official URN for the HTTP-Artifact binding (transport) +const HTTPArtifactBinding = "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Artifact" + +// SOAPBinding is the official URN for the SOAP binding (transport) +const SOAPBinding = "urn:oasis:names:tc:SAML:2.0:bindings:SOAP" + +// SOAPBindingV1 is the URN for the SOAP binding in SAML 1.0 +const SOAPBindingV1 = "urn:oasis:names:tc:SAML:1.0:bindings:SOAP-binding" + +// EntitiesDescriptor represents the SAML object of the same name. +// +// See http://docs.oasis-open.org/security/saml/v2.0/saml-metadata-2.0-os.pdf §2.3.1 +type EntitiesDescriptor struct { + XMLName xml.Name `xml:"urn:oasis:names:tc:SAML:2.0:metadata EntitiesDescriptor"` + ID *string `xml:",attr,omitempty"` + ValidUntil *time.Time `xml:"validUntil,attr,omitempty"` + CacheDuration *time.Duration `xml:"cacheDuration,attr,omitempty"` + Name *string `xml:",attr,omitempty"` + Signature *etree.Element + EntitiesDescriptors []EntitiesDescriptor `xml:"urn:oasis:names:tc:SAML:2.0:metadata EntitiesDescriptor"` + EntityDescriptors []EntityDescriptor `xml:"urn:oasis:names:tc:SAML:2.0:metadata EntityDescriptor"` +} + +// Organization represents the SAML Organization object. +// +// See http://docs.oasis-open.org/security/saml/v2.0/saml-metadata-2.0-os.pdf §2.3.2.1 +type Organization struct { + OrganizationNames []LocalizedName `xml:"OrganizationName"` + OrganizationDisplayNames []LocalizedName `xml:"OrganizationDisplayName"` + OrganizationURLs []LocalizedURI `xml:"OrganizationURL"` +} + +// LocalizedName represents the SAML type localizedNameType. +// +// See http://docs.oasis-open.org/security/saml/v2.0/saml-metadata-2.0-os.pdf §2.2.4 +type LocalizedName struct { + Lang string `xml:"http://www.w3.org/XML/1998/namespace lang,attr"` + Value string `xml:",chardata"` +} + +// LocalizedURI represents the SAML type localizedURIType. +// +// See http://docs.oasis-open.org/security/saml/v2.0/saml-metadata-2.0-os.pdf §2.2.5 +type LocalizedURI struct { + Lang string `xml:"http://www.w3.org/XML/1998/namespace lang,attr"` + Value string `xml:",chardata"` +} + +// ContactPerson represents the SAML element ContactPerson. +// +// See http://docs.oasis-open.org/security/saml/v2.0/saml-metadata-2.0-os.pdf §2.3.2.2 +type ContactPerson struct { + ContactType string `xml:"contactType,attr"` + Company string + GivenName string + SurName string + EmailAddresses []string `xml:"EmailAddress"` + TelephoneNumbers []string `xml:"TelephoneNumber"` +} + +// RoleDescriptor represents the SAML element RoleDescriptor. +// +// See http://docs.oasis-open.org/security/saml/v2.0/saml-metadata-2.0-os.pdf §2.4.1 +type RoleDescriptor struct { + ID string `xml:",attr,omitempty"` + ValidUntil *time.Time `xml:"validUntil,attr,omitempty"` + CacheDuration time.Duration `xml:"cacheDuration,attr,omitempty"` + ProtocolSupportEnumeration string `xml:"protocolSupportEnumeration,attr"` + ErrorURL string `xml:"errorURL,attr,omitempty"` + Signature *etree.Element + KeyDescriptors []KeyDescriptor `xml:"KeyDescriptor,omitempty"` + Organization *Organization `xml:"Organization,omitempty"` + ContactPeople []ContactPerson `xml:"ContactPerson,omitempty"` +} + +// KeyDescriptor represents the XMLSEC object of the same name +type KeyDescriptor struct { + Use string `xml:"use,attr"` + KeyInfo KeyInfo `xml:"http://www.w3.org/2000/09/xmldsig# KeyInfo"` + EncryptionMethods []EncryptionMethod `xml:"EncryptionMethod"` +} + +// EncryptionMethod represents the XMLSEC object of the same name +type EncryptionMethod struct { + Algorithm string `xml:"Algorithm,attr"` +} + +// KeyInfo represents the XMLSEC object of the same name +type KeyInfo struct { + XMLName xml.Name `xml:"http://www.w3.org/2000/09/xmldsig# KeyInfo"` + X509Data X509Data `xml:"X509Data"` +} + +// X509Data represents the XMLSEC object of the same name +type X509Data struct { + XMLName xml.Name `xml:"http://www.w3.org/2000/09/xmldsig# X509Data"` + X509Certificates []X509Certificate `xml:"X509Certificate"` +} + +// X509Certificate represents the XMLSEC object of the same name +type X509Certificate struct { + XMLName xml.Name `xml:"http://www.w3.org/2000/09/xmldsig# X509Certificate"` + Data string `xml:",chardata"` +} + +// Endpoint represents the SAML EndpointType object. +// +// See http://docs.oasis-open.org/security/saml/v2.0/saml-metadata-2.0-os.pdf §2.2.2 +type Endpoint struct { + Binding string `xml:"Binding,attr"` + Location string `xml:"Location,attr"` + ResponseLocation string `xml:"ResponseLocation,attr,omitempty"` +} + +func checkEndpointLocation(binding string, location string) (string, error) { + // Within the SAML standard, the complex type EndpointType describes a + // SAML protocol binding endpoint at which a SAML entity can be sent + // protocol messages. In particular, the location of an endpoint type is + // defined as follows in the Metadata for the OASIS Security Assertion + // Markup Language (SAML) V2.0 - 2.2.2 Complex Type EndpointType: + // + // Location [Required] A required URI attribute that specifies the + // location of the endpoint. The allowable syntax of this URI depends + // on the protocol binding. + switch binding { + case HTTPPostBinding, + HTTPRedirectBinding, + HTTPArtifactBinding, + SOAPBinding, + SOAPBindingV1: + locationURL, err := url.Parse(location) + if err != nil { + return "", fmt.Errorf("invalid url %q: %w", location, err) + } + switch locationURL.Scheme { + case "http", "https": + // ok + default: + return "", fmt.Errorf("invalid url scheme %q for binding %q", + locationURL.Scheme, binding) + } + default: + // We don't know what form location should take, but the protocol + // requires that we validate its syntax. + // + // In practice, lots of metadata contains random bindings, for example + // "urn:mace:shibboleth:1.0:profiles:AuthnRequest" from our own test suite. + // + // We can't fail, but we also can't allow a location parameter whose syntax we + // cannot verify. The least-bad course of action here is to set location to + // and empty string, and hope the caller doesn't care need it. + location = "" + } + + return location, nil +} + +// UnmarshalXML implements xml.Unmarshaler +func (m *Endpoint) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error { + type Alias Endpoint + aux := &struct { + *Alias + }{ + Alias: (*Alias)(m), + } + if err := d.DecodeElement(aux, &start); err != nil { + return err + } + + var err error + m.Location, err = checkEndpointLocation(m.Binding, m.Location) + if err != nil { + return err + } + if m.ResponseLocation != "" { + m.ResponseLocation, err = checkEndpointLocation(m.Binding, m.ResponseLocation) + if err != nil { + return err + } + } + + return nil +} + +// IndexedEndpoint represents the SAML IndexedEndpointType object. +// +// See http://docs.oasis-open.org/security/saml/v2.0/saml-metadata-2.0-os.pdf §2.2.3 +type IndexedEndpoint struct { + Binding string `xml:"Binding,attr"` + Location string `xml:"Location,attr"` + ResponseLocation *string `xml:"ResponseLocation,attr,omitempty"` + Index int `xml:"index,attr"` + IsDefault *bool `xml:"isDefault,attr"` +} + +// UnmarshalXML implements xml.Unmarshaler +func (m *IndexedEndpoint) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error { + type Alias IndexedEndpoint + aux := &struct { + *Alias + }{ + Alias: (*Alias)(m), + } + if err := d.DecodeElement(aux, &start); err != nil { + return err + } + + var err error + m.Location, err = checkEndpointLocation(m.Binding, m.Location) + if err != nil { + return err + } + if m.ResponseLocation != nil { + responseLocation, err := checkEndpointLocation(m.Binding, *m.ResponseLocation) + if err != nil { + return err + } + if responseLocation != "" { + m.ResponseLocation = &responseLocation + } else { + m.ResponseLocation = nil + } + } + + return nil +} + +// SSODescriptor represents the SAML complex type SSODescriptor +// +// See http://docs.oasis-open.org/security/saml/v2.0/saml-metadata-2.0-os.pdf §2.4.2 +type SSODescriptor struct { + RoleDescriptor + ArtifactResolutionServices []IndexedEndpoint `xml:"ArtifactResolutionService"` + SingleLogoutServices []Endpoint `xml:"SingleLogoutService"` + ManageNameIDServices []Endpoint `xml:"ManageNameIDService"` + NameIDFormats []SAMLNameIDFormat `xml:"NameIDFormat"` +} + +// IDPSSODescriptor represents the SAML IDPSSODescriptorType object. +// +// See http://docs.oasis-open.org/security/saml/v2.0/saml-metadata-2.0-os.pdf §2.4.3 +type IDPSSODescriptor struct { + XMLName xml.Name `xml:"urn:oasis:names:tc:SAML:2.0:metadata IDPSSODescriptor"` + SSODescriptor + WantAuthnRequestsSigned *bool `xml:",attr"` + + SingleSignOnServices []Endpoint `xml:"SingleSignOnService"` + ArtifactResolutionServices []Endpoint `xml:"ArtifactResolutionService"` + NameIDMappingServices []Endpoint `xml:"NameIDMappingService"` + AssertionIDRequestServices []Endpoint `xml:"AssertionIDRequestService"` + AttributeProfiles []string `xml:"AttributeProfile"` + Attributes []Attribute `xml:"Attribute"` +} + +// SPSSODescriptor represents the SAML SPSSODescriptorType object. +// +// See http://docs.oasis-open.org/security/saml/v2.0/saml-metadata-2.0-os.pdf §2.4.2 +type SPSSODescriptor struct { + XMLName xml.Name `xml:"urn:oasis:names:tc:SAML:2.0:metadata SPSSODescriptor"` + SSODescriptor + AuthnRequestsSigned *bool `xml:",attr"` + WantAssertionsSigned *bool `xml:",attr"` + AssertionConsumerServices []IndexedEndpoint `xml:"AssertionConsumerService"` + AttributeConsumingServices []AttributeConsumingService `xml:"AttributeConsumingService"` +} + +// AttributeConsumingService represents the SAML AttributeConsumingService object. +// +// See http://docs.oasis-open.org/security/saml/v2.0/saml-metadata-2.0-os.pdf §2.4.4.1 +type AttributeConsumingService struct { + Index int `xml:"index,attr"` + IsDefault *bool `xml:"isDefault,attr"` + ServiceNames []LocalizedName `xml:"ServiceName"` + ServiceDescriptions []LocalizedName `xml:"ServiceDescription"` + RequestedAttributes []RequestedAttribute `xml:"RequestedAttribute"` +} + +// RequestedAttribute represents the SAML RequestedAttribute object. +// +// See http://docs.oasis-open.org/security/saml/v2.0/saml-metadata-2.0-os.pdf §2.4.4.2 +type RequestedAttribute struct { + Attribute + IsRequired *bool `xml:"isRequired,attr"` +} + +// AuthnAuthorityDescriptor represents the SAML AuthnAuthorityDescriptor object. +// +// See http://docs.oasis-open.org/security/saml/v2.0/saml-metadata-2.0-os.pdf §2.4.5 +type AuthnAuthorityDescriptor struct { + RoleDescriptor + AuthnQueryServices []Endpoint `xml:"AuthnQueryService"` + AssertionIDRequestServices []Endpoint `xml:"AssertionIDRequestService"` + NameIDFormats []SAMLNameIDFormat `xml:"NameIDFormat"` +} + +// PDPDescriptor represents the SAML PDPDescriptor object. +// +// See http://docs.oasis-open.org/security/saml/v2.0/saml-metadata-2.0-os.pdf §2.4.6 +type PDPDescriptor struct { + RoleDescriptor + AuthzServices []Endpoint `xml:"AuthzService"` + AssertionIDRequestServices []Endpoint `xml:"AssertionIDRequestService"` + NameIDFormats []SAMLNameIDFormat `xml:"NameIDFormat"` +} + +// AttributeAuthorityDescriptor represents the SAML AttributeAuthorityDescriptor object. +// +// See http://docs.oasis-open.org/security/saml/v2.0/saml-metadata-2.0-os.pdf §2.4.7 +type AttributeAuthorityDescriptor struct { + RoleDescriptor + AttributeServices []Endpoint `xml:"AttributeService"` + AssertionIDRequestServices []Endpoint `xml:"AssertionIDRequestService"` + NameIDFormats []SAMLNameIDFormat `xml:"NameIDFormat"` + AttributeProfiles []string `xml:"AttributeProfile"` + Attributes []Attribute `xml:"Attribute"` +} + +// AffiliationDescriptor represents the SAML AffiliationDescriptor object. +// +// See http://docs.oasis-open.org/security/saml/v2.0/saml-metadata-2.0-os.pdf §2.5 +type AffiliationDescriptor struct { + AffiliationOwnerID string `xml:"affiliationOwnerID,attr"` + ID string `xml:",attr"` + ValidUntil time.Time `xml:"validUntil,attr,omitempty"` + CacheDuration time.Duration `xml:"cacheDuration,attr"` + Signature *etree.Element + AffiliateMembers []string `xml:"AffiliateMember"` + KeyDescriptors []KeyDescriptor `xml:"KeyDescriptor"` +} diff --git a/pkg/lib/saml/samlprotocol/schema.go b/pkg/lib/saml/samlprotocol/schema.go index cb796f4183..245f66e63b 100644 --- a/pkg/lib/saml/samlprotocol/schema.go +++ b/pkg/lib/saml/samlprotocol/schema.go @@ -9,15 +9,12 @@ import ( "time" "github.com/beevik/etree" - crewjamsaml "github.com/crewjam/saml" "github.com/russellhaering/goxmldsig/etreeutils" ) // Note(tung): This file was copied from https://github.com/crewjam/saml/blob/193e551d9a8420216fae88c2b8f4b46696b7bb63/schema.go // We've made some changes to some schemas so that they can be used properly in our code -type RelaxedTime = crewjamsaml.RelaxedTime - // RequestedAuthnContext represents the SAML object of the same name, an indication of the // requirements on the authentication process. type RequestedAuthnContext struct { diff --git a/pkg/lib/saml/samlprotocol/time.go b/pkg/lib/saml/samlprotocol/time.go new file mode 100644 index 0000000000..8ade9477fb --- /dev/null +++ b/pkg/lib/saml/samlprotocol/time.go @@ -0,0 +1,47 @@ +// NOTE: Copied from https://github.com/crewjam/saml/blob/1dfacaea137943953459ad09aedf007c1b92deb2/time.go + +package samlprotocol + +import "time" + +type RelaxedTime time.Time + +const timeFormat = "2006-01-02T15:04:05.999Z07:00" + +func (m RelaxedTime) MarshalText() ([]byte, error) { + // According to section 1.2.2 of the OASIS SAML 1.1 spec, we can't trust + // other applications to handle time resolution finer than a millisecond. + // + // The time MUST be expressed in UTC. + return []byte(m.String()), nil +} + +func (m RelaxedTime) String() string { + return time.Time(m).UTC().Format(timeFormat) +} + +func (m *RelaxedTime) UnmarshalText(text []byte) error { + if len(text) == 0 { + *m = RelaxedTime(time.Time{}) + return nil + } + t, err1 := time.Parse(time.RFC3339, string(text)) + if err1 == nil { + *m = RelaxedTime(t) + return nil + } + + t, err2 := time.Parse(time.RFC3339Nano, string(text)) + if err2 == nil { + *m = RelaxedTime(t) + return nil + } + + t, err2 = time.Parse("2006-01-02T15:04:05.999999999", string(text)) + if err2 == nil { + *m = RelaxedTime(t) + return nil + } + + return err1 +} diff --git a/pkg/lib/saml/service.go b/pkg/lib/saml/service.go index 404d39cb8a..93114f467e 100644 --- a/pkg/lib/saml/service.go +++ b/pkg/lib/saml/service.go @@ -11,7 +11,6 @@ import ( "time" "github.com/beevik/etree" - crewjamsaml "github.com/crewjam/saml" dsig "github.com/russellhaering/goxmldsig" @@ -89,14 +88,14 @@ func (s *Service) IdpMetadata(serviceProviderId string) (*samlprotocol.Metadata, return nil, samlprotocol.ErrServiceProviderNotFound } - keyDescriptors := []crewjamsaml.KeyDescriptor{} + keyDescriptors := []samlprotocol.KeyDescriptor{} if cert, ok := s.SAMLIdpSigningMaterials.FindSigningCert(s.SAMLConfig.Signing.KeyID); ok { keyDescriptors = append(keyDescriptors, - crewjamsaml.KeyDescriptor{ + samlprotocol.KeyDescriptor{ Use: "signing", - KeyInfo: crewjamsaml.KeyInfo{ - X509Data: crewjamsaml.X509Data{ - X509Certificates: []crewjamsaml.X509Certificate{ + KeyInfo: samlprotocol.KeyInfo{ + X509Data: samlprotocol.X509Data{ + X509Certificates: []samlprotocol.X509Certificate{ {Data: cert.Certificate.Base64Data()}, }, }, @@ -104,20 +103,20 @@ func (s *Service) IdpMetadata(serviceProviderId string) (*samlprotocol.Metadata, }) } - ssoServices := []crewjamsaml.Endpoint{} + ssoServices := []samlprotocol.Endpoint{} for _, binding := range samlprotocol.SSOSupportedBindings { - ssoServices = append(ssoServices, crewjamsaml.Endpoint{ + ssoServices = append(ssoServices, samlprotocol.Endpoint{ Binding: string(binding), Location: s.Endpoints.SAMLLoginURL(sp.GetID()).String(), }) } - sloServices := []crewjamsaml.Endpoint{} + sloServices := []samlprotocol.Endpoint{} if sp.SLOEnabled { for _, binding := range samlprotocol.SLOSupportedBindings { - sloServices = append(sloServices, crewjamsaml.Endpoint{ + sloServices = append(sloServices, samlprotocol.Endpoint{ Binding: string(binding), Location: s.Endpoints.SAMLLogoutURL(sp.GetID()).String(), }) @@ -126,15 +125,15 @@ func (s *Service) IdpMetadata(serviceProviderId string) (*samlprotocol.Metadata, descriptor := samlprotocol.EntityDescriptor{ EntityID: s.IdpEntityID(), - IDPSSODescriptors: []crewjamsaml.IDPSSODescriptor{ + IDPSSODescriptors: []samlprotocol.IDPSSODescriptor{ { - SSODescriptor: crewjamsaml.SSODescriptor{ - RoleDescriptor: crewjamsaml.RoleDescriptor{ + SSODescriptor: samlprotocol.SSODescriptor{ + RoleDescriptor: samlprotocol.RoleDescriptor{ ProtocolSupportEnumeration: "urn:oasis:names:tc:SAML:2.0:protocol", KeyDescriptors: keyDescriptors, }, - NameIDFormats: []crewjamsaml.NameIDFormat{ - crewjamsaml.NameIDFormat(sp.NameIDFormat), + NameIDFormats: []samlprotocol.SAMLNameIDFormat{ + sp.NameIDFormat, }, SingleLogoutServices: sloServices, },