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
// 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
// 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:
@ -55,23 +40,23 @@ require (
github.com/gorilla/websocket v1.5.0
github.com/joshlf/go-acl v0.0.0-20200411065538-eae00ae38531
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/errors v0.9.1
github.com/sclevine/agouti v3.0.0+incompatible
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/stretchr/testify v1.8.0
github.com/tdewolff/minify/v2 v2.12.2
github.com/stretchr/testify v1.8.1
github.com/tdewolff/minify/v2 v2.12.4
go.uber.org/atomic v1.10.0
go.uber.org/zap v1.23.0
golang.org/x/crypto v0.0.0-20220919173607-35f4265a4bc0
golang.org/x/net v0.0.0-20220923203811-8be639271d50
golang.org/x/oauth2 v0.0.0-20220909003341-f21342109be1
golang.org/x/sync v0.0.0-20220923202941-7f9b1623fab7
golang.org/x/term v0.0.0-20220919170432-7a66f970e087
golang.org/x/text v0.3.7
go.uber.org/zap v1.24.0
golang.org/x/crypto v0.4.0
golang.org/x/net v0.4.0
golang.org/x/oauth2 v0.3.0
golang.org/x/sync v0.1.0
golang.org/x/term v0.3.0
golang.org/x/text v0.5.0
gopkg.in/square/go-jose.v2 v2.6.0
k8s.io/api v0.25.2
k8s.io/apiextensions-apiserver v0.25.2
@ -79,11 +64,11 @@ require (
k8s.io/apiserver v0.25.2
k8s.io/client-go 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/kube-aggregator v0.25.2
k8s.io/kube-openapi v0.0.0-20220803164354-a70c9af30aea
k8s.io/utils v0.0.0-20220922133306-665eaaec4324
k8s.io/kube-openapi v0.0.0-20221207184640-f3cff1453715
k8s.io/utils v0.0.0-20221128185143-99ec85e7a448
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-systemd/v22 v22.3.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/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/evanphx/json-patch v5.6.0+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/grpc-ecosystem/go-grpc-prometheus v1.2.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/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/joshlf/testutil v0.0.0-20170608050642-b5d8aa79d93d // 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/reflect2 v1.0.2 // 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-convenience v0.1.0 // 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/jwalterweatherman v1.1.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/client/pkg/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.uber.org/multierr v1.8.0 // 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/tools v0.1.12 // 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() {
it.Before(func() {
activeOIDCAuthcodeSession := &authorizationcode.Session{
Version: "3",
Version: "4",
Active: true,
Request: &fosite.Request{
ID: "request-id-1",
@ -308,7 +308,7 @@ func TestGarbageCollectorControllerSync(t *testing.T) {
r.NoError(kubeClient.Tracker().Add(activeOIDCAuthcodeSessionSecret))
inactiveOIDCAuthcodeSession := &authorizationcode.Session{
Version: "3",
Version: "4",
Active: false,
Request: &fosite.Request{
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() {
it.Before(func() {
activeOIDCAuthcodeSession := &authorizationcode.Session{
Version: "3",
Version: "4",
Active: true,
Request: &fosite.Request{
ID: "request-id-1",
@ -432,7 +432,7 @@ func TestGarbageCollectorControllerSync(t *testing.T) {
r.NoError(kubeClient.Tracker().Add(activeOIDCAuthcodeSessionSecret))
inactiveOIDCAuthcodeSession := &authorizationcode.Session{
Version: "3",
Version: "4",
Active: false,
Request: &fosite.Request{
ID: "request-id-2",
@ -511,7 +511,7 @@ func TestGarbageCollectorControllerSync(t *testing.T) {
when("there is an invalid, expired authcode secret", func() {
it.Before(func() {
invalidOIDCAuthcodeSession := &authorizationcode.Session{
Version: "3",
Version: "4",
Active: true,
Request: &fosite.Request{
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() {
it.Before(func() {
wrongProviderNameOIDCAuthcodeSession := &authorizationcode.Session{
Version: "3",
Version: "4",
Active: true,
Request: &fosite.Request{
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() {
it.Before(func() {
wrongProviderNameOIDCAuthcodeSession := &authorizationcode.Session{
Version: "3",
Version: "4",
Active: true,
Request: &fosite.Request{
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() {
it.Before(func() {
activeOIDCAuthcodeSession := &authorizationcode.Session{
Version: "3",
Version: "4",
Active: true,
Request: &fosite.Request{
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() {
it.Before(func() {
activeOIDCAuthcodeSession := &authorizationcode.Session{
Version: "3",
Version: "4",
Active: true,
Request: &fosite.Request{
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() {
it.Before(func() {
offlineAccessGrantedOIDCAccessTokenSession := &accesstoken.Session{
Version: "3",
Version: "4",
Request: &fosite.Request{
GrantedScope: fosite.Arguments{"scope1", "scope2", "offline_access"},
ID: "request-id-1",
@ -951,7 +951,7 @@ func TestGarbageCollectorControllerSync(t *testing.T) {
r.NoError(kubeClient.Tracker().Add(offlineAccessGrantedOIDCAccessTokenSessionSecret))
offlineAccessNotGrantedOIDCAccessTokenSession := &accesstoken.Session{
Version: "3",
Version: "4",
Request: &fosite.Request{
GrantedScope: fosite.Arguments{"scope1", "scope2"},
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() {
it.Before(func() {
offlineAccessGrantedOIDCAccessTokenSession := &accesstoken.Session{
Version: "3",
Version: "4",
Request: &fosite.Request{
GrantedScope: fosite.Arguments{"scope1", "scope2", "offline_access"},
ID: "request-id-1",
@ -1075,7 +1075,7 @@ func TestGarbageCollectorControllerSync(t *testing.T) {
r.NoError(kubeClient.Tracker().Add(offlineAccessGrantedOIDCAccessTokenSessionSecret))
offlineAccessNotGrantedOIDCAccessTokenSession := &accesstoken.Session{
Version: "3",
Version: "4",
Request: &fosite.Request{
GrantedScope: fosite.Arguments{"scope1", "scope2"},
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() {
it.Before(func() {
oidcRefreshSession := &refreshtoken.Session{
Version: "3",
Version: "4",
Request: &fosite.Request{
ID: "request-id-1",
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() {
it.Before(func() {
oidcRefreshSession := &refreshtoken.Session{
Version: "3",
Version: "4",
Request: &fosite.Request{
ID: "request-id-1",
Client: &clientregistry.Client{},

View File

@ -10,6 +10,7 @@ import (
"testing"
"time"
"github.com/ory/fosite"
"github.com/ory/fosite/compose"
"github.com/stretchr/testify/require"
corev1 "k8s.io/api/core/v1"
@ -42,7 +43,7 @@ func TestStorage(t *testing.T) {
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:
// code, signature, err := hmac.GenerateAuthorizeCode(ctx, nil)
@ -117,7 +118,7 @@ func TestStorage(t *testing.T) {
resource: "access-tokens",
mocks: nil,
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, 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 {
signature := hmac.AuthorizeCodeSignature(authorizationCode1)
signature := hmac.AuthorizeCodeSignature(context.Background(), authorizationCode1)
require.NotEmpty(t, signature)
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)
},
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, 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 {
signature := hmac.AuthorizeCodeSignature(authorizationCode3)
signature := hmac.AuthorizeCodeSignature(context.Background(), authorizationCode3)
require.NotEmpty(t, signature)
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 {
signature := hmac.AuthorizeCodeSignature(authorizationCode3)
signature := hmac.AuthorizeCodeSignature(context.Background(), authorizationCode3)
require.NotEmpty(t, signature)
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)
},
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, validateSecretName(signature, false)) // signature is not valid secret name as-is
@ -934,7 +935,7 @@ func TestStorage(t *testing.T) {
require.NoError(t, err)
},
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, validateSecretName(signature, false)) // signature is not valid secret name as-is
@ -998,7 +999,7 @@ func TestStorage(t *testing.T) {
require.NoError(t, err)
},
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, validateSecretName(signature, false)) // signature is not valid secret name as-is
@ -1062,7 +1063,7 @@ func TestStorage(t *testing.T) {
require.NoError(t, err)
},
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, validateSecretName(signature, false)) // signature is not valid secret name as-is
@ -1126,7 +1127,7 @@ func TestStorage(t *testing.T) {
require.NoError(t, err)
},
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, validateSecretName(signature, false)) // signature is not valid secret name as-is
@ -1170,7 +1171,7 @@ func TestStorage(t *testing.T) {
mocks: nil,
lifetime: func() time.Duration { return 0 }, // 0 == infinity
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, validateSecretName(signature, false)) // signature is not valid secret name as-is
@ -1232,7 +1233,7 @@ func TestStorage(t *testing.T) {
mocks: nil,
lifetime: func() time.Duration { return 0 }, // 0 == infinity
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, 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 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.
accessTokenStorageVersion = "3"
// Version 4 is when fosite added json tags to their openid.DefaultSession struct.
accessTokenStorageVersion = "4"
)
type RevocationStorage interface {

View File

@ -11,6 +11,7 @@ import (
"github.com/ory/fosite"
"github.com/ory/fosite/handler/openid"
"github.com/ory/fosite/token/jwt"
"github.com/pkg/errors"
"github.com/stretchr/testify/require"
corev1 "k8s.io/api/core/v1"
@ -53,7 +54,7 @@ func TestAccessTokenStorage(t *testing.T) {
},
},
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"),
},
Type: "storage.pinniped.dev/access-token",
@ -122,7 +123,7 @@ func TestAccessTokenStorageRevocation(t *testing.T) {
},
},
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"),
},
Type: "storage.pinniped.dev/access-token",
@ -195,7 +196,7 @@ func TestWrongVersion(t *testing.T) {
_, 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) {
@ -213,7 +214,7 @@ func TestNilSessionRequest(t *testing.T) {
},
},
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"),
},
Type: "storage.pinniped.dev/access-token",
@ -297,13 +298,13 @@ func TestReadFromSecret(t *testing.T) {
},
},
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"),
},
Type: "storage.pinniped.dev/access-token",
},
wantSession: &Session{
Version: "3",
Version: "4",
Request: &fosite.Request{
ID: "abcd-1",
Client: &clientregistry.Client{},
@ -311,6 +312,8 @@ func TestReadFromSecret(t *testing.T) {
Fosite: &openid.DefaultSession{
Username: "snorlax",
Subject: "panda",
Claims: &jwt.IDTokenClaims{JTI: "xyz"},
Headers: &jwt.Headers{Extra: map[string]interface{}{"myheader": "foo"}},
},
Custom: &psession.CustomSessionData{
Username: "fake-username",
@ -359,7 +362,7 @@ func TestReadFromSecret(t *testing.T) {
},
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",
@ -372,7 +375,7 @@ func TestReadFromSecret(t *testing.T) {
},
},
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"),
},
Type: "storage.pinniped.dev/access-token",

View File

@ -31,7 +31,8 @@ const (
// Version 1 was the initial release of storage.
// 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.
authorizeCodeStorageVersion = "3"
// Version 4 is when fosite added json tags to their openid.DefaultSession struct.
authorizeCodeStorageVersion = "4"
)
var _ oauth2.AuthorizeCodeStorage = &authorizeCodeStorage{}
@ -314,25 +315,25 @@ const ExpectedAuthorizeCodeSessionJSONFromFuzzing = `{
},
"session": {
"fosite": {
"Claims": {
"JTI": "褗6巽ēđų蓼tùZ蛆鬣a\"ÙǞ0觢",
"Issuer": "j¦鲶H股ƲLŋZ-{",
"Subject": "ehpƧ蓟",
"Audience": [
"id_token_claims": {
"jti": "褗6巽ēđų蓼tùZ蛆鬣a\"ÙǞ0觢",
"iss": "j¦鲶H股ƲLŋZ-{",
"sub": "ehpƧ蓟",
"aud": [
"驜Ŗ~ů崧軒q腟u尿宲!"
],
"Nonce": "ǎ^嫯R忑隯ƗƋ*L\u0026",
"ExpiresAt": "1989-06-02T14:40:29.613836765Z",
"IssuedAt": "2052-03-26T02:39:27.882495556Z",
"RequestedAt": "2038-04-06T10:46:24.698586972Z",
"AuthTime": "2003-01-05T11:30:18.206004879Z",
"AccessTokenHash": "ğǫ\\aȊ4ț髄Al",
"AuthenticationContextClassReference": "曓蓳n匟鯘磹*金爃鶴滱ůĮǐ_c3#",
"AuthenticationMethodsReferences": [
"nonce": "ǎ^嫯R忑隯ƗƋ*L\u0026",
"exp": "1989-06-02T14:40:29.613836765Z",
"iat": "2052-03-26T02:39:27.882495556Z",
"rat": "2038-04-06T10:46:24.698586972Z",
"auth_time": "2003-01-05T11:30:18.206004879Z",
"at_hash": "ğǫ\\aȊ4ț髄Al",
"acr": "曓蓳n匟鯘磹*金爃鶴滱ůĮǐ_c3#",
"amr": [
"装ƹýĸŴB岺Ð嫹Sx镯荫őł疂ư墫"
],
"CodeHash": "\u0026鶡",
"Extra": {
"c_hash": "\u0026鶡",
"ext": {
"rǓ\\BRë_g\"ʎ啴SƇMǃļū": {
"4撎胬龯,t猟i\u0026\u0026Q@ǤǟǗ": [
1239190737
@ -347,8 +348,8 @@ const ExpectedAuthorizeCodeSessionJSONFromFuzzing = `{
"鑳绪": 2738428764
}
},
"Headers": {
"Extra": {
"headers": {
"extra": {
"d謺錳4帳ŅǃĊ": 663773398,
"Ř鸨EJ": {
"Ǽǟ迍阊v\"豑觳翢砜": [
@ -363,11 +364,11 @@ const ExpectedAuthorizeCodeSessionJSONFromFuzzing = `{
}
}
},
"ExpiresAt": {
"expires_at": {
"韁臯氃妪婝rȤ\"h丬鎒ơ娻}ɼƟ": "1970-04-27T04:31:30.902468229Z"
},
"Username": "髉龳ǽÙ",
"Subject": "\u0026¥潝邎Ȗ莅ŝǔ盕戙鵮碡ʯiŬŽ"
"username": "髉龳ǽÙ",
"subject": "\u0026¥潝邎Ȗ莅ŝǔ盕戙鵮碡ʯiŬŽ"
},
"custom": {
"username": "Ĝ眧Ĭ",
@ -408,5 +409,5 @@ const ExpectedAuthorizeCodeSessionJSONFromFuzzing = `{
"筫MN\u0026錝D肁Ŷɽ蔒PR}Ųʓl{"
]
},
"version": "3"
"version": "4"
}`

View File

@ -19,6 +19,7 @@ import (
"github.com/ory/fosite"
"github.com/ory/fosite/handler/oauth2"
"github.com/ory/fosite/handler/openid"
"github.com/ory/fosite/token/jwt"
"github.com/pkg/errors"
"github.com/stretchr/testify/require"
"gopkg.in/square/go-jose.v2"
@ -65,7 +66,7 @@ func TestAuthorizationCodeStorage(t *testing.T) {
},
},
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"),
},
Type: "storage.pinniped.dev/authcode",
@ -85,7 +86,7 @@ func TestAuthorizationCodeStorage(t *testing.T) {
},
},
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"),
},
Type: "storage.pinniped.dev/authcode",
@ -203,7 +204,7 @@ func TestWrongVersion(t *testing.T) {
_, 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) {
@ -218,7 +219,7 @@ func TestNilSessionRequest(t *testing.T) {
},
},
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"),
},
Type: "storage.pinniped.dev/authcode",
@ -385,7 +386,7 @@ func TestFuzzAndJSONNewValidEmptyAuthorizeCodeSession(t *testing.T) {
// set these to match CreateAuthorizeCodeSession so that .JSONEq works
validSession.Active = true
validSession.Version = "3"
validSession.Version = "4"
validSessionJSONBytes, err := json.MarshalIndent(validSession, "", "\t")
require.NoError(t, err)
@ -420,13 +421,13 @@ func TestReadFromSecret(t *testing.T) {
},
},
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"),
},
Type: "storage.pinniped.dev/authcode",
},
wantSession: &Session{
Version: "3",
Version: "4",
Active: true,
Request: &fosite.Request{
ID: "abcd-1",
@ -435,6 +436,8 @@ func TestReadFromSecret(t *testing.T) {
Fosite: &openid.DefaultSession{
Username: "snorlax",
Subject: "panda",
Claims: &jwt.IDTokenClaims{JTI: "xyz"},
Headers: &jwt.Headers{Extra: map[string]interface{}{"myheader": "foo"}},
},
Custom: &psession.CustomSessionData{
Username: "fake-username",
@ -483,7 +486,7 @@ func TestReadFromSecret(t *testing.T) {
},
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",
@ -496,7 +499,7 @@ func TestReadFromSecret(t *testing.T) {
},
},
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"),
},
Type: "storage.pinniped.dev/authcode",

View File

@ -31,7 +31,8 @@ const (
// Version 1 was the initial release of storage.
// 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.
oidcStorageVersion = "3"
// Version 4 is when fosite added json tags to their openid.DefaultSession struct.
oidcStorageVersion = "4"
)
var _ openid.OpenIDConnectRequestStorage = &openIDConnectRequestStorage{}

View File

@ -52,7 +52,7 @@ func TestOpenIdConnectStorage(t *testing.T) {
},
},
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"),
},
Type: "storage.pinniped.dev/oidc",
@ -137,7 +137,7 @@ func TestWrongVersion(t *testing.T) {
_, 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) {
@ -152,7 +152,7 @@ func TestNilSessionRequest(t *testing.T) {
},
},
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"),
},
Type: "storage.pinniped.dev/oidc",

View File

@ -29,7 +29,8 @@ const (
// Version 1 was the initial release of storage.
// 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.
pkceStorageVersion = "3"
// Version 4 is when fosite added json tags to their openid.DefaultSession struct.
pkceStorageVersion = "4"
)
var _ pkce.PKCERequestStorage = &pkceStorage{}

View File

@ -52,7 +52,7 @@ func TestPKCEStorage(t *testing.T) {
},
},
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"),
},
Type: "storage.pinniped.dev/pkce",
@ -140,7 +140,7 @@ func TestWrongVersion(t *testing.T) {
_, 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) {
@ -158,7 +158,7 @@ func TestNilSessionRequest(t *testing.T) {
},
},
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"),
},
Type: "storage.pinniped.dev/pkce",

View File

@ -30,7 +30,8 @@ const (
// Version 1 was the initial release of storage.
// 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.
refreshTokenStorageVersion = "3"
// Version 4 is when fosite added json tags to their openid.DefaultSession struct.
refreshTokenStorageVersion = "4"
)
type RevocationStorage interface {

View File

@ -11,6 +11,7 @@ import (
"github.com/ory/fosite"
"github.com/ory/fosite/handler/openid"
"github.com/ory/fosite/token/jwt"
"github.com/pkg/errors"
"github.com/stretchr/testify/require"
corev1 "k8s.io/api/core/v1"
@ -52,7 +53,7 @@ func TestRefreshTokenStorage(t *testing.T) {
},
},
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"),
},
Type: "storage.pinniped.dev/refresh-token",
@ -122,7 +123,7 @@ func TestRefreshTokenStorageRevocation(t *testing.T) {
},
},
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"),
},
Type: "storage.pinniped.dev/refresh-token",
@ -177,7 +178,7 @@ func TestRefreshTokenStorageRevokeRefreshTokenMaybeGracePeriod(t *testing.T) {
},
},
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"),
},
Type: "storage.pinniped.dev/refresh-token",
@ -251,7 +252,7 @@ func TestWrongVersion(t *testing.T) {
_, 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) {
@ -269,7 +270,7 @@ func TestNilSessionRequest(t *testing.T) {
},
},
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"),
},
Type: "storage.pinniped.dev/refresh-token",
@ -353,13 +354,13 @@ func TestReadFromSecret(t *testing.T) {
},
},
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"),
},
Type: "storage.pinniped.dev/refresh-token",
},
wantSession: &Session{
Version: "3",
Version: "4",
Request: &fosite.Request{
ID: "abcd-1",
Client: &clientregistry.Client{},
@ -367,6 +368,8 @@ func TestReadFromSecret(t *testing.T) {
Fosite: &openid.DefaultSession{
Username: "snorlax",
Subject: "panda",
Claims: &jwt.IDTokenClaims{JTI: "xyz"},
Headers: &jwt.Headers{Extra: map[string]interface{}{"myheader": "foo"}},
},
Custom: &psession.CustomSessionData{
Username: "fake-username",
@ -392,7 +395,7 @@ func TestReadFromSecret(t *testing.T) {
},
},
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"),
},
Type: "storage.pinniped.dev/not-refresh-token",
@ -415,7 +418,7 @@ func TestReadFromSecret(t *testing.T) {
},
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",
@ -428,7 +431,7 @@ func TestReadFromSecret(t *testing.T) {
},
},
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"),
},
Type: "storage.pinniped.dev/refresh-token",

View File

@ -125,7 +125,7 @@ func handleAuthRequestForLDAPUpstreamCLIFlow(
return nil
}
if !requireStaticClientForUsernameAndPasswordHeaders(w, oauthHelper, authorizeRequester) {
if !requireStaticClientForUsernameAndPasswordHeaders(r, w, oauthHelper, authorizeRequester) {
return nil
}
@ -140,7 +140,7 @@ func handleAuthRequestForLDAPUpstreamCLIFlow(
return httperr.New(http.StatusBadGateway, "unexpected error during upstream authentication")
}
if !authenticated {
oidc.WriteAuthorizeError(w, oauthHelper, authorizeRequester,
oidc.WriteAuthorizeError(r, w, oauthHelper, authorizeRequester,
fosite.ErrAccessDenied.WithHintf("Username/password not accepted by LDAP provider."), true)
return nil
}
@ -203,7 +203,7 @@ func handleAuthRequestForOIDCUpstreamPasswordGrant(
return nil
}
if !requireStaticClientForUsernameAndPasswordHeaders(w, oauthHelper, authorizeRequester) {
if !requireStaticClientForUsernameAndPasswordHeaders(r, w, oauthHelper, authorizeRequester) {
return nil
}
@ -214,7 +214,7 @@ func handleAuthRequestForOIDCUpstreamPasswordGrant(
if !oidcUpstream.AllowsPasswordGrant() {
// 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(
"Resource owner password credentials grant is not allowed for this upstream provider according to its configuration."), true)
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
// 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.)
oidc.WriteAuthorizeError(w, oauthHelper, authorizeRequester,
oidc.WriteAuthorizeError(r, w, oauthHelper, authorizeRequester,
fosite.ErrAccessDenied.WithDebug(err.Error()), true) // WithDebug hides the error from the client
return nil
}
@ -237,7 +237,7 @@ func handleAuthRequestForOIDCUpstreamPasswordGrant(
subject, username, groups, err := downstreamsession.GetDownstreamIdentityFromUpstreamIDToken(oidcUpstream, token.IDToken.Claims)
if err != nil {
// 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,
)
return nil
@ -245,7 +245,7 @@ func handleAuthRequestForOIDCUpstreamPasswordGrant(
customSessionData, err := downstreamsession.MakeDownstreamOIDCCustomSessionData(oidcUpstream, token, username)
if err != nil {
oidc.WriteAuthorizeError(w, oauthHelper, authorizeRequester,
oidc.WriteAuthorizeError(r, w, oauthHelper, authorizeRequester,
fosite.ErrAccessDenied.WithHintf("Reason: %s.", err.Error()), true,
)
return nil
@ -321,10 +321,10 @@ func handleAuthRequestForOIDCUpstreamBrowserFlow(
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
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)
}
return isStaticClient
@ -334,7 +334,7 @@ func requireNonEmptyUsernameAndPasswordHeaders(r *http.Request, w http.ResponseW
username := r.Header.Get(oidcapi.AuthorizeUsernameHeaderName)
password := r.Header.Get(oidcapi.AuthorizePasswordHeaderName)
if username == "" || password == "" {
oidc.WriteAuthorizeError(w, oauthHelper, authorizeRequester,
oidc.WriteAuthorizeError(r, w, oauthHelper, authorizeRequester,
fosite.ErrAccessDenied.WithHintf("Missing or blank username or password."), true)
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) {
authorizeRequester, err := oauthHelper.NewAuthorizeRequest(r.Context(), r)
if err != nil {
oidc.WriteAuthorizeError(w, oauthHelper, authorizeRequester, err, isBrowserless)
oidc.WriteAuthorizeError(r, w, oauthHelper, authorizeRequester, err, isBrowserless)
return nil, false
}
@ -458,7 +458,7 @@ func handleBrowserFlowAuthRequest(
},
})
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
}
@ -488,7 +488,7 @@ func handleBrowserFlowAuthRequest(
promptParam := r.Form.Get(promptParamName)
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
}

View File

@ -89,7 +89,7 @@ func NewHandler(
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
})

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 (
accessTokenPrefix = "pin_at_" // "Pinniped access token" abbreviated.
refreshTokenPrefix = "pin_rt_" // "Pinniped refresh token" abbreviated.
authcodePrefix = "pin_ac_" // "Pinniped authorization code" abbreviated.
pinAccessTokenPrefix = "pin_at_" // "Pinniped access token" abbreviated.
oryAccessTokenPrefix = "ory_at_"
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
@ -29,16 +34,18 @@ const (
// FederationDomain has a valid signing key.
//
// 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 {
fositeConfig *compose.Config
fositeConfig *fosite.Config
keyFunc func() []byte
}
var _ oauth2.CoreStrategy = &dynamicOauth2HMACStrategy{}
func newDynamicOauth2HMACStrategy(
fositeConfig *compose.Config,
fositeConfig *fosite.Config,
keyFunc func() []byte,
) *dynamicOauth2HMACStrategy {
return &dynamicOauth2HMACStrategy{
@ -47,17 +54,26 @@ func newDynamicOauth2HMACStrategy(
}
}
func (s *dynamicOauth2HMACStrategy) AccessTokenSignature(token string) string {
return s.delegate().AccessTokenSignature(token)
func replacePrefix(s, prefixToReplace, newPrefix string) string {
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(
ctx context.Context,
requester fosite.Requester,
) (token string, signature string, err error) {
) (string, string, error) {
token, sig, err := s.delegate().GenerateAccessToken(ctx, requester)
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
}
@ -66,25 +82,30 @@ func (s *dynamicOauth2HMACStrategy) ValidateAccessToken(
ctx context.Context,
requester fosite.Requester,
token string,
) (err error) {
if !strings.HasPrefix(token, accessTokenPrefix) {
) error {
if !strings.HasPrefix(token, pinAccessTokenPrefix) {
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 {
return s.delegate().RefreshTokenSignature(token)
func (s *dynamicOauth2HMACStrategy) RefreshTokenSignature(ctx context.Context, token string) string {
return s.delegate().RefreshTokenSignature(ctx, token)
}
func (s *dynamicOauth2HMACStrategy) GenerateRefreshToken(
ctx context.Context,
requester fosite.Requester,
) (token string, signature string, err error) {
) (string, string, error) {
token, sig, err := s.delegate().GenerateRefreshToken(ctx, requester)
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
}
@ -93,25 +114,30 @@ func (s *dynamicOauth2HMACStrategy) ValidateRefreshToken(
ctx context.Context,
requester fosite.Requester,
token string,
) (err error) {
if !strings.HasPrefix(token, refreshTokenPrefix) {
) error {
if !strings.HasPrefix(token, pinRefreshTokenPrefix) {
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 {
return s.delegate().AuthorizeCodeSignature(token)
func (s *dynamicOauth2HMACStrategy) AuthorizeCodeSignature(ctx context.Context, token string) string {
return s.delegate().AuthorizeCodeSignature(ctx, token)
}
func (s *dynamicOauth2HMACStrategy) GenerateAuthorizeCode(
ctx context.Context,
requester fosite.Requester,
) (token string, signature string, err error) {
) (string, string, error) {
authcode, sig, err := s.delegate().GenerateAuthorizeCode(ctx, requester)
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
}
@ -120,14 +146,14 @@ func (s *dynamicOauth2HMACStrategy) ValidateAuthorizeCode(
ctx context.Context,
requester fosite.Requester,
token string,
) (err error) {
if !strings.HasPrefix(token, authcodePrefix) {
) error {
if !strings.HasPrefix(token, pinAuthcodePrefix) {
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 {
return compose.NewOAuth2HMACStrategy(s.fositeConfig, s.keyFunc(), nil)
return compose.NewOAuth2HMACStrategy(NewDynamicGlobalSecretConfig(s.fositeConfig, s.keyFunc))
}

View File

@ -11,20 +11,19 @@ import (
"time"
"github.com/ory/fosite"
"github.com/ory/fosite/compose"
"github.com/stretchr/testify/require"
)
func TestDynamicOauth2HMACStrategy_Signatures(t *testing.T) {
s := &dynamicOauth2HMACStrategy{
fositeConfig: &compose.Config{}, // defaults are good enough for this unit test
keyFunc: func() []byte { return []byte("12345678901234567890123456789012") },
}
s := newDynamicOauth2HMACStrategy(
&fosite.Config{}, // defaults are good enough for this unit test
func() []byte { return []byte("12345678901234567890123456789012") }, // 32 character secret key
)
tests := []struct {
name string
token string
signatureFunc func(token string) (signature string)
signatureFunc func(ctx context.Context, token string) (signature string)
wantSignature string
}{
{
@ -52,21 +51,21 @@ func TestDynamicOauth2HMACStrategy_Signatures(t *testing.T) {
t.Run(tt.name, func(t *testing.T) {
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) {
s := &dynamicOauth2HMACStrategy{
fositeConfig: &compose.Config{}, // defaults are good enough for this unit test
keyFunc: func() []byte { return []byte("12345678901234567890123456789012") }, // 32 character secret key
}
s := newDynamicOauth2HMACStrategy(
&fosite.Config{}, // defaults are good enough for this unit test
func() []byte { return []byte("12345678901234567890123456789012") }, // 32 character secret key
)
generateTokenErrorCausingStrategy := &dynamicOauth2HMACStrategy{
fositeConfig: &compose.Config{},
keyFunc: func() []byte { return []byte("too_short_causes_error") }, // secret key is below required 32 characters
}
generateTokenErrorCausingStrategy := newDynamicOauth2HMACStrategy(
&fosite.Config{},
func() []byte { return []byte("too_short_causes_error") }, // secret key is below required 32 characters
)
tests := []struct {
name string
@ -135,10 +134,10 @@ func TestDynamicOauth2HMACStrategy_Generate(t *testing.T) {
}
func TestDynamicOauth2HMACStrategy_Validate(t *testing.T) {
s := &dynamicOauth2HMACStrategy{
fositeConfig: &compose.Config{}, // defaults are good enough for this unit test
keyFunc: func() []byte { return []byte("12345678901234567890123456789012") },
}
s := newDynamicOauth2HMACStrategy(
&fosite.Config{}, // defaults are good enough for this unit test
func() []byte { return []byte("12345678901234567890123456789012") }, // 32 character secret key
)
tests := []struct {
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
package oidc
@ -7,6 +7,7 @@ import (
"context"
"crypto/ecdsa"
"reflect"
"time"
"github.com/ory/fosite"
"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
// FederationDomain has a valid signing key.
type dynamicOpenIDConnectECDSAStrategy struct {
fositeConfig *compose.Config
fositeConfig *fosite.Config
jwksProvider jwks.DynamicJWKSProvider
}
var _ openid.OpenIDConnectTokenStrategy = &dynamicOpenIDConnectECDSAStrategy{}
func newDynamicOpenIDConnectECDSAStrategy(
fositeConfig *compose.Config,
fositeConfig *fosite.Config,
jwksProvider jwks.DynamicJWKSProvider,
) *dynamicOpenIDConnectECDSAStrategy {
return &dynamicOpenIDConnectECDSAStrategy{
@ -44,6 +45,7 @@ func newDynamicOpenIDConnectECDSAStrategy(
func (s *dynamicOpenIDConnectECDSAStrategy) GenerateIDToken(
ctx context.Context,
lifespan time.Duration,
requester fosite.Requester,
) (string, error) {
_, 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 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
package oidc
@ -12,9 +12,9 @@ import (
"errors"
"net/url"
"testing"
"time"
"github.com/ory/fosite"
"github.com/ory/fosite/compose"
"github.com/ory/fosite/handler/openid"
"github.com/ory/fosite/token/jwt"
"github.com/stretchr/testify/require"
@ -95,7 +95,7 @@ func TestDynamicOpenIDConnectECDSAStrategy(t *testing.T) {
test.jwksProvider(jwksProvider)
}
s := newDynamicOpenIDConnectECDSAStrategy(
&compose.Config{IDTokenIssuer: test.issuer},
&fosite.Config{IDTokenIssuer: test.issuer},
jwksProvider,
)
@ -114,7 +114,7 @@ func TestDynamicOpenIDConnectECDSAStrategy(t *testing.T) {
"nonce": {goodNonce},
},
}
idToken, err := s.GenerateIDToken(context.Background(), requester)
idToken, err := s.GenerateIDToken(context.Background(), 2*time.Hour, requester)
if test.wantErrorType != nil {
require.True(t, errors.Is(err, test.wantErrorType))
require.EqualError(t, err.(*fosite.RFC6749Error).Cause(), test.wantErrorCause)

View File

@ -5,10 +5,12 @@
package oidc
import (
"context"
"crypto/subtle"
"errors"
"fmt"
"net/http"
"net/url"
"time"
"github.com/felixge/httpsnoop"
@ -195,7 +197,11 @@ func FositeOauth2Helper(
jwksProvider jwks.DynamicJWKSProvider,
timeoutsConfiguration TimeoutsConfiguration,
) fosite.OAuth2Provider {
oauthConfig := &compose.Config{
isRedirectURISecureStrict := func(_ context.Context, uri *url.URL) bool {
return fosite.IsRedirectURISecureStrict(uri)
}
oauthConfig := &fosite.Config{
IDTokenIssuer: issuer,
AuthorizeCodeLifespan: timeoutsConfiguration.AuthorizeCodeLifespan,
@ -217,10 +223,16 @@ func FositeOauth2Helper(
MinParameterEntropy: fosite.MinParameterEntropy,
// 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,
oauthStore,
&compose.CommonStrategy{
@ -228,7 +240,6 @@ func FositeOauth2Helper(
CoreStrategy: newDynamicOauth2HMACStrategy(oauthConfig, hmacSecretOfLengthAtLeast32Func),
OpenIDConnectTokenStrategy: newDynamicOpenIDConnectECDSAStrategy(oauthConfig, jwksProvider),
},
nil, // hasher, defaults to using BCrypt when nil. Used for hashing client secrets.
compose.OAuth2AuthorizeExplicitFactory,
compose.OAuth2RefreshTokenGrantFactory,
compose.OpenIDConnectExplicitFactory,
@ -236,8 +247,8 @@ func FositeOauth2Helper(
compose.OAuth2PKCEFactory,
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
@ -403,7 +414,7 @@ func FindUpstreamIDPByNameAndType(
// 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.
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) {
// When trace level logging is enabled, include the stack trace in the log message.
keysAndValues := FositeErrorForLog(err)
@ -420,7 +431,7 @@ func WriteAuthorizeError(w http.ResponseWriter, oauthHelper fosite.OAuth2Provide
w = rewriteStatusSeeOtherToStatusFoundForBrowserless(w)
}
// 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
@ -437,13 +448,13 @@ func PerformAuthcodeRedirect(
authorizeResponder, err := oauthHelper.NewAuthorizeResponse(r.Context(), authorizeRequester, openIDSession)
if err != nil {
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
}
if isBrowserless {
w = rewriteStatusSeeOtherToStatusFoundForBrowserless(w)
}
oauthHelper.WriteAuthorizeResponse(w, authorizeRequester, authorizeResponder)
oauthHelper.WriteAuthorizeResponse(r.Context(), w, authorizeRequester, authorizeResponder)
}
func rewriteStatusSeeOtherToStatusFoundForBrowserless(w http.ResponseWriter) http.ResponseWriter {

View File

@ -35,7 +35,7 @@ func NewHandler(
accessRequest, err := oauthHelper.NewAccessRequest(r.Context(), r, session)
if err != nil {
plog.Info("token request error", oidc.FositeErrorForLog(err)...)
oauthHelper.WriteAccessError(w, accessRequest, err)
oauthHelper.WriteAccessError(r.Context(), w, accessRequest, err)
return nil
}
@ -48,7 +48,7 @@ func NewHandler(
err = upstreamRefresh(r.Context(), accessRequest, idpLister)
if err != nil {
plog.Info("upstream refresh error", oidc.FositeErrorForLog(err)...)
oauthHelper.WriteAccessError(w, accessRequest, err)
oauthHelper.WriteAccessError(r.Context(), w, accessRequest, err)
return nil
}
}
@ -68,11 +68,11 @@ func NewHandler(
accessResponse, err := oauthHelper.NewAccessResponse(r.Context(), accessRequest)
if err != nil {
plog.Info("token response error", oidc.FositeErrorForLog(err)...)
oauthHelper.WriteAccessError(w, accessRequest, err)
oauthHelper.WriteAccessError(r.Context(), w, accessRequest, err)
return nil
}
oauthHelper.WriteAccessResponse(w, accessRequest, accessResponse)
oauthHelper.WriteAccessResponse(r.Context(), w, accessRequest, accessResponse)
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")
}
// 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
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.wantGroups, // the old groups from the initial login
test.authcodeExchange.customSessionData, // the old custom session data from the initial login
wantAtHashClaimInIDToken,
wantNonceValueInIDToken,
refreshResponse,
authCode,
@ -3784,8 +3781,9 @@ func TestRefreshGrant(t *testing.T) {
err = secondIDTokenDecoded.UnsafeClaimsWithoutVerification(&claimsOfSecondIDToken)
require.NoError(t, err)
requireClaimsAreNotEqual(t, "jti", claimsOfFirstIDToken, claimsOfSecondIDToken) // JWT ID
requireClaimsAreNotEqual(t, "exp", claimsOfFirstIDToken, claimsOfSecondIDToken) // expires at
requireClaimsAreNotEqual(t, "jti", claimsOfFirstIDToken, claimsOfSecondIDToken) // JWT ID
requireClaimsAreNotEqual(t, "at_hash", claimsOfFirstIDToken, claimsOfSecondIDToken) // access token hash
requireClaimsAreNotEqual(t, "exp", claimsOfFirstIDToken, claimsOfSecondIDToken) // expires at
require.Greater(t, claimsOfSecondIDToken["exp"], claimsOfFirstIDToken["exp"])
requireClaimsAreNotEqual(t, "iat", claimsOfFirstIDToken, claimsOfSecondIDToken) // issued at
require.Greater(t, claimsOfSecondIDToken["iat"], claimsOfFirstIDToken["iat"])
@ -3880,15 +3878,13 @@ func exchangeAuthcodeForTokens(
t.Logf("response: %#v", rsp)
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,
test.want,
test.want.wantUsername, // the old username 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
wantAtHashClaimInIDToken,
wantNonceValueInIDToken,
rsp,
authCode,
@ -3907,7 +3903,6 @@ func requireTokenEndpointBehavior(
oldUsername string,
oldGroups []string,
oldCustomSessionData *psession.CustomSessionData,
wantAtHashClaimInIDToken bool,
wantNonceValueInIDToken bool,
tokenEndpointResponse *httptest.ResponseRecorder,
authCode string,
@ -3942,7 +3937,7 @@ func requireTokenEndpointBehavior(
expectedNumberOfIDSessionsStored := 0
if wantIDToken {
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 {
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{},
jwtSigningKey *ecdsa.PrivateKey,
wantClientID string,
wantAtHashClaimInIDToken bool,
wantNonceValueInIDToken bool,
wantUsernameInIDToken string,
wantGroupsInIDToken []string,
@ -4541,13 +4535,7 @@ func requireValidIDToken(
Username string `json:"username"`
}
// Note that there is a bug in fosite which prevents the `at_hash` claim from appearing in this ID token
// 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")
}
idTokenFields := []string{"sub", "aud", "iss", "jti", "auth_time", "exp", "iat", "rat", "azp", "at_hash"}
if wantNonceValueInIDToken {
idTokenFields = append(idTokenFields, "nonce")
}
@ -4590,12 +4578,8 @@ func requireValidIDToken(
testutil.RequireTimeInDelta(t, goodRequestedAtTime, requestedAt, timeComparisonFudge)
testutil.RequireTimeInDelta(t, goodAuthTime, authTime, timeComparisonFudge)
if wantAtHashClaimInIDToken {
require.NotEmpty(t, actualAccessToken)
require.Equal(t, hashAccessToken(actualAccessToken), claims.AccessTokenHash)
} else {
require.Empty(t, claims.AccessTokenHash)
}
require.NotEmpty(t, actualAccessToken)
require.Equal(t, hashAccessToken(actualAccessToken), claims.AccessTokenHash)
}
func deepCopyRequestForm(r *http.Request) *http.Request {

View File

@ -9,7 +9,6 @@ import (
"strings"
"github.com/ory/fosite"
"github.com/ory/fosite/compose"
"github.com/ory/fosite/handler/oauth2"
"github.com/ory/fosite/handler/openid"
"github.com/pkg/errors"
@ -28,11 +27,12 @@ type stsParams struct {
requestedAudience string
}
func TokenExchangeFactory(config *compose.Config, storage interface{}, strategy interface{}) interface{} {
func TokenExchangeFactory(config fosite.Configurator, storage interface{}, strategy interface{}) interface{} {
return &TokenExchangeHandler{
idTokenStrategy: strategy.(openid.OpenIDConnectTokenStrategy),
accessTokenStrategy: strategy.(oauth2.AccessTokenStrategy),
accessTokenStorage: storage.(oauth2.AccessTokenStorage),
fositeConfig: config,
}
}
@ -40,12 +40,13 @@ type TokenExchangeHandler struct {
idTokenStrategy openid.OpenIDConnectTokenStrategy
accessTokenStrategy oauth2.AccessTokenStrategy
accessTokenStorage oauth2.AccessTokenStorage
fositeConfig fosite.Configurator
}
var _ fosite.TokenEndpointHandler = (*TokenExchangeHandler)(nil)
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 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) {
downscoped := fosite.NewAccessRequest(requester.GetSession())
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 {
@ -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) {
// 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())
if err != nil {
// 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
}
func (t *TokenExchangeHandler) CanSkipClientAuth(_ fosite.AccessRequester) bool {
func (t *TokenExchangeHandler) CanSkipClientAuth(_ context.Context, _ fosite.AccessRequester) bool {
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)
}

View File

@ -24,6 +24,7 @@ const (
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"
)

View File

@ -4,6 +4,8 @@
package integration
import (
"context"
"hash"
"net/http"
"net/http/httptest"
"net/url"
@ -185,11 +187,39 @@ func formpostTemplateServer(t *testing.T, redirectURI string, responseParams 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.
func formpostRandomParams(t *testing.T) url.Values {
t.Helper()
generator := &hmac.HMACStrategy{GlobalSecret: testlib.RandBytes(t, 32), TokenEntropy: 32}
authCode, _, err := generator.Generate()
generator := &hmac.HMACStrategy{Config: newTestHMACStrategyConfigurator(testlib.RandBytes(t, 32), 32)}
authCode, _, err := generator.Generate(context.Background())
require.NoError(t, err)
return url.Values{
"code": []string{authCode},

View File

@ -5,6 +5,7 @@ package integration
import (
"context"
"crypto/sha256"
"crypto/tls"
"encoding/base64"
"encoding/json"
@ -2005,7 +2006,7 @@ func testSupervisorLogin(
}
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 the test wants the username scope to have been granted, then also expect the claim in the ID token.
expectedIDTokenClaims = append(expectedIDTokenClaims, "username")
@ -2052,7 +2053,7 @@ func testSupervisorLogin(
refreshedTokenResponse, err := refreshSource.Token()
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"}
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.
@ -2182,6 +2183,21 @@ func verifyTokenResponse(
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.
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) {

View File

@ -34,10 +34,10 @@ func TestAuthorizeCodeStorage(t *testing.T) {
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:
// code, signature, err := hmac.GenerateAuthorizeCode(ctx, nil)
signature := hmac.AuthorizeCodeSignature(code)
signature := hmac.AuthorizeCodeSignature(context.Background(), code)
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,
// so expect those here.
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)
require.NoError(t, err)
require.JSONEq(t, string(expectedSessionStorageJSON), string(initialSecret.Data["pinniped-storage-data"]))