scimitar 1.10.0 → 2.0.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.
Files changed (61) hide show
  1. checksums.yaml +4 -4
  2. data/app/controllers/scimitar/active_record_backed_resources_controller.rb +23 -98
  3. data/app/controllers/scimitar/application_controller.rb +13 -41
  4. data/app/controllers/scimitar/resource_types_controller.rb +2 -0
  5. data/app/controllers/scimitar/resources_controller.rb +2 -0
  6. data/app/controllers/scimitar/schemas_controller.rb +3 -366
  7. data/app/controllers/scimitar/service_provider_configurations_controller.rb +1 -0
  8. data/app/models/scimitar/complex_types/address.rb +6 -0
  9. data/app/models/scimitar/engine_configuration.rb +5 -15
  10. data/app/models/scimitar/error_response.rb +0 -12
  11. data/app/models/scimitar/lists/query_parser.rb +13 -113
  12. data/app/models/scimitar/resource_invalid_error.rb +1 -1
  13. data/app/models/scimitar/resources/base.rb +9 -53
  14. data/app/models/scimitar/resources/mixin.rb +59 -646
  15. data/app/models/scimitar/schema/address.rb +0 -1
  16. data/app/models/scimitar/schema/attribute.rb +5 -14
  17. data/app/models/scimitar/schema/base.rb +1 -1
  18. data/app/models/scimitar/schema/name.rb +2 -2
  19. data/app/models/scimitar/schema/user.rb +10 -10
  20. data/app/models/scimitar/schema/vdtp.rb +1 -1
  21. data/app/models/scimitar/service_provider_configuration.rb +3 -14
  22. data/config/initializers/scimitar.rb +3 -69
  23. data/lib/scimitar/engine.rb +12 -57
  24. data/lib/scimitar/support/hash_with_indifferent_case_insensitive_access.rb +10 -140
  25. data/lib/scimitar/version.rb +2 -2
  26. data/lib/scimitar.rb +2 -7
  27. data/spec/apps/dummy/app/controllers/mock_groups_controller.rb +1 -1
  28. data/spec/apps/dummy/app/models/mock_group.rb +1 -1
  29. data/spec/apps/dummy/app/models/mock_user.rb +9 -52
  30. data/spec/apps/dummy/config/application.rb +1 -0
  31. data/spec/apps/dummy/config/environments/test.rb +28 -5
  32. data/spec/apps/dummy/config/initializers/scimitar.rb +10 -90
  33. data/spec/apps/dummy/config/routes.rb +7 -28
  34. data/spec/apps/dummy/db/migrate/20210304014602_create_mock_users.rb +1 -11
  35. data/spec/apps/dummy/db/migrate/20210308044214_create_join_table_mock_groups_mock_users.rb +3 -8
  36. data/spec/apps/dummy/db/schema.rb +4 -12
  37. data/spec/controllers/scimitar/application_controller_spec.rb +3 -126
  38. data/spec/controllers/scimitar/resource_types_controller_spec.rb +2 -2
  39. data/spec/controllers/scimitar/schemas_controller_spec.rb +48 -344
  40. data/spec/models/scimitar/complex_types/address_spec.rb +4 -3
  41. data/spec/models/scimitar/complex_types/email_spec.rb +2 -0
  42. data/spec/models/scimitar/lists/query_parser_spec.rb +9 -146
  43. data/spec/models/scimitar/resources/base_spec.rb +71 -217
  44. data/spec/models/scimitar/resources/base_validation_spec.rb +5 -43
  45. data/spec/models/scimitar/resources/mixin_spec.rb +129 -1508
  46. data/spec/models/scimitar/schema/attribute_spec.rb +3 -22
  47. data/spec/models/scimitar/schema/base_spec.rb +1 -1
  48. data/spec/models/scimitar/schema/user_spec.rb +2 -12
  49. data/spec/requests/active_record_backed_resources_controller_spec.rb +66 -1016
  50. data/spec/requests/application_controller_spec.rb +3 -16
  51. data/spec/requests/engine_spec.rb +0 -75
  52. data/spec/spec_helper.rb +1 -9
  53. data/spec/support/hash_with_indifferent_case_insensitive_access_spec.rb +0 -108
  54. metadata +26 -37
  55. data/LICENSE.txt +0 -21
  56. data/README.md +0 -717
  57. data/lib/scimitar/support/utilities.rb +0 -111
  58. data/spec/apps/dummy/app/controllers/custom_create_mock_users_controller.rb +0 -25
  59. data/spec/apps/dummy/app/controllers/custom_replace_mock_users_controller.rb +0 -25
  60. data/spec/apps/dummy/app/controllers/custom_save_mock_users_controller.rb +0 -24
  61. data/spec/apps/dummy/app/controllers/custom_update_mock_users_controller.rb +0 -25
@@ -81,102 +81,6 @@ RSpec.describe Scimitar::Resources::Mixin do
81
81
  include Scimitar::Resources::Mixin
82
82
  end
83
83
 
84
- # A simple schema containing two attributes that looks very like complex
85
- # type "name", except shorter and with "familyName" never returned.
86
- #
87
- NestedReturnedNeverTestNameSchema = Class.new(Scimitar::Schema::Base) do
88
- def self.id
89
- 'nested-returned-never-name-id'
90
- end
91
-
92
- def self.scim_attributes
93
- @scim_attributes ||= [
94
- Scimitar::Schema::Attribute.new(name: 'familyName', type: 'string', required: true, returned: 'never'),
95
- Scimitar::Schema::Attribute.new(name: 'givenName', type: 'string', required: true)
96
- ]
97
- end
98
- end
99
-
100
- # A complex type that uses the above schema, giving us the ability to define
101
- # an attribute using this complex type, with therefore the *nested* attribute
102
- # "familyName" being never returned.
103
- #
104
- NestedReturnedNeverTestNameType = Class.new(Scimitar::ComplexTypes::Base) do
105
- set_schema NestedReturnedNeverTestNameSchema
106
- end
107
-
108
- # A test schema that uses the above type, the standard name type (but that
109
- # *entire* top-level attribute is never returned) and a simple String item.
110
- #
111
- NestedReturnedNeverTestSchema = Class.new(Scimitar::Schema::Base) do
112
- def self.id
113
- 'nested-returned-never-id'
114
- end
115
-
116
- def self.scim_attributes
117
- [
118
- Scimitar::Schema::Attribute.new(
119
- name: 'name', complexType: NestedReturnedNeverTestNameType
120
- ),
121
- Scimitar::Schema::Attribute.new(
122
- name: 'privateName', complexType: Scimitar::ComplexTypes::Name, returned: 'never'
123
- ),
124
- Scimitar::Schema::Attribute.new(
125
- name: 'simpleName', type: 'string'
126
- )
127
- ]
128
- end
129
- end
130
-
131
- # Define a resource that is based upon the above schema.
132
- #
133
- NestedReturnedNeverTestResourse = Class.new(Scimitar::Resources::Base) do
134
- set_schema NestedReturnedNeverTestSchema
135
- end
136
-
137
- # Create a testable model that is our internal representation of the above
138
- # resource.
139
- #
140
- class NestedReturnedNeverTest
141
- include ActiveModel::Model
142
-
143
- def self.scim_resource_type
144
- return NestedReturnedNeverTestResourse
145
- end
146
-
147
- attr_accessor :given_name,
148
- :last_name,
149
- :private_given_name,
150
- :private_last_name,
151
- :simple_name
152
-
153
- def self.scim_attributes_map
154
- return {
155
- name: {
156
- givenName: :given_name,
157
- familyName: :last_name
158
- },
159
-
160
- privateName: {
161
- givenName: :private_given_name,
162
- familyName: :private_last_name
163
- },
164
-
165
- simpleName: :simple_name
166
- }
167
- end
168
-
169
- def self.scim_mutable_attributes
170
- return nil
171
- end
172
-
173
- def self.scim_queryable_attributes
174
- return nil
175
- end
176
-
177
- include Scimitar::Resources::Mixin
178
- end
179
-
180
84
  # ===========================================================================
181
85
  # Errant class definitions
182
86
  # ===========================================================================
@@ -255,127 +159,41 @@ RSpec.describe Scimitar::Resources::Mixin do
255
159
  # =========================================================================
256
160
 
257
161
  context '#to_scim' do
