scimitar 2.12.0 → 2.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4e8493f9f42ec39cc98639167c39e60261400208a011e21d202116d5ca43daff
4
- data.tar.gz: fb1fe271b42006d0e0054207d873b18ad15851d76597bf78c591f2254eab6688
3
+ metadata.gz: 172c9b71a2da57bcc0b0ebc02c551a5ed8e70c7efda9fcbb01a6cea7b99f6c6b
4
+ data.tar.gz: 0d6fc7e2dcbe4fabcef481c197b96c6f757af8cc5f1ad8b5990a13c346d39517
5
5
  SHA512:
6
- metadata.gz: 4f2d68d767cdf33d97684697466dbca05122783f6a081010f874ef8546cc684c3fe9ca8c72478c6214a7493ffc3d80dfd8a742d03c0748968a32b3dc76980e05
7
- data.tar.gz: 456146fa9cd91c44208e689ba4ba5663626e4ba8bcd3fbdcd592e78785e5af63449d41e68c465e84f8567352a505d02a200b8fbca36b38b7a9d61bc7784b73b8
6
+ metadata.gz: 060e4aff1aa65516fef31e8c0403dfda82d2b015e990bcfed7b45e78830b8f545db30c8abe915126e4a760e62c4f5f34660b19b0d30f1815cc6575406961f9c4
7
+ data.tar.gz: 85659a28c38997bbb937fe153b48f77c4fe75f98d6c6dbad82006d8cd30d029db5f5e7e33e7fd614ee2630aba2ec558338d9de4e888be884fa11eb1159c261a4
data/README.md CHANGED
@@ -123,7 +123,7 @@ Here's an example where Warden is being used for authentication, with Warden sto
123
123
 
