scimitar 1.7.1 → 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 (44) hide show
  1. checksums.yaml +4 -4
  2. data/app/controllers/scimitar/active_record_backed_resources_controller.rb +10 -49
  3. data/app/controllers/scimitar/application_controller.rb +11 -35
  4. data/app/controllers/scimitar/schemas_controller.rb +0 -5
  5. data/app/models/scimitar/engine_configuration.rb +5 -13
  6. data/app/models/scimitar/error_response.rb +0 -12
  7. data/app/models/scimitar/lists/query_parser.rb +10 -25
  8. data/app/models/scimitar/resources/base.rb +4 -14
  9. data/app/models/scimitar/resources/mixin.rb +13 -137
  10. data/app/models/scimitar/schema/address.rb +0 -1
  11. data/app/models/scimitar/schema/attribute.rb +5 -14
  12. data/app/models/scimitar/schema/base.rb +1 -1
  13. data/app/models/scimitar/schema/vdtp.rb +1 -1
  14. data/app/models/scimitar/service_provider_configuration.rb +3 -14
  15. data/config/initializers/scimitar.rb +3 -28
  16. data/lib/scimitar/version.rb +2 -2
  17. data/lib/scimitar.rb +2 -6
  18. data/spec/apps/dummy/app/controllers/mock_groups_controller.rb +1 -1
  19. data/spec/apps/dummy/app/models/mock_group.rb +1 -1
  20. data/spec/apps/dummy/app/models/mock_user.rb +8 -36
  21. data/spec/apps/dummy/config/application.rb +1 -0
  22. data/spec/apps/dummy/config/environments/test.rb +28 -5
  23. data/spec/apps/dummy/config/initializers/scimitar.rb +10 -61
  24. data/spec/apps/dummy/config/routes.rb +6 -15
  25. data/spec/apps/dummy/db/migrate/20210304014602_create_mock_users.rb +1 -10
  26. data/spec/apps/dummy/db/migrate/20210308044214_create_join_table_mock_groups_mock_users.rb +3 -8
  27. data/spec/apps/dummy/db/schema.rb +4 -11
  28. data/spec/controllers/scimitar/application_controller_spec.rb +3 -72
  29. data/spec/controllers/scimitar/resource_types_controller_spec.rb +2 -2
  30. data/spec/controllers/scimitar/schemas_controller_spec.rb +2 -10
  31. data/spec/models/scimitar/complex_types/email_spec.rb +2 -0
  32. data/spec/models/scimitar/lists/query_parser_spec.rb +9 -76
  33. data/spec/models/scimitar/resources/base_spec.rb +70 -208
  34. data/spec/models/scimitar/resources/base_validation_spec.rb +2 -27
  35. data/spec/models/scimitar/resources/mixin_spec.rb +43 -768
  36. data/spec/models/scimitar/schema/attribute_spec.rb +3 -22
  37. data/spec/models/scimitar/schema/base_spec.rb +1 -1
  38. data/spec/models/scimitar/schema/user_spec.rb +0 -10
  39. data/spec/requests/active_record_backed_resources_controller_spec.rb +64 -423
  40. data/spec/requests/application_controller_spec.rb +3 -16
  41. metadata +7 -11
  42. data/LICENSE.txt +0 -21
  43. data/README.md +0 -671
  44. data/spec/apps/dummy/app/controllers/custom_save_mock_users_controller.rb +0 -24
@@ -1,42 +1,25 @@
1
1
  require 'spec_helper'
2
- require 'time'
3
2
 
4
3
  RSpec.describe Scimitar::ActiveRecordBackedResourcesController do
5
4
  before :each do
6
5
  allow_any_instance_of(Scimitar::ApplicationController).to receive(:authenticated?).and_return(true)
7
6
 
8
- # If a sort order is unspecified, the controller defaults to ID ascending.
9
- # With UUID based IDs, testing life is made easier by ensuring that the
10
- # creation order matches an ascending UUID sort order (which is what would
11
- # happen if we were using integer primary keys).
12
- #
13
- lmt = Time.parse("2023-01-09 14:25:00 +1300")
14
- ids = 3.times.map { SecureRandom.uuid }.sort()
15
-
16
- @u1 = MockUser.create(primary_key: ids.shift(), username: '1', first_name: 'Foo', last_name: 'Ark', home_email_address: 'home_1@test.com', scim_uid: '001', created_at: lmt, updated_at: lmt + 1)
17
- @u2 = MockUser.create(primary_key: ids.shift(), username: '2', first_name: 'Foo', last_name: 'Bar', home_email_address: 'home_2@test.com', scim_uid: '002', created_at: lmt, updated_at: lmt + 2)
18
- @u3 = MockUser.create(primary_key: ids.shift(), username: '3', first_name: 'Foo', home_email_address: 'home_3@test.com', scim_uid: '003', created_at: lmt, updated_at: lmt + 3)
19
-
20
- @g1 = MockGroup.create!(display_name: 'Group 1')
21
- @g2 = MockGroup.create!(display_name: 'Group 2')
22
- @g3 = MockGroup.create!(display_name: 'Group 3')
7
+ @u1 = MockUser.create(username: '1', first_name: 'Foo', last_name: 'Ark', home_email_address: 'home_1@test.com')
8
+ @u2 = MockUser.create(username: '2', first_name: 'Foo', last_name: 'Bar', home_email_address: 'home_2@test.com')
9
+ @u3 = MockUser.create(username: '3', first_name: 'Foo', home_email_address: 'home_3@test.com')
23
10
  end
