scimitar 1.5.2 → 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 +6 -27
- data/app/controllers/scimitar/application_controller.rb +9 -29
- data/app/models/scimitar/engine_configuration.rb +3 -7
- data/app/models/scimitar/error_response.rb +0 -12
- data/app/models/scimitar/errors.rb +1 -1
- data/app/models/scimitar/lists/query_parser.rb +4 -14
- data/app/models/scimitar/resources/base.rb +1 -1
- data/app/models/scimitar/resources/mixin.rb +4 -113
- data/app/models/scimitar/schema/address.rb +0 -1
- data/app/models/scimitar/schema/attribute.rb +1 -1
- data/app/models/scimitar/schema/base.rb +3 -1
- data/app/models/scimitar/schema/vdtp.rb +1 -1
- data/config/initializers/scimitar.rb +70 -86
- data/lib/scimitar/version.rb +2 -2
- 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 -19
- 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 +9 -44
- data/spec/apps/dummy/config/routes.rb +0 -4
- data/spec/apps/dummy/db/migrate/20210304014602_create_mock_users.rb +1 -9
- 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 -10
- data/spec/controllers/scimitar/application_controller_spec.rb +1 -70
- data/spec/controllers/scimitar/schemas_controller_spec.rb +2 -2
- data/spec/models/scimitar/complex_types/email_spec.rb +2 -0
- data/spec/models/scimitar/lists/query_parser_spec.rb +9 -9
- data/spec/models/scimitar/resources/base_spec.rb +66 -161
- data/spec/models/scimitar/resources/base_validation_spec.rb +2 -27
- data/spec/models/scimitar/resources/mixin_spec.rb +43 -757
- data/spec/models/scimitar/resources/user_spec.rb +4 -4
- data/spec/models/scimitar/schema/attribute_spec.rb +3 -0
- 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 +40 -309
- data/spec/requests/application_controller_spec.rb +3 -17
- metadata +7 -7
@@ -42,25 +42,25 @@ RSpec.describe Scimitar::Resources::User do
|
|
42
42
|
let(:user) { described_class.new }
|
43
43
|
|
44
44
|
it 'adds the error when the value is a string' do
|
45
|
-
user.add_errors_from_hash({key: 'some error'})
|
45
|
+
user.add_errors_from_hash(errors_hash: {key: 'some error'})
|
46
46
|
expect(user.errors.messages.to_h).to eql({key: ['some error']})
|
47
47
|
expect(user.errors.full_messages).to eql(['Key some error'])
|
48
48
|
end
|
49
49
|
|
50
50
|
it 'adds the error when the value is an array' do
|
51
|
-
user.add_errors_from_hash({key: ['error1', 'error2']})
|
51
|
+
user.add_errors_from_hash(errors_hash: {key: ['error1', 'error2']})
|
52
52
|
expect(user.errors.messages.to_h).to eql({key: ['error1', 'error2']})
|
53
53
|
expect(user.errors.full_messages).to eql(['Key error1', 'Key error2'])
|
54
54
|
end
|
55
55
|
|
56
56
|
it 'adds the error with prefix when the value is a string' do
|
57
|
-
user.add_errors_from_hash({key: 'some error'}, prefix: :pre)
|
57
|
+
user.add_errors_from_hash(errors_hash: {key: 'some error'}, prefix: :pre)
|
58
58
|
expect(user.errors.messages.to_h).to eql({:'pre.key' => ['some error']})
|
59
59
|
expect(user.errors.full_messages).to eql(['Pre key some error'])
|
60
60
|
end
|
61
61
|
|
62
62
|
it 'adds the error wity prefix when the value is an array' do
|
63
|
-
user.add_errors_from_hash({key: ['error1', 'error2']}, prefix: :pre)
|
63
|
+
user.add_errors_from_hash(errors_hash: {key: ['error1', 'error2']}, prefix: :pre)
|
64
64
|
expect(user.errors.messages.to_h).to eql({:'pre.key' => ['error1', 'error2']})
|
65
65
|
expect(user.errors.full_messages).to eql(['Pre key error1', 'Pre key error2'])
|
66
66
|
end
|
@@ -21,8 +21,10 @@ RSpec.describe Scimitar::Schema::Attribute do
|
|
21
21
|
expect(name.type).to eql('complex')
|
22
22
|
expect(name.subAttributes).to eql(Scimitar::Schema::Name.scim_attributes)
|
23
23
|
end
|
24
|
+
|
24
25
|
end
|
25
26
|
|
27
|
+
|
26
28
|
context '#valid?' do
|
27
29
|
it 'is invalid if attribute is required but value is blank' do
|
28
30
|
attribute = described_class.new(name: 'userName', type: 'string', required: true)
|
@@ -74,4 +76,5 @@ RSpec.describe Scimitar::Schema::Attribute do
|
|
74
76
|
expect(described_class.new(name: 'startDate', type: 'dateTime').valid?('gaga')).to be(false)
|
75
77
|
end
|
76
78
|
end
|
79
|
+
|
77
80
|
end
|
@@ -419,16 +419,6 @@ RSpec.describe Scimitar::Schema::User do
|
|
419
419
|
"name": "type",
|
420
420
|
"type": "string"
|
421
421
|
},
|
422
|
-
{
|
423
|
-
"multiValued": false,
|
424
|
-
"required": false,
|
425
|
-
"caseExact": false,
|
426
|
-
"mutability": "readWrite",
|
427
|
-
"uniqueness": "none",
|
428
|
-
"returned": "default",
|
429
|
-
"name": "primary",
|
430
|
-
"type": "boolean"
|
431
|
-
},
|
432
422
|
{
|
433
423
|
"multiValued": false,
|
434
424
|
"required": false,
|
@@ -1,27 +1,12 @@
|
|
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
|
-
# With UUID based IDs, testing life is made easier by ensuring that the
|
12
|
-
# creation order matches an ascending UUID sort order (which is what would
|
13
|
-
# happen if we were using integer primary keys).
|
14
|
-
#
|
15
|
-
lmt = Time.parse("2023-01-09 14:25:00 +1300")
|
16
|
-
ids = 3.times.map { SecureRandom.uuid }.sort()
|
17
|
-
|
18
|
-
@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)
|
19
|
-
@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)
|
20
|
-
@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)
|
21
|
-
|
22
|
-
@g1 = MockGroup.create!(display_name: 'Group 1')
|
23
|
-
@g2 = MockGroup.create!(display_name: 'Group 2')
|
24
|
-
@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')
|
25
10
|
end
|
26
11
|
|
27
12
|
# ===========================================================================
|
@@ -44,65 +29,26 @@ RSpec.describe Scimitar::ActiveRecordBackedResourcesController do
|
|
44
29
|
end # "context 'with no items' do"
|
45
30
|
|
46
31
|
context 'with items' do
|
47
|
-
|
48
|
-
|
49
|
-
get '/Users', params: { format: :scim }
|
50
|
-
|
51
|
-
expect(response.status).to eql(200)
|
52
|
-
result = JSON.parse(response.body)
|
53
|
-
|
54
|
-
expect(result['totalResults']).to eql(3)
|
55
|
-
expect(result['Resources'].size).to eql(3)
|
56
|
-
|
57
|
-
ids = result['Resources'].map { |resource| resource['id'] }
|
58
|
-
expect(ids).to match_array([@u1.primary_key.to_s, @u2.primary_key.to_s, @u3.primary_key.to_s])
|
59
|
-
|
60
|
-
usernames = result['Resources'].map { |resource| resource['userName'] }
|
61
|
-
expect(usernames).to match_array(['1', '2', '3'])
|
62
|
-
end
|
63
|
-
end # "context 'with a UUID, renamed primary key column' do"
|
64
|
-
|
65
|
-
context 'with an integer, conventionally named primary key column' do
|
66
|
-
it 'returns all items' do
|
67
|
-
get '/Groups', params: { format: :scim }
|
68
|
-
|
69
|
-
expect(response.status).to eql(200)
|
70
|
-
result = JSON.parse(response.body)
|
71
|
-
|
72
|
-
expect(result['totalResults']).to eql(3)
|
73
|
-
expect(result['Resources'].size).to eql(3)
|
74
|
-
|
75
|
-
ids = result['Resources'].map { |resource| resource['id'] }
|
76
|
-
expect(ids).to match_array([@g1.id.to_s, @g2.id.to_s, @g3.id.to_s])
|
77
|
-
|
78
|
-
usernames = result['Resources'].map { |resource| resource['displayName'] }
|
79
|
-
expect(usernames).to match_array(['Group 1', 'Group 2', 'Group 3'])
|
80
|
-
end
|
81
|
-
end # "context 'with an integer, conventionally named primary key column' do"
|
82
|
-
|
83
|
-
it 'applies a filter, with case-insensitive value comparison' do
|
84
|
-
get '/Users', params: {
|
85
|
-
format: :scim,
|
86
|
-
filter: 'name.givenName eq "FOO" and name.familyName pr and emails ne "home_1@test.com"'
|
87
|
-
}
|
32
|
+
it 'returns all items' do
|
33
|
+
get '/Users', params: { format: :scim }
|
88
34
|
|
89
35
|
expect(response.status).to eql(200)
|
90
36
|
result = JSON.parse(response.body)
|
91
37
|
|
92
|
-
expect(result['totalResults']).to eql(
|
93
|
-
expect(result['Resources'].size).to eql(
|
38
|
+
expect(result['totalResults']).to eql(3)
|
39
|
+
expect(result['Resources'].size).to eql(3)
|
94
40
|
|
95
41
|
ids = result['Resources'].map { |resource| resource['id'] }
|
96
|
-
expect(ids).to match_array([@u2.
|
42
|
+
expect(ids).to match_array([@u1.id.to_s, @u2.id.to_s, @u3.id.to_s])
|
97
43
|
|
98
44
|
usernames = result['Resources'].map { |resource| resource['userName'] }
|
99
|
-
expect(usernames).to match_array(['2'])
|
45
|
+
expect(usernames).to match_array(['1', '2', '3'])
|
100
46
|
end
|
101
47
|
|
102
|
-
it 'applies a filter, with case-insensitive
|
48
|
+
it 'applies a filter, with case-insensitive value comparison' do
|
103
49
|
get '/Users', params: {
|
104
50
|
format: :scim,
|
105
|
-
filter: 'name.
|
51
|
+
filter: 'name.givenName eq "Foo" and name.familyName pr and emails ne "home_1@TEST.COM"'
|
106
52
|
}
|
107
53
|
|
108
54
|
expect(response.status).to eql(200)
|
@@ -112,74 +58,12 @@ RSpec.describe Scimitar::ActiveRecordBackedResourcesController do
|
|
112
58
|
expect(result['Resources'].size).to eql(1)
|
113
59
|
|
114
60
|
ids = result['Resources'].map { |resource| resource['id'] }
|
115
|
-
expect(ids).to match_array([@u2.
|
61
|
+
expect(ids).to match_array([@u2.id.to_s])
|
116
62
|
|
117
63
|
usernames = result['Resources'].map { |resource| resource['userName'] }
|
118
64
|
expect(usernames).to match_array(['2'])
|
119
65
|
end
|
120
66
|
|
121
|
-
# Strange attribute capitalisation in tests here builds on test coverage
|
122
|
-
# for now-fixed GitHub issue #37.
|
123
|
-
#
|
124
|
-
context '"meta" / IDs (GitHub issue #36)' do
|
125
|
-
it 'applies a filter on primary keys, using direct comparison (rather than e.g. case-insensitive operators)' do
|
126
|
-
get '/Users', params: {
|
127
|
-
format: :scim,
|
128
|
-
filter: "id eq \"#{@u3.primary_key}\""
|
129
|
-
}
|
130
|
-
|
131
|
-
expect(response.status).to eql(200)
|
132
|
-
result = JSON.parse(response.body)
|
133
|
-
|
134
|
-
expect(result['totalResults']).to eql(1)
|
135
|
-
expect(result['Resources'].size).to eql(1)
|
136
|
-
|
137
|
-
ids = result['Resources'].map { |resource| resource['id'] }
|
138
|
-
expect(ids).to match_array([@u3.primary_key.to_s])
|
139
|
-
|
140
|
-
usernames = result['Resources'].map { |resource| resource['userName'] }
|
141
|
-
expect(usernames).to match_array(['3'])
|
142
|
-
end
|
143
|
-
|
144
|
-
it 'applies a filter on external IDs, using direct comparison' do
|
145
|
-
get '/Users', params: {
|
146
|
-
format: :scim,
|
147
|
-
filter: "externalID eq \"#{@u2.scim_uid}\""
|
148
|
-
}
|
149
|
-
|
150
|
-
expect(response.status).to eql(200)
|
151
|
-
result = JSON.parse(response.body)
|
152
|
-
|
153
|
-
expect(result['totalResults']).to eql(1)
|
154
|
-
expect(result['Resources'].size).to eql(1)
|
155
|
-
|
156
|
-
ids = result['Resources'].map { |resource| resource['id'] }
|
157
|
-
expect(ids).to match_array([@u2.primary_key.to_s])
|
158
|
-
|
159
|
-
usernames = result['Resources'].map { |resource| resource['userName'] }
|
160
|
-
expect(usernames).to match_array(['2'])
|
161
|
-
end
|
162
|
-
|
163
|
-
it 'applies a filter on "meta" entries, using direct comparison' do
|
164
|
-
get '/Users', params: {
|
165
|
-
format: :scim,
|
166
|
-
filter: "Meta.LastModified eq \"#{@u3.updated_at}\""
|
167
|
-
}
|
168
|
-
|
169
|
-
expect(response.status).to eql(200)
|
170
|
-
result = JSON.parse(response.body)
|
171
|
-
|
172
|
-
expect(result['totalResults']).to eql(1)
|
173
|
-
expect(result['Resources'].size).to eql(1)
|
174
|
-
|
175
|
-
ids = result['Resources'].map { |resource| resource['id'] }
|
176
|
-
expect(ids).to match_array([@u3.primary_key.to_s])
|
177
|
-
|
178
|
-
usernames = result['Resources'].map { |resource| resource['userName'] }
|
179
|
-
expect(usernames).to match_array(['3'])
|
180
|
-
end
|
181
|
-
end # "context '"meta" / IDs (GitHub issue #36)' do"
|
182
|
-
|
183
67
|
it 'obeys a page size' do
|
184
68
|
get '/Users', params: {
|
185
69
|
format: :scim,
|
@@ -193,7 +77,7 @@ RSpec.describe Scimitar::ActiveRecordBackedResourcesController do
|
|
193
77
|
expect(result['Resources'].size).to eql(2)
|
194
78
|
|
195
79
|
ids = result['Resources'].map { |resource| resource['id'] }
|
196
|
-
expect(ids).to match_array([@u1.
|
80
|
+
expect(ids).to match_array([@u1.id.to_s, @u2.id.to_s])
|
197
81
|
|
198
82
|
usernames = result['Resources'].map { |resource| resource['userName'] }
|
199
83
|
expect(usernames).to match_array(['1', '2'])
|
@@ -212,7 +96,7 @@ RSpec.describe Scimitar::ActiveRecordBackedResourcesController do
|
|
212
96
|
expect(result['Resources'].size).to eql(2)
|
213
97
|
|
214
98
|
ids = result['Resources'].map { |resource| resource['id'] }
|
215
|
-
expect(ids).to match_array([@u2.
|
99
|
+
expect(ids).to match_array([@u2.id.to_s, @u3.id.to_s])
|
216
100
|
|
217
101
|
usernames = result['Resources'].map { |resource| resource['userName'] }
|
218
102
|
expect(usernames).to match_array(['2', '3'])
|
@@ -236,34 +120,18 @@ RSpec.describe Scimitar::ActiveRecordBackedResourcesController do
|
|
236
120
|
# ===========================================================================
|
237
121
|
|
238
122
|
context '#show' do
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
get "/Users/#{@u2.primary_key}", params: { format: :scim }
|
243
|
-
|
244
|
-
expect(response.status).to eql(200)
|
245
|
-
result = JSON.parse(response.body)
|
246
|
-
|
247
|
-
expect(result['id']).to eql(@u2.primary_key.to_s)
|
248
|
-
expect(result['userName']).to eql('2')
|
249
|
-
expect(result['name']['familyName']).to eql('Bar')
|
250
|
-
expect(result['meta']['resourceType']).to eql('User')
|
251
|
-
end
|
252
|
-
end # "context 'with a UUID, renamed primary key column' do"
|
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 }
|
253
126
|
|
254
|
-
|
255
|
-
|
256
|
-
expect_any_instance_of(MockGroupsController).to receive(:show).once.and_call_original
|
257
|
-
get "/Groups/#{@g2.id}", params: { format: :scim }
|
258
|
-
|
259
|
-
expect(response.status).to eql(200)
|
260
|
-
result = JSON.parse(response.body)
|
127
|
+
expect(response.status).to eql(200)
|
128
|
+
result = JSON.parse(response.body)
|
261
129
|
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
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
|
267
135
|
|
268
136
|
it 'renders 404' do
|
269
137
|
get '/Users/xyz', params: { format: :scim }
|
@@ -296,7 +164,7 @@ RSpec.describe Scimitar::ActiveRecordBackedResourcesController do
|
|
296
164
|
expect(response.status).to eql(201)
|
297
165
|
result = JSON.parse(response.body)
|
298
166
|
|
299
|
-
expect(result['id']).to eql(new_mock.
|
167
|
+
expect(result['id']).to eql(new_mock.id.to_s)
|
300
168
|
expect(result['meta']['resourceType']).to eql('User')
|
301
169
|
expect(new_mock.username).to eql('4')
|
302
170
|
end
|
@@ -312,7 +180,6 @@ RSpec.describe Scimitar::ActiveRecordBackedResourcesController do
|
|
312
180
|
givenName: 'Given',
|
313
181
|
familyName: 'Family'
|
314
182
|
},
|
315
|
-
meta: { resourceType: 'User' },
|
316
183
|
emails: [
|
317
184
|
{
|
318
185
|
type: 'work',
|
@@ -337,7 +204,7 @@ RSpec.describe Scimitar::ActiveRecordBackedResourcesController do
|
|
337
204
|
expect(response.status).to eql(201)
|
338
205
|
result = JSON.parse(response.body)
|
339
206
|
|
340
|
-
expect(result['id']).to eql(new_mock.
|
207
|
+
expect(result['id']).to eql(new_mock.id.to_s)
|
341
208
|
expect(result['meta']['resourceType']).to eql('User')
|
342
209
|
expect(new_mock.username).to eql('4')
|
343
210
|
expect(new_mock.first_name).to eql('Given')
|
@@ -411,13 +278,13 @@ RSpec.describe Scimitar::ActiveRecordBackedResourcesController do
|
|
411
278
|
|
412
279
|
expect_any_instance_of(MockUsersController).to receive(:replace).once.and_call_original
|
413
280
|
expect {
|
414
|
-
put "/Users/#{@u2.
|
281
|
+
put "/Users/#{@u2.id}", params: attributes.merge(format: :scim)
|
415
282
|
}.to_not change { MockUser.count }
|
416
283
|
|
417
284
|
expect(response.status).to eql(200)
|
418
285
|
result = JSON.parse(response.body)
|
419
286
|
|
420
|
-
expect(result['id']).to eql(@u2.
|
287
|
+
expect(result['id']).to eql(@u2.id.to_s)
|
421
288
|
expect(result['meta']['resourceType']).to eql('User')
|
422
289
|
|
423
290
|
@u2.reload
|
@@ -439,7 +306,7 @@ RSpec.describe Scimitar::ActiveRecordBackedResourcesController do
|
|
439
306
|
|
440
307
|
it 'notes schema validation failures' do
|
441
308
|
expect {
|
442
|
-
put "/Users/#{@u2.
|
309
|
+
put "/Users/#{@u2.id}", params: {
|
443
310
|
format: :scim
|
444
311
|
# userName parameter is required by schema, but missing
|
445
312
|
}
|
@@ -518,13 +385,13 @@ RSpec.describe Scimitar::ActiveRecordBackedResourcesController do
|
|
518
385
|
|
519
386
|
expect_any_instance_of(MockUsersController).to receive(:update).once.and_call_original
|
520
387
|
expect {
|
521
|
-
patch "/Users/#{@u2.
|
388
|
+
patch "/Users/#{@u2.id}", params: payload.merge(format: :scim)
|
522
389
|
}.to_not change { MockUser.count }
|
523
390
|
|
524
391
|
expect(response.status).to eql(200)
|
525
392
|
result = JSON.parse(response.body)
|
526
393
|
|
527
|
-
expect(result['id']).to eql(@u2.
|
394
|
+
expect(result['id']).to eql(@u2.id.to_s)
|
528
395
|
expect(result['meta']['resourceType']).to eql('User')
|
529
396
|
|
530
397
|
@u2.reload
|
@@ -555,13 +422,13 @@ RSpec.describe Scimitar::ActiveRecordBackedResourcesController do
|
|
555
422
|
|
556
423
|
expect_any_instance_of(MockUsersController).to receive(:update).once.and_call_original
|
557
424
|
expect {
|
558
|
-
patch "/Users/#{@u2.
|
425
|
+
patch "/Users/#{@u2.id}", params: payload.merge(format: :scim)
|
559
426
|
}.to_not change { MockUser.count }
|
560
427
|
|
561
428
|
expect(response.status).to eql(200)
|
562
429
|
result = JSON.parse(response.body)
|
563
430
|
|
564
|
-
expect(result['id']).to eql(@u2.
|
431
|
+
expect(result['id']).to eql(@u2.id.to_s)
|
565
432
|
expect(result['meta']['resourceType']).to eql('User')
|
566
433
|
|
567
434
|
@u2.reload
|
@@ -587,13 +454,13 @@ RSpec.describe Scimitar::ActiveRecordBackedResourcesController do
|
|
587
454
|
|
588
455
|
expect_any_instance_of(MockUsersController).to receive(:update).once.and_call_original
|
589
456
|
expect {
|
590
|
-
patch "/Users/#{@u2.
|
457
|
+
patch "/Users/#{@u2.id}", params: payload.merge(format: :scim)
|
591
458
|
}.to_not change { MockUser.count }
|
592
459
|
|
593
460
|
expect(response.status).to eql(200)
|
594
461
|
result = JSON.parse(response.body)
|
595
462
|
|
596
|
-
expect(result['id']).to eql(@u2.
|
463
|
+
expect(result['id']).to eql(@u2.id.to_s)
|
597
464
|
expect(result['meta']['resourceType']).to eql('User')
|
598
465
|
|
599
466
|
@u2.reload
|
@@ -619,13 +486,13 @@ RSpec.describe Scimitar::ActiveRecordBackedResourcesController do
|
|
619
486
|
|
620
487
|
expect_any_instance_of(MockUsersController).to receive(:update).once.and_call_original
|
621
488
|
expect {
|
622
|
-
patch "/Users/#{@u2.
|
489
|
+
patch "/Users/#{@u2.id}", params: payload.merge(format: :scim)
|
623
490
|
}.to_not change { MockUser.count }
|
624
491
|
|
625
492
|
expect(response.status).to eql(200)
|
626
493
|
result = JSON.parse(response.body)
|
627
494
|
|
628
|
-
expect(result['id']).to eql(@u2.
|
495
|
+
expect(result['id']).to eql(@u2.id.to_s)
|
629
496
|
expect(result['meta']['resourceType']).to eql('User')
|
630
497
|
|
631
498
|
@u2.reload
|
@@ -649,7 +516,7 @@ RSpec.describe Scimitar::ActiveRecordBackedResourcesController do
|
|
649
516
|
|
650
517
|
it 'notes Rails validation failures' do
|
651
518
|
expect {
|
652
|
-
patch "/Users/#{@u2.
|
519
|
+
patch "/Users/#{@u2.id}", params: {
|
653
520
|
format: :scim,
|
654
521
|
Operations: [
|
655
522
|
{
|
@@ -693,142 +560,6 @@ RSpec.describe Scimitar::ActiveRecordBackedResourcesController do
|
|
693
560
|
result = JSON.parse(response.body)
|
694
561
|
expect(result['status']).to eql('404')
|
695
562
|
end
|
696
|
-
|
697
|
-
context 'when removing users from groups' do
|
698
|
-
before :each do
|
699
|
-
@g1.mock_users << @u1
|
700
|
-
@g1.mock_users << @u2
|
701
|
-
@g1.mock_users << @u3
|
702
|
-
|
703
|
-
# (Self-check) Verify group representation
|
704
|
-
#
|
705
|
-
get "/Groups/#{@g1.id}", params: { format: :scim }
|
706
|
-
|
707
|
-
expect(response.status).to eql(200)
|
708
|
-
result = JSON.parse(response.body)
|
709
|
-
|
710
|
-
expect(result['members'].map { |m| m['value'] }.sort()).to eql(MockUser.pluck(:primary_key).sort())
|
711
|
-
end
|
712
|
-
|
713
|
-
it 'can remove all users' do
|
714
|
-
expect {
|
715
|
-
expect {
|
716
|
-
patch "/Groups/#{@g1.id}", params: {
|
717
|
-
format: :scim,
|
718
|
-
Operations: [
|
719
|
-
{
|
720
|
-
op: 'remove',
|
721
|
-
path: 'members'
|
722
|
-
}
|
723
|
-
]
|
724
|
-
}
|
725
|
-
}.to_not change { MockUser.count }
|
726
|
-
}.to_not change { MockGroup.count }
|
727
|
-
|
728
|
-
get "/Groups/#{@g1.id}", params: { format: :scim }
|
729
|
-
|
730
|
-
expect(response.status).to eql(200)
|
731
|
-
result = JSON.parse(response.body)
|
732
|
-
|
733
|
-
expect(result['members']).to be_empty
|
734
|
-
expect(@g1.reload().mock_users).to be_empty
|
735
|
-
end
|
736
|
-
|
737
|
-
# Define via 'let':
|
738
|
-
#
|
739
|
-
# * Hash 'payload', to send via 'patch'
|
740
|
-
# * MockUser 'removed_user', which is the user that should be removed
|
741
|
-
#
|
742
|
-
shared_examples 'a user remover' do
|
743
|
-
it 'which removes the identified user' do
|
744
|
-
expect {
|
745
|
-
expect {
|
746
|
-
patch "/Groups/#{@g1.id}", params: payload()
|
747
|
-
}.to_not change { MockUser.count }
|
748
|
-
}.to_not change { MockGroup.count }
|
749
|
-
|
750
|
-
expected_remaining_user_ids = MockUser
|
751
|
-
.where.not(primary_key: removed_user().id)
|
752
|
-
.pluck(:primary_key)
|
753
|
-
.sort()
|
754
|
-
|
755
|
-
get "/Groups/#{@g1.id}", params: { format: :scim }
|
756
|
-
|
757
|
-
expect(response.status).to eql(200)
|
758
|
-
result = JSON.parse(response.body)
|
759
|
-
|
760
|
-
expect(result['members'].map { |m| m['value'] }.sort()).to eql(expected_remaining_user_ids)
|
761
|
-
expect(@g1.reload().mock_users.map(&:primary_key).sort()).to eql(expected_remaining_user_ids)
|
762
|
-
end
|
763
|
-
end
|
764
|
-
|
765
|
-
# https://tools.ietf.org/html/rfc7644#section-3.5.2.2
|
766
|
-
#
|
767
|
-
context 'and using an RFC-compliant payload' do
|
768
|
-
let(:removed_user) { @u2 }
|
769
|
-
let(:payload) do
|
770
|
-
{
|
771
|
-
format: :scim,
|
772
|
-
Operations: [
|
773
|
-
{
|
774
|
-
op: 'remove',
|
775
|
-
path: "members[value eq \"#{removed_user().primary_key}\"]",
|
776
|
-
}
|
777
|
-
]
|
778
|
-
}
|
779
|
-
end
|
780
|
-
|
781
|
-
it_behaves_like 'a user remover'
|
782
|
-
end # context 'and using an RFC-compliant payload' do
|
783
|
-
|
784
|
-
# https://learn.microsoft.com/en-us/azure/active-directory/app-provisioning/use-scim-to-provision-users-and-groups#update-group-remove-members
|
785
|
-
#
|
786
|
-
context 'and using a Microsoft variant payload' do
|
787
|
-
let(:removed_user) { @u2 }
|
788
|
-
let(:payload) do
|
789
|
-
{
|
790
|
-
format: :scim,
|
791
|
-
Operations: [
|
792
|
-
{
|
793
|
-
op: 'remove',
|
794
|
-
path: 'members',
|
795
|
-
value: [{
|
796
|
-
'$ref' => nil,
|
797
|
-
'value' => removed_user().primary_key
|
798
|
-
}]
|
799
|
-
}
|
800
|
-
]
|
801
|
-
}
|
802
|
-
end
|
803
|
-
|
804
|
-
it_behaves_like 'a user remover'
|
805
|
-
end # context 'and using a Microsoft variant payload' do
|
806
|
-
|
807
|
-
# https://help.salesforce.com/s/articleView?id=sf.identity_scim_manage_groups.htm&type=5
|
808
|
-
#
|
809
|
-
context 'and using a Salesforce variant payload' do
|
810
|
-
let(:removed_user) { @u2 }
|
811
|
-
let(:payload) do
|
812
|
-
{
|
813
|
-
format: :scim,
|
814
|
-
Operations: [
|
815
|
-
{
|
816
|
-
op: 'remove',
|
817
|
-
path: 'members',
|
818
|
-
value: {
|
819
|
-
'members' => [{
|
820
|
-
'$ref' => nil,
|
821
|
-
'value' => removed_user().primary_key
|
822
|
-
}]
|
823
|
-
}
|
824
|
-
}
|
825
|
-
]
|
826
|
-
}
|
827
|
-
end
|
828
|
-
|
829
|
-
it_behaves_like 'a user remover'
|
830
|
-
end # context 'and using a Salesforce variant payload' do
|
831
|
-
end # "context 'when removing users from groups' do"
|
832
563
|
end # "context '#update' do"
|
833
564
|
|
834
565
|
# ===========================================================================
|
@@ -838,7 +569,7 @@ RSpec.describe Scimitar::ActiveRecordBackedResourcesController do
|
|
838
569
|
expect_any_instance_of(MockUsersController).to receive(:destroy).once.and_call_original
|
839
570
|
expect_any_instance_of(MockUser).to receive(:destroy!).once.and_call_original
|
840
571
|
expect {
|
841
|
-
delete "/Users/#{@u2.
|
572
|
+
delete "/Users/#{@u2.id}", params: { format: :scim }
|
842
573
|
}.to change { MockUser.count }.by(-1)
|
843
574
|
|
844
575
|
expect(response.status).to eql(204)
|
@@ -850,7 +581,7 @@ RSpec.describe Scimitar::ActiveRecordBackedResourcesController do
|
|
850
581
|
expect_any_instance_of(MockUser).to_not receive(:destroy!)
|
851
582
|
|
852
583
|
expect {
|
853
|
-
delete "/CustomDestroyUsers/#{@u2.
|
584
|
+
delete "/CustomDestroyUsers/#{@u2.id}", params: { format: :scim }
|
854
585
|
}.to_not change { MockUser.count }
|
855
586
|
|
856
587
|
expect(response.status).to eql(204)
|
@@ -11,14 +11,11 @@ RSpec.describe Scimitar::ApplicationController do
|
|
11
11
|
end
|
12
12
|
|
13
13
|
context 'format handling' do
|
14
|
-
it 'renders "
|
14
|
+
it 'renders "not acceptable" if the request does not use SCIM type' do
|
15
15
|
get '/CustomRequestVerifiers', params: { format: :html }
|
16
16
|
|
17
|
-
expect(response).to have_http_status(:
|
18
|
-
|
19
|
-
expect(parsed_body['request']['is_scim' ]).to eql(true)
|
20
|
-
expect(parsed_body['request']['format' ]).to eql('application/scim+json')
|
21
|
-
expect(parsed_body['request']['content_type']).to eql('application/scim+json')
|
17
|
+
expect(response).to have_http_status(:not_acceptable)
|
18
|
+
expect(JSON.parse(response.body)['detail']).to eql('Only application/scim+json type is accepted.')
|
22
19
|
end
|
23
20
|
|
24
21
|
it 'renders 400 if given bad JSON' do
|
@@ -26,7 +23,6 @@ RSpec.describe Scimitar::ApplicationController do
|
|
26
23
|
|
27
24
|
expect(response).to have_http_status(:bad_request)
|
28
25
|
expect(JSON.parse(response.body)['detail']).to start_with('Invalid JSON - ')
|
29
|
-
expect(JSON.parse(response.body)['detail']).to include("'not-json-12345'")
|
30
26
|
end
|
31
27
|
|
32
28
|
it 'translates Content-Type to Rails request format' do
|
@@ -39,16 +35,6 @@ RSpec.describe Scimitar::ApplicationController do
|
|
39
35
|
expect(parsed_body['request']['content_type']).to eql('application/scim+json')
|
40
36
|
end
|
41
37
|
|
42
|
-
it 'translates Content-Type with charset to Rails request format' do
|
43
|
-
get '/CustomRequestVerifiers', headers: { 'CONTENT_TYPE' => 'application/scim+json; charset=utf-8' }
|
44
|
-
|
45
|
-
expect(response).to have_http_status(:ok)
|
46
|
-
parsed_body = JSON.parse(response.body)
|
47
|
-
expect(parsed_body['request']['is_scim' ]).to eql(true)
|
48
|
-
expect(parsed_body['request']['format' ]).to eql('application/scim+json')
|
49
|
-
expect(parsed_body['request']['content_type']).to eql('application/scim+json; charset=utf-8')
|
50
|
-
end
|
51
|
-
|
52
38
|
it 'translates Rails request format to header' do
|
53
39
|
get '/CustomRequestVerifiers', params: { format: :scim }
|
54
40
|
|