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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: dd0f794321cd3d306d36b16935e133e3ded9270b
4
- data.tar.gz: 244f8025c48879a691c7faa6f6e76b3b45efe2e0
3
+ metadata.gz: 9cde1e60d3798de927cee92894d391d3387e1619
4
+ data.tar.gz: 204c11e52933dd2d88acb0e812d9e5183d3c09c0
5
5
  SHA512:
6
- metadata.gz: 57d00e95ce2bf7a4d1ddbc224aff0a2790255a6bd1c1ac907f7450b95a30b63c7ca2cc404d0754c7ee363761386fc5c1567f1f50114957488d538be85d2cbda8
7
- data.tar.gz: e2ab805eb2ba0db7969601cb825f827c8d5346c8ebd34db8e836543a6726b7327c20b68f092681555b56f1832d8dd8512d08d8bc1ddaf3170e6f221d5b3ab1a0
6
+ metadata.gz: cb22139780ad29697e690025ad740b2cb1673b3486f854744182972f4ff382a873c7f362a59936b39c82788742c5e486237d7b29c4346acfd3f0a2532e97f582
7
+ data.tar.gz: 7d06c76f61740a24724d4261a3b72d9117a5e5f36d6c600168271981c3c45ce23d1e637a9b677809d3996229e0df616e573d3994997ecb54a20310bfa349c65f
data/.rubocop.yml CHANGED
@@ -39,7 +39,7 @@ Metrics/MethodLength:
39
39
  Max: 15
40
40
 
41
41
  Metrics/AbcSize:
42
- Max: 20
42
+ Max: 24
43
43
 
44
44
  Metrics/CyclomaticComplexity:
45
45
  Max: 10
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- shamu (0.0.18)
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.0.0)
117
+ nio4r (2.1.0)
118
118
  nokogiri (1.7.2)
119
119
  mini_portile2 (~> 2.1.0)
120
120
  notiffany (0.1.1)
@@ -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 )
@@ -49,6 +49,12 @@ module Shamu
49
49
  end
50
50
  end
51
51
 
52
+ # @return [Boolean] true if entities have been added to the transaction.
53
+ def entities?
54
+ entities.present?
55
+ end
56
+
57
+
52
58
  private
53
59
 
54
60
  attr_reader :entities
@@ -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 #{ criteria.class.name }"
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/AbcSize, Metrics/MethodLength
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 }
@@ -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
- attr_reader :principal
45
+ attr_reader :principal
46
46
 
47
47
  # @!attribute
48
48
  # @return [Array<Roles>] roles that have been granted to the {#principal}.
49
- attr_reader :roles
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 = principal || Principal.new
55
- @roles = 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
- permissions
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 << :user if principal.user_id && self.class.role_defined?( :user )
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
- fail IncompleteSetupError, "Permissions have not been defined. Add a private `permissions` method to #{ self.class.name }" # rubocop:disable Metrics/LineLength
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, resource, &block )
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, resource, &block )
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
@@ -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 unless method_defined? :roles_service
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
@@ -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
- @on_success_callbacks ||= []
58
- @on_success_callbacks << block
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
- @on_fail_callbacks ||= []
64
- @on_fail_callbacks << block
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
- @on_complete_callbacks ||= []
70
- @on_complete_callbacks << block
69
+ @on_complete_blocks ||= []
70
+ @on_complete_blocks << block
71
71
  end
72
72
 
73
- # Run any {#on_success} or #{on_fail} callbacks.
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 run_callbacks( success )
78
+ def complete( success )
77
79
  if success
78
- @on_success_callbacks && @on_success_callbacks.each( &:call )
80
+ @on_success_blocks && @on_success_blocks.each( &:call )
79
81
  else
80
- @on_fail_callbacks && @on_fail_callbacks.each( &:call )
82
+ @on_fail_blocks && @on_fail_blocks.each( &:call )
81
83
  end
82
84
 
83
- @on_fail_callbacks && @on_fail_callbacks.each( &:call )
85
+ @on_complete_blocks && @on_complete_blocks.each( &:call )
84
86
  end
85
87
 
86
88
  class << self
@@ -91,7 +91,7 @@ module Shamu
91
91
  sources = yield( request )
92
92
 
93
93
  result = Result.coerce( sources, request: request )
94
- request.run_callbacks( result.valid? )
94
+ request.complete( result.valid? )
95
95
 
96
96
  result
97
97
  end
data/lib/shamu/version.rb CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  module Shamu
4
4
  # The primary version number
5
- VERSION_NUMBER = "0.0.18"
5
+ VERSION_NUMBER = "0.0.19"
6
6
 
7
7
  # Version suffix such as 'beta' or 'alpha'
8
8
  VERSION_SUFFIX = ""
@@ -11,12 +11,32 @@ describe Shamu::Entities::Entity do
11
11
  end
12
12
  end
13
13
 
14
- describe "#to_attributes" do
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
- it "does not include model attributes" do
19
- expect( instance.to_attributes ).not_to have_key :user
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 ) { Shamu::Security::ActiveRecordPolicy.new }
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: :user
11
- role :user
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 :user when principal is signed in" do
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?( :user ) ).to be_truthy
46
+ expect( policy.in_role?( :authenticated ) ).to be_truthy
47
47
  end
48
48
 
49
- it "is false for :user when principal is anonymous" do
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?( :user ) ).to be_falsy
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.18
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-08 00:00:00.000000000 Z
11
+ date: 2017-06-11 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activemodel