Chrononaut-aegis 1.2.0

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 +189 -0
  4. data/Rakefile +37 -0
  5. data/VERSION +1 -0
  6. data/aegis.gemspec +93 -0
  7. data/init.rb +2 -0
  8. data/lib/aegis.rb +8 -0
  9. data/lib/aegis/constants.rb +6 -0
  10. data/lib/aegis/has_role.rb +77 -0
  11. data/lib/aegis/normalization.rb +26 -0
  12. data/lib/aegis/permission_error.rb +5 -0
  13. data/lib/aegis/permission_evaluator.rb +33 -0
  14. data/lib/aegis/permissions.rb +110 -0
  15. data/lib/aegis/role.rb +54 -0
  16. data/lib/rails/active_record.rb +5 -0
  17. data/test/app_root/app/controllers/application_controller.rb +2 -0
  18. data/test/app_root/app/models/permissions.rb +48 -0
  19. data/test/app_root/app/models/soldier.rb +5 -0
  20. data/test/app_root/app/models/user.rb +6 -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 +43 -0
  37. data/test/permissions_test.rb +92 -0
  38. data/test/test_helper.rb +23 -0
  39. data/test/validation_test.rb +49 -0
  40. metadata +110 -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,189 @@
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 'makandra-aegis', :lib => 'aegis', :source => 'http://gems.github.com'
9
+ Then do a
10
+ sudo rake gems:install
11
+
12
+ Alternatively, use
13
+ sudo gem sources -a http://gems.github.com
14
+ sudo gem install makandra-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
+ == Details
80
+
81
+ === Roles
82
+
83
+ To equip a (user) model with any permissions, you simply call *has_role* within
84
+ the model:
85
+ class User < ActiveRecord::Base
86
+ has_role
87
+ end
88
+ Aegis assumes that the corresponding database table has a string-valued column
89
+ called +role_name+. You may override the name with the <tt>:name_accessor =>
90
+ :my_role_column</tt> option.
91
+
92
+ The roles and permissions themselves are defined in a class inheriting from
93
+ <b>Aegis::Permissions</b>. To define roles you create a model <tt>permissions.rb</tt>
94
+ and use the *role* method:
95
+ class Permissions < Aegis::Permissions
96
+ role 'role_name'
97
+ end
98
+
99
+ By default, users belonging to this role are not permitted anything. You may
100
+ override this with <tt>:default_permission => :allow</tt>, e.g.
101
+ role 'admin', :default_permission => :allow
102
+
103
+ You might want to specifiy a default role:
104
+ class Permissions < Aegis::Permissions
105
+ default_role 'role_name'
106
+ end
107
+
108
+ This role will be returned for objects that has +nil+ as their +role_name+. This
109
+ greatly reduces noise in your database (i.e. if you have 100 000 users, you don't
110
+ have to store 'role_name' for each row, just for your non-default
111
+ roles). +default_role+ takes the same options as +role+.
112
+
113
+ To explicitly make sure that a given row won't have a permission object, set
114
+ +role_name+ to the empty string ("").
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
+ === Credits
186
+
187
+ Henning Koch, Tobias Kraze, Bjørn Arild Mæland
188
+
189
+ www.makandra.de
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')
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.2.0
data/aegis.gemspec ADDED
@@ -0,0 +1,93 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ Gem::Specification.new do |s|
4
+ s.name = %q{aegis}
5
+ s.version = "1.2.0"
6
+
7
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
8
+ s.authors = ["Henning Koch"]
9
+ s.date = %q{2009-07-09}
10
+ 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.}
11
+ s.email = %q{github@makandra.de}
12
+ s.extra_rdoc_files = [
13
+ "README.rdoc"
14
+ ]
15
+ s.files = [
16
+ ".gitignore",
17
+ "MIT-LICENSE",
18
+ "README.rdoc",
19
+ "Rakefile",
20
+ "VERSION",
21
+ "aegis.gemspec",
22
+ "init.rb",
23
+ "lib/aegis.rb",
24
+ "lib/aegis/constants.rb",
25
+ "lib/aegis/has_role.rb",
26
+ "lib/aegis/normalization.rb",
27
+ "lib/aegis/permission_error.rb",
28
+ "lib/aegis/permission_evaluator.rb",
29
+ "lib/aegis/permissions.rb",
30
+ "lib/aegis/role.rb",
31
+ "lib/rails/active_record.rb",
32
+ "test/app_root/app/controllers/application_controller.rb",
33
+ "test/app_root/app/models/permissions.rb",
34
+ "test/app_root/app/models/soldier.rb",
35
+ "test/app_root/app/models/user.rb",
36
+ "test/app_root/config/boot.rb",
37
+ "test/app_root/config/database.yml",
38
+ "test/app_root/config/environment.rb",
39
+ "test/app_root/config/environments/in_memory.rb",
40
+ "test/app_root/config/environments/mysql.rb",
41
+ "test/app_root/config/environments/postgresql.rb",
42
+ "test/app_root/config/environments/sqlite.rb",
43
+ "test/app_root/config/environments/sqlite3.rb",
44
+ "test/app_root/config/routes.rb",
45
+ "test/app_root/db/migrate/20090408115228_create_users.rb",
46
+ "test/app_root/db/migrate/20090429075648_create_soldiers.rb",
47
+ "test/app_root/lib/console_with_fixtures.rb",
48
+ "test/app_root/log/.gitignore",
49
+ "test/app_root/script/console",
50
+ "test/has_role_options_test.rb",
51
+ "test/has_role_test.rb",
52
+ "test/permissions_test.rb",
53
+ "test/test_helper.rb",
54
+ "test/validation_test.rb"
55
+ ]
56
+ s.homepage = %q{http://github.com/makandra/aegis}
57
+ s.rdoc_options = ["--charset=UTF-8"]
58
+ s.require_paths = ["lib"]
59
+ s.rubygems_version = %q{1.3.4}
60
+ s.summary = %q{Role-based permissions for your user models.}
61
+ s.test_files = [
62
+ "test/app_root/app/controllers/application_controller.rb",
63
+ "test/app_root/app/models/permissions.rb",
64
+ "test/app_root/app/models/soldier.rb",
65
+ "test/app_root/app/models/user.rb",
66
+ "test/app_root/config/boot.rb",
67
+ "test/app_root/config/environment.rb",
68
+ "test/app_root/config/environments/in_memory.rb",
69
+ "test/app_root/config/environments/mysql.rb",
70
+ "test/app_root/config/environments/postgresql.rb",
71
+ "test/app_root/config/environments/sqlite.rb",
72
+ "test/app_root/config/environments/sqlite3.rb",
73
+ "test/app_root/config/routes.rb",
74
+ "test/app_root/db/migrate/20090408115228_create_users.rb",
75
+ "test/app_root/db/migrate/20090429075648_create_soldiers.rb",
76
+ "test/app_root/lib/console_with_fixtures.rb",
77
+ "test/has_role_options_test.rb",
78
+ "test/has_role_test.rb",
79
+ "test/permissions_test.rb",
80
+ "test/test_helper.rb",
81
+ "test/validation_test.rb"
82
+ ]
83
+
84
+ if s.respond_to? :specification_version then
85
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
86
+ s.specification_version = 3
87
+
88
+ if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
89
+ else
90
+ end
91
+ else
92
+ end
93
+ end
data/init.rb ADDED
@@ -0,0 +1,2 @@
1
+ # Include hook code here
2
+ require File.dirname(__FILE__) + "/lib/aegis"
data/lib/aegis.rb ADDED
@@ -0,0 +1,8 @@
1
+ # Include hook code here
2
+ require 'aegis/constants'
3
+ require 'aegis/normalization'
4
+ require 'aegis/permission_error'
5
+ require 'aegis/role'
6
+ require 'aegis/permissions'
7
+ require 'aegis/has_role'
8
+ require 'rails/active_record'
@@ -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,77 @@
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
+ @aegis_role_name_reader = (options[:name_reader] || "role_name").to_sym
25
+ @aegis_role_name_writer = (options[:name_writer] || "role_name=").to_sym
26
+
27
+ def aegis_role_name_reader
28
+ self.class.class_eval{ @aegis_role_name_reader }
29
+ end
30
+
31
+ def aegis_role_name_writer
32
+ self.class.class_eval{ @aegis_role_name_writer }
33
+ end
34
+
35
+ def aegis_role_name
36
+ send(aegis_role_name_reader)
37
+ end
38
+
39
+ def aegis_role_name=(value)
40
+ send(aegis_role_name_writer, value)
41
+ end
42
+
43
+ def role
44
+ ::Permissions.find_role_by_name!(aegis_role_name)
45
+ end
46
+
47
+ def role=(role_or_name)
48
+ self.aegis_role_name = if role_or_name.is_a?(Aegis::Role)
49
+ role_or_name.name
50
+ else
51
+ role_or_name.to_s
52
+ end
53
+ end
54
+
55
+ private
56
+
57
+ # Delegate may_...? and may_...! methods to the user's role.
58
+ def method_missing_with_aegis_permissions(symb, *args)
59
+ method_name = symb.to_s
60
+ if method_name =~ /^may_(.+?)[\!\?]$/
61
+ role.send(symb, self, *args)
62
+ elsif method_name =~ /^(.*?)\?$/ && queried_role = ::Permissions.find_role_by_name($1)
63
+ role == queried_role
64
+ else
65
+ method_missing_without_aegis_permissions(symb, *args)
66
+ end
67
+ end
68
+
69
+ alias_method_chain :method_missing, :aegis_permissions
70
+
71
+ end
72
+
73
+ end
74
+
75
+ end
76
+
77
+ end