scimitar 2.14.0 → 2.15.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/LICENSE.txt +1 -1
- data/Rakefile +1 -2
- data/app/controllers/scimitar/active_record_backed_resources_controller.rb +11 -5
- data/app/controllers/scimitar/application_controller.rb +1 -1
- data/app/models/scimitar/engine_configuration.rb +4 -2
- data/app/models/scimitar/lists/count.rb +6 -3
- data/app/models/scimitar/resources/mixin.rb +7 -1
- data/config/initializers/scimitar.rb +9 -1
- data/lib/scimitar/version.rb +2 -2
- data/spec/controllers/scimitar/application_controller_spec.rb +37 -19
- data/spec/models/scimitar/lists/count_spec.rb +8 -3
- data/spec/models/scimitar/resources/mixin_spec.rb +110 -2
- data/spec/requests/active_record_backed_resources_controller_spec.rb +86 -0
- metadata +6 -6
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 7c705a0369ccfd6204da2dd2a0e5a17a15f2fa658703218404d7556c1568a891
|
|
4
|
+
data.tar.gz: cd8ab1a8fc432533a6a0e4ee6045f20677c9164dfd330c416a68b5728b58947f
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 11846119f8df609b6f75f95a436a78e2ca9dceed1a9d236cd08c89df052e45677efcee573943feaee719b7fff6f648bd13622a47d32f493bb69d3b8a58415363
|
|
7
|
+
data.tar.gz: 32bdc2ba6b4613c498ed988bfb2a4397c26f1d0196341f071b52d4bdb13065b9a658654dba1666bbb75c6a185afa924d6b6aa60d2f4ef9784cc9af6df4395623
|
data/LICENSE.txt
CHANGED
data/Rakefile
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
require 'rake'
|
|
2
2
|
require 'rspec/core/rake_task'
|
|
3
3
|
require 'rdoc/task'
|
|
4
|
-
require 'sdoc'
|
|
5
4
|
|
|
6
5
|
RSpec::Core::RakeTask.new(:default) do | t |
|
|
7
6
|
end
|
|
@@ -12,5 +11,5 @@ Rake::RDocTask.new do | rd |
|
|
|
12
11
|
rd.title = 'Scimitar'
|
|
13
12
|
rd.main = 'README.md'
|
|
14
13
|
rd.rdoc_dir = 'docs/rdoc'
|
|
15
|
-
rd.generator = '
|
|
14
|
+
rd.generator = 'rdoc'
|
|
16
15
|
end
|
|
@@ -36,11 +36,17 @@ module Scimitar
|
|
|
36
36
|
|
|
37
37
|
pagination_info = scim_pagination_info(query.count())
|
|
38
38
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
39
|
+
# SCIM 2.0 RFC 7644: When count=0, return metadata only (no Resources).
|
|
40
|
+
# This avoids an unnecessary database query for record data.
|
|
41
|
+
page_of_results = if pagination_info.limit == 0
|
|
42
|
+
[]
|
|
43
|
+
else
|
|
44
|
+
query
|
|
45
|
+
.order(@id_column => :asc)
|
|
46
|
+
.offset(pagination_info.offset)
|
|
47
|
+
.limit(pagination_info.limit)
|
|
48
|
+
.to_a()
|
|
49
|
+
end
|
|
44
50
|
|
|
45
51
|
super(pagination_info, page_of_results) do | record |
|
|
46
52
|
record_to_scim(record)
|
|
@@ -48,7 +48,7 @@ module Scimitar
|
|
|
48
48
|
#
|
|
49
49
|
def handle_scim_error(error_response, exception = error_response)
|
|
50
50
|
unless Scimitar.engine_configuration.exception_reporter.nil?
|
|
51
|
-
Scimitar.engine_configuration.exception_reporter
|
|
51
|
+
instance_exec(exception, &Scimitar.engine_configuration.exception_reporter)
|
|
52
52
|
end
|
|
53
53
|
|
|
54
54
|
render json: error_response, status: error_response.status
|
|
@@ -16,6 +16,7 @@ module Scimitar
|
|
|
16
16
|
:application_controller_mixin,
|
|
17
17
|
:exception_reporter,
|
|
18
18
|
:optional_value_fields_required,
|
|
19
|
+
:render_mapped_nil_values_in_response,
|
|
19
20
|
:schema_list_from_attribute_mappings,
|
|
20
21
|
)
|
|
21
22
|
|
|
@@ -25,8 +26,9 @@ module Scimitar
|
|
|
25
26
|
# Set defaults that may be overridden by the initializer.
|
|
26
27
|
#
|
|
27
28
|
defaults = {
|
|
28
|
-
optional_value_fields_required:
|
|
29
|
-
|
|
29
|
+
optional_value_fields_required: true,
|
|
30
|
+
render_mapped_nil_values_in_response: true,
|
|
31
|
+
schema_list_from_attribute_mappings: []
|
|
30
32
|
}
|
|
31
33
|
|
|
32
34
|
super(defaults.merge(attributes))
|
|
@@ -15,9 +15,12 @@ module Scimitar
|
|
|
15
15
|
|
|
16
16
|
# Set a limit (page size) value.
|
|
17
17
|
#
|
|
18
|
-
# +value+:: Integer value held in a String. Must be >=
|
|
18
|
+
# +value+:: Integer value held in a String. Must be >= 0.
|
|
19
|
+
#
|
|
20
|
+
# Per SCIM 2.0 RFC 7644 Section 3.4.2.4: "A value of '0' indicates that
|
|
21
|
+
# no resource results are to be returned except for 'totalResults'."
|
|
19
22
|
#
|
|
20
|
-
# Raises exceptions if given non-numeric
|
|
23
|
+
# Raises exceptions if given non-numeric or negative input.
|
|
21
24
|
#
|
|
22
25
|
def limit=(value)
|
|
23
26
|
value = value&.to_s
|
|
@@ -25,7 +28,7 @@ module Scimitar
|
|
|
25
28
|
|
|
26
29
|
validate_numericality(value)
|
|
27
30
|
input = value.to_i
|
|
28
|
-
raise if input <
|
|
31
|
+
raise if input < 0
|
|
29
32
|
@limit = input
|
|
30
33
|
end
|
|
31
34
|
|
|
@@ -612,7 +612,13 @@ module Scimitar
|
|
|
612
612
|
end
|
|
613
613
|
end
|
|
614
614
|
|
|
615
|
-
|
|
615
|
+
if (
|
|
616
|
+
include_attributes.any? or
|
|
617
|
+
! Scimitar.engine_configuration.render_mapped_nil_values_in_response
|
|
618
|
+
)
|
|
619
|
+
result.compact!
|
|
620
|
+
end
|
|
621
|
+
|
|
616
622
|
result
|
|
617
623
|
|
|
618
624
|
when Array # Static or dynamic mapping against lists in data source
|
|
@@ -102,7 +102,9 @@ Rails.application.config.to_prepare do # (required for >= Rails 7 / Zeitwerk)
|
|
|
102
102
|
# JSON response to the API caller. If you want exceptions to also be
|
|
103
103
|
# reported to a third party system such as sentry.io or raygun.com, you can
|
|
104
104
|
# configure a Proc to do so. It is passed a Ruby exception subclass object.
|
|
105
|
-
#
|
|
105
|
+
# The Proc is called via 'instance_exec' in the controller context, so you
|
|
106
|
+
# have access to things like 'request', 'params' and 'action_name'. For
|
|
107
|
+
# example, a minimal sentry.io reporter might do this:
|
|
106
108
|
#
|
|
107
109
|
# exception_reporter: Proc.new do | exception |
|
|
108
110
|
# Sentry.capture_exception(exception)
|
|
@@ -119,6 +121,12 @@ Rails.application.config.to_prepare do # (required for >= Rails 7 / Zeitwerk)
|
|
|
119
121
|
#
|
|
120
122
|
# optional_value_fields_required: false
|
|
121
123
|
|
|
124
|
+
# When rendering responses, +nil+ values can either still be included via
|
|
125
|
+
# the attributes map with a JSON value of +null+, or omitted. By default,
|
|
126
|
+
# all attributes in your map are returned in responses.
|
|
127
|
+
#
|
|
128
|
+
# render_mapped_nil_values_in_response: false
|
|
129
|
+
|
|
122
130
|
# The SCIM standard `/Schemas` endpoint lists, by default, all known schema
|
|
123
131
|
# definitions with the mutabilty (read-write, read-only, write-only) state
|
|
124
132
|
# described by those definitions, and includes all defined attributes. For
|
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 = '2.
|
|
6
|
+
VERSION = '2.15.0'
|
|
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 = '
|
|
11
|
+
DATE = '2026-03-06'
|
|
12
12
|
|
|
13
13
|
end
|
|
@@ -352,8 +352,9 @@ RSpec.describe Scimitar::ApplicationController do
|
|
|
352
352
|
context 'with an exception reporter' do
|
|
353
353
|
around :each do | example |
|
|
354
354
|
original_configuration = Scimitar.engine_configuration.exception_reporter
|
|
355
|
+
exceptions = @exceptions = []
|
|
355
356
|
Scimitar.engine_configuration.exception_reporter = Proc.new do | exception |
|
|
356
|
-
|
|
357
|
+
exceptions << exception
|
|
357
358
|
end
|
|
358
359
|
example.run()
|
|
359
360
|
ensure
|
|
@@ -364,8 +365,8 @@ RSpec.describe Scimitar::ApplicationController do
|
|
|
364
365
|
it 'is invoked' do
|
|
365
366
|
get :index, params: { format: :scim }
|
|
366
367
|
|
|
367
|
-
expect(@
|
|
368
|
-
expect(@
|
|
368
|
+
expect(@exceptions.first).to be_a(RuntimeError)
|
|
369
|
+
expect(@exceptions.first.message).to eql('Bang')
|
|
369
370
|
end
|
|
370
371
|
end
|
|
371
372
|
|
|
@@ -379,8 +380,8 @@ RSpec.describe Scimitar::ApplicationController do
|
|
|
379
380
|
it 'is invoked' do
|
|
380
381
|
get :index, params: { format: :scim }
|
|
381
382
|
|
|
382
|
-
expect(@
|
|
383
|
-
expect(@
|
|
383
|
+
expect(@exceptions.first).to be_a(ActiveRecord::RecordNotFound)
|
|
384
|
+
expect(@exceptions.first.message).to eql('42')
|
|
384
385
|
end
|
|
385
386
|
end
|
|
386
387
|
|
|
@@ -398,8 +399,8 @@ RSpec.describe Scimitar::ApplicationController do
|
|
|
398
399
|
it 'is invoked' do
|
|
399
400
|
get :index, params: { format: :scim }
|
|
400
401
|
|
|
401
|
-
expect(@
|
|
402
|
-
expect(@
|
|
402
|
+
expect(@exceptions.first).to be_a(ActionDispatch::Http::Parameters::ParseError)
|
|
403
|
+
expect(@exceptions.first.message).to eql('Hello')
|
|
403
404
|
end
|
|
404
405
|
end
|
|
405
406
|
|
|
@@ -412,8 +413,8 @@ RSpec.describe Scimitar::ApplicationController do
|
|
|
412
413
|
request.headers['Content-Type'] = 'text/plain'
|
|
413
414
|
get :index
|
|
414
415
|
|
|
415
|
-
expect(@
|
|
416
|
-
expect(@
|
|
416
|
+
expect(@exceptions.first).to be_a(Scimitar::ErrorResponse)
|
|
417
|
+
expect(@exceptions.first.message).to eql('Only application/scim+json type is accepted.')
|
|
417
418
|
end
|
|
418
419
|
end
|
|
419
420
|
|
|
@@ -423,16 +424,16 @@ RSpec.describe Scimitar::ApplicationController do
|
|
|
423
424
|
request.headers['User-Agent' ] = 'Google-Auto-Provisioning'
|
|
424
425
|
get :index
|
|
425
426
|
|
|
426
|
-
expect(@
|
|
427
|
-
expect(@
|
|
427
|
+
expect(@exceptions.first).to be_a(RuntimeError)
|
|
428
|
+
expect(@exceptions.first.message).to eql('Bang')
|
|
428
429
|
end
|
|
429
430
|
|
|
430
431
|
it 'is invoked early for unrecognised agents' do
|
|
431
432
|
request.headers['Content-Type'] = 'application/json'
|
|
432
433
|
get :index
|
|
433
434
|
|
|
434
|
-
expect(@
|
|
435
|
-
expect(@
|
|
435
|
+
expect(@exceptions.first).to be_a(Scimitar::ErrorResponse)
|
|
436
|
+
expect(@exceptions.first.message).to eql('Only application/scim+json type is accepted.')
|
|
436
437
|
end
|
|
437
438
|
end # "context 'and with Google SCIM calls' do"
|
|
438
439
|
|
|
@@ -459,8 +460,8 @@ RSpec.describe Scimitar::ApplicationController do
|
|
|
459
460
|
request.headers['Content-Type'] = 'application/json+success'
|
|
460
461
|
get :index
|
|
461
462
|
|
|
462
|
-
expect(@
|
|
463
|
-
expect(@
|
|
463
|
+
expect(@exceptions.first).to be_a(RuntimeError)
|
|
464
|
+
expect(@exceptions.first.message).to eql('Bang')
|
|
464
465
|
|
|
465
466
|
expect(request.format == :scim).to eql(true)
|
|
466
467
|
expect(request.headers['CONTENT_TYPE']).to eql('application/scim+json')
|
|
@@ -472,8 +473,8 @@ RSpec.describe Scimitar::ApplicationController do
|
|
|
472
473
|
request.headers['Content-Type'] = 'application/json+preserve'
|
|
473
474
|
get :index
|
|
474
475
|
|
|
475
|
-
expect(@
|
|
476
|
-
expect(@
|
|
476
|
+
expect(@exceptions.first).to be_a(RuntimeError)
|
|
477
|
+
expect(@exceptions.first.message).to eql('Bang')
|
|
477
478
|
|
|
478
479
|
expect(request.format == :html).to eql(true)
|
|
479
480
|
expect(request.headers['CONTENT_TYPE']).to eql('application/json+preserve')
|
|
@@ -485,11 +486,28 @@ RSpec.describe Scimitar::ApplicationController do
|
|
|
485
486
|
request.headers['Content-Type'] = 'application/json+fail'
|
|
486
487
|
get :index
|
|
487
488
|
|
|
488
|
-
expect(@
|
|
489
|
-
expect(@
|
|
489
|
+
expect(@exceptions.first).to be_a(Scimitar::ErrorResponse)
|
|
490
|
+
expect(@exceptions.first.message).to eql('Only application/scim+json type is accepted.')
|
|
490
491
|
end
|
|
491
492
|
end # "context 'returning "fail"' do"
|
|
492
493
|
end # "context 'and with a custom request sanitizer' do"
|
|
494
|
+
|
|
495
|
+
context 'evaluated in controller context' do
|
|
496
|
+
it 'has access to controller methods like request and params' do
|
|
497
|
+
reported_request_method = nil
|
|
498
|
+
reported_params = nil
|
|
499
|
+
Scimitar.engine_configuration.exception_reporter = Proc.new do | exception |
|
|
500
|
+
reported_request_method = request.method
|
|
501
|
+
reported_params = params
|
|
502
|
+
end
|
|
503
|
+
|
|
504
|
+
get :index, params: { format: :scim, foo: 'bar' }
|
|
505
|
+
|
|
506
|
+
expect(reported_request_method).to eql('GET')
|
|
507
|
+
expect(reported_params['format']).to eql('scim')
|
|
508
|
+
expect(reported_params['foo']).to eql('bar')
|
|
509
|
+
end
|
|
510
|
+
end
|
|
493
511
|
end # "context 'exception reporter' do"
|
|
494
512
|
end # "context 'error handling' do"
|
|
495
513
|
end
|
|
@@ -34,12 +34,17 @@ RSpec.describe Scimitar::Lists::Count do
|
|
|
34
34
|
expect { @instance.limit = 'A' }.to raise_error(RuntimeError)
|
|
35
35
|
end
|
|
36
36
|
|
|
37
|
-
it '
|
|
38
|
-
expect { @instance.limit = '0' }.
|
|
37
|
+
it 'allows count=0 per SCIM 2.0 specification (RFC 7644)' do
|
|
38
|
+
expect { @instance.limit = '0' }.to_not raise_error
|
|
39
|
+
expect(@instance.limit).to eql(0)
|
|
39
40
|
end
|
|
40
41
|
|
|
41
|
-
it '
|
|
42
|
+
it 'allows count=0 as integer' do
|
|
43
|
+
expect { @instance.limit = 0 }.to_not raise_error
|
|
44
|
+
expect(@instance.limit).to eql(0)
|
|
45
|
+
end
|
|
42
46
|
|
|
47
|
+
it 'complains about attempts to set negative values' do
|
|
43
48
|
expect { @instance.limit = '-10' }.to raise_error(RuntimeError)
|
|
44
49
|
end
|
|
45
50
|
end # "context 'on-read error checking' do"
|
|
@@ -267,7 +267,7 @@ RSpec.describe Scimitar::Resources::Mixin do
|
|
|
267
267
|
instance.first_name = 'Foo'
|
|
268
268
|
instance.last_name = 'Bar'
|
|
269
269
|
instance.work_email_address = 'foo.bar@test.com'
|
|
270
|
-
instance.home_email_address =
|
|
270
|
+
instance.home_email_address = 'foo.bar@example.com'
|
|
271
271
|
instance.work_phone_number = '+642201234567'
|
|
272
272
|
instance.organization = 'SOMEORG'
|
|
273
273
|
|
|
@@ -299,6 +299,49 @@ RSpec.describe Scimitar::Resources::Mixin do
|
|
|
299
299
|
'urn:ietf:params:scim:schemas:extension:manager:1.0:User' => {},
|
|
300
300
|
})
|
|
301
301
|
end
|
|
302
|
+
|
|
303
|
+
it 'hides "nil" value attributes' do
|
|
304
|
+
uuid = SecureRandom.uuid
|
|
305
|
+
|
|
306
|
+
instance = MockUser.new
|
|
307
|
+
instance.primary_key = uuid
|
|
308
|
+
instance.scim_uid = 'AA02984'
|
|
309
|
+
instance.username = nil
|
|
310
|
+
instance.password = 'correcthorsebatterystaple'
|
|
311
|
+
instance.first_name = nil
|
|
312
|
+
instance.last_name = 'Bar'
|
|
313
|
+
instance.work_email_address = 'foo.bar@test.com'
|
|
314
|
+
instance.home_email_address = 'foo.bar@example.com'
|
|
315
|
+
instance.work_phone_number = '+642201234567'
|
|
316
|
+
instance.organization = 'SOMEORG'
|
|
317
|
+
|
|
318
|
+
g1 = MockGroup.create!(display_name: 'Group 1')
|
|
319
|
+
g2 = MockGroup.create!(display_name: 'Group 2')
|
|
320
|
+
g3 = MockGroup.create!(display_name: 'Group 3')
|
|
321
|
+
|
|
322
|
+
g1.mock_users << instance
|
|
323
|
+
g3.mock_users << instance
|
|
324
|
+
|
|
325
|
+
scim = instance.to_scim(location: "https://test.com/mock_users/#{uuid}", include_attributes: %w[id userName name groups.display groups.value organization])
|
|
326
|
+
json = scim.to_json()
|
|
327
|
+
hash = JSON.parse(json)
|
|
328
|
+
|
|
329
|
+
expect(hash).to eql({
|
|
330
|
+
'id' => uuid,
|
|
331
|
+
'name' => {'familyName'=>'Bar'},
|
|
332
|
+
'groups' => [{'display'=>g1.display_name, 'value'=>g1.id.to_s}, {'display'=>g3.display_name, 'value'=>g3.id.to_s}],
|
|
333
|
+
'meta' => {'location'=>"https://test.com/mock_users/#{uuid}", 'resourceType'=>'User'},
|
|
334
|
+
'schemas' => [
|
|
335
|
+
'urn:ietf:params:scim:schemas:core:2.0:User',
|
|
336
|
+
'urn:ietf:params:scim:schemas:extension:enterprise:2.0:User',
|
|
337
|
+
'urn:ietf:params:scim:schemas:extension:manager:1.0:User',
|
|
338
|
+
],
|
|
339
|
+
'urn:ietf:params:scim:schemas:extension:enterprise:2.0:User' => {
|
|
340
|
+
'organization' => 'SOMEORG',
|
|
341
|
+
},
|
|
342
|
+
'urn:ietf:params:scim:schemas:extension:manager:1.0:User' => {},
|
|
343
|
+
})
|
|
344
|
+
end
|
|
302
345
|
end # "context 'with list of requested attributes' do"
|
|
303
346
|
|
|
304
347
|
context 'with a UUID, renamed primary key column' do
|
|
@@ -332,7 +375,7 @@ RSpec.describe Scimitar::Resources::Mixin do
|
|
|
332
375
|
'userName' => 'foo',
|
|
333
376
|
'name' => {'givenName'=>'Foo', 'familyName'=>'Bar'},
|
|
334
377
|
'active' => true,
|
|
335
|
-
'emails' => [{'type'=>'work', 'primary'=>true, 'value'=>'foo.bar@test.com'}, {
|
|
378
|
+
'emails' => [{'type'=>'work', 'primary'=>true, 'value'=>'foo.bar@test.com'}, {'primary'=>false, 'type'=>'home', 'value'=>nil}],
|
|
336
379
|
'phoneNumbers'=> [{'type'=>'work', 'primary'=>false, 'value'=>'+642201234567'}],
|
|
337
380
|
'id' => uuid,
|
|
338
381
|
'externalId' => 'AA02984',
|
|
@@ -353,6 +396,71 @@ RSpec.describe Scimitar::Resources::Mixin do
|
|
|
353
396
|
},
|
|
354
397
|
})
|
|
355
398
|
end
|
|
399
|
+
|
|
400
|
+
context 'and when configured to omit "nil" values in the response' do
|
|
401
|
+
around :each do | example |
|
|
402
|
+
original_configuration = Scimitar.engine_configuration.render_mapped_nil_values_in_response
|
|
403
|
+
Scimitar.engine_configuration.render_mapped_nil_values_in_response = false
|
|
404
|
+
example.run()
|
|
405
|
+
ensure
|
|
406
|
+
Scimitar.engine_configuration.render_mapped_nil_values_in_response = original_configuration
|
|
407
|
+
end
|
|
408
|
+
|
|
409
|
+
it 'omits "nil" values as expected' do
|
|
410
|
+
uuid = SecureRandom.uuid
|
|
411
|
+
|
|
412
|
+
instance = MockUser.new
|
|
413
|
+
instance.primary_key = uuid
|
|
414
|
+
instance.scim_uid = 'AA02984'
|
|
415
|
+
instance.username = 'foo'
|
|
416
|
+
instance.password = 'correcthorsebatterystaple'
|
|
417
|
+
instance.first_name = nil
|
|
418
|
+
instance.last_name = 'Bar'
|
|
419
|
+
instance.work_email_address = 'foo.bar@test.com'
|
|
420
|
+
instance.home_email_address = nil
|
|
421
|
+
instance.work_phone_number = '+642201234567'
|
|
422
|
+
instance.organization = 'SOMEORG'
|
|
423
|
+
|
|
424
|
+
g1 = MockGroup.create!(display_name: 'Group 1')
|
|
425
|
+
g2 = MockGroup.create!(display_name: 'Group 2')
|
|
426
|
+
g3 = MockGroup.create!(display_name: 'Group 3')
|
|
427
|
+
|
|
428
|
+
g1.mock_users << instance
|
|
429
|
+
g3.mock_users << instance
|
|
430
|
+
|
|
431
|
+
scim = instance.to_scim(location: "https://test.com/mock_users/#{uuid}")
|
|
432
|
+
json = scim.to_json()
|
|
433
|
+
hash = JSON.parse(json)
|
|
434
|
+
|
|
435
|
+
# Note currently limited implementation for things like static maps,
|
|
436
|
+
# where part of the returned value is included; in this case, the
|
|
437
|
+
# "primary" value is "false" for e-mail of type "home", so the
|
|
438
|
+
# structure for that *does* appear in the output array even though
|
|
439
|
+
# the source dynamic data field from the NockUser instance is "nil".
|
|
440
|
+
#
|
|
441
|
+
expect(hash).to eql({
|
|
442
|
+
'userName' => 'foo',
|
|
443
|
+
'name' => {'familyName'=>'Bar'},
|
|
444
|
+
'active' => true,
|
|
445
|
+
'emails' => [{'type'=>'work', 'primary'=>true, 'value'=>'foo.bar@test.com'}, {'primary' => false, 'type' => 'home'}],
|
|
446
|
+
'phoneNumbers'=> [{'type'=>'work', 'primary'=>false, 'value'=>'+642201234567'}],
|
|
447
|
+
'id' => uuid,
|
|
448
|
+
'externalId' => 'AA02984',
|
|
449
|
+
'groups' => [{'display'=>g1.display_name, 'value'=>g1.id.to_s}, {'display'=>g3.display_name, 'value'=>g3.id.to_s}],
|
|
450
|
+
'meta' => {'location'=>"https://test.com/mock_users/#{uuid}", 'resourceType'=>'User'},
|
|
451
|
+
'schemas' => [
|
|
452
|
+
'urn:ietf:params:scim:schemas:core:2.0:User',
|
|
453
|
+
'urn:ietf:params:scim:schemas:extension:enterprise:2.0:User',
|
|
454
|
+
'urn:ietf:params:scim:schemas:extension:manager:1.0:User',
|
|
455
|
+
],
|
|
456
|
+
'urn:ietf:params:scim:schemas:extension:enterprise:2.0:User' => {
|
|
457
|
+
'organization' => 'SOMEORG',
|
|
458
|
+
'primaryEmail' => instance.work_email_address,
|
|
459
|
+
},
|
|
460
|
+
'urn:ietf:params:scim:schemas:extension:manager:1.0:User' => {}
|
|
461
|
+
})
|
|
462
|
+
end
|
|
463
|
+
end # "context 'and when configured to omit "nil" values in the response'" do"
|
|
356
464
|
end # "context 'with a UUID, renamed primary key column' do"
|
|
357
465
|
|
|
358
466
|
context 'with an integer, conventionally named primary key column' do
|
|
@@ -292,6 +292,92 @@ RSpec.describe Scimitar::ActiveRecordBackedResourcesController do
|
|
|
292
292
|
usernames = result['Resources'].map { |resource| resource['userName'] }
|
|
293
293
|
expect(usernames).to match_array(['2', '3'])
|
|
294
294
|
end
|
|
295
|
+
|
|
296
|
+
# SCIM 2.0 RFC 7644 Section 3.4.2.4: count=0 support
|
|
297
|
+
context 'with count=0' do
|
|
298
|
+
it 'returns 200 OK' do
|
|
299
|
+
get '/Users', params: {
|
|
300
|
+
format: :scim,
|
|
301
|
+
count: 0
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
expect(response.status).to eql(200)
|
|
305
|
+
expect(response.headers['Content-Type']).to eql('application/scim+json; charset=utf-8')
|
|
306
|
+
end
|
|
307
|
+
|
|
308
|
+
it 'returns totalResults with actual count' do
|
|
309
|
+
get '/Users', params: {
|
|
310
|
+
format: :scim,
|
|
311
|
+
count: 0
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
result = JSON.parse(response.body)
|
|
315
|
+
expect(result['totalResults']).to eql(3)
|
|
316
|
+
end
|
|
317
|
+
|
|
318
|
+
it 'returns itemsPerPage as 0' do
|
|
319
|
+
get '/Users', params: {
|
|
320
|
+
format: :scim,
|
|
321
|
+
count: 0
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
result = JSON.parse(response.body)
|
|
325
|
+
expect(result['itemsPerPage']).to eql(0)
|
|
326
|
+
end
|
|
327
|
+
|
|
328
|
+
it 'returns empty Resources array' do
|
|
329
|
+
get '/Users', params: {
|
|
330
|
+
format: :scim,
|
|
331
|
+
count: 0
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
result = JSON.parse(response.body)
|
|
335
|
+
expect(result['Resources']).to eql([])
|
|
336
|
+
end
|
|
337
|
+
|
|
338
|
+
it 'respects startIndex parameter' do
|
|
339
|
+
get '/Users', params: {
|
|
340
|
+
format: :scim,
|
|
341
|
+
count: 0,
|
|
342
|
+
startIndex: 5
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
result = JSON.parse(response.body)
|
|
346
|
+
expect(result['startIndex']).to eql(5)
|
|
347
|
+
end
|
|
348
|
+
|
|
349
|
+
it 'applies filters when calculating totalResults' do
|
|
350
|
+
get '/Users', params: {
|
|
351
|
+
format: :scim,
|
|
352
|
+
count: 0,
|
|
353
|
+
filter: 'name.familyName eq "Bar"'
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
result = JSON.parse(response.body)
|
|
357
|
+
expect(result['totalResults']).to eql(1)
|
|
358
|
+
expect(result['Resources']).to eql([])
|
|
359
|
+
end
|
|
360
|
+
|
|
361
|
+
it 'does not query for records (performance optimization)' do
|
|
362
|
+
# We should get the count but not fetch records
|
|
363
|
+
query_double = instance_double(ActiveRecord::Relation)
|
|
364
|
+
allow(MockUser).to receive(:all).and_return(query_double)
|
|
365
|
+
allow(query_double).to receive(:count).and_return(3)
|
|
366
|
+
|
|
367
|
+
# Should NOT call order, offset, limit, or to_a when count=0
|
|
368
|
+
expect(query_double).not_to receive(:order)
|
|
369
|
+
expect(query_double).not_to receive(:offset)
|
|
370
|
+
expect(query_double).not_to receive(:limit)
|
|
371
|
+
expect(query_double).not_to receive(:to_a)
|
|
372
|
+
|
|
373
|
+
get '/Users', params: {
|
|
374
|
+
format: :scim,
|
|
375
|
+
count: 0
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
expect(response.status).to eql(200)
|
|
379
|
+
end
|
|
380
|
+
end # "context 'with count=0' do"
|
|
295
381
|
end # "context 'with items' do"
|
|
296
382
|
|
|
297
383
|
context 'with bad calls' do
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: scimitar
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 2.
|
|
4
|
+
version: 2.15.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
|
-
- RIPA Global
|
|
8
7
|
- Andrew David Hodgkinson
|
|
8
|
+
- RIPA Global
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date:
|
|
11
|
+
date: 2026-03-06 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: rails
|
|
@@ -86,14 +86,14 @@ dependencies:
|
|
|
86
86
|
requirements:
|
|
87
87
|
- - "~>"
|
|
88
88
|
- !ruby/object:Gem::Version
|
|
89
|
-
version: '
|
|
89
|
+
version: '7.2'
|
|
90
90
|
type: :development
|
|
91
91
|
prerelease: false
|
|
92
92
|
version_requirements: !ruby/object:Gem::Requirement
|
|
93
93
|
requirements:
|
|
94
94
|
- - "~>"
|
|
95
95
|
- !ruby/object:Gem::Version
|
|
96
|
-
version: '
|
|
96
|
+
version: '7.2'
|
|
97
97
|
- !ruby/object:Gem::Dependency
|
|
98
98
|
name: warden
|
|
99
99
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -296,7 +296,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
296
296
|
- !ruby/object:Gem::Version
|
|
297
297
|
version: '0'
|
|
298
298
|
requirements: []
|
|
299
|
-
rubygems_version:
|
|
299
|
+
rubygems_version: 4.0.3
|
|
300
300
|
specification_version: 4
|
|
301
301
|
summary: SCIM v2 for Rails
|
|
302
302
|
test_files:
|