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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8a60a7aa88fe30a83bdf337787203b276a39e456c9818653fd30cdc256102318
4
- data.tar.gz: 3d092d6ff249121c2414686f215c6530f0e4fc24addf921953f6d6010ead6d13
3
+ metadata.gz: d10a10d763c621e797fbe343a113a8610f9317a06455f177c60c42062663b4da
4
+ data.tar.gz: 251c1b73b7dd51ded63d85f7b0816163a9bd567099db9ce5f57e4929301d7a49
5
5
  SHA512:
6
- metadata.gz: 85ad5d6b4b1613eab67380772389e5789478d64adcca7116a054c71c879c0ada502312982027cbf3dbfa2c833aec5cfd12eae3d4946fd30289678909b2e2d87b
7
- data.tar.gz: dfd9259e908867af51bd751d559cc9a323861dad2b5313e295e92d7b527356fee39c6accaca3c26d88bed4fca006e98f01bc7954ae91ab8626316270c3c8c507
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 (currently unused).
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(_exception)
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
- def handle_scim_error(error_response)
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
- # No defaults yet - reserved for future use.
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: true),
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
@@ -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.2'
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-06-15'
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
- end
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
@@ -18,6 +18,4 @@ RSpec.describe Scimitar::ComplexTypes::Email do
18
18
  expect(described_class.new(value: 'a@b.c').as_json).to eq('value' => 'a@b.c')
19
19
  end
20
20
  end
21
-
22
21
  end
23
-
@@ -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
- end
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
@@ -11,7 +11,7 @@ RSpec.describe Scimitar::Schema::Base do
11
11
  end
12
12
 
13
13
  context '#initialize' do
14
- it 'creates a meta' do
14
+ it 'creates "meta"' do
15
15
  schema = described_class.new
16
16
  expect(schema.meta.resourceType).to eql('Schema')
17
17
  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.2
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-06-15 00:00:00.000000000 Z
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.3
263
+ rubygems_version: 3.3.7
264
264
  signing_key:
265
265
  specification_version: 4
266
266
  summary: SCIM v2 for Rails