In my Rails app, there are 3 models, defined by a has_many :through
association:
class User < ActiveRecord::Base
has_many :administrations
has_many :calendars, through: :administrations
end
class Calendar < ActiveRecord::Base
has_many :administrations
has_many :users, through: :administrations
end
class Administration < ActiveRecord::Base
belongs_to :user
belongs_to :calendar
end
The join Administration
model has a role
attribute, that we use to define the role — Owner, Editor or Viewer — of a given user
for a given calendar
.
Indeed, in the app, a user can be Owner of a calendar, and Viewer of another calendar for instance.
I implemented authentication with Devise.
I have also started implementing authorization with Pundit: authorization is currently working for calendars
, where users
can perform different actions depending on their roles
.
UPDATE: here is the current CalendarPolicy
:
class CalendarPolicy < ApplicationPolicy
attr_reader :user, :calendar
def initialize(user, calendar)
@user = user
@calendar = calendar
end
def index?
user.owner?(calendar) || user.editor?(calendar) || user.viewer?(calendar)
end
def create?
true
end
def show?
user.owner?(calendar) || user.editor?(calendar) || user.viewer?(calendar)
end
def update?
user.owner?(calendar) || user.editor?(calendar)
end
def edit?
user.owner?(calendar) || user.editor?(calendar)
end
def destroy?
user.owner?(calendar)
end
end
Now, I would like to implement a Pundit policy for the Administration model, as follows:
Index
, Show
, Create
, New
, Edit
, Update
and Destroy
actions on the Administrations of this calendar.Index
action to see all the users of a calendar and 2. perform Destroy
action on his own Administration to "leave the calendar".My problem is the following:
user
and a calendar
, as explained above.administration_id
, user_id
and calendar_id
.user
and the actual record
(which would be administration here).On the GitHub page of Pundit, in the Additional context section, we can read the following:
Additional context
Pundit strongly encourages you to model your application in such a way that the only context you need for authorization is a user object and a domain model that you want to check authorization for. If you find yourself needing more context than that, consider whether you are authorizing the right domain model, maybe another domain model (or a wrapper around multiple domain models) can provide the context you need.
Pundit does not allow you to pass additional arguments to policies for precisely this reason.
However, in very rare cases, you might need to authorize based on more context than just the currently authenticated user. Suppose for example that authorization is dependent on IP address in addition to the authenticated user. In that case, one option is to create a special class which wraps up both user and IP and passes it to the policy.
Does a has_many :through
association constitute one of the "very rare cases" mentioned above or is there a simpler way to implement authorization for my Administration
join model?
I don't think, that this is an exceptional case. What's the reason to break good practices, so explicitly stated in the link you provided?
You can just add add_viewer
, add_editor
, remove_viewer
, remove_editor
actions in your CalendarController.
First two can be authorized with your old CalendarPolicy
.
class CalendarPolicy
# old staff here
def add_viewer?
user.is_owner?(calendar)
end
def add_editor?
user.is_owner?(calendar)
end
end
For remove operations you will need AdministrationPolicy
though (I was wrong, saying Calendar policy is enogh):
class AdministrationPolicy
attr_reader :user, :authorization
def remove_viwer?
authorization.viewer? and authorization.user == user
end
def remove_editor?
authorization.editor? and authorization.user == user
end
end