scimitar 1.5.2 → 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 (39) hide show
  1. checksums.yaml +4 -4
  2. data/app/controllers/scimitar/active_record_backed_resources_controller.rb +6 -27
  3. data/app/controllers/scimitar/application_controller.rb +9 -29
  4. data/app/models/scimitar/engine_configuration.rb +3 -7
  5. data/app/models/scimitar/error_response.rb +0 -12
  6. data/app/models/scimitar/errors.rb +1 -1
  7. data/app/models/scimitar/lists/query_parser.rb +4 -14
  8. data/app/models/scimitar/resources/base.rb +1 -1
  9. data/app/models/scimitar/resources/mixin.rb +4 -113
  10. data/app/models/scimitar/schema/address.rb +0 -1
  11. data/app/models/scimitar/schema/attribute.rb +1 -1
  12. data/app/models/scimitar/schema/base.rb +3 -1
  13. data/app/models/scimitar/schema/vdtp.rb +1 -1
  14. data/config/initializers/scimitar.rb +70 -86
  15. data/lib/scimitar/version.rb +2 -2
  16. data/spec/apps/dummy/app/controllers/mock_groups_controller.rb +1 -1
  17. data/spec/apps/dummy/app/models/mock_group.rb +1 -1
  18. data/spec/apps/dummy/app/models/mock_user.rb +8 -19
  19. data/spec/apps/dummy/config/application.rb +1 -0
  20. data/spec/apps/dummy/config/environments/test.rb +28 -5
  21. data/spec/apps/dummy/config/initializers/scimitar.rb +9 -44
  22. data/spec/apps/dummy/config/routes.rb +0 -4
  23. data/spec/apps/dummy/db/migrate/20210304014602_create_mock_users.rb +1 -9
  24. data/spec/apps/dummy/db/migrate/20210308044214_create_join_table_mock_groups_mock_users.rb +3 -8
  25. data/spec/apps/dummy/db/schema.rb +4 -10
  26. data/spec/controllers/scimitar/application_controller_spec.rb +1 -70
  27. data/spec/controllers/scimitar/schemas_controller_spec.rb +2 -2
  28. data/spec/models/scimitar/complex_types/email_spec.rb +2 -0
  29. data/spec/models/scimitar/lists/query_parser_spec.rb +9 -9
  30. data/spec/models/scimitar/resources/base_spec.rb +66 -161
  31. data/spec/models/scimitar/resources/base_validation_spec.rb +2 -27
  32. data/spec/models/scimitar/resources/mixin_spec.rb +43 -757
  33. data/spec/models/scimitar/resources/user_spec.rb +4 -4
  34. data/spec/models/scimitar/schema/attribute_spec.rb +3 -0
  35. data/spec/models/scimitar/schema/base_spec.rb +1 -1
  36. data/spec/models/scimitar/schema/user_spec.rb +0 -10
  37. data/spec/requests/active_record_backed_resources_controller_spec.rb +40 -309
  38. data/spec/requests/application_controller_spec.rb +3 -17
  39. metadata +7 -7
@@ -159,73 +159,41 @@ RSpec.describe Scimitar::Resources::Mixin do
159
159
  # =========================================================================
160
160
 
161
161
  context '#to_scim' do
