scimaenaga 0.5.0 → 0.7.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 (34) hide show
  1. checksums.yaml +4 -4
  2. data/MIT-LICENSE +1 -0
  3. data/README.md +2 -14
  4. data/app/controllers/concerns/scim_rails/exception_handler.rb +43 -1
  5. data/app/controllers/scim_rails/scim_groups_controller.rb +64 -40
  6. data/app/controllers/scim_rails/scim_users_controller.rb +39 -65
  7. data/app/libraries/scim_patch.rb +15 -10
  8. data/app/libraries/scim_patch_operation.rb +127 -24
  9. data/app/models/scim_rails/scim_query_parser.rb +5 -3
  10. data/config/routes.rb +2 -0
  11. data/lib/generators/scim_rails/templates/initializer.rb +0 -6
  12. data/lib/scim_rails/config.rb +1 -2
  13. data/lib/scim_rails/version.rb +1 -1
  14. data/spec/controllers/scim_rails/scim_groups_controller_spec.rb +249 -136
  15. data/spec/controllers/scim_rails/scim_users_controller_spec.rb +413 -203
  16. data/spec/dummy/app/models/user.rb +21 -0
  17. data/spec/dummy/bin/setup +2 -0
  18. data/spec/dummy/config/initializers/scim_rails_config.rb +6 -4
  19. data/spec/dummy/db/development.sqlite3 +0 -0
  20. data/spec/dummy/db/migrate/20220117095407_add_country_to_users.rb +5 -0
  21. data/spec/dummy/db/migrate/20220131090107_add_deletable_to_users.rb +5 -0
  22. data/spec/dummy/db/schema.rb +7 -5
  23. data/spec/dummy/db/seeds.rb +15 -1
  24. data/spec/dummy/db/test.sqlite3 +0 -0
  25. data/spec/dummy/log/development.log +0 -0
  26. data/spec/dummy/log/test.log +5770 -0
  27. data/spec/dummy/put_group.http +5 -0
  28. data/spec/dummy/tmp/restart.txt +0 -0
  29. data/spec/factories/user.rb +2 -0
  30. data/spec/libraries/scim_patch_operation_spec.rb +61 -31
  31. data/spec/libraries/scim_patch_spec.rb +38 -29
  32. data/spec/models/scim_query_parser_spec.rb +30 -0
  33. metadata +83 -67
  34. data/spec/support/scim_rails_config.rb +0 -59
@@ -1,32 +1,32 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "spec_helper"
3
+ require 'spec_helper'
4
4
 
5
5
  RSpec.describe ScimRails::ScimUsersController, type: :controller do
6
6
  include AuthHelper
7
7
 
8
8
  routes { ScimRails::Engine.routes }
9
9
 
10
- describe "index" do
10
+ describe 'index' do
11
11
  let(:company) { create(:company) }
12
12
 
13
- context "when unauthorized" do
14
- it "returns scim+json content type" do
13
+ context 'when unauthorized' do
14
+ it 'returns scim+json content type' do
15
15
  get :index, as: :json
16
16
 
17
- expect(response.media_type).to eq "application/scim+json"
17
+ expect(response.media_type).to eq 'application/scim+json'
18
18
  end
19
19
 
20
- it "fails with no credentials" do
20
+ it 'fails with no credentials' do
21
21
  get :index, as: :json
22
22
 
23
23
  expect(response.status).to eq 401
24
24
  end
25
25
 
26
- it "fails with invalid credentials" do
27
- request.env["HTTP_AUTHORIZATION"] =
26
+ it 'fails with invalid credentials' do
27
+ request.env['HTTP_AUTHORIZATION'] =
28
28
  ActionController::HttpAuthentication::Basic
29
- .encode_credentials("unauthorized", "123456")
29
+ .encode_credentials('unauthorized', '123456')
30
30
 
31
31
  get :index, as: :json
32
32
 
@@ -34,56 +34,57 @@ RSpec.describe ScimRails::ScimUsersController, type: :controller do
34
34
  end
35
35
  end
36
36
 
37
- context "when when authorized" do
37
+ context 'when when authorized' do
38
38
  before :each do
39
39
  http_login(company)
40
40
  end
41
41
 
42
- it "returns scim+json content type" do
42
+ it 'returns scim+json content type' do
43
43
  get :index, as: :json
44
44
 
45
- expect(response.media_type).to eq "application/scim+json"
45
+ expect(response.media_type).to eq 'application/scim+json'
46
46
  end
47
47
 
48
- it "is successful with valid credentials" do
48
+ it 'is successful with valid credentials' do
49
49
  get :index, as: :json
50
50
 
51
51
  expect(response.status).to eq 200
52
52
  end
53
53
 
54
- it "returns all results" do
54
+ it 'returns all results' do
55
55
  create_list(:user, 10, company: company)
56
56
 
57
57
  get :index, as: :json
58
58
  response_body = JSON.parse(response.body)
59
- expect(response_body.dig("schemas", 0)).to eq "urn:ietf:params:scim:api:messages:2.0:ListResponse"
60
- expect(response_body["totalResults"]).to eq 10
59
+ expect(response_body.dig('schemas',
60
+ 0)).to eq 'urn:ietf:params:scim:api:messages:2.0:ListResponse'
61
+ expect(response_body['totalResults']).to eq 10
61
62
  end
62
63
 
63
- it "defaults to 100 results" do
64
+ it 'defaults to 100 results' do
64
65
  create_list(:user, 300, company: company)
