Upgrade project Go dependencies
Most of the changes in this commit are because of these fosite PRs which changed behavior and/or APIs in fosite: - https://github.com/ory/fosite/pull/667 - https://github.com/ory/fosite/pull/679 (from me!) - https://github.com/ory/fosite/pull/675 - https://github.com/ory/fosite/pull/688 Due to the changes in fosite PR #688, we need to bump our storage version for anything which stores the DefaultSession struct as JSON.
This commit is contained in:
parent
d35306aa85
commit
e1a0367b03
55
go.mod
55
go.mod
@ -2,21 +2,6 @@ module go.pinniped.dev
|
|||||||
|
|
||||||
go 1.17
|
go 1.17
|
||||||
|
|
||||||
// Unfortuntely, having any indirect dependency on github.com/oleiade/reflections@v1.0.0
|
|
||||||
// seems to cause Dependabot to stop scanning our dependencies due to a checksum error for the package.
|
|
||||||
// The cause of the checksum error is described in https://github.com/oleiade/reflections/issues/14.
|
|
||||||
//
|
|
||||||
// According to `go mod graph`, this dependency is (currently) coming from:
|
|
||||||
// go.pinniped.dev -> github.com/ory/x@v0.0.212 -> github.com/ory/analytics-go/v4@v4.0.0 -> github.com/ory/x@v0.0.110 -> github.com/ory/fosite@v0.29.0 -> github.com/oleiade/reflections@v1.0.0
|
|
||||||
// So the issue is that older versions of ory/x had a direct dependency on an old version of Fosite.
|
|
||||||
// Newer versions of ory/x do not depend on fosite anymore. We can use a replace directive until none
|
|
||||||
// of our indirect dependencies pull in any old versions of ory/x anymore.
|
|
||||||
//
|
|
||||||
// Whenever we upgrade fosite and ory/x, we can try removing this replace directive and running
|
|
||||||
// `go mod download` to see if github.com/oleiade/reflections@v1.0.0 still appears in our go.sum.
|
|
||||||
// As long as it does, we probably need to keep this replace directive.
|
|
||||||
replace github.com/oleiade/reflections => github.com/oleiade/reflections v1.0.1
|
|
||||||
|
|
||||||
// bumping github.com/ory/x to higher than v0.0.297 breaks k8s.io/apiserver via go.opentelemetry.io/otel/semconv
|
// bumping github.com/ory/x to higher than v0.0.297 breaks k8s.io/apiserver via go.opentelemetry.io/otel/semconv
|
||||||
// force the use of an old version for now as it seems to allow a newer ory/x without breaking the apiserver lib.
|
// force the use of an old version for now as it seems to allow a newer ory/x without breaking the apiserver lib.
|
||||||
// all go.opentelemetry.io replace directives are copied from:
|
// all go.opentelemetry.io replace directives are copied from:
|
||||||
@ -55,23 +40,23 @@ require (
|
|||||||
github.com/gorilla/websocket v1.5.0
|
github.com/gorilla/websocket v1.5.0
|
||||||
github.com/joshlf/go-acl v0.0.0-20200411065538-eae00ae38531
|
github.com/joshlf/go-acl v0.0.0-20200411065538-eae00ae38531
|
||||||
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826
|
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826
|
||||||
github.com/ory/fosite v0.42.2
|
github.com/ory/fosite v0.44.0
|
||||||
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8
|
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8
|
||||||
github.com/pkg/errors v0.9.1
|
github.com/pkg/errors v0.9.1
|
||||||
github.com/sclevine/agouti v3.0.0+incompatible
|
github.com/sclevine/agouti v3.0.0+incompatible
|
||||||
github.com/sclevine/spec v1.4.0
|
github.com/sclevine/spec v1.4.0
|
||||||
github.com/spf13/cobra v1.5.0
|
github.com/spf13/cobra v1.6.1
|
||||||
github.com/spf13/pflag v1.0.5
|
github.com/spf13/pflag v1.0.5
|
||||||
github.com/stretchr/testify v1.8.0
|
github.com/stretchr/testify v1.8.1
|
||||||
github.com/tdewolff/minify/v2 v2.12.2
|
github.com/tdewolff/minify/v2 v2.12.4
|
||||||
go.uber.org/atomic v1.10.0
|
go.uber.org/atomic v1.10.0
|
||||||
go.uber.org/zap v1.23.0
|
go.uber.org/zap v1.24.0
|
||||||
golang.org/x/crypto v0.0.0-20220919173607-35f4265a4bc0
|
golang.org/x/crypto v0.4.0
|
||||||
golang.org/x/net v0.0.0-20220923203811-8be639271d50
|
golang.org/x/net v0.4.0
|
||||||
golang.org/x/oauth2 v0.0.0-20220909003341-f21342109be1
|
golang.org/x/oauth2 v0.3.0
|
||||||
golang.org/x/sync v0.0.0-20220923202941-7f9b1623fab7
|
golang.org/x/sync v0.1.0
|
||||||
golang.org/x/term v0.0.0-20220919170432-7a66f970e087
|
golang.org/x/term v0.3.0
|
||||||
golang.org/x/text v0.3.7
|
golang.org/x/text v0.5.0
|
||||||
gopkg.in/square/go-jose.v2 v2.6.0
|
gopkg.in/square/go-jose.v2 v2.6.0
|
||||||
k8s.io/api v0.25.2
|
k8s.io/api v0.25.2
|
||||||
k8s.io/apiextensions-apiserver v0.25.2
|
k8s.io/apiextensions-apiserver v0.25.2
|
||||||
@ -79,11 +64,11 @@ require (
|
|||||||
k8s.io/apiserver v0.25.2
|
k8s.io/apiserver v0.25.2
|
||||||
k8s.io/client-go v0.25.2
|
k8s.io/client-go v0.25.2
|
||||||
k8s.io/component-base v0.25.2
|
k8s.io/component-base v0.25.2
|
||||||
k8s.io/gengo v0.0.0-20220913193501-391367153a38
|
k8s.io/gengo v0.0.0-20221011193443-fad74ee6edd9
|
||||||
k8s.io/klog/v2 v2.80.1
|
k8s.io/klog/v2 v2.80.1
|
||||||
k8s.io/kube-aggregator v0.25.2
|
k8s.io/kube-aggregator v0.25.2
|
||||||
k8s.io/kube-openapi v0.0.0-20220803164354-a70c9af30aea
|
k8s.io/kube-openapi v0.0.0-20221207184640-f3cff1453715
|
||||||
k8s.io/utils v0.0.0-20220922133306-665eaaec4324
|
k8s.io/utils v0.0.0-20221128185143-99ec85e7a448
|
||||||
sigs.k8s.io/yaml v1.3.0
|
sigs.k8s.io/yaml v1.3.0
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -105,8 +90,11 @@ require (
|
|||||||
github.com/coreos/go-semver v0.3.0 // indirect
|
github.com/coreos/go-semver v0.3.0 // indirect
|
||||||
github.com/coreos/go-systemd/v22 v22.3.2 // indirect
|
github.com/coreos/go-systemd/v22 v22.3.2 // indirect
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
|
github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
|
||||||
|
github.com/cristalhq/jwt/v4 v4.0.2 // indirect
|
||||||
|
github.com/dave/jennifer v1.4.0 // indirect
|
||||||
github.com/dgraph-io/ristretto v0.1.0 // indirect
|
github.com/dgraph-io/ristretto v0.1.0 // indirect
|
||||||
github.com/dustin/go-humanize v1.0.0 // indirect
|
github.com/dustin/go-humanize v1.0.0 // indirect
|
||||||
|
github.com/ecordell/optgen v0.0.6 // indirect
|
||||||
github.com/emicklei/go-restful/v3 v3.8.0 // indirect
|
github.com/emicklei/go-restful/v3 v3.8.0 // indirect
|
||||||
github.com/evanphx/json-patch v5.6.0+incompatible // indirect
|
github.com/evanphx/json-patch v5.6.0+incompatible // indirect
|
||||||
github.com/form3tech-oss/jwt-go v3.2.5+incompatible // indirect
|
github.com/form3tech-oss/jwt-go v3.2.5+incompatible // indirect
|
||||||
@ -123,9 +111,11 @@ require (
|
|||||||
github.com/google/gnostic v0.6.9 // indirect
|
github.com/google/gnostic v0.6.9 // indirect
|
||||||
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 // indirect
|
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 // indirect
|
||||||
github.com/grpc-ecosystem/grpc-gateway v1.16.0 // indirect
|
github.com/grpc-ecosystem/grpc-gateway v1.16.0 // indirect
|
||||||
|
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
|
||||||
|
github.com/hashicorp/go-retryablehttp v0.7.0 // indirect
|
||||||
github.com/hashicorp/hcl v1.0.0 // indirect
|
github.com/hashicorp/hcl v1.0.0 // indirect
|
||||||
github.com/imdario/mergo v0.3.13 // indirect
|
github.com/imdario/mergo v0.3.13 // indirect
|
||||||
github.com/inconshreveable/mousetrap v1.0.0 // indirect
|
github.com/inconshreveable/mousetrap v1.0.1 // indirect
|
||||||
github.com/josharian/intern v1.0.0 // indirect
|
github.com/josharian/intern v1.0.0 // indirect
|
||||||
github.com/joshlf/testutil v0.0.0-20170608050642-b5d8aa79d93d // indirect
|
github.com/joshlf/testutil v0.0.0-20170608050642-b5d8aa79d93d // indirect
|
||||||
github.com/json-iterator/go v1.1.12 // indirect
|
github.com/json-iterator/go v1.1.12 // indirect
|
||||||
@ -138,6 +128,7 @@ require (
|
|||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
||||||
|
github.com/onsi/ginkgo v1.16.5 // indirect
|
||||||
github.com/ory/go-acc v0.2.8 // indirect
|
github.com/ory/go-acc v0.2.8 // indirect
|
||||||
github.com/ory/go-convenience v0.1.0 // indirect
|
github.com/ory/go-convenience v0.1.0 // indirect
|
||||||
github.com/ory/viper v1.7.5 // indirect
|
github.com/ory/viper v1.7.5 // indirect
|
||||||
@ -155,7 +146,7 @@ require (
|
|||||||
github.com/spf13/cast v1.5.0 // indirect
|
github.com/spf13/cast v1.5.0 // indirect
|
||||||
github.com/spf13/jwalterweatherman v1.1.0 // indirect
|
github.com/spf13/jwalterweatherman v1.1.0 // indirect
|
||||||
github.com/subosito/gotenv v1.4.0 // indirect
|
github.com/subosito/gotenv v1.4.0 // indirect
|
||||||
github.com/tdewolff/parse/v2 v2.6.3 // indirect
|
github.com/tdewolff/parse/v2 v2.6.4 // indirect
|
||||||
go.etcd.io/etcd/api/v3 v3.5.4 // indirect
|
go.etcd.io/etcd/api/v3 v3.5.4 // indirect
|
||||||
go.etcd.io/etcd/client/pkg/v3 v3.5.4 // indirect
|
go.etcd.io/etcd/client/pkg/v3 v3.5.4 // indirect
|
||||||
go.etcd.io/etcd/client/v3 v3.5.4 // indirect
|
go.etcd.io/etcd/client/v3 v3.5.4 // indirect
|
||||||
@ -172,7 +163,7 @@ require (
|
|||||||
go.opentelemetry.io/proto/otlp v0.15.0 // indirect
|
go.opentelemetry.io/proto/otlp v0.15.0 // indirect
|
||||||
go.uber.org/multierr v1.8.0 // indirect
|
go.uber.org/multierr v1.8.0 // indirect
|
||||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 // indirect
|
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 // indirect
|
||||||
golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10 // indirect
|
golang.org/x/sys v0.3.0 // indirect
|
||||||
golang.org/x/time v0.0.0-20220411224347-583f2d630306 // indirect
|
golang.org/x/time v0.0.0-20220411224347-583f2d630306 // indirect
|
||||||
golang.org/x/tools v0.1.12 // indirect
|
golang.org/x/tools v0.1.12 // indirect
|
||||||
google.golang.org/appengine v1.6.7 // indirect
|
google.golang.org/appengine v1.6.7 // indirect
|
||||||
|
@ -263,7 +263,7 @@ func TestGarbageCollectorControllerSync(t *testing.T) {
|
|||||||
when("there are valid, expired authcode secrets which contain upstream refresh tokens", func() {
|
when("there are valid, expired authcode secrets which contain upstream refresh tokens", func() {
|
||||||
it.Before(func() {
|
it.Before(func() {
|
||||||
activeOIDCAuthcodeSession := &authorizationcode.Session{
|
activeOIDCAuthcodeSession := &authorizationcode.Session{
|
||||||
Version: "3",
|
Version: "4",
|
||||||
Active: true,
|
Active: true,
|
||||||
Request: &fosite.Request{
|
Request: &fosite.Request{
|
||||||
ID: "request-id-1",
|
ID: "request-id-1",
|
||||||
@ -308,7 +308,7 @@ func TestGarbageCollectorControllerSync(t *testing.T) {
|
|||||||
r.NoError(kubeClient.Tracker().Add(activeOIDCAuthcodeSessionSecret))
|
r.NoError(kubeClient.Tracker().Add(activeOIDCAuthcodeSessionSecret))
|
||||||
|
|
||||||
inactiveOIDCAuthcodeSession := &authorizationcode.Session{
|
inactiveOIDCAuthcodeSession := &authorizationcode.Session{
|
||||||
Version: "3",
|
Version: "4",
|
||||||
Active: false,
|
Active: false,
|
||||||
Request: &fosite.Request{
|
Request: &fosite.Request{
|
||||||
ID: "request-id-2",
|
ID: "request-id-2",
|
||||||
@ -387,7 +387,7 @@ func TestGarbageCollectorControllerSync(t *testing.T) {
|
|||||||
when("there are valid, expired authcode secrets which contain upstream access tokens", func() {
|
when("there are valid, expired authcode secrets which contain upstream access tokens", func() {
|
||||||
it.Before(func() {
|
it.Before(func() {
|
||||||
activeOIDCAuthcodeSession := &authorizationcode.Session{
|
activeOIDCAuthcodeSession := &authorizationcode.Session{
|
||||||
Version: "3",
|
Version: "4",
|
||||||
Active: true,
|
Active: true,
|
||||||
Request: &fosite.Request{
|
Request: &fosite.Request{
|
||||||
ID: "request-id-1",
|
ID: "request-id-1",
|
||||||
@ -432,7 +432,7 @@ func TestGarbageCollectorControllerSync(t *testing.T) {
|
|||||||
r.NoError(kubeClient.Tracker().Add(activeOIDCAuthcodeSessionSecret))
|
r.NoError(kubeClient.Tracker().Add(activeOIDCAuthcodeSessionSecret))
|
||||||
|
|
||||||
inactiveOIDCAuthcodeSession := &authorizationcode.Session{
|
inactiveOIDCAuthcodeSession := &authorizationcode.Session{
|
||||||
Version: "3",
|
Version: "4",
|
||||||
Active: false,
|
Active: false,
|
||||||
Request: &fosite.Request{
|
Request: &fosite.Request{
|
||||||
ID: "request-id-2",
|
ID: "request-id-2",
|
||||||
@ -511,7 +511,7 @@ func TestGarbageCollectorControllerSync(t *testing.T) {
|
|||||||
when("there is an invalid, expired authcode secret", func() {
|
when("there is an invalid, expired authcode secret", func() {
|
||||||
it.Before(func() {
|
it.Before(func() {
|
||||||
invalidOIDCAuthcodeSession := &authorizationcode.Session{
|
invalidOIDCAuthcodeSession := &authorizationcode.Session{
|
||||||
Version: "3",
|
Version: "4",
|
||||||
Active: true,
|
Active: true,
|
||||||
Request: &fosite.Request{
|
Request: &fosite.Request{
|
||||||
ID: "", // it is invalid for there to be a missing request ID
|
ID: "", // it is invalid for there to be a missing request ID
|
||||||
@ -580,7 +580,7 @@ func TestGarbageCollectorControllerSync(t *testing.T) {
|
|||||||
when("there is a valid, expired authcode secret but its upstream name does not match any existing upstream", func() {
|
when("there is a valid, expired authcode secret but its upstream name does not match any existing upstream", func() {
|
||||||
it.Before(func() {
|
it.Before(func() {
|
||||||
wrongProviderNameOIDCAuthcodeSession := &authorizationcode.Session{
|
wrongProviderNameOIDCAuthcodeSession := &authorizationcode.Session{
|
||||||
Version: "3",
|
Version: "4",
|
||||||
Active: true,
|
Active: true,
|
||||||
Request: &fosite.Request{
|
Request: &fosite.Request{
|
||||||
ID: "request-id-1",
|
ID: "request-id-1",
|
||||||
@ -651,7 +651,7 @@ func TestGarbageCollectorControllerSync(t *testing.T) {
|
|||||||
when("there is a valid, expired authcode secret but its upstream UID does not match any existing upstream", func() {
|
when("there is a valid, expired authcode secret but its upstream UID does not match any existing upstream", func() {
|
||||||
it.Before(func() {
|
it.Before(func() {
|
||||||
wrongProviderNameOIDCAuthcodeSession := &authorizationcode.Session{
|
wrongProviderNameOIDCAuthcodeSession := &authorizationcode.Session{
|
||||||
Version: "3",
|
Version: "4",
|
||||||
Active: true,
|
Active: true,
|
||||||
Request: &fosite.Request{
|
Request: &fosite.Request{
|
||||||
ID: "request-id-1",
|
ID: "request-id-1",
|
||||||
@ -722,7 +722,7 @@ func TestGarbageCollectorControllerSync(t *testing.T) {
|
|||||||
when("there is a valid, recently expired authcode secret but the upstream revocation fails", func() {
|
when("there is a valid, recently expired authcode secret but the upstream revocation fails", func() {
|
||||||
it.Before(func() {
|
it.Before(func() {
|
||||||
activeOIDCAuthcodeSession := &authorizationcode.Session{
|
activeOIDCAuthcodeSession := &authorizationcode.Session{
|
||||||
Version: "3",
|
Version: "4",
|
||||||
Active: true,
|
Active: true,
|
||||||
Request: &fosite.Request{
|
Request: &fosite.Request{
|
||||||
ID: "request-id-1",
|
ID: "request-id-1",
|
||||||
@ -827,7 +827,7 @@ func TestGarbageCollectorControllerSync(t *testing.T) {
|
|||||||
when("there is a valid, long-since expired authcode secret but the upstream revocation fails", func() {
|
when("there is a valid, long-since expired authcode secret but the upstream revocation fails", func() {
|
||||||
it.Before(func() {
|
it.Before(func() {
|
||||||
activeOIDCAuthcodeSession := &authorizationcode.Session{
|
activeOIDCAuthcodeSession := &authorizationcode.Session{
|
||||||
Version: "3",
|
Version: "4",
|
||||||
Active: true,
|
Active: true,
|
||||||
Request: &fosite.Request{
|
Request: &fosite.Request{
|
||||||
ID: "request-id-1",
|
ID: "request-id-1",
|
||||||
@ -906,7 +906,7 @@ func TestGarbageCollectorControllerSync(t *testing.T) {
|
|||||||
when("there are valid, expired access token secrets which contain upstream refresh tokens", func() {
|
when("there are valid, expired access token secrets which contain upstream refresh tokens", func() {
|
||||||
it.Before(func() {
|
it.Before(func() {
|
||||||
offlineAccessGrantedOIDCAccessTokenSession := &accesstoken.Session{
|
offlineAccessGrantedOIDCAccessTokenSession := &accesstoken.Session{
|
||||||
Version: "3",
|
Version: "4",
|
||||||
Request: &fosite.Request{
|
Request: &fosite.Request{
|
||||||
GrantedScope: fosite.Arguments{"scope1", "scope2", "offline_access"},
|
GrantedScope: fosite.Arguments{"scope1", "scope2", "offline_access"},
|
||||||
ID: "request-id-1",
|
ID: "request-id-1",
|
||||||
@ -951,7 +951,7 @@ func TestGarbageCollectorControllerSync(t *testing.T) {
|
|||||||
r.NoError(kubeClient.Tracker().Add(offlineAccessGrantedOIDCAccessTokenSessionSecret))
|
r.NoError(kubeClient.Tracker().Add(offlineAccessGrantedOIDCAccessTokenSessionSecret))
|
||||||
|
|
||||||
offlineAccessNotGrantedOIDCAccessTokenSession := &accesstoken.Session{
|
offlineAccessNotGrantedOIDCAccessTokenSession := &accesstoken.Session{
|
||||||
Version: "3",
|
Version: "4",
|
||||||
Request: &fosite.Request{
|
Request: &fosite.Request{
|
||||||
GrantedScope: fosite.Arguments{"scope1", "scope2"},
|
GrantedScope: fosite.Arguments{"scope1", "scope2"},
|
||||||
ID: "request-id-2",
|
ID: "request-id-2",
|
||||||
@ -1030,7 +1030,7 @@ func TestGarbageCollectorControllerSync(t *testing.T) {
|
|||||||
when("there are valid, expired access token secrets which contain upstream access tokens", func() {
|
when("there are valid, expired access token secrets which contain upstream access tokens", func() {
|
||||||
it.Before(func() {
|
it.Before(func() {
|
||||||
offlineAccessGrantedOIDCAccessTokenSession := &accesstoken.Session{
|
offlineAccessGrantedOIDCAccessTokenSession := &accesstoken.Session{
|
||||||
Version: "3",
|
Version: "4",
|
||||||
Request: &fosite.Request{
|
Request: &fosite.Request{
|
||||||
GrantedScope: fosite.Arguments{"scope1", "scope2", "offline_access"},
|
GrantedScope: fosite.Arguments{"scope1", "scope2", "offline_access"},
|
||||||
ID: "request-id-1",
|
ID: "request-id-1",
|
||||||
@ -1075,7 +1075,7 @@ func TestGarbageCollectorControllerSync(t *testing.T) {
|
|||||||
r.NoError(kubeClient.Tracker().Add(offlineAccessGrantedOIDCAccessTokenSessionSecret))
|
r.NoError(kubeClient.Tracker().Add(offlineAccessGrantedOIDCAccessTokenSessionSecret))
|
||||||
|
|
||||||
offlineAccessNotGrantedOIDCAccessTokenSession := &accesstoken.Session{
|
offlineAccessNotGrantedOIDCAccessTokenSession := &accesstoken.Session{
|
||||||
Version: "3",
|
Version: "4",
|
||||||
Request: &fosite.Request{
|
Request: &fosite.Request{
|
||||||
GrantedScope: fosite.Arguments{"scope1", "scope2"},
|
GrantedScope: fosite.Arguments{"scope1", "scope2"},
|
||||||
ID: "request-id-2",
|
ID: "request-id-2",
|
||||||
@ -1154,7 +1154,7 @@ func TestGarbageCollectorControllerSync(t *testing.T) {
|
|||||||
when("there are valid, expired refresh secrets which contain upstream refresh tokens", func() {
|
when("there are valid, expired refresh secrets which contain upstream refresh tokens", func() {
|
||||||
it.Before(func() {
|
it.Before(func() {
|
||||||
oidcRefreshSession := &refreshtoken.Session{
|
oidcRefreshSession := &refreshtoken.Session{
|
||||||
Version: "3",
|
Version: "4",
|
||||||
Request: &fosite.Request{
|
Request: &fosite.Request{
|
||||||
ID: "request-id-1",
|
ID: "request-id-1",
|
||||||
Client: &clientregistry.Client{},
|
Client: &clientregistry.Client{},
|
||||||
@ -1231,7 +1231,7 @@ func TestGarbageCollectorControllerSync(t *testing.T) {
|
|||||||
when("there are valid, expired refresh secrets which contain upstream access tokens", func() {
|
when("there are valid, expired refresh secrets which contain upstream access tokens", func() {
|
||||||
it.Before(func() {
|
it.Before(func() {
|
||||||
oidcRefreshSession := &refreshtoken.Session{
|
oidcRefreshSession := &refreshtoken.Session{
|
||||||
Version: "3",
|
Version: "4",
|
||||||
Request: &fosite.Request{
|
Request: &fosite.Request{
|
||||||
ID: "request-id-1",
|
ID: "request-id-1",
|
||||||
Client: &clientregistry.Client{},
|
Client: &clientregistry.Client{},
|
||||||
|
@ -10,6 +10,7 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/ory/fosite"
|
||||||
"github.com/ory/fosite/compose"
|
"github.com/ory/fosite/compose"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
corev1 "k8s.io/api/core/v1"
|
corev1 "k8s.io/api/core/v1"
|
||||||
@ -42,7 +43,7 @@ func TestStorage(t *testing.T) {
|
|||||||
Tracker() coretesting.ObjectTracker
|
Tracker() coretesting.ObjectTracker
|
||||||
}
|
}
|
||||||
|
|
||||||
hmac := compose.NewOAuth2HMACStrategy(&compose.Config{}, []byte("super-secret-32-byte-for-testing"), nil)
|
hmac := compose.NewOAuth2HMACStrategy(&fosite.Config{GlobalSecret: []byte("super-secret-32-byte-for-testing")})
|
||||||
// test data generation via:
|
// test data generation via:
|
||||||
// code, signature, err := hmac.GenerateAuthorizeCode(ctx, nil)
|
// code, signature, err := hmac.GenerateAuthorizeCode(ctx, nil)
|
||||||
|
|
||||||
@ -117,7 +118,7 @@ func TestStorage(t *testing.T) {
|
|||||||
resource: "access-tokens",
|
resource: "access-tokens",
|
||||||
mocks: nil,
|
mocks: nil,
|
||||||
run: func(t *testing.T, storage Storage, fakeClock *clocktesting.FakeClock) error {
|
run: func(t *testing.T, storage Storage, fakeClock *clocktesting.FakeClock) error {
|
||||||
signature := hmac.AuthorizeCodeSignature(authorizationCode1)
|
signature := hmac.AuthorizeCodeSignature(context.Background(), authorizationCode1)
|
||||||
require.NotEmpty(t, signature)
|
require.NotEmpty(t, signature)
|
||||||
require.NotEmpty(t, validateSecretName(signature, false)) // signature is not valid secret name as-is
|
require.NotEmpty(t, validateSecretName(signature, false)) // signature is not valid secret name as-is
|
||||||
|
|
||||||
@ -288,7 +289,7 @@ func TestStorage(t *testing.T) {
|
|||||||
})
|
})
|
||||||
},
|
},
|
||||||
run: func(t *testing.T, storage Storage, fakeClock *clocktesting.FakeClock) error {
|
run: func(t *testing.T, storage Storage, fakeClock *clocktesting.FakeClock) error {
|
||||||
signature := hmac.AuthorizeCodeSignature(authorizationCode1)
|
signature := hmac.AuthorizeCodeSignature(context.Background(), authorizationCode1)
|
||||||
require.NotEmpty(t, signature)
|
require.NotEmpty(t, signature)
|
||||||
require.NotEmpty(t, validateSecretName(signature, false)) // signature is not valid secret name as-is
|
require.NotEmpty(t, validateSecretName(signature, false)) // signature is not valid secret name as-is
|
||||||
|
|
||||||
@ -424,7 +425,7 @@ func TestStorage(t *testing.T) {
|
|||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
},
|
},
|
||||||
run: func(t *testing.T, storage Storage, fakeClock *clocktesting.FakeClock) error {
|
run: func(t *testing.T, storage Storage, fakeClock *clocktesting.FakeClock) error {
|
||||||
signature := hmac.AuthorizeCodeSignature(authorizationCode2)
|
signature := hmac.AuthorizeCodeSignature(context.Background(), authorizationCode2)
|
||||||
require.NotEmpty(t, signature)
|
require.NotEmpty(t, signature)
|
||||||
require.NotEmpty(t, validateSecretName(signature, false)) // signature is not valid secret name as-is
|
require.NotEmpty(t, validateSecretName(signature, false)) // signature is not valid secret name as-is
|
||||||
|
|
||||||
@ -493,7 +494,7 @@ func TestStorage(t *testing.T) {
|
|||||||
})
|
})
|
||||||
},
|
},
|
||||||
run: func(t *testing.T, storage Storage, fakeClock *clocktesting.FakeClock) error {
|
run: func(t *testing.T, storage Storage, fakeClock *clocktesting.FakeClock) error {
|
||||||
signature := hmac.AuthorizeCodeSignature(authorizationCode3)
|
signature := hmac.AuthorizeCodeSignature(context.Background(), authorizationCode3)
|
||||||
require.NotEmpty(t, signature)
|
require.NotEmpty(t, signature)
|
||||||
require.NotEmpty(t, validateSecretName(signature, false)) // signature is not valid secret name as-is
|
require.NotEmpty(t, validateSecretName(signature, false)) // signature is not valid secret name as-is
|
||||||
|
|
||||||
@ -593,7 +594,7 @@ func TestStorage(t *testing.T) {
|
|||||||
})
|
})
|
||||||
},
|
},
|
||||||
run: func(t *testing.T, storage Storage, fakeClock *clocktesting.FakeClock) error {
|
run: func(t *testing.T, storage Storage, fakeClock *clocktesting.FakeClock) error {
|
||||||
signature := hmac.AuthorizeCodeSignature(authorizationCode3)
|
signature := hmac.AuthorizeCodeSignature(context.Background(), authorizationCode3)
|
||||||
require.NotEmpty(t, signature)
|
require.NotEmpty(t, signature)
|
||||||
require.NotEmpty(t, validateSecretName(signature, false)) // signature is not valid secret name as-is
|
require.NotEmpty(t, validateSecretName(signature, false)) // signature is not valid secret name as-is
|
||||||
|
|
||||||
@ -672,7 +673,7 @@ func TestStorage(t *testing.T) {
|
|||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
},
|
},
|
||||||
run: func(t *testing.T, storage Storage, fakeClock *clocktesting.FakeClock) error {
|
run: func(t *testing.T, storage Storage, fakeClock *clocktesting.FakeClock) error {
|
||||||
signature := hmac.AuthorizeCodeSignature(authorizationCode2)
|
signature := hmac.AuthorizeCodeSignature(context.Background(), authorizationCode2)
|
||||||
require.NotEmpty(t, signature)
|
require.NotEmpty(t, signature)
|
||||||
require.NotEmpty(t, validateSecretName(signature, false)) // signature is not valid secret name as-is
|
require.NotEmpty(t, validateSecretName(signature, false)) // signature is not valid secret name as-is
|
||||||
|
|
||||||
@ -934,7 +935,7 @@ func TestStorage(t *testing.T) {
|
|||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
},
|
},
|
||||||
run: func(t *testing.T, storage Storage, fakeClock *clocktesting.FakeClock) error {
|
run: func(t *testing.T, storage Storage, fakeClock *clocktesting.FakeClock) error {
|
||||||
signature := hmac.AuthorizeCodeSignature(authorizationCode3)
|
signature := hmac.AuthorizeCodeSignature(context.Background(), authorizationCode3)
|
||||||
require.NotEmpty(t, signature)
|
require.NotEmpty(t, signature)
|
||||||
require.NotEmpty(t, validateSecretName(signature, false)) // signature is not valid secret name as-is
|
require.NotEmpty(t, validateSecretName(signature, false)) // signature is not valid secret name as-is
|
||||||
|
|
||||||
@ -998,7 +999,7 @@ func TestStorage(t *testing.T) {
|
|||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
},
|
},
|
||||||
run: func(t *testing.T, storage Storage, fakeClock *clocktesting.FakeClock) error {
|
run: func(t *testing.T, storage Storage, fakeClock *clocktesting.FakeClock) error {
|
||||||
signature := hmac.AuthorizeCodeSignature(authorizationCode3)
|
signature := hmac.AuthorizeCodeSignature(context.Background(), authorizationCode3)
|
||||||
require.NotEmpty(t, signature)
|
require.NotEmpty(t, signature)
|
||||||
require.NotEmpty(t, validateSecretName(signature, false)) // signature is not valid secret name as-is
|
require.NotEmpty(t, validateSecretName(signature, false)) // signature is not valid secret name as-is
|
||||||
|
|
||||||
@ -1062,7 +1063,7 @@ func TestStorage(t *testing.T) {
|
|||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
},
|
},
|
||||||
run: func(t *testing.T, storage Storage, fakeClock *clocktesting.FakeClock) error {
|
run: func(t *testing.T, storage Storage, fakeClock *clocktesting.FakeClock) error {
|
||||||
signature := hmac.AuthorizeCodeSignature(authorizationCode3)
|
signature := hmac.AuthorizeCodeSignature(context.Background(), authorizationCode3)
|
||||||
require.NotEmpty(t, signature)
|
require.NotEmpty(t, signature)
|
||||||
require.NotEmpty(t, validateSecretName(signature, false)) // signature is not valid secret name as-is
|
require.NotEmpty(t, validateSecretName(signature, false)) // signature is not valid secret name as-is
|
||||||
|
|
||||||
@ -1126,7 +1127,7 @@ func TestStorage(t *testing.T) {
|
|||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
},
|
},
|
||||||
run: func(t *testing.T, storage Storage, fakeClock *clocktesting.FakeClock) error {
|
run: func(t *testing.T, storage Storage, fakeClock *clocktesting.FakeClock) error {
|
||||||
signature := hmac.AuthorizeCodeSignature(authorizationCode3)
|
signature := hmac.AuthorizeCodeSignature(context.Background(), authorizationCode3)
|
||||||
require.NotEmpty(t, signature)
|
require.NotEmpty(t, signature)
|
||||||
require.NotEmpty(t, validateSecretName(signature, false)) // signature is not valid secret name as-is
|
require.NotEmpty(t, validateSecretName(signature, false)) // signature is not valid secret name as-is
|
||||||
|
|
||||||
@ -1170,7 +1171,7 @@ func TestStorage(t *testing.T) {
|
|||||||
mocks: nil,
|
mocks: nil,
|
||||||
lifetime: func() time.Duration { return 0 }, // 0 == infinity
|
lifetime: func() time.Duration { return 0 }, // 0 == infinity
|
||||||
run: func(t *testing.T, storage Storage, fakeClock *clocktesting.FakeClock) error {
|
run: func(t *testing.T, storage Storage, fakeClock *clocktesting.FakeClock) error {
|
||||||
signature := hmac.AuthorizeCodeSignature(authorizationCode1)
|
signature := hmac.AuthorizeCodeSignature(context.Background(), authorizationCode1)
|
||||||
require.NotEmpty(t, signature)
|
require.NotEmpty(t, signature)
|
||||||
require.NotEmpty(t, validateSecretName(signature, false)) // signature is not valid secret name as-is
|
require.NotEmpty(t, validateSecretName(signature, false)) // signature is not valid secret name as-is
|
||||||
|
|
||||||
@ -1232,7 +1233,7 @@ func TestStorage(t *testing.T) {
|
|||||||
mocks: nil,
|
mocks: nil,
|
||||||
lifetime: func() time.Duration { return 0 }, // 0 == infinity
|
lifetime: func() time.Duration { return 0 }, // 0 == infinity
|
||||||
run: func(t *testing.T, storage Storage, fakeClock *clocktesting.FakeClock) error {
|
run: func(t *testing.T, storage Storage, fakeClock *clocktesting.FakeClock) error {
|
||||||
signature := hmac.AuthorizeCodeSignature(authorizationCode1)
|
signature := hmac.AuthorizeCodeSignature(context.Background(), authorizationCode1)
|
||||||
require.NotEmpty(t, signature)
|
require.NotEmpty(t, signature)
|
||||||
require.NotEmpty(t, validateSecretName(signature, false)) // signature is not valid secret name as-is
|
require.NotEmpty(t, validateSecretName(signature, false)) // signature is not valid secret name as-is
|
||||||
|
|
||||||
|
@ -30,7 +30,8 @@ const (
|
|||||||
// Version 1 was the initial release of storage.
|
// Version 1 was the initial release of storage.
|
||||||
// Version 2 is when we switched to storing psession.PinnipedSession inside the fosite request.
|
// Version 2 is when we switched to storing psession.PinnipedSession inside the fosite request.
|
||||||
// Version 3 is when we added the Username field to the psession.CustomSessionData.
|
// Version 3 is when we added the Username field to the psession.CustomSessionData.
|
||||||
accessTokenStorageVersion = "3"
|
// Version 4 is when fosite added json tags to their openid.DefaultSession struct.
|
||||||
|
accessTokenStorageVersion = "4"
|
||||||
)
|
)
|
||||||
|
|
||||||
type RevocationStorage interface {
|
type RevocationStorage interface {
|
||||||
|
@ -11,6 +11,7 @@ import (
|
|||||||
|
|
||||||
"github.com/ory/fosite"
|
"github.com/ory/fosite"
|
||||||
"github.com/ory/fosite/handler/openid"
|
"github.com/ory/fosite/handler/openid"
|
||||||
|
"github.com/ory/fosite/token/jwt"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
corev1 "k8s.io/api/core/v1"
|
corev1 "k8s.io/api/core/v1"
|
||||||
@ -53,7 +54,7 @@ func TestAccessTokenStorage(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
Data: map[string][]byte{
|
Data: map[string][]byte{
|
||||||
"pinniped-storage-data": []byte(`{"request":{"id":"abcd-1","requestedAt":"0001-01-01T00:00:00Z","client":{"id":"pinny","redirect_uris":null,"grant_types":null,"response_types":null,"scopes":null,"audience":null,"public":true,"jwks_uri":"where","jwks":null,"token_endpoint_auth_method":"something","request_uris":null,"request_object_signing_alg":"","token_endpoint_auth_signing_alg":""},"scopes":null,"grantedScopes":null,"form":{"key":["val"]},"session":{"fosite":{"Claims":null,"Headers":null,"ExpiresAt":null,"Username":"snorlax","Subject":"panda"},"custom":{"username":"fake-username","providerUID":"fake-provider-uid","providerName":"fake-provider-name","providerType":"fake-provider-type","warnings":null,"oidc":{"upstreamRefreshToken":"fake-upstream-refresh-token","upstreamAccessToken":"","upstreamSubject":"some-subject","upstreamIssuer":"some-issuer"}}},"requestedAudience":null,"grantedAudience":null},"version":"3"}`),
|
"pinniped-storage-data": []byte(`{"request":{"id":"abcd-1","requestedAt":"0001-01-01T00:00:00Z","client":{"id":"pinny","redirect_uris":null,"grant_types":null,"response_types":null,"scopes":null,"audience":null,"public":true,"jwks_uri":"where","jwks":null,"token_endpoint_auth_method":"something","request_uris":null,"request_object_signing_alg":"","token_endpoint_auth_signing_alg":""},"scopes":null,"grantedScopes":null,"form":{"key":["val"]},"session":{"fosite":{"id_token_claims":null,"headers":null,"expires_at":null,"username":"snorlax","subject":"panda"},"custom":{"username":"fake-username","providerUID":"fake-provider-uid","providerName":"fake-provider-name","providerType":"fake-provider-type","warnings":null,"oidc":{"upstreamRefreshToken":"fake-upstream-refresh-token","upstreamAccessToken":"","upstreamSubject":"some-subject","upstreamIssuer":"some-issuer"}}},"requestedAudience":null,"grantedAudience":null},"version":"4"}`),
|
||||||
"pinniped-storage-version": []byte("1"),
|
"pinniped-storage-version": []byte("1"),
|
||||||
},
|
},
|
||||||
Type: "storage.pinniped.dev/access-token",
|
Type: "storage.pinniped.dev/access-token",
|
||||||
@ -122,7 +123,7 @@ func TestAccessTokenStorageRevocation(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
Data: map[string][]byte{
|
Data: map[string][]byte{
|
||||||
"pinniped-storage-data": []byte(`{"request":{"id":"abcd-1","requestedAt":"0001-01-01T00:00:00Z","client":{"id":"pinny","redirect_uris":null,"grant_types":null,"response_types":null,"scopes":null,"audience":null,"public":true,"jwks_uri":"where","jwks":null,"token_endpoint_auth_method":"something","request_uris":null,"request_object_signing_alg":"","token_endpoint_auth_signing_alg":""},"scopes":null,"grantedScopes":null,"form":{"key":["val"]},"session":{"fosite":{"Claims":null,"Headers":null,"ExpiresAt":null,"Username":"snorlax","Subject":"panda"},"custom":{"username":"fake-username","providerUID":"fake-provider-uid","providerName":"fake-provider-name","providerType":"fake-provider-type","warnings":null,"oidc":{"upstreamRefreshToken":"fake-upstream-refresh-token","upstreamAccessToken":"","upstreamSubject":"some-subject","upstreamIssuer":"some-issuer"}}},"requestedAudience":null,"grantedAudience":null},"version":"3"}`),
|
"pinniped-storage-data": []byte(`{"request":{"id":"abcd-1","requestedAt":"0001-01-01T00:00:00Z","client":{"id":"pinny","redirect_uris":null,"grant_types":null,"response_types":null,"scopes":null,"audience":null,"public":true,"jwks_uri":"where","jwks":null,"token_endpoint_auth_method":"something","request_uris":null,"request_object_signing_alg":"","token_endpoint_auth_signing_alg":""},"scopes":null,"grantedScopes":null,"form":{"key":["val"]},"session":{"fosite":{"id_token_claims":null,"headers":null,"expires_at":null,"username":"snorlax","subject":"panda"},"custom":{"username":"fake-username","providerUID":"fake-provider-uid","providerName":"fake-provider-name","providerType":"fake-provider-type","warnings":null,"oidc":{"upstreamRefreshToken":"fake-upstream-refresh-token","upstreamAccessToken":"","upstreamSubject":"some-subject","upstreamIssuer":"some-issuer"}}},"requestedAudience":null,"grantedAudience":null},"version":"4"}`),
|
||||||
"pinniped-storage-version": []byte("1"),
|
"pinniped-storage-version": []byte("1"),
|
||||||
},
|
},
|
||||||
Type: "storage.pinniped.dev/access-token",
|
Type: "storage.pinniped.dev/access-token",
|
||||||
@ -195,7 +196,7 @@ func TestWrongVersion(t *testing.T) {
|
|||||||
|
|
||||||
_, err = storage.GetAccessTokenSession(ctx, "fancy-signature", nil)
|
_, err = storage.GetAccessTokenSession(ctx, "fancy-signature", nil)
|
||||||
|
|
||||||
require.EqualError(t, err, "access token request data has wrong version: access token session for fancy-signature has version not-the-right-version instead of 3")
|
require.EqualError(t, err, "access token request data has wrong version: access token session for fancy-signature has version not-the-right-version instead of 4")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestNilSessionRequest(t *testing.T) {
|
func TestNilSessionRequest(t *testing.T) {
|
||||||
@ -213,7 +214,7 @@ func TestNilSessionRequest(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
Data: map[string][]byte{
|
Data: map[string][]byte{
|
||||||
"pinniped-storage-data": []byte(`{"nonsense-key": "nonsense-value","version":"3"}`),
|
"pinniped-storage-data": []byte(`{"nonsense-key": "nonsense-value","version":"4"}`),
|
||||||
"pinniped-storage-version": []byte("1"),
|
"pinniped-storage-version": []byte("1"),
|
||||||
},
|
},
|
||||||
Type: "storage.pinniped.dev/access-token",
|
Type: "storage.pinniped.dev/access-token",
|
||||||
@ -297,13 +298,13 @@ func TestReadFromSecret(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
Data: map[string][]byte{
|
Data: map[string][]byte{
|
||||||
"pinniped-storage-data": []byte(`{"request":{"id":"abcd-1","session":{"fosite":{"Claims":null,"Headers":null,"ExpiresAt":null,"Username":"snorlax","Subject":"panda"},"custom":{"username":"fake-username","providerUID":"fake-provider-uid","providerName":"fake-provider-name","providerType":"fake-provider-type","oidc":{"upstreamRefreshToken":"fake-upstream-refresh-token"}}}},"version":"3","active": true}`),
|
"pinniped-storage-data": []byte(`{"request":{"id":"abcd-1","session":{"fosite":{"id_token_claims":{"jti": "xyz"},"headers":{"extra":{"myheader": "foo"}},"expires_at":null,"username":"snorlax","subject":"panda"},"custom":{"username":"fake-username","providerUID":"fake-provider-uid","providerName":"fake-provider-name","providerType":"fake-provider-type","oidc":{"upstreamRefreshToken":"fake-upstream-refresh-token"}}}},"version":"4","active": true}`),
|
||||||
"pinniped-storage-version": []byte("1"),
|
"pinniped-storage-version": []byte("1"),
|
||||||
},
|
},
|
||||||
Type: "storage.pinniped.dev/access-token",
|
Type: "storage.pinniped.dev/access-token",
|
||||||
},
|
},
|
||||||
wantSession: &Session{
|
wantSession: &Session{
|
||||||
Version: "3",
|
Version: "4",
|
||||||
Request: &fosite.Request{
|
Request: &fosite.Request{
|
||||||
ID: "abcd-1",
|
ID: "abcd-1",
|
||||||
Client: &clientregistry.Client{},
|
Client: &clientregistry.Client{},
|
||||||
@ -311,6 +312,8 @@ func TestReadFromSecret(t *testing.T) {
|
|||||||
Fosite: &openid.DefaultSession{
|
Fosite: &openid.DefaultSession{
|
||||||
Username: "snorlax",
|
Username: "snorlax",
|
||||||
Subject: "panda",
|
Subject: "panda",
|
||||||
|
Claims: &jwt.IDTokenClaims{JTI: "xyz"},
|
||||||
|
Headers: &jwt.Headers{Extra: map[string]interface{}{"myheader": "foo"}},
|
||||||
},
|
},
|
||||||
Custom: &psession.CustomSessionData{
|
Custom: &psession.CustomSessionData{
|
||||||
Username: "fake-username",
|
Username: "fake-username",
|
||||||
@ -359,7 +362,7 @@ func TestReadFromSecret(t *testing.T) {
|
|||||||
},
|
},
|
||||||
Type: "storage.pinniped.dev/access-token",
|
Type: "storage.pinniped.dev/access-token",
|
||||||
},
|
},
|
||||||
wantErr: "access token request data has wrong version: access token session has version wrong-version-here instead of 3",
|
wantErr: "access token request data has wrong version: access token session has version wrong-version-here instead of 4",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "missing request",
|
name: "missing request",
|
||||||
@ -372,7 +375,7 @@ func TestReadFromSecret(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
Data: map[string][]byte{
|
Data: map[string][]byte{
|
||||||
"pinniped-storage-data": []byte(`{"version":"3","active": true}`),
|
"pinniped-storage-data": []byte(`{"version":"4","active": true}`),
|
||||||
"pinniped-storage-version": []byte("1"),
|
"pinniped-storage-version": []byte("1"),
|
||||||
},
|
},
|
||||||
Type: "storage.pinniped.dev/access-token",
|
Type: "storage.pinniped.dev/access-token",
|
||||||
|
@ -31,7 +31,8 @@ const (
|
|||||||
// Version 1 was the initial release of storage.
|
// Version 1 was the initial release of storage.
|
||||||
// Version 2 is when we switched to storing psession.PinnipedSession inside the fosite request.
|
// Version 2 is when we switched to storing psession.PinnipedSession inside the fosite request.
|
||||||
// Version 3 is when we added the Username field to the psession.CustomSessionData.
|
// Version 3 is when we added the Username field to the psession.CustomSessionData.
|
||||||
authorizeCodeStorageVersion = "3"
|
// Version 4 is when fosite added json tags to their openid.DefaultSession struct.
|
||||||
|
authorizeCodeStorageVersion = "4"
|
||||||
)
|
)
|
||||||
|
|
||||||
var _ oauth2.AuthorizeCodeStorage = &authorizeCodeStorage{}
|
var _ oauth2.AuthorizeCodeStorage = &authorizeCodeStorage{}
|
||||||
@ -314,25 +315,25 @@ const ExpectedAuthorizeCodeSessionJSONFromFuzzing = `{
|
|||||||
},
|
},
|
||||||
"session": {
|
"session": {
|
||||||
"fosite": {
|
"fosite": {
|
||||||
"Claims": {
|
"id_token_claims": {
|
||||||
"JTI": "褗6巽ēđų蓼tùZ蛆鬣a\"ÙǞ0觢",
|
"jti": "褗6巽ēđų蓼tùZ蛆鬣a\"ÙǞ0觢",
|
||||||
"Issuer": "j¦鲶H股ƲLŋZ-{",
|
"iss": "j¦鲶H股ƲLŋZ-{",
|
||||||
"Subject": "ehpƧ蓟",
|
"sub": "ehpƧ蓟",
|
||||||
"Audience": [
|
"aud": [
|
||||||
"驜Ŗ~ů崧軒q腟u尿宲!"
|
"驜Ŗ~ů崧軒q腟u尿宲!"
|
||||||
],
|
],
|
||||||
"Nonce": "ǎ^嫯R忑隯ƗƋ*L\u0026",
|
"nonce": "ǎ^嫯R忑隯ƗƋ*L\u0026",
|
||||||
"ExpiresAt": "1989-06-02T14:40:29.613836765Z",
|
"exp": "1989-06-02T14:40:29.613836765Z",
|
||||||
"IssuedAt": "2052-03-26T02:39:27.882495556Z",
|
"iat": "2052-03-26T02:39:27.882495556Z",
|
||||||
"RequestedAt": "2038-04-06T10:46:24.698586972Z",
|
"rat": "2038-04-06T10:46:24.698586972Z",
|
||||||
"AuthTime": "2003-01-05T11:30:18.206004879Z",
|
"auth_time": "2003-01-05T11:30:18.206004879Z",
|
||||||
"AccessTokenHash": "ğǫ\\aȊ4ț髄Al",
|
"at_hash": "ğǫ\\aȊ4ț髄Al",
|
||||||
"AuthenticationContextClassReference": "曓蓳n匟鯘磹*金爃鶴滱ůĮǐ_c3#",
|
"acr": "曓蓳n匟鯘磹*金爃鶴滱ůĮǐ_c3#",
|
||||||
"AuthenticationMethodsReferences": [
|
"amr": [
|
||||||
"装ƹýĸŴB岺Ð嫹Sx镯荫őł疂ư墫"
|
"装ƹýĸŴB岺Ð嫹Sx镯荫őł疂ư墫"
|
||||||
],
|
],
|
||||||
"CodeHash": "\u0026鶡",
|
"c_hash": "\u0026鶡",
|
||||||
"Extra": {
|
"ext": {
|
||||||
"rǓ\\BRë_g\"ʎ啴SƇMǃļū": {
|
"rǓ\\BRë_g\"ʎ啴SƇMǃļū": {
|
||||||
"4撎胬龯,t猟i\u0026\u0026Q@ǤǟǗ": [
|
"4撎胬龯,t猟i\u0026\u0026Q@ǤǟǗ": [
|
||||||
1239190737
|
1239190737
|
||||||
@ -347,8 +348,8 @@ const ExpectedAuthorizeCodeSessionJSONFromFuzzing = `{
|
|||||||
"鑳绪": 2738428764
|
"鑳绪": 2738428764
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"Headers": {
|
"headers": {
|
||||||
"Extra": {
|
"extra": {
|
||||||
"d謺錳4帳ŅǃĊ": 663773398,
|
"d謺錳4帳ŅǃĊ": 663773398,
|
||||||
"Ř鸨EJ": {
|
"Ř鸨EJ": {
|
||||||
"Ǽǟ迍阊v\"豑觳翢砜": [
|
"Ǽǟ迍阊v\"豑觳翢砜": [
|
||||||
@ -363,11 +364,11 @@ const ExpectedAuthorizeCodeSessionJSONFromFuzzing = `{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"ExpiresAt": {
|
"expires_at": {
|
||||||
"韁臯氃妪婝rȤ\"h丬鎒ơ娻}ɼƟ": "1970-04-27T04:31:30.902468229Z"
|
"韁臯氃妪婝rȤ\"h丬鎒ơ娻}ɼƟ": "1970-04-27T04:31:30.902468229Z"
|
||||||
},
|
},
|
||||||
"Username": "髉龳ǽÙ",
|
"username": "髉龳ǽÙ",
|
||||||
"Subject": "\u0026¥潝邎Ȗ莅ŝǔ盕戙鵮碡ʯiŬŽ"
|
"subject": "\u0026¥潝邎Ȗ莅ŝǔ盕戙鵮碡ʯiŬŽ"
|
||||||
},
|
},
|
||||||
"custom": {
|
"custom": {
|
||||||
"username": "Ĝ眧Ĭ",
|
"username": "Ĝ眧Ĭ",
|
||||||
@ -408,5 +409,5 @@ const ExpectedAuthorizeCodeSessionJSONFromFuzzing = `{
|
|||||||
"筫MN\u0026錝D肁Ŷɽ蔒PR}Ųʓl{"
|
"筫MN\u0026錝D肁Ŷɽ蔒PR}Ųʓl{"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"version": "3"
|
"version": "4"
|
||||||
}`
|
}`
|
||||||
|
@ -19,6 +19,7 @@ import (
|
|||||||
"github.com/ory/fosite"
|
"github.com/ory/fosite"
|
||||||
"github.com/ory/fosite/handler/oauth2"
|
"github.com/ory/fosite/handler/oauth2"
|
||||||
"github.com/ory/fosite/handler/openid"
|
"github.com/ory/fosite/handler/openid"
|
||||||
|
"github.com/ory/fosite/token/jwt"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
"gopkg.in/square/go-jose.v2"
|
"gopkg.in/square/go-jose.v2"
|
||||||
@ -65,7 +66,7 @@ func TestAuthorizationCodeStorage(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
Data: map[string][]byte{
|
Data: map[string][]byte{
|
||||||
"pinniped-storage-data": []byte(`{"active":true,"request":{"id":"abcd-1","requestedAt":"0001-01-01T00:00:00Z","client":{"id":"pinny","redirect_uris":null,"grant_types":null,"response_types":null,"scopes":null,"audience":null,"public":true,"jwks_uri":"where","jwks":null,"token_endpoint_auth_method":"something","request_uris":null,"request_object_signing_alg":"","token_endpoint_auth_signing_alg":""},"scopes":null,"grantedScopes":null,"form":{"key":["val"]},"session":{"fosite":{"Claims":null,"Headers":null,"ExpiresAt":null,"Username":"snorlax","Subject":"panda"},"custom":{"username":"fake-username","providerUID":"fake-provider-uid","providerName":"fake-provider-name","providerType":"fake-provider-type","warnings":null,"oidc":{"upstreamRefreshToken":"fake-upstream-refresh-token","upstreamAccessToken":"","upstreamSubject":"some-subject","upstreamIssuer":"some-issuer"}}},"requestedAudience":null,"grantedAudience":null},"version":"3"}`),
|
"pinniped-storage-data": []byte(`{"active":true,"request":{"id":"abcd-1","requestedAt":"0001-01-01T00:00:00Z","client":{"id":"pinny","redirect_uris":null,"grant_types":null,"response_types":null,"scopes":null,"audience":null,"public":true,"jwks_uri":"where","jwks":null,"token_endpoint_auth_method":"something","request_uris":null,"request_object_signing_alg":"","token_endpoint_auth_signing_alg":""},"scopes":null,"grantedScopes":null,"form":{"key":["val"]},"session":{"fosite":{"id_token_claims":null,"headers":null,"expires_at":null,"username":"snorlax","subject":"panda"},"custom":{"username":"fake-username","providerUID":"fake-provider-uid","providerName":"fake-provider-name","providerType":"fake-provider-type","warnings":null,"oidc":{"upstreamRefreshToken":"fake-upstream-refresh-token","upstreamAccessToken":"","upstreamSubject":"some-subject","upstreamIssuer":"some-issuer"}}},"requestedAudience":null,"grantedAudience":null},"version":"4"}`),
|
||||||
"pinniped-storage-version": []byte("1"),
|
"pinniped-storage-version": []byte("1"),
|
||||||
},
|
},
|
||||||
Type: "storage.pinniped.dev/authcode",
|
Type: "storage.pinniped.dev/authcode",
|
||||||
@ -85,7 +86,7 @@ func TestAuthorizationCodeStorage(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
Data: map[string][]byte{
|
Data: map[string][]byte{
|
||||||
"pinniped-storage-data": []byte(`{"active":false,"request":{"id":"abcd-1","requestedAt":"0001-01-01T00:00:00Z","client":{"id":"pinny","redirect_uris":null,"grant_types":null,"response_types":null,"scopes":null,"audience":null,"public":true,"jwks_uri":"where","jwks":null,"token_endpoint_auth_method":"something","request_uris":null,"request_object_signing_alg":"","token_endpoint_auth_signing_alg":""},"scopes":null,"grantedScopes":null,"form":{"key":["val"]},"session":{"fosite":{"Claims":null,"Headers":null,"ExpiresAt":null,"Username":"snorlax","Subject":"panda"},"custom":{"username":"fake-username","providerUID":"fake-provider-uid","providerName":"fake-provider-name","providerType":"fake-provider-type","warnings":null,"oidc":{"upstreamRefreshToken":"fake-upstream-refresh-token","upstreamAccessToken":"","upstreamSubject":"some-subject","upstreamIssuer":"some-issuer"}}},"requestedAudience":null,"grantedAudience":null},"version":"3"}`),
|
"pinniped-storage-data": []byte(`{"active":false,"request":{"id":"abcd-1","requestedAt":"0001-01-01T00:00:00Z","client":{"id":"pinny","redirect_uris":null,"grant_types":null,"response_types":null,"scopes":null,"audience":null,"public":true,"jwks_uri":"where","jwks":null,"token_endpoint_auth_method":"something","request_uris":null,"request_object_signing_alg":"","token_endpoint_auth_signing_alg":""},"scopes":null,"grantedScopes":null,"form":{"key":["val"]},"session":{"fosite":{"id_token_claims":null,"headers":null,"expires_at":null,"username":"snorlax","subject":"panda"},"custom":{"username":"fake-username","providerUID":"fake-provider-uid","providerName":"fake-provider-name","providerType":"fake-provider-type","warnings":null,"oidc":{"upstreamRefreshToken":"fake-upstream-refresh-token","upstreamAccessToken":"","upstreamSubject":"some-subject","upstreamIssuer":"some-issuer"}}},"requestedAudience":null,"grantedAudience":null},"version":"4"}`),
|
||||||
"pinniped-storage-version": []byte("1"),
|
"pinniped-storage-version": []byte("1"),
|
||||||
},
|
},
|
||||||
Type: "storage.pinniped.dev/authcode",
|
Type: "storage.pinniped.dev/authcode",
|
||||||
@ -203,7 +204,7 @@ func TestWrongVersion(t *testing.T) {
|
|||||||
|
|
||||||
_, err = storage.GetAuthorizeCodeSession(ctx, "fancy-signature", nil)
|
_, err = storage.GetAuthorizeCodeSession(ctx, "fancy-signature", nil)
|
||||||
|
|
||||||
require.EqualError(t, err, "authorization request data has wrong version: authorization code session for fancy-signature has version not-the-right-version instead of 3")
|
require.EqualError(t, err, "authorization request data has wrong version: authorization code session for fancy-signature has version not-the-right-version instead of 4")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestNilSessionRequest(t *testing.T) {
|
func TestNilSessionRequest(t *testing.T) {
|
||||||
@ -218,7 +219,7 @@ func TestNilSessionRequest(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
Data: map[string][]byte{
|
Data: map[string][]byte{
|
||||||
"pinniped-storage-data": []byte(`{"nonsense-key": "nonsense-value", "version":"3", "active": true}`),
|
"pinniped-storage-data": []byte(`{"nonsense-key": "nonsense-value", "version":"4", "active": true}`),
|
||||||
"pinniped-storage-version": []byte("1"),
|
"pinniped-storage-version": []byte("1"),
|
||||||
},
|
},
|
||||||
Type: "storage.pinniped.dev/authcode",
|
Type: "storage.pinniped.dev/authcode",
|
||||||
@ -385,7 +386,7 @@ func TestFuzzAndJSONNewValidEmptyAuthorizeCodeSession(t *testing.T) {
|
|||||||
|
|
||||||
// set these to match CreateAuthorizeCodeSession so that .JSONEq works
|
// set these to match CreateAuthorizeCodeSession so that .JSONEq works
|
||||||
validSession.Active = true
|
validSession.Active = true
|
||||||
validSession.Version = "3"
|
validSession.Version = "4"
|
||||||
|
|
||||||
validSessionJSONBytes, err := json.MarshalIndent(validSession, "", "\t")
|
validSessionJSONBytes, err := json.MarshalIndent(validSession, "", "\t")
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
@ -420,13 +421,13 @@ func TestReadFromSecret(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
Data: map[string][]byte{
|
Data: map[string][]byte{
|
||||||
"pinniped-storage-data": []byte(`{"request":{"id":"abcd-1","session":{"fosite":{"Claims":null,"Headers":null,"ExpiresAt":null,"Username":"snorlax","Subject":"panda"},"custom":{"username":"fake-username","providerUID":"fake-provider-uid","providerName":"fake-provider-name","providerType":"fake-provider-type","oidc":{"upstreamRefreshToken":"fake-upstream-refresh-token"}}}},"version":"3","active": true}`),
|
"pinniped-storage-data": []byte(`{"request":{"id":"abcd-1","session":{"fosite":{"id_token_claims":{"jti": "xyz"},"headers":{"extra":{"myheader": "foo"}},"expires_at":null,"username":"snorlax","subject":"panda"},"custom":{"username":"fake-username","providerUID":"fake-provider-uid","providerName":"fake-provider-name","providerType":"fake-provider-type","oidc":{"upstreamRefreshToken":"fake-upstream-refresh-token"}}}},"version":"4","active": true}`),
|
||||||
"pinniped-storage-version": []byte("1"),
|
"pinniped-storage-version": []byte("1"),
|
||||||
},
|
},
|
||||||
Type: "storage.pinniped.dev/authcode",
|
Type: "storage.pinniped.dev/authcode",
|
||||||
},
|
},
|
||||||
wantSession: &Session{
|
wantSession: &Session{
|
||||||
Version: "3",
|
Version: "4",
|
||||||
Active: true,
|
Active: true,
|
||||||
Request: &fosite.Request{
|
Request: &fosite.Request{
|
||||||
ID: "abcd-1",
|
ID: "abcd-1",
|
||||||
@ -435,6 +436,8 @@ func TestReadFromSecret(t *testing.T) {
|
|||||||
Fosite: &openid.DefaultSession{
|
Fosite: &openid.DefaultSession{
|
||||||
Username: "snorlax",
|
Username: "snorlax",
|
||||||
Subject: "panda",
|
Subject: "panda",
|
||||||
|
Claims: &jwt.IDTokenClaims{JTI: "xyz"},
|
||||||
|
Headers: &jwt.Headers{Extra: map[string]interface{}{"myheader": "foo"}},
|
||||||
},
|
},
|
||||||
Custom: &psession.CustomSessionData{
|
Custom: &psession.CustomSessionData{
|
||||||
Username: "fake-username",
|
Username: "fake-username",
|
||||||
@ -483,7 +486,7 @@ func TestReadFromSecret(t *testing.T) {
|
|||||||
},
|
},
|
||||||
Type: "storage.pinniped.dev/authcode",
|
Type: "storage.pinniped.dev/authcode",
|
||||||
},
|
},
|
||||||
wantErr: "authorization request data has wrong version: authorization code session has version wrong-version-here instead of 3",
|
wantErr: "authorization request data has wrong version: authorization code session has version wrong-version-here instead of 4",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "missing request",
|
name: "missing request",
|
||||||
@ -496,7 +499,7 @@ func TestReadFromSecret(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
Data: map[string][]byte{
|
Data: map[string][]byte{
|
||||||
"pinniped-storage-data": []byte(`{"version":"3","active": true}`),
|
"pinniped-storage-data": []byte(`{"version":"4","active": true}`),
|
||||||
"pinniped-storage-version": []byte("1"),
|
"pinniped-storage-version": []byte("1"),
|
||||||
},
|
},
|
||||||
Type: "storage.pinniped.dev/authcode",
|
Type: "storage.pinniped.dev/authcode",
|
||||||
|
@ -31,7 +31,8 @@ const (
|
|||||||
// Version 1 was the initial release of storage.
|
// Version 1 was the initial release of storage.
|
||||||
// Version 2 is when we switched to storing psession.PinnipedSession inside the fosite request.
|
// Version 2 is when we switched to storing psession.PinnipedSession inside the fosite request.
|
||||||
// Version 3 is when we added the Username field to the psession.CustomSessionData.
|
// Version 3 is when we added the Username field to the psession.CustomSessionData.
|
||||||
oidcStorageVersion = "3"
|
// Version 4 is when fosite added json tags to their openid.DefaultSession struct.
|
||||||
|
oidcStorageVersion = "4"
|
||||||
)
|
)
|
||||||
|
|
||||||
var _ openid.OpenIDConnectRequestStorage = &openIDConnectRequestStorage{}
|
var _ openid.OpenIDConnectRequestStorage = &openIDConnectRequestStorage{}
|
||||||
|
@ -52,7 +52,7 @@ func TestOpenIdConnectStorage(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
Data: map[string][]byte{
|
Data: map[string][]byte{
|
||||||
"pinniped-storage-data": []byte(`{"request":{"id":"abcd-1","requestedAt":"0001-01-01T00:00:00Z","client":{"id":"pinny","redirect_uris":null,"grant_types":null,"response_types":null,"scopes":null,"audience":null,"public":true,"jwks_uri":"where","jwks":null,"token_endpoint_auth_method":"something","request_uris":null,"request_object_signing_alg":"","token_endpoint_auth_signing_alg":""},"scopes":null,"grantedScopes":null,"form":{"key":["val"]},"session":{"fosite":{"Claims":null,"Headers":null,"ExpiresAt":null,"Username":"snorlax","Subject":"panda"},"custom":{"username":"fake-username","providerUID":"fake-provider-uid","providerName":"fake-provider-name","providerType":"fake-provider-type","warnings":null,"oidc":{"upstreamRefreshToken":"fake-upstream-refresh-token","upstreamAccessToken":"","upstreamSubject":"some-subject","upstreamIssuer":"some-issuer"}}},"requestedAudience":null,"grantedAudience":null},"version":"3"}`),
|
"pinniped-storage-data": []byte(`{"request":{"id":"abcd-1","requestedAt":"0001-01-01T00:00:00Z","client":{"id":"pinny","redirect_uris":null,"grant_types":null,"response_types":null,"scopes":null,"audience":null,"public":true,"jwks_uri":"where","jwks":null,"token_endpoint_auth_method":"something","request_uris":null,"request_object_signing_alg":"","token_endpoint_auth_signing_alg":""},"scopes":null,"grantedScopes":null,"form":{"key":["val"]},"session":{"fosite":{"id_token_claims":null,"headers":null,"expires_at":null,"username":"snorlax","subject":"panda"},"custom":{"username":"fake-username","providerUID":"fake-provider-uid","providerName":"fake-provider-name","providerType":"fake-provider-type","warnings":null,"oidc":{"upstreamRefreshToken":"fake-upstream-refresh-token","upstreamAccessToken":"","upstreamSubject":"some-subject","upstreamIssuer":"some-issuer"}}},"requestedAudience":null,"grantedAudience":null},"version":"4"}`),
|
||||||
"pinniped-storage-version": []byte("1"),
|
"pinniped-storage-version": []byte("1"),
|
||||||
},
|
},
|
||||||
Type: "storage.pinniped.dev/oidc",
|
Type: "storage.pinniped.dev/oidc",
|
||||||
@ -137,7 +137,7 @@ func TestWrongVersion(t *testing.T) {
|
|||||||
|
|
||||||
_, err = storage.GetOpenIDConnectSession(ctx, "fancy-code.fancy-signature", nil)
|
_, err = storage.GetOpenIDConnectSession(ctx, "fancy-code.fancy-signature", nil)
|
||||||
|
|
||||||
require.EqualError(t, err, "oidc request data has wrong version: oidc session for fancy-signature has version not-the-right-version instead of 3")
|
require.EqualError(t, err, "oidc request data has wrong version: oidc session for fancy-signature has version not-the-right-version instead of 4")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestNilSessionRequest(t *testing.T) {
|
func TestNilSessionRequest(t *testing.T) {
|
||||||
@ -152,7 +152,7 @@ func TestNilSessionRequest(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
Data: map[string][]byte{
|
Data: map[string][]byte{
|
||||||
"pinniped-storage-data": []byte(`{"nonsense-key": "nonsense-value","version":"3"}`),
|
"pinniped-storage-data": []byte(`{"nonsense-key": "nonsense-value","version":"4"}`),
|
||||||
"pinniped-storage-version": []byte("1"),
|
"pinniped-storage-version": []byte("1"),
|
||||||
},
|
},
|
||||||
Type: "storage.pinniped.dev/oidc",
|
Type: "storage.pinniped.dev/oidc",
|
||||||
|
@ -29,7 +29,8 @@ const (
|
|||||||
// Version 1 was the initial release of storage.
|
// Version 1 was the initial release of storage.
|
||||||
// Version 2 is when we switched to storing psession.PinnipedSession inside the fosite request.
|
// Version 2 is when we switched to storing psession.PinnipedSession inside the fosite request.
|
||||||
// Version 3 is when we added the Username field to the psession.CustomSessionData.
|
// Version 3 is when we added the Username field to the psession.CustomSessionData.
|
||||||
pkceStorageVersion = "3"
|
// Version 4 is when fosite added json tags to their openid.DefaultSession struct.
|
||||||
|
pkceStorageVersion = "4"
|
||||||
)
|
)
|
||||||
|
|
||||||
var _ pkce.PKCERequestStorage = &pkceStorage{}
|
var _ pkce.PKCERequestStorage = &pkceStorage{}
|
||||||
|
@ -52,7 +52,7 @@ func TestPKCEStorage(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
Data: map[string][]byte{
|
Data: map[string][]byte{
|
||||||
"pinniped-storage-data": []byte(`{"request":{"id":"abcd-1","requestedAt":"0001-01-01T00:00:00Z","client":{"id":"pinny","redirect_uris":null,"grant_types":null,"response_types":null,"scopes":null,"audience":null,"public":true,"jwks_uri":"where","jwks":null,"token_endpoint_auth_method":"something","request_uris":null,"request_object_signing_alg":"","token_endpoint_auth_signing_alg":""},"scopes":null,"grantedScopes":null,"form":{"key":["val"]},"session":{"fosite":{"Claims":null,"Headers":null,"ExpiresAt":null,"Username":"snorlax","Subject":"panda"},"custom":{"username":"fake-username","providerUID":"fake-provider-uid","providerName":"fake-provider-name","providerType":"fake-provider-type","warnings":null,"oidc":{"upstreamRefreshToken":"fake-upstream-refresh-token","upstreamAccessToken":"","upstreamSubject":"some-subject","upstreamIssuer":"some-issuer"}}},"requestedAudience":null,"grantedAudience":null},"version":"3"}`),
|
"pinniped-storage-data": []byte(`{"request":{"id":"abcd-1","requestedAt":"0001-01-01T00:00:00Z","client":{"id":"pinny","redirect_uris":null,"grant_types":null,"response_types":null,"scopes":null,"audience":null,"public":true,"jwks_uri":"where","jwks":null,"token_endpoint_auth_method":"something","request_uris":null,"request_object_signing_alg":"","token_endpoint_auth_signing_alg":""},"scopes":null,"grantedScopes":null,"form":{"key":["val"]},"session":{"fosite":{"id_token_claims":null,"headers":null,"expires_at":null,"username":"snorlax","subject":"panda"},"custom":{"username":"fake-username","providerUID":"fake-provider-uid","providerName":"fake-provider-name","providerType":"fake-provider-type","warnings":null,"oidc":{"upstreamRefreshToken":"fake-upstream-refresh-token","upstreamAccessToken":"","upstreamSubject":"some-subject","upstreamIssuer":"some-issuer"}}},"requestedAudience":null,"grantedAudience":null},"version":"4"}`),
|
||||||
"pinniped-storage-version": []byte("1"),
|
"pinniped-storage-version": []byte("1"),
|
||||||
},
|
},
|
||||||
Type: "storage.pinniped.dev/pkce",
|
Type: "storage.pinniped.dev/pkce",
|
||||||
@ -140,7 +140,7 @@ func TestWrongVersion(t *testing.T) {
|
|||||||
|
|
||||||
_, err = storage.GetPKCERequestSession(ctx, "fancy-signature", nil)
|
_, err = storage.GetPKCERequestSession(ctx, "fancy-signature", nil)
|
||||||
|
|
||||||
require.EqualError(t, err, "pkce request data has wrong version: pkce session for fancy-signature has version not-the-right-version instead of 3")
|
require.EqualError(t, err, "pkce request data has wrong version: pkce session for fancy-signature has version not-the-right-version instead of 4")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestNilSessionRequest(t *testing.T) {
|
func TestNilSessionRequest(t *testing.T) {
|
||||||
@ -158,7 +158,7 @@ func TestNilSessionRequest(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
Data: map[string][]byte{
|
Data: map[string][]byte{
|
||||||
"pinniped-storage-data": []byte(`{"nonsense-key": "nonsense-value","version":"3"}`),
|
"pinniped-storage-data": []byte(`{"nonsense-key": "nonsense-value","version":"4"}`),
|
||||||
"pinniped-storage-version": []byte("1"),
|
"pinniped-storage-version": []byte("1"),
|
||||||
},
|
},
|
||||||
Type: "storage.pinniped.dev/pkce",
|
Type: "storage.pinniped.dev/pkce",
|
||||||
|
@ -30,7 +30,8 @@ const (
|
|||||||
// Version 1 was the initial release of storage.
|
// Version 1 was the initial release of storage.
|
||||||
// Version 2 is when we switched to storing psession.PinnipedSession inside the fosite request.
|
// Version 2 is when we switched to storing psession.PinnipedSession inside the fosite request.
|
||||||
// Version 3 is when we added the Username field to the psession.CustomSessionData.
|
// Version 3 is when we added the Username field to the psession.CustomSessionData.
|
||||||
refreshTokenStorageVersion = "3"
|
// Version 4 is when fosite added json tags to their openid.DefaultSession struct.
|
||||||
|
refreshTokenStorageVersion = "4"
|
||||||
)
|
)
|
||||||
|
|
||||||
type RevocationStorage interface {
|
type RevocationStorage interface {
|
||||||
|
@ -11,6 +11,7 @@ import (
|
|||||||
|
|
||||||
"github.com/ory/fosite"
|
"github.com/ory/fosite"
|
||||||
"github.com/ory/fosite/handler/openid"
|
"github.com/ory/fosite/handler/openid"
|
||||||
|
"github.com/ory/fosite/token/jwt"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
corev1 "k8s.io/api/core/v1"
|
corev1 "k8s.io/api/core/v1"
|
||||||
@ -52,7 +53,7 @@ func TestRefreshTokenStorage(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
Data: map[string][]byte{
|
Data: map[string][]byte{
|
||||||
"pinniped-storage-data": []byte(`{"request":{"id":"abcd-1","requestedAt":"0001-01-01T00:00:00Z","client":{"id":"pinny","redirect_uris":null,"grant_types":null,"response_types":null,"scopes":null,"audience":null,"public":true,"jwks_uri":"where","jwks":null,"token_endpoint_auth_method":"something","request_uris":null,"request_object_signing_alg":"","token_endpoint_auth_signing_alg":""},"scopes":null,"grantedScopes":null,"form":{"key":["val"]},"session":{"fosite":{"Claims":null,"Headers":null,"ExpiresAt":null,"Username":"snorlax","Subject":"panda"},"custom":{"username":"fake-username","providerUID":"fake-provider-uid","providerName":"fake-provider-name","providerType":"fake-provider-type","warnings":null,"oidc":{"upstreamRefreshToken":"fake-upstream-refresh-token","upstreamAccessToken":"","upstreamSubject":"some-subject","upstreamIssuer":"some-issuer"}}},"requestedAudience":null,"grantedAudience":null},"version":"3"}`),
|
"pinniped-storage-data": []byte(`{"request":{"id":"abcd-1","requestedAt":"0001-01-01T00:00:00Z","client":{"id":"pinny","redirect_uris":null,"grant_types":null,"response_types":null,"scopes":null,"audience":null,"public":true,"jwks_uri":"where","jwks":null,"token_endpoint_auth_method":"something","request_uris":null,"request_object_signing_alg":"","token_endpoint_auth_signing_alg":""},"scopes":null,"grantedScopes":null,"form":{"key":["val"]},"session":{"fosite":{"id_token_claims":null,"headers":null,"expires_at":null,"username":"snorlax","subject":"panda"},"custom":{"username":"fake-username","providerUID":"fake-provider-uid","providerName":"fake-provider-name","providerType":"fake-provider-type","warnings":null,"oidc":{"upstreamRefreshToken":"fake-upstream-refresh-token","upstreamAccessToken":"","upstreamSubject":"some-subject","upstreamIssuer":"some-issuer"}}},"requestedAudience":null,"grantedAudience":null},"version":"4"}`),
|
||||||
"pinniped-storage-version": []byte("1"),
|
"pinniped-storage-version": []byte("1"),
|
||||||
},
|
},
|
||||||
Type: "storage.pinniped.dev/refresh-token",
|
Type: "storage.pinniped.dev/refresh-token",
|
||||||
@ -122,7 +123,7 @@ func TestRefreshTokenStorageRevocation(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
Data: map[string][]byte{
|
Data: map[string][]byte{
|
||||||
"pinniped-storage-data": []byte(`{"request":{"id":"abcd-1","requestedAt":"0001-01-01T00:00:00Z","client":{"id":"pinny","redirect_uris":null,"grant_types":null,"response_types":null,"scopes":null,"audience":null,"public":true,"jwks_uri":"where","jwks":null,"token_endpoint_auth_method":"something","request_uris":null,"request_object_signing_alg":"","token_endpoint_auth_signing_alg":""},"scopes":null,"grantedScopes":null,"form":{"key":["val"]},"session":{"fosite":{"Claims":null,"Headers":null,"ExpiresAt":null,"Username":"snorlax","Subject":"panda"},"custom":{"username":"fake-username","providerUID":"fake-provider-uid","providerName":"fake-provider-name","providerType":"fake-provider-type","warnings":null,"oidc":{"upstreamRefreshToken":"fake-upstream-refresh-token","upstreamAccessToken":"","upstreamSubject":"some-subject","upstreamIssuer":"some-issuer"}}},"requestedAudience":null,"grantedAudience":null},"version":"3"}`),
|
"pinniped-storage-data": []byte(`{"request":{"id":"abcd-1","requestedAt":"0001-01-01T00:00:00Z","client":{"id":"pinny","redirect_uris":null,"grant_types":null,"response_types":null,"scopes":null,"audience":null,"public":true,"jwks_uri":"where","jwks":null,"token_endpoint_auth_method":"something","request_uris":null,"request_object_signing_alg":"","token_endpoint_auth_signing_alg":""},"scopes":null,"grantedScopes":null,"form":{"key":["val"]},"session":{"fosite":{"id_token_claims":null,"headers":null,"expires_at":null,"username":"snorlax","subject":"panda"},"custom":{"username":"fake-username","providerUID":"fake-provider-uid","providerName":"fake-provider-name","providerType":"fake-provider-type","warnings":null,"oidc":{"upstreamRefreshToken":"fake-upstream-refresh-token","upstreamAccessToken":"","upstreamSubject":"some-subject","upstreamIssuer":"some-issuer"}}},"requestedAudience":null,"grantedAudience":null},"version":"4"}`),
|
||||||
"pinniped-storage-version": []byte("1"),
|
"pinniped-storage-version": []byte("1"),
|
||||||
},
|
},
|
||||||
Type: "storage.pinniped.dev/refresh-token",
|
Type: "storage.pinniped.dev/refresh-token",
|
||||||
@ -177,7 +178,7 @@ func TestRefreshTokenStorageRevokeRefreshTokenMaybeGracePeriod(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
Data: map[string][]byte{
|
Data: map[string][]byte{
|
||||||
"pinniped-storage-data": []byte(`{"request":{"id":"abcd-1","requestedAt":"0001-01-01T00:00:00Z","client":{"id":"pinny","redirect_uris":null,"grant_types":null,"response_types":null,"scopes":null,"audience":null,"public":true,"jwks_uri":"where","jwks":null,"token_endpoint_auth_method":"something","request_uris":null,"request_object_signing_alg":"","token_endpoint_auth_signing_alg":""},"scopes":null,"grantedScopes":null,"form":{"key":["val"]},"session":{"fosite":{"Claims":null,"Headers":null,"ExpiresAt":null,"Username":"snorlax","Subject":"panda"},"custom":{"username":"fake-username","providerUID":"fake-provider-uid","providerName":"fake-provider-name","providerType":"fake-provider-type","warnings":null,"oidc":{"upstreamRefreshToken":"fake-upstream-refresh-token","upstreamAccessToken":"","upstreamSubject":"some-subject","upstreamIssuer":"some-issuer"}}},"requestedAudience":null,"grantedAudience":null},"version":"3"}`),
|
"pinniped-storage-data": []byte(`{"request":{"id":"abcd-1","requestedAt":"0001-01-01T00:00:00Z","client":{"id":"pinny","redirect_uris":null,"grant_types":null,"response_types":null,"scopes":null,"audience":null,"public":true,"jwks_uri":"where","jwks":null,"token_endpoint_auth_method":"something","request_uris":null,"request_object_signing_alg":"","token_endpoint_auth_signing_alg":""},"scopes":null,"grantedScopes":null,"form":{"key":["val"]},"session":{"fosite":{"id_token_claims":null,"headers":null,"expires_at":null,"username":"snorlax","subject":"panda"},"custom":{"username":"fake-username","providerUID":"fake-provider-uid","providerName":"fake-provider-name","providerType":"fake-provider-type","warnings":null,"oidc":{"upstreamRefreshToken":"fake-upstream-refresh-token","upstreamAccessToken":"","upstreamSubject":"some-subject","upstreamIssuer":"some-issuer"}}},"requestedAudience":null,"grantedAudience":null},"version":"4"}`),
|
||||||
"pinniped-storage-version": []byte("1"),
|
"pinniped-storage-version": []byte("1"),
|
||||||
},
|
},
|
||||||
Type: "storage.pinniped.dev/refresh-token",
|
Type: "storage.pinniped.dev/refresh-token",
|
||||||
@ -251,7 +252,7 @@ func TestWrongVersion(t *testing.T) {
|
|||||||
|
|
||||||
_, err = storage.GetRefreshTokenSession(ctx, "fancy-signature", nil)
|
_, err = storage.GetRefreshTokenSession(ctx, "fancy-signature", nil)
|
||||||
|
|
||||||
require.EqualError(t, err, "refresh token request data has wrong version: refresh token session for fancy-signature has version not-the-right-version instead of 3")
|
require.EqualError(t, err, "refresh token request data has wrong version: refresh token session for fancy-signature has version not-the-right-version instead of 4")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestNilSessionRequest(t *testing.T) {
|
func TestNilSessionRequest(t *testing.T) {
|
||||||
@ -269,7 +270,7 @@ func TestNilSessionRequest(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
Data: map[string][]byte{
|
Data: map[string][]byte{
|
||||||
"pinniped-storage-data": []byte(`{"nonsense-key": "nonsense-value","version":"3"}`),
|
"pinniped-storage-data": []byte(`{"nonsense-key": "nonsense-value","version":"4"}`),
|
||||||
"pinniped-storage-version": []byte("1"),
|
"pinniped-storage-version": []byte("1"),
|
||||||
},
|
},
|
||||||
Type: "storage.pinniped.dev/refresh-token",
|
Type: "storage.pinniped.dev/refresh-token",
|
||||||
@ -353,13 +354,13 @@ func TestReadFromSecret(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
Data: map[string][]byte{
|
Data: map[string][]byte{
|
||||||
"pinniped-storage-data": []byte(`{"request":{"id":"abcd-1","session":{"fosite":{"Claims":null,"Headers":null,"ExpiresAt":null,"Username":"snorlax","Subject":"panda"},"custom":{"username":"fake-username","providerUID":"fake-provider-uid","providerName":"fake-provider-name","providerType":"fake-provider-type","oidc":{"upstreamRefreshToken":"fake-upstream-refresh-token"}}}},"version":"3","active": true}`),
|
"pinniped-storage-data": []byte(`{"request":{"id":"abcd-1","session":{"fosite":{"id_token_claims":{"jti": "xyz"},"headers":{"extra":{"myheader": "foo"}},"expires_at":null,"username":"snorlax","subject":"panda"},"custom":{"username":"fake-username","providerUID":"fake-provider-uid","providerName":"fake-provider-name","providerType":"fake-provider-type","oidc":{"upstreamRefreshToken":"fake-upstream-refresh-token"}}}},"version":"4","active": true}`),
|
||||||
"pinniped-storage-version": []byte("1"),
|
"pinniped-storage-version": []byte("1"),
|
||||||
},
|
},
|
||||||
Type: "storage.pinniped.dev/refresh-token",
|
Type: "storage.pinniped.dev/refresh-token",
|
||||||
},
|
},
|
||||||
wantSession: &Session{
|
wantSession: &Session{
|
||||||
Version: "3",
|
Version: "4",
|
||||||
Request: &fosite.Request{
|
Request: &fosite.Request{
|
||||||
ID: "abcd-1",
|
ID: "abcd-1",
|
||||||
Client: &clientregistry.Client{},
|
Client: &clientregistry.Client{},
|
||||||
@ -367,6 +368,8 @@ func TestReadFromSecret(t *testing.T) {
|
|||||||
Fosite: &openid.DefaultSession{
|
Fosite: &openid.DefaultSession{
|
||||||
Username: "snorlax",
|
Username: "snorlax",
|
||||||
Subject: "panda",
|
Subject: "panda",
|
||||||
|
Claims: &jwt.IDTokenClaims{JTI: "xyz"},
|
||||||
|
Headers: &jwt.Headers{Extra: map[string]interface{}{"myheader": "foo"}},
|
||||||
},
|
},
|
||||||
Custom: &psession.CustomSessionData{
|
Custom: &psession.CustomSessionData{
|
||||||
Username: "fake-username",
|
Username: "fake-username",
|
||||||
@ -392,7 +395,7 @@ func TestReadFromSecret(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
Data: map[string][]byte{
|
Data: map[string][]byte{
|
||||||
"pinniped-storage-data": []byte(`{"request":{"id":"abcd-1"},"version":"3","active": true}`),
|
"pinniped-storage-data": []byte(`{"request":{"id":"abcd-1"},"version":"4","active": true}`),
|
||||||
"pinniped-storage-version": []byte("1"),
|
"pinniped-storage-version": []byte("1"),
|
||||||
},
|
},
|
||||||
Type: "storage.pinniped.dev/not-refresh-token",
|
Type: "storage.pinniped.dev/not-refresh-token",
|
||||||
@ -415,7 +418,7 @@ func TestReadFromSecret(t *testing.T) {
|
|||||||
},
|
},
|
||||||
Type: "storage.pinniped.dev/refresh-token",
|
Type: "storage.pinniped.dev/refresh-token",
|
||||||
},
|
},
|
||||||
wantErr: "refresh token request data has wrong version: refresh token session has version wrong-version-here instead of 3",
|
wantErr: "refresh token request data has wrong version: refresh token session has version wrong-version-here instead of 4",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "missing request",
|
name: "missing request",
|
||||||
@ -428,7 +431,7 @@ func TestReadFromSecret(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
Data: map[string][]byte{
|
Data: map[string][]byte{
|
||||||
"pinniped-storage-data": []byte(`{"version":"3","active": true}`),
|
"pinniped-storage-data": []byte(`{"version":"4","active": true}`),
|
||||||
"pinniped-storage-version": []byte("1"),
|
"pinniped-storage-version": []byte("1"),
|
||||||
},
|
},
|
||||||
Type: "storage.pinniped.dev/refresh-token",
|
Type: "storage.pinniped.dev/refresh-token",
|
||||||
|
@ -125,7 +125,7 @@ func handleAuthRequestForLDAPUpstreamCLIFlow(
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if !requireStaticClientForUsernameAndPasswordHeaders(w, oauthHelper, authorizeRequester) {
|
if !requireStaticClientForUsernameAndPasswordHeaders(r, w, oauthHelper, authorizeRequester) {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -140,7 +140,7 @@ func handleAuthRequestForLDAPUpstreamCLIFlow(
|
|||||||
return httperr.New(http.StatusBadGateway, "unexpected error during upstream authentication")
|
return httperr.New(http.StatusBadGateway, "unexpected error during upstream authentication")
|
||||||
}
|
}
|
||||||
if !authenticated {
|
if !authenticated {
|
||||||
oidc.WriteAuthorizeError(w, oauthHelper, authorizeRequester,
|
oidc.WriteAuthorizeError(r, w, oauthHelper, authorizeRequester,
|
||||||
fosite.ErrAccessDenied.WithHintf("Username/password not accepted by LDAP provider."), true)
|
fosite.ErrAccessDenied.WithHintf("Username/password not accepted by LDAP provider."), true)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -203,7 +203,7 @@ func handleAuthRequestForOIDCUpstreamPasswordGrant(
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if !requireStaticClientForUsernameAndPasswordHeaders(w, oauthHelper, authorizeRequester) {
|
if !requireStaticClientForUsernameAndPasswordHeaders(r, w, oauthHelper, authorizeRequester) {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -214,7 +214,7 @@ func handleAuthRequestForOIDCUpstreamPasswordGrant(
|
|||||||
|
|
||||||
if !oidcUpstream.AllowsPasswordGrant() {
|
if !oidcUpstream.AllowsPasswordGrant() {
|
||||||
// Return a user-friendly error for this case which is entirely within our control.
|
// Return a user-friendly error for this case which is entirely within our control.
|
||||||
oidc.WriteAuthorizeError(w, oauthHelper, authorizeRequester,
|
oidc.WriteAuthorizeError(r, w, oauthHelper, authorizeRequester,
|
||||||
fosite.ErrAccessDenied.WithHint(
|
fosite.ErrAccessDenied.WithHint(
|
||||||
"Resource owner password credentials grant is not allowed for this upstream provider according to its configuration."), true)
|
"Resource owner password credentials grant is not allowed for this upstream provider according to its configuration."), true)
|
||||||
return nil
|
return nil
|
||||||
@ -229,7 +229,7 @@ func handleAuthRequestForOIDCUpstreamPasswordGrant(
|
|||||||
// However, the exact response is undefined in the sense that there is no such thing as a password grant in
|
// However, the exact response is undefined in the sense that there is no such thing as a password grant in
|
||||||
// the OIDC spec, so we don't try too hard to read the upstream errors in this case. (E.g. Dex departs from the
|
// the OIDC spec, so we don't try too hard to read the upstream errors in this case. (E.g. Dex departs from the
|
||||||
// spec and returns something other than an "invalid_grant" error for bad resource owner credentials.)
|
// spec and returns something other than an "invalid_grant" error for bad resource owner credentials.)
|
||||||
oidc.WriteAuthorizeError(w, oauthHelper, authorizeRequester,
|
oidc.WriteAuthorizeError(r, w, oauthHelper, authorizeRequester,
|
||||||
fosite.ErrAccessDenied.WithDebug(err.Error()), true) // WithDebug hides the error from the client
|
fosite.ErrAccessDenied.WithDebug(err.Error()), true) // WithDebug hides the error from the client
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -237,7 +237,7 @@ func handleAuthRequestForOIDCUpstreamPasswordGrant(
|
|||||||
subject, username, groups, err := downstreamsession.GetDownstreamIdentityFromUpstreamIDToken(oidcUpstream, token.IDToken.Claims)
|
subject, username, groups, err := downstreamsession.GetDownstreamIdentityFromUpstreamIDToken(oidcUpstream, token.IDToken.Claims)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// Return a user-friendly error for this case which is entirely within our control.
|
// Return a user-friendly error for this case which is entirely within our control.
|
||||||
oidc.WriteAuthorizeError(w, oauthHelper, authorizeRequester,
|
oidc.WriteAuthorizeError(r, w, oauthHelper, authorizeRequester,
|
||||||
fosite.ErrAccessDenied.WithHintf("Reason: %s.", err.Error()), true,
|
fosite.ErrAccessDenied.WithHintf("Reason: %s.", err.Error()), true,
|
||||||
)
|
)
|
||||||
return nil
|
return nil
|
||||||
@ -245,7 +245,7 @@ func handleAuthRequestForOIDCUpstreamPasswordGrant(
|
|||||||
|
|
||||||
customSessionData, err := downstreamsession.MakeDownstreamOIDCCustomSessionData(oidcUpstream, token, username)
|
customSessionData, err := downstreamsession.MakeDownstreamOIDCCustomSessionData(oidcUpstream, token, username)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
oidc.WriteAuthorizeError(w, oauthHelper, authorizeRequester,
|
oidc.WriteAuthorizeError(r, w, oauthHelper, authorizeRequester,
|
||||||
fosite.ErrAccessDenied.WithHintf("Reason: %s.", err.Error()), true,
|
fosite.ErrAccessDenied.WithHintf("Reason: %s.", err.Error()), true,
|
||||||
)
|
)
|
||||||
return nil
|
return nil
|
||||||
@ -321,10 +321,10 @@ func handleAuthRequestForOIDCUpstreamBrowserFlow(
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func requireStaticClientForUsernameAndPasswordHeaders(w http.ResponseWriter, oauthHelper fosite.OAuth2Provider, authorizeRequester fosite.AuthorizeRequester) bool {
|
func requireStaticClientForUsernameAndPasswordHeaders(r *http.Request, w http.ResponseWriter, oauthHelper fosite.OAuth2Provider, authorizeRequester fosite.AuthorizeRequester) bool {
|
||||||
isStaticClient := authorizeRequester.GetClient().GetID() == oidcapi.ClientIDPinnipedCLI
|
isStaticClient := authorizeRequester.GetClient().GetID() == oidcapi.ClientIDPinnipedCLI
|
||||||
if !isStaticClient {
|
if !isStaticClient {
|
||||||
oidc.WriteAuthorizeError(w, oauthHelper, authorizeRequester,
|
oidc.WriteAuthorizeError(r, w, oauthHelper, authorizeRequester,
|
||||||
fosite.ErrAccessDenied.WithHintf("This client is not allowed to submit username or password headers to this endpoint."), true)
|
fosite.ErrAccessDenied.WithHintf("This client is not allowed to submit username or password headers to this endpoint."), true)
|
||||||
}
|
}
|
||||||
return isStaticClient
|
return isStaticClient
|
||||||
@ -334,7 +334,7 @@ func requireNonEmptyUsernameAndPasswordHeaders(r *http.Request, w http.ResponseW
|
|||||||
username := r.Header.Get(oidcapi.AuthorizeUsernameHeaderName)
|
username := r.Header.Get(oidcapi.AuthorizeUsernameHeaderName)
|
||||||
password := r.Header.Get(oidcapi.AuthorizePasswordHeaderName)
|
password := r.Header.Get(oidcapi.AuthorizePasswordHeaderName)
|
||||||
if username == "" || password == "" {
|
if username == "" || password == "" {
|
||||||
oidc.WriteAuthorizeError(w, oauthHelper, authorizeRequester,
|
oidc.WriteAuthorizeError(r, w, oauthHelper, authorizeRequester,
|
||||||
fosite.ErrAccessDenied.WithHintf("Missing or blank username or password."), true)
|
fosite.ErrAccessDenied.WithHintf("Missing or blank username or password."), true)
|
||||||
return "", "", false
|
return "", "", false
|
||||||
}
|
}
|
||||||
@ -344,7 +344,7 @@ func requireNonEmptyUsernameAndPasswordHeaders(r *http.Request, w http.ResponseW
|
|||||||
func newAuthorizeRequest(r *http.Request, w http.ResponseWriter, oauthHelper fosite.OAuth2Provider, isBrowserless bool) (fosite.AuthorizeRequester, bool) {
|
func newAuthorizeRequest(r *http.Request, w http.ResponseWriter, oauthHelper fosite.OAuth2Provider, isBrowserless bool) (fosite.AuthorizeRequester, bool) {
|
||||||
authorizeRequester, err := oauthHelper.NewAuthorizeRequest(r.Context(), r)
|
authorizeRequester, err := oauthHelper.NewAuthorizeRequest(r.Context(), r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
oidc.WriteAuthorizeError(w, oauthHelper, authorizeRequester, err, isBrowserless)
|
oidc.WriteAuthorizeError(r, w, oauthHelper, authorizeRequester, err, isBrowserless)
|
||||||
return nil, false
|
return nil, false
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -458,7 +458,7 @@ func handleBrowserFlowAuthRequest(
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
oidc.WriteAuthorizeError(w, oauthHelper, authorizeRequester, err, false)
|
oidc.WriteAuthorizeError(r, w, oauthHelper, authorizeRequester, err, false)
|
||||||
return nil, nil // already wrote the error response, don't return error
|
return nil, nil // already wrote the error response, don't return error
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -488,7 +488,7 @@ func handleBrowserFlowAuthRequest(
|
|||||||
|
|
||||||
promptParam := r.Form.Get(promptParamName)
|
promptParam := r.Form.Get(promptParamName)
|
||||||
if promptParam == promptParamNone && oidc.ScopeWasRequested(authorizeRequester, oidcapi.ScopeOpenID) {
|
if promptParam == promptParamNone && oidc.ScopeWasRequested(authorizeRequester, oidcapi.ScopeOpenID) {
|
||||||
oidc.WriteAuthorizeError(w, oauthHelper, authorizeRequester, fosite.ErrLoginRequired, false)
|
oidc.WriteAuthorizeError(r, w, oauthHelper, authorizeRequester, fosite.ErrLoginRequired, false)
|
||||||
return nil, nil // already wrote the error response, don't return error
|
return nil, nil // already wrote the error response, don't return error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -89,7 +89,7 @@ func NewHandler(
|
|||||||
return httperr.Wrap(http.StatusInternalServerError, "error while generating and saving authcode", err)
|
return httperr.Wrap(http.StatusInternalServerError, "error while generating and saving authcode", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
oauthHelper.WriteAuthorizeResponse(w, authorizeRequester, authorizeResponder)
|
oauthHelper.WriteAuthorizeResponse(r.Context(), w, authorizeRequester, authorizeResponder)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
|
63
internal/oidc/dynamic_global_secret_config.go
Normal file
63
internal/oidc/dynamic_global_secret_config.go
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
// Copyright 2020-2022 the Pinniped contributors. All Rights Reserved.
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
package oidc
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"hash"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/ory/fosite"
|
||||||
|
"github.com/ory/fosite/compose"
|
||||||
|
)
|
||||||
|
|
||||||
|
// DynamicGlobalSecretConfig is a wrapper around fosite.Config which allows us to always return dynamic secrets,
|
||||||
|
// since those secrets can change at any time when they are loaded or reloaded by our controllers.
|
||||||
|
type DynamicGlobalSecretConfig struct {
|
||||||
|
fositeConfig *fosite.Config
|
||||||
|
keyFunc func() []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ compose.HMACSHAStrategyConfigurator = &DynamicGlobalSecretConfig{}
|
||||||
|
|
||||||
|
func NewDynamicGlobalSecretConfig(
|
||||||
|
fositeConfig *fosite.Config,
|
||||||
|
keyFunc func() []byte,
|
||||||
|
) *DynamicGlobalSecretConfig {
|
||||||
|
return &DynamicGlobalSecretConfig{
|
||||||
|
fositeConfig: fositeConfig,
|
||||||
|
keyFunc: keyFunc,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DynamicGlobalSecretConfig) GetAccessTokenLifespan(ctx context.Context) time.Duration {
|
||||||
|
return d.fositeConfig.GetAccessTokenLifespan(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DynamicGlobalSecretConfig) GetRefreshTokenLifespan(ctx context.Context) time.Duration {
|
||||||
|
return d.fositeConfig.GetRefreshTokenLifespan(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DynamicGlobalSecretConfig) GetAuthorizeCodeLifespan(ctx context.Context) time.Duration {
|
||||||
|
return d.fositeConfig.GetAuthorizeCodeLifespan(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DynamicGlobalSecretConfig) GetTokenEntropy(ctx context.Context) int {
|
||||||
|
return d.fositeConfig.GetTokenEntropy(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DynamicGlobalSecretConfig) GetHMACHasher(ctx context.Context) func() hash.Hash {
|
||||||
|
return d.fositeConfig.GetHMACHasher(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DynamicGlobalSecretConfig) GetGlobalSecret(ctx context.Context) ([]byte, error) {
|
||||||
|
// Always call keyFunc() without ever caching its value, because that is the whole point
|
||||||
|
// of this type. We want the global secret to be dynamic.
|
||||||
|
return d.keyFunc(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DynamicGlobalSecretConfig) GetRotatedGlobalSecrets(ctx context.Context) ([][]byte, error) {
|
||||||
|
// We don't support having multiple global secrets yet, but when we do we will need to implement this.
|
||||||
|
return nil, nil
|
||||||
|
}
|
@ -14,9 +14,14 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
accessTokenPrefix = "pin_at_" // "Pinniped access token" abbreviated.
|
pinAccessTokenPrefix = "pin_at_" // "Pinniped access token" abbreviated.
|
||||||
refreshTokenPrefix = "pin_rt_" // "Pinniped refresh token" abbreviated.
|
oryAccessTokenPrefix = "ory_at_"
|
||||||
authcodePrefix = "pin_ac_" // "Pinniped authorization code" abbreviated.
|
|
||||||
|
pinRefreshTokenPrefix = "pin_rt_" // "Pinniped refresh token" abbreviated.
|
||||||
|
oryRefreshTokenPrefix = "ory_rt_"
|
||||||
|
|
||||||
|
pinAuthcodePrefix = "pin_ac_" // "Pinniped authorization code" abbreviated.
|
||||||
|
oryAuthcodePrefix = "ory_ac_"
|
||||||
)
|
)
|
||||||
|
|
||||||
// dynamicOauth2HMACStrategy is an oauth2.CoreStrategy that can dynamically load an HMAC key to sign
|
// dynamicOauth2HMACStrategy is an oauth2.CoreStrategy that can dynamically load an HMAC key to sign
|
||||||
@ -29,16 +34,18 @@ const (
|
|||||||
// FederationDomain has a valid signing key.
|
// FederationDomain has a valid signing key.
|
||||||
//
|
//
|
||||||
// Tokens start with a custom prefix to make them identifiable as tokens when seen by a user
|
// Tokens start with a custom prefix to make them identifiable as tokens when seen by a user
|
||||||
// out of context, such as when accidentally committed to a GitHub repo.
|
// out of context, such as when accidentally committed to a GitHub repo. After we implemented the
|
||||||
|
// custom prefix feature, fosite later added the same feature, but did not make the prefix customizable.
|
||||||
|
// Therefore, this code has been updated to replace the fosite prefix with our custom prefix.
|
||||||
type dynamicOauth2HMACStrategy struct {
|
type dynamicOauth2HMACStrategy struct {
|
||||||
fositeConfig *compose.Config
|
fositeConfig *fosite.Config
|
||||||
keyFunc func() []byte
|
keyFunc func() []byte
|
||||||
}
|
}
|
||||||
|
|
||||||
var _ oauth2.CoreStrategy = &dynamicOauth2HMACStrategy{}
|
var _ oauth2.CoreStrategy = &dynamicOauth2HMACStrategy{}
|
||||||
|
|
||||||
func newDynamicOauth2HMACStrategy(
|
func newDynamicOauth2HMACStrategy(
|
||||||
fositeConfig *compose.Config,
|
fositeConfig *fosite.Config,
|
||||||
keyFunc func() []byte,
|
keyFunc func() []byte,
|
||||||
) *dynamicOauth2HMACStrategy {
|
) *dynamicOauth2HMACStrategy {
|
||||||
return &dynamicOauth2HMACStrategy{
|
return &dynamicOauth2HMACStrategy{
|
||||||
@ -47,17 +54,26 @@ func newDynamicOauth2HMACStrategy(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *dynamicOauth2HMACStrategy) AccessTokenSignature(token string) string {
|
func replacePrefix(s, prefixToReplace, newPrefix string) string {
|
||||||
return s.delegate().AccessTokenSignature(token)
|
return newPrefix + strings.TrimPrefix(s, prefixToReplace)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *dynamicOauth2HMACStrategy) AccessTokenSignature(ctx context.Context, token string) string {
|
||||||
|
return s.delegate().AccessTokenSignature(ctx, token)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *dynamicOauth2HMACStrategy) GenerateAccessToken(
|
func (s *dynamicOauth2HMACStrategy) GenerateAccessToken(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
requester fosite.Requester,
|
requester fosite.Requester,
|
||||||
) (token string, signature string, err error) {
|
) (string, string, error) {
|
||||||
token, sig, err := s.delegate().GenerateAccessToken(ctx, requester)
|
token, sig, err := s.delegate().GenerateAccessToken(ctx, requester)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
token = accessTokenPrefix + token
|
if !strings.HasPrefix(token, oryAccessTokenPrefix) {
|
||||||
|
// This would only happen if fosite changed how it generates tokens. Defensive programming here.
|
||||||
|
return "", "", errorsx.WithStack(fosite.ErrInvalidTokenFormat.
|
||||||
|
WithDebugf("Generated token does not have expected prefix"))
|
||||||
|
}
|
||||||
|
token = replacePrefix(token, oryAccessTokenPrefix, pinAccessTokenPrefix)
|
||||||
}
|
}
|
||||||
return token, sig, err
|
return token, sig, err
|
||||||
}
|
}
|
||||||
@ -66,25 +82,30 @@ func (s *dynamicOauth2HMACStrategy) ValidateAccessToken(
|
|||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
requester fosite.Requester,
|
requester fosite.Requester,
|
||||||
token string,
|
token string,
|
||||||
) (err error) {
|
) error {
|
||||||
if !strings.HasPrefix(token, accessTokenPrefix) {
|
if !strings.HasPrefix(token, pinAccessTokenPrefix) {
|
||||||
return errorsx.WithStack(fosite.ErrInvalidTokenFormat.
|
return errorsx.WithStack(fosite.ErrInvalidTokenFormat.
|
||||||
WithDebugf("Access token did not have prefix %q", accessTokenPrefix))
|
WithDebugf("Access token did not have prefix %q", pinAccessTokenPrefix))
|
||||||
}
|
}
|
||||||
return s.delegate().ValidateAccessToken(ctx, requester, token[len(accessTokenPrefix):])
|
return s.delegate().ValidateAccessToken(ctx, requester, replacePrefix(token, pinAccessTokenPrefix, oryAccessTokenPrefix))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *dynamicOauth2HMACStrategy) RefreshTokenSignature(token string) string {
|
func (s *dynamicOauth2HMACStrategy) RefreshTokenSignature(ctx context.Context, token string) string {
|
||||||
return s.delegate().RefreshTokenSignature(token)
|
return s.delegate().RefreshTokenSignature(ctx, token)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *dynamicOauth2HMACStrategy) GenerateRefreshToken(
|
func (s *dynamicOauth2HMACStrategy) GenerateRefreshToken(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
requester fosite.Requester,
|
requester fosite.Requester,
|
||||||
) (token string, signature string, err error) {
|
) (string, string, error) {
|
||||||
token, sig, err := s.delegate().GenerateRefreshToken(ctx, requester)
|
token, sig, err := s.delegate().GenerateRefreshToken(ctx, requester)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
token = refreshTokenPrefix + token
|
if !strings.HasPrefix(token, oryRefreshTokenPrefix) {
|
||||||
|
// This would only happen if fosite changed how it generates tokens. Defensive programming here.
|
||||||
|
return "", "", errorsx.WithStack(fosite.ErrInvalidTokenFormat.
|
||||||
|
WithDebugf("Generated token does not have expected prefix"))
|
||||||
|
}
|
||||||
|
token = replacePrefix(token, oryRefreshTokenPrefix, pinRefreshTokenPrefix)
|
||||||
}
|
}
|
||||||
return token, sig, err
|
return token, sig, err
|
||||||
}
|
}
|
||||||
@ -93,25 +114,30 @@ func (s *dynamicOauth2HMACStrategy) ValidateRefreshToken(
|
|||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
requester fosite.Requester,
|
requester fosite.Requester,
|
||||||
token string,
|
token string,
|
||||||
) (err error) {
|
) error {
|
||||||
if !strings.HasPrefix(token, refreshTokenPrefix) {
|
if !strings.HasPrefix(token, pinRefreshTokenPrefix) {
|
||||||
return errorsx.WithStack(fosite.ErrInvalidTokenFormat.
|
return errorsx.WithStack(fosite.ErrInvalidTokenFormat.
|
||||||
WithDebugf("Refresh token did not have prefix %q", refreshTokenPrefix))
|
WithDebugf("Refresh token did not have prefix %q", pinRefreshTokenPrefix))
|
||||||
}
|
}
|
||||||
return s.delegate().ValidateRefreshToken(ctx, requester, token[len(refreshTokenPrefix):])
|
return s.delegate().ValidateRefreshToken(ctx, requester, replacePrefix(token, pinRefreshTokenPrefix, oryRefreshTokenPrefix))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *dynamicOauth2HMACStrategy) AuthorizeCodeSignature(token string) string {
|
func (s *dynamicOauth2HMACStrategy) AuthorizeCodeSignature(ctx context.Context, token string) string {
|
||||||
return s.delegate().AuthorizeCodeSignature(token)
|
return s.delegate().AuthorizeCodeSignature(ctx, token)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *dynamicOauth2HMACStrategy) GenerateAuthorizeCode(
|
func (s *dynamicOauth2HMACStrategy) GenerateAuthorizeCode(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
requester fosite.Requester,
|
requester fosite.Requester,
|
||||||
) (token string, signature string, err error) {
|
) (string, string, error) {
|
||||||
authcode, sig, err := s.delegate().GenerateAuthorizeCode(ctx, requester)
|
authcode, sig, err := s.delegate().GenerateAuthorizeCode(ctx, requester)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
authcode = authcodePrefix + authcode
|
if !strings.HasPrefix(authcode, oryAuthcodePrefix) {
|
||||||
|
// This would only happen if fosite changed how it generates tokens. Defensive programming here.
|
||||||
|
return "", "", errorsx.WithStack(fosite.ErrInvalidTokenFormat.
|
||||||
|
WithDebugf("Generated token does not have expected prefix"))
|
||||||
|
}
|
||||||
|
authcode = replacePrefix(authcode, oryAuthcodePrefix, pinAuthcodePrefix)
|
||||||
}
|
}
|
||||||
return authcode, sig, err
|
return authcode, sig, err
|
||||||
}
|
}
|
||||||
@ -120,14 +146,14 @@ func (s *dynamicOauth2HMACStrategy) ValidateAuthorizeCode(
|
|||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
requester fosite.Requester,
|
requester fosite.Requester,
|
||||||
token string,
|
token string,
|
||||||
) (err error) {
|
) error {
|
||||||
if !strings.HasPrefix(token, authcodePrefix) {
|
if !strings.HasPrefix(token, pinAuthcodePrefix) {
|
||||||
return errorsx.WithStack(fosite.ErrInvalidTokenFormat.
|
return errorsx.WithStack(fosite.ErrInvalidTokenFormat.
|
||||||
WithDebugf("Authorization code did not have prefix %q", authcodePrefix))
|
WithDebugf("Authorization code did not have prefix %q", pinAuthcodePrefix))
|
||||||
}
|
}
|
||||||
return s.delegate().ValidateAuthorizeCode(ctx, requester, token[len(authcodePrefix):])
|
return s.delegate().ValidateAuthorizeCode(ctx, requester, replacePrefix(token, pinAuthcodePrefix, oryAuthcodePrefix))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *dynamicOauth2HMACStrategy) delegate() *oauth2.HMACSHAStrategy {
|
func (s *dynamicOauth2HMACStrategy) delegate() *oauth2.HMACSHAStrategy {
|
||||||
return compose.NewOAuth2HMACStrategy(s.fositeConfig, s.keyFunc(), nil)
|
return compose.NewOAuth2HMACStrategy(NewDynamicGlobalSecretConfig(s.fositeConfig, s.keyFunc))
|
||||||
}
|
}
|
||||||
|
@ -11,20 +11,19 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/ory/fosite"
|
"github.com/ory/fosite"
|
||||||
"github.com/ory/fosite/compose"
|
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestDynamicOauth2HMACStrategy_Signatures(t *testing.T) {
|
func TestDynamicOauth2HMACStrategy_Signatures(t *testing.T) {
|
||||||
s := &dynamicOauth2HMACStrategy{
|
s := newDynamicOauth2HMACStrategy(
|
||||||
fositeConfig: &compose.Config{}, // defaults are good enough for this unit test
|
&fosite.Config{}, // defaults are good enough for this unit test
|
||||||
keyFunc: func() []byte { return []byte("12345678901234567890123456789012") },
|
func() []byte { return []byte("12345678901234567890123456789012") }, // 32 character secret key
|
||||||
}
|
)
|
||||||
|
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
token string
|
token string
|
||||||
signatureFunc func(token string) (signature string)
|
signatureFunc func(ctx context.Context, token string) (signature string)
|
||||||
wantSignature string
|
wantSignature string
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
@ -52,21 +51,21 @@ func TestDynamicOauth2HMACStrategy_Signatures(t *testing.T) {
|
|||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
require.Equal(t, tt.wantSignature, tt.signatureFunc(tt.token))
|
require.Equal(t, tt.wantSignature, tt.signatureFunc(context.Background(), tt.token))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestDynamicOauth2HMACStrategy_Generate(t *testing.T) {
|
func TestDynamicOauth2HMACStrategy_Generate(t *testing.T) {
|
||||||
s := &dynamicOauth2HMACStrategy{
|
s := newDynamicOauth2HMACStrategy(
|
||||||
fositeConfig: &compose.Config{}, // defaults are good enough for this unit test
|
&fosite.Config{}, // defaults are good enough for this unit test
|
||||||
keyFunc: func() []byte { return []byte("12345678901234567890123456789012") }, // 32 character secret key
|
func() []byte { return []byte("12345678901234567890123456789012") }, // 32 character secret key
|
||||||
}
|
)
|
||||||
|
|
||||||
generateTokenErrorCausingStrategy := &dynamicOauth2HMACStrategy{
|
generateTokenErrorCausingStrategy := newDynamicOauth2HMACStrategy(
|
||||||
fositeConfig: &compose.Config{},
|
&fosite.Config{},
|
||||||
keyFunc: func() []byte { return []byte("too_short_causes_error") }, // secret key is below required 32 characters
|
func() []byte { return []byte("too_short_causes_error") }, // secret key is below required 32 characters
|
||||||
}
|
)
|
||||||
|
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
@ -135,10 +134,10 @@ func TestDynamicOauth2HMACStrategy_Generate(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestDynamicOauth2HMACStrategy_Validate(t *testing.T) {
|
func TestDynamicOauth2HMACStrategy_Validate(t *testing.T) {
|
||||||
s := &dynamicOauth2HMACStrategy{
|
s := newDynamicOauth2HMACStrategy(
|
||||||
fositeConfig: &compose.Config{}, // defaults are good enough for this unit test
|
&fosite.Config{}, // defaults are good enough for this unit test
|
||||||
keyFunc: func() []byte { return []byte("12345678901234567890123456789012") },
|
func() []byte { return []byte("12345678901234567890123456789012") }, // 32 character secret key
|
||||||
}
|
)
|
||||||
|
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
// Copyright 2020-2021 the Pinniped contributors. All Rights Reserved.
|
// Copyright 2020-2022 the Pinniped contributors. All Rights Reserved.
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
package oidc
|
package oidc
|
||||||
@ -7,6 +7,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"crypto/ecdsa"
|
"crypto/ecdsa"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/ory/fosite"
|
"github.com/ory/fosite"
|
||||||
"github.com/ory/fosite/compose"
|
"github.com/ory/fosite/compose"
|
||||||
@ -26,14 +27,14 @@ import (
|
|||||||
// could have an invariant that routes to an FederationDomain's endpoints are only wired up if an
|
// could have an invariant that routes to an FederationDomain's endpoints are only wired up if an
|
||||||
// FederationDomain has a valid signing key.
|
// FederationDomain has a valid signing key.
|
||||||
type dynamicOpenIDConnectECDSAStrategy struct {
|
type dynamicOpenIDConnectECDSAStrategy struct {
|
||||||
fositeConfig *compose.Config
|
fositeConfig *fosite.Config
|
||||||
jwksProvider jwks.DynamicJWKSProvider
|
jwksProvider jwks.DynamicJWKSProvider
|
||||||
}
|
}
|
||||||
|
|
||||||
var _ openid.OpenIDConnectTokenStrategy = &dynamicOpenIDConnectECDSAStrategy{}
|
var _ openid.OpenIDConnectTokenStrategy = &dynamicOpenIDConnectECDSAStrategy{}
|
||||||
|
|
||||||
func newDynamicOpenIDConnectECDSAStrategy(
|
func newDynamicOpenIDConnectECDSAStrategy(
|
||||||
fositeConfig *compose.Config,
|
fositeConfig *fosite.Config,
|
||||||
jwksProvider jwks.DynamicJWKSProvider,
|
jwksProvider jwks.DynamicJWKSProvider,
|
||||||
) *dynamicOpenIDConnectECDSAStrategy {
|
) *dynamicOpenIDConnectECDSAStrategy {
|
||||||
return &dynamicOpenIDConnectECDSAStrategy{
|
return &dynamicOpenIDConnectECDSAStrategy{
|
||||||
@ -44,6 +45,7 @@ func newDynamicOpenIDConnectECDSAStrategy(
|
|||||||
|
|
||||||
func (s *dynamicOpenIDConnectECDSAStrategy) GenerateIDToken(
|
func (s *dynamicOpenIDConnectECDSAStrategy) GenerateIDToken(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
|
lifespan time.Duration,
|
||||||
requester fosite.Requester,
|
requester fosite.Requester,
|
||||||
) (string, error) {
|
) (string, error) {
|
||||||
_, activeJwk := s.jwksProvider.GetJWKS(s.fositeConfig.IDTokenIssuer)
|
_, activeJwk := s.jwksProvider.GetJWKS(s.fositeConfig.IDTokenIssuer)
|
||||||
@ -67,5 +69,10 @@ func (s *dynamicOpenIDConnectECDSAStrategy) GenerateIDToken(
|
|||||||
return "", fosite.ErrServerError.WithWrap(constable.Error("JWK must be of type ecdsa"))
|
return "", fosite.ErrServerError.WithWrap(constable.Error("JWK must be of type ecdsa"))
|
||||||
}
|
}
|
||||||
|
|
||||||
return compose.NewOpenIDConnectECDSAStrategy(s.fositeConfig, key).GenerateIDToken(ctx, requester)
|
keyGetter := func(context.Context) (interface{}, error) {
|
||||||
|
return key, nil
|
||||||
|
}
|
||||||
|
strategy := compose.NewOpenIDConnectStrategy(keyGetter, s.fositeConfig)
|
||||||
|
|
||||||
|
return strategy.GenerateIDToken(ctx, lifespan, requester)
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
// Copyright 2020-2021 the Pinniped contributors. All Rights Reserved.
|
// Copyright 2020-2022 the Pinniped contributors. All Rights Reserved.
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
package oidc
|
package oidc
|
||||||
@ -12,9 +12,9 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"net/url"
|
"net/url"
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/ory/fosite"
|
"github.com/ory/fosite"
|
||||||
"github.com/ory/fosite/compose"
|
|
||||||
"github.com/ory/fosite/handler/openid"
|
"github.com/ory/fosite/handler/openid"
|
||||||
"github.com/ory/fosite/token/jwt"
|
"github.com/ory/fosite/token/jwt"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
@ -95,7 +95,7 @@ func TestDynamicOpenIDConnectECDSAStrategy(t *testing.T) {
|
|||||||
test.jwksProvider(jwksProvider)
|
test.jwksProvider(jwksProvider)
|
||||||
}
|
}
|
||||||
s := newDynamicOpenIDConnectECDSAStrategy(
|
s := newDynamicOpenIDConnectECDSAStrategy(
|
||||||
&compose.Config{IDTokenIssuer: test.issuer},
|
&fosite.Config{IDTokenIssuer: test.issuer},
|
||||||
jwksProvider,
|
jwksProvider,
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -114,7 +114,7 @@ func TestDynamicOpenIDConnectECDSAStrategy(t *testing.T) {
|
|||||||
"nonce": {goodNonce},
|
"nonce": {goodNonce},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
idToken, err := s.GenerateIDToken(context.Background(), requester)
|
idToken, err := s.GenerateIDToken(context.Background(), 2*time.Hour, requester)
|
||||||
if test.wantErrorType != nil {
|
if test.wantErrorType != nil {
|
||||||
require.True(t, errors.Is(err, test.wantErrorType))
|
require.True(t, errors.Is(err, test.wantErrorType))
|
||||||
require.EqualError(t, err.(*fosite.RFC6749Error).Cause(), test.wantErrorCause)
|
require.EqualError(t, err.(*fosite.RFC6749Error).Cause(), test.wantErrorCause)
|
||||||
|
@ -5,10 +5,12 @@
|
|||||||
package oidc
|
package oidc
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"crypto/subtle"
|
"crypto/subtle"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"net/url"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/felixge/httpsnoop"
|
"github.com/felixge/httpsnoop"
|
||||||
@ -195,7 +197,11 @@ func FositeOauth2Helper(
|
|||||||
jwksProvider jwks.DynamicJWKSProvider,
|
jwksProvider jwks.DynamicJWKSProvider,
|
||||||
timeoutsConfiguration TimeoutsConfiguration,
|
timeoutsConfiguration TimeoutsConfiguration,
|
||||||
) fosite.OAuth2Provider {
|
) fosite.OAuth2Provider {
|
||||||
oauthConfig := &compose.Config{
|
isRedirectURISecureStrict := func(_ context.Context, uri *url.URL) bool {
|
||||||
|
return fosite.IsRedirectURISecureStrict(uri)
|
||||||
|
}
|
||||||
|
|
||||||
|
oauthConfig := &fosite.Config{
|
||||||
IDTokenIssuer: issuer,
|
IDTokenIssuer: issuer,
|
||||||
|
|
||||||
AuthorizeCodeLifespan: timeoutsConfiguration.AuthorizeCodeLifespan,
|
AuthorizeCodeLifespan: timeoutsConfiguration.AuthorizeCodeLifespan,
|
||||||
@ -217,10 +223,16 @@ func FositeOauth2Helper(
|
|||||||
MinParameterEntropy: fosite.MinParameterEntropy,
|
MinParameterEntropy: fosite.MinParameterEntropy,
|
||||||
|
|
||||||
// do not allow custom scheme redirects, only https and http (on loopback)
|
// do not allow custom scheme redirects, only https and http (on loopback)
|
||||||
RedirectSecureChecker: fosite.IsRedirectURISecureStrict,
|
RedirectSecureChecker: isRedirectURISecureStrict,
|
||||||
|
|
||||||
|
// html template for rendering the authorization response when the request has response_mode=form_post
|
||||||
|
FormPostHTMLTemplate: formposthtml.Template(),
|
||||||
|
|
||||||
|
// defaults to using BCrypt when nil
|
||||||
|
ClientSecretsHasher: nil,
|
||||||
}
|
}
|
||||||
|
|
||||||
provider := compose.Compose(
|
oAuth2Provider := compose.Compose(
|
||||||
oauthConfig,
|
oauthConfig,
|
||||||
oauthStore,
|
oauthStore,
|
||||||
&compose.CommonStrategy{
|
&compose.CommonStrategy{
|
||||||
@ -228,7 +240,6 @@ func FositeOauth2Helper(
|
|||||||
CoreStrategy: newDynamicOauth2HMACStrategy(oauthConfig, hmacSecretOfLengthAtLeast32Func),
|
CoreStrategy: newDynamicOauth2HMACStrategy(oauthConfig, hmacSecretOfLengthAtLeast32Func),
|
||||||
OpenIDConnectTokenStrategy: newDynamicOpenIDConnectECDSAStrategy(oauthConfig, jwksProvider),
|
OpenIDConnectTokenStrategy: newDynamicOpenIDConnectECDSAStrategy(oauthConfig, jwksProvider),
|
||||||
},
|
},
|
||||||
nil, // hasher, defaults to using BCrypt when nil. Used for hashing client secrets.
|
|
||||||
compose.OAuth2AuthorizeExplicitFactory,
|
compose.OAuth2AuthorizeExplicitFactory,
|
||||||
compose.OAuth2RefreshTokenGrantFactory,
|
compose.OAuth2RefreshTokenGrantFactory,
|
||||||
compose.OpenIDConnectExplicitFactory,
|
compose.OpenIDConnectExplicitFactory,
|
||||||
@ -236,8 +247,8 @@ func FositeOauth2Helper(
|
|||||||
compose.OAuth2PKCEFactory,
|
compose.OAuth2PKCEFactory,
|
||||||
TokenExchangeFactory, // handle the "urn:ietf:params:oauth:grant-type:token-exchange" grant type
|
TokenExchangeFactory, // handle the "urn:ietf:params:oauth:grant-type:token-exchange" grant type
|
||||||
)
|
)
|
||||||
provider.(*fosite.Fosite).FormPostHTMLTemplate = formposthtml.Template()
|
|
||||||
return provider
|
return oAuth2Provider
|
||||||
}
|
}
|
||||||
|
|
||||||
// FositeErrorForLog generates a list of information about the provided Fosite error that can be
|
// FositeErrorForLog generates a list of information about the provided Fosite error that can be
|
||||||
@ -403,7 +414,7 @@ func FindUpstreamIDPByNameAndType(
|
|||||||
|
|
||||||
// WriteAuthorizeError writes an authorization error as it should be returned by the authorization endpoint and other
|
// WriteAuthorizeError writes an authorization error as it should be returned by the authorization endpoint and other
|
||||||
// similar endpoints that are the end of the downstream authcode flow. Errors responses are written in the usual fosite style.
|
// similar endpoints that are the end of the downstream authcode flow. Errors responses are written in the usual fosite style.
|
||||||
func WriteAuthorizeError(w http.ResponseWriter, oauthHelper fosite.OAuth2Provider, authorizeRequester fosite.AuthorizeRequester, err error, isBrowserless bool) {
|
func WriteAuthorizeError(r *http.Request, w http.ResponseWriter, oauthHelper fosite.OAuth2Provider, authorizeRequester fosite.AuthorizeRequester, err error, isBrowserless bool) {
|
||||||
if plog.Enabled(plog.LevelTrace) {
|
if plog.Enabled(plog.LevelTrace) {
|
||||||
// When trace level logging is enabled, include the stack trace in the log message.
|
// When trace level logging is enabled, include the stack trace in the log message.
|
||||||
keysAndValues := FositeErrorForLog(err)
|
keysAndValues := FositeErrorForLog(err)
|
||||||
@ -420,7 +431,7 @@ func WriteAuthorizeError(w http.ResponseWriter, oauthHelper fosite.OAuth2Provide
|
|||||||
w = rewriteStatusSeeOtherToStatusFoundForBrowserless(w)
|
w = rewriteStatusSeeOtherToStatusFoundForBrowserless(w)
|
||||||
}
|
}
|
||||||
// Return an error according to OIDC spec 3.1.2.6 (second paragraph).
|
// Return an error according to OIDC spec 3.1.2.6 (second paragraph).
|
||||||
oauthHelper.WriteAuthorizeError(w, authorizeRequester, err)
|
oauthHelper.WriteAuthorizeError(r.Context(), w, authorizeRequester, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// PerformAuthcodeRedirect successfully completes a downstream login by creating a session and
|
// PerformAuthcodeRedirect successfully completes a downstream login by creating a session and
|
||||||
@ -437,13 +448,13 @@ func PerformAuthcodeRedirect(
|
|||||||
authorizeResponder, err := oauthHelper.NewAuthorizeResponse(r.Context(), authorizeRequester, openIDSession)
|
authorizeResponder, err := oauthHelper.NewAuthorizeResponse(r.Context(), authorizeRequester, openIDSession)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
plog.WarningErr("error while generating and saving authcode", err, "fositeErr", FositeErrorForLog(err))
|
plog.WarningErr("error while generating and saving authcode", err, "fositeErr", FositeErrorForLog(err))
|
||||||
WriteAuthorizeError(w, oauthHelper, authorizeRequester, err, isBrowserless)
|
WriteAuthorizeError(r, w, oauthHelper, authorizeRequester, err, isBrowserless)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if isBrowserless {
|
if isBrowserless {
|
||||||
w = rewriteStatusSeeOtherToStatusFoundForBrowserless(w)
|
w = rewriteStatusSeeOtherToStatusFoundForBrowserless(w)
|
||||||
}
|
}
|
||||||
oauthHelper.WriteAuthorizeResponse(w, authorizeRequester, authorizeResponder)
|
oauthHelper.WriteAuthorizeResponse(r.Context(), w, authorizeRequester, authorizeResponder)
|
||||||
}
|
}
|
||||||
|
|
||||||
func rewriteStatusSeeOtherToStatusFoundForBrowserless(w http.ResponseWriter) http.ResponseWriter {
|
func rewriteStatusSeeOtherToStatusFoundForBrowserless(w http.ResponseWriter) http.ResponseWriter {
|
||||||
|
@ -35,7 +35,7 @@ func NewHandler(
|
|||||||
accessRequest, err := oauthHelper.NewAccessRequest(r.Context(), r, session)
|
accessRequest, err := oauthHelper.NewAccessRequest(r.Context(), r, session)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
plog.Info("token request error", oidc.FositeErrorForLog(err)...)
|
plog.Info("token request error", oidc.FositeErrorForLog(err)...)
|
||||||
oauthHelper.WriteAccessError(w, accessRequest, err)
|
oauthHelper.WriteAccessError(r.Context(), w, accessRequest, err)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -48,7 +48,7 @@ func NewHandler(
|
|||||||
err = upstreamRefresh(r.Context(), accessRequest, idpLister)
|
err = upstreamRefresh(r.Context(), accessRequest, idpLister)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
plog.Info("upstream refresh error", oidc.FositeErrorForLog(err)...)
|
plog.Info("upstream refresh error", oidc.FositeErrorForLog(err)...)
|
||||||
oauthHelper.WriteAccessError(w, accessRequest, err)
|
oauthHelper.WriteAccessError(r.Context(), w, accessRequest, err)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -68,11 +68,11 @@ func NewHandler(
|
|||||||
accessResponse, err := oauthHelper.NewAccessResponse(r.Context(), accessRequest)
|
accessResponse, err := oauthHelper.NewAccessResponse(r.Context(), accessRequest)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
plog.Info("token response error", oidc.FositeErrorForLog(err)...)
|
plog.Info("token response error", oidc.FositeErrorForLog(err)...)
|
||||||
oauthHelper.WriteAccessError(w, accessRequest, err)
|
oauthHelper.WriteAccessError(r.Context(), w, accessRequest, err)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
oauthHelper.WriteAccessResponse(w, accessRequest, accessResponse)
|
oauthHelper.WriteAccessResponse(r.Context(), w, accessRequest, accessResponse)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
|
@ -3735,8 +3735,6 @@ func TestRefreshGrant(t *testing.T) {
|
|||||||
require.Len(t, reqContextWarningRecorder.Warnings, 0, "wanted no warnings on the request context, but found some")
|
require.Len(t, reqContextWarningRecorder.Warnings, 0, "wanted no warnings on the request context, but found some")
|
||||||
}
|
}
|
||||||
|
|
||||||
// The bug in fosite that prevents at_hash from appearing in the initial ID token does not impact the refreshed ID token
|
|
||||||
wantAtHashClaimInIDToken := true
|
|
||||||
// Refreshed ID tokens do not include the nonce from the original auth request
|
// Refreshed ID tokens do not include the nonce from the original auth request
|
||||||
wantNonceValueInIDToken := false
|
wantNonceValueInIDToken := false
|
||||||
|
|
||||||
@ -3745,7 +3743,6 @@ func TestRefreshGrant(t *testing.T) {
|
|||||||
test.authcodeExchange.want.wantUsername, // the old username from the initial login
|
test.authcodeExchange.want.wantUsername, // the old username from the initial login
|
||||||
test.authcodeExchange.want.wantGroups, // the old groups from the initial login
|
test.authcodeExchange.want.wantGroups, // the old groups from the initial login
|
||||||
test.authcodeExchange.customSessionData, // the old custom session data from the initial login
|
test.authcodeExchange.customSessionData, // the old custom session data from the initial login
|
||||||
wantAtHashClaimInIDToken,
|
|
||||||
wantNonceValueInIDToken,
|
wantNonceValueInIDToken,
|
||||||
refreshResponse,
|
refreshResponse,
|
||||||
authCode,
|
authCode,
|
||||||
@ -3784,8 +3781,9 @@ func TestRefreshGrant(t *testing.T) {
|
|||||||
err = secondIDTokenDecoded.UnsafeClaimsWithoutVerification(&claimsOfSecondIDToken)
|
err = secondIDTokenDecoded.UnsafeClaimsWithoutVerification(&claimsOfSecondIDToken)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
requireClaimsAreNotEqual(t, "jti", claimsOfFirstIDToken, claimsOfSecondIDToken) // JWT ID
|
requireClaimsAreNotEqual(t, "jti", claimsOfFirstIDToken, claimsOfSecondIDToken) // JWT ID
|
||||||
requireClaimsAreNotEqual(t, "exp", claimsOfFirstIDToken, claimsOfSecondIDToken) // expires at
|
requireClaimsAreNotEqual(t, "at_hash", claimsOfFirstIDToken, claimsOfSecondIDToken) // access token hash
|
||||||
|
requireClaimsAreNotEqual(t, "exp", claimsOfFirstIDToken, claimsOfSecondIDToken) // expires at
|
||||||
require.Greater(t, claimsOfSecondIDToken["exp"], claimsOfFirstIDToken["exp"])
|
require.Greater(t, claimsOfSecondIDToken["exp"], claimsOfFirstIDToken["exp"])
|
||||||
requireClaimsAreNotEqual(t, "iat", claimsOfFirstIDToken, claimsOfSecondIDToken) // issued at
|
requireClaimsAreNotEqual(t, "iat", claimsOfFirstIDToken, claimsOfSecondIDToken) // issued at
|
||||||
require.Greater(t, claimsOfSecondIDToken["iat"], claimsOfFirstIDToken["iat"])
|
require.Greater(t, claimsOfSecondIDToken["iat"], claimsOfFirstIDToken["iat"])
|
||||||
@ -3880,15 +3878,13 @@ func exchangeAuthcodeForTokens(
|
|||||||
t.Logf("response: %#v", rsp)
|
t.Logf("response: %#v", rsp)
|
||||||
t.Logf("response body: %q", rsp.Body.String())
|
t.Logf("response body: %q", rsp.Body.String())
|
||||||
|
|
||||||
wantAtHashClaimInIDToken := false // due to a bug in fosite, the at_hash claim is not filled in during authcode exchange
|
wantNonceValueInIDToken := true // ID tokens returned by the authcode exchange must include the nonce from the auth request (unlike refreshed ID tokens)
|
||||||
wantNonceValueInIDToken := true // ID tokens returned by the authcode exchange must include the nonce from the auth request (unlike refreshed ID tokens)
|
|
||||||
|
|
||||||
requireTokenEndpointBehavior(t,
|
requireTokenEndpointBehavior(t,
|
||||||
test.want,
|
test.want,
|
||||||
test.want.wantUsername, // the old username from the initial login
|
test.want.wantUsername, // the old username from the initial login
|
||||||
test.want.wantGroups, // the old groups from the initial login
|
test.want.wantGroups, // the old groups from the initial login
|
||||||
test.customSessionData, // the old custom session data from the initial login
|
test.customSessionData, // the old custom session data from the initial login
|
||||||
wantAtHashClaimInIDToken,
|
|
||||||
wantNonceValueInIDToken,
|
wantNonceValueInIDToken,
|
||||||
rsp,
|
rsp,
|
||||||
authCode,
|
authCode,
|
||||||
@ -3907,7 +3903,6 @@ func requireTokenEndpointBehavior(
|
|||||||
oldUsername string,
|
oldUsername string,
|
||||||
oldGroups []string,
|
oldGroups []string,
|
||||||
oldCustomSessionData *psession.CustomSessionData,
|
oldCustomSessionData *psession.CustomSessionData,
|
||||||
wantAtHashClaimInIDToken bool,
|
|
||||||
wantNonceValueInIDToken bool,
|
wantNonceValueInIDToken bool,
|
||||||
tokenEndpointResponse *httptest.ResponseRecorder,
|
tokenEndpointResponse *httptest.ResponseRecorder,
|
||||||
authCode string,
|
authCode string,
|
||||||
@ -3942,7 +3937,7 @@ func requireTokenEndpointBehavior(
|
|||||||
expectedNumberOfIDSessionsStored := 0
|
expectedNumberOfIDSessionsStored := 0
|
||||||
if wantIDToken {
|
if wantIDToken {
|
||||||
expectedNumberOfIDSessionsStored = 1
|
expectedNumberOfIDSessionsStored = 1
|
||||||
requireValidIDToken(t, parsedResponseBody, jwtSigningKey, test.wantClientID, wantAtHashClaimInIDToken, wantNonceValueInIDToken, test.wantUsername, test.wantGroups, parsedResponseBody["access_token"].(string), requestTime)
|
requireValidIDToken(t, parsedResponseBody, jwtSigningKey, test.wantClientID, wantNonceValueInIDToken, test.wantUsername, test.wantGroups, parsedResponseBody["access_token"].(string), requestTime)
|
||||||
}
|
}
|
||||||
if wantRefreshToken {
|
if wantRefreshToken {
|
||||||
requireValidRefreshTokenStorage(t, parsedResponseBody, oauthStore, test.wantClientID, test.wantRequestedScopes, test.wantGrantedScopes, test.wantUsername, test.wantGroups, test.wantCustomSessionDataStored, secrets, requestTime)
|
requireValidRefreshTokenStorage(t, parsedResponseBody, oauthStore, test.wantClientID, test.wantRequestedScopes, test.wantGrantedScopes, test.wantUsername, test.wantGroups, test.wantCustomSessionDataStored, secrets, requestTime)
|
||||||
@ -4509,7 +4504,6 @@ func requireValidIDToken(
|
|||||||
body map[string]interface{},
|
body map[string]interface{},
|
||||||
jwtSigningKey *ecdsa.PrivateKey,
|
jwtSigningKey *ecdsa.PrivateKey,
|
||||||
wantClientID string,
|
wantClientID string,
|
||||||
wantAtHashClaimInIDToken bool,
|
|
||||||
wantNonceValueInIDToken bool,
|
wantNonceValueInIDToken bool,
|
||||||
wantUsernameInIDToken string,
|
wantUsernameInIDToken string,
|
||||||
wantGroupsInIDToken []string,
|
wantGroupsInIDToken []string,
|
||||||
@ -4541,13 +4535,7 @@ func requireValidIDToken(
|
|||||||
Username string `json:"username"`
|
Username string `json:"username"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Note that there is a bug in fosite which prevents the `at_hash` claim from appearing in this ID token
|
idTokenFields := []string{"sub", "aud", "iss", "jti", "auth_time", "exp", "iat", "rat", "azp", "at_hash"}
|
||||||
// during the initial authcode exchange, but does not prevent `at_hash` from appearing in the refreshed ID token.
|
|
||||||
// We can add a workaround for this later.
|
|
||||||
idTokenFields := []string{"sub", "aud", "iss", "jti", "auth_time", "exp", "iat", "rat", "azp"}
|
|
||||||
if wantAtHashClaimInIDToken {
|
|
||||||
idTokenFields = append(idTokenFields, "at_hash")
|
|
||||||
}
|
|
||||||
if wantNonceValueInIDToken {
|
if wantNonceValueInIDToken {
|
||||||
idTokenFields = append(idTokenFields, "nonce")
|
idTokenFields = append(idTokenFields, "nonce")
|
||||||
}
|
}
|
||||||
@ -4590,12 +4578,8 @@ func requireValidIDToken(
|
|||||||
testutil.RequireTimeInDelta(t, goodRequestedAtTime, requestedAt, timeComparisonFudge)
|
testutil.RequireTimeInDelta(t, goodRequestedAtTime, requestedAt, timeComparisonFudge)
|
||||||
testutil.RequireTimeInDelta(t, goodAuthTime, authTime, timeComparisonFudge)
|
testutil.RequireTimeInDelta(t, goodAuthTime, authTime, timeComparisonFudge)
|
||||||
|
|
||||||
if wantAtHashClaimInIDToken {
|
require.NotEmpty(t, actualAccessToken)
|
||||||
require.NotEmpty(t, actualAccessToken)
|
require.Equal(t, hashAccessToken(actualAccessToken), claims.AccessTokenHash)
|
||||||
require.Equal(t, hashAccessToken(actualAccessToken), claims.AccessTokenHash)
|
|
||||||
} else {
|
|
||||||
require.Empty(t, claims.AccessTokenHash)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func deepCopyRequestForm(r *http.Request) *http.Request {
|
func deepCopyRequestForm(r *http.Request) *http.Request {
|
||||||
|
@ -9,7 +9,6 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/ory/fosite"
|
"github.com/ory/fosite"
|
||||||
"github.com/ory/fosite/compose"
|
|
||||||
"github.com/ory/fosite/handler/oauth2"
|
"github.com/ory/fosite/handler/oauth2"
|
||||||
"github.com/ory/fosite/handler/openid"
|
"github.com/ory/fosite/handler/openid"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
@ -28,11 +27,12 @@ type stsParams struct {
|
|||||||
requestedAudience string
|
requestedAudience string
|
||||||
}
|
}
|
||||||
|
|
||||||
func TokenExchangeFactory(config *compose.Config, storage interface{}, strategy interface{}) interface{} {
|
func TokenExchangeFactory(config fosite.Configurator, storage interface{}, strategy interface{}) interface{} {
|
||||||
return &TokenExchangeHandler{
|
return &TokenExchangeHandler{
|
||||||
idTokenStrategy: strategy.(openid.OpenIDConnectTokenStrategy),
|
idTokenStrategy: strategy.(openid.OpenIDConnectTokenStrategy),
|
||||||
accessTokenStrategy: strategy.(oauth2.AccessTokenStrategy),
|
accessTokenStrategy: strategy.(oauth2.AccessTokenStrategy),
|
||||||
accessTokenStorage: storage.(oauth2.AccessTokenStorage),
|
accessTokenStorage: storage.(oauth2.AccessTokenStorage),
|
||||||
|
fositeConfig: config,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -40,12 +40,13 @@ type TokenExchangeHandler struct {
|
|||||||
idTokenStrategy openid.OpenIDConnectTokenStrategy
|
idTokenStrategy openid.OpenIDConnectTokenStrategy
|
||||||
accessTokenStrategy oauth2.AccessTokenStrategy
|
accessTokenStrategy oauth2.AccessTokenStrategy
|
||||||
accessTokenStorage oauth2.AccessTokenStorage
|
accessTokenStorage oauth2.AccessTokenStorage
|
||||||
|
fositeConfig fosite.Configurator
|
||||||
}
|
}
|
||||||
|
|
||||||
var _ fosite.TokenEndpointHandler = (*TokenExchangeHandler)(nil)
|
var _ fosite.TokenEndpointHandler = (*TokenExchangeHandler)(nil)
|
||||||
|
|
||||||
func (t *TokenExchangeHandler) HandleTokenEndpointRequest(ctx context.Context, requester fosite.AccessRequester) error {
|
func (t *TokenExchangeHandler) HandleTokenEndpointRequest(ctx context.Context, requester fosite.AccessRequester) error {
|
||||||
if !t.CanHandleTokenEndpointRequest(requester) {
|
if !t.CanHandleTokenEndpointRequest(ctx, requester) {
|
||||||
return errors.WithStack(fosite.ErrUnknownRequest)
|
return errors.WithStack(fosite.ErrUnknownRequest)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
@ -110,7 +111,12 @@ func (t *TokenExchangeHandler) PopulateTokenEndpointResponse(ctx context.Context
|
|||||||
func (t *TokenExchangeHandler) mintJWT(ctx context.Context, requester fosite.Requester, audience string) (string, error) {
|
func (t *TokenExchangeHandler) mintJWT(ctx context.Context, requester fosite.Requester, audience string) (string, error) {
|
||||||
downscoped := fosite.NewAccessRequest(requester.GetSession())
|
downscoped := fosite.NewAccessRequest(requester.GetSession())
|
||||||
downscoped.Client.(*fosite.DefaultClient).ID = audience
|
downscoped.Client.(*fosite.DefaultClient).ID = audience
|
||||||
return t.idTokenStrategy.GenerateIDToken(ctx, downscoped)
|
|
||||||
|
// Note: if we wanted to support clients with custom token lifespans, then we would need to call
|
||||||
|
// fosite.GetEffectiveLifespan() to determine the lifespan here.
|
||||||
|
idTokenLifespan := t.fositeConfig.GetIDTokenLifespan(ctx)
|
||||||
|
|
||||||
|
return t.idTokenStrategy.GenerateIDToken(ctx, idTokenLifespan, downscoped)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *TokenExchangeHandler) validateSession(requester fosite.Requester) error {
|
func (t *TokenExchangeHandler) validateSession(requester fosite.Requester) error {
|
||||||
@ -185,7 +191,7 @@ func (t *TokenExchangeHandler) validateParams(params url.Values) (*stsParams, er
|
|||||||
|
|
||||||
func (t *TokenExchangeHandler) validateAccessToken(ctx context.Context, requester fosite.AccessRequester, accessToken string) (fosite.Requester, error) {
|
func (t *TokenExchangeHandler) validateAccessToken(ctx context.Context, requester fosite.AccessRequester, accessToken string) (fosite.Requester, error) {
|
||||||
// Look up the access token's stored session data.
|
// Look up the access token's stored session data.
|
||||||
signature := t.accessTokenStrategy.AccessTokenSignature(accessToken)
|
signature := t.accessTokenStrategy.AccessTokenSignature(ctx, accessToken)
|
||||||
originalRequester, err := t.accessTokenStorage.GetAccessTokenSession(ctx, signature, requester.GetSession())
|
originalRequester, err := t.accessTokenStorage.GetAccessTokenSession(ctx, signature, requester.GetSession())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// The access token was not found, or there was some other error while reading it.
|
// The access token was not found, or there was some other error while reading it.
|
||||||
@ -198,10 +204,10 @@ func (t *TokenExchangeHandler) validateAccessToken(ctx context.Context, requeste
|
|||||||
return originalRequester, nil
|
return originalRequester, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *TokenExchangeHandler) CanSkipClientAuth(_ fosite.AccessRequester) bool {
|
func (t *TokenExchangeHandler) CanSkipClientAuth(_ context.Context, _ fosite.AccessRequester) bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *TokenExchangeHandler) CanHandleTokenEndpointRequest(requester fosite.AccessRequester) bool {
|
func (t *TokenExchangeHandler) CanHandleTokenEndpointRequest(_ context.Context, requester fosite.AccessRequester) bool {
|
||||||
return requester.GetGrantTypes().ExactOne(oidcapi.GrantTypeTokenExchange)
|
return requester.GetGrantTypes().ExactOne(oidcapi.GrantTypeTokenExchange)
|
||||||
}
|
}
|
||||||
|
@ -24,6 +24,7 @@ const (
|
|||||||
|
|
||||||
ErrOIDCClientSecretStorageVersion = constable.Error("OIDC client secret storage data has wrong version")
|
ErrOIDCClientSecretStorageVersion = constable.Error("OIDC client secret storage data has wrong version")
|
||||||
|
|
||||||
|
// Version 1 was the initial release of the OIDCClientSecretRequest API, which uses OIDCClientSecretStorage for storage.
|
||||||
oidcClientSecretStorageVersion = "1"
|
oidcClientSecretStorageVersion = "1"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -4,6 +4,8 @@
|
|||||||
package integration
|
package integration
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
|
"hash"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
"net/url"
|
"net/url"
|
||||||
@ -185,11 +187,39 @@ func formpostTemplateServer(t *testing.T, redirectURI string, responseParams url
|
|||||||
return server.URL
|
return server.URL
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type testHMACStrategyConfigurator struct {
|
||||||
|
secret []byte
|
||||||
|
entropy int
|
||||||
|
}
|
||||||
|
|
||||||
|
func newTestHMACStrategyConfigurator(secret []byte, entropy int) hmac.HMACStrategyConfigurator {
|
||||||
|
return &testHMACStrategyConfigurator{
|
||||||
|
secret: secret,
|
||||||
|
entropy: entropy,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *testHMACStrategyConfigurator) GetTokenEntropy(_ context.Context) int {
|
||||||
|
return t.entropy
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *testHMACStrategyConfigurator) GetGlobalSecret(_ context.Context) ([]byte, error) {
|
||||||
|
return t.secret, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *testHMACStrategyConfigurator) GetRotatedGlobalSecrets(_ context.Context) ([][]byte, error) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *testHMACStrategyConfigurator) GetHMACHasher(_ context.Context) func() hash.Hash {
|
||||||
|
return nil // nil will cause fosite to use a default hasher
|
||||||
|
}
|
||||||
|
|
||||||
// formpostRandomParams is a helper to generate random OAuth2 response parameters for testing.
|
// formpostRandomParams is a helper to generate random OAuth2 response parameters for testing.
|
||||||
func formpostRandomParams(t *testing.T) url.Values {
|
func formpostRandomParams(t *testing.T) url.Values {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
generator := &hmac.HMACStrategy{GlobalSecret: testlib.RandBytes(t, 32), TokenEntropy: 32}
|
generator := &hmac.HMACStrategy{Config: newTestHMACStrategyConfigurator(testlib.RandBytes(t, 32), 32)}
|
||||||
authCode, _, err := generator.Generate()
|
authCode, _, err := generator.Generate(context.Background())
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
return url.Values{
|
return url.Values{
|
||||||
"code": []string{authCode},
|
"code": []string{authCode},
|
||||||
|
@ -5,6 +5,7 @@ package integration
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"crypto/sha256"
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
@ -2005,7 +2006,7 @@ func testSupervisorLogin(
|
|||||||
}
|
}
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
expectedIDTokenClaims := []string{"iss", "exp", "sub", "aud", "auth_time", "iat", "jti", "nonce", "rat", "azp"}
|
expectedIDTokenClaims := []string{"iss", "exp", "sub", "aud", "auth_time", "iat", "jti", "nonce", "rat", "azp", "at_hash"}
|
||||||
if slices.Contains(wantDownstreamScopes, "username") {
|
if slices.Contains(wantDownstreamScopes, "username") {
|
||||||
// If the test wants the username scope to have been granted, then also expect the claim in the ID token.
|
// If the test wants the username scope to have been granted, then also expect the claim in the ID token.
|
||||||
expectedIDTokenClaims = append(expectedIDTokenClaims, "username")
|
expectedIDTokenClaims = append(expectedIDTokenClaims, "username")
|
||||||
@ -2052,7 +2053,7 @@ func testSupervisorLogin(
|
|||||||
refreshedTokenResponse, err := refreshSource.Token()
|
refreshedTokenResponse, err := refreshSource.Token()
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
// When refreshing, expect to get an "at_hash" claim, but no "nonce" claim.
|
// When refreshing, do not expect a "nonce" claim.
|
||||||
expectRefreshedIDTokenClaims := []string{"iss", "exp", "sub", "aud", "auth_time", "iat", "jti", "rat", "azp", "at_hash"}
|
expectRefreshedIDTokenClaims := []string{"iss", "exp", "sub", "aud", "auth_time", "iat", "jti", "rat", "azp", "at_hash"}
|
||||||
if slices.Contains(wantDownstreamScopes, "username") {
|
if slices.Contains(wantDownstreamScopes, "username") {
|
||||||
// If the test wants the username scope to have been granted, then also expect the claim in the refreshed ID token.
|
// If the test wants the username scope to have been granted, then also expect the claim in the refreshed ID token.
|
||||||
@ -2182,6 +2183,21 @@ func verifyTokenResponse(
|
|||||||
require.NotEmpty(t, tokenResponse.RefreshToken)
|
require.NotEmpty(t, tokenResponse.RefreshToken)
|
||||||
// Refresh tokens should start with the custom prefix "pin_rt_" to make them identifiable as refresh tokens when seen by a user out of context.
|
// Refresh tokens should start with the custom prefix "pin_rt_" to make them identifiable as refresh tokens when seen by a user out of context.
|
||||||
require.True(t, strings.HasPrefix(tokenResponse.RefreshToken, "pin_rt_"), "token %q did not have expected prefix 'pin_rt_'", tokenResponse.RefreshToken)
|
require.True(t, strings.HasPrefix(tokenResponse.RefreshToken, "pin_rt_"), "token %q did not have expected prefix 'pin_rt_'", tokenResponse.RefreshToken)
|
||||||
|
|
||||||
|
// The at_hash claim should be present and should be equal to the hash of the access token.
|
||||||
|
actualAccessTokenHashClaimValue := idTokenClaims["at_hash"]
|
||||||
|
require.NotEmpty(t, actualAccessTokenHashClaimValue)
|
||||||
|
require.Equal(t, hashAccessToken(tokenResponse.AccessToken), actualAccessTokenHashClaimValue)
|
||||||
|
}
|
||||||
|
|
||||||
|
func hashAccessToken(accessToken string) string {
|
||||||
|
// See https://openid.net/specs/openid-connect-core-1_0.html#CodeIDToken.
|
||||||
|
// "Access Token hash value. Its value is the base64url encoding of the left-most half of
|
||||||
|
// the hash of the octets of the ASCII representation of the access_token value, where the
|
||||||
|
// hash algorithm used is the hash algorithm used in the alg Header Parameter of the ID
|
||||||
|
// Token's JOSE Header."
|
||||||
|
b := sha256.Sum256([]byte(accessToken))
|
||||||
|
return base64.RawURLEncoding.EncodeToString(b[:len(b)/2])
|
||||||
}
|
}
|
||||||
|
|
||||||
func requestAuthorizationAndExpectImmediateRedirectToCallback(t *testing.T, _, downstreamAuthorizeURL, downstreamCallbackURL, _, _ string, _ *http.Client) {
|
func requestAuthorizationAndExpectImmediateRedirectToCallback(t *testing.T, _, downstreamAuthorizeURL, downstreamCallbackURL, _, _ string, _ *http.Client) {
|
||||||
|
@ -34,10 +34,10 @@ func TestAuthorizeCodeStorage(t *testing.T) {
|
|||||||
name = "pinniped-storage-authcode-jssfhaibxdkiaugxufbsso3bixmfo7fzjvuevxbr35c4xdxolqga"
|
name = "pinniped-storage-authcode-jssfhaibxdkiaugxufbsso3bixmfo7fzjvuevxbr35c4xdxolqga"
|
||||||
)
|
)
|
||||||
|
|
||||||
hmac := compose.NewOAuth2HMACStrategy(&compose.Config{}, []byte("super-secret-32-byte-for-testing"), nil)
|
hmac := compose.NewOAuth2HMACStrategy(&fosite.Config{GlobalSecret: []byte("super-secret-32-byte-for-testing")})
|
||||||
// test data generation via:
|
// test data generation via:
|
||||||
// code, signature, err := hmac.GenerateAuthorizeCode(ctx, nil)
|
// code, signature, err := hmac.GenerateAuthorizeCode(ctx, nil)
|
||||||
signature := hmac.AuthorizeCodeSignature(code)
|
signature := hmac.AuthorizeCodeSignature(context.Background(), code)
|
||||||
|
|
||||||
secrets := client.CoreV1().Secrets(env.SupervisorNamespace)
|
secrets := client.CoreV1().Secrets(env.SupervisorNamespace)
|
||||||
|
|
||||||
@ -91,7 +91,7 @@ func TestAuthorizeCodeStorage(t *testing.T) {
|
|||||||
// Note that CreateAuthorizeCodeSession() sets Active to true and also sets the Version before storing the session,
|
// Note that CreateAuthorizeCodeSession() sets Active to true and also sets the Version before storing the session,
|
||||||
// so expect those here.
|
// so expect those here.
|
||||||
session.Active = true
|
session.Active = true
|
||||||
session.Version = "3" // this is the value of the authorizationcode.authorizeCodeStorageVersion constant
|
session.Version = "4" // this is the value of the authorizationcode.authorizeCodeStorageVersion constant
|
||||||
expectedSessionStorageJSON, err := json.Marshal(session)
|
expectedSessionStorageJSON, err := json.Marshal(session)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.JSONEq(t, string(expectedSessionStorageJSON), string(initialSecret.Data["pinniped-storage-data"]))
|
require.JSONEq(t, string(expectedSessionStorageJSON), string(initialSecret.Data["pinniped-storage-data"]))
|
||||||
|
Loading…
Reference in New Issue
Block a user