2025-01-09 12:46:36 +01:00
|
|
|
package scim
|
|
|
|
|
|
|
|
import (
|
|
|
|
"encoding/json"
|
|
|
|
"net/http"
|
|
|
|
"path"
|
|
|
|
|
|
|
|
"github.com/gorilla/mux"
|
|
|
|
"github.com/zitadel/logging"
|
|
|
|
|
|
|
|
"github.com/zitadel/zitadel/internal/api/authz"
|
|
|
|
zhttp "github.com/zitadel/zitadel/internal/api/http"
|
|
|
|
zhttp_middlware "github.com/zitadel/zitadel/internal/api/http/middleware"
|
|
|
|
sconfig "github.com/zitadel/zitadel/internal/api/scim/config"
|
|
|
|
smiddleware "github.com/zitadel/zitadel/internal/api/scim/middleware"
|
|
|
|
sresources "github.com/zitadel/zitadel/internal/api/scim/resources"
|
|
|
|
"github.com/zitadel/zitadel/internal/api/scim/schemas"
|
|
|
|
"github.com/zitadel/zitadel/internal/api/scim/serrors"
|
|
|
|
"github.com/zitadel/zitadel/internal/command"
|
|
|
|
"github.com/zitadel/zitadel/internal/crypto"
|
|
|
|
"github.com/zitadel/zitadel/internal/query"
|
|
|
|
)
|
|
|
|
|
|
|
|
func NewServer(
|
|
|
|
command *command.Commands,
|
|
|
|
query *query.Queries,
|
|
|
|
verifier *authz.ApiTokenVerifier,
|
|
|
|
userCodeAlg crypto.EncryptionAlgorithm,
|
|
|
|
config *sconfig.Config,
|
|
|
|
middlewares ...zhttp_middlware.MiddlewareWithErrorFunc) http.Handler {
|
|
|
|
verifier.RegisterServer("SCIM-V2", schemas.HandlerPrefix, AuthMapping)
|
|
|
|
return buildHandler(command, query, userCodeAlg, config, middlewares...)
|
|
|
|
}
|
|
|
|
|
|
|
|
func buildHandler(
|
|
|
|
command *command.Commands,
|
|
|
|
query *query.Queries,
|
|
|
|
userCodeAlg crypto.EncryptionAlgorithm,
|
|
|
|
cfg *sconfig.Config,
|
|
|
|
middlewares ...zhttp_middlware.MiddlewareWithErrorFunc) http.Handler {
|
|
|
|
|
|
|
|
router := mux.NewRouter()
|
|
|
|
|
|
|
|
// content type middleware needs to run at the very beginning to correctly set content types of errors
|
|
|
|
middlewares = append([]zhttp_middlware.MiddlewareWithErrorFunc{smiddleware.ContentTypeMiddleware}, middlewares...)
|
|
|
|
middlewares = append(middlewares, smiddleware.ScimContextMiddleware(query))
|
|
|
|
scimMiddleware := zhttp_middlware.ChainedWithErrorHandler(serrors.ErrorHandler, middlewares...)
|
|
|
|
mapResource(router, scimMiddleware, sresources.NewUsersHandler(command, query, userCodeAlg, cfg))
|
|
|
|
return router
|
|
|
|
}
|
|
|
|
|
|
|
|
func mapResource[T sresources.ResourceHolder](router *mux.Router, mw zhttp_middlware.ErrorHandlerFunc, handler sresources.ResourceHandler[T]) {
|
|
|
|
adapter := sresources.NewResourceHandlerAdapter[T](handler)
|
|
|
|
resourceRouter := router.PathPrefix("/" + path.Join(zhttp.OrgIdInPathVariable, string(handler.ResourceNamePlural()))).Subrouter()
|
|
|
|
|
|
|
|
resourceRouter.Handle("", mw(handleResourceCreatedResponse(adapter.Create))).Methods(http.MethodPost)
|
2025-01-21 13:31:54 +01:00
|
|
|
resourceRouter.Handle("", mw(handleJsonResponse(adapter.List))).Methods(http.MethodGet)
|
|
|
|
resourceRouter.Handle("/.search", mw(handleJsonResponse(adapter.List))).Methods(http.MethodPost)
|
2025-01-10 12:15:06 +01:00
|
|
|
resourceRouter.Handle("/{id}", mw(handleResourceResponse(adapter.Get))).Methods(http.MethodGet)
|
2025-01-14 15:44:41 +01:00
|
|
|
resourceRouter.Handle("/{id}", mw(handleResourceResponse(adapter.Replace))).Methods(http.MethodPut)
|
2025-01-27 13:36:07 +01:00
|
|
|
resourceRouter.Handle("/{id}", mw(handleEmptyResponse(adapter.Update))).Methods(http.MethodPatch)
|
2025-01-09 15:12:13 +01:00
|
|
|
resourceRouter.Handle("/{id}", mw(handleEmptyResponse(adapter.Delete))).Methods(http.MethodDelete)
|
2025-01-09 12:46:36 +01:00
|
|
|
}
|
|
|
|
|
2025-01-21 13:31:54 +01:00
|
|
|
func handleJsonResponse[T any](next func(r *http.Request) (T, error)) zhttp_middlware.HandlerFuncWithError {
|
|
|
|
return func(w http.ResponseWriter, r *http.Request) error {
|
|
|
|
entity, err := next(r)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
err = json.NewEncoder(w).Encode(entity)
|
|
|
|
logging.OnError(err).Warn("scim json response encoding failed")
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2025-01-09 12:46:36 +01:00
|
|
|
func handleResourceCreatedResponse[T sresources.ResourceHolder](next func(*http.Request) (T, error)) zhttp_middlware.HandlerFuncWithError {
|
|
|
|
return func(w http.ResponseWriter, r *http.Request) error {
|
|
|
|
entity, err := next(r)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
resource := entity.GetResource()
|
|
|
|
w.Header().Set(zhttp.Location, resource.Meta.Location)
|
|
|
|
w.WriteHeader(http.StatusCreated)
|
|
|
|
|
|
|
|
err = json.NewEncoder(w).Encode(entity)
|
|
|
|
logging.OnError(err).Warn("scim json response encoding failed")
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
}
|
2025-01-09 15:12:13 +01:00
|
|
|
|
2025-01-10 12:15:06 +01:00
|
|
|
func handleResourceResponse[T sresources.ResourceHolder](next func(*http.Request) (T, error)) zhttp_middlware.HandlerFuncWithError {
|
|
|
|
return func(w http.ResponseWriter, r *http.Request) error {
|
|
|
|
entity, err := next(r)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
resource := entity.GetResource()
|
|
|
|
w.Header().Set(zhttp.ContentLocation, resource.Meta.Location)
|
|
|
|
|
|
|
|
err = json.NewEncoder(w).Encode(entity)
|
|
|
|
logging.OnError(err).Warn("scim json response encoding failed")
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2025-01-09 15:12:13 +01:00
|
|
|
func handleEmptyResponse(next func(*http.Request) error) zhttp_middlware.HandlerFuncWithError {
|
|
|
|
return func(w http.ResponseWriter, r *http.Request) error {
|
|
|
|
err := next(r)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
w.WriteHeader(http.StatusNoContent)
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
}
|