Giter Club home page Giter Club logo

scim-patch's Introduction

SCIM-PATCH

npm version Downloads Build Status FOSSA Status Coverage Status Sonarcloud Status

RFC7644 SCIM(System for Cross-domain Identity Management) 2.0 implementation of the "Modifying with PATCH" section 3.5.2.

TL;DR

Important things to know, this library can :

  • Validate a SCIM Patch query.
  • Patch a SCIM resource from a SCIM Patch Query.

Want to have an example on how it works, check this example.

More Details

This library is implementing the 3.5.2. Modifying with PATCH chapter of the SCIM RFC https://tools.ietf.org/html/rfc7644#section-3.5.2.
It will allow you to create a SCIM resources and to patch them using the SCIM Query language.

Validation of a SCIM Query.

import {patchBodyValidation} from 'scim-patch';

const scimBody: ScimPatchOperation = 
{
  'schemas': ['urn:ietf:params:scim:api:messages:2.0:PatchOp'],
  'Operations': [
    {op: 'replace', path: 'name.familyName', value: 'newFamilyName'}
  ]
};

try {
  patchBodyValidation(scimBody);
} catch (error) {
  // Here if there are an error in you SCIM request.
}

Patch a SCIM resource from a SCIM Patch Query.

This implements the PATCH of a SCIM object from a SCIM Query. You should create a valid SCIM resource by extending the ScimResource interface.

export interface ScimUser extends ScimResource {
    schemas: ['urn:ietf:params:scim:schemas:core:2.0:User'];
    userName: string;
    name: {
        familyName: string;
        givenName: string;
    };
    active: boolean;
    emails: Array<{
        value: string;
        primary: boolean;
    }>;
    roles?: Array<{
        value: string;
        type?: string;
    }>;
    meta: ScimMeta & { resourceType: 'User' };
};

After you have created your object you can patch it by calling the scimPatch operation.

const scimUser: ScimUser = {
  schemas: ['urn:ietf:params:scim:schemas:core:2.0:User'],
  userName: '[email protected]',
  name: { familyName: 'user1', givenName: 'user2' },
  active: true,
  emails: [{value: '[email protected]', primary: true}],
  meta: { resourceType: 'User', created: new Date(), lastModified: new Date() }
};

const patch: ScimPatchOperation = { op: 'replace', value: { active: false } };
const patchedUser = scimPatch(scimUser, patch);
// scimUser === patchedUser, see Options section if you want to avoid updating the original object

This particular operation will return :

{ 
  "schemas": [ "urn:ietf:params:scim:schemas:core:2.0:User" ],
  "userName": "[email protected]",
  "name": { "familyName": "user1", "givenName": "user2" },
  "active": false,
  "emails": [{"value": "[email protected]", "primary": true }],
  "meta": { "resourceType": "User", "created": "2019-12-19T14:36:08.838Z", "lastModified": "2019-12-19T14:36:08.838Z" }
}

Options

Mutate Document

By default scimPatch() is updating the scim resource you pass in the function.
If you want to avoid this, you can add an option while calling scimPatch(), it will do a copy of the object and work on this copy.

Your call will look like this now:

const patchedUser = scimPatch(scimUser, patch, {mutateDocument: false});
// scimUser !== patchedUser
Treat Missing as Add

By default scimPatch() will treat as Add a replace operation that targets an attribute that does not exist. If you prefer to throw an error instead, then set treatMissingAsAdd: false

// scimUser has no addresses
 const patch = {
    op: 'replace',
    path: 'addresses[type eq "work"].country',
    value: 'Australia',
};
const patchedUser = scimPatch(scimUser, patch, {treatMissingAsAdd: false});
// patchedUser.addresses[0].country === "Australia"

How can I contribute?

See the contributor's guide for some helpful tips.

Contributors

Thanks so much to our contributors.

scim-patch's People

Contributors

alicial avatar asutosh97 avatar barryhagan avatar dependabot-preview[bot] avatar dependabot[bot] avatar francesco-lanciana avatar jirihofman avatar leonardo-speranzon avatar luddd3 avatar markwongsk avatar randomhash avatar thomaspoignant avatar xtellurian avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar

scim-patch's Issues

Remove not working properly

Describe the bug
Hello, remove value from array leads to clean remove of all items from array

To Reproduce
Object to patch

