permissive 0.0.1 → 0.2.0.alpha

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.
data/README.markdown.html DELETED
@@ -1,191 +0,0 @@
1
- <h1>Permissive gives your ActiveRecord models granular permission support</h1>
2
-
3
- <p>Permissive combines a model-based permissions system with bitmasking to
4
- create a flexible approach to maintaining permissions on your ActiveRecord
5
- models. It supports an easy-to-use set of methods for accessing and
6
- determining permissions, including some fun metaprogramming.</p>
7
-
8
- <h2>Installation</h2>
9
-
10
- <ol>
11
- <li><p>Get yourself some code. You can install as a gem:</p>
12
-
13
- <p><code>gem install permissive</code></p>
14
-
15
- <p>or as a plugin:</p>
16
-
17
- <p><code>script/plugin install git://github.com/flipsasser/permissive.git</code></p></li>
18
- <li><p>Generate a migration so you can get some sweet table action:</p>
19
-
20
- <p><code>script/generate permissive_migration</code></p>
21
-
22
- <p><code>rake db:migrate</code></p></li>
23
- </ol>
24
-
25
-
26
- <h2>Usage</h2>
27
-
28
- <p>First, define a few permissions constants. We'll define them in <code>Rails.root/config/initializers/permissive.rb</code>. The best practice is to name them in a verb format that follows this pattern: "Object can <code>DO_PERMISSION_NAME</code>".</p>
29
-
30
- <p>Permission constants need to be int values counting up from zero. We use ints because Permissive uses bit masking to keep permissions data compact and performant.</p>
31
-
32
- <pre><code>module Permissive::Permissions
33
- MANAGE_GAMES = 0
34
- CONTROL_RIDES = 1
35
- PUNCH = 2
36
- end
37
- </code></pre>
38
-
39
- <p>And that's all it takes to configure permissions! Now that we have them, let's grant them to a model or two:</p>
40
-
41
- <pre><code>class Employee &lt; ActiveRecord::Base
42
- acts_as_permissive
43
- validates_presence_of :first_name, :last_name
44
- end
45
-
46
- class Company &lt; ActiveRecord::Base
47
- validates_presence_of :name
48
- end
49
- </code></pre>
50
-
51
- <p>Easy-peasy, right? Let's try granting a few permissions:</p>
52
-
53
- <pre><code>@james = Employee.create(:first_name =&gt; 'James', :last_name =&gt; 'Brennan')
54
- @frigo = Employee.create(:first_name =&gt; 'Tommy', :last_name =&gt; 'Frigo')
55
- @adventureland = Company.create(:name =&gt; 'Adventureland')
56
-
57
- # Okay, let's do some granting. We'll start by scoping to a specific company.
58
- @james.can!(:manage_games, :on =&gt; @adventureland)
59
-
60
- # Now let's do some permission checking.
61
- @james.can?(:manage_games, :on =&gt; @adventureland) #=&gt; true
62
-
63
- # We can also use the metaprogramming syntax:
64
- @james.can_manage_games_on?(@adventureland) #=&gt; true
65
- @james.can_control_rides_on?(@adventureland) #=&gt; false
66
-
67
- # We can check for multiple permissions, too:
68
- @james.can?(:manage_games, :control_rides) #=&gt; false
69
- # OR:
70
- @james.can_manage_games_and_control_rides?
71
-
72
- # Scoping can be done through any object
73
- @frigo.can!(:punch, :on =&gt; @james)
74
- @frigo.can_punch_on?(@james) #=&gt; true
75
-
76
- # And the permissions aren't reciprocal
77
- @james.can_punch_on?(@frigo) #=&gt; false
78
-
79
- # Of course, we can grant global (non-scoped) permissions, too:
80
- @frigo.can!(:control_rides)
81
- @frigo.can_control_rides? #=&gt; true
82
-
83
- # BUT! Global permissions don't override scoped permissions.
84
- @frigo.can_control_rides_on?(@adventureland) #=&gt; false
85
-
86
- # Likewise, scoped permissions don't bubble up globally:
87
- @james.can_manage_games? #=&gt; false
88
-
89
- # And, last but not least, let's take all of those great permissions away:
90
- @james.revoke(:manage_games, :on =&gt; @adventureland)
91
-
92
- # We can revoke all permissions, in any scope, too:
93
- @frigo.revoke(:all)
94
- </code></pre>
95
-
96
- <p>And that's it!</p>
97
-
98
- <h2>Scoping</h2>
99
-
100
- <p>Permissive supports scoping at the class-configuration level, which adds relationships to permitted objects:</p>
101
-
102
- <pre><code>class Employee &lt; ActiveRecord::Base
103
- acts_as_permissive :scope =&gt; :company
104
- end
105
-
106
- @frigo.permissive_companies #=&gt; [Company 1, Company 2]
107
- </code></pre>
108
-
109
- <h2>Replacing Permissions</h2>
110
-
111
- <p>Sometimes you want to overwrite all previous permissions in a can! method. That's pretty easy: just add :reset => true to the options.</p>
112
-
113
- <pre><code>@frigo.can!(:control_rides, :on =&gt; @adventureland, :reset =&gt; true)
114
- </code></pre>
115
-
116
- <h2>Next Steps</h2>
117
-
118
- <p>There's a number of things I want to add to the permissive settings. At the moment, Permissive currently support scoping at the class level, BUT all it really does is add a <code>has_many</code> relationship. <code>@employee.can!(:do_anything)</code> will still work, as will <code>@employee.can!(:do_something, :on =&gt; @something_that_isnt_a_company)</code>. That's pretty confusing to me. Adding more granular permissions might be cooler:</p>
119
-
120
- <pre><code>class Employee &lt; ActiveRecord::Base
121
- has_permissions do
122
- on :companies
123
- on :employees
124
- end
125
- end
126
- </code></pre>
127
-
128
- <p>which might yield something like</p>
129
-
130
- <pre><code>@employee.permissive_companies
131
- # and
132
- @employee.can_control_rides_in_company @adventureland
133
- </code></pre>
134
-
135
- <p>I'd also like to support a more intelligent grammar:</p>
136
-
137
- <pre><code>@james.can_punch? @frigo
138
- @frigo.can!(:control_rides, :in =&gt; @adventureland)
139
- </code></pre>
140
-
141
- <p>Meta-programmed methods for granting and revoking would be cool, too:</p>
142
-
143
- <pre><code>@james.can_punch! @frigo
144
- @frigo.cannot_control_rides_in! @adventureland
145
- </code></pre>
146
-
147
- <p>And while we're on the subject of metaprogramming, let's add some OR-ing to the whole thing:</p>
148
-
149
- <pre><code>@james.can_control_rides_or_manage_games_in? @adventureland
150
- </code></pre>
151
-
152
- <p>I'd also like to enable Permissive::Templates (pre-set permission groups, like roles):</p>
153
-
154
- <pre><code>administrator = Permissive::Template.named('Administrator')
155
- @james.acts_like administrator
156
- </code></pre>
157
-
158
- <p>Next up! I currently use a manual reset to grant permissions through a controller. It would by great to DRY this stuff up and provide some decent path for moving permissions into HTML forms. Right now, it looks something like this:</p>
159
-
160
- <pre><code>&lt;%= check_box_tag("employee[permissions][]", Permissive::Permissions::CONTROL_RIDES, @employee.can_control_rides?) %&gt; Control rides
161
- </code></pre>
162
-
163
- <p>.. and in the controller:</p>
164
-
165
- <pre><code>def update
166
- @employee.can!(params[:employees].delete(:permissions), :revert =&gt; true)
167
- respond_to do |format|
168
- ...
169
- end
170
- end
171
- </code></pre>
172
-
173
- <p>Finally, I'd like to use the <code>grant_mask</code> support that exists on the Permissive::Permission model to control what people can or cannot allow others to do. This would necessitate one of two things - first, a quick way of iterating over a person's granting permissions, e.g.:</p>
174
-
175
- <pre><code>&lt;% current_user.grant_permissions.each do |permission| %&gt;
176
- &lt;!-- Do something! --&gt;
177
- &lt;% end %&gt;
178
- </code></pre>
179
-
180
- <p>and second, write-time checking of grantor permissions. Something like this, maybe:</p>
181
-
182
- <pre><code>def update
183
- current_user.grant(params[:employees][:permissions], :to =&gt; @employee)
184
- end
185
- </code></pre>
186
-
187
- <p>which would allow the Permissive::Permission model to make sure whatever <code>current_user</code> is granting to @employee, they're <strong>allowed</strong> to grant to @employee.</p>
188
-
189
- <p>And that's it! Like all of my projects, I extracted it from some live development - which means it, too, is still in development. So please feel free to contribute!</p>
190
-
191
- <p>Copyright (c) 2009 Flip Sasser, released under the MIT license</p>
@@ -1,134 +0,0 @@
1
- module Permissive
2
- module ActsAsPermissive
3
- def self.included(base)
4
- base.class_eval do
5
- # This is the core of the Permissive module. It allows you to define a
6
- # permissive model structure complete with :scope. This will dynamically
7
- # generate scoped, polymorphic relationships across one or more models.
8
- def self.acts_as_permissive(options = {})
9
- options.assert_valid_keys(:scope)
10
- has_many :permissions, :class_name => 'Permissive::Permission', :as => :permitted_object do
11
- def can!(*args)
12
- options = args.last.is_a?(Hash) ? args.pop : {}
13
- options.assert_valid_keys(:on, :reset)
14
- if options[:on]
15
- permission = proxy_owner.permissions.find_or_initialize_by_scoped_object_id_and_scoped_object_type(options[:on].id, options[:on].class.to_s)
16
- else
17
- permission = Permissive::Permission.find_or_initialize_by_permitted_object_id_and_permitted_object_type(proxy_owner.id, proxy_owner.class.to_s)
18
- end
19
- if options[:reset]
20
- permission.mask = 0
21
- permission.grant_mask = 0
22
- end
23
- args.flatten.each do |name|
24
- bit = bit_for(name)
25
- unless permission.mask & bit != 0
26
- permission.mask = permission.mask | bit
27
- end
28
- end
29
- permission.save!
30
- end
31
-
32
- def can?(*args)
33
- options = args.last.is_a?(Hash) ? args.pop : {}
34
- bits = args.map{|name| bit_for(name) }
35
- # scope = nil
36
- # if options[:on]
37
- # scope = scoped(:conditions => ['scoped_object_id = ? AND scoped_object_type = ?', options[:on].id, options[:on].class.to_s])
38
- # else
39
- # scope = scoped(:conditions => ['scoped_object_id IS NULL AND scoped_object_type IS NULL'])
40
- # end
41
- # Skip the trip to the database if the proxy has been loaded up already...
42
- # TODO: Fix this per-scope ... somehow ... probably beyond the scope of this project.
43
- # if @loaded
44
- # bits.all?{|bit| self.select{|permission| permission.mask & bit != 0} }
45
- # else
46
- on(options[:on]).count(:conditions => [bits.map { 'permissive_permissions.mask & ?' }.join(' AND '), *bits]) > 0
47
- # end
48
- end
49
-
50
- def revoke(*args)
51
- options = args.last.is_a?(Hash) ? args.pop : {}
52
- if args.length == 1 && args.first == :all
53
- on(options[:on]).destroy_all
54
- else
55
- bits = args.map{|name| bit_for(name) }
56
- on(options[:on]).each do |permission|
57
- bits.each do |bit|
58
- if permission.mask & bit
59
- permission.mask = permission.mask ^ bit
60
- end
61
- end
62
- permission.save!
63
- end
64
- end
65
- end
66
- end
67
-
68
- if options[:scope]
69
- scope_name = "permissive_#{options[:scope].to_s}"
70
- unless reflection = reflect_on_association(scope_name)
71
- # TODO: There's just no way this should be working. It's WAY too
72
- # fragile. We need support for something more intelligent here,
73
- # like an options hash that includes :scope_type.
74
- namespace = self.to_s.split('::')
75
- if namespace.length > 1
76
- namespace.pop
77
- class_name = namespace.join('::')
78
- else
79
- class_name = ''
80
- end
81
- class_name << "::#{options[:scope].to_s.classify}"
82
- has_many scope_name, :through => :permissions, :source => :scoped_object, :source_type => class_name
83
- end
84
- end
85
-
86
- class_eval do
87
- # Pass calls to the instance down to its permissions collection
88
- # e.g. current_user.can(:view_comments) will bubble to
89
- # current_user.permissions.can(:view_comments)
90
- def can!(*args)
91
- permissions.can!(*args)
92
- end
93
-
94
- # Pass calls to the instance down to its permissions collection
95
- # e.g. current_user.can(:view_comments) will bubble to
96
- # current_user.permissions.can(:view_comments)
97
- def can?(*args)
98
- permissions.can?(*args)
99
- end
100
-
101
- def revoke(*args)
102
- permissions.revoke(*args)
103
- end
104
-
105
- def method_missing(method, *args)
106
- if method.to_s =~ /^can_([^\?]+)\?$/
107
- permissions = $1
108
- options = {}
109
- if permissions =~ /_on$/
110
- permissions.chomp!('_on')
111
- options[:on] = args.shift
112
- end
113
- permissions = permissions.split('_and_')
114
- if permissions.all? {|permission| Permissive::Permissions.hash.has_key?(permission.downcase.to_sym) }
115
- class_eval <<-end_eval
116
- def #{method}#{"(scope)" if options[:on]}
117
- can?(#{[permissions, args].flatten.join(', ').inspect}#{", :on => scope" if options[:on]})
118
- end
119
- end_eval
120
- return can?(*[permissions, options].flatten)
121
- end
122
- end
123
- super
124
- end
125
- end
126
- end
127
- end
128
- end
129
- end
130
- end
131
-
132
- if defined?(ActiveRecord::Base)
133
- ActiveRecord::Base.send :include, Permissive::ActsAsPermissive
134
- end
@@ -1,29 +0,0 @@
1
- # TODO: Abstract this module more later
2
- module Permissive
3
- class PermissionError < StandardError; end;
4
-
5
- module Permissions
6
- class << self
7
- def const_set(*args)
8
- @@hash = nil
9
- super
10
- end
11
-
12
- def hash
13
- @@hash ||= begin
14
- bitwise_hash = constants.inject({}) do |hash, constant_name|
15
- hash[constant_name.downcase] = 2 ** Permissive::Permissions.const_get(constant_name.to_sym)
16
- hash
17
- end
18
- inverted_hash = bitwise_hash.invert
19
- bitwise_hash.values.sort.inject(ActiveSupport::OrderedHash.new) do |hash, value|
20
- hash[inverted_hash[value].to_sym] = value
21
- hash
22
- end
23
- rescue ArgumentError
24
- raise Permissive::PermissionError.new("Permissions must be integers or longs. Strings, symbols, and floats are not currently supported.")
25
- end
26
- end
27
- end
28
- end
29
- end
@@ -1,192 +0,0 @@
1
- require File.join(File.dirname(__FILE__), 'spec_helper')
2
-
3
- # Setup some basic models to test with. We'll set permissions on both,
4
- # and then test :scope'd permissions through both.
5
- class Permissive::Organization < ActiveRecord::Base
6
- set_table_name :permissive_organizations
7
- end
8
-
9
- class Permissive::User < ActiveRecord::Base
10
- set_table_name :permissive_users
11
- end
12
-
13
- describe Permissive::Permission do
14
- before :each do
15
- PermissiveSpecHelper.db_up
16
- end
17
-
18
- after :each do
19
- PermissiveSpecHelper.db_down
20
- end
21
-
22
- describe "`acts_as_permissive' default class method" do
23
- [Permissive::User, Permissive::Organization].each do |model|
24
- before :each do
25
- model.acts_as_permissive
26
- end
27
-
28
- describe model do
29
- it "should create a permissions reflection" do
30
- model.new.should respond_to(:permissions)
31
- end
32
-
33
- it "should create a `can?' method" do
34
- model.new.should respond_to(:can?)
35
- end
36
-
37
- it "should create a `revoke' method" do
38
- model.new.should respond_to(:revoke)
39
- end
40
- end
41
- end
42
- end
43
-
44
- describe "permissions checking" do
45
- before :each do
46
- Permissive::User.acts_as_permissive
47
- @user = Permissive::User.create
48
- end
49
-
50
- it "should allow permissions checks through the `can?' method" do
51
- @user.can?(:edit_organizations).should be_false
52
- end
53
-
54
- it "should respond to the `can!' method" do
55
- @user.should respond_to(:can!)
56
- end
57
-
58
- it "should allow permissions setting through the `can!' method" do
59
- count = @user.permissions.count
60
- @user.can!(:view_users)
61
- @user.permissions.count.should == count.next
62
- end
63
-
64
- it "should return correct permissions through the `can?' method" do
65
- @user.can!(:view_users)
66
- @user.can?(:view_users).should be_true
67
- ['FINALIZE_LAB_SELECTION_LIST', 'SEARCH_APPLICANTS', 'CREATE_BASIC_USER', 'VIEW_BUDGET_REPORT'].each do |permission|
68
- @user.can?(permission).should be_false
69
- end
70
- end
71
-
72
- it "should return correct permissions on multiple requests" do
73
- @user.can!(:view_users)
74
- @user.can!(:view_budget_report)
75
- @user.can?(:view_users, :view_budget_report).should be_true
76
- ['FINALIZE_LAB_SELECTION_LIST', 'SEARCH_APPLICANTS', 'CREATE_BASIC_USER'].each do |permission|
77
- @user.can?(:view_users, permission).should be_false
78
- end
79
- end
80
-
81
- it "should revoke the correct permissions through the `revoke' method" do
82
- @user.can!(:view_users, :view_budget_report)
83
- @user.can?(:view_users).should be_true
84
- @user.can?(:view_budget_report).should be_true
85
- @user.revoke(:view_users)
86
- @user.can?(:view_users).should be_false
87
- @user.can?(:view_budget_report).should be_true
88
- end
89
-
90
- it "should revoke the full permissions through the `revoke' method w/an :all argument" do
91
- @user.can!(:view_users, :view_budget_report)
92
- @user.can?(:view_users).should be_true
93
- @user.can?(:view_budget_report).should be_true
94
- @user.revoke(:all)
95
- @user.can?(:view_users).should be_false
96
- @user.can?(:view_budget_report).should be_false
97
- end
98
- end
99
-
100
- describe "scoped permissions" do
101
- before :each do
102
- Permissive::User.acts_as_permissive(:scope => :organizations)
103
- @user = Permissive::User.create
104
- @organization = Permissive::Organization.create
105
- end
106
-
107
- it "should allow scoped permissions checks through the `can?' method" do
108
- @user.can?(:view_users, :on => @organization).should be_false
109
- end
110
-
111
- it "should return correct permissions through a scoped `can?' method" do
112
- @user.can!(:view_users, :on => @organization)
113
- @user.can?(:view_users, :on => @organization).should be_true
114
- end
115
-
116
- it "should not respond to generic permissions on scoped permissions" do
117
- @user.can!(:view_users, :on => @organization)
118
- @user.can?(:view_users).should be_false
119
- @user.can?(:view_users, :on => @organization).should be_true
120
- end
121
-
122
- it "should revoke the correct permissions through the `revoke' method" do
123
- @user.can!(:view_users, :view_budget_report, :on => @organization)
124
- @user.can?(:view_users, :on => @organization).should be_true
125
- @user.can?(:view_budget_report, :on => @organization).should be_true
126
- @user.revoke(:view_users, :on => @organization)
127
- @user.can?(:view_users, :on => @organization).should be_false
128
- @user.can?(:view_budget_report, :on => @organization).should be_true
129
- end
130
-
131
- it "should revoke the full permissions through the `revoke' method w/an :all argument" do
132
- @user.can!(:create_basic_user)
133
- @user.can!(:view_users, :view_budget_report, :on => @organization)
134
- @user.can?(:view_users, :on => @organization).should be_true
135
- @user.can?(:view_budget_report, :on => @organization).should be_true
136
- @user.can?(:create_basic_user).should be_true
137
- @user.revoke(:all, :on => @organization)
138
- !@user.can?(:view_users, :on => @organization).should be_false
139
- !@user.can?(:view_budget_report, :on => @organization).should be_false
140
- @user.can?(:create_basic_user).should be_true
141
- end
142
-
143
- end
144
-
145
- describe "automatic method creation" do
146
- before :each do
147
- Permissive::User.acts_as_permissive(:scope => :organizations)
148
- @user = Permissive::User.create
149
- @organization = Permissive::Organization.create
150
- @user.can!(:finalize_lab_selection_list)
151
- @user.can!(:create_basic_user)
152
- @user.can!(:view_users, :on => @organization)
153
- end
154
-
155
- it "should not respond to invalid permission methods" do
156
- lambda {
157
- @user.can_finalize_lab_selection_list_fu?
158
- }.should raise_error(NoMethodError)
159
- end
160
-
161
- it "should cache chained methods" do
162
- @user.respond_to?(:can_finalize_lab_selection_list_and_view_users?).should be_false
163
- @user.can_finalize_lab_selection_list_and_view_users?.should be_false
164
- @user.should respond_to(:can_finalize_lab_selection_list_and_view_users?)
165
- end
166
-
167
- it "should respond to valid permission methods" do
168
- @user.can_finalize_lab_selection_list?.should be_true
169
- @user.can_create_basic_user?.should be_true
170
- ['SEARCH_APPLICANTS', 'VIEW_USERS', 'VIEW_BUDGET_REPORT'].each do |permission|
171
- @user.send("can_#{permission.downcase}?").should be_false
172
- end
173
- end
174
-
175
- it "should respond to chained permission methods" do
176
- @user.can_finalize_lab_selection_list_and_create_basic_user?.should be_true
177
- ['SEARCH_APPLICANTS', 'VIEW_USERS', 'VIEW_BUDGET_REPORT'].each do |permission|
178
- @user.send("can_finalize_lab_selection_list_and_#{permission.downcase}?").should be_false
179
- end
180
- end
181
-
182
- it "should respond to scoped permission methods" do
183
- @user.can_view_users_on?(@organization).should be_true
184
- ['FINALIZE_LAB_SELECTION_LIST', 'SEARCH_APPLICANTS', 'CREATE_BASIC_USER', 'VIEW_BUDGET_REPORT'].each do |permission|
185
- @user.send("can_#{permission.downcase}_on?", @organization).should be_false
186
- end
187
- end
188
-
189
- end
190
- end
191
-
192
- PermissiveSpecHelper.clear_log