permissive 0.0.0

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 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