{
      schemas: [ 'urn:ietf:params:scim:schemas:core:2.0:Group' ],
      id: 'some-id',
      objectsToPatch: [
        {
          value: 'remove-me',
          display: ''
        },
        {
          value: 'do-not-remove-me',
          display: ''
        }
      ]
    }

Operations:

[
    {
      op: 'remove',
      path: 'objectsToPatch',
      value: [
        {
          value: 'remove-me',
          display: ''
        }
      ]
    }
  ]

Actual results

{
      schemas: [ 'urn:ietf:params:scim:schemas:core:2.0:Group' ],
      id: 'some-id'
}

Expected behavior

{
      schemas: [ 'urn:ietf:params:scim:schemas:core:2.0:Group' ],
      id: 'some-id',
      objectsToPatch: [
        {
          value: 'do-not-remove-me',
          display: ''
        }
      ]
}

Additional information

image

This part treated by the big SCIM users, as one login, as shown in the example above

Path Resolution

Describe the bug
Expression emails[value eq '[email protected]'] returns Invalid SCIM Patch: This part of the path emails[value eq 'jjones@entitechsolutions is invalid for SCIM patch request

To Reproduce
Steps to reproduce the behavior:

  1. Submit emails[value eq '[email protected]'] to ScimPath

Expected behavior
Should execute filter expression

Desktop (please complete the following information):

  • OS: OSX
  • 12.0.1

Additional context
The issue is the path resolution which splits using a period. This means that the above expression splits into

  • emails[value eq 'jjones@blah
  • com']
    which should presumably be
  • emails[value eq '[email protected]']

Add multi-valued attributes

Describe the bug
"Add" operation does not function correctly for multi-valued attributes. Per https://tools.ietf.org/html/rfc7644#page-33, an add operation for a multi-valued attribute should use an array value, i.e. { "op":"add", "path": "members", "value": [ { ... } ] }, but scimPatch treats the array as the value, as if it expects { "op":"add", "path": "members", "value": { ... } }.

To Reproduce
(Javascript)

const scimPatch = require("scim-patch");
const assert = require('assert');

const scimUser = {
  schemas: ['urn:ietf:params:scim:schemas:core:2.0:User'],
  members: [
    {
      display: "The Wizzard",
      "$ref": "https://example.com/v2/Users/xxxx",
      value: "xxxx"
    },
    // Should also be able to comment this member out...
    {
      display: "Babs Jensen",
      "$ref": "https://example.com/v2/Users/2819c223-7f76-453a-919d-413861904646",
      value: "2819c223-7f76-453a-919d-413861904646"
    }
    // ... and get the same result
  ]
};
const patches = [
  {
    op: 'Add',
    path: 'members',
    value: [
      {
        display: "Babs Jensen",
        "$ref": "https://example.com/v2/Users/2819c223-7f76-453a-919d-413861904646",
        value: "2819c223-7f76-453a-919d-413861904646"
      }
    ]
  }
];


try {
  scimPatch.patchBodyValidation({
    schemas: ['urn:ietf:params:scim:api:messages:2.0:PatchOp'],
    Operations: patches
  });
}
catch (error) {
  console.log(error);
}

const patchedScimUser = (()=>{
  try {
    return scimPatch.scimPatch(scimUser, patches);
  }
  catch (error) {
    console.log(error);
    return scimUser;
  }
})();

const expectMembers = [
  {
    display: "The Wizzard",
    "$ref": "https://example.com/v2/Users/xxxx",
    value: "xxxx"
  },
  {
    display: "Babs Jensen",
    "$ref": "https://example.com/v2/Users/2819c223-7f76-453a-919d-413861904646",
    value: "2819c223-7f76-453a-919d-413861904646"
  },
];

patchedScimUser.members.forEach(
  (member, i) => assert.deepEqual(
    expectMembers.length > i ? expectMembers[i] : undefined,
    member,
  )
);

Expected behavior
Assertion should succeed. The new member should be found to exist in the "members" array and should not be added. (If the member were not found, it should be added.)

Replace operations for attributes that do not exist should be treated as an add

Active Directory sends SCIM messages with type=replace for attributes that do not exist.

I'd like to be able to control whether those replace messages are handled as adds.

I considered adding my own checks for this case outside this library, but
a) It would make for ugly code
b) It seems like others may encounter this issue

The relevant part of the spec is:
https://www.rfc-editor.org/rfc/rfc7644#section-3.5.2.3

If the target location path specifies an attribute that does not
exist, the service provider SHALL treat the operation as an "add".

