ContainerImage.Pinniped/pkg/oidcclient/filesession/cachefile_test.go

270 lines
7.2 KiB
Go
Raw Permalink Normal View History

2023-08-29 22:03:11 +00:00
// Copyright 2020-2023 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"
"go.pinniped.dev/pkg/oidcclient"
"go.pinniped.dev/pkg/oidcclient/oidctypes"
)
// validSession should be the same data as `testdata/valid.yaml`.
var validSession = sessionCache{
TypeMeta: metav1.TypeMeta{APIVersion: "config.supervisor.pinniped.dev/v1alpha1", Kind: "SessionCache"},
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()),
Tokens: oidctypes.Token{
AccessToken: &oidctypes.AccessToken{
Token: "test-access-token",
Type: "Bearer",
Expiry: metav1.NewTime(time.Date(2020, 10, 20, 19, 46, 30, 0, time.UTC).Local()),
},
IDToken: &oidctypes.IDToken{
Token: "test-id-token",
Expiry: metav1.NewTime(time.Date(2020, 10, 20, 19, 42, 07, 0, time.UTC).Local()),
Claims: map[string]interface{}{
"foo": "bar",
"nested": map[string]interface{}{
"key1": "value1",
"key2": "value2",
},
},
},
RefreshToken: &oidctypes.RefreshToken{
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{
TypeMeta: metav1.TypeMeta{APIVersion: "config.supervisor.pinniped.dev/v1alpha1", Kind: "SessionCache"},
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",
wantErr: `unsupported session version: v1.TypeMeta{Kind:"NotASessionCache", APIVersion:"config.supervisor.pinniped.dev/v2alpha6"}`,
},
{
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()
require.Equal(t, metav1.TypeMeta{APIVersion: "config.supervisor.pinniped.dev/v1alpha1", Kind: "SessionCache"}, got.TypeMeta)
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()
2023-08-29 22:03:11 +00:00
tmp := t.TempDir() + "/sessions.yaml"
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()
2023-08-29 22:03:11 +00:00
require.NoError(t, validSession.writeTo(t.TempDir()+"/sessions.yaml"))
})
}
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),
Tokens: oidctypes.Token{
IDToken: &oidctypes.IDToken{
Token: "",
Expiry: metav1.NewTime(now.Add(1 * time.Minute)),
},
},
},
// ID token is expired.
{
LastUsedTimestamp: metav1.NewTime(now),
Tokens: oidctypes.Token{
IDToken: &oidctypes.IDToken{
Token: "test-id-token",
Expiry: metav1.NewTime(now.Add(-1 * time.Minute)),
},
},
},
// Access token is empty, but not nil.
{
LastUsedTimestamp: metav1.NewTime(now),
Tokens: oidctypes.Token{
AccessToken: &oidctypes.AccessToken{
Token: "",
Expiry: metav1.NewTime(now.Add(1 * time.Minute)),
},
},
},
// Access token is expired.
{
LastUsedTimestamp: metav1.NewTime(now),
Tokens: oidctypes.Token{
AccessToken: &oidctypes.AccessToken{
Token: "test-access-token",
Expiry: metav1.NewTime(now.Add(-1 * time.Minute)),
},
},
},
// Refresh token is empty, but not nil.
{
LastUsedTimestamp: metav1.NewTime(now),
Tokens: oidctypes.Token{
RefreshToken: &oidctypes.RefreshToken{
Token: "",
},
},
},
// Session has a refresh token but it hasn't been used in >90 days.
{
LastUsedTimestamp: metav1.NewTime(now.AddDate(-1, 0, 0)),
Tokens: oidctypes.Token{
RefreshToken: &oidctypes.RefreshToken{
Token: "test-refresh-token",
},
},
},
// Two entries that are still valid.
{
CreationTimestamp: metav1.NewTime(now.Add(-1 * time.Hour)),
LastUsedTimestamp: metav1.NewTime(now),
Tokens: oidctypes.Token{
RefreshToken: &oidctypes.RefreshToken{
Token: "test-refresh-token2",
},
},
},
{
CreationTimestamp: metav1.NewTime(now.Add(-2 * time.Hour)),
LastUsedTimestamp: metav1.NewTime(now),
Tokens: oidctypes.Token{
RefreshToken: &oidctypes.RefreshToken{
Token: "test-refresh-token1",
},
},
},
}
// Expect that all but the last two valid session are pruned, and that they're sorted.
require.Equal(t, &sessionCache{
TypeMeta: metav1.TypeMeta{APIVersion: "config.supervisor.pinniped.dev/v1alpha1", Kind: "SessionCache"},
Sessions: []sessionEntry{
{
CreationTimestamp: metav1.NewTime(now.Add(-2 * time.Hour)),
LastUsedTimestamp: metav1.NewTime(now),
Tokens: oidctypes.Token{
RefreshToken: &oidctypes.RefreshToken{
Token: "test-refresh-token1",
},
},
},
{
CreationTimestamp: metav1.NewTime(now.Add(-1 * time.Hour)),
LastUsedTimestamp: metav1.NewTime(now),
Tokens: oidctypes.Token{
RefreshToken: &oidctypes.RefreshToken{
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)
}