scimitar 1.10.0 → 2.0.0

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