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