65
66
 
66
67
  get :index, as: :json
67
68
  response_body = JSON.parse(response.body)
68
- expect(response_body["totalResults"]).to eq 300
69
- expect(response_body["Resources"].count).to eq 100
69
+ expect(response_body['totalResults']).to eq 300
70
+ expect(response_body['Resources'].count).to eq 100
70
71
  end
71
72
 
72
- it "paginates results" do
73
+ it 'paginates results' do
73
74
  create_list(:user, 400, company: company)
74
75
  expect(company.users.first.id).to eq 1
75
76
 
76
77
  get :index, params: {
77
78
  startIndex: 101,
78
- count: 200
79
+ count: 200,
79
80
  }, as: :json
80
81
  response_body = JSON.parse(response.body)
81
- expect(response_body["totalResults"]).to eq 400
82
- expect(response_body["Resources"].count).to eq 200
83
- expect(response_body.dig("Resources", 0, "id")).to eq 101
82
+ expect(response_body['totalResults']).to eq 400
83
+ expect(response_body['Resources'].count).to eq 200
84
+ expect(response_body.dig('Resources', 0, 'id')).to eq 101
84
85
  end
85
86
 
86
- it "paginates results by configurable scim_users_list_order" do
87
+ it 'paginates results by configurable scim_users_list_order' do
87
88
  allow(ScimRails.config).to receive(:scim_users_list_order).and_return({ created_at: :desc })
88
89
 
89
90
  create_list(:user, 400, company: company)
@@ -91,78 +92,79 @@ RSpec.describe ScimRails::ScimUsersController, type: :controller do
91
92
 
92
93
  get :index, params: {
93
94
  startIndex: 1,
94
- count: 10
95
+ count: 10,
95
96
  }, as: :json
96
97
  response_body = JSON.parse(response.body)
97
- expect(response_body["totalResults"]).to eq 400
98
- expect(response_body["Resources"].count).to eq 10
99
- expect(response_body.dig("Resources", 0, "id")).to eq 400
98
+ expect(response_body['totalResults']).to eq 400
99
+ expect(response_body['Resources'].count).to eq 10
100
+ expect(response_body.dig('Resources', 0, 'id')).to eq 400
100
101
  end
101
102
 
102
- it "filters results by provided email filter" do
103
- create(:user, email: "test1@example.com", company: company)
104
- create(:user, email: "test2@example.com", company: company)
103
+ it 'filters results by provided email filter' do
104
+ create(:user, email: 'test1@example.com', company: company)
105
+ create(:user, email: 'test2@example.com', company: company)
105
106
 
106
107
  get :index, params: {
107
- filter: "email eq test1@example.com"
108
+ filter: 'email eq test1@example.com',
108
109
  }, as: :json
109
110
  response_body = JSON.parse(response.body)
110
- expect(response_body["totalResults"]).to eq 1
111
- expect(response_body["Resources"].count).to eq 1
111
+ expect(response_body['totalResults']).to eq 1
112
+ expect(response_body['Resources'].count).to eq 1
112
113
  end
113
114
 
114
- it "filters results by provided name filter" do
115
- create(:user, first_name: "Chidi", last_name: "Anagonye", company: company)
116
- create(:user, first_name: "Eleanor", last_name: "Shellstrop", company: company)
115
+ it 'filters results by provided name filter' do
116
+ create(:user, first_name: 'Chidi', last_name: 'Anagonye', company: company)
117
+ create(:user, first_name: 'Eleanor', last_name: 'Shellstrop', company: company)
117
118
 
118
119
  get :index, params: {
119
- filter: "familyName eq Shellstrop"
120
+ filter: 'familyName eq Shellstrop',
120
121
  }, as: :json
121
122
  response_body = JSON.parse(response.body)
122
- expect(response_body["totalResults"]).to eq 1
123
- expect(response_body["Resources"].count).to eq 1
123
+ expect(response_body['totalResults']).to eq 1
124
+ expect(response_body['Resources'].count).to eq 1
124
125
  end
125
126
 
126
- it "returns no results for unfound filter parameters" do
127
+ it 'returns no results for unfound filter parameters' do
127
128
  get :index, params: {
128
- filter: "familyName eq fake_not_there"
129
+ filter: 'familyName eq fake_not_there',
129
130
  }, as: :json
130
131
  response_body = JSON.parse(response.body)
131
- expect(response_body["totalResults"]).to eq 0
132
- expect(response_body["Resources"].count).to eq 0
132
+ expect(response_body['totalResults']).to eq 0
133
+ expect(response_body['Resources'].count).to eq 0
133
134
  end
134
135
 
135
- it "returns no results for undefined filter queries" do
136
+ it 'returns no results for undefined filter queries' do
136
137
  get :index, params: {
137
- filter: "address eq 101 Nowhere USA"
138
+ filter: 'address eq 101 Nowhere USA',
138
139
  }, as: :json
139
140
  expect(response.status).to eq 400
140
141
  response_body = JSON.parse(response.body)
141
- expect(response_body.dig("schemas", 0)).to eq "urn:ietf:params:scim:api:messages:2.0:Error"
142
+ expect(response_body.dig('schemas',
143
+ 0)).to eq 'urn:ietf:params:scim:api:messages:2.0:Error'
142
144
  end
143
145
  end
144
146
  end
145
147
 
146
- describe "show" do
148
+ describe 'show' do
147
149
  let(:company) { create(:company) }
148
150
 
