scimitar 2.0.2 → 2.1.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/application_controller.rb +20 -6
- data/app/models/scimitar/engine_configuration.rb +7 -3
- data/app/models/scimitar/error_response.rb +12 -0
- data/app/models/scimitar/lists/query_parser.rb +3 -3
- data/app/models/scimitar/schema/address.rb +1 -0
- data/app/models/scimitar/schema/vdtp.rb +1 -1
- data/config/initializers/scimitar.rb +20 -0
- data/lib/scimitar/version.rb +2 -2
- data/spec/controllers/scimitar/application_controller_spec.rb +66 -1
- data/spec/models/scimitar/complex_types/email_spec.rb +0 -2
- data/spec/models/scimitar/resources/base_validation_spec.rb +27 -2
- data/spec/models/scimitar/schema/base_spec.rb +1 -1
- data/spec/models/scimitar/schema/user_spec.rb +10 -0
- metadata +3 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d10a10d763c621e797fbe343a113a8610f9317a06455f177c60c42062663b4da
|
4
|
+
data.tar.gz: 251c1b73b7dd51ded63d85f7b0816163a9bd567099db9ce5f57e4929301d7a49
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 77accce401fb9f6fbb91a0ebf53294754286867cc2becce4f8acccbbd35f90c9062174d1da0e0ad4c80a92acc053c4a0b30898260fe95dc580bf3df33453f975
|
7
|
+
data.tar.gz: 9e64bad4582b54c988f612f9a0920b291431a79f62f9656bcad2658c5a0ded291e9cf96f47cac5f2b130c47a30edbf3b68b6f8abe96ca9cb8a7bb8dcaea83bd2
|
@@ -25,10 +25,11 @@ module Scimitar
|
|
25
25
|
#
|
26
26
|
# ...to "globally" invoke this handler if you wish.
|
27
27
|
#
|
28
|
-
# +_exception+:: Exception instance
|
28
|
+
# +_exception+:: Exception instance, used for a configured error reporter
|
29
|
+
# via #handle_scim_error (if present).
|
29
30
|
#
|
30
|
-
def handle_resource_not_found(
|
31
|
-
handle_scim_error(NotFoundError.new(params[:id]))
|
31
|
+
def handle_resource_not_found(exception)
|
32
|
+
handle_scim_error(NotFoundError.new(params[:id]), exception)
|
32
33
|
end
|
33
34
|
|
34
35
|
# This base controller uses:
|
@@ -38,9 +39,22 @@ module Scimitar
|
|
38
39
|
# ...to "globally" invoke this handler for all Scimitar errors (including
|
39
40
|
# subclasses).
|
40
41
|
#
|
42
|
+
# Mandatory parameters are:
|
43
|
+
#
|
41
44
|
# +error_response+:: Scimitar::ErrorResponse (or subclass) instance.
|
42
45
|
#
|
43
|
-
|
46
|
+
# Optional parameters are:
|
47
|
+
#
|
48
|
+
# *exception+:: If a Ruby exception was the reason this method is being
|
49
|
+
# called, pass it here. Any configured exception reporting
|
50
|
+
# mechanism will be invokved with the given parameter.
|
51
|
+
# Otherwise, the +error_response+ value is reported.
|
52
|
+
#
|
53
|
+
def handle_scim_error(error_response, exception = error_response)
|
54
|
+
unless Scimitar.engine_configuration.exception_reporter.nil?
|
55
|
+
Scimitar.engine_configuration.exception_reporter.call(exception)
|
56
|
+
end
|
57
|
+
|
44
58
|
render json: error_response, status: error_response.status
|
45
59
|
end
|
46
60
|
|
@@ -55,7 +69,7 @@ module Scimitar
|
|
55
69
|
# +exception+:: Exception instance.
|
56
70
|
#
|
57
71
|
def handle_bad_json_error(exception)
|
58
|
-
handle_scim_error(ErrorResponse.new(status: 400, detail: "Invalid JSON - #{exception.message}"))
|
72
|
+
handle_scim_error(ErrorResponse.new(status: 400, detail: "Invalid JSON - #{exception.message}"), exception)
|
59
73
|
end
|
60
74
|
|
61
75
|
# This base controller uses:
|
@@ -68,7 +82,7 @@ module Scimitar
|
|
68
82
|
#
|
69
83
|
def handle_unexpected_error(exception)
|
70
84
|
Rails.logger.error("#{exception.message}\n#{exception.backtrace}")
|
71
|
-
handle_scim_error(ErrorResponse.new(status: 500, detail: exception.message))
|
85
|
+
handle_scim_error(ErrorResponse.new(status: 500, detail: exception.message), exception)
|
72
86
|
end
|
73
87
|
|
74
88
|
# =========================================================================
|
@@ -9,13 +9,17 @@ module Scimitar
|
|
9
9
|
|
10
10
|
attr_accessor :basic_authenticator,
|
11
11
|
:token_authenticator,
|
12
|
-
:application_controller_mixin
|
12
|
+
:application_controller_mixin,
|
13
|
+
:exception_reporter,
|
14
|
+
:optional_value_fields_required
|
13
15
|
|
14
16
|
def initialize(attributes = {})
|
15
17
|
|
16
|
-
#
|
18
|
+
# Set defaults that may be overridden by the initializer.
|
17
19
|
#
|
18
|
-
defaults = {
|
20
|
+
defaults = {
|
21
|
+
optional_value_fields_required: true
|
22
|
+
}
|
19
23
|
|
20
24
|
super(defaults.merge(attributes))
|
21
25
|
end
|
@@ -16,5 +16,17 @@ module Scimitar
|
|
16
16
|
data['scimType'] = scimType if scimType
|
17
17
|
data
|
18
18
|
end
|
19
|
+
|
20
|
+
# From v1, Scimitar used attribute "detail" for the exception text; it was
|
21
|
+
# only for JSON responses at the time, but in hindsight was a bad choice.
|
22
|
+
# It should have been "message" given inheritance from StandardError, which
|
23
|
+
# then works properly with e.g. error reporting services.
|
24
|
+
#
|
25
|
+
# The "detail" attribute is still present, for backwards compatibility with
|
26
|
+
# any client code that might be using this class.
|
27
|
+
#
|
28
|
+
def message
|
29
|
+
self.detail
|
30
|
+
end
|
19
31
|
end
|
20
32
|
end
|
@@ -633,7 +633,7 @@ module Scimitar
|
|
633
633
|
when 'pr'
|
634
634
|
arel_table.grouping(arel_column.not_eq_all(['', nil]))
|
635
635
|
else
|
636
|
-
raise Scimitar::FilterError
|
636
|
+
raise Scimitar::FilterError.new("Unsupported operator: '#{scim_operator}'")
|
637
637
|
end
|
638
638
|
|
639
639
|
if index == 0
|
@@ -656,10 +656,10 @@ module Scimitar
|
|
656
656
|
# +scim_attribute+:: SCIM attribute from a filter string.
|
657
657
|
#
|
658
658
|
def activerecord_columns(scim_attribute)
|
659
|
-
raise Scimitar::FilterError if scim_attribute.blank?
|
659
|
+
raise Scimitar::FilterError.new("No scim_attribute provided") if scim_attribute.blank?
|
660
660
|
|
661
661
|
mapped_attribute = self.attribute_map()[scim_attribute]
|
662
|
-
raise Scimitar::FilterError if mapped_attribute.blank?
|
662
|
+
raise Scimitar::FilterError.new("Unable to find domain attribute from SCIM attribute: '#{scim_attribute}'") if mapped_attribute.blank?
|
663
663
|
|
664
664
|
if mapped_attribute[:ignore]
|
665
665
|
return []
|
@@ -10,6 +10,7 @@ module Scimitar
|
|
10
10
|
def self.scim_attributes
|
11
11
|
@scim_attributes ||= [
|
12
12
|
Attribute.new(name: 'type', type: 'string'),
|
13
|
+
Attribute.new(name: 'primary', type: 'boolean'),
|
13
14
|
Attribute.new(name: 'formatted', type: 'string'),
|
14
15
|
Attribute.new(name: 'streetAddress', type: 'string'),
|
15
16
|
Attribute.new(name: 'locality', type: 'string'),
|
@@ -7,7 +7,7 @@ module Scimitar
|
|
7
7
|
class Vdtp < Base
|
8
8
|
def self.scim_attributes
|
9
9
|
@scim_attributes ||= [
|
10
|
-
Attribute.new(name: 'value', type: 'string', required:
|
10
|
+
Attribute.new(name: 'value', type: 'string', required: Scimitar.engine_configuration.optional_value_fields_required),
|
11
11
|
Attribute.new(name: 'display', type: 'string', mutability: 'readOnly'),
|
12
12
|
Attribute.new(name: 'type', type: 'string'),
|
13
13
|
Attribute.new(name: 'primary', type: 'boolean'),
|
@@ -81,6 +81,26 @@ Rails.application.config.to_prepare do # (required for >= Rails 7 / Zeitwerk)
|
|
81
81
|
# Note that both basic and token authentication can be declared, with the
|
82
82
|
# parameters in the inbound HTTP request determining which is invoked.
|
83
83
|
|
84
|
+
# Scimitar rescues certain error cases and exceptions, in order to return a
|
85
|
+
# JSON response to the API caller. If you want exceptions to also be
|
86
|
+
# reported to a third party system such as sentry.io or raygun.com, you can
|
87
|
+
# configure a Proc to do so. It is passed a Ruby exception subclass object.
|
88
|
+
# For example, a minimal sentry.io reporter might do this:
|
89
|
+
#
|
90
|
+
# exception_reporter: Proc.new do | exception |
|
91
|
+
# Sentry.capture_exception(exception)
|
92
|
+
# end
|
93
|
+
#
|
94
|
+
# You will still need to configure your reporting system according to its
|
95
|
+
# documentation (e.g. via a Rails "config/initializers/<foo>.rb" file).
|
96
|
+
|
97
|
+
# Scimilar treats "VDTP" (Value, Display, Type, Primary) attribute values,
|
98
|
+
# used for e.g. e-mail addresses or phone numbers, as required by default.
|
99
|
+
# If you encounter a service which calls these with e.g. "null" value data,
|
100
|
+
# you can configure all values to be optional. You'll need to deal with
|
101
|
+
# whatever that means for you receiving system in your model code.
|
102
|
+
#
|
103
|
+
# optional_value_fields_required: false
|
84
104
|
})
|
85
105
|
|
86
106
|
end
|
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.0
|
6
|
+
VERSION = '2.1.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 = '2022-
|
11
|
+
DATE = '2022-07-14'
|
12
12
|
|
13
13
|
end
|
@@ -169,5 +169,70 @@ RSpec.describe Scimitar::ApplicationController do
|
|
169
169
|
expect(parsed_body).to include('status' => '500')
|
170
170
|
expect(parsed_body).to include('detail' => 'Bang')
|
171
171
|
end
|
172
|
-
|
172
|
+
|
173
|
+
context 'with an exception reporter' do
|
174
|
+
around :each do | example |
|
175
|
+
original_configuration = Scimitar.engine_configuration.exception_reporter
|
176
|
+
Scimitar.engine_configuration.exception_reporter = Proc.new do | exception |
|
177
|
+
@exception = exception
|
178
|
+
end
|
179
|
+
example.run()
|
180
|
+
ensure
|
181
|
+
Scimitar.engine_configuration.exception_reporter = original_configuration
|
182
|
+
end
|
183
|
+
|
184
|
+
context 'and "internal server error"' do
|
185
|
+
it 'is invoked' do
|
186
|
+
get :index, params: { format: :scim }
|
187
|
+
|
188
|
+
expect(@exception).to be_a(RuntimeError)
|
189
|
+
expect(@exception.message).to eql('Bang')
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
193
|
+
context 'and "not found"' do
|
194
|
+
controller do
|
195
|
+
def index
|
196
|
+
handle_resource_not_found(ActiveRecord::RecordNotFound.new(42))
|
197
|
+
end
|
198
|
+
end
|
199
|
+
|
200
|
+
it 'is invoked' do
|
201
|
+
get :index, params: { format: :scim }
|
202
|
+
|
203
|
+
expect(@exception).to be_a(ActiveRecord::RecordNotFound)
|
204
|
+
expect(@exception.message).to eql('42')
|
205
|
+
end
|
206
|
+
end
|
207
|
+
|
208
|
+
context 'and bad JSON' do
|
209
|
+
controller do
|
210
|
+
def index
|
211
|
+
raise ActionDispatch::Http::Parameters::ParseError.new("Hello")
|
212
|
+
end
|
213
|
+
end
|
214
|
+
|
215
|
+
it 'is invoked' do
|
216
|
+
get :index, params: { format: :scim }
|
217
|
+
|
218
|
+
expect(@exception).to be_a(ActionDispatch::Http::Parameters::ParseError)
|
219
|
+
expect(@exception.message).to eql('Hello')
|
220
|
+
end
|
221
|
+
end
|
222
|
+
|
223
|
+
context 'and a bad content type' do
|
224
|
+
controller do
|
225
|
+
def index; end
|
226
|
+
end
|
227
|
+
|
228
|
+
it 'is invoked' do
|
229
|
+
request.headers['Content-Type'] = 'text/plain'
|
230
|
+
get :index
|
231
|
+
|
232
|
+
expect(@exception).to be_a(Scimitar::ErrorResponse)
|
233
|
+
expect(@exception.message).to eql('Only application/scim+json type is accepted.')
|
234
|
+
end
|
235
|
+
end
|
236
|
+
end # "context 'exception reporter' do"
|
237
|
+
end # "context 'error handling' do"
|
173
238
|
end
|
@@ -1,7 +1,6 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
3
|
RSpec.describe Scimitar::Resources::Base do
|
4
|
-
|
5
4
|
context '#valid?' do
|
6
5
|
MyCustomSchema = Class.new(Scimitar::Schema::Base) do
|
7
6
|
def self.id
|
@@ -21,6 +20,9 @@ RSpec.describe Scimitar::Resources::Base do
|
|
21
20
|
),
|
22
21
|
Scimitar::Schema::Attribute.new(
|
23
22
|
name: 'complexNames', complexType: Scimitar::ComplexTypes::Name, multiValued:true, required: false
|
23
|
+
),
|
24
|
+
Scimitar::Schema::Attribute.new(
|
25
|
+
name: 'vdtpTestByEmail', complexType: Scimitar::ComplexTypes::Email, required: false
|
24
26
|
)
|
25
27
|
]
|
26
28
|
end
|
@@ -57,5 +59,28 @@ RSpec.describe Scimitar::Resources::Base do
|
|
57
59
|
expect(resource.valid?).to be(false)
|
58
60
|
expect(resource.errors.full_messages).to match_array(["Complexnames has to follow the complexType format.", "Complexnames familyname has the wrong type. It has to be a(n) string."])
|
59
61
|
end
|
60
|
-
|
62
|
+
|
63
|
+
context 'configuration of required values in VDTP schema' do
|
64
|
+
around :each do | example |
|
65
|
+
original_configuration = Scimitar.engine_configuration.optional_value_fields_required
|
66
|
+
Scimitar::Schema::Email.instance_variable_set('@scim_attributes', nil)
|
67
|
+
example.run()
|
68
|
+
ensure
|
69
|
+
Scimitar.engine_configuration.optional_value_fields_required = original_configuration
|
70
|
+
Scimitar::Schema::Email.instance_variable_set('@scim_attributes', nil)
|
71
|
+
end
|
72
|
+
|
73
|
+
it 'requires a value by default' do
|
74
|
+
resource = MyCustomResource.new(vdtpTestByEmail: { value: nil }, enforce: false)
|
75
|
+
expect(resource.valid?).to be(false)
|
76
|
+
expect(resource.errors.full_messages).to match_array(['Vdtptestbyemail value is required'])
|
77
|
+
end
|
78
|
+
|
79
|
+
it 'can be configured for optional values' do
|
80
|
+
Scimitar.engine_configuration.optional_value_fields_required = false
|
81
|
+
resource = MyCustomResource.new(vdtpTestByEmail: { value: nil }, enforce: false)
|
82
|
+
expect(resource.valid?).to be(true)
|
83
|
+
end
|
84
|
+
end # "context 'configuration of required values in VDTP schema' do"
|
85
|
+
end # "context '#valid?' do"
|
61
86
|
end
|
@@ -419,6 +419,16 @@ RSpec.describe Scimitar::Schema::User do
|
|
419
419
|
"name": "type",
|
420
420
|
"type": "string"
|
421
421
|
},
|
422
|
+
{
|
423
|
+
"multiValued": false,
|
424
|
+
"required": false,
|
425
|
+
"caseExact": false,
|
426
|
+
"mutability": "readWrite",
|
427
|
+
"uniqueness": "none",
|
428
|
+
"returned": "default",
|
429
|
+
"name": "primary",
|
430
|
+
"type": "boolean"
|
431
|
+
},
|
422
432
|
{
|
423
433
|
"multiValued": false,
|
424
434
|
"required": false,
|
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: 2.0
|
4
|
+
version: 2.1.0
|
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: 2022-
|
12
|
+
date: 2022-07-14 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: rails
|
@@ -260,7 +260,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
260
260
|
- !ruby/object:Gem::Version
|
261
261
|
version: '0'
|
262
262
|
requirements: []
|
263
|
-
rubygems_version: 3.3.
|
263
|
+
rubygems_version: 3.3.7
|
264
264
|
signing_key:
|
265
265
|
specification_version: 4
|
266
266
|
summary: SCIM v2 for Rails
|