Module: Authorization::AuthorizationInController::ClassMethods

Defined in:
lib/declarative_authorization/in_controller.rb

Instance Method Summary collapse

Instance Method Details

#all_filter_access_permissionsObject

Collecting all the ControllerPermission objects from the controller hierarchy. Permissions for actions are overwritten by calls to filter_access_to in child controllers with the same action.



280
281
282
283
284
285
286
287
288
289
290
291
# File 'lib/declarative_authorization/in_controller.rb', line 280

def all_filter_access_permissions # :nodoc:
  ancestors.inject([]) do |perms, mod|
    if mod.respond_to?(:filter_access_permissions)
      perms + 
        mod.filter_access_permissions.collect do |p1| 
          p1.clone.remove_actions(perms.inject(Set.new) {|actions, p2| actions + p2.actions})
        end
    else
      perms
    end
  end
end

#filter_access_to(*args, &filter_block) ⇒ Object

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 infered 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)) and permitted_to!(:delete, User.find(params)) 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 controller_name, pluralized. [:+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 behaviour: filter_access_to :show, :attribute_check => true, :load_method => lambda { User.find(params) }


248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
# File 'lib/declarative_authorization/in_controller.rb', line 248

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

  # collect permits in controller array for use in one before_filter
  unless filter_chain.any? {|filter| filter.method == :filter_access_filter}
    before_filter :filter_access_filter
  end
  
  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

#filter_resource_access(options = {}) ⇒ Object

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 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+]. [:+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 collaction 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[:branch]).  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. [:+no_attribute_check+] Allows to set actions for which no attribute check should be perfomed. 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.



383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
# File 'lib/declarative_authorization/in_controller.rb', line 383

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_method = :"load_#{options[:nested_in].to_s.singularize}"
    before_filter do |controller|
      if controller.respond_to?(load_method)
        controller.send(load_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)
        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)
      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)
      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