3
0
corteza/system/scim/user_handler.go
2021-01-25 18:05:24 +01:00

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")
}
}