scimitar 1.1.0 → 1.3.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/active_record_backed_resources_controller.rb +2 -2
- data/app/controllers/scimitar/application_controller.rb +29 -9
- 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/resources/base.rb +1 -1
- 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 +70 -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
- data/spec/requests/active_record_backed_resources_controller_spec.rb +1 -0
- data/spec/requests/application_controller_spec.rb +6 -3
- metadata +11 -8
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 742d9f1195cc1ec3c86c17e2ca731119413925f3b5e0a3bd67fc90bf7183c2dd
|
4
|
+
data.tar.gz: c4e90cbd8d59eb564deebfd0278e96f2f1896b9fa6c0eb8697f1975115e2cf47
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b1df7b83b8adaf904891983a1059a690ed7435917c8ae409a81254a2b13314e35ce45d630aecc917165c527314cd140913ce4d31494da4a8ca54c65f8b7f6d6f
|
7
|
+
data.tar.gz: 2855fcbe539ae73ff31fbae32a7e4841a31be1c48c39eae94ae34b9ab96341fdb17e53ee82d42c318a5bf928143d39181461a9bbb86d435ecb37e4fdda6876da
|
@@ -131,8 +131,8 @@ module Scimitar
|
|
131
131
|
raise NotImplementedError
|
132
132
|
end
|
133
133
|
|
134
|
-
# Find a
|
135
|
-
#
|
134
|
+
# Find a record by ID. Subclasses can override this if they need special
|
135
|
+
# lookup behaviour.
|
136
136
|
#
|
137
137
|
# +record_id+:: Record ID (SCIM schema 'id' value - "our" ID).
|
138
138
|
#
|
@@ -25,10 +25,12 @@ module Scimitar
|
|
25
25
|
#
|
26
26
|
# ...to "globally" invoke this handler if you wish.
|
27
27
|
#
|
28
|
-
# +_exception+:: Exception instance (currently unused).
|
29
28
|
#
|
30
|
-
|
31
|
-
|
29
|
+
# +exception+:: Exception instance, used for a configured error reporter
|
30
|
+
# via #handle_scim_error (if present).
|
31
|
+
#
|
32
|
+
def handle_resource_not_found(exception)
|
33
|
+
handle_scim_error(NotFoundError.new(params[:id]), exception)
|
32
34
|
end
|
33
35
|
|
34
36
|
# This base controller uses:
|
@@ -38,9 +40,22 @@ module Scimitar
|
|
38
40
|
# ...to "globally" invoke this handler for all Scimitar errors (including
|
39
41
|
# subclasses).
|
40
42
|
#
|
43
|
+
# Mandatory parameters are:
|
44
|
+
#
|
41
45
|
# +error_response+:: Scimitar::ErrorResponse (or subclass) instance.
|
42
46
|
#
|
43
|
-
|
47
|
+
# Optional parameters are:
|
48
|
+
#
|
49
|
+
# *exception+:: If a Ruby exception was the reason this method is being
|
50
|
+
# called, pass it here. Any configured exception reporting
|
51
|
+
# mechanism will be invokved with the given parameter.
|
52
|
+
# Otherwise, the +error_response+ value is reported.
|
53
|
+
#
|
54
|
+
def handle_scim_error(error_response, exception = error_response)
|
55
|
+
unless Scimitar.engine_configuration.exception_reporter.nil?
|
56
|
+
Scimitar.engine_configuration.exception_reporter.call(exception)
|
57
|
+
end
|
58
|
+
|
44
59
|
render json: error_response, status: error_response.status
|
45
60
|
end
|
46
61
|
|
@@ -55,7 +70,7 @@ module Scimitar
|
|
55
70
|
# +exception+:: Exception instance.
|
56
71
|
#
|
57
72
|
def handle_bad_json_error(exception)
|
58
|
-
handle_scim_error(ErrorResponse.new(status: 400, detail: "Invalid JSON - #{exception.message}"))
|
73
|
+
handle_scim_error(ErrorResponse.new(status: 400, detail: "Invalid JSON - #{exception.message}"), exception)
|
59
74
|
end
|
60
75
|
|
61
76
|
# This base controller uses:
|
@@ -68,7 +83,7 @@ module Scimitar
|
|
68
83
|
#
|
69
84
|
def handle_unexpected_error(exception)
|
70
85
|
Rails.logger.error("#{exception.message}\n#{exception.backtrace}")
|
71
|
-
handle_scim_error(ErrorResponse.new(status: 500, detail: exception.message))
|
86
|
+
handle_scim_error(ErrorResponse.new(status: 500, detail: exception.message), exception)
|
72
87
|
end
|
73
88
|
|
74
89
|
# =========================================================================
|
@@ -82,12 +97,17 @@ module Scimitar
|
|
82
97
|
# request and subclass processing.
|
83
98
|
#
|
84
99
|
def require_scim
|
85
|
-
|
100
|
+
scim_mime_type = Mime::Type.lookup_by_extension(:scim).to_s
|
101
|
+
|
102
|
+
if request.content_type.nil?
|
103
|
+
request.format = :scim
|
104
|
+
request.headers['CONTENT_TYPE'] = scim_mime_type
|
105
|
+
elsif request.content_type&.downcase == scim_mime_type
|
86
106
|
request.format = :scim
|
87
107
|
elsif request.format == :scim
|
88
|
-
request.headers['CONTENT_TYPE'] =
|
108
|
+
request.headers['CONTENT_TYPE'] = scim_mime_type
|
89
109
|
else
|
90
|
-
handle_scim_error(ErrorResponse.new(status: 406, detail: "Only #{
|
110
|
+
handle_scim_error(ErrorResponse.new(status: 406, detail: "Only #{scim_mime_type} type is accepted."))
|
91
111
|
end
|
92
112
|
end
|
93
113
|
|
@@ -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
|
+
# Originally Scimitar used attribute "detail" for 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 []
|
@@ -138,7 +138,7 @@ module Scimitar
|
|
138
138
|
end
|
139
139
|
|
140
140
|
def as_json(options = {})
|
141
|
-
self.meta = Meta.new unless self.meta
|
141
|
+
self.meta = Meta.new unless self.meta && self.meta.is_a?(Meta)
|
142
142
|
meta.resourceType = self.class.resource_type_id
|
143
143
|
original_hash = super(options).except('errors')
|
144
144
|
original_hash.merge!('schemas' => self.class.schemas.map(&:id))
|
@@ -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'),
|
@@ -79,4 +79,24 @@ Scimitar.engine_configuration = Scimitar::EngineConfiguration.new({
|
|
79
79
|
# Note that both basic and token authentication can be declared, with the
|
80
80
|
# parameters in the inbound HTTP request determining which is invoked.
|
81
81
|
|
82
|
+
# Scimitar rescues certain error cases and exceptions, in order to return a
|
83
|
+
# JSON response to the API caller. If you want exceptions to also be
|
84
|
+
# reported to a third party system such as sentry.io or raygun.com, you can
|
85
|
+
# configure a Proc to do so. It is passed a Ruby exception subclass object.
|
86
|
+
# For example, a minimal sentry.io reporter might do this:
|
87
|
+
#
|
88
|
+
# exception_reporter: Proc.new do | exception |
|
89
|
+
# Sentry.capture_exception(exception)
|
90
|
+
# end
|
91
|
+
#
|
92
|
+
# You will still need to configure your reporting system according to its
|
93
|
+
# documentation (e.g. via a Rails "config/initializers/<foo>.rb" file).
|
94
|
+
|
95
|
+
# Scimilar treats "VDTP" (Value, Display, Type, Primary) attribute values,
|
96
|
+
# used for e.g. e-mail addresses or phone numbers, as required by default.
|
97
|
+
# If you encounter a service which calls these with e.g. "null" value data,
|
98
|
+
# you can configure all values to be optional. You'll need to deal with
|
99
|
+
# whatever that means for you receiving system in your model code.
|
100
|
+
#
|
101
|
+
# optional_value_fields_required: false
|
82
102
|
})
|
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.
|
6
|
+
VERSION = '1.3.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 = '2022-07-14'
|
12
12
|
|
13
13
|
end
|
@@ -169,5 +169,74 @@ 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
|
+
begin
|
212
|
+
raise 'Hello'
|
213
|
+
rescue
|
214
|
+
raise ActionDispatch::Http::Parameters::ParseError
|
215
|
+
end
|
216
|
+
end
|
217
|
+
end
|
218
|
+
|
219
|
+
it 'is invoked' do
|
220
|
+
get :index, params: { format: :scim }
|
221
|
+
|
222
|
+
expect(@exception).to be_a(ActionDispatch::Http::Parameters::ParseError)
|
223
|
+
expect(@exception.message).to eql('Hello')
|
224
|
+
end
|
225
|
+
end
|
226
|
+
|
227
|
+
context 'and a bad content type' do
|
228
|
+
controller do
|
229
|
+
def index; end
|
230
|
+
end
|
231
|
+
|
232
|
+
it 'is invoked' do
|
233
|
+
request.headers['Content-Type'] = 'text/plain'
|
234
|
+
get :index
|
235
|
+
|
236
|
+
expect(@exception).to be_a(Scimitar::ErrorResponse)
|
237
|
+
expect(@exception.message).to eql('Only application/scim+json type is accepted.')
|
238
|
+
end
|
239
|
+
end
|
240
|
+
end # "context 'exception reporter' do"
|
241
|
+
end # "context 'error handling' do"
|
173
242
|
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,
|
@@ -11,11 +11,14 @@ RSpec.describe Scimitar::ApplicationController do
|
|
11
11
|
end
|
12
12
|
|
13
13
|
context 'format handling' do
|
14
|
-
it 'renders "
|
14
|
+
it 'renders "OK" if the request does not provide any Content-Type value' do
|
15
15
|
get '/CustomRequestVerifiers', params: { format: :html }
|
16
16
|
|
17
|
-
expect(response).to have_http_status(:
|
18
|
-
|
17
|
+
expect(response).to have_http_status(:ok)
|
18
|
+
parsed_body = JSON.parse(response.body)
|
19
|
+
expect(parsed_body['request']['is_scim' ]).to eql(true)
|
20
|
+
expect(parsed_body['request']['format' ]).to eql('application/scim+json')
|
21
|
+
expect(parsed_body['request']['content_type']).to eql('application/scim+json')
|
19
22
|
end
|
20
23
|
|
21
24
|
it 'renders 400 if given bad JSON' do
|
metadata
CHANGED
@@ -1,15 +1,15 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: scimitar
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
|
-
-
|
7
|
+
- RIPA Global
|
8
8
|
- Andrew David Hodgkinson
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date:
|
12
|
+
date: 2022-07-14 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: rails
|
@@ -115,17 +115,17 @@ dependencies:
|
|
115
115
|
requirements:
|
116
116
|
- - "~>"
|
117
117
|
- !ruby/object:Gem::Version
|
118
|
-
version: '1.
|
118
|
+
version: '1.2'
|
119
119
|
type: :development
|
120
120
|
prerelease: false
|
121
121
|
version_requirements: !ruby/object:Gem::Requirement
|
122
122
|
requirements:
|
123
123
|
- - "~>"
|
124
124
|
- !ruby/object:Gem::Version
|
125
|
-
version: '1.
|
125
|
+
version: '1.2'
|
126
126
|
description: SCIM v2 support for Users and Groups in Ruby On Rails
|
127
127
|
email:
|
128
|
-
- dev@
|
128
|
+
- dev@ripaglobal.com
|
129
129
|
executables: []
|
130
130
|
extensions: []
|
131
131
|
extra_rdoc_files: []
|
@@ -237,11 +237,14 @@ files:
|
|
237
237
|
- spec/spec_helper.rb
|
238
238
|
- spec/spec_helper_spec.rb
|
239
239
|
- spec/support/hash_with_indifferent_case_insensitive_access_spec.rb
|
240
|
-
homepage: https://
|
240
|
+
homepage: https://www.ripaglobal.com/
|
241
241
|
licenses:
|
242
242
|
- MIT
|
243
243
|
metadata:
|
244
|
-
|
244
|
+
homepage_uri: https://www.ripaglobal.com/
|
245
|
+
source_code_uri: https://github.com/RIPAGlobal/scimitar/
|
246
|
+
bug_tracker_uri: https://github.com/RIPAGlobal/scimitar/issues/
|
247
|
+
changelog_uri: https://github.com/RIPAGlobal/scimitar/blob/master/CHANGELOG.md
|
245
248
|
post_install_message:
|
246
249
|
rdoc_options: []
|
247
250
|
require_paths:
|