258
- context 'with list of requested attributes' do
259
- it 'compiles instance attribute values into a SCIM representation, including only the requested attributes' do
260
- uuid = SecureRandom.uuid
261
-
262
- instance = MockUser.new
263
- instance.primary_key = uuid
264
- instance.scim_uid = 'AA02984'
265
- instance.username = 'foo'
266
- instance.password = 'correcthorsebatterystaple'
267
- instance.first_name = 'Foo'
268
- instance.last_name = 'Bar'
269
- instance.work_email_address = 'foo.bar@test.com'
270
- instance.home_email_address = nil
271
- instance.work_phone_number = '+642201234567'
272
- instance.organization = 'SOMEORG'
273
-
274
- g1 = MockGroup.create!(display_name: 'Group 1')
275
- g2 = MockGroup.create!(display_name: 'Group 2')
276
- g3 = MockGroup.create!(display_name: 'Group 3')
277
-
278
- g1.mock_users << instance
279
- g3.mock_users << instance
280
-
281
- scim = instance.to_scim(location: "https://test.com/mock_users/#{uuid}", include_attributes: %w[id userName name groups.display groups.value organization])
282
- json = scim.to_json()
283
- hash = JSON.parse(json)
284
-
285
- expect(hash).to eql({
286
- 'id' => uuid,
287
- 'userName' => 'foo',
288
- 'name' => {'givenName'=>'Foo', 'familyName'=>'Bar'},
289
- 'groups' => [{'display'=>g1.display_name, 'value'=>g1.id.to_s}, {'display'=>g3.display_name, 'value'=>g3.id.to_s}],
290
- 'meta' => {'location'=>"https://test.com/mock_users/#{uuid}", 'resourceType'=>'User'},
291
- 'schemas' => [
292
- 'urn:ietf:params:scim:schemas:core:2.0:User',
293
- 'urn:ietf:params:scim:schemas:extension:enterprise:2.0:User',
294
- 'urn:ietf:params:scim:schemas:extension:manager:1.0:User',
295
- ],
296
- 'urn:ietf:params:scim:schemas:extension:enterprise:2.0:User' => {
297
- 'organization' => 'SOMEORG',
298
- },
299
- 'urn:ietf:params:scim:schemas:extension:manager:1.0:User' => {},
300
- })
301
- end
302
- end # "context 'with list of requested attributes' do"
303
-
304
- context 'with a UUID, renamed primary key column' do
305
- it 'compiles instance attribute values into a SCIM representation, but omits do-not-return fields' do
306
- uuid = SecureRandom.uuid
307
-
308
- instance = MockUser.new
309
- instance.primary_key = uuid
310
- instance.scim_uid = 'AA02984'
311
- instance.username = 'foo'
312
- instance.password = 'correcthorsebatterystaple'
313
- instance.first_name = 'Foo'
314
- instance.last_name = 'Bar'
315
- instance.work_email_address = 'foo.bar@test.com'
316
- instance.home_email_address = nil
317
- instance.work_phone_number = '+642201234567'
318
- instance.organization = 'SOMEORG'
319
-
320
- g1 = MockGroup.create!(display_name: 'Group 1')
321
- g2 = MockGroup.create!(display_name: 'Group 2')
322
- g3 = MockGroup.create!(display_name: 'Group 3')
323
-
324
- g1.mock_users << instance
325
- g3.mock_users << instance
326
-
327
- scim = instance.to_scim(location: "https://test.com/mock_users/#{uuid}")
328
- json = scim.to_json()
329
- hash = JSON.parse(json)
330
-
331
- expect(hash).to eql({
332
- 'userName' => 'foo',
333
- 'name' => {'givenName'=>'Foo', 'familyName'=>'Bar'},
334
- 'active' => true,
335
- 'emails' => [{'type'=>'work', 'primary'=>true, 'value'=>'foo.bar@test.com'}, {"primary"=>false, "type"=>"home", "value"=>nil}],
336
- 'phoneNumbers'=> [{'type'=>'work', 'primary'=>false, 'value'=>'+642201234567'}],
337
- 'id' => uuid,
338
- 'externalId' => 'AA02984',
339
- 'groups' => [{'display'=>g1.display_name, 'value'=>g1.id.to_s}, {'display'=>g3.display_name, 'value'=>g3.id.to_s}],
340
- 'meta' => {'location'=>"https://test.com/mock_users/#{uuid}", 'resourceType'=>'User'},
341
- 'schemas' => [
342
- 'urn:ietf:params:scim:schemas:core:2.0:User',
343
- 'urn:ietf:params:scim:schemas:extension:enterprise:2.0:User',
344
- 'urn:ietf:params:scim:schemas:extension:manager:1.0:User',
345
- ],
346
- 'urn:ietf:params:scim:schemas:extension:enterprise:2.0:User' => {
347
- 'organization' => 'SOMEORG',
348
- 'department' => nil,
349
- 'primaryEmail' => instance.work_email_address,
350
- },
351
- 'urn:ietf:params:scim:schemas:extension:manager:1.0:User' => {
352
- 'manager' => nil
353
- },
354
- })
355
- end
356
- end # "context 'with a UUID, renamed primary key column' do"
357
-
358
- context 'with an integer, conventionally named primary key column' do
359
- it 'compiles instance attribute values into a SCIM representation' do
360
- instance = MockGroup.new
361
- instance.id = 42
362
- instance.scim_uid = 'GG02984'
363
- instance.display_name = 'Some group'
364
-
365
- scim = instance.to_scim(location: 'https://test.com/mock_groups/42')
366
- json = scim.to_json()
367
- hash = JSON.parse(json)
368
-
369
- expect(hash).to eql({
370
- 'displayName' => 'Some group',
371
- 'id' => '42', # Note, String
372
- 'externalId' => 'GG02984',
373
- 'members' => [],
374
- 'meta' => {'location'=>'https://test.com/mock_groups/42', 'resourceType'=>'Group'},
375
- 'schemas' => ['urn:ietf:params:scim:schemas:core:2.0:Group']
376
- })
377
- end
378
- end # "context 'with an integer, conventionally named primary key column' do"
162
+ it 'compiles instance attribute values into a SCIM representation' do
163
+ instance = MockUser.new
164
+ instance.id = 42
165
+ instance.scim_uid = 'AA02984'
166
+ instance.username = 'foo'
167
+ instance.first_name = 'Foo'
168
+ instance.last_name = 'Bar'
169
+ instance.work_email_address = 'foo.bar@test.com'
170
+ instance.home_email_address = nil
171
+ instance.work_phone_number = '+642201234567'
172
+
173
+ g1 = MockGroup.create!(display_name: 'Group 1')
174
+ g2 = MockGroup.create!(display_name: 'Group 2')
175
+ g3 = MockGroup.create!(display_name: 'Group 3')
176
+
177
+ g1.mock_users << instance
178
+ g3.mock_users << instance
179
+
180
+ scim = instance.to_scim(location: 'https://test.com/mock_users/42')
181
+ json = scim.to_json()
182
+ hash = JSON.parse(json)
183
+
184
+ expect(hash).to eql({
185
+ 'userName' => 'foo',
186
+ 'name' => {'givenName'=>'Foo', 'familyName'=>'Bar'},
187
+ 'active' => true,
188
+ 'emails' => [{'type'=>'work', 'primary'=>true, 'value'=>'foo.bar@test.com'}, {"primary"=>false, "type"=>"home", "value"=>nil}],
189
+ 'phoneNumbers'=> [{'type'=>'work', 'primary'=>false, 'value'=>'+642201234567'}],
190
+ 'id' => '42', # Note, String
191
+ 'externalId' => 'AA02984',
192
+ 'groups' => [{'display'=>g1.display_name, 'value'=>g1.id.to_s}, {'display'=>g3.display_name, 'value'=>g3.id.to_s}],
193
+ 'meta' => {'location'=>'https://test.com/mock_users/42', 'resourceType'=>'User'},
194
+ 'schemas' => ['urn:ietf:params:scim:schemas:core:2.0:User']
195
+ })
196
+ end
379
197
 
380
198
  context 'with optional timestamps' do
381
199
  context 'creation only' do
@@ -474,13 +292,7 @@ RSpec.describe Scimitar::Resources::Mixin do
474
292
  ],
475
293
 
476
294
  'meta' => {'location'=>'https://test.com/static_map_test', 'resourceType'=>'User'},
477
- 'schemas' => [
478
- 'urn:ietf:params:scim:schemas:core:2.0:User',
479
- 'urn:ietf:params:scim:schemas:extension:enterprise:2.0:User',
480
- 'urn:ietf:params:scim:schemas:extension:manager:1.0:User',
481
- ],
482
- 'urn:ietf:params:scim:schemas:extension:enterprise:2.0:User' => {},
483
- 'urn:ietf:params:scim:schemas:extension:manager:1.0:User' => {},
295
+ 'schemas' => ['urn:ietf:params:scim:schemas:core:2.0:User']
484
296
  })
485
297
  end
486
298
  end # "context 'using static mappings' do"
@@ -507,42 +319,12 @@ RSpec.describe Scimitar::Resources::Mixin do
507
319
  ],
508
320
 
509
321
  'meta' => {'location'=>'https://test.com/dynamic_map_test', 'resourceType'=>'User'},
510
- 'schemas' => [
511
- 'urn:ietf:params:scim:schemas:core:2.0:User',
512
- 'urn:ietf:params:scim:schemas:extension:enterprise:2.0:User',
513
- 'urn:ietf:params:scim:schemas:extension:manager:1.0:User',
514
- ],
515
- 'urn:ietf:params:scim:schemas:extension:enterprise:2.0:User' => {},
516
- 'urn:ietf:params:scim:schemas:extension:manager:1.0:User' => {},
322
+ 'schemas' => ['urn:ietf:params:scim:schemas:core:2.0:User']
517
323
  })
518
324
  end
519
325
  end # "context 'using dynamic lists' do"
520
326
  end # "context 'with arrays' do"
521
327
 
522
- context 'with "returned: \'never\' fields' do
523
- it 'hides appropriate top-level and nested attributes' do
524
- instance = NestedReturnedNeverTest.new(
525
- given_name: 'One',
526
- last_name: 'Two',
527
- private_given_name: 'Three',
528
- private_last_name: 'Four',
529
- simple_name: 'Five'
530
- )
531
-
532
- scim = instance.to_scim(location: 'https://test.com/never_retutrned_test')
533
- json = scim.to_json()
534
- hash = JSON.parse(json)
535
-
536
- expect(hash).to eql({
537
- 'name' => { 'givenName' => 'One' },
538
- 'simpleName' => 'Five',
539
-
540
- 'meta' => {'location'=>'https://test.com/never_retutrned_test', 'resourceType'=>'NestedReturnedNeverTestResourse'},
541
- 'schemas' => ['nested-returned-never-id']
542
- })
543
- end
544
- end # "context 'with "returned: \'never\' fields' do"
545
-
546
328
  context 'with bad definitions' do
547
329
  it 'complains about non-Hash entries in mapping Arrays' do
548
330
  expect(StaticMapTest).to receive(:scim_attributes_map).and_return({
@@ -586,7 +368,6 @@ RSpec.describe Scimitar::Resources::Mixin do
586
368
  it 'ignoring read-only lists' do
587
369
  hash = {
588
370
  'userName' => 'foo',
589
- 'password' => 'staplebatteryhorsecorrect',
590
371
  'name' => {'givenName' => 'Foo', 'familyName' => 'Bar'},
591
372
  'active' => true,
592
373
  'emails' => [{'type' => 'work', 'primary' => true, 'value' => 'foo.bar@test.com'}],
@@ -595,12 +376,7 @@ RSpec.describe Scimitar::Resources::Mixin do
595
376
  'id' => '42', # Note, String
596
377
  'externalId' => 'AA02984',
597
378
  'meta' => {'location' => 'https://test.com/mock_users/42', 'resourceType' => 'User'},
598
- 'schemas' => ['urn:ietf:params:scim:schemas:core:2.0:User', 'urn:ietf:params:scim:schemas:extension:enterprise:2.0:User'],
599
-
600
- 'urn:ietf:params:scim:schemas:extension:enterprise:2.0:User' => {
601
- 'organization' => 'SOMEORG',
602
- 'DEPARTMENT' => 'SOMEDPT'
603
- }
379
+ 'schemas' => ['urn:ietf:params:scim:schemas:core:2.0:User']
604
380
  }
605
381
 
606
382
  hash = spec_helper_hupcase(hash) if force_upper_case
@@ -611,14 +387,11 @@ RSpec.describe Scimitar::Resources::Mixin do
611
387
 
612
388
  expect(instance.scim_uid ).to eql('AA02984')
613
389
  expect(instance.username ).to eql('foo')
614
- expect(instance.password ).to eql('staplebatteryhorsecorrect')
615
390
  expect(instance.first_name ).to eql('Foo')
616
391
  expect(instance.last_name ).to eql('Bar')
617
392
  expect(instance.work_email_address).to eql('foo.bar@test.com')
618
393
  expect(instance.home_email_address).to be_nil
619
394
  expect(instance.work_phone_number ).to eql('+642201234567')
620
- expect(instance.organization ).to eql('SOMEORG')
621
- expect(instance.department ).to eql('SOMEDPT')
622
395
  end
623
396
 
624
397
  it 'honouring read-write lists' do
@@ -632,8 +405,8 @@ RSpec.describe Scimitar::Resources::Mixin do
632
405
  'displayName' => 'Foo Group',
633
406
  'members' => [
634
407
  {'type' => 'Group', 'value' => g1.id.to_s},
635
- {'type' => 'User', 'value' => u1.primary_key.to_s},
636
- {'type' => 'User', 'value' => u3.primary_key.to_s}
408
+ {'type' => 'User', 'value' => u1.id.to_s},
409
+ {'type' => 'User', 'value' => u3.id.to_s}
637
410
  ],
638
411
  'externalId' => 'GG01536',
639
412
  'meta' => {'location'=>'https://test.com/mock_groups/1', 'resourceType'=>'Group'},
@@ -680,10 +453,8 @@ RSpec.describe Scimitar::Resources::Mixin do
680
453
  end # "context 'using upper case' do"
681
454
 
682
455
  it 'clears things not present in input' do
683
- uuid = SecureRandom.uuid
684
-
685
456
  instance = MockUser.new
686
- instance.primary_key = uuid
457
+ instance.id = 42
687
458
  instance.scim_uid = 'AA02984'
688
459
  instance.username = 'foo'
689
460
  instance.first_name = 'Foo'
@@ -694,7 +465,7 @@ RSpec.describe Scimitar::Resources::Mixin do
694
465
 
695
466
  instance.from_scim!(scim_hash: {})
696
467
 
697
- expect(instance.primary_key ).to eql(uuid)
468
+ expect(instance.id ).to eql(42)
698
469
  expect(instance.scim_uid ).to be_nil
699
470
  expect(instance.username ).to be_nil
700
471
  expect(instance.first_name ).to be_nil
@@ -883,8 +654,7 @@ RSpec.describe Scimitar::Resources::Mixin do
883
654
  nature: 'add',
884
655
  path: path,
885
656
  value: 'foo',
886
- altering_hash: scim_hash,
887
- with_attr_map: { userName: :user_name }
657
+ altering_hash: scim_hash
888
658
  )
889
659
 
890
660
  expect(scim_hash['userName']).to eql('foo')
@@ -899,30 +669,13 @@ RSpec.describe Scimitar::Resources::Mixin do
899
669
  nature: 'add',
900
670
  path: path,
901
671
  value: 'Baz',
902
- altering_hash: scim_hash,
903
- with_attr_map: { name: { givenName: :first_name, familyName: :last_name } }
672
+ altering_hash: scim_hash
904
673
  )
