Repository: scimmyjs/scimmy (js)

Issue:
Complex value gives error response if unknown sub-attribute is sent
If we disable the `name.honorificPrefix` field from the user definition (`SCIMMY.Schemas.User.definition.truncate('name.honorificPrefix')`) and then send data that contains that field, such as:

```json
{
  "addresses": [{
    "country": "Philippines",
    "streetAddress": "870 McLaughlin Turnpike",
    "formatted": "Suite 431 1780 Albert Green, Holliefort, NE 20440-9954",
    "postalCode": "40331",
    "locality": "Tanikaport",
    "primary": true
  }],
  "displayName": "Lavonda Runolfsdottir",
  "nickName": "Sherman Wadd Evver",
  "externalId": "travis.spinka",
  "active": true,
  "userName": "elenafadel",
  "title": "Forward Creative Director",
  "phoneNumbers": [{
    "value": "818-532-0271",
    "primary": true
  }],
  "emails" : [{
    "value": "joya.considine@opensource.suvera.dev",
    "primary": true
  }],
  "password": "garth.yost",
  "schemas": ["urn:ietf:params:scim:schemas:core:2.0:User"],
  "name": {
    "formatted": "Cathrine Gleason",
    "familyName": "Becker",
    "givenName": "Jacklyn",
    "honorificPrefix": "Mrs."
  }
}
```

We will get a 400 error:
```json
{
  "schemas": ["urn:ietf:params:scim:api:messages:2.0:Error"],
  "status": "400", "scimType": "invalidValue",
  "detail": "Complex attribute 'name' does not declare subAttribute 'honorificPrefix'"
}
```

We should instead ignore such fields, just as we do when specifying `SCIMMY.Schemas.User.definition.truncate('phoneNumbers')`, which gives a 200 response for the input above.

I am not sure how to fix this myself, but it likely involves updating https://github.com/scimmyjs/scimmy/blob/8a25458fbc122e27d6b65cf52a91bdd0d140a62d/src/lib/types/attribute.js#L581 and the tests.

Pull request description:
Ignore unknown sub-attributes when coercing complex values in `SCIMMY.Types.Attribute`
Currently, when a client creates or updates a resource containing a complex attribute, if the value contains an unknown sub-attribute, a `400 Bad Request` exception with `scimType` of `invalidValue` will be raised. If instead the resource directly contains an unknown attribute value, no exception will be raised and it will be stripped from the resource instance.

This change removes the exception raised when complex attribute values contain unknown sub-attribute values, bringing the behaviour in line with handling of direct unknown attribute values (fixes #41). As the `SCIMMY.Config.set` method used the previous exception to determine whether supplied values contained unknown attributes, it has also been updated to iterate over the attribute names of the supplied complex value and check that each is defined in the ServiceProviderConfig schema. Test fixtures have also been updated to reflect this change.

Patch:
diff --git a/src/lib/config.js b/src/lib/config.js
index 28f59dd..ecd55eb 100644
--- a/src/lib/config.js
+++ b/src/lib/config.js
@@ -193,6 +193,9 @@ export default class Config {
                     // No shorthand, make sure value is an object
                     else if (value === Object(value)) {
                         try {
+                            // Make sure all object keys correspond to valid config attributes
+                            for (let name of Object.keys(value)) Schemas.ServiceProviderConfig.definition.attribute(`${key}.${name}`);
+                            
                             // Coerce the value and assign it to the config property
                             Object.assign(target, Schemas.ServiceProviderConfig.definition.attribute(key)
                                 .coerce({...target, supported: true, ...value}));
diff --git a/src/lib/types/attribute.js b/src/lib/types/attribute.js
index 309d500..cfabbff 100644
--- a/src/lib/types/attribute.js
+++ b/src/lib/types/attribute.js
@@ -573,15 +573,8 @@ export class Attribute {
                         for (let [key, value] of Object.entries(source)) try {
                             target[key.toLowerCase()] = value;
                         } catch (ex) {
-                            // Attempted to add an undeclared attribute to the value
-                            if (ex instanceof TypeError && ex.message.endsWith("not extensible")) {
-                                ex.message = `Complex attribute '${this.name}' `
-                                    + (typeof source !== "object" || Array.isArray(source)
-                                    ? `expected complex value but found type '${typeof source}'`
-                                    : `does not declare subAttribute '${key}'`);
-                            }
-                            
-                            throw ex;
+                            // Ignore attempts to add an undeclared attribute to the value, raise all other errors
+                            if (!(ex instanceof TypeError && ex.message.endsWith("not extensible"))) throw ex;
                         }
                         
                         // Reassign values to catch missing required sub-attributes


Test patch:
diff --git a/test/lib/config.js b/test/lib/config.js
index 832196e..c7e3988 100644
--- a/test/lib/config.js
+++ b/test/lib/config.js
@@ -58,7 +58,7 @@ describe("SCIMMY.Config", () => {
                 {name: "TypeError", message: "SCIM configuration: schema does not define attribute 'test'"},
                 "Static method 'set' accepted unknown attribute object key");
             assert.throws(() => SCIMMY.Config.set("patch", {test: true}),
-                {name: "TypeError", message: "SCIM configuration: complex attribute 'patch' does not declare subAttribute 'test'"},
+                {name: "TypeError", message: "SCIM configuration: attribute 'patch' of schema 'urn:ietf:params:scim:schemas:core:2.0:ServiceProviderConfig' does not declare subAttribute 'test'"},
                 "Static method 'set' accepted unknown sub-attribute object key");
         });