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