2021-06-15 16:27:30 +00:00
// Copyright 2020-2021 the Pinniped contributors. All Rights Reserved.
2020-12-04 22:31:06 +00:00
// SPDX-License-Identifier: Apache-2.0
package refreshtoken
import (
"context"
"net/url"
"testing"
"time"
"github.com/ory/fosite"
2021-11-10 23:34:19 +00:00
"github.com/ory/fosite/handler/openid"
2020-12-04 22:31:06 +00:00
"github.com/pkg/errors"
"github.com/stretchr/testify/require"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/client-go/kubernetes/fake"
2020-12-10 22:47:58 +00:00
corev1client "k8s.io/client-go/kubernetes/typed/core/v1"
2020-12-04 22:31:06 +00:00
coretesting "k8s.io/client-go/testing"
2021-12-10 22:22:36 +00:00
clocktesting "k8s.io/utils/clock/testing"
2021-06-15 16:27:30 +00:00
"go.pinniped.dev/internal/oidc/clientregistry"
2021-10-06 22:28:13 +00:00
"go.pinniped.dev/internal/psession"
"go.pinniped.dev/internal/testutil"
2020-12-04 22:31:06 +00:00
)
const namespace = "test-ns"
var secretsGVR = schema . GroupVersionResource {
Group : "" ,
Version : "v1" ,
Resource : "secrets" ,
}
2020-12-10 22:47:58 +00:00
var fakeNow = time . Date ( 2030 , time . January , 1 , 0 , 0 , 0 , 0 , time . UTC )
var lifetime = time . Minute * 10
var fakeNowPlusLifetimeAsString = metav1 . Time { Time : fakeNow . Add ( lifetime ) } . Format ( time . RFC3339 )
2020-12-04 22:31:06 +00:00
func TestRefreshTokenStorage ( t * testing . T ) {
wantActions := [ ] coretesting . Action {
coretesting . NewCreateAction ( secretsGVR , namespace , & corev1 . Secret {
ObjectMeta : metav1 . ObjectMeta {
Name : "pinniped-storage-refresh-token-pwu5zs7lekbhnln2w4" ,
ResourceVersion : "" ,
Labels : map [ string ] string {
2020-12-04 22:39:11 +00:00
"storage.pinniped.dev/type" : "refresh-token" ,
2020-12-04 22:31:06 +00:00
"storage.pinniped.dev/request-id" : "abcd-1" ,
} ,
2020-12-10 22:47:58 +00:00
Annotations : map [ string ] string {
"storage.pinniped.dev/garbage-collect-after" : fakeNowPlusLifetimeAsString ,
} ,
2020-12-04 22:31:06 +00:00
} ,
Data : map [ string ] [ ] byte {
2021-10-06 22:28:13 +00:00
"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"} ` ) ,
2020-12-04 22:31:06 +00:00
"pinniped-storage-version" : [ ] byte ( "1" ) ,
} ,
Type : "storage.pinniped.dev/refresh-token" ,
} ) ,
coretesting . NewGetAction ( secretsGVR , namespace , "pinniped-storage-refresh-token-pwu5zs7lekbhnln2w4" ) ,
coretesting . NewDeleteAction ( secretsGVR , namespace , "pinniped-storage-refresh-token-pwu5zs7lekbhnln2w4" ) ,
}
2020-12-10 22:47:58 +00:00
ctx , client , _ , storage := makeTestSubject ( )
2020-12-04 22:31:06 +00:00
request := & fosite . Request {
ID : "abcd-1" ,
RequestedAt : time . Time { } ,
2021-06-15 16:27:30 +00:00
Client : & clientregistry . Client {
DefaultOpenIDConnectClient : fosite . DefaultOpenIDConnectClient {
DefaultClient : & fosite . DefaultClient {
ID : "pinny" ,
Secret : nil ,
RedirectURIs : nil ,
GrantTypes : nil ,
ResponseTypes : nil ,
Scopes : nil ,
Audience : nil ,
Public : true ,
} ,
JSONWebKeysURI : "where" ,
JSONWebKeys : nil ,
TokenEndpointAuthMethod : "something" ,
RequestURIs : nil ,
RequestObjectSigningAlgorithm : "" ,
TokenEndpointAuthSigningAlgorithm : "" ,
2020-12-04 22:31:06 +00:00
} ,
} ,
2021-10-06 22:28:13 +00:00
RequestedScope : nil ,
GrantedScope : nil ,
Form : url . Values { "key" : [ ] string { "val" } } ,
Session : testutil . NewFakePinnipedSession ( ) ,
2020-12-04 22:31:06 +00:00
RequestedAudience : nil ,
GrantedAudience : nil ,
}
err := storage . CreateRefreshTokenSession ( ctx , "fancy-signature" , request )
require . NoError ( t , err )
newRequest , err := storage . GetRefreshTokenSession ( ctx , "fancy-signature" , nil )
require . NoError ( t , err )
require . Equal ( t , request , newRequest )
err = storage . DeleteRefreshTokenSession ( ctx , "fancy-signature" )
require . NoError ( t , err )
2021-10-06 22:28:13 +00:00
testutil . LogActualJSONFromCreateAction ( t , client , 0 ) // makes it easier to update expected values when needed
2020-12-04 22:31:06 +00:00
require . Equal ( t , wantActions , client . Actions ( ) )
}
func TestRefreshTokenStorageRevocation ( t * testing . T ) {
wantActions := [ ] coretesting . Action {
coretesting . NewCreateAction ( secretsGVR , namespace , & corev1 . Secret {
ObjectMeta : metav1 . ObjectMeta {
Name : "pinniped-storage-refresh-token-pwu5zs7lekbhnln2w4" ,
ResourceVersion : "" ,
Labels : map [ string ] string {
2020-12-04 22:39:11 +00:00
"storage.pinniped.dev/type" : "refresh-token" ,
2020-12-04 22:31:06 +00:00
"storage.pinniped.dev/request-id" : "abcd-1" ,
} ,
2020-12-10 22:47:58 +00:00
Annotations : map [ string ] string {
"storage.pinniped.dev/garbage-collect-after" : fakeNowPlusLifetimeAsString ,
} ,
2020-12-04 22:31:06 +00:00
} ,
Data : map [ string ] [ ] byte {
2021-10-06 22:28:13 +00:00
"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"} ` ) ,
2020-12-04 22:31:06 +00:00
"pinniped-storage-version" : [ ] byte ( "1" ) ,
} ,
Type : "storage.pinniped.dev/refresh-token" ,
} ) ,
coretesting . NewListAction ( secretsGVR , schema . GroupVersionKind { Group : "" , Version : "v1" , Kind : "Secret" } , namespace , metav1 . ListOptions {
2020-12-04 22:39:11 +00:00
LabelSelector : "storage.pinniped.dev/type=refresh-token,storage.pinniped.dev/request-id=abcd-1" ,
2020-12-04 22:31:06 +00:00
} ) ,
coretesting . NewDeleteAction ( secretsGVR , namespace , "pinniped-storage-refresh-token-pwu5zs7lekbhnln2w4" ) ,
}
2020-12-10 22:47:58 +00:00
ctx , client , _ , storage := makeTestSubject ( )
2020-12-04 22:31:06 +00:00
request := & fosite . Request {
ID : "abcd-1" ,
RequestedAt : time . Time { } ,
2021-06-15 16:27:30 +00:00
Client : & clientregistry . Client {
DefaultOpenIDConnectClient : fosite . DefaultOpenIDConnectClient {
DefaultClient : & fosite . DefaultClient {
ID : "pinny" ,
Public : true ,
} ,
JSONWebKeysURI : "where" ,
TokenEndpointAuthMethod : "something" ,
2020-12-04 22:31:06 +00:00
} ,
} ,
2021-10-06 22:28:13 +00:00
Form : url . Values { "key" : [ ] string { "val" } } ,
Session : testutil . NewFakePinnipedSession ( ) ,
2020-12-04 22:31:06 +00:00
}
err := storage . CreateRefreshTokenSession ( ctx , "fancy-signature" , request )
require . NoError ( t , err )
// Revoke the request ID of the session that we just created
err = storage . RevokeRefreshToken ( ctx , "abcd-1" )
require . NoError ( t , err )
2021-10-06 22:28:13 +00:00
testutil . LogActualJSONFromCreateAction ( t , client , 0 ) // makes it easier to update expected values when needed
2020-12-04 22:31:06 +00:00
require . Equal ( t , wantActions , client . Actions ( ) )
}
func TestGetNotFound ( t * testing . T ) {
2020-12-10 22:47:58 +00:00
ctx , _ , _ , storage := makeTestSubject ( )
2020-12-04 22:31:06 +00:00
_ , notFoundErr := storage . GetRefreshTokenSession ( ctx , "non-existent-signature" , nil )
require . EqualError ( t , notFoundErr , "not_found" )
require . True ( t , errors . Is ( notFoundErr , fosite . ErrNotFound ) )
}
func TestWrongVersion ( t * testing . T ) {
2020-12-10 22:47:58 +00:00
ctx , _ , secrets , storage := makeTestSubject ( )
2020-12-04 22:31:06 +00:00
secret := & corev1 . Secret {
ObjectMeta : metav1 . ObjectMeta {
Name : "pinniped-storage-refresh-token-pwu5zs7lekbhnln2w4" ,
ResourceVersion : "" ,
Labels : map [ string ] string {
2020-12-04 22:39:11 +00:00
"storage.pinniped.dev/type" : "refresh-token" ,
2020-12-04 22:31:06 +00:00
} ,
2020-12-10 22:47:58 +00:00
Annotations : map [ string ] string {
"storage.pinniped.dev/garbage-collect-after" : fakeNowPlusLifetimeAsString ,
} ,
2020-12-04 22:31:06 +00:00
} ,
Data : map [ string ] [ ] byte {
2021-10-06 22:28:13 +00:00
"pinniped-storage-data" : [ ] byte ( ` { "request": { "id":"abcd-1"},"version":"not-the-right-version"} ` ) ,
2020-12-04 22:31:06 +00:00
"pinniped-storage-version" : [ ] byte ( "1" ) ,
} ,
Type : "storage.pinniped.dev/refresh-token" ,
}
_ , err := secrets . Create ( ctx , secret , metav1 . CreateOptions { } )
require . NoError ( t , err )
_ , err = storage . GetRefreshTokenSession ( ctx , "fancy-signature" , nil )
2021-10-06 22:28:13 +00:00
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" )
2020-12-04 22:31:06 +00:00
}
func TestNilSessionRequest ( t * testing . T ) {
2020-12-10 22:47:58 +00:00
ctx , _ , secrets , storage := makeTestSubject ( )
2020-12-04 22:31:06 +00:00
secret := & corev1 . Secret {
ObjectMeta : metav1 . ObjectMeta {
Name : "pinniped-storage-refresh-token-pwu5zs7lekbhnln2w4" ,
ResourceVersion : "" ,
Labels : map [ string ] string {
2020-12-04 22:39:11 +00:00
"storage.pinniped.dev/type" : "refresh-token" ,
2020-12-04 22:31:06 +00:00
} ,
2020-12-10 22:47:58 +00:00
Annotations : map [ string ] string {
"storage.pinniped.dev/garbage-collect-after" : fakeNowPlusLifetimeAsString ,
} ,
2020-12-04 22:31:06 +00:00
} ,
Data : map [ string ] [ ] byte {
2021-10-06 22:28:13 +00:00
"pinniped-storage-data" : [ ] byte ( ` { "nonsense-key": "nonsense-value","version":"2"} ` ) ,
2020-12-04 22:31:06 +00:00
"pinniped-storage-version" : [ ] byte ( "1" ) ,
} ,
Type : "storage.pinniped.dev/refresh-token" ,
}
_ , err := secrets . Create ( ctx , secret , metav1 . CreateOptions { } )
require . NoError ( t , err )
_ , err = storage . GetRefreshTokenSession ( ctx , "fancy-signature" , nil )
require . EqualError ( t , err , "malformed refresh token session for fancy-signature: refresh token request data must be present" )
}
func TestCreateWithNilRequester ( t * testing . T ) {
2020-12-10 22:47:58 +00:00
ctx , _ , _ , storage := makeTestSubject ( )
2020-12-04 22:31:06 +00:00
err := storage . CreateRefreshTokenSession ( ctx , "signature-doesnt-matter" , nil )
require . EqualError ( t , err , "requester must be of type fosite.Request" )
}
func TestCreateWithWrongRequesterDataTypes ( t * testing . T ) {
2020-12-10 22:47:58 +00:00
ctx , _ , _ , storage := makeTestSubject ( )
2020-12-04 22:31:06 +00:00
request := & fosite . Request {
Session : nil ,
2021-06-15 16:27:30 +00:00
Client : & clientregistry . Client { } ,
2020-12-04 22:31:06 +00:00
}
err := storage . CreateRefreshTokenSession ( ctx , "signature-doesnt-matter" , request )
2021-10-06 22:28:13 +00:00
require . EqualError ( t , err , "requester's session must be of type PinnipedSession" )
2020-12-04 22:31:06 +00:00
request = & fosite . Request {
2021-10-06 22:28:13 +00:00
Session : & psession . PinnipedSession { } ,
2020-12-04 22:31:06 +00:00
Client : nil ,
}
err = storage . CreateRefreshTokenSession ( ctx , "signature-doesnt-matter" , request )
2021-06-15 16:27:30 +00:00
require . EqualError ( t , err , "requester's client must be of type clientregistry.Client" )
2020-12-04 22:31:06 +00:00
}
func TestCreateWithoutRequesterID ( t * testing . T ) {
2020-12-10 22:47:58 +00:00
ctx , client , _ , storage := makeTestSubject ( )
2020-12-04 22:31:06 +00:00
request := & fosite . Request {
ID : "" , // empty ID
2021-10-06 22:28:13 +00:00
Session : & psession . PinnipedSession { } ,
2021-06-15 16:27:30 +00:00
Client : & clientregistry . Client { } ,
2020-12-04 22:31:06 +00:00
}
err := storage . CreateRefreshTokenSession ( ctx , "signature-doesnt-matter" , request )
require . NoError ( t , err )
// the blank ID was filled in with an auto-generated ID
require . NotEmpty ( t , request . ID )
require . Len ( t , client . Actions ( ) , 1 )
actualAction := client . Actions ( ) [ 0 ] . ( coretesting . CreateActionImpl )
actualSecret := actualAction . GetObject ( ) . ( * corev1 . Secret )
// The generated secret was labeled with that auto-generated request ID
require . Equal ( t , request . ID , actualSecret . Labels [ "storage.pinniped.dev/request-id" ] )
}
2020-12-10 22:47:58 +00:00
func makeTestSubject ( ) ( context . Context , * fake . Clientset , corev1client . SecretInterface , RevocationStorage ) {
client := fake . NewSimpleClientset ( )
secrets := client . CoreV1 ( ) . Secrets ( namespace )
2021-12-10 22:22:36 +00:00
return context . Background ( ) , client , secrets , New ( secrets , clocktesting . NewFakeClock ( fakeNow ) . Now , lifetime )
2020-12-10 22:47:58 +00:00
}
2021-11-10 23:34:19 +00:00
func TestReadFromSecret ( t * testing . T ) {
tests := [ ] struct {
name string
secret * corev1 . Secret
wantSession * Session
wantErr string
} {
{
name : "happy path" ,
secret : & corev1 . Secret {
ObjectMeta : metav1 . ObjectMeta {
Name : "pinniped-storage-refresh-token-pwu5zs7lekbhnln2w4" ,
ResourceVersion : "" ,
Labels : map [ string ] string {
"storage.pinniped.dev/type" : "refresh-token" ,
} ,
} ,
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": { "providerUID":"fake-provider-uid","providerName":"fake-provider-name","providerType":"fake-provider-type","oidc": { "upstreamRefreshToken":"fake-upstream-refresh-token"}}}},"version":"2","active": true} ` ) ,
"pinniped-storage-version" : [ ] byte ( "1" ) ,
} ,
Type : "storage.pinniped.dev/refresh-token" ,
} ,
wantSession : & Session {
Version : "2" ,
Request : & fosite . Request {
ID : "abcd-1" ,
Client : & clientregistry . Client { } ,
Session : & psession . PinnipedSession {
Fosite : & openid . DefaultSession {
Username : "snorlax" ,
Subject : "panda" ,
} ,
Custom : & psession . CustomSessionData {
ProviderUID : "fake-provider-uid" ,
ProviderName : "fake-provider-name" ,
ProviderType : "fake-provider-type" ,
OIDC : & psession . OIDCSessionData {
UpstreamRefreshToken : "fake-upstream-refresh-token" ,
} ,
} ,
} ,
} ,
} ,
} ,
{
name : "wrong secret type" ,
secret : & corev1 . Secret {
ObjectMeta : metav1 . ObjectMeta {
Name : "pinniped-storage-refresh-token-pwu5zs7lekbhnln2w4" ,
ResourceVersion : "" ,
Labels : map [ string ] string {
"storage.pinniped.dev/type" : "refresh-token" ,
} ,
} ,
Data : map [ string ] [ ] byte {
"pinniped-storage-data" : [ ] byte ( ` { "request": { "id":"abcd-1"},"version":"2","active": true} ` ) ,
"pinniped-storage-version" : [ ] byte ( "1" ) ,
} ,
Type : "storage.pinniped.dev/not-refresh-token" ,
} ,
wantErr : "secret storage data has incorrect type: storage.pinniped.dev/not-refresh-token must equal storage.pinniped.dev/refresh-token" ,
} ,
{
name : "wrong session version" ,
secret : & corev1 . Secret {
ObjectMeta : metav1 . ObjectMeta {
Name : "pinniped-storage-refresh-token-pwu5zs7lekbhnln2w4" ,
ResourceVersion : "" ,
Labels : map [ string ] string {
"storage.pinniped.dev/type" : "refresh-token" ,
} ,
} ,
Data : map [ string ] [ ] byte {
"pinniped-storage-data" : [ ] byte ( ` { "request": { "id":"abcd-1"},"version":"wrong-version-here","active": true} ` ) ,
"pinniped-storage-version" : [ ] byte ( "1" ) ,
} ,
Type : "storage.pinniped.dev/refresh-token" ,
} ,
wantErr : "refresh token request data has wrong version: refresh token session has version wrong-version-here instead of 2" ,
} ,
{
name : "missing request" ,
secret : & corev1 . Secret {
ObjectMeta : metav1 . ObjectMeta {
Name : "pinniped-storage-refresh-token-pwu5zs7lekbhnln2w4" ,
ResourceVersion : "" ,
Labels : map [ string ] string {
"storage.pinniped.dev/type" : "refresh-token" ,
} ,
} ,
Data : map [ string ] [ ] byte {
"pinniped-storage-data" : [ ] byte ( ` { "version":"2","active": true} ` ) ,
"pinniped-storage-version" : [ ] byte ( "1" ) ,
} ,
Type : "storage.pinniped.dev/refresh-token" ,
} ,
wantErr : "malformed refresh token session: refresh token request data must be present" ,
} ,
}
for _ , tt := range tests {
tt := tt
t . Run ( tt . name , func ( t * testing . T ) {
t . Parallel ( )
session , err := ReadFromSecret ( tt . secret )
if tt . wantErr == "" {
require . NoError ( t , err )
require . Equal ( t , tt . wantSession , session )
} else {
require . EqualError ( t , err , tt . wantErr )
require . Nil ( t , session )
}
} )
}
}