162
- context 'with a UUID, renamed primary key column' do
163
- it 'compiles instance attribute values into a SCIM representation' do
164
- uuid = SecureRandom.uuid
165
-
166
- instance = MockUser.new
167
- instance.primary_key = uuid
168
- instance.scim_uid = 'AA02984'
169
- instance.username = 'foo'
170
- instance.first_name = 'Foo'
171
- instance.last_name = 'Bar'
172
- instance.work_email_address = 'foo.bar@test.com'
173
- instance.home_email_address = nil
174
- instance.work_phone_number = '+642201234567'
175
- instance.organization = 'SOMEORG'
176
-
177
- g1 = MockGroup.create!(display_name: 'Group 1')
178
- g2 = MockGroup.create!(display_name: 'Group 2')
179
- g3 = MockGroup.create!(display_name: 'Group 3')
180
-
181
- g1.mock_users << instance
182
- g3.mock_users << instance
183
-
184
- scim = instance.to_scim(location: "https://test.com/mock_users/#{uuid}")
185
- json = scim.to_json()
186
- hash = JSON.parse(json)
187
-
188
- expect(hash).to eql({
189
- 'userName' => 'foo',
190
- 'name' => {'givenName'=>'Foo', 'familyName'=>'Bar'},
191
- 'active' => true,
192
- 'emails' => [{'type'=>'work', 'primary'=>true, 'value'=>'foo.bar@test.com'}, {"primary"=>false, "type"=>"home", "value"=>nil}],
193
- 'phoneNumbers'=> [{'type'=>'work', 'primary'=>false, 'value'=>'+642201234567'}],
194
- 'id' => uuid,
195
- 'externalId' => 'AA02984',
196
- 'groups' => [{'display'=>g1.display_name, 'value'=>g1.id.to_s}, {'display'=>g3.display_name, 'value'=>g3.id.to_s}],
197
- 'meta' => {'location'=>"https://test.com/mock_users/#{uuid}", 'resourceType'=>'User'},
198
- 'schemas' => ['urn:ietf:params:scim:schemas:core:2.0:User', 'urn:ietf:params:scim:schemas:extension:enterprise:2.0:User'],
199
-
200
- 'urn:ietf:params:scim:schemas:extension:enterprise:2.0:User' => {
201
- 'organization' => 'SOMEORG',
202
- 'department' => nil
203
- }
204
- })
205
- end
206
- end # "context 'with a UUID, renamed primary key column' do"
207
-
208
- context 'with an integer, conventionally named primary key column' do
209
- it 'compiles instance attribute values into a SCIM representation' do
210
- instance = MockGroup.new
211
- instance.id = 42
212
- instance.scim_uid = 'GG02984'
213
- instance.display_name = 'Some group'
214
-
215
- scim = instance.to_scim(location: 'https://test.com/mock_groups/42')
216
- json = scim.to_json()
217
- hash = JSON.parse(json)
218
-
219
- expect(hash).to eql({
220
- 'displayName' => 'Some group',
221
- 'id' => '42', # Note, String
222
- 'externalId' => 'GG02984',
223
- 'members' => [],
224
- 'meta' => {'location'=>'https://test.com/mock_groups/42', 'resourceType'=>'Group'},
225
- 'schemas' => ['urn:ietf:params:scim:schemas:core:2.0:Group']
226
- })
227
- end
228
- 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
229
197
 
230
198
  context 'with optional timestamps' do
231
199
  context 'creation only' do
@@ -324,9 +292,7 @@ RSpec.describe Scimitar::Resources::Mixin do
324
292
  ],
325
293
 
326
294
  'meta' => {'location'=>'https://test.com/static_map_test', 'resourceType'=>'User'},
327
- 'schemas' => ['urn:ietf:params:scim:schemas:core:2.0:User', 'urn:ietf:params:scim:schemas:extension:enterprise:2.0:User'],
328
-
329
- 'urn:ietf:params:scim:schemas:extension:enterprise:2.0:User' => {}
295
+ 'schemas' => ['urn:ietf:params:scim:schemas:core:2.0:User']
330
296
  })
331
297
  end
332
298
  end # "context 'using static mappings' do"
@@ -353,9 +319,7 @@ RSpec.describe Scimitar::Resources::Mixin do
353
319
  ],
354
320
 
355
321
  'meta' => {'location'=>'https://test.com/dynamic_map_test', 'resourceType'=>'User'},
356
- 'schemas' => ['urn:ietf:params:scim:schemas:core:2.0:User', 'urn:ietf:params:scim:schemas:extension:enterprise:2.0:User'],
357
-
358
- 'urn:ietf:params:scim:schemas:extension:enterprise:2.0:User' => {}
322
+ 'schemas' => ['urn:ietf:params:scim:schemas:core:2.0:User']
359
323
  })
360
324
  end
361
325
  end # "context 'using dynamic lists' do"
@@ -412,12 +376,7 @@ RSpec.describe Scimitar::Resources::Mixin do
412
376
  'id' => '42', # Note, String
413
377
  'externalId' => 'AA02984',
414
378
  'meta' => {'location' => 'https://test.com/mock_users/42', 'resourceType' => 'User'},
415
- 'schemas' => ['urn:ietf:params:scim:schemas:core:2.0:User', 'urn:ietf:params:scim:schemas:extension:enterprise:2.0:User'],
416
-
417
- 'urn:ietf:params:scim:schemas:extension:enterprise:2.0:User' => {
418
- 'organization' => 'SOMEORG',
419
- 'DEPARTMENT' => 'SOMEDPT'
420
- }
379
+ 'schemas' => ['urn:ietf:params:scim:schemas:core:2.0:User']
421
380
  }
422
381
 
423
382
  hash = spec_helper_hupcase(hash) if force_upper_case
@@ -433,8 +392,6 @@ RSpec.describe Scimitar::Resources::Mixin do
433
392
  expect(instance.work_email_address).to eql('foo.bar@test.com')
434
393
  expect(instance.home_email_address).to be_nil
