Resolves the scope identifier for an encrypted field.
An encrypted field declares how its scope is resolved, not what the
scope is: scope: :process reads the current process scope, scope: :none
declares the field global, and scope: MyApp.SomeResolver names a module
implementing this behaviour (ADR-0001 decision 5f).
The behaviour is one callback. resolve/2 is handed the operation being
performed and the declared context of the field it is being performed on,
and answers with the scope identifier, :none, or an error:
{:ok, scope}- encrypt or decrypt under this scope's key.:none- this value has no scope. A global field, deliberately: its ciphertext is not crypto-shreddable with a scope key.{:error, reason}- the scope could not be resolved. The caller raises; it never falls back to a default scope, because a row written under the wrong key is unrecoverable in a way an exception is not (ADR-0001 decision 5c).
The default strategy is not privileged
Encryptor.Ecto.ScopeContext.Process - the implementation behind
scope: :process - is an ordinary implementation of this behaviour with no
access the callback does not give a host's own module. A host that already
has an ambient request context can substitute its own resolver by changing
the :scope option and nothing else.
Example
A payment host that has already resolved the merchant onto a Plug-assigned struct, and would rather read it there than mirror it into a second place:
defmodule Payments.MerchantResolver do
@behaviour Encryptor.Ecto.ScopeContext
@impl Encryptor.Ecto.ScopeContext
def resolve(_operation, _params) do
case Payments.RequestContext.current() do
%{merchant_id: id} when is_binary(id) -> {:ok, id}
_other -> {:error, :no_request_context}
end
end
endDeclared at the field's type module:
defmodule Payments.Encrypted.Binary do
use Encryptor.Ecto.Binary,
vault: Payments.Vault,
scope: Payments.MerchantResolver
endoperation is passed because a resolver may legitimately answer differently
for a write and a read - a host reading historical rows through a background
reporting job has a :load scope it does not have on :dump.
Summary
Types
The operation the type is performing when it asks.
The declared context of the field being dumped or loaded.
Callbacks
Resolves the scope identifier for one dump or load.
Types
@type operation() :: :dump | :load
The operation the type is performing when it asks.
@type params() :: %{ :vault => module(), :table => String.t(), :column => String.t(), optional(:opts) => keyword() }
The declared context of the field being dumped or loaded.
:table and :column are the field's declared context values, frozen at
declaration time (ADR-0001 acceptance amendment 5), not necessarily the
physical names in use today. :opts carries the field's remaining declared
options for a resolver that wants them.