2020-10-21 17:54:26 +00:00
// Copyright 2020 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
package filesession
import (
"os"
"testing"
"time"
"github.com/stretchr/testify/require"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2020-11-24 19:38:28 +00:00
"go.pinniped.dev/internal/testutil"
2020-11-17 18:46:54 +00:00
"go.pinniped.dev/pkg/oidcclient"
2020-11-30 23:02:03 +00:00
"go.pinniped.dev/pkg/oidcclient/oidctypes"
2020-10-21 17:54:26 +00:00
)
// validSession should be the same data as `testdata/valid.yaml`.
var validSession = sessionCache {
2020-10-30 20:09:14 +00:00
TypeMeta : metav1 . TypeMeta { APIVersion : "config.supervisor.pinniped.dev/v1alpha1" , Kind : "SessionCache" } ,
2020-10-21 17:54:26 +00:00
Sessions : [ ] sessionEntry {
{
Key : oidcclient . SessionCacheKey {
Issuer : "test-issuer" ,
ClientID : "test-client-id" ,
Scopes : [ ] string { "email" , "offline_access" , "openid" , "profile" } ,
RedirectURI : "http://localhost:0/callback" ,
} ,
CreationTimestamp : metav1 . NewTime ( time . Date ( 2020 , 10 , 20 , 18 , 42 , 7 , 0 , time . UTC ) . Local ( ) ) ,
LastUsedTimestamp : metav1 . NewTime ( time . Date ( 2020 , 10 , 20 , 18 , 45 , 31 , 0 , time . UTC ) . Local ( ) ) ,
2020-11-30 23:02:03 +00:00
Tokens : oidctypes . Token {
AccessToken : & oidctypes . AccessToken {
2020-10-21 17:54:26 +00:00
Token : "test-access-token" ,
Type : "Bearer" ,
Expiry : metav1 . NewTime ( time . Date ( 2020 , 10 , 20 , 19 , 46 , 30 , 0 , time . UTC ) . Local ( ) ) ,
} ,
2020-11-30 23:02:03 +00:00
IDToken : & oidctypes . IDToken {
2020-10-21 17:54:26 +00:00
Token : "test-id-token" ,
Expiry : metav1 . NewTime ( time . Date ( 2020 , 10 , 20 , 19 , 42 , 07 , 0 , time . UTC ) . Local ( ) ) ,
2020-12-04 21:15:33 +00:00
Claims : map [ string ] interface { } {
"foo" : "bar" ,
"nested" : map [ string ] interface { } {
"key1" : "value1" ,
"key2" : "value2" ,
} ,
} ,
2020-10-21 17:54:26 +00:00
} ,
2020-11-30 23:02:03 +00:00
RefreshToken : & oidctypes . RefreshToken {
2020-10-21 17:54:26 +00:00
Token : "test-refresh-token" ,
} ,
} ,
} ,
} ,
}
func TestReadSessionCache ( t * testing . T ) {
t . Parallel ( )
tests := [ ] struct {
name string
path string
want * sessionCache
wantErr string
} {
{
name : "does not exist" ,
path : "./testdata/does-not-exist.yaml" ,
want : & sessionCache {
2020-10-30 20:09:14 +00:00
TypeMeta : metav1 . TypeMeta { APIVersion : "config.supervisor.pinniped.dev/v1alpha1" , Kind : "SessionCache" } ,
2020-10-21 17:54:26 +00:00
Sessions : [ ] sessionEntry { } ,
} ,
} ,
{
name : "other file error" ,
path : "./testdata/" ,
wantErr : "could not read session file: read ./testdata/: is a directory" ,
} ,
{
name : "invalid YAML" ,
path : "./testdata/invalid.yaml" ,
wantErr : "invalid session file: error unmarshaling JSON: while decoding JSON: json: cannot unmarshal string into Go value of type filesession.sessionCache" ,
} ,
{
name : "wrong version" ,
path : "./testdata/wrong-version.yaml" ,
2020-10-30 20:09:14 +00:00
wantErr : ` unsupported session version: v1.TypeMeta { Kind:"NotASessionCache", APIVersion:"config.supervisor.pinniped.dev/v2alpha6"} ` ,
2020-10-21 17:54:26 +00:00
} ,
{
name : "valid" ,
path : "./testdata/valid.yaml" ,
want : & validSession ,
} ,
}
for _ , tt := range tests {
tt := tt
t . Run ( tt . name , func ( t * testing . T ) {
t . Parallel ( )
got , err := readSessionCache ( tt . path )
if tt . wantErr != "" {
require . EqualError ( t , err , tt . wantErr )
require . Nil ( t , got )
return
}
require . NoError ( t , err )
require . NotNil ( t , got )
require . Equal ( t , tt . want , got )
} )
}
}
func TestEmptySessionCache ( t * testing . T ) {
t . Parallel ( )
got := emptySessionCache ( )
2020-10-30 20:09:14 +00:00
require . Equal ( t , metav1 . TypeMeta { APIVersion : "config.supervisor.pinniped.dev/v1alpha1" , Kind : "SessionCache" } , got . TypeMeta )
2020-10-21 17:54:26 +00:00
require . Equal ( t , 0 , len ( got . Sessions ) )
require . Equal ( t , 1 , cap ( got . Sessions ) )
}
func TestWriteTo ( t * testing . T ) {
t . Parallel ( )
t . Run ( "io error" , func ( t * testing . T ) {
t . Parallel ( )
2020-11-24 19:38:28 +00:00
tmp := testutil . TempDir ( t ) + "/sessions.yaml"
2020-10-21 17:54:26 +00:00
require . NoError ( t , os . Mkdir ( tmp , 0700 ) )
err := validSession . writeTo ( tmp )
require . EqualError ( t , err , "open " + tmp + ": is a directory" )
} )
t . Run ( "success" , func ( t * testing . T ) {
t . Parallel ( )
2020-11-24 19:38:28 +00:00
require . NoError ( t , validSession . writeTo ( testutil . TempDir ( t ) + "/sessions.yaml" ) )
2020-10-21 17:54:26 +00:00
} )
}
func TestNormalized ( t * testing . T ) {
t . Parallel ( )
t . Run ( "empty" , func ( t * testing . T ) {
t . Parallel ( )
require . Equal ( t , emptySessionCache ( ) , emptySessionCache ( ) . normalized ( ) )
} )
t . Run ( "nonempty" , func ( t * testing . T ) {
t . Parallel ( )
input := emptySessionCache ( )
now := time . Now ( )
input . Sessions = [ ] sessionEntry {
// ID token is empty, but not nil.
{
LastUsedTimestamp : metav1 . NewTime ( now ) ,
2020-11-30 23:02:03 +00:00
Tokens : oidctypes . Token {
IDToken : & oidctypes . IDToken {
2020-10-21 17:54:26 +00:00
Token : "" ,
Expiry : metav1 . NewTime ( now . Add ( 1 * time . Minute ) ) ,
} ,
} ,
} ,
// ID token is expired.
{
LastUsedTimestamp : metav1 . NewTime ( now ) ,
2020-11-30 23:02:03 +00:00
Tokens : oidctypes . Token {
IDToken : & oidctypes . IDToken {
2020-10-21 17:54:26 +00:00
Token : "test-id-token" ,
Expiry : metav1 . NewTime ( now . Add ( - 1 * time . Minute ) ) ,
} ,
} ,
} ,
// Access token is empty, but not nil.
{
LastUsedTimestamp : metav1 . NewTime ( now ) ,
2020-11-30 23:02:03 +00:00
Tokens : oidctypes . Token {
AccessToken : & oidctypes . AccessToken {
2020-10-21 17:54:26 +00:00
Token : "" ,
Expiry : metav1 . NewTime ( now . Add ( 1 * time . Minute ) ) ,
} ,
} ,
} ,
// Access token is expired.
{
LastUsedTimestamp : metav1 . NewTime ( now ) ,
2020-11-30 23:02:03 +00:00
Tokens : oidctypes . Token {
AccessToken : & oidctypes . AccessToken {
2020-10-21 17:54:26 +00:00
Token : "test-access-token" ,
Expiry : metav1 . NewTime ( now . Add ( - 1 * time . Minute ) ) ,
} ,
} ,
} ,
// Refresh token is empty, but not nil.
{
LastUsedTimestamp : metav1 . NewTime ( now ) ,
2020-11-30 23:02:03 +00:00
Tokens : oidctypes . Token {
RefreshToken : & oidctypes . RefreshToken {
2020-10-21 17:54:26 +00:00
Token : "" ,
} ,
} ,
} ,
// Session has a refresh token but it hasn't been used in >90 days.
{
LastUsedTimestamp : metav1 . NewTime ( now . AddDate ( - 1 , 0 , 0 ) ) ,
2020-11-30 23:02:03 +00:00
Tokens : oidctypes . Token {
RefreshToken : & oidctypes . RefreshToken {
2020-10-21 17:54:26 +00:00
Token : "test-refresh-token" ,
} ,
} ,
} ,
// Two entries that are still valid.
{
CreationTimestamp : metav1 . NewTime ( now . Add ( - 1 * time . Hour ) ) ,
LastUsedTimestamp : metav1 . NewTime ( now ) ,
2020-11-30 23:02:03 +00:00
Tokens : oidctypes . Token {
RefreshToken : & oidctypes . RefreshToken {
2020-10-21 17:54:26 +00:00
Token : "test-refresh-token2" ,
} ,
} ,
} ,
{
CreationTimestamp : metav1 . NewTime ( now . Add ( - 2 * time . Hour ) ) ,
LastUsedTimestamp : metav1 . NewTime ( now ) ,
2020-11-30 23:02:03 +00:00
Tokens : oidctypes . Token {
RefreshToken : & oidctypes . RefreshToken {
2020-10-21 17:54:26 +00:00
Token : "test-refresh-token1" ,
} ,
} ,
} ,
}
// Expect that all but the last two valid session are pruned, and that they're sorted.
require . Equal ( t , & sessionCache {
2020-10-30 20:09:14 +00:00
TypeMeta : metav1 . TypeMeta { APIVersion : "config.supervisor.pinniped.dev/v1alpha1" , Kind : "SessionCache" } ,
2020-10-21 17:54:26 +00:00
Sessions : [ ] sessionEntry {
{
CreationTimestamp : metav1 . NewTime ( now . Add ( - 2 * time . Hour ) ) ,
LastUsedTimestamp : metav1 . NewTime ( now ) ,
2020-11-30 23:02:03 +00:00
Tokens : oidctypes . Token {
RefreshToken : & oidctypes . RefreshToken {
2020-10-21 17:54:26 +00:00
Token : "test-refresh-token1" ,
} ,
} ,
} ,
{
CreationTimestamp : metav1 . NewTime ( now . Add ( - 1 * time . Hour ) ) ,
LastUsedTimestamp : metav1 . NewTime ( now ) ,
2020-11-30 23:02:03 +00:00
Tokens : oidctypes . Token {
RefreshToken : & oidctypes . RefreshToken {
2020-10-21 17:54:26 +00:00
Token : "test-refresh-token2" ,
} ,
} ,
} ,
} ,
} , input . normalized ( ) )
} )
}
func TestLookup ( t * testing . T ) {
t . Parallel ( )
require . Nil ( t , validSession . lookup ( oidcclient . SessionCacheKey { } ) )
require . NotNil ( t , validSession . lookup ( oidcclient . SessionCacheKey {
Issuer : "test-issuer" ,
ClientID : "test-client-id" ,
Scopes : [ ] string { "email" , "offline_access" , "openid" , "profile" } ,
RedirectURI : "http://localhost:0/callback" ,
} ) )
}
func TestInsert ( t * testing . T ) {
t . Parallel ( )
c := emptySessionCache ( )
c . insert ( sessionEntry { } )
require . Len ( t , c . Sessions , 1 )
}