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
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 348227d90785d1980e12b0f2d6ff1c71a6861761a4b35c5c10b5147894f949de
4
- data.tar.gz: 8bc3cfb0ba591b1eff717044b91c9db4d34f9fbc13285d234bf526a52be8ce48
3
+ metadata.gz: 58fc798213cd88360e3f55430717995ad195c6bac873128907233e82261fa4b1
4
+ data.tar.gz: 5e0e88484123fb42ffd42e1174439c88773615b4be4b1c8aa2e3f7eb38f7b8c3
5
5
  SHA512:
6
- metadata.gz: d1ed9e73ffc03e6362f2bd498ad37f5f4a915a7fd09c64470700ee729e8baec062a43e91f1f6a937f37fec325533351fc12bd1d2f1e2b5f067efd5c5c209871d
7
- data.tar.gz: a7810a0c341f779cb8be98213d3c9eb6d5ac98669b5dea67e389bec787a3f65a8ed1e378a005e3d0415d0b7563f49fa0cf3dafdda2917220c92d38ce3a7c65d0
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: ["urn:ietf:params:scim:api:messages:2.0:Error"],
32
- status: "500"
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: ["urn:ietf:params:scim:api:messages:2.0:Error"],
43
- detail: "Authorization failure. The authorization header is invalid or missing.",
44
- status: "401"
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: ["urn:ietf:params:scim:api:messages:2.0:Error"],
54
- scimType: "invalidFilter",
55
- detail: "The specified filter syntax was invalid, or the specified attribute and filter comparison combination is not supported.",
56
- status: "400"
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: ["urn:ietf:params:scim:api:messages:2.0:Error"],
66
- detail: "Invalid PATCH request. This PATCH endpoint only supports deprovisioning and reprovisioning records.",
67
- status: "422"
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: ["urn:ietf:params:scim:api:messages:2.0:Error"],
77
- detail: "Delete operation is disabled for the requested resource.",
78
- status: "501"
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 ActiveRecord::RecordNotFound do |e|
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: ["urn:ietf:params:scim:api:messages:2.0:Error"],
139
+ schemas: ['urn:ietf:params:scim:api:messages:2.0:Error'],
88
140
  detail: "Resource #{e.id} not found.",
89
- status: "404"
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: ["urn:ietf:params:scim:api:messages:2.0:Error"],
152
+ schemas: ['urn:ietf:params:scim:api:messages:2.0:Error'],
101
153
  detail: e.message,
102
- status: "409"
154
+ status: '409',
103
155
  },
104
156
  :conflict
105
157
  )
106
158
  else
107
159
  json_response(
108
160
  {
109
- schemas: ["urn:ietf:params:scim:api:messages:2.0:Error"],
161
+ schemas: ['urn:ietf:params:scim:api:messages:2.0:Error'],
110
162
  detail: e.message,
111
- status: "422"
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, ScimRails.config.mutable_group_attributes_schema)
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::UnsupportedDeleteRequest
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.public_send(ScimRails.config.group_destroy_method)
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, ScimRails.config.mutable_user_attributes_schema)
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
@@ -4,18 +4,24 @@
4
4
  class ScimPatch
5
5
  attr_accessor :operations
6
6
 
7
- def initialize(params, mutable_attributes_schema)
8
- # FIXME: raise proper error.
9
- unless params['schemas'] == ['urn:ietf:params:scim:api:messages:2.0:PatchOp']
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
- @operations = params['Operations'].map do |operation|
17
- ScimPatchOperation.new(operation['op'], operation['path'], operation['value'],
18
- mutable_attributes_schema)
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
- attr_accessor :op, :path_scim, :path_sp, :value
5
+ attr_reader :op, :path_scim, :path_sp, :value
6
6
 
7
- def initialize(op, path, value, mutable_attributes_schema)
8
- # FIXME: Raise proper Error
9
- raise StandardError unless op.downcase.in? %w[add replace remove]
10
-
11
- # No path is not supported.
12
- # FIXME: Raise proper Error
13
- raise ScimRails::ExceptionHandler::UnsupportedPatchRequest if path.nil?
14
- raise ScimRails::ExceptionHandler::UnsupportedPatchRequest if value.nil?
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
- current_member_ids = model
29
- .public_send(ScimRails.config.group_member_relation_attribute)
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
- # Only the member addition process is saved by each ids
40
- model.public_send("#{ScimRails.config.group_member_relation_attribute}=",
41
- member_ids.uniq)
42
- return
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
- case @op
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 convert_path(path, mutable_attributes_schema)
56
- # For now, library does not support Multi-Valued Attributes properly.
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
- # Library ignores filter conditions (like [type eq "work"])
68
- # and always uses the first element of the array
69
- dig_keys = path.gsub(/\[(.+?)\]/, '.0').split('.').map do |step|
70
- step == '0' ? 0 : step.to_sym
71
- end
72
- mutable_attributes_schema.dig(*dig_keys)
73
- end
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
- def convert_bool_if_string(value, path)
76
- # This method correct value in requests from Azure AD according to SCIM.
77
- # When path is not active, do nothing and return
78
- return value if path != 'active'
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
- case value
81
- when 'true', 'True' then
82
- return true
83
- when 'false', 'False' then
84
- return false
85
- else
86
- return value
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