mirror of
https://github.com/zitadel/zitadel.git
synced 2025-08-12 01:37:31 +00:00
feat: create user scim v2 endpoint (#9132)
# Which Problems Are Solved - Adds infrastructure code (basic implementation, error handling, middlewares, ...) to implement the SCIM v2 interface - Adds support for the user create SCIM v2 endpoint # How the Problems Are Solved - Adds support for the user create SCIM v2 endpoint under `POST /scim/v2/{orgID}/Users` # Additional Context Part of #8140
This commit is contained in:
140
internal/api/scim/serrors/errors.go
Normal file
140
internal/api/scim/serrors/errors.go
Normal file
@@ -0,0 +1,140 @@
|
||||
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 ""
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user