ContainerImage.Pinniped/test/integration/supervisor_storage_test.go

141 lines
5.5 KiB
Go

// Copyright 2020-2022 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
package integration
import (
"context"
"encoding/json"
stderrors "errors"
"testing"
"time"
"github.com/ory/fosite"
"github.com/ory/fosite/compose"
"github.com/stretchr/testify/require"
v1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"go.pinniped.dev/internal/fositestorage/authorizationcode"
"go.pinniped.dev/internal/oidc/clientregistry"
"go.pinniped.dev/internal/testutil"
"go.pinniped.dev/test/testlib"
)
func TestAuthorizeCodeStorage(t *testing.T) {
env := testlib.IntegrationEnv(t)
client := testlib.NewKubernetesClientset(t)
const (
// randomly generated HMAC authorization code (see below)
code = "TQ72B8YjdEOZyxridYbTLE-pzoK4hpdkZxym5j4EmSc.TKRTgQG41IBQ16FDKTthRdhXfLlNaErcMd9Fy47uXAw"
// name of the secret that will be created in Kube
name = "pinniped-storage-authcode-jssfhaibxdkiaugxufbsso3bixmfo7fzjvuevxbr35c4xdxolqga"
)
hmac := compose.NewOAuth2HMACStrategy(&compose.Config{}, []byte("super-secret-32-byte-for-testing"), nil)
// test data generation via:
// code, signature, err := hmac.GenerateAuthorizeCode(ctx, nil)
signature := hmac.AuthorizeCodeSignature(code)
secrets := client.CoreV1().Secrets(env.SupervisorNamespace)
t.Cleanup(func() {
ctx, cancel := context.WithTimeout(context.Background(), time.Minute)
defer cancel()
err := secrets.Delete(ctx, name, metav1.DeleteOptions{})
require.NoError(t, err)
})
ctx, cancel := context.WithTimeout(context.Background(), time.Minute)
defer cancel()
sessionStorageLifetime := 5 * time.Minute
storage := authorizationcode.New(secrets, time.Now, sessionStorageLifetime)
// the session for this signature should not exist yet
notFoundRequest, err := storage.GetAuthorizeCodeSession(ctx, signature, nil)
require.Error(t, err)
require.True(t, stderrors.Is(err, fosite.ErrNotFound))
require.Nil(t, notFoundRequest)
// Create a fake session to store below. Fill in a few fields to make sure we can get them back.
session := authorizationcode.NewValidEmptyAuthorizeCodeSession()
session.Request = &fosite.Request{
ID: "abcd-1",
RequestedAt: time.Time{},
Client: &clientregistry.Client{
DefaultOpenIDConnectClient: fosite.DefaultOpenIDConnectClient{
DefaultClient: &fosite.DefaultClient{
ID: "pinny",
},
JSONWebKeysURI: "where",
TokenEndpointAuthMethod: "something",
},
},
Session: testutil.NewFakePinnipedSession(),
}
err = storage.CreateAuthorizeCodeSession(ctx, signature, session.Request)
require.NoError(t, err)
// trying to create the session again fails because it already exists
err = storage.CreateAuthorizeCodeSession(ctx, signature, session.Request)
require.Error(t, err)
require.True(t, errors.IsAlreadyExists(err))
// check that the data stored in Kube matches what we put in
initialSecret, err := secrets.Get(ctx, name, metav1.GetOptions{})
require.NoError(t, err)
// Note that CreateAuthorizeCodeSession() sets Active to true and also sets the Version before storing the session,
// so expect those here.
session.Active = true
session.Version = "3" // this is the value of the authorizationcode.authorizeCodeStorageVersion constant
expectedSessionStorageJSON, err := json.Marshal(session)
require.NoError(t, err)
require.JSONEq(t, string(expectedSessionStorageJSON), string(initialSecret.Data["pinniped-storage-data"]))
// check that the Secret got the expected annotations
actualGCAfterValue := initialSecret.Annotations["storage.pinniped.dev/garbage-collect-after"]
require.NotEmpty(t, actualGCAfterValue)
parsedActualGCAfterValue, err := time.Parse(time.RFC3339, actualGCAfterValue)
require.NoError(t, err)
testutil.RequireTimeInDelta(t, time.Now().Add(sessionStorageLifetime), parsedActualGCAfterValue, 30*time.Second)
// check that the Secret got the right labels
require.Equal(t, map[string]string{"storage.pinniped.dev/type": "authcode"}, initialSecret.Labels)
// check that the Secret got the right type
require.Equal(t, v1.SecretType("storage.pinniped.dev/authcode"), initialSecret.Type)
// we should be able to get the session now and the request should be the same as what we put in
request, err := storage.GetAuthorizeCodeSession(ctx, signature, nil)
require.NoError(t, err)
require.Equal(t, session.Request, request)
// simulate the authorization code being exchanged
err = storage.InvalidateAuthorizeCodeSession(ctx, signature)
require.NoError(t, err)
// trying to get the authcode session after it was invalidated should fail
invalidatedRequest, err := storage.GetAuthorizeCodeSession(ctx, signature, nil)
require.Error(t, err)
require.True(t, stderrors.Is(err, fosite.ErrInvalidatedAuthorizeCode))
require.Equal(t, session.Request, invalidatedRequest)
// trying to use the code session more than once should fail
err = storage.InvalidateAuthorizeCodeSession(ctx, signature)
require.Error(t, err)
require.True(t, stderrors.Is(err, fosite.ErrInvalidatedAuthorizeCode))
// the data stored in Kube should be exactly the same but it should be marked as used
invalidatedSecret, err := secrets.Get(ctx, name, metav1.GetOptions{})
require.NoError(t, err)
// InvalidateAuthorizeCodeSession() sets Active to false, so update the expected value accordingly.
session.Active = false
expectedInvalidatedJSON, err := json.Marshal(session)
require.NoError(t, err)
require.JSONEq(t, string(expectedInvalidatedJSON), string(invalidatedSecret.Data["pinniped-storage-data"]))
}