Returns the context for authorization checks in the current controller. Uses the controller_name and prepends any namespaces underscored and joined with underscores.
E.g.
AllThosePeopleController => :all_those_people AnyName::Space::ThingsController => :any_name_space_things
# File lib/declarative_authorization/in_controller.rb, line 553 def decl_auth_context prefixes = name.split('::')[0..-2].map(&:underscore) ((prefixes + [controller_name]) * '_').to_sym end
Defines a filter to be applied according to the authorization of the
current user. Requires at least one symbol corresponding to an action as
parameter. The special symbol :all
refers to all action. The
all :all
statement is only employed if no specific statement
is present.
class UserController < ApplicationController filter_access_to :index filter_access_to :new, :edit filter_access_to :all ... end
The default is to allow access unconditionally if no rule matches. Thus,
including the filter_access_to
:all
statement is
a good idea, implementing a default-deny policy.
When the access is denied, the method permission_denied
is
called on the current controller, if defined. Else, a simple "you are not
allowed" string is output. Log.info is given more information on the
reasons of denial.
def permission_denied flash[:error] = 'Sorry, you are not allowed to the requested page.' respond_to do |format| format.html { redirect_to(:back) rescue redirect_to('/') } format.xml { head :unauthorized } format.js { head :unauthorized } end end
By default, required privileges are inferred from the action name and the
controller name. Thus, in UserController :edit
requires
:edit
users
. To specify required privilege, use
the option :require
filter_access_to :new, :create, :require => :create, :context => :users
Without the :attribute_check
option, no constraints from the
authorization rules are enforced because for some actions (collections,
new
, create
), there is no object to evaluate
conditions against. To allow attribute checks on all actions, it is a
common pattern to provide custom objects through
before_filters
:
class BranchesController < ApplicationController before_filter :load_company before_filter :new_branch_from_company_and_params, :only => [:index, :new, :create] filter_access_to :all, :attribute_check => true protected def new_branch_from_company_and_params @branch = @company.branches.new(params[:branch]) end end
NOTE: before_filters
need to be defined before the first
filter_access_to
call.
For further customization, a custom filter expression may be formulated in a block, which is then evaluated in the context of the controller on a matching request. That is, for checking two objects, use the following:
filter_access_to :merge do permitted_to!(:update, User.find(params[:original_id])) and permitted_to!(:delete, User.find(params[:id])) end
The block should raise a Authorization::AuthorizationError or return false if the access is to be denied.
Later calls to #filter_access_to with overlapping actions overwrite previous ones for that action.
All options:
require
Privilege required; defaults to action_name
context
The privilege's context, defaults to #decl_auth_context, which consists of controller_name, prepended by any namespaces
attribute_check
Enables the check of attributes defined in the authorization rules. Defaults to false. If enabled, #filter_access_to will use a context object from one of the following sources (in that order):
the method from the :load_method
option,
an instance variable named after the singular of the context (by default from the controller name, e.g. @post for PostsController),
a find on the context model, using params[:id]
as id value.
Any of these methods will only be employed if :attribute_check
is enabled.
model
The data model to load a context object from. Defaults to the context, singularized.
load_method
Specify a method by symbol or a Proc object which should be used to load
the object. Both should return the loaded object. If a Proc object is
given, e.g. by way of lambda
, it is called in the instance of
the controller.
Example demonstrating the default behavior:
filter_access_to :show, :attribute_check => true, :load_method => lambda { User.find(params[:id]) }
# File lib/declarative_authorization/in_controller.rb, line 286 def filter_access_to (*args, &filter_block) options = args.last.is_a?(Hash) ? args.pop : {} options = { :require => nil, :context => nil, :attribute_check => false, :model => nil, :load_method => nil }.merge!(options) privilege = options[:require] context = options[:context] actions = args.flatten # prevent setting filter_access_filter multiple times skip_before_filter :filter_access_filter before_filter :filter_access_filter filter_access_permissions.each do |perm| perm.remove_actions(actions) end filter_access_permissions << ControllerPermission.new(actions, privilege, context, options[:attribute_check], options[:model], options[:load_method], filter_block) end
To DRY up the #filter_access_to statements in restful controllers, #filter_resource_access combines typical #filter_access_to and before_filter calls, which set up the instance variables.
The simplest case are top-level resource controllers with only the seven CRUD methods, e.g.
class CompanyController < ApplicationController filter_resource_access def index... end
Here, all CRUD actions are protected through a #filter_access_to
:all statement. :attribute_check
is enabled for all actions
except for the collection action :index
. To have an object
for attribute checks available, #filter_resource_access
will set the instance variable @company
in before filters.
For the member actions (:show
, :edit
,
:update
, :destroy
) @company is set to Company.find(params). For new
actions
(:new
, :create
), #filter_resource_access
creates a new object from company parameters: Company.new(params.
For nested resources, the parent object may be loaded automatically.
class BranchController < ApplicationController filter_resource_access :nested_in => :companies end
Again, the CRUD actions are protected. Now, for all CRUD actions, the
parent object @company is loaded from params. It is also used when creating
@branch for new
actions. Here, attribute_check is enabled for
the collection :index
as well, checking attributes on a
@company.branches.new method.
In many cases, the default seven CRUD actions are not sufficient. As in
the resource definition for routing you may thus give additional member,
new and collection methods. The options
allow you to specify
the required privileges for each action by providing a hash or an array of
pairs. By default, for each action the action name is taken as privilege
(action search in the example below requires the privilege :index
:companies). Any controller action that is not specified and does not
belong to the seven CRUD actions is handled as a member method.
class CompanyController < ApplicationController filter_resource_access :collection => [[:search, :index], :index], :additional_member => {:mark_as_key_company => :update} end
The additional_
* options add to the respective CRUD actions,
the other options (:member
, :collection
,
:new
) replace their respective CRUD actions.
filter_resource_access :member => { :toggle_open => :update }
Would declare :toggle_open as the only member action in the controller and require that permission :update is granted for the current user.
filter_resource_access :additional_member => { :toggle_open => :update }
Would add a member action :toggle_open
to the default members,
such as :show
.
If :collection
is an array of method names #filter_resource_access
will associate a permission with the method that is the same as the method
name and no attribute checks will be performed unless
:attribute_check => true
is added in the options.
You can override the default object loading by implementing any of the
following instance methods on the controller. Examples are given for the
BranchController (with nested_in
set to
:companies
):
new_branch_from_params
Used for new
actions.
new_branch_for_collection
Used for collection
actions if the nested_in
option is set.
load_branch
Used for member
actions.
load_company
Used for all new
, member
, and
collection
actions if the nested_in
option is
set.
All options:
member
Member methods are actions like show
, which have an params from which to load the controller object and
assign it to @controller_name, e.g. @branch
.
By default, member actions are [:show
, :edit
,
:update
, :destroy
]. Also, any action not
belonging to the seven CRUD actions are handled as member actions.
There are three different syntax to specify member, collection and new actions.
Hash: Lets you set the required privilege for each action:
{:show
=> :show
,
:mark_as_important
=> :update
}
Array of actions or pairs: [:show
,
[:mark_as_important
, :update
]], with single
actions requiring the privilege of the same name as the method.
Single method symbol: :show
additional_member
Allows to add additional member actions to the default resource
member
actions.
collection
Collection actions are like :index
, actions without any
controller object to check attributes of. If nested_in
is
given, a new object is created from the parent object, e.g.
@company.branches.new. Without nested_in
, attribute check is
deactivated for these actions. By default, collection is set to
:index
.
additional_collection
Allows to add additional collection actions to the default resource
collection
actions.
new
new
methods are actions such as new
and
create
, which don't receive a params
to load an object from, but a params hash with attributes for
a new object. The attributes will be used here to create a new object and
check the object against the authorization rules. The object is assigned
to @controller_name_singular, e.g. @branch.
If nested_in
is given, the new object is created from the
parent_object.controller_name proxy, e.g. company.branches.new(params). By default,
new
is set to [:new, :create].
additional_new
Allows to add additional new actions to the default resource
new
actions.
context
The context is used to determine the model to load objects from for the before_filters and the context of privileges to use in authorization checks.
nested_in
Specifies the parent controller if the resource is nested in another one.
This is used to automatically load the parent object, e.g.
@company
from params for a
BranchController nested in a CompanyController.
shallow
Only relevant when used in conjunction with nested_in
.
Specifies a nested resource as being a shallow nested resource, resulting
in the controller not attempting to load a parent object for all member
actions defined by member
and additional_member
or rather the default member actions (:show
,
:edit
, :update
, :destroy
).
no_attribute_check
Allows to set actions for which no attribute check should be performed. See
#filter_access_to
on details. By default, with no nested_in
,
no_attribute_check
is set to all collections. If
nested_in
is given no_attribute_check
is empty by
default.
# File lib/declarative_authorization/in_controller.rb, line 464 def filter_resource_access(options = {}) options = { :new => [:new, :create], :additional_new => nil, :member => [:show, :edit, :update, :destroy], :additional_member => nil, :collection => [:index], :additional_collection => nil, #:new_method_for_collection => nil, # only symbol method name #:new_method => nil, # only symbol method name #:load_method => nil, # only symbol method name :no_attribute_check => nil, :context => nil, :nested_in => nil, }.merge(options) new_actions = actions_from_option( options[:new] ).merge( actions_from_option(options[:additional_new]) ) members = actions_from_option(options[:member]).merge( actions_from_option(options[:additional_member])) collections = actions_from_option(options[:collection]).merge( actions_from_option(options[:additional_collection])) options[:no_attribute_check] ||= collections.keys unless options[:nested_in] unless options[:nested_in].blank? load_parent_method = :"load_#{options[:nested_in].to_s.singularize}" shallow_exceptions = options[:shallow] ? {:except => members.keys} : {} before_filter shallow_exceptions do |controller| if controller.respond_to?(load_parent_method, true) controller.send(load_parent_method) else controller.send(:load_parent_controller_object, options[:nested_in]) end end new_for_collection_method = :"new_#{controller_name.singularize}_for_collection" before_filter :only => collections.keys do |controller| # new_for_collection if controller.respond_to?(new_for_collection_method, true) controller.send(new_for_collection_method) else controller.send(:new_controller_object_for_collection, options[:context] || controller_name, options[:nested_in]) end end end new_from_params_method = :"new_#{controller_name.singularize}_from_params" before_filter :only => new_actions.keys do |controller| # new_from_params if controller.respond_to?(new_from_params_method, true) controller.send(new_from_params_method) else controller.send(:new_controller_object_from_params, options[:context] || controller_name, options[:nested_in]) end end load_method = :"load_#{controller_name.singularize}" before_filter :only => members.keys do |controller| # load controller object if controller.respond_to?(load_method, true) controller.send(load_method) else controller.send(:load_controller_object, options[:context] || controller_name) end end filter_access_to :all, :attribute_check => true, :context => options[:context] members.merge(new_actions).merge(collections).each do |action, privilege| if action != privilege or (options[:no_attribute_check] and options[:no_attribute_check].include?(action)) filter_options = { :context => options[:context], :attribute_check => !options[:no_attribute_check] || !options[:no_attribute_check].include?(action) } filter_options[:require] = privilege if action != privilege filter_access_to(action, filter_options) end end end