435
394
  expect(instance.work_phone_number ).to eql('+642201234567')
436
- expect(instance.organization ).to eql('SOMEORG')
437
- expect(instance.department ).to eql('SOMEDPT')
438
395
  end
439
396
 
440
397
  it 'honouring read-write lists' do
@@ -448,8 +405,8 @@ RSpec.describe Scimitar::Resources::Mixin do
448
405
  'displayName' => 'Foo Group',
449
406
  'members' => [
450
407
  {'type' => 'Group', 'value' => g1.id.to_s},
451
- {'type' => 'User', 'value' => u1.primary_key.to_s},
452
- {'type' => 'User', 'value' => u3.primary_key.to_s}
408
+ {'type' => 'User', 'value' => u1.id.to_s},
409
+ {'type' => 'User', 'value' => u3.id.to_s}
453
410
  ],
454
411
  'externalId' => 'GG01536',
455
412
  'meta' => {'location'=>'https://test.com/mock_groups/1', 'resourceType'=>'Group'},
@@ -496,10 +453,8 @@ RSpec.describe Scimitar::Resources::Mixin do
496
453
  end # "context 'using upper case' do"
497
454
 
498
455
  it 'clears things not present in input' do
499
- uuid = SecureRandom.uuid
500
-
501
456
  instance = MockUser.new
502
- instance.primary_key = uuid
457
+ instance.id = 42
503
458
  instance.scim_uid = 'AA02984'
504
459
  instance.username = 'foo'
505
460
  instance.first_name = 'Foo'
@@ -510,7 +465,7 @@ RSpec.describe Scimitar::Resources::Mixin do
510
465
 
511
466
  instance.from_scim!(scim_hash: {})
512
467
 
513
- expect(instance.primary_key ).to eql(uuid)
468
+ expect(instance.id ).to eql(42)
514
469
  expect(instance.scim_uid ).to be_nil
515
470
  expect(instance.username ).to be_nil
516
471
  expect(instance.first_name ).to be_nil
@@ -721,21 +676,6 @@ RSpec.describe Scimitar::Resources::Mixin do
721
676
  expect(scim_hash['name']['familyName']).to eql('Bar')
722
677
  end
723
678
 
724
- it 'with schema extensions: overwrites' do
725
- path = [ 'urn:ietf:params:scim:schemas:extension:enterprise:2.0:User', 'organization' ]
726
- scim_hash = { 'urn:ietf:params:scim:schemas:extension:enterprise:2.0:User' => { 'organization' => 'SOMEORG' } }.with_indifferent_case_insensitive_access()
727
-
728
- @instance.send(
729
- :from_patch_backend!,
730
- nature: 'add',
731
- path: path,
732
- value: 'OTHERORG',
733
- altering_hash: scim_hash
734
- )
735
-
736
- expect(scim_hash['urn:ietf:params:scim:schemas:extension:enterprise:2.0:User']['organization' ]).to eql('OTHERORG')
737
- end
738
-
739
679
  # For 'add', filter at end-of-path is nonsensical and not
740
680
  # supported by spec or Scimitar; we only test mid-path filters.
741
681
  #
@@ -924,21 +864,6 @@ RSpec.describe Scimitar::Resources::Mixin do
924
864
  expect(scim_hash['name']['givenName']).to eql('Baz')
925
865
  end
926
866
 
927
- it 'with schema extensions: adds' do
928
- path = [ 'urn:ietf:params:scim:schemas:extension:enterprise:2.0:User', 'organization' ]
929
- scim_hash = {}.with_indifferent_case_insensitive_access()
930
-
931
- @instance.send(
932
- :from_patch_backend!,
933
- nature: 'add',
934
- path: path,
935
- value: 'SOMEORG',
936
- altering_hash: scim_hash
937
- )
938
-
939
- expect(scim_hash['urn:ietf:params:scim:schemas:extension:enterprise:2.0:User']['organization' ]).to eql('SOMEORG')
940
- end
941
-
942
867
  context 'with filter mid-path: adds' do
943
868
  it 'by string match' do
944
869
  path = [ 'emails[type eq "work"]', 'value' ]
@@ -1277,595 +1202,6 @@ RSpec.describe Scimitar::Resources::Mixin do
1277
1202
 
1278
1203
  expect(scim_hash).to_not have_key('emails')
1279
1204
  end
