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