Search code examples
ruby-on-railspundit

Rails Company-specific user permissions with Pundit


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

Solution

  • 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