aegis 1.1.3

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