scimitar 2.0.2 → 2.1.0

Sign up to get free protection for your applications and to get access to all the features.
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