24
11
 
25
12
  # ===========================================================================
26
13
 
27
14
  context '#index' do
28
15
  context 'with no items' do
29
- before :each do
16
+ it 'returns empty list' do
30
17
  MockUser.delete_all
31
- end
32
18
 
33
- it 'returns empty list' do
34
19
  expect_any_instance_of(MockUsersController).to receive(:index).once.and_call_original
35
20
  get '/Users', params: { format: :scim }
36
21
 
37
- expect(response.status ).to eql(200)
38
- expect(response.headers['Content-Type']).to eql('application/scim+json; charset=utf-8')
39
-
22
+ expect(response.status).to eql(200)
40
23
  result = JSON.parse(response.body)
41
24
 
42
25
  expect(result['totalResults']).to eql(0)
@@ -46,172 +29,55 @@ RSpec.describe Scimitar::ActiveRecordBackedResourcesController do
46
29
  end # "context 'with no items' do"
47
30
 
48
31
  context 'with items' do
49
- context 'with a UUID, renamed primary key column' do
50
- it 'returns all items' do
51
- get '/Users', params: { format: :scim }
52
-
53
- expect(response.status ).to eql(200)
54
- expect(response.headers['Content-Type']).to eql('application/scim+json; charset=utf-8')
55
-
56
- result = JSON.parse(response.body)
57
-
58
- expect(result['totalResults']).to eql(3)
59
- expect(result['Resources'].size).to eql(3)
60
-
61
- ids = result['Resources'].map { |resource| resource['id'] }
62
- expect(ids).to match_array([@u1.primary_key.to_s, @u2.primary_key.to_s, @u3.primary_key.to_s])
63
-
64
- usernames = result['Resources'].map { |resource| resource['userName'] }
65
- expect(usernames).to match_array(['1', '2', '3'])
66
- end
67
- end # "context 'with a UUID, renamed primary key column' do"
68
-
69
- context 'with an integer, conventionally named primary key column' do
70
- it 'returns all items' do
71
- get '/Groups', params: { format: :scim }
72
-
73
- expect(response.status ).to eql(200)
74
- expect(response.headers['Content-Type']).to eql('application/scim+json; charset=utf-8')
75
-
76
- result = JSON.parse(response.body)
77
-
78
- expect(result['totalResults']).to eql(3)
79
- expect(result['Resources'].size).to eql(3)
80
-
81
- ids = result['Resources'].map { |resource| resource['id'] }
82
- expect(ids).to match_array([@g1.id.to_s, @g2.id.to_s, @g3.id.to_s])
83
-
84
- usernames = result['Resources'].map { |resource| resource['displayName'] }
85
- expect(usernames).to match_array(['Group 1', 'Group 2', 'Group 3'])
86
- end
87
- end # "context 'with an integer, conventionally named primary key column' do"
88
-
89
- it 'applies a filter, with case-insensitive value comparison' do
90
- get '/Users', params: {
91
- format: :scim,
92
- filter: 'name.givenName eq "FOO" and name.familyName pr and emails ne "home_1@test.com"'
93
- }
94
-
95
- expect(response.status ).to eql(200)
96
- expect(response.headers['Content-Type']).to eql('application/scim+json; charset=utf-8')
32
+ it 'returns all items' do
33
+ get '/Users', params: { format: :scim }
97
34
 
35
+ expect(response.status).to eql(200)
98
36
  result = JSON.parse(response.body)
99
37
 
100
- expect(result['totalResults']).to eql(1)
101
- expect(result['Resources'].size).to eql(1)
38
+ expect(result['totalResults']).to eql(3)
39
+ expect(result['Resources'].size).to eql(3)
102
40
 
103
41
  ids = result['Resources'].map { |resource| resource['id'] }
104
- expect(ids).to match_array([@u2.primary_key.to_s])
42
+ expect(ids).to match_array([@u1.id.to_s, @u2.id.to_s, @u3.id.to_s])
105
43
 
106
44
  usernames = result['Resources'].map { |resource| resource['userName'] }
107
- expect(usernames).to match_array(['2'])
45
+ expect(usernames).to match_array(['1', '2', '3'])
108
46
  end
109
47
 
