Skip to content

Commit dcdafd9

Browse files
authored
fix: enforce tenant name rules at creation (#550)
Closes #549
1 parent cc6929d commit dcdafd9

File tree

6 files changed

+121
-1
lines changed

6 files changed

+121
-1
lines changed

lib/multitenancy.ex

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ defmodule AshPostgres.MultiTenancy do
55

66
@tenant_name_regex ~r/^[a-zA-Z0-9_-]+$/
77
def create_tenant!(tenant_name, repo) do
8+
validate_tenant_name!(tenant_name)
89
Ecto.Adapters.SQL.query!(repo, "CREATE SCHEMA IF NOT EXISTS \"#{tenant_name}\"", [])
910

1011
migrate_tenant(tenant_name, repo)
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
{
2+
"attributes": [
3+
{
4+
"allow_nil?": false,
5+
"default": "nil",
6+
"generated?": false,
7+
"primary_key?": true,
8+
"references": null,
9+
"size": null,
10+
"source": "name",
11+
"type": "text"
12+
}
13+
],
14+
"base_filter": null,
15+
"check_constraints": [],
16+
"custom_indexes": [],
17+
"custom_statements": [],
18+
"has_create_action": true,
19+
"hash": "63124167427BA3C61197814348217EFC967CDAA398102552836E26BD93E198C8",
20+
"identities": [],
21+
"multitenancy": {
22+
"attribute": null,
23+
"global": null,
24+
"strategy": null
25+
},
26+
"repo": "Elixir.AshPostgres.TestRepo",
27+
"schema": null,
28+
"table": "multitenant_named_orgs"
29+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
defmodule AshPostgres.TestRepo.Migrations.MigrateResources53 do
2+
@moduledoc """
3+
Updates resources based on their most recent snapshots.
4+
5+
This file was autogenerated with `mix ash_postgres.generate_migrations`
6+
"""
7+
8+
use Ecto.Migration
9+
10+
def up do
11+
create table(:multitenant_named_orgs, primary_key: false) do
12+
add(:name, :text, null: false, primary_key: true)
13+
end
14+
end
15+
16+
def down do
17+
drop(table(:multitenant_named_orgs))
18+
end
19+
end

test/multitenancy_test.exs

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ defmodule AshPostgres.Test.MultitenancyTest do
22
use AshPostgres.RepoCase, async: false
33

44
require Ash.Query
5-
alias AshPostgres.MultitenancyTest.{CompositeKeyPost, Org, Post, User}
5+
alias AshPostgres.MultitenancyTest.{CompositeKeyPost, NamedOrg, Org, Post, User}
66
alias AshPostgres.Test.Post, as: GlobalPost
77

88
setup do
@@ -292,4 +292,33 @@ defmodule AshPostgres.Test.MultitenancyTest do
292292
|> Ash.create!()
293293
end
294294
end
295+
296+
test "rejects characters other than alphanumericals, - and _ on tenant creation" do
297+
assert_raise(
298+
Ash.Error.Unknown,
299+
~r/Tenant name must match ~r\/\^\[a-zA-Z0-9_-]\+\$\/, got:/,
300+
fn ->
301+
NamedOrg
302+
|> Ash.Changeset.for_create(:create, %{name: "🚫"})
303+
|> Ash.create!()
304+
end
305+
)
306+
end
307+
308+
test "rejects characters other than alphanumericals, - and _ when renaming tenant" do
309+
org =
310+
NamedOrg
311+
|> Ash.Changeset.for_create(:create, %{name: "toto"})
312+
|> Ash.create!()
313+
314+
assert_raise(
315+
Ash.Error.Unknown,
316+
~r/Tenant name must match ~r\/\^\[a-zA-Z0-9_-]\+\$\/, got:/,
317+
fn ->
318+
org
319+
|> Ash.Changeset.for_update(:update, %{name: "🚫"})
320+
|> Ash.update!()
321+
end
322+
)
323+
end
295324
end

test/support/multitenancy/domain.ex

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ defmodule AshPostgres.MultitenancyTest.Domain do
44

55
resources do
66
resource(AshPostgres.MultitenancyTest.Org)
7+
resource(AshPostgres.MultitenancyTest.NamedOrg)
78
resource(AshPostgres.MultitenancyTest.User)
89
resource(AshPostgres.MultitenancyTest.Post)
910
resource(AshPostgres.MultitenancyTest.PostLink)
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
defmodule AshPostgres.MultitenancyTest.NamedOrg do
2+
@moduledoc false
3+
use Ash.Resource,
4+
domain: AshPostgres.MultitenancyTest.Domain,
5+
data_layer: AshPostgres.DataLayer
6+
7+
defimpl Ash.ToTenant do
8+
def to_tenant(%{name: name}, resource) do
9+
if Ash.Resource.Info.data_layer(resource) == AshPostgres.DataLayer &&
10+
Ash.Resource.Info.multitenancy_strategy(resource) == :context do
11+
"org_#{name}"
12+
else
13+
name
14+
end
15+
end
16+
end
17+
18+
attributes do
19+
attribute(:name, :string,
20+
primary_key?: true,
21+
allow_nil?: false,
22+
public?: true,
23+
writable?: true
24+
)
25+
end
26+
27+
actions do
28+
default_accept(:*)
29+
30+
defaults([:create, :read, :update, :destroy])
31+
end
32+
33+
postgres do
34+
table "multitenant_named_orgs"
35+
repo(AshPostgres.TestRepo)
36+
37+
manage_tenant do
38+
template(["org_", :name])
39+
end
40+
end
41+
end

0 commit comments

Comments
 (0)