restful_acl 2.0.7 → 2.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,4 +1,4 @@
1
- Copyright (c) 2008 Matt Darby
1
+ Copyright (c) 2009 Matt Darby
2
2
 
3
3
  Permission is hereby granted, free of charge, to any person obtaining
4
4
  a copy of this software and associated documentation files (the
data/README.textile CHANGED
@@ -20,8 +20,8 @@ Add the gem to your environment.rb file as thus:
20
20
 
21
21
  RESTful_ACL requires two named routes: "error" and "denied". Add the following to your routes.rb file:
22
22
  <pre>
23
- map.error '/error', :controller => 'some_controller', :action => 'error_action'
24
- map.denied '/denied', :controller => 'some_controller', :action => 'denied_action'
23
+ map.error 'error', :controller => 'some_controller', :action => 'error_action'
24
+ map.denied 'denied', :controller => 'some_controller', :action => 'denied_action'
25
25
  </pre>
26
26
 
27
27
  h3. How to Use
@@ -32,15 +32,15 @@ Add @before_filter :has_permission?@ into any controller that you'd like to rest
32
32
 
33
33
  h4. Models
34
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.
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
36
 
37
37
  <pre>
38
38
  class Issue < ActiveRecord::Base
39
39
  logical_parent :some_model_name
40
-
40
+
41
41
  # This method checks permissions for the :index action
42
42
  def self.is_indexable_by(user, parent = nil)
43
-
43
+
44
44
  end
45
45
 
46
46
  # This method checks permissions for the :create and :new action
@@ -65,6 +65,17 @@ Define a parent resource (if one exists) by using the @logical_parent@ method, a
65
65
  end
66
66
  </pre>
67
67
 
68
+ h5. Singleton Resources
69
+
70
+ RESTful_ACL 2.1+ supports singleton resources. Just pass @:singleton@ to the @logical_parent@
71
+
72
+ <pre>
73
+ class Car < ActiveRecord::Base
74
+ logical_parent :owner, :singleton
75
+ ...
76
+ end
77
+ </pre>
78
+
68
79
  h4. View Helpers
69
80
 
70
81
  There are five view helpers also included in RESTful_ACL: @#indexable@, @#creatable@, @#readable@, @#updatable@, and @#deletable@. These enable you to do nifty things like:
@@ -83,18 +94,18 @@ Let's say that you have two resources: Project and Issue. A Project has many Iss
83
94
  <pre>
84
95
  class Issue < ActiveRecord::Base
85
96
  logical_parent :project
86
-
97
+
87
98
  belongs_to :author
88
99
  belongs_to :project
89
100
 
90
101
  def self.is_indexable_by(user, parent = nil)
91
102
  user.projects.include?(parent)
92
103
  end
93
-
104
+
94
105
  def self.is_creatable_by(user, parent = nil)
95
106
  user.projects.include?(parent)
96
107
  end
97
-
108
+
98
109
  def is_updatable_by(user, parent = nil)
99
110
  user == author && parent.is_active?
100
111
  end
@@ -121,7 +132,7 @@ I normally do something along these lines in RSpec:
121
132
  before do
122
133
  @project = mock_model(Project)
123
134
  @author = mock_model(User, :projects => [@project])
124
-
135
+
125
136
  @issue = Issue.factory_girl(:issue, :author => @author, :project => @project)
126
137
  end
127
138
 
@@ -144,19 +155,6 @@ I normally do something along these lines in RSpec:
144
155
  end
145
156
  </pre>
146
157
 
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
158
  h3. Help
161
159
 
162
160
  Add a ticket to "RESTful_ACL's Lighthouse Account":http://mdarby.lighthouseapp.com/projects/28698-restful_acl/overview
data/Rakefile CHANGED
@@ -1,22 +1,45 @@
1
+ require 'rubygems'
1
2
  require 'rake'
2
- require 'rake/testtask'
3
- require 'rake/rdoctask'
4
3
 
5
- desc 'Default: run unit tests.'
6
- task :default => :test
4
+ begin
5
+ require 'jeweler'
6
+ Jeweler::Tasks.new do |gem|
7
+ gem.name = "restful_acl"
8
+ gem.summary = "A Ruby on Rails plugin that provides fine grained access control to RESTful resources."
9
+ gem.description = "A Ruby on Rails plugin that provides fine grained access control to RESTful resources."
10
+ gem.email = "matt@matt-darby.com"
11
+ gem.homepage = "http://github.com/mdarby/restful_acl"
12
+ gem.authors = ["Matt Darby"]
13
+ gem.add_development_dependency "rspec", ">= 1.2.9"
14
+ # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
15
+ end
16
+ Jeweler::GemcutterTasks.new
17
+ rescue LoadError
18
+ puts "Jeweler (or a dependency) not available. Install it with: sudo gem install jeweler"
19
+ end
20
+
21
+ require 'spec/rake/spectask'
22
+ Spec::Rake::SpecTask.new(:spec) do |spec|
23
+ spec.libs << 'lib' << 'spec'
24
+ spec.spec_files = FileList['spec/**/*_spec.rb']
25
+ end
7
26
 
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
27
+ Spec::Rake::SpecTask.new(:rcov) do |spec|
28
+ spec.libs << 'lib' << 'spec'
29
+ spec.pattern = 'spec/**/*_spec.rb'
30
+ spec.rcov = true
13
31
  end
14
32
 
15
- desc 'Generate documentation for the restful_acl plugin.'
16
- Rake::RDocTask.new(:rdoc) do |rdoc|
33
+ task :spec => :check_dependencies
34
+
35
+ task :default => :spec
36
+
37
+ require 'rake/rdoctask'
38
+ Rake::RDocTask.new do |rdoc|
39
+ version = File.exist?('VERSION') ? File.read('VERSION') : ""
40
+
17
41
  rdoc.rdoc_dir = 'rdoc'
18
- rdoc.title = 'RestfulAcl'
19
- rdoc.options << '--line-numbers' << '--inline-source'
20
- rdoc.rdoc_files.include('README')
42
+ rdoc.title = "restful_acl #{version}"
43
+ rdoc.rdoc_files.include('README*')
21
44
  rdoc.rdoc_files.include('lib/**/*.rb')
22
45
  end
data/lib/controller.rb ADDED
@@ -0,0 +1,114 @@
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
+ attr_accessor :object, :parent, :klass, :user
11
+
12
+ def has_permission?
13
+ return true if administrator?
14
+
15
+ load_actors(params[:id])
16
+
17
+ begin
18
+ # Let's let the Model decide what is acceptable
19
+ permission_denied unless case params[:action]
20
+ when "index" then @klass.is_indexable_by(@user, @parent)
21
+ when "new", "create" then @klass.is_creatable_by(@user, @parent)
22
+ when "show" then @object.is_readable_by(@user, @parent)
23
+ when "edit", "update" then @object.is_updatable_by(@user, @parent)
24
+ when "destroy" then @object.is_deletable_by(@user, @parent)
25
+ else check_non_restful_route
26
+ end
27
+
28
+ rescue NoMethodError => e
29
+ # Misconfiguration: A RESTful_ACL specific method is missing.
30
+ raise_error(e)
31
+ rescue
32
+ # Failsafe: If any funny business is going on, log and redirect
33
+ routing_error
34
+ end
35
+ end
36
+
37
+ private
38
+
39
+ def load_actors(id)
40
+ @user = current_user
41
+
42
+ # Load the Model based on the controller name
43
+ @klass = self.controller_name.classify.demodulize.constantize
44
+
45
+ if id.present?
46
+ # Load the object and possible parent requested
47
+ @object = @klass.find(params[:id])
48
+ @parent = @object.get_mom if @klass.has_parent?
49
+ else
50
+ # No object was requested, so we need to go to the URI to figure out the parent
51
+ @parent = get_mom_from_request_uri(@klass) if @klass.has_parent?
52
+
53
+ if @klass.is_singleton?
54
+ @object = @parent.send(@klass.to_s.tableize.singularize.to_sym)
55
+ else
56
+ # No object was requested (index, create actions)
57
+ @object = nil
58
+ end
59
+ end
60
+ end
61
+
62
+ def check_non_restful_route
63
+ if @object
64
+ @object.is_readable_by(@user, @parent)
65
+ elsif @klass
66
+ @klass.is_indexable_by(@user, @parent)
67
+ else
68
+ false # If all else fails, deny access
69
+ end
70
+ end
71
+
72
+ def get_method_from_error(error)
73
+ error.message.gsub('`', "'").split("'").at(1)
74
+ end
75
+
76
+ def raise_error(error)
77
+ method = get_method_from_error(error)
78
+ message = (is_class_method?(method)) ? "#{@klass}#self.#{method}" : "#{@klass}##{method}"
79
+ raise NoMethodError, "[RESTful_ACL] #{message}(user, parent = nil) seems to be missing?"
80
+ end
81
+
82
+ def is_class_method?(method)
83
+ method =~ /[index|creat]able/
84
+ end
85
+
86
+ def get_mom_from_request_uri(child_klass)
87
+ parent_klass = child_klass.mom.to_s
88
+ bits = request.request_uri.split('/')
89
+ parent_id = bits.at(bits.index(parent_klass.pluralize) + 1)
90
+
91
+ parent_klass.classify.constantize.find(parent_id)
92
+ end
93
+
94
+ def administrator?
95
+ @user.respond_to?("is_admin?") && @user.is_admin?
96
+ end
97
+
98
+ def blame
99
+ @user.respond_to?(:login) ? @user.login : @user.username
100
+ end
101
+
102
+ def permission_denied
103
+ logger.info("[RESTful_ACL] Permission denied to %s at %s for %s" % [(logged_in? ? blame : 'guest'), Time.now, request.request_uri])
104
+ redirect_to denied_url
105
+ end
106
+
107
+ def routing_error
108
+ logger.info("[RESTful_ACL] Routing error by %s at %s for %s" % [(logged_in? ? blame : 'guest'), Time.now, request.request_uri])
109
+ redirect_to error_url
110
+ end
111
+
112
+ end
113
+
114
+ end
@@ -48,11 +48,11 @@ module RestfulAclHelper
48
48
  end
49
49
 
50
50
  def parent_klass
51
- klass.mom.to_s.classify.constantize
51
+ klass.parent.to_s.classify.constantize
52
52
  end
53
53
 
54
54
  def parent_id
55
- params["#{klass.mom.to_s}_id"]
55
+ params["#{klass.parent.to_s}_id"]
56
56
  end
57
57
 
58
58
  def admin_enabled
@@ -6,15 +6,21 @@ module RestfulAclModel
6
6
  end
7
7
 
8
8
  module ClassMethods
9
- attr_accessor :mom
9
+ attr_accessor :mom, :singleton
10
+
11
+ def logical_parent(model, *options)
12
+ @mom = model
13
+ @singleton = options.include?(:singleton)
10
14
 
11
- def logical_parent(model)
12
- self.mom = model
13
15
  include RestfulAclModel::InstanceMethods
14
16
  end
15
17
 
16
18
  def has_parent?
17
- !self.mom.nil?
19
+ @mom.present?
20
+ end
21
+
22
+ def is_singleton?
23
+ @singleton.present?
18
24
  end
19
25
 
20
26
  end
@@ -47,6 +53,7 @@ module RestfulAclModel
47
53
  def parent_id
48
54
  self.instance_eval("#{mom}_id")
49
55
  end
56
+
50
57
  end
51
58
 
52
59
  end
data/rails/init.rb CHANGED
@@ -1,6 +1,6 @@
1
- require 'restful_acl_controller'
2
- require 'restful_acl_helper'
3
- require 'restful_acl_model'
1
+ require 'controller'
2
+ require 'helper'
3
+ require 'model'
4
4
 
5
5
  ActionController::Base.send :include, RestfulAclController
6
6
  ActionView::Base.send :include, RestfulAclHelper
@@ -0,0 +1,26 @@
1
+ Gem::Specification.new do |s|
2
+ s.name = "restful_acl"
3
+ s.version = "2.1"
4
+ s.date = "2009-11-23"
5
+ s.summary = "Object-level access control"
6
+ s.email = "matt@matt-darby.com"
7
+ s.homepage = "http://github.com/mdarby/restful_acl"
8
+ s.description = "A Rails gem that provides fine grained access control to RESTful resources."
9
+ s.has_rdoc = false
10
+ s.authors = ["Matt Darby"]
11
+ s.files = [
12
+ "LICENSE",
13
+ "README.textile",
14
+ "Rakefile",
15
+ "init.rb",
16
+ "lib/controller.rb",
17
+ "lib/helper.rb",
18
+ "lib/model.rb",
19
+ "rails/init.rb",
20
+ "restful_acl.gemspec",
21
+ "spec/restful_acl_spec.rb",
22
+ "spec/spec.opts",
23
+ "spec/spec_helper.rb",
24
+ "spec/widgets.rb"
25
+ ]
26
+ end
@@ -0,0 +1,75 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+
3
+ describe "RestfulAcl" do
4
+
5
+ describe "when used in a RESTful resource" do
6
+
7
+ before do
8
+ @widget = Widget.new
9
+ end
10
+
11
+ describe "handling GET index" do
12
+ it "does something" do
13
+ end
14
+ end
15
+
16
+ describe "handling GET show" do
17
+
18
+ end
19
+
20
+ describe "handling GET new" do
21
+
22
+ end
23
+
24
+ describe "handling GET edit" do
25
+
26
+ end
27
+
28
+ describe "handling POST create" do
29
+
30
+ end
31
+
32
+ describe "handling PUT update" do
33
+
34
+ end
35
+
36
+ describe "handling DELETE destroy" do
37
+
38
+ end
39
+
40
+ end
41
+
42
+ describe "when used in a Singleton resource" do
43
+
44
+ describe "handling GET index" do
45
+
46
+ end
47
+
48
+ describe "handling GET show" do
49
+
50
+ end
51
+
52
+ describe "handling GET new" do
53
+
54
+ end
55
+
56
+ describe "handling GET edit" do
57
+
58
+ end
59
+
60
+ describe "handling POST create" do
61
+
62
+ end
63
+
64
+ describe "handling PUT update" do
65
+
66
+ end
67
+
68
+ describe "handling DELETE destroy" do
69
+
70
+ end
71
+
72
+
73
+ end
74
+
75
+ end
data/spec/spec.opts ADDED
@@ -0,0 +1 @@
1
+ --color
@@ -0,0 +1,10 @@
1
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
2
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
3
+ require 'rubygems'
4
+ require 'restful_acl'
5
+ require 'spec'
6
+ require 'widgets'
7
+
8
+ Spec::Runner.configure do |config|
9
+
10
+ end
data/spec/widgets.rb ADDED
@@ -0,0 +1,12 @@
1
+ class ParentWidget
2
+ end
3
+
4
+ class Widget
5
+ include RestfulAclModel
6
+ logical_parent :parent_widget
7
+ end
8
+
9
+ class SingletonWidget
10
+ include RestfulAclModel
11
+ logical_parent :parent_widget
12
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: restful_acl
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.7
4
+ version: "2.1"
5
5
  platform: ruby
6
6
  authors:
7
7
  - Matt Darby
@@ -9,11 +9,11 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2009-11-20 00:00:00 -05:00
12
+ date: 2009-11-23 00:00:00 -05:00
13
13
  default_executable:
14
14
  dependencies: []
15
15
 
16
- description: A Rails gem that provides fine grained access control to RESTful resources in a Rails 2.0+ application.
16
+ description: A Rails gem that provides fine grained access control to RESTful resources.
17
17
  email: matt@matt-darby.com
18
18
  executables: []
19
19
 
@@ -22,16 +22,19 @@ extensions: []
22
22
  extra_rdoc_files: []
23
23
 
24
24
  files:
25
- - MIT-LICENSE
25
+ - LICENSE
26
26
  - README.textile
27
27
  - Rakefile
28
28
  - init.rb
29
- - install.rb
30
- - lib/restful_acl_controller.rb
31
- - lib/restful_acl_helper.rb
32
- - lib/restful_acl_model.rb
29
+ - lib/controller.rb
30
+ - lib/helper.rb
31
+ - lib/model.rb
33
32
  - rails/init.rb
34
- - uninstall.rb
33
+ - restful_acl.gemspec
34
+ - spec/restful_acl_spec.rb
35
+ - spec/spec.opts
36
+ - spec/spec_helper.rb
37
+ - spec/widgets.rb
35
38
  has_rdoc: false
36
39
  homepage: http://github.com/mdarby/restful_acl
37
40
  licenses: []
data/install.rb DELETED
@@ -1 +0,0 @@
1
- # Install hook code here
@@ -1,100 +0,0 @@
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.demodulize.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
data/uninstall.rb DELETED
@@ -1 +0,0 @@
1
- # Uninstall hook code here