110
- it 'applies a filter, with case-insensitive attribute matching (GitHub issue #37)' do
48
+ it 'applies a filter, with case-insensitive value comparison' do
111
49
  get '/Users', params: {
112
50
  format: :scim,
113
- filter: 'name.GIVENNAME eq "Foo" and name.Familyname pr and emails ne "home_1@test.com"'
51
+ filter: 'name.givenName eq "Foo" and name.familyName pr and emails ne "home_1@TEST.COM"'
114
52
  }
115
53
 
116
- expect(response.status ).to eql(200)
117
- expect(response.headers['Content-Type']).to eql('application/scim+json; charset=utf-8')
118
-
54
+ expect(response.status).to eql(200)
119
55
  result = JSON.parse(response.body)
120
56
 
121
57
  expect(result['totalResults']).to eql(1)
122
58
  expect(result['Resources'].size).to eql(1)
123
59
 
124
60
  ids = result['Resources'].map { |resource| resource['id'] }
125
- expect(ids).to match_array([@u2.primary_key.to_s])
61
+ expect(ids).to match_array([@u2.id.to_s])
126
62
 
127
63
  usernames = result['Resources'].map { |resource| resource['userName'] }
128
64
  expect(usernames).to match_array(['2'])
129
65
  end
130
66
 
131
- # Strange attribute capitalisation in tests here builds on test coverage
132
- # for now-fixed GitHub issue #37.
133
- #
134
- context '"meta" / IDs (GitHub issue #36)' do
135
- it 'applies a filter on primary keys, using direct comparison (rather than e.g. case-insensitive operators)' do
136
- get '/Users', params: {
137
- format: :scim,
138
- filter: "id eq \"#{@u3.primary_key}\""
139
- }
140
-
141
- expect(response.status ).to eql(200)
142
- expect(response.headers['Content-Type']).to eql('application/scim+json; charset=utf-8')
143
-
144
- result = JSON.parse(response.body)
145
-
146
- expect(result['totalResults']).to eql(1)
147
- expect(result['Resources'].size).to eql(1)
148
-
149
- ids = result['Resources'].map { |resource| resource['id'] }
150
- expect(ids).to match_array([@u3.primary_key.to_s])
151
-
152
- usernames = result['Resources'].map { |resource| resource['userName'] }
153
- expect(usernames).to match_array(['3'])
154
- end
155
-
156
- it 'applies a filter on external IDs, using direct comparison' do
157
- get '/Users', params: {
158
- format: :scim,
159
- filter: "externalID eq \"#{@u2.scim_uid}\""
160
- }
161
-
162
- expect(response.status ).to eql(200)
163
- expect(response.headers['Content-Type']).to eql('application/scim+json; charset=utf-8')
164
-
165
- result = JSON.parse(response.body)
166
-
167
- expect(result['totalResults']).to eql(1)
168
- expect(result['Resources'].size).to eql(1)
169
-
170
- ids = result['Resources'].map { |resource| resource['id'] }
171
- expect(ids).to match_array([@u2.primary_key.to_s])
172
-
173
- usernames = result['Resources'].map { |resource| resource['userName'] }
174
- expect(usernames).to match_array(['2'])
175
- end
176
-
177
- it 'applies a filter on "meta" entries, using direct comparison' do
178
- get '/Users', params: {
179
- format: :scim,
180
- filter: "Meta.LastModified eq \"#{@u3.updated_at}\""
181
- }
182
-
183
- expect(response.status ).to eql(200)
184
- expect(response.headers['Content-Type']).to eql('application/scim+json; charset=utf-8')
185
-
186
- result = JSON.parse(response.body)
187
-
188
- expect(result['totalResults']).to eql(1)
189
- expect(result['Resources'].size).to eql(1)
190
-
191
- ids = result['Resources'].map { |resource| resource['id'] }
192
- expect(ids).to match_array([@u3.primary_key.to_s])
193
-
194
- usernames = result['Resources'].map { |resource| resource['userName'] }
195
- expect(usernames).to match_array(['3'])
196
- end
197
- end # "context '"meta" / IDs (GitHub issue #36)' do"
198
-
199
67
  it 'obeys a page size' do
200
68
  get '/Users', params: {
201
69
  format: :scim,
202
70
  count: 2
203
71
  }
204
72
 
205
- expect(response.status ).to eql(200)
206
- expect(response.headers['Content-Type']).to eql('application/scim+json; charset=utf-8')
207
-
73
+ expect(response.status).to eql(200)
208
74
  result = JSON.parse(response.body)
209
75
 
210
76
  expect(result['totalResults']).to eql(3)
211
77
  expect(result['Resources'].size).to eql(2)
212
78
 
213
79
  ids = result['Resources'].map { |resource| resource['id'] }
214
- expect(ids).to match_array([@u1.primary_key.to_s, @u2.primary_key.to_s])
80
+ expect(ids).to match_array([@u1.id.to_s, @u2.id.to_s])
215
81
 
