be9-acl9 0.10.0 → 0.11.0
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/CHANGELOG.textile +8 -0
- data/README.textile +12 -3
- data/Rakefile +10 -0
- data/VERSION.yml +1 -1
- data/lib/acl9/config.rb +1 -0
- data/lib/acl9/controller_extensions.rb +22 -1
- data/lib/acl9/controller_extensions/dsl_base.rb +8 -4
- data/lib/acl9/controller_extensions/generators.rb +52 -10
- data/lib/acl9/helpers.rb +2 -2
- data/lib/acl9/model_extensions.rb +82 -8
- data/lib/acl9/model_extensions/object.rb +32 -0
- data/lib/acl9/model_extensions/subject.rb +85 -17
- data/test/access_control_test.rb +144 -15
- data/test/dsl_base_test.rb +64 -62
- data/test/roles_test.rb +67 -22
- data/test/support/controllers.rb +57 -3
- data/test/support/models.rb +20 -0
- data/test/support/schema.rb +22 -0
- data/test/test_helper.rb +4 -0
- metadata +9 -8
data/CHANGELOG.textile
CHANGED
@@ -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
|
data/README.textile
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
h1.
|
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
|
data/VERSION.yml
CHANGED
data/lib/acl9/config.rb
CHANGED
@@ -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
|
-
|
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
|
112
|
-
when
|
113
|
-
when
|
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}(
|
136
|
-
|
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
|
data/lib/acl9/helpers.rb
CHANGED
@@ -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
|
-
|
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.
|
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.
|
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
|