powerhome-scimitar 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (116) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE.txt +21 -0
  3. data/README.md +708 -0
  4. data/Rakefile +16 -0
  5. data/app/controllers/scimitar/active_record_backed_resources_controller.rb +257 -0
  6. data/app/controllers/scimitar/application_controller.rb +157 -0
  7. data/app/controllers/scimitar/resource_types_controller.rb +28 -0
  8. data/app/controllers/scimitar/resources_controller.rb +203 -0
  9. data/app/controllers/scimitar/schemas_controller.rb +21 -0
  10. data/app/controllers/scimitar/service_provider_configurations_controller.rb +8 -0
  11. data/app/models/scimitar/authentication_error.rb +9 -0
  12. data/app/models/scimitar/authentication_scheme.rb +18 -0
  13. data/app/models/scimitar/bulk.rb +8 -0
  14. data/app/models/scimitar/complex_types/address.rb +12 -0
  15. data/app/models/scimitar/complex_types/base.rb +83 -0
  16. data/app/models/scimitar/complex_types/email.rb +12 -0
  17. data/app/models/scimitar/complex_types/entitlement.rb +12 -0
  18. data/app/models/scimitar/complex_types/ims.rb +12 -0
  19. data/app/models/scimitar/complex_types/name.rb +12 -0
  20. data/app/models/scimitar/complex_types/phone_number.rb +12 -0
  21. data/app/models/scimitar/complex_types/photo.rb +12 -0
  22. data/app/models/scimitar/complex_types/reference_group.rb +12 -0
  23. data/app/models/scimitar/complex_types/reference_member.rb +12 -0
  24. data/app/models/scimitar/complex_types/role.rb +12 -0
  25. data/app/models/scimitar/complex_types/x509_certificate.rb +12 -0
  26. data/app/models/scimitar/engine_configuration.rb +32 -0
  27. data/app/models/scimitar/error_response.rb +32 -0
  28. data/app/models/scimitar/errors.rb +14 -0
  29. data/app/models/scimitar/filter.rb +11 -0
  30. data/app/models/scimitar/filter_error.rb +22 -0
  31. data/app/models/scimitar/invalid_syntax_error.rb +9 -0
  32. data/app/models/scimitar/lists/count.rb +64 -0
  33. data/app/models/scimitar/lists/query_parser.rb +745 -0
  34. data/app/models/scimitar/meta.rb +7 -0
  35. data/app/models/scimitar/not_found_error.rb +10 -0
  36. data/app/models/scimitar/resource_invalid_error.rb +9 -0
  37. data/app/models/scimitar/resource_type.rb +29 -0
  38. data/app/models/scimitar/resources/base.rb +190 -0
  39. data/app/models/scimitar/resources/group.rb +13 -0
  40. data/app/models/scimitar/resources/mixin.rb +1524 -0
  41. data/app/models/scimitar/resources/user.rb +13 -0
  42. data/app/models/scimitar/schema/address.rb +25 -0
  43. data/app/models/scimitar/schema/attribute.rb +132 -0
  44. data/app/models/scimitar/schema/base.rb +90 -0
  45. data/app/models/scimitar/schema/derived_attributes.rb +24 -0
  46. data/app/models/scimitar/schema/email.rb +10 -0
  47. data/app/models/scimitar/schema/entitlement.rb +10 -0
  48. data/app/models/scimitar/schema/group.rb +27 -0
  49. data/app/models/scimitar/schema/ims.rb +10 -0
  50. data/app/models/scimitar/schema/name.rb +20 -0
  51. data/app/models/scimitar/schema/phone_number.rb +10 -0
  52. data/app/models/scimitar/schema/photo.rb +10 -0
  53. data/app/models/scimitar/schema/reference_group.rb +23 -0
  54. data/app/models/scimitar/schema/reference_member.rb +21 -0
  55. data/app/models/scimitar/schema/role.rb +10 -0
  56. data/app/models/scimitar/schema/user.rb +52 -0
  57. data/app/models/scimitar/schema/vdtp.rb +18 -0
  58. data/app/models/scimitar/schema/x509_certificate.rb +22 -0
  59. data/app/models/scimitar/service_provider_configuration.rb +60 -0
  60. data/app/models/scimitar/supportable.rb +14 -0
  61. data/app/views/layouts/scimitar/application.html.erb +14 -0
  62. data/config/initializers/scimitar.rb +111 -0
  63. data/config/routes.rb +6 -0
  64. data/lib/scimitar/engine.rb +63 -0
  65. data/lib/scimitar/support/hash_with_indifferent_case_insensitive_access.rb +216 -0
  66. data/lib/scimitar/support/utilities.rb +51 -0
  67. data/lib/scimitar/version.rb +13 -0
  68. data/lib/scimitar.rb +29 -0
  69. data/spec/apps/dummy/app/controllers/custom_create_mock_users_controller.rb +25 -0
  70. data/spec/apps/dummy/app/controllers/custom_destroy_mock_users_controller.rb +24 -0
  71. data/spec/apps/dummy/app/controllers/custom_replace_mock_users_controller.rb +25 -0
  72. data/spec/apps/dummy/app/controllers/custom_request_verifiers_controller.rb +30 -0
  73. data/spec/apps/dummy/app/controllers/custom_save_mock_users_controller.rb +24 -0
  74. data/spec/apps/dummy/app/controllers/custom_update_mock_users_controller.rb +25 -0
  75. data/spec/apps/dummy/app/controllers/mock_groups_controller.rb +13 -0
  76. data/spec/apps/dummy/app/controllers/mock_users_controller.rb +13 -0
  77. data/spec/apps/dummy/app/models/mock_group.rb +83 -0
  78. data/spec/apps/dummy/app/models/mock_user.rb +132 -0
  79. data/spec/apps/dummy/config/application.rb +18 -0
  80. data/spec/apps/dummy/config/boot.rb +2 -0
  81. data/spec/apps/dummy/config/environment.rb +2 -0
  82. data/spec/apps/dummy/config/environments/test.rb +38 -0
  83. data/spec/apps/dummy/config/initializers/cookies_serializer.rb +3 -0
  84. data/spec/apps/dummy/config/initializers/scimitar.rb +61 -0
  85. data/spec/apps/dummy/config/initializers/session_store.rb +3 -0
  86. data/spec/apps/dummy/config/routes.rb +45 -0
  87. data/spec/apps/dummy/db/migrate/20210304014602_create_mock_users.rb +24 -0
  88. data/spec/apps/dummy/db/migrate/20210308020313_create_mock_groups.rb +10 -0
  89. data/spec/apps/dummy/db/migrate/20210308044214_create_join_table_mock_groups_mock_users.rb +13 -0
  90. data/spec/apps/dummy/db/schema.rb +48 -0
  91. data/spec/controllers/scimitar/application_controller_spec.rb +296 -0
  92. data/spec/controllers/scimitar/resource_types_controller_spec.rb +94 -0
  93. data/spec/controllers/scimitar/resources_controller_spec.rb +247 -0
  94. data/spec/controllers/scimitar/schemas_controller_spec.rb +83 -0
  95. data/spec/controllers/scimitar/service_provider_configurations_controller_spec.rb +22 -0
  96. data/spec/models/scimitar/complex_types/address_spec.rb +18 -0
  97. data/spec/models/scimitar/complex_types/email_spec.rb +21 -0
  98. data/spec/models/scimitar/lists/count_spec.rb +147 -0
  99. data/spec/models/scimitar/lists/query_parser_spec.rb +830 -0
  100. data/spec/models/scimitar/resource_type_spec.rb +21 -0
  101. data/spec/models/scimitar/resources/base_spec.rb +485 -0
  102. data/spec/models/scimitar/resources/base_validation_spec.rb +86 -0
  103. data/spec/models/scimitar/resources/mixin_spec.rb +3562 -0
  104. data/spec/models/scimitar/resources/user_spec.rb +68 -0
  105. data/spec/models/scimitar/schema/attribute_spec.rb +99 -0
  106. data/spec/models/scimitar/schema/base_spec.rb +64 -0
  107. data/spec/models/scimitar/schema/group_spec.rb +87 -0
  108. data/spec/models/scimitar/schema/user_spec.rb +720 -0
  109. data/spec/requests/active_record_backed_resources_controller_spec.rb +1354 -0
  110. data/spec/requests/application_controller_spec.rb +61 -0
  111. data/spec/requests/controller_configuration_spec.rb +17 -0
  112. data/spec/requests/engine_spec.rb +45 -0
  113. data/spec/spec_helper.rb +101 -0
  114. data/spec/spec_helper_spec.rb +30 -0
  115. data/spec/support/hash_with_indifferent_case_insensitive_access_spec.rb +169 -0
  116. metadata +321 -0
