scimaenaga 0.7.0 → 0.9.1

Sign up to get free protection for your applications and to get access to all the features.
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