-
Notifications
You must be signed in to change notification settings - Fork 123
Authorization
If you find yourself writing a conditional checking the question, "Is the user allowed to view/do this?" that is an authorization concern. Pet Rescue utilizes the gem Action Policy as our authorization framework. If you are familiar with Pundit, you will see many similarities. If you want to learn more about authorization or have questions about how Action Policy works, their documentation is excellent.
If a policy object is not behaving as you expect, check out the defaults set in application_policy.rb
.
Here are examples with comments explaining how Action Policy is used in Pet Rescue:
Typical policy use in controllers
# app/controllers/organizations/staff/pets_controller.rb
class Organizations::Staff::PetsController < Organizations::BaseController
before_action :set_pet, only: [:show, :edit, :update, :destroy, :attach_images, :attach_files]
# Calling a policy in a setter method is a common pattern.
# We fetch the record and then check the user is authorized for the record.
def set_pet
@pet = Pet.find(params[:id])
# `authorize!` is an Action Policy method.
# `authorize!` has useful defaults that you can find in the docs.
authorize! @pet
# The above code is the same as saying:
# authorize! @pet, context: {user: current_user}, with: Organizations::PetPolicy
end
# Using the authz in the setter method allows us to separate the authorization layer from the body of the action.
def update
if @pet.update(pet_params)
respond_to do |format|
format.html { redirect_to staff_pet_path(@pet), notice: "Pet updated successfully." }
format.turbo_stream if params[:pet][:toggle] == "true"
end
else
render :edit, status: :unprocessable_entity
end
end
end
Note: Pet Rescue controllers are setup in ApplicationController
to require all controller actions to invoke the authorization layer or else ActionPolicy::UnauthorizedAction
will be raised.
Policy use outside their default matched scope
# app/controllers/concerns/organization_scopable.rb
module OrganizationScopable
# `allowed_to?` is a predicate version of `authorize!`.
# In this use case, we are checking multiple policies to see if the user is allowed
# to perform the controller `index` action for either dashboard.
# If they don't have permission to `index` either dashboard, they get sent to a public route.
def after_sign_in_path_for(resource_or_scope)
if allowed_to?(
:index?, with: Organizations::DashboardPolicy,
context: {organization: Current.organization}
)
staff_dashboard_index_path
elsif allowed_to?(
:index?, with: Organizations::AdopterFosterDashboardPolicy,
context: {organization: Current.organization}
)
adopter_fosterer_dashboard_index_path
else
adoptable_pets_path
end
end
end
Policy use in views
<%# app/views/organizations/adoptable_pets/show.html.erb %>
<%# `allowed_to?` is also useful for views! %>
<%# Here we use Action Policy to check if a user is authorized to visit a page %>
<%# to determine if we will render a link to that page. %>
<%# Note how `new_adopter_foster_profile_path` matches with `allowed_to?(:new?, AdopterFosterProfile)` %>
<% elsif allowed_to?(:new?, AdopterFosterProfile) %>
<%= link_to t('.complete_my_profile'), new_adopter_fosterer_profile_path %>
<% end %>
Again, if you want to understand how the policies work, check out the Action Policy documentation.
You can also check out app/models/concerns/authorizable.rb
to see how we are implementing roles and permissions which are used closely with the policies. The PR that added Action Policy to Pet Rescue also has some discussion explaining the implementation.
Most of Pet Rescue's authorization logic outside of Action Policy can be found in app/models/concerns/authorizable.rb
. Here, you will find two other concepts: roles and permissions.
In Action Policy, we currently have four roles:
:adopter
:fosterer
:staff
-
:admin
(staff admin)
Each of these roles is associated with an array of permissions within authorizable.rb
. Permissions are names for business logic in regards to what users can do, such as :create_adopter_applications
.
You should not see or use roles often outside of authorizable.rb
. The primary purpose of roles is to associate Users
with a list of permissions. In turn, you should not see permissions referenced outside of Policies
too much either. The relationship goal is for the other application layers to interact only with policies and their rules.
Note, at the time of this writing, we are using rolify for Role
management but there is some talk of moving away from it (#679).