905
674
 
906
675
  expect(scim_hash['name']['givenName' ]).to eql('Baz')
907
676
  expect(scim_hash['name']['familyName']).to eql('Bar')
908
677
  end
909
678
 
910
- it 'with schema extensions: overwrites' do
911
- path = [ 'urn:ietf:params:scim:schemas:extension:enterprise:2.0:User', 'organization' ]
912
- scim_hash = { 'urn:ietf:params:scim:schemas:extension:enterprise:2.0:User' => { 'organization' => 'SOMEORG' } }.with_indifferent_case_insensitive_access()
913
-
914
- @instance.send(
915
- :from_patch_backend!,
916
- nature: 'add',
917
- path: path,
918
- value: 'OTHERORG',
919
- altering_hash: scim_hash,
920
- with_attr_map: { organization: :org_name }
921
- )
922
-
923
- expect(scim_hash['urn:ietf:params:scim:schemas:extension:enterprise:2.0:User']['organization' ]).to eql('OTHERORG')
924
- end
925
-
926
679
  # For 'add', filter at end-of-path is nonsensical and not
927
680
  # supported by spec or Scimitar; we only test mid-path filters.
928
681
  #
@@ -947,13 +700,7 @@ RSpec.describe Scimitar::Resources::Mixin do
947
700
  nature: 'add',
948
701
  path: path,
949
702
  value: 'added_over_original@test.com',
950
- altering_hash: scim_hash,
951
- with_attr_map: {
952
- emails: [
953
- { match: 'type', with: 'home', using: { value: :home_email, primary: true } },
954
- { match: 'type', with: 'work', using: { value: :work_email } },
955
- ]
956
- }
703
+ altering_hash: scim_hash
957
704
  )
958
705
 
959
706
  expect(scim_hash['emails'][0]['value']).to eql('home@test.com')
@@ -979,13 +726,7 @@ RSpec.describe Scimitar::Resources::Mixin do
979
726
  nature: 'add',
980
727
  path: path,
981
728
  value: 'added_over_original@test.com',
982
- altering_hash: scim_hash,
983
- with_attr_map: {
984
- emails: [
985
- { match: 'type', with: 'home', using: { value: :home_email, primary: true } },
986
- { match: 'type', with: 'work', using: { value: :work_email } },
987
- ]
988
- }
729
+ altering_hash: scim_hash
989
730
  )
990
731
 
991
732
  expect(scim_hash['emails'][0]['value']).to eql('home@test.com')
@@ -1012,13 +753,7 @@ RSpec.describe Scimitar::Resources::Mixin do
1012
753
  nature: 'add',
1013
754
  path: path,
1014
755
  value: 'added_over_original@test.com',
1015
- altering_hash: scim_hash,
1016
- with_attr_map: {
1017
- emails: [
1018
- { match: 'type', with: 'home', using: { value: :home_email, primary: true } },
1019
- { match: 'type', with: 'work', using: { value: :work_email } },
1020
- ]
1021
- }
756
+ altering_hash: scim_hash
1022
757
  )
1023
758
 
1024
759
  expect(scim_hash['emails'][0]['value']).to eql('added_over_original@test.com')
@@ -1042,13 +777,7 @@ RSpec.describe Scimitar::Resources::Mixin do
1042
777
  nature: 'add',
1043
778
  path: path,
1044
779
  value: [ { 'type' => 'work', 'value' => 'work@test.com' } ], # NOTE - to-add value is an Array (and must be)
1045
- altering_hash: scim_hash,
1046
- with_attr_map: {
1047
- emails: [
1048
- { match: 'type', with: 'home', using: { value: :home_email, primary: true } },
1049
- { match: 'type', with: 'work', using: { value: :work_email } },
1050
- ]
1051
- }
780
+ altering_hash: scim_hash
1052
781
  )
1053
782
 
1054
783
  expect(scim_hash['emails'].size).to eql(2)
@@ -1096,12 +825,7 @@ RSpec.describe Scimitar::Resources::Mixin do
1096
825
  nature: 'add',
1097
826
  path: ['root'],
1098
827
  value: {'members' => [{'value' => '3'}]},
1099
- altering_hash: scim_hash,
1100
- with_attr_map: {
1101
- members: [
1102
- { list: :members, using: { value: :id } }
1103
- ]
1104
- }
828
+ altering_hash: scim_hash
1105
829
  )
1106
830
 
1107
831
  expect(scim_hash['root']['members']).to match_array([{'value' => '1'}, {'value' => '2'}, {'value' => '3'}])
@@ -1119,8 +843,7 @@ RSpec.describe Scimitar::Resources::Mixin do
1119
843
  nature: 'add',
1120
844
  path: path,
1121
845
  value: 'foo',
1122
- altering_hash: scim_hash,
1123
- with_attr_map: { userName: :user_name }
846
+ altering_hash: scim_hash
1124
847
  )
1125
848
 
1126
849
  expect(scim_hash['userName']).to eql('foo')
@@ -1135,29 +858,12 @@ RSpec.describe Scimitar::Resources::Mixin do
1135
858
  nature: 'add',
1136
859
  path: path,
1137
860
  value: 'Baz',
1138
- altering_hash: scim_hash,
1139
- with_attr_map: { name: { givenName: :first_name, familyName: :last_name } }
861
+ altering_hash: scim_hash
1140
862
  )
1141
863
 
1142
864
  expect(scim_hash['name']['givenName']).to eql('Baz')
1143
865
  end
1144
866
 
1145
- it 'with schema extensions: adds' do
1146
- path = [ 'urn:ietf:params:scim:schemas:extension:enterprise:2.0:User', 'organization' ]
1147
- scim_hash = {}.with_indifferent_case_insensitive_access()
1148
-
1149
- @instance.send(
1150
- :from_patch_backend!,
1151
- nature: 'add',
1152
- path: path,
1153
- value: 'SOMEORG',
1154
- altering_hash: scim_hash,
1155
- with_attr_map: { organization: :org_name }
1156
- )
1157
-
1158
- expect(scim_hash['urn:ietf:params:scim:schemas:extension:enterprise:2.0:User']['organization' ]).to eql('SOMEORG')
1159
- end
1160
-
1161
867
  context 'with filter mid-path: adds' do
1162
868
  it 'by string match' do
1163
869
  path = [ 'emails[type eq "work"]', 'value' ]
@@ -1178,13 +884,7 @@ RSpec.describe Scimitar::Resources::Mixin do
1178
884
  nature: 'add',
1179
885
  path: path,
1180
886
  value: 'added@test.com',
1181
- altering_hash: scim_hash,
1182
- with_attr_map: {
1183
- emails: [
1184
- { match: 'type', with: 'home', using: { value: :home_email, primary: true } },
1185
- { match: 'type', with: 'work', using: { value: :work_email } },
1186
- ]
1187
- }
887
+ altering_hash: scim_hash
1188
888
  )
1189
889
 
1190
890
  expect(scim_hash['emails'][0]['value']).to eql('home@test.com')
@@ -1209,13 +909,7 @@ RSpec.describe Scimitar::Resources::Mixin do
1209
909
  nature: 'add',
1210
910
  path: path,
1211
911
  value: 'added@test.com',
1212
- altering_hash: scim_hash,
1213
- with_attr_map: {
1214
- emails: [
1215
- { match: 'type', with: 'home', using: { value: :home_email, primary: true } },
1216
- { match: 'type', with: 'work', using: { value: :work_email } },
1217
- ]
1218
- }
912
+ altering_hash: scim_hash
1219
913
  )
1220
914
 
1221
915
  expect(scim_hash['emails'][0]['value']).to eql('home@test.com')
@@ -1231,13 +925,7 @@ RSpec.describe Scimitar::Resources::Mixin do
1231
925
  nature: 'add',
1232
926
  path: path,
1233
927
  value: 'added@test.com',
1234
- altering_hash: scim_hash,
1235
- with_attr_map: {
1236
- emails: [
1237
- { match: 'type', with: 'home', using: { value: :home_email, primary: true } },
1238
- { match: 'type', with: 'work', using: { value: :work_email } },
1239
- ]
1240
- }
928
+ altering_hash: scim_hash
1241
929
  )
1242
930
 
1243
931
  expect(scim_hash['emails'][0]['value']).to eql('added@test.com')
@@ -1261,13 +949,7 @@ RSpec.describe Scimitar::Resources::Mixin do
1261
949
  nature: 'add',
1262
950
  path: path,
1263
951
  value: 'added@test.com',
1264
- altering_hash: scim_hash,
1265
- with_attr_map: {
1266
- emails: [
1267
- { match: 'type', with: 'home', using: { value: :home_email, primary: true } },
1268
- { match: 'type', with: 'work', using: { value: :work_email } },
1269
- ]
1270
- }
952
+ altering_hash: scim_hash
1271
953
  )
1272
954
 
1273
955
  expect(scim_hash['emails'][0]['value']).to eql('added@test.com')
@@ -1284,13 +966,7 @@ RSpec.describe Scimitar::Resources::Mixin do
1284
966
  nature: 'add',
1285
967
  path: path,
1286
968
  value: [ { 'type' => 'work', 'value' => 'work@test.com' } ], # NOTE - to-add value is an Array (and must be)
1287
- altering_hash: scim_hash,
1288
- with_attr_map: {
1289
- emails: [
1290
- { match: 'type', with: 'home', using: { value: :home_email, primary: true } },
1291
- { match: 'type', with: 'work', using: { value: :work_email } },
1292
- ]
1293
- }
969
+ altering_hash: scim_hash
1294
970
  )
1295
971
 
1296
972
  expect(scim_hash['emails'].size).to eql(1)
@@ -1306,7 +982,7 @@ RSpec.describe Scimitar::Resources::Mixin do
1306
982
  #
1307
983
  context 'remove' do
1308
984
  context 'when prior value already exists' do
1309
- it 'simple value: clears to "nil" in order to remove' do
985
+ it 'simple value: removes' do
1310
986
  path = [ 'userName' ]
1311
987
  scim_hash = { 'userName' => 'bar' }.with_indifferent_case_insensitive_access()
1312
988
 
@@ -1315,14 +991,13 @@ RSpec.describe Scimitar::Resources::Mixin do
1315
991
  nature: 'remove',
1316
992
  path: path,
1317
993
  value: nil,
1318
- altering_hash: scim_hash,
1319
- with_attr_map: { userName: :user_name }
994
+ altering_hash: scim_hash
1320
995
  )
1321
996
 
1322
- expect(scim_hash).to eql({ 'userName' => nil })
997
+ expect(scim_hash).to be_empty
1323
998
  end
1324
999
 
1325
- it 'nested simple value: clears to "nil" in order to remove' do
1000
+ it 'nested simple value: removes' do
1326
1001
  path = [ 'name', 'givenName' ]
1327
1002
  scim_hash = { 'name' => { 'givenName' => 'Foo', 'familyName' => 'Bar' } }.with_indifferent_case_insensitive_access()
1328
1003
 
@@ -1331,15 +1006,15 @@ RSpec.describe Scimitar::Resources::Mixin do
1331
1006
  nature: 'remove',
1332
1007
  path: path,
1333
1008
  value: nil,
1334
- altering_hash: scim_hash,
1335
- with_attr_map: { name: { givenName: :first_name, familyName: :last_name } }
1009
+ altering_hash: scim_hash
1336
1010
  )
1337
1011
 
1338
- expect(scim_hash).to eql({ 'name' => { 'givenName' => nil, 'familyName' => 'Bar' } })
1012
+ expect(scim_hash['name']).to_not have_key('givenName')
1013
+ expect(scim_hash['name']['familyName']).to eql('Bar')
1339
1014
  end
