bmaland-aegis 1.1.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (40) hide show
  1. data/.gitignore +3 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.rdoc +201 -0
  4. data/Rakefile +40 -0
  5. data/VERSION +1 -0
  6. data/init.rb +2 -0
  7. data/lib/aegis/constants.rb +6 -0
  8. data/lib/aegis/has_role.rb +79 -0
  9. data/lib/aegis/normalization.rb +26 -0
  10. data/lib/aegis/permission_error.rb +5 -0
  11. data/lib/aegis/permission_evaluator.rb +34 -0
  12. data/lib/aegis/permissions.rb +108 -0
  13. data/lib/aegis/role.rb +55 -0
  14. data/lib/aegis.rb +10 -0
  15. data/lib/rails/active_record.rb +5 -0
  16. data/test/app_root/app/controllers/application_controller.rb +2 -0
  17. data/test/app_root/app/models/permissions.rb +49 -0
  18. data/test/app_root/app/models/soldier.rb +5 -0
  19. data/test/app_root/app/models/user.rb +6 -0
  20. data/test/app_root/app/models/user_subclass.rb +2 -0
  21. data/test/app_root/config/boot.rb +114 -0
  22. data/test/app_root/config/database.yml +21 -0
  23. data/test/app_root/config/environment.rb +14 -0
  24. data/test/app_root/config/environments/in_memory.rb +0 -0
  25. data/test/app_root/config/environments/mysql.rb +0 -0
  26. data/test/app_root/config/environments/postgresql.rb +0 -0
  27. data/test/app_root/config/environments/sqlite.rb +0 -0
  28. data/test/app_root/config/environments/sqlite3.rb +0 -0
  29. data/test/app_root/config/routes.rb +4 -0
  30. data/test/app_root/db/migrate/20090408115228_create_users.rb +14 -0
  31. data/test/app_root/db/migrate/20090429075648_create_soldiers.rb +16 -0
  32. data/test/app_root/lib/console_with_fixtures.rb +4 -0
  33. data/test/app_root/log/.gitignore +1 -0
  34. data/test/app_root/script/console +7 -0
  35. data/test/has_role_options_test.rb +28 -0
  36. data/test/has_role_test.rb +48 -0
  37. data/test/permissions_test.rb +109 -0
  38. data/test/test_helper.rb +23 -0
  39. data/test/validation_test.rb +49 -0
  40. metadata +113 -0
