mirror of
https://github.com/smallstep/certificates.git
synced 2024-11-17 15:29:21 +00:00
231b5d8406
Upgrade chi to the v5 module path to avoid deprecation warning about v4 and earlier on the old module path. See https://github.com/go-chi/chi/blob/v4.1.3/go.mod#L1-L4 Signed-off-by: Dominic Evans <dominic.evans@uk.ibm.com>
690 lines
22 KiB
Go
690 lines
22 KiB
Go
package api
|
||
|
||
import (
|
||
"bytes"
|
||
"context"
|
||
"encoding/json"
|
||
"errors"
|
||
"io"
|
||
"net/http"
|
||
"net/http/httptest"
|
||
"strings"
|
||
"testing"
|
||
|
||
"github.com/go-chi/chi/v5"
|
||
"github.com/smallstep/certificates/authority"
|
||
"github.com/smallstep/certificates/authority/admin"
|
||
"github.com/stretchr/testify/assert"
|
||
"go.step.sm/linkedca"
|
||
"google.golang.org/protobuf/encoding/protojson"
|
||
)
|
||
|
||
// ignore secret and id since those are set by the server
|
||
func assertEqualWebhook(t *testing.T, a, b *linkedca.Webhook) {
|
||
assert.Equal(t, a.Name, b.Name)
|
||
assert.Equal(t, a.Url, b.Url)
|
||
assert.Equal(t, a.Kind, b.Kind)
|
||
assert.Equal(t, a.CertType, b.CertType)
|
||
assert.Equal(t, a.DisableTlsClientAuth, b.DisableTlsClientAuth)
|
||
|
||
assert.Equal(t, a.GetAuth(), b.GetAuth())
|
||
}
|
||
|
||
func TestWebhookAdminResponder_CreateProvisionerWebhook(t *testing.T) {
|
||
type test struct {
|
||
auth adminAuthority
|
||
body []byte
|
||
ctx context.Context
|
||
err *admin.Error
|
||
response *linkedca.Webhook
|
||
statusCode int
|
||
}
|
||
var tests = map[string]func(t *testing.T) test{
|
||
"fail/existing-webhook": func(t *testing.T) test {
|
||
webhook := &linkedca.Webhook{
|
||
Name: "already-exists",
|
||
Url: "https://example.com",
|
||
}
|
||
prov := &linkedca.Provisioner{
|
||
Name: "provName",
|
||
Webhooks: []*linkedca.Webhook{webhook},
|
||
}
|
||
ctx := linkedca.NewContextWithProvisioner(context.Background(), prov)
|
||
err := admin.NewError(admin.ErrorConflictType, `provisioner "provName" already has a webhook with the name "already-exists"`)
|
||
err.Message = `provisioner "provName" already has a webhook with the name "already-exists"`
|
||
body := []byte(`
|
||
{
|
||
"name": "already-exists",
|
||
"url": "https://example.com",
|
||
"kind": "ENRICHING"
|
||
}`)
|
||
return test{
|
||
ctx: ctx,
|
||
body: body,
|
||
err: err,
|
||
statusCode: 409,
|
||
}
|
||
},
|
||
"fail/read.ProtoJSON": func(t *testing.T) test {
|
||
prov := &linkedca.Provisioner{
|
||
Name: "provName",
|
||
}
|
||
ctx := linkedca.NewContextWithProvisioner(context.Background(), prov)
|
||
adminErr := admin.NewError(admin.ErrorBadRequestType, "proto: syntax error (line 1:2): invalid value ?")
|
||
adminErr.Message = "proto: syntax error (line 1:2): invalid value ?"
|
||
body := []byte("{?}")
|
||
return test{
|
||
ctx: ctx,
|
||
body: body,
|
||
err: adminErr,
|
||
statusCode: 400,
|
||
}
|
||
},
|
||
"fail/missing-name": func(t *testing.T) test {
|
||
prov := &linkedca.Provisioner{
|
||
Name: "provName",
|
||
}
|
||
ctx := linkedca.NewContextWithProvisioner(context.Background(), prov)
|
||
adminErr := admin.NewError(admin.ErrorBadRequestType, "webhook name is required")
|
||
adminErr.Message = "webhook name is required"
|
||
body := []byte(`{"url": "https://example.com", "kind": "ENRICHING"}`)
|
||
return test{
|
||
ctx: ctx,
|
||
body: body,
|
||
err: adminErr,
|
||
statusCode: 400,
|
||
}
|
||
},
|
||
"fail/missing-url": func(t *testing.T) test {
|
||
prov := &linkedca.Provisioner{
|
||
Name: "provName",
|
||
}
|
||
ctx := linkedca.NewContextWithProvisioner(context.Background(), prov)
|
||
adminErr := admin.NewError(admin.ErrorBadRequestType, "webhook url is invalid")
|
||
adminErr.Message = "webhook url is invalid"
|
||
body := []byte(`{"name": "metadata", "kind": "ENRICHING"}`)
|
||
return test{
|
||
ctx: ctx,
|
||
body: body,
|
||
err: adminErr,
|
||
statusCode: 400,
|
||
}
|
||
},
|
||
"fail/relative-url": func(t *testing.T) test {
|
||
prov := &linkedca.Provisioner{
|
||
Name: "provName",
|
||
}
|
||
ctx := linkedca.NewContextWithProvisioner(context.Background(), prov)
|
||
adminErr := admin.NewError(admin.ErrorBadRequestType, "webhook url is invalid")
|
||
adminErr.Message = "webhook url is invalid"
|
||
body := []byte(`{"name": "metadata", "url": "example.com/path", "kind": "ENRICHING"}`)
|
||
return test{
|
||
ctx: ctx,
|
||
body: body,
|
||
err: adminErr,
|
||
statusCode: 400,
|
||
}
|
||
},
|
||
"fail/http-url": func(t *testing.T) test {
|
||
prov := &linkedca.Provisioner{
|
||
Name: "provName",
|
||
}
|
||
ctx := linkedca.NewContextWithProvisioner(context.Background(), prov)
|
||
adminErr := admin.NewError(admin.ErrorBadRequestType, "webhook url must use https")
|
||
adminErr.Message = "webhook url must use https"
|
||
body := []byte(`{"name": "metadata", "url": "http://example.com", "kind": "ENRICHING"}`)
|
||
return test{
|
||
ctx: ctx,
|
||
body: body,
|
||
err: adminErr,
|
||
statusCode: 400,
|
||
}
|
||
},
|
||
"fail/basic-auth-in-url": func(t *testing.T) test {
|
||
prov := &linkedca.Provisioner{
|
||
Name: "provName",
|
||
}
|
||
ctx := linkedca.NewContextWithProvisioner(context.Background(), prov)
|
||
adminErr := admin.NewError(admin.ErrorBadRequestType, "webhook url may not contain username or password")
|
||
adminErr.Message = "webhook url may not contain username or password"
|
||
body := []byte(`
|
||
{
|
||
"name": "metadata",
|
||
"url": "https://user:pass@example.com",
|
||
"kind": "ENRICHING"
|
||
}`)
|
||
return test{
|
||
ctx: ctx,
|
||
body: body,
|
||
err: adminErr,
|
||
statusCode: 400,
|
||
}
|
||
},
|
||
"fail/secret-in-request": func(t *testing.T) test {
|
||
prov := &linkedca.Provisioner{
|
||
Name: "provName",
|
||
}
|
||
ctx := linkedca.NewContextWithProvisioner(context.Background(), prov)
|
||
adminErr := admin.NewError(admin.ErrorBadRequestType, "webhook secret must not be set")
|
||
adminErr.Message = "webhook secret must not be set"
|
||
body := []byte(`
|
||
{
|
||
"name": "metadata",
|
||
"url": "https://example.com",
|
||
"kind": "ENRICHING",
|
||
"secret": "secret"
|
||
}`)
|
||
return test{
|
||
ctx: ctx,
|
||
body: body,
|
||
err: adminErr,
|
||
statusCode: 400,
|
||
}
|
||
},
|
||
"fail/unsupported-webhook-kind": func(t *testing.T) test {
|
||
prov := &linkedca.Provisioner{
|
||
Name: "provName",
|
||
}
|
||
ctx := linkedca.NewContextWithProvisioner(context.Background(), prov)
|
||
adminErr := admin.NewError(admin.ErrorBadRequestType, `(line 5:13): invalid value for enum type: "UNSUPPORTED"`)
|
||
adminErr.Message = `(line 5:13): invalid value for enum type: "UNSUPPORTED"`
|
||
body := []byte(`
|
||
{
|
||
"name": "metadata",
|
||
"url": "https://example.com",
|
||
"kind": "UNSUPPORTED",
|
||
}`)
|
||
return test{
|
||
ctx: ctx,
|
||
body: body,
|
||
err: adminErr,
|
||
statusCode: 400,
|
||
}
|
||
},
|
||
"fail/auth.UpdateProvisioner-error": func(t *testing.T) test {
|
||
adm := &linkedca.Admin{
|
||
Subject: "step",
|
||
}
|
||
prov := &linkedca.Provisioner{
|
||
Name: "provName",
|
||
}
|
||
ctx := linkedca.NewContextWithAdmin(context.Background(), adm)
|
||
ctx = linkedca.NewContextWithProvisioner(ctx, prov)
|
||
adminErr := admin.NewError(admin.ErrorServerInternalType, "error creating provisioner webhook: force")
|
||
adminErr.Message = "error creating provisioner webhook: force"
|
||
body := []byte(`{"name": "metadata", "url": "https://example.com", "kind": "ENRICHING"}`)
|
||
return test{
|
||
ctx: ctx,
|
||
auth: &mockAdminAuthority{
|
||
MockUpdateProvisioner: func(ctx context.Context, nu *linkedca.Provisioner) error {
|
||
return &authority.PolicyError{
|
||
Typ: authority.StoreFailure,
|
||
Err: errors.New("force"),
|
||
}
|
||
},
|
||
},
|
||
body: body,
|
||
err: adminErr,
|
||
statusCode: 500,
|
||
}
|
||
},
|
||
"ok": func(t *testing.T) test {
|
||
prov := &linkedca.Provisioner{
|
||
Name: "provName",
|
||
}
|
||
ctx := linkedca.NewContextWithProvisioner(context.Background(), prov)
|
||
body := []byte(`{"name": "metadata", "url": "https://example.com", "kind": "ENRICHING", "certType": "X509"}`)
|
||
return test{
|
||
ctx: ctx,
|
||
auth: &mockAdminAuthority{
|
||
MockUpdateProvisioner: func(ctx context.Context, nu *linkedca.Provisioner) error {
|
||
assert.Equal(t, linkedca.Webhook_X509, nu.Webhooks[0].CertType)
|
||
return nil
|
||
},
|
||
},
|
||
body: body,
|
||
response: &linkedca.Webhook{
|
||
Name: "metadata",
|
||
Url: "https://example.com",
|
||
Kind: linkedca.Webhook_ENRICHING,
|
||
CertType: linkedca.Webhook_X509,
|
||
},
|
||
statusCode: 201,
|
||
}
|
||
},
|
||
}
|
||
for name, prep := range tests {
|
||
tc := prep(t)
|
||
t.Run(name, func(t *testing.T) {
|
||
mockMustAuthority(t, tc.auth)
|
||
ctx := admin.NewContext(tc.ctx, &admin.MockDB{})
|
||
war := NewWebhookAdminResponder()
|
||
|
||
req := httptest.NewRequest("POST", "/foo", io.NopCloser(bytes.NewBuffer(tc.body)))
|
||
req = req.WithContext(ctx)
|
||
w := httptest.NewRecorder()
|
||
|
||
war.CreateProvisionerWebhook(w, req)
|
||
res := w.Result()
|
||
|
||
assert.Equal(t, tc.statusCode, res.StatusCode)
|
||
|
||
if res.StatusCode >= 400 {
|
||
|
||
body, err := io.ReadAll(res.Body)
|
||
res.Body.Close()
|
||
assert.NoError(t, err)
|
||
|
||
ae := testAdminError{}
|
||
assert.NoError(t, json.Unmarshal(bytes.TrimSpace(body), &ae))
|
||
|
||
assert.Equal(t, tc.err.Type, ae.Type)
|
||
assert.Equal(t, tc.err.StatusCode(), res.StatusCode)
|
||
assert.Equal(t, tc.err.Detail, ae.Detail)
|
||
assert.Equal(t, []string{"application/json"}, res.Header["Content-Type"])
|
||
|
||
// when the error message starts with "proto", we expect it to have
|
||
// a syntax error (in the tests). If the message doesn't start with "proto",
|
||
// we expect a full string match.
|
||
if strings.HasPrefix(tc.err.Message, "proto:") {
|
||
assert.True(t, strings.Contains(ae.Message, "syntax error"))
|
||
} else {
|
||
assert.Equal(t, tc.err.Message, ae.Message)
|
||
}
|
||
|
||
return
|
||
}
|
||
|
||
resp := &linkedca.Webhook{}
|
||
body, err := io.ReadAll(res.Body)
|
||
assert.NoError(t, err)
|
||
assert.NoError(t, protojson.Unmarshal(body, resp))
|
||
|
||
assertEqualWebhook(t, tc.response, resp)
|
||
assert.NotEmpty(t, resp.Secret)
|
||
assert.NotEmpty(t, resp.Id)
|
||
})
|
||
}
|
||
}
|
||
|
||
func TestWebhookAdminResponder_DeleteProvisionerWebhook(t *testing.T) {
|
||
type test struct {
|
||
auth adminAuthority
|
||
err *admin.Error
|
||
statusCode int
|
||
provisionerWebhooks []*linkedca.Webhook
|
||
webhookName string
|
||
}
|
||
var tests = map[string]func(t *testing.T) test{
|
||
"fail/auth.UpdateProvisioner-error": func(t *testing.T) test {
|
||
adminErr := admin.NewError(admin.ErrorServerInternalType, "error deleting provisioner webhook: force")
|
||
adminErr.Message = "error deleting provisioner webhook: force"
|
||
return test{
|
||
err: adminErr,
|
||
auth: &mockAdminAuthority{
|
||
MockUpdateProvisioner: func(ctx context.Context, nu *linkedca.Provisioner) error {
|
||
return &authority.PolicyError{
|
||
Typ: authority.StoreFailure,
|
||
Err: errors.New("force"),
|
||
}
|
||
},
|
||
},
|
||
statusCode: 500,
|
||
webhookName: "my-webhook",
|
||
provisionerWebhooks: []*linkedca.Webhook{
|
||
{Name: "my-webhook", Url: "https://example.com", Kind: linkedca.Webhook_ENRICHING},
|
||
},
|
||
}
|
||
},
|
||
"ok/not-found": func(t *testing.T) test {
|
||
return test{
|
||
statusCode: 200,
|
||
webhookName: "no-exists",
|
||
provisionerWebhooks: nil,
|
||
}
|
||
},
|
||
"ok": func(t *testing.T) test {
|
||
return test{
|
||
statusCode: 200,
|
||
webhookName: "exists",
|
||
auth: &mockAdminAuthority{
|
||
MockUpdateProvisioner: func(ctx context.Context, nu *linkedca.Provisioner) error {
|
||
assert.Equal(t, nu.Webhooks, []*linkedca.Webhook{
|
||
{Name: "my-2nd-webhook", Url: "https://example.com", Kind: linkedca.Webhook_ENRICHING},
|
||
})
|
||
return nil
|
||
},
|
||
},
|
||
provisionerWebhooks: []*linkedca.Webhook{
|
||
{Name: "exists", Url: "https.example.com", Kind: linkedca.Webhook_ENRICHING},
|
||
{Name: "my-2nd-webhook", Url: "https://example.com", Kind: linkedca.Webhook_ENRICHING},
|
||
},
|
||
}
|
||
},
|
||
}
|
||
for name, prep := range tests {
|
||
tc := prep(t)
|
||
t.Run(name, func(t *testing.T) {
|
||
mockMustAuthority(t, tc.auth)
|
||
|
||
chiCtx := chi.NewRouteContext()
|
||
chiCtx.URLParams.Add("webhookName", tc.webhookName)
|
||
ctx := context.WithValue(context.Background(), chi.RouteCtxKey, chiCtx)
|
||
prov := &linkedca.Provisioner{
|
||
Name: "provName",
|
||
Webhooks: tc.provisionerWebhooks,
|
||
}
|
||
ctx = linkedca.NewContextWithProvisioner(ctx, prov)
|
||
ctx = admin.NewContext(ctx, &admin.MockDB{})
|
||
req := httptest.NewRequest("DELETE", "/foo", http.NoBody).WithContext(ctx)
|
||
|
||
war := NewWebhookAdminResponder()
|
||
|
||
w := httptest.NewRecorder()
|
||
|
||
war.DeleteProvisionerWebhook(w, req)
|
||
res := w.Result()
|
||
|
||
assert.Equal(t, tc.statusCode, res.StatusCode)
|
||
|
||
if res.StatusCode >= 400 {
|
||
|
||
body, err := io.ReadAll(res.Body)
|
||
res.Body.Close()
|
||
assert.NoError(t, err)
|
||
|
||
ae := testAdminError{}
|
||
assert.NoError(t, json.Unmarshal(bytes.TrimSpace(body), &ae))
|
||
|
||
assert.Equal(t, tc.err.Type, ae.Type)
|
||
assert.Equal(t, tc.err.StatusCode(), res.StatusCode)
|
||
assert.Equal(t, tc.err.Detail, ae.Detail)
|
||
assert.Equal(t, []string{"application/json"}, res.Header["Content-Type"])
|
||
|
||
// when the error message starts with "proto", we expect it to have
|
||
// a syntax error (in the tests). If the message doesn't start with "proto",
|
||
// we expect a full string match.
|
||
if strings.HasPrefix(tc.err.Message, "proto:") {
|
||
assert.True(t, strings.Contains(ae.Message, "syntax error"))
|
||
} else {
|
||
assert.Equal(t, tc.err.Message, ae.Message)
|
||
}
|
||
|
||
return
|
||
}
|
||
|
||
body, err := io.ReadAll(res.Body)
|
||
assert.NoError(t, err)
|
||
res.Body.Close()
|
||
response := DeleteResponse{}
|
||
assert.NoError(t, json.Unmarshal(bytes.TrimSpace(body), &response))
|
||
assert.Equal(t, "ok", response.Status)
|
||
assert.Equal(t, []string{"application/json"}, res.Header["Content-Type"])
|
||
})
|
||
}
|
||
}
|
||
|
||
func TestWebhookAdminResponder_UpdateProvisionerWebhook(t *testing.T) {
|
||
type test struct {
|
||
auth adminAuthority
|
||
adminDB admin.DB
|
||
body []byte
|
||
ctx context.Context
|
||
err *admin.Error
|
||
response *linkedca.Webhook
|
||
statusCode int
|
||
}
|
||
var tests = map[string]func(t *testing.T) test{
|
||
"fail/not-found": func(t *testing.T) test {
|
||
prov := &linkedca.Provisioner{
|
||
Name: "provName",
|
||
Webhooks: []*linkedca.Webhook{{Name: "exists", Url: "https://example.com", Kind: linkedca.Webhook_ENRICHING}},
|
||
}
|
||
ctx := linkedca.NewContextWithProvisioner(context.Background(), prov)
|
||
err := admin.NewError(admin.ErrorNotFoundType, `provisioner "provName" has no webhook with the name "no-exists"`)
|
||
err.Message = `provisioner "provName" has no webhook with the name "no-exists"`
|
||
body := []byte(`
|
||
{
|
||
"name": "no-exists",
|
||
"url": "https://example.com",
|
||
"kind": "ENRICHING"
|
||
}`)
|
||
return test{
|
||
ctx: ctx,
|
||
adminDB: &admin.MockDB{},
|
||
body: body,
|
||
err: err,
|
||
statusCode: 404,
|
||
}
|
||
},
|
||
"fail/read.ProtoJSON": func(t *testing.T) test {
|
||
prov := &linkedca.Provisioner{
|
||
Name: "provName",
|
||
Webhooks: []*linkedca.Webhook{{Name: "my-webhook", Url: "https://example.com", Kind: linkedca.Webhook_ENRICHING}},
|
||
}
|
||
ctx := linkedca.NewContextWithProvisioner(context.Background(), prov)
|
||
adminErr := admin.NewError(admin.ErrorBadRequestType, "proto: syntax error (line 1:2): invalid value ?")
|
||
adminErr.Message = "proto: syntax error (line 1:2): invalid value ?"
|
||
body := []byte("{?}")
|
||
return test{
|
||
ctx: ctx,
|
||
adminDB: &admin.MockDB{},
|
||
body: body,
|
||
err: adminErr,
|
||
statusCode: 400,
|
||
}
|
||
},
|
||
"fail/missing-name": func(t *testing.T) test {
|
||
prov := &linkedca.Provisioner{
|
||
Name: "provName",
|
||
Webhooks: []*linkedca.Webhook{{Name: "my-webhook", Url: "https://example.com", Kind: linkedca.Webhook_ENRICHING}},
|
||
}
|
||
ctx := linkedca.NewContextWithProvisioner(context.Background(), prov)
|
||
adminErr := admin.NewError(admin.ErrorBadRequestType, "webhook name is required")
|
||
adminErr.Message = "webhook name is required"
|
||
body := []byte(`{"url": "https://example.com", "kind": "ENRICHING"}`)
|
||
return test{
|
||
ctx: ctx,
|
||
adminDB: &admin.MockDB{},
|
||
body: body,
|
||
err: adminErr,
|
||
statusCode: 400,
|
||
}
|
||
},
|
||
"fail/missing-url": func(t *testing.T) test {
|
||
prov := &linkedca.Provisioner{
|
||
Name: "provName",
|
||
Webhooks: []*linkedca.Webhook{{Name: "my-webhook", Url: "https://example.com", Kind: linkedca.Webhook_ENRICHING}},
|
||
}
|
||
ctx := linkedca.NewContextWithProvisioner(context.Background(), prov)
|
||
adminErr := admin.NewError(admin.ErrorBadRequestType, "webhook url is invalid")
|
||
adminErr.Message = "webhook url is invalid"
|
||
body := []byte(`{"name": "metadata", "kind": "ENRICHING"}`)
|
||
return test{
|
||
ctx: ctx,
|
||
adminDB: &admin.MockDB{},
|
||
body: body,
|
||
err: adminErr,
|
||
statusCode: 400,
|
||
}
|
||
},
|
||
"fail/relative-url": func(t *testing.T) test {
|
||
prov := &linkedca.Provisioner{
|
||
Name: "provName",
|
||
Webhooks: []*linkedca.Webhook{{Name: "my-webhook", Url: "https://example.com", Kind: linkedca.Webhook_ENRICHING}},
|
||
}
|
||
ctx := linkedca.NewContextWithProvisioner(context.Background(), prov)
|
||
adminErr := admin.NewError(admin.ErrorBadRequestType, "webhook url is invalid")
|
||
adminErr.Message = "webhook url is invalid"
|
||
body := []byte(`{"name": "metadata", "url": "example.com/path", "kind": "ENRICHING"}`)
|
||
return test{
|
||
ctx: ctx,
|
||
adminDB: &admin.MockDB{},
|
||
body: body,
|
||
err: adminErr,
|
||
statusCode: 400,
|
||
}
|
||
},
|
||
"fail/http-url": func(t *testing.T) test {
|
||
prov := &linkedca.Provisioner{
|
||
Name: "provName",
|
||
Webhooks: []*linkedca.Webhook{{Name: "my-webhook", Url: "https://example.com", Kind: linkedca.Webhook_ENRICHING}},
|
||
}
|
||
ctx := linkedca.NewContextWithProvisioner(context.Background(), prov)
|
||
adminErr := admin.NewError(admin.ErrorBadRequestType, "webhook url must use https")
|
||
adminErr.Message = "webhook url must use https"
|
||
body := []byte(`{"name": "metadata", "url": "http://example.com", "kind": "ENRICHING"}`)
|
||
return test{
|
||
ctx: ctx,
|
||
adminDB: &admin.MockDB{},
|
||
body: body,
|
||
err: adminErr,
|
||
statusCode: 400,
|
||
}
|
||
},
|
||
"fail/basic-auth-in-url": func(t *testing.T) test {
|
||
prov := &linkedca.Provisioner{
|
||
Name: "provName",
|
||
Webhooks: []*linkedca.Webhook{{Name: "my-webhook", Url: "https://example.com", Kind: linkedca.Webhook_ENRICHING}},
|
||
}
|
||
ctx := linkedca.NewContextWithProvisioner(context.Background(), prov)
|
||
adminErr := admin.NewError(admin.ErrorBadRequestType, "webhook url may not contain username or password")
|
||
adminErr.Message = "webhook url may not contain username or password"
|
||
body := []byte(`
|
||
{
|
||
"name": "my-webhook",
|
||
"url": "https://user:pass@example.com",
|
||
"kind": "ENRICHING"
|
||
}`)
|
||
return test{
|
||
ctx: ctx,
|
||
adminDB: &admin.MockDB{},
|
||
body: body,
|
||
err: adminErr,
|
||
statusCode: 400,
|
||
}
|
||
},
|
||
"fail/different-secret-in-request": func(t *testing.T) test {
|
||
prov := &linkedca.Provisioner{
|
||
Name: "provName",
|
||
Webhooks: []*linkedca.Webhook{{Name: "my-webhook", Url: "https://example.com", Kind: linkedca.Webhook_ENRICHING, Secret: "c2VjcmV0"}},
|
||
}
|
||
ctx := linkedca.NewContextWithProvisioner(context.Background(), prov)
|
||
adminErr := admin.NewError(admin.ErrorBadRequestType, "webhook secret cannot be updated")
|
||
adminErr.Message = "webhook secret cannot be updated"
|
||
body := []byte(`
|
||
{
|
||
"name": "my-webhook",
|
||
"url": "https://example.com",
|
||
"kind": "ENRICHING",
|
||
"secret": "secret"
|
||
}`)
|
||
return test{
|
||
ctx: ctx,
|
||
body: body,
|
||
err: adminErr,
|
||
statusCode: 400,
|
||
}
|
||
},
|
||
"fail/auth.UpdateProvisioner-error": func(t *testing.T) test {
|
||
prov := &linkedca.Provisioner{
|
||
Name: "provName",
|
||
Webhooks: []*linkedca.Webhook{{Name: "my-webhook", Url: "https://example.com", Kind: linkedca.Webhook_ENRICHING}},
|
||
}
|
||
ctx := linkedca.NewContextWithProvisioner(context.Background(), prov)
|
||
adminErr := admin.NewError(admin.ErrorServerInternalType, "error updating provisioner webhook: force")
|
||
adminErr.Message = "error updating provisioner webhook: force"
|
||
body := []byte(`{"name": "my-webhook", "url": "https://example.com", "kind": "ENRICHING"}`)
|
||
return test{
|
||
ctx: ctx,
|
||
adminDB: &admin.MockDB{},
|
||
auth: &mockAdminAuthority{
|
||
MockUpdateProvisioner: func(ctx context.Context, nu *linkedca.Provisioner) error {
|
||
return &authority.PolicyError{
|
||
Typ: authority.StoreFailure,
|
||
Err: errors.New("force"),
|
||
}
|
||
},
|
||
},
|
||
body: body,
|
||
err: adminErr,
|
||
statusCode: 500,
|
||
}
|
||
},
|
||
"ok": func(t *testing.T) test {
|
||
prov := &linkedca.Provisioner{
|
||
Name: "provName",
|
||
Webhooks: []*linkedca.Webhook{{Name: "my-webhook", Url: "https://example.com", Kind: linkedca.Webhook_ENRICHING}},
|
||
}
|
||
ctx := linkedca.NewContextWithProvisioner(context.Background(), prov)
|
||
body := []byte(`{"name": "my-webhook", "url": "https://example.com", "kind": "ENRICHING"}`)
|
||
return test{
|
||
ctx: ctx,
|
||
adminDB: &admin.MockDB{},
|
||
auth: &mockAdminAuthority{
|
||
MockUpdateProvisioner: func(ctx context.Context, nu *linkedca.Provisioner) error {
|
||
return nil
|
||
},
|
||
},
|
||
body: body,
|
||
response: &linkedca.Webhook{
|
||
Name: "my-webhook",
|
||
Url: "https://example.com",
|
||
Kind: linkedca.Webhook_ENRICHING,
|
||
},
|
||
statusCode: 201,
|
||
}
|
||
},
|
||
}
|
||
for name, prep := range tests {
|
||
tc := prep(t)
|
||
t.Run(name, func(t *testing.T) {
|
||
mockMustAuthority(t, tc.auth)
|
||
ctx := admin.NewContext(tc.ctx, tc.adminDB)
|
||
war := NewWebhookAdminResponder()
|
||
|
||
req := httptest.NewRequest("PUT", "/foo", io.NopCloser(bytes.NewBuffer(tc.body)))
|
||
req = req.WithContext(ctx)
|
||
w := httptest.NewRecorder()
|
||
|
||
war.UpdateProvisionerWebhook(w, req)
|
||
res := w.Result()
|
||
|
||
assert.Equal(t, tc.statusCode, res.StatusCode)
|
||
|
||
if res.StatusCode >= 400 {
|
||
|
||
body, err := io.ReadAll(res.Body)
|
||
res.Body.Close()
|
||
assert.NoError(t, err)
|
||
|
||
ae := testAdminError{}
|
||
assert.NoError(t, json.Unmarshal(bytes.TrimSpace(body), &ae))
|
||
|
||
assert.Equal(t, tc.err.Type, ae.Type)
|
||
assert.Equal(t, tc.err.StatusCode(), res.StatusCode)
|
||
assert.Equal(t, tc.err.Detail, ae.Detail)
|
||
assert.Equal(t, []string{"application/json"}, res.Header["Content-Type"])
|
||
|
||
// when the error message starts with "proto", we expect it to have
|
||
// a syntax error (in the tests). If the message doesn't start with "proto",
|
||
// we expect a full string match.
|
||
if strings.HasPrefix(tc.err.Message, "proto:") {
|
||
assert.True(t, strings.Contains(ae.Message, "syntax error"))
|
||
} else {
|
||
assert.Equal(t, tc.err.Message, ae.Message)
|
||
}
|
||
|
||
return
|
||
}
|
||
|
||
resp := &linkedca.Webhook{}
|
||
body, err := io.ReadAll(res.Body)
|
||
assert.NoError(t, err)
|
||
assert.NoError(t, protojson.Unmarshal(body, resp))
|
||
|
||
assertEqualWebhook(t, tc.response, resp)
|
||
})
|
||
}
|
||
}
|