1280
-
1281
- # What we expect:
1282
- #
1283
- # https://tools.ietf.org/html/rfc7644#section-3.5.2.2
1284
- # https://docs.snowflake.com/en/user-guide/scim-intro.html#patch-scim-v2-groups-id
1285
- #
1286
- # ...vs accounting for the unusual payloads we sometimes get,
1287
- # tested here.
1288
- #
1289
- context 'special cases' do
1290
-
1291
- # https://learn.microsoft.com/en-us/azure/active-directory/app-provisioning/use-scim-to-provision-users-and-groups#update-group-remove-members
1292
- #
1293
- context 'Microsoft-style payload' do
1294
- context 'removing a user from a group' do
1295
- it 'removes identified user' do
1296
- path = [ 'members' ]
1297
- value = [ { '$ref' => nil, 'value' => 'f648f8d5ea4e4cd38e9c' } ]
1298
- scim_hash = {
1299
- 'displayname' => 'Mock group',
1300
- 'members' => [
1301
- {
1302
- 'value' => '50ca93d04ab0c2de4772',
1303
- 'display' => 'Ingrid Smith',
1304
- 'type' => 'User'
1305
- },
1306
- {
1307
- 'value' => 'f648f8d5ea4e4cd38e9c',
1308
- 'display' => 'Fred Smith',
1309
- 'type' => 'User'
1310
- },
1311
- {
1312
- 'value' => 'a774d480e8112101375b',
1313
- 'display' => 'Taylor Smith',
1314
- 'type' => 'User'
1315
- }
1316
- ]
1317
- }.with_indifferent_case_insensitive_access()
1318
-
1319
- @instance.send(
1320
- :from_patch_backend!,
1321
- nature: 'remove',
1322
- path: path,
1323
- value: value,
1324
- altering_hash: scim_hash
1325
- )
1326
-
1327
- expect(scim_hash).to eql({
1328
- 'displayname' => 'Mock group',
1329
- 'members' => [
1330
- {
1331
- 'value' => '50ca93d04ab0c2de4772',
1332
- 'display' => 'Ingrid Smith',
1333
- 'type' => 'User'
1334
- },
1335
- {
1336
- 'value' => 'a774d480e8112101375b',
1337
- 'display' => 'Taylor Smith',
1338
- 'type' => 'User'
1339
- }
1340
- ]
1341
- })
1342
- end
1343
-
1344
- it 'removes multiple identified users' do
1345
- path = [ 'members' ]
1346
- value = [
1347
- { '$ref' => nil, 'value' => 'f648f8d5ea4e4cd38e9c' },
1348
- { '$ref' => nil, 'value' => '50ca93d04ab0c2de4772' }
1349
- ]
1350
- scim_hash = {
1351
- 'displayname' => 'Mock group',
1352
- 'members' => [
1353
- {
1354
- 'value' => '50ca93d04ab0c2de4772',
1355
- 'display' => 'Ingrid Smith',
1356
- 'type' => 'User'
1357
- },
1358
- {
1359
- 'value' => 'f648f8d5ea4e4cd38e9c',
1360
- 'display' => 'Fred Smith',
1361
- 'type' => 'User'
1362
- },
1363
- {
1364
- 'value' => 'a774d480e8112101375b',
1365
- 'display' => 'Taylor Smith',
1366
- 'type' => 'User'
1367
- }
1368
- ]
1369
- }.with_indifferent_case_insensitive_access()
1370
-
1371
- @instance.send(
1372
- :from_patch_backend!,
1373
- nature: 'remove',
1374
- path: path,
1375
- value: value,
1376
- altering_hash: scim_hash
1377
- )
1378
-
1379
- expect(scim_hash).to eql({
1380
- 'displayname' => 'Mock group',
1381
- 'members' => [
1382
- {
1383
- 'value' => 'a774d480e8112101375b',
1384
- 'display' => 'Taylor Smith',
1385
- 'type' => 'User'
1386
- }
1387
- ]
1388
- })
1389
- end
1390
-
1391
- it 'removes all users individually without error' do
1392
- path = [ 'members' ]
1393
- value = [ { '$ref' => nil, 'value' => 'f648f8d5ea4e4cd38e9c' } ]
1394
- scim_hash = {
1395
- 'displayname' => 'Mock group',
1396
- 'members' => [
1397
- {
1398
- 'value' => 'f648f8d5ea4e4cd38e9c',
1399
- 'display' => 'Fred Smith',
1400
- 'type' => 'User'
1401
- }
1402
- ]
1403
- }.with_indifferent_case_insensitive_access()
1404
-
1405
- @instance.send(
1406
- :from_patch_backend!,
1407
- nature: 'remove',
1408
- path: path,
1409
- value: value,
1410
- altering_hash: scim_hash
1411
- )
1412
-
1413
- expect(scim_hash).to eql({
1414
- 'displayname' => 'Mock group',
1415
- 'members' => []
1416
- })
1417
- end
1418
-
1419
- it 'can match on multiple attributes' do
1420
- path = [ 'members' ]
1421
- value = [ { '$ref' => nil, 'value' => 'f648f8d5ea4e4cd38e9c', 'type' => 'User' } ]
1422
- scim_hash = {
1423
- 'displayname' => 'Mock group',
1424
- 'members' => [
1425
- {
1426
- 'value' => 'f648f8d5ea4e4cd38e9c',
1427
- 'display' => 'Fred Smith',
1428
- 'type' => 'User'
1429
- }
1430
- ]
1431
- }.with_indifferent_case_insensitive_access()
1432
-
1433
- @instance.send(
1434
- :from_patch_backend!,
1435
- nature: 'remove',
1436
- path: path,
1437
- value: value,
1438
- altering_hash: scim_hash
1439
- )
1440
-
1441
- expect(scim_hash).to eql({
1442
- 'displayname' => 'Mock group',
1443
- 'members' => []
1444
- })
1445
- end
1446
-
1447
- it 'ignores unrecognised users' do
1448
- path = [ 'members' ]
1449
- value = [ { '$ref' => nil, 'value' => '11b054a9c85216ed9356' } ]
1450
- scim_hash = {
1451
- 'displayname' => 'Mock group',
1452
- 'members' => [
1453
- {
1454
- 'value' => 'f648f8d5ea4e4cd38e9c',
1455
- 'display' => 'Fred Smith',
1456
- 'type' => 'User'
1457
- }
1458
- ]
1459
- }.with_indifferent_case_insensitive_access()
1460
-
1461
- @instance.send(
1462
- :from_patch_backend!,
1463
- nature: 'remove',
1464
- path: path,
1465
- value: value,
1466
- altering_hash: scim_hash
1467
- )
1468
-
1469
- # The 'value' mismatched, so the user was not removed.
1470
- #
1471
- expect(scim_hash).to eql({
1472
- 'displayname' => 'Mock group',
1473
- 'members' => [
1474
- {
1475
- 'value' => 'f648f8d5ea4e4cd38e9c',
1476
- 'display' => 'Fred Smith',
1477
- 'type' => 'User'
1478
- }
1479
- ]
1480
- })
1481
- end
1482
-
1483
- it 'ignores a mismatch on (for example) "type"' do
1484
- path = [ 'members' ]
1485
- value = [ { '$ref' => nil, 'value' => 'f648f8d5ea4e4cd38e9c', 'type' => 'Group' } ]
1486
- scim_hash = {
1487
- 'displayname' => 'Mock group',
1488
- 'members' => [
1489
- {
1490
- 'value' => 'f648f8d5ea4e4cd38e9c',
1491
- 'display' => 'Fred Smith',
1492
- 'type' => 'User'
1493
- }
1494
- ]
1495
- }.with_indifferent_case_insensitive_access()
1496
-
1497
- @instance.send(
1498
- :from_patch_backend!,
1499
- nature: 'remove',
1500
- path: path,
1501
- value: value,
1502
- altering_hash: scim_hash
1503
- )
1504
-
1505
- # Type 'Group' mismatches 'User', so the user was not
1506
- # removed.
1507
- #
1508
- expect(scim_hash).to eql({
1509
- 'displayname' => 'Mock group',
1510
- 'members' => [
1511
- {
1512
- 'value' => 'f648f8d5ea4e4cd38e9c',
1513
- 'display' => 'Fred Smith',
1514
- 'type' => 'User'
1515
- }
1516
- ]
1517
- })
1518
- end
1519
-
1520
- it 'matches keys case-insensitive' do
1521
- path = [ 'members' ]
1522
- value = [ { '$ref' => nil, 'VALUe' => 'f648f8d5ea4e4cd38e9c' } ]
1523
- scim_hash = {
1524
- 'displayname' => 'Mock group',
1525
- 'memBERS' => [
1526
- {
1527
- 'vaLUe' => 'f648f8d5ea4e4cd38e9c',
1528
- 'display' => 'Fred Smith',
1529
- 'type' => 'User'
1530
- }
1531
- ]
1532
- }.with_indifferent_case_insensitive_access()
1533
-
1534
- @instance.send(
1535
- :from_patch_backend!,
1536
- nature: 'remove',
1537
- path: path,
1538
- value: value,
1539
- altering_hash: scim_hash
1540
- )
1541
-
1542
- expect(scim_hash).to eql({
1543
- 'displayname' => 'Mock group',
1544
- 'members' => []
1545
- })
1546
- end
1547
-
1548
- it 'matches values case-sensitive' do
1549
- path = [ 'members' ]
1550
- value = [ { '$ref' => nil, 'value' => 'f648f8d5ea4e4cd38e9c', 'type' => 'USER' } ]
1551
- scim_hash = {
1552
- 'displayname' => 'Mock group',
1553
- 'members' => [
1554
- {
1555
- 'value' => 'f648f8d5ea4e4cd38e9c',
1556
- 'display' => 'Fred Smith',
1557
- 'type' => 'User'
1558
- }
1559
- ]
1560
- }.with_indifferent_case_insensitive_access()
1561
-
1562
- @instance.send(
1563
- :from_patch_backend!,
1564
- nature: 'remove',
1565
- path: path,
1566
- value: value,
1567
- altering_hash: scim_hash
1568
- )
1569
-
1570
- # USER mismatchs User, so the user was not removed.
1571
- #
1572
- expect(scim_hash).to eql({
1573
- 'displayname' => 'Mock group',
1574
- 'members' => [
1575
- {
1576
- 'value' => 'f648f8d5ea4e4cd38e9c',
1577
- 'display' => 'Fred Smith',
1578
- 'type' => 'User'
1579
- }
1580
- ]
1581
- })
1582
- end
1583
- end # "context 'removing a user from a group' do"
1584
-
1585
- context 'generic use' do
1586
- it 'removes matched items' do
1587
- path = [ 'emails' ]
1588
- value = [ { 'type' => 'work' } ]
1589
- scim_hash = {
1590
- 'emails' => [
1591
- {
1592
- 'type' => 'home',
1593
- 'value' => 'home@test.com'
1594
- },
1595
- {
1596
- 'type' => 'work',
1597
- 'value' => 'work@test.com'
1598
- }
1599
- ]
1600
- }.with_indifferent_case_insensitive_access()
1601
-
1602
- @instance.send(
1603
- :from_patch_backend!,
1604
- nature: 'remove',
1605
- path: path,
1606
- value: value,
1607
- altering_hash: scim_hash
1608
- )
1609
-
1610
- expect(scim_hash).to eql({
1611
- 'emails' => [
1612
- {
1613
- 'type' => 'home',
1614
- 'value' => 'home@test.com'
1615
- }
1616
- ]
1617
- })
1618
- end
1619
-
1620
- it 'ignores unmatched items' do
1621
- path = [ 'emails' ]
1622
- value = [ { 'type' => 'missing' } ]
1623
- scim_hash = {
1624
- 'emails' => [
1625
- {
1626
- 'type' => 'home',
1627
- 'value' => 'home@test.com'
1628
- },
1629
- {
1630
- 'type' => 'work',
1631
- 'value' => 'work@test.com'
1632
- }
1633
- ]
1634
- }.with_indifferent_case_insensitive_access()
1635
-
1636
- @instance.send(
1637
- :from_patch_backend!,
1638
- nature: 'remove',
1639
- path: path,
1640
- value: value,
1641
- altering_hash: scim_hash
1642
- )
1643
-
1644
- expect(scim_hash).to eql({
1645
- 'emails' => [
1646
- {
1647
- 'type' => 'home',
1648
- 'value' => 'home@test.com'
1649
- },
1650
- {
1651
- 'type' => 'work',
1652
- 'value' => 'work@test.com'
1653
- }
1654
- ]
1655
- })
1656
- end
1657
-
1658
- it 'compares string forms' do
1659
- path = [ 'test' ]
1660
- value = [
1661
- { 'active' => true, 'value' => '12' },
1662
- { 'active' => 'false', 'value' => 42 }
1663
- ]
1664
- scim_hash = {
1665
- 'test' => [
1666
- {
1667
- 'active' => 'true',
1668
- 'value' => 12
1669
- },
1670
- {
1671
- 'active' => false,
1672
- 'value' => '42'
1673
- }
1674
- ]
1675
- }.with_indifferent_case_insensitive_access()
1676
-
1677
- @instance.send(
1678
- :from_patch_backend!,
1679
- nature: 'remove',
1680
- path: path,
1681
- value: value,
1682
- altering_hash: scim_hash
1683
- )
1684
-
1685
- expect(scim_hash).to eql({'test' => []})
1686
- end
1687
-
1688
- it 'handles a singular to-remove value rather than an array' do
1689
- path = [ 'emails' ]
1690
- value = { 'type' => 'work' }
1691
- scim_hash = {
1692
- 'emails' => [
1693
- {
1694
- 'type' => 'home',
1695
- 'value' => 'home@test.com'
1696
- },
1697
- {
1698
- 'type' => 'work',
1699
- 'value' => 'work@test.com'
1700
- }
1701
- ]
1702
- }.with_indifferent_case_insensitive_access()
1703
-
1704
- @instance.send(
1705
- :from_patch_backend!,
1706
- nature: 'remove',
1707
- path: path,
1708
- value: value,
1709
- altering_hash: scim_hash
1710
- )
1711
-
1712
- expect(scim_hash).to eql({
1713
- 'emails' => [
1714
- {
1715
- 'type' => 'home',
1716
- 'value' => 'home@test.com'
1717
- }
1718
- ]
1719
- })
1720
- end
1721
-
1722
- it 'handles simple values rather than object (Hash) values' do
1723
- path = [ 'test' ]
1724
- value = 42
1725
- scim_hash = {
1726
- 'test' => [
1727
- '21',
1728
- '42',
1729
- '15'
1730
- ]
1731
- }.with_indifferent_case_insensitive_access()
1732
-
1733
- @instance.send(
1734
- :from_patch_backend!,
1735
- nature: 'remove',
1736
- path: path,
1737
- value: value,
1738
- altering_hash: scim_hash
1739
- )
1740
-
1741
- expect(scim_hash).to eql({
1742
- 'test' => [
1743
- '21',
1744
- '15'
1745
- ]
1746
- })
1747
- end
1748
- end
1749
- end # "context 'Microsoft-style payload' do"
1750
-
1751
- # https://help.salesforce.com/s/articleView?id=sf.identity_scim_manage_groups.htm&type=5
1752
- #
1753
- context 'Salesforce-style payload' do
1754
- it 'removes identified user' do
1755
- path = [ 'members' ]
1756
- value = { 'members' => [ { '$ref' => nil, 'value' => 'f648f8d5ea4e4cd38e9c' } ] }
1757
- scim_hash = {
1758
- 'displayname' => 'Mock group',
1759
- 'members' => [
1760
- {
1761
- 'value' => '50ca93d04ab0c2de4772',
1762
- 'display' => 'Ingrid Smith',
1763
- 'type' => 'User'
1764
- },
1765
- {
1766
- 'value' => 'f648f8d5ea4e4cd38e9c',
1767
- 'display' => 'Fred Smith',
1768
- 'type' => 'User'
1769
- }
1770
- ]
1771
- }.with_indifferent_case_insensitive_access()
1772
-
1773
- @instance.send(
1774
- :from_patch_backend!,
1775
- nature: 'remove',
1776
- path: path,
1777
- value: value,
1778
- altering_hash: scim_hash
1779
- )
1780
-
1781
- expect(scim_hash).to eql({
1782
- 'displayname' => 'Mock group',
1783
- 'members' => [
1784
- {
1785
- 'value' => '50ca93d04ab0c2de4772',
1786
- 'display' => 'Ingrid Smith',
1787
- 'type' => 'User'
1788
- }
1789
- ]
1790
- })
1791
- end
1792
-
1793
- it 'matches the "members" key case-insensitive' do
1794
- path = [ 'members' ]
1795
- value = { 'MEMBERS' => [ { '$ref' => nil, 'value' => 'f648f8d5ea4e4cd38e9c' } ] }
1796
- scim_hash = {
1797
- 'displayname' => 'Mock group',
1798
- 'members' => [
1799
- {
1800
- 'value' => 'f648f8d5ea4e4cd38e9c',
1801
- 'display' => 'Fred Smith',
1802
- 'type' => 'User'
1803
- },
1804
- {
1805
- 'value' => 'a774d480e8112101375b',
1806
- 'display' => 'Taylor Smith',
1807
- 'type' => 'User'
1808
- }
1809
- ]
1810
- }.with_indifferent_case_insensitive_access()
1811
-
1812
- @instance.send(
1813
- :from_patch_backend!,
1814
- nature: 'remove',
1815
- path: path,
1816
- value: value,
1817
- altering_hash: scim_hash
1818
- )
1819
-
1820
- expect(scim_hash).to eql({
1821
- 'displayname' => 'Mock group',
1822
- 'members' => [
1823
- {
1824
- 'value' => 'a774d480e8112101375b',
1825
- 'display' => 'Taylor Smith',
1826
- 'type' => 'User'
1827
- }
1828
- ]
1829
- })
1830
- end
1831
-
1832
- it 'ignores unrecognised users' do
1833
- path = [ 'members' ]
1834
- value = { 'members' => [ { '$ref' => nil, 'value' => '11b054a9c85216ed9356' } ] }
1835
- scim_hash = {
1836
- 'displayname' => 'Mock group',
1837
- 'members' => [
1838
- {
1839
- 'value' => 'f648f8d5ea4e4cd38e9c',
1840
- 'display' => 'Fred Smith',
1841
- 'type' => 'User'
1842
- }
1843
- ]
1844
- }.with_indifferent_case_insensitive_access()
1845
-
1846
- @instance.send(
1847
- :from_patch_backend!,
1848
- nature: 'remove',
1849
- path: path,
1850
- value: value,
1851
- altering_hash: scim_hash
1852
- )
1853
-
1854
- # The 'value' mismatched, so the user was not removed.
1855
- #
1856
- expect(scim_hash).to eql({
1857
- 'displayname' => 'Mock group',
1858
- 'members' => [
1859
- {
1860
- 'value' => 'f648f8d5ea4e4cd38e9c',
1861
- 'display' => 'Fred Smith',
1862
- 'type' => 'User'
1863
- }
1864
- ]
1865
- })
1866
- end
1867
- end # "context 'Salesforce-style payload' do"
1868
- end # "context 'special cases' do"
1869
1205
  end # context 'when prior value already exists' do