Fix scimPatch type signature to include generics.

Is your feature request related to a problem? Please describe.
The type signature of scimPatch requires unnecessary type casts on the consumer end.

Describe the solution you'd like
Instead of

scimPatch(scimResource: ScimResource, patchOperations: Array<ScimPatchOperation>): ScimResource

the signature

scimPatch<T extends ScimResource>(scimResource: T, patchOperations: Array<ScimPatchOperation>): T

Allows the consumer to pass in their own ScimResource type.

Describe alternatives you've considered
Manually typecasting the result back eg

scimPatch(resource, ops) as MyScimResourceType;

Additional context
Here's a proposed PR: markwongsk#1 which I can open here if this change sounds reasonable.

Cannot add new entry to multi-valued attribute using a path filter

Describe the bug

Similar to the issue in #42, when using Azure AD, SCIM PATCH requests can be sent using filters to add entries to a multi-valued attribute. The fix for #42 handles completely missing or empty array attributes, but we also need to support adding a new value to an existing non-empty array.

Attempting this type of PATCH currently throws with this message: Invalid SCIM Patch: Target location is a multi-valued attribute for which a value selection filter (:patch.value) has been supplied and no record match was made.

Additionally, when throwing NoTarget, the message should include the patch.path, not the patch.value. Right now you get the value you are trying to set, not the filter path.

To Reproduce

  • Create a SCIM user that has a "home" address.
  • Attempt to PATCH that user with a new "work" address.
{
    "schemas": [
        "urn:ietf:params:scim:api:messages:2.0:PatchOp"
    ],
    "Operations":
    [
        {"op":"Add", "path":"addresses[type eq \"work\"].formatted", "value":"123 fake st."}
    ]   
}

Expected behavior
The new work address is added, and the user now has both "home" and "work" type addresses.

Additional context
Add any other context about the problem here.

scimPatch mutates the original object

Describe the bug
Currently, the scimPatch function mutates the original object passed in the argument. Can we change it to take a copy of the original before applying the patches?

To Reproduce

// Declare some scimResource & payload
const patchedResource = scimPatch(scimResource, payload.Operations);
console.log(patchedResource === scimResource); // true

Expected behavior
The output for the above script should be false

Additional context
Incase if we want to ensure backward compatibility, how about introducing a flag that is used to decide whether to mutate the original object or take a copy

Add sub-attribute to empty multi-valued attribute

Describe the bug
Add/Replace a specific sub-attribute of a complex multi-valued attribute selected by "valuePath" filter is not working when current value if undefined/null or an empty array [] is provided.

To Reproduce

const scimPatch = require("scim-patch")
const assert = require("assert")

const resource = {
  schemas: ["urn:ietf:params:scim:schemas:core:2.0:User"],
  // addresses: [],
}

const patches = [
  { op: "Add", path: "addresses[type eq \"work\"].streetAddress", value: "1010 Broadway Ave"},
]

const expected = {
  schemas: ["urn:ietf:params:scim:schemas:core:2.0:User"],
  addresses: [{
    type: "work",
    streetAddress: "1010 Broadway Ave"
  }]
}


const patchedResource = scimPatch.scimPatch(resource, patches)

// throws
// InvalidScimPatchOp [Error]: Invalid SCIM Patch: Impossible to search on a mono valued attribute.

assert.deepStrictEqual(patchedResource, expected)

When empty array is provided, another error occurs

const resource = {
  schemas: ["urn:ietf:params:scim:schemas:core:2.0:User"],
  addresses: [],
}

const patches = [
  { op: "Add", path: "addresses[type eq \"work\"].streetAddress", value: "1010 Broadway Ave"},
]

scimPatch.scimPatch(resource, patches)

// throws
// /scim-patch/lib/src/scimPatch.js:115
//          resource[lastSubPath] = addOrReplaceAttribute(resource[lastSubPath], patch);
//                                                              ^
//
//  TypeError: Cannot read property 'streetAddress' of undefined

Expected behavior
The new "streetAddress" and "type" should be added to the resource.
Expeced value for the addresses attribute is:

[
  {
    "type": "work",
    "streetAddress": "1010 Broadway Ave"
  }
]

patchBodyValidation should be resistent if body.Operations is not an array

Describe the bug
In my unit-test suite I test my scim service against a various number of invalid patch requests. I just tested my test suite with this node package and encountered that the library crashes if operations is not an array. Sure this is a edge case and an invalid request but the package should be able to handle this.

