nbrew-simple_access_control 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,4 @@
1
+ .DS_Store
2
+ .svn
3
+ pkg
4
+ nbrew-simple_access_control.gemspec
data/README ADDED
@@ -0,0 +1,162 @@
1
+ SimpleAccessControl
2
+ ===================
3
+
4
+ Acknowledgements: I give all credit to Ezra and Technoweenie for their two plugins which
5
+ inspired the interface design and a lot of the code for this one.
6
+
7
+ SimpleAccessControl is a streamlined, intuitive authorisation system. It derives heavily from
8
+ acl_system2 and has made clear some problems which plagued me when first using it. Some
9
+ fixes to acl_system2's design:
10
+
11
+ * a normal Rails syntax:
12
+ access_rule 'admin', :only => :index
13
+ access_rule '(moderator || admin)', :only => :new
14
+ * error handling for helper methods (permit? bombed when current_user == nil)
15
+ * one-line parser, easy to replace or alter
16
+ * proper before_filter usage, meaning access rules are parsed only when needed
17
+ * no overrideable default (which I found counter-intuitive in the end)
18
+
19
+ Also, it has two methods, access_control and permit?, for those moving from acl_system2.
20
+
21
+ But, let me stress, everyone likes a slightly different system, so this one may not be
22
+ your style. I find it synchronises very well with the interface of Acts as Authenticated (even
23
+ though I have modified it so much that it's now called Authenticated Cookie).
24
+
25
+ INSTALLATION
26
+ ============
27
+
28
+ Create the following migration:
29
+
30
+ create_table "roles", :force => true do |t|
31
+ t.column "title", :string
32
+ end
33
+ create_table "roles_users", :id => false, :force => true do |t|
34
+ t.column "role_id", :integer
35
+ t.column "user_id", :integer
36
+ end
37
+
38
+ In your User model, you must have:
39
+
40
+ has_and_belongs_to_many :roles
41
+
42
+ In your Roles model, you must have:
43
+
44
+ has_and_belongs_to_many :users
45
+
46
+ Your controllers must have the following two methods or variants of them:
47
+
48
+ # Returns a User object
49
+ def current_user
50
+ @current_user
51
+ end
52
+
53
+ # Returns true or false if a User object exists for this session
54
+ def logged_in?
55
+ @current_user.is_a? User
56
+ end
57
+
58
+
59
+ SPECIAL NEEDS
60
+ =============
61
+
62
+ If you want to permit anonymous users without demanding that they are logged in, first you
63
+ must ensure that logged_in? returns true in all cases, otherwise permission will be denied.
64
+ The following approach should work:
65
+
66
+ 1. Create the 'guest' and 'user' roles, e.g.:
67
+
68
+ guest = Role.create(:title => 'guest')
69
+ user = Role.create(:title => 'user')
70
+
71
+ 2. In your registration/user creation area, ensure all real users have the 'user' role, e.g.:
72
+
73
+ @user = User.create(params[:user])
74
+ unless @user.roles.any? { |r| r.title == 'user' }
75
+ @user.roles << Role.find_by_title('user')
76
+ end
77
+ @user.save
78
+
79
+ [At this point you have two options: a real or virtual anonymous account]
80
+
81
+ First Approach: Real Anonymous User
82
+
83
+ 3a. Create an anonymous user, e.g.:
84
+
85
+ @anonymous = User.create(:login => 'anonymous', :password => '*', :activated => true)
86
+
87
+ 4a. Add the role to the Anonymous user (in a migration or in script/console), e.g.:
88
+
89
+ anonymous.roles << Role.find_by_title('guest')
90
+ anonymous.save
91
+
92
+ 5a. In your ApplicationController, set unauthenticated users as 'anonymous', e.g.:
93
+
94
+ before_filter :default_to_guest
95
+
96
+ def default_to_guest
97
+ self.current_user = User.find_by_login('anonymous', :include => :roles) unless logged_in?
98
+ end
99
+
100
+
101
+ Second Approach: Virtual Anonymous User
102
+
103
+ 3a. In your ApplicationController, create a virtual anonymous account if unauthenticated:
104
+
105
+ before_filter :default_to_virtual_guest
106
+ def default_to_virtual_guest
107
+ self.current_user = self.anonymous_user unless logged_in?
108
+ end
109
+
110
+ def anonymous_user
111
+ anonymous = User.new(:login => 'anonymous', :name => 'Guest')
112
+ anonymous.roles << Role.new(:title => 'guest')
113
+ anonymous.readonly!
114
+ anonymous
115
+ end
116
+
117
+
118
+ USAGE
119
+ =====
120
+
121
+ The plugin is automatically hooked into ActionController::Base.
122
+
123
+ In your controllers, add access rules like so:
124
+
125
+ access_rule 'admin', :only => :destroy
126
+ access_rule 'user || admin', :only => [:new, :create, :edit, :update]
127
+
128
+ Note the use of Ruby-style operators. These strings are real conditionals and should be treated as
129
+ such. Every grouping of non-operator characters will be considered a role title.
130
+
131
+ In your views, you can use the following:
132
+
133
+ <% restrict_to 'admin || moderator' do %>
134
+ <%= link_to "Admin Area", admin_area_url %>
135
+ <% end %>
136
+
137
+ AND
138
+
139
+ <%= link_to("Admin Area", admin_area_url) if has_permission?('admin || moderator') %>
140
+
141
+ There are also transitional methods which help you move from acl_system2 to this plugin -- I do this
142
+ not to denegrate acl_system2 but because I did this for myself and decided to include it. The two
143
+ systems are rather similar.
144
+
145
+ Also, there are two callbacks, permission_granted and permission_denied, which may define in your
146
+ controllers to customise their response. For example:
147
+
148
+ def permission_granted
149
+ logger.info("[authentication] Permission granted to %s at %s for %s" %
150
+ [(logged_in? ? current_user.login : 'guest'), Time.now, request.request_uri])
151
+ end
152
+
153
+ def permission_denied
154
+ logger.info("[authentication] Permission denied to %s at %s for %s" %
155
+ [(logged_in? ? current_user.login : 'guest'), Time.now, request.request_uri])
156
+ end
157
+
158
+
159
+ That's it!
160
+
161
+
162
+ VARIATION BY MABS29
data/Rakefile ADDED
@@ -0,0 +1,43 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+ require 'rake/testtask'
4
+ require 'rake/rdoctask'
5
+
6
+
7
+ begin
8
+ require 'jeweler'
9
+ Jeweler::Tasks.new do |gem|
10
+ gem.name = "nbrew-simple_access_control"
11
+ gem.summary = %Q{Simple role-based access control plugin for Rails controllers and views.}
12
+ gem.description = %Q{Simple access controls for use with ActsAsAuthenticated, RestfulAuthentication, etc. A clone of Mathew Abonyi's (mabs29) repo: http://mabs29.googlecode.com/svn/trunk/plugins/simple_access_control}
13
+ gem.email = "nhyde@bigdrift.com"
14
+ gem.homepage = "http://github.com/nbrew/simple_access_control"
15
+ gem.authors = ["Mathew Abonyi"]
16
+ gem.add_development_dependency "shoulda", ">= 0"
17
+ # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
18
+ end
19
+ Jeweler::GemcutterTasks.new
20
+ rescue LoadError
21
+ puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
22
+ end
23
+
24
+
25
+ desc 'Default: run unit tests.'
26
+ task :test => :check_dependencies
27
+ task :default => :test
28
+
29
+ desc 'Test the simple_access_control plugin.'
30
+ Rake::TestTask.new(:test) do |t|
31
+ t.libs << 'lib'
32
+ t.pattern = 'test/**/*_test.rb'
33
+ t.verbose = true
34
+ end
35
+
36
+ desc 'Generate documentation for the simple_access_control plugin.'
37
+ Rake::RDocTask.new(:rdoc) do |rdoc|
38
+ rdoc.rdoc_dir = 'rdoc'
39
+ rdoc.title = 'SimpleAccessControl'
40
+ rdoc.options << '--line-numbers' << '--inline-source'
41
+ rdoc.rdoc_files.include('README')
42
+ rdoc.rdoc_files.include('lib/**/*.rb')
43
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.0.1
data/install.rb ADDED
@@ -0,0 +1 @@
1
+ # Install hook code here
@@ -0,0 +1,128 @@
1
+ # SimpleAccessControl
2
+ #
3
+ # Acknowledgements: I give all credit to Ezra and Technoweenie for their two plugins which
4
+ # inspired the interface design and a lot of the code for this one.
5
+ #
6
+ # SimpleAccessControl is a streamlined, intuitive authorisation system. It derives heavily from
7
+ # acl_system2 and has made clear some problems which plagued the author when first using it. Some
8
+ # fixes to acl_system2's design:
9
+ #
10
+ # * a normal Rails syntax:
11
+ # access_rule 'admin', :only => :index
12
+ # access_rule '(moderator || admin)', :only => :new
13
+ # * error handling for helper methods (permit? bombs out with current_user == nil)
14
+ # * one-line parser, easy to replace or alter
15
+ # * proper before_filter usage, meaning access rules are parsed only when needed
16
+ # * no overrideable default (which I found counter-intuitive in the end)
17
+ #
18
+ # Also, it has two methods, access_control and permit?, for those moving from acl_system2.
19
+ #
20
+ # But, let me stress, everyone likes a slightly different system, so this one may not be
21
+ # your style. I find it synchronises very well with the interface of Acts as Authenticated (even
22
+ # though I have modified it so much that it's now called Authenticated Cookie).
23
+ #
24
+ module SimpleAccessControl
25
+
26
+ def self.included(base)
27
+ base.extend(ClassMethods)
28
+ if base.respond_to?(:helper_method)
29
+ base.send :helper_method, :restrict_to
30
+ base.send :helper_method, :has_permission?
31
+ base.send :helper_method, :permit?
32
+ end
33
+ end
34
+
35
+ module ClassMethods
36
+
37
+ # Support for acl_system2 migration
38
+ def access_control(ruleset = {})
39
+ ruleset.each do |actions, rule|
40
+ case actions
41
+ when :DEFAULT
42
+ access_rule rule
43
+ when Array, Symbol, String
44
+ access_rule rule, :only => actions
45
+ end
46
+ end
47
+ end
48
+
49
+ # This is the core of the filtering system and it couldn't be simpler:
50
+ # access_rule '(admin || moderator)', :only => [:edit, :update]
51
+ def access_rule(rule, filter_options = {})
52
+ before_filter (filter_options||{}) { |c| c.send :permission_required, rule }
53
+ end
54
+ end
55
+
56
+ protected
57
+
58
+ # As in AAA you have login_required, here you have permission_required. Pass it a
59
+ # rule and it will use SimpleAccessControl#has_permission? to evaluate against the
60
+ # current user. Use SimpleAccessControl#has_permission? if you are not guarding an
61
+ # action or whole controller. An empty or nil rule will always return true.
62
+ # permission_required('admin')
63
+ def permission_required(rule = nil)
64
+ if respond_to?(:logged_in?) && logged_in? && has_permission?(rule)
65
+ send(:permission_granted) if respond_to?(:permission_granted)
66
+ true
67
+ else
68
+ send(:permission_denied) if respond_to?(:permission_denied)
69
+ false
70
+ end
71
+ end
72
+
73
+ # For use in both controllers and views.
74
+ # has_permission?('role')
75
+ # has_permission?('admin', other_user)
76
+ def has_permission?(rule, user = nil)
77
+ user ||= (send(:current_user) if respond_to?(:current_user)) || nil
78
+ access_controller.process(rule, user)
79
+ end
80
+
81
+ # For those of you converting from acl_system2
82
+ def permit?(rule, context = {})
83
+ has_permission?(rule, (context && context[:user] ? context[:user] : nil))
84
+ end
85
+
86
+ # A much shortened version of Ezra's acl_system2 version.
87
+ # restrict_to "admin | moderator" do
88
+ # link_to "foo"
89
+ # end
90
+ def restrict_to(rule, user = nil)
91
+ yield if block_given? && has_permission?(rule, user)
92
+ end
93
+
94
+ def access_controller #:nodoc:
95
+ @access_controller ||= AccessControlHandler.new
96
+ end
97
+
98
+ # A dramatically simpler version than that found in acl_system2
99
+ # It is SLOWER because it uses instance_eval to analyse the conditional, but it's DRY.
100
+ class AccessControlHandler
101
+
102
+ # Takes a string (which may be a complex conditional string or a single word as a string
103
+ # or symbol) and checks if the user has those roles
104
+ def process(string, user)
105
+ return(check('', user)) if string.blank?
106
+ if string =~ /^([^()\|&!]+)$/ then check($1, user) # it is simple enough to just pump through
107
+ else instance_eval("!! (#{parse(string)})") # give it the going-over
108
+ end
109
+ end
110
+
111
+ # Super-simple parsing, turning single or multiple & and | into && and ||. Wraps all the roles
112
+ # in a check call to be evaluated.
113
+ def parse(string)
114
+ string.gsub(/(\|+|\&+)/) { $1[0,1]*2 }.gsub(/([^()|&! ]+)/) { "check('#{$1}', user)" }
115
+ end
116
+
117
+ # The heart of the system, all credit to Ezra for the original algorithm
118
+ # Defaults to false if there is no user or that user does not have a roles association
119
+ # Defaults to true if the role is blank
120
+ def check(role, user)
121
+ return(false) if user.blank? || !user.respond_to?(:roles)
122
+ return(true) if role.blank?
123
+ user.roles.map{ |r| r.title.downcase }.include? role.downcase
124
+ end
125
+
126
+ end
127
+
128
+ end
@@ -0,0 +1,4 @@
1
+ # desc "Explaining what the task does"
2
+ # task :simple_access_control do
3
+ # # Task goes here
4
+ # end
data/rails/init.rb ADDED
@@ -0,0 +1,3 @@
1
+ require_dependency 'simple_access_control'
2
+
3
+ ActionController::Base.send :include, SimpleAccessControl
data/test/helper.rb ADDED
@@ -0,0 +1,10 @@
1
+ require 'rubygems'
2
+ require 'test/unit'
3
+ require 'shoulda'
4
+
5
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
6
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
7
+ require 'simple_access_control'
8
+
9
+ class Test::Unit::TestCase
10
+ end
@@ -0,0 +1,8 @@
1
+ require 'test/helper'
2
+
3
+ class SimpleAccessControlTest < Test::Unit::TestCase
4
+ # Replace this with your real tests.
5
+ should "probably rename this file and start testing for real" do
6
+ flunk "hey buddy, you should probably rename this file and start testing for real"
7
+ end
8
+ end
metadata ADDED
@@ -0,0 +1,90 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: nbrew-simple_access_control
3
+ version: !ruby/object:Gem::Version
4
+ hash: 29
5
+ prerelease: false
6
+ segments:
7
+ - 0
8
+ - 0
9
+ - 1
10
+ version: 0.0.1
11
+ platform: ruby
12
+ authors:
13
+ - Mathew Abonyi
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2010-07-12 00:00:00 -07:00
19
+ default_executable:
20
+ dependencies:
21
+ - !ruby/object:Gem::Dependency
22
+ name: shoulda
23
+ prerelease: false
24
+ requirement: &id001 !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ">="
28
+ - !ruby/object:Gem::Version
29
+ hash: 3
30
+ segments:
31
+ - 0
32
+ version: "0"
33
+ type: :development
34
+ version_requirements: *id001
35
+ description: "Simple access controls for use with ActsAsAuthenticated, RestfulAuthentication, etc. A clone of Mathew Abonyi's (mabs29) repo: http://mabs29.googlecode.com/svn/trunk/plugins/simple_access_control"
36
+ email: nhyde@bigdrift.com
37
+ executables: []
38
+
39
+ extensions: []
40
+
41
+ extra_rdoc_files:
42
+ - README
43
+ files:
44
+ - .gitignore
45
+ - README
46
+ - Rakefile
47
+ - VERSION
48
+ - install.rb
49
+ - lib/simple_access_control.rb
50
+ - lib/tasks/simple_access_control_tasks.rake
51
+ - rails/init.rb
52
+ - test/helper.rb
53
+ - test/simple_access_control_test.rb
54
+ has_rdoc: true
55
+ homepage: http://github.com/nbrew/simple_access_control
56
+ licenses: []
57
+
58
+ post_install_message:
59
+ rdoc_options:
60
+ - --charset=UTF-8
61
+ require_paths:
62
+ - lib
63
+ required_ruby_version: !ruby/object:Gem::Requirement
64
+ none: false
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ hash: 3
69
+ segments:
70
+ - 0
71
+ version: "0"
72
+ required_rubygems_version: !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ">="
76
+ - !ruby/object:Gem::Version
77
+ hash: 3
78
+ segments:
79
+ - 0
80
+ version: "0"
81
+ requirements: []
82
+
83
+ rubyforge_project:
84
+ rubygems_version: 1.3.7
85
+ signing_key:
86
+ specification_version: 3
87
+ summary: Simple role-based access control plugin for Rails controllers and views.
88
+ test_files:
89
+ - test/helper.rb
90
+ - test/simple_access_control_test.rb