scimaenaga 0.6.1 → 0.9.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 58fc798213cd88360e3f55430717995ad195c6bac873128907233e82261fa4b1
|
4
|
+
data.tar.gz: 5e0e88484123fb42ffd42e1174439c88773615b4be4b1c8aa2e3f7eb38f7b8c3
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 66e68eba027f29f0f87bb94c339356c9b6bb5dce37561f1a9f35a96fd8d34d8fc2f7fbb7d0901106b2cf66fdc7fef18cf3861c593c6795c68834e8d67793ba32
|
7
|
+
data.tar.gz: 6809bbeab71b8cb0bf645ecdca6819c43e2bbfa5bf26936b8772a28fdace71b47c63b1211c5454033168b8812b4ba249c7ae0423c6a1b047faafb16b6caf69ee
|
data/README.md
CHANGED
@@ -250,6 +250,21 @@ ScimRails.configure do |config|
|
|
250
250
|
end
|
251
251
|
```
|
252
252
|
|
253
|
+
### Schemas endpoint
|
254
|
+
|
255
|
+
If you need Schemas endpoint configure `schemas`.
|
256
|
+
(Azure AD requires Schemas endpoint when registering to Application Gallery.)
|
257
|
+
|
258
|
+
You have to configure `schemas` as
|
259
|
+
- corresponding with other configurations.
|
260
|
+
e.g.) When `userName` is defined in `mutable_user_attributes`, configure `userName` as `mutability: 'readWrite'`.
|
261
|
+
|
262
|
+
- corresponding with your model.
|
263
|
+
e.g.) When `userName` must be specified configure `userName` as `required: true`
|
264
|
+
|
265
|
+
Sample config (with comment) is written in lib/generators/scim_rails/templates/initializer.rb.
|
266
|
+
For more details, read [Schema Definition](https://datatracker.ietf.org/doc/html/rfc7643#section-7), and [Schema Representation](https://datatracker.ietf.org/doc/html/rfc7643#section-8.7)
|
267
|
+
|
253
268
|
## Contributing
|
254
269
|
|
255
270
|
### [Code of Conduct](https://github.com/StudistCorporation/scimaenaga/blob/master/CODE_OF_CONDUCT.md)
|
@@ -7,6 +7,9 @@ module ScimRails
|
|
7
7
|
class InvalidCredentials < StandardError
|
8
8
|
end
|
9
9
|
|
10
|
+
class InvalidRequest < StandardError
|
11
|
+
end
|
12
|
+
|
10
13
|
class InvalidQuery < StandardError
|
11
14
|
end
|
12
15
|
|
@@ -16,6 +19,21 @@ module ScimRails
|
|
16
19
|
class UnsupportedDeleteRequest < StandardError
|
17
20
|
end
|
18
21
|
|
22
|
+
class InvalidConfiguration < StandardError
|
23
|
+
end
|
24
|
+
|
25
|
+
class UnexpectedError < StandardError
|
26
|
+
end
|
27
|
+
|
28
|
+
class ResourceNotFound < StandardError
|
29
|
+
attr_reader :id
|
30
|
+
|
31
|
+
def initialize(id)
|
32
|
+
super
|
33
|
+
@id = id
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
19
37
|
included do
|
20
38
|
if Rails.env.production?
|
21
39
|
rescue_from StandardError do |exception|
|
@@ -28,8 +46,8 @@ module ScimRails
|
|
28
46
|
|
29
47
|
json_response(
|
30
48
|
{
|
31
|
-
schemas: [
|
32
|
-
status:
|
49
|
+
schemas: ['urn:ietf:params:scim:api:messages:2.0:Error'],
|
50
|
+
status: '500',
|
33
51
|
},
|
34
52
|
:internal_server_error
|
35
53
|
)
|
@@ -39,21 +57,32 @@ module ScimRails
|
|
39
57
|
rescue_from ScimRails::ExceptionHandler::InvalidCredentials do
|
40
58
|
json_response(
|
41
59
|
{
|
42
|
-
schemas: [
|
43
|
-
detail:
|
44
|
-
status:
|
60
|
+
schemas: ['urn:ietf:params:scim:api:messages:2.0:Error'],
|
61
|
+
detail: 'Authorization failure. The authorization header is invalid or missing.',
|
62
|
+
status: '401',
|
45
63
|
},
|
46
64
|
:unauthorized
|
47
65
|
)
|
48
66
|
end
|
49
67
|
|
68
|
+
rescue_from ScimRails::ExceptionHandler::InvalidRequest do |e|
|
69
|
+
json_response(
|
70
|
+
{
|
71
|
+
schemas: ['urn:ietf:params:scim:api:messages:2.0:Error'],
|
72
|
+
detail: "Invalid request. #{e.message}",
|
73
|
+
status: '400',
|
74
|
+
},
|
75
|
+
:bad_request
|
76
|
+
)
|
77
|
+
end
|
78
|
+
|
50
79
|
rescue_from ScimRails::ExceptionHandler::InvalidQuery do
|
51
80
|
json_response(
|
52
81
|
{
|
53
|
-
schemas: [
|
54
|
-
scimType:
|
55
|
-
detail:
|
56
|
-
status:
|
82
|
+
schemas: ['urn:ietf:params:scim:api:messages:2.0:Error'],
|
83
|
+
scimType: 'invalidFilter',
|
84
|
+
detail: 'The specified filter syntax was invalid, or the specified attribute and filter comparison combination is not supported.',
|
85
|
+
status: '400',
|
57
86
|
},
|
58
87
|
:bad_request
|
59
88
|
)
|
@@ -62,9 +91,9 @@ module ScimRails
|
|
62
91
|
rescue_from ScimRails::ExceptionHandler::UnsupportedPatchRequest do
|
63
92
|
json_response(
|
64
93
|
{
|
65
|
-
schemas: [
|
66
|
-
detail:
|
67
|
-
status:
|
94
|
+
schemas: ['urn:ietf:params:scim:api:messages:2.0:Error'],
|
95
|
+
detail: 'Invalid PATCH request.',
|
96
|
+
status: '422',
|
68
97
|
},
|
69
98
|
:unprocessable_entity
|
70
99
|
)
|
@@ -73,20 +102,43 @@ module ScimRails
|
|
73
102
|
rescue_from ScimRails::ExceptionHandler::UnsupportedDeleteRequest do
|
74
103
|
json_response(
|
75
104
|
{
|
76
|
-
schemas: [
|
77
|
-
detail:
|
78
|
-
status:
|
105
|
+
schemas: ['urn:ietf:params:scim:api:messages:2.0:Error'],
|
106
|
+
detail: 'Delete operation is disabled for the requested resource.',
|
107
|
+
status: '501',
|
79
108
|
},
|
80
109
|
:not_implemented
|
81
110
|
)
|
82
111
|
end
|
83
112
|
|
84
|
-
rescue_from
|
113
|
+
rescue_from ScimRails::ExceptionHandler::InvalidConfiguration do |e|
|
114
|
+
json_response(
|
115
|
+
{
|
116
|
+
schemas: ['urn:ietf:params:scim:api:messages:2.0:Error'],
|
117
|
+
detail: "Invalid configuration. #{e.message}",
|
118
|
+
status: '500',
|
119
|
+
},
|
120
|
+
:internal_server_error
|
121
|
+
)
|
122
|
+
end
|
123
|
+
|
124
|
+
rescue_from ScimRails::ExceptionHandler::UnexpectedError do |e|
|
125
|
+
json_response(
|
126
|
+
{
|
127
|
+
schemas: ['urn:ietf:params:scim:api:messages:2.0:Error'],
|
128
|
+
detail: "Unexpected Error. #{e.message}",
|
129
|
+
status: '500',
|
130
|
+
},
|
131
|
+
:internal_server_error
|
132
|
+
)
|
133
|
+
end
|
134
|
+
|
135
|
+
rescue_from ActiveRecord::RecordNotFound,
|
136
|
+
ScimRails::ExceptionHandler::ResourceNotFound do |e|
|
85
137
|
json_response(
|
86
138
|
{
|
87
|
-
schemas: [
|
139
|
+
schemas: ['urn:ietf:params:scim:api:messages:2.0:Error'],
|
88
140
|
detail: "Resource #{e.id} not found.",
|
89
|
-
status:
|
141
|
+
status: '404',
|
90
142
|
},
|
91
143
|
:not_found
|
92
144
|
)
|
@@ -97,18 +149,18 @@ module ScimRails
|
|
97
149
|
when /has already been taken/
|
98
150
|
json_response(
|
99
151
|
{
|
100
|
-
schemas: [
|
152
|
+
schemas: ['urn:ietf:params:scim:api:messages:2.0:Error'],
|
101
153
|
detail: e.message,
|
102
|
-
status:
|
154
|
+
status: '409',
|
103
155
|
},
|
104
156
|
:conflict
|
105
157
|
)
|
106
158
|
else
|
107
159
|
json_response(
|
108
160
|
{
|
109
|
-
schemas: [
|
161
|
+
schemas: ['urn:ietf:params:scim:api:messages:2.0:Error'],
|
110
162
|
detail: e.message,
|
111
|
-
status:
|
163
|
+
status: '422',
|
112
164
|
},
|
113
165
|
:unprocessable_entity
|
114
166
|
)
|
@@ -60,7 +60,7 @@ module ScimRails
|
|
60
60
|
group = @company
|
61
61
|
.public_send(ScimRails.config.scim_groups_scope)
|
62
62
|
.find(params[:id])
|
63
|
-
patch = ScimPatch.new(params,
|
63
|
+
patch = ScimPatch.new(params, :group)
|
64
64
|
patch.save(group)
|
65
65
|
|
66
66
|
json_scim_response(object: group)
|
@@ -68,13 +68,24 @@ module ScimRails
|
|
68
68
|
|
69
69
|
def destroy
|
70
70
|
unless ScimRails.config.group_destroy_method
|
71
|
-
raise ScimRails::ExceptionHandler::
|
71
|
+
raise ScimRails::ExceptionHandler::InvalidConfiguration
|
72
72
|
end
|
73
73
|
|
74
74
|
group = @company
|
75
75
|
.public_send(ScimRails.config.scim_groups_scope)
|
76
76
|
.find(params[:id])
|
77
|
-
group
|
77
|
+
raise ActiveRecord::RecordNotFound unless group
|
78
|
+
|
79
|
+
begin
|
80
|
+
group.public_send(ScimRails.config.group_destroy_method)
|
81
|
+
rescue NoMethodError => e
|
82
|
+
raise ScimRails::ExceptionHandler::InvalidConfiguration, e.message
|
83
|
+
rescue ActiveRecord::RecordNotDestroyed => e
|
84
|
+
raise ScimRails::ExceptionHandler::InvalidRequest, e.message
|
85
|
+
rescue => e
|
86
|
+
raise ScimRails::ExceptionHandler::UnexpectedError, e.message
|
87
|
+
end
|
88
|
+
|
78
89
|
head :no_content
|
79
90
|
end
|
80
91
|
|
@@ -0,0 +1,42 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ScimRails
|
4
|
+
class ScimSchemasController < ScimRails::ApplicationController
|
5
|
+
def index
|
6
|
+
schemas = ScimRails.config.schemas
|
7
|
+
|
8
|
+
counts = ScimCount.new(
|
9
|
+
start_index: params[:startIndex],
|
10
|
+
limit: params[:count],
|
11
|
+
total: schemas.count
|
12
|
+
)
|
13
|
+
|
14
|
+
list_schemas_response(schemas, counts)
|
15
|
+
end
|
16
|
+
|
17
|
+
def show
|
18
|
+
schema = ScimRails.config.schemas.find do |s|
|
19
|
+
s[:id] == params[:id]
|
20
|
+
end
|
21
|
+
|
22
|
+
raise ScimRails::ExceptionHandler::ResourceNotFound, params[:id] if schema.nil?
|
23
|
+
|
24
|
+
json_response(schema)
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def list_schemas_response(schemas, counts)
|
30
|
+
response = {
|
31
|
+
schemas: [
|
32
|
+
'urn:ietf:params:scim:api:messages:2.0:ListResponse'
|
33
|
+
],
|
34
|
+
totalResults: counts.total,
|
35
|
+
startIndex: counts.start_index,
|
36
|
+
itemsPerPage: counts.limit,
|
37
|
+
Resources: schemas[counts.offset...counts.offset + counts.limit],
|
38
|
+
}
|
39
|
+
json_response(response)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -3,7 +3,6 @@
|
|
3
3
|
module ScimRails
|
4
4
|
class ScimUsersController < ScimRails::ApplicationController
|
5
5
|
|
6
|
-
|
7
6
|
def index
|
8
7
|
if params[:filter].present?
|
9
8
|
query = ScimRails::ScimQueryParser.new(
|
@@ -50,8 +49,6 @@ module ScimRails
|
|
50
49
|
json_scim_response(object: user, status: :created)
|
51
50
|
end
|
52
51
|
|
53
|
-
|
54
|
-
|
55
52
|
def show
|
56
53
|
user = @company.public_send(ScimRails.config.scim_users_scope).find(params[:id])
|
57
54
|
json_scim_response(object: user)
|
@@ -65,12 +62,33 @@ module ScimRails
|
|
65
62
|
|
66
63
|
def patch_update
|
67
64
|
user = @company.public_send(ScimRails.config.scim_users_scope).find(params[:id])
|
68
|
-
patch = ScimPatch.new(params,
|
65
|
+
patch = ScimPatch.new(params, :user)
|
69
66
|
patch.save(user)
|
70
67
|
|
71
68
|
json_scim_response(object: user)
|
72
69
|
end
|
73
70
|
|
71
|
+
def destroy
|
72
|
+
unless ScimRails.config.user_destroy_method
|
73
|
+
raise ScimRails::ExceptionHandler::InvalidConfiguration
|
74
|
+
end
|
75
|
+
|
76
|
+
user = @company.public_send(ScimRails.config.scim_users_scope).find(params[:id])
|
77
|
+
raise ActiveRecord::RecordNotFound unless user
|
78
|
+
|
79
|
+
begin
|
80
|
+
user.public_send(ScimRails.config.user_destroy_method)
|
81
|
+
rescue NoMethodError => e
|
82
|
+
raise ScimRails::ExceptionHandler::InvalidConfiguration, e.message
|
83
|
+
rescue ActiveRecord::RecordNotDestroyed => e
|
84
|
+
raise ScimRails::ExceptionHandler::InvalidRequest, e.message
|
85
|
+
rescue => e
|
86
|
+
raise ScimRails::ExceptionHandler::UnexpectedError, e.message
|
87
|
+
end
|
88
|
+
|
89
|
+
head :no_content
|
90
|
+
end
|
91
|
+
|
74
92
|
private
|
75
93
|
|
76
94
|
def permitted_user_params
|
data/app/libraries/scim_patch.rb
CHANGED
@@ -4,18 +4,24 @@
|
|
4
4
|
class ScimPatch
|
5
5
|
attr_accessor :operations
|
6
6
|
|
7
|
-
def initialize(params,
|
8
|
-
|
9
|
-
|
10
|
-
raise StandardError
|
11
|
-
end
|
12
|
-
if params['Operations'].nil?
|
7
|
+
def initialize(params, resource_type)
|
8
|
+
if params['schemas'] != ['urn:ietf:params:scim:api:messages:2.0:PatchOp'] ||
|
9
|
+
params['Operations'].nil?
|
13
10
|
raise ScimRails::ExceptionHandler::UnsupportedPatchRequest
|
14
11
|
end
|
15
12
|
|
16
|
-
|
17
|
-
|
18
|
-
|
13
|
+
# complex-value(Hash) operation is converted to multiple single-value operations
|
14
|
+
converted_operations = ScimPatchOperationConverter.convert(params['Operations'])
|
15
|
+
@operations = converted_operations.map do |o|
|
16
|
+
create_operation(resource_type, o['op'], o['path'], o['value'])
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def create_operation(resource_type, op, path, value)
|
21
|
+
if resource_type == :user
|
22
|
+
ScimPatchOperationUser.new(op, path, value)
|
23
|
+
else
|
24
|
+
ScimPatchOperationGroup.new(op, path, value)
|
19
25
|
end
|
20
26
|
end
|
21
27
|
|
@@ -2,88 +2,67 @@
|
|
2
2
|
|
3
3
|
# Parse One of "Operations" in PATCH request
|
4
4
|
class ScimPatchOperation
|
5
|
-
|
5
|
+
attr_reader :op, :path_scim, :path_sp, :value
|
6
6
|
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
@op = op.downcase.to_sym
|
17
|
-
@path_scim = path
|
18
|
-
@path_sp = convert_path(path, mutable_attributes_schema)
|
19
|
-
@value = convert_bool_if_string(value, @path_scim)
|
20
|
-
end
|
21
|
-
|
22
|
-
def save(model)
|
23
|
-
if @path_scim == 'members' # Only members are supported for value is an array
|
24
|
-
update_member_ids = @value.map do |v|
|
25
|
-
v[ScimRails.config.group_member_relation_schema.keys.first]
|
26
|
-
end
|
7
|
+
# path presence is guaranteed by ScimPatchOperationConverter
|
8
|
+
#
|
9
|
+
# value must be String or Array.
|
10
|
+
# complex-value(Hash) is converted to multiple single-value operations by ScimPatchOperationConverter
|
11
|
+
def initialize(op, path, value)
|
12
|
+
if !op.in?(%w[add replace remove]) || path.nil?
|
13
|
+
raise ScimRails::ExceptionHandler::UnsupportedPatchRequest
|
14
|
+
end
|
27
15
|
|
28
|
-
|
29
|
-
|
30
|
-
case @op
|
31
|
-
when :add
|
32
|
-
member_ids = current_member_ids.concat(update_member_ids)
|
33
|
-
when :replace
|
34
|
-
member_ids = current_member_ids.concat(update_member_ids)
|
35
|
-
when :remove
|
36
|
-
member_ids = current_member_ids - update_member_ids
|
37
|
-
end
|
16
|
+
# define validate method in the inherited class
|
17
|
+
validate(op, path, value)
|
38
18
|
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
end
|
19
|
+
@op = op
|
20
|
+
@value = value
|
21
|
+
@path_scim = parse_path_scim(path)
|
22
|
+
@path_sp = path_scim_to_path_sp(@path_scim)
|
44
23
|
|
45
|
-
|
46
|
-
when :add, :replace
|
47
|
-
model.attributes = { @path_sp => @value }
|
48
|
-
when :remove
|
49
|
-
model.attributes = { @path_sp => nil }
|
50
|
-
end
|
24
|
+
# define parse method in the inherited class
|
51
25
|
end
|
52
26
|
|
53
27
|
private
|
54
28
|
|
55
|
-
def
|
56
|
-
#
|
57
|
-
# examle:
|
58
|
-
# path = 'emails[type eq "work"].value'
|
59
|
-
# mutable_attributes_schema = {
|
60
|
-
# emails: [
|
61
|
-
# {
|
62
|
-
# value: :mail_address,
|
63
|
-
# }
|
64
|
-
# ],
|
65
|
-
# }
|
29
|
+
def parse_path_scim(path)
|
30
|
+
# 'emails[type eq "work"].value' is parsed as follows:
|
66
31
|
#
|
67
|
-
#
|
68
|
-
#
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
32
|
+
# {
|
33
|
+
# attribute: 'emails',
|
34
|
+
# filter: {
|
35
|
+
# attribute: 'type',
|
36
|
+
# operator: 'eq',
|
37
|
+
# parameter: 'work'
|
38
|
+
# },
|
39
|
+
# rest_path: ['value']
|
40
|
+
# }
|
41
|
+
#
|
42
|
+
# This method suport only single operator
|
74
43
|
|
75
|
-
|
76
|
-
#
|
77
|
-
|
78
|
-
|
44
|
+
# path: emails.value
|
45
|
+
# filter_string: type eq "work"
|
46
|
+
path_str = path.dup
|
47
|
+
filter_string = path_str.slice!(/\[(.+?)\]/, 0)&.slice(/\[(.+?)\]/, 1)
|
79
48
|
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
49
|
+
# path_elements: ['emails', 'value']
|
50
|
+
path_elements = path_str.split('.')
|
51
|
+
|
52
|
+
# filter_elements: ['type', 'eq', '"work"']
|
53
|
+
filter_elements = filter_string&.split(' ')
|
54
|
+
path_scim = { attribute: path_elements[0],
|
55
|
+
rest_path: path_elements.slice(1...path_elements.length), }
|
56
|
+
if filter_elements.present?
|
57
|
+
path_scim[:filter] = {
|
58
|
+
attribute: filter_elements[0],
|
59
|
+
operator: filter_elements[1],
|
60
|
+
# delete double quotation
|
61
|
+
parameter: filter_elements[2].slice(1...filter_elements[2].length - 1),
|
62
|
+
}
|
87
63
|
end
|
64
|
+
|
65
|
+
path_scim
|
88
66
|
end
|
67
|
+
|
89
68
|
end
|
@@ -0,0 +1,90 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# 1. Convert each multi-value operation to single-value operations.
|
4
|
+
# 2. Fix wrong "value".
|
5
|
+
# 3. convert "op" to lower case
|
6
|
+
#
|
7
|
+
# About #1
|
8
|
+
# to handle request pattern A and B in the same way,
|
9
|
+
# convert complex-value to path + single-value
|
10
|
+
#
|
11
|
+
# Typical request is path_base = nil and value = complex-value:
|
12
|
+
# {
|
13
|
+
# "op": "replace",
|
14
|
+
# "value": {
|
15
|
+
# "displayName": "Taro Suzuki",
|
16
|
+
# "name.givenName": "taro"
|
17
|
+
# }
|
18
|
+
# }
|
19
|
+
#
|
20
|
+
# This request is converted to:
|
21
|
+
# [
|
22
|
+
# {
|
23
|
+
# "op": "replace",
|
24
|
+
# "path": "displayName",
|
25
|
+
# "value": "Taro Suzuki",
|
26
|
+
# }
|
27
|
+
# {
|
28
|
+
# "op": "replace",
|
29
|
+
# "path": "name.givenName",
|
30
|
+
# "value": "taro"
|
31
|
+
# }
|
32
|
+
# ]
|
33
|
+
class ScimPatchOperationConverter
|
34
|
+
class << self
|
35
|
+
def convert(operations)
|
36
|
+
operations.each_with_object([]) do |o, result|
|
37
|
+
value = o['value']
|
38
|
+
value = value.permit!.to_h if value.instance_of?(ActionController::Parameters)
|
39
|
+
|
40
|
+
if value.is_a?(Hash)
|
41
|
+
converted_operations = convert_to_single_value_operations(o['op'], o['path'],
|
42
|
+
value)
|
43
|
+
result.concat(converted_operations)
|
44
|
+
next
|
45
|
+
end
|
46
|
+
|
47
|
+
result << fix_operation_format(o['op'], o['path'], value)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
private
|
52
|
+
|
53
|
+
def convert_to_single_value_operations(op, path_base, hash_value)
|
54
|
+
hash_value.map do |k, v|
|
55
|
+
path = if path_base.present?
|
56
|
+
"#{path_base}.#{k}"
|
57
|
+
else
|
58
|
+
k
|
59
|
+
end
|
60
|
+
fix_operation_format(op, path, v)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def fix_operation_format(op, path, value)
|
65
|
+
{ 'op' => op.downcase, 'path' => path, 'value' => fix_value(value, path) }
|
66
|
+
end
|
67
|
+
|
68
|
+
def fix_value(value, path)
|
69
|
+
if path == 'active'
|
70
|
+
convert_bool_if_string(value)
|
71
|
+
else
|
72
|
+
value
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
# According to SCIM, correct value in requests from Azure AD.
|
77
|
+
# When using non-application gallery in Azure AD, and feature flag is not specified,
|
78
|
+
# Azure AD sends string for 'active' attribute
|
79
|
+
def convert_bool_if_string(value)
|
80
|
+
case value
|
81
|
+
when 'true', 'True'
|
82
|
+
return true
|
83
|
+
when 'false', 'False'
|
84
|
+
return false
|
85
|
+
else
|
86
|
+
return value
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|