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.
- checksums.yaml +4 -4
- data/app/controllers/scimitar/active_record_backed_resources_controller.rb +20 -94
- data/app/controllers/scimitar/application_controller.rb +13 -41
- data/app/controllers/scimitar/schemas_controller.rb +0 -5
- data/app/models/scimitar/complex_types/address.rb +6 -0
- data/app/models/scimitar/engine_configuration.rb +5 -13
- data/app/models/scimitar/error_response.rb +0 -12
- data/app/models/scimitar/lists/query_parser.rb +10 -25
- data/app/models/scimitar/resource_invalid_error.rb +1 -1
- data/app/models/scimitar/resources/base.rb +4 -14
- data/app/models/scimitar/resources/mixin.rb +13 -140
- data/app/models/scimitar/schema/address.rb +0 -1
- data/app/models/scimitar/schema/attribute.rb +5 -14
- data/app/models/scimitar/schema/base.rb +1 -1
- data/app/models/scimitar/schema/vdtp.rb +1 -1
- data/app/models/scimitar/service_provider_configuration.rb +3 -14
- data/config/initializers/scimitar.rb +3 -28
- data/lib/scimitar/version.rb +2 -2
- data/lib/scimitar.rb +2 -7
- data/spec/apps/dummy/app/controllers/mock_groups_controller.rb +1 -1
- data/spec/apps/dummy/app/models/mock_group.rb +1 -1
- data/spec/apps/dummy/app/models/mock_user.rb +8 -36
- data/spec/apps/dummy/config/application.rb +1 -0
- data/spec/apps/dummy/config/environments/test.rb +28 -5
- data/spec/apps/dummy/config/initializers/scimitar.rb +10 -61
- data/spec/apps/dummy/config/routes.rb +7 -28
- data/spec/apps/dummy/db/migrate/20210304014602_create_mock_users.rb +1 -10
- data/spec/apps/dummy/db/migrate/20210308044214_create_join_table_mock_groups_mock_users.rb +3 -8
- data/spec/apps/dummy/db/schema.rb +4 -11
- data/spec/controllers/scimitar/application_controller_spec.rb +3 -126
- data/spec/controllers/scimitar/resource_types_controller_spec.rb +2 -2
- data/spec/controllers/scimitar/schemas_controller_spec.rb +2 -10
- data/spec/models/scimitar/complex_types/address_spec.rb +4 -3
- data/spec/models/scimitar/complex_types/email_spec.rb +2 -0
- data/spec/models/scimitar/lists/query_parser_spec.rb +9 -76
- data/spec/models/scimitar/resources/base_spec.rb +70 -208
- data/spec/models/scimitar/resources/base_validation_spec.rb +2 -27
- data/spec/models/scimitar/resources/mixin_spec.rb +43 -790
- data/spec/models/scimitar/schema/attribute_spec.rb +3 -22
- data/spec/models/scimitar/schema/base_spec.rb +1 -1
- data/spec/models/scimitar/schema/user_spec.rb +0 -10
- data/spec/requests/active_record_backed_resources_controller_spec.rb +66 -709
- data/spec/requests/application_controller_spec.rb +3 -16
- data/spec/spec_helper.rb +0 -8
- metadata +14 -25
- data/LICENSE.txt +0 -21
- data/README.md +0 -710
- data/lib/scimitar/support/utilities.rb +0 -51
- data/spec/apps/dummy/app/controllers/custom_create_mock_users_controller.rb +0 -25
- data/spec/apps/dummy/app/controllers/custom_replace_mock_users_controller.rb +0 -25
- data/spec/apps/dummy/app/controllers/custom_save_mock_users_controller.rb +0 -24
- 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
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
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'
|
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'
|
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'
|
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.
|
455
|
-
{'type' => 'User', 'value' => u3.
|
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.
|
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.
|
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:
|
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
|
|