scimaenaga 0.7.0 → 0.9.1

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 (65) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +22 -7
  3. data/Rakefile +6 -8
  4. data/app/controllers/concerns/{scim_rails → scimaenaga}/exception_handler.rb +47 -37
  5. data/app/controllers/concerns/scimaenaga/response.rb +94 -0
  6. data/app/controllers/scimaenaga/application_controller.rb +72 -0
  7. data/app/controllers/{scim_rails → scimaenaga}/scim_groups_controller.rb +26 -26
  8. data/app/controllers/scimaenaga/scim_schemas_controller.rb +42 -0
  9. data/app/controllers/scimaenaga/scim_users_controller.rb +104 -0
  10. data/app/helpers/{scim_rails → scimaenaga}/application_helper.rb +1 -1
  11. data/app/libraries/scim_patch.rb +16 -9
  12. data/app/libraries/scim_patch_operation.rb +51 -141
  13. data/app/libraries/scim_patch_operation_converter.rb +90 -0
  14. data/app/libraries/scim_patch_operation_group.rb +100 -0
  15. data/app/libraries/scim_patch_operation_user.rb +53 -0
  16. data/app/models/{scim_rails → scimaenaga}/application_record.rb +1 -1
  17. data/app/models/scimaenaga/authorize_api_request.rb +39 -0
  18. data/app/models/{scim_rails → scimaenaga}/scim_count.rb +8 -4
  19. data/app/models/scimaenaga/scim_query_parser.rb +49 -0
  20. data/config/routes.rb +15 -13
  21. data/lib/generators/scimaenaga/USAGE +8 -0
  22. data/lib/generators/scimaenaga/scimaenaga_generator.rb +7 -0
  23. data/lib/generators/{scim_rails → scimaenaga}/templates/initializer.rb +128 -22
  24. data/lib/{scim_rails → scimaenaga}/config.rb +9 -7
  25. data/lib/scimaenaga/encoder.rb +27 -0
  26. data/lib/scimaenaga/engine.rb +12 -0
  27. data/lib/scimaenaga/version.rb +5 -0
  28. data/lib/scimaenaga.rb +6 -0
  29. data/lib/tasks/{scim_rails_tasks.rake → scimaenaga_tasks.rake} +1 -1
  30. data/spec/controllers/{scim_rails → scimaenaga}/scim_groups_controller_spec.rb +8 -8
  31. data/spec/controllers/{scim_rails → scimaenaga}/scim_groups_request_spec.rb +18 -18
  32. data/spec/controllers/scimaenaga/scim_schemas_controller_spec.rb +238 -0
  33. data/spec/controllers/scimaenaga/scim_schemas_request_spec.rb +39 -0
  34. data/spec/controllers/{scim_rails → scimaenaga}/scim_users_controller_spec.rb +14 -15
  35. data/spec/controllers/{scim_rails → scimaenaga}/scim_users_request_spec.rb +20 -20
  36. data/spec/dummy/app/assets/config/manifest.js +1 -1
  37. data/spec/dummy/config/application.rb +1 -2
  38. data/spec/dummy/config/initializers/{scim_rails_config.rb → scimaenaga_config.rb} +25 -25
  39. data/spec/dummy/config/routes.rb +1 -1
  40. data/spec/factories/company.rb +3 -3
  41. data/spec/lib/scimaenaga/encoder_spec.rb +64 -0
  42. data/spec/libraries/scim_patch_operation_group_spec.rb +165 -0
  43. data/spec/libraries/scim_patch_operation_user_spec.rb +101 -0
  44. data/spec/libraries/scim_patch_spec.rb +129 -45
  45. data/spec/models/scim_query_parser_spec.rb +5 -6
  46. metadata +107 -108
  47. data/app/controllers/concerns/scim_rails/response.rb +0 -94
  48. data/app/controllers/scim_rails/application_controller.rb +0 -72
  49. data/app/controllers/scim_rails/scim_users_controller.rb +0 -107
  50. data/app/models/scim_rails/authorize_api_request.rb +0 -40
  51. data/app/models/scim_rails/scim_query_parser.rb +0 -49
  52. data/lib/generators/scim_rails/USAGE +0 -8
  53. data/lib/generators/scim_rails/scim_rails_generator.rb +0 -7
  54. data/lib/scim_rails/encoder.rb +0 -25
  55. data/lib/scim_rails/engine.rb +0 -12
  56. data/lib/scim_rails/version.rb +0 -5
  57. data/lib/scim_rails.rb +0 -6
  58. data/spec/dummy/db/development.sqlite3 +0 -0
  59. data/spec/dummy/db/test.sqlite3 +0 -0
  60. data/spec/dummy/log/development.log +0 -0
  61. data/spec/dummy/log/test.log +0 -5770
  62. data/spec/dummy/put_group.http +0 -5
  63. data/spec/dummy/tmp/restart.txt +0 -0
  64. data/spec/lib/scim_rails/encoder_spec.rb +0 -62
  65. data/spec/libraries/scim_patch_operation_spec.rb +0 -116