To Reproduce
Steps to reproduce the behavior:

Sample request:

{
  "Operations": {
    "op": "add",
    "path": "members",
    "value": {}
  },
  "schemas": [
    "urn:ietf:params:scim:api:messages:2.0:PatchOp"
  ]
}

exception:

TypeError: body.Operations.forEach is not a function
    at Object.patchBodyValidation (/***/node_modules/scim-patch/src/scimPatch.ts:87:21)

Patch with replace + non-matching filter on a multi-valued attribute does not error

Describe the bug
Currently, when I patch a multi-valued attribute with a valuePath filter that matches nothing, the replacement value gets added as a multi-valued attribute. The SCIM spec details:

o If the target location is a multi-valued attribute for which a
value selection filter ("valuePath") has been supplied and no
record match was made, the service provider SHALL indicate failure
by returning HTTP status code 400 and a "scimType" error code of
"noTarget".

This is probably due to this line: https://github.com/thomaspoignant/scim-patch/blob/master/src/scimPatch.ts#L194, which I think is misinterpreting complex attribute as a multi-valued attribute.

To Reproduce

    let group = { id: 'blah', displayName: 'name', members: [] }; // empty SCIM group.
    const op = [
      {   
        op: 'replace',
        path: 'members[value eq "bogus"]',
        value: 'this value should not be added',
      },  
    ];
    const patchedGroup = scimPatch(group, op);

Expected behavior
a SCIM Error of noTarget
Actual behavior
'this value should not be added' is added as an array value of group.members

Replace op with value of empty object results in circular reference

Describe the bug
We've seen Azure AD SCIM send replace operations with an empty object in the value field:

{
  op: 'replace',
  path: 'phoneNumbers[type eq "work"].value',
  value: {},
}

They sometimes send this operation when there isn't a multi-valued attribute of that type. When this happens, the scim-patch library applies the update in a way that introduces a circular reference to the resource object. This causes errors when we call JSON.stringify on the resource object.

To Reproduce

const scimPatch = require("scim-patch")
const assert = require("assert")

const resource = {
   phoneNumbers: [
    {
      type: 'mobile',
      value: '7865439908',
    },
  ],
}

const operation = {
  op: 'replace',
  path: 'phoneNumbers[type eq "work"].value',
  value: {},
}

const expected = {
  phoneNumbers: [
    {
      type: 'mobile',
      value: '7865439908',
    },
    {
      type: 'work',
      value: {},
    },
  ],
}

const patchedResource = scimPatch.scimPatch(resource, [operation], { mutateDocument: false, treatMissingAsAdd: true },)

assert.deepStrictEqual(patchedResource, expected)

console.log(patchedResource);
=> {
      phoneNumbers: [
        { type: 'mobile', value: '7865439908' },
        { type: 'work', value: [Circular *1] }
      ]
    }

Expected behavior
We would expect operations to be applied to resources in a way that doesn't introduce a circular reference in the object. Though, admittedly, this update is a bit strange from Azure AD SCIM (the value should be a string here I believe, and this only seems to happen with an empty object).

Additional context
We wanted to flag this in the upstream library. We're currently looking at workarounds on our end to either edit the resource before sending it to the library, or checking for circular references when we get the resource from the library.

Path with a complex filter match only the first occurrence

Describe the bug
When using a selection filter on multi-valued complex attribute only the first occurrence is considered.

To Reproduce

const scimUser: ScimUser = {
    schemas: ['urn:ietf:params:scim:schemas:core:2.0:User'],
    userName: '[email protected]',
    name: { familyName: 'user1', givenName: 'user2' },
    active: true,
    emails: [
        {value: '[email protected]', primary: true},
        {value: '[email protected]', primary: true},
        {value: '[email protected]', primary: true},
        {value: '[email protected]', primary: false},
        {value: '[email protected]', primary: false},
        {value: '[email protected]', primary: false},
        {value: '[email protected]', primary: false},
        {value: '[email protected]', primary: false},
        {value: '[email protected]', primary: false},
        {value: '[email protected]', primary: false},
        {value: '[email protected]', primary: false},
    ],
    meta: { resourceType: 'User', created: new Date(), lastModified: new Date() }
};

