Chrononaut-aegis 1.2.0

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 +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