@@ -0,0 +1,104 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Scimaenaga
4
+ class ScimUsersController < Scimaenaga::ApplicationController
5
+
6
+ def index
7
+ if params[:filter].present?
8
+ query = Scimaenaga::ScimQueryParser.new(
9
+ params[:filter], Scimaenaga.config.queryable_user_attributes
10
+ )
11
+
12
+ users = @company
13
+ .public_send(Scimaenaga.config.scim_users_scope)
14
+ .where(
15
+ "#{Scimaenaga.config.scim_users_model
16
+ .connection.quote_column_name(query.attribute)} #{query.operator} ?",
17
+ query.parameter
18
+ )
19
+ .order(Scimaenaga.config.scim_users_list_order)
20
+ else
21
+ users = @company
22
+ .public_send(Scimaenaga.config.scim_users_scope)
23
+ .order(Scimaenaga.config.scim_users_list_order)
24
+ end
25
+
26
+ counts = ScimCount.new(
27
+ start_index: params[:startIndex],
28
+ limit: params[:count],
29
+ total: users.count
30
+ )
31
+
32
+ json_scim_response(object: users, counts: counts)
33
+ end
34
+
35
+ def create
36
+ if Scimaenaga.config.scim_user_prevent_update_on_create
37
+ user = @company
38
+ .public_send(Scimaenaga.config.scim_users_scope)
39
+ .create!(permitted_user_params)
40
+ else
41
+ username_key = Scimaenaga.config.queryable_user_attributes[:userName]
42
+ find_by_username = {}
43
+ find_by_username[username_key] = permitted_user_params[username_key]
44
+ user = @company
45
+ .public_send(Scimaenaga.config.scim_users_scope)
46
+ .find_or_create_by(find_by_username)
47
+ user.update!(permitted_user_params)
48
+ end
49
+ json_scim_response(object: user, status: :created)
50
+ end
51
+
52
+ def show
53
+ user = @company.public_send(Scimaenaga.config.scim_users_scope).find(params[:id])
54
+ json_scim_response(object: user)
55
+ end
56
+
57
+ def put_update
58
+ user = @company.public_send(Scimaenaga.config.scim_users_scope).find(params[:id])
59
+ user.update!(permitted_user_params)
60
+ json_scim_response(object: user)
61
+ end
62
+
63
+ def patch_update
64
+ user = @company.public_send(Scimaenaga.config.scim_users_scope).find(params[:id])
65
+ patch = ScimPatch.new(params, :user)
66
+ patch.save(user)
67
+
68
+ json_scim_response(object: user)
69
+ end
70
+
71
+ def destroy
72
+ unless Scimaenaga.config.user_destroy_method
73
+ raise Scimaenaga::ExceptionHandler::InvalidConfiguration
74
+ end
75
+
76
+ user = @company.public_send(Scimaenaga.config.scim_users_scope).find(params[:id])
77
+ raise ActiveRecord::RecordNotFound unless user
78
+
79
+ begin
80
+ user.public_send(Scimaenaga.config.user_destroy_method)
81
+ rescue NoMethodError => e
82
+ raise Scimaenaga::ExceptionHandler::InvalidConfiguration, e.message
83
+ rescue ActiveRecord::RecordNotDestroyed => e
84
+ raise Scimaenaga::ExceptionHandler::InvalidRequest, e.message
85
+ rescue StandardError => e
86
+ raise Scimaenaga::ExceptionHandler::UnexpectedError, e.message
87
+ end
88
+
89
+ head :no_content
90
+ end
91
+
92
+ private
93
+
94
+ def permitted_user_params
95
+ Scimaenaga.config.mutable_user_attributes.each.with_object({}) do |attribute, hash|
96
+ hash[attribute] = find_value_for(attribute)
97
+ end
98
+ end
99
+
100
+ def controller_schema
101
+ Scimaenaga.config.mutable_user_attributes_schema
102
+ end
103
+ end
104
+ end
@@ -1,4 +1,4 @@
1
- module ScimRails
1
+ module Scimaenaga
2
2
  module ApplicationHelper
