joshuaclayton-sentinel 0.1.0 → 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
data/README.textile CHANGED
@@ -8,7 +8,7 @@ Sentinels are objects that track permissions. They're flexible, handy, and very
8
8
 
9
9
  For example, here's a ForumSentinel:
10
10
 
11
- <code><pre>
11
+ <pre><code>
12
12
  class ForumSentinel < Sentinel::Sentinel
13
13
  def creatable?
14
14
  current_user_admin?
@@ -40,7 +40,7 @@ class ForumSentinel < Sentinel::Sentinel
40
40
  current_user? && self.current_user.admin?
41
41
  end
42
42
  end
43
- </pre></code>
43
+ </code></pre>
44
44
 
45
45
  So, what's this guy do? He personally tracks ability to essentially CRUD a forum, based on the current user.
46
46
 
@@ -54,7 +54,7 @@ But, there's more.
54
54
 
55
55
  You may be asking, "What about when I'm looping through a recordset and want to determine permissions on the fly?" That's a legitimate question, really. I've got an easy answer for you. Imagine your view looks something like this:
56
56
 
57
- <code><pre>
57
+ <pre><code>
58
58
  <% @forums.each do |forum| %>
59
59
  <% sentinel = ForumSentinel.new(:current_user => current_user, :forum => forum) %>
60
60
  <% if sentinel.viewable? %>
@@ -64,11 +64,11 @@ You may be asking, "What about when I'm looping through a recordset and want to
64
64
  </div>
65
65
  <% end %>
66
66
  <% end %>
67
- </pre></code>
67
+ </code></pre>
68
68
 
69
69
  You get the idea. This is still pretty nasty though, since we're instantiating a new sentinel for each item in the recordset. Let's handle this in the controller.
70
70
 
71
- <code><pre>
71
+ <pre><code>
72
72
  class ForumsController < ApplicationController
73
73
  controls_access_with do
74
74
  ForumSentinel.new :current_user => current_user, :forum => @forum
@@ -76,11 +76,11 @@ class ForumsController < ApplicationController
76
76
 
77
77
  # ...etc
78
78
  end
79
- </pre></code>
79
+ </code></pre>
80
80
 
81
- Here, we setup the sentinel in the controller and make a @sentinel@ view helper to access the instantiated object. So, if `@forum` is set up in the show action, we'll have access to it. The index action, not so much. Not to fear.
81
+ Here, we setup the sentinel in the controller and make a @sentinel@ view helper to access the instantiated object. So, if @@forum@ is set up in the show action, we'll have access to it. The index action, not so much. Not to fear.
82
82
 
83
- <code><pre>
83
+ <pre><code>
84
84
  <% @forums.each do |forum| %>
85
85
  <% if sentinel[:forum => forum].viewable? %>
86
86
  <div id="<%= dom_id(forum) %>">
@@ -89,17 +89,17 @@ Here, we setup the sentinel in the controller and make a @sentinel@ view helper
89
89
  </div>
90
90
  <% end %>
91
91
  <% end %>
92
- </pre></code>
92
+ </code></pre>
93
93
 
94
- Essentially the same view as before, except we're not instantiating on every line and it keeps the view nice and clean. Notice we call `[]`, passing in a hash? Those are _temporary_ (as in, that call only) overrides. We assign forum to the current forum we're looping through and have the sentinel return permissions scoped to itself with whatever overrides.
94
+ Essentially the same view as before, except we're not instantiating on every line and it keeps the view nice and clean. Notice we call @[]@, passing in a hash? Those are _temporary_ (as in, that call only) overrides. We assign forum to the current forum we're looping through and have the sentinel return permissions scoped to itself with whatever overrides.
95
95
 
96
- So, handling permissions in the views are pretty easy now; hell, testing should be pretty simple too, since stubbing out simple methods like `viewable?`, `editable?`, etc will be cake.
96
+ So, handling permissions in the views are pretty easy now; hell, testing should be pretty simple too, since stubbing out simple methods like @viewable?@, @editable?@, etc will be cake.
97
97
 
