It’s straightforward and slick to prevent users from being able to reach controllers for resources they’re not authorized for. For example:
# Controllers that require Admin permissions.
scope "/", Eecrit do
pipe_through [:browser, :require_login, :require_admin]
resources "/animals", OldAnimalController
resources "/procedures", OldProcedureController
resources "/procedure_descriptions", OldProcedureDescriptionController
end
# Controllers that require superuser permissions.
scope "/", Eecrit do
pipe_through [:browser, :require_login, :require_superuser]
resources "/users", UserController
resources "/ability_groups", AbilityGroupController
resources "/organizations", OrganizationController
end
However, it’s not enough to “lock the door”: you also don’t want unauthorized users to even see the door. For example, consider this control panel for a superuser:
Admins shouldn’t see the bottom three buttons. The way I’m doing that now is via this View function:
def commands(conn, current_user) do
raw_builder do
unless current_user do
build_commands([{"Please log in", session_path(conn, :new)}])
end
if empowered?(current_user, :is_admin) do
build_commands([{"Work With Animals", old_animal_path(conn, :index)},
{"Work With Procedures", old_procedure_path(conn, :index)}])
end
if empowered?(current_user, :is_superuser) do
build_commands([{"Users", user_path(conn, :index)},
{"Organizations", organization_path(conn, :index)},
{"Ability Groups", ability_group_path(conn, :index)}])
end
end
end
(I’m using webassembly to construct a “partial” in code rather than in an eex
template.) This works, but it’s not automatic in the way that using plugs is. That is, whenever I create a resource, the structure of router.ex
forces me to choose its level of authorization. Everything works from there without any effort from me. But whenever I create a link to a resource, I ought to think “given the resource and the current user’s authorization, should I even generate the link?” I’d rather have that thinking happen automatically: if I ask for a link to a resource, I get either it or nil
(so that nothing shows in the output).
(I know that “link or nothing” won’t necessarily be the right thing for all places in the UI, but it would be enough in the cases I’ve got or currently foresee.)
So what I’d like would be something where my mix phoenix.gen.html
task would generate a model with an attribute like this:
defmodule Eecrit.OldAnimal do
use Eecrit.Web, :model
@requires_ability :inaccessible_until_you_change_this
...
… and then both plugs and link-constructing functions would key off of it. But I’m having trouble seeing how the plumbing would work.
So I’d be interested if anyone’s done anything like this, if there are libraries that help with this, etc. Thanks.
(I know about canada/canary, but I don’t see how to use them to implement the “tag the resource and everything else is done for you”. Slight reluctance to clump all resource permissions in one defimpl
for User
, but I guess a default “user can do nothing” has the necessary effect of forcing you to edit the file.)
(As you might tell, when it comes to security, I’m of the “any human failure [especially to act] fails safe” school.)