schrockwell / bodyguard

Simple authorization conventions for Phoenix apps

Home Page:https://hexdocs.pm/bodyguard/

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Provide documentation on how to do nested resources.

archseer opened this issue · comments

I have a nested resource as such:

resources "/companies", CompanyController, only: [:create, :index, :show, :update] do
  resources "/users", UserController, only: [:index]
end

I want the user to be able to view all of the users by the same company, but not any other company's users. The problem is, on a route like /companies/:id/users, the id parameter is ignored and not passed further.

Parameters: %{"company_id" => "e6cda375-72d8-4bd3-9437-112db20dc62e"}

defmodule App.User.Policy do
  # I can't really do validations here, because on index just the User module is passed in, with no other context.
  def can?(_user, :index, User), do: true
end

Should I separately be authorizing the company and then the user/index action? That means I'd have to separately load the company from the db... when in my case I could simply compare current_user.company_id == id

Should I separately be authorizing the company and then the user/index action?

In general, yes. Since this is a nested resource, you should probably be authorizing the parent resource in EVERY action of the child resource, regardless. This is easy to add as a controller plug.

If you're truly just checking the ID, create a struct %Company{id: params["company_id"]} in memory and authorize that directly. Then you don't need to hit the DB since you don't need the entire contents of the schema. If, later on, your authorization needs become more complex, then you can simply modify that plug to load the full Company schema from the DB.

In your particular case, one could argue that if the user is only ever accessing stuff about their current company, that there should be a /my_company route and MyCompany controller that assigns company_id as current_user.company.id, bypassing that authorization check since company_id is no longer user-supplied, and also being more semantic. Then you could also create a matching policy, like MyCompanyUsersPolicy, which would have the specific authorization logic as well as scope information for the index (i.e. only fetch users where their company_id matches current_user.company_id).