authorizer 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/History.txt +4 -0
- data/Manifest.txt +17 -0
- data/PostInstall.txt +7 -0
- data/README.rdoc +160 -0
- data/Rakefile +26 -0
- data/app/models/object_role.rb +59 -0
- data/lib/authorizer.rb +7 -0
- data/lib/authorizer/admin.rb +112 -0
- data/lib/authorizer/application_controller.rb +65 -0
- data/lib/authorizer/base.rb +320 -0
- data/lib/authorizer/exceptions.rb +6 -0
- data/lib/authorizer/object_observer.rb +21 -0
- data/lib/authorizer/user_observer.rb +26 -0
- data/rails/init.rb +11 -0
- data/script/console +10 -0
- data/script/destroy +14 -0
- data/script/generate +14 -0
- metadata +149 -0
data/History.txt
ADDED
data/Manifest.txt
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
History.txt
|
2
|
+
Manifest.txt
|
3
|
+
PostInstall.txt
|
4
|
+
README.rdoc
|
5
|
+
Rakefile
|
6
|
+
lib/authorizer.rb
|
7
|
+
script/console
|
8
|
+
script/destroy
|
9
|
+
script/generate
|
10
|
+
lib/authorizer/admin.rb
|
11
|
+
lib/authorizer/application_controller.rb
|
12
|
+
lib/authorizer/base.rb
|
13
|
+
lib/authorizer/exceptions.rb
|
14
|
+
lib/authorizer/object_observer.rb
|
15
|
+
lib/authorizer/user_observer.rb
|
16
|
+
app/models/object_role.rb
|
17
|
+
rails/init.rb
|
data/PostInstall.txt
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
Remaining installation steps: (1 and 2 already done!)
|
2
|
+
|
3
|
+
3. generate the migration by running "script/generate authorizer_migration"
|
4
|
+
4. run "rake db:migrate" to migrate your database
|
5
|
+
5. Add observers to 'config/environment.rb'
|
6
|
+
|
7
|
+
config.active_record.observers = "Authorizer::UserObserver", "Authorizer::ObjectObserver"
|
data/README.rdoc
ADDED
@@ -0,0 +1,160 @@
|
|
1
|
+
= authorizer
|
2
|
+
|
3
|
+
* https://github.com/cmdjohnson/authorizer
|
4
|
+
|
5
|
+
== DESCRIPTION:
|
6
|
+
|
7
|
+
Authorizer is a gem for Ruby (in conjunction with Rails 2.3) that does authorization for you on a per-object basis. What makes this gem different from e.g. declarative_authorization and cancan is they define one role for the entire application. With Authorizer, you define roles for different users on every Rails object.
|
8
|
+
|
9
|
+
Let's use a Dropbox analogy.
|
10
|
+
|
11
|
+
With Dropbox, you can choose which folder you want to share. For instance:
|
12
|
+
|
13
|
+
Al has a home folder with these subfolders in it:
|
14
|
+
- Music (shared with Bob)
|
15
|
+
- Pictures (shared with Casper and Bob)
|
16
|
+
- News (shared with no-one)
|
17
|
+
|
18
|
+
This causes Al to have all 3 folders in his Dropbox. Bob has 2 and Casper has only 1 folder called Pictures.
|
19
|
+
|
20
|
+
In other words, a user has access to a subset of the entire collection of folders. Bob has access to 2 of Al's folders, namely Music and Pictures. But he doesn't even see the News folder, nor can he download files from it.
|
21
|
+
|
22
|
+
Bob's access to the two folders is both read and write, so let's call that role "admin". Al is the owner of all 3 folders and has a role called "owner". This leads to the following Roles table:
|
23
|
+
|
24
|
+
folder_name user_name role
|
25
|
+
Music Al owner
|
26
|
+
Bob admin
|
27
|
+
Pictures Al owner
|
28
|
+
Bob admin
|
29
|
+
Casper admin
|
30
|
+
News Al owner
|
31
|
+
|
32
|
+
Now if we would allow Bob to also access the News folder but only read from it, we could add the role called "reader" to the table:
|
33
|
+
|
34
|
+
folder_name user_name role
|
35
|
+
News Bob reader
|
36
|
+
|
37
|
+
This is exactly what Authorizer does for your Rails application.
|
38
|
+
|
39
|
+
== FEATURES/PROBLEMS:
|
40
|
+
|
41
|
+
Handles authorization for you.
|
42
|
+
|
43
|
+
== SYNOPSIS:
|
44
|
+
|
45
|
+
Authorize a user on an object
|
46
|
+
|
47
|
+
Authorizer::Base.authorize_user( :object => object )
|
48
|
+
|
49
|
+
=> true/false
|
50
|
+
|
51
|
+
If you want to know if the current user is authorized on an object, use:
|
52
|
+
|
53
|
+
Authorizer::Base.user_is_authorized?( :object => object)
|
54
|
+
=> true/false
|
55
|
+
|
56
|
+
Remove authorization from an object
|
57
|
+
|
58
|
+
Authorizer::Base.remove_authorization( :object => object )
|
59
|
+
=> true/false
|
60
|
+
|
61
|
+
Find all objects that the current user is authorized to use
|
62
|
+
|
63
|
+
Authorizer::Base.find("Post", :all, { :conditions => { :name => "my_post" } }) # [ #<Post id: 1>, #<Post id: 2> ]
|
64
|
+
Authorizer::Base.find("Post", :first) #<Post id: 1>
|
65
|
+
Authorizer::Base.find("Post", [ 1, 6 ]) # [ #<Post id: 1>, #<Post id: 6> ]
|
66
|
+
|
67
|
+
If you are using inherited_resources, you can also use these filters in your controller class:
|
68
|
+
|
69
|
+
# own created objects so you can access them after creation
|
70
|
+
after_filter :own_created_object, :only => :create
|
71
|
+
# authorize entire controller
|
72
|
+
before_filter :authorize, :only => [ :show, :edit, :update, :destroy ]
|
73
|
+
|
74
|
+
This obviously works out of the box with resource-oriented controllers, but with anything different you'll have to make your own choices.
|
75
|
+
|
76
|
+
If you're just getting started with Authorizer but you already have a running app, you can have one user own all objects with this method:
|
77
|
+
|
78
|
+
Authorizer::Admin.create_brand_new_object_roles(:user => User.first)
|
79
|
+
|
80
|
+
This method will guess what objects to use by checking for descendants of ActiveRecord::Base.
|
81
|
+
|
82
|
+
If you just want to do this for the Post and Category classes, use:
|
83
|
+
|
84
|
+
Authorizer::Admin.create_brand_new_object_roles(:user => User.first, :objects => [ "Post", "Category" ])
|
85
|
+
|
86
|
+
Authorizer uses ActiveRecord observers to make sure it doesn't make any mess, for instance, when a user is deleted, all of his authorization objects are deleted as well. Should you want more control over this garbage collection process, or if you are a cleanfreak, use this to get rid of any stale authorization objects lying around in your database: (protip: embed into rake task!)
|
87
|
+
|
88
|
+
Authorizer::Admin.remove_all_unused_authorization_objects
|
89
|
+
|
90
|
+
== REQUIREMENTS:
|
91
|
+
|
92
|
+
- Ruby (this gem was tested with 1.8.7)
|
93
|
+
- Rails 2.3 (tested with 2.3.11 and 2.3.12)
|
94
|
+
- Authlogic (for authentication)
|
95
|
+
|
96
|
+
Optional:
|
97
|
+
- inherited_resources if you want to use the controller filters supplied with this gem. Otherwise, you'll have to check for authorization yourself.
|
98
|
+
|
99
|
+
== INSTALL:
|
100
|
+
|
101
|
+
Installation
|
102
|
+
===
|
103
|
+
|
104
|
+
1. sudo gem install authorizer
|
105
|
+
2. add "authorizer" to your Gemfile (I hope you've stopped using config.gem already even if you are on Rails 2.3?)
|
106
|
+
3. generate a migration for authorization objects:
|
107
|
+
|
108
|
+
script/generate migration CreateObjectRoles
|
109
|
+
|
110
|
+
Paste this code into the newly generated file:
|
111
|
+
|
112
|
+
def self.up
|
113
|
+
create_table :object_roles do |t|
|
114
|
+
t.string :klazz_name
|
115
|
+
t.integer :object_reference
|
116
|
+
t.references :user
|
117
|
+
t.string :role
|
118
|
+
|
119
|
+
t.timestamps
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
def self.down
|
124
|
+
drop_table :object_roles
|
125
|
+
end
|
126
|
+
|
127
|
+
4. run "rake db:migrate" to migrate your database
|
128
|
+
|
129
|
+
That's it!
|
130
|
+
|
131
|
+
== DEVELOPERS:
|
132
|
+
|
133
|
+
Reviews, patches and bug tickets are welcome!
|
134
|
+
|
135
|
+
{authorizer_project}[https://github.com/cmdjohnson/authorizer_project] is a sample project that shows the usage of the Authorizer gem. It also contains all tests for your testing pleasure.
|
136
|
+
|
137
|
+
== LICENSE:
|
138
|
+
|
139
|
+
(The MIT License)
|
140
|
+
|
141
|
+
Copyright (c) 2011 Commander Johnson <commanderjohnson@gmail.com>
|
142
|
+
|
143
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
144
|
+
a copy of this software and associated documentation files (the
|
145
|
+
'Software'), to deal in the Software without restriction, including
|
146
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
147
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
148
|
+
permit persons to whom the Software is furnished to do so, subject to
|
149
|
+
the following conditions:
|
150
|
+
|
151
|
+
The above copyright notice and this permission notice shall be
|
152
|
+
included in all copies or substantial portions of the Software.
|
153
|
+
|
154
|
+
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
155
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
156
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
157
|
+
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
158
|
+
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
159
|
+
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
160
|
+
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/Rakefile
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
gem 'hoe', '>= 2.1.0'
|
3
|
+
require 'hoe'
|
4
|
+
require 'fileutils'
|
5
|
+
require './lib/authorizer'
|
6
|
+
|
7
|
+
Hoe.plugin :newgem
|
8
|
+
# Hoe.plugin :website
|
9
|
+
# Hoe.plugin :cucumberfeatures
|
10
|
+
|
11
|
+
# Generate all the Rake tasks
|
12
|
+
# Run 'rake -T' to see list of generated tasks (from gem root directory)
|
13
|
+
$hoe = Hoe.spec 'authorizer' do
|
14
|
+
self.developer 'CmdJohnson', 'commanderjohnson@gmail.com'
|
15
|
+
self.post_install_message = 'PostInstall.txt'
|
16
|
+
self.rubyforge_name = self.name
|
17
|
+
self.extra_deps = [['options_checker','>= 0.0.1']]
|
18
|
+
|
19
|
+
end
|
20
|
+
|
21
|
+
require 'newgem/tasks'
|
22
|
+
Dir['tasks/**/*.rake'].each { |t| load t }
|
23
|
+
|
24
|
+
# TODO - want other tests/tasks run by default? Add them to the list
|
25
|
+
# remove_task :default
|
26
|
+
# task :default => [:spec, :features]
|
@@ -0,0 +1,59 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
class ObjectRole < ActiveRecord::Base
|
3
|
+
##############################################################################
|
4
|
+
# class methods
|
5
|
+
##############################################################################
|
6
|
+
|
7
|
+
def self.roles
|
8
|
+
[ "owner" ]
|
9
|
+
end
|
10
|
+
|
11
|
+
# What ObjectRoles does this object have associated?
|
12
|
+
def self.find_all_by_object(object)
|
13
|
+
raise "Can only operate on ActiveRecord::Base objects." unless object.is_a?(ActiveRecord::Base)
|
14
|
+
raise "Can only operate on saved objects" if object.new_record?
|
15
|
+
|
16
|
+
klazz_name = object.class.to_s
|
17
|
+
object_reference = object.id
|
18
|
+
|
19
|
+
ObjectRole.find(:all, :conditions => { :klazz_name => klazz_name, :object_reference => object_reference } )
|
20
|
+
end
|
21
|
+
|
22
|
+
##############################################################################
|
23
|
+
# associations
|
24
|
+
##############################################################################
|
25
|
+
|
26
|
+
belongs_to :user
|
27
|
+
|
28
|
+
##############################################################################
|
29
|
+
# validations
|
30
|
+
##############################################################################
|
31
|
+
|
32
|
+
validates_presence_of :klazz_name, :object_reference, :user_id, :role
|
33
|
+
validates_numericality_of :object_reference, :only_integer => true
|
34
|
+
validates_inclusion_of :role, :in => ObjectRole.roles
|
35
|
+
|
36
|
+
##############################################################################
|
37
|
+
# constructor
|
38
|
+
##############################################################################
|
39
|
+
|
40
|
+
##############################################################################
|
41
|
+
# instance methods
|
42
|
+
##############################################################################
|
43
|
+
|
44
|
+
def description
|
45
|
+
"#{self.klazz_name} #{self.object_reference}"
|
46
|
+
end
|
47
|
+
|
48
|
+
def object
|
49
|
+
obj = nil
|
50
|
+
|
51
|
+
begin
|
52
|
+
klazz = eval(self.klazz_name)
|
53
|
+
obj = klazz.find(self.object_reference)
|
54
|
+
rescue
|
55
|
+
end
|
56
|
+
|
57
|
+
obj
|
58
|
+
end
|
59
|
+
end
|
data/lib/authorizer.rb
ADDED
@@ -0,0 +1,112 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
module Authorizer
|
3
|
+
############################################################################
|
4
|
+
# helper class that does administrative functions for you
|
5
|
+
############################################################################
|
6
|
+
class Admin < Base
|
7
|
+
# not everybody uses "User" as user class name. We do :o -=- :))) ffFF ww ppO :)))
|
8
|
+
@@user_class_name = "User"
|
9
|
+
|
10
|
+
############################################################################
|
11
|
+
# create_brand_new_object_roles
|
12
|
+
#
|
13
|
+
# For if you've been working without Authorizer and want to start using it.
|
14
|
+
# Obviously, if you don't have any ObjectRoles then you'll find yourself blocked out of your own app.
|
15
|
+
# This method will assign all objects listed in an array to a certain user.
|
16
|
+
# For instance:
|
17
|
+
#
|
18
|
+
# user = User.find(1)
|
19
|
+
# objects = [ "Post", "Category" ]
|
20
|
+
# Authorizer::Admin.create_brand_new_object_roles( :user => user, :objects => objects )
|
21
|
+
#
|
22
|
+
# If objects is not specified, Admin will look for all descendants of ActiveRecord::Base
|
23
|
+
# and exclude the ObjectRole and User classes.
|
24
|
+
#
|
25
|
+
# Authorizer::Admin.create_brand_new_object_roles( :user => user )
|
26
|
+
############################################################################
|
27
|
+
|
28
|
+
def self.create_brand_new_object_roles(options = {})
|
29
|
+
OptionsChecker.check(options, [ :user ])
|
30
|
+
|
31
|
+
objects = gather_direct_descendants_of_activerecord_base || options[:objects]
|
32
|
+
|
33
|
+
ret = false
|
34
|
+
|
35
|
+
raise "objects must be an Array" unless objects.is_a?(Array)
|
36
|
+
|
37
|
+
# Nothing to do ..
|
38
|
+
return ret if objects.blank?
|
39
|
+
|
40
|
+
begin
|
41
|
+
user_id = options[:user].id
|
42
|
+
rescue
|
43
|
+
end
|
44
|
+
|
45
|
+
unless user_id.nil?
|
46
|
+
for object in objects
|
47
|
+
evaled_klazz = nil
|
48
|
+
|
49
|
+
begin
|
50
|
+
evaled_klazz = eval(object)
|
51
|
+
rescue
|
52
|
+
end
|
53
|
+
|
54
|
+
unless evaled_klazz.nil?
|
55
|
+
# One is enough to return exit status OK.
|
56
|
+
ret = true
|
57
|
+
# Let's find all. This is the same as Post.all
|
58
|
+
if evaled_klazz.is_a?(Class)
|
59
|
+
collection = evaled_klazz.all
|
60
|
+
|
61
|
+
# Go
|
62
|
+
unless collection.blank?
|
63
|
+
for coll_ in collection
|
64
|
+
ObjectRole.create!( :klazz_name => object, :object_reference => coll_.id, :user_id => user_id, :role => "owner" )
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
ret
|
73
|
+
end
|
74
|
+
|
75
|
+
############################################################################
|
76
|
+
# remove_all_unused_authorization_objects
|
77
|
+
#############################################################################
|
78
|
+
# Remove all stale (non-object) authorization objects.
|
79
|
+
############################################################################
|
80
|
+
|
81
|
+
def self.remove_all_unused_authorization_objects options = {}
|
82
|
+
# no options
|
83
|
+
# ___
|
84
|
+
# Let's iterate all ObjectRoles
|
85
|
+
for object_role in ObjectRole.all
|
86
|
+
object_role.destroy if object_role.object.nil?
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
protected
|
91
|
+
|
92
|
+
############################################################################
|
93
|
+
# gather_direct_descendants_of_activerecord_base
|
94
|
+
############################################################################
|
95
|
+
|
96
|
+
def self.gather_direct_descendants_of_activerecord_base
|
97
|
+
ret = []
|
98
|
+
|
99
|
+
classes = ActiveRecord::Base.send(:subclasses)
|
100
|
+
|
101
|
+
# Go through it twice to determine direct descendants
|
102
|
+
for klazz in classes
|
103
|
+
# Push the class name, not the class object itself.
|
104
|
+
klazz_name = klazz.to_s
|
105
|
+
# May never use the ObjectRole or User class.
|
106
|
+
ret.push klazz_name if !klazz_name.eql?("ObjectRole") && !klazz_name.eql?(@@user_class_name)
|
107
|
+
end
|
108
|
+
|
109
|
+
ret
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
# Use this file to add a couple of helper methods to ApplicationController
|
3
|
+
|
4
|
+
################################################################################
|
5
|
+
# Addendum to ApplicationController
|
6
|
+
################################################################################
|
7
|
+
# These methods are heavily dependent on InheritedResources, more specifically the 'resource' method.
|
8
|
+
#
|
9
|
+
# Otherwise there would be no predefined way of peeking into a controller's resource object.
|
10
|
+
################################################################################
|
11
|
+
|
12
|
+
# for user_not_authorized
|
13
|
+
require 'authorizer/exceptions'
|
14
|
+
|
15
|
+
class ApplicationController < ActionController::Base
|
16
|
+
helper_method :own_created_object, :authorize
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
##############################################################################
|
21
|
+
# authorizer
|
22
|
+
##############################################################################
|
23
|
+
|
24
|
+
def own_created_object
|
25
|
+
ret = true # always return true otherwise the filter chain would be blocked.
|
26
|
+
|
27
|
+
begin
|
28
|
+
r = resource
|
29
|
+
rescue
|
30
|
+
end
|
31
|
+
|
32
|
+
unless r.nil?
|
33
|
+
# only if this objet was successfully created will we do this.
|
34
|
+
unless r.new_record?
|
35
|
+
Authorizer::Base.authorize_user( :object => r )
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
ret
|
40
|
+
end
|
41
|
+
|
42
|
+
def authorize
|
43
|
+
ret = false # return false by default, effectively using a whitelist method.
|
44
|
+
|
45
|
+
begin
|
46
|
+
r = resource
|
47
|
+
rescue
|
48
|
+
end
|
49
|
+
|
50
|
+
unless r.nil?
|
51
|
+
auth = Authorizer::Base.user_is_authorized?( :object => r )
|
52
|
+
|
53
|
+
if auth.eql?(false)
|
54
|
+
raise Authorizer::UserNotAuthorized.new("You are not authorized to access this resource.")
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
ret
|
59
|
+
end
|
60
|
+
|
61
|
+
##############################################################################
|
62
|
+
# end authorizer
|
63
|
+
##############################################################################
|
64
|
+
end
|
65
|
+
|
@@ -0,0 +1,320 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
################################################################################
|
3
|
+
# Authorizer
|
4
|
+
#
|
5
|
+
# Authorizer is a Ruby class that authorizes using the ObjectRole record.
|
6
|
+
################################################################################
|
7
|
+
|
8
|
+
# for user_not_authorized
|
9
|
+
require 'authorizer/exceptions'
|
10
|
+
require 'authorizer/application_controller'
|
11
|
+
|
12
|
+
module Authorizer
|
13
|
+
class Base < ApplicationController
|
14
|
+
############################################################################
|
15
|
+
# authorize_user
|
16
|
+
#
|
17
|
+
# If no user is specified, authorizes the current user.
|
18
|
+
# If no role is specified, "owner" is used as role.
|
19
|
+
############################################################################
|
20
|
+
|
21
|
+
def self.authorize_user(options)
|
22
|
+
OptionsChecker.check(options, [ :object ])
|
23
|
+
|
24
|
+
ret = false
|
25
|
+
|
26
|
+
object = options[:object]
|
27
|
+
role = options[:role] || "owner"
|
28
|
+
user = options[:user] || get_current_user
|
29
|
+
|
30
|
+
return false if basic_check_fails?(options)
|
31
|
+
|
32
|
+
check_user(user)
|
33
|
+
# Checks done. Let's go.
|
34
|
+
|
35
|
+
or_ = find_object_role(object, user)
|
36
|
+
|
37
|
+
# This time, we want it to be nil.
|
38
|
+
if or_.nil? && !user.nil?
|
39
|
+
klazz_name = object.class.to_s
|
40
|
+
object_reference = object.id
|
41
|
+
|
42
|
+
ObjectRole.create!( :klazz_name => klazz_name, :object_reference => object_reference, :user => user, :role => role )
|
43
|
+
Rails.logger.debug("Authorizer: created authorization on #{object} for current_user with ID #{user.id} witih role #{role}")
|
44
|
+
ret = true
|
45
|
+
end
|
46
|
+
|
47
|
+
ret
|
48
|
+
end
|
49
|
+
|
50
|
+
############################################################################
|
51
|
+
# user_is_authorized?
|
52
|
+
#
|
53
|
+
# If no user is specified, current_user is used.
|
54
|
+
############################################################################
|
55
|
+
|
56
|
+
def self.user_is_authorized? options
|
57
|
+
OptionsChecker.check(options, [ :object ])
|
58
|
+
|
59
|
+
ret = false
|
60
|
+
|
61
|
+
check = basic_check_fails?(options)
|
62
|
+
return ret if check
|
63
|
+
|
64
|
+
object = options[:object]
|
65
|
+
user = options[:user] || get_current_user
|
66
|
+
|
67
|
+
# Checks
|
68
|
+
check_user(user)
|
69
|
+
# Checks done. Let's go.
|
70
|
+
|
71
|
+
or_ = find_object_role(object, user)
|
72
|
+
|
73
|
+
# Congratulations, you've been Authorized.
|
74
|
+
unless or_.nil?
|
75
|
+
ret = true
|
76
|
+
end
|
77
|
+
|
78
|
+
if ret
|
79
|
+
Rails.logger.debug("Authorizer: authorized current_user with ID #{user.id} to access #{or_.description} because of role #{or_.role}") unless user.nil? || or_.nil?
|
80
|
+
else
|
81
|
+
Rails.logger.debug("Authorizer: authorization failed for current_user with ID #{user.id} to access #{object.to_s}") unless user.nil? || object.nil?
|
82
|
+
end
|
83
|
+
|
84
|
+
ret
|
85
|
+
end
|
86
|
+
|
87
|
+
############################################################################
|
88
|
+
# remove_authorization
|
89
|
+
############################################################################
|
90
|
+
# Remove authorization a user has on a certain object.
|
91
|
+
############################################################################
|
92
|
+
|
93
|
+
def self.remove_authorization options = {}
|
94
|
+
OptionsChecker.check(options, [ :object ])
|
95
|
+
|
96
|
+
ret = false
|
97
|
+
|
98
|
+
return ret if basic_check_fails?(options)
|
99
|
+
|
100
|
+
object = options[:object]
|
101
|
+
user = options[:user] || get_current_user
|
102
|
+
|
103
|
+
# Check
|
104
|
+
check_user(user)
|
105
|
+
# Checks done. Let's go.
|
106
|
+
|
107
|
+
or_ = find_object_role(object, user)
|
108
|
+
|
109
|
+
unless or_.nil?
|
110
|
+
Rails.logger.debug("Authorizer: removed authorization for user ID #{user.id} on #{or_.description}")
|
111
|
+
|
112
|
+
or_.destroy
|
113
|
+
|
114
|
+
ret = true
|
115
|
+
end
|
116
|
+
|
117
|
+
ret
|
118
|
+
end
|
119
|
+
|
120
|
+
############################################################################
|
121
|
+
# find
|
122
|
+
############################################################################
|
123
|
+
# Out of the collection of all Posts, return the subset that belongs to the current user.
|
124
|
+
# External method that maps to the internal_find which is the generic find method.
|
125
|
+
#
|
126
|
+
# Arguments:
|
127
|
+
# - class_name: which class to use, e.g. "Post"
|
128
|
+
# - what: will be passed on to the ActiveRecord find function (e.g. Post.find(what))
|
129
|
+
# - find_options: will also be passed on (e.g. Post.find(what, find_options))
|
130
|
+
# - authorizer_options: options for authorizer, e.g. { :user => @user }
|
131
|
+
############################################################################
|
132
|
+
|
133
|
+
def self.find(class_name, what, find_options = {}, authorizer_options = {})
|
134
|
+
options = { :class_name => class_name, :what => what, :find_options => find_options }
|
135
|
+
my_options = authorizer_options.merge(options) # options overrides user-specified options.
|
136
|
+
|
137
|
+
internal_find(my_options)
|
138
|
+
end
|
139
|
+
|
140
|
+
############################################################################
|
141
|
+
# is_authorized?
|
142
|
+
#
|
143
|
+
# Checks if the corresponding role.eql?("owner")
|
144
|
+
############################################################################
|
145
|
+
|
146
|
+
def self.is_authorized? object
|
147
|
+
user_is_authorized? :object => object
|
148
|
+
end
|
149
|
+
|
150
|
+
############################################################################
|
151
|
+
# create_ownership
|
152
|
+
#
|
153
|
+
# ObjectRole.create!( :klazz_name => object.class.to_s, :object_reference => object.id, :user => current_user, :role => "owner" )
|
154
|
+
############################################################################
|
155
|
+
|
156
|
+
def self.create_ownership object
|
157
|
+
ret = false
|
158
|
+
|
159
|
+
return ret if basic_check_fails?(object)
|
160
|
+
|
161
|
+
ret = authorize_user( :object => object )
|
162
|
+
|
163
|
+
ret
|
164
|
+
end
|
165
|
+
|
166
|
+
protected
|
167
|
+
|
168
|
+
############################################################################
|
169
|
+
# get_current_user
|
170
|
+
############################################################################
|
171
|
+
# helper method to not be dependent on the current_user method
|
172
|
+
############################################################################
|
173
|
+
|
174
|
+
def self.get_current_user
|
175
|
+
ret = nil
|
176
|
+
|
177
|
+
begin
|
178
|
+
session = UserSession.find
|
179
|
+
ret = session.user
|
180
|
+
rescue
|
181
|
+
end
|
182
|
+
|
183
|
+
ret
|
184
|
+
end
|
185
|
+
|
186
|
+
############################################################################
|
187
|
+
# internal_find
|
188
|
+
############################################################################
|
189
|
+
# Extract some info from ObjectRole objects and then pass the info through
|
190
|
+
# to the ActiveRecord finder.
|
191
|
+
############################################################################
|
192
|
+
|
193
|
+
def self.internal_find(options = {})
|
194
|
+
# Options
|
195
|
+
OptionsChecker.check(options, [ :what, :class_name ])
|
196
|
+
|
197
|
+
# assign
|
198
|
+
class_name = options[:class_name]
|
199
|
+
what = options[:what]
|
200
|
+
find_options = options[:find_options] || {}
|
201
|
+
user = options[:user] || get_current_user
|
202
|
+
|
203
|
+
# We don't do the what checks anymore, ActiveRecord::Base.find does that for us now.
|
204
|
+
#what_checks = [ :all, :first, :last, :id ]
|
205
|
+
#raise "What must be one of #{what_checks.inspect}" unless what_checks.include?(what)
|
206
|
+
|
207
|
+
# Check userrrrrrrrrrrr --- =====================- ---= ===-=- *&((28 @((8
|
208
|
+
check_user(user)
|
209
|
+
# rrrr
|
210
|
+
ret = nil
|
211
|
+
# Checks
|
212
|
+
# Checks done. Let's go.
|
213
|
+
# Get the real klazz
|
214
|
+
klazz = nil
|
215
|
+
# Check it
|
216
|
+
begin
|
217
|
+
klazz = eval(class_name)
|
218
|
+
rescue
|
219
|
+
end
|
220
|
+
# oooo ooo ooo ___ --- === __- --_- ++_+_ =--- +- =+=-=- =-= <--- ice beam!
|
221
|
+
unless klazz.nil?
|
222
|
+
# now we know klazz really exists.
|
223
|
+
# let's find the object_role objects that match the user and klaz.
|
224
|
+
# Get the object_role objects
|
225
|
+
object_roles_conditions = { :klazz_name => class_name, :user_id => user.id }
|
226
|
+
object_roles = ObjectRole.find(:all, :conditions => object_roles_conditions )
|
227
|
+
# Get a list of IDs. These are objects that are owned by the current_user
|
228
|
+
object_role_ids = object_roles.collect { |or_| or_.object_reference } # [ 1, 1, 1, 1 ]
|
229
|
+
# Make it at least an array if object_role_ids returns nil
|
230
|
+
object_role_ids ||= []
|
231
|
+
# Try to emulate find as good as we can
|
232
|
+
# so don't skip this, try to always pass it on.
|
233
|
+
unless object_roles.nil?
|
234
|
+
# Prepare find_options
|
235
|
+
leading_find_options = {} # insert conventions here if needed
|
236
|
+
my_find_options = find_options.merge(leading_find_options)
|
237
|
+
# If the user passed an Array we should filter it with the list of available (authorized) objects.
|
238
|
+
#
|
239
|
+
# http://www.ruby-doc.org/core/classes/Array.html
|
240
|
+
# &
|
241
|
+
# Set Intersection—Returns a new array containing elements common to the two arrays, with no duplicates.
|
242
|
+
safe_what = what
|
243
|
+
if what.is_a?(Array)
|
244
|
+
safe_what = what & object_role_ids
|
245
|
+
end
|
246
|
+
# The big show. Let's call out F I N D !!!!!!
|
247
|
+
# INF FINFD FIWI FFIND IF FIND FIND FIND FIND FIND FIND FIND FIND
|
248
|
+
# FIND FIND FIND FIND FIND FIND FIND FIND FIND FIND FIND FIND FIND FIND
|
249
|
+
# FIND FIND FIND FIND FIND FIND FIND FIND FIND FIND FIND FIND FIND FIND
|
250
|
+
# FIND FIND FIND FIND FIND FIND FIND FIND FIND FIND FIND FIND FIND FIND
|
251
|
+
# FIND FIND FIND FIND FIND FIND FIND FIND FIND FIND FIND FIND FIND FIND
|
252
|
+
# FIND FIND FIND FIND FIND FIND FIND FIND FIND FIND FIND FIND FIND FIND
|
253
|
+
# FIND FIND FIND FIND FIND FIND FIND FIND FIND FIND FIND FIND FIND FIND
|
254
|
+
# FIND FIND FIND FIND FIND FIND FIND FIND FIND FIND FIND FIND FIND FIND
|
255
|
+
# FIND FIND FIND FIND FIND FIND FIND FIND FIND FIND FIND FIND FIND FIND
|
256
|
+
# FIND FIND FIND FIND FIND FIND FIND FIND FIND FIND FIND FIND FIND FIND
|
257
|
+
# FIND FIND FIND FIND FIND FIND FIND FIND FIND FIND FIND FIND FIND FIND
|
258
|
+
if safe_what.eql?(:all)
|
259
|
+
ret = klazz.find(:all, my_find_options)
|
260
|
+
elsif safe_what.eql?(:first)
|
261
|
+
ret = klazz.find(object_role_ids.first, my_find_options)
|
262
|
+
elsif safe_what.eql?(:last)
|
263
|
+
ret = klazz.find(object_role_ids.last, my_find_options)
|
264
|
+
else
|
265
|
+
ret = klazz.find(safe_what, my_find_options)
|
266
|
+
end
|
267
|
+
# SAFE WHAT???? SAFE WHAT???? SAFE WHAT???? SAFE WHAT???? SAFE WHAT????
|
268
|
+
# SAFE WHAT???? SAFE WHAT???? SAFE WHAT???? SAFE WHAT???? SAFE WHAT????
|
269
|
+
# SAFE WHAT???? SAFE WHAT???? SAFE WHAT???? SAFE WHAT???? SAFE WHAT????
|
270
|
+
# SAFE WHAT???? SAFE WHAT???? SAFE WHAT???? SAFE WHAT???? SAFE WHAT????
|
271
|
+
# SAFE WHAT???? SAFE WHAT???? SAFE WHAT???? SAFE WHAT???? SAFE WHAT????
|
272
|
+
# SAFE WHAT???? SAFE WHAT???? SAFE WHAT???? SAFE WHAT???? SAFE WHAT????
|
273
|
+
# SAFE WHAT???? SAFE WHAT???? SAFE WHAT???? SAFE WHAT???? SAFE WHAT????
|
274
|
+
# SAFE WHAT???? SAFE WHAT???? SAFE WHAT???? SAFE WHAT???? SAFE WHAT????
|
275
|
+
end
|
276
|
+
end
|
277
|
+
|
278
|
+
ret
|
279
|
+
end
|
280
|
+
|
281
|
+
def self.find_object_role(object, user)
|
282
|
+
return nil if object.nil? || user.nil?
|
283
|
+
|
284
|
+
# Check
|
285
|
+
check_user(user)
|
286
|
+
# Checks done. Let's go.
|
287
|
+
|
288
|
+
klazz_name = object.class.to_s
|
289
|
+
object_reference = object.id
|
290
|
+
|
291
|
+
unless user.nil?
|
292
|
+
or_ = ObjectRole.first( :conditions => { :klazz_name => klazz_name, :object_reference => object_reference, :user_id => user.id } )
|
293
|
+
end
|
294
|
+
|
295
|
+
or_
|
296
|
+
end
|
297
|
+
|
298
|
+
def self.basic_check_fails?(options)
|
299
|
+
ret = false
|
300
|
+
|
301
|
+
unless options[:object].nil?
|
302
|
+
if !options[:object].is_a?(ActiveRecord::Base) || options[:object].new_record?
|
303
|
+
raise "object must be subclass of ActiveRecord::Base and must also be saved."
|
304
|
+
end
|
305
|
+
end
|
306
|
+
|
307
|
+
ret
|
308
|
+
end
|
309
|
+
|
310
|
+
def self.check_user(user)
|
311
|
+
ret = true
|
312
|
+
|
313
|
+
raise "User cannot be nil" if user.nil?
|
314
|
+
raise "User must inherit from ActiveRecord::Base" unless user.is_a?(ActiveRecord::Base)
|
315
|
+
raise "User must be saved" if user.new_record?
|
316
|
+
|
317
|
+
ret
|
318
|
+
end
|
319
|
+
end
|
320
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
module Authorizer
|
3
|
+
# Observes users and deleted any associated ObjectRole objects when the user gets deleted.
|
4
|
+
class ObjectObserver < ActiveRecord::Observer
|
5
|
+
# Observe this.
|
6
|
+
observe ActiveRecord::Base
|
7
|
+
|
8
|
+
# W DONT DO DIZ
|
9
|
+
# let's use before_destroy instead of after_destroy. More chance it will still have an ID >:)))))))))) :') :DDDDDDDDDDDDDDDDDDDDDDD
|
10
|
+
# W DONT DO DIZ
|
11
|
+
def after_destroy(object)
|
12
|
+
return nil if object.is_a?(User) # Users are covered by the other observer class.
|
13
|
+
# Find all ObjectRole records that point to this object.
|
14
|
+
object_roles = ObjectRole.find_all_by_object(object)
|
15
|
+
# Walk through 'em
|
16
|
+
for object_role in object_roles
|
17
|
+
object_role.destroy
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
module Authorizer
|
3
|
+
# Observes users and deleted any associated ObjectRole objects when the user gets deleted.
|
4
|
+
class UserObserver < ActiveRecord::Observer
|
5
|
+
# Observe this.
|
6
|
+
observe :user
|
7
|
+
|
8
|
+
# W DONT DO DIZ
|
9
|
+
# let's use before_destroy instead of after_destroy. More chance it will still have an ID >:)))))))))) :') :DDDDDDDDDDDDDDDDDDDDDDD
|
10
|
+
# W DONT DO DIZ
|
11
|
+
def after_destroy(user)
|
12
|
+
# Default
|
13
|
+
object_roles = []
|
14
|
+
# Find all ObjectRole records that point to this user's ID.
|
15
|
+
begin
|
16
|
+
object_roles = ObjectRole.find_all_by_user_id(user.id)
|
17
|
+
rescue
|
18
|
+
end
|
19
|
+
# Walk through 'em
|
20
|
+
# Not executed if anything happens (array.size = 0)
|
21
|
+
for object_role in object_roles
|
22
|
+
object_role.destroy
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
data/rails/init.rb
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
################################################################################
|
3
|
+
# init.rb
|
4
|
+
#
|
5
|
+
# This file will load the Observers we need to prevent the database from becoming clogged with stale authorization objects.
|
6
|
+
################################################################################
|
7
|
+
|
8
|
+
config.after_initialize do
|
9
|
+
ActiveRecord::Base.observers << Authorizer::UserObserver
|
10
|
+
ActiveRecord::Base.observers << Authorizer::ObjectObserver
|
11
|
+
end
|
data/script/console
ADDED
@@ -0,0 +1,10 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# File: script/console
|
3
|
+
irb = RUBY_PLATFORM =~ /(:?mswin|mingw)/ ? 'irb.bat' : 'irb'
|
4
|
+
|
5
|
+
libs = " -r irb/completion"
|
6
|
+
# Perhaps use a console_lib to store any extra methods I may want available in the cosole
|
7
|
+
# libs << " -r #{File.dirname(__FILE__) + '/../lib/console_lib/console_logger.rb'}"
|
8
|
+
libs << " -r #{File.dirname(__FILE__) + '/../lib/authorizer.rb'}"
|
9
|
+
puts "Loading authorizer gem"
|
10
|
+
exec "#{irb} #{libs} --simple-prompt"
|
data/script/destroy
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
APP_ROOT = File.expand_path(File.join(File.dirname(__FILE__), '..'))
|
3
|
+
|
4
|
+
begin
|
5
|
+
require 'rubigen'
|
6
|
+
rescue LoadError
|
7
|
+
require 'rubygems'
|
8
|
+
require 'rubigen'
|
9
|
+
end
|
10
|
+
require 'rubigen/scripts/destroy'
|
11
|
+
|
12
|
+
ARGV.shift if ['--help', '-h'].include?(ARGV[0])
|
13
|
+
RubiGen::Base.use_component_sources! [:rubygems, :newgem, :newgem_theme, :test_unit]
|
14
|
+
RubiGen::Scripts::Destroy.new.run(ARGV)
|
data/script/generate
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
APP_ROOT = File.expand_path(File.join(File.dirname(__FILE__), '..'))
|
3
|
+
|
4
|
+
begin
|
5
|
+
require 'rubigen'
|
6
|
+
rescue LoadError
|
7
|
+
require 'rubygems'
|
8
|
+
require 'rubigen'
|
9
|
+
end
|
10
|
+
require 'rubigen/scripts/generate'
|
11
|
+
|
12
|
+
ARGV.shift if ['--help', '-h'].include?(ARGV[0])
|
13
|
+
RubiGen::Base.use_component_sources! [:rubygems, :newgem, :newgem_theme, :test_unit]
|
14
|
+
RubiGen::Scripts::Generate.new.run(ARGV)
|
metadata
ADDED
@@ -0,0 +1,149 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: authorizer
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 29
|
5
|
+
prerelease: false
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 0
|
9
|
+
- 1
|
10
|
+
version: 0.0.1
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- CmdJohnson
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2011-07-26 00:00:00 +02:00
|
19
|
+
default_executable:
|
20
|
+
dependencies:
|
21
|
+
- !ruby/object:Gem::Dependency
|
22
|
+
name: options_checker
|
23
|
+
prerelease: false
|
24
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ">="
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
hash: 29
|
30
|
+
segments:
|
31
|
+
- 0
|
32
|
+
- 0
|
33
|
+
- 1
|
34
|
+
version: 0.0.1
|
35
|
+
type: :runtime
|
36
|
+
version_requirements: *id001
|
37
|
+
- !ruby/object:Gem::Dependency
|
38
|
+
name: hoe
|
39
|
+
prerelease: false
|
40
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ">="
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
hash: 47
|
46
|
+
segments:
|
47
|
+
- 2
|
48
|
+
- 8
|
49
|
+
- 0
|
50
|
+
version: 2.8.0
|
51
|
+
type: :development
|
52
|
+
version_requirements: *id002
|
53
|
+
description: |-
|
54
|
+
Authorizer is a gem for Ruby (in conjunction with Rails 2.3) that does authorization for you on a per-object basis. What makes this gem different from e.g. declarative_authorization and cancan is they define one role for the entire application. With Authorizer, you define roles for different users on every Rails object.
|
55
|
+
|
56
|
+
Let's use a Dropbox analogy.
|
57
|
+
|
58
|
+
With Dropbox, you can choose which folder you want to share. For instance:
|
59
|
+
|
60
|
+
Al has a home folder with these subfolders in it:
|
61
|
+
- Music (shared with Bob)
|
62
|
+
- Pictures (shared with Casper and Bob)
|
63
|
+
- News (shared with no-one)
|
64
|
+
|
65
|
+
This causes Al to have all 3 folders in his Dropbox. Bob has 2 and Casper has only 1 folder called Pictures.
|
66
|
+
|
67
|
+
In other words, a user has access to a subset of the entire collection of folders. Bob has access to 2 of Al's folders, namely Music and Pictures. But he doesn't even see the News folder, nor can he download files from it.
|
68
|
+
|
69
|
+
Bob's access to the two folders is both read and write, so let's call that role "admin". Al is the owner of all 3 folders and has a role called "owner". This leads to the following Roles table:
|
70
|
+
|
71
|
+
folder_name user_name role
|
72
|
+
Music Al owner
|
73
|
+
Bob admin
|
74
|
+
Pictures Al owner
|
75
|
+
Bob admin
|
76
|
+
Casper admin
|
77
|
+
News Al owner
|
78
|
+
|
79
|
+
Now if we would allow Bob to also access the News folder but only read from it, we could add the role called "reader" to the table:
|
80
|
+
|
81
|
+
folder_name user_name role
|
82
|
+
News Bob reader
|
83
|
+
|
84
|
+
This is exactly what Authorizer does for your Rails application.
|
85
|
+
email:
|
86
|
+
- commanderjohnson@gmail.com
|
87
|
+
executables: []
|
88
|
+
|
89
|
+
extensions: []
|
90
|
+
|
91
|
+
extra_rdoc_files:
|
92
|
+
- History.txt
|
93
|
+
- Manifest.txt
|
94
|
+
- PostInstall.txt
|
95
|
+
files:
|
96
|
+
- History.txt
|
97
|
+
- Manifest.txt
|
98
|
+
- PostInstall.txt
|
99
|
+
- README.rdoc
|
100
|
+
- Rakefile
|
101
|
+
- lib/authorizer.rb
|
102
|
+
- script/console
|
103
|
+
- script/destroy
|
104
|
+
- script/generate
|
105
|
+
- lib/authorizer/admin.rb
|
106
|
+
- lib/authorizer/application_controller.rb
|
107
|
+
- lib/authorizer/base.rb
|
108
|
+
- lib/authorizer/exceptions.rb
|
109
|
+
- lib/authorizer/object_observer.rb
|
110
|
+
- lib/authorizer/user_observer.rb
|
111
|
+
- app/models/object_role.rb
|
112
|
+
- rails/init.rb
|
113
|
+
has_rdoc: true
|
114
|
+
homepage: https://github.com/cmdjohnson/authorizer
|
115
|
+
licenses: []
|
116
|
+
|
117
|
+
post_install_message: PostInstall.txt
|
118
|
+
rdoc_options:
|
119
|
+
- --main
|
120
|
+
- README.rdoc
|
121
|
+
require_paths:
|
122
|
+
- lib
|
123
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
124
|
+
none: false
|
125
|
+
requirements:
|
126
|
+
- - ">="
|
127
|
+
- !ruby/object:Gem::Version
|
128
|
+
hash: 3
|
129
|
+
segments:
|
130
|
+
- 0
|
131
|
+
version: "0"
|
132
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
133
|
+
none: false
|
134
|
+
requirements:
|
135
|
+
- - ">="
|
136
|
+
- !ruby/object:Gem::Version
|
137
|
+
hash: 3
|
138
|
+
segments:
|
139
|
+
- 0
|
140
|
+
version: "0"
|
141
|
+
requirements: []
|
142
|
+
|
143
|
+
rubyforge_project: authorizer
|
144
|
+
rubygems_version: 1.3.7
|
145
|
+
signing_key:
|
146
|
+
specification_version: 3
|
147
|
+
summary: Authorizer is a gem for Ruby (in conjunction with Rails 2.3) that does authorization for you on a per-object basis
|
148
|
+
test_files: []
|
149
|
+
|