149
- context "when unauthorized" do
150
- it "returns scim+json content type" do
151
+ context 'when unauthorized' do
152
+ it 'returns scim+json content type' do
151
153
  get :show, params: { id: 1 }, as: :json
152
154
 
153
- expect(response.media_type).to eq "application/scim+json"
155
+ expect(response.media_type).to eq 'application/scim+json'
154
156
  end
155
157
 
156
- it "fails with no credentials" do
158
+ it 'fails with no credentials' do
157
159
  get :show, params: { id: 1 }, as: :json
158
160
 
159
161
  expect(response.status).to eq 401
160
162
  end
161
163
 
162
- it "fails with invalid credentials" do
163
- request.env["HTTP_AUTHORIZATION"] =
164
+ it 'fails with invalid credentials' do
165
+ request.env['HTTP_AUTHORIZATION'] =
164
166
  ActionController::HttpAuthentication::Basic
165
- .encode_credentials("unauthorized", "123456")
167
+ .encode_credentials('unauthorized', '123456')
166
168
 
167
169
  get :show, params: { id: 1 }, as: :json
168
170
 
@@ -170,31 +172,31 @@ RSpec.describe ScimRails::ScimUsersController, type: :controller do
170
172
  end
171
173
  end
172
174
 
173
- context "when authorized" do
175
+ context 'when authorized' do
174
176
  before :each do
175
177
  http_login(company)
176
178
  end
177
179
 
178
- it "returns scim+json content type" do
180
+ it 'returns scim+json content type' do
179
181
  get :show, params: { id: 1 }, as: :json
180
182
 
181
- expect(response.media_type).to eq "application/scim+json"
183
+ expect(response.media_type).to eq 'application/scim+json'
182
184
  end
183
185
 
184
- it "is successful with valid credentials" do
186
+ it 'is successful with valid credentials' do
185
187
  create(:user, id: 1, company: company)
186
188
  get :show, params: { id: 1 }, as: :json
187
189
 
188
190
  expect(response.status).to eq 200
189
191
  end
190
192
 
191
- it "returns :not_found for id that cannot be found" do
192
- get :show, params: { id: "fake_id" }, as: :json
193
+ it 'returns :not_found for id that cannot be found' do
194
+ get :show, params: { id: 'fake_id' }, as: :json
193
195
 
194
196
  expect(response.status).to eq 404
195
197
  end
196
198
 
197
- it "returns :not_found for a correct id but unauthorized company" do
199
+ it 'returns :not_found for a correct id but unauthorized company' do
198
200
  new_company = create(:company)
199
201
  create(:user, company: new_company, id: 1)
200
202
 
@@ -205,26 +207,26 @@ RSpec.describe ScimRails::ScimUsersController, type: :controller do
205
207
  end
206
208
  end
207
209
 
208
- describe "create" do
210
+ describe 'create' do
209
211
  let(:company) { create(:company) }
210
212
 
211
- context "when unauthorized" do
212
- it "returns scim+json content type" do
213
+ context 'when unauthorized' do
214
+ it 'returns scim+json content type' do
213
215
  post :create, as: :json
214
216
 
215
- expect(response.media_type).to eq "application/scim+json"
217
+ expect(response.media_type).to eq 'application/scim+json'
216
218
  end
217
219
 
218
- it "fails with no credentials" do
220
+ it 'fails with no credentials' do
219
221
  post :create, as: :json
220
222
 
221
223
  expect(response.status).to eq 401
222
224
  end
223
225
 
224
- it "fails with invalid credentials" do
225
- request.env["HTTP_AUTHORIZATION"] =
226
+ it 'fails with invalid credentials' do
227
+ request.env['HTTP_AUTHORIZATION'] =
226
228
  ActionController::HttpAuthentication::Basic
227
- .encode_credentials("unauthorized", "123456")
229
+ .encode_credentials('unauthorized', '123456')
228
230
 
229
231
  post :create, as: :json
230
232
 
@@ -232,139 +234,139 @@ RSpec.describe ScimRails::ScimUsersController, type: :controller do
232
234
  end
233
235
  end
234
236
 
235
- context "when authorized" do
237
+ context 'when authorized' do
236
238
  before :each do
237
239
  http_login(company)
238
240
  end
239
241
 
240
- it "returns scim+json content type" do
242
+ it 'returns scim+json content type' do
241
243
  post :create, params: {
242
244
  name: {
243
- givenName: "New",
244
- familyName: "User"
245
+ givenName: 'New',
246
+ familyName: 'User',
245
247
  },
246
248
  emails: [
247
249
  {
248
- value: "new@example.com"
250
+ value: 'new@example.com',
249
251
  }
250
- ]
252
+ ],
251
253
  }, as: :json
252
254
 
253
- expect(response.media_type).to eq "application/scim+json"
255
+ expect(response.media_type).to eq 'application/scim+json'
254
256
  end
255
257
 
256
- it "is successful with valid credentials" do
258
+ it 'is successful with valid credentials' do
257
259
  expect(company.users.count).to eq 0
258
260
 
259
261
  post :create, params: {
260
262
  name: {
261
- givenName: "New",
262
- familyName: "User"
263
+ givenName: 'New',
264
+ familyName: 'User',
263
265
  },
264
266
  emails: [
265
267
  {
266
- value: "new@example.com"
268
+ value: 'new@example.com',
267
269
  }
268
- ]
270
+ ],
269
271
  }, as: :json
270
272
 
