Use PinnipedSession type instead of fosite's DefaultSesssion type

This will allow us to store custom data inside the fosite session
storage for all downstream OIDC sessions.

Signed-off-by: Margo Crawford <margaretc@vmware.com>
This commit is contained in:
Ryan Richard 2021-10-06 15:28:13 -07:00 committed by Margo Crawford
parent 6e41c10584
commit c6f1d29538
18 changed files with 462 additions and 315 deletions

View File

@ -10,7 +10,6 @@ import (
"github.com/ory/fosite" "github.com/ory/fosite"
"github.com/ory/fosite/handler/oauth2" "github.com/ory/fosite/handler/oauth2"
"github.com/ory/fosite/handler/openid"
"k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/api/errors"
corev1client "k8s.io/client-go/kubernetes/typed/core/v1" corev1client "k8s.io/client-go/kubernetes/typed/core/v1"
@ -18,6 +17,7 @@ import (
"go.pinniped.dev/internal/crud" "go.pinniped.dev/internal/crud"
"go.pinniped.dev/internal/fositestorage" "go.pinniped.dev/internal/fositestorage"
"go.pinniped.dev/internal/oidc/clientregistry" "go.pinniped.dev/internal/oidc/clientregistry"
"go.pinniped.dev/internal/psession"
) )
const ( const (
@ -26,7 +26,7 @@ const (
ErrInvalidAccessTokenRequestVersion = constable.Error("access token request data has wrong version") ErrInvalidAccessTokenRequestVersion = constable.Error("access token request data has wrong version")
ErrInvalidAccessTokenRequestData = constable.Error("access token request data must be present") ErrInvalidAccessTokenRequestData = constable.Error("access token request data must be present")
accessTokenStorageVersion = "1" accessTokenStorageVersion = "2"
) )
type RevocationStorage interface { type RevocationStorage interface {
@ -110,7 +110,7 @@ func newValidEmptyAccessTokenSession() *session {
return &session{ return &session{
Request: &fosite.Request{ Request: &fosite.Request{
Client: &clientregistry.Client{}, Client: &clientregistry.Client{},
Session: &openid.DefaultSession{}, Session: &psession.PinnipedSession{},
}, },
} }
} }

View File

@ -10,7 +10,6 @@ import (
"time" "time"
"github.com/ory/fosite" "github.com/ory/fosite"
"github.com/ory/fosite/handler/openid"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
corev1 "k8s.io/api/core/v1" corev1 "k8s.io/api/core/v1"
@ -22,6 +21,8 @@ import (
coretesting "k8s.io/client-go/testing" coretesting "k8s.io/client-go/testing"
"go.pinniped.dev/internal/oidc/clientregistry" "go.pinniped.dev/internal/oidc/clientregistry"
"go.pinniped.dev/internal/psession"
"go.pinniped.dev/internal/testutil"
) )
const namespace = "test-ns" const namespace = "test-ns"
@ -51,7 +52,7 @@ func TestAccessTokenStorage(t *testing.T) {
}, },
}, },
Data: map[string][]byte{ Data: map[string][]byte{
"pinniped-storage-data": []byte(`{"request":{"id":"abcd-1","requestedAt":"0001-01-01T00:00:00Z","client":{"id":"pinny","redirect_uris":null,"grant_types":null,"response_types":null,"scopes":null,"audience":null,"public":true,"jwks_uri":"where","jwks":null,"token_endpoint_auth_method":"something","request_uris":null,"request_object_signing_alg":"","token_endpoint_auth_signing_alg":""},"scopes":null,"grantedScopes":null,"form":{"key":["val"]},"session":{"Claims":null,"Headers":null,"ExpiresAt":null,"Username":"snorlax","Subject":"panda"},"requestedAudience":null,"grantedAudience":null},"version":"1"}`), "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":{"providerUID":"fake-provider-uid","providerName":"fake-provider-name","providerType":"fake-provider-type","oidc":{"upstreamRefreshToken":"fake-upstream-refresh-token"}}},"requestedAudience":null,"grantedAudience":null},"version":"2"}`),
"pinniped-storage-version": []byte("1"), "pinniped-storage-version": []byte("1"),
}, },
Type: "storage.pinniped.dev/access-token", Type: "storage.pinniped.dev/access-token",
@ -84,16 +85,10 @@ func TestAccessTokenStorage(t *testing.T) {
RequestObjectSigningAlgorithm: "", RequestObjectSigningAlgorithm: "",
TokenEndpointAuthSigningAlgorithm: "", TokenEndpointAuthSigningAlgorithm: "",
}}, }},
RequestedScope: nil, RequestedScope: nil,
GrantedScope: nil, GrantedScope: nil,
Form: url.Values{"key": []string{"val"}}, Form: url.Values{"key": []string{"val"}},
Session: &openid.DefaultSession{ Session: testutil.NewFakePinnipedSession(),
Claims: nil,
Headers: nil,
ExpiresAt: nil,
Username: "snorlax",
Subject: "panda",
},
RequestedAudience: nil, RequestedAudience: nil,
GrantedAudience: nil, GrantedAudience: nil,
} }
@ -107,6 +102,7 @@ func TestAccessTokenStorage(t *testing.T) {
err = storage.DeleteAccessTokenSession(ctx, "fancy-signature") err = storage.DeleteAccessTokenSession(ctx, "fancy-signature")
require.NoError(t, err) require.NoError(t, err)
testutil.LogActualJSONFromCreateAction(t, client, 0) // makes it easier to update expected values when needed
require.Equal(t, wantActions, client.Actions()) require.Equal(t, wantActions, client.Actions())
} }
@ -125,7 +121,7 @@ func TestAccessTokenStorageRevocation(t *testing.T) {
}, },
}, },
Data: map[string][]byte{ Data: map[string][]byte{
"pinniped-storage-data": []byte(`{"request":{"id":"abcd-1","requestedAt":"0001-01-01T00:00:00Z","client":{"id":"pinny","redirect_uris":null,"grant_types":null,"response_types":null,"scopes":null,"audience":null,"public":true,"jwks_uri":"where","jwks":null,"token_endpoint_auth_method":"something","request_uris":null,"request_object_signing_alg":"","token_endpoint_auth_signing_alg":""},"scopes":null,"grantedScopes":null,"form":{"key":["val"]},"session":{"Claims":null,"Headers":null,"ExpiresAt":null,"Username":"snorlax","Subject":"panda"},"requestedAudience":null,"grantedAudience":null},"version":"1"}`), "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":{"providerUID":"fake-provider-uid","providerName":"fake-provider-name","providerType":"fake-provider-type","oidc":{"upstreamRefreshToken":"fake-upstream-refresh-token"}}},"requestedAudience":null,"grantedAudience":null},"version":"2"}`),
"pinniped-storage-version": []byte("1"), "pinniped-storage-version": []byte("1"),
}, },
Type: "storage.pinniped.dev/access-token", Type: "storage.pinniped.dev/access-token",
@ -151,11 +147,8 @@ func TestAccessTokenStorageRevocation(t *testing.T) {
TokenEndpointAuthMethod: "something", TokenEndpointAuthMethod: "something",
}, },
}, },
Form: url.Values{"key": []string{"val"}}, Form: url.Values{"key": []string{"val"}},
Session: &openid.DefaultSession{ Session: testutil.NewFakePinnipedSession(),
Username: "snorlax",
Subject: "panda",
},
} }
err := storage.CreateAccessTokenSession(ctx, "fancy-signature", request) err := storage.CreateAccessTokenSession(ctx, "fancy-signature", request)
require.NoError(t, err) require.NoError(t, err)
@ -164,6 +157,7 @@ func TestAccessTokenStorageRevocation(t *testing.T) {
err = storage.RevokeAccessToken(ctx, "abcd-1") err = storage.RevokeAccessToken(ctx, "abcd-1")
require.NoError(t, err) require.NoError(t, err)
testutil.LogActualJSONFromCreateAction(t, client, 0) // makes it easier to update expected values when needed
require.Equal(t, wantActions, client.Actions()) require.Equal(t, wantActions, client.Actions())
} }
@ -190,7 +184,7 @@ func TestWrongVersion(t *testing.T) {
}, },
}, },
Data: map[string][]byte{ Data: map[string][]byte{
"pinniped-storage-data": []byte(`{"request":{"id":"abcd-1","requestedAt":"0001-01-01T00:00:00Z","client":{"id":"pinny","redirect_uris":null,"grant_types":null,"response_types":null,"scopes":null,"audience":null,"public":true,"jwks_uri":"where","jwks":null,"token_endpoint_auth_method":"something","request_uris":null,"request_object_signing_alg":"","token_endpoint_auth_signing_alg":""},"scopes":null,"grantedScopes":null,"form":{"key":["val"]},"session":{"Claims":null,"Headers":null,"ExpiresAt":null,"Username":"snorlax","Subject":"panda"},"requestedAudience":null,"grantedAudience":null},"version":"not-the-right-version"}`), "pinniped-storage-data": []byte(`{"request":{"id":"abcd-1"},"version":"not-the-right-version"}`),
"pinniped-storage-version": []byte("1"), "pinniped-storage-version": []byte("1"),
}, },
Type: "storage.pinniped.dev/access-token", Type: "storage.pinniped.dev/access-token",
@ -200,7 +194,7 @@ func TestWrongVersion(t *testing.T) {
_, err = storage.GetAccessTokenSession(ctx, "fancy-signature", nil) _, err = storage.GetAccessTokenSession(ctx, "fancy-signature", nil)
require.EqualError(t, err, "access token request data has wrong version: access token session for fancy-signature has version not-the-right-version instead of 1") 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 2")
} }
func TestNilSessionRequest(t *testing.T) { func TestNilSessionRequest(t *testing.T) {
@ -218,7 +212,7 @@ func TestNilSessionRequest(t *testing.T) {
}, },
}, },
Data: map[string][]byte{ Data: map[string][]byte{
"pinniped-storage-data": []byte(`{"nonsense-key": "nonsense-value","version":"1"}`), "pinniped-storage-data": []byte(`{"nonsense-key": "nonsense-value","version":"2"}`),
"pinniped-storage-version": []byte("1"), "pinniped-storage-version": []byte("1"),
}, },
Type: "storage.pinniped.dev/access-token", Type: "storage.pinniped.dev/access-token",
@ -246,10 +240,10 @@ func TestCreateWithWrongRequesterDataTypes(t *testing.T) {
Client: &clientregistry.Client{}, Client: &clientregistry.Client{},
} }
err := storage.CreateAccessTokenSession(ctx, "signature-doesnt-matter", request) err := storage.CreateAccessTokenSession(ctx, "signature-doesnt-matter", request)
require.EqualError(t, err, "requester's session must be of type openid.DefaultSession") require.EqualError(t, err, "requester's session must be of type PinnipedSession")
request = &fosite.Request{ request = &fosite.Request{
Session: &openid.DefaultSession{}, Session: &psession.PinnipedSession{},
Client: nil, Client: nil,
} }
err = storage.CreateAccessTokenSession(ctx, "signature-doesnt-matter", request) err = storage.CreateAccessTokenSession(ctx, "signature-doesnt-matter", request)
@ -261,7 +255,7 @@ func TestCreateWithoutRequesterID(t *testing.T) {
request := &fosite.Request{ request := &fosite.Request{
ID: "", // empty ID ID: "", // empty ID
Session: &openid.DefaultSession{}, Session: &psession.PinnipedSession{},
Client: &clientregistry.Client{}, Client: &clientregistry.Client{},
} }
err := storage.CreateAccessTokenSession(ctx, "signature-doesnt-matter", request) err := storage.CreateAccessTokenSession(ctx, "signature-doesnt-matter", request)

View File

@ -11,7 +11,6 @@ import (
"github.com/ory/fosite" "github.com/ory/fosite"
"github.com/ory/fosite/handler/oauth2" "github.com/ory/fosite/handler/oauth2"
"github.com/ory/fosite/handler/openid"
"k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/api/errors"
corev1client "k8s.io/client-go/kubernetes/typed/core/v1" corev1client "k8s.io/client-go/kubernetes/typed/core/v1"
@ -19,6 +18,7 @@ import (
"go.pinniped.dev/internal/crud" "go.pinniped.dev/internal/crud"
"go.pinniped.dev/internal/fositestorage" "go.pinniped.dev/internal/fositestorage"
"go.pinniped.dev/internal/oidc/clientregistry" "go.pinniped.dev/internal/oidc/clientregistry"
"go.pinniped.dev/internal/psession"
) )
const ( const (
@ -27,7 +27,7 @@ const (
ErrInvalidAuthorizeRequestData = constable.Error("authorization request data must be present") ErrInvalidAuthorizeRequestData = constable.Error("authorization request data must be present")
ErrInvalidAuthorizeRequestVersion = constable.Error("authorization request data has wrong version") ErrInvalidAuthorizeRequestVersion = constable.Error("authorization request data has wrong version")
authorizeCodeStorageVersion = "1" authorizeCodeStorageVersion = "2"
) )
var _ oauth2.AuthorizeCodeStorage = &authorizeCodeStorage{} var _ oauth2.AuthorizeCodeStorage = &authorizeCodeStorage{}
@ -139,7 +139,7 @@ func NewValidEmptyAuthorizeCodeSession() *AuthorizeCodeSession {
return &AuthorizeCodeSession{ return &AuthorizeCodeSession{
Request: &fosite.Request{ Request: &fosite.Request{
Client: &clientregistry.Client{}, Client: &clientregistry.Client{},
Session: &openid.DefaultSession{}, Session: &psession.PinnipedSession{},
}, },
} }
} }
@ -169,161 +169,172 @@ func (e *errSerializationFailureWithCause) Error() string {
// ExpectedAuthorizeCodeSessionJSONFromFuzzing is used for round tripping tests. // ExpectedAuthorizeCodeSessionJSONFromFuzzing is used for round tripping tests.
// It is exported to allow integration tests to use it. // It is exported to allow integration tests to use it.
const ExpectedAuthorizeCodeSessionJSONFromFuzzing = `{ const ExpectedAuthorizeCodeSessionJSONFromFuzzing = `{
"active": true, "active": true,
"request": { "request": {
"id": "曑x螠Gæ鄋楨", "id": "曑x螠Gæ鄋楨",
"requestedAt": "2082-11-10T18:36:11.627253638Z", "requestedAt": "2082-11-10T18:36:11.627253638Z",
"client": { "client": {
"id": ":NJ¸Ɣ8(黋馛ÄRɴJa¶z", "id": ":NJ¸Ɣ8(黋馛ÄRɴJa¶z",
"client_secret": "UQ==", "client_secret": "UQ==",
"redirect_uris": [ "redirect_uris": [
"ǖ枭kʍ切厦ȳ箦;¥ʊXĝ奨誷傥祩d", "ǖ枭kʍ切厦ȳ箦;¥ʊXĝ奨誷傥祩d",
"zŇZ", "zŇZ",
"優蒼ĊɌț訫DŽǽeʀO2ƚ&N" "優蒼ĊɌț訫DŽǽeʀO2ƚ\u0026N"
], ],
"grant_types": [ "grant_types": [
"唐W6ɻ橩斚薛ɑƐ" "唐W6ɻ橩斚薛ɑƐ"
], ],
"response_types": [ "response_types": [
"w", "w",
"ǔŭe[u@阽羂ŷ-Ĵ½輢OÅ濲喾H" "ǔŭe[u@阽羂ŷ-Ĵ½輢OÅ濲喾H"
], ],
"scopes": [ "scopes": [
"G螩歐湡ƙı唡ɸğƎ&胢輢Ƈĵƚ" "G螩歐湡ƙı唡ɸğƎ\u0026胢輢Ƈĵƚ"
], ],
"audience": [ "audience": [
"ě" "ě"
], ],
"public": false, "public": false,
"jwks_uri": "o*泞羅ʘ Ⱦķ瀊垰7ã\")", "jwks_uri": "o*泞羅ʘ Ⱦķ瀊垰7ã\")",
"jwks": { "jwks": {
"keys": [ "keys": [
{ {
"kty": "OKP", "kty": "OKP",
"crv": "Ed25519", "crv": "Ed25519",
"x": "nK9xgX_iN7u3u_i8YOO7ZRT_WK028Vd_nhtsUu7Eo6E", "x": "nK9xgX_iN7u3u_i8YOO7ZRT_WK028Vd_nhtsUu7Eo6E",
"x5u": { "x5u": {
"Scheme": "", "Scheme": "",
"Opaque": "", "Opaque": "",
"User": null, "User": null,
"Host": "", "Host": "",
"Path": "", "Path": "",
"RawPath": "", "RawPath": "",
"ForceQuery": false, "ForceQuery": false,
"RawQuery": "", "RawQuery": "",
"Fragment": "", "Fragment": "",
"RawFragment": "" "RawFragment": ""
} }
}, },
{ {
"kty": "OKP", "kty": "OKP",
"crv": "Ed25519", "crv": "Ed25519",
"x": "UbbswQgzWhfGCRlwQmMp6fw_HoIoqkIaKT-2XN2fuYU", "x": "UbbswQgzWhfGCRlwQmMp6fw_HoIoqkIaKT-2XN2fuYU",
"x5u": { "x5u": {
"Scheme": "", "Scheme": "",
"Opaque": "", "Opaque": "",
"User": null, "User": null,
"Host": "", "Host": "",
"Path": "", "Path": "",
"RawPath": "", "RawPath": "",
"ForceQuery": false, "ForceQuery": false,
"RawQuery": "", "RawQuery": "",
"Fragment": "", "Fragment": "",
"RawFragment": "" "RawFragment": ""
} }
} }
] ]
}, },
"token_endpoint_auth_method": "ƿʥǟȒ伉<x¹T鼓c吏", "token_endpoint_auth_method": "ƿʥǟȒ伉\u003cx¹T鼓c吏",
"request_uris": [ "request_uris": [
"Ć捘j]=谅ʑɑɮ$Ól4Ȟ", "Ć捘j]=谅ʑɑɮ$Ól4Ȟ",
",Q7钎漡臧n" ",Q7钎漡臧n"
], ],
"request_object_signing_alg": "3@¡廜+v,淬Ʋ4Dʧ呩锏緍场", "request_object_signing_alg": "3@¡廜+v,淬Ʋ4Dʧ呩锏緍场",
"token_endpoint_auth_signing_alg": "(ưƓǴ罷ǹ~]ea胠" "token_endpoint_auth_signing_alg": "(ưƓǴ罷ǹ~]ea胠"
}, },
"scopes": [ "scopes": [
"ĩv絹b垇IŕĩǀŻQ'k頂箨J-a稆", "ĩv絹b垇IŕĩǀŻQ'k頂箨J-a稆",
"啶#昏Q遐*\\髎bŸ" "啶#昏Q遐*\\髎bŸ"
], ],
"grantedScopes": [ "grantedScopes": [
"慂UFƼĮǡ鑻Z" "慂UFƼĮǡ鑻Z"
], ],
"form": { "form": {
"褾攚ŝlĆ厦駳骪l拁乖¡J¿Ƈ妔": [ "褾攚ŝlĆ厦駳骪l拁乖¡J¿Ƈ妔": [
"懧¥ɂĵ~Čyʊ恀c\"NJřðȿ/", "懧¥ɂĵ~Čyʊ恀c\"NJřðȿ/",
"裢?霃谥vƘ:ƿ/濔Aʉ<", "裢?霃谥vƘ:ƿ/濔Aʉ\u003c",
"ȭ$奍囀Dž悷鵱民撲ʓeŘ嬀j¤" "ȭ$奍囀Dž悷鵱民撲ʓeŘ嬀j¤"
], ],
"诞": [ "诞": [
"狲N<Cq罉ZPſĝEK郊©l", "狲N\u003cCq罉ZPſĝEK郊©l",
"餚LJ/ɷȑ潠[ĝU噤'pX ", "餚LJ/ɷȑ潠[ĝU噤'pX ",
"Y妶ǵ!ȁu狍ɶȳsčɦƦ诱" "Y妶ǵ!ȁu狍ɶȳsčɦƦ诱"
] ]
}, },
"session": { "session": {
"Claims": { "fosite": {
"JTI": "攬林Ñz焁糳¿o>Q鱙翑ȲŻ", "Claims": {
"Issuer": "锰劝旣樎Ȱ鍌#ȳńƩŴȭ", "JTI": "u妔隤ʑƍš駎竪0ɔ闏À1",
"Subject": "绝TFNJĆw宵ɚeY48珎²", "Issuer": "麤ã桒嘞\\摗Ǘū稖咾鎅ǸÖ绝TF",
"Audience": [ "Subject": "巽ēđų蓼tùZ蛆鬣a\"ÙǞ0觢Û±",
"éã越|j¦鲶H股ƲLŋZ-{5£踉4" "Audience": [
], "H股ƲL",
"Nonce": "5^驜Ŗ~ů崧軒q腟u尿", "肟v\u0026đehpƧ",
"ExpiresAt": "2065-11-30T13:47:03.613000626Z", "5^驜Ŗ~ů崧軒q腟u尿"
"IssuedAt": "1976-02-22T09:57:20.479850437Z", ],
"RequestedAt": "2016-04-13T04:18:53.648949323Z", "Nonce": "ğ",
"AuthTime": "2098-07-12T04:38:54.034043015Z", "ExpiresAt": "2016-11-22T21:33:58.460521133Z",
"AccessTokenHash": "嫯R", "IssuedAt": "1990-07-25T23:42:07.055978334Z",
"AuthenticationContextClassReference": "¤'+ʣ", "RequestedAt": "1971-01-30T00:23:36.377684025Z",
"AuthenticationMethodsReference": "L&ɽ艄ʬʏ", "AuthTime": "2088-11-09T12:09:14.051840239Z",
"CodeHash": "ğǫ\\aȊ4ț髄Al", "AccessTokenHash": "蕖¤'+ʣȍ瓁U4鞀",
"Extra": { "AuthenticationContextClassReference": "ʏÑęN\u003c_z",
"PƢ曰": { "AuthenticationMethodsReference": "ț髄A",
"ĸŴB岺Ð嫹Sx镯荫ő": [ "CodeHash": "4磔_袻vÓG-壧丵礴鋈k蟵pAɂʅ",
843216989 "Extra": {
], "#\u0026PƢ曰l騌蘙螤\\阏Đ镴Ƥm蔻ǭ\\鿞": 1677215584,
"疂ư墫ɓ": { "Y\u0026鶡萷ɵ啜s攦Ɩïdnǔ": {
"\\BRë_g\"ʎ啴SƇMǃļ": { ",t猟i\u0026\u0026Q@ǤǟǗǪ飘ȱF?Ƈ": {
"ʦ4": false "~劰û橸ɽ銐ƭ?}H": null,
}, "癑勦e骲v0H晦XŘO溪V蔓": {
"鶡萷ɵ啜s攦": null "碼Ǫ": false
} }
}, },
"曓蓳n匟鯘磹*金爃鶴滱ůĮǐ_c3#": 2520197933 "钻煐ɨəÅDČ{Ȩʦ4撎": [
} 3684968178
}, ]
"Headers": { }
"Extra": { }
"寱ĊƑ÷Ƒ螞费Ďğ~劰û橸ɽ銐ƭ?}": {
"ȜʁɁ;Bd謺錳4帳ŅǃĊd": {
"翢砜Fȏl鐉诳DT=3骜": {
"ų厷ɁOƪ穋嶿鳈恱va|载ǰɱ汶C": false
},
"鸨EJ毕懴řĬń戹%c": null
}, },
"室癑勦e骲v0H晦XŘO溪V蔓Ȍ+~ē": [ "Headers": {
954647573 "Extra": {
] "ĊdŘ鸨EJ毕懴řĬń戹": {
}, "诳DT=3骜Ǹ,": {
"麈ƵDǀ\\郂üţ垂": 1572524915 "\u003e": {
"ǰ": false
},
"ɁOƪ穋嶿鳈恱va": null
},
"豑觳翢砜Fȏl": [
927958776
]
},
"埅ȜʁɁ;Bd謺錳4帳Ņ": 388005986
}
},
"ExpiresAt": {
"C]ɲ'=ĸ闒NȢȰ.醋": "1970-07-19T18:03:29.902062193Z",
"fɤȆʪ融ƆuŤn": "2064-01-24T20:34:16.593152073Z",
"爣縗ɦüHêQ仏1ő": "2102-03-17T06:24:40.256846902Z"
},
"Username": "韁臯氃妪婝rȤ\"h丬鎒ơ娻}ɼƟ",
"Subject": "闺髉龳ǽÙ龦O亾EW莛8嘶×"
},
"custom": {
"providerUID": "鵮碡ʯiŬŽ非Ĝ眧Ĭ葜SŦ餧Ĭ倏4",
"providerName": "nŐǛ3",
"providerType": "闣ʬ橳(ý綃ʃʚƟ覣k眐4Ĉt",
"oidc": {
"upstreamRefreshToken": "嵽痊w©Ź榨Q|ôɵt毇妬"
}
} }
},
"ExpiresAt": {
"'=ĸ闒NȢȰ.醋fʜ": "2031-10-18T22:07:34.950803105Z",
"ɦüHêQ仏1őƖ2Ė暮唍ǞʜƢú4": "2049-05-13T15:27:20.968432454Z"
},
"Username": "+韁臯氃妪婝rȤ\"h丬鎒ơ娻}ɼƟȥE",
"Subject": "龳ǽÙ龦O亾EW莛8嘶×姮c恭企"
}, },
"requestedAudience": [ "requestedAudience": [
"邖ɐ5檄¬", "6鉢緋uƴŤȱʀļÂ?墖\u003cƬb獭潜Ʃ饾"
"Ĭ葜SŦ餧Ĭ倏4ĵ嶼仒篻ɥ闣ʬ橳(ý綃"
], ],
"grantedAudience": [ "grantedAudience": [
"ʚƟ覣k眐4ĈtC嵽痊w©Ź榨Q|ô", "|鬌R蜚蠣麹概÷驣7Ʀ澉1æɽ誮rʨ鷞"
"猊Ia瓕巈環_ɑƍ蛊ʚ£:設虝2"
] ]
}, },
"version": "1" "version": "2"
}` }`

View File

@ -18,7 +18,6 @@ import (
fuzz "github.com/google/gofuzz" fuzz "github.com/google/gofuzz"
"github.com/ory/fosite" "github.com/ory/fosite"
"github.com/ory/fosite/handler/oauth2" "github.com/ory/fosite/handler/oauth2"
"github.com/ory/fosite/handler/openid"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"gopkg.in/square/go-jose.v2" "gopkg.in/square/go-jose.v2"
@ -34,6 +33,8 @@ import (
"go.pinniped.dev/internal/fositestorage" "go.pinniped.dev/internal/fositestorage"
"go.pinniped.dev/internal/oidc/clientregistry" "go.pinniped.dev/internal/oidc/clientregistry"
"go.pinniped.dev/internal/psession"
"go.pinniped.dev/internal/testutil"
) )
const namespace = "test-ns" const namespace = "test-ns"
@ -62,7 +63,7 @@ func TestAuthorizationCodeStorage(t *testing.T) {
}, },
}, },
Data: map[string][]byte{ Data: map[string][]byte{
"pinniped-storage-data": []byte(`{"active":true,"request":{"id":"abcd-1","requestedAt":"0001-01-01T00:00:00Z","client":{"id":"pinny","redirect_uris":null,"grant_types":null,"response_types":null,"scopes":null,"audience":null,"public":true,"jwks_uri":"where","jwks":null,"token_endpoint_auth_method":"something","request_uris":null,"request_object_signing_alg":"","token_endpoint_auth_signing_alg":""},"scopes":null,"grantedScopes":null,"form":{"key":["val"]},"session":{"Claims":null,"Headers":null,"ExpiresAt":null,"Username":"snorlax","Subject":"panda"},"requestedAudience":null,"grantedAudience":null},"version":"1"}`), "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":{"providerUID":"fake-provider-uid","providerName":"fake-provider-name","providerType":"fake-provider-type","oidc":{"upstreamRefreshToken":"fake-upstream-refresh-token"}}},"requestedAudience":null,"grantedAudience":null},"version":"2"}`),
"pinniped-storage-version": []byte("1"), "pinniped-storage-version": []byte("1"),
}, },
Type: "storage.pinniped.dev/authcode", Type: "storage.pinniped.dev/authcode",
@ -81,7 +82,7 @@ func TestAuthorizationCodeStorage(t *testing.T) {
}, },
}, },
Data: map[string][]byte{ Data: map[string][]byte{
"pinniped-storage-data": []byte(`{"active":false,"request":{"id":"abcd-1","requestedAt":"0001-01-01T00:00:00Z","client":{"id":"pinny","redirect_uris":null,"grant_types":null,"response_types":null,"scopes":null,"audience":null,"public":true,"jwks_uri":"where","jwks":null,"token_endpoint_auth_method":"something","request_uris":null,"request_object_signing_alg":"","token_endpoint_auth_signing_alg":""},"scopes":null,"grantedScopes":null,"form":{"key":["val"]},"session":{"Claims":null,"Headers":null,"ExpiresAt":null,"Username":"snorlax","Subject":"panda"},"requestedAudience":null,"grantedAudience":null},"version":"1"}`), "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":{"providerUID":"fake-provider-uid","providerName":"fake-provider-name","providerType":"fake-provider-type","oidc":{"upstreamRefreshToken":"fake-upstream-refresh-token"}}},"requestedAudience":null,"grantedAudience":null},"version":"2"}`),
"pinniped-storage-version": []byte("1"), "pinniped-storage-version": []byte("1"),
}, },
Type: "storage.pinniped.dev/authcode", Type: "storage.pinniped.dev/authcode",
@ -113,16 +114,10 @@ func TestAuthorizationCodeStorage(t *testing.T) {
TokenEndpointAuthSigningAlgorithm: "", TokenEndpointAuthSigningAlgorithm: "",
}, },
}, },
RequestedScope: nil, RequestedScope: nil,
GrantedScope: nil, GrantedScope: nil,
Form: url.Values{"key": []string{"val"}}, Form: url.Values{"key": []string{"val"}},
Session: &openid.DefaultSession{ Session: testutil.NewFakePinnipedSession(),
Claims: nil,
Headers: nil,
ExpiresAt: nil,
Username: "snorlax",
Subject: "panda",
},
RequestedAudience: nil, RequestedAudience: nil,
GrantedAudience: nil, GrantedAudience: nil,
} }
@ -136,6 +131,8 @@ func TestAuthorizationCodeStorage(t *testing.T) {
err = storage.InvalidateAuthorizeCodeSession(ctx, "fancy-signature") err = storage.InvalidateAuthorizeCodeSession(ctx, "fancy-signature")
require.NoError(t, err) require.NoError(t, err)
testutil.LogActualJSONFromCreateAction(t, client, 0) // makes it easier to update expected values when needed
testutil.LogActualJSONFromUpdateAction(t, client, 3) // makes it easier to update expected values when needed
require.Equal(t, wantActions, client.Actions()) require.Equal(t, wantActions, client.Actions())
// Doing a Get on an invalidated session should still return the session, but also return an error. // Doing a Get on an invalidated session should still return the session, but also return an error.
@ -173,7 +170,7 @@ func TestInvalidateWhenConflictOnUpdateHappens(t *testing.T) {
request := &fosite.Request{ request := &fosite.Request{
ID: "some-request-id", ID: "some-request-id",
Client: &clientregistry.Client{}, Client: &clientregistry.Client{},
Session: &openid.DefaultSession{}, Session: testutil.NewFakePinnipedSession(),
} }
err := storage.CreateAuthorizeCodeSession(ctx, "fancy-signature", request) err := storage.CreateAuthorizeCodeSession(ctx, "fancy-signature", request)
require.NoError(t, err) require.NoError(t, err)
@ -193,7 +190,7 @@ func TestWrongVersion(t *testing.T) {
}, },
}, },
Data: map[string][]byte{ Data: map[string][]byte{
"pinniped-storage-data": []byte(`{"request":{"id":"abcd-1","requestedAt":"0001-01-01T00:00:00Z","client":{"id":"pinny","redirect_uris":null,"grant_types":null,"response_types":null,"scopes":null,"audience":null,"public":true,"jwks_uri":"where","jwks":null,"token_endpoint_auth_method":"something","request_uris":null,"request_object_signing_alg":"","token_endpoint_auth_signing_alg":""},"scopes":null,"grantedScopes":null,"form":{"key":["val"]},"session":{"Claims":null,"Headers":null,"ExpiresAt":null,"Username":"snorlax","Subject":"panda"},"requestedAudience":null,"grantedAudience":null},"version":"not-the-right-version", "active": true}`), "pinniped-storage-data": []byte(`{"request":{"id":"abcd-1"},"version":"not-the-right-version","active": true}`),
"pinniped-storage-version": []byte("1"), "pinniped-storage-version": []byte("1"),
}, },
Type: "storage.pinniped.dev/authcode", Type: "storage.pinniped.dev/authcode",
@ -203,7 +200,7 @@ func TestWrongVersion(t *testing.T) {
_, err = storage.GetAuthorizeCodeSession(ctx, "fancy-signature", nil) _, err = storage.GetAuthorizeCodeSession(ctx, "fancy-signature", nil)
require.EqualError(t, err, "authorization request data has wrong version: authorization code session for fancy-signature has version not-the-right-version instead of 1") require.EqualError(t, err, "authorization request data has wrong version: authorization code session for fancy-signature has version not-the-right-version instead of 2")
} }
func TestNilSessionRequest(t *testing.T) { func TestNilSessionRequest(t *testing.T) {
@ -218,7 +215,7 @@ func TestNilSessionRequest(t *testing.T) {
}, },
}, },
Data: map[string][]byte{ Data: map[string][]byte{
"pinniped-storage-data": []byte(`{"nonsense-key": "nonsense-value", "version":"1", "active": true}`), "pinniped-storage-data": []byte(`{"nonsense-key": "nonsense-value", "version":"2", "active": true}`),
"pinniped-storage-version": []byte("1"), "pinniped-storage-version": []byte("1"),
}, },
Type: "storage.pinniped.dev/authcode", Type: "storage.pinniped.dev/authcode",
@ -246,10 +243,10 @@ func TestCreateWithWrongRequesterDataTypes(t *testing.T) {
Client: &clientregistry.Client{}, Client: &clientregistry.Client{},
} }
err := storage.CreateAuthorizeCodeSession(ctx, "signature-doesnt-matter", request) err := storage.CreateAuthorizeCodeSession(ctx, "signature-doesnt-matter", request)
require.EqualError(t, err, "requester's session must be of type openid.DefaultSession") require.EqualError(t, err, "requester's session must be of type PinnipedSession")
request = &fosite.Request{ request = &fosite.Request{
Session: &openid.DefaultSession{}, Session: &psession.PinnipedSession{},
Client: nil, Client: nil,
} }
err = storage.CreateAuthorizeCodeSession(ctx, "signature-doesnt-matter", request) err = storage.CreateAuthorizeCodeSession(ctx, "signature-doesnt-matter", request)
@ -274,7 +271,7 @@ func TestFuzzAndJSONNewValidEmptyAuthorizeCodeSession(t *testing.T) {
// checked above // checked above
defaultClient := validSession.Request.Client.(*clientregistry.Client) defaultClient := validSession.Request.Client.(*clientregistry.Client)
defaultSession := validSession.Request.Session.(*openid.DefaultSession) pinnipedSession := validSession.Request.Session.(*psession.PinnipedSession)
// makes it easier to use a raw string // makes it easier to use a raw string
replacer := strings.NewReplacer("`", "a") replacer := strings.NewReplacer("`", "a")
@ -297,12 +294,12 @@ func TestFuzzAndJSONNewValidEmptyAuthorizeCodeSession(t *testing.T) {
*fc = defaultClient *fc = defaultClient
}, },
func(fs *fosite.Session, c fuzz.Continue) { func(fs *fosite.Session, c fuzz.Continue) {
c.Fuzz(defaultSession) c.Fuzz(pinnipedSession)
*fs = defaultSession *fs = pinnipedSession
}, },
// these types contain an interface{} that we need to handle // these types contain an interface{} that we need to handle
// this is safe because we explicitly provide the openid.DefaultSession concrete type // this is safe because we explicitly provide the PinnipedSession concrete type
func(value *map[string]interface{}, c fuzz.Continue) { func(value *map[string]interface{}, c fuzz.Continue) {
// cover all the JSON data types just in case // cover all the JSON data types just in case
*value = map[string]interface{}{ *value = map[string]interface{}{
@ -382,7 +379,7 @@ func TestFuzzAndJSONNewValidEmptyAuthorizeCodeSession(t *testing.T) {
// set these to match CreateAuthorizeCodeSession so that .JSONEq works // set these to match CreateAuthorizeCodeSession so that .JSONEq works
validSession.Active = true validSession.Active = true
validSession.Version = "1" validSession.Version = "2"
validSessionJSONBytes, err := json.MarshalIndent(validSession, "", "\t") validSessionJSONBytes, err := json.MarshalIndent(validSession, "", "\t")
require.NoError(t, err) require.NoError(t, err)

View File

@ -5,16 +5,16 @@ package fositestorage
import ( import (
"github.com/ory/fosite" "github.com/ory/fosite"
"github.com/ory/fosite/handler/openid"
"go.pinniped.dev/internal/constable" "go.pinniped.dev/internal/constable"
"go.pinniped.dev/internal/oidc/clientregistry" "go.pinniped.dev/internal/oidc/clientregistry"
"go.pinniped.dev/internal/psession"
) )
const ( const (
ErrInvalidRequestType = constable.Error("requester must be of type fosite.Request") ErrInvalidRequestType = constable.Error("requester must be of type fosite.Request")
ErrInvalidClientType = constable.Error("requester's client must be of type clientregistry.Client") ErrInvalidClientType = constable.Error("requester's client must be of type clientregistry.Client")
ErrInvalidSessionType = constable.Error("requester's session must be of type openid.DefaultSession") ErrInvalidSessionType = constable.Error("requester's session must be of type PinnipedSession")
StorageRequestIDLabelName = "storage.pinniped.dev/request-id" //nolint:gosec // this is not a credential StorageRequestIDLabelName = "storage.pinniped.dev/request-id" //nolint:gosec // this is not a credential
) )
@ -27,7 +27,7 @@ func ValidateAndExtractAuthorizeRequest(requester fosite.Requester) (*fosite.Req
if !ok2 { if !ok2 {
return nil, ErrInvalidClientType return nil, ErrInvalidClientType
} }
_, ok3 := request.Session.(*openid.DefaultSession) _, ok3 := request.Session.(*psession.PinnipedSession)
if !ok3 { if !ok3 {
return nil, ErrInvalidSessionType return nil, ErrInvalidSessionType
} }

View File

@ -18,6 +18,7 @@ import (
"go.pinniped.dev/internal/crud" "go.pinniped.dev/internal/crud"
"go.pinniped.dev/internal/fositestorage" "go.pinniped.dev/internal/fositestorage"
"go.pinniped.dev/internal/oidc/clientregistry" "go.pinniped.dev/internal/oidc/clientregistry"
"go.pinniped.dev/internal/psession"
) )
const ( const (
@ -27,7 +28,7 @@ const (
ErrInvalidOIDCRequestData = constable.Error("oidc request data must be present") ErrInvalidOIDCRequestData = constable.Error("oidc request data must be present")
ErrMalformedAuthorizationCode = constable.Error("malformed authorization code") ErrMalformedAuthorizationCode = constable.Error("malformed authorization code")
oidcStorageVersion = "1" oidcStorageVersion = "2"
) )
var _ openid.OpenIDConnectRequestStorage = &openIDConnectRequestStorage{} var _ openid.OpenIDConnectRequestStorage = &openIDConnectRequestStorage{}
@ -112,7 +113,7 @@ func newValidEmptyOIDCSession() *session {
return &session{ return &session{
Request: &fosite.Request{ Request: &fosite.Request{
Client: &clientregistry.Client{}, Client: &clientregistry.Client{},
Session: &openid.DefaultSession{}, Session: &psession.PinnipedSession{},
}, },
} }
} }

View File

@ -22,6 +22,8 @@ import (
coretesting "k8s.io/client-go/testing" coretesting "k8s.io/client-go/testing"
"go.pinniped.dev/internal/oidc/clientregistry" "go.pinniped.dev/internal/oidc/clientregistry"
"go.pinniped.dev/internal/psession"
"go.pinniped.dev/internal/testutil"
) )
const namespace = "test-ns" const namespace = "test-ns"
@ -50,7 +52,7 @@ func TestOpenIdConnectStorage(t *testing.T) {
}, },
}, },
Data: map[string][]byte{ Data: map[string][]byte{
"pinniped-storage-data": []byte(`{"request":{"id":"abcd-1","requestedAt":"0001-01-01T00:00:00Z","client":{"id":"pinny","redirect_uris":null,"grant_types":null,"response_types":null,"scopes":null,"audience":null,"public":true,"jwks_uri":"where","jwks":null,"token_endpoint_auth_method":"something","request_uris":null,"request_object_signing_alg":"","token_endpoint_auth_signing_alg":""},"scopes":null,"grantedScopes":null,"form":{"key":["val"]},"session":{"Claims":null,"Headers":null,"ExpiresAt":null,"Username":"snorlax","Subject":"panda"},"requestedAudience":null,"grantedAudience":null},"version":"1"}`), "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":{"providerUID":"fake-provider-uid","providerName":"fake-provider-name","providerType":"fake-provider-type","oidc":{"upstreamRefreshToken":"fake-upstream-refresh-token"}}},"requestedAudience":null,"grantedAudience":null},"version":"2"}`),
"pinniped-storage-version": []byte("1"), "pinniped-storage-version": []byte("1"),
}, },
Type: "storage.pinniped.dev/oidc", Type: "storage.pinniped.dev/oidc",
@ -84,16 +86,10 @@ func TestOpenIdConnectStorage(t *testing.T) {
TokenEndpointAuthSigningAlgorithm: "", TokenEndpointAuthSigningAlgorithm: "",
}, },
}, },
RequestedScope: nil, RequestedScope: nil,
GrantedScope: nil, GrantedScope: nil,
Form: url.Values{"key": []string{"val"}}, Form: url.Values{"key": []string{"val"}},
Session: &openid.DefaultSession{ Session: testutil.NewFakePinnipedSession(),
Claims: nil,
Headers: nil,
ExpiresAt: nil,
Username: "snorlax",
Subject: "panda",
},
RequestedAudience: nil, RequestedAudience: nil,
GrantedAudience: nil, GrantedAudience: nil,
} }
@ -107,6 +103,7 @@ func TestOpenIdConnectStorage(t *testing.T) {
err = storage.DeleteOpenIDConnectSession(ctx, "fancy-code.fancy-signature") err = storage.DeleteOpenIDConnectSession(ctx, "fancy-code.fancy-signature")
require.NoError(t, err) require.NoError(t, err)
testutil.LogActualJSONFromCreateAction(t, client, 0) // makes it easier to update expected values when needed
require.Equal(t, wantActions, client.Actions()) require.Equal(t, wantActions, client.Actions())
} }
@ -130,7 +127,7 @@ func TestWrongVersion(t *testing.T) {
}, },
}, },
Data: map[string][]byte{ Data: map[string][]byte{
"pinniped-storage-data": []byte(`{"request":{"id":"abcd-1","requestedAt":"0001-01-01T00:00:00Z","client":{"id":"pinny","redirect_uris":null,"grant_types":null,"response_types":null,"scopes":null,"audience":null,"public":true,"jwks_uri":"where","jwks":null,"token_endpoint_auth_method":"something","request_uris":null,"request_object_signing_alg":"","token_endpoint_auth_signing_alg":""},"scopes":null,"grantedScopes":null,"form":{"key":["val"]},"session":{"Claims":null,"Headers":null,"ExpiresAt":null,"Username":"snorlax","Subject":"panda"},"requestedAudience":null,"grantedAudience":null},"version":"not-the-right-version"}`), "pinniped-storage-data": []byte(`{"request":{"id":"abcd-1"},"version":"not-the-right-version"}`),
"pinniped-storage-version": []byte("1"), "pinniped-storage-version": []byte("1"),
}, },
Type: "storage.pinniped.dev/oidc", Type: "storage.pinniped.dev/oidc",
@ -140,7 +137,7 @@ func TestWrongVersion(t *testing.T) {
_, err = storage.GetOpenIDConnectSession(ctx, "fancy-code.fancy-signature", nil) _, err = storage.GetOpenIDConnectSession(ctx, "fancy-code.fancy-signature", nil)
require.EqualError(t, err, "oidc request data has wrong version: oidc session for fancy-signature has version not-the-right-version instead of 1") require.EqualError(t, err, "oidc request data has wrong version: oidc session for fancy-signature has version not-the-right-version instead of 2")
} }
func TestNilSessionRequest(t *testing.T) { func TestNilSessionRequest(t *testing.T) {
@ -155,7 +152,7 @@ func TestNilSessionRequest(t *testing.T) {
}, },
}, },
Data: map[string][]byte{ Data: map[string][]byte{
"pinniped-storage-data": []byte(`{"nonsense-key": "nonsense-value","version":"1"}`), "pinniped-storage-data": []byte(`{"nonsense-key": "nonsense-value","version":"2"}`),
"pinniped-storage-version": []byte("1"), "pinniped-storage-version": []byte("1"),
}, },
Type: "storage.pinniped.dev/oidc", Type: "storage.pinniped.dev/oidc",
@ -183,10 +180,10 @@ func TestCreateWithWrongRequesterDataTypes(t *testing.T) {
Client: &clientregistry.Client{}, Client: &clientregistry.Client{},
} }
err := storage.CreateOpenIDConnectSession(ctx, "authcode.signature-doesnt-matter", request) err := storage.CreateOpenIDConnectSession(ctx, "authcode.signature-doesnt-matter", request)
require.EqualError(t, err, "requester's session must be of type openid.DefaultSession") require.EqualError(t, err, "requester's session must be of type PinnipedSession")
request = &fosite.Request{ request = &fosite.Request{
Session: &openid.DefaultSession{}, Session: &psession.PinnipedSession{},
Client: nil, Client: nil,
} }
err = storage.CreateOpenIDConnectSession(ctx, "authcode.signature-doesnt-matter", request) err = storage.CreateOpenIDConnectSession(ctx, "authcode.signature-doesnt-matter", request)

View File

@ -9,7 +9,6 @@ import (
"time" "time"
"github.com/ory/fosite" "github.com/ory/fosite"
"github.com/ory/fosite/handler/openid"
"github.com/ory/fosite/handler/pkce" "github.com/ory/fosite/handler/pkce"
"k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/api/errors"
corev1client "k8s.io/client-go/kubernetes/typed/core/v1" corev1client "k8s.io/client-go/kubernetes/typed/core/v1"
@ -18,6 +17,7 @@ import (
"go.pinniped.dev/internal/crud" "go.pinniped.dev/internal/crud"
"go.pinniped.dev/internal/fositestorage" "go.pinniped.dev/internal/fositestorage"
"go.pinniped.dev/internal/oidc/clientregistry" "go.pinniped.dev/internal/oidc/clientregistry"
"go.pinniped.dev/internal/psession"
) )
const ( const (
@ -26,7 +26,7 @@ const (
ErrInvalidPKCERequestVersion = constable.Error("pkce request data has wrong version") ErrInvalidPKCERequestVersion = constable.Error("pkce request data has wrong version")
ErrInvalidPKCERequestData = constable.Error("pkce request data must be present") ErrInvalidPKCERequestData = constable.Error("pkce request data must be present")
pkceStorageVersion = "1" pkceStorageVersion = "2"
) )
var _ pkce.PKCERequestStorage = &pkceStorage{} var _ pkce.PKCERequestStorage = &pkceStorage{}
@ -96,7 +96,7 @@ func newValidEmptyPKCESession() *session {
return &session{ return &session{
Request: &fosite.Request{ Request: &fosite.Request{
Client: &clientregistry.Client{}, Client: &clientregistry.Client{},
Session: &openid.DefaultSession{}, Session: &psession.PinnipedSession{},
}, },
} }
} }

View File

@ -10,7 +10,6 @@ import (
"time" "time"
"github.com/ory/fosite" "github.com/ory/fosite"
"github.com/ory/fosite/handler/openid"
"github.com/ory/fosite/handler/pkce" "github.com/ory/fosite/handler/pkce"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
@ -23,6 +22,8 @@ import (
coretesting "k8s.io/client-go/testing" coretesting "k8s.io/client-go/testing"
"go.pinniped.dev/internal/oidc/clientregistry" "go.pinniped.dev/internal/oidc/clientregistry"
"go.pinniped.dev/internal/psession"
"go.pinniped.dev/internal/testutil"
) )
const namespace = "test-ns" const namespace = "test-ns"
@ -51,7 +52,7 @@ func TestPKCEStorage(t *testing.T) {
}, },
}, },
Data: map[string][]byte{ Data: map[string][]byte{
"pinniped-storage-data": []byte(`{"request":{"id":"abcd-1","requestedAt":"0001-01-01T00:00:00Z","client":{"id":"pinny","redirect_uris":null,"grant_types":null,"response_types":null,"scopes":null,"audience":null,"public":true,"jwks_uri":"where","jwks":null,"token_endpoint_auth_method":"something","request_uris":null,"request_object_signing_alg":"","token_endpoint_auth_signing_alg":""},"scopes":null,"grantedScopes":null,"form":{"key":["val"]},"session":{"Claims":null,"Headers":null,"ExpiresAt":null,"Username":"snorlax","Subject":"panda"},"requestedAudience":null,"grantedAudience":null},"version":"1"}`), "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":{"providerUID":"fake-provider-uid","providerName":"fake-provider-name","providerType":"fake-provider-type","oidc":{"upstreamRefreshToken":"fake-upstream-refresh-token"}}},"requestedAudience":null,"grantedAudience":null},"version":"2"}`),
"pinniped-storage-version": []byte("1"), "pinniped-storage-version": []byte("1"),
}, },
Type: "storage.pinniped.dev/pkce", Type: "storage.pinniped.dev/pkce",
@ -85,16 +86,10 @@ func TestPKCEStorage(t *testing.T) {
TokenEndpointAuthSigningAlgorithm: "", TokenEndpointAuthSigningAlgorithm: "",
}, },
}, },
RequestedScope: nil, RequestedScope: nil,
GrantedScope: nil, GrantedScope: nil,
Form: url.Values{"key": []string{"val"}}, Form: url.Values{"key": []string{"val"}},
Session: &openid.DefaultSession{ Session: testutil.NewFakePinnipedSession(),
Claims: nil,
Headers: nil,
ExpiresAt: nil,
Username: "snorlax",
Subject: "panda",
},
RequestedAudience: nil, RequestedAudience: nil,
GrantedAudience: nil, GrantedAudience: nil,
} }
@ -108,6 +103,7 @@ func TestPKCEStorage(t *testing.T) {
err = storage.DeletePKCERequestSession(ctx, "fancy-signature") err = storage.DeletePKCERequestSession(ctx, "fancy-signature")
require.NoError(t, err) require.NoError(t, err)
testutil.LogActualJSONFromCreateAction(t, client, 0) // makes it easier to update expected values when needed
require.Equal(t, wantActions, client.Actions()) require.Equal(t, wantActions, client.Actions())
} }
@ -134,7 +130,7 @@ func TestWrongVersion(t *testing.T) {
}, },
}, },
Data: map[string][]byte{ Data: map[string][]byte{
"pinniped-storage-data": []byte(`{"request":{"id":"abcd-1","requestedAt":"0001-01-01T00:00:00Z","client":{"id":"pinny","redirect_uris":null,"grant_types":null,"response_types":null,"scopes":null,"audience":null,"public":true,"jwks_uri":"where","jwks":null,"token_endpoint_auth_method":"something","request_uris":null,"request_object_signing_alg":"","token_endpoint_auth_signing_alg":""},"scopes":null,"grantedScopes":null,"form":{"key":["val"]},"session":{"Claims":null,"Headers":null,"ExpiresAt":null,"Username":"snorlax","Subject":"panda"},"requestedAudience":null,"grantedAudience":null},"version":"not-the-right-version"}`), "pinniped-storage-data": []byte(`{"request":{"id":"abcd-1"},"version":"not-the-right-version"}`),
"pinniped-storage-version": []byte("1"), "pinniped-storage-version": []byte("1"),
}, },
Type: "storage.pinniped.dev/pkce", Type: "storage.pinniped.dev/pkce",
@ -144,7 +140,7 @@ func TestWrongVersion(t *testing.T) {
_, err = storage.GetPKCERequestSession(ctx, "fancy-signature", nil) _, err = storage.GetPKCERequestSession(ctx, "fancy-signature", nil)
require.EqualError(t, err, "pkce request data has wrong version: pkce session for fancy-signature has version not-the-right-version instead of 1") require.EqualError(t, err, "pkce request data has wrong version: pkce session for fancy-signature has version not-the-right-version instead of 2")
} }
func TestNilSessionRequest(t *testing.T) { func TestNilSessionRequest(t *testing.T) {
@ -162,7 +158,7 @@ func TestNilSessionRequest(t *testing.T) {
}, },
}, },
Data: map[string][]byte{ Data: map[string][]byte{
"pinniped-storage-data": []byte(`{"nonsense-key": "nonsense-value","version":"1"}`), "pinniped-storage-data": []byte(`{"nonsense-key": "nonsense-value","version":"2"}`),
"pinniped-storage-version": []byte("1"), "pinniped-storage-version": []byte("1"),
}, },
Type: "storage.pinniped.dev/pkce", Type: "storage.pinniped.dev/pkce",
@ -190,10 +186,10 @@ func TestCreateWithWrongRequesterDataTypes(t *testing.T) {
Client: &clientregistry.Client{}, Client: &clientregistry.Client{},
} }
err := storage.CreatePKCERequestSession(ctx, "signature-doesnt-matter", request) err := storage.CreatePKCERequestSession(ctx, "signature-doesnt-matter", request)
require.EqualError(t, err, "requester's session must be of type openid.DefaultSession") require.EqualError(t, err, "requester's session must be of type PinnipedSession")
request = &fosite.Request{ request = &fosite.Request{
Session: &openid.DefaultSession{}, Session: &psession.PinnipedSession{},
Client: nil, Client: nil,
} }
err = storage.CreatePKCERequestSession(ctx, "signature-doesnt-matter", request) err = storage.CreatePKCERequestSession(ctx, "signature-doesnt-matter", request)

