bump(github.com/coreos/go-oidc): 065b426bd41667456c1a924468f507673629c46b
This commit is contained in:
parent
2d8cb9c4ad
commit
379af0405c
43
Godeps/Godeps.json
generated
43
Godeps/Godeps.json
generated
@ -895,24 +895,8 @@
|
|||||||
"Rev": "95a726a27e09030f9ccbd9982a1508f5a6d25ada"
|
"Rev": "95a726a27e09030f9ccbd9982a1508f5a6d25ada"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "github.com/coreos/go-oidc/http",
|
"ImportPath": "github.com/coreos/go-oidc",
|
||||||
"Rev": "a4973d9a4225417aecf5d450a9522f00c1f7130f"
|
"Rev": "065b426bd41667456c1a924468f507673629c46b"
|
||||||
},
|
|
||||||
{
|
|
||||||
"ImportPath": "github.com/coreos/go-oidc/jose",
|
|
||||||
"Rev": "a4973d9a4225417aecf5d450a9522f00c1f7130f"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ImportPath": "github.com/coreos/go-oidc/key",
|
|
||||||
"Rev": "a4973d9a4225417aecf5d450a9522f00c1f7130f"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ImportPath": "github.com/coreos/go-oidc/oauth2",
|
|
||||||
"Rev": "a4973d9a4225417aecf5d450a9522f00c1f7130f"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ImportPath": "github.com/coreos/go-oidc/oidc",
|
|
||||||
"Rev": "a4973d9a4225417aecf5d450a9522f00c1f7130f"
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "github.com/coreos/go-semver/semver",
|
"ImportPath": "github.com/coreos/go-semver/semver",
|
||||||
@ -953,21 +937,6 @@
|
|||||||
"Comment": "v2-8-gfa29b1d",
|
"Comment": "v2-8-gfa29b1d",
|
||||||
"Rev": "fa29b1d70f0beaddd4c7021607cc3c3be8ce94b8"
|
"Rev": "fa29b1d70f0beaddd4c7021607cc3c3be8ce94b8"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"ImportPath": "github.com/coreos/pkg/health",
|
|
||||||
"Comment": "v2-8-gfa29b1d",
|
|
||||||
"Rev": "fa29b1d70f0beaddd4c7021607cc3c3be8ce94b8"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ImportPath": "github.com/coreos/pkg/httputil",
|
|
||||||
"Comment": "v2-8-gfa29b1d",
|
|
||||||
"Rev": "fa29b1d70f0beaddd4c7021607cc3c3be8ce94b8"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ImportPath": "github.com/coreos/pkg/timeutil",
|
|
||||||
"Comment": "v2-8-gfa29b1d",
|
|
||||||
"Rev": "fa29b1d70f0beaddd4c7021607cc3c3be8ce94b8"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"ImportPath": "github.com/coreos/rkt/api/v1alpha",
|
"ImportPath": "github.com/coreos/rkt/api/v1alpha",
|
||||||
"Comment": "v1.25.0",
|
"Comment": "v1.25.0",
|
||||||
@ -2480,6 +2449,14 @@
|
|||||||
"ImportPath": "github.com/pmezard/go-difflib/difflib",
|
"ImportPath": "github.com/pmezard/go-difflib/difflib",
|
||||||
"Rev": "d8ed2627bdf02c080bf22230dbb337003b7aba2d"
|
"Rev": "d8ed2627bdf02c080bf22230dbb337003b7aba2d"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "github.com/pquerna/cachecontrol",
|
||||||
|
"Rev": "0dec1b30a0215bb68605dfc568e8855066c9202d"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "github.com/pquerna/cachecontrol/cacheobject",
|
||||||
|
"Rev": "0dec1b30a0215bb68605dfc568e8855066c9202d"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "github.com/prometheus/client_golang/prometheus",
|
"ImportPath": "github.com/prometheus/client_golang/prometheus",
|
||||||
"Comment": "v0.8.0-83-ge7e9030",
|
"Comment": "v0.8.0-83-ge7e9030",
|
||||||
|
1892
Godeps/LICENSES
generated
1892
Godeps/LICENSES
generated
File diff suppressed because it is too large
Load Diff
@ -172,7 +172,6 @@ filegroup(
|
|||||||
"//staging/src/k8s.io/client-go/listers/storage/v1alpha1:all-srcs",
|
"//staging/src/k8s.io/client-go/listers/storage/v1alpha1:all-srcs",
|
||||||
"//staging/src/k8s.io/client-go/listers/storage/v1beta1:all-srcs",
|
"//staging/src/k8s.io/client-go/listers/storage/v1beta1:all-srcs",
|
||||||
"//staging/src/k8s.io/client-go/pkg/version:all-srcs",
|
"//staging/src/k8s.io/client-go/pkg/version:all-srcs",
|
||||||
"//staging/src/k8s.io/client-go/plugin/pkg/auth/authenticator/token/oidc/testing:all-srcs",
|
|
||||||
"//staging/src/k8s.io/client-go/plugin/pkg/client/auth:all-srcs",
|
"//staging/src/k8s.io/client-go/plugin/pkg/client/auth:all-srcs",
|
||||||
"//staging/src/k8s.io/client-go/rest:all-srcs",
|
"//staging/src/k8s.io/client-go/rest:all-srcs",
|
||||||
"//staging/src/k8s.io/client-go/scale:all-srcs",
|
"//staging/src/k8s.io/client-go/scale:all-srcs",
|
||||||
|
68
staging/src/k8s.io/apiserver/Godeps/Godeps.json
generated
68
staging/src/k8s.io/apiserver/Godeps/Godeps.json
generated
@ -327,24 +327,8 @@
|
|||||||
"Rev": "95a726a27e09030f9ccbd9982a1508f5a6d25ada"
|
"Rev": "95a726a27e09030f9ccbd9982a1508f5a6d25ada"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "github.com/coreos/go-oidc/http",
|
"ImportPath": "github.com/coreos/go-oidc",
|
||||||
"Rev": "a4973d9a4225417aecf5d450a9522f00c1f7130f"
|
"Rev": "065b426bd41667456c1a924468f507673629c46b"
|
||||||
},
|
|
||||||
{
|
|
||||||
"ImportPath": "github.com/coreos/go-oidc/jose",
|
|
||||||
"Rev": "a4973d9a4225417aecf5d450a9522f00c1f7130f"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ImportPath": "github.com/coreos/go-oidc/key",
|
|
||||||
"Rev": "a4973d9a4225417aecf5d450a9522f00c1f7130f"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ImportPath": "github.com/coreos/go-oidc/oauth2",
|
|
||||||
"Rev": "a4973d9a4225417aecf5d450a9522f00c1f7130f"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ImportPath": "github.com/coreos/go-oidc/oidc",
|
|
||||||
"Rev": "a4973d9a4225417aecf5d450a9522f00c1f7130f"
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "github.com/coreos/go-semver/semver",
|
"ImportPath": "github.com/coreos/go-semver/semver",
|
||||||
@ -362,18 +346,6 @@
|
|||||||
"ImportPath": "github.com/coreos/pkg/capnslog",
|
"ImportPath": "github.com/coreos/pkg/capnslog",
|
||||||
"Rev": "fa29b1d70f0beaddd4c7021607cc3c3be8ce94b8"
|
"Rev": "fa29b1d70f0beaddd4c7021607cc3c3be8ce94b8"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"ImportPath": "github.com/coreos/pkg/health",
|
|
||||||
"Rev": "fa29b1d70f0beaddd4c7021607cc3c3be8ce94b8"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ImportPath": "github.com/coreos/pkg/httputil",
|
|
||||||
"Rev": "fa29b1d70f0beaddd4c7021607cc3c3be8ce94b8"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ImportPath": "github.com/coreos/pkg/timeutil",
|
|
||||||
"Rev": "fa29b1d70f0beaddd4c7021607cc3c3be8ce94b8"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"ImportPath": "github.com/davecgh/go-spew/spew",
|
"ImportPath": "github.com/davecgh/go-spew/spew",
|
||||||
"Rev": "782f4967f2dc4564575ca782fe2d04090b5faca8"
|
"Rev": "782f4967f2dc4564575ca782fe2d04090b5faca8"
|
||||||
@ -558,6 +530,14 @@
|
|||||||
"ImportPath": "github.com/pmezard/go-difflib/difflib",
|
"ImportPath": "github.com/pmezard/go-difflib/difflib",
|
||||||
"Rev": "d8ed2627bdf02c080bf22230dbb337003b7aba2d"
|
"Rev": "d8ed2627bdf02c080bf22230dbb337003b7aba2d"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "github.com/pquerna/cachecontrol",
|
||||||
|
"Rev": "0dec1b30a0215bb68605dfc568e8855066c9202d"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "github.com/pquerna/cachecontrol/cacheobject",
|
||||||
|
"Rev": "0dec1b30a0215bb68605dfc568e8855066c9202d"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "github.com/prometheus/client_golang/prometheus",
|
"ImportPath": "github.com/prometheus/client_golang/prometheus",
|
||||||
"Rev": "e7e903064f5e9eb5da98208bae10b475d4db0f8c"
|
"Rev": "e7e903064f5e9eb5da98208bae10b475d4db0f8c"
|
||||||
@ -614,6 +594,14 @@
|
|||||||
"ImportPath": "golang.org/x/crypto/blowfish",
|
"ImportPath": "golang.org/x/crypto/blowfish",
|
||||||
"Rev": "81e90905daefcd6fd217b62423c0908922eadb30"
|
"Rev": "81e90905daefcd6fd217b62423c0908922eadb30"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "golang.org/x/crypto/ed25519",
|
||||||
|
"Rev": "81e90905daefcd6fd217b62423c0908922eadb30"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "golang.org/x/crypto/ed25519/internal/edwards25519",
|
||||||
|
"Rev": "81e90905daefcd6fd217b62423c0908922eadb30"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "golang.org/x/crypto/nacl/secretbox",
|
"ImportPath": "golang.org/x/crypto/nacl/secretbox",
|
||||||
"Rev": "81e90905daefcd6fd217b62423c0908922eadb30"
|
"Rev": "81e90905daefcd6fd217b62423c0908922eadb30"
|
||||||
@ -670,6 +658,14 @@
|
|||||||
"ImportPath": "golang.org/x/net/websocket",
|
"ImportPath": "golang.org/x/net/websocket",
|
||||||
"Rev": "1c05540f6879653db88113bc4a2b70aec4bd491f"
|
"Rev": "1c05540f6879653db88113bc4a2b70aec4bd491f"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "golang.org/x/oauth2",
|
||||||
|
"Rev": "a6bd8cefa1811bd24b86f8902872e4e8225f74c4"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "golang.org/x/oauth2/internal",
|
||||||
|
"Rev": "a6bd8cefa1811bd24b86f8902872e4e8225f74c4"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "golang.org/x/sys/unix",
|
"ImportPath": "golang.org/x/sys/unix",
|
||||||
"Rev": "95c6576299259db960f6c5b9b69ea52422860fce"
|
"Rev": "95c6576299259db960f6c5b9b69ea52422860fce"
|
||||||
@ -814,6 +810,18 @@
|
|||||||
"ImportPath": "gopkg.in/natefinch/lumberjack.v2",
|
"ImportPath": "gopkg.in/natefinch/lumberjack.v2",
|
||||||
"Rev": "20b71e5b60d756d3d2f80def009790325acc2b23"
|
"Rev": "20b71e5b60d756d3d2f80def009790325acc2b23"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "gopkg.in/square/go-jose.v2",
|
||||||
|
"Rev": "f8f38de21b4dcd69d0413faf231983f5fd6634b1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "gopkg.in/square/go-jose.v2/cipher",
|
||||||
|
"Rev": "f8f38de21b4dcd69d0413faf231983f5fd6634b1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "gopkg.in/square/go-jose.v2/json",
|
||||||
|
"Rev": "f8f38de21b4dcd69d0413faf231983f5fd6634b1"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "gopkg.in/yaml.v2",
|
"ImportPath": "gopkg.in/yaml.v2",
|
||||||
"Rev": "670d4cfef0544295bc27a114dbac37980d83185a"
|
"Rev": "670d4cfef0544295bc27a114dbac37980d83185a"
|
||||||
|
@ -9,13 +9,13 @@ load(
|
|||||||
go_test(
|
go_test(
|
||||||
name = "go_default_test",
|
name = "go_default_test",
|
||||||
srcs = ["oidc_test.go"],
|
srcs = ["oidc_test.go"],
|
||||||
|
data = glob(["testdata/**"]),
|
||||||
embed = [":go_default_library"],
|
embed = [":go_default_library"],
|
||||||
importpath = "k8s.io/apiserver/plugin/pkg/authenticator/token/oidc",
|
importpath = "k8s.io/apiserver/plugin/pkg/authenticator/token/oidc",
|
||||||
deps = [
|
deps = [
|
||||||
"//vendor/github.com/coreos/go-oidc/jose:go_default_library",
|
"//vendor/github.com/coreos/go-oidc:go_default_library",
|
||||||
"//vendor/github.com/coreos/go-oidc/oidc:go_default_library",
|
"//vendor/gopkg.in/square/go-jose.v2:go_default_library",
|
||||||
"//vendor/k8s.io/apiserver/pkg/authentication/user:go_default_library",
|
"//vendor/k8s.io/apiserver/pkg/authentication/user:go_default_library",
|
||||||
"//vendor/k8s.io/apiserver/plugin/pkg/authenticator/token/oidc/testing:go_default_library",
|
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -24,11 +24,10 @@ go_library(
|
|||||||
srcs = ["oidc.go"],
|
srcs = ["oidc.go"],
|
||||||
importpath = "k8s.io/apiserver/plugin/pkg/authenticator/token/oidc",
|
importpath = "k8s.io/apiserver/plugin/pkg/authenticator/token/oidc",
|
||||||
deps = [
|
deps = [
|
||||||
"//vendor/github.com/coreos/go-oidc/jose:go_default_library",
|
"//vendor/github.com/coreos/go-oidc:go_default_library",
|
||||||
"//vendor/github.com/coreos/go-oidc/oidc:go_default_library",
|
|
||||||
"//vendor/github.com/golang/glog:go_default_library",
|
"//vendor/github.com/golang/glog:go_default_library",
|
||||||
"//vendor/k8s.io/apimachinery/pkg/util/net:go_default_library",
|
"//vendor/k8s.io/apimachinery/pkg/util/net:go_default_library",
|
||||||
"//vendor/k8s.io/apimachinery/pkg/util/runtime:go_default_library",
|
"//vendor/k8s.io/apimachinery/pkg/util/wait:go_default_library",
|
||||||
"//vendor/k8s.io/apiserver/pkg/authentication/user:go_default_library",
|
"//vendor/k8s.io/apiserver/pkg/authentication/user:go_default_library",
|
||||||
"//vendor/k8s.io/client-go/util/cert:go_default_library",
|
"//vendor/k8s.io/client-go/util/cert:go_default_library",
|
||||||
],
|
],
|
||||||
@ -43,9 +42,6 @@ filegroup(
|
|||||||
|
|
||||||
filegroup(
|
filegroup(
|
||||||
name = "all-srcs",
|
name = "all-srcs",
|
||||||
srcs = [
|
srcs = [":package-srcs"],
|
||||||
":package-srcs",
|
|
||||||
"//staging/src/k8s.io/apiserver/plugin/pkg/authenticator/token/oidc/testing:all-srcs",
|
|
||||||
],
|
|
||||||
tags = ["automanaged"],
|
tags = ["automanaged"],
|
||||||
)
|
)
|
||||||
|
@ -1,30 +0,0 @@
|
|||||||
package(default_visibility = ["//visibility:public"])
|
|
||||||
|
|
||||||
load(
|
|
||||||
"@io_bazel_rules_go//go:def.bzl",
|
|
||||||
"go_library",
|
|
||||||
)
|
|
||||||
|
|
||||||
go_library(
|
|
||||||
name = "go_default_library",
|
|
||||||
srcs = ["provider.go"],
|
|
||||||
importpath = "k8s.io/apiserver/plugin/pkg/authenticator/token/oidc/testing",
|
|
||||||
deps = [
|
|
||||||
"//vendor/github.com/coreos/go-oidc/jose:go_default_library",
|
|
||||||
"//vendor/github.com/coreos/go-oidc/key:go_default_library",
|
|
||||||
"//vendor/github.com/coreos/go-oidc/oidc:go_default_library",
|
|
||||||
],
|
|
||||||
)
|
|
||||||
|
|
||||||
filegroup(
|
|
||||||
name = "package-srcs",
|
|
||||||
srcs = glob(["**"]),
|
|
||||||
tags = ["automanaged"],
|
|
||||||
visibility = ["//visibility:private"],
|
|
||||||
)
|
|
||||||
|
|
||||||
filegroup(
|
|
||||||
name = "all-srcs",
|
|
||||||
srcs = [":package-srcs"],
|
|
||||||
tags = ["automanaged"],
|
|
||||||
)
|
|
36
staging/src/k8s.io/client-go/Godeps/Godeps.json
generated
36
staging/src/k8s.io/client-go/Godeps/Godeps.json
generated
@ -30,38 +30,6 @@
|
|||||||
"ImportPath": "github.com/Azure/go-autorest/autorest/date",
|
"ImportPath": "github.com/Azure/go-autorest/autorest/date",
|
||||||
"Rev": "d4e6b95c12a08b4de2d48b45d5b4d594e5d32fab"
|
"Rev": "d4e6b95c12a08b4de2d48b45d5b4d594e5d32fab"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"ImportPath": "github.com/coreos/go-oidc/http",
|
|
||||||
"Rev": "a4973d9a4225417aecf5d450a9522f00c1f7130f"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ImportPath": "github.com/coreos/go-oidc/jose",
|
|
||||||
"Rev": "a4973d9a4225417aecf5d450a9522f00c1f7130f"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ImportPath": "github.com/coreos/go-oidc/key",
|
|
||||||
"Rev": "a4973d9a4225417aecf5d450a9522f00c1f7130f"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ImportPath": "github.com/coreos/go-oidc/oauth2",
|
|
||||||
"Rev": "a4973d9a4225417aecf5d450a9522f00c1f7130f"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ImportPath": "github.com/coreos/go-oidc/oidc",
|
|
||||||
"Rev": "a4973d9a4225417aecf5d450a9522f00c1f7130f"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ImportPath": "github.com/coreos/pkg/health",
|
|
||||||
"Rev": "fa29b1d70f0beaddd4c7021607cc3c3be8ce94b8"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ImportPath": "github.com/coreos/pkg/httputil",
|
|
||||||
"Rev": "fa29b1d70f0beaddd4c7021607cc3c3be8ce94b8"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ImportPath": "github.com/coreos/pkg/timeutil",
|
|
||||||
"Rev": "fa29b1d70f0beaddd4c7021607cc3c3be8ce94b8"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"ImportPath": "github.com/davecgh/go-spew/spew",
|
"ImportPath": "github.com/davecgh/go-spew/spew",
|
||||||
"Rev": "782f4967f2dc4564575ca782fe2d04090b5faca8"
|
"Rev": "782f4967f2dc4564575ca782fe2d04090b5faca8"
|
||||||
@ -178,10 +146,6 @@
|
|||||||
"ImportPath": "github.com/imdario/mergo",
|
"ImportPath": "github.com/imdario/mergo",
|
||||||
"Rev": "6633656539c1639d9d78127b7d47c622b5d7b6dc"
|
"Rev": "6633656539c1639d9d78127b7d47c622b5d7b6dc"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"ImportPath": "github.com/jonboulle/clockwork",
|
|
||||||
"Rev": "72f9bd7c4e0c2a40055ab3d0f09654f730cce982"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"ImportPath": "github.com/json-iterator/go",
|
"ImportPath": "github.com/json-iterator/go",
|
||||||
"Rev": "13f86432b882000a51c6e610c620974462691a97"
|
"Rev": "13f86432b882000a51c6e610c620974462691a97"
|
||||||
|
@ -1,30 +0,0 @@
|
|||||||
package(default_visibility = ["//visibility:public"])
|
|
||||||
|
|
||||||
load(
|
|
||||||
"@io_bazel_rules_go//go:def.bzl",
|
|
||||||
"go_library",
|
|
||||||
)
|
|
||||||
|
|
||||||
go_library(
|
|
||||||
name = "go_default_library",
|
|
||||||
srcs = ["provider.go"],
|
|
||||||
importpath = "k8s.io/client-go/plugin/pkg/auth/authenticator/token/oidc/testing",
|
|
||||||
deps = [
|
|
||||||
"//vendor/github.com/coreos/go-oidc/jose:go_default_library",
|
|
||||||
"//vendor/github.com/coreos/go-oidc/key:go_default_library",
|
|
||||||
"//vendor/github.com/coreos/go-oidc/oidc:go_default_library",
|
|
||||||
],
|
|
||||||
)
|
|
||||||
|
|
||||||
filegroup(
|
|
||||||
name = "package-srcs",
|
|
||||||
srcs = glob(["**"]),
|
|
||||||
tags = ["automanaged"],
|
|
||||||
visibility = ["//visibility:private"],
|
|
||||||
)
|
|
||||||
|
|
||||||
filegroup(
|
|
||||||
name = "all-srcs",
|
|
||||||
srcs = [":package-srcs"],
|
|
||||||
tags = ["automanaged"],
|
|
||||||
)
|
|
10
vendor/BUILD
vendored
10
vendor/BUILD
vendored
@ -119,11 +119,7 @@ filegroup(
|
|||||||
"//vendor/github.com/coreos/etcd/store:all-srcs",
|
"//vendor/github.com/coreos/etcd/store:all-srcs",
|
||||||
"//vendor/github.com/coreos/etcd/version:all-srcs",
|
"//vendor/github.com/coreos/etcd/version:all-srcs",
|
||||||
"//vendor/github.com/coreos/etcd/wal:all-srcs",
|
"//vendor/github.com/coreos/etcd/wal:all-srcs",
|
||||||
"//vendor/github.com/coreos/go-oidc/http:all-srcs",
|
"//vendor/github.com/coreos/go-oidc:all-srcs",
|
||||||
"//vendor/github.com/coreos/go-oidc/jose:all-srcs",
|
|
||||||
"//vendor/github.com/coreos/go-oidc/key:all-srcs",
|
|
||||||
"//vendor/github.com/coreos/go-oidc/oauth2:all-srcs",
|
|
||||||
"//vendor/github.com/coreos/go-oidc/oidc:all-srcs",
|
|
||||||
"//vendor/github.com/coreos/go-semver/semver:all-srcs",
|
"//vendor/github.com/coreos/go-semver/semver:all-srcs",
|
||||||
"//vendor/github.com/coreos/go-systemd/daemon:all-srcs",
|
"//vendor/github.com/coreos/go-systemd/daemon:all-srcs",
|
||||||
"//vendor/github.com/coreos/go-systemd/dbus:all-srcs",
|
"//vendor/github.com/coreos/go-systemd/dbus:all-srcs",
|
||||||
@ -132,9 +128,6 @@ filegroup(
|
|||||||
"//vendor/github.com/coreos/go-systemd/util:all-srcs",
|
"//vendor/github.com/coreos/go-systemd/util:all-srcs",
|
||||||
"//vendor/github.com/coreos/pkg/capnslog:all-srcs",
|
"//vendor/github.com/coreos/pkg/capnslog:all-srcs",
|
||||||
"//vendor/github.com/coreos/pkg/dlopen:all-srcs",
|
"//vendor/github.com/coreos/pkg/dlopen:all-srcs",
|
||||||
"//vendor/github.com/coreos/pkg/health:all-srcs",
|
|
||||||
"//vendor/github.com/coreos/pkg/httputil:all-srcs",
|
|
||||||
"//vendor/github.com/coreos/pkg/timeutil:all-srcs",
|
|
||||||
"//vendor/github.com/coreos/rkt/api/v1alpha:all-srcs",
|
"//vendor/github.com/coreos/rkt/api/v1alpha:all-srcs",
|
||||||
"//vendor/github.com/cpuguy83/go-md2man/md2man:all-srcs",
|
"//vendor/github.com/cpuguy83/go-md2man/md2man:all-srcs",
|
||||||
"//vendor/github.com/cyphar/filepath-securejoin:all-srcs",
|
"//vendor/github.com/cyphar/filepath-securejoin:all-srcs",
|
||||||
@ -314,6 +307,7 @@ filegroup(
|
|||||||
"//vendor/github.com/pkg/errors:all-srcs",
|
"//vendor/github.com/pkg/errors:all-srcs",
|
||||||
"//vendor/github.com/pkg/sftp:all-srcs",
|
"//vendor/github.com/pkg/sftp:all-srcs",
|
||||||
"//vendor/github.com/pmezard/go-difflib/difflib:all-srcs",
|
"//vendor/github.com/pmezard/go-difflib/difflib:all-srcs",
|
||||||
|
"//vendor/github.com/pquerna/cachecontrol:all-srcs",
|
||||||
"//vendor/github.com/prometheus/client_golang/prometheus:all-srcs",
|
"//vendor/github.com/prometheus/client_golang/prometheus:all-srcs",
|
||||||
"//vendor/github.com/prometheus/client_model/go:all-srcs",
|
"//vendor/github.com/prometheus/client_model/go:all-srcs",
|
||||||
"//vendor/github.com/prometheus/common/expfmt:all-srcs",
|
"//vendor/github.com/prometheus/common/expfmt:all-srcs",
|
||||||
|
2
vendor/github.com/coreos/go-oidc/.gitignore
generated
vendored
Normal file
2
vendor/github.com/coreos/go-oidc/.gitignore
generated
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
/bin
|
||||||
|
/gopath
|
16
vendor/github.com/coreos/go-oidc/.travis.yml
generated
vendored
Normal file
16
vendor/github.com/coreos/go-oidc/.travis.yml
generated
vendored
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
language: go
|
||||||
|
|
||||||
|
go:
|
||||||
|
- 1.7.5
|
||||||
|
- 1.8
|
||||||
|
|
||||||
|
install:
|
||||||
|
- go get -v -t github.com/coreos/go-oidc/...
|
||||||
|
- go get golang.org/x/tools/cmd/cover
|
||||||
|
- go get github.com/golang/lint/golint
|
||||||
|
|
||||||
|
script:
|
||||||
|
- ./test
|
||||||
|
|
||||||
|
notifications:
|
||||||
|
email: false
|
17
vendor/github.com/coreos/go-oidc/jose/BUILD → vendor/github.com/coreos/go-oidc/BUILD
generated
vendored
17
vendor/github.com/coreos/go-oidc/jose/BUILD → vendor/github.com/coreos/go-oidc/BUILD
generated
vendored
@ -3,17 +3,18 @@ load("@io_bazel_rules_go//go:def.bzl", "go_library")
|
|||||||
go_library(
|
go_library(
|
||||||
name = "go_default_library",
|
name = "go_default_library",
|
||||||
srcs = [
|
srcs = [
|
||||||
"claims.go",
|
|
||||||
"doc.go",
|
|
||||||
"jose.go",
|
"jose.go",
|
||||||
"jwk.go",
|
"jwks.go",
|
||||||
"jws.go",
|
"oidc.go",
|
||||||
"jwt.go",
|
"verify.go",
|
||||||
"sig.go",
|
|
||||||
"sig_rsa.go",
|
|
||||||
],
|
],
|
||||||
importpath = "github.com/coreos/go-oidc/jose",
|
importpath = "github.com/coreos/go-oidc",
|
||||||
visibility = ["//visibility:public"],
|
visibility = ["//visibility:public"],
|
||||||
|
deps = [
|
||||||
|
"//vendor/github.com/pquerna/cachecontrol:go_default_library",
|
||||||
|
"//vendor/golang.org/x/oauth2:go_default_library",
|
||||||
|
"//vendor/gopkg.in/square/go-jose.v2:go_default_library",
|
||||||
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
filegroup(
|
filegroup(
|
71
vendor/github.com/coreos/go-oidc/CONTRIBUTING.md
generated
vendored
Normal file
71
vendor/github.com/coreos/go-oidc/CONTRIBUTING.md
generated
vendored
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
# How to Contribute
|
||||||
|
|
||||||
|
CoreOS projects are [Apache 2.0 licensed](LICENSE) and accept contributions via
|
||||||
|
GitHub pull requests. This document outlines some of the conventions on
|
||||||
|
development workflow, commit message formatting, contact points and other
|
||||||
|
resources to make it easier to get your contribution accepted.
|
||||||
|
|
||||||
|
# Certificate of Origin
|
||||||
|
|
||||||
|
By contributing to this project you agree to the Developer Certificate of
|
||||||
|
Origin (DCO). This document was created by the Linux Kernel community and is a
|
||||||
|
simple statement that you, as a contributor, have the legal right to make the
|
||||||
|
contribution. See the [DCO](DCO) file for details.
|
||||||
|
|
||||||
|
# Email and Chat
|
||||||
|
|
||||||
|
The project currently uses the general CoreOS email list and IRC channel:
|
||||||
|
- Email: [coreos-dev](https://groups.google.com/forum/#!forum/coreos-dev)
|
||||||
|
- IRC: #[coreos](irc://irc.freenode.org:6667/#coreos) IRC channel on freenode.org
|
||||||
|
|
||||||
|
Please avoid emailing maintainers found in the MAINTAINERS file directly. They
|
||||||
|
are very busy and read the mailing lists.
|
||||||
|
|
||||||
|
## Getting Started
|
||||||
|
|
||||||
|
- Fork the repository on GitHub
|
||||||
|
- Read the [README](README.md) for build and test instructions
|
||||||
|
- Play with the project, submit bugs, submit patches!
|
||||||
|
|
||||||
|
## Contribution Flow
|
||||||
|
|
||||||
|
This is a rough outline of what a contributor's workflow looks like:
|
||||||
|
|
||||||
|
- Create a topic branch from where you want to base your work (usually master).
|
||||||
|
- Make commits of logical units.
|
||||||
|
- Make sure your commit messages are in the proper format (see below).
|
||||||
|
- Push your changes to a topic branch in your fork of the repository.
|
||||||
|
- Make sure the tests pass, and add any new tests as appropriate.
|
||||||
|
- Submit a pull request to the original repository.
|
||||||
|
|
||||||
|
Thanks for your contributions!
|
||||||
|
|
||||||
|
### Format of the Commit Message
|
||||||
|
|
||||||
|
We follow a rough convention for commit messages that is designed to answer two
|
||||||
|
questions: what changed and why. The subject line should feature the what and
|
||||||
|
the body of the commit should describe the why.
|
||||||
|
|
||||||
|
```
|
||||||
|
scripts: add the test-cluster command
|
||||||
|
|
||||||
|
this uses tmux to setup a test cluster that you can easily kill and
|
||||||
|
start for debugging.
|
||||||
|
|
||||||
|
Fixes #38
|
||||||
|
```
|
||||||
|
|
||||||
|
The format can be described more formally as follows:
|
||||||
|
|
||||||
|
```
|
||||||
|
<subsystem>: <what changed>
|
||||||
|
<BLANK LINE>
|
||||||
|
<why this change was made>
|
||||||
|
<BLANK LINE>
|
||||||
|
<footer>
|
||||||
|
```
|
||||||
|
|
||||||
|
The first line is the subject and should be no longer than 70 characters, the
|
||||||
|
second line is always blank, and other lines should be wrapped at 80 characters.
|
||||||
|
This allows the message to be easier to read on GitHub as well as in various
|
||||||
|
git tools.
|
36
vendor/github.com/coreos/go-oidc/DCO
generated
vendored
Normal file
36
vendor/github.com/coreos/go-oidc/DCO
generated
vendored
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
Developer Certificate of Origin
|
||||||
|
Version 1.1
|
||||||
|
|
||||||
|
Copyright (C) 2004, 2006 The Linux Foundation and its contributors.
|
||||||
|
660 York Street, Suite 102,
|
||||||
|
San Francisco, CA 94110 USA
|
||||||
|
|
||||||
|
Everyone is permitted to copy and distribute verbatim copies of this
|
||||||
|
license document, but changing it is not allowed.
|
||||||
|
|
||||||
|
|
||||||
|
Developer's Certificate of Origin 1.1
|
||||||
|
|
||||||
|
By making a contribution to this project, I certify that:
|
||||||
|
|
||||||
|
(a) The contribution was created in whole or in part by me and I
|
||||||
|
have the right to submit it under the open source license
|
||||||
|
indicated in the file; or
|
||||||
|
|
||||||
|
(b) The contribution is based upon previous work that, to the best
|
||||||
|
of my knowledge, is covered under an appropriate open source
|
||||||
|
license and I have the right under that license to submit that
|
||||||
|
work with modifications, whether created in whole or in part
|
||||||
|
by me, under the same open source license (unless I am
|
||||||
|
permitted to submit under a different license), as indicated
|
||||||
|
in the file; or
|
||||||
|
|
||||||
|
(c) The contribution was provided directly to me by some other
|
||||||
|
person who certified (a), (b) or (c) and I have not modified
|
||||||
|
it.
|
||||||
|
|
||||||
|
(d) I understand and agree that this project and the contribution
|
||||||
|
are public and that a record of the contribution (including all
|
||||||
|
personal information I submit with it, including my sign-off) is
|
||||||
|
maintained indefinitely and may be redistributed consistent with
|
||||||
|
this project or the open source license(s) involved.
|
3
vendor/github.com/coreos/go-oidc/MAINTAINERS
generated
vendored
Normal file
3
vendor/github.com/coreos/go-oidc/MAINTAINERS
generated
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
Bobby Rullo <bobby.rullo@coreos.com> (@bobbyrullo)
|
||||||
|
Ed Rooth <ed.rooth@coreos.com> (@sym3tri)
|
||||||
|
Eric Chiang <eric.chiang@coreos.com> (@ericchiang)
|
72
vendor/github.com/coreos/go-oidc/README.md
generated
vendored
Normal file
72
vendor/github.com/coreos/go-oidc/README.md
generated
vendored
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
# go-oidc
|
||||||
|
|
||||||
|
[](https://godoc.org/github.com/coreos/go-oidc)
|
||||||
|
[](https://travis-ci.org/coreos/go-oidc)
|
||||||
|
|
||||||
|
## OpenID Connect support for Go
|
||||||
|
|
||||||
|
This package enables OpenID Connect support for the [golang.org/x/oauth2](https://godoc.org/golang.org/x/oauth2) package.
|
||||||
|
|
||||||
|
```go
|
||||||
|
provider, err := oidc.NewProvider(ctx, "https://accounts.google.com")
|
||||||
|
if err != nil {
|
||||||
|
// handle error
|
||||||
|
}
|
||||||
|
|
||||||
|
// Configure an OpenID Connect aware OAuth2 client.
|
||||||
|
oauth2Config := oauth2.Config{
|
||||||
|
ClientID: clientID,
|
||||||
|
ClientSecret: clientSecret,
|
||||||
|
RedirectURL: redirectURL,
|
||||||
|
|
||||||
|
// Discovery returns the OAuth2 endpoints.
|
||||||
|
Endpoint: provider.Endpoint(),
|
||||||
|
|
||||||
|
// "openid" is a required scope for OpenID Connect flows.
|
||||||
|
Scopes: []string{oidc.ScopeOpenID, "profile", "email"},
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
OAuth2 redirects are unchanged.
|
||||||
|
|
||||||
|
```go
|
||||||
|
func handleRedirect(w http.ResponseWriter, r *http.Request) {
|
||||||
|
http.Redirect(w, r, oauth2Config.AuthCodeURL(state), http.StatusFound)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The on responses, the provider can be used to verify ID Tokens.
|
||||||
|
|
||||||
|
```go
|
||||||
|
var verifier = provider.Verifier(&oidc.Config{ClientID: clientID})
|
||||||
|
|
||||||
|
func handleOAuth2Callback(w http.ResponseWriter, r *http.Request) {
|
||||||
|
// Verify state and errors.
|
||||||
|
|
||||||
|
oauth2Token, err := oauth2Config.Exchange(ctx, r.URL.Query().Get("code"))
|
||||||
|
if err != nil {
|
||||||
|
// handle error
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract the ID Token from OAuth2 token.
|
||||||
|
rawIDToken, ok := oauth2Token.Extra("id_token").(string)
|
||||||
|
if !ok {
|
||||||
|
// handle missing token
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse and verify ID Token payload.
|
||||||
|
idToken, err := verifier.Verify(ctx, rawIDToken)
|
||||||
|
if err != nil {
|
||||||
|
// handle error
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract custom claims
|
||||||
|
var claims struct {
|
||||||
|
Email string `json:"email"`
|
||||||
|
Verified bool `json:"email_verified"`
|
||||||
|
}
|
||||||
|
if err := idToken.Claims(&claims); err != nil {
|
||||||
|
// handle error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
61
vendor/github.com/coreos/go-oidc/code-of-conduct.md
generated
vendored
Normal file
61
vendor/github.com/coreos/go-oidc/code-of-conduct.md
generated
vendored
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
## CoreOS Community Code of Conduct
|
||||||
|
|
||||||
|
### Contributor Code of Conduct
|
||||||
|
|
||||||
|
As contributors and maintainers of this project, and in the interest of
|
||||||
|
fostering an open and welcoming community, we pledge to respect all people who
|
||||||
|
contribute through reporting issues, posting feature requests, updating
|
||||||
|
documentation, submitting pull requests or patches, and other activities.
|
||||||
|
|
||||||
|
We are committed to making participation in this project a harassment-free
|
||||||
|
experience for everyone, regardless of level of experience, gender, gender
|
||||||
|
identity and expression, sexual orientation, disability, personal appearance,
|
||||||
|
body size, race, ethnicity, age, religion, or nationality.
|
||||||
|
|
||||||
|
Examples of unacceptable behavior by participants include:
|
||||||
|
|
||||||
|
* The use of sexualized language or imagery
|
||||||
|
* Personal attacks
|
||||||
|
* Trolling or insulting/derogatory comments
|
||||||
|
* Public or private harassment
|
||||||
|
* Publishing others' private information, such as physical or electronic addresses, without explicit permission
|
||||||
|
* Other unethical or unprofessional conduct.
|
||||||
|
|
||||||
|
Project maintainers have the right and responsibility to remove, edit, or
|
||||||
|
reject comments, commits, code, wiki edits, issues, and other contributions
|
||||||
|
that are not aligned to this Code of Conduct. By adopting this Code of Conduct,
|
||||||
|
project maintainers commit themselves to fairly and consistently applying these
|
||||||
|
principles to every aspect of managing this project. Project maintainers who do
|
||||||
|
not follow or enforce the Code of Conduct may be permanently removed from the
|
||||||
|
project team.
|
||||||
|
|
||||||
|
This code of conduct applies both within project spaces and in public spaces
|
||||||
|
when an individual is representing the project or its community.
|
||||||
|
|
||||||
|
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||||
|
reported by contacting a project maintainer, Brandon Philips
|
||||||
|
<brandon.philips@coreos.com>, and/or Rithu John <rithu.john@coreos.com>.
|
||||||
|
|
||||||
|
This Code of Conduct is adapted from the Contributor Covenant
|
||||||
|
(http://contributor-covenant.org), version 1.2.0, available at
|
||||||
|
http://contributor-covenant.org/version/1/2/0/
|
||||||
|
|
||||||
|
### CoreOS Events Code of Conduct
|
||||||
|
|
||||||
|
CoreOS events are working conferences intended for professional networking and
|
||||||
|
collaboration in the CoreOS community. Attendees are expected to behave
|
||||||
|
according to professional standards and in accordance with their employer’s
|
||||||
|
policies on appropriate workplace behavior.
|
||||||
|
|
||||||
|
While at CoreOS events or related social networking opportunities, attendees
|
||||||
|
should not engage in discriminatory or offensive speech or actions including
|
||||||
|
but not limited to gender, sexuality, race, age, disability, or religion.
|
||||||
|
Speakers should be especially aware of these concerns.
|
||||||
|
|
||||||
|
CoreOS does not condone any statements by speakers contrary to these standards.
|
||||||
|
CoreOS reserves the right to deny entrance and/or eject from an event (without
|
||||||
|
refund) any individual found to be engaging in discriminatory or offensive
|
||||||
|
speech or actions.
|
||||||
|
|
||||||
|
Please bring any concerns to the immediate attention of designated on-site
|
||||||
|
staff, Brandon Philips <brandon.philips@coreos.com>, and/or Rithu John <rithu.john@coreos.com>.
|
7
vendor/github.com/coreos/go-oidc/http/client.go
generated
vendored
7
vendor/github.com/coreos/go-oidc/http/client.go
generated
vendored
@ -1,7 +0,0 @@
|
|||||||
package http
|
|
||||||
|
|
||||||
import "net/http"
|
|
||||||
|
|
||||||
type Client interface {
|
|
||||||
Do(*http.Request) (*http.Response, error)
|
|
||||||
}
|
|
2
vendor/github.com/coreos/go-oidc/http/doc.go
generated
vendored
2
vendor/github.com/coreos/go-oidc/http/doc.go
generated
vendored
@ -1,2 +0,0 @@
|
|||||||
// Package http is DEPRECATED. Use net/http instead.
|
|
||||||
package http
|
|
161
vendor/github.com/coreos/go-oidc/http/http.go
generated
vendored
161
vendor/github.com/coreos/go-oidc/http/http.go
generated
vendored
@ -1,161 +0,0 @@
|
|||||||
package http
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/base64"
|
|
||||||
"encoding/json"
|
|
||||||
"errors"
|
|
||||||
"log"
|
|
||||||
"net/http"
|
|
||||||
"net/url"
|
|
||||||
"path"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
func WriteError(w http.ResponseWriter, code int, msg string) {
|
|
||||||
e := struct {
|
|
||||||
Error string `json:"error"`
|
|
||||||
}{
|
|
||||||
Error: msg,
|
|
||||||
}
|
|
||||||
b, err := json.Marshal(e)
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("go-oidc: failed to marshal %#v: %v", e, err)
|
|
||||||
code = http.StatusInternalServerError
|
|
||||||
b = []byte(`{"error":"server_error"}`)
|
|
||||||
}
|
|
||||||
w.Header().Set("Content-Type", "application/json")
|
|
||||||
w.WriteHeader(code)
|
|
||||||
w.Write(b)
|
|
||||||
}
|
|
||||||
|
|
||||||
// BasicAuth parses a username and password from the request's
|
|
||||||
// Authorization header. This was pulled from golang master:
|
|
||||||
// https://codereview.appspot.com/76540043
|
|
||||||
func BasicAuth(r *http.Request) (username, password string, ok bool) {
|
|
||||||
auth := r.Header.Get("Authorization")
|
|
||||||
if auth == "" {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if !strings.HasPrefix(auth, "Basic ") {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
c, err := base64.StdEncoding.DecodeString(strings.TrimPrefix(auth, "Basic "))
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
cs := string(c)
|
|
||||||
s := strings.IndexByte(cs, ':')
|
|
||||||
if s < 0 {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
return cs[:s], cs[s+1:], true
|
|
||||||
}
|
|
||||||
|
|
||||||
func cacheControlMaxAge(hdr string) (time.Duration, bool, error) {
|
|
||||||
for _, field := range strings.Split(hdr, ",") {
|
|
||||||
parts := strings.SplitN(strings.TrimSpace(field), "=", 2)
|
|
||||||
k := strings.ToLower(strings.TrimSpace(parts[0]))
|
|
||||||
if k != "max-age" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(parts) == 1 {
|
|
||||||
return 0, false, errors.New("max-age has no value")
|
|
||||||
}
|
|
||||||
|
|
||||||
v := strings.TrimSpace(parts[1])
|
|
||||||
if v == "" {
|
|
||||||
return 0, false, errors.New("max-age has empty value")
|
|
||||||
}
|
|
||||||
|
|
||||||
age, err := strconv.Atoi(v)
|
|
||||||
if err != nil {
|
|
||||||
return 0, false, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if age <= 0 {
|
|
||||||
return 0, false, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return time.Duration(age) * time.Second, true, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0, false, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func expires(date, expires string) (time.Duration, bool, error) {
|
|
||||||
if date == "" || expires == "" {
|
|
||||||
return 0, false, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var te time.Time
|
|
||||||
var err error
|
|
||||||
if expires == "0" {
|
|
||||||
return 0, false, nil
|
|
||||||
}
|
|
||||||
te, err = time.Parse(time.RFC1123, expires)
|
|
||||||
if err != nil {
|
|
||||||
return 0, false, err
|
|
||||||
}
|
|
||||||
|
|
||||||
td, err := time.Parse(time.RFC1123, date)
|
|
||||||
if err != nil {
|
|
||||||
return 0, false, err
|
|
||||||
}
|
|
||||||
|
|
||||||
ttl := te.Sub(td)
|
|
||||||
|
|
||||||
// headers indicate data already expired, caller should not
|
|
||||||
// have to care about this case
|
|
||||||
if ttl <= 0 {
|
|
||||||
return 0, false, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return ttl, true, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func Cacheable(hdr http.Header) (time.Duration, bool, error) {
|
|
||||||
ttl, ok, err := cacheControlMaxAge(hdr.Get("Cache-Control"))
|
|
||||||
if err != nil || ok {
|
|
||||||
return ttl, ok, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return expires(hdr.Get("Date"), hdr.Get("Expires"))
|
|
||||||
}
|
|
||||||
|
|
||||||
// MergeQuery appends additional query values to an existing URL.
|
|
||||||
func MergeQuery(u url.URL, q url.Values) url.URL {
|
|
||||||
uv := u.Query()
|
|
||||||
for k, vs := range q {
|
|
||||||
for _, v := range vs {
|
|
||||||
uv.Add(k, v)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
u.RawQuery = uv.Encode()
|
|
||||||
return u
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewResourceLocation appends a resource id to the end of the requested URL path.
|
|
||||||
func NewResourceLocation(reqURL *url.URL, id string) string {
|
|
||||||
var u url.URL
|
|
||||||
u = *reqURL
|
|
||||||
u.Path = path.Join(u.Path, id)
|
|
||||||
u.RawQuery = ""
|
|
||||||
u.Fragment = ""
|
|
||||||
return u.String()
|
|
||||||
}
|
|
||||||
|
|
||||||
// CopyRequest returns a clone of the provided *http.Request.
|
|
||||||
// The returned object is a shallow copy of the struct and a
|
|
||||||
// deep copy of its Header field.
|
|
||||||
func CopyRequest(r *http.Request) *http.Request {
|
|
||||||
r2 := *r
|
|
||||||
r2.Header = make(http.Header)
|
|
||||||
for k, s := range r.Header {
|
|
||||||
r2.Header[k] = s
|
|
||||||
}
|
|
||||||
return &r2
|
|
||||||
}
|
|
29
vendor/github.com/coreos/go-oidc/http/url.go
generated
vendored
29
vendor/github.com/coreos/go-oidc/http/url.go
generated
vendored
@ -1,29 +0,0 @@
|
|||||||
package http
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"net/url"
|
|
||||||
)
|
|
||||||
|
|
||||||
// ParseNonEmptyURL checks that a string is a parsable URL which is also not empty
|
|
||||||
// since `url.Parse("")` does not return an error. Must contian a scheme and a host.
|
|
||||||
func ParseNonEmptyURL(u string) (*url.URL, error) {
|
|
||||||
if u == "" {
|
|
||||||
return nil, errors.New("url is empty")
|
|
||||||
}
|
|
||||||
|
|
||||||
ur, err := url.Parse(u)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if ur.Scheme == "" {
|
|
||||||
return nil, errors.New("url scheme is empty")
|
|
||||||
}
|
|
||||||
|
|
||||||
if ur.Host == "" {
|
|
||||||
return nil, errors.New("url host is empty")
|
|
||||||
}
|
|
||||||
|
|
||||||
return ur, nil
|
|
||||||
}
|
|
20
vendor/github.com/coreos/go-oidc/jose.go
generated
vendored
Normal file
20
vendor/github.com/coreos/go-oidc/jose.go
generated
vendored
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
// +build !golint
|
||||||
|
|
||||||
|
// Don't lint this file. We don't want to have to add a comment to each constant.
|
||||||
|
|
||||||
|
package oidc
|
||||||
|
|
||||||
|
const (
|
||||||
|
// JOSE asymmetric signing algorithm values as defined by RFC 7518
|
||||||
|
//
|
||||||
|
// see: https://tools.ietf.org/html/rfc7518#section-3.1
|
||||||
|
RS256 = "RS256" // RSASSA-PKCS-v1.5 using SHA-256
|
||||||
|
RS384 = "RS384" // RSASSA-PKCS-v1.5 using SHA-384
|
||||||
|
RS512 = "RS512" // RSASSA-PKCS-v1.5 using SHA-512
|
||||||
|
ES256 = "ES256" // ECDSA using P-256 and SHA-256
|
||||||
|
ES384 = "ES384" // ECDSA using P-384 and SHA-384
|
||||||
|
ES512 = "ES512" // ECDSA using P-521 and SHA-512
|
||||||
|
PS256 = "PS256" // RSASSA-PSS using SHA256 and MGF1-SHA256
|
||||||
|
PS384 = "PS384" // RSASSA-PSS using SHA384 and MGF1-SHA384
|
||||||
|
PS512 = "PS512" // RSASSA-PSS using SHA512 and MGF1-SHA512
|
||||||
|
)
|
126
vendor/github.com/coreos/go-oidc/jose/claims.go
generated
vendored
126
vendor/github.com/coreos/go-oidc/jose/claims.go
generated
vendored
@ -1,126 +0,0 @@
|
|||||||
package jose
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"math"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Claims map[string]interface{}
|
|
||||||
|
|
||||||
func (c Claims) Add(name string, value interface{}) {
|
|
||||||
c[name] = value
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c Claims) StringClaim(name string) (string, bool, error) {
|
|
||||||
cl, ok := c[name]
|
|
||||||
if !ok {
|
|
||||||
return "", false, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
v, ok := cl.(string)
|
|
||||||
if !ok {
|
|
||||||
return "", false, fmt.Errorf("unable to parse claim as string: %v", name)
|
|
||||||
}
|
|
||||||
|
|
||||||
return v, true, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c Claims) StringsClaim(name string) ([]string, bool, error) {
|
|
||||||
cl, ok := c[name]
|
|
||||||
if !ok {
|
|
||||||
return nil, false, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if v, ok := cl.([]string); ok {
|
|
||||||
return v, true, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// When unmarshaled, []string will become []interface{}.
|
|
||||||
if v, ok := cl.([]interface{}); ok {
|
|
||||||
var ret []string
|
|
||||||
for _, vv := range v {
|
|
||||||
str, ok := vv.(string)
|
|
||||||
if !ok {
|
|
||||||
return nil, false, fmt.Errorf("unable to parse claim as string array: %v", name)
|
|
||||||
}
|
|
||||||
ret = append(ret, str)
|
|
||||||
}
|
|
||||||
return ret, true, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil, false, fmt.Errorf("unable to parse claim as string array: %v", name)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c Claims) Int64Claim(name string) (int64, bool, error) {
|
|
||||||
cl, ok := c[name]
|
|
||||||
if !ok {
|
|
||||||
return 0, false, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
v, ok := cl.(int64)
|
|
||||||
if !ok {
|
|
||||||
vf, ok := cl.(float64)
|
|
||||||
if !ok {
|
|
||||||
return 0, false, fmt.Errorf("unable to parse claim as int64: %v", name)
|
|
||||||
}
|
|
||||||
v = int64(vf)
|
|
||||||
}
|
|
||||||
|
|
||||||
return v, true, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c Claims) Float64Claim(name string) (float64, bool, error) {
|
|
||||||
cl, ok := c[name]
|
|
||||||
if !ok {
|
|
||||||
return 0, false, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
v, ok := cl.(float64)
|
|
||||||
if !ok {
|
|
||||||
vi, ok := cl.(int64)
|
|
||||||
if !ok {
|
|
||||||
return 0, false, fmt.Errorf("unable to parse claim as float64: %v", name)
|
|
||||||
}
|
|
||||||
v = float64(vi)
|
|
||||||
}
|
|
||||||
|
|
||||||
return v, true, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c Claims) TimeClaim(name string) (time.Time, bool, error) {
|
|
||||||
v, ok, err := c.Float64Claim(name)
|
|
||||||
if !ok || err != nil {
|
|
||||||
return time.Time{}, ok, err
|
|
||||||
}
|
|
||||||
|
|
||||||
s := math.Trunc(v)
|
|
||||||
ns := (v - s) * math.Pow(10, 9)
|
|
||||||
return time.Unix(int64(s), int64(ns)).UTC(), true, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func decodeClaims(payload []byte) (Claims, error) {
|
|
||||||
var c Claims
|
|
||||||
if err := json.Unmarshal(payload, &c); err != nil {
|
|
||||||
return nil, fmt.Errorf("malformed JWT claims, unable to decode: %v", err)
|
|
||||||
}
|
|
||||||
return c, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func marshalClaims(c Claims) ([]byte, error) {
|
|
||||||
b, err := json.Marshal(c)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return b, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func encodeClaims(c Claims) (string, error) {
|
|
||||||
b, err := marshalClaims(c)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
return encodeSegment(b), nil
|
|
||||||
}
|
|
2
vendor/github.com/coreos/go-oidc/jose/doc.go
generated
vendored
2
vendor/github.com/coreos/go-oidc/jose/doc.go
generated
vendored
@ -1,2 +0,0 @@
|
|||||||
// Package jose is DEPRECATED. Use gopkg.in/square/go-jose.v2 instead.
|
|
||||||
package jose
|
|
112
vendor/github.com/coreos/go-oidc/jose/jose.go
generated
vendored
112
vendor/github.com/coreos/go-oidc/jose/jose.go
generated
vendored
@ -1,112 +0,0 @@
|
|||||||
package jose
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/base64"
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
HeaderMediaType = "typ"
|
|
||||||
HeaderKeyAlgorithm = "alg"
|
|
||||||
HeaderKeyID = "kid"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
// Encryption Algorithm Header Parameter Values for JWS
|
|
||||||
// See: https://tools.ietf.org/html/draft-ietf-jose-json-web-algorithms-40#page-6
|
|
||||||
AlgHS256 = "HS256"
|
|
||||||
AlgHS384 = "HS384"
|
|
||||||
AlgHS512 = "HS512"
|
|
||||||
AlgRS256 = "RS256"
|
|
||||||
AlgRS384 = "RS384"
|
|
||||||
AlgRS512 = "RS512"
|
|
||||||
AlgES256 = "ES256"
|
|
||||||
AlgES384 = "ES384"
|
|
||||||
AlgES512 = "ES512"
|
|
||||||
AlgPS256 = "PS256"
|
|
||||||
AlgPS384 = "PS384"
|
|
||||||
AlgPS512 = "PS512"
|
|
||||||
AlgNone = "none"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
// Algorithm Header Parameter Values for JWE
|
|
||||||
// See: https://tools.ietf.org/html/draft-ietf-jose-json-web-algorithms-40#section-4.1
|
|
||||||
AlgRSA15 = "RSA1_5"
|
|
||||||
AlgRSAOAEP = "RSA-OAEP"
|
|
||||||
AlgRSAOAEP256 = "RSA-OAEP-256"
|
|
||||||
AlgA128KW = "A128KW"
|
|
||||||
AlgA192KW = "A192KW"
|
|
||||||
AlgA256KW = "A256KW"
|
|
||||||
AlgDir = "dir"
|
|
||||||
AlgECDHES = "ECDH-ES"
|
|
||||||
AlgECDHESA128KW = "ECDH-ES+A128KW"
|
|
||||||
AlgECDHESA192KW = "ECDH-ES+A192KW"
|
|
||||||
AlgECDHESA256KW = "ECDH-ES+A256KW"
|
|
||||||
AlgA128GCMKW = "A128GCMKW"
|
|
||||||
AlgA192GCMKW = "A192GCMKW"
|
|
||||||
AlgA256GCMKW = "A256GCMKW"
|
|
||||||
AlgPBES2HS256A128KW = "PBES2-HS256+A128KW"
|
|
||||||
AlgPBES2HS384A192KW = "PBES2-HS384+A192KW"
|
|
||||||
AlgPBES2HS512A256KW = "PBES2-HS512+A256KW"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
// Encryption Algorithm Header Parameter Values for JWE
|
|
||||||
// See: https://tools.ietf.org/html/draft-ietf-jose-json-web-algorithms-40#page-22
|
|
||||||
EncA128CBCHS256 = "A128CBC-HS256"
|
|
||||||
EncA128CBCHS384 = "A128CBC-HS384"
|
|
||||||
EncA256CBCHS512 = "A256CBC-HS512"
|
|
||||||
EncA128GCM = "A128GCM"
|
|
||||||
EncA192GCM = "A192GCM"
|
|
||||||
EncA256GCM = "A256GCM"
|
|
||||||
)
|
|
||||||
|
|
||||||
type JOSEHeader map[string]string
|
|
||||||
|
|
||||||
func (j JOSEHeader) Validate() error {
|
|
||||||
if _, exists := j[HeaderKeyAlgorithm]; !exists {
|
|
||||||
return fmt.Errorf("header missing %q parameter", HeaderKeyAlgorithm)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func decodeHeader(seg string) (JOSEHeader, error) {
|
|
||||||
b, err := decodeSegment(seg)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
var h JOSEHeader
|
|
||||||
err = json.Unmarshal(b, &h)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return h, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func encodeHeader(h JOSEHeader) (string, error) {
|
|
||||||
b, err := json.Marshal(h)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
return encodeSegment(b), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Decode JWT specific base64url encoding with padding stripped
|
|
||||||
func decodeSegment(seg string) ([]byte, error) {
|
|
||||||
if l := len(seg) % 4; l != 0 {
|
|
||||||
seg += strings.Repeat("=", 4-l)
|
|
||||||
}
|
|
||||||
return base64.URLEncoding.DecodeString(seg)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Encode JWT specific base64url encoding with padding stripped
|
|
||||||
func encodeSegment(seg []byte) string {
|
|
||||||
return strings.TrimRight(base64.URLEncoding.EncodeToString(seg), "=")
|
|
||||||
}
|
|
135
vendor/github.com/coreos/go-oidc/jose/jwk.go
generated
vendored
135
vendor/github.com/coreos/go-oidc/jose/jwk.go
generated
vendored
@ -1,135 +0,0 @@
|
|||||||
package jose
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"encoding/base64"
|
|
||||||
"encoding/binary"
|
|
||||||
"encoding/json"
|
|
||||||
"math/big"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
// JSON Web Key
|
|
||||||
// https://tools.ietf.org/html/draft-ietf-jose-json-web-key-36#page-5
|
|
||||||
type JWK struct {
|
|
||||||
ID string
|
|
||||||
Type string
|
|
||||||
Alg string
|
|
||||||
Use string
|
|
||||||
Exponent int
|
|
||||||
Modulus *big.Int
|
|
||||||
Secret []byte
|
|
||||||
}
|
|
||||||
|
|
||||||
type jwkJSON struct {
|
|
||||||
ID string `json:"kid"`
|
|
||||||
Type string `json:"kty"`
|
|
||||||
Alg string `json:"alg"`
|
|
||||||
Use string `json:"use"`
|
|
||||||
Exponent string `json:"e"`
|
|
||||||
Modulus string `json:"n"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (j *JWK) MarshalJSON() ([]byte, error) {
|
|
||||||
t := jwkJSON{
|
|
||||||
ID: j.ID,
|
|
||||||
Type: j.Type,
|
|
||||||
Alg: j.Alg,
|
|
||||||
Use: j.Use,
|
|
||||||
Exponent: encodeExponent(j.Exponent),
|
|
||||||
Modulus: encodeModulus(j.Modulus),
|
|
||||||
}
|
|
||||||
|
|
||||||
return json.Marshal(&t)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (j *JWK) UnmarshalJSON(data []byte) error {
|
|
||||||
var t jwkJSON
|
|
||||||
err := json.Unmarshal(data, &t)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
e, err := decodeExponent(t.Exponent)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
n, err := decodeModulus(t.Modulus)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
j.ID = t.ID
|
|
||||||
j.Type = t.Type
|
|
||||||
j.Alg = t.Alg
|
|
||||||
j.Use = t.Use
|
|
||||||
j.Exponent = e
|
|
||||||
j.Modulus = n
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type JWKSet struct {
|
|
||||||
Keys []JWK `json:"keys"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func decodeExponent(e string) (int, error) {
|
|
||||||
decE, err := decodeBase64URLPaddingOptional(e)
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
var eBytes []byte
|
|
||||||
if len(decE) < 8 {
|
|
||||||
eBytes = make([]byte, 8-len(decE), 8)
|
|
||||||
eBytes = append(eBytes, decE...)
|
|
||||||
} else {
|
|
||||||
eBytes = decE
|
|
||||||
}
|
|
||||||
eReader := bytes.NewReader(eBytes)
|
|
||||||
var E uint64
|
|
||||||
err = binary.Read(eReader, binary.BigEndian, &E)
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
return int(E), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func encodeExponent(e int) string {
|
|
||||||
b := make([]byte, 8)
|
|
||||||
binary.BigEndian.PutUint64(b, uint64(e))
|
|
||||||
var idx int
|
|
||||||
for ; idx < 8; idx++ {
|
|
||||||
if b[idx] != 0x0 {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return base64.RawURLEncoding.EncodeToString(b[idx:])
|
|
||||||
}
|
|
||||||
|
|
||||||
// Turns a URL encoded modulus of a key into a big int.
|
|
||||||
func decodeModulus(n string) (*big.Int, error) {
|
|
||||||
decN, err := decodeBase64URLPaddingOptional(n)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
N := big.NewInt(0)
|
|
||||||
N.SetBytes(decN)
|
|
||||||
return N, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func encodeModulus(n *big.Int) string {
|
|
||||||
return base64.RawURLEncoding.EncodeToString(n.Bytes())
|
|
||||||
}
|
|
||||||
|
|
||||||
// decodeBase64URLPaddingOptional decodes Base64 whether there is padding or not.
|
|
||||||
// The stdlib version currently doesn't handle this.
|
|
||||||
// We can get rid of this is if this bug:
|
|
||||||
// https://github.com/golang/go/issues/4237
|
|
||||||
// ever closes.
|
|
||||||
func decodeBase64URLPaddingOptional(e string) ([]byte, error) {
|
|
||||||
if m := len(e) % 4; m != 0 {
|
|
||||||
e += strings.Repeat("=", 4-m)
|
|
||||||
}
|
|
||||||
return base64.URLEncoding.DecodeString(e)
|
|
||||||
}
|
|
51
vendor/github.com/coreos/go-oidc/jose/jws.go
generated
vendored
51
vendor/github.com/coreos/go-oidc/jose/jws.go
generated
vendored
@ -1,51 +0,0 @@
|
|||||||
package jose
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
type JWS struct {
|
|
||||||
RawHeader string
|
|
||||||
Header JOSEHeader
|
|
||||||
RawPayload string
|
|
||||||
Payload []byte
|
|
||||||
Signature []byte
|
|
||||||
}
|
|
||||||
|
|
||||||
// Given a raw encoded JWS token parses it and verifies the structure.
|
|
||||||
func ParseJWS(raw string) (JWS, error) {
|
|
||||||
parts := strings.Split(raw, ".")
|
|
||||||
if len(parts) != 3 {
|
|
||||||
return JWS{}, fmt.Errorf("malformed JWS, only %d segments", len(parts))
|
|
||||||
}
|
|
||||||
|
|
||||||
rawSig := parts[2]
|
|
||||||
jws := JWS{
|
|
||||||
RawHeader: parts[0],
|
|
||||||
RawPayload: parts[1],
|
|
||||||
}
|
|
||||||
|
|
||||||
header, err := decodeHeader(jws.RawHeader)
|
|
||||||
if err != nil {
|
|
||||||
return JWS{}, fmt.Errorf("malformed JWS, unable to decode header, %s", err)
|
|
||||||
}
|
|
||||||
if err = header.Validate(); err != nil {
|
|
||||||
return JWS{}, fmt.Errorf("malformed JWS, %s", err)
|
|
||||||
}
|
|
||||||
jws.Header = header
|
|
||||||
|
|
||||||
payload, err := decodeSegment(jws.RawPayload)
|
|
||||||
if err != nil {
|
|
||||||
return JWS{}, fmt.Errorf("malformed JWS, unable to decode payload: %s", err)
|
|
||||||
}
|
|
||||||
jws.Payload = payload
|
|
||||||
|
|
||||||
sig, err := decodeSegment(rawSig)
|
|
||||||
if err != nil {
|
|
||||||
return JWS{}, fmt.Errorf("malformed JWS, unable to decode signature: %s", err)
|
|
||||||
}
|
|
||||||
jws.Signature = sig
|
|
||||||
|
|
||||||
return jws, nil
|
|
||||||
}
|
|
82
vendor/github.com/coreos/go-oidc/jose/jwt.go
generated
vendored
82
vendor/github.com/coreos/go-oidc/jose/jwt.go
generated
vendored
@ -1,82 +0,0 @@
|
|||||||
package jose
|
|
||||||
|
|
||||||
import "strings"
|
|
||||||
|
|
||||||
type JWT JWS
|
|
||||||
|
|
||||||
func ParseJWT(token string) (jwt JWT, err error) {
|
|
||||||
jws, err := ParseJWS(token)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
return JWT(jws), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewJWT(header JOSEHeader, claims Claims) (jwt JWT, err error) {
|
|
||||||
jwt = JWT{}
|
|
||||||
|
|
||||||
jwt.Header = header
|
|
||||||
jwt.Header[HeaderMediaType] = "JWT"
|
|
||||||
|
|
||||||
claimBytes, err := marshalClaims(claims)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
jwt.Payload = claimBytes
|
|
||||||
|
|
||||||
eh, err := encodeHeader(header)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
jwt.RawHeader = eh
|
|
||||||
|
|
||||||
ec, err := encodeClaims(claims)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
jwt.RawPayload = ec
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (j *JWT) KeyID() (string, bool) {
|
|
||||||
kID, ok := j.Header[HeaderKeyID]
|
|
||||||
return kID, ok
|
|
||||||
}
|
|
||||||
|
|
||||||
func (j *JWT) Claims() (Claims, error) {
|
|
||||||
return decodeClaims(j.Payload)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Encoded data part of the token which may be signed.
|
|
||||||
func (j *JWT) Data() string {
|
|
||||||
return strings.Join([]string{j.RawHeader, j.RawPayload}, ".")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Full encoded JWT token string in format: header.claims.signature
|
|
||||||
func (j *JWT) Encode() string {
|
|
||||||
d := j.Data()
|
|
||||||
s := encodeSegment(j.Signature)
|
|
||||||
return strings.Join([]string{d, s}, ".")
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewSignedJWT(claims Claims, s Signer) (*JWT, error) {
|
|
||||||
header := JOSEHeader{
|
|
||||||
HeaderKeyAlgorithm: s.Alg(),
|
|
||||||
HeaderKeyID: s.ID(),
|
|
||||||
}
|
|
||||||
|
|
||||||
jwt, err := NewJWT(header, claims)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
sig, err := s.Sign([]byte(jwt.Data()))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
jwt.Signature = sig
|
|
||||||
|
|
||||||
return &jwt, nil
|
|
||||||
}
|
|
24
vendor/github.com/coreos/go-oidc/jose/sig.go
generated
vendored
24
vendor/github.com/coreos/go-oidc/jose/sig.go
generated
vendored
@ -1,24 +0,0 @@
|
|||||||
package jose
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Verifier interface {
|
|
||||||
ID() string
|
|
||||||
Alg() string
|
|
||||||
Verify(sig []byte, data []byte) error
|
|
||||||
}
|
|
||||||
|
|
||||||
type Signer interface {
|
|
||||||
Verifier
|
|
||||||
Sign(data []byte) (sig []byte, err error)
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewVerifier(jwk JWK) (Verifier, error) {
|
|
||||||
if jwk.Type != "RSA" {
|
|
||||||
return nil, fmt.Errorf("unsupported key type %q", jwk.Type)
|
|
||||||
}
|
|
||||||
|
|
||||||
return NewVerifierRSA(jwk)
|
|
||||||
}
|
|
67
vendor/github.com/coreos/go-oidc/jose/sig_rsa.go
generated
vendored
67
vendor/github.com/coreos/go-oidc/jose/sig_rsa.go
generated
vendored
@ -1,67 +0,0 @@
|
|||||||
package jose
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto"
|
|
||||||
"crypto/rand"
|
|
||||||
"crypto/rsa"
|
|
||||||
"fmt"
|
|
||||||
)
|
|
||||||
|
|
||||||
type VerifierRSA struct {
|
|
||||||
KeyID string
|
|
||||||
Hash crypto.Hash
|
|
||||||
PublicKey rsa.PublicKey
|
|
||||||
}
|
|
||||||
|
|
||||||
type SignerRSA struct {
|
|
||||||
PrivateKey rsa.PrivateKey
|
|
||||||
VerifierRSA
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewVerifierRSA(jwk JWK) (*VerifierRSA, error) {
|
|
||||||
if jwk.Alg != "" && jwk.Alg != "RS256" {
|
|
||||||
return nil, fmt.Errorf("unsupported key algorithm %q", jwk.Alg)
|
|
||||||
}
|
|
||||||
|
|
||||||
v := VerifierRSA{
|
|
||||||
KeyID: jwk.ID,
|
|
||||||
PublicKey: rsa.PublicKey{
|
|
||||||
N: jwk.Modulus,
|
|
||||||
E: jwk.Exponent,
|
|
||||||
},
|
|
||||||
Hash: crypto.SHA256,
|
|
||||||
}
|
|
||||||
|
|
||||||
return &v, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewSignerRSA(kid string, key rsa.PrivateKey) *SignerRSA {
|
|
||||||
return &SignerRSA{
|
|
||||||
PrivateKey: key,
|
|
||||||
VerifierRSA: VerifierRSA{
|
|
||||||
KeyID: kid,
|
|
||||||
PublicKey: key.PublicKey,
|
|
||||||
Hash: crypto.SHA256,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (v *VerifierRSA) ID() string {
|
|
||||||
return v.KeyID
|
|
||||||
}
|
|
||||||
|
|
||||||
func (v *VerifierRSA) Alg() string {
|
|
||||||
return "RS256"
|
|
||||||
}
|
|
||||||
|
|
||||||
func (v *VerifierRSA) Verify(sig []byte, data []byte) error {
|
|
||||||
h := v.Hash.New()
|
|
||||||
h.Write(data)
|
|
||||||
return rsa.VerifyPKCS1v15(&v.PublicKey, v.Hash, h.Sum(nil), sig)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *SignerRSA) Sign(data []byte) ([]byte, error) {
|
|
||||||
h := s.Hash.New()
|
|
||||||
h.Write(data)
|
|
||||||
return rsa.SignPKCS1v15(rand.Reader, &s.PrivateKey, s.Hash, h.Sum(nil))
|
|
||||||
}
|
|
228
vendor/github.com/coreos/go-oidc/jwks.go
generated
vendored
Normal file
228
vendor/github.com/coreos/go-oidc/jwks.go
generated
vendored
Normal file
@ -0,0 +1,228 @@
|
|||||||
|
package oidc
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/pquerna/cachecontrol"
|
||||||
|
jose "gopkg.in/square/go-jose.v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
// keysExpiryDelta is the allowed clock skew between a client and the OpenID Connect
|
||||||
|
// server.
|
||||||
|
//
|
||||||
|
// When keys expire, they are valid for this amount of time after.
|
||||||
|
//
|
||||||
|
// If the keys have not expired, and an ID Token claims it was signed by a key not in
|
||||||
|
// the cache, if and only if the keys expire in this amount of time, the keys will be
|
||||||
|
// updated.
|
||||||
|
const keysExpiryDelta = 30 * time.Second
|
||||||
|
|
||||||
|
// NewRemoteKeySet returns a KeySet that can validate JSON web tokens by using HTTP
|
||||||
|
// GETs to fetch JSON web token sets hosted at a remote URL. This is automatically
|
||||||
|
// used by NewProvider using the URLs returned by OpenID Connect discovery, but is
|
||||||
|
// exposed for providers that don't support discovery or to prevent round trips to the
|
||||||
|
// discovery URL.
|
||||||
|
//
|
||||||
|
// The returned KeySet is a long lived verifier that caches keys based on cache-control
|
||||||
|
// headers. Reuse a common remote key set instead of creating new ones as needed.
|
||||||
|
//
|
||||||
|
// The behavior of the returned KeySet is undefined once the context is canceled.
|
||||||
|
func NewRemoteKeySet(ctx context.Context, jwksURL string) KeySet {
|
||||||
|
return newRemoteKeySet(ctx, jwksURL, time.Now)
|
||||||
|
}
|
||||||
|
|
||||||
|
func newRemoteKeySet(ctx context.Context, jwksURL string, now func() time.Time) *remoteKeySet {
|
||||||
|
if now == nil {
|
||||||
|
now = time.Now
|
||||||
|
}
|
||||||
|
return &remoteKeySet{jwksURL: jwksURL, ctx: ctx, now: now}
|
||||||
|
}
|
||||||
|
|
||||||
|
type remoteKeySet struct {
|
||||||
|
jwksURL string
|
||||||
|
ctx context.Context
|
||||||
|
now func() time.Time
|
||||||
|
|
||||||
|
// guard all other fields
|
||||||
|
mu sync.Mutex
|
||||||
|
|
||||||
|
// inflight suppresses parallel execution of updateKeys and allows
|
||||||
|
// multiple goroutines to wait for its result.
|
||||||
|
inflight *inflight
|
||||||
|
|
||||||
|
// A set of cached keys and their expiry.
|
||||||
|
cachedKeys []jose.JSONWebKey
|
||||||
|
expiry time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
// inflight is used to wait on some in-flight request from multiple goroutines.
|
||||||
|
type inflight struct {
|
||||||
|
doneCh chan struct{}
|
||||||
|
|
||||||
|
keys []jose.JSONWebKey
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
|
||||||
|
func newInflight() *inflight {
|
||||||
|
return &inflight{doneCh: make(chan struct{})}
|
||||||
|
}
|
||||||
|
|
||||||
|
// wait returns a channel that multiple goroutines can receive on. Once it returns
|
||||||
|
// a value, the inflight request is done and result() can be inspected.
|
||||||
|
func (i *inflight) wait() <-chan struct{} {
|
||||||
|
return i.doneCh
|
||||||
|
}
|
||||||
|
|
||||||
|
// done can only be called by a single goroutine. It records the result of the
|
||||||
|
// inflight request and signals other goroutines that the result is safe to
|
||||||
|
// inspect.
|
||||||
|
func (i *inflight) done(keys []jose.JSONWebKey, err error) {
|
||||||
|
i.keys = keys
|
||||||
|
i.err = err
|
||||||
|
close(i.doneCh)
|
||||||
|
}
|
||||||
|
|
||||||
|
// result cannot be called until the wait() channel has returned a value.
|
||||||
|
func (i *inflight) result() ([]jose.JSONWebKey, error) {
|
||||||
|
return i.keys, i.err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *remoteKeySet) VerifySignature(ctx context.Context, jwt string) ([]byte, error) {
|
||||||
|
jws, err := jose.ParseSigned(jwt)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("oidc: malformed jwt: %v", err)
|
||||||
|
}
|
||||||
|
return r.verify(ctx, jws)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *remoteKeySet) verify(ctx context.Context, jws *jose.JSONWebSignature) ([]byte, error) {
|
||||||
|
// We don't support JWTs signed with multiple signatures.
|
||||||
|
keyID := ""
|
||||||
|
for _, sig := range jws.Signatures {
|
||||||
|
keyID = sig.Header.KeyID
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
keys, expiry := r.keysFromCache()
|
||||||
|
|
||||||
|
// Don't check expiry yet. This optimizes for when the provider is unavailable.
|
||||||
|
for _, key := range keys {
|
||||||
|
if keyID == "" || key.KeyID == keyID {
|
||||||
|
if payload, err := jws.Verify(&key); err == nil {
|
||||||
|
return payload, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !r.now().Add(keysExpiryDelta).After(expiry) {
|
||||||
|
// Keys haven't expired, don't refresh.
|
||||||
|
return nil, errors.New("failed to verify id token signature")
|
||||||
|
}
|
||||||
|
|
||||||
|
keys, err := r.keysFromRemote(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("fetching keys %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, key := range keys {
|
||||||
|
if keyID == "" || key.KeyID == keyID {
|
||||||
|
if payload, err := jws.Verify(&key); err == nil {
|
||||||
|
return payload, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, errors.New("failed to verify id token signature")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *remoteKeySet) keysFromCache() (keys []jose.JSONWebKey, expiry time.Time) {
|
||||||
|
r.mu.Lock()
|
||||||
|
defer r.mu.Unlock()
|
||||||
|
return r.cachedKeys, r.expiry
|
||||||
|
}
|
||||||
|
|
||||||
|
// keysFromRemote syncs the key set from the remote set, records the values in the
|
||||||
|
// cache, and returns the key set.
|
||||||
|
func (r *remoteKeySet) keysFromRemote(ctx context.Context) ([]jose.JSONWebKey, error) {
|
||||||
|
// Need to lock to inspect the inflight request field.
|
||||||
|
r.mu.Lock()
|
||||||
|
// If there's not a current inflight request, create one.
|
||||||
|
if r.inflight == nil {
|
||||||
|
r.inflight = newInflight()
|
||||||
|
|
||||||
|
// This goroutine has exclusive ownership over the current inflight
|
||||||
|
// request. It releases the resource by nil'ing the inflight field
|
||||||
|
// once the goroutine is done.
|
||||||
|
go func() {
|
||||||
|
// Sync keys and finish inflight when that's done.
|
||||||
|
keys, expiry, err := r.updateKeys()
|
||||||
|
|
||||||
|
r.inflight.done(keys, err)
|
||||||
|
|
||||||
|
// Lock to update the keys and indicate that there is no longer an
|
||||||
|
// inflight request.
|
||||||
|
r.mu.Lock()
|
||||||
|
defer r.mu.Unlock()
|
||||||
|
|
||||||
|
if err == nil {
|
||||||
|
r.cachedKeys = keys
|
||||||
|
r.expiry = expiry
|
||||||
|
}
|
||||||
|
|
||||||
|
// Free inflight so a different request can run.
|
||||||
|
r.inflight = nil
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
inflight := r.inflight
|
||||||
|
r.mu.Unlock()
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return nil, ctx.Err()
|
||||||
|
case <-inflight.wait():
|
||||||
|
return inflight.result()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *remoteKeySet) updateKeys() ([]jose.JSONWebKey, time.Time, error) {
|
||||||
|
req, err := http.NewRequest("GET", r.jwksURL, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, time.Time{}, fmt.Errorf("oidc: can't create request: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := doRequest(r.ctx, req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, time.Time{}, fmt.Errorf("oidc: get keys failed %v", err)
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
body, err := ioutil.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, time.Time{}, fmt.Errorf("unable to read response body: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
return nil, time.Time{}, fmt.Errorf("oidc: get keys failed: %s %s", resp.Status, body)
|
||||||
|
}
|
||||||
|
|
||||||
|
var keySet jose.JSONWebKeySet
|
||||||
|
err = unmarshalResp(resp, body, &keySet)
|
||||||
|
if err != nil {
|
||||||
|
return nil, time.Time{}, fmt.Errorf("oidc: failed to decode keys: %v %s", err, body)
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the server doesn't provide cache control headers, assume the
|
||||||
|
// keys expire immediately.
|
||||||
|
expiry := r.now()
|
||||||
|
|
||||||
|
_, e, err := cachecontrol.CachableResponse(req, resp, cachecontrol.Options{})
|
||||||
|
if err == nil && e.After(expiry) {
|
||||||
|
expiry = e
|
||||||
|
}
|
||||||
|
return keySet.Keys, expiry, nil
|
||||||
|
}
|
35
vendor/github.com/coreos/go-oidc/key/BUILD
generated
vendored
35
vendor/github.com/coreos/go-oidc/key/BUILD
generated
vendored
@ -1,35 +0,0 @@
|
|||||||
load("@io_bazel_rules_go//go:def.bzl", "go_library")
|
|
||||||
|
|
||||||
go_library(
|
|
||||||
name = "go_default_library",
|
|
||||||
srcs = [
|
|
||||||
"doc.go",
|
|
||||||
"key.go",
|
|
||||||
"manager.go",
|
|
||||||
"repo.go",
|
|
||||||
"rotate.go",
|
|
||||||
"sync.go",
|
|
||||||
],
|
|
||||||
importpath = "github.com/coreos/go-oidc/key",
|
|
||||||
visibility = ["//visibility:public"],
|
|
||||||
deps = [
|
|
||||||
"//vendor/github.com/coreos/go-oidc/jose:go_default_library",
|
|
||||||
"//vendor/github.com/coreos/pkg/health:go_default_library",
|
|
||||||
"//vendor/github.com/coreos/pkg/timeutil:go_default_library",
|
|
||||||
"//vendor/github.com/jonboulle/clockwork:go_default_library",
|
|
||||||
],
|
|
||||||
)
|
|
||||||
|
|
||||||
filegroup(
|
|
||||||
name = "package-srcs",
|
|
||||||
srcs = glob(["**"]),
|
|
||||||
tags = ["automanaged"],
|
|
||||||
visibility = ["//visibility:private"],
|
|
||||||
)
|
|
||||||
|
|
||||||
filegroup(
|
|
||||||
name = "all-srcs",
|
|
||||||
srcs = [":package-srcs"],
|
|
||||||
tags = ["automanaged"],
|
|
||||||
visibility = ["//visibility:public"],
|
|
||||||
)
|
|
2
vendor/github.com/coreos/go-oidc/key/doc.go
generated
vendored
2
vendor/github.com/coreos/go-oidc/key/doc.go
generated
vendored
@ -1,2 +0,0 @@
|
|||||||
// Package key is DEPRECATED. Use github.com/coreos/go-oidc instead.
|
|
||||||
package key
|
|
153
vendor/github.com/coreos/go-oidc/key/key.go
generated
vendored
153
vendor/github.com/coreos/go-oidc/key/key.go
generated
vendored
@ -1,153 +0,0 @@
|
|||||||
package key
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/rand"
|
|
||||||
"crypto/rsa"
|
|
||||||
"encoding/hex"
|
|
||||||
"encoding/json"
|
|
||||||
"io"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/coreos/go-oidc/jose"
|
|
||||||
)
|
|
||||||
|
|
||||||
func NewPublicKey(jwk jose.JWK) *PublicKey {
|
|
||||||
return &PublicKey{jwk: jwk}
|
|
||||||
}
|
|
||||||
|
|
||||||
type PublicKey struct {
|
|
||||||
jwk jose.JWK
|
|
||||||
}
|
|
||||||
|
|
||||||
func (k *PublicKey) MarshalJSON() ([]byte, error) {
|
|
||||||
return json.Marshal(&k.jwk)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (k *PublicKey) UnmarshalJSON(data []byte) error {
|
|
||||||
var jwk jose.JWK
|
|
||||||
if err := json.Unmarshal(data, &jwk); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
k.jwk = jwk
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (k *PublicKey) ID() string {
|
|
||||||
return k.jwk.ID
|
|
||||||
}
|
|
||||||
|
|
||||||
func (k *PublicKey) Verifier() (jose.Verifier, error) {
|
|
||||||
return jose.NewVerifierRSA(k.jwk)
|
|
||||||
}
|
|
||||||
|
|
||||||
type PrivateKey struct {
|
|
||||||
KeyID string
|
|
||||||
PrivateKey *rsa.PrivateKey
|
|
||||||
}
|
|
||||||
|
|
||||||
func (k *PrivateKey) ID() string {
|
|
||||||
return k.KeyID
|
|
||||||
}
|
|
||||||
|
|
||||||
func (k *PrivateKey) Signer() jose.Signer {
|
|
||||||
return jose.NewSignerRSA(k.ID(), *k.PrivateKey)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (k *PrivateKey) JWK() jose.JWK {
|
|
||||||
return jose.JWK{
|
|
||||||
ID: k.KeyID,
|
|
||||||
Type: "RSA",
|
|
||||||
Alg: "RS256",
|
|
||||||
Use: "sig",
|
|
||||||
Exponent: k.PrivateKey.PublicKey.E,
|
|
||||||
Modulus: k.PrivateKey.PublicKey.N,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type KeySet interface {
|
|
||||||
ExpiresAt() time.Time
|
|
||||||
}
|
|
||||||
|
|
||||||
type PublicKeySet struct {
|
|
||||||
keys []PublicKey
|
|
||||||
index map[string]*PublicKey
|
|
||||||
expiresAt time.Time
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewPublicKeySet(jwks []jose.JWK, exp time.Time) *PublicKeySet {
|
|
||||||
keys := make([]PublicKey, len(jwks))
|
|
||||||
index := make(map[string]*PublicKey)
|
|
||||||
for i, jwk := range jwks {
|
|
||||||
keys[i] = *NewPublicKey(jwk)
|
|
||||||
index[keys[i].ID()] = &keys[i]
|
|
||||||
}
|
|
||||||
return &PublicKeySet{
|
|
||||||
keys: keys,
|
|
||||||
index: index,
|
|
||||||
expiresAt: exp,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *PublicKeySet) ExpiresAt() time.Time {
|
|
||||||
return s.expiresAt
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *PublicKeySet) Keys() []PublicKey {
|
|
||||||
return s.keys
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *PublicKeySet) Key(id string) *PublicKey {
|
|
||||||
return s.index[id]
|
|
||||||
}
|
|
||||||
|
|
||||||
type PrivateKeySet struct {
|
|
||||||
keys []*PrivateKey
|
|
||||||
ActiveKeyID string
|
|
||||||
expiresAt time.Time
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewPrivateKeySet(keys []*PrivateKey, exp time.Time) *PrivateKeySet {
|
|
||||||
return &PrivateKeySet{
|
|
||||||
keys: keys,
|
|
||||||
ActiveKeyID: keys[0].ID(),
|
|
||||||
expiresAt: exp.UTC(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *PrivateKeySet) Keys() []*PrivateKey {
|
|
||||||
return s.keys
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *PrivateKeySet) ExpiresAt() time.Time {
|
|
||||||
return s.expiresAt
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *PrivateKeySet) Active() *PrivateKey {
|
|
||||||
for i, k := range s.keys {
|
|
||||||
if k.ID() == s.ActiveKeyID {
|
|
||||||
return s.keys[i]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type GeneratePrivateKeyFunc func() (*PrivateKey, error)
|
|
||||||
|
|
||||||
func GeneratePrivateKey() (*PrivateKey, error) {
|
|
||||||
pk, err := rsa.GenerateKey(rand.Reader, 2048)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
keyID := make([]byte, 20)
|
|
||||||
if _, err := io.ReadFull(rand.Reader, keyID); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
k := PrivateKey{
|
|
||||||
KeyID: hex.EncodeToString(keyID),
|
|
||||||
PrivateKey: pk,
|
|
||||||
}
|
|
||||||
|
|
||||||
return &k, nil
|
|
||||||
}
|
|
99
vendor/github.com/coreos/go-oidc/key/manager.go
generated
vendored
99
vendor/github.com/coreos/go-oidc/key/manager.go
generated
vendored
@ -1,99 +0,0 @@
|
|||||||
package key
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/jonboulle/clockwork"
|
|
||||||
|
|
||||||
"github.com/coreos/go-oidc/jose"
|
|
||||||
"github.com/coreos/pkg/health"
|
|
||||||
)
|
|
||||||
|
|
||||||
type PrivateKeyManager interface {
|
|
||||||
ExpiresAt() time.Time
|
|
||||||
Signer() (jose.Signer, error)
|
|
||||||
JWKs() ([]jose.JWK, error)
|
|
||||||
PublicKeys() ([]PublicKey, error)
|
|
||||||
|
|
||||||
WritableKeySetRepo
|
|
||||||
health.Checkable
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewPrivateKeyManager() PrivateKeyManager {
|
|
||||||
return &privateKeyManager{
|
|
||||||
clock: clockwork.NewRealClock(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type privateKeyManager struct {
|
|
||||||
keySet *PrivateKeySet
|
|
||||||
clock clockwork.Clock
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *privateKeyManager) ExpiresAt() time.Time {
|
|
||||||
if m.keySet == nil {
|
|
||||||
return m.clock.Now().UTC()
|
|
||||||
}
|
|
||||||
|
|
||||||
return m.keySet.ExpiresAt()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *privateKeyManager) Signer() (jose.Signer, error) {
|
|
||||||
if err := m.Healthy(); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return m.keySet.Active().Signer(), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *privateKeyManager) JWKs() ([]jose.JWK, error) {
|
|
||||||
if err := m.Healthy(); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
keys := m.keySet.Keys()
|
|
||||||
jwks := make([]jose.JWK, len(keys))
|
|
||||||
for i, k := range keys {
|
|
||||||
jwks[i] = k.JWK()
|
|
||||||
}
|
|
||||||
return jwks, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *privateKeyManager) PublicKeys() ([]PublicKey, error) {
|
|
||||||
jwks, err := m.JWKs()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
keys := make([]PublicKey, len(jwks))
|
|
||||||
for i, jwk := range jwks {
|
|
||||||
keys[i] = *NewPublicKey(jwk)
|
|
||||||
}
|
|
||||||
return keys, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *privateKeyManager) Healthy() error {
|
|
||||||
if m.keySet == nil {
|
|
||||||
return errors.New("private key manager uninitialized")
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(m.keySet.Keys()) == 0 {
|
|
||||||
return errors.New("private key manager zero keys")
|
|
||||||
}
|
|
||||||
|
|
||||||
if m.keySet.ExpiresAt().Before(m.clock.Now().UTC()) {
|
|
||||||
return errors.New("private key manager keys expired")
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *privateKeyManager) Set(keySet KeySet) error {
|
|
||||||
privKeySet, ok := keySet.(*PrivateKeySet)
|
|
||||||
if !ok {
|
|
||||||
return errors.New("unable to cast to PrivateKeySet")
|
|
||||||
}
|
|
||||||
|
|
||||||
m.keySet = privKeySet
|
|
||||||
return nil
|
|
||||||
}
|
|
55
vendor/github.com/coreos/go-oidc/key/repo.go
generated
vendored
55
vendor/github.com/coreos/go-oidc/key/repo.go
generated
vendored
@ -1,55 +0,0 @@
|
|||||||
package key
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"sync"
|
|
||||||
)
|
|
||||||
|
|
||||||
var ErrorNoKeys = errors.New("no keys found")
|
|
||||||
|
|
||||||
type WritableKeySetRepo interface {
|
|
||||||
Set(KeySet) error
|
|
||||||
}
|
|
||||||
|
|
||||||
type ReadableKeySetRepo interface {
|
|
||||||
Get() (KeySet, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
type PrivateKeySetRepo interface {
|
|
||||||
WritableKeySetRepo
|
|
||||||
ReadableKeySetRepo
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewPrivateKeySetRepo() PrivateKeySetRepo {
|
|
||||||
return &memPrivateKeySetRepo{}
|
|
||||||
}
|
|
||||||
|
|
||||||
type memPrivateKeySetRepo struct {
|
|
||||||
mu sync.RWMutex
|
|
||||||
pks PrivateKeySet
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *memPrivateKeySetRepo) Set(ks KeySet) error {
|
|
||||||
pks, ok := ks.(*PrivateKeySet)
|
|
||||||
if !ok {
|
|
||||||
return errors.New("unable to cast to PrivateKeySet")
|
|
||||||
} else if pks == nil {
|
|
||||||
return errors.New("nil KeySet")
|
|
||||||
}
|
|
||||||
|
|
||||||
r.mu.Lock()
|
|
||||||
defer r.mu.Unlock()
|
|
||||||
|
|
||||||
r.pks = *pks
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *memPrivateKeySetRepo) Get() (KeySet, error) {
|
|
||||||
r.mu.RLock()
|
|
||||||
defer r.mu.RUnlock()
|
|
||||||
|
|
||||||
if r.pks.keys == nil {
|
|
||||||
return nil, ErrorNoKeys
|
|
||||||
}
|
|
||||||
return KeySet(&r.pks), nil
|
|
||||||
}
|
|
159
vendor/github.com/coreos/go-oidc/key/rotate.go
generated
vendored
159
vendor/github.com/coreos/go-oidc/key/rotate.go
generated
vendored
@ -1,159 +0,0 @@
|
|||||||
package key
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"log"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
ptime "github.com/coreos/pkg/timeutil"
|
|
||||||
"github.com/jonboulle/clockwork"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
ErrorPrivateKeysExpired = errors.New("private keys have expired")
|
|
||||||
)
|
|
||||||
|
|
||||||
func NewPrivateKeyRotator(repo PrivateKeySetRepo, ttl time.Duration) *PrivateKeyRotator {
|
|
||||||
return &PrivateKeyRotator{
|
|
||||||
repo: repo,
|
|
||||||
ttl: ttl,
|
|
||||||
|
|
||||||
keep: 2,
|
|
||||||
generateKey: GeneratePrivateKey,
|
|
||||||
clock: clockwork.NewRealClock(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type PrivateKeyRotator struct {
|
|
||||||
repo PrivateKeySetRepo
|
|
||||||
generateKey GeneratePrivateKeyFunc
|
|
||||||
clock clockwork.Clock
|
|
||||||
keep int
|
|
||||||
ttl time.Duration
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *PrivateKeyRotator) expiresAt() time.Time {
|
|
||||||
return r.clock.Now().UTC().Add(r.ttl)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *PrivateKeyRotator) Healthy() error {
|
|
||||||
pks, err := r.privateKeySet()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if r.clock.Now().After(pks.ExpiresAt()) {
|
|
||||||
return ErrorPrivateKeysExpired
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *PrivateKeyRotator) privateKeySet() (*PrivateKeySet, error) {
|
|
||||||
ks, err := r.repo.Get()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
pks, ok := ks.(*PrivateKeySet)
|
|
||||||
if !ok {
|
|
||||||
return nil, errors.New("unable to cast to PrivateKeySet")
|
|
||||||
}
|
|
||||||
return pks, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *PrivateKeyRotator) nextRotation() (time.Duration, error) {
|
|
||||||
pks, err := r.privateKeySet()
|
|
||||||
if err == ErrorNoKeys {
|
|
||||||
return 0, nil
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
|
|
||||||
now := r.clock.Now()
|
|
||||||
|
|
||||||
// Ideally, we want to rotate after half the TTL has elapsed.
|
|
||||||
idealRotationTime := pks.ExpiresAt().Add(-r.ttl / 2)
|
|
||||||
|
|
||||||
// If we are past the ideal rotation time, rotate immediatly.
|
|
||||||
return max(0, idealRotationTime.Sub(now)), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func max(a, b time.Duration) time.Duration {
|
|
||||||
if a > b {
|
|
||||||
return a
|
|
||||||
}
|
|
||||||
return b
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *PrivateKeyRotator) Run() chan struct{} {
|
|
||||||
attempt := func() {
|
|
||||||
k, err := r.generateKey()
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("go-oidc: failed generating signing key: %v", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
exp := r.expiresAt()
|
|
||||||
if err := rotatePrivateKeys(r.repo, k, r.keep, exp); err != nil {
|
|
||||||
log.Printf("go-oidc: key rotation failed: %v", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
stop := make(chan struct{})
|
|
||||||
go func() {
|
|
||||||
for {
|
|
||||||
var nextRotation time.Duration
|
|
||||||
var sleep time.Duration
|
|
||||||
var err error
|
|
||||||
for {
|
|
||||||
if nextRotation, err = r.nextRotation(); err == nil {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
sleep = ptime.ExpBackoff(sleep, time.Minute)
|
|
||||||
log.Printf("go-oidc: error getting nextRotation, retrying in %v: %v", sleep, err)
|
|
||||||
time.Sleep(sleep)
|
|
||||||
}
|
|
||||||
|
|
||||||
select {
|
|
||||||
case <-r.clock.After(nextRotation):
|
|
||||||
attempt()
|
|
||||||
case <-stop:
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
return stop
|
|
||||||
}
|
|
||||||
|
|
||||||
func rotatePrivateKeys(repo PrivateKeySetRepo, k *PrivateKey, keep int, exp time.Time) error {
|
|
||||||
ks, err := repo.Get()
|
|
||||||
if err != nil && err != ErrorNoKeys {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
var keys []*PrivateKey
|
|
||||||
if ks != nil {
|
|
||||||
pks, ok := ks.(*PrivateKeySet)
|
|
||||||
if !ok {
|
|
||||||
return errors.New("unable to cast to PrivateKeySet")
|
|
||||||
}
|
|
||||||
keys = pks.Keys()
|
|
||||||
}
|
|
||||||
|
|
||||||
keys = append([]*PrivateKey{k}, keys...)
|
|
||||||
if l := len(keys); l > keep {
|
|
||||||
keys = keys[0:keep]
|
|
||||||
}
|
|
||||||
|
|
||||||
nks := PrivateKeySet{
|
|
||||||
keys: keys,
|
|
||||||
ActiveKeyID: k.ID(),
|
|
||||||
expiresAt: exp,
|
|
||||||
}
|
|
||||||
|
|
||||||
return repo.Set(KeySet(&nks))
|
|
||||||
}
|
|
91
vendor/github.com/coreos/go-oidc/key/sync.go
generated
vendored
91
vendor/github.com/coreos/go-oidc/key/sync.go
generated
vendored
@ -1,91 +0,0 @@
|
|||||||
package key
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"log"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/jonboulle/clockwork"
|
|
||||||
|
|
||||||
"github.com/coreos/pkg/timeutil"
|
|
||||||
)
|
|
||||||
|
|
||||||
func NewKeySetSyncer(r ReadableKeySetRepo, w WritableKeySetRepo) *KeySetSyncer {
|
|
||||||
return &KeySetSyncer{
|
|
||||||
readable: r,
|
|
||||||
writable: w,
|
|
||||||
clock: clockwork.NewRealClock(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type KeySetSyncer struct {
|
|
||||||
readable ReadableKeySetRepo
|
|
||||||
writable WritableKeySetRepo
|
|
||||||
clock clockwork.Clock
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *KeySetSyncer) Run() chan struct{} {
|
|
||||||
stop := make(chan struct{})
|
|
||||||
go func() {
|
|
||||||
var failing bool
|
|
||||||
var next time.Duration
|
|
||||||
for {
|
|
||||||
exp, err := syncKeySet(s.readable, s.writable, s.clock)
|
|
||||||
if err != nil || exp == 0 {
|
|
||||||
if !failing {
|
|
||||||
failing = true
|
|
||||||
next = time.Second
|
|
||||||
} else {
|
|
||||||
next = timeutil.ExpBackoff(next, time.Minute)
|
|
||||||
}
|
|
||||||
if exp == 0 {
|
|
||||||
log.Printf("Synced to already expired key set, retrying in %v: %v", next, err)
|
|
||||||
|
|
||||||
} else {
|
|
||||||
log.Printf("Failed syncing key set, retrying in %v: %v", next, err)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
failing = false
|
|
||||||
next = exp / 2
|
|
||||||
}
|
|
||||||
|
|
||||||
select {
|
|
||||||
case <-s.clock.After(next):
|
|
||||||
continue
|
|
||||||
case <-stop:
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
return stop
|
|
||||||
}
|
|
||||||
|
|
||||||
func Sync(r ReadableKeySetRepo, w WritableKeySetRepo) (time.Duration, error) {
|
|
||||||
return syncKeySet(r, w, clockwork.NewRealClock())
|
|
||||||
}
|
|
||||||
|
|
||||||
// syncKeySet copies the keyset from r to the KeySet at w and returns the duration in which the KeySet will expire.
|
|
||||||
// If keyset has already expired, returns a zero duration.
|
|
||||||
func syncKeySet(r ReadableKeySetRepo, w WritableKeySetRepo, clock clockwork.Clock) (exp time.Duration, err error) {
|
|
||||||
var ks KeySet
|
|
||||||
ks, err = r.Get()
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if ks == nil {
|
|
||||||
err = errors.New("no source KeySet")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = w.Set(ks); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
now := clock.Now()
|
|
||||||
if ks.ExpiresAt().After(now) {
|
|
||||||
exp = ks.ExpiresAt().Sub(now)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
2
vendor/github.com/coreos/go-oidc/oauth2/doc.go
generated
vendored
2
vendor/github.com/coreos/go-oidc/oauth2/doc.go
generated
vendored
@ -1,2 +0,0 @@
|
|||||||
// Package oauth2 is DEPRECATED. Use golang.org/x/oauth instead.
|
|
||||||
package oauth2
|
|
29
vendor/github.com/coreos/go-oidc/oauth2/error.go
generated
vendored
29
vendor/github.com/coreos/go-oidc/oauth2/error.go
generated
vendored
@ -1,29 +0,0 @@
|
|||||||
package oauth2
|
|
||||||
|
|
||||||
const (
|
|
||||||
ErrorAccessDenied = "access_denied"
|
|
||||||
ErrorInvalidClient = "invalid_client"
|
|
||||||
ErrorInvalidGrant = "invalid_grant"
|
|
||||||
ErrorInvalidRequest = "invalid_request"
|
|
||||||
ErrorServerError = "server_error"
|
|
||||||
ErrorUnauthorizedClient = "unauthorized_client"
|
|
||||||
ErrorUnsupportedGrantType = "unsupported_grant_type"
|
|
||||||
ErrorUnsupportedResponseType = "unsupported_response_type"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Error struct {
|
|
||||||
Type string `json:"error"`
|
|
||||||
Description string `json:"error_description,omitempty"`
|
|
||||||
State string `json:"state,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *Error) Error() string {
|
|
||||||
if e.Description != "" {
|
|
||||||
return e.Type + ": " + e.Description
|
|
||||||
}
|
|
||||||
return e.Type
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewError(typ string) *Error {
|
|
||||||
return &Error{Type: typ}
|
|
||||||
}
|
|
416
vendor/github.com/coreos/go-oidc/oauth2/oauth2.go
generated
vendored
416
vendor/github.com/coreos/go-oidc/oauth2/oauth2.go
generated
vendored
@ -1,416 +0,0 @@
|
|||||||
package oauth2
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"io/ioutil"
|
|
||||||
"mime"
|
|
||||||
"net/http"
|
|
||||||
"net/url"
|
|
||||||
"sort"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
phttp "github.com/coreos/go-oidc/http"
|
|
||||||
)
|
|
||||||
|
|
||||||
// ResponseTypesEqual compares two response_type values. If either
|
|
||||||
// contains a space, it is treated as an unordered list. For example,
|
|
||||||
// comparing "code id_token" and "id_token code" would evaluate to true.
|
|
||||||
func ResponseTypesEqual(r1, r2 string) bool {
|
|
||||||
if !strings.Contains(r1, " ") || !strings.Contains(r2, " ") {
|
|
||||||
// fast route, no split needed
|
|
||||||
return r1 == r2
|
|
||||||
}
|
|
||||||
|
|
||||||
// split, sort, and compare
|
|
||||||
r1Fields := strings.Fields(r1)
|
|
||||||
r2Fields := strings.Fields(r2)
|
|
||||||
if len(r1Fields) != len(r2Fields) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
sort.Strings(r1Fields)
|
|
||||||
sort.Strings(r2Fields)
|
|
||||||
for i, r1Field := range r1Fields {
|
|
||||||
if r1Field != r2Fields[i] {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
const (
|
|
||||||
// OAuth2.0 response types registered by OIDC.
|
|
||||||
//
|
|
||||||
// See: https://openid.net/specs/oauth-v2-multiple-response-types-1_0.html#RegistryContents
|
|
||||||
ResponseTypeCode = "code"
|
|
||||||
ResponseTypeCodeIDToken = "code id_token"
|
|
||||||
ResponseTypeCodeIDTokenToken = "code id_token token"
|
|
||||||
ResponseTypeIDToken = "id_token"
|
|
||||||
ResponseTypeIDTokenToken = "id_token token"
|
|
||||||
ResponseTypeToken = "token"
|
|
||||||
ResponseTypeNone = "none"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
GrantTypeAuthCode = "authorization_code"
|
|
||||||
GrantTypeClientCreds = "client_credentials"
|
|
||||||
GrantTypeUserCreds = "password"
|
|
||||||
GrantTypeImplicit = "implicit"
|
|
||||||
GrantTypeRefreshToken = "refresh_token"
|
|
||||||
|
|
||||||
AuthMethodClientSecretPost = "client_secret_post"
|
|
||||||
AuthMethodClientSecretBasic = "client_secret_basic"
|
|
||||||
AuthMethodClientSecretJWT = "client_secret_jwt"
|
|
||||||
AuthMethodPrivateKeyJWT = "private_key_jwt"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Config struct {
|
|
||||||
Credentials ClientCredentials
|
|
||||||
Scope []string
|
|
||||||
RedirectURL string
|
|
||||||
AuthURL string
|
|
||||||
TokenURL string
|
|
||||||
|
|
||||||
// Must be one of the AuthMethodXXX methods above. Right now, only
|
|
||||||
// AuthMethodClientSecretPost and AuthMethodClientSecretBasic are supported.
|
|
||||||
AuthMethod string
|
|
||||||
}
|
|
||||||
|
|
||||||
type Client struct {
|
|
||||||
hc phttp.Client
|
|
||||||
creds ClientCredentials
|
|
||||||
scope []string
|
|
||||||
authURL *url.URL
|
|
||||||
redirectURL *url.URL
|
|
||||||
tokenURL *url.URL
|
|
||||||
authMethod string
|
|
||||||
}
|
|
||||||
|
|
||||||
type ClientCredentials struct {
|
|
||||||
ID string
|
|
||||||
Secret string
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewClient(hc phttp.Client, cfg Config) (c *Client, err error) {
|
|
||||||
if len(cfg.Credentials.ID) == 0 {
|
|
||||||
err = errors.New("missing client id")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(cfg.Credentials.Secret) == 0 {
|
|
||||||
err = errors.New("missing client secret")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if cfg.AuthMethod == "" {
|
|
||||||
cfg.AuthMethod = AuthMethodClientSecretBasic
|
|
||||||
} else if cfg.AuthMethod != AuthMethodClientSecretPost && cfg.AuthMethod != AuthMethodClientSecretBasic {
|
|
||||||
err = fmt.Errorf("auth method %q is not supported", cfg.AuthMethod)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
au, err := phttp.ParseNonEmptyURL(cfg.AuthURL)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
tu, err := phttp.ParseNonEmptyURL(cfg.TokenURL)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Allow empty redirect URL in the case where the client
|
|
||||||
// only needs to verify a given token.
|
|
||||||
ru, err := url.Parse(cfg.RedirectURL)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
c = &Client{
|
|
||||||
creds: cfg.Credentials,
|
|
||||||
scope: cfg.Scope,
|
|
||||||
redirectURL: ru,
|
|
||||||
authURL: au,
|
|
||||||
tokenURL: tu,
|
|
||||||
hc: hc,
|
|
||||||
authMethod: cfg.AuthMethod,
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Return the embedded HTTP client
|
|
||||||
func (c *Client) HttpClient() phttp.Client {
|
|
||||||
return c.hc
|
|
||||||
}
|
|
||||||
|
|
||||||
// Generate the url for initial redirect to oauth provider.
|
|
||||||
func (c *Client) AuthCodeURL(state, accessType, prompt string) string {
|
|
||||||
v := c.commonURLValues()
|
|
||||||
v.Set("state", state)
|
|
||||||
if strings.ToLower(accessType) == "offline" {
|
|
||||||
v.Set("access_type", "offline")
|
|
||||||
}
|
|
||||||
|
|
||||||
if prompt != "" {
|
|
||||||
v.Set("prompt", prompt)
|
|
||||||
}
|
|
||||||
v.Set("response_type", "code")
|
|
||||||
|
|
||||||
q := v.Encode()
|
|
||||||
u := *c.authURL
|
|
||||||
if u.RawQuery == "" {
|
|
||||||
u.RawQuery = q
|
|
||||||
} else {
|
|
||||||
u.RawQuery += "&" + q
|
|
||||||
}
|
|
||||||
return u.String()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Client) commonURLValues() url.Values {
|
|
||||||
return url.Values{
|
|
||||||
"redirect_uri": {c.redirectURL.String()},
|
|
||||||
"scope": {strings.Join(c.scope, " ")},
|
|
||||||
"client_id": {c.creds.ID},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Client) newAuthenticatedRequest(urlToken string, values url.Values) (*http.Request, error) {
|
|
||||||
var req *http.Request
|
|
||||||
var err error
|
|
||||||
switch c.authMethod {
|
|
||||||
case AuthMethodClientSecretPost:
|
|
||||||
values.Set("client_secret", c.creds.Secret)
|
|
||||||
req, err = http.NewRequest("POST", urlToken, strings.NewReader(values.Encode()))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
case AuthMethodClientSecretBasic:
|
|
||||||
req, err = http.NewRequest("POST", urlToken, strings.NewReader(values.Encode()))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
encodedID := url.QueryEscape(c.creds.ID)
|
|
||||||
encodedSecret := url.QueryEscape(c.creds.Secret)
|
|
||||||
req.SetBasicAuth(encodedID, encodedSecret)
|
|
||||||
default:
|
|
||||||
panic("misconfigured client: auth method not supported")
|
|
||||||
}
|
|
||||||
|
|
||||||
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
|
||||||
return req, nil
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// ClientCredsToken posts the client id and secret to obtain a token scoped to the OAuth2 client via the "client_credentials" grant type.
|
|
||||||
// May not be supported by all OAuth2 servers.
|
|
||||||
func (c *Client) ClientCredsToken(scope []string) (result TokenResponse, err error) {
|
|
||||||
v := url.Values{
|
|
||||||
"scope": {strings.Join(scope, " ")},
|
|
||||||
"grant_type": {GrantTypeClientCreds},
|
|
||||||
}
|
|
||||||
|
|
||||||
req, err := c.newAuthenticatedRequest(c.tokenURL.String(), v)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
resp, err := c.hc.Do(req)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
return parseTokenResponse(resp)
|
|
||||||
}
|
|
||||||
|
|
||||||
// UserCredsToken posts the username and password to obtain a token scoped to the OAuth2 client via the "password" grant_type
|
|
||||||
// May not be supported by all OAuth2 servers.
|
|
||||||
func (c *Client) UserCredsToken(username, password string) (result TokenResponse, err error) {
|
|
||||||
v := url.Values{
|
|
||||||
"scope": {strings.Join(c.scope, " ")},
|
|
||||||
"grant_type": {GrantTypeUserCreds},
|
|
||||||
"username": {username},
|
|
||||||
"password": {password},
|
|
||||||
}
|
|
||||||
|
|
||||||
req, err := c.newAuthenticatedRequest(c.tokenURL.String(), v)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
resp, err := c.hc.Do(req)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
return parseTokenResponse(resp)
|
|
||||||
}
|
|
||||||
|
|
||||||
// RequestToken requests a token from the Token Endpoint with the specified grantType.
|
|
||||||
// If 'grantType' == GrantTypeAuthCode, then 'value' should be the authorization code.
|
|
||||||
// If 'grantType' == GrantTypeRefreshToken, then 'value' should be the refresh token.
|
|
||||||
func (c *Client) RequestToken(grantType, value string) (result TokenResponse, err error) {
|
|
||||||
v := c.commonURLValues()
|
|
||||||
|
|
||||||
v.Set("grant_type", grantType)
|
|
||||||
v.Set("client_secret", c.creds.Secret)
|
|
||||||
switch grantType {
|
|
||||||
case GrantTypeAuthCode:
|
|
||||||
v.Set("code", value)
|
|
||||||
case GrantTypeRefreshToken:
|
|
||||||
v.Set("refresh_token", value)
|
|
||||||
default:
|
|
||||||
err = fmt.Errorf("unsupported grant_type: %v", grantType)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
req, err := c.newAuthenticatedRequest(c.tokenURL.String(), v)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
resp, err := c.hc.Do(req)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
return parseTokenResponse(resp)
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseTokenResponse(resp *http.Response) (result TokenResponse, err error) {
|
|
||||||
body, err := ioutil.ReadAll(resp.Body)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
badStatusCode := resp.StatusCode < 200 || resp.StatusCode > 299
|
|
||||||
|
|
||||||
contentType, _, err := mime.ParseMediaType(resp.Header.Get("Content-Type"))
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
result = TokenResponse{
|
|
||||||
RawBody: body,
|
|
||||||
}
|
|
||||||
|
|
||||||
newError := func(typ, desc, state string) error {
|
|
||||||
if typ == "" {
|
|
||||||
return fmt.Errorf("unrecognized error %s", body)
|
|
||||||
}
|
|
||||||
return &Error{typ, desc, state}
|
|
||||||
}
|
|
||||||
|
|
||||||
if contentType == "application/x-www-form-urlencoded" || contentType == "text/plain" {
|
|
||||||
var vals url.Values
|
|
||||||
vals, err = url.ParseQuery(string(body))
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if error := vals.Get("error"); error != "" || badStatusCode {
|
|
||||||
err = newError(error, vals.Get("error_description"), vals.Get("state"))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
e := vals.Get("expires_in")
|
|
||||||
if e == "" {
|
|
||||||
e = vals.Get("expires")
|
|
||||||
}
|
|
||||||
if e != "" {
|
|
||||||
result.Expires, err = strconv.Atoi(e)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
result.AccessToken = vals.Get("access_token")
|
|
||||||
result.TokenType = vals.Get("token_type")
|
|
||||||
result.IDToken = vals.Get("id_token")
|
|
||||||
result.RefreshToken = vals.Get("refresh_token")
|
|
||||||
result.Scope = vals.Get("scope")
|
|
||||||
} else {
|
|
||||||
var r struct {
|
|
||||||
AccessToken string `json:"access_token"`
|
|
||||||
TokenType string `json:"token_type"`
|
|
||||||
IDToken string `json:"id_token"`
|
|
||||||
RefreshToken string `json:"refresh_token"`
|
|
||||||
Scope string `json:"scope"`
|
|
||||||
State string `json:"state"`
|
|
||||||
ExpiresIn json.Number `json:"expires_in"` // Azure AD returns string
|
|
||||||
Expires int `json:"expires"`
|
|
||||||
Error string `json:"error"`
|
|
||||||
Desc string `json:"error_description"`
|
|
||||||
}
|
|
||||||
if err = json.Unmarshal(body, &r); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if r.Error != "" || badStatusCode {
|
|
||||||
err = newError(r.Error, r.Desc, r.State)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
result.AccessToken = r.AccessToken
|
|
||||||
result.TokenType = r.TokenType
|
|
||||||
result.IDToken = r.IDToken
|
|
||||||
result.RefreshToken = r.RefreshToken
|
|
||||||
result.Scope = r.Scope
|
|
||||||
if expiresIn, err := r.ExpiresIn.Int64(); err != nil {
|
|
||||||
result.Expires = r.Expires
|
|
||||||
} else {
|
|
||||||
result.Expires = int(expiresIn)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
type TokenResponse struct {
|
|
||||||
AccessToken string
|
|
||||||
TokenType string
|
|
||||||
Expires int
|
|
||||||
IDToken string
|
|
||||||
RefreshToken string // OPTIONAL.
|
|
||||||
Scope string // OPTIONAL, if identical to the scope requested by the client, otherwise, REQUIRED.
|
|
||||||
RawBody []byte // In case callers need some other non-standard info from the token response
|
|
||||||
}
|
|
||||||
|
|
||||||
type AuthCodeRequest struct {
|
|
||||||
ResponseType string
|
|
||||||
ClientID string
|
|
||||||
RedirectURL *url.URL
|
|
||||||
Scope []string
|
|
||||||
State string
|
|
||||||
}
|
|
||||||
|
|
||||||
func ParseAuthCodeRequest(q url.Values) (AuthCodeRequest, error) {
|
|
||||||
acr := AuthCodeRequest{
|
|
||||||
ResponseType: q.Get("response_type"),
|
|
||||||
ClientID: q.Get("client_id"),
|
|
||||||
State: q.Get("state"),
|
|
||||||
Scope: make([]string, 0),
|
|
||||||
}
|
|
||||||
|
|
||||||
qs := strings.TrimSpace(q.Get("scope"))
|
|
||||||
if qs != "" {
|
|
||||||
acr.Scope = strings.Split(qs, " ")
|
|
||||||
}
|
|
||||||
|
|
||||||
err := func() error {
|
|
||||||
if acr.ClientID == "" {
|
|
||||||
return NewError(ErrorInvalidRequest)
|
|
||||||
}
|
|
||||||
|
|
||||||
redirectURL := q.Get("redirect_uri")
|
|
||||||
if redirectURL != "" {
|
|
||||||
ru, err := url.Parse(redirectURL)
|
|
||||||
if err != nil {
|
|
||||||
return NewError(ErrorInvalidRequest)
|
|
||||||
}
|
|
||||||
acr.RedirectURL = ru
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}()
|
|
||||||
|
|
||||||
return acr, err
|
|
||||||
}
|
|
374
vendor/github.com/coreos/go-oidc/oidc.go
generated
vendored
Normal file
374
vendor/github.com/coreos/go-oidc/oidc.go
generated
vendored
Normal file
@ -0,0 +1,374 @@
|
|||||||
|
// Package oidc implements OpenID Connect client logic for the golang.org/x/oauth2 package.
|
||||||
|
package oidc
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"crypto/sha256"
|
||||||
|
"crypto/sha512"
|
||||||
|
"encoding/base64"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"hash"
|
||||||
|
"io/ioutil"
|
||||||
|
"mime"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"golang.org/x/oauth2"
|
||||||
|
jose "gopkg.in/square/go-jose.v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// ScopeOpenID is the mandatory scope for all OpenID Connect OAuth2 requests.
|
||||||
|
ScopeOpenID = "openid"
|
||||||
|
|
||||||
|
// ScopeOfflineAccess is an optional scope defined by OpenID Connect for requesting
|
||||||
|
// OAuth2 refresh tokens.
|
||||||
|
//
|
||||||
|
// Support for this scope differs between OpenID Connect providers. For instance
|
||||||
|
// Google rejects it, favoring appending "access_type=offline" as part of the
|
||||||
|
// authorization request instead.
|
||||||
|
//
|
||||||
|
// See: https://openid.net/specs/openid-connect-core-1_0.html#OfflineAccess
|
||||||
|
ScopeOfflineAccess = "offline_access"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
errNoAtHash = errors.New("id token did not have an access token hash")
|
||||||
|
errInvalidAtHash = errors.New("access token hash does not match value in ID token")
|
||||||
|
)
|
||||||
|
|
||||||
|
// ClientContext returns a new Context that carries the provided HTTP client.
|
||||||
|
//
|
||||||
|
// This method sets the same context key used by the golang.org/x/oauth2 package,
|
||||||
|
// so the returned context works for that package too.
|
||||||
|
//
|
||||||
|
// myClient := &http.Client{}
|
||||||
|
// ctx := oidc.ClientContext(parentContext, myClient)
|
||||||
|
//
|
||||||
|
// // This will use the custom client
|
||||||
|
// provider, err := oidc.NewProvider(ctx, "https://accounts.example.com")
|
||||||
|
//
|
||||||
|
func ClientContext(ctx context.Context, client *http.Client) context.Context {
|
||||||
|
return context.WithValue(ctx, oauth2.HTTPClient, client)
|
||||||
|
}
|
||||||
|
|
||||||
|
func doRequest(ctx context.Context, req *http.Request) (*http.Response, error) {
|
||||||
|
client := http.DefaultClient
|
||||||
|
if c, ok := ctx.Value(oauth2.HTTPClient).(*http.Client); ok {
|
||||||
|
client = c
|
||||||
|
}
|
||||||
|
return client.Do(req.WithContext(ctx))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Provider represents an OpenID Connect server's configuration.
|
||||||
|
type Provider struct {
|
||||||
|
issuer string
|
||||||
|
authURL string
|
||||||
|
tokenURL string
|
||||||
|
userInfoURL string
|
||||||
|
|
||||||
|
// Raw claims returned by the server.
|
||||||
|
rawClaims []byte
|
||||||
|
|
||||||
|
remoteKeySet KeySet
|
||||||
|
}
|
||||||
|
|
||||||
|
type cachedKeys struct {
|
||||||
|
keys []jose.JSONWebKey
|
||||||
|
expiry time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
type providerJSON struct {
|
||||||
|
Issuer string `json:"issuer"`
|
||||||
|
AuthURL string `json:"authorization_endpoint"`
|
||||||
|
TokenURL string `json:"token_endpoint"`
|
||||||
|
JWKSURL string `json:"jwks_uri"`
|
||||||
|
UserInfoURL string `json:"userinfo_endpoint"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewProvider uses the OpenID Connect discovery mechanism to construct a Provider.
|
||||||
|
//
|
||||||
|
// The issuer is the URL identifier for the service. For example: "https://accounts.google.com"
|
||||||
|
// or "https://login.salesforce.com".
|
||||||
|
func NewProvider(ctx context.Context, issuer string) (*Provider, error) {
|
||||||
|
wellKnown := strings.TrimSuffix(issuer, "/") + "/.well-known/openid-configuration"
|
||||||
|
req, err := http.NewRequest("GET", wellKnown, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
resp, err := doRequest(ctx, req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
body, err := ioutil.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("unable to read response body: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
return nil, fmt.Errorf("%s: %s", resp.Status, body)
|
||||||
|
}
|
||||||
|
|
||||||
|
var p providerJSON
|
||||||
|
err = unmarshalResp(resp, body, &p)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("oidc: failed to decode provider discovery object: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if p.Issuer != issuer {
|
||||||
|
return nil, fmt.Errorf("oidc: issuer did not match the issuer returned by provider, expected %q got %q", issuer, p.Issuer)
|
||||||
|
}
|
||||||
|
return &Provider{
|
||||||
|
issuer: p.Issuer,
|
||||||
|
authURL: p.AuthURL,
|
||||||
|
tokenURL: p.TokenURL,
|
||||||
|
userInfoURL: p.UserInfoURL,
|
||||||
|
rawClaims: body,
|
||||||
|
remoteKeySet: NewRemoteKeySet(ctx, p.JWKSURL),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Claims unmarshals raw fields returned by the server during discovery.
|
||||||
|
//
|
||||||
|
// var claims struct {
|
||||||
|
// ScopesSupported []string `json:"scopes_supported"`
|
||||||
|
// ClaimsSupported []string `json:"claims_supported"`
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// if err := provider.Claims(&claims); err != nil {
|
||||||
|
// // handle unmarshaling error
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// For a list of fields defined by the OpenID Connect spec see:
|
||||||
|
// https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderMetadata
|
||||||
|
func (p *Provider) Claims(v interface{}) error {
|
||||||
|
if p.rawClaims == nil {
|
||||||
|
return errors.New("oidc: claims not set")
|
||||||
|
}
|
||||||
|
return json.Unmarshal(p.rawClaims, v)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Endpoint returns the OAuth2 auth and token endpoints for the given provider.
|
||||||
|
func (p *Provider) Endpoint() oauth2.Endpoint {
|
||||||
|
return oauth2.Endpoint{AuthURL: p.authURL, TokenURL: p.tokenURL}
|
||||||
|
}
|
||||||
|
|
||||||
|
// UserInfo represents the OpenID Connect userinfo claims.
|
||||||
|
type UserInfo struct {
|
||||||
|
Subject string `json:"sub"`
|
||||||
|
Profile string `json:"profile"`
|
||||||
|
Email string `json:"email"`
|
||||||
|
EmailVerified bool `json:"email_verified"`
|
||||||
|
|
||||||
|
claims []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
// Claims unmarshals the raw JSON object claims into the provided object.
|
||||||
|
func (u *UserInfo) Claims(v interface{}) error {
|
||||||
|
if u.claims == nil {
|
||||||
|
return errors.New("oidc: claims not set")
|
||||||
|
}
|
||||||
|
return json.Unmarshal(u.claims, v)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UserInfo uses the token source to query the provider's user info endpoint.
|
||||||
|
func (p *Provider) UserInfo(ctx context.Context, tokenSource oauth2.TokenSource) (*UserInfo, error) {
|
||||||
|
if p.userInfoURL == "" {
|
||||||
|
return nil, errors.New("oidc: user info endpoint is not supported by this provider")
|
||||||
|
}
|
||||||
|
|
||||||
|
req, err := http.NewRequest("GET", p.userInfoURL, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("oidc: create GET request: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
token, err := tokenSource.Token()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("oidc: get access token: %v", err)
|
||||||
|
}
|
||||||
|
token.SetAuthHeader(req)
|
||||||
|
|
||||||
|
resp, err := doRequest(ctx, req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
body, err := ioutil.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
return nil, fmt.Errorf("%s: %s", resp.Status, body)
|
||||||
|
}
|
||||||
|
|
||||||
|
var userInfo UserInfo
|
||||||
|
if err := json.Unmarshal(body, &userInfo); err != nil {
|
||||||
|
return nil, fmt.Errorf("oidc: failed to decode userinfo: %v", err)
|
||||||
|
}
|
||||||
|
userInfo.claims = body
|
||||||
|
return &userInfo, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// IDToken is an OpenID Connect extension that provides a predictable representation
|
||||||
|
// of an authorization event.
|
||||||
|
//
|
||||||
|
// The ID Token only holds fields OpenID Connect requires. To access additional
|
||||||
|
// claims returned by the server, use the Claims method.
|
||||||
|
type IDToken struct {
|
||||||
|
// The URL of the server which issued this token. OpenID Connect
|
||||||
|
// requires this value always be identical to the URL used for
|
||||||
|
// initial discovery.
|
||||||
|
//
|
||||||
|
// Note: Because of a known issue with Google Accounts' implementation
|
||||||
|
// this value may differ when using Google.
|
||||||
|
//
|
||||||
|
// See: https://developers.google.com/identity/protocols/OpenIDConnect#obtainuserinfo
|
||||||
|
Issuer string
|
||||||
|
|
||||||
|
// The client ID, or set of client IDs, that this token is issued for. For
|
||||||
|
// common uses, this is the client that initialized the auth flow.
|
||||||
|
//
|
||||||
|
// This package ensures the audience contains an expected value.
|
||||||
|
Audience []string
|
||||||
|
|
||||||
|
// A unique string which identifies the end user.
|
||||||
|
Subject string
|
||||||
|
|
||||||
|
// Expiry of the token. Ths package will not process tokens that have
|
||||||
|
// expired unless that validation is explicitly turned off.
|
||||||
|
Expiry time.Time
|
||||||
|
// When the token was issued by the provider.
|
||||||
|
IssuedAt time.Time
|
||||||
|
|
||||||
|
// Initial nonce provided during the authentication redirect.
|
||||||
|
//
|
||||||
|
// This package does NOT provided verification on the value of this field
|
||||||
|
// and it's the user's responsibility to ensure it contains a valid value.
|
||||||
|
Nonce string
|
||||||
|
|
||||||
|
// at_hash claim, if set in the ID token. Callers can verify an access token
|
||||||
|
// that corresponds to the ID token using the VerifyAccessToken method.
|
||||||
|
AccessTokenHash string
|
||||||
|
|
||||||
|
// signature algorithm used for ID token, needed to compute a verification hash of an
|
||||||
|
// access token
|
||||||
|
sigAlgorithm string
|
||||||
|
|
||||||
|
// Raw payload of the id_token.
|
||||||
|
claims []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
// Claims unmarshals the raw JSON payload of the ID Token into a provided struct.
|
||||||
|
//
|
||||||
|
// idToken, err := idTokenVerifier.Verify(rawIDToken)
|
||||||
|
// if err != nil {
|
||||||
|
// // handle error
|
||||||
|
// }
|
||||||
|
// var claims struct {
|
||||||
|
// Email string `json:"email"`
|
||||||
|
// EmailVerified bool `json:"email_verified"`
|
||||||
|
// }
|
||||||
|
// if err := idToken.Claims(&claims); err != nil {
|
||||||
|
// // handle error
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
func (i *IDToken) Claims(v interface{}) error {
|
||||||
|
if i.claims == nil {
|
||||||
|
return errors.New("oidc: claims not set")
|
||||||
|
}
|
||||||
|
return json.Unmarshal(i.claims, v)
|
||||||
|
}
|
||||||
|
|
||||||
|
// VerifyAccessToken verifies that the hash of the access token that corresponds to the iD token
|
||||||
|
// matches the hash in the id token. It returns an error if the hashes don't match.
|
||||||
|
// It is the caller's responsibility to ensure that the optional access token hash is present for the ID token
|
||||||
|
// before calling this method. See https://openid.net/specs/openid-connect-core-1_0.html#CodeIDToken
|
||||||
|
func (i *IDToken) VerifyAccessToken(accessToken string) error {
|
||||||
|
if i.AccessTokenHash == "" {
|
||||||
|
return errNoAtHash
|
||||||
|
}
|
||||||
|
var h hash.Hash
|
||||||
|
switch i.sigAlgorithm {
|
||||||
|
case RS256, ES256, PS256:
|
||||||
|
h = sha256.New()
|
||||||
|
case RS384, ES384, PS384:
|
||||||
|
h = sha512.New384()
|
||||||
|
case RS512, ES512, PS512:
|
||||||
|
h = sha512.New()
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("oidc: unsupported signing algorithm %q", i.sigAlgorithm)
|
||||||
|
}
|
||||||
|
h.Write([]byte(accessToken)) // hash documents that Write will never return an error
|
||||||
|
sum := h.Sum(nil)[:h.Size()/2]
|
||||||
|
actual := base64.RawURLEncoding.EncodeToString(sum)
|
||||||
|
if actual != i.AccessTokenHash {
|
||||||
|
return errInvalidAtHash
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type idToken struct {
|
||||||
|
Issuer string `json:"iss"`
|
||||||
|
Subject string `json:"sub"`
|
||||||
|
Audience audience `json:"aud"`
|
||||||
|
Expiry jsonTime `json:"exp"`
|
||||||
|
IssuedAt jsonTime `json:"iat"`
|
||||||
|
Nonce string `json:"nonce"`
|
||||||
|
AtHash string `json:"at_hash"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type audience []string
|
||||||
|
|
||||||
|
func (a *audience) UnmarshalJSON(b []byte) error {
|
||||||
|
var s string
|
||||||
|
if json.Unmarshal(b, &s) == nil {
|
||||||
|
*a = audience{s}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
var auds []string
|
||||||
|
if err := json.Unmarshal(b, &auds); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
*a = audience(auds)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type jsonTime time.Time
|
||||||
|
|
||||||
|
func (j *jsonTime) UnmarshalJSON(b []byte) error {
|
||||||
|
var n json.Number
|
||||||
|
if err := json.Unmarshal(b, &n); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
var unix int64
|
||||||
|
|
||||||
|
if t, err := n.Int64(); err == nil {
|
||||||
|
unix = t
|
||||||
|
} else {
|
||||||
|
f, err := n.Float64()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
unix = int64(f)
|
||||||
|
}
|
||||||
|
*j = jsonTime(time.Unix(unix, 0))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func unmarshalResp(r *http.Response, body []byte, v interface{}) error {
|
||||||
|
err := json.Unmarshal(body, &v)
|
||||||
|
if err == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
ct := r.Header.Get("Content-Type")
|
||||||
|
mediaType, _, parseErr := mime.ParseMediaType(ct)
|
||||||
|
if parseErr == nil && mediaType == "application/json" {
|
||||||
|
return fmt.Errorf("got Content-Type = application/json, but could not unmarshal as JSON: %v", err)
|
||||||
|
}
|
||||||
|
return fmt.Errorf("expected Content-Type = application/json, got %q: %v", ct, err)
|
||||||
|
}
|
40
vendor/github.com/coreos/go-oidc/oidc/BUILD
generated
vendored
40
vendor/github.com/coreos/go-oidc/oidc/BUILD
generated
vendored
@ -1,40 +0,0 @@
|
|||||||
load("@io_bazel_rules_go//go:def.bzl", "go_library")
|
|
||||||
|
|
||||||
go_library(
|
|
||||||
name = "go_default_library",
|
|
||||||
srcs = [
|
|
||||||
"client.go",
|
|
||||||
"doc.go",
|
|
||||||
"identity.go",
|
|
||||||
"interface.go",
|
|
||||||
"key.go",
|
|
||||||
"provider.go",
|
|
||||||
"transport.go",
|
|
||||||
"util.go",
|
|
||||||
"verification.go",
|
|
||||||
],
|
|
||||||
importpath = "github.com/coreos/go-oidc/oidc",
|
|
||||||
visibility = ["//visibility:public"],
|
|
||||||
deps = [
|
|
||||||
"//vendor/github.com/coreos/go-oidc/http:go_default_library",
|
|
||||||
"//vendor/github.com/coreos/go-oidc/jose:go_default_library",
|
|
||||||
"//vendor/github.com/coreos/go-oidc/key:go_default_library",
|
|
||||||
"//vendor/github.com/coreos/go-oidc/oauth2:go_default_library",
|
|
||||||
"//vendor/github.com/coreos/pkg/timeutil:go_default_library",
|
|
||||||
"//vendor/github.com/jonboulle/clockwork:go_default_library",
|
|
||||||
],
|
|
||||||
)
|
|
||||||
|
|
||||||
filegroup(
|
|
||||||
name = "package-srcs",
|
|
||||||
srcs = glob(["**"]),
|
|
||||||
tags = ["automanaged"],
|
|
||||||
visibility = ["//visibility:private"],
|
|
||||||
)
|
|
||||||
|
|
||||||
filegroup(
|
|
||||||
name = "all-srcs",
|
|
||||||
srcs = [":package-srcs"],
|
|
||||||
tags = ["automanaged"],
|
|
||||||
visibility = ["//visibility:public"],
|
|
||||||
)
|
|
846
vendor/github.com/coreos/go-oidc/oidc/client.go
generated
vendored
846
vendor/github.com/coreos/go-oidc/oidc/client.go
generated
vendored
@ -1,846 +0,0 @@
|
|||||||
package oidc
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"net/http"
|
|
||||||
"net/mail"
|
|
||||||
"net/url"
|
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
phttp "github.com/coreos/go-oidc/http"
|
|
||||||
"github.com/coreos/go-oidc/jose"
|
|
||||||
"github.com/coreos/go-oidc/key"
|
|
||||||
"github.com/coreos/go-oidc/oauth2"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
// amount of time that must pass after the last key sync
|
|
||||||
// completes before another attempt may begin
|
|
||||||
keySyncWindow = 5 * time.Second
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
DefaultScope = []string{"openid", "email", "profile"}
|
|
||||||
|
|
||||||
supportedAuthMethods = map[string]struct{}{
|
|
||||||
oauth2.AuthMethodClientSecretBasic: struct{}{},
|
|
||||||
oauth2.AuthMethodClientSecretPost: struct{}{},
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
type ClientCredentials oauth2.ClientCredentials
|
|
||||||
|
|
||||||
type ClientIdentity struct {
|
|
||||||
Credentials ClientCredentials
|
|
||||||
Metadata ClientMetadata
|
|
||||||
}
|
|
||||||
|
|
||||||
type JWAOptions struct {
|
|
||||||
// SigningAlg specifies an JWA alg for signing JWTs.
|
|
||||||
//
|
|
||||||
// Specifying this field implies different actions depending on the context. It may
|
|
||||||
// require objects be serialized and signed as a JWT instead of plain JSON, or
|
|
||||||
// require an existing JWT object use the specified alg.
|
|
||||||
//
|
|
||||||
// See: http://openid.net/specs/openid-connect-registration-1_0.html#ClientMetadata
|
|
||||||
SigningAlg string
|
|
||||||
// EncryptionAlg, if provided, specifies that the returned or sent object be stored
|
|
||||||
// (or nested) within a JWT object and encrypted with the provided JWA alg.
|
|
||||||
EncryptionAlg string
|
|
||||||
// EncryptionEnc specifies the JWA enc algorithm to use with EncryptionAlg. If
|
|
||||||
// EncryptionAlg is provided and EncryptionEnc is omitted, this field defaults
|
|
||||||
// to A128CBC-HS256.
|
|
||||||
//
|
|
||||||
// If EncryptionEnc is provided EncryptionAlg must also be specified.
|
|
||||||
EncryptionEnc string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (opt JWAOptions) valid() error {
|
|
||||||
if opt.EncryptionEnc != "" && opt.EncryptionAlg == "" {
|
|
||||||
return errors.New("encryption encoding provided with no encryption algorithm")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (opt JWAOptions) defaults() JWAOptions {
|
|
||||||
if opt.EncryptionAlg != "" && opt.EncryptionEnc == "" {
|
|
||||||
opt.EncryptionEnc = jose.EncA128CBCHS256
|
|
||||||
}
|
|
||||||
return opt
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
// Ensure ClientMetadata satisfies these interfaces.
|
|
||||||
_ json.Marshaler = &ClientMetadata{}
|
|
||||||
_ json.Unmarshaler = &ClientMetadata{}
|
|
||||||
)
|
|
||||||
|
|
||||||
// ClientMetadata holds metadata that the authorization server associates
|
|
||||||
// with a client identifier. The fields range from human-facing display
|
|
||||||
// strings such as client name, to items that impact the security of the
|
|
||||||
// protocol, such as the list of valid redirect URIs.
|
|
||||||
//
|
|
||||||
// See http://openid.net/specs/openid-connect-registration-1_0.html#ClientMetadata
|
|
||||||
//
|
|
||||||
// TODO: support language specific claim representations
|
|
||||||
// http://openid.net/specs/openid-connect-registration-1_0.html#LanguagesAndScripts
|
|
||||||
type ClientMetadata struct {
|
|
||||||
RedirectURIs []url.URL // Required
|
|
||||||
|
|
||||||
// A list of OAuth 2.0 "response_type" values that the client wishes to restrict
|
|
||||||
// itself to. Either "code", "token", or another registered extension.
|
|
||||||
//
|
|
||||||
// If omitted, only "code" will be used.
|
|
||||||
ResponseTypes []string
|
|
||||||
// A list of OAuth 2.0 grant types the client wishes to restrict itself to.
|
|
||||||
// The grant type values used by OIDC are "authorization_code", "implicit",
|
|
||||||
// and "refresh_token".
|
|
||||||
//
|
|
||||||
// If ommitted, only "authorization_code" will be used.
|
|
||||||
GrantTypes []string
|
|
||||||
// "native" or "web". If omitted, "web".
|
|
||||||
ApplicationType string
|
|
||||||
|
|
||||||
// List of email addresses.
|
|
||||||
Contacts []mail.Address
|
|
||||||
// Name of client to be presented to the end-user.
|
|
||||||
ClientName string
|
|
||||||
// URL that references a logo for the Client application.
|
|
||||||
LogoURI *url.URL
|
|
||||||
// URL of the home page of the Client.
|
|
||||||
ClientURI *url.URL
|
|
||||||
// Profile data policies and terms of use to be provided to the end user.
|
|
||||||
PolicyURI *url.URL
|
|
||||||
TermsOfServiceURI *url.URL
|
|
||||||
|
|
||||||
// URL to or the value of the client's JSON Web Key Set document.
|
|
||||||
JWKSURI *url.URL
|
|
||||||
JWKS *jose.JWKSet
|
|
||||||
|
|
||||||
// URL referencing a flie with a single JSON array of redirect URIs.
|
|
||||||
SectorIdentifierURI *url.URL
|
|
||||||
|
|
||||||
SubjectType string
|
|
||||||
|
|
||||||
// Options to restrict the JWS alg and enc values used for server responses and requests.
|
|
||||||
IDTokenResponseOptions JWAOptions
|
|
||||||
UserInfoResponseOptions JWAOptions
|
|
||||||
RequestObjectOptions JWAOptions
|
|
||||||
|
|
||||||
// Client requested authorization method and signing options for the token endpoint.
|
|
||||||
//
|
|
||||||
// Defaults to "client_secret_basic"
|
|
||||||
TokenEndpointAuthMethod string
|
|
||||||
TokenEndpointAuthSigningAlg string
|
|
||||||
|
|
||||||
// DefaultMaxAge specifies the maximum amount of time in seconds before an authorized
|
|
||||||
// user must reauthroize.
|
|
||||||
//
|
|
||||||
// If 0, no limitation is placed on the maximum.
|
|
||||||
DefaultMaxAge int64
|
|
||||||
// RequireAuthTime specifies if the auth_time claim in the ID token is required.
|
|
||||||
RequireAuthTime bool
|
|
||||||
|
|
||||||
// Default Authentication Context Class Reference values for authentication requests.
|
|
||||||
DefaultACRValues []string
|
|
||||||
|
|
||||||
// URI that a third party can use to initiate a login by the relaying party.
|
|
||||||
//
|
|
||||||
// See: http://openid.net/specs/openid-connect-core-1_0.html#ThirdPartyInitiatedLogin
|
|
||||||
InitiateLoginURI *url.URL
|
|
||||||
// Pre-registered request_uri values that may be cached by the server.
|
|
||||||
RequestURIs []url.URL
|
|
||||||
}
|
|
||||||
|
|
||||||
// Defaults returns a shallow copy of ClientMetadata with default
|
|
||||||
// values replacing omitted fields.
|
|
||||||
func (m ClientMetadata) Defaults() ClientMetadata {
|
|
||||||
if len(m.ResponseTypes) == 0 {
|
|
||||||
m.ResponseTypes = []string{oauth2.ResponseTypeCode}
|
|
||||||
}
|
|
||||||
if len(m.GrantTypes) == 0 {
|
|
||||||
m.GrantTypes = []string{oauth2.GrantTypeAuthCode}
|
|
||||||
}
|
|
||||||
if m.ApplicationType == "" {
|
|
||||||
m.ApplicationType = "web"
|
|
||||||
}
|
|
||||||
if m.TokenEndpointAuthMethod == "" {
|
|
||||||
m.TokenEndpointAuthMethod = oauth2.AuthMethodClientSecretBasic
|
|
||||||
}
|
|
||||||
m.IDTokenResponseOptions = m.IDTokenResponseOptions.defaults()
|
|
||||||
m.UserInfoResponseOptions = m.UserInfoResponseOptions.defaults()
|
|
||||||
m.RequestObjectOptions = m.RequestObjectOptions.defaults()
|
|
||||||
return m
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *ClientMetadata) MarshalJSON() ([]byte, error) {
|
|
||||||
e := m.toEncodableStruct()
|
|
||||||
return json.Marshal(&e)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *ClientMetadata) UnmarshalJSON(data []byte) error {
|
|
||||||
var e encodableClientMetadata
|
|
||||||
if err := json.Unmarshal(data, &e); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
meta, err := e.toStruct()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := meta.Valid(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
*m = meta
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type encodableClientMetadata struct {
|
|
||||||
RedirectURIs []string `json:"redirect_uris"` // Required
|
|
||||||
ResponseTypes []string `json:"response_types,omitempty"`
|
|
||||||
GrantTypes []string `json:"grant_types,omitempty"`
|
|
||||||
ApplicationType string `json:"application_type,omitempty"`
|
|
||||||
Contacts []string `json:"contacts,omitempty"`
|
|
||||||
ClientName string `json:"client_name,omitempty"`
|
|
||||||
LogoURI string `json:"logo_uri,omitempty"`
|
|
||||||
ClientURI string `json:"client_uri,omitempty"`
|
|
||||||
PolicyURI string `json:"policy_uri,omitempty"`
|
|
||||||
TermsOfServiceURI string `json:"tos_uri,omitempty"`
|
|
||||||
JWKSURI string `json:"jwks_uri,omitempty"`
|
|
||||||
JWKS *jose.JWKSet `json:"jwks,omitempty"`
|
|
||||||
SectorIdentifierURI string `json:"sector_identifier_uri,omitempty"`
|
|
||||||
SubjectType string `json:"subject_type,omitempty"`
|
|
||||||
IDTokenSignedResponseAlg string `json:"id_token_signed_response_alg,omitempty"`
|
|
||||||
IDTokenEncryptedResponseAlg string `json:"id_token_encrypted_response_alg,omitempty"`
|
|
||||||
IDTokenEncryptedResponseEnc string `json:"id_token_encrypted_response_enc,omitempty"`
|
|
||||||
UserInfoSignedResponseAlg string `json:"userinfo_signed_response_alg,omitempty"`
|
|
||||||
UserInfoEncryptedResponseAlg string `json:"userinfo_encrypted_response_alg,omitempty"`
|
|
||||||
UserInfoEncryptedResponseEnc string `json:"userinfo_encrypted_response_enc,omitempty"`
|
|
||||||
RequestObjectSigningAlg string `json:"request_object_signing_alg,omitempty"`
|
|
||||||
RequestObjectEncryptionAlg string `json:"request_object_encryption_alg,omitempty"`
|
|
||||||
RequestObjectEncryptionEnc string `json:"request_object_encryption_enc,omitempty"`
|
|
||||||
TokenEndpointAuthMethod string `json:"token_endpoint_auth_method,omitempty"`
|
|
||||||
TokenEndpointAuthSigningAlg string `json:"token_endpoint_auth_signing_alg,omitempty"`
|
|
||||||
DefaultMaxAge int64 `json:"default_max_age,omitempty"`
|
|
||||||
RequireAuthTime bool `json:"require_auth_time,omitempty"`
|
|
||||||
DefaultACRValues []string `json:"default_acr_values,omitempty"`
|
|
||||||
InitiateLoginURI string `json:"initiate_login_uri,omitempty"`
|
|
||||||
RequestURIs []string `json:"request_uris,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *encodableClientMetadata) toStruct() (ClientMetadata, error) {
|
|
||||||
p := stickyErrParser{}
|
|
||||||
m := ClientMetadata{
|
|
||||||
RedirectURIs: p.parseURIs(c.RedirectURIs, "redirect_uris"),
|
|
||||||
ResponseTypes: c.ResponseTypes,
|
|
||||||
GrantTypes: c.GrantTypes,
|
|
||||||
ApplicationType: c.ApplicationType,
|
|
||||||
Contacts: p.parseEmails(c.Contacts, "contacts"),
|
|
||||||
ClientName: c.ClientName,
|
|
||||||
LogoURI: p.parseURI(c.LogoURI, "logo_uri"),
|
|
||||||
ClientURI: p.parseURI(c.ClientURI, "client_uri"),
|
|
||||||
PolicyURI: p.parseURI(c.PolicyURI, "policy_uri"),
|
|
||||||
TermsOfServiceURI: p.parseURI(c.TermsOfServiceURI, "tos_uri"),
|
|
||||||
JWKSURI: p.parseURI(c.JWKSURI, "jwks_uri"),
|
|
||||||
JWKS: c.JWKS,
|
|
||||||
SectorIdentifierURI: p.parseURI(c.SectorIdentifierURI, "sector_identifier_uri"),
|
|
||||||
SubjectType: c.SubjectType,
|
|
||||||
TokenEndpointAuthMethod: c.TokenEndpointAuthMethod,
|
|
||||||
TokenEndpointAuthSigningAlg: c.TokenEndpointAuthSigningAlg,
|
|
||||||
DefaultMaxAge: c.DefaultMaxAge,
|
|
||||||
RequireAuthTime: c.RequireAuthTime,
|
|
||||||
DefaultACRValues: c.DefaultACRValues,
|
|
||||||
InitiateLoginURI: p.parseURI(c.InitiateLoginURI, "initiate_login_uri"),
|
|
||||||
RequestURIs: p.parseURIs(c.RequestURIs, "request_uris"),
|
|
||||||
IDTokenResponseOptions: JWAOptions{
|
|
||||||
c.IDTokenSignedResponseAlg,
|
|
||||||
c.IDTokenEncryptedResponseAlg,
|
|
||||||
c.IDTokenEncryptedResponseEnc,
|
|
||||||
},
|
|
||||||
UserInfoResponseOptions: JWAOptions{
|
|
||||||
c.UserInfoSignedResponseAlg,
|
|
||||||
c.UserInfoEncryptedResponseAlg,
|
|
||||||
c.UserInfoEncryptedResponseEnc,
|
|
||||||
},
|
|
||||||
RequestObjectOptions: JWAOptions{
|
|
||||||
c.RequestObjectSigningAlg,
|
|
||||||
c.RequestObjectEncryptionAlg,
|
|
||||||
c.RequestObjectEncryptionEnc,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
if p.firstErr != nil {
|
|
||||||
return ClientMetadata{}, p.firstErr
|
|
||||||
}
|
|
||||||
return m, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// stickyErrParser parses URIs and email addresses. Once it encounters
|
|
||||||
// a parse error, subsequent calls become no-op.
|
|
||||||
type stickyErrParser struct {
|
|
||||||
firstErr error
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *stickyErrParser) parseURI(s, field string) *url.URL {
|
|
||||||
if p.firstErr != nil || s == "" {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
u, err := url.Parse(s)
|
|
||||||
if err == nil {
|
|
||||||
if u.Host == "" {
|
|
||||||
err = errors.New("no host in URI")
|
|
||||||
} else if u.Scheme != "http" && u.Scheme != "https" {
|
|
||||||
err = errors.New("invalid URI scheme")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
p.firstErr = fmt.Errorf("failed to parse %s: %v", field, err)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return u
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *stickyErrParser) parseURIs(s []string, field string) []url.URL {
|
|
||||||
if p.firstErr != nil || len(s) == 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
uris := make([]url.URL, len(s))
|
|
||||||
for i, val := range s {
|
|
||||||
if val == "" {
|
|
||||||
p.firstErr = fmt.Errorf("invalid URI in field %s", field)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if u := p.parseURI(val, field); u != nil {
|
|
||||||
uris[i] = *u
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return uris
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *stickyErrParser) parseEmails(s []string, field string) []mail.Address {
|
|
||||||
if p.firstErr != nil || len(s) == 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
addrs := make([]mail.Address, len(s))
|
|
||||||
for i, addr := range s {
|
|
||||||
if addr == "" {
|
|
||||||
p.firstErr = fmt.Errorf("invalid email in field %s", field)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
a, err := mail.ParseAddress(addr)
|
|
||||||
if err != nil {
|
|
||||||
p.firstErr = fmt.Errorf("invalid email in field %s: %v", field, err)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
addrs[i] = *a
|
|
||||||
}
|
|
||||||
return addrs
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *ClientMetadata) toEncodableStruct() encodableClientMetadata {
|
|
||||||
return encodableClientMetadata{
|
|
||||||
RedirectURIs: urisToStrings(m.RedirectURIs),
|
|
||||||
ResponseTypes: m.ResponseTypes,
|
|
||||||
GrantTypes: m.GrantTypes,
|
|
||||||
ApplicationType: m.ApplicationType,
|
|
||||||
Contacts: emailsToStrings(m.Contacts),
|
|
||||||
ClientName: m.ClientName,
|
|
||||||
LogoURI: uriToString(m.LogoURI),
|
|
||||||
ClientURI: uriToString(m.ClientURI),
|
|
||||||
PolicyURI: uriToString(m.PolicyURI),
|
|
||||||
TermsOfServiceURI: uriToString(m.TermsOfServiceURI),
|
|
||||||
JWKSURI: uriToString(m.JWKSURI),
|
|
||||||
JWKS: m.JWKS,
|
|
||||||
SectorIdentifierURI: uriToString(m.SectorIdentifierURI),
|
|
||||||
SubjectType: m.SubjectType,
|
|
||||||
IDTokenSignedResponseAlg: m.IDTokenResponseOptions.SigningAlg,
|
|
||||||
IDTokenEncryptedResponseAlg: m.IDTokenResponseOptions.EncryptionAlg,
|
|
||||||
IDTokenEncryptedResponseEnc: m.IDTokenResponseOptions.EncryptionEnc,
|
|
||||||
UserInfoSignedResponseAlg: m.UserInfoResponseOptions.SigningAlg,
|
|
||||||
UserInfoEncryptedResponseAlg: m.UserInfoResponseOptions.EncryptionAlg,
|
|
||||||
UserInfoEncryptedResponseEnc: m.UserInfoResponseOptions.EncryptionEnc,
|
|
||||||
RequestObjectSigningAlg: m.RequestObjectOptions.SigningAlg,
|
|
||||||
RequestObjectEncryptionAlg: m.RequestObjectOptions.EncryptionAlg,
|
|
||||||
RequestObjectEncryptionEnc: m.RequestObjectOptions.EncryptionEnc,
|
|
||||||
TokenEndpointAuthMethod: m.TokenEndpointAuthMethod,
|
|
||||||
TokenEndpointAuthSigningAlg: m.TokenEndpointAuthSigningAlg,
|
|
||||||
DefaultMaxAge: m.DefaultMaxAge,
|
|
||||||
RequireAuthTime: m.RequireAuthTime,
|
|
||||||
DefaultACRValues: m.DefaultACRValues,
|
|
||||||
InitiateLoginURI: uriToString(m.InitiateLoginURI),
|
|
||||||
RequestURIs: urisToStrings(m.RequestURIs),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func uriToString(u *url.URL) string {
|
|
||||||
if u == nil {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
return u.String()
|
|
||||||
}
|
|
||||||
|
|
||||||
func urisToStrings(urls []url.URL) []string {
|
|
||||||
if len(urls) == 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
sli := make([]string, len(urls))
|
|
||||||
for i, u := range urls {
|
|
||||||
sli[i] = u.String()
|
|
||||||
}
|
|
||||||
return sli
|
|
||||||
}
|
|
||||||
|
|
||||||
func emailsToStrings(addrs []mail.Address) []string {
|
|
||||||
if len(addrs) == 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
sli := make([]string, len(addrs))
|
|
||||||
for i, addr := range addrs {
|
|
||||||
sli[i] = addr.String()
|
|
||||||
}
|
|
||||||
return sli
|
|
||||||
}
|
|
||||||
|
|
||||||
// Valid determines if a ClientMetadata conforms with the OIDC specification.
|
|
||||||
//
|
|
||||||
// Valid is called by UnmarshalJSON.
|
|
||||||
//
|
|
||||||
// NOTE(ericchiang): For development purposes Valid does not mandate 'https' for
|
|
||||||
// URLs fields where the OIDC spec requires it. This may change in future releases
|
|
||||||
// of this package. See: https://github.com/coreos/go-oidc/issues/34
|
|
||||||
func (m *ClientMetadata) Valid() error {
|
|
||||||
if len(m.RedirectURIs) == 0 {
|
|
||||||
return errors.New("zero redirect URLs")
|
|
||||||
}
|
|
||||||
|
|
||||||
validURI := func(u *url.URL, fieldName string) error {
|
|
||||||
if u.Host == "" {
|
|
||||||
return fmt.Errorf("no host for uri field %s", fieldName)
|
|
||||||
}
|
|
||||||
if u.Scheme != "http" && u.Scheme != "https" {
|
|
||||||
return fmt.Errorf("uri field %s scheme is not http or https", fieldName)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
uris := []struct {
|
|
||||||
val *url.URL
|
|
||||||
name string
|
|
||||||
}{
|
|
||||||
{m.LogoURI, "logo_uri"},
|
|
||||||
{m.ClientURI, "client_uri"},
|
|
||||||
{m.PolicyURI, "policy_uri"},
|
|
||||||
{m.TermsOfServiceURI, "tos_uri"},
|
|
||||||
{m.JWKSURI, "jwks_uri"},
|
|
||||||
{m.SectorIdentifierURI, "sector_identifier_uri"},
|
|
||||||
{m.InitiateLoginURI, "initiate_login_uri"},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, uri := range uris {
|
|
||||||
if uri.val == nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if err := validURI(uri.val, uri.name); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
uriLists := []struct {
|
|
||||||
vals []url.URL
|
|
||||||
name string
|
|
||||||
}{
|
|
||||||
{m.RedirectURIs, "redirect_uris"},
|
|
||||||
{m.RequestURIs, "request_uris"},
|
|
||||||
}
|
|
||||||
for _, list := range uriLists {
|
|
||||||
for _, uri := range list.vals {
|
|
||||||
if err := validURI(&uri, list.name); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
options := []struct {
|
|
||||||
option JWAOptions
|
|
||||||
name string
|
|
||||||
}{
|
|
||||||
{m.IDTokenResponseOptions, "id_token response"},
|
|
||||||
{m.UserInfoResponseOptions, "userinfo response"},
|
|
||||||
{m.RequestObjectOptions, "request_object"},
|
|
||||||
}
|
|
||||||
for _, option := range options {
|
|
||||||
if err := option.option.valid(); err != nil {
|
|
||||||
return fmt.Errorf("invalid JWA values for %s: %v", option.name, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type ClientRegistrationResponse struct {
|
|
||||||
ClientID string // Required
|
|
||||||
ClientSecret string
|
|
||||||
RegistrationAccessToken string
|
|
||||||
RegistrationClientURI string
|
|
||||||
// If IsZero is true, unspecified.
|
|
||||||
ClientIDIssuedAt time.Time
|
|
||||||
// Time at which the client_secret will expire.
|
|
||||||
// If IsZero is true, it will not expire.
|
|
||||||
ClientSecretExpiresAt time.Time
|
|
||||||
|
|
||||||
ClientMetadata
|
|
||||||
}
|
|
||||||
|
|
||||||
type encodableClientRegistrationResponse struct {
|
|
||||||
ClientID string `json:"client_id"` // Required
|
|
||||||
ClientSecret string `json:"client_secret,omitempty"`
|
|
||||||
RegistrationAccessToken string `json:"registration_access_token,omitempty"`
|
|
||||||
RegistrationClientURI string `json:"registration_client_uri,omitempty"`
|
|
||||||
ClientIDIssuedAt int64 `json:"client_id_issued_at,omitempty"`
|
|
||||||
// Time at which the client_secret will expire, in seconds since the epoch.
|
|
||||||
// If 0 it will not expire.
|
|
||||||
ClientSecretExpiresAt int64 `json:"client_secret_expires_at"` // Required
|
|
||||||
|
|
||||||
encodableClientMetadata
|
|
||||||
}
|
|
||||||
|
|
||||||
func unixToSec(t time.Time) int64 {
|
|
||||||
if t.IsZero() {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
return t.Unix()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ClientRegistrationResponse) MarshalJSON() ([]byte, error) {
|
|
||||||
e := encodableClientRegistrationResponse{
|
|
||||||
ClientID: c.ClientID,
|
|
||||||
ClientSecret: c.ClientSecret,
|
|
||||||
RegistrationAccessToken: c.RegistrationAccessToken,
|
|
||||||
RegistrationClientURI: c.RegistrationClientURI,
|
|
||||||
ClientIDIssuedAt: unixToSec(c.ClientIDIssuedAt),
|
|
||||||
ClientSecretExpiresAt: unixToSec(c.ClientSecretExpiresAt),
|
|
||||||
encodableClientMetadata: c.ClientMetadata.toEncodableStruct(),
|
|
||||||
}
|
|
||||||
return json.Marshal(&e)
|
|
||||||
}
|
|
||||||
|
|
||||||
func secToUnix(sec int64) time.Time {
|
|
||||||
if sec == 0 {
|
|
||||||
return time.Time{}
|
|
||||||
}
|
|
||||||
return time.Unix(sec, 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ClientRegistrationResponse) UnmarshalJSON(data []byte) error {
|
|
||||||
var e encodableClientRegistrationResponse
|
|
||||||
if err := json.Unmarshal(data, &e); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if e.ClientID == "" {
|
|
||||||
return errors.New("no client_id in client registration response")
|
|
||||||
}
|
|
||||||
metadata, err := e.encodableClientMetadata.toStruct()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
*c = ClientRegistrationResponse{
|
|
||||||
ClientID: e.ClientID,
|
|
||||||
ClientSecret: e.ClientSecret,
|
|
||||||
RegistrationAccessToken: e.RegistrationAccessToken,
|
|
||||||
RegistrationClientURI: e.RegistrationClientURI,
|
|
||||||
ClientIDIssuedAt: secToUnix(e.ClientIDIssuedAt),
|
|
||||||
ClientSecretExpiresAt: secToUnix(e.ClientSecretExpiresAt),
|
|
||||||
ClientMetadata: metadata,
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type ClientConfig struct {
|
|
||||||
HTTPClient phttp.Client
|
|
||||||
Credentials ClientCredentials
|
|
||||||
Scope []string
|
|
||||||
RedirectURL string
|
|
||||||
ProviderConfig ProviderConfig
|
|
||||||
KeySet key.PublicKeySet
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewClient(cfg ClientConfig) (*Client, error) {
|
|
||||||
// Allow empty redirect URL in the case where the client
|
|
||||||
// only needs to verify a given token.
|
|
||||||
ru, err := url.Parse(cfg.RedirectURL)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("invalid redirect URL: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
c := Client{
|
|
||||||
credentials: cfg.Credentials,
|
|
||||||
httpClient: cfg.HTTPClient,
|
|
||||||
scope: cfg.Scope,
|
|
||||||
redirectURL: ru.String(),
|
|
||||||
providerConfig: newProviderConfigRepo(cfg.ProviderConfig),
|
|
||||||
keySet: cfg.KeySet,
|
|
||||||
}
|
|
||||||
|
|
||||||
if c.httpClient == nil {
|
|
||||||
c.httpClient = http.DefaultClient
|
|
||||||
}
|
|
||||||
|
|
||||||
if c.scope == nil {
|
|
||||||
c.scope = make([]string, len(DefaultScope))
|
|
||||||
copy(c.scope, DefaultScope)
|
|
||||||
}
|
|
||||||
|
|
||||||
return &c, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type Client struct {
|
|
||||||
httpClient phttp.Client
|
|
||||||
providerConfig *providerConfigRepo
|
|
||||||
credentials ClientCredentials
|
|
||||||
redirectURL string
|
|
||||||
scope []string
|
|
||||||
keySet key.PublicKeySet
|
|
||||||
providerSyncer *ProviderConfigSyncer
|
|
||||||
|
|
||||||
keySetSyncMutex sync.RWMutex
|
|
||||||
lastKeySetSync time.Time
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Client) Healthy() error {
|
|
||||||
now := time.Now().UTC()
|
|
||||||
|
|
||||||
cfg := c.providerConfig.Get()
|
|
||||||
|
|
||||||
if cfg.Empty() {
|
|
||||||
return errors.New("oidc client provider config empty")
|
|
||||||
}
|
|
||||||
|
|
||||||
if !cfg.ExpiresAt.IsZero() && cfg.ExpiresAt.Before(now) {
|
|
||||||
return errors.New("oidc client provider config expired")
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Client) OAuthClient() (*oauth2.Client, error) {
|
|
||||||
cfg := c.providerConfig.Get()
|
|
||||||
authMethod, err := chooseAuthMethod(cfg)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
ocfg := oauth2.Config{
|
|
||||||
Credentials: oauth2.ClientCredentials(c.credentials),
|
|
||||||
RedirectURL: c.redirectURL,
|
|
||||||
AuthURL: cfg.AuthEndpoint.String(),
|
|
||||||
TokenURL: cfg.TokenEndpoint.String(),
|
|
||||||
Scope: c.scope,
|
|
||||||
AuthMethod: authMethod,
|
|
||||||
}
|
|
||||||
|
|
||||||
return oauth2.NewClient(c.httpClient, ocfg)
|
|
||||||
}
|
|
||||||
|
|
||||||
func chooseAuthMethod(cfg ProviderConfig) (string, error) {
|
|
||||||
if len(cfg.TokenEndpointAuthMethodsSupported) == 0 {
|
|
||||||
return oauth2.AuthMethodClientSecretBasic, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, authMethod := range cfg.TokenEndpointAuthMethodsSupported {
|
|
||||||
if _, ok := supportedAuthMethods[authMethod]; ok {
|
|
||||||
return authMethod, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return "", errors.New("no supported auth methods")
|
|
||||||
}
|
|
||||||
|
|
||||||
// SyncProviderConfig starts the provider config syncer
|
|
||||||
func (c *Client) SyncProviderConfig(discoveryURL string) chan struct{} {
|
|
||||||
r := NewHTTPProviderConfigGetter(c.httpClient, discoveryURL)
|
|
||||||
s := NewProviderConfigSyncer(r, c.providerConfig)
|
|
||||||
stop := s.Run()
|
|
||||||
s.WaitUntilInitialSync()
|
|
||||||
return stop
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Client) maybeSyncKeys() error {
|
|
||||||
tooSoon := func() bool {
|
|
||||||
return time.Now().UTC().Before(c.lastKeySetSync.Add(keySyncWindow))
|
|
||||||
}
|
|
||||||
|
|
||||||
// ignore request to sync keys if a sync operation has been
|
|
||||||
// attempted too recently
|
|
||||||
if tooSoon() {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
c.keySetSyncMutex.Lock()
|
|
||||||
defer c.keySetSyncMutex.Unlock()
|
|
||||||
|
|
||||||
// check again, as another goroutine may have been holding
|
|
||||||
// the lock while updating the keys
|
|
||||||
if tooSoon() {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
cfg := c.providerConfig.Get()
|
|
||||||
r := NewRemotePublicKeyRepo(c.httpClient, cfg.KeysEndpoint.String())
|
|
||||||
w := &clientKeyRepo{client: c}
|
|
||||||
_, err := key.Sync(r, w)
|
|
||||||
c.lastKeySetSync = time.Now().UTC()
|
|
||||||
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
type clientKeyRepo struct {
|
|
||||||
client *Client
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *clientKeyRepo) Set(ks key.KeySet) error {
|
|
||||||
pks, ok := ks.(*key.PublicKeySet)
|
|
||||||
if !ok {
|
|
||||||
return errors.New("unable to cast to PublicKey")
|
|
||||||
}
|
|
||||||
r.client.keySet = *pks
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Client) ClientCredsToken(scope []string) (jose.JWT, error) {
|
|
||||||
cfg := c.providerConfig.Get()
|
|
||||||
|
|
||||||
if !cfg.SupportsGrantType(oauth2.GrantTypeClientCreds) {
|
|
||||||
return jose.JWT{}, fmt.Errorf("%v grant type is not supported", oauth2.GrantTypeClientCreds)
|
|
||||||
}
|
|
||||||
|
|
||||||
oac, err := c.OAuthClient()
|
|
||||||
if err != nil {
|
|
||||||
return jose.JWT{}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
t, err := oac.ClientCredsToken(scope)
|
|
||||||
if err != nil {
|
|
||||||
return jose.JWT{}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
jwt, err := jose.ParseJWT(t.IDToken)
|
|
||||||
if err != nil {
|
|
||||||
return jose.JWT{}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return jwt, c.VerifyJWT(jwt)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ExchangeAuthCode exchanges an OAuth2 auth code for an OIDC JWT ID token.
|
|
||||||
func (c *Client) ExchangeAuthCode(code string) (jose.JWT, error) {
|
|
||||||
oac, err := c.OAuthClient()
|
|
||||||
if err != nil {
|
|
||||||
return jose.JWT{}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
t, err := oac.RequestToken(oauth2.GrantTypeAuthCode, code)
|
|
||||||
if err != nil {
|
|
||||||
return jose.JWT{}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
jwt, err := jose.ParseJWT(t.IDToken)
|
|
||||||
if err != nil {
|
|
||||||
return jose.JWT{}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return jwt, c.VerifyJWT(jwt)
|
|
||||||
}
|
|
||||||
|
|
||||||
// RefreshToken uses a refresh token to exchange for a new OIDC JWT ID Token.
|
|
||||||
func (c *Client) RefreshToken(refreshToken string) (jose.JWT, error) {
|
|
||||||
oac, err := c.OAuthClient()
|
|
||||||
if err != nil {
|
|
||||||
return jose.JWT{}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
t, err := oac.RequestToken(oauth2.GrantTypeRefreshToken, refreshToken)
|
|
||||||
if err != nil {
|
|
||||||
return jose.JWT{}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
jwt, err := jose.ParseJWT(t.IDToken)
|
|
||||||
if err != nil {
|
|
||||||
return jose.JWT{}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return jwt, c.VerifyJWT(jwt)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Client) VerifyJWT(jwt jose.JWT) error {
|
|
||||||
var keysFunc func() []key.PublicKey
|
|
||||||
if kID, ok := jwt.KeyID(); ok {
|
|
||||||
keysFunc = c.keysFuncWithID(kID)
|
|
||||||
} else {
|
|
||||||
keysFunc = c.keysFuncAll()
|
|
||||||
}
|
|
||||||
|
|
||||||
v := NewJWTVerifier(
|
|
||||||
c.providerConfig.Get().Issuer.String(),
|
|
||||||
c.credentials.ID,
|
|
||||||
c.maybeSyncKeys, keysFunc)
|
|
||||||
|
|
||||||
return v.Verify(jwt)
|
|
||||||
}
|
|
||||||
|
|
||||||
// keysFuncWithID returns a function that retrieves at most unexpired
|
|
||||||
// public key from the Client that matches the provided ID
|
|
||||||
func (c *Client) keysFuncWithID(kID string) func() []key.PublicKey {
|
|
||||||
return func() []key.PublicKey {
|
|
||||||
c.keySetSyncMutex.RLock()
|
|
||||||
defer c.keySetSyncMutex.RUnlock()
|
|
||||||
|
|
||||||
if c.keySet.ExpiresAt().Before(time.Now()) {
|
|
||||||
return []key.PublicKey{}
|
|
||||||
}
|
|
||||||
|
|
||||||
k := c.keySet.Key(kID)
|
|
||||||
if k == nil {
|
|
||||||
return []key.PublicKey{}
|
|
||||||
}
|
|
||||||
|
|
||||||
return []key.PublicKey{*k}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// keysFuncAll returns a function that retrieves all unexpired public
|
|
||||||
// keys from the Client
|
|
||||||
func (c *Client) keysFuncAll() func() []key.PublicKey {
|
|
||||||
return func() []key.PublicKey {
|
|
||||||
c.keySetSyncMutex.RLock()
|
|
||||||
defer c.keySetSyncMutex.RUnlock()
|
|
||||||
|
|
||||||
if c.keySet.ExpiresAt().Before(time.Now()) {
|
|
||||||
return []key.PublicKey{}
|
|
||||||
}
|
|
||||||
|
|
||||||
return c.keySet.Keys()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type providerConfigRepo struct {
|
|
||||||
mu sync.RWMutex
|
|
||||||
config ProviderConfig // do not access directly, use Get()
|
|
||||||
}
|
|
||||||
|
|
||||||
func newProviderConfigRepo(pc ProviderConfig) *providerConfigRepo {
|
|
||||||
return &providerConfigRepo{sync.RWMutex{}, pc}
|
|
||||||
}
|
|
||||||
|
|
||||||
// returns an error to implement ProviderConfigSetter
|
|
||||||
func (r *providerConfigRepo) Set(cfg ProviderConfig) error {
|
|
||||||
r.mu.Lock()
|
|
||||||
defer r.mu.Unlock()
|
|
||||||
r.config = cfg
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *providerConfigRepo) Get() ProviderConfig {
|
|
||||||
r.mu.RLock()
|
|
||||||
defer r.mu.RUnlock()
|
|
||||||
return r.config
|
|
||||||
}
|
|
2
vendor/github.com/coreos/go-oidc/oidc/doc.go
generated
vendored
2
vendor/github.com/coreos/go-oidc/oidc/doc.go
generated
vendored
@ -1,2 +0,0 @@
|
|||||||
// Package oidc is DEPRECATED. Use github.com/coreos/go-oidc instead.
|
|
||||||
package oidc
|
|
44
vendor/github.com/coreos/go-oidc/oidc/identity.go
generated
vendored
44
vendor/github.com/coreos/go-oidc/oidc/identity.go
generated
vendored
@ -1,44 +0,0 @@
|
|||||||
package oidc
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/coreos/go-oidc/jose"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Identity struct {
|
|
||||||
ID string
|
|
||||||
Name string
|
|
||||||
Email string
|
|
||||||
ExpiresAt time.Time
|
|
||||||
}
|
|
||||||
|
|
||||||
func IdentityFromClaims(claims jose.Claims) (*Identity, error) {
|
|
||||||
if claims == nil {
|
|
||||||
return nil, errors.New("nil claim set")
|
|
||||||
}
|
|
||||||
|
|
||||||
var ident Identity
|
|
||||||
var err error
|
|
||||||
var ok bool
|
|
||||||
|
|
||||||
if ident.ID, ok, err = claims.StringClaim("sub"); err != nil {
|
|
||||||
return nil, err
|
|
||||||
} else if !ok {
|
|
||||||
return nil, errors.New("missing required claim: sub")
|
|
||||||
}
|
|
||||||
|
|
||||||
if ident.Email, _, err = claims.StringClaim("email"); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
exp, ok, err := claims.TimeClaim("exp")
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
} else if ok {
|
|
||||||
ident.ExpiresAt = exp
|
|
||||||
}
|
|
||||||
|
|
||||||
return &ident, nil
|
|
||||||
}
|
|
3
vendor/github.com/coreos/go-oidc/oidc/interface.go
generated
vendored
3
vendor/github.com/coreos/go-oidc/oidc/interface.go
generated
vendored
@ -1,3 +0,0 @@
|
|||||||
package oidc
|
|
||||||
|
|
||||||
type LoginFunc func(ident Identity, sessionKey string) (redirectURL string, err error)
|
|
67
vendor/github.com/coreos/go-oidc/oidc/key.go
generated
vendored
67
vendor/github.com/coreos/go-oidc/oidc/key.go
generated
vendored
@ -1,67 +0,0 @@
|
|||||||
package oidc
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"errors"
|
|
||||||
"net/http"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
phttp "github.com/coreos/go-oidc/http"
|
|
||||||
"github.com/coreos/go-oidc/jose"
|
|
||||||
"github.com/coreos/go-oidc/key"
|
|
||||||
)
|
|
||||||
|
|
||||||
// DefaultPublicKeySetTTL is the default TTL set on the PublicKeySet if no
|
|
||||||
// Cache-Control header is provided by the JWK Set document endpoint.
|
|
||||||
const DefaultPublicKeySetTTL = 24 * time.Hour
|
|
||||||
|
|
||||||
// NewRemotePublicKeyRepo is responsible for fetching the JWK Set document.
|
|
||||||
func NewRemotePublicKeyRepo(hc phttp.Client, ep string) *remotePublicKeyRepo {
|
|
||||||
return &remotePublicKeyRepo{hc: hc, ep: ep}
|
|
||||||
}
|
|
||||||
|
|
||||||
type remotePublicKeyRepo struct {
|
|
||||||
hc phttp.Client
|
|
||||||
ep string
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get returns a PublicKeySet fetched from the JWK Set document endpoint. A TTL
|
|
||||||
// is set on the Key Set to avoid it having to be re-retrieved for every
|
|
||||||
// encryption event. This TTL is typically controlled by the endpoint returning
|
|
||||||
// a Cache-Control header, but defaults to 24 hours if no Cache-Control header
|
|
||||||
// is found.
|
|
||||||
func (r *remotePublicKeyRepo) Get() (key.KeySet, error) {
|
|
||||||
req, err := http.NewRequest("GET", r.ep, nil)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
resp, err := r.hc.Do(req)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
var d struct {
|
|
||||||
Keys []jose.JWK `json:"keys"`
|
|
||||||
}
|
|
||||||
if err := json.NewDecoder(resp.Body).Decode(&d); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(d.Keys) == 0 {
|
|
||||||
return nil, errors.New("zero keys in response")
|
|
||||||
}
|
|
||||||
|
|
||||||
ttl, ok, err := phttp.Cacheable(resp.Header)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if !ok {
|
|
||||||
ttl = DefaultPublicKeySetTTL
|
|
||||||
}
|
|
||||||
|
|
||||||
exp := time.Now().UTC().Add(ttl)
|
|
||||||
ks := key.NewPublicKeySet(d.Keys, exp)
|
|
||||||
return ks, nil
|
|
||||||
}
|
|
687
vendor/github.com/coreos/go-oidc/oidc/provider.go
generated
vendored
687
vendor/github.com/coreos/go-oidc/oidc/provider.go
generated
vendored
@ -1,687 +0,0 @@
|
|||||||
package oidc
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"log"
|
|
||||||
"net/http"
|
|
||||||
"net/url"
|
|
||||||
"strings"
|
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/coreos/pkg/timeutil"
|
|
||||||
"github.com/jonboulle/clockwork"
|
|
||||||
|
|
||||||
phttp "github.com/coreos/go-oidc/http"
|
|
||||||
"github.com/coreos/go-oidc/oauth2"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
// Subject Identifier types defined by the OIDC spec. Specifies if the provider
|
|
||||||
// should provide the same sub claim value to all clients (public) or a unique
|
|
||||||
// value for each client (pairwise).
|
|
||||||
//
|
|
||||||
// See: http://openid.net/specs/openid-connect-core-1_0.html#SubjectIDTypes
|
|
||||||
SubjectTypePublic = "public"
|
|
||||||
SubjectTypePairwise = "pairwise"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
// Default values for omitted provider config fields.
|
|
||||||
//
|
|
||||||
// Use ProviderConfig's Defaults method to fill a provider config with these values.
|
|
||||||
DefaultGrantTypesSupported = []string{oauth2.GrantTypeAuthCode, oauth2.GrantTypeImplicit}
|
|
||||||
DefaultResponseModesSupported = []string{"query", "fragment"}
|
|
||||||
DefaultTokenEndpointAuthMethodsSupported = []string{oauth2.AuthMethodClientSecretBasic}
|
|
||||||
DefaultClaimTypesSupported = []string{"normal"}
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
MaximumProviderConfigSyncInterval = 24 * time.Hour
|
|
||||||
MinimumProviderConfigSyncInterval = time.Minute
|
|
||||||
|
|
||||||
discoveryConfigPath = "/.well-known/openid-configuration"
|
|
||||||
)
|
|
||||||
|
|
||||||
// internally configurable for tests
|
|
||||||
var minimumProviderConfigSyncInterval = MinimumProviderConfigSyncInterval
|
|
||||||
|
|
||||||
var (
|
|
||||||
// Ensure ProviderConfig satisfies these interfaces.
|
|
||||||
_ json.Marshaler = &ProviderConfig{}
|
|
||||||
_ json.Unmarshaler = &ProviderConfig{}
|
|
||||||
)
|
|
||||||
|
|
||||||
// ProviderConfig represents the OpenID Provider Metadata specifying what
|
|
||||||
// configurations a provider supports.
|
|
||||||
//
|
|
||||||
// See: http://openid.net/specs/openid-connect-discovery-1_0.html#ProviderMetadata
|
|
||||||
type ProviderConfig struct {
|
|
||||||
Issuer *url.URL // Required
|
|
||||||
AuthEndpoint *url.URL // Required
|
|
||||||
TokenEndpoint *url.URL // Required if grant types other than "implicit" are supported
|
|
||||||
UserInfoEndpoint *url.URL
|
|
||||||
KeysEndpoint *url.URL // Required
|
|
||||||
RegistrationEndpoint *url.URL
|
|
||||||
EndSessionEndpoint *url.URL
|
|
||||||
CheckSessionIFrame *url.URL
|
|
||||||
|
|
||||||
// Servers MAY choose not to advertise some supported scope values even when this
|
|
||||||
// parameter is used, although those defined in OpenID Core SHOULD be listed, if supported.
|
|
||||||
ScopesSupported []string
|
|
||||||
// OAuth2.0 response types supported.
|
|
||||||
ResponseTypesSupported []string // Required
|
|
||||||
// OAuth2.0 response modes supported.
|
|
||||||
//
|
|
||||||
// If omitted, defaults to DefaultResponseModesSupported.
|
|
||||||
ResponseModesSupported []string
|
|
||||||
// OAuth2.0 grant types supported.
|
|
||||||
//
|
|
||||||
// If omitted, defaults to DefaultGrantTypesSupported.
|
|
||||||
GrantTypesSupported []string
|
|
||||||
ACRValuesSupported []string
|
|
||||||
// SubjectTypesSupported specifies strategies for providing values for the sub claim.
|
|
||||||
SubjectTypesSupported []string // Required
|
|
||||||
|
|
||||||
// JWA signing and encryption algorith values supported for ID tokens.
|
|
||||||
IDTokenSigningAlgValues []string // Required
|
|
||||||
IDTokenEncryptionAlgValues []string
|
|
||||||
IDTokenEncryptionEncValues []string
|
|
||||||
|
|
||||||
// JWA signing and encryption algorith values supported for user info responses.
|
|
||||||
UserInfoSigningAlgValues []string
|
|
||||||
UserInfoEncryptionAlgValues []string
|
|
||||||
UserInfoEncryptionEncValues []string
|
|
||||||
|
|
||||||
// JWA signing and encryption algorith values supported for request objects.
|
|
||||||
ReqObjSigningAlgValues []string
|
|
||||||
ReqObjEncryptionAlgValues []string
|
|
||||||
ReqObjEncryptionEncValues []string
|
|
||||||
|
|
||||||
TokenEndpointAuthMethodsSupported []string
|
|
||||||
TokenEndpointAuthSigningAlgValuesSupported []string
|
|
||||||
DisplayValuesSupported []string
|
|
||||||
ClaimTypesSupported []string
|
|
||||||
ClaimsSupported []string
|
|
||||||
ServiceDocs *url.URL
|
|
||||||
ClaimsLocalsSupported []string
|
|
||||||
UILocalsSupported []string
|
|
||||||
ClaimsParameterSupported bool
|
|
||||||
RequestParameterSupported bool
|
|
||||||
RequestURIParamaterSupported bool
|
|
||||||
RequireRequestURIRegistration bool
|
|
||||||
|
|
||||||
Policy *url.URL
|
|
||||||
TermsOfService *url.URL
|
|
||||||
|
|
||||||
// Not part of the OpenID Provider Metadata
|
|
||||||
ExpiresAt time.Time
|
|
||||||
}
|
|
||||||
|
|
||||||
// Defaults returns a shallow copy of ProviderConfig with default
|
|
||||||
// values replacing omitted fields.
|
|
||||||
//
|
|
||||||
// var cfg oidc.ProviderConfig
|
|
||||||
// // Fill provider config with default values for omitted fields.
|
|
||||||
// cfg = cfg.Defaults()
|
|
||||||
//
|
|
||||||
func (p ProviderConfig) Defaults() ProviderConfig {
|
|
||||||
setDefault := func(val *[]string, defaultVal []string) {
|
|
||||||
if len(*val) == 0 {
|
|
||||||
*val = defaultVal
|
|
||||||
}
|
|
||||||
}
|
|
||||||
setDefault(&p.GrantTypesSupported, DefaultGrantTypesSupported)
|
|
||||||
setDefault(&p.ResponseModesSupported, DefaultResponseModesSupported)
|
|
||||||
setDefault(&p.TokenEndpointAuthMethodsSupported, DefaultTokenEndpointAuthMethodsSupported)
|
|
||||||
setDefault(&p.ClaimTypesSupported, DefaultClaimTypesSupported)
|
|
||||||
return p
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *ProviderConfig) MarshalJSON() ([]byte, error) {
|
|
||||||
e := p.toEncodableStruct()
|
|
||||||
return json.Marshal(&e)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *ProviderConfig) UnmarshalJSON(data []byte) error {
|
|
||||||
var e encodableProviderConfig
|
|
||||||
if err := json.Unmarshal(data, &e); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
conf, err := e.toStruct()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := conf.Valid(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
*p = conf
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type encodableProviderConfig struct {
|
|
||||||
Issuer string `json:"issuer"`
|
|
||||||
AuthEndpoint string `json:"authorization_endpoint"`
|
|
||||||
TokenEndpoint string `json:"token_endpoint"`
|
|
||||||
UserInfoEndpoint string `json:"userinfo_endpoint,omitempty"`
|
|
||||||
KeysEndpoint string `json:"jwks_uri"`
|
|
||||||
RegistrationEndpoint string `json:"registration_endpoint,omitempty"`
|
|
||||||
EndSessionEndpoint string `json:"end_session_endpoint,omitempty"`
|
|
||||||
CheckSessionIFrame string `json:"check_session_iframe,omitempty"`
|
|
||||||
|
|
||||||
// Use 'omitempty' for all slices as per OIDC spec:
|
|
||||||
// "Claims that return multiple values are represented as JSON arrays.
|
|
||||||
// Claims with zero elements MUST be omitted from the response."
|
|
||||||
// http://openid.net/specs/openid-connect-discovery-1_0.html#ProviderConfigurationResponse
|
|
||||||
|
|
||||||
ScopesSupported []string `json:"scopes_supported,omitempty"`
|
|
||||||
ResponseTypesSupported []string `json:"response_types_supported,omitempty"`
|
|
||||||
ResponseModesSupported []string `json:"response_modes_supported,omitempty"`
|
|
||||||
GrantTypesSupported []string `json:"grant_types_supported,omitempty"`
|
|
||||||
ACRValuesSupported []string `json:"acr_values_supported,omitempty"`
|
|
||||||
SubjectTypesSupported []string `json:"subject_types_supported,omitempty"`
|
|
||||||
|
|
||||||
IDTokenSigningAlgValues []string `json:"id_token_signing_alg_values_supported,omitempty"`
|
|
||||||
IDTokenEncryptionAlgValues []string `json:"id_token_encryption_alg_values_supported,omitempty"`
|
|
||||||
IDTokenEncryptionEncValues []string `json:"id_token_encryption_enc_values_supported,omitempty"`
|
|
||||||
UserInfoSigningAlgValues []string `json:"userinfo_signing_alg_values_supported,omitempty"`
|
|
||||||
UserInfoEncryptionAlgValues []string `json:"userinfo_encryption_alg_values_supported,omitempty"`
|
|
||||||
UserInfoEncryptionEncValues []string `json:"userinfo_encryption_enc_values_supported,omitempty"`
|
|
||||||
ReqObjSigningAlgValues []string `json:"request_object_signing_alg_values_supported,omitempty"`
|
|
||||||
ReqObjEncryptionAlgValues []string `json:"request_object_encryption_alg_values_supported,omitempty"`
|
|
||||||
ReqObjEncryptionEncValues []string `json:"request_object_encryption_enc_values_supported,omitempty"`
|
|
||||||
|
|
||||||
TokenEndpointAuthMethodsSupported []string `json:"token_endpoint_auth_methods_supported,omitempty"`
|
|
||||||
TokenEndpointAuthSigningAlgValuesSupported []string `json:"token_endpoint_auth_signing_alg_values_supported,omitempty"`
|
|
||||||
|
|
||||||
DisplayValuesSupported []string `json:"display_values_supported,omitempty"`
|
|
||||||
ClaimTypesSupported []string `json:"claim_types_supported,omitempty"`
|
|
||||||
ClaimsSupported []string `json:"claims_supported,omitempty"`
|
|
||||||
ServiceDocs string `json:"service_documentation,omitempty"`
|
|
||||||
ClaimsLocalsSupported []string `json:"claims_locales_supported,omitempty"`
|
|
||||||
UILocalsSupported []string `json:"ui_locales_supported,omitempty"`
|
|
||||||
ClaimsParameterSupported bool `json:"claims_parameter_supported,omitempty"`
|
|
||||||
RequestParameterSupported bool `json:"request_parameter_supported,omitempty"`
|
|
||||||
RequestURIParamaterSupported bool `json:"request_uri_parameter_supported,omitempty"`
|
|
||||||
RequireRequestURIRegistration bool `json:"require_request_uri_registration,omitempty"`
|
|
||||||
|
|
||||||
Policy string `json:"op_policy_uri,omitempty"`
|
|
||||||
TermsOfService string `json:"op_tos_uri,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cfg ProviderConfig) toEncodableStruct() encodableProviderConfig {
|
|
||||||
return encodableProviderConfig{
|
|
||||||
Issuer: uriToString(cfg.Issuer),
|
|
||||||
AuthEndpoint: uriToString(cfg.AuthEndpoint),
|
|
||||||
TokenEndpoint: uriToString(cfg.TokenEndpoint),
|
|
||||||
UserInfoEndpoint: uriToString(cfg.UserInfoEndpoint),
|
|
||||||
KeysEndpoint: uriToString(cfg.KeysEndpoint),
|
|
||||||
RegistrationEndpoint: uriToString(cfg.RegistrationEndpoint),
|
|
||||||
EndSessionEndpoint: uriToString(cfg.EndSessionEndpoint),
|
|
||||||
CheckSessionIFrame: uriToString(cfg.CheckSessionIFrame),
|
|
||||||
ScopesSupported: cfg.ScopesSupported,
|
|
||||||
ResponseTypesSupported: cfg.ResponseTypesSupported,
|
|
||||||
ResponseModesSupported: cfg.ResponseModesSupported,
|
|
||||||
GrantTypesSupported: cfg.GrantTypesSupported,
|
|
||||||
ACRValuesSupported: cfg.ACRValuesSupported,
|
|
||||||
SubjectTypesSupported: cfg.SubjectTypesSupported,
|
|
||||||
IDTokenSigningAlgValues: cfg.IDTokenSigningAlgValues,
|
|
||||||
IDTokenEncryptionAlgValues: cfg.IDTokenEncryptionAlgValues,
|
|
||||||
IDTokenEncryptionEncValues: cfg.IDTokenEncryptionEncValues,
|
|
||||||
UserInfoSigningAlgValues: cfg.UserInfoSigningAlgValues,
|
|
||||||
UserInfoEncryptionAlgValues: cfg.UserInfoEncryptionAlgValues,
|
|
||||||
UserInfoEncryptionEncValues: cfg.UserInfoEncryptionEncValues,
|
|
||||||
ReqObjSigningAlgValues: cfg.ReqObjSigningAlgValues,
|
|
||||||
ReqObjEncryptionAlgValues: cfg.ReqObjEncryptionAlgValues,
|
|
||||||
ReqObjEncryptionEncValues: cfg.ReqObjEncryptionEncValues,
|
|
||||||
TokenEndpointAuthMethodsSupported: cfg.TokenEndpointAuthMethodsSupported,
|
|
||||||
TokenEndpointAuthSigningAlgValuesSupported: cfg.TokenEndpointAuthSigningAlgValuesSupported,
|
|
||||||
DisplayValuesSupported: cfg.DisplayValuesSupported,
|
|
||||||
ClaimTypesSupported: cfg.ClaimTypesSupported,
|
|
||||||
ClaimsSupported: cfg.ClaimsSupported,
|
|
||||||
ServiceDocs: uriToString(cfg.ServiceDocs),
|
|
||||||
ClaimsLocalsSupported: cfg.ClaimsLocalsSupported,
|
|
||||||
UILocalsSupported: cfg.UILocalsSupported,
|
|
||||||
ClaimsParameterSupported: cfg.ClaimsParameterSupported,
|
|
||||||
RequestParameterSupported: cfg.RequestParameterSupported,
|
|
||||||
RequestURIParamaterSupported: cfg.RequestURIParamaterSupported,
|
|
||||||
RequireRequestURIRegistration: cfg.RequireRequestURIRegistration,
|
|
||||||
Policy: uriToString(cfg.Policy),
|
|
||||||
TermsOfService: uriToString(cfg.TermsOfService),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e encodableProviderConfig) toStruct() (ProviderConfig, error) {
|
|
||||||
p := stickyErrParser{}
|
|
||||||
conf := ProviderConfig{
|
|
||||||
Issuer: p.parseURI(e.Issuer, "issuer"),
|
|
||||||
AuthEndpoint: p.parseURI(e.AuthEndpoint, "authorization_endpoint"),
|
|
||||||
TokenEndpoint: p.parseURI(e.TokenEndpoint, "token_endpoint"),
|
|
||||||
UserInfoEndpoint: p.parseURI(e.UserInfoEndpoint, "userinfo_endpoint"),
|
|
||||||
KeysEndpoint: p.parseURI(e.KeysEndpoint, "jwks_uri"),
|
|
||||||
RegistrationEndpoint: p.parseURI(e.RegistrationEndpoint, "registration_endpoint"),
|
|
||||||
EndSessionEndpoint: p.parseURI(e.EndSessionEndpoint, "end_session_endpoint"),
|
|
||||||
CheckSessionIFrame: p.parseURI(e.CheckSessionIFrame, "check_session_iframe"),
|
|
||||||
ScopesSupported: e.ScopesSupported,
|
|
||||||
ResponseTypesSupported: e.ResponseTypesSupported,
|
|
||||||
ResponseModesSupported: e.ResponseModesSupported,
|
|
||||||
GrantTypesSupported: e.GrantTypesSupported,
|
|
||||||
ACRValuesSupported: e.ACRValuesSupported,
|
|
||||||
SubjectTypesSupported: e.SubjectTypesSupported,
|
|
||||||
IDTokenSigningAlgValues: e.IDTokenSigningAlgValues,
|
|
||||||
IDTokenEncryptionAlgValues: e.IDTokenEncryptionAlgValues,
|
|
||||||
IDTokenEncryptionEncValues: e.IDTokenEncryptionEncValues,
|
|
||||||
UserInfoSigningAlgValues: e.UserInfoSigningAlgValues,
|
|
||||||
UserInfoEncryptionAlgValues: e.UserInfoEncryptionAlgValues,
|
|
||||||
UserInfoEncryptionEncValues: e.UserInfoEncryptionEncValues,
|
|
||||||
ReqObjSigningAlgValues: e.ReqObjSigningAlgValues,
|
|
||||||
ReqObjEncryptionAlgValues: e.ReqObjEncryptionAlgValues,
|
|
||||||
ReqObjEncryptionEncValues: e.ReqObjEncryptionEncValues,
|
|
||||||
TokenEndpointAuthMethodsSupported: e.TokenEndpointAuthMethodsSupported,
|
|
||||||
TokenEndpointAuthSigningAlgValuesSupported: e.TokenEndpointAuthSigningAlgValuesSupported,
|
|
||||||
DisplayValuesSupported: e.DisplayValuesSupported,
|
|
||||||
ClaimTypesSupported: e.ClaimTypesSupported,
|
|
||||||
ClaimsSupported: e.ClaimsSupported,
|
|
||||||
ServiceDocs: p.parseURI(e.ServiceDocs, "service_documentation"),
|
|
||||||
ClaimsLocalsSupported: e.ClaimsLocalsSupported,
|
|
||||||
UILocalsSupported: e.UILocalsSupported,
|
|
||||||
ClaimsParameterSupported: e.ClaimsParameterSupported,
|
|
||||||
RequestParameterSupported: e.RequestParameterSupported,
|
|
||||||
RequestURIParamaterSupported: e.RequestURIParamaterSupported,
|
|
||||||
RequireRequestURIRegistration: e.RequireRequestURIRegistration,
|
|
||||||
Policy: p.parseURI(e.Policy, "op_policy-uri"),
|
|
||||||
TermsOfService: p.parseURI(e.TermsOfService, "op_tos_uri"),
|
|
||||||
}
|
|
||||||
if p.firstErr != nil {
|
|
||||||
return ProviderConfig{}, p.firstErr
|
|
||||||
}
|
|
||||||
return conf, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Empty returns if a ProviderConfig holds no information.
|
|
||||||
//
|
|
||||||
// This case generally indicates a ProviderConfigGetter has experienced an error
|
|
||||||
// and has nothing to report.
|
|
||||||
func (p ProviderConfig) Empty() bool {
|
|
||||||
return p.Issuer == nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func contains(sli []string, ele string) bool {
|
|
||||||
for _, s := range sli {
|
|
||||||
if s == ele {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// Valid determines if a ProviderConfig conforms with the OIDC specification.
|
|
||||||
// If Valid returns successfully it guarantees required field are non-nil and
|
|
||||||
// URLs are well formed.
|
|
||||||
//
|
|
||||||
// Valid is called by UnmarshalJSON.
|
|
||||||
//
|
|
||||||
// NOTE(ericchiang): For development purposes Valid does not mandate 'https' for
|
|
||||||
// URLs fields where the OIDC spec requires it. This may change in future releases
|
|
||||||
// of this package. See: https://github.com/coreos/go-oidc/issues/34
|
|
||||||
func (p ProviderConfig) Valid() error {
|
|
||||||
grantTypes := p.GrantTypesSupported
|
|
||||||
if len(grantTypes) == 0 {
|
|
||||||
grantTypes = DefaultGrantTypesSupported
|
|
||||||
}
|
|
||||||
implicitOnly := true
|
|
||||||
for _, grantType := range grantTypes {
|
|
||||||
if grantType != oauth2.GrantTypeImplicit {
|
|
||||||
implicitOnly = false
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(p.SubjectTypesSupported) == 0 {
|
|
||||||
return errors.New("missing required field subject_types_supported")
|
|
||||||
}
|
|
||||||
if len(p.IDTokenSigningAlgValues) == 0 {
|
|
||||||
return errors.New("missing required field id_token_signing_alg_values_supported")
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(p.ScopesSupported) != 0 && !contains(p.ScopesSupported, "openid") {
|
|
||||||
return errors.New("scoped_supported must be unspecified or include 'openid'")
|
|
||||||
}
|
|
||||||
|
|
||||||
if !contains(p.IDTokenSigningAlgValues, "RS256") {
|
|
||||||
return errors.New("id_token_signing_alg_values_supported must include 'RS256'")
|
|
||||||
}
|
|
||||||
|
|
||||||
uris := []struct {
|
|
||||||
val *url.URL
|
|
||||||
name string
|
|
||||||
required bool
|
|
||||||
}{
|
|
||||||
{p.Issuer, "issuer", true},
|
|
||||||
{p.AuthEndpoint, "authorization_endpoint", true},
|
|
||||||
{p.TokenEndpoint, "token_endpoint", !implicitOnly},
|
|
||||||
{p.UserInfoEndpoint, "userinfo_endpoint", false},
|
|
||||||
{p.KeysEndpoint, "jwks_uri", true},
|
|
||||||
{p.RegistrationEndpoint, "registration_endpoint", false},
|
|
||||||
{p.EndSessionEndpoint, "end_session_endpoint", false},
|
|
||||||
{p.CheckSessionIFrame, "check_session_iframe", false},
|
|
||||||
{p.ServiceDocs, "service_documentation", false},
|
|
||||||
{p.Policy, "op_policy_uri", false},
|
|
||||||
{p.TermsOfService, "op_tos_uri", false},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, uri := range uris {
|
|
||||||
if uri.val == nil {
|
|
||||||
if !uri.required {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
return fmt.Errorf("empty value for required uri field %s", uri.name)
|
|
||||||
}
|
|
||||||
if uri.val.Host == "" {
|
|
||||||
return fmt.Errorf("no host for uri field %s", uri.name)
|
|
||||||
}
|
|
||||||
if uri.val.Scheme != "http" && uri.val.Scheme != "https" {
|
|
||||||
return fmt.Errorf("uri field %s schemeis not http or https", uri.name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Supports determines if provider supports a client given their respective metadata.
|
|
||||||
func (p ProviderConfig) Supports(c ClientMetadata) error {
|
|
||||||
if err := p.Valid(); err != nil {
|
|
||||||
return fmt.Errorf("invalid provider config: %v", err)
|
|
||||||
}
|
|
||||||
if err := c.Valid(); err != nil {
|
|
||||||
return fmt.Errorf("invalid client config: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fill default values for omitted fields
|
|
||||||
c = c.Defaults()
|
|
||||||
p = p.Defaults()
|
|
||||||
|
|
||||||
// Do the supported values list the requested one?
|
|
||||||
supports := []struct {
|
|
||||||
supported []string
|
|
||||||
requested string
|
|
||||||
name string
|
|
||||||
}{
|
|
||||||
{p.IDTokenSigningAlgValues, c.IDTokenResponseOptions.SigningAlg, "id_token_signed_response_alg"},
|
|
||||||
{p.IDTokenEncryptionAlgValues, c.IDTokenResponseOptions.EncryptionAlg, "id_token_encryption_response_alg"},
|
|
||||||
{p.IDTokenEncryptionEncValues, c.IDTokenResponseOptions.EncryptionEnc, "id_token_encryption_response_enc"},
|
|
||||||
{p.UserInfoSigningAlgValues, c.UserInfoResponseOptions.SigningAlg, "userinfo_signed_response_alg"},
|
|
||||||
{p.UserInfoEncryptionAlgValues, c.UserInfoResponseOptions.EncryptionAlg, "userinfo_encryption_response_alg"},
|
|
||||||
{p.UserInfoEncryptionEncValues, c.UserInfoResponseOptions.EncryptionEnc, "userinfo_encryption_response_enc"},
|
|
||||||
{p.ReqObjSigningAlgValues, c.RequestObjectOptions.SigningAlg, "request_object_signing_alg"},
|
|
||||||
{p.ReqObjEncryptionAlgValues, c.RequestObjectOptions.EncryptionAlg, "request_object_encryption_alg"},
|
|
||||||
{p.ReqObjEncryptionEncValues, c.RequestObjectOptions.EncryptionEnc, "request_object_encryption_enc"},
|
|
||||||
}
|
|
||||||
for _, field := range supports {
|
|
||||||
if field.requested == "" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if !contains(field.supported, field.requested) {
|
|
||||||
return fmt.Errorf("provider does not support requested value for field %s", field.name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
stringsEqual := func(s1, s2 string) bool { return s1 == s2 }
|
|
||||||
|
|
||||||
// For lists, are the list of requested values a subset of the supported ones?
|
|
||||||
supportsAll := []struct {
|
|
||||||
supported []string
|
|
||||||
requested []string
|
|
||||||
name string
|
|
||||||
// OAuth2.0 response_type can be space separated lists where order doesn't matter.
|
|
||||||
// For example "id_token token" is the same as "token id_token"
|
|
||||||
// Support a custom compare method.
|
|
||||||
comp func(s1, s2 string) bool
|
|
||||||
}{
|
|
||||||
{p.GrantTypesSupported, c.GrantTypes, "grant_types", stringsEqual},
|
|
||||||
{p.ResponseTypesSupported, c.ResponseTypes, "response_type", oauth2.ResponseTypesEqual},
|
|
||||||
}
|
|
||||||
for _, field := range supportsAll {
|
|
||||||
requestLoop:
|
|
||||||
for _, req := range field.requested {
|
|
||||||
for _, sup := range field.supported {
|
|
||||||
if field.comp(req, sup) {
|
|
||||||
continue requestLoop
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return fmt.Errorf("provider does not support requested value for field %s", field.name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO(ericchiang): Are there more checks we feel comfortable with begin strict about?
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p ProviderConfig) SupportsGrantType(grantType string) bool {
|
|
||||||
var supported []string
|
|
||||||
if len(p.GrantTypesSupported) == 0 {
|
|
||||||
supported = DefaultGrantTypesSupported
|
|
||||||
} else {
|
|
||||||
supported = p.GrantTypesSupported
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, t := range supported {
|
|
||||||
if t == grantType {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
type ProviderConfigGetter interface {
|
|
||||||
Get() (ProviderConfig, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
type ProviderConfigSetter interface {
|
|
||||||
Set(ProviderConfig) error
|
|
||||||
}
|
|
||||||
|
|
||||||
type ProviderConfigSyncer struct {
|
|
||||||
from ProviderConfigGetter
|
|
||||||
to ProviderConfigSetter
|
|
||||||
clock clockwork.Clock
|
|
||||||
|
|
||||||
initialSyncDone bool
|
|
||||||
initialSyncWait sync.WaitGroup
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewProviderConfigSyncer(from ProviderConfigGetter, to ProviderConfigSetter) *ProviderConfigSyncer {
|
|
||||||
return &ProviderConfigSyncer{
|
|
||||||
from: from,
|
|
||||||
to: to,
|
|
||||||
clock: clockwork.NewRealClock(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *ProviderConfigSyncer) Run() chan struct{} {
|
|
||||||
stop := make(chan struct{})
|
|
||||||
|
|
||||||
var next pcsStepper
|
|
||||||
next = &pcsStepNext{aft: time.Duration(0)}
|
|
||||||
|
|
||||||
s.initialSyncWait.Add(1)
|
|
||||||
go func() {
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case <-s.clock.After(next.after()):
|
|
||||||
next = next.step(s.sync)
|
|
||||||
case <-stop:
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
return stop
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *ProviderConfigSyncer) WaitUntilInitialSync() {
|
|
||||||
s.initialSyncWait.Wait()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *ProviderConfigSyncer) sync() (time.Duration, error) {
|
|
||||||
cfg, err := s.from.Get()
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = s.to.Set(cfg); err != nil {
|
|
||||||
return 0, fmt.Errorf("error setting provider config: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !s.initialSyncDone {
|
|
||||||
s.initialSyncWait.Done()
|
|
||||||
s.initialSyncDone = true
|
|
||||||
}
|
|
||||||
|
|
||||||
return nextSyncAfter(cfg.ExpiresAt, s.clock), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type pcsStepFunc func() (time.Duration, error)
|
|
||||||
|
|
||||||
type pcsStepper interface {
|
|
||||||
after() time.Duration
|
|
||||||
step(pcsStepFunc) pcsStepper
|
|
||||||
}
|
|
||||||
|
|
||||||
type pcsStepNext struct {
|
|
||||||
aft time.Duration
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n *pcsStepNext) after() time.Duration {
|
|
||||||
return n.aft
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n *pcsStepNext) step(fn pcsStepFunc) (next pcsStepper) {
|
|
||||||
ttl, err := fn()
|
|
||||||
if err == nil {
|
|
||||||
next = &pcsStepNext{aft: ttl}
|
|
||||||
} else {
|
|
||||||
next = &pcsStepRetry{aft: time.Second}
|
|
||||||
log.Printf("go-oidc: provider config sync failed, retrying in %v: %v", next.after(), err)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
type pcsStepRetry struct {
|
|
||||||
aft time.Duration
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *pcsStepRetry) after() time.Duration {
|
|
||||||
return r.aft
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *pcsStepRetry) step(fn pcsStepFunc) (next pcsStepper) {
|
|
||||||
ttl, err := fn()
|
|
||||||
if err == nil {
|
|
||||||
next = &pcsStepNext{aft: ttl}
|
|
||||||
} else {
|
|
||||||
next = &pcsStepRetry{aft: timeutil.ExpBackoff(r.aft, time.Minute)}
|
|
||||||
log.Printf("go-oidc: provider config sync failed, retrying in %v: %v", next.after(), err)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func nextSyncAfter(exp time.Time, clock clockwork.Clock) time.Duration {
|
|
||||||
if exp.IsZero() {
|
|
||||||
return MaximumProviderConfigSyncInterval
|
|
||||||
}
|
|
||||||
|
|
||||||
t := exp.Sub(clock.Now()) / 2
|
|
||||||
if t > MaximumProviderConfigSyncInterval {
|
|
||||||
t = MaximumProviderConfigSyncInterval
|
|
||||||
} else if t < minimumProviderConfigSyncInterval {
|
|
||||||
t = minimumProviderConfigSyncInterval
|
|
||||||
}
|
|
||||||
|
|
||||||
return t
|
|
||||||
}
|
|
||||||
|
|
||||||
type httpProviderConfigGetter struct {
|
|
||||||
hc phttp.Client
|
|
||||||
issuerURL string
|
|
||||||
clock clockwork.Clock
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewHTTPProviderConfigGetter(hc phttp.Client, issuerURL string) *httpProviderConfigGetter {
|
|
||||||
return &httpProviderConfigGetter{
|
|
||||||
hc: hc,
|
|
||||||
issuerURL: issuerURL,
|
|
||||||
clock: clockwork.NewRealClock(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *httpProviderConfigGetter) Get() (cfg ProviderConfig, err error) {
|
|
||||||
// If the Issuer value contains a path component, any terminating / MUST be removed before
|
|
||||||
// appending /.well-known/openid-configuration.
|
|
||||||
// https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderConfigurationRequest
|
|
||||||
discoveryURL := strings.TrimSuffix(r.issuerURL, "/") + discoveryConfigPath
|
|
||||||
req, err := http.NewRequest("GET", discoveryURL, nil)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
resp, err := r.hc.Do(req)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
if err = json.NewDecoder(resp.Body).Decode(&cfg); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var ttl time.Duration
|
|
||||||
var ok bool
|
|
||||||
ttl, ok, err = phttp.Cacheable(resp.Header)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
} else if ok {
|
|
||||||
cfg.ExpiresAt = r.clock.Now().UTC().Add(ttl)
|
|
||||||
}
|
|
||||||
|
|
||||||
// The issuer value returned MUST be identical to the Issuer URL that was directly used to retrieve the configuration information.
|
|
||||||
// http://openid.net/specs/openid-connect-discovery-1_0.html#ProviderConfigurationValidation
|
|
||||||
if !urlEqual(cfg.Issuer.String(), r.issuerURL) {
|
|
||||||
err = fmt.Errorf(`"issuer" in config (%v) does not match provided issuer URL (%v)`, cfg.Issuer, r.issuerURL)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func FetchProviderConfig(hc phttp.Client, issuerURL string) (ProviderConfig, error) {
|
|
||||||
if hc == nil {
|
|
||||||
hc = http.DefaultClient
|
|
||||||
}
|
|
||||||
|
|
||||||
g := NewHTTPProviderConfigGetter(hc, issuerURL)
|
|
||||||
return g.Get()
|
|
||||||
}
|
|
||||||
|
|
||||||
func WaitForProviderConfig(hc phttp.Client, issuerURL string) (pcfg ProviderConfig) {
|
|
||||||
return waitForProviderConfig(hc, issuerURL, clockwork.NewRealClock())
|
|
||||||
}
|
|
||||||
|
|
||||||
func waitForProviderConfig(hc phttp.Client, issuerURL string, clock clockwork.Clock) (pcfg ProviderConfig) {
|
|
||||||
var sleep time.Duration
|
|
||||||
var err error
|
|
||||||
for {
|
|
||||||
pcfg, err = FetchProviderConfig(hc, issuerURL)
|
|
||||||
if err == nil {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
sleep = timeutil.ExpBackoff(sleep, time.Minute)
|
|
||||||
fmt.Printf("Failed fetching provider config, trying again in %v: %v\n", sleep, err)
|
|
||||||
time.Sleep(sleep)
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
88
vendor/github.com/coreos/go-oidc/oidc/transport.go
generated
vendored
88
vendor/github.com/coreos/go-oidc/oidc/transport.go
generated
vendored
@ -1,88 +0,0 @@
|
|||||||
package oidc
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"net/http"
|
|
||||||
"sync"
|
|
||||||
|
|
||||||
phttp "github.com/coreos/go-oidc/http"
|
|
||||||
"github.com/coreos/go-oidc/jose"
|
|
||||||
)
|
|
||||||
|
|
||||||
type TokenRefresher interface {
|
|
||||||
// Verify checks if the provided token is currently valid or not.
|
|
||||||
Verify(jose.JWT) error
|
|
||||||
|
|
||||||
// Refresh attempts to authenticate and retrieve a new token.
|
|
||||||
Refresh() (jose.JWT, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
type ClientCredsTokenRefresher struct {
|
|
||||||
Issuer string
|
|
||||||
OIDCClient *Client
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ClientCredsTokenRefresher) Verify(jwt jose.JWT) (err error) {
|
|
||||||
_, err = VerifyClientClaims(jwt, c.Issuer)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ClientCredsTokenRefresher) Refresh() (jwt jose.JWT, err error) {
|
|
||||||
if err = c.OIDCClient.Healthy(); err != nil {
|
|
||||||
err = fmt.Errorf("unable to authenticate, unhealthy OIDC client: %v", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
jwt, err = c.OIDCClient.ClientCredsToken([]string{"openid"})
|
|
||||||
if err != nil {
|
|
||||||
err = fmt.Errorf("unable to verify auth code with issuer: %v", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
type AuthenticatedTransport struct {
|
|
||||||
TokenRefresher
|
|
||||||
http.RoundTripper
|
|
||||||
|
|
||||||
mu sync.Mutex
|
|
||||||
jwt jose.JWT
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *AuthenticatedTransport) verifiedJWT() (jose.JWT, error) {
|
|
||||||
t.mu.Lock()
|
|
||||||
defer t.mu.Unlock()
|
|
||||||
|
|
||||||
if t.TokenRefresher.Verify(t.jwt) == nil {
|
|
||||||
return t.jwt, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
jwt, err := t.TokenRefresher.Refresh()
|
|
||||||
if err != nil {
|
|
||||||
return jose.JWT{}, fmt.Errorf("unable to acquire valid JWT: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
t.jwt = jwt
|
|
||||||
return t.jwt, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetJWT sets the JWT held by the Transport.
|
|
||||||
// This is useful for cases in which you want to set an initial JWT.
|
|
||||||
func (t *AuthenticatedTransport) SetJWT(jwt jose.JWT) {
|
|
||||||
t.mu.Lock()
|
|
||||||
defer t.mu.Unlock()
|
|
||||||
|
|
||||||
t.jwt = jwt
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *AuthenticatedTransport) RoundTrip(r *http.Request) (*http.Response, error) {
|
|
||||||
jwt, err := t.verifiedJWT()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
req := phttp.CopyRequest(r)
|
|
||||||
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", jwt.Encode()))
|
|
||||||
return t.RoundTripper.RoundTrip(req)
|
|
||||||
}
|
|
109
vendor/github.com/coreos/go-oidc/oidc/util.go
generated
vendored
109
vendor/github.com/coreos/go-oidc/oidc/util.go
generated
vendored
@ -1,109 +0,0 @@
|
|||||||
package oidc
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/rand"
|
|
||||||
"encoding/base64"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"net"
|
|
||||||
"net/http"
|
|
||||||
"net/url"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/coreos/go-oidc/jose"
|
|
||||||
)
|
|
||||||
|
|
||||||
// RequestTokenExtractor funcs extract a raw encoded token from a request.
|
|
||||||
type RequestTokenExtractor func(r *http.Request) (string, error)
|
|
||||||
|
|
||||||
// ExtractBearerToken is a RequestTokenExtractor which extracts a bearer token from a request's
|
|
||||||
// Authorization header.
|
|
||||||
func ExtractBearerToken(r *http.Request) (string, error) {
|
|
||||||
ah := r.Header.Get("Authorization")
|
|
||||||
if ah == "" {
|
|
||||||
return "", errors.New("missing Authorization header")
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(ah) <= 6 || strings.ToUpper(ah[0:6]) != "BEARER" {
|
|
||||||
return "", errors.New("should be a bearer token")
|
|
||||||
}
|
|
||||||
|
|
||||||
val := ah[7:]
|
|
||||||
if len(val) == 0 {
|
|
||||||
return "", errors.New("bearer token is empty")
|
|
||||||
}
|
|
||||||
|
|
||||||
return val, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// CookieTokenExtractor returns a RequestTokenExtractor which extracts a token from the named cookie in a request.
|
|
||||||
func CookieTokenExtractor(cookieName string) RequestTokenExtractor {
|
|
||||||
return func(r *http.Request) (string, error) {
|
|
||||||
ck, err := r.Cookie(cookieName)
|
|
||||||
if err != nil {
|
|
||||||
return "", fmt.Errorf("token cookie not found in request: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if ck.Value == "" {
|
|
||||||
return "", errors.New("token cookie found but is empty")
|
|
||||||
}
|
|
||||||
|
|
||||||
return ck.Value, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewClaims(iss, sub string, aud interface{}, iat, exp time.Time) jose.Claims {
|
|
||||||
return jose.Claims{
|
|
||||||
// required
|
|
||||||
"iss": iss,
|
|
||||||
"sub": sub,
|
|
||||||
"aud": aud,
|
|
||||||
"iat": iat.Unix(),
|
|
||||||
"exp": exp.Unix(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func GenClientID(hostport string) (string, error) {
|
|
||||||
b, err := randBytes(32)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
var host string
|
|
||||||
if strings.Contains(hostport, ":") {
|
|
||||||
host, _, err = net.SplitHostPort(hostport)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
host = hostport
|
|
||||||
}
|
|
||||||
|
|
||||||
return fmt.Sprintf("%s@%s", base64.URLEncoding.EncodeToString(b), host), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func randBytes(n int) ([]byte, error) {
|
|
||||||
b := make([]byte, n)
|
|
||||||
got, err := rand.Read(b)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
} else if n != got {
|
|
||||||
return nil, errors.New("unable to generate enough random data")
|
|
||||||
}
|
|
||||||
return b, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// urlEqual checks two urls for equality using only the host and path portions.
|
|
||||||
func urlEqual(url1, url2 string) bool {
|
|
||||||
u1, err := url.Parse(url1)
|
|
||||||
if err != nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
u2, err := url.Parse(url2)
|
|
||||||
if err != nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
return strings.ToLower(u1.Host+u1.Path) == strings.ToLower(u2.Host+u2.Path)
|
|
||||||
}
|
|
190
vendor/github.com/coreos/go-oidc/oidc/verification.go
generated
vendored
190
vendor/github.com/coreos/go-oidc/oidc/verification.go
generated
vendored
@ -1,190 +0,0 @@
|
|||||||
package oidc
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/jonboulle/clockwork"
|
|
||||||
|
|
||||||
"github.com/coreos/go-oidc/jose"
|
|
||||||
"github.com/coreos/go-oidc/key"
|
|
||||||
)
|
|
||||||
|
|
||||||
func VerifySignature(jwt jose.JWT, keys []key.PublicKey) (bool, error) {
|
|
||||||
jwtBytes := []byte(jwt.Data())
|
|
||||||
for _, k := range keys {
|
|
||||||
v, err := k.Verifier()
|
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
if v.Verify(jwt.Signature, jwtBytes) == nil {
|
|
||||||
return true, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// containsString returns true if the given string(needle) is found
|
|
||||||
// in the string array(haystack).
|
|
||||||
func containsString(needle string, haystack []string) bool {
|
|
||||||
for _, v := range haystack {
|
|
||||||
if v == needle {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// Verify claims in accordance with OIDC spec
|
|
||||||
// http://openid.net/specs/openid-connect-basic-1_0.html#IDTokenValidation
|
|
||||||
func VerifyClaims(jwt jose.JWT, issuer, clientID string) error {
|
|
||||||
now := time.Now().UTC()
|
|
||||||
|
|
||||||
claims, err := jwt.Claims()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
ident, err := IdentityFromClaims(claims)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if ident.ExpiresAt.Before(now) {
|
|
||||||
return errors.New("token is expired")
|
|
||||||
}
|
|
||||||
|
|
||||||
// iss REQUIRED. Issuer Identifier for the Issuer of the response.
|
|
||||||
// The iss value is a case sensitive URL using the https scheme that contains scheme,
|
|
||||||
// host, and optionally, port number and path components and no query or fragment components.
|
|
||||||
if iss, exists := claims["iss"].(string); exists {
|
|
||||||
if !urlEqual(iss, issuer) {
|
|
||||||
return fmt.Errorf("invalid claim value: 'iss'. expected=%s, found=%s.", issuer, iss)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return errors.New("missing claim: 'iss'")
|
|
||||||
}
|
|
||||||
|
|
||||||
// iat REQUIRED. Time at which the JWT was issued.
|
|
||||||
// Its value is a JSON number representing the number of seconds from 1970-01-01T0:0:0Z
|
|
||||||
// as measured in UTC until the date/time.
|
|
||||||
if _, exists := claims["iat"].(float64); !exists {
|
|
||||||
return errors.New("missing claim: 'iat'")
|
|
||||||
}
|
|
||||||
|
|
||||||
// aud REQUIRED. Audience(s) that this ID Token is intended for.
|
|
||||||
// It MUST contain the OAuth 2.0 client_id of the Relying Party as an audience value.
|
|
||||||
// It MAY also contain identifiers for other audiences. In the general case, the aud
|
|
||||||
// value is an array of case sensitive strings. In the common special case when there
|
|
||||||
// is one audience, the aud value MAY be a single case sensitive string.
|
|
||||||
if aud, ok, err := claims.StringClaim("aud"); err == nil && ok {
|
|
||||||
if aud != clientID {
|
|
||||||
return fmt.Errorf("invalid claims, 'aud' claim and 'client_id' do not match, aud=%s, client_id=%s", aud, clientID)
|
|
||||||
}
|
|
||||||
} else if aud, ok, err := claims.StringsClaim("aud"); err == nil && ok {
|
|
||||||
if !containsString(clientID, aud) {
|
|
||||||
return fmt.Errorf("invalid claims, cannot find 'client_id' in 'aud' claim, aud=%v, client_id=%s", aud, clientID)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return errors.New("invalid claim value: 'aud' is required, and should be either string or string array")
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// VerifyClientClaims verifies all the required claims are valid for a "client credentials" JWT.
|
|
||||||
// Returns the client ID if valid, or an error if invalid.
|
|
||||||
func VerifyClientClaims(jwt jose.JWT, issuer string) (string, error) {
|
|
||||||
claims, err := jwt.Claims()
|
|
||||||
if err != nil {
|
|
||||||
return "", fmt.Errorf("failed to parse JWT claims: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
iss, ok, err := claims.StringClaim("iss")
|
|
||||||
if err != nil {
|
|
||||||
return "", fmt.Errorf("failed to parse 'iss' claim: %v", err)
|
|
||||||
} else if !ok {
|
|
||||||
return "", errors.New("missing required 'iss' claim")
|
|
||||||
} else if !urlEqual(iss, issuer) {
|
|
||||||
return "", fmt.Errorf("'iss' claim does not match expected issuer, iss=%s", iss)
|
|
||||||
}
|
|
||||||
|
|
||||||
sub, ok, err := claims.StringClaim("sub")
|
|
||||||
if err != nil {
|
|
||||||
return "", fmt.Errorf("failed to parse 'sub' claim: %v", err)
|
|
||||||
} else if !ok {
|
|
||||||
return "", errors.New("missing required 'sub' claim")
|
|
||||||
}
|
|
||||||
|
|
||||||
if aud, ok, err := claims.StringClaim("aud"); err == nil && ok {
|
|
||||||
if aud != sub {
|
|
||||||
return "", fmt.Errorf("invalid claims, 'aud' claim and 'sub' claim do not match, aud=%s, sub=%s", aud, sub)
|
|
||||||
}
|
|
||||||
} else if aud, ok, err := claims.StringsClaim("aud"); err == nil && ok {
|
|
||||||
if !containsString(sub, aud) {
|
|
||||||
return "", fmt.Errorf("invalid claims, cannot find 'sud' in 'aud' claim, aud=%v, sub=%s", aud, sub)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return "", errors.New("invalid claim value: 'aud' is required, and should be either string or string array")
|
|
||||||
}
|
|
||||||
|
|
||||||
now := time.Now().UTC()
|
|
||||||
exp, ok, err := claims.TimeClaim("exp")
|
|
||||||
if err != nil {
|
|
||||||
return "", fmt.Errorf("failed to parse 'exp' claim: %v", err)
|
|
||||||
} else if !ok {
|
|
||||||
return "", errors.New("missing required 'exp' claim")
|
|
||||||
} else if exp.Before(now) {
|
|
||||||
return "", fmt.Errorf("token already expired at: %v", exp)
|
|
||||||
}
|
|
||||||
|
|
||||||
return sub, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type JWTVerifier struct {
|
|
||||||
issuer string
|
|
||||||
clientID string
|
|
||||||
syncFunc func() error
|
|
||||||
keysFunc func() []key.PublicKey
|
|
||||||
clock clockwork.Clock
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewJWTVerifier(issuer, clientID string, syncFunc func() error, keysFunc func() []key.PublicKey) JWTVerifier {
|
|
||||||
return JWTVerifier{
|
|
||||||
issuer: issuer,
|
|
||||||
clientID: clientID,
|
|
||||||
syncFunc: syncFunc,
|
|
||||||
keysFunc: keysFunc,
|
|
||||||
clock: clockwork.NewRealClock(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (v *JWTVerifier) Verify(jwt jose.JWT) error {
|
|
||||||
// Verify claims before verifying the signature. This is an optimization to throw out
|
|
||||||
// tokens we know are invalid without undergoing an expensive signature check and
|
|
||||||
// possibly a re-sync event.
|
|
||||||
if err := VerifyClaims(jwt, v.issuer, v.clientID); err != nil {
|
|
||||||
return fmt.Errorf("oidc: JWT claims invalid: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
ok, err := VerifySignature(jwt, v.keysFunc())
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("oidc: JWT signature verification failed: %v", err)
|
|
||||||
} else if ok {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = v.syncFunc(); err != nil {
|
|
||||||
return fmt.Errorf("oidc: failed syncing KeySet: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
ok, err = VerifySignature(jwt, v.keysFunc())
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("oidc: JWT signature verification failed: %v", err)
|
|
||||||
} else if !ok {
|
|
||||||
return errors.New("oidc: unable to verify JWT signature: no matching keys")
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
16
vendor/github.com/coreos/go-oidc/test
generated
vendored
Executable file
16
vendor/github.com/coreos/go-oidc/test
generated
vendored
Executable file
@ -0,0 +1,16 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
# Filter out any files with a !golint build tag.
|
||||||
|
LINTABLE=$( go list -tags=golint -f '
|
||||||
|
{{- range $i, $file := .GoFiles -}}
|
||||||
|
{{ $file }} {{ end }}
|
||||||
|
{{ range $i, $file := .TestGoFiles -}}
|
||||||
|
{{ $file }} {{ end }}' github.com/coreos/go-oidc )
|
||||||
|
|
||||||
|
go test -v -i -race github.com/coreos/go-oidc/...
|
||||||
|
go test -v -race github.com/coreos/go-oidc/...
|
||||||
|
golint -set_exit_status $LINTABLE
|
||||||
|
go vet github.com/coreos/go-oidc/...
|
||||||
|
go build -v ./example/...
|
243
vendor/github.com/coreos/go-oidc/verify.go
generated
vendored
Normal file
243
vendor/github.com/coreos/go-oidc/verify.go
generated
vendored
Normal file
@ -0,0 +1,243 @@
|
|||||||
|
package oidc
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"encoding/base64"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"golang.org/x/oauth2"
|
||||||
|
jose "gopkg.in/square/go-jose.v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
issuerGoogleAccounts = "https://accounts.google.com"
|
||||||
|
issuerGoogleAccountsNoScheme = "accounts.google.com"
|
||||||
|
)
|
||||||
|
|
||||||
|
// KeySet is a set of publc JSON Web Keys that can be used to validate the signature
|
||||||
|
// of JSON web tokens. This is expected to be backed by a remote key set through
|
||||||
|
// provider metadata discovery or an in-memory set of keys delivered out-of-band.
|
||||||
|
type KeySet interface {
|
||||||
|
// VerifySignature parses the JSON web token, verifies the signature, and returns
|
||||||
|
// the raw payload. Header and claim fields are validated by other parts of the
|
||||||
|
// package. For example, the KeySet does not need to check values such as signature
|
||||||
|
// algorithm, issuer, and audience since the IDTokenVerifier validates these values
|
||||||
|
// independently.
|
||||||
|
//
|
||||||
|
// If VerifySignature makes HTTP requests to verify the token, it's expected to
|
||||||
|
// use any HTTP client associated with the context through ClientContext.
|
||||||
|
VerifySignature(ctx context.Context, jwt string) (payload []byte, err error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// IDTokenVerifier provides verification for ID Tokens.
|
||||||
|
type IDTokenVerifier struct {
|
||||||
|
keySet KeySet
|
||||||
|
config *Config
|
||||||
|
issuer string
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewVerifier returns a verifier manually constructed from a key set and issuer URL.
|
||||||
|
//
|
||||||
|
// It's easier to use provider discovery to construct an IDTokenVerifier than creating
|
||||||
|
// one directly. This method is intended to be used with provider that don't support
|
||||||
|
// metadata discovery, or avoiding round trips when the key set URL is already known.
|
||||||
|
//
|
||||||
|
// This constructor can be used to create a verifier directly using the issuer URL and
|
||||||
|
// JSON Web Key Set URL without using discovery:
|
||||||
|
//
|
||||||
|
// keySet := oidc.NewRemoteKeySet(ctx, "https://www.googleapis.com/oauth2/v3/certs")
|
||||||
|
// verifier := oidc.NewVerifier("https://accounts.google.com", keySet, config)
|
||||||
|
//
|
||||||
|
// Since KeySet is an interface, this constructor can also be used to supply custom
|
||||||
|
// public key sources. For example, if a user wanted to supply public keys out-of-band
|
||||||
|
// and hold them statically in-memory:
|
||||||
|
//
|
||||||
|
// // Custom KeySet implementation.
|
||||||
|
// keySet := newStatisKeySet(publicKeys...)
|
||||||
|
//
|
||||||
|
// // Verifier uses the custom KeySet implementation.
|
||||||
|
// verifier := oidc.NewVerifier("https://auth.example.com", keySet, config)
|
||||||
|
//
|
||||||
|
func NewVerifier(issuerURL string, keySet KeySet, config *Config) *IDTokenVerifier {
|
||||||
|
return &IDTokenVerifier{keySet: keySet, config: config, issuer: issuerURL}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Config is the configuration for an IDTokenVerifier.
|
||||||
|
type Config struct {
|
||||||
|
// Expected audience of the token. For a majority of the cases this is expected to be
|
||||||
|
// the ID of the client that initialized the login flow. It may occasionally differ if
|
||||||
|
// the provider supports the authorizing party (azp) claim.
|
||||||
|
//
|
||||||
|
// If not provided, users must explicitly set SkipClientIDCheck.
|
||||||
|
ClientID string
|
||||||
|
// If specified, only this set of algorithms may be used to sign the JWT.
|
||||||
|
//
|
||||||
|
// Since many providers only support RS256, SupportedSigningAlgs defaults to this value.
|
||||||
|
SupportedSigningAlgs []string
|
||||||
|
|
||||||
|
// If true, no ClientID check performed. Must be true if ClientID field is empty.
|
||||||
|
SkipClientIDCheck bool
|
||||||
|
// If true, token expiry is not checked.
|
||||||
|
SkipExpiryCheck bool
|
||||||
|
|
||||||
|
// Time function to check Token expiry. Defaults to time.Now
|
||||||
|
Now func() time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verifier returns an IDTokenVerifier that uses the provider's key set to verify JWTs.
|
||||||
|
//
|
||||||
|
// The returned IDTokenVerifier is tied to the Provider's context and its behavior is
|
||||||
|
// undefined once the Provider's context is canceled.
|
||||||
|
func (p *Provider) Verifier(config *Config) *IDTokenVerifier {
|
||||||
|
return NewVerifier(p.issuer, p.remoteKeySet, config)
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseJWT(p string) ([]byte, error) {
|
||||||
|
parts := strings.Split(p, ".")
|
||||||
|
if len(parts) < 2 {
|
||||||
|
return nil, fmt.Errorf("oidc: malformed jwt, expected 3 parts got %d", len(parts))
|
||||||
|
}
|
||||||
|
payload, err := base64.RawURLEncoding.DecodeString(parts[1])
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("oidc: malformed jwt payload: %v", err)
|
||||||
|
}
|
||||||
|
return payload, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func contains(sli []string, ele string) bool {
|
||||||
|
for _, s := range sli {
|
||||||
|
if s == ele {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify parses a raw ID Token, verifies it's been signed by the provider, preforms
|
||||||
|
// any additional checks depending on the Config, and returns the payload.
|
||||||
|
//
|
||||||
|
// Verify does NOT do nonce validation, which is the callers responsibility.
|
||||||
|
//
|
||||||
|
// See: https://openid.net/specs/openid-connect-core-1_0.html#IDTokenValidation
|
||||||
|
//
|
||||||
|
// oauth2Token, err := oauth2Config.Exchange(ctx, r.URL.Query().Get("code"))
|
||||||
|
// if err != nil {
|
||||||
|
// // handle error
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// // Extract the ID Token from oauth2 token.
|
||||||
|
// rawIDToken, ok := oauth2Token.Extra("id_token").(string)
|
||||||
|
// if !ok {
|
||||||
|
// // handle error
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// token, err := verifier.Verify(ctx, rawIDToken)
|
||||||
|
//
|
||||||
|
func (v *IDTokenVerifier) Verify(ctx context.Context, rawIDToken string) (*IDToken, error) {
|
||||||
|
jws, err := jose.ParseSigned(rawIDToken)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("oidc: malformed jwt: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Throw out tokens with invalid claims before trying to verify the token. This lets
|
||||||
|
// us do cheap checks before possibly re-syncing keys.
|
||||||
|
payload, err := parseJWT(rawIDToken)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("oidc: malformed jwt: %v", err)
|
||||||
|
}
|
||||||
|
var token idToken
|
||||||
|
if err := json.Unmarshal(payload, &token); err != nil {
|
||||||
|
return nil, fmt.Errorf("oidc: failed to unmarshal claims: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
t := &IDToken{
|
||||||
|
Issuer: token.Issuer,
|
||||||
|
Subject: token.Subject,
|
||||||
|
Audience: []string(token.Audience),
|
||||||
|
Expiry: time.Time(token.Expiry),
|
||||||
|
IssuedAt: time.Time(token.IssuedAt),
|
||||||
|
Nonce: token.Nonce,
|
||||||
|
AccessTokenHash: token.AtHash,
|
||||||
|
claims: payload,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check issuer.
|
||||||
|
if t.Issuer != v.issuer {
|
||||||
|
// Google sometimes returns "accounts.google.com" as the issuer claim instead of
|
||||||
|
// the required "https://accounts.google.com". Detect this case and allow it only
|
||||||
|
// for Google.
|
||||||
|
//
|
||||||
|
// We will not add hooks to let other providers go off spec like this.
|
||||||
|
if !(v.issuer == issuerGoogleAccounts && t.Issuer == issuerGoogleAccountsNoScheme) {
|
||||||
|
return nil, fmt.Errorf("oidc: id token issued by a different provider, expected %q got %q", v.issuer, t.Issuer)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If a client ID has been provided, make sure it's part of the audience. SkipClientIDCheck must be true if ClientID is empty.
|
||||||
|
//
|
||||||
|
// This check DOES NOT ensure that the ClientID is the party to which the ID Token was issued (i.e. Authorized party).
|
||||||
|
if !v.config.SkipClientIDCheck {
|
||||||
|
if v.config.ClientID != "" {
|
||||||
|
if !contains(t.Audience, v.config.ClientID) {
|
||||||
|
return nil, fmt.Errorf("oidc: expected audience %q got %q", v.config.ClientID, t.Audience)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return nil, fmt.Errorf("oidc: invalid configuration, clientID must be provided or SkipClientIDCheck must be set")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If a SkipExpiryCheck is false, make sure token is not expired.
|
||||||
|
if !v.config.SkipExpiryCheck {
|
||||||
|
now := time.Now
|
||||||
|
if v.config.Now != nil {
|
||||||
|
now = v.config.Now
|
||||||
|
}
|
||||||
|
|
||||||
|
if t.Expiry.Before(now()) {
|
||||||
|
return nil, fmt.Errorf("oidc: token is expired (Token Expiry: %v)", t.Expiry)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
switch len(jws.Signatures) {
|
||||||
|
case 0:
|
||||||
|
return nil, fmt.Errorf("oidc: id token not signed")
|
||||||
|
case 1:
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("oidc: multiple signatures on id token not supported")
|
||||||
|
}
|
||||||
|
|
||||||
|
sig := jws.Signatures[0]
|
||||||
|
supportedSigAlgs := v.config.SupportedSigningAlgs
|
||||||
|
if len(supportedSigAlgs) == 0 {
|
||||||
|
supportedSigAlgs = []string{RS256}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !contains(supportedSigAlgs, sig.Header.Algorithm) {
|
||||||
|
return nil, fmt.Errorf("oidc: id token signed with unsupported algorithm, expected %q got %q", supportedSigAlgs, sig.Header.Algorithm)
|
||||||
|
}
|
||||||
|
|
||||||
|
t.sigAlgorithm = sig.Header.Algorithm
|
||||||
|
|
||||||
|
gotPayload, err := v.keySet.VerifySignature(ctx, rawIDToken)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to verify signature: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure that the payload returned by the square actually matches the payload parsed earlier.
|
||||||
|
if !bytes.Equal(gotPayload, payload) {
|
||||||
|
return nil, errors.New("oidc: internal error, payload parsed did not match previous payload")
|
||||||
|
}
|
||||||
|
|
||||||
|
return t, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Nonce returns an auth code option which requires the ID Token created by the
|
||||||
|
// OpenID Connect provider to contain the specified nonce.
|
||||||
|
func Nonce(nonce string) oauth2.AuthCodeOption {
|
||||||
|
return oauth2.SetAuthURLParam("nonce", nonce)
|
||||||
|
}
|
23
vendor/github.com/coreos/pkg/health/BUILD
generated
vendored
23
vendor/github.com/coreos/pkg/health/BUILD
generated
vendored
@ -1,23 +0,0 @@
|
|||||||
load("@io_bazel_rules_go//go:def.bzl", "go_library")
|
|
||||||
|
|
||||||
go_library(
|
|
||||||
name = "go_default_library",
|
|
||||||
srcs = ["health.go"],
|
|
||||||
importpath = "github.com/coreos/pkg/health",
|
|
||||||
visibility = ["//visibility:public"],
|
|
||||||
deps = ["//vendor/github.com/coreos/pkg/httputil:go_default_library"],
|
|
||||||
)
|
|
||||||
|
|
||||||
filegroup(
|
|
||||||
name = "package-srcs",
|
|
||||||
srcs = glob(["**"]),
|
|
||||||
tags = ["automanaged"],
|
|
||||||
visibility = ["//visibility:private"],
|
|
||||||
)
|
|
||||||
|
|
||||||
filegroup(
|
|
||||||
name = "all-srcs",
|
|
||||||
srcs = [":package-srcs"],
|
|
||||||
tags = ["automanaged"],
|
|
||||||
visibility = ["//visibility:public"],
|
|
||||||
)
|
|
11
vendor/github.com/coreos/pkg/health/README.md
generated
vendored
11
vendor/github.com/coreos/pkg/health/README.md
generated
vendored
@ -1,11 +0,0 @@
|
|||||||
health
|
|
||||||
====
|
|
||||||
|
|
||||||
A simple framework for implementing an HTTP health check endpoint on servers.
|
|
||||||
|
|
||||||
Users implement their `health.Checkable` types, and create a `health.Checker`, from which they can get an `http.HandlerFunc` using `health.Checker.MakeHealthHandlerFunc`.
|
|
||||||
|
|
||||||
### Documentation
|
|
||||||
|
|
||||||
For more details, visit the docs on [gopkgdoc](http://godoc.org/github.com/coreos/pkg/health)
|
|
||||||
|
|
127
vendor/github.com/coreos/pkg/health/health.go
generated
vendored
127
vendor/github.com/coreos/pkg/health/health.go
generated
vendored
@ -1,127 +0,0 @@
|
|||||||
package health
|
|
||||||
|
|
||||||
import (
|
|
||||||
"expvar"
|
|
||||||
"fmt"
|
|
||||||
"log"
|
|
||||||
"net/http"
|
|
||||||
|
|
||||||
"github.com/coreos/pkg/httputil"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Checkables should return nil when the thing they are checking is healthy, and an error otherwise.
|
|
||||||
type Checkable interface {
|
|
||||||
Healthy() error
|
|
||||||
}
|
|
||||||
|
|
||||||
// Checker provides a way to make an endpoint which can be probed for system health.
|
|
||||||
type Checker struct {
|
|
||||||
// Checks are the Checkables to be checked when probing.
|
|
||||||
Checks []Checkable
|
|
||||||
|
|
||||||
// Unhealthyhandler is called when one or more of the checks are unhealthy.
|
|
||||||
// If not provided DefaultUnhealthyHandler is called.
|
|
||||||
UnhealthyHandler UnhealthyHandler
|
|
||||||
|
|
||||||
// HealthyHandler is called when all checks are healthy.
|
|
||||||
// If not provided, DefaultHealthyHandler is called.
|
|
||||||
HealthyHandler http.HandlerFunc
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c Checker) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|
||||||
unhealthyHandler := c.UnhealthyHandler
|
|
||||||
if unhealthyHandler == nil {
|
|
||||||
unhealthyHandler = DefaultUnhealthyHandler
|
|
||||||
}
|
|
||||||
|
|
||||||
successHandler := c.HealthyHandler
|
|
||||||
if successHandler == nil {
|
|
||||||
successHandler = DefaultHealthyHandler
|
|
||||||
}
|
|
||||||
|
|
||||||
if r.Method != "GET" {
|
|
||||||
w.Header().Set("Allow", "GET")
|
|
||||||
w.WriteHeader(http.StatusMethodNotAllowed)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := Check(c.Checks); err != nil {
|
|
||||||
unhealthyHandler(w, r, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
successHandler(w, r)
|
|
||||||
}
|
|
||||||
|
|
||||||
type UnhealthyHandler func(w http.ResponseWriter, r *http.Request, err error)
|
|
||||||
|
|
||||||
type StatusResponse struct {
|
|
||||||
Status string `json:"status"`
|
|
||||||
Details *StatusResponseDetails `json:"details,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type StatusResponseDetails struct {
|
|
||||||
Code int `json:"code,omitempty"`
|
|
||||||
Message string `json:"message,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func Check(checks []Checkable) (err error) {
|
|
||||||
errs := []error{}
|
|
||||||
for _, c := range checks {
|
|
||||||
if e := c.Healthy(); e != nil {
|
|
||||||
errs = append(errs, e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
switch len(errs) {
|
|
||||||
case 0:
|
|
||||||
err = nil
|
|
||||||
case 1:
|
|
||||||
err = errs[0]
|
|
||||||
default:
|
|
||||||
err = fmt.Errorf("multiple health check failure: %v", errs)
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func DefaultHealthyHandler(w http.ResponseWriter, r *http.Request) {
|
|
||||||
err := httputil.WriteJSONResponse(w, http.StatusOK, StatusResponse{
|
|
||||||
Status: "ok",
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
// TODO(bobbyrullo): replace with logging from new logging pkg,
|
|
||||||
// once it lands.
|
|
||||||
log.Printf("Failed to write JSON response: %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func DefaultUnhealthyHandler(w http.ResponseWriter, r *http.Request, err error) {
|
|
||||||
writeErr := httputil.WriteJSONResponse(w, http.StatusInternalServerError, StatusResponse{
|
|
||||||
Status: "error",
|
|
||||||
Details: &StatusResponseDetails{
|
|
||||||
Code: http.StatusInternalServerError,
|
|
||||||
Message: err.Error(),
|
|
||||||
},
|
|
||||||
})
|
|
||||||
if writeErr != nil {
|
|
||||||
// TODO(bobbyrullo): replace with logging from new logging pkg,
|
|
||||||
// once it lands.
|
|
||||||
log.Printf("Failed to write JSON response: %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ExpvarHandler is copied from https://golang.org/src/expvar/expvar.go, where it's sadly unexported.
|
|
||||||
func ExpvarHandler(w http.ResponseWriter, r *http.Request) {
|
|
||||||
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
|
||||||
fmt.Fprintf(w, "{\n")
|
|
||||||
first := true
|
|
||||||
expvar.Do(func(kv expvar.KeyValue) {
|
|
||||||
if !first {
|
|
||||||
fmt.Fprintf(w, ",\n")
|
|
||||||
}
|
|
||||||
first = false
|
|
||||||
fmt.Fprintf(w, "%q: %s", kv.Key, kv.Value)
|
|
||||||
})
|
|
||||||
fmt.Fprintf(w, "\n}\n")
|
|
||||||
}
|
|
25
vendor/github.com/coreos/pkg/httputil/BUILD
generated
vendored
25
vendor/github.com/coreos/pkg/httputil/BUILD
generated
vendored
@ -1,25 +0,0 @@
|
|||||||
load("@io_bazel_rules_go//go:def.bzl", "go_library")
|
|
||||||
|
|
||||||
go_library(
|
|
||||||
name = "go_default_library",
|
|
||||||
srcs = [
|
|
||||||
"cookie.go",
|
|
||||||
"json.go",
|
|
||||||
],
|
|
||||||
importpath = "github.com/coreos/pkg/httputil",
|
|
||||||
visibility = ["//visibility:public"],
|
|
||||||
)
|
|
||||||
|
|
||||||
filegroup(
|
|
||||||
name = "package-srcs",
|
|
||||||
srcs = glob(["**"]),
|
|
||||||
tags = ["automanaged"],
|
|
||||||
visibility = ["//visibility:private"],
|
|
||||||
)
|
|
||||||
|
|
||||||
filegroup(
|
|
||||||
name = "all-srcs",
|
|
||||||
srcs = [":package-srcs"],
|
|
||||||
tags = ["automanaged"],
|
|
||||||
visibility = ["//visibility:public"],
|
|
||||||
)
|
|
13
vendor/github.com/coreos/pkg/httputil/README.md
generated
vendored
13
vendor/github.com/coreos/pkg/httputil/README.md
generated
vendored
@ -1,13 +0,0 @@
|
|||||||
httputil
|
|
||||||
====
|
|
||||||
|
|
||||||
Common code for dealing with HTTP.
|
|
||||||
|
|
||||||
Includes:
|
|
||||||
|
|
||||||
* Code for returning JSON responses.
|
|
||||||
|
|
||||||
### Documentation
|
|
||||||
|
|
||||||
Visit the docs on [gopkgdoc](http://godoc.org/github.com/coreos/pkg/httputil)
|
|
||||||
|
|
21
vendor/github.com/coreos/pkg/httputil/cookie.go
generated
vendored
21
vendor/github.com/coreos/pkg/httputil/cookie.go
generated
vendored
@ -1,21 +0,0 @@
|
|||||||
package httputil
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/http"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
// DeleteCookies effectively deletes all named cookies
|
|
||||||
// by wiping all data and setting to expire immediately.
|
|
||||||
func DeleteCookies(w http.ResponseWriter, cookieNames ...string) {
|
|
||||||
for _, n := range cookieNames {
|
|
||||||
c := &http.Cookie{
|
|
||||||
Name: n,
|
|
||||||
Value: "",
|
|
||||||
Path: "/",
|
|
||||||
MaxAge: -1,
|
|
||||||
Expires: time.Time{},
|
|
||||||
}
|
|
||||||
http.SetCookie(w, c)
|
|
||||||
}
|
|
||||||
}
|
|
27
vendor/github.com/coreos/pkg/httputil/json.go
generated
vendored
27
vendor/github.com/coreos/pkg/httputil/json.go
generated
vendored
@ -1,27 +0,0 @@
|
|||||||
package httputil
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"net/http"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
JSONContentType = "application/json"
|
|
||||||
)
|
|
||||||
|
|
||||||
func WriteJSONResponse(w http.ResponseWriter, code int, resp interface{}) error {
|
|
||||||
enc, err := json.Marshal(resp)
|
|
||||||
if err != nil {
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
w.Header().Set("Content-Type", JSONContentType)
|
|
||||||
w.WriteHeader(code)
|
|
||||||
|
|
||||||
_, err = w.Write(enc)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
22
vendor/github.com/coreos/pkg/timeutil/BUILD
generated
vendored
22
vendor/github.com/coreos/pkg/timeutil/BUILD
generated
vendored
@ -1,22 +0,0 @@
|
|||||||
load("@io_bazel_rules_go//go:def.bzl", "go_library")
|
|
||||||
|
|
||||||
go_library(
|
|
||||||
name = "go_default_library",
|
|
||||||
srcs = ["backoff.go"],
|
|
||||||
importpath = "github.com/coreos/pkg/timeutil",
|
|
||||||
visibility = ["//visibility:public"],
|
|
||||||
)
|
|
||||||
|
|
||||||
filegroup(
|
|
||||||
name = "package-srcs",
|
|
||||||
srcs = glob(["**"]),
|
|
||||||
tags = ["automanaged"],
|
|
||||||
visibility = ["//visibility:private"],
|
|
||||||
)
|
|
||||||
|
|
||||||
filegroup(
|
|
||||||
name = "all-srcs",
|
|
||||||
srcs = [":package-srcs"],
|
|
||||||
tags = ["automanaged"],
|
|
||||||
visibility = ["//visibility:public"],
|
|
||||||
)
|
|
15
vendor/github.com/coreos/pkg/timeutil/backoff.go
generated
vendored
15
vendor/github.com/coreos/pkg/timeutil/backoff.go
generated
vendored
@ -1,15 +0,0 @@
|
|||||||
package timeutil
|
|
||||||
|
|
||||||
import (
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
func ExpBackoff(prev, max time.Duration) time.Duration {
|
|
||||||
if prev == 0 {
|
|
||||||
return time.Second
|
|
||||||
}
|
|
||||||
if prev > max/2 {
|
|
||||||
return max
|
|
||||||
}
|
|
||||||
return 2 * prev
|
|
||||||
}
|
|
10
vendor/github.com/pquerna/cachecontrol/.travis.yml
generated
vendored
Normal file
10
vendor/github.com/pquerna/cachecontrol/.travis.yml
generated
vendored
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
language: go
|
||||||
|
|
||||||
|
install:
|
||||||
|
- go get -d -v ./...
|
||||||
|
- go get -u github.com/stretchr/testify/require
|
||||||
|
|
||||||
|
go:
|
||||||
|
- 1.7
|
||||||
|
- 1.8
|
||||||
|
- tip
|
@ -3,13 +3,12 @@ load("@io_bazel_rules_go//go:def.bzl", "go_library")
|
|||||||
go_library(
|
go_library(
|
||||||
name = "go_default_library",
|
name = "go_default_library",
|
||||||
srcs = [
|
srcs = [
|
||||||
|
"api.go",
|
||||||
"doc.go",
|
"doc.go",
|
||||||
"error.go",
|
|
||||||
"oauth2.go",
|
|
||||||
],
|
],
|
||||||
importpath = "github.com/coreos/go-oidc/oauth2",
|
importpath = "github.com/pquerna/cachecontrol",
|
||||||
visibility = ["//visibility:public"],
|
visibility = ["//visibility:public"],
|
||||||
deps = ["//vendor/github.com/coreos/go-oidc/http:go_default_library"],
|
deps = ["//vendor/github.com/pquerna/cachecontrol/cacheobject:go_default_library"],
|
||||||
)
|
)
|
||||||
|
|
||||||
filegroup(
|
filegroup(
|
||||||
@ -21,7 +20,10 @@ filegroup(
|
|||||||
|
|
||||||
filegroup(
|
filegroup(
|
||||||
name = "all-srcs",
|
name = "all-srcs",
|
||||||
srcs = [":package-srcs"],
|
srcs = [
|
||||||
|
":package-srcs",
|
||||||
|
"//vendor/github.com/pquerna/cachecontrol/cacheobject:all-srcs",
|
||||||
|
],
|
||||||
tags = ["automanaged"],
|
tags = ["automanaged"],
|
||||||
visibility = ["//visibility:public"],
|
visibility = ["//visibility:public"],
|
||||||
)
|
)
|
202
vendor/github.com/pquerna/cachecontrol/LICENSE
generated
vendored
Normal file
202
vendor/github.com/pquerna/cachecontrol/LICENSE
generated
vendored
Normal file
@ -0,0 +1,202 @@
|
|||||||
|
|
||||||
|
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.
|
107
vendor/github.com/pquerna/cachecontrol/README.md
generated
vendored
Normal file
107
vendor/github.com/pquerna/cachecontrol/README.md
generated
vendored
Normal file
@ -0,0 +1,107 @@
|
|||||||
|
# cachecontrol: HTTP Caching Parser and Interpretation
|
||||||
|
|
||||||
|
[](https://godoc.org/github.com/pquerna/cachecontrol)[](https://travis-ci.org/pquerna/cachecontrol)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
`cachecontrol` implements [RFC 7234](http://tools.ietf.org/html/rfc7234) __Hypertext Transfer Protocol (HTTP/1.1): Caching__. It does this by parsing the `Cache-Control` and other headers, providing information about requests and responses -- but `cachecontrol` does not implement an actual cache backend, just the control plane to make decisions about if a particular response is cachable.
|
||||||
|
|
||||||
|
# Usage
|
||||||
|
|
||||||
|
`cachecontrol.CachableResponse` returns an array of [reasons](https://godoc.org/github.com/pquerna/cachecontrol/cacheobject#Reason) why a response should not be cached and when it expires. In the case that `len(reasons) == 0`, the response is cachable according to the RFC. However, some people want non-compliant caches for various business use cases, so each reason is specifically named, so if your cache wants to cache `POST` requests, it can easily do that, but still be RFC compliant in other situations.
|
||||||
|
|
||||||
|
# Examples
|
||||||
|
|
||||||
|
## Can you cache Example.com?
|
||||||
|
|
||||||
|
```go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/pquerna/cachecontrol"
|
||||||
|
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
req, _ := http.NewRequest("GET", "http://www.example.com/", nil)
|
||||||
|
|
||||||
|
res, _ := http.DefaultClient.Do(req)
|
||||||
|
_, _ = ioutil.ReadAll(res.Body)
|
||||||
|
|
||||||
|
reasons, expires, _ := cachecontrol.CachableResponse(req, res, cachecontrol.Options{})
|
||||||
|
|
||||||
|
fmt.Println("Reasons to not cache: ", reasons)
|
||||||
|
fmt.Println("Expiration: ", expires.String())
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Can I use this in a high performance caching server?
|
||||||
|
|
||||||
|
`cachecontrol` is divided into two packages: `cachecontrol` with a high level API, and a lower level `cacheobject` package. Use [Object](https://godoc.org/github.com/pquerna/cachecontrol/cacheobject#Object) in a high performance use case where you have previously parsed headers containing dates or would like to avoid memory allocations.
|
||||||
|
|
||||||
|
```go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/pquerna/cachecontrol/cacheobject"
|
||||||
|
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
req, _ := http.NewRequest("GET", "http://www.example.com/", nil)
|
||||||
|
|
||||||
|
res, _ := http.DefaultClient.Do(req)
|
||||||
|
_, _ = ioutil.ReadAll(res.Body)
|
||||||
|
|
||||||
|
reqDir, _ := cacheobject.ParseRequestCacheControl(req.Header.Get("Cache-Control"))
|
||||||
|
|
||||||
|
resDir, _ := cacheobject.ParseResponseCacheControl(res.Header.Get("Cache-Control"))
|
||||||
|
expiresHeader, _ := http.ParseTime(res.Header.Get("Expires"))
|
||||||
|
dateHeader, _ := http.ParseTime(res.Header.Get("Date"))
|
||||||
|
lastModifiedHeader, _ := http.ParseTime(res.Header.Get("Last-Modified"))
|
||||||
|
|
||||||
|
obj := cacheobject.Object{
|
||||||
|
RespDirectives: resDir,
|
||||||
|
RespHeaders: res.Header,
|
||||||
|
RespStatusCode: res.StatusCode,
|
||||||
|
RespExpiresHeader: expiresHeader,
|
||||||
|
RespDateHeader: dateHeader,
|
||||||
|
RespLastModifiedHeader: lastModifiedHeader,
|
||||||
|
|
||||||
|
ReqDirectives: reqDir,
|
||||||
|
ReqHeaders: req.Header,
|
||||||
|
ReqMethod: req.Method,
|
||||||
|
|
||||||
|
NowUTC: time.Now().UTC(),
|
||||||
|
}
|
||||||
|
rv := cacheobject.ObjectResults{}
|
||||||
|
|
||||||
|
cacheobject.CachableObject(&obj, &rv)
|
||||||
|
cacheobject.ExpirationObject(&obj, &rv)
|
||||||
|
|
||||||
|
fmt.Println("Errors: ", rv.OutErr)
|
||||||
|
fmt.Println("Reasons to not cache: ", rv.OutReasons)
|
||||||
|
fmt.Println("Warning headers to add: ", rv.OutWarnings)
|
||||||
|
fmt.Println("Expiration: ", rv.OutExpirationTime.String())
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Improvements, bugs, adding features, and taking cachecontrol new directions!
|
||||||
|
|
||||||
|
Please [open issues in Github](https://github.com/pquerna/cachecontrol/issues) for ideas, bugs, and general thoughts. Pull requests are of course preferred :)
|
||||||
|
|
||||||
|
# Credits
|
||||||
|
|
||||||
|
`cachecontrol` has recieved significant contributions from:
|
||||||
|
|
||||||
|
* [Paul Querna](https://github.com/pquerna)
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
`cachecontrol` is licensed under the [Apache License, Version 2.0](./LICENSE)
|
48
vendor/github.com/pquerna/cachecontrol/api.go
generated
vendored
Normal file
48
vendor/github.com/pquerna/cachecontrol/api.go
generated
vendored
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
/**
|
||||||
|
* Copyright 2015 Paul Querna
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
package cachecontrol
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/pquerna/cachecontrol/cacheobject"
|
||||||
|
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Options struct {
|
||||||
|
// Set to True for a prviate cache, which is not shared amoung users (eg, in a browser)
|
||||||
|
// Set to False for a "shared" cache, which is more common in a server context.
|
||||||
|
PrivateCache bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// Given an HTTP Request, the future Status Code, and an ResponseWriter,
|
||||||
|
// determine the possible reasons a response SHOULD NOT be cached.
|
||||||
|
func CachableResponseWriter(req *http.Request,
|
||||||
|
statusCode int,
|
||||||
|
resp http.ResponseWriter,
|
||||||
|
opts Options) ([]cacheobject.Reason, time.Time, error) {
|
||||||
|
return cacheobject.UsingRequestResponse(req, statusCode, resp.Header(), opts.PrivateCache)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Given an HTTP Request and Response, determine the possible reasons a response SHOULD NOT
|
||||||
|
// be cached.
|
||||||
|
func CachableResponse(req *http.Request,
|
||||||
|
resp *http.Response,
|
||||||
|
opts Options) ([]cacheobject.Reason, time.Time, error) {
|
||||||
|
return cacheobject.UsingRequestResponse(req, resp.StatusCode, resp.Header, opts.PrivateCache)
|
||||||
|
}
|
@ -3,12 +3,13 @@ load("@io_bazel_rules_go//go:def.bzl", "go_library")
|
|||||||
go_library(
|
go_library(
|
||||||
name = "go_default_library",
|
name = "go_default_library",
|
||||||
srcs = [
|
srcs = [
|
||||||
"client.go",
|
"directive.go",
|
||||||
"doc.go",
|
"lex.go",
|
||||||
"http.go",
|
"object.go",
|
||||||
"url.go",
|
"reasons.go",
|
||||||
|
"warning.go",
|
||||||
],
|
],
|
||||||
importpath = "github.com/coreos/go-oidc/http",
|
importpath = "github.com/pquerna/cachecontrol/cacheobject",
|
||||||
visibility = ["//visibility:public"],
|
visibility = ["//visibility:public"],
|
||||||
)
|
)
|
||||||
|
|
510
vendor/github.com/pquerna/cachecontrol/cacheobject/directive.go
generated
vendored
Normal file
510
vendor/github.com/pquerna/cachecontrol/cacheobject/directive.go
generated
vendored
Normal file
@ -0,0 +1,510 @@
|
|||||||
|
/**
|
||||||
|
* Copyright 2015 Paul Querna
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
package cacheobject
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"math"
|
||||||
|
"net/http"
|
||||||
|
"net/textproto"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TODO(pquerna): add extensions from here: http://www.iana.org/assignments/http-cache-directives/http-cache-directives.xhtml
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrQuoteMismatch = errors.New("Missing closing quote")
|
||||||
|
ErrMaxAgeDeltaSeconds = errors.New("Failed to parse delta-seconds in `max-age`")
|
||||||
|
ErrSMaxAgeDeltaSeconds = errors.New("Failed to parse delta-seconds in `s-maxage`")
|
||||||
|
ErrMaxStaleDeltaSeconds = errors.New("Failed to parse delta-seconds in `min-fresh`")
|
||||||
|
ErrMinFreshDeltaSeconds = errors.New("Failed to parse delta-seconds in `min-fresh`")
|
||||||
|
ErrNoCacheNoArgs = errors.New("Unexpected argument to `no-cache`")
|
||||||
|
ErrNoStoreNoArgs = errors.New("Unexpected argument to `no-store`")
|
||||||
|
ErrNoTransformNoArgs = errors.New("Unexpected argument to `no-transform`")
|
||||||
|
ErrOnlyIfCachedNoArgs = errors.New("Unexpected argument to `only-if-cached`")
|
||||||
|
ErrMustRevalidateNoArgs = errors.New("Unexpected argument to `must-revalidate`")
|
||||||
|
ErrPublicNoArgs = errors.New("Unexpected argument to `public`")
|
||||||
|
ErrProxyRevalidateNoArgs = errors.New("Unexpected argument to `proxy-revalidate`")
|
||||||
|
)
|
||||||
|
|
||||||
|
func whitespace(b byte) bool {
|
||||||
|
if b == '\t' || b == ' ' {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func parse(value string, cd cacheDirective) error {
|
||||||
|
var err error = nil
|
||||||
|
i := 0
|
||||||
|
|
||||||
|
for i < len(value) && err == nil {
|
||||||
|
// eat leading whitespace or commas
|
||||||
|
if whitespace(value[i]) || value[i] == ',' {
|
||||||
|
i++
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
j := i + 1
|
||||||
|
|
||||||
|
for j < len(value) {
|
||||||
|
if !isToken(value[j]) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
j++
|
||||||
|
}
|
||||||
|
|
||||||
|
token := strings.ToLower(value[i:j])
|
||||||
|
tokenHasFields := hasFieldNames(token)
|
||||||
|
/*
|
||||||
|
println("GOT TOKEN:")
|
||||||
|
println(" i -> ", i)
|
||||||
|
println(" j -> ", j)
|
||||||
|
println(" token -> ", token)
|
||||||
|
*/
|
||||||
|
|
||||||
|
if j+1 < len(value) && value[j] == '=' {
|
||||||
|
k := j + 1
|
||||||
|
// minimum size two bytes of "", but we let httpUnquote handle it.
|
||||||
|
if k < len(value) && value[k] == '"' {
|
||||||
|
eaten, result := httpUnquote(value[k:])
|
||||||
|
if eaten == -1 {
|
||||||
|
return ErrQuoteMismatch
|
||||||
|
}
|
||||||
|
i = k + eaten
|
||||||
|
|
||||||
|
err = cd.addPair(token, result)
|
||||||
|
} else {
|
||||||
|
z := k
|
||||||
|
for z < len(value) {
|
||||||
|
if tokenHasFields {
|
||||||
|
if whitespace(value[z]) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if whitespace(value[z]) || value[z] == ',' {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
z++
|
||||||
|
}
|
||||||
|
i = z
|
||||||
|
|
||||||
|
result := value[k:z]
|
||||||
|
if result != "" && result[len(result)-1] == ',' {
|
||||||
|
result = result[:len(result)-1]
|
||||||
|
}
|
||||||
|
|
||||||
|
err = cd.addPair(token, result)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if token != "," {
|
||||||
|
err = cd.addToken(token)
|
||||||
|
}
|
||||||
|
i = j
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeltaSeconds specifies a non-negative integer, representing
|
||||||
|
// time in seconds: http://tools.ietf.org/html/rfc7234#section-1.2.1
|
||||||
|
//
|
||||||
|
// When set to -1, this means unset.
|
||||||
|
//
|
||||||
|
type DeltaSeconds int32
|
||||||
|
|
||||||
|
// Parser for delta-seconds, a uint31, more or less:
|
||||||
|
// http://tools.ietf.org/html/rfc7234#section-1.2.1
|
||||||
|
func parseDeltaSeconds(v string) (DeltaSeconds, error) {
|
||||||
|
n, err := strconv.ParseUint(v, 10, 32)
|
||||||
|
if err != nil {
|
||||||
|
if numError, ok := err.(*strconv.NumError); ok {
|
||||||
|
if numError.Err == strconv.ErrRange {
|
||||||
|
return DeltaSeconds(math.MaxInt32), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return DeltaSeconds(-1), err
|
||||||
|
} else {
|
||||||
|
if n > math.MaxInt32 {
|
||||||
|
return DeltaSeconds(math.MaxInt32), nil
|
||||||
|
} else {
|
||||||
|
return DeltaSeconds(n), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fields present in a header.
|
||||||
|
type FieldNames map[string]bool
|
||||||
|
|
||||||
|
// internal interface for shared methods of RequestCacheDirectives and ResponseCacheDirectives
|
||||||
|
type cacheDirective interface {
|
||||||
|
addToken(s string) error
|
||||||
|
addPair(s string, v string) error
|
||||||
|
}
|
||||||
|
|
||||||
|
// LOW LEVEL API: Repersentation of possible request directives in a `Cache-Control` header: http://tools.ietf.org/html/rfc7234#section-5.2.1
|
||||||
|
//
|
||||||
|
// Note: Many fields will be `nil` in practice.
|
||||||
|
//
|
||||||
|
type RequestCacheDirectives struct {
|
||||||
|
|
||||||
|
// max-age(delta seconds): http://tools.ietf.org/html/rfc7234#section-5.2.1.1
|
||||||
|
//
|
||||||
|
// The "max-age" request directive indicates that the client is
|
||||||
|
// unwilling to accept a response whose age is greater than the
|
||||||
|
// specified number of seconds. Unless the max-stale request directive
|
||||||
|
// is also present, the client is not willing to accept a stale
|
||||||
|
// response.
|
||||||
|
MaxAge DeltaSeconds
|
||||||
|
|
||||||
|
// max-stale(delta seconds): http://tools.ietf.org/html/rfc7234#section-5.2.1.2
|
||||||
|
//
|
||||||
|
// The "max-stale" request directive indicates that the client is
|
||||||
|
// willing to accept a response that has exceeded its freshness
|
||||||
|
// lifetime. If max-stale is assigned a value, then the client is
|
||||||
|
// willing to accept a response that has exceeded its freshness lifetime
|
||||||
|
// by no more than the specified number of seconds. If no value is
|
||||||
|
// assigned to max-stale, then the client is willing to accept a stale
|
||||||
|
// response of any age.
|
||||||
|
MaxStale DeltaSeconds
|
||||||
|
|
||||||
|
// min-fresh(delta seconds): http://tools.ietf.org/html/rfc7234#section-5.2.1.3
|
||||||
|
//
|
||||||
|
// The "min-fresh" request directive indicates that the client is
|
||||||
|
// willing to accept a response whose freshness lifetime is no less than
|
||||||
|
// its current age plus the specified time in seconds. That is, the
|
||||||
|
// client wants a response that will still be fresh for at least the
|
||||||
|
// specified number of seconds.
|
||||||
|
MinFresh DeltaSeconds
|
||||||
|
|
||||||
|
// no-cache(bool): http://tools.ietf.org/html/rfc7234#section-5.2.1.4
|
||||||
|
//
|
||||||
|
// The "no-cache" request directive indicates that a cache MUST NOT use
|
||||||
|
// a stored response to satisfy the request without successful
|
||||||
|
// validation on the origin server.
|
||||||
|
NoCache bool
|
||||||
|
|
||||||
|
// no-store(bool): http://tools.ietf.org/html/rfc7234#section-5.2.1.5
|
||||||
|
//
|
||||||
|
// The "no-store" request directive indicates that a cache MUST NOT
|
||||||
|
// store any part of either this request or any response to it. This
|
||||||
|
// directive applies to both private and shared caches.
|
||||||
|
NoStore bool
|
||||||
|
|
||||||
|
// no-transform(bool): http://tools.ietf.org/html/rfc7234#section-5.2.1.6
|
||||||
|
//
|
||||||
|
// The "no-transform" request directive indicates that an intermediary
|
||||||
|
// (whether or not it implements a cache) MUST NOT transform the
|
||||||
|
// payload, as defined in Section 5.7.2 of RFC7230.
|
||||||
|
NoTransform bool
|
||||||
|
|
||||||
|
// only-if-cached(bool): http://tools.ietf.org/html/rfc7234#section-5.2.1.7
|
||||||
|
//
|
||||||
|
// The "only-if-cached" request directive indicates that the client only
|
||||||
|
// wishes to obtain a stored response.
|
||||||
|
OnlyIfCached bool
|
||||||
|
|
||||||
|
// Extensions: http://tools.ietf.org/html/rfc7234#section-5.2.3
|
||||||
|
//
|
||||||
|
// The Cache-Control header field can be extended through the use of one
|
||||||
|
// or more cache-extension tokens, each with an optional value. A cache
|
||||||
|
// MUST ignore unrecognized cache directives.
|
||||||
|
Extensions []string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cd *RequestCacheDirectives) addToken(token string) error {
|
||||||
|
var err error = nil
|
||||||
|
|
||||||
|
switch token {
|
||||||
|
case "max-age":
|
||||||
|
err = ErrMaxAgeDeltaSeconds
|
||||||
|
case "max-stale":
|
||||||
|
err = ErrMaxStaleDeltaSeconds
|
||||||
|
case "min-fresh":
|
||||||
|
err = ErrMinFreshDeltaSeconds
|
||||||
|
case "no-cache":
|
||||||
|
cd.NoCache = true
|
||||||
|
case "no-store":
|
||||||
|
cd.NoStore = true
|
||||||
|
case "no-transform":
|
||||||
|
cd.NoTransform = true
|
||||||
|
case "only-if-cached":
|
||||||
|
cd.OnlyIfCached = true
|
||||||
|
default:
|
||||||
|
cd.Extensions = append(cd.Extensions, token)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cd *RequestCacheDirectives) addPair(token string, v string) error {
|
||||||
|
var err error = nil
|
||||||
|
|
||||||
|
switch token {
|
||||||
|
case "max-age":
|
||||||
|
cd.MaxAge, err = parseDeltaSeconds(v)
|
||||||
|
if err != nil {
|
||||||
|
err = ErrMaxAgeDeltaSeconds
|
||||||
|
}
|
||||||
|
case "max-stale":
|
||||||
|
cd.MaxStale, err = parseDeltaSeconds(v)
|
||||||
|
if err != nil {
|
||||||
|
err = ErrMaxStaleDeltaSeconds
|
||||||
|
}
|
||||||
|
case "min-fresh":
|
||||||
|
cd.MinFresh, err = parseDeltaSeconds(v)
|
||||||
|
if err != nil {
|
||||||
|
err = ErrMinFreshDeltaSeconds
|
||||||
|
}
|
||||||
|
case "no-cache":
|
||||||
|
err = ErrNoCacheNoArgs
|
||||||
|
case "no-store":
|
||||||
|
err = ErrNoStoreNoArgs
|
||||||
|
case "no-transform":
|
||||||
|
err = ErrNoTransformNoArgs
|
||||||
|
case "only-if-cached":
|
||||||
|
err = ErrOnlyIfCachedNoArgs
|
||||||
|
default:
|
||||||
|
// TODO(pquerna): this sucks, making user re-parse
|
||||||
|
cd.Extensions = append(cd.Extensions, token+"="+v)
|
||||||
|
}
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// LOW LEVEL API: Parses a Cache Control Header from a Request into a set of directives.
|
||||||
|
func ParseRequestCacheControl(value string) (*RequestCacheDirectives, error) {
|
||||||
|
cd := &RequestCacheDirectives{
|
||||||
|
MaxAge: -1,
|
||||||
|
MaxStale: -1,
|
||||||
|
MinFresh: -1,
|
||||||
|
}
|
||||||
|
|
||||||
|
err := parse(value, cd)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return cd, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// LOW LEVEL API: Repersentation of possible response directives in a `Cache-Control` header: http://tools.ietf.org/html/rfc7234#section-5.2.2
|
||||||
|
//
|
||||||
|
// Note: Many fields will be `nil` in practice.
|
||||||
|
//
|
||||||
|
type ResponseCacheDirectives struct {
|
||||||
|
|
||||||
|
// must-revalidate(bool): http://tools.ietf.org/html/rfc7234#section-5.2.2.1
|
||||||
|
//
|
||||||
|
// The "must-revalidate" response directive indicates that once it has
|
||||||
|
// become stale, a cache MUST NOT use the response to satisfy subsequent
|
||||||
|
// requests without successful validation on the origin server.
|
||||||
|
MustRevalidate bool
|
||||||
|
|
||||||
|
// no-cache(FieldName): http://tools.ietf.org/html/rfc7234#section-5.2.2.2
|
||||||
|
//
|
||||||
|
// The "no-cache" response directive indicates that the response MUST
|
||||||
|
// NOT be used to satisfy a subsequent request without successful
|
||||||
|
// validation on the origin server.
|
||||||
|
//
|
||||||
|
// If the no-cache response directive specifies one or more field-names,
|
||||||
|
// then a cache MAY use the response to satisfy a subsequent request,
|
||||||
|
// subject to any other restrictions on caching. However, any header
|
||||||
|
// fields in the response that have the field-name(s) listed MUST NOT be
|
||||||
|
// sent in the response to a subsequent request without successful
|
||||||
|
// revalidation with the origin server.
|
||||||
|
NoCache FieldNames
|
||||||
|
|
||||||
|
// no-cache(cast-to-bool): http://tools.ietf.org/html/rfc7234#section-5.2.2.2
|
||||||
|
//
|
||||||
|
// While the RFC defines optional field-names on a no-cache directive,
|
||||||
|
// many applications only want to know if any no-cache directives were
|
||||||
|
// present at all.
|
||||||
|
NoCachePresent bool
|
||||||
|
|
||||||
|
// no-store(bool): http://tools.ietf.org/html/rfc7234#section-5.2.2.3
|
||||||
|
//
|
||||||
|
// The "no-store" request directive indicates that a cache MUST NOT
|
||||||
|
// store any part of either this request or any response to it. This
|
||||||
|
// directive applies to both private and shared caches.
|
||||||
|
NoStore bool
|
||||||
|
|
||||||
|
// no-transform(bool): http://tools.ietf.org/html/rfc7234#section-5.2.2.4
|
||||||
|
//
|
||||||
|
// The "no-transform" response directive indicates that an intermediary
|
||||||
|
// (regardless of whether it implements a cache) MUST NOT transform the
|
||||||
|
// payload, as defined in Section 5.7.2 of RFC7230.
|
||||||
|
NoTransform bool
|
||||||
|
|
||||||
|
// public(bool): http://tools.ietf.org/html/rfc7234#section-5.2.2.5
|
||||||
|
//
|
||||||
|
// The "public" response directive indicates that any cache MAY store
|
||||||
|
// the response, even if the response would normally be non-cacheable or
|
||||||
|
// cacheable only within a private cache.
|
||||||
|
Public bool
|
||||||
|
|
||||||
|
// private(FieldName): http://tools.ietf.org/html/rfc7234#section-5.2.2.6
|
||||||
|
//
|
||||||
|
// The "private" response directive indicates that the response message
|
||||||
|
// is intended for a single user and MUST NOT be stored by a shared
|
||||||
|
// cache. A private cache MAY store the response and reuse it for later
|
||||||
|
// requests, even if the response would normally be non-cacheable.
|
||||||
|
//
|
||||||
|
// If the private response directive specifies one or more field-names,
|
||||||
|
// this requirement is limited to the field-values associated with the
|
||||||
|
// listed response header fields. That is, a shared cache MUST NOT
|
||||||
|
// store the specified field-names(s), whereas it MAY store the
|
||||||
|
// remainder of the response message.
|
||||||
|
Private FieldNames
|
||||||
|
|
||||||
|
// private(cast-to-bool): http://tools.ietf.org/html/rfc7234#section-5.2.2.6
|
||||||
|
//
|
||||||
|
// While the RFC defines optional field-names on a private directive,
|
||||||
|
// many applications only want to know if any private directives were
|
||||||
|
// present at all.
|
||||||
|
PrivatePresent bool
|
||||||
|
|
||||||
|
// proxy-revalidate(bool): http://tools.ietf.org/html/rfc7234#section-5.2.2.7
|
||||||
|
//
|
||||||
|
// The "proxy-revalidate" response directive has the same meaning as the
|
||||||
|
// must-revalidate response directive, except that it does not apply to
|
||||||
|
// private caches.
|
||||||
|
ProxyRevalidate bool
|
||||||
|
|
||||||
|
// max-age(delta seconds): http://tools.ietf.org/html/rfc7234#section-5.2.2.8
|
||||||
|
//
|
||||||
|
// The "max-age" response directive indicates that the response is to be
|
||||||
|
// considered stale after its age is greater than the specified number
|
||||||
|
// of seconds.
|
||||||
|
MaxAge DeltaSeconds
|
||||||
|
|
||||||
|
// s-maxage(delta seconds): http://tools.ietf.org/html/rfc7234#section-5.2.2.9
|
||||||
|
//
|
||||||
|
// The "s-maxage" response directive indicates that, in shared caches,
|
||||||
|
// the maximum age specified by this directive overrides the maximum age
|
||||||
|
// specified by either the max-age directive or the Expires header
|
||||||
|
// field. The s-maxage directive also implies the semantics of the
|
||||||
|
// proxy-revalidate response directive.
|
||||||
|
SMaxAge DeltaSeconds
|
||||||
|
|
||||||
|
// Extensions: http://tools.ietf.org/html/rfc7234#section-5.2.3
|
||||||
|
//
|
||||||
|
// The Cache-Control header field can be extended through the use of one
|
||||||
|
// or more cache-extension tokens, each with an optional value. A cache
|
||||||
|
// MUST ignore unrecognized cache directives.
|
||||||
|
Extensions []string
|
||||||
|
}
|
||||||
|
|
||||||
|
// LOW LEVEL API: Parses a Cache Control Header from a Response into a set of directives.
|
||||||
|
func ParseResponseCacheControl(value string) (*ResponseCacheDirectives, error) {
|
||||||
|
cd := &ResponseCacheDirectives{
|
||||||
|
MaxAge: -1,
|
||||||
|
SMaxAge: -1,
|
||||||
|
}
|
||||||
|
|
||||||
|
err := parse(value, cd)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return cd, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cd *ResponseCacheDirectives) addToken(token string) error {
|
||||||
|
var err error = nil
|
||||||
|
switch token {
|
||||||
|
case "must-revalidate":
|
||||||
|
cd.MustRevalidate = true
|
||||||
|
case "no-cache":
|
||||||
|
cd.NoCachePresent = true
|
||||||
|
case "no-store":
|
||||||
|
cd.NoStore = true
|
||||||
|
case "no-transform":
|
||||||
|
cd.NoTransform = true
|
||||||
|
case "public":
|
||||||
|
cd.Public = true
|
||||||
|
case "private":
|
||||||
|
cd.PrivatePresent = true
|
||||||
|
case "proxy-revalidate":
|
||||||
|
cd.ProxyRevalidate = true
|
||||||
|
case "max-age":
|
||||||
|
err = ErrMaxAgeDeltaSeconds
|
||||||
|
case "s-maxage":
|
||||||
|
err = ErrSMaxAgeDeltaSeconds
|
||||||
|
default:
|
||||||
|
cd.Extensions = append(cd.Extensions, token)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func hasFieldNames(token string) bool {
|
||||||
|
switch token {
|
||||||
|
case "no-cache":
|
||||||
|
return true
|
||||||
|
case "private":
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cd *ResponseCacheDirectives) addPair(token string, v string) error {
|
||||||
|
var err error = nil
|
||||||
|
|
||||||
|
switch token {
|
||||||
|
case "must-revalidate":
|
||||||
|
err = ErrMustRevalidateNoArgs
|
||||||
|
case "no-cache":
|
||||||
|
cd.NoCachePresent = true
|
||||||
|
tokens := strings.Split(v, ",")
|
||||||
|
if cd.NoCache == nil {
|
||||||
|
cd.NoCache = make(FieldNames)
|
||||||
|
}
|
||||||
|
for _, t := range tokens {
|
||||||
|
k := http.CanonicalHeaderKey(textproto.TrimString(t))
|
||||||
|
cd.NoCache[k] = true
|
||||||
|
}
|
||||||
|
case "no-store":
|
||||||
|
err = ErrNoStoreNoArgs
|
||||||
|
case "no-transform":
|
||||||
|
err = ErrNoTransformNoArgs
|
||||||
|
case "public":
|
||||||
|
err = ErrPublicNoArgs
|
||||||
|
case "private":
|
||||||
|
cd.PrivatePresent = true
|
||||||
|
tokens := strings.Split(v, ",")
|
||||||
|
if cd.Private == nil {
|
||||||
|
cd.Private = make(FieldNames)
|
||||||
|
}
|
||||||
|
for _, t := range tokens {
|
||||||
|
k := http.CanonicalHeaderKey(textproto.TrimString(t))
|
||||||
|
cd.Private[k] = true
|
||||||
|
}
|
||||||
|
case "proxy-revalidate":
|
||||||
|
err = ErrProxyRevalidateNoArgs
|
||||||
|
case "max-age":
|
||||||
|
cd.MaxAge, err = parseDeltaSeconds(v)
|
||||||
|
case "s-maxage":
|
||||||
|
cd.SMaxAge, err = parseDeltaSeconds(v)
|
||||||
|
default:
|
||||||
|
// TODO(pquerna): this sucks, making user re-parse, and its technically not 'quoted' like the original,
|
||||||
|
// but this is still easier, just a SplitN on "="
|
||||||
|
cd.Extensions = append(cd.Extensions, token+"="+v)
|
||||||
|
}
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
93
vendor/github.com/pquerna/cachecontrol/cacheobject/lex.go
generated
vendored
Normal file
93
vendor/github.com/pquerna/cachecontrol/cacheobject/lex.go
generated
vendored
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
// Copyright 2009 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package cacheobject
|
||||||
|
|
||||||
|
// This file deals with lexical matters of HTTP
|
||||||
|
|
||||||
|
func isSeparator(c byte) bool {
|
||||||
|
switch c {
|
||||||
|
case '(', ')', '<', '>', '@', ',', ';', ':', '\\', '"', '/', '[', ']', '?', '=', '{', '}', ' ', '\t':
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func isCtl(c byte) bool { return (0 <= c && c <= 31) || c == 127 }
|
||||||
|
|
||||||
|
func isChar(c byte) bool { return 0 <= c && c <= 127 }
|
||||||
|
|
||||||
|
func isAnyText(c byte) bool { return !isCtl(c) }
|
||||||
|
|
||||||
|
func isQdText(c byte) bool { return isAnyText(c) && c != '"' }
|
||||||
|
|
||||||
|
func isToken(c byte) bool { return isChar(c) && !isCtl(c) && !isSeparator(c) }
|
||||||
|
|
||||||
|
// Valid escaped sequences are not specified in RFC 2616, so for now, we assume
|
||||||
|
// that they coincide with the common sense ones used by GO. Malformed
|
||||||
|
// characters should probably not be treated as errors by a robust (forgiving)
|
||||||
|
// parser, so we replace them with the '?' character.
|
||||||
|
func httpUnquotePair(b byte) byte {
|
||||||
|
// skip the first byte, which should always be '\'
|
||||||
|
switch b {
|
||||||
|
case 'a':
|
||||||
|
return '\a'
|
||||||
|
case 'b':
|
||||||
|
return '\b'
|
||||||
|
case 'f':
|
||||||
|
return '\f'
|
||||||
|
case 'n':
|
||||||
|
return '\n'
|
||||||
|
case 'r':
|
||||||
|
return '\r'
|
||||||
|
case 't':
|
||||||
|
return '\t'
|
||||||
|
case 'v':
|
||||||
|
return '\v'
|
||||||
|
case '\\':
|
||||||
|
return '\\'
|
||||||
|
case '\'':
|
||||||
|
return '\''
|
||||||
|
case '"':
|
||||||
|
return '"'
|
||||||
|
}
|
||||||
|
return '?'
|
||||||
|
}
|
||||||
|
|
||||||
|
// raw must begin with a valid quoted string. Only the first quoted string is
|
||||||
|
// parsed and is unquoted in result. eaten is the number of bytes parsed, or -1
|
||||||
|
// upon failure.
|
||||||
|
func httpUnquote(raw string) (eaten int, result string) {
|
||||||
|
buf := make([]byte, len(raw))
|
||||||
|
if raw[0] != '"' {
|
||||||
|
return -1, ""
|
||||||
|
}
|
||||||
|
eaten = 1
|
||||||
|
j := 0 // # of bytes written in buf
|
||||||
|
for i := 1; i < len(raw); i++ {
|
||||||
|
switch b := raw[i]; b {
|
||||||
|
case '"':
|
||||||
|
eaten++
|
||||||
|
buf = buf[0:j]
|
||||||
|
return i + 1, string(buf)
|
||||||
|
case '\\':
|
||||||
|
if len(raw) < i+2 {
|
||||||
|
return -1, ""
|
||||||
|
}
|
||||||
|
buf[j] = httpUnquotePair(raw[i+1])
|
||||||
|
eaten += 2
|
||||||
|
j++
|
||||||
|
i++
|
||||||
|
default:
|
||||||
|
if isQdText(b) {
|
||||||
|
buf[j] = b
|
||||||
|
} else {
|
||||||
|
buf[j] = '?'
|
||||||
|
}
|
||||||
|
eaten++
|
||||||
|
j++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1, ""
|
||||||
|
}
|
378
vendor/github.com/pquerna/cachecontrol/cacheobject/object.go
generated
vendored
Normal file
378
vendor/github.com/pquerna/cachecontrol/cacheobject/object.go
generated
vendored
Normal file
@ -0,0 +1,378 @@
|
|||||||
|
/**
|
||||||
|
* Copyright 2015 Paul Querna
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
package cacheobject
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// LOW LEVEL API: Repersents a potentially cachable HTTP object.
|
||||||
|
//
|
||||||
|
// This struct is designed to be serialized efficiently, so in a high
|
||||||
|
// performance caching server, things like Date-Strings don't need to be
|
||||||
|
// parsed for every use of a cached object.
|
||||||
|
type Object struct {
|
||||||
|
CacheIsPrivate bool
|
||||||
|
|
||||||
|
RespDirectives *ResponseCacheDirectives
|
||||||
|
RespHeaders http.Header
|
||||||
|
RespStatusCode int
|
||||||
|
RespExpiresHeader time.Time
|
||||||
|
RespDateHeader time.Time
|
||||||
|
RespLastModifiedHeader time.Time
|
||||||
|
|
||||||
|
ReqDirectives *RequestCacheDirectives
|
||||||
|
ReqHeaders http.Header
|
||||||
|
ReqMethod string
|
||||||
|
|
||||||
|
NowUTC time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
// LOW LEVEL API: Repersents the results of examinig an Object with
|
||||||
|
// CachableObject and ExpirationObject.
|
||||||
|
//
|
||||||
|
// TODO(pquerna): decide if this is a good idea or bad
|
||||||
|
type ObjectResults struct {
|
||||||
|
OutReasons []Reason
|
||||||
|
OutWarnings []Warning
|
||||||
|
OutExpirationTime time.Time
|
||||||
|
OutErr error
|
||||||
|
}
|
||||||
|
|
||||||
|
// LOW LEVEL API: Check if a object is cachable.
|
||||||
|
func CachableObject(obj *Object, rv *ObjectResults) {
|
||||||
|
rv.OutReasons = nil
|
||||||
|
rv.OutWarnings = nil
|
||||||
|
rv.OutErr = nil
|
||||||
|
|
||||||
|
switch obj.ReqMethod {
|
||||||
|
case "GET":
|
||||||
|
break
|
||||||
|
case "HEAD":
|
||||||
|
break
|
||||||
|
case "POST":
|
||||||
|
/**
|
||||||
|
POST: http://tools.ietf.org/html/rfc7231#section-4.3.3
|
||||||
|
|
||||||
|
Responses to POST requests are only cacheable when they include
|
||||||
|
explicit freshness information (see Section 4.2.1 of [RFC7234]).
|
||||||
|
However, POST caching is not widely implemented. For cases where an
|
||||||
|
origin server wishes the client to be able to cache the result of a
|
||||||
|
POST in a way that can be reused by a later GET, the origin server
|
||||||
|
MAY send a 200 (OK) response containing the result and a
|
||||||
|
Content-Location header field that has the same value as the POST's
|
||||||
|
effective request URI (Section 3.1.4.2).
|
||||||
|
*/
|
||||||
|
if !hasFreshness(obj.ReqDirectives, obj.RespDirectives, obj.RespHeaders, obj.RespExpiresHeader, obj.CacheIsPrivate) {
|
||||||
|
rv.OutReasons = append(rv.OutReasons, ReasonRequestMethodPOST)
|
||||||
|
}
|
||||||
|
|
||||||
|
case "PUT":
|
||||||
|
rv.OutReasons = append(rv.OutReasons, ReasonRequestMethodPUT)
|
||||||
|
|
||||||
|
case "DELETE":
|
||||||
|
rv.OutReasons = append(rv.OutReasons, ReasonRequestMethodDELETE)
|
||||||
|
|
||||||
|
case "CONNECT":
|
||||||
|
rv.OutReasons = append(rv.OutReasons, ReasonRequestMethodCONNECT)
|
||||||
|
|
||||||
|
case "OPTIONS":
|
||||||
|
rv.OutReasons = append(rv.OutReasons, ReasonRequestMethodOPTIONS)
|
||||||
|
|
||||||
|
case "TRACE":
|
||||||
|
rv.OutReasons = append(rv.OutReasons, ReasonRequestMethodTRACE)
|
||||||
|
|
||||||
|
// HTTP Extension Methods: http://www.iana.org/assignments/http-methods/http-methods.xhtml
|
||||||
|
//
|
||||||
|
// To my knowledge, none of them are cachable. Please open a ticket if this is not the case!
|
||||||
|
//
|
||||||
|
default:
|
||||||
|
rv.OutReasons = append(rv.OutReasons, ReasonRequestMethodUnkown)
|
||||||
|
}
|
||||||
|
|
||||||
|
if obj.ReqDirectives.NoStore {
|
||||||
|
rv.OutReasons = append(rv.OutReasons, ReasonRequestNoStore)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Storing Responses to Authenticated Requests: http://tools.ietf.org/html/rfc7234#section-3.2
|
||||||
|
authz := obj.ReqHeaders.Get("Authorization")
|
||||||
|
if authz != "" {
|
||||||
|
if obj.RespDirectives.MustRevalidate ||
|
||||||
|
obj.RespDirectives.Public ||
|
||||||
|
obj.RespDirectives.SMaxAge != -1 {
|
||||||
|
// Expires of some kind present, this is potentially OK.
|
||||||
|
} else {
|
||||||
|
rv.OutReasons = append(rv.OutReasons, ReasonRequestAuthorizationHeader)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if obj.RespDirectives.PrivatePresent && !obj.CacheIsPrivate {
|
||||||
|
rv.OutReasons = append(rv.OutReasons, ReasonResponsePrivate)
|
||||||
|
}
|
||||||
|
|
||||||
|
if obj.RespDirectives.NoStore {
|
||||||
|
rv.OutReasons = append(rv.OutReasons, ReasonResponseNoStore)
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
the response either:
|
||||||
|
|
||||||
|
* contains an Expires header field (see Section 5.3), or
|
||||||
|
|
||||||
|
* contains a max-age response directive (see Section 5.2.2.8), or
|
||||||
|
|
||||||
|
* contains a s-maxage response directive (see Section 5.2.2.9)
|
||||||
|
and the cache is shared, or
|
||||||
|
|
||||||
|
* contains a Cache Control Extension (see Section 5.2.3) that
|
||||||
|
allows it to be cached, or
|
||||||
|
|
||||||
|
* has a status code that is defined as cacheable by default (see
|
||||||
|
Section 4.2.2), or
|
||||||
|
|
||||||
|
* contains a public response directive (see Section 5.2.2.5).
|
||||||
|
*/
|
||||||
|
|
||||||
|
expires := obj.RespHeaders.Get("Expires") != ""
|
||||||
|
statusCachable := cachableStatusCode(obj.RespStatusCode)
|
||||||
|
|
||||||
|
if expires ||
|
||||||
|
obj.RespDirectives.MaxAge != -1 ||
|
||||||
|
(obj.RespDirectives.SMaxAge != -1 && !obj.CacheIsPrivate) ||
|
||||||
|
statusCachable ||
|
||||||
|
obj.RespDirectives.Public {
|
||||||
|
/* cachable by default, at least one of the above conditions was true */
|
||||||
|
} else {
|
||||||
|
rv.OutReasons = append(rv.OutReasons, ReasonResponseUncachableByDefault)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var twentyFourHours = time.Duration(24 * time.Hour)
|
||||||
|
|
||||||
|
const debug = false
|
||||||
|
|
||||||
|
// LOW LEVEL API: Update an objects expiration time.
|
||||||
|
func ExpirationObject(obj *Object, rv *ObjectResults) {
|
||||||
|
/**
|
||||||
|
* Okay, lets calculate Freshness/Expiration now. woo:
|
||||||
|
* http://tools.ietf.org/html/rfc7234#section-4.2
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
o If the cache is shared and the s-maxage response directive
|
||||||
|
(Section 5.2.2.9) is present, use its value, or
|
||||||
|
|
||||||
|
o If the max-age response directive (Section 5.2.2.8) is present,
|
||||||
|
use its value, or
|
||||||
|
|
||||||
|
o If the Expires response header field (Section 5.3) is present, use
|
||||||
|
its value minus the value of the Date response header field, or
|
||||||
|
|
||||||
|
o Otherwise, no explicit expiration time is present in the response.
|
||||||
|
A heuristic freshness lifetime might be applicable; see
|
||||||
|
Section 4.2.2.
|
||||||
|
*/
|
||||||
|
|
||||||
|
var expiresTime time.Time
|
||||||
|
|
||||||
|
if obj.RespDirectives.SMaxAge != -1 && !obj.CacheIsPrivate {
|
||||||
|
expiresTime = obj.NowUTC.Add(time.Second * time.Duration(obj.RespDirectives.SMaxAge))
|
||||||
|
} else if obj.RespDirectives.MaxAge != -1 {
|
||||||
|
expiresTime = obj.NowUTC.UTC().Add(time.Second * time.Duration(obj.RespDirectives.MaxAge))
|
||||||
|
} else if !obj.RespExpiresHeader.IsZero() {
|
||||||
|
serverDate := obj.RespDateHeader
|
||||||
|
if serverDate.IsZero() {
|
||||||
|
// common enough case when a Date: header has not yet been added to an
|
||||||
|
// active response.
|
||||||
|
serverDate = obj.NowUTC
|
||||||
|
}
|
||||||
|
expiresTime = obj.NowUTC.Add(obj.RespExpiresHeader.Sub(serverDate))
|
||||||
|
} else if !obj.RespLastModifiedHeader.IsZero() {
|
||||||
|
// heuristic freshness lifetime
|
||||||
|
rv.OutWarnings = append(rv.OutWarnings, WarningHeuristicExpiration)
|
||||||
|
|
||||||
|
// http://httpd.apache.org/docs/2.4/mod/mod_cache.html#cachelastmodifiedfactor
|
||||||
|
// CacheMaxExpire defaults to 24 hours
|
||||||
|
// CacheLastModifiedFactor: is 0.1
|
||||||
|
//
|
||||||
|
// expiry-period = MIN(time-since-last-modified-date * factor, 24 hours)
|
||||||
|
//
|
||||||
|
// obj.NowUTC
|
||||||
|
|
||||||
|
since := obj.RespLastModifiedHeader.Sub(obj.NowUTC)
|
||||||
|
since = time.Duration(float64(since) * -0.1)
|
||||||
|
|
||||||
|
if since > twentyFourHours {
|
||||||
|
expiresTime = obj.NowUTC.Add(twentyFourHours)
|
||||||
|
} else {
|
||||||
|
expiresTime = obj.NowUTC.Add(since)
|
||||||
|
}
|
||||||
|
|
||||||
|
if debug {
|
||||||
|
println("Now UTC: ", obj.NowUTC.String())
|
||||||
|
println("Last-Modified: ", obj.RespLastModifiedHeader.String())
|
||||||
|
println("Since: ", since.String())
|
||||||
|
println("TwentyFourHours: ", twentyFourHours.String())
|
||||||
|
println("Expiration: ", expiresTime.String())
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// TODO(pquerna): what should the default behavoir be for expiration time?
|
||||||
|
}
|
||||||
|
|
||||||
|
rv.OutExpirationTime = expiresTime
|
||||||
|
}
|
||||||
|
|
||||||
|
// Evaluate cachability based on an HTTP request, and parts of the response.
|
||||||
|
func UsingRequestResponse(req *http.Request,
|
||||||
|
statusCode int,
|
||||||
|
respHeaders http.Header,
|
||||||
|
privateCache bool) ([]Reason, time.Time, error) {
|
||||||
|
|
||||||
|
var reqHeaders http.Header
|
||||||
|
var reqMethod string
|
||||||
|
|
||||||
|
var reqDir *RequestCacheDirectives = nil
|
||||||
|
respDir, err := ParseResponseCacheControl(respHeaders.Get("Cache-Control"))
|
||||||
|
if err != nil {
|
||||||
|
return nil, time.Time{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if req != nil {
|
||||||
|
reqDir, err = ParseRequestCacheControl(req.Header.Get("Cache-Control"))
|
||||||
|
if err != nil {
|
||||||
|
return nil, time.Time{}, err
|
||||||
|
}
|
||||||
|
reqHeaders = req.Header
|
||||||
|
reqMethod = req.Method
|
||||||
|
}
|
||||||
|
|
||||||
|
var expiresHeader time.Time
|
||||||
|
var dateHeader time.Time
|
||||||
|
var lastModifiedHeader time.Time
|
||||||
|
|
||||||
|
if respHeaders.Get("Expires") != "" {
|
||||||
|
expiresHeader, err = http.ParseTime(respHeaders.Get("Expires"))
|
||||||
|
if err != nil {
|
||||||
|
// sometimes servers will return `Expires: 0` or `Expires: -1` to
|
||||||
|
// indicate expired content
|
||||||
|
expiresHeader = time.Time{}
|
||||||
|
}
|
||||||
|
expiresHeader = expiresHeader.UTC()
|
||||||
|
}
|
||||||
|
|
||||||
|
if respHeaders.Get("Date") != "" {
|
||||||
|
dateHeader, err = http.ParseTime(respHeaders.Get("Date"))
|
||||||
|
if err != nil {
|
||||||
|
return nil, time.Time{}, err
|
||||||
|
}
|
||||||
|
dateHeader = dateHeader.UTC()
|
||||||
|
}
|
||||||
|
|
||||||
|
if respHeaders.Get("Last-Modified") != "" {
|
||||||
|
lastModifiedHeader, err = http.ParseTime(respHeaders.Get("Last-Modified"))
|
||||||
|
if err != nil {
|
||||||
|
return nil, time.Time{}, err
|
||||||
|
}
|
||||||
|
lastModifiedHeader = lastModifiedHeader.UTC()
|
||||||
|
}
|
||||||
|
|
||||||
|
obj := Object{
|
||||||
|
CacheIsPrivate: privateCache,
|
||||||
|
|
||||||
|
RespDirectives: respDir,
|
||||||
|
RespHeaders: respHeaders,
|
||||||
|
RespStatusCode: statusCode,
|
||||||
|
RespExpiresHeader: expiresHeader,
|
||||||
|
RespDateHeader: dateHeader,
|
||||||
|
RespLastModifiedHeader: lastModifiedHeader,
|
||||||
|
|
||||||
|
ReqDirectives: reqDir,
|
||||||
|
ReqHeaders: reqHeaders,
|
||||||
|
ReqMethod: reqMethod,
|
||||||
|
|
||||||
|
NowUTC: time.Now().UTC(),
|
||||||
|
}
|
||||||
|
rv := ObjectResults{}
|
||||||
|
|
||||||
|
CachableObject(&obj, &rv)
|
||||||
|
if rv.OutErr != nil {
|
||||||
|
return nil, time.Time{}, rv.OutErr
|
||||||
|
}
|
||||||
|
|
||||||
|
ExpirationObject(&obj, &rv)
|
||||||
|
if rv.OutErr != nil {
|
||||||
|
return nil, time.Time{}, rv.OutErr
|
||||||
|
}
|
||||||
|
|
||||||
|
return rv.OutReasons, rv.OutExpirationTime, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// calculate if a freshness directive is present: http://tools.ietf.org/html/rfc7234#section-4.2.1
|
||||||
|
func hasFreshness(reqDir *RequestCacheDirectives, respDir *ResponseCacheDirectives, respHeaders http.Header, respExpires time.Time, privateCache bool) bool {
|
||||||
|
if !privateCache && respDir.SMaxAge != -1 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
if respDir.MaxAge != -1 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
if !respExpires.IsZero() || respHeaders.Get("Expires") != "" {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func cachableStatusCode(statusCode int) bool {
|
||||||
|
/*
|
||||||
|
Responses with status codes that are defined as cacheable by default
|
||||||
|
(e.g., 200, 203, 204, 206, 300, 301, 404, 405, 410, 414, and 501 in
|
||||||
|
this specification) can be reused by a cache with heuristic
|
||||||
|
expiration unless otherwise indicated by the method definition or
|
||||||
|
explicit cache controls [RFC7234]; all other status codes are not
|
||||||
|
cacheable by default.
|
||||||
|
*/
|
||||||
|
switch statusCode {
|
||||||
|
case 200:
|
||||||
|
return true
|
||||||
|
case 203:
|
||||||
|
return true
|
||||||
|
case 204:
|
||||||
|
return true
|
||||||
|
case 206:
|
||||||
|
return true
|
||||||
|
case 300:
|
||||||
|
return true
|
||||||
|
case 301:
|
||||||
|
return true
|
||||||
|
case 404:
|
||||||
|
return true
|
||||||
|
case 405:
|
||||||
|
return true
|
||||||
|
case 410:
|
||||||
|
return true
|
||||||
|
case 414:
|
||||||
|
return true
|
||||||
|
case 501:
|
||||||
|
return true
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
95
vendor/github.com/pquerna/cachecontrol/cacheobject/reasons.go
generated
vendored
Normal file
95
vendor/github.com/pquerna/cachecontrol/cacheobject/reasons.go
generated
vendored
Normal file
@ -0,0 +1,95 @@
|
|||||||
|
/**
|
||||||
|
* Copyright 2015 Paul Querna
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
package cacheobject
|
||||||
|
|
||||||
|
// Repersents a potential Reason to not cache an object.
|
||||||
|
//
|
||||||
|
// Applications may wish to ignore specific reasons, which will make them non-RFC
|
||||||
|
// compliant, but this type gives them specific cases they can choose to ignore,
|
||||||
|
// making them compliant in as many cases as they can.
|
||||||
|
type Reason int
|
||||||
|
|
||||||
|
const (
|
||||||
|
|
||||||
|
// The request method was POST and an Expiration header was not supplied.
|
||||||
|
ReasonRequestMethodPOST Reason = iota
|
||||||
|
|
||||||
|
// The request method was PUT and PUTs are not cachable.
|
||||||
|
ReasonRequestMethodPUT
|
||||||
|
|
||||||
|
// The request method was DELETE and DELETEs are not cachable.
|
||||||
|
ReasonRequestMethodDELETE
|
||||||
|
|
||||||
|
// The request method was CONNECT and CONNECTs are not cachable.
|
||||||
|
ReasonRequestMethodCONNECT
|
||||||
|
|
||||||
|
// The request method was OPTIONS and OPTIONS are not cachable.
|
||||||
|
ReasonRequestMethodOPTIONS
|
||||||
|
|
||||||
|
// The request method was TRACE and TRACEs are not cachable.
|
||||||
|
ReasonRequestMethodTRACE
|
||||||
|
|
||||||
|
// The request method was not recognized by cachecontrol, and should not be cached.
|
||||||
|
ReasonRequestMethodUnkown
|
||||||
|
|
||||||
|
// The request included an Cache-Control: no-store header
|
||||||
|
ReasonRequestNoStore
|
||||||
|
|
||||||
|
// The request included an Authorization header without an explicit Public or Expiration time: http://tools.ietf.org/html/rfc7234#section-3.2
|
||||||
|
ReasonRequestAuthorizationHeader
|
||||||
|
|
||||||
|
// The response included an Cache-Control: no-store header
|
||||||
|
ReasonResponseNoStore
|
||||||
|
|
||||||
|
// The response included an Cache-Control: private header and this is not a Private cache
|
||||||
|
ReasonResponsePrivate
|
||||||
|
|
||||||
|
// The response failed to meet at least one of the conditions specified in RFC 7234 section 3: http://tools.ietf.org/html/rfc7234#section-3
|
||||||
|
ReasonResponseUncachableByDefault
|
||||||
|
)
|
||||||
|
|
||||||
|
func (r Reason) String() string {
|
||||||
|
switch r {
|
||||||
|
case ReasonRequestMethodPOST:
|
||||||
|
return "ReasonRequestMethodPOST"
|
||||||
|
case ReasonRequestMethodPUT:
|
||||||
|
return "ReasonRequestMethodPUT"
|
||||||
|
case ReasonRequestMethodDELETE:
|
||||||
|
return "ReasonRequestMethodDELETE"
|
||||||
|
case ReasonRequestMethodCONNECT:
|
||||||
|
return "ReasonRequestMethodCONNECT"
|
||||||
|
case ReasonRequestMethodOPTIONS:
|
||||||
|
return "ReasonRequestMethodOPTIONS"
|
||||||
|
case ReasonRequestMethodTRACE:
|
||||||
|
return "ReasonRequestMethodTRACE"
|
||||||
|
case ReasonRequestMethodUnkown:
|
||||||
|
return "ReasonRequestMethodUnkown"
|
||||||
|
case ReasonRequestNoStore:
|
||||||
|
return "ReasonRequestNoStore"
|
||||||
|
case ReasonRequestAuthorizationHeader:
|
||||||
|
return "ReasonRequestAuthorizationHeader"
|
||||||
|
case ReasonResponseNoStore:
|
||||||
|
return "ReasonResponseNoStore"
|
||||||
|
case ReasonResponsePrivate:
|
||||||
|
return "ReasonResponsePrivate"
|
||||||
|
case ReasonResponseUncachableByDefault:
|
||||||
|
return "ReasonResponseUncachableByDefault"
|
||||||
|
}
|
||||||
|
|
||||||
|
panic(r)
|
||||||
|
}
|
107
vendor/github.com/pquerna/cachecontrol/cacheobject/warning.go
generated
vendored
Normal file
107
vendor/github.com/pquerna/cachecontrol/cacheobject/warning.go
generated
vendored
Normal file
@ -0,0 +1,107 @@
|
|||||||
|
/**
|
||||||
|
* Copyright 2015 Paul Querna
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
package cacheobject
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Repersents an HTTP Warning: http://tools.ietf.org/html/rfc7234#section-5.5
|
||||||
|
type Warning int
|
||||||
|
|
||||||
|
const (
|
||||||
|
// Response is Stale
|
||||||
|
// A cache SHOULD generate this whenever the sent response is stale.
|
||||||
|
WarningResponseIsStale Warning = 110
|
||||||
|
|
||||||
|
// Revalidation Failed
|
||||||
|
// A cache SHOULD generate this when sending a stale
|
||||||
|
// response because an attempt to validate the response failed, due to an
|
||||||
|
// inability to reach the server.
|
||||||
|
WarningRevalidationFailed Warning = 111
|
||||||
|
|
||||||
|
// Disconnected Operation
|
||||||
|
// A cache SHOULD generate this if it is intentionally disconnected from
|
||||||
|
// the rest of the network for a period of time.
|
||||||
|
WarningDisconnectedOperation Warning = 112
|
||||||
|
|
||||||
|
// Heuristic Expiration
|
||||||
|
//
|
||||||
|
// A cache SHOULD generate this if it heuristically chose a freshness
|
||||||
|
// lifetime greater than 24 hours and the response's age is greater than
|
||||||
|
// 24 hours.
|
||||||
|
WarningHeuristicExpiration Warning = 113
|
||||||
|
|
||||||
|
// Miscellaneous Warning
|
||||||
|
//
|
||||||
|
// The warning text can include arbitrary information to be presented to
|
||||||
|
// a human user or logged. A system receiving this warning MUST NOT
|
||||||
|
// take any automated action, besides presenting the warning to the
|
||||||
|
// user.
|
||||||
|
WarningMiscellaneousWarning Warning = 199
|
||||||
|
|
||||||
|
// Transformation Applied
|
||||||
|
//
|
||||||
|
// This Warning code MUST be added by a proxy if it applies any
|
||||||
|
// transformation to the representation, such as changing the
|
||||||
|
// content-coding, media-type, or modifying the representation data,
|
||||||
|
// unless this Warning code already appears in the response.
|
||||||
|
WarningTransformationApplied Warning = 214
|
||||||
|
|
||||||
|
// Miscellaneous Persistent Warning
|
||||||
|
//
|
||||||
|
// The warning text can include arbitrary information to be presented to
|
||||||
|
// a human user or logged. A system receiving this warning MUST NOT
|
||||||
|
// take any automated action.
|
||||||
|
WarningMiscellaneousPersistentWarning Warning = 299
|
||||||
|
)
|
||||||
|
|
||||||
|
func (w Warning) HeaderString(agent string, date time.Time) string {
|
||||||
|
if agent == "" {
|
||||||
|
agent = "-"
|
||||||
|
} else {
|
||||||
|
// TODO(pquerna): this doesn't escape agent if it contains bad things.
|
||||||
|
agent = `"` + agent + `"`
|
||||||
|
}
|
||||||
|
return fmt.Sprintf(`%d %s "%s" %s`, w, agent, w.String(), date.Format(http.TimeFormat))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w Warning) String() string {
|
||||||
|
switch w {
|
||||||
|
case WarningResponseIsStale:
|
||||||
|
return "Response is Stale"
|
||||||
|
case WarningRevalidationFailed:
|
||||||
|
return "Revalidation Failed"
|
||||||
|
case WarningDisconnectedOperation:
|
||||||
|
return "Disconnected Operation"
|
||||||
|
case WarningHeuristicExpiration:
|
||||||
|
return "Heuristic Expiration"
|
||||||
|
case WarningMiscellaneousWarning:
|
||||||
|
// TODO(pquerna): ideally had a better way to override this one code.
|
||||||
|
return "Miscellaneous Warning"
|
||||||
|
case WarningTransformationApplied:
|
||||||
|
return "Transformation Applied"
|
||||||
|
case WarningMiscellaneousPersistentWarning:
|
||||||
|
// TODO(pquerna): same as WarningMiscellaneousWarning
|
||||||
|
return "Miscellaneous Persistent Warning"
|
||||||
|
}
|
||||||
|
|
||||||
|
panic(w)
|
||||||
|
}
|
25
vendor/github.com/pquerna/cachecontrol/doc.go
generated
vendored
Normal file
25
vendor/github.com/pquerna/cachecontrol/doc.go
generated
vendored
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
/**
|
||||||
|
* Copyright 2015 Paul Querna
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Package cachecontrol implements the logic for HTTP Caching
|
||||||
|
//
|
||||||
|
// Deciding if an HTTP Response can be cached is often harder
|
||||||
|
// and more bug prone than an actual cache storage backend.
|
||||||
|
// cachecontrol provides a simple interface to determine if
|
||||||
|
// request and response pairs are cachable as defined under
|
||||||
|
// RFC 7234 http://tools.ietf.org/html/rfc7234
|
||||||
|
package cachecontrol
|
Loading…
Reference in New Issue
Block a user