scimitar 2.5.0 → 2.11.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/LICENSE.txt +21 -0
- data/README.md +721 -0
- data/app/controllers/scimitar/active_record_backed_resources_controller.rb +72 -18
- data/app/controllers/scimitar/application_controller.rb +17 -9
- data/app/controllers/scimitar/resource_types_controller.rb +7 -3
- data/app/controllers/scimitar/resources_controller.rb +0 -2
- data/app/controllers/scimitar/schemas_controller.rb +366 -3
- data/app/controllers/scimitar/service_provider_configurations_controller.rb +3 -2
- data/app/models/scimitar/complex_types/address.rb +0 -6
- data/app/models/scimitar/complex_types/base.rb +2 -2
- data/app/models/scimitar/engine_configuration.rb +3 -1
- data/app/models/scimitar/lists/query_parser.rb +97 -12
- data/app/models/scimitar/resource_invalid_error.rb +1 -1
- data/app/models/scimitar/resource_type.rb +4 -6
- data/app/models/scimitar/resources/base.rb +52 -8
- data/app/models/scimitar/resources/mixin.rb +539 -76
- data/app/models/scimitar/schema/attribute.rb +18 -8
- data/app/models/scimitar/schema/base.rb +2 -2
- data/app/models/scimitar/schema/name.rb +2 -2
- data/app/models/scimitar/schema/user.rb +10 -10
- data/config/initializers/scimitar.rb +49 -3
- data/lib/scimitar/engine.rb +57 -12
- data/lib/scimitar/support/hash_with_indifferent_case_insensitive_access.rb +140 -10
- data/lib/scimitar/support/utilities.rb +111 -0
- data/lib/scimitar/version.rb +2 -2
- data/lib/scimitar.rb +1 -0
- data/spec/apps/dummy/app/controllers/custom_create_mock_users_controller.rb +25 -0
- data/spec/apps/dummy/app/controllers/custom_replace_mock_users_controller.rb +25 -0
- data/spec/apps/dummy/app/controllers/custom_save_mock_users_controller.rb +24 -0
- data/spec/apps/dummy/app/controllers/custom_update_mock_users_controller.rb +25 -0
- data/spec/apps/dummy/app/models/mock_user.rb +20 -3
- data/spec/apps/dummy/config/application.rb +8 -0
- data/spec/apps/dummy/config/initializers/scimitar.rb +40 -3
- data/spec/apps/dummy/config/routes.rb +18 -1
- data/spec/apps/dummy/db/migrate/20210304014602_create_mock_users.rb +2 -0
- data/spec/apps/dummy/db/schema.rb +3 -1
- data/spec/controllers/scimitar/application_controller_spec.rb +56 -2
- data/spec/controllers/scimitar/resource_types_controller_spec.rb +8 -4
- data/spec/controllers/scimitar/schemas_controller_spec.rb +344 -48
- data/spec/controllers/scimitar/service_provider_configurations_controller_spec.rb +1 -0
- data/spec/models/scimitar/complex_types/address_spec.rb +3 -4
- data/spec/models/scimitar/lists/query_parser_spec.rb +70 -0
- data/spec/models/scimitar/resources/base_spec.rb +55 -13
- data/spec/models/scimitar/resources/base_validation_spec.rb +16 -3
- data/spec/models/scimitar/resources/mixin_spec.rb +781 -124
- data/spec/models/scimitar/schema/attribute_spec.rb +22 -0
- data/spec/models/scimitar/schema/user_spec.rb +2 -2
- data/spec/requests/active_record_backed_resources_controller_spec.rb +723 -40
- data/spec/requests/engine_spec.rb +75 -0
- data/spec/spec_helper.rb +10 -2
- data/spec/support/hash_with_indifferent_case_insensitive_access_spec.rb +108 -0
- metadata +42 -34
@@ -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
|
|
@@ -9,67 +10,362 @@ RSpec.describe Scimitar::SchemasController do
|
|
9
10
|
super
|
10
11
|
end
|
11
12
|
end
|
13
|
+
|
12
14
|
context '#index' do
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
expect(parsed_body.length).to eql(3)
|
18
|
-
schema_names = parsed_body.map {|schema| schema['name']}
|
19
|
-
expect(schema_names).to match_array(['User', 'ExtendedUser', 'Group'])
|
20
|
-
end
|
15
|
+
shared_examples 'a Schema list which' do
|
16
|
+
it 'returns a valid ListResponse' do
|
17
|
+
get :index, params: { format: :scim }
|
18
|
+
expect(response).to be_ok
|
21
19
|
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
20
|
+
parsed_body = JSON.parse(response.body)
|
21
|
+
schema_count = parsed_body['Resources']&.size
|
22
|
+
|
23
|
+
expect(parsed_body['schemas' ]).to match_array(['urn:ietf:params:scim:api:messages:2.0:ListResponse'])
|
24
|
+
expect(parsed_body['totalResults']).to eql(schema_count)
|
25
|
+
expect(parsed_body['itemsPerPage']).to eql(schema_count)
|
26
|
+
expect(parsed_body['startIndex' ]).to eql(1)
|
27
|
+
end
|
28
|
+
|
29
|
+
it 'returns a collection of supported schemas' do
|
30
|
+
get :index, params: { format: :scim }
|
31
|
+
expect(response).to be_ok
|
32
|
+
|
33
|
+
parsed_body = JSON.parse(response.body)
|
34
|
+
expect(parsed_body['Resources']&.size).to eql(4)
|
35
|
+
|
36
|
+
schema_names = parsed_body['Resources'].map {|schema| schema['name']}
|
37
|
+
expect(schema_names).to match_array(['User', 'EnterpriseExtendedUser', 'ManagementExtendedUser', 'Group'])
|
38
|
+
end
|
39
|
+
|
40
|
+
it 'returns only the User schema when its ID is provided' do
|
41
|
+
get :index, params: { name: Scimitar::Schema::User.id, format: :scim }
|
42
|
+
expect(response).to be_ok
|
28
43
|
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
44
|
+
parsed_body = JSON.parse(response.body)
|
45
|
+
expect(parsed_body.dig('Resources', 0, 'name')).to eql('User')
|
46
|
+
end
|
47
|
+
|
48
|
+
it 'includes the controller customised schema location' do
|
49
|
+
get :index, params: { name: Scimitar::Schema::User.id, format: :scim }
|
50
|
+
expect(response).to be_ok
|
51
|
+
|
52
|
+
parsed_body = JSON.parse(response.body)
|
53
|
+
expect(parsed_body.dig('Resources', 0, 'meta', 'location')).to eq scim_schemas_url(name: Scimitar::Schema::User.id, test: 1)
|
54
|
+
end
|
55
|
+
|
56
|
+
it 'returns only the Group schema when its ID is provided' do
|
57
|
+
get :index, params: { name: Scimitar::Schema::Group.id, format: :scim }
|
58
|
+
expect(response).to be_ok
|
59
|
+
|
60
|
+
parsed_body = JSON.parse(response.body)
|
61
|
+
|
62
|
+
expect(parsed_body['Resources' ]&.size).to eql(1)
|
63
|
+
expect(parsed_body['totalResults'] ).to eql(1)
|
64
|
+
expect(parsed_body['itemsPerPage'] ).to eql(1)
|
65
|
+
expect(parsed_body['startIndex' ] ).to eql(1)
|
66
|
+
|
67
|
+
expect(parsed_body.dig('Resources', 0, 'name')).to eql('Group')
|
68
|
+
end
|
34
69
|
end
|
35
70
|
|
36
|
-
context 'with
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
Scimitar::
|
71
|
+
context 'with default engine configuration of schema_list_from_attribute_mappings undefined' do
|
72
|
+
it_behaves_like 'a Schema list which'
|
73
|
+
|
74
|
+
it 'returns all attributes' do
|
75
|
+
get :index, params: { name: Scimitar::Schema::User.id, format: :scim }
|
76
|
+
expect(response).to be_ok
|
77
|
+
|
78
|
+
parsed_body = JSON.parse(response.body)
|
79
|
+
user_attrs = parsed_body['Resources'].find { | r | r['name'] == 'User' }
|
80
|
+
|
81
|
+
expect(user_attrs['attributes'].find { | a | a['name'] == 'ims' }).to be_present
|
82
|
+
expect(user_attrs['attributes'].find { | a | a['name'] == 'entitlements' }).to be_present
|
83
|
+
expect(user_attrs['attributes'].find { | a | a['name'] == 'x509Certificates' }).to be_present
|
84
|
+
|
85
|
+
name_attr = user_attrs['attributes'].find { | a | a['name'] == 'name' }
|
86
|
+
|
87
|
+
expect(name_attr['subAttributes'].find { | s | s['name'] == 'honorificPrefix' }).to be_present
|
88
|
+
expect(name_attr['subAttributes'].find { | s | s['name'] == 'honorificSuffix' }).to be_present
|
41
89
|
end
|
42
90
|
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
91
|
+
context 'with custom resource types' do
|
92
|
+
around :each do | example |
|
93
|
+
example.run()
|
94
|
+
ensure
|
95
|
+
Scimitar::Engine.reset_custom_resources
|
96
|
+
end
|
97
|
+
|
98
|
+
it 'returns only the License schemas when its ID is provided' do
|
99
|
+
license_schema = Class.new(Scimitar::Schema::Base) do
|
100
|
+
def initialize(options = {})
|
101
|
+
super(name: 'License', id: self.class.id(), description: 'Represents a License')
|
102
|
+
end
|
103
|
+
def self.id; 'urn:ietf:params:scim:schemas:license'; end
|
104
|
+
def self.scim_attributes; []; end
|
52
105
|
end
|
53
|
-
|
54
|
-
|
106
|
+
|
107
|
+
license_resource = Class.new(Scimitar::Resources::Base) do
|
108
|
+
set_schema(license_schema)
|
109
|
+
def self.endpoint; '/License'; end
|
55
110
|
end
|
111
|
+
|
112
|
+
Scimitar::Engine.add_custom_resource(license_resource)
|
113
|
+
|
114
|
+
get :index, params: { name: license_schema.id, format: :scim }
|
115
|
+
expect(response).to be_ok
|
116
|
+
|
117
|
+
parsed_body = JSON.parse(response.body)
|
118
|
+
expect(parsed_body.dig('Resources', 0, 'name')).to eql('License')
|
56
119
|
end
|
120
|
+
end # "context 'with custom resource types' do"
|
121
|
+
end # "context 'with default engine configuration of schema_list_from_attribute_mappings undefined' do"
|
57
122
|
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
123
|
+
context 'with engine configuration of schema_list_from_attribute_mappings set' do
|
124
|
+
context 'standard resources' do
|
125
|
+
around :each do | example |
|
126
|
+
old_config = Scimitar.engine_configuration.schema_list_from_attribute_mappings
|
127
|
+
Scimitar.engine_configuration.schema_list_from_attribute_mappings = [
|
128
|
+
MockUser,
|
129
|
+
MockGroup
|
130
|
+
]
|
131
|
+
example.run()
|
132
|
+
ensure
|
133
|
+
Scimitar.engine_configuration.schema_list_from_attribute_mappings = old_config
|
63
134
|
end
|
64
135
|
|
65
|
-
|
136
|
+
it_behaves_like 'a Schema list which'
|
66
137
|
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
138
|
+
it 'returns only mapped attributes' do
|
139
|
+
get :index, params: { name: Scimitar::Schema::User.id, format: :scim }
|
140
|
+
expect(response).to be_ok
|
141
|
+
|
142
|
+
parsed_body = JSON.parse(response.body)
|
143
|
+
user_attrs = parsed_body['Resources'].find { | r | r['name'] == 'User' }
|
144
|
+
password_attr = user_attrs['attributes'].find { | a | a['name'] == 'password' }
|
145
|
+
|
146
|
+
expect(password_attr['mutability']).to eql('writeOnly')
|
147
|
+
|
148
|
+
expect(user_attrs['attributes'].find { | a | a['name'] == 'ims' }).to_not be_present
|
149
|
+
expect(user_attrs['attributes'].find { | a | a['name'] == 'entitlements' }).to_not be_present
|
150
|
+
expect(user_attrs['attributes'].find { | a | a['name'] == 'x509Certificates' }).to_not be_present
|
151
|
+
|
152
|
+
name_attr = user_attrs['attributes'].find { | a | a['name'] == 'name' }
|
153
|
+
|
154
|
+
expect(name_attr['subAttributes'].find { | s | s['name'] == 'givenName' }).to be_present
|
155
|
+
expect(name_attr['subAttributes'].find { | s | s['name'] == 'familyName' }).to be_present
|
156
|
+
expect(name_attr['subAttributes'].find { | s | s['name'] == 'honorificPrefix' }).to_not be_present
|
157
|
+
expect(name_attr['subAttributes'].find { | s | s['name'] == 'honorificSuffix' }).to_not be_present
|
158
|
+
|
159
|
+
emails_attr = user_attrs['attributes' ].find { | a | a['name'] == 'emails' }
|
160
|
+
value_attr = emails_attr['subAttributes'].find { | a | a['name'] == 'value' }
|
161
|
+
primary_attr = emails_attr['subAttributes'].find { | a | a['name'] == 'primary' }
|
162
|
+
|
163
|
+
expect( value_attr['mutability']).to eql('readWrite')
|
164
|
+
expect(primary_attr['mutability']).to eql('readOnly')
|
165
|
+
|
166
|
+
expect(emails_attr['subAttributes'].find { | s | s['name'] == 'type' }).to_not be_present
|
167
|
+
expect(emails_attr['subAttributes'].find { | s | s['name'] == 'display' }).to_not be_present
|
168
|
+
|
169
|
+
groups_attr = user_attrs['attributes' ].find { | a | a['name'] == 'groups' }
|
170
|
+
value_attr = groups_attr['subAttributes'].find { | a | a['name'] == 'value' }
|
171
|
+
display_attr = groups_attr['subAttributes'].find { | a | a['name'] == 'display' }
|
172
|
+
|
173
|
+
expect( value_attr['mutability']).to eql('readOnly')
|
174
|
+
expect(display_attr['mutability']).to eql('readOnly')
|
175
|
+
end
|
71
176
|
end
|
72
|
-
end
|
73
|
-
end
|
74
|
-
end
|
75
177
|
|
178
|
+
context 'with custom resource types' do
|
179
|
+
let(:license_schema) {
|
180
|
+
Class.new(Scimitar::Schema::Base) do
|
181
|
+
def initialize(options = {})
|
182
|
+
super(
|
183
|
+
id: self.class.id(),
|
184
|
+
name: 'License',
|
185
|
+
description: 'Represents a license',
|
186
|
+
scim_attributes: self.class.scim_attributes
|
187
|
+
)
|
188
|
+
end
|
189
|
+
def self.id; 'urn:ietf:params:scim:schemas:license'; end
|
190
|
+
def self.scim_attributes
|
191
|
+
[
|
192
|
+
Scimitar::Schema::Attribute.new(name: 'licenseNumber', type: 'string'),
|
193
|
+
Scimitar::Schema::Attribute.new(name: 'licenseExpired', type: 'boolean', mutability: 'readOnly'),
|
194
|
+
]
|
195
|
+
end
|
196
|
+
end
|
197
|
+
}
|
198
|
+
|
199
|
+
let(:license_resource) {
|
200
|
+
local_var_license_schema = license_schema()
|
201
|
+
|
202
|
+
Class.new(Scimitar::Resources::Base) do
|
203
|
+
set_schema(local_var_license_schema)
|
204
|
+
def self.endpoint; '/License'; end
|
205
|
+
end
|
206
|
+
}
|
207
|
+
|
208
|
+
let(:license_model_base) {
|
209
|
+
local_var_license_resource = license_resource()
|
210
|
+
|
211
|
+
Class.new do
|
212
|
+
singleton_class.class_eval do
|
213
|
+
define_method(:scim_resource_type) { local_var_license_resource }
|
214
|
+
end
|
215
|
+
end
|
216
|
+
}
|
217
|
+
|
218
|
+
around :each do | example |
|
219
|
+
old_config = Scimitar.engine_configuration.schema_list_from_attribute_mappings
|
220
|
+
Scimitar::Engine.add_custom_resource(license_resource())
|
221
|
+
example.run()
|
222
|
+
ensure
|
223
|
+
Scimitar.engine_configuration.schema_list_from_attribute_mappings = old_config
|
224
|
+
Scimitar::Engine.reset_custom_resources
|
225
|
+
end
|
226
|
+
|
227
|
+
context 'with an empty attribute map' do
|
228
|
+
it 'returns no attributes' do
|
229
|
+
license_model = Class.new(license_model_base()) do
|
230
|
+
attr_accessor :license_number
|
231
|
+
|
232
|
+
def self.scim_mutable_attributes; nil; end
|
233
|
+
def self.scim_queryable_attributes; nil; end
|
234
|
+
def self.scim_attributes_map; {}; end # Empty map
|
235
|
+
|
236
|
+
include Scimitar::Resources::Mixin
|
237
|
+
end
|
238
|
+
|
239
|
+
Scimitar.engine_configuration.schema_list_from_attribute_mappings = [license_model]
|
240
|
+
|
241
|
+
get :index, params: { format: :scim }
|
242
|
+
expect(response).to be_ok
|
243
|
+
|
244
|
+
parsed_body = JSON.parse(response.body)
|
245
|
+
|
246
|
+
expect(parsed_body.dig('Resources', 0, 'name' )).to eql('License')
|
247
|
+
expect(parsed_body.dig('Resources', 0, 'attributes')).to be_empty
|
248
|
+
end
|
249
|
+
end # "context 'with an empty attribute map' do"
|
250
|
+
|
251
|
+
context 'with a defined attribute map' do
|
252
|
+
it 'returns only the License schemas when its ID is provided' do
|
253
|
+
license_model = Class.new(license_model_base()) do
|
254
|
+
attr_accessor :license_number
|
255
|
+
|
256
|
+
def self.scim_mutable_attributes; nil; end
|
257
|
+
def self.scim_queryable_attributes; nil; end
|
258
|
+
def self.scim_attributes_map # Simple map
|
259
|
+
{ licenseNumber: :license_number }
|
260
|
+
end
|
261
|
+
|
262
|
+
include Scimitar::Resources::Mixin
|
263
|
+
end
|
264
|
+
|
265
|
+
Scimitar.engine_configuration.schema_list_from_attribute_mappings = [license_model]
|
266
|
+
|
267
|
+
get :index, params: { format: :scim }
|
268
|
+
expect(response).to be_ok
|
269
|
+
|
270
|
+
parsed_body = JSON.parse(response.body)
|
271
|
+
|
272
|
+
expect(parsed_body.dig('Resources', 0, 'name' )).to eql('License')
|
273
|
+
expect(parsed_body.dig('Resources', 0, 'attributes').size ).to eql(1)
|
274
|
+
expect(parsed_body.dig('Resources', 0, 'attributes', 0, 'name' )).to eql('licenseNumber')
|
275
|
+
expect(parsed_body.dig('Resources', 0, 'attributes', 0, 'mutability')).to eql('readWrite')
|
276
|
+
end
|
277
|
+
end # "context 'with a defined attribute map' do"
|
278
|
+
|
279
|
+
context 'with mutability overridden' do
|
280
|
+
it 'returns read-only when expected' do
|
281
|
+
license_model = Class.new(license_model_base()) do
|
282
|
+
attr_accessor :license_number
|
283
|
+
|
284
|
+
def self.scim_mutable_attributes; []; end # Note empty array, NOT "nil" - no mutable attributes
|
285
|
+
def self.scim_queryable_attributes; nil; end
|
286
|
+
def self.scim_attributes_map
|
287
|
+
{ licenseNumber: :license_number }
|
288
|
+
end
|
289
|
+
|
290
|
+
include Scimitar::Resources::Mixin
|
291
|
+
end
|
292
|
+
|
293
|
+
Scimitar.engine_configuration.schema_list_from_attribute_mappings = [license_model]
|
294
|
+
|
295
|
+
get :index, params: { format: :scim }
|
296
|
+
expect(response).to be_ok
|
297
|
+
|
298
|
+
parsed_body = JSON.parse(response.body)
|
299
|
+
|
300
|
+
expect(parsed_body.dig('Resources', 0, 'name' )).to eql('License')
|
301
|
+
expect(parsed_body.dig('Resources', 0, 'attributes').size ).to eql(1)
|
302
|
+
expect(parsed_body.dig('Resources', 0, 'attributes', 0, 'name' )).to eql('licenseNumber')
|
303
|
+
expect(parsed_body.dig('Resources', 0, 'attributes', 0, 'mutability')).to eql('readOnly')
|
304
|
+
end
|
305
|
+
|
306
|
+
it 'returns write-only when expected' do
|
307
|
+
license_model = Class.new(license_model_base()) do
|
308
|
+
attr_writer :license_number # Writer only, no reader
|
309
|
+
|
310
|
+
def self.scim_mutable_attributes; nil; end
|
311
|
+
def self.scim_queryable_attributes; nil; end
|
312
|
+
def self.scim_attributes_map
|
313
|
+
{ licenseNumber: :license_number }
|
314
|
+
end
|
315
|
+
|
316
|
+
include Scimitar::Resources::Mixin
|
317
|
+
end
|
318
|
+
|
319
|
+
Scimitar.engine_configuration.schema_list_from_attribute_mappings = [license_model]
|
320
|
+
|
321
|
+
get :index, params: { format: :scim }
|
322
|
+
expect(response).to be_ok
|
323
|
+
|
324
|
+
parsed_body = JSON.parse(response.body)
|
325
|
+
|
326
|
+
expect(parsed_body.dig('Resources', 0, 'name' )).to eql('License')
|
327
|
+
expect(parsed_body.dig('Resources', 0, 'attributes').size ).to eql(1)
|
328
|
+
expect(parsed_body.dig('Resources', 0, 'attributes', 0, 'name' )).to eql('licenseNumber')
|
329
|
+
expect(parsed_body.dig('Resources', 0, 'attributes', 0, 'mutability')).to eql('writeOnly')
|
330
|
+
end
|
331
|
+
|
332
|
+
it 'handles conflicts via reality-wins' do
|
333
|
+
license_model = Class.new(license_model_base()) do
|
334
|
+
def self.scim_mutable_attributes; [:licence_expired]; end
|
335
|
+
def self.scim_queryable_attributes; nil; end
|
336
|
+
def self.scim_attributes_map
|
337
|
+
{ licenseNumber: :license_number, licenseExpired: :licence_expired }
|
338
|
+
end
|
339
|
+
|
340
|
+
include Scimitar::Resources::Mixin
|
341
|
+
end
|
342
|
+
|
343
|
+
Scimitar.engine_configuration.schema_list_from_attribute_mappings = [license_model]
|
344
|
+
|
345
|
+
get :index, params: { format: :scim }
|
346
|
+
expect(response).to be_ok
|
347
|
+
|
348
|
+
parsed_body = JSON.parse(response.body)
|
349
|
+
attributes = parsed_body.dig('Resources', 0, 'attributes')
|
350
|
+
|
351
|
+
expect(parsed_body.dig('Resources', 0, 'name')).to eql('License')
|
352
|
+
expect(attributes.size).to eql(2)
|
353
|
+
|
354
|
+
number_attr = attributes.find { | a | a['name'] == 'licenseNumber' }
|
355
|
+
expiry_attr = attributes.find { | a | a['name'] == 'licenseExpired' }
|
356
|
+
|
357
|
+
# Number attribute - no reader or writer, so code has to shrug and
|
358
|
+
# say "it's broken, so I'll quote the schema verbatim'.
|
359
|
+
#
|
360
|
+
# Expiry attribute - is read-only in schema, but we declare it as a
|
361
|
+
# writable attribute and provide no reader. This clashes badly; the
|
362
|
+
# schema read-only declaration is ignored in favour of reality.
|
363
|
+
#
|
364
|
+
expect(number_attr['mutability']).to eql('readWrite')
|
365
|
+
expect(expiry_attr['mutability']).to eql('writeOnly')
|
366
|
+
end
|
367
|
+
end # "context 'with mutability overridden' do"
|
368
|
+
end # "context 'with custom resource types' do"
|
369
|
+
end # "context 'with engine configuration of schema_list_from_attribute_mappings: true' do"
|
370
|
+
end # "context '#index' do
|
371
|
+
end
|
@@ -15,6 +15,7 @@ RSpec.describe Scimitar::ServiceProviderConfigurationsController do
|
|
15
15
|
|
16
16
|
expect(response).to be_ok
|
17
17
|
expect(JSON.parse(response.body)).to include('patch' => {'supported' => true})
|
18
|
+
expect(JSON.parse(response.body)).to_not include('uses_defaults' => true)
|
18
19
|
expect(JSON.parse(response.body)).to include('filter' => {'maxResults' => Scimitar::Filter::MAX_RESULTS_DEFAULT, 'supported' => true})
|
19
20
|
end
|
20
21
|
end
|
@@ -2,8 +2,8 @@ require 'spec_helper'
|
|
2
2
|
|
3
3
|
RSpec.describe Scimitar::ComplexTypes::Address do
|
4
4
|
context '#as_json' do
|
5
|
-
it 'assumes
|
6
|
-
expect(described_class.new.as_json).to eq(
|
5
|
+
it 'assumes no defaults' do
|
6
|
+
expect(described_class.new.as_json).to eq({})
|
7
7
|
end
|
8
8
|
|
9
9
|
it 'allows a custom address type' do
|
@@ -11,9 +11,8 @@ RSpec.describe Scimitar::ComplexTypes::Address do
|
|
11
11
|
end
|
12
12
|
|
13
13
|
it 'shows the set address' do
|
14
|
-
expect(described_class.new(country: 'NZ').as_json).to eq('
|
14
|
+
expect(described_class.new(country: 'NZ').as_json).to eq('country' => 'NZ')
|
15
15
|
end
|
16
16
|
end
|
17
17
|
|
18
18
|
end
|
19
|
-
|
@@ -60,6 +60,15 @@ RSpec.describe Scimitar::Lists::QueryParser do
|
|
60
60
|
expect(%Q("O'Malley")).to eql(tree[2])
|
61
61
|
end
|
62
62
|
|
63
|
+
it "extended attribute equals" do
|
64
|
+
@instance.parse(%Q(primaryEmail eq "foo@bar.com"))
|
65
|
+
|
66
|
+
rpn = @instance.rpn
|
67
|
+
expect('primaryEmail').to eql(rpn[0])
|
68
|
+
expect(%Q("foo@bar.com")).to eql(rpn[1])
|
69
|
+
expect('eq').to eql(rpn[2])
|
70
|
+
end
|
71
|
+
|
63
72
|
it "user name starts with" do
|
64
73
|
@instance.parse(%Q(userName sw "J"))
|
65
74
|
|
@@ -337,6 +346,67 @@ RSpec.describe Scimitar::Lists::QueryParser do
|
|
337
346
|
result = @instance.send(:flatten_filter, 'emails[type eq "work" and value co "@example.com" ] or userType eq "Admin" or ims[type eq "xmpp" and value co "@foo.com"]')
|
338
347
|
expect(result).to eql('emails.type eq "work" and emails.value co "@example.com" or userType eq "Admin" or ims.type eq "xmpp" and ims.value co "@foo.com"')
|
339
348
|
end
|
349
|
+
|
350
|
+
it 'handles an example previously described as unsupported in README.md' do
|
351
|
+
result = @instance.send(:flatten_filter, 'filter=userType eq "Employee" and emails[type eq "work" and value co "@example.com"]')
|
352
|
+
expect(result).to eql('filter=userType eq "Employee" and emails.type eq "work" and emails.value co "@example.com"')
|
353
|
+
end
|
354
|
+
|
355
|
+
# https://github.com/pond/scimitar/issues/116
|
356
|
+
#
|
357
|
+
context 'with schema IDs (GitHub issue #116)' do
|
358
|
+
it 'handles simple attributes' do
|
359
|
+
result = @instance.send(:flatten_filter, 'urn:ietf:params:scim:schemas:extension:enterprise:2.0:User:employeeId eq "gsar"')
|
360
|
+
expect(result).to eql('employeeId eq "gsar"')
|
361
|
+
end
|
362
|
+
|
363
|
+
it 'handles dotted attribute paths' do
|
364
|
+
result = @instance.send(:flatten_filter, 'urn:ietf:params:scim:schemas:extension:enterprise:2.0:User:imaginary.path eq "gsar"')
|
365
|
+
expect(result).to eql('imaginary.path eq "gsar"')
|
366
|
+
end
|
367
|
+
|
368
|
+
it 'replaces all examples' do
|
369
|
+
result = @instance.send(:flatten_filter, 'urn:ietf:params:scim:schemas:extension:enterprise:2.0:User:employeeId eq "gsar" or urn:ietf:params:scim:schemas:extension:enterprise:2.0:User:imaginary.path eq "gsar"')
|
370
|
+
expect(result).to eql('employeeId eq "gsar" or imaginary.path eq "gsar"')
|
371
|
+
end
|
372
|
+
|
373
|
+
it 'handles the square bracket form with schema ID at the root' do
|
374
|
+
result = @instance.send(:flatten_filter, 'urn:ietf:params:scim:schemas:extension:enterprise:2.0:User[employeeId eq "gsar"')
|
375
|
+
expect(result).to eql('employeeId eq "gsar"')
|
376
|
+
end
|
377
|
+
|
378
|
+
it 'handles the square bracket form with schema ID and attribute at the root' do
|
379
|
+
result = @instance.send(:flatten_filter, 'urn:ietf:params:scim:schemas:extension:enterprise:2.0:User:imaginary[path eq "gsar"')
|
380
|
+
expect(result).to eql('imaginary.path eq "gsar"')
|
381
|
+
end
|
382
|
+
end
|
383
|
+
|
384
|
+
# https://github.com/pond/scimitar/issues/115
|
385
|
+
#
|
386
|
+
context 'broken filters from Microsoft (GitHub issue #115)' do
|
387
|
+
it 'work with "eq"' do
|
388
|
+
result = @instance.send(:flatten_filter, 'emails[type eq "work"].value eq "foo@bar.com"')
|
389
|
+
expect(result).to eql('emails.type eq "work" and emails.value eq "foo@bar.com"')
|
390
|
+
end
|
391
|
+
|
392
|
+
it 'work with "ne"' do # (just check a couple of operators, not all!)
|
393
|
+
result = @instance.send(:flatten_filter, 'emails[type eq "work"].value ne "foo@bar.com"')
|
394
|
+
expect(result).to eql('emails.type eq "work" and emails.value ne "foo@bar.com"')
|
395
|
+
end
|
396
|
+
|
397
|
+
it 'preserve input case' do
|
398
|
+
result = @instance.send(:flatten_filter, 'emaiLs[TYPE eq "work"].valUE eq "FOO@bar.com"')
|
399
|
+
expect(result).to eql('emaiLs.TYPE eq "work" and emaiLs.valUE eq "FOO@bar.com"')
|
400
|
+
end
|
401
|
+
|
402
|
+
# At the time of writing, this was used in a "belt and braces" request
|
403
|
+
# spec in 'active_record_backed_resources_controller_spec.rb'.
|
404
|
+
#
|
405
|
+
it 'handles more complex, hypothetical cases' do
|
406
|
+
result = @instance.send(:flatten_filter, 'name[givenName eq "FOO"].familyName pr and emails ne "home_1@test.com"')
|
407
|
+
expect(result).to eql('name.givenName eq "FOO" and name.familyName pr and emails ne "home_1@test.com"')
|
408
|
+
end
|
409
|
+
end # "context 'broken filters from Microsoft' do"
|
340
410
|
end # "context 'when flattening is needed' do"
|
341
411
|
|
342
412
|
context 'with bad filters' do
|