fix(api): return correct http code on assets api (#6388)

* fix(api): return correct http code on assets api

* add test

* fix test
This commit is contained in:
Livio Spring 2023-08-18 15:51:11 +02:00 committed by GitHub
parent 8b44794c75
commit 69b49ac0ed
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 195 additions and 10 deletions

View File

@ -42,7 +42,7 @@ func (h *Handler) Commands() *command.Commands {
} }
func (h *Handler) ErrorHandler() ErrorHandler { func (h *Handler) ErrorHandler() ErrorHandler {
return DefaultErrorHandler return h.errorHandler
} }
func (h *Handler) Storage() static.Storage { func (h *Handler) Storage() static.Storage {
@ -75,10 +75,14 @@ type Downloader interface {
ResourceOwner(ctx context.Context, ownerPath string) string ResourceOwner(ctx context.Context, ownerPath string) string
} }
type ErrorHandler func(http.ResponseWriter, *http.Request, error, int) type ErrorHandler func(w http.ResponseWriter, r *http.Request, err error, defaultCode int)
func DefaultErrorHandler(w http.ResponseWriter, r *http.Request, err error, code int) { func DefaultErrorHandler(w http.ResponseWriter, r *http.Request, err error, defaultCode int) {
logging.WithFields("uri", r.RequestURI).WithError(err).Warn("error occurred on asset api") logging.WithFields("uri", r.RequestURI).WithError(err).Warn("error occurred on asset api")
code, ok := http_util.ZitadelErrorToHTTPStatusCode(err)
if !ok {
code = defaultCode
}
http.Error(w, err.Error(), code) http.Error(w, err.Error(), code)
} }
@ -162,7 +166,7 @@ func UploadHandleFunc(s AssetsService, uploader Uploader) func(http.ResponseWrit
} }
err = uploader.UploadAsset(ctx, ctxData.OrgID, uploadInfo, s.Commands()) err = uploader.UploadAsset(ctx, ctxData.OrgID, uploadInfo, s.Commands())
if err != nil { if err != nil {
s.ErrorHandler()(w, r, fmt.Errorf("upload failed: %v", err), http.StatusInternalServerError) s.ErrorHandler()(w, r, fmt.Errorf("upload failed: %w", err), http.StatusInternalServerError)
return return
} }
} }
@ -190,10 +194,6 @@ func DownloadHandleFunc(s AssetsService, downloader Downloader) func(http.Respon
return return
} }
if err = GetAsset(w, r, resourceOwner, objectName, s.Storage()); err != nil { if err = GetAsset(w, r, resourceOwner, objectName, s.Storage()); err != nil {
if strings.Contains(err.Error(), "DATAB-pCP8P") {
s.ErrorHandler()(w, r, err, http.StatusNotFound)
return
}
s.ErrorHandler()(w, r, err, http.StatusInternalServerError) s.ErrorHandler()(w, r, err, http.StatusInternalServerError)
} }
} }
@ -206,11 +206,11 @@ func GetAsset(w http.ResponseWriter, r *http.Request, resourceOwner, objectName
} }
data, getInfo, err := storage.GetObject(r.Context(), authz.GetInstance(r.Context()).InstanceID(), resourceOwner, objectName) data, getInfo, err := storage.GetObject(r.Context(), authz.GetInstance(r.Context()).InstanceID(), resourceOwner, objectName)
if err != nil { if err != nil {
return fmt.Errorf("download failed: %v", err) return fmt.Errorf("download failed: %w", err)
} }
info, err := getInfo() info, err := getInfo()
if err != nil { if err != nil {
return fmt.Errorf("download failed: %v", err) return fmt.Errorf("download failed: %w", err)
} }
if info.Hash == strings.Trim(r.Header.Get(http_util.IfNoneMatch), "\"") { if info.Hash == strings.Trim(r.Header.Get(http_util.IfNoneMatch), "\"") {
w.Header().Set(http_util.LastModified, info.LastModified.Format(time.RFC1123)) w.Header().Set(http_util.LastModified, info.LastModified.Format(time.RFC1123))

View File

@ -0,0 +1,47 @@
package http
import (
"errors"
"net/http"
caos_errs "github.com/zitadel/zitadel/internal/errors"
)
func ZitadelErrorToHTTPStatusCode(err error) (statusCode int, ok bool) {
if err == nil {
return http.StatusOK, true
}
//nolint:errorlint
switch err.(type) {
case *caos_errs.AlreadyExistsError:
return http.StatusConflict, true
case *caos_errs.DeadlineExceededError:
return http.StatusGatewayTimeout, true
case *caos_errs.InternalError:
return http.StatusInternalServerError, true
case *caos_errs.InvalidArgumentError:
return http.StatusBadRequest, true
case *caos_errs.NotFoundError:
return http.StatusNotFound, true
case *caos_errs.PermissionDeniedError:
return http.StatusForbidden, true
case *caos_errs.PreconditionFailedError:
// use the same code as grpc-gateway:
// https://github.com/grpc-ecosystem/grpc-gateway/blob/9e33e38f15cb7d2f11096366e62ea391a3459ba9/runtime/errors.go#L59
return http.StatusBadRequest, true
case *caos_errs.UnauthenticatedError:
return http.StatusUnauthorized, true
case *caos_errs.UnavailableError:
return http.StatusServiceUnavailable, true
case *caos_errs.UnimplementedError:
return http.StatusNotImplemented, true
case *caos_errs.ResourceExhaustedError:
return http.StatusTooManyRequests, true
default:
c := new(caos_errs.CaosError)
if errors.As(err, &c) {
return ZitadelErrorToHTTPStatusCode(errors.Unwrap(err))
}
return http.StatusInternalServerError, false
}
}

View File

@ -0,0 +1,138 @@
package http
import (
"errors"
"fmt"
"net/http"
"testing"
caos_errors "github.com/zitadel/zitadel/internal/errors"
)
func TestZitadelErrorToHTTPStatusCode(t *testing.T) {
type args struct {
err error
}
tests := []struct {
name string
args args
wantStatusCode int
wantOk bool
}{
{
name: "no error",
args: args{
err: nil,
},
wantStatusCode: http.StatusOK,
wantOk: true,
},
{
name: "wrapped already exists",
args: args{
err: fmt.Errorf("wrapped %w", caos_errors.ThrowAlreadyExists(nil, "id", "message")),
},
wantStatusCode: http.StatusConflict,
wantOk: true,
},
{
name: "wrapped deadline exceeded",
args: args{
err: fmt.Errorf("wrapped %w", caos_errors.ThrowDeadlineExceeded(nil, "id", "message")),
},
wantStatusCode: http.StatusGatewayTimeout,
wantOk: true,
},
{
name: "wrapped internal",
args: args{
err: fmt.Errorf("wrapped %w", caos_errors.ThrowInternal(nil, "id", "message")),
},
wantStatusCode: http.StatusInternalServerError,
wantOk: true,
},
{
name: "wrapped invalid argument",
args: args{
err: fmt.Errorf("wrapped %w", caos_errors.ThrowInvalidArgument(nil, "id", "message")),
},
wantStatusCode: http.StatusBadRequest,
wantOk: true,
},
{
name: "wrapped not found",
args: args{
err: fmt.Errorf("wrapped %w", caos_errors.ThrowNotFound(nil, "id", "message")),
},
wantStatusCode: http.StatusNotFound,
wantOk: true,
},
{
name: "wrapped permission denied",
args: args{
err: fmt.Errorf("wrapped %w", caos_errors.ThrowPermissionDenied(nil, "id", "message")),
},
wantStatusCode: http.StatusForbidden,
wantOk: true,
},
{
name: "wrapped precondition failed",
args: args{
err: fmt.Errorf("wrapped %w", caos_errors.ThrowPreconditionFailed(nil, "id", "message")),
},
wantStatusCode: http.StatusBadRequest,
wantOk: true,
},
{
name: "wrapped unauthenticated",
args: args{
err: fmt.Errorf("wrapped %w", caos_errors.ThrowUnauthenticated(nil, "id", "message")),
},
wantStatusCode: http.StatusUnauthorized,
wantOk: true,
},
{
name: "wrapped unavailable",
args: args{
err: fmt.Errorf("wrapped %w", caos_errors.ThrowUnavailable(nil, "id", "message")),
},
wantStatusCode: http.StatusServiceUnavailable,
wantOk: true,
},
{
name: "wrapped unimplemented",
args: args{
err: fmt.Errorf("wrapped %w", caos_errors.ThrowUnimplemented(nil, "id", "message")),
},
wantStatusCode: http.StatusNotImplemented,
wantOk: true,
},
{
name: "wrapped resource exhausted",
args: args{
err: fmt.Errorf("wrapped %w", caos_errors.ThrowResourceExhausted(nil, "id", "message")),
},
wantStatusCode: http.StatusTooManyRequests,
wantOk: true,
},
{
name: "no caos/zitadel error",
args: args{
err: errors.New("error"),
},
wantStatusCode: http.StatusInternalServerError,
wantOk: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
gotStatusCode, gotOk := ZitadelErrorToHTTPStatusCode(tt.args.err)
if gotStatusCode != tt.wantStatusCode {
t.Errorf("ZitadelErrorToHTTPStatusCode() gotStatusCode = %v, want %v", gotStatusCode, tt.wantStatusCode)
}
if gotOk != tt.wantOk {
t.Errorf("ZitadelErrorToHTTPStatusCode() gotOk = %v, want %v", gotOk, tt.wantOk)
}
})
}
}