Defining abilities in more complex environment wit

2019-07-24 23:41发布

问题:

in my rails app (I use devise and cancan), each (registered) user belongs to exactly one role ('Administrator' or 'Users') but to at least one group (something like 'Family', 'Friends', 'Co-workers'). At runtime, when a new folder (see below) is created, a habtm relation to one or many groups can be set, which defines who can access the folder. Selecting no group at all should result in a world-wide accessible folder (i.e. users do not have to be logged in to access these folders). But right now, I don't know yet, how to define such world-wide accessible folders in my ability.rb, because I do not know how to define "can read folders which have no groups associated to it".

The relevant snippet of my app/models/ability.rb looks like this:

user ||= User.new
if user.role? :Administrator
  can :manage, :all
elsif user.role? :Users
  # user should only be able to read folders, whose associated groups they are member of
  can :read, Folder, :groups => { :id => user.group_ids }
else
  # here goes the world-wide-accessible-folders part, I guess
  # but I don't know how to define it:
  ## can :read, Folder, :groups => { 0 } ???
end

The relevant snippet of my app/controllers/folders_controller.rb looks like this:

class FoldersController < ApplicationController
  before_filter :authenticate_user!
  load_and_authorize_resource

Can someone give me a hint?

回答1:

I had the same problem just the other day. I figured out the solution after reading the CanCan readme, which you should do if you haven't yet.

You can view my solution here: Context aware authorization using CanCan

To give you an answer more specific to your use case, do the follow:

In your application controller you need to define some logic which will pick your abilities.

class ApplicationController < ActionController::Base
  check_authorization

  def current_ability
    if <no group selected logic> # Maybe: params[:controller] == 'groups'
      @current_ability = NoGroupSelectedAbility.new(current_user)
    else
      @current_ability = GroupSelectedAbility.new(current_user)
    end
  end

  # Application Controller Logic Below
end

You'll then need to create a new ability (or abilities) in your app/models/ folder. You can also do cool stuff like this:

if request.path_parameters[:controller] == groups
  @current_ability = GroupsAbility.new(current_group_relation)
end 

Where current_group_relation is defined in app/controllers/groups_controller.rb. This will give you specific abilities for specific controllers. Remember that a parent classes can call methods in child classes in Ruby. You can define a method in your controller, and call it from ApplicationController, as long as you are certain what controller is currently being used to handle the request.

Hope that helps.

EDIT: I wanted to show you what a custom ability looks like.

# File path: app/models/group_ability.rb
class GroupsAbility
  include CanCan::Ability

  # This can take in whatever you want, you decide what to argument to 
  # use in your Application Controller
  def initialize(group_relation)
    group_relation ||= GroupRelation.new

    if group_relation.id.nil?
      # User does not have a relation to a group
      can :read, all
    elsif group_relation.moderator?
      # Allow a mod to manage all group relations associated with the mod's group.
      can :manage, :all, :id => group_relation.group.id
    end
  end
end