98
98
  "What about the controllers?" you may ask. Don't worry about the controllers; this is just as easy.
99
99
 
100
100
  I introduce to you... @grants_access_to@.
101
101
 
102
- <code><pre>
102
+ <pre><code>
103
103
  class ForumsController < ApplicationController
104
104
  controls_access_with do
105
105
  ForumSentinel.new :current_user => current_user, :forum => @forum
@@ -110,13 +110,13 @@ class ForumsController < ApplicationController
110
110
  grants_access_to :viewable?, :only => [:show]
111
111
  grants_access_to :destroyable?, :only => [:destroy]
112
112
  end
113
- </pre></code>
113
+ </code></pre>
114
114
 
115
115
  @grants_access_to@ is essentially a @before_filter@ on crack. It uses the sentinel we've set up and calls methods on it. So, if the sentinel returns true when :reorderable? is called, it won't deny the request. Other filters, however, may.
116
116
 
117
117
  You need not call methods on the sentinel if you don't want to. Let's say you want to check if a user is logged in and an admin (contrived example, I know).
118
118
 
119
- <code><pre>
119
+ <pre><code>
120
120
  class ForumsController < ApplicationController
121
121
  controls_access_with do
122
122
  ForumSentinel.new :current_user => current_user, :forum => @forum
@@ -130,13 +130,13 @@ class ForumsController < ApplicationController
130
130
  s.creatable? && s.forum.private?
131
131
  end
132
132
  end
133
- </pre></code>
133
+ </code></pre>
134
134
 
135
135
  The first @grants_access_to@ evaluates in the scope of the controller. If the block passed has an arity of 1 (one required block-level variable), it evaluates in the context of the sentinel.
136
136
 
137
137
  When granting access, you may want to handle different checks differently. You can essentially how the controller handles how things are denied. For example, you may want to include a couple basics within @ApplicationController@.
138
138
 
139
- <code><pre>
139
+ <pre><code>
140
140
  class ApplicationController < ActionController::Base
141
141
  on_denied_with :forbid_access do
142
142
  respond_to do |wants|
@@ -157,11 +157,11 @@ class ApplicationController < ActionController::Base
157
157
  end
158
158
  end
159
159
  end
160
- </pre></code>
160
+ </code></pre>
161
161
 
162
162
  If these are set up, you can then have your actions deny with whatever you want, like so:
163
163
 
164
- <code><pre>
164
+ <pre><code>
165
165
  class ForumsController < ApplicationController
166
166
  controls_access_with do
167
167
  ForumSentinel.new :current_user => current_user, :forum => @forum
@@ -172,7 +172,7 @@ class ForumsController < ApplicationController
172
172
  grants_access_to :viewable?, :only => [:show], :denies_with => :unauthorized
173
173
  grants_access_to :destroyable?, :only => [:destroy], :denies_with => :forbidden
174
174
  end
175
- </pre></code>
175
+ </code></pre>
176
176
 
177
177
  Testing the sentinels themselves are fairly easy to do; I won't go into detail with that.
178
178
 
@@ -180,7 +180,7 @@ Testing the controllers, however, can be a bit tricky. Luckily, there are a han
180
180
 
181
181
  Here's a short example of what you may want to test:
182
182
 
183
- <code><pre>
183
+ <pre><code>
184
184
  class SentinelControllerTest < ActionController::TestCase
185
185
  include ActionView::Helpers::UrlHelper
186
186
  include ActionView::Helpers::TagHelper
@@ -207,9 +207,9 @@ class SentinelControllerTest < ActionController::TestCase
207
207
  should_grant_access_to "post :create, :forum => {:name => 'My New Forum'}"
208
208
  end
209
209
  end
210
- </pre></code>
210
+ </code></pre>
211
211
 