1340
1015
 
1341
1016
  context 'with filter mid-path' do
1342
- it 'by string match: clears to "nil" in order to remove' do
1017
+ it 'by string match: removes' do
1343
1018
  path = [ 'emails[type eq "work"]', 'value' ]
1344
1019
  scim_hash = {
1345
1020
  'emails' => [
@@ -1359,30 +1034,14 @@ RSpec.describe Scimitar::Resources::Mixin do
1359
1034
  nature: 'remove',
1360
1035
  path: path,
1361
1036
  value: nil,
1362
- altering_hash: scim_hash,
1363
- with_attr_map: {
1364
- emails: [
1365
- { match: 'type', with: 'home', using: { value: :home_email, primary: true } },
1366
- { match: 'type', with: 'work', using: { value: :work_email } },
1367
- ]
1368
- }
1037
+ altering_hash: scim_hash
1369
1038
  )
1370
1039
 
1371
- expect(scim_hash).to eql({
1372
- 'emails' => [
1373
- {
1374
- 'type' => 'home',
1375
- 'value' => 'home@test.com'
1376
- },
1377
- {
1378
- 'type' => 'work',
1379
- 'value' => nil
1380
- }
1381
- ]
1382
- })
1040
+ expect(scim_hash['emails'][0]['value']).to eql('home@test.com')
1041
+ expect(scim_hash['emails'][1]).to_not have_key('value')
1383
1042
  end
1384
1043
 
1385
- it 'by boolean match: clears to "nil" in order to remove' do
1044
+ it 'by boolean match: removes' do
1386
1045
  path = [ 'emails[primary eq true]', 'value' ]
1387
1046
  scim_hash = {
1388
1047
  'emails' => [
@@ -1401,29 +1060,14 @@ RSpec.describe Scimitar::Resources::Mixin do
1401
1060
  nature: 'remove',
1402
1061
  path: path,
1403
1062
  value: nil,
1404
- altering_hash: scim_hash,
1405
- with_attr_map: {
1406
- emails: [
1407
- { match: 'type', with: 'home', using: { value: :home_email, primary: true } },
1408
- { match: 'type', with: 'work', using: { value: :work_email } },
1409
- ]
1410
- }
1063
+ altering_hash: scim_hash
1411
1064
  )
1412
1065
 
1413
- expect(scim_hash).to eql({
1414
- 'emails' => [
1415
- {
1416
- 'value' => 'home@test.com'
1417
- },
1418
- {
1419
- 'value' => nil,
1420
- 'primary' => true
1421
- }
1422
- ]
1423
- })
1066
+ expect(scim_hash['emails'][0]['value']).to eql('home@test.com')
1067
+ expect(scim_hash['emails'][1]).to_not have_key('value')
1424
1068
  end
1425
1069
 
1426
- it 'multiple matches: clears all to "nil" in order to remove' do
1070
+ it 'multiple matches: removes all' do
1427
1071
  path = [ 'emails[type eq "work"]', 'value' ]
1428
1072
  scim_hash = {
1429
1073
  'emails' => [
@@ -1434,11 +1078,7 @@ RSpec.describe Scimitar::Resources::Mixin do
1434
1078
  {
1435
1079
  'type' => 'work',
1436
1080
  'value' => 'work_2@test.com'
1437
- },
1438
- {
1439
- 'type' => 'home',
1440
- 'value' => 'home@test.com'
1441
- },
1081
+ }
1442
1082
  ]
1443
1083
  }.with_indifferent_case_insensitive_access()
1444
1084
 
@@ -1447,36 +1087,16 @@ RSpec.describe Scimitar::Resources::Mixin do
1447
1087
  nature: 'remove',
1448
1088
  path: path,
1449
1089
  value: nil,
1450
- altering_hash: scim_hash,
1451
- with_attr_map: {
1452
- emails: [
1453
- { match: 'type', with: 'home', using: { value: :home_email, primary: true } },
1454
- { match: 'type', with: 'work', using: { value: :work_email } },
1455
- ]
1456
- }
1090
+ altering_hash: scim_hash
1457
1091
  )
1458
1092
 
1459
- expect(scim_hash).to eql({
1460
- 'emails' => [
1461
- {
1462
- 'type' => 'work',
1463
- 'value' => nil
1464
- },
1465
- {
1466
- 'type' => 'work',
1467
- 'value' => nil
1468
- },
1469
- {
1470
- 'type' => 'home',
1471
- 'value' => 'home@test.com'
1472
- },
1473
- ]
1474
- })
1093
+ expect(scim_hash['emails'][0]).to_not have_key('value')
1094
+ expect(scim_hash['emails'][1]).to_not have_key('value')
1475
1095
  end
1476
1096
  end # "context 'with filter mid-path' do"
1477
1097
 
1478
1098
  context 'with filter at end of path' do
1479
- it 'by string match: clears to "nil" in order to remove' do
1099
+ it 'by string match: removes entire matching array entry' do
1480
1100
  path = [ 'emails[type eq "work"]' ]
1481
1101
  scim_hash = {
1482
1102
  'emails' => [
@@ -1496,39 +1116,23 @@ RSpec.describe Scimitar::Resources::Mixin do
1496
1116
  nature: 'remove',
1497
1117
  path: path,
1498
1118
  value: nil,
1499
- altering_hash: scim_hash,
1500
- with_attr_map: {
1501
- emails: [
1502
- { match: 'type', with: 'home', using: { value: :home_email, primary: true } },
1503
- { match: 'type', with: 'work', using: { value: :work_email } },
1504
- ]
1505
- }
1119
+ altering_hash: scim_hash
1506
1120
  )
1507
1121
 
1508
- expect(scim_hash).to eql({
1509
- 'emails' => [
1510
- {
1511
- 'type' => 'home',
1512
- 'value' => 'home@test.com'
1513
- },
1514
- {
1515
- 'type' => 'work',
1516
- 'value' => nil
1517
- }
1518
- ]
1519
- })
1122
+ expect(scim_hash['emails'].size).to eql(1)
1123
+ expect(scim_hash['emails'][0]['value']).to eql('home@test.com')
1520
1124
  end
1521
1125
 
1522
- it 'by boolean match: clears to "nil" in order to remove' do
1126
+ it 'by boolean match: removes entire matching array entry' do
1523
1127
  path = [ 'emails[primary eq true]' ]
1524
1128
  scim_hash = {
1525
1129
  'emails' => [
1526
1130
  {
1527
- 'value' => 'home@test.com',
1528
- 'primary' => true
1131
+ 'value' => 'home@test.com'
1529
1132
  },
1530
1133
  {
1531
- 'value' => 'work@test.com'
1134
+ 'value' => 'work@test.com',
1135
+ 'primary' => true
1532
1136
  }
1533
1137
  ]
1534
1138
  }.with_indifferent_case_insensitive_access()
@@ -1538,29 +1142,14 @@ RSpec.describe Scimitar::Resources::Mixin do
1538
1142
  nature: 'remove',
1539
1143
  path: path,
1540
1144
  value: nil,
1541
- altering_hash: scim_hash,
1542
- with_attr_map: {
1543
- emails: [
1544
- { match: 'type', with: 'home', using: { value: :home_email, primary: true } },
1545
- { match: 'type', with: 'work', using: { value: :work_email } },
1546
- ]
1547
- }
1145
+ altering_hash: scim_hash
1548
1146
  )
1549
1147
 
1550
- expect(scim_hash).to eql({
1551
- 'emails' => [
1552
- {
1553
- 'value' => nil,
1554
- 'primary' => true
1555
- },
1556
- {
1557
- 'value' => 'work@test.com'
1558
- }
1559
- ]
1560
- })
1148
+ expect(scim_hash['emails'].size).to eql(1)
1149
+ expect(scim_hash['emails'][0]['value']).to eql('home@test.com')
1561
1150
  end
1562
1151
 
1563
- it 'multiple matches: clears all to "nil" in order to remove' do
1152
+ it 'multiple matches: removes all matching array entries' do
1564
1153
  path = [ 'emails[type eq "work"]' ]
1565
1154
  scim_hash = {
1566
1155
  'emails' => [
@@ -1584,45 +1173,21 @@ RSpec.describe Scimitar::Resources::Mixin do
1584
1173
  nature: 'remove',
1585
1174
  path: path,
1586
1175
  value: nil,
1587
- altering_hash: scim_hash,
1588
- with_attr_map: {
1589
- emails: [
1590
- { match: 'type', with: 'home', using: { value: :home_email, primary: true } },
1591
- { match: 'type', with: 'work', using: { value: :work_email } },
1592
- ]
1593
- }
1176
+ altering_hash: scim_hash
1594
1177
  )
1595
1178
 
1596
- expect(scim_hash).to eql({
1597
- 'emails' => [
1598
- {
1599
- 'type' => 'work',
1600
- 'value' => nil
1601
- },
1602
- {
1603
- 'type' => 'work',
1604
- 'value' => nil
1605
- },
1606
- {
1607
- 'type' => 'home',
1608
- 'value' => 'home@test.com'
1609
- },
1610
- ]
1611
- })
1179
+ expect(scim_hash['emails'].size).to eql(1)
1180
+ expect(scim_hash['emails'][0]['value']).to eql('home@test.com')
1612
1181
  end
1613
1182
  end # "context 'with filter at end of path' do"
1614
1183
 
1615
- it 'whole array: clears mapped values to "nil" to remove them' do
1184
+ it 'whole array: removes' do
1616
1185
  path = [ 'emails' ]
1617
1186
  scim_hash = {
1618
1187
  'emails' => [
1619
1188
  {
1620
1189
  'type' => 'home',
1621
1190
  'value' => 'home@test.com'
1622
- },
1623
- {
1624
- 'type' => 'work',
1625
- 'value' => 'work@test.com'
1626
1191
  }
1627
1192
  ]
1628
1193
  }.with_indifferent_case_insensitive_access()
@@ -1632,741 +1197,11 @@ RSpec.describe Scimitar::Resources::Mixin do
1632
1197
  nature: 'remove',
1633
1198
  path: path,
1634
1199
  value: nil,
1635
- altering_hash: scim_hash,
1636
- with_attr_map: {
1637
- emails: [
1638
- { match: 'type', with: 'home', using: { value: :home_email, primary: true } },
1639
- { match: 'type', with: 'work', using: { value: :work_email } },
1640
- ]
1641
- }
1200
+ altering_hash: scim_hash
1642
1201
  )
1643
1202
 
1644
- expect(scim_hash).to eql({
1645
- 'emails' => [
1646
- {
1647
- 'type' => 'home',
1648
- 'value' => nil
1649
- },
1650
- {
1651
- 'type' => 'work',
1652
- 'value' => nil
1653
- }
1654
- ]
1655
- })
1203
+ expect(scim_hash).to_not have_key('emails')
1656
1204
  end