1870
1206
 
1871
1207
  context 'when value is not present' do
@@ -2411,24 +1747,6 @@ RSpec.describe Scimitar::Resources::Mixin do
2411
1747
  expect(scim_hash['emails'][0]['type' ]).to eql('work')
2412
1748
  expect(scim_hash['emails'][0]['value']).to eql('work@test.com')
2413
1749
  end
2414
-
2415
- context 'when prior value already exists, and no path' do
2416
- it 'simple value: overwrites' do
2417
- path = [ 'root' ]
2418
- scim_hash = { 'root' => { 'userName' => 'bar', 'active' => true } }.with_indifferent_case_insensitive_access()
2419
-
2420
- @instance.send(
2421
- :from_patch_backend!,
2422
- nature: 'replace',
2423
- path: path,
2424
- value: { 'active' => false }.with_indifferent_case_insensitive_access(),
2425
- altering_hash: scim_hash
2426
- )
2427
-
2428
- expect(scim_hash['root']['userName']).to eql('bar')
2429
- expect(scim_hash['root']['active']).to eql(false)
2430
- end
2431
- end
2432
1750
  end # context 'when value is not present' do
2433
1751
  end # "context 'replace' do"
2434
1752
 
@@ -2590,7 +1908,7 @@ RSpec.describe Scimitar::Resources::Mixin do
2590
1908
  :from_patch_backend!,
