be9-acl9 0.10.0 → 0.11.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,3 +1,11 @@
1
+ h2. 0.11.0 (16-Sep-2009)
2
+
3
+ * :protect_global_roles
4
+ * Subject#roles renamed to Subject#role_objects
5
+ * Fix namespaced models in roles backend (thanks goes to Tomas Jogin)
6
+ * Action name override in boolean methods.
7
+ * @:query_method@ option for @access_control@.
8
+
1
9
  h2. 0.10.0 (03-May-2009)
2
10
 
3
11
  * Use context+matchy combo for testing
@@ -1,4 +1,4 @@
1
- h1. Acl9
1
+ h1. Introduction
2
2
 
3
3
  Acl9 is yet another solution for role-based authorization in Rails. It consists of two
4
4
  subsystems which can be used separately.
@@ -38,6 +38,17 @@ An example:
38
38
  end
39
39
  </code></pre>
40
40
 
41
+ h1. Contacts
42
+
43
+ Acl9 is hosted "on the GitHub":http://github.com/be9/acl9.
44
+
45
+ You may find tutorials and additional docs on the "wiki page":http://wiki.github.com/be9/acl9.
46
+
47
+ Rdocs are available "here":http://rdoc.info/projects/be9/acl9.
48
+
49
+ If you have questions, please post to the
50
+ "acl9-discuss group":http://groups.google.com/group/acl9-discuss
51
+
41
52
  h1. Installation
42
53
 
43
54
  Acl9 can be installed as a gem from "GitHub":http://github.com.
@@ -875,5 +886,3 @@ An imaginary view:
875
886
  </code></pre>
876
887
 
877
888
  Copyright (c) 2009 Oleg Dashevskii, released under the MIT license.
878
-
879
- Contact me at "#{%w(Oleg Dashevskii).join('').downcase}@gmail.com"
data/Rakefile CHANGED
@@ -27,3 +27,13 @@ Rake::TestTask.new(:test) do |test|
27
27
  test.pattern = 'test/**/*_test.rb'
28
28
  test.verbose = false
29
29
  end
30
+
31
+ begin
32
+ require 'yard'
33
+
34
+ YARD::Rake::YardocTask.new do |t|
35
+ t.files = ['lib/**/*.rb']
36
+ #t.options = ['--any', '--extra', '--opts'] # optional
37
+ end
38
+ rescue LoadError
39
+ end
@@ -1,4 +1,4 @@
1
1
  ---
2
- :minor: 10
2
+ :minor: 11
3
3
  :patch: 0
4
4
  :major: 0
@@ -3,6 +3,7 @@ module Acl9
3
3
  :default_role_class_name => 'Role',
4
4
  :default_subject_class_name => 'User',
5
5
  :default_subject_method => :current_user,
6
+ :protect_global_roles => false,
6
7
  }
7
8
 
8
9
  mattr_reader :config
@@ -46,18 +46,39 @@ module Acl9
46
46
 
47
47
  method = opts[:as_method]
48
48
 
49
+ query_method_available = true
49
50
  generator = case
50
51
  when method && filter
51
52
  Acl9::Dsl::Generators::FilterMethod.new(subject_method, method)
52
53
  when method && !filter
54
+ query_method_available = false
53
55
  Acl9::Dsl::Generators::BooleanMethod.new(subject_method, method)
54
56
  else
55
57
  Acl9::Dsl::Generators::FilterLambda.new(subject_method)
56
58
  end
57
59
 
58
60
  generator.acl_block!(&block)
59
-
61
+
60
62
  generator.install_on(self, opts)
63
+
64
+ if query_method_available && (query_method = opts.delete(:query_method))
65
+ case query_method
66
+ when true
67
+ if method
68
+ query_method = "#{method}?"
69
+ else
70
+ raise ArgumentError, "You must specify :query_method as Symbol"
71
+ end
72
+ when Symbol, String
73
+ # okay here
74
+ else
75
+ raise ArgumentError, "Invalid value for :query_method"
76
+ end
77
+
78
+ second_generator = Acl9::Dsl::Generators::BooleanMethod.new(subject_method, query_method)
79
+ second_generator.acl_block!(&block)
80
+ second_generator.install_on(self, opts)
81
+ end
61
82
  end
62
83
  end
63
84
  end
@@ -95,9 +95,13 @@ module Acl9
95
95
 
96
96
  alias action actions
97
97
 
98
+ def logged_in; false end
98
99
  def anonymous; nil end
99
100
  def all; true end
100
- def logged_in; false end
101
+
102
+ alias everyone all
103
+ alias everybody all
104
+ alias anyone all
101
105
 
102
106
  def _parse_and_add_rule(*args)
