10

我有一个小型 Phoenix 应用程序,允许用户登录并查阅他们的个人资料。我使用了以下简单路线:

resources "/users", MyApp.UserController

但这允许每个用户通过:index操作查看用户列表,以及删除或更新任何用户。

限制仅管理员访问的最简单方法是什么?我应该在每个操作前添加检查吗?或者我应该创建一个"/admin"资源来处理这些操作?推荐的方法是什么?

4

3 回答 3

14

您将在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类似你对受限功能的引用。

于 2014-09-26T14:29:03.933 回答
2

您还可以为经过身份验证的端点定义一个单独的管道:

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

管理范围只是一个例子,它可以是任何你喜欢的,但管道的想法保持一致。

这种技术将使您的控制器保持清洁,但并非总是可行的。这取决于您的确切要求。

于 2016-03-16T16:59:47.687 回答
0

在 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

笔记:

  1. 当我们使用时,use Myapp.Helpers.Connection我们可以使用没有模块名称的导入函数,这允许我们在插件中使用它。
  2. 该代码defmacro __using__(_)是必需的,因为您必须包含using宏并将所有应编译的代码放入 using 模块中
  3. 在我的案例中,使用的是授权功能来限制用户对某些控制器操作的访问。如果用户有所需的要求,该authorize/3函数用于返回真或假。
  4. 在 defmacro 内部,我们使用来自 plug 调用的模式匹配来覆盖 authorize_crud。
  5. 一个理想的数据列表Accounts.list_permissions(user)
[:can_access_categories,:can_create_categories]
  1. 进入匹配的权限是permissions插件中的任何内容。所以
[:can_access_categories]
于 2021-12-10T01:01:29.407 回答