From 2f7042becb004e362f0dbaa88b26b5e93f3ca17f Mon Sep 17 00:00:00 2001 From: Andrew Hodgkinson Date: Thu, 13 Jun 2024 15:24:27 +1200 Subject: [PATCH] Handle URNs in filters via the same strip-out approach as for PATCH traversal --- README.md | 2 ++ app/models/scimitar/lists/query_parser.rb | 31 ++++++++++++++++++- .../scimitar/lists/query_parser_spec.rb | 29 +++++++++++++++++ 3 files changed, 61 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 3d4908c..3b68bc9 100644 --- a/README.md +++ b/README.md @@ -542,6 +542,8 @@ Whatever you provide in the `::id` method in your extension class will be used a } ``` +**IMPORTANT: Attribute names must be unique** across your entire combined schema, regardless of URNs used. This is because of a limitation in Scimitar's implementation. [This GitHub issue](https://github.com/RIPAGlobal/scimitar/issues/130) explains more. If this is a problem for you, please comment on the GitHub issue to help the maintainers understand the level of demand for remediation. + Resource extensions can provide any fields you choose, under any ID/URN you choose, to either RFC-described resources or entirely custom SCIM resources. There are no hard-coded assumptions or other "magic" that might require you to only extend RFC-described resources with RFC-described extensions. Of course, if you use custom resources or custom extensions that are not described by the SCIM RFCs, then the SCIM API you provide may only work with custom-written API callers that are aware of your bespoke resources and/or extensions. Extensions can also contain complex attributes such as groups. For instance, if you want the ability to write to groups from the User resource perspective (since 'groups' collection in a SCIM User resource is read-only), you can add one attribute to your extension like this: diff --git a/app/models/scimitar/lists/query_parser.rb b/app/models/scimitar/lists/query_parser.rb index ef22333..127a987 100644 --- a/app/models/scimitar/lists/query_parser.rb +++ b/app/models/scimitar/lists/query_parser.rb @@ -466,7 +466,36 @@ def flatten_filter(filter) end end - return rewritten.join(' ') + # Handle schema IDs. + # + # Scimitar currently has a limitation where it strips schema IDs in + # things like PATCH operation path traversal; see + # https://github.com/RIPAGlobal/scimitar/issues/130. At least that + # makes things easy here; use the same approach and strip them out! + # + # We don't know which resource is being queried at this layer of the + # software. If Scimitar were to bump major version, then an extension + # to QueryParser#parse to include this information would be wise. In + # the mean time, all we can do is enumerate all extension schema + # subclasses with IDs and remove those IDs if present in the filter. + # + # Inbound unrecognised schema IDs will be left behind. If the client + # Scimitar application hasn't defined requested schemas, it would + # very likely never have been able to handle the filter either way. + # + rewritten_joined = rewritten.join(' ') + if rewritten_joined.include?(':') + + # README.md notes that extensions *must* be a subclass of + # Scimitar::Schema::Base and must define IDs. + # + known_schema_ids = Scimitar::Schema::Base.subclasses.map { |s| s.new.id }.compact + known_schema_ids.each do | schema_id | + rewritten_joined.gsub!(/#{schema_id}[\:\.]/, '') + end + end + + return rewritten_joined end # Service method to DRY up #flatten_filter a little. Applies a prefix diff --git a/spec/models/scimitar/lists/query_parser_spec.rb b/spec/models/scimitar/lists/query_parser_spec.rb index 5d8a4a8..f7a1b52 100644 --- a/spec/models/scimitar/lists/query_parser_spec.rb +++ b/spec/models/scimitar/lists/query_parser_spec.rb @@ -347,6 +347,35 @@ expect(result).to eql('emails.type eq "work" and emails.value co "@example.com" or userType eq "Admin" or ims.type eq "xmpp" and ims.value co "@foo.com"') end + # https://github.com/RIPAGlobal/scimitar/issues/116 + # + context 'with schema IDs (GitHub issue #116)' do + it 'handles simple attributes' do + result = @instance.send(:flatten_filter, 'urn:ietf:params:scim:schemas:extension:enterprise:2.0:User:employeeId eq "gsar"') + expect(result).to eql('employeeId eq "gsar"') + end + + it 'handles dotted attribute paths' do + result = @instance.send(:flatten_filter, 'urn:ietf:params:scim:schemas:extension:enterprise:2.0:User:imaginary.path eq "gsar"') + expect(result).to eql('imaginary.path eq "gsar"') + end + + it 'replaces all examples' do + result = @instance.send(:flatten_filter, 'urn:ietf:params:scim:schemas:extension:enterprise:2.0:User:employeeId eq "gsar" or urn:ietf:params:scim:schemas:extension:enterprise:2.0:User:imaginary.path eq "gsar"') + expect(result).to eql('employeeId eq "gsar" or imaginary.path eq "gsar"') + end + + it 'handles the square bracket form with schema ID at the root' do + result = @instance.send(:flatten_filter, 'urn:ietf:params:scim:schemas:extension:enterprise:2.0:User[employeeId eq "gsar"') + expect(result).to eql('employeeId eq "gsar"') + end + + it 'handles the square bracket form with schema ID and attribute at the root' do + result = @instance.send(:flatten_filter, 'urn:ietf:params:scim:schemas:extension:enterprise:2.0:User:imaginary[path eq "gsar"') + expect(result).to eql('imaginary.path eq "gsar"') + end + end + # https://github.com/RIPAGlobal/scimitar/issues/115 # context 'broken filters from Microsoft (GitHub issue #115)' do