212
- @sentinel_context@ allows you to stub out responses for whatever methods you want on the sentinel. Assign attributes (`:current_user`, `:forum`, etc) or stub the permission methods themselves (that's what I would recommend, since your sentinel unit tests should check what the permissions return).
212
+ @sentinel_context@ allows you to stub out responses for whatever methods you want on the sentinel. Assign attributes (@:current_user@, @:forum@, etc) or stub the permission methods themselves (that's what I would recommend, since your sentinel unit tests should check what the permissions return).
213
213
 
214
214
  @should_not_guard@ ensures that @grants_access_to@ never gets called on that action. @should_grant_access_to@ and @should_deny_access_to@ are fairly straightforward. If @grants_access_to@ denies with a certain handler, you'll want to pass that handler name in (otherwise, you'll have failing tests).
215
215
 
data/Rakefile CHANGED
@@ -3,7 +3,7 @@ require 'rake'
3
3
  require 'echoe'
4
4
  require 'rake/rdoctask'
5
5
 
6
- Echoe.new("sentinel", "0.1.0") do |p|
6
+ Echoe.new("sentinel", "0.1.1") do |p|
7
7
  p.description = "Simple authorization for Rails"
8
8
  p.url = "http://github.com/joshuaclayton/sentinel"
9
9
  p.author = "Joshua Clayton"
@@ -45,6 +45,7 @@ module Sentinel
45
45
 
46
46
  def grants_access_to(*args, &block)
47
47
  options = args.extract_options!
48
+
48
49
  block = args.shift if args.first.respond_to?(:call)
49
50
  sentinel_method = args.first
50
51
  denied_handler = options.delete(:denies_with) || :default
data/sentinel.gemspec CHANGED
@@ -2,11 +2,11 @@
2
2
 
3
3
  Gem::Specification.new do |s|
4
4
  s.name = %q{sentinel}
5
- s.version = "0.1.0"
5
+ s.version = "0.1.1"
6
6
 
7
7
  s.required_rubygems_version = Gem::Requirement.new(">= 1.2") if s.respond_to? :required_rubygems_version=
8
8
  s.authors = ["Joshua Clayton"]
9
- s.date = %q{2009-04-06}
9
+ s.date = %q{2009-04-08}
10
10
  s.description = %q{Simple authorization for Rails}
11
11
  s.email = %q{joshua.clayton@gmail.com}
12
12
  s.extra_rdoc_files = ["lib/sentinel/controller.rb", "lib/sentinel/sentinel.rb", "lib/sentinel.rb", "README.textile"]
@@ -18,10 +18,27 @@ module Sentinel
18
18
 
19
19
  def denied_with(denied_name, &block)
20
20
  context "denied_with #{denied_name}" do
21
+ without_before_filters do # this strips out any other preconditions so we can properly test the handler
22
+ setup do
23
+ action = "action_#{Digest::MD5.hexdigest(Time.now.to_s.split(//).sort_by {rand}.join)}"
24
+ @controller.class.grants_access_to lambda { false }, :only => [action.to_sym], :denies_with => denied_name
25
+ get action.to_sym
26
+ end
27
+
28
+ merge_block(&block)
29
+ end
30
+ end
31
+ end
32
+
33
+ def without_before_filters(&block)
34
+ context "" do
21
35
  setup do
22
- action = "action_#{Digest::MD5.hexdigest(Time.now.to_s.split(//).sort_by {rand}.join)}"
23
- @controller.class.grants_access_to lambda { false }, :only => [action.to_sym], :denies_with => denied_name
24
- get action.to_sym
36
+ @filter_chain = @controller.class.filter_chain
37
+ @controller.class.write_inheritable_attribute("filter_chain", ActionController::Filters::FilterChain.new)
38
+ end
39
+
40
+ teardown do
41
+ @controller.class.write_inheritable_attribute("filter_chain", @filter_chain)
25
42
  end
26
43
 
27
44
  merge_block(&block)
@@ -51,12 +68,20 @@ module Sentinel
51
68
 
52
69
  def should_not_guard(command)
53
70
  context "performing `#{command}`" do
54
- should "not use guard with a sentinel" do
71
+ setup do
55
72
  @controller.class.expects(:access_granted).never
56
73
  @controller.class.expects(:access_denied).never
74
+ @controller.class_eval do
75
+ def rescue_action(e) raise e end; # force the controller to reraise the exception error
76
+ end
77
+ end
78
+
79
+ should "not use guard with a sentinel" do
80
+ eval command
57
81
  end
58
82
  end
59
83
  end
84
+
60
85
  end
61
86
  end
62
87
 
@@ -9,7 +9,9 @@ class SentinelControllerTest < ActionController::TestCase
9
9
  end
10
10
 
11
11
  sentinel_context do
12
- should_not_guard "get :index"
12
+ without_before_filters do
13
+ should_not_guard "get :index"
14
+ end
13
15
  end
14
16
 
15
17
  sentinel_context({:viewable? => true}) do
@@ -30,15 +32,22 @@ class SentinelControllerTest < ActionController::TestCase
30
32
  should_deny_access_to "get :show", :with => :sentinel_unauthorized
31
33
  end
32
34
 
33
- denied_with :redirect_to_index do
34
- should_redirect_to("forums root") { url_for(:controller => "forums")}
35
- end
36
-
37
- denied_with :sentinel_unauthorized do
38
- should_respond_with :forbidden
35
+ context "A controller-global grants_access_to that denies access" do
36
+ # this ensures that, even with a failing grants_access_to, we can properly test all denied_with handlers
37
+ setup do
38
+ @controller.stubs(:stubbed_method).returns(false)
39
+ end
39
40
 
40
- should "render text as response" do
41
- assert_equal "This is an even more unique default restricted warning", @response.body
41
+ denied_with :redirect_to_index do
42
+ should_redirect_to("forums root") { url_for(:controller => "forums", :action => "secondary_index")}
43
+ end
44
+
45
+ denied_with :sentinel_unauthorized do
46
+ should_respond_with :forbidden
47
+
48
+ should "render text as response" do
49
+ assert_equal "This is an even more unique default restricted warning", @response.body
50
+ end
42
51
  end
43
52
  end
44
53
  end
@@ -3,13 +3,19 @@ class ForumsController < ApplicationController
3
3
  ForumSentinel.new :current_user => current_user, :forum => @forum
4
4
  end
5
5
 
6
+ grants_access_to lambda { stubbed_method }, :denies_with => :redirect_to_index
7
+
8
+ grants_access_to :denies_with => :sentinel_unauthorized do
9
+ stubbed_method_two
10
+ end
11
+
6
12
  grants_access_to :reorderable?, :only => [:reorder]
7
13
  grants_access_to :creatable?, :only => [:new, :create], :denies_with => :redirect_to_index
8
14
  grants_access_to :viewable?, :only => [:show], :denies_with => :sentinel_unauthorized
9
15
  grants_access_to :destroyable?, :only => [:destroy]
10
16
 
11
17
  on_denied_with :redirect_to_index do
12
- redirect_to url_for(:controller => "forums")
18
+ redirect_to url_for(:controller => "forums", :action => "secondary_index")
13
19
  end
14
20
 
15
21
  on_denied_with :sentinel_unauthorized do
@@ -23,6 +29,10 @@ class ForumsController < ApplicationController
23
29
  handle_successfully
24
30
  end
25
31
 
32
+ def secondary_index
33
+ handle_successfully
34
+ end
35
+
26
36
  def new
27
37
  handle_successfully
28
38
  end
@@ -48,4 +58,12 @@ class ForumsController < ApplicationController
48
58
  def handle_successfully
49
59
  render :text => "forums"
50
60
  end
61
+
62
+ def stubbed_method
63
+ true
64
+ end
65
+
66
+ def stubbed_method_two
67
+ true
68
+ end
51
69
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: joshuaclayton-sentinel
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.1.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Joshua Clayton
@@ -9,7 +9,7 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2009-04-06 00:00:00 -07:00
12
+ date: 2009-04-08 00:00:00 -07:00
13
13
  default_executable:
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency