scimitar 1.10.0 → 2.0.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.
Files changed (61) hide show
  1. checksums.yaml +4 -4
  2. data/app/controllers/scimitar/active_record_backed_resources_controller.rb +23 -98
  3. data/app/controllers/scimitar/application_controller.rb +13 -41
  4. data/app/controllers/scimitar/resource_types_controller.rb +2 -0
  5. data/app/controllers/scimitar/resources_controller.rb +2 -0
  6. data/app/controllers/scimitar/schemas_controller.rb +3 -366
  7. data/app/controllers/scimitar/service_provider_configurations_controller.rb +1 -0
  8. data/app/models/scimitar/complex_types/address.rb +6 -0
  9. data/app/models/scimitar/engine_configuration.rb +5 -15
  10. data/app/models/scimitar/error_response.rb +0 -12
  11. data/app/models/scimitar/lists/query_parser.rb +13 -113
  12. data/app/models/scimitar/resource_invalid_error.rb +1 -1
  13. data/app/models/scimitar/resources/base.rb +9 -53
  14. data/app/models/scimitar/resources/mixin.rb +59 -646
  15. data/app/models/scimitar/schema/address.rb +0 -1
  16. data/app/models/scimitar/schema/attribute.rb +5 -14
  17. data/app/models/scimitar/schema/base.rb +1 -1
  18. data/app/models/scimitar/schema/name.rb +2 -2
  19. data/app/models/scimitar/schema/user.rb +10 -10
  20. data/app/models/scimitar/schema/vdtp.rb +1 -1
  21. data/app/models/scimitar/service_provider_configuration.rb +3 -14
  22. data/config/initializers/scimitar.rb +3 -69
  23. data/lib/scimitar/engine.rb +12 -57
  24. data/lib/scimitar/support/hash_with_indifferent_case_insensitive_access.rb +10 -140
  25. data/lib/scimitar/version.rb +2 -2
  26. data/lib/scimitar.rb +2 -7
  27. data/spec/apps/dummy/app/controllers/mock_groups_controller.rb +1 -1
  28. data/spec/apps/dummy/app/models/mock_group.rb +1 -1
  29. data/spec/apps/dummy/app/models/mock_user.rb +9 -52
  30. data/spec/apps/dummy/config/application.rb +1 -0
  31. data/spec/apps/dummy/config/environments/test.rb +28 -5
  32. data/spec/apps/dummy/config/initializers/scimitar.rb +10 -90
  33. data/spec/apps/dummy/config/routes.rb +7 -28
  34. data/spec/apps/dummy/db/migrate/20210304014602_create_mock_users.rb +1 -11
  35. data/spec/apps/dummy/db/migrate/20210308044214_create_join_table_mock_groups_mock_users.rb +3 -8
  36. data/spec/apps/dummy/db/schema.rb +4 -12
  37. data/spec/controllers/scimitar/application_controller_spec.rb +3 -126
  38. data/spec/controllers/scimitar/resource_types_controller_spec.rb +2 -2
  39. data/spec/controllers/scimitar/schemas_controller_spec.rb +48 -344
  40. data/spec/models/scimitar/complex_types/address_spec.rb +4 -3
  41. data/spec/models/scimitar/complex_types/email_spec.rb +2 -0
  42. data/spec/models/scimitar/lists/query_parser_spec.rb +9 -146
  43. data/spec/models/scimitar/resources/base_spec.rb +71 -217
  44. data/spec/models/scimitar/resources/base_validation_spec.rb +5 -43
  45. data/spec/models/scimitar/resources/mixin_spec.rb +129 -1508
  46. data/spec/models/scimitar/schema/attribute_spec.rb +3 -22
  47. data/spec/models/scimitar/schema/base_spec.rb +1 -1
  48. data/spec/models/scimitar/schema/user_spec.rb +2 -12
  49. data/spec/requests/active_record_backed_resources_controller_spec.rb +66 -1016
  50. data/spec/requests/application_controller_spec.rb +3 -16
  51. data/spec/requests/engine_spec.rb +0 -75
  52. data/spec/spec_helper.rb +1 -9
  53. data/spec/support/hash_with_indifferent_case_insensitive_access_spec.rb +0 -108
  54. metadata +26 -37
  55. data/LICENSE.txt +0 -21
  56. data/README.md +0 -717
  57. data/lib/scimitar/support/utilities.rb +0 -111
  58. data/spec/apps/dummy/app/controllers/custom_create_mock_users_controller.rb +0 -25
  59. data/spec/apps/dummy/app/controllers/custom_replace_mock_users_controller.rb +0 -25
  60. data/spec/apps/dummy/app/controllers/custom_save_mock_users_controller.rb +0 -24
  61. data/spec/apps/dummy/app/controllers/custom_update_mock_users_controller.rb +0 -25
