shamu 0.0.18 → 0.0.19
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/.rubocop.yml +1 -1
- data/Gemfile.lock +2 -2
- data/lib/shamu/attributes.rb +5 -0
- data/lib/shamu/auditing/support.rb +2 -2
- data/lib/shamu/auditing/transaction.rb +6 -0
- data/lib/shamu/entities/active_record.rb +6 -2
- data/lib/shamu/entities/active_record_soft_destroy.rb +11 -0
- data/lib/shamu/entities/entity.rb +23 -0
- data/lib/shamu/entities/list_scope/window_paging.rb +1 -1
- data/lib/shamu/rspec/matchers.rb +23 -1
- data/lib/shamu/security/active_record_policy.rb +3 -2
- data/lib/shamu/security/no_policy.rb +2 -2
- data/lib/shamu/security/policy.rb +77 -10
- data/lib/shamu/security/policy_rule.rb +5 -1
- data/lib/shamu/security/roles.rb +4 -2
- data/lib/shamu/security/support.rb +16 -2
- data/lib/shamu/security.rb +2 -1
- data/lib/shamu/services/active_record.rb +11 -0
- data/lib/shamu/services/active_record_crud.rb +3 -3
- data/lib/shamu/services/request.rb +13 -11
- data/lib/shamu/services/request_support.rb +1 -1
- data/lib/shamu/version.rb +1 -1
- data/spec/lib/shamu/entities/entity_spec.rb +24 -4
- data/spec/lib/shamu/security/active_record_policy_spec.rb +9 -2
- data/spec/lib/shamu/security/policy_spec.rb +31 -9
- data/spec/lib/shamu/services/active_record_crud_spec.rb +3 -2
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 9cde1e60d3798de927cee92894d391d3387e1619
|
4
|
+
data.tar.gz: 204c11e52933dd2d88acb0e812d9e5183d3c09c0
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: cb22139780ad29697e690025ad740b2cb1673b3486f854744182972f4ff382a873c7f362a59936b39c82788742c5e486237d7b29c4346acfd3f0a2532e97f582
|
7
|
+
data.tar.gz: 7d06c76f61740a24724d4261a3b72d9117a5e5f36d6c600168271981c3c45ce23d1e637a9b677809d3996229e0df616e573d3994997ecb54a20310bfa349c65f
|
data/.rubocop.yml
CHANGED
data/Gemfile.lock
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
shamu (0.0.
|
4
|
+
shamu (0.0.19)
|
5
5
|
activemodel (>= 5.0)
|
6
6
|
activesupport (>= 5.0)
|
7
7
|
crc32 (~> 1)
|
@@ -114,7 +114,7 @@ GEM
|
|
114
114
|
minitest (5.10.2)
|
115
115
|
multi_json (1.12.1)
|
116
116
|
nenv (0.3.0)
|
117
|
-
nio4r (2.
|
117
|
+
nio4r (2.1.0)
|
118
118
|
nokogiri (1.7.2)
|
119
119
|
mini_portile2 (~> 2.1.0)
|
120
120
|
notiffany (0.1.1)
|
data/lib/shamu/attributes.rb
CHANGED
@@ -51,6 +51,11 @@ module Shamu
|
|
51
51
|
end
|
52
52
|
end
|
53
53
|
|
54
|
+
# @return [Hash] a hash with the keys for each of the given names.
|
55
|
+
def slice( *names )
|
56
|
+
to_attributes only: names
|
57
|
+
end
|
58
|
+
|
54
59
|
# Indicates if the object has an attribute with the given name. Aliased to
|
55
60
|
# {#key?} to make the object look like a Hash.
|
56
61
|
def attribute?( name )
|
@@ -51,7 +51,7 @@ module Shamu
|
|
51
51
|
# should call {Transaction#append_entity} to include any parent
|
52
52
|
# entities in the entity path.
|
53
53
|
# @yieldreturn [Services::Result]
|
54
|
-
def audit_request( request, action: :smart, &block )
|
54
|
+
def audit_request( request, action: :smart, &block ) # rubocop:disable Metrics/PerceivedComplexity
|
55
55
|
transaction = Transaction.new \
|
56
56
|
user_id_chain: auditing_security_principal.user_id_chain,
|
57
57
|
changes: request.to_attributes( only: request.assigned_attributes ),
|
@@ -63,7 +63,7 @@ module Shamu
|
|
63
63
|
if result.valid?
|
64
64
|
if result.entity
|
65
65
|
transaction.append_entity result.entity
|
66
|
-
elsif request.respond_to?( :id ) && defined? entity_class
|
66
|
+
elsif !transaction.entities? && request.respond_to?( :id ) && defined? entity_class
|
67
67
|
transaction.append_entity [ entity_class, request.id ]
|
68
68
|
end
|
69
69
|
auditing_service.commit( transaction )
|
@@ -27,12 +27,12 @@ module Shamu
|
|
27
27
|
# @return [ActiveRecord::Relation]
|
28
28
|
def by_list_scope( scope )
|
29
29
|
criteria = all
|
30
|
-
criteria = apply_custom_list_scope( criteria, scope )
|
31
30
|
criteria = apply_paging_scope( criteria, scope ) if scope.respond_to?( :paged? )
|
32
31
|
criteria = apply_scoped_paging_scope( criteria, scope ) if scope.respond_to?( :scoped_page? )
|
33
32
|
criteria = apply_window_paging_scope( criteria, scope ) if scope.respond_to?( :window_paged? )
|
34
33
|
criteria = apply_dates_scope( criteria, scope ) if scope.respond_to?( :dated? )
|
35
34
|
criteria = apply_sorting_scope( criteria, scope ) if scope.respond_to?( :sorted? )
|
35
|
+
criteria = apply_custom_list_scope( criteria, scope )
|
36
36
|
criteria
|
37
37
|
end
|
38
38
|
|
@@ -106,12 +106,16 @@ module Shamu
|
|
106
106
|
def apply_custom_list_scope( criteria, scope )
|
107
107
|
custom_list_scope_attributes( scope ).each do |name|
|
108
108
|
scope_name = :"by_#{ name }"
|
109
|
+
apply_name = :"apply_#{ name }_list_scope"
|
110
|
+
|
109
111
|
if criteria.respond_to?( scope_name )
|
110
112
|
value = scope.send( name )
|
111
113
|
criteria = criteria.send scope_name, value if value.present?
|
114
|
+
elsif criteria.respond_to?( apply_name )
|
115
|
+
criteria = criteria.send apply_name, criteria, scope
|
112
116
|
else
|
113
117
|
# rubocop:disable Metrics/LineLength
|
114
|
-
fail ArgumentError, "Cannot apply '#{ name }' filter from #{ scope.class.name }. Add 'scope :#{ scope_name }, ->( #{ name } ) { ... }' to #{
|
118
|
+
fail ArgumentError, "Cannot apply '#{ name }' filter from #{ scope.class.name }. Add 'scope :#{ scope_name }, ->( #{ name } ) { ... }' or 'def self.#{ apply_name }( criteria, scope )' to #{ self.name }"
|
115
119
|
end
|
116
120
|
end
|
117
121
|
|
@@ -40,6 +40,17 @@ module Shamu
|
|
40
40
|
# Exclude destroyed records by default.
|
41
41
|
default_scope { except_destroyed }
|
42
42
|
|
43
|
+
# Apply list scoping that includes targeting `destroyed` state.
|
44
|
+
def self.apply_destroyed_list_scope( criteria, scope )
|
45
|
+
return criteria if scope.destroyed.nil?
|
46
|
+
|
47
|
+
if scope.destroyed
|
48
|
+
criteria.destroyed
|
49
|
+
else
|
50
|
+
criteria.except_destroyed
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
43
54
|
#
|
44
55
|
# @!endgroup Scopes
|
45
56
|
|
@@ -114,6 +114,29 @@ module Shamu
|
|
114
114
|
self
|
115
115
|
end
|
116
116
|
|
117
|
+
# Redact attributes on the entity.
|
118
|
+
#
|
119
|
+
# @param [Array<Symbol>,Hash] attributes to redact on the entity. Either
|
120
|
+
# a list of attributes to set to nil or a hash of attributes with their
|
121
|
+
# values.
|
122
|
+
def redact!( *attributes )
|
123
|
+
hash =
|
124
|
+
if attributes.first.is_a?( Symbol )
|
125
|
+
Hash[ attributes.zip( [ nil ] * attributes.length ) ]
|
126
|
+
else
|
127
|
+
attributes.first
|
128
|
+
end
|
129
|
+
|
130
|
+
assign_attributes hash
|
131
|
+
self
|
132
|
+
end
|
133
|
+
|
134
|
+
# @return [Entity] a modified version of the entity with the given
|
135
|
+
# attributes redacted.
|
136
|
+
def redact( attributes )
|
137
|
+
dup.redact!( attributes )
|
138
|
+
end
|
139
|
+
|
117
140
|
private
|
118
141
|
|
119
142
|
def serialize_attribute?( name, options )
|
@@ -35,7 +35,7 @@ module Shamu
|
|
35
35
|
#
|
36
36
|
# @!endgroup Attributes
|
37
37
|
|
38
|
-
def self.included( base ) # rubocop:disable Metrics/
|
38
|
+
def self.included( base ) # rubocop:disable Metrics/MethodLength
|
39
39
|
super
|
40
40
|
|
41
41
|
base.attribute :first, coerce: :to_i, default: ->() { default_first }
|
data/lib/shamu/rspec/matchers.rb
CHANGED
@@ -1,3 +1,25 @@
|
|
1
|
+
RSpec::Matchers.define :raise_access_denied do |_expected|
|
2
|
+
def supports_block_expectations?
|
3
|
+
true
|
4
|
+
end
|
5
|
+
|
6
|
+
match do |actual|
|
7
|
+
begin
|
8
|
+
subject && subject.stub(:access_denied) do |exception|
|
9
|
+
raise exception
|
10
|
+
end
|
11
|
+
|
12
|
+
actual.call
|
13
|
+
false
|
14
|
+
rescue CanCan::AccessDenied
|
15
|
+
true
|
16
|
+
rescue Services::Security::AccessDeniedError
|
17
|
+
true
|
18
|
+
rescue
|
19
|
+
false
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
1
23
|
|
2
24
|
RSpec::Matchers.define :be_permitted_to do |*args|
|
3
25
|
|
@@ -41,4 +63,4 @@ RSpec::Matchers.define :absolutely_be_permitted_to do |*args|
|
|
41
63
|
match do |policy|
|
42
64
|
policy.permit?( *args ) == :yes
|
43
65
|
end
|
44
|
-
end
|
66
|
+
end
|
@@ -47,10 +47,11 @@ module Shamu
|
|
47
47
|
# when applying the refinement.
|
48
48
|
# @return [ActiveRecord::Relation] the refined relation.
|
49
49
|
def refine_relation( action, relation, additional_context = nil )
|
50
|
+
resolve_permissions
|
50
51
|
refined = false
|
51
52
|
|
52
53
|
refinements.each do |refinement|
|
53
|
-
if refinement.match?( action, relation )
|
54
|
+
if refinement.match?( action, relation, additional_context )
|
54
55
|
refined = true
|
55
56
|
relation = refinement.apply( relation, additional_context ) || relation
|
56
57
|
end
|
@@ -103,4 +104,4 @@ module Shamu
|
|
103
104
|
|
104
105
|
end
|
105
106
|
end
|
106
|
-
end
|
107
|
+
end
|
@@ -3,7 +3,7 @@ module Shamu
|
|
3
3
|
|
4
4
|
# Used in specs and service to service delegated requests to effectively
|
5
5
|
# offer no policy and permit all actions.
|
6
|
-
class NoPolicy
|
6
|
+
class NoPolicy < Policy
|
7
7
|
|
8
8
|
# (see Policy#permit?)
|
9
9
|
def permit?( * )
|
@@ -12,4 +12,4 @@ module Shamu
|
|
12
12
|
|
13
13
|
end
|
14
14
|
end
|
15
|
-
end
|
15
|
+
end
|
@@ -42,17 +42,24 @@ module Shamu
|
|
42
42
|
|
43
43
|
# @!attribute
|
44
44
|
# @return [Principal] principal holding user identity and access credentials.
|
45
|
-
|
45
|
+
attr_reader :principal
|
46
46
|
|
47
47
|
# @!attribute
|
48
48
|
# @return [Array<Roles>] roles that have been granted to the {#principal}.
|
49
|
-
|
49
|
+
attr_reader :roles
|
50
|
+
|
51
|
+
# @!attribute
|
52
|
+
# @return [Array<Integer>] additional user ids that the {#principal} is
|
53
|
+
# may act on behalf of.
|
54
|
+
attr_reader :related_user_ids
|
55
|
+
|
50
56
|
#
|
51
57
|
# @!endgroup Dependencies
|
52
58
|
|
53
|
-
def initialize( principal: nil, roles: nil )
|
54
|
-
@principal
|
55
|
-
@roles
|
59
|
+
def initialize( principal: nil, roles: nil, related_user_ids: nil )
|
60
|
+
@principal = principal || Principal.new
|
61
|
+
@roles = roles || []
|
62
|
+
@related_user_ids = Array.wrap( related_user_ids )
|
56
63
|
end
|
57
64
|
|
58
65
|
# Authorize the given `action` on the given resource. If it is not
|
@@ -101,7 +108,7 @@ module Shamu
|
|
101
108
|
def rules
|
102
109
|
@rules ||= begin
|
103
110
|
@rules = []
|
104
|
-
|
111
|
+
resolve_permissions
|
105
112
|
@rules
|
106
113
|
end
|
107
114
|
end
|
@@ -123,11 +130,20 @@ module Shamu
|
|
123
130
|
def principal_roles
|
124
131
|
@principal_roles ||= begin
|
125
132
|
expanded = self.class.expand_roles( *roles )
|
126
|
-
expanded << :
|
133
|
+
expanded << :authenticated if principal.user_id && self.class.role_defined?( :authenticated )
|
127
134
|
expanded
|
128
135
|
end
|
129
136
|
end
|
130
137
|
|
138
|
+
# @!visibility public
|
139
|
+
#
|
140
|
+
# @param [Integer] id of the candidate user.
|
141
|
+
# @return [Boolean] true if the given id is one of the authorized user
|
142
|
+
# ids on the principal.
|
143
|
+
def is_principal?( id ) # rubocop:disable Style/PredicateName
|
144
|
+
principal.try( :user_id ) == id || related_user_ids.include?( id )
|
145
|
+
end
|
146
|
+
|
131
147
|
# ============================================================================
|
132
148
|
# @!group DSL
|
133
149
|
#
|
@@ -155,9 +171,24 @@ module Shamu
|
|
155
171
|
#
|
156
172
|
# @return [void]
|
157
173
|
def permissions
|
158
|
-
|
174
|
+
if respond_to?( :anonymous_permissions, true ) && respond_to?( :authenticated_permissions, true )
|
175
|
+
if principal.user_id
|
176
|
+
authenticated_permissions
|
177
|
+
else
|
178
|
+
anonymous_permissions
|
179
|
+
end
|
180
|
+
else
|
181
|
+
fail IncompleteSetupError, "Permissions have not been defined. Add a private `permissions` method to #{ self.class.name }" # rubocop:disable Metrics/LineLength
|
182
|
+
end
|
159
183
|
end
|
160
184
|
|
185
|
+
# Makes sure the {#permissions} method is invoked only once.
|
186
|
+
def resolve_permissions
|
187
|
+
return if @permissions_resolved
|
188
|
+
@permissions_resolved = true
|
189
|
+
permissions
|
190
|
+
end
|
191
|
+
|
161
192
|
# @!visibility public
|
162
193
|
#
|
163
194
|
# Permit one or more `actions` to be performed on a given `resource`.
|
@@ -167,6 +198,8 @@ module Shamu
|
|
167
198
|
# called if the resource offered to {#permit?} is a Class or Module.
|
168
199
|
#
|
169
200
|
# @example
|
201
|
+
# end
|
202
|
+
# end
|
170
203
|
# permit :read, UserEntity
|
171
204
|
# permit :show, :dashboard
|
172
205
|
# permit :update, UserEntity do |user|
|
@@ -185,8 +218,9 @@ module Shamu
|
|
185
218
|
# @yieldparam [Object] additional_context offered to {#permit?}.
|
186
219
|
# @yieldreturn [:yes, :maybe, false] see {#permit?}.
|
187
220
|
# @return [void]
|
188
|
-
def permit( *actions,
|
221
|
+
def permit( *actions, &block )
|
189
222
|
result = @when_elevated ? :maybe : :yes
|
223
|
+
resource, actions = extract_resource( actions )
|
190
224
|
|
191
225
|
add_rule( actions, resource, result, &block )
|
192
226
|
end
|
@@ -200,7 +234,8 @@ module Shamu
|
|
200
234
|
# @yield (see #permit)
|
201
235
|
# @yieldparam (see #permit)
|
202
236
|
# @yieldreturn [Boolean] true to deny the action.
|
203
|
-
def deny( *actions,
|
237
|
+
def deny( *actions, &block )
|
238
|
+
resource, actions = extract_resource( actions )
|
204
239
|
add_rule( actions, resource, false, &block )
|
205
240
|
end
|
206
241
|
|
@@ -247,9 +282,41 @@ module Shamu
|
|
247
282
|
aliases[to] |= actions
|
248
283
|
end
|
249
284
|
|
285
|
+
# @!visibility public
|
286
|
+
#
|
287
|
+
# Define the `resource` to {#permit} or {#deny} access to. Inside the
|
288
|
+
# block you can omit the `resource` param on DSL methods that expect
|
289
|
+
# it.
|
290
|
+
#
|
291
|
+
# @example
|
292
|
+
# resource UserEntity do
|
293
|
+
# permit :read
|
294
|
+
# permit :update do |user|
|
295
|
+
# user.id == principal.user_id
|
296
|
+
# end
|
297
|
+
#
|
298
|
+
# permit :chop, OtherKindOfEntity
|
299
|
+
# end
|
300
|
+
def resource( resource )
|
301
|
+
last_resource = @dsl_resource
|
302
|
+
@dsl_resource = resource
|
303
|
+
yield
|
304
|
+
ensure
|
305
|
+
@dsl_resource = last_resource
|
306
|
+
end
|
307
|
+
|
250
308
|
#
|
251
309
|
# @!endgroup DSL
|
252
310
|
|
311
|
+
def dsl_resource
|
312
|
+
@dsl_resource || fail( "Provide a `resource` argument or use a #resource block to declare the protected resource." ) # rubocop:disable Metrics/LineLength
|
313
|
+
end
|
314
|
+
|
315
|
+
def extract_resource( actions )
|
316
|
+
resource = actions.last.is_a?( Symbol ) ? dsl_resource : actions.pop
|
317
|
+
[ resource, actions ]
|
318
|
+
end
|
319
|
+
|
253
320
|
def add_rule( actions, resource, result, &block )
|
254
321
|
rules.unshift PolicyRule.new( expand_aliases( actions ), resource, result, block )
|
255
322
|
end
|
@@ -33,6 +33,7 @@ module Shamu
|
|
33
33
|
#
|
34
34
|
# @return [Boolean] true if the rule is a match.
|
35
35
|
def match?( action, resource, additional_context )
|
36
|
+
return true if actions.include? :any
|
36
37
|
return false unless actions.include? action
|
37
38
|
return false unless resource_match?( resource )
|
38
39
|
|
@@ -52,8 +53,11 @@ module Shamu
|
|
52
53
|
def resource_match?( candidate )
|
53
54
|
return true if resource == candidate
|
54
55
|
return true if resource.is_a?( Module ) && candidate.is_a?( resource )
|
56
|
+
|
57
|
+
# Allow 'doubles' to match in specs
|
58
|
+
true if defined?( RSpec::Mocks::Double ) && candidate.is_a?( RSpec::Mocks::Double )
|
55
59
|
end
|
56
60
|
|
57
61
|
end
|
58
62
|
end
|
59
|
-
end
|
63
|
+
end
|
data/lib/shamu/security/roles.rb
CHANGED
@@ -27,7 +27,7 @@ module Shamu
|
|
27
27
|
# @param [Array<Symbol>] roles
|
28
28
|
# @return [Array<Symbol>] the expanded roles.
|
29
29
|
def expand_roles( *roles )
|
30
|
-
expand_roles_into( roles,
|
30
|
+
expand_roles_into( roles, Set.new ).to_a
|
31
31
|
end
|
32
32
|
|
33
33
|
# @param [Symbol] the role to check.
|
@@ -39,6 +39,8 @@ module Shamu
|
|
39
39
|
private
|
40
40
|
|
41
41
|
def expand_roles_into( roles, expanded )
|
42
|
+
raise "No roles defined for #{ name }" unless self.roles.present?
|
43
|
+
|
42
44
|
roles.each do |name|
|
43
45
|
name = name.to_sym
|
44
46
|
|
@@ -59,4 +61,4 @@ module Shamu
|
|
59
61
|
end
|
60
62
|
end
|
61
63
|
end
|
62
|
-
end
|
64
|
+
end
|
@@ -23,7 +23,7 @@ module Shamu
|
|
23
23
|
|
24
24
|
included do
|
25
25
|
attr_dependency :security_principal, Security::Principal unless method_defined? :security_principal
|
26
|
-
attr_dependency :roles_service, Security::RolesService
|
26
|
+
attr_dependency :roles_service, Security::RolesService
|
27
27
|
end
|
28
28
|
|
29
29
|
# @return [Policy] the security {Policy} for the service.
|
@@ -79,6 +79,20 @@ module Shamu
|
|
79
79
|
security_principal.service_delegate?
|
80
80
|
end
|
81
81
|
|
82
|
+
|
83
|
+
class_methods do
|
84
|
+
|
85
|
+
# Define the {Policy} class to use when enforcing policy on the service
|
86
|
+
# methods.
|
87
|
+
def policy_class( klass )
|
88
|
+
define_method :policy_class do
|
89
|
+
klass
|
90
|
+
end
|
91
|
+
|
92
|
+
private :policy_class
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
82
96
|
end
|
83
97
|
end
|
84
|
-
end
|
98
|
+
end
|
data/lib/shamu/security.rb
CHANGED
@@ -3,6 +3,7 @@ module Shamu
|
|
3
3
|
module Security
|
4
4
|
require "shamu/security/error"
|
5
5
|
require "shamu/security/principal"
|
6
|
+
require "shamu/security/delegate_principal"
|
6
7
|
require "shamu/security/policy"
|
7
8
|
require "shamu/security/policy_rule"
|
8
9
|
require "shamu/security/no_policy"
|
@@ -40,4 +41,4 @@ module Shamu
|
|
40
41
|
end
|
41
42
|
|
42
43
|
end
|
43
|
-
end
|
44
|
+
end
|
@@ -4,9 +4,20 @@ module Shamu
|
|
4
4
|
# Helper methods useful for services that interact with {ActiveRecord::Base}
|
5
5
|
# models.
|
6
6
|
module ActiveRecord
|
7
|
+
extend ActiveSupport::Concern
|
8
|
+
|
9
|
+
included do
|
10
|
+
# Override to make sure we always catch ActiveRecord not found errors.
|
11
|
+
def with_request( * )
|
12
|
+
wrap_not_found do
|
13
|
+
super
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
7
17
|
|
8
18
|
private
|
9
19
|
|
20
|
+
|
10
21
|
# @!visibility public
|
11
22
|
#
|
12
23
|
# Watch for ActiveRecord::RecordNotFound errors and rethrow as a
|
@@ -43,7 +43,7 @@ module Shamu
|
|
43
43
|
extend ActiveSupport::Concern
|
44
44
|
|
45
45
|
# Known DSL methods defined by {ActiveRecordCrud}.
|
46
|
-
DSL_METHODS = %i[ create update change destroy find list lookup finders ].freeze
|
46
|
+
DSL_METHODS = %i[ create update change destroy find list lookup finders crud ].freeze
|
47
47
|
|
48
48
|
included do |base|
|
49
49
|
base.include Shamu::Services::RequestSupport
|
@@ -120,7 +120,7 @@ module Shamu
|
|
120
120
|
define_singleton_method( :model_class ) { model_class }
|
121
121
|
|
122
122
|
( Array( methods ) & DSL_METHODS ).each do |method|
|
123
|
-
send method
|
123
|
+
send :"define_#{ method }"
|
124
124
|
end
|
125
125
|
|
126
126
|
define_build_entities( &block )
|
@@ -236,7 +236,7 @@ module Shamu
|
|
236
236
|
|
237
237
|
with_request params, klass do |request, *args|
|
238
238
|
record = default_scope.find( request.id )
|
239
|
-
authorize! :destroy, build_entity( record )
|
239
|
+
authorize! :destroy, build_entity( record ), request
|
240
240
|
|
241
241
|
instance_exec record, request, *args, &block if block_given?
|
242
242
|
next record unless record.destroy
|
@@ -54,33 +54,35 @@ module Shamu
|
|
54
54
|
|
55
55
|
# Execute block if the request is satisfied by the service successfully.
|
56
56
|
def on_success( &block )
|
57
|
-
@
|
58
|
-
@
|
57
|
+
@on_success_blocks ||= []
|
58
|
+
@on_success_blocks << block
|
59
59
|
end
|
60
60
|
|
61
61
|
# Execute block if the request is not satisfied by the service.
|
62
62
|
def on_fail( &block )
|
63
|
-
@
|
64
|
-
@
|
63
|
+
@on_fail_blocks ||= []
|
64
|
+
@on_fail_blocks << block
|
65
65
|
end
|
66
66
|
|
67
67
|
# Execute block when the service is done processing the request.
|
68
68
|
def on_complete( &block )
|
69
|
-
@
|
70
|
-
@
|
69
|
+
@on_complete_blocks ||= []
|
70
|
+
@on_complete_blocks << block
|
71
71
|
end
|
72
72
|
|
73
|
-
#
|
73
|
+
# Mark the request as complete and run any {#on_success} or #{on_fail}
|
74
|
+
# callbacks.
|
75
|
+
#
|
74
76
|
# @param [Boolean] success true if the request was completed
|
75
77
|
# successfully.
|
76
|
-
def
|
78
|
+
def complete( success )
|
77
79
|
if success
|
78
|
-
@
|
80
|
+
@on_success_blocks && @on_success_blocks.each( &:call )
|
79
81
|
else
|
80
|
-
@
|
82
|
+
@on_fail_blocks && @on_fail_blocks.each( &:call )
|
81
83
|
end
|
82
84
|
|
83
|
-
@
|
85
|
+
@on_complete_blocks && @on_complete_blocks.each( &:call )
|
84
86
|
end
|
85
87
|
|
86
88
|
class << self
|
data/lib/shamu/version.rb
CHANGED
@@ -11,12 +11,32 @@ describe Shamu::Entities::Entity do
|
|
11
11
|
end
|
12
12
|
end
|
13
13
|
|
14
|
-
|
14
|
+
context "with instance" do
|
15
15
|
let( :user ) { OpenStruct.new( name: "Heisenberg", email: "blue@rock.com" ) }
|
16
16
|
let( :instance ) { klass.new( user: user ) }
|
17
17
|
|
18
|
-
|
19
|
-
|
18
|
+
describe "#to_attributes" do
|
19
|
+
|
20
|
+
it "does not include model attributes" do
|
21
|
+
expect( instance.to_attributes ).not_to have_key :user
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
describe "#redact" do
|
26
|
+
it "clears the assigned attribute" do
|
27
|
+
redacted = instance.redact( :name )
|
28
|
+
expect( redacted.name ).to be_nil
|
29
|
+
end
|
30
|
+
|
31
|
+
it "it returns instance of the same type" do
|
32
|
+
redacted = instance.redact( :name )
|
33
|
+
expect( redacted ).to be_a klass
|
34
|
+
end
|
35
|
+
|
36
|
+
it "assigns redacted values" do
|
37
|
+
redacted = instance.redact( name: "REDACTED" )
|
38
|
+
expect( redacted.name ).to eq "REDACTED"
|
39
|
+
end
|
20
40
|
end
|
21
41
|
end
|
22
42
|
|
@@ -53,4 +73,4 @@ describe Shamu::Entities::Entity do
|
|
53
73
|
expect( klass.null_entity.new.name ).to eq "Unknown"
|
54
74
|
end
|
55
75
|
end
|
56
|
-
end
|
76
|
+
end
|
@@ -1,10 +1,17 @@
|
|
1
1
|
require "spec_helper"
|
2
2
|
require "shamu/active_record"
|
3
3
|
|
4
|
+
module ActiveRecordPolicySpec
|
5
|
+
class Policy < Shamu::Security::ActiveRecordPolicy
|
6
|
+
def permissions
|
7
|
+
end
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
4
11
|
describe Shamu::Security::ActiveRecordPolicy do
|
5
12
|
use_active_record
|
6
13
|
|
7
|
-
let( :policy ) {
|
14
|
+
let( :policy ) { ActiveRecordPolicySpec::Policy.new }
|
8
15
|
|
9
16
|
describe "#refine_relation" do
|
10
17
|
before( :each ) do
|
@@ -35,4 +42,4 @@ describe Shamu::Security::ActiveRecordPolicy do
|
|
35
42
|
end.to change { policy.send( :refinements ).length }
|
36
43
|
end
|
37
44
|
end
|
38
|
-
end
|
45
|
+
end
|
@@ -7,11 +7,11 @@ describe Shamu::Security::Policy do
|
|
7
7
|
let( :klass ) do
|
8
8
|
Class.new( Shamu::Security::Policy ) do
|
9
9
|
role :super_user, inherits: :admin
|
10
|
-
role :admin, inherits: :
|
11
|
-
role :
|
10
|
+
role :admin, inherits: :authenticated
|
11
|
+
role :authenticated
|
12
12
|
|
13
13
|
public :in_role?, :add_rule, :rules, :permit, :deny, :when_elevated,
|
14
|
-
:alias_action, :expand_aliases
|
14
|
+
:alias_action, :expand_aliases, :resource
|
15
15
|
end
|
16
16
|
end
|
17
17
|
|
@@ -40,16 +40,16 @@ describe Shamu::Security::Policy do
|
|
40
40
|
end
|
41
41
|
|
42
42
|
describe "#in_role?" do
|
43
|
-
it "is true for :
|
43
|
+
it "is true for :authenticated when principal is signed in" do
|
44
44
|
allow( policy.principal ).to receive( :user_id ).and_return 1
|
45
45
|
|
46
|
-
expect( policy.in_role?( :
|
46
|
+
expect( policy.in_role?( :authenticated ) ).to be_truthy
|
47
47
|
end
|
48
48
|
|
49
|
-
it "is false for :
|
49
|
+
it "is false for :authenticated when principal is anonymous" do
|
50
50
|
allow( policy.principal ).to receive( :user_id ).and_return nil
|
51
51
|
|
52
|
-
expect( policy.in_role?( :
|
52
|
+
expect( policy.in_role?( :authenticated ) ).to be_falsy
|
53
53
|
end
|
54
54
|
|
55
55
|
it "is true for an explicitly assigned role" do
|
@@ -110,7 +110,7 @@ describe Shamu::Security::Policy do
|
|
110
110
|
it "adds a :maybe rule when defined within #when_elevated" do
|
111
111
|
allow( policy ).to receive( :permissions ) do
|
112
112
|
policy.when_elevated do
|
113
|
-
policy.permit :do, :stuff
|
113
|
+
policy.permit :do, :stuff, nil
|
114
114
|
end
|
115
115
|
end
|
116
116
|
|
@@ -118,6 +118,28 @@ describe Shamu::Security::Policy do
|
|
118
118
|
end
|
119
119
|
end
|
120
120
|
|
121
|
+
describe "#resource" do
|
122
|
+
it "fails if no resource block and resource omitted to permit" do
|
123
|
+
expect do
|
124
|
+
policy.permit :read
|
125
|
+
end.to raise_exception( /resource/ )
|
126
|
+
end
|
127
|
+
|
128
|
+
it "uses the dsl resource when omitted" do
|
129
|
+
expect( policy ).to receive( :add_rule ).with( [ :read ], Object, :yes )
|
130
|
+
policy.resource Object do
|
131
|
+
policy.permit :read
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
it "uses an explicit resource when provided" do
|
136
|
+
expect( policy ).to receive( :add_rule ).with( [ :read ], Symbol, :yes )
|
137
|
+
policy.resource Object do
|
138
|
+
policy.permit :read, Symbol
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
121
143
|
describe "#add_rule" do
|
122
144
|
before( :each ) do
|
123
145
|
allow( policy ).to receive( :permissions )
|
@@ -155,4 +177,4 @@ describe Shamu::Security::Policy do
|
|
155
177
|
expect( policy.expand_aliases( [ :read ] ) ).to include :index
|
156
178
|
end
|
157
179
|
end
|
158
|
-
end
|
180
|
+
end
|
@@ -74,7 +74,7 @@ describe Shamu::Services::ActiveRecordCrud do
|
|
74
74
|
|
75
75
|
Shamu::Services::ActiveRecordCrud::DSL_METHODS.each do |method|
|
76
76
|
it "defines standard DSL method `#{ method }` when passed via `:methods`" do
|
77
|
-
expect( klass ).to receive( method )
|
77
|
+
expect( klass ).to receive( :"define_#{ method }" )
|
78
78
|
klass.resource( ActiveRecordCrudSpec::FavoriteEntity, ActiveRecordSpec::Favorite, methods: method )
|
79
79
|
end
|
80
80
|
end
|
@@ -434,7 +434,8 @@ describe Shamu::Services::ActiveRecordCrud do
|
|
434
434
|
it "calls #authorize!" do
|
435
435
|
expect( service ).to receive( :authorize! ).with(
|
436
436
|
:destroy,
|
437
|
-
kind_of( ActiveRecordCrudSpec::FavoriteEntity )
|
437
|
+
kind_of( ActiveRecordCrudSpec::FavoriteEntity ),
|
438
|
+
kind_of( ActiveRecordCrudSpec::Request::Destroy )
|
438
439
|
)
|
439
440
|
|
440
441
|
service.destroy entity.id
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: shamu
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.19
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Paul Alexander
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2017-06-
|
11
|
+
date: 2017-06-11 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activemodel
|