scimitar 1.5.0 → 1.5.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/app/controllers/scimitar/application_controller.rb +2 -2
- data/app/models/scimitar/resources/mixin.rb +36 -3
- data/lib/scimitar/version.rb +2 -2
- data/spec/apps/dummy/app/models/mock_user.rb +9 -1
- data/spec/apps/dummy/config/initializers/scimitar.rb +37 -0
- data/spec/apps/dummy/db/migrate/20210304014602_create_mock_users.rb +8 -0
- data/spec/apps/dummy/db/schema.rb +2 -0
- data/spec/controllers/scimitar/schemas_controller_spec.rb +2 -2
- data/spec/models/scimitar/resources/base_spec.rb +161 -66
- data/spec/models/scimitar/resources/mixin_spec.rb +84 -5
- data/spec/requests/active_record_backed_resources_controller_spec.rb +1 -1
- data/spec/requests/application_controller_spec.rb +10 -0
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b55b1f0a0a700b57690cb05fc02241cf47f60a4ad8c92946a32366c90ee56d8c
|
4
|
+
data.tar.gz: a7143dcd7d1381250e66529c70288659a2018fb26fc16639c14ba796acc805d5
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: fb740b528770a918fc3237cabdc953affcde23fa7787cc9e9383a6d71b104e2d6325c2829545d367983a3bb64435179a0523b6d785d8e6d3eb7b2bfdb6c82748
|
7
|
+
data.tar.gz: 9f73bcafefbd7de57f258d1dc1c18e020b8dd7bca6330f7b288e308168daf7cb698051a9f2d4eaee9717139978df4e7a9fe854ba269ab94b2b46f1030d2ab9db
|
@@ -99,10 +99,10 @@ module Scimitar
|
|
99
99
|
def require_scim
|
100
100
|
scim_mime_type = Mime::Type.lookup_by_extension(:scim).to_s
|
101
101
|
|
102
|
-
if request.
|
102
|
+
if request.media_type.nil? || request.media_type.empty?
|
103
103
|
request.format = :scim
|
104
104
|
request.headers['CONTENT_TYPE'] = scim_mime_type
|
105
|
-
elsif request.
|
105
|
+
elsif request.media_type.downcase == scim_mime_type
|
106
106
|
request.format = :scim
|
107
107
|
elsif request.format == :scim
|
108
108
|
request.headers['CONTENT_TYPE'] = scim_mime_type
|
@@ -443,9 +443,30 @@ module Scimitar
|
|
443
443
|
ci_scim_hash = { 'root' => ci_scim_hash }.with_indifferent_case_insensitive_access()
|
444
444
|
end
|
445
445
|
|
446
|
+
# Handle extension schema. Contributed by @bettysteger and
|
447
|
+
# @MorrisFreeman via:
|
448
|
+
#
|
449
|
+
# https://github.com/RIPAGlobal/scimitar/issues/48
|
450
|
+
# https://github.com/RIPAGlobal/scimitar/pull/49
|
451
|
+
#
|
452
|
+
# Note the ":" separating the schema ID (URN) from the attribute.
|
453
|
+
# The nature of JSON rendering / other payloads might lead you to
|
454
|
+
# expect a "." as with any complex types, but that's not the case;
|
455
|
+
# see https://tools.ietf.org/html/rfc7644#section-3.10, or
|
456
|
+
# https://tools.ietf.org/html/rfc7644#section-3.5.2 of which in
|
457
|
+
# particular, https://tools.ietf.org/html/rfc7644#page-35.
|
458
|
+
#
|
459
|
+
paths = []
|
460
|
+
self.class.scim_resource_type.extended_schemas.each do |schema|
|
461
|
+
path_str.downcase.split(schema.id.downcase + ':').drop(1).each do |path|
|
462
|
+
paths += [schema.id] + path.split('.')
|
463
|
+
end
|
464
|
+
end
|
465
|
+
paths = path_str.split('.') if paths.empty?
|
466
|
+
|
446
467
|
self.from_patch_backend!(
|
447
468
|
nature: nature,
|
448
|
-
path:
|
469
|
+
path: paths,
|
449
470
|
value: value,
|
450
471
|
altering_hash: ci_scim_hash
|
451
472
|
)
|
@@ -616,7 +637,19 @@ module Scimitar
|
|
616
637
|
attrs_map_or_leaf_value.each do | scim_attribute, sub_attrs_map_or_leaf_value |
|
617
638
|
next if scim_attribute&.to_s&.downcase == 'id' && path.empty?
|
618
639
|
|
619
|
-
|
640
|
+
# Handle extension schema. Contributed by @bettysteger and
|
641
|
+
# @MorrisFreeman via:
|
642
|
+
#
|
643
|
+
# https://github.com/RIPAGlobal/scimitar/issues/48
|
644
|
+
# https://github.com/RIPAGlobal/scimitar/pull/49
|
645
|
+
#
|
646
|
+
attribute_tree = []
|
647
|
+
resource_class.extended_schemas.each do |schema|
|
648
|
+
attribute_tree << schema.id and break if schema.scim_attributes.any? { |attribute| attribute.name == scim_attribute.to_s }
|
649
|
+
end
|
650
|
+
attribute_tree << scim_attribute.to_s
|
651
|
+
|
652
|
+
sub_scim_hash_or_leaf_value = scim_hash_or_leaf_value&.dig(*attribute_tree)
|
620
653
|
|
621
654
|
self.from_scim_backend!(
|
622
655
|
attrs_map_or_leaf_value: sub_attrs_map_or_leaf_value,
|
@@ -914,7 +947,7 @@ module Scimitar
|
|
914
947
|
# which would imply "payload removes all users", there is the
|
915
948
|
# clear intent to remove just one.
|
916
949
|
#
|
917
|
-
# https://
|
950
|
+
# https://tools.ietf.org/html/rfc7644#section-3.5.2.2
|
918
951
|
# https://learn.microsoft.com/en-us/azure/active-directory/app-provisioning/use-scim-to-provision-users-and-groups#update-group-remove-members
|
919
952
|
#
|
920
953
|
# Since remove-all in the face of remove-one is destructive, we
|
data/lib/scimitar/version.rb
CHANGED
@@ -3,11 +3,11 @@ module Scimitar
|
|
3
3
|
# Gem version. If this changes, be sure to re-run "bundle install" or
|
4
4
|
# "bundle update".
|
5
5
|
#
|
6
|
-
VERSION = '1.5.
|
6
|
+
VERSION = '1.5.2'
|
7
7
|
|
8
8
|
# Date for VERSION. If this changes, be sure to re-run "bundle install"
|
9
9
|
# or "bundle update".
|
10
10
|
#
|
11
|
-
DATE = '2023-
|
11
|
+
DATE = '2023-03-21'
|
12
12
|
|
13
13
|
end
|
@@ -13,6 +13,8 @@ class MockUser < ActiveRecord::Base
|
|
13
13
|
work_email_address
|
14
14
|
home_email_address
|
15
15
|
work_phone_number
|
16
|
+
organization
|
17
|
+
department
|
16
18
|
}
|
17
19
|
|
18
20
|
has_and_belongs_to_many :mock_groups
|
@@ -82,7 +84,13 @@ class MockUser < ActiveRecord::Base
|
|
82
84
|
}
|
83
85
|
}
|
84
86
|
],
|
85
|
-
active: :is_active
|
87
|
+
active: :is_active,
|
88
|
+
|
89
|
+
# Custom extension schema - see configuration in
|
90
|
+
# "spec/apps/dummy/config/initializers/scimitar.rb".
|
91
|
+
#
|
92
|
+
organization: :organization,
|
93
|
+
department: :department
|
86
94
|
}
|
87
95
|
end
|
88
96
|
|
@@ -1,5 +1,14 @@
|
|
1
1
|
# Test app configuration.
|
2
2
|
#
|
3
|
+
# Note that as a result of https://github.com/RIPAGlobal/scimitar/issues/48,
|
4
|
+
# tests include a custom extension of the core User schema. A shortcoming of
|
5
|
+
# some of the code from which Scimitar was originally built is that those
|
6
|
+
# extensions are done with class-level ivars, so it is largely impossible (or
|
7
|
+
# at least, impractical in tests) to avoid polluting the core class itself
|
8
|
+
# with the extension.
|
9
|
+
#
|
10
|
+
# All related schema tests are written with this in mind.
|
11
|
+
#
|
3
12
|
Scimitar.engine_configuration = Scimitar::EngineConfiguration.new({
|
4
13
|
|
5
14
|
application_controller_mixin: Module.new do
|
@@ -12,3 +21,31 @@ Scimitar.engine_configuration = Scimitar::EngineConfiguration.new({
|
|
12
21
|
end
|
13
22
|
|
14
23
|
})
|
24
|
+
|
25
|
+
module ScimSchemaExtensions
|
26
|
+
module User
|
27
|
+
class Enterprise < Scimitar::Schema::Base
|
28
|
+
def initialize(options = {})
|
29
|
+
super(
|
30
|
+
name: 'ExtendedUser',
|
31
|
+
description: 'Enterprise extension for a User',
|
32
|
+
id: self.class.id,
|
33
|
+
scim_attributes: self.class.scim_attributes
|
34
|
+
)
|
35
|
+
end
|
36
|
+
|
37
|
+
def self.id
|
38
|
+
'urn:ietf:params:scim:schemas:extension:enterprise:2.0:User'
|
39
|
+
end
|
40
|
+
|
41
|
+
def self.scim_attributes
|
42
|
+
[
|
43
|
+
Scimitar::Schema::Attribute.new(name: 'organization', type: 'string'),
|
44
|
+
Scimitar::Schema::Attribute.new(name: 'department', type: 'string')
|
45
|
+
]
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
Scimitar::Resources::User.extend_schema ScimSchemaExtensions::User::Enterprise
|
@@ -3,6 +3,8 @@ class CreateMockUsers < ActiveRecord::Migration[6.1]
|
|
3
3
|
create_table :mock_users, id: :uuid, primary_key: :primary_key do |t|
|
4
4
|
t.timestamps
|
5
5
|
|
6
|
+
# Support part of the core schema
|
7
|
+
#
|
6
8
|
t.text :scim_uid
|
7
9
|
t.text :username
|
8
10
|
t.text :first_name
|
@@ -10,6 +12,12 @@ class CreateMockUsers < ActiveRecord::Migration[6.1]
|
|
10
12
|
t.text :work_email_address
|
11
13
|
t.text :home_email_address
|
12
14
|
t.text :work_phone_number
|
15
|
+
|
16
|
+
# Support the custom extension schema - see configuration in
|
17
|
+
# "spec/apps/dummy/config/initializers/scimitar.rb".
|
18
|
+
#
|
19
|
+
t.text :organization
|
20
|
+
t.text :department
|
13
21
|
end
|
14
22
|
end
|
15
23
|
end
|
@@ -39,6 +39,8 @@ ActiveRecord::Schema.define(version: 2021_03_08_044214) do
|
|
39
39
|
t.text "work_email_address"
|
40
40
|
t.text "home_email_address"
|
41
41
|
t.text "work_phone_number"
|
42
|
+
t.text "organization"
|
43
|
+
t.text "department"
|
42
44
|
end
|
43
45
|
|
44
46
|
add_foreign_key "mock_groups_users", "mock_groups"
|
@@ -14,9 +14,9 @@ RSpec.describe Scimitar::SchemasController do
|
|
14
14
|
get :index, params: { format: :scim }
|
15
15
|
expect(response).to be_ok
|
16
16
|
parsed_body = JSON.parse(response.body)
|
17
|
-
expect(parsed_body.length).to eql(
|
17
|
+
expect(parsed_body.length).to eql(3)
|
18
18
|
schema_names = parsed_body.map {|schema| schema['name']}
|
19
|
-
expect(schema_names).to match_array(['User', 'Group'])
|
19
|
+
expect(schema_names).to match_array(['User', 'ExtendedUser', 'Group'])
|
20
20
|
end
|
21
21
|
|
22
22
|
it 'returns only the User schema when its id is provided' do
|
@@ -250,90 +250,185 @@ RSpec.describe Scimitar::Resources::Base do
|
|
250
250
|
end # "context 'dynamic setters based on schema' do"
|
251
251
|
|
252
252
|
context 'schema extension' do
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
253
|
+
context 'of custom schema' do
|
254
|
+
ThirdCustomSchema = Class.new(Scimitar::Schema::Base) do
|
255
|
+
def self.id
|
256
|
+
'custom-id'
|
257
|
+
end
|
257
258
|
|
258
|
-
|
259
|
-
|
259
|
+
def self.scim_attributes
|
260
|
+
[ Scimitar::Schema::Attribute.new(name: 'name', type: 'string') ]
|
261
|
+
end
|
260
262
|
end
|
261
|
-
end
|
262
263
|
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
264
|
+
ExtensionSchema = Class.new(Scimitar::Schema::Base) do
|
265
|
+
def self.id
|
266
|
+
'extension-id'
|
267
|
+
end
|
267
268
|
|
268
|
-
|
269
|
-
|
269
|
+
def self.scim_attributes
|
270
|
+
[ Scimitar::Schema::Attribute.new(name: 'relationship', type: 'string', required: true) ]
|
271
|
+
end
|
270
272
|
end
|
271
|
-
end
|
272
273
|
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
274
|
+
let(:resource_class) {
|
275
|
+
Class.new(Scimitar::Resources::Base) do
|
276
|
+
set_schema ThirdCustomSchema
|
277
|
+
extend_schema ExtensionSchema
|
277
278
|
|
278
|
-
|
279
|
-
|
279
|
+
def self.endpoint
|
280
|
+
'/gaga'
|
281
|
+
end
|
282
|
+
|
283
|
+
def self.resource_type_id
|
284
|
+
'CustomResource'
|
285
|
+
end
|
280
286
|
end
|
287
|
+
}
|
281
288
|
|
282
|
-
|
283
|
-
|
289
|
+
context '#initialize' do
|
290
|
+
it 'allows setting extension attributes' do
|
291
|
+
resource = resource_class.new('extension-id' => {relationship: 'GAGA'})
|
292
|
+
expect(resource.relationship).to eql('GAGA')
|
284
293
|
end
|
285
|
-
end
|
286
|
-
|
294
|
+
end # "context '#initialize' do"
|
295
|
+
|
296
|
+
context '#as_json' do
|
297
|
+
it 'namespaces the extension attributes' do
|
298
|
+
resource = resource_class.new(relationship: 'GAGA')
|
299
|
+
hash = resource.as_json
|
300
|
+
expect(hash["schemas"]).to eql(['custom-id', 'extension-id'])
|
301
|
+
expect(hash["extension-id"]).to eql("relationship" => 'GAGA')
|
302
|
+
end
|
303
|
+
end # "context '#as_json' do"
|
287
304
|
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
|
305
|
+
context '.resource_type' do
|
306
|
+
it 'appends the extension schemas' do
|
307
|
+
resource_type = resource_class.resource_type('http://gaga')
|
308
|
+
expect(resource_type.meta.location).to eql('http://gaga')
|
309
|
+
expect(resource_type.schemaExtensions.count).to eql(1)
|
310
|
+
end
|
294
311
|
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
expect(hash["schemas"]).to eql(['custom-id', 'extension-id'])
|
300
|
-
expect(hash["extension-id"]).to eql("relationship" => 'GAGA')
|
301
|
-
end
|
302
|
-
end # "context '#as_json' do"
|
312
|
+
context 'validation' do
|
313
|
+
it 'validates into custom schema' do
|
314
|
+
resource = resource_class.new('extension-id' => {})
|
315
|
+
expect(resource.valid?).to eql(false)
|
303
316
|
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
|
309
|
-
end
|
317
|
+
resource = resource_class.new('extension-id' => {relationship: 'GAGA'})
|
318
|
+
expect(resource.relationship).to eql('GAGA')
|
319
|
+
expect(resource.valid?).to eql(true)
|
320
|
+
end
|
321
|
+
end # context 'validation'
|
322
|
+
end # "context '.resource_type' do"
|
310
323
|
|
311
|
-
context '
|
312
|
-
it '
|
313
|
-
|
314
|
-
expect(
|
324
|
+
context '.find_attribute' do
|
325
|
+
it 'finds in first schema' do
|
326
|
+
found = resource_class().find_attribute('name') # Defined in ThirdCustomSchema
|
327
|
+
expect(found).to be_present
|
328
|
+
expect(found.name).to eql('name')
|
329
|
+
expect(found.type).to eql('string')
|
330
|
+
end
|
315
331
|
|
316
|
-
|
317
|
-
|
318
|
-
expect(
|
332
|
+
it 'finds across schemas' do
|
333
|
+
found = resource_class().find_attribute('relationship') # Defined in ExtensionSchema
|
334
|
+
expect(found).to be_present
|
335
|
+
expect(found.name).to eql('relationship')
|
336
|
+
expect(found.type).to eql('string')
|
319
337
|
end
|
320
|
-
end # context '
|
321
|
-
end # "context '
|
338
|
+
end # "context '.find_attribute' do"
|
339
|
+
end # "context 'of custom schema' do"
|
322
340
|
|
323
|
-
context '
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
expect(found.type).to eql('string')
|
329
|
-
end
|
341
|
+
context 'of core schema' do
|
342
|
+
EnterpriseExtensionSchema = Class.new(Scimitar::Schema::Base) do
|
343
|
+
def self.id
|
344
|
+
'urn:ietf:params:scim:schemas:extension:enterprise:2.0:User'
|
345
|
+
end
|
330
346
|
|
331
|
-
|
332
|
-
|
333
|
-
|
334
|
-
|
335
|
-
|
347
|
+
def self.scim_attributes
|
348
|
+
[
|
349
|
+
Scimitar::Schema::Attribute.new(name: 'organization', type: 'string'),
|
350
|
+
Scimitar::Schema::Attribute.new(name: 'department', type: 'string')
|
351
|
+
]
|
352
|
+
end
|
336
353
|
end
|
337
|
-
|
354
|
+
|
355
|
+
let(:resource_class) {
|
356
|
+
Class.new(Scimitar::Resources::Base) do
|
357
|
+
set_schema Scimitar::Schema::User
|
358
|
+
extend_schema EnterpriseExtensionSchema
|
359
|
+
|
360
|
+
def self.endpoint
|
361
|
+
'/Users'
|
362
|
+
end
|
363
|
+
|
364
|
+
def self.resource_type_id
|
365
|
+
'User'
|
366
|
+
end
|
367
|
+
end
|
368
|
+
}
|
369
|
+
|
370
|
+
context '#initialize' do
|
371
|
+
it 'allows setting extension attributes' do
|
372
|
+
resource = resource_class.new('urn:ietf:params:scim:schemas:extension:enterprise:2.0:User' => {organization: 'SOMEORG', department: 'SOMEDPT'})
|
373
|
+
|
374
|
+
expect(resource.organization).to eql('SOMEORG')
|
375
|
+
expect(resource.department ).to eql('SOMEDPT')
|
376
|
+
end
|
377
|
+
end # "context '#initialize' do"
|
378
|
+
|
379
|
+
context '#as_json' do
|
380
|
+
it 'namespaces the extension attributes' do
|
381
|
+
resource = resource_class.new(organization: 'SOMEORG', department: 'SOMEDPT')
|
382
|
+
hash = resource.as_json
|
383
|
+
|
384
|
+
expect(hash['schemas']).to eql(['urn:ietf:params:scim:schemas:core:2.0:User', 'urn:ietf:params:scim:schemas:extension:enterprise:2.0:User'])
|
385
|
+
expect(hash['urn:ietf:params:scim:schemas:extension:enterprise:2.0:User']).to eql('organization' => 'SOMEORG', 'department' => 'SOMEDPT')
|
386
|
+
end
|
387
|
+
end # "context '#as_json' do"
|
388
|
+
|
389
|
+
context '.resource_type' do
|
390
|
+
it 'appends the extension schemas' do
|
391
|
+
resource_type = resource_class.resource_type('http://example.com')
|
392
|
+
expect(resource_type.meta.location).to eql('http://example.com')
|
393
|
+
expect(resource_type.schemaExtensions.count).to eql(1)
|
394
|
+
end
|
395
|
+
|
396
|
+
context 'validation' do
|
397
|
+
it 'validates into custom schema' do
|
398
|
+
resource = resource_class.new('urn:ietf:params:scim:schemas:extension:enterprise:2.0:User' => {})
|
399
|
+
expect(resource.valid?).to eql(false)
|
400
|
+
|
401
|
+
resource = resource_class.new(
|
402
|
+
'urn:ietf:params:scim:schemas:extension:enterprise:2.0:User' => {
|
403
|
+
userName: 'SOMEUSR',
|
404
|
+
organization: 'SOMEORG',
|
405
|
+
department: 'SOMEDPT'
|
406
|
+
}
|
407
|
+
)
|
408
|
+
|
409
|
+
expect(resource.organization).to eql('SOMEORG')
|
410
|
+
expect(resource.department ).to eql('SOMEDPT')
|
411
|
+
expect(resource.valid? ).to eql(true)
|
412
|
+
end
|
413
|
+
end # context 'validation'
|
414
|
+
end # "context '.resource_type' do"
|
415
|
+
|
416
|
+
context '.find_attribute' do
|
417
|
+
it 'finds in first schema' do
|
418
|
+
found = resource_class().find_attribute('userName') # Defined in Scimitar::Schema::User
|
419
|
+
|
420
|
+
expect(found).to be_present
|
421
|
+
expect(found.name).to eql('userName')
|
422
|
+
expect(found.type).to eql('string')
|
423
|
+
end
|
424
|
+
|
425
|
+
it 'finds across schemas' do
|
426
|
+
found = resource_class().find_attribute('organization') # Defined in EnterpriseExtensionSchema
|
427
|
+
expect(found).to be_present
|
428
|
+
expect(found.name).to eql('organization')
|
429
|
+
expect(found.type).to eql('string')
|
430
|
+
end
|
431
|
+
end # "context '.find_attribute' do"
|
432
|
+
end # "context 'of core schema' do"
|
338
433
|
end # "context 'schema extension' do"
|
339
434
|
end
|
@@ -172,6 +172,7 @@ RSpec.describe Scimitar::Resources::Mixin do
|
|
172
172
|
instance.work_email_address = 'foo.bar@test.com'
|
173
173
|
instance.home_email_address = nil
|
174
174
|
instance.work_phone_number = '+642201234567'
|
175
|
+
instance.organization = 'SOMEORG'
|
175
176
|
|
176
177
|
g1 = MockGroup.create!(display_name: 'Group 1')
|
177
178
|
g2 = MockGroup.create!(display_name: 'Group 2')
|
@@ -194,7 +195,12 @@ RSpec.describe Scimitar::Resources::Mixin do
|
|
194
195
|
'externalId' => 'AA02984',
|
195
196
|
'groups' => [{'display'=>g1.display_name, 'value'=>g1.id.to_s}, {'display'=>g3.display_name, 'value'=>g3.id.to_s}],
|
196
197
|
'meta' => {'location'=>"https://test.com/mock_users/#{uuid}", 'resourceType'=>'User'},
|
197
|
-
'schemas' => ['urn:ietf:params:scim:schemas:core:2.0:User']
|
198
|
+
'schemas' => ['urn:ietf:params:scim:schemas:core:2.0:User', 'urn:ietf:params:scim:schemas:extension:enterprise:2.0:User'],
|
199
|
+
|
200
|
+
'urn:ietf:params:scim:schemas:extension:enterprise:2.0:User' => {
|
201
|
+
'organization' => 'SOMEORG',
|
202
|
+
'department' => nil
|
203
|
+
}
|
198
204
|
})
|
199
205
|
end
|
200
206
|
end # "context 'with a UUID, renamed primary key column' do"
|
@@ -318,7 +324,9 @@ RSpec.describe Scimitar::Resources::Mixin do
|
|
318
324
|
],
|
319
325
|
|
320
326
|
'meta' => {'location'=>'https://test.com/static_map_test', 'resourceType'=>'User'},
|
321
|
-
'schemas' => ['urn:ietf:params:scim:schemas:core:2.0:User']
|
327
|
+
'schemas' => ['urn:ietf:params:scim:schemas:core:2.0:User', 'urn:ietf:params:scim:schemas:extension:enterprise:2.0:User'],
|
328
|
+
|
329
|
+
'urn:ietf:params:scim:schemas:extension:enterprise:2.0:User' => {}
|
322
330
|
})
|
323
331
|
end
|
324
332
|
end # "context 'using static mappings' do"
|
@@ -345,7 +353,9 @@ RSpec.describe Scimitar::Resources::Mixin do
|
|
345
353
|
],
|
346
354
|
|
347
355
|
'meta' => {'location'=>'https://test.com/dynamic_map_test', 'resourceType'=>'User'},
|
348
|
-
'schemas' => ['urn:ietf:params:scim:schemas:core:2.0:User']
|
356
|
+
'schemas' => ['urn:ietf:params:scim:schemas:core:2.0:User', 'urn:ietf:params:scim:schemas:extension:enterprise:2.0:User'],
|
357
|
+
|
358
|
+
'urn:ietf:params:scim:schemas:extension:enterprise:2.0:User' => {}
|
349
359
|
})
|
350
360
|
end
|
351
361
|
end # "context 'using dynamic lists' do"
|
@@ -402,7 +412,12 @@ RSpec.describe Scimitar::Resources::Mixin do
|
|
402
412
|
'id' => '42', # Note, String
|
403
413
|
'externalId' => 'AA02984',
|
404
414
|
'meta' => {'location' => 'https://test.com/mock_users/42', 'resourceType' => 'User'},
|
405
|
-
'schemas' => ['urn:ietf:params:scim:schemas:core:2.0:User']
|
415
|
+
'schemas' => ['urn:ietf:params:scim:schemas:core:2.0:User', 'urn:ietf:params:scim:schemas:extension:enterprise:2.0:User'],
|
416
|
+
|
417
|
+
'urn:ietf:params:scim:schemas:extension:enterprise:2.0:User' => {
|
418
|
+
'organization' => 'SOMEORG',
|
419
|
+
'DEPARTMENT' => 'SOMEDPT'
|
420
|
+
}
|
406
421
|
}
|
407
422
|
|
408
423
|
hash = spec_helper_hupcase(hash) if force_upper_case
|
@@ -418,6 +433,8 @@ RSpec.describe Scimitar::Resources::Mixin do
|
|
418
433
|
expect(instance.work_email_address).to eql('foo.bar@test.com')
|
419
434
|
expect(instance.home_email_address).to be_nil
|
420
435
|
expect(instance.work_phone_number ).to eql('+642201234567')
|
436
|
+
expect(instance.organization ).to eql('SOMEORG')
|
437
|
+
expect(instance.department ).to eql('SOMEDPT')
|
421
438
|
end
|
422
439
|
|
423
440
|
it 'honouring read-write lists' do
|
@@ -704,6 +721,21 @@ RSpec.describe Scimitar::Resources::Mixin do
|
|
704
721
|
expect(scim_hash['name']['familyName']).to eql('Bar')
|
705
722
|
end
|
706
723
|
|
724
|
+
it 'with schema extensions: overwrites' do
|
725
|
+
path = [ 'urn:ietf:params:scim:schemas:extension:enterprise:2.0:User', 'organization' ]
|
726
|
+
scim_hash = { 'urn:ietf:params:scim:schemas:extension:enterprise:2.0:User' => { 'organization' => 'SOMEORG' } }.with_indifferent_case_insensitive_access()
|
727
|
+
|
728
|
+
@instance.send(
|
729
|
+
:from_patch_backend!,
|
730
|
+
nature: 'add',
|
731
|
+
path: path,
|
732
|
+
value: 'OTHERORG',
|
733
|
+
altering_hash: scim_hash
|
734
|
+
)
|
735
|
+
|
736
|
+
expect(scim_hash['urn:ietf:params:scim:schemas:extension:enterprise:2.0:User']['organization' ]).to eql('OTHERORG')
|
737
|
+
end
|
738
|
+
|
707
739
|
# For 'add', filter at end-of-path is nonsensical and not
|
708
740
|
# supported by spec or Scimitar; we only test mid-path filters.
|
709
741
|
#
|
@@ -892,6 +924,21 @@ RSpec.describe Scimitar::Resources::Mixin do
|
|
892
924
|
expect(scim_hash['name']['givenName']).to eql('Baz')
|
893
925
|
end
|
894
926
|
|
927
|
+
it 'with schema extensions: adds' do
|
928
|
+
path = [ 'urn:ietf:params:scim:schemas:extension:enterprise:2.0:User', 'organization' ]
|
929
|
+
scim_hash = {}.with_indifferent_case_insensitive_access()
|
930
|
+
|
931
|
+
@instance.send(
|
932
|
+
:from_patch_backend!,
|
933
|
+
nature: 'add',
|
934
|
+
path: path,
|
935
|
+
value: 'SOMEORG',
|
936
|
+
altering_hash: scim_hash
|
937
|
+
)
|
938
|
+
|
939
|
+
expect(scim_hash['urn:ietf:params:scim:schemas:extension:enterprise:2.0:User']['organization' ]).to eql('SOMEORG')
|
940
|
+
end
|
941
|
+
|
895
942
|
context 'with filter mid-path: adds' do
|
896
943
|
it 'by string match' do
|
897
944
|
path = [ 'emails[type eq "work"]', 'value' ]
|
@@ -1233,7 +1280,7 @@ RSpec.describe Scimitar::Resources::Mixin do
|
|
1233
1280
|
|
1234
1281
|
# What we expect:
|
1235
1282
|
#
|
1236
|
-
# https://
|
1283
|
+
# https://tools.ietf.org/html/rfc7644#section-3.5.2.2
|
1237
1284
|
# https://docs.snowflake.com/en/user-guide/scim-intro.html#patch-scim-v2-groups-id
|
1238
1285
|
#
|
1239
1286
|
# ...vs accounting for the unusual payloads we sometimes get,
|
@@ -2680,6 +2727,38 @@ RSpec.describe Scimitar::Resources::Mixin do
|
|
2680
2727
|
expect(@instance.first_name).to eql('Baz')
|
2681
2728
|
end
|
2682
2729
|
|
2730
|
+
# Note odd ":" separating schema ID from first attribute, although
|
2731
|
+
# the nature of JSON rendering / other payloads might lead you to
|
2732
|
+
# expect a "." as with any other path component.
|
2733
|
+
#
|
2734
|
+
# Note the ":" separating the schema ID (URN) from the attribute.
|
2735
|
+
# The nature of JSON rendering / other payloads might lead you to
|
2736
|
+
# expect a "." as with any complex types, but that's not the case;
|
2737
|
+
# see https://tools.ietf.org/html/rfc7644#section-3.10, or
|
2738
|
+
# https://tools.ietf.org/html/rfc7644#section-3.5.2 of which in
|
2739
|
+
# particular, https://tools.ietf.org/html/rfc7644#page-35.
|
2740
|
+
#
|
2741
|
+
it 'which updates attributes defined by extension schema' do
|
2742
|
+
@instance.update!(department: 'SOMEDPT')
|
2743
|
+
|
2744
|
+
path = 'urn:ietf:params:scim:schemas:extension:enterprise:2.0:User:department'
|
2745
|
+
path = path.upcase if force_upper_case
|
2746
|
+
|
2747
|
+
patch = {
|
2748
|
+
'schemas' => ['urn:ietf:params:scim:api:messages:2.0:PatchOp'],
|
2749
|
+
'Operations' => [
|
2750
|
+
{
|
2751
|
+
'op' => 'replace',
|
2752
|
+
'path' => path,
|
2753
|
+
'value' => 'OTHERDPT'
|
2754
|
+
}
|
2755
|
+
]
|
2756
|
+
}
|
2757
|
+
|
2758
|
+
@instance.from_scim_patch!(patch_hash: patch)
|
2759
|
+
expect(@instance.department).to eql('OTHERDPT')
|
2760
|
+
end
|
2761
|
+
|
2683
2762
|
it 'which updates with filter match' do
|
2684
2763
|
@instance.update!(work_email_address: 'work@test.com', home_email_address: 'home@test.com')
|
2685
2764
|
|
@@ -762,7 +762,7 @@ RSpec.describe Scimitar::ActiveRecordBackedResourcesController do
|
|
762
762
|
end
|
763
763
|
end
|
764
764
|
|
765
|
-
# https://
|
765
|
+
# https://tools.ietf.org/html/rfc7644#section-3.5.2.2
|
766
766
|
#
|
767
767
|
context 'and using an RFC-compliant payload' do
|
768
768
|
let(:removed_user) { @u2 }
|
@@ -39,6 +39,16 @@ RSpec.describe Scimitar::ApplicationController do
|
|
39
39
|
expect(parsed_body['request']['content_type']).to eql('application/scim+json')
|
40
40
|
end
|
41
41
|
|
42
|
+
it 'translates Content-Type with charset to Rails request format' do
|
43
|
+
get '/CustomRequestVerifiers', headers: { 'CONTENT_TYPE' => 'application/scim+json; charset=utf-8' }
|
44
|
+
|
45
|
+
expect(response).to have_http_status(:ok)
|
46
|
+
parsed_body = JSON.parse(response.body)
|
47
|
+
expect(parsed_body['request']['is_scim' ]).to eql(true)
|
48
|
+
expect(parsed_body['request']['format' ]).to eql('application/scim+json')
|
49
|
+
expect(parsed_body['request']['content_type']).to eql('application/scim+json; charset=utf-8')
|
50
|
+
end
|
51
|
+
|
42
52
|
it 'translates Rails request format to header' do
|
43
53
|
get '/CustomRequestVerifiers', params: { format: :scim }
|
44
54
|
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: scimitar
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.5.
|
4
|
+
version: 1.5.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- RIPA Global
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2023-
|
12
|
+
date: 2023-03-21 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: rails
|