271
273
  expect(response.status).to eq 201
272
274
  expect(company.users.count).to eq 1
273
275
  user = company.users.first
274
276
  expect(user.persisted?).to eq true
275
- expect(user.first_name).to eq "New"
276
- expect(user.last_name).to eq "User"
277
- expect(user.email).to eq "new@example.com"
277
+ expect(user.first_name).to eq 'New'
278
+ expect(user.last_name).to eq 'User'
279
+ expect(user.email).to eq 'new@example.com'
278
280
  end
279
281
 
280
- it "ignores unconfigured params" do
282
+ it 'ignores unconfigured params' do
281
283
  post :create, params: {
282
284
  name: {
283
- formattedName: "New User",
284
- givenName: "New",
285
- familyName: "User"
285
+ formattedName: 'New User',
286
+ givenName: 'New',
287
+ familyName: 'User',
286
288
  },
287
289
  emails: [
288
290
  {
289
- value: "new@example.com"
291
+ value: 'new@example.com',
290
292
  }
291
- ]
293
+ ],
292
294
  }, as: :json
293
295
 
294
296
  expect(response.status).to eq 201
295
297
  expect(company.users.count).to eq 1
296
298
  end
297
299
 
298
- it "returns 422 if required params are missing" do
300
+ it 'returns 422 if required params are missing' do
299
301
  post :create, params: {
300
302
  name: {
301
- familyName: "User"
303
+ familyName: 'User',
302
304
  },
303
305
  emails: [
304
306
  {
305
- value: "new@example.com"
307
+ value: 'new@example.com',
306
308
  }
307
- ]
309
+ ],
308
310
  }, as: :json
309
311
 
310
312
  expect(response.status).to eq 422
311
313
  expect(company.users.count).to eq 0
312
314
  end
313
315
 
314
- it "returns 201 if user already exists and updates user" do
315
- create(:user, email: "new@example.com", company: company)
316
+ it 'returns 201 if user already exists and updates user' do
317
+ create(:user, email: 'new@example.com', company: company)
316
318
 
317
319
  post :create, params: {
318
320
  name: {
319
- givenName: "Not New",
320
- familyName: "User"
321
+ givenName: 'Not New',
322
+ familyName: 'User',
321
323
  },
322
324
  emails: [
323
325
  {
324
- value: "new@example.com"
326
+ value: 'new@example.com',
325
327
  }
326
- ]
328
+ ],
327
329
  }, as: :json
328
330
 
329
331
  expect(response.status).to eq 201
330
332
  expect(company.users.count).to eq 1
331
- expect(company.users.first.first_name).to eq "Not New"
333
+ expect(company.users.first.first_name).to eq 'Not New'
332
334
  end
333
335
 
334
- it "returns 409 if user already exists and config.scim_user_prevent_update_on_create is set to true" do
336
+ it 'returns 409 if user already exists and config.scim_user_prevent_update_on_create is set to true' do
335
337
  allow(ScimRails.config).to receive(:scim_user_prevent_update_on_create).and_return(true)
336
- create(:user, email: "new@example.com", company: company)
338
+ create(:user, email: 'new@example.com', company: company)
337
339
 
338
340
  post :create, params: {
339
341
  name: {
340
- givenName: "Not New",
341
- familyName: "User"
342
+ givenName: 'Not New',
343
+ familyName: 'User',
342
344
  },
343
345
  emails: [
344
346
  {
345
- value: "new@example.com"
347
+ value: 'new@example.com',
346
348
  }
347
- ]
349
+ ],
348
350
  }, as: :json
349
351
 
350
352
  expect(response.status).to eq 409
351
353
  expect(company.users.count).to eq 1
352
354
  end
353
355
 
354
- it "creates and archives inactive user" do
356
+ it 'creates and archives inactive user' do
355
357
  post :create, params: {
356
358
  id: 1,
357
- userName: "test@example.com",
359
+ userName: 'test@example.com',
358
360
  name: {
359
- givenName: "Test",
360
- familyName: "User"
361
+ givenName: 'Test',
362
+ familyName: 'User',
361
363
  },
362
364
  emails: [
363
365
  {
364
- value: "test@example.com"
366
+ value: 'test@example.com',
365
367
  }
366
368
  ],
367
- active: "false"
369
+ active: false,
368
370
  }, as: :json
369
371
 
370
372
  expect(response.status).to eq 201
@@ -375,26 +377,26 @@ RSpec.describe ScimRails::ScimUsersController, type: :controller do
375
377
  end
376
378
  end
377
379
 
378
- describe "put update" do
380
+ describe 'put update' do
379
381
  let(:company) { create(:company) }
380
382
 
381
- context "when unauthorized" do
382
- it "returns scim+json content type" do
383
+ context 'when unauthorized' do
384
+ it 'returns scim+json content type' do
383
385
  put :put_update, params: { id: 1 }, as: :json
384
386
 
385
- expect(response.media_type).to eq "application/scim+json"
387
+ expect(response.media_type).to eq 'application/scim+json'
386
388
  end
387
389
 
388
- it "fails with no credentials" do
390
+ it 'fails with no credentials' do
389
391
  put :put_update, params: { id: 1 }, as: :json
390
392
 
391
393
  expect(response.status).to eq 401
392
394
  end
393
395
 
394
- it "fails with invalid credentials" do
395
- request.env["HTTP_AUTHORIZATION"] =
396
+ it 'fails with invalid credentials' do
397
+ request.env['HTTP_AUTHORIZATION'] =
396
398
  ActionController::HttpAuthentication::Basic
