authorize 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (124) hide show
  1. data/.gitignore +5 -0
  2. data/Gemfile +3 -0
  3. data/Gemfile.lock +42 -0
  4. data/LICENSE +20 -0
  5. data/README +155 -0
  6. data/Rakefile +25 -0
  7. data/TODO.txt +9 -0
  8. data/authorize.gemspec +25 -0
  9. data/generators/authorize/USAGE +8 -0
  10. data/generators/authorize/authorize_generator.rb +7 -0
  11. data/generators/authorize/templates/migrate/create_authorizations.rb +26 -0
  12. data/install.rb +1 -0
  13. data/lib/authorize.rb +2 -0
  14. data/lib/authorize/action_controller.rb +59 -0
  15. data/lib/authorize/action_view.rb +4 -0
  16. data/lib/authorize/active_record.rb +37 -0
  17. data/lib/authorize/bitmask.rb +84 -0
  18. data/lib/authorize/exceptions.rb +30 -0
  19. data/lib/authorize/graph.rb +4 -0
  20. data/lib/authorize/graph/directed_acyclic_graph.rb +10 -0
  21. data/lib/authorize/graph/directed_acyclic_graph_reverse_traverser.rb +27 -0
  22. data/lib/authorize/graph/directed_acyclic_graph_traverser.rb +30 -0
  23. data/lib/authorize/graph/directed_graph.rb +27 -0
  24. data/lib/authorize/graph/edge.rb +58 -0
  25. data/lib/authorize/graph/factory.rb +39 -0
  26. data/lib/authorize/graph/fixtures.rb +33 -0
  27. data/lib/authorize/graph/graph.rb +55 -0
  28. data/lib/authorize/graph/traverser.rb +89 -0
  29. data/lib/authorize/graph/undirected_graph.rb +14 -0
  30. data/lib/authorize/graph/vertex.rb +53 -0
  31. data/lib/authorize/permission.rb +97 -0
  32. data/lib/authorize/redis.rb +2 -0
  33. data/lib/authorize/redis/array.rb +36 -0
  34. data/lib/authorize/redis/base.rb +165 -0
  35. data/lib/authorize/redis/connection_manager.rb +88 -0
  36. data/lib/authorize/redis/connection_specification.rb +16 -0
  37. data/lib/authorize/redis/factory.rb +64 -0
  38. data/lib/authorize/redis/fixtures.rb +22 -0
  39. data/lib/authorize/redis/hash.rb +34 -0
  40. data/lib/authorize/redis/model_reference.rb +21 -0
  41. data/lib/authorize/redis/model_set.rb +19 -0
  42. data/lib/authorize/redis/set.rb +42 -0
  43. data/lib/authorize/redis/string.rb +17 -0
  44. data/lib/authorize/resource.rb +4 -0
  45. data/lib/authorize/resource_pool.rb +87 -0
  46. data/lib/authorize/role.rb +115 -0
  47. data/lib/authorize/test_helper.rb +42 -0
  48. data/lib/authorize/trustee.rb +4 -0
  49. data/lib/authorize/version.rb +3 -0
  50. data/rails/init.rb +5 -0
  51. data/tasks/authorize_tasks.rake +4 -0
  52. data/test/Rakefile +7 -0
  53. data/test/app/controllers/application_controller.rb +5 -0
  54. data/test/app/controllers/thingy_controller.rb +11 -0
  55. data/test/app/controllers/widgets_controller.rb +2 -0
  56. data/test/app/models/public.rb +14 -0
  57. data/test/app/models/user.rb +8 -0
  58. data/test/app/models/widget.rb +7 -0
  59. data/test/config/boot.rb +109 -0
  60. data/test/config/database.yml +25 -0
  61. data/test/config/environment.rb +28 -0
  62. data/test/config/environments/development.rb +4 -0
  63. data/test/config/environments/test.rb +0 -0
  64. data/test/config/initializers/mask.rb +1 -0
  65. data/test/config/initializers/redis.rb +8 -0
  66. data/test/config/routes.rb +5 -0
  67. data/test/db/.gitignore +1 -0
  68. data/test/db/schema.rb +26 -0
  69. data/test/log/.gitignore +2 -0
  70. data/test/public/javascripts/application.js +2 -0
  71. data/test/public/javascripts/controls.js +963 -0
  72. data/test/public/javascripts/dragdrop.js +972 -0
  73. data/test/public/javascripts/effects.js +1120 -0
  74. data/test/public/javascripts/prototype.js +4225 -0
  75. data/test/script/about +3 -0
  76. data/test/script/console +3 -0
  77. data/test/script/dbconsole +3 -0
  78. data/test/script/destroy +3 -0
  79. data/test/script/generate +3 -0
  80. data/test/script/performance/benchmarker +3 -0
  81. data/test/script/performance/profiler +3 -0
  82. data/test/script/performance/request +3 -0
  83. data/test/script/plugin +3 -0
  84. data/test/script/process/inspector +3 -0
  85. data/test/script/process/reaper +3 -0
  86. data/test/script/process/spawner +3 -0
  87. data/test/script/runner +3 -0
  88. data/test/script/server +3 -0
  89. data/test/test/fixtures/authorize/role_graph.yml +11 -0
  90. data/test/test/fixtures/permissions.yml +27 -0
  91. data/test/test/fixtures/redis/redis.yml +8 -0
  92. data/test/test/fixtures/redis/role_graph.yml +29 -0
  93. data/test/test/fixtures/roles.yml +28 -0
  94. data/test/test/fixtures/users.yml +12 -0
  95. data/test/test/fixtures/widgets.yml +12 -0
  96. data/test/test/functional/controller_class_test.rb +36 -0
  97. data/test/test/functional/controller_test.rb +46 -0
  98. data/test/test/test_helper.rb +35 -0
  99. data/test/test/unit/bitmask_test.rb +112 -0
  100. data/test/test/unit/fixture_test.rb +59 -0
  101. data/test/test/unit/graph_directed_acyclic_graph_reverse_traverser_test.rb +43 -0
  102. data/test/test/unit/graph_directed_acyclic_graph_traverser_test.rb +57 -0
  103. data/test/test/unit/graph_directed_graph_test.rb +66 -0
  104. data/test/test/unit/graph_edge_test.rb +53 -0
  105. data/test/test/unit/graph_graph_test.rb +50 -0
  106. data/test/test/unit/graph_traverser_test.rb +43 -0
  107. data/test/test/unit/graph_vertex_test.rb +57 -0
  108. data/test/test/unit/permission_test.rb +123 -0
  109. data/test/test/unit/redis_array_test.rb +60 -0
  110. data/test/test/unit/redis_connection_manager_test.rb +54 -0
  111. data/test/test/unit/redis_factory_test.rb +85 -0
  112. data/test/test/unit/redis_fixture_test.rb +18 -0
  113. data/test/test/unit/redis_hash_test.rb +43 -0
  114. data/test/test/unit/redis_model_reference_test.rb +39 -0
  115. data/test/test/unit/redis_set_test.rb +68 -0
  116. data/test/test/unit/redis_string_test.rb +25 -0
  117. data/test/test/unit/redis_test.rb +121 -0
  118. data/test/test/unit/resource_pool_test.rb +93 -0
  119. data/test/test/unit/resource_test.rb +33 -0
  120. data/test/test/unit/role_test.rb +143 -0
  121. data/test/test/unit/trustee_test.rb +35 -0
  122. data/test/tmp/.gitignore +2 -0
  123. data/uninstall.rb +1 -0
  124. metadata +319 -0
data/.gitignore ADDED
@@ -0,0 +1,5 @@
1
+ .settings
2
+ rdoc
3
+ pkg/*
4
+ *.gem
5
+ .bundle
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source "http://rubygems.org"
2
+
3
+ gemspec
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,8 @@
1
+ Description:
2
+ Generates a migration for the authorizations table suitable for use with the Authorize plugin.
3
+
4
+ Example:
5
+ ./script/generate authorize authorizations
6
+
7
+ This will create:
8
+ db/migrate/<timestamp>_create_authorizations.rb
@@ -0,0 +1,7 @@
1
+ class AuthorizeGenerator < Rails::Generator::NamedBase
2
+ def manifest
3
+ record do |m|
4
+ m.migration_template "migrate/create_authorizations.rb", "db/migrate", {:migration_file_name => 'create_authorizations'}
5
+ end
6
+ end
7
+ 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,2 @@
1
+ module Authorize
2
+ end
@@ -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,4 @@
1
+ module Authorize
2
+ module ActionView
3
+ end
4
+ 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