diff --git a/authority/admin/api/acme.go b/authority/admin/api/acme.go index 01d706ab..f671059e 100644 --- a/authority/admin/api/acme.go +++ b/authority/admin/api/acme.go @@ -1,17 +1,13 @@ package api import ( - "context" "fmt" "net/http" - "github.com/go-chi/chi" - "go.step.sm/linkedca" "github.com/smallstep/certificates/api/render" "github.com/smallstep/certificates/authority/admin" - "github.com/smallstep/certificates/authority/provisioner" ) // CreateExternalAccountKeyRequest is the type for POST /admin/acme/eab requests @@ -38,50 +34,29 @@ type GetExternalAccountKeysResponse struct { func (h *Handler) requireEABEnabled(next http.HandlerFunc) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { ctx := r.Context() - provName := chi.URLParam(r, "provisionerName") - eabEnabled, prov, err := h.provisionerHasEABEnabled(ctx, provName) - if err != nil { - render.Error(w, err) + prov := linkedca.ProvisionerFromContext(ctx) + + details := prov.GetDetails() + if details == nil { + render.Error(w, admin.NewErrorISE("error getting details for provisioner '%s'", prov.GetName())) return } - if !eabEnabled { - render.Error(w, admin.NewError(admin.ErrorBadRequestType, "ACME EAB not enabled for provisioner %s", prov.GetName())) + + acmeProvisioner := details.GetACME() + if acmeProvisioner == nil { + render.Error(w, admin.NewErrorISE("error getting ACME details for provisioner '%s'", prov.GetName())) return } - ctx = linkedca.NewContextWithProvisioner(ctx, prov) + + if !acmeProvisioner.RequireEab { + render.Error(w, admin.NewError(admin.ErrorBadRequestType, "ACME EAB not enabled for provisioner '%s'", prov.GetName())) + return + } + next(w, r.WithContext(ctx)) } } -// provisionerHasEABEnabled determines if the "requireEAB" setting for an ACME -// provisioner is set to true and thus has EAB enabled. -func (h *Handler) provisionerHasEABEnabled(ctx context.Context, provisionerName string) (bool, *linkedca.Provisioner, error) { - var ( - p provisioner.Interface - err error - ) - if p, err = h.auth.LoadProvisionerByName(provisionerName); err != nil { - return false, nil, admin.WrapErrorISE(err, "error loading provisioner %s", provisionerName) - } - - prov, err := h.adminDB.GetProvisioner(ctx, p.GetID()) - if err != nil { - return false, nil, admin.WrapErrorISE(err, "error getting provisioner with ID: %s", p.GetID()) - } - - details := prov.GetDetails() - if details == nil { - return false, nil, admin.NewErrorISE("error getting details for provisioner with ID: %s", p.GetID()) - } - - acmeProvisioner := details.GetACME() - if acmeProvisioner == nil { - return false, nil, admin.NewErrorISE("error getting ACME details for provisioner with ID: %s", p.GetID()) - } - - return acmeProvisioner.GetRequireEab(), prov, nil -} - type acmeAdminResponderInterface interface { GetExternalAccountKeys(w http.ResponseWriter, r *http.Request) CreateExternalAccountKey(w http.ResponseWriter, r *http.Request) diff --git a/authority/admin/api/acme_test.go b/authority/admin/api/acme_test.go index 5c61656d..2c7bbd37 100644 --- a/authority/admin/api/acme_test.go +++ b/authority/admin/api/acme_test.go @@ -4,7 +4,6 @@ import ( "bytes" "context" "encoding/json" - "errors" "io" "net/http" "net/http/httptest" @@ -20,7 +19,6 @@ import ( "github.com/smallstep/assert" "github.com/smallstep/certificates/authority/admin" - "github.com/smallstep/certificates/authority/provisioner" ) func readProtoJSON(r io.ReadCloser, m proto.Message) error { @@ -35,106 +33,76 @@ func readProtoJSON(r io.ReadCloser, m proto.Message) error { func TestHandler_requireEABEnabled(t *testing.T) { type test struct { ctx context.Context - adminDB admin.DB - auth adminAuthority next http.HandlerFunc err *admin.Error statusCode int } var tests = map[string]func(t *testing.T) test{ - "fail/h.provisionerHasEABEnabled": func(t *testing.T) test { - chiCtx := chi.NewRouteContext() - chiCtx.URLParams.Add("provisionerName", "provName") - ctx := context.WithValue(context.Background(), chi.RouteCtxKey, chiCtx) - auth := &mockAdminAuthority{ - MockLoadProvisionerByName: func(name string) (provisioner.Interface, error) { - assert.Equals(t, "provName", name) - return nil, errors.New("force") - }, + "fail/prov.GetDetails": func(t *testing.T) test { + prov := &linkedca.Provisioner{ + Id: "provID", + Name: "provName", } - err := admin.NewErrorISE("error loading provisioner provName: force") - err.Message = "error loading provisioner provName: force" + ctx := linkedca.NewContextWithProvisioner(context.Background(), prov) + err := admin.NewErrorISE("error getting details for provisioner 'provName'") + err.Message = "error getting details for provisioner 'provName'" + return test{ + ctx: ctx, + err: err, + statusCode: 500, + } + }, + "fail/details.GetACME": func(t *testing.T) test { + prov := &linkedca.Provisioner{ + Id: "provID", + Name: "provName", + Details: &linkedca.ProvisionerDetails{}, + } + ctx := linkedca.NewContextWithProvisioner(context.Background(), prov) + err := admin.NewErrorISE("error getting ACME details for provisioner 'provName'") + err.Message = "error getting ACME details for provisioner 'provName'" return test{ ctx: ctx, - auth: auth, err: err, statusCode: 500, } }, "ok/eab-disabled": func(t *testing.T) test { - chiCtx := chi.NewRouteContext() - chiCtx.URLParams.Add("provisionerName", "provName") - ctx := context.WithValue(context.Background(), chi.RouteCtxKey, chiCtx) - auth := &mockAdminAuthority{ - MockLoadProvisionerByName: func(name string) (provisioner.Interface, error) { - assert.Equals(t, "provName", name) - return &provisioner.MockProvisioner{ - MgetID: func() string { - return "provID" + prov := &linkedca.Provisioner{ + Id: "provID", + Name: "provName", + Details: &linkedca.ProvisionerDetails{ + Data: &linkedca.ProvisionerDetails_ACME{ + ACME: &linkedca.ACMEProvisioner{ + RequireEab: false, }, - }, nil - }, - } - db := &admin.MockDB{ - MockGetProvisioner: func(ctx context.Context, id string) (*linkedca.Provisioner, error) { - assert.Equals(t, "provID", id) - return &linkedca.Provisioner{ - Id: "provID", - Name: "provName", - Details: &linkedca.ProvisionerDetails{ - Data: &linkedca.ProvisionerDetails_ACME{ - ACME: &linkedca.ACMEProvisioner{ - RequireEab: false, - }, - }, - }, - }, nil + }, }, } + ctx := linkedca.NewContextWithProvisioner(context.Background(), prov) err := admin.NewError(admin.ErrorBadRequestType, "ACME EAB not enabled for provisioner provName") - err.Message = "ACME EAB not enabled for provisioner provName" + err.Message = "ACME EAB not enabled for provisioner 'provName'" return test{ ctx: ctx, - auth: auth, - adminDB: db, err: err, statusCode: 400, } }, "ok/eab-enabled": func(t *testing.T) test { - chiCtx := chi.NewRouteContext() - chiCtx.URLParams.Add("provisionerName", "provName") - ctx := context.WithValue(context.Background(), chi.RouteCtxKey, chiCtx) - auth := &mockAdminAuthority{ - MockLoadProvisionerByName: func(name string) (provisioner.Interface, error) { - assert.Equals(t, "provName", name) - return &provisioner.MockProvisioner{ - MgetID: func() string { - return "provID" + prov := &linkedca.Provisioner{ + Id: "provID", + Name: "provName", + Details: &linkedca.ProvisionerDetails{ + Data: &linkedca.ProvisionerDetails_ACME{ + ACME: &linkedca.ACMEProvisioner{ + RequireEab: true, }, - }, nil - }, - } - db := &admin.MockDB{ - MockGetProvisioner: func(ctx context.Context, id string) (*linkedca.Provisioner, error) { - assert.Equals(t, "provID", id) - return &linkedca.Provisioner{ - Id: "provID", - Name: "provName", - Details: &linkedca.ProvisionerDetails{ - Data: &linkedca.ProvisionerDetails_ACME{ - ACME: &linkedca.ACMEProvisioner{ - RequireEab: true, - }, - }, - }, - }, nil + }, }, } + ctx := linkedca.NewContextWithProvisioner(context.Background(), prov) return test{ - ctx: ctx, - auth: auth, - adminDB: db, + ctx: ctx, next: func(w http.ResponseWriter, r *http.Request) { w.Write(nil) // mock response with status 200 }, @@ -146,13 +114,9 @@ func TestHandler_requireEABEnabled(t *testing.T) { for name, prep := range tests { tc := prep(t) t.Run(name, func(t *testing.T) { - h := &Handler{ - auth: tc.auth, - adminDB: tc.adminDB, - acmeDB: nil, - } + h := &Handler{} - req := httptest.NewRequest("GET", "/foo", nil) // chi routing is prepared in test setup + req := httptest.NewRequest("GET", "/foo", nil) req = req.WithContext(tc.ctx) w := httptest.NewRecorder() h.requireEABEnabled(tc.next)(w, req) @@ -179,216 +143,6 @@ func TestHandler_requireEABEnabled(t *testing.T) { } } -func TestHandler_provisionerHasEABEnabled(t *testing.T) { - type test struct { - adminDB admin.DB - auth adminAuthority - provisionerName string - want bool - err *admin.Error - } - var tests = map[string]func(t *testing.T) test{ - "fail/auth.LoadProvisionerByName": func(t *testing.T) test { - auth := &mockAdminAuthority{ - MockLoadProvisionerByName: func(name string) (provisioner.Interface, error) { - assert.Equals(t, "provName", name) - return nil, errors.New("force") - }, - } - return test{ - auth: auth, - provisionerName: "provName", - want: false, - err: admin.WrapErrorISE(errors.New("force"), "error loading provisioner provName"), - } - }, - "fail/db.GetProvisioner": func(t *testing.T) test { - auth := &mockAdminAuthority{ - MockLoadProvisionerByName: func(name string) (provisioner.Interface, error) { - assert.Equals(t, "provName", name) - return &provisioner.MockProvisioner{ - MgetID: func() string { - return "provID" - }, - }, nil - }, - } - db := &admin.MockDB{ - MockGetProvisioner: func(ctx context.Context, id string) (*linkedca.Provisioner, error) { - assert.Equals(t, "provID", id) - return nil, errors.New("force") - }, - } - return test{ - auth: auth, - adminDB: db, - provisionerName: "provName", - want: false, - err: admin.WrapErrorISE(errors.New("force"), "error loading provisioner provName"), - } - }, - "fail/prov.GetDetails": func(t *testing.T) test { - auth := &mockAdminAuthority{ - MockLoadProvisionerByName: func(name string) (provisioner.Interface, error) { - assert.Equals(t, "provName", name) - return &provisioner.MockProvisioner{ - MgetID: func() string { - return "provID" - }, - }, nil - }, - } - db := &admin.MockDB{ - MockGetProvisioner: func(ctx context.Context, id string) (*linkedca.Provisioner, error) { - assert.Equals(t, "provID", id) - return &linkedca.Provisioner{ - Id: "provID", - Name: "provName", - Details: nil, - }, nil - }, - } - return test{ - auth: auth, - adminDB: db, - provisionerName: "provName", - want: false, - err: admin.WrapErrorISE(errors.New("force"), "error loading provisioner provName"), - } - }, - "fail/details.GetACME": func(t *testing.T) test { - auth := &mockAdminAuthority{ - MockLoadProvisionerByName: func(name string) (provisioner.Interface, error) { - assert.Equals(t, "provName", name) - return &provisioner.MockProvisioner{ - MgetID: func() string { - return "provID" - }, - }, nil - }, - } - db := &admin.MockDB{ - MockGetProvisioner: func(ctx context.Context, id string) (*linkedca.Provisioner, error) { - assert.Equals(t, "provID", id) - return &linkedca.Provisioner{ - Id: "provID", - Name: "provName", - Details: &linkedca.ProvisionerDetails{ - Data: &linkedca.ProvisionerDetails_ACME{ - ACME: nil, - }, - }, - }, nil - }, - } - return test{ - auth: auth, - adminDB: db, - provisionerName: "provName", - want: false, - err: admin.WrapErrorISE(errors.New("force"), "error loading provisioner provName"), - } - }, - "ok/eab-disabled": func(t *testing.T) test { - auth := &mockAdminAuthority{ - MockLoadProvisionerByName: func(name string) (provisioner.Interface, error) { - assert.Equals(t, "eab-disabled", name) - return &provisioner.MockProvisioner{ - MgetID: func() string { - return "provID" - }, - }, nil - }, - } - db := &admin.MockDB{ - MockGetProvisioner: func(ctx context.Context, id string) (*linkedca.Provisioner, error) { - assert.Equals(t, "provID", id) - return &linkedca.Provisioner{ - Id: "provID", - Name: "eab-disabled", - Details: &linkedca.ProvisionerDetails{ - Data: &linkedca.ProvisionerDetails_ACME{ - ACME: &linkedca.ACMEProvisioner{ - RequireEab: false, - }, - }, - }, - }, nil - }, - } - return test{ - adminDB: db, - auth: auth, - provisionerName: "eab-disabled", - want: false, - } - }, - "ok/eab-enabled": func(t *testing.T) test { - auth := &mockAdminAuthority{ - MockLoadProvisionerByName: func(name string) (provisioner.Interface, error) { - assert.Equals(t, "eab-enabled", name) - return &provisioner.MockProvisioner{ - MgetID: func() string { - return "provID" - }, - }, nil - }, - } - db := &admin.MockDB{ - MockGetProvisioner: func(ctx context.Context, id string) (*linkedca.Provisioner, error) { - assert.Equals(t, "provID", id) - return &linkedca.Provisioner{ - Id: "provID", - Name: "eab-enabled", - Details: &linkedca.ProvisionerDetails{ - Data: &linkedca.ProvisionerDetails_ACME{ - ACME: &linkedca.ACMEProvisioner{ - RequireEab: true, - }, - }, - }, - }, nil - }, - } - return test{ - adminDB: db, - auth: auth, - provisionerName: "eab-enabled", - want: true, - } - }, - } - for name, prep := range tests { - tc := prep(t) - t.Run(name, func(t *testing.T) { - h := &Handler{ - auth: tc.auth, - adminDB: tc.adminDB, - acmeDB: nil, - } - got, prov, err := h.provisionerHasEABEnabled(context.TODO(), tc.provisionerName) - if (err != nil) != (tc.err != nil) { - t.Errorf("Handler.provisionerHasEABEnabled() error = %v, want err %v", err, tc.err) - return - } - if tc.err != nil { - assert.Type(t, &linkedca.Provisioner{}, prov) - assert.Type(t, &admin.Error{}, err) - adminError, _ := err.(*admin.Error) - assert.Equals(t, tc.err.Type, adminError.Type) - assert.Equals(t, tc.err.Status, adminError.Status) - assert.Equals(t, tc.err.StatusCode(), adminError.StatusCode()) - assert.Equals(t, tc.err.Message, adminError.Message) - assert.Equals(t, tc.err.Detail, adminError.Detail) - return - } - if got != tc.want { - t.Errorf("Handler.provisionerHasEABEnabled() = %v, want %v", got, tc.want) - } - }) - } -} - func TestCreateExternalAccountKeyRequest_Validate(t *testing.T) { type fields struct { Reference string diff --git a/authority/admin/api/handler.go b/authority/admin/api/handler.go index aa7b6300..c8ad316b 100644 --- a/authority/admin/api/handler.go +++ b/authority/admin/api/handler.go @@ -62,10 +62,10 @@ func (h *Handler) Route(r api.Router) { r.MethodFunc("DELETE", "/admins/{id}", authnz(h.DeleteAdmin)) // ACME External Account Binding Keys - r.MethodFunc("GET", "/acme/eab/{provisionerName}/{reference}", authnz(requireEABEnabled(h.acmeResponder.GetExternalAccountKeys))) - r.MethodFunc("GET", "/acme/eab/{provisionerName}", authnz(requireEABEnabled(h.acmeResponder.GetExternalAccountKeys))) - r.MethodFunc("POST", "/acme/eab/{provisionerName}", authnz(requireEABEnabled(h.acmeResponder.CreateExternalAccountKey))) - r.MethodFunc("DELETE", "/acme/eab/{provisionerName}/{id}", authnz(requireEABEnabled(h.acmeResponder.DeleteExternalAccountKey))) + r.MethodFunc("GET", "/acme/eab/{provisionerName}/{reference}", authnz(h.loadProvisionerByName(requireEABEnabled(h.acmeResponder.GetExternalAccountKeys)))) + r.MethodFunc("GET", "/acme/eab/{provisionerName}", authnz(h.loadProvisionerByName(requireEABEnabled(h.acmeResponder.GetExternalAccountKeys)))) + r.MethodFunc("POST", "/acme/eab/{provisionerName}", authnz(h.loadProvisionerByName(requireEABEnabled(h.acmeResponder.CreateExternalAccountKey)))) + r.MethodFunc("DELETE", "/acme/eab/{provisionerName}/{id}", authnz(h.loadProvisionerByName(requireEABEnabled(h.acmeResponder.DeleteExternalAccountKey)))) // Policy - Authority r.MethodFunc("GET", "/policy", authnz(enabledInStandalone(h.policyResponder.GetAuthorityPolicy))) @@ -74,16 +74,14 @@ func (h *Handler) Route(r api.Router) { r.MethodFunc("DELETE", "/policy", authnz(enabledInStandalone(h.policyResponder.DeleteAuthorityPolicy))) // Policy - Provisioner - //r.MethodFunc("GET", "/provisioners/{name}/policy", noauth(h.policyResponder.GetProvisionerPolicy)) r.MethodFunc("GET", "/provisioners/{provisionerName}/policy", authnz(disabledInStandalone(h.loadProvisionerByName(h.policyResponder.GetProvisionerPolicy)))) r.MethodFunc("POST", "/provisioners/{provisionerName}/policy", authnz(disabledInStandalone(h.loadProvisionerByName(h.policyResponder.CreateProvisionerPolicy)))) r.MethodFunc("PUT", "/provisioners/{provisionerName}/policy", authnz(disabledInStandalone(h.loadProvisionerByName(h.policyResponder.UpdateProvisionerPolicy)))) r.MethodFunc("DELETE", "/provisioners/{provisionerName}/policy", authnz(disabledInStandalone(h.loadProvisionerByName(h.policyResponder.DeleteProvisionerPolicy)))) // Policy - ACME Account - // TODO: ensure we don't clash with eab; might want to change eab paths slightly (as long as we don't have it released completely; needs changes in adminClient too) - r.MethodFunc("GET", "/acme/{provisionerName}/{accountID}/policy", authnz(disabledInStandalone(h.policyResponder.GetACMEAccountPolicy))) - r.MethodFunc("POST", "/acme/{provisionerName}/{accountID}/policy", authnz(disabledInStandalone(h.policyResponder.CreateACMEAccountPolicy))) - r.MethodFunc("PUT", "/acme/{provisionerName}/{accountID}/policy", authnz(disabledInStandalone(h.policyResponder.UpdateACMEAccountPolicy))) - r.MethodFunc("DELETE", "/acme/{provisionerName}/{accountID}/policy", authnz(disabledInStandalone(h.policyResponder.DeleteACMEAccountPolicy))) + r.MethodFunc("GET", "/acme/policy/{provisionerName}/{accountID}", authnz(disabledInStandalone(h.loadProvisionerByName(h.requireEABEnabled(h.policyResponder.GetACMEAccountPolicy))))) + r.MethodFunc("POST", "/acme/policy/{provisionerName}/{accountID}", authnz(disabledInStandalone(h.loadProvisionerByName(h.requireEABEnabled(h.policyResponder.CreateACMEAccountPolicy))))) + r.MethodFunc("PUT", "/acme/policy/{provisionerName}/{accountID}", authnz(disabledInStandalone(h.loadProvisionerByName(h.requireEABEnabled(h.policyResponder.UpdateACMEAccountPolicy))))) + r.MethodFunc("DELETE", "/acme/policy/{provisionerName}/{accountID}", authnz(disabledInStandalone(h.loadProvisionerByName(h.requireEABEnabled(h.policyResponder.DeleteACMEAccountPolicy))))) } diff --git a/authority/admin/api/middleware.go b/authority/admin/api/middleware.go index 01d6d61f..98477a5e 100644 --- a/authority/admin/api/middleware.go +++ b/authority/admin/api/middleware.go @@ -59,6 +59,8 @@ func (h *Handler) loadProvisionerByName(next http.HandlerFunc) http.HandlerFunc p provisioner.Interface err error ) + + // TODO(hs): distinguish 404 vs. 500 if p, err = h.auth.LoadProvisionerByName(name); err != nil { render.Error(w, admin.WrapErrorISE(err, "error loading provisioner %s", name)) return @@ -66,7 +68,7 @@ func (h *Handler) loadProvisionerByName(next http.HandlerFunc) http.HandlerFunc prov, err := h.adminDB.GetProvisioner(ctx, p.GetID()) if err != nil { - render.Error(w, err) + render.Error(w, admin.WrapErrorISE(err, "error retrieving provisioner %s", name)) return } diff --git a/authority/admin/api/middleware_test.go b/authority/admin/api/middleware_test.go index 54732dc6..c7314e71 100644 --- a/authority/admin/api/middleware_test.go +++ b/authority/admin/api/middleware_test.go @@ -4,12 +4,14 @@ import ( "bytes" "context" "encoding/json" + "errors" "io" "net/http" "net/http/httptest" "testing" "time" + "github.com/go-chi/chi" "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "google.golang.org/protobuf/types/known/timestamppb" @@ -18,6 +20,7 @@ import ( "github.com/smallstep/assert" "github.com/smallstep/certificates/authority/admin" + "github.com/smallstep/certificates/authority/provisioner" ) func TestHandler_requireAPIEnabled(t *testing.T) { @@ -220,3 +223,136 @@ func TestHandler_extractAuthorizeTokenAdmin(t *testing.T) { }) } } + +func TestHandler_loadProvisionerByName(t *testing.T) { + type test struct { + adminDB admin.DB + auth adminAuthority + ctx context.Context + next http.HandlerFunc + err *admin.Error + statusCode int + } + var tests = map[string]func(t *testing.T) test{ + "fail/auth.LoadProvisionerByName": func(t *testing.T) test { + chiCtx := chi.NewRouteContext() + chiCtx.URLParams.Add("provisionerName", "provName") + ctx := context.WithValue(context.Background(), chi.RouteCtxKey, chiCtx) + auth := &mockAdminAuthority{ + MockLoadProvisionerByName: func(name string) (provisioner.Interface, error) { + assert.Equals(t, "provName", name) + return nil, errors.New("force") + }, + } + err := admin.WrapErrorISE(errors.New("force"), "error loading provisioner provName") + err.Message = "error loading provisioner provName: force" + return test{ + ctx: ctx, + auth: auth, + statusCode: 500, + err: err, + } + }, + "fail/db.GetProvisioner": func(t *testing.T) test { + chiCtx := chi.NewRouteContext() + chiCtx.URLParams.Add("provisionerName", "provName") + ctx := context.WithValue(context.Background(), chi.RouteCtxKey, chiCtx) + auth := &mockAdminAuthority{ + MockLoadProvisionerByName: func(name string) (provisioner.Interface, error) { + assert.Equals(t, "provName", name) + return &provisioner.MockProvisioner{ + MgetID: func() string { + return "provID" + }, + }, nil + }, + } + db := &admin.MockDB{ + MockGetProvisioner: func(ctx context.Context, id string) (*linkedca.Provisioner, error) { + assert.Equals(t, "provID", id) + return nil, errors.New("force") + }, + } + err := admin.WrapErrorISE(errors.New("force"), "error retrieving provisioner provName") + err.Message = "error retrieving provisioner provName: force" + return test{ + ctx: ctx, + auth: auth, + adminDB: db, + statusCode: 500, + err: err, + } + }, + "ok": func(t *testing.T) test { + chiCtx := chi.NewRouteContext() + chiCtx.URLParams.Add("provisionerName", "provName") + ctx := context.WithValue(context.Background(), chi.RouteCtxKey, chiCtx) + auth := &mockAdminAuthority{ + MockLoadProvisionerByName: func(name string) (provisioner.Interface, error) { + assert.Equals(t, "provName", name) + return &provisioner.MockProvisioner{ + MgetID: func() string { + return "provID" + }, + }, nil + }, + } + db := &admin.MockDB{ + MockGetProvisioner: func(ctx context.Context, id string) (*linkedca.Provisioner, error) { + assert.Equals(t, "provID", id) + return &linkedca.Provisioner{ + Id: "provID", + Name: "provName", + }, nil + }, + } + return test{ + ctx: ctx, + auth: auth, + adminDB: db, + statusCode: 200, + next: func(w http.ResponseWriter, r *http.Request) { + prov := linkedca.ProvisionerFromContext(r.Context()) + assert.NotNil(t, prov) + assert.Equals(t, "provID", prov.GetId()) + assert.Equals(t, "provName", prov.GetName()) + w.Write(nil) // mock response with status 200 + }, + } + }, + } + for name, prep := range tests { + tc := prep(t) + t.Run(name, func(t *testing.T) { + h := &Handler{ + auth: tc.auth, + adminDB: tc.adminDB, + } + + req := httptest.NewRequest("GET", "/foo", nil) // chi routing is prepared in test setup + req = req.WithContext(tc.ctx) + + w := httptest.NewRecorder() + h.loadProvisionerByName(tc.next)(w, req) + res := w.Result() + + assert.Equals(t, tc.statusCode, res.StatusCode) + + body, err := io.ReadAll(res.Body) + res.Body.Close() + assert.FatalError(t, err) + + if res.StatusCode >= 400 { + err := admin.Error{} + assert.FatalError(t, json.Unmarshal(bytes.TrimSpace(body), &err)) + + assert.Equals(t, tc.err.Type, err.Type) + assert.Equals(t, tc.err.Message, err.Message) + assert.Equals(t, tc.err.StatusCode(), res.StatusCode) + assert.Equals(t, tc.err.Detail, err.Detail) + assert.Equals(t, []string{"application/json"}, res.Header["Content-Type"]) + return + } + }) + } +}