1657
-
1658
- # What we expect:
1659
- #
1660
- # https://tools.ietf.org/html/rfc7644#section-3.5.2.2
1661
- # https://docs.snowflake.com/en/user-guide/scim-intro.html#patch-scim-v2-groups-id
1662
- #
1663
- # ...vs accounting for the unusual payloads we sometimes get,
1664
- # tested here.
1665
- #
1666
- context 'special cases' do
1667
-
1668
- # https://learn.microsoft.com/en-us/azure/active-directory/app-provisioning/use-scim-to-provision-users-and-groups#update-group-remove-members
1669
- #
1670
- context 'Microsoft-style payload' do
1671
- context 'removing a user from a group' do
1672
- it 'removes identified user' do
1673
- path = [ 'members' ]
1674
- value = [ { '$ref' => nil, 'value' => 'f648f8d5ea4e4cd38e9c' } ]
1675
- scim_hash = {
1676
- 'displayname' => 'Mock group',
1677
- 'members' => [
1678
- {
1679
- 'value' => '50ca93d04ab0c2de4772',
1680
- 'display' => 'Ingrid Smith',
1681
- 'type' => 'User'
1682
- },
1683
- {
1684
- 'value' => 'f648f8d5ea4e4cd38e9c',
1685
- 'display' => 'Fred Smith',
1686
- 'type' => 'User'
1687
- },
1688
- {
1689
- 'value' => 'a774d480e8112101375b',
1690
- 'display' => 'Taylor Smith',
1691
- 'type' => 'User'
1692
- }
1693
- ]
1694
- }.with_indifferent_case_insensitive_access()
1695
-
1696
- @instance.send(
1697
- :from_patch_backend!,
1698
- nature: 'remove',
1699
- path: path,
1700
- value: value,
1701
- altering_hash: scim_hash,
1702
- with_attr_map: {
1703
- members: [
1704
- { list: :members, using: { value: :id, display: :full_name, type: 'User' } }
1705
- ]
1706
- }
1707
- )
1708
-
1709
- expect(scim_hash).to eql({
1710
- 'displayname' => 'Mock group',
1711
- 'members' => [
1712
- {
1713
- 'value' => '50ca93d04ab0c2de4772',
1714
- 'display' => 'Ingrid Smith',
1715
- 'type' => 'User'
1716
- },
1717
- {
1718
- 'value' => 'a774d480e8112101375b',
1719
- 'display' => 'Taylor Smith',
1720
- 'type' => 'User'
1721
- }
1722
- ]
1723
- })
1724
- end
1725
-
1726
- it 'removes multiple identified users' do
1727
- path = [ 'members' ]
1728
- value = [
1729
- { '$ref' => nil, 'value' => 'f648f8d5ea4e4cd38e9c' },
1730
- { '$ref' => nil, 'value' => '50ca93d04ab0c2de4772' }
1731
- ]
1732
- scim_hash = {
1733
- 'displayname' => 'Mock group',
1734
- 'members' => [
1735
- {
1736
- 'value' => '50ca93d04ab0c2de4772',
1737
- 'display' => 'Ingrid Smith',
1738
- 'type' => 'User'
1739
- },
1740
- {
1741
- 'value' => 'f648f8d5ea4e4cd38e9c',
1742
- 'display' => 'Fred Smith',
1743
- 'type' => 'User'
1744
- },
1745
- {
1746
- 'value' => 'a774d480e8112101375b',
1747
- 'display' => 'Taylor Smith',
1748
- 'type' => 'User'
1749
- }
1750
- ]
1751
- }.with_indifferent_case_insensitive_access()
1752
-
1753
- @instance.send(
1754
- :from_patch_backend!,
1755
- nature: 'remove',
1756
- path: path,
1757
- value: value,
1758
- altering_hash: scim_hash,
1759
- with_attr_map: {
1760
- members: [
1761
- { list: :members, using: { value: :id, display: :full_name, type: 'User' } }
1762
- ]
1763
- }
1764
- )
1765
-
1766
- expect(scim_hash).to eql({
1767
- 'displayname' => 'Mock group',
1768
- 'members' => [
1769
- {
1770
- 'value' => 'a774d480e8112101375b',
1771
- 'display' => 'Taylor Smith',
1772
- 'type' => 'User'
1773
- }
1774
- ]
1775
- })
1776
- end
1777
-
1778
- it 'removes all users individually without error' do
1779
- path = [ 'members' ]
1780
- value = [ { '$ref' => nil, 'value' => 'f648f8d5ea4e4cd38e9c' } ]
1781
- scim_hash = {
1782
- 'displayname' => 'Mock group',
1783
- 'members' => [
1784
- {
1785
- 'value' => 'f648f8d5ea4e4cd38e9c',
1786
- 'display' => 'Fred Smith',
1787
- 'type' => 'User'
1788
- }
1789
- ]
1790
- }.with_indifferent_case_insensitive_access()
1791
-
1792
- @instance.send(
1793
- :from_patch_backend!,
1794
- nature: 'remove',
1795
- path: path,
1796
- value: value,
1797
- altering_hash: scim_hash,
1798
- with_attr_map: {
1799
- members: [
1800
- { list: :members, using: { value: :id, display: :full_name, type: 'User' } }
1801
- ]
1802
- }
1803
- )
1804
-
1805
- expect(scim_hash).to eql({
1806
- 'displayname' => 'Mock group',
1807
- 'members' => []
1808
- })
1809
- end
1810
-
1811
- it 'can match on multiple attributes' do
1812
- path = [ 'members' ]
1813
- value = [ { '$ref' => nil, 'value' => 'f648f8d5ea4e4cd38e9c', 'type' => 'User' } ]
1814
- scim_hash = {
1815
- 'displayname' => 'Mock group',
1816
- 'members' => [
1817
- {
1818
- 'value' => 'f648f8d5ea4e4cd38e9c',
1819
- 'display' => 'Fred Smith',
1820
- 'type' => 'User'
1821
- }
1822
- ]
1823
- }.with_indifferent_case_insensitive_access()
1824
-
1825
- @instance.send(
1826
- :from_patch_backend!,
1827
- nature: 'remove',
1828
- path: path,
1829
- value: value,
1830
- altering_hash: scim_hash,
1831
- with_attr_map: {
1832
- members: [
1833
- { list: :members, using: { value: :id, display: :full_name, type: 'User' } }
1834
- ]
1835
- }
1836
- )
1837
-
1838
- expect(scim_hash).to eql({
1839
- 'displayname' => 'Mock group',
1840
- 'members' => []
1841
- })
1842
- end
1843
-
1844
- it 'ignores unrecognised users' do
1845
- path = [ 'members' ]
1846
- value = [ { '$ref' => nil, 'value' => '11b054a9c85216ed9356' } ]
1847
- scim_hash = {
1848
- 'displayname' => 'Mock group',
1849
- 'members' => [
1850
- {
1851
- 'value' => 'f648f8d5ea4e4cd38e9c',
1852
- 'display' => 'Fred Smith',
1853
- 'type' => 'User'
1854
- }
1855
- ]
1856
- }.with_indifferent_case_insensitive_access()
1857
-
1858
- @instance.send(
1859
- :from_patch_backend!,
1860
- nature: 'remove',
1861
- path: path,
1862
- value: value,
1863
- altering_hash: scim_hash,
1864
- with_attr_map: {
1865
- members: [
1866
- { list: :members, using: { value: :id, display: :full_name, type: 'User' } }
1867
- ]
1868
- }
1869
- )
1870
-
1871
- # The 'value' mismatched, so the user was not removed.
1872
- #
1873
- expect(scim_hash).to eql({
1874
- 'displayname' => 'Mock group',
1875
- 'members' => [
1876
- {
1877
- 'value' => 'f648f8d5ea4e4cd38e9c',
1878
- 'display' => 'Fred Smith',
1879
- 'type' => 'User'
1880
- }
1881
- ]
1882
- })
1883
- end
1884
-
1885
- it 'ignores a mismatch on (for example) "type"' do
1886
- path = [ 'members' ]
1887
- value = [ { '$ref' => nil, 'value' => 'f648f8d5ea4e4cd38e9c', 'type' => 'Group' } ]
1888
- scim_hash = {
1889
- 'displayname' => 'Mock group',
1890
- 'members' => [
1891
- {
1892
- 'value' => 'f648f8d5ea4e4cd38e9c',
1893
- 'display' => 'Fred Smith',
1894
- 'type' => 'User'
1895
- }
1896
- ]
1897
- }.with_indifferent_case_insensitive_access()
1898
-
1899
- @instance.send(
1900
- :from_patch_backend!,
1901
- nature: 'remove',
1902
- path: path,
1903
- value: value,
1904
- altering_hash: scim_hash,
1905
- with_attr_map: {
1906
- members: [
1907
- { list: :members, using: { value: :id, display: :full_name, type: 'User' } }
1908
- ]
1909
- }
1910
- )
1911
-
1912
- # Type 'Group' mismatches 'User', so the user was not
1913
- # removed.
1914
- #
1915
- expect(scim_hash).to eql({
1916
- 'displayname' => 'Mock group',
1917
- 'members' => [
1918
- {
1919
- 'value' => 'f648f8d5ea4e4cd38e9c',
1920
- 'display' => 'Fred Smith',
1921
- 'type' => 'User'
1922
- }
1923
- ]
1924
- })
1925
- end
1926
-
1927
- it 'matches keys case-insensitive (but preserves case in response)' do
1928
- path = [ 'members' ]
1929
- value = [ { '$ref' => nil, 'VALUe' => 'f648f8d5ea4e4cd38e9c' } ]
1930
- scim_hash = {
1931
- 'displayname' => 'Mock group',
1932
- 'memBERS' => [
1933
- {
1934
- 'vaLUe' => 'f648f8d5ea4e4cd38e9c',
1935
- 'display' => 'Fred Smith',
1936
- 'type' => 'User'
1937
- }
1938
- ]
1939
- }.with_indifferent_case_insensitive_access()
1940
-
1941
- @instance.send(
1942
- :from_patch_backend!,
1943
- nature: 'remove',
1944
- path: path,
1945
- value: value,
1946
- altering_hash: scim_hash,
1947
- with_attr_map: {
1948
- members: [
1949
- { list: :members, using: { value: :id, display: :full_name, type: 'User' } }
1950
- ]
1951
- }
1952
- )
1953
-
1954
- expect(scim_hash).to eql({
1955
- 'displayname' => 'Mock group',
1956
- 'memBERS' => []
1957
- })
1958
- end
1959
-
1960
- it 'matches values case-sensitive' do
1961
- path = [ 'members' ]
1962
- value = [ { '$ref' => nil, 'value' => 'f648f8d5ea4e4cd38e9c', 'type' => 'USER' } ]
1963
- scim_hash = {
1964
- 'displayName' => 'Mock group',
1965
- 'members' => [
1966
- {
1967
- 'value' => 'f648f8d5ea4e4cd38e9c',
1968
- 'display' => 'Fred Smith',
1969
- 'type' => 'User'
1970
- }
1971
- ]
1972
- }.with_indifferent_case_insensitive_access()
1973
-
1974
- @instance.send(
1975
- :from_patch_backend!,
1976
- nature: 'remove',
1977
- path: path,
1978
- value: value,
1979
- altering_hash: scim_hash,
1980
- with_attr_map: {
1981
- members: [
1982
- { list: :members, using: { value: :id, display: :full_name, type: 'User' } }
1983
- ]
1984
- }
1985
- )
1986
-
1987
- # USER mismatches User, so the user was not removed.
1988
- #
1989
- expect(scim_hash).to eql({
1990
- 'displayName' => 'Mock group',
1991
- 'members' => [
1992
- {
1993
- 'value' => 'f648f8d5ea4e4cd38e9c',
1994
- 'display' => 'Fred Smith',
1995
- 'type' => 'User'
1996
- }
1997
- ]
1998
- })
1999
- end
2000
- end # "context 'removing a user from a group' do"
2001
-
2002
- context 'generic use' do
2003
- it 'clears static map matched items to "nil" in order to remove' do
2004
- path = [ 'emails' ]
2005
- value = [ { 'type' => 'work' } ]
2006
- scim_hash = {
2007
- 'emails' => [
2008
- {
2009
- 'type' => 'home',
2010
- 'value' => 'home@test.com'
2011
- },
2012
- {
2013
- 'type' => 'work',
2014
- 'value' => 'work@test.com'
2015
- }
2016
- ]
2017
- }.with_indifferent_case_insensitive_access()
2018
-
2019
- @instance.send(
2020
- :from_patch_backend!,
2021
- nature: 'remove',
2022
- path: path,
2023
- value: value,
2024
- altering_hash: scim_hash,
2025
- with_attr_map: {
2026
- emails: [
2027
- { match: 'type', with: 'home', using: { value: :home_email, primary: true } },
2028
- { match: 'type', with: 'work', using: { value: :work_email } },
2029
- ]
2030
- }
2031
- )
2032
-
2033
- expect(scim_hash).to eql({
2034
- 'emails' => [
2035
- {
2036
- 'type' => 'home',
2037
- 'value' => 'home@test.com'
2038
- },
2039
- {
2040
- 'type' => 'work',
2041
- 'value' => nil
2042
- }
2043
- ]
2044
- })
2045
- end
2046
-
2047
- it 'ignores unmatched items' do
2048
- path = [ 'emails' ]
2049
- value = [ { 'type' => 'missing' } ]
2050
- scim_hash = {
2051
- 'emails' => [
2052
- {
2053
- 'type' => 'home',
2054
- 'value' => 'home@test.com'
2055
- },
2056
- {
2057
- 'type' => 'work',
2058
- 'value' => 'work@test.com'
2059
- }
2060
- ]
2061
- }.with_indifferent_case_insensitive_access()
2062
-
2063
- @instance.send(
2064
- :from_patch_backend!,
2065
- nature: 'remove',
2066
- path: path,
2067
- value: value,
2068
- altering_hash: scim_hash,
2069
- with_attr_map: {
2070
- emails: [
2071
- { match: 'type', with: 'home', using: { value: :home_email, primary: true } },
2072
- { match: 'type', with: 'work', using: { value: :work_email } },
2073
- ]
2074
- }
2075
- )
2076
-
2077
- expect(scim_hash).to eql({
2078
- 'emails' => [
2079
- {
2080
- 'type' => 'home',
2081
- 'value' => 'home@test.com'
2082
- },
2083
- {
2084
- 'type' => 'work',
2085
- 'value' => 'work@test.com'
2086
- }
2087
- ]
2088
- })
2089
- end
2090
-
2091
- it 'compares string forms' do
2092
- path = [ 'test' ]
2093
- value = [
2094
- { 'active' => true, 'value' => '12' },
2095
- { 'active' => 'false', 'value' => 42 }
2096
- ]
2097
- scim_hash = {
2098
- 'test' => [
2099
- {
2100
- 'active' => 'true',
2101
- 'value' => 12
2102
- },
2103
- {
2104
- 'active' => false,
2105
- 'value' => '42'
2106
- },
2107
- {
2108
- 'active' => 'hello',
2109
- 'value' => 'world'
2110
- }
2111
- ]
2112
- }.with_indifferent_case_insensitive_access()
2113
-
2114
- @instance.send(
2115
- :from_patch_backend!,
2116
- nature: 'remove',
2117
- path: path,
2118
- value: value,
2119
- altering_hash: scim_hash,
2120
- with_attr_map: {
2121
- test: [
2122
- {
2123
- list: :test,
2124
- using: {
2125
- active: :active,
2126
- value: :value
2127
- }
2128
- }
2129
- ]
2130
- }
2131
- )
2132
-
2133
- expect(scim_hash).to eql({
2134
- 'test' => [
2135
- {
2136
- 'active' => 'hello',
2137
- 'value' => 'world'
2138
- }
2139
- ]
2140
- })
2141
- end
2142
-
2143
- it 'handles a singular to-remove value rather than an array' do
2144
- path = [ 'emails' ]
2145
- value = { 'type' => 'work' }
2146
- scim_hash = {
2147
- 'emails' => [
2148
- {
2149
- 'type' => 'home',
2150
- 'value' => 'home@test.com'
2151
- },
2152
- {
2153
- 'type' => 'work',
2154
- 'value' => 'work@test.com'
2155
- }
2156
- ]
2157
- }.with_indifferent_case_insensitive_access()
2158
-
2159
- @instance.send(
2160
- :from_patch_backend!,
2161
- nature: 'remove',
2162
- path: path,
2163
- value: value,
2164
- altering_hash: scim_hash,
2165
- with_attr_map: {
2166
- emails: [
2167
- { match: 'type', with: 'home', using: { value: :home_email } },
2168
- { match: 'type', with: 'work', using: { value: :work_email } },
2169
- ]
2170
- }
2171
- )
2172
-
2173
- expect(scim_hash).to eql({
2174
- 'emails' => [
2175
- {
2176
- 'type' => 'home',
2177
- 'value' => 'home@test.com'
2178
- },
2179
- {
2180
- 'type' => 'work',
2181
- 'value' => nil
2182
- }
2183
- ]
2184
- })
2185
- end
2186
-
2187
- it 'handles simple values rather than object (Hash) values' do
2188
- path = [ 'test' ]
2189
- value = 42
2190
- scim_hash = {
2191
- 'test' => [
2192
- '21',
2193
- '42',
2194
- '15'
2195
- ]
2196
- }.with_indifferent_case_insensitive_access()
2197
-
2198
- @instance.send(
2199
- :from_patch_backend!,
2200
- nature: 'remove',
2201
- path: path,
2202
- value: value,
2203
- altering_hash: scim_hash,
2204
- with_attr_map: {
2205
- test: []
2206
- }
2207
- )
2208
-
2209
- expect(scim_hash).to eql({
2210
- 'test' => [
2211
- '21',
2212
- '15'
2213
- ]
2214
- })
2215
- end
2216
- end
2217
- end # "context 'Microsoft-style payload' do"
2218
-
2219
- # https://help.salesforce.com/s/articleView?id=sf.identity_scim_manage_groups.htm&type=5
2220
- #
2221
- context 'Salesforce-style payload' do
2222
- it 'removes identified user' do
2223
- path = [ 'members' ]
2224
- value = { 'members' => [ { '$ref' => nil, 'value' => 'f648f8d5ea4e4cd38e9c' } ] }
2225
- scim_hash = {
2226
- 'displayname' => 'Mock group',
2227
- 'members' => [
2228
- {
2229
- 'value' => '50ca93d04ab0c2de4772',
2230
- 'display' => 'Ingrid Smith',
2231
- 'type' => 'User'
2232
- },
2233
- {
2234
- 'value' => 'f648f8d5ea4e4cd38e9c',
2235
- 'display' => 'Fred Smith',
2236
- 'type' => 'User'
2237
- }
2238
- ]
2239
- }.with_indifferent_case_insensitive_access()
2240
-
2241
- @instance.send(
2242
- :from_patch_backend!,
2243
- nature: 'remove',
2244
- path: path,
2245
- value: value,
2246
- altering_hash: scim_hash,
2247
- with_attr_map: {
2248
- displayName: :name,
2249
- members: [
2250
- list: :members,
2251
- using: {
2252
- value: :id,
2253
- display: :full_name,
2254
- type: 'User'
2255
- }
2256
- ]
2257
- }
2258
- )
2259
-
2260
- expect(scim_hash).to eql({
2261
- 'displayname' => 'Mock group',
2262
- 'members' => [
2263
- {
2264
- 'value' => '50ca93d04ab0c2de4772',
2265
- 'display' => 'Ingrid Smith',
2266
- 'type' => 'User'
2267
- }
2268
- ]
2269
- })
2270
- end
2271
-
2272
- it 'matches the "members" key case-insensitive' do
2273
- path = [ 'members' ]
2274
- value = { 'MEMBERS' => [ { '$ref' => nil, 'value' => 'f648f8d5ea4e4cd38e9c' } ] }
2275
- scim_hash = {
2276
- 'displayname' => 'Mock group',
2277
- 'members' => [
2278
- {
2279
- 'value' => 'f648f8d5ea4e4cd38e9c',
2280
- 'display' => 'Fred Smith',
2281
- 'type' => 'User'
2282
- },
2283
- {
2284
- 'value' => 'a774d480e8112101375b',
2285
- 'display' => 'Taylor Smith',
2286
- 'type' => 'User'
2287
- }
2288
- ]
2289
- }.with_indifferent_case_insensitive_access()
2290
-
2291
- @instance.send(
2292
- :from_patch_backend!,
2293
- nature: 'remove',
2294
- path: path,
2295
- value: value,
2296
- altering_hash: scim_hash,
2297
- with_attr_map: {
2298
- displayName: :name,
2299
- members: [
2300
- list: :members,
2301
- using: {
2302
- value: :id,
2303
- display: :full_name,
2304
- type: 'User'
2305
- }
2306
- ]
2307
- }
2308
- )
2309
-
2310
- expect(scim_hash).to eql({
2311
- 'displayname' => 'Mock group',
2312
- 'members' => [
2313
- {
2314
- 'value' => 'a774d480e8112101375b',
2315
- 'display' => 'Taylor Smith',
2316
- 'type' => 'User'
2317
- }
2318
- ]
2319
- })
2320
- end
2321
-
2322
- it 'ignores unrecognised users' do
2323
- path = [ 'members' ]
2324
- value = { 'members' => [ { '$ref' => nil, 'value' => '11b054a9c85216ed9356' } ] }
2325
- scim_hash = {
2326
- 'displayname' => 'Mock group',
2327
- 'members' => [
2328
- {
2329
- 'value' => 'f648f8d5ea4e4cd38e9c',
2330
- 'display' => 'Fred Smith',
2331
- 'type' => 'User'
2332
- }
2333
- ]
2334
- }.with_indifferent_case_insensitive_access()
2335
-
2336
- @instance.send(
2337
- :from_patch_backend!,
2338
- nature: 'remove',
2339
- path: path,
2340
- value: value,
2341
- altering_hash: scim_hash,
2342
- with_attr_map: {
2343
- displayName: :name,
2344
- members: [
2345
- list: :members,
2346
- using: {
2347
- value: :id,
2348
- display: :full_name,
2349
- type: 'User'
2350
- }
2351
- ]
2352
- }
2353
- )
2354
-
2355
- # The 'value' mismatched, so the user was not removed.
2356
- #
2357
- expect(scim_hash).to eql({
2358
- 'displayname' => 'Mock group',
2359
- 'members' => [
2360
- {
2361
- 'value' => 'f648f8d5ea4e4cd38e9c',
2362
- 'display' => 'Fred Smith',
2363
- 'type' => 'User'
2364
- }
2365
- ]
2366
- })
2367
- end
2368
- end # "context 'Salesforce-style payload' do"
2369
- end # "context 'special cases' do"
2370
1205
  end # context 'when prior value already exists' do
