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.
- checksums.yaml +4 -4
- data/README.md +22 -7
- data/Rakefile +6 -8
- data/app/controllers/concerns/{scim_rails → scimaenaga}/exception_handler.rb +47 -37
- data/app/controllers/concerns/scimaenaga/response.rb +94 -0
- data/app/controllers/scimaenaga/application_controller.rb +72 -0
- data/app/controllers/{scim_rails → scimaenaga}/scim_groups_controller.rb +26 -26
- data/app/controllers/scimaenaga/scim_schemas_controller.rb +42 -0
- data/app/controllers/scimaenaga/scim_users_controller.rb +104 -0
- data/app/helpers/{scim_rails → scimaenaga}/application_helper.rb +1 -1
- data/app/libraries/scim_patch.rb +16 -9
- data/app/libraries/scim_patch_operation.rb +51 -141
- 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/app/models/{scim_rails → scimaenaga}/application_record.rb +1 -1
- data/app/models/scimaenaga/authorize_api_request.rb +39 -0
- data/app/models/{scim_rails → scimaenaga}/scim_count.rb +8 -4
- data/app/models/scimaenaga/scim_query_parser.rb +49 -0
- data/config/routes.rb +15 -13
- data/lib/generators/scimaenaga/USAGE +8 -0
- data/lib/generators/scimaenaga/scimaenaga_generator.rb +7 -0
- data/lib/generators/{scim_rails → scimaenaga}/templates/initializer.rb +128 -22
- data/lib/{scim_rails → scimaenaga}/config.rb +9 -7
- data/lib/scimaenaga/encoder.rb +27 -0
- data/lib/scimaenaga/engine.rb +12 -0
- data/lib/scimaenaga/version.rb +5 -0
- data/lib/scimaenaga.rb +6 -0
- data/lib/tasks/{scim_rails_tasks.rake → scimaenaga_tasks.rake} +1 -1
- data/spec/controllers/{scim_rails → scimaenaga}/scim_groups_controller_spec.rb +8 -8
- data/spec/controllers/{scim_rails → scimaenaga}/scim_groups_request_spec.rb +18 -18
- data/spec/controllers/scimaenaga/scim_schemas_controller_spec.rb +238 -0
- data/spec/controllers/scimaenaga/scim_schemas_request_spec.rb +39 -0
- data/spec/controllers/{scim_rails → scimaenaga}/scim_users_controller_spec.rb +14 -15
- data/spec/controllers/{scim_rails → scimaenaga}/scim_users_request_spec.rb +20 -20
- data/spec/dummy/app/assets/config/manifest.js +1 -1
- data/spec/dummy/config/application.rb +1 -2
- data/spec/dummy/config/initializers/{scim_rails_config.rb → scimaenaga_config.rb} +25 -25
- data/spec/dummy/config/routes.rb +1 -1
- data/spec/factories/company.rb +3 -3
- data/spec/lib/scimaenaga/encoder_spec.rb +64 -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 +129 -45
- data/spec/models/scim_query_parser_spec.rb +5 -6
- metadata +107 -108
- data/app/controllers/concerns/scim_rails/response.rb +0 -94
- data/app/controllers/scim_rails/application_controller.rb +0 -72
- data/app/controllers/scim_rails/scim_users_controller.rb +0 -107
- data/app/models/scim_rails/authorize_api_request.rb +0 -40
- data/app/models/scim_rails/scim_query_parser.rb +0 -49
- data/lib/generators/scim_rails/USAGE +0 -8
- data/lib/generators/scim_rails/scim_rails_generator.rb +0 -7
- data/lib/scim_rails/encoder.rb +0 -25
- data/lib/scim_rails/engine.rb +0 -12
- data/lib/scim_rails/version.rb +0 -5
- data/lib/scim_rails.rb +0 -6
- 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 -5770
- data/spec/dummy/put_group.http +0 -5
- data/spec/dummy/tmp/restart.txt +0 -0
- data/spec/lib/scim_rails/encoder_spec.rb +0 -62
- 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
|
data/app/libraries/scim_patch.rb
CHANGED
@@ -4,17 +4,24 @@
|
|
4
4
|
class ScimPatch
|
5
5
|
attr_accessor :operations
|
6
6
|
|
7
|
-
def initialize(params,
|
8
|
-
|
9
|
-
|
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
|
-
|
12
|
-
|
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
|
-
|
16
|
-
|
17
|
-
|
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
|
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
|
-
|
7
|
-
#
|
8
|
-
#
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
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
|
-
|
16
|
+
# define validate method in the inherited class
|
17
|
+
validate(op, path, value)
|
21
18
|
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
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
|
-
|
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
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
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
|
-
#
|
137
|
-
|
138
|
-
|
139
|
-
|
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
|
-
|
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
|
@@ -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
|
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
|
-
|
34
|
-
|
35
|
-
|
37
|
+
def validate_numericality(input)
|
38
|
+
raise unless input.match?(/\A\d+\z/)
|
39
|
+
end
|
36
40
|
|
37
41
|
end
|
38
42
|
end
|