scimitar 1.8.1 → 2.0.0

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