2591
1909
  nature: 'remove',
2592
1910
  path: ['complex[type eq "type1"]', 'data', 'nested[nature eq "nature2"]', 'info'],
2593
- value: nil,
1911
+ value: [{ 'deeper' => 'addition' }],
2594
1912
  altering_hash: scim_hash
2595
1913
  )
2596
1914
 
@@ -2727,38 +2045,6 @@ RSpec.describe Scimitar::Resources::Mixin do
2727
2045
  expect(@instance.first_name).to eql('Baz')
2728
2046
  end
2729
2047
 
2730
- # Note odd ":" separating schema ID from first attribute, although
2731
- # the nature of JSON rendering / other payloads might lead you to
2732
- # expect a "." as with any other path component.
2733
- #
2734
- # Note the ":" separating the schema ID (URN) from the attribute.
2735
- # The nature of JSON rendering / other payloads might lead you to
2736
- # expect a "." as with any complex types, but that's not the case;
2737
- # see https://tools.ietf.org/html/rfc7644#section-3.10, or
2738
- # https://tools.ietf.org/html/rfc7644#section-3.5.2 of which in
2739
- # particular, https://tools.ietf.org/html/rfc7644#page-35.
2740
- #
2741
- it 'which updates attributes defined by extension schema' do
2742
- @instance.update!(department: 'SOMEDPT')
2743
-
2744
- path = 'urn:ietf:params:scim:schemas:extension:enterprise:2.0:User:department'
2745
- path = path.upcase if force_upper_case
2746
-
2747
- patch = {
2748
- 'schemas' => ['urn:ietf:params:scim:api:messages:2.0:PatchOp'],
2749
- 'Operations' => [
2750
- {
2751
- 'op' => 'replace',
2752
- 'path' => path,
2753
- 'value' => 'OTHERDPT'
2754
- }
2755
- ]
2756
- }
2757
-
2758
- @instance.from_scim_patch!(patch_hash: patch)
2759
- expect(@instance.department).to eql('OTHERDPT')
2760
- end
2761
-
2762
2048
  it 'which updates with filter match' do
2763
2049
  @instance.update!(work_email_address: 'work@test.com', home_email_address: 'home@test.com')
2764
2050