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