scimitar 1.10.0 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
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