216
82
  usernames = result['Resources'].map { |resource| resource['userName'] }
217
83
  expect(usernames).to match_array(['1', '2'])
@@ -223,16 +89,14 @@ RSpec.describe Scimitar::ActiveRecordBackedResourcesController do
223
89
  startIndex: 2
224
90
  }
225
91
 
226
- expect(response.status ).to eql(200)
227
- expect(response.headers['Content-Type']).to eql('application/scim+json; charset=utf-8')
228
-
92
+ expect(response.status).to eql(200)
229
93
  result = JSON.parse(response.body)
230
94
 
231
95
  expect(result['totalResults']).to eql(3)
232
96
  expect(result['Resources'].size).to eql(2)
233
97
 
234
98
  ids = result['Resources'].map { |resource| resource['id'] }
235
- expect(ids).to match_array([@u2.primary_key.to_s, @u3.primary_key.to_s])
99
+ expect(ids).to match_array([@u2.id.to_s, @u3.id.to_s])
236
100
 
237
101
  usernames = result['Resources'].map { |resource| resource['userName'] }
238
102
  expect(usernames).to match_array(['2', '3'])
@@ -246,11 +110,8 @@ RSpec.describe Scimitar::ActiveRecordBackedResourcesController do
246
110
  filter: 'name.givenName'
247
111
  }
248
112
 
249
- expect(response.status ).to eql(400)
250
- expect(response.headers['Content-Type']).to eql('application/scim+json; charset=utf-8')
251
-
113
+ expect(response.status).to eql(400)
252
114
  result = JSON.parse(response.body)
253
-
254
115
  expect(result['scimType']).to eql('invalidFilter')
255
116
  end
256
117
  end # "context 'with bad calls' do"
@@ -259,47 +120,24 @@ RSpec.describe Scimitar::ActiveRecordBackedResourcesController do
259
120
  # ===========================================================================
260
121
 
261
122
  context '#show' do
262
- context 'with a UUID, renamed primary key column' do
263
- it 'shows an item' do
264
- expect_any_instance_of(MockUsersController).to receive(:show).once.and_call_original
265
- get "/Users/#{@u2.primary_key}", params: { format: :scim }
266
-
267
- expect(response.status ).to eql(200)
268
- expect(response.headers['Content-Type']).to eql('application/scim+json; charset=utf-8')
269
-
270
- result = JSON.parse(response.body)
271
-
272
- expect(result['id']).to eql(@u2.primary_key.to_s)
273
- expect(result['userName']).to eql('2')
274
- expect(result['name']['familyName']).to eql('Bar')
275
- expect(result['meta']['resourceType']).to eql('User')
276
- end
277
- end # "context 'with a UUID, renamed primary key column' do"
278
-
279
- context 'with an integer, conventionally named primary key column' do
280
- it 'shows an item' do
281
- expect_any_instance_of(MockGroupsController).to receive(:show).once.and_call_original
282
- get "/Groups/#{@g2.id}", params: { format: :scim }
283
-
284
- expect(response.status ).to eql(200)
285
- expect(response.headers['Content-Type']).to eql('application/scim+json; charset=utf-8')
123
+ it 'shows an item' do
124
+ expect_any_instance_of(MockUsersController).to receive(:show).once.and_call_original
125
+ get "/Users/#{@u2.id}", params: { format: :scim }
286
126
 
287
- result = JSON.parse(response.body)
127
+ expect(response.status).to eql(200)
128
+ result = JSON.parse(response.body)
288
129
 
289
- expect(result['id']).to eql(@g2.id.to_s) # Note - ID was converted String; not Integer
290
- expect(result['displayName']).to eql('Group 2')
291
- expect(result['meta']['resourceType']).to eql('Group')
292
- end
293
- end # "context 'with an integer, conventionally named primary key column' do"
130
+ expect(result['id']).to eql(@u2.id.to_s) # Note - ID was converted String; not Integer
131
+ expect(result['userName']).to eql('2')
132
+ expect(result['name']['familyName']).to eql('Bar')
133
+ expect(result['meta']['resourceType']).to eql('User')
134
+ end
294
135
 
295
136
  it 'renders 404' do
296
137
  get '/Users/xyz', params: { format: :scim }
297
138
 
298
- expect(response.status ).to eql(404)
299
- expect(response.headers['Content-Type']).to eql('application/scim+json; charset=utf-8')
300
-
139
+ expect(response.status).to eql(404)
301
140
  result = JSON.parse(response.body)
302
-
303
141
  expect(result['status']).to eql('404')
304
142
  end
305
143
  end # "context '#show' do"
@@ -312,7 +150,7 @@ RSpec.describe Scimitar::ActiveRecordBackedResourcesController do
312
150
  it 'with minimal parameters' do
313
151
  mock_before = MockUser.all.to_a
314
152
 
