acts_as_permission 1.2.0 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,4 @@
1
+ *.gem
2
+ .bundle
3
+ Gemfile.lock
4
+ pkg/*
data/.rvmrc ADDED
@@ -0,0 +1 @@
1
+ rvm ruby-1.9.2@acts_as_permission
data/Gemfile ADDED
@@ -0,0 +1,11 @@
1
+ source "http://rubygems.org"
2
+
3
+ case ENV["MODEL_ADAPTER"]
4
+ when nil, "active_record"
5
+ gem "sqlite3"
6
+ gem "activerecord", :require => "active_record"
7
+ else
8
+ raise "Unknown model adapter: #{ENV["MODEL_ADAPTER"]}"
9
+ end
10
+
11
+ gemspec
data/MIT-LICENSE CHANGED
@@ -1,4 +1,4 @@
1
- Copyright (c) 2009 Cyril Wack
1
+ Copyright (c) 2009-2011 Cyril Wack
2
2
 
3
3
  Permission is hereby granted, free of charge, to any person obtaining
4
4
  a copy of this software and associated documentation files (the
data/README.rdoc CHANGED
@@ -1,49 +1,296 @@
1
1
  = Acts as permission
2
2
 
3
- This Rails plugin provides a list of permissions attached to a resource, according to ACL concept. Permissions determine specific access rights for a resource depending of its controller actions such as: show, edit, update, destroy, evaluated from the given resource and such as: index, new, create, evaluated from the parent of the given resource.
3
+ Acts as permission is a plugin for Ruby on Rails that allows to assign a list of
4
+ permissions on an object, according to the ACL concept, where each permission
5
+ can be extended to a subject.
4
6
 
5
- Each permission field attributed to a resource can be defined as true, false or nil. When a resource permission is set as nil, the same permission is searched from its nearest relative to its parent farthest and the first given boolean value is returned. If permission is nil for a resource and its potential parents, then the method returns false.
7
+ More specifically, it can make possible to allow or to deny any action of the
8
+ controller of a protected resource. These actions are called permittables.
6
9
 
7
- == Install
10
+ A permittable action can be directly attached to a resource. Examples of such
11
+ actions:
8
12
 
9
- Install the gem (recommended):
13
+ * <tt>show</tt>,
14
+ * <tt>edit</tt>,
15
+ * <tt>update</tt>,
16
+ * <tt>destroy</tt>.
10
17
 
11
- sudo gem install acts_as_permission
18
+ Or it can be indirectly, through a parent resource. Examples:
12
19
 
13
- Alternatively, you can install it as a Rails plugin:
20
+ * <tt>index</tt>,
21
+ * <tt>new</tt>,
22
+ * <tt>create</tt>.
14
23
 
15
- script/plugin install git://github.com/cyril/acts_as_permission.git
24
+ Here is an example of query to a direct article's action:
16
25
 
17
- Generate and apply the migration:
26
+ @article = Article.find(params[:id])
27
+ @article.permission?("articles#destroy") # => false
18
28
 
19
- script/generate acts_as_permission model
20
- rake db:migrate
29
+ Same query, extended to a user:
30
+
31
+ @article.permission?("articles#destroy", @bob) # => nil
32
+ @article.permission?("articles#destroy", @admin) # => true
33
+
34
+ A query example on an indirect articles' action, through a category:
35
+
36
+ @category = Category.find(params[:category_id])
37
+ @category.permission?("articles#index") # => true
38
+
39
+ Other examples, on unpermittable actions:
40
+
41
+ @category.permission?("articles#read") # => nil
42
+ @category.permission?("silk_routes#index") # => nil
43
+
44
+ The value of a permission depends on its context, which includes a route and an
45
+ optional extension to a permitted resource.
46
+
47
+ The <tt>permission?(route, ext = nil)</tt> query may return, depending on the
48
+ context:
49
+
50
+ * <tt>true</tt>, if the permission is allowed;
51
+ * <tt>false</tt>, if the permission is denied;
52
+ * <tt>nil</tt>, if the permission is indefinable (resulting of the unknowned
53
+ context).
54
+
55
+ == Philosophy
56
+
57
+ General library that does only one thing, without any feature.
58
+
59
+ == Installation
60
+
61
+ Include the gem in your <tt>Gemfile</tt>:
62
+
63
+ gem 'acts_as_permission'
21
64
 
22
- == Example
65
+ And run the +bundle+ command. Or as a plugin:
23
66
 
24
- script/generate acts_as_permission Category
25
- script/generate acts_as_permission Article show edit update destroy
67
+ rails plugin install git://github.com/cyril/acts_as_permission.git
68
+
69
+ Then, generate files and apply the migration:
70
+
71
+ rails generate permissions
26
72
  rake db:migrate
27
73
 
28
- class Category < ActiveRecord::Base
29
- acts_as_permission
74
+ == Getting started
75
+
76
+ === Configuring models
77
+
78
+ Permittable models have to be declared with <tt>acts_as_permission</tt>. And
79
+ they have to be so with a default permission mask. For example:
80
+
81
+ # app/models/article.rb
82
+ class Article < ActiveRecord::Base
83
+ acts_as_permission({
84
+ 'articles#show' => [true, {}],
85
+ 'articles#edit' => [false, {
86
+ :permitted_id => 1,
87
+ :permitted_type => "User",
88
+ :value => true }],
89
+ 'articles#update' => [false, {
90
+ :permitted_id => 1,
91
+ :permitted_type => "User",
92
+ :value => true }],
93
+ 'articles#destroy' => [false, {
94
+ :permitted_id => 1,
95
+ :permitted_type => "User",
96
+ :value => true }],
97
+ 'comments#index' => true,
98
+ 'comments#new' => [true, [{
99
+ :permitted_id => 3,
100
+ :permitted_type => "User",
101
+ :value => false }]],
102
+ 'comments#create' => [true, [{
103
+ :permitted_id => 3,
104
+ :permitted_type => "User",
105
+ :value => false }]]})
106
+
30
107
  belongs_to :user
31
- has_many :articles
108
+ has_many :comments, :dependent => :destroy
32
109
  end
33
110
 
34
- class Article < ActiveRecord::Base
35
- acts_as_permission :category
36
- belongs_to :category
111
+ # app/models/comment.rb
112
+ class Comment < ActiveRecord::Base
113
+ acts_as_permission([
114
+ ["comments#show", true],
115
+ ["comments#edit", [false, [
116
+ {:permitted_id => 1, :permitted_type => "User", :value => true},
117
+ {:permitted_id => 2, :permitted_type => "User", :value => true} ]]],
118
+ ["comments#update", [false, [
119
+ {:permitted_id => 1, :permitted_type => "User", :value => true},
120
+ {:permitted_id => 2, :permitted_type => "User", :value => true} ]]],
121
+ ["comments#destroy", [false, {
122
+ :permitted_id => 1,
123
+ :permitted_type => "User",
124
+ :value => true }]]])
125
+
126
+ belongs_to :article
37
127
  belongs_to :user
38
128
  end
39
129
 
40
- # Check permission to list articles of the given category:
41
- @category.has_permission?('index')
130
+ Optionally, some models (such as <tt>User</tt>, <tt>Group</tt>, <tt>Role</tt>)
131
+ can also be declared as permitted with <tt>is_able_to_be_permitted</tt>.
132
+ Example:
133
+
134
+ # app/models/user.rb
135
+ class User < ActiveRecord::Base
136
+ is_able_to_be_permitted
137
+
138
+ with_options :dependent => :destroy do |opts|
139
+ opts.has_many :articles
140
+ opts.has_many :comments
141
+ end
142
+ end
143
+
144
+ === Configuring controllers
145
+
146
+ Example of a fully protected comments controller:
147
+
148
+ class CommentsController < ApplicationController
149
+ before_filter :check_permissions
150
+
151
+ # GET /comments
152
+ # GET /comments.xml
153
+ def index
154
+ @comments = current_resource.comments
155
+
156
+ respond_to do |format|
157
+ format.html # index.html.erb
158
+ format.xml { render :xml => @comments }
159
+ end
160
+ end
161
+
162
+ # GET /comments/1
163
+ # GET /comments/1.xml
164
+ def show
165
+ @comment = current_resource
166
+
167
+ respond_to do |format|
168
+ format.html # show.html.erb
169
+ format.xml { render :xml => @comment }
170
+ end
171
+ end
172
+
173
+ # GET /comments/new
174
+ # GET /comments/new.xml
175
+ def new
176
+ @comment = current_resource.comments.build
177
+
178
+ respond_to do |format|
179
+ format.html # new.html.erb
180
+ format.xml { render :xml => @comment }
181
+ end
182
+ end
183
+
184
+ # GET /comments/1/edit
185
+ def edit
186
+ @comment = current_resource
187
+ end
188
+
189
+ # POST /comments
190
+ # POST /comments.xml
191
+ def create
192
+ @comment = current_resource.comments.build(params[:comment])
193
+
194
+ respond_to do |format|
195
+ if @comment.save
196
+ format.html { redirect_to(@comment,
197
+ :notice => 'Comment was successfully created.') }
198
+ format.xml { render :xml => @comment, :status => :created,
199
+ :location => @comment }
200
+ else
201
+ format.html { render :action => "new" }
202
+ format.xml { render :xml => @comment.errors,
203
+ :status => :unprocessable_entity }
204
+ end
205
+ end
206
+ end
207
+
208
+ # PUT /comments/1
209
+ # PUT /comments/1.xml
210
+ def update
211
+ @comment = current_resource
212
+
213
+ respond_to do |format|
214
+ if @comment.update_attributes(params[:comment])
215
+ format.html { redirect_to(@comment,
216
+ :notice => 'Comment was successfully updated.') }
217
+ format.xml { head :ok }
218
+ else
219
+ format.html { render :action => "edit" }
220
+ format.xml { render :xml => @comment.errors,
221
+ :status => :unprocessable_entity }
222
+ end
223
+ end
224
+ end
225
+
226
+ # DELETE /comments/1
227
+ # DELETE /comments/1.xml
228
+ def destroy
229
+ @comment = current_resource
230
+ @comment.destroy
231
+
232
+ respond_to do |format|
233
+ format.html { redirect_to(comments_url) }
234
+ format.xml { head :ok }
235
+ end
236
+ end
237
+
238
+ protected
239
+
240
+ def check_permissions
241
+ route = [ params[:controller],
242
+ params[:action] ].join('#')
243
+
244
+ unless (current_user &&
245
+ current_resource.permission?(route, current_user)) ||
246
+ current_resource.permission?(route)
247
+ respond_to do |format|
248
+ format.html { redirect_to(:back, :warning => '403 Forbidden',
249
+ :status => :forbidden) }
250
+ format.xml { render :xml => '403 Forbidden', :status => :forbidden }
251
+ end
252
+ end
253
+ end
254
+
255
+ def current_resource
256
+ @current_resource ||= if params[:id]
257
+ Comment.find(params[:id])
258
+ else
259
+ Article.find(params[:article_id], :readonly => true)
260
+ end
261
+ end
262
+ end
263
+
264
+ === Configuring views
265
+
266
+ We can now perform some checks on related views from a comment instance, thanks
267
+ to the protected actions of its controller, in order to only display allowed
268
+ links:
269
+
270
+ <% if current_user && @comment.permission?("comments#edit", current_user) ||
271
+ @comment.permission?("comments#edit") %>
272
+ <%= link_to "Edit comment",
273
+ edit_article_comment_path(@comment.article, @comment) %>
274
+ <% end %>
275
+
276
+ And also some indirect checks from the current article instance, like this one:
277
+
278
+ <% if current_user && @article.permission?("comments#index", current_user) ||
279
+ @article.permission?("comments#index") %>
280
+ <%= link_to "Comments", article_comments_path(@article) %>
281
+ <% end %>
282
+
283
+ Or this other one:
284
+
285
+ <% if current_user && @article.permission?("comments#new", current_user) ||
286
+ @article.permission?("comments#new") %>
287
+ <%= link_to "New comment", new_article_comment_path(@article) %>
288
+ <% end %>
289
+
290
+ ==== Form helper
42
291
 
43
- # Check permission to destroy the given article:
44
- @article.has_permission?('destroy')
292
+ Object's permissions management is as simple as:
45
293
 
46
- # Form helper that generate permissions fields for each article:
47
- <%= permission_fields :article %>
294
+ <%= permission_fields f %>
48
295
 
49
- Copyright (c) 2009 Cyril Wack, released under the MIT license
296
+ Copyright (c) 2009-2011 Cyril Wack, released under the MIT license
data/Rakefile CHANGED
@@ -1,12 +1,12 @@
1
- require 'rubygems'
2
- require 'rake'
3
- require 'echoe'
1
+ require 'bundler'
2
+ Bundler::GemHelper.install_tasks
4
3
 
5
- Echoe.new('acts_as_permission', '1.2.0') do |p|
6
- p.description = "Simple Rails plugin to assign a list of permissions on a resource."
7
- p.url = "http://github.com/cyril/acts_as_permission"
8
- p.author = "Cyril Wack"
9
- p.email = "cyril.wack@gmail.com"
10
- p.ignore_pattern = ["tmp/*", "script/*"]
11
- p.development_dependencies = []
4
+ require 'rake/testtask'
5
+
6
+ Rake::TestTask.new do |t|
7
+ t.libs << 'lib'
8
+ t.libs << 'test'
9
+ t.test_files = FileList["test/**/*_{helper,test}.rb"]
12
10
  end