@@ -1,7 +1,6 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  RSpec.describe Scimitar::SchemasController do
4
- routes { Scimitar::Engine.routes }
5
4
 
6
5
  before(:each) { allow(controller).to receive(:authenticated?).and_return(true) }
7
6
 
@@ -10,362 +9,67 @@ RSpec.describe Scimitar::SchemasController do
10
9
  super
11
10
  end
12
11
  end
13
-
14
12
  context '#index' do
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
19
-
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
43
-
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
13
+ it 'returns a collection of supported schemas' do
14
+ get :index, params: { format: :scim }
15
+ expect(response).to be_ok
16
+ parsed_body = JSON.parse(response.body)
17
+ expect(parsed_body.length).to eql(2)
18
+ schema_names = parsed_body.map {|schema| schema['name']}
19
+ expect(schema_names).to match_array(['User', 'Group'])
69
20
  end
70
21
 
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
89
- end
90
-
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
105
- end
106
-
107
- license_resource = Class.new(Scimitar::Resources::Base) do
108
- set_schema(license_schema)
109
- def self.endpoint; '/License'; end
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')
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"
122
-
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
134
- end
135
-
136
- it_behaves_like 'a Schema list which'
137
-
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
22
+ it 'returns only the User schema when its id is provided' do
23
+ get :index, params: { name: Scimitar::Schema::User.id, format: :scim }
24
+ expect(response).to be_ok
25
+ parsed_body = JSON.parse(response.body)
26
+ expect(parsed_body['name']).to eql('User')
27
+ end
168
28
 
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' }
29
+ it 'returns only the Group schema when its id is provided' do
30
+ get :index, params: { name: Scimitar::Schema::Group.id, format: :scim }
31
+ expect(response).to be_ok
32
+ parsed_body = JSON.parse(response.body)
33
+ expect(parsed_body['name']).to eql('Group')
34
+ end
172
35
 
173
- expect( value_attr['mutability']).to eql('readOnly')
174
- expect(display_attr['mutability']).to eql('readOnly')
175
- end
36
+ context 'with custom resource types' do
37
+ around :each do | example |
38
+ example.run()
39
+ ensure
40
+ Scimitar::Engine.reset_custom_resources
176
41
  end
177
42
 
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
43
+ it 'returns only the License schemas when its id is provided' do
44
+ license_schema = Class.new(Scimitar::Schema::Base) do
45
+ def initialize(options = {})
46
+ super(name: 'License',
47
+ id: self.class.id,
48
+ description: 'Represents a License')
196
49
  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
50
+ def self.id
51
+ 'License'
205
52
  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
53
+ def self.scim_attributes
54
+ []
215
55
  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
56
  end
226
57
 
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')
58
+ license_resource = Class.new(Scimitar::Resources::Base) do
59
+ set_schema license_schema
60
+ def self.endopint
61
+ '/Gaga'
276
62
  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)
63
+ end
353
64
 
354
- number_attr = attributes.find { | a | a['name'] == 'licenseNumber' }
355
- expiry_attr = attributes.find { | a | a['name'] == 'licenseExpired' }
65
+ Scimitar::Engine.add_custom_resource(license_resource)
356
66
 
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
67
+ get :index, params: { name: license_schema.id, format: :scim }
68
+ expect(response).to be_ok
69
+ parsed_body = JSON.parse(response.body)
70
+ expect(parsed_body['name']).to eql('License')
71
+ end
72
+ end
73
+ end
371
74
  end
75
+
@@ -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 no defaults' do
6
- expect(described_class.new.as_json).to eq({})
5
+ it 'assumes a type of "work" as a default' do
6
+ expect(described_class.new.as_json).to eq('type' => 'work')
7
7
  end
8
8
 
9
9
  it 'allows a custom address type' do
@@ -11,8 +11,9 @@ 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('country' => 'NZ')
14
+ expect(described_class.new(country: 'NZ').as_json).to eq('type' => 'work', 'country' => 'NZ')
15
15
  end
16
16
  end
17
17
 
18
18
  end
19
+
@@ -18,4 +18,6 @@ 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
+
21
22
  end
23
+