315
- attributes = { userName: '4' } # Minimum required by schema
153
+ attributes = { userName: '4' } # Minimum required by schema
316
154
  attributes = spec_helper_hupcase(attributes) if force_upper_case
317
155
 
318
156
  expect_any_instance_of(MockUsersController).to receive(:create).once.and_call_original
@@ -323,12 +161,10 @@ RSpec.describe Scimitar::ActiveRecordBackedResourcesController do
323
161
  mock_after = MockUser.all.to_a
324
162
  new_mock = (mock_after - mock_before).first
325
163
 
326
- expect(response.status ).to eql(201)
327
- expect(response.headers['Content-Type']).to eql('application/scim+json; charset=utf-8')
328
-
164
+ expect(response.status).to eql(201)
329
165
  result = JSON.parse(response.body)
330
166
 
331
- expect(result['id']).to eql(new_mock.primary_key.to_s)
167
+ expect(result['id']).to eql(new_mock.id.to_s)
332
168
  expect(result['meta']['resourceType']).to eql('User')
333
169
  expect(new_mock.username).to eql('4')
334
170
  end
@@ -344,7 +180,6 @@ RSpec.describe Scimitar::ActiveRecordBackedResourcesController do
344
180
  givenName: 'Given',
345
181
  familyName: 'Family'
346
182
  },
347
- meta: { resourceType: 'User' },
348
183
  emails: [
349
184
  {
350
185
  type: 'work',
@@ -366,9 +201,7 @@ RSpec.describe Scimitar::ActiveRecordBackedResourcesController do
366
201
  mock_after = MockUser.all.to_a
367
202
  new_mock = (mock_after - mock_before).first
368
203
 
369
- expect(response.status ).to eql(201)
370
- expect(response.headers['Content-Type']).to eql('application/scim+json; charset=utf-8')
371
-
204
+ expect(response.status).to eql(201)
372
205
  result = JSON.parse(response.body)
373
206
 
374
207
  expect(result['id']).to eql(new_mock.id.to_s)
@@ -399,11 +232,8 @@ RSpec.describe Scimitar::ActiveRecordBackedResourcesController do
399
232
  }
400
233
  }.to_not change { MockUser.count }
401
234
 
402
- expect(response.status ).to eql(409)
403
- expect(response.headers['Content-Type']).to eql('application/scim+json; charset=utf-8')
404
-
235
+ expect(response.status).to eql(409)
405
236
  result = JSON.parse(response.body)
406
-
407
237
  expect(result['scimType']).to eql('uniqueness')
408
238
  expect(result['detail']).to include('already been taken')
409
239
  end
@@ -416,11 +246,8 @@ RSpec.describe Scimitar::ActiveRecordBackedResourcesController do
416
246
  }
417
247
  }.to_not change { MockUser.count }
418
248
 
419
- expect(response.status ).to eql(400)
420
- expect(response.headers['Content-Type']).to eql('application/scim+json; charset=utf-8')
421
-
249
+ expect(response.status).to eql(400)
422
250
  result = JSON.parse(response.body)
423
-
424
251
  expect(result['scimType']).to eql('invalidValue')
425
252
  expect(result['detail']).to include('is required')
426
253
  end
@@ -433,32 +260,12 @@ RSpec.describe Scimitar::ActiveRecordBackedResourcesController do
433
260
  }
434
261
  }.to_not change { MockUser.count }
435
262
 
436
- expect(response.status ).to eql(400)
437
- expect(response.headers['Content-Type']).to eql('application/scim+json; charset=utf-8')
438
-
263
+ expect(response.status).to eql(400)
439
264
  result = JSON.parse(response.body)
440
265
 
441
266
  expect(result['scimType']).to eql('invalidValue')
442
267
  expect(result['detail']).to include('is reserved')
443
268
  end
444
-
445
- it 'invokes a block if given one' do
446
- mock_before = MockUser.all.to_a
447
- attributes = { userName: '5' } # Minimum required by schema
448
-
449
- expect_any_instance_of(CustomSaveMockUsersController).to receive(:create).once.and_call_original
450
- expect {
451
- post "/CustomSaveUsers", params: attributes.merge(format: :scim)
452
- }.to change { MockUser.count }.by(1)
453
-
454
- mock_after = MockUser.all.to_a
455
- new_mock = (mock_after - mock_before).first
456
-
457
- expect(response.status ).to eql(201)
458
- expect(response.headers['Content-Type']).to eql('application/scim+json; charset=utf-8')
459
-
460
- expect(new_mock.username).to eql(CustomSaveMockUsersController::CUSTOM_SAVE_BLOCK_USERNAME_INDICATOR)
461
- end
462
269
  end # "context '#create' do"
463
270
 
464
271
  # ===========================================================================
@@ -471,15 +278,13 @@ RSpec.describe Scimitar::ActiveRecordBackedResourcesController do
471
278
 
472
279
  expect_any_instance_of(MockUsersController).to receive(:replace).once.and_call_original