data/.gitignore ADDED
@@ -0,0 +1,3 @@
1
+ doc
2
+ pkg
3
+ *.gem
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,201 @@
1
+ = Aegis - role-based permissions for your user models
2
+
3
+ Aegis allows you to manage fine-grained, complex permission for user accounts in a central place.
4
+
5
+ == Installation
6
+
7
+ Add the following to your <tt>Initializer.run</tt> block in your <tt>environment.rb</tt>:
8
+ config.gem 'aegis', :source => 'http://gemcutter.org'
9
+ Then do a
10
+ sudo rake gems:install
11
+
12
+ Alternatively, use
13
+ sudo gem sources -a http://gemcutter.org
14
+ sudo gem install aegis
15
+
16
+ == Example
17
+
18
+ First, let's define some roles:
19
+
20
+ # app/models/permissions.rb
21
+ class Permissions < Aegis::Permissions
22
+
23
+ role :guest
24
+ role :registered_user
25
+ role :moderator
26
+ role :administrator, :default_permission => :allow
27
+
28
+ permission :edit_post do |user, post|
29
+ allow :registered_user do
30
+ post.creator == user # a registered_user can only edit his own posts
31
+ end
32
+ allow :moderator
33
+ end
34
+
35
+ permission :read_post do |post|
36
+ allow :everyone
37
+ deny :guest do
38
+ post.private? # guests may not read private posts
39
+ end
40
+ end
41
+
42
+ end
43
+
44
+
45
+ Now we assign roles to users. For this, the users table needs to have a string
46
+ column +role_name+.
47
+
48
+ # app/models/user.rb
49
+ class User
50
+ has_role
51
+ end
52
+
53
+
54
+ These permissions may be used in views and controllers:
55
+
56
+ # app/views/posts/index.html.erb
57
+ @posts.each do |post|
58
+ <% if current_user.may_read_post? post %>
59
+ <%= render post %>
60
+ <% if current_user.may_edit_post? post %>
61
+ <%= link_to 'Edit', edit_post_path(post) %>
62
+ <% end %>
63
+ <% end %>
64
+ <% end %>
65
+
66
+
67
+ # app/controllers/posts_controller.rb
68
+ class PostsController
69
+ # ...
70
+
71
+ def update
72
+ @post = Post.find(params[:id])
73
+ current_user.may_edit_post! @post # raises an Aegis::PermissionError for unauthorized access
74
+ # ...
75
+ end
76
+
77
+ end
78
+
79
+ You might want to specifiy a default role:
80
+ class Permissions < Aegis::Permissions
81
+ default_role 'role_name'
82
+ end
83
+
84
+ This role will be returned for objects that has +nil+ as their +role_name+. This
85
+ greatly reduces noise in your database (i.e. if you have 100 000 users, you don't
86
+ have to store 'role_name' for each row, just for your non-default
87
+ roles). +default_role+ takes the same options as +role+.
88
+
89
+ To explicitly make sure that a given row won't have a permission object, set
90
+ +role_name+ to the empty string ("").
91
+
92
+ == Details
93
+
94
+ === Roles
95
+
96
+ To equip a (user) model with any permissions, you simply call *has_role* within
97
+ the model:
98
+ class User < ActiveRecord::Base
99
+ has_role
100
+ end
101
+ Aegis assumes that the corresponding database table has a string-valued column
102
+ called +role_name+. You may override the name with the <tt>:name_accessor =>
103
+ :my_role_column</tt> option.
104
+
105
+ The roles and permissions themselves are defined in a class inheriting from
106
+ <b>Aegis::Permissions</b>. To define roles you create a model <tt>permissions.rb</tt>
107
+ and use the *role* method:
108
+ class Permissions < Aegis::Permissions
109
+ role 'role_name'
110
+ end
111
+
112
+ By default, users belonging to this role are not permitted anything. You may
113
+ override this with <tt>:default_permission => :allow</tt>, e.g.
114
+ role 'admin', :default_permission => :allow
115
+
116
+ === Permissions
117
+
118
+ Permissions are specified with the *permission* method and *allow* and *deny*
119
+ permission :do_something do
120
+ allow :role_a, :role_b
121
+ deny :role_c
122
+ end
123
+
124
+ Your user model just received two methods called <b>User#may_do_something?</b>
125
+ and <b>User#may_do_something!</b>. The first one with the ? returns true for users with
126
+ +role_a+ and +role_b+, and false for users with +role_c+. The second one with the ! raises an
127
+ Aegis::PermissionError for +role_c+.
128
+
129
+ === Normalization
130
+
131
+ Aegis will perform some normalization. For example, the permissions
132
+ +edit_something+ and +update_something+ will be the same, each granting both
133
+ <tt>may_edit_something?</tt> and <tt>may_update_something?</tt>. The following normalizations
134
+ are active:
135
+ * edit = update
136
+ * show = list = view = read
137
+ * delete = remove = destroy
138
+
139
+ === Complex permissions (with parameters)
140
+
141
+ *allow* and *deny* can also take a block that may return +true+ or +false+
142
+ indicating if this really applies. So
143
+ permission :pull_april_fools_prank do
144
+ allow :everyone do
145
+ Date.today.month == 4 and Date.today.day == 1
146
+ end
147
+ end
148
+ will generate a <tt>may_pull_april_fools_prank?</tt> method that only returns true on
149
+ April 1.
150
+
151
+ This becomes more useful if you pass parameters to a <tt>may_...?</tt> method, which
152
+ are passed through to the permission block (together with the user object). This
153
+ way you can define more complex permissions like
154
+ permission :edit_post do |current_user, post|
155
+ allow :registered_user do
156
+ post.owner == current_user
157
+ end
158
+ allow :admin
159
+ end
160
+ which will permit admins and post owners to edit posts.
161
+
162
+ === For your convenience
163
+
164
+ As a convenience, if you create a permission ending in a plural 's', this
165
+ automatically includes the singular form. That is, after
166
+ permission :read_posts do
167
+ allow :everyone
168
+ end
169
+ <tt>.may_read_post? @post</tt> will return true, as well.
170
+
171
+ If you want to grant +create_something+, +read_something+, +update_something+
172
+ and +destroy_something+ permissions all at once, just use
173
+ permission :crud_something do
174
+ allow :admin
175
+ end
176
+
177
+ If several permission blocks (or several allow and denies) apply to a certain
178
+ role, the later one always wins. That is
179
+ permission :do_something do
180
+ deny :everyone
181
+ allow :admin
182
+ end
183
+ will work as expected.
184
+
185
+ === Our stance on multiple roles per user
186
+
187
+ We believe that you should only distinguish roles that have different ways of resolving their permissions. A typical set of roles would be
188
+
189
+ * anonymous guest (has access to nothing with some exceptions)
190
+ * signed up user (has access to some things depending on its attributes and associations)
191
+ * administrator (has access to everything)
192
+
193
+ We don't do multiple, parametrized roles like "leader for project #2" and "author of post #7".
194
+ That would be reinventing associations. Just use a single :user role and let your permission block
195
+ query regular associations and attributes.
196
+
197
+ === Credits
198
+
199
+ Henning Koch, Tobias Kraze
200
+
201
+ {link www.makandra.de}[http://www.makandra.de/]
data/Rakefile ADDED
@@ -0,0 +1,40 @@
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')
21
+ rdoc.rdoc_files.include('lib/**/*.rb')
22
+ end
23
+
24
+ begin
25
+ gem 'jeweler', '>= 1.3.0'
26
+ require 'jeweler'
27
+ Jeweler::Tasks.new do |gemspec|
28
+ gemspec.name = "bmaland-aegis"
29
+ gemspec.summary = "Role-based permissions for your user models."
30
+ gemspec.email = "github@makandra.de"
31
+ gemspec.homepage = "http://github.com/bmaland/aegis"
32
+ 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."
33
+ gemspec.authors = ["Henning Koch"]
34
+ gemspec.version = '1.1.5'
35
+ end
36
+ rescue LoadError
37
+ puts "Jeweler not available. Install it with: sudo gem install technicalpickles-jeweler -s http://gems.github.com"
38
+ end
39
+
40
+ Jeweler::GemcutterTasks.new
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 1.1.5
data/init.rb ADDED
@@ -0,0 +1,2 @@
1
+ # Include hook code here
2
+ require File.dirname(__FILE__) + "/lib/aegis"
@@ -0,0 +1,6 @@
1
+ module Aegis
2
+ module Constants
3
+ EVERYONE_ROLE_NAME = :everyone
4
+ CRUD_VERBS = ["create", "read", "update", "destroy"]
5
+ end
6
+ end
@@ -0,0 +1,79 @@
1
+ module Aegis
2
+ module HasRole
3
+
4
+ def validates_role_name(options = {})
5
+ validates_each :role_name do |record, attr, value|
6
+ options[:message] ||= ActiveRecord::Errors.default_error_messages[:inclusion]
7
+ role = ::Permissions.find_role_by_name(value)
8
+ record.errors.add attr, options[:message] if role.nil?
9
+ end
10
+ end
11
+
12
+ alias_method :validates_role, :validates_role_name
13
+
14
+ def has_role(options = {})
15
+
16
+ if options[:name_accessor]
17
+ options[:name_reader] = "#{options[:name_accessor]}"
18
+ options[:name_writer] = "#{options[:name_accessor]}="
19
+ options.delete(:name_accessor)
20
+ end
21
+
22
+ self.class_eval do
23
+
24
+ class_inheritable_accessor :aegis_role_name_reader, :aegis_role_name_writer
25
+
26
+ self.aegis_role_name_reader = (options[:name_reader] || "role_name").to_sym
27
+ self.aegis_role_name_writer = (options[:name_writer] || "role_name=").to_sym
28
+
29
+ def aegis_role_name_reader
30
+ self.class.class_eval{ aegis_role_name_reader }
31
+ end
32
+
33
+ def aegis_role_name_writer
34
+ self.class.class_eval{ aegis_role_name_writer }
35
+ end
36
+
37
+ def aegis_role_name
38
+ send(aegis_role_name_reader)
39
+ end
40
+
41
+ def aegis_role_name=(value)
42
+ send(aegis_role_name_writer, value)
43
+ end
44
+
45
+ def role
46
+ ::Permissions.find_role_by_name!(aegis_role_name)
47
+ end
48
+
49
+ def role=(role_or_name)
50
+ self.aegis_role_name = if role_or_name.is_a?(Aegis::Role)
51
+ role_or_name.name
52
+ else
53
+ role_or_name.to_s
54
+ end
55
+ end
56
+
57
+ private
58
+
59
+ # Delegate may_...? and may_...! methods to the user's role.
60
+ def method_missing_with_aegis_permissions(symb, *args)
61
+ method_name = symb.to_s
62
+ if method_name =~ /^may_(.+?)[\!\?]$/
63
+ role.send(symb, self, *args)
64
+ elsif method_name =~ /^(.*?)\?$/ && queried_role = ::Permissions.find_role_by_name($1)
65
+ role == queried_role
66
+ else
67
+ method_missing_without_aegis_permissions(symb, *args)
68
+ end
69
+ end
70
+
71
+ alias_method_chain :method_missing, :aegis_permissions
72
+
73
+ end
74
+
75
+ end
76
+
77
+ end
78
+
79
+ end
@@ -0,0 +1,26 @@
1
+ module Aegis
2
+ class Normalization
3
+
4
+ VERB_NORMALIZATIONS = {
5
+ "edit" => "update",
6
+ "show" => "read",
7
+ "list" => "read",
8
+ "view" => "read",
9
+ "delete" => "destroy",
10
+ "remove" => "destroy"
11
+ }
12
+
13
+ def self.normalize_verb(verb)
14
+ VERB_NORMALIZATIONS[verb] || verb
15
+ end
16
+
17
+ def self.normalize_permission(permission)
18
+ if permission =~ /^([^_]+?)_(.+?)$/
19
+ verb, target = $1, $2
20
+ permission = normalize_verb(verb) + "_" + target
21
+ end
22
+ permission
23
+ end
24
+
25
+ end
26
+ end
@@ -0,0 +1,5 @@
1
+ module Aegis
2
+ class PermissionError < StandardError
3
+
4
+ end
5
+ end
@@ -0,0 +1,34 @@
1
+ module Aegis
2
+ class PermissionEvaluator
3
+
4
+ def initialize(role)
5
+ @role = role
6
+ end
7
+
8
+ def evaluate(permissions, rule_args)
9
+ @result = @role.allow_by_default?
10
+ permissions.each do |permission|
11
+ instance_exec(*rule_args, &permission)
12
+ end
13
+ @result
14
+ end
15
+
16
+ def allow(*role_name_or_names, &block)
17
+ rule_encountered(role_name_or_names, true, &block)
18
+ end
19
+
20
+ def deny(*role_name_or_names, &block)
21
+ rule_encountered(role_name_or_names, false, &block)
22
+ end
23
+
24
+ def rule_encountered(role_name_or_names, is_allow, &block)
25
+ role_names = Array(role_name_or_names)
26
+ if role_names.include?(@role.name) || role_names.include?(Aegis::Constants::EVERYONE_ROLE_NAME)
27
+ @result = (block ? block.call : true)
28
+ @result = !@result unless is_allow
29
+ end
30
+ end
31
+
32
+ end
33
+ end
34
+
@@ -0,0 +1,108 @@
1
+ module Aegis
2
+ class Permissions
3
+
4
+ def self.inherited(base)
5
+ base.class_eval do
6
+ @default_role = nil
7
+ @roles_by_name = {}
8
+ @permission_blocks = Hash.new { |hash, key| hash[key] = [] }
9
+ extend ClassMethods
10
+ end
11
+ end
12
+
13
+ module ClassMethods
14
+
15
+ def default_role(role_name, options = {})
16
+ @default_role = role(role_name, options)
17
+ end
18
+
19
+ def role(role_name, options = {})
20
+ role_name = role_name.to_sym
21
+ role_name != Aegis::Constants::EVERYONE_ROLE_NAME or raise "Cannot define a role named: #{Aegis::Constants::EVERYONE_ROLE_NAME}"
22
+ @roles_by_name[role_name] = Aegis::Role.new(role_name, self, options)
23
+ end
24
+
25
+ def find_all_role_names
26
+ @roles_by_name.keys
27
+ end
28
+
29
+ def find_all_roles
30
+ @roles_by_name.values.sort
31
+ end
32
+
33
+ def find_role_by_name(name)
34
+ return nil if name == ""
35
+ # If name is nil, try to return the default role.
36
+ name.nil? ? @default_role : @roles_by_name[name.to_sym]
37
+ end
38
+
39
+ def find_role_by_name!(name)
40
+ find_role_by_name(name) or raise "Undefined role: #{name}"
41
+ end
42
+
43
+ def permission(*permission_name_or_names, &block)
44
+ permission_names = Array(permission_name_or_names).map(&:to_s)
45
+ permission_names.each do |permission_name|
46
+ add_split_crud_permission(permission_name, &block)
47
+ end
48
+ end
49
+
50
+ def may?(role_or_role_name, permission, *args)
51
+ role = role_or_role_name.is_a?(Aegis::Role) ? role_or_role_name : find_role_by_name(role_or_role_name)
52
+ blocks = @permission_blocks[permission.to_sym]
53
+ evaluate_permission_blocks(role, blocks, *args)
54
+ end
55
+
56
+ def evaluate_permission_blocks(role, blocks, *args)
57
+ evaluator = Aegis::PermissionEvaluator.new(role)
58
+ evaluator.evaluate(blocks, args)
59
+ end
60
+
61
+ def denied?(*args)
62
+ !allowed?(*args)
63
+ end
64
+
65
+ private
66
+
67
+ def add_split_crud_permission(permission_name, &block)
68
+ if permission_name =~ /^crud_(.+?)$/
69
+ target = $1
70
+ Aegis::Constants::CRUD_VERBS.each do |verb|
71
+ add_normalized_permission("#{verb}_#{target}", &block)
72
+ end
73
+ else
74
+ add_normalized_permission(permission_name, &block)
75
+ end
76
+ end
77
+
78
+ def add_normalized_permission(permission_name, &block)
79
+ normalized_permission_name = Aegis::Normalization.normalize_permission(permission_name)
80
+ add_singularized_permission(normalized_permission_name, &block)
81
+ end
82
+
83
+ def add_singularized_permission(permission_name, &block)
84
+ if permission_name =~ /^([^_]+?)_(.+?)$/
85
+ verb = $1
86
+ target = $2
87
+ singular_target = target.singularize
88
+ if singular_target.length < target.length
89
+ singular_block = lambda do |*args|
90
+ args.delete_at 1
91
+ instance_exec(*args, &block)
92
+ end
93
+ singular_permission_name = "#{verb}_#{singular_target}"
94
+ add_permission(singular_permission_name, &singular_block)
95
+ end
96
+ end
97
+ add_permission(permission_name, &block)
98
+ end
99
+
100
+ def add_permission(permission_name, &block)
101
+ permission_name = permission_name.to_sym
102
+ @permission_blocks[permission_name] << block
103
+ end
104
+
105
+ end # module ClassMethods
106
+
107
+ end # class Permissions
108
+ end # module Aegis
data/lib/aegis/role.rb ADDED
@@ -0,0 +1,55 @@
1
+ module Aegis
2
+ class Role
3
+
4
+ attr_reader :name, :default_permission
5
+
6
+ # permissions is a hash like: permissions[:edit_user] = lambda { |user| ... }
7
+ def initialize(name, permissions, options)
8
+ @name = name
9
+ @permissions = permissions
10
+ @default_permission = options[:default_permission] == :allow ? :allow : :deny
11
+ freeze
12
+ end
13
+
14
+ def allow_by_default?
15
+ @default_permission == :allow
16
+ end
17
+
18
+ def may?(permission, *args)
19
+ # puts "may? #{permission}, #{args}"
20
+ @permissions.may?(self, permission, *args)
21
+ end
22
+
23
+ def <=>(other)
24
+ name.to_s <=> other.name.to_s
25
+ end
26
+
27
+ def to_s
28
+ name.to_s.humanize
29
+ end
30
+
31
+ def id
32
+ name.to_s
33
+ end
34
+
35
+ private
36
+
37
+ def method_missing(symb, *args)
38
+ method_name = symb.to_s
39
+ if method_name =~ /^may_(.+)(\?|\!)$/
40
+ permission, severity = $1, $2
41
+ permission = Aegis::Normalization.normalize_permission(permission)
42
+ may = may?(permission, *args)
43
+ if severity == '!' && !may
44
+ raise PermissionError, "Access denied: #{permission}"
45
+ else
46
+ may
47
+ end
48
+ else
49
+ super
50
+ end
51
+ end
52
+
53
+
54
+ end
55
+ end
data/lib/aegis.rb ADDED
@@ -0,0 +1,10 @@
1
+ # Include hook code here
2
+ require 'aegis/constants'
3
+ require 'aegis/has_role'
4
+ require 'aegis/normalization'
5
+ require 'aegis/permission_error'
6
+ require 'aegis/permission_evaluator'
7
+ require 'aegis/permissions'
8
+ require 'aegis/role'
9
+ require 'rails/active_record'
10
+
@@ -0,0 +1,5 @@
1
+ ActiveRecord::Base.class_eval do
2
+
3
+ extend Aegis::HasRole
4
+
5
+ end
@@ -0,0 +1,2 @@
1
+ class ApplicationController < ActionController::Base
2
+ end
@@ -0,0 +1,49 @@
1
+
2
+ class Permissions < Aegis::Permissions
3
+
4
+ default_role :guest
5
+ role :student
6
+ role :admin, :default_permission => :allow
7
+
8
+ permission :use_empty do
9
+ end
10
+
11
+ permission :use_simple do
12
+ allow :student
13
+ deny :admin
14
+ end
15
+
16
+ permission :update_users do
17
+ allow :student
18
+ deny :admin
19
+ end
20
+
21
+ permission :crud_projects do
22
+ allow :student
23
+ end
24
+
25
+ permission :edit_drinks do
26
+ allow :student
27
+ deny :admin
28
+ end
29
+
30
+ permission :hug do
31
+ allow :everyone
32
+ end
33
+
34
+ permission :divide do |user, left, right|
35
+ allow :student do
36
+ right != 0
37
+ end
38
+ end
39
+
40
+ permission :draw do
41
+ allow :everyone
42
+ end
43
+
44
+ permission :draw do
45
+ deny :student
46
+ deny :admin
47
+ end
48
+
49
+ end
@@ -0,0 +1,5 @@
1
+ class Soldier < ActiveRecord::Base
2
+
3
+ has_role :name_accessor => "rank"
4
+
5
+ end
@@ -0,0 +1,6 @@
1
+ class User < ActiveRecord::Base
2
+
3
+ has_role
4
+ validates_role_name
5
+
6
+ end
@@ -0,0 +1,2 @@
1
+ class UserSubclass < User
2
+ end
@@ -0,0 +1,114 @@
1
+ # Allow customization of the rails framework path
2
+ RAILS_FRAMEWORK_ROOT = (ENV['RAILS_FRAMEWORK_ROOT'] || "#{File.dirname(__FILE__)}/../../../../../../vendor/rails") unless defined?(RAILS_FRAMEWORK_ROOT)
3
+
4
+ # Don't change this file!
5
+ # Configure your app in config/environment.rb and config/environments/*.rb
6
+
7
+ RAILS_ROOT = "#{File.dirname(__FILE__)}/.." unless defined?(RAILS_ROOT)
8
+
9
+ module Rails
10
+ class << self
11
+ def boot!
12
+ unless booted?
13
+ preinitialize
14
+ pick_boot.run
15
+ end
16
+ end
17
+
18
+ def booted?
19
+ defined? Rails::Initializer
20
+ end
21
+
22
+ def pick_boot
23
+ (vendor_rails? ? VendorBoot : GemBoot).new
24
+ end
25
+
26
+ def vendor_rails?
27
+ File.exist?(RAILS_FRAMEWORK_ROOT)
28
+ end
29
+
30
+ def preinitialize
31
+ load(preinitializer_path) if File.exist?(preinitializer_path)
32
+ end
33
+
34
+ def preinitializer_path
35
+ "#{RAILS_ROOT}/config/preinitializer.rb"
36
+ end
37
+ end
38
+
39
+ class Boot
40
+ def run
41
+ load_initializer
42
+ Rails::Initializer.run(:set_load_path)
43
+ end
44
+ end
45
+
46
+ class VendorBoot < Boot
47
+ def load_initializer
48
+ require "#{RAILS_FRAMEWORK_ROOT}/railties/lib/initializer"
49
+ Rails::Initializer.run(:install_gem_spec_stubs)
50
+ end
51
+ end
52
+
53
+ class GemBoot < Boot
54
+ def load_initializer
55
+ self.class.load_rubygems
56
+ load_rails_gem
57
+ require 'initializer'
58
+ end
59
+
60
+ def load_rails_gem
61
+ if version = self.class.gem_version
62
+ gem 'rails', version
63
+ else
64
+ gem 'rails'
65
+ end
66
+ rescue Gem::LoadError => load_error
67
+ $stderr.puts %(Missing the Rails #{version} gem. Please `gem install -v=#{version} rails`, update your RAILS_GEM_VERSION setting in config/environment.rb for the Rails version you do have installed, or comment out RAILS_GEM_VERSION to use the latest version installed.)
68
+ exit 1
69
+ end
70
+
71
+ class << self
72
+ def rubygems_version
73
+ Gem::RubyGemsVersion rescue nil
74
+ end
75
+
76
+ def gem_version
77
+ if defined? RAILS_GEM_VERSION
78
+ RAILS_GEM_VERSION
79
+ elsif ENV.include?('RAILS_GEM_VERSION')
80
+ ENV['RAILS_GEM_VERSION']
81
+ else
82
+ parse_gem_version(read_environment_rb)
83
+ end
84
+ end
85
+
86
+ def load_rubygems
87
+ require 'rubygems'
88
+ min_version = '1.1.1'
89
+ unless rubygems_version >= min_version
90
+ $stderr.puts %Q(Rails requires RubyGems >= #{min_version} (you have #{rubygems_version}). Please `gem update --system` and try again.)
91
+ exit 1
92
+ end
93
+
94
+ rescue LoadError
95
+ $stderr.puts %Q(Rails requires RubyGems >= #{min_version}. Please install RubyGems and try again: http://rubygems.rubyforge.org)
96
+ exit 1
97
+ end
98
+
99
+ def parse_gem_version(text)
100
+ $1 if text =~ /^[^#]*RAILS_GEM_VERSION\s*=\s*["']([!~<>=]*\s*[\d.]+)["']/
101
+ end
102
+
103
+ private
104
+ def read_environment_rb
105
+ environment_rb = "#{RAILS_ROOT}/config/environment.rb"
106
+ environment_rb = "#{HELPER_RAILS_ROOT}/config/environment.rb" unless File.exists?(environment_rb)
107
+ File.read(environment_rb)
108
+ end
109
+ end
110
+ end
111
+ end
112
+
113
+ # All that for this:
114
+ Rails.boot!
@@ -0,0 +1,21 @@
1
+ in_memory:
2
+ adapter: sqlite3
3
+ database: ":memory:"
4
+ verbosity: quiet
5
+ sqlite:
6
+ adapter: sqlite
7
+ dbfile: plugin_test.sqlite.db
8
+ sqlite3:
9
+ adapter: sqlite3
10
+ dbfile: plugin_test.sqlite3.db
11
+ postgresql:
12
+ adapter: postgresql
13
+ username: postgres
14
+ password: postgres
15
+ database: plugin_test
16
+ mysql:
17
+ adapter: mysql
18
+ host: localhost
19
+ username: root
20
+ password:
21
+ database: plugin_test
@@ -0,0 +1,14 @@
1
+ require File.join(File.dirname(__FILE__), 'boot')
2
+
3
+ Rails::Initializer.run do |config|
4
+ config.cache_classes = false
5
+ config.whiny_nils = true
6
+ config.action_controller.session = { :key => "_myapp_session", :secret => "gwirofjweroijger8924rt2zfwehfuiwehb1378rifowenfoqwphf23" }
7
+ config.plugin_locators.unshift(
8
+ Class.new(Rails::Plugin::Locator) do
9
+ def plugins
10
+ [Rails::Plugin.new(File.expand_path('.'))]
11
+ end
12
+ end
13
+ ) unless defined?(PluginTestHelper::PluginLocator)
14
+ end
File without changes
File without changes
File without changes
File without changes
File without changes
@@ -0,0 +1,4 @@
1
+ ActionController::Routing::Routes.draw do |map|
2
+ map.connect ':controller/:action/:id'
3
+ map.connect ':controller/:action/:id.:format'
4
+ end
@@ -0,0 +1,14 @@
1
+ class CreateUsers < ActiveRecord::Migration
2
+
3
+ def self.up
4
+ create_table :users do |t|
5
+ t.string "role_name"
6
+ t.timestamps
7
+ end
8
+ end
9
+
10
+ def self.down
11
+ drop_table :users
12
+ end
13
+
14
+ end
@@ -0,0 +1,16 @@
1
+ class CreateSoldiers < ActiveRecord::Migration
2
+
3
+ def self.up
4
+
5
+ create_table :soldiers do |t|
6
+ t.string :rank
7
+ t.timestamps
8
+ end
9
+
10
+ end
11
+
12
+ def self.down
13
+ drop_table :soldiers
14
+ end
15
+
16
+ end
@@ -0,0 +1,4 @@
1
+ # Loads fixtures into the database when running the test app via the console
2
+ (ENV['FIXTURES'] ? ENV['FIXTURES'].split(/,/) : Dir.glob(File.join(Rails.root, '../fixtures/*.{yml,csv}'))).each do |fixture_file|
3
+ Fixtures.create_fixtures(File.join(Rails.root, '../fixtures'), File.basename(fixture_file, '.*'))
4
+ end
@@ -0,0 +1 @@
1
+ *.log
@@ -0,0 +1,7 @@
1
+ irb = RUBY_PLATFORM =~ /(:?mswin|mingw)/ ? 'irb.bat' : 'irb'
2
+ libs = " -r irb/completion"
3
+ libs << " -r test/test_helper"
4
+ libs << " -r console_app"
5
+ libs << " -r console_with_helpers"
6
+ libs << " -r console_with_fixtures"
7
+ exec "#{irb} #{libs} --simple-prompt"
@@ -0,0 +1,28 @@
1
+ require "test/test_helper"
2
+
3
+ class HasRoleOptionsTest < ActiveSupport::TestCase
4
+
5
+ context "A record with a custom role field" do
6
+
7
+ setup do
8
+ @soldier = Soldier.new
9
+ end
10
+
11
+ should "allow its role to be written and read" do
12
+ @soldier.role = "guest"
13
+ assert "guest", @soldier.role.name
14
+ end
15
+
16
+ should "store the role name in the custom field" do
17
+ assert "guest", @soldier.rank
18
+ end
19
+
20
+ should "still work with permissions" do
21
+ @soldier.role = "guest"
22
+ assert @soldier.may_hug?
23
+ assert !@soldier.may_update_users?
24
+ end
25
+
26
+ end
27
+
28
+ end
@@ -0,0 +1,48 @@
1
+ require "test/test_helper"
2
+
3
+ class HasRoleTest < ActiveSupport::TestCase
4
+
5
+ context "Objects that have an aegis role" do
6
+
7
+ setup do
8
+ @guest = User.new(:role_name => "guest")
9
+ @student = User.new(:role_name => "student")
10
+ @student_subclass = UserSubclass.new(:role_name => "student")
11
+ @admin = User.new(:role_name => "admin")
12
+ end
13
+
14
+ should "know their role" do
15
+ assert :guest, @guest.role.name
16
+ assert :student, @student.role.name
17
+ assert :student_subclass, @student.role.name
18
+ assert :admin, @admin.role.name
19
+ end
20
+
21
+ should "know if they belong to a role" do
22
+ assert @guest.guest?
23
+ assert !@guest.student?
24
+ assert !@guest.admin?
25
+ assert !@student.guest?
26
+ assert !@student_subclass.guest?
27
+ assert @student.student?
28
+ assert @student_subclass.student?
29
+ assert !@student.admin?
30
+ assert !@student_subclass.admin?
31
+ assert !@admin.guest?
32
+ assert !@admin.student?
33
+ assert @admin.admin?
34
+ end
35
+
36
+ should "still behave as usual when a method ending in a '?' does not map to a role query" do
37
+ assert_raise NoMethodError do
38
+ @guest.nonexisting_method?
39
+ end
40
+ end
41
+
42
+ should "have a default roule" do
43
+ assert :guest, User.new(:role_name => nil).role.name
44
+ end
45
+
46
+ end
47
+
48
+ end
@@ -0,0 +1,109 @@
1
+ require "test/test_helper"
2
+
3
+ class PermissionsTest < ActiveSupport::TestCase
4
+
5
+ context "Aegis permissions" do
6
+
7
+ setup do
8
+ @guest = User.new(:role_name => "guest")
9
+ @student = User.new(:role_name => "student")
10
+ @student_subclass = UserSubclass.new(:role_name => "student")
11
+ @admin = User.new(:role_name => "admin")
12
+ end
13
+
14
+ should "use the default permission for actions without any allow or grant directives" do
15
+ assert !@guest.may_use_empty?
16
+ assert !@student.may_use_empty?
17
+ assert !@student_subclass.may_use_empty?
18
+ assert @admin.may_use_empty?
19
+ end
20
+
21
+ should "understand simple allow and deny directives" do
22
+ assert !@guest.may_use_simple?
23
+ assert @student.may_use_simple?
24
+ assert @student_subclass.may_use_simple?
25
+ assert !@admin.may_use_simple?
26
+ end
27
+
28
+ should 'raise exceptions when a denied action is queried with an exclamation mark' do
29
+ assert_raise Aegis::PermissionError do
30
+ @guest.may_use_simple!
31
+ end
32
+ assert_raise Aegis::PermissionError do
33
+ @admin.may_use_simple!
34
+ end
35
+ end
36
+
37
+ should 'do nothing if an allowed action is queried with an exclamation mark' do
38
+ assert_nothing_raised do
39
+ @student.may_use_simple!
40
+ @student_subclass.may_use_simple!
41
+ end
42
+ end
43
+
44
+ should "implicate the singular form of an action described in plural form" do
45
+ assert !@guest.may_update_users?
46
+ assert !@guest.may_update_user?("foo")
47
+ assert @student.may_update_users?
48
+ assert @student_subclass.may_update_users?
49
+ assert @student.may_update_user?("foo")
50
+ assert @student_subclass.may_update_user?("foo")
51
+ assert !@admin.may_update_users?
52
+ assert !@admin.may_update_user?("foo")
53
+ end
54
+
55
+ should 'implicate create, read, update and destroy forms for actions named "crud_..."' do
56
+ assert @student.may_create_projects?
57
+ assert @student_subclass.may_create_projects?
58
+ assert @student.may_read_projects?
59
+ assert @student_subclass.may_read_projects?
60
+ assert @student.may_update_projects?
61
+ assert @student_subclass.may_update_projects?
62
+ assert @student.may_destroy_projects?
63
+ assert @student_subclass.may_destroy_projects?
64
+ end
65
+
66
+ should 'perform normalization of CRUD verbs (e.g. "edit" and "update")' do
67
+ assert !@guest.may_edit_drinks?
68
+ assert @student.may_edit_drinks?
69
+ assert @student_subclass.may_edit_drinks?
70
+ assert !@admin.may_edit_drinks?
71
+ assert !@guest.may_update_drinks?
72
+ assert @student.may_update_drinks?
73
+ assert @student_subclass.may_update_drinks?
74
+ assert !@admin.may_update_drinks?
75
+ end
76
+
77
+ should "be able to grant or deny actions to all roles using :everyone" do
78
+ assert @guest.may_hug?
79
+ assert @student.may_hug?
80
+ assert @student_subclass.may_hug?
81
+ assert @admin.may_hug?
82
+ end
83
+
84
+ should "allow the definition of parametrized actions" do
85
+ assert !@guest.may_divide?(10, 2)
86
+ assert @student.may_divide?(10, 2)
87
+ assert @student_subclass.may_divide?(10, 2)
88
+ assert !@student.may_divide?(10, 0)
89
+ assert !@student_subclass.may_divide?(10, 0)
90
+ assert @admin.may_divide?(10, 2)
91
+ assert @admin.may_divide?(10, 0)
92
+ end
93
+
94
+ should 'use default permissions for undefined actions' do
95
+ !@student.may_do_undefined_stuff?("foo")
96
+ !@student_subclass.may_do_undefined_stuff?("foo")
97
+ @admin.may_do_undefined_stuff?("foo")
98
+ end
99
+
100
+ should 'overshadow previous action definitions with the same name' do
101
+ assert @guest.may_draw?
102
+ assert !@student.may_draw?
103
+ assert !@student_subclass.may_draw?
104
+ assert !@admin.may_draw?
105
+ end
106
+
107
+ end
108
+
109
+ end
@@ -0,0 +1,23 @@
1
+ # Set the default environment to sqlite3's in_memory database
2
+ ENV['RAILS_ENV'] ||= 'in_memory'
3
+
4
+ # Load the Rails environment and testing framework
5
+ require "#{File.dirname(__FILE__)}/app_root/config/environment"
6
+ require "#{File.dirname(__FILE__)}/../lib/aegis"
7
+ require 'test_help'
8
+ require 'action_view/test_case' # Load additional test classes not done automatically by < Rails 2.2.2
9
+
10
+ require "shoulda"
11
+
12
+ # Undo changes to RAILS_ENV
13
+ silence_warnings {RAILS_ENV = ENV['RAILS_ENV']}
14
+
15
+ # Run the migrations
16
+ ActiveRecord::Migrator.migrate("#{Rails.root}/db/migrate")
17
+
18
+ # Set default fixture loading properties
19
+ ActiveSupport::TestCase.class_eval do
20
+ self.use_transactional_fixtures = true
21
+ self.use_instantiated_fixtures = false
22
+ self.fixture_path = "#{File.dirname(__FILE__)}/fixtures"
23
+ end
@@ -0,0 +1,49 @@
1
+ require "test/test_helper"
2
+
3
+ class ValidationTest < ActiveSupport::TestCase
4
+
5
+ context "A model that has and validates its role" do
6
+
7
+ setup do
8
+ @user = User.new()
9
+ end
10
+
11
+ context "that has a role_name mapping to a role" do
12
+
13
+ setup do
14
+ @user.role_name = "admin"
15
+ end
16
+
17
+ should "be valid" do
18
+ assert @user.valid?
19
+ end
20
+
21
+ end
22
+
23
+ context "that has a blank role_name" do
24
+
25
+ setup do
26
+ @user.role_name = ""
27
+ end
28
+
29
+ should "not be valid" do
30
+ assert !@user.valid?
31
+ end
32
+
33
+ end
34
+
35
+ context "that has a role_name not mapping to a role" do
36
+
37
+ setup do
38
+ @user.role_name = "nonexisting_role_name"
39
+ end
40
+
41
+ should "not be valid" do
42
+ assert !@user.valid?
43
+ end
44
+
45
+ end
46
+
47
+ end
48
+
49
+ end
metadata ADDED
@@ -0,0 +1,113 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: bmaland-aegis
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.1.5
5
+ platform: ruby
6
+ authors:
7
+ - Henning Koch
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-11-10 00:00:00 +01:00
13
+ default_executable:
14
+ dependencies: []
15
+
16
+ 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.
17
+ email: github@makandra.de
18
+ executables: []
19
+
20
+ extensions: []
21
+
22
+ extra_rdoc_files:
23
+ - README.rdoc
24
+ files:
25
+ - .gitignore
26
+ - MIT-LICENSE
27
+ - README.rdoc
28
+ - Rakefile
29
+ - VERSION
30
+ - init.rb
31
+ - lib/aegis.rb
32
+ - lib/aegis/constants.rb
33
+ - lib/aegis/has_role.rb
34
+ - lib/aegis/normalization.rb
35
+ - lib/aegis/permission_error.rb
36
+ - lib/aegis/permission_evaluator.rb
37
+ - lib/aegis/permissions.rb
38
+ - lib/aegis/role.rb
39
+ - lib/rails/active_record.rb
40
+ - test/app_root/app/controllers/application_controller.rb
41
+ - test/app_root/app/models/permissions.rb
42
+ - test/app_root/app/models/soldier.rb
43
+ - test/app_root/app/models/user.rb
44
+ - test/app_root/app/models/user_subclass.rb
45
+ - test/app_root/config/boot.rb
46
+ - test/app_root/config/database.yml
47
+ - test/app_root/config/environment.rb
48
+ - test/app_root/config/environments/in_memory.rb
49
+ - test/app_root/config/environments/mysql.rb
50
+ - test/app_root/config/environments/postgresql.rb
51
+ - test/app_root/config/environments/sqlite.rb
52
+ - test/app_root/config/environments/sqlite3.rb
53
+ - test/app_root/config/routes.rb
54
+ - test/app_root/db/migrate/20090408115228_create_users.rb
55
+ - test/app_root/db/migrate/20090429075648_create_soldiers.rb
56
+ - test/app_root/lib/console_with_fixtures.rb
57
+ - test/app_root/log/.gitignore
58
+ - test/app_root/script/console
59
+ - test/has_role_options_test.rb
60
+ - test/has_role_test.rb
61
+ - test/permissions_test.rb
62
+ - test/test_helper.rb
63
+ - test/validation_test.rb
64
+ has_rdoc: true
65
+ homepage: http://github.com/bmaland/aegis
66
+ licenses: []
67
+
68
+ post_install_message:
69
+ rdoc_options:
70
+ - --charset=UTF-8
71
+ require_paths:
72
+ - lib
73
+ required_ruby_version: !ruby/object:Gem::Requirement
74
+ requirements:
75
+ - - ">="
76
+ - !ruby/object:Gem::Version
77
+ version: "0"
78
+ version:
79
+ required_rubygems_version: !ruby/object:Gem::Requirement
80
+ requirements:
81
+ - - ">="
82
+ - !ruby/object:Gem::Version
83
+ version: "0"
84
+ version:
85
+ requirements: []
86
+
87
+ rubyforge_project:
88
+ rubygems_version: 1.3.5
89
+ signing_key:
90
+ specification_version: 3
91
+ summary: Role-based permissions for your user models.
92
+ test_files:
93
+ - test/validation_test.rb
94
+ - test/permissions_test.rb
95
+ - test/test_helper.rb
96
+ - test/app_root/db/migrate/20090408115228_create_users.rb
97
+ - test/app_root/db/migrate/20090429075648_create_soldiers.rb
98
+ - test/app_root/lib/console_with_fixtures.rb
99
+ - test/app_root/app/controllers/application_controller.rb
100
+ - test/app_root/app/models/permissions.rb
101
+ - test/app_root/app/models/soldier.rb
102
+ - test/app_root/app/models/user_subclass.rb
103
+ - test/app_root/app/models/user.rb
104
+ - test/app_root/config/environments/sqlite3.rb
105
+ - test/app_root/config/environments/sqlite.rb
106
+ - test/app_root/config/environments/postgresql.rb
107
+ - test/app_root/config/environments/in_memory.rb
108
+ - test/app_root/config/environments/mysql.rb
109
+ - test/app_root/config/environment.rb
110
+ - test/app_root/config/routes.rb
111
+ - test/app_root/config/boot.rb
112
+ - test/has_role_options_test.rb
113
+ - test/has_role_test.rb