3
3
  end
4
4
  end
@@ -4,17 +4,24 @@
4
4
  class ScimPatch
5
5
  attr_accessor :operations
6
6
 
7
- def initialize(params, mutable_attributes_schema)
8
- unless params['schemas'] == ['urn:ietf:params:scim:api:messages:2.0:PatchOp']
9
- raise ScimRails::ExceptionHandler::UnsupportedPatchRequest
7
+ def initialize(params, resource_type)
8
+ if params['schemas'] != ['urn:ietf:params:scim:api:messages:2.0:PatchOp'] ||
9
+ params['Operations'].nil?
10
+ raise Scimaenaga::ExceptionHandler::UnsupportedPatchRequest
10
11
  end
11
- if params['Operations'].nil?
12
- raise ScimRails::ExceptionHandler::UnsupportedPatchRequest
12
+
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'])
13
17
  end
18
+ end
14
19
 
15
- @operations = params['Operations'].map do |operation|
16
- ScimPatchOperation.new(operation['op'], operation['path'], operation['value'],
17
- mutable_attributes_schema)
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)
18
25
  end
19
26
  end
20
27
 
@@ -28,6 +35,6 @@ class ScimPatch
28
35
  rescue ActiveRecord::RecordNotFound
29
36
  raise
30
37
  rescue StandardError
31
- raise ScimRails::ExceptionHandler::UnsupportedPatchRequest
38
+ raise Scimaenaga::ExceptionHandler::UnsupportedPatchRequest
32
39
  end
33
40
  end
@@ -2,157 +2,67 @@
2
2
 
3
3
  # Parse One of "Operations" in PATCH request
4
4
  class ScimPatchOperation
5
-
6
- # 1 Operation means 1 attribute change
7
- # If 1 value is specified, @operations has 1 "Operation" struct.
8
- # If 3 value is specified, @operations has 3 "Operation" struct.
9
- attr_accessor :operations
10
-
11
- Operation = Struct.new('Operation', :op, :path_scim, :path_sp, :value)
12
-
13
- def initialize(op, path, value, mutable_attributes_schema)
14
- op_downcase = op.downcase
15
-
16
- unless op_downcase.in? %w[add replace remove]
17
- raise ScimRails::ExceptionHandler::UnsupportedPatchRequest
5
+ attr_reader :op, :path_scim, :path_sp, :value
6
+
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 Scimaenaga::ExceptionHandler::UnsupportedPatchRequest
18
14
  end
19
15
 
20
- @operations = []
16
+ # define validate method in the inherited class
17
+ validate(op, path, value)
21
18
 