473
280
  expect {
474
- put "/Users/#{@u2.primary_key}", params: attributes.merge(format: :scim)
281
+ put "/Users/#{@u2.id}", params: attributes.merge(format: :scim)
475
282
  }.to_not change { MockUser.count }
476
283
 
477
- expect(response.status ).to eql(200)
478
- expect(response.headers['Content-Type']).to eql('application/scim+json; charset=utf-8')
479
-
284
+ expect(response.status).to eql(200)
480
285
  result = JSON.parse(response.body)
481
286
 
482
- expect(result['id']).to eql(@u2.primary_key.to_s)
287
+ expect(result['id']).to eql(@u2.id.to_s)
483
288
  expect(result['meta']['resourceType']).to eql('User')
484
289
 
485
290
  @u2.reload
@@ -501,17 +306,14 @@ RSpec.describe Scimitar::ActiveRecordBackedResourcesController do
501
306
 
502
307
  it 'notes schema validation failures' do
503
308
  expect {
504
- put "/Users/#{@u2.primary_key}", params: {
309
+ put "/Users/#{@u2.id}", params: {
505
310
  format: :scim
506
311
  # userName parameter is required by schema, but missing
507
312
  }
508
313
  }.to_not change { MockUser.count }
509
314
 
510
- expect(response.status ).to eql(400)
511
- expect(response.headers['Content-Type']).to eql('application/scim+json; charset=utf-8')
512
-
315
+ expect(response.status).to eql(400)
513
316
  result = JSON.parse(response.body)
514
-
515
317
  expect(result['scimType']).to eql('invalidValue')
516
318
  expect(result['detail']).to include('is required')
517
319
 
@@ -531,9 +333,7 @@ RSpec.describe Scimitar::ActiveRecordBackedResourcesController do
531
333
  }
532
334
  }.to_not change { MockUser.count }
533
335
 
534
- expect(response.status ).to eql(400)
535
- expect(response.headers['Content-Type']).to eql('application/scim+json; charset=utf-8')
536
-
336
+ expect(response.status).to eql(400)
537
337
  result = JSON.parse(response.body)
538
338
 
539
339
  expect(result['scimType']).to eql('invalidValue')
@@ -555,11 +355,8 @@ RSpec.describe Scimitar::ActiveRecordBackedResourcesController do
555
355
  }
556
356
  }.to_not change { MockUser.count }
557
357
 
558
- expect(response.status ).to eql(404)
559
- expect(response.headers['Content-Type']).to eql('application/scim+json; charset=utf-8')
560
-
358
+ expect(response.status).to eql(404)
561
359
  result = JSON.parse(response.body)
562
-
563
360
  expect(result['status']).to eql('404')
564
361
  end
565
362
  end # "context '#replace' do"
@@ -588,15 +385,13 @@ RSpec.describe Scimitar::ActiveRecordBackedResourcesController do
588
385
 
589
386
  expect_any_instance_of(MockUsersController).to receive(:update).once.and_call_original
590
387
  expect {
591
- patch "/Users/#{@u2.primary_key}", params: payload.merge(format: :scim)
388
+ patch "/Users/#{@u2.id}", params: payload.merge(format: :scim)
592
389
  }.to_not change { MockUser.count }
593
390
 
594
- expect(response.status ).to eql(200)
595
- expect(response.headers['Content-Type']).to eql('application/scim+json; charset=utf-8')
596
-
391
+ expect(response.status).to eql(200)
597
392
  result = JSON.parse(response.body)
598
393
 
599
- expect(result['id']).to eql(@u2.primary_key.to_s)
394
+ expect(result['id']).to eql(@u2.id.to_s)
600
395
  expect(result['meta']['resourceType']).to eql('User')
601
396
 
602
397
  @u2.reload
@@ -627,15 +422,13 @@ RSpec.describe Scimitar::ActiveRecordBackedResourcesController do
627
422
 
628
423
  expect_any_instance_of(MockUsersController).to receive(:update).once.and_call_original
629
424
  expect {
630
- patch "/Users/#{@u2.primary_key}", params: payload.merge(format: :scim)
425
+ patch "/Users/#{@u2.id}", params: payload.merge(format: :scim)
631
426
  }.to_not change { MockUser.count }
632
427
 
633
- expect(response.status ).to eql(200)
634
- expect(response.headers['Content-Type']).to eql('application/scim+json; charset=utf-8')
635
-
428
+ expect(response.status).to eql(200)
636
429
  result = JSON.parse(response.body)
637
430
 
638
- expect(result['id']).to eql(@u2.primary_key.to_s)
431
+ expect(result['id']).to eql(@u2.id.to_s)
639
432
  expect(result['meta']['resourceType']).to eql('User')
640
433
 
641
434
  @u2.reload
@@ -661,15 +454,13 @@ RSpec.describe Scimitar::ActiveRecordBackedResourcesController do
661
454
 