103
107
  options = args.extract_options!
@@ -108,9 +112,9 @@ module Acl9
108
112
 
109
113
  role_checks = args.map do |who|
110
114
  case who
111
- when nil then "#{_subject_ref}.nil?" # anonymous
112
- when false then "!#{_subject_ref}.nil?" # logged_in
113
- when true then "true" # all
115
+ when anonymous() then "#{_subject_ref}.nil?"
116
+ when logged_in() then "!#{_subject_ref}.nil?"
117
+ when all() then "true"
114
118
  else
115
119
  "!#{_subject_ref}.nil? && #{_subject_ref}.has_role?('#{who.to_s.singularize}', #{object})"
116
120
  end
@@ -1,7 +1,35 @@
1
1
  require File.join(File.dirname(__FILE__), 'dsl_base')
2
2
 
3
3
  module Acl9
4
+ ##
5
+ # This exception is raised whenever ACL block finds that the current user
6
+ # is not authorized for the controller action he wants to execute.
7
+ # @example How to catch this exception in ApplicationController
8
+ # class ApplicationController < ActionController::Base
9
+ # rescue_from 'Acl9::AccessDenied', :with => :access_denied
10
+ #
11
+ # # ...other stuff...
12
+ # private
13
+ #
14
+ # def access_denied
15
+ # if current_user
16
+ # # It's presumed you have a template with words of pity and regret
17
+ # # for unhappy user who is not authorized to do what he wanted
18
+ # render :template => 'home/access_denied'
19
+ # else
20
+ # # In this case user has not even logged in. Might be OK after login.
21
+ # flash[:notice] = 'Access denied. Try to log in first.'
22
+ # redirect_to login_path
23
+ # end
24
+ # end
25
+ # end
26
+ #
4
27
  class AccessDenied < StandardError; end
28
+
29
+ ##
30
+ # This exception is raised when acl9 has generated invalid code for the
31
+ # filtering method or block. Should never happen, and it's a bug when it
32
+ # happens.
5
33
  class FilterSyntaxError < StandardError; end
6
34
 
7
35
  module Dsl
@@ -49,7 +77,7 @@ module Acl9
49
77
  logger.debug self.to_s
50
78
  logger.debug "======"
51
79
  end
52
-
80
+
53
81
  def logger
54
82
  ActionController::Base.logger
55
83
  end
@@ -76,13 +104,15 @@ module Acl9
76
104
  end
77
105
  end
78
106
  RUBY
79
-
107
+
80
108
  self.instance_eval(code, __FILE__, __LINE__)
81
109
  rescue SyntaxError
82
110
  raise FilterSyntaxError, code
83
111
  end
84
112
  end
85
-
113
+
114
+ ################################################################
115
+
86
116
  class FilterMethod < BaseGenerator
87
117
  def initialize(subject_method, method_name)
88
118
  super
@@ -90,7 +120,7 @@ module Acl9
90
120
  @method_name = method_name
91
121
  @controller = nil
92
122
  end
93
-
123
+
94
124
  def install_on(controller_class, options)
95
125
  super
96
126
  _add_method(controller_class)
@@ -105,7 +135,7 @@ module Acl9
105
135
  rescue SyntaxError
106
136
  raise FilterSyntaxError, code
107
137
  end
108
-
138
+
109
139
  def to_method_code
110
140
  <<-RUBY
111
141
  def #{@method_name}
@@ -116,7 +146,9 @@ module Acl9
116
146
  RUBY
117
147
  end
118
148
  end
119
-
149
+
150
+ ################################################################
151
+
120
152
  class BooleanMethod < FilterMethod
121
153
  def install_on(controller_class, opts)
122
154
  debug_dump(controller_class) if opts[:debug]
@@ -128,12 +160,20 @@ module Acl9
128
160
  end
129
161
  end
130
162
 
131
- protected
132
-
163
+ protected
164
+
133
165
  def to_method_code
134
166
  <<-RUBY
135
- def #{@method_name}(options = {})
136
- #{allowance_expression}
167
+ def #{@method_name}(*args)
168
+ options = args.extract_options!
169
+
170
+ unless args.size <= 1
171
+ raise ArgumentError, "call #{@method_name} with 0, 1 or 2 arguments"
172
+ end
173
+
174
+ action_name = args.empty? ? self.action_name : args.first.to_s
175
+
176
+ return #{allowance_expression}
137
177
  end
138
178
  RUBY
139
179
  end
@@ -143,6 +183,8 @@ module Acl9
143
183
  end
144
184
  end
145
185
 
186
+ ################################################################
187
+
146
188
  class HelperMethod < BooleanMethod
