bmaland-aegis 1.1.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (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