scimaenaga 0.6.1 → 0.9.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/README.md +15 -0
- data/app/controllers/concerns/scim_rails/exception_handler.rb +74 -22
- data/app/controllers/scim_rails/scim_groups_controller.rb +14 -3
- data/app/controllers/scim_rails/scim_schemas_controller.rb +42 -0
- data/app/controllers/scim_rails/scim_users_controller.rb +22 -4
- data/app/libraries/scim_patch.rb +15 -9
- data/app/libraries/scim_patch_operation.rb +50 -71
- data/app/libraries/scim_patch_operation_converter.rb +90 -0
- data/app/libraries/scim_patch_operation_group.rb +100 -0
- data/app/libraries/scim_patch_operation_user.rb +53 -0
- data/config/routes.rb +14 -11
- data/lib/generators/scim_rails/templates/initializer.rb +106 -0
- data/lib/scim_rails/config.rb +8 -5
- data/lib/scim_rails/version.rb +1 -1
- data/spec/controllers/scim_rails/scim_groups_controller_spec.rb +25 -7
- data/spec/controllers/scim_rails/scim_schemas_controller_spec.rb +238 -0
- data/spec/controllers/scim_rails/scim_schemas_request_spec.rb +39 -0
- data/spec/controllers/scim_rails/scim_users_controller_spec.rb +361 -220
- data/spec/dummy/app/models/user.rb +9 -0
- data/spec/dummy/config/initializers/scim_rails_config.rb +27 -24
- data/spec/dummy/db/migrate/20220131090107_add_deletable_to_users.rb +5 -0
- data/spec/dummy/db/schema.rb +2 -1
- data/spec/dummy/db/seeds.rb +10 -1
- data/spec/factories/user.rb +2 -0
- data/spec/libraries/scim_patch_operation_group_spec.rb +165 -0
- data/spec/libraries/scim_patch_operation_user_spec.rb +101 -0
- data/spec/libraries/scim_patch_spec.rb +135 -53
- metadata +80 -80
- data/spec/dummy/db/development.sqlite3 +0 -0
- data/spec/dummy/db/test.sqlite3 +0 -0
- data/spec/dummy/log/development.log +0 -0
- data/spec/dummy/log/test.log +0 -377
- data/spec/dummy/put_group.http +0 -5
- data/spec/dummy/tmp/restart.txt +0 -0
- data/spec/libraries/scim_patch_operation_spec.rb +0 -96
@@ -0,0 +1,100 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class ScimPatchOperationGroup < ScimPatchOperation
|
4
|
+
|
5
|
+
def save(model)
|
6
|
+
if @path_scim[:attribute] == 'members'
|
7
|
+
save_members(model)
|
8
|
+
return
|
9
|
+
end
|
10
|
+
|
11
|
+
case @op
|
12
|
+
when 'add', 'replace'
|
13
|
+
model.attributes = { @path_sp => @value }
|
14
|
+
when 'remove'
|
15
|
+
model.attributes = { @path_sp => nil }
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def save_members(model)
|
22
|
+
current_member_ids = model.public_send(member_relation_attribute).map(&:to_s)
|
23
|
+
|
24
|
+
case @op
|
25
|
+
when 'add'
|
26
|
+
member_ids = add_member_ids(current_member_ids)
|
27
|
+
when 'replace'
|
28
|
+
member_ids = replace_member_ids
|
29
|
+
when 'remove'
|
30
|
+
member_ids = remove_member_ids(current_member_ids)
|
31
|
+
end
|
32
|
+
|
33
|
+
model.public_send("#{member_relation_attribute}=", member_ids.uniq)
|
34
|
+
end
|
35
|
+
|
36
|
+
def add_member_ids(current_member_ids)
|
37
|
+
current_member_ids.concat(member_ids_from_value)
|
38
|
+
end
|
39
|
+
|
40
|
+
def replace_member_ids
|
41
|
+
member_ids_from_value
|
42
|
+
end
|
43
|
+
|
44
|
+
def remove_member_ids(current_member_ids)
|
45
|
+
removed_member_ids = if member_ids_from_value.present?
|
46
|
+
member_ids_from_value
|
47
|
+
else
|
48
|
+
[member_id_from_filter]
|
49
|
+
end
|
50
|
+
current_member_ids - removed_member_ids
|
51
|
+
end
|
52
|
+
|
53
|
+
def member_ids_from_value
|
54
|
+
@member_ids_from_value ||= @value&.map do |v|
|
55
|
+
v['value'].to_s
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def member_id_from_filter
|
60
|
+
@path_scim.dig(:filter, :parameter)
|
61
|
+
end
|
62
|
+
|
63
|
+
def member_relation_attribute
|
64
|
+
ScimRails.config.group_member_relation_attribute
|
65
|
+
end
|
66
|
+
|
67
|
+
def validate(_op, _path, _value)
|
68
|
+
return
|
69
|
+
end
|
70
|
+
|
71
|
+
def path_scim_to_path_sp(path_scim)
|
72
|
+
# path_scim example1:
|
73
|
+
# {
|
74
|
+
# attribute: 'members',
|
75
|
+
# filter: {
|
76
|
+
# attribute: 'value',
|
77
|
+
# operator: 'eq',
|
78
|
+
# parameter: 'XXXX'
|
79
|
+
# },
|
80
|
+
# rest_path: []
|
81
|
+
# }
|
82
|
+
|
83
|
+
# path_scim example2:
|
84
|
+
# {
|
85
|
+
# attribute: 'displayName',
|
86
|
+
# filter: nil,
|
87
|
+
# rest_path: []
|
88
|
+
# }
|
89
|
+
if path_scim[:attribute] == 'members'
|
90
|
+
return ScimRails.config.group_member_relation_attribute
|
91
|
+
end
|
92
|
+
|
93
|
+
dig_keys = [path_scim[:attribute].to_sym]
|
94
|
+
dig_keys.concat(path_scim[:rest_path].map(&:to_sym))
|
95
|
+
|
96
|
+
# *dig_keys example: displayName
|
97
|
+
ScimRails.config.mutable_group_attributes_schema.dig(*dig_keys)
|
98
|
+
end
|
99
|
+
|
100
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class ScimPatchOperationUser < ScimPatchOperation
|
4
|
+
|
5
|
+
def save(model)
|
6
|
+
case @op
|
7
|
+
when 'add', 'replace'
|
8
|
+
model.attributes = { @path_sp => @value }
|
9
|
+
when 'remove'
|
10
|
+
model.attributes = { @path_sp => nil }
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
private
|
15
|
+
|
16
|
+
def validate(_op, _path, value)
|
17
|
+
if value.instance_of? Array
|
18
|
+
raise ScimRails::ExceptionHandler::UnsupportedPatchRequest
|
19
|
+
end
|
20
|
+
|
21
|
+
return
|
22
|
+
end
|
23
|
+
|
24
|
+
def path_scim_to_path_sp(path_scim)
|
25
|
+
# path_scim example1:
|
26
|
+
# {
|
27
|
+
# attribute: 'emails',
|
28
|
+
# filter: {
|
29
|
+
# attribute: 'type',
|
30
|
+
# operator: 'eq',
|
31
|
+
# parameter: 'work'
|
32
|
+
# },
|
33
|
+
# rest_path: ['value']
|
34
|
+
# }
|
35
|
+
#
|
36
|
+
# path_scim example2:
|
37
|
+
# {
|
38
|
+
# attribute: 'name',
|
39
|
+
# filter: nil,
|
40
|
+
# rest_path: ['givenName']
|
41
|
+
# }
|
42
|
+
dig_keys = [path_scim[:attribute].to_sym]
|
43
|
+
|
44
|
+
# Library ignores filter conditions ([type eq "work"])
|
45
|
+
dig_keys << 0 if path_scim[:attribute] == 'emails'
|
46
|
+
|
47
|
+
dig_keys.concat(path_scim[:rest_path].map(&:to_sym))
|
48
|
+
|
49
|
+
# *dig_keys example: emails, 0, value
|
50
|
+
ScimRails.config.mutable_user_attributes_schema.dig(*dig_keys)
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
data/config/routes.rb
CHANGED
@@ -1,13 +1,16 @@
|
|
1
1
|
ScimRails::Engine.routes.draw do
|
2
|
-
get 'scim/v2/Users',
|
3
|
-
post 'scim/v2/Users',
|
4
|
-
get 'scim/v2/Users/:id',
|
5
|
-
put 'scim/v2/Users/:id',
|
6
|
-
patch 'scim/v2/Users/:id',
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
2
|
+
get 'scim/v2/Users', action: :index, controller: 'scim_users'
|
3
|
+
post 'scim/v2/Users', action: :create, controller: 'scim_users'
|
4
|
+
get 'scim/v2/Users/:id', action: :show, controller: 'scim_users'
|
5
|
+
put 'scim/v2/Users/:id', action: :put_update, controller: 'scim_users'
|
6
|
+
patch 'scim/v2/Users/:id', action: :patch_update, controller: 'scim_users'
|
7
|
+
delete 'scim/v2/Users/:id', action: :destroy, controller: 'scim_users'
|
8
|
+
get 'scim/v2/Groups', action: :index, controller: 'scim_groups'
|
9
|
+
post 'scim/v2/Groups', action: :create, controller: 'scim_groups'
|
10
|
+
get 'scim/v2/Groups/:id', action: :show, controller: 'scim_groups'
|
11
|
+
put 'scim/v2/Groups/:id', action: :put_update, controller: 'scim_groups'
|
12
|
+
patch 'scim/v2/Groups/:id', action: :patch_update, controller: 'scim_groups'
|
13
|
+
delete 'scim/v2/Groups/:id', action: :destroy, controller: 'scim_groups'
|
14
|
+
get 'scim/v2/Schemas', action: :index, controller: 'scim_schemas'
|
15
|
+
get 'scim/v2/Schemas/:id', action: :show, controller: 'scim_schemas'
|
13
16
|
end
|
@@ -157,4 +157,110 @@ ScimRails.configure do |config|
|
|
157
157
|
# Set group_destroy_method to a method on the Group model
|
158
158
|
# to be called on a destroy request
|
159
159
|
# config.group_destroy_method = :destroy!
|
160
|
+
|
161
|
+
# /Schemas settings.
|
162
|
+
# These settings are not used in /Users and /Groups for now.
|
163
|
+
# Configure this only when you need Schemas endpoint.
|
164
|
+
# Schemas endpoint returns the configured values as-is.
|
165
|
+
config.schemas = [
|
166
|
+
# Define User schemas
|
167
|
+
{
|
168
|
+
# Normally you don't have to change schemas/id/name/description
|
169
|
+
schemas: ['urn:ietf:params:scim:schemas:core:2.0:Schema'],
|
170
|
+
id: 'urn:ietf:params:scim:schemas:core:2.0:User',
|
171
|
+
name: 'User',
|
172
|
+
description: 'User Account',
|
173
|
+
|
174
|
+
# Configure 'attributes' as it corresponds with other configurations and your model
|
175
|
+
attributes: [
|
176
|
+
{
|
177
|
+
# Name of SCIM attribute. It must be configured in "user_schema"
|
178
|
+
name: 'userName',
|
179
|
+
|
180
|
+
# "type" must be string/boolan/decimal/integer/dateTime/reference
|
181
|
+
# "complex" value is not supported now
|
182
|
+
type: 'string',
|
183
|
+
|
184
|
+
# Multi value attribute is not supported, must be false
|
185
|
+
multiValued: false,
|
186
|
+
|
187
|
+
description: 'Unique identifier for the User. REQUIRED.',
|
188
|
+
|
189
|
+
# Specify true when you require this attribute
|
190
|
+
required: true,
|
191
|
+
|
192
|
+
# In this Library, String value is always handled as case exact
|
193
|
+
caseExact: true,
|
194
|
+
|
195
|
+
# "mutability" must be readOnly/readWrite/writeOnly
|
196
|
+
# "immutable" is not supported.
|
197
|
+
# readOnly: attribute is defined in queryable_user_attributes but not in mutable_user_attributes and user_schema
|
198
|
+
# readWrite: attribute is defined in queryable_user_attributes, mutable_user_attributes and user_schema
|
199
|
+
# writeOnly: attribute is defined in mutable_user_attributes, and user_schema but not in queryable_user_attributes
|
200
|
+
mutability: 'readWrite',
|
201
|
+
|
202
|
+
# "returned" must be always/never. default and request are not supported
|
203
|
+
# always: attribute is defined in user_schema
|
204
|
+
# never: attribute is not defined in user_schema
|
205
|
+
returned: 'always',
|
206
|
+
|
207
|
+
# "uniqueness" must be none/server/global. It's dependent on your service
|
208
|
+
uniqueness: 'server',
|
209
|
+
}
|
210
|
+
],
|
211
|
+
meta: {
|
212
|
+
resourceType: 'Schema',
|
213
|
+
location:
|
214
|
+
'/v2/Schemas/urn:ietf:params:scim:schemas:core:2.0:User',
|
215
|
+
},
|
216
|
+
},
|
217
|
+
# define Group schemas
|
218
|
+
{
|
219
|
+
schemas: ['urn:ietf:params:scim:schemas:core:2.0:Schema'],
|
220
|
+
id: 'urn:ietf:params:scim:schemas:core:2.0:Group',
|
221
|
+
name: 'Group',
|
222
|
+
description: 'Group',
|
223
|
+
attributes: [
|
224
|
+
{
|
225
|
+
# Same as the User attributes
|
226
|
+
name: 'displayName',
|
227
|
+
type: 'string',
|
228
|
+
multiValued: false,
|
229
|
+
description: 'A human-readable name for the Group. REQUIRED.',
|
230
|
+
required: true,
|
231
|
+
caseExact: true,
|
232
|
+
mutability: 'readWrite',
|
233
|
+
returned: 'always',
|
234
|
+
uniqueness: 'none',
|
235
|
+
},
|
236
|
+
{
|
237
|
+
name: 'members',
|
238
|
+
|
239
|
+
# Only "members" can be configured as a complex and multivalued attribute
|
240
|
+
type: 'complex',
|
241
|
+
multiValued: true,
|
242
|
+
|
243
|
+
description: 'A list of members of the Group.',
|
244
|
+
required: false,
|
245
|
+
subAttributes: [
|
246
|
+
{
|
247
|
+
name: 'value',
|
248
|
+
type: 'string',
|
249
|
+
multiValued: false,
|
250
|
+
description: 'Identifier of the member of this Group.',
|
251
|
+
required: false,
|
252
|
+
caseExact: true,
|
253
|
+
mutability: 'immutable',
|
254
|
+
returned: 'default',
|
255
|
+
uniqueness: 'none',
|
256
|
+
}
|
257
|
+
],
|
258
|
+
}
|
259
|
+
],
|
260
|
+
meta: {
|
261
|
+
resourceType: 'Schema',
|
262
|
+
location: '/v2/Schemas/urn:ietf:params:scim:schemas:core:2.0:Group',
|
263
|
+
},
|
264
|
+
}
|
265
|
+
]
|
160
266
|
end
|
data/lib/scim_rails/config.rb
CHANGED
@@ -13,7 +13,7 @@ module ScimRails
|
|
13
13
|
|
14
14
|
# Class containing configuration of ScimRails
|
15
15
|
class Config
|
16
|
-
ALGO_NONE =
|
16
|
+
ALGO_NONE = 'none'
|
17
17
|
|
18
18
|
attr_writer \
|
19
19
|
:basic_auth_model,
|
@@ -44,20 +44,23 @@ module ScimRails
|
|
44
44
|
:user_attributes,
|
45
45
|
:user_schema,
|
46
46
|
:group_schema,
|
47
|
-
:
|
47
|
+
:user_destroy_method,
|
48
|
+
:group_destroy_method,
|
49
|
+
:schemas
|
48
50
|
|
49
51
|
def initialize
|
50
|
-
@basic_auth_model =
|
52
|
+
@basic_auth_model = 'Company'
|
51
53
|
@scim_users_list_order = :id
|
52
|
-
@scim_users_model =
|
54
|
+
@scim_users_model = 'User'
|
53
55
|
@scim_groups_list_order = :id
|
54
|
-
@scim_groups_model =
|
56
|
+
@scim_groups_model = 'Group'
|
55
57
|
@signing_algorithm = ALGO_NONE
|
56
58
|
@user_schema = {}
|
57
59
|
@user_attributes = []
|
58
60
|
@user_abbreviated_schema = {}
|
59
61
|
@group_schema = {}
|
60
62
|
@group_abbreviated_schema = {}
|
63
|
+
@schemas = []
|
61
64
|
end
|
62
65
|
|
63
66
|
def mutable_user_attributes_schema
|
data/lib/scim_rails/version.rb
CHANGED
@@ -505,12 +505,6 @@ RSpec.describe ScimRails::ScimGroupsController, type: :controller do
|
|
505
505
|
end
|
506
506
|
|
507
507
|
context 'when Group destroy method is configured' do
|
508
|
-
before do
|
509
|
-
allow(ScimRails.config).to(
|
510
|
-
receive(:group_destroy_method).and_return(:destroy!)
|
511
|
-
)
|
512
|
-
end
|
513
|
-
|
514
508
|
it 'returns empty response' do
|
515
509
|
delete :destroy, params: { id: 1 }, as: :json
|
516
510
|
|
@@ -557,7 +551,31 @@ RSpec.describe ScimRails::ScimGroupsController, type: :controller do
|
|
557
551
|
delete :destroy, params: { id: 1 }, as: :json
|
558
552
|
end.not_to change { company.groups.reload.count }.from(1)
|
559
553
|
|
560
|
-
expect(response.status).to eq
|
554
|
+
expect(response.status).to eq 500
|
555
|
+
end
|
556
|
+
end
|
557
|
+
|
558
|
+
context 'when Group destroy method is invalid' do
|
559
|
+
it 'does not delete Group' do
|
560
|
+
allow(ScimRails.config).to(
|
561
|
+
receive(:group_destroy_method).and_return('destory!')
|
562
|
+
)
|
563
|
+
|
564
|
+
expect do
|
565
|
+
delete :destroy, params: { id: 1 }, as: :json
|
566
|
+
end.not_to change { company.groups.reload.count }.from(1)
|
567
|
+
|
568
|
+
expect(response.status).to eq 500
|
569
|
+
end
|
570
|
+
end
|
571
|
+
|
572
|
+
context 'whenr target Group is not found' do
|
573
|
+
it 'return 404 not found' do
|
574
|
+
expect do
|
575
|
+
delete :destroy, params: { id: 999999 }, as: :json
|
576
|
+
end.not_to change { company.groups.reload.count }.from(1)
|
577
|
+
|
578
|
+
expect(response.status).to eq 404
|
561
579
|
end
|
562
580
|
end
|
563
581
|
end
|
@@ -0,0 +1,238 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
|
5
|
+
RSpec.describe ScimRails::ScimSchemasController, type: :controller do
|
6
|
+
include AuthHelper
|
7
|
+
|
8
|
+
routes { ScimRails::Engine.routes }
|
9
|
+
|
10
|
+
let(:schemas) do
|
11
|
+
[
|
12
|
+
{
|
13
|
+
schemas: ['urn:ietf:params:scim:schemas:core:2.0:Schema'],
|
14
|
+
id: 'urn:ietf:params:scim:schemas:core:2.0:User',
|
15
|
+
name: 'User',
|
16
|
+
description: 'User Account',
|
17
|
+
attributes: [
|
18
|
+
{
|
19
|
+
name: 'userName',
|
20
|
+
type: 'string',
|
21
|
+
multiValued: false,
|
22
|
+
description: 'Unique identifier for the User. REQUIRED.',
|
23
|
+
required: true,
|
24
|
+
caseExact: false,
|
25
|
+
mutability: 'readWrite',
|
26
|
+
returned: 'default',
|
27
|
+
uniqueness: 'server',
|
28
|
+
}
|
29
|
+
],
|
30
|
+
meta: {
|
31
|
+
resourceType: 'Schema',
|
32
|
+
location:
|
33
|
+
'/v2/Schemas/urn:ietf:params:scim:schemas:core:2.0:User',
|
34
|
+
},
|
35
|
+
},
|
36
|
+
{
|
37
|
+
schemas: ['urn:ietf:params:scim:schemas:core:2.0:Schema'],
|
38
|
+
id: 'urn:ietf:params:scim:schemas:core:2.0:Group',
|
39
|
+
name: 'Group',
|
40
|
+
description: 'Group',
|
41
|
+
attributes: [
|
42
|
+
{
|
43
|
+
name: 'displayName',
|
44
|
+
type: 'string',
|
45
|
+
multiValued: false,
|
46
|
+
description: 'A human-readable name for the Group. REQUIRED.',
|
47
|
+
required: false,
|
48
|
+
caseExact: false,
|
49
|
+
mutability: 'readWrite',
|
50
|
+
returned: 'default',
|
51
|
+
uniqueness: 'none',
|
52
|
+
}
|
53
|
+
],
|
54
|
+
meta: {
|
55
|
+
resourceType: 'Schema',
|
56
|
+
location: '/v2/Schemas/urn:ietf:params:scim:schemas:core:2.0:Group',
|
57
|
+
},
|
58
|
+
}
|
59
|
+
]
|
60
|
+
end
|
61
|
+
|
62
|
+
let(:schemas_110) do
|
63
|
+
[
|
64
|
+
{ id: 'dummy1' }, { id: 'dummy2' }, { id: 'dummy3' },
|
65
|
+
{ id: 'dummy4' }, { id: 'dummy5' }, { id: 'dummy6' },
|
66
|
+
{ id: 'dummy7' }, { id: 'dummy8' }, { id: 'dummy9' },
|
67
|
+
{ id: 'dummy10' }, { id: 'dummy11' }, { id: 'dummy12' },
|
68
|
+
{ id: 'dummy13' }, { id: 'dummy14' }, { id: 'dummy15' },
|
69
|
+
{ id: 'dummy16' }, { id: 'dummy17' }, { id: 'dummy18' },
|
70
|
+
{ id: 'dummy19' }, { id: 'dummy20' }, { id: 'dummy21' },
|
71
|
+
{ id: 'dummy22' }, { id: 'dummy23' }, { id: 'dummy24' },
|
72
|
+
{ id: 'dummy25' }, { id: 'dummy26' }, { id: 'dummy27' },
|
73
|
+
{ id: 'dummy28' }, { id: 'dummy29' }, { id: 'dummy30' },
|
74
|
+
{ id: 'dummy31' }, { id: 'dummy32' }, { id: 'dummy33' },
|
75
|
+
{ id: 'dummy34' }, { id: 'dummy35' }, { id: 'dummy36' },
|
76
|
+
{ id: 'dummy37' }, { id: 'dummy38' }, { id: 'dummy39' },
|
77
|
+
{ id: 'dummy40' }, { id: 'dummy41' }, { id: 'dummy42' },
|
78
|
+
{ id: 'dummy43' }, { id: 'dummy44' }, { id: 'dummy45' },
|
79
|
+
{ id: 'dummy46' }, { id: 'dummy47' }, { id: 'dummy48' },
|
80
|
+
{ id: 'dummy49' }, { id: 'dummy50' }, { id: 'dummy51' },
|
81
|
+
{ id: 'dummy52' }, { id: 'dummy53' }, { id: 'dummy54' },
|
82
|
+
{ id: 'dummy55' }, { id: 'dummy56' }, { id: 'dummy57' },
|
83
|
+
{ id: 'dummy58' }, { id: 'dummy59' }, { id: 'dummy60' },
|
84
|
+
{ id: 'dummy61' }, { id: 'dummy62' }, { id: 'dummy63' },
|
85
|
+
{ id: 'dummy64' }, { id: 'dummy65' }, { id: 'dummy66' },
|
86
|
+
{ id: 'dummy67' }, { id: 'dummy68' }, { id: 'dummy69' },
|
87
|
+
{ id: 'dummy70' }, { id: 'dummy71' }, { id: 'dummy72' },
|
88
|
+
{ id: 'dummy73' }, { id: 'dummy74' }, { id: 'dummy75' },
|
89
|
+
{ id: 'dummy76' }, { id: 'dummy77' }, { id: 'dummy78' },
|
90
|
+
{ id: 'dummy79' }, { id: 'dummy80' }, { id: 'dummy81' },
|
91
|
+
{ id: 'dummy82' }, { id: 'dummy83' }, { id: 'dummy84' },
|
92
|
+
{ id: 'dummy85' }, { id: 'dummy86' }, { id: 'dummy87' },
|
93
|
+
{ id: 'dummy88' }, { id: 'dummy89' }, { id: 'dummy90' },
|
94
|
+
{ id: 'dummy91' }, { id: 'dummy92' }, { id: 'dummy93' },
|
95
|
+
{ id: 'dummy94' }, { id: 'dummy95' }, { id: 'dummy96' },
|
96
|
+
{ id: 'dummy97' }, { id: 'dummy98' }, { id: 'dummy99' },
|
97
|
+
{ id: 'dummy100' }, { id: 'dummy101' }, { id: 'dummy102' },
|
98
|
+
{ id: 'dummy103' }, { id: 'dummy104' }, { id: 'dummy105' },
|
99
|
+
{ id: 'dummy106' }, { id: 'dummy107' }, { id: 'dummy108' },
|
100
|
+
{ id: 'dummy109' }, { id: 'dummy110' }
|
101
|
+
]
|
102
|
+
end
|
103
|
+
|
104
|
+
describe 'index' do
|
105
|
+
let(:company) { create(:company) }
|
106
|
+
|
107
|
+
context 'when unauthorized' do
|
108
|
+
it 'returns scim+json content type' do
|
109
|
+
get :index, as: :json
|
110
|
+
|
111
|
+
expect(response.media_type).to eq 'application/scim+json'
|
112
|
+
end
|
113
|
+
|
114
|
+
it 'fails with no credentials' do
|
115
|
+
get :index, as: :json
|
116
|
+
|
117
|
+
expect(response.status).to eq 401
|
118
|
+
end
|
119
|
+
|
120
|
+
it 'fails with invalid credentials' do
|
121
|
+
request.env['HTTP_AUTHORIZATION'] =
|
122
|
+
ActionController::HttpAuthentication::Basic
|
123
|
+
.encode_credentials('unauthorized', '123456')
|
124
|
+
|
125
|
+
get :index, as: :json
|
126
|
+
|
127
|
+
expect(response.status).to eq 401
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
context 'when authorized' do
|
132
|
+
before :each do
|
133
|
+
http_login(company)
|
134
|
+
end
|
135
|
+
|
136
|
+
it 'returns scim+json content type' do
|
137
|
+
get :index, as: :json
|
138
|
+
|
139
|
+
expect(response.media_type).to eq 'application/scim+json'
|
140
|
+
end
|
141
|
+
|
142
|
+
it 'is successful with valid credentials' do
|
143
|
+
get :index, as: :json
|
144
|
+
|
145
|
+
expect(response.status).to eq 200
|
146
|
+
end
|
147
|
+
|
148
|
+
it 'returns all results' do
|
149
|
+
allow(ScimRails.config).to(receive(:schemas).and_return(schemas))
|
150
|
+
get :index, as: :json
|
151
|
+
response_body = JSON.parse(response.body)
|
152
|
+
expect(response_body.dig('schemas', 0)).to(
|
153
|
+
eq 'urn:ietf:params:scim:api:messages:2.0:ListResponse'
|
154
|
+
)
|
155
|
+
expect(response_body['totalResults']).to eq 2
|
156
|
+
end
|
157
|
+
|
158
|
+
it 'defaults to 100 results' do
|
159
|
+
allow(ScimRails.config).to(receive(:schemas).and_return(schemas_110))
|
160
|
+
|
161
|
+
get :index, as: :json
|
162
|
+
response_body = JSON.parse(response.body)
|
163
|
+
expect(response_body['totalResults']).to eq 110
|
164
|
+
expect(response_body['startIndex']).to eq 1
|
165
|
+
expect(response_body['Resources'].count).to eq 100
|
166
|
+
end
|
167
|
+
|
168
|
+
it 'paginates results' do
|
169
|
+
allow(ScimRails.config).to(receive(:schemas).and_return(schemas_110))
|
170
|
+
get :index, params: {
|
171
|
+
startIndex: 101,
|
172
|
+
count: 5,
|
173
|
+
}, as: :json
|
174
|
+
response_body = JSON.parse(response.body)
|
175
|
+
expect(response_body['totalResults']).to eq 110
|
176
|
+
expect(response_body['startIndex']).to eq 101
|
177
|
+
expect(response_body['Resources'].count).to eq 5
|
178
|
+
expect(response_body.dig('Resources', 0, 'id')).to eq 'dummy101'
|
179
|
+
end
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
describe 'show' do
|
184
|
+
let(:company) { create(:company) }
|
185
|
+
|
186
|
+
context 'when unauthorized' do
|
187
|
+
it 'returns scim+json content type' do
|
188
|
+
get :show, params: { id: 1 }, as: :json
|
189
|
+
|
190
|
+
expect(response.media_type).to eq 'application/scim+json'
|
191
|
+
end
|
192
|
+
|
193
|
+
it 'fails with no credentials' do
|
194
|
+
get :show, params: { id: 1 }, as: :json
|
195
|
+
|
196
|
+
expect(response.status).to eq 401
|
197
|
+
end
|
198
|
+
|
199
|
+
it 'fails with invalid credentials' do
|
200
|
+
request.env['HTTP_AUTHORIZATION'] =
|
201
|
+
ActionController::HttpAuthentication::Basic
|
202
|
+
.encode_credentials('unauthorized', '123456')
|
203
|
+
|
204
|
+
get :show, params: { id: 1 }, as: :json
|
205
|
+
|
206
|
+
expect(response.status).to eq 401
|
207
|
+
end
|
208
|
+
end
|
209
|
+
|
210
|
+
context 'when authorized' do
|
211
|
+
before :each do
|
212
|
+
http_login(company)
|
213
|
+
end
|
214
|
+
|
215
|
+
it 'returns scim+json content type' do
|
216
|
+
allow(ScimRails.config).to(receive(:schemas).and_return(schemas))
|
217
|
+
get :show, params: { id: 'urn:ietf:params:scim:schemas:core:2.0:User' }, as: :json
|
218
|
+
|
219
|
+
expect(response.media_type).to eq 'application/scim+json'
|
220
|
+
end
|
221
|
+
|
222
|
+
it 'is successful with valid credentials' do
|
223
|
+
allow(ScimRails.config).to(receive(:schemas).and_return(schemas))
|
224
|
+
get :show, params: { id: 'urn:ietf:params:scim:schemas:core:2.0:User' }, as: :json
|
225
|
+
|
226
|
+
response_body = JSON.parse(response.body)
|
227
|
+
expect(response.status).to eq 200
|
228
|
+
expect(response_body['name']).to eq 'User'
|
229
|
+
end
|
230
|
+
|
231
|
+
it 'returns :not_found for id that cannot be found' do
|
232
|
+
get :show, params: { id: 'fake_id' }, as: :json
|
233
|
+
|
234
|
+
expect(response.status).to eq 404
|
235
|
+
end
|
236
|
+
end
|
237
|
+
end
|
238
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
|
5
|
+
RSpec.describe ScimRails::ScimSchemasController, type: :request do
|
6
|
+
let(:company) { create(:company) }
|
7
|
+
let(:credentials) do
|
8
|
+
Base64.encode64("#{company.subdomain}:#{company.api_token}")
|
9
|
+
end
|
10
|
+
let(:authorization) { "Basic #{credentials}" }
|
11
|
+
|
12
|
+
def get_request(content_type = 'application/scim+json')
|
13
|
+
get '/scim/v2/Schemas',
|
14
|
+
headers: {
|
15
|
+
Authorization: authorization,
|
16
|
+
'Content-Type': content_type,
|
17
|
+
}
|
18
|
+
end
|
19
|
+
|
20
|
+
context 'OAuth Bearer Authorization' do
|
21
|
+
context 'with valid token' do
|
22
|
+
let(:authorization) { "Bearer #{company.api_token}" }
|
23
|
+
|
24
|
+
it 'supports OAuth bearer authorization and succeeds' do
|
25
|
+
get_request
|
26
|
+
expect(response.status).to eq 200
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
context 'with invalid token' do
|
31
|
+
let(:authorization) { "Bearer #{SecureRandom.hex}" }
|
32
|
+
|
33
|
+
it 'The request fails' do
|
34
|
+
get_request
|
35
|
+
expect(response.status).to eq 401
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|