2371
1206
 
2372
1207
  context 'when value is not present' do
@@ -2379,8 +1214,7 @@ RSpec.describe Scimitar::Resources::Mixin do
2379
1214
  nature: 'remove',
2380
1215
  path: path,
2381
1216
  value: nil,
2382
- altering_hash: scim_hash,
2383
- with_attr_map: { userName: :user_name }
1217
+ altering_hash: scim_hash
2384
1218
  )
2385
1219
 
2386
1220
  expect(scim_hash).to be_empty
@@ -2395,8 +1229,7 @@ RSpec.describe Scimitar::Resources::Mixin do
2395
1229
  nature: 'remove',
2396
1230
  path: path,
2397
1231
  value: nil,
2398
- altering_hash: scim_hash,
2399
- with_attr_map: { name: { givenName: :first_name, familyName: :last_name } }
1232
+ altering_hash: scim_hash
2400
1233
  )
2401
1234
 
2402
1235
  expect(scim_hash['name']).to_not have_key('givenName')
@@ -2420,13 +1253,7 @@ RSpec.describe Scimitar::Resources::Mixin do
2420
1253
  nature: 'remove',
2421
1254
  path: path,
2422
1255
  value: nil,
2423
- altering_hash: scim_hash,
2424
- with_attr_map: {
2425
- emails: [
2426
- { match: 'type', with: 'home', using: { value: :home_email } },
2427
- { match: 'type', with: 'work', using: { value: :work_email } },
2428
- ]
2429
- }
1256
+ altering_hash: scim_hash
2430
1257
  )
2431
1258
 
2432
1259
  expect(scim_hash['emails'].size).to eql(1)
@@ -2448,13 +1275,7 @@ RSpec.describe Scimitar::Resources::Mixin do
2448
1275
  nature: 'remove',
2449
1276
  path: path,
2450
1277
  value: nil,
