Skip to content

Authorization

Ken Maeshima edited this page May 10, 2024 · 2 revisions

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.

Examples

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.

Roles and Permissions

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:

  1. :adopter
  2. :fosterer
  3. :staff
  4. :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).