cjbottaro-aegis 1.3.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +3 -0
- data/MIT-LICENSE +20 -0
- data/README.rdoc +182 -0
- data/Rakefile +37 -0
- data/VERSION +1 -0
- data/aegis.gemspec +117 -0
- data/lib/aegis.rb +11 -0
- data/lib/aegis/constants.rb +6 -0
- data/lib/aegis/has_role.rb +156 -0
- data/lib/aegis/meta_class.rb +13 -0
- data/lib/aegis/normalization.rb +26 -0
- data/lib/aegis/permission_error.rb +5 -0
- data/lib/aegis/permission_evaluator.rb +41 -0
- data/lib/aegis/permissions.rb +108 -0
- data/lib/aegis/role.rb +57 -0
- data/lib/aegis/role_assignments.rb +10 -0
- data/lib/rails/active_record.rb +5 -0
- data/test/app_root/app/controllers/application_controller.rb +2 -0
- data/test/app_root/app/models/account.rb +3 -0
- data/test/app_root/app/models/forum.rb +8 -0
- data/test/app_root/app/models/permissions.rb +70 -0
- data/test/app_root/app/models/post.rb +7 -0
- data/test/app_root/app/models/soldier.rb +5 -0
- data/test/app_root/app/models/user.rb +7 -0
- data/test/app_root/config/boot.rb +114 -0
- data/test/app_root/config/database.yml +21 -0
- data/test/app_root/config/environment.rb +14 -0
- data/test/app_root/config/environments/in_memory.rb +0 -0
- data/test/app_root/config/environments/mysql.rb +0 -0
- data/test/app_root/config/environments/postgresql.rb +0 -0
- data/test/app_root/config/environments/sqlite.rb +0 -0
- data/test/app_root/config/environments/sqlite3.rb +0 -0
- data/test/app_root/config/routes.rb +4 -0
- data/test/app_root/db/migrate/20090408115228_create_users.rb +15 -0
- data/test/app_root/db/migrate/20090429075648_create_soldiers.rb +16 -0
- data/test/app_root/db/migrate/20090903234709_create_role_assignments.rb +16 -0
- data/test/app_root/db/migrate/20090903234759_create_accounts.rb +11 -0
- data/test/app_root/db/migrate/20090903234821_create_forums.rb +12 -0
- data/test/app_root/db/migrate/20090903234828_create_posts.rb +12 -0
- data/test/app_root/lib/console_with_fixtures.rb +4 -0
- data/test/app_root/log/.gitignore +1 -0
- data/test/app_root/script/console +7 -0
- data/test/fixtures/accounts.yml +5 -0
- data/test/fixtures/forums.yml +11 -0
- data/test/fixtures/posts.yml +23 -0
- data/test/fixtures/role_assignments.yml +0 -0
- data/test/fixtures/users.yml +2 -0
- data/test/has_role_options_test.rb +33 -0
- data/test/has_role_test.rb +88 -0
- data/test/permissions_test.rb +117 -0
- data/test/test_helper.rb +44 -0
- data/test/validation_test.rb +49 -0
- metadata +130 -0
data/.gitignore
ADDED
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2009 Henning Koch
|
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.rdoc
ADDED
@@ -0,0 +1,182 @@
|
|
1
|
+
= Abstract
|
2
|
+
|
3
|
+
Aegis modified to support multiple roles per actors based on context and your data model hierarchy.
|
4
|
+
|
5
|
+
This file will only describe the new functionality I added to Aegis. See the original Aegis README.rdoc here: http://github.com/makandra/aegis/tree/master
|
6
|
+
|
7
|
+
== Description (aka longer abstract)
|
8
|
+
|
9
|
+
Aegis is a pretty simple permission system: a user belongs to a single role, and a role has many permissions associated with it.
|
10
|
+
|
11
|
+
My work requires a user to have multiple roles. Which one is chosen at any given time is based on a context. For example, we want <tt>user:123</tt> to have role <tt>:writer</tt> in <tt>forum:abc</tt>, but role <tt>:reader</tt> in <tt>forum:xyz</tt>.
|
12
|
+
|
13
|
+
Furthermore, we want to have a role hierarchy that follows our object model. If no role is defined for a user for a given context, then we ask if that context has a parent and if a role is defined there (again for that user). For example, does <tt>user:123</tt> have a role for <tt>forum:ijk</tt>? No. Does <tt>forum:ijk</tt> have a parent? Yes, it is <tt>account:1</tt>. Does <tt>user:123</tt> have a role in <tt>account:1</tt>? Yes, it is <tt>:writer</tt>.
|
14
|
+
|
15
|
+
Note that all previous Aegis functionality still exists and works (none of the existing unit tests were changed at all).
|
16
|
+
|
17
|
+
== Installation
|
18
|
+
|
19
|
+
Add the following to your <tt>Initializer.run</tt> block in your <tt>environment.rb</tt>:
|
20
|
+
config.gem 'cjbottaro-aegis', :lib => 'aegis', :source => 'http://gems.github.com'
|
21
|
+
Then do a
|
22
|
+
sudo rake gems:install
|
23
|
+
|
24
|
+
Alternatively, use
|
25
|
+
sudo gem sources -a http://gems.github.com
|
26
|
+
sudo gem install cjbottaro-aegis
|
27
|
+
|
28
|
+
== Database Migration
|
29
|
+
|
30
|
+
You need to create a table to keep track of which actors are assigned which roles for each context.
|
31
|
+
|
32
|
+
class CreateRoleAssignments < ActiveRecord::Migration
|
33
|
+
def self.up
|
34
|
+
create_table :role_assignments do |t|
|
35
|
+
t.string :actor_type, :null => false
|
36
|
+
t.integer :actor_id, :null => false
|
37
|
+
t.string :role_name, :null => false
|
38
|
+
t.string :context_type, :null => false
|
39
|
+
t.integer :context_id, :null => false
|
40
|
+
t.timestamps
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def self.down
|
45
|
+
drop_table :role_assignments
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
== Example
|
50
|
+
|
51
|
+
First, let's define a data model for our example. An Account has many Users and Forums. A Forum has many Posts.
|
52
|
+
|
53
|
+
Account
|
54
|
+
|-> User
|
55
|
+
|-> Forum
|
56
|
+
|-> Post
|
57
|
+
|
58
|
+
Now let's assign some roles for our user.
|
59
|
+
|
60
|
+
user = User.find_by_name("chris")
|
61
|
+
forum = Forum.find_by_name("coping with metrosexuality")
|
62
|
+
RoleAssignment.create! :actor => user,
|
63
|
+
:role_name => :admin,
|
64
|
+
:context => forum
|
65
|
+
post = forum.posts.find_by_title("acceptance")
|
66
|
+
RoleAssignment.create! :actor => user,
|
67
|
+
:role_name => :reader,
|
68
|
+
:context => post
|
69
|
+
|
70
|
+
Now we can query permissions.
|
71
|
+
|
72
|
+
user.may_create_posts_in?(forum) # user has role of :admin in this forum.
|
73
|
+
=> true
|
74
|
+
|
75
|
+
user.may_edit_content_for?(post) # user has role of :reader for this post.
|
76
|
+
=> false
|
77
|
+
|
78
|
+
post = forum.posts.find_by_title("denial")
|
79
|
+
user.may_edit_content_for?(post) # user does not have role for this post, so we look at that
|
80
|
+
# post's parent and see he is a an :admin there.
|
81
|
+
=> true
|
82
|
+
|
83
|
+
The syntax is very similar to normal Aegis except you can suffix permission queries with <tt>_in?</tt> or <tt>_for?</tt> and the last argument to the query is the context. So permission queries that accept parameters work like normal, you just pass in the context as the last parameter.
|
84
|
+
|
85
|
+
== How does Aegis know the object model hierarchy?
|
86
|
+
|
87
|
+
First way... you specify the hierarchy as a hash where the key is a class name and the value is an association name that invokes the parent.
|
88
|
+
|
89
|
+
class User < ActiveRecord::Base
|
90
|
+
has_role :hierarchy => { "Post" => "forum",
|
91
|
+
"Forum" => "account" }
|
92
|
+
end
|
93
|
+
|
94
|
+
Second way... you specify a single association name that will always return a model's parent.
|
95
|
+
|
96
|
+
class User < ActiveRecord::Base
|
97
|
+
has_role :hierarchy_accessor => "parent"
|
98
|
+
end
|
99
|
+
|
100
|
+
class Post < ActiveRecord::Base
|
101
|
+
belongs_to :forum
|
102
|
+
def parent
|
103
|
+
forum
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
class Forum < ActiveRecord::Base
|
108
|
+
belongs_to :account
|
109
|
+
def parent
|
110
|
+
account
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
== Forcing certain roles under specific conditions
|
115
|
+
|
116
|
+
My work had an additional requirement that if User#is_admin? is true, then that user should always have the role of <tt>:superuser</tt>, regardless of the context.
|
117
|
+
|
118
|
+
class User < ActiveRecord::Base
|
119
|
+
has_role :force_superuser_if => Proc.new{ |user| user.is_admin? }
|
120
|
+
end
|
121
|
+
|
122
|
+
user = User.first(:conditions => { :is_admin => true })
|
123
|
+
user.role_in(anything)
|
124
|
+
=> :superuser
|
125
|
+
|
126
|
+
user.may_edit_content_for?(anything)
|
127
|
+
=> true
|
128
|
+
|
129
|
+
The format of the key is special. It must be of the form <tt>force_<role_name>_if</tt>. There can be multiple keys of this form. The role is determined by the first one who's Proc returns true. They are evaluated in random order (thus is the nature of Hashes).
|
130
|
+
|
131
|
+
== Optimizations
|
132
|
+
|
133
|
+
We don't want to read the role assignments from the database every time we do a permission check. The solution to this is to read them once when the user logs in, store it in the session, then load from the session on each request.
|
134
|
+
|
135
|
+
In your login controller/action...
|
136
|
+
|
137
|
+
if successful_login?
|
138
|
+
session[:permissions] = current_user.role_assignments_hash
|
139
|
+
else
|
140
|
+
...
|
141
|
+
end
|
142
|
+
|
143
|
+
In your controller/action that checks if a user is logged in...
|
144
|
+
|
145
|
+
if logged_in?
|
146
|
+
current_user.role_assignments_override = session[:permissions]
|
147
|
+
else
|
148
|
+
...
|
149
|
+
end
|
150
|
+
|
151
|
+
This way the role assignments are only read when a user logs in.
|
152
|
+
|
153
|
+
== Associating permissions to contexts
|
154
|
+
|
155
|
+
Now that we have contexts, one might think we should be able to say only certain permissions make sense in given contexts.
|
156
|
+
|
157
|
+
Permissions < Aegis::Permissions
|
158
|
+
permission :delete_forum do
|
159
|
+
context "Account"
|
160
|
+
allow :admin
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
user = User.find(...)
|
165
|
+
post = Post.find(...)
|
166
|
+
|
167
|
+
user.may_delete_forum(post)
|
168
|
+
=> Exception, "permission :delete_forum does not make sense for Post"
|
169
|
+
|
170
|
+
Note that you can specify multiple contexts...
|
171
|
+
|
172
|
+
Permissions < Aegis::Permissions
|
173
|
+
permission :some_permission do
|
174
|
+
contexts "Classname1", "Classname2"
|
175
|
+
allow :admin
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
179
|
+
=== Author
|
180
|
+
|
181
|
+
Christopher J. Bottaro
|
182
|
+
http://github.com/cjbottaro
|
data/Rakefile
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
require 'rake'
|
2
|
+
require 'rake/testtask'
|
3
|
+
require 'rake/rdoctask'
|
4
|
+
|
5
|
+
desc 'Default: run unit tests.'
|
6
|
+
task :default => :test
|
7
|
+
|
8
|
+
desc 'Test the aegis gem.'
|
9
|
+
Rake::TestTask.new(:test) do |t|
|
10
|
+
t.libs << 'lib'
|
11
|
+
t.pattern = 'test/**/*_test.rb'
|
12
|
+
t.verbose = true
|
13
|
+
end
|
14
|
+
|
15
|
+
desc 'Generate documentation for the aegis plugin.'
|
16
|
+
Rake::RDocTask.new(:rdoc) do |rdoc|
|
17
|
+
rdoc.rdoc_dir = 'rdoc'
|
18
|
+
rdoc.title = 'Aegis'
|
19
|
+
rdoc.options << '--line-numbers' << '--inline-source'
|
20
|
+
rdoc.rdoc_files.include('README.rdoc')
|
21
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
22
|
+
end
|
23
|
+
|
24
|
+
begin
|
25
|
+
require 'jeweler'
|
26
|
+
Jeweler::Tasks.new do |gemspec|
|
27
|
+
gemspec.name = "aegis"
|
28
|
+
gemspec.summary = "Role-based permissions for your user models."
|
29
|
+
gemspec.email = "github@makandra.de"
|
30
|
+
gemspec.homepage = "http://github.com/makandra/aegis"
|
31
|
+
gemspec.description = "Aegis is a role-based permission system, where all users are given a role. It is possible to define detailed and complex permissions for each role very easily."
|
32
|
+
gemspec.authors = ["Henning Koch"]
|
33
|
+
end
|
34
|
+
rescue LoadError
|
35
|
+
puts "Jeweler not available. Install it with: sudo gem install technicalpickles-jeweler -s http://gems.github.com"
|
36
|
+
end
|
37
|
+
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
1.3.0
|
data/aegis.gemspec
ADDED
@@ -0,0 +1,117 @@
|
|
1
|
+
# Generated by jeweler
|
2
|
+
# DO NOT EDIT THIS FILE
|
3
|
+
# Instead, edit Jeweler::Tasks in Rakefile, and run `rake gemspec`
|
4
|
+
# -*- encoding: utf-8 -*-
|
5
|
+
|
6
|
+
Gem::Specification.new do |s|
|
7
|
+
s.name = %q{aegis}
|
8
|
+
s.version = "1.3.0"
|
9
|
+
|
10
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
|
+
s.authors = ["Henning Koch"]
|
12
|
+
s.date = %q{2009-09-07}
|
13
|
+
s.description = %q{Aegis is a role-based permission system, where all users are given a role. It is possible to define detailed and complex permissions for each role very easily.}
|
14
|
+
s.email = %q{github@makandra.de}
|
15
|
+
s.extra_rdoc_files = [
|
16
|
+
"README.rdoc"
|
17
|
+
]
|
18
|
+
s.files = [
|
19
|
+
".gitignore",
|
20
|
+
"MIT-LICENSE",
|
21
|
+
"README.rdoc",
|
22
|
+
"Rakefile",
|
23
|
+
"VERSION",
|
24
|
+
"aegis.gemspec",
|
25
|
+
"lib/aegis.rb",
|
26
|
+
"lib/aegis/constants.rb",
|
27
|
+
"lib/aegis/has_role.rb",
|
28
|
+
"lib/aegis/meta_class.rb",
|
29
|
+
"lib/aegis/normalization.rb",
|
30
|
+
"lib/aegis/permission_error.rb",
|
31
|
+
"lib/aegis/permission_evaluator.rb",
|
32
|
+
"lib/aegis/permissions.rb",
|
33
|
+
"lib/aegis/role.rb",
|
34
|
+
"lib/aegis/role_assignments.rb",
|
35
|
+
"lib/rails/active_record.rb",
|
36
|
+
"test/app_root/app/controllers/application_controller.rb",
|
37
|
+
"test/app_root/app/models/account.rb",
|
38
|
+
"test/app_root/app/models/forum.rb",
|
39
|
+
"test/app_root/app/models/permissions.rb",
|
40
|
+
"test/app_root/app/models/post.rb",
|
41
|
+
"test/app_root/app/models/soldier.rb",
|
42
|
+
"test/app_root/app/models/user.rb",
|
43
|
+
"test/app_root/config/boot.rb",
|
44
|
+
"test/app_root/config/database.yml",
|
45
|
+
"test/app_root/config/environment.rb",
|
46
|
+
"test/app_root/config/environments/in_memory.rb",
|
47
|
+
"test/app_root/config/environments/mysql.rb",
|
48
|
+
"test/app_root/config/environments/postgresql.rb",
|
49
|
+
"test/app_root/config/environments/sqlite.rb",
|
50
|
+
"test/app_root/config/environments/sqlite3.rb",
|
51
|
+
"test/app_root/config/routes.rb",
|
52
|
+
"test/app_root/db/migrate/20090408115228_create_users.rb",
|
53
|
+
"test/app_root/db/migrate/20090429075648_create_soldiers.rb",
|
54
|
+
"test/app_root/db/migrate/20090903234709_create_role_assignments.rb",
|
55
|
+
"test/app_root/db/migrate/20090903234759_create_accounts.rb",
|
56
|
+
"test/app_root/db/migrate/20090903234821_create_forums.rb",
|
57
|
+
"test/app_root/db/migrate/20090903234828_create_posts.rb",
|
58
|
+
"test/app_root/lib/console_with_fixtures.rb",
|
59
|
+
"test/app_root/log/.gitignore",
|
60
|
+
"test/app_root/script/console",
|
61
|
+
"test/fixtures/accounts.yml",
|
62
|
+
"test/fixtures/forums.yml",
|
63
|
+
"test/fixtures/posts.yml",
|
64
|
+
"test/fixtures/role_assignments.yml",
|
65
|
+
"test/fixtures/users.yml",
|
66
|
+
"test/has_role_options_test.rb",
|
67
|
+
"test/has_role_test.rb",
|
68
|
+
"test/permissions_test.rb",
|
69
|
+
"test/test_helper.rb",
|
70
|
+
"test/validation_test.rb"
|
71
|
+
]
|
72
|
+
s.has_rdoc = true
|
73
|
+
s.homepage = %q{http://github.com/makandra/aegis}
|
74
|
+
s.rdoc_options = ["--charset=UTF-8"]
|
75
|
+
s.require_paths = ["lib"]
|
76
|
+
s.rubygems_version = %q{1.3.2}
|
77
|
+
s.summary = %q{Role-based permissions for your user models.}
|
78
|
+
s.test_files = [
|
79
|
+
"test/app_root/app/controllers/application_controller.rb",
|
80
|
+
"test/app_root/app/models/account.rb",
|
81
|
+
"test/app_root/app/models/forum.rb",
|
82
|
+
"test/app_root/app/models/permissions.rb",
|
83
|
+
"test/app_root/app/models/post.rb",
|
84
|
+
"test/app_root/app/models/soldier.rb",
|
85
|
+
"test/app_root/app/models/user.rb",
|
86
|
+
"test/app_root/config/boot.rb",
|
87
|
+
"test/app_root/config/environment.rb",
|
88
|
+
"test/app_root/config/environments/in_memory.rb",
|
89
|
+
"test/app_root/config/environments/mysql.rb",
|
90
|
+
"test/app_root/config/environments/postgresql.rb",
|
91
|
+
"test/app_root/config/environments/sqlite.rb",
|
92
|
+
"test/app_root/config/environments/sqlite3.rb",
|
93
|
+
"test/app_root/config/routes.rb",
|
94
|
+
"test/app_root/db/migrate/20090408115228_create_users.rb",
|
95
|
+
"test/app_root/db/migrate/20090429075648_create_soldiers.rb",
|
96
|
+
"test/app_root/db/migrate/20090903234709_create_role_assignments.rb",
|
97
|
+
"test/app_root/db/migrate/20090903234759_create_accounts.rb",
|
98
|
+
"test/app_root/db/migrate/20090903234821_create_forums.rb",
|
99
|
+
"test/app_root/db/migrate/20090903234828_create_posts.rb",
|
100
|
+
"test/app_root/lib/console_with_fixtures.rb",
|
101
|
+
"test/has_role_options_test.rb",
|
102
|
+
"test/has_role_test.rb",
|
103
|
+
"test/permissions_test.rb",
|
104
|
+
"test/test_helper.rb",
|
105
|
+
"test/validation_test.rb"
|
106
|
+
]
|
107
|
+
|
108
|
+
if s.respond_to? :specification_version then
|
109
|
+
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
110
|
+
s.specification_version = 3
|
111
|
+
|
112
|
+
if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
|
113
|
+
else
|
114
|
+
end
|
115
|
+
else
|
116
|
+
end
|
117
|
+
end
|
data/lib/aegis.rb
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
# Include hook code here
|
2
|
+
require 'aegis/meta_class'
|
3
|
+
require 'aegis/constants'
|
4
|
+
require 'aegis/normalization'
|
5
|
+
require 'aegis/permission_error'
|
6
|
+
require 'aegis/role'
|
7
|
+
require 'aegis/role_assignments'
|
8
|
+
require 'aegis/permissions'
|
9
|
+
require 'aegis/has_role'
|
10
|
+
require 'rails/active_record'
|
11
|
+
|
@@ -0,0 +1,156 @@
|
|
1
|
+
module Aegis
|
2
|
+
module HasRole
|
3
|
+
|
4
|
+
def self.extended(mod)
|
5
|
+
mod.send(:extend, ClassMethods)
|
6
|
+
end
|
7
|
+
|
8
|
+
module ClassMethods
|
9
|
+
|
10
|
+
def validates_role_name(options = {})
|
11
|
+
validates_each :role_name do |record, attr, value|
|
12
|
+
options[:message] ||= ActiveRecord::Errors.default_error_messages[:inclusion]
|
13
|
+
role = ::Permissions.find_role_by_name(value)
|
14
|
+
record.errors.add attr, options[:message] if role.nil?
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
alias_method :validates_role, :validates_role_name
|
19
|
+
|
20
|
+
def has_role(options = {})
|
21
|
+
|
22
|
+
if options[:name_accessor]
|
23
|
+
options[:name_reader] = "#{options[:name_accessor]}"
|
24
|
+
options[:name_writer] = "#{options[:name_accessor]}="
|
25
|
+
options.delete(:name_accessor)
|
26
|
+
end
|
27
|
+
|
28
|
+
@aegis_role_name_reader = (options[:name_reader] || "role_name").to_sym
|
29
|
+
@aegis_role_name_writer = (options[:name_writer] || "role_name=").to_sym
|
30
|
+
@aegis_role_hierarchy = (options[:hierarchy] || {})
|
31
|
+
@aegis_role_hierarchy_accessor = (options[:hierarchy_accessor])
|
32
|
+
@aegis_forced_roles = options.inject({}) do |memo, (k, v)|
|
33
|
+
(m = k.to_s.match(/force_(.+)_if/)) and (memo[v] = m[1])
|
34
|
+
memo
|
35
|
+
end
|
36
|
+
|
37
|
+
meta_eval do
|
38
|
+
attr_reader :aegis_role_name_reader
|
39
|
+
attr_reader :aegis_role_name_writer
|
40
|
+
attr_reader :aegis_role_hierarchy
|
41
|
+
attr_reader :aegis_role_hierarchy_accessor
|
42
|
+
attr_reader :aegis_forced_roles
|
43
|
+
end
|
44
|
+
|
45
|
+
has_many :role_assignments, :class_name => "Aegis::RoleAssignment",
|
46
|
+
:as => "actor"
|
47
|
+
|
48
|
+
attr_writer :role_assignments_override
|
49
|
+
|
50
|
+
include(InstanceMethods)
|
51
|
+
|
52
|
+
alias_method_chain :method_missing, :aegis_permissions
|
53
|
+
end
|
54
|
+
|
55
|
+
alias_method :has_roles, :has_role
|
56
|
+
|
57
|
+
end # module ClassMethods
|
58
|
+
|
59
|
+
module InstanceMethods
|
60
|
+
|
61
|
+
def aegis_role_name_reader
|
62
|
+
self.class.aegis_role_name_reader
|
63
|
+
end
|
64
|
+
|
65
|
+
def aegis_role_name_writer
|
66
|
+
self.class.aegis_role_name_writer
|
67
|
+
end
|
68
|
+
|
69
|
+
def aegis_role_hierarchy
|
70
|
+
self.class.aegis_role_hierarchy
|
71
|
+
end
|
72
|
+
|
73
|
+
def aegis_role_hierarchy_accessor
|
74
|
+
self.class.aegis_role_hierarchy_accessor
|
75
|
+
end
|
76
|
+
|
77
|
+
def aegis_forced_roles
|
78
|
+
self.class.aegis_forced_roles
|
79
|
+
end
|
80
|
+
|
81
|
+
def aegis_role_name
|
82
|
+
send(aegis_role_name_reader)
|
83
|
+
end
|
84
|
+
|
85
|
+
def aegis_role_name=(value)
|
86
|
+
send(aegis_role_name_writer, value)
|
87
|
+
end
|
88
|
+
|
89
|
+
def role
|
90
|
+
(forced_role = role_forced) and return forced_role
|
91
|
+
::Permissions.find_role_by_name!(aegis_role_name)
|
92
|
+
end
|
93
|
+
|
94
|
+
def role=(role_or_name)
|
95
|
+
self.aegis_role_name = if role_or_name.is_a?(Aegis::Role)
|
96
|
+
role_or_name.name
|
97
|
+
else
|
98
|
+
role_or_name.to_s
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
def role_in(context, tried_contexts = [])
|
103
|
+
(forced_role = role_forced) and return forced_role
|
104
|
+
context_key = "#{context.class}:#{context.id}"
|
105
|
+
tried_contexts << context_key
|
106
|
+
roles_by_context = role_assignments_hash
|
107
|
+
if roles_by_context.has_key?(context_key)
|
108
|
+
::Permissions.find_role_by_name!(roles_by_context[context_key])
|
109
|
+
elsif !aegis_role_hierarchy.blank? and (parent_class_accessor = aegis_role_hierarchy[context.class.name])
|
110
|
+
role_in(context.send(parent_class_accessor), tried_contexts)
|
111
|
+
elsif aegis_role_hierarchy_accessor and context.respond_to?(aegis_role_hierarchy_accessor)
|
112
|
+
role_in(context.send(aegis_role_hierarchy_accessor), tried_contexts)
|
113
|
+
else
|
114
|
+
actor_key = "#{self.class}:#{self.id}"
|
115
|
+
tried_contexts = tried_contexts.join(", ")
|
116
|
+
raise Aegis::PermissionError, "cannot find role for #{actor_key} in #{tried_contexts}"
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
def role_assignments_hash(*args)
|
121
|
+
return @role_assignments_override unless @role_assignments_override.blank?
|
122
|
+
role_assignments.inject({}) do |memo, role_assignment|
|
123
|
+
memo[role_assignment.context_key] = role_assignment.role_name
|
124
|
+
memo
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
def role_forced
|
129
|
+
aegis_forced_roles.each do |proc, role_name|
|
130
|
+
return role_name if proc.call(self)
|
131
|
+
end
|
132
|
+
nil
|
133
|
+
end
|
134
|
+
|
135
|
+
private
|
136
|
+
|
137
|
+
# Delegate may_...? and may_...! methods to the user's role.
|
138
|
+
def method_missing_with_aegis_permissions(symb, *args)
|
139
|
+
method_name = symb.to_s
|
140
|
+
if method_name =~ /^may_(.+?)_in[\!\?]$/
|
141
|
+
#method_name = method_name[0...-4] + method_name[-1, 1] # may_edit_post_in? => may_edit_post?
|
142
|
+
role_in(args.first).send(method_name, self, *args)
|
143
|
+
elsif method_name =~ /^may_(.+?)[\!\?]$/
|
144
|
+
role.send(symb, self, *args)
|
145
|
+
elsif method_name =~ /^(.*?)\?$/ && queried_role = ::Permissions.find_role_by_name($1)
|
146
|
+
role == queried_role
|
147
|
+
else
|
148
|
+
method_missing_without_aegis_permissions(symb, *args)
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
end # InstanceMethods
|
153
|
+
|
154
|
+
end # module HasRole
|
155
|
+
|
156
|
+
end # module Aegis
|