141 lines
3.8 KiB
Go
Raw Normal View History

package serrors
import (
"context"
"encoding/json"
"errors"
"fmt"
"net/http"
"strconv"
"github.com/zitadel/logging"
"golang.org/x/text/language"
http_util "github.com/zitadel/zitadel/internal/api/http"
zhttp_middleware "github.com/zitadel/zitadel/internal/api/http/middleware"
"github.com/zitadel/zitadel/internal/api/scim/schemas"
"github.com/zitadel/zitadel/internal/i18n"
"github.com/zitadel/zitadel/internal/zerrors"
)
type scimErrorType string
type wrappedScimError struct {
Parent error
ScimType scimErrorType
}
type scimError struct {
Schemas []schemas.ScimSchemaType `json:"schemas"`
ScimType scimErrorType `json:"scimType,omitempty"`
Detail string `json:"detail,omitempty"`
StatusCode int `json:"-"`
Status string `json:"status"`
ZitadelDetail *errorDetail `json:"urn:ietf:params:scim:api:zitadel:messages:2.0:ErrorDetail,omitempty"`
}
type errorDetail struct {
ID string `json:"id"`
Message string `json:"message"`
}
const (
// ScimTypeInvalidValue A required value was missing,
// or the value specified was not compatible with the operation,
// or attribute type (see Section 2.2 of RFC7643),
// or resource schema (see Section 4 of RFC7643).
ScimTypeInvalidValue scimErrorType = "invalidValue"
// ScimTypeInvalidSyntax The request body message structure was invalid or did
// not conform to the request schema.
ScimTypeInvalidSyntax scimErrorType = "invalidSyntax"
)
var translator *i18n.Translator
func ErrorHandler(next zhttp_middleware.HandlerFuncWithError) http.Handler {
var err error
translator, err = i18n.NewZitadelTranslator(language.English)
logging.OnError(err).Panic("unable to get translator")
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if err = next(w, r); err == nil {
return
}
scimErr := mapToScimJsonError(r.Context(), err)
w.WriteHeader(scimErr.StatusCode)
jsonErr := json.NewEncoder(w).Encode(scimErr)
logging.OnError(jsonErr).Warn("Failed to marshal scim error response")
})
}
func ThrowInvalidValue(parent error) error {
return &wrappedScimError{
Parent: parent,
ScimType: ScimTypeInvalidValue,
}
}
func ThrowInvalidSyntax(parent error) error {
return &wrappedScimError{
Parent: parent,
ScimType: ScimTypeInvalidSyntax,
}
}
func (err *scimError) Error() string {
return fmt.Sprintf("SCIM Error: %s: %s", err.ScimType, err.Detail)
}
func (err *wrappedScimError) Error() string {
return fmt.Sprintf("SCIM Error: %s: %s", err.ScimType, err.Parent.Error())
}
func mapToScimJsonError(ctx context.Context, err error) *scimError {
scimErr := new(wrappedScimError)
if ok := errors.As(err, &scimErr); ok {
mappedErr := mapToScimJsonError(ctx, scimErr.Parent)
mappedErr.ScimType = scimErr.ScimType
return mappedErr
}
zitadelErr := new(zerrors.ZitadelError)
if ok := errors.As(err, &zitadelErr); !ok {
return &scimError{
Schemas: []schemas.ScimSchemaType{schemas.IdError},
Detail: "Unknown internal server error",
Status: strconv.Itoa(http.StatusInternalServerError),
StatusCode: http.StatusInternalServerError,
}
}
statusCode, ok := http_util.ZitadelErrorToHTTPStatusCode(err)
if !ok {
statusCode = http.StatusInternalServerError
}
localizedMsg := translator.LocalizeFromCtx(ctx, zitadelErr.GetMessage(), nil)
return &scimError{
Schemas: []schemas.ScimSchemaType{schemas.IdError, schemas.IdZitadelErrorDetail},
ScimType: mapErrorToScimErrorType(err),
Detail: localizedMsg,
StatusCode: statusCode,
Status: strconv.Itoa(statusCode),
ZitadelDetail: &errorDetail{
ID: zitadelErr.GetID(),
Message: zitadelErr.GetMessage(),
},
}
}
func mapErrorToScimErrorType(err error) scimErrorType {
switch {
case zerrors.IsErrorInvalidArgument(err):
return ScimTypeInvalidValue
default:
return ""
}
}