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