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