authorize 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 +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
|