Skip to content

Commit

Permalink
Merge pull request #4 from ivixvi/feat/remove-singular-attribute
Browse files Browse the repository at this point in the history
feat: implement RemoveOperation for Singular Attribute
  • Loading branch information
ivixvi authored Jul 8, 2024
2 parents e253e37 + 3e8fa3b commit c57df3c
Show file tree
Hide file tree
Showing 5 changed files with 145 additions and 20 deletions.
2 changes: 1 addition & 1 deletion example/handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ type testResourceHandler struct {
data map[string]testData
schema schema.Schema
schemaExtensions []scim.SchemaExtension
patcher scimpatch.Patcher
patcher *scimpatch.Patcher
}

// notImplemented is error returns 501 Not Implemented.
Expand Down
10 changes: 4 additions & 6 deletions example/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ import (
)

func newTestServer() scim.Server {
userSchema := schema.CoreUserSchema()
enterprizeUserSchema := schema.ExtensionEnterpriseUser()
patcher := scimpatch.NewPatcher(userSchema, []schema.Schema{enterprizeUserSchema})
s, err := scim.NewServer(
&scim.ServerArgs{
ServiceProviderConfig: &scim.ServiceProviderConfig{},
Expand All @@ -29,12 +32,7 @@ func newTestServer() scim.Server {
schemaExtensions: []scim.SchemaExtension{
{Schema: schema.ExtensionEnterpriseUser()},
},
patcher: scimpatch.Patcher{
Schema: schema.CoreUserSchema(),
SchemaExtensions: []scim.SchemaExtension{
{Schema: schema.ExtensionEnterpriseUser()},
},
},
patcher: patcher,
},
},
},
Expand Down
77 changes: 71 additions & 6 deletions scimpatch.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,26 @@ import (
)

type Patcher struct {
Schema schema.Schema
SchemaExtensions []scim.SchemaExtension
schema schema.Schema
schemas map[string]schema.Schema
}

// NewPatcher は Pathcher の実態を取得します。
func NewPatcher(s schema.Schema, extentions []schema.Schema) *Patcher {
schemas := map[string]schema.Schema{s.ID: s}
for _, s := range extentions {
schemas[s.ID] = s
}
return &Patcher{
schema: s,
schemas: schemas,
}
}

// Apply は RFC7644 3.5.2. Modifying with PATCH の実装です。
// data に op が適用された ResourceAttributes を返却します。
// see. https://datatracker.ietf.org/doc/html/rfc7644#section-3.5.2
func (p Patcher) Apply(op scim.PatchOperation, data scim.ResourceAttributes) (scim.ResourceAttributes, error) {
func (p *Patcher) Apply(op scim.PatchOperation, data scim.ResourceAttributes) (scim.ResourceAttributes, error) {
switch strings.ToLower(op.Op) {
case scim.PatchOperationAdd:
return p.add(op, data)
Expand All @@ -32,26 +44,79 @@ func (p Patcher) Apply(op scim.PatchOperation, data scim.ResourceAttributes) (sc
// data に op が適用された ResourceAttributes を返却します。
// see. https://datatracker.ietf.org/doc/html/rfc7644#section-3.5.2.1
// 基本は Validated な op を想定しているため、エラーハンドリングは属性を確認するうえで対応することになる最小限のチェックとなっています。
func (p Patcher) add(op scim.PatchOperation, data scim.ResourceAttributes) (scim.ResourceAttributes, error) {
func (p *Patcher) add(op scim.PatchOperation, data scim.ResourceAttributes) (scim.ResourceAttributes, error) {
return data, nil
}

// replace は RFC7644 3.5.2.3. Replace Operation の実装です。
// data に op が適用された ResourceAttributes を返却します。
// see. https://datatracker.ietf.org/doc/html/rfc7644#section-3.5.2.3
// 基本は Validated な op を想定しているため、エラーハンドリングは属性を確認するうえで対応することになる最小限のチェックとなっています。
func (p Patcher) replace(op scim.PatchOperation, data scim.ResourceAttributes) (scim.ResourceAttributes, error) {
func (p *Patcher) replace(op scim.PatchOperation, data scim.ResourceAttributes) (scim.ResourceAttributes, error) {
return data, nil
}

// remove は RFC7644 3.5.2.2. Remove Operation の実装です。
// data に op が適用された ResourceAttributes を返却します。
// see. https://datatracker.ietf.org/doc/html/rfc7644#section-3.5.2.2
// 基本は Validated な op を想定しているため、エラーハンドリングは属性を確認するうえで対応することになる最小限のチェックとなっています。
func (p Patcher) remove(op scim.PatchOperation, data scim.ResourceAttributes) (scim.ResourceAttributes, error) {
func (p *Patcher) remove(op scim.PatchOperation, data scim.ResourceAttributes) (scim.ResourceAttributes, error) {
if op.Path == nil {
// If "path" is unspecified, the operation fails with HTTP status code 400 and a "scimType" error code of "noTarget".
return scim.ResourceAttributes{}, errors.ScimErrorNoTarget
}
// Resolve Attribute
attrName := op.Path.AttributePath.AttributeName
attr, ok := p.containsAttribute(attrName)
if !ok {
return scim.ResourceAttributes{}, errors.ScimErrorInvalidPath
}
if cannotBePatched(op.Op, attr) {
return scim.ResourceAttributes{}, errors.ScimErrorMutability
}
scopedMap := p.getScopedMap(op, data)
switch {
case !attr.HasSubAttributes() && !attr.MultiValued():
delete(scopedMap, attrName)
}
data = p.setScopedMap(op, data, scopedMap)
return data, nil
}

// containsAttribute は attrName がサーバーで利用されているスキーマの属性名として適切かを確認し、取得します。
func (p *Patcher) containsAttribute(attrName string) (schema.CoreAttribute, bool) {
for _, schema := range p.schemas {
attr, ok := schema.Attributes.ContainsAttribute(attrName)
if ok {
return attr, ok
}
}
return schema.CoreAttribute{}, false
}

// getScopedMap は 処理対象であるmapまでのスコープをたどり該当のmapを返却します
func (p *Patcher) getScopedMap(op scim.PatchOperation, data scim.ResourceAttributes) scim.ResourceAttributes {
uriPrefix, containsURI := containsURIPrefix(op.Path)
if containsURI {
data, ok := data[uriPrefix].(map[string]interface{})
if !ok {
data = scim.ResourceAttributes{}
}
return data
}

return data
}

// setScopedMap は 処理対象であるmapまでのスコープをたどりscopedMapに置換したdataを返却します
func (p *Patcher) setScopedMap(op scim.PatchOperation, data scim.ResourceAttributes, scopedMap scim.ResourceAttributes) scim.ResourceAttributes {
uriPrefix, containsURI := containsURIPrefix(op.Path)
if containsURI {
if len(scopedMap) == 0 {
delete(data, uriPrefix)
} else {
data[uriPrefix] = scopedMap
}
}
return data
}
42 changes: 35 additions & 7 deletions scimpatch_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (

"github.com/elimity-com/scim"
"github.com/elimity-com/scim/errors"
"github.com/elimity-com/scim/schema"
"github.com/ivixvi/scimpatch"
filter "github.com/scim2/filter-parser/v2"
)
Expand Down Expand Up @@ -56,16 +57,45 @@ func TestPatcher_Apply(t *testing.T) {
},
},
{
name: "Replace operation",
name: "Remove operation - Core Singular Attribute",
op: scim.PatchOperation{
Op: "remove",
Path: path("displayName"),
},
data: scim.ResourceAttributes{
"displayName": "Bob Green",
},
expected: scim.ResourceAttributes{},
},
{
name: "Remove operation - Extention Singular Attribute - All Removed.",
op: scim.PatchOperation{
Op: "remove",
Path: path("urn:ietf:params:scim:schemas:extension:enterprise:2.0:User:department"),
},
data: scim.ResourceAttributes{
"urn:ietf:params:scim:schemas:extension:enterprise:2.0:User": map[string]interface{}{
"department": "2B Sales",
},
},
expected: scim.ResourceAttributes{},
},
{
name: "Remove operation - Extention Singular Attribute - Partially Removed.",
op: scim.PatchOperation{
Op: "remove",
Path: path("urn:ietf:params:scim:schemas:extension:enterprise:2.0:User:department"),
},
data: scim.ResourceAttributes{
"urn:ietf:params:scim:schemas:extension:enterprise:2.0:User": map[string]interface{}{
"division": "Sales",
"department": "2B Sales",
},
},
expected: scim.ResourceAttributes{
"displayName": "Bob Green",
"urn:ietf:params:scim:schemas:extension:enterprise:2.0:User": map[string]interface{}{
"division": "Sales",
},
},
},
}
Expand All @@ -74,7 +104,7 @@ func TestPatcher_Apply(t *testing.T) {
t.Run(tc.name, func(t *testing.T) {
t.Log(tc.name)
// Create a Patcher instance with a dummy schema
patcher := scimpatch.Patcher{}
patcher := scimpatch.NewPatcher(schema.CoreUserSchema(), []schema.Schema{schema.ExtensionEnterpriseUser()})

// Apply the PatchOperation
result, err := patcher.Apply(tc.op, tc.data)
Expand All @@ -83,10 +113,8 @@ func TestPatcher_Apply(t *testing.T) {
}

// Check if the result matches the expected data
for key, expectedValue := range tc.expected {
if result[key] != expectedValue {
t.Errorf("for key %q, expected %v, got %v", key, expectedValue, result[key])
}
if !(fmt.Sprint(result) == fmt.Sprint(tc.expected)) {
t.Errorf("actual: %v, expected: %v", result, tc.expected)
}
})
}
Expand Down
34 changes: 34 additions & 0 deletions utils.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package scimpatch

import (
"github.com/elimity-com/scim"
"github.com/elimity-com/scim/schema"
filter "github.com/scim2/filter-parser/v2"
)

var (
attributeMutabilityImmutable = "immutable"
attributeMutabilityReadOnly = "readOnly"
)

func cannotBePatched(op string, attr schema.CoreAttribute) bool {
return isImmutable(op, attr) || isReadOnly(attr)
}

func isImmutable(op string, attr schema.CoreAttribute) bool {
return attr.Mutability() == attributeMutabilityImmutable && (op == scim.PatchOperationReplace || op == scim.PatchOperationRemove)
}

func isReadOnly(attr schema.CoreAttribute) bool {
return attr.Mutability() == attributeMutabilityReadOnly
}

func containsURIPrefix(path *filter.Path) (string, bool) {
ok := false
uriPrefix := ""
if path != nil && path.AttributePath.URIPrefix != nil {
ok = true
uriPrefix = *path.AttributePath.URIPrefix
}
return uriPrefix, ok
}

0 comments on commit c57df3c

Please sign in to comment.