From ef2f828250eeb47e2fc462b7e70bf7416adf7228 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Tue, 26 Nov 2019 01:50:53 -0500 Subject: [PATCH] WIP --- README.md | 1 + lib/ash.ex | 21 +++++++++++---------- lib/ash/authorization/authorizer.ex | 5 +++++ lib/ash/authorization/built_in.ex | 1 + lib/ash/authorization/rule.ex | 2 +- lib/ash/data_layer/actions.ex | 22 ++++++++++++++-------- 6 files changed, 33 insertions(+), 19 deletions(-) diff --git a/README.md b/README.md index f2e55eda..e8e8dc8d 100644 --- a/README.md +++ b/README.md @@ -19,3 +19,4 @@ * all actions need to be performed in a transaction * document authorization thoroughly. *batch* (default) checks need to return a list of `ids` for which the check passed. * So many parts of the system are reliant on things having an `id` key explicitly. THis will need to be addressed some day, and will be a huge pain in the ass +* Validate that the user resource has a get action diff --git a/lib/ash.ex b/lib/ash.ex index 99c87480..8d0b220e 100644 --- a/lib/ash.ex +++ b/lib/ash.ex @@ -58,14 +58,14 @@ defmodule Ash do resource.data_layer() end - def get(resource, id, params \\ %{}, action \\ nil) do + def get(resource, id, params \\ %{}, action \\ %{}) do # TODO: Figure out this interface params_with_filter = params |> Map.put_new(:filter, %{}) |> Map.update!(:filter, &Map.put(&1, :id, id)) - case read(resource, params_with_filter, action) do + case read(resource, params_with_filter) do {:ok, %{results: [single_result]}} -> {:ok, single_result} @@ -81,26 +81,27 @@ defmodule Ash do end # TODO: params - def read(resource, user, params \\ %{}, action \\ nil) do - action = action || primary_action(resource, :read) + def read(resource, params \\ %{}) do + action = Map.get(params, :action) || primary_action(resource, :read) + params = Map.put_new(params, :user, :__none__) Ash.DataLayer.Actions.run_read_action(resource, action, params) end # TODO: auth - def create(resource, attributes, relationships, params \\ %{}, action \\ nil) do - action = action || primary_action(resource, :create) + def create(resource, attributes, relationships, params \\ %{}) do + action = Map.get(params, :action) || primary_action(resource, :create) Ash.DataLayer.Actions.run_create_action(resource, action, attributes, relationships, params) end # TODO: auth - def update(%resource{} = record, attributes, relationships, params \\ %{}, action \\ nil) do - action = action || primary_action(resource, :update) + def update(%resource{} = record, attributes, relationships, params \\ %{}) do + action = Map.get(params, :action) || primary_action(resource, :update) Ash.DataLayer.Actions.run_update_action(record, action, attributes, relationships, params) end # TODO: auth - def destroy(%resource{} = record, params \\ %{}, action \\ nil) do - action = action || primary_action(resource, :destroy) + def destroy(%resource{} = record, params \\ %{}) do + action = Map.get(params, :action) || primary_action(resource, :destroy) Ash.DataLayer.Actions.run_destroy_action(record, action, params) end diff --git a/lib/ash/authorization/authorizer.ex b/lib/ash/authorization/authorizer.ex index d608c208..cabd6abe 100644 --- a/lib/ash/authorization/authorizer.ex +++ b/lib/ash/authorization/authorizer.ex @@ -1,6 +1,9 @@ defmodule Ash.Authorization.Authorizer do alias Ash.Authorization.Rule + def authorize_precheck(:__none__, rules, _context), + do: {%{prediction: :allow}, Enum.map(rules, fn _ -> %{} end)} + def authorize_precheck(user, rules, context) do rules |> Enum.reduce({%{}, []}, fn rule, {instructions, per_check_data} -> @@ -17,6 +20,8 @@ defmodule Ash.Authorization.Authorizer do # Never call authorize w/o first calling authorize_precheck before # the operation + def authorize(:__none__, _, _, _, _), do: :allow + def authorize(user, data, rules, context, per_check_data) do {_decision, remaining_records} = rules diff --git a/lib/ash/authorization/built_in.ex b/lib/ash/authorization/built_in.ex index 1766e8f5..8a811271 100644 --- a/lib/ash/authorization/built_in.ex +++ b/lib/ash/authorization/built_in.ex @@ -9,6 +9,7 @@ defmodule Ash.Authorization.BuiltIn do item |> Map.get(relationship_name) |> Kernel.||([]) + |> List.wrap() |> Enum.find(fn related -> Map.get(related, relationship.destination_field) == user.id end) diff --git a/lib/ash/authorization/rule.ex b/lib/ash/authorization/rule.ex index 8693c568..de83f4d8 100644 --- a/lib/ash/authorization/rule.ex +++ b/lib/ash/authorization/rule.ex @@ -51,7 +51,7 @@ defmodule Ash.Authorization.Rule do end def run_check( - %{check: check, extra_context: extra_context, kind: kind}, + %{check: check, extra_context: extra_context, kind: kind} = rule, user, data, context diff --git a/lib/ash/data_layer/actions.ex b/lib/ash/data_layer/actions.ex index 1da6b0c3..626d13aa 100644 --- a/lib/ash/data_layer/actions.ex +++ b/lib/ash/data_layer/actions.ex @@ -2,7 +2,7 @@ defmodule Ash.DataLayer.Actions do def run_create_action(resource, action, attributes, relationships, params) do case Ash.Data.create(resource, action, attributes, relationships, params) do {:ok, record} -> - Ash.Data.side_load(record, Map.get(params, :include, []), resource) + Ash.Data.side_load(record, Map.get(params, :side_load, []), resource) {:error, error} -> {:error, error} @@ -11,7 +11,8 @@ defmodule Ash.DataLayer.Actions do def run_update_action(%resource{} = record, action, attributes, relationships, params) do with {:ok, record} <- Ash.Data.update(record, action, attributes, relationships, params), - {:ok, [record]} <- Ash.Data.side_load([record], Map.get(params, :include, []), resource) do + {:ok, [record]} <- + Ash.Data.side_load([record], Map.get(params, :side_load, []), resource) do {:ok, record} else {:error, error} -> {:error, error} @@ -29,27 +30,32 @@ defmodule Ash.DataLayer.Actions do params: params } - user = Map.get(params, :user) + user = Map.get(params || %{}, :user) with {%{prediction: prediction} = instructions, per_check_data} when prediction != :unauthorized <- Ash.Authorization.Authorizer.authorize_precheck(user, action.rules, auth_context), - params <- add_auth_side_loads(params, instructions), {:ok, query} <- Ash.Data.resource_to_query(resource), {:ok, filtered_query} <- Ash.Data.filter(resource, query, params), {:ok, paginator} <- Ash.DataLayer.Paginator.paginate(resource, action, filtered_query, params), {:ok, found} <- Ash.Data.get_many(paginator.query, resource), + side_load_param <- + deep_merge_side_loads( + Map.get(params, :side_load, []), + Map.get(instructions, :side_load, []) + ), + {:ok, side_loaded} <- + Ash.Data.side_load(found, side_load_param, resource), :allow <- Ash.Authorization.Authorizer.authorize( user, - found, + side_loaded, action.rules, auth_context, per_check_data - ), - {:ok, result} <- Ash.Data.side_load(found, Map.get(params, :include, []), resource) do - {:ok, %{paginator | results: result}} + ) do + {:ok, %{paginator | results: side_loaded}} else {%{prediction: :unauthorized}, _} -> # TODO: Nice errors here!