in lib/scim/scim_group.go [253:326]
func (h *GroupHandler) Patch(r *http.Request, name string) (proto.Message, error) {
h.save = &spb.Group{}
proto.Merge(h.save, h.item)
memberCounter := 0
for i, patch := range h.patch.Operations {
path := patch.Path
if memberPathRE.MatchString(path) {
path = "member"
}
src := ""
var dst *string
switch path {
case "displayName":
src = patchSource(patch.Value)
dst = &h.save.DisplayName
if patch.Op == "remove" || len(src) == 0 {
return nil, errutil.NewIndexError(codes.InvalidArgument, errutil.ErrorPath("scim", "groups", name, path), i, fmt.Sprintf("value must not be empty"))
}
case "members":
if patch.Op != "add" {
return nil, errutil.NewIndexError(codes.InvalidArgument, errutil.ErrorPath("scim", "groups", name, path), i, fmt.Sprintf("op %q is not valid", patch.Op))
}
member, err := h.patchMember(patch.Object, name, memberCounter)
if err != nil {
return nil, err
}
memberCounter++
if err := h.store.WriteTx(storage.GroupMemberDatatype, getRealm(r), name, member.Value, storage.LatestRev, member, nil, h.tx); err != nil {
return nil, errutil.NewIndexError(codes.InvalidArgument, errutil.ErrorPath("scim", "groups", name, path), i, err.Error())
}
case "member":
if patch.Op != "remove" {
return nil, errutil.NewIndexError(codes.InvalidArgument, errutil.ErrorPath("scim", "groups", name, path), i, fmt.Sprintf("op %q is not valid", patch.Op))
}
match := memberPathRE.FindStringSubmatch(patch.Path)
if len(match) < 2 {
return nil, errutil.NewIndexError(codes.InvalidArgument, errutil.ErrorPath("scim", "groups", name, path), i, fmt.Sprintf("invalid member path %q", patch.Path))
}
memberName := match[1]
if err := h.store.DeleteTx(storage.GroupMemberDatatype, getRealm(r), name, memberName, storage.LatestRev, h.tx); err != nil {
if storage.ErrNotFound(err) {
return nil, errutil.NewIndexError(codes.InvalidArgument, errutil.ErrorPath("scim", "groups", name, path), i, fmt.Sprintf("%q is not a member of the group", memberName))
}
return nil, errutil.NewIndexError(codes.InvalidArgument, errutil.ErrorPath("scim", "groups", name, path), i, err.Error())
}
default:
return nil, errutil.NewIndexError(codes.InvalidArgument, errutil.ErrorPath("scim", "groups", name, path), i, fmt.Sprintf("invalid path %q", patch.Path))
}
if dst == nil {
continue
}
if patch.Op != "remove" && len(src) == 0 {
return nil, errutil.NewIndexError(codes.InvalidArgument, errutil.ErrorPath("scim", "groups", name, path), i, fmt.Sprintf("cannot set an empty value"))
}
switch patch.Op {
case "add":
fallthrough
case "replace":
*dst = src
case "remove":
*dst = ""
default:
return nil, errutil.NewIndexError(codes.InvalidArgument, errutil.ErrorPath("scim", "groups", name, path), i, fmt.Sprintf("invalid op %q", patch.Op))
}
}
// Output the new result: Get() will return contents from h.item with the latest edits from h.save.
// Needs a deep copy since h.save as the item saved will not include members once Save() is called
// but the item returned to the client will include members.
h.item = proto.Clone(h.save).(*spb.Group)
return h.Get(r, name)
}