Extends SCIM implementation to support role membership management

- Updates request payload for patching group
- Updates tests
This commit is contained in:
Vivek Patel
2021-05-11 17:01:03 +05:30
parent 63dbe7024d
commit 1fd9bdfa7c
4 changed files with 65 additions and 14 deletions
+9 -9
View File
@@ -159,22 +159,22 @@ func (h groupsHandler) patch(w http.ResponseWriter, r *http.Request) {
// validate and collect operations
for _, op := range payload.Operations {
if op.Path != "members" {
// allow only "members" path
sendError(w, newErrorfResponse(http.StatusBadRequest, "unsupported path: %q", op.Path))
return
}
// iterate through operation's values, load user and schedule op
for _, userExternalId := range op.Value {
u, err = lookupUserByExternalId(ctx, h.userSvc, h.externalIdValidator, userExternalId.Value)
for path, userExternalId := range op.Value {
if path != "members" {
// allow only "members" path
sendError(w, newErrorfResponse(http.StatusBadRequest, "unsupported path: %q", op.Path))
return
}
u, err = lookupUserByExternalId(ctx, h.userSvc, h.externalIdValidator, userExternalId)
if err != nil {
sendError(w, err)
return
}
if u == nil {
sendError(w, newErrorfResponse(http.StatusBadRequest, "no such user: %q", userExternalId.Value))
sendError(w, newErrorfResponse(http.StatusBadRequest, "no such user: %q", userExternalId))
return
}
+38
View File
@@ -0,0 +1,38 @@
package scim
import (
"fmt"
"github.com/stretchr/testify/require"
"strings"
"testing"
)
func TestOperationsRequestDecodeJSON(t *testing.T) {
const user1Id = `00000000-0000-0000-0000-000000000001`
var (
req = require.New(t)
payload operationsRequest
s = strings.NewReader(fmt.Sprintf(
`{"Operations":[{"op":"add","path":"members[value eq \"%s\"]"}],"schemas":["urn:ietf:params:scim:schemas:core:2.0:PatchOp"]}`,
user1Id,
))
expectedPayload = operationsRequest{
Schemas: []string{"urn:ietf:params:scim:schemas:core:2.0:PatchOp"},
Operations: []operationRequest{
{
Operation: "add",
Path: fmt.Sprintf("members[value eq \"%s\"]", user1Id),
Value: map[string]string{
"members": user1Id,
},
},
},
}
)
err := payload.decodeJSON(s)
req.NoError(err)
req.Equal(expectedPayload, payload)
}
+16 -3
View File
@@ -4,6 +4,7 @@ import (
"encoding/json"
"fmt"
"io"
"regexp"
)
const (
@@ -22,9 +23,7 @@ type (
operationRequest struct {
Operation string `json:"op"`
Path string `json:"path"`
Value []struct {
Value string `json:"value"`
} `json:"value"`
Value map[string]string
}
)
@@ -33,5 +32,19 @@ func (req *operationsRequest) decodeJSON(r io.Reader) error {
return fmt.Errorf("could not decode operations payload: %w", err)
}
// Compiles req.Path and updates req.Value
for i, op := range req.Operations {
r := regexp.MustCompile(`([a-z]\w+)\[value eq \"([\w]{8}-[\w]{4}-[\w]{4}-[\w]{4}-[\w]{12})\"\]`)
strings := r.FindStringSubmatch(op.Path)
if len(strings) == 3 {
key := strings[1]
if op.Value == nil {
op.Value = make(map[string]string)
}
op.Value[key] = strings[2]
req.Operations[i].Value = op.Value
}
}
return nil
}
+2 -2
View File
@@ -349,7 +349,7 @@ func TestScimPatchingGroupMembership(t *testing.T) {
h.scimApiInit(scimSetWithExternalId, scimSetWithUUIDValidator).
Patch(fmt.Sprintf("/Groups/%s", groupId)).
JSON(fmt.Sprintf(
`{"Operations":[{"op":"add","path":"members","value":[{"value":%q}]}],"schemas":["urn:ietf:params:scim:schemas:core:2.0:PatchOp"]}`,
`{"Operations":[{"op":"add","path":"members[value eq \"%s\"]"}],"schemas":["urn:ietf:params:scim:schemas:core:2.0:PatchOp"]}`,
user1Id,
)).
Expect(t).
@@ -363,7 +363,7 @@ func TestScimPatchingGroupMembership(t *testing.T) {
h.scimApiInit(scimSetWithExternalId, scimSetWithUUIDValidator).
Patch(fmt.Sprintf("/Groups/%s", groupId)).
JSON(fmt.Sprintf(
`{"Operations":[{"op":"add","path":"members","value":[{"value":%q}]},{"op":"remove","path":"members","value":[{"value":%q}]}],"schemas":["urn:ietf:params:scim:schemas:core:2.0:PatchOp"]}`,
`{"Operations":[{"op":"add","path":"members[value eq \"%[1]s\"]"},{"op":"remove","path":"members[value eq \"%[2]s\"]"}],"schemas":["urn:ietf:params:scim:schemas:core:2.0:PatchOp"]}`,
user2Id,
user1Id,
)).