const patches: ScimPatchOperation[] = [
    { op: 'replace', path: 'emails[value co "gmail"].value', value: '[email protected]'  }, // Match only first
    { op: 'replace', path: 'emails[value co "gmail"]', value: { value:'[email protected]' } }, // Match only first
    { op: 'add', path: 'emails[value co "gmail"]', value: { value:'[email protected]' } }, // Match only first
    { op: 'add', path: 'emails[value co "gmail"].value', value:'[email protected]'}, // Match only first (*)
    { op: 'remove', path: 'emails[value co "gmail"].value'}, // Match only first
    // { op: 'remove', path: 'emails[value co "gmail"]'}, // This match all the sele
];

const patchedUser = scimPatch(scimUser, patches);
console.log(patchedUser)

ScimUser is defined as in the README

Expected behavior
Is expected that these operation apply to all attributes that satisfy the filter.

I'm not sure that this is the correct way to interpret the RFC.
The only one explained clearly is the one marked by the * in the last point of page 43 (of RFC 7644)

Desktop (please complete the following information):

Add/Replace operations with no path but using custom schema attributes do not work

Describe the bug
The path parsing when supplying a patch operation with no path but using a custom schema attribute is not correct.

To Reproduce
Attempt this patch operation:

        {
            "schemas": [
                "urn:ietf:params:scim:schemas:core:2.0:PatchOp"
            ],
            "Operations":[
                {
                    "op":"replace",
                    "value": {
                        "urn:ietf:params:scim:schemas:extension:enterprise:2.0:User:organization": "organization",
                        "urn:ietf:params:scim:schemas:extension:enterprise:2.0:User:department": "department"
                   }
                },
                {
                    "op":"add",
                    "value": {
                        "urn:ietf:params:scim:schemas:extension:enterprise:2.0:User:division": "division"
                   }
                }
            ]
        }

Expected behavior
The add and replace operations should work even when using custom schema attributes.

Additional context
We are applying to be part of the Azure App Gallery and this was one of the tests that we failed and need to fix in order to be approved.

validate should throw an error if a replace operation is called without an value

Describe the bug
In my unit-test suite I test my scim service against a various number of invalid patch requests. I just tested my test suite with this node package and encountered that the following call is not rejected even though the following body is not valid IMO.

To Reproduce
Steps to reproduce the behavior:

Sample request:

{
  "Operations": [
    {
      "op": "replace",
      "path": "displayName"
    }
  ],
  "schemas": [
    "urn:ietf:params:scim:api:messages:2.0:PatchOp"
  ]
}

expected

the function patchBodyValidation should throw an error.

Parsing of paths that include schema URIs is not correct

Describe the bug
The path parsing when supplying a patch operation for a custom schema path is not correct per https://datatracker.ietf.org/doc/html/rfc7644#section-3.10

Additionally, the parser will not handle core schema URIs in patch paths.

The patch below will not update properties on the user, but the paths are valid per the rules in 3.10.

To Reproduce
Attempt this patch operation:

        {
            "schemas": [
                "urn:ietf:params:scim:schemas:core:2.0:PatchOp"
            ],
            "Operations":[
                {
                    "op":"replace",
                    "path":"urn:ietf:params:scim:schemas:core:2.0:User:name.givenName",
                    "value":"newName"
                },
                {
                    "op":"replace",
                    "path":"urn:ietf:params:scim:schemas:extension:enterprise:2.0:User:division",
                    "value":"newDivision"
                }
            ]
        }

Expected behavior
Both the user's givenName and division would be updated.

Add operations failing for addresses and phoneNumbers

Describe the bug
When scimUser does not have type:work in the addresses array and we do Add operation on "path": "addresses[type eq "work"].country", Patch is failing due to the below error thrown in addOrReplaceObjectAttribute

if (patch.op === 'add')
            throw new scimErrors_1.InvalidScimPatchOp('Invalid patch query.');

When i comment the above error line, it works perfectly fine. Another solution is to pre populate the address and phoneNumbers like below (this works for only cases where nothing is present in these fields)

 scimUser.addresses = scimUser.addresses || [{ type: 'work' }];
  scimUser.phoneNumbers = scimUser.phoneNumbers || [{ type: 'mobile' }];

To Reproduce
Steps to reproduce the behavior:
You can try this on runkit as well.

const {scimPatch} = require("scim-patch")

const scimUser = {"userName":"[email protected]","emails":[{"primary":true,"type":"work","value":"[email protected]"}],"schemas":["urn:ietf:params:scim:schemas:core:2.0:User"],"id":"b83fb889"};

