scimitar 1.5.2 → 2.0.0

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