refactor(portal): remove soft delete elements from portal code (#10607)

Why:

* In previous commits, the portal code had been updated to use hard
deletion rather than soft deletion of data. The fields used in the soft
deletion were still kept in the DB and the code to allow for zero
downtime rollout and an easy rollback if necessary. To continue with
that work the portal code has now been updated to remove any reference
to the soft deleted fields (e.g. deleted_at, persistent_id, etc...).
While the code has been updated the actual data in the DB will need to
remain for now, to once again allow for a zero downtime rollout. Once
this commit has been deployed to production another PR can follow to
remove the columns from the necessary tables in the DB.


Related: #8187
This commit is contained in:
Brian Manifold
2025-10-18 10:02:26 -07:00
committed by GitHub
parent 9b6ebb01ed
commit 27565ea5c8
132 changed files with 689 additions and 3976 deletions

View File

@@ -65,7 +65,7 @@ defmodule API.ActorGroupMembershipController do
) do
subject = conn.assigns.subject
preload = [:memberships]
filter = [deleted?: false, editable?: true]
filter = [editable?: true]
with {:ok, group} <-
Actors.fetch_group_by_id(actor_group_id, subject, preload: preload, filter: filter),
@@ -105,7 +105,7 @@ defmodule API.ActorGroupMembershipController do
remove = Map.get(params, "remove", [])
subject = conn.assigns.subject
preload = [:memberships]
filter = [deleted?: false, editable?: true]
filter = [editable?: true]
with {:ok, group} <-
Actors.fetch_group_by_id(actor_group_id, subject, preload: preload, filter: filter),

View File

@@ -43,7 +43,7 @@ defmodule API.PolicyController do
# Show a specific Policy
def show(conn, %{"id" => id}) do
with {:ok, policy} <- Policies.fetch_policy_by_id_or_persistent_id(id, conn.assigns.subject) do
with {:ok, policy} <- Policies.fetch_policy_by_id(id, conn.assigns.subject) do
render(conn, :show, policy: policy)
end
end
@@ -91,7 +91,7 @@ defmodule API.PolicyController do
def update(conn, %{"id" => id, "policy" => params}) do
subject = conn.assigns.subject
with {:ok, policy} <- Policies.fetch_policy_by_id_or_persistent_id(id, subject) do
with {:ok, policy} <- Policies.fetch_policy_by_id(id, subject) do
case Policies.update_policy(policy, params, subject) do
{:ok, policy} ->
render(conn, :show, policy: policy)
@@ -124,7 +124,7 @@ defmodule API.PolicyController do
def delete(conn, %{"id" => id}) do
subject = conn.assigns.subject
with {:ok, policy} <- Policies.fetch_policy_by_id_or_persistent_id(id, subject),
with {:ok, policy} <- Policies.fetch_policy_by_id(id, subject),
{:ok, policy} <- Policies.delete_policy(policy, subject) do
render(conn, :show, policy: policy)
end

View File

@@ -43,7 +43,7 @@ defmodule API.ResourceController do
def show(conn, %{"id" => id}) do
with {:ok, resource} <-
Resources.fetch_resource_by_id_or_persistent_id(id, conn.assigns.subject) do
Resources.fetch_resource_by_id(id, conn.assigns.subject) do
render(conn, :show, resource: resource)
end
end
@@ -92,7 +92,7 @@ defmodule API.ResourceController do
subject = conn.assigns.subject
attrs = set_param_defaults(params)
with {:ok, resource} <- Resources.fetch_active_resource_by_id_or_persistent_id(id, subject) do
with {:ok, resource} <- Resources.fetch_resource_by_id(id, subject) do
case Resources.update_resource(resource, attrs, subject) do
{:ok, updated_resource} ->
render(conn, :show, resource: updated_resource)
@@ -124,7 +124,7 @@ defmodule API.ResourceController do
def delete(conn, %{"id" => id}) do
subject = conn.assigns.subject
with {:ok, resource} <- Resources.fetch_resource_by_id_or_persistent_id(id, subject),
with {:ok, resource} <- Resources.fetch_resource_by_id(id, subject),
{:ok, resource} <- Resources.delete_resource(resource, subject) do
render(conn, :show, resource: resource)
end

View File

@@ -102,8 +102,9 @@ defmodule API.Gateway.ChannelTest do
capture_log(fn ->
send(socket.channel_pid, %Changes.Change{lsn: 50})
# Wait for the channel to process and emit the log
Process.sleep(1)
# Force the channel to process the message before continuing
# :sys.get_state/1 is synchronous and will wait for all pending messages to be handled
:sys.get_state(socket.channel_pid)
end)
assert message =~ "[warning] Out of order or duplicate change received; ignoring"

View File

@@ -60,7 +60,8 @@ defmodule API.Relay.ChannelTest do
describe "handle_in/3 for unknown messages" do
test "it doesn't crash", %{socket: socket} do
ref = push(socket, "unknown_message", %{})
assert_reply ref, :error, %{reason: :unknown_message}
assert_reply ref, :error, %{reason: :unknown_message}, 1000
end
end
end

View File

@@ -11,7 +11,7 @@ defmodule Domain.Accounts do
def all_accounts_by_ids!(ids) do
if Enum.all?(ids, &Repo.valid_uuid?/1) do
Account.Query.not_deleted()
Account.Query.all()
|> Account.Query.by_id({:in, ids})
|> Repo.all()
else
@@ -43,7 +43,7 @@ defmodule Domain.Accounts do
def fetch_account_by_id(id, %Auth.Subject{} = subject, opts \\ []) do
with :ok <- Auth.ensure_has_permissions(subject, Authorizer.manage_own_account_permission()),
true <- Repo.valid_uuid?(id) do
Account.Query.not_deleted()
Account.Query.all()
|> Account.Query.by_id(id)
|> Authorizer.for_subject(subject)
|> Repo.fetch(Account.Query, opts)
@@ -58,13 +58,13 @@ defmodule Domain.Accounts do
def fetch_account_by_id_or_slug("", _opts), do: {:error, :not_found}
def fetch_account_by_id_or_slug(id_or_slug, opts) do
Account.Query.not_deleted()
Account.Query.all()
|> Account.Query.by_id_or_slug(id_or_slug)
|> Repo.fetch(Account.Query, opts)
end
def fetch_account_by_id!(id) do
Account.Query.not_deleted()
Account.Query.all()
|> Account.Query.by_id(id)
|> Repo.one!()
end
@@ -80,7 +80,7 @@ defmodule Domain.Accounts do
def update_account(%Account{} = account, attrs, %Auth.Subject{} = subject) do
with :ok <- Auth.ensure_has_permissions(subject, Authorizer.manage_own_account_permission()) do
Account.Query.not_deleted()
Account.Query.all()
|> Account.Query.by_id(account.id)
|> Authorizer.for_subject(subject)
|> Repo.fetch_and_update(Account.Query,
@@ -118,8 +118,7 @@ defmodule Domain.Accounts do
Map.fetch!(account.features || %Features{}, feature) || false
end
# TODO: HARD-DELETE - Update after `deleted_at` is removed from DB
def account_active?(%{deleted_at: nil, disabled_at: nil}), do: true
def account_active?(%{disabled_at: nil}), do: true
def account_active?(_account), do: false
def ensure_has_access_to(%Auth.Subject{} = subject, %Account{} = account) do
@@ -134,7 +133,7 @@ defmodule Domain.Accounts do
slug_candidate = Domain.NameGenerator.generate_slug()
queryable =
Account.Query.not_deleted()
Account.Query.all()
|> Account.Query.by_slug(slug_candidate)
if Repo.exists?(queryable) do

View File

@@ -25,41 +25,31 @@ defmodule Domain.Accounts.Account do
# We mention all schemas here to leverage Ecto compile-time reference checks,
# because later we will have to shard data by account_id.
# TODO: HARD-DELETE - Remove `where` after `deleted_at` is removed from the DB
has_many :actors, Domain.Actors.Actor, where: [deleted_at: nil]
has_many :actor_group_memberships, Domain.Actors.Membership, where: [deleted_at: nil]
has_many :actor_groups, Domain.Actors.Group, where: [deleted_at: nil]
has_many :actors, Domain.Actors.Actor
has_many :actor_group_memberships, Domain.Actors.Membership
has_many :actor_groups, Domain.Actors.Group
# TODO: HARD-DELETE - Remove `where` after `deleted_at` is removed from the DB
has_many :auth_providers, Domain.Auth.Provider, where: [deleted_at: nil]
has_many :auth_identities, Domain.Auth.Identity, where: [deleted_at: nil]
has_many :auth_providers, Domain.Auth.Provider
has_many :auth_identities, Domain.Auth.Identity
# TODO: HARD-DELETE - Remove `where` after `deleted_at` is removed from the DB
has_many :network_addresses, Domain.Network.Address, where: [deleted_at: nil]
has_many :network_addresses, Domain.Network.Address
# TODO: HARD-DELETE - Remove `where` after `deleted_at` is removed from the DB
has_many :policies, Domain.Policies.Policy, where: [deleted_at: nil]
has_many :policies, Domain.Policies.Policy
# TODO: HARD-DELETE - Remove `where` after `deleted_at` is removed from the DB
has_many :flows, Domain.Flows.Flow, where: [deleted_at: nil]
has_many :flows, Domain.Flows.Flow
# TODO: HARD-DELETE - Remove `where` after `deleted_at` is removed from the DB
has_many :resources, Domain.Resources.Resource, where: [deleted_at: nil]
has_many :resource_connections, Domain.Resources.Connection, where: [deleted_at: nil]
has_many :resources, Domain.Resources.Resource
has_many :resource_connections, Domain.Resources.Connection
# TODO: HARD-DELETE - Remove `where` after `deleted_at` is removed from the DB
has_many :clients, Domain.Clients.Client, where: [deleted_at: nil]
has_many :clients, Domain.Clients.Client
# TODO: HARD-DELETE - Remove `where` after `deleted_at` is removed from the DB
has_many :gateways, Domain.Gateways.Gateway, where: [deleted_at: nil]
has_many :gateway_groups, Domain.Gateways.Group, where: [deleted_at: nil]
has_many :gateways, Domain.Gateways.Gateway
has_many :gateway_groups, Domain.Gateways.Group
# TODO: HARD-DELETE - Remove `where` after `deleted_at` is removed from the DB
has_many :relays, Domain.Relays.Relay, where: [deleted_at: nil]
has_many :relay_groups, Domain.Relays.Group, where: [deleted_at: nil]
has_many :relays, Domain.Relays.Relay
has_many :relay_groups, Domain.Relays.Group
# TODO: HARD-DELETE - Remove `where` after `deleted_at` is removed from the DB
has_many :tokens, Domain.Tokens.Token, where: [deleted_at: nil]
has_many :tokens, Domain.Tokens.Token
field :warning, :string
field :warning_delivery_attempts, :integer, default: 0
@@ -68,8 +58,6 @@ defmodule Domain.Accounts.Account do
field :disabled_reason, :string
field :disabled_at, :utc_datetime_usec
# TODO: HARD-DELETE - Remove field after soft deletion is removed
field :deleted_at, :utc_datetime_usec
timestamps()
end
end

View File

@@ -5,16 +5,11 @@ defmodule Domain.Accounts.Account.Query do
from(accounts in Domain.Accounts.Account, as: :accounts)
end
# TODO: HARD-DELETE - Remove after `deleted_at` column is removed from DB
def not_deleted(queryable \\ all()) do
where(queryable, [accounts: accounts], is_nil(accounts.deleted_at))
end
def disabled(queryable \\ all()) do
where(queryable, [accounts: accounts], not is_nil(accounts.disabled_at))
end
def not_disabled(queryable \\ not_deleted()) do
def not_disabled(queryable \\ all()) do
where(queryable, [accounts: accounts], is_nil(accounts.disabled_at))
end
@@ -100,7 +95,7 @@ defmodule Domain.Accounts.Account.Query do
@impl Domain.Repo.Query
def preloads,
do: [
clients: {Domain.Clients.Client.Query.not_deleted(), Domain.Clients.Client.Query.preloads()}
clients: {Domain.Clients.Client.Query.all(), Domain.Clients.Client.Query.preloads()}
]
@impl Domain.Repo.Query

View File

@@ -2,7 +2,7 @@ defmodule Domain.Actors do
alias Domain.Actors.Membership
alias Web.Clients
alias Domain.Repo
alias Domain.{Accounts, Auth, Tokens, Clients, Policies, Billing}
alias Domain.{Accounts, Auth, Tokens, Clients, Billing}
alias Domain.Actors.{Authorizer, Actor, Group}
require Ecto.Query
require Logger
@@ -12,7 +12,7 @@ defmodule Domain.Actors do
def fetch_groups_count_grouped_by_provider_id(%Auth.Subject{} = subject) do
with :ok <- Auth.ensure_has_permissions(subject, Authorizer.manage_actors_permission()) do
groups =
Group.Query.not_deleted()
Group.Query.all()
|> Group.Query.group_by_provider_id()
|> Authorizer.for_subject(subject)
|> Repo.all()
@@ -48,7 +48,7 @@ defmodule Domain.Actors do
def list_groups(%Auth.Subject{} = subject, opts \\ []) do
with :ok <- Auth.ensure_has_permissions(subject, Authorizer.manage_actors_permission()) do
Group.Query.not_deleted()
Group.Query.all()
|> Authorizer.for_subject(subject)
|> Repo.list(Group.Query, opts)
end
@@ -56,7 +56,7 @@ defmodule Domain.Actors do
def list_groups_for(%Actor{} = actor, %Auth.Subject{} = subject, opts \\ []) do
with :ok <- Auth.ensure_has_permissions(subject, Authorizer.manage_actors_permission()) do
Group.Query.not_deleted()
Group.Query.all()
|> Group.Query.by_actor_id(actor.id)
|> Authorizer.for_subject(subject)
|> Repo.list(Group.Query, opts)
@@ -66,7 +66,7 @@ defmodule Domain.Actors do
def all_groups!(%Auth.Subject{} = subject, opts \\ []) do
{preload, _opts} = Keyword.pop(opts, :preload, [])
Group.Query.not_deleted()
Group.Query.all()
|> Authorizer.for_subject(subject)
|> Repo.all()
|> Repo.preload(preload)
@@ -75,7 +75,7 @@ defmodule Domain.Actors do
def all_editable_groups!(%Auth.Subject{} = subject, opts \\ []) do
{preload, _opts} = Keyword.pop(opts, :preload, [])
Group.Query.not_deleted()
Group.Query.all()
|> Group.Query.editable()
|> Authorizer.for_subject(subject)
|> Repo.all()
@@ -90,7 +90,7 @@ defmodule Domain.Actors do
def list_editable_groups(%Auth.Subject{} = subject, opts \\ []) do
with :ok <- Auth.ensure_has_permissions(subject, Authorizer.manage_actors_permission()) do
Group.Query.not_deleted()
Group.Query.all()
|> Group.Query.editable()
|> Authorizer.for_subject(subject)
|> Repo.list(Group.Query, opts)
@@ -101,7 +101,7 @@ defmodule Domain.Actors do
with :ok <- Auth.ensure_has_permissions(subject, Authorizer.manage_actors_permission()) do
ids = groups |> Enum.map(& &1.id) |> Enum.uniq()
Group.Query.not_deleted()
Group.Query.all()
|> Group.Query.by_id({:in, ids})
|> Group.Query.preload_few_actors_for_each_group(limit)
|> Authorizer.for_subject(subject)
@@ -114,7 +114,7 @@ defmodule Domain.Actors do
ids = actors |> Enum.map(& &1.id) |> Enum.uniq()
{:ok, peek} =
Actor.Query.not_deleted()
Actor.Query.all()
|> Actor.Query.by_id({:in, ids})
|> Actor.Query.preload_few_groups_for_each_actor(limit)
|> Authorizer.for_subject(subject)
@@ -141,7 +141,7 @@ defmodule Domain.Actors do
ids = actors |> Enum.map(& &1.id) |> Enum.uniq()
{:ok, peek} =
Actor.Query.not_deleted()
Actor.Query.all()
|> Actor.Query.by_id({:in, ids})
|> Actor.Query.preload_few_clients_for_each_actor(limit)
|> Authorizer.for_subject(subject)
@@ -224,7 +224,7 @@ defmodule Domain.Actors do
def update_group(%Group{provider_id: nil} = group, attrs, %Auth.Subject{} = subject) do
with :ok <- Auth.ensure_has_permissions(subject, Authorizer.manage_actors_permission()) do
Group.Query.not_deleted()
Group.Query.all()
|> Group.Query.by_id(group.id)
|> Authorizer.for_subject(subject)
|> Repo.fetch_and_update(Group.Query,
@@ -256,93 +256,6 @@ defmodule Domain.Actors do
{:error, :synced_group}
end
# TODO: HARD-DELETE - Remove this once the `deleted_at` field in the DB is gone
def soft_delete_group(%Group{provider_id: nil} = group, %Auth.Subject{} = subject) do
queryable =
Group.Query.not_deleted()
|> Group.Query.by_id(group.id)
case soft_delete_groups(queryable, subject) do
{:ok, [group]} ->
{:ok, _policies} = Policies.soft_delete_policies_for(group, subject)
# TODO: Hard delete
# Consider using a trigger or transaction to handle the side effects of soft-deletions to ensure consistency
{_count, _memberships} =
Membership.Query.all()
|> Membership.Query.by_group_id(group.id)
|> Repo.delete_all()
{:ok, group}
{:ok, []} ->
{:error, :not_found}
{:error, reason} ->
{:error, reason}
end
end
# TODO: HARD-DELETE - Remove this once the `deleted_at` field in the DB is gone
def soft_delete_group(%Group{}, %Auth.Subject{}) do
{:error, :synced_group}
end
# TODO: HARD-DELETE - Remove this once the `deleted_at` field in the DB is gone
def soft_delete_groups_for(%Auth.Provider{} = provider, %Auth.Subject{} = subject) do
queryable =
Group.Query.not_deleted()
|> Group.Query.by_provider_id(provider.id)
|> Group.Query.by_account_id(provider.account_id)
# TODO: Hard delete
# Consider using a trigger or transaction to handle the side effects of soft-deletions to ensure consistency
{_count, _memberships} =
Membership.Query.by_group_provider_id(provider.id)
|> Repo.delete_all()
with {:ok, groups} <- soft_delete_groups(queryable, subject) do
{:ok, _policies} = Policies.soft_delete_policies_for(provider, subject)
{:ok, groups}
end
end
# TODO: HARD-DELETE - Remove this once the `deleted_at` field in the DB is gone
defp soft_delete_groups(queryable, subject) do
with :ok <- Auth.ensure_has_permissions(subject, Authorizer.manage_actors_permission()) do
{_count, groups} =
queryable
|> Authorizer.for_subject(subject)
|> Group.Query.delete()
|> Repo.update_all([])
{:ok, groups}
end
end
@doc false
# used in sync workers
# TODO: HARD-DELETE - Remove this once the `deleted_at` field in the DB is gone
def soft_delete_groups(queryable) do
{_count, groups} =
queryable
|> Group.Query.delete()
|> Repo.update_all([])
:ok =
Enum.each(groups, fn group ->
{:ok, _policies} = Domain.Policies.soft_delete_policies_for(group)
end)
# TODO: Hard delete
# Consider using a trigger or transaction to handle the side effects of soft-deletions to ensure consistency
{_count, _memberships} =
Membership.Query.by_group_id({:in, Enum.map(groups, & &1.id)})
|> Repo.delete_all()
{:ok, groups}
end
def group_synced?(%Group{provider_id: nil}), do: false
def group_synced?(%Group{}), do: true
@@ -350,11 +263,7 @@ defmodule Domain.Actors do
def group_managed?(%Group{}), do: false
def group_editable?(%Group{} = group),
do: not group_soft_deleted?(group) and not group_synced?(group) and not group_managed?(group)
# TODO: HARD-DELETE - Remove this once the `deleted_at` field in the DB is gone
def group_soft_deleted?(%Group{deleted_at: nil}), do: false
def group_soft_deleted?(%Group{}), do: true
do: not group_synced?(group) and not group_managed?(group)
# Actors
@@ -379,18 +288,6 @@ defmodule Domain.Actors do
|> Repo.aggregate(:count)
end
def count_synced_actors_for_provider(%Auth.Provider{} = provider) do
Actor.Query.not_deleted()
|> Actor.Query.by_deleted_identity_provider_id(provider.id)
|> Actor.Query.by_stale_for_provider(provider.id)
|> Repo.all()
Actor.Query.not_deleted()
|> Actor.Query.by_deleted_identity_provider_id(provider.id)
|> Actor.Query.by_stale_for_provider(provider.id)
|> Repo.aggregate(:count)
end
def fetch_actor_by_id(id, %Auth.Subject{} = subject, opts \\ []) do
with :ok <- Auth.ensure_has_permissions(subject, Authorizer.manage_actors_permission()),
true <- Repo.valid_uuid?(id) do
@@ -441,7 +338,7 @@ defmodule Domain.Actors do
def list_actors(%Auth.Subject{} = subject, opts \\ []) do
with :ok <- Auth.ensure_has_permissions(subject, Authorizer.manage_actors_permission()) do
Actor.Query.not_deleted()
Actor.Query.all()
|> Authorizer.for_subject(subject)
|> Repo.list(Actor.Query, opts)
end
@@ -520,7 +417,7 @@ defmodule Domain.Actors do
def update_actor(%Actor{} = actor, attrs, %Auth.Subject{} = subject) do
with :ok <- Auth.ensure_has_permissions(subject, Authorizer.manage_actors_permission()) do
Actor.Query.not_deleted()
Actor.Query.all()
|> Actor.Query.by_id(actor.id)
|> Authorizer.for_subject(subject)
|> Repo.fetch_and_update(Actor.Query,
@@ -569,7 +466,7 @@ defmodule Domain.Actors do
[]
group_ids ->
Group.Query.not_deleted()
Group.Query.all()
|> Group.Query.not_editable()
|> Group.Query.by_id({:in, group_ids})
|> Repo.all()
@@ -578,7 +475,7 @@ defmodule Domain.Actors do
def disable_actor(%Actor{} = actor, %Auth.Subject{} = subject) do
with :ok <- Auth.ensure_has_permissions(subject, Authorizer.manage_actors_permission()) do
Actor.Query.not_deleted()
Actor.Query.all()
|> Actor.Query.by_id(actor.id)
|> Authorizer.for_subject(subject)
|> Repo.fetch_and_update(Actor.Query,
@@ -596,7 +493,7 @@ defmodule Domain.Actors do
def enable_actor(%Actor{} = actor, %Auth.Subject{} = subject) do
with :ok <- Auth.ensure_has_permissions(subject, Authorizer.manage_actors_permission()) do
Actor.Query.not_deleted()
Actor.Query.all()
|> Actor.Query.by_id(actor.id)
|> Authorizer.for_subject(subject)
|> Repo.fetch_and_update(Actor.Query, with: &Actor.Changeset.enable_actor/1)
@@ -616,35 +513,6 @@ defmodule Domain.Actors do
end
end
# TODO: HARD-DELETE - Remove this after `deleted_at` column is removed from DB
def soft_delete_actor(%Actor{} = actor, %Auth.Subject{} = subject) do
with :ok <- Auth.ensure_has_permissions(subject, Authorizer.manage_actors_permission()) do
Actor.Query.not_deleted()
|> Actor.Query.by_id(actor.id)
|> Authorizer.for_subject(subject)
|> Repo.fetch_and_update(Actor.Query,
with: fn actor ->
if actor.type != :account_admin_user or other_enabled_admins_exist?(actor) do
:ok = Auth.soft_delete_identities_for(actor, subject)
:ok = Clients.delete_clients_for(actor, subject)
# TODO: Hard delete
# Consider using a trigger or transaction to handle the side effects of soft-deletions to ensure consistency
{_count, _memberships} =
Membership.Query.by_actor_id(actor.id)
|> Repo.delete_all()
{:ok, _tokens} = Tokens.delete_tokens_for(actor, subject)
Actor.Changeset.delete_actor(actor)
else
:cant_delete_the_last_admin
end
end
)
end
end
def delete_unsynced_actors(%Auth.Provider{} = provider, synced_at) do
{count, _deleted_actors} =
Actor.Query.all()
@@ -678,34 +546,13 @@ defmodule Domain.Actors do
{:ok, %{deleted_memberships: count}}
end
def delete_stale_synced_actors_for_provider(
%Auth.Provider{} = provider,
%Auth.Subject{} = subject
) do
with :ok <- Auth.ensure_has_permissions(subject, Authorizer.manage_actors_permission()) do
Actor.Query.not_deleted()
|> Authorizer.for_subject(subject)
|> Actor.Query.by_deleted_identity_provider_id(provider.id)
|> Actor.Query.by_stale_for_provider(provider.id)
|> Repo.all()
|> Enum.each(fn actor ->
{:ok, _actor} = delete_actor(actor, subject)
end)
end
end
def actor_synced?(%Actor{last_synced_at: nil}), do: false
def actor_synced?(%Actor{}), do: true
# TODO: HARD-DELETE - Remove this once the `deleted_at` field in the DB is gone
def actor_deleted?(%Actor{deleted_at: nil}), do: false
def actor_deleted?(%Actor{}), do: true
def actor_disabled?(%Actor{disabled_at: nil}), do: false
def actor_disabled?(%Actor{}), do: true
# TODO: HARD-DELETE - Update this once the `deleted_at` field in the DB is gone
def actor_active?(%Actor{disabled_at: nil, deleted_at: nil}), do: true
def actor_active?(%Actor{disabled_at: nil}), do: true
def actor_active?(%Actor{}), do: false
defp other_enabled_admins_exist?(%Actor{

View File

@@ -7,20 +7,13 @@ defmodule Domain.Actors.Actor do
field :name, :string
# TODO: HARD-DELETE - Remove `where` after `deleted_at` is removed from DB
has_many :identities, Domain.Auth.Identity, where: [deleted_at: nil]
has_many :identities, Domain.Auth.Identity
# TODO: HARD-DELETE - Remove `where` after `deleted_at` is removed from DB
has_many :clients, Domain.Clients.Client,
where: [deleted_at: nil],
preload_order: [desc: :last_seen_at]
has_many :clients, Domain.Clients.Client, preload_order: [desc: :last_seen_at]
# TODO: HARD-DELETE - Remove `where` after `deleted_at` is removed from DB
has_many :tokens, Domain.Tokens.Token, where: [deleted_at: nil]
has_many :tokens, Domain.Tokens.Token
has_many :memberships, Domain.Actors.Membership, on_replace: :delete
# TODO: where doesn't work on join tables so soft-deleted records will be preloaded,
# ref https://github.com/firezone/firezone/issues/2162
has_many :groups, through: [:memberships, :group]
belongs_to :account, Domain.Accounts.Account
@@ -29,8 +22,6 @@ defmodule Domain.Actors.Actor do
field :last_synced_at, :utc_datetime_usec
field :disabled_at, :utc_datetime_usec
# TODO: HARD-DELETE - Remove field after soft deletion is removed
field :deleted_at, :utc_datetime_usec
timestamps()
end
end

View File

@@ -85,13 +85,6 @@ defmodule Domain.Actors.Actor.Changeset do
|> put_change(:disabled_at, nil)
end
# TODO: HARD-DELETE - Remove after `deleted_at` is removed from DB
def delete_actor(%Actor{} = actor) do
actor
|> change()
|> put_default_value(:deleted_at, DateTime.utc_now())
end
defp validate_granted_permissions(changeset, subject) do
validate_change(changeset, :type, fn :type, granted_actor_type ->
if Auth.can_grant_role?(subject, granted_actor_type) do

View File

@@ -5,13 +5,7 @@ defmodule Domain.Actors.Actor.Query do
from(actors in Domain.Actors.Actor, as: :actors)
end
# TODO: HARD-DELETE - Remove after `deleted_at` column is removed from DB
def not_deleted do
all()
|> where([actors: actors], is_nil(actors.deleted_at))
end
def not_disabled(queryable \\ not_deleted()) do
def not_disabled(queryable \\ all()) do
where(queryable, [actors: actors], is_nil(actors.disabled_at))
end
@@ -47,34 +41,6 @@ defmodule Domain.Actors.Actor.Query do
where(queryable, [actors: actors], actors.last_synced_at != ^synced_at)
end
# TODO: HARD-DELETE - Remove after `deleted_at` column is removed in DB
def by_deleted_identity_provider_id(queryable, provider_id) do
queryable
|> join(:inner, [actors: actors], identities in ^Domain.Auth.Identity.Query.deleted(),
on: identities.actor_id == actors.id,
as: :deleted_identities
)
|> where(
[deleted_identities: deleted_identities],
deleted_identities.provider_id == ^provider_id
)
end
# TODO: HARD-DELETE - Update after `deleted_at` column is removed in DB
def by_stale_for_provider(queryable, provider_id) do
subquery =
Domain.Auth.Identity.Query.all()
|> where(
[identities: identities],
identities.actor_id == parent_as(:actors).id and
(identities.provider_id != ^provider_id or
is_nil(identities.deleted_at))
)
queryable
|> where([actors: actors], not exists(subquery))
end
def by_type(queryable, {:in, types}) do
where(queryable, [actors: actors], actors.type in ^types)
end
@@ -98,7 +64,7 @@ defmodule Domain.Actors.Actor.Query do
def with_joined_clients(queryable, limit) do
subquery =
Domain.Clients.Client.Query.not_deleted()
Domain.Clients.Client.Query.all()
|> where([clients: clients], clients.actor_id == parent_as(:actors).id)
|> order_by([clients: clients], desc: clients.last_seen_at)
|> limit(^limit)
@@ -138,13 +104,6 @@ defmodule Domain.Actors.Actor.Query do
subquery =
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.not_deleted(),
on: groups.id == memberships.group_id
)
|> select([memberships: memberships], memberships.group_id)
|> limit(^limit)
@@ -168,7 +127,7 @@ defmodule Domain.Actors.Actor.Query do
queryable,
:left,
[memberships: memberships],
groups in ^Domain.Actors.Group.Query.not_deleted(),
groups in ^Domain.Actors.Group.Query.all(),
on: groups.id == memberships.group_id,
as: :groups
)
@@ -179,7 +138,7 @@ defmodule Domain.Actors.Actor.Query do
queryable,
:left,
[actors: actors],
clients in ^Domain.Clients.Client.Query.not_deleted(),
clients in ^Domain.Clients.Client.Query.all(),
on: clients.actor_id == actors.id,
as: :clients
)
@@ -191,7 +150,7 @@ defmodule Domain.Actors.Actor.Query do
queryable,
:left,
[actors: actors],
identities in ^Domain.Auth.Identity.Query.not_deleted(),
identities in ^Domain.Auth.Identity.Query.all(),
on: identities.actor_id == actors.id,
as: ^binding
)
@@ -277,11 +236,6 @@ defmodule Domain.Actors.Actor.Query do
name: :group_id,
type: {:string, :uuid},
fun: &filter_by_group_id/2
},
%Domain.Repo.Filter{
name: :deleted?,
type: :boolean,
fun: &filter_deleted/1
}
]
@@ -347,9 +301,4 @@ defmodule Domain.Actors.Actor.Query do
{queryable, dynamic(exists(subquery))}
end
# TODO: HARD-DELETE - Remove after `deleted_at` column is removed in DB
def filter_deleted(queryable) do
{queryable, dynamic([actors: actors], not is_nil(actors.deleted_at))}
end
end

View File

@@ -11,15 +11,10 @@ defmodule Domain.Actors.Group do
field :last_synced_at, :utc_datetime_usec
# TODO: HARD-DELETE - Remove `where` after `deleted_at` column is removed from DB
has_many :policies, Domain.Policies.Policy,
foreign_key: :actor_group_id,
where: [deleted_at: nil]
has_many :policies, Domain.Policies.Policy, foreign_key: :actor_group_id
has_many :memberships, Domain.Actors.Membership, on_replace: :delete
# TODO: where doesn't work on join tables so soft-deleted records will be preloaded,
# ref https://github.com/firezone/firezone/issues/2162
has_many :actors, through: [:memberships, :actor]
field :created_by, Ecto.Enum, values: ~w[actor identity provider system]a
@@ -27,8 +22,6 @@ defmodule Domain.Actors.Group do
belongs_to :account, Domain.Accounts.Account
# TODO: HARD-DELETE - Remove field after soft deletion is removed
field :deleted_at, :utc_datetime_usec
timestamps()
end
end

View File

@@ -11,8 +11,7 @@ defmodule Domain.Actors.Group.Changeset do
"WHERE provider_id IS NOT NULL AND provider_identifier IS NOT NULL"}
end
# TODO: Update after `deleted_at` is removed from the DB
def upsert_on_conflict, do: {:replace, ~w[name updated_at deleted_at]a}
def upsert_on_conflict, do: {:replace, ~w[name updated_at]a}
def create(%Accounts.Account{} = account, attrs, %Auth.Subject{} = subject) do
%Actors.Group{memberships: []}
@@ -36,7 +35,6 @@ defmodule Domain.Actors.Group.Changeset do
|> put_subject_trail(:created_by, :system)
end
# TODO: Update after `deleted_at` is removed from the DB
def create(%Auth.Provider{} = provider, attrs) do
%Actors.Group{memberships: []}
|> cast(attrs, ~w[name provider_identifier last_synced_at]a)
@@ -45,8 +43,6 @@ defmodule Domain.Actors.Group.Changeset do
|> changeset()
|> put_change(:provider_id, provider.id)
|> put_change(:account_id, provider.account_id)
# resurrect synced groups
|> put_change(:deleted_at, nil)
|> put_subject_trail(:created_by, :provider)
end

