scimaenaga 0.6.2 → 0.7.0
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/app/controllers/concerns/scim_rails/exception_handler.rb +43 -1
- data/app/controllers/scim_rails/scim_groups_controller.rb +13 -2
- data/app/controllers/scim_rails/scim_users_controller.rb +21 -0
- data/app/libraries/scim_patch.rb +1 -2
- data/app/libraries/scim_patch_operation.rb +105 -36
- data/config/routes.rb +1 -0
- data/lib/scim_rails/config.rb +1 -0
- data/lib/scim_rails/version.rb +1 -1
- data/spec/controllers/scim_rails/scim_groups_controller_spec.rb +25 -7
- data/spec/controllers/scim_rails/scim_users_controller_spec.rb +361 -220
- data/spec/dummy/app/models/user.rb +9 -0
- data/spec/dummy/config/initializers/scim_rails_config.rb +3 -0
- data/spec/dummy/db/development.sqlite3 +0 -0
- data/spec/dummy/db/migrate/20220131090107_add_deletable_to_users.rb +5 -0
- data/spec/dummy/db/schema.rb +2 -1
- data/spec/dummy/db/seeds.rb +10 -1
- data/spec/dummy/db/test.sqlite3 +0 -0
- data/spec/dummy/log/development.log +0 -0
- data/spec/dummy/log/test.log +5770 -0
- data/spec/dummy/put_group.http +5 -0
- data/spec/dummy/tmp/restart.txt +0 -0
- data/spec/factories/user.rb +2 -0
- data/spec/libraries/scim_patch_operation_spec.rb +51 -31
- data/spec/libraries/scim_patch_spec.rb +31 -33
- metadata +81 -67
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 6d2d55daa03a9b1e4771b1f2e1287a75075514d57faeb0075ea8784a7dd8cc9e
|
4
|
+
data.tar.gz: b4a38c2c629cce871703a2a1874c3be93e34d55f0d5eacadae8da9b8c5e5c438
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 43e2ec416e2f9bad5c185042aff3e6fd576437d3d9bff5ac6ad5d1f930246fa0b59b12e1940a5498e386c8c8f385a245d531907e7ab5850cc8ebc5a7c35df607
|
7
|
+
data.tar.gz: 284bead26bc660ca0d4cc229dd5f13102501904b7658e3e780f5ea5657eb367c52cd5399d4a29dcd20af66eee19ce4592af7defa62c2f182bb7b23f76ebe6a31
|
@@ -7,6 +7,9 @@ module ScimRails
|
|
7
7
|
class InvalidCredentials < StandardError
|
8
8
|
end
|
9
9
|
|
10
|
+
class InvalidRequest < StandardError
|
11
|
+
end
|
12
|
+
|
10
13
|
class InvalidQuery < StandardError
|
11
14
|
end
|
12
15
|
|
@@ -16,6 +19,12 @@ module ScimRails
|
|
16
19
|
class UnsupportedDeleteRequest < StandardError
|
17
20
|
end
|
18
21
|
|
22
|
+
class InvalidConfiguration < StandardError
|
23
|
+
end
|
24
|
+
|
25
|
+
class UnexpectedError < StandardError
|
26
|
+
end
|
27
|
+
|
19
28
|
included do
|
20
29
|
if Rails.env.production?
|
21
30
|
rescue_from StandardError do |exception|
|
@@ -47,6 +56,17 @@ module ScimRails
|
|
47
56
|
)
|
48
57
|
end
|
49
58
|
|
59
|
+
rescue_from ScimRails::ExceptionHandler::InvalidRequest do |e|
|
60
|
+
json_response(
|
61
|
+
{
|
62
|
+
schemas: ["urn:ietf:params:scim:api:messages:2.0:Error"],
|
63
|
+
detail: "Invalid request. #{e.message}",
|
64
|
+
status: "400"
|
65
|
+
},
|
66
|
+
:bad_request
|
67
|
+
)
|
68
|
+
end
|
69
|
+
|
50
70
|
rescue_from ScimRails::ExceptionHandler::InvalidQuery do
|
51
71
|
json_response(
|
52
72
|
{
|
@@ -63,7 +83,7 @@ module ScimRails
|
|
63
83
|
json_response(
|
64
84
|
{
|
65
85
|
schemas: ["urn:ietf:params:scim:api:messages:2.0:Error"],
|
66
|
-
detail: "Invalid PATCH request.
|
86
|
+
detail: "Invalid PATCH request.",
|
67
87
|
status: "422"
|
68
88
|
},
|
69
89
|
:unprocessable_entity
|
@@ -81,6 +101,28 @@ module ScimRails
|
|
81
101
|
)
|
82
102
|
end
|
83
103
|
|
104
|
+
rescue_from ScimRails::ExceptionHandler::InvalidConfiguration do |e|
|
105
|
+
json_response(
|
106
|
+
{
|
107
|
+
schemas: ["urn:ietf:params:scim:api:messages:2.0:Error"],
|
108
|
+
detail: "Invalid configuration. #{e.message}",
|
109
|
+
status: "500"
|
110
|
+
},
|
111
|
+
:internal_server_error
|
112
|
+
)
|
113
|
+
end
|
114
|
+
|
115
|
+
rescue_from ScimRails::ExceptionHandler::UnexpectedError do |e|
|
116
|
+
json_response(
|
117
|
+
{
|
118
|
+
schemas: ["urn:ietf:params:scim:api:messages:2.0:Error"],
|
119
|
+
detail: "Unexpected Error. #{e.message}",
|
120
|
+
status: "500"
|
121
|
+
},
|
122
|
+
:internal_server_error
|
123
|
+
)
|
124
|
+
end
|
125
|
+
|
84
126
|
rescue_from ActiveRecord::RecordNotFound do |e|
|
85
127
|
json_response(
|
86
128
|
{
|
@@ -68,13 +68,24 @@ module ScimRails
|
|
68
68
|
|
69
69
|
def destroy
|
70
70
|
unless ScimRails.config.group_destroy_method
|
71
|
-
raise ScimRails::ExceptionHandler::
|
71
|
+
raise ScimRails::ExceptionHandler::InvalidConfiguration
|
72
72
|
end
|
73
73
|
|
74
74
|
group = @company
|
75
75
|
.public_send(ScimRails.config.scim_groups_scope)
|
76
76
|
.find(params[:id])
|
77
|
-
group
|
77
|
+
raise ActiveRecord::RecordNotFound unless group
|
78
|
+
|
79
|
+
begin
|
80
|
+
group.public_send(ScimRails.config.group_destroy_method)
|
81
|
+
rescue NoMethodError => e
|
82
|
+
raise ScimRails::ExceptionHandler::InvalidConfiguration, e.message
|
83
|
+
rescue ActiveRecord::RecordNotDestroyed => e
|
84
|
+
raise ScimRails::ExceptionHandler::InvalidRequest, e.message
|
85
|
+
rescue => e
|
86
|
+
raise ScimRails::ExceptionHandler::UnexpectedError, e.message
|
87
|
+
end
|
88
|
+
|
78
89
|
head :no_content
|
79
90
|
end
|
80
91
|
|
@@ -71,6 +71,27 @@ module ScimRails
|
|
71
71
|
json_scim_response(object: user)
|
72
72
|
end
|
73
73
|
|
74
|
+
def destroy
|
75
|
+
unless ScimRails.config.user_destroy_method
|
76
|
+
raise ScimRails::ExceptionHandler::InvalidConfiguration
|
77
|
+
end
|
78
|
+
|
79
|
+
user = @company.public_send(ScimRails.config.scim_users_scope).find(params[:id])
|
80
|
+
raise ActiveRecord::RecordNotFound unless user
|
81
|
+
|
82
|
+
begin
|
83
|
+
user.public_send(ScimRails.config.user_destroy_method)
|
84
|
+
rescue NoMethodError => e
|
85
|
+
raise ScimRails::ExceptionHandler::InvalidConfiguration, e.message
|
86
|
+
rescue ActiveRecord::RecordNotDestroyed => e
|
87
|
+
raise ScimRails::ExceptionHandler::InvalidRequest, e.message
|
88
|
+
rescue => e
|
89
|
+
raise ScimRails::ExceptionHandler::UnexpectedError, e.message
|
90
|
+
end
|
91
|
+
|
92
|
+
head :no_content
|
93
|
+
end
|
94
|
+
|
74
95
|
private
|
75
96
|
|
76
97
|
def permitted_user_params
|
data/app/libraries/scim_patch.rb
CHANGED
@@ -5,9 +5,8 @@ class ScimPatch
|
|
5
5
|
attr_accessor :operations
|
6
6
|
|
7
7
|
def initialize(params, mutable_attributes_schema)
|
8
|
-
# FIXME: raise proper error.
|
9
8
|
unless params['schemas'] == ['urn:ietf:params:scim:api:messages:2.0:PatchOp']
|
10
|
-
raise
|
9
|
+
raise ScimRails::ExceptionHandler::UnsupportedPatchRequest
|
11
10
|
end
|
12
11
|
if params['Operations'].nil?
|
13
12
|
raise ScimRails::ExceptionHandler::UnsupportedPatchRequest
|
@@ -2,57 +2,126 @@
|
|
2
2
|
|
3
3
|
# Parse One of "Operations" in PATCH request
|
4
4
|
class ScimPatchOperation
|
5
|
-
|
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)
|
6
12
|
|
7
13
|
def initialize(op, path, value, mutable_attributes_schema)
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
14
|
+
op_downcase = op.downcase
|
15
|
+
|
16
|
+
unless op_downcase.in? %w[add replace remove]
|
17
|
+
raise ScimRails::ExceptionHandler::UnsupportedPatchRequest
|
18
|
+
end
|
19
|
+
|
20
|
+
@operations = []
|
21
|
+
|
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
|
20
53
|
end
|
21
54
|
|
22
55
|
def save(model)
|
23
|
-
|
24
|
-
|
25
|
-
|
56
|
+
@operations.each do |operation|
|
57
|
+
apply_operation(model, operation)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
private
|
62
|
+
|
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
|
26
85
|
end
|
27
86
|
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
when :add
|
32
|
-
member_ids = current_member_ids.concat(update_member_ids)
|
33
|
-
when :replace
|
34
|
-
member_ids = current_member_ids.concat(update_member_ids)
|
87
|
+
case operation.op
|
88
|
+
when :add, :replace
|
89
|
+
model.attributes = { operation.path_sp => operation.value }
|
35
90
|
when :remove
|
36
|
-
|
91
|
+
model.attributes = { operation.path_sp => nil }
|
37
92
|
end
|
38
|
-
|
39
|
-
# Only the member addition process is saved by each ids
|
40
|
-
model.public_send("#{ScimRails.config.group_member_relation_attribute}=",
|
41
|
-
member_ids.uniq)
|
42
|
-
return
|
43
93
|
end
|
44
94
|
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
model.attributes = { @path_sp => nil }
|
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)
|
50
99
|
end
|
51
|
-
end
|
52
100
|
|
53
|
-
|
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
|
54
121
|
|
55
122
|
def convert_path(path, mutable_attributes_schema)
|
123
|
+
return nil if path.nil?
|
124
|
+
|
56
125
|
# For now, library does not support Multi-Valued Attributes properly.
|
57
126
|
# examle:
|
58
127
|
# path = 'emails[type eq "work"].value'
|
data/config/routes.rb
CHANGED
@@ -4,6 +4,7 @@ ScimRails::Engine.routes.draw do
|
|
4
4
|
get 'scim/v2/Users/:id', action: :show, controller: 'scim_users'
|
5
5
|
put 'scim/v2/Users/:id', action: :put_update, controller: 'scim_users'
|
6
6
|
patch 'scim/v2/Users/:id', action: :patch_update, controller: 'scim_users'
|
7
|
+
delete 'scim/v2/Users/:id', action: :destroy, controller: 'scim_users'
|
7
8
|
get 'scim/v2/Groups', action: :index, controller: 'scim_groups'
|
8
9
|
post 'scim/v2/Groups', action: :create, controller: 'scim_groups'
|
9
10
|
get 'scim/v2/Groups/:id', action: :show, controller: 'scim_groups'
|
data/lib/scim_rails/config.rb
CHANGED
data/lib/scim_rails/version.rb
CHANGED
@@ -505,12 +505,6 @@ RSpec.describe ScimRails::ScimGroupsController, type: :controller do
|
|
505
505
|
end
|
506
506
|
|
507
507
|
context 'when Group destroy method is configured' do
|
508
|
-
before do
|
509
|
-
allow(ScimRails.config).to(
|
510
|
-
receive(:group_destroy_method).and_return(:destroy!)
|
511
|
-
)
|
512
|
-
end
|
513
|
-
|
514
508
|
it 'returns empty response' do
|
515
509
|
delete :destroy, params: { id: 1 }, as: :json
|
516
510
|
|
@@ -557,7 +551,31 @@ RSpec.describe ScimRails::ScimGroupsController, type: :controller do
|
|
557
551
|
delete :destroy, params: { id: 1 }, as: :json
|
558
552
|
end.not_to change { company.groups.reload.count }.from(1)
|
559
553
|
|
560
|
-
expect(response.status).to eq
|
554
|
+
expect(response.status).to eq 500
|
555
|
+
end
|
556
|
+
end
|
557
|
+
|
558
|
+
context 'when Group destroy method is invalid' do
|
559
|
+
it 'does not delete Group' do
|
560
|
+
allow(ScimRails.config).to(
|
561
|
+
receive(:group_destroy_method).and_return('destory!')
|
562
|
+
)
|
563
|
+
|
564
|
+
expect do
|
565
|
+
delete :destroy, params: { id: 1 }, as: :json
|
566
|
+
end.not_to change { company.groups.reload.count }.from(1)
|
567
|
+
|
568
|
+
expect(response.status).to eq 500
|
569
|
+
end
|
570
|
+
end
|
571
|
+
|
572
|
+
context 'whenr target Group is not found' do
|
573
|
+
it 'return 404 not found' do
|
574
|
+
expect do
|
575
|
+
delete :destroy, params: { id: 999999 }, as: :json
|
576
|
+
end.not_to change { company.groups.reload.count }.from(1)
|
577
|
+
|
578
|
+
expect(response.status).to eq 404
|
561
579
|
end
|
562
580
|
end
|
563
581
|
end
|