397
- .encode_credentials("unauthorized", "123456")
399
+ .encode_credentials('unauthorized', '123456')
398
400
 
399
401
  put :put_update, params: { id: 1 }, as: :json
400
402
 
@@ -402,50 +404,56 @@ RSpec.describe ScimRails::ScimUsersController, type: :controller do
402
404
  end
403
405
  end
404
406
 
405
- context "when authorized" do
407
+ context 'when authorized' do
406
408
  let!(:user) { create(:user, id: 1, company: company) }
407
409
 
408
410
  before :each do
409
411
  http_login(company)
410
412
  end
411
413
 
412
- it "returns scim+json content type" do
414
+ it 'returns scim+json content type' do
413
415
  put :put_update, params: put_params, as: :json
414
416
 
415
- expect(response.media_type).to eq "application/scim+json"
417
+ expect(response.media_type).to eq 'application/scim+json'
416
418
  end
417
419
 
418
- it "is successful with with valid credentials" do
420
+ it 'is successful with valid credentials' do
419
421
  put :put_update, params: put_params, as: :json
420
422
 
421
423
  expect(response.status).to eq 200
422
424
  end
423
425
 
424
- it "deprovisions an active record" do
425
- request.content_type = "application/scim+json"
426
+ it 'successfully change user email' do
427
+ put :put_update, params: put_params(id: user.id), as: :json
428
+
429
+ expect(user.reload.email).to eq 'test@example.com'
430
+ end
431
+
432
+ it 'deprovisions an active record' do
433
+ request.content_type = 'application/scim+json'
426
434
  put :put_update, params: put_params(active: false), as: :json
427
435
 
428
436
  expect(response.status).to eq 200
429
437
  expect(user.reload.active?).to eq false
430
438
  end
431
439
 
432
- it "reprovisions an inactive record" do
440
+ it 'reprovisions an inactive record' do
433
441
  user.archive!
434
442
  expect(user.reload.active?).to eq false
435
- request.content_type = "application/scim+json"
443
+ request.content_type = 'application/scim+json'
436
444
  put :put_update, params: put_params(active: true), as: :json
437
445
 
438
446
  expect(response.status).to eq 200
439
447
  expect(user.reload.active?).to eq true
440
448
  end
441
449
 
442
- it "returns :not_found for id that cannot be found" do
443
- put :put_update, params: { id: "fake_id" }, as: :json
450
+ it 'returns :not_found for id that cannot be found' do
451
+ put :put_update, params: { id: 'fake_id' }, as: :json
444
452
 
445
453
  expect(response.status).to eq 404
446
454
  end
447
455
 
448
- it "returns :not_found for a correct id but unauthorized company" do
456
+ it 'returns :not_found for a correct id but unauthorized company' do
449
457
  new_company = create(:company)
450
458
  create(:user, company: new_company, id: 1000)
451
459
 
@@ -454,16 +462,16 @@ RSpec.describe ScimRails::ScimUsersController, type: :controller do
454
462
  expect(response.status).to eq 404
455
463
  end
456
464
 
457
- it "is returns 422 with incomplete request" do
465
+ it 'is returns 422 with incomplete request' do
458
466
  put :put_update, params: {
459
467
  id: 1,
460
- userName: "test@example.com",
468
+ userName: 'test@example.com',
461
469
  emails: [
462
470
  {
463
- value: "test@example.com"
471
+ value: 'test@example.com',
464
472
  }
465
473
  ],
466
- active: "true"
474
+ active: 'true',
467
475
  }, as: :json
468
476
 
469
477
  expect(response.status).to eq 422
@@ -471,26 +479,26 @@ RSpec.describe ScimRails::ScimUsersController, type: :controller do
471
479
  end
472
480
  end
473
481
 
474
- describe "patch update" do
482
+ describe 'patch update' do
475
483
  let(:company) { create(:company) }
476
484
 
477
- context "when unauthorized" do
478
- it "returns scim+json content type" do
485
+ context 'when unauthorized' do
486
+ it 'returns scim+json content type' do
479
487
  patch :patch_update, params: patch_params(id: 1), as: :json
480
488
 
481
- expect(response.media_type).to eq "application/scim+json"
489
+ expect(response.media_type).to eq 'application/scim+json'
482
490
  end
483
491
 
484
- it "fails with no credentials" do
492
+ it 'fails with no credentials' do
485
493
  patch :patch_update, params: patch_params(id: 1), as: :json
486
494
 
487
495
  expect(response.status).to eq 401
488
496
  end
489
497
 
490
- it "fails with invalid credentials" do
491
- request.env["HTTP_AUTHORIZATION"] =
498
+ it 'fails with invalid credentials' do
499
+ request.env['HTTP_AUTHORIZATION'] =
492
500
  ActionController::HttpAuthentication::Basic
493
- .encode_credentials("unauthorized", "123456")
501
+ .encode_credentials('unauthorized', '123456')
494
502
 
495
503
  patch :patch_update, params: patch_params(id: 1), as: :json
496
504
 
@@ -498,32 +506,55 @@ RSpec.describe ScimRails::ScimUsersController, type: :controller do
498
506
  end
499
507
  end
500
508
 
501
- context "when authorized" do
509
+ context 'when authorized' do
502
510
  let!(:user) { create(:user, id: 1, company: company) }
503
511
 
504
512
  before :each do
505
513
  http_login(company)
506
514
  end
507
515
 
508
- it "returns scim+json content type" do
509
- patch :patch_update, params: patch_params(id: 1), as: :json
516
+ it 'returns scim+json content type' do
517
+ patch :patch_update, params: patch_params(id: user.id), as: :json
510
518
 
511
- expect(response.media_type).to eq "application/scim+json"
519
+ expect(response.media_type).to eq 'application/scim+json'
512
520
  end
513
521
 
514
- it "is successful with valid credentials" do
515
- patch :patch_update, params: patch_params(id: 1), as: :json
522
+ it 'is successful with valid credentials' do
523
+ patch :patch_update, params: patch_params(id: user.id), as: :json
516
524
 
517
525
  expect(response.status).to eq 200
518
526
  end
519
527
 
520
- it "returns :not_found for id that cannot be found" do
521
- get :patch_update, params: patch_params(id: "fake_id"), as: :json
528
+ it 'rollback all changes when contains any invalid operation' do
529
+ expect do
530
+ patch :patch_update, params: {
531
+ schemas: ['urn:ietf:params:scim:api:messages:2.0:PatchOp'],
532
+ id: user.id,
533
+ Operations: [
534
+ {
535
+ op: 'Replace',
536
+ path: 'emails[type eq "work"].value',
537
+ value: 'change@example.com',
538
+ },
539
+ {
540
+ op: 'Replace',
541
+ value: 'hoge',
542
+ }
543
+ ],
544
+ },
545
+ as: :json
546
+ end.to_not change { user.reload.email }
547
+
548
+ expect(response.status).to eq 422
549
+ end
550
+
551
+ it 'returns :not_found for id that cannot be found' do
552
+ get :patch_update, params: patch_params(id: 'fake_id'), as: :json
522
553
 
523
554
  expect(response.status).to eq 404
524
555
  end
525
556
 
526
- it "returns :not_found for a correct id but unauthorized company" do
557
+ it 'returns :not_found for a correct id but unauthorized company' do
527
558
  new_company = create(:company)
528
559
  create(:user, company: new_company, id: 1000)
529
560
 
@@ -532,12 +563,15 @@ RSpec.describe ScimRails::ScimUsersController, type: :controller do
532
563
  expect(response.status).to eq 404
533
564
  end
534
565
 
535
- xit "successfully archives user" do
566
+ it 'successfully archives user' do
536
567
  expect(company.users.count).to eq 1
537
568
  user = company.users.first
538
569
  expect(user.archived?).to eq false
539
570
 
540
- patch :patch_update, params: patch_params(id: 1), as: :json
571
+ patch \
572
+ :patch_update,
573
+ params: patch_active_params(id: user.id, active: false),
574
+ as: :json
541
575
 
542
576
  expect(response.status).to eq 200
543
577
  expect(company.users.count).to eq 1
@@ -545,14 +579,14 @@ RSpec.describe ScimRails::ScimUsersController, type: :controller do
545
579
  expect(user.archived?).to eq true
546
580
  end
547
581
 
548
- xit "successfully restores user" do
582
+ it 'successfully restores user' do
549
583
  expect(company.users.count).to eq 1
550
584
  user = company.users.first.tap(&:archive!)
551
585
  expect(user.archived?).to eq true
552
586
 
553
587
  patch \
554
588
  :patch_update,
555
- params: patch_params(id: 1, active: true),
589
+ params: patch_active_params(id: 1, active: true),
556
590
  as: :json
557
591
 
558
592
  expect(response.status).to eq 200
@@ -561,7 +595,7 @@ RSpec.describe ScimRails::ScimUsersController, type: :controller do
561
595
  expect(user.archived?).to eq false
562
596
  end
563
597
 
564
- it "successfully change user email" do
598
+ it 'successfully change user email' do
565
599
  expect(company.users.count).to eq 1
566
600
  user = company.users.first
567
601
  user.update(email: 'test@example.com')
@@ -569,7 +603,7 @@ RSpec.describe ScimRails::ScimUsersController, type: :controller do
569
603
 
570
604
  patch \
571
605
  :patch_update,
572
- params: patch_params(id: 1, active: true),
606
+ params: patch_params(id: 1),
573
607
  as: :json
574
608
 
575
609
  expect(response.status).to eq 200
@@ -578,121 +612,297 @@ RSpec.describe ScimRails::ScimUsersController, type: :controller do
578
612
  expect(user.email).to eq 'change@example.com'
579
613
  end
580
614
 
581
- it "is case insensetive for op value" do
615
+ it 'is case insensetive for op value' do
582
616
  # Note, this is for backward compatibility. op should always
583
617
  # be lower case and support for case insensitivity will be removed
584
618
  patch :patch_update, params: {
585
- schemas: ["urn:ietf:params:scim:api:messages:2.0:PatchOp"],
619
+ schemas: ['urn:ietf:params:scim:api:messages:2.0:PatchOp'],
586
620
  id: 1,
587
621
  Operations: [
588
- {
589
- op: "Replace",
590
- path: "emails[type eq \"work\"].value",
591
- value: "test@example.com"
622
+ {
623
+ op: 'Replace',
624
+ path: 'emails[type eq "work"].value',
625
+ value: 'test@example.com',
592
626
  }
593
- ]
627
+ ],
594
628
  }, as: :json
595
629
 
596
630
  expect(response.status).to eq 200
597
631
  end
598
632
 
599
- xit "returns 422 when value is not an object" do
633
+ it "don't update if not included in mutable attributes" do
634
+ expect do
635
+ patch :patch_update, params: {
636
+ schemas: ['urn:ietf:params:scim:api:messages:2.0:PatchOp'],
637
+ id: user.id,
638
+ Operations: [
639
+ {
640
+ op: 'Replace',
641
+ path: 'emails[type eq "work"].value',
642
+ value: 'change@example.com',
643
+ },
644
+ {
645
+ op: 'Replace',
646
+ path: 'country',
647
+ value: 'Japan',
648
+ }
649
+ ],
650
+ }, as: :json
651
+ end.not_to change { user.reload.country }
652
+
653
+ expect(response.status).to eq 422
654
+ end
655
+
656
+ xit 'returns 422 when value is not an object' do
600
657
  patch :patch_update, params: {
601
- schemas: ["urn:ietf:params:scim:api:messages:2.0:PatchOp"],
658
+ schemas: ['urn:ietf:params:scim:api:messages:2.0:PatchOp'],
602
659
  id: 1,
603
660
  Operations: [
604
661
  {
605
- op: "replace",
606
- path: "emails[type eq \"work\"].value",
607
- value: "francis@example.com"
662
+ op: 'replace',
663
+ path: 'emails[type eq "work"].value',
664
+ value: 'francis@example.com',
608
665
  }
609
- ]
666
+ ],
610
667
  }
611
668
 
612
669
  expect(response.status).to eq 422
613
670
  response_body = JSON.parse(response.body)
614
- expect(response_body.dig("schemas", 0)).to eq "urn:ietf:params:scim:api:messages:2.0:Error"
671
+ expect(response_body.dig('schemas',
672
+ 0)).to eq 'urn:ietf:params:scim:api:messages:2.0:Error'
615
673
  end
616
674
 
617
- it "returns 422 when value is missing" do
675
+ it 'returns 422 when value is missing' do
618
676
  patch :patch_update, params: {
619
- schemas: ["urn:ietf:params:scim:api:messages:2.0:PatchOp"],
677
+ schemas: ['urn:ietf:params:scim:api:messages:2.0:PatchOp'],
620
678
  id: 1,
621
679
  Operations: [
622
680
  {
623
- op: "replace",
624
- path: "emails[type eq \"work\"].value"
681
+ op: 'replace',
682
+ path: 'emails[type eq "work"].value',
625
683
  }
626
- ]
684
+ ],
627
685
  }, as: :json
628
686
 
629
687
  expect(response.status).to eq 422
630
688
  response_body = JSON.parse(response.body)
631
- expect(response_body.dig("schemas", 0)).to eq "urn:ietf:params:scim:api:messages:2.0:Error"
689
+ expect(response_body.dig('schemas',
690
+ 0)).to eq 'urn:ietf:params:scim:api:messages:2.0:Error'
632
691
  end
633
692
 
634
- it "returns 422 when path is missing" do
693
+ it 'returns 422 when path is missing' do
635
694
  patch :patch_update, params: {
636
- schemas: ["urn:ietf:params:scim:api:messages:2.0:PatchOp"],
695
+ schemas: ['urn:ietf:params:scim:api:messages:2.0:PatchOp'],
637
696
  id: 1,
638
697
  Operations: [
639
698
  {
640
- op: "replace"
699
+ op: 'replace',
641
700
  }
642
- ]
701
+ ],
643
702
  }, as: :json
644
703
 
645
704
  expect(response.status).to eq 422
646
705
  response_body = JSON.parse(response.body)
647
- expect(response_body.dig("schemas", 0)).to eq "urn:ietf:params:scim:api:messages:2.0:Error"
706
+ expect(response_body.dig('schemas',
707
+ 0)).to eq 'urn:ietf:params:scim:api:messages:2.0:Error'
648
708
  end
649
709
 
650
- it "returns 422 operations key is missing" do
710
+ it 'returns 422 operations key is missing' do
651
711
  patch :patch_update, params: {
652
- schemas: ["urn:ietf:params:scim:api:messages:2.0:PatchOp"],
712
+ schemas: ['urn:ietf:params:scim:api:messages:2.0:PatchOp'],
653
713
  id: 1,
654
714
  Foobars: [
655
715
  {
656
- op: "replace"
716
+ op: 'replace',
657
717
  }
658
- ]
718
+ ],
659
719
  }, as: :json
660
720
 
661
721
  expect(response.status).to eq 422
662
722
  response_body = JSON.parse(response.body)
663
- expect(response_body.dig("schemas", 0)).to eq "urn:ietf:params:scim:api:messages:2.0:Error"
723
+ expect(response_body.dig('schemas',
724
+ 0)).to eq 'urn:ietf:params:scim:api:messages:2.0:Error'
725
+ end
726
+
727
+ it 'successfully change multiple attributes' do
728
+ patch :patch_update, params: {
729
+ schemas: ['urn:ietf:params:scim:api:messages:2.0:PatchOp'],
730
+ id: 1,
731
+ Operations: [
732
+ {
733
+ op: 'Replace',
734
+ path: 'emails[type eq "work"].value',
735
+ value: 'changed@example.com',
736
+ },
737
+ {
738
+ op: 'Replace',
739
+ value: {
740
+ 'name.givenName': 'changedGivenName',
741
+ 'name.familyName': 'changedFamilyName',
742
+ },
743
+ }
744
+ ],
745
+ }, as: :json
746
+
747
+ user.reload
748
+ expect(user.email).to eq 'changed@example.com'
749
+ expect(user.first_name).to eq 'changedGivenName'
750
+ expect(user.last_name).to eq 'changedFamilyName'
751
+ end
752
+ end
753
+ end
754
+
755
+ describe 'destroy' do
756
+ let(:company) { create(:company) }
757
+
758
+ context "when unauthorized" do
759
+ it "returns scim+json content type" do
760
+ delete :destroy, params: { id: 1 }, as: :json
761
+
762
+ expect(response.media_type).to eq "application/scim+json"
763
+ end
764
+
765
+ it "fails with no credentials" do
766
+ delete :destroy, params: { id: 1 }, as: :json
767
+
768
+ expect(response.status).to eq 401
769
+ end
770
+
771
+ it "fails with invalid credentials" do
772
+ request.env["HTTP_AUTHORIZATION"] =
773
+ ActionController::HttpAuthentication::Basic
774
+ .encode_credentials("unauthorized", "123456")
775
+
776
+ delete :destroy, params: { id: 1 }, as: :json
777
+
778
+ expect(response.status).to eq 401
779
+ end
780
+ end
781
+
782
+ context 'when authorized' do
783
+ let!(:user) { create(:user, id: 1, company: company) }
784
+
785
+ before :each do
786
+ http_login(company)
787
+ end
788
+
789
+ context 'when User destroy method is configured' do
790
+
791
+ it 'is sucessful with valid credentials' do
792
+ delete :destroy, params: { id: 1 }, as: :json
793
+
794
+ expect(response.status).to eq 204
795
+ end
796
+
797
+ it 'returns empty response' do
798
+ delete :destroy, params: { id: 1 }, as: :json
799
+
800
+ expect(response.body).to be_empty
801
+ end
802
+
803
+ it 'successfully deletes User' do
804
+ expect do
805
+ delete :destroy, params: { id: 1 }, as: :json
806
+ end.to change { company.users.reload.count }.from(1).to(0)
807
+
808
+ expect(response.status).to eq 204
809
+ end
810
+ end
811
+
812
+ context 'when User destroy method is not configured' do
813
+ it 'does not delete User' do
814
+ allow(ScimRails.config).to(
815
+ receive(:user_destroy_method).and_return(nil)
816
+ )
817
+
818
+ expect do
819
+ delete :destroy, params: { id: 1 }, as: :json
820
+ end.not_to change { company.users.reload.count }.from(1)
821
+
822
+ expect(response.status).to eq 500
823
+ end
824
+ end
825
+
826
+ context 'when User destroy method is invalid' do
827
+ it 'does not delete User' do
828
+ allow(ScimRails.config).to(
829
+ receive(:user_destroy_method).and_return('destory!')
830
+ )
831
+
832
+ expect do
833
+ delete :destroy, params: { id: 1 }, as: :json
834
+ end.not_to change { company.users.reload.count }.from(1)
835
+
836
+ expect(response.status).to eq 500
837
+ end
838
+ end
839
+
840
+ context 'when target User is not found' do
841
+ it 'return 404 not found' do
842
+ expect do
843
+ delete :destroy, params: { id: 999999 }, as: :json
844
+ end.not_to change { company.users.reload.count }.from(1)
845
+
846
+ expect(response.status).to eq 404
847
+ end
848
+ end
849
+
850
+ context 'when target User are not allowed to delete' do
851
+ let!(:user) { create(:user, id: 1, company: company, deletable: false) }
852
+
853
+ it 'does not delete user' do
854
+ expect do
855
+ delete :destroy, params: { id: 1 }, as: :json
856
+ end.not_to change { company.users.reload.count }.from(1)
857
+
858
+ expect(response.status).to eq 400
859
+ end
664
860
  end
665
861
  end
666
862
  end
667
863
 
668
864
  def patch_params(id:, active: false)
669
865
  {
670
- schemas: ["urn:ietf:params:scim:api:messages:2.0:PatchOp"],
866
+ schemas: ['urn:ietf:params:scim:api:messages:2.0:PatchOp'],
671
867
  id: id,
672
868
  Operations: [
673
869
  {
674
- op: "Replace",
675
- path: "emails[type eq \"work\"].value",
676
- value: "change@example.com"
870
+ op: 'Replace',
871
+ path: 'emails[type eq "work"].value',
872
+ value: 'change@example.com',
677
873
  }
678
- ]
874
+ ],
679
875
  }
680
876
  end
681
877
 
682
- def put_params(active: true)
878
+ def patch_active_params(id:, active: false)
683
879
  {
684
- id: 1,
685
- userName: "test@example.com",
880
+ schemas: ['urn:ietf:params:scim:api:messages:2.0:PatchOp'],
881
+ id: id,
882
+ Operations: [
883
+ {
884
+ op: 'Replace',
885
+ path: 'active',
886
+ value: active,
887
+ }
888
+ ],
889
+ }
890
+ end
891
+
892
+ def put_params(id: 1, active: true)
893
+ {
894
+ id: id,
895
+ userName: 'test@example.com',
686
896
  name: {
687
- givenName: "Test",
688
- familyName: "User"
897
+ givenName: 'Test',
898
+ familyName: 'User',
689
899
  },
690
900
  emails: [
691
901
  {
692
- value: "test@example.com"
902
+ value: 'test@example.com',
693
903
  }
694
904
  ],
695
- active: active
905
+ active: active,
696
906
  }
697
907
  end
698
908
  end