scimitar 1.5.3 → 1.7.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 +22 -4
- data/app/controllers/scimitar/application_controller.rb +0 -1
- data/app/controllers/scimitar/schemas_controller.rb +5 -0
- data/app/models/scimitar/errors.rb +1 -1
- data/app/models/scimitar/lists/query_parser.rb +11 -6
- data/app/models/scimitar/resources/base.rb +13 -3
- data/app/models/scimitar/resources/mixin.rb +24 -9
- data/app/models/scimitar/schema/attribute.rb +15 -6
- data/app/models/scimitar/schema/base.rb +4 -2
- data/config/initializers/scimitar.rb +8 -3
- data/lib/scimitar/version.rb +2 -2
- data/lib/scimitar.rb +0 -12
- data/spec/apps/dummy/app/controllers/custom_save_mock_users_controller.rb +24 -0
- data/spec/apps/dummy/app/models/mock_user.rb +18 -1
- data/spec/apps/dummy/config/initializers/scimitar.rb +8 -0
- data/spec/apps/dummy/config/routes.rb +11 -6
- data/spec/apps/dummy/db/migrate/20210304014602_create_mock_users.rb +1 -0
- data/spec/apps/dummy/db/schema.rb +1 -0
- data/spec/controllers/scimitar/resource_types_controller_spec.rb +2 -2
- data/spec/controllers/scimitar/schemas_controller_spec.rb +8 -0
- data/spec/models/scimitar/lists/query_parser_spec.rb +68 -1
- data/spec/models/scimitar/resources/base_spec.rb +48 -5
- data/spec/models/scimitar/resources/mixin_spec.rb +12 -1
- data/spec/models/scimitar/resources/user_spec.rb +4 -4
- data/spec/models/scimitar/schema/attribute_spec.rb +22 -0
- data/spec/requests/active_record_backed_resources_controller_spec.rb +18 -4
- data/spec/requests/application_controller_spec.rb +1 -2
- metadata +5 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 415331a6848887b5279a7f2c36ba5406989d490104a9f14735baf270bbb4f40a
|
4
|
+
data.tar.gz: 0ae6da1d6530f5fa4e5bce62f7e4188439281d768ada5f94534c19b3da8f8db9
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a62b9ccb023fb73f16a6b98a6a457caa9e3b2fcd72d420b992c2798705d604c91a013e4606a7f9be6da3fa2ea4ce03a5b7292f181ef8ad3d794ec6e8beb9ef3b
|
7
|
+
data.tar.gz: 80ad36659de5ddd2e9b8b7f8fc3e74c0a663b0a00b01a812edf641352b9b9ee37e8cf010cc86233789a1488df34dd0c79c493aebadb3f06b315ae26a5c03e1ff
|
@@ -153,7 +153,13 @@ module Scimitar
|
|
153
153
|
# Save a record, dealing with validation exceptions by raising SCIM
|
154
154
|
# errors.
|
155
155
|
#
|
156
|
-
# +record+:: ActiveRecord subclass to save
|
156
|
+
# +record+:: ActiveRecord subclass to save.
|
157
|
+
#
|
158
|
+
# If you just let this superclass handle things, it'll call the standard
|
159
|
+
# +#save!+ method on the record. If you pass a block, then this block is
|
160
|
+
# invoked and passed the ActiveRecord model instance to be saved. You can
|
161
|
+
# then do things like calling a different method, using a service object
|
162
|
+
# of some kind, perform audit-related operations and so-on.
|
157
163
|
#
|
158
164
|
# The return value is not used internally, making life easier for
|
159
165
|
# overriding subclasses to "do the right thing" / avoid mistakes (instead
|
@@ -161,10 +167,22 @@ module Scimitar
|
|
161
167
|
# and relying upon this to generate correct response payloads - an early
|
162
168
|
# version of the gem did this and it caused a confusing subclass bug).
|
163
169
|
#
|
164
|
-
def save!(record)
|
165
|
-
|
166
|
-
|
170
|
+
def save!(record, &block)
|
171
|
+
if block_given?
|
172
|
+
yield(record)
|
173
|
+
else
|
174
|
+
record.save!
|
175
|
+
end
|
167
176
|
rescue ActiveRecord::RecordInvalid => exception
|
177
|
+
handle_invalid_record(exception.record)
|
178
|
+
end
|
179
|
+
|
180
|
+
# Deal with validation errors by responding with an appropriate SCIM
|
181
|
+
# error.
|
182
|
+
#
|
183
|
+
# +record+:: The record with validation errors.
|
184
|
+
#
|
185
|
+
def handle_invalid_record(record)
|
168
186
|
joined_errors = record.errors.full_messages.join('; ')
|
169
187
|
|
170
188
|
# https://tools.ietf.org/html/rfc7644#page-12
|
@@ -4,6 +4,11 @@ module Scimitar
|
|
4
4
|
class SchemasController < ApplicationController
|
5
5
|
def index
|
6
6
|
schemas = Scimitar::Engine.schemas
|
7
|
+
|
8
|
+
schemas.each do |schema|
|
9
|
+
schema.meta.location = scim_schemas_url(name: schema.id)
|
10
|
+
end
|
11
|
+
|
7
12
|
schemas_by_id = schemas.reduce({}) do |hash, schema|
|
8
13
|
hash[schema.id] = schema
|
9
14
|
hash
|
@@ -192,7 +192,7 @@ module Scimitar
|
|
192
192
|
|
193
193
|
ast.push(self.start_group? ? self.parse_group() : self.pop())
|
194
194
|
|
195
|
-
|
195
|
+
if ast.last.is_a?(String) && !UNARY_OPERATORS.include?(ast.last.downcase) || ast.last.is_a?(Array)
|
196
196
|
expect_op ^= true
|
197
197
|
end
|
198
198
|
end
|
@@ -601,9 +601,15 @@ module Scimitar
|
|
601
601
|
column_names = self.activerecord_columns(scim_attribute)
|
602
602
|
value = self.activerecord_parameter(scim_parameter)
|
603
603
|
value_for_like = self.sql_modified_value(scim_operator, value)
|
604
|
-
|
604
|
+
arel_columns = column_names.map do |column|
|
605
|
+
if base_scope.model.column_names.include?(column.to_s)
|
606
|
+
arel_table[column]
|
607
|
+
elsif column.is_a?(Arel::Attribute)
|
608
|
+
column
|
609
|
+
end
|
610
|
+
end
|
605
611
|
|
606
|
-
raise Scimitar::FilterError unless
|
612
|
+
raise Scimitar::FilterError unless arel_columns.all?
|
607
613
|
|
608
614
|
unless case_sensitive
|
609
615
|
lc_scim_attribute = scim_attribute.downcase()
|
@@ -615,8 +621,7 @@ module Scimitar
|
|
615
621
|
)
|
616
622
|
end
|
617
623
|
|
618
|
-
|
619
|
-
arel_column = arel_table[column_name]
|
624
|
+
arel_columns.each.with_index do | arel_column, index |
|
620
625
|
arel_operation = case scim_operator
|
621
626
|
when 'eq'
|
622
627
|
if case_sensitive
|
@@ -641,7 +646,7 @@ module Scimitar
|
|
641
646
|
when 'co', 'sw', 'ew'
|
642
647
|
arel_column.matches(value_for_like, nil, case_sensitive)
|
643
648
|
when 'pr'
|
644
|
-
|
649
|
+
arel_column.relation.grouping(arel_column.not_eq_all(['', nil]))
|
645
650
|
else
|
646
651
|
raise Scimitar::FilterError.new("Unsupported operator: '#{scim_operator}'")
|
647
652
|
end
|
@@ -112,7 +112,7 @@ module Scimitar
|
|
112
112
|
end
|
113
113
|
|
114
114
|
def self.complex_scim_attributes
|
115
|
-
|
115
|
+
schemas.flat_map(&:scim_attributes).select(&:complexType).group_by(&:name)
|
116
116
|
end
|
117
117
|
|
118
118
|
def complex_type_from_hash(scim_attribute, attr_value)
|
@@ -139,13 +139,23 @@ module Scimitar
|
|
139
139
|
|
140
140
|
def as_json(options = {})
|
141
141
|
self.meta = Meta.new unless self.meta && self.meta.is_a?(Meta)
|
142
|
-
meta.resourceType = self.class.resource_type_id
|
143
|
-
|
142
|
+
self.meta.resourceType = self.class.resource_type_id
|
143
|
+
|
144
|
+
non_returnable_attributes = self.class
|
145
|
+
.schemas
|
146
|
+
.flat_map(&:scim_attributes)
|
147
|
+
.filter_map { |attribute| attribute.name if attribute.returned == 'never' }
|
148
|
+
|
149
|
+
non_returnable_attributes << 'errors'
|
150
|
+
|
151
|
+
original_hash = super(options).except(*non_returnable_attributes)
|
144
152
|
original_hash.merge!('schemas' => self.class.schemas.map(&:id))
|
153
|
+
|
145
154
|
self.class.extended_schemas.each do |extension_schema|
|
146
155
|
extension_attributes = extension_schema.scim_attributes.map(&:name)
|
147
156
|
original_hash.merge!(extension_schema.id => original_hash.extract!(*extension_attributes))
|
148
157
|
end
|
158
|
+
|
149
159
|
original_hash
|
150
160
|
end
|
151
161
|
|
@@ -220,13 +220,8 @@ module Scimitar
|
|
220
220
|
# allow for different client searching "styles", given ambiguities in RFC
|
221
221
|
# 7644 filter examples).
|
222
222
|
#
|
223
|
-
# Each value is a
|
224
|
-
#
|
225
|
-
# want to map using 'OR' for a single search on the corresponding SCIM
|
226
|
-
# attribute; or ':ignore' with value 'true', which means that a fitler on
|
227
|
-
# the matching attribute is ignored rather than resulting in an "invalid
|
228
|
-
# filter" exception - beware possibilities for surprised clients getting a
|
229
|
-
# broader result set than expected. Example:
|
223
|
+
# Each value is a hash of queryable SCIM attribute options, described
|
224
|
+
# below - for example:
|
230
225
|
#
|
231
226
|
# def self.scim_queryable_attributes
|
232
227
|
# return {
|
@@ -234,10 +229,27 @@ module Scimitar
|
|
234
229
|
# 'name.familyName' => { column: :last_name },
|
235
230
|
# 'emails' => { columns: [ :work_email_address, :home_email_address ] },
|
236
231
|
# 'emails.value' => { columns: [ :work_email_address, :home_email_address ] },
|
237
|
-
# 'emails.type' => { ignore: true }
|
232
|
+
# 'emails.type' => { ignore: true },
|
233
|
+
# 'groups.value' => { column: Group.arel_table[:id] }
|
238
234
|
# }
|
239
235
|
# end
|
240
236
|
#
|
237
|
+
# Column references can be either a Symbol representing a column within
|
238
|
+
# the resource model table, or an <tt>Arel::Attribute</tt> instance via
|
239
|
+
# e.g. <tt>MyModel.arel_table[:my_column]</tt>.
|
240
|
+
#
|
241
|
+
# === Queryable SCIM attribute options
|
242
|
+
#
|
243
|
+
# +:column+:: Just one simple column for a mapping.
|
244
|
+
#
|
245
|
+
# +:columns+:: An Array of columns that you want to map using 'OR' for a
|
246
|
+
# single search of the corresponding entity.
|
247
|
+
#
|
248
|
+
# +:ignore+:: When set to +true+, the matching attribute is ignored rather
|
249
|
+
# than resulting in an "invalid filter" exception. Beware
|
250
|
+
# possibilities for surprised clients getting a broader result
|
251
|
+
# set than expected, since a constraint may have been ignored.
|
252
|
+
#
|
241
253
|
# Filtering is currently limited and searching within e.g. arrays of data
|
242
254
|
# is not supported; only simple top-level keys can be mapped.
|
243
255
|
#
|
@@ -406,8 +418,11 @@ module Scimitar
|
|
406
418
|
def from_scim_patch!(patch_hash:)
|
407
419
|
frozen_ci_patch_hash = patch_hash.with_indifferent_case_insensitive_access().freeze()
|
408
420
|
ci_scim_hash = self.to_scim(location: '(unused)').as_json().with_indifferent_case_insensitive_access()
|
421
|
+
operations = frozen_ci_patch_hash['operations']
|
422
|
+
|
423
|
+
raise Scimitar::InvalidSyntaxError.new("Missing PATCH \"operations\"") unless operations
|
409
424
|
|
410
|
-
|
425
|
+
operations.each do |operation|
|
411
426
|
nature = operation['op' ]&.downcase
|
412
427
|
path_str = operation['path' ]
|
413
428
|
value = operation['value']
|
@@ -88,19 +88,28 @@ module Scimitar
|
|
88
88
|
end
|
89
89
|
value.class.schema.valid?(value)
|
90
90
|
return true if value.errors.empty?
|
91
|
-
add_errors_from_hash(value.errors.to_hash, prefix: self.name)
|
91
|
+
add_errors_from_hash(errors_hash: value.errors.to_hash, prefix: self.name)
|
92
92
|
false
|
93
93
|
end
|
94
94
|
|
95
95
|
def valid_simple_type?(value)
|
96
|
-
|
97
|
-
|
98
|
-
(type
|
99
|
-
|
100
|
-
|
96
|
+
if multiValued
|
97
|
+
valid = value.is_a?(Array) && value.all? { |v| simple_type?(v) }
|
98
|
+
errors.add(self.name, "or one of its elements has the wrong type. It has to be an array of #{self.type}s.") unless valid
|
99
|
+
else
|
100
|
+
valid = simple_type?(value)
|
101
|
+
errors.add(self.name, "has the wrong type. It has to be a(n) #{self.type}.") unless valid
|
102
|
+
end
|
101
103
|
valid
|
102
104
|
end
|
103
105
|
|
106
|
+
def simple_type?(value)
|
107
|
+
(type == 'string' && value.is_a?(String)) ||
|
108
|
+
(type == 'boolean' && (value.is_a?(TrueClass) || value.is_a?(FalseClass))) ||
|
109
|
+
(type == 'integer' && (value.is_a?(Integer))) ||
|
110
|
+
(type == 'dateTime' && valid_date_time?(value))
|
111
|
+
end
|
112
|
+
|
104
113
|
def valid_date_time?(value)
|
105
114
|
!!Time.iso8601(value)
|
106
115
|
rescue ArgumentError
|
@@ -13,7 +13,7 @@ module Scimitar
|
|
13
13
|
|
14
14
|
# Converts the schema to its json representation that will be returned by /SCHEMAS end-point of a SCIM service provider.
|
15
15
|
def as_json(options = {})
|
16
|
-
@meta.location
|
16
|
+
@meta.location ||= Scimitar::Engine.routes.url_helpers.scim_schemas_path(name: id)
|
17
17
|
original = super
|
18
18
|
original.merge('attributes' => original.delete('scim_attributes'))
|
19
19
|
end
|
@@ -26,7 +26,9 @@ module Scimitar
|
|
26
26
|
#
|
27
27
|
def self.valid?(resource)
|
28
28
|
cloned_scim_attributes.each do |scim_attribute|
|
29
|
-
|
29
|
+
unless scim_attribute.valid?(resource.send(scim_attribute.name))
|
30
|
+
resource.add_errors_from_hash(errors_hash: scim_attribute.errors.to_hash)
|
31
|
+
end
|
30
32
|
end
|
31
33
|
end
|
32
34
|
|
@@ -38,9 +38,10 @@ Rails.application.config.to_prepare do # (required for >= Rails 7 / Zeitwerk)
|
|
38
38
|
Scimitar.engine_configuration = Scimitar::EngineConfiguration.new({
|
39
39
|
|
40
40
|
# If you have filters you want to run for any Scimitar action/route, you
|
41
|
-
# can define them here.
|
42
|
-
#
|
43
|
-
# verification
|
41
|
+
# can define them here. You can also override any shared controller methods
|
42
|
+
# here. For example, you might use a before-action to set up some
|
43
|
+
# multi-tenancy related state, skip Rails CSRF token verification, or
|
44
|
+
# customise how Scimitar generates URLs:
|
44
45
|
#
|
45
46
|
# application_controller_mixin: Module.new do
|
46
47
|
# def self.included(base)
|
@@ -54,6 +55,10 @@ Rails.application.config.to_prepare do # (required for >= Rails 7 / Zeitwerk)
|
|
54
55
|
# prepend_before_action :setup_some_kind_of_multi_tenancy_data
|
55
56
|
# end
|
56
57
|
# end
|
58
|
+
#
|
59
|
+
# def scim_schemas_url(options)
|
60
|
+
# super(custom_param: 'value', **options)
|
61
|
+
# end
|
57
62
|
# end, # ...other configuration entries might follow...
|
58
63
|
|
59
64
|
# If you want to support username/password authentication:
|
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.7.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 = '2023-
|
11
|
+
DATE = '2023-11-15'
|
12
12
|
|
13
13
|
end
|
data/lib/scimitar.rb
CHANGED
@@ -25,16 +25,4 @@ module Scimitar
|
|
25
25
|
@engine_configuration ||= EngineConfiguration.new
|
26
26
|
@engine_configuration
|
27
27
|
end
|
28
|
-
|
29
|
-
# Set in a "Rails.application.config.to_prepare" block by Scimitar itself to
|
30
|
-
# establish default values. Older Scimitar client applications might not use
|
31
|
-
# that wrapper; we don't want to overwrite settings they configured, but we
|
32
|
-
# *do* want to let them overwrite the defaults. Thus, '||=" is used here but
|
33
|
-
# not in ::service_provider_configuration=.
|
34
|
-
#
|
35
|
-
# Client applications should not call this method themselves.
|
36
|
-
#
|
37
|
-
def self.default_service_provider_configuration(default_configuration)
|
38
|
-
@service_provider_configuration ||= custom_configuration
|
39
|
-
end
|
40
28
|
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# For tests only - uses custom 'save!' implementation which passes a block to
|
2
|
+
# Scimitar::ActiveRecordBackedResourcesController#save!.
|
3
|
+
#
|
4
|
+
class CustomSaveMockUsersController < Scimitar::ActiveRecordBackedResourcesController
|
5
|
+
|
6
|
+
CUSTOM_SAVE_BLOCK_USERNAME_INDICATOR = 'Custom save-block invoked'
|
7
|
+
|
8
|
+
protected
|
9
|
+
|
10
|
+
def save!(_record)
|
11
|
+
super do | record |
|
12
|
+
record.update!(username: CUSTOM_SAVE_BLOCK_USERNAME_INDICATOR)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def storage_class
|
17
|
+
MockUser
|
18
|
+
end
|
19
|
+
|
20
|
+
def storage_scope
|
21
|
+
MockUser.all
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
@@ -1,5 +1,7 @@
|
|
1
1
|
class MockUser < ActiveRecord::Base
|
2
2
|
|
3
|
+
self.primary_key = :primary_key
|
4
|
+
|
3
5
|
# ===========================================================================
|
4
6
|
# TEST ATTRIBUTES - see db/migrate/20210304014602_create_mock_users.rb etc.
|
5
7
|
# ===========================================================================
|
@@ -8,6 +10,7 @@ class MockUser < ActiveRecord::Base
|
|
8
10
|
primary_key
|
9
11
|
scim_uid
|
10
12
|
username
|
13
|
+
password
|
11
14
|
first_name
|
12
15
|
last_name
|
13
16
|
work_email_address
|
@@ -15,6 +18,7 @@ class MockUser < ActiveRecord::Base
|
|
15
18
|
work_phone_number
|
16
19
|
organization
|
17
20
|
department
|
21
|
+
mock_groups
|
18
22
|
}
|
19
23
|
|
20
24
|
has_and_belongs_to_many :mock_groups
|
@@ -43,6 +47,7 @@ class MockUser < ActiveRecord::Base
|
|
43
47
|
id: :primary_key,
|
44
48
|
externalId: :scim_uid,
|
45
49
|
userName: :username,
|
50
|
+
password: :password,
|
46
51
|
name: {
|
47
52
|
givenName: :first_name,
|
48
53
|
familyName: :last_name
|
@@ -90,7 +95,17 @@ class MockUser < ActiveRecord::Base
|
|
90
95
|
# "spec/apps/dummy/config/initializers/scimitar.rb".
|
91
96
|
#
|
92
97
|
organization: :organization,
|
93
|
-
department: :department
|
98
|
+
department: :department,
|
99
|
+
userGroups: [
|
100
|
+
{
|
101
|
+
list: :mock_groups,
|
102
|
+
find_with: ->(value) { MockGroup.find(value["value"]) },
|
103
|
+
using: {
|
104
|
+
value: :id,
|
105
|
+
display: :display_name
|
106
|
+
}
|
107
|
+
}
|
108
|
+
]
|
94
109
|
}
|
95
110
|
end
|
96
111
|
|
@@ -105,6 +120,8 @@ class MockUser < ActiveRecord::Base
|
|
105
120
|
'meta.lastModified' => { column: :updated_at },
|
106
121
|
'name.givenName' => { column: :first_name },
|
107
122
|
'name.familyName' => { column: :last_name },
|
123
|
+
'groups' => { column: MockGroup.arel_table[:id] },
|
124
|
+
'groups.value' => { column: MockGroup.arel_table[:id] },
|
108
125
|
'emails' => { columns: [ :work_email_address, :home_email_address ] },
|
109
126
|
'emails.value' => { columns: [ :work_email_address, :home_email_address ] },
|
110
127
|
'emails.type' => { ignore: true } # We can't filter on that; it'll just search all e-mails
|
@@ -26,6 +26,14 @@ Scimitar.engine_configuration = Scimitar::EngineConfiguration.new({
|
|
26
26
|
before_action :test_hook
|
27
27
|
end
|
28
28
|
end
|
29
|
+
|
30
|
+
def scim_schemas_url(options)
|
31
|
+
super(test: 1, **options)
|
32
|
+
end
|
33
|
+
|
34
|
+
def scim_resource_type_url(options)
|
35
|
+
super(test: 1, **options)
|
36
|
+
end
|
29
37
|
end
|
30
38
|
|
31
39
|
})
|
@@ -6,12 +6,12 @@
|
|
6
6
|
Rails.application.routes.draw do
|
7
7
|
mount Scimitar::Engine, at: '/'
|
8
8
|
|
9
|
-
get 'Users',
|
10
|
-
get 'Users/:id',
|
11
|
-
post 'Users',
|
12
|
-
put 'Users/:id',
|
13
|
-
patch 'Users/:id',
|
14
|
-
delete 'Users/:id',
|
9
|
+
get 'Users', to: 'mock_users#index'
|
10
|
+
get 'Users/:id', to: 'mock_users#show'
|
11
|
+
post 'Users', to: 'mock_users#create'
|
12
|
+
put 'Users/:id', to: 'mock_users#replace'
|
13
|
+
patch 'Users/:id', to: 'mock_users#update'
|
14
|
+
delete 'Users/:id', to: 'mock_users#destroy'
|
15
15
|
|
16
16
|
get 'Groups', to: 'mock_groups#index'
|
17
17
|
get 'Groups/:id', to: 'mock_groups#show'
|
@@ -21,6 +21,11 @@ Rails.application.routes.draw do
|
|
21
21
|
#
|
22
22
|
delete 'CustomDestroyUsers/:id', to: 'custom_destroy_mock_users#destroy'
|
23
23
|
|
24
|
+
# For testing blocks passed to ActiveRecordBackedResourcesController#save!
|
25
|
+
#
|
26
|
+
post 'CustomSaveUsers', to: 'custom_save_mock_users#create'
|
27
|
+
get 'CustomSaveUsers/:id', to: 'custom_save_mock_users#show'
|
28
|
+
|
24
29
|
# For testing environment inside Scimitar::ApplicationController subclasses.
|
25
30
|
#
|
26
31
|
get 'CustomRequestVerifiers', to: 'custom_request_verifiers#index'
|
@@ -9,8 +9,8 @@ RSpec.describe Scimitar::ResourceTypesController do
|
|
9
9
|
it 'renders the resource type for user' do
|
10
10
|
get :index, format: :scim
|
11
11
|
response_hash = JSON.parse(response.body)
|
12
|
-
expected_response = [ Scimitar::Resources::User.resource_type(scim_resource_type_url(name: 'User')),
|
13
|
-
Scimitar::Resources::Group.resource_type(scim_resource_type_url(name: 'Group'))
|
12
|
+
expected_response = [ Scimitar::Resources::User.resource_type(scim_resource_type_url(name: 'User', test: 1)),
|
13
|
+
Scimitar::Resources::Group.resource_type(scim_resource_type_url(name: 'Group', test: 1))
|
14
14
|
].to_json
|
15
15
|
|
16
16
|
response_hash = JSON.parse(response.body)
|
@@ -1,6 +1,7 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
3
|
RSpec.describe Scimitar::SchemasController do
|
4
|
+
routes { Scimitar::Engine.routes }
|
4
5
|
|
5
6
|
before(:each) { allow(controller).to receive(:authenticated?).and_return(true) }
|
6
7
|
|
@@ -26,6 +27,13 @@ RSpec.describe Scimitar::SchemasController do
|
|
26
27
|
expect(parsed_body['name']).to eql('User')
|
27
28
|
end
|
28
29
|
|
30
|
+
it 'includes the controller customized schema location' do
|
31
|
+
get :index, params: { name: Scimitar::Schema::User.id, format: :scim }
|
32
|
+
expect(response).to be_ok
|
33
|
+
parsed_body = JSON.parse(response.body)
|
34
|
+
expect(parsed_body.dig('meta', 'location')).to eq scim_schemas_url(name: Scimitar::Schema::User.id, test: 1)
|
35
|
+
end
|
36
|
+
|
29
37
|
it 'returns only the Group schema when its id is provided' do
|
30
38
|
get :index, params: { name: Scimitar::Schema::Group.id, format: :scim }
|
31
39
|
expect(response).to be_ok
|
@@ -405,7 +405,7 @@ RSpec.describe Scimitar::Lists::QueryParser do
|
|
405
405
|
query = @instance.to_activerecord_query(MockUser.all)
|
406
406
|
|
407
407
|
expect(query.count).to eql(1)
|
408
|
-
expect(query.pluck(:primary_key)).to eql([user_1.
|
408
|
+
expect(query.pluck(:primary_key)).to eql([user_1.primary_key])
|
409
409
|
|
410
410
|
@instance.parse('name.givenName sw J') # First name starts with 'J'
|
411
411
|
query = @instance.to_activerecord_query(MockUser.all)
|
@@ -481,6 +481,66 @@ RSpec.describe Scimitar::Lists::QueryParser do
|
|
481
481
|
end
|
482
482
|
end # "context 'when instructed to ignore an attribute' do"
|
483
483
|
|
484
|
+
context 'when an arel column is mapped' do
|
485
|
+
let(:scope_with_groups) { MockUser.left_joins(:mock_groups) }
|
486
|
+
|
487
|
+
context 'with binary operators' do
|
488
|
+
it 'reads across all using OR' do
|
489
|
+
@instance.parse('groups eq "12345"')
|
490
|
+
query = @instance.to_activerecord_query(scope_with_groups)
|
491
|
+
|
492
|
+
expect(query.to_sql).to eql(<<~SQL.squish)
|
493
|
+
SELECT "mock_users".*
|
494
|
+
FROM "mock_users"
|
495
|
+
LEFT OUTER JOIN "mock_groups_users" ON "mock_groups_users"."mock_user_id" = "mock_users"."primary_key"
|
496
|
+
LEFT OUTER JOIN "mock_groups" ON "mock_groups"."id" = "mock_groups_users"."mock_group_id"
|
497
|
+
WHERE "mock_groups"."id" ILIKE 12345
|
498
|
+
SQL
|
499
|
+
end
|
500
|
+
|
501
|
+
it 'works with other query elements using correct precedence' do
|
502
|
+
@instance.parse('groups eq "12345" and emails eq "any@test.com"')
|
503
|
+
query = @instance.to_activerecord_query(scope_with_groups)
|
504
|
+
|
505
|
+
expect(query.to_sql).to eql(<<~SQL.squish)
|
506
|
+
SELECT "mock_users".*
|
507
|
+
FROM "mock_users"
|
508
|
+
LEFT OUTER JOIN "mock_groups_users" ON "mock_groups_users"."mock_user_id" = "mock_users"."primary_key"
|
509
|
+
LEFT OUTER JOIN "mock_groups" ON "mock_groups"."id" = "mock_groups_users"."mock_group_id"
|
510
|
+
WHERE "mock_groups"."id" ILIKE 12345 AND ("mock_users"."work_email_address" ILIKE 'any@test.com' OR "mock_users"."home_email_address" ILIKE 'any@test.com')
|
511
|
+
SQL
|
512
|
+
end
|
513
|
+
end # "context 'with binary operators' do"
|
514
|
+
|
515
|
+
context 'with unary operators' do
|
516
|
+
it 'reads across all using OR' do
|
517
|
+
@instance.parse('groups pr')
|
518
|
+
query = @instance.to_activerecord_query(scope_with_groups)
|
519
|
+
|
520
|
+
expect(query.to_sql).to eql(<<~SQL.squish)
|
521
|
+
SELECT "mock_users".*
|
522
|
+
FROM "mock_users"
|
523
|
+
LEFT OUTER JOIN "mock_groups_users" ON "mock_groups_users"."mock_user_id" = "mock_users"."primary_key"
|
524
|
+
LEFT OUTER JOIN "mock_groups" ON "mock_groups"."id" = "mock_groups_users"."mock_group_id"
|
525
|
+
WHERE ("mock_groups"."id" != NULL AND "mock_groups"."id" IS NOT NULL)
|
526
|
+
SQL
|
527
|
+
end
|
528
|
+
|
529
|
+
it 'works with other query elements using correct precedence' do
|
530
|
+
@instance.parse('name.familyName eq "John" and groups pr')
|
531
|
+
query = @instance.to_activerecord_query(scope_with_groups)
|
532
|
+
|
533
|
+
expect(query.to_sql).to eql(<<~SQL.squish)
|
534
|
+
SELECT "mock_users".*
|
535
|
+
FROM "mock_users"
|
536
|
+
LEFT OUTER JOIN "mock_groups_users" ON "mock_groups_users"."mock_user_id" = "mock_users"."primary_key"
|
537
|
+
LEFT OUTER JOIN "mock_groups" ON "mock_groups"."id" = "mock_groups_users"."mock_group_id"
|
538
|
+
WHERE "mock_users"."last_name" ILIKE 'John' AND ("mock_groups"."id" != NULL AND "mock_groups"."id" IS NOT NULL)
|
539
|
+
SQL
|
540
|
+
end
|
541
|
+
end # "context 'with unary operators' do
|
542
|
+
end # "context 'when an arel column is mapped' do"
|
543
|
+
|
484
544
|
context 'with complex cases' do
|
485
545
|
context 'using AND' do
|
486
546
|
it 'generates expected SQL' do
|
@@ -532,6 +592,13 @@ RSpec.describe Scimitar::Lists::QueryParser do
|
|
532
592
|
expect(query.to_sql).to eql(%q{SELECT "mock_users".* FROM "mock_users" WHERE "mock_users"."first_name" ILIKE 'Jane' AND ("mock_users"."last_name" ILIKE '%avi%' OR "mock_users"."last_name" ILIKE '%ith')})
|
533
593
|
end
|
534
594
|
|
595
|
+
it 'combined parentheses generates expected SQL' do
|
596
|
+
@instance.parse('(name.givenName eq "Jane" OR name.givenName eq "Jaden") and (name.familyName co "avi" or name.familyName ew "ith")')
|
597
|
+
query = @instance.to_activerecord_query(MockUser.all)
|
598
|
+
|
599
|
+
expect(query.to_sql).to eql(%q{SELECT "mock_users".* FROM "mock_users" WHERE ("mock_users"."first_name" ILIKE 'Jane' OR "mock_users"."first_name" ILIKE 'Jaden') AND ("mock_users"."last_name" ILIKE '%avi%' OR "mock_users"."last_name" ILIKE '%ith')})
|
600
|
+
end
|
601
|
+
|
535
602
|
it 'finds expected items' do
|
536
603
|
user_1 = MockUser.create(username: '1', first_name: 'Jane', last_name: 'Davis') # Match
|
537
604
|
user_2 = MockUser.create(username: '2', first_name: 'Jane', last_name: 'Smith') # Match
|
@@ -14,7 +14,10 @@ RSpec.describe Scimitar::Resources::Base do
|
|
14
14
|
),
|
15
15
|
Scimitar::Schema::Attribute.new(
|
16
16
|
name: 'names', multiValued: true, complexType: Scimitar::ComplexTypes::Name, required: false
|
17
|
-
)
|
17
|
+
),
|
18
|
+
Scimitar::Schema::Attribute.new(
|
19
|
+
name: 'privateName', complexType: Scimitar::ComplexTypes::Name, required: false, returned: false
|
20
|
+
),
|
18
21
|
]
|
19
22
|
end
|
20
23
|
end
|
@@ -30,6 +33,10 @@ RSpec.describe Scimitar::Resources::Base do
|
|
30
33
|
name: {
|
31
34
|
givenName: 'John',
|
32
35
|
familyName: 'Smith'
|
36
|
+
},
|
37
|
+
privateName: {
|
38
|
+
givenName: 'Alt John',
|
39
|
+
familyName: 'Alt Smith'
|
33
40
|
}
|
34
41
|
}
|
35
42
|
|
@@ -39,6 +46,9 @@ RSpec.describe Scimitar::Resources::Base do
|
|
39
46
|
expect(resource.name.is_a?(Scimitar::ComplexTypes::Name)).to be(true)
|
40
47
|
expect(resource.name.givenName).to eql('John')
|
41
48
|
expect(resource.name.familyName).to eql('Smith')
|
49
|
+
expect(resource.privateName.is_a?(Scimitar::ComplexTypes::Name)).to be(true)
|
50
|
+
expect(resource.privateName.givenName).to eql('Alt John')
|
51
|
+
expect(resource.privateName.familyName).to eql('Alt Smith')
|
42
52
|
end
|
43
53
|
|
44
54
|
it 'which builds an array of nested resources' do
|
@@ -101,14 +111,38 @@ RSpec.describe Scimitar::Resources::Base do
|
|
101
111
|
context '#as_json' do
|
102
112
|
it 'renders the json with the resourceType' do
|
103
113
|
resource = CustomResourse.new(name: {
|
104
|
-
givenName:
|
114
|
+
givenName: 'John',
|
105
115
|
familyName: 'Smith'
|
106
116
|
})
|
107
117
|
|
108
118
|
result = resource.as_json
|
109
|
-
|
119
|
+
|
120
|
+
expect(result['schemas'] ).to eql(['custom-id'])
|
121
|
+
expect(result['meta']['resourceType']).to eql('CustomResourse')
|
122
|
+
expect(result['errors'] ).to be_nil
|
123
|
+
end
|
124
|
+
|
125
|
+
it 'excludes attributes that are flagged as do-not-return' do
|
126
|
+
resource = CustomResourse.new(
|
127
|
+
name: {
|
128
|
+
givenName: 'John',
|
129
|
+
familyName: 'Smith'
|
130
|
+
},
|
131
|
+
privateName: {
|
132
|
+
givenName: 'Alt John',
|
133
|
+
familyName: 'Alt Smith'
|
134
|
+
}
|
135
|
+
)
|
136
|
+
|
137
|
+
result = resource.as_json
|
138
|
+
|
139
|
+
expect(result['schemas'] ).to eql(['custom-id'])
|
110
140
|
expect(result['meta']['resourceType']).to eql('CustomResourse')
|
111
|
-
expect(result['errors']).to be_nil
|
141
|
+
expect(result['errors'] ).to be_nil
|
142
|
+
expect(result['name'] ).to be_present
|
143
|
+
expect(result['name']['givenName'] ).to eql('John')
|
144
|
+
expect(result['name']['familyName'] ).to eql('Smith')
|
145
|
+
expect(result['privateName'] ).to be_present
|
112
146
|
end
|
113
147
|
end # "context '#as_json' do"
|
114
148
|
|
@@ -267,7 +301,10 @@ RSpec.describe Scimitar::Resources::Base do
|
|
267
301
|
end
|
268
302
|
|
269
303
|
def self.scim_attributes
|
270
|
-
[
|
304
|
+
[
|
305
|
+
Scimitar::Schema::Attribute.new(name: 'relationship', type: 'string', required: true),
|
306
|
+
Scimitar::Schema::Attribute.new(name: "userGroups", multiValued: true, complexType: Scimitar::ComplexTypes::ReferenceGroup, mutability: "writeOnly")
|
307
|
+
]
|
271
308
|
end
|
272
309
|
end
|
273
310
|
|
@@ -291,6 +328,12 @@ RSpec.describe Scimitar::Resources::Base do
|
|
291
328
|
resource = resource_class.new('extension-id' => {relationship: 'GAGA'})
|
292
329
|
expect(resource.relationship).to eql('GAGA')
|
293
330
|
end
|
331
|
+
|
332
|
+
it 'allows setting complex extension attributes' do
|
333
|
+
user_groups = [{ value: '123' }, { value: '456'}]
|
334
|
+
resource = resource_class.new('extension-id' => {userGroups: user_groups})
|
335
|
+
expect(resource.userGroups.map(&:value)).to eql(['123', '456'])
|
336
|
+
end
|
294
337
|
end # "context '#initialize' do"
|
295
338
|
|
296
339
|
context '#as_json' do
|
@@ -160,13 +160,14 @@ RSpec.describe Scimitar::Resources::Mixin do
|
|
160
160
|
|
161
161
|
context '#to_scim' do
|
162
162
|
context 'with a UUID, renamed primary key column' do
|
163
|
-
it 'compiles instance attribute values into a SCIM representation' do
|
163
|
+
it 'compiles instance attribute values into a SCIM representation, but omits do-not-return fields' do
|
164
164
|
uuid = SecureRandom.uuid
|
165
165
|
|
166
166
|
instance = MockUser.new
|
167
167
|
instance.primary_key = uuid
|
168
168
|
instance.scim_uid = 'AA02984'
|
169
169
|
instance.username = 'foo'
|
170
|
+
instance.password = 'correcthorsebatterystaple'
|
170
171
|
instance.first_name = 'Foo'
|
171
172
|
instance.last_name = 'Bar'
|
172
173
|
instance.work_email_address = 'foo.bar@test.com'
|
@@ -404,6 +405,7 @@ RSpec.describe Scimitar::Resources::Mixin do
|
|
404
405
|
it 'ignoring read-only lists' do
|
405
406
|
hash = {
|
406
407
|
'userName' => 'foo',
|
408
|
+
'password' => 'staplebatteryhorsecorrect',
|
407
409
|
'name' => {'givenName' => 'Foo', 'familyName' => 'Bar'},
|
408
410
|
'active' => true,
|
409
411
|
'emails' => [{'type' => 'work', 'primary' => true, 'value' => 'foo.bar@test.com'}],
|
@@ -428,6 +430,7 @@ RSpec.describe Scimitar::Resources::Mixin do
|
|
428
430
|
|
429
431
|
expect(instance.scim_uid ).to eql('AA02984')
|
430
432
|
expect(instance.username ).to eql('foo')
|
433
|
+
expect(instance.password ).to eql('staplebatteryhorsecorrect')
|
431
434
|
expect(instance.first_name ).to eql('Foo')
|
432
435
|
expect(instance.last_name ).to eql('Bar')
|
433
436
|
expect(instance.work_email_address).to eql('foo.bar@test.com')
|
@@ -2685,6 +2688,14 @@ RSpec.describe Scimitar::Resources::Mixin do
|
|
2685
2688
|
#
|
2686
2689
|
context 'public interface' do
|
2687
2690
|
shared_examples 'a patcher' do | force_upper_case: |
|
2691
|
+
it 'gives the user a comprehensible error when operations are missing' do
|
2692
|
+
patch = { 'schemas' => ['urn:ietf:params:scim:api:messages:2.0:PatchOp'] }
|
2693
|
+
|
2694
|
+
expect do
|
2695
|
+
@instance.from_scim_patch!(patch_hash: patch)
|
2696
|
+
end.to raise_error Scimitar::InvalidSyntaxError, "Missing PATCH \"operations\""
|
2697
|
+
end
|
2698
|
+
|
2688
2699
|
it 'which updates simple values' do
|
2689
2700
|
@instance.update!(username: 'foo')
|
2690
2701
|
|
@@ -42,25 +42,25 @@ RSpec.describe Scimitar::Resources::User do
|
|
42
42
|
let(:user) { described_class.new }
|
43
43
|
|
44
44
|
it 'adds the error when the value is a string' do
|
45
|
-
user.add_errors_from_hash({key: 'some error'})
|
45
|
+
user.add_errors_from_hash(errors_hash: {key: 'some error'})
|
46
46
|
expect(user.errors.messages.to_h).to eql({key: ['some error']})
|
47
47
|
expect(user.errors.full_messages).to eql(['Key some error'])
|
48
48
|
end
|
49
49
|
|
50
50
|
it 'adds the error when the value is an array' do
|
51
|
-
user.add_errors_from_hash({key: ['error1', 'error2']})
|
51
|
+
user.add_errors_from_hash(errors_hash: {key: ['error1', 'error2']})
|
52
52
|
expect(user.errors.messages.to_h).to eql({key: ['error1', 'error2']})
|
53
53
|
expect(user.errors.full_messages).to eql(['Key error1', 'Key error2'])
|
54
54
|
end
|
55
55
|
|
56
56
|
it 'adds the error with prefix when the value is a string' do
|
57
|
-
user.add_errors_from_hash({key: 'some error'}, prefix: :pre)
|
57
|
+
user.add_errors_from_hash(errors_hash: {key: 'some error'}, prefix: :pre)
|
58
58
|
expect(user.errors.messages.to_h).to eql({:'pre.key' => ['some error']})
|
59
59
|
expect(user.errors.full_messages).to eql(['Pre key some error'])
|
60
60
|
end
|
61
61
|
|
62
62
|
it 'adds the error wity prefix when the value is an array' do
|
63
|
-
user.add_errors_from_hash({key: ['error1', 'error2']}, prefix: :pre)
|
63
|
+
user.add_errors_from_hash(errors_hash: {key: ['error1', 'error2']}, prefix: :pre)
|
64
64
|
expect(user.errors.messages.to_h).to eql({:'pre.key' => ['error1', 'error2']})
|
65
65
|
expect(user.errors.full_messages).to eql(['Pre key error1', 'Pre key error2'])
|
66
66
|
end
|
@@ -46,6 +46,28 @@ RSpec.describe Scimitar::Schema::Attribute do
|
|
46
46
|
expect(attribute.errors.messages.to_h).to eql({userName: ['has the wrong type. It has to be a(n) string.']})
|
47
47
|
end
|
48
48
|
|
49
|
+
it 'is valid if multi-valued and type is string and given value is an array of strings' do
|
50
|
+
attribute = described_class.new(name: 'scopes', multiValued: true, type: 'string')
|
51
|
+
expect(attribute.valid?(['something', 'something else'])).to be(true)
|
52
|
+
end
|
53
|
+
|
54
|
+
it 'is valid if multi-valued and type is string and given value is an empty array' do
|
55
|
+
attribute = described_class.new(name: 'scopes', multiValued: true, type: 'string')
|
56
|
+
expect(attribute.valid?([])).to be(true)
|
57
|
+
end
|
58
|
+
|
59
|
+
it 'is invalid if multi-valued and type is string and given value is not an array' do
|
60
|
+
attribute = described_class.new(name: 'scopes', multiValued: true, type: 'string')
|
61
|
+
expect(attribute.valid?('something')).to be(false)
|
62
|
+
expect(attribute.errors.messages.to_h).to eql({scopes: ['or one of its elements has the wrong type. It has to be an array of strings.']})
|
63
|
+
end
|
64
|
+
|
65
|
+
it 'is invalid if multi-valued and type is string and given value is an array containing another type' do
|
66
|
+
attribute = described_class.new(name: 'scopes', multiValued: true, type: 'string')
|
67
|
+
expect(attribute.valid?(['something', 123])).to be(false)
|
68
|
+
expect(attribute.errors.messages.to_h).to eql({scopes: ['or one of its elements has the wrong type. It has to be an array of strings.']})
|
69
|
+
end
|
70
|
+
|
49
71
|
it 'is valid if type is boolean and given value is boolean' do
|
50
72
|
expect(described_class.new(name: 'name', type: 'boolean').valid?(false)).to be(true)
|
51
73
|
expect(described_class.new(name: 'name', type: 'boolean').valid?(true)).to be(true)
|
@@ -5,8 +5,6 @@ RSpec.describe Scimitar::ActiveRecordBackedResourcesController do
|
|
5
5
|
before :each do
|
6
6
|
allow_any_instance_of(Scimitar::ApplicationController).to receive(:authenticated?).and_return(true)
|
7
7
|
|
8
|
-
lmt = Time.parse("2023-01-09 14:25:00 +1300")
|
9
|
-
|
10
8
|
# If a sort order is unspecified, the controller defaults to ID ascending.
|
11
9
|
# With UUID based IDs, testing life is made easier by ensuring that the
|
12
10
|
# creation order matches an ascending UUID sort order (which is what would
|
@@ -282,7 +280,7 @@ RSpec.describe Scimitar::ActiveRecordBackedResourcesController do
|
|
282
280
|
it 'with minimal parameters' do
|
283
281
|
mock_before = MockUser.all.to_a
|
284
282
|
|
285
|
-
attributes = { userName: '4' }
|
283
|
+
attributes = { userName: '4' } # Minimum required by schema
|
286
284
|
attributes = spec_helper_hupcase(attributes) if force_upper_case
|
287
285
|
|
288
286
|
expect_any_instance_of(MockUsersController).to receive(:create).once.and_call_original
|
@@ -337,7 +335,7 @@ RSpec.describe Scimitar::ActiveRecordBackedResourcesController do
|
|
337
335
|
expect(response.status).to eql(201)
|
338
336
|
result = JSON.parse(response.body)
|
339
337
|
|
340
|
-
expect(result['id']).to eql(new_mock.
|
338
|
+
expect(result['id']).to eql(new_mock.id.to_s)
|
341
339
|
expect(result['meta']['resourceType']).to eql('User')
|
342
340
|
expect(new_mock.username).to eql('4')
|
343
341
|
expect(new_mock.first_name).to eql('Given')
|
@@ -399,6 +397,22 @@ RSpec.describe Scimitar::ActiveRecordBackedResourcesController do
|
|
399
397
|
expect(result['scimType']).to eql('invalidValue')
|
400
398
|
expect(result['detail']).to include('is reserved')
|
401
399
|
end
|
400
|
+
|
401
|
+
it 'invokes a block if given one' do
|
402
|
+
mock_before = MockUser.all.to_a
|
403
|
+
attributes = { userName: '5' } # Minimum required by schema
|
404
|
+
|
405
|
+
expect_any_instance_of(CustomSaveMockUsersController).to receive(:create).once.and_call_original
|
406
|
+
expect {
|
407
|
+
post "/CustomSaveUsers", params: attributes.merge(format: :scim)
|
408
|
+
}.to change { MockUser.count }.by(1)
|
409
|
+
|
410
|
+
mock_after = MockUser.all.to_a
|
411
|
+
new_mock = (mock_after - mock_before).first
|
412
|
+
|
413
|
+
expect(response.status).to eql(201)
|
414
|
+
expect(new_mock.username).to eql(CustomSaveMockUsersController::CUSTOM_SAVE_BLOCK_USERNAME_INDICATOR)
|
415
|
+
end
|
402
416
|
end # "context '#create' do"
|
403
417
|
|
404
418
|
# ===========================================================================
|
@@ -18,7 +18,7 @@ RSpec.describe Scimitar::ApplicationController do
|
|
18
18
|
parsed_body = JSON.parse(response.body)
|
19
19
|
expect(parsed_body['request']['is_scim' ]).to eql(true)
|
20
20
|
expect(parsed_body['request']['format' ]).to eql('application/scim+json')
|
21
|
-
expect(parsed_body['request']['content_type']).to eql('application/scim+json')
|
21
|
+
expect(parsed_body['request']['content_type']).to eql('application/scim+json') # Filled in by ApplicationController#require_scim
|
22
22
|
end
|
23
23
|
|
24
24
|
it 'renders 400 if given bad JSON' do
|
@@ -26,7 +26,6 @@ RSpec.describe Scimitar::ApplicationController do
|
|
26
26
|
|
27
27
|
expect(response).to have_http_status(:bad_request)
|
28
28
|
expect(JSON.parse(response.body)['detail']).to start_with('Invalid JSON - ')
|
29
|
-
expect(JSON.parse(response.body)['detail']).to include("'not-json-12345'")
|
30
29
|
end
|
31
30
|
|
32
31
|
it 'translates Content-Type to Rails request format' do
|
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: 1.
|
4
|
+
version: 1.7.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: 2023-
|
12
|
+
date: 2023-11-15 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: rails
|
@@ -196,6 +196,7 @@ files:
|
|
196
196
|
- lib/scimitar/version.rb
|
197
197
|
- spec/apps/dummy/app/controllers/custom_destroy_mock_users_controller.rb
|
198
198
|
- spec/apps/dummy/app/controllers/custom_request_verifiers_controller.rb
|
199
|
+
- spec/apps/dummy/app/controllers/custom_save_mock_users_controller.rb
|
199
200
|
- spec/apps/dummy/app/controllers/mock_groups_controller.rb
|
200
201
|
- spec/apps/dummy/app/controllers/mock_users_controller.rb
|
201
202
|
- spec/apps/dummy/app/models/mock_group.rb
|
@@ -260,13 +261,14 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
260
261
|
- !ruby/object:Gem::Version
|
261
262
|
version: '0'
|
262
263
|
requirements: []
|
263
|
-
rubygems_version: 3.4.
|
264
|
+
rubygems_version: 3.4.10
|
264
265
|
signing_key:
|
265
266
|
specification_version: 4
|
266
267
|
summary: SCIM v2 for Rails
|
267
268
|
test_files:
|
268
269
|
- spec/apps/dummy/app/controllers/custom_destroy_mock_users_controller.rb
|
269
270
|
- spec/apps/dummy/app/controllers/custom_request_verifiers_controller.rb
|
271
|
+
- spec/apps/dummy/app/controllers/custom_save_mock_users_controller.rb
|
270
272
|
- spec/apps/dummy/app/controllers/mock_groups_controller.rb
|
271
273
|
- spec/apps/dummy/app/controllers/mock_users_controller.rb
|
272
274
|
- spec/apps/dummy/app/models/mock_group.rb
|