scimaenaga 0.6.1 → 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (36) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +15 -0
  3. data/app/controllers/concerns/scim_rails/exception_handler.rb +74 -22
  4. data/app/controllers/scim_rails/scim_groups_controller.rb +14 -3
  5. data/app/controllers/scim_rails/scim_schemas_controller.rb +42 -0
  6. data/app/controllers/scim_rails/scim_users_controller.rb +22 -4
  7. data/app/libraries/scim_patch.rb +15 -9
  8. data/app/libraries/scim_patch_operation.rb +50 -71
  9. data/app/libraries/scim_patch_operation_converter.rb +90 -0
  10. data/app/libraries/scim_patch_operation_group.rb +100 -0
  11. data/app/libraries/scim_patch_operation_user.rb +53 -0
  12. data/config/routes.rb +14 -11
  13. data/lib/generators/scim_rails/templates/initializer.rb +106 -0
  14. data/lib/scim_rails/config.rb +8 -5
  15. data/lib/scim_rails/version.rb +1 -1
  16. data/spec/controllers/scim_rails/scim_groups_controller_spec.rb +25 -7
  17. data/spec/controllers/scim_rails/scim_schemas_controller_spec.rb +238 -0
  18. data/spec/controllers/scim_rails/scim_schemas_request_spec.rb +39 -0
  19. data/spec/controllers/scim_rails/scim_users_controller_spec.rb +361 -220
  20. data/spec/dummy/app/models/user.rb +9 -0
  21. data/spec/dummy/config/initializers/scim_rails_config.rb +27 -24
  22. data/spec/dummy/db/migrate/20220131090107_add_deletable_to_users.rb +5 -0
  23. data/spec/dummy/db/schema.rb +2 -1
  24. data/spec/dummy/db/seeds.rb +10 -1
  25. data/spec/factories/user.rb +2 -0
  26. data/spec/libraries/scim_patch_operation_group_spec.rb +165 -0
  27. data/spec/libraries/scim_patch_operation_user_spec.rb +101 -0
  28. data/spec/libraries/scim_patch_spec.rb +135 -53
  29. metadata +80 -80
  30. data/spec/dummy/db/development.sqlite3 +0 -0
  31. data/spec/dummy/db/test.sqlite3 +0 -0
  32. data/spec/dummy/log/development.log +0 -0
  33. data/spec/dummy/log/test.log +0 -377
  34. data/spec/dummy/put_group.http +0 -5
  35. data/spec/dummy/tmp/restart.txt +0 -0
  36. 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', 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
- get 'scim/v2/Groups', action: :index, controller: 'scim_groups'
8
- post 'scim/v2/Groups', action: :create, controller: 'scim_groups'
9
- get 'scim/v2/Groups/:id', action: :show, controller: 'scim_groups'
10
- put 'scim/v2/Groups/:id', action: :put_update, controller: 'scim_groups'
11
- patch 'scim/v2/Groups/:id', action: :patch_update, controller: 'scim_groups'
12
- delete 'scim/v2/Groups/:id', action: :destroy, controller: 'scim_groups'
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
@@ -13,7 +13,7 @@ module ScimRails
13
13
 
14
14
  # Class containing configuration of ScimRails
15
15
  class Config
16
- ALGO_NONE = "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
- :group_destroy_method
47
+ :user_destroy_method,
48
+ :group_destroy_method,
49
+ :schemas
48
50
 
49
51
  def initialize
50
- @basic_auth_model = "Company"
52
+ @basic_auth_model = 'Company'
51
53
  @scim_users_list_order = :id
52
- @scim_users_model = "User"
54
+ @scim_users_model = 'User'
53
55
  @scim_groups_list_order = :id
54
- @scim_groups_model = "Group"
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ScimRails
4
- VERSION = '0.6.1'
4
+ VERSION = '0.9.0'
5
5
  end
@@ -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 501
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