const operations = [{"op":"add","path":"addresses[type eq \"work\"].country","value":"country11"}]
const patched = scimPatch(scimUser, operations)

Expected behavior
Expecting it to work without throwing the error and add type work if its not already present.

Additional context
Its one of the Microsoft Azure AD test cases that we received when we submitted our API for user provisioning gallery app.

`remove` operation on nested fields that don't exist ends up adding fields till (depth - 1)

Description & Steps to Reproduce

const userResource = {
    "schemas": [
        "urn:ietf:params:scim:schemas:core:2.0:User"
    ],
    "id": "tea_4",
    "userName": "spiderman",
    "name": {
        "familyName": "Parker",
        "givenName": "Peter"
    },
    "active": true,
    "emails": [
        {
            "value": "[email protected]",
            "primary": true
        }
    ],
    "roles": [],
    "meta": {
        "resourceType": "User",
        "created": "2019-11-20T09:25:30.208Z",
        "lastModified": "2019-11-20T09:25:30.208Z",
        "location": "**REQUIRED**/Users/tea_4"
    },
};

const operations = [
    {
        op: "remove",
        path: "someField.level_1_depth.level_2_depth.final_depth",
    }
];

const patchedUser = scimPatch(userResource, operations);
console.log(patchedUser);

If we run the above script, it results in the following output

{
    "schemas": [
        "urn:ietf:params:scim:schemas:core:2.0:User"
    ],
    "id": "tea_4",
    "userName": "spiderman",
    "name": {
        "familyName": "Parker",
        "givenName": "Peter"
    },
    "active": true,
    "emails": [
        {
            "value": "[email protected]",
            "primary": true
        }
    ],
    "roles": [],
    "meta": {
        "resourceType": "User",
        "created": "2019-11-20T09:25:30.208Z",
        "lastModified": "2019-11-20T09:25:30.208Z",
        "location": "**REQUIRED**/Users/tea_4"
    },
    "someField": {
        "level_1_depth": {
            "level_2_depth": {}
        }
    }
};

Expected behavior
The expected output would be: the original document remains untouched

Some Azure AD remove operations are not supported

Describe the bug
Here is a SCIM PATCH request that I received from Azure AD that looks like this:

{ op: 'remove', 'path': 'manager[value eq "manager-id"]'};

The error that I got was:

Error: Invalid SCIM Patch: Impossible to search on a mono valued attribute.
    at extractArray (/app/node_modules/scim-patch/lib/src/scimPatch.js:227:15)
    at applyRemoveOperation (/app/node_modules/scim-patch/lib/src/scimPatch.js:145:44)
    at /app/node_modules/scim-patch/lib/src/scimPatch.js:67:24
    at Array.reduce (<anonymous>)
    at scimPatch (/app/node_modules/scim-patch/lib/src/scimPatch.js:63:28)

To Reproduce

  1. In the test file scimPatch.test.ts, add this test:
it('REMOVE: array filter on complex field (Azure AD)', done => {
  const schemaExtension = 'urn:ietf:params:scim:schemas:extension:enterprise:2.0:User';
  const patch: ScimPatchRemoveOperation = { op: 'remove', 'path': 'manager[value eq "manager-id"]'};
  scimUser[schemaExtension] = {
    manager: {
        value: 'manager-id',
        $ref: ''
    }
  };
  const afterPatch: any = scimPatch(scimUser, [patch]);

  expect(afterPatch[schemaExtension].manager).not.to.exist;
  return done();
});
  1. Run npm run build and npm run test, receive the same error:
SCIM PATCH
       remove
         REMOVE: array filter on complex field (Azure AD):
     Error: Invalid SCIM Patch: Impossible to search on a mono valued attribute.
      at extractArray (lib/src/scimPatch.js:60:563)
      at applyRemoveOperation (lib/src/scimPatch.js:41:59)
      at /Users/mac-yubingh/Projects/github.com/yubing24/scim-patch/lib/src/scimPatch.js:25:349
      at Array.reduce (<anonymous>)
      at scimPatch (lib/src/scimPatch.js:25:137)
      at Context.<anonymous> (lib/test/scimPatch.test.js:941:58)
      at process.processImmediate (node:internal/timers:471:21)

Expected behavior
I am expecting that the manager field should be removed from the Enterprise User schema in the patched document.

Screenshots
N/A