11
+
12
+ task :default => :test
data/VERSION.yml CHANGED
@@ -1,4 +1,4 @@
1
1
  ---
2
- :major: 1
3
- :minor: 2
2
+ :major: 2
3
+ :minor: 0
4
4
  :patch: 0
@@ -1,32 +1,18 @@
1
- # -*- encoding: utf-8 -*-
2
-
3
1
  Gem::Specification.new do |s|
4
- s.name = %q{acts_as_permission}
5
- s.version = "1.2.0"
6
-
7
- s.required_rubygems_version = Gem::Requirement.new(">= 1.2") if s.respond_to? :required_rubygems_version=
8
- s.authors = ["Cyril Wack"]
9
- s.cert_chain = ["/Users/cyril/gem-public_cert.pem"]
10
- s.date = %q{2010-04-12}
2
+ s.name = "acts_as_permission"
3
+ s.version = Psych.load_file("VERSION.yml").values.join('.')
4
+ s.platform = Gem::Platform::RUBY
5
+ s.authors = ["Cyril Wack"]
6
+ s.email = ["cyril@gosu.fr"]
7
+ s.homepage = "http://github.com/cyril/acts_as_permission"
8
+ s.summary = %q{Simple permission solution for Rails.}
11
9
  s.description = %q{Simple Rails plugin to assign a list of permissions on a resource.}
