permissive 0.0.0 → 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gemspec +45 -0
- data/.gitignore +2 -0
- data/MIT-LICENSE +20 -0
- data/README.markdown +18 -18
- data/README.markdown.html +191 -0
- data/Rakefile +36 -0
- data/VERSION +1 -1
- data/generators/permissive_migration/USAGE +7 -0
- data/generators/permissive_migration/permissive_migration_generator.rb +7 -0
- data/generators/permissive_migration/templates/permissive_migration.rb +20 -0
- data/lib/permissive.rb +15 -0
- data/lib/permissive/acts_as_permissive.rb +134 -0
- data/lib/permissive/permission.rb +43 -0
- data/lib/permissive/permissions.rb +29 -0
- data/rails/init.rb +2 -0
- data/spec/rcov.opts +2 -0
- data/spec/spec.opts +3 -0
- metadata +21 -2
data/.gemspec
ADDED
@@ -0,0 +1,45 @@
|
|
1
|
+
# Generated by jeweler
|
2
|
+
# DO NOT EDIT THIS FILE DIRECTLY
|
3
|
+
# Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
|
4
|
+
# -*- encoding: utf-8 -*-
|
5
|
+
|
6
|
+
Gem::Specification.new do |s|
|
7
|
+
s.name = %q{permissive}
|
8
|
+
s.version = "0.0.1"
|
9
|
+
|
10
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
|
+
s.authors = ["Flip Sasser", "Simon Parsons"]
|
12
|
+
s.date = %q{2009-11-01}
|
13
|
+
s.description = %q{Permissive combines a model-based permissions system with bitmasking to
|
14
|
+
create a flexible approach to maintaining permissions on your ActiveRecord
|
15
|
+
models. It supports an easy-to-use set of methods for accessing and
|
16
|
+
determining permissions, including some fun metaprogramming.}
|
17
|
+
s.email = %q{flip@x451.com}
|
18
|
+
s.extra_rdoc_files = [
|
19
|
+
"README.markdown"
|
20
|
+
]
|
21
|
+
s.files = [
|
22
|
+
"VERSION"
|
23
|
+
]
|
24
|
+
s.homepage = %q{http://github.com/flipsasser/permissive}
|
25
|
+
s.rdoc_options = ["--charset=UTF-8"]
|
26
|
+
s.require_paths = ["lib"]
|
27
|
+
s.rubygems_version = %q{1.3.5}
|
28
|
+
s.summary = %q{Permissive gives your ActiveRecord models granular permission support}
|
29
|
+
s.test_files = [
|
30
|
+
"spec/acts_as_permissive_spec.rb",
|
31
|
+
"spec/permissions_spec.rb",
|
32
|
+
"spec/spec_helper.rb"
|
33
|
+
]
|
34
|
+
|
35
|
+
if s.respond_to? :specification_version then
|
36
|
+
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
37
|
+
s.specification_version = 3
|
38
|
+
|
39
|
+
if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
|
40
|
+
else
|
41
|
+
end
|
42
|
+
else
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
data/.gitignore
ADDED
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2009 Flip Sasser
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.markdown
CHANGED
@@ -13,7 +13,7 @@ Installation
|
|
13
13
|
`gem install permissive`
|
14
14
|
|
15
15
|
or as a plugin:
|
16
|
-
|
16
|
+
|
17
17
|
`script/plugin install git://github.com/flipsasser/permissive.git`
|
18
18
|
|
19
19
|
2. Generate a migration so you can get some sweet table action:
|
@@ -30,20 +30,20 @@ First, define a few permissions constants. We'll define them in `Rails.root/conf
|
|
30
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
31
|
|
32
32
|
module Permissive::Permissions
|
33
|
-
|
34
|
-
|
35
|
-
|
33
|
+
MANAGE_GAMES = 0
|
34
|
+
CONTROL_RIDES = 1
|
35
|
+
PUNCH = 2
|
36
36
|
end
|
37
37
|
|
38
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
39
|
|
40
40
|
class Employee < ActiveRecord::Base
|
41
|
-
|
42
|
-
|
41
|
+
acts_as_permissive
|
42
|
+
validates_presence_of :first_name, :last_name
|
43
43
|
end
|
44
44
|
|
45
45
|
class Company < ActiveRecord::Base
|
46
|
-
|
46
|
+
validates_presence_of :name
|
47
47
|
end
|
48
48
|
|
49
49
|
Easy-peasy, right? Let's try granting a few permissions:
|
@@ -98,7 +98,7 @@ Scoping
|
|
98
98
|
Permissive supports scoping at the class-configuration level, which adds relationships to permitted objects:
|
99
99
|
|
100
100
|
class Employee < ActiveRecord::Base
|
101
|
-
|
101
|
+
acts_as_permissive :scope => :company
|
102
102
|
end
|
103
103
|
|
104
104
|
@frigo.permissive_companies #=> [Company 1, Company 2]
|
@@ -116,10 +116,10 @@ Next Steps
|
|
116
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
117
|
|
118
118
|
class Employee < ActiveRecord::Base
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
119
|
+
has_permissions do
|
120
|
+
on :companies
|
121
|
+
on :employees
|
122
|
+
end
|
123
123
|
end
|
124
124
|
|
125
125
|
which might yield something like
|
@@ -154,10 +154,10 @@ Next up! I currently use a manual reset to grant permissions through a controlle
|
|
154
154
|
.. and in the controller:
|
155
155
|
|
156
156
|
def update
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
157
|
+
@employee.can!(params[:employees].delete(:permissions), :revert => true)
|
158
|
+
respond_to do |format|
|
159
|
+
...
|
160
|
+
end
|
161
161
|
end
|
162
162
|
|
163
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.:
|
@@ -169,11 +169,11 @@ Finally, I'd like to use the `grant_mask` support that exists on the Permissive:
|
|
169
169
|
and second, write-time checking of grantor permissions. Something like this, maybe:
|
170
170
|
|
171
171
|
def update
|
172
|
-
|
172
|
+
current_user.grant(params[:employees][:permissions], :to => @employee)
|
173
173
|
end
|
174
174
|
|
175
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
176
|
|
177
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
178
|
|
179
|
-
Copyright (c) 2009 Flip Sasser, released under the MIT license
|
179
|
+
Copyright (c) 2009 Flip Sasser & Simon Parsons, released under the MIT license
|
@@ -0,0 +1,191 @@
|
|
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 < ActiveRecord::Base
|
42
|
+
acts_as_permissive
|
43
|
+
validates_presence_of :first_name, :last_name
|
44
|
+
end
|
45
|
+
|
46
|
+
class Company < 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 => 'James', :last_name => 'Brennan')
|
54
|
+
@frigo = Employee.create(:first_name => 'Tommy', :last_name => 'Frigo')
|
55
|
+
@adventureland = Company.create(:name => 'Adventureland')
|
56
|
+
|
57
|
+
# Okay, let's do some granting. We'll start by scoping to a specific company.
|
58
|
+
@james.can!(:manage_games, :on => @adventureland)
|
59
|
+
|
60
|
+
# Now let's do some permission checking.
|
61
|
+
@james.can?(:manage_games, :on => @adventureland) #=> true
|
62
|
+
|
63
|
+
# We can also use the metaprogramming syntax:
|
64
|
+
@james.can_manage_games_on?(@adventureland) #=> true
|
65
|
+
@james.can_control_rides_on?(@adventureland) #=> false
|
66
|
+
|
67
|
+
# We can check for multiple permissions, too:
|
68
|
+
@james.can?(:manage_games, :control_rides) #=> 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 => @james)
|
74
|
+
@frigo.can_punch_on?(@james) #=> true
|
75
|
+
|
76
|
+
# And the permissions aren't reciprocal
|
77
|
+
@james.can_punch_on?(@frigo) #=> false
|
78
|
+
|
79
|
+
# Of course, we can grant global (non-scoped) permissions, too:
|
80
|
+
@frigo.can!(:control_rides)
|
81
|
+
@frigo.can_control_rides? #=> true
|
82
|
+
|
83
|
+
# BUT! Global permissions don't override scoped permissions.
|
84
|
+
@frigo.can_control_rides_on?(@adventureland) #=> false
|
85
|
+
|
86
|
+
# Likewise, scoped permissions don't bubble up globally:
|
87
|
+
@james.can_manage_games? #=> false
|
88
|
+
|
89
|
+
# And, last but not least, let's take all of those great permissions away:
|
90
|
+
@james.revoke(:manage_games, :on => @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 < ActiveRecord::Base
|
103
|
+
acts_as_permissive :scope => :company
|
104
|
+
end
|
105
|
+
|
106
|
+
@frigo.permissive_companies #=> [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 => @adventureland, :reset => 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 => @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 < 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 => @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><%= check_box_tag("employee[permissions][]", Permissive::Permissions::CONTROL_RIDES, @employee.can_control_rides?) %> 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 => 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><% current_user.grant_permissions.each do |permission| %>
|
176
|
+
<!-- Do something! -->
|
177
|
+
<% end %>
|
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 => @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>
|
data/Rakefile
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
require 'rake'
|
2
|
+
require 'spec/rake/spectask'
|
3
|
+
|
4
|
+
desc 'Default: run specs.'
|
5
|
+
task :default => :spec
|
6
|
+
|
7
|
+
desc 'Run the specs'
|
8
|
+
Spec::Rake::SpecTask.new(:spec) do |t|
|
9
|
+
t.spec_opts = ['--colour --format progress --loadby mtime --reverse']
|
10
|
+
t.spec_files = FileList['spec/**/*_spec.rb']
|
11
|
+
end
|
12
|
+
|
13
|
+
namespace :spec do
|
14
|
+
desc 'Run the specs with Rcov output'
|
15
|
+
Spec::Rake::SpecTask.new(:rcov) do |t|
|
16
|
+
t.spec_files = FileList['spec/**/*.rb']
|
17
|
+
t.rcov = true
|
18
|
+
t.rcov_opts = ['--exclude', 'examples']
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
begin
|
23
|
+
require 'jeweler'
|
24
|
+
Jeweler::Tasks.new do |gemspec|
|
25
|
+
gemspec.name = "permissive"
|
26
|
+
gemspec.summary = "Permissive gives your ActiveRecord models granular permission support"
|
27
|
+
gemspec.description = %{Permissive combines a model-based permissions system with bitmasking to
|
28
|
+
create a flexible approach to maintaining permissions on your ActiveRecord
|
29
|
+
models. It supports an easy-to-use set of methods for accessing and
|
30
|
+
determining permissions, including some fun metaprogramming.}
|
31
|
+
gemspec.email = "flip@x451.com"
|
32
|
+
gemspec.homepage = "http://github.com/flipsasser/permissive"
|
33
|
+
gemspec.authors = ["Flip Sasser", "Simon Parsons"]
|
34
|
+
end
|
35
|
+
rescue LoadError
|
36
|
+
end
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.0.
|
1
|
+
0.0.1
|
@@ -0,0 +1,7 @@
|
|
1
|
+
Usage:
|
2
|
+
|
3
|
+
script/generate permissive_migration
|
4
|
+
|
5
|
+
This will create a migration that will add the Permissive tables to your
|
6
|
+
application. You can modify this migration to your heart's content. When
|
7
|
+
you're done, run `rake db:migrate` and Permissive will be ready to go!
|
@@ -0,0 +1,20 @@
|
|
1
|
+
class InstallPermissive < ActiveRecord::Migration
|
2
|
+
def self.up
|
3
|
+
create_table :permissive_permissions do |t|
|
4
|
+
t.integer :permitted_object_id
|
5
|
+
t.string :permitted_object_type, :limit => 32
|
6
|
+
t.integer :scoped_object_id
|
7
|
+
t.string :scoped_object_type, :limit => 32
|
8
|
+
t.integer :mask, :default => 0
|
9
|
+
t.integer :grant_mask, :default => 0
|
10
|
+
end
|
11
|
+
add_index :permissive_permissions, [:permitted_object_id, :permitted_object_type], :name => 'permissive_permitted'
|
12
|
+
add_index :permissive_permissions, [:scoped_object_id, :scoped_object_type], :name => 'permissive_scoped'
|
13
|
+
add_index :permissive_permissions, :mask, :name => 'permissive_masks'
|
14
|
+
add_index :permissive_permissions, :grant_mask, :name => 'permissive_grant_masks'
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.down
|
18
|
+
drop_table :permissive_permissions
|
19
|
+
end
|
20
|
+
end
|
data/lib/permissive.rb
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'permissive/permission'
|
2
|
+
require 'permissive/acts_as_permissive'
|
3
|
+
require 'permissive/permissions'
|
4
|
+
|
5
|
+
module Permissive
|
6
|
+
@@strong = false
|
7
|
+
|
8
|
+
def self.strong
|
9
|
+
@@strong
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.strong=(new_strong)
|
13
|
+
@@strong = !!new_strong
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,134 @@
|
|
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
|
@@ -0,0 +1,43 @@
|
|
1
|
+
# This is the core permission class that Permissive uses.
|
2
|
+
module Permissive
|
3
|
+
class Permission < ActiveRecord::Base
|
4
|
+
attr_writer :grant_template, :template
|
5
|
+
belongs_to :permitted_object, :polymorphic => true
|
6
|
+
belongs_to :scoped_object, :polymorphic => true
|
7
|
+
named_scope :on, lambda {|scoped_object|
|
8
|
+
if scoped_object.nil?
|
9
|
+
{:conditions => ['scoped_object_id IS NULL AND scoped_object_type IS NULL']}
|
10
|
+
else
|
11
|
+
{:conditions => ['scoped_object_id = ? AND scoped_object_type = ?', scoped_object.id, scoped_object.class.to_s]}
|
12
|
+
end
|
13
|
+
}
|
14
|
+
set_table_name :permissive_permissions
|
15
|
+
validates_presence_of :grant_mask, :mask, :permitted_object
|
16
|
+
|
17
|
+
class << self
|
18
|
+
# Use this anywhere!
|
19
|
+
def bit_for(permission)
|
20
|
+
Permissive::Permissions.hash[permission.to_s.downcase.to_sym] || 0
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
protected
|
25
|
+
def before_save
|
26
|
+
# Save permission templates or "Roles"
|
27
|
+
if @grant_template
|
28
|
+
grant_mask = @grant_template
|
29
|
+
end
|
30
|
+
if @template
|
31
|
+
mask = @template
|
32
|
+
end
|
33
|
+
|
34
|
+
# If Permissive is set to be seriously intense about who can grant what to
|
35
|
+
# whom, it makes sure no bits on the grant_mask exceed those of the
|
36
|
+
# permission mask
|
37
|
+
# TODO: You know ... this.
|
38
|
+
# if grant_mask && Permissive.strong
|
39
|
+
# grant_mask = grant_mask & mask
|
40
|
+
# end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,29 @@
|
|
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
|
data/rails/init.rb
ADDED
data/spec/rcov.opts
ADDED
data/spec/spec.opts
ADDED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: permissive
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Flip Sasser
|
@@ -26,9 +26,28 @@ extensions: []
|
|
26
26
|
|
27
27
|
extra_rdoc_files:
|
28
28
|
- README.markdown
|
29
|
+
- README.markdown.html
|
29
30
|
files:
|
30
|
-
-
|
31
|
+
- .gemspec
|
32
|
+
- .gitignore
|
33
|
+
- MIT-LICENSE
|
31
34
|
- README.markdown
|
35
|
+
- Rakefile
|
36
|
+
- VERSION
|
37
|
+
- generators/permissive_migration/USAGE
|
38
|
+
- generators/permissive_migration/permissive_migration_generator.rb
|
39
|
+
- generators/permissive_migration/templates/permissive_migration.rb
|
40
|
+
- lib/permissive.rb
|
41
|
+
- lib/permissive/acts_as_permissive.rb
|
42
|
+
- lib/permissive/permission.rb
|
43
|
+
- lib/permissive/permissions.rb
|
44
|
+
- rails/init.rb
|
45
|
+
- spec/acts_as_permissive_spec.rb
|
46
|
+
- spec/permissions_spec.rb
|
47
|
+
- spec/rcov.opts
|
48
|
+
- spec/spec.opts
|
49
|
+
- spec/spec_helper.rb
|
50
|
+
- README.markdown.html
|
32
51
|
has_rdoc: true
|
33
52
|
homepage: http://github.com/flipsasser/permissive
|
34
53
|
licenses: []
|