124
124
  ```ruby
125
125
  Scimitar.engine_configuration = Scimitar::EngineConfiguration.new({
126
- custome_authenticator: Proc.new do
126
+ custom_authenticator: Proc.new do
127
127
 
128
128
  # In this example we catch the Warden 'throw' for failed authentication, as
129
129
  # well as allowing Warden to successfully find an *authenticated* user, but
@@ -255,8 +255,8 @@ class User < ActiveRecord::Base
255
255
  ],
256
256
 
257
257
  # NB The 'groups' collection in a SCIM User resource is read-only, so
258
- # we provide no ":find_with" key for looking up records for writing
259
- # updates to the associated collection.
258
+ # we provide no ":find_with" or ":find_all_with" key for looking up
259
+ # records for writing updates to the associated collection.
260
260
  #
261
261
  groups: [
262
262
  {
@@ -101,6 +101,9 @@ module Scimitar
101
101
  request.format = :scim
102
102
  elsif request.format == :scim
103
103
  request.headers['CONTENT_TYPE'] = scim_mime_type
104
+ elsif request.media_type.downcase == 'application/json' && request.user_agent.start_with?('Google') # https://github.com/pond/scimitar/issues/142
105
+ request.format = :scim
106
+ request.headers["CONTENT_TYPE"] = scim_mime_type
104
107
  else
105
108
  handle_scim_error(ErrorResponse.new(status: 406, detail: "Only #{scim_mime_type} type is accepted."))
106
109
  end
@@ -145,7 +145,8 @@ module Scimitar
145
145
  # display: :full_name # <-- i.e. Team.users[n].full_name
146
146
  # },
147
147
  # class: Team, # Optional; see below
148
- # find_with: -> (scim_list_entry) {...} # See below
148
+ # find_with: -> (scim_list_entry) {...}, # See below
149
+ # find_all_with: -> (scim_list_entries) {...} # Optional, See below
149
150
  # }
150
151
  # ],
151
152
  # #...
@@ -165,6 +166,11 @@ module Scimitar
165
166
  # Scimitar::EngineConfiguration::schema_list_from_attribute_mappings is
166
167
  # defined; see documentation of that option for more information.
167
168
  #
169
+ # To avoid N+1 queries when resolving many entries (e.g. Group members
170
+ # during PATCH), you can instead provide ":find_all_with" which is passed
171
+ # the entire Array of SCIM entries and should return an Array of resolved
172
+ # model instances. If both are provided, ":find_all_with" is preferred.
173
+ #
168
174
  # Note that you can only use either:
169
175
  #
170
176
  # * One or more static maps where each matches some other piece of source
@@ -315,7 +321,7 @@ module Scimitar
315
321
  enum.each do | static_or_dynamic_mapping |
316
322
  if static_or_dynamic_mapping.key?(:match) # Static
317
323
  extractor.call(static_or_dynamic_mapping[:using])
318
- elsif static_or_dynamic_mapping.key?(:find_with) # Dynamic
324
+ elsif static_or_dynamic_mapping.key?(:find_with) || static_or_dynamic_mapping.key?(:find_all_with) # Dynamic
319
325
  @scim_mutable_attributes << static_or_dynamic_mapping[:list]
320
326
  end
321
327
  end
@@ -839,9 +845,17 @@ module Scimitar
839
845
  method = "#{mapped_array_entry[:list]}="
840
846
 
841
847
  if (attribute&.mutability == 'readWrite' || attribute&.mutability == 'writeOnly') && self.respond_to?(method)
842
- find_with_proc = mapped_array_entry[:find_with]
848
+ find_all_with_proc = mapped_array_entry[:find_all_with]
849
+ find_with_proc = mapped_array_entry[:find_with]
850
+
851
+ if find_all_with_proc.respond_to?(:call)
852
+ scim_entries = (scim_hash_or_leaf_value || [])
853
+ mapped_list = find_all_with_proc.call(scim_entries) || []
843
854
 
844
- unless find_with_proc.nil?
855
+ mapped_list.compact!
856
+
857
+ self.public_send(method, mapped_list)
858
+ elsif find_with_proc.respond_to?(:call)
845
859
  mapped_list = (scim_hash_or_leaf_value || []).map do | source_list_entry |
846
860
  find_with_proc.call(source_list_entry)
847
861
  end
@@ -3,11 +3,11 @@ module Scimitar
3
3
  # Gem version. If this changes, be sure to re-run "bundle install" or
4
4
  # "bundle update".
5
5
  #
6
- VERSION = '2.12.0'
6
+ VERSION = '2.13.0'
7
7
 
8
8
  # Date for VERSION. If this changes, be sure to re-run "bundle install"
9
9
  # or "bundle update".
10
10
  #
11
- DATE = '2025-08-15'
11
+ DATE = '2025-09-12'
12
12
 
13
13
  end
@@ -0,0 +1,13 @@
1
+ class MockBatchGroupsController < Scimitar::ActiveRecordBackedResourcesController
2
+
3
+ protected
4
+
5
+ def storage_class
6
+ MockGroupBatch
7
+ end
8
+
9
+ def storage_scope
10
+ MockGroupBatch.all
11
+ end
12
+
13
+ end
@@ -0,0 +1,20 @@
1
+ class MockGroupBatch < MockGroup
2
+ def self.scim_attributes_map
3
+ {
4
+ id: :id,
5
+ externalId: :scim_uid,
6
+ displayName: :display_name,
7
+ members: [
8
+ {
9
+ list: :scim_users_and_groups,
10
+ using: { value: :id },
11
+ # Minimal mock: assume user-only entries (type omitted => User)
12
+ find_all_with: -> (entries) do
13
+ ids = entries.map { |e| e['value'] }
14
+ MockUser.where(primary_key: ids).to_a
15
+ end
16
+ }
17
+ ]
18
+ }
19
+ end
20
+ end
@@ -17,6 +17,12 @@ Rails.application.routes.draw do
17
17
  get 'Groups/:id', to: 'mock_groups#show'
18
18
  patch 'Groups/:id', to: 'mock_groups#update'
19
19
 
20
+ # Batch lookup variant for testing the mixin 'find_all_with' option.
21
+ #
22
+ get 'BatchGroups', to: 'mock_batch_groups#index'
23
+ get 'BatchGroups/:id', to: 'mock_batch_groups#show'
24
+ patch 'BatchGroups/:id', to: 'mock_batch_groups#update'
25
+
20
26
  # For testing blocks passed to ActiveRecordBackedResourcesController#create,
21
27
  # #update, #replace and #destroy.
22
28
  #
@@ -416,6 +416,25 @@ RSpec.describe Scimitar::ApplicationController do
416
416
  expect(@exception.message).to eql('Only application/scim+json type is accepted.')
417
417
  end
418
418
  end
419
+
420
+ context 'and with Google SCIM calls' do
421
+ it 'reaches the controller action if the expected agent is making the request' do
422
+ request.headers['Content-Type'] = 'application/json'
423
+ request.headers['User-Agent' ] = 'Google-Auto-Provisioning'
424
+ get :index
425
+
426
+ expect(@exception).to be_a(RuntimeError)
427
+ expect(@exception.message).to eql('Bang')
428
+ end
429
+
430
+ it 'is invoked early for unrecognised agents' do
431
+ request.headers['Content-Type'] = 'application/json'
432
+ get :index
433
+
434
+ expect(@exception).to be_a(Scimitar::ErrorResponse)
435
+ expect(@exception.message).to eql('Only application/scim+json type is accepted.')
436
+ end
437
+ end # "context 'and with Google SCIM calls' do"
419
438
  end # "context 'exception reporter' do"
420
439
  end # "context 'error handling' do"
421
440
  end
@@ -1137,6 +1137,39 @@ RSpec.describe Scimitar::ActiveRecordBackedResourcesController do
1137
1137
  expect(@u2.password).to eql('correcthorsebatterystaple')
1138
1138
  end
1139
1139
 
1140
+ context "when updating group members using :find_all_with" do
1141
+ it "uses :find_all_with to batch-resolve users and updates associations" do
1142
+ payload = {
1143
+ schemas: [ 'urn:ietf:params:scim:api:messages:2.0:PatchOp' ],
1144
+ Operations: [
1145
+ {
1146
+ op: 'add',
1147
+ path: 'members',
1148
+ value: [
1149
+ { 'value' => @u1.primary_key },
1150
+ { 'value' => @u2.primary_key }
1151
+ ]
1152
+ }
1153
+ ]
1154
+ }
1155
+
1156
+ payload = spec_helper_hupcase(payload) if force_upper_case
1157
+
1158
+ patch "/BatchGroups/#{@g1.id}", params: payload.merge({ format: :scim })
1159
+
1160
+ expect(response.status).to eql(200)
1161
+
1162
+ # Verify membership updated
1163
+ get "/BatchGroups/#{@g1.id}", params: { format: :scim }
1164
+ expect(response.status).to eql(200)
1165
+ result = JSON.parse(response.body)
1166
+
1167
+ values = result.fetch('members', []).map { |m| m['value'] }
1168
+ expect(values).to include(@u1.primary_key.to_s)
1169
+ expect(values).to include(@u2.primary_key.to_s)
1170
+ end
1171
+ end
1172
+
1140
1173
  context 'which clears attributes' do
1141
1174
  before :each do
1142
1175
  @u2.update!(work_email_address: 'work_2@test.com')
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: scimitar
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.12.0
4
+ version: 2.13.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - RIPA Global
8
8
  - Andrew David Hodgkinson
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2025-08-15 00:00:00.000000000 Z
11
+ date: 2025-09-12 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -230,9 +230,11 @@ files:
230
230
  - spec/apps/dummy/app/controllers/custom_request_verifiers_controller.rb
231
231
  - spec/apps/dummy/app/controllers/custom_save_mock_users_controller.rb
232
232
  - spec/apps/dummy/app/controllers/custom_update_mock_users_controller.rb
233
+ - spec/apps/dummy/app/controllers/mock_batch_groups_controller.rb
233
234
  - spec/apps/dummy/app/controllers/mock_groups_controller.rb
234
235
  - spec/apps/dummy/app/controllers/mock_users_controller.rb
235
236
  - spec/apps/dummy/app/models/mock_group.rb
237
+ - spec/apps/dummy/app/models/mock_group_batch.rb
236
238
  - spec/apps/dummy/app/models/mock_user.rb
237
239
  - spec/apps/dummy/config/application.rb
238
240
  - spec/apps/dummy/config/boot.rb
@@ -304,9 +306,11 @@ test_files:
304
306
  - spec/apps/dummy/app/controllers/custom_request_verifiers_controller.rb
305
307
  - spec/apps/dummy/app/controllers/custom_save_mock_users_controller.rb
306
308
  - spec/apps/dummy/app/controllers/custom_update_mock_users_controller.rb
309
+ - spec/apps/dummy/app/controllers/mock_batch_groups_controller.rb
307
310
  - spec/apps/dummy/app/controllers/mock_groups_controller.rb
308
311
  - spec/apps/dummy/app/controllers/mock_users_controller.rb
309
312
  - spec/apps/dummy/app/models/mock_group.rb
313
+ - spec/apps/dummy/app/models/mock_group_batch.rb
310
314
  - spec/apps/dummy/app/models/mock_user.rb
311
315
  - spec/apps/dummy/config/application.rb
312
316
  - spec/apps/dummy/config/boot.rb