2451
- altering_hash: scim_hash,
2452
- with_attr_map: {
2453
- emails: [
2454
- { match: 'type', with: 'home', using: { value: :home_email } },
2455
- { match: 'type', with: 'work', using: { value: :work_email } },
2456
- ]
2457
- }
1278
+ altering_hash: scim_hash
2458
1279
  )
2459
1280
 
2460
1281
  expect(scim_hash['emails'].size).to eql(1)
@@ -2477,13 +1298,7 @@ RSpec.describe Scimitar::Resources::Mixin do
2477
1298
  nature: 'remove',
2478
1299
  path: path,
2479
1300
  value: nil,
2480
- altering_hash: scim_hash,
2481
- with_attr_map: {
2482
- emails: [
2483
- { match: 'type', with: 'home', using: { value: :home_email } },
2484
- { match: 'type', with: 'work', using: { value: :work_email } },
2485
- ]
2486
- }
1301
+ altering_hash: scim_hash
2487
1302
  )
2488
1303
 
2489
1304
  expect(scim_hash['emails'].size).to eql(1)
@@ -2501,13 +1316,7 @@ RSpec.describe Scimitar::Resources::Mixin do
2501
1316
  nature: 'remove',
2502
1317
  path: path,
2503
1318
  value: nil,
2504
- altering_hash: scim_hash,
2505
- with_attr_map: {
2506
- emails: [
2507
- { match: 'type', with: 'home', using: { value: :home_email } },
2508
- { match: 'type', with: 'work', using: { value: :work_email } },
2509
- ]
2510
- }
1319
+ altering_hash: scim_hash
2511
1320
  )
2512
1321
 
2513
1322
  expect(scim_hash).to be_empty
@@ -2529,13 +1338,7 @@ RSpec.describe Scimitar::Resources::Mixin do
2529
1338
  nature: 'remove',
2530
1339
  path: path,
2531
1340
  value: nil,
2532
- altering_hash: scim_hash,
2533
- with_attr_map: {
2534
- emails: [
2535
- { match: 'type', with: 'home', using: { value: :home_email } },
2536
- { match: 'type', with: 'work', using: { value: :work_email } },
2537
- ]
2538
- }
1341
+ altering_hash: scim_hash
2539
1342
  )
2540
1343
 
2541
1344
  expect(scim_hash['emails'].size).to eql(1)
@@ -2552,13 +1355,7 @@ RSpec.describe Scimitar::Resources::Mixin do
2552
1355
  nature: 'remove',
2553
1356
  path: path,
2554
1357
  value: nil,
2555
- altering_hash: scim_hash,
2556
- with_attr_map: {
2557
- emails: [
2558
- { match: 'type', with: 'home', using: { value: :home_email } },
2559
- { match: 'type', with: 'work', using: { value: :work_email } },
2560
- ]
2561
- }
1358
+ altering_hash: scim_hash
2562
1359
  )
2563
1360
 
2564
1361
  expect(scim_hash).to_not have_key('emails')
@@ -2584,8 +1381,7 @@ RSpec.describe Scimitar::Resources::Mixin do
2584
1381
  nature: 'replace',
2585
1382
  path: path,
2586
1383
  value: 'foo',
2587
- altering_hash: scim_hash,
2588
- with_attr_map: { userName: :user_name }
1384
+ altering_hash: scim_hash
2589
1385
  )
2590
1386
 
2591
1387
  expect(scim_hash['userName']).to eql('foo')
@@ -2600,8 +1396,7 @@ RSpec.describe Scimitar::Resources::Mixin do
2600
1396
  nature: 'replace',
2601
1397
  path: path,
2602
1398
  value: 'Baz',
2603
- altering_hash: scim_hash,
2604
- with_attr_map: { name: { givenName: :first_name, familyName: :last_name } }
1399
+ altering_hash: scim_hash
2605
1400
  )
2606
1401
 
2607
1402
  expect(scim_hash['name']['givenName' ]).to eql('Baz')
@@ -2629,13 +1424,7 @@ RSpec.describe Scimitar::Resources::Mixin do
2629
1424
  nature: 'replace',
2630
1425
  path: path,
2631
1426
  value: 'added_over_original@test.com',
2632
- altering_hash: scim_hash,
2633
- with_attr_map: {
2634
- emails: [
2635
- { match: 'type', with: 'home', using: { value: :home_email, primary: true } },
2636
- { match: 'type', with: 'work', using: { value: :work_email } },
2637
- ]
2638
- }
1427
+ altering_hash: scim_hash
2639
1428
  )
2640
1429
 
2641
1430
  expect(scim_hash['emails'][0]['value']).to eql('home@test.com')
@@ -2661,13 +1450,7 @@ RSpec.describe Scimitar::Resources::Mixin do
2661
1450
  nature: 'replace',
2662
1451
  path: path,
2663
1452
  value: 'added_over_original@test.com',
2664
- altering_hash: scim_hash,
2665
- with_attr_map: {
2666
- emails: [
2667
- { match: 'type', with: 'home', using: { value: :home_email, primary: true } },
2668
- { match: 'type', with: 'work', using: { value: :work_email } },
2669
- ]
2670
- }
1453
+ altering_hash: scim_hash
2671
1454
  )
2672
1455
 
2673
1456
  expect(scim_hash['emails'][0]['value']).to eql('home@test.com')
@@ -2694,13 +1477,7 @@ RSpec.describe Scimitar::Resources::Mixin do
2694
1477
  nature: 'replace',
2695
1478
  path: path,
2696
1479
  value: 'added_over_original@test.com',
2697
- altering_hash: scim_hash,
2698
- with_attr_map: {
2699
- emails: [
2700
- { match: 'type', with: 'home', using: { value: :home_email, primary: true } },
2701
- { match: 'type', with: 'work', using: { value: :work_email } },
2702
- ]
2703
- }
1480
+ altering_hash: scim_hash
2704
1481
  )
2705
1482
 
2706
1483
  expect(scim_hash['emails'][0]['value']).to eql('added_over_original@test.com')
@@ -2729,13 +1506,7 @@ RSpec.describe Scimitar::Resources::Mixin do
2729
1506
  nature: 'replace',
2730
1507
  path: path,
2731
1508
  value: {'type' => 'home', 'primary' => true, 'value' => 'home@test.com'},
2732
- altering_hash: scim_hash,
2733
- with_attr_map: {
2734
- emails: [
2735
- { match: 'type', with: 'home', using: { value: :home_email, primary: true } },
2736
- { match: 'type', with: 'work', using: { value: :work_email } },
2737
- ]
2738
- }
1509
+ altering_hash: scim_hash
2739
1510
  )
2740
1511
 
2741
1512
  expect(scim_hash['emails'].size).to eql(2)
@@ -2769,13 +1540,7 @@ RSpec.describe Scimitar::Resources::Mixin do
2769
1540
  nature: 'replace',
2770
1541
  path: path,
2771
1542
  value: {'type' => 'workinate', 'value' => 'replaced@test.com'},
2772
- altering_hash: scim_hash,
2773
- with_attr_map: {
2774
- emails: [
2775
- { match: 'type', with: 'home', using: { value: :home_email, primary: true } },
2776
- { match: 'type', with: 'work', using: { value: :work_email } },
2777
- ]
2778
- }
1543
+ altering_hash: scim_hash
2779
1544
  )
2780
1545
 
2781
1546
  expect(scim_hash['emails'].size).to eql(3)
@@ -2804,13 +1569,7 @@ RSpec.describe Scimitar::Resources::Mixin do
2804
1569
  nature: 'replace',
2805
1570
  path: path,
2806
1571
  value: [ { 'type' => 'work', 'value' => 'work@test.com' } ], # NOTE - to-add value is an Array (and must be)
2807
- altering_hash: scim_hash,
2808
- with_attr_map: {
2809
- emails: [
2810
- { match: 'type', with: 'home', using: { value: :home_email, primary: true } },
2811
- { match: 'type', with: 'work', using: { value: :work_email } },
2812
- ]
2813
- }
1572
+ altering_hash: scim_hash
2814
1573
  )
2815
1574
 
2816
1575
  expect(scim_hash['emails'].size).to eql(1)
@@ -2829,8 +1588,7 @@ RSpec.describe Scimitar::Resources::Mixin do
2829
1588
  nature: 'replace',
2830
1589
  path: path,
2831
1590
  value: 'foo',
2832
- altering_hash: scim_hash,
2833
- with_attr_map: { userName: :user_name }
1591
+ altering_hash: scim_hash
2834
1592
  )
2835
1593
 
2836
1594
  expect(scim_hash['userName']).to eql('foo')
@@ -2845,8 +1603,7 @@ RSpec.describe Scimitar::Resources::Mixin do
2845
1603
  nature: 'replace',
2846
1604
  path: path,
2847
1605
  value: 'Baz',
2848
- altering_hash: scim_hash,
2849
- with_attr_map: { name: { givenName: :first_name, familyName: :last_name } }
1606
+ altering_hash: scim_hash
2850
1607
  )
2851
1608
 
2852
1609
  expect(scim_hash['name']['givenName']).to eql('Baz')
@@ -2872,13 +1629,7 @@ RSpec.describe Scimitar::Resources::Mixin do
2872
1629
  nature: 'replace',
2873
1630
  path: path,
2874
1631
  value: 'added@test.com',
2875
- altering_hash: scim_hash,
2876
- with_attr_map: {
2877
- emails: [
2878
- { match: 'type', with: 'home', using: { value: :home_email, primary: true } },
2879
- { match: 'type', with: 'work', using: { value: :work_email } },
2880
- ]
2881
- }
1632
+ altering_hash: scim_hash
2882
1633
  )
2883
1634
 
2884
1635
  expect(scim_hash['emails'][0]['value']).to eql('home@test.com')
@@ -2903,13 +1654,7 @@ RSpec.describe Scimitar::Resources::Mixin do
2903
1654
  nature: 'replace',
2904
1655
  path: path,
2905
1656
  value: 'added@test.com',
2906
- altering_hash: scim_hash,
2907
- with_attr_map: {
2908
- emails: [
2909
- { match: 'type', with: 'home', using: { value: :home_email, primary: true } },
2910
- { match: 'type', with: 'work', using: { value: :work_email } },
2911
- ]
2912
- }
1657
+ altering_hash: scim_hash
2913
1658
  )
2914
1659
 
2915
1660
  expect(scim_hash['emails'][0]['value']).to eql('home@test.com')
@@ -2934,13 +1679,7 @@ RSpec.describe Scimitar::Resources::Mixin do
2934
1679
  nature: 'replace',
2935
1680
  path: path,
2936
1681
  value: 'added@test.com',
2937
- altering_hash: scim_hash,
2938
- with_attr_map: {
2939
- emails: [
2940
- { match: 'type', with: 'home', using: { value: :home_email, primary: true } },
2941
- { match: 'type', with: 'work', using: { value: :work_email } },
2942
- ]
2943
- }
1682
+ altering_hash: scim_hash
2944
1683
  )
2945
1684
 
2946
1685
  expect(scim_hash['emails'][0]['value']).to eql('added@test.com')
@@ -2958,13 +1697,7 @@ RSpec.describe Scimitar::Resources::Mixin do
2958
1697
  nature: 'replace',
2959
1698
  path: path,
2960
1699
  value: {'type' => 'work', 'value' => 'work@test.com'},
2961
- altering_hash: scim_hash,
2962
- with_attr_map: {
2963
- emails: [
2964
- { match: 'type', with: 'home', using: { value: :home_email, primary: true } },
2965
- { match: 'type', with: 'work', using: { value: :work_email } },
2966
- ]
2967
- }
1700
+ altering_hash: scim_hash
2968
1701
  )
2969
1702
 
2970
1703
  expect(scim_hash['emails'].size).to eql(1)
@@ -2988,13 +1721,7 @@ RSpec.describe Scimitar::Resources::Mixin do
2988
1721
  nature: 'replace',
2989
1722
  path: path,