662
455
  expect_any_instance_of(MockUsersController).to receive(:update).once.and_call_original
663
456
  expect {
664
- patch "/Users/#{@u2.primary_key}", params: payload.merge(format: :scim)
457
+ patch "/Users/#{@u2.id}", params: payload.merge(format: :scim)
665
458
  }.to_not change { MockUser.count }
666
459
 
667
- expect(response.status ).to eql(200)
668
- expect(response.headers['Content-Type']).to eql('application/scim+json; charset=utf-8')
669
-
460
+ expect(response.status).to eql(200)
670
461
  result = JSON.parse(response.body)
671
462
 
672
- expect(result['id']).to eql(@u2.primary_key.to_s)
463
+ expect(result['id']).to eql(@u2.id.to_s)
673
464
  expect(result['meta']['resourceType']).to eql('User')
674
465
 
675
466
  @u2.reload
@@ -695,15 +486,13 @@ RSpec.describe Scimitar::ActiveRecordBackedResourcesController do
695
486
 
696
487
  expect_any_instance_of(MockUsersController).to receive(:update).once.and_call_original
697
488
  expect {
698
- patch "/Users/#{@u2.primary_key}", params: payload.merge(format: :scim)
489
+ patch "/Users/#{@u2.id}", params: payload.merge(format: :scim)
699
490
  }.to_not change { MockUser.count }
700
491
 
701
- expect(response.status ).to eql(200)
702
- expect(response.headers['Content-Type']).to eql('application/scim+json; charset=utf-8')
703
-
492
+ expect(response.status).to eql(200)
704
493
  result = JSON.parse(response.body)
705
494
 
706
- expect(result['id']).to eql(@u2.primary_key.to_s)
495
+ expect(result['id']).to eql(@u2.id.to_s)
707
496
  expect(result['meta']['resourceType']).to eql('User')
708
497
 
709
498
  @u2.reload
@@ -727,7 +516,7 @@ RSpec.describe Scimitar::ActiveRecordBackedResourcesController do
727
516
 
728
517
  it 'notes Rails validation failures' do