147
189
  def initialize(subject_method, method)
148
190
  super
@@ -8,9 +8,9 @@ module Acl9
8
8
  def access_control(method, opts = {}, &block)
9
9
  subject_method = opts.delete(:subject_method) || Acl9::config[:default_subject_method]
10
10
  raise ArgumentError, "Block must be supplied to access_control" unless block
11
-
11
+
12
12
  generator = Acl9::Dsl::Generators::HelperMethod.new(subject_method, method)
13
-
13
+
14
14
  generator.acl_block!(&block)
15
15
  generator.install_on(self, opts)
16
16
  end
@@ -2,29 +2,75 @@ require File.join(File.dirname(__FILE__), 'model_extensions', 'subject')
2
2
  require File.join(File.dirname(__FILE__), 'model_extensions', 'object')
3
3
 
4
4
  module Acl9
5
- module ModelExtensions
5
+ module ModelExtensions #:nodoc:
6
6
  def self.included(base)
7
7
  base.extend(ClassMethods)
8
8
  end
9
9
 
10
10
  module ClassMethods
11
+ # Add #has_role? and other role methods to the class.
12
+ # Makes a class a auth. subject class.
13
+ #
14
+ # @param [Hash] options the options for tuning
15
+ # @option options [String] :role_class_name (Acl9::config[:default_role_class_name])
16
+ # Class name of the role class (e.g. 'AccountRole')
17
+ # @option options [String] :join_table_name (Acl9::config[:default_join_table_name])
18
+ # Join table name (e.g. 'accounts_account_roles')
19
+ # @example
20
+ # class User < ActiveRecord::Base
21
+ # acts_as_authorization_subject
22
+ # end
23
+ #
24
+ # user = User.new
25
+ # user.roles #=> returns Role objects, associated with the user
26
+ # user.has_role!(...)
27
+ # user.has_no_role!(...)
28
+ #
29
+ # # other functions from Acl9::ModelExtensions::Subject are made available
30
+ #
31
+ # @see Acl9::ModelExtensions::Subject
32
+ #
11
33
  def acts_as_authorization_subject(options = {})
12
34
  role = options[:role_class_name] || Acl9::config[:default_role_class_name]
13
- has_and_belongs_to_many :roles, :class_name => role
35
+ join_table = options[:join_table_name] || Acl9::config[:default_join_table_name] ||
36
+ join_table_name(undecorated_table_name(self.to_s), undecorated_table_name(role))
37
+
38
+ has_and_belongs_to_many :role_objects, :class_name => role, :join_table => join_table
14
39
 
15
40
  cattr_accessor :_auth_role_class_name
16
41
  self._auth_role_class_name = role
17
42
 
18
- include Acl9::ModelExtensions::Subject
43
+ include Acl9::ModelExtensions::Subject
19
44
  end
20
45
 
46
+ # Add role query and set methods to the class (making it an auth object class).
47
+ #
48
+ # @param [Hash] options the options for tuning
49
+ # @option options [String] :subject_class_name (Acl9::config[:default_subject_class_name])
50
+ # Subject class name (e.g. 'User', or 'Account)
51
+ # @option options [String] :role_class_name (Acl9::config[:default_role_class_name])
52
+ # Role class name (e.g. 'AccountRole')
53
+ # @example
54
+ # class Product < ActiveRecord::Base
55
+ # acts_as_authorization_object
56
+ # end
57
+ #
58
+ # product = Product.new
59
+ # product.accepted_roles #=> returns Role objects, associated with the product
60
+ # product.users #=> returns User objects, associated with the product
61
+ # product.accepts_role!(...)
62
+ # product.accepts_no_role!(...)
63
+ # # other functions from Acl9::ModelExtensions::Object are made available
64
+ #
65
+ # @see Acl9::ModelExtensions::Object
66
+ #
21
67
  def acts_as_authorization_object(options = {})
22
68
  subject = options[:subject_class_name] || Acl9::config[:default_subject_class_name]
23
- subj_table = subject.tableize
69
+ subj_table = subject.constantize.table_name
24
70
  subj_col = subject.underscore
25
71
 
26
72
  role = options[:role_class_name] || Acl9::config[:default_role_class_name]
27
- role_table = role.tableize
73
+ role_table = role.constantize.table_name
28
74
 
29
75
  sql_tables = <<-EOS
30
76
  FROM #{subj_table}
@@ -36,21 +82,49 @@ module Acl9
36
82
  WHERE authorizable_type = '#{self.class.base_class.to_s}'
37
83
  AND authorizable_id = #{id}
38
84
  EOS
39
-
85
+
40
86
  has_many :accepted_roles, :as => :authorizable, :class_name => role, :dependent => :destroy
