Skip to content

Commit

Permalink
Resolve #36 to produce v2.1.3 (building on #38)
Browse files Browse the repository at this point in the history
  • Loading branch information
pond committed Jan 9, 2023
1 parent 76e88a9 commit 4424b0b
Show file tree
Hide file tree
Showing 11 changed files with 104 additions and 17 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
# 2.1.3 (2023-01-09)

* Fix https://github.com/RIPAGlobal/scimitar/issues/36 - filters are case-sensitive for special cases of `id`, `externalId` and `meta.*` attributes. A model must still declare if and how these are searchable via its `::scim_queryable_attributes` implementation, just as with any other attribute.

# 2.1.2 (2023-01-09)

* Fix https://github.com/RIPAGlobal/scimitar/issues/37 - filters now correctly support case insensitive attribute names.
Expand Down
2 changes: 1 addition & 1 deletion Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ GIT
PATH
remote: .
specs:
scimitar (2.1.2)
scimitar (2.1.3)
rails (~> 7.0)

GEM
Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -441,6 +441,8 @@ If you believe choices made in this section may be incorrect, please [create a G

* Currently filtering for lists is always matched case-insensitive regardless of schema declarations that might indicate otherwise, for `eq`, `ne`, `co`, `sw` and `ew` operators; for greater/less-thank style filters, case is maintained with simple `>`, `<` etc. database operations in use. The standard Group and User schema have `caseExact` set to `false` for just about anything readily queryable, so this hopefully would only ever potentially be an issue for custom schema.

* As an exception to the above, attributes `id`, `externalId` and `meta.*` are matched case-sensitive. Filters that use `eq` on such attributes will end up a comparison using `=` rather than e.g. `ILIKE` (arising from https://github.com/RIPAGlobal/scimitar/issues/36).

* The `PATCH` mechanism is supported, but where filters are included, only a single "attribute eq value" is permitted - no other operators or combinations. For example, a work e-mail address's value could be replaced by a PATCH patch of `emails[type eq "work"].value`. For in-path filters such as this, other operators such as `ne` are not supported; combinations with "and"/"or" are not supported; negation with "not" is not supported.

If you would like to see something listed in the session implemented, please [create a GitHub issue](https://github.com/RIPAGlobal/scimitar/issues/new) asking for it to be implemented, or if possible, implement the feature and send a Pull Request.
Expand Down
10 changes: 10 additions & 0 deletions app/models/scimitar/lists/query_parser.rb
Original file line number Diff line number Diff line change
Expand Up @@ -605,6 +605,16 @@ def apply_scim_filter(

raise Scimitar::FilterError unless all_supported

unless case_sensitive
lc_scim_attribute = scim_attribute.downcase()

case_sensitive = (
lc_scim_attribute == 'id' ||
lc_scim_attribute == 'externalid' ||
lc_scim_attribute.start_with?('meta.')
)
end

column_names.each.with_index do | column_name, index |
arel_column = arel_table[column_name]
arel_operation = case scim_operator
Expand Down
2 changes: 1 addition & 1 deletion lib/scimitar/version.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ module Scimitar
# Gem version. If this changes, be sure to re-run "bundle install" or
# "bundle update".
#
VERSION = '2.1.2'
VERSION = '2.1.3'

# Date for VERSION. If this changes, be sure to re-run "bundle install"
# or "bundle update".
Expand Down
13 changes: 8 additions & 5 deletions spec/apps/dummy/app/models/mock_user.rb
Original file line number Diff line number Diff line change
Expand Up @@ -92,11 +92,14 @@ def self.scim_mutable_attributes

def self.scim_queryable_attributes
return {
'name.givenName' => { column: :first_name },
'name.familyName' => { column: :last_name },
'emails' => { columns: [ :work_email_address, :home_email_address ] },
'emails.value' => { columns: [ :work_email_address, :home_email_address ] },
'emails.type' => { ignore: true } # We can't filter on that; it'll just search all e-mails
'id' => { column: :id },
'externalId' => { column: :scim_uid },
'meta.lastModified' => { column: :updated_at },
'name.givenName' => { column: :first_name },
'name.familyName' => { column: :last_name },
'emails' => { columns: [ :work_email_address, :home_email_address ] },
'emails.value' => { columns: [ :work_email_address, :home_email_address ] },
'emails.type' => { ignore: true } # We can't filter on that; it'll just search all e-mails
}
end

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
class AddTimestampsToMockUser < ActiveRecord::Migration[7.0]
def change
add_timestamps :mock_users
end
end
5 changes: 3 additions & 2 deletions spec/apps/dummy/db/schema.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.

ActiveRecord::Schema.define(version: 2021_03_08_044214) do

ActiveRecord::Schema[7.0].define(version: 2023_01_09_012729) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"

Expand All @@ -37,6 +36,8 @@
t.text "work_email_address"
t.text "home_email_address"
t.text "work_phone_number"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end

end
2 changes: 1 addition & 1 deletion spec/models/scimitar/lists/query_parser_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -590,7 +590,7 @@
end

it 'complains if there is no column mapping available' do
expect { @instance.send(:activerecord_columns, 'externalId') }.to raise_error(Scimitar::FilterError)
expect { @instance.send(:activerecord_columns, 'userName') }.to raise_error(Scimitar::FilterError)
end

it 'complains about malformed declarations' do
Expand Down
3 changes: 0 additions & 3 deletions spec/models/scimitar/schema/attribute_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,8 @@
expect(name.type).to eql('complex')
expect(name.subAttributes).to eql(Scimitar::Schema::Name.scim_attributes)
end

end


context '#valid?' do
it 'is invalid if attribute is required but value is blank' do
attribute = described_class.new(name: 'userName', type: 'string', required: true)
Expand Down Expand Up @@ -76,5 +74,4 @@
expect(described_class.new(name: 'startDate', type: 'dateTime').valid?('gaga')).to be(false)
end
end

end
73 changes: 69 additions & 4 deletions spec/requests/active_record_backed_resources_controller_spec.rb
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
require 'spec_helper'
require 'time'

RSpec.describe Scimitar::ActiveRecordBackedResourcesController do
before :each do
allow_any_instance_of(Scimitar::ApplicationController).to receive(:authenticated?).and_return(true)

@u1 = MockUser.create(username: '1', first_name: 'Foo', last_name: 'Ark', home_email_address: '[email protected]')
@u2 = MockUser.create(username: '2', first_name: 'Foo', last_name: 'Bar', home_email_address: '[email protected]')
@u3 = MockUser.create(username: '3', first_name: 'Foo', home_email_address: '[email protected]')
lmt = Time.parse("2023-01-09 14:25:00 +1300")

@u1 = MockUser.create(username: '1', first_name: 'Foo', last_name: 'Ark', home_email_address: '[email protected]', scim_uid: '001', created_at: lmt, updated_at: lmt + 1)
@u2 = MockUser.create(username: '2', first_name: 'Foo', last_name: 'Bar', home_email_address: '[email protected]', scim_uid: '002', created_at: lmt, updated_at: lmt + 2)
@u3 = MockUser.create(username: '3', first_name: 'Foo', home_email_address: '[email protected]', scim_uid: '003', created_at: lmt, updated_at: lmt + 3)
end

# ===========================================================================
Expand Down Expand Up @@ -48,7 +51,7 @@
it 'applies a filter, with case-insensitive value comparison' do
get '/Users', params: {
format: :scim,
filter: 'name.givenName eq "Foo" and name.familyName pr and emails ne "home_1@TEST.COM"'
filter: 'name.givenName eq "FOO" and name.familyName pr and emails ne "home_1@test.com"'
}

expect(response.status).to eql(200)
Expand Down Expand Up @@ -83,6 +86,68 @@
expect(usernames).to match_array(['2'])
end

# Strange attribute capitalisation in tests here builds on test coverage
# for now-fixed GitHub issue #37.
#
context '"meta" / IDs (GitHub issue #36)' do
it 'applies a filter on primary keys, using direct comparison (rather than e.g. case-insensitive operators)' do
get '/Users', params: {
format: :scim,
filter: "id eq \"#{@u3.id}\""
}

expect(response.status).to eql(200)
result = JSON.parse(response.body)

expect(result['totalResults']).to eql(1)
expect(result['Resources'].size).to eql(1)

ids = result['Resources'].map { |resource| resource['id'] }
expect(ids).to match_array([@u3.id.to_s])

usernames = result['Resources'].map { |resource| resource['userName'] }
expect(usernames).to match_array(['3'])
end

it 'applies a filter on external IDs, using direct comparison' do
get '/Users', params: {
format: :scim,
filter: "externalID eq \"#{@u2.scim_uid}\""
}

expect(response.status).to eql(200)
result = JSON.parse(response.body)

expect(result['totalResults']).to eql(1)
expect(result['Resources'].size).to eql(1)

ids = result['Resources'].map { |resource| resource['id'] }
expect(ids).to match_array([@u2.id.to_s])

usernames = result['Resources'].map { |resource| resource['userName'] }
expect(usernames).to match_array(['2'])
end

it 'applies a filter on "meta" entries, using direct comparison' do
get '/Users', params: {
format: :scim,
filter: "Meta.LastModified eq \"#{@u3.updated_at}\""
}

expect(response.status).to eql(200)
result = JSON.parse(response.body)

expect(result['totalResults']).to eql(1)
expect(result['Resources'].size).to eql(1)

ids = result['Resources'].map { |resource| resource['id'] }
expect(ids).to match_array([@u3.id.to_s])

usernames = result['Resources'].map { |resource| resource['userName'] }
expect(usernames).to match_array(['3'])
end
end # "context '"meta" / IDs (GitHub issue #36)' do"

it 'obeys a page size' do
get '/Users', params: {
format: :scim,
Expand Down

0 comments on commit 4424b0b

Please sign in to comment.