729
518
  expect {
730
- patch "/Users/#{@u2.primary_key}", params: {
519
+ patch "/Users/#{@u2.id}", params: {
731
520
  format: :scim,
732
521
  Operations: [
733
522
  {
@@ -739,9 +528,7 @@ RSpec.describe Scimitar::ActiveRecordBackedResourcesController do
739
528
  }
740
529
  }.to_not change { MockUser.count }
741
530
 
742
- expect(response.status ).to eql(400)
743
- expect(response.headers['Content-Type']).to eql('application/scim+json; charset=utf-8')
744
-
531
+ expect(response.status).to eql(400)
745
532
  result = JSON.parse(response.body)
746
533
 
747
534
  expect(result['scimType']).to eql('invalidValue')
@@ -769,153 +556,10 @@ RSpec.describe Scimitar::ActiveRecordBackedResourcesController do
769
556
  }
770
557
  }.to_not change { MockUser.count }
771
558
 
772
- expect(response.status ).to eql(404)
773
- expect(response.headers['Content-Type']).to eql('application/scim+json; charset=utf-8')
774
-
559
+ expect(response.status).to eql(404)
775
560
  result = JSON.parse(response.body)
776
-
777
561
  expect(result['status']).to eql('404')
778
562
  end
779
-
780
- context 'when removing users from groups' do
781
- before :each do
782
- @g1.mock_users << @u1
783
- @g1.mock_users << @u2
784
- @g1.mock_users << @u3
785
-
786
- # (Self-check) Verify group representation
787
- #
788
- get "/Groups/#{@g1.id}", params: { format: :scim }
789
-
790
- expect(response.status).to eql(200)
791
- result = JSON.parse(response.body)
792
-
793
- expect(result['members'].map { |m| m['value'] }.sort()).to eql(MockUser.pluck(:primary_key).sort())
794
- end
795
-
796
- it 'can remove all users' do
797
- expect {
798
- expect {
799
- patch "/Groups/#{@g1.id}", params: {
800
- format: :scim,
801
- Operations: [
802
- {
803
- op: 'remove',
804
- path: 'members'
805
- }
806
- ]
807
- }
808
- }.to_not change { MockUser.count }
809
- }.to_not change { MockGroup.count }
810
-
811
- get "/Groups/#{@g1.id}", params: { format: :scim }
812
-
813
- expect(response.status ).to eql(200)
814
- expect(response.headers['Content-Type']).to eql('application/scim+json; charset=utf-8')
815
-
816
- result = JSON.parse(response.body)
817
-
818
- expect(result['members']).to be_empty
819
- expect(@g1.reload().mock_users).to be_empty
820
- end
821
-
822
- # Define via 'let':
823
- #
824
- # * Hash 'payload', to send via 'patch'
825
- # * MockUser 'removed_user', which is the user that should be removed
826
- #
827
- shared_examples 'a user remover' do
828
- it 'which removes the identified user' do
829
- expect {
830
- expect {
831
- patch "/Groups/#{@g1.id}", params: payload()
832
- }.to_not change { MockUser.count }
833
- }.to_not change { MockGroup.count }
834
-
835
- expected_remaining_user_ids = MockUser
836
- .where.not(primary_key: removed_user().id)
837
- .pluck(:primary_key)
838
- .sort()
839
-
840
- get "/Groups/#{@g1.id}", params: { format: :scim }
841
-
842
- expect(response.status ).to eql(200)
843
- expect(response.headers['Content-Type']).to eql('application/scim+json; charset=utf-8')
844
-
845
- result = JSON.parse(response.body)
846
-
847
- expect(result['members'].map { |m| m['value'] }.sort()).to eql(expected_remaining_user_ids)
848
- expect(@g1.reload().mock_users.map(&:primary_key).sort()).to eql(expected_remaining_user_ids)
849
- end
850
- end
851
-
852
- # https://tools.ietf.org/html/rfc7644#section-3.5.2.2
853
- #
854
- context 'and using an RFC-compliant payload' do
855
- let(:removed_user) { @u2 }
856
- let(:payload) do
857
- {
858
- format: :scim,
859
- Operations: [
860
- {
861
- op: 'remove',
862
- path: "members[value eq \"#{removed_user().primary_key}\"]",
863
- }
864
- ]
865
- }
866
- end
867
-
868
- it_behaves_like 'a user remover'
869
- end # context 'and using an RFC-compliant payload' do
870
-
871
- # https://learn.microsoft.com/en-us/azure/active-directory/app-provisioning/use-scim-to-provision-users-and-groups#update-group-remove-members
872
- #
873
- context 'and using a Microsoft variant payload' do
874
- let(:removed_user) { @u2 }
875
- let(:payload) do
876
- {
877
- format: :scim,
878
- Operations: [
879
- {
880
- op: 'remove',
881
- path: 'members',
882
- value: [{
883
- '$ref' => nil,
884
- 'value' => removed_user().primary_key
885
- }]
886
- }
887
- ]
888
- }
889
- end
890
-
891
- it_behaves_like 'a user remover'
892
- end # context 'and using a Microsoft variant payload' do
893
-
894
- # https://help.salesforce.com/s/articleView?id=sf.identity_scim_manage_groups.htm&type=5
895
- #
896
- context 'and using a Salesforce variant payload' do
897
- let(:removed_user) { @u2 }
898
- let(:payload) do
899
- {
900
- format: :scim,
901
- Operations: [
902
- {
903
- op: 'remove',
904
- path: 'members',
905
- value: {
906
- 'members' => [{
907
- '$ref' => nil,
908
- 'value' => removed_user().primary_key
909
- }]
910
- }
911
- }
912
- ]
913
- }
914
- end
915
-
916
- it_behaves_like 'a user remover'
917
- end # context 'and using a Salesforce variant payload' do
918
- end # "context 'when removing users from groups' do"
919
563
  end # "context '#update' do"
920
564
 
921
565
  # ===========================================================================
@@ -925,7 +569,7 @@ RSpec.describe Scimitar::ActiveRecordBackedResourcesController do
925
569
  expect_any_instance_of(MockUsersController).to receive(:destroy).once.and_call_original
926
570
  expect_any_instance_of(MockUser).to receive(:destroy!).once.and_call_original
927
571
  expect {
928
- delete "/Users/#{@u2.primary_key}", params: { format: :scim }
572
+ delete "/Users/#{@u2.id}", params: { format: :scim }
929
573
  }.to change { MockUser.count }.by(-1)
930
574
 
931
575
  expect(response.status).to eql(204)
@@ -937,7 +581,7 @@ RSpec.describe Scimitar::ActiveRecordBackedResourcesController do
937
581
  expect_any_instance_of(MockUser).to_not receive(:destroy!)
938
582
 
939
583
  expect {
940
- delete "/CustomDestroyUsers/#{@u2.primary_key}", params: { format: :scim }
584
+ delete "/CustomDestroyUsers/#{@u2.id}", params: { format: :scim }
941
585
  }.to_not change { MockUser.count }
942
586
 
943
587
  expect(response.status).to eql(204)
@@ -952,11 +596,8 @@ RSpec.describe Scimitar::ActiveRecordBackedResourcesController do
952
596
  delete '/Users/xyz', params: { format: :scim }
953
597
  }.to_not change { MockUser.count }
954
598
 
955
- expect(response.status ).to eql(404)
956
- expect(response.headers['Content-Type']).to eql('application/scim+json; charset=utf-8')
957
-
599
+ expect(response.status).to eql(404)
958
600
  result = JSON.parse(response.body)
959
-
960
601
  expect(result['status']).to eql('404')
961
602
  end
962
603
  end # "context '#destroy' do"