Defining abilities in more complex environment wit

2019-07-24 23:23发布

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条回答
老娘就宠你
2楼-- · 2019-07-24 23:55

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
查看更多
登录 后发表回答