2020-07-23 15:05:21 +00:00
|
|
|
/*
|
|
|
|
Copyright 2020 VMware, Inc.
|
|
|
|
SPDX-License-Identifier: Apache-2.0
|
|
|
|
*/
|
|
|
|
|
|
|
|
package loginrequest
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
2020-07-27 13:08:39 +00:00
|
|
|
"crypto/x509/pkix"
|
2020-07-23 15:05:21 +00:00
|
|
|
"errors"
|
2020-07-24 16:52:38 +00:00
|
|
|
"fmt"
|
2020-07-23 15:05:21 +00:00
|
|
|
"testing"
|
|
|
|
"time"
|
|
|
|
|
2020-07-27 13:08:39 +00:00
|
|
|
"github.com/golang/mock/gomock"
|
2020-07-23 15:05:21 +00:00
|
|
|
"github.com/stretchr/testify/require"
|
2020-07-23 16:50:23 +00:00
|
|
|
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
2020-07-23 15:05:21 +00:00
|
|
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
|
|
"k8s.io/apimachinery/pkg/runtime"
|
|
|
|
"k8s.io/apiserver/pkg/authentication/authenticator"
|
2020-07-24 15:21:36 +00:00
|
|
|
"k8s.io/apiserver/pkg/authentication/user"
|
2020-07-23 15:05:21 +00:00
|
|
|
genericapirequest "k8s.io/apiserver/pkg/endpoints/request"
|
|
|
|
"k8s.io/apiserver/pkg/registry/rest"
|
|
|
|
|
|
|
|
placeholderapi "github.com/suzerain-io/placeholder-name-api/pkg/apis/placeholder"
|
2020-07-27 13:08:39 +00:00
|
|
|
"github.com/suzerain-io/placeholder-name/internal/mocks/mockcertissuer"
|
2020-07-23 15:05:21 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
type contextKey struct{}
|
|
|
|
|
|
|
|
type FakeToken struct {
|
|
|
|
calledWithToken string
|
|
|
|
calledWithContext context.Context
|
|
|
|
timeout time.Duration
|
|
|
|
reachedTimeout bool
|
|
|
|
cancelled bool
|
|
|
|
webhookStartedRunningNotificationChan chan bool
|
2020-07-24 15:21:36 +00:00
|
|
|
returnResponse *authenticator.Response
|
2020-07-23 23:01:55 +00:00
|
|
|
returnUnauthenticated bool
|
2020-07-24 15:21:36 +00:00
|
|
|
returnErr error
|
2020-07-23 15:05:21 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (f *FakeToken) AuthenticateToken(ctx context.Context, token string) (*authenticator.Response, bool, error) {
|
|
|
|
f.calledWithToken = token
|
|
|
|
f.calledWithContext = ctx
|
|
|
|
if f.webhookStartedRunningNotificationChan != nil {
|
|
|
|
f.webhookStartedRunningNotificationChan <- true
|
|
|
|
}
|
|
|
|
afterCh := time.After(f.timeout)
|
|
|
|
select {
|
|
|
|
case <-afterCh:
|
|
|
|
f.reachedTimeout = true
|
|
|
|
case <-ctx.Done():
|
|
|
|
f.cancelled = true
|
|
|
|
}
|
2020-07-24 15:21:36 +00:00
|
|
|
return f.returnResponse, !f.returnUnauthenticated, f.returnErr
|
2020-07-23 15:05:21 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func callCreate(ctx context.Context, storage *REST, loginRequest *placeholderapi.LoginRequest) (runtime.Object, error) {
|
|
|
|
return storage.Create(
|
|
|
|
ctx,
|
|
|
|
loginRequest,
|
|
|
|
rest.ValidateAllObjectFunc,
|
|
|
|
&metav1.CreateOptions{
|
|
|
|
DryRun: []string{},
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
func validLoginRequest() *placeholderapi.LoginRequest {
|
2020-07-24 18:00:29 +00:00
|
|
|
return validLoginRequestWithToken("some token")
|
2020-07-23 23:01:55 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func validLoginRequestWithToken(token string) *placeholderapi.LoginRequest {
|
2020-07-23 15:05:21 +00:00
|
|
|
return loginRequest(placeholderapi.LoginRequestSpec{
|
|
|
|
Type: placeholderapi.TokenLoginCredentialType,
|
2020-07-23 23:01:55 +00:00
|
|
|
Token: &placeholderapi.LoginRequestTokenCredential{Value: token},
|
2020-07-23 15:05:21 +00:00
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
func loginRequest(spec placeholderapi.LoginRequestSpec) *placeholderapi.LoginRequest {
|
|
|
|
return &placeholderapi.LoginRequest{
|
|
|
|
TypeMeta: metav1.TypeMeta{},
|
|
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
|
|
Name: "request name",
|
|
|
|
},
|
|
|
|
Spec: spec,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-07-24 18:00:29 +00:00
|
|
|
func webhookSuccessResponse() *authenticator.Response {
|
|
|
|
return &authenticator.Response{User: &user.DefaultInfo{
|
|
|
|
Name: "some-user",
|
|
|
|
UID: "",
|
|
|
|
Groups: []string{},
|
|
|
|
Extra: nil,
|
|
|
|
}}
|
|
|
|
}
|
|
|
|
|
2020-07-23 16:50:23 +00:00
|
|
|
func requireAPIError(t *testing.T, response runtime.Object, err error, expectedErrorTypeChecker func(err error) bool, expectedErrorMessage string) {
|
|
|
|
t.Helper()
|
|
|
|
require.Nil(t, response)
|
|
|
|
require.True(t, expectedErrorTypeChecker(err))
|
|
|
|
var status apierrors.APIStatus
|
|
|
|
errors.As(err, &status)
|
|
|
|
require.Contains(t, status.Status().Message, expectedErrorMessage)
|
|
|
|
}
|
|
|
|
|
2020-07-24 18:00:29 +00:00
|
|
|
func requireSuccessfulResponseWithAuthenticationFailureMessage(t *testing.T, err error, response runtime.Object) {
|
|
|
|
require.NoError(t, err)
|
|
|
|
require.Equal(t, response, &placeholderapi.LoginRequest{
|
|
|
|
Status: placeholderapi.LoginRequestStatus{
|
|
|
|
Credential: nil,
|
|
|
|
Message: "authentication failed",
|
|
|
|
},
|
|
|
|
})
|
2020-07-24 15:21:36 +00:00
|
|
|
}
|
|
|
|
|
2020-07-27 13:08:39 +00:00
|
|
|
func successfulIssuer(ctrl *gomock.Controller) CertIssuer {
|
|
|
|
issuer := mockcertissuer.NewMockCertIssuer(ctrl)
|
|
|
|
issuer.EXPECT().
|
|
|
|
IssuePEM(gomock.Any(), gomock.Any(), gomock.Any()).
|
|
|
|
Return([]byte("test-cert"), []byte("test-key"), nil)
|
|
|
|
return issuer
|
|
|
|
}
|
|
|
|
|
2020-07-23 23:01:55 +00:00
|
|
|
func TestCreateSucceedsWhenGivenATokenAndTheWebhookAuthenticatesTheToken(t *testing.T) {
|
2020-07-27 13:08:39 +00:00
|
|
|
ctrl := gomock.NewController(t)
|
|
|
|
defer ctrl.Finish()
|
|
|
|
|
2020-07-23 23:01:55 +00:00
|
|
|
webhook := FakeToken{
|
2020-07-24 15:21:36 +00:00
|
|
|
returnResponse: &authenticator.Response{
|
|
|
|
User: &user.DefaultInfo{
|
|
|
|
Name: "test-user",
|
|
|
|
Groups: []string{"test-group-1", "test-group-2"},
|
|
|
|
},
|
|
|
|
},
|
2020-07-23 23:01:55 +00:00
|
|
|
returnUnauthenticated: false,
|
|
|
|
}
|
2020-07-27 13:08:39 +00:00
|
|
|
|
|
|
|
issuer := mockcertissuer.NewMockCertIssuer(ctrl)
|
|
|
|
issuer.EXPECT().IssuePEM(
|
|
|
|
pkix.Name{
|
|
|
|
CommonName: "test-user",
|
|
|
|
OrganizationalUnit: []string{"test-group-1", "test-group-2"}},
|
|
|
|
[]string{},
|
2020-07-27 18:38:32 +00:00
|
|
|
1*time.Hour,
|
2020-07-27 13:08:39 +00:00
|
|
|
).Return([]byte("test-cert"), []byte("test-key"), nil)
|
|
|
|
|
|
|
|
storage := NewREST(&webhook, issuer)
|
2020-07-23 15:05:21 +00:00
|
|
|
requestToken := "a token"
|
2020-07-23 23:01:55 +00:00
|
|
|
|
|
|
|
response, err := callCreate(context.Background(), storage, validLoginRequestWithToken(requestToken))
|
2020-07-23 15:05:21 +00:00
|
|
|
|
|
|
|
require.NoError(t, err)
|
2020-07-30 21:43:20 +00:00
|
|
|
require.IsType(t, &placeholderapi.LoginRequest{}, response)
|
|
|
|
|
|
|
|
expires := response.(*placeholderapi.LoginRequest).Status.Credential.ExpirationTimestamp
|
|
|
|
require.NotNil(t, expires)
|
|
|
|
require.InDelta(t, time.Now().Add(1*time.Hour).Unix(), expires.Unix(), 5)
|
2020-07-31 03:05:32 +00:00
|
|
|
response.(*placeholderapi.LoginRequest).Status.Credential.ExpirationTimestamp = metav1.Time{}
|
2020-07-30 21:43:20 +00:00
|
|
|
|
2020-07-23 15:05:21 +00:00
|
|
|
require.Equal(t, response, &placeholderapi.LoginRequest{
|
|
|
|
Status: placeholderapi.LoginRequestStatus{
|
2020-07-24 15:21:36 +00:00
|
|
|
User: &placeholderapi.User{
|
|
|
|
Name: "test-user",
|
|
|
|
Groups: []string{"test-group-1", "test-group-2"},
|
|
|
|
},
|
2020-07-23 23:01:55 +00:00
|
|
|
Credential: &placeholderapi.LoginRequestCredential{
|
2020-07-31 03:05:32 +00:00
|
|
|
ExpirationTimestamp: metav1.Time{},
|
2020-07-27 13:08:39 +00:00
|
|
|
ClientCertificateData: "test-cert",
|
|
|
|
ClientKeyData: "test-key",
|
2020-07-23 23:01:55 +00:00
|
|
|
},
|
|
|
|
Message: "",
|
2020-07-23 15:05:21 +00:00
|
|
|
},
|
|
|
|
})
|
|
|
|
require.Equal(t, requestToken, webhook.calledWithToken)
|
|
|
|
}
|
|
|
|
|
2020-07-27 13:08:39 +00:00
|
|
|
func TestCreateFailsWithValidTokenWhenCertIssuerFails(t *testing.T) {
|
|
|
|
ctrl := gomock.NewController(t)
|
|
|
|
defer ctrl.Finish()
|
|
|
|
|
|
|
|
webhook := FakeToken{
|
|
|
|
returnResponse: &authenticator.Response{
|
|
|
|
User: &user.DefaultInfo{
|
|
|
|
Name: "test-user",
|
|
|
|
Groups: []string{"test-group-1", "test-group-2"},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
returnUnauthenticated: false,
|
|
|
|
}
|
|
|
|
|
|
|
|
issuer := mockcertissuer.NewMockCertIssuer(ctrl)
|
|
|
|
issuer.EXPECT().
|
|
|
|
IssuePEM(gomock.Any(), gomock.Any(), gomock.Any()).
|
|
|
|
Return(nil, nil, fmt.Errorf("some certificate authority error"))
|
|
|
|
|
|
|
|
storage := NewREST(&webhook, issuer)
|
|
|
|
requestToken := "a token"
|
|
|
|
|
|
|
|
response, err := callCreate(context.Background(), storage, validLoginRequestWithToken(requestToken))
|
|
|
|
requireSuccessfulResponseWithAuthenticationFailureMessage(t, err, response)
|
|
|
|
require.Equal(t, requestToken, webhook.calledWithToken)
|
|
|
|
}
|
|
|
|
|
2020-07-23 23:01:55 +00:00
|
|
|
func TestCreateSucceedsWithAnUnauthenticatedStatusWhenGivenATokenAndTheWebhookDoesNotAuthenticateTheToken(t *testing.T) {
|
2020-07-23 15:05:21 +00:00
|
|
|
webhook := FakeToken{
|
2020-07-23 23:01:55 +00:00
|
|
|
returnUnauthenticated: true,
|
2020-07-23 15:05:21 +00:00
|
|
|
}
|
2020-07-27 13:08:39 +00:00
|
|
|
storage := NewREST(&webhook, nil)
|
2020-07-23 23:01:55 +00:00
|
|
|
requestToken := "a token"
|
2020-07-23 15:05:21 +00:00
|
|
|
|
2020-07-23 23:01:55 +00:00
|
|
|
response, err := callCreate(context.Background(), storage, validLoginRequestWithToken(requestToken))
|
2020-07-23 15:05:21 +00:00
|
|
|
|
2020-07-24 18:00:29 +00:00
|
|
|
requireSuccessfulResponseWithAuthenticationFailureMessage(t, err, response)
|
2020-07-23 23:01:55 +00:00
|
|
|
require.Equal(t, requestToken, webhook.calledWithToken)
|
2020-07-23 15:05:21 +00:00
|
|
|
}
|
|
|
|
|
2020-07-23 23:01:55 +00:00
|
|
|
func TestCreateSucceedsWithAnUnauthenticatedStatusWhenWebhookFails(t *testing.T) {
|
2020-07-23 15:05:21 +00:00
|
|
|
webhook := FakeToken{
|
2020-07-23 23:01:55 +00:00
|
|
|
returnErr: errors.New("some webhook error"),
|
2020-07-23 15:05:21 +00:00
|
|
|
}
|
2020-07-27 13:08:39 +00:00
|
|
|
storage := NewREST(&webhook, nil)
|
2020-07-23 15:05:21 +00:00
|
|
|
|
2020-07-23 23:01:55 +00:00
|
|
|
response, err := callCreate(context.Background(), storage, validLoginRequest())
|
2020-07-23 15:05:21 +00:00
|
|
|
|
2020-07-24 18:00:29 +00:00
|
|
|
requireSuccessfulResponseWithAuthenticationFailureMessage(t, err, response)
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestCreateSucceedsWithAnUnauthenticatedStatusWhenWebhookDoesNotReturnAnyUserInfo(t *testing.T) {
|
|
|
|
webhook := FakeToken{
|
|
|
|
returnResponse: &authenticator.Response{},
|
|
|
|
}
|
2020-07-27 13:08:39 +00:00
|
|
|
storage := NewREST(&webhook, nil)
|
2020-07-24 18:00:29 +00:00
|
|
|
|
|
|
|
response, err := callCreate(context.Background(), storage, validLoginRequest())
|
|
|
|
|
|
|
|
requireSuccessfulResponseWithAuthenticationFailureMessage(t, err, response)
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestCreateSucceedsWithAnUnauthenticatedStatusWhenWebhookReturnsAnEmptyUsername(t *testing.T) {
|
|
|
|
webhook := FakeToken{
|
|
|
|
returnResponse: &authenticator.Response{
|
|
|
|
User: &user.DefaultInfo{
|
|
|
|
Name: "",
|
|
|
|
},
|
2020-07-23 23:01:55 +00:00
|
|
|
},
|
2020-07-24 18:00:29 +00:00
|
|
|
}
|
2020-07-27 13:08:39 +00:00
|
|
|
storage := NewREST(&webhook, nil)
|
2020-07-24 18:00:29 +00:00
|
|
|
|
|
|
|
response, err := callCreate(context.Background(), storage, validLoginRequest())
|
|
|
|
|
|
|
|
requireSuccessfulResponseWithAuthenticationFailureMessage(t, err, response)
|
2020-07-23 15:05:21 +00:00
|
|
|
}
|
|
|
|
|
2020-07-23 23:01:55 +00:00
|
|
|
func TestCreateDoesNotPassAdditionalContextInfoToTheWebhook(t *testing.T) {
|
2020-07-27 13:08:39 +00:00
|
|
|
ctrl := gomock.NewController(t)
|
|
|
|
defer ctrl.Finish()
|
|
|
|
|
2020-07-24 15:21:36 +00:00
|
|
|
webhook := FakeToken{
|
2020-07-24 18:00:29 +00:00
|
|
|
returnResponse: webhookSuccessResponse(),
|
2020-07-24 15:21:36 +00:00
|
|
|
}
|
2020-07-27 13:08:39 +00:00
|
|
|
storage := NewREST(&webhook, successfulIssuer(ctrl))
|
2020-07-23 23:01:55 +00:00
|
|
|
ctx := context.WithValue(context.Background(), contextKey{}, "context-value")
|
2020-07-23 15:05:21 +00:00
|
|
|
|
|
|
|
_, err := callCreate(ctx, storage, validLoginRequest())
|
2020-07-23 23:01:55 +00:00
|
|
|
|
|
|
|
require.NoError(t, err)
|
|
|
|
require.Nil(t, webhook.calledWithContext.Value("context-key"))
|
2020-07-23 15:05:21 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func TestCreateFailsWhenGivenTheWrongInputType(t *testing.T) {
|
|
|
|
notALoginRequest := runtime.Unknown{}
|
2020-07-27 13:08:39 +00:00
|
|
|
response, err := NewREST(&FakeToken{}, nil).Create(
|
2020-07-23 15:05:21 +00:00
|
|
|
genericapirequest.NewContext(),
|
|
|
|
¬ALoginRequest,
|
|
|
|
rest.ValidateAllObjectFunc,
|
|
|
|
&metav1.CreateOptions{})
|
|
|
|
|
2020-07-23 16:50:23 +00:00
|
|
|
requireAPIError(t, response, err, apierrors.IsBadRequest, "not a LoginRequest")
|
2020-07-23 15:05:21 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func TestCreateFailsWhenTokenIsNilInRequest(t *testing.T) {
|
2020-07-27 13:08:39 +00:00
|
|
|
storage := NewREST(&FakeToken{}, nil)
|
2020-07-23 15:05:21 +00:00
|
|
|
response, err := callCreate(context.Background(), storage, loginRequest(placeholderapi.LoginRequestSpec{
|
|
|
|
Type: placeholderapi.TokenLoginCredentialType,
|
|
|
|
Token: nil,
|
|
|
|
}))
|
|
|
|
|
2020-07-23 16:50:23 +00:00
|
|
|
requireAPIError(t, response, err, apierrors.IsInvalid,
|
|
|
|
`.placeholder.suzerain-io.github.io "request name" is invalid: spec.token.value: Required value: token must be supplied`)
|
2020-07-23 15:05:21 +00:00
|
|
|
}
|
|
|
|
|
2020-07-23 23:01:55 +00:00
|
|
|
func TestCreateFailsWhenTypeInRequestIsMissing(t *testing.T) {
|
2020-07-27 13:08:39 +00:00
|
|
|
storage := NewREST(&FakeToken{}, nil)
|
2020-07-23 23:01:55 +00:00
|
|
|
response, err := callCreate(context.Background(), storage, loginRequest(placeholderapi.LoginRequestSpec{
|
|
|
|
Type: "",
|
|
|
|
Token: &placeholderapi.LoginRequestTokenCredential{Value: "a token"},
|
|
|
|
}))
|
|
|
|
|
|
|
|
requireAPIError(t, response, err, apierrors.IsInvalid,
|
|
|
|
`.placeholder.suzerain-io.github.io "request name" is invalid: spec.type: Required value: type must be supplied`)
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestCreateFailsWhenTypeInRequestIsNotLegal(t *testing.T) {
|
2020-07-27 13:08:39 +00:00
|
|
|
storage := NewREST(&FakeToken{}, nil)
|
2020-07-23 23:01:55 +00:00
|
|
|
response, err := callCreate(context.Background(), storage, loginRequest(placeholderapi.LoginRequestSpec{
|
|
|
|
Type: "this in an invalid type",
|
|
|
|
Token: &placeholderapi.LoginRequestTokenCredential{Value: "a token"},
|
|
|
|
}))
|
|
|
|
|
|
|
|
requireAPIError(t, response, err, apierrors.IsInvalid,
|
|
|
|
`.placeholder.suzerain-io.github.io "request name" is invalid: spec.type: Invalid value: "this in an invalid type": unrecognized type`)
|
|
|
|
}
|
|
|
|
|
2020-07-23 15:05:21 +00:00
|
|
|
func TestCreateFailsWhenTokenValueIsEmptyInRequest(t *testing.T) {
|
2020-07-27 13:08:39 +00:00
|
|
|
storage := NewREST(&FakeToken{}, nil)
|
2020-07-23 15:05:21 +00:00
|
|
|
response, err := callCreate(context.Background(), storage, loginRequest(placeholderapi.LoginRequestSpec{
|
|
|
|
Type: placeholderapi.TokenLoginCredentialType,
|
|
|
|
Token: &placeholderapi.LoginRequestTokenCredential{Value: ""},
|
|
|
|
}))
|
|
|
|
|
2020-07-23 16:50:23 +00:00
|
|
|
requireAPIError(t, response, err, apierrors.IsInvalid,
|
|
|
|
`.placeholder.suzerain-io.github.io "request name" is invalid: spec.token.value: Required value: token must be supplied`)
|
2020-07-23 15:05:21 +00:00
|
|
|
}
|
|
|
|
|
2020-07-24 16:52:38 +00:00
|
|
|
func TestCreateFailsWhenValidationFails(t *testing.T) {
|
2020-07-27 13:08:39 +00:00
|
|
|
storage := NewREST(&FakeToken{}, nil)
|
2020-07-24 16:52:38 +00:00
|
|
|
response, err := storage.Create(
|
|
|
|
context.Background(),
|
|
|
|
validLoginRequest(),
|
|
|
|
func(ctx context.Context, obj runtime.Object) error {
|
|
|
|
return fmt.Errorf("some validation error")
|
|
|
|
},
|
2020-07-24 18:00:29 +00:00
|
|
|
&metav1.CreateOptions{})
|
2020-07-24 16:52:38 +00:00
|
|
|
require.Nil(t, response)
|
|
|
|
require.EqualError(t, err, "some validation error")
|
|
|
|
}
|
|
|
|
|
2020-07-24 18:00:29 +00:00
|
|
|
func TestCreateDoesNotAllowValidationFunctionToMutateRequest(t *testing.T) {
|
2020-07-27 13:08:39 +00:00
|
|
|
ctrl := gomock.NewController(t)
|
|
|
|
defer ctrl.Finish()
|
|
|
|
|
2020-07-24 18:00:29 +00:00
|
|
|
webhook := FakeToken{
|
|
|
|
returnResponse: webhookSuccessResponse(),
|
|
|
|
}
|
2020-07-27 13:08:39 +00:00
|
|
|
storage := NewREST(&webhook, successfulIssuer(ctrl))
|
2020-07-24 18:00:29 +00:00
|
|
|
requestToken := "a token"
|
|
|
|
response, err := storage.Create(
|
|
|
|
context.Background(),
|
|
|
|
validLoginRequestWithToken(requestToken),
|
|
|
|
func(ctx context.Context, obj runtime.Object) error {
|
|
|
|
loginRequest, _ := obj.(*placeholderapi.LoginRequest)
|
|
|
|
loginRequest.Spec.Token.Value = "foobaz"
|
|
|
|
return nil
|
|
|
|
},
|
|
|
|
&metav1.CreateOptions{})
|
|
|
|
require.NoError(t, err)
|
|
|
|
require.NotEmpty(t, response)
|
|
|
|
require.Equal(t, requestToken, webhook.calledWithToken) // i.e. not called with foobaz
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestCreateDoesNotAllowValidationFunctionToSeeTheActualRequestToken(t *testing.T) {
|
2020-07-27 13:08:39 +00:00
|
|
|
ctrl := gomock.NewController(t)
|
|
|
|
defer ctrl.Finish()
|
|
|
|
|
2020-07-24 18:00:29 +00:00
|
|
|
webhook := FakeToken{
|
|
|
|
returnResponse: webhookSuccessResponse(),
|
|
|
|
}
|
2020-07-27 13:08:39 +00:00
|
|
|
|
|
|
|
storage := NewREST(&webhook, successfulIssuer(ctrl))
|
2020-07-24 18:00:29 +00:00
|
|
|
validationFunctionWasCalled := false
|
|
|
|
var validationFunctionSawTokenValue string
|
|
|
|
response, err := storage.Create(
|
|
|
|
context.Background(),
|
|
|
|
validLoginRequest(),
|
|
|
|
func(ctx context.Context, obj runtime.Object) error {
|
|
|
|
loginRequest, _ := obj.(*placeholderapi.LoginRequest)
|
|
|
|
validationFunctionWasCalled = true
|
|
|
|
validationFunctionSawTokenValue = loginRequest.Spec.Token.Value
|
|
|
|
return nil
|
|
|
|
},
|
|
|
|
&metav1.CreateOptions{})
|
|
|
|
require.NoError(t, err)
|
|
|
|
require.NotEmpty(t, response)
|
|
|
|
require.True(t, validationFunctionWasCalled)
|
|
|
|
require.Empty(t, validationFunctionSawTokenValue)
|
|
|
|
}
|
|
|
|
|
2020-07-23 15:05:21 +00:00
|
|
|
func TestCreateFailsWhenRequestOptionsDryRunIsNotEmpty(t *testing.T) {
|
2020-07-27 13:08:39 +00:00
|
|
|
response, err := NewREST(&FakeToken{}, nil).Create(
|
2020-07-23 15:05:21 +00:00
|
|
|
genericapirequest.NewContext(),
|
|
|
|
validLoginRequest(),
|
|
|
|
rest.ValidateAllObjectFunc,
|
|
|
|
&metav1.CreateOptions{
|
|
|
|
DryRun: []string{"some dry run flag"},
|
|
|
|
})
|
|
|
|
|
2020-07-23 16:50:23 +00:00
|
|
|
requireAPIError(t, response, err, apierrors.IsInvalid,
|
|
|
|
`.placeholder.suzerain-io.github.io "request name" is invalid: dryRun: Unsupported value: []string{"some dry run flag"}`)
|
2020-07-23 15:05:21 +00:00
|
|
|
}
|
2020-07-23 23:01:55 +00:00
|
|
|
|
|
|
|
func TestCreateCancelsTheWebhookInvocationWhenTheCallToCreateIsCancelledItself(t *testing.T) {
|
2020-07-27 13:08:39 +00:00
|
|
|
ctrl := gomock.NewController(t)
|
|
|
|
defer ctrl.Finish()
|
|
|
|
|
2020-07-23 23:01:55 +00:00
|
|
|
webhookStartedRunningNotificationChan := make(chan bool)
|
|
|
|
webhook := FakeToken{
|
|
|
|
timeout: time.Second * 2,
|
|
|
|
webhookStartedRunningNotificationChan: webhookStartedRunningNotificationChan,
|
2020-07-24 18:00:29 +00:00
|
|
|
returnResponse: webhookSuccessResponse(),
|
2020-07-23 23:01:55 +00:00
|
|
|
}
|
2020-07-27 13:08:39 +00:00
|
|
|
storage := NewREST(&webhook, successfulIssuer(ctrl))
|
2020-07-23 23:01:55 +00:00
|
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
|
|
|
|
|
|
c := make(chan bool)
|
|
|
|
go func() {
|
|
|
|
_, err := callCreate(ctx, storage, validLoginRequest())
|
|
|
|
c <- true
|
|
|
|
require.NoError(t, err)
|
|
|
|
}()
|
|
|
|
|
|
|
|
require.False(t, webhook.cancelled)
|
|
|
|
require.False(t, webhook.reachedTimeout)
|
|
|
|
<-webhookStartedRunningNotificationChan // wait long enough to make sure that the webhook has started
|
|
|
|
cancel() // cancel the context that was passed to storage.Create() above
|
|
|
|
<-c // wait for the above call to storage.Create() to be finished
|
|
|
|
require.True(t, webhook.cancelled)
|
|
|
|
require.False(t, webhook.reachedTimeout)
|
|
|
|
require.Equal(t, context.Canceled, webhook.calledWithContext.Err()) // the inner context is cancelled
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestCreateAllowsTheWebhookInvocationToFinishWhenTheCallToCreateIsNotCancelledItself(t *testing.T) {
|
2020-07-27 13:08:39 +00:00
|
|
|
ctrl := gomock.NewController(t)
|
|
|
|
defer ctrl.Finish()
|
|
|
|
|
2020-07-23 23:01:55 +00:00
|
|
|
webhookStartedRunningNotificationChan := make(chan bool)
|
|
|
|
webhook := FakeToken{
|
|
|
|
timeout: 0,
|
|
|
|
webhookStartedRunningNotificationChan: webhookStartedRunningNotificationChan,
|
2020-07-24 18:00:29 +00:00
|
|
|
returnResponse: webhookSuccessResponse(),
|
2020-07-23 23:01:55 +00:00
|
|
|
}
|
2020-07-27 13:08:39 +00:00
|
|
|
storage := NewREST(&webhook, successfulIssuer(ctrl))
|
2020-07-23 23:01:55 +00:00
|
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
|
|
defer cancel()
|
|
|
|
|
|
|
|
c := make(chan bool)
|
|
|
|
go func() {
|
|
|
|
_, err := callCreate(ctx, storage, validLoginRequest())
|
|
|
|
c <- true
|
|
|
|
require.NoError(t, err)
|
|
|
|
}()
|
|
|
|
|
|
|
|
require.False(t, webhook.cancelled)
|
|
|
|
require.False(t, webhook.reachedTimeout)
|
|
|
|
<-webhookStartedRunningNotificationChan // wait long enough to make sure that the webhook has started
|
|
|
|
<-c // wait for the above call to storage.Create() to be finished
|
|
|
|
require.False(t, webhook.cancelled)
|
|
|
|
require.True(t, webhook.reachedTimeout)
|
|
|
|
require.Equal(t, context.Canceled, webhook.calledWithContext.Err()) // the inner context is cancelled (in this case by the "defer")
|
|
|
|
}
|