permissive 0.0.0

Sign up to get free protection for your applications and to get access to all the features.
data/README.markdown ADDED
@@ -0,0 +1,179 @@
1
+ Permissive gives your ActiveRecord models granular permission support
2
+ =
3
+ 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.
7
+
8
+ Installation
9
+ -
10
+
11
+ 1. Get yourself some code. You can install as a gem:
12
+
13
+ `gem install permissive`
14
+
15
+ or as a plugin:
16
+
17
+ `script/plugin install git://github.com/flipsasser/permissive.git`
18
+
19
+ 2. Generate a migration so you can get some sweet table action:
20
+
21
+ `script/generate permissive_migration`
22
+
23
+ `rake db:migrate`
24
+
25
+ Usage
26
+ -
27
+
28
+ First, define a few permissions constants. We'll define them in `Rails.root/config/initializers/permissive.rb`. The best practice is to name them in a verb format that follows this pattern: "Object can `DO_PERMISSION_NAME`".
29
+
30
+ 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.
31
+
32
+ module Permissive::Permissions
33
+ MANAGE_GAMES = 0
34
+ CONTROL_RIDES = 1
35
+ PUNCH = 2
36
+ end
37
+
38
+ And that's all it takes to configure permissions! Now that we have them, let's grant them to a model or two:
39
+
40
+ class Employee < ActiveRecord::Base
41
+ acts_as_permissive
42
+ validates_presence_of :first_name, :last_name
43
+ end
44
+
45
+ class Company < ActiveRecord::Base
46
+ validates_presence_of :name
47
+ end
48
+
49
+ Easy-peasy, right? Let's try granting a few permissions:
50
+
51
+ @james = Employee.create(:first_name => 'James', :last_name => 'Brennan')
52
+ @frigo = Employee.create(:first_name => 'Tommy', :last_name => 'Frigo')
53
+ @adventureland = Company.create(:name => 'Adventureland')
54
+
55
+ # Okay, let's do some granting. We'll start by scoping to a specific company.
56
+ @james.can!(:manage_games, :on => @adventureland)
57
+
58
+ # Now let's do some permission checking.
59
+ @james.can?(:manage_games, :on => @adventureland) #=> true
60
+
61
+ # We can also use the metaprogramming syntax:
62
+ @james.can_manage_games_on?(@adventureland) #=> true
63
+ @james.can_control_rides_on?(@adventureland) #=> false
64
+
65
+ # We can check for multiple permissions, too:
66
+ @james.can?(:manage_games, :control_rides) #=> false
67
+ # OR:
68
+ @james.can_manage_games_and_control_rides?
69
+
70
+ # Scoping can be done through any object
71
+ @frigo.can!(:punch, :on => @james)
72
+ @frigo.can_punch_on?(@james) #=> true
73
+
74
+ # And the permissions aren't reciprocal
75
+ @james.can_punch_on?(@frigo) #=> false
76
+
77
+ # Of course, we can grant global (non-scoped) permissions, too:
78
+ @frigo.can!(:control_rides)
79
+ @frigo.can_control_rides? #=> true
80
+
81
+ # BUT! Global permissions don't override scoped permissions.
82
+ @frigo.can_control_rides_on?(@adventureland) #=> false
83
+
84
+ # Likewise, scoped permissions don't bubble up globally:
85
+ @james.can_manage_games? #=> false
86
+
87
+ # And, last but not least, let's take all of those great permissions away:
88
+ @james.revoke(:manage_games, :on => @adventureland)
89
+
90
+ # We can revoke all permissions, in any scope, too:
91
+ @frigo.revoke(:all)
92
+
93
+ And that's it!
94
+
95
+ Scoping
96
+ -
97
+
98
+ Permissive supports scoping at the class-configuration level, which adds relationships to permitted objects:
99
+
100
+ class Employee < ActiveRecord::Base
101
+ acts_as_permissive :scope => :company
102
+ end
103
+
104
+ @frigo.permissive_companies #=> [Company 1, Company 2]
105
+
106
+ Replacing Permissions
107
+ -
108
+
109
+ Sometimes you want to overwrite all previous permissions in a can! method. That's pretty easy: just add :reset => true to the options.
110
+
111
+ @frigo.can!(:control_rides, :on => @adventureland, :reset => true)
112
+
113
+ Next Steps
114
+ -
115
+
116
+ 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 `has_many` relationship. `@employee.can!(:do_anything)` will still work, as will `@employee.can!(:do_something, :on => @something_that_isnt_a_company)`. That's pretty confusing to me. Adding more granular permissions might be cooler:
117
+
118
+ class Employee < ActiveRecord::Base
119
+ has_permissions do
120
+ on :companies
121
+ on :employees
122
+ end
123
+ end
124
+
125
+ which might yield something like
126
+
127
+ @employee.permissive_companies
128
+ # and
129
+ @employee.can_control_rides_in_company @adventureland
130
+
131
+ I'd also like to support a more intelligent grammar:
132
+
133
+ @james.can_punch? @frigo
134
+ @frigo.can!(:control_rides, :in => @adventureland)
135
+
136
+ Meta-programmed methods for granting and revoking would be cool, too:
137
+
138
+ @james.can_punch! @frigo
139
+ @frigo.cannot_control_rides_in! @adventureland
140
+
141
+ And while we're on the subject of metaprogramming, let's add some OR-ing to the whole thing:
142
+
143
+ @james.can_control_rides_or_manage_games_in? @adventureland
144
+
145
+ I'd also like to enable Permissive::Templates (pre-set permission groups, like roles):
146
+
147
+ administrator = Permissive::Template.named('Administrator')
148
+ @james.acts_like administrator
149
+
150
+ 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:
151
+
152
+ <%= check_box_tag("employee[permissions][]", Permissive::Permissions::CONTROL_RIDES, @employee.can_control_rides?) %> Control rides
153
+
154
+ .. and in the controller:
155
+
156
+ def update
157
+ @employee.can!(params[:employees].delete(:permissions), :revert => true)
158
+ respond_to do |format|
159
+ ...
160
+ end
161
+ end
162
+
163
+ Finally, I'd like to use the `grant_mask` 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.:
164
+
165
+ <% current_user.grant_permissions.each do |permission| %>
166
+ <!-- Do something! -->
167
+ <% end %>
168
+
169
+ and second, write-time checking of grantor permissions. Something like this, maybe:
170
+
171
+ def update
172
+ current_user.grant(params[:employees][:permissions], :to => @employee)
173
+ end
174
+
175
+ which would allow the Permissive::Permission model to make sure whatever `current_user` is granting to @employee, they're **allowed** to grant to @employee.
176
+
177
+ 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!
178
+
179
+ Copyright (c) 2009 Flip Sasser, released under the MIT license
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.0.0
@@ -0,0 +1,192 @@
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
@@ -0,0 +1,44 @@
1
+ require File.join(File.dirname(__FILE__), 'spec_helper')
2
+ describe Permissive::Permissions do
3
+ before :each do
4
+ PermissiveSpecHelper.db_up
5
+ end
6
+
7
+ after :each do
8
+ PermissiveSpecHelper.db_down
9
+ end
10
+
11
+ context "permission constants" do
12
+ it "should have a `hash' method" do
13
+ Permissive::Permissions.should respond_to(:hash)
14
+ end
15
+
16
+ it "should return an ordered hash when `hash' is called" do
17
+ Permissive::Permissions.hash.should be_instance_of(ActiveSupport::OrderedHash)
18
+ end
19
+
20
+ it "should have symbol keys for the permission constants" do
21
+ Permissive::Permissions.hash.has_key?(:finalize_lab_selection_list).should be_true
22
+ end
23
+
24
+ it "should convert all CONSTANT values to base-2 compatible integers" do
25
+ Permissive::Permissions.constants.each do |constant|
26
+ Permissive::Permissions.hash[constant.downcase.to_sym].should == 2 ** Permissive::Permissions.const_get(constant)
27
+ end
28
+ end
29
+
30
+ it "should explode when a constant isn't Numeric" do
31
+ Permissive::Permissions.const_set('FOOBAR', 'achoo')
32
+ lambda {
33
+ Permissive::Permissions.hash
34
+ }.should raise_error(Permissive::PermissionError)
35
+
36
+ Permissive::Permissions.const_set('FOOBAR', 5)
37
+ lambda {
38
+ Permissive::Permissions.hash
39
+ }.should_not raise_error(Permissive::PermissionError)
40
+ end
41
+ end
42
+ end
43
+
44
+ PermissiveSpecHelper.clear_log
@@ -0,0 +1,59 @@
1
+ require 'rubygems'
2
+ require 'activerecord'
3
+ require 'permissive'
4
+
5
+ module PermissiveSpecHelper
6
+ def self.clear_log
7
+ File.open(PermissiveSpecHelper.log_path, 'w') do |file|
8
+ file.puts ''
9
+ end
10
+ end
11
+
12
+ def self.db_down
13
+ File.unlink(db) if File.exists?(db)
14
+ end
15
+
16
+ def self.db_up
17
+ db_down
18
+ ActiveRecord::Base.establish_connection({:adapter => 'sqlite3', :database => db, :pool => 5, :timeout => 5000})
19
+ silence_stream(STDOUT) do
20
+ ActiveRecord::Schema.define do
21
+ create_table :permissive_users, :force => true do |t|
22
+ t.timestamps
23
+ end
24
+ create_table :permissive_organizations, :force => true do |t|
25
+ t.timestamps
26
+ end
27
+ create_table :permissive_permissions do |t|
28
+ t.integer :permitted_object_id
29
+ t.string :permitted_object_type, :limit => 32
30
+ t.integer :scoped_object_id
31
+ t.string :scoped_object_type, :limit => 32
32
+ t.integer :mask, :default => 0
33
+ t.integer :grant_mask, :default => 0
34
+ end
35
+ end
36
+ end
37
+ end
38
+
39
+ def self.log_path
40
+ File.join(File.dirname(__FILE__), 'spec.log')
41
+ end
42
+
43
+ private
44
+ def self.db
45
+ @@db ||= File.expand_path(File.join(File.dirname(__FILE__), 'test.sqlite3'))
46
+ end
47
+ end
48
+
49
+ # Setup some test permissions
50
+ module Permissive::Permissions
51
+ FINALIZE_LAB_SELECTION_LIST = 0
52
+ SEARCH_APPLICANTS = 1
53
+ CREATE_BASIC_USER = 2
54
+ VIEW_USERS = 3
55
+ VIEW_BUDGET_REPORT = 4
56
+ end
57
+
58
+ # Setup the logging
59
+ ActiveRecord::Base.logger = Logger.new(PermissiveSpecHelper.log_path)
metadata ADDED
@@ -0,0 +1,63 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: permissive
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Flip Sasser
8
+ - Simon Parsons
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+
13
+ date: 2009-11-01 00:00:00 -04:00
14
+ default_executable:
15
+ dependencies: []
16
+
17
+ description: |-
18
+ Permissive combines a model-based permissions system with bitmasking to
19
+ create a flexible approach to maintaining permissions on your ActiveRecord
20
+ models. It supports an easy-to-use set of methods for accessing and
21
+ determining permissions, including some fun metaprogramming.
22
+ email: flip@x451.com
23
+ executables: []
24
+
25
+ extensions: []
26
+
27
+ extra_rdoc_files:
28
+ - README.markdown
29
+ files:
30
+ - VERSION
31
+ - README.markdown
32
+ has_rdoc: true
33
+ homepage: http://github.com/flipsasser/permissive
34
+ licenses: []
35
+
36
+ post_install_message:
37
+ rdoc_options:
38
+ - --charset=UTF-8
39
+ require_paths:
40
+ - lib
41
+ required_ruby_version: !ruby/object:Gem::Requirement
42
+ requirements:
43
+ - - ">="
44
+ - !ruby/object:Gem::Version
45
+ version: "0"
46
+ version:
47
+ required_rubygems_version: !ruby/object:Gem::Requirement
48
+ requirements:
49
+ - - ">="
50
+ - !ruby/object:Gem::Version
51
+ version: "0"
52
+ version:
53
+ requirements: []
54
+
55
+ rubyforge_project:
56
+ rubygems_version: 1.3.5
57
+ signing_key:
58
+ specification_version: 3
59
+ summary: Permissive gives your ActiveRecord models granular permission support
60
+ test_files:
61
+ - spec/acts_as_permissive_spec.rb
62
+ - spec/permissions_spec.rb
63
+ - spec/spec_helper.rb