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.
- checksums.yaml +4 -4
- data/app/controllers/scimitar/active_record_backed_resources_controller.rb +10 -49
- data/app/controllers/scimitar/application_controller.rb +11 -35
- data/app/controllers/scimitar/schemas_controller.rb +0 -5
- data/app/models/scimitar/engine_configuration.rb +5 -13
- data/app/models/scimitar/error_response.rb +0 -12
- data/app/models/scimitar/lists/query_parser.rb +10 -25
- data/app/models/scimitar/resources/base.rb +4 -14
- data/app/models/scimitar/resources/mixin.rb +13 -137
- data/app/models/scimitar/schema/address.rb +0 -1
- data/app/models/scimitar/schema/attribute.rb +5 -14
- data/app/models/scimitar/schema/base.rb +1 -1
- data/app/models/scimitar/schema/vdtp.rb +1 -1
- data/app/models/scimitar/service_provider_configuration.rb +3 -14
- data/config/initializers/scimitar.rb +3 -28
- data/lib/scimitar/version.rb +2 -2
- data/lib/scimitar.rb +2 -6
- data/spec/apps/dummy/app/controllers/mock_groups_controller.rb +1 -1
- data/spec/apps/dummy/app/models/mock_group.rb +1 -1
- data/spec/apps/dummy/app/models/mock_user.rb +8 -36
- data/spec/apps/dummy/config/application.rb +1 -0
- data/spec/apps/dummy/config/environments/test.rb +28 -5
- data/spec/apps/dummy/config/initializers/scimitar.rb +10 -61
- data/spec/apps/dummy/config/routes.rb +6 -15
- data/spec/apps/dummy/db/migrate/20210304014602_create_mock_users.rb +1 -10
- data/spec/apps/dummy/db/migrate/20210308044214_create_join_table_mock_groups_mock_users.rb +3 -8
- data/spec/apps/dummy/db/schema.rb +4 -11
- data/spec/controllers/scimitar/application_controller_spec.rb +3 -72
- data/spec/controllers/scimitar/resource_types_controller_spec.rb +2 -2
- data/spec/controllers/scimitar/schemas_controller_spec.rb +2 -10
- data/spec/models/scimitar/complex_types/email_spec.rb +2 -0
- data/spec/models/scimitar/lists/query_parser_spec.rb +9 -76
- data/spec/models/scimitar/resources/base_spec.rb +70 -208
- data/spec/models/scimitar/resources/base_validation_spec.rb +2 -27
- data/spec/models/scimitar/resources/mixin_spec.rb +43 -768
- data/spec/models/scimitar/schema/attribute_spec.rb +3 -22
- data/spec/models/scimitar/schema/base_spec.rb +1 -1
- data/spec/models/scimitar/schema/user_spec.rb +0 -10
- data/spec/requests/active_record_backed_resources_controller_spec.rb +64 -423
- data/spec/requests/application_controller_spec.rb +3 -16
- metadata +7 -11
- data/LICENSE.txt +0 -21
- data/README.md +0 -671
- 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
|
-
|
9
|
-
|
10
|
-
|
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
|
-
|
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
|
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
|
-
|
50
|
-
|
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(
|
101
|
-
expect(result['Resources'].size).to eql(
|
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.
|
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
|
48
|
+
it 'applies a filter, with case-insensitive value comparison' do
|
111
49
|
get '/Users', params: {
|
112
50
|
format: :scim,
|
113
|
-
filter: 'name.
|
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
|
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.
|
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
|
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.
|
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
|
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.
|
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
|
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
|
-
|
263
|
-
|
264
|
-
|
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
|
-
|
127
|
+
expect(response.status).to eql(200)
|
128
|
+
result = JSON.parse(response.body)
|
288
129
|
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
end
|
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
|
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' }
|
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
|
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.
|
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
|
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
|
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
|
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
|
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.
|
281
|
+
put "/Users/#{@u2.id}", params: attributes.merge(format: :scim)
|
475
282
|
}.to_not change { MockUser.count }
|
476
283
|
|
477
|
-
expect(response.status
|
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.
|
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.
|
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
|
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
|
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
|
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.
|
388
|
+
patch "/Users/#{@u2.id}", params: payload.merge(format: :scim)
|
592
389
|
}.to_not change { MockUser.count }
|
593
390
|
|
594
|
-
expect(response.status
|
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.
|
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.
|
425
|
+
patch "/Users/#{@u2.id}", params: payload.merge(format: :scim)
|
631
426
|
}.to_not change { MockUser.count }
|
632
427
|
|
633
|
-
expect(response.status
|
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.
|
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.
|
457
|
+
patch "/Users/#{@u2.id}", params: payload.merge(format: :scim)
|
665
458
|
}.to_not change { MockUser.count }
|
666
459
|
|
667
|
-
expect(response.status
|
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.
|
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.
|
489
|
+
patch "/Users/#{@u2.id}", params: payload.merge(format: :scim)
|
699
490
|
}.to_not change { MockUser.count }
|
700
491
|
|
701
|
-
expect(response.status
|
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.
|
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.
|
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
|
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
|
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.
|
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.
|
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
|
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"
|