2

I'm actually not sure if this is a Pundit or general permissions architectural problem, but I setup a simple Pundit policy to restrict the actions a member within a company can perform. Users are joined as a Member to a company in a has_many, through: relationship. The Member model has a role attribute of owner or user.

Given a User that is a member of a Store, how can I restrict the access in a controller for the User's association to the Store? Below is a Admin::MembersController where a store owner can invite other members. How can I restrict this to the given User in pundit through their member association to the store? The policy below doesn't work, returning an array of records. If I were to check against only the first record it works but I feel that is because of my limited understanding.

All of the tutorials and documentation I see online for CCC and Pundit involve application-wide permissions. But I need more granular control.

For example, my application has hundreds of companies. Each company has a user who is an "owner" and they login each day to look at their earnings information. That owner/user wants to invite Joe Smith to the application so they can also look at the data and make changes. But they don't want Joe Smith to be able to see certain types of data. So we restrict Joe Smith's access to certain data for that company.

class Admin::MembersController < Admin::BaseController

  def index
    @company_members = current_company.members
    authorize([:admin, @company_members])
  end
end

Policy

class Admin::MemberPolicy < ApplicationPolicy

  def index?
    return [ record.user_id, record.store_id ].include? user.id
    ## this works return [ record.first.user_id, record.first.store_id ].include? user.id
  end
end

User.rb

class User < ApplicationRecord
  # Automatically remove the associated `members` join records
  has_many :members, dependent: :destroy
  has_many :stores, through: :members
end

Member.rb

class Member < ApplicationRecord
  belongs_to :store
  belongs_to :user

  enum role: [ :owner, :user ]
end

Store.rb

class Store < ApplicationRecord
  has_many :members
  has_many :users, through: :members
end
VegaStudios
  • 378
  • 1
  • 4
  • 22
  • Can you explain what do you mean by restricting access to association? From what I understand maybe you want to run different queries based on roles to hide certain data from user but show that to the owner – asimhashmi Aug 27 '19 at 05:34
  • @AsimHashmi Exactly what you mentioned. The attribute that defines the `owner` is a `role` on the Member model (which is a join model) and causing the confusing with Pundit – VegaStudios Aug 27 '19 at 05:46

2 Answers2

1

I got some insight from the contributors on Pundit; the most reasonable way to go about it this is to use a domain object which represents the context that a user is in - there is information about this in the Readme (https://github.com/varvet/pundit#additional-context). The UserContext object will provide references to a user and organization.

class ApplicationController
  include Pundit

  def pundit_user
    if session[:organization_id]
      UserContext.new(current_user, Organization.find(session[:organization_id]))
    else
      UserContext.new(current_user)
    end
  end
end

class UserContext
  attr_reader :user, :organization

  def initialize(user, organization = nil)
    @user           = user
    @organization   = organization
  end
end
VegaStudios
  • 378
  • 1
  • 4
  • 22
0

I think what you are looking for is scopes in pundit. You want to restrict certain data access to members of store and show that data to owner of that store.

For that purpose you need to change your query according to the user role

Something like this:

class EarningPolicy < ApplicationPolicy
  class Scope
    attr_reader :user, :scope

    def initialize(user, scope)
      @user  = user
      @scope = scope
    end

    def resolve
      # here check for owner and show all data
      if user.members.owner # here you query your memberships to find the owner role.
        scope.all
      else
        # here show only data relevant to member
        scope.where(published: true)
      end
    end
  end
end

You can now use this class like this in your controller

def index
  @earnings = earning_scope(Earning)
end

Hope it helps

asimhashmi
  • 4,258
  • 1
  • 14
  • 34
  • Thanks for the response @AsimHashmi. This is a good start. But the role attribute isn't on the user - it's on the joined Member model and a user can have many memberships. I'm unsure how to drill down to access the current users membership. In CanCan you can pass a third argument for something like `store_id` – VegaStudios Aug 27 '19 at 14:34
  • @VegaStudios you can query your user model to find the owner membership and if it doesn't exists, then you can restrict the data being shown to user. – asimhashmi Aug 31 '19 at 06:53