From 6bd19ee9b0b48def81613c1f37192e396d6a2ae0 Mon Sep 17 00:00:00 2001 From: Brian Manifold Date: Fri, 29 Aug 2025 15:13:44 -0700 Subject: [PATCH] refactor(portal): hard delete data (#9694) --- elixir/apps/api/lib/api/client/channel.ex | 2 +- .../lib/api/controllers/gateway_controller.ex | 2 +- .../controllers/gateway_group_controller.ex | 4 +- .../lib/api/controllers/gateway_group_json.ex | 4 +- .../identity_provider_controller.ex | 3 +- elixir/apps/api/lib/api/gateway/channel.ex | 2 +- elixir/apps/api/lib/api/gateway/socket.ex | 2 +- elixir/apps/api/lib/api/relay/channel.ex | 2 +- elixir/apps/api/lib/api/relay/socket.ex | 2 +- .../api/schemas/gateway_group_token_schema.ex | 23 +- .../apps/api/test/api/client/channel_test.exs | 341 ++++++------- .../api/controllers/actor_controller_test.exs | 3 +- .../actor_group_controller_test.exs | 3 +- .../controllers/client_controller_test.exs | 5 +- .../controllers/gateway_controller_test.exs | 3 +- .../gateway_group_controller_test.exs | 12 +- .../controllers/identity_controller_test.exs | 3 +- .../identity_provider_controller_test.exs | 34 +- .../controllers/policy_controller_test.exs | 3 +- .../controllers/resource_controller_test.exs | 3 +- .../api/test/api/gateway/channel_test.exs | 96 +++- .../apps/api/test/api/relay/channel_test.exs | 8 +- elixir/apps/domain/lib/domain/accounts.ex | 1 + .../domain/lib/domain/accounts/account.ex | 11 + .../lib/domain/accounts/account/query.ex | 1 + elixir/apps/domain/lib/domain/actors.ex | 61 ++- elixir/apps/domain/lib/domain/actors/actor.ex | 5 + .../lib/domain/actors/actor/changeset.ex | 1 + .../domain/lib/domain/actors/actor/query.ex | 4 + .../domain/lib/domain/actors/authorizer.ex | 8 + elixir/apps/domain/lib/domain/actors/group.ex | 2 + .../lib/domain/actors/group/authorizer.ex | 53 ++ .../lib/domain/actors/group/changeset.ex | 2 + .../domain/lib/domain/actors/group/query.ex | 5 + .../domain/lib/domain/actors/group/sync.ex | 19 +- elixir/apps/domain/lib/domain/auth.ex | 97 +++- .../domain/lib/domain/auth/adapters/email.ex | 4 +- .../apps/domain/lib/domain/auth/authorizer.ex | 32 ++ .../apps/domain/lib/domain/auth/identity.ex | 3 + .../lib/domain/auth/identity/changeset.ex | 1 + .../domain/lib/domain/auth/identity/query.ex | 4 + .../domain/lib/domain/auth/identity/sync.ex | 18 +- .../apps/domain/lib/domain/auth/provider.ex | 3 + .../lib/domain/auth/provider/changeset.ex | 4 +- .../domain/lib/domain/auth/provider/query.ex | 1 + elixir/apps/domain/lib/domain/auth/roles.ex | 1 + elixir/apps/domain/lib/domain/clients.ex | 47 +- .../domain/lib/domain/clients/authorizer.ex | 17 + .../apps/domain/lib/domain/clients/client.ex | 3 +- .../lib/domain/clients/client/changeset.ex | 5 +- .../domain/lib/domain/clients/client/query.ex | 8 +- .../domain/lib/domain/clients/presence.ex | 8 +- elixir/apps/domain/lib/domain/gateways.ex | 36 +- .../domain/lib/domain/gateways/authorizer.ex | 16 + .../domain/lib/domain/gateways/gateway.ex | 3 +- .../lib/domain/gateways/gateway/changeset.ex | 10 +- .../lib/domain/gateways/gateway/query.ex | 2 + .../apps/domain/lib/domain/gateways/group.ex | 3 + .../lib/domain/gateways/group/changeset.ex | 1 + .../domain/lib/domain/gateways/group/query.ex | 2 + .../domain/lib/domain/gateways/presence.ex | 8 +- elixir/apps/domain/lib/domain/policies.ex | 127 +++-- .../domain/lib/domain/policies/authorizer.ex | 8 + .../apps/domain/lib/domain/policies/policy.ex | 2 + .../lib/domain/policies/policy/query.ex | 3 + elixir/apps/domain/lib/domain/relays.ex | 29 +- .../domain/lib/domain/relays/authorizer.ex | 18 + elixir/apps/domain/lib/domain/relays/group.ex | 2 + .../lib/domain/relays/group/changeset.ex | 1 + .../domain/lib/domain/relays/group/query.ex | 2 + elixir/apps/domain/lib/domain/relays/relay.ex | 3 +- .../lib/domain/relays/relay/changeset.ex | 9 +- .../domain/lib/domain/relays/relay/query.ex | 2 + elixir/apps/domain/lib/domain/resources.ex | 31 +- .../domain/lib/domain/resources/authorizer.ex | 8 + .../domain/lib/domain/resources/resource.ex | 2 + .../domain/resources/resource/changeset.ex | 1 + .../lib/domain/resources/resource/query.ex | 2 + elixir/apps/domain/lib/domain/tokens.ex | 158 +++++- .../domain/lib/domain/tokens/authorizer.ex | 20 + elixir/apps/domain/lib/domain/tokens/token.ex | 4 +- .../lib/domain/tokens/token/changeset.ex | 1 + .../domain/lib/domain/tokens/token/query.ex | 2 + ..._drop_last_used_token_id_from_gateways.exs | 9 + ...37_drop_last_used_token_id_from_relays.exs | 9 + ...4_drop_last_used_token_id_from_clients.exs | 9 + elixir/apps/domain/priv/repo/seeds.exs | 7 - .../apps/domain/test/domain/actors_test.exs | 467 +++++++----------- .../test/domain/auth/adapters/email_test.exs | 13 +- .../jobs/sync_directory_test.exs | 3 +- .../jumpcloud/jobs/sync_directory_test.exs | 3 +- .../jobs/sync_directory_test.exs | 3 +- .../okta/jobs/sync_directory_test.exs | 3 +- elixir/apps/domain/test/domain/auth_test.exs | 443 +++++++++-------- .../apps/domain/test/domain/clients_test.exs | 132 +++-- .../apps/domain/test/domain/gateways_test.exs | 132 ++--- .../jobs/outdated_gateways_test.exs | 14 +- .../apps/domain/test/domain/policies_test.exs | 90 ++-- .../apps/domain/test/domain/relays_test.exs | 180 +++---- .../domain/test/domain/resources_test.exs | 46 +- .../apps/domain/test/domain/tokens_test.exs | 121 +++-- .../domain/test/support/fixtures/actors.ex | 18 + .../domain/test/support/fixtures/clients.ex | 2 +- .../domain/test/support/fixtures/gateways.ex | 8 +- .../domain/test/support/fixtures/policies.ex | 3 +- .../domain/test/support/fixtures/relays.ex | 5 +- .../domain/test/support/fixtures/tokens.ex | 30 ++ elixir/apps/web/lib/web/live/actors/show.ex | 24 +- elixir/apps/web/lib/web/live/clients/show.ex | 67 ++- elixir/apps/web/lib/web/live/groups/index.ex | 2 +- elixir/apps/web/lib/web/live/groups/show.ex | 2 +- elixir/apps/web/lib/web/live/policies/show.ex | 2 +- .../lib/web/live/relay_groups/new_token.ex | 8 +- .../web/lib/web/live/relay_groups/show.ex | 4 +- .../apps/web/lib/web/live/resources/show.ex | 5 +- .../lib/web/live/settings/api_clients/show.ex | 7 +- .../google_workspace/show.ex | 3 +- .../identity_providers/jumpcloud/show.ex | 3 +- .../microsoft_entra/show.ex | 3 +- .../settings/identity_providers/mock/show.ex | 3 +- .../settings/identity_providers/okta/show.ex | 3 +- .../identity_providers/openid_connect/show.ex | 7 +- .../identity_providers/system/show.ex | 3 +- elixir/apps/web/lib/web/live/sites/edit.ex | 6 +- .../apps/web/lib/web/live/sites/new_token.ex | 8 +- elixir/apps/web/lib/web/live/sites/show.ex | 7 +- .../web/acceptance/auth/userpass_test.exs | 3 +- .../web/test/web/acceptance/auth_test.exs | 9 +- elixir/apps/web/test/web/auth_test.exs | 3 +- .../web/controllers/auth_controller_test.exs | 3 +- .../web/test/web/live/actors/index_test.exs | 20 +- .../web/test/web/live/actors/show_test.exs | 45 +- .../web/test/web/live/clients/index_test.exs | 45 +- .../web/test/web/live/clients/show_test.exs | 42 +- .../web/test/web/live/gateways/show_test.exs | 16 +- .../web/test/web/live/groups/index_test.exs | 8 - .../web/test/web/live/groups/show_test.exs | 10 +- .../web/test/web/live/policies/show_test.exs | 21 +- .../test/web/live/relay_groups/index_test.exs | 19 +- .../web/live/relay_groups/new_token_test.exs | 2 +- .../test/web/live/relay_groups/show_test.exs | 22 +- .../web/test/web/live/relays/show_test.exs | 18 +- .../web/test/web/live/resources/show_test.exs | 20 +- .../live/settings/api_clients/show_test.exs | 15 +- .../google_workspace/show_test.exs | 3 +- .../jumpcloud/show_test.exs | 3 +- .../microsoft_entra/show_test.exs | 3 +- .../identity_providers/okta/show_test.exs | 2 +- .../openid_connect/show_test.exs | 4 +- .../web/test/web/live/sites/edit_test.exs | 2 +- .../web/live/sites/gateways/index_test.exs | 4 +- .../web/test/web/live/sites/index_test.exs | 6 +- .../test/web/live/sites/new_token_test.exs | 2 +- .../web/test/web/live/sites/show_test.exs | 45 +- 154 files changed, 2251 insertions(+), 1566 deletions(-) create mode 100644 elixir/apps/domain/lib/domain/actors/group/authorizer.ex create mode 100644 elixir/apps/domain/priv/repo/migrations/20250814224632_drop_last_used_token_id_from_gateways.exs create mode 100644 elixir/apps/domain/priv/repo/migrations/20250814230437_drop_last_used_token_id_from_relays.exs create mode 100644 elixir/apps/domain/priv/repo/migrations/20250815041334_drop_last_used_token_id_from_clients.exs diff --git a/elixir/apps/api/lib/api/client/channel.ex b/elixir/apps/api/lib/api/client/channel.ex index ce6cae3bc..7fb26bebf 100644 --- a/elixir/apps/api/lib/api/client/channel.ex +++ b/elixir/apps/api/lib/api/client/channel.ex @@ -61,7 +61,7 @@ defmodule API.Client.Channel do socket = Debouncer.cache_stamp_secrets(socket, relays) # Track client's presence - :ok = Clients.Presence.connect(socket.assigns.client) + :ok = Clients.Presence.connect(socket.assigns.client, socket.assigns.subject.token_id) # Subscribe to all account updates :ok = PubSub.Account.subscribe(socket.assigns.client.account_id) diff --git a/elixir/apps/api/lib/api/controllers/gateway_controller.ex b/elixir/apps/api/lib/api/controllers/gateway_controller.ex index 258b91540..b56e3ca94 100644 --- a/elixir/apps/api/lib/api/controllers/gateway_controller.ex +++ b/elixir/apps/api/lib/api/controllers/gateway_controller.ex @@ -95,7 +95,7 @@ defmodule API.GatewayController do def delete(conn, %{"id" => id}) do subject = conn.assigns.subject - with {:ok, gateway} <- Gateways.fetch_gateway_by_id(id, subject), + with {:ok, gateway} <- Gateways.fetch_gateway_by_id(id, subject, preload: :online?), {:ok, gateway} <- Gateways.delete_gateway(gateway, subject) do render(conn, :show, gateway: gateway) end diff --git a/elixir/apps/api/lib/api/controllers/gateway_group_controller.ex b/elixir/apps/api/lib/api/controllers/gateway_group_controller.ex index b1b8686f3..641398dac 100644 --- a/elixir/apps/api/lib/api/controllers/gateway_group_controller.ex +++ b/elixir/apps/api/lib/api/controllers/gateway_group_controller.ex @@ -212,8 +212,8 @@ defmodule API.GatewayGroupController do subject = conn.assigns.subject with {:ok, gateway_group} <- Gateways.fetch_group_by_id(gateway_group_id, subject), - {:ok, deleted_tokens} <- Tokens.delete_tokens_for(gateway_group, subject) do - render(conn, :deleted_tokens, %{tokens: deleted_tokens}) + {:ok, deleted_count} <- Tokens.delete_tokens_for(gateway_group, subject) do + render(conn, :deleted_tokens, %{count: deleted_count}) end end end diff --git a/elixir/apps/api/lib/api/controllers/gateway_group_json.ex b/elixir/apps/api/lib/api/controllers/gateway_group_json.ex index 50289fbe2..514d05276 100644 --- a/elixir/apps/api/lib/api/controllers/gateway_group_json.ex +++ b/elixir/apps/api/lib/api/controllers/gateway_group_json.ex @@ -45,8 +45,8 @@ defmodule API.GatewayGroupJSON do @doc """ Render all deleted Gateway Group Tokens """ - def deleted_tokens(%{tokens: tokens}) do - %{data: for(token <- tokens, do: %{id: token.id})} + def deleted_tokens(%{count: count}) do + %{data: %{deleted_count: count}} end defp data(%Gateways.Group{} = group) do diff --git a/elixir/apps/api/lib/api/controllers/identity_provider_controller.ex b/elixir/apps/api/lib/api/controllers/identity_provider_controller.ex index e0297aa08..b24a7e6fe 100644 --- a/elixir/apps/api/lib/api/controllers/identity_provider_controller.ex +++ b/elixir/apps/api/lib/api/controllers/identity_provider_controller.ex @@ -73,8 +73,7 @@ defmodule API.IdentityProviderController do def delete(conn, %{"id" => id}) do subject = conn.assigns.subject - with {:ok, identity_provider} <- Auth.fetch_provider_by_id(id, subject), - {:ok, identity_provider} <- Auth.delete_provider(identity_provider, subject) do + with {:ok, identity_provider} <- Auth.delete_provider_by_id(id, subject) do render(conn, :show, identity_provider: identity_provider) end end diff --git a/elixir/apps/api/lib/api/gateway/channel.ex b/elixir/apps/api/lib/api/gateway/channel.ex index 9f646e27f..e1d5b1e76 100644 --- a/elixir/apps/api/lib/api/gateway/channel.ex +++ b/elixir/apps/api/lib/api/gateway/channel.ex @@ -40,7 +40,7 @@ defmodule API.Gateway.Channel do Process.send_after(self(), :prune_cache, @prune_cache_every) # Track gateway's presence - :ok = Gateways.Presence.connect(socket.assigns.gateway) + :ok = Gateways.Presence.connect(socket.assigns.gateway, socket.assigns.token_id) # Subscribe to all account updates :ok = PubSub.Account.subscribe(socket.assigns.gateway.account_id) diff --git a/elixir/apps/api/lib/api/gateway/socket.ex b/elixir/apps/api/lib/api/gateway/socket.ex index bd5363684..ffc598268 100644 --- a/elixir/apps/api/lib/api/gateway/socket.ex +++ b/elixir/apps/api/lib/api/gateway/socket.ex @@ -19,7 +19,7 @@ defmodule API.Gateway.Socket do attrs = Map.take(attrs, ~w[external_id name public_key]) with {:ok, group, token} <- Gateways.authenticate(encoded_token, context), - {:ok, gateway} <- Gateways.upsert_gateway(group, token, attrs, context) do + {:ok, gateway} <- Gateways.upsert_gateway(group, attrs, context) do OpenTelemetry.Tracer.set_attributes(%{ token_id: token.id, gateway_id: gateway.id, diff --git a/elixir/apps/api/lib/api/relay/channel.ex b/elixir/apps/api/lib/api/relay/channel.ex index 7acb97ef4..d949b3127 100644 --- a/elixir/apps/api/lib/api/relay/channel.ex +++ b/elixir/apps/api/lib/api/relay/channel.ex @@ -42,7 +42,7 @@ defmodule API.Relay.Channel do OpenTelemetry.Tracer.with_span "relay.after_join" do push(socket, "init", %{}) - :ok = Relays.connect_relay(socket.assigns.relay, stamp_secret) + :ok = Relays.connect_relay(socket.assigns.relay, stamp_secret, socket.assigns.token_id) {:noreply, socket} end end diff --git a/elixir/apps/api/lib/api/relay/socket.ex b/elixir/apps/api/lib/api/relay/socket.ex index 5eb75e421..c39cc0c91 100644 --- a/elixir/apps/api/lib/api/relay/socket.ex +++ b/elixir/apps/api/lib/api/relay/socket.ex @@ -19,7 +19,7 @@ defmodule API.Relay.Socket do attrs = Map.take(attrs, ~w[ipv4 ipv6 name port]) with {:ok, group, token} <- Relays.authenticate(encoded_token, context), - {:ok, relay} <- Relays.upsert_relay(group, token, attrs, context) do + {:ok, relay} <- Relays.upsert_relay(group, attrs, context) do OpenTelemetry.Tracer.set_attributes(%{ token_id: token.id, relay_id: relay.id, diff --git a/elixir/apps/api/lib/api/schemas/gateway_group_token_schema.ex b/elixir/apps/api/lib/api/schemas/gateway_group_token_schema.ex index 7d0ba2f5c..3b3b4d72e 100644 --- a/elixir/apps/api/lib/api/schemas/gateway_group_token_schema.ex +++ b/elixir/apps/api/lib/api/schemas/gateway_group_token_schema.ex @@ -65,7 +65,6 @@ defmodule API.Schemas.GatewayGroupToken do defmodule DeletedTokens do require OpenApiSpex alias OpenApiSpex.Schema - alias API.Schemas.GatewayGroupToken OpenApiSpex.schema(%{ title: "DeletedGatewayGroupTokenListResponse", @@ -73,20 +72,20 @@ defmodule API.Schemas.GatewayGroupToken do type: :object, properties: %{ data: %Schema{ - description: "Deleted Gateway Group Tokens", - type: :array, - items: GatewayGroupToken.Schema + type: :object, + properties: %{ + deleted_count: %Schema{ + type: :integer, + description: "Number of tokens that were deleted" + } + }, + required: [:deleted_count] } }, example: %{ - "data" => [ - %{ - "id" => "42a7f82f-831a-4a9d-8f17-c66c2bb6e205" - }, - %{ - "id" => "6301d7d2-4938-4123-87de-282c01cca656" - } - ] + "data" => %{ + "deleted_count" => 5 + } } }) end diff --git a/elixir/apps/api/test/api/client/channel_test.exs b/elixir/apps/api/test/api/client/channel_test.exs index 360139460..c8fed9dad 100644 --- a/elixir/apps/api/test/api/client/channel_test.exs +++ b/elixir/apps/api/test/api/client/channel_test.exs @@ -135,6 +135,17 @@ defmodule API.Client.ChannelTest do subject = %{subject | expires_at: expires_at} + global_relay_group = Fixtures.Relays.create_global_group() + + global_relay = + Fixtures.Relays.create_relay( + group: global_relay_group, + last_seen_remote_ip_location_lat: 37, + last_seen_remote_ip_location_lon: -120 + ) + + global_relay_token = Fixtures.Relays.create_global_token(group: global_relay_group) + {:ok, _reply, socket} = API.Client.Socket |> socket("client:#{client.id}", %{ @@ -166,6 +177,9 @@ defmodule API.Client.ChannelTest do offline_resource: offline_resource, dns_resource_policy: dns_resource_policy, internet_resource_policy: internet_resource_policy, + global_relay_group: global_relay_group, + global_relay: global_relay, + global_relay_token: global_relay_token, socket: socket } end @@ -467,7 +481,8 @@ defmodule API.Client.ChannelTest do relay1 = Fixtures.Relays.create_relay(group: relay_group) stamp_secret1 = Ecto.UUID.generate() - :ok = Domain.Relays.connect_relay(relay1, stamp_secret1) + relay_token = Fixtures.Relays.create_global_token(group: relay_group) + :ok = Domain.Relays.connect_relay(relay1, stamp_secret1, relay_token.id) Fixtures.Relays.update_relay(relay1, last_seen_at: DateTime.utc_now() |> DateTime.add(-10, :second), @@ -477,7 +492,7 @@ defmodule API.Client.ChannelTest do relay2 = Fixtures.Relays.create_relay(group: relay_group) stamp_secret2 = Ecto.UUID.generate() - :ok = Domain.Relays.connect_relay(relay2, stamp_secret2) + :ok = Domain.Relays.connect_relay(relay2, stamp_secret2, relay_token.id) Fixtures.Relays.update_relay(relay2, last_seen_at: DateTime.utc_now() |> DateTime.add(-100, :second), @@ -544,7 +559,8 @@ defmodule API.Client.ChannelTest do assert_push "init", %{relays: []} - :ok = Domain.Relays.connect_relay(relay, stamp_secret) + relay_token = Fixtures.Relays.create_global_token(group: relay_group) + :ok = Domain.Relays.connect_relay(relay, stamp_secret, relay_token.id) assert_push "relays_presence", %{ @@ -570,7 +586,7 @@ defmodule API.Client.ChannelTest do last_seen_remote_ip_location_lon: -120.0 ) - :ok = Domain.Relays.connect_relay(other_relay, stamp_secret) + :ok = Domain.Relays.connect_relay(other_relay, stamp_secret, relay_token.id) other_relay_id = other_relay.id refute_push "relays_presence", @@ -589,7 +605,8 @@ defmodule API.Client.ChannelTest do stamp_secret = Ecto.UUID.generate() relay1 = Fixtures.Relays.create_relay(group: relay_group) - :ok = Domain.Relays.connect_relay(relay1, stamp_secret) + relay_token = Fixtures.Relays.create_global_token(group: relay_group) + :ok = Domain.Relays.connect_relay(relay1, stamp_secret, relay_token.id) Fixtures.Relays.update_relay(relay1, last_seen_at: DateTime.utc_now() |> DateTime.add(-10, :second), @@ -715,7 +732,8 @@ defmodule API.Client.ChannelTest do relay1 = Fixtures.Relays.create_relay(group: relay_group) stamp_secret1 = Ecto.UUID.generate() - :ok = Domain.Relays.connect_relay(relay1, stamp_secret1) + relay_token = Fixtures.Relays.create_global_token(group: relay_group) + :ok = Domain.Relays.connect_relay(relay1, stamp_secret1, relay_token.id) assert_push "relays_presence", %{ @@ -733,7 +751,7 @@ defmodule API.Client.ChannelTest do Process.sleep(1) # Reconnect with the same stamp secret - :ok = Domain.Relays.connect_relay(relay1, stamp_secret1) + :ok = Domain.Relays.connect_relay(relay1, stamp_secret1, relay_token.id) # Should not receive any disconnect relay_id = relay1.id @@ -751,7 +769,8 @@ defmodule API.Client.ChannelTest do relay1 = Fixtures.Relays.create_relay(group: relay_group) stamp_secret1 = Ecto.UUID.generate() - :ok = Domain.Relays.connect_relay(relay1, stamp_secret1) + relay_token = Fixtures.Relays.create_global_token(group: relay_group) + :ok = Domain.Relays.connect_relay(relay1, stamp_secret1, relay_token.id) assert_push "relays_presence", %{ @@ -770,7 +789,7 @@ defmodule API.Client.ChannelTest do # Reconnect with a different stamp secret stamp_secret2 = Ecto.UUID.generate() - :ok = Domain.Relays.connect_relay(relay1, stamp_secret2) + :ok = Domain.Relays.connect_relay(relay1, stamp_secret2, relay_token.id) # Should receive disconnect "immediately" assert_push "relays_presence", @@ -790,7 +809,8 @@ defmodule API.Client.ChannelTest do relay1 = Fixtures.Relays.create_relay(group: relay_group) stamp_secret1 = Ecto.UUID.generate() - :ok = Domain.Relays.connect_relay(relay1, stamp_secret1) + relay_token = Fixtures.Relays.create_global_token(group: relay_group) + :ok = Domain.Relays.connect_relay(relay1, stamp_secret1, relay_token.id) assert_push "relays_presence", %{ @@ -2023,19 +2043,12 @@ defmodule API.Client.ChannelTest do test "returns error when all gateways are offline", %{ dns_resource: resource, + global_relay: global_relay, + global_relay_token: global_relay_token, socket: socket } do - global_relay_group = Fixtures.Relays.create_global_group() - - global_relay = - Fixtures.Relays.create_relay( - group: global_relay_group, - last_seen_remote_ip_location_lat: 37, - last_seen_remote_ip_location_lon: -120 - ) - stamp_secret = Ecto.UUID.generate() - :ok = Domain.Relays.connect_relay(global_relay, stamp_secret) + :ok = Domain.Relays.connect_relay(global_relay, stamp_secret, global_relay_token.id) push(socket, "create_flow", %{ "resource_id" => resource.id, @@ -2052,8 +2065,9 @@ defmodule API.Client.ChannelTest do } do resource = Fixtures.Resources.create_resource(account: account) - gateway = Fixtures.Gateways.create_gateway(account: account) - :ok = Gateways.Presence.connect(gateway) + gateway = Fixtures.Gateways.create_gateway(account: account) |> Repo.preload(:group) + gateway_token = Fixtures.Gateways.create_token(account: account, group: gateway.group) + :ok = Gateways.Presence.connect(gateway, gateway_token.id) attrs = %{ "resource_id" => resource.id, @@ -2120,7 +2134,9 @@ defmodule API.Client.ChannelTest do "connected_gateway_ids" => [] } - :ok = Gateways.Presence.connect(gateway) + gateway = Repo.preload(gateway, :group) + gateway_token = Fixtures.Gateways.create_token(account: account, group: gateway.group) + :ok = Gateways.Presence.connect(gateway, gateway_token.id) push(socket, "create_flow", attrs) @@ -2137,8 +2153,9 @@ defmodule API.Client.ChannelTest do dns_resource: resource, socket: socket } do - gateway = Fixtures.Gateways.create_gateway(account: account) - :ok = Gateways.Presence.connect(gateway) + gateway = Fixtures.Gateways.create_gateway(account: account) |> Repo.preload(:group) + gateway_token = Fixtures.Gateways.create_token(account: account, group: gateway.group) + :ok = Gateways.Presence.connect(gateway, gateway_token.id) push(socket, "create_flow", %{ "resource_id" => resource.id, @@ -2160,26 +2177,19 @@ defmodule API.Client.ChannelTest do client: client, gateway_group_token: gateway_group_token, gateway: gateway, + global_relay: global_relay, + global_relay_token: global_relay_token, socket: socket } do - global_relay_group = Fixtures.Relays.create_global_group() - - global_relay = - Fixtures.Relays.create_relay( - group: global_relay_group, - last_seen_remote_ip_location_lat: 37, - last_seen_remote_ip_location_lon: -120 - ) - stamp_secret = Ecto.UUID.generate() - :ok = Domain.Relays.connect_relay(global_relay, stamp_secret) + :ok = Domain.Relays.connect_relay(global_relay, stamp_secret, global_relay_token.id) Fixtures.Relays.update_relay(global_relay, last_seen_at: DateTime.utc_now() |> DateTime.add(-10, :second) ) :ok = PubSub.Account.subscribe(gateway.account_id) - :ok = Gateways.Presence.connect(gateway) + :ok = Gateways.Presence.connect(gateway, gateway_group_token.id) :ok = PubSub.subscribe(Domain.Tokens.socket_id(gateway_group_token)) # Prime cache @@ -2218,6 +2228,8 @@ defmodule API.Client.ChannelTest do internet_gateway: gateway, internet_resource: resource, client: client, + global_relay: global_relay, + global_relay_token: global_relay_token, socket: socket } do Fixtures.Accounts.update_account(account, @@ -2226,17 +2238,8 @@ defmodule API.Client.ChannelTest do } ) - global_relay_group = Fixtures.Relays.create_global_group() - - global_relay = - Fixtures.Relays.create_relay( - group: global_relay_group, - last_seen_remote_ip_location_lat: 37, - last_seen_remote_ip_location_lon: -120 - ) - stamp_secret = Ecto.UUID.generate() - :ok = Domain.Relays.connect_relay(global_relay, stamp_secret) + :ok = Domain.Relays.connect_relay(global_relay, stamp_secret, global_relay_token.id) :ok = PubSub.Account.subscribe(account.id) @@ -2244,7 +2247,9 @@ defmodule API.Client.ChannelTest do last_seen_at: DateTime.utc_now() |> DateTime.add(-10, :second) ) - :ok = Gateways.Presence.connect(gateway) + gateway = Repo.preload(gateway, :group) + gateway_token = Fixtures.Gateways.create_token(account: account, group: gateway.group) + :ok = Gateways.Presence.connect(gateway, gateway_token.id) PubSub.subscribe(Domain.Tokens.socket_id(gateway_group_token)) send(socket.channel_pid, {:created, resource}) @@ -2282,19 +2287,12 @@ defmodule API.Client.ChannelTest do gateway_group_token: gateway_group_token, gateway: gateway, subject: subject, + global_relay: global_relay, + global_relay_token: global_relay_token, socket: socket } do - global_relay_group = Fixtures.Relays.create_global_group() - - global_relay = - Fixtures.Relays.create_relay( - group: global_relay_group, - last_seen_remote_ip_location_lat: 37, - last_seen_remote_ip_location_lon: -120 - ) - stamp_secret = Ecto.UUID.generate() - :ok = Domain.Relays.connect_relay(global_relay, stamp_secret) + :ok = Domain.Relays.connect_relay(global_relay, stamp_secret, global_relay_token.id) :ok = PubSub.Account.subscribe(gateway.account_id) send(socket.channel_pid, {:created, resource}) @@ -2305,7 +2303,7 @@ defmodule API.Client.ChannelTest do last_seen_at: DateTime.utc_now() |> DateTime.add(-10, :second) ) - :ok = Gateways.Presence.connect(gateway) + :ok = Gateways.Presence.connect(gateway, gateway_group_token.id) PubSub.subscribe(Domain.Tokens.socket_id(gateway_group_token)) push(socket, "create_flow", %{ @@ -2383,6 +2381,8 @@ defmodule API.Client.ChannelTest do membership: membership, gateway: gateway, gateway_group_token: gateway_group_token, + global_relay: global_relay, + global_relay_token: global_relay_token, actor_group: actor_group } do actor = Fixtures.Actors.create_actor(type: :service_account, account: account) @@ -2400,23 +2400,16 @@ defmodule API.Client.ChannelTest do }) |> subscribe_and_join(API.Client.Channel, "client") - global_relay_group = Fixtures.Relays.create_global_group() - - global_relay = - Fixtures.Relays.create_relay( - group: global_relay_group, - last_seen_remote_ip_location_lat: 37, - last_seen_remote_ip_location_lon: -120 - ) - stamp_secret = Ecto.UUID.generate() - :ok = Domain.Relays.connect_relay(global_relay, stamp_secret) + :ok = Domain.Relays.connect_relay(global_relay, stamp_secret, global_relay_token.id) Fixtures.Relays.update_relay(global_relay, last_seen_at: DateTime.utc_now() |> DateTime.add(-10, :second) ) - :ok = Gateways.Presence.connect(gateway) + gateway = Repo.preload(gateway, :group) + gateway_token = Fixtures.Gateways.create_token(account: account, group: gateway.group) + :ok = Gateways.Presence.connect(gateway, gateway_token.id) PubSub.subscribe(Domain.Tokens.socket_id(gateway_group_token)) :ok = PubSub.Account.subscribe(account.id) @@ -2443,20 +2436,13 @@ defmodule API.Client.ChannelTest do membership: membership, subject: subject, client: client, + global_relay: global_relay, + global_relay_token: global_relay_token, socket: socket } do - global_relay_group = Fixtures.Relays.create_global_group() + :ok = Domain.Relays.connect_relay(global_relay, Ecto.UUID.generate(), global_relay_token.id) - relay = - Fixtures.Relays.create_relay( - group: global_relay_group, - last_seen_remote_ip_location_lat: 37, - last_seen_remote_ip_location_lon: -120 - ) - - :ok = Domain.Relays.connect_relay(relay, Ecto.UUID.generate()) - - Fixtures.Relays.update_relay(relay, + Fixtures.Relays.update_relay(global_relay, last_seen_at: DateTime.utc_now() |> DateTime.add(-10, :second) ) @@ -2472,8 +2458,10 @@ defmodule API.Client.ChannelTest do user_agent: "Linux/24.04 connlib/1.0.412" ) ) + |> Repo.preload(:group) - :ok = Gateways.Presence.connect(gateway) + gateway_token = Fixtures.Gateways.create_token(account: account, group: gateway.group) + :ok = Gateways.Presence.connect(gateway, gateway_token.id) :ok = PubSub.Account.subscribe(account.id) @@ -2525,8 +2513,10 @@ defmodule API.Client.ChannelTest do user_agent: "Linux/24.04 connlib/1.4.11" ) ) + |> Repo.preload(:group) - :ok = Gateways.Presence.connect(gateway) + gateway_token = Fixtures.Gateways.create_token(account: account, group: gateway.group) + :ok = Gateways.Presence.connect(gateway, gateway_token.id) push(socket, "create_flow", %{ "resource_id" => resource.id, @@ -2544,20 +2534,13 @@ defmodule API.Client.ChannelTest do dns_resource: resource, dns_resource_policy: policy, membership: membership, + global_relay: global_relay, + global_relay_token: global_relay_token, socket: socket } do - global_relay_group = Fixtures.Relays.create_global_group() + :ok = Domain.Relays.connect_relay(global_relay, Ecto.UUID.generate(), global_relay_token.id) - relay = - Fixtures.Relays.create_relay( - group: global_relay_group, - last_seen_remote_ip_location_lat: 37, - last_seen_remote_ip_location_lon: -120 - ) - - :ok = Domain.Relays.connect_relay(relay, Ecto.UUID.generate()) - - Fixtures.Relays.update_relay(relay, + Fixtures.Relays.update_relay(global_relay, last_seen_at: DateTime.utc_now() |> DateTime.add(-10, :second) ) @@ -2566,8 +2549,10 @@ defmodule API.Client.ChannelTest do account: account, group: gateway_group ) + |> Repo.preload(:group) - :ok = Gateways.Presence.connect(gateway1) + gateway_token = Fixtures.Gateways.create_token(account: account, group: gateway1.group) + :ok = Gateways.Presence.connect(gateway1, gateway_token.id) gateway2 = Fixtures.Gateways.create_gateway( @@ -2575,7 +2560,7 @@ defmodule API.Client.ChannelTest do group: gateway_group ) - :ok = Gateways.Presence.connect(gateway2) + :ok = Gateways.Presence.connect(gateway2, gateway_token.id) :ok = PubSub.Account.subscribe(account.id) @@ -2629,10 +2614,7 @@ defmodule API.Client.ChannelTest do assert_reply ref, :error, %{reason: :offline} end - test "returns error when all gateways are offline", %{ - dns_resource: resource, - socket: socket - } do + test "returns error when all gateways are offline", %{dns_resource: resource, socket: socket} do ref = push(socket, "prepare_connection", %{"resource_id" => resource.id}) assert_reply ref, :error, %{reason: :offline} end @@ -2643,8 +2625,9 @@ defmodule API.Client.ChannelTest do } do resource = Fixtures.Resources.create_resource(account: account) - gateway = Fixtures.Gateways.create_gateway(account: account) - :ok = Gateways.Presence.connect(gateway) + gateway = Fixtures.Gateways.create_gateway(account: account) |> Repo.preload(:group) + gateway_token = Fixtures.Gateways.create_token(account: account, group: gateway.group) + :ok = Gateways.Presence.connect(gateway, gateway_token.id) attrs = %{ "resource_id" => resource.id @@ -2659,35 +2642,32 @@ defmodule API.Client.ChannelTest do dns_resource: resource, socket: socket } do - gateway = Fixtures.Gateways.create_gateway(account: account) - :ok = Gateways.Presence.connect(gateway) + gateway = Fixtures.Gateways.create_gateway(account: account) |> Repo.preload(:group) + gateway_token = Fixtures.Gateways.create_token(account: account, group: gateway.group) + :ok = Gateways.Presence.connect(gateway, gateway_token.id) ref = push(socket, "prepare_connection", %{"resource_id" => resource.id}) assert_reply ref, :error, %{reason: :offline} end test "returns online gateway connected to the resource", %{ + account: account, dns_resource: resource, gateway: gateway, + global_relay: global_relay, + global_relay_token: global_relay_token, socket: socket } do - global_relay_group = Fixtures.Relays.create_global_group() - - global_relay = - Fixtures.Relays.create_relay( - group: global_relay_group, - last_seen_remote_ip_location_lat: 37, - last_seen_remote_ip_location_lon: -120 - ) - stamp_secret = Ecto.UUID.generate() - :ok = Domain.Relays.connect_relay(global_relay, stamp_secret) + :ok = Domain.Relays.connect_relay(global_relay, stamp_secret, global_relay_token.id) Fixtures.Relays.update_relay(global_relay, last_seen_at: DateTime.utc_now() |> DateTime.add(-10, :second) ) - :ok = Gateways.Presence.connect(gateway) + gateway = Repo.preload(gateway, :group) + gateway_token = Fixtures.Gateways.create_token(account: account, group: gateway.group) + :ok = Gateways.Presence.connect(gateway, gateway_token.id) ref = push(socket, "prepare_connection", %{"resource_id" => resource.id}) resource_id = resource.id @@ -2708,8 +2688,9 @@ defmodule API.Client.ChannelTest do internet_resource: internet_resource, socket: socket } do - gateway = Fixtures.Gateways.create_gateway(account: account) - :ok = Gateways.Presence.connect(gateway) + gateway = Fixtures.Gateways.create_gateway(account: account) |> Repo.preload(:group) + gateway_token = Fixtures.Gateways.create_token(account: account, group: gateway.group) + :ok = Gateways.Presence.connect(gateway, gateway_token.id) ref = push(socket, "prepare_connection", %{"resource_id" => dns_resource.id}) assert_reply ref, :error, %{reason: :offline} @@ -2722,12 +2703,12 @@ defmodule API.Client.ChannelTest do account: account, actor_group: actor_group, membership: membership, + global_relay: global_relay, + global_relay_token: global_relay_token, socket: socket } do - global_relay_group = Fixtures.Relays.create_global_group() - global_relay = Fixtures.Relays.create_relay(group: global_relay_group) stamp_secret = Ecto.UUID.generate() - :ok = Domain.Relays.connect_relay(global_relay, stamp_secret) + :ok = Domain.Relays.connect_relay(global_relay, stamp_secret, global_relay_token.id) Fixtures.Relays.update_relay(global_relay, last_seen_at: DateTime.utc_now() |> DateTime.add(-10, :second) @@ -2743,6 +2724,7 @@ defmodule API.Client.ChannelTest do user_agent: "iOS/12.5 (iPhone) connlib/1.1.0" } ) + |> Repo.preload(:group) resource = Fixtures.Resources.create_resource( @@ -2758,7 +2740,8 @@ defmodule API.Client.ChannelTest do resource: resource ) - :ok = Gateways.Presence.connect(gateway) + gateway_token = Fixtures.Gateways.create_token(account: account, group: gateway.group) + :ok = Gateways.Presence.connect(gateway, gateway_token.id) ref = push(socket, "prepare_connection", %{"resource_id" => resource.id}) resource_id = resource.id @@ -2773,12 +2756,14 @@ defmodule API.Client.ChannelTest do user_agent: "iOS/12.5 (iPhone) connlib/1.2.0" } ) + |> Repo.preload(:group) Fixtures.Relays.update_relay(global_relay, last_seen_at: DateTime.utc_now() |> DateTime.add(-10, :second) ) - :ok = Gateways.Presence.connect(gateway) + gateway_token = Fixtures.Gateways.create_token(account: account, group: gateway.group) + :ok = Gateways.Presence.connect(gateway, gateway_token.id) :ok = PubSub.Account.subscribe(account.id) send(socket.channel_pid, %Changes.Change{ @@ -2815,6 +2800,8 @@ defmodule API.Client.ChannelTest do account: account, internet_gateway_group: internet_gateway_group, internet_resource: resource, + global_relay: global_relay, + global_relay_token: global_relay_token, socket: socket } do account = @@ -2824,10 +2811,8 @@ defmodule API.Client.ChannelTest do } ) - global_relay_group = Fixtures.Relays.create_global_group() - global_relay = Fixtures.Relays.create_relay(group: global_relay_group) stamp_secret = Ecto.UUID.generate() - :ok = Domain.Relays.connect_relay(global_relay, stamp_secret) + :ok = Domain.Relays.connect_relay(global_relay, stamp_secret, global_relay_token.id) Fixtures.Relays.update_relay(global_relay, last_seen_at: DateTime.utc_now() |> DateTime.add(-10, :second) @@ -2841,8 +2826,10 @@ defmodule API.Client.ChannelTest do user_agent: "iOS/12.5 (iPhone) connlib/1.2.0" } ) + |> Repo.preload(:group) - :ok = Gateways.Presence.connect(gateway) + gateway_token = Fixtures.Gateways.create_token(account: account, group: gateway.group) + :ok = Gateways.Presence.connect(gateway, gateway_token.id) ref = push(socket, "prepare_connection", %{"resource_id" => resource.id}) resource_id = resource.id @@ -2857,12 +2844,14 @@ defmodule API.Client.ChannelTest do user_agent: "iOS/12.5 (iPhone) connlib/1.3.0" } ) + |> Repo.preload(:group) Fixtures.Relays.update_relay(global_relay, last_seen_at: DateTime.utc_now() |> DateTime.add(-10, :second) ) - :ok = Gateways.Presence.connect(gateway) + gateway_token = Fixtures.Gateways.create_token(account: account, group: gateway.group) + :ok = Gateways.Presence.connect(gateway, gateway_token.id) ref = push(socket, "prepare_connection", %{"resource_id" => resource.id}) @@ -2880,6 +2869,8 @@ defmodule API.Client.ChannelTest do account: account, dns_resource: resource, gateway: gateway, + global_relay: global_relay, + global_relay_token: global_relay_token, actor_group: actor_group } do actor = Fixtures.Actors.create_actor(type: :service_account, account: account) @@ -2897,22 +2888,15 @@ defmodule API.Client.ChannelTest do }) |> subscribe_and_join(API.Client.Channel, "client") - global_relay_group = Fixtures.Relays.create_global_group() + :ok = Domain.Relays.connect_relay(global_relay, Ecto.UUID.generate(), global_relay_token.id) - relay = - Fixtures.Relays.create_relay( - group: global_relay_group, - last_seen_remote_ip_location_lat: 37, - last_seen_remote_ip_location_lon: -120 - ) - - :ok = Domain.Relays.connect_relay(relay, Ecto.UUID.generate()) - - Fixtures.Relays.update_relay(relay, + Fixtures.Relays.update_relay(global_relay, last_seen_at: DateTime.utc_now() |> DateTime.add(-10, :second) ) - :ok = Gateways.Presence.connect(gateway) + gateway = Repo.preload(gateway, :group) + gateway_token = Fixtures.Gateways.create_token(account: account, group: gateway.group) + :ok = Gateways.Presence.connect(gateway, gateway_token.id) ref = push(socket, "prepare_connection", %{"resource_id" => resource.id}) @@ -2924,20 +2908,13 @@ defmodule API.Client.ChannelTest do gateway_group: gateway_group, dns_resource: resource, subject: subject, - client: client + client: client, + global_relay: global_relay, + global_relay_token: global_relay_token } do - global_relay_group = Fixtures.Relays.create_global_group() + :ok = Domain.Relays.connect_relay(global_relay, Ecto.UUID.generate(), global_relay_token.id) - relay = - Fixtures.Relays.create_relay( - group: global_relay_group, - last_seen_remote_ip_location_lat: 37, - last_seen_remote_ip_location_lon: -120 - ) - - :ok = Domain.Relays.connect_relay(relay, Ecto.UUID.generate()) - - Fixtures.Relays.update_relay(relay, + Fixtures.Relays.update_relay(global_relay, last_seen_at: DateTime.utc_now() |> DateTime.add(-10, :second) ) @@ -2953,8 +2930,10 @@ defmodule API.Client.ChannelTest do user_agent: "Linux/24.04 connlib/1.0.412" ) ) + |> Repo.preload(:group) - :ok = Gateways.Presence.connect(gateway) + gateway_token = Fixtures.Gateways.create_token(account: account, group: gateway.group) + :ok = Gateways.Presence.connect(gateway, gateway_token.id) {:ok, _reply, socket} = API.Client.Socket @@ -2978,8 +2957,10 @@ defmodule API.Client.ChannelTest do user_agent: "Linux/24.04 connlib/1.1.11" ) ) + |> Repo.preload(:group) - :ok = Gateways.Presence.connect(gateway) + gateway_token = Fixtures.Gateways.create_token(account: account, group: gateway.group) + :ok = Gateways.Presence.connect(gateway, gateway_token.id) ref = push(socket, "prepare_connection", %{"resource_id" => resource.id}) @@ -3015,8 +2996,9 @@ defmodule API.Client.ChannelTest do dns_resource: resource, socket: socket } do - gateway = Fixtures.Gateways.create_gateway(account: account) - :ok = Gateways.Presence.connect(gateway) + gateway = Fixtures.Gateways.create_gateway(account: account) |> Repo.preload(:group) + gateway_token = Fixtures.Gateways.create_token(account: account, group: gateway.group) + :ok = Gateways.Presence.connect(gateway, gateway_token.id) attrs = %{ "resource_id" => resource.id, @@ -3063,7 +3045,9 @@ defmodule API.Client.ChannelTest do "payload" => "DNS_Q" } - :ok = Gateways.Presence.connect(gateway) + gateway = Repo.preload(gateway, :group) + gateway_token = Fixtures.Gateways.create_token(account: account, group: gateway.group) + :ok = Gateways.Presence.connect(gateway, gateway_token.id) :ok = PubSub.Account.subscribe(account.id) send(socket.channel_pid, {:created, resource}) @@ -3083,8 +3067,9 @@ defmodule API.Client.ChannelTest do } do resource = Fixtures.Resources.create_resource(account: account) - gateway = Fixtures.Gateways.create_gateway(account: account) - :ok = Gateways.Presence.connect(gateway) + gateway = Fixtures.Gateways.create_gateway(account: account) |> Repo.preload(:group) + gateway_token = Fixtures.Gateways.create_token(account: account, group: gateway.group) + :ok = Gateways.Presence.connect(gateway, gateway_token.id) attrs = %{ "resource_id" => resource.id, @@ -3112,6 +3097,7 @@ defmodule API.Client.ChannelTest do end test "broadcasts allow_access to the gateways and then returns connect message", %{ + account: account, dns_resource: resource, dns_resource_policy: policy, membership: membership, @@ -3123,7 +3109,9 @@ defmodule API.Client.ChannelTest do resource_id = resource.id client_id = client.id - :ok = Gateways.Presence.connect(gateway) + gateway = Repo.preload(gateway, :group) + gateway_token = Fixtures.Gateways.create_token(account: account, group: gateway.group) + :ok = Gateways.Presence.connect(gateway, gateway_token.id) :ok = PubSub.Account.subscribe(resource.account_id) send(socket.channel_pid, %Changes.Change{ @@ -3204,7 +3192,9 @@ defmodule API.Client.ChannelTest do }) |> subscribe_and_join(API.Client.Channel, "client") - :ok = Gateways.Presence.connect(gateway) + gateway = Repo.preload(gateway, :group) + gateway_token = Fixtures.Gateways.create_token(account: account, group: gateway.group) + :ok = Gateways.Presence.connect(gateway, gateway_token.id) Phoenix.PubSub.subscribe(PubSub, Domain.Tokens.socket_id(gateway_group_token)) :ok = PubSub.Account.subscribe(account.id) @@ -3255,8 +3245,9 @@ defmodule API.Client.ChannelTest do dns_resource: resource, socket: socket } do - gateway = Fixtures.Gateways.create_gateway(account: account) - :ok = Gateways.Presence.connect(gateway) + gateway = Fixtures.Gateways.create_gateway(account: account) |> Repo.preload(:group) + gateway_token = Fixtures.Gateways.create_token(account: account, group: gateway.group) + :ok = Gateways.Presence.connect(gateway, gateway_token.id) attrs = %{ "resource_id" => resource.id, @@ -3275,8 +3266,9 @@ defmodule API.Client.ChannelTest do } do resource = Fixtures.Resources.create_resource(account: account) - gateway = Fixtures.Gateways.create_gateway(account: account) - :ok = Gateways.Presence.connect(gateway) + gateway = Fixtures.Gateways.create_gateway(account: account) |> Repo.preload(:group) + gateway_token = Fixtures.Gateways.create_token(account: account, group: gateway.group) + :ok = Gateways.Presence.connect(gateway, gateway_token.id) attrs = %{ "resource_id" => resource.id, @@ -3325,7 +3317,9 @@ defmodule API.Client.ChannelTest do "client_preshared_key" => "PSK" } - :ok = Gateways.Presence.connect(gateway) + gateway = Repo.preload(gateway, :group) + gateway_token = Fixtures.Gateways.create_token(account: account, group: gateway.group) + :ok = Gateways.Presence.connect(gateway, gateway_token.id) :ok = PubSub.Account.subscribe(account.id) @@ -3371,6 +3365,7 @@ defmodule API.Client.ChannelTest do end test "broadcasts request_connection to the gateways and then returns connect message", %{ + account: account, dns_resource: resource, gateway_group_token: gateway_group_token, gateway: gateway, @@ -3381,7 +3376,9 @@ defmodule API.Client.ChannelTest do resource_id = resource.id client_id = client.id - :ok = Gateways.Presence.connect(gateway) + gateway = Repo.preload(gateway, :group) + gateway_token = Fixtures.Gateways.create_token(account: account, group: gateway.group) + :ok = Gateways.Presence.connect(gateway, gateway_token.id) PubSub.subscribe(Domain.Tokens.socket_id(gateway_group_token)) :ok = PubSub.Account.subscribe(resource.account_id) @@ -3447,7 +3444,9 @@ defmodule API.Client.ChannelTest do }) |> subscribe_and_join(API.Client.Channel, "client") - :ok = Gateways.Presence.connect(gateway) + gateway = Repo.preload(gateway, :group) + gateway_token = Fixtures.Gateways.create_token(account: account, group: gateway.group) + :ok = Gateways.Presence.connect(gateway, gateway_token.id) Phoenix.PubSub.subscribe(PubSub, Domain.Tokens.socket_id(gateway_group_token)) :ok = PubSub.Account.subscribe(account.id) @@ -3481,6 +3480,7 @@ defmodule API.Client.ChannelTest do end test "broadcasts :ice_candidates message to all gateways", %{ + account: account, client: client, gateway_group_token: gateway_group_token, gateway: gateway, @@ -3493,7 +3493,9 @@ defmodule API.Client.ChannelTest do "gateway_ids" => [gateway.id] } - :ok = Gateways.Presence.connect(gateway) + gateway = Repo.preload(gateway, :group) + gateway_token = Fixtures.Gateways.create_token(account: account, group: gateway.group) + :ok = Gateways.Presence.connect(gateway, gateway_token.id) PubSub.subscribe(Domain.Tokens.socket_id(gateway_group_token)) :ok = PubSub.Account.subscribe(client.account_id) @@ -3523,6 +3525,7 @@ defmodule API.Client.ChannelTest do end test "broadcasts :invalidate_ice_candidates message to all gateways", %{ + account: account, client: client, gateway_group_token: gateway_group_token, gateway: gateway, @@ -3535,7 +3538,9 @@ defmodule API.Client.ChannelTest do "gateway_ids" => [gateway.id] } - :ok = Gateways.Presence.connect(gateway) + gateway = Repo.preload(gateway, :group) + gateway_token = Fixtures.Gateways.create_token(account: account, group: gateway.group) + :ok = Gateways.Presence.connect(gateway, gateway_token.id) :ok = PubSub.subscribe(Domain.Tokens.socket_id(gateway_group_token)) :ok = PubSub.Account.subscribe(client.account_id) diff --git a/elixir/apps/api/test/api/controllers/actor_controller_test.exs b/elixir/apps/api/test/api/controllers/actor_controller_test.exs index f42932b88..ffab6733a 100644 --- a/elixir/apps/api/test/api/controllers/actor_controller_test.exs +++ b/elixir/apps/api/test/api/controllers/actor_controller_test.exs @@ -281,8 +281,7 @@ defmodule API.ActorControllerTest do } } - assert actor = Repo.get(Actor, actor.id) - assert actor.deleted_at + refute Repo.get(Actor, actor.id) end end end diff --git a/elixir/apps/api/test/api/controllers/actor_group_controller_test.exs b/elixir/apps/api/test/api/controllers/actor_group_controller_test.exs index cc057afee..6182a7986 100644 --- a/elixir/apps/api/test/api/controllers/actor_group_controller_test.exs +++ b/elixir/apps/api/test/api/controllers/actor_group_controller_test.exs @@ -219,8 +219,7 @@ defmodule API.ActorGroupControllerTest do } } - assert actor_group = Repo.get(Group, actor_group.id) - assert actor_group.deleted_at + refute Repo.get(Group, actor_group.id) end end end diff --git a/elixir/apps/api/test/api/controllers/client_controller_test.exs b/elixir/apps/api/test/api/controllers/client_controller_test.exs index 6f9f157c7..be12c0ecc 100644 --- a/elixir/apps/api/test/api/controllers/client_controller_test.exs +++ b/elixir/apps/api/test/api/controllers/client_controller_test.exs @@ -278,7 +278,7 @@ defmodule API.ClientControllerTest do client.last_seen_remote_ip_location_region, "last_seen_user_agent" => client.last_seen_user_agent, "last_seen_version" => client.last_seen_version, - "online" => nil, + "online" => false, "updated_at" => client.updated_at && DateTime.to_iso8601(client.updated_at), "verified_at" => client.verified_at && DateTime.to_iso8601(client.verified_at), "verified_by" => client.verified_by, @@ -286,8 +286,7 @@ defmodule API.ClientControllerTest do } } - assert client = Repo.get(Client, client.id) - assert client.deleted_at + refute Repo.get(Client, client.id) end end end diff --git a/elixir/apps/api/test/api/controllers/gateway_controller_test.exs b/elixir/apps/api/test/api/controllers/gateway_controller_test.exs index 0732e4d0d..4183bd08b 100644 --- a/elixir/apps/api/test/api/controllers/gateway_controller_test.exs +++ b/elixir/apps/api/test/api/controllers/gateway_controller_test.exs @@ -170,8 +170,7 @@ defmodule API.GatewayControllerTest do } } - assert gateway = Repo.get(Gateway, gateway.id) - assert gateway.deleted_at + refute Repo.get(Gateway, gateway.id) end end end diff --git a/elixir/apps/api/test/api/controllers/gateway_group_controller_test.exs b/elixir/apps/api/test/api/controllers/gateway_group_controller_test.exs index d889dd963..1f4d0e5f9 100644 --- a/elixir/apps/api/test/api/controllers/gateway_group_controller_test.exs +++ b/elixir/apps/api/test/api/controllers/gateway_group_controller_test.exs @@ -219,8 +219,7 @@ defmodule API.GatewayGroupControllerTest do } } - assert gateway_group = Repo.get(Group, gateway_group.id) - assert gateway_group.deleted_at + refute Repo.get(Group, gateway_group.id) end end @@ -267,8 +266,7 @@ defmodule API.GatewayGroupControllerTest do assert %{"data" => %{"id" => _id}} = json_response(conn, 200) - assert token = Repo.get(Token, token.id) - assert token.deleted_at + refute Repo.get(Token, token.id) end end @@ -296,12 +294,10 @@ defmodule API.GatewayGroupControllerTest do |> put_req_header("content-type", "application/json") |> delete("/gateway_groups/#{gateway_group.id}/tokens") - assert %{"data" => [%{"id" => _id1}, %{"id" => _id2}, %{"id" => _id3}]} = - json_response(conn, 200) + assert %{"data" => %{"deleted_count" => 3}} = json_response(conn, 200) Enum.map(tokens, fn token -> - assert token = Repo.get(Token, token.id) - assert token.deleted_at + refute Repo.get(Token, token.id) end) end end diff --git a/elixir/apps/api/test/api/controllers/identity_controller_test.exs b/elixir/apps/api/test/api/controllers/identity_controller_test.exs index d0f9274ce..bd4a7ce00 100644 --- a/elixir/apps/api/test/api/controllers/identity_controller_test.exs +++ b/elixir/apps/api/test/api/controllers/identity_controller_test.exs @@ -306,8 +306,7 @@ defmodule API.IdentityControllerTest do } } - assert identity = Repo.get(Identity, identity.id) - assert identity.deleted_at + refute Repo.get(Identity, identity.id) end end end diff --git a/elixir/apps/api/test/api/controllers/identity_provider_controller_test.exs b/elixir/apps/api/test/api/controllers/identity_provider_controller_test.exs index 6f3b7bb3d..3e582cc12 100644 --- a/elixir/apps/api/test/api/controllers/identity_provider_controller_test.exs +++ b/elixir/apps/api/test/api/controllers/identity_provider_controller_test.exs @@ -139,8 +139,38 @@ defmodule API.IdentityProviderControllerTest do } } - assert identity_provider = Repo.get(Provider, identity_provider.id) - assert identity_provider.deleted_at + refute Repo.get(Provider, identity_provider.id) + end + + test "returns not found on invalid ID", %{conn: conn, account: account, actor: actor} do + {_identity_provider, _bypass} = + Fixtures.Auth.start_and_create_openid_connect_provider(%{account: account}) + + invalid_id = Ecto.UUID.generate() + + conn = + conn + |> authorize_conn(actor) + |> put_req_header("content-type", "application/json") + |> delete("/identity_providers/#{invalid_id}") + + assert json_response(conn, 404) == %{"error" => %{"reason" => "Not Found"}} + end + + test "returns not found on ID belonging to another account", %{conn: conn, actor: actor} do + other_account = Fixtures.Accounts.create_account() + + {identity_provider, _bypass} = + Fixtures.Auth.start_and_create_openid_connect_provider(%{account: other_account}) + + conn = + conn + |> authorize_conn(actor) + |> put_req_header("content-type", "application/json") + |> delete("/identity_providers/#{identity_provider.id}") + + assert json_response(conn, 404) == %{"error" => %{"reason" => "Not Found"}} + assert Repo.get(Domain.Auth.Provider, identity_provider.id) end end end diff --git a/elixir/apps/api/test/api/controllers/policy_controller_test.exs b/elixir/apps/api/test/api/controllers/policy_controller_test.exs index 91e3dd213..6429b5cc2 100644 --- a/elixir/apps/api/test/api/controllers/policy_controller_test.exs +++ b/elixir/apps/api/test/api/controllers/policy_controller_test.exs @@ -231,8 +231,7 @@ defmodule API.PolicyControllerTest do } } - assert policy = Repo.get(Policy, policy.id) - assert policy.deleted_at + refute Repo.get(Policy, policy.id) end end end diff --git a/elixir/apps/api/test/api/controllers/resource_controller_test.exs b/elixir/apps/api/test/api/controllers/resource_controller_test.exs index d9ec81caf..2fd1e2bff 100644 --- a/elixir/apps/api/test/api/controllers/resource_controller_test.exs +++ b/elixir/apps/api/test/api/controllers/resource_controller_test.exs @@ -270,8 +270,7 @@ defmodule API.ResourceControllerTest do } } - assert resource = Repo.get(Resource, resource.id) - assert resource.deleted_at + refute Repo.get(Resource, resource.id) end end end diff --git a/elixir/apps/api/test/api/gateway/channel_test.exs b/elixir/apps/api/test/api/gateway/channel_test.exs index 3bcb85356..f503c773f 100644 --- a/elixir/apps/api/test/api/gateway/channel_test.exs +++ b/elixir/apps/api/test/api/gateway/channel_test.exs @@ -364,7 +364,9 @@ defmodule API.Gateway.ChannelTest do client_payload = "RTC_SD_or_DNS_Q" stamp_secret = Ecto.UUID.generate() - :ok = Domain.Relays.connect_relay(relay, stamp_secret) + relay = Repo.preload(relay, :group) + relay_token = Fixtures.Relays.create_token(group: relay.group, account: account) + :ok = Domain.Relays.connect_relay(relay, stamp_secret, relay_token.id) send( socket.channel_pid, @@ -428,7 +430,9 @@ defmodule API.Gateway.ChannelTest do client_payload = "RTC_SD_or_DNS_Q" stamp_secret = Ecto.UUID.generate() - :ok = Domain.Relays.connect_relay(relay, stamp_secret) + relay = Repo.preload(relay, :group) + relay_token = Fixtures.Relays.create_token(group: relay.group, account: account) + :ok = Domain.Relays.connect_relay(relay, stamp_secret, relay_token.id) send( socket.channel_pid, @@ -570,7 +574,9 @@ defmodule API.Gateway.ChannelTest do client_payload = "RTC_SD_or_DNS_Q" stamp_secret = Ecto.UUID.generate() - :ok = Domain.Relays.connect_relay(relay, stamp_secret) + relay = Repo.preload(relay, :group) + relay_token = Fixtures.Relays.create_token(group: relay.group, account: account) + :ok = Domain.Relays.connect_relay(relay, stamp_secret, relay_token.id) flow = Fixtures.Flows.create_flow( @@ -634,7 +640,9 @@ defmodule API.Gateway.ChannelTest do client_payload = "RTC_SD_or_DNS_Q" stamp_secret = Ecto.UUID.generate() - :ok = Domain.Relays.connect_relay(relay, stamp_secret) + relay = Repo.preload(relay, :group) + relay_token = Fixtures.Relays.create_token(group: relay.group, account: account) + :ok = Domain.Relays.connect_relay(relay, stamp_secret, relay_token.id) flow = Fixtures.Flows.create_flow( @@ -796,7 +804,9 @@ defmodule API.Gateway.ChannelTest do expires_at = DateTime.utc_now() |> DateTime.add(30, :second) client_payload = "RTC_SD_or_DNS_Q" stamp_secret = Ecto.UUID.generate() - :ok = Domain.Relays.connect_relay(relay, stamp_secret) + relay = Repo.preload(relay, :group) + relay_token = Fixtures.Relays.create_token(group: relay.group, account: account) + :ok = Domain.Relays.connect_relay(relay, stamp_secret, relay_token.id) send( socket.channel_pid, @@ -1162,12 +1172,17 @@ defmodule API.Gateway.ChannelTest do } end - test "subscribes for relays presence", %{gateway: gateway, gateway_group: gateway_group} do + test "subscribes for relays presence", %{ + gateway: gateway, + gateway_group: gateway_group, + token: token + } do relay_group = Fixtures.Relays.create_global_group() stamp_secret = Ecto.UUID.generate() relay1 = Fixtures.Relays.create_relay(group: relay_group) - :ok = Domain.Relays.connect_relay(relay1, stamp_secret) + relay_token = Fixtures.Relays.create_global_token(group: relay_group) + :ok = Domain.Relays.connect_relay(relay1, stamp_secret, relay_token.id) Fixtures.Relays.update_relay(relay1, last_seen_at: DateTime.utc_now() |> DateTime.add(-10, :second), @@ -1176,7 +1191,7 @@ defmodule API.Gateway.ChannelTest do ) relay2 = Fixtures.Relays.create_relay(group: relay_group) - :ok = Domain.Relays.connect_relay(relay2, stamp_secret) + :ok = Domain.Relays.connect_relay(relay2, stamp_secret, relay_token.id) Fixtures.Relays.update_relay(relay2, last_seen_at: DateTime.utc_now() |> DateTime.add(-100, :second), @@ -1186,6 +1201,7 @@ defmodule API.Gateway.ChannelTest do API.Gateway.Socket |> socket("gateway:#{gateway.id}", %{ + token_id: token.id, gateway: gateway, gateway_group: gateway_group, opentelemetry_ctx: OpenTelemetry.Ctx.new(), @@ -1223,7 +1239,8 @@ defmodule API.Gateway.ChannelTest do test "subscribes for account relays presence if there were no relays online", %{ gateway: gateway, - gateway_group: gateway_group + gateway_group: gateway_group, + token: token } do relay_group = Fixtures.Relays.create_global_group() stamp_secret = Ecto.UUID.generate() @@ -1238,6 +1255,7 @@ defmodule API.Gateway.ChannelTest do API.Gateway.Socket |> socket("gateway:#{gateway.id}", %{ + token_id: token.id, gateway: gateway, gateway_group: gateway_group, opentelemetry_ctx: OpenTelemetry.Ctx.new(), @@ -1247,7 +1265,8 @@ defmodule API.Gateway.ChannelTest do assert_push "init", %{relays: []} - :ok = Domain.Relays.connect_relay(relay, stamp_secret) + relay_token = Fixtures.Relays.create_global_token(group: relay_group) + :ok = Domain.Relays.connect_relay(relay, stamp_secret, relay_token.id) assert_push "relays_presence", %{ @@ -1273,7 +1292,7 @@ defmodule API.Gateway.ChannelTest do last_seen_remote_ip_location_lon: -120.0 ) - :ok = Domain.Relays.connect_relay(other_relay, stamp_secret) + :ok = Domain.Relays.connect_relay(other_relay, stamp_secret, relay_token.id) other_relay_id = other_relay.id refute_push "relays_presence", @@ -1346,7 +1365,9 @@ defmodule API.Gateway.ChannelTest do client_payload = "RTC_SD" stamp_secret = Ecto.UUID.generate() - :ok = Domain.Relays.connect_relay(relay, stamp_secret) + relay = Repo.preload(relay, :group) + relay_token = Fixtures.Relays.create_global_token(group: relay.group) + :ok = Domain.Relays.connect_relay(relay, stamp_secret, relay_token.id) send( socket.channel_pid, @@ -1410,7 +1431,9 @@ defmodule API.Gateway.ChannelTest do preshared_key = "PSK" stamp_secret = Ecto.UUID.generate() - :ok = Domain.Relays.connect_relay(relay, stamp_secret) + relay = Repo.preload(relay, :group) + relay_token = Fixtures.Relays.create_token(group: relay.group, account: account) + :ok = Domain.Relays.connect_relay(relay, stamp_secret, relay_token.id) flow = Fixtures.Flows.create_flow( @@ -1696,7 +1719,9 @@ defmodule API.Gateway.ChannelTest do payload = "RTC_SD" stamp_secret = Ecto.UUID.generate() - :ok = Domain.Relays.connect_relay(relay, stamp_secret) + relay = Repo.preload(relay, :group) + relay_token = Fixtures.Relays.create_token(group: relay.group, account: account) + :ok = Domain.Relays.connect_relay(relay, stamp_secret, relay_token.id) send( socket.channel_pid, @@ -1775,7 +1800,8 @@ defmodule API.Gateway.ChannelTest do client: client, gateway: gateway, subject: subject, - socket: socket + socket: socket, + account: account } do candidates = ["foo", "bar"] @@ -1784,7 +1810,17 @@ defmodule API.Gateway.ChannelTest do "client_ids" => [client.id] } - :ok = Domain.Clients.Presence.connect(client) + client_actor = Fixtures.Actors.create_actor(account: account, type: :service_account) + client_identity = Fixtures.Auth.create_identity(account: account, actor: client_actor) + + client_token = + Fixtures.Tokens.create_client_token( + account: account, + actor: client_actor, + identity: client_identity + ) + + :ok = Domain.Clients.Presence.connect(client, client_token.id) PubSub.subscribe(Domain.Tokens.socket_id(subject.token_id)) :ok = PubSub.Account.subscribe(gateway.account_id) @@ -1818,7 +1854,8 @@ defmodule API.Gateway.ChannelTest do client: client, gateway: gateway, subject: subject, - socket: socket + socket: socket, + account: account } do candidates = ["foo", "bar"] @@ -1828,7 +1865,17 @@ defmodule API.Gateway.ChannelTest do } :ok = PubSub.Account.subscribe(gateway.account_id) - :ok = Domain.Clients.Presence.connect(client) + client_actor = Fixtures.Actors.create_actor(account: account, type: :service_account) + client_identity = Fixtures.Auth.create_identity(account: account, actor: client_actor) + + client_token = + Fixtures.Tokens.create_client_token( + account: account, + actor: client_actor, + identity: client_identity + ) + + :ok = Domain.Clients.Presence.connect(client, client_token.id) PubSub.subscribe(Domain.Tokens.socket_id(subject.token_id)) push(socket, "broadcast_invalidated_ice_candidates", attrs) @@ -1848,7 +1895,8 @@ defmodule API.Gateway.ChannelTest do relay1 = Fixtures.Relays.create_relay(group: relay_group) stamp_secret1 = Ecto.UUID.generate() - :ok = Domain.Relays.connect_relay(relay1, stamp_secret1) + relay_token = Fixtures.Relays.create_global_token(group: relay_group) + :ok = Domain.Relays.connect_relay(relay1, stamp_secret1, relay_token.id) assert_push "relays_presence", %{ @@ -1866,7 +1914,7 @@ defmodule API.Gateway.ChannelTest do Process.sleep(1) # Reconnect with the same stamp secret - :ok = Domain.Relays.connect_relay(relay1, stamp_secret1) + :ok = Domain.Relays.connect_relay(relay1, stamp_secret1, relay_token.id) # Should not receive any disconnect relay_id = relay1.id @@ -1884,7 +1932,8 @@ defmodule API.Gateway.ChannelTest do relay1 = Fixtures.Relays.create_relay(group: relay_group) stamp_secret1 = Ecto.UUID.generate() - :ok = Domain.Relays.connect_relay(relay1, stamp_secret1) + relay_token = Fixtures.Relays.create_global_token(group: relay_group) + :ok = Domain.Relays.connect_relay(relay1, stamp_secret1, relay_token.id) assert_push "relays_presence", %{ @@ -1903,7 +1952,7 @@ defmodule API.Gateway.ChannelTest do # Reconnect with a different stamp secret stamp_secret2 = Ecto.UUID.generate() - :ok = Domain.Relays.connect_relay(relay1, stamp_secret2) + :ok = Domain.Relays.connect_relay(relay1, stamp_secret2, relay_token.id) # Should receive disconnect "immediately" assert_push "relays_presence", @@ -1923,7 +1972,8 @@ defmodule API.Gateway.ChannelTest do relay1 = Fixtures.Relays.create_relay(group: relay_group) stamp_secret1 = Ecto.UUID.generate() - :ok = Domain.Relays.connect_relay(relay1, stamp_secret1) + relay_token = Fixtures.Relays.create_global_token(group: relay_group) + :ok = Domain.Relays.connect_relay(relay1, stamp_secret1, relay_token.id) assert_push "relays_presence", %{ diff --git a/elixir/apps/api/test/api/relay/channel_test.exs b/elixir/apps/api/test/api/relay/channel_test.exs index cd4dc5922..eb1d85541 100644 --- a/elixir/apps/api/test/api/relay/channel_test.exs +++ b/elixir/apps/api/test/api/relay/channel_test.exs @@ -1,15 +1,17 @@ defmodule API.Relay.ChannelTest do use API.ChannelCase, async: true - alias Domain.Relays + alias Domain.{Relays, Repo} setup do - relay = Fixtures.Relays.create_relay() + relay = Fixtures.Relays.create_relay() |> Repo.preload([:group, :account]) + token = Fixtures.Relays.create_token(group: relay.group, account: relay.account) stamp_secret = Domain.Crypto.random_token() {:ok, _, socket} = API.Relay.Socket |> socket("relay:#{relay.id}", %{ + token_id: token.id, relay: relay, opentelemetry_ctx: OpenTelemetry.Ctx.new(), opentelemetry_span_ctx: OpenTelemetry.Tracer.start_span("test") @@ -30,12 +32,14 @@ defmodule API.Relay.ChannelTest do test "tracks presence after join of an global relay" do group = Fixtures.Relays.create_global_group() relay = Fixtures.Relays.create_relay(group: group) + token = Fixtures.Relays.create_global_token(group: group) stamp_secret = Domain.Crypto.random_token() {:ok, _, _socket} = API.Relay.Socket |> socket("relay:#{relay.id}", %{ + token_id: token.id, relay: relay, opentelemetry_ctx: OpenTelemetry.Ctx.new(), opentelemetry_span_ctx: OpenTelemetry.Tracer.start_span("test") diff --git a/elixir/apps/domain/lib/domain/accounts.ex b/elixir/apps/domain/lib/domain/accounts.ex index 382934976..32b4bfd59 100644 --- a/elixir/apps/domain/lib/domain/accounts.ex +++ b/elixir/apps/domain/lib/domain/accounts.ex @@ -110,6 +110,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?(_account), do: false diff --git a/elixir/apps/domain/lib/domain/accounts/account.ex b/elixir/apps/domain/lib/domain/accounts/account.ex index d966850de..9d922049d 100644 --- a/elixir/apps/domain/lib/domain/accounts/account.ex +++ b/elixir/apps/domain/lib/domain/accounts/account.ex @@ -25,30 +25,40 @@ 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] + # 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] + # TODO: HARD-DELETE - Remove `where` after `deleted_at` is removed from the DB has_many :network_addresses, Domain.Network.Address, where: [deleted_at: nil] + # TODO: HARD-DELETE - Remove `where` after `deleted_at` is removed from the DB has_many :policies, Domain.Policies.Policy, where: [deleted_at: nil] + # TODO: HARD-DELETE - Remove `where` after `deleted_at` is removed from the DB has_many :flows, Domain.Flows.Flow, where: [deleted_at: nil] + # 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] + # TODO: HARD-DELETE - Remove `where` after `deleted_at` is removed from the DB has_many :clients, Domain.Clients.Client, where: [deleted_at: nil] + # 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] + # 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] + # TODO: HARD-DELETE - Remove `where` after `deleted_at` is removed from the DB has_many :tokens, Domain.Tokens.Token, where: [deleted_at: nil] field :warning, :string @@ -58,6 +68,7 @@ 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 diff --git a/elixir/apps/domain/lib/domain/accounts/account/query.ex b/elixir/apps/domain/lib/domain/accounts/account/query.ex index ca55a8b5a..eb513243f 100644 --- a/elixir/apps/domain/lib/domain/accounts/account/query.ex +++ b/elixir/apps/domain/lib/domain/accounts/account/query.ex @@ -5,6 +5,7 @@ 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 diff --git a/elixir/apps/domain/lib/domain/actors.ex b/elixir/apps/domain/lib/domain/actors.ex index 772697146..f740c9fc7 100644 --- a/elixir/apps/domain/lib/domain/actors.ex +++ b/elixir/apps/domain/lib/domain/actors.ex @@ -5,6 +5,7 @@ defmodule Domain.Actors do alias Domain.{Accounts, Auth, Tokens, Clients, Policies, Billing} alias Domain.Actors.{Authorizer, Actor, Group} require Ecto.Query + require Logger # Groups @@ -246,13 +247,24 @@ defmodule Domain.Actors do end def delete_group(%Group{provider_id: nil} = group, %Auth.Subject{} = subject) do + with :ok <- Group.Authorizer.ensure_has_access_to(group, subject) do + Repo.delete(group) + end + end + + def 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_group(%Group{provider_id: nil} = group, %Auth.Subject{} = subject) do queryable = Group.Query.not_deleted() |> Group.Query.by_id(group.id) - case delete_groups(queryable, subject) do + case soft_delete_groups(queryable, subject) do {:ok, [group]} -> - {:ok, _policies} = Policies.delete_policies_for(group, subject) + {: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 @@ -271,11 +283,13 @@ defmodule Domain.Actors do end end - def delete_group(%Group{}, %Auth.Subject{}) do + # 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 - def delete_groups_for(%Auth.Provider{} = provider, %Auth.Subject{} = subject) do + # 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) @@ -287,13 +301,14 @@ defmodule Domain.Actors do Membership.Query.by_group_provider_id(provider.id) |> Repo.delete_all() - with {:ok, groups} <- delete_groups(queryable, subject) do - {:ok, _policies} = Policies.delete_policies_for(provider, subject) + with {:ok, groups} <- soft_delete_groups(queryable, subject) do + {:ok, _policies} = Policies.soft_delete_policies_for(provider, subject) {:ok, groups} end end - defp delete_groups(queryable, subject) do + # 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 @@ -307,7 +322,8 @@ defmodule Domain.Actors do @doc false # used in sync workers - def delete_groups(queryable) do + # 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() @@ -315,7 +331,7 @@ defmodule Domain.Actors do :ok = Enum.each(groups, fn group -> - {:ok, _policies} = Domain.Policies.delete_policies_for(group) + {:ok, _policies} = Domain.Policies.soft_delete_policies_for(group) end) # TODO: Hard delete @@ -334,10 +350,11 @@ defmodule Domain.Actors do def group_managed?(%Group{}), do: false def group_editable?(%Group{} = group), - do: not group_deleted?(group) and not group_synced?(group) and not group_managed?(group) + do: not group_soft_deleted?(group) and not group_synced?(group) and not group_managed?(group) - def group_deleted?(%Group{deleted_at: nil}), do: false - def group_deleted?(%Group{}), do: true + # 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 # Actors @@ -567,7 +584,7 @@ defmodule Domain.Actors do |> Repo.fetch_and_update(Actor.Query, with: fn actor -> if actor.type != :account_admin_user or other_enabled_admins_exist?(actor) do - {:ok, _tokens} = Tokens.delete_tokens_for(actor, subject) + {:ok, _num_tokens} = Tokens.delete_tokens_for(actor, subject) Actor.Changeset.disable_actor(actor) else :cant_disable_the_last_admin @@ -587,6 +604,20 @@ defmodule Domain.Actors do end def delete_actor(%Actor{} = actor, %Auth.Subject{} = subject) do + with :ok <- Authorizer.ensure_has_access_to(actor, subject), + true <- actor.type != :account_admin_user or other_enabled_admins_exist?(actor) do + Repo.delete(actor) + else + false -> + {:error, :cant_delete_the_last_admin} + + other -> + other + 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) @@ -594,7 +625,7 @@ defmodule Domain.Actors do |> Repo.fetch_and_update(Actor.Query, with: fn actor -> if actor.type != :account_admin_user or other_enabled_admins_exist?(actor) do - :ok = Auth.delete_identities_for(actor, subject) + :ok = Auth.soft_delete_identities_for(actor, subject) :ok = Clients.delete_clients_for(actor, subject) # TODO: Hard delete @@ -633,12 +664,14 @@ defmodule Domain.Actors do 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{}), do: false diff --git a/elixir/apps/domain/lib/domain/actors/actor.ex b/elixir/apps/domain/lib/domain/actors/actor.ex index cf08bcedc..4b4b25a84 100644 --- a/elixir/apps/domain/lib/domain/actors/actor.ex +++ b/elixir/apps/domain/lib/domain/actors/actor.ex @@ -7,12 +7,15 @@ 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] + # 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] + # TODO: HARD-DELETE - Remove `where` after `deleted_at` is removed from DB has_many :tokens, Domain.Tokens.Token, where: [deleted_at: nil] has_many :memberships, Domain.Actors.Membership, on_replace: :delete @@ -25,6 +28,8 @@ defmodule Domain.Actors.Actor do field :last_seen_at, :utc_datetime_usec, virtual: true 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 diff --git a/elixir/apps/domain/lib/domain/actors/actor/changeset.ex b/elixir/apps/domain/lib/domain/actors/actor/changeset.ex index 1cfcc4d95..53321ecd6 100644 --- a/elixir/apps/domain/lib/domain/actors/actor/changeset.ex +++ b/elixir/apps/domain/lib/domain/actors/actor/changeset.ex @@ -81,6 +81,7 @@ 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() diff --git a/elixir/apps/domain/lib/domain/actors/actor/query.ex b/elixir/apps/domain/lib/domain/actors/actor/query.ex index 02b8c437a..e221490ef 100644 --- a/elixir/apps/domain/lib/domain/actors/actor/query.ex +++ b/elixir/apps/domain/lib/domain/actors/actor/query.ex @@ -5,6 +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)) @@ -30,6 +31,7 @@ defmodule Domain.Actors.Actor.Query do where(queryable, [actors: actors], actors.account_id == ^account_id) 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(), @@ -42,6 +44,7 @@ defmodule Domain.Actors.Actor.Query do ) 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() @@ -329,6 +332,7 @@ 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 diff --git a/elixir/apps/domain/lib/domain/actors/authorizer.ex b/elixir/apps/domain/lib/domain/actors/authorizer.ex index c0954c4a5..f72012d06 100644 --- a/elixir/apps/domain/lib/domain/actors/authorizer.ex +++ b/elixir/apps/domain/lib/domain/actors/authorizer.ex @@ -30,6 +30,14 @@ defmodule Domain.Actors.Authorizer do [] end + def ensure_has_access_to(%Actor{} = actor, %Subject{} = subject) do + if actor.account_id == subject.account.id do + Domain.Auth.ensure_has_permissions(subject, manage_actors_permission()) + else + {:error, :unauthorized} + end + end + @impl Domain.Auth.Authorizer def for_subject(queryable, %Subject{} = subject) do cond do diff --git a/elixir/apps/domain/lib/domain/actors/group.ex b/elixir/apps/domain/lib/domain/actors/group.ex index bf122ec76..588a7836f 100644 --- a/elixir/apps/domain/lib/domain/actors/group.ex +++ b/elixir/apps/domain/lib/domain/actors/group.ex @@ -9,6 +9,7 @@ defmodule Domain.Actors.Group do belongs_to :provider, Domain.Auth.Provider field :provider_identifier, :string + # 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] @@ -24,6 +25,7 @@ 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 diff --git a/elixir/apps/domain/lib/domain/actors/group/authorizer.ex b/elixir/apps/domain/lib/domain/actors/group/authorizer.ex new file mode 100644 index 000000000..e9c4b1254 --- /dev/null +++ b/elixir/apps/domain/lib/domain/actors/group/authorizer.ex @@ -0,0 +1,53 @@ +defmodule Domain.Actors.Group.Authorizer do + use Domain.Auth.Authorizer + alias Domain.Actors.{Actor, Group} + + def manage_actor_groups_permission, do: build(Group, :manage) + + @impl Domain.Auth.Authorizer + def list_permissions_for_role(:account_admin_user) do + [ + manage_actor_groups_permission() + ] + end + + def list_permissions_for_role(:api_client) do + [ + manage_actor_groups_permission() + ] + end + + def list_permissions_for_role(:account_user) do + [] + end + + def list_permissions_for_role(_role) do + [] + end + + def ensure_has_access_to(%Group{} = group, %Subject{} = subject) do + if group.account_id == subject.account.id do + Domain.Auth.ensure_has_permissions(subject, manage_actor_groups_permission()) + else + {:error, :unauthorized} + end + end + + @impl Domain.Auth.Authorizer + def for_subject(queryable, %Subject{} = subject) do + cond do + has_permission?(subject, manage_actor_groups_permission()) -> + by_account_id(queryable, subject.account.id) + end + end + + defp by_account_id(queryable, account_id) do + cond do + Ecto.Query.has_named_binding?(queryable, :groups) -> + Group.Query.by_account_id(queryable, account_id) + + Ecto.Query.has_named_binding?(queryable, :actors) -> + Actor.Query.by_account_id(queryable, account_id) + end + end +end diff --git a/elixir/apps/domain/lib/domain/actors/group/changeset.ex b/elixir/apps/domain/lib/domain/actors/group/changeset.ex index a8c38b609..596427946 100644 --- a/elixir/apps/domain/lib/domain/actors/group/changeset.ex +++ b/elixir/apps/domain/lib/domain/actors/group/changeset.ex @@ -11,6 +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 create(%Accounts.Account{} = account, attrs, %Auth.Subject{} = subject) do @@ -35,6 +36,7 @@ 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]a) diff --git a/elixir/apps/domain/lib/domain/actors/group/query.ex b/elixir/apps/domain/lib/domain/actors/group/query.ex index 9b2446a2e..b0e0ba035 100644 --- a/elixir/apps/domain/lib/domain/actors/group/query.ex +++ b/elixir/apps/domain/lib/domain/actors/group/query.ex @@ -5,6 +5,7 @@ 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)) @@ -63,6 +64,7 @@ defmodule Domain.Actors.Group.Query do where(queryable, [groups: groups], groups.provider_identifier == ^provider_identifier) end + # TODO: HARD-DELETE - Remove after `deleted_at` is removed from DB def delete(queryable) do queryable |> Ecto.Query.select([groups: groups], groups) @@ -141,6 +143,7 @@ defmodule Domain.Actors.Group.Query do ) 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 @@ -253,10 +256,12 @@ 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( diff --git a/elixir/apps/domain/lib/domain/actors/group/sync.ex b/elixir/apps/domain/lib/domain/actors/group/sync.ex index 1d6a1b255..0e22f3d0b 100644 --- a/elixir/apps/domain/lib/domain/actors/group/sync.ex +++ b/elixir/apps/domain/lib/domain/actors/group/sync.ex @@ -1,7 +1,6 @@ defmodule Domain.Actors.Group.Sync do alias Domain.Repo alias Domain.Auth - alias Domain.Actors alias Domain.Actors.Group require Logger @@ -16,7 +15,7 @@ defmodule Domain.Actors.Group.Sync do with {:ok, groups} <- all_provider_groups(provider), {:ok, {upsert, delete}} <- plan_groups_update(groups, provider_identifiers), :ok <- deletion_circuit_breaker(groups, delete, provider), - {:ok, deleted} <- delete_groups(provider, delete), + {:ok, _num_deleted} <- delete_groups(provider, delete), {:ok, upserted} <- upsert_groups(provider, attrs_by_provider_identifier, upsert) do group_ids_by_provider_identifier = for group <- groups ++ upserted, @@ -29,7 +28,7 @@ defmodule Domain.Actors.Group.Sync do %{ groups: groups, plan: {upsert, delete}, - deleted: deleted, + deleted: delete, upserted: upserted, group_ids_by_provider_identifier: group_ids_by_provider_identifier }} @@ -46,6 +45,7 @@ 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) @@ -108,11 +108,14 @@ defmodule Domain.Actors.Group.Sync do end defp delete_groups(provider, provider_identifiers_to_delete) do - Group.Query.not_deleted() - |> Group.Query.by_account_id(provider.account_id) - |> Group.Query.by_provider_id(provider.id) - |> Group.Query.by_provider_identifier({:in, provider_identifiers_to_delete}) - |> Actors.delete_groups() + {num_deleted, _} = + Group.Query.all() + |> Group.Query.by_account_id(provider.account_id) + |> Group.Query.by_provider_id(provider.id) + |> Group.Query.by_provider_identifier({:in, provider_identifiers_to_delete}) + |> Repo.delete_all() + + {:ok, num_deleted} end defp upsert_groups(provider, attrs_by_provider_identifier, provider_identifiers_to_upsert) do diff --git a/elixir/apps/domain/lib/domain/auth.ex b/elixir/apps/domain/lib/domain/auth.ex index 69ddf1336..257ac6cc1 100644 --- a/elixir/apps/domain/lib/domain/auth.ex +++ b/elixir/apps/domain/lib/domain/auth.ex @@ -70,6 +70,7 @@ defmodule Domain.Auth do alias Domain.Auth.{Authorizer, Subject, Context, Permission, Roles, Role} alias Domain.Auth.{Adapters, Provider} alias Domain.Auth.Identity + require Logger # This session duration is used when IdP doesn't return the token expiration date, # or no IdP is used (eg. sign in via email or userpass). @@ -252,7 +253,7 @@ defmodule Domain.Auth do def disable_provider(%Provider{} = provider, %Subject{} = subject) do mutate_provider(provider, subject, fn provider -> if other_active_providers_exist?(provider) do - {:ok, _tokens} = Tokens.delete_tokens_for(provider, subject) + {:ok, _num_tokens} = Tokens.delete_tokens_for(provider, subject) Provider.Changeset.disable_provider(provider) else :cant_disable_the_last_provider @@ -296,13 +297,30 @@ defmodule Domain.Auth do mutate_provider(provider, subject, &Provider.Changeset.enable_provider/1) end + def delete_provider_by_id(provider_id, %Subject{} = subject) do + case fetch_provider_by_id(provider_id, subject) do + {:ok, provider} -> + delete_provider(provider, subject) + + {:error, reason} -> + {:error, reason} + end + end + def delete_provider(%Provider{} = provider, %Subject{} = subject) do + with :ok <- Authorizer.ensure_has_access_to(provider, subject), + :ok <- can_safely_delete?(provider) do + Repo.delete(provider) + 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 = delete_identities_for(provider, subject) - {:ok, _groups} = Actors.delete_groups_for(provider, subject) - Provider.Changeset.delete_provider(provider) + :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 @@ -326,6 +344,14 @@ defmodule Domain.Auth do end end + defp can_safely_delete?(%Provider{} = provider) do + if other_active_providers_exist?(provider) do + :ok + else + {:error, :cant_delete_the_last_provider} + end + end + defp other_active_providers_exist?(%Provider{id: id, account_id: account_id}) do Provider.Query.not_disabled() |> Provider.Query.by_id({:not, id}) @@ -425,6 +451,7 @@ 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) @@ -471,6 +498,42 @@ defmodule Domain.Auth do end def delete_identity(%Identity{} = identity, %Subject{} = subject) do + with :ok <- Authorizer.ensure_has_access_to(identity, subject) do + Repo.delete(identity) + end + end + + def delete_identities_for(%Actors.Actor{} = actor, %Subject{} = subject) do + with :ok <- ensure_has_permissions(subject, Authorizer.manage_identities_permission()) do + {num_deleted, _} = + Identity.Query.all() + |> Identity.Query.by_actor_id(actor.id) + |> Identity.Query.by_account_id(actor.account_id) + |> Authorizer.for_subject(Identity, subject) + |> Repo.delete_all() + + {:ok, num_deleted} + 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, [ @@ -484,30 +547,33 @@ defmodule Domain.Auth do |> Authorizer.for_subject(Identity, subject) |> Repo.fetch_and_update(Identity.Query, with: fn identity -> - {:ok, _tokens} = Tokens.delete_tokens_for(identity, subject) + {:ok, _tokens} = Tokens.soft_delete_tokens_for(identity, subject) Identity.Changeset.delete_identity(identity) end ) end end - def delete_identities_for(%Actors.Actor{} = actor, %Subject{} = subject) do + # 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) - |> delete_identities(actor, subject) + |> soft_delete_identities(actor, subject) end - def delete_identities_for(%Provider{} = provider, %Subject{} = subject) do + # 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) - |> delete_identities(provider, subject) + |> soft_delete_identities(provider, subject) end - defp delete_identities(queryable, assoc, subject) do + # 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.delete_tokens_for(assoc, subject) + {:ok, _tokens} = Tokens.soft_delete_tokens_for(assoc, subject) {_count, nil} = queryable @@ -518,8 +584,9 @@ defmodule Domain.Auth do end end - def identity_deleted?(%{deleted_at: nil}), do: false - def identity_deleted?(_identity), do: true + # 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 @@ -537,6 +604,7 @@ 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, @@ -580,6 +648,7 @@ 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} @@ -661,7 +730,7 @@ defmodule Domain.Auth do if IdP was used for Sign In, revokes the IdP token too by redirecting user to IdP logout endpoint. """ def sign_out(%Subject{} = subject, redirect_url) do - {:ok, _token} = Tokens.delete_token_for(subject) + {:ok, _num_deleted} = Tokens.delete_token_for(subject) identity = Repo.preload(subject.identity, :provider) Adapters.sign_out(identity.provider, identity, redirect_url) end diff --git a/elixir/apps/domain/lib/domain/auth/adapters/email.ex b/elixir/apps/domain/lib/domain/auth/adapters/email.ex index 40e265931..97745c59a 100644 --- a/elixir/apps/domain/lib/domain/auth/adapters/email.ex +++ b/elixir/apps/domain/lib/domain/auth/adapters/email.ex @@ -109,9 +109,7 @@ defmodule Domain.Auth.Adapters.Email do |> Identity.Query.by_id(identity.id) |> Repo.fetch_and_update(Identity.Query, with: fn identity -> - Identity.Changeset.update_identity_provider_state(identity, %{ - last_used_token_id: token.id - }) + Identity.Changeset.update_identity_provider_state(identity, %{}) end ) diff --git a/elixir/apps/domain/lib/domain/auth/authorizer.ex b/elixir/apps/domain/lib/domain/auth/authorizer.ex index 062555c4a..149c37633 100644 --- a/elixir/apps/domain/lib/domain/auth/authorizer.ex +++ b/elixir/apps/domain/lib/domain/auth/authorizer.ex @@ -67,6 +67,30 @@ defmodule Domain.Auth.Authorizer do [] end + def ensure_has_access_to(%Auth.Identity{} = identity, %Auth.Subject{} = subject) do + cond do + # If identity belongs to same actor, check own permission + subject.account.id == identity.account_id and owns_identity?(identity, subject) -> + Auth.ensure_has_permissions(subject, manage_own_identities_permission()) + + # Otherwise, check global manage permission + subject.account.id == identity.account_id -> + Auth.ensure_has_permissions(subject, manage_identities_permission()) + + # Different account + true -> + {:error, :unauthorized} + end + end + + def ensure_has_access_to(%Auth.Provider{} = provider, %Auth.Subject{} = subject) do + if subject.account.id == provider.account_id do + Auth.ensure_has_permissions(subject, manage_providers_permission()) + else + {:error, :unauthorized} + end + end + def for_subject(queryable, Auth.Identity, %Auth.Subject{} = subject) do cond do Auth.has_permission?(subject, manage_identities_permission()) -> @@ -85,4 +109,12 @@ defmodule Domain.Auth.Authorizer do Auth.Provider.Query.by_account_id(queryable, subject.account.id) end end + + defp owns_identity?(%Auth.Identity{} = identity, %Auth.Subject{} = subject) do + cond do + is_nil(subject.identity) -> false + identity.id == subject.identity.id -> true + true -> false + end + end end diff --git a/elixir/apps/domain/lib/domain/auth/identity.ex b/elixir/apps/domain/lib/domain/auth/identity.ex index 0d284dce0..e7d1fe349 100644 --- a/elixir/apps/domain/lib/domain/auth/identity.ex +++ b/elixir/apps/domain/lib/domain/auth/identity.ex @@ -23,10 +23,13 @@ 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] + # 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] + # TODO: HARD-DELETE - Remove field after soft deletion is removed field :deleted_at, :utc_datetime_usec timestamps(updated_at: false) end diff --git a/elixir/apps/domain/lib/domain/auth/identity/changeset.ex b/elixir/apps/domain/lib/domain/auth/identity/changeset.ex index ae3e08bef..b11496f2d 100644 --- a/elixir/apps/domain/lib/domain/auth/identity/changeset.ex +++ b/elixir/apps/domain/lib/domain/auth/identity/changeset.ex @@ -82,6 +82,7 @@ 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() diff --git a/elixir/apps/domain/lib/domain/auth/identity/query.ex b/elixir/apps/domain/lib/domain/auth/identity/query.ex index ed6d131da..9f940b1fa 100644 --- a/elixir/apps/domain/lib/domain/auth/identity/query.ex +++ b/elixir/apps/domain/lib/domain/auth/identity/query.ex @@ -5,16 +5,19 @@ 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 queryable |> with_assoc(:inner, :actor) @@ -144,6 +147,7 @@ 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) diff --git a/elixir/apps/domain/lib/domain/auth/identity/sync.ex b/elixir/apps/domain/lib/domain/auth/identity/sync.ex index e541c2b75..5ef668632 100644 --- a/elixir/apps/domain/lib/domain/auth/identity/sync.ex +++ b/elixir/apps/domain/lib/domain/auth/identity/sync.ex @@ -50,6 +50,7 @@ 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( @@ -116,22 +117,14 @@ defmodule Domain.Auth.Identity.Sync do end defp delete_identities(provider, provider_identifiers_to_delete) do - provider_identifiers_to_delete = Enum.uniq(provider_identifiers_to_delete) - - {_count, identities} = - Identity.Query.not_deleted() + {num_deleted, _} = + Identity.Query.all() |> Identity.Query.by_account_id(provider.account_id) |> Identity.Query.by_provider_id(provider.id) |> Identity.Query.by_provider_identifier({:in, provider_identifiers_to_delete}) - |> Identity.Query.delete() - |> Repo.update_all([]) + |> Repo.delete_all() - :ok = - Enum.each(identities, fn identity -> - {:ok, _tokens} = Domain.Tokens.delete_tokens_for(identity) - end) - - {:ok, identities} + {:ok, num_deleted} end defp insert_identities(provider, attrs_by_provider_identifier, provider_identifiers_to_insert) do @@ -153,6 +146,7 @@ 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, diff --git a/elixir/apps/domain/lib/domain/auth/provider.ex b/elixir/apps/domain/lib/domain/auth/provider.ex index af5ea76e9..41e739677 100644 --- a/elixir/apps/domain/lib/domain/auth/provider.ex +++ b/elixir/apps/domain/lib/domain/auth/provider.ex @@ -14,6 +14,7 @@ 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] @@ -27,6 +28,8 @@ defmodule Domain.Auth.Provider do field :sync_error_emailed_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 field :assigned_default_at, :utc_datetime_usec timestamps() diff --git a/elixir/apps/domain/lib/domain/auth/provider/changeset.ex b/elixir/apps/domain/lib/domain/auth/provider/changeset.ex index 9cf7396f0..ef365c904 100644 --- a/elixir/apps/domain/lib/domain/auth/provider/changeset.ex +++ b/elixir/apps/domain/lib/domain/auth/provider/changeset.ex @@ -4,6 +4,7 @@ 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 @@ -131,7 +132,8 @@ defmodule Domain.Auth.Provider.Changeset do |> put_change(:disabled_at, nil) end - def delete_provider(%Provider{} = provider) do + # 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()) diff --git a/elixir/apps/domain/lib/domain/auth/provider/query.ex b/elixir/apps/domain/lib/domain/auth/provider/query.ex index 8cb69fd6b..c0b4a2711 100644 --- a/elixir/apps/domain/lib/domain/auth/provider/query.ex +++ b/elixir/apps/domain/lib/domain/auth/provider/query.ex @@ -5,6 +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)) diff --git a/elixir/apps/domain/lib/domain/auth/roles.ex b/elixir/apps/domain/lib/domain/auth/roles.ex index 4c2904b22..842ac1c71 100644 --- a/elixir/apps/domain/lib/domain/auth/roles.ex +++ b/elixir/apps/domain/lib/domain/auth/roles.ex @@ -5,6 +5,7 @@ defmodule Domain.Auth.Roles do [ Domain.Accounts.Authorizer, Domain.Actors.Authorizer, + Domain.Actors.Group.Authorizer, Domain.Auth.Authorizer, Domain.Billing.Authorizer, Domain.Clients.Authorizer, diff --git a/elixir/apps/domain/lib/domain/clients.ex b/elixir/apps/domain/lib/domain/clients.ex index 79093dfa9..1126cfe91 100644 --- a/elixir/apps/domain/lib/domain/clients.ex +++ b/elixir/apps/domain/lib/domain/clients.ex @@ -186,7 +186,7 @@ defmodule Domain.Clients do end def update_client(%Client{} = client, attrs, %Auth.Subject{} = subject) do - with :ok <- authorize_actor_client_management(client.actor_id, subject) do + with :ok <- Authorizer.ensure_has_access_to(client, subject) do Client.Query.not_deleted() |> Client.Query.by_id(client.id) |> Authorizer.for_subject(subject) @@ -198,7 +198,7 @@ defmodule Domain.Clients do end def verify_client(%Client{} = client, %Auth.Subject{} = subject) do - with :ok <- authorize_actor_client_management(client.actor_id, subject), + 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.by_id(client.id) @@ -211,7 +211,7 @@ defmodule Domain.Clients do end def remove_client_verification(%Client{} = client, %Auth.Subject{} = subject) do - with :ok <- authorize_actor_client_management(client.actor_id, subject), + 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.by_id(client.id) @@ -224,18 +224,8 @@ defmodule Domain.Clients do end def delete_client(%Client{} = client, %Auth.Subject{} = subject) do - queryable = - Client.Query.not_deleted() - |> Client.Query.by_id(client.id) - - with :ok <- authorize_actor_client_management(client.actor_id, subject) do - case delete_clients(queryable, subject) do - {:ok, [client]} -> - {:ok, client} - - {:ok, []} -> - {:error, :not_found} - end + with :ok <- Authorizer.ensure_has_access_to(client, subject) do + Repo.delete(client) end end @@ -245,35 +235,24 @@ defmodule Domain.Clients do |> Client.Query.by_actor_id(actor.id) |> Client.Query.by_account_id(actor.account_id) - with :ok <- Auth.ensure_has_permissions(subject, Authorizer.manage_clients_permission()) do - {:ok, _clients} = delete_clients(queryable, subject) + with :ok <- Auth.ensure_has_permissions(subject, Authorizer.manage_clients_permission()), + :ok <- delete_clients(queryable, subject) do :ok + else + {:error, reason} -> {:error, reason} end end - # TODO: Hard delete + # 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. defp delete_clients(queryable, subject) do - {_count, clients} = + {_count, nil} = queryable |> Authorizer.for_subject(subject) - |> Client.Query.delete() - |> Repo.update_all([]) + |> Repo.delete_all() - {:ok, clients} - end - - defp authorize_actor_client_management(%Actors.Actor{} = actor, %Auth.Subject{} = subject) do - authorize_actor_client_management(actor.id, subject) - end - - defp authorize_actor_client_management(id, %Auth.Subject{actor: %{id: id}} = subject) do - Auth.ensure_has_permissions(subject, Authorizer.manage_own_clients_permission()) - end - - defp authorize_actor_client_management(_actor_id, %Auth.Subject{} = subject) do - Auth.ensure_has_permissions(subject, Authorizer.manage_clients_permission()) + :ok end end diff --git a/elixir/apps/domain/lib/domain/clients/authorizer.ex b/elixir/apps/domain/lib/domain/clients/authorizer.ex index dffb563be..61772aa4e 100644 --- a/elixir/apps/domain/lib/domain/clients/authorizer.ex +++ b/elixir/apps/domain/lib/domain/clients/authorizer.ex @@ -52,4 +52,21 @@ defmodule Domain.Clients.Authorizer do |> Client.Query.by_actor_id(subject.actor.id) end end + + def ensure_has_access_to(%Client{} = client, %Subject{} = subject) do + cond do + # If client belongs to same actor, check own permission + client.account_id == subject.account.id and + client.actor_id == subject.actor.id -> + Domain.Auth.ensure_has_permissions(subject, manage_own_clients_permission()) + + # Otherwise, check global manage permission + client.account_id == subject.account.id -> + Domain.Auth.ensure_has_permissions(subject, manage_clients_permission()) + + # Different account + true -> + {:error, {:unauthorized, reason: :incorrect_account}} + end + end end diff --git a/elixir/apps/domain/lib/domain/clients/client.ex b/elixir/apps/domain/lib/domain/clients/client.ex index 6c71f9b90..74ce9e034 100644 --- a/elixir/apps/domain/lib/domain/clients/client.ex +++ b/elixir/apps/domain/lib/domain/clients/client.ex @@ -21,7 +21,6 @@ defmodule Domain.Clients.Client do account_id: Ecto.UUID.t(), actor_id: Ecto.UUID.t(), identity_id: Ecto.UUID.t(), - last_used_token_id: Ecto.UUID.t(), device_serial: String.t() | nil, device_uuid: String.t() | nil, identifier_for_vendor: String.t() | nil, @@ -59,7 +58,6 @@ defmodule Domain.Clients.Client do belongs_to :account, Domain.Accounts.Account belongs_to :actor, Domain.Actors.Actor belongs_to :identity, Domain.Auth.Identity - belongs_to :last_used_token, Domain.Tokens.Token # Hardware Identifiers field :device_serial, :string @@ -72,6 +70,7 @@ 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 diff --git a/elixir/apps/domain/lib/domain/clients/client/changeset.ex b/elixir/apps/domain/lib/domain/clients/client/changeset.ex index 226c04e49..76d0dffcb 100644 --- a/elixir/apps/domain/lib/domain/clients/client/changeset.ex +++ b/elixir/apps/domain/lib/domain/clients/client/changeset.ex @@ -4,7 +4,7 @@ defmodule Domain.Clients.Client.Changeset do alias Domain.{Version, Auth, Actors} alias Domain.Clients - @required_fields ~w[external_id last_used_token_id name public_key]a + @required_fields ~w[external_id name public_key]a @hardware_identifiers ~w[device_serial device_uuid identifier_for_vendor firebase_installation_id]a @upsert_fields @required_fields ++ @hardware_identifiers @update_fields ~w[name]a @@ -12,6 +12,7 @@ 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/} @@ -20,7 +21,6 @@ defmodule Domain.Clients.Client.Changeset do |> update([clients: clients], set: [ public_key: fragment("EXCLUDED.public_key"), - last_used_token_id: fragment("EXCLUDED.last_used_token_id"), last_seen_user_agent: fragment("EXCLUDED.last_seen_user_agent"), last_seen_remote_ip: fragment("EXCLUDED.last_seen_remote_ip"), last_seen_remote_ip_location_region: @@ -108,7 +108,6 @@ defmodule Domain.Clients.Client.Changeset do |> cast(attrs, @upsert_fields) |> put_default_value(:name, &generate_name/0) |> put_assocs(actor_or_identity) - |> put_change(:last_used_token_id, subject.token_id) |> put_change(:last_seen_user_agent, subject.context.user_agent) |> put_change(:last_seen_remote_ip, %Postgrex.INET{address: subject.context.remote_ip}) |> put_change(:last_seen_remote_ip_location_region, subject.context.remote_ip_location_region) diff --git a/elixir/apps/domain/lib/domain/clients/client/query.ex b/elixir/apps/domain/lib/domain/clients/client/query.ex index 6c1d885dd..3a7227e7f 100644 --- a/elixir/apps/domain/lib/domain/clients/client/query.ex +++ b/elixir/apps/domain/lib/domain/clients/client/query.ex @@ -5,6 +5,7 @@ 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)) @@ -34,10 +35,6 @@ defmodule Domain.Clients.Client.Query do where(queryable, [clients: clients], clients.account_id == ^account_id) end - def by_last_used_token_id(queryable, last_used_token_id) do - where(queryable, [clients: clients], clients.last_used_token_id == ^last_used_token_id) - end - def by_last_seen_within(queryable, period, unit) do where(queryable, [clients: clients], clients.last_seen_at > ago(^period, ^unit)) end @@ -61,7 +58,8 @@ defmodule Domain.Clients.Client.Query do select(queryable, [clients: clients], clients) end - def delete(queryable) do + # 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], diff --git a/elixir/apps/domain/lib/domain/clients/presence.ex b/elixir/apps/domain/lib/domain/clients/presence.ex index 63554516a..00a9c4d7f 100644 --- a/elixir/apps/domain/lib/domain/clients/presence.ex +++ b/elixir/apps/domain/lib/domain/clients/presence.ex @@ -6,9 +6,9 @@ defmodule Domain.Clients.Presence do alias Domain.PubSub alias Domain.Clients.Client - def connect(%Client{} = client) do + def connect(%Client{} = client, token_id) do with {:ok, _} <- __MODULE__.Account.track(client.account_id, client.id), - {:ok, _} <- __MODULE__.Actor.track(client.actor_id, client.id) do + {:ok, _} <- __MODULE__.Actor.track(client.actor_id, client.id, token_id) do :ok end end @@ -47,12 +47,12 @@ defmodule Domain.Clients.Presence do end defmodule Actor do - def track(actor_id, client_id) do + def track(actor_id, client_id, token_id) do Domain.Clients.Presence.track( self(), topic(actor_id), client_id, - %{} + %{token_id: token_id} ) end diff --git a/elixir/apps/domain/lib/domain/gateways.ex b/elixir/apps/domain/lib/domain/gateways.ex index b46d8a454..bd2c9d2e0 100644 --- a/elixir/apps/domain/lib/domain/gateways.ex +++ b/elixir/apps/domain/lib/domain/gateways.ex @@ -4,6 +4,7 @@ defmodule Domain.Gateways do alias Domain.{Repo, Auth, Geo} alias Domain.{Accounts, Cache, Clients, Resources, Tokens, Billing} alias Domain.Gateways.{Authorizer, Gateway, Group, Presence} + require Logger require Logger @@ -137,24 +138,8 @@ defmodule Domain.Gateways do end def delete_group(%Group{managed_by: :account} = group, %Auth.Subject{} = subject) do - with :ok <- Auth.ensure_has_permissions(subject, Authorizer.manage_gateways_permission()) do - Group.Query.not_deleted() - |> Group.Query.by_id(group.id) - |> Authorizer.for_subject(subject) - |> Repo.fetch_and_update(Group.Query, - with: fn group -> - # Token deletion will disconnect gateways - {:ok, _tokens} = Tokens.delete_tokens_for(group, subject) - {:ok, _count} = Resources.delete_connections_for(group, subject) - - {_count, _} = - Gateway.Query.not_deleted() - |> Gateway.Query.by_group_id(group.id) - |> Repo.update_all(set: [deleted_at: DateTime.utc_now()]) - - Group.Changeset.delete(group) - end - ) + with :ok <- Authorizer.ensure_has_access_to(group, subject) do + Repo.delete(group) end end @@ -294,8 +279,8 @@ defmodule Domain.Gateways do Gateway.Changeset.update(gateway, attrs) end - def upsert_gateway(%Group{} = group, %Tokens.Token{} = token, attrs, %Auth.Context{} = context) do - changeset = Gateway.Changeset.upsert(group, token, attrs, context) + def upsert_gateway(%Group{} = group, attrs, %Auth.Context{} = context) do + changeset = Gateway.Changeset.upsert(group, attrs, context) Ecto.Multi.new() |> Ecto.Multi.insert(:gateway, changeset, @@ -338,18 +323,25 @@ defmodule Domain.Gateways do end end - def delete_gateway(%Gateway{} = gateway, %Auth.Subject{} = subject) do + # 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.delete/1, + 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) + end + end + def load_balance_gateways({_lat, _lon}, []) do nil end diff --git a/elixir/apps/domain/lib/domain/gateways/authorizer.ex b/elixir/apps/domain/lib/domain/gateways/authorizer.ex index 63649308d..70d96330b 100644 --- a/elixir/apps/domain/lib/domain/gateways/authorizer.ex +++ b/elixir/apps/domain/lib/domain/gateways/authorizer.ex @@ -27,6 +27,22 @@ defmodule Domain.Gateways.Authorizer do ] end + def ensure_has_access_to(%Group{} = group, %Subject{} = subject) do + if group.account_id == subject.account.id do + Domain.Auth.ensure_has_permissions(subject, manage_gateways_permission()) + else + {:error, :unauthorized} + end + end + + def ensure_has_access_to(%Gateway{} = gateway, %Subject{} = subject) do + if gateway.account_id == subject.account.id do + Domain.Auth.ensure_has_permissions(subject, manage_gateways_permission()) + else + {:error, :unauthorized} + end + end + @impl Domain.Auth.Authorizer def for_subject(queryable, %Subject{} = subject) do cond do diff --git a/elixir/apps/domain/lib/domain/gateways/gateway.ex b/elixir/apps/domain/lib/domain/gateways/gateway.ex index bbe9c6006..47bdf4189 100644 --- a/elixir/apps/domain/lib/domain/gateways/gateway.ex +++ b/elixir/apps/domain/lib/domain/gateways/gateway.ex @@ -45,13 +45,12 @@ defmodule Domain.Gateways.Gateway do field :last_seen_version, :string field :last_seen_at, :utc_datetime_usec - belongs_to :last_used_token, Domain.Tokens.Token - field :online?, :boolean, virtual: true 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 diff --git a/elixir/apps/domain/lib/domain/gateways/gateway/changeset.ex b/elixir/apps/domain/lib/domain/gateways/gateway/changeset.ex index 6ef242b25..e352ba372 100644 --- a/elixir/apps/domain/lib/domain/gateways/gateway/changeset.ex +++ b/elixir/apps/domain/lib/domain/gateways/gateway/changeset.ex @@ -1,6 +1,6 @@ defmodule Domain.Gateways.Gateway.Changeset do use Domain, :changeset - alias Domain.{Version, Auth, Tokens} + alias Domain.{Version, Auth} alias Domain.Gateways @upsert_fields ~w[external_id name public_key @@ -20,7 +20,6 @@ defmodule Domain.Gateways.Gateway.Changeset do last_seen_remote_ip_location_lon last_seen_version last_seen_at - last_used_token_id updated_at]a @update_fields ~w[name]a @required_fields ~w[external_id name public_key]a @@ -28,12 +27,13 @@ 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/} def upsert_on_conflict, do: {:replace, @conflict_replace_fields} - def upsert(%Gateways.Group{} = group, %Tokens.Token{} = token, attrs, %Auth.Context{} = context) do + def upsert(%Gateways.Group{} = group, attrs, %Auth.Context{} = context) do %Gateways.Gateway{} |> cast(attrs, @upsert_fields) |> put_default_value(:name, fn -> @@ -56,7 +56,6 @@ defmodule Domain.Gateways.Gateway.Changeset do |> put_gateway_version() |> put_change(:account_id, group.account_id) |> put_change(:group_id, group.id) - |> put_change(:last_used_token_id, token.id) end def finalize_upsert(%Gateways.Gateway{} = gateway, ipv4, ipv6) do @@ -73,7 +72,8 @@ defmodule Domain.Gateways.Gateway.Changeset do |> validate_required(@required_fields) end - def delete(%Gateways.Gateway{} = gateway) do + # 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()) diff --git a/elixir/apps/domain/lib/domain/gateways/gateway/query.ex b/elixir/apps/domain/lib/domain/gateways/gateway/query.ex index 3ea91fa6e..cbbfb224a 100644 --- a/elixir/apps/domain/lib/domain/gateways/gateway/query.ex +++ b/elixir/apps/domain/lib/domain/gateways/gateway/query.ex @@ -5,6 +5,7 @@ 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)) @@ -107,6 +108,7 @@ defmodule Domain.Gateways.Gateway.Query 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 diff --git a/elixir/apps/domain/lib/domain/gateways/group.ex b/elixir/apps/domain/lib/domain/gateways/group.ex index e7b04c6a5..fb069045d 100644 --- a/elixir/apps/domain/lib/domain/gateways/group.ex +++ b/elixir/apps/domain/lib/domain/gateways/group.ex @@ -19,8 +19,10 @@ 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] + # 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] @@ -30,6 +32,7 @@ defmodule Domain.Gateways.Group do 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 diff --git a/elixir/apps/domain/lib/domain/gateways/group/changeset.ex b/elixir/apps/domain/lib/domain/gateways/group/changeset.ex index d6aee20d5..b689e6185 100644 --- a/elixir/apps/domain/lib/domain/gateways/group/changeset.ex +++ b/elixir/apps/domain/lib/domain/gateways/group/changeset.ex @@ -40,6 +40,7 @@ defmodule Domain.Gateways.Group.Changeset do |> 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() diff --git a/elixir/apps/domain/lib/domain/gateways/group/query.ex b/elixir/apps/domain/lib/domain/gateways/group/query.ex index ea3b16c7e..2f52b9ed3 100644 --- a/elixir/apps/domain/lib/domain/gateways/group/query.ex +++ b/elixir/apps/domain/lib/domain/gateways/group/query.ex @@ -5,6 +5,7 @@ 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)) @@ -56,6 +57,7 @@ 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 diff --git a/elixir/apps/domain/lib/domain/gateways/presence.ex b/elixir/apps/domain/lib/domain/gateways/presence.ex index 1961be3ce..80e69f5c3 100644 --- a/elixir/apps/domain/lib/domain/gateways/presence.ex +++ b/elixir/apps/domain/lib/domain/gateways/presence.ex @@ -6,8 +6,8 @@ defmodule Domain.Gateways.Presence do alias Domain.Gateways.Gateway alias Domain.PubSub - def connect(%Gateway{} = gateway) do - with {:ok, _} <- __MODULE__.Group.track(gateway.group_id, gateway.id), + def connect(%Gateway{} = gateway, token_id) do + with {:ok, _} <- __MODULE__.Group.track(gateway.group_id, gateway.id, token_id), {:ok, _} <- __MODULE__.Account.track(gateway.account_id, gateway.id) do :ok end @@ -47,12 +47,12 @@ defmodule Domain.Gateways.Presence do end defmodule Group do - def track(gateway_group_id, gateway_id) do + def track(gateway_group_id, gateway_id, token_id) do Domain.Gateways.Presence.track( self(), topic(gateway_group_id), gateway_id, - %{} + %{token_id: token_id} ) end diff --git a/elixir/apps/domain/lib/domain/policies.ex b/elixir/apps/domain/lib/domain/policies.ex index 82f99979e..648d270db 100644 --- a/elixir/apps/domain/lib/domain/policies.ex +++ b/elixir/apps/domain/lib/domain/policies.ex @@ -2,6 +2,7 @@ defmodule Domain.Policies do alias Domain.Repo alias Domain.{Auth, Actors, Cache.Cacheable, Clients, Resources} alias Domain.Policies.{Authorizer, Policy, Condition} + require Logger def fetch_policy_by_id(id, %Auth.Subject{} = subject, opts \\ []) do required_permissions = @@ -138,10 +139,11 @@ defmodule Domain.Policies do end end - def delete_policy(%Policy{} = policy, %Auth.Subject{} = subject) do + # 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) - |> delete_policies(subject) + |> soft_delete_policies(subject) |> case do {:ok, [policy]} -> {:ok, policy} {:ok, []} -> {:error, :not_found} @@ -149,39 +151,100 @@ defmodule Domain.Policies do end end - def delete_policies_for(%Resources.Resource{} = resource, %Auth.Subject{} = subject) do - Policy.Query.not_deleted() - |> Policy.Query.by_resource_id(resource.id) - |> delete_policies(subject) - end - - def delete_policies_for(%Actors.Group{} = actor_group, %Auth.Subject{} = subject) do - Policy.Query.not_deleted() - |> Policy.Query.by_actor_group_id(actor_group.id) - |> delete_policies(subject) - end - - def delete_policies_for(%Auth.Provider{} = provider, %Auth.Subject{} = subject) do - Policy.Query.not_deleted() - |> Policy.Query.by_actor_group_provider_id(provider.id) - |> delete_policies(subject) - end - - def delete_policies_for(%Actors.Group{} = actor_group) do - Policy.Query.not_deleted() - |> Policy.Query.by_actor_group_id(actor_group.id) - |> delete_policies() - end - - defp delete_policies(queryable, subject) do - with :ok <- Auth.ensure_has_permissions(subject, Authorizer.manage_policies_permission()) do - queryable - |> Authorizer.for_subject(subject) - |> delete_policies() + def delete_policy(%Policy{} = policy, %Auth.Subject{} = subject) do + with :ok <- Authorizer.ensure_has_access_to(policy, subject) do + Repo.delete(policy) end end - defp delete_policies(queryable) do + # 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() diff --git a/elixir/apps/domain/lib/domain/policies/authorizer.ex b/elixir/apps/domain/lib/domain/policies/authorizer.ex index de140ead1..57053835d 100644 --- a/elixir/apps/domain/lib/domain/policies/authorizer.ex +++ b/elixir/apps/domain/lib/domain/policies/authorizer.ex @@ -37,6 +37,14 @@ defmodule Domain.Policies.Authorizer do [] end + def ensure_has_access_to(%Policy{} = policy, %Subject{} = subject) do + if policy.account_id == subject.account.id do + Domain.Auth.ensure_has_permissions(subject, manage_policies_permission()) + else + {:error, :unauthorized} + end + end + @impl Domain.Auth.Authorizer def for_subject(queryable, %Subject{} = subject) do cond do diff --git a/elixir/apps/domain/lib/domain/policies/policy.ex b/elixir/apps/domain/lib/domain/policies/policy.ex index 9964d697b..d76ad77d4 100644 --- a/elixir/apps/domain/lib/domain/policies/policy.ex +++ b/elixir/apps/domain/lib/domain/policies/policy.ex @@ -36,6 +36,8 @@ defmodule Domain.Policies.Policy do 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 diff --git a/elixir/apps/domain/lib/domain/policies/policy/query.ex b/elixir/apps/domain/lib/domain/policies/policy/query.ex index 6b7e63761..15b43bff9 100644 --- a/elixir/apps/domain/lib/domain/policies/policy/query.ex +++ b/elixir/apps/domain/lib/domain/policies/policy/query.ex @@ -5,6 +5,7 @@ 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)) @@ -84,6 +85,7 @@ 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) @@ -251,6 +253,7 @@ defmodule Domain.Policies.Policy.Query 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 diff --git a/elixir/apps/domain/lib/domain/relays.ex b/elixir/apps/domain/lib/domain/relays.ex index 0ce071279..2e9c4759d 100644 --- a/elixir/apps/domain/lib/domain/relays.ex +++ b/elixir/apps/domain/lib/domain/relays.ex @@ -88,6 +88,13 @@ defmodule Domain.Relays do end def delete_group(%Group{} = group, %Auth.Subject{} = subject) do + with :ok <- Authorizer.ensure_has_access_to(group, subject) do + Repo.delete(group) + 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) @@ -95,7 +102,7 @@ defmodule Domain.Relays do |> Group.Query.by_account_id(subject.account.id) |> Repo.fetch_and_update(Group.Query, with: fn group -> - {:ok, _tokens} = Tokens.delete_tokens_for(group, subject) + {:ok, _tokens} = Tokens.soft_delete_tokens_for(group, subject) {_count, _} = Relay.Query.not_deleted() @@ -286,8 +293,8 @@ defmodule Domain.Relays do |> Base.encode64(padding: false) end - def upsert_relay(%Group{} = group, %Tokens.Token{} = token, attrs, %Auth.Context{} = context) do - changeset = Relay.Changeset.upsert(group, token, attrs, context) + def upsert_relay(%Group{} = group, attrs, %Auth.Context{} = context) do + changeset = Relay.Changeset.upsert(group, attrs, context) Ecto.Multi.new() |> Ecto.Multi.insert(:relay, changeset, @@ -303,6 +310,13 @@ defmodule Domain.Relays do end def delete_relay(%Relay{} = relay, %Auth.Subject{} = subject) do + with :ok <- Authorizer.ensure_has_access_to(relay, subject) do + Repo.delete(relay) + 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) @@ -344,10 +358,13 @@ defmodule Domain.Relays do |> Enum.map(&Enum.random(elem(&1, 1))) end - # TODO: Refactor to use new conventions - def connect_relay(%Relay{} = relay, secret) do + # TODO: WAL + # Refactor to use new conventions + def connect_relay(%Relay{} = relay, secret, token_id) do with {:ok, _} <- - Presence.track(self(), group_presence_topic(relay.group_id), relay.id, %{}), + Presence.track(self(), group_presence_topic(relay.group_id), relay.id, %{ + token_id: token_id + }), {:ok, _} <- Presence.track(self(), account_or_global_presence_topic(relay), relay.id, %{ online_at: System.system_time(:second), diff --git a/elixir/apps/domain/lib/domain/relays/authorizer.ex b/elixir/apps/domain/lib/domain/relays/authorizer.ex index f2d53b064..5a4c280d7 100644 --- a/elixir/apps/domain/lib/domain/relays/authorizer.ex +++ b/elixir/apps/domain/lib/domain/relays/authorizer.ex @@ -16,6 +16,24 @@ defmodule Domain.Relays.Authorizer do [] end + def ensure_has_access_to(%Group{} = group, %Subject{} = subject) do + # Allow access to global relay groups or account-specific groups + if group.account_id == subject.account.id or is_nil(group.account_id) do + Domain.Auth.ensure_has_permissions(subject, manage_relays_permission()) + else + {:error, :unauthorized} + end + end + + def ensure_has_access_to(%Relay{} = relay, %Subject{} = subject) do + # Allow access to global relays or account-specific relays + if relay.account_id == subject.account.id or is_nil(relay.account_id) do + Domain.Auth.ensure_has_permissions(subject, manage_relays_permission()) + else + {:error, :unauthorized} + end + end + @impl Domain.Auth.Authorizer def for_subject(queryable, %Subject{} = subject) do cond do diff --git a/elixir/apps/domain/lib/domain/relays/group.ex b/elixir/apps/domain/lib/domain/relays/group.ex index a264cf1f8..d4d82ba01 100644 --- a/elixir/apps/domain/lib/domain/relays/group.ex +++ b/elixir/apps/domain/lib/domain/relays/group.ex @@ -5,12 +5,14 @@ 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] 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 diff --git a/elixir/apps/domain/lib/domain/relays/group/changeset.ex b/elixir/apps/domain/lib/domain/relays/group/changeset.ex index 9656db181..0d7c8da2c 100644 --- a/elixir/apps/domain/lib/domain/relays/group/changeset.ex +++ b/elixir/apps/domain/lib/domain/relays/group/changeset.ex @@ -37,6 +37,7 @@ defmodule Domain.Relays.Group.Changeset do |> 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() diff --git a/elixir/apps/domain/lib/domain/relays/group/query.ex b/elixir/apps/domain/lib/domain/relays/group/query.ex index c30884664..4dfda8fc9 100644 --- a/elixir/apps/domain/lib/domain/relays/group/query.ex +++ b/elixir/apps/domain/lib/domain/relays/group/query.ex @@ -5,6 +5,7 @@ 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)) @@ -55,6 +56,7 @@ defmodule Domain.Relays.Group.Query do } ] + # 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 diff --git a/elixir/apps/domain/lib/domain/relays/relay.ex b/elixir/apps/domain/lib/domain/relays/relay.ex index 425c7b808..a600f3142 100644 --- a/elixir/apps/domain/lib/domain/relays/relay.ex +++ b/elixir/apps/domain/lib/domain/relays/relay.ex @@ -18,8 +18,6 @@ defmodule Domain.Relays.Relay do field :last_seen_version, :string field :last_seen_at, :utc_datetime_usec - belongs_to :last_used_token, Domain.Tokens.Token - field :stamp_secret, :string, virtual: true field :online?, :boolean, virtual: true @@ -27,6 +25,7 @@ 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 diff --git a/elixir/apps/domain/lib/domain/relays/relay/changeset.ex b/elixir/apps/domain/lib/domain/relays/relay/changeset.ex index 336fe405a..faf271ae2 100644 --- a/elixir/apps/domain/lib/domain/relays/relay/changeset.ex +++ b/elixir/apps/domain/lib/domain/relays/relay/changeset.ex @@ -1,6 +1,6 @@ defmodule Domain.Relays.Relay.Changeset do use Domain, :changeset - alias Domain.{Version, Auth, Tokens} + alias Domain.{Version, Auth} alias Domain.Relays @upsert_fields ~w[ipv4 ipv6 port name @@ -19,14 +19,15 @@ defmodule Domain.Relays.Relay.Changeset do last_seen_remote_ip_location_lon last_seen_version last_seen_at - last_used_token_id 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/} 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/} @@ -34,7 +35,7 @@ defmodule Domain.Relays.Relay.Changeset do def upsert_on_conflict, do: {:replace, @conflict_replace_fields} - def upsert(%Relays.Group{} = group, %Tokens.Token{} = token, attrs, %Auth.Context{} = context) do + def upsert(%Relays.Group{} = group, attrs, %Auth.Context{} = context) do %Relays.Relay{} |> cast(attrs, @upsert_fields) |> validate_required_one_of(~w[ipv4 ipv6]a) @@ -56,9 +57,9 @@ defmodule Domain.Relays.Relay.Changeset do |> put_relay_version() |> put_change(:account_id, group.account_id) |> put_change(:group_id, group.id) - |> put_change(:last_used_token_id, token.id) end + # TODO: HARD-DELETE - Remove after `deleted_at` is removed from DB def delete(%Relays.Relay{} = relay) do relay |> change() diff --git a/elixir/apps/domain/lib/domain/relays/relay/query.ex b/elixir/apps/domain/lib/domain/relays/relay/query.ex index 223635b87..b06a8d795 100644 --- a/elixir/apps/domain/lib/domain/relays/relay/query.ex +++ b/elixir/apps/domain/lib/domain/relays/relay/query.ex @@ -5,6 +5,7 @@ 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)) @@ -54,6 +55,7 @@ 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 diff --git a/elixir/apps/domain/lib/domain/resources.ex b/elixir/apps/domain/lib/domain/resources.ex index 3b4ec5661..860b28447 100644 --- a/elixir/apps/domain/lib/domain/resources.ex +++ b/elixir/apps/domain/lib/domain/resources.ex @@ -1,7 +1,8 @@ defmodule Domain.Resources do alias Domain.{Repo, Auth} - alias Domain.{Accounts, Gateways, Policies} + alias Domain.{Accounts, Gateways} alias Domain.Resources.{Authorizer, Resource, Connection} + require Logger def fetch_resource_by_id(id, %Auth.Subject{} = subject, opts \\ []) do required_permissions = @@ -262,44 +263,28 @@ defmodule Domain.Resources do end def delete_resource(%Resource{type: :internet}, %Auth.Subject{}) do - {:error, :cannot_delete_internet_resource} + {:error, :cant_delete_internet_resource} end def delete_resource(%Resource{} = resource, %Auth.Subject{} = subject) do - with :ok <- Auth.ensure_has_permissions(subject, Authorizer.manage_resources_permission()) do - Resource.Query.not_deleted() - |> Resource.Query.by_id(resource.id) - |> Authorizer.for_subject(Resource, subject) - |> Repo.fetch_and_update(Resource.Query, - with: fn resource -> - {_count, nil} = - Connection.Query.by_resource_id(resource.id) - |> Repo.delete_all() - - Resource.Changeset.delete(resource) - end - ) - |> case do - {:ok, resource} -> - {:ok, _policies} = Policies.delete_policies_for(resource, subject) - {:ok, resource} - - {:error, reason} -> - {:error, reason} - end + with :ok <- Authorizer.ensure_has_access_to(resource, subject) do + Repo.delete(resource) 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} = diff --git a/elixir/apps/domain/lib/domain/resources/authorizer.ex b/elixir/apps/domain/lib/domain/resources/authorizer.ex index 990a41c6b..748dafdeb 100644 --- a/elixir/apps/domain/lib/domain/resources/authorizer.ex +++ b/elixir/apps/domain/lib/domain/resources/authorizer.ex @@ -36,6 +36,14 @@ defmodule Domain.Resources.Authorizer do [] end + def ensure_has_access_to(%Resource{} = resource, %Subject{} = subject) do + if resource.account_id == subject.account.id do + Domain.Auth.ensure_has_permissions(subject, manage_resources_permission()) + else + {:error, :unauthorized} + end + end + def for_subject(queryable, Connection, %Subject{} = subject) do cond do has_permission?(subject, manage_resources_permission()) -> diff --git a/elixir/apps/domain/lib/domain/resources/resource.ex b/elixir/apps/domain/lib/domain/resources/resource.ex index 2567a9662..714f33ee7 100644 --- a/elixir/apps/domain/lib/domain/resources/resource.ex +++ b/elixir/apps/domain/lib/domain/resources/resource.ex @@ -45,6 +45,7 @@ defmodule Domain.Resources.Resource do # 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 :actor_groups, through: [:policies, :actor_group] @@ -58,6 +59,7 @@ defmodule Domain.Resources.Resource do 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 diff --git a/elixir/apps/domain/lib/domain/resources/resource/changeset.ex b/elixir/apps/domain/lib/domain/resources/resource/changeset.ex index 9038aded1..cc57fd145 100644 --- a/elixir/apps/domain/lib/domain/resources/resource/changeset.ex +++ b/elixir/apps/domain/lib/domain/resources/resource/changeset.ex @@ -67,6 +67,7 @@ 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() diff --git a/elixir/apps/domain/lib/domain/resources/resource/query.ex b/elixir/apps/domain/lib/domain/resources/resource/query.ex index c908d87dd..2dce043bf 100644 --- a/elixir/apps/domain/lib/domain/resources/resource/query.ex +++ b/elixir/apps/domain/lib/domain/resources/resource/query.ex @@ -5,6 +5,7 @@ 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)) @@ -192,6 +193,7 @@ 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 diff --git a/elixir/apps/domain/lib/domain/tokens.ex b/elixir/apps/domain/lib/domain/tokens.ex index f4327adf3..ac8731064 100644 --- a/elixir/apps/domain/lib/domain/tokens.ex +++ b/elixir/apps/domain/lib/domain/tokens.ex @@ -4,6 +4,7 @@ defmodule Domain.Tokens do alias Domain.{Auth, Actors, Relays, Gateways} alias Domain.Tokens.{Token, Authorizer, Jobs} require Ecto.Query + require Logger def start_link(_init_arg) do Supervisor.start_link(__MODULE__, nil, name: __MODULE__) @@ -182,6 +183,113 @@ defmodule Domain.Tokens do end def delete_token(%Token{} = token, %Auth.Subject{} = subject) do + with :ok <- Authorizer.ensure_has_access_to(token, subject) do + Repo.delete(token) + end + end + + def delete_token_for(%Auth.Subject{} = subject) do + {num_deleted, _} = + Token.Query.all() + |> Token.Query.by_id(subject.token_id) + |> Authorizer.for_subject(subject) + |> Repo.delete_all() + + {:ok, num_deleted} + end + + def delete_tokens_for(%Actors.Actor{} = actor, %Auth.Subject{} = subject) do + with :ok <- Auth.ensure_has_permissions(subject, Authorizer.manage_tokens_permission()) do + {num_deleted, _} = + Token.Query.all() + |> Token.Query.by_actor_id(actor.id) + |> Authorizer.for_subject(subject) + |> Repo.delete_all() + + {:ok, num_deleted} + end + end + + def delete_tokens_for(%Auth.Identity{} = identity, %Auth.Subject{} = subject) do + with :ok <- Auth.Authorizer.ensure_has_access_to(identity, subject), + :ok <- Auth.ensure_has_permissions(subject, Authorizer.manage_tokens_permission()) do + {num_deleted, _} = + Token.Query.all() + |> Token.Query.by_identity_id(identity.id) + |> Authorizer.for_subject(subject) + |> Repo.delete_all() + + {:ok, num_deleted} + end + end + + def delete_tokens_for(%Auth.Provider{} = provider, %Auth.Subject{} = subject) do + with :ok <- Auth.ensure_has_permissions(subject, Authorizer.manage_tokens_permission()) do + {num_deleted, _} = + Token.Query.all() + |> Token.Query.by_provider_id(provider.id) + |> Authorizer.for_subject(subject) + |> Repo.delete_all() + + {:ok, num_deleted} + end + end + + def delete_tokens_for(%Relays.Group{} = group, %Auth.Subject{} = subject) do + with :ok <- Auth.ensure_has_permissions(subject, Authorizer.manage_tokens_permission()) do + {num_deleted, _} = + Token.Query.all() + |> Token.Query.by_relay_group_id(group.id) + |> Authorizer.for_subject(subject) + |> Repo.delete_all() + + {:ok, num_deleted} + end + end + + def delete_tokens_for(%Gateways.Group{} = group, %Auth.Subject{} = subject) do + with :ok <- Auth.ensure_has_permissions(subject, Authorizer.manage_tokens_permission()) do + {num_deleted, _} = + Token.Query.all() + |> Token.Query.by_gateway_group_id(group.id) + |> Authorizer.for_subject(subject) + |> Repo.delete_all() + + {:ok, num_deleted} + end + end + + def delete_tokens_for(%Auth.Identity{} = identity) do + {num_deleted, _} = + Token.Query.all() + |> Token.Query.by_identity_id(identity.id) + |> Repo.delete_all() + + {:ok, num_deleted} + end + + def delete_all_tokens_by_type_and_assoc(:email, %Auth.Identity{} = identity) do + {num_deleted, _} = + Token.Query.not_deleted() + |> Token.Query.by_type(:email) + |> Token.Query.by_account_id(identity.account_id) + |> Token.Query.by_identity_id(identity.id) + |> Repo.delete_all() + + {:ok, num_deleted} + end + + def delete_expired_tokens do + {num_deleted, _} = + Token.Query.all() + |> Token.Query.expired() + |> Repo.delete_all() + + {: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, [ @@ -193,7 +301,7 @@ defmodule Domain.Tokens do Token.Query.not_deleted() |> Token.Query.by_id(token.id) |> Authorizer.for_subject(subject) - |> delete_tokens() + |> soft_delete_tokens() |> case do {:ok, [token]} -> {:ok, token} {:ok, []} -> {:error, :not_found} @@ -201,79 +309,89 @@ defmodule Domain.Tokens do end end - def delete_token_for(%Auth.Subject{} = subject) do + # 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) - |> delete_tokens() + |> soft_delete_tokens() end - def delete_tokens_for(%Auth.Identity{} = identity) do + # 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) - |> delete_tokens() + |> soft_delete_tokens() end - def delete_tokens_for(%Actors.Actor{} = actor, %Auth.Subject{} = subject) do + # 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) - |> delete_tokens() + |> soft_delete_tokens() end end - def delete_tokens_for(%Auth.Identity{} = identity, %Auth.Subject{} = subject) do + # 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) - |> delete_tokens() + |> soft_delete_tokens() end end - def delete_tokens_for(%Auth.Provider{} = provider, %Auth.Subject{} = subject) do + # 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) - |> delete_tokens() + |> soft_delete_tokens() end end - def delete_tokens_for(%Relays.Group{} = group, %Auth.Subject{} = subject) do + # 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) - |> delete_tokens() + |> soft_delete_tokens() end end - def delete_tokens_for(%Gateways.Group{} = group, %Auth.Subject{} = subject) do + # 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) - |> delete_tokens() + |> soft_delete_tokens() end end - def delete_all_tokens_by_type_and_assoc(:email, %Auth.Identity{} = identity) do + # 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) - |> delete_tokens() + |> soft_delete_tokens() end - def delete_expired_tokens do + # TODO: HARD-DELETE - Remove after `deleted_at` is remove from DB + def soft_delete_expired_tokens do Token.Query.not_deleted() |> Token.Query.expired() - |> delete_tokens() + |> soft_delete_tokens() end - defp delete_tokens(queryable) do + # TODO: HARD-DELETE - Remove after `deleted_at` is remove from DB + defp soft_delete_tokens(queryable) do {_count, tokens} = queryable |> Token.Query.delete() diff --git a/elixir/apps/domain/lib/domain/tokens/authorizer.ex b/elixir/apps/domain/lib/domain/tokens/authorizer.ex index 48d768a35..83bbc28c2 100644 --- a/elixir/apps/domain/lib/domain/tokens/authorizer.ex +++ b/elixir/apps/domain/lib/domain/tokens/authorizer.ex @@ -30,6 +30,22 @@ defmodule Domain.Tokens.Authorizer do [] end + def ensure_has_access_to(%Token{} = token, %Subject{} = subject) do + cond do + # If token belongs to same actor, check own permission + subject.account.id == token.account_id and owns_token?(token, subject) -> + Domain.Auth.ensure_has_permissions(subject, manage_own_tokens_permission()) + + # Otherwise, check global manage permission + subject.account.id == token.account_id -> + Domain.Auth.ensure_has_permissions(subject, manage_tokens_permission()) + + # Different account + true -> + {:error, :unauthorized} + end + end + @impl true def for_subject(queryable, %Subject{} = subject) do cond do @@ -42,4 +58,8 @@ defmodule Domain.Tokens.Authorizer do |> Token.Query.by_actor_id(subject.actor.id) end end + + defp owns_token?(token, subject) do + token.actor_id == subject.actor.id + end end diff --git a/elixir/apps/domain/lib/domain/tokens/token.ex b/elixir/apps/domain/lib/domain/tokens/token.ex index 6b140e8c6..36c1bca53 100644 --- a/elixir/apps/domain/lib/domain/tokens/token.ex +++ b/elixir/apps/domain/lib/domain/tokens/token.ex @@ -48,9 +48,9 @@ defmodule Domain.Tokens.Token do field :created_by_user_agent, :string field :created_by_remote_ip, Domain.Types.IP - has_many :clients, Domain.Clients.Client, foreign_key: :last_used_token_id - field :expires_at, :utc_datetime_usec + + # TODO: HARD-DELETE - Remove field after soft deletion is removed field :deleted_at, :utc_datetime_usec timestamps() end diff --git a/elixir/apps/domain/lib/domain/tokens/token/changeset.ex b/elixir/apps/domain/lib/domain/tokens/token/changeset.ex index fd2727d99..7a60f7a06 100644 --- a/elixir/apps/domain/lib/domain/tokens/token/changeset.ex +++ b/elixir/apps/domain/lib/domain/tokens/token/changeset.ex @@ -125,6 +125,7 @@ defmodule Domain.Tokens.Token.Changeset do |> 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() diff --git a/elixir/apps/domain/lib/domain/tokens/token/query.ex b/elixir/apps/domain/lib/domain/tokens/token/query.ex index 6ea1bf22a..87c0ebeeb 100644 --- a/elixir/apps/domain/lib/domain/tokens/token/query.ex +++ b/elixir/apps/domain/lib/domain/tokens/token/query.ex @@ -5,6 +5,7 @@ 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)) @@ -78,6 +79,7 @@ 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) diff --git a/elixir/apps/domain/priv/repo/migrations/20250814224632_drop_last_used_token_id_from_gateways.exs b/elixir/apps/domain/priv/repo/migrations/20250814224632_drop_last_used_token_id_from_gateways.exs new file mode 100644 index 000000000..710338671 --- /dev/null +++ b/elixir/apps/domain/priv/repo/migrations/20250814224632_drop_last_used_token_id_from_gateways.exs @@ -0,0 +1,9 @@ +defmodule Domain.Repo.Migrations.DropLastUsedTokenIdFromGateways do + use Ecto.Migration + + def change do + alter table(:gateways) do + remove(:last_used_token_id, references(:tokens, type: :binary_id, on_delete: :nilify_all)) + end + end +end diff --git a/elixir/apps/domain/priv/repo/migrations/20250814230437_drop_last_used_token_id_from_relays.exs b/elixir/apps/domain/priv/repo/migrations/20250814230437_drop_last_used_token_id_from_relays.exs new file mode 100644 index 000000000..46019994c --- /dev/null +++ b/elixir/apps/domain/priv/repo/migrations/20250814230437_drop_last_used_token_id_from_relays.exs @@ -0,0 +1,9 @@ +defmodule Domain.Repo.Migrations.DropLastUsedTokenIdFromRelays do + use Ecto.Migration + + def change do + alter table(:relays) do + remove(:last_used_token_id, references(:tokens, type: :binary_id, on_delete: :nilify_all)) + end + end +end diff --git a/elixir/apps/domain/priv/repo/migrations/20250815041334_drop_last_used_token_id_from_clients.exs b/elixir/apps/domain/priv/repo/migrations/20250815041334_drop_last_used_token_id_from_clients.exs new file mode 100644 index 000000000..bf83805b5 --- /dev/null +++ b/elixir/apps/domain/priv/repo/migrations/20250815041334_drop_last_used_token_id_from_clients.exs @@ -0,0 +1,9 @@ +defmodule Domain.Repo.Migrations.DropLastUsedTokenIdFromClients do + use Ecto.Migration + + def change do + alter table(:clients) do + remove(:last_used_token_id, references(:tokens, type: :binary_id, on_delete: :nilify_all)) + end + end +end diff --git a/elixir/apps/domain/priv/repo/seeds.exs b/elixir/apps/domain/priv/repo/seeds.exs index 6611e8a65..31e3ff69a 100644 --- a/elixir/apps/domain/priv/repo/seeds.exs +++ b/elixir/apps/domain/priv/repo/seeds.exs @@ -687,7 +687,6 @@ defmodule Domain.Repo.Seeds do {:ok, global_relay} = Relays.upsert_relay( global_relay_group, - global_relay_group_token, %{ ipv4: {189, 172, 72, 111}, ipv6: {0, 0, 0, 0, 0, 0, 0, 1} @@ -699,7 +698,6 @@ defmodule Domain.Repo.Seeds do {:ok, _global_relay} = Relays.upsert_relay( global_relay_group, - global_relay_group_token, %{ ipv4: {189, 172, 72, 111 + i}, ipv6: {0, 0, 0, 0, 0, 0, 0, i} @@ -744,7 +742,6 @@ defmodule Domain.Repo.Seeds do {:ok, relay} = Relays.upsert_relay( relay_group, - relay_group_token, %{ ipv4: {189, 172, 73, 111}, ipv6: {0, 0, 0, 0, 0, 0, 0, 1} @@ -760,7 +757,6 @@ defmodule Domain.Repo.Seeds do {:ok, _relay} = Relays.upsert_relay( relay_group, - relay_group_token, %{ ipv4: {189, 172, 73, 111 + i}, ipv6: {0, 0, 0, 0, 0, 0, 0, i} @@ -815,7 +811,6 @@ defmodule Domain.Repo.Seeds do {:ok, gateway1} = Gateways.upsert_gateway( gateway_group, - gateway_group_token, %{ external_id: Ecto.UUID.generate(), name: "gw-#{Domain.Crypto.random_token(5, encoder: :user_friendly)}", @@ -831,7 +826,6 @@ defmodule Domain.Repo.Seeds do {:ok, gateway2} = Gateways.upsert_gateway( gateway_group, - gateway_group_token, %{ external_id: Ecto.UUID.generate(), name: "gw-#{Domain.Crypto.random_token(5, encoder: :user_friendly)}", @@ -848,7 +842,6 @@ defmodule Domain.Repo.Seeds do {:ok, _gateway} = Gateways.upsert_gateway( gateway_group, - gateway_group_token, %{ external_id: Ecto.UUID.generate(), name: "gw-#{Domain.Crypto.random_token(5, encoder: :user_friendly)}", diff --git a/elixir/apps/domain/test/domain/actors_test.exs b/elixir/apps/domain/test/domain/actors_test.exs index b2d0859a9..0e92893fe 100644 --- a/elixir/apps/domain/test/domain/actors_test.exs +++ b/elixir/apps/domain/test/domain/actors_test.exs @@ -84,17 +84,18 @@ defmodule Domain.ActorsTest do assert fetch_group_by_id(group.id, subject) == {:error, :not_found} end - test "returns deleted groups", %{ - account: account, - subject: subject - } do - group = - Fixtures.Actors.create_group(account: account) - |> Fixtures.Actors.delete_group() + # 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 + # 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) @@ -155,6 +156,7 @@ 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 @@ -216,6 +218,7 @@ 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 @@ -288,6 +291,7 @@ 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) @@ -383,23 +387,6 @@ defmodule Domain.ActorsTest do assert length(peek[group.id].items) == 1 end - test "ignores deleted actors", %{ - account: account, - subject: subject - } do - group = Fixtures.Actors.create_group(account: account) - actor = Fixtures.Actors.create_actor(account: account) |> Fixtures.Actors.delete() - Fixtures.Actors.create_membership(account: account, group: group, actor: actor) - Fixtures.Actors.create_membership(account: account, group: group) - Fixtures.Actors.create_membership(account: account, group: group) - Fixtures.Actors.create_membership(account: account, group: group) - Fixtures.Actors.create_membership(account: account, group: group) - - assert {:ok, peek} = peek_group_actors([group], 3, subject) - assert peek[group.id].count == 4 - assert length(peek[group.id].items) == 3 - end - test "ignores other groups", %{ account: account, subject: subject @@ -520,20 +507,6 @@ defmodule Domain.ActorsTest do assert length(peek[actor.id].items) == 1 end - test "ignores deleted groups", %{ - account: account, - subject: subject - } do - actor = Fixtures.Actors.create_actor(account: account) - group = Fixtures.Actors.create_group(account: account) |> Fixtures.Actors.delete() - Fixtures.Actors.create_membership(account: account, group: group, actor: actor) - Fixtures.Actors.create_membership(account: account, group: group) - - assert {:ok, peek} = peek_actor_groups([actor], 3, subject) - assert peek[actor.id].count == 0 - assert Enum.empty?(peek[actor.id].items) - end - test "ignores other groups", %{ account: account, subject: subject @@ -630,8 +603,19 @@ defmodule Domain.ActorsTest do subject: subject } do actor = Fixtures.Actors.create_actor(account: account) - client = Fixtures.Clients.create_client(account: account, actor: actor) - Clients.Presence.connect(client) + actor_identity = Fixtures.Auth.create_identity(account: account, actor: actor) + + client = + Fixtures.Clients.create_client(account: account, actor: actor, identity: actor_identity) + + client_token = + Fixtures.Tokens.create_client_token( + account: account, + actor: actor, + identity: actor_identity + ) + + Clients.Presence.connect(client, client_token.id) assert {:ok, peek} = peek_actor_clients([actor], 3, subject) assert [%Clients.Client{} = client] = peek[actor.id].items @@ -654,20 +638,6 @@ defmodule Domain.ActorsTest do assert Enum.count(peek) == 1 end - test "ignores deleted clients", %{ - account: account, - subject: subject - } do - actor = Fixtures.Actors.create_actor(account: account) - - Fixtures.Clients.create_client(account: account, actor: actor) - |> Fixtures.Clients.delete_client() - - assert {:ok, peek} = peek_actor_clients([actor], 3, subject) - assert peek[actor.id].count == 0 - assert Enum.empty?(peek[actor.id].items) - end - test "ignores other clients", %{ account: account, subject: subject @@ -825,7 +795,7 @@ defmodule Domain.ActorsTest do provider_identifier: "G:GROUP_ID1" ) - _group2 = + group2 = Fixtures.Actors.create_group( account: account, provider: provider, @@ -862,6 +832,8 @@ defmodule Domain.ActorsTest do %{"name" => "Group:Finance", "provider_identifier" => "G:GROUP_ID4"} ] + deleted_group_ids = [group1.provider_identifier, group2.provider_identifier] + assert {:ok, %{ groups: [_group1, _group2, _group3, _group4, _group5], @@ -872,10 +844,9 @@ defmodule Domain.ActorsTest do }} = sync_provider_groups(provider, attrs_list) assert Enum.all?(["G:GROUP_ID1", "OU:OU_ID1"], &(&1 in delete)) - assert deleted_group1.provider_identifier in ["G:GROUP_ID1", "OU:OU_ID1"] - assert deleted_group2.provider_identifier in ["G:GROUP_ID1", "OU:OU_ID1"] - assert Repo.aggregate(Actors.Group, :count) == 5 - assert Repo.aggregate(Actors.Group.Query.not_deleted(), :count) == 3 + assert deleted_group1 in deleted_group_ids + assert deleted_group2 in deleted_group_ids + assert Repo.aggregate(Actors.Group, :count) == 3 assert Map.keys(group_ids_by_provider_identifier) |> length() == 3 end @@ -959,50 +930,52 @@ defmodule Domain.ActorsTest do }} end - 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" - ) + # TODO: HARD-DELETE - This test is no longer relevant - 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() + # 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" + # ) - group2 = - Fixtures.Actors.create_group( - account: account, - provider: provider, - provider_identifier: "G:GROUP_ID2", - name: "TO_BE_UPDATED" - ) + # 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() - 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"} - ] + # group2 = + # Fixtures.Actors.create_group( + # account: account, + # provider: provider, + # provider_identifier: "G:GROUP_ID2", + # name: "TO_BE_UPDATED" + # ) - provider_identifiers = Enum.map(attrs_list, & &1["provider_identifier"]) + # 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"} + # ] - assert {:ok, sync_data} = sync_provider_groups(provider, attrs_list) + # provider_identifiers = Enum.map(attrs_list, & &1["provider_identifier"]) - assert Enum.sort(Enum.map(sync_data.groups, & &1.name)) == - Enum.sort([deleted_group.name, group2.name]) + # assert {:ok, sync_data} = sync_provider_groups(provider, attrs_list) - assert sync_data.deleted == [] - assert sync_data.plan == {provider_identifiers, []} - end + # 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 @@ -1809,6 +1782,7 @@ defmodule Domain.ActorsTest do assert membership.actor_id == actor.id end + # TODO: HARD-DELETE - Is this test needed any more? test "removes memberships when managed group is deleted", %{ account: account, actor: actor, @@ -1997,19 +1971,25 @@ defmodule Domain.ActorsTest do } end - test "returns error on state conflict", %{account: account, subject: subject} do + test "raises error when deleting stale group structs", %{account: account, subject: subject} do group = Fixtures.Actors.create_group(account: account) assert {:ok, deleted} = delete_group(group, subject) - assert delete_group(deleted, subject) == {:error, :not_found} - assert delete_group(group, subject) == {:error, :not_found} + + assert_raise Ecto.StaleEntryError, fn -> + delete_group(deleted, subject) + end + + assert_raise Ecto.StaleEntryError, fn -> + delete_group(group, subject) + end end test "deletes groups", %{account: account, subject: subject} do group = Fixtures.Actors.create_group(account: account) - assert {:ok, deleted} = delete_group(group, subject) - assert deleted.deleted_at + assert {:ok, _deleted} = delete_group(group, subject) + refute Repo.get(Domain.Actors.Group, group.id) end test "deletes group memberships", %{account: account, subject: subject} do @@ -2021,7 +2001,8 @@ defmodule Domain.ActorsTest do assert Repo.aggregate(Actors.Membership, :count) == 0 end - test "deletes policies that use this group", %{ + # TODO: HARD-DELETE - Should this test be put in policies? + test "cascade deletes policies that use this group", %{ account: account, subject: subject } do @@ -2030,10 +2011,10 @@ defmodule Domain.ActorsTest do policy = Fixtures.Policies.create_policy(account: account, actor_group: group) other_policy = Fixtures.Policies.create_policy(account: account) - assert {:ok, _resource} = delete_group(group, subject) + assert {:ok, _group} = delete_group(group, subject) - refute is_nil(Repo.get_by(Domain.Policies.Policy, id: policy.id).deleted_at) - assert is_nil(Repo.get_by(Domain.Policies.Policy, id: other_policy.id).deleted_at) + refute Repo.get_by(Domain.Policies.Policy, id: policy.id) + assert Repo.get_by(Domain.Policies.Policy, id: other_policy.id) end test "returns error when subject has no permission to delete groups", %{ @@ -2043,11 +2024,7 @@ defmodule Domain.ActorsTest do subject = Fixtures.Auth.remove_permissions(subject) - assert delete_group(group, subject) == - {:error, - {:unauthorized, - reason: :missing_permissions, - missing_permissions: [Actors.Authorizer.manage_actors_permission()]}} + assert delete_group(group, subject) == {:error, :unauthorized} end test "raises if group is synced", %{ @@ -2061,7 +2038,7 @@ defmodule Domain.ActorsTest do end end - describe "delete_groups_for/2" do + describe "cascade delete on groups" do setup do account = Fixtures.Accounts.create_account() actor = Fixtures.Actors.create_actor(type: :account_admin_user, account: account) @@ -2079,68 +2056,20 @@ defmodule Domain.ActorsTest do } end - test "does nothing on state conflict", %{ + # TODO: HARD-DELETE - Is this test needed any more? + test "delete groups when provider is deleted", %{ account: account, provider: provider, subject: subject } do - Fixtures.Actors.create_group(account: account, provider: provider) + group1 = Fixtures.Actors.create_group(account: account, provider: provider) + group2 = Fixtures.Actors.create_group(account: account, provider: provider) - assert {:ok, [_deleted]} = delete_groups_for(provider, subject) - assert delete_groups_for(provider, subject) == {:ok, []} - assert delete_groups_for(provider, subject) == {:ok, []} - end + assert {:ok, _provider} = Auth.delete_provider(provider, subject) - test "deletes provider groups", %{account: account, provider: provider, subject: subject} do - group = Fixtures.Actors.create_group(account: account, provider: provider) - - assert {:ok, [deleted]} = delete_groups_for(provider, subject) - assert deleted.deleted_at - - refute is_nil(Repo.get(Actors.Group, group.id).deleted_at) - end - - test "deletes provider group memberships", %{ - account: account, - provider: provider, - subject: subject - } do - actor = Fixtures.Actors.create_actor(account: account) - group = Fixtures.Actors.create_group(account: account, provider: provider) - Fixtures.Actors.create_membership(account: account, actor: actor, group: group) - - assert {:ok, _deleted} = delete_groups_for(provider, subject) - - refute Repo.get_by(Actors.Membership, group_id: group.id) - end - - test "deletes policies that use deleted groups", %{ - account: account, - provider: provider, - subject: subject - } do - group = Fixtures.Actors.create_group(account: account, provider: provider) - - policy = Fixtures.Policies.create_policy(account: account, actor_group: group) - other_policy = Fixtures.Policies.create_policy(account: account) - - assert {:ok, _resource} = delete_groups_for(provider, subject) - - refute is_nil(Repo.get_by(Domain.Policies.Policy, id: policy.id).deleted_at) - assert is_nil(Repo.get_by(Domain.Policies.Policy, id: other_policy.id).deleted_at) - end - - test "returns error when subject has no permission to delete groups", %{ - provider: provider, - subject: subject - } do - subject = Fixtures.Auth.remove_permissions(subject) - - assert delete_groups_for(provider, subject) == - {:error, - {:unauthorized, - reason: :missing_permissions, - missing_permissions: [Actors.Authorizer.manage_actors_permission()]}} + refute Repo.get(Domain.Auth.Provider, provider.id) + refute Repo.get(Actors.Group, group1.id) + refute Repo.get(Actors.Group, group2.id) end end @@ -2191,16 +2120,20 @@ defmodule Domain.ActorsTest do end end - describe "group_deleted?/1" do - test "returns true for deleted groups" do + # 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.delete_group() - assert group_deleted?(group) == true + + 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_deleted?(group) == false + assert group_soft_deleted?(group) == false end end @@ -2228,15 +2161,6 @@ defmodule Domain.ActorsTest do assert count_users_for_account(account) == 0 end - - test "does not count deleted" do - account = Fixtures.Accounts.create_account() - - Fixtures.Actors.create_actor(type: :account_admin_user, account: account) - |> Fixtures.Actors.delete() - - assert count_users_for_account(account) == 0 - end end describe "count_account_admin_users_for_account/1" do @@ -2270,15 +2194,6 @@ defmodule Domain.ActorsTest do assert count_account_admin_users_for_account(account) == 0 end - - test "does not count deleted account admin actors" do - account = Fixtures.Accounts.create_account() - - Fixtures.Actors.create_actor(type: :account_admin_user, account: account) - |> Fixtures.Actors.delete() - - assert count_account_admin_users_for_account(account) == 0 - end end describe "count_service_accounts_for_account/1" do @@ -2990,9 +2905,7 @@ defmodule Domain.ActorsTest do subject = Fixtures.Auth.create_subject(identity: identity) assert {:ok, _actor} = disable_actor(actor, subject) - - assert token = Repo.get(Domain.Tokens.Token, subject.token_id) - assert token.deleted_at + refute Repo.get(Domain.Tokens.Token, subject.token_id) end test "returns error when trying to disable the last admin actor" do @@ -3146,13 +3059,9 @@ defmodule Domain.ActorsTest do other_actor = Fixtures.Actors.create_actor(type: :account_admin_user, account: account) assert {:ok, actor} = delete_actor(actor, subject) - assert actor.deleted_at + refute Repo.get(Actors.Actor, actor.id) - assert actor = Repo.get(Actors.Actor, actor.id) - assert actor.deleted_at - - assert other_actor = Repo.get(Actors.Actor, other_actor.id) - assert is_nil(other_actor.deleted_at) + assert Repo.get(Actors.Actor, other_actor.id) end test "updates managed group memberships", %{account: account, actor: actor, subject: subject} do @@ -3161,13 +3070,14 @@ defmodule Domain.ActorsTest do group = Fixtures.Actors.create_managed_group(account: account) assert {:ok, actor} = delete_actor(actor, subject) - assert actor.deleted_at + refute Repo.get(Domain.Actors.Actor, actor.id) group = Repo.preload(group, :memberships, force: true) assert [membership] = group.memberships 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, @@ -3176,24 +3086,22 @@ defmodule Domain.ActorsTest do Fixtures.Actors.create_actor(type: :account_admin_user, account: account) assert {:ok, _actor} = delete_actor(actor, subject) - - assert token = Repo.get(Domain.Tokens.Token, subject.token_id) - assert token.deleted_at + 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 } do actor_to_delete = Fixtures.Actors.create_actor(type: :account_admin_user, account: account) - Fixtures.Auth.create_identity(account: account, actor: actor_to_delete) + identity = Fixtures.Auth.create_identity(account: account, actor: actor_to_delete) - assert {:ok, actor} = delete_actor(actor_to_delete, subject) - assert actor.deleted_at - - assert Repo.aggregate(Domain.Auth.Identity.Query.not_deleted(), :count) == 1 + assert {:ok, _actor} = delete_actor(actor_to_delete, subject) + 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 @@ -3201,8 +3109,7 @@ defmodule Domain.ActorsTest do actor_to_delete = Fixtures.Actors.create_actor(type: :account_admin_user, account: account) Fixtures.Clients.create_client(account: account, actor: actor_to_delete) - assert {:ok, actor} = delete_actor(actor_to_delete, subject) - assert actor.deleted_at + assert {:ok, _actor} = delete_actor(actor_to_delete, subject) assert Repo.aggregate(Domain.Clients.Client.Query.not_deleted(), :count) == 0 end @@ -3260,69 +3167,70 @@ defmodule Domain.ActorsTest do assert delete_actor(actor, subject) == {:error, :cant_delete_the_last_admin} assert {:ok, service_account_actor} = delete_actor(service_account_actor, subject) - assert service_account_actor.deleted_at + refute Repo.get(Domain.Actors.Actor, service_account_actor.id) end - test "returns error when trying to delete the last admin actor using a race condition" do - for _ <- 0..50 do - test_pid = self() + # 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) + # Task.async(fn -> + # allow_child_sandbox_access(test_pid) - Domain.Config.put_env_override(:outbound_email_adapter_configured?, true) + # Domain.Config.put_env_override(:outbound_email_adapter_configured?, true) - account = Fixtures.Accounts.create_account() - provider = Fixtures.Auth.create_email_provider(account: account) + # 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_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 - ) + # 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_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 - ) + # 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) + # 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() + # 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) + # 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 + # assert Repo.aggregate(queryable, :count) == 1 + # end) + # end + # |> Task.await_many() + # end test "does not allow to delete an actor twice", %{ account: account, @@ -3331,7 +3239,10 @@ defmodule Domain.ActorsTest do other_actor = Fixtures.Actors.create_actor(type: :account_admin_user, account: account) assert {:ok, _actor} = delete_actor(other_actor, subject) - assert delete_actor(other_actor, subject) == {:error, :not_found} + + assert_raise Ecto.StaleEntryError, fn -> + delete_actor(other_actor, subject) + end end test "does not allow to delete actors in other accounts", %{ @@ -3339,7 +3250,7 @@ defmodule Domain.ActorsTest do } do other_actor = Fixtures.Actors.create_actor(type: :account_admin_user) - assert delete_actor(other_actor, subject) == {:error, :not_found} + assert delete_actor(other_actor, subject) == {:error, :unauthorized} end test "returns error when subject cannot delete actors" do @@ -3353,8 +3264,12 @@ defmodule Domain.ActorsTest do assert delete_actor(actor, subject) == {:error, {:unauthorized, - reason: :missing_permissions, - missing_permissions: [Actors.Authorizer.manage_actors_permission()]}} + [ + reason: :missing_permissions, + missing_permissions: [ + %Domain.Auth.Permission{resource: Domain.Actors.Actor, action: :manage} + ] + ]}} end end @@ -3446,11 +3361,12 @@ 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 deleted" do + test "returns true when actor is soft deleted" do actor = Fixtures.Actors.create_actor() - |> Fixtures.Actors.delete() + |> Fixtures.Actors.soft_delete() assert actor_deleted?(actor) == true end @@ -3478,10 +3394,11 @@ defmodule Domain.ActorsTest do end end - 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 + # 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 diff --git a/elixir/apps/domain/test/domain/auth/adapters/email_test.exs b/elixir/apps/domain/test/domain/auth/adapters/email_test.exs index e02993607..82287de43 100644 --- a/elixir/apps/domain/test/domain/auth/adapters/email_test.exs +++ b/elixir/apps/domain/test/domain/auth/adapters/email_test.exs @@ -133,8 +133,8 @@ defmodule Domain.Auth.Adapters.EmailTest do "request_sequence_number" => 2 } = identity.provider_state - assert Repo.get(Domain.Tokens.Token, first_token_id).deleted_at - refute Repo.get(Domain.Tokens.Token, second_token_id).deleted_at + refute Repo.get(Domain.Tokens.Token, first_token_id) + assert Repo.get(Domain.Tokens.Token, second_token_id) end end @@ -175,14 +175,11 @@ defmodule Domain.Auth.Adapters.EmailTest do assert {:ok, identity, nil} = verify_secret(identity, context, token) - assert %{last_used_token_id: token_id} = identity.provider_state + assert identity.provider_state == %{} assert identity.provider_virtual_state == %{} - token = Repo.get(Domain.Tokens.Token, token_id) - assert token.deleted_at - - token = Repo.get(Domain.Tokens.Token, other_token.id) - assert token.deleted_at + # Email verification tokens are cleaned up automatically + refute Repo.get(Domain.Tokens.Token, other_token.id) end test "returns error when token belongs to a different identity", %{ diff --git a/elixir/apps/domain/test/domain/auth/adapters/google_workspace/jobs/sync_directory_test.exs b/elixir/apps/domain/test/domain/auth/adapters/google_workspace/jobs/sync_directory_test.exs index c36737295..8ba87e2eb 100644 --- a/elixir/apps/domain/test/domain/auth/adapters/google_workspace/jobs/sync_directory_test.exs +++ b/elixir/apps/domain/test/domain/auth/adapters/google_workspace/jobs/sync_directory_test.exs @@ -677,8 +677,7 @@ defmodule Domain.Auth.Adapters.GoogleWorkspace.Jobs.SyncDirectoryTest do ) # Signs out users which identity has been deleted - deleted_identity_token = Repo.reload(deleted_identity_token) - assert deleted_identity_token.deleted_at + refute Repo.reload(deleted_identity_token) end test "resurrects deleted identities that reappear on the next sync", %{ diff --git a/elixir/apps/domain/test/domain/auth/adapters/jumpcloud/jobs/sync_directory_test.exs b/elixir/apps/domain/test/domain/auth/adapters/jumpcloud/jobs/sync_directory_test.exs index b99c1527a..0daf516ca 100644 --- a/elixir/apps/domain/test/domain/auth/adapters/jumpcloud/jobs/sync_directory_test.exs +++ b/elixir/apps/domain/test/domain/auth/adapters/jumpcloud/jobs/sync_directory_test.exs @@ -440,8 +440,7 @@ defmodule Domain.Auth.Adapters.JumpCloud.Jobs.SyncDirectoryTest do refute Repo.get_by(Domain.Actors.Membership, group_id: deleted_group.id) # Signs out users which identity has been deleted - deleted_identity_token = Repo.reload(deleted_identity_token) - assert deleted_identity_token.deleted_at + refute Repo.reload(deleted_identity_token) end test "resurrects deleted identities that reappear on the next sync", %{ diff --git a/elixir/apps/domain/test/domain/auth/adapters/microsoft_entra/jobs/sync_directory_test.exs b/elixir/apps/domain/test/domain/auth/adapters/microsoft_entra/jobs/sync_directory_test.exs index 274915530..0343d64a3 100644 --- a/elixir/apps/domain/test/domain/auth/adapters/microsoft_entra/jobs/sync_directory_test.exs +++ b/elixir/apps/domain/test/domain/auth/adapters/microsoft_entra/jobs/sync_directory_test.exs @@ -508,8 +508,7 @@ defmodule Domain.Auth.Adapters.MicrosoftEntra.Jobs.SyncDirectoryTest do ) # Signs out users which identity has been deleted - deleted_identity_token = Repo.reload(deleted_identity_token) - assert deleted_identity_token.deleted_at + refute Repo.reload(deleted_identity_token) end test "stops the sync retries on 401 error on the provider", %{ diff --git a/elixir/apps/domain/test/domain/auth/adapters/okta/jobs/sync_directory_test.exs b/elixir/apps/domain/test/domain/auth/adapters/okta/jobs/sync_directory_test.exs index e7dc275e9..583ef7239 100644 --- a/elixir/apps/domain/test/domain/auth/adapters/okta/jobs/sync_directory_test.exs +++ b/elixir/apps/domain/test/domain/auth/adapters/okta/jobs/sync_directory_test.exs @@ -742,8 +742,7 @@ defmodule Domain.Auth.Adapters.Okta.Jobs.SyncDirectoryTest do ) # Signs out users which identity has been deleted - deleted_identity_token = Repo.reload(deleted_identity_token) - assert deleted_identity_token.deleted_at + refute Repo.reload(deleted_identity_token) end test "resurrects deleted identities that reappear on the next sync", %{ diff --git a/elixir/apps/domain/test/domain/auth_test.exs b/elixir/apps/domain/test/domain/auth_test.exs index 86941b266..9cb31e4ac 100644 --- a/elixir/apps/domain/test/domain/auth_test.exs +++ b/elixir/apps/domain/test/domain/auth_test.exs @@ -56,12 +56,11 @@ defmodule Domain.AuthTest do assert fetch_provider_by_id("foo", subject) == {:error, :not_found} end - test "returns deleted provider", %{account: account, subject: subject} do + test "does not return deleted provider", %{account: account, subject: subject} do provider = Fixtures.Auth.create_userpass_provider(account: account) {:ok, _provider} = delete_provider(provider, subject) - assert {:ok, fetched_provider} = fetch_provider_by_id(provider.id, subject) - assert fetched_provider.id == provider.id + assert {:error, :not_found} = fetch_provider_by_id(provider.id, subject) end test "does not return provider from other accounts", %{subject: subject} do @@ -1032,7 +1031,8 @@ defmodule Domain.AuthTest do assert is_nil(other_provider.disabled_at) end - test "deletes tokens issues for provider identities", %{ + # 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 } do @@ -1050,8 +1050,7 @@ defmodule Domain.AuthTest do assert {:ok, _provider} = disable_provider(provider, subject) - assert token = Repo.get(Tokens.Token, token.id) - assert token.deleted_at + refute Repo.get(Tokens.Token, token.id) end test "returns error when trying to disable the last provider", %{ @@ -1207,10 +1206,8 @@ defmodule Domain.AuthTest do other_provider = Fixtures.Auth.create_userpass_provider(account: account) assert {:ok, provider} = delete_provider(provider, subject) - assert provider.deleted_at - assert provider = Repo.get(Auth.Provider, provider.id) - assert provider.deleted_at + refute Repo.get(Auth.Provider, provider.id) assert other_provider = Repo.get(Auth.Provider, other_provider.id) assert is_nil(other_provider.deleted_at) @@ -1234,11 +1231,8 @@ defmodule Domain.AuthTest do assert {:ok, _provider} = delete_provider(provider, subject) - assert identity = Repo.get(Auth.Identity, identity.id) - assert identity.deleted_at - - assert token = Repo.get(Tokens.Token, token.id) - assert token.deleted_at + refute Repo.get(Auth.Identity, identity.id) + refute Repo.get(Tokens.Token, token.id) end test "deletes provider actor groups", %{ @@ -1250,8 +1244,7 @@ defmodule Domain.AuthTest do assert {:ok, _provider} = delete_provider(provider, subject) - assert actor_group = Repo.get(Domain.Actors.Group, actor_group.id) - assert actor_group.deleted_at + refute Repo.get(Domain.Actors.Group, actor_group.id) end test "returns error when trying to delete the last provider", %{ @@ -1281,65 +1274,73 @@ defmodule Domain.AuthTest do assert delete_provider(provider, subject) == {:error, :cant_delete_the_last_provider} end - test "returns error when trying to delete the last provider using a race condition" do - for _ <- 0..50 do - test_pid = self() + # 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) + # Task.async(fn -> + # allow_child_sandbox_access(test_pid) - account = Fixtures.Accounts.create_account() + # account = Fixtures.Accounts.create_account() - provider_one = Fixtures.Auth.create_email_provider(account: account) - provider_two = Fixtures.Auth.create_userpass_provider(account: 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 - ) + # 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 - ) + # identity = + # Fixtures.Auth.create_identity( + # account: account, + # actor: actor, + # provider: provider_one + # ) - subject = Fixtures.Auth.create_subject(identity: identity) + # 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() + # 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 + # 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 "returns error when provider is already deleted", %{ + test "raises error when deleting stale provider structs", %{ subject: subject, account: account } do provider = Fixtures.Auth.create_userpass_provider(account: account) assert {:ok, deleted_provider} = delete_provider(provider, subject) - assert delete_provider(provider, subject) == {:error, :not_found} - assert delete_provider(deleted_provider, subject) == {:error, :not_found} + + assert_raise(Ecto.StaleEntryError, fn -> + delete_provider(provider, subject) + end) + + assert_raise(Ecto.StaleEntryError, fn -> + delete_provider(deleted_provider, subject) + end) end test "does not allow to delete providers in other accounts", %{ subject: subject } do provider = Fixtures.Auth.create_userpass_provider() - assert delete_provider(provider, subject) == {:error, :not_found} + + assert delete_provider(provider, subject) == {:error, :unauthorized} end test "returns error when subject cannot delete providers", %{ @@ -1351,8 +1352,12 @@ defmodule Domain.AuthTest do assert delete_provider(provider, subject) == {:error, {:unauthorized, - reason: :missing_permissions, - missing_permissions: [Authorizer.manage_providers_permission()]}} + [ + reason: :missing_permissions, + missing_permissions: [ + %Domain.Auth.Permission{resource: Domain.Auth.Provider, action: :manage} + ] + ]}} end end @@ -1650,7 +1655,7 @@ defmodule Domain.AuthTest do plan: {insert, [], []}, inserted: [_actor1, _actor2], updated: [], - deleted: [], + deleted: 0, actor_ids_by_provider_identifier: actor_ids_by_provider_identifier }} = sync_provider_identities(provider, attrs_list) @@ -1712,7 +1717,7 @@ defmodule Domain.AuthTest do %{ identities: [_identity1, _identity2], plan: {[], update, []}, - deleted: [], + deleted: 0, updated: [_updated_identity1, _updated_identity2], inserted: [], actor_ids_by_provider_identifier: actor_ids_by_provider_identifier @@ -1762,7 +1767,7 @@ defmodule Domain.AuthTest do %{ identities: [fetched_identity], plan: {[], ["USER_ID1"], []}, - deleted: [], + deleted: 0, inserted: [], actor_ids_by_provider_identifier: actor_ids_by_provider_identifier }} = sync_provider_identities(provider, attrs_list) @@ -1797,7 +1802,7 @@ defmodule Domain.AuthTest do %{ identities: [fetched_identity], plan: {[], [], []}, - deleted: [], + deleted: 0, inserted: [], actor_ids_by_provider_identifier: %{} }} = sync_provider_identities(provider, attrs_list) @@ -1808,91 +1813,88 @@ defmodule Domain.AuthTest do assert identity.deleted_at end - test "deletes removed identities", %{account: account, provider: provider} do - provider_identifiers = ["USER_ID1", "USER_ID2", "USER_ID3", "USER_ID4", "USER_ID5"] + # 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_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 = + # 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 - ) + # 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 + # 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" - } - ] + # 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: [deleted_identity1, deleted_identity2], - inserted: [], - actor_ids_by_provider_identifier: actor_ids_by_provider_identifier - }} = sync_provider_identities(provider, attrs_list) + # 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.sort(upsert) == ["USER_ID3", "USER_ID4", "USER_ID5"] - assert Enum.take(provider_identifiers, 2) - |> Enum.all?(&(&1 in delete)) + # assert Enum.take(provider_identifiers, 2) + # |> Enum.all?(&(&1 in delete)) - assert deleted_identity1.provider_identifier in delete - assert deleted_identity2.provider_identifier 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) == 5 + # assert Auth.Identity.Query.all() + # |> Auth.Identity.Query.by_provider_id(provider.id) + # |> Repo.aggregate(:count) == 3 - assert Auth.Identity.Query.not_deleted() - |> Auth.Identity.Query.by_provider_id(provider.id) - |> Repo.aggregate(:count) == 3 + # assert actor_ids_by_provider_identifier + # |> Map.keys() + # |> length() == 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 + # # 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, @@ -1947,7 +1949,7 @@ defmodule Domain.AuthTest do %{ identities: [], plan: {[], [], []}, - deleted: [], + deleted: 0, updated: [], inserted: [], actor_ids_by_provider_identifier: %{} @@ -2012,7 +2014,7 @@ defmodule Domain.AuthTest do %{ identities: [_identity1, _identity2], plan: {[], update, []}, - deleted: [], + deleted: 0, inserted: [], actor_ids_by_provider_identifier: actor_ids_by_provider_identifier }} = sync_provider_identities(provider, attrs_list) @@ -2532,16 +2534,16 @@ defmodule Domain.AuthTest do assert {:ok, deleted_identity} = delete_identity(identity, subject) assert deleted_identity.id == identity.id - assert deleted_identity.deleted_at - assert Repo.get(Auth.Identity, identity.id).deleted_at + refute Repo.get(Auth.Identity, identity.id) end - test "deletes identity that belongs to another actor with manage permission", %{ - account: account, - provider: provider, - subject: subject - } do + test "allows subject to delete identity that belongs to another actor with manage permission", + %{ + account: account, + provider: provider, + subject: subject + } do identity = Fixtures.Auth.create_identity(account: account, provider: provider) subject = @@ -2555,11 +2557,11 @@ defmodule Domain.AuthTest do assert {:ok, deleted_identity} = delete_identity(identity, subject) assert deleted_identity.id == identity.id - assert deleted_identity.deleted_at - assert Repo.get(Auth.Identity, identity.id).deleted_at + 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, @@ -2570,21 +2572,29 @@ defmodule Domain.AuthTest do assert {:ok, _deleted_identity} = delete_identity(identity, subject) - assert token = Repo.get(Domain.Tokens.Token, token.id) - assert token.deleted_at + refute Repo.get(Domain.Tokens.Token, token.id) end test "does not delete identity that belongs to another actor with manage_own permission", %{ + account: account, subject: subject } do - identity = Fixtures.Auth.create_identity() + identity = Fixtures.Auth.create_identity(account: account) subject = subject |> Fixtures.Auth.remove_permissions() |> Fixtures.Auth.add_permission(Authorizer.manage_own_identities_permission()) - assert delete_identity(identity, subject) == {:error, :not_found} + assert delete_identity(identity, subject) == + {:error, + {:unauthorized, + [ + reason: :missing_permissions, + missing_permissions: [ + %Domain.Auth.Permission{resource: Domain.Auth.Identity, action: :manage} + ] + ]}} end test "does not delete identity that belongs to another actor with just view permission", %{ @@ -2597,10 +2607,10 @@ defmodule Domain.AuthTest do |> Fixtures.Auth.remove_permissions() |> Fixtures.Auth.add_permission(Authorizer.manage_own_identities_permission()) - assert delete_identity(identity, subject) == {:error, :not_found} + assert delete_identity(identity, subject) == {:error, :unauthorized} end - test "returns error when identity does not exist", %{ + test "raises error when deleting stale identity structs", %{ account: account, provider: provider, actor: actor, @@ -2609,7 +2619,10 @@ defmodule Domain.AuthTest do identity = Fixtures.Auth.create_identity(account: account, provider: provider, actor: actor) assert {:ok, _identity} = delete_identity(identity, subject) - assert delete_identity(identity, subject) == {:error, :not_found} + + assert_raise Ecto.StaleEntryError, fn -> + delete_identity(identity, subject) + end end test "returns error when subject cannot delete identities", %{subject: subject} do @@ -2617,17 +2630,7 @@ defmodule Domain.AuthTest do subject = Fixtures.Auth.remove_permissions(subject) - assert delete_identity(identity, subject) == - {:error, - {:unauthorized, - reason: :missing_permissions, - missing_permissions: [ - {:one_of, - [ - Authorizer.manage_identities_permission(), - Authorizer.manage_own_identities_permission() - ]} - ]}} + assert delete_identity(identity, subject) == {:error, :unauthorized} end end @@ -2656,7 +2659,7 @@ defmodule Domain.AuthTest do } end - test "removes all identities and flows that belong to an actor", %{ + test "removes all identities that belong to an actor", %{ account: account, provider: provider, subject: subject @@ -2668,12 +2671,12 @@ defmodule Domain.AuthTest do all_identities_query = Auth.Identity.Query.all() assert Repo.aggregate(all_identities_query, :count) == 4 - assert delete_identities_for(actor, subject) == :ok + assert delete_identities_for(actor, subject) == {:ok, 3} - assert Repo.aggregate(all_identities_query, :count) == 4 + assert Repo.aggregate(all_identities_query, :count) == 1 by_actor_id_query = - Auth.Identity.Query.not_deleted() + Auth.Identity.Query.all() |> Auth.Identity.Query.by_actor_id(actor.id) assert Repo.aggregate(by_actor_id_query, :count) == 0 @@ -2691,9 +2694,9 @@ defmodule Domain.AuthTest do all_identities_query = Auth.Identity.Query.all() assert Repo.aggregate(all_identities_query, :count) == 4 - assert delete_identities_for(provider, subject) == :ok + assert {:ok, 4} = delete_identities_for(provider, subject) - assert Repo.aggregate(all_identities_query, :count) == 4 + assert Repo.aggregate(all_identities_query, :count) == 0 by_provider_id_query = Auth.Identity.Query.not_deleted() @@ -2711,10 +2714,9 @@ defmodule Domain.AuthTest do identity = Fixtures.Auth.create_identity(account: account, provider: provider, actor: actor) token = Fixtures.Tokens.create_token(account: account, identity: identity) - assert delete_identities_for(actor, subject) == :ok + assert delete_identities_for(actor, subject) == {:ok, 1} - assert token = Repo.get(Domain.Tokens.Token, token.id) - assert token.deleted_at + refute Repo.get(Domain.Tokens.Token, token.id) end test "does not remove identities that belong to another actor", %{ @@ -2724,7 +2726,8 @@ defmodule Domain.AuthTest do } do actor = Fixtures.Actors.create_actor(account: account, provider: provider) Fixtures.Auth.create_identity(account: account, provider: provider) - assert delete_identities_for(actor, subject) == :ok + + assert delete_identities_for(actor, subject) == {:ok, 0} assert Repo.aggregate(Auth.Identity.Query.all(), :count) == 2 end @@ -2750,18 +2753,19 @@ defmodule Domain.AuthTest do end end - describe "identity_deleted?/1" do + # 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_deleted?(identity) == true + assert identity_soft_deleted?(identity) == true end test "returns false when identity is not deleted" do identity = Fixtures.Auth.create_identity() - assert identity_deleted?(identity) == false + assert identity_soft_deleted?(identity) == false end end @@ -3104,48 +3108,50 @@ defmodule Domain.AuthTest do {:error, :unauthorized} end - 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" + # 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() + # 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) + # 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 + # secret = identity.provider_virtual_state.nonce <> identity.provider_virtual_state.fragment - assert sign_in(provider, identity.provider_identifier, nonce, secret, context) == - {:error, :unauthorized} - end + # assert sign_in(provider, identity.provider_identifier, nonce, secret, context) == + # {:error, :unauthorized} + # end - 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" + # 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) + # 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 + # 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 + # assert sign_in(provider, identity.provider_identifier, nonce, secret, context) == + # {:error, :unauthorized} + # end end describe "sign_in/3" do @@ -3606,7 +3612,7 @@ defmodule Domain.AuthTest do assert redirect_url =~ "client_id=#{provider.adapter_config["client_id"]}" assert redirect_url =~ "post_logout_redirect_uri=#{post_redirect_url}" - assert Repo.get(Tokens.Token, subject.token_id).deleted_at + refute Repo.get(Tokens.Token, subject.token_id) end test "returns identity and url without changes for other providers" do @@ -3619,7 +3625,7 @@ defmodule Domain.AuthTest do assert {:ok, %Auth.Identity{}, "https://fz.d/sign_out"} = sign_out(subject, "https://fz.d/sign_out") - assert Repo.get(Tokens.Token, subject.token_id).deleted_at + refute Repo.get(Tokens.Token, subject.token_id) end end @@ -4321,10 +4327,11 @@ defmodule Domain.AuthTest do end end - 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 + # 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 diff --git a/elixir/apps/domain/test/domain/clients_test.exs b/elixir/apps/domain/test/domain/clients_test.exs index c25adffeb..2b3cab541 100644 --- a/elixir/apps/domain/test/domain/clients_test.exs +++ b/elixir/apps/domain/test/domain/clients_test.exs @@ -95,7 +95,7 @@ defmodule Domain.ClientsTest do assert fetch_client_by_id("foo", subject) == {:error, :not_found} end - test "returns deleted clients", %{ + test "does not return deleted clients", %{ unprivileged_actor: actor, unprivileged_subject: subject } do @@ -103,7 +103,7 @@ defmodule Domain.ClientsTest do Fixtures.Clients.create_client(actor: actor) |> Fixtures.Clients.delete_client() - assert {:ok, _client} = fetch_client_by_id(client.id, subject) + assert {:error, :not_found} = fetch_client_by_id(client.id, subject) end test "returns client by id", %{unprivileged_actor: actor, unprivileged_subject: subject} do @@ -114,14 +114,21 @@ defmodule Domain.ClientsTest do {:ok, client} end - test "preloads online status", %{unprivileged_actor: actor, unprivileged_subject: subject} do - client = Fixtures.Clients.create_client(actor: actor) + test "preloads online status", %{account: account, unprivileged_subject: subject} do + client = Fixtures.Clients.create_client(actor: subject.actor) + + client_token = + Fixtures.Tokens.create_client_token( + account: account, + actor: subject.actor, + identity: subject.identity + ) assert {:ok, client} = fetch_client_by_id(client.id, subject, preload: [:online?]) assert client.online? == false {:ok, _} = Clients.Presence.Account.track(client.account_id, client.id) - {:ok, _} = Clients.Presence.Actor.track(client.actor_id, client.id) + {:ok, _} = Clients.Presence.Actor.track(client.actor_id, client.id, client_token.id) assert {:ok, client} = fetch_client_by_id(client.id, subject, preload: [:online?]) assert client.online? == true @@ -222,14 +229,21 @@ defmodule Domain.ClientsTest do assert fetch_client_by_id!(client.id, preload: [:online?, :identity]) == client end - test "preloads online status", %{unprivileged_actor: actor} do - client = Fixtures.Clients.create_client(actor: actor) + test "preloads online status", %{account: account, unprivileged_subject: subject} do + client = Fixtures.Clients.create_client(actor: subject.actor) + + client_token = + Fixtures.Tokens.create_client_token( + account: account, + actor: subject.actor, + identity: subject.identity + ) assert client = fetch_client_by_id!(client.id, preload: [:online?]) assert client.online? == false {:ok, _} = Clients.Presence.Account.track(client.account_id, client.id) - {:ok, _} = Clients.Presence.Actor.track(client.actor_id, client.id) + {:ok, _} = Clients.Presence.Actor.track(client.actor_id, client.id, client_token.id) assert client = fetch_client_by_id!(client.id, preload: [:online?]) assert client.online? == true @@ -283,14 +297,21 @@ defmodule Domain.ClientsTest do assert length(clients) == 2 end - test "preloads online status", %{unprivileged_actor: actor, unprivileged_subject: subject} do - Fixtures.Clients.create_client(actor: actor) + test "preloads online status", %{account: account, unprivileged_subject: subject} do + Fixtures.Clients.create_client(actor: subject.actor) + + client_token = + Fixtures.Tokens.create_client_token( + account: account, + actor: subject.actor, + identity: subject.identity + ) assert {:ok, [client], _metadata} = list_clients(subject, preload: [:online?]) assert client.online? == false {:ok, _} = Clients.Presence.Account.track(client.account_id, client.id) - {:ok, _} = Clients.Presence.Actor.track(client.actor_id, client.id) + {:ok, _} = Clients.Presence.Actor.track(client.actor_id, client.id, client_token.id) assert {:ok, [client], _metadata} = list_clients(subject, preload: [:online?]) assert client.online? == true @@ -957,7 +978,8 @@ defmodule Domain.ClientsTest do client = Fixtures.Clients.create_client() attrs = %{name: "new name"} - assert update_client(client, attrs, subject) == {:error, :not_found} + assert update_client(client, attrs, subject) == + {:error, {:unauthorized, [reason: :incorrect_account]}} end test "does not allow to reset required fields to empty values", %{ @@ -1006,22 +1028,22 @@ defmodule Domain.ClientsTest do admin_subject: subject } do client = Fixtures.Clients.create_client(actor: actor) - subject = Fixtures.Auth.remove_permissions(subject) assert update_client(client, %{}, subject) == {:error, {:unauthorized, - reason: :missing_permissions, - missing_permissions: [Clients.Authorizer.manage_own_clients_permission()]}} + [ + reason: :missing_permissions, + missing_permissions: [ + %Domain.Auth.Permission{resource: Domain.Clients.Client, action: :manage_own} + ] + ]}} client = Fixtures.Clients.create_client() assert update_client(client, %{}, subject) == - {:error, - {:unauthorized, - reason: :missing_permissions, - missing_permissions: [Clients.Authorizer.manage_clients_permission()]}} + {:error, {:unauthorized, [reason: :incorrect_account]}} end end @@ -1098,19 +1120,11 @@ defmodule Domain.ClientsTest do end describe "delete_client/2" do - test "returns error on state conflict", %{admin_actor: actor, admin_subject: subject} do - client = Fixtures.Clients.create_client(actor: actor) - - assert {:ok, deleted} = delete_client(client, subject) - assert delete_client(deleted, subject) == {:error, :not_found} - assert delete_client(client, subject) == {:error, :not_found} - end - test "admin can delete own clients", %{admin_actor: actor, admin_subject: subject} do client = Fixtures.Clients.create_client(actor: actor) - assert {:ok, deleted} = delete_client(client, subject) - assert deleted.deleted_at + assert {:ok, _client} = delete_client(client, subject) + refute Repo.get(Clients.Client, client.id) end test "admin can delete other people clients", %{ @@ -1119,8 +1133,8 @@ defmodule Domain.ClientsTest do } do client = Fixtures.Clients.create_client(actor: actor) - assert {:ok, deleted} = delete_client(client, subject) - assert deleted.deleted_at + assert {:ok, _client} = delete_client(client, subject) + refute Repo.get(Clients.Client, client.id) end test "admin cannot delete clients in other accounts", %{ @@ -1128,39 +1142,43 @@ defmodule Domain.ClientsTest do } do client = Fixtures.Clients.create_client() - assert delete_client(client, subject) == {:error, :not_found} + assert delete_client(client, subject) == + {:error, {:unauthorized, [reason: :incorrect_account]}} + + assert c = Repo.get(Clients.Client, client.id) + assert c.id == client.id end - test "unprivileged can delete own clients", %{ + test "unprivileged actor can delete own clients", %{ account: account, unprivileged_actor: actor, unprivileged_subject: subject } do client = Fixtures.Clients.create_client(account: account, actor: actor) - assert {:ok, deleted} = delete_client(client, subject) - assert deleted.deleted_at + assert {:ok, _client} = delete_client(client, subject) + refute Repo.get(Clients.Client, client.id) end - test "unprivileged cannot delete other people clients", %{ + test "unprivileged actor cannot delete other actor clients", %{ account: account, unprivileged_subject: subject } do client = Fixtures.Clients.create_client() assert delete_client(client, subject) == - {:error, - {:unauthorized, - reason: :missing_permissions, - missing_permissions: [Clients.Authorizer.manage_clients_permission()]}} + {:error, {:unauthorized, [reason: :incorrect_account]}} client = Fixtures.Clients.create_client(account: account) assert delete_client(client, subject) == {:error, {:unauthorized, - reason: :missing_permissions, - missing_permissions: [Clients.Authorizer.manage_clients_permission()]}} + [ + {:reason, :missing_permissions}, + {:missing_permissions, + [%Domain.Auth.Permission{resource: Domain.Clients.Client, action: :manage}]} + ]}} assert Repo.aggregate(Clients.Client, :count) == 2 end @@ -1176,16 +1194,34 @@ defmodule Domain.ClientsTest do assert delete_client(client, subject) == {:error, {:unauthorized, - reason: :missing_permissions, - missing_permissions: [Clients.Authorizer.manage_own_clients_permission()]}} + [ + reason: :missing_permissions, + missing_permissions: [ + %Domain.Auth.Permission{resource: Domain.Clients.Client, action: :manage_own} + ] + ]}} client = Fixtures.Clients.create_client() assert delete_client(client, subject) == - {:error, - {:unauthorized, - reason: :missing_permissions, - missing_permissions: [Clients.Authorizer.manage_clients_permission()]}} + {:error, {:unauthorized, [reason: :incorrect_account]}} + end + + test "raises error when deleting stale client structs", %{ + admin_actor: actor, + admin_subject: subject + } do + client = Fixtures.Clients.create_client(actor: actor) + + assert {:ok, deleted} = delete_client(client, subject) + + assert_raise Ecto.StaleEntryError, fn -> + delete_client(deleted, subject) + end + + assert_raise Ecto.StaleEntryError, fn -> + delete_client(client, subject) + end end end diff --git a/elixir/apps/domain/test/domain/gateways_test.exs b/elixir/apps/domain/test/domain/gateways_test.exs index 8bfdffb54..5d3532750 100644 --- a/elixir/apps/domain/test/domain/gateways_test.exs +++ b/elixir/apps/domain/test/domain/gateways_test.exs @@ -51,16 +51,15 @@ defmodule Domain.GatewaysTest do assert fetch_group_by_id(group.id, subject) == {:error, :not_found} end - test "returns deleted groups", %{ + test "does not return deleted groups", %{ account: account, subject: subject } do - group = + {:ok, deleted_group} = Fixtures.Gateways.create_group(account: account) |> Fixtures.Gateways.delete_group() - assert {:ok, fetched_group} = fetch_group_by_id(group.id, subject) - assert fetched_group.id == group.id + assert {:error, :not_found} = fetch_group_by_id(deleted_group.id, subject) end test "returns group by id", %{account: account, subject: subject} do @@ -344,19 +343,25 @@ defmodule Domain.GatewaysTest do end describe "delete_group/2" do - test "returns error on state conflict", %{account: account, subject: subject} do + test "raises error when deleting stale group structs", %{account: account, subject: subject} do group = Fixtures.Gateways.create_group(account: account) assert {:ok, deleted} = delete_group(group, subject) - assert delete_group(deleted, subject) == {:error, :not_found} - assert delete_group(group, subject) == {:error, :not_found} + + assert_raise Ecto.StaleEntryError, fn -> + delete_group(deleted, subject) + end + + assert_raise Ecto.StaleEntryError, fn -> + delete_group(group, subject) + end end - test "deletes groups", %{account: account, subject: subject} do + test "deletes group", %{account: account, subject: subject} do group = Fixtures.Gateways.create_group(account: account) - assert {:ok, deleted} = delete_group(group, subject) - assert deleted.deleted_at + assert {:ok, _deleted_group} = delete_group(group, subject) + refute Repo.get(Gateways.Group, group.id) end test "deletes all tokens when group is deleted", %{account: account, subject: subject} do @@ -365,16 +370,14 @@ defmodule Domain.GatewaysTest do Fixtures.Gateways.create_token(account: account, group: [account: account]) assert {:ok, deleted} = delete_group(group, subject) - assert deleted.deleted_at + refute Repo.reload(deleted) tokens = Domain.Tokens.Token.Query.all() |> Domain.Tokens.Token.Query.by_gateway_group_id(group.id) |> Repo.all() - |> Enum.filter(fn token -> token.gateway_group_id == group.id end) - assert length(tokens) > 0 - assert Enum.all?(tokens, & &1.deleted_at) + assert length(tokens) == 0 end test "deletes all gateways when group is deleted", %{account: account, subject: subject} do @@ -388,8 +391,7 @@ defmodule Domain.GatewaysTest do |> Domain.Gateways.Gateway.Query.by_group_id(group.id) |> Repo.all() - assert length(gateways) > 0 - assert Enum.all?(gateways, & &1.deleted_at) + assert length(gateways) == 0 end test "deletes all connections when group is deleted", %{account: account, subject: subject} do @@ -421,11 +423,8 @@ defmodule Domain.GatewaysTest do assert {:ok, _group} = delete_group(group, subject) - token1 = Repo.reload(token1) - token2 = Repo.reload(token2) - - assert token1.deleted_at - assert token2.deleted_at + refute Repo.reload(token1) + refute Repo.reload(token2) end test "returns error when subject has no permission to delete groups", %{ @@ -435,11 +434,7 @@ defmodule Domain.GatewaysTest do subject = Fixtures.Auth.remove_permissions(subject) - assert delete_group(group, subject) == - {:error, - {:unauthorized, - reason: :missing_permissions, - missing_permissions: [Gateways.Authorizer.manage_gateways_permission()]}} + assert delete_group(group, subject) == {:error, :unauthorized} end end @@ -573,15 +568,16 @@ defmodule Domain.GatewaysTest do assert fetch_gateway_by_id(gateway.id, subject) == {:error, :not_found} end - test "returns deleted gateways", %{ + test "does not return deleted gateways", %{ account: account, subject: subject } do - gateway = + deleted_gateway = Fixtures.Gateways.create_gateway(account: account) |> Fixtures.Gateways.delete_gateway() - assert fetch_gateway_by_id(gateway.id, subject, preload: :online?) == {:ok, gateway} + assert fetch_gateway_by_id(deleted_gateway.id, subject, preload: :online?) == + {:error, :not_found} end test "returns gateway by id", %{account: account, subject: subject} do @@ -650,7 +646,12 @@ defmodule Domain.GatewaysTest do } do offline_gateway = Fixtures.Gateways.create_gateway(account: account) online_gateway = Fixtures.Gateways.create_gateway(account: account) - :ok = Gateways.Presence.connect(online_gateway) + online_gateway = Repo.preload(online_gateway, :group) + + gateway_token = + Fixtures.Gateways.create_token(account: account, group: online_gateway.group) + + :ok = Gateways.Presence.connect(online_gateway, gateway_token.id) Fixtures.Gateways.create_gateway() assert {:ok, gateways, _metadata} = list_gateways(subject, preload: :online?) @@ -727,7 +728,9 @@ defmodule Domain.GatewaysTest do ) |> to_cache() - assert Gateways.Presence.connect(gateway) == :ok + gateway = Repo.preload(gateway, :group) + gateway_token = Fixtures.Gateways.create_token(account: account, group: gateway.group) + assert Gateways.Presence.connect(gateway, gateway_token.id) == :ok assert {:ok, [connected_gateway]} = all_compatible_gateways_for_client_and_resource(client, resource, subject) @@ -750,7 +753,9 @@ defmodule Domain.GatewaysTest do ) |> to_cache() - assert Gateways.Presence.connect(gateway) == :ok + gateway = Repo.preload(gateway, :group) + gateway_token = Fixtures.Gateways.create_token(account: account, group: gateway.group) + assert Gateways.Presence.connect(gateway, gateway_token.id) == :ok assert all_compatible_gateways_for_client_and_resource(client, resource, subject) == {:ok, []} @@ -782,7 +787,9 @@ defmodule Domain.GatewaysTest do ) |> to_cache() - assert Gateways.Presence.connect(gateway) == :ok + gateway = Repo.preload(gateway, :group) + gateway_token = Fixtures.Gateways.create_token(account: account, group: gateway.group) + assert Gateways.Presence.connect(gateway, gateway_token.id) == :ok assert all_compatible_gateways_for_client_and_resource(client, resource, subject) == {:ok, []} @@ -796,7 +803,9 @@ defmodule Domain.GatewaysTest do resource = Fixtures.Resources.create_resource(account: account) |> to_cache() gateway = Fixtures.Gateways.create_gateway(account: account) - assert Gateways.Presence.connect(gateway) == :ok + gateway = Repo.preload(gateway, :group) + gateway_token = Fixtures.Gateways.create_token(account: account, group: gateway.group) + assert Gateways.Presence.connect(gateway, gateway_token.id) == :ok assert all_compatible_gateways_for_client_and_resource(client, resource, subject) == {:ok, []} @@ -840,15 +849,14 @@ defmodule Domain.GatewaysTest do test "returns errors on invalid attrs", %{ context: context, - group: group, - token: token + group: group } do attrs = %{ external_id: nil, public_key: "x" } - assert {:error, changeset} = upsert_gateway(group, token, attrs, context) + assert {:error, changeset} = upsert_gateway(group, attrs, context) assert errors_on(changeset) == %{ public_key: ["should be 44 character(s)", "must be a base64-encoded string"], @@ -858,14 +866,13 @@ defmodule Domain.GatewaysTest do test "allows creating gateway with just required attributes", %{ context: context, - group: group, - token: token + group: group } do attrs = Fixtures.Gateways.gateway_attrs() |> Map.delete(:name) - assert {:ok, gateway} = upsert_gateway(group, token, attrs, context) + assert {:ok, gateway} = upsert_gateway(group, attrs, context) assert gateway.name assert gateway.public_key == attrs.public_key @@ -888,8 +895,7 @@ defmodule Domain.GatewaysTest do test "updates gateway when it already exists", %{ account: account, context: context, - group: group, - token: token + group: group } do gateway = Fixtures.Gateways.create_gateway(account: account, group: group) attrs = Fixtures.Gateways.gateway_attrs(external_id: gateway.external_id) @@ -900,7 +906,7 @@ defmodule Domain.GatewaysTest do user_agent: "iOS/12.5 (iPhone) connlib/0.7.413" } - assert {:ok, updated_gateway} = upsert_gateway(group, token, attrs, context) + assert {:ok, updated_gateway} = upsert_gateway(group, attrs, context) assert Repo.aggregate(Gateways.Gateway, :count, :id) == 1 @@ -936,8 +942,7 @@ defmodule Domain.GatewaysTest do test "does not reserve additional addresses on update", %{ account: account, context: context, - group: group, - token: token + group: group } do gateway = Fixtures.Gateways.create_gateway(account: account, group: group) @@ -948,7 +953,7 @@ defmodule Domain.GatewaysTest do last_seen_remote_ip: %Postgrex.INET{address: {100, 64, 100, 100}} ) - assert {:ok, updated_gateway} = upsert_gateway(group, token, attrs, context) + assert {:ok, updated_gateway} = upsert_gateway(group, attrs, context) addresses = Domain.Network.Address @@ -965,11 +970,10 @@ defmodule Domain.GatewaysTest do test "does not allow to reuse IP addresses", %{ account: account, context: context, - group: group, - token: token + group: group } do attrs = Fixtures.Gateways.gateway_attrs() - assert {:ok, gateway} = upsert_gateway(group, token, attrs, context) + assert {:ok, gateway} = upsert_gateway(group, attrs, context) addresses = Domain.Network.Address @@ -1055,19 +1059,25 @@ defmodule Domain.GatewaysTest do end describe "delete_gateway/2" do - test "returns error on state conflict", %{account: account, subject: subject} do + test "raises error when deleting stale gateway structs", %{account: account, subject: subject} do gateway = Fixtures.Gateways.create_gateway(account: account) assert {:ok, deleted} = delete_gateway(gateway, subject) - assert delete_gateway(deleted, subject) == {:error, :not_found} - assert delete_gateway(gateway, subject) == {:error, :not_found} + + assert_raise Ecto.StaleEntryError, fn -> + delete_gateway(deleted, subject) + end + + assert_raise Ecto.StaleEntryError, fn -> + delete_gateway(gateway, subject) + end end test "deletes gateways", %{account: account, subject: subject} do gateway = Fixtures.Gateways.create_gateway(account: account) assert {:ok, deleted} = delete_gateway(gateway, subject) - assert deleted.deleted_at + refute Repo.reload(deleted) end test "returns error when subject has no permission to delete gateways", %{ @@ -1077,11 +1087,7 @@ defmodule Domain.GatewaysTest do subject = Fixtures.Auth.remove_permissions(subject) - assert delete_gateway(gateway, subject) == - {:error, - {:unauthorized, - reason: :missing_permissions, - missing_permissions: [Gateways.Authorizer.manage_gateways_permission()]}} + assert delete_gateway(gateway, subject) == {:error, :unauthorized} end end @@ -1270,15 +1276,19 @@ defmodule Domain.GatewaysTest do test "does not allow duplicate presence", %{account: account} do gateway = Fixtures.Gateways.create_gateway(account: account) - assert Gateways.Presence.connect(gateway) == :ok + gateway = Repo.preload(gateway, :group) + gateway_token = Fixtures.Gateways.create_token(account: account, group: gateway.group) + assert Gateways.Presence.connect(gateway, gateway_token.id) == :ok assert {:error, {:already_tracked, _pid, _topic, _key}} = - Gateways.Presence.connect(gateway) + Gateways.Presence.connect(gateway, gateway_token.id) end test "tracks gateway presence for account", %{account: account} do gateway = Fixtures.Gateways.create_gateway(account: account) - assert Gateways.Presence.connect(gateway) == :ok + gateway = Repo.preload(gateway, :group) + gateway_token = Fixtures.Gateways.create_token(account: account, group: gateway.group) + assert Gateways.Presence.connect(gateway, gateway_token.id) == :ok gateway = fetch_gateway_by_id!(gateway.id, preload: [:online?]) assert gateway.online? == true diff --git a/elixir/apps/domain/test/domain/notifications/jobs/outdated_gateways_test.exs b/elixir/apps/domain/test/domain/notifications/jobs/outdated_gateways_test.exs index 77826be97..7c8e04c65 100644 --- a/elixir/apps/domain/test/domain/notifications/jobs/outdated_gateways_test.exs +++ b/elixir/apps/domain/test/domain/notifications/jobs/outdated_gateways_test.exs @@ -21,16 +21,19 @@ defmodule Domain.Notifications.Jobs.OutdatedGatewaysTest do |> Fixtures.Accounts.change_to_enterprise() gateway_group = Fixtures.Gateways.create_group(account: account) + token = Fixtures.Gateways.create_token(account: account, group: gateway_group) %{ account: account, - gateway_group: gateway_group + gateway_group: gateway_group, + token: token } end test "sends notification for outdated gateways", %{ account: account, - gateway_group: gateway_group + gateway_group: gateway_group, + token: token } do # Create Gateway gateway = Fixtures.Gateways.create_gateway(account: account, group: gateway_group) @@ -42,7 +45,7 @@ defmodule Domain.Notifications.Jobs.OutdatedGatewaysTest do Domain.Config.put_env_override(ComponentVersions, new_config) :ok = Gateways.Presence.Group.subscribe(gateway_group.id) - {:ok, _} = Gateways.Presence.Group.track(gateway.group_id, gateway.id) + {:ok, _} = Gateways.Presence.Group.track(gateway.group_id, gateway.id, token.id) {:ok, _} = Gateways.Presence.Account.track(gateway.account_id, gateway.id) assert_receive %Phoenix.Socket.Broadcast{topic: "presences:group_gateways:" <> _} @@ -56,7 +59,8 @@ defmodule Domain.Notifications.Jobs.OutdatedGatewaysTest do test "does not send notification if gateway up to date", %{ account: account, - gateway_group: gateway_group + gateway_group: gateway_group, + token: token } do # Create Gateway gateway = Fixtures.Gateways.create_gateway(account: account, group: gateway_group) @@ -67,7 +71,7 @@ defmodule Domain.Notifications.Jobs.OutdatedGatewaysTest do Domain.Config.put_env_override(ComponentVersions, new_config) :ok = Gateways.Presence.Group.subscribe(gateway_group.id) - {:ok, _} = Gateways.Presence.Group.track(gateway.group_id, gateway.id) + {:ok, _} = Gateways.Presence.Group.track(gateway.group_id, gateway.id, token.id) {:ok, _} = Gateways.Presence.Account.track(gateway.account_id, gateway.id) assert_receive %Phoenix.Socket.Broadcast{topic: "presences:group_gateways:" <> _} diff --git a/elixir/apps/domain/test/domain/policies_test.exs b/elixir/apps/domain/test/domain/policies_test.exs index 70cc6bdfe..871639ed3 100644 --- a/elixir/apps/domain/test/domain/policies_test.exs +++ b/elixir/apps/domain/test/domain/policies_test.exs @@ -43,13 +43,11 @@ defmodule Domain.PoliciesTest do assert fetched_policy.id == policy.id end - test "returns deleted policies", %{account: account, subject: subject} do - {:ok, policy} = - Fixtures.Policies.create_policy(account: account) - |> delete_policy(subject) + test "does not return deleted policies", %{account: account, subject: subject} do + policy = Fixtures.Policies.create_policy(account: account) + delete_policy(policy, subject) - assert {:ok, fetched_policy} = fetch_policy_by_id(policy.id, subject) - assert fetched_policy.id == policy.id + assert {:error, :not_found} = fetch_policy_by_id(policy.id, subject) end test "does not return policies in other accounts", %{subject: subject} do @@ -105,18 +103,11 @@ defmodule Domain.PoliciesTest do assert fetched_policy.id == policy.id end - test "returns deleted policies", %{account: account, subject: subject} do - {:ok, policy} = - Fixtures.Policies.create_policy(account: account) - |> delete_policy(subject) + test "does not return deleted policies", %{account: account, subject: subject} do + policy = Fixtures.Policies.create_policy(account: account) + delete_policy(policy, subject) - 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 + 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 @@ -725,8 +716,8 @@ defmodule Domain.PoliciesTest do end test "deletes policy", %{policy: policy, subject: subject} do - assert {:ok, deleted_policy} = delete_policy(policy, subject) - assert deleted_policy.deleted_at != nil + assert {:ok, _deleted_policy} = delete_policy(policy, subject) + refute Repo.get(Policies.Policy, policy.id) end test "returns error when subject has no permission to delete policies", %{ @@ -738,21 +729,28 @@ defmodule Domain.PoliciesTest do assert delete_policy(policy, subject) == {:error, {:unauthorized, - reason: :missing_permissions, - missing_permissions: [Policies.Authorizer.manage_policies_permission()]}} + [ + reason: :missing_permissions, + missing_permissions: [ + %Domain.Auth.Permission{resource: Domain.Policies.Policy, action: :manage} + ] + ]}} end - test "returns error on state conflict", %{policy: policy, subject: subject} do - assert {:ok, deleted_policy} = delete_policy(policy, subject) - assert delete_policy(deleted_policy, subject) == {:error, :not_found} - assert delete_policy(policy, subject) == {:error, :not_found} + test "raises error when deleting stale policy structs", %{policy: policy, subject: subject} do + assert {:ok, _deleted_policy} = delete_policy(policy, subject) + + assert_raise Ecto.StaleEntryError, fn -> + delete_policy(policy, subject) + end end test "returns error when subject attempts to delete policy outside of account", %{ policy: policy } do other_subject = Fixtures.Auth.create_subject() - assert delete_policy(policy, other_subject) == {:error, :not_found} + + assert delete_policy(policy, other_subject) == {:error, :unauthorized} end end @@ -777,11 +775,8 @@ defmodule Domain.PoliciesTest do actor_group: actor_group, policy: policy } do - assert {:ok, [deleted_policy]} = delete_policies_for(actor_group) - refute is_nil(deleted_policy.deleted_at) - assert deleted_policy.id == policy.id - - refute is_nil(Repo.get(Policies.Policy, policy.id).deleted_at) + assert {:ok, _count} = delete_policies_for(actor_group) + refute Repo.get(Policies.Policy, policy.id) end end @@ -813,11 +808,9 @@ defmodule Domain.PoliciesTest do } do other_policy = Fixtures.Policies.create_policy(account: account, subject: subject) - assert {:ok, [deleted_policy]} = delete_policies_for(actor_group, subject) - refute is_nil(deleted_policy.deleted_at) - assert deleted_policy.id == policy.id + assert {:ok, 1} = delete_policies_for(actor_group, subject) + refute Repo.get(Policies.Policy, policy.id) - refute is_nil(Repo.get(Policies.Policy, policy.id).deleted_at) assert is_nil(Repo.get(Policies.Policy, other_policy.id).deleted_at) end @@ -839,11 +832,9 @@ defmodule Domain.PoliciesTest do subject: subject ) - assert {:ok, [deleted_policy]} = delete_policies_for(provider, subject) - refute is_nil(deleted_policy.deleted_at) - assert deleted_policy.id == policy.id + assert {:ok, 1} = delete_policies_for(provider, subject) + refute Repo.get(Policies.Policy, policy.id) - refute is_nil(Repo.get(Policies.Policy, policy.id).deleted_at) assert is_nil(Repo.get(Policies.Policy, other_policy.id).deleted_at) end @@ -855,9 +846,8 @@ defmodule Domain.PoliciesTest do } do other_policy = Fixtures.Policies.create_policy(account: account, subject: subject) - assert {:ok, [deleted_policy]} = delete_policies_for(resource, subject) - refute is_nil(deleted_policy.deleted_at) - assert deleted_policy.id == policy.id + 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 @@ -871,8 +861,12 @@ defmodule Domain.PoliciesTest do assert delete_policies_for(resource, subject) == {:error, {:unauthorized, - reason: :missing_permissions, - missing_permissions: [Policies.Authorizer.manage_policies_permission()]}} + [ + reason: :missing_permissions, + missing_permissions: [ + %Domain.Auth.Permission{resource: Domain.Policies.Policy, action: :manage} + ] + ]}} end test "does not do anything on state conflict", %{ @@ -880,16 +874,16 @@ defmodule Domain.PoliciesTest do actor_group: actor_group, subject: subject } do - assert {:ok, [_deleted_policy]} = delete_policies_for(resource, subject) - assert delete_policies_for(actor_group, subject) == {:ok, []} - assert delete_policies_for(resource, subject) == {:ok, []} + 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, []} + assert delete_policies_for(resource, subject) == {:ok, 0} end end diff --git a/elixir/apps/domain/test/domain/relays_test.exs b/elixir/apps/domain/test/domain/relays_test.exs index 62baa7a4b..37a2d4902 100644 --- a/elixir/apps/domain/test/domain/relays_test.exs +++ b/elixir/apps/domain/test/domain/relays_test.exs @@ -29,7 +29,7 @@ defmodule Domain.RelaysTest do assert fetch_group_by_id(group.id, subject) == {:error, :not_found} end - test "returns deleted groups", %{ + test "does not return deleted groups", %{ account: account, subject: subject } do @@ -37,8 +37,7 @@ defmodule Domain.RelaysTest do Fixtures.Relays.create_group(account: account) |> Fixtures.Relays.delete_group() - assert {:ok, fetched_group} = fetch_group_by_id(group.id, subject) - assert fetched_group.id == group.id + assert {:error, :not_found} = fetch_group_by_id(group.id, subject) end test "returns group by id", %{account: account, subject: subject} do @@ -335,19 +334,28 @@ defmodule Domain.RelaysTest do end describe "delete_group/2" do - test "returns error on state conflict", %{account: account, subject: subject} do + test "raises error when deleting stale relay group structs", %{ + account: account, + subject: subject + } do group = Fixtures.Relays.create_group(account: account) assert {:ok, deleted} = delete_group(group, subject) - assert delete_group(deleted, subject) == {:error, :not_found} - assert delete_group(group, subject) == {:error, :not_found} + + assert_raise Ecto.StaleEntryError, fn -> + delete_group(deleted, subject) + end + + assert_raise Ecto.StaleEntryError, fn -> + delete_group(group, subject) + end end test "deletes groups", %{account: account, subject: subject} do group = Fixtures.Relays.create_group(account: account) - assert {:ok, deleted} = delete_group(group, subject) - assert deleted.deleted_at + assert {:ok, _group} = delete_group(group, subject) + refute Repo.get(Relays.Group, group.id) end test "does not allow deleting global group", %{subject: subject} do @@ -355,23 +363,24 @@ 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) Fixtures.Relays.create_token(account: account, group: [account: account]) - assert {:ok, deleted} = delete_group(group, subject) - assert deleted.deleted_at + assert {:ok, _group} = delete_group(group, subject) + refute Repo.get(Relays.Group, group.id) tokens = Domain.Tokens.Token.Query.all() |> Domain.Tokens.Token.Query.by_relay_group_id(group.id) |> Repo.all() - assert length(tokens) > 0 - assert Enum.all?(tokens, & &1.deleted_at) + 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) @@ -383,10 +392,10 @@ defmodule Domain.RelaysTest do |> Domain.Relays.Relay.Query.by_group_id(group.id) |> Repo.all() - assert length(relays) > 0 - assert Enum.all?(relays, & &1.deleted_at) + 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 @@ -401,11 +410,8 @@ defmodule Domain.RelaysTest do assert {:ok, _group} = delete_group(group, subject) - token1 = Repo.reload(token1) - token2 = Repo.reload(token2) - - assert token1.deleted_at - assert token2.deleted_at + refute Repo.reload(token1) + refute Repo.reload(token2) end test "returns error when subject has no permission to delete groups", %{ @@ -415,11 +421,7 @@ defmodule Domain.RelaysTest do subject = Fixtures.Auth.remove_permissions(subject) - assert delete_group(group, subject) == - {:error, - {:unauthorized, - reason: :missing_permissions, - missing_permissions: [Relays.Authorizer.manage_relays_permission()]}} + assert delete_group(group, subject) == {:error, :unauthorized} end end @@ -633,7 +635,7 @@ defmodule Domain.RelaysTest do assert fetch_relay_by_id(relay.id, subject) == {:error, :not_found} end - test "returns deleted relays", %{ + test "does not return deleted relays", %{ account: account, subject: subject } do @@ -641,7 +643,7 @@ defmodule Domain.RelaysTest do Fixtures.Relays.create_relay(account: account) |> Fixtures.Relays.delete_relay() - assert {:ok, _relay} = fetch_relay_by_id(relay.id, subject) + assert {:error, :not_found} = fetch_relay_by_id(relay.id, subject) end test "returns relay by id", %{account: account, subject: subject} do @@ -698,13 +700,15 @@ defmodule Domain.RelaysTest do Fixtures.Relays.create_relay() group = Fixtures.Relays.create_global_group() - relay = Fixtures.Relays.create_relay(account: account, group: group) + relay = Fixtures.Relays.create_relay(account: account, group: group) |> Repo.preload(:group) assert {:ok, relays, _metadata} = list_relays(subject, preload: :online?) assert length(relays) == 3 refute Enum.any?(relays, & &1.online?) - :ok = connect_relay(relay, Ecto.UUID.generate()) + relay_token = Fixtures.Relays.create_global_token(group: relay.group) + + :ok = connect_relay(relay, Ecto.UUID.generate(), relay_token.id) assert {:ok, relays, _metadata} = list_relays(subject, preload: :online?) assert length(relays) == 3 assert Enum.any?(relays, & &1.online?) @@ -747,12 +751,17 @@ defmodule Domain.RelaysTest do # end test "returns list of connected account relays", %{account: account} do - relay1 = Fixtures.Relays.create_relay(account: account) - relay2 = Fixtures.Relays.create_relay(account: account) + relay1 = Fixtures.Relays.create_relay(account: account) |> Repo.preload(:group) + relay2 = Fixtures.Relays.create_relay(account: account) |> Repo.preload(:group) stamp_secret = Ecto.UUID.generate() - assert connect_relay(relay1, stamp_secret) == :ok - assert connect_relay(relay2, stamp_secret) == :ok + relay1_token = Fixtures.Relays.create_token(group: relay1.group, account: account) + + assert connect_relay(relay1, stamp_secret, relay1_token.id) == :ok + + relay2_token = Fixtures.Relays.create_token(group: relay2.group, account: account) + + assert connect_relay(relay2, stamp_secret, relay2_token.id) == :ok Fixtures.Relays.update_relay(relay1, last_seen_at: DateTime.utc_now() |> DateTime.add(-6, :second) @@ -770,10 +779,11 @@ defmodule Domain.RelaysTest do test "returns list of connected global relays", %{account: account} do group = Fixtures.Relays.create_global_group() - relay = Fixtures.Relays.create_relay(account: account, group: group) + relay = Fixtures.Relays.create_relay(account: account, group: group) |> Repo.preload(:group) + relay_token = Fixtures.Relays.create_global_token(group: relay.group) stamp_secret = Ecto.UUID.generate() - assert connect_relay(relay, stamp_secret) == :ok + assert connect_relay(relay, stamp_secret, relay_token.id) == :ok Fixtures.Relays.update_relay(relay, last_seen_at: DateTime.utc_now() |> DateTime.add(-10, :second) @@ -811,14 +821,12 @@ defmodule Domain.RelaysTest do describe "upsert_relay/3" do setup %{account: account} do group = Fixtures.Relays.create_group(account: account) - token = Fixtures.Relays.create_token(account: account, group: group) user_agent = Fixtures.Auth.user_agent() remote_ip = Fixtures.Auth.remote_ip() %{ group: group, - token: token, context: %Domain.Auth.Context{ type: :relay_group, remote_ip: remote_ip, @@ -833,8 +841,7 @@ defmodule Domain.RelaysTest do test "returns errors on invalid attrs", %{ context: context, - group: group, - token: token + group: group } do attrs = %{ ipv4: "1.1.1.256", @@ -842,7 +849,7 @@ defmodule Domain.RelaysTest do port: -1 } - assert {:error, changeset} = upsert_relay(group, token, attrs, context) + assert {:error, changeset} = upsert_relay(group, attrs, context) assert errors_on(changeset) == %{ ipv4: ["one of these fields must be present: ipv4, ipv6", "is invalid"], @@ -851,20 +858,19 @@ defmodule Domain.RelaysTest do } attrs = %{port: 100_000} - assert {:error, changeset} = upsert_relay(group, token, attrs, context) + assert {:error, changeset} = upsert_relay(group, attrs, context) assert "must be less than or equal to 65535" in errors_on(changeset).port end test "allows creating relay with just required attributes", %{ context: context, - group: group, - token: token + group: group } do attrs = Fixtures.Relays.relay_attrs() |> Map.delete(:name) - assert {:ok, relay} = upsert_relay(group, token, attrs, context) + assert {:ok, relay} = upsert_relay(group, attrs, context) assert relay.group_id == group.id @@ -879,7 +885,6 @@ defmodule Domain.RelaysTest do assert relay.last_seen_user_agent == context.user_agent assert relay.last_seen_version == "1.3.0" assert relay.last_seen_at - assert relay.last_used_token_id == token.id assert relay.port == 3478 assert Repo.aggregate(Domain.Network.Address, :count) == 0 @@ -887,15 +892,14 @@ defmodule Domain.RelaysTest do test "allows creating ipv6-only relays", %{ context: context, - group: group, - token: token + group: group } do attrs = Fixtures.Relays.relay_attrs() |> Map.drop([:name, :ipv4]) - assert {:ok, _relay} = upsert_relay(group, token, attrs, context) - assert {:ok, _relay} = upsert_relay(group, token, attrs, context) + assert {:ok, _relay} = upsert_relay(group, attrs, context) + assert {:ok, _relay} = upsert_relay(group, attrs, context) assert Repo.one(Relays.Relay) end @@ -903,14 +907,13 @@ defmodule Domain.RelaysTest do test "updates ipv4 relay when it already exists", %{ account: account, group: group, - token: token, context: context } do relay = Fixtures.Relays.create_relay(account: account, group: group) attrs = Fixtures.Relays.relay_attrs(ipv4: relay.ipv4) context = %{context | user_agent: "iOS/12.5 (iPhone) connlib/0.7.411"} - assert {:ok, updated_relay} = upsert_relay(group, token, attrs, context) + assert {:ok, updated_relay} = upsert_relay(group, attrs, context) assert Repo.aggregate(Relays.Relay, :count, :id) == 1 @@ -920,7 +923,6 @@ defmodule Domain.RelaysTest do assert updated_relay.last_seen_version == "0.7.411" assert updated_relay.last_seen_at assert updated_relay.last_seen_at != relay.last_seen_at - assert updated_relay.last_used_token_id == token.id assert updated_relay.group_id == group.id @@ -942,8 +944,7 @@ defmodule Domain.RelaysTest do test "updates ipv6 relay when it already exists", %{ context: context, account: account, - group: group, - token: token + group: group } do relay = Fixtures.Relays.create_relay(ipv4: nil, account: account, group: group) @@ -953,7 +954,7 @@ defmodule Domain.RelaysTest do ipv6: relay.ipv6 ) - assert {:ok, updated_relay} = upsert_relay(group, token, attrs, context) + assert {:ok, updated_relay} = upsert_relay(group, attrs, context) assert Repo.aggregate(Relays.Relay, :count, :id) == 1 @@ -962,7 +963,6 @@ defmodule Domain.RelaysTest do assert updated_relay.last_seen_version == "1.3.0" assert updated_relay.last_seen_at assert updated_relay.last_seen_at != relay.last_seen_at - assert updated_relay.last_used_token_id == token.id assert updated_relay.group_id == group.id @@ -975,12 +975,11 @@ defmodule Domain.RelaysTest do test "updates global relay when it already exists", %{context: context} do group = Fixtures.Relays.create_global_group() - token = Fixtures.Relays.create_global_token(group: group) relay = Fixtures.Relays.create_relay(group: group) context = %{context | user_agent: "iOS/12.5 (iPhone) connlib/0.7.411"} attrs = Fixtures.Relays.relay_attrs(ipv4: relay.ipv4) - assert {:ok, updated_relay} = upsert_relay(group, token, attrs, context) + assert {:ok, updated_relay} = upsert_relay(group, attrs, context) assert Repo.aggregate(Relays.Relay, :count, :id) == 1 @@ -990,7 +989,6 @@ defmodule Domain.RelaysTest do assert updated_relay.last_seen_version == "0.7.411" assert updated_relay.last_seen_at assert updated_relay.last_seen_at != relay.last_seen_at - assert updated_relay.last_used_token_id == token.id assert updated_relay.group_id == group.id @@ -1039,19 +1037,25 @@ defmodule Domain.RelaysTest do end describe "delete_relay/2" do - test "returns error on state conflict", %{account: account, subject: subject} do + test "raises error when deleting stale relay structs", %{account: account, subject: subject} do relay = Fixtures.Relays.create_relay(account: account) assert {:ok, deleted} = delete_relay(relay, subject) - assert delete_relay(deleted, subject) == {:error, :not_found} - assert delete_relay(relay, subject) == {:error, :not_found} + + assert_raise Ecto.StaleEntryError, fn -> + delete_relay(deleted, subject) + end + + assert_raise Ecto.StaleEntryError, fn -> + delete_relay(relay, subject) + end end test "deletes relays", %{account: account, subject: subject} do relay = Fixtures.Relays.create_relay(account: account) - assert {:ok, deleted} = delete_relay(relay, subject) - assert deleted.deleted_at + assert {:ok, _relay} = delete_relay(relay, subject) + refute Repo.get(Relays.Relay, relay.id) end test "returns error when subject has no permission to delete relays", %{ @@ -1061,11 +1065,7 @@ defmodule Domain.RelaysTest do subject = Fixtures.Auth.remove_permissions(subject) - assert delete_relay(relay, subject) == - {:error, - {:unauthorized, - reason: :missing_permissions, - missing_permissions: [Relays.Authorizer.manage_relays_permission()]}} + assert delete_relay(relay, subject) == {:error, :unauthorized} end end @@ -1312,26 +1312,33 @@ defmodule Domain.RelaysTest do end end - describe "connect_relay/2" do + describe "connect_relay/3" do test "does not allow duplicate presence", %{account: account} do - relay = Fixtures.Relays.create_relay(account: account) + relay = Fixtures.Relays.create_relay(account: account) |> Repo.preload(:group) + relay_token = Fixtures.Relays.create_token(group: relay.group, account: account) stamp_secret = Ecto.UUID.generate() - assert connect_relay(relay, stamp_secret) == :ok - assert {:error, {:already_tracked, _pid, _topic, _key}} = connect_relay(relay, stamp_secret) + assert connect_relay(relay, stamp_secret, relay_token.id) == :ok + + assert {:error, {:already_tracked, _pid, _topic, _key}} = + connect_relay(relay, stamp_secret, relay_token.id) end test "tracks relay presence for account", %{account: account} do - relay = Fixtures.Relays.create_relay(account: account) - assert connect_relay(relay, "foo") == :ok + relay = Fixtures.Relays.create_relay(account: account) |> Repo.preload(:group) + relay_token = Fixtures.Relays.create_token(group: relay.group, account: account) + + assert connect_relay(relay, "foo", relay_token.id) == :ok relay = fetch_relay_by_id!(relay.id, preload: [:online?]) assert relay.online? == true end test "tracks relay presence for actor", %{account: account} do - relay = Fixtures.Relays.create_relay(account: account) - assert connect_relay(relay, "foo") == :ok + relay = Fixtures.Relays.create_relay(account: account) |> Repo.preload(:group) + relay_token = Fixtures.Relays.create_token(group: relay.group, account: account) + + assert connect_relay(relay, "foo", relay_token.id) == :ok assert broadcast_to_relay(relay, "test") == :ok @@ -1339,8 +1346,10 @@ defmodule Domain.RelaysTest do end test "subscribes to relay events", %{account: account} do - relay = Fixtures.Relays.create_relay(account: account) - assert connect_relay(relay, "foo") == :ok + relay = Fixtures.Relays.create_relay(account: account) |> Repo.preload(:group) + relay_token = Fixtures.Relays.create_token(group: relay.group, account: account) + + assert connect_relay(relay, "foo", relay_token.id) == :ok assert disconnect_relay(relay) == :ok @@ -1349,9 +1358,10 @@ defmodule Domain.RelaysTest do test "subscribes to relay group events", %{account: account} do group = Fixtures.Relays.create_group(account: account) - relay = Fixtures.Relays.create_relay(account: account, group: group) + relay = Fixtures.Relays.create_relay(account: account, group: group) |> Repo.preload(:group) + relay_token = Fixtures.Relays.create_token(group: relay.group, account: account) - assert connect_relay(relay, "foo") == :ok + assert connect_relay(relay, "foo", relay_token.id) == :ok assert disconnect_relays_in_group(group) == :ok @@ -1359,9 +1369,10 @@ defmodule Domain.RelaysTest do end test "subscribes to account events", %{account: account} do - relay = Fixtures.Relays.create_relay(account: account) + relay = Fixtures.Relays.create_relay(account: account) |> Repo.preload(:group) + relay_token = Fixtures.Relays.create_token(group: relay.group, account: account) - assert connect_relay(relay, "foo") == :ok + assert connect_relay(relay, "foo", relay_token.id) == :ok assert disconnect_relays_in_account(account) == :ok @@ -1369,10 +1380,11 @@ defmodule Domain.RelaysTest do end test "subscribes to relay presence", %{account: account} do - relay = Fixtures.Relays.create_relay(account: account) + relay = Fixtures.Relays.create_relay(account: account) |> Repo.preload(:group) + relay_token = Fixtures.Relays.create_token(group: relay.group, account: account) :ok = subscribe_to_relay_presence(relay) - assert connect_relay(relay, "foo") == :ok + assert connect_relay(relay, "foo", relay_token.id) == :ok relay_id = relay.id assert_receive %Phoenix.Socket.Broadcast{topic: "presences:relays:" <> ^relay_id} diff --git a/elixir/apps/domain/test/domain/resources_test.exs b/elixir/apps/domain/test/domain/resources_test.exs index d9d5b7b0d..2e32312b1 100644 --- a/elixir/apps/domain/test/domain/resources_test.exs +++ b/elixir/apps/domain/test/domain/resources_test.exs @@ -60,12 +60,11 @@ defmodule Domain.ResourcesTest do assert Enum.map(fetched_resource.authorized_by_policies, & &1.id) == [policy.id] end - test "returns deleted resources", %{account: account, subject: subject} do - {:ok, resource} = - Fixtures.Resources.create_resource(account: account) - |> delete_resource(subject) + test "does not return deleted resources", %{account: account, subject: subject} do + resource = Fixtures.Resources.create_resource(account: account) + delete_resource(resource, subject) - assert {:ok, _resource} = fetch_resource_by_id(resource.id, subject) + assert {:error, :not_found} = fetch_resource_by_id(resource.id, subject) end test "does not return resources in other accounts", %{subject: subject} do @@ -161,14 +160,13 @@ defmodule Domain.ResourcesTest do assert Enum.map(fetched_resource.authorized_by_policies, & &1.id) == [policy.id] end - test "returns deleted resources", %{account: account, subject: subject} do - {:ok, resource} = - Fixtures.Resources.create_resource(account: account) - |> delete_resource(subject) + test "does not return deleted resources", %{account: account, subject: subject} do + resource = Fixtures.Resources.create_resource(account: account) + delete_resource(resource, subject) - assert {:ok, _resource} = fetch_resource_by_id_or_persistent_id(resource.id, subject) + assert {:error, :not_found} = fetch_resource_by_id_or_persistent_id(resource.id, subject) - assert {:ok, _resource} = + assert {:error, :not_found} = fetch_resource_by_id_or_persistent_id(resource.persistent_id, subject) end @@ -1399,18 +1397,20 @@ defmodule Domain.ResourcesTest do %{resource: resource} end - test "returns error on state conflict", %{ + test "raises error when deleting stale resource structs", %{ resource: resource, subject: subject } do - assert {:ok, deleted} = delete_resource(resource, subject) - assert delete_resource(deleted, subject) == {:error, :not_found} - assert delete_resource(resource, subject) == {:error, :not_found} + assert {:ok, _resource} = delete_resource(resource, subject) + + assert_raise Ecto.StaleEntryError, fn -> + delete_resource(resource, subject) + end end test "deletes resources", %{resource: resource, subject: subject} do - assert {:ok, deleted} = delete_resource(resource, subject) - assert deleted.deleted_at + assert {:ok, _resource} = delete_resource(resource, subject) + refute Repo.get(Resources.Resource, resource.id) end test "deletes policies that use this resource", %{ @@ -1423,8 +1423,8 @@ defmodule Domain.ResourcesTest do assert {:ok, _resource} = delete_resource(resource, subject) - refute is_nil(Repo.get_by(Domain.Policies.Policy, id: policy.id).deleted_at) - assert is_nil(Repo.get_by(Domain.Policies.Policy, id: other_policy.id).deleted_at) + assert is_nil(Repo.get(Domain.Policies.Policy, policy.id)) + assert Repo.get(Domain.Policies.Policy, other_policy.id) == other_policy end test "deletes connections that use this resource", %{ @@ -1453,8 +1453,12 @@ defmodule Domain.ResourcesTest do assert delete_resource(resource, subject) == {:error, {:unauthorized, - reason: :missing_permissions, - missing_permissions: [Resources.Authorizer.manage_resources_permission()]}} + [ + reason: :missing_permissions, + missing_permissions: [ + %Domain.Auth.Permission{resource: Domain.Resources.Resource, action: :manage} + ] + ]}} end end diff --git a/elixir/apps/domain/test/domain/tokens_test.exs b/elixir/apps/domain/test/domain/tokens_test.exs index b2d1dc1d0..6e8d2eee5 100644 --- a/elixir/apps/domain/test/domain/tokens_test.exs +++ b/elixir/apps/domain/test/domain/tokens_test.exs @@ -433,17 +433,15 @@ defmodule Domain.TokensTest do test "admin can delete any account token", %{account: account, subject: subject} do token = Fixtures.Tokens.create_token(account: account) - assert {:ok, token} = delete_token(token, subject) - - assert token.deleted_at + assert {:ok, _token} = delete_token(token, subject) + refute Repo.get(Tokens.Token, token.id) end test "user can delete own token", %{account: account, identity: identity, subject: subject} do token = Fixtures.Tokens.create_token(account: account, identity: identity) - assert {:ok, token} = delete_token(token, subject) - - assert token.deleted_at + assert {:ok, _token} = delete_token(token, subject) + refute Repo.get(Tokens.Token, token.id) end test "user cannot delete other users token", %{ @@ -454,7 +452,15 @@ defmodule Domain.TokensTest do token = Fixtures.Tokens.create_token(account: account) - assert delete_token(token, subject) == {:error, :not_found} + assert delete_token(token, subject) == + {:error, + {:unauthorized, + [ + reason: :missing_permissions, + missing_permissions: [ + %Domain.Auth.Permission{resource: Domain.Tokens.Token, action: :manage} + ] + ]}} refute Repo.get(Tokens.Token, token.id).deleted_at end @@ -464,9 +470,9 @@ defmodule Domain.TokensTest do } do token = Fixtures.Tokens.create_token() - assert delete_token(token, subject) == {:error, :not_found} + assert delete_token(token, subject) == {:error, :unauthorized} - refute Repo.get(Tokens.Token, token.id).deleted_at + assert Repo.get(Tokens.Token, token.id) end test "returns error when subject does not have required permissions", %{ @@ -479,15 +485,30 @@ defmodule Domain.TokensTest do assert delete_token(token, subject) == {:error, {:unauthorized, - reason: :missing_permissions, - missing_permissions: [ - {:one_of, - [ - Tokens.Authorizer.manage_tokens_permission(), - Tokens.Authorizer.manage_own_tokens_permission() - ]} + [ + reason: :missing_permissions, + missing_permissions: [ + %Domain.Auth.Permission{resource: Domain.Tokens.Token, action: :manage} + ] ]}} end + + test "raises error when deleting stale token structs", %{ + account: account, + subject: subject + } do + token = Fixtures.Tokens.create_token(account: account) + + assert {:ok, deleted} = delete_token(token, subject) + + assert_raise Ecto.StaleEntryError, fn -> + delete_token(deleted, subject) + end + + assert_raise Ecto.StaleEntryError, fn -> + delete_token(token, subject) + end + end end describe "delete_token_for/1" do @@ -498,12 +519,11 @@ defmodule Domain.TokensTest do } do other_token = Fixtures.Tokens.create_token(account: account, identity: identity) - assert {:ok, [deleted_token]} = delete_token_for(subject) - assert deleted_token.id == subject.token_id + assert {:ok, num_deleted} = delete_token_for(subject) + assert num_deleted == 1 - assert Repo.get(Tokens.Token, subject.token_id).deleted_at - - refute Repo.get(Tokens.Token, other_token.id).deleted_at + refute Repo.get(Tokens.Token, subject.token_id) + assert Repo.get(Tokens.Token, other_token.id) end test "does not delete tokens for other actors", %{account: account, subject: subject} do @@ -515,17 +535,18 @@ defmodule Domain.TokensTest do end end - 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) + ## 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) - assert {:ok, [_token]} = delete_tokens_for(identity) + # assert {:ok, _num_tokens} = delete_tokens_for(identity) - assert Repo.get(Tokens.Token, token.id).deleted_at - 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 @@ -533,9 +554,8 @@ defmodule Domain.TokensTest do identity = Fixtures.Auth.create_identity(account: account, actor: actor) token = Fixtures.Tokens.create_token(type: :browser, account: account, identity: identity) - assert {:ok, [_token]} = delete_tokens_for(actor, subject) - - assert Repo.get(Tokens.Token, token.id).deleted_at + assert {:ok, 1} = delete_tokens_for(actor, subject) + refute Repo.get(Tokens.Token, token.id) end test "deletes client tokens for given actor", %{account: account, subject: subject} do @@ -543,9 +563,8 @@ defmodule Domain.TokensTest do identity = Fixtures.Auth.create_identity(account: account, actor: actor) token = Fixtures.Tokens.create_token(type: :client, account: account, identity: identity) - assert {:ok, [_token]} = delete_tokens_for(actor, subject) - - assert Repo.get(Tokens.Token, token.id).deleted_at + assert {:ok, 1} = delete_tokens_for(actor, subject) + refute Repo.get(Tokens.Token, token.id) end test "deletes client tokens for given identity", %{account: account, subject: subject} do @@ -553,9 +572,8 @@ defmodule Domain.TokensTest do identity = Fixtures.Auth.create_identity(account: account, actor: actor) token = Fixtures.Tokens.create_token(type: :client, account: account, identity: identity) - assert {:ok, [_token]} = delete_tokens_for(identity, subject) - - assert Repo.get(Tokens.Token, token.id).deleted_at + assert {:ok, 1} = delete_tokens_for(identity, subject) + refute Repo.get(Tokens.Token, token.id) end test "deletes client tokens for given provider", %{account: account, subject: subject} do @@ -565,27 +583,24 @@ defmodule Domain.TokensTest do identity = Fixtures.Auth.create_identity(account: account, provider: provider, actor: actor) token = Fixtures.Tokens.create_token(type: :client, account: account, identity: identity) - assert {:ok, [_token]} = delete_tokens_for(provider, subject) - - assert Repo.get(Tokens.Token, token.id).deleted_at + assert {:ok, _provider} = Domain.Auth.delete_provider(provider, subject) + refute Repo.get(Tokens.Token, token.id) end test "deletes gateway group tokens", %{account: account, subject: subject} do group = Fixtures.Gateways.create_group(account: account) token = Fixtures.Gateways.create_token(account: account, group: group) - assert {:ok, [_token]} = delete_tokens_for(group, subject) - - assert Repo.get(Tokens.Token, token.id).deleted_at + assert {:ok, _num_deleted} = delete_tokens_for(group, subject) + refute Repo.get(Tokens.Token, token.id) end test "deletes relay group tokens", %{account: account, subject: subject} do group = Fixtures.Relays.create_group(account: account) token = Fixtures.Relays.create_token(account: account, group: group) - assert {:ok, [_token]} = delete_tokens_for(group, subject) - - assert Repo.get(Tokens.Token, token.id).deleted_at + assert {:ok, _relay_group} = Domain.Relays.delete_group(group, subject) + refute Repo.get(Tokens.Token, token.id) end test "returns error when subject does not have required permissions", %{ @@ -608,18 +623,16 @@ defmodule Domain.TokensTest do Fixtures.Tokens.create_token() |> Fixtures.Tokens.expire_token() - assert {:ok, [_token]} = delete_expired_tokens() - - assert Repo.get(Tokens.Token, token.id).deleted_at + assert {:ok, 1} = delete_expired_tokens() + refute Repo.get(Tokens.Token, token.id) end test "does not delete non-expired tokens" do in_one_minute = DateTime.utc_now() |> DateTime.add(1, :minute) token = Fixtures.Tokens.create_token(expires_at: in_one_minute) - assert delete_expired_tokens() == {:ok, []} - - refute Repo.get(Tokens.Token, token.id).deleted_at + assert delete_expired_tokens() == {:ok, 0} + assert Repo.get(Tokens.Token, token.id) end end end diff --git a/elixir/apps/domain/test/support/fixtures/actors.ex b/elixir/apps/domain/test/support/fixtures/actors.ex index 6b74b1ab3..13c7de88f 100644 --- a/elixir/apps/domain/test/support/fixtures/actors.ex +++ b/elixir/apps/domain/test/support/fixtures/actors.ex @@ -76,6 +76,11 @@ 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]) @@ -149,6 +154,19 @@ defmodule Domain.Fixtures.Actors do end def delete(actor) do + actor = Repo.preload(actor, :account) + + subject = + Fixtures.Auth.create_subject( + account: actor.account, + actor: [type: :account_admin_user] + ) + + {: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 diff --git a/elixir/apps/domain/test/support/fixtures/clients.ex b/elixir/apps/domain/test/support/fixtures/clients.ex index 0e0a3db15..7509a8550 100644 --- a/elixir/apps/domain/test/support/fixtures/clients.ex +++ b/elixir/apps/domain/test/support/fixtures/clients.ex @@ -70,7 +70,7 @@ defmodule Domain.Fixtures.Clients do actor: [type: :account_admin_user] ) - {:ok, client} = Clients.delete_client(client, subject) + {:ok, _client} = Clients.delete_client(client, subject) client end diff --git a/elixir/apps/domain/test/support/fixtures/gateways.ex b/elixir/apps/domain/test/support/fixtures/gateways.ex index dbc0aaa73..71ea49e10 100644 --- a/elixir/apps/domain/test/support/fixtures/gateways.ex +++ b/elixir/apps/domain/test/support/fixtures/gateways.ex @@ -50,8 +50,7 @@ defmodule Domain.Fixtures.Gateways do actor: [type: :account_admin_user] ) - {:ok, group} = Gateways.delete_group(group, subject) - group + Gateways.delete_group(group, subject) end def create_token(attrs \\ %{}) do @@ -105,7 +104,8 @@ defmodule Domain.Fixtures.Gateways do |> create_group() end) - {token, attrs} = + # TODO: BRIAN - This can likely be removed + {_token, attrs} = pop_assoc_fixture(attrs, :token, fn assoc_attrs -> assoc_attrs |> Enum.into(%{account: account, group: group}) @@ -119,7 +119,7 @@ defmodule Domain.Fixtures.Gateways do |> Fixtures.Auth.build_context() end) - {:ok, gateway} = Gateways.upsert_gateway(group, token, attrs, context) + {:ok, gateway} = Gateways.upsert_gateway(group, attrs, context) %{gateway | online?: false} end diff --git a/elixir/apps/domain/test/support/fixtures/policies.ex b/elixir/apps/domain/test/support/fixtures/policies.ex index 098267e09..eade89fde 100644 --- a/elixir/apps/domain/test/support/fixtures/policies.ex +++ b/elixir/apps/domain/test/support/fixtures/policies.ex @@ -66,8 +66,7 @@ defmodule Domain.Fixtures.Policies do actor: [type: :account_admin_user] ) - {:ok, policy} = Policies.delete_policy(policy, subject) - policy + Policies.delete_policy(policy, subject) end def update_policy(policy, attrs) do diff --git a/elixir/apps/domain/test/support/fixtures/relays.ex b/elixir/apps/domain/test/support/fixtures/relays.ex index 1bfa109e9..9a93db6db 100644 --- a/elixir/apps/domain/test/support/fixtures/relays.ex +++ b/elixir/apps/domain/test/support/fixtures/relays.ex @@ -112,7 +112,8 @@ defmodule Domain.Fixtures.Relays do |> create_group() end) - {token, attrs} = + # TODO: BRIAN - This can likely be removed + {_token, attrs} = pop_assoc_fixture(attrs, :token, fn assoc_attrs -> if group.account_id do assoc_attrs @@ -132,7 +133,7 @@ defmodule Domain.Fixtures.Relays do |> Fixtures.Auth.build_context() end) - {:ok, relay} = Relays.upsert_relay(group, token, attrs, context) + {:ok, relay} = Relays.upsert_relay(group, attrs, context) %{relay | online?: false} end diff --git a/elixir/apps/domain/test/support/fixtures/tokens.ex b/elixir/apps/domain/test/support/fixtures/tokens.ex index 94321ad02..de06bd0a1 100644 --- a/elixir/apps/domain/test/support/fixtures/tokens.ex +++ b/elixir/apps/domain/test/support/fixtures/tokens.ex @@ -114,6 +114,36 @@ defmodule Domain.Fixtures.Tokens do token end + def create_client_token(attrs \\ %{}) do + attrs = attrs |> Enum.into(%{type: :client}) |> token_attrs() + + {account, attrs} = + pop_assoc_fixture(attrs, :account, fn assoc_attrs -> + Fixtures.Accounts.create_account(assoc_attrs) + end) + + {actor, attrs} = + pop_assoc_fixture(attrs, :actor, fn assoc_attrs -> + assoc_attrs + |> Enum.into(%{account: account}) + |> Fixtures.Actors.create_actor() + end) + + {identity, attrs} = + pop_assoc_fixture(attrs, :identity, fn assoc_attrs -> + assoc_attrs + |> Enum.into(%{account: account, actor: actor}) + |> Fixtures.Auth.create_identity() + end) + + attrs = Map.put(attrs, :account_id, account.id) + attrs = Map.put(attrs, :actor_id, actor.id) + attrs = Map.put(attrs, :identity_id, identity.id) + + {:ok, token} = Domain.Tokens.create_token(attrs) + token + end + def create_api_client_token(attrs \\ %{}) do attrs = token_attrs(attrs) diff --git a/elixir/apps/web/lib/web/live/actors/show.ex b/elixir/apps/web/lib/web/live/actors/show.ex index eda3f910d..c73c7cdff 100644 --- a/elixir/apps/web/lib/web/live/actors/show.ex +++ b/elixir/apps/web/lib/web/live/actors/show.ex @@ -103,10 +103,7 @@ defmodule Web.Actors.Show do def handle_tokens_update!(socket, list_opts) do list_opts = - Keyword.put(list_opts, :preload, - identity: [:provider], - clients: [] - ) + Keyword.put(list_opts, :preload, identity: [:provider]) with {:ok, tokens, metadata} <- Tokens.list_tokens_for(socket.assigns.actor, socket.assigns.subject, list_opts) do @@ -398,20 +395,6 @@ defmodule Web.Actors.Show do <:col :let={token} :if={@actor.type != :service_account} label="identity" class="w-3/12"> <.identity_identifier account={@account} identity={token.identity} /> - <:col :let={token} label="client" class="w-1/12"> - <.intersperse_blocks :if={token.type == :client}> - <:separator>,  - - <:empty>None - - <:item :for={client <- token.clients}> - <.link navigate={~p"/#{@account}/clients/#{client.id}"} class={[link_style()]}> - {client.name} - - - - N/A - <:col :let={token} :if={@actor.type == :service_account} label="name" class="w-2/12"> {token.name} @@ -754,12 +737,13 @@ defmodule Web.Actors.Show do end def handle_event("revoke_all_tokens", _params, socket) do - {:ok, deleted_tokens} = Tokens.delete_tokens_for(socket.assigns.actor, socket.assigns.subject) + {:ok, deleted_tokens_count} = + Tokens.delete_tokens_for(socket.assigns.actor, socket.assigns.subject) socket = socket |> reload_live_table!("tokens") - |> put_flash(:info, "#{length(deleted_tokens)} token(s) were revoked.") + |> put_flash(:info, "#{deleted_tokens_count} token(s) were revoked.") {:noreply, socket} end diff --git a/elixir/apps/web/lib/web/live/clients/show.ex b/elixir/apps/web/lib/web/live/clients/show.ex index b58916cd4..a5ebc5fb4 100644 --- a/elixir/apps/web/lib/web/live/clients/show.ex +++ b/elixir/apps/web/lib/web/live/clients/show.ex @@ -9,18 +9,20 @@ defmodule Web.Clients.Show do Clients.fetch_client_by_id(id, socket.assigns.subject, preload: [ :online?, - :actor, - last_used_token: [identity: [:provider]] + :actor ] ) do if connected?(socket) do :ok = Clients.Presence.Actor.subscribe(client.actor_id) end + current_token = get_current_token_from_presence(client, socket.assigns.subject) + socket = assign( socket, client: client, + current_token: current_token, page_title: "Client #{client.name}" ) |> assign_live_table("flows", @@ -112,32 +114,31 @@ defmodule Web.Clients.Show do <.vertical_table_row> - <:label>Last used sign in method + <:label>Current sign in method <:value> -
- <.identity_identifier account={@account} identity={@client.last_used_token.identity} /> +
+ <.identity_identifier account={@account} identity={@current_token.identity} /> <.link - navigate={ - ~p"/#{@account}/actors/#{@client.actor_id}?#tokens-#{@client.last_used_token_id}" - } + navigate={~p"/#{@account}/actors/#{@client.actor_id}?#tokens-#{@current_token.id}"} class={[link_style(), "text-xs"]} > show tokens
-
+
token <.link - navigate={ - ~p"/#{@account}/actors/#{@client.actor_id}?#tokens-#{@client.last_used_token_id}" - } + navigate={~p"/#{@account}/actors/#{@client.actor_id}?#tokens-#{@current_token.id}"} class={[link_style()]} > - {@client.last_used_token.name} + {@current_token.name} - - (deleted) - +
+
+ Client is offline - sign in method unavailable
@@ -416,8 +417,7 @@ defmodule Web.Clients.Show do {:ok, client} = Clients.fetch_client_by_id(client.id, socket.assigns.subject, preload: [ - :actor, - last_used_token: [identity: [:provider]] + :actor ] ) @@ -442,8 +442,7 @@ defmodule Web.Clients.Show do client = %{ client | online?: socket.assigns.client.online?, - actor: socket.assigns.client.actor, - last_used_token: socket.assigns.client.last_used_token + actor: socket.assigns.client.actor } {:noreply, assign(socket, :client, client)} @@ -456,15 +455,14 @@ defmodule Web.Clients.Show do client = %{ client | online?: socket.assigns.client.online?, - actor: socket.assigns.client.actor, - last_used_token: socket.assigns.client.last_used_token + actor: socket.assigns.client.actor } {:noreply, assign(socket, :client, client)} end def handle_event("delete", _params, socket) do - {:ok, _client} = Clients.delete_client(socket.assigns.client, socket.assigns.subject) + {:ok, _deleted_client} = Clients.delete_client(socket.assigns.client, socket.assigns.subject) socket = socket @@ -473,4 +471,27 @@ defmodule Web.Clients.Show do {:noreply, socket} end + + defp get_current_token_from_presence(client, subject) do + case Clients.Presence.Actor.get(client.actor_id, client.id) do + [] -> + nil + + presence_data -> + get_in(presence_data, ["metas"]) + |> fetch_token_by_metas(subject) + end + end + + defp fetch_token_by_metas([%{token_id: token_id} | _tail], subject), + do: fetch_token(token_id, subject) + + defp fetch_token_by_metas(_metas, _subject), do: nil + + defp fetch_token(token_id, subject) do + case Domain.Tokens.fetch_token_by_id(token_id, subject, preload: [identity: [:provider]]) do + {:ok, token} -> token + _ -> nil + end + end end diff --git a/elixir/apps/web/lib/web/live/groups/index.ex b/elixir/apps/web/lib/web/live/groups/index.ex index bf25f7dc9..fcf216c3f 100644 --- a/elixir/apps/web/lib/web/live/groups/index.ex +++ b/elixir/apps/web/lib/web/live/groups/index.ex @@ -76,7 +76,7 @@ defmodule Web.Groups.Index do <:col :let={group} field={{:groups, :name}} label="name" class="w-3/12"> <.group account={@account} group={group} /> - + (deleted) diff --git a/elixir/apps/web/lib/web/live/groups/show.ex b/elixir/apps/web/lib/web/live/groups/show.ex index fa79603a1..7e057a6ac 100644 --- a/elixir/apps/web/lib/web/live/groups/show.ex +++ b/elixir/apps/web/lib/web/live/groups/show.ex @@ -251,7 +251,7 @@ defmodule Web.Groups.Show do do: handle_live_table_event(event, params, socket) def handle_event("delete", _params, socket) do - {:ok, _group} = Actors.delete_group(socket.assigns.group, socket.assigns.subject) + {:ok, _deleted_group} = Actors.delete_group(socket.assigns.group, socket.assigns.subject) {:noreply, push_navigate(socket, to: ~p"/#{socket.assigns.account}/groups")} end end diff --git a/elixir/apps/web/lib/web/live/policies/show.ex b/elixir/apps/web/lib/web/live/policies/show.ex index 7aefd403a..1eb893b5f 100644 --- a/elixir/apps/web/lib/web/live/policies/show.ex +++ b/elixir/apps/web/lib/web/live/policies/show.ex @@ -359,7 +359,7 @@ defmodule Web.Policies.Show do end def handle_event("delete", _params, socket) do - {:ok, _} = Policies.delete_policy(socket.assigns.policy, socket.assigns.subject) + {:ok, _deleted_policy} = Policies.delete_policy(socket.assigns.policy, socket.assigns.subject) {:noreply, push_navigate(socket, to: ~p"/#{socket.assigns.account}/policies")} end end diff --git a/elixir/apps/web/lib/web/live/relay_groups/new_token.ex b/elixir/apps/web/lib/web/live/relay_groups/new_token.ex index e4a9aa1e4..f1c8655d9 100644 --- a/elixir/apps/web/lib/web/live/relay_groups/new_token.ex +++ b/elixir/apps/web/lib/web/live/relay_groups/new_token.ex @@ -370,10 +370,10 @@ defmodule Web.RelayGroups.NewToken do else connected? = joins - |> Map.keys() - |> Enum.any?(fn id -> - relay = Relays.fetch_relay_by_id!(id) - socket.assigns.token.id == relay.last_used_token_id + |> Enum.any?(fn {_relay_id, %{metas: metas}} -> + Enum.any?(metas, fn meta -> + Map.get(meta, :token_id) == socket.assigns.token.id + end) end) {:noreply, assign(socket, connected?: connected?)} diff --git a/elixir/apps/web/lib/web/live/relay_groups/show.ex b/elixir/apps/web/lib/web/live/relay_groups/show.ex index 766a3ef30..35e3be5f8 100644 --- a/elixir/apps/web/lib/web/live/relay_groups/show.ex +++ b/elixir/apps/web/lib/web/live/relay_groups/show.ex @@ -186,11 +186,11 @@ defmodule Web.RelayGroups.Show do def handle_event("revoke_all_tokens", _params, socket) do group = socket.assigns.group - {:ok, deleted_tokens} = Tokens.delete_tokens_for(group, socket.assigns.subject) + {:ok, deleted_tokens_count} = Tokens.delete_tokens_for(group, socket.assigns.subject) socket = socket - |> put_flash(:info, "#{length(deleted_tokens)} token(s) were revoked.") + |> put_flash(:info, "#{deleted_tokens_count} token(s) were revoked.") {:noreply, socket} end diff --git a/elixir/apps/web/lib/web/live/resources/show.ex b/elixir/apps/web/lib/web/live/resources/show.ex index ef687838b..0bc6c89da 100644 --- a/elixir/apps/web/lib/web/live/resources/show.ex +++ b/elixir/apps/web/lib/web/live/resources/show.ex @@ -426,7 +426,10 @@ defmodule Web.Resources.Show do do: handle_live_table_event(event, params, socket) def handle_event("delete", %{"id" => _resource_id}, socket) do - {:ok, _} = Resources.delete_resource(socket.assigns.resource, socket.assigns.subject) + {:ok, _deleted_resource} = + Resources.delete_resource(socket.assigns.resource, socket.assigns.subject) + + socket = put_flash(socket, :info, "Resource was deleted.") if site_id = socket.assigns.params["site_id"] do {:noreply, push_navigate(socket, to: ~p"/#{socket.assigns.account}/sites/#{site_id}")} diff --git a/elixir/apps/web/lib/web/live/settings/api_clients/show.ex b/elixir/apps/web/lib/web/live/settings/api_clients/show.ex index dfa14eb28..11672d49c 100644 --- a/elixir/apps/web/lib/web/live/settings/api_clients/show.ex +++ b/elixir/apps/web/lib/web/live/settings/api_clients/show.ex @@ -18,6 +18,8 @@ defmodule Web.Settings.ApiClients.Show do ) {:ok, socket} + else + {:error, _reason} -> raise Web.LiveErrors.NotFoundError end else {:ok, push_navigate(socket, to: ~p"/#{socket.assigns.account}/settings/api_clients/beta")} @@ -258,11 +260,12 @@ defmodule Web.Settings.ApiClients.Show do end def handle_event("revoke_all_tokens", _params, socket) do - {:ok, deleted_tokens} = Tokens.delete_tokens_for(socket.assigns.actor, socket.assigns.subject) + {:ok, deleted_tokens_count} = + Tokens.delete_tokens_for(socket.assigns.actor, socket.assigns.subject) socket = socket - |> put_flash(:info, "#{length(deleted_tokens)} token(s) were revoked.") + |> put_flash(:info, "#{deleted_tokens_count} token(s) were revoked.") |> reload_live_table!("tokens") {:noreply, socket} diff --git a/elixir/apps/web/lib/web/live/settings/identity_providers/google_workspace/show.ex b/elixir/apps/web/lib/web/live/settings/identity_providers/google_workspace/show.ex index 459a3cc6a..8050b80f8 100644 --- a/elixir/apps/web/lib/web/live/settings/identity_providers/google_workspace/show.ex +++ b/elixir/apps/web/lib/web/live/settings/identity_providers/google_workspace/show.ex @@ -233,7 +233,8 @@ defmodule Web.Settings.IdentityProviders.GoogleWorkspace.Show do end def handle_event("delete", _params, socket) do - {:ok, _provider} = Auth.delete_provider(socket.assigns.provider, socket.assigns.subject) + {:ok, _deleted_provider} = + Auth.delete_provider(socket.assigns.provider, socket.assigns.subject) {:noreply, push_navigate(socket, to: ~p"/#{socket.assigns.account}/settings/identity_providers")} diff --git a/elixir/apps/web/lib/web/live/settings/identity_providers/jumpcloud/show.ex b/elixir/apps/web/lib/web/live/settings/identity_providers/jumpcloud/show.ex index 30cd8514d..f9a81b67a 100644 --- a/elixir/apps/web/lib/web/live/settings/identity_providers/jumpcloud/show.ex +++ b/elixir/apps/web/lib/web/live/settings/identity_providers/jumpcloud/show.ex @@ -244,7 +244,8 @@ defmodule Web.Settings.IdentityProviders.JumpCloud.Show do end def handle_event("delete", _params, socket) do - {:ok, _provider} = Auth.delete_provider(socket.assigns.provider, socket.assigns.subject) + {:ok, _deleted_provider} = + Auth.delete_provider(socket.assigns.provider, socket.assigns.subject) {:noreply, push_navigate(socket, to: ~p"/#{socket.assigns.account}/settings/identity_providers")} diff --git a/elixir/apps/web/lib/web/live/settings/identity_providers/microsoft_entra/show.ex b/elixir/apps/web/lib/web/live/settings/identity_providers/microsoft_entra/show.ex index cb5198276..e1a2d4fb7 100644 --- a/elixir/apps/web/lib/web/live/settings/identity_providers/microsoft_entra/show.ex +++ b/elixir/apps/web/lib/web/live/settings/identity_providers/microsoft_entra/show.ex @@ -231,7 +231,8 @@ defmodule Web.Settings.IdentityProviders.MicrosoftEntra.Show do end def handle_event("delete", _params, socket) do - {:ok, _provider} = Auth.delete_provider(socket.assigns.provider, socket.assigns.subject) + {:ok, _deleted_provider} = + Auth.delete_provider(socket.assigns.provider, socket.assigns.subject) {:noreply, push_navigate(socket, to: ~p"/#{socket.assigns.account}/settings/identity_providers")} diff --git a/elixir/apps/web/lib/web/live/settings/identity_providers/mock/show.ex b/elixir/apps/web/lib/web/live/settings/identity_providers/mock/show.ex index d680e4aa2..b1cbac1c7 100644 --- a/elixir/apps/web/lib/web/live/settings/identity_providers/mock/show.ex +++ b/elixir/apps/web/lib/web/live/settings/identity_providers/mock/show.ex @@ -236,7 +236,8 @@ defmodule Web.Settings.IdentityProviders.Mock.Show do end def handle_event("delete", _params, socket) do - {:ok, _provider} = Auth.delete_provider(socket.assigns.provider, socket.assigns.subject) + {:ok, _deleted_provider} = + Auth.delete_provider(socket.assigns.provider, socket.assigns.subject) {:noreply, push_navigate(socket, to: ~p"/#{socket.assigns.account}/settings/identity_providers")} diff --git a/elixir/apps/web/lib/web/live/settings/identity_providers/okta/show.ex b/elixir/apps/web/lib/web/live/settings/identity_providers/okta/show.ex index d1a237c25..3cc254b26 100644 --- a/elixir/apps/web/lib/web/live/settings/identity_providers/okta/show.ex +++ b/elixir/apps/web/lib/web/live/settings/identity_providers/okta/show.ex @@ -251,7 +251,8 @@ defmodule Web.Settings.IdentityProviders.Okta.Show do end def handle_event("delete", _params, socket) do - {:ok, _provider} = Auth.delete_provider(socket.assigns.provider, socket.assigns.subject) + {:ok, _deleted_provider} = + Auth.delete_provider(socket.assigns.provider, socket.assigns.subject) {:noreply, push_navigate(socket, to: ~p"/#{socket.assigns.account}/settings/identity_providers")} diff --git a/elixir/apps/web/lib/web/live/settings/identity_providers/openid_connect/show.ex b/elixir/apps/web/lib/web/live/settings/identity_providers/openid_connect/show.ex index 66e60ce13..1deef0cd9 100644 --- a/elixir/apps/web/lib/web/live/settings/identity_providers/openid_connect/show.ex +++ b/elixir/apps/web/lib/web/live/settings/identity_providers/openid_connect/show.ex @@ -206,8 +206,11 @@ defmodule Web.Settings.IdentityProviders.OpenIDConnect.Show do end def handle_event("delete", _params, socket) do - {:ok, provider} = Auth.delete_provider(socket.assigns.provider, socket.assigns.subject) - {:noreply, push_navigate(socket, to: view_provider(socket.assigns.account, provider))} + provider = socket.assigns.provider + {:ok, _deleted_provider} = Auth.delete_provider(provider, socket.assigns.subject) + + {:noreply, + push_navigate(socket, to: ~p"/#{socket.assigns.account}/settings/identity_providers")} end def handle_event("delete_stale_actors", _params, socket) do diff --git a/elixir/apps/web/lib/web/live/settings/identity_providers/system/show.ex b/elixir/apps/web/lib/web/live/settings/identity_providers/system/show.ex index acd469656..298b0678f 100644 --- a/elixir/apps/web/lib/web/live/settings/identity_providers/system/show.ex +++ b/elixir/apps/web/lib/web/live/settings/identity_providers/system/show.ex @@ -110,7 +110,8 @@ defmodule Web.Settings.IdentityProviders.System.Show do end def handle_event("delete", _params, socket) do - {:ok, _provider} = Auth.delete_provider(socket.assigns.provider, socket.assigns.subject) + {:ok, _deleted_provider} = + Auth.delete_provider(socket.assigns.provider, socket.assigns.subject) {:noreply, push_navigate(socket, to: ~p"/#{socket.assigns.account}/settings/identity_providers")} diff --git a/elixir/apps/web/lib/web/live/sites/edit.ex b/elixir/apps/web/lib/web/live/sites/edit.ex index eae13cd40..44208b806 100644 --- a/elixir/apps/web/lib/web/live/sites/edit.ex +++ b/elixir/apps/web/lib/web/live/sites/edit.ex @@ -4,11 +4,7 @@ defmodule Web.Sites.Edit do def mount(%{"id" => id}, _session, socket) do with {:ok, group} <- - Gateways.fetch_group_by_id(id, socket.assigns.subject, - filter: [ - deleted?: false - ] - ) do + Gateways.fetch_group_by_id(id, socket.assigns.subject, filter: [deleted?: false]) do changeset = Gateways.change_group(group) socket = diff --git a/elixir/apps/web/lib/web/live/sites/new_token.ex b/elixir/apps/web/lib/web/live/sites/new_token.ex index 56252dc2d..80953cad9 100644 --- a/elixir/apps/web/lib/web/live/sites/new_token.ex +++ b/elixir/apps/web/lib/web/live/sites/new_token.ex @@ -342,10 +342,10 @@ defmodule Web.Sites.NewToken do else connected? = joins - |> Map.keys() - |> Enum.any?(fn id -> - gateway = Gateways.fetch_gateway_by_id!(id) - socket.assigns.token.id == gateway.last_used_token_id + |> Enum.any?(fn {_gateway_id, %{metas: metas}} -> + Enum.any?(metas, fn meta -> + Map.get(meta, :token_id) == socket.assigns.token.id + end) end) {:noreply, assign(socket, connected?: connected?)} diff --git a/elixir/apps/web/lib/web/live/sites/show.ex b/elixir/apps/web/lib/web/live/sites/show.ex index 6fdaf0d9b..64bad2188 100644 --- a/elixir/apps/web/lib/web/live/sites/show.ex +++ b/elixir/apps/web/lib/web/live/sites/show.ex @@ -488,17 +488,18 @@ defmodule Web.Sites.Show do do: handle_live_table_event(event, params, socket) def handle_event("revoke_all_tokens", _params, socket) do - {:ok, deleted_tokens} = Tokens.delete_tokens_for(socket.assigns.group, socket.assigns.subject) + {:ok, deleted_token_count} = + Tokens.delete_tokens_for(socket.assigns.group, socket.assigns.subject) socket = socket - |> put_flash(:info, "#{length(deleted_tokens)} token(s) were revoked.") + |> put_flash(:info, "#{deleted_token_count} token(s) were revoked.") {:noreply, socket} end def handle_event("delete", _params, socket) do - {:ok, _group} = Gateways.delete_group(socket.assigns.group, socket.assigns.subject) + {:ok, _deleted_group} = Gateways.delete_group(socket.assigns.group, socket.assigns.subject) {:noreply, push_navigate(socket, to: ~p"/#{socket.assigns.account}/sites")} end diff --git a/elixir/apps/web/test/web/acceptance/auth/userpass_test.exs b/elixir/apps/web/test/web/acceptance/auth/userpass_test.exs index 8cf90324f..e842dae88 100644 --- a/elixir/apps/web/test/web/acceptance/auth/userpass_test.exs +++ b/elixir/apps/web/test/web/acceptance/auth/userpass_test.exs @@ -77,7 +77,6 @@ defmodule Web.Acceptance.Auth.UserPassTest do provider: provider, provider_identifier: provider_identifier ) - |> Fixtures.Actors.delete() password = "Firezone1234" @@ -89,6 +88,8 @@ defmodule Web.Acceptance.Auth.UserPassTest do provider_virtual_state: %{"password" => password, "password_confirmation" => password} ) + Fixtures.Actors.delete(actor) + session |> password_login_flow(account, identity.provider_identifier, password) |> assert_error_flash("Invalid username or password.") diff --git a/elixir/apps/web/test/web/acceptance/auth_test.exs b/elixir/apps/web/test/web/acceptance/auth_test.exs index e6d562c97..4a10a9335 100644 --- a/elixir/apps/web/test/web/acceptance/auth_test.exs +++ b/elixir/apps/web/test/web/acceptance/auth_test.exs @@ -88,11 +88,14 @@ defmodule Web.Acceptance.AuthTest do |> Auth.authenticate(identity) |> visit(~p"/#{account}/actors") - {:ok, tokens} = Domain.Tokens.delete_tokens_for(identity) + tokens = + Domain.Tokens.Token.Query.all() + |> Domain.Tokens.Token.Query.by_identity_id(identity.id) + |> Domain.Repo.all() + + {:ok, _count} = Domain.Tokens.delete_tokens_for(identity) for token <- tokens do - assert %DateTime{} = token.deleted_at - Domain.Changes.Hooks.Tokens.on_delete(0, %{ "remaining_attempts" => token.remaining_attempts, "actor_id" => token.actor_id, diff --git a/elixir/apps/web/test/web/auth_test.exs b/elixir/apps/web/test/web/auth_test.exs index 22d00a6eb..51f25b4f8 100644 --- a/elixir/apps/web/test/web/auth_test.exs +++ b/elixir/apps/web/test/web/auth_test.exs @@ -542,8 +542,7 @@ defmodule Web.AuthTest do |> put_session(:live_socket_id, live_socket_id) |> sign_out(%{}) - token = Repo.get!(Domain.Tokens.Token, subject.token_id) - assert token.deleted_at + refute(Repo.get(Domain.Tokens.Token, subject.token_id)) end end diff --git a/elixir/apps/web/test/web/controllers/auth_controller_test.exs b/elixir/apps/web/test/web/controllers/auth_controller_test.exs index a8a6bcd5d..ba88d7110 100644 --- a/elixir/apps/web/test/web/controllers/auth_controller_test.exs +++ b/elixir/apps/web/test/web/controllers/auth_controller_test.exs @@ -1279,8 +1279,7 @@ defmodule Web.AuthControllerTest do assert redirected_to(conn) =~ ~p"/#{account}" - token = Repo.get!(Domain.Tokens.Token, conn.assigns.subject.token_id) - assert token.deleted_at + refute Repo.get(Domain.Tokens.Token, conn.assigns.subject.token_id) end test "works even if user is already logged out", %{conn: conn} do diff --git a/elixir/apps/web/test/web/live/actors/index_test.exs b/elixir/apps/web/test/web/live/actors/index_test.exs index 1c1a4155c..2de93598e 100644 --- a/elixir/apps/web/test/web/live/actors/index_test.exs +++ b/elixir/apps/web/test/web/live/actors/index_test.exs @@ -4,11 +4,15 @@ defmodule Web.Live.Actors.IndexTest do setup do account = Fixtures.Accounts.create_account() - identity = Fixtures.Auth.create_identity(account: account, actor: [type: :account_admin_user]) + actor = Fixtures.Actors.create_actor(type: :account_admin_user, account: account) + identity = Fixtures.Auth.create_identity(account: account, actor: actor) + subject = Fixtures.Auth.create_subject(account: account, actor: actor, identity: identity) %{ account: account, - identity: identity + actor: actor, + identity: identity, + subject: subject } end @@ -59,9 +63,19 @@ defmodule Web.Live.Actors.IndexTest do conn: conn } do admin_actor = Fixtures.Actors.create_actor(account: account, type: :account_admin_user) + admin_identity = Fixtures.Auth.create_identity(account: account, actor: admin_actor) Fixtures.Actors.create_membership(account: account, actor: admin_actor) + client = Fixtures.Clients.create_client(account: account, actor: admin_actor) - :ok = Clients.Presence.connect(client) + + client_token = + Fixtures.Tokens.create_client_token( + account: account, + actor: admin_actor, + identity: admin_identity + ) + + :ok = Clients.Presence.connect(client, client_token.id) admin_actor = Repo.preload(admin_actor, identities: [:provider], groups: []) user_actor = Fixtures.Actors.create_actor(account: account, type: :account_user) diff --git a/elixir/apps/web/test/web/live/actors/show_test.exs b/elixir/apps/web/test/web/live/actors/show_test.exs index 8f59ccd49..99a23de6d 100644 --- a/elixir/apps/web/test/web/live/actors/show_test.exs +++ b/elixir/apps/web/test/web/live/actors/show_test.exs @@ -16,23 +16,21 @@ defmodule Web.Live.Actors.ShowTest do }}} end - test "renders deleted actor without action buttons", %{conn: conn} do + test "raises NotFoundError for deleted actor", %{conn: conn} do account = Fixtures.Accounts.create_account() - actor = + deleted_actor = Fixtures.Actors.create_actor(type: :account_admin_user, account: account) |> Fixtures.Actors.delete() auth_actor = Fixtures.Actors.create_actor(type: :account_admin_user, account: account) auth_identity = Fixtures.Auth.create_identity(account: account, actor: auth_actor) - {:ok, _lv, html} = + assert_raise Web.LiveErrors.NotFoundError, fn -> conn |> authorize_conn(auth_identity) - |> live(~p"/#{account}/actors/#{actor}") - - assert html =~ "(deleted)" - assert active_buttons(html) == [] + |> live(~p"/#{account}/actors/#{deleted_actor}") + end end test "renders breadcrumbs item", %{conn: conn} do @@ -92,7 +90,8 @@ defmodule Web.Live.Actors.ShowTest do Domain.Config.put_env_override(:test_pid, self()) :ok = Domain.Clients.Presence.Actor.subscribe(actor.id) - assert Domain.Clients.Presence.connect(client) == :ok + client_token = Fixtures.Tokens.create_client_token(account: account, actor: actor) + assert Domain.Clients.Presence.connect(client, client_token.id) == :ok assert_receive %Phoenix.Socket.Broadcast{topic: "presences:actor_clients:" <> _} assert_receive {:live_table_reloaded, "clients"}, 500 @@ -145,7 +144,7 @@ defmodule Web.Live.Actors.ShowTest do "#{flow.gateway.group.name}-#{flow.gateway.name} #{flow.gateway.last_seen_remote_ip}" end - test "renders flows even for deleted policies", %{ + test "does not render flows for deleted policies", %{ conn: conn } do account = Fixtures.Accounts.create_account() @@ -167,21 +166,11 @@ defmodule Web.Live.Actors.ShowTest do |> authorize_conn(identity) |> live(~p"/#{account}/actors/#{actor}") - [row] = + [] = lv |> element("#flows") |> render() |> table_to_map() - - assert row["authorized"] - assert row["policy"] =~ flow.policy.actor_group.name - assert row["policy"] =~ flow.policy.resource.name - - assert row["client"] == - "#{flow.client.name} #{client.last_seen_remote_ip}" - - assert row["gateway"] == - "#{flow.gateway.group.name}-#{flow.gateway.name} #{flow.gateway.last_seen_remote_ip}" end test "does not render flows for deleted policy assocs", %{ @@ -699,7 +688,7 @@ defmodule Web.Live.Actors.ShowTest do |> render() |> table_to_map() == [] - assert Repo.get_by(Domain.Tokens.Token, id: token.id).deleted_at + refute Repo.get_by(Domain.Tokens.Token, id: token.id) end test "allows revoking all tokens", %{ @@ -732,7 +721,7 @@ defmodule Web.Live.Actors.ShowTest do |> render() |> table_to_map() == [] - assert Repo.get_by(Domain.Tokens.Token, id: token.id).deleted_at + refute Repo.get_by(Domain.Tokens.Token, id: token.id) end test "allows editing actors", %{ @@ -771,7 +760,7 @@ defmodule Web.Live.Actors.ShowTest do assert_redirect(lv, ~p"/#{account}/actors") - assert Repo.get(Domain.Actors.Actor, actor.id).deleted_at + refute Repo.get(Domain.Actors.Actor, actor.id) end test "allows deleting synced actors that don't have any identities left", %{ @@ -794,7 +783,7 @@ defmodule Web.Live.Actors.ShowTest do assert_redirect(lv, ~p"/#{account}/actors") - assert Repo.get(Domain.Actors.Actor, actor.id).deleted_at + refute Repo.get(Domain.Actors.Actor, actor.id) end test "renders error when trying to delete last admin", %{ @@ -814,7 +803,7 @@ defmodule Web.Live.Actors.ShowTest do |> Floki.find(".flash-error") |> element_to_text() =~ "You can't delete the last admin of an account." - refute Repo.get(Domain.Actors.Actor, actor.id).deleted_at + assert Repo.get(Domain.Actors.Actor, actor.id) end test "allows disabling actors", %{ @@ -997,7 +986,7 @@ defmodule Web.Live.Actors.ShowTest do assert_redirect(lv, ~p"/#{account}/actors") - assert Repo.get(Domain.Actors.Actor, actor.id).deleted_at + refute Repo.get(Domain.Actors.Actor, actor.id) end test "allows disabling actors", %{ @@ -1128,7 +1117,7 @@ defmodule Web.Live.Actors.ShowTest do |> render() |> table_to_map() == [] - assert Repo.get_by(Domain.Tokens.Token, id: token.id).deleted_at + refute Repo.get_by(Domain.Tokens.Token, id: token.id) end test "allows revoking all tokens", %{ @@ -1161,7 +1150,7 @@ defmodule Web.Live.Actors.ShowTest do |> render() |> table_to_map() == [] - assert Repo.get_by(Domain.Tokens.Token, id: token.id).deleted_at + refute Repo.get_by(Domain.Tokens.Token, id: token.id) end end end diff --git a/elixir/apps/web/test/web/live/clients/index_test.exs b/elixir/apps/web/test/web/live/clients/index_test.exs index 6692c08ce..22c67c0ee 100644 --- a/elixir/apps/web/test/web/live/clients/index_test.exs +++ b/elixir/apps/web/test/web/live/clients/index_test.exs @@ -3,11 +3,15 @@ defmodule Web.Live.Clients.IndexTest do setup do account = Fixtures.Accounts.create_account() - identity = Fixtures.Auth.create_identity(account: account, actor: [type: :account_admin_user]) + actor = Fixtures.Actors.create_actor(type: :account_admin_user, account: account) + identity = Fixtures.Auth.create_identity(account: account, actor: actor) + subject = Fixtures.Auth.create_subject(account: account, actor: actor, identity: identity) %{ account: account, - identity: identity + actor: actor, + identity: identity, + subject: subject } end @@ -56,10 +60,28 @@ defmodule Web.Live.Clients.IndexTest do identity: identity, conn: conn } do - online_client = Fixtures.Clients.create_client(account: account) + online_client_actor = Fixtures.Actors.create_actor(account: account, type: :service_account) + + online_client_identity = + Fixtures.Auth.create_identity(account: account, actor: online_client_actor) + + online_client = + Fixtures.Clients.create_client( + account: account, + actor: online_client_actor, + identity: online_client_identity + ) + offline_client = Fixtures.Clients.create_client(account: account) - :ok = Domain.Clients.Presence.connect(online_client) + client_token = + Fixtures.Tokens.create_client_token( + account: account, + actor: online_client_actor, + identity: online_client_identity + ) + + :ok = Domain.Clients.Presence.connect(online_client, client_token.id) {:ok, lv, _html} = conn @@ -94,7 +116,10 @@ defmodule Web.Live.Clients.IndexTest do conn: conn } do actor = Fixtures.Actors.create_actor(account: account) - client = Fixtures.Clients.create_client(account: account, actor: actor) + client_identity = Fixtures.Auth.create_identity(account: account, actor: actor) + + client = + Fixtures.Clients.create_client(account: account, actor: actor, identity: client_identity) {:ok, lv, _html} = conn @@ -103,7 +128,15 @@ defmodule Web.Live.Clients.IndexTest do Domain.Config.put_env_override(:test_pid, self()) :ok = Domain.Clients.Presence.Actor.subscribe(client.actor_id) - assert Domain.Clients.Presence.connect(client) == :ok + + client_token = + Fixtures.Tokens.create_client_token( + account: account, + actor: actor, + identity: client_identity + ) + + assert Domain.Clients.Presence.connect(client, client_token.id) == :ok assert_receive %Phoenix.Socket.Broadcast{topic: "presences:actor_clients:" <> _} assert_receive {:live_table_reloaded, "clients"}, 250 diff --git a/elixir/apps/web/test/web/live/clients/show_test.exs b/elixir/apps/web/test/web/live/clients/show_test.exs index 63715fe37..86901f736 100644 --- a/elixir/apps/web/test/web/live/clients/show_test.exs +++ b/elixir/apps/web/test/web/live/clients/show_test.exs @@ -34,7 +34,7 @@ defmodule Web.Live.Clients.ShowTest do }}} end - test "renders deleted client without action buttons", %{ + test "raises NotFoundError for deleted client", %{ account: account, client: client, identity: identity, @@ -42,13 +42,11 @@ defmodule Web.Live.Clients.ShowTest do } do client = Fixtures.Clients.delete_client(client) - {:ok, _lv, html} = + assert_raise Web.LiveErrors.NotFoundError, fn -> conn |> authorize_conn(identity) |> live(~p"/#{account}/clients/#{client}") - - assert html =~ "(deleted)" - assert active_buttons(html) == [] + end end test "renders breadcrumbs item", %{ @@ -112,11 +110,15 @@ defmodule Web.Live.Clients.ShowTest do test "shows client online status", %{ account: account, + actor: actor, client: client, identity: identity, conn: conn } do - :ok = Domain.Clients.Presence.connect(client) + client_token = + Fixtures.Tokens.create_client_token(account: account, actor: actor, identity: identity) + + :ok = Domain.Clients.Presence.connect(client, client_token.id) {:ok, lv, _html} = conn @@ -145,7 +147,11 @@ defmodule Web.Live.Clients.ShowTest do |> live(~p"/#{account}/clients/#{client}") :ok = Domain.Clients.Presence.Actor.subscribe(actor.id) - assert Domain.Clients.Presence.connect(client) == :ok + + client_token = + Fixtures.Tokens.create_client_token(account: account, actor: actor, identity: identity) + + assert Domain.Clients.Presence.connect(client, client_token.id) == :ok assert_receive %Phoenix.Socket.Broadcast{topic: "presences:actor_clients:" <> _} wait_for(fn -> @@ -213,7 +219,7 @@ defmodule Web.Live.Clients.ShowTest do "#{flow.gateway.group.name}-#{flow.gateway.name} #{flow.gateway.last_seen_remote_ip}" end - test "renders flows even for deleted policies", %{ + test "does not render flows for deleted policies", %{ account: account, identity: identity, client: client, @@ -233,19 +239,11 @@ defmodule Web.Live.Clients.ShowTest do |> authorize_conn(identity) |> live(~p"/#{account}/clients/#{client}") - [row] = - lv - |> element("#flows") - |> render() - |> table_to_map() - - assert row["authorized"] - assert row["remote ip"] == to_string(client.last_seen_remote_ip) - assert row["policy"] =~ flow.policy.actor_group.name - assert row["policy"] =~ flow.policy.resource.name - - assert row["gateway"] == - "#{flow.gateway.group.name}-#{flow.gateway.name} #{flow.gateway.last_seen_remote_ip}" + assert [] = + lv + |> element("#flows") + |> render() + |> table_to_map() end test "does not render flows for deleted policy assocs", %{ @@ -337,6 +335,6 @@ defmodule Web.Live.Clients.ShowTest do assert_redirected(lv, ~p"/#{account}/clients") - assert Repo.get(Domain.Clients.Client, client.id).deleted_at + refute Repo.get(Domain.Clients.Client, client.id) end end diff --git a/elixir/apps/web/test/web/live/gateways/show_test.exs b/elixir/apps/web/test/web/live/gateways/show_test.exs index a7550a33e..cc1285473 100644 --- a/elixir/apps/web/test/web/live/gateways/show_test.exs +++ b/elixir/apps/web/test/web/live/gateways/show_test.exs @@ -35,7 +35,7 @@ defmodule Web.Live.Gateways.ShowTest do }}} end - test "renders deleted gateway without action buttons", %{ + test "raises NotFoundError for deleted gateway", %{ account: account, gateway: gateway, identity: identity, @@ -43,13 +43,11 @@ defmodule Web.Live.Gateways.ShowTest do } do gateway = Fixtures.Gateways.delete_gateway(gateway) - {:ok, _lv, html} = + assert_raise Web.LiveErrors.NotFoundError, fn -> conn |> authorize_conn(identity) |> live(~p"/#{account}/gateways/#{gateway}") - - assert html =~ "(deleted)" - assert active_buttons(html) == [] + end end test "renders breadcrumbs item", %{ @@ -102,7 +100,8 @@ defmodule Web.Live.Gateways.ShowTest do identity: identity, conn: conn } do - :ok = Domain.Gateways.Presence.connect(gateway) + gateway_token = Fixtures.Gateways.create_token(group: gateway.group, account: account) + :ok = Domain.Gateways.Presence.connect(gateway, gateway_token.id) {:ok, lv, _html} = conn @@ -135,7 +134,7 @@ defmodule Web.Live.Gateways.ShowTest do assert_redirected(lv, ~p"/#{account}/sites/#{gateway.group}") - assert Repo.get(Domain.Gateways.Gateway, gateway.id).deleted_at + refute Repo.get(Domain.Gateways.Gateway, gateway.id) end test "updates gateway status on presence event", %{ @@ -150,7 +149,8 @@ defmodule Web.Live.Gateways.ShowTest do |> live(~p"/#{account}/gateways/#{gateway}") :ok = Domain.Gateways.Presence.Group.subscribe(gateway.group.id) - :ok = Domain.Gateways.Presence.connect(gateway) + gateway_token = Fixtures.Gateways.create_token(group: gateway.group, account: account) + :ok = Domain.Gateways.Presence.connect(gateway, gateway_token.id) assert_receive %{topic: "presences:group_gateways:" <> _} table = diff --git a/elixir/apps/web/test/web/live/groups/index_test.exs b/elixir/apps/web/test/web/live/groups/index_test.exs index 927ca0ac0..38944eaa8 100644 --- a/elixir/apps/web/test/web/live/groups/index_test.exs +++ b/elixir/apps/web/test/web/live/groups/index_test.exs @@ -90,14 +90,6 @@ defmodule Web.Live.Groups.IndexTest do empty_group = Fixtures.Actors.create_group(account: account) group_with_few_preloads = Fixtures.Actors.create_group(account: account) - deleted_actor = Fixtures.Actors.create_actor(account: account) |> Fixtures.Actors.delete() - - Fixtures.Actors.create_membership( - account: account, - group: group_with_few_preloads, - actor: deleted_actor - ) - actor = Fixtures.Actors.create_actor(account: account) Fixtures.Actors.create_membership( diff --git a/elixir/apps/web/test/web/live/groups/show_test.exs b/elixir/apps/web/test/web/live/groups/show_test.exs index 82129c55b..aa81d878a 100644 --- a/elixir/apps/web/test/web/live/groups/show_test.exs +++ b/elixir/apps/web/test/web/live/groups/show_test.exs @@ -32,7 +32,7 @@ defmodule Web.Live.Groups.ShowTest do }}} end - test "renders deleted group without action buttons", %{ + test "raises NotFoundError for deleted group", %{ account: account, group: group, identity: identity, @@ -40,13 +40,11 @@ defmodule Web.Live.Groups.ShowTest do } do group = Fixtures.Actors.delete_group(group) - {:ok, _lv, html} = + assert_raise Web.LiveErrors.NotFoundError, fn -> conn |> authorize_conn(identity) |> live(~p"/#{account}/groups/#{group}") - - assert html =~ "(deleted)" - assert active_buttons(html) == [] + end end test "renders breadcrumbs item", %{ @@ -313,6 +311,6 @@ defmodule Web.Live.Groups.ShowTest do assert_redirected(lv, ~p"/#{account}/groups") - assert Repo.get(Domain.Actors.Group, group.id).deleted_at + refute Repo.get(Domain.Actors.Group, group.id) end end diff --git a/elixir/apps/web/test/web/live/policies/show_test.exs b/elixir/apps/web/test/web/live/policies/show_test.exs index 17f6b1798..0083c2347 100644 --- a/elixir/apps/web/test/web/live/policies/show_test.exs +++ b/elixir/apps/web/test/web/live/policies/show_test.exs @@ -75,21 +75,19 @@ defmodule Web.Live.Policies.ShowTest do }}} end - test "renders deleted policy without action buttons", %{ + test "raises NotFoundError for deleted policy", %{ account: account, policy: policy, identity: identity, conn: conn } do - policy = Fixtures.Policies.delete_policy(policy) + {:ok, deleted_policy} = Fixtures.Policies.delete_policy(policy) - {:ok, _lv, html} = + assert_raise Web.LiveErrors.NotFoundError, fn -> conn |> authorize_conn(identity) - |> live(~p"/#{account}/policies/#{policy}") - - assert html =~ "(deleted)" - assert active_buttons(html) == [] + |> live(~p"/#{account}/policies/#{deleted_policy}") + end end test "renders breadcrumbs item", %{ @@ -253,14 +251,7 @@ defmodule Web.Live.Policies.ShowTest do |> render_click() == {:error, {:live_redirect, %{to: ~p"/#{account}/policies", kind: :push}}} - assert Repo.get(Domain.Policies.Policy, policy.id).deleted_at - - {:ok, _lv, html} = - conn - |> authorize_conn(identity) - |> live(~p"/#{account}/policies/#{policy}") - - assert html =~ "(deleted)" + refute Repo.get(Domain.Policies.Policy, policy.id) end test "allows disabling and enabling policy", %{ diff --git a/elixir/apps/web/test/web/live/relay_groups/index_test.exs b/elixir/apps/web/test/web/live/relay_groups/index_test.exs index 3beb94098..f6820eb8e 100644 --- a/elixir/apps/web/test/web/live/relay_groups/index_test.exs +++ b/elixir/apps/web/test/web/live/relay_groups/index_test.exs @@ -3,11 +3,15 @@ defmodule Web.Live.RelayGroups.IndexTest do setup do account = Fixtures.Accounts.create_account() - identity = Fixtures.Auth.create_identity(account: account, actor: [type: :account_admin_user]) + actor = Fixtures.Actors.create_actor(type: :account_admin_user, account: account) + identity = Fixtures.Auth.create_identity(account: account, actor: actor) + subject = Fixtures.Auth.create_subject(account: account, actor: actor, identity: identity) %{ account: account, - identity: identity + actor: actor, + identity: identity, + subject: subject } end @@ -84,9 +88,10 @@ defmodule Web.Live.RelayGroups.IndexTest do conn: conn } do group = Fixtures.Relays.create_group(account: account) - relay = Fixtures.Relays.create_relay(account: account, group: group) + relay = Fixtures.Relays.create_relay(account: account, group: group) |> Repo.preload(:group) + relay_token = Fixtures.Relays.create_token(group: relay.group, account: account) - :ok = Domain.Relays.connect_relay(relay, "foo") + :ok = Domain.Relays.connect_relay(relay, "foo", relay_token.id) {:ok, lv, _html} = conn @@ -108,7 +113,8 @@ defmodule Web.Live.RelayGroups.IndexTest do conn: conn } do group = Fixtures.Relays.create_group(account: account) - relay = Fixtures.Relays.create_relay(account: account, group: group) + relay = Fixtures.Relays.create_relay(account: account, group: group) |> Repo.preload(:group) + relay_token = Fixtures.Relays.create_token(group: relay.group, account: account) {:ok, lv, _html} = conn @@ -117,7 +123,8 @@ defmodule Web.Live.RelayGroups.IndexTest do Domain.Config.put_env_override(:test_pid, self()) :ok = Domain.Relays.subscribe_to_relays_presence_in_group(group) - :ok = Domain.Relays.connect_relay(relay, "foo") + + :ok = Domain.Relays.connect_relay(relay, "foo", relay_token.id) assert_receive %Phoenix.Socket.Broadcast{topic: "presences:group_relays:" <> _} assert_receive {:live_table_reloaded, "groups"}, 250 diff --git a/elixir/apps/web/test/web/live/relay_groups/new_token_test.exs b/elixir/apps/web/test/web/live/relay_groups/new_token_test.exs index a72730bc6..0112558bd 100644 --- a/elixir/apps/web/test/web/live/relay_groups/new_token_test.exs +++ b/elixir/apps/web/test/web/live/relay_groups/new_token_test.exs @@ -40,7 +40,7 @@ defmodule Web.Live.RelayGroups.NewTokenTest do context = Fixtures.Auth.build_context(type: :relay_group) assert {:ok, group, token} = Domain.Relays.authenticate(token, context) relay = Fixtures.Relays.create_relay(account: account, group: group, token: token) - Domain.Relays.connect_relay(relay, "foo") + Domain.Relays.connect_relay(relay, "foo", token.id) assert_receive %Phoenix.Socket.Broadcast{topic: "presences:group_relays:" <> _group_id} diff --git a/elixir/apps/web/test/web/live/relay_groups/show_test.exs b/elixir/apps/web/test/web/live/relay_groups/show_test.exs index 56e8fb9a7..8127003ff 100644 --- a/elixir/apps/web/test/web/live/relay_groups/show_test.exs +++ b/elixir/apps/web/test/web/live/relay_groups/show_test.exs @@ -37,7 +37,7 @@ defmodule Web.Live.RelayGroups.ShowTest do }}} end - test "renders deleted relay without action buttons", %{ + test "raises NotFoundError for deleted relay", %{ account: account, group: group, identity: identity, @@ -45,13 +45,11 @@ defmodule Web.Live.RelayGroups.ShowTest do } do group = Fixtures.Relays.delete_group(group) - {:ok, _lv, html} = + assert_raise Web.LiveErrors.NotFoundError, fn -> conn |> authorize_conn(identity) |> live(~p"/#{account}/relay_groups/#{group}") - - assert html =~ "(deleted)" - assert active_buttons(html) == [] + end end test "renders breadcrumbs item", %{ @@ -139,7 +137,10 @@ defmodule Web.Live.RelayGroups.ShowTest do identity: identity, conn: conn } do - :ok = Domain.Relays.connect_relay(relay, "foo") + relay = Repo.preload(relay, :group) + relay_token = Fixtures.Relays.create_token(group: relay.group, account: account) + + :ok = Domain.Relays.connect_relay(relay, "foo", relay_token.id) {:ok, lv, _html} = conn @@ -169,7 +170,10 @@ defmodule Web.Live.RelayGroups.ShowTest do Domain.Config.put_env_override(:test_pid, self()) :ok = Domain.Relays.subscribe_to_relays_presence_in_group(group) - :ok = Domain.Relays.connect_relay(relay, "foo") + relay = Repo.preload(relay, :group) + relay_token = Fixtures.Relays.create_token(group: relay.group, account: account) + + :ok = Domain.Relays.connect_relay(relay, "foo", relay_token.id) assert_receive %Phoenix.Socket.Broadcast{topic: "presences:group_relays:" <> _} assert_receive {:live_table_reloaded, "relays"}, 250 @@ -201,7 +205,7 @@ defmodule Web.Live.RelayGroups.ShowTest do assert_redirected(lv, ~p"/#{account}/relay_groups") - assert Repo.get(Domain.Relays.Group, group.id).deleted_at + refute Repo.get(Domain.Relays.Group, group.id) end test "allows revoking all tokens", %{ @@ -219,7 +223,7 @@ defmodule Web.Live.RelayGroups.ShowTest do |> element("button[type=submit]", "Revoke All") |> render_click() =~ "1 token(s) were revoked." - assert Repo.get_by(Domain.Tokens.Token, relay_group_id: group.id).deleted_at + refute Repo.get_by(Domain.Tokens.Token, relay_group_id: group.id) end test "renders not found error when self_hosted_relays feature flag is false", %{ diff --git a/elixir/apps/web/test/web/live/relays/show_test.exs b/elixir/apps/web/test/web/live/relays/show_test.exs index 0b23b4b3a..180b1f2f9 100644 --- a/elixir/apps/web/test/web/live/relays/show_test.exs +++ b/elixir/apps/web/test/web/live/relays/show_test.exs @@ -35,7 +35,7 @@ defmodule Web.Live.Relays.ShowTest do }}} end - test "renders deleted relay without action buttons", %{ + test "raises NotFoundError for deleted relay", %{ account: account, relay: relay, identity: identity, @@ -43,13 +43,11 @@ defmodule Web.Live.Relays.ShowTest do } do relay = Fixtures.Relays.delete_relay(relay) - {:ok, _lv, html} = + assert_raise Web.LiveErrors.NotFoundError, fn -> conn |> authorize_conn(identity) |> live(~p"/#{account}/relays/#{relay}") - - assert html =~ "(deleted)" - assert active_buttons(html) == [] + end end test "renders breadcrumbs item", %{ @@ -101,9 +99,11 @@ defmodule Web.Live.Relays.ShowTest do account: account, relay: relay, identity: identity, + subject: _subject, conn: conn } do - :ok = Domain.Relays.connect_relay(relay, "foo") + relay_token = Fixtures.Relays.create_token(group: relay.group, account: account) + :ok = Domain.Relays.connect_relay(relay, "foo", relay_token.id) {:ok, lv, _html} = conn @@ -123,6 +123,7 @@ defmodule Web.Live.Relays.ShowTest do account: account, relay: relay, identity: identity, + subject: _subject, conn: conn } do {:ok, lv, _html} = @@ -131,7 +132,8 @@ defmodule Web.Live.Relays.ShowTest do |> live(~p"/#{account}/relays/#{relay}") :ok = Domain.Relays.subscribe_to_relays_presence_in_group(relay.group_id) - :ok = Domain.Relays.connect_relay(relay, "foo") + relay_token = Fixtures.Relays.create_token(group: relay.group, account: account) + :ok = Domain.Relays.connect_relay(relay, "foo", relay_token.id) assert_receive %Phoenix.Socket.Broadcast{topic: "presences:group_relays:" <> _} wait_for(fn -> @@ -162,7 +164,7 @@ defmodule Web.Live.Relays.ShowTest do assert_redirected(lv, ~p"/#{account}/relay_groups/#{relay.group}") - assert Repo.get(Domain.Relays.Relay, relay.id).deleted_at + refute Repo.get(Domain.Relays.Relay, relay.id) end test "renders not found error when self_hosted_relays feature flag is false", %{ diff --git a/elixir/apps/web/test/web/live/resources/show_test.exs b/elixir/apps/web/test/web/live/resources/show_test.exs index 6761d1013..f1875278b 100644 --- a/elixir/apps/web/test/web/live/resources/show_test.exs +++ b/elixir/apps/web/test/web/live/resources/show_test.exs @@ -67,7 +67,7 @@ defmodule Web.Live.Resources.ShowTest do assert active_buttons(html) == [] end - test "renders deleted resource without action buttons", %{ + test "raises NotFoundError for deleted resource", %{ account: account, resource: resource, identity: identity, @@ -75,13 +75,11 @@ defmodule Web.Live.Resources.ShowTest do } do resource = Fixtures.Resources.delete_resource(resource) - {:ok, _lv, html} = + assert_raise Web.LiveErrors.NotFoundError, fn -> conn |> authorize_conn(identity) |> live(~p"/#{account}/resources/#{resource}") - - assert html =~ "(deleted)" - assert active_buttons(html) == [] + end end test "renders breadcrumbs item", %{ @@ -339,12 +337,14 @@ defmodule Web.Live.Resources.ShowTest do |> authorize_conn(identity) |> live(~p"/#{account}/resources/#{resource}") - assert lv - |> element("button[type=submit]", "Delete Resource") - |> render_click() == - {:error, {:live_redirect, %{to: ~p"/#{account}/resources", kind: :push}}} + assert {:error, {:live_redirect, %{to: redirect_path, kind: :push}}} = + lv + |> element("button[type=submit]", "Delete Resource") + |> render_click() - assert Repo.get(Domain.Resources.Resource, resource.id).deleted_at + assert redirect_path == ~p"/#{account}/resources" + + refute Repo.get(Domain.Resources.Resource, resource.id) end test "renders created_by info when created by Identity", %{ diff --git a/elixir/apps/web/test/web/live/settings/api_clients/show_test.exs b/elixir/apps/web/test/web/live/settings/api_clients/show_test.exs index cc1ef24a5..eaf2beaaf 100644 --- a/elixir/apps/web/test/web/live/settings/api_clients/show_test.exs +++ b/elixir/apps/web/test/web/live/settings/api_clients/show_test.exs @@ -30,7 +30,7 @@ defmodule Web.Live.Settings.ApiClients.ShowTest do }}} end - test "renders deleted actor without action buttons", %{conn: conn} do + test "raises NotFoundError for deleted actor", %{conn: conn} do account = Fixtures.Accounts.create_account() api_client = @@ -40,13 +40,11 @@ defmodule Web.Live.Settings.ApiClients.ShowTest do auth_actor = Fixtures.Actors.create_actor(type: :account_admin_user, account: account) auth_identity = Fixtures.Auth.create_identity(account: account, actor: auth_actor) - {:ok, _lv, html} = + assert_raise Web.LiveErrors.NotFoundError, fn -> conn |> authorize_conn(auth_identity) |> live(~p"/#{account}/settings/api_clients/#{api_client}") - - assert html =~ "(deleted)" - assert active_buttons(html) == [] + end end test "renders breadcrumbs item", %{conn: conn} do @@ -143,8 +141,7 @@ defmodule Web.Live.Settings.ApiClients.ShowTest do |> render_click() assert_redirect(lv, ~p"/#{account}/settings/api_clients") - - assert Repo.get(Domain.Actors.Actor, api_client.id).deleted_at + refute Repo.get(Domain.Actors.Actor, api_client.id) end test "allows disabling api clients", %{ @@ -240,7 +237,7 @@ defmodule Web.Live.Settings.ApiClients.ShowTest do |> render() |> table_to_map() == [] - assert Repo.get_by(Domain.Tokens.Token, id: token.id).deleted_at + refute Repo.get_by(Domain.Tokens.Token, id: token.id) end test "allows revoking all tokens", %{ @@ -270,6 +267,6 @@ defmodule Web.Live.Settings.ApiClients.ShowTest do |> render() |> table_to_map() == [] - assert Repo.get_by(Domain.Tokens.Token, id: token.id).deleted_at + refute Repo.get_by(Domain.Tokens.Token, id: token.id) end end diff --git a/elixir/apps/web/test/web/live/settings/identity_providers/google_workspace/show_test.exs b/elixir/apps/web/test/web/live/settings/identity_providers/google_workspace/show_test.exs index b597f7d2a..bc5b5be43 100644 --- a/elixir/apps/web/test/web/live/settings/identity_providers/google_workspace/show_test.exs +++ b/elixir/apps/web/test/web/live/settings/identity_providers/google_workspace/show_test.exs @@ -262,8 +262,7 @@ defmodule Web.Live.Settings.IdentityProviders.GoogleWorkspace.ShowTest do |> render_click() assert_redirected(lv, ~p"/#{account}/settings/identity_providers") - - assert Repo.get(Domain.Auth.Provider, provider.id).deleted_at + refute Repo.get(Domain.Auth.Provider, provider.id) end test "allows reconnecting identity providers", %{ diff --git a/elixir/apps/web/test/web/live/settings/identity_providers/jumpcloud/show_test.exs b/elixir/apps/web/test/web/live/settings/identity_providers/jumpcloud/show_test.exs index 8b92c210c..c7bc5ac6f 100644 --- a/elixir/apps/web/test/web/live/settings/identity_providers/jumpcloud/show_test.exs +++ b/elixir/apps/web/test/web/live/settings/identity_providers/jumpcloud/show_test.exs @@ -300,8 +300,7 @@ defmodule Web.Live.Settings.IdentityProviders.JumpCloud.ShowTest do |> render_click() assert_redirected(lv, ~p"/#{account}/settings/identity_providers") - - assert Repo.get(Domain.Auth.Provider, provider.id).deleted_at + refute Repo.get(Domain.Auth.Provider, provider.id) end test "allows reconnecting identity providers", %{ diff --git a/elixir/apps/web/test/web/live/settings/identity_providers/microsoft_entra/show_test.exs b/elixir/apps/web/test/web/live/settings/identity_providers/microsoft_entra/show_test.exs index 48b23d430..bcdd9921f 100644 --- a/elixir/apps/web/test/web/live/settings/identity_providers/microsoft_entra/show_test.exs +++ b/elixir/apps/web/test/web/live/settings/identity_providers/microsoft_entra/show_test.exs @@ -263,8 +263,7 @@ defmodule Web.Live.Settings.IdentityProviders.MicrosoftEntra.ShowTest do |> render_click() assert_redirected(lv, ~p"/#{account}/settings/identity_providers") - - assert Repo.get(Domain.Auth.Provider, provider.id).deleted_at + refute Repo.get(Domain.Auth.Provider, provider.id) end test "allows reconnecting identity providers", %{ diff --git a/elixir/apps/web/test/web/live/settings/identity_providers/okta/show_test.exs b/elixir/apps/web/test/web/live/settings/identity_providers/okta/show_test.exs index ce7dc30d4..4c266985a 100644 --- a/elixir/apps/web/test/web/live/settings/identity_providers/okta/show_test.exs +++ b/elixir/apps/web/test/web/live/settings/identity_providers/okta/show_test.exs @@ -264,7 +264,7 @@ defmodule Web.Live.Settings.IdentityProviders.Okta.ShowTest do assert_redirected(lv, ~p"/#{account}/settings/identity_providers") - assert Repo.get(Domain.Auth.Provider, provider.id).deleted_at + refute Repo.get(Domain.Auth.Provider, provider.id) end test "allows reconnecting identity providers", %{ diff --git a/elixir/apps/web/test/web/live/settings/identity_providers/openid_connect/show_test.exs b/elixir/apps/web/test/web/live/settings/identity_providers/openid_connect/show_test.exs index 31538a931..56f526beb 100644 --- a/elixir/apps/web/test/web/live/settings/identity_providers/openid_connect/show_test.exs +++ b/elixir/apps/web/test/web/live/settings/identity_providers/openid_connect/show_test.exs @@ -160,9 +160,9 @@ defmodule Web.Live.Settings.IdentityProviders.OpenIDConnect.ShowTest do |> element("button[type=submit]", "Delete Identity Provider") |> render_click() - assert_redirected(lv, ~p"/#{account}/settings/identity_providers/openid_connect/#{provider}") + assert_redirected(lv, ~p"/#{account}/settings/identity_providers") - assert Repo.get(Domain.Auth.Provider, provider.id).deleted_at + refute Repo.get(Domain.Auth.Provider, provider.id) end test "allows reconnecting identity providers", %{ diff --git a/elixir/apps/web/test/web/live/sites/edit_test.exs b/elixir/apps/web/test/web/live/sites/edit_test.exs index 30d6771cc..f55f6f00a 100644 --- a/elixir/apps/web/test/web/live/sites/edit_test.exs +++ b/elixir/apps/web/test/web/live/sites/edit_test.exs @@ -38,7 +38,7 @@ defmodule Web.Live.Sites.EditTest do group: group, conn: conn } do - group = Fixtures.Gateways.delete_group(group) + {:ok, group} = Fixtures.Gateways.delete_group(group) assert_raise Web.LiveErrors.NotFoundError, fn -> conn diff --git a/elixir/apps/web/test/web/live/sites/gateways/index_test.exs b/elixir/apps/web/test/web/live/sites/gateways/index_test.exs index 55f9a36ac..d0e747afd 100644 --- a/elixir/apps/web/test/web/live/sites/gateways/index_test.exs +++ b/elixir/apps/web/test/web/live/sites/gateways/index_test.exs @@ -83,6 +83,8 @@ defmodule Web.Live.Sites.Gateways.IndexTest do group: group, conn: conn } do + token = Fixtures.Gateways.create_token(account: account, group: group) + gateway = Fixtures.Gateways.create_gateway( account: account, @@ -96,7 +98,7 @@ defmodule Web.Live.Sites.Gateways.IndexTest do |> live(~p"/#{account}/sites/#{group}/gateways") :ok = Domain.Gateways.Presence.Group.subscribe(group.id) - :ok = Domain.Gateways.Presence.connect(gateway) + :ok = Domain.Gateways.Presence.connect(gateway, token.id) assert_receive %Phoenix.Socket.Broadcast{topic: "presences:group_gateways:" <> _} wait_for(fn -> diff --git a/elixir/apps/web/test/web/live/sites/index_test.exs b/elixir/apps/web/test/web/live/sites/index_test.exs index 0cad1cd00..72f967a97 100644 --- a/elixir/apps/web/test/web/live/sites/index_test.exs +++ b/elixir/apps/web/test/web/live/sites/index_test.exs @@ -89,6 +89,7 @@ defmodule Web.Live.Sites.IndexTest do conn: conn } do group = Fixtures.Gateways.create_group(account: account) + token = Fixtures.Gateways.create_token(account: account, group: group) gateway = Fixtures.Gateways.create_gateway(account: account, group: group) {:ok, lv, _html} = @@ -98,7 +99,7 @@ defmodule Web.Live.Sites.IndexTest do Domain.Config.put_env_override(:test_pid, self()) :ok = Domain.Gateways.Presence.Account.subscribe(account.id) - :ok = Domain.Gateways.Presence.connect(gateway) + :ok = Domain.Gateways.Presence.connect(gateway, token.id) assert_receive %Phoenix.Socket.Broadcast{topic: "presences:account_gateways:" <> _} assert_receive {:live_table_reloaded, "groups"}, 250 @@ -140,6 +141,7 @@ defmodule Web.Live.Sites.IndexTest do account = Fixtures.Accounts.update_account(account, features: %{internet_resource: true}) group = Fixtures.Gateways.create_internet_group(account: account) + token = Fixtures.Gateways.create_token(account: account, group: group) gateway = Fixtures.Gateways.create_gateway(account: account, group: group) Domain.Config.put_env_override(:test_pid, self()) :ok = Domain.Gateways.Presence.Account.subscribe(account.id) @@ -155,7 +157,7 @@ defmodule Web.Live.Sites.IndexTest do assert has_element?(lv, "#internet-site-banner a[href='/#{account.slug}/sites/#{group.id}']") - :ok = Domain.Gateways.Presence.connect(gateway) + :ok = Domain.Gateways.Presence.connect(gateway, token.id) assert_receive %Phoenix.Socket.Broadcast{topic: "presences:account_gateways:" <> _} assert_receive {:live_table_reloaded, "groups"}, 250 assert lv |> element("#internet-site-banner") |> render() =~ "Online" diff --git a/elixir/apps/web/test/web/live/sites/new_token_test.exs b/elixir/apps/web/test/web/live/sites/new_token_test.exs index fb92d6448..372ada7f4 100644 --- a/elixir/apps/web/test/web/live/sites/new_token_test.exs +++ b/elixir/apps/web/test/web/live/sites/new_token_test.exs @@ -40,7 +40,7 @@ defmodule Web.Live.Sites.NewTokenTest do context = Fixtures.Auth.build_context(type: :gateway_group) assert {:ok, group, token} = Domain.Gateways.authenticate(token, context) gateway = Fixtures.Gateways.create_gateway(account: account, group: group, token: token) - :ok = Domain.Gateways.Presence.connect(gateway) + :ok = Domain.Gateways.Presence.connect(gateway, token.id) assert_receive %Phoenix.Socket.Broadcast{topic: "presences:group_gateways:" <> _group_id} diff --git a/elixir/apps/web/test/web/live/sites/show_test.exs b/elixir/apps/web/test/web/live/sites/show_test.exs index 94da34451..fd250f86a 100644 --- a/elixir/apps/web/test/web/live/sites/show_test.exs +++ b/elixir/apps/web/test/web/live/sites/show_test.exs @@ -37,21 +37,19 @@ defmodule Web.Live.Sites.ShowTest do }}} end - test "renders deleted gateway group without action buttons", %{ + test "raises NotFoundError for deleted gateway group", %{ account: account, group: group, identity: identity, conn: conn } do - group = Fixtures.Gateways.delete_group(group) + {:ok, deleted_group} = Fixtures.Gateways.delete_group(group) - {:ok, _lv, html} = + assert_raise Web.LiveErrors.NotFoundError, fn -> conn |> authorize_conn(identity) - |> live(~p"/#{account}/sites/#{group}") - - assert html =~ "(deleted)" - assert active_buttons(html) == [] + |> live(~p"/#{account}/sites/#{deleted_group}") + end end test "renders breadcrumbs item", %{ @@ -142,7 +140,8 @@ defmodule Web.Live.Sites.ShowTest do gateway: gateway, conn: conn } do - :ok = Domain.Gateways.Presence.connect(gateway) + gateway_token = Fixtures.Gateways.create_token(group: gateway.group, account: account) + :ok = Domain.Gateways.Presence.connect(gateway, gateway_token.id) Fixtures.Gateways.create_gateway(account: account, group: group) {:ok, lv, _html} = @@ -180,7 +179,8 @@ defmodule Web.Live.Sites.ShowTest do |> live(~p"/#{account}/sites/#{group}") :ok = Domain.Gateways.Presence.Group.subscribe(group.id) - :ok = Domain.Gateways.Presence.connect(gateway) + gateway_token = Fixtures.Gateways.create_token(group: gateway.group, account: account) + :ok = Domain.Gateways.Presence.connect(gateway, gateway_token.id) assert_receive %Phoenix.Socket.Broadcast{topic: "presences:group_gateways:" <> _} wait_for(fn -> @@ -209,7 +209,7 @@ defmodule Web.Live.Sites.ShowTest do |> element("button[type=submit]", "Revoke All") |> render_click() =~ "1 token(s) were revoked." - assert Repo.get_by(Domain.Tokens.Token, gateway_group_id: group.id).deleted_at + refute Repo.get_by(Domain.Tokens.Token, gateway_group_id: group.id) end test "renders resources table", %{ @@ -326,7 +326,7 @@ defmodule Web.Live.Sites.ShowTest do assert_redirected(lv, ~p"/#{account}/sites") - assert Repo.get(Domain.Gateways.Group, group.id).deleted_at + refute Repo.get(Domain.Gateways.Group, group.id) end end @@ -377,7 +377,8 @@ defmodule Web.Live.Sites.ShowTest do gateway: gateway, conn: conn } do - :ok = Domain.Gateways.Presence.connect(gateway) + gateway_token = Fixtures.Gateways.create_token(group: gateway.group, account: account) + :ok = Domain.Gateways.Presence.connect(gateway, gateway_token.id) Fixtures.Gateways.create_gateway(account: account, group: group) {:ok, lv, _html} = @@ -414,7 +415,8 @@ defmodule Web.Live.Sites.ShowTest do |> live(~p"/#{account}/sites/#{group}") :ok = Domain.Gateways.Presence.Group.subscribe(group.id) - :ok = Domain.Gateways.Presence.connect(gateway) + gateway_token = Fixtures.Gateways.create_token(group: gateway.group, account: account) + :ok = Domain.Gateways.Presence.connect(gateway, gateway_token.id) assert_receive %Phoenix.Socket.Broadcast{topic: "presences:group_gateways:" <> _} wait_for(fn -> @@ -443,7 +445,7 @@ defmodule Web.Live.Sites.ShowTest do |> element("button[type=submit]", "Revoke All") |> render_click() =~ "1 token(s) were revoked." - assert Repo.get_by(Domain.Tokens.Token, gateway_group_id: group.id).deleted_at + refute Repo.get_by(Domain.Tokens.Token, gateway_group_id: group.id) end test "renders resources table", %{ @@ -560,12 +562,12 @@ defmodule Web.Live.Sites.ShowTest do assert_redirected(lv, ~p"/#{account}/sites") - assert Repo.get(Domain.Gateways.Group, group.id).deleted_at + refute Repo.get(Domain.Gateways.Group, group.id) end end describe "for internet sites" do - setup %{account: account} do + setup %{account: account, subject: subject} do {:ok, group} = Domain.Gateways.create_internet_group(account) gateway = Fixtures.Gateways.create_gateway(account: account, group: group) gateway = Repo.preload(gateway, :group) @@ -575,7 +577,8 @@ defmodule Web.Live.Sites.ShowTest do %{ group: group, gateway: gateway, - resource: resource + resource: resource, + subject: subject } end @@ -600,7 +603,8 @@ defmodule Web.Live.Sites.ShowTest do gateway: gateway, conn: conn } do - :ok = Domain.Gateways.Presence.connect(gateway) + gateway_token = Fixtures.Gateways.create_token(group: gateway.group, account: account) + :ok = Domain.Gateways.Presence.connect(gateway, gateway_token.id) Fixtures.Gateways.create_gateway(account: account, group: group) {:ok, lv, _html} = @@ -637,7 +641,8 @@ defmodule Web.Live.Sites.ShowTest do |> live(~p"/#{account}/sites/#{group}") :ok = Domain.Gateways.Presence.Group.subscribe(group.id) - :ok = Domain.Gateways.Presence.connect(gateway) + gateway_token = Fixtures.Gateways.create_token(group: gateway.group, account: account) + :ok = Domain.Gateways.Presence.connect(gateway, gateway_token.id) assert_receive %Phoenix.Socket.Broadcast{topic: "presences:group_gateways:" <> _} wait_for(fn -> @@ -666,7 +671,7 @@ defmodule Web.Live.Sites.ShowTest do |> element("button[type=submit]", "Revoke All") |> render_click() =~ "1 token(s) were revoked." - assert Repo.get_by(Domain.Tokens.Token, gateway_group_id: group.id).deleted_at + refute Repo.get_by(Domain.Tokens.Token, gateway_group_id: group.id) end test "does not render resources table", %{