scimitar 1.5.0 → 1.5.2
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/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
|