231 lines
4.9 KiB
Go
231 lines
4.9 KiB
Go
package scim
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"net/http"
|
|
"regexp"
|
|
"strconv"
|
|
|
|
"github.com/cortezaproject/corteza-server/pkg/errors"
|
|
"github.com/cortezaproject/corteza-server/system/service"
|
|
"github.com/cortezaproject/corteza-server/system/types"
|
|
"github.com/go-chi/chi"
|
|
)
|
|
|
|
type (
|
|
passwordSetter interface {
|
|
SetPassword(context.Context, uint64, string) error
|
|
}
|
|
|
|
usersHandler struct {
|
|
externalIdAsPrimary bool
|
|
externalIdValidator *regexp.Regexp
|
|
|
|
svc service.UserService
|
|
passSvc passwordSetter
|
|
sec getSecurityContextFn
|
|
}
|
|
)
|
|
|
|
func (h usersHandler) get(w http.ResponseWriter, r *http.Request) {
|
|
var (
|
|
res = h.lookup(h.sec(r), chi.URLParam(r, "id"), w)
|
|
)
|
|
|
|
if res == nil {
|
|
return
|
|
}
|
|
|
|
send(w, http.StatusOK, newUserResourceResponse(res))
|
|
}
|
|
|
|
func (h usersHandler) create(w http.ResponseWriter, r *http.Request) {
|
|
defer r.Body.Close()
|
|
|
|
var (
|
|
ctx = h.sec(r)
|
|
svc = h.svc
|
|
payload = &userResourceRequest{}
|
|
err error
|
|
existing *types.User
|
|
code = http.StatusBadRequest
|
|
)
|
|
|
|
if err = payload.decodeJSON(r.Body); err != nil {
|
|
sendError(w, newErrorResponse(code, err))
|
|
return
|
|
}
|
|
|
|
{
|
|
// do we need to upsert?
|
|
if payload.ExternalId != nil {
|
|
existing, err = h.lookupByExternalId(ctx, *payload.ExternalId)
|
|
if err != nil {
|
|
sendError(w, newErrorResponse(code, err))
|
|
return
|
|
}
|
|
} else if email := payload.Emails.getFirst(); email != "" {
|
|
existing, err = svc.FindByEmail(ctx, email)
|
|
if err != nil && !errors.Is(err, service.UserErrNotFound()) {
|
|
sendError(w, newErrorResponse(http.StatusInternalServerError, err))
|
|
return
|
|
}
|
|
}
|
|
}
|
|
|
|
res, err := h.save(ctx, payload, existing)
|
|
if err != nil {
|
|
sendError(w, err)
|
|
return
|
|
}
|
|
|
|
status := http.StatusOK
|
|
if res.UpdatedAt == nil {
|
|
status = http.StatusCreated
|
|
}
|
|
|
|
send(w, status, newUserResourceResponse(res))
|
|
}
|
|
|
|
func (h usersHandler) replace(w http.ResponseWriter, r *http.Request) {
|
|
defer r.Body.Close()
|
|
|
|
var (
|
|
ctx = h.sec(r)
|
|
existing = h.lookup(ctx, chi.URLParam(r, "id"), w)
|
|
payload = &userResourceRequest{}
|
|
)
|
|
|
|
if err := payload.decodeJSON(r.Body); err != nil {
|
|
sendError(w, newErrorResponse(http.StatusBadRequest, err))
|
|
return
|
|
}
|
|
|
|
res, err := h.save(ctx, payload, existing)
|
|
if err != nil {
|
|
sendError(w, err)
|
|
return
|
|
}
|
|
|
|
status := http.StatusOK
|
|
if res.UpdatedAt == nil {
|
|
status = http.StatusCreated
|
|
}
|
|
|
|
send(w, status, newUserResourceResponse(res))
|
|
}
|
|
|
|
func (h usersHandler) save(ctx context.Context, req *userResourceRequest, existing *types.User) (res *types.User, err error) {
|
|
var (
|
|
svc = h.svc
|
|
)
|
|
|
|
if existing == nil || !existing.Valid() {
|
|
// in case when we did not find a valid user,
|
|
// start from blank
|
|
existing = &types.User{}
|
|
}
|
|
|
|
res = existing
|
|
req.applyTo(res)
|
|
|
|
if res.ID > 0 {
|
|
res, err = svc.Update(ctx, res)
|
|
} else {
|
|
res, err = svc.Create(ctx, res)
|
|
}
|
|
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if req.Password != nil && *req.Password != "" {
|
|
err = h.passSvc.SetPassword(ctx, res.ID, *req.Password)
|
|
if err != nil {
|
|
return
|
|
}
|
|
}
|
|
|
|
return res, nil
|
|
}
|
|
|
|
func (h usersHandler) delete(w http.ResponseWriter, r *http.Request) {
|
|
var (
|
|
ctx = h.sec(r)
|
|
svc = h.svc
|
|
res = h.lookup(ctx, chi.URLParam(r, "id"), w)
|
|
)
|
|
|
|
if res == nil {
|
|
return
|
|
}
|
|
|
|
if err := svc.Delete(ctx, res.ID); err != nil {
|
|
sendError(w, newErrorResponse(http.StatusBadRequest, err))
|
|
} else {
|
|
w.WriteHeader(http.StatusNoContent)
|
|
}
|
|
}
|
|
|
|
// loads role from request path params
|
|
//
|
|
// handles errors by writing them to response
|
|
func (h usersHandler) lookup(ctx context.Context, id string, w http.ResponseWriter) *types.User {
|
|
var (
|
|
svc = h.svc
|
|
)
|
|
|
|
if h.externalIdAsPrimary {
|
|
res, err := h.lookupByExternalId(ctx, id)
|
|
if err != nil {
|
|
sendError(w, err)
|
|
return nil
|
|
}
|
|
|
|
if res == nil {
|
|
sendError(w, newErrorResponse(http.StatusNotFound, fmt.Errorf("user not found")))
|
|
}
|
|
|
|
return res
|
|
} else {
|
|
groupId, err := strconv.ParseUint(id, 10, 64)
|
|
if err != nil || groupId == 0 {
|
|
sendError(w, newErrorResponse(http.StatusBadRequest, err))
|
|
return nil
|
|
}
|
|
|
|
role, err := svc.FindByID(ctx, groupId)
|
|
if err != nil {
|
|
sendError(w, newErrorResponse(http.StatusBadRequest, err))
|
|
return nil
|
|
}
|
|
|
|
return role
|
|
}
|
|
}
|
|
|
|
func (h usersHandler) lookupByExternalId(ctx context.Context, id string) (r *types.User, err error) {
|
|
return lookupUserByExternalId(ctx, h.svc, h.externalIdValidator, id)
|
|
}
|
|
|
|
func lookupUserByExternalId(ctx context.Context, svc service.UserService, v *regexp.Regexp, id string) (r *types.User, err error) {
|
|
if v != nil && !v.MatchString(id) {
|
|
return nil, newErrorfResponse(http.StatusBadRequest, "invalid external ID")
|
|
}
|
|
|
|
rr, _, err := svc.Find(ctx, types.UserFilter{Labels: map[string]string{userLabel_SCIM_externalId: id}})
|
|
if err != nil {
|
|
return nil, newErrorResponse(http.StatusInternalServerError, err)
|
|
}
|
|
|
|
switch len(rr) {
|
|
case 0:
|
|
return nil, nil
|
|
case 1:
|
|
return rr[0], nil
|
|
default:
|
|
return nil, newErrorfResponse(http.StatusPreconditionFailed, "more than one user matches this externalId")
|
|
}
|
|
}
|