authority 2.3.2 → 2.4.0

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGELOG.markdown CHANGED
@@ -1,5 +1,19 @@
1
1
  # Changelog
2
2
 
3
+ Authority does its best to use [semantic versioning](http://semver.org).
4
+
5
+ ## v2.4.0
6
+
7
+ Controller method `authorize_actions_for` can now be given a method name to dynamically determine the class to authorize. For example, `authorize_actions_for :model_class` will call the `model_class` method on the controller instance at request time.
8
+
9
+ ## v2.3.2
10
+
11
+ - Updated `can?` to only pass options if it was given options.
12
+
13
+ ## v2.3.1
14
+
15
+ - Had second thought and reworked `can?(:action)` to call `Application_authorizer.authorizes_to_#{action}?`. Ensured it's backwards compatible for the few people who started using this in the last day or so.
16
+
3
17
  ## v2.3.0
4
18
 
5
19
  - Added generic `current_user.can?(:mimic_lemurs)` for cases where there is no resource to work with. This calls a corresponding class method on `ApplicationAuthorizer`, like `ApplicationAuthorizer.can_mimic_lemurs?`.
data/README.markdown CHANGED
@@ -6,7 +6,7 @@ Authority will work fine with a standalone app or a single sign-on system. You c
6
6
 
7
7
  It requires that you already have some kind of user object in your application, accessible from all controllers and views via a method like `current_user` (configurable).
8
8
 
9
- [![Build Status](https://secure.travis-ci.org/nathanl/authority.png?branch=master)](http://travis-ci.org/nathanl/authority)
9
+ <!--[![Build Status](https://secure.travis-ci.org/nathanl/authority.png?branch=master)](http://travis-ci.org/nathanl/authority) -->
10
10
  <!-- [![Dependency Status](https://gemnasium.com/nathanl/authority.png)](https://gemnasium.com/nathanl/authority) -->
11
11
  [![Code Climate](https://codeclimate.com/badge.png)](https://codeclimate.com/github/nathanl/authority)
12
12
 
@@ -353,6 +353,35 @@ end
353
353
 
354
354
  As with other authorization checks, you can also pass options here, and they'll be sent along to your authorization method: `authorize_action_for(@llama, :sporting => @hat_style)`. Generally, though, your authorization will depend on some attribute or association of the model instance, so the authorizer can check `@llama.neck_strength` and `@llama.owner.nationality`, etc, without needing any additional information.
355
355
 
356
+ Note that you can also call `authority_actions` as many times as you like, so you can specify one mapping at a time if you prefer:
357
+
358
+ ```ruby
359
+ class LlamasController < ApplicationController
360
+ def breed
361
+ # some code
362
+ end
363
+ authority_actions :breed => 'create'
364
+
365
+ def vaporize
366
+ # some code
367
+ end
368
+ authority_actions :vaporize => 'delete'
369
+ end
370
+ ```
371
+
372
+ Finally, note that if you have a controller that dynamically determines the class it's working with, you can pass the name of a controller instance method to `authorize_actions_for` instead of a class, and the class will be looked up when a request is made.
373
+
374
+ ```ruby
375
+ class LlamasController < ApplicationController
376
+
377
+ authorize_actions_for :llama_class
378
+
379
+ def llama_class
380
+ [StandardLlama, LludicriousLlama].sample
381
+ end
382
+ end
383
+ ```
384
+
356
385
  <a name="views">
357
386
  ### Views
358
387
 
@@ -387,44 +416,23 @@ Use this very sparingly, and consider it a [code smell](http://en.wikipedia.org/
387
416
  <a name="security_violations_and_logging">
388
417
  ## Security Violations & Logging
389
418
 
390
- Anytime a user attempts an unauthorized action, Authority calls whatever controller method is specified by your `security_violation_handler` option, handing it the exception. The default handler is `authority_forbidden`, which Authority adds to your `ApplicationController`. It does the following:
419
+ If you're using Authority's view helpers, users should only see links for actions they're authorized to take. If a user deliberately tries to access a restricted resource (for instance, by typing the URL directly), Authority raises and rescues an `Authority::SecurityViolation`.
420
+
421
+ When it rescues the exception, Authority calls whatever controller method is specified by your `security_violation_handler` option, handing it the exception. The default handler is `authority_forbidden`, which Authority mixes in to your `ApplicationController`. It does the following:
391
422
 
392
423
  - Renders `public/403.html`
393
424
  - Logs the violation to whatever logger you configured.
394
425
 
395
- You can define your own `authority_forbidden` method:
396
-
426
+ You can define your own `authority_forbidden` method on `ApplicationController` and/or any other controller. For example:
397
427
 
398
428
  ```ruby
399
429
  # Send 'em back where they came from with a slap on the wrist
400
- def authority_forbidden(exception)
430
+ def authority_forbidden(error)
401
431
  Authority.logger.warn(error.message)
402
432
  redirect_to request.referrer.presence || root_path, :alert => 'You are not authorized to complete that action.'
403
433
  end
404
434
  ```
405
435
 
406
- ... or specify a different handler like this:
407
-
408
- ```ruby
409
- # config/initializers/authority.rb
410
- config.security_violation_handler = :fire_ze_missiles
411
- ```
412
- Then define the method on your controller:
413
-
414
- ```ruby
415
- # app/controllers/application_controller.rb
416
- class ApplicationController < ActionController::Base
417
-
418
- def fire_ze_missiles(exception)
419
- # Log? Set a flash message? Dispatch minions to
420
- # fill their mailbox with goose droppings? It's up to you.
421
- end
422
- ...
423
- end
424
- ```
425
-
426
- If you want different error handling per controller, define `fire_ze_missiles` on each of them.
427
-
428
436
  Your method will be handed the `SecurityViolation`, which has a `message` method. In case you want to build your own message, it also exposes `user`, `action` and `resource`.
429
437
 
430
438
  <a name="credits">
data/TODO.markdown CHANGED
@@ -1,5 +1,7 @@
1
1
  # TODO
2
2
 
3
+ - Consider removing `config.security_violation_handler`, since `authority_forbidden` can already be redefined on any controller
4
+
3
5
  ## Tests
4
6
 
5
7
  - Test with Rails 4 and Ruby 2.0
data/lib/authority.rb CHANGED
@@ -31,19 +31,16 @@ module Authority
31
31
  # @param [Hash] options, arbitrary options hash to delegate to the authorizer
32
32
  # @raise [SecurityViolation] if user is not allowed to perform action on resource
33
33
  # @return [Model] resource instance
34
- def self.enforce(action, resource, user, *options)
35
- unless action_authorized?(action, resource, user, *options)
34
+ def self.enforce(action, resource, user, options = {})
35
+ unless action_authorized?(action, resource, user, options)
36
36
  raise SecurityViolation.new(user, action, resource)
37
37
  end
38
38
  resource
39
39
  end
40
40
 
41
- def self.action_authorized?(action, resource, user, *options)
42
- if options.empty?
43
- user.send("can_#{action}?", resource)
44
- else
45
- user.send("can_#{action}?", resource, Hash[*options])
46
- end
41
+ def self.action_authorized?(action, resource, user, options = {})
42
+ resource_and_maybe_options = [resource, options].tap {|args| args.pop if args.last == {}}
43
+ user.send("can_#{action}?", *resource_and_maybe_options)
47
44
  end
48
45
 
49
46
  class << self
@@ -29,11 +29,8 @@ module Authority
29
29
  Authority.adjectives.each do |adjective|
30
30
  class_eval <<-RUBY, __FILE__, __LINE__ + 1
31
31
  def self.#{adjective}_by?(user, options = {})
32
- if options.empty?
33
- default(:#{adjective}, user)
34
- else
35
- default(:#{adjective}, user, options)
36
- end
32
+ user_and_maybe_options = [user, options].tap {|args| args.pop if args.last == {}}
33
+ default(:#{adjective}, *user_and_maybe_options)
37
34
  end
38
35
  RUBY
39
36
  end
@@ -14,18 +14,23 @@ module Authority
14
14
 
15
15
  included do
16
16
  rescue_from(Authority::SecurityViolation, :with => Authority::Controller.security_violation_callback)
17
- class_attribute :authority_resource
17
+ class << self
18
+ attr_accessor :authority_resource
19
+ end
18
20
  end
19
21
 
20
22
  module ClassMethods
21
23
 
22
24
  # Sets up before_filter to ensure user is allowed to perform a given controller action
23
25
  #
24
- # @param [Class] model_class - class whose authorizer should be consulted
25
- # @param [Hash] options - can contain :actions to be merged with existing
26
+ # @param [Class OR Symbol] resource_or_finder - class whose authorizer
27
+ # should be consulted, or instance method on the controller which will
28
+ # determine that class when the request is made
29
+ # @param [Hash] options - can contain :actions to
30
+ # be merged with existing
26
31
  # ones and any other options applicable to a before_filter
27
- def authorize_actions_for(model_class, options = {})
28
- self.authority_resource = model_class
32
+ def authorize_actions_for(resource_or_finder, options = {})
33
+ self.authority_resource = resource_or_finder
29
34
  authority_actions(options[:actions] || {})
30
35
  before_filter :run_authorization_check, options
31
36
  end
@@ -86,7 +91,17 @@ module Authority
86
91
  # The `before_filter` that will be setup to run when the class method
87
92
  # `authorize_actions_for` is called
88
93
  def run_authorization_check
89
- authorize_action_for self.class.authority_resource
94
+ authorize_action_for authority_resource
95
+ end
96
+
97
+ def authority_resource
98
+ return self.class.authority_resource if self.class.authority_resource.is_a?(Class)
99
+ return send(self.class.authority_resource) if respond_to?(self.class.authority_resource)
100
+ raise MissingResource.new(
101
+ "Trying to authorize actions for '#{self.class.authority_resource}', but can't. \
102
+ Must be either a resource class OR the name of a controller instance method that \
103
+ returns one.".squeeze(' ')
104
+ )
90
105
  end
91
106
 
92
107
  # Convenience wrapper for sending configured `user_method` to extract the
@@ -97,6 +112,7 @@ module Authority
97
112
  send(Authority.configuration.user_method)
98
113
  end
99
114
 
100
- class MissingAction < StandardError ; end
115
+ class MissingAction < StandardError ; end
116
+ class MissingResource < StandardError ; end
101
117
  end
102
118
  end
@@ -11,17 +11,14 @@ module Authority
11
11
  Authority.verbs.each do |verb|
12
12
  class_eval <<-RUBY, __FILE__, __LINE__ + 1
13
13
  def can_#{verb}?(resource, options = {})
14
- if options.empty?
15
- resource.#{Authority.abilities[verb]}_by?(self)
16
- else
17
- resource.#{Authority.abilities[verb]}_by?(self, options)
18
- end
14
+ self_and_maybe_options = [self, options].tap {|args| args.pop if args.last == {}}
15
+ resource.#{Authority.abilities[verb]}_by?(*self_and_maybe_options)
19
16
  end
20
17
  RUBY
21
18
  end
22
19
 
23
20
  def can?(action, options = {})
24
- self_and_maybe_options = [self, (options == {} ? nil : options)].compact # throw out if nil
21
+ self_and_maybe_options = [self, options].tap {|args| args.pop if args.last == {}}
25
22
  begin
26
23
  ApplicationAuthorizer.send("authorizes_to_#{action}?", *self_and_maybe_options)
27
24
  rescue NoMethodError => original_exception
@@ -1,3 +1,3 @@
1
1
  module Authority
2
- VERSION = "2.3.2"
2
+ VERSION = "2.4.0"
3
3
  end
@@ -4,7 +4,7 @@
4
4
  <title>Forbidden</title>
5
5
  </head>
6
6
  <body>
7
- <h1>Access Denied, HUMAN!</h1>
8
- <p>No, seriously, you can't do that...</p>
7
+ <h1>Access Denied</h1>
8
+ <p>You do not have permission to access this resource.</p>
9
9
  </body>
10
10
  </html>
@@ -3,7 +3,8 @@ Authority.configure do |config|
3
3
  # USER_METHOD
4
4
  # ===========
5
5
  # Authority needs the name of a method, available in any controller, which
6
- # will return the currently logged-in user.
6
+ # will return the currently logged-in user. (If this varies by controller,
7
+ # just create a common alias.)
7
8
  #
8
9
  # Default is:
9
10
  #
@@ -44,14 +45,6 @@ Authority.configure do |config|
44
45
  # :delete => 'deletable'
45
46
  # }
46
47
 
47
- # SECURITY_VIOLATION_HANDLER
48
- # ==========================
49
- # If a SecurityViolation is raised, what controller method should be used to rescue it?
50
- #
51
- # Default is:
52
- #
53
- # config.security_violation_handler = :authority_forbidden # Defined in controller.rb
54
-
55
48
  # LOGGER
56
49
  # ======
57
50
  # If a user tries to perform an unauthorized action, where should we log that fact?
@@ -87,11 +87,16 @@ describe Authority::Controller do
87
87
 
88
88
  describe "authorize_actions_for" do
89
89
 
90
- it "allows specifying the model to protect" do
90
+ it "allows specifying the class of the model to protect" do
91
91
  controller_class.authorize_actions_for(resource_class)
92
92
  expect(controller_class.authority_resource).to eq(resource_class)
93
93
  end
94
94
 
95
+ it "allows specifying an instance method to find the class of the model to protect" do
96
+ controller_class.authorize_actions_for(:finder_method)
97
+ expect(controller_class.authority_resource).to eq(:finder_method)
98
+ end
99
+
95
100
  it "sets up a before_filter, passing the options it was given" do
96
101
  filter_options = {:only => [:show, :edit, :update]}
97
102
  controller_class.should_receive(:before_filter).with(:run_authorization_check, filter_options)
@@ -146,9 +151,48 @@ describe Authority::Controller do
146
151
 
147
152
  describe "run_authorization_check (used as a before_filter)" do
148
153
 
149
- it "checks authorization on the model specified" do
150
- controller_instance.should_receive(:authorize_action_for).with(resource_class)
151
- controller_instance.send(:run_authorization_check)
154
+ context "if a resource class was specified" do
155
+
156
+ it "checks authorization on the model specified" do
157
+ controller_instance.should_receive(:authorize_action_for).with(resource_class)
158
+ controller_instance.send(:run_authorization_check)
159
+ end
160
+
161
+ end
162
+
163
+ context "if a method for determining the class was specified" do
164
+
165
+ let(:resource_class) { Hash }
166
+ let(:controller_class) do
167
+ Class.new(ExampleController).tap do |c|
168
+ c.send(:include, Authority::Controller)
169
+ c.authorize_actions_for(:method_to_find_class)
170
+ end
171
+ end
172
+
173
+ context "if the controller has such an instance method" do
174
+
175
+ before :each do
176
+ controller_instance.stub(:method_to_find_class).and_return(resource_class)
177
+ end
178
+
179
+ it "checks authorization on class returned by that method" do
180
+ controller_instance.should_receive(:authorize_action_for).with(resource_class)
181
+ controller_instance.send(:run_authorization_check)
182
+ end
183
+
184
+ end
185
+
186
+ context "if the controller has no such instance method" do
187
+
188
+ it "raises an exception" do
189
+ expect{controller_instance.send(:run_authorization_check)}.to raise_error(
190
+ Authority::Controller::MissingResource
191
+ )
192
+ end
193
+
194
+ end
195
+
152
196
  end
153
197
 
154
198
  it "raises a MissingAction if there is no corresponding action for the controller" do
@@ -42,7 +42,7 @@ describe Authority do
42
42
  let(:user) { ExampleUser.new }
43
43
  let(:resource_class) { ExampleResource }
44
44
 
45
- describe "if given options" do
45
+ describe "when given options" do
46
46
 
47
47
  it "checks the user's authorization, passing along the options" do
48
48
  options = { :for => 'context' }
@@ -52,7 +52,7 @@ describe Authority do
52
52
 
53
53
  end
54
54
 
55
- describe "if not given options" do
55
+ describe "when not given options" do
56
56
 
57
57
  it "checks the user's authorization, passing no options" do
58
58
  user.should_receive(:can_delete?).with(resource_class).and_return(true)
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: authority
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.3.2
4
+ version: 2.4.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -10,11 +10,11 @@ authors:
10
10
  autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
- date: 2012-12-11 00:00:00.000000000 Z
13
+ date: 2013-02-13 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: rails
17
- requirement: &2152546080 !ruby/object:Gem::Requirement
17
+ requirement: !ruby/object:Gem::Requirement
18
18
  none: false
19
19
  requirements:
20
20
  - - ! '>='
@@ -22,7 +22,12 @@ dependencies:
22
22
  version: 3.0.0
23
23
  type: :runtime
24
24
  prerelease: false
25
- version_requirements: *2152546080
25
+ version_requirements: !ruby/object:Gem::Requirement
26
+ none: false
27
+ requirements:
28
+ - - ! '>='
29
+ - !ruby/object:Gem::Version
30
+ version: 3.0.0
26
31
  description: Authority helps you authorize actions in your Rails app. It's ORM-neutral
27
32
  and has very little fancy syntax; just group your models under one or more Authorizer
28
33
  classes and write plain Ruby methods on them.
@@ -82,15 +87,21 @@ required_ruby_version: !ruby/object:Gem::Requirement
82
87
  - - ! '>='
83
88
  - !ruby/object:Gem::Version
84
89
  version: '0'
90
+ segments:
91
+ - 0
92
+ hash: -37039868613240694
85
93
  required_rubygems_version: !ruby/object:Gem::Requirement
86
94
  none: false
87
95
  requirements:
88
96
  - - ! '>='
89
97
  - !ruby/object:Gem::Version
90
98
  version: '0'
99
+ segments:
100
+ - 0
101
+ hash: -37039868613240694
91
102
  requirements: []
92
103
  rubyforge_project:
93
- rubygems_version: 1.8.16
104
+ rubygems_version: 1.8.24
94
105
  signing_key:
95
106
  specification_version: 3
96
107
  summary: Authority helps you authorize actions in your Rails app using plain Ruby