View File

@ -10,7 +10,6 @@ import (
"github.com/ory/fosite" "github.com/ory/fosite"
"github.com/ory/fosite/handler/oauth2" "github.com/ory/fosite/handler/oauth2"
"github.com/ory/fosite/handler/openid"
"k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/api/errors"
corev1client "k8s.io/client-go/kubernetes/typed/core/v1" corev1client "k8s.io/client-go/kubernetes/typed/core/v1"
@ -18,6 +17,7 @@ import (
"go.pinniped.dev/internal/crud" "go.pinniped.dev/internal/crud"
"go.pinniped.dev/internal/fositestorage" "go.pinniped.dev/internal/fositestorage"
"go.pinniped.dev/internal/oidc/clientregistry" "go.pinniped.dev/internal/oidc/clientregistry"
"go.pinniped.dev/internal/psession"
) )
const ( const (
@ -26,7 +26,7 @@ const (
ErrInvalidRefreshTokenRequestVersion = constable.Error("refresh token request data has wrong version") ErrInvalidRefreshTokenRequestVersion = constable.Error("refresh token request data has wrong version")
ErrInvalidRefreshTokenRequestData = constable.Error("refresh token request data must be present") ErrInvalidRefreshTokenRequestData = constable.Error("refresh token request data must be present")
refreshTokenStorageVersion = "1" refreshTokenStorageVersion = "2"
) )
type RevocationStorage interface { type RevocationStorage interface {
@ -110,7 +110,7 @@ func newValidEmptyRefreshTokenSession() *session {
return &session{ return &session{
Request: &fosite.Request{ Request: &fosite.Request{
Client: &clientregistry.Client{}, Client: &clientregistry.Client{},
Session: &openid.DefaultSession{}, Session: &psession.PinnipedSession{},
}, },
} }
} }

View File

@ -10,7 +10,6 @@ import (
"time" "time"
"github.com/ory/fosite" "github.com/ory/fosite"
"github.com/ory/fosite/handler/openid"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
corev1 "k8s.io/api/core/v1" corev1 "k8s.io/api/core/v1"
@ -22,6 +21,8 @@ import (
coretesting "k8s.io/client-go/testing" coretesting "k8s.io/client-go/testing"
"go.pinniped.dev/internal/oidc/clientregistry" "go.pinniped.dev/internal/oidc/clientregistry"
"go.pinniped.dev/internal/psession"
"go.pinniped.dev/internal/testutil"
) )
const namespace = "test-ns" const namespace = "test-ns"
@ -50,7 +51,7 @@ func TestRefreshTokenStorage(t *testing.T) {
}, },
}, },
Data: map[string][]byte{ Data: map[string][]byte{
"pinniped-storage-data": []byte(`{"request":{"id":"abcd-1","requestedAt":"0001-01-01T00:00:00Z","client":{"id":"pinny","redirect_uris":null,"grant_types":null,"response_types":null,"scopes":null,"audience":null,"public":true,"jwks_uri":"where","jwks":null,"token_endpoint_auth_method":"something","request_uris":null,"request_object_signing_alg":"","token_endpoint_auth_signing_alg":""},"scopes":null,"grantedScopes":null,"form":{"key":["val"]},"session":{"Claims":null,"Headers":null,"ExpiresAt":null,"Username":"snorlax","Subject":"panda"},"requestedAudience":null,"grantedAudience":null},"version":"1"}`), "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":{"providerUID":"fake-provider-uid","providerName":"fake-provider-name","providerType":"fake-provider-type","oidc":{"upstreamRefreshToken":"fake-upstream-refresh-token"}}},"requestedAudience":null,"grantedAudience":null},"version":"2"}`),
"pinniped-storage-version": []byte("1"), "pinniped-storage-version": []byte("1"),
}, },
Type: "storage.pinniped.dev/refresh-token", Type: "storage.pinniped.dev/refresh-token",
@ -84,16 +85,10 @@ func TestRefreshTokenStorage(t *testing.T) {
TokenEndpointAuthSigningAlgorithm: "", TokenEndpointAuthSigningAlgorithm: "",
}, },
}, },
RequestedScope: nil, RequestedScope: nil,
GrantedScope: nil, GrantedScope: nil,
Form: url.Values{"key": []string{"val"}}, Form: url.Values{"key": []string{"val"}},
Session: &openid.DefaultSession{ Session: testutil.NewFakePinnipedSession(),
Claims: nil,
Headers: nil,
ExpiresAt: nil,
Username: "snorlax",
Subject: "panda",
},
RequestedAudience: nil, RequestedAudience: nil,
GrantedAudience: nil, GrantedAudience: nil,
} }
@ -107,6 +102,7 @@ func TestRefreshTokenStorage(t *testing.T) {
err = storage.DeleteRefreshTokenSession(ctx, "fancy-signature") err = storage.DeleteRefreshTokenSession(ctx, "fancy-signature")
require.NoError(t, err) require.NoError(t, err)
testutil.LogActualJSONFromCreateAction(t, client, 0) // makes it easier to update expected values when needed
require.Equal(t, wantActions, client.Actions()) require.Equal(t, wantActions, client.Actions())
} }
@ -125,7 +121,7 @@ func TestRefreshTokenStorageRevocation(t *testing.T) {
}, },
}, },
Data: map[string][]byte{ Data: map[string][]byte{
"pinniped-storage-data": []byte(`{"request":{"id":"abcd-1","requestedAt":"0001-01-01T00:00:00Z","client":{"id":"pinny","redirect_uris":null,"grant_types":null,"response_types":null,"scopes":null,"audience":null,"public":true,"jwks_uri":"where","jwks":null,"token_endpoint_auth_method":"something","request_uris":null,"request_object_signing_alg":"","token_endpoint_auth_signing_alg":""},"scopes":null,"grantedScopes":null,"form":{"key":["val"]},"session":{"Claims":null,"Headers":null,"ExpiresAt":null,"Username":"snorlax","Subject":"panda"},"requestedAudience":null,"grantedAudience":null},"version":"1"}`), "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":{"providerUID":"fake-provider-uid","providerName":"fake-provider-name","providerType":"fake-provider-type","oidc":{"upstreamRefreshToken":"fake-upstream-refresh-token"}}},"requestedAudience":null,"grantedAudience":null},"version":"2"}`),
"pinniped-storage-version": []byte("1"), "pinniped-storage-version": []byte("1"),
}, },
Type: "storage.pinniped.dev/refresh-token", Type: "storage.pinniped.dev/refresh-token",
@ -151,11 +147,8 @@ func TestRefreshTokenStorageRevocation(t *testing.T) {
TokenEndpointAuthMethod: "something", TokenEndpointAuthMethod: "something",
}, },
}, },
Form: url.Values{"key": []string{"val"}}, Form: url.Values{"key": []string{"val"}},
Session: &openid.DefaultSession{ Session: testutil.NewFakePinnipedSession(),
Username: "snorlax",
Subject: "panda",
},
} }
err := storage.CreateRefreshTokenSession(ctx, "fancy-signature", request) err := storage.CreateRefreshTokenSession(ctx, "fancy-signature", request)
require.NoError(t, err) require.NoError(t, err)
@ -164,6 +157,7 @@ func TestRefreshTokenStorageRevocation(t *testing.T) {
err = storage.RevokeRefreshToken(ctx, "abcd-1") err = storage.RevokeRefreshToken(ctx, "abcd-1")
require.NoError(t, err) require.NoError(t, err)
testutil.LogActualJSONFromCreateAction(t, client, 0) // makes it easier to update expected values when needed
require.Equal(t, wantActions, client.Actions()) require.Equal(t, wantActions, client.Actions())
} }
@ -190,7 +184,7 @@ func TestWrongVersion(t *testing.T) {
}, },
}, },
Data: map[string][]byte{ Data: map[string][]byte{
"pinniped-storage-data": []byte(`{"request":{"id":"abcd-1","requestedAt":"0001-01-01T00:00:00Z","client":{"id":"pinny","redirect_uris":null,"grant_types":null,"response_types":null,"scopes":null,"audience":null,"public":true,"jwks_uri":"where","jwks":null,"token_endpoint_auth_method":"something","request_uris":null,"request_object_signing_alg":"","token_endpoint_auth_signing_alg":""},"scopes":null,"grantedScopes":null,"form":{"key":["val"]},"session":{"Claims":null,"Headers":null,"ExpiresAt":null,"Username":"snorlax","Subject":"panda"},"requestedAudience":null,"grantedAudience":null},"version":"not-the-right-version"}`), "pinniped-storage-data": []byte(`{"request":{"id":"abcd-1"},"version":"not-the-right-version"}`),
"pinniped-storage-version": []byte("1"), "pinniped-storage-version": []byte("1"),
}, },
Type: "storage.pinniped.dev/refresh-token", Type: "storage.pinniped.dev/refresh-token",
@ -200,7 +194,7 @@ func TestWrongVersion(t *testing.T) {
_, err = storage.GetRefreshTokenSession(ctx, "fancy-signature", nil) _, err = storage.GetRefreshTokenSession(ctx, "fancy-signature", nil)
require.EqualError(t, err, "refresh token request data has wrong version: refresh token session for fancy-signature has version not-the-right-version instead of 1") 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 2")
} }
func TestNilSessionRequest(t *testing.T) { func TestNilSessionRequest(t *testing.T) {
@ -218,7 +212,7 @@ func TestNilSessionRequest(t *testing.T) {
}, },
}, },
Data: map[string][]byte{ Data: map[string][]byte{
"pinniped-storage-data": []byte(`{"nonsense-key": "nonsense-value","version":"1"}`), "pinniped-storage-data": []byte(`{"nonsense-key": "nonsense-value","version":"2"}`),
"pinniped-storage-version": []byte("1"), "pinniped-storage-version": []byte("1"),
}, },
Type: "storage.pinniped.dev/refresh-token", Type: "storage.pinniped.dev/refresh-token",
@ -246,10 +240,10 @@ func TestCreateWithWrongRequesterDataTypes(t *testing.T) {
Client: &clientregistry.Client{}, Client: &clientregistry.Client{},
} }
err := storage.CreateRefreshTokenSession(ctx, "signature-doesnt-matter", request) err := storage.CreateRefreshTokenSession(ctx, "signature-doesnt-matter", request)
require.EqualError(t, err, "requester's session must be of type openid.DefaultSession") require.EqualError(t, err, "requester's session must be of type PinnipedSession")
request = &fosite.Request{ request = &fosite.Request{
Session: &openid.DefaultSession{}, Session: &psession.PinnipedSession{},
Client: nil, Client: nil,
} }
err = storage.CreateRefreshTokenSession(ctx, "signature-doesnt-matter", request) err = storage.CreateRefreshTokenSession(ctx, "signature-doesnt-matter", request)
@ -261,7 +255,7 @@ func TestCreateWithoutRequesterID(t *testing.T) {
request := &fosite.Request{ request := &fosite.Request{
ID: "", // empty ID ID: "", // empty ID
Session: &openid.DefaultSession{}, Session: &psession.PinnipedSession{},
Client: &clientregistry.Client{}, Client: &clientregistry.Client{},
} }
err := storage.CreateRefreshTokenSession(ctx, "signature-doesnt-matter", request) err := storage.CreateRefreshTokenSession(ctx, "signature-doesnt-matter", request)

View File

@ -25,6 +25,7 @@ import (
"go.pinniped.dev/internal/oidc/downstreamsession" "go.pinniped.dev/internal/oidc/downstreamsession"
"go.pinniped.dev/internal/oidc/provider" "go.pinniped.dev/internal/oidc/provider"
"go.pinniped.dev/internal/plog" "go.pinniped.dev/internal/plog"
"go.pinniped.dev/internal/psession"
"go.pinniped.dev/pkg/oidcclient/nonce" "go.pinniped.dev/pkg/oidcclient/nonce"
"go.pinniped.dev/pkg/oidcclient/pkce" "go.pinniped.dev/pkg/oidcclient/pkce"
) )
@ -173,12 +174,14 @@ func handleAuthRequestForOIDCUpstreamAuthcodeGrant(
} }
now := time.Now() now := time.Now()
_, err := oauthHelper.NewAuthorizeResponse(r.Context(), authorizeRequester, &openid.DefaultSession{ _, err := oauthHelper.NewAuthorizeResponse(r.Context(), authorizeRequester, &psession.PinnipedSession{
Claims: &jwt.IDTokenClaims{ Fosite: &openid.DefaultSession{
// Temporary claim values to allow `NewAuthorizeResponse` to perform other OIDC validations. Claims: &jwt.IDTokenClaims{
Subject: "none", // Temporary claim values to allow `NewAuthorizeResponse` to perform other OIDC validations.
AuthTime: now, Subject: "none",
RequestedAt: now, AuthTime: now,
RequestedAt: now,
},
}, },
}) })
if err != nil { if err != nil {

View File

@ -18,6 +18,7 @@ import (
"go.pinniped.dev/internal/oidc" "go.pinniped.dev/internal/oidc"
"go.pinniped.dev/internal/oidc/provider" "go.pinniped.dev/internal/oidc/provider"
"go.pinniped.dev/internal/plog" "go.pinniped.dev/internal/plog"
"go.pinniped.dev/internal/psession"
) )
const ( const (
@ -35,19 +36,21 @@ const (
) )
// MakeDownstreamSession creates a downstream OIDC session. // MakeDownstreamSession creates a downstream OIDC session.
func MakeDownstreamSession(subject string, username string, groups []string) *openid.DefaultSession { func MakeDownstreamSession(subject string, username string, groups []string) *psession.PinnipedSession {
now := time.Now().UTC() now := time.Now().UTC()
openIDSession := &openid.DefaultSession{ openIDSession := &psession.PinnipedSession{
Claims: &jwt.IDTokenClaims{ Fosite: &openid.DefaultSession{
Subject: subject, Claims: &jwt.IDTokenClaims{
RequestedAt: now, Subject: subject,
AuthTime: now, RequestedAt: now,
AuthTime: now,
},
}, },
} }
if groups == nil { if groups == nil {
groups = []string{} groups = []string{}
} }
openIDSession.Claims.Extra = map[string]interface{}{ openIDSession.IDTokenClaims().Extra = map[string]interface{}{
oidc.DownstreamUsernameClaim: username, oidc.DownstreamUsernameClaim: username,
oidc.DownstreamGroupsClaim: groups, oidc.DownstreamGroupsClaim: groups,
} }

View File

@ -1,4 +1,4 @@
// Copyright 2020 the Pinniped contributors. All Rights Reserved. // Copyright 2020-2021 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
// Package token provides a handler for the OIDC token endpoint. // Package token provides a handler for the OIDC token endpoint.
@ -8,19 +8,19 @@ import (
"net/http" "net/http"
"github.com/ory/fosite" "github.com/ory/fosite"
"github.com/ory/fosite/handler/openid"
"go.pinniped.dev/internal/httputil/httperr" "go.pinniped.dev/internal/httputil/httperr"
"go.pinniped.dev/internal/oidc" "go.pinniped.dev/internal/oidc"
"go.pinniped.dev/internal/plog" "go.pinniped.dev/internal/plog"
"go.pinniped.dev/internal/psession"
) )
func NewHandler( func NewHandler(
oauthHelper fosite.OAuth2Provider, oauthHelper fosite.OAuth2Provider,
) http.Handler { ) http.Handler {
return httperr.HandlerFunc(func(w http.ResponseWriter, r *http.Request) error { return httperr.HandlerFunc(func(w http.ResponseWriter, r *http.Request) error {
var session openid.DefaultSession session := psession.NewPinnipedSession()
accessRequest, err := oauthHelper.NewAccessRequest(r.Context(), r, &session) accessRequest, err := oauthHelper.NewAccessRequest(r.Context(), r, session)
if err != nil { if err != nil {
plog.Info("token request error", oidc.FositeErrorForLog(err)...) plog.Info("token request error", oidc.FositeErrorForLog(err)...)
oauthHelper.WriteAccessError(w, accessRequest, err) oauthHelper.WriteAccessError(w, accessRequest, err)

View File

@ -46,6 +46,7 @@ import (
"go.pinniped.dev/internal/here" "go.pinniped.dev/internal/here"
"go.pinniped.dev/internal/oidc" "go.pinniped.dev/internal/oidc"
"go.pinniped.dev/internal/oidc/jwks" "go.pinniped.dev/internal/oidc/jwks"
"go.pinniped.dev/internal/psession"
"go.pinniped.dev/internal/testutil" "go.pinniped.dev/internal/testutil"
"go.pinniped.dev/internal/testutil/oidctestutil" "go.pinniped.dev/internal/testutil/oidctestutil"
) )
@ -58,7 +59,6 @@ const (
goodNonce = "some-nonce-value-with-enough-bytes-to-exceed-min-allowed" goodNonce = "some-nonce-value-with-enough-bytes-to-exceed-min-allowed"
goodSubject = "https://issuer?sub=some-subject" goodSubject = "https://issuer?sub=some-subject"
goodUsername = "some-username" goodUsername = "some-username"
goodGroups = "group1,groups2"
hmacSecret = "this needs to be at least 32 characters to meet entropy requirements" hmacSecret = "this needs to be at least 32 characters to meet entropy requirements"
@ -72,6 +72,8 @@ const (
var ( var (
goodAuthTime = time.Date(1, 2, 3, 4, 5, 6, 7, time.UTC) goodAuthTime = time.Date(1, 2, 3, 4, 5, 6, 7, time.UTC)
goodRequestedAtTime = time.Date(7, 6, 5, 4, 3, 2, 1, time.UTC) goodRequestedAtTime = time.Date(7, 6, 5, 4, 3, 2, 1, time.UTC)
goodGroups = []string{"group1", "groups2"}
expectedGoodGroups = []interface{}{"group1", "groups2"}
hmacSecretFunc = func() []byte { hmacSecretFunc = func() []byte {
return []byte(hmacSecret) return []byte(hmacSecret)
@ -813,7 +815,7 @@ func TestTokenExchange(t *testing.T) {
require.Equal(t, goodSubject, tokenClaims["sub"]) require.Equal(t, goodSubject, tokenClaims["sub"])
require.Equal(t, goodIssuer, tokenClaims["iss"]) require.Equal(t, goodIssuer, tokenClaims["iss"])
require.Equal(t, goodUsername, tokenClaims["username"]) require.Equal(t, goodUsername, tokenClaims["username"])
require.Equal(t, goodGroups, tokenClaims["groups"]) require.Equal(t, expectedGoodGroups, tokenClaims["groups"])
// Also assert that some are the same as the original downstream ID token. // Also assert that some are the same as the original downstream ID token.
requireClaimsAreEqual(t, "iss", claimsOfFirstIDToken, tokenClaims) // issuer requireClaimsAreEqual(t, "iss", claimsOfFirstIDToken, tokenClaims) // issuer
@ -1358,18 +1360,25 @@ func makeOauthHelperWithNilPrivateJWTSigningKey(
func simulateAuthEndpointHavingAlreadyRun(t *testing.T, authRequest *http.Request, oauthHelper fosite.OAuth2Provider) fosite.AuthorizeResponder { func simulateAuthEndpointHavingAlreadyRun(t *testing.T, authRequest *http.Request, oauthHelper fosite.OAuth2Provider) fosite.AuthorizeResponder {
// We only set the fields in the session that Fosite wants us to set. // We only set the fields in the session that Fosite wants us to set.
ctx := context.Background() ctx := context.Background()
session := &openid.DefaultSession{ session := &psession.PinnipedSession{
Claims: &jwt.IDTokenClaims{ Fosite: &openid.DefaultSession{
Subject: goodSubject, Claims: &jwt.IDTokenClaims{
RequestedAt: goodRequestedAtTime, Subject: goodSubject,
AuthTime: goodAuthTime, RequestedAt: goodRequestedAtTime,
Extra: map[string]interface{}{ AuthTime: goodAuthTime,
oidc.DownstreamUsernameClaim: goodUsername, Extra: map[string]interface{}{
oidc.DownstreamGroupsClaim: goodGroups, oidc.DownstreamUsernameClaim: goodUsername,
oidc.DownstreamGroupsClaim: goodGroups,
},
},
Subject: "", // not used, note that callback_handler.go does not set this
Username: "", // not used, note that callback_handler.go does not set this
},
Custom: &psession.PinnipedSessionData{
OIDC: &psession.OIDCSessionData{
UpstreamRefreshToken: "starting-fake-refresh-token",
}, },
}, },
Subject: "", // not used, note that callback_handler.go does not set this
Username: "", // not used, note that callback_handler.go does not set this
} }
authRequester, err := oauthHelper.NewAuthorizeRequest(ctx, authRequest) authRequester, err := oauthHelper.NewAuthorizeRequest(ctx, authRequest)
require.NoError(t, err) require.NoError(t, err)
@ -1588,19 +1597,19 @@ func requireValidStoredRequest(
require.Equal(t, wantRequestForm, request.GetRequestForm()) // Fosite stores access token request without form require.Equal(t, wantRequestForm, request.GetRequestForm()) // Fosite stores access token request without form
// Cast session to the type we think it should be. // Cast session to the type we think it should be.
session, ok := request.GetSession().(*openid.DefaultSession) session, ok := request.GetSession().(*psession.PinnipedSession)
require.Truef(t, ok, "could not cast %T to %T", request.GetSession(), &openid.DefaultSession{}) require.Truef(t, ok, "could not cast %T to %T", request.GetSession(), &psession.PinnipedSession{})
// Assert that the session claims are what we think they should be, but only if we are doing OIDC. // Assert that the session claims are what we think they should be, but only if we are doing OIDC.
if contains(wantGrantedScopes, "openid") { if contains(wantGrantedScopes, "openid") {
claims := session.Claims claims := session.Fosite.Claims
require.Empty(t, claims.JTI) // When claims.JTI is empty, Fosite will generate a UUID for this field. require.Empty(t, claims.JTI) // When claims.JTI is empty, Fosite will generate a UUID for this field.
require.Equal(t, goodSubject, claims.Subject) require.Equal(t, goodSubject, claims.Subject)
// Our custom claims from the authorize endpoint should still be set. // Our custom claims from the authorize endpoint should still be set.
require.Equal(t, map[string]interface{}{ require.Equal(t, map[string]interface{}{
"username": goodUsername, "username": goodUsername,
"groups": goodGroups, "groups": expectedGoodGroups,
}, claims.Extra) }, claims.Extra)
// We are in charge of setting these fields. For the purpose of testing, we ensure that the // We are in charge of setting these fields. For the purpose of testing, we ensure that the
@ -1610,7 +1619,7 @@ func requireValidStoredRequest(
// These fields will all be given good defaults by fosite at runtime and we only need to use them // These fields will all be given good defaults by fosite at runtime and we only need to use them
// if we want to override the default behaviors. We currently don't need to override these defaults, // if we want to override the default behaviors. We currently don't need to override these defaults,
// so they do not end up being stored. Fosite sets its defaults at runtime in openid.DefaultSession's // so they do not end up being stored. Fosite sets its defaults at runtime in openid.DefaultStrategy's
// GenerateIDToken() method. // GenerateIDToken() method.
require.Empty(t, claims.Issuer) require.Empty(t, claims.Issuer)
require.Empty(t, claims.Audience) require.Empty(t, claims.Audience)
@ -1630,11 +1639,11 @@ func requireValidStoredRequest(
} }
// Assert that the session headers are what we think they should be. // Assert that the session headers are what we think they should be.
headers := session.Headers headers := session.Fosite.Headers
require.Empty(t, headers) require.Empty(t, headers)
// Assert that the token expirations are what we think they should be. // Assert that the token expirations are what we think they should be.
authCodeExpiresAt, ok := session.ExpiresAt[fosite.AuthorizeCode] authCodeExpiresAt, ok := session.Fosite.ExpiresAt[fosite.AuthorizeCode]
require.True(t, ok, "expected session to hold expiration time for auth code") require.True(t, ok, "expected session to hold expiration time for auth code")
testutil.RequireTimeInDelta( testutil.RequireTimeInDelta(
t, t,
@ -1644,7 +1653,7 @@ func requireValidStoredRequest(
) )
// OpenID Connect sessions do not store access token expiration information. // OpenID Connect sessions do not store access token expiration information.
accessTokenExpiresAt, ok := session.ExpiresAt[fosite.AccessToken] accessTokenExpiresAt, ok := session.Fosite.ExpiresAt[fosite.AccessToken]
if wantAccessTokenExpiresAt { if wantAccessTokenExpiresAt {
require.True(t, ok, "expected session to hold expiration time for access token") require.True(t, ok, "expected session to hold expiration time for access token")
testutil.RequireTimeInDelta( testutil.RequireTimeInDelta(
@ -1658,8 +1667,8 @@ func requireValidStoredRequest(
} }
// We don't use these, so they should be empty. // We don't use these, so they should be empty.
require.Empty(t, session.Username) require.Empty(t, session.Fosite.Username)
require.Empty(t, session.Subject) require.Empty(t, session.Fosite.Subject)
} }
func requireGarbageCollectTimeInDelta(t *testing.T, tokenString string, typeLabel string, secrets v1.SecretInterface, wantExpirationTime time.Time, deltaTime time.Duration) { func requireGarbageCollectTimeInDelta(t *testing.T, tokenString string, typeLabel string, secrets v1.SecretInterface, wantExpirationTime time.Time, deltaTime time.Duration) {
@ -1709,7 +1718,7 @@ func requireValidIDToken(
IssuedAt int64 `json:"iat"` IssuedAt int64 `json:"iat"`
RequestedAt int64 `json:"rat"` RequestedAt int64 `json:"rat"`
AuthTime int64 `json:"auth_time"` AuthTime int64 `json:"auth_time"`
Groups string `json:"groups"` Groups []string `json:"groups"`
Username string `json:"username"` Username string `json:"username"`
} }

View File

@ -0,0 +1,99 @@
// Copyright 2021 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
package psession
import (
"time"
"github.com/mohae/deepcopy"
"github.com/ory/fosite"
"github.com/ory/fosite/handler/openid"
"github.com/ory/fosite/token/jwt"
)
// PinnipedSession is a session container which includes the fosite standard stuff plus custom Pinniped stuff.
type PinnipedSession struct {
// Delegate most things to the standard fosite OpenID JWT session.
Fosite *openid.DefaultSession `json:"fosite,omitempty"`
// Custom Pinniped extensions to the session data.
Custom *PinnipedSessionData `json:"custom,omitempty"`
}
var _ openid.Session = &PinnipedSession{}
// PinnipedSessionData is the custom session data needed by Pinniped. It should be treated as a union type,
// where the value of ProviderType decides which other fields to use.
type PinnipedSessionData struct {
// The Kubernetes resource UID of the identity provider CRD for the upstream IDP used to start this session.
// This should be validated again upon downstream refresh to make sure that we are not refreshing against
// a different identity provider CRD which just happens to have the same name.
// This implies that when a user deletes an identity provider CRD, then the sessions that were started
// using that identity provider will not be able to perform any more downstream refreshes.
ProviderUID string `json:"providerUID"`
// The Kubernetes resource name of the identity provider CRD for the upstream IDP used to start this session.
// Used during a downstream refresh to decide which upstream to refresh.
// Also used to decide which of the pointer types below should be used.
ProviderName string `json:"providerName"`
// The type of the identity provider for the upstream IDP used to start this session.
// Used during a downstream refresh to decide which upstream to refresh.
ProviderType string `json:"providerType"`
// Only used when ProviderType == "oidc".
OIDC *OIDCSessionData `json:"oidc,omitempty"`
}
// OIDCSessionData is the additional data needed by Pinniped when the upstream IDP is an OIDC provider.
type OIDCSessionData struct {
UpstreamRefreshToken string `json:"upstreamRefreshToken"`
}
// NewPinnipedSession returns a new empty session.
func NewPinnipedSession() *PinnipedSession {
return &PinnipedSession{
Fosite: &openid.DefaultSession{
Claims: &jwt.IDTokenClaims{},
Headers: &jwt.Headers{},
},
Custom: &PinnipedSessionData{},
}
}
func (s *PinnipedSession) Clone() fosite.Session {
// Implementation copied from openid.DefaultSession's clone method.
if s == nil {
return nil
}
return deepcopy.Copy(s).(fosite.Session)
}
func (s *PinnipedSession) SetExpiresAt(key fosite.TokenType, exp time.Time) {
s.Fosite.SetExpiresAt(key, exp)
}
func (s *PinnipedSession) GetExpiresAt(key fosite.TokenType) time.Time {
return s.Fosite.GetExpiresAt(key)
}
func (s *PinnipedSession) GetUsername() string {
return s.Fosite.GetUsername()
}
func (s *PinnipedSession) SetSubject(subject string) {
s.Fosite.SetSubject(subject)
}
func (s *PinnipedSession) GetSubject() string {
return s.Fosite.GetSubject()
}
func (s *PinnipedSession) IDTokenHeaders() *jwt.Headers {
return s.Fosite.IDTokenHeaders()
}
func (s *PinnipedSession) IDTokenClaims() *jwt.IDTokenClaims {
return s.Fosite.IDTokenClaims()
}

View File

@ -16,7 +16,6 @@ import (
coreosoidc "github.com/coreos/go-oidc/v3/oidc" coreosoidc "github.com/coreos/go-oidc/v3/oidc"
"github.com/ory/fosite" "github.com/ory/fosite"
"github.com/ory/fosite/handler/openid"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"golang.org/x/oauth2" "golang.org/x/oauth2"
"gopkg.in/square/go-jose.v2" "gopkg.in/square/go-jose.v2"
@ -31,6 +30,7 @@ import (
pkce2 "go.pinniped.dev/internal/fositestorage/pkce" pkce2 "go.pinniped.dev/internal/fositestorage/pkce"
"go.pinniped.dev/internal/fositestoragei" "go.pinniped.dev/internal/fositestoragei"
"go.pinniped.dev/internal/oidc/provider" "go.pinniped.dev/internal/oidc/provider"
"go.pinniped.dev/internal/psession"
"go.pinniped.dev/internal/testutil" "go.pinniped.dev/internal/testutil"
"go.pinniped.dev/pkg/oidcclient/nonce" "go.pinniped.dev/pkg/oidcclient/nonce"
"go.pinniped.dev/pkg/oidcclient/oidctypes" "go.pinniped.dev/pkg/oidcclient/oidctypes"
@ -563,7 +563,7 @@ func validateAuthcodeStorage(
wantDownstreamRequestedScopes []string, wantDownstreamRequestedScopes []string,
wantDownstreamClientID string, wantDownstreamClientID string,
wantDownstreamRedirectURI string, wantDownstreamRedirectURI string,
) (*fosite.Request, *openid.DefaultSession) { ) (*fosite.Request, *psession.PinnipedSession) {
t.Helper() t.Helper()
const ( const (
@ -591,16 +591,16 @@ func validateAuthcodeStorage(
testutil.RequireTimeInDelta(t, time.Now(), storedRequestFromAuthcode.RequestedAt, timeComparisonFudgeFactor) testutil.RequireTimeInDelta(t, time.Now(), storedRequestFromAuthcode.RequestedAt, timeComparisonFudgeFactor)
// We're not using these fields yet, so confirm that we did not set them (for now). // We're not using these fields yet, so confirm that we did not set them (for now).
require.Empty(t, storedSessionFromAuthcode.Subject) require.Empty(t, storedSessionFromAuthcode.Fosite.Subject)
require.Empty(t, storedSessionFromAuthcode.Username) require.Empty(t, storedSessionFromAuthcode.Fosite.Username)
require.Empty(t, storedSessionFromAuthcode.Headers) require.Empty(t, storedSessionFromAuthcode.Fosite.Headers)
// The authcode that we are issuing should be good for the length of time that we declare in the fosite config. // The authcode that we are issuing should be good for the length of time that we declare in the fosite config.
testutil.RequireTimeInDelta(t, time.Now().Add(authCodeExpirationSeconds*time.Second), storedSessionFromAuthcode.ExpiresAt[fosite.AuthorizeCode], timeComparisonFudgeFactor) testutil.RequireTimeInDelta(t, time.Now().Add(authCodeExpirationSeconds*time.Second), storedSessionFromAuthcode.Fosite.ExpiresAt[fosite.AuthorizeCode], timeComparisonFudgeFactor)
require.Len(t, storedSessionFromAuthcode.ExpiresAt, 1) require.Len(t, storedSessionFromAuthcode.Fosite.ExpiresAt, 1)
// Now confirm the ID token claims. // Now confirm the ID token claims.
actualClaims := storedSessionFromAuthcode.Claims actualClaims := storedSessionFromAuthcode.Fosite.Claims
// Check the user's identity, which are put into the downstream ID token's subject, username and groups claims. // Check the user's identity, which are put into the downstream ID token's subject, username and groups claims.
require.Equal(t, wantDownstreamIDTokenSubject, actualClaims.Subject) require.Equal(t, wantDownstreamIDTokenSubject, actualClaims.Subject)
@ -642,7 +642,7 @@ func validatePKCEStorage(
oauthStore fositestoragei.AllFositeStorage, oauthStore fositestoragei.AllFositeStorage,
storeKey string, storeKey string,
storedRequestFromAuthcode *fosite.Request, storedRequestFromAuthcode *fosite.Request,
storedSessionFromAuthcode *openid.DefaultSession, storedSessionFromAuthcode *psession.PinnipedSession,
wantDownstreamPKCEChallenge, wantDownstreamPKCEChallengeMethod string, wantDownstreamPKCEChallenge, wantDownstreamPKCEChallengeMethod string,
) { ) {
t.Helper() t.Helper()
@ -667,7 +667,7 @@ func validateIDSessionStorage(
oauthStore fositestoragei.AllFositeStorage, oauthStore fositestoragei.AllFositeStorage,
storeKey string, storeKey string,
storedRequestFromAuthcode *fosite.Request, storedRequestFromAuthcode *fosite.Request,
storedSessionFromAuthcode *openid.DefaultSession, storedSessionFromAuthcode *psession.PinnipedSession,
wantDownstreamNonce string, wantDownstreamNonce string,
) { ) {
t.Helper() t.Helper()
@ -686,13 +686,13 @@ func validateIDSessionStorage(
require.Equal(t, wantDownstreamNonce, storedRequestFromIDSession.Form.Get("nonce")) require.Equal(t, wantDownstreamNonce, storedRequestFromIDSession.Form.Get("nonce"))
} }
func castStoredAuthorizeRequest(t *testing.T, storedAuthorizeRequest fosite.Requester) (*fosite.Request, *openid.DefaultSession) { func castStoredAuthorizeRequest(t *testing.T, storedAuthorizeRequest fosite.Requester) (*fosite.Request, *psession.PinnipedSession) {
t.Helper() t.Helper()
storedRequest, ok := storedAuthorizeRequest.(*fosite.Request) storedRequest, ok := storedAuthorizeRequest.(*fosite.Request)
require.Truef(t, ok, "could not cast %T to %T", storedAuthorizeRequest, &fosite.Request{}) require.Truef(t, ok, "could not cast %T to %T", storedAuthorizeRequest, &fosite.Request{})
storedSession, ok := storedAuthorizeRequest.GetSession().(*openid.DefaultSession) storedSession, ok := storedAuthorizeRequest.GetSession().(*psession.PinnipedSession)
require.Truef(t, ok, "could not cast %T to %T", storedAuthorizeRequest.GetSession(), &openid.DefaultSession{}) require.Truef(t, ok, "could not cast %T to %T", storedAuthorizeRequest.GetSession(), &psession.PinnipedSession{})
return storedRequest, storedSession return storedRequest, storedSession
} }

View File

@ -0,0 +1,43 @@
// Copyright 2021 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
package testutil
import (
"testing"
"github.com/ory/fosite/handler/openid"
v1 "k8s.io/api/core/v1"
"k8s.io/client-go/kubernetes/fake"
testing2 "k8s.io/client-go/testing"
"go.pinniped.dev/internal/psession"
)
func NewFakePinnipedSession() *psession.PinnipedSession {
return &psession.PinnipedSession{
Fosite: &openid.DefaultSession{
Claims: nil,
Headers: nil,
ExpiresAt: nil,
Username: "snorlax",
Subject: "panda",
},
Custom: &psession.PinnipedSessionData{
ProviderUID: "fake-provider-uid",
ProviderType: "fake-provider-type",
ProviderName: "fake-provider-name",
OIDC: &psession.OIDCSessionData{
UpstreamRefreshToken: "fake-upstream-refresh-token",
},
},
}
}
func LogActualJSONFromCreateAction(t *testing.T, client *fake.Clientset, actionIndex int) {
t.Log("actual value of CreateAction secret data", string(client.Actions()[actionIndex].(testing2.CreateActionImpl).Object.(*v1.Secret).Data["pinniped-storage-data"]))
}
func LogActualJSONFromUpdateAction(t *testing.T, client *fake.Clientset, actionIndex int) {
t.Log("actual value of UpdateAction secret data", string(client.Actions()[actionIndex].(testing2.UpdateActionImpl).Object.(*v1.Secret).Data["pinniped-storage-data"]))
}