41
87
 
42
88
  has_many :"#{subj_table}",
43
89
  :finder_sql => ("SELECT DISTINCT #{subj_table}.*" + sql_tables + sql_where),
44
90
  :counter_sql => ("SELECT COUNT(DISTINCT #{subj_table}.id)" + sql_tables + sql_where),
45
91
  :readonly => true
46
-
92
+
47
93
  include Acl9::ModelExtensions::Object
48
94
  end
49
95
 
96
+ # Make a class an auth role class.
97
+ #
98
+ # You'll probably never create or use objects of this class directly.
99
+ # Various auth. subject and object methods will do that for you
100
+ # internally.
101
+ #
102
+ # @param [Hash] options the options for tuning
103
+ # @option options [String] :subject_class_name (Acl9::config[:default_subject_class_name])
104
+ # Subject class name (e.g. 'User', or 'Account)
105
+ # @option options [String] :join_table_name (Acl9::config[:default_join_table_name])
106
+ # Join table name (e.g. 'accounts_account_roles')
107
+ #
108
+ # @example
109
+ # class Role < ActiveRecord::Base
110
+ # acts_as_authorization_role
111
+ # end
112
+ #
113
+ # @see Acl9::ModelExtensions::Subject#has_role!
114
+ # @see Acl9::ModelExtensions::Subject#has_role?
115
+ # @see Acl9::ModelExtensions::Subject#has_no_role!
116
+ # @see Acl9::ModelExtensions::Object#accepts_role!
117
+ # @see Acl9::ModelExtensions::Object#accepts_role?
118
+ # @see Acl9::ModelExtensions::Object#accepts_no_role!
50
119
  def acts_as_authorization_role(options = {})
51
120
  subject = options[:subject_class_name] || Acl9::config[:default_subject_class_name]
121
+ join_table = options[:join_table_name] || Acl9::config[:default_join_table_name] ||
122
+ join_table_name(undecorated_table_name(self.to_s), undecorated_table_name(subject))
123
+
124
+ has_and_belongs_to_many subject.demodulize.tableize.to_sym,
125
+ :class_name => subject,
126
+ :join_table => join_table
52
127
 
53
- has_and_belongs_to_many subject.tableize.to_sym
54
128
  belongs_to :authorizable, :polymorphic => true
55
129
  end
56
130
  end
@@ -1,24 +1,56 @@
1
1
  module Acl9
2
2
  module ModelExtensions
3
3
  module Object
4
+ ##
5
+ # Role check.
6
+ #
7
+ # @return [Boolean] Returns true if +subject+ has a role +role_name+ on this object.
8
+ #
9
+ # @param [Symbol,String] role_name Role name
10
+ # @param [Subject] subject Subject to add role for
11
+ # @see Acl9::ModelExtensions::Subject#has_role?
4
12
  def accepts_role?(role_name, subject)
5
13
  subject.has_role? role_name, self
6
14
  end
7
15
 
16
+ ##
17
+ # Add role on the object to specified subject.
18
+ #
19
+ # @param [Symbol,String] role_name Role name
20
+ # @param [Subject] subject Subject to add role for
21
+ # @see Acl9::ModelExtensions::Subject#has_role!
8
22
  def accepts_role!(role_name, subject)
9
23
  subject.has_role! role_name, self
10
24
  end
11
25
 
26
+ ##
27
+ # Free specified subject of a role on this object.
28
+ #
29
+ # @param [Symbol,String] role_name Role name
30
+ # @param [Subject] subject Subject to remove role from
31
+ # @see Acl9::ModelExtensions::Subject#has_no_role!
12
32
  def accepts_no_role!(role_name, subject)
13
33
  subject.has_no_role! role_name, self
14
34
  end
15
35
 
36
+ ##
37
+ # Are there any roles for the specified +subject+ on this object?
38
+ #
39
+ # @param [Subject] subject Subject to query roles
40
+ # @return [Boolean] Returns true if +subject+ has any roles on this object.
41
+ # @see Acl9::ModelExtensions::Subject#has_roles_for?
16
42
  def accepts_roles_by?(subject)
17
43
  subject.has_roles_for? self
18
44
  end
19
45
 
20
46
  alias :accepts_role_by? :accepts_roles_by?
21
47
 
48
+ ##
49
+ # Which roles does +subject+ have on this object?
50
+ #
51
+ # @return [Array<Role>] Role instances, associated both with +subject+ and +object+
52
+ # @param [Subject] subject Subject to query roles
53
+ # @see Acl9::ModelExtensions::Subject#roles_for
22
54
  def accepted_roles_by(subject)
23
55
  subject.roles_for self
24
56
  end