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