aegis 1.1.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (39) hide show
  1. data/.gitignore +3 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.rdoc +176 -0
  4. data/Rakefile +37 -0
  5. data/VERSION +1 -0
  6. data/aegis.gemspec +92 -0
  7. data/lib/aegis.rb +9 -0
  8. data/lib/aegis/constants.rb +6 -0
  9. data/lib/aegis/has_role.rb +77 -0
  10. data/lib/aegis/normalization.rb +26 -0
  11. data/lib/aegis/permission_error.rb +5 -0
  12. data/lib/aegis/permission_evaluator.rb +34 -0
  13. data/lib/aegis/permissions.rb +108 -0
  14. data/lib/aegis/role.rb +55 -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/config/boot.rb +114 -0
  21. data/test/app_root/config/database.yml +21 -0
  22. data/test/app_root/config/environment.rb +14 -0
  23. data/test/app_root/config/environments/in_memory.rb +0 -0
  24. data/test/app_root/config/environments/mysql.rb +0 -0
  25. data/test/app_root/config/environments/postgresql.rb +0 -0
  26. data/test/app_root/config/environments/sqlite.rb +0 -0
  27. data/test/app_root/config/environments/sqlite3.rb +0 -0
  28. data/test/app_root/config/routes.rb +4 -0
  29. data/test/app_root/db/migrate/20090408115228_create_users.rb +14 -0
  30. data/test/app_root/db/migrate/20090429075648_create_soldiers.rb +16 -0
  31. data/test/app_root/lib/console_with_fixtures.rb +4 -0
  32. data/test/app_root/log/.gitignore +1 -0
  33. data/test/app_root/script/console +7 -0
  34. data/test/has_role_options_test.rb +28 -0
  35. data/test/has_role_test.rb +39 -0
  36. data/test/permissions_test.rb +92 -0
  37. data/test/test_helper.rb +23 -0
  38. data/test/validation_test.rb +49 -0
  39. metadata +111 -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,176 @@
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
+ === Permissions
104
+
105
+ Permissions are specified with the *permission* method and *allow* and *deny*
106
+ permission :do_something do
107
+ allow :role_a, :role_b
108
+ deny :role_c
109
+ end
110
+
111
+ Your user model just received two methods called <b>User#may_do_something?</b>
112
+ and <b>User#may_do_something!</b>. The first one with the ? returns true for users with
113
+ +role_a+ and +role_b+, and false for users with +role_c+. The second one with the ! raises an
114
+ Aegis::PermissionError for +role_c+.
115
+
116
+ === Normalization
117
+
118
+ Aegis will perform some normalization. For example, the permissions
119
+ +edit_something+ and +update_something+ will be the same, each granting both
120
+ <tt>may_edit_something?</tt> and <tt>may_update_something?</tt>. The following normalizations
121
+ are active:
122
+ * edit = update
123
+ * show = list = view = read
124
+ * delete = remove = destroy
125
+
126
+ === Complex permissions (with parameters)
127
+
128
+ *allow* and *deny* can also take a block that may return +true+ or +false+
129
+ indicating if this really applies. So
130
+ permission :pull_april_fools_prank do
131
+ allow :everyone do
132
+ Date.today.month == 4 and Date.today.day == 1
133
+ end
134
+ end
135
+ will generate a <tt>may_pull_april_fools_prank?</tt> method that only returns true on
136
+ April 1.
137
+
138
+ This becomes more useful if you pass parameters to a <tt>may_...?</tt> method, which
139
+ are passed through to the permission block (together with the user object). This
140
+ way you can define more complex permissions like
141
+ permission :edit_post do |current_user, post|
142
+ allow :registered_user do
143
+ post.owner == current_user
144
+ end
145
+ allow :admin
146
+ end
147
+ which will permit admins and post owners to edit posts.
148
+
149
+ === For your convenience
150
+
151
+ As a convenience, if you create a permission ending in a plural 's', this
152
+ automatically includes the singular form. That is, after
153
+ permission :read_posts do
154
+ allow :everyone
155
+ end
156
+ <tt>.may_read_post? @post</tt> will return true, as well.
157
+
158
+ If you want to grant +create_something+, +read_something+, +update_something+
159
+ and +destroy_something+ permissions all at once, just use
160
+ permission :crud_something do
161
+ allow :admin
162
+ end
163
+
164
+ If several permission blocks (or several allow and denies) apply to a certain
165
+ role, the later one always wins. That is
166
+ permission :do_something do
167
+ deny :everyone
168
+ allow :admin
169
+ end
170
+ will work as expected.
171
+
172
+ === Credits
173
+
174
+ Henning Koch, Tobias Kraze
175
+
176
+ {link www.makandra.de}[http://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.1.3
data/aegis.gemspec ADDED
@@ -0,0 +1,92 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ Gem::Specification.new do |s|
4
+ s.name = %q{aegis}
5
+ s.version = "1.1.3"
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-10-15}
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
+ "lib/aegis.rb",
23
+ "lib/aegis/constants.rb",
24
+ "lib/aegis/has_role.rb",
25
+ "lib/aegis/normalization.rb",
26
+ "lib/aegis/permission_error.rb",
27
+ "lib/aegis/permission_evaluator.rb",
28
+ "lib/aegis/permissions.rb",
29
+ "lib/aegis/role.rb",
30
+ "lib/rails/active_record.rb",
31
+ "test/app_root/app/controllers/application_controller.rb",
32
+ "test/app_root/app/models/permissions.rb",
33
+ "test/app_root/app/models/soldier.rb",
34
+ "test/app_root/app/models/user.rb",
35
+ "test/app_root/config/boot.rb",
36
+ "test/app_root/config/database.yml",
37
+ "test/app_root/config/environment.rb",
38
+ "test/app_root/config/environments/in_memory.rb",
39
+ "test/app_root/config/environments/mysql.rb",
40
+ "test/app_root/config/environments/postgresql.rb",
41
+ "test/app_root/config/environments/sqlite.rb",
42
+ "test/app_root/config/environments/sqlite3.rb",
43
+ "test/app_root/config/routes.rb",
44
+ "test/app_root/db/migrate/20090408115228_create_users.rb",
45
+ "test/app_root/db/migrate/20090429075648_create_soldiers.rb",
46
+ "test/app_root/lib/console_with_fixtures.rb",
47
+ "test/app_root/log/.gitignore",
48
+ "test/app_root/script/console",
49
+ "test/has_role_options_test.rb",
50
+ "test/has_role_test.rb",
51
+ "test/permissions_test.rb",
52
+ "test/test_helper.rb",
53
+ "test/validation_test.rb"
54
+ ]
55
+ s.homepage = %q{http://github.com/makandra/aegis}
56
+ s.rdoc_options = ["--charset=UTF-8"]
57
+ s.require_paths = ["lib"]
58
+ s.rubygems_version = %q{1.3.5}
59
+ s.summary = %q{Role-based permissions for your user models.}
60
+ s.test_files = [
61
+ "test/app_root/app/models/permissions.rb",
62
+ "test/app_root/app/models/soldier.rb",
63
+ "test/app_root/app/models/user.rb",
64
+ "test/app_root/app/controllers/application_controller.rb",
65
+ "test/app_root/config/environment.rb",
66
+ "test/app_root/config/environments/mysql.rb",
67
+ "test/app_root/config/environments/postgresql.rb",
68
+ "test/app_root/config/environments/sqlite3.rb",
69
+ "test/app_root/config/environments/in_memory.rb",
70
+ "test/app_root/config/environments/sqlite.rb",
71
+ "test/app_root/config/boot.rb",
72
+ "test/app_root/config/routes.rb",
73
+ "test/app_root/db/migrate/20090429075648_create_soldiers.rb",
74
+ "test/app_root/db/migrate/20090408115228_create_users.rb",
75
+ "test/app_root/lib/console_with_fixtures.rb",
76
+ "test/validation_test.rb",
77
+ "test/test_helper.rb",
78
+ "test/has_role_options_test.rb",
79
+ "test/has_role_test.rb",
80
+ "test/permissions_test.rb"
81
+ ]
82
+
83
+ if s.respond_to? :specification_version then
84
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
85
+ s.specification_version = 3
86
+
87
+ if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
88
+ else
89
+ end
90
+ else
91
+ end
92
+ end
data/lib/aegis.rb ADDED
@@ -0,0 +1,9 @@
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'
9
+
@@ -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
@@ -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