authorize 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +5 -0
- data/Gemfile +3 -0
- data/Gemfile.lock +42 -0
- data/LICENSE +20 -0
- data/README +155 -0
- data/Rakefile +25 -0
- data/TODO.txt +9 -0
- data/authorize.gemspec +25 -0
- data/generators/authorize/USAGE +8 -0
- data/generators/authorize/authorize_generator.rb +7 -0
- data/generators/authorize/templates/migrate/create_authorizations.rb +26 -0
- data/install.rb +1 -0
- data/lib/authorize.rb +2 -0
- data/lib/authorize/action_controller.rb +59 -0
- data/lib/authorize/action_view.rb +4 -0
- data/lib/authorize/active_record.rb +37 -0
- data/lib/authorize/bitmask.rb +84 -0
- data/lib/authorize/exceptions.rb +30 -0
- data/lib/authorize/graph.rb +4 -0
- data/lib/authorize/graph/directed_acyclic_graph.rb +10 -0
- data/lib/authorize/graph/directed_acyclic_graph_reverse_traverser.rb +27 -0
- data/lib/authorize/graph/directed_acyclic_graph_traverser.rb +30 -0
- data/lib/authorize/graph/directed_graph.rb +27 -0
- data/lib/authorize/graph/edge.rb +58 -0
- data/lib/authorize/graph/factory.rb +39 -0
- data/lib/authorize/graph/fixtures.rb +33 -0
- data/lib/authorize/graph/graph.rb +55 -0
- data/lib/authorize/graph/traverser.rb +89 -0
- data/lib/authorize/graph/undirected_graph.rb +14 -0
- data/lib/authorize/graph/vertex.rb +53 -0
- data/lib/authorize/permission.rb +97 -0
- data/lib/authorize/redis.rb +2 -0
- data/lib/authorize/redis/array.rb +36 -0
- data/lib/authorize/redis/base.rb +165 -0
- data/lib/authorize/redis/connection_manager.rb +88 -0
- data/lib/authorize/redis/connection_specification.rb +16 -0
- data/lib/authorize/redis/factory.rb +64 -0
- data/lib/authorize/redis/fixtures.rb +22 -0
- data/lib/authorize/redis/hash.rb +34 -0
- data/lib/authorize/redis/model_reference.rb +21 -0
- data/lib/authorize/redis/model_set.rb +19 -0
- data/lib/authorize/redis/set.rb +42 -0
- data/lib/authorize/redis/string.rb +17 -0
- data/lib/authorize/resource.rb +4 -0
- data/lib/authorize/resource_pool.rb +87 -0
- data/lib/authorize/role.rb +115 -0
- data/lib/authorize/test_helper.rb +42 -0
- data/lib/authorize/trustee.rb +4 -0
- data/lib/authorize/version.rb +3 -0
- data/rails/init.rb +5 -0
- data/tasks/authorize_tasks.rake +4 -0
- data/test/Rakefile +7 -0
- data/test/app/controllers/application_controller.rb +5 -0
- data/test/app/controllers/thingy_controller.rb +11 -0
- data/test/app/controllers/widgets_controller.rb +2 -0
- data/test/app/models/public.rb +14 -0
- data/test/app/models/user.rb +8 -0
- data/test/app/models/widget.rb +7 -0
- data/test/config/boot.rb +109 -0
- data/test/config/database.yml +25 -0
- data/test/config/environment.rb +28 -0
- data/test/config/environments/development.rb +4 -0
- data/test/config/environments/test.rb +0 -0
- data/test/config/initializers/mask.rb +1 -0
- data/test/config/initializers/redis.rb +8 -0
- data/test/config/routes.rb +5 -0
- data/test/db/.gitignore +1 -0
- data/test/db/schema.rb +26 -0
- data/test/log/.gitignore +2 -0
- data/test/public/javascripts/application.js +2 -0
- data/test/public/javascripts/controls.js +963 -0
- data/test/public/javascripts/dragdrop.js +972 -0
- data/test/public/javascripts/effects.js +1120 -0
- data/test/public/javascripts/prototype.js +4225 -0
- data/test/script/about +3 -0
- data/test/script/console +3 -0
- data/test/script/dbconsole +3 -0
- data/test/script/destroy +3 -0
- data/test/script/generate +3 -0
- data/test/script/performance/benchmarker +3 -0
- data/test/script/performance/profiler +3 -0
- data/test/script/performance/request +3 -0
- data/test/script/plugin +3 -0
- data/test/script/process/inspector +3 -0
- data/test/script/process/reaper +3 -0
- data/test/script/process/spawner +3 -0
- data/test/script/runner +3 -0
- data/test/script/server +3 -0
- data/test/test/fixtures/authorize/role_graph.yml +11 -0
- data/test/test/fixtures/permissions.yml +27 -0
- data/test/test/fixtures/redis/redis.yml +8 -0
- data/test/test/fixtures/redis/role_graph.yml +29 -0
- data/test/test/fixtures/roles.yml +28 -0
- data/test/test/fixtures/users.yml +12 -0
- data/test/test/fixtures/widgets.yml +12 -0
- data/test/test/functional/controller_class_test.rb +36 -0
- data/test/test/functional/controller_test.rb +46 -0
- data/test/test/test_helper.rb +35 -0
- data/test/test/unit/bitmask_test.rb +112 -0
- data/test/test/unit/fixture_test.rb +59 -0
- data/test/test/unit/graph_directed_acyclic_graph_reverse_traverser_test.rb +43 -0
- data/test/test/unit/graph_directed_acyclic_graph_traverser_test.rb +57 -0
- data/test/test/unit/graph_directed_graph_test.rb +66 -0
- data/test/test/unit/graph_edge_test.rb +53 -0
- data/test/test/unit/graph_graph_test.rb +50 -0
- data/test/test/unit/graph_traverser_test.rb +43 -0
- data/test/test/unit/graph_vertex_test.rb +57 -0
- data/test/test/unit/permission_test.rb +123 -0
- data/test/test/unit/redis_array_test.rb +60 -0
- data/test/test/unit/redis_connection_manager_test.rb +54 -0
- data/test/test/unit/redis_factory_test.rb +85 -0
- data/test/test/unit/redis_fixture_test.rb +18 -0
- data/test/test/unit/redis_hash_test.rb +43 -0
- data/test/test/unit/redis_model_reference_test.rb +39 -0
- data/test/test/unit/redis_set_test.rb +68 -0
- data/test/test/unit/redis_string_test.rb +25 -0
- data/test/test/unit/redis_test.rb +121 -0
- data/test/test/unit/resource_pool_test.rb +93 -0
- data/test/test/unit/resource_test.rb +33 -0
- data/test/test/unit/role_test.rb +143 -0
- data/test/test/unit/trustee_test.rb +35 -0
- data/test/tmp/.gitignore +2 -0
- data/uninstall.rb +1 -0
- metadata +319 -0
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,42 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
authorize (0.0.1)
|
5
|
+
redis (~> 2.1)
|
6
|
+
|
7
|
+
GEM
|
8
|
+
remote: http://rubygems.org/
|
9
|
+
specs:
|
10
|
+
actionmailer (2.3.10)
|
11
|
+
actionpack (= 2.3.10)
|
12
|
+
actionpack (2.3.10)
|
13
|
+
activesupport (= 2.3.10)
|
14
|
+
rack (~> 1.1.0)
|
15
|
+
activerecord (2.3.10)
|
16
|
+
activesupport (= 2.3.10)
|
17
|
+
activeresource (2.3.10)
|
18
|
+
activesupport (= 2.3.10)
|
19
|
+
activesupport (2.3.10)
|
20
|
+
mocha (0.9.10)
|
21
|
+
rake
|
22
|
+
rack (1.1.0)
|
23
|
+
rails (2.3.10)
|
24
|
+
actionmailer (= 2.3.10)
|
25
|
+
actionpack (= 2.3.10)
|
26
|
+
activerecord (= 2.3.10)
|
27
|
+
activeresource (= 2.3.10)
|
28
|
+
activesupport (= 2.3.10)
|
29
|
+
rake (>= 0.8.3)
|
30
|
+
rake (0.8.7)
|
31
|
+
redis (2.1.1)
|
32
|
+
sqlite3 (1.3.3)
|
33
|
+
|
34
|
+
PLATFORMS
|
35
|
+
ruby
|
36
|
+
|
37
|
+
DEPENDENCIES
|
38
|
+
authorize!
|
39
|
+
mocha (~> 0.9)
|
40
|
+
rails (~> 2.3)
|
41
|
+
redis (~> 2.1)
|
42
|
+
sqlite3 (~> 1.3)
|
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2007-2009 Christopher Cyrus Hapgood
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README
ADDED
@@ -0,0 +1,155 @@
|
|
1
|
+
Authorize
|
2
|
+
=========
|
3
|
+
Authorize is a Ruby on Rails plugin providing a sophisticated Role-Based Access Control (RBAC) system. Current functionality highlights include:
|
4
|
+
|
5
|
+
* Polymorphic association of ActiveRecord models as authorizable resources.
|
6
|
+
* Three-level (global, class, instance) authorizations over resources.
|
7
|
+
* "Acts" to support a single ActiveRecord model being both an authorizable subject and trustee.
|
8
|
+
* Hierarchical role tree supporting rich modelling of role assignment.
|
9
|
+
* High-performance resolution of effective roles using Redis key-value database as a graph database.
|
10
|
+
|
11
|
+
For more information on the theory of RBAC, see http://en.wikipedia.org/wiki/Role-based_access_control
|
12
|
+
|
13
|
+
----------------
|
14
|
+
ActionPack
|
15
|
+
The Authorize plugin extends ActionController and ActionView with the ability to check permissions and react accordingly. There are two approaches:
|
16
|
+
a simple boolean check (permit?) and a more sophisticated predicated block (permit) with a configurable callback. In both cases, the method accepts
|
17
|
+
a permissions description hash. For example, using the boolean version:
|
18
|
+
|
19
|
+
permit?(:update => widget)
|
20
|
+
|
21
|
+
More complex expressions typically involve checking for permissions to multiple model instances. The following predicate, for example,
|
22
|
+
is true if the current roles include the :all permission over foo OR the :read permission over bar:
|
23
|
+
|
24
|
+
permit?({:all => foo, :read => bar})
|
25
|
+
|
26
|
+
Clean hooks are available for identifying the appropriate roles for the current request. No "User" class is assumed, only a
|
27
|
+
ApplicationController#roles method that returns an enumeration of the roles for the current request. A simple implementation might
|
28
|
+
work something like this:
|
29
|
+
|
30
|
+
class ApplicationController << ActionController::Base
|
31
|
+
def roles
|
32
|
+
User.current.role.roles
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
----------------
|
37
|
+
ActiveRecord
|
38
|
+
The Authorize plugin extends ActiveRecord with two methods: authorizable_resource and authorizable_trustee. A given model may invoke
|
39
|
+
either or both, depending on requirements. Models are thus extended with additional capabilities as follows:
|
40
|
+
|
41
|
+
Trustee
|
42
|
+
A #role association is defined that links a trustee to a "primary" or "identity" role (Authorize::Role). This role serves as the entry
|
43
|
+
point for traversing the role hierarchy and determining the effective set of roles (identity role plus its children) for a given trustee.
|
44
|
+
|
45
|
+
Resource
|
46
|
+
A #permissions association is defined that links a resource to the set of permissions (Authorize::Permission) that define the available
|
47
|
+
access modes to the resource.
|
48
|
+
|
49
|
+
In addition to the macro methods described above, two ActiveRecord::Base subclasses are defined:
|
50
|
+
|
51
|
+
Authorize::Permission
|
52
|
+
Permissions link roles to resources along with a defined access mode. Access modes are limited to the classics (read, update, delete, etc.),
|
53
|
+
but interpretation of them is application-specific (but see the note below about the list mode). Permissions apply to resources at one of
|
54
|
+
three levels:
|
55
|
+
|
56
|
+
Instance (e.g. update permission for the Widget instance with id 6324)
|
57
|
+
Class (e.g. list permission for all instances of Widget)
|
58
|
+
Global (e.g. read permission for all instances of every model class)
|
59
|
+
|
60
|
+
NOTE: To maximize performance, the list access mode is assumed to be included in EVERY instance of Authorize::Permission. This allows
|
61
|
+
efficient SQL joins with model tables (widgets, for example) and the permissions table (authorize_permissions). The permissions table
|
62
|
+
can grow quite large, and this implied mode obviates the need to index the mask field and add complex conditions to permissions queries.
|
63
|
+
|
64
|
+
Authorize::Role
|
65
|
+
Roles allow flexible modelling of application-specific functions. The Role class is rather thin and mainly serves to identify a role
|
66
|
+
instance and polymorphically associate it, where appropriate, to a trustee. Critically, it has the sole interface (#roles) into the
|
67
|
+
role graph (a DAG) stored in the Redis database.
|
68
|
+
----------------
|
69
|
+
|
70
|
+
A Note on Performance
|
71
|
+
|
72
|
+
Performance of the <Subject Class>.authorized named scope is critical to effective use of this plugin. However, it is difficult to optimize across multiple
|
73
|
+
database systems for multiple use cases. Empirically, it seems to be best to use a UNION of the three cases that can yield an authorization: global, class-
|
74
|
+
based and instance based. Alternatives using moderately complex nested or expanded OR clauses fail to optimize correctly on MySQL 5.0 and degrade terribly
|
75
|
+
with substantial authorization and subject volume. Not surprisingly, COALESCE also fails to optimize nicely. A JOIN-based solution was considered, but the
|
76
|
+
semantics of a JOIN are such that duplicate subject records are returned. The duplicates could be eliminated with :group and :having options, but at the cost
|
77
|
+
of transparency of the #authorized named scope.
|
78
|
+
|
79
|
+
Indexing of the authorization table is very important. See the test application's schema for an reasonable set of indices.
|
80
|
+
|
81
|
+
Code examples of alternatives:
|
82
|
+
|
83
|
+
# Baseline with nested booleans
|
84
|
+
c1 = Authorize::Permission.sanitize_sql_hash_for_conditions(:subject_type => nil)
|
85
|
+
c2 = Authorize::Permission.sanitize_sql_hash_for_conditions(:subject_type => base_class.name)
|
86
|
+
c3l = "%s.%s" % [reflection.quoted_table_name, connection.quote_column_name(reflection.primary_key_name)]
|
87
|
+
c3r = "%s.%s" % [connection.quote_table_name(table_name), connection.quote_column_name(primary_key)]
|
88
|
+
c4 = Authorize::Permission.sanitize_sql_hash_for_conditions(:subject_id => nil)
|
89
|
+
subject_condition_clause = "#{c1} OR (#{c2} AND (#{c3l} = #{c3r} OR #{c4}))"
|
90
|
+
named_scope :a0, lambda {|tokens, roles|
|
91
|
+
scope = Authorize::Permission.scoped(:conditions => subject_condition_clause).with(tokens).as(roles)
|
92
|
+
c = scope.construct_finder_sql({:select => 1, :from => "#{reflection.quoted_table_name} a"}).gsub(/#{reflection.quoted_table_name}\./, 'a.')
|
93
|
+
{:conditions => "EXISTS (%s)" % c}
|
94
|
+
}
|
95
|
+
|
96
|
+
# Baseline with booleans expanded into three ORs
|
97
|
+
c1 = Authorize::Permission.sanitize_sql_hash_for_conditions(:subject_type => nil)
|
98
|
+
c2 = Authorize::Permission.sanitize_sql_hash_for_conditions(:subject_type => base_class.name)
|
99
|
+
c3l = "%s.%s" % [reflection.quoted_table_name, connection.quote_column_name(reflection.primary_key_name)]
|
100
|
+
c3r = "%s.%s" % [connection.quote_table_name(table_name), connection.quote_column_name(primary_key)]
|
101
|
+
c4 = Authorize::Permission.sanitize_sql_hash_for_conditions(:subject_id => nil)
|
102
|
+
subject_condition_clause = "#{c1} OR (#{c2} AND #{c3l} = #{c3r}) OR (#{c1} AND #{c4})"
|
103
|
+
named_scope :a1, lambda {|tokens, roles|
|
104
|
+
scope = Authorize::Permission.scoped(:conditions => subject_condition_clause).with(tokens).as(roles)
|
105
|
+
c = scope.construct_finder_sql({:select => 1, :from => "#{reflection.quoted_table_name} a"}).gsub(/#{reflection.quoted_table_name}\./, 'a.')
|
106
|
+
{:conditions => "EXISTS (%s)" % c}
|
107
|
+
}
|
108
|
+
|
109
|
+
# COALESCE replacing OR (and a subtle but harmless semantic shift)
|
110
|
+
auth_fk = "#{reflection.quoted_table_name}.#{connection.quote_column_name(reflection.primary_key_name)}"
|
111
|
+
subject_pk = "#{connection.quote_table_name(table_name)}.#{connection.quote_column_name(primary_key)}"
|
112
|
+
auth_fk_type = "#{reflection.quoted_table_name}.#{connection.quote_column_name(Authorize::Permission.reflections[:subject].options[:foreign_type])}"
|
113
|
+
subject_condition_clause = "%s = COALESCE(#{auth_fk_type}, %s) AND #{subject_pk} = COALESCE(#{auth_fk}, #{subject_pk})" % ([connection.quote(base_class.name)] * 2)
|
114
|
+
named_scope :a2, lambda {|tokens, roles|
|
115
|
+
scope = Authorize::Permission.scoped(:conditions => subject_condition_clause).with(tokens).as(roles)
|
116
|
+
c = scope.construct_finder_sql({:select => 1, :from => "#{reflection.quoted_table_name} a"}).gsub(/#{reflection.quoted_table_name}\./, 'a.')
|
117
|
+
{:conditions => "EXISTS (%s)" % c}
|
118
|
+
}
|
119
|
+
|
120
|
+
# Correlated subquery with COALESCE
|
121
|
+
auth_fk = "#{reflection.quoted_table_name}.#{connection.quote_column_name(reflection.primary_key_name)}"
|
122
|
+
subject_pk = "#{connection.quote_table_name(table_name)}.#{connection.quote_column_name(primary_key)}"
|
123
|
+
auth_fk_type = "#{reflection.quoted_table_name}.#{connection.quote_column_name(Authorize::Permission.reflections[:subject].options[:foreign_type])}"
|
124
|
+
subject_condition_clause = "%s = COALESCE(#{auth_fk_type}, %s)" % ([connection.quote(base_class.name)] * 2)
|
125
|
+
select_clause = "COALESCE(#{auth_fk}, #{subject_pk})"
|
126
|
+
named_scope :a3, lambda {|tokens, roles|
|
127
|
+
scope = Authorize::Permission.scoped(:conditions => subject_condition_clause).with(tokens).as(roles)
|
128
|
+
c = scope.construct_finder_sql({:select => select_clause})
|
129
|
+
{:conditions => "#{subject_pk} IN (#{c})"}
|
130
|
+
}
|
131
|
+
|
132
|
+
# Three-way union - nice performance but UGLY query
|
133
|
+
auth_fk = "#{reflection.quoted_table_name}.#{connection.quote_column_name(reflection.primary_key_name)}"
|
134
|
+
subject_pk = "#{connection.quote_table_name(table_name)}.#{connection.quote_column_name(primary_key)}"
|
135
|
+
named_scope :a4, lambda {|tokens, roles|
|
136
|
+
scope = Authorize::Permission.with(tokens).as(roles)
|
137
|
+
sq0 = scope.construct_finder_sql({:select => true, :conditions => {:subject_id => nil, :subject_type => nil}})
|
138
|
+
sq1 = scope.construct_finder_sql({:select => true, :conditions => {:subject_type => base_class.name, :subject_id => nil}})
|
139
|
+
sq2 = scope.scoped(:conditions => "#{auth_fk} = #{subject_pk}").construct_finder_sql({:select => true, :conditions => {:subject_type => base_class.name}})
|
140
|
+
{:conditions => "EXISTS (#{sq0} UNION #{sq1} UNION #{sq2})"}
|
141
|
+
}
|
142
|
+
|
143
|
+
# Join - possible to get nice performance, but semantics collapse
|
144
|
+
auth_fk = "#{reflection.quoted_table_name}.#{connection.quote_column_name(reflection.primary_key_name)}"
|
145
|
+
subject_pk = "#{connection.quote_table_name(table_name)}.#{connection.quote_column_name(primary_key)}"
|
146
|
+
auth_fk_type = "#{reflection.quoted_table_name}.#{connection.quote_column_name(Authorize::Permission.reflections[:subject].options[:foreign_type])}"
|
147
|
+
subject_condition_clause = "%s = COALESCE(#{auth_fk_type}, %s) AND #{subject_pk} = COALESCE(#{auth_fk}, #{subject_pk})" % ([connection.quote(base_class.name)] * 2)
|
148
|
+
named_scope :a9, lambda {|tokens, roles|
|
149
|
+
ascope = Authorize::Permission.with(tokens).as(roles).current_scoped_methods[:find][:conditions]
|
150
|
+
{:joins => "JOIN authorizations ON #{subject_condition_clause}", :conditions => {:authorizations => ascope}}
|
151
|
+
}
|
152
|
+
|
153
|
+
TODO:
|
154
|
+
* Flexible configuration of permission bits
|
155
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'rake'
|
2
|
+
require 'rake/testtask'
|
3
|
+
require 'rake/rdoctask'
|
4
|
+
require 'bundler'
|
5
|
+
|
6
|
+
Bundler::GemHelper.install_tasks
|
7
|
+
|
8
|
+
desc 'Default: run unit tests.'
|
9
|
+
task :default => :test
|
10
|
+
|
11
|
+
desc 'Test the authorize plugin.'
|
12
|
+
Rake::TestTask.new(:test) do |t|
|
13
|
+
t.libs << 'test/test'
|
14
|
+
t.pattern = 'test/test/**/*_test.rb'
|
15
|
+
t.verbose = true
|
16
|
+
end
|
17
|
+
|
18
|
+
desc 'Generate documentation for the authorize plugin.'
|
19
|
+
Rake::RDocTask.new(:rdoc) do |rdoc|
|
20
|
+
rdoc.rdoc_dir = 'rdoc'
|
21
|
+
rdoc.title = 'Authorize'
|
22
|
+
rdoc.options << '--line-numbers' << '--inline-source'
|
23
|
+
rdoc.rdoc_files.include('README')
|
24
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
25
|
+
end
|
data/TODO.txt
ADDED
@@ -0,0 +1,9 @@
|
|
1
|
+
Aggregate at the database: bit_or for MySQL, custom function for SQLite3 (http://snippets.dzone.com/posts/show/3717)
|
2
|
+
Per-model permission operations
|
3
|
+
Index into roles has_many association with relation string
|
4
|
+
Scrap name attribute and let I18N handle Role#to_s with relation string and reference (I18n.t 'role.MOD', :thing => "Sabre 36")
|
5
|
+
REDIS: ModelArray and ModelHash (key, value, key/value) like ModelSet
|
6
|
+
REDIS: Deprecate exists? class and instance methods -the semantics are fishy for degenerate objects, particularly degenerate containers.
|
7
|
+
GRAPH: Add UndirectedEdge to make UndirectedGraph's edges more efficient and semantically clean (with one set of properties, not two).
|
8
|
+
GRAPH: Streamline Fixtures to only use new once.
|
9
|
+
Eliminate reliance on ActiveSupport dependency loader.
|
data/authorize.gemspec
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "authorize/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "authorize"
|
7
|
+
s.version = Authorize::VERSION
|
8
|
+
s.platform = Gem::Platform::RUBY
|
9
|
+
s.authors = ["Chris Hapgood"]
|
10
|
+
s.email = ["cch1@hapgoods.com"]
|
11
|
+
s.homepage = ""
|
12
|
+
s.summary = %q{A fast and flexible RBAC for Rails}
|
13
|
+
s.description = %q{Authorize implements a full-blown Role-based Access Control (RBAC) system for Ruby on Rails}
|
14
|
+
|
15
|
+
s.rubyforge_project = "authorize"
|
16
|
+
|
17
|
+
s.files = `git ls-files`.split("\n")
|
18
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
19
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
20
|
+
s.require_paths = ["lib"]
|
21
|
+
s.add_development_dependency('mocha', '~>0.9')
|
22
|
+
s.add_development_dependency('sqlite3', '~>1.3')
|
23
|
+
s.add_development_dependency('rails', '~>2.3')
|
24
|
+
s.add_runtime_dependency('redis', '~>2.1')
|
25
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
class CreateAuthorizations < ActiveRecord::Migration
|
2
|
+
def self.up
|
3
|
+
create_table :authorize_permissions, :force => true do |t|
|
4
|
+
t.references :role, :null => false
|
5
|
+
t.references :resource, :polymorphic => true
|
6
|
+
t.integer :mask, :limit => 255, :default => 1, :null => false
|
7
|
+
t.datetime :updated_at
|
8
|
+
end
|
9
|
+
add_index :authorize_permissions, [:role_id, :resource_id, :resource_type], :unique => true, :name => "index_authorize_permissions_on_role_id_and_resource"
|
10
|
+
add_index :authorize_permissions, [:resource_id, :resource_type, :role_id], :unique => true, :name => "index_authorize_permissions_on_resource_and_role_id"
|
11
|
+
|
12
|
+
create_table :authorize_roles, :force => true do |t|
|
13
|
+
t.references :resource, :polymorphic => true
|
14
|
+
t.string :name
|
15
|
+
t.string :relation, :limit => 3
|
16
|
+
t.datetime :updated_at
|
17
|
+
end
|
18
|
+
add_index :authorize_roles, [:resource_id, :resource_type, :relation], :unique => true, :name => "index_authorize_roles_on_resource_and_role_id"
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.down
|
22
|
+
drop_table :authorize_roles
|
23
|
+
drop_table :authorize_permissions
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
data/install.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
# Install hook code here
|
data/lib/authorize.rb
ADDED
@@ -0,0 +1,59 @@
|
|
1
|
+
module Authorize
|
2
|
+
module ActionController
|
3
|
+
def self.included(recipient)
|
4
|
+
if recipient.respond_to?(:rescue_responses)
|
5
|
+
recipient.rescue_responses['Authorize::AuthorizationError'] = :forbidden
|
6
|
+
end
|
7
|
+
recipient.extend(ClassMethods)
|
8
|
+
recipient.class_eval do
|
9
|
+
include InstanceMethods
|
10
|
+
helper_method :permit?
|
11
|
+
helper_method :permit
|
12
|
+
helper_method :handle_authorization_failure
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
module ClassMethods
|
17
|
+
# Allow action-level authorization check with an appended before_filter.
|
18
|
+
def permit(authorization_expression, options = {})
|
19
|
+
auth_options = options.slice!(:only, :except)
|
20
|
+
append_before_filter(options) do |controller|
|
21
|
+
controller.permit(authorization_expression, auth_options)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
module InstanceMethods
|
27
|
+
# Simple predicate for authorization.
|
28
|
+
def permit?(authorization_hash, options = {})
|
29
|
+
authorization_hash.any? do |(modes, resource)|
|
30
|
+
request_mask = Authorize::Permission::Mask[modes]
|
31
|
+
roles = options[:roles] || self.roles
|
32
|
+
Authorize::Permission.over(resource).as(roles).permit?(request_mask).tap do |authorized|
|
33
|
+
Rails.logger.debug("Authorization check: #{authorized ? '✔' : '✖'} #{request_mask}")
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
# Allow method-level authorization checks.
|
39
|
+
# permit (without a trailing question mark) invokes the callback "handle_authorization_failure" by default.
|
40
|
+
# Specify :callback => false to turn off callbacks.
|
41
|
+
def permit(authorization_hash, options = {})
|
42
|
+
options = {:callback => :handle_authorization_failure}.merge(options)
|
43
|
+
callback = options.delete(:callback)
|
44
|
+
if permit?(authorization_hash, options)
|
45
|
+
yield if block_given?
|
46
|
+
else
|
47
|
+
__send__(callback) if callback
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
private
|
52
|
+
# Handle authorization failure within permit. Override this callback in your ApplicationController
|
53
|
+
# for custom behavior. This method typically returns the value for the around_filter
|
54
|
+
def handle_authorization_failure
|
55
|
+
raise Authorize::AuthorizationError, 'You are not authorized for the requested operation.'
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module Authorize
|
2
|
+
module ActiveRecord
|
3
|
+
def self.included(recipient)
|
4
|
+
recipient.extend(ClassMethods)
|
5
|
+
end
|
6
|
+
|
7
|
+
module ClassMethods
|
8
|
+
def authorizable_trustee
|
9
|
+
include Authorize::Trustee
|
10
|
+
# The "identity" role -the single role that represents this trustee. It is also the root vertex for collecting
|
11
|
+
# the set of roles belonging to the trustee.
|
12
|
+
has_one :role, :class_name => "Authorize::Role", :as => :resource, :conditions => {:relation => nil}, :dependent => :destroy
|
13
|
+
after_create {|trustee| trustee.create_role(:name => to_s)}
|
14
|
+
end
|
15
|
+
|
16
|
+
def authorizable_resource
|
17
|
+
include Authorize::Resource
|
18
|
+
has_many :permissions, :class_name => "Authorize::Permission", :as => :resource, :dependent => :delete_all
|
19
|
+
# The roles that represent relations/associations of a resource
|
20
|
+
has_many :roles, :class_name => "Authorize::Role", :as => :resource
|
21
|
+
reflection = reflections[:permissions]
|
22
|
+
auth_fk = "#{reflection.quoted_table_name}.#{connection.quote_column_name(reflection.primary_key_name)}"
|
23
|
+
resource_pk = "#{connection.quote_table_name(table_name)}.#{connection.quote_column_name(primary_key)}"
|
24
|
+
# See README file for a discussion of the performance of this named scope
|
25
|
+
named_scope :permitted, lambda {|*args|
|
26
|
+
roles, modes = *args
|
27
|
+
scope = Permission.as(roles)
|
28
|
+
scope = scope.to_do(Authorize::Permission::Mask[modes]) if modes
|
29
|
+
sq0 = scope.construct_finder_sql({:select => 1, :conditions => {:resource_id => nil, :resource_type => nil}})
|
30
|
+
sq1 = scope.construct_finder_sql({:select => 1, :conditions => {:resource_type => base_class.name, :resource_id => nil}})
|
31
|
+
sq2 = scope.scoped(:conditions => "#{auth_fk} = #{resource_pk}").construct_finder_sql({:select => 1, :conditions => {:resource_type => base_class.name}})
|
32
|
+
{:conditions => "EXISTS (#{sq0} UNION #{sq1} UNION #{sq2})"}
|
33
|
+
}
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|