restful_acl 2.0.6
Sign up to get free protection for your applications and to get access to all the features.
- data/MIT-LICENSE +20 -0
- data/README.textile +168 -0
- data/Rakefile +22 -0
- data/init.rb +1 -0
- data/install.rb +1 -0
- data/lib/restful_acl_controller.rb +100 -0
- data/lib/restful_acl_helper.rb +62 -0
- data/lib/restful_acl_model.rb +52 -0
- data/rails/init.rb +9 -0
- data/uninstall.rb +1 -0
- metadata +64 -0
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2008 Matt Darby
|
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.textile
ADDED
@@ -0,0 +1,168 @@
|
|
1
|
+
h2. RESTful_ACL
|
2
|
+
|
3
|
+
A Ruby on Rails plugin that provides fine grained access control through the MVC stack to RESTful resources in a Ruby on Rails 2.0+ application. Authorization is as simple as true or false.
|
4
|
+
|
5
|
+
h3. What it does
|
6
|
+
|
7
|
+
RESTful_ACL is a simple Access Control Layer for Ruby on Rails. It restricts access on a fine-grained level to any RESTful MVC stack. Every application is different and everyone likes to setup their User / Account / Role resources differently; this plugin will allow you to do your thing and keep that thing locked down.
|
8
|
+
|
9
|
+
h3. Requirements
|
10
|
+
|
11
|
+
RESTful_ACL requires the super amazing "RESTful_Authentication":https://github.com/technoweenie/restful-authentication plugin.
|
12
|
+
|
13
|
+
h3. How to Install
|
14
|
+
|
15
|
+
Install the RESTful_ACL gem:
|
16
|
+
<pre>sudo gem install mdarby-restful_acl -s http://gems.github.com</pre>
|
17
|
+
|
18
|
+
Add the gem to your environment.rb file as thus:
|
19
|
+
<pre>config.gem "mdarby-restful_acl", :lib => 'restful_acl_controller'</pre>
|
20
|
+
|
21
|
+
RESTful_ACL requires two named routes: "error" and "denied". Add the following to your routes.rb file:
|
22
|
+
<pre>
|
23
|
+
map.error '/error', :controller => 'some_controller', :action => 'error_action'
|
24
|
+
map.denied '/denied', :controller => 'some_controller', :action => 'denied_action'
|
25
|
+
</pre>
|
26
|
+
|
27
|
+
h3. How to Use
|
28
|
+
|
29
|
+
h4. Controllers
|
30
|
+
|
31
|
+
Add @before_filter :has_permission?@ into any controller that you'd like to restrict access to (or application_controller.rb for your entire app).
|
32
|
+
|
33
|
+
h4. Models
|
34
|
+
|
35
|
+
Define a parent resource (if one exists) by using the @logical_parent@ method, and define the following five methods in the model of every resource you'd like to restrict access to. The five methods can contain anything you'd like so long as they return a boolean true or false. This allows you to define your User's roles any way you wish.
|
36
|
+
|
37
|
+
<pre>
|
38
|
+
class Issue < ActiveRecord::Base
|
39
|
+
logical_parent :some_model_name
|
40
|
+
|
41
|
+
# This method checks permissions for the :index action
|
42
|
+
def self.is_indexable_by(user, parent = nil)
|
43
|
+
|
44
|
+
end
|
45
|
+
|
46
|
+
# This method checks permissions for the :create and :new action
|
47
|
+
def self.is_creatable_by(user, parent = nil)
|
48
|
+
|
49
|
+
end
|
50
|
+
|
51
|
+
# This method checks permissions for the :show action
|
52
|
+
def is_readable_by(user, parent = nil)
|
53
|
+
|
54
|
+
end
|
55
|
+
|
56
|
+
# This method checks permissions for the :update and :edit action
|
57
|
+
def is_updatable_by(user, parent = nil)
|
58
|
+
|
59
|
+
end
|
60
|
+
|
61
|
+
# This method checks permissions for the :destroy action
|
62
|
+
def is_deletable_by(user, parent = nil)
|
63
|
+
|
64
|
+
end
|
65
|
+
end
|
66
|
+
</pre>
|
67
|
+
|
68
|
+
h4. View Helpers
|
69
|
+
|
70
|
+
There are five view helpers also included in RESTful_ACL: @#indexable@, @#creatable@, @#readable@, @#updatable@, and @#deletable@. These enable you to do nifty things like:
|
71
|
+
<pre>
|
72
|
+
<%= link_to ‘Foo Index’, foos_path if indexable %>
|
73
|
+
<%= link_to 'Edit Foo', edit_foo_path(@foo) if updatable(@foo) %>
|
74
|
+
<%= link_to 'Create Foo', new_foo_path if creatable %>
|
75
|
+
<%= link_to 'View Foo', foo_path(@foo) if readable(@foo) %>
|
76
|
+
<%= link_to 'Delete Foo', foo_path(@foo) if deletable(@foo), :method => :destroy %>
|
77
|
+
</pre>
|
78
|
+
|
79
|
+
h3. Huh? Here's an example
|
80
|
+
|
81
|
+
Let's say that you have two resources: Project and Issue. A Project has many Issues, an Issue belongs to a Project. I'd like to make sure that the current user is a member of the Project before they can create a new Issue in that Project:
|
82
|
+
|
83
|
+
<pre>
|
84
|
+
class Issue < ActiveRecord::Base
|
85
|
+
logical_parent :project
|
86
|
+
|
87
|
+
belongs_to :author
|
88
|
+
belongs_to :project
|
89
|
+
|
90
|
+
def self.is_indexable_by(user, parent = nil)
|
91
|
+
user.projects.include?(parent)
|
92
|
+
end
|
93
|
+
|
94
|
+
def self.is_creatable_by(user, parent = nil)
|
95
|
+
user.projects.include?(parent)
|
96
|
+
end
|
97
|
+
|
98
|
+
def is_updatable_by(user, parent = nil)
|
99
|
+
user == author && parent.is_active?
|
100
|
+
end
|
101
|
+
|
102
|
+
def is_deletable_by(user, parent = nil)
|
103
|
+
user == author
|
104
|
+
end
|
105
|
+
|
106
|
+
def is_readable_by(user, parent = nil)
|
107
|
+
user.projects.include?(parent)
|
108
|
+
end
|
109
|
+
end
|
110
|
+
</pre>
|
111
|
+
|
112
|
+
h3. Admins RULE!
|
113
|
+
|
114
|
+
RESTful_ACL grants global access to all actions to site administrators. To enable this, make sure that your User model defines an @is_admin?@ method *and/or* an @is_admin@ attribute. If the @current_user.is_admin?@ returns true, access will be granted automatically.
|
115
|
+
|
116
|
+
h3. How to Test
|
117
|
+
|
118
|
+
I normally do something along these lines in RSpec:
|
119
|
+
<pre>
|
120
|
+
describe "Issue" do
|
121
|
+
before do
|
122
|
+
@project = mock_model(Project)
|
123
|
+
@author = mock_model(User, :projects => [@project])
|
124
|
+
|
125
|
+
@issue = Issue.factory_girl(:issue, :author => @author, :project => @project)
|
126
|
+
end
|
127
|
+
|
128
|
+
it "should be modifiable by the author when the Project is active" do
|
129
|
+
@project.stub!(:is_active? => true)
|
130
|
+
@issue.is_updatable_by(@author, @project).should be_true
|
131
|
+
end
|
132
|
+
|
133
|
+
it "should be deletable by the author" do
|
134
|
+
@issue.is_deletable_by(@author, @project).should be_true
|
135
|
+
end
|
136
|
+
|
137
|
+
it "should be readable by those assigned to the Project" do
|
138
|
+
Issue.is_readable_by(@author, @project).should be_true
|
139
|
+
end
|
140
|
+
|
141
|
+
it "should be creatable by those assigned to the Project" do
|
142
|
+
Issue.is_creatable_by(@author, @project).should be_true
|
143
|
+
end
|
144
|
+
end
|
145
|
+
</pre>
|
146
|
+
|
147
|
+
h3. Caveats
|
148
|
+
|
149
|
+
RESTful_ACL doesn't work with nested singleton resources. Wha? Yeah. Those are things in routes.rb like:
|
150
|
+
|
151
|
+
<pre>
|
152
|
+
# Note the singular forms in 'user.resource :profile'
|
153
|
+
map.resources :users do |user|
|
154
|
+
user.resource :profile
|
155
|
+
end
|
156
|
+
</pre>
|
157
|
+
|
158
|
+
In these situations I normally skip permission checking altogether as a Profile will always be mapped to the currently logged in User, regardless of the @params[:user_id]@ passed in. You don't trust those either right? Good.
|
159
|
+
|
160
|
+
h3. Help
|
161
|
+
|
162
|
+
Add a ticket to "RESTful_ACL's Lighthouse Account":http://mdarby.lighthouseapp.com/projects/28698-restful_acl/overview
|
163
|
+
|
164
|
+
h3. About the Author
|
165
|
+
|
166
|
+
My name is "Matt Darby.":http://blog.matt-darby.com I’m an IT Manager and pro-web-dev at for "Dynamix Engineering":http://dynamix-ltd.com and hold a Master’s Degree in Computer Science from "Franklin University":http://www.franklin.edu in sunny "Columbus, OH.":http://en.wikipedia.org/wiki/Columbus,_Ohio
|
167
|
+
|
168
|
+
Feel free to check out my "blog":http://blog.matt-darby.com or "recommend me":http://www.workingwithrails.com/person/10908-matt-darby
|
data/Rakefile
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'rake'
|
2
|
+
require 'rake/testtask'
|
3
|
+
require 'rake/rdoctask'
|
4
|
+
|
5
|
+
desc 'Default: run unit tests.'
|
6
|
+
task :default => :test
|
7
|
+
|
8
|
+
desc 'Test the restful_acl plugin.'
|
9
|
+
Rake::TestTask.new(:test) do |t|
|
10
|
+
t.libs << 'lib'
|
11
|
+
t.pattern = 'test/**/*_test.rb'
|
12
|
+
t.verbose = true
|
13
|
+
end
|
14
|
+
|
15
|
+
desc 'Generate documentation for the restful_acl plugin.'
|
16
|
+
Rake::RDocTask.new(:rdoc) do |rdoc|
|
17
|
+
rdoc.rdoc_dir = 'rdoc'
|
18
|
+
rdoc.title = 'RestfulAcl'
|
19
|
+
rdoc.options << '--line-numbers' << '--inline-source'
|
20
|
+
rdoc.rdoc_files.include('README')
|
21
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
22
|
+
end
|
data/init.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require File.dirname(__FILE__) + "/rails/init"
|
data/install.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
# Install hook code here
|
@@ -0,0 +1,100 @@
|
|
1
|
+
module RestfulAclController
|
2
|
+
|
3
|
+
def self.included(base)
|
4
|
+
base.extend(ClassMethods)
|
5
|
+
base.send :include, ClassMethods
|
6
|
+
end
|
7
|
+
|
8
|
+
module ClassMethods
|
9
|
+
|
10
|
+
def has_permission?
|
11
|
+
return true if administrator?
|
12
|
+
|
13
|
+
begin
|
14
|
+
# Load the Model based on the controller name
|
15
|
+
klass = self.controller_name.classify.constantize
|
16
|
+
|
17
|
+
if params[:id]
|
18
|
+
# Load the object and possible parent requested
|
19
|
+
object = klass.find(params[:id])
|
20
|
+
parent = object.get_mom rescue nil
|
21
|
+
else
|
22
|
+
# No object was requested, so we need to go to the URI to figure out the parent
|
23
|
+
object = nil
|
24
|
+
parent = get_parent_from_request_uri(klass) if klass.has_parent?
|
25
|
+
end
|
26
|
+
|
27
|
+
# Let's let the Model decide what is acceptable
|
28
|
+
permission_denied unless case params[:action]
|
29
|
+
when "index" then klass.is_indexable_by(current_user, parent)
|
30
|
+
when "new", "create" then klass.is_creatable_by(current_user, parent)
|
31
|
+
when "show" then object.is_readable_by(current_user, parent)
|
32
|
+
when "edit", "update" then object.is_updatable_by(current_user, parent)
|
33
|
+
when "destroy" then object.is_deletable_by(current_user, parent)
|
34
|
+
else check_non_restful_route(current_user, klass, object, parent)
|
35
|
+
end
|
36
|
+
|
37
|
+
rescue NoMethodError => e
|
38
|
+
# Misconfiguration: A RESTful_ACL specific method is missing.
|
39
|
+
raise_error(klass, e)
|
40
|
+
rescue
|
41
|
+
# Failsafe: If any funny business is going on, log and redirect
|
42
|
+
routing_error
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
private
|
47
|
+
|
48
|
+
def check_non_restful_route(user, klass, object, parent)
|
49
|
+
if object
|
50
|
+
object.is_readable_by(user, parent)
|
51
|
+
elsif klass
|
52
|
+
klass.is_indexable_by(user, parent)
|
53
|
+
else
|
54
|
+
# If all else fails, deny access
|
55
|
+
false
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def get_method_from_error(error)
|
60
|
+
error.message.gsub('`', "'").split("'").at(1)
|
61
|
+
end
|
62
|
+
|
63
|
+
def raise_error(klass, error)
|
64
|
+
method = get_method_from_error(error)
|
65
|
+
message = (is_class_method?(method)) ? "#{klass}#self.#{method}" : "#{klass}##{method}"
|
66
|
+
raise NoMethodError, "[RESTful_ACL] #{message}(user, parent = nil) seems to be missing?"
|
67
|
+
end
|
68
|
+
|
69
|
+
def is_class_method?(method)
|
70
|
+
method =~ /(indexable|creatable)/
|
71
|
+
end
|
72
|
+
|
73
|
+
def get_parent_from_request_uri(child_klass)
|
74
|
+
parent_klass = child_klass.mom.to_s
|
75
|
+
bits = request.request_uri.split('/')
|
76
|
+
parent_id = bits.at(bits.index(parent_klass.pluralize) + 1)
|
77
|
+
|
78
|
+
parent_klass.classify.constantize.find(parent_id)
|
79
|
+
end
|
80
|
+
|
81
|
+
def administrator?
|
82
|
+
current_user.respond_to?("is_admin?") && current_user.is_admin?
|
83
|
+
end
|
84
|
+
|
85
|
+
def permission_denied
|
86
|
+
logger.info("[RESTful_ACL] Permission denied to %s at %s for %s" %
|
87
|
+
[(logged_in? ? current_user.login : 'guest'), Time.now, request.request_uri])
|
88
|
+
|
89
|
+
redirect_to denied_url
|
90
|
+
end
|
91
|
+
|
92
|
+
def routing_error
|
93
|
+
logger.info("[RESTful_ACL] Routing error by %s at %s for %s" %
|
94
|
+
[(logged_in? ? current_user.login : 'guest'), Time.now, request.request_uri])
|
95
|
+
|
96
|
+
redirect_to error_url
|
97
|
+
end
|
98
|
+
|
99
|
+
end
|
100
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
module RestfulAclHelper
|
2
|
+
def indexable
|
3
|
+
return true if admin_enabled
|
4
|
+
klass.is_indexable_by(current_user, parent_obj)
|
5
|
+
end
|
6
|
+
|
7
|
+
def creatable
|
8
|
+
return true if admin_enabled
|
9
|
+
klass.is_creatable_by(current_user, parent_obj)
|
10
|
+
end
|
11
|
+
alias_method :createable, :creatable
|
12
|
+
|
13
|
+
|
14
|
+
def updatable(object)
|
15
|
+
return true if admin_enabled
|
16
|
+
|
17
|
+
parent = object.get_mom rescue nil
|
18
|
+
object.is_updatable_by(current_user, parent)
|
19
|
+
end
|
20
|
+
alias_method :updateable, :updatable
|
21
|
+
|
22
|
+
|
23
|
+
def deletable(object)
|
24
|
+
return true if admin_enabled
|
25
|
+
|
26
|
+
parent = object.get_mom rescue nil
|
27
|
+
object.is_deletable_by(current_user, parent)
|
28
|
+
end
|
29
|
+
alias_method :deleteable, :deletable
|
30
|
+
|
31
|
+
|
32
|
+
def readable(object)
|
33
|
+
return true if admin_enabled
|
34
|
+
|
35
|
+
parent = object.get_mom rescue nil
|
36
|
+
object.is_readable_by(current_user, parent)
|
37
|
+
end
|
38
|
+
|
39
|
+
|
40
|
+
private
|
41
|
+
|
42
|
+
def klass
|
43
|
+
params[:controller].classify.constantize
|
44
|
+
end
|
45
|
+
|
46
|
+
def parent_obj
|
47
|
+
parent_klass.find(parent_id) rescue nil
|
48
|
+
end
|
49
|
+
|
50
|
+
def parent_klass
|
51
|
+
klass.mom.to_s.classify.constantize
|
52
|
+
end
|
53
|
+
|
54
|
+
def parent_id
|
55
|
+
params["#{klass.mom.to_s}_id"]
|
56
|
+
end
|
57
|
+
|
58
|
+
def admin_enabled
|
59
|
+
current_user.respond_to?("is_admin?") && current_user.is_admin?
|
60
|
+
end
|
61
|
+
|
62
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
module RestfulAclModel
|
2
|
+
|
3
|
+
def self.included(base)
|
4
|
+
base.extend(ClassMethods)
|
5
|
+
base.send :include, ClassMethods
|
6
|
+
end
|
7
|
+
|
8
|
+
module ClassMethods
|
9
|
+
attr_accessor :mom
|
10
|
+
|
11
|
+
def logical_parent(model)
|
12
|
+
self.mom = model
|
13
|
+
include RestfulAclModel::InstanceMethods
|
14
|
+
end
|
15
|
+
|
16
|
+
def has_parent?
|
17
|
+
!self.mom.nil?
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
21
|
+
|
22
|
+
|
23
|
+
module InstanceMethods
|
24
|
+
|
25
|
+
def get_mom
|
26
|
+
parent_klass.find(parent_id) if has_parent?
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
def klass
|
32
|
+
self.class
|
33
|
+
end
|
34
|
+
|
35
|
+
def mom
|
36
|
+
klass.mom
|
37
|
+
end
|
38
|
+
|
39
|
+
def has_parent?
|
40
|
+
!mom.nil?
|
41
|
+
end
|
42
|
+
|
43
|
+
def parent_klass
|
44
|
+
mom.to_s.classify.constantize
|
45
|
+
end
|
46
|
+
|
47
|
+
def parent_id
|
48
|
+
self.instance_eval("#{mom}_id")
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
end
|
data/rails/init.rb
ADDED
@@ -0,0 +1,9 @@
|
|
1
|
+
require 'restful_acl_controller'
|
2
|
+
require 'restful_acl_helper'
|
3
|
+
require 'restful_acl_model'
|
4
|
+
|
5
|
+
ActionController::Base.send :include, RestfulAclController
|
6
|
+
ActionView::Base.send :include, RestfulAclHelper
|
7
|
+
ActiveRecord::Base.send :include, RestfulAclModel
|
8
|
+
|
9
|
+
RAILS_DEFAULT_LOGGER.debug "** [RESTful_ACL] loaded"
|
data/uninstall.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
# Uninstall hook code here
|
metadata
ADDED
@@ -0,0 +1,64 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: restful_acl
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 2.0.6
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Matt Darby
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2009-02-07 00:00:00 -05:00
|
13
|
+
default_executable:
|
14
|
+
dependencies: []
|
15
|
+
|
16
|
+
description: A Rails gem that provides fine grained access control to RESTful resources in a Rails 2.0+ application.
|
17
|
+
email: matt@matt-darby.com
|
18
|
+
executables: []
|
19
|
+
|
20
|
+
extensions: []
|
21
|
+
|
22
|
+
extra_rdoc_files: []
|
23
|
+
|
24
|
+
files:
|
25
|
+
- MIT-LICENSE
|
26
|
+
- README.textile
|
27
|
+
- Rakefile
|
28
|
+
- init.rb
|
29
|
+
- install.rb
|
30
|
+
- lib/restful_acl_controller.rb
|
31
|
+
- lib/restful_acl_helper.rb
|
32
|
+
- lib/restful_acl_model.rb
|
33
|
+
- rails/init.rb
|
34
|
+
- uninstall.rb
|
35
|
+
has_rdoc: true
|
36
|
+
homepage: http://github.com/mdarby/restful_acl
|
37
|
+
licenses: []
|
38
|
+
|
39
|
+
post_install_message:
|
40
|
+
rdoc_options: []
|
41
|
+
|
42
|
+
require_paths:
|
43
|
+
- lib
|
44
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
45
|
+
requirements:
|
46
|
+
- - ">="
|
47
|
+
- !ruby/object:Gem::Version
|
48
|
+
version: "0"
|
49
|
+
version:
|
50
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: "0"
|
55
|
+
version:
|
56
|
+
requirements: []
|
57
|
+
|
58
|
+
rubyforge_project:
|
59
|
+
rubygems_version: 1.3.5
|
60
|
+
signing_key:
|
61
|
+
specification_version: 3
|
62
|
+
summary: Object-level access control
|
63
|
+
test_files: []
|
64
|
+
|