12
- s.email = %q{cyril.wack@gmail.com}
13
- s.extra_rdoc_files = ["README.rdoc", "lib/acts_as_permission.rb", "lib/permissions_helper.rb"]
14
- s.files = ["MIT-LICENSE", "README.rdoc", "Rakefile", "VERSION.yml", "generators/acts_as_permission/USAGE", "generators/acts_as_permission/acts_as_permission_generator.rb", "init.rb", "lib/acts_as_permission.rb", "lib/permissions_helper.rb", "Manifest", "acts_as_permission.gemspec"]
15
- s.homepage = %q{http://github.com/cyril/acts_as_permission}
16
- s.rdoc_options = ["--line-numbers", "--inline-source", "--title", "Acts_as_permission", "--main", "README.rdoc"]
17
- s.require_paths = ["lib"]
18
- s.rubyforge_project = %q{acts_as_permission}
19
- s.rubygems_version = %q{1.3.6}
20
- s.signing_key = %q{/Users/cyril/gem-private_key.pem}
21
- s.summary = %q{Simple Rails plugin to assign a list of permissions on a resource.}
22
10
 
23
- if s.respond_to? :specification_version then
24
- current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
25
- s.specification_version = 3
11
+ s.rubyforge_project = "acts_as_permission"
26
12
 
27
- if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
28
- else
29
- end
30
- else
31
- end
13
+ s.add_runtime_dependency "railties", ">= 3.0.0"
14
+
15
+ s.files = `git ls-files`.split("\n")
16
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
17
+ s.require_paths = ["lib"]
32
18
  end
@@ -0,0 +1,35 @@
1
+ <%= content_tag :fieldset, :id => "#{f.object}_permissions" do %>
2
+ <%= content_tag :legend, t('.legend') %>
3
+
4
+ <%= f.fields_for :permissions do |permission_fields| %>
5
+ <p>
6
+ <strong>
7
+ <%= permission_fields.object.route %>
8
+
9
+ <% if permission_fields.object.permitted.present? %>
10
+ ; <%= [ permission_fields.object.permitted.type,
11
+ permission_fields.object.permitted.id ].join('-') %>
12
+ <% end %>
13
+ </strong>
14
+ </p>
15
+
16
+ <p>
17
+ <label>
18
+ <%= permission_fields.radio_button(:value, true) %>
19
+ <%= t('.verb.allow') %>
20
+ </label>
21
+
22
+ <label>
23
+ <%= permission_fields.radio_button(:value, false) %>
24
+ <%= t('.verb.deny') %>
25
+ </label>
26
+
27
+ <br />
28
+
29
+ <label>
30
+ <%= t('.destroy') %>:
31
+ <%= permission_fields.check_box :_destroy %>
32
+ </label>
33
+ </p>
34
+ <% end %>
35
+ <% end %>
@@ -0,0 +1,8 @@
1
+ en:
2
+ acts_as_permission:
3
+ permission_fields:
4
+ legend: "Permissions"
5
+ verb:
6
+ allow: "allow"
7
+ deny: "deny"
8
+ destroy: "Delete"
@@ -0,0 +1,8 @@
1
+ fr:
2
+ acts_as_permission:
3
+ permission_fields:
4
+ legend: "Permissions"
5
+ verb:
6
+ allow: "autoriser"
7
+ deny: "refuser"
8
+ destroy: "Supprimer"
@@ -0,0 +1,8 @@
1
+ ja:
2
+ acts_as_permission:
3
+ permission_fields:
4
+ legend: "パーミッション"
5
+ verb:
6
+ allow: "許可する"
7
+ deny: "否定する"
8
+ destroy: "削除する"
@@ -1,51 +1,136 @@
1
- require 'active_record/base'
2
-
3
1
  module ActsAsPermission
4
- def self.included(base)
5
- base.extend(ClassMethods)
6
- end
2
+ module Permittable
3
+ def permission(route, ext = nil)
4
+ ext = {
5
+ :permitted_type => (ext.blank? ? nil : ext.class.name),
6
+ :permitted_id => (ext.blank? ? nil : ext.id) } unless ext.is_a? Hash
7
+
8
+ context = {:route => route}.merge(ext)
7
9
 
8
- module ClassMethods
9
- attr_accessor :parental_resource_permission
10
+ create_default_permission!(route, ext) unless permissions.exists?(context)
10
11
 
11
- def acts_as_permission(resource = nil)
12
- attr_accessible :index_permission, :new_permission, :create_permission, :show_permission, :edit_permission, :update_permission, :destroy_permission
13
- before_save :complete_permissions_if_needed
12
+ permissions.first(:conditions => context)
13
+ end
14
+
15
+ def permission?(route, ext = nil)
16
+ permission = permission(route, ext)
17
+ permission.value? if permission.present?
18
+ end
14
19
 
15
- self.parental_resource_permission = resource
20
+ def create_permission!(route, value, ext = nil)
21
+ return unless self.class.permittable?(route)
16
22
 
17
- class_eval <<-EOV
18
- include ActsAsPermission::InstanceMethods
23
+ ext = {
24
+ :permitted_type => (ext.blank? ? nil : ext.class.name),
25
+ :permitted_id => (ext.blank? ? nil : ext.id) } unless ext.is_a? Hash
19
26
 
20
- def self.could_have_permission_from_the_parent_resource?
21
- !self.parental_resource_permission.nil?
27
+ context = {:route => route}.merge(ext)
28
+ parameters = context.merge(:value => value)
29
+
30
+ permissions.create(parameters) unless permissions.exists?(context)
31
+ end
32
+
33
+ def create_default_permissions!
34
+ permissions = self.class.permissions.map do |route, masks|
35
+ masks.map do |mask|
36
+ ext = mask.dup
37
+ value = ext.delete(:value)
38
+
39
+ create_permission!(route, value, ext)
22
40
  end
23
- EOV
41
+ end
42
+
43
+ permissions.flatten!
44
+ permissions.compact
45
+ end
46
+
47
+ def mass_assignment_authorizer
48
+ super + [:permissions_attributes]
24
49
  end
25
- end
26
50
 
27
- module InstanceMethods
28
51
  def has_permission?(action)
29
- if self.respond_to?("#{action}_permission")
30
- return self.send("#{action}_permission") unless self.send("#{action}_permission").nil?
52
+ ActiveSupport::Deprecation.warn 'has_permission?(action) is deprecated ' +
53
+ 'and may be removed from future releases, use permission?(route, ext ' +
54
+ '= nil) instead.'
55
+
56
+ permission? [self.class.name.tableize, action].join('#')
57
+ end
58
+
59
+ protected
60
+
61
+ def create_default_permission!(route, ext = nil)
62
+ return unless self.class.permittable?(route)
63
+
64
+ ext = {
65
+ :permitted_type => (ext.blank? ? nil : ext.class.name),
66
+ :permitted_id => (ext.blank? ? nil : ext.id) } unless ext.is_a? Hash
67
+
68
+ masks = self.class.permissions[route.to_sym].select do |mask|
69
+ mask[:permitted_id] == ext[:permitted_id] &&
70
+ mask[:permitted_type] == ext[:permitted_type]
31
71
  end
32
72
 
33
- if self.class.could_have_permission_from_the_parent_resource?
34
- self.send(self.class.parental_resource_permission).has_permission?(action)
35
- else
36
- false
73
+ if (mask = masks.first).present?
74
+ parameters = mask.merge({:route => route.to_s})
75
+ permissions.create(parameters)
37
76
  end
38
77
  end
78
+ end
39
79
 
40
- private
41
-
42
- def complete_permissions_if_needed
43
- self.update_permission = self.edit_permission if self.respond_to?('edit_permission') && self.respond_to?('update_permission')
44
- self.create_permission = self.new_permission if self.respond_to?('new_permission') && self.respond_to?('create_permission')
45
- true
80
+ module Permitted
81
+ def permitted?(object, route)
82
+ object.permission?(route, self)
46
83
  end
47
84
  end
48
85
  end
49
86
 
50
- ActiveRecord::Base.class_eval { include ActsAsPermission }
51
- ActionController::Base.helper PermissionsHelper
87
+ class ActiveRecord::Base
88
+ def self.is_able_to_be_permitted
89
+ has_many :permissions, :as => :permitted, :dependent => :destroy
90
+ validates_associated :permissions
91
+
92
+ include ActsAsPermission::Permitted
93
+ end
94
+
95
+ def self.acts_as_permission(acl)
96
+ @acl = acl.to_a.inject({}) do |list, permission|
97
+ route, masks = permission.to_a[0], permission.to_a[1]
98
+ masks = [masks] unless masks.is_a?(Array)
99
+
100
+ permission = {:value => masks.first}
101
+ permission.freeze
102
+
103
+ extensions = masks[1] || []
104
+ extensions = [extensions] unless extensions.is_a?(Array)
105
+ extensions.map! do |ext|
106
+ ext ||= {}
107
+ ext.freeze
108
+ end
109
+
110
+ permissions = extensions.unshift(permission)
111
+ permissions.compact!
112
+ permissions.uniq!
113
+ permissions.freeze
114
+
115
+ list.update({route.to_sym => permissions})
116
+ end
117
+
118
+ @acl.freeze
119
+
120
+ has_many :permissions, :as => :permittable, :dependent => :destroy
121
+ accepts_nested_attributes_for :permissions, :allow_destroy => true
122
+ validates_associated :permissions
123
+
124
+ class << self
125
+ def permittable?(route)
126
+ @acl.has_key?(route.to_sym)
127
+ end
128
+
129
+ def permissions
130
+ @acl
131
+ end
132
+ end
133
+
134
+ include ActsAsPermission::Permittable
135
+ end
136
+ end
@@ -0,0 +1,8 @@
1
+ Description:
2
+ Copies the following files
3
+ - create_permissions.rb to db/migrate/
4
+ - permission.rb to app/models/
5
+ - permissions_helper.rb to app/helpers/
6
+
7
+ Usage:
8
+ `rails generate permissions`