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:
Ryan Richard 2022-12-13 16:18:51 -08:00
parent d35306aa85
commit e1a0367b03
29 changed files with 439 additions and 2159 deletions

55
go.mod
View File

@ -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

1963
go.sum

File diff suppressed because it is too large Load Diff

View File

@ -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{},

View File

@ -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

View File

@ -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 {

View File

@ -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",

View File

@ -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"
}` }`

View File

@ -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",

View File

@ -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{}

View File

@ -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",

View File

@ -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{}

View File

@ -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",

View File

@ -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 {

View File

@ -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",

View File

@ -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
} }

View File

@ -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
}) })

View 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
}

View File

@ -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))
} }

View File

@ -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

View File

@ -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)
} }

View File

@ -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)

View File

@ -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 {

View File

@ -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
}) })

View File

@ -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,
@ -3785,6 +3782,7 @@ func TestRefreshGrant(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
requireClaimsAreNotEqual(t, "jti", claimsOfFirstIDToken, claimsOfSecondIDToken) // JWT ID requireClaimsAreNotEqual(t, "jti", claimsOfFirstIDToken, claimsOfSecondIDToken) // JWT ID
requireClaimsAreNotEqual(t, "at_hash", claimsOfFirstIDToken, claimsOfSecondIDToken) // access token hash
requireClaimsAreNotEqual(t, "exp", claimsOfFirstIDToken, claimsOfSecondIDToken) // expires at 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
@ -3880,7 +3878,6 @@ 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,
@ -3888,7 +3885,6 @@ func exchangeAuthcodeForTokens(
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 {

View File

@ -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)
} }

View File

@ -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"
) )

View File

@ -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},

View File

@ -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) {

View File

@ -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"]))