View File

@@ -6,12 +6,6 @@ defmodule Domain.Actors.Group.Query do
from(groups in Domain.Actors.Group, as: :groups)
end
# TODO: HARD-DELETE - Remove after `deleted_at` is removed from DB
def not_deleted do
all()
|> where([groups: groups], is_nil(groups.deleted_at))
end
def not_editable(queryable) do
where(queryable, [groups: groups], not is_nil(groups.provider_id) or groups.type != :static)
end
@@ -69,17 +63,6 @@ defmodule Domain.Actors.Group.Query do
where(queryable, [groups: groups], groups.last_synced_at != ^synced_at)
end
# TODO: HARD-DELETE - Remove after `deleted_at` is removed from DB
def delete(queryable) do
queryable
|> Ecto.Query.select([groups: groups], groups)
|> Ecto.Query.update([groups: groups],
set: [
deleted_at: fragment("COALESCE(?, timezone('UTC', NOW()))", groups.deleted_at)
]
)
end
def group_by_provider_id(queryable) do
queryable
|> group_by([groups: groups], groups.provider_id)
@@ -112,13 +95,6 @@ defmodule Domain.Actors.Group.Query do
subquery =
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.not_deleted(),
on: actors.id == memberships.actor_id
)
|> select([memberships: memberships], memberships.actor_id)
|> limit(^limit)
@@ -142,7 +118,7 @@ defmodule Domain.Actors.Group.Query do
queryable,
:left,
[memberships: memberships],
actors in ^Domain.Actors.Actor.Query.not_deleted(),
actors in ^Domain.Actors.Actor.Query.all(),
on: actors.id == memberships.actor_id,
as: :actors
)
@@ -180,43 +156,21 @@ defmodule Domain.Actors.Group.Query do
{:ok, %{upserted_groups: count}}
end
# TODO: Update after `deleted_at` is removed from DB
# TODO: IDP Sync
# See: https://github.com/firezone/firezone/issues/8750
# We use CTE here which should be very performant even for very large inserts and deletions
# We use CTE here which should be very performant even for very large inserts
def update_everyone_group_memberships(account_id) do
# Delete memberships for actors and groups that are soft-deleted
delete_memberships =
from(
agm in Domain.Actors.Membership,
where:
agm.account_id == ^account_id and
(exists(
from(a in Domain.Actors.Actor,
where: a.id == parent_as(:agm).actor_id and not is_nil(a.deleted_at)
)
) or
exists(
from(g in Domain.Actors.Group,
where: g.id == parent_as(:agm).group_id and not is_nil(g.deleted_at)
)
))
)
|> from(as: :agm)
# Insert memberships for the cross join of non-deleted user actors and managed groups
# Insert memberships for the cross join of user actors and managed groups
insert_with_cte_fn = fn repo, _changes ->
current_memberships_cte =
from(
a in Domain.Actors.Actor,
cross_join: g in Domain.Actors.Group,
where:
is_nil(a.deleted_at) and
a.account_id == ^account_id and
a.account_id == ^account_id and
a.type in [:account_user, :account_admin_user] and
g.type == :managed and
g.account_id == ^account_id and
is_nil(g.deleted_at),
g.account_id == ^account_id,
select: %{
actor_id: a.id,
group_id: g.id
@@ -244,7 +198,6 @@ defmodule Domain.Actors.Group.Query do
end
Ecto.Multi.new()
|> Ecto.Multi.delete_all(:delete_memberships, delete_memberships)
|> Ecto.Multi.run(:insert_memberships, insert_with_cte_fn)
end
@@ -273,11 +226,6 @@ defmodule Domain.Actors.Group.Query do
values: &Domain.Auth.all_third_party_providers!/1,
fun: &filter_by_provider_id/2
},
%Domain.Repo.Filter{
name: :deleted?,
type: :boolean,
fun: &filter_deleted/1
},
%Domain.Repo.Filter{
name: :editable?,
type: :boolean,
@@ -293,19 +241,11 @@ defmodule Domain.Actors.Group.Query do
{queryable, dynamic([groups: groups], groups.provider_id == ^provider_id)}
end
# TODO: HARD-DELETE - Remove after `deleted_at` is removed from DB
def filter_deleted(queryable) do
{queryable, dynamic([groups: groups], not is_nil(groups.deleted_at))}
end
# TODO: Update after `deleted_at` is removed from DB
def filter_editable(queryable) do
{queryable,
dynamic(
[groups: groups],
is_nil(groups.provider_id) and
is_nil(groups.deleted_at) and
groups.type == :static
is_nil(groups.provider_id) and groups.type == :static
)}
end
end

View File

@@ -45,21 +45,15 @@ defmodule Domain.Actors.Group.Sync do
{:ok, groups}
end
# TODO: Update after `deleted_at` is removed from DB
defp plan_groups_update(groups, provider_identifiers) do
identifiers_set = MapSet.new(provider_identifiers)
{upsert, delete} =
Enum.reduce(groups, {provider_identifiers, []}, fn group, {upsert, delete} ->
cond do
MapSet.member?(identifiers_set, group.provider_identifier) ->
{upsert, delete}
!is_nil(group.deleted_at) ->
{upsert, delete}
true ->
{upsert -- [group.provider_identifier], [group.provider_identifier] ++ delete}
if MapSet.member?(identifiers_set, group.provider_identifier) do
{upsert, delete}
else
{upsert -- [group.provider_identifier], [group.provider_identifier] ++ delete}
end
end)

View File

@@ -84,14 +84,14 @@ defmodule Domain.Actors.Membership.Query do
end
def with_joined_actors(queryable \\ all()) do
join(queryable, :inner, [memberships: memberships], actors in ^Actor.Query.not_deleted(),
join(queryable, :inner, [memberships: memberships], actors in ^Actor.Query.all(),
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.not_deleted(),
join(queryable, :inner, [memberships: memberships], groups in ^Group.Query.all(),
on: groups.id == memberships.group_id,
as: :groups
)
@@ -139,13 +139,11 @@ defmodule Domain.Actors.Membership.Query do
ai.provider_identifier = mi.user_provider_identifier
AND ai.account_id = $#{account_id}
AND ai.provider_id = $#{provider_id}
AND ai.deleted_at IS NULL
)
JOIN actor_groups ag ON (
ag.provider_identifier = mi.group_provider_identifier
AND ag.account_id = $#{account_id}
AND ag.provider_id = $#{provider_id}
AND ag.deleted_at IS NULL
)
)
INSERT INTO actor_group_memberships (id, actor_id, group_id, account_id, last_synced_at)

View File

@@ -163,7 +163,7 @@ defmodule Domain.Auth do
def list_providers(%Subject{} = subject, opts \\ []) do
with :ok <- ensure_has_permissions(subject, Authorizer.manage_providers_permission()) do
Provider.Query.not_deleted()
Provider.Query.all()
|> Authorizer.for_subject(Provider, subject)
|> Repo.list(Provider.Query, opts)
end
@@ -177,7 +177,7 @@ defmodule Domain.Auth do
end
def all_third_party_providers!(%Subject{} = subject) do
Provider.Query.not_deleted()
Provider.Query.all()
|> Provider.Query.by_account_id(subject.account.id)
|> Provider.Query.by_adapter({:not_in, [:email, :userpass]})
|> Authorizer.for_subject(Provider, subject)
@@ -185,7 +185,7 @@ defmodule Domain.Auth do
end
def all_providers!(%Subject{} = subject) do
Provider.Query.not_deleted()
Provider.Query.all()
|> Provider.Query.by_account_id(subject.account.id)
|> Authorizer.for_subject(Provider, subject)
|> Repo.all()
@@ -314,30 +314,10 @@ defmodule Domain.Auth do
end
end
def soft_delete_provider(%Provider{} = provider, %Subject{} = subject) do
provider
|> mutate_provider(subject, fn provider ->
if other_active_providers_exist?(provider) do
:ok = soft_delete_identities_for(provider, subject)
{:ok, _groups} = Actors.soft_delete_groups_for(provider, subject)
Provider.Changeset.soft_delete_provider(provider)
else
:cant_delete_the_last_provider
end
end)
|> case do
{:ok, provider} ->
Adapters.ensure_deprovisioned(provider)
{:error, reason} ->
{:error, reason}
end
end
defp mutate_provider(%Provider{} = provider, %Subject{} = subject, callback)
when is_function(callback, 1) do
with :ok <- ensure_has_permissions(subject, Authorizer.manage_providers_permission()) do
Provider.Query.not_deleted()
Provider.Query.all()
|> Provider.Query.by_id(provider.id)
|> Authorizer.for_subject(Provider, subject)
|> Repo.fetch_and_update(Provider.Query, with: callback)
@@ -367,7 +347,7 @@ defmodule Domain.Auth do
# Identities
def max_last_seen_at_by_actor_ids(actor_ids) do
Identity.Query.not_deleted()
Identity.Query.all()
|> Identity.Query.by_actor_id({:in, actor_ids})
|> Identity.Query.max_last_seen_at_grouped_by_actor_id()
|> Repo.all()
@@ -392,7 +372,7 @@ defmodule Domain.Auth do
def fetch_identity_by_id(id, %Subject{} = subject, opts \\ []) do
with :ok <- ensure_has_permissions(subject, Authorizer.manage_identities_permission()),
true <- Repo.valid_uuid?(id) do
Identity.Query.not_deleted()
Identity.Query.all()
|> Identity.Query.by_id(id)
|> Authorizer.for_subject(Identity, subject)
|> Repo.fetch(Identity.Query, opts)
@@ -406,7 +386,7 @@ defmodule Domain.Auth do
def fetch_identities_count_grouped_by_provider_id(%Subject{} = subject) do
with :ok <- ensure_has_permissions(subject, Authorizer.manage_identities_permission()) do
identities =
Identity.Query.not_deleted()
Identity.Query.all()
|> Identity.Query.group_by_provider_id()
|> Authorizer.for_subject(Identity, subject)
|> Repo.all()
@@ -419,14 +399,14 @@ defmodule Domain.Auth do
end
def all_identities_for(%Actors.Actor{} = actor, opts \\ []) do
Identity.Query.not_deleted()
Identity.Query.all()
|> Identity.Query.by_actor_id(actor.id)
|> Repo.all(opts)
end
def list_identities_for(%Actors.Actor{} = actor, %Subject{} = subject, opts \\ []) do
with :ok <- ensure_has_permissions(subject, Authorizer.manage_identities_permission()) do
Identity.Query.not_deleted()
Identity.Query.all()
|> Identity.Query.by_actor_id(actor.id)
|> Authorizer.for_subject(Identity, subject)
|> Repo.list(Identity.Query, opts)
@@ -451,14 +431,11 @@ defmodule Domain.Auth do
end
# used by IdP adapters
# TODO: HARD-DELETE - Remove `unsafe_fragment` after `deleted_at` column is removed from DB
def upsert_identity(%Actors.Actor{} = actor, %Provider{} = provider, attrs) do
Identity.Changeset.create_identity(actor, provider, attrs)
|> Adapters.identity_changeset(provider)
|> Repo.insert(
conflict_target:
{:unsafe_fragment,
~s/(account_id, provider_id, provider_identifier) WHERE deleted_at IS NULL/},
conflict_target: {:unsafe_fragment, "(account_id, provider_id, provider_identifier)"},
on_conflict: {:replace, [:provider_state]},
returning: true
)
@@ -516,78 +493,6 @@ defmodule Domain.Auth do
end
end
# TODO: HARD-DELETE
# This function should not be necessary after hard delete because deleting a provider
# will delete all of it's identities using a cascading delete in the DB
def delete_identities_for(%Provider{} = provider, %Subject{} = subject) do
with :ok <- ensure_has_permissions(subject, Authorizer.manage_identities_permission()) do
{num_deleted, _} =
Identity.Query.all()
|> Identity.Query.by_provider_id(provider.id)
|> Identity.Query.by_account_id(provider.account_id)
|> Authorizer.for_subject(Identity, subject)
|> Repo.delete_all()
{:ok, num_deleted}
end
end
# TODO: HARD-DELETE - Remove after `deleted_at` column is removed in DB
def soft_delete_identity(%Identity{} = identity, %Subject{} = subject) do
required_permissions =
{:one_of,
[
Authorizer.manage_identities_permission(),
Authorizer.manage_own_identities_permission()
]}
with :ok <- ensure_has_permissions(subject, required_permissions) do
Identity.Query.not_deleted()
|> Identity.Query.by_id(identity.id)
|> Authorizer.for_subject(Identity, subject)
|> Repo.fetch_and_update(Identity.Query,
with: fn identity ->
{:ok, _tokens} = Tokens.soft_delete_tokens_for(identity, subject)
Identity.Changeset.delete_identity(identity)
end
)
end
end
# TODO: HARD-DELETE - Remove after `deleted_at` column is removed in DB
def soft_delete_identities_for(%Actors.Actor{} = actor, %Subject{} = subject) do
Identity.Query.not_deleted()
|> Identity.Query.by_actor_id(actor.id)
|> Identity.Query.by_account_id(actor.account_id)
|> soft_delete_identities(actor, subject)
end
# TODO: HARD-DELETE - Remove after `deleted_at` column is removed in DB
def soft_delete_identities_for(%Provider{} = provider, %Subject{} = subject) do
Identity.Query.not_deleted()
|> Identity.Query.by_provider_id(provider.id)
|> Identity.Query.by_account_id(provider.account_id)
|> soft_delete_identities(provider, subject)
end
# TODO: HARD-DELETE - Remove after `deleted_at` column is removed in DB
defp soft_delete_identities(queryable, assoc, subject) do
with :ok <- ensure_has_permissions(subject, Authorizer.manage_identities_permission()) do
{:ok, _tokens} = Tokens.soft_delete_tokens_for(assoc, subject)
{_count, nil} =
queryable
|> Authorizer.for_subject(Identity, subject)
|> Repo.update_all(set: [deleted_at: DateTime.utc_now(), provider_state: %{}])
:ok
end
end
# TODO: HARD-DELETE - Remove after `deleted_at` column is removed in DB
def identity_soft_deleted?(%{deleted_at: nil}), do: false
def identity_soft_deleted?(_identity), do: true
# Sign Up / In / Off
@doc """
@@ -604,18 +509,6 @@ defmodule Domain.Auth do
{:error, :unauthorized}
end
# TODO: HARD-DELETE - Remove after `deleted_at` column is removed in DB
def sign_in(
%Provider{deleted_at: deleted_at},
_id_or_provider_identifier,
_token_nonce,
_secret,
%Context{}
)
when not is_nil(deleted_at) do
{:error, :unauthorized}
end
def sign_in(
%Provider{} = provider,
id_or_provider_identifier,
@@ -648,12 +541,6 @@ defmodule Domain.Auth do
{:error, :unauthorized}
end
# TODO: HARD-DELETE - Remove after `deleted_at` column is removed in DB
def sign_in(%Provider{deleted_at: deleted_at}, _token_nonce, _payload, %Context{})
when not is_nil(deleted_at) do
{:error, :unauthorized}
end
def sign_in(%Provider{} = provider, token_nonce, payload, %Context{} = context) do
with {:ok, identity, expires_at} <- Adapters.verify_and_update_identity(provider, payload),
identity = Repo.preload(identity, :actor),

View File

@@ -201,7 +201,7 @@ defmodule Domain.Auth.Adapters.OpenIDConnect do
with {:ok, _provider_identifier, _email, adapter_state} <-
fetch_state(provider, token_params, identifier_claim) do
Provider.Query.not_deleted()
Provider.Query.all()
|> Provider.Query.by_id(provider.id)
|> Repo.fetch_and_update(Provider.Query,
with: fn provider ->
@@ -215,7 +215,7 @@ defmodule Domain.Auth.Adapters.OpenIDConnect do
)
else
{:error, :expired_token} ->
Provider.Query.not_deleted()
Provider.Query.all()
|> Provider.Query.by_id(provider.id)
|> Repo.fetch_and_update(Provider.Query,
with: fn provider ->
@@ -228,7 +228,7 @@ defmodule Domain.Auth.Adapters.OpenIDConnect do
{:error, :expired}
{:error, :invalid_token} ->
Provider.Query.not_deleted()
Provider.Query.all()
|> Provider.Query.by_id(provider.id)
|> Repo.fetch_and_update(Provider.Query,
with: fn provider ->
@@ -253,7 +253,7 @@ defmodule Domain.Auth.Adapters.OpenIDConnect do
with {:ok, _provider_identifier, _email, identity_state} <-
fetch_state(identity.provider, token_params, identifier_claim) do
Identity.Query.not_deleted()
Identity.Query.all()
|> Identity.Query.by_id(identity.id)
|> Repo.fetch_and_update(Identity.Query,
with: &Identity.Changeset.update_identity_provider_state(&1, identity_state)

View File

@@ -23,14 +23,10 @@ defmodule Domain.Auth.Identity do
field :created_by, Ecto.Enum, values: ~w[system provider identity]a
field :created_by_subject, :map
# TODO: HARD-DELETE - Remove `where` after `deleted_at` is removed from the DB
has_many :clients, Domain.Clients.Client, where: [deleted_at: nil]
has_many :clients, Domain.Clients.Client
# TODO: HARD-DELETE - Remove `where` after `deleted_at` is removed from the DB
has_many :tokens, Domain.Tokens.Token, foreign_key: :identity_id, where: [deleted_at: nil]
has_many :tokens, Domain.Tokens.Token, foreign_key: :identity_id
# TODO: HARD-DELETE - Remove field after soft deletion is removed
field :deleted_at, :utc_datetime_usec
timestamps(updated_at: false)
end
end

View File

@@ -60,7 +60,6 @@ defmodule Domain.Auth.Identity.Changeset do
Actors.Actor.Changeset.sync(actor, attrs)
end
)
|> put_change(:deleted_at, nil)
|> changeset()
end
@@ -82,15 +81,6 @@ defmodule Domain.Auth.Identity.Changeset do
|> put_change(:provider_virtual_state, virtual_state)
end
# TODO: HARD-DELETE - Remove after `deleted_at` is removed from DB
def delete_identity(%Identity{} = identity) do
identity
|> change()
|> put_change(:provider_state, %{})
|> put_change(:provider_virtual_state, %{})
|> put_default_value(:deleted_at, DateTime.utc_now())
end
defp maybe_put_email_from_identifier(changeset) do
identifier = get_field(changeset, :provider_identifier)
email = get_field(changeset, :email)

View File

@@ -6,26 +6,11 @@ defmodule Domain.Auth.Identity.Query do
from(identities in Domain.Auth.Identity, as: :identities)
end
# TODO: HARD-DELETE - Remove after `deleted_at` is removed from DB
def not_deleted do
all()
|> where([identities: identities], is_nil(identities.deleted_at))
end
# TODO: HARD-DELETE - Remove after `deleted_at` is removed from DB
def deleted do
all()
|> where([identities: identities], not is_nil(identities.deleted_at))
end
# TODO: Update after `deleted_at` is removed from DB
def not_disabled(queryable \\ not_deleted()) do
def not_disabled(queryable \\ all()) do
queryable
|> with_assoc(:inner, :actor)
|> where([actor: actor], is_nil(actor.deleted_at))
|> where([actor: actor], is_nil(actor.disabled_at))
|> with_assoc(:inner, :provider)
|> where([provider: provider], is_nil(provider.deleted_at))
|> where([provider: provider], is_nil(provider.disabled_at))
end
@@ -152,18 +137,6 @@ defmodule Domain.Auth.Identity.Query do
})
end
# TODO: HARD-DELETE - Remove after `deleted_at` is removed from DB
def delete(queryable) do
queryable
|> Ecto.Query.select([identities: identities], identities)
|> Ecto.Query.update([identities: identities],
set: [
deleted_at: fragment("COALESCE(?, timezone('UTC', NOW()))", identities.deleted_at),
provider_state: ^%{}
]
)
end
def with_preloaded_assoc(queryable, type \\ :left, assoc) do
queryable
|> with_assoc(type, assoc)
@@ -211,7 +184,6 @@ defmodule Domain.Auth.Identity.Query do
WHERE ai.account_id = $#{account_id}
AND ai.provider_id = $#{provider_id}
AND ai.provider_identifier IN (SELECT provider_identifier FROM input_data)
AND ai.deleted_at IS NULL
),
actors_to_create AS (
SELECT
@@ -271,7 +243,6 @@ defmodule Domain.Auth.Identity.Query do
FROM all_actor_mappings aam
LEFT JOIN existing_identities ei ON ei.provider_identifier = aam.provider_identifier
ON CONFLICT (account_id, provider_id, provider_identifier)
WHERE deleted_at IS NULL
DO UPDATE SET
email = EXCLUDED.email,
provider_state = COALESCE(auth_identities.provider_state, '{}'::jsonb) ||

View File

@@ -50,7 +50,6 @@ defmodule Domain.Auth.Identity.Sync do
{:ok, identities}
end
# TODO: Update after `deleted_at` is removed from DB
defp plan_identities_update(identities, provider_identifiers) do
{insert, update, delete} =
Enum.reduce(
@@ -59,15 +58,10 @@ defmodule Domain.Auth.Identity.Sync do
fn identity, {insert, update, delete} ->
insert = insert -- [identity.provider_identifier]
cond do
identity.provider_identifier in provider_identifiers ->
{insert, [identity.provider_identifier] ++ update, delete}
not is_nil(identity.deleted_at) ->
{insert, update, delete}
true ->
{insert, update, [identity.provider_identifier] ++ delete}
if identity.provider_identifier in provider_identifiers do
{insert, [identity.provider_identifier] ++ update, delete}
else
{insert, update, [identity.provider_identifier] ++ delete}
end
end
)
@@ -146,7 +140,6 @@ defmodule Domain.Auth.Identity.Sync do
end)
end
# TODO: Update after `deleted_at` is removed from DB
defp update_identities_and_actors(
identities,
attrs_by_provider_identifier,
@@ -159,19 +152,7 @@ defmodule Domain.Auth.Identity.Sync do
end)
|> Repo.preload(:actor)
|> Enum.reduce(%{}, fn identity, acc ->
acc_identity = Map.get(acc, identity.provider_identifier)
# make sure that deleted identities have the least priority in case of conflicts
cond do
is_nil(acc_identity) ->
Map.put(acc, identity.provider_identifier, identity)
is_nil(acc_identity.deleted_at) ->
acc
true ->
Map.put(acc, identity.provider_identifier, identity)
end
Map.put(acc, identity.provider_identifier, identity)
end)
provider_identifiers_to_update

View File

@@ -14,9 +14,8 @@ defmodule Domain.Auth.Provider do
belongs_to :account, Domain.Accounts.Account
# TODO: HARD-DELETE - Remove `where` after `deleted_at` is removed from DB
has_many :actor_groups, Domain.Actors.Group, where: [deleted_at: nil]
has_many :identities, Domain.Auth.Identity, where: [deleted_at: nil]
has_many :actor_groups, Domain.Actors.Group
has_many :identities, Domain.Auth.Identity
field :created_by, Ecto.Enum, values: ~w[system identity actor]a
field :created_by_subject, :map
@@ -29,8 +28,6 @@ defmodule Domain.Auth.Provider do
field :disabled_at, :utc_datetime_usec
# TODO: HARD-DELETE - Remove field after soft deletion is removed
field :deleted_at, :utc_datetime_usec
field :assigned_default_at, :utc_datetime_usec
timestamps()
end

View File

@@ -4,10 +4,9 @@ defmodule Domain.Auth.Provider.Changeset do
alias Domain.Auth.{Subject, Provider, Adapters}
@create_fields ~w[id name adapter provisioner adapter_config adapter_state disabled_at assigned_default_at]a
# TODO: HARD-DELETE - Update after `deleted_at` is removed from DB
@update_fields ~w[name adapter_config
last_syncs_failed last_sync_error sync_disabled_at sync_error_emailed_at
adapter_state provisioner disabled_at deleted_at]a
adapter_state provisioner disabled_at]a
@required_fields ~w[name adapter adapter_config provisioner]a
def create(account, attrs, %Subject{} = subject) do
@@ -131,11 +130,4 @@ defmodule Domain.Auth.Provider.Changeset do
|> change()
|> put_change(:disabled_at, nil)
end
# TODO: HARD-DELETE - Remove after `deleted_at` is removed from DB
def soft_delete_provider(%Provider{} = provider) do
provider
|> change()
|> put_default_value(:deleted_at, DateTime.utc_now())
end
end

View File

@@ -5,13 +5,7 @@ defmodule Domain.Auth.Provider.Query do
from(provider in Domain.Auth.Provider, as: :providers)
end
# TODO: HARD-DELETE - Remove after `deleted_at` is removed from the DB
def not_deleted do
all()
|> where([providers: providers], is_nil(providers.deleted_at))
end
def not_disabled(queryable \\ not_deleted()) do
def not_disabled(queryable \\ all()) do
where(queryable, [providers: providers], is_nil(providers.disabled_at))
end

View File

@@ -18,13 +18,13 @@ defmodule Domain.Clients do
end
def count_by_account_id(account_id) do
Client.Query.not_deleted()
Client.Query.all()
|> Client.Query.by_account_id(account_id)
|> Repo.aggregate(:count)
end
def count_1m_active_users_for_account(%Accounts.Account{} = account) do
Client.Query.not_deleted()
Client.Query.all()
|> Client.Query.by_account_id(account.id)
|> Client.Query.by_last_seen_within(1, "month")
|> Client.Query.select_distinct_actor_id()
@@ -34,13 +34,13 @@ defmodule Domain.Clients do
end
def count_by_actor_id(actor_id) do
Client.Query.not_deleted()
Client.Query.all()
|> Client.Query.by_actor_id(actor_id)
|> Repo.aggregate(:count)
end
def count_incompatible_for(account, gateway_version) do
Client.Query.not_deleted()
Client.Query.all()
|> Client.Query.by_account_id(account.id)
|> Client.Query.by_last_seen_within(1, "week")
|> Client.Query.by_incompatible_for(gateway_version)
@@ -49,7 +49,7 @@ defmodule Domain.Clients do
end
def fetch_client_by_id(id, preload: :identity) do
Client.Query.not_deleted()
Client.Query.all()
|> Client.Query.by_id(id)
|> Repo.fetch(Client.Query, preload: :identity)
end
@@ -75,7 +75,7 @@ defmodule Domain.Clients do
end
def fetch_client_by_id!(id, opts \\ []) do
Client.Query.not_deleted()
Client.Query.all()
|> Client.Query.by_id(id)
|> Repo.fetch!(Client.Query, opts)
end
@@ -89,7 +89,7 @@ defmodule Domain.Clients do
]}
with :ok <- Auth.ensure_has_permissions(subject, required_permissions) do
Client.Query.not_deleted()
Client.Query.all()
|> Authorizer.for_subject(subject)
|> Repo.list(Client.Query, opts)
end
@@ -109,7 +109,7 @@ defmodule Domain.Clients do
with :ok <- Auth.ensure_has_permissions(subject, required_permissions),
true <- Repo.valid_uuid?(actor_id) do
Client.Query.not_deleted()
Client.Query.all()
|> Client.Query.by_actor_id(actor_id)
|> Authorizer.for_subject(subject)
|> Repo.list(Client.Query, opts)
@@ -196,7 +196,7 @@ defmodule Domain.Clients do
def update_client(%Client{} = client, attrs, %Auth.Subject{} = subject) do
with :ok <- Authorizer.ensure_has_access_to(client, subject) do
Client.Query.not_deleted()
Client.Query.all()
|> Client.Query.by_id(client.id)
|> Authorizer.for_subject(subject)
|> Repo.fetch_and_update(Client.Query,
@@ -209,7 +209,7 @@ defmodule Domain.Clients do
def verify_client(%Client{} = client, %Auth.Subject{} = subject) do
with :ok <- Authorizer.ensure_has_access_to(client, subject),
:ok <- Auth.ensure_has_permissions(subject, Authorizer.verify_clients_permission()) do
Client.Query.not_deleted()
Client.Query.all()
|> Client.Query.by_id(client.id)
|> Authorizer.for_subject(subject)
|> Repo.fetch_and_update(Client.Query,
@@ -222,7 +222,7 @@ defmodule Domain.Clients do
def remove_client_verification(%Client{} = client, %Auth.Subject{} = subject) do
with :ok <- Authorizer.ensure_has_access_to(client, subject),
:ok <- Auth.ensure_has_permissions(subject, Authorizer.verify_clients_permission()) do
Client.Query.not_deleted()
Client.Query.all()
|> Client.Query.by_id(client.id)
|> Authorizer.for_subject(subject)
|> Repo.fetch_and_update(Client.Query,
@@ -240,7 +240,7 @@ defmodule Domain.Clients do
def delete_clients_for(%Actors.Actor{} = actor, %Auth.Subject{} = subject) do
queryable =
Client.Query.not_deleted()
Client.Query.all()
|> Client.Query.by_actor_id(actor.id)
|> Client.Query.by_account_id(actor.account_id)
@@ -252,7 +252,6 @@ defmodule Domain.Clients do
end
end
# TODO: HARD-DELETE
# We don't necessarily want to delete associated tokens when deleting a client because
# that token could be a multi-owner token in the case of a headless client.
# Instead we need to introduce the concept of ephemeral clients/gateways and permanent ones.

View File

@@ -28,7 +28,6 @@ defmodule Domain.Clients.Client do
verified_at: DateTime.t() | nil,
verified_by: :system | :actor | :identity | nil,
verified_by_subject: map() | nil,
deleted_at: DateTime.t() | nil,
inserted_at: DateTime.t(),
updated_at: DateTime.t()
}
@@ -70,8 +69,6 @@ defmodule Domain.Clients.Client do
field :verified_by, Ecto.Enum, values: [:system, :actor, :identity]
field :verified_by_subject, :map
# TODO: HARD-DELETE - Remove field after soft deletion is removed
field :deleted_at, :utc_datetime_usec
timestamps()
end
end

View File

@@ -12,9 +12,8 @@ defmodule Domain.Clients.Client.Changeset do
# WireGuard base64-encoded string length
@key_length 44
# TODO: Update or remove after `deleted_at` is removed from DB
def upsert_conflict_target,
do: {:unsafe_fragment, ~s/(account_id, actor_id, external_id) WHERE deleted_at IS NULL/}
do: {:unsafe_fragment, ~s/(account_id, actor_id, external_id)/}
def upsert_on_conflict do
Clients.Client.Query.all()

View File

@@ -5,12 +5,6 @@ defmodule Domain.Clients.Client.Query do
from(clients in Domain.Clients.Client, as: :clients)
end
# TODO: HARD-DELETE - Remove after `deleted_at` column is removed from DB
def not_deleted do
all()
|> where([clients: clients], is_nil(clients.deleted_at))
end
def by_id(queryable, id) do
where(queryable, [clients: clients], clients.id == ^id)
end
@@ -45,7 +39,7 @@ defmodule Domain.Clients.Client.Query do
|> distinct(true)
end
def count_clients_by_actor_id(queryable \\ not_deleted()) do
def count_clients_by_actor_id(queryable \\ all()) do
queryable
|> group_by([clients: clients], clients.actor_id)
|> select([clients: clients], %{
@@ -67,28 +61,13 @@ defmodule Domain.Clients.Client.Query do
)
end
def returning_not_deleted(queryable) do
select(queryable, [clients: clients], clients)
end
# TODO: HARD-DELETE - Remove after `deleted_at` is removed from the DB
def soft_delete(queryable) do
queryable
|> Ecto.Query.select([clients: clients], clients)
|> Ecto.Query.update([clients: clients],
set: [
deleted_at: fragment("COALESCE(?, timezone('UTC', NOW()))", clients.deleted_at)
]
)
end
def with_joined_actor(queryable) do
with_named_binding(queryable, :actor, fn queryable, binding ->
join(
queryable,
:inner,
[clients: clients],
actor in ^Domain.Actors.Actor.Query.not_deleted(),
actor in ^Domain.Actors.Actor.Query.all(),
on: clients.actor_id == actor.id,
as: ^binding
)

View File

@@ -21,14 +21,14 @@ defmodule Domain.Gateways do
end
def count_groups_for_account(%Accounts.Account{} = account) do
Group.Query.not_deleted()
Group.Query.all()
|> Group.Query.by_account_id(account.id)
|> Group.Query.by_managed_by(:account)
|> Repo.aggregate(:count)
end
def fetch_gateway_by_id(id) do
Gateway.Query.not_deleted()
Gateway.Query.all()
|> Gateway.Query.by_id(id)
|> Repo.fetch(Gateway.Query, [])
end
@@ -54,7 +54,7 @@ defmodule Domain.Gateways do
end
def fetch_internet_group(%Accounts.Account{} = account) do
Group.Query.not_deleted()
Group.Query.all()
|> Group.Query.by_managed_by(:system)
|> Group.Query.by_account_id(account.id)
|> Group.Query.by_name("Internet")
@@ -63,21 +63,21 @@ defmodule Domain.Gateways do
def list_groups(%Auth.Subject{} = subject, opts \\ []) do
with :ok <- Auth.ensure_has_permissions(subject, Authorizer.manage_gateways_permission()) do
Group.Query.not_deleted()
Group.Query.all()
|> Authorizer.for_subject(subject)
|> Repo.list(Group.Query, opts)
end
end
def all_groups!(%Auth.Subject{} = subject) do
Group.Query.not_deleted()
Group.Query.all()
|> Group.Query.by_managed_by(:account)
|> Authorizer.for_subject(subject)
|> Repo.all()
end
def all_groups_for_account!(%Accounts.Account{} = account) do
Group.Query.not_deleted()
Group.Query.all()
|> Group.Query.by_managed_by(:account)
|> Group.Query.by_account_id(account.id)
|> Repo.all()
@@ -124,7 +124,7 @@ defmodule Domain.Gateways do
def update_group(%Group{managed_by: :account} = group, attrs, %Auth.Subject{} = subject) do
with :ok <- Auth.ensure_has_permissions(subject, Authorizer.manage_gateways_permission()) do
Group.Query.not_deleted()
Group.Query.all()
|> Group.Query.by_id(group.id)
|> Authorizer.for_subject(subject)
|> Repo.fetch_and_update(
@@ -167,7 +167,7 @@ defmodule Domain.Gateways do
def authenticate(encoded_token, %Auth.Context{} = context) when is_binary(encoded_token) do
with {:ok, token} <- Tokens.use_token(encoded_token, context),
queryable = Group.Query.not_deleted() |> Group.Query.by_id(token.gateway_group_id),
queryable = Group.Query.all() |> Group.Query.by_id(token.gateway_group_id),
{:ok, group} <- Repo.fetch(queryable, Group.Query, []) do
{:ok, group, token}
else
@@ -197,14 +197,14 @@ defmodule Domain.Gateways do
end
def fetch_gateway_by_id!(id, opts \\ []) do
Gateway.Query.not_deleted()
Gateway.Query.all()
|> Gateway.Query.by_id(id)
|> Repo.fetch!(Gateway.Query, opts)
end
def list_gateways(%Auth.Subject{} = subject, opts \\ []) do
with :ok <- Auth.ensure_has_permissions(subject, Authorizer.manage_gateways_permission()) do
Gateway.Query.not_deleted()
Gateway.Query.all()
|> Authorizer.for_subject(subject)
|> Repo.list(Gateway.Query, opts)
end
@@ -213,7 +213,7 @@ defmodule Domain.Gateways do
def all_gateways_for_account!(%Account{} = account, opts \\ []) do
{preload, _opts} = Keyword.pop(opts, :preload, [])
Gateway.Query.not_deleted()
Gateway.Query.all()
|> Gateway.Query.by_account_id(account.id)
|> Repo.all()
|> Repo.preload(preload)
@@ -265,7 +265,7 @@ defmodule Domain.Gateways do
|> Map.keys()
gateways =
Gateway.Query.not_deleted()
Gateway.Query.all()
|> Gateway.Query.by_ids(connected_gateway_ids)
|> Gateway.Query.by_account_id(subject.account.id)
|> Gateway.Query.by_resource_id(resource_id)
@@ -314,7 +314,7 @@ defmodule Domain.Gateways do
def update_gateway(%Gateway{} = gateway, attrs, %Auth.Subject{} = subject) do
with :ok <- Auth.ensure_has_permissions(subject, Authorizer.manage_gateways_permission()) do
Gateway.Query.not_deleted()
Gateway.Query.all()
|> Gateway.Query.by_id(gateway.id)
|> Authorizer.for_subject(subject)
|> Repo.fetch_and_update(Gateway.Query,
@@ -324,19 +324,6 @@ defmodule Domain.Gateways do
end
end
# TODO: HARD-DELETE - Remove this once deleted_at field is gone
def soft_delete_gateway(%Gateway{} = gateway, %Auth.Subject{} = subject) do
with :ok <- Auth.ensure_has_permissions(subject, Authorizer.manage_gateways_permission()) do
Gateway.Query.not_deleted()
|> Gateway.Query.by_id(gateway.id)
|> Authorizer.for_subject(subject)
|> Repo.fetch_and_update(Gateway.Query,
with: &Gateway.Changeset.soft_delete/1,
preload: [:online?]
)
end
end
def delete_gateway(%Gateway{} = gateway, %Auth.Subject{} = subject) do
with :ok <- Authorizer.ensure_has_access_to(gateway, subject) do
Repo.delete(gateway)

View File

@@ -20,7 +20,6 @@ defmodule Domain.Gateways.Gateway do
online?: boolean(),
account_id: Ecto.UUID.t(),
group_id: Ecto.UUID.t(),
deleted_at: DateTime.t(),
inserted_at: DateTime.t(),
updated_at: DateTime.t()
}
@@ -50,8 +49,6 @@ defmodule Domain.Gateways.Gateway do
belongs_to :account, Domain.Accounts.Account
belongs_to :group, Domain.Gateways.Group
# TODO: HARD-DELETE - Remove field after soft deletion is removed
field :deleted_at, :utc_datetime_usec
timestamps()
end
end

View File

@@ -27,9 +27,8 @@ defmodule Domain.Gateways.Gateway.Changeset do
# WireGuard base64-encoded string length
@key_length 44
# TODO: Update or remove after `deleted_at` is removed from DB
def upsert_conflict_target,
do: {:unsafe_fragment, ~s/(account_id, group_id, external_id) WHERE deleted_at IS NULL/}
do: {:unsafe_fragment, ~s/(account_id, group_id, external_id)/}
def upsert_on_conflict, do: {:replace, @conflict_replace_fields}
@@ -72,13 +71,6 @@ defmodule Domain.Gateways.Gateway.Changeset do
|> validate_required(@required_fields)
end
# TODO: HARD-DELETE - Remove after `deleted_at` is removed from DB
def soft_delete(%Gateways.Gateway{} = gateway) do
gateway
|> change()
|> put_default_value(:deleted_at, DateTime.utc_now())
end
defp changeset(changeset) do
changeset
|> trim_change(:name)

View File

@@ -5,12 +5,6 @@ defmodule Domain.Gateways.Gateway.Query do
from(gateways in Domain.Gateways.Gateway, as: :gateways)
end
# TODO: HARD-DELETE - Remove after `deleted_at` column is removed from DB
def not_deleted do
all()
|> where([gateways: gateways], is_nil(gateways.deleted_at))
end
def by_id(queryable, id) do
where(queryable, [gateways: gateways], gateways.id == ^id)
end
@@ -37,10 +31,6 @@ defmodule Domain.Gateways.Gateway.Query do
|> where([connections: connections], connections.resource_id == ^resource_id)
end
def returning_not_deleted(queryable) do
select(queryable, [gateways: gateways], gateways)
end
def with_joined_connections(queryable) do
with_named_binding(queryable, :connections, fn queryable, binding ->
queryable
@@ -92,11 +82,6 @@ defmodule Domain.Gateways.Gateway.Query do
name: :ids,
type: {:list, {:string, :uuid}},
fun: &filter_by_ids/2
},
%Domain.Repo.Filter{
name: :deleted?,
type: :boolean,
fun: &filter_deleted/1
}
]
@@ -107,9 +92,4 @@ defmodule Domain.Gateways.Gateway.Query do
def filter_by_ids(queryable, ids) do
{queryable, dynamic([gateways: gateways], gateways.id in ^ids)}
end
# TODO: HARD-DELETE - Remove after `deleted_at` column is removed from DB
def filter_deleted(queryable) do
{queryable, dynamic([gateways: gateways], not is_nil(gateways.deleted_at))}
end
end

View File

@@ -8,7 +8,6 @@ defmodule Domain.Gateways.Group do
account_id: Ecto.UUID.t(),
created_by: :actor | :identity | :system,
created_by_subject: map(),
deleted_at: DateTime.t() | nil,
inserted_at: DateTime.t(),
updated_at: DateTime.t()
}
@@ -19,21 +18,15 @@ defmodule Domain.Gateways.Group do
field :managed_by, Ecto.Enum, values: ~w[account system]a
belongs_to :account, Domain.Accounts.Account
# TODO: HARD-DELETE - Remove `where` after `deleted_at` column is remove
has_many :gateways, Domain.Gateways.Gateway, foreign_key: :group_id, where: [deleted_at: nil]
has_many :gateways, Domain.Gateways.Gateway, foreign_key: :group_id
# TODO: HARD-DELETE - Remove `where` after `deleted_at` column is remove
has_many :tokens, Domain.Tokens.Token,
foreign_key: :gateway_group_id,
where: [deleted_at: nil]
has_many :tokens, Domain.Tokens.Token, foreign_key: :gateway_group_id
has_many :connections, Domain.Resources.Connection, foreign_key: :gateway_group_id
field :created_by, Ecto.Enum, values: ~w[actor identity system]a
field :created_by_subject, :map
# TODO: HARD-DELETE - Remove field after soft deletion is removed
field :deleted_at, :utc_datetime_usec
timestamps()
end
end

View File

@@ -39,11 +39,4 @@ defmodule Domain.Gateways.Group.Changeset do
|> validate_length(:name, min: 1, max: 64)
|> unique_constraint(:name, name: :gateway_groups_account_id_name_managed_by_index)
end
# TODO: HARD-DELETE - Remove after `deleted_at` is removed from the DB
def delete(%Gateways.Group{} = group) do
group
|> change()
|> put_default_value(:deleted_at, DateTime.utc_now())
end
end

View File

@@ -5,12 +5,6 @@ defmodule Domain.Gateways.Group.Query do
from(groups in Domain.Gateways.Group, as: :groups)
end
# TODO: HARD-DELETE - Remove after `deleted_at` is removed from DB
def not_deleted do
all()
|> where([groups: groups], is_nil(groups.deleted_at))
end
def by_id(queryable, id) do
where(queryable, [groups: groups], groups.id == ^id)
end
@@ -45,11 +39,6 @@ defmodule Domain.Gateways.Group.Query do
@impl Domain.Repo.Query
def filters,
do: [
%Domain.Repo.Filter{
name: :deleted?,
type: :boolean,
fun: &filter_deleted/1
},
%Domain.Repo.Filter{
name: :managed_by,
type: :string,
@@ -57,11 +46,6 @@ defmodule Domain.Gateways.Group.Query do
}
]
# TODO: HARD-DELETE - Remove after `deleted_at` is removed from DB
def filter_deleted(queryable) do
{queryable, dynamic([groups: groups], not is_nil(groups.deleted_at))}
end
def filter_managed_by(queryable, managed_by) do
{queryable, dynamic([groups: groups], groups.managed_by == ^managed_by)}
end

View File

@@ -109,7 +109,7 @@ defmodule Domain.Ops do
To delete an account you need to disable it first by cancelling its subscription in Stripe.
"""
def delete_disabled_account(id) do
Domain.Accounts.Account.Query.not_deleted()
Domain.Accounts.Account.Query.all()
|> Domain.Accounts.Account.Query.disabled()
|> Domain.Accounts.Account.Query.by_id(id)
|> Domain.Repo.one!()

View File

@@ -1,6 +1,6 @@
defmodule Domain.Policies do
alias Domain.Repo
alias Domain.{Auth, Actors, Cache.Cacheable, Clients, Resources}
alias Domain.{Auth, Cache.Cacheable, Clients}
alias Domain.Policies.{Authorizer, Policy, Condition}
require Logger
@@ -24,26 +24,6 @@ defmodule Domain.Policies do
end
end
def fetch_policy_by_id_or_persistent_id(id, %Auth.Subject{} = subject, opts \\ []) do
required_permissions =
{:one_of,
[
Authorizer.manage_policies_permission(),
Authorizer.view_available_policies_permission()
]}
with :ok <- Auth.ensure_has_permissions(subject, required_permissions),
true <- Repo.valid_uuid?(id) do
Policy.Query.all()
|> Policy.Query.by_id_or_persistent_id(id)
|> Authorizer.for_subject(subject)
|> Repo.fetch(Policy.Query, opts)
else
false -> {:error, :not_found}
other -> other
end
end
def list_policies(%Auth.Subject{} = subject, opts \\ []) do
required_permissions =
{:one_of,
@@ -53,7 +33,7 @@ defmodule Domain.Policies do
]}
with :ok <- Auth.ensure_has_permissions(subject, required_permissions) do
Policy.Query.not_deleted()
Policy.Query.all()
|> Authorizer.for_subject(subject)
|> Repo.list(Policy.Query, opts)
end
@@ -108,7 +88,7 @@ defmodule Domain.Policies do
def update_policy(%Policy{} = policy, attrs, %Auth.Subject{} = subject) do
with :ok <- Auth.ensure_has_permissions(subject, Authorizer.manage_policies_permission()),
:ok <- ensure_has_access_to(subject, policy) do
Policy.Query.not_deleted()
Policy.Query.all()
|> Policy.Query.by_id(policy.id)
|> Authorizer.for_subject(subject)
|> Repo.fetch_and_update(Policy.Query,
@@ -119,7 +99,7 @@ defmodule Domain.Policies do
def disable_policy(%Policy{} = policy, %Auth.Subject{} = subject) do
with :ok <- Auth.ensure_has_permissions(subject, Authorizer.manage_policies_permission()) do
Policy.Query.not_deleted()
Policy.Query.all()
|> Policy.Query.by_id(policy.id)
|> Authorizer.for_subject(subject)
|> Repo.fetch_and_update(Policy.Query,
@@ -130,7 +110,7 @@ defmodule Domain.Policies do
def enable_policy(%Policy{} = policy, %Auth.Subject{} = subject) do
with :ok <- Auth.ensure_has_permissions(subject, Authorizer.manage_policies_permission()) do
Policy.Query.not_deleted()
Policy.Query.all()
|> Policy.Query.by_id(policy.id)
|> Authorizer.for_subject(subject)
|> Repo.fetch_and_update(Policy.Query,
@@ -139,120 +119,12 @@ defmodule Domain.Policies do
end
end
# TODO: HARD-DELETE - Remove after `deleted_at` column is removed from DB
def soft_delete_policy(%Policy{} = policy, %Auth.Subject{} = subject) do
Policy.Query.not_deleted()
|> Policy.Query.by_id(policy.id)
|> soft_delete_policies(subject)
|> case do
{:ok, [policy]} -> {:ok, policy}
{:ok, []} -> {:error, :not_found}
{:error, reason} -> {:error, reason}
end
end
def delete_policy(%Policy{} = policy, %Auth.Subject{} = subject) do
with :ok <- Authorizer.ensure_has_access_to(policy, subject) do
Repo.delete(policy)
end
end
# TODO: HARD-DELETE - Should not be needed after hard delete is implemented
def delete_policies_for(%Resources.Resource{} = resource, %Auth.Subject{} = subject) do
with :ok <- Auth.ensure_has_permissions(subject, Authorizer.manage_policies_permission()) do
{count, nil} =
Policy.Query.all()
|> Policy.Query.by_resource_id(resource.id)
|> Authorizer.for_subject(subject)
|> Repo.delete_all()
{:ok, count}
end
end
# TODO: HARD-DELETE - Should not be needed after hard delete is implemented
def delete_policies_for(%Actors.Group{} = actor_group, %Auth.Subject{} = subject) do
with :ok <- Auth.ensure_has_permissions(subject, Authorizer.manage_policies_permission()) do
{count, nil} =
Policy.Query.all()
|> Policy.Query.by_actor_group_id(actor_group.id)
|> Authorizer.for_subject(subject)
|> Repo.delete_all()
{:ok, count}
end
end
# TODO: HARD-DELETE - Should not be needed after hard delete is implemented
def delete_policies_for(%Auth.Provider{} = provider, %Auth.Subject{} = subject) do
with :ok <- Auth.ensure_has_permissions(subject, Authorizer.manage_policies_permission()) do
{count, nil} =
Policy.Query.all()
|> Policy.Query.by_actor_group_provider_id(provider.id)
|> Authorizer.for_subject(subject)
|> Repo.delete_all()
{:ok, count}
end
end
# TODO: HARD-DELETE - Should not be needed after hard delete is implemented
def delete_policies_for(%Actors.Group{} = actor_group) do
{count, nil} =
Policy.Query.all()
|> Policy.Query.by_actor_group_id(actor_group.id)
|> Repo.delete_all()
{:ok, count}
end
# TODO: HARD-DELETE - Remove after `deleted_at` column is removed from DB
def soft_delete_policies_for(%Resources.Resource{} = resource, %Auth.Subject{} = subject) do
Policy.Query.not_deleted()
|> Policy.Query.by_resource_id(resource.id)
|> soft_delete_policies(subject)
end
# TODO: HARD-DELETE - Remove after `deleted_at` column is removed from DB
def soft_delete_policies_for(%Actors.Group{} = actor_group, %Auth.Subject{} = subject) do
Policy.Query.not_deleted()
|> Policy.Query.by_actor_group_id(actor_group.id)
|> soft_delete_policies(subject)
end
# TODO: HARD-DELETE - Remove after `deleted_at` column is removed from DB
def soft_delete_policies_for(%Auth.Provider{} = provider, %Auth.Subject{} = subject) do
Policy.Query.not_deleted()
|> Policy.Query.by_actor_group_provider_id(provider.id)
|> soft_delete_policies(subject)
end
# TODO: HARD-DELETE - Remove after `deleted_at` column is removed from DB
def soft_delete_policies_for(%Actors.Group{} = actor_group) do
Policy.Query.not_deleted()
|> Policy.Query.by_actor_group_id(actor_group.id)
|> soft_delete_policies()
end
# TODO: HARD-DELETE - Remove after `deleted_at` column is removed from DB
defp soft_delete_policies(queryable, subject) do
with :ok <- Auth.ensure_has_permissions(subject, Authorizer.manage_policies_permission()) do
queryable
|> Authorizer.for_subject(subject)
|> soft_delete_policies()
end
end
# TODO: HARD-DELETE - Remove after `deleted_at` column is removed from DB
defp soft_delete_policies(queryable) do
{_count, policies} =
queryable
|> Policy.Query.delete()
|> Repo.update_all([])
{:ok, policies}
end
def filter_by_conforming_policies_for_client(policies, %Clients.Client{} = client) do
Enum.filter(policies, fn policy ->
policy.conditions

View File

@@ -3,7 +3,6 @@ defmodule Domain.Policies.Policy do
@type t :: %__MODULE__{
id: Ecto.UUID.t(),
persistent_id: Ecto.UUID.t(),
description: String.t() | nil,
conditions: [Domain.Policies.Condition.t()],
actor_group_id: Ecto.UUID.t(),
@@ -11,16 +10,12 @@ defmodule Domain.Policies.Policy do
account_id: Ecto.UUID.t(),
created_by: :actor | :identity,
created_by_subject: map(),
replaced_by_policy_id: Ecto.UUID.t() | nil,
disabled_at: DateTime.t() | nil,
deleted_at: DateTime.t() | nil,
inserted_at: DateTime.t(),
updated_at: DateTime.t()
}
schema "policies" do
field :persistent_id, Ecto.UUID
field :description, :string
embeds_many :conditions, Domain.Policies.Condition, on_replace: :delete
@@ -32,13 +27,8 @@ defmodule Domain.Policies.Policy do
field :created_by, Ecto.Enum, values: ~w[actor identity]a
field :created_by_subject, :map
belongs_to :replaced_by_policy, Domain.Policies.Policy
has_one :replaces_policy, Domain.Policies.Policy, foreign_key: :replaced_by_policy_id
field :disabled_at, :utc_datetime_usec
# TODO: HARD-DELETE - Remove field after soft deletion is removed
field :deleted_at, :utc_datetime_usec
timestamps()
end
end

View File

@@ -15,7 +15,6 @@ defmodule Domain.Policies.Policy.Changeset do
|> changeset()
|> put_change(:account_id, subject.account.id)
|> put_subject_trail(:created_by, subject)
|> put_change(:persistent_id, Ecto.UUID.generate())
end
def update(%Policy{} = policy, attrs) do

View File

@@ -5,17 +5,11 @@ defmodule Domain.Policies.Policy.Query do
from(policies in Domain.Policies.Policy, as: :policies)
end
# TODO: HARD-DELETE - Remove after `deleted_at` column is removed from DB
def not_deleted do
all()
|> where([policies: policies], is_nil(policies.deleted_at))
end
def not_disabled(queryable \\ not_deleted()) do
def not_disabled(queryable \\ all()) do
where(queryable, [policies: policies], is_nil(policies.disabled_at))
end
def disabled(queryable \\ not_deleted()) do
def disabled(queryable \\ all()) do
where(queryable, [policies: policies], not is_nil(policies.disabled_at))
end
@@ -23,14 +17,6 @@ defmodule Domain.Policies.Policy.Query do
where(queryable, [policies: policies], policies.id == ^id)
end
def by_id_or_persistent_id(queryable, id) do
where(queryable, [policies: policies], policies.id == ^id)
|> or_where(
[policies: policies],
policies.persistent_id == ^id and is_nil(policies.replaced_by_policy_id)
)
end
def by_account_id(queryable, account_id) do
where(queryable, [policies: policies], policies.account_id == ^account_id)
end
@@ -85,17 +71,6 @@ defmodule Domain.Policies.Policy.Query do
})
end
# TODO: HARD-DELETE - Remove after `deleted_at` column is removed from DB
def delete(queryable) do
queryable
|> Ecto.Query.select([policies: policies], policies)
|> Ecto.Query.update([policies: policies],
set: [
deleted_at: fragment("COALESCE(?, timezone('UTC', NOW()))", policies.deleted_at)
]
)
end
def with_joined_actor_group(queryable) do
with_named_binding(queryable, :actor_group, fn queryable, binding ->
join(queryable, :inner, [policies: policies], actor_group in assoc(policies, ^binding),
@@ -192,11 +167,6 @@ defmodule Domain.Policies.Policy.Query do
{"Disabled", "disabled"}
],
fun: &filter_by_status/2
},
%Domain.Repo.Filter{
name: :deleted?,
type: :boolean,
fun: &filter_deleted/1
}
]
@@ -252,9 +222,4 @@ defmodule Domain.Policies.Policy.Query do
def filter_by_status(queryable, "disabled") do
{queryable, dynamic([policies: policies], not is_nil(policies.disabled_at))}
end
# TODO: HARD-DELETE - Remove after `deleted_at` column is removed from DB
def filter_deleted(queryable) do
{queryable, dynamic([policies: policies], not is_nil(policies.deleted_at))}
end
end

View File

@@ -39,7 +39,7 @@ defmodule Domain.Relays do
def list_groups(%Auth.Subject{} = subject, opts \\ []) do
with :ok <- Auth.ensure_has_permissions(subject, Authorizer.manage_relays_permission()) do
Group.Query.not_deleted()
Group.Query.all()
|> Authorizer.for_subject(subject)
|> Repo.list(Group.Query, opts)
end
@@ -93,30 +93,6 @@ defmodule Domain.Relays do
end
end
# TODO: HARD-DELETE - Remove after `deleted_at` column is removed from DB
def soft_delete_group(%Group{} = group, %Auth.Subject{} = subject) do
with :ok <- Auth.ensure_has_permissions(subject, Authorizer.manage_relays_permission()) do
Group.Query.not_deleted()
|> Group.Query.by_id(group.id)
|> Authorizer.for_subject(subject)
|> Group.Query.by_account_id(subject.account.id)
|> Repo.fetch_and_update(Group.Query,
with: fn group ->
{:ok, _tokens} = Tokens.soft_delete_tokens_for(group, subject)
{_count, _} =
Relay.Query.not_deleted()
|> Relay.Query.by_group_id(group.id)
|> Repo.update_all(set: [deleted_at: DateTime.utc_now()])
Group.Changeset.delete(group)
end,
# TODO: Remove self-hosted relays
after_commit: &disconnect_relays_in_group/1
)
end
end
def create_token(%Group{account_id: nil} = group, attrs) do
attrs =
Map.merge(attrs, %{
@@ -153,7 +129,7 @@ defmodule Domain.Relays do
def authenticate(encoded_token, %Auth.Context{} = context) when is_binary(encoded_token) do
with {:ok, token} <- Tokens.use_token(encoded_token, context),
queryable = Group.Query.not_deleted() |> Group.Query.by_id(token.relay_group_id),
queryable = Group.Query.all() |> Group.Query.by_id(token.relay_group_id),
{:ok, group} <- Repo.fetch(queryable, Group.Query, []) do
{:ok, group, token}
else
@@ -176,14 +152,14 @@ defmodule Domain.Relays do
end
def fetch_relay_by_id!(id, opts \\ []) do
Relay.Query.not_deleted()
Relay.Query.all()
|> Relay.Query.by_id(id)
|> Repo.fetch!(Relay.Query, opts)
end
def list_relays(%Auth.Subject{} = subject, opts \\ []) do
with :ok <- Auth.ensure_has_permissions(subject, Authorizer.manage_relays_permission()) do
Relay.Query.not_deleted()
Relay.Query.all()
|> Authorizer.for_subject(subject)
|> Repo.list(Relay.Query, opts)
end
@@ -257,7 +233,7 @@ defmodule Domain.Relays do
connected_relay_ids = Map.keys(connected_relays) -- except_ids
relays =
Relay.Query.not_deleted()
Relay.Query.all()
|> Relay.Query.by_ids(connected_relay_ids)
|> Relay.Query.global_or_by_account_id(account_id)
# |> Relay.Query.by_last_seen_at_greater_than(5, "second", :ago)
@@ -315,20 +291,6 @@ defmodule Domain.Relays do
end
end
# TODO: HARD-DELETE - Remove after `deleted_at` is removed from DB
def soft_delete_relay(%Relay{} = relay, %Auth.Subject{} = subject) do
with :ok <- Auth.ensure_has_permissions(subject, Authorizer.manage_relays_permission()) do
Relay.Query.not_deleted()
|> Relay.Query.by_id(relay.id)
|> Authorizer.for_subject(subject)
|> Repo.fetch_and_update(Relay.Query,
with: &Relay.Changeset.delete/1,
# TODO: Remove self-hosted relays
after_commit: &disconnect_relay/1
)
end
end
@doc """
Selects 3 nearest relays to the given location and then picks one of them randomly.
"""

View File

@@ -5,15 +5,12 @@ defmodule Domain.Relays.Group do
field :name, :string
belongs_to :account, Domain.Accounts.Account
# TODO: HARD-DELETE - Remove `where` after `deleted_at` is removed from the DB
has_many :relays, Domain.Relays.Relay, foreign_key: :group_id, where: [deleted_at: nil]
has_many :tokens, Domain.Tokens.Token, foreign_key: :relay_group_id, where: [deleted_at: nil]
has_many :relays, Domain.Relays.Relay, foreign_key: :group_id
has_many :tokens, Domain.Tokens.Token, foreign_key: :relay_group_id
field :created_by, Ecto.Enum, values: ~w[system identity]a
field :created_by_subject, :map
# TODO: HARD-DELETE - Remove field after soft deletion is removed
field :deleted_at, :utc_datetime_usec
timestamps()
end
end

View File

@@ -36,11 +36,4 @@ defmodule Domain.Relays.Group.Changeset do
|> unique_constraint(:name, name: :relay_groups_name_index)
|> unique_constraint(:name, name: :relay_groups_account_id_name_index)
end
# TODO: HARD-DELETE - Remove after `deleted_at` is removed from the DB
def delete(%Relays.Group{} = group) do
group
|> change()
|> put_default_value(:deleted_at, DateTime.utc_now())
end
end

View File

@@ -5,12 +5,6 @@ defmodule Domain.Relays.Group.Query do
from(groups in Domain.Relays.Group, as: :groups)
end
# TODO: HARD-DELETE - Remove after `deleted_at` is removed from the DB
def not_deleted do
all()
|> where([groups: groups], is_nil(groups.deleted_at))
end
def by_id(queryable, id) do
where(queryable, [groups: groups], groups.id == ^id)
end
@@ -47,17 +41,5 @@ defmodule Domain.Relays.Group.Query do
]
@impl Domain.Repo.Query
def filters,
do: [
%Domain.Repo.Filter{
name: :deleted?,
type: :boolean,
fun: &filter_deleted/1
}
]
# TODO: HARD-DELETE - Remove after `deleted_at` is removed from the DB
def filter_deleted(queryable) do
{queryable, dynamic([groups: groups], not is_nil(groups.deleted_at))}
end
def filters, do: []
end

View File

@@ -25,8 +25,6 @@ defmodule Domain.Relays.Relay do
belongs_to :account, Domain.Accounts.Account
belongs_to :group, Domain.Relays.Group
# TODO: HARD-DELETE - Remove field after soft deletion is removed
field :deleted_at, :utc_datetime_usec
timestamps()
end
end

View File

@@ -21,16 +21,12 @@ defmodule Domain.Relays.Relay.Changeset do
last_seen_at
updated_at]a
# TODO: HARD-DELETE - Update or remove after `deleted_at` is removed from DB
def upsert_conflict_target(%{account_id: nil}) do
{:unsafe_fragment,
~s/(COALESCE(ipv4, ipv6), port) WHERE deleted_at IS NULL AND account_id IS NULL/}
{:unsafe_fragment, ~s/(COALESCE(ipv4, ipv6), port) WHERE account_id IS NULL/}
end
# TODO: HARD-DELETE - Update or remove after `deleted_at` is removed from DB
def upsert_conflict_target(%{account_id: _account_id}) do
{:unsafe_fragment,
~s/(account_id, COALESCE(ipv4, ipv6), port) WHERE deleted_at IS NULL AND account_id IS NOT NULL/}
{:unsafe_fragment, ~s/(account_id, COALESCE(ipv4, ipv6), port) WHERE account_id IS NOT NULL/}
end
def upsert_on_conflict, do: {:replace, @conflict_replace_fields}
@@ -59,13 +55,6 @@ defmodule Domain.Relays.Relay.Changeset do
|> put_change(:group_id, group.id)
end
# TODO: HARD-DELETE - Remove after `deleted_at` is removed from DB
def delete(%Relays.Relay{} = relay) do
relay
|> change()
|> put_default_value(:deleted_at, DateTime.utc_now())
end
def put_relay_version(changeset) do
with {_data_or_changes, user_agent} when not is_nil(user_agent) <-
fetch_field(changeset, :last_seen_user_agent),

View File

@@ -5,12 +5,6 @@ defmodule Domain.Relays.Relay.Query do
from(relays in Domain.Relays.Relay, as: :relays)
end
# TODO: HARD-DELETE - Remove after `deleted_at` is removed from DB
def not_deleted do
all()
|> where([relays: relays], is_nil(relays.deleted_at))
end
def by_id(queryable, id) do
where(queryable, [relays: relays], relays.id == ^id)
end
@@ -55,11 +49,6 @@ defmodule Domain.Relays.Relay.Query do
order_by(queryable, [relays: relays], asc_nulls_first: relays.account_id)
end
# TODO: HARD-DELETE - Remove or possibly rename after `deleted_at` is removed from DB
def returning_not_deleted(queryable) do
select(queryable, [relays: relays], relays)
end
def with_preloaded_user(queryable) do
with_named_binding(queryable, :user, fn queryable, binding ->
queryable

View File

@@ -24,6 +24,16 @@ defmodule Domain.Resources do
end
end
def fetch_resource_by_id!(id) do
if Repo.valid_uuid?(id) do
Resource.Query.all()
|> Resource.Query.by_id(id)
|> Repo.one!()
else
{:error, :not_found}
end
end
def fetch_internet_resource(%Accounts.Account{} = account) do
Resource.Query.all()
|> Resource.Query.by_account_id(account.id)
@@ -41,80 +51,20 @@ defmodule Domain.Resources do
end
end
def fetch_resource_by_id_or_persistent_id(id, %Auth.Subject{} = subject, opts \\ []) do
required_permissions =
{:one_of,
[
Authorizer.manage_resources_permission(),
Authorizer.view_available_resources_permission()
]}
with :ok <- Auth.ensure_has_permissions(subject, required_permissions),
true <- Repo.valid_uuid?(id) do
Resource.Query.all()
|> Resource.Query.by_id_or_persistent_id(id)
|> Authorizer.for_subject(Resource, subject)
|> Repo.fetch(Resource.Query, opts)
else
false -> {:error, :not_found}
other -> other
end
end
def fetch_active_resource_by_id_or_persistent_id(id, %Auth.Subject{} = subject, opts \\ []) do
required_permissions =
{:one_of,
[
Authorizer.manage_resources_permission(),
Authorizer.view_available_resources_permission()
]}
with :ok <- Auth.ensure_has_permissions(subject, required_permissions),
true <- Repo.valid_uuid?(id) do
Resource.Query.not_deleted()
|> Resource.Query.by_id_or_persistent_id(id)
|> Authorizer.for_subject(Resource, subject)
|> Repo.fetch(Resource.Query, opts)
else
false -> {:error, :not_found}
other -> other
end
end
def fetch_resource_by_id!(id) do
if Repo.valid_uuid?(id) do
Resource.Query.not_deleted()
|> Resource.Query.by_id(id)
|> Repo.one!()
else
{:error, :not_found}
end
end
def fetch_all_resources_by_ids(ids) do
Resource.Query.not_deleted()
Resource.Query.all()
|> Resource.Query.by_id({:in, ids})
|> Repo.all()
|> Repo.preload(:gateway_groups)
end
def fetch_resource_by_id_or_persistent_id!(id) do
if Repo.valid_uuid?(id) do
Resource.Query.not_deleted()
|> Resource.Query.by_id_or_persistent_id(id)
|> Repo.one!()
else
{:error, :not_found}
end
end
def all_authorized_resources(%Auth.Subject{} = subject, opts \\ []) do
with :ok <-
Auth.ensure_has_permissions(subject, Authorizer.view_available_resources_permission()) do
{preload, opts} = Keyword.pop(opts, :preload, [])
resources =
Resource.Query.not_deleted()
Resource.Query.all()
|> Resource.Query.by_account_id(subject.account.id)
|> Resource.Query.by_authorized_actor_id(subject.actor.id)
|> Resource.Query.with_at_least_one_gateway_group()
@@ -126,32 +76,32 @@ defmodule Domain.Resources do
end
def all_resources!(%Auth.Subject{} = subject) do
Resource.Query.not_deleted()
Resource.Query.all()
|> Resource.Query.by_account_id(subject.account.id)
|> Resource.Query.filter_features(subject.account)
|> Authorizer.for_subject(Resource, subject)
|> Repo.all()
end
def list_resources(%Auth.Subject{} = subject, opts \\ []) do
with :ok <- Auth.ensure_has_permissions(subject, Authorizer.manage_resources_permission()) do
Resource.Query.not_deleted()
|> Resource.Query.filter_features(subject.account)
|> Authorizer.for_subject(Resource, subject)
|> Repo.list(Resource.Query, opts)
end
end
def all_resources!(%Auth.Subject{} = subject, opts \\ []) do
{preload, _opts} = Keyword.pop(opts, :preload, [])
Resource.Query.not_deleted()
Resource.Query.all()
|> Resource.Query.filter_features(subject.account)
|> Authorizer.for_subject(Resource, subject)
|> Repo.all()
|> Repo.preload(preload)
end
def list_resources(%Auth.Subject{} = subject, opts \\ []) do
with :ok <- Auth.ensure_has_permissions(subject, Authorizer.manage_resources_permission()) do
Resource.Query.all()
|> Resource.Query.filter_features(subject.account)
|> Authorizer.for_subject(Resource, subject)
|> Repo.list(Resource.Query, opts)
end
end
def count_resources_for_gateway(%Gateways.Gateway{} = gateway, %Auth.Subject{} = subject) do
required_permissions =
{:one_of,
@@ -162,7 +112,7 @@ defmodule Domain.Resources do
with :ok <- Auth.ensure_has_permissions(subject, required_permissions) do
count =
Resource.Query.not_deleted()
Resource.Query.all()
|> Authorizer.for_subject(Resource, subject)
|> Resource.Query.by_gateway_group_id(gateway.group_id)
|> Repo.aggregate(:count)
@@ -181,7 +131,7 @@ defmodule Domain.Resources do
with :ok <- Auth.ensure_has_permissions(subject, required_permissions) do
resources =
Resource.Query.not_deleted()
Resource.Query.all()
|> Resource.Query.by_account_id(subject.account.id)
|> Resource.Query.by_gateway_group_id(gateway.group_id)
|> Repo.all()
@@ -195,7 +145,7 @@ defmodule Domain.Resources do
ids = resources |> Enum.map(& &1.id) |> Enum.uniq()
{:ok, peek} =
Resource.Query.not_deleted()
Resource.Query.all()
|> Resource.Query.by_id({:in, ids})
|> Authorizer.for_subject(Resource, subject)
|> Resource.Query.preload_few_actor_groups_for_each_resource(limit)
@@ -249,7 +199,7 @@ defmodule Domain.Resources do
def update_resource(%Resource{} = resource, attrs, %Auth.Subject{} = subject) do
with :ok <- Auth.ensure_has_permissions(subject, Authorizer.manage_resources_permission()) do
Resource.Query.not_deleted()
Resource.Query.all()
|> Resource.Query.by_id(resource.id)
|> Authorizer.for_subject(Resource, subject)
|> Repo.fetch_and_update(Resource.Query,
@@ -272,30 +222,6 @@ defmodule Domain.Resources do
end
end
# TODO: HARD-DELETE (shouldn't be needed)
def delete_connections_for(%Gateways.Group{} = gateway_group, %Auth.Subject{} = subject) do
Connection.Query.by_gateway_group_id(gateway_group.id)
|> delete_connections(subject)
end
# TODO: HARD-DELETE (shouldn't be needed)
def delete_connections_for(%Resource{} = resource, %Auth.Subject{} = subject) do
Connection.Query.by_resource_id(resource.id)
|> delete_connections(subject)
end
# TODO: HARD-DELETE (shouldn't be needed)
defp delete_connections(queryable, subject) do
with :ok <- Auth.ensure_has_permissions(subject, Authorizer.manage_resources_permission()) do
{count, nil} =
queryable
|> Authorizer.for_subject(Connection, subject)
|> Repo.delete_all()
{:ok, count}
end
end
def connected?(
resource_id,
%Gateways.Gateway{} = gateway

View File

@@ -8,7 +8,6 @@ defmodule Domain.Resources.Resource do
@type t :: %__MODULE__{
id: Ecto.UUID.t(),
persistent_id: Ecto.UUID.t() | nil,
address: String.t(),
address_description: String.t() | nil,
name: String.t(),
@@ -18,15 +17,11 @@ defmodule Domain.Resources.Resource do
account_id: Ecto.UUID.t(),
created_by: String.t(),
created_by_subject: map(),
replaced_by_resource_id: Ecto.UUID.t() | nil,
deleted_at: DateTime.t() | nil,
inserted_at: DateTime.t(),
updated_at: DateTime.t()
}
schema "resources" do
field :persistent_id, Ecto.UUID
field :address, :string
field :address_description, :string
field :name, :string
@@ -41,12 +36,9 @@ defmodule Domain.Resources.Resource do
belongs_to :account, Domain.Accounts.Account
has_many :connections, Domain.Resources.Connection, on_replace: :delete
# TODO: where doesn't work on join tables so soft-deleted records will be preloaded,
# ref https://github.com/firezone/firezone/issues/2162
has_many :gateway_groups, through: [:connections, :gateway_group]
# TODO: HARD-DELETE - Remove `where` after `deleted_at` is removed from the DB
has_many :policies, Domain.Policies.Policy, where: [deleted_at: nil]
has_many :policies, Domain.Policies.Policy
has_many :actor_groups, through: [:policies, :actor_group]
# Warning: do not do Repo.preload/2 for this field, it will not work intentionally,
@@ -56,11 +48,6 @@ defmodule Domain.Resources.Resource do
field :created_by, Ecto.Enum, values: ~w[identity actor system]a
field :created_by_subject, :map
belongs_to :replaced_by_resource, Domain.Resources.Resource
has_one :replaces_resource, Domain.Resources.Resource, foreign_key: :replaced_by_resource_id
# TODO: HARD-DELETE - Remove field after soft deletion is removed
field :deleted_at, :utc_datetime_usec
timestamps()
end
end

View File

@@ -31,7 +31,6 @@ defmodule Domain.Resources.Resource.Changeset do
|> cast(attrs, @fields)
|> changeset()
|> validate_required(@required_fields)
|> put_change(:persistent_id, Ecto.UUID.generate())
|> put_change(:account_id, account.id)
|> validate_address(account)
|> cast_assoc(:connections,
@@ -47,7 +46,6 @@ defmodule Domain.Resources.Resource.Changeset do
|> changeset()
|> validate_required(@required_fields)
|> validate_address(account)
|> put_change(:persistent_id, Ecto.UUID.generate())
|> put_change(:account_id, account.id)
|> put_subject_trail(:created_by, :system)
|> cast_assoc(:connections,
@@ -67,13 +65,6 @@ defmodule Domain.Resources.Resource.Changeset do
)
end
# TODO: HARD-DELETE - Remove after `deleted_at` column is removed from DB
def delete(%Resource{} = resource) do
resource
|> change()
|> put_default_value(:deleted_at, DateTime.utc_now())
end
defp validate_address(changeset, account) do
if has_errors?(changeset, :type) do
changeset

View File

@@ -5,12 +5,6 @@ defmodule Domain.Resources.Resource.Query do
from(resources in Domain.Resources.Resource, as: :resources)
end
# TODO: HARD-DELETE - Remove after `deleted_at` column is removed from DB
def not_deleted do
all()
|> where([resources: resources], is_nil(resources.deleted_at))
end
def filter_features(queryable, %Domain.Accounts.Account{} = account) do
if Domain.Accounts.internet_resource_enabled?(account) do
queryable
@@ -31,14 +25,6 @@ defmodule Domain.Resources.Resource.Query do
where(queryable, [resources: resources], resources.type == ^type)
end
def by_id_or_persistent_id(queryable, id) do
where(queryable, [resources: resources], resources.id == ^id)
|> or_where(
[resources: resources],
resources.persistent_id == ^id and is_nil(resources.replaced_by_resource_id)
)
end
def by_account_id(queryable, account_id) do
where(queryable, [resources: resources], resources.account_id == ^account_id)
end
@@ -85,7 +71,7 @@ defmodule Domain.Resources.Resource.Query do
|> limit(^limit)
actor_groups_subquery =
Domain.Actors.Group.Query.not_deleted()
Domain.Actors.Group.Query.all()
|> where([groups: groups], groups.id in subquery(policies_subquery))
join(
@@ -135,7 +121,7 @@ defmodule Domain.Resources.Resource.Query do
|> join(
:inner,
[connections: connections],
gateway_group in ^Domain.Gateways.Group.Query.not_deleted(),
gateway_group in ^Domain.Gateways.Group.Query.all(),
on: gateway_group.id == connections.gateway_group_id,
as: ^binding
)
@@ -167,11 +153,6 @@ defmodule Domain.Resources.Resource.Query do
values: [],
fun: &filter_by_gateway_group_id/2
},
%Domain.Repo.Filter{
name: :deleted?,
type: :boolean,
fun: &filter_deleted/1
},
%Domain.Repo.Filter{
name: :type,
type: {:list, :string},
@@ -193,11 +174,6 @@ defmodule Domain.Resources.Resource.Query do
dynamic([connections: connections], connections.gateway_group_id == ^gateway_group_id)}
end
# TODO: HARD-DELETE - Remove after `deleted_at` column is removed from DB
def filter_deleted(queryable) do
{queryable, dynamic([resources: resources], not is_nil(resources.deleted_at))}
end
def filter_by_type(queryable, {:not_in, types}) do
{queryable, dynamic([resources: resources], resources.type not in ^types)}
end

View File

@@ -21,7 +21,7 @@ defmodule Domain.Tokens do
end
def fetch_token_by_id(id) do
Token.Query.not_deleted()
Token.Query.all()
|> Token.Query.not_expired()
|> Token.Query.by_id(id)
|> Repo.fetch(Token.Query, [])
@@ -48,7 +48,7 @@ defmodule Domain.Tokens do
end
def all_active_browser_session_tokens! do
Token.Query.not_deleted()
Token.Query.all()
|> Token.Query.expires_in(15, :minute)
|> Token.Query.by_type(:browser)
|> Repo.all()
@@ -56,7 +56,7 @@ defmodule Domain.Tokens do
def list_subject_tokens(%Auth.Subject{} = subject, opts \\ []) do
with :ok <- Auth.ensure_has_permissions(subject, Authorizer.manage_own_tokens_permission()) do
Token.Query.not_deleted()
Token.Query.all()
|> Token.Query.by_actor_id(subject.actor.id)
|> list_tokens(subject, opts)
end
@@ -64,7 +64,7 @@ defmodule Domain.Tokens do
def list_tokens_for(%Actors.Actor{} = actor, %Auth.Subject{} = subject, opts \\ []) do
with :ok <- Auth.ensure_has_permissions(subject, Authorizer.manage_tokens_permission()) do
Token.Query.not_deleted()
Token.Query.all()
|> Token.Query.by_actor_id(actor.id)
|> list_tokens(subject, opts)
end
@@ -93,7 +93,7 @@ defmodule Domain.Tokens do
(hardcoded token duration for Okta and Google Workspace).
"""
def update_token(%Token{} = token, attrs) do
Token.Query.not_deleted()
Token.Query.all()
|> Token.Query.not_expired()
|> Token.Query.by_id(token.id)
|> Repo.fetch_and_update(Token.Query, with: &Token.Changeset.update(&1, attrs))
@@ -136,7 +136,7 @@ defmodule Domain.Tokens do
end
defp fetch_token_for_use(id, account_id, context_type) do
Token.Query.not_deleted()
Token.Query.all()
|> Token.Query.not_expired()
|> Token.Query.by_id(id)
|> Token.Query.by_account_id(account_id)
@@ -270,7 +270,7 @@ defmodule Domain.Tokens do
def delete_all_tokens_by_type_and_assoc(:email, %Auth.Identity{} = identity) do
{num_deleted, _} =
Token.Query.not_deleted()
Token.Query.all()
|> Token.Query.by_type(:email)
|> Token.Query.by_account_id(identity.account_id)
|> Token.Query.by_identity_id(identity.id)
@@ -288,118 +288,6 @@ defmodule Domain.Tokens do
{:ok, num_deleted}
end
# TODO: HARD-DELETE - Remove after `deleted_at` is remove from DB
def soft_delete_token(%Token{} = token, %Auth.Subject{} = subject) do
required_permissions =
{:one_of,
[
Authorizer.manage_tokens_permission(),
Authorizer.manage_own_tokens_permission()
]}
with :ok <- Auth.ensure_has_permissions(subject, required_permissions) do
Token.Query.not_deleted()
|> Token.Query.by_id(token.id)
|> Authorizer.for_subject(subject)
|> soft_delete_tokens()
|> case do
{:ok, [token]} -> {:ok, token}
{:ok, []} -> {:error, :not_found}
end
end
end
# TODO: HARD-DELETE - Remove after `deleted_at` is remove from DB
def soft_delete_token_for(%Auth.Subject{} = subject) do
Token.Query.not_deleted()
|> Token.Query.by_id(subject.token_id)
|> Authorizer.for_subject(subject)
|> soft_delete_tokens()
end
# TODO: HARD-DELETE - Remove after `deleted_at` is remove from DB
def soft_delete_tokens_for(%Auth.Identity{} = identity) do
Token.Query.not_deleted()
|> Token.Query.by_identity_id(identity.id)
|> soft_delete_tokens()
end
# TODO: HARD-DELETE - Remove after `deleted_at` is remove from DB
def soft_delete_tokens_for(%Actors.Actor{} = actor, %Auth.Subject{} = subject) do
with :ok <- Auth.ensure_has_permissions(subject, Authorizer.manage_tokens_permission()) do
Token.Query.not_deleted()
|> Token.Query.by_actor_id(actor.id)
|> Authorizer.for_subject(subject)
|> soft_delete_tokens()
end
end
# TODO: HARD-DELETE - Remove after `deleted_at` is remove from DB
def soft_delete_tokens_for(%Auth.Identity{} = identity, %Auth.Subject{} = subject) do
with :ok <- Auth.ensure_has_permissions(subject, Authorizer.manage_tokens_permission()) do
Token.Query.not_deleted()
|> Token.Query.by_identity_id(identity.id)
|> Authorizer.for_subject(subject)
|> soft_delete_tokens()
end
end
# TODO: HARD-DELETE - Remove after `deleted_at` is remove from DB
def soft_delete_tokens_for(%Auth.Provider{} = provider, %Auth.Subject{} = subject) do
with :ok <- Auth.ensure_has_permissions(subject, Authorizer.manage_tokens_permission()) do
Token.Query.not_deleted()
|> Token.Query.by_provider_id(provider.id)
|> Authorizer.for_subject(subject)
|> soft_delete_tokens()
end
end
# TODO: HARD-DELETE - Remove after `deleted_at` is remove from DB
def soft_delete_tokens_for(%Relays.Group{} = group, %Auth.Subject{} = subject) do
with :ok <- Auth.ensure_has_permissions(subject, Authorizer.manage_tokens_permission()) do
Token.Query.not_deleted()
|> Token.Query.by_relay_group_id(group.id)
|> Authorizer.for_subject(subject)
|> soft_delete_tokens()
end
end
# TODO: HARD-DELETE - Remove after `deleted_at` is remove from DB
def soft_delete_tokens_for(%Gateways.Group{} = group, %Auth.Subject{} = subject) do
with :ok <- Auth.ensure_has_permissions(subject, Authorizer.manage_tokens_permission()) do
Token.Query.not_deleted()
|> Token.Query.by_gateway_group_id(group.id)
|> Authorizer.for_subject(subject)
|> soft_delete_tokens()
end
end
# TODO: HARD-DELETE - Remove after `deleted_at` is remove from DB
def soft_delete_all_tokens_by_type_and_assoc(:email, %Auth.Identity{} = identity) do
Token.Query.not_deleted()
|> Token.Query.by_type(:email)
|> Token.Query.by_account_id(identity.account_id)
|> Token.Query.by_identity_id(identity.id)
|> soft_delete_tokens()
end
# TODO: HARD-DELETE - Remove after `deleted_at` is remove from DB
def soft_delete_expired_tokens do
Token.Query.not_deleted()
|> Token.Query.expired()
|> soft_delete_tokens()
end
# TODO: HARD-DELETE - Remove after `deleted_at` is remove from DB
defp soft_delete_tokens(queryable) do
{_count, tokens} =
queryable
|> Token.Query.delete()
|> Repo.update_all([])
{:ok, tokens}
end
defp fetch_config! do
Domain.Config.fetch_env!(:domain, __MODULE__)
end

View File

@@ -50,8 +50,6 @@ defmodule Domain.Tokens.Token do
field :expires_at, :utc_datetime_usec
# TODO: HARD-DELETE - Remove field after soft deletion is removed
field :deleted_at, :utc_datetime_usec
timestamps()
end
end

View File

@@ -124,11 +124,4 @@ defmodule Domain.Tokens.Token.Changeset do
|> put_change(:last_seen_at, DateTime.utc_now())
|> validate_required(~w[last_seen_user_agent last_seen_remote_ip]a)
end
# TODO: HARD-DELETE - Remove after `deleted_at` is removed from DB
def delete(%Token{} = token) do
token
|> change()
|> put_default_value(:deleted_at, DateTime.utc_now())
end
end

View File

@@ -5,12 +5,6 @@ defmodule Domain.Tokens.Token.Query do
from(tokens in Domain.Tokens.Token, as: :tokens)
end
# TODO: HARD-DELETE - Remove after `deleted_at` column is removed from DB
def not_deleted do
all()
|> where([tokens: tokens], is_nil(tokens.deleted_at))
end
def not_expired(queryable) do
where(
queryable,
@@ -79,17 +73,6 @@ defmodule Domain.Tokens.Token.Query do
where(queryable, [tokens: tokens], tokens.gateway_group_id == ^gateway_group_id)
end
# TODO: HARD-DELETE - Remove after `deleted_at` column is removed from DB
def delete(queryable) do
queryable
|> Ecto.Query.select([tokens: tokens], tokens)
|> Ecto.Query.update([tokens: tokens],
set: [
deleted_at: fragment("COALESCE(?, timezone('UTC', NOW()))", tokens.deleted_at)
]
)
end
def with_joined_account(queryable) do
with_named_binding(queryable, :account, fn queryable, binding ->
join(queryable, :inner, [tokens: tokens], account in assoc(tokens, ^binding), as: ^binding)

View File

@@ -0,0 +1,66 @@
defmodule Domain.Repo.Migrations.RemoveSoftDeletedData do
use Ecto.Migration
def up do
# Delete all soft-deleted records from tables that have deleted_at column
# This is a data cleanup migration before removing the soft delete functionality
# The order of execution was chosen to try and minimize ON DELETE CASCADE deletions
execute("""
DELETE FROM tokens WHERE deleted_at IS NOT NULL
""")
execute("""
DELETE FROM resources WHERE deleted_at IS NOT NULL
""")
execute("""
DELETE FROM auth_identities WHERE deleted_at IS NOT NULL
""")
execute("""
DELETE FROM clients WHERE deleted_at IS NOT NULL
""")
execute("""
DELETE FROM gateways WHERE deleted_at IS NOT NULL
""")
execute("""
DELETE FROM actors WHERE deleted_at IS NOT NULL
""")
execute("""
DELETE FROM actor_groups WHERE deleted_at IS NOT NULL
""")
execute("""
DELETE FROM auth_providers WHERE deleted_at IS NOT NULL
""")
execute("""
DELETE FROM gateway_groups WHERE deleted_at IS NOT NULL
""")
execute("""
DELETE FROM policies WHERE deleted_at IS NOT NULL
""")
execute("""
DELETE FROM relays WHERE deleted_at IS NOT NULL
""")
execute("""
DELETE FROM relay_groups WHERE deleted_at IS NOT NULL
""")
execute("""
DELETE FROM accounts WHERE deleted_at IS NOT NULL
""")
end
def down do
# no-op Deleted data cannot be restored
end
end

View File

@@ -0,0 +1,31 @@
defmodule Domain.Repo.Migrations.AddDefaultPersistentId do
use Ecto.Migration
def up do
# Add default UUID generation for resources.persistent_id
execute("""
ALTER TABLE resources
ALTER COLUMN persistent_id SET DEFAULT gen_random_uuid()
""")
# Add default UUID generation for policies.persistent_id
execute("""
ALTER TABLE policies
ALTER COLUMN persistent_id SET DEFAULT gen_random_uuid()
""")
end
def down do
# Remove default for resources.persistent_id
execute("""
ALTER TABLE resources
ALTER COLUMN persistent_id DROP DEFAULT
""")
# Remove default for policies.persistent_id
execute("""
ALTER TABLE policies
ALTER COLUMN persistent_id DROP DEFAULT
""")
end
end

View File

@@ -0,0 +1,169 @@
defmodule Domain.Repo.Migrations.RecreateUniqueIndexesWithoutDeletedAt do
use Ecto.Migration
@disable_ddl_transaction true
def up do
# Clients
drop_if_exists(
index(:clients, [:account_id, :actor_id, :external_id],
name: :clients_account_id_actor_id_external_id_index,
concurrently: true
)
)
create_if_not_exists(
unique_index(:clients, [:account_id, :actor_id, :external_id],
name: :clients_account_id_actor_id_external_id_index,
concurrently: true
)
)
# Gateways
drop_if_exists(
index(:gateways, [:account_id, :group_id, :external_id],
name: :gateways_account_id_group_id_external_id_index,
concurrently: true
)
)
create_if_not_exists(
unique_index(:gateways, [:account_id, :group_id, :external_id],
name: :gateways_account_id_group_id_external_id_index,
concurrently: true
)
)
# Global Relays (requires raw SQL due to COALESCE)
execute("DROP INDEX CONCURRENTLY IF EXISTS global_relays_unique_address_index")
execute("""
CREATE UNIQUE INDEX CONCURRENTLY IF NOT EXISTS global_relays_unique_address_index
ON relays (COALESCE(ipv4, ipv6), port)
WHERE account_id IS NULL
""")
# Account Relays (requires raw SQL due to COALESCE)
execute("DROP INDEX CONCURRENTLY IF EXISTS relays_unique_address_index")
execute("""
CREATE UNIQUE INDEX CONCURRENTLY IF NOT EXISTS relays_unique_address_index
ON relays (account_id, COALESCE(ipv4, ipv6), port)
WHERE account_id IS NOT NULL
""")
# Auth Identities - provider_identifier unique index
drop_if_exists(
index(:auth_identities, [:account_id, :provider_id, :provider_identifier],
name: :auth_identities_account_id_provider_id_provider_identifier_idx,
concurrently: true
)
)
create_if_not_exists(
unique_index(:auth_identities, [:account_id, :provider_id, :provider_identifier],
name: :auth_identities_account_id_provider_id_provider_identifier_idx,
concurrently: true
)
)
# Auth Identities - email unique index
drop_if_exists(
index(:auth_identities, [:account_id, :provider_id, :email, :provider_identifier],
name: :auth_identities_acct_id_provider_id_email_prov_ident_unique_idx,
concurrently: true
)
)
create_if_not_exists(
unique_index(:auth_identities, [:account_id, :provider_id, :email, :provider_identifier],
name: :auth_identities_acct_id_provider_id_email_prov_ident_unique_idx,
concurrently: true
)
)
end
def down do
# Clients
drop_if_exists(
index(:clients, [:account_id, :actor_id, :external_id],
name: :clients_account_id_actor_id_external_id_index,
concurrently: true
)
)
create_if_not_exists(
unique_index(:clients, [:account_id, :actor_id, :external_id],
name: :clients_account_id_actor_id_external_id_index,
where: "deleted_at IS NULL",
concurrently: true
)
)
# Gateways
drop_if_exists(
index(:gateways, [:account_id, :group_id, :external_id],
name: :gateways_account_id_group_id_external_id_index,
concurrently: true
)
)
create_if_not_exists(
unique_index(:gateways, [:account_id, :group_id, :external_id],
name: :gateways_account_id_group_id_external_id_index,
where: "deleted_at IS NULL",
concurrently: true
)
)
# Global Relays (requires raw SQL due to COALESCE)
execute("DROP INDEX IF EXISTS global_relays_unique_address_index")
execute("""
CREATE UNIQUE INDEX IF NOT EXISTS global_relays_unique_address_index
ON relays (COALESCE(ipv4, ipv6), port)
WHERE deleted_at IS NULL AND account_id IS NULL
""")
# Account Relays (requires raw SQL due to COALESCE)
execute("DROP INDEX IF EXISTS relays_unique_address_index")
execute("""
CREATE UNIQUE INDEX IF NOT EXISTS relays_unique_address_index
ON relays (account_id, COALESCE(ipv4, ipv6), port)
WHERE deleted_at IS NULL AND account_id IS NOT NULL
""")
# Auth Identities - provider_identifier unique index
drop_if_exists(
index(:auth_identities, [:account_id, :provider_id, :provider_identifier],
name: :auth_identities_account_id_provider_id_provider_identifier_idx,
concurrently: true
)
)
create_if_not_exists(
unique_index(:auth_identities, [:account_id, :provider_id, :provider_identifier],
name: :auth_identities_account_id_provider_id_provider_identifier_idx,
where: "deleted_at IS NULL",
concurrently: true
)
)
# Auth Identities - email unique index
drop_if_exists(
index(:auth_identities, [:account_id, :provider_id, :email, :provider_identifier],
name: :auth_identities_acct_id_provider_id_email_prov_ident_unique_idx,
concurrently: true
)
)
create_if_not_exists(
unique_index(:auth_identities, [:account_id, :provider_id, :email, :provider_identifier],
name: :auth_identities_acct_id_provider_id_email_prov_ident_unique_idx,
where: "deleted_at IS NULL",
concurrently: true
)
)
end
end

View File

@@ -35,7 +35,7 @@ defmodule Domain.AccountsTest do
end
test "does not return deleted accounts" do
account = Fixtures.Accounts.create_account() |> Fixtures.Accounts.delete_account()
{:ok, account} = Fixtures.Accounts.create_account() |> Fixtures.Accounts.delete_account()
accounts = all_accounts_by_ids!([account.id])
assert length(accounts) == 0
end
@@ -783,11 +783,6 @@ defmodule Domain.AccountsTest do
assert account_active?(account) == true
end
test "returns false when account is deleted" do
account = Fixtures.Accounts.create_account() |> Fixtures.Accounts.delete_account()
assert account_active?(account) == false
end
test "returns false when account is disabled" do
account = Fixtures.Accounts.create_account() |> Fixtures.Accounts.disable_account()
assert account_active?(account) == false

View File

@@ -84,19 +84,6 @@ defmodule Domain.ActorsTest do
assert fetch_group_by_id(group.id, subject) == {:error, :not_found}
end
# TODO: HARD-DELETE - This test is no longer relevant
# test "returns deleted groups", %{
# account: account,
# subject: subject
# } do
# group =
# Fixtures.Actors.create_group(account: account)
# |> Fixtures.Actors.delete_group()
# 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
group = Fixtures.Actors.create_group(account: account)
assert {:ok, fetched_group} = fetch_group_by_id(group.id, subject)
@@ -156,17 +143,6 @@ defmodule Domain.ActorsTest do
assert {:ok, [], _metadata} = list_groups(subject)
end
# TODO: HARD-DELETE - Is this test needed any more?
test "does not list deleted groups", %{
account: account,
subject: subject
} do
Fixtures.Actors.create_group(account: account)
|> Fixtures.Actors.delete_group()
assert {:ok, [], _metadata} = list_groups(subject)
end
test "returns all groups", %{
account: account,
subject: subject
@@ -218,17 +194,6 @@ defmodule Domain.ActorsTest do
assert {:ok, [], _metadata} = list_editable_groups(subject)
end
# TODO: HARD-DELETE - Is this test needed any more?
test "does not list deleted groups", %{
account: account,
subject: subject
} do
Fixtures.Actors.create_group(account: account)
|> Fixtures.Actors.delete_group()
assert {:ok, [], _metadata} = list_editable_groups(subject)
end
test "returns all editable groups", %{
account: account,
subject: subject
@@ -291,16 +256,6 @@ defmodule Domain.ActorsTest do
assert {:ok, [], _metadata} = list_groups_for(actor, subject)
end
# TODO: HARD-DELETE - Is this test needed any more?
test "does not list deleted groups", %{account: account, actor: actor, subject: subject} do
group = Fixtures.Actors.create_group(account: account)
Fixtures.Actors.create_membership(account: account, actor: actor, group: group)
Fixtures.Actors.delete_group(group)
assert {:ok, [], _metadata} = list_groups_for(actor, subject)
end
test "returns all groups for actor", %{
account: account,
actor: actor,
@@ -894,7 +849,7 @@ defmodule Domain.ActorsTest do
sync_provider_groups(provider, attrs_list)
assert Repo.aggregate(Actors.Group, :count) == 5
assert Repo.aggregate(Actors.Group.Query.not_deleted(), :count) == 5
assert Repo.aggregate(Actors.Group.Query.all(), :count) == 5
end
test "ignores groups that are not synced from the provider", %{
@@ -927,53 +882,6 @@ defmodule Domain.ActorsTest do
group_ids_by_provider_identifier: %{}
}}
end
# TODO: HARD-DELETE - This test is no longer relevant
# test "ignores synced groups that are soft deleted", %{
# account: account,
# provider: provider
# } do
# deleted_group =
# Fixtures.Actors.create_group(
# account: account,
# provider: provider,
# provider_identifier: "G:GROUP_ID1",
# name: "ALREADY_DELETED"
# )
# Domain.Actors.Group.Query.not_deleted()
# |> Domain.Actors.Group.Query.by_account_id(account.id)
# |> Domain.Actors.Group.Query.by_provider_id(provider.id)
# |> Domain.Actors.Group.Query.by_provider_identifier(
# {:in, [deleted_group.provider_identifier]}
# )
# |> Domain.Actors.delete_groups()
# group2 =
# Fixtures.Actors.create_group(
# account: account,
# provider: provider,
# provider_identifier: "G:GROUP_ID2",
# name: "TO_BE_UPDATED"
# )
# attrs_list = [
# %{"name" => "Group:Infrastructure", "provider_identifier" => "G:GROUP_ID2"},
# %{"name" => "Group:Security", "provider_identifier" => "G:GROUP_ID3"},
# %{"name" => "Group:Finance", "provider_identifier" => "G:GROUP_ID4"}
# ]
# provider_identifiers = Enum.map(attrs_list, & &1["provider_identifier"])
# assert {:ok, sync_data} = sync_provider_groups(provider, attrs_list)
# assert Enum.sort(Enum.map(sync_data.groups, & &1.name)) ==
# Enum.sort([deleted_group.name, group2.name])
# assert sync_data.deleted == []
# assert sync_data.plan == {provider_identifiers, []}
# end
end
describe "sync_provider_memberships/2" do
@@ -1780,7 +1688,9 @@ defmodule Domain.ActorsTest do
assert membership.actor_id == actor.id
end
# TODO: HARD-DELETE - Is this test needed any more?
# This test isn't strictly necessary when using ON DELETE CASCADE, however
# leaving this test allows us to know if there is ever a change in the
# foreign key effects
test "removes memberships when managed group is deleted", %{
account: account,
actor: actor,
@@ -1999,7 +1909,8 @@ defmodule Domain.ActorsTest do
assert Repo.aggregate(Actors.Membership, :count) == 0
end
# TODO: HARD-DELETE - Should this test be put in policies?
# Leaving this test here for now. May want to consider moving this test
# to the policy tests rather than the actor group tests.
test "cascade deletes policies that use this group", %{
account: account,
subject: subject
@@ -2053,22 +1964,6 @@ defmodule Domain.ActorsTest do
subject: subject
}
end
# TODO: HARD-DELETE - Is this test needed any more?
test "delete groups when provider is deleted", %{
account: account,
provider: provider,
subject: subject
} do
group1 = Fixtures.Actors.create_group(account: account, provider: provider)
group2 = Fixtures.Actors.create_group(account: account, provider: provider)
assert {:ok, _provider} = Auth.delete_provider(provider, subject)
refute Repo.get(Domain.Auth.Provider, provider.id)
refute Repo.get(Actors.Group, group1.id)
refute Repo.get(Actors.Group, group2.id)
end
end
describe "group_synced?/1" do
@@ -2118,23 +2013,6 @@ defmodule Domain.ActorsTest do
end
end
# TODO: HARD-DELETE - Remove after soft delete functionality is gone
describe "group_soft_deleted?/1" do
test "returns true for soft deleted groups" do
account = Fixtures.Accounts.create_account()
group =
Fixtures.Actors.create_group(account: account) |> Fixtures.Actors.soft_delete_group()
assert group_soft_deleted?(group) == true
end
test "returns false for manually created groups" do
group = Fixtures.Actors.create_group()
assert group_soft_deleted?(group) == false
end
end
describe "count_users_for_account/1" do
test "returns 0 when actors are in another account", %{} do
account = Fixtures.Accounts.create_account()
@@ -2237,77 +2115,6 @@ defmodule Domain.ActorsTest do
end
end
describe "count_synced_actors_for_provider/1" do
test "returns 0 when there are no actors" do
account = Fixtures.Accounts.create_account()
provider = Fixtures.Auth.create_userpass_provider(account: account)
assert count_synced_actors_for_provider(provider) == 0
end
test "returns 0 when there are no synced actors" do
account = Fixtures.Accounts.create_account()
provider = Fixtures.Auth.create_userpass_provider(account: account)
Fixtures.Actors.create_actor(type: :account_admin_user, account: account)
assert count_synced_actors_for_provider(provider) == 0
end
test "returns count of synced actors owned only by the given provider" do
account = Fixtures.Accounts.create_account()
{provider, _bypass} =
Fixtures.Auth.start_and_create_openid_connect_provider(account: account)
actor1 =
Fixtures.Actors.create_actor(
type: :account_admin_user,
account: account
)
Fixtures.Auth.create_identity(account: account, actor: actor1, provider: provider)
|> Fixtures.Auth.delete_identity()
actor2 =
Fixtures.Actors.create_actor(
type: :account_admin_user,
account: account
)
Fixtures.Auth.create_identity(account: account, actor: actor2, provider: provider)
|> Fixtures.Auth.delete_identity()
actor3 =
Fixtures.Actors.create_actor(
type: :account_admin_user,
account: account
)
Fixtures.Auth.create_identity(account: account, actor: actor3)
|> Fixtures.Auth.delete_identity()
actor4 =
Fixtures.Actors.create_actor(
type: :account_admin_user,
account: account
)
Fixtures.Auth.create_identity(account: account, actor: actor4, provider: provider)
|> Fixtures.Auth.delete_identity()
Fixtures.Auth.create_identity(account: account, actor: actor4)
|> Fixtures.Auth.delete_identity()
actor5 =
Fixtures.Actors.create_actor(
type: :account_admin_user,
account: account
)
Fixtures.Auth.create_identity(account: account, actor: actor5, provider: provider)
assert count_synced_actors_for_provider(provider) == 2
end
end
describe "fetch_actor_by_id/3" do
test "returns error when actor is not found" do
subject = Fixtures.Auth.create_subject()
@@ -2538,7 +2345,6 @@ defmodule Domain.ActorsTest do
assert actor.name == attrs.name
assert actor.type == attrs.type
assert is_nil(actor.disabled_at)
assert is_nil(actor.deleted_at)
end
test "trims whitespace when creating an actor", %{
@@ -2551,7 +2357,6 @@ defmodule Domain.ActorsTest do
assert actor.name == actor_name
assert is_nil(actor.disabled_at)
assert is_nil(actor.deleted_at)
end
end
@@ -2579,7 +2384,6 @@ defmodule Domain.ActorsTest do
assert actor.name == attrs.name
assert actor.type == attrs.type
assert is_nil(actor.disabled_at)
assert is_nil(actor.deleted_at)
end
test "trims whitespace when creating an actor", %{
@@ -2593,7 +2397,6 @@ defmodule Domain.ActorsTest do
assert actor.name == actor_name
assert is_nil(actor.disabled_at)
assert is_nil(actor.deleted_at)
end
test "returns error when seats limit is exceeded (admins)", %{
@@ -3075,7 +2878,6 @@ defmodule Domain.ActorsTest do
assert membership.actor_id == new_actor.id
end
# TODO: HARD-DELETE - Move this test to Tokens since it has the FK constraint
test "deletes token", %{
account: account,
actor: actor,
@@ -3087,7 +2889,6 @@ defmodule Domain.ActorsTest do
refute Repo.get(Domain.Tokens.Token, subject.token_id)
end
# TODO: HARD-DELETE - Move this test to AuthIdentities since it has the FK constraint
test "deletes actor identities", %{
account: account,
subject: subject
@@ -3099,7 +2900,6 @@ defmodule Domain.ActorsTest do
refute Repo.get(Domain.Auth.Identity, identity.id)
end
# TODO: HARD-DELETE - Move this test to Clients since it has the FK constraint
test "deletes actor clients", %{
account: account,
subject: subject
@@ -3109,7 +2909,7 @@ defmodule Domain.ActorsTest do
assert {:ok, _actor} = delete_actor(actor_to_delete, subject)
assert Repo.aggregate(Domain.Clients.Client.Query.not_deleted(), :count) == 0
assert Repo.aggregate(Domain.Clients.Client.Query.all(), :count) == 0
end
test "deletes actor memberships", %{
@@ -3168,68 +2968,6 @@ defmodule Domain.ActorsTest do
refute Repo.get(Domain.Actors.Actor, service_account_actor.id)
end
# TODO: HARD-DELETE - Need to figure out if we care about this case
# test "returns error when trying to delete the last admin actor using a race condition" do
# for _ <- 0..50 do
# test_pid = self()
# Task.async(fn ->
# allow_child_sandbox_access(test_pid)
# Domain.Config.put_env_override(:outbound_email_adapter_configured?, true)
# account = Fixtures.Accounts.create_account()
# provider = Fixtures.Auth.create_email_provider(account: account)
# actor_one =
# Fixtures.Actors.create_actor(
# type: :account_admin_user,
# account: account,
# provider: provider
# )
# actor_two =
# Fixtures.Actors.create_actor(
# type: :account_admin_user,
# account: account,
# provider: provider
# )
# identity_one =
# Fixtures.Auth.create_identity(
# account: account,
# actor: actor_one,
# provider: provider
# )
# identity_two =
# Fixtures.Auth.create_identity(
# account: account,
# actor: actor_two,
# provider: provider
# )
# subject_one = Fixtures.Auth.create_subject(identity: identity_one)
# subject_two = Fixtures.Auth.create_subject(identity: identity_two)
# for {actor, subject} <- [{actor_two, subject_one}, {actor_one, subject_two}] do
# Task.async(fn ->
# allow_child_sandbox_access(test_pid)
# delete_actor(actor, subject)
# end)
# end
# |> Task.await_many()
# queryable =
# Actors.Actor.Query.not_deleted()
# |> Actors.Actor.Query.by_account_id(account.id)
# assert Repo.aggregate(queryable, :count) == 1
# end)
# end
# |> Task.await_many()
# end
test "does not allow to delete an actor twice", %{
account: account,
subject: subject
@@ -3271,78 +3009,6 @@ defmodule Domain.ActorsTest do
end
end
describe "delete_stale_synced_actors_for_provider/2" do
test "deletes actors synced with only the given provider" do
account = Fixtures.Accounts.create_account()
subject = Fixtures.Auth.create_subject(account: account)
{provider, _bypass} =
Fixtures.Auth.start_and_create_openid_connect_provider(account: account)
actor1 =
Fixtures.Actors.create_actor(
type: :account_admin_user,
account: account
)
Fixtures.Auth.create_identity(account: account, actor: actor1, provider: provider)
|> Fixtures.Auth.delete_identity()
actor2 =
Fixtures.Actors.create_actor(
type: :account_admin_user,
account: account
)
Fixtures.Auth.create_identity(account: account, actor: actor2, provider: provider)
|> Fixtures.Auth.delete_identity()
actor3 =
Fixtures.Actors.create_actor(
type: :account_admin_user,
account: account
)
Fixtures.Auth.create_identity(account: account, actor: actor3)
|> Fixtures.Auth.delete_identity()
actor4 =
Fixtures.Actors.create_actor(
type: :account_admin_user,
account: account
)
Fixtures.Auth.create_identity(account: account, actor: actor4, provider: provider)
|> Fixtures.Auth.delete_identity()
Fixtures.Auth.create_identity(account: account, actor: actor4)
|> Fixtures.Auth.delete_identity()
assert delete_stale_synced_actors_for_provider(provider, subject) == :ok
not_deleted_actors = Repo.all(Actors.Actor.Query.not_deleted())
not_deleted_actor_ids = not_deleted_actors |> Enum.map(& &1.id) |> Enum.sort()
assert not_deleted_actor_ids == Enum.sort([actor4.id, actor3.id, subject.actor.id])
end
test "returns error when subject cannot delete actors" do
account = Fixtures.Accounts.create_account()
{provider, _bypass} =
Fixtures.Auth.start_and_create_openid_connect_provider(account: account)
subject =
Fixtures.Auth.create_subject(account: account)
|> Fixtures.Auth.remove_permissions()
assert delete_stale_synced_actors_for_provider(provider, subject) ==
{:error,
{:unauthorized,
reason: :missing_permissions,
missing_permissions: [Actors.Authorizer.manage_actors_permission()]}}
end
end
describe "actor_synced?/1" do
test "returns true when actor is synced" do
actor = Fixtures.Actors.create_actor()
@@ -3359,23 +3025,6 @@ defmodule Domain.ActorsTest do
end
end
# TODO: HARD-DELETE - Remove after soft deletion functionality is removed
describe "actor_deleted?/1" do
test "returns true when actor is soft deleted" do
actor =
Fixtures.Actors.create_actor()
|> Fixtures.Actors.soft_delete()
assert actor_deleted?(actor) == true
end
test "returns false when actor is not deleted" do
actor = Fixtures.Actors.create_actor()
assert actor_deleted?(actor) == false
end
end
describe "actor_disabled?/1" do
test "returns true when actor is disabled" do
actor =
@@ -3662,12 +3311,4 @@ defmodule Domain.ActorsTest do
{:ok, %{deleted_memberships: 0}}
end
end
# TODO: HARD-DELETE - This may not be needed anymore
# defp allow_child_sandbox_access(parent_pid) do
# Ecto.Adapters.SQL.Sandbox.allow(Repo, parent_pid, self())
# # Allow is async call we need to break current process execution
# # to allow sandbox to be enabled
# :timer.sleep(10)
# end
end

View File

@@ -680,167 +680,6 @@ defmodule Domain.Auth.Adapters.GoogleWorkspace.Jobs.SyncDirectoryTest do
refute Repo.reload(deleted_identity_token)
end
test "resurrects deleted identities that reappear on the next sync", %{
account: account,
provider: provider
} do
actor = Fixtures.Actors.create_actor(account: account)
provider_identifier = "USER_ID1"
identity =
Fixtures.Auth.create_identity(
account: account,
provider: provider,
actor: actor,
provider_identifier: provider_identifier
)
inserted_at = identity.inserted_at
id = identity.id
# Soft delete the identity
Repo.update_all(Domain.Auth.Identity, set: [deleted_at: DateTime.utc_now()])
assert Domain.Auth.all_identities_for(actor) == []
# Simulate a sync
bypass = Bypass.open()
users = [
%{
"agreedToTerms" => true,
"archived" => false,
"creationTime" => "2023-06-10T17:32:06.000Z",
"id" => "USER_ID1",
"kind" => "admin#directory#user",
"lastLoginTime" => "2023-06-26T13:53:30.000Z",
"name" => %{
"familyName" => "Manifold",
"fullName" => "Brian Manifold",
"givenName" => "Brian"
},
"orgUnitPath" => "/Engineering",
"organizations" => [],
"phones" => [],
"primaryEmail" => "b@firez.xxx"
}
]
GoogleWorkspaceDirectory.override_endpoint_url("http://localhost:#{bypass.port}/")
GoogleWorkspaceDirectory.mock_groups_list_endpoint(
bypass,
200,
JSON.encode!(%{"groups" => []})
)
GoogleWorkspaceDirectory.mock_organization_units_list_endpoint(
bypass,
200,
JSON.encode!(%{"organizationUnits" => []})
)
GoogleWorkspaceDirectory.mock_users_list_endpoint(
bypass,
200,
JSON.encode!(%{"users" => users})
)
GoogleWorkspaceDirectory.mock_token_endpoint(bypass)
{:ok, pid} = Task.Supervisor.start_link()
assert execute(%{task_supervisor: pid}) == :ok
# Assert that the identity has been resurrected
assert resurrected_identity = Repo.get(Domain.Auth.Identity, id)
assert resurrected_identity.inserted_at == inserted_at
assert resurrected_identity.id == id
assert resurrected_identity.deleted_at == nil
assert Domain.Auth.all_identities_for(actor) == [resurrected_identity]
end
test "resurrects deleted groups that reappear on the next sync", %{
account: account,
provider: provider
} do
actor_group =
Fixtures.Actors.create_group(
account: account,
provider: provider,
provider_identifier: "G:GROUP_ID1"
)
inserted_at = actor_group.inserted_at
id = actor_group.id
# Soft delete the group
Repo.update_all(Domain.Actors.Group, set: [deleted_at: DateTime.utc_now()])
# Assert that the group and associated policy has been soft-deleted
assert Domain.Actors.Group.Query.not_deleted() |> Repo.all() == []
# Simulate a sync
bypass = Bypass.open()
groups = [
%{
"kind" => "admin#directory#group",
"id" => "GROUP_ID1",
"etag" => "\"ET\"",
"email" => "i@fiez.xxx",
"name" => "Infrastructure",
"directMembersCount" => "5",
"description" => "Group to handle infrastructure alerts and management",
"adminCreated" => true,
"aliases" => [
"pnr@firez.one"
],
"nonEditableAliases" => [
"i@ext.fiez.xxx"
]
}
]
GoogleWorkspaceDirectory.override_endpoint_url("http://localhost:#{bypass.port}/")
GoogleWorkspaceDirectory.mock_groups_list_endpoint(
bypass,
200,
JSON.encode!(%{"groups" => groups})
)
GoogleWorkspaceDirectory.mock_organization_units_list_endpoint(
bypass,
200,
JSON.encode!(%{"organizationUnits" => []})
)
GoogleWorkspaceDirectory.mock_users_list_endpoint(
bypass,
200,
JSON.encode!(%{"users" => []})
)
GoogleWorkspaceDirectory.mock_group_members_list_endpoint(
bypass,
"GROUP_ID1",
200,
JSON.encode!(%{"members" => []})
)
GoogleWorkspaceDirectory.mock_token_endpoint(bypass)
{:ok, pid} = Task.Supervisor.start_link()
assert execute(%{task_supervisor: pid}) == :ok
# Assert that the group has been resurrected
assert resurrected_group = Repo.get(Domain.Actors.Group, id)
assert resurrected_group.inserted_at == inserted_at
assert resurrected_group.id == id
assert resurrected_group.deleted_at == nil
assert Domain.Actors.Group.Query.not_deleted() |> Repo.all() == [resurrected_group]
end
test "persists the sync error on the provider", %{provider: provider} do
error_message =
"Admin SDK API has not been used in project XXXX before or it is disabled. " <>

View File

@@ -443,128 +443,6 @@ defmodule Domain.Auth.Adapters.JumpCloud.Jobs.SyncDirectoryTest do
refute Repo.reload(deleted_identity_token)
end
test "resurrects deleted identities that reappear on the next sync", %{
bypass: bypass,
account: account,
provider: provider
} do
actor = Fixtures.Actors.create_actor(account: account)
provider_identifier = "USER_JDOE_ID"
identity =
Fixtures.Auth.create_identity(
account: account,
provider: provider,
actor: actor,
provider_identifier: provider_identifier
)
inserted_at = identity.inserted_at
id = identity.id
# Soft delete the identity
Repo.update_all(Domain.Auth.Identity, set: [deleted_at: DateTime.utc_now()])
assert Domain.Auth.all_identities_for(actor) == []
# Simulate a sync
users = [
%{
"id" => "workos_user_jdoe_id",
"object" => "directory_user",
"custom_attributes" => %{},
"directory_id" => "dir_123",
"organization_id" => "org_123",
"emails" => [
%{
"primary" => true,
"type" => "type",
"value" => "jdoe@example.local"
}
],
"groups" => [],
"idp_id" => "USER_JDOE_ID",
"first_name" => "John",
"last_name" => "Doe",
"job_title" => "Software Eng",
"raw_attributes" => %{},
"state" => "active",
"username" => "jdoe@example.local",
"created_at" => "2023-07-17T20:07:20.055Z",
"updated_at" => "2023-07-17T20:07:20.055Z"
}
]
WorkOSDirectory.override_base_url("http://localhost:#{bypass.port}/")
WorkOSDirectory.mock_list_directories_endpoint(bypass)
WorkOSDirectory.mock_list_groups_endpoint(bypass, [])
WorkOSDirectory.mock_list_users_endpoint(bypass, users)
{:ok, pid} = Task.Supervisor.start_link()
assert execute(%{task_supervisor: pid}) == :ok
# Assert that the identity has been resurrected
assert resurrected_identity = Repo.get(Domain.Auth.Identity, id)
assert resurrected_identity.inserted_at == inserted_at
assert resurrected_identity.id == id
assert resurrected_identity.deleted_at == nil
assert Domain.Auth.all_identities_for(actor) == [resurrected_identity]
end
test "resurrects deleted groups that reappear on the next sync", %{
bypass: bypass,
account: account,
provider: provider
} do
actor_group =
Fixtures.Actors.create_group(
account: account,
provider: provider,
provider_identifier: "G:GROUP_ENGINEERING_ID"
)
inserted_at = actor_group.inserted_at
id = actor_group.id
# Soft delete the group
Repo.update_all(Domain.Actors.Group, set: [deleted_at: DateTime.utc_now()])
# Assert that the group and associated policy has been soft-deleted
assert Domain.Actors.Group.Query.not_deleted() |> Repo.all() == []
# Simulate a sync
groups = [
%{
"id" => "GROUP_ENGINEERING_ID",
"object" => "directory_group",
"idp_id" => "engineering",
"directory_id" => "dir_123",
"organization_id" => "org_123",
"name" => "Engineering",
"created_at" => "2021-10-27 15:21:50.640958",
"updated_at" => "2021-12-13 12:15:45.531847",
"raw_attributes" => %{}
}
]
WorkOSDirectory.override_base_url("http://localhost:#{bypass.port}/")
WorkOSDirectory.mock_list_directories_endpoint(bypass)
WorkOSDirectory.mock_list_groups_endpoint(bypass, groups)
WorkOSDirectory.mock_list_users_endpoint(bypass, [])
{:ok, pid} = Task.Supervisor.start_link()
assert execute(%{task_supervisor: pid}) == :ok
# Assert that the group has been resurrected
assert resurrected_group = Repo.get(Domain.Actors.Group, id)
assert resurrected_group.inserted_at == inserted_at
assert resurrected_group.id == id
assert resurrected_group.deleted_at == nil
assert Domain.Actors.Group.Query.not_deleted() |> Repo.all() == [resurrected_group]
end
test "stops the sync retires on 401 error from WorkOS", %{provider: provider} do
bypass = Bypass.open()
WorkOSDirectory.override_base_url("http://localhost:#{bypass.port}")

View File

@@ -141,128 +141,6 @@ defmodule Domain.Auth.Adapters.MicrosoftEntra.Jobs.SyncDirectoryTest do
assert updated_provider.last_synced_at != provider.last_synced_at
end
test "resurrects deleted identities that reappear on the next sync", %{
account: account,
provider: provider
} do
actor = Fixtures.Actors.create_actor(account: account)
provider_identifier = "USER_JDOE_ID"
identity =
Fixtures.Auth.create_identity(
account: account,
provider: provider,
actor: actor,
provider_identifier: provider_identifier
)
inserted_at = identity.inserted_at
id = identity.id
# Soft delete the identity
Repo.update_all(Domain.Auth.Identity, set: [deleted_at: DateTime.utc_now()])
assert Domain.Auth.all_identities_for(actor) == []
# Simulate a sync
bypass = Bypass.open()
users = [
%{
"id" => "USER_JDOE_ID",
"displayName" => "John Doe",
"givenName" => "John",
"surname" => "Doe",
"userPrincipalName" => "jdoe@example.local",
"mail" => "jdoe@example.local",
"accountEnabled" => true
}
]
MicrosoftEntraDirectory.override_endpoint_url("http://localhost:#{bypass.port}/")
MicrosoftEntraDirectory.mock_groups_list_endpoint(
bypass,
200,
JSON.encode!(%{"value" => []})
)
MicrosoftEntraDirectory.mock_users_list_endpoint(
bypass,
200,
JSON.encode!(%{"value" => users})
)
{:ok, pid} = Task.Supervisor.start_link()
assert execute(%{task_supervisor: pid}) == :ok
# Assert that the identity has been resurrected
assert resurrected_identity = Repo.get(Domain.Auth.Identity, id)
assert resurrected_identity.inserted_at == inserted_at
assert resurrected_identity.id == id
assert resurrected_identity.deleted_at == nil
assert Domain.Auth.all_identities_for(actor) == [resurrected_identity]
end
test "resurrects deleted groups that reappear on the next sync", %{
account: account,
provider: provider
} do
actor_group =
Fixtures.Actors.create_group(
account: account,
provider: provider,
provider_identifier: "G:GROUP_ALL_ID"
)
inserted_at = actor_group.inserted_at
id = actor_group.id
# Soft delete the group
Repo.update_all(Domain.Actors.Group, set: [deleted_at: DateTime.utc_now()])
# Assert that the group and associated policy has been soft-deleted
assert Domain.Actors.Group.Query.not_deleted() |> Repo.all() == []
# Simulate a sync
bypass = Bypass.open()
groups = [
%{"id" => "GROUP_ALL_ID", "displayName" => "All"}
]
MicrosoftEntraDirectory.override_endpoint_url("http://localhost:#{bypass.port}/")
MicrosoftEntraDirectory.mock_groups_list_endpoint(
bypass,
200,
JSON.encode!(%{"value" => groups})
)
MicrosoftEntraDirectory.mock_group_members_list_endpoint(
bypass,
"GROUP_ALL_ID",
200,
JSON.encode!(%{"value" => []})
)
MicrosoftEntraDirectory.mock_users_list_endpoint(
bypass,
200,
JSON.encode!(%{"value" => []})
)
{:ok, pid} = Task.Supervisor.start_link()
assert execute(%{task_supervisor: pid}) == :ok
# Assert that the group has been resurrected
assert resurrected_group = Repo.get(Domain.Actors.Group, id)
assert resurrected_group.inserted_at == inserted_at
assert resurrected_group.id == id
assert resurrected_group.deleted_at == nil
assert Domain.Actors.Group.Query.not_deleted() |> Repo.all() == [resurrected_group]
end
test "does not crash on endpoint errors" do
bypass = Bypass.open()
Bypass.down(bypass)

View File

@@ -745,154 +745,6 @@ defmodule Domain.Auth.Adapters.Okta.Jobs.SyncDirectoryTest do
refute Repo.reload(deleted_identity_token)
end
test "resurrects deleted identities that reappear on the next sync", %{
bypass: bypass,
account: account,
provider: provider
} do
actor = Fixtures.Actors.create_actor(account: account)
provider_identifier = "USER_JDOE_ID"
identity =
Fixtures.Auth.create_identity(
account: account,
provider: provider,
actor: actor,
provider_identifier: provider_identifier
)
inserted_at = identity.inserted_at
id = identity.id
# Soft delete the identity
Repo.update_all(Domain.Auth.Identity, set: [deleted_at: DateTime.utc_now()])
assert Domain.Auth.all_identities_for(actor) == []
# Simulate a sync
users = [
%{
"id" => "USER_JDOE_ID",
"status" => "ACTIVE",
"created" => "2023-12-21T18:30:05.000Z",
"activated" => nil,
"statusChanged" => "2023-12-21T20:04:06.000Z",
"lastLogin" => "2024-02-08T05:14:25.000Z",
"lastUpdated" => "2023-12-21T20:04:06.000Z",
"passwordChanged" => "2023-12-21T20:04:06.000Z",
"type" => %{"id" => "otye1rmouoEfu7KCV5d7"},
"profile" => %{
"firstName" => "John",
"lastName" => "Doe",
"mobilePhone" => nil,
"secondEmail" => nil,
"login" => "jdoe@example.com",
"email" => "jdoe@example.com"
},
"_links" => %{
"self" => %{
"href" => "http://localhost:#{bypass.port}/api/v1/users/OT6AZkcmzkDXwkXcjTHY"
}
}
}
]
OktaDirectory.mock_groups_list_endpoint(bypass, 200, JSON.encode!([]))
OktaDirectory.mock_users_list_endpoint(bypass, 200, JSON.encode!(users))
{:ok, pid} = Task.Supervisor.start_link()
assert execute(%{task_supervisor: pid}) == :ok
# Assert that the identity has been resurrected
assert resurrected_identity = Repo.get(Domain.Auth.Identity, id)
assert resurrected_identity.inserted_at == inserted_at
assert resurrected_identity.id == id
assert resurrected_identity.deleted_at == nil
assert Domain.Auth.all_identities_for(actor) == [resurrected_identity]
end
test "resurrects deleted groups that reappear on the next sync", %{
bypass: bypass,
account: account,
provider: provider
} do
actor_group =
Fixtures.Actors.create_group(
account: account,
provider: provider,
provider_identifier: "G:GROUP_DEVOPS_ID"
)
inserted_at = actor_group.inserted_at
id = actor_group.id
# Soft delete the group
Repo.update_all(Domain.Actors.Group, set: [deleted_at: DateTime.utc_now()])
# Assert that the group and associated policy has been soft-deleted
assert Domain.Actors.Group.Query.not_deleted() |> Repo.all() == []
# Simulate a sync
groups = [
%{
"id" => "GROUP_DEVOPS_ID",
"created" => "2024-02-07T04:32:03.000Z",
"lastUpdated" => "2024-02-07T04:32:03.000Z",
"lastMembershipUpdated" => "2024-02-07T04:32:38.000Z",
"objectClass" => [
"okta:user_group"
],
"type" => "OKTA_GROUP",
"profile" => %{
"name" => "DevOps",
"description" => ""
},
"_links" => %{
"logo" => [
%{
"name" => "medium",
"href" => "http://localhost/md/image.png",
"type" => "image/png"
},
%{
"name" => "large",
"href" => "http://localhost/lg/image.png",
"type" => "image/png"
}
],
"users" => %{
"href" => "http://localhost:#{bypass.port}/api/v1/groups/00gezqhvv4IFj2Avg5d7/users"
},
"apps" => %{
"href" => "http://localhost:#{bypass.port}/api/v1/groups/00gezqhvv4IFj2Avg5d7/apps"
}
}
}
]
OktaDirectory.mock_users_list_endpoint(bypass, 200, JSON.encode!([]))
OktaDirectory.mock_groups_list_endpoint(bypass, 200, JSON.encode!(groups))
OktaDirectory.mock_group_members_list_endpoint(
bypass,
"GROUP_DEVOPS_ID",
200,
JSON.encode!([])
)
{:ok, pid} = Task.Supervisor.start_link()
assert execute(%{task_supervisor: pid}) == :ok
# Assert that the group has been resurrected
assert resurrected_group = Repo.get(Domain.Actors.Group, id)
assert resurrected_group.inserted_at == inserted_at
assert resurrected_group.id == id
assert resurrected_group.deleted_at == nil
assert Domain.Actors.Group.Query.not_deleted() |> Repo.all() == [resurrected_group]
end
test "persists the sync error on the provider", %{provider: provider, bypass: bypass} do
response = %{
"errorCode" => "E0000011",

View File

@@ -444,21 +444,6 @@ defmodule Domain.AuthTest do
assert all_providers_pending_token_refresh_by_adapter!(:google_workspace) == []
end
test "ignores deleted providers" do
{provider, _bypass} = Fixtures.Auth.start_and_create_google_workspace_provider()
Domain.Fixture.update!(provider, %{
deleted_at: DateTime.utc_now(),
adapter_state: %{
"access_token" => "OIDC_ACCESS_TOKEN",
"refresh_token" => "OIDC_REFRESH_TOKEN",
"expires_at" => DateTime.utc_now()
}
})
assert all_providers_pending_token_refresh_by_adapter!(:google_workspace) == []
end
test "ignores non-custom provisioners" do
{provider, _bypass} = Fixtures.Auth.start_and_create_google_workspace_provider()
@@ -533,19 +518,6 @@ defmodule Domain.AuthTest do
assert all_providers_pending_sync_by_adapter!(:google_workspace) == []
end
test "ignores deleted providers" do
{provider, _bypass} = Fixtures.Auth.start_and_create_google_workspace_provider()
Domain.Fixture.update!(provider, %{
deleted_at: DateTime.utc_now(),
adapter_state: %{
"expires_at" => DateTime.utc_now()
}
})
assert all_providers_pending_sync_by_adapter!(:google_workspace) == []
end
test "ignores non-custom provisioners" do
{provider, _bypass} = Fixtures.Auth.start_and_create_google_workspace_provider()
@@ -781,7 +753,6 @@ defmodule Domain.AuthTest do
assert provider.created_by_subject == %{"email" => nil, "name" => "System"}
assert is_nil(provider.disabled_at)
assert is_nil(provider.deleted_at)
end
test "trims whitespace when creating a provider", %{
@@ -961,7 +932,6 @@ defmodule Domain.AuthTest do
assert provider.account_id == subject.account.id
assert is_nil(provider.disabled_at)
assert is_nil(provider.deleted_at)
end
test "returns error when subject cannot manage providers", %{
@@ -1031,7 +1001,6 @@ defmodule Domain.AuthTest do
assert is_nil(other_provider.disabled_at)
end
# TODO: HARD-DELETE - This test should be moved to Tokens since it has the FK
test "deletes tokens issued for provider identities", %{
account: account,
subject: subject
@@ -1209,8 +1178,7 @@ defmodule Domain.AuthTest do
refute Repo.get(Auth.Provider, provider.id)
assert other_provider = Repo.get(Auth.Provider, other_provider.id)
assert is_nil(other_provider.deleted_at)
assert Repo.get(Auth.Provider, other_provider.id)
end
test "deletes provider identities and tokens", %{
@@ -1274,51 +1242,6 @@ defmodule Domain.AuthTest do
assert delete_provider(provider, subject) == {:error, :cant_delete_the_last_provider}
end
# TODO: HARD-DELETE - Need to figure out if we care about this case
# test "returns error when trying to delete the last provider using a race condition" do
# for _ <- 0..50 do
# test_pid = self()
# Task.async(fn ->
# allow_child_sandbox_access(test_pid)
# account = Fixtures.Accounts.create_account()
# provider_one = Fixtures.Auth.create_email_provider(account: account)
# provider_two = Fixtures.Auth.create_userpass_provider(account: account)
# actor =
# Fixtures.Actors.create_actor(
# type: :account_admin_user,
# account: account,
# provider: provider_one
# )
# identity =
# Fixtures.Auth.create_identity(
# account: account,
# actor: actor,
# provider: provider_one
# )
# subject = Fixtures.Auth.create_subject(identity: identity)
# for provider <- [provider_two, provider_one] do
# Task.async(fn ->
# allow_child_sandbox_access(test_pid)
# delete_provider(provider, subject)
# end)
# end
# |> Task.await_many()
# assert Auth.Provider.Query.not_deleted()
# |> Auth.Provider.Query.by_account_id(account.id)
# |> Repo.aggregate(:count) == 1
# end)
# end
# |> Task.await_many()
# end
test "raises error when deleting stale provider structs", %{
subject: subject,
account: account
@@ -1740,162 +1663,87 @@ defmodule Domain.AuthTest do
assert Enum.count(actor_ids_by_provider_identifier) == 2
end
test "does not re-create actors for deleted identities", %{
account: account,
provider: provider
} do
identity =
test "deletes removed identities", %{account: account, provider: provider} do
provider_identifiers = ["USER_ID1", "USER_ID2", "USER_ID3", "USER_ID4", "USER_ID5"]
deleted_identity_actor = Fixtures.Actors.create_actor(account: account)
deleted_identity =
Fixtures.Auth.create_identity(
account: account,
provider: provider,
provider_identifier: "USER_ID1",
actor: [type: :account_admin_user]
actor: deleted_identity_actor,
provider_identifier: Enum.at(provider_identifiers, 0)
)
|> Fixtures.Auth.delete_identity()
deleted_identity_token =
Fixtures.Tokens.create_token(
account: account,
actor: deleted_identity_actor,
identity: deleted_identity
)
for n <- 1..4 do
Fixtures.Auth.create_identity(
account: account,
provider: provider,
provider_identifier: Enum.at(provider_identifiers, n)
)
end
attrs_list = [
%{
"actor" => %{
"name" => "Brian Manifold",
"name" => "Joe Smith",
"type" => "account_user"
},
"provider_identifier" => "USER_ID1"
"provider_identifier" => "USER_ID3"
},
%{
"actor" => %{
"name" => "Jennie Smith",
"type" => "account_user"
},
"provider_identifier" => "USER_ID4"
},
%{
"actor" => %{
"name" => "Jane Doe",
"type" => "account_admin_user"
},
"provider_identifier" => "USER_ID5"
}
]
assert {:ok,
%{
identities: [fetched_identity],
plan: {[], ["USER_ID1"], []},
deleted_count: 0,
identities: [_id1, _id2, _id3, _id4, _id5],
plan: {[], upsert, delete},
deleted_count: 2,
inserted: [],
actor_ids_by_provider_identifier: actor_ids_by_provider_identifier
}} = sync_provider_identities(provider, attrs_list)
assert fetched_identity.actor_id == identity.actor_id
assert actor_ids_by_provider_identifier == %{"USER_ID1" => identity.actor_id}
assert Enum.sort(upsert) == ["USER_ID3", "USER_ID4", "USER_ID5"]
identity = Repo.get(Auth.Identity, identity.id)
assert identity.actor_id == identity.actor_id
refute identity.deleted_at
assert Enum.take(provider_identifiers, 2)
|> Enum.all?(&(&1 in delete))
actor = Repo.get(Domain.Actors.Actor, identity.actor_id)
assert actor.name == "Brian Manifold"
refute Repo.get_by(Auth.Identity, provider_identifier: "USER_ID1")
refute Repo.get_by(Auth.Identity, provider_identifier: "USER_ID2")
assert Auth.Identity.Query.all()
|> Auth.Identity.Query.by_provider_id(provider.id)
|> Repo.aggregate(:count) == 3
assert actor_ids_by_provider_identifier
|> Map.keys()
|> length() == 3
# Signs out users which identity has been deleted
refute Repo.reload(deleted_identity_token)
end
test "does not attempt to delete identities that are already deleted", %{
account: account,
provider: provider
} do
identity =
Fixtures.Auth.create_identity(
account: account,
provider: provider,
provider_identifier: "USER_ID1",
actor: [type: :account_admin_user]
)
|> Fixtures.Auth.delete_identity()
attrs_list = []
assert {:ok,
%{
identities: [fetched_identity],
plan: {[], [], []},
deleted_count: 0,
inserted: [],
actor_ids_by_provider_identifier: %{}
}} = sync_provider_identities(provider, attrs_list)
assert fetched_identity.id == identity.id
identity = Repo.get(Auth.Identity, identity.id)
assert identity.deleted_at
end
# TODO: HARD-DELETE - Need to figure out if the flows message checking is necessary
# test "deletes removed identities", %{account: account, provider: provider} do
# provider_identifiers = ["USER_ID1", "USER_ID2", "USER_ID3", "USER_ID4", "USER_ID5"]
# deleted_identity_actor = Fixtures.Actors.create_actor(account: account)
# deleted_identity =
# Fixtures.Auth.create_identity(
# account: account,
# provider: provider,
# actor: deleted_identity_actor,
# provider_identifier: Enum.at(provider_identifiers, 0)
# )
# deleted_identity_token =
# Fixtures.Tokens.create_token(
# account: account,
# actor: deleted_identity_actor,
# identity: deleted_identity
# )
# for n <- 1..4 do
# Fixtures.Auth.create_identity(
# account: account,
# provider: provider,
# provider_identifier: Enum.at(provider_identifiers, n)
# )
# end
# attrs_list = [
# %{
# "actor" => %{
# "name" => "Joe Smith",
# "type" => "account_user"
# },
# "provider_identifier" => "USER_ID3"
# },
# %{
# "actor" => %{
# "name" => "Jennie Smith",
# "type" => "account_user"
# },
# "provider_identifier" => "USER_ID4"
# },
# %{
# "actor" => %{
# "name" => "Jane Doe",
# "type" => "account_admin_user"
# },
# "provider_identifier" => "USER_ID5"
# }
# ]
# assert {:ok,
# %{
# identities: [_id1, _id2, _id3, _id4, _id5],
# plan: {[], upsert, delete},
# deleted: 2,
# inserted: [],
# actor_ids_by_provider_identifier: actor_ids_by_provider_identifier
# }} = sync_provider_identities(provider, attrs_list)
# assert Enum.sort(upsert) == ["USER_ID3", "USER_ID4", "USER_ID5"]
# assert Enum.take(provider_identifiers, 2)
# |> Enum.all?(&(&1 in delete))
# refute Repo.get_by(Auth.Identity, provider_identifier: "USER_ID1")
# refute Repo.get_by(Auth.Identity, provider_identifier: "USER_ID2")
# assert Auth.Identity.Query.all()
# |> Auth.Identity.Query.by_provider_id(provider.id)
# |> Repo.aggregate(:count) == 3
# assert actor_ids_by_provider_identifier
# |> Map.keys()
# |> length() == 3
# # Signs out users which identity has been deleted
# deleted_identity_token = Repo.reload(deleted_identity_token)
# assert deleted_identity_token.deleted_at
# end
test "circuit breaker prevents mass deletions of identities", %{
account: account,
provider: provider
@@ -1919,7 +1767,7 @@ defmodule Domain.AuthTest do
|> Auth.Identity.Query.by_provider_id(provider.id)
|> Repo.aggregate(:count) == 5
assert Auth.Identity.Query.not_deleted()
assert Auth.Identity.Query.all()
|> Auth.Identity.Query.by_provider_id(provider.id)
|> Repo.aggregate(:count) == 5
end
@@ -1978,63 +1826,6 @@ defmodule Domain.AuthTest do
assert Repo.aggregate(Auth.Identity, :count) == 0
assert Repo.aggregate(Domain.Actors.Actor, :count) == 0
end
test "resolves provider identifier conflicts across actors", %{
account: account,
provider: provider
} do
identity1 =
Fixtures.Auth.create_identity(
account: account,
provider: provider,
provider_identifier: "USER_ID1",
actor: [type: :account_admin_user]
)
|> Fixtures.Auth.delete_identity()
identity2 =
Fixtures.Auth.create_identity(
account: account,
provider: provider,
provider_identifier: "USER_ID1",
actor: [type: :account_admin_user]
)
attrs_list = [
%{
"actor" => %{
"name" => "Brian Manifold",
"type" => "account_user"
},
"provider_identifier" => "USER_ID1"
}
]
assert {:ok,
%{
identities: [_identity1, _identity2],
plan: {[], update, []},
deleted_count: 0,
inserted: [],
actor_ids_by_provider_identifier: actor_ids_by_provider_identifier
}} = sync_provider_identities(provider, attrs_list)
assert length(update) == 2
assert update == ["USER_ID1", "USER_ID1"]
identity1 = Repo.get(Domain.Auth.Identity, identity1.id) |> Repo.preload(:actor)
assert identity1.deleted_at
assert identity1.actor.name != "Brian Manifold"
identity2 = Repo.get(Domain.Auth.Identity, identity2.id) |> Repo.preload(:actor)
refute identity2.deleted_at
assert identity2.actor.name == "Brian Manifold"
assert Map.get(actor_ids_by_provider_identifier, identity2.provider_identifier) ==
identity2.actor.id
assert Enum.count(actor_ids_by_provider_identifier) == 1
end
end
describe "upsert_identity/3" do
@@ -2087,7 +1878,6 @@ defmodule Domain.AuthTest do
assert identity.provider_state == %{}
assert identity.provider_virtual_state == %{}
assert identity.account_id == provider.account_id
assert is_nil(identity.deleted_at)
end
test "trims whitespace when creating an identity", %{
@@ -2270,8 +2060,6 @@ defmodule Domain.AuthTest do
"name" => subject.actor.name,
"email" => subject.identity.email
}
assert is_nil(identity.deleted_at)
end
test "trims whitespace when creating an identity", %{
@@ -2389,7 +2177,6 @@ defmodule Domain.AuthTest do
assert %{"password_hash" => _} = identity.provider_state
assert %{password_hash: _} = identity.provider_virtual_state.changes
assert identity.account_id == provider.account_id
assert is_nil(identity.deleted_at)
end
test "creates an identity when provider_identifier is an email address" do
@@ -2423,7 +2210,6 @@ defmodule Domain.AuthTest do
assert %{"password_hash" => _} = identity.provider_state
assert %{password_hash: _} = identity.provider_virtual_state.changes
assert identity.account_id == provider.account_id
assert is_nil(identity.deleted_at)
end
test "returns error when identifier is invalid" do
@@ -2561,7 +2347,6 @@ defmodule Domain.AuthTest do
refute Repo.get(Auth.Identity, identity.id)
end
# TODO: HARD-DELETE - This test should be moved to tokens since it has the FK
test "deletes token", %{
account: account,
provider: provider,
@@ -2682,29 +2467,6 @@ defmodule Domain.AuthTest do
assert Repo.aggregate(by_actor_id_query, :count) == 0
end
test "removes all identities and flows that belong to a provider", %{
account: account,
provider: provider,
subject: subject
} do
actor = Fixtures.Actors.create_actor(account: account, provider: provider)
Fixtures.Auth.create_identity(account: account, provider: provider, actor: actor)
Fixtures.Auth.create_identity(account: account, provider: provider, actor: actor)
Fixtures.Auth.create_identity(account: account, provider: provider, actor: actor)
all_identities_query = Auth.Identity.Query.all()
assert Repo.aggregate(all_identities_query, :count) == 4
assert {:ok, 4} = delete_identities_for(provider, subject)
assert Repo.aggregate(all_identities_query, :count) == 0
by_provider_id_query =
Auth.Identity.Query.not_deleted()
|> Auth.Identity.Query.by_provider_id(provider.id)
assert Repo.aggregate(by_provider_id_query, :count) == 0
end
test "deletes tokens", %{
account: account,
provider: provider,
@@ -2753,22 +2515,6 @@ defmodule Domain.AuthTest do
end
end
# TODO: HARD-DELETE - Remove or update after soft deletion is removed
describe "identity_soft_deleted?/1" do
test "returns true when identity is deleted" do
identity =
Fixtures.Auth.create_identity()
|> Fixtures.Auth.delete_identity()
assert identity_soft_deleted?(identity) == true
end
test "returns false when identity is not deleted" do
identity = Fixtures.Auth.create_identity()
assert identity_soft_deleted?(identity) == false
end
end
# Authentication
describe "sign_in/4" do
@@ -3107,51 +2853,6 @@ defmodule Domain.AuthTest do
assert sign_in(provider, identity.provider_identifier, nonce, secret, context) ==
{:error, :unauthorized}
end
# TODO: HARD-DELETE - Not sure this test is needed any more. I don't think this is a reachable state.
# test "returns error when actor is deleted", %{
# account: account,
# provider: provider,
# user_agent: user_agent,
# remote_ip: remote_ip
# } do
# nonce = "test_nonce_for_firezone"
# actor =
# Fixtures.Actors.create_actor(type: :account_admin_user, account: account)
# |> Fixtures.Actors.delete()
# identity = Fixtures.Auth.create_identity(account: account, provider: provider, actor: actor)
# context = %Auth.Context{type: :browser, user_agent: user_agent, remote_ip: remote_ip}
# {:ok, identity} = Domain.Auth.Adapters.Email.request_sign_in_token(identity, context)
# secret = identity.provider_virtual_state.nonce <> identity.provider_virtual_state.fragment
# assert sign_in(provider, identity.provider_identifier, nonce, secret, context) ==
# {:error, :unauthorized}
# end
# TODO: HARD-DELETE - Not sure this test is needed any more. I don't think this is a reachable state.
# test "returns error when provider is deleted", %{
# account: account,
# provider: provider,
# user_agent: user_agent,
# remote_ip: remote_ip
# } do
# nonce = "test_nonce_for_firezone"
# actor = Fixtures.Actors.create_actor(type: :account_admin_user, account: account)
# identity = Fixtures.Auth.create_identity(account: account, provider: provider, actor: actor)
# subject = Fixtures.Auth.create_subject(identity: identity)
# {:ok, _provider} = delete_provider(provider, subject)
# context = %Auth.Context{type: :browser, user_agent: user_agent, remote_ip: remote_ip}
# {:ok, identity} = Domain.Auth.Adapters.Email.request_sign_in_token(identity, context)
# secret = identity.provider_virtual_state.nonce <> identity.provider_virtual_state.fragment
# assert sign_in(provider, identity.provider_identifier, nonce, secret, context) ==
# {:error, :unauthorized}
# end
end
describe "sign_in/3" do
@@ -4326,12 +4027,4 @@ defmodule Domain.AuthTest do
refute can_grant_role?(subject, :account_admin_user)
end
end
# TODO: HARD-DELETE - This may not be needed anymore
# defp allow_child_sandbox_access(parent_pid) do
# Ecto.Adapters.SQL.Sandbox.allow(Repo, parent_pid, self())
# # Allow is async call we need to break current process execution
# # to allow sandbox to be enabled
# :timer.sleep(10)
# end
end

View File

@@ -273,14 +273,13 @@ defmodule Domain.ChangeLogs.ReplicationConnectionTest do
}
end
test "adds delete operation to flush buffer for non-soft-deleted records", %{account: account} do
test "adds delete operation to flush buffer for deleted records", %{account: account} do
table = "resources"
old_data = %{
"id" => Ecto.UUID.generate(),
"account_id" => account.id,
"name" => "deleted resource",
"deleted_at" => nil
"name" => "deleted resource"
}
lsn = 12347

View File

@@ -30,26 +30,6 @@ defmodule Domain.Changes.Hooks.AccountsTest do
assert account.id == account_id
end
test "sends delete when soft-deleted" do
account_id = "00000000-0000-0000-0000-000000000002"
:ok = PubSub.Account.subscribe(account_id)
old_data = %{
"id" => account_id,
"deleted_at" => nil
}
data = %{
"id" => account_id,
"deleted_at" => "2023-10-01T00:00:00Z"
}
assert :ok == on_update(0, old_data, data)
assert_receive %Change{op: :delete, old_struct: %Accounts.Account{} = account, lsn: 0}
assert account.id == account_id
end
end
describe "delete/1" do
@@ -57,25 +37,18 @@ defmodule Domain.Changes.Hooks.AccountsTest do
account_id = "00000000-0000-0000-0000-000000000003"
:ok = PubSub.Account.subscribe(account_id)
old_data = %{
"id" => account_id,
"deleted_at" => "2023-10-01T00:00:00Z"
}
old_data = %{"id" => account_id}
assert :ok == on_delete(0, old_data)
assert_receive %Change{op: :delete, old_struct: %Accounts.Account{} = account, lsn: 0}
assert account.id == account_id
assert account.deleted_at == ~U[2023-10-01 00:00:00.000000Z]
end
test "deletes associated flows when account is deleted" do
account = Fixtures.Accounts.create_account()
flow = Fixtures.Flows.create_flow(account: account)
old_data = %{
"id" => account.id,
"deleted_at" => "2023-10-01T00:00:00Z"
}
old_data = %{"id" => account.id}
assert :ok == on_delete(0, old_data)
assert Repo.get_by(Domain.Flows.Flow, id: flow.id) == nil

View File

@@ -1,5 +1,5 @@
defmodule Domain.Changes.Hooks.ActorGroupMembershipsTest do
use API.ChannelCase, async: true
use Domain.DataCase, async: true
import Domain.Changes.Hooks.ActorGroupMemberships
alias Domain.{Actors, Changes.Change, Flows, PubSub}

View File

@@ -10,23 +10,6 @@ defmodule Domain.Changes.Hooks.ClientsTest do
end
describe "update/2" do
test "soft-delete broadcasts deleted client" do
client = Fixtures.Clients.create_client()
:ok = PubSub.Account.subscribe(client.account_id)
old_data = %{"id" => client.id, "deleted_at" => nil, "account_id" => client.account_id}
data = %{
"id" => client.id,
"deleted_at" => DateTime.utc_now(),
"account_id" => client.account_id
}
assert :ok == on_update(0, old_data, data)
assert_receive %Change{op: :delete, old_struct: %Clients.Client{} = deleted_client, lsn: 0}
assert deleted_client.id == client.id
end
test "update broadcasts updated client" do
account = Fixtures.Accounts.create_account()
client = Fixtures.Clients.create_client(account: account)

View File

@@ -25,8 +25,7 @@ defmodule Domain.Changes.Hooks.GatewayGroupsTest do
old_data = %{
"id" => "00000000-0000-0000-0000-000000000001",
"account_id" => account_id,
"name" => "Old Gateway Group",
"deleted_at" => nil
"name" => "Old Gateway Group"
}
data = Map.put(old_data, "name", "Updated Gateway Group")

View File

@@ -10,38 +10,6 @@ defmodule Domain.Changes.Hooks.GatewaysTest do
end
describe "update/2" do
test "soft-delete broadcasts deleted gateway" do
account = Fixtures.Accounts.create_account()
gateway = Fixtures.Gateways.create_gateway(account: account)
:ok = PubSub.Account.subscribe(account.id)
old_data = %{"id" => gateway.id, "deleted_at" => nil, "account_id" => account.id}
data = Map.put(old_data, "deleted_at", "2023-01-01T00:00:00Z")
assert :ok = on_update(0, old_data, data)
assert_receive %Change{
op: :delete,
old_struct: %Gateways.Gateway{} = deleted_gateway,
lsn: 0
}
assert deleted_gateway.id == gateway.id
end
test "soft-delete deletes flows" do
account = Fixtures.Accounts.create_account()
gateway = Fixtures.Gateways.create_gateway(account: account)
old_data = %{"id" => gateway.id, "deleted_at" => nil, "account_id" => account.id}
data = Map.put(old_data, "deleted_at", "2023-01-01T00:00:00Z")
assert flow = Fixtures.Flows.create_flow(gateway: gateway, account: account)
assert :ok = on_update(0, old_data, data)
refute Repo.get_by(Domain.Flows.Flow, id: flow.id)
end
test "update returns :ok" do
assert :ok = on_update(0, %{}, %{})
end
@@ -57,8 +25,7 @@ defmodule Domain.Changes.Hooks.GatewaysTest do
old_data = %{
"id" => gateway.id,
"account_id" => account.id,
"name" => "Test Gateway",
"deleted_at" => nil
"name" => "Test Gateway"
}
assert :ok = on_delete(0, old_data)
@@ -76,7 +43,7 @@ defmodule Domain.Changes.Hooks.GatewaysTest do
account = Fixtures.Accounts.create_account()
gateway = Fixtures.Gateways.create_gateway(account: account)
old_data = %{"id" => gateway.id, "account_id" => account.id, "deleted_at" => nil}
old_data = %{"id" => gateway.id, "account_id" => account.id}
assert flow = Fixtures.Flows.create_flow(gateway: gateway, account: account)
assert :ok = on_delete(0, old_data)

View File

@@ -14,8 +14,7 @@ defmodule Domain.Changes.Hooks.PoliciesTest do
"account_id" => account.id,
"actor_group_id" => policy.actor_group_id,
"resource_id" => policy.resource_id,
"disabled_at" => nil,
"deleted_at" => nil
"disabled_at" => nil
}
assert :ok == on_insert(0, data)
@@ -40,8 +39,7 @@ defmodule Domain.Changes.Hooks.PoliciesTest do
"account_id" => account.id,
"actor_group_id" => policy.actor_group_id,
"resource_id" => policy.resource_id,
"disabled_at" => nil,
"deleted_at" => nil
"disabled_at" => nil
}
data = Map.put(old_data, "disabled_at", "2023-10-01T00:00:00Z")
@@ -74,8 +72,7 @@ defmodule Domain.Changes.Hooks.PoliciesTest do
"account_id" => account.id,
"actor_group_id" => policy.actor_group_id,
"resource_id" => policy.resource_id,
"disabled_at" => "2023-09-01T00:00:00Z",
"deleted_at" => nil
"disabled_at" => "2023-09-01T00:00:00Z"
}
data = Map.put(old_data, "disabled_at", nil)
@@ -89,62 +86,6 @@ defmodule Domain.Changes.Hooks.PoliciesTest do
assert policy.resource_id == data["resource_id"]
end
test "soft-delete broadcasts deleted policy" do
account = Fixtures.Accounts.create_account()
policy = Fixtures.Policies.create_policy(account: account)
:ok = PubSub.Account.subscribe(account.id)
old_data = %{
"id" => policy.id,
"account_id" => account.id,
"actor_group_id" => policy.actor_group_id,
"resource_id" => policy.resource_id,
"disabled_at" => nil,
"deleted_at" => nil
}
data = Map.put(old_data, "deleted_at", "2023-10-01T00:00:00Z")
assert :ok == on_update(0, old_data, data)
assert_receive %Change{op: :delete, old_struct: %Policies.Policy{} = policy, lsn: 0}
assert policy.id == old_data["id"]
assert policy.account_id == old_data["account_id"]
assert policy.actor_group_id == old_data["actor_group_id"]
assert policy.resource_id == old_data["resource_id"]
end
test "soft-delete deletes flows" do
account = Fixtures.Accounts.create_account()
resource = Fixtures.Resources.create_resource(account: account)
policy =
Fixtures.Policies.create_policy(
account: account,
resource: resource
)
old_data = %{
"id" => policy.id,
"account_id" => account.id,
"actor_group_id" => policy.actor_group_id,
"resource_id" => resource.id,
"deleted_at" => nil
}
data = Map.put(old_data, "deleted_at", "2023-10-01T00:00:00Z")
assert flow =
Fixtures.Flows.create_flow(
policy: policy,
resource: resource,
account: account
)
assert :ok = on_update(0, old_data, data)
refute Repo.get_by(Domain.Flows.Flow, id: flow.id)
end
test "non-breaking update broadcasts updated policy" do
account = Fixtures.Accounts.create_account()
policy = Fixtures.Policies.create_policy(account: account)
@@ -156,8 +97,7 @@ defmodule Domain.Changes.Hooks.PoliciesTest do
"account_id" => account.id,
"actor_group_id" => policy.actor_group_id,
"resource_id" => policy.resource_id,
"disabled_at" => nil,
"deleted_at" => nil
"disabled_at" => nil
}
data = Map.put(old_data, "description", "Updated description")
@@ -186,8 +126,7 @@ defmodule Domain.Changes.Hooks.PoliciesTest do
"id" => policy.id,
"account_id" => account.id,
"actor_group_id" => policy.actor_group_id,
"resource_id" => policy.resource_id,
"deleted_at" => nil
"resource_id" => policy.resource_id
}
data = Map.put(old_data, "resource_id", "00000000-0000-0000-0000-000000000001")
@@ -210,8 +149,7 @@ defmodule Domain.Changes.Hooks.PoliciesTest do
"id" => policy.id,
"account_id" => account.id,
"actor_group_id" => policy.actor_group_id,
"resource_id" => policy.resource_id,
"deleted_at" => nil
"resource_id" => policy.resource_id
}
data = Map.put(old_data, "actor_group_id", "00000000-0000-0000-0000-000000000001")
@@ -233,8 +171,7 @@ defmodule Domain.Changes.Hooks.PoliciesTest do
"resource_id" => policy.resource_id,
"conditions" => [
%{"property" => "remote_ip", "operator" => "is_in", "values" => ["10.0.0.1"]}
],
"deleted_at" => nil
]
}
data =
@@ -284,8 +221,7 @@ defmodule Domain.Changes.Hooks.PoliciesTest do
"id" => policy.id,
"account_id" => account.id,
"actor_group_id" => policy.actor_group_id,
"resource_id" => policy.resource_id,
"deleted_at" => nil
"resource_id" => policy.resource_id
}
assert flow = Fixtures.Flows.create_flow(policy: policy, account: account)

View File

@@ -18,8 +18,7 @@ defmodule Domain.Changes.Hooks.ResourcesTest do
"type" => resource.type,
"address" => resource.address,
"filters" => filters,
"ip_stack" => resource.ip_stack,
"deleted_at" => nil
"ip_stack" => resource.ip_stack
}
assert :ok == on_insert(0, data)
@@ -41,66 +40,6 @@ defmodule Domain.Changes.Hooks.ResourcesTest do
end
describe "update/2" do
test "soft-delete broadcasts deleted resource" do
account = Fixtures.Accounts.create_account()
filters = [%{"protocol" => "tcp", "ports" => ["80", "443"]}]
resource = Fixtures.Resources.create_resource(account: account, filters: filters)
:ok = PubSub.Account.subscribe(account.id)
old_data = %{
"id" => resource.id,
"account_id" => account.id,
"address_description" => resource.address_description,
"type" => resource.type,
"address" => resource.address,
"filters" => filters,
"ip_stack" => resource.ip_stack,
"deleted_at" => nil
}
data = Map.put(old_data, "deleted_at", "2023-10-01T00:00:00Z")
assert :ok == on_update(0, old_data, data)
assert_receive %Change{
op: :delete,
old_struct: %Resources.Resource{} = deleted_resource,
lsn: 0
}
assert deleted_resource.id == resource.id
assert deleted_resource.account_id == resource.account_id
assert deleted_resource.type == resource.type
assert deleted_resource.address == resource.address
assert deleted_resource.filters == resource.filters
assert deleted_resource.ip_stack == resource.ip_stack
assert deleted_resource.address_description == resource.address_description
end
test "soft-delete deletes flows" do
account = Fixtures.Accounts.create_account()
filters = [%{"protocol" => "tcp", "ports" => ["80", "443"]}]
resource = Fixtures.Resources.create_resource(account: account, filters: filters)
old_data = %{
"id" => resource.id,
"account_id" => account.id,
"address_description" => resource.address_description,
"type" => resource.type,
"address" => resource.address,
"filters" => filters,
"ip_stack" => resource.ip_stack,
"deleted_at" => nil
}
data = Map.put(old_data, "deleted_at", "2023-10-01T00:00:00Z")
assert flow = Fixtures.Flows.create_flow(resource: resource, account: account)
assert :ok = on_update(0, old_data, data)
refute Repo.get_by(Flows.Flow, id: flow.id)
end
test "regular update broadcasts updated resource" do
account = Fixtures.Accounts.create_account()
filters = [%{"protocol" => "tcp", "ports" => ["80", "443"]}]
@@ -115,8 +54,7 @@ defmodule Domain.Changes.Hooks.ResourcesTest do
"type" => resource.type,
"address" => resource.address,
"filters" => filters,
"ip_stack" => resource.ip_stack,
"deleted_at" => nil
"ip_stack" => resource.ip_stack
}
data = Map.put(old_data, "address", "new-address.example.com")
@@ -151,8 +89,7 @@ defmodule Domain.Changes.Hooks.ResourcesTest do
"type" => "dns",
"address" => resource.address,
"filters" => filters,
"ip_stack" => resource.ip_stack,
"deleted_at" => nil
"ip_stack" => resource.ip_stack
}
data = Map.put(old_data, "type", "cidr")
@@ -178,8 +115,7 @@ defmodule Domain.Changes.Hooks.ResourcesTest do
"type" => resource.type,
"address" => resource.address,
"filters" => filters,
"ip_stack" => resource.ip_stack,
"deleted_at" => nil
"ip_stack" => resource.ip_stack
}
assert :ok == on_delete(0, old_data)
@@ -211,8 +147,7 @@ defmodule Domain.Changes.Hooks.ResourcesTest do
"type" => resource.type,
"address" => resource.address,
"filters" => filters,
"ip_stack" => resource.ip_stack,
"deleted_at" => nil
"ip_stack" => resource.ip_stack
}
assert flow = Fixtures.Flows.create_flow(resource: resource, account: account)

View File

@@ -14,48 +14,6 @@ defmodule Domain.Changes.Hooks.TokensTest do
assert :ok = on_update(0, %{"type" => "email"}, %{"type" => "email"})
end
test "soft-delete broadcasts disconnect" do
account = Fixtures.Accounts.create_account()
token = Fixtures.Tokens.create_token(account: account)
:ok = PubSub.subscribe("sessions:#{token.id}")
old_data = %{
"id" => token.id,
"account_id" => account.id,
"type" => token.type,
"deleted_at" => nil
}
assert :ok == on_delete(0, old_data)
assert_receive %Phoenix.Socket.Broadcast{
topic: topic,
event: "disconnect"
}
assert topic == "sessions:#{token.id}"
end
test "soft-delete deletes flows" do
account = Fixtures.Accounts.create_account()
token = Fixtures.Tokens.create_token(account: account)
old_data = %{
"id" => token.id,
"account_id" => account.id,
"type" => token.type,
"deleted_at" => nil
}
data = Map.put(old_data, "deleted_at", "2023-10-01T00:00:00Z")
assert flow = Fixtures.Flows.create_flow(account: account, token: token)
assert flow.token_id == token.id
assert :ok = on_update(0, old_data, data)
refute Repo.get_by(Flows.Flow, id: flow.id)
end
test "regular update returns :ok" do
assert :ok = on_update(0, %{}, %{})
end
@@ -71,8 +29,7 @@ defmodule Domain.Changes.Hooks.TokensTest do
old_data = %{
"id" => token.id,
"account_id" => account.id,
"type" => token.type,
"deleted_at" => nil
"type" => token.type
}
assert :ok == on_delete(0, old_data)
@@ -92,8 +49,7 @@ defmodule Domain.Changes.Hooks.TokensTest do
old_data = %{
"id" => token.id,
"account_id" => account.id,
"type" => token.type,
"deleted_at" => nil
"type" => token.type
}
assert flow = Fixtures.Flows.create_flow(account: account, token: token)

View File

@@ -1306,7 +1306,7 @@ defmodule Domain.ClientsTest do
Fixtures.Clients.create_client(actor: actor)
query =
Clients.Client.Query.not_deleted()
Clients.Client.Query.all()
|> Clients.Client.Query.by_actor_id(actor.id)
assert Repo.aggregate(query, :count) == 3

View File

@@ -409,7 +409,7 @@ defmodule Domain.GatewaysTest do
assert {:ok, _group} = delete_group(group, subject)
assert Resources.Resource.Query.not_deleted()
assert Resources.Resource.Query.all()
|> Resources.Resource.Query.by_gateway_group_id(group.id)
|> Repo.aggregate(:count) == 0
end

View File

@@ -81,81 +81,6 @@ defmodule Domain.PoliciesTest do
end
end
describe "fetch_policy_by_id_or_persistent_id/3" do
test "returns error when policy does not exist", %{subject: subject} do
assert fetch_policy_by_id_or_persistent_id(Ecto.UUID.generate(), subject) ==
{:error, :not_found}
end
test "returns error when UUID is invalid", %{subject: subject} do
assert fetch_policy_by_id_or_persistent_id("foo", subject) == {:error, :not_found}
end
test "returns policy when policy exists", %{account: account, subject: subject} do
policy = Fixtures.Policies.create_policy(account: account)
assert {:ok, fetched_policy} = fetch_policy_by_id_or_persistent_id(policy.id, subject)
assert fetched_policy.id == policy.id
assert {:ok, fetched_policy} =
fetch_policy_by_id_or_persistent_id(policy.persistent_id, subject)
assert fetched_policy.id == policy.id
end
test "does not return deleted policies", %{account: account, subject: subject} do
policy = Fixtures.Policies.create_policy(account: account)
delete_policy(policy, subject)
assert {:error, :not_found} = fetch_policy_by_id_or_persistent_id(policy.id, subject)
end
test "does not return policies in other accounts", %{subject: subject} do
policy = Fixtures.Policies.create_policy()
assert fetch_policy_by_id_or_persistent_id(policy.id, subject) == {:error, :not_found}
assert fetch_policy_by_id_or_persistent_id(policy.persistent_id, subject) ==
{:error, :not_found}
end
test "returns error when subject has no permission to view policies", %{subject: subject} do
subject = Fixtures.Auth.remove_permissions(subject)
assert fetch_policy_by_id_or_persistent_id(Ecto.UUID.generate(), subject) ==
{:error,
{:unauthorized,
reason: :missing_permissions,
missing_permissions: [
{:one_of,
[
Policies.Authorizer.manage_policies_permission(),
Policies.Authorizer.view_available_policies_permission()
]}
]}}
end
# TODO: add a test that soft-deleted assocs are not preloaded
test "associations are preloaded when opts given", %{account: account, subject: subject} do
policy = Fixtures.Policies.create_policy(account: account)
{:ok, policy} =
fetch_policy_by_id_or_persistent_id(policy.id, subject,
preload: [:actor_group, :resource]
)
assert Ecto.assoc_loaded?(policy.actor_group)
assert Ecto.assoc_loaded?(policy.resource)
{:ok, policy} =
fetch_policy_by_id_or_persistent_id(policy.persistent_id, subject,
preload: [:actor_group, :resource]
)
assert Ecto.assoc_loaded?(policy.actor_group)
assert Ecto.assoc_loaded?(policy.resource)
end
end
describe "list_policies/2" do
test "returns empty list when there are no policies", %{subject: subject} do
assert {:ok, [], _metadata} = list_policies(subject)
@@ -754,139 +679,6 @@ defmodule Domain.PoliciesTest do
end
end
describe "delete_policies_for/1" do
setup %{resource: resource, actor_group: actor_group, account: account, subject: subject} do
policy =
Fixtures.Policies.create_policy(
account: account,
resource: resource,
actor_group: actor_group,
subject: subject
)
%{
resource: resource,
actor_group: actor_group,
policy: policy
}
end
test "deletes policies for actor group provider", %{
actor_group: actor_group,
policy: policy
} do
assert {:ok, _count} = delete_policies_for(actor_group)
refute Repo.get(Policies.Policy, policy.id)
end
end
describe "delete_policies_for/2" do
setup %{account: account, subject: subject} do
resource = Fixtures.Resources.create_resource(account: account)
actor_group = Fixtures.Actors.create_group(account: account)
policy =
Fixtures.Policies.create_policy(
account: account,
resource: resource,
actor_group: actor_group,
subject: subject
)
%{
resource: resource,
actor_group: actor_group,
policy: policy
}
end
test "deletes policies for actor group", %{
account: account,
policy: policy,
actor_group: actor_group,
subject: subject
} do
other_policy = Fixtures.Policies.create_policy(account: account, subject: subject)
assert {:ok, 1} = delete_policies_for(actor_group, subject)
refute Repo.get(Policies.Policy, policy.id)
assert is_nil(Repo.get(Policies.Policy, other_policy.id).deleted_at)
end
test "deletes policies for actor group provider", %{
account: account,
resource: resource,
policy: other_policy,
subject: subject
} do
Domain.Config.put_env_override(:outbound_email_adapter_configured?, true)
provider = Fixtures.Auth.create_email_provider(account: account)
actor_group = Fixtures.Actors.create_group(account: account, provider: provider)
policy =
Fixtures.Policies.create_policy(
account: account,
resource: resource,
actor_group: actor_group,
subject: subject
)
assert {:ok, 1} = delete_policies_for(provider, subject)
refute Repo.get(Policies.Policy, policy.id)
assert is_nil(Repo.get(Policies.Policy, other_policy.id).deleted_at)
end
test "deletes policies for resource", %{
account: account,
policy: policy,
resource: resource,
subject: subject
} do
other_policy = Fixtures.Policies.create_policy(account: account, subject: subject)
assert {:ok, _deleted_policy} = delete_policies_for(resource, subject)
refute Repo.get(Policies.Policy, policy.id)
assert is_nil(Repo.get(Policies.Policy, other_policy.id).deleted_at)
end
test "returns error when subject has no permission to delete policies", %{
resource: resource,
subject: subject
} do
subject = Fixtures.Auth.remove_permissions(subject)
assert delete_policies_for(resource, subject) ==
{:error,
{:unauthorized,
[
reason: :missing_permissions,
missing_permissions: [
%Domain.Auth.Permission{resource: Domain.Policies.Policy, action: :manage}
]
]}}
end
test "does not do anything on state conflict", %{
resource: resource,
actor_group: actor_group,
subject: subject
} do
assert {:ok, _count} = delete_policies_for(resource, subject)
assert delete_policies_for(actor_group, subject) == {:ok, 0}
assert delete_policies_for(resource, subject) == {:ok, 0}
end
test "does not delete policies outside of account", %{
resource: resource
} do
subject = Fixtures.Auth.create_subject()
assert delete_policies_for(resource, subject) == {:ok, 0}
end
end
describe "filter_by_conforming_policies_for_client/2" do
test "returns empty list when there are no policies", %{} do
client = Fixtures.Clients.create_client()

View File

@@ -363,7 +363,6 @@ defmodule Domain.RelaysTest do
assert delete_group(group, subject) == {:error, :unauthorized}
end
# TODO: HARD-DELETE - This test should be moved to Tokens since it holds the FK
test "deletes all tokens when group is deleted", %{account: account, subject: subject} do
group = Fixtures.Relays.create_group(account: account)
Fixtures.Relays.create_token(account: account, group: group)
@@ -380,7 +379,6 @@ defmodule Domain.RelaysTest do
assert length(tokens) == 0
end
# TODO: HARD-DELETE - This test should be moved to Tokens since it holds the FK
test "deletes all relays when group is deleted", %{account: account, subject: subject} do
group = Fixtures.Relays.create_group(account: account)
Fixtures.Relays.create_relay(account: account, group: group)
@@ -395,7 +393,6 @@ defmodule Domain.RelaysTest do
assert length(relays) == 0
end
# TODO: HARD-DELETE - This test should be moved to Tokens since it holds the FK
test "deletes associated tokens", %{
account: account,
subject: subject

View File

@@ -29,7 +29,7 @@ defmodule Domain.Repo.FilterTest do
type: :boolean,
name: :bool,
fun: fn queryable ->
{queryable, dynamic([accounts: accounts], is_nil(accounts.deleted_at))}
{queryable, dynamic([accounts: accounts], is_nil(accounts.disabled_at))}
end
}
}
@@ -41,7 +41,7 @@ defmodule Domain.Repo.FilterTest do
|> inspect() == """
#Ecto.Query<from a0 in Domain.Accounts.Account,\
as: :accounts,\
where: is_nil(a0.deleted_at)>\
where: is_nil(a0.disabled_at)>\
"""
assert {queryable, dynamic} = build_dynamic(queryable, [bool: false], filters, nil)
@@ -51,7 +51,7 @@ defmodule Domain.Repo.FilterTest do
|> inspect() == """
#Ecto.Query<from a0 in Domain.Accounts.Account,\
as: :accounts,\
where: not is_nil(a0.deleted_at)>\
where: not is_nil(a0.disabled_at)>\
"""
end

View File

@@ -269,26 +269,27 @@ defmodule Domain.RepoTest do
end
end
test "allows to set custom order", %{
account: account,
query_module: query_module,
queryable: queryable
} do
dt1 = ~U[2000-01-01 00:00:00.000000Z]
dt2 = ~U[2000-01-02 00:00:00.000000Z]
# TODO: BRIAN - Check if this test can be updated to use something other than deleted_at
# test "allows to set custom order", %{
# account: account,
# query_module: query_module,
# queryable: queryable
# } do
# dt1 = ~U[2000-01-01 00:00:00.000000Z]
# dt2 = ~U[2000-01-02 00:00:00.000000Z]
Fixtures.Actors.create_actor(account: account)
|> Fixtures.Actors.update(deleted_at: dt1)
# Fixtures.Actors.create_actor(account: account)
# |> Fixtures.Actors.update(deleted_at: dt1)
Fixtures.Actors.create_actor(account: account)
|> Fixtures.Actors.update(deleted_at: dt2)
# Fixtures.Actors.create_actor(account: account)
# |> Fixtures.Actors.update(deleted_at: dt2)
assert {:ok, [%{deleted_at: ^dt1}, %{deleted_at: ^dt2}], _metadata} =
list(queryable, query_module, order_by: [{:actors, :asc, :deleted_at}])
# assert {:ok, [%{deleted_at: ^dt1}, %{deleted_at: ^dt2}], _metadata} =
# list(queryable, query_module, order_by: [{:actors, :asc, :deleted_at}])
assert {:ok, [%{deleted_at: ^dt2}, %{deleted_at: ^dt1}], _metadata} =
list(queryable, query_module, order_by: [{:actors, :desc, :deleted_at}])
end
# assert {:ok, [%{deleted_at: ^dt2}, %{deleted_at: ^dt1}], _metadata} =
# list(queryable, query_module, order_by: [{:actors, :desc, :deleted_at}])
# end
test "allows to filter results" do
query_module = Domain.Accounts.Account.Query

View File

@@ -103,122 +103,6 @@ defmodule Domain.ResourcesTest do
end
end
describe "fetch_resource_by_id_or_persistent_id/3" do
test "returns error when resource does not exist", %{subject: subject} do
assert fetch_resource_by_id_or_persistent_id(Ecto.UUID.generate(), subject) ==
{:error, :not_found}
end
test "returns error when UUID is invalid", %{subject: subject} do
assert fetch_resource_by_id_or_persistent_id("foo", subject) == {:error, :not_found}
end
test "returns resource for account admin", %{account: account, subject: subject} do
resource = Fixtures.Resources.create_resource(account: account)
assert {:ok, fetched_resource} = fetch_resource_by_id_or_persistent_id(resource.id, subject)
assert fetched_resource.id == resource.id
assert {:ok, fetched_resource} =
fetch_resource_by_id_or_persistent_id(resource.persistent_id, subject)
assert fetched_resource.id == resource.id
end
test "returns authorized resource for account user", %{
account: account
} do
actor_group = Fixtures.Actors.create_group(account: account)
actor = Fixtures.Actors.create_actor(type: :account_user, account: account)
Fixtures.Actors.create_membership(account: account, actor: actor, group: actor_group)
identity = Fixtures.Auth.create_identity(account: account, actor: actor)
subject = Fixtures.Auth.create_subject(identity: identity)
resource = Fixtures.Resources.create_resource(account: account)
assert fetch_resource_by_id_or_persistent_id(resource.id, subject) == {:error, :not_found}
assert fetch_resource_by_id_or_persistent_id(resource.persistent_id, subject) ==
{:error, :not_found}
policy =
Fixtures.Policies.create_policy(
account: account,
actor_group: actor_group,
resource: resource
)
assert {:ok, fetched_resource} = fetch_resource_by_id_or_persistent_id(resource.id, subject)
assert fetched_resource.id == resource.id
assert Enum.map(fetched_resource.authorized_by_policies, & &1.id) == [policy.id]
assert {:ok, fetched_resource} =
fetch_resource_by_id_or_persistent_id(resource.persistent_id, subject)
assert fetched_resource.id == resource.id
assert Enum.map(fetched_resource.authorized_by_policies, & &1.id) == [policy.id]
end
test "does not return deleted resources", %{account: account, subject: subject} do
resource = Fixtures.Resources.create_resource(account: account)
delete_resource(resource, subject)
assert {:error, :not_found} = fetch_resource_by_id_or_persistent_id(resource.id, subject)
assert {:error, :not_found} =
fetch_resource_by_id_or_persistent_id(resource.persistent_id, subject)
end
test "does not return resources in other accounts", %{subject: subject} do
resource = Fixtures.Resources.create_resource()
assert fetch_resource_by_id_or_persistent_id(resource.id, subject) == {:error, :not_found}
assert fetch_resource_by_id_or_persistent_id(resource.persistent_id, subject) ==
{:error, :not_found}
end
test "returns error when subject has no permission to view resources", %{subject: subject} do
subject = Fixtures.Auth.remove_permissions(subject)
assert fetch_resource_by_id_or_persistent_id(Ecto.UUID.generate(), subject) ==
{:error,
{:unauthorized,
reason: :missing_permissions,
missing_permissions: [
{:one_of,
[
Resources.Authorizer.manage_resources_permission(),
Resources.Authorizer.view_available_resources_permission()
]}
]}}
end
test "associations are preloaded when opts given", %{account: account, subject: subject} do
gateway_group = Fixtures.Gateways.create_group(account: account)
resource =
Fixtures.Resources.create_resource(
account: account,
connections: [%{gateway_group_id: gateway_group.id}]
)
assert {:ok, resource} =
fetch_resource_by_id_or_persistent_id(resource.id, subject, preload: :connections)
assert Ecto.assoc_loaded?(resource.connections)
assert length(resource.connections) == 1
assert {:ok, resource} =
fetch_resource_by_id_or_persistent_id(resource.persistent_id, subject,
preload: :connections
)
assert Ecto.assoc_loaded?(resource.connections)
assert length(resource.connections) == 1
end
end
describe "all_authorized_resources/1" do
test "returns empty list when there are no resources", %{subject: subject} do
assert {:ok, []} = all_authorized_resources(subject)
@@ -232,33 +116,6 @@ defmodule Domain.ResourcesTest do
assert {:ok, []} = all_authorized_resources(subject)
end
test "does not list deleted resources", %{
account: account,
actor: actor,
subject: subject
} do
gateway_group = Fixtures.Gateways.create_group(account: account)
resource =
Fixtures.Resources.create_resource(
account: account,
connections: [%{gateway_group_id: gateway_group.id}]
)
actor_group = Fixtures.Actors.create_group(account: account)
Fixtures.Actors.create_membership(account: account, actor: actor, group: actor_group)
Fixtures.Policies.create_policy(
account: account,
actor_group: actor_group,
resource: resource
)
resource |> Ecto.Changeset.change(deleted_at: DateTime.utc_now()) |> Repo.update!()
assert {:ok, []} = all_authorized_resources(subject)
end
test "does not list resources authorized by disabled policy", %{
account: account,
actor: actor,
@@ -1462,49 +1319,6 @@ defmodule Domain.ResourcesTest do
end
end
describe "delete_connections_for/2" do
setup %{account: account, subject: subject} do
group = Fixtures.Gateways.create_group(account: account, subject: subject)
resource =
Fixtures.Resources.create_resource(
account: account,
connections: [%{gateway_group_id: group.id}]
)
%{
group: group,
resource: resource
}
end
test "does nothing on state conflict", %{
group: group,
subject: subject
} do
assert delete_connections_for(group, subject) == {:ok, 1}
assert delete_connections_for(group, subject) == {:ok, 0}
end
test "deletes connections for actor group", %{group: group, subject: subject} do
assert delete_connections_for(group, subject) == {:ok, 1}
assert Repo.aggregate(Resources.Connection.Query.by_gateway_group_id(group.id), :count) == 0
end
test "returns error when subject has no permission to manage resources", %{
group: group,
subject: subject
} do
subject = Fixtures.Auth.remove_permissions(subject)
assert delete_connections_for(group, subject) ==
{:error,
{:unauthorized,
reason: :missing_permissions,
missing_permissions: [Resources.Authorizer.manage_resources_permission()]}}
end
end
describe "adapt_resource_for_version/2" do
setup do
account = Fixtures.Accounts.create_account()

View File

@@ -424,7 +424,7 @@ defmodule Domain.TokensTest do
end
test "does not extend expiration of deleted tokens", %{token: token} do
token = Fixtures.Tokens.delete_token(token)
{:ok, token} = Fixtures.Tokens.delete_token(token)
assert update_token(token, %{}) == {:error, :not_found}
end
end
@@ -462,7 +462,7 @@ defmodule Domain.TokensTest do
]
]}}
refute Repo.get(Tokens.Token, token.id).deleted_at
assert Repo.get(Tokens.Token, token.id)
end
test "does not delete tokens that belong to other accounts", %{
@@ -531,22 +531,21 @@ defmodule Domain.TokensTest do
assert {:ok, _token} = delete_token_for(subject)
refute Repo.get(Tokens.Token, token.id).deleted_at
assert Repo.get(Tokens.Token, token.id)
end
end
## TODO: HARD-DELETE - This test should not be relevant after soft deletion is removed
# describe "delete_tokens_for/1" do
# test "deletes browser tokens for given identity", %{account: account} do
# actor = Fixtures.Actors.create_actor(account: account)
# identity = Fixtures.Auth.create_identity(account: account, actor: actor)
# token = Fixtures.Tokens.create_token(type: :browser, account: account, identity: identity)
describe "delete_tokens_for/1" do
test "deletes browser tokens for given identity", %{account: account} do
actor = Fixtures.Actors.create_actor(account: account)
identity = Fixtures.Auth.create_identity(account: account, actor: actor)
token = Fixtures.Tokens.create_token(type: :browser, account: account, identity: identity)
# assert {:ok, _num_tokens} = delete_tokens_for(identity)
assert {:ok, _num_tokens} = delete_tokens_for(identity)
# refute Repo.get(Tokens.Token, token.id)
# end
# end
refute Repo.get(Tokens.Token, token.id)
end
end
describe "delete_tokens_for/2" do
test "deletes browser tokens for given actor", %{account: account, subject: subject} do

View File

@@ -41,7 +41,7 @@ defmodule Domain.Fixtures.Accounts do
end
def delete_account(%Accounts.Account{} = account) do
update_account(account, deleted_at: DateTime.utc_now())
Repo.delete(account)
end
def disable_account(%Accounts.Account{} = account) do

View File

@@ -76,11 +76,6 @@ defmodule Domain.Fixtures.Actors do
group
end
# TODO: HARD-DELETE - Remove after soft deletion functionality is removed
def soft_delete_group(group) do
update!(group, %{deleted_at: DateTime.utc_now()})
end
def actor_attrs(attrs \\ %{}) do
first_name = Enum.random(~w[Wade Dave Seth Riley Gilbert Jorge Dan Brian Roberto Ramon Juan])
last_name = Enum.random(~w[Robyn Traci Desiree Jon Bob Karl Joe Alberta Lynda Cara Brandi B])
@@ -166,8 +161,4 @@ defmodule Domain.Fixtures.Actors do
{:ok, deleted_actor} = Domain.Actors.delete_actor(actor, subject)
deleted_actor
end
def soft_delete(actor) do
update!(actor, %{deleted_at: DateTime.utc_now()})
end
end

View File

@@ -376,7 +376,8 @@ defmodule Domain.Fixtures.Auth do
end
def delete_provider(provider) do
update!(provider, deleted_at: DateTime.utc_now())
{:ok, deleted_provider} = Repo.delete(provider)
deleted_provider
end
def fail_provider_sync(provider) do
@@ -460,7 +461,7 @@ defmodule Domain.Fixtures.Auth do
end
def delete_identity(identity) do
update!(identity, deleted_at: DateTime.utc_now())
Repo.delete!(identity)
end
def build_context(attrs \\ %{}) do

View File

@@ -1,6 +1,5 @@
defmodule Domain.Fixtures.Tokens do
use Domain.Fixture
alias Domain.Tokens
def remote_ip, do: Enum.random([unique_ipv4(), unique_ipv6()])
def user_agent, do: "iOS/12.5 (iPhone; #{unique_integer()}) connlib/0.7.412"
@@ -198,9 +197,7 @@ defmodule Domain.Fixtures.Tokens do
end
def delete_token(token) do
token
|> Tokens.Token.Changeset.delete()
|> Domain.Repo.update!()
Domain.Repo.delete(token)
end
def expire_token(token) do

View File

@@ -29,10 +29,7 @@ defmodule Web.Actors.Components do
<span :if={Actors.actor_disabled?(@actor)} class="text-red-800">
Disabled
</span>
<span :if={Actors.actor_deleted?(@actor)} class="text-red-800">
Deleted
</span>
<span :if={not Actors.actor_disabled?(@actor) and not Actors.actor_deleted?(@actor)}>
<span :if={not Actors.actor_disabled?(@actor)}>
Active
</span>
"""

View File

@@ -5,12 +5,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],
filter: [
deleted?: false
]
) do
Actors.fetch_actor_by_id(id, socket.assigns.subject, preload: [:memberships]) do
changeset = Actors.change_actor(actor)
socket =

View File

@@ -4,12 +4,7 @@ defmodule Web.Actors.EditGroups do
def mount(%{"id" => id}, _session, socket) do
with {:ok, actor} <-
Actors.fetch_actor_by_id(id, socket.assigns.subject,
preload: [:memberships],
filter: [
deleted?: false
]
) do
Actors.fetch_actor_by_id(id, socket.assigns.subject, preload: [:memberships]) do
current_group_ids = Enum.map(actor.memberships, & &1.group_id)
socket =

View File

@@ -7,7 +7,6 @@ defmodule Web.Actors.ServiceAccounts.NewIdentity do
Actors.fetch_actor_by_id(id, socket.assigns.subject,
preload: [:memberships],
filter: [
deleted?: false,
types: ["service_account"]
]
) do

View File

@@ -159,15 +159,14 @@ defmodule Web.Actors.Show do
<:title>
{actor_type(@actor.type)}: <span class="font-medium">{@actor.name}</span>
<span :if={@actor.id == @subject.actor.id} class="text-sm text-neutral-400">(you)</span>
<span :if={Actors.actor_deleted?(@actor)} class="text-red-600">(deleted)</span>
<span :if={Actors.actor_disabled?(@actor)} class="text-red-600">(disabled)</span>
</:title>
<:action :if={is_nil(@actor.deleted_at)}>
<:action>
<.edit_button navigate={~p"/#{@account}/actors/#{@actor}/edit"}>
Edit {actor_type(@actor.type)}
</.edit_button>
</:action>
<:action :if={is_nil(@actor.deleted_at) and not Actors.actor_disabled?(@actor)}>
<:action :if={not Actors.actor_disabled?(@actor)}>
<.button_with_confirmation
id="disable_actor"
style="warning"
@@ -188,7 +187,7 @@ defmodule Web.Actors.Show do
Disable {actor_type(@actor.type)}
</.button_with_confirmation>
</:action>
<:action :if={is_nil(@actor.deleted_at) and Actors.actor_disabled?(@actor)}>
<:action :if={Actors.actor_disabled?(@actor)}>
<.button_with_confirmation
id="enable_actor"
style="warning"
@@ -244,7 +243,7 @@ defmodule Web.Actors.Show do
Each authentication identity is associated with an identity provider and is used to identify the actor upon successful authentication.
</:help>
<:action :if={is_nil(@actor.deleted_at) and Enum.any?(@available_providers)}>
<:action :if={Enum.any?(@available_providers)}>
<.add_button
:if={@actor.type != :service_account}
navigate={~p"/#{@account}/actors/users/#{@actor}/new_identity"}
@@ -316,7 +315,7 @@ defmodule Web.Actors.Show do
<div class="flex justify-center text-center text-neutral-500 p-4">
<div class="w-auto pb-4">
No authentication identities to display.
<span :if={is_nil(@actor.deleted_at) and @actor.type == :service_account}>
<span :if={@actor.type == :service_account}>
<.link
class={[link_style()]}
navigate={~p"/#{@account}/actors/service_accounts/#{@actor}/new_identity"}
@@ -325,7 +324,7 @@ defmodule Web.Actors.Show do
</.link>
to authenticate this service account.
</span>
<span :if={is_nil(@actor.deleted_at) and @actor.type != :service_account}>
<span :if={@actor.type != :service_account}>
<.link
class={[link_style()]}
navigate={~p"/#{@account}/actors/users/#{@actor}/new_identity"}
@@ -347,7 +346,7 @@ defmodule Web.Actors.Show do
Authentication tokens are used to authenticate the actor. Revoke tokens to sign the actor out of all associated client sessions.
</:help>
<:action :if={is_nil(@actor.deleted_at) and @actor.type == :service_account}>
<:action :if={@actor.type == :service_account}>
<.add_button
:if={@actor.type == :service_account}
navigate={~p"/#{@account}/actors/service_accounts/#{@actor}/new_identity"}
@@ -356,7 +355,7 @@ defmodule Web.Actors.Show do
</.add_button>
</:action>
<:action :if={is_nil(@actor.deleted_at)}>
<:action>
<.button_with_confirmation
id="revoke_all_tokens"
style="danger"
@@ -581,7 +580,7 @@ defmodule Web.Actors.Show do
</:content>
</.section>
<.danger_zone :if={is_nil(@actor.deleted_at)}>
<.danger_zone>
<:action :if={not Actors.actor_synced?(@actor) or @identities == []}>
<.button_with_confirmation
id="delete_actor"

View File

@@ -8,7 +8,6 @@ defmodule Web.Actors.Users.NewIdentity do
Actors.fetch_actor_by_id(id, socket.assigns.subject,
preload: [:memberships, :identities],
filter: [
deleted?: false,
types: ["account_user", "account_admin_user"]
]
) do

View File

@@ -3,8 +3,7 @@ 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),
nil <- client.deleted_at do
with {:ok, client} <- Clients.fetch_client_by_id(id, socket.assigns.subject) do
changeset = Clients.change_client(client)
socket =

View File

@@ -73,10 +73,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 :if={is_nil(@client.deleted_at)}>
<:action>
<.edit_button navigate={~p"/#{@account}/clients/#{@client}/edit"}>
Edit Client
</.edit_button>
@@ -182,7 +181,7 @@ defmodule Web.Clients.Show do
Information about the device that the Client is running on.
</:help>
<:action :if={is_nil(@client.deleted_at) and not is_nil(@client.verified_at)}>
<:action :if={not is_nil(@client.verified_at)}>
<.button_with_confirmation
id="remove_client_verification"
style="danger"
@@ -203,7 +202,7 @@ defmodule Web.Clients.Show do
Remove verification
</.button_with_confirmation>
</:action>
<:action :if={is_nil(@client.deleted_at) and is_nil(@client.verified_at)}>
<:action :if={is_nil(@client.verified_at)}>
<.button_with_confirmation
id="verify_client"
style="warning"
@@ -331,7 +330,7 @@ defmodule Web.Clients.Show do
</:content>
</.section>
<.danger_zone :if={is_nil(@client.deleted_at)}>
<.danger_zone>
<:action>
<.button_with_confirmation
id="delete_client"

View File

@@ -39,7 +39,6 @@ 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">
@@ -106,7 +105,7 @@ defmodule Web.Gateways.Show do
</:content>
</.section>
<.danger_zone :if={is_nil(@gateway.deleted_at)}>
<.danger_zone>
<:action>
<.button_with_confirmation
id="delete_gateway"

View File

@@ -7,7 +7,6 @@ defmodule Web.Groups.Edit do
Actors.fetch_group_by_id(id, socket.assigns.subject,
preload: [:memberships],
filter: [
deleted?: false,
editable?: true
]
) do

View File

@@ -8,7 +8,6 @@ defmodule Web.Groups.EditActors do
Actors.fetch_group_by_id(id, socket.assigns.subject,
preload: [:memberships],
filter: [
deleted?: false,
editable?: true
]
) do

View File

@@ -75,10 +75,6 @@ defmodule Web.Groups.Index do
>
<:col :let={group} field={{:groups, :name}} label="name" class="w-3/12">
<.group account={@account} group={group} />
<span :if={Actors.group_soft_deleted?(group)} class="text-xs text-neutral-100">
(deleted)
</span>
</:col>
<:col :let={group} label="actors">
<.peek peek={Map.fetch!(@group_actors, group.id)}>

Some files were not shown because too many files have changed in this diff Show More