EctoTenancyEnforcer
EctoTenancyEnforcer provides a way to ensure that all queries made from your Elixir application, using Ecto, have tenancy set. If a query is made that does not have tenancy set, you can handle that as an error or have it throw an exception.
This library does not try to set tenancy on your queries. There are a lot more edge cases in that, and the stakes are a lot higher. That makes this library opinionated that it is a good idea for you to always set tenancy yourself.
Both where and joins are checked for tenancy, but more may be added over time if a valid use case arises.
Configuration
The EctoTenancyEnforcer.enforce/2 (and !) functions are to be used in the Ecto.Repo.prepare_query/3 callback. This callback
provides the entire Ecto.Query struct that is then introspected.
If you want to handle the tenancy error yourself (or log it), then use the non-bang version. If you want it to error out and not execute the query, then use the bang version.
The enforced_schemas key is where you configure your schemas that are tenant'd. The default column is assumed to be tenant_id,
but you can customize it. To customize it, use the format {MySchema, :my_tenant_column} like in the following example.
defmodule MyApp.Repo do
use Ecto.Repo,
otp_app: :my_app,
adapter: Ecto.Adapters.Postgres
@enforced_schemas [Company, {Person, :tenant_id}, {Alternate, :team_id}]
def init(_type, config) do
config = Keyword.merge(config, Application.get_env(:my_app, MyApp.Repo))
{:ok, config}
end
def prepare_query(_operation, query, opts) do
unless Keyword.get(opts, :tenancy_unchecked) do
EctoTenancyEnforcer.enforce!(query, enforced_schemas: @enforced_schemas)
end
{query, opts}
end
end
Query Examples
The following queries are all allowed (assuming Person and Company are enforced schemas):
//TODO
The following queries would not be allowed: //TODO
Preloading
Unfortunately, I can not currently find a way to do preloading in a functional manner. This is one situation where I think that the tenancy should
come from the source objects. Due to this, you must always use an Ecto.Query for preloading.
The following examples demonstrate allowed and not-allowed preloading:
// TODO
Contributing
// TODO