22
- # To handle request pattern A and B in the same way,
23
- # convert complex-value to path + single-value
24
- #
25
- # pattern A
26
- # {
27
- # "op": "replace",
28
- # "value": {
29
- # "displayName": "Suzuki Taro",
30
- # "name.givenName": "taro"
31
- # }
32
- # }
33
- # => [{path: displayName, value: "Suzuki Taro"}, {path: name.givenName, value: "taro"}]
34
- #
35
- # pattern B
36
- # [
37
- # {
38
- # "op": "replace",
39
- # "path": "displayName"
40
- # "value": "Suzuki Taro",
41
- # },
42
- # {
43
- # "op": "replace",
44
- # "path": "name.givenNAme"
45
- # "value": "taro",
46
- # },
47
- # ]
48
- if value.instance_of?(Hash) || value.instance_of?(ActionController::Parameters)
49
- create_multiple_operations(op_downcase, path, value, mutable_attributes_schema)
50
- else
51
- create_operation(op_downcase, path, value, mutable_attributes_schema)
52
- end
53
- end
19
+ @op = op
20
+ @value = value
21
+ @path_scim = parse_path_scim(path)
22
+ @path_sp = path_scim_to_path_sp(@path_scim)
54
23
 
55
- def save(model)
56
- @operations.each do |operation|
57
- apply_operation(model, operation)
58
- end
24
+ # define parse method in the inherited class
59
25
  end
60
26
 
61
27
  private
62
28
 
63
- def apply_operation(model, operation)
64
- if operation.path_scim == 'members' # Only members are supported for value is an array
65
- update_member_ids = operation.value.map do |v|
66
- v[ScimRails.config.group_member_relation_schema.keys.first].to_s
67
- end
68
-
69
- current_member_ids = model.public_send(
70
- ScimRails.config.group_member_relation_attribute
71
- ).map(&:to_s)
72
- case operation.op
73
- when :add
74
- member_ids = current_member_ids.concat(update_member_ids)
75
- when :replace
76
- member_ids = current_member_ids.concat(update_member_ids)
77
- when :remove
78
- member_ids = current_member_ids - update_member_ids
79
- end
80
-
81
- # Only the member addition process is saved by each ids
82
- model.public_send("#{ScimRails.config.group_member_relation_attribute}=",
83
- member_ids.uniq)
84
- return
85
- end
86
-
87
- case operation.op
88
- when :add, :replace
89
- model.attributes = { operation.path_sp => operation.value }
90
- when :remove
91
- model.attributes = { operation.path_sp => nil }
92
- end
93
- end
94
-
95
- def create_operation(op, path_scim, value, mutable_attributes_schema)
96
- path_sp = convert_path(path_scim, mutable_attributes_schema)
97
- value = convert_bool_if_string(value, path_scim)
98
- @operations << Operation.new(op.to_sym, path_scim, path_sp, value)
99
- end
100
-
101
- # convert hash value to 1 path + 1 value
102
- # each path is created by path_scim_base + key of value
103
- def create_multiple_operations(op, path_scim_base, hash_value, mutable_attributes_schema)
104
- hash_value.each do |k, v|
105
- # Typical request is path_scim_base = nil and value = complex-value:
106
- # {
107
- # "op": "replace",
108
- # "value": {
109
- # "displayName": "Taro Suzuki",
110
- # "name.givenName": "taro"
111
- # }
112
- # }
113
- path_scim = if path_scim_base.present?
114
- "#{path_scim_base}.#{k}"
115
- else
116
- k
117
- end
118
- create_operation(op, path_scim, v, mutable_attributes_schema)
119
- end
120
- end
121
-
122
- def convert_path(path, mutable_attributes_schema)
123
- return nil if path.nil?
124
-
125
- # For now, library does not support Multi-Valued Attributes properly.
126
- # examle:
127
- # path = 'emails[type eq "work"].value'
128
- # mutable_attributes_schema = {
129
- # emails: [
130
- # {
131
- # value: :mail_address,
132
- # }
133
- # ],
134
- # }
29
+ def parse_path_scim(path)
30
+ # 'emails[type eq "work"].value' is parsed as follows:
31
+ #
32
+ # {
33
+ # attribute: 'emails',
34
+ # filter: {
35
+ # attribute: 'type',
36
+ # operator: 'eq',
37
+ # parameter: 'work'
38
+ # },
39
+ # rest_path: ['value']
40
+ # }
135
41
  #