2990
1723
  value: {'type' => 'work', 'value' => 'work@test.com'},
2991
- altering_hash: scim_hash,
2992
- with_attr_map: {
2993
- emails: [
2994
- { match: 'type', with: 'home', using: { value: :home_email, primary: true } },
2995
- { match: 'type', with: 'work', using: { value: :work_email } },
2996
- ]
2997
- }
1724
+ altering_hash: scim_hash
2998
1725
  )
2999
1726
 
3000
1727
  expect(scim_hash['emails'].size).to eql(2)
@@ -3013,41 +1740,13 @@ RSpec.describe Scimitar::Resources::Mixin do
3013
1740
  nature: 'replace',
3014
1741
  path: path,
3015
1742
  value: [ { 'type' => 'work', 'value' => 'work@test.com' } ], # NOTE - to-add value is an Array (and must be)
3016
- altering_hash: scim_hash,
3017
- with_attr_map: {
3018
- emails: [
3019
- { match: 'type', with: 'home', using: { value: :home_email, primary: true } },
3020
- { match: 'type', with: 'work', using: { value: :work_email } },
3021
- ]
3022
- }
1743
+ altering_hash: scim_hash
3023
1744
  )
3024
1745
 
3025
1746
  expect(scim_hash['emails'].size).to eql(1)
3026
1747
  expect(scim_hash['emails'][0]['type' ]).to eql('work')
3027
1748
  expect(scim_hash['emails'][0]['value']).to eql('work@test.com')
3028
1749
  end
3029
-
3030
- context 'when prior value already exists, and no path' do
3031
- it 'simple value: overwrites' do
3032
- path = [ 'root' ]
3033
- scim_hash = { 'root' => { 'userName' => 'bar', 'active' => true } }.with_indifferent_case_insensitive_access()
3034
-
3035
- @instance.send(
3036
- :from_patch_backend!,
3037
- nature: 'replace',
3038
- path: path,
3039
- value: { 'active' => false }.with_indifferent_case_insensitive_access(),
3040
- altering_hash: scim_hash,
3041
- with_attr_map: {
3042
- userName: :user_name,
3043
- active: :active
3044
- }
3045
- )
3046
-
3047
- expect(scim_hash['root']['userName']).to eql('bar')
3048
- expect(scim_hash['root']['active']).to eql(false)
3049
- end
3050
- end
3051
1750
  end # context 'when value is not present' do
3052
1751
  end # "context 'replace' do"
3053
1752
 
@@ -3162,8 +1861,7 @@ RSpec.describe Scimitar::Resources::Mixin do
3162
1861
  nature: 'add',
3163
1862
  path: ['complex[type eq "type1"]', 'data', 'nested[nature eq "nature2"]', 'info'],
3164
1863
  value: [{ 'deeper' => 'addition' }],
3165
- altering_hash: scim_hash,
3166
- with_attr_map: @contrived_class.scim_attributes_map()
1864
+ altering_hash: scim_hash
3167
1865
  )
3168
1866
 
3169
1867
  expect(scim_hash.dig('complex', 0, 'data', 'nested', 0, 'info').count).to eql(1) # Unchanged
@@ -3186,8 +1884,7 @@ RSpec.describe Scimitar::Resources::Mixin do
3186
1884
  nature: 'replace',
3187
1885
  path: ['complex[type eq "type1"]', 'data', 'nested[nature eq "nature2"]', 'info'],
3188
1886
  value: [{ 'deeper' => 'addition' }],
3189
- altering_hash: scim_hash,
3190
- with_attr_map: @contrived_class.scim_attributes_map()
1887
+ altering_hash: scim_hash
3191
1888
  )
3192
1889
 
3193
1890
  expect(scim_hash.dig('complex', 0, 'data', 'nested', 0, 'info').count).to eql(1) # Unchanged?
@@ -3204,25 +1901,24 @@ RSpec.describe Scimitar::Resources::Mixin do
3204
1901
  expect(scim_hash.dig('complex', 2, 'data', 'nested', 0, 'info', 0, 'deep')).to eql('nature2deep3') # Unchanged
3205
1902
  end
3206
1903
 
3207
- it 'removes via clearing to "nil" or empty Array across multiple deep matching points' do
1904
+ it 'removes across multiple deep matching points' do
3208
1905
  scim_hash = @original_hash.deep_dup().with_indifferent_case_insensitive_access()
3209
1906
  contrived_instance = @contrived_class.new
3210
1907
  contrived_instance.send(
3211
1908
  :from_patch_backend!,
3212
1909
  nature: 'remove',
3213
1910
  path: ['complex[type eq "type1"]', 'data', 'nested[nature eq "nature2"]', 'info'],
3214
- value: nil,
3215
- altering_hash: scim_hash,
3216
- with_attr_map: @contrived_class.scim_attributes_map()
1911
+ value: [{ 'deeper' => 'addition' }],
1912
+ altering_hash: scim_hash
3217
1913
  )
3218
1914
 
3219
1915
  expect(scim_hash.dig('complex', 0, 'data', 'nested', 0, 'info').count).to eql(1) # Unchanged
3220
- expect(scim_hash.dig('complex', 0, 'data', 'nested', 1, 'info')).to eql([])
1916
+ expect(scim_hash.dig('complex', 0, 'data', 'nested', 1, 'info')).to be_nil
3221
1917
  expect(scim_hash.dig('complex', 0, 'data', 'nested', 2, 'nature')).to be_present
3222
- expect(scim_hash.dig('complex', 0, 'data', 'nested', 1, 'info')).to eql([])
1918
+ expect(scim_hash.dig('complex', 0, 'data', 'nested', 1, 'info')).to be_nil
3223
1919
  expect(scim_hash.dig('complex', 0, 'data', 'nested', 2, 'nature')).to be_present
3224
1920
 
3225
- expect(scim_hash.dig('complex', 1, 'data', 'nested', 0, 'info')).to eql([])
1921
+ expect(scim_hash.dig('complex', 1, 'data', 'nested', 0, 'info')).to be_nil
3226
1922
  expect(scim_hash.dig('complex', 1, 'data', 'nested', 0, 'nature')).to be_present
3227
1923
 
3228
1924
  expect(scim_hash.dig('complex', 2, 'data', 'nested', 0, 'info').count).to eql(1) # Unchanged
@@ -3256,13 +1952,7 @@ RSpec.describe Scimitar::Resources::Mixin do
3256
1952
  nature: 'replace',
3257
1953
  path: path,
3258
1954
  value: 'ignored',
3259
- altering_hash: scim_hash,
3260
- with_attr_map: {
3261
- emails: [
3262
- { match: 'type', with: 'home', using: { value: :home_email, primary: true } },
3263
- { match: 'type', with: 'work', using: { value: :work_email } },
3264
- ]
3265
- }
1955
+ altering_hash: scim_hash
3266
1956
  )
3267
1957
  end.to raise_error(Scimitar::ErrorResponse) { |e| expect(e.as_json['scimType']).to eql('invalidSyntax') }
3268
1958
  end
@@ -3279,8 +1969,7 @@ RSpec.describe Scimitar::Resources::Mixin do
3279
1969
  nature: 'replace',
3280
1970
  path: path,
3281
1971
  value: 'ignored',
3282
- altering_hash: scim_hash,
3283
- with_attr_map: { userName: :user_name }
1972
+ altering_hash: scim_hash
3284
1973
  )
3285
1974
  end.to raise_error(Scimitar::ErrorResponse) { |e| expect(e.as_json['scimType']).to eql('invalidSyntax') }
3286
1975
  end
@@ -3300,13 +1989,7 @@ RSpec.describe Scimitar::Resources::Mixin do
3300
1989
  nature: 'replace',
3301
1990
  path: path,
3302
1991
  value: 'ignored',
3303
- altering_hash: scim_hash,
3304
- with_attr_map: {
3305
- emails: [
3306
- { match: 'type', with: 'home', using: { value: :home_email, primary: true } },
3307
- { match: 'type', with: 'work', using: { value: :work_email } },
3308
- ]
3309
- }
1992
+ altering_hash: scim_hash
3310
1993
  )
3311
1994
  end.to raise_error(Scimitar::ErrorResponse) { |e| expect(e.as_json['scimType']).to eql('invalidSyntax') }
3312
1995
  end
@@ -3320,14 +2003,6 @@ RSpec.describe Scimitar::Resources::Mixin do
3320
2003
  #
3321
2004
  context 'public interface' do
3322
2005
  shared_examples 'a patcher' do | force_upper_case: |
3323
- it 'gives the user a comprehensible error when operations are missing' do
3324
- patch = { 'schemas' => ['urn:ietf:params:scim:api:messages:2.0:PatchOp'] }
3325
-
3326
- expect do
3327
- @instance.from_scim_patch!(patch_hash: patch)
3328
- end.to raise_error Scimitar::InvalidSyntaxError, "Missing PATCH \"operations\""
3329
- end
3330
-
3331
2006
  it 'which updates simple values' do
3332
2007
  @instance.update!(username: 'foo')
3333
2008
 
@@ -3349,28 +2024,6 @@ RSpec.describe Scimitar::Resources::Mixin do
3349
2024
  expect(@instance.username).to eql('1234')
3350
2025
  end
3351
2026
 
3352
- it 'which updates nested values using root syntax' do
3353
- @instance.update!(first_name: 'Foo', last_name: 'Bar')
3354
-
3355
- path = 'name.givenName'
3356
- path = path.upcase if force_upper_case
3357
-
3358
- patch = {
3359
- 'schemas' => ['urn:ietf:params:scim:api:messages:2.0:PatchOp'],
3360
- 'Operations' => [
3361
- {
3362
- 'op' => 'replace',
3363
- 'value' => {
3364
- path => 'Baz'
3365
- }
3366
- }
3367
- ]
3368
- }
3369
-
3370
- @instance.from_scim_patch!(patch_hash: patch)
3371
- expect(@instance.first_name).to eql('Baz')
3372
- end
3373
-
3374
2027
  it 'which updates nested values' do
3375
2028
  @instance.update!(first_name: 'Foo', last_name: 'Bar')
3376
2029
 
@@ -3392,38 +2045,6 @@ RSpec.describe Scimitar::Resources::Mixin do
3392
2045
  expect(@instance.first_name).to eql('Baz')
3393
2046
  end
3394
2047
 
3395
- # Note odd ":" separating schema ID from first attribute, although
3396
- # the nature of JSON rendering / other payloads might lead you to
3397
- # expect a "." as with any other path component.
3398
- #
3399
- # Note the ":" separating the schema ID (URN) from the attribute.
3400
- # The nature of JSON rendering / other payloads might lead you to
3401
- # expect a "." as with any complex types, but that's not the case;
3402
- # see https://tools.ietf.org/html/rfc7644#section-3.10, or
3403
- # https://tools.ietf.org/html/rfc7644#section-3.5.2 of which in
3404
- # particular, https://tools.ietf.org/html/rfc7644#page-35.
3405
- #
3406
- it 'which updates attributes defined by extension schema' do
3407
- @instance.update!(department: 'SOMEDPT')
3408
-
3409
- path = 'urn:ietf:params:scim:schemas:extension:enterprise:2.0:User:department'
3410
- path = path.upcase if force_upper_case
3411
-
3412
- patch = {
3413
- 'schemas' => ['urn:ietf:params:scim:api:messages:2.0:PatchOp'],
3414
- 'Operations' => [
3415
- {
3416
- 'op' => 'replace',
3417
- 'path' => path,
3418
- 'value' => 'OTHERDPT'
3419
- }
3420
- ]
3421
- }
3422
-
3423
- @instance.from_scim_patch!(patch_hash: patch)
3424
- expect(@instance.department).to eql('OTHERDPT')
3425
- end
3426
-
3427
2048
  it 'which updates with filter match' do
3428
2049
  @instance.update!(work_email_address: 'work@test.com', home_email_address: 'home@test.com')
3429
2050