Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 17 additions & 4 deletions lib/multi_tenancy.ex
Original file line number Diff line number Diff line change
Expand Up @@ -103,9 +103,22 @@ defmodule AshSqlite.MultiTenancy do
end
end

# Escapes rather than sanitises, so no two tenants share a file and no tenant names a
# path outside `dir`.
defp encode(tenant) when is_binary(tenant) and byte_size(tenant) > 0 do
@doc """
The name, without its `.db` extension, of the file `tenant`'s database is stored in.

Escapes rather than sanitises, so no two tenants share a file, even on a
case-insensitive filesystem, and no tenant names a path outside `dir`. Lowercase
letters, digits, `-`, `_` and `.` are kept, and every other byte becomes `~` and two
lowercase hex digits:

iex> AshSqlite.MultiTenancy.encode("Acme:2026")
"~41cme~3a2026"

Raises `ArgumentError` for a tenant that is not a non-empty binary, or that encodes to
more than 244 bytes.
"""
@spec encode(String.t()) :: String.t()
def encode(tenant) when is_binary(tenant) and byte_size(tenant) > 0 do
encoded = tenant |> :binary.bin_to_list() |> Enum.map_join(&encode_byte/1)

if byte_size(encoded) > @max_encoded_bytes do
Expand All @@ -119,7 +132,7 @@ defmodule AshSqlite.MultiTenancy do
encoded
end

defp encode(tenant) do
def encode(tenant) do
raise ArgumentError, """
expected a non-empty binary tenant, got: #{inspect(tenant)}

Expand Down
10 changes: 10 additions & 0 deletions test/multi_tenancy/multi_tenancy_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,16 @@ defmodule AshSqlite.MultiTenancyTest do
for ref <- refs, do: assert_receive({:DOWN, ^ref, _, _, _})
end

doctest AshSqlite.MultiTenancy

test "encode/1 is public, so a tenant's file name can be computed outside the repo" do
assert AshSqlite.MultiTenancy.encode("acme") == "acme"
assert AshSqlite.MultiTenancy.encode("a:b") == "a~3ab"
assert AshSqlite.MultiTenancy.encode("../../etc/x") == "..~2f..~2fetc~2fx"
assert AshSqlite.MultiTenancy.encode("Acme") == "~41cme"
assert AshSqlite.MultiTenancy.encode("a~3ab") == "a~7e3ab"
end

test "tenants that differ only by case get different files", %{dir: dir} do
start_repo(dir)

Expand Down
Loading