refactor(portal): hard delete data (#9694)

This commit is contained in:
Brian Manifold
2025-08-29 15:13:44 -07:00
committed by GitHub
parent 516be7417e
commit 6bd19ee9b0
154 changed files with 2251 additions and 1566 deletions

View File

@@ -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)

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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)

View File

@@ -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,

View File

@@ -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

View File

@@ -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,

View File

@@ -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

View File

@@ -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)

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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",
%{

View File

@@ -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")

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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()

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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)

View File

@@ -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(

View File

@@ -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

View File

@@ -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

View File

@@ -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
)

View File

@@ -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

View File

@@ -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

View File

@@ -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()

View File

@@ -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)

View File

@@ -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,

View File

@@ -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()

View File

@@ -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())

View File

@@ -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))

View File

@@ -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,

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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)

View File

@@ -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],

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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())

View File

@@ -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

View File

@@ -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

View File

@@ -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()

View File

@@ -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

View File

@@ -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

View File

@@ -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()

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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),

View File

@@ -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

View File

@@ -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

View File

@@ -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()

View File

@@ -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

View File

@@ -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

View File

@@ -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()

View File

@@ -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

View File

@@ -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} =

View File

@@ -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()) ->

View File

@@ -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

View File

@@ -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()

View File

@@ -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

View File

@@ -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()

View File

@@ -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

View File

@@ -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

View File

@@ -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()

View File

@@ -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)

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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)}",

View File

@@ -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

View File

@@ -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", %{

View File

@@ -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", %{

View File

@@ -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", %{

View File

@@ -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", %{

View File

@@ -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", %{

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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:" <> _}

View File

@@ -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

View File

@@ -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}

View File

@@ -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

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