nbrew-simple_access_control 0.0.1

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