Render deleted entities on fetch (#2692)

Since we have flows we should either delete the flow when the related
entity is deleted (making them not very useful) or allow viewing deleted
entities properly marking them and removing all action buttons in the
UI:

<img width="1728" alt="Screenshot 2023-11-22 at 13 41 51"
src="https://github.com/firezone/firezone/assets/1877644/ae7f14b9-9607-4de0-a90f-049faf7e4374">
<img width="1728" alt="Screenshot 2023-11-22 at 13 41 54"
src="https://github.com/firezone/firezone/assets/1877644/491f8e1f-6aad-459b-b038-6100c25b3bf4">
<img width="1728" alt="Screenshot 2023-11-22 at 13 41 48"
src="https://github.com/firezone/firezone/assets/1877644/9200e521-0d92-41b5-9197-355353f09a50">

<img width="1728" alt="Screenshot 2023-11-22 at 13 07 47"
src="https://github.com/firezone/firezone/assets/1877644/dca59bbd-9771-4b06-b32b-f17cf0047520">

This change only affects fetching relation by ID (eg. `actors/:id`),
rest of pages (index, edit) will not show deleted entities unless they
are a critical relation (eg. for Policy to work both actor group and
resource are needed):

<img width="1728" alt="Screenshot 2023-11-22 at 13 42 23"
src="https://github.com/firezone/firezone/assets/1877644/d8b15011-838a-477d-97c8-5c7109299cb9">

Closes #2681

Signed-off-by: Andrew Dryga <andrew@dryga.com>
This commit is contained in:
Andrew Dryga
2023-11-30 13:55:07 -06:00
committed by GitHub
parent af5cc38f9e
commit 55e8d3407f
56 changed files with 510 additions and 214 deletions

View File

@@ -29,7 +29,8 @@ defmodule Domain.Actors do
true <- Validator.valid_uuid?(id) do
{preload, _opts} = Keyword.pop(opts, :preload, [])
Group.Query.by_id(id)
Group.Query.all()
|> Group.Query.by_id(id)
|> Authorizer.for_subject(subject)
|> Repo.fetch()
|> case do
@@ -47,7 +48,7 @@ defmodule Domain.Actors do
{preload, _opts} = Keyword.pop(opts, :preload, [])
{:ok, groups} =
Group.Query.all()
Group.Query.not_deleted()
|> Authorizer.for_subject(subject)
|> Repo.list()
@@ -153,7 +154,8 @@ defmodule Domain.Actors do
true <- Validator.valid_uuid?(id) do
{preload, _opts} = Keyword.pop(opts, :preload, [])
Actor.Query.by_id(id)
Actor.Query.all()
|> Actor.Query.by_id(id)
|> Authorizer.for_subject(subject)
|> Repo.fetch()
|> case do
@@ -185,7 +187,7 @@ defmodule Domain.Actors do
with :ok <- Auth.ensure_has_permissions(subject, Authorizer.manage_actors_permission()) do
{:ok, actors} =
Actor.Query.all()
Actor.Query.not_deleted()
|> Authorizer.for_subject(subject)
|> Repo.list()

View File

@@ -3,10 +3,14 @@ defmodule Domain.Actors.Actor.Query do
def all do
from(actors in Domain.Actors.Actor, as: :actors)
end
def not_deleted do
all()
|> where([actors: actors], is_nil(actors.deleted_at))
end
def by_id(queryable \\ all(), id)
def by_id(queryable \\ not_deleted(), id)
def by_id(queryable, {:in, ids}) do
where(queryable, [actors: actors], actors.id in ^ids)
@@ -20,19 +24,19 @@ defmodule Domain.Actors.Actor.Query do
where(queryable, [actors: actors], actors.id == ^id)
end
def by_account_id(queryable \\ all(), account_id) do
def by_account_id(queryable \\ not_deleted(), account_id) do
where(queryable, [actors: actors], actors.account_id == ^account_id)
end
def by_type(queryable \\ all(), type) do
def by_type(queryable \\ not_deleted(), type) do
where(queryable, [actors: actors], actors.type == ^type)
end
def not_disabled(queryable \\ all()) do
def not_disabled(queryable \\ not_deleted()) do
where(queryable, [actors: actors], is_nil(actors.disabled_at))
end
def preload_few_groups_for_each_actor(queryable \\ all(), limit) do
def preload_few_groups_for_each_actor(queryable \\ not_deleted(), limit) do
queryable
|> with_joined_memberships(limit)
|> with_joined_groups()
@@ -49,7 +53,10 @@ defmodule Domain.Actors.Actor.Query do
Domain.Actors.Membership.Query.all()
|> where([memberships: memberships], memberships.actor_id == parent_as(:actors).id)
# we need second join to exclude soft deleted actors before applying a limit
|> join(:inner, [memberships: memberships], groups in ^Domain.Actors.Group.Query.all(),
|> join(
:inner,
[memberships: memberships],
groups in ^Domain.Actors.Group.Query.not_deleted(),
on: groups.id == memberships.group_id
)
|> select([memberships: memberships], memberships.group_id)
@@ -70,18 +77,22 @@ defmodule Domain.Actors.Actor.Query do
)
end
def with_joined_groups(queryable \\ all()) do
join(queryable, :left, [memberships: memberships], groups in ^Domain.Actors.Group.Query.all(),
def with_joined_groups(queryable \\ not_deleted()) do
join(
queryable,
:left,
[memberships: memberships],
groups in ^Domain.Actors.Group.Query.not_deleted(),
on: groups.id == memberships.group_id,
as: :groups
)
end
def lock(queryable \\ all()) do
def lock(queryable \\ not_deleted()) do
lock(queryable, "FOR UPDATE")
end
def with_assoc(queryable \\ all(), qual \\ :left, assoc) do
def with_assoc(queryable \\ not_deleted(), qual \\ :left, assoc) do
with_named_binding(queryable, assoc, fn query, binding ->
join(query, qual, [actors: actors], a in assoc(actors, ^binding), as: ^binding)
end)

View File

@@ -5,7 +5,7 @@ defmodule Domain.Actors.Group do
field :name, :string
# Those fields will be set for groups we synced from IdP's
belongs_to :provider, Domain.Auth.Provider, where: [deleted_at: nil]
belongs_to :provider, Domain.Auth.Provider
field :provider_identifier, :string
has_many :policies, Domain.Policies.Policy,

View File

@@ -3,10 +3,14 @@ defmodule Domain.Actors.Group.Query do
def all do
from(groups in Domain.Actors.Group, as: :groups)
end
def not_deleted do
all()
|> where([groups: groups], is_nil(groups.deleted_at))
end
def by_id(queryable \\ all(), id)
def by_id(queryable \\ not_deleted(), id)
def by_id(queryable, {:in, ids}) do
where(queryable, [groups: groups], groups.id in ^ids)
@@ -16,19 +20,19 @@ defmodule Domain.Actors.Group.Query do
where(queryable, [groups: groups], groups.id == ^id)
end
def by_account_id(queryable \\ all(), account_id) do
def by_account_id(queryable \\ not_deleted(), account_id) do
where(queryable, [groups: groups], groups.account_id == ^account_id)
end
def by_provider_id(queryable \\ all(), provider_id) do
def by_provider_id(queryable \\ not_deleted(), provider_id) do
where(queryable, [groups: groups], groups.provider_id == ^provider_id)
end
def by_not_empty_provider_id(queryable \\ all()) do
def by_not_empty_provider_id(queryable \\ not_deleted()) do
where(queryable, [groups: groups], not is_nil(groups.provider_id))
end
def by_provider_identifier(queryable \\ all(), provider_identifier)
def by_provider_identifier(queryable \\ not_deleted(), provider_identifier)
def by_provider_identifier(queryable, {:in, provider_identifiers}) do
where(queryable, [groups: groups], groups.provider_identifier in ^provider_identifiers)
@@ -38,7 +42,7 @@ defmodule Domain.Actors.Group.Query do
where(queryable, [groups: groups], groups.provider_identifier == ^provider_identifier)
end
def group_by_provider_id(queryable \\ all()) do
def group_by_provider_id(queryable \\ not_deleted()) do
queryable
|> group_by([groups: groups], groups.provider_id)
|> where([groups: groups], not is_nil(groups.provider_id))
@@ -48,7 +52,7 @@ defmodule Domain.Actors.Group.Query do
})
end
def preload_few_actors_for_each_group(queryable \\ all(), limit) do
def preload_few_actors_for_each_group(queryable \\ not_deleted(), limit) do
queryable
|> with_joined_memberships(limit)
|> with_joined_actors()
@@ -71,7 +75,10 @@ defmodule Domain.Actors.Group.Query do
Domain.Actors.Membership.Query.all()
|> where([memberships: memberships], memberships.group_id == parent_as(:groups).id)
# we need second join to exclude soft deleted actors before applying a limit
|> join(:inner, [memberships: memberships], actors in ^Domain.Actors.Actor.Query.all(),
|> join(
:inner,
[memberships: memberships],
actors in ^Domain.Actors.Actor.Query.not_deleted(),
on: actors.id == memberships.actor_id
)
|> select([memberships: memberships], memberships.actor_id)
@@ -92,14 +99,18 @@ defmodule Domain.Actors.Group.Query do
)
end
def with_joined_actors(queryable \\ all()) do
join(queryable, :left, [memberships: memberships], actors in ^Domain.Actors.Actor.Query.all(),
def with_joined_actors(queryable \\ not_deleted()) do
join(
queryable,
:left,
[memberships: memberships],
actors in ^Domain.Actors.Actor.Query.not_deleted(),
on: actors.id == memberships.actor_id,
as: :actors
)
end
def lock(queryable \\ all()) do
def lock(queryable \\ not_deleted()) do
lock(queryable, "FOR UPDATE")
end
end

View File

@@ -69,14 +69,14 @@ defmodule Domain.Actors.Membership.Query do
end
def with_joined_actors(queryable \\ all()) do
join(queryable, :inner, [memberships: memberships], actors in ^Actor.Query.all(),
join(queryable, :inner, [memberships: memberships], actors in ^Actor.Query.not_deleted(),
on: actors.id == memberships.actor_id,
as: :actors
)
end
def with_joined_groups(queryable \\ all()) do
join(queryable, :inner, [memberships: memberships], groups in ^Group.Query.all(),
join(queryable, :inner, [memberships: memberships], groups in ^Group.Query.not_deleted(),
on: groups.id == memberships.group_id,
as: :groups
)

View File

@@ -3,10 +3,14 @@ defmodule Domain.Auth.Identity.Query do
def all do
from(identities in Domain.Auth.Identity, as: :identities)
end
def not_deleted do
all()
|> where([identities: identities], is_nil(identities.deleted_at))
end
def by_id(queryable \\ all(), id)
def by_id(queryable \\ not_deleted(), id)
def by_id(queryable, {:not, id}) do
where(queryable, [identities: identities], identities.id != ^id)
@@ -16,26 +20,26 @@ defmodule Domain.Auth.Identity.Query do
where(queryable, [identities: identities], identities.id == ^id)
end
def by_account_id(queryable \\ all(), account_id) do
def by_account_id(queryable \\ not_deleted(), account_id) do
where(queryable, [identities: identities], identities.account_id == ^account_id)
end
def by_actor_id(queryable \\ all(), actor_id) do
def by_actor_id(queryable \\ not_deleted(), actor_id) do
where(queryable, [identities: identities], identities.actor_id == ^actor_id)
end
def by_provider_id(queryable \\ all(), provider_id) do
def by_provider_id(queryable \\ not_deleted(), provider_id) do
queryable
|> where([identities: identities], identities.provider_id == ^provider_id)
|> with_assoc(:inner, :provider)
|> where([provider: provider], is_nil(provider.disabled_at) and is_nil(provider.deleted_at))
end
def by_adapter(queryable \\ all(), adapter) do
def by_adapter(queryable \\ not_deleted(), adapter) do
where(queryable, [identities: identities], identities.adapter == ^adapter)
end
def by_provider_identifier(queryable \\ all(), provider_identifier)
def by_provider_identifier(queryable \\ not_deleted(), provider_identifier)
def by_provider_identifier(queryable, {:in, provider_identifiers}) do
where(
@@ -53,7 +57,7 @@ defmodule Domain.Auth.Identity.Query do
)
end
def by_id_or_provider_identifier(queryable \\ all(), id_or_provider_identifier) do
def by_id_or_provider_identifier(queryable \\ not_deleted(), id_or_provider_identifier) do
if Domain.Validator.valid_uuid?(id_or_provider_identifier) do
where(
queryable,
@@ -66,18 +70,18 @@ defmodule Domain.Auth.Identity.Query do
end
end
def not_disabled(queryable \\ all()) do
def not_disabled(queryable \\ not_deleted()) do
queryable
|> join(:inner, [identities: identities], actors in assoc(identities, :actor), as: :actors)
|> where([actors: actors], is_nil(actors.deleted_at))
|> where([actors: actors], is_nil(actors.disabled_at))
end
def lock(queryable \\ all()) do
def lock(queryable \\ not_deleted()) do
lock(queryable, "FOR UPDATE")
end
def group_by_provider_id(queryable \\ all()) do
def group_by_provider_id(queryable \\ not_deleted()) do
queryable
|> group_by([identities: identities], identities.provider_id)
|> select([identities: identities], %{
@@ -86,13 +90,13 @@ defmodule Domain.Auth.Identity.Query do
})
end
def with_preloaded_assoc(queryable \\ all(), type \\ :left, assoc) do
def with_preloaded_assoc(queryable \\ not_deleted(), type \\ :left, assoc) do
queryable
|> with_assoc(type, assoc)
|> preload([{^assoc, assoc}], [{^assoc, assoc}])
end
def with_assoc(queryable \\ all(), type \\ :left, assoc) do
def with_assoc(queryable \\ not_deleted(), type \\ :left, assoc) do
with_named_binding(queryable, assoc, fn query, binding ->
join(query, type, [identities: identities], a in assoc(identities, ^binding), as: ^binding)
end)

View File

@@ -3,10 +3,14 @@ defmodule Domain.Auth.Provider.Query do
def all do
from(provider in Domain.Auth.Provider, as: :provider)
end
def not_deleted do
all()
|> where([provider: provider], is_nil(provider.deleted_at))
end
def by_id(queryable \\ all(), id)
def by_id(queryable \\ not_deleted(), id)
def by_id(queryable, {:not, id}) do
where(queryable, [provider: provider], provider.id != ^id)
@@ -16,7 +20,7 @@ defmodule Domain.Auth.Provider.Query do
where(queryable, [provider: provider], provider.id == ^id)
end
def by_adapter(queryable \\ all(), adapter)
def by_adapter(queryable \\ not_deleted(), adapter)
def by_adapter(queryable, {:not_in, adapters}) do
where(queryable, [provider: provider], provider.adapter not in ^adapters)
@@ -26,7 +30,7 @@ defmodule Domain.Auth.Provider.Query do
where(queryable, [provider: provider], provider.adapter == ^adapter)
end
def last_synced_at(queryable \\ all(), {:lt, datetime}) do
def last_synced_at(queryable \\ not_deleted(), {:lt, datetime}) do
where(
queryable,
[provider: provider],
@@ -34,7 +38,7 @@ defmodule Domain.Auth.Provider.Query do
)
end
def by_non_empty_refresh_token(queryable \\ all()) do
def by_non_empty_refresh_token(queryable \\ not_deleted()) do
where(
queryable,
[provider: provider],
@@ -42,7 +46,7 @@ defmodule Domain.Auth.Provider.Query do
)
end
def token_expires_at(queryable \\ all(), {:lt, datetime}) do
def token_expires_at(queryable \\ not_deleted(), {:lt, datetime}) do
where(
queryable,
[provider: provider],
@@ -50,19 +54,19 @@ defmodule Domain.Auth.Provider.Query do
)
end
def by_provisioner(queryable \\ all(), provisioner) do
def by_provisioner(queryable \\ not_deleted(), provisioner) do
where(queryable, [provider: provider], provider.provisioner == ^provisioner)
end
def by_account_id(queryable \\ all(), account_id) do
def by_account_id(queryable \\ not_deleted(), account_id) do
where(queryable, [provider: provider], provider.account_id == ^account_id)
end
def not_disabled(queryable \\ all()) do
def not_disabled(queryable \\ not_deleted()) do
where(queryable, [provider: provider], is_nil(provider.disabled_at))
end
def lock(queryable \\ all()) do
def lock(queryable \\ not_deleted()) do
lock(queryable, "FOR UPDATE")
end
end

View File

@@ -38,7 +38,8 @@ defmodule Domain.Clients do
true <- Validator.valid_uuid?(id) do
{preload, _opts} = Keyword.pop(opts, :preload, [])
Client.Query.by_id(id)
Client.Query.all()
|> Client.Query.by_id(id)
|> Authorizer.for_subject(subject)
|> Repo.fetch()
|> case do
@@ -80,7 +81,7 @@ defmodule Domain.Clients do
with :ok <- Auth.ensure_has_permissions(subject, required_permissions) do
{:ok, clients} =
Client.Query.all()
Client.Query.not_deleted()
|> Authorizer.for_subject(subject)
|> Repo.list()

View File

@@ -3,26 +3,30 @@ defmodule Domain.Clients.Client.Query do
def all do
from(clients in Domain.Clients.Client, as: :clients)
end
def not_deleted do
all()
|> where([clients: clients], is_nil(clients.deleted_at))
end
def by_id(queryable \\ all(), id) do
def by_id(queryable \\ not_deleted(), id) do
where(queryable, [clients: clients], clients.id == ^id)
end
def by_actor_id(queryable \\ all(), actor_id) do
def by_actor_id(queryable \\ not_deleted(), actor_id) do
where(queryable, [clients: clients], clients.actor_id == ^actor_id)
end
def by_account_id(queryable \\ all(), account_id) do
def by_account_id(queryable \\ not_deleted(), account_id) do
where(queryable, [clients: clients], clients.account_id == ^account_id)
end
def returning_all(queryable \\ all()) do
def returning_not_deleted(queryable \\ not_deleted()) do
select(queryable, [clients: clients], clients)
end
def with_preloaded_actor(queryable \\ all()) do
def with_preloaded_actor(queryable \\ not_deleted()) do
with_named_binding(queryable, :actor, fn queryable, binding ->
queryable
|> join(:inner, [clients: clients], actor in assoc(clients, ^binding), as: ^binding)
@@ -30,7 +34,7 @@ defmodule Domain.Clients.Client.Query do
end)
end
def with_preloaded_identity(queryable \\ all()) do
def with_preloaded_identity(queryable \\ not_deleted()) do
with_named_binding(queryable, :identity, fn queryable, binding ->
queryable
|> join(:inner, [clients: clients], identity in assoc(clients, ^binding), as: ^binding)

View File

@@ -42,7 +42,8 @@ defmodule Domain.Gateways do
true <- Validator.valid_uuid?(id) do
{preload, _opts} = Keyword.pop(opts, :preload, [])
Group.Query.by_id(id)
Group.Query.all()
|> Group.Query.by_id(id)
|> Authorizer.for_subject(subject)
|> Repo.fetch()
|> case do
@@ -68,7 +69,7 @@ defmodule Domain.Gateways do
{preload, _opts} = Keyword.pop(opts, :preload, [])
{:ok, groups} =
Group.Query.all()
Group.Query.not_deleted()
|> Authorizer.for_subject(subject)
|> Repo.list()
@@ -193,7 +194,8 @@ defmodule Domain.Gateways do
true <- Validator.valid_uuid?(id) do
{preload, _opts} = Keyword.pop(opts, :preload, [])
Gateway.Query.by_id(id)
Gateway.Query.all()
|> Gateway.Query.by_id(id)
|> Authorizer.for_subject(subject)
|> Repo.fetch()
|> case do
@@ -228,7 +230,7 @@ defmodule Domain.Gateways do
with :ok <- Auth.ensure_has_permissions(subject, Authorizer.manage_gateways_permission()) do
{:ok, gateways} =
Gateway.Query.all()
Gateway.Query.not_deleted()
|> Authorizer.for_subject(subject)
|> Repo.list()
@@ -263,7 +265,7 @@ defmodule Domain.Gateways do
{preload, _opts} = Keyword.pop(opts, :preload, [])
{:ok, gateways} =
Gateway.Query.all()
Gateway.Query.not_deleted()
|> Gateway.Query.by_group_id(group.id)
|> Authorizer.for_subject(subject)
|> Repo.list()

View File

@@ -3,40 +3,44 @@ defmodule Domain.Gateways.Gateway.Query do
def all do
from(gateways in Domain.Gateways.Gateway, as: :gateways)
end
def not_deleted do
all()
|> where([gateways: gateways], is_nil(gateways.deleted_at))
end
def by_id(queryable \\ all(), id) do
def by_id(queryable \\ not_deleted(), id) do
where(queryable, [gateways: gateways], gateways.id == ^id)
end
def by_ids(queryable \\ all(), ids) do
def by_ids(queryable \\ not_deleted(), ids) do
where(queryable, [gateways: gateways], gateways.id in ^ids)
end
def by_user_id(queryable \\ all(), user_id) do
def by_user_id(queryable \\ not_deleted(), user_id) do
where(queryable, [gateways: gateways], gateways.user_id == ^user_id)
end
def by_group_id(queryable \\ all(), group_id) do
def by_group_id(queryable \\ not_deleted(), group_id) do
where(queryable, [gateways: gateways], gateways.group_id == ^group_id)
end
def by_account_id(queryable \\ all(), account_id) do
def by_account_id(queryable \\ not_deleted(), account_id) do
where(queryable, [gateways: gateways], gateways.account_id == ^account_id)
end
def by_resource_id(queryable \\ all(), resource_id) do
def by_resource_id(queryable \\ not_deleted(), resource_id) do
queryable
|> with_joined_connections()
|> where([connections: connections], connections.resource_id == ^resource_id)
end
def returning_all(queryable \\ all()) do
def returning_not_deleted(queryable \\ not_deleted()) do
select(queryable, [gateways: gateways], gateways)
end
def with_joined_connections(queryable \\ all()) do
def with_joined_connections(queryable \\ not_deleted()) do
with_named_binding(queryable, :connections, fn queryable, binding ->
queryable
|> join(
@@ -49,7 +53,7 @@ defmodule Domain.Gateways.Gateway.Query do
end)
end
def with_preloaded_user(queryable \\ all()) do
def with_preloaded_user(queryable \\ not_deleted()) do
with_named_binding(queryable, :user, fn queryable, binding ->
queryable
|> join(:inner, [gateways: gateways], user in assoc(gateways, ^binding), as: ^binding)

View File

@@ -3,14 +3,18 @@ defmodule Domain.Gateways.Group.Query do
def all do
from(groups in Domain.Gateways.Group, as: :groups)
end
def not_deleted do
all()
|> where([groups: groups], is_nil(groups.deleted_at))
end
def by_id(queryable \\ all(), id) do
def by_id(queryable \\ not_deleted(), id) do
where(queryable, [groups: groups], groups.id == ^id)
end
def by_account_id(queryable \\ all(), account_id) do
def by_account_id(queryable \\ not_deleted(), account_id) do
where(queryable, [groups: groups], groups.account_id == ^account_id)
end
end

View File

@@ -1,16 +1,16 @@
defmodule Domain.Gateways.Token.Query do
use Domain, :query
def all do
def not_deleted do
from(token in Domain.Gateways.Token, as: :token)
|> where([token: token], is_nil(token.deleted_at))
end
def by_id(queryable \\ all(), id) do
def by_id(queryable \\ not_deleted(), id) do
where(queryable, [token: token], token.id == ^id)
end
def by_group_id(queryable \\ all(), group_id) do
def by_group_id(queryable \\ not_deleted(), group_id) do
where(queryable, [token: token], token.group_id == ^group_id)
end
end

View File

@@ -15,7 +15,8 @@ defmodule Domain.Policies do
with :ok <- Auth.ensure_has_permissions(subject, required_permissions),
true <- Validator.valid_uuid?(id) do
Policy.Query.by_id(id)
Policy.Query.all()
|> Policy.Query.by_id(id)
|> Authorizer.for_subject(subject)
|> Repo.fetch()
|> case do
@@ -40,7 +41,7 @@ defmodule Domain.Policies do
with :ok <- Auth.ensure_has_permissions(subject, required_permissions) do
{:ok, policies} =
Policy.Query.all()
Policy.Query.not_deleted()
|> Authorizer.for_subject(subject)
|> Repo.list()

View File

@@ -4,8 +4,8 @@ defmodule Domain.Policies.Policy do
schema "policies" do
field :description, :string
belongs_to :actor_group, Domain.Actors.Group, where: [deleted_at: nil]
belongs_to :resource, Domain.Resources.Resource, where: [deleted_at: nil]
belongs_to :actor_group, Domain.Actors.Group
belongs_to :resource, Domain.Resources.Resource
belongs_to :account, Domain.Accounts.Account
field :created_by, Ecto.Enum, values: ~w[identity]a

View File

@@ -3,6 +3,10 @@ defmodule Domain.Policies.Policy.Query do
def all do
from(policies in Domain.Policies.Policy, as: :policies)
end
def not_deleted do
from(policies in Domain.Policies.Policy, as: :policies)
|> where([policies: policies], is_nil(policies.deleted_at))
|> with_joined_actor_group()
|> where([actor_group: actor_group], is_nil(actor_group.deleted_at))
@@ -10,29 +14,29 @@ defmodule Domain.Policies.Policy.Query do
|> where([resource: resource], is_nil(resource.deleted_at))
end
def by_id(queryable \\ all(), id) do
def by_id(queryable \\ not_deleted(), id) do
where(queryable, [policies: policies], policies.id == ^id)
end
def by_account_id(queryable \\ all(), account_id) do
def by_account_id(queryable \\ not_deleted(), account_id) do
where(queryable, [policies: policies], policies.account_id == ^account_id)
end
def by_resource_id(queryable \\ all(), resource_id) do
def by_resource_id(queryable \\ not_deleted(), resource_id) do
where(queryable, [policies: policies], policies.resource_id == ^resource_id)
end
def by_resource_ids(queryable \\ all(), resource_ids) do
def by_resource_ids(queryable \\ not_deleted(), resource_ids) do
where(queryable, [policies: policies], policies.resource_id in ^resource_ids)
end
def by_actor_id(queryable \\ all(), actor_id) do
def by_actor_id(queryable \\ not_deleted(), actor_id) do
queryable
|> with_joined_memberships()
|> where([memberships: memberships], memberships.actor_id == ^actor_id)
end
def count_by_resource_id(queryable \\ all()) do
def count_by_resource_id(queryable \\ not_deleted()) do
queryable
|> group_by([policies: policies], policies.resource_id)
|> select([policies: policies], %{
@@ -41,7 +45,7 @@ defmodule Domain.Policies.Policy.Query do
})
end
def with_joined_actor_group(queryable \\ all()) do
def with_joined_actor_group(queryable \\ not_deleted()) do
with_named_binding(queryable, :actor_group, fn queryable, binding ->
join(queryable, :inner, [policies: policies], actor_group in assoc(policies, ^binding),
as: ^binding
@@ -49,7 +53,7 @@ defmodule Domain.Policies.Policy.Query do
end)
end
def with_joined_resource(queryable \\ all()) do
def with_joined_resource(queryable \\ not_deleted()) do
with_named_binding(queryable, :resource, fn queryable, binding ->
join(queryable, :inner, [policies: policies], resource in assoc(policies, ^binding),
as: ^binding
@@ -57,7 +61,7 @@ defmodule Domain.Policies.Policy.Query do
end)
end
def with_joined_memberships(queryable \\ all()) do
def with_joined_memberships(queryable \\ not_deleted()) do
queryable
|> with_joined_actor_group()
|> with_named_binding(:memberships, fn queryable, binding ->

View File

@@ -21,7 +21,8 @@ defmodule Domain.Relays do
true <- Validator.valid_uuid?(id) do
{preload, _opts} = Keyword.pop(opts, :preload, [])
Group.Query.by_id(id)
Group.Query.all()
|> Group.Query.by_id(id)
|> Authorizer.for_subject(subject)
|> Repo.fetch()
|> case do
@@ -47,7 +48,7 @@ defmodule Domain.Relays do
{preload, _opts} = Keyword.pop(opts, :preload, [])
{:ok, groups} =
Group.Query.all()
Group.Query.not_deleted()
|> Authorizer.for_subject(subject)
|> Repo.list()
@@ -186,7 +187,8 @@ defmodule Domain.Relays do
true <- Validator.valid_uuid?(id) do
{preload, _opts} = Keyword.pop(opts, :preload, [])
Relay.Query.by_id(id)
Relay.Query.all()
|> Relay.Query.by_id(id)
|> Authorizer.for_subject(subject)
|> Repo.fetch()
|> case do
@@ -221,7 +223,7 @@ defmodule Domain.Relays do
{preload, _opts} = Keyword.pop(opts, :preload, [])
{:ok, relays} =
Relay.Query.all()
Relay.Query.not_deleted()
|> Authorizer.for_subject(subject)
|> Repo.list()

View File

@@ -3,18 +3,22 @@ defmodule Domain.Relays.Group.Query do
def all do
from(groups in Domain.Relays.Group, as: :groups)
end
def not_deleted do
all()
|> where([groups: groups], is_nil(groups.deleted_at))
end
def by_id(queryable \\ all(), id) do
def by_id(queryable \\ not_deleted(), id) do
where(queryable, [groups: groups], groups.id == ^id)
end
def by_account_id(queryable \\ all(), account_id) do
def by_account_id(queryable \\ not_deleted(), account_id) do
where(queryable, [groups: groups], groups.account_id == ^account_id)
end
def global_or_by_account_id(queryable \\ all(), account_id) do
def global_or_by_account_id(queryable \\ not_deleted(), account_id) do
where(
queryable,
[groups: groups],

View File

@@ -3,26 +3,30 @@ defmodule Domain.Relays.Relay.Query do
def all do
from(relays in Domain.Relays.Relay, as: :relays)
end
def not_deleted do
all()
|> where([relays: relays], is_nil(relays.deleted_at))
end
def by_id(queryable \\ all(), id) do
def by_id(queryable \\ not_deleted(), id) do
where(queryable, [relays: relays], relays.id == ^id)
end
def by_ids(queryable \\ all(), ids) do
def by_ids(queryable \\ not_deleted(), ids) do
where(queryable, [relays: relays], relays.id in ^ids)
end
def by_user_id(queryable \\ all(), user_id) do
def by_user_id(queryable \\ not_deleted(), user_id) do
where(queryable, [relays: relays], relays.user_id == ^user_id)
end
def by_account_id(queryable \\ all(), account_id) do
def by_account_id(queryable \\ not_deleted(), account_id) do
where(queryable, [relays: relays], relays.account_id == ^account_id)
end
def public(queryable \\ all()) do
def public(queryable \\ not_deleted()) do
where(
queryable,
[relays: relays],
@@ -30,7 +34,7 @@ defmodule Domain.Relays.Relay.Query do
)
end
def public_or_by_account_id(queryable \\ all(), account_id) do
def public_or_by_account_id(queryable \\ not_deleted(), account_id) do
where(
queryable,
[relays: relays],
@@ -38,7 +42,7 @@ defmodule Domain.Relays.Relay.Query do
)
end
def global_or_by_account_id(queryable \\ all(), account_id) do
def global_or_by_account_id(queryable \\ not_deleted(), account_id) do
where(
queryable,
[relays: relays],
@@ -46,11 +50,11 @@ defmodule Domain.Relays.Relay.Query do
)
end
def returning_all(queryable \\ all()) do
def returning_not_deleted(queryable \\ not_deleted()) do
select(queryable, [relays: relays], relays)
end
def with_preloaded_user(queryable \\ all()) do
def with_preloaded_user(queryable \\ not_deleted()) do
with_named_binding(queryable, :user, fn queryable, binding ->
queryable
|> join(:inner, [relays: relays], user in assoc(relays, ^binding), as: ^binding)

View File

@@ -1,16 +1,16 @@
defmodule Domain.Relays.Token.Query do
use Domain, :query
def all do
def not_deleted do
from(token in Domain.Relays.Token, as: :token)
|> where([token: token], is_nil(token.deleted_at))
end
def by_id(queryable \\ all(), id) do
def by_id(queryable \\ not_deleted(), id) do
where(queryable, [token: token], token.id == ^id)
end
def by_group_id(queryable \\ all(), group_id) do
def by_group_id(queryable \\ not_deleted(), group_id) do
where(queryable, [token: token], token.group_id == ^group_id)
end
end

View File

@@ -15,7 +15,8 @@ defmodule Domain.Resources do
with :ok <- Auth.ensure_has_permissions(subject, required_permissions),
true <- Validator.valid_uuid?(id) do
Resource.Query.by_id(id)
Resource.Query.all()
|> Resource.Query.by_id(id)
|> Authorizer.for_subject(subject)
|> Repo.fetch()
|> case do
@@ -77,7 +78,7 @@ defmodule Domain.Resources do
{preload, _opts} = Keyword.pop(opts, :preload, [])
{:ok, resources} =
Resource.Query.all()
Resource.Query.not_deleted()
|> Authorizer.for_subject(subject)
|> Repo.list()
@@ -95,7 +96,7 @@ defmodule Domain.Resources do
with :ok <- Auth.ensure_has_permissions(subject, required_permissions) do
count =
Resource.Query.all()
Resource.Query.not_deleted()
|> Authorizer.for_subject(subject)
|> Resource.Query.by_gateway_group_id(gateway.group_id)
|> Repo.aggregate(:count)
@@ -114,7 +115,7 @@ defmodule Domain.Resources do
with :ok <- Auth.ensure_has_permissions(subject, required_permissions) do
resources =
Resource.Query.all()
Resource.Query.not_deleted()
|> Resource.Query.by_account_id(subject.account.id)
|> Resource.Query.by_gateway_group_id(gateway.group_id)
|> Repo.all()

View File

@@ -3,10 +3,14 @@ defmodule Domain.Resources.Resource.Query do
def all do
from(resources in Domain.Resources.Resource, as: :resources)
end
def not_deleted do
all()
|> where([resources: resources], is_nil(resources.deleted_at))
end
def by_id(queryable \\ all(), id)
def by_id(queryable \\ not_deleted(), id)
def by_id(queryable, {:in, ids}) do
where(queryable, [resources: resources], resources.id in ^ids)
@@ -16,11 +20,11 @@ defmodule Domain.Resources.Resource.Query do
where(queryable, [resources: resources], resources.id == ^id)
end
def by_account_id(queryable \\ all(), account_id) do
def by_account_id(queryable \\ not_deleted(), account_id) do
where(queryable, [resources: resources], resources.account_id == ^account_id)
end
def by_authorized_actor_id(queryable \\ all(), actor_id) do
def by_authorized_actor_id(queryable \\ not_deleted(), actor_id) do
subquery = Domain.Policies.Policy.Query.by_actor_id(actor_id)
queryable
@@ -37,7 +41,7 @@ defmodule Domain.Resources.Resource.Query do
|> select_merge([authorized_by_policies: policies], %{authorized_by_policy: policies})
end
def preload_few_actor_groups_for_each_resource(queryable \\ all(), limit) do
def preload_few_actor_groups_for_each_resource(queryable \\ not_deleted(), limit) do
queryable
|> with_joined_actor_groups(limit)
|> with_joined_policies_counts()
@@ -53,13 +57,13 @@ defmodule Domain.Resources.Resource.Query do
def with_joined_actor_groups(queryable, limit) do
policies_subquery =
Domain.Policies.Policy.Query.all()
Domain.Policies.Policy.Query.not_deleted()
|> where([policies: policies], policies.resource_id == parent_as(:resources).id)
|> select([policies: policies], policies.actor_group_id)
|> limit(^limit)
actor_groups_subquery =
Domain.Actors.Group.Query.all()
Domain.Actors.Group.Query.not_deleted()
|> where([groups: groups], groups.id in subquery(policies_subquery))
join(
@@ -81,13 +85,13 @@ defmodule Domain.Resources.Resource.Query do
)
end
def by_gateway_group_id(queryable \\ all(), gateway_group_id) do
def by_gateway_group_id(queryable \\ not_deleted(), gateway_group_id) do
queryable
|> with_joined_connections()
|> where([connections: connections], connections.gateway_group_id == ^gateway_group_id)
end
def with_joined_connections(queryable \\ all()) do
def with_joined_connections(queryable \\ not_deleted()) do
with_named_binding(queryable, :connections, fn queryable, binding ->
queryable
|> join(

View File

@@ -30,7 +30,7 @@ defmodule Domain.ActorsTest do
assert fetch_group_by_id(group.id, subject) == {:error, :not_found}
end
test "does not return deleted groups", %{
test "returns deleted groups", %{
account: account,
subject: subject
} do
@@ -38,7 +38,8 @@ defmodule Domain.ActorsTest do
Fixtures.Actors.create_group(account: account)
|> Fixtures.Actors.delete_group()
assert fetch_group_by_id(group.id, subject) == {:error, :not_found}
assert {:ok, fetched_group} = fetch_group_by_id(group.id, subject)
assert fetched_group.id == group.id
end
test "returns group by id", %{account: account, subject: subject} do
@@ -497,7 +498,7 @@ defmodule Domain.ActorsTest do
assert Enum.all?(["G:GROUP_ID1", "OU:OU_ID1"], &(&1 in delete))
assert Repo.aggregate(Actors.Group, :count) == 2
assert Repo.aggregate(Actors.Group.Query.all(), :count) == 0
assert Repo.aggregate(Actors.Group.Query.not_deleted(), :count) == 0
assert Enum.empty?(group_ids_by_provider_identifier)
end
@@ -1871,8 +1872,8 @@ defmodule Domain.ActorsTest do
assert {:ok, actor} = delete_actor(actor_to_delete, subject)
assert actor.deleted_at
assert Repo.aggregate(Domain.Clients.Client.Query.all(), :count) == 0
assert Repo.aggregate(Domain.Auth.Identity.Query.all(), :count) == 1
assert Repo.aggregate(Domain.Clients.Client.Query.not_deleted(), :count) == 0
assert Repo.aggregate(Domain.Auth.Identity.Query.not_deleted(), :count) == 1
end
test "returns error when trying to delete the last admin actor" do

View File

@@ -1258,7 +1258,7 @@ defmodule Domain.AuthTest do
assert Enum.all?(provider_identifiers, &(&1 in delete))
assert Repo.aggregate(Auth.Identity, :count) == 2
assert Repo.aggregate(Auth.Identity.Query.all(), :count) == 0
assert Repo.aggregate(Auth.Identity.Query.not_deleted(), :count) == 0
assert Enum.empty?(actor_ids_by_provider_identifier)
end
@@ -1798,7 +1798,7 @@ defmodule Domain.AuthTest do
assert Repo.aggregate(Auth.Identity.Query.all(), :count) == 3
assert delete_actor_identities(actor) == :ok
assert Repo.aggregate(Auth.Identity.Query.all(), :count) == 0
assert Repo.aggregate(Auth.Identity.Query.not_deleted(), :count) == 0
end
test "does not remove identities that belong to another actor", %{

View File

@@ -55,7 +55,7 @@ defmodule Domain.ClientsTest do
assert fetch_client_by_id("foo", subject) == {:error, :not_found}
end
test "does not return deleted clients", %{
test "returns deleted clients", %{
unprivileged_actor: actor,
unprivileged_subject: subject
} do
@@ -63,7 +63,7 @@ defmodule Domain.ClientsTest do
Fixtures.Clients.create_client(actor: actor)
|> Fixtures.Clients.delete_client()
assert fetch_client_by_id(client.id, subject) == {:error, :not_found}
assert {:ok, _client} = fetch_client_by_id(client.id, subject)
end
test "returns client by id", %{unprivileged_actor: actor, unprivileged_subject: subject} do
@@ -702,9 +702,9 @@ defmodule Domain.ClientsTest do
Fixtures.Clients.create_client(actor: actor)
Fixtures.Clients.create_client(actor: actor)
assert Repo.aggregate(Clients.Client.Query.all(), :count) == 3
assert Repo.aggregate(Clients.Client.Query.not_deleted(), :count) == 3
assert delete_actor_clients(actor) == :ok
assert Repo.aggregate(Clients.Client.Query.all(), :count) == 0
assert Repo.aggregate(Clients.Client.Query.not_deleted(), :count) == 0
end
test "does not remove clients that belong to another actor" do

View File

@@ -29,7 +29,7 @@ defmodule Domain.GatewaysTest do
assert fetch_group_by_id(group.id, subject) == {:error, :not_found}
end
test "does not return deleted groups", %{
test "returns deleted groups", %{
account: account,
subject: subject
} do
@@ -37,7 +37,8 @@ defmodule Domain.GatewaysTest do
Fixtures.Gateways.create_group(account: account)
|> Fixtures.Gateways.delete_group()
assert fetch_group_by_id(group.id, subject) == {:error, :not_found}
assert {:ok, fetched_group} = fetch_group_by_id(group.id, subject)
assert fetched_group.id == group.id
end
test "returns group by id", %{account: account, subject: subject} do
@@ -387,7 +388,7 @@ defmodule Domain.GatewaysTest do
assert fetch_gateway_by_id(gateway.id, subject) == {:error, :not_found}
end
test "does not return deleted gateways", %{
test "returns deleted gateways", %{
account: account,
subject: subject
} do
@@ -395,7 +396,7 @@ defmodule Domain.GatewaysTest do
Fixtures.Gateways.create_gateway(account: account)
|> Fixtures.Gateways.delete_gateway()
assert fetch_gateway_by_id(gateway.id, subject) == {:error, :not_found}
assert fetch_gateway_by_id(gateway.id, subject) == {:ok, gateway}
end
test "returns gateway by id", %{account: account, subject: subject} do

View File

@@ -34,12 +34,13 @@ defmodule Domain.PoliciesTest do
assert fetched_policy.id == policy.id
end
test "does not return deleted policy", %{account: account, subject: subject} do
test "returns deleted policies", %{account: account, subject: subject} do
{:ok, policy} =
Fixtures.Policies.create_policy(account: account)
|> delete_policy(subject)
assert fetch_policy_by_id(policy.id, subject) == {:error, :not_found}
assert {:ok, fetched_policy} = fetch_policy_by_id(policy.id, subject)
assert fetched_policy.id == policy.id
end
test "does not return policies in other accounts", %{subject: subject} do

View File

@@ -29,7 +29,7 @@ defmodule Domain.RelaysTest do
assert fetch_group_by_id(group.id, subject) == {:error, :not_found}
end
test "does not return deleted groups", %{
test "returns deleted groups", %{
account: account,
subject: subject
} do
@@ -37,7 +37,8 @@ defmodule Domain.RelaysTest do
Fixtures.Relays.create_group(account: account)
|> Fixtures.Relays.delete_group()
assert fetch_group_by_id(group.id, subject) == {:error, :not_found}
assert {:ok, fetched_group} = fetch_group_by_id(group.id, subject)
assert fetched_group.id == group.id
end
test "returns group by id", %{account: account, subject: subject} do
@@ -409,7 +410,7 @@ defmodule Domain.RelaysTest do
assert fetch_relay_by_id(relay.id, subject) == {:error, :not_found}
end
test "does not return deleted relays", %{
test "returns deleted relays", %{
account: account,
subject: subject
} do
@@ -417,7 +418,7 @@ defmodule Domain.RelaysTest do
Fixtures.Relays.create_relay(account: account)
|> Fixtures.Relays.delete_relay()
assert fetch_relay_by_id(relay.id, subject) == {:error, :not_found}
assert {:ok, _relay} = fetch_relay_by_id(relay.id, subject)
end
test "returns relay by id", %{account: account, subject: subject} do

View File

@@ -60,12 +60,12 @@ defmodule Domain.ResourcesTest do
refute is_nil(fetched_resource.authorized_by_policy)
end
test "does not return deleted resources", %{account: account, subject: subject} do
test "returns deleted resources", %{account: account, subject: subject} do
{:ok, resource} =
Fixtures.Resources.create_resource(account: account)
|> delete_resource(subject)
assert fetch_resource_by_id(resource.id, subject) == {:error, :not_found}
assert {:ok, _resource} = fetch_resource_by_id(resource.id, subject)
end
test "does not return resources in other accounts", %{subject: subject} do

View File

@@ -6,6 +6,7 @@ defmodule Web.Actors.Edit do
def mount(%{"id" => id}, _session, socket) do
with {:ok, actor} <-
Actors.fetch_actor_by_id(id, socket.assigns.subject, preload: [:memberships]),
nil <- actor.deleted_at,
{:ok, groups} <- Actors.list_groups(socket.assigns.subject) do
changeset = Actors.change_actor(actor)
@@ -19,7 +20,7 @@ defmodule Web.Actors.Edit do
page_title: "Edit actor #{actor.name}"
]}
else
{:error, _reason} -> raise Web.LiveErrors.NotFoundError
_other -> raise Web.LiveErrors.NotFoundError
end
end

View File

@@ -25,7 +25,7 @@ defmodule Web.Actors.Show do
flow_activities_enabled?: Domain.Config.flow_activities_enabled?()
)}
else
{:error, _reason} -> raise Web.LiveErrors.NotFoundError
_other -> raise Web.LiveErrors.NotFoundError
end
end
@@ -42,8 +42,9 @@ defmodule Web.Actors.Show do
<:title>
<%= actor_type(@actor.type) %>: <span class="font-bold"><%= @actor.name %></span>
<span :if={@actor.id == @subject.actor.id} class="text-gray-400">(you)</span>
<span :if={not is_nil(@actor.deleted_at)} class="text-red-600">(deleted)</span>
</:title>
<:action>
<:action :if={is_nil(@actor.deleted_at)}>
<.edit_button navigate={~p"/#{@account}/actors/#{@actor}/edit"}>
Edit <%= actor_type(@actor.type) %>
</.edit_button>
@@ -90,7 +91,7 @@ defmodule Web.Actors.Show do
<.section>
<:title>Authentication Identities</:title>
<:action>
<:action :if={is_nil(@actor.deleted_at)}>
<.add_button
:if={@actor.type == :service_account}
navigate={~p"/#{@account}/actors/service_accounts/#{@actor}/new_identity"}
@@ -98,7 +99,7 @@ defmodule Web.Actors.Show do
Create Token
</.add_button>
</:action>
<:action>
<:action :if={is_nil(@actor.deleted_at)}>
<.add_button
:if={@actor.type != :service_account}
navigate={~p"/#{@account}/actors/users/#{@actor}/new_identity"}
@@ -139,13 +140,13 @@ defmodule Web.Actors.Show do
No authentication identities to display
</div>
<.add_button
:if={@actor.type == :service_account}
:if={is_nil(@actor.deleted_at) and @actor.type == :service_account}
navigate={~p"/#{@account}/actors/service_accounts/#{@actor}/new_identity"}
>
Create Token
</.add_button>
<.add_button
:if={@actor.type != :service_account}
:if={is_nil(@actor.deleted_at) and @actor.type != :service_account}
navigate={~p"/#{@account}/actors/users/#{@actor}/new_identity"}
>
Create Identity
@@ -175,9 +176,6 @@ defmodule Web.Actors.Show do
</:col>
<:empty>
<div class="text-center text-slate-500 p-4">No clients to display</div>
<div class="text-center text-slate-500 mb-4">
Clients are created automatically when user connects to a resource.
</div>
</:empty>
</.table>
</:content>
@@ -231,7 +229,7 @@ defmodule Web.Actors.Show do
</:content>
</.section>
<.danger_zone>
<.danger_zone :if={is_nil(@actor.deleted_at)}>
<:action>
<.delete_button
:if={not Actors.actor_synced?(@actor)}

View File

@@ -3,11 +3,12 @@ defmodule Web.Clients.Edit do
alias Domain.Clients
def mount(%{"id" => id}, _session, socket) do
with {:ok, client} <- Clients.fetch_client_by_id(id, socket.assigns.subject) do
with {:ok, client} <- Clients.fetch_client_by_id(id, socket.assigns.subject),
nil <- client.deleted_at do
changeset = Clients.change_client(client)
{:ok, assign(socket, client: client, form: to_form(changeset))}
else
{:error, _reason} -> raise Web.LiveErrors.NotFoundError
_other -> raise Web.LiveErrors.NotFoundError
end
end

View File

@@ -37,8 +37,9 @@ defmodule Web.Clients.Show do
<.section>
<:title>
Client Details
<span :if={not is_nil(@client.deleted_at)} class="text-red-600">(deleted)</span>
</:title>
<:action>
<:action :if={is_nil(@client.deleted_at)}>
<.edit_button navigate={~p"/#{@account}/clients/#{@client}/edit"}>
Edit Client
</.edit_button>
@@ -143,7 +144,7 @@ defmodule Web.Clients.Show do
</:content>
</.section>
<.danger_zone>
<.danger_zone :if={is_nil(@client.deleted_at)}>
<:action>
<.delete_button
phx-click="delete"

View File

@@ -36,6 +36,7 @@ defmodule Web.Gateways.Show do
<.section>
<:title>
Gateway: <code><%= @gateway.name %></code>
<span :if={not is_nil(@gateway.deleted_at)} class="text-red-600">(deleted)</span>
</:title>
<:content>
<.vertical_table id="gateway">
@@ -102,7 +103,7 @@ defmodule Web.Gateways.Show do
</:content>
</.section>
<.danger_zone>
<.danger_zone :if={is_nil(@gateway.deleted_at)}>
<:action>
<.delete_button
phx-click="delete"

View File

@@ -5,6 +5,7 @@ defmodule Web.Groups.Edit do
def mount(%{"id" => id}, _session, socket) do
with {:ok, group} <-
Actors.fetch_group_by_id(id, socket.assigns.subject, preload: [:memberships]),
nil <- group.deleted_at,
false <- Actors.group_synced?(group) do
changeset = Actors.change_group(group)
{:ok, assign(socket, group: group, form: to_form(changeset))}

View File

@@ -6,6 +6,7 @@ defmodule Web.Groups.EditActors do
def mount(%{"id" => id}, _session, socket) do
with {:ok, group} <-
Actors.fetch_group_by_id(id, socket.assigns.subject, preload: [:memberships]),
nil <- group.deleted_at,
false <- Actors.group_synced?(group),
{:ok, actors} <-
Actors.list_actors(socket.assigns.subject, preload: [identities: :provider]) do

View File

@@ -31,8 +31,9 @@ defmodule Web.Groups.Show do
<.section>
<:title>
Group: <code><%= @group.name %></code>
<span :if={not is_nil(@group.deleted_at)} class="text-red-600">(deleted)</span>
</:title>
<:action>
<:action :if={is_nil(@group.deleted_at)}>
<.edit_button
:if={not Actors.group_synced?(@group)}
navigate={~p"/#{@account}/groups/#{@group}/edit"}
@@ -58,7 +59,7 @@ defmodule Web.Groups.Show do
<.section>
<:title>Actors</:title>
<:action>
<:action :if={is_nil(@group.deleted_at)}>
<.edit_button
:if={not Actors.group_synced?(@group)}
navigate={~p"/#{@account}/groups/#{@group}/edit_actors"}
@@ -85,7 +86,7 @@ defmodule Web.Groups.Show do
No actors in group
</div>
<.edit_button
:if={not Actors.group_synced?(@group)}
:if={is_nil(@group.deleted_at)}
navigate={~p"/#{@account}/groups/#{@group}/edit"}
>
Edit Group
@@ -100,7 +101,7 @@ defmodule Web.Groups.Show do
</:content>
</.section>
<.danger_zone>
<.danger_zone :if={is_nil(@group.deleted_at)}>
<:action>
<.delete_button
phx-click="delete"

View File

@@ -7,7 +7,8 @@ defmodule Web.Policies.Edit do
with {:ok, policy} <-
Policies.fetch_policy_by_id(id, socket.assigns.subject,
preload: [:actor_group, :resource]
) do
),
nil <- policy.deleted_at do
form = to_form(Policies.Policy.Changeset.update(policy, %{}))
socket = assign(socket, policy: policy, page_title: "Edit Policy", form: form)
{:ok, socket, temporary_assigns: [form: %Phoenix.HTML.Form{}]}

View File

@@ -38,8 +38,9 @@ defmodule Web.Policies.Show do
<.section>
<:title>
<%= @page_title %>: <code><%= @policy.id %></code>
<span :if={not is_nil(@policy.deleted_at)} class="text-red-600">(deleted)</span>
</:title>
<:action>
<:action :if={is_nil(@policy.deleted_at)}>
<.edit_button navigate={~p"/#{@account}/policies/#{@policy}/edit"}>
Edit Policy
</.edit_button>
@@ -62,6 +63,9 @@ defmodule Web.Policies.Show do
<.link navigate={~p"/#{@account}/groups/#{@policy.actor_group_id}"} class={link_style()}>
<%= @policy.actor_group.name %>
</.link>
<span :if={not is_nil(@policy.actor_group.deleted_at)} class="text-red-600">
(deleted)
</span>
</:value>
</.vertical_table_row>
<.vertical_table_row>
@@ -72,6 +76,9 @@ defmodule Web.Policies.Show do
<.link navigate={~p"/#{@account}/resources/#{@policy.resource_id}"} class={link_style()}>
<%= @policy.resource.name %>
</.link>
<span :if={not is_nil(@policy.resource.deleted_at)} class="text-red-600">
(deleted)
</span>
</:value>
</.vertical_table_row>
<.vertical_table_row>
@@ -140,7 +147,7 @@ defmodule Web.Policies.Show do
</:content>
</.section>
<.danger_zone>
<.danger_zone :if={is_nil(@policy.deleted_at)}>
<:action>
<.delete_button
phx-click="delete"

View File

@@ -4,7 +4,8 @@ defmodule Web.RelayGroups.Edit do
def mount(%{"id" => id}, _session, socket) do
with true <- Domain.Config.self_hosted_relays_enabled?(),
{:ok, group} <- Relays.fetch_group_by_id(id, socket.assigns.subject) do
{:ok, group} <- Relays.fetch_group_by_id(id, socket.assigns.subject),
nil <- group.deleted_at do
changeset = Relays.change_group(group)
{:ok, assign(socket, group: group, form: to_form(changeset))}
else

View File

@@ -30,8 +30,9 @@ defmodule Web.RelayGroups.Show do
<.section>
<:title>
Relay Instance Group: <code><%= @group.name %></code>
<span :if={not is_nil(@group.deleted_at)} class="text-red-600">(deleted)</span>
</:title>
<:action :if={@group.account_id}>
<:action :if={not is_nil(@group.account_id) and is_nil(@group.deleted_at)}>
<.edit_button navigate={~p"/#{@account}/relay_groups/#{@group}/edit"}>
Edit Instance Group
</.edit_button>
@@ -56,7 +57,7 @@ defmodule Web.RelayGroups.Show do
<.section>
<:title>Relays</:title>
<:action>
<:action :if={not is_nil(@group.account_id) and is_nil(@group.deleted_at)}>
<.add_button navigate={~p"/#{@account}/relay_groups/#{@group}/new_token"}>
Deploy
</.add_button>
@@ -94,7 +95,7 @@ defmodule Web.RelayGroups.Show do
</:content>
</.section>
<.danger_zone>
<.danger_zone :if={not is_nil(@group.account_id) and is_nil(@group.deleted_at)}>
<:action :if={@group.account_id}>
<.delete_button
phx-click="delete"

View File

@@ -43,6 +43,7 @@ defmodule Web.Relays.Show do
<code><%= @relay.ipv4 %></code>
</:item>
</.intersperse_blocks>
<span :if={not is_nil(@relay.deleted_at)} class="text-red-600">(deleted)</span>
</:title>
<:content>
<div class="bg-white overflow-hidden">
@@ -112,9 +113,9 @@ defmodule Web.Relays.Show do
</:content>
</.section>
<.danger_zone>
<.danger_zone :if={is_nil(@relay.deleted_at)}>
<:action :if={@relay.account_id}>
<.delete_button phx-click="delete">
<.delete_button phx-click="delete" data-confirm="Are you sure you want to delete this relay?">
Delete Relay
</.delete_button>
</:action>

View File

@@ -6,6 +6,7 @@ defmodule Web.Resources.Edit do
def mount(%{"id" => id} = params, _session, socket) do
with {:ok, resource} <-
Resources.fetch_resource_by_id(id, socket.assigns.subject, preload: :gateway_groups),
nil <- resource.deleted_at,
{:ok, gateway_groups} <- Gateways.list_groups(socket.assigns.subject) do
form = Resources.change_resource(resource, socket.assigns.subject) |> to_form()

View File

@@ -41,8 +41,9 @@ defmodule Web.Resources.Show do
<.section>
<:title>
Resource: <code><%= @resource.name %></code>
<span :if={not is_nil(@resource.deleted_at)} class="text-red-600">(deleted)</span>
</:title>
<:action>
<:action :if={is_nil(@resource.deleted_at)}>
<.edit_button navigate={~p"/#{@account}/resources/#{@resource.id}/edit?#{@params}"}>
Edit Resource
</.edit_button>
@@ -232,7 +233,7 @@ defmodule Web.Resources.Show do
</:content>
</.section>
<.danger_zone>
<.danger_zone :if={is_nil(@resource.deleted_at)}>
<:action>
<.delete_button
data-confirm="Are you sure want to delete this resource?"

View File

@@ -4,11 +4,12 @@ defmodule Web.Sites.Edit do
alias Domain.Gateways
def mount(%{"id" => id}, _session, socket) do
with {:ok, group} <- Gateways.fetch_group_by_id(id, socket.assigns.subject) do
with {:ok, group} <- Gateways.fetch_group_by_id(id, socket.assigns.subject),
nil <- group.deleted_at do
changeset = Gateways.change_group(group)
{:ok, assign(socket, group: group, form: to_form(changeset))}
else
{:error, _reason} -> raise Web.LiveErrors.NotFoundError
_other -> raise Web.LiveErrors.NotFoundError
end
end

View File

@@ -46,8 +46,9 @@ defmodule Web.Sites.Show do
<.section>
<:title>
Site: <code><%= @group.name %></code>
<span :if={not is_nil(@group.deleted_at)} class="text-red-600">(deleted)</span>
</:title>
<:action>
<:action :if={is_nil(@group.deleted_at)}>
<.edit_button navigate={~p"/#{@account}/sites/#{@group}/edit"}>
Edit Site
</.edit_button>
@@ -80,7 +81,7 @@ defmodule Web.Sites.Show do
see all <.icon name="hero-arrow-right" class="w-2 h-2" />
</.link>
</:title>
<:action>
<:action :if={is_nil(@group.deleted_at)}>
<.add_button navigate={~p"/#{@account}/sites/#{@group}/new_token"}>
Deploy
</.add_button>
@@ -112,12 +113,12 @@ defmodule Web.Sites.Show do
<div class="pb-4">
No gateways to display.
</div>
<div class="pb-4">
<div :if={is_nil(@group.deleted_at)} class="pb-4">
<.add_button navigate={~p"/#{@account}/sites/#{@group}/new_token"}>
Deploy a Gateway
</.add_button>
</div>
<div>
<div :if={is_nil(@group.deleted_at)}>
<p>
Deploy gateways to terminate connections to your site's resources. All gateways deployed within a site must be able to reach all its resources.
</p>
@@ -133,7 +134,7 @@ defmodule Web.Sites.Show do
<:title>
Resources
</:title>
<:action>
<:action :if={is_nil(@group.deleted_at)}>
<.add_button navigate={~p"/#{@account}/resources/new?site_id=#{@group}"}>
Create
</.add_button>
@@ -212,7 +213,7 @@ defmodule Web.Sites.Show do
</:content>
</.section>
<.danger_zone>
<.danger_zone :if={is_nil(@group.deleted_at)}>
<:action>
<.delete_button
phx-click="delete"

View File

@@ -14,20 +14,26 @@ defmodule Web.Live.Actors.ShowTest do
}}}
end
test "renders not found error when actor is deleted", %{conn: conn} do
test "renders deleted actor without action buttons", %{conn: conn} do
account = Fixtures.Accounts.create_account()
actor =
Fixtures.Actors.create_actor(type: :account_admin_user, account: account)
|> Fixtures.Actors.delete()
identity = Fixtures.Auth.create_identity(account: account, actor: actor)
auth_actor = Fixtures.Actors.create_actor(type: :account_admin_user, account: account)
auth_identity = Fixtures.Auth.create_identity(account: account, actor: auth_actor)
assert_raise Web.LiveErrors.NotFoundError, fn ->
{:ok, _lv, html} =
conn
|> authorize_conn(identity)
|> authorize_conn(auth_identity)
|> live(~p"/#{account}/actors/#{actor}")
end
assert html =~ "(deleted)"
refute html =~ "Danger Zone"
refute html =~ "Add"
refute html =~ "Edit"
refute html =~ "Deploy"
end
test "renders breadcrumbs item", %{conn: conn} do
@@ -46,7 +52,7 @@ defmodule Web.Live.Actors.ShowTest do
assert breadcrumbs =~ actor.name
end
test "renders logs table", %{
test "renders flows table", %{
conn: conn
} do
account = Fixtures.Accounts.create_account()
@@ -85,6 +91,87 @@ defmodule Web.Live.Actors.ShowTest do
"#{flow.gateway.group.name}-#{flow.gateway.name} (189.172.73.153)"
end
test "renders flows even for deleted policies", %{
conn: conn
} do
account = Fixtures.Accounts.create_account()
actor = Fixtures.Actors.create_actor(type: :account_admin_user, account: account)
identity = Fixtures.Auth.create_identity(account: account, actor: actor)
client = Fixtures.Clients.create_client(account: account, actor: actor)
flow =
Fixtures.Flows.create_flow(
account: account,
client: client
)
flow = Repo.preload(flow, [:client, gateway: [:group], policy: [:actor_group, :resource]])
Fixtures.Policies.delete_policy(flow.policy)
{:ok, lv, _html} =
conn
|> authorize_conn(identity)
|> live(~p"/#{account}/actors/#{actor}")
[row] =
lv
|> element("#flows")
|> render()
|> table_to_map()
assert row["authorized at"]
assert row["expires at"]
assert row["policy"] =~ flow.policy.actor_group.name
assert row["policy"] =~ flow.policy.resource.name
assert row["client (ip)"] ==
"#{flow.client.name} (#{client.last_seen_remote_ip})"
assert row["gateway (ip)"] ==
"#{flow.gateway.group.name}-#{flow.gateway.name} (189.172.73.153)"
end
test "renders flows even for deleted policy assocs", %{
conn: conn
} do
account = Fixtures.Accounts.create_account()
actor = Fixtures.Actors.create_actor(type: :account_admin_user, account: account)
identity = Fixtures.Auth.create_identity(account: account, actor: actor)
client = Fixtures.Clients.create_client(account: account, actor: actor)
flow =
Fixtures.Flows.create_flow(
account: account,
client: client
)
flow = Repo.preload(flow, [:client, gateway: [:group], policy: [:actor_group, :resource]])
Fixtures.Actors.delete_group(flow.policy.actor_group)
Fixtures.Resources.delete_resource(flow.policy.resource)
{:ok, lv, _html} =
conn
|> authorize_conn(identity)
|> live(~p"/#{account}/actors/#{actor}")
[row] =
lv
|> element("#flows")
|> render()
|> table_to_map()
assert row["authorized at"]
assert row["expires at"]
assert row["policy"] =~ flow.policy.actor_group.name
assert row["policy"] =~ flow.policy.resource.name
assert row["client (ip)"] ==
"#{flow.client.name} (#{client.last_seen_remote_ip})"
assert row["gateway (ip)"] ==
"#{flow.gateway.group.name}-#{flow.gateway.name} (189.172.73.153)"
end
describe "users" do
setup do
account = Fixtures.Accounts.create_account()

View File

@@ -32,7 +32,7 @@ defmodule Web.Live.Clients.ShowTest do
}}}
end
test "renders not found error when client is deleted", %{
test "renders deleted client without action buttons", %{
account: account,
client: client,
identity: identity,
@@ -40,11 +40,17 @@ defmodule Web.Live.Clients.ShowTest do
} do
client = Fixtures.Clients.delete_client(client)
assert_raise Web.LiveErrors.NotFoundError, fn ->
{:ok, _lv, html} =
conn
|> authorize_conn(identity)
|> live(~p"/#{account}/clients/#{client}")
end
assert html =~ "(deleted)"
refute html =~ "Danger Zone"
refute html =~ "Add"
refute html =~ "Delete"
refute html =~ "Edit"
refute html =~ "Deploy"
end
test "renders breadcrumbs item", %{
@@ -112,7 +118,7 @@ defmodule Web.Live.Clients.ShowTest do
|> Map.fetch!("owner") =~ actor.name
end
test "renders logs table", %{
test "renders flows table", %{
account: account,
identity: identity,
client: client,
@@ -147,6 +153,79 @@ defmodule Web.Live.Clients.ShowTest do
"#{flow.gateway.group.name}-#{flow.gateway.name} (189.172.73.153)"
end
test "renders flows even for deleted policies", %{
account: account,
identity: identity,
client: client,
conn: conn
} do
flow =
Fixtures.Flows.create_flow(
account: account,
client: client
)
flow = Repo.preload(flow, [:client, gateway: [:group], policy: [:actor_group, :resource]])
Fixtures.Policies.delete_policy(flow.policy)
{:ok, lv, _html} =
conn
|> authorize_conn(identity)
|> live(~p"/#{account}/clients/#{client}")
[row] =
lv
|> element("#flows")
|> render()
|> table_to_map()
assert row["authorized at"]
assert row["expires at"]
assert row["remote ip"] == to_string(client.last_seen_remote_ip)
assert row["policy"] =~ flow.policy.actor_group.name
assert row["policy"] =~ flow.policy.resource.name
assert row["gateway (ip)"] ==
"#{flow.gateway.group.name}-#{flow.gateway.name} (189.172.73.153)"
end
test "renders flows even for deleted policy assocs", %{
account: account,
identity: identity,
client: client,
conn: conn
} do
flow =
Fixtures.Flows.create_flow(
account: account,
client: client
)
flow = Repo.preload(flow, [:client, gateway: [:group], policy: [:actor_group, :resource]])
Fixtures.Actors.delete_group(flow.policy.actor_group)
Fixtures.Resources.delete_resource(flow.policy.resource)
{:ok, lv, _html} =
conn
|> authorize_conn(identity)
|> live(~p"/#{account}/clients/#{client}")
[row] =
lv
|> element("#flows")
|> render()
|> table_to_map()
assert row["authorized at"]
assert row["expires at"]
assert row["remote ip"] == to_string(client.last_seen_remote_ip)
assert row["policy"] =~ flow.policy.actor_group.name
assert row["policy"] =~ flow.policy.resource.name
assert row["gateway (ip)"] ==
"#{flow.gateway.group.name}-#{flow.gateway.name} (189.172.73.153)"
end
test "allows editing clients", %{
account: account,
client: client,

View File

@@ -33,7 +33,7 @@ defmodule Web.Live.Gateways.ShowTest do
}}}
end
test "renders not found error when gateway is deleted", %{
test "renders deleted gateway without action buttons", %{
account: account,
gateway: gateway,
identity: identity,
@@ -41,11 +41,16 @@ defmodule Web.Live.Gateways.ShowTest do
} do
gateway = Fixtures.Gateways.delete_gateway(gateway)
assert_raise Web.LiveErrors.NotFoundError, fn ->
{:ok, _lv, html} =
conn
|> authorize_conn(identity)
|> live(~p"/#{account}/gateways/#{gateway}")
end
assert html =~ "(deleted)"
refute html =~ "Danger Zone"
refute html =~ "Add"
refute html =~ "Delete"
refute html =~ "Edit"
end
test "renders breadcrumbs item", %{

View File

@@ -30,7 +30,7 @@ defmodule Web.Live.Groups.ShowTest do
}}}
end
test "renders not found error when group is deleted", %{
test "renders deleted group without action buttons", %{
account: account,
group: group,
identity: identity,
@@ -38,11 +38,17 @@ defmodule Web.Live.Groups.ShowTest do
} do
group = Fixtures.Actors.delete_group(group)
assert_raise Web.LiveErrors.NotFoundError, fn ->
{:ok, _lv, html} =
conn
|> authorize_conn(identity)
|> live(~p"/#{account}/groups/#{group}")
end
assert html =~ "(deleted)"
refute html =~ "Danger Zone"
refute html =~ "Add"
refute html =~ "Delete"
refute html =~ "Edit"
refute html =~ "Deploy"
end
test "renders breadcrumbs item", %{

View File

@@ -43,7 +43,7 @@ defmodule Web.Live.Policies.ShowTest do
}}}
end
test "renders not found error when gateway is deleted", %{
test "renders deleted gateway group without action buttons", %{
account: account,
policy: policy,
identity: identity,
@@ -51,11 +51,17 @@ defmodule Web.Live.Policies.ShowTest do
} do
policy = Fixtures.Policies.delete_policy(policy)
assert_raise Web.LiveErrors.NotFoundError, fn ->
{:ok, _lv, html} =
conn
|> authorize_conn(identity)
|> live(~p"/#{account}/policies/#{policy}")
end
assert html =~ "(deleted)"
refute html =~ "Danger Zone"
refute html =~ "Add"
refute html =~ "Delete"
refute html =~ "Edit"
refute html =~ "Deploy"
end
test "renders breadcrumbs item", %{

View File

@@ -35,7 +35,7 @@ defmodule Web.Live.RelayGroups.ShowTest do
}}}
end
test "renders not found error when relay is deleted", %{
test "renders deleted relay without action buttons", %{
account: account,
group: group,
identity: identity,
@@ -43,11 +43,17 @@ defmodule Web.Live.RelayGroups.ShowTest do
} do
group = Fixtures.Relays.delete_group(group)
assert_raise Web.LiveErrors.NotFoundError, fn ->
{:ok, _lv, html} =
conn
|> authorize_conn(identity)
|> live(~p"/#{account}/relay_groups/#{group}")
end
assert html =~ "(deleted)"
refute html =~ "Danger Zone"
refute html =~ "Add"
refute html =~ "Delete"
refute html =~ "Edit"
refute html =~ "Deploy"
end
test "renders breadcrumbs item", %{

View File

@@ -33,7 +33,7 @@ defmodule Web.Live.Relays.ShowTest do
}}}
end
test "renders not found error when relay is deleted", %{
test "renders deleted relay without action buttons", %{
account: account,
relay: relay,
identity: identity,
@@ -41,11 +41,16 @@ defmodule Web.Live.Relays.ShowTest do
} do
relay = Fixtures.Relays.delete_relay(relay)
assert_raise Web.LiveErrors.NotFoundError, fn ->
{:ok, _lv, html} =
conn
|> authorize_conn(identity)
|> live(~p"/#{account}/relays/#{relay}")
end
assert html =~ "(deleted)"
refute html =~ "Danger Zone"
refute html =~ "Add"
refute html =~ "Delete"
refute html =~ "Edit"
end
test "renders breadcrumbs item", %{

View File

@@ -43,7 +43,7 @@ defmodule Web.Live.Resources.ShowTest do
}}}
end
test "renders not found error when resource is deleted", %{
test "renders deleted resource without action buttons", %{
account: account,
resource: resource,
identity: identity,
@@ -51,11 +51,16 @@ defmodule Web.Live.Resources.ShowTest do
} do
resource = Fixtures.Resources.delete_resource(resource)
assert_raise Web.LiveErrors.NotFoundError, fn ->
{:ok, _lv, html} =
conn
|> authorize_conn(identity)
|> live(~p"/#{account}/resources/#{resource}")
end
assert html =~ "(deleted)"
refute html =~ "Danger Zone"
refute html =~ "Delete"
refute html =~ "Edit"
refute html =~ "Deploy"
end
test "renders breadcrumbs item", %{

View File

@@ -1,4 +1,4 @@
defmodule Web.Live.Nav.SidebarTest do
defmodule Web.SidebarTest do
use Web.ConnCase, async: true
setup do

View File

@@ -35,7 +35,7 @@ defmodule Web.Live.Sites.ShowTest do
}}}
end
test "renders not found error when gateway is deleted", %{
test "renders deleted gateway group without action buttons", %{
account: account,
group: group,
identity: identity,
@@ -43,11 +43,17 @@ defmodule Web.Live.Sites.ShowTest do
} do
group = Fixtures.Gateways.delete_group(group)
assert_raise Web.LiveErrors.NotFoundError, fn ->
{:ok, _lv, html} =
conn
|> authorize_conn(identity)
|> live(~p"/#{account}/sites/#{group}")
end
assert html =~ "(deleted)"
refute html =~ "Danger Zone"
refute html =~ "Add"
refute html =~ "Delete"
refute html =~ "Edit"
refute html =~ "Deploy"
end
test "renders breadcrumbs item", %{