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.
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