Desktop (please complete the following information):

  • OS: Darwin <computer-name> 22.5.0 Darwin Kernel Version 22.5.0: Thu Jun 8 22:22:22 PDT 2023; root:xnu-8796.121.3~7/RELEASE_X86_64 x86_64
  • Browser: N/A
  • Version: 0.7.1

Smartphone (please complete the following information):

  • Device: N/A
  • OS: N/A
  • Browser: N/A
  • Version: N/A

Additional context
Add any other context about the problem here.

scim-patch partial match doesn't work

Describe the bug
Sending a request using patch with a partial match to the value attribute does not complete the operation passed in the request.
Using this example request from ietf rfc7644:

{ "schemas":
       ["urn:ietf:params:scim:api:messages:2.0:PatchOp"],
     "Operations":[
       {
        "op":"add",
        "path":"members",
        "value":[
         {
           "display": "Babs Jensen",
           "$ref":
   "https://example.com/v2/Users/2819c223...413861904646",
           "value": "2819c223-7f76-453a-919d-413861904646"
         }
        ]
       },
       ... + additional operations if needed ...
     ]
   }

Having the 'value' attribute only have either the 'display' or 'value' sub-attribute (and not both) does not complete the operation.

To Reproduce
Steps to reproduce the behavior:

Following the above example, let's also assume that the 'display' attribute is an email field and that a user exists, send the request with that user's email address - in the display attribute - with (e.g. a remove operation) and you will see that the user will not be removed. It requires that the sub-attribute 'value' is also populated.

Expected behavior

I would expect that the 'display' attribute would be enough to identify the user and apply the operation passed in the request.

Screenshots

{ "schemas":
       ["urn:ietf:params:scim:api:messages:2.0:PatchOp"],
     "Operations":[
       {
        "op":"add",
        "path":"members",
        "value":[
         {
           "display": "[email protected]"
         }
        ]
       },
     ]
   }

Desktop (please complete the following information):

  • OS: Windows
  • Browser: Chrome

'add' on array attribute performs 'replace' when 'path' is absent

Describe the bug

Let's look at the following 4 code snippets and their corresponding outputs

Case 1

Code

const scimResource = {
    displayName: "ramayan",
    members: [
        { name: "ram" },
        { name: "laxman" }
    ]
}

const patches = {
    Operations: [
        {
            op: "add",
            path: "members",
            value: { name: "hanuman" }
        },
    ]
}

const patchedResource = scimPatch(scimResource, patches.Operations, { mutateDocument: false });
console.log(patchedResource);

Output

{
  displayName: 'ramayan',
  members: [ { name: 'ram' }, { name: 'laxman' }, { name: 'hanuman' } ]
}

Case 2

Code

const scimResource = {
    displayName: "ramayan",
    members: [
        { name: "ram" },
        { name: "laxman" }
    ]
}

const patches = {
    Operations: [
        {
            op: "add",
            path: "members",
            value: [{ name: "hanuman" }]
        },
    ]
}

const patchedResource = scimPatch(scimResource, patches.Operations, { mutateDocument: false });
console.log(patchedResource);

Output

{
  displayName: 'ramayan',
  members: [ { name: 'ram' }, { name: 'laxman' }, { name: 'hanuman' } ]
}

Case 3

Code

const scimResource = {
    displayName: "ramayan",
    members: [
        { name: "ram" },
        { name: "laxman" }
    ]
}

const patches = {
    Operations: [
        {
            op: "add",
            value: {
                members: { name: "hanuman" }
            }
        },
    ]
}

const patchedResource = scimPatch(scimResource, patches.Operations, { mutateDocument: false });
console.log(patchedResource);

Output

{ displayName: 'ramayan', members: { name: 'hanuman' } }

Case 4

Code

const scimResource = {
    displayName: "ramayan",
    members: [
        { name: "ram" },
        { name: "laxman" }
    ]
}

const patches = {
    Operations: [
        {
            op: "add",
            value: {
                members: [{ name: "hanuman" }]
            }
        },
    ]
}

const patchedResource = scimPatch(scimResource, patches.Operations, { mutateDocument: false });
console.log(patchedResource);

Output

{ displayName: 'ramayan', members: [ { name: 'hanuman' } ] }

Case 1 & 2 give the correct output, whereas case 3 & 4 produce the wrong output.
For case 3 & 4, if the operation was "replace", then that output would have been correct.

I've figured out a fix, will be creating a PR soon

Add operations with no path and object dot notation not supported

Describe the bug
Azure AD SCIM sends their add operations for name attributes in the following format:

{
  "op": "add",
  "value": {
    "name.givenName": "John",
    "name.familyName": "Doe",
    "name.formatted": "John Doe"
  }
}

When you apply this operation to a resource using this library, it doesn't recognize the object dot notation, and adds the attributes in the following way:

{
  name.givenName: "John",
  name.familyName: "Doe",
  name.formatted: "John Doe"
}

However, we would expect them to be added like the following:

{
  name: {
    givenName: "John",
    familyName: "Doe",
    formatted: "John Doe"
  }
}

To Reproduce

const scimPatch = require("scim-patch")
const assert = require("assert")

const resource = {
  email: '[email protected]',
}

const operation = {
  op: 'add',
  value: {
    'name.givenName': 'John',
    'name.familyName': 'Doe',
    'name.formatted': 'John Doe',
  },
}

const expected = {
  email: '[email protected]',
  name: {
    givenName: "John",
    familyName: "Doe",
    formatted: "John Doe",
  },
}


const patchedResource = scimPatch.scimPatch(resource, [operation])

assert.deepStrictEqual(patchedResource, expected)

Expected behavior
The library should support parsing of attributes in the object dot notation. This only occurs in one level of nesting, and just for add operations.

Additional context
I reached out to Microsoft support because I didn't think this agreed with the SCIM RFC. They quoted the following in their argument that it does agree with the RFC:

Took me a bit to find it in the spec – but it’s here: https://datatracker.ietf.org/doc/html/rfc7644#page-37
The following example shows how to add one or more attributes to a User resource without using a "path" attribute.

PATCH /Users/2819c223-7f76-453a-919d-413861904646
   Host: example.com
   Accept: application/scim+json
   Content-Type: application/scim+json
   Authorization: Bearer h480djs93hd8
   If-Match: W/"a330bc54f0671c9"
 
   {
     "schemas":
       ["urn:ietf:params:scim:api:messages:2.0:PatchOp"],
     "Operations":[{
       "op":"add",
       "value":{
         "emails":[
           {
             "value":[email protected],
             "type":"home"
           }
         ],
         "nickname":"Babs"
     }]
   }
 
   In the above example, an additional value is added to the
   multi-valued attribute "emails".  The second attribute, "nickname",
   is added to the User resource.  If the resource already had an
   existing "nickname", the value is replaced per the processing rules
   above for single-valued attributes.

The section I previously linked (page 37 or 7644) combined with https://datatracker.ietf.org/doc/html/rfc7644#section-3.10 together explain how we’re allowed to do this.

3.10.  Attribute Notation
 
   All operations share a common scheme for referencing simple and
   complex attributes.  In general, attributes are uniquely identified
   by prefixing the attribute name with its schema URN separated by a
   colon (":") character; e.g., the core User resource attribute
   'userName' is identified as
   "urn:ietf:params:scim:schemas:core:2.0:User:userName".  Clients MAY
   omit core schema attribute URN prefixes but SHOULD fully qualify
   extended attributes with the associated schema extension URN to avoid
   naming conflicts.  For example, the attribute 'age' defined in
   "urn:ietf:params:scim:schemas:exampleCo:2.0:hr" is uniquely
   identified as "urn:ietf:params:scim:schemas:exampleCo:2.0:hr:age".
   Complex attributes' sub-attributes are referenced via nested dot
   ('.') notation, i.e., {urn}:{Attribute name}.{Sub-Attribute name}.
   For example, the fully qualified path for a User's givenName is
   "urn:ietf:params:scim:schemas:core:2.0:User:name.givenName".  All
   facets (URN, attribute, and sub-attribute name) of the fully encoded
   attribute name are case insensitive.

Replace array does not replace the array

A new array is pushed instead:

members [ { value: 'test1' } ]
operation { op: 'replace', path: 'members', value: [ { value: 'test2' } ] }
members [ { value: 'test1' }, [ { value: 'test2' } ] ]

`addOrReplaceObjectAttribute` raises the exception when an property is null

Version: 0.5.6

Description

When an property is null, addOrReplaceObjectAttribute raises the following exception: Invalid patch query

scimPatch({ externalId: null }, [{ op: "add", path: "externalId", value: "1" }])

However, it behaves properly while adding value to undefined property:

const result = scimPatch({ externalId: undefined }, [{ op: "add", path: "externalId", value: "1" }]);

expect(result).toEqual({ externalId: "1" })

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.