@@ -0,0 +1,21 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe Scimitar::ResourceType do
4
+ context '#as_json' do
5
+
6
+ it 'adds the extensionSchemas' do
7
+ resource_type = Scimitar::ResourceType.new(
8
+ endpoint: '/Gaga',
9
+ schema: 'urn:ietf:params:scim:schemas:core:2.0:User',
10
+ schemaExtensions: ['urn:ietf:params:scim:schemas:extension:enterprise:2.0:User']
11
+ )
12
+
13
+ expect(resource_type.as_json['schemaExtensions']).to eql([{
14
+ "schema" => 'urn:ietf:params:scim:schemas:extension:enterprise:2.0:User',
15
+ "required" => false
16
+ }])
17
+
18
+ end
19
+
20
+ end
21
+ end
@@ -0,0 +1,485 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe Scimitar::Resources::Base do
4
+ context 'basic operation' do
5
+ FirstCustomSchema = Class.new(Scimitar::Schema::Base) do
6
+ def self.id
7
+ 'custom-id'
8
+ end
9
+
10
+ def self.scim_attributes
11
+ [
12
+ Scimitar::Schema::Attribute.new(
13
+ name: 'name', complexType: Scimitar::ComplexTypes::Name, required: false
14
+ ),
15
+ Scimitar::Schema::Attribute.new(
16
+ name: 'names', multiValued: true, complexType: Scimitar::ComplexTypes::Name, required: false
17
+ ),
18
+ Scimitar::Schema::Attribute.new(
19
+ name: 'privateName', complexType: Scimitar::ComplexTypes::Name, required: false, returned: 'never'
20
+ ),
21
+ ]
22
+ end
23
+ end
24
+
25
+ CustomResourse = Class.new(Scimitar::Resources::Base) do
26
+ set_schema FirstCustomSchema
27
+ end
28
+
29
+ context '#initialize' do
30
+ it 'accepts nil for non-required attributes' do
31
+ resource = CustomResourse.new(name: nil, names: nil, privateName: nil)
32
+
33
+ expect(resource.name).to be_nil
34
+ expect(resource.names).to be_nil
35
+ expect(resource.privateName).to be_nil
36
+ end
37
+
38
+ shared_examples 'an initializer' do | force_upper_case: |
39
+ it 'which builds the nested type' do
40
+ attributes = {
41
+ name: {
42
+ givenName: 'John',
43
+ familyName: 'Smith'
44
+ },
45
+ privateName: {
46
+ givenName: 'Alt John',
47
+ familyName: 'Alt Smith'
48
+ }
49
+ }
50
+
51
+ attributes = spec_helper_hupcase(attributes) if force_upper_case
52
+ resource = CustomResourse.new(attributes)
53
+
54
+ expect(resource.name.is_a?(Scimitar::ComplexTypes::Name)).to be(true)
55
+ expect(resource.name.givenName).to eql('John')
56
+ expect(resource.name.familyName).to eql('Smith')
57
+ expect(resource.privateName.is_a?(Scimitar::ComplexTypes::Name)).to be(true)
58
+ expect(resource.privateName.givenName).to eql('Alt John')
59
+ expect(resource.privateName.familyName).to eql('Alt Smith')
60
+ end
61
+
62
+ it 'which builds an array of nested resources' do
63
+ attributes = {
64
+ names:[
65
+ {
66
+ givenName: 'John',
67
+ familyName: 'Smith'
68
+ },
69
+ {
70
+ givenName: 'Jane',
71
+ familyName: 'Snow'
72
+ }
73
+ ]
74
+ }
75
+
76
+ attributes = spec_helper_hupcase(attributes) if force_upper_case
77
+ resource = CustomResourse.new(attributes)
78
+
79
+ expect(resource.names.is_a?(Array)).to be(true)
80
+ expect(resource.names.first.is_a?(Scimitar::ComplexTypes::Name)).to be(true)
81
+ expect(resource.names.first.givenName).to eql('John')
82
+ expect(resource.names.first.familyName).to eql('Smith')
83
+ expect(resource.names.second.is_a?(Scimitar::ComplexTypes::Name)).to be(true)
84
+ expect(resource.names.second.givenName).to eql('Jane')
85
+ expect(resource.names.second.familyName).to eql('Snow')
86
+ expect(resource.valid?).to be(true)
87
+ end
88
+
89
+ it 'which builds an array of nested resources which is invalid if the hash does not follow the schema of the complex type' do
90
+ attributes = {
91
+ names: [
92
+ {
93
+ givenName: 'John',
94
+ familyName: 123
95
+ }
96
+ ]
97
+ }
98
+
99
+ attributes = spec_helper_hupcase(attributes) if force_upper_case
100
+ resource = CustomResourse.new(attributes)
101
+
102
+ expect(resource.names.is_a?(Array)).to be(true)
103
+ expect(resource.names.first.is_a?(Scimitar::ComplexTypes::Name)).to be(true)
104
+ expect(resource.names.first.givenName).to eql('John')
105
+ expect(resource.names.first.familyName).to eql(123)
106
+ expect(resource.valid?).to be(false)
107
+ end
108
+ end # "shared_examples 'an initializer' do | force_upper_case: |"
109
+
110
+ context 'using schema-matched case' do
111
+ it_behaves_like 'an initializer', force_upper_case: false
112
+ end # "context 'using schema-matched case' do"
113
+
114
+ context 'using upper case' do
115
+ it_behaves_like 'an initializer', force_upper_case: true
116
+ end # "context 'using upper case' do"
117
+ end # "context '#initialize' do"
118
+
119
+ context '#as_json' do
120
+ it 'renders the json with the resourceType' do
121
+ resource = CustomResourse.new(name: {
122
+ givenName: 'John',
123
+ familyName: 'Smith'
124
+ })
125
+
126
+ result = resource.as_json
127
+
128
+ expect(result['schemas'] ).to eql(['custom-id'])
129
+ expect(result['meta']['resourceType']).to eql('CustomResourse')
130
+ expect(result['errors'] ).to be_nil
131
+ end
132
+
133
+ it 'excludes attributes that are flagged as do-not-return' do
134
+ resource = CustomResourse.new(
135
+ name: {
136
+ givenName: 'John',
137
+ familyName: 'Smith'
138
+ },
139
+ privateName: {
140
+ givenName: 'Alt John',
141
+ familyName: 'Alt Smith'
142
+ }
143
+ )
144
+
145
+ result = resource.as_json
146
+
147
+ expect(result['schemas'] ).to eql(['custom-id'])
148
+ expect(result['meta']['resourceType']).to eql('CustomResourse')
149
+ expect(result['errors'] ).to be_nil
150
+ expect(result['name'] ).to be_present
151
+ expect(result['name']['givenName'] ).to eql('John')
152
+ expect(result['name']['familyName'] ).to eql('Smith')
153
+ expect(result['privateName'] ).to be_present
154
+ end
155
+ end # "context '#as_json' do"
156
+
157
+ context '.find_attribute' do
158
+ shared_examples 'a finder' do | force_upper_case: |
159
+ it 'which finds in complex type' do
160
+ args = ['name', 'givenName']
161
+ args.map!(&:upcase) if force_upper_case
162
+
163
+ found = CustomResourse.find_attribute(*args)
164
+
165
+ expect(found).to be_present
166
+ expect(found.name).to eql('givenName')
167
+ expect(found.type).to eql('string')
168
+ end
169
+
170
+ it 'which finds in multi-value type, without index' do
171
+ args = ['names', 'givenName']
172
+ args.map!(&:upcase) if force_upper_case
173
+
174
+ found = CustomResourse.find_attribute(*args)
175
+
176
+ expect(found).to be_present
177
+ expect(found.name).to eql('givenName')
178
+ expect(found.type).to eql('string')
179
+ end
180
+
181
+ it 'which finds in multi-value type, ignoring index' do
182
+ args = if force_upper_case
183
+ ['NAMES', 42, 'GIVENNAME']
184
+ else
185
+ ['names', 42, 'givenName']
186
+ end
187
+
188
+ found = CustomResourse.find_attribute(*args)
189
+
190
+ expect(found).to be_present
191
+ expect(found.name).to eql('givenName')
192
+ expect(found.type).to eql('string')
193
+ end # "shared_examples 'a finder' do | force_upper_case: |"
194
+ end
195
+
196
+ context 'using schema-matched case' do
197
+ it_behaves_like 'a finder', force_upper_case: false
198
+ end # "context 'using schema-matched case' do"
199
+
200
+ context 'using upper case' do
201
+ it_behaves_like 'a finder', force_upper_case: true
202
+ end # "context 'using upper case' do"
203
+ end # "context '.find_attribute' do"
204
+ end # "context 'basic operation' do"
205
+
206
+ context 'dynamic setters based on schema' do
207
+ SecondCustomSchema = Class.new(Scimitar::Schema::Base) do
208
+ def self.scim_attributes
209
+ [
210
+ Scimitar::Schema::Attribute.new(name: 'customField', type: 'string', required: false),
211
+ Scimitar::Schema::Attribute.new(name: 'anotherCustomField', type: 'boolean', required: false),
212
+ Scimitar::Schema::Attribute.new(name: 'name', complexType: Scimitar::ComplexTypes::Name, required: false)
213
+ ]
214
+ end
215
+ end
216
+
217
+ CustomNameType = Class.new(Scimitar::ComplexTypes::Base) do
218
+ set_schema Scimitar::Schema::Name
219
+ end
220
+
221
+ it 'defines a setter for an attribute in the schema' do
222
+ described_class.set_schema SecondCustomSchema
223
+ resource = described_class.new(customField: '100',
224
+ anotherCustomField: true)
225
+ expect(resource.customField).to eql('100')
226
+ expect(resource.anotherCustomField).to eql(true)
227
+ expect(resource.valid?).to be(true)
228
+ end
229
+
230
+ it 'defines a setter for an attribute in the schema' do
231
+ described_class.set_schema SecondCustomSchema
232
+ resource = described_class.new(anotherCustomField: false)
233
+ expect(resource.anotherCustomField).to eql(false)
234
+ expect(resource.valid?).to be(true)
235
+ end
236
+
237
+ it 'validates that the provided attributes match their schema' do
238
+ described_class.set_schema SecondCustomSchema
239
+ resource = described_class.new(
240
+ name: Scimitar::ComplexTypes::Name.new(
241
+ givenName: 'John',
242
+ familyName: 'Smith'
243
+ ))
244
+ expect(resource.valid?).to be(true)
245
+ end
246
+
247
+ it 'validates that nested types' do
248
+ described_class.set_schema SecondCustomSchema
249
+ resource = described_class.new(
250
+ name: Scimitar::ComplexTypes::Name.new(
251
+ givenName: 100,
252
+ familyName: 'Smith'
253
+ ))
254
+ expect(resource.valid?).to be(false)
255
+ end
256
+
257
+ it 'allows custom complex types as long as the schema matches' do
258
+ described_class.set_schema SecondCustomSchema
259
+ resource = described_class.new(
260
+ name: CustomNameType.new(
261
+ givenName: 'John',
262
+ familyName: 'Smith'
263
+ ))
264
+ expect(resource.valid?).to be(true)
265
+ end
266
+
267
+ it 'doesn\'t accept email for a name' do
268
+ described_class.set_schema SecondCustomSchema
269
+ resource = described_class.new(
270
+ name: Scimitar::ComplexTypes::Email.new(
271
+ value: 'john@smith.com',
272
+ primary: true
273
+ ))
274
+ expect(resource.valid?).to be(false)
275
+ end
276
+
277
+ it 'doesn\'t accept a complex type for a string' do
278
+ described_class.set_schema SecondCustomSchema
279
+ resource = described_class.new(
280
+ customField: Scimitar::ComplexTypes::Email.new(
281
+ value: 'john@smith.com',
282
+ primary: true
283
+ ))
284
+ expect(resource.valid?).to be(false)
285
+ end
286
+
287
+ it 'doesn\'t accept a string for a boolean' do
288
+ described_class.set_schema SecondCustomSchema
289
+ resource = described_class.new(anotherCustomField: 'value')
290
+ expect(resource.valid?).to be(false)
291
+ end
292
+ end # "context 'dynamic setters based on schema' do"
293
+
294
+ context 'schema extension' do
295
+ context 'of custom schema' do
296
+ ThirdCustomSchema = Class.new(Scimitar::Schema::Base) do
297
+ def self.id
298
+ 'custom-id'
299
+ end
300
+
301
+ def self.scim_attributes
302
+ [ Scimitar::Schema::Attribute.new(name: 'name', type: 'string') ]
303
+ end
304
+ end
305
+
306
+ ExtensionSchema = Class.new(Scimitar::Schema::Base) do
307
+ def self.id
308
+ 'extension-id'
309
+ end
310
+
311
+ def self.scim_attributes
312
+ [
313
+ Scimitar::Schema::Attribute.new(name: 'relationship', type: 'string', required: true),
314
+ Scimitar::Schema::Attribute.new(name: "userGroups", multiValued: true, complexType: Scimitar::ComplexTypes::ReferenceGroup, mutability: "writeOnly")
315
+ ]
316
+ end
317
+ end
318
+
319
+ let(:resource_class) {
320
+ Class.new(Scimitar::Resources::Base) do
321
+ set_schema ThirdCustomSchema
322
+ extend_schema ExtensionSchema
323
+
324
+ def self.endpoint
325
+ '/gaga'
326
+ end
327
+
328
+ def self.resource_type_id
329
+ 'CustomResource'
330
+ end
331
+ end
332
+ }
333
+
334
+ context '#initialize' do
335
+ it 'allows setting extension attributes' do
336
+ resource = resource_class.new('extension-id' => {relationship: 'GAGA'})
337
+ expect(resource.relationship).to eql('GAGA')
338
+ end
339
+
340
+ it 'allows setting complex extension attributes' do
341
+ user_groups = [{ value: '123' }, { value: '456'}]
342
+ resource = resource_class.new('extension-id' => {userGroups: user_groups})
343
+ expect(resource.userGroups.map(&:value)).to eql(['123', '456'])
344
+ end
345
+ end # "context '#initialize' do"
346
+
347
+ context '#as_json' do
348
+ it 'namespaces the extension attributes' do
349
+ resource = resource_class.new(relationship: 'GAGA')
350
+ hash = resource.as_json
351
+ expect(hash["schemas"]).to eql(['custom-id', 'extension-id'])
352
+ expect(hash["extension-id"]).to eql("relationship" => 'GAGA')
353
+ end
354
+ end # "context '#as_json' do"
355
+
356
+ context '.resource_type' do
357
+ it 'appends the extension schemas' do
358
+ resource_type = resource_class.resource_type('http://gaga')
359
+ expect(resource_type.meta.location).to eql('http://gaga')
360
+ expect(resource_type.schemaExtensions.count).to eql(1)
361
+ end
362
+
363
+ context 'validation' do
364
+ it 'validates into custom schema' do
365
+ resource = resource_class.new('extension-id' => {})
366
+ expect(resource.valid?).to eql(false)
367
+
368
+ resource = resource_class.new('extension-id' => {relationship: 'GAGA'})
369
+ expect(resource.relationship).to eql('GAGA')
370
+ expect(resource.valid?).to eql(true)
371
+ end
372
+ end # context 'validation'
373
+ end # "context '.resource_type' do"
374
+
375
+ context '.find_attribute' do
376
+ it 'finds in first schema' do
377
+ found = resource_class().find_attribute('name') # Defined in ThirdCustomSchema
378
+ expect(found).to be_present
379
+ expect(found.name).to eql('name')
380
+ expect(found.type).to eql('string')
381
+ end
382
+
383
+ it 'finds across schemas' do
384
+ found = resource_class().find_attribute('relationship') # Defined in ExtensionSchema
385
+ expect(found).to be_present
386
+ expect(found.name).to eql('relationship')
387
+ expect(found.type).to eql('string')
388
+ end
389
+ end # "context '.find_attribute' do"
390
+ end # "context 'of custom schema' do"
391
+
392
+ context 'of core schema' do
393
+ EnterpriseExtensionSchema = Class.new(Scimitar::Schema::Base) do
394
+ def self.id
395
+ 'urn:ietf:params:scim:schemas:extension:enterprise:2.0:User'
396
+ end
397
+
398
+ def self.scim_attributes
399
+ [
400
+ Scimitar::Schema::Attribute.new(name: 'organization', type: 'string'),
401
+ Scimitar::Schema::Attribute.new(name: 'department', type: 'string')
402
+ ]
403
+ end
404
+ end
405
+
406
+ let(:resource_class) {
407
+ Class.new(Scimitar::Resources::Base) do
408
+ set_schema Scimitar::Schema::User
409
+ extend_schema EnterpriseExtensionSchema
410
+
411
+ def self.endpoint
412
+ '/Users'
413
+ end
414
+
415
+ def self.resource_type_id
416
+ 'User'
417
+ end
418
+ end
419
+ }
420
+
421
+ context '#initialize' do
422
+ it 'allows setting extension attributes' do
423
+ resource = resource_class.new('urn:ietf:params:scim:schemas:extension:enterprise:2.0:User' => {organization: 'SOMEORG', department: 'SOMEDPT'})
424
+
425
+ expect(resource.organization).to eql('SOMEORG')
426
+ expect(resource.department ).to eql('SOMEDPT')
427
+ end
428
+ end # "context '#initialize' do"
429
+
430
+ context '#as_json' do
431
+ it 'namespaces the extension attributes' do
432
+ resource = resource_class.new(organization: 'SOMEORG', department: 'SOMEDPT')
433
+ hash = resource.as_json
434
+
435
+ expect(hash['schemas']).to eql(['urn:ietf:params:scim:schemas:core:2.0:User', 'urn:ietf:params:scim:schemas:extension:enterprise:2.0:User'])
436
+ expect(hash['urn:ietf:params:scim:schemas:extension:enterprise:2.0:User']).to eql('organization' => 'SOMEORG', 'department' => 'SOMEDPT')
437
+ end
438
+ end # "context '#as_json' do"
439
+
440
+ context '.resource_type' do
441
+ it 'appends the extension schemas' do
442
+ resource_type = resource_class.resource_type('http://example.com')
443
+ expect(resource_type.meta.location).to eql('http://example.com')
444
+ expect(resource_type.schemaExtensions.count).to eql(1)
445
+ end
446
+
447
+ context 'validation' do
448
+ it 'validates into custom schema' do
449
+ resource = resource_class.new('urn:ietf:params:scim:schemas:extension:enterprise:2.0:User' => {})
450
+ expect(resource.valid?).to eql(false)
451
+
452
+ resource = resource_class.new(
453
+ 'urn:ietf:params:scim:schemas:extension:enterprise:2.0:User' => {
454
+ userName: 'SOMEUSR',
455
+ organization: 'SOMEORG',
456
+ department: 'SOMEDPT'
457
+ }
458
+ )
459
+
460
+ expect(resource.organization).to eql('SOMEORG')
461
+ expect(resource.department ).to eql('SOMEDPT')
462
+ expect(resource.valid? ).to eql(true)
463
+ end
464
+ end # context 'validation'
465
+ end # "context '.resource_type' do"
466
+
467
+ context '.find_attribute' do
468
+ it 'finds in first schema' do
469
+ found = resource_class().find_attribute('userName') # Defined in Scimitar::Schema::User
470
+
471
+ expect(found).to be_present
472
+ expect(found.name).to eql('userName')
473
+ expect(found.type).to eql('string')
474
+ end
475
+
476
+ it 'finds across schemas' do
477
+ found = resource_class().find_attribute('organization') # Defined in EnterpriseExtensionSchema
478
+ expect(found).to be_present
479
+ expect(found.name).to eql('organization')
480
+ expect(found.type).to eql('string')
481
+ end
482
+ end # "context '.find_attribute' do"
483
+ end # "context 'of core schema' do"
484
+ end # "context 'schema extension' do"
485
+ end
@@ -0,0 +1,86 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe Scimitar::Resources::Base do
4
+ context '#valid?' do
5
+ MyCustomSchema = Class.new(Scimitar::Schema::Base) do
6
+ def self.id
7
+ 'custom-id'
8
+ end
9
+
10
+ def self.scim_attributes
11
+ [
12
+ Scimitar::Schema::Attribute.new(
13
+ name: 'userName', type: 'string', required: false
14
+ ),
15
+ Scimitar::Schema::Attribute.new(
16
+ name: 'enforce', type: 'boolean', required: true
17
+ ),
18
+ Scimitar::Schema::Attribute.new(
19
+ name: 'complexName', complexType: Scimitar::ComplexTypes::Name, required: false
20
+ ),
21
+ Scimitar::Schema::Attribute.new(
22
+ name: 'complexNames', complexType: Scimitar::ComplexTypes::Name, multiValued:true, required: false
23
+ ),
24
+ Scimitar::Schema::Attribute.new(
25
+ name: 'vdtpTestByEmail', complexType: Scimitar::ComplexTypes::Email, required: false
26
+ )
27
+ ]
28
+ end
29
+ end
30
+
31
+ MyCustomResource = Class.new(Scimitar::Resources::Base) do
32
+ set_schema MyCustomSchema
33
+ end
34
+
35
+ it 'adds validation errors to the resource for simple attributes' do
36
+ resource = MyCustomResource.new(userName: 10)
37
+ expect(resource.valid?).to be(false)
38
+ expect(resource.errors.full_messages).to match_array(['Username has the wrong type. It has to be a(n) string.', 'Enforce is required'])
39
+ end
40
+
41
+ it 'adds validation errors to the resource for the complex attribute when the value does not match the schema' do
42
+ resource = MyCustomResource.new(complexName: 10, enforce: false)
43
+ expect(resource.valid?).to be(false)
44
+ expect(resource.errors.full_messages).to match_array(['Complexname has to follow the complexType format.'])
45
+ end
46
+
47
+ it 'adds validation errors to the resource from what the complex type schema returns' do
48
+ resource = MyCustomResource.new(complexName: { givenName: 10 }, enforce: false)
49
+ expect(resource.valid?).to be(false)
50
+ expect(resource.errors.full_messages).to match_array(["Complexname familyname is required", "Complexname givenname has the wrong type. It has to be a(n) string."])
51
+ end
52
+
53
+ it 'adds validation errors to the resource from what the complex type schema returns when it is multi-valued' do
54
+ resource = MyCustomResource.new(complexNames: [
55
+ "Jane Austen",
56
+ { givenName: 'Jane', familyName: true }
57
+ ],
58
+ enforce: false)
59
+ expect(resource.valid?).to be(false)
60
+ expect(resource.errors.full_messages).to match_array(["Complexnames has to follow the complexType format.", "Complexnames familyname has the wrong type. It has to be a(n) string."])
61
+ end
62
+
63
+ context 'configuration of required values in VDTP schema' do
64
+ around :each do | example |
65
+ original_configuration = Scimitar.engine_configuration.optional_value_fields_required
66
+ Scimitar::Schema::Email.instance_variable_set('@scim_attributes', nil)
67
+ example.run()
68
+ ensure
69
+ Scimitar.engine_configuration.optional_value_fields_required = original_configuration
70
+ Scimitar::Schema::Email.instance_variable_set('@scim_attributes', nil)
71
+ end
72
+
73
+ it 'requires a value by default' do
74
+ resource = MyCustomResource.new(vdtpTestByEmail: { value: nil }, enforce: false)
75
+ expect(resource.valid?).to be(false)
76
+ expect(resource.errors.full_messages).to match_array(['Vdtptestbyemail value is required'])
77
+ end
78
+
79
+ it 'can be configured for optional values' do
80
+ Scimitar.engine_configuration.optional_value_fields_required = false
81
+ resource = MyCustomResource.new(vdtpTestByEmail: { value: nil }, enforce: false)
82
+ expect(resource.valid?).to be(true)
83
+ end
84
+ end # "context 'configuration of required values in VDTP schema' do"
85
+ end # "context '#valid?' do"
86
+ end