我有一个小型 Phoenix 应用程序,允许用户登录并查阅他们的个人资料。我使用了以下简单路线:
resources "/users", MyApp.UserController
但这允许每个用户通过:index
操作查看用户列表,以及删除或更新任何用户。
限制仅管理员访问的最简单方法是什么?我应该在每个操作前添加检查吗?或者我应该创建一个"/admin"
资源来处理这些操作?推荐的方法是什么?
我有一个小型 Phoenix 应用程序,允许用户登录并查阅他们的个人资料。我使用了以下简单路线:
resources "/users", MyApp.UserController
但这允许每个用户通过:index
操作查看用户列表,以及删除或更新任何用户。
限制仅管理员访问的最简单方法是什么?我应该在每个操作前添加检查吗?或者我应该创建一个"/admin"
资源来处理这些操作?推荐的方法是什么?
您将在UserController
. 0.4.x
没有条件 plug 的能力,但您可以通过以下方式实现您想要的:
defmodule MyApp.UserController do
use Phoenix.Controller
plug :authenticate, :admin
plug :action
def index(conn, _) do
render conn, "index"
end
def create(conn, params) do
# do the creating
end
...
defp authenticate(conn, :admin) do
do_auth(conn, action_name(conn))
end
defp do_auth(conn, action) when action in [:create, :update, :destroy] do
if AdminAuth.authenticated?(conn) do
conn
else
halt conn
end
end
defp do_auth(conn, _action), do: conn
end
即将到来的更改0.5
将允许更简单的条件插件,即:
defmodule MyApp.UserController do
use Phoenix.Controller
plug :authenticate, :admin when action in [:create, :update, :destroy]
def index(conn, _) do
render conn, "index"
end
def create(conn, params) do
# do the creating
end
...
defp authenticate(conn, :admin) do
if AdminAuth.authenticated?(conn) do
conn
else
halt conn
end
end
end
将公共/受限访问的控制器分开是一个好主意,所以我会添加一个Admin.UserController
类似你对受限功能的引用。
您还可以为经过身份验证的端点定义一个单独的管道:
defmodule MyApp.Router do
use MyApp.Web, :router
pipeline :admin do
plug :accepts, ["html"]
plug Authentication # this represents some plug that provides authentication
end
scope "/", MyApp do
pipe_through :browser
resources "/things", ThingController
end
scope "/admin", MyApp do
pipe_through :admin
resources "/admin/things", Admin.ThingsController
end
end
管理范围只是一个例子,它可以是任何你喜欢的,但管道的想法保持一致。
这种技术将使您的控制器保持清洁,但并非总是可行的。这取决于您的确切要求。
在 Chris Mccord 的基础上,您实际上可以在不占用控制器大量空间的情况下做到这一点。像这样:
defmodule MyAppWeb.CategoryController do
use MyAppWeb, :controller
use MyApp.Helpers.Connection
plug :authorize_crud, %{action: :read, permissions: [:can_access_categories]} when action in [:index, :show]
plug :authorize_crud, %{action: :create, permissions: [:can_create_categories]} when action in [:new, :create]
plug :authorize_crud, %{action: :update, permissions: [:can_update_categories]} when action in [:edit, :update]
def index(conn, _params) do
conn
end
def new(conn, _params) do
conn
end
def create(conn, %{"category" => category_params}) do
conn
end
def update(conn, %{"id" => id, "category" => category_params}) do
conn
end
end
defmodule MyApp.Helpers.Connection do
@spec authorize(%Plug.Conn{}, list(), list()) :: %Plug.Conn{}
def authorize(conn, user_permissions \\ [], required_permissions \\ []) do
import Plug.Conn
up = user_permissions |> MapSet.new()
rp = required_permissions |> MapSet.new()
case MapSet.subset?(rp, up) do
true ->
conn
false ->
conn
|> put_status(404)
|> Phoenix.Controller.render(MyAppWeb.ErrorView, "404.html", %{layout: false})
|> halt()
end
end
defmacro __using__(_) do
quote do
def authorize_crud(conn, opts = %{action: :read, permissions: permissions}) do
check(conn, permissions)
end
def authorize_crud(conn, opts = %{action: :create, permissions: permissions}) do
check(conn, permissions)
end
def authorize_crud(conn, opts = %{action: :update, permissions: permissions}) do
check(conn, permissions)
end
def authorize_crud(conn, opts = %{action: :destroy, permissions: permissions}) do
check(conn, permissions)
end
def check(conn, permissions) do
user = conn.assigns.current_user |> Repo.preload(:role)
MyApp.Helpers.Connection.authorize(conn, Accounts.list_permissions(user), permissions)
end
end
end
end
笔记:
use Myapp.Helpers.Connection
我们可以使用没有模块名称的导入函数,这允许我们在插件中使用它。defmacro __using__(_)
是必需的,因为您必须包含using宏并将所有应编译的代码放入 using 模块中authorize/3
函数用于返回真或假。Accounts.list_permissions(user)
是[:can_access_categories,:can_create_categories]
permissions
插件中的任何内容。所以[:can_access_categories]