136
- # Library ignores filter conditions (like [type eq "work"])
137
- # and always uses the first element of the array
138
- dig_keys = path.gsub(/\[(.+?)\]/, '.0').split('.').map do |step|
139
- step == '0' ? 0 : step.to_sym
42
+ # This method suport only single operator
43
+
44
+ # path: emails.value
45
+ # filter_string: type eq "work"
46
+ path_str = path.dup
47
+ filter_string = path_str.slice!(/\[(.+?)\]/, 0)&.slice(/\[(.+?)\]/, 1)
48
+
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
+ }
140
63
  end
141
- mutable_attributes_schema.dig(*dig_keys)
142
- end
143
64
 
144
- def convert_bool_if_string(value, path)
145
- # This method correct value in requests from Azure AD according to SCIM.
146
- # When path is not active, do nothing and return
147
- return value if path != 'active'
148
-
149
- case value
150
- when 'true', 'True'
151
- return true
152
- when 'false', 'False'
153
- return false
154
- else
155
- return value
156
- end
65
+ path_scim
157
66
  end
67
+
158
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
@@ -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
+ Scimaenaga.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 Scimaenaga.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
+ Scimaenaga.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 Scimaenaga::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
+ Scimaenaga.config.mutable_user_attributes_schema.dig(*dig_keys)
51
+ end
52
+
53
+ end
@@ -1,4 +1,4 @@
1
- module ScimRails
1
+ module Scimaenaga
2
2
  class ApplicationRecord < ActiveRecord::Base
3
3
  self.abstract_class = true
4
4
  end
@@ -0,0 +1,39 @@
1
+ module Scimaenaga
2
+ class AuthorizeApiRequest
3
+
4
+ def initialize(searchable_attribute:, authentication_attribute:)
5
+ @searchable_attribute = searchable_attribute
6
+ @authentication_attribute = authentication_attribute
7
+
8
+ if searchable_attribute.blank? || authentication_attribute.blank?
9
+ raise Scimaenaga::ExceptionHandler::InvalidCredentials
10
+ end
11
+
12
+ @search_parameter = { Scimaenaga.config.basic_auth_model_searchable_attribute => @searchable_attribute }
13
+ end
14
+
15
+ def company
16
+ company = find_company
17
+ authorize(company)
18
+ company
19
+ end
20
+
21
+ private
22
+
23
+ attr_reader :authentication_attribute, :search_parameter, :searchable_attribute
24
+
25
+ def find_company
26
+ @company ||= Scimaenaga.config.basic_auth_model.find_by!(search_parameter)
27
+ rescue ActiveRecord::RecordNotFound
28
+ raise Scimaenaga::ExceptionHandler::InvalidCredentials
29
+ end
30
+
31
+ def authorize(authentication_model)
32
+ authorized = ActiveSupport::SecurityUtils.secure_compare(
33
+ authentication_model.public_send(Scimaenaga.config.basic_auth_model_authenticatable_attribute),
34
+ authentication_attribute
35
+ )
36
+ raise Scimaenaga::ExceptionHandler::InvalidCredentials unless authorized
37
+ end
38
+ end
39
+ end
@@ -1,4 +1,4 @@
1
- module ScimRails
1
+ module Scimaenaga
2
2
  class ScimCount
3
3
  include ActiveModel::Model
4
4
 
@@ -10,17 +10,21 @@ module ScimRails
10
10
 
11
11
  def limit
12
12
  return 100 if @limit.blank?
13
+
13
14
  validate_numericality(@limit)
14
15
  input = @limit.to_i
15
16
  raise if input < 1
17
+
16
18
  input
17
19
  end
18
20
 
19
21
  def start_index
20
22
  return 1 if @start_index.blank?
23
+
21
24
  validate_numericality(@start_index)
22
25
  input = @start_index.to_i
23
26
  return 1 if input < 1
27
+
24
28
  input
25
29
  end
26
30
 
@@ -30,9 +34,9 @@ module ScimRails
30
34
 
31
35
  private
32
36
 
33
- def validate_numericality(input)
34
- raise unless input.match?(/\A\d+\z/)
35
- end
37
+ def validate_numericality(input)
38
+ raise unless input.match?(/\A\d+\z/)
39
+ end
36
40
 
37
41
  end
38
42
  end