mirror of
https://github.com/outbackdingo/firezone.git
synced 2026-01-27 10:18:54 +00:00
Rename Devices to Clients in Elixir app (#2008)
Renaming it back to clients to reflect service accounts and headless clients use cases in the terminology. Such a rename will be very painful on live data so better if we do it early on. --------- Co-authored-by: Jamil Bou Kheir <jamilbk@users.noreply.github.com>
This commit is contained in:
@@ -94,7 +94,7 @@ Now you can verify that it's working by connecting to a websocket:
|
||||
|
||||
</details>
|
||||
<details>
|
||||
<summary>Device</summary>
|
||||
<summary>Client</summary>
|
||||
|
||||
```elixir
|
||||
❯ export CLIENT_TOKEN_FROM_SEEDS="SFMyNTY.g2gDaANkAAhpZGVudGl0eW0AAAAkN2RhN2QxY2QtMTExYy00NGE3LWI1YWMtNDAyN2I5ZDIzMGU1bQAAACDZI3ehOZSu3JOSMREkvzrtKjs8jkrW6fpbVw9opDYmi24GANjCD-qIAWIB4TOA.XhoLEDjIzuv1SXEVUV6lfIHW12n5-J5aBDUKCl8ovMk"
|
||||
@@ -102,25 +102,25 @@ Now you can verify that it's working by connecting to a websocket:
|
||||
# Panel will only accept token if it's coming with this User-Agent header and from IP 172.28.0.1
|
||||
❯ export CLIENT_USER_AGENT="iOS/12.5 (iPhone) connlib/0.7.412"
|
||||
|
||||
❯ websocat --header="User-Agent: ${CLIENT_USER_AGENT}" "ws://127.0.0.1:8081/device/websocket?token=${CLIENT_TOKEN_FROM_SEEDS}&external_id=thisisrandomandpersistent&name_suffix=kkX1&public_key=kceI60D6PrwOIiGoVz6hD7VYCgD1H57IVQlPJTTieUE="
|
||||
❯ websocat --header="User-Agent: ${CLIENT_USER_AGENT}" "ws://127.0.0.1:8081/client/websocket?token=${CLIENT_TOKEN_FROM_SEEDS}&external_id=thisisrandomandpersistent&name_suffix=kkX1&public_key=kceI60D6PrwOIiGoVz6hD7VYCgD1H57IVQlPJTTieUE="
|
||||
|
||||
# Here is what you will see in docker logs firezone-api-1
|
||||
# firezone-api-1 | {"domain":["elixir"],"erl_level":"info","logging.googleapis.com/sourceLocation":{"file":"lib/phoenix/logger.ex","line":306,"function":"Elixir.Phoenix.Logger.phoenix_socket_connected/4"},"message":"CONNECTED TO API.Device.Socket in 83ms\n Transport: :websocket\n Serializer: Phoenix.Socket.V1.JSONSerializer\n Parameters: %{\"external_id\" => \"thisisrandomandpersistent\", \"name_suffix\" => \"kkX1\", \"public_key\" => \"[FILTERED]\", \"token\" => \"[FILTERED]\"}","severity":"INFO","time":"2023-06-23T21:01:49.566Z"}
|
||||
# firezone-api-1 | {"domain":["elixir"],"erl_level":"info","logging.googleapis.com/sourceLocation":{"file":"lib/phoenix/logger.ex","line":306,"function":"Elixir.Phoenix.Logger.phoenix_socket_connected/4"},"message":"CONNECTED TO API.Client.Socket in 83ms\n Transport: :websocket\n Serializer: Phoenix.Socket.V1.JSONSerializer\n Parameters: %{\"external_id\" => \"thisisrandomandpersistent\", \"name_suffix\" => \"kkX1\", \"public_key\" => \"[FILTERED]\", \"token\" => \"[FILTERED]\"}","severity":"INFO","time":"2023-06-23T21:01:49.566Z"}
|
||||
|
||||
# After this you need to join the `device` topic and pass a `stamp_secret` in the payload.
|
||||
# After this you need to join the `client` topic and pass a `stamp_secret` in the payload.
|
||||
# For details on this structure see https://hexdocs.pm/phoenix/Phoenix.Socket.Message.html
|
||||
❯ {"event":"phx_join","topic":"device","payload":{},"ref":"unique_string_ref","join_ref":"unique_join_ref"}
|
||||
❯ {"event":"phx_join","topic":"client","payload":{},"ref":"unique_string_ref","join_ref":"unique_join_ref"}
|
||||
|
||||
{"ref":"unique_string_ref","topic":"device","event":"phx_reply","payload":{"status":"ok","response":{}}}
|
||||
{"ref":null,"topic":"device","event":"init","payload":{"interface":{"ipv6":"fd00:2021:1111::11:f4bd","upstream_dns":[],"ipv4":"100.71.71.245"},"resources":[{"id":"4429d3aa-53ea-4c03-9435-4dee2899672b","name":"172.20.0.1/16","type":"cidr","address":"172.20.0.0/16"},{"id":"85a1cffc-70d3-46dd-aa6b-776192af7b06","name":"gitlab.mycorp.com","type":"dns","address":"gitlab.mycorp.com","ipv6":"fd00:2021:1111::5:b370","ipv4":"100.85.109.146"}]}}
|
||||
{"ref":"unique_string_ref","topic":"client","event":"phx_reply","payload":{"status":"ok","response":{}}}
|
||||
{"ref":null,"topic":"client","event":"init","payload":{"interface":{"ipv6":"fd00:2021:1111::11:f4bd","upstream_dns":[],"ipv4":"100.71.71.245"},"resources":[{"id":"4429d3aa-53ea-4c03-9435-4dee2899672b","name":"172.20.0.1/16","type":"cidr","address":"172.20.0.0/16"},{"id":"85a1cffc-70d3-46dd-aa6b-776192af7b06","name":"gitlab.mycorp.com","type":"dns","address":"gitlab.mycorp.com","ipv6":"fd00:2021:1111::5:b370","ipv4":"100.85.109.146"}]}}
|
||||
|
||||
# List online relays for a Resource
|
||||
❯ {"event":"list_relays","topic":"device","payload":{"resource_id":"4429d3aa-53ea-4c03-9435-4dee2899672b"},"ref":"unique_list_relays_ref"}
|
||||
❯ {"event":"list_relays","topic":"client","payload":{"resource_id":"4429d3aa-53ea-4c03-9435-4dee2899672b"},"ref":"unique_list_relays_ref"}
|
||||
|
||||
{"ref":"unique_list_relays_ref","topic":"device","event":"phx_reply","payload":{"status":"ok","response":{"relays":[{"type":"stun","uri":"stun:172.28.0.101:3478"},{"type":"turn","username":"1719090081:UVxHhieTJWaD8_Sg","password":"Ml65XDZyYpuBiEIvk/q0Zy6EEJ1ZwGa4pWztXFP+tOo","uri":"turn:172.28.0.101:3478","expires_at":1719090081}],"resource_id":"4429d3aa-53ea-4c03-9435-4dee2899672b"}}}
|
||||
{"ref":"unique_list_relays_ref","topic":"client","event":"phx_reply","payload":{"status":"ok","response":{"relays":[{"type":"stun","uri":"stun:172.28.0.101:3478"},{"type":"turn","username":"1719090081:UVxHhieTJWaD8_Sg","password":"Ml65XDZyYpuBiEIvk/q0Zy6EEJ1ZwGa4pWztXFP+tOo","uri":"turn:172.28.0.101:3478","expires_at":1719090081}],"resource_id":"4429d3aa-53ea-4c03-9435-4dee2899672b"}}}
|
||||
|
||||
# Initiate connection to a resource
|
||||
❯ {"event":"request_connection","topic":"device","payload":{"resource_id":"4429d3aa-53ea-4c03-9435-4dee2899672b","device_rtc_session_description":"RTC_SD","device_preshared_key":"+HapiGI5UdeRjKuKTwk4ZPPYpCnlXHvvqebcIevL+2A="},"ref":"unique_request_connection_ref"}
|
||||
❯ {"event":"request_connection","topic":"client","payload":{"resource_id":"4429d3aa-53ea-4c03-9435-4dee2899672b","client_rtc_session_description":"RTC_SD","client_preshared_key":"+HapiGI5UdeRjKuKTwk4ZPPYpCnlXHvvqebcIevL+2A="},"ref":"unique_request_connection_ref"}
|
||||
|
||||
```
|
||||
|
||||
@@ -212,7 +212,7 @@ identity = Domain.Repo.get_by(Domain.Auth.Identity, provider_id: provider.id, pr
|
||||
subject = Domain.Auth.build_subject(identity, nil, user_agent, remote_ip)
|
||||
```
|
||||
|
||||
Listing connected gateways, relays, devices for an account:
|
||||
Listing connected gateways, relays, clients for an account:
|
||||
|
||||
```elixir
|
||||
account_id = "c89bcc8c-9392-4dae-a40d-888aef6d28e0"
|
||||
@@ -220,7 +220,7 @@ account_id = "c89bcc8c-9392-4dae-a40d-888aef6d28e0"
|
||||
%{
|
||||
gateways: Domain.Gateways.Presence.list("gateways:#{account_id}"),
|
||||
relays: Domain.Relays.Presence.list("relays:#{account_id}"),
|
||||
devices: Domain.Devices.Presence.list("devices:#{account_id}"),
|
||||
clients: Domain.Clients.Presence.list("clients:#{account_id}"),
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
defmodule API.Device.Channel do
|
||||
defmodule API.Client.Channel do
|
||||
use API, :channel
|
||||
alias API.Device.Views
|
||||
alias Domain.{Devices, Resources, Gateways, Relays}
|
||||
alias API.Client.Views
|
||||
alias Domain.{Clients, Resources, Gateways, Relays}
|
||||
require Logger
|
||||
|
||||
@impl true
|
||||
def join("device", _payload, socket) do
|
||||
def join("client", _payload, socket) do
|
||||
expires_in =
|
||||
DateTime.diff(socket.assigns.subject.expires_at, DateTime.utc_now(), :millisecond)
|
||||
|
||||
@@ -20,15 +20,15 @@ defmodule API.Device.Channel do
|
||||
|
||||
@impl true
|
||||
def handle_info(:after_join, socket) do
|
||||
API.Endpoint.subscribe("device:#{socket.assigns.device.id}")
|
||||
:ok = Devices.connect_device(socket.assigns.device)
|
||||
API.Endpoint.subscribe("client:#{socket.assigns.client.id}")
|
||||
:ok = Clients.connect_client(socket.assigns.client)
|
||||
|
||||
{:ok, resources} = Domain.Resources.list_resources(socket.assigns.subject)
|
||||
|
||||
:ok =
|
||||
push(socket, "init", %{
|
||||
resources: Views.Resource.render_many(resources),
|
||||
interface: Views.Interface.render(socket.assigns.device)
|
||||
interface: Views.Interface.render(socket.assigns.client)
|
||||
})
|
||||
|
||||
{:noreply, socket}
|
||||
@@ -40,7 +40,7 @@ defmodule API.Device.Channel do
|
||||
end
|
||||
|
||||
# This message is sent by the gateway when it is ready
|
||||
# to accept the connection from the device
|
||||
# to accept the connection from the client
|
||||
def handle_info(
|
||||
{:connect, socket_ref, resource_id, gateway_public_key, rtc_session_description},
|
||||
socket
|
||||
@@ -108,7 +108,7 @@ defmodule API.Device.Channel do
|
||||
end
|
||||
end
|
||||
|
||||
# This message is sent by the device when it already has connection to a gateway,
|
||||
# This message is sent by the client when it already has connection to a gateway,
|
||||
# but wants to connect to a new resource
|
||||
def handle_in(
|
||||
"reuse_connection",
|
||||
@@ -127,7 +127,7 @@ defmodule API.Device.Channel do
|
||||
gateway,
|
||||
{:allow_access,
|
||||
%{
|
||||
device_id: socket.assigns.device.id,
|
||||
client_id: socket.assigns.client.id,
|
||||
resource_id: resource.id,
|
||||
authorization_expires_at: socket.assigns.subject.expires_at
|
||||
}}
|
||||
@@ -140,14 +140,14 @@ defmodule API.Device.Channel do
|
||||
end
|
||||
end
|
||||
|
||||
# This message is sent by the device when it wants to connect to a new gateway
|
||||
# This message is sent by the client when it wants to connect to a new gateway
|
||||
def handle_in(
|
||||
"request_connection",
|
||||
%{
|
||||
"gateway_id" => gateway_id,
|
||||
"resource_id" => resource_id,
|
||||
"device_rtc_session_description" => device_rtc_session_description,
|
||||
"device_preshared_key" => preshared_key
|
||||
"client_rtc_session_description" => client_rtc_session_description,
|
||||
"client_preshared_key" => preshared_key
|
||||
},
|
||||
socket
|
||||
) do
|
||||
@@ -160,11 +160,11 @@ defmodule API.Device.Channel do
|
||||
gateway,
|
||||
{:request_connection, {self(), socket_ref(socket)},
|
||||
%{
|
||||
device_id: socket.assigns.device.id,
|
||||
client_id: socket.assigns.client.id,
|
||||
resource_id: resource.id,
|
||||
authorization_expires_at: socket.assigns.subject.expires_at,
|
||||
device_rtc_session_description: device_rtc_session_description,
|
||||
device_preshared_key: preshared_key
|
||||
client_rtc_session_description: client_rtc_session_description,
|
||||
client_preshared_key: preshared_key
|
||||
}}
|
||||
)
|
||||
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
defmodule API.Device.Socket do
|
||||
defmodule API.Client.Socket do
|
||||
use Phoenix.Socket
|
||||
alias Domain.{Auth, Devices}
|
||||
alias Domain.{Auth, Clients}
|
||||
require Logger
|
||||
|
||||
## Channels
|
||||
|
||||
channel "device", API.Device.Channel
|
||||
channel "client", API.Client.Channel
|
||||
|
||||
## Authentication
|
||||
|
||||
@@ -20,11 +20,11 @@ defmodule API.Device.Socket do
|
||||
real_ip = API.Sockets.real_ip(x_headers, peer_data)
|
||||
|
||||
with {:ok, subject} <- Auth.sign_in(token, user_agent, real_ip),
|
||||
{:ok, device} <- Devices.upsert_device(attrs, subject) do
|
||||
{:ok, client} <- Clients.upsert_client(attrs, subject) do
|
||||
socket =
|
||||
socket
|
||||
|> assign(:subject, subject)
|
||||
|> assign(:device, device)
|
||||
|> assign(:client, client)
|
||||
|
||||
{:ok, socket}
|
||||
else
|
||||
@@ -32,7 +32,7 @@ defmodule API.Device.Socket do
|
||||
{:error, :invalid_token}
|
||||
|
||||
{:error, reason} ->
|
||||
Logger.debug("Error connecting device websocket: #{inspect(reason)}")
|
||||
Logger.debug("Error connecting client websocket: #{inspect(reason)}")
|
||||
{:error, reason}
|
||||
end
|
||||
end
|
||||
@@ -42,5 +42,5 @@ defmodule API.Device.Socket do
|
||||
end
|
||||
|
||||
@impl true
|
||||
def id(socket), do: "device:#{socket.assigns.device.id}"
|
||||
def id(socket), do: "client:#{socket.assigns.client.id}"
|
||||
end
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
defmodule API.Device.Views.Interface do
|
||||
alias Domain.Devices
|
||||
defmodule API.Client.Views.Interface do
|
||||
alias Domain.Clients
|
||||
|
||||
def render(%Devices.Device{} = device) do
|
||||
def render(%Clients.Client{} = client) do
|
||||
upstream_dns =
|
||||
Devices.fetch_device_config!(device)
|
||||
Clients.fetch_client_config!(client)
|
||||
|> Keyword.fetch!(:upstream_dns)
|
||||
|
||||
%{
|
||||
upstream_dns: upstream_dns,
|
||||
ipv4: device.ipv4,
|
||||
ipv6: device.ipv6
|
||||
ipv4: client.ipv4,
|
||||
ipv6: client.ipv6
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
defmodule API.Device.Views.Relay do
|
||||
defmodule API.Client.Views.Relay do
|
||||
alias Domain.Relays
|
||||
|
||||
def render_many(relays, expires_at) do
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
defmodule API.Device.Views.Resource do
|
||||
defmodule API.Client.Views.Resource do
|
||||
alias Domain.Resources
|
||||
|
||||
def render_many(resources) do
|
||||
|
||||
@@ -21,7 +21,7 @@ defmodule API.Endpoint do
|
||||
plug Plug.Telemetry, event_prefix: [:phoenix, :endpoint]
|
||||
|
||||
socket "/gateway", API.Gateway.Socket, API.Sockets.options()
|
||||
socket "/device", API.Device.Socket, API.Sockets.options()
|
||||
socket "/client", API.Client.Socket, API.Sockets.options()
|
||||
socket "/relay", API.Relay.Socket, API.Sockets.options()
|
||||
|
||||
plug :healthz
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
defmodule API.Gateway.Channel do
|
||||
use API, :channel
|
||||
alias API.Gateway.Views
|
||||
alias Domain.{Devices, Resources, Relays, Gateways}
|
||||
alias Domain.{Clients, Resources, Relays, Gateways}
|
||||
require Logger
|
||||
|
||||
def broadcast(%Gateways.Gateway{} = gateway, payload) do
|
||||
@@ -33,7 +33,7 @@ defmodule API.Gateway.Channel do
|
||||
|
||||
def handle_info({:allow_access, attrs}, socket) do
|
||||
%{
|
||||
device_id: device_id,
|
||||
client_id: client_id,
|
||||
resource_id: resource_id,
|
||||
authorization_expires_at: authorization_expires_at
|
||||
} = attrs
|
||||
@@ -41,7 +41,7 @@ defmodule API.Gateway.Channel do
|
||||
resource = Resources.fetch_resource_by_id!(resource_id)
|
||||
|
||||
push(socket, "allow_access", %{
|
||||
device_id: device_id,
|
||||
client_id: client_id,
|
||||
resource: Views.Resource.render(resource),
|
||||
expires_at: DateTime.to_unix(authorization_expires_at, :second)
|
||||
})
|
||||
@@ -51,19 +51,19 @@ defmodule API.Gateway.Channel do
|
||||
|
||||
def handle_info({:request_connection, {channel_pid, socket_ref}, attrs}, socket) do
|
||||
%{
|
||||
device_id: device_id,
|
||||
client_id: client_id,
|
||||
resource_id: resource_id,
|
||||
authorization_expires_at: authorization_expires_at,
|
||||
device_rtc_session_description: rtc_session_description,
|
||||
device_preshared_key: preshared_key
|
||||
client_rtc_session_description: rtc_session_description,
|
||||
client_preshared_key: preshared_key
|
||||
} = attrs
|
||||
|
||||
Logger.debug("Gateway received connection request message",
|
||||
device_id: device_id,
|
||||
client_id: client_id,
|
||||
resource_id: resource_id
|
||||
)
|
||||
|
||||
device = Devices.fetch_device_by_id!(device_id, preload: [:actor])
|
||||
client = Clients.fetch_client_by_id!(client_id, preload: [:actor])
|
||||
resource = Resources.fetch_resource_by_id!(resource_id)
|
||||
{:ok, relays} = Relays.list_connected_relays_for_resource(resource)
|
||||
|
||||
@@ -71,15 +71,15 @@ defmodule API.Gateway.Channel do
|
||||
|
||||
push(socket, "request_connection", %{
|
||||
ref: ref,
|
||||
actor: Views.Actor.render(device.actor),
|
||||
actor: Views.Actor.render(client.actor),
|
||||
relays: Views.Relay.render_many(relays, authorization_expires_at),
|
||||
resource: Views.Resource.render(resource),
|
||||
device: Views.Device.render(device, rtc_session_description, preshared_key),
|
||||
client: Views.Client.render(client, rtc_session_description, preshared_key),
|
||||
expires_at: DateTime.to_unix(authorization_expires_at, :second)
|
||||
})
|
||||
|
||||
Logger.debug("Awaiting gateway connection_ready message",
|
||||
device_id: device_id,
|
||||
client_id: client_id,
|
||||
resource_id: resource_id,
|
||||
ref: ref
|
||||
)
|
||||
@@ -108,7 +108,7 @@ defmodule API.Gateway.Channel do
|
||||
rtc_session_description}
|
||||
)
|
||||
|
||||
Logger.debug("Gateway replied to the Device with :connect message",
|
||||
Logger.debug("Gateway replied to the Client with :connect message",
|
||||
resource_id: resource_id,
|
||||
channel_pid: inspect(channel_pid),
|
||||
ref: ref
|
||||
@@ -123,7 +123,7 @@ defmodule API.Gateway.Channel do
|
||||
# "ended_at" => ended_at,
|
||||
# "metrics" => [
|
||||
# %{
|
||||
# "device_id" => device_id,
|
||||
# "client_id" => client_id,
|
||||
# "resource_id" => resource_id,
|
||||
# "rx_bytes" => 0,
|
||||
# "tx_packets" => 0
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
defmodule API.Gateway.Views.Device do
|
||||
alias Domain.Devices
|
||||
defmodule API.Gateway.Views.Client do
|
||||
alias Domain.Clients
|
||||
|
||||
def render(%Devices.Device{} = device, device_rtc_session_description, preshared_key) do
|
||||
def render(%Clients.Client{} = client, client_rtc_session_description, preshared_key) do
|
||||
%{
|
||||
id: device.id,
|
||||
rtc_session_description: device_rtc_session_description,
|
||||
id: client.id,
|
||||
rtc_session_description: client_rtc_session_description,
|
||||
peer: %{
|
||||
persistent_keepalive: 25,
|
||||
public_key: device.public_key,
|
||||
public_key: client.public_key,
|
||||
preshared_key: preshared_key,
|
||||
ipv4: device.ipv4,
|
||||
ipv6: device.ipv6
|
||||
ipv4: client.ipv4,
|
||||
ipv6: client.ipv6
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
defmodule API.Gateway.Views.Relay do
|
||||
def render_many(relays, expires_at) do
|
||||
Enum.flat_map(relays, &API.Device.Views.Relay.render(&1, expires_at))
|
||||
Enum.flat_map(relays, &API.Client.Views.Relay.render(&1, expires_at))
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
defmodule API.Device.ChannelTest do
|
||||
defmodule API.Client.ChannelTest do
|
||||
use API.ChannelCase
|
||||
|
||||
setup do
|
||||
account = Fixtures.Accounts.create_account()
|
||||
Fixtures.Config.upsert_configuration(account: account, devices_upstream_dns: ["1.1.1.1"])
|
||||
Fixtures.Config.upsert_configuration(account: account, clients_upstream_dns: ["1.1.1.1"])
|
||||
actor = Fixtures.Actors.create_actor(type: :account_admin_user, account: account)
|
||||
identity = Fixtures.Auth.create_identity(actor: actor, account: account)
|
||||
subject = Fixtures.Auth.create_subject(identity: identity)
|
||||
device = Fixtures.Devices.create_device(subject: subject)
|
||||
client = Fixtures.Clients.create_client(subject: subject)
|
||||
gateway = Fixtures.Gateways.create_gateway(account: account)
|
||||
|
||||
dns_resource =
|
||||
@@ -29,19 +29,19 @@ defmodule API.Device.ChannelTest do
|
||||
subject = %{subject | expires_at: expires_at}
|
||||
|
||||
{:ok, _reply, socket} =
|
||||
API.Device.Socket
|
||||
|> socket("device:#{device.id}", %{
|
||||
device: device,
|
||||
API.Client.Socket
|
||||
|> socket("client:#{client.id}", %{
|
||||
client: client,
|
||||
subject: subject
|
||||
})
|
||||
|> subscribe_and_join(API.Device.Channel, "device")
|
||||
|> subscribe_and_join(API.Client.Channel, "client")
|
||||
|
||||
%{
|
||||
account: account,
|
||||
actor: actor,
|
||||
identity: identity,
|
||||
subject: subject,
|
||||
device: device,
|
||||
client: client,
|
||||
gateway: gateway,
|
||||
dns_resource: dns_resource,
|
||||
cidr_resource: cidr_resource,
|
||||
@@ -50,14 +50,14 @@ defmodule API.Device.ChannelTest do
|
||||
end
|
||||
|
||||
describe "join/3" do
|
||||
test "tracks presence after join", %{account: account, device: device} do
|
||||
presence = Domain.Devices.Presence.list("devices:#{account.id}")
|
||||
test "tracks presence after join", %{account: account, client: client} do
|
||||
presence = Domain.Clients.Presence.list("clients:#{account.id}")
|
||||
|
||||
assert %{metas: [%{online_at: online_at, phx_ref: _ref}]} = Map.fetch!(presence, device.id)
|
||||
assert %{metas: [%{online_at: online_at, phx_ref: _ref}]} = Map.fetch!(presence, client.id)
|
||||
assert is_number(online_at)
|
||||
end
|
||||
|
||||
test "expires the channel when token is expired", %{device: device, subject: subject} do
|
||||
test "expires the channel when token is expired", %{client: client, subject: subject} do
|
||||
expires_at = DateTime.utc_now() |> DateTime.add(25, :millisecond)
|
||||
subject = %{subject | expires_at: expires_at}
|
||||
|
||||
@@ -66,18 +66,18 @@ defmodule API.Device.ChannelTest do
|
||||
Process.flag(:trap_exit, true)
|
||||
|
||||
{:ok, _reply, _socket} =
|
||||
API.Device.Socket
|
||||
|> socket("device:#{device.id}", %{
|
||||
device: device,
|
||||
API.Client.Socket
|
||||
|> socket("client:#{client.id}", %{
|
||||
client: client,
|
||||
subject: subject
|
||||
})
|
||||
|> subscribe_and_join(API.Device.Channel, "device")
|
||||
|> subscribe_and_join(API.Client.Channel, "client")
|
||||
|
||||
assert_push "token_expired", %{}, 250
|
||||
end
|
||||
|
||||
test "sends list of resources after join", %{
|
||||
device: device,
|
||||
client: client,
|
||||
dns_resource: dns_resource,
|
||||
cidr_resource: cidr_resource
|
||||
} do
|
||||
@@ -100,8 +100,8 @@ defmodule API.Device.ChannelTest do
|
||||
} in resources
|
||||
|
||||
assert interface == %{
|
||||
ipv4: device.ipv4,
|
||||
ipv6: device.ipv6,
|
||||
ipv4: client.ipv4,
|
||||
ipv6: client.ipv6,
|
||||
upstream_dns: [
|
||||
%Postgrex.INET{address: {1, 1, 1, 1}}
|
||||
]
|
||||
@@ -274,11 +274,11 @@ defmodule API.Device.ChannelTest do
|
||||
test "broadcasts allow_access to the gateways and then returns connect message", %{
|
||||
dns_resource: resource,
|
||||
gateway: gateway,
|
||||
device: device,
|
||||
client: client,
|
||||
socket: socket
|
||||
} do
|
||||
resource_id = resource.id
|
||||
device_id = device.id
|
||||
client_id = client.id
|
||||
|
||||
:ok = Domain.Gateways.connect_gateway(gateway)
|
||||
Phoenix.PubSub.subscribe(Domain.PubSub, API.Gateway.Socket.id(gateway))
|
||||
@@ -294,7 +294,7 @@ defmodule API.Device.ChannelTest do
|
||||
|
||||
assert %{
|
||||
resource_id: ^resource_id,
|
||||
device_id: ^device_id,
|
||||
client_id: ^client_id,
|
||||
authorization_expires_at: authorization_expires_at
|
||||
} = payload
|
||||
|
||||
@@ -307,8 +307,8 @@ defmodule API.Device.ChannelTest do
|
||||
attrs = %{
|
||||
"resource_id" => Ecto.UUID.generate(),
|
||||
"gateway_id" => gateway.id,
|
||||
"device_rtc_session_description" => "RTC_SD",
|
||||
"device_preshared_key" => "PSK"
|
||||
"client_rtc_session_description" => "RTC_SD",
|
||||
"client_preshared_key" => "PSK"
|
||||
}
|
||||
|
||||
ref = push(socket, "request_connection", attrs)
|
||||
@@ -319,8 +319,8 @@ defmodule API.Device.ChannelTest do
|
||||
attrs = %{
|
||||
"resource_id" => resource.id,
|
||||
"gateway_id" => Ecto.UUID.generate(),
|
||||
"device_rtc_session_description" => "RTC_SD",
|
||||
"device_preshared_key" => "PSK"
|
||||
"client_rtc_session_description" => "RTC_SD",
|
||||
"client_preshared_key" => "PSK"
|
||||
}
|
||||
|
||||
ref = push(socket, "request_connection", attrs)
|
||||
@@ -338,8 +338,8 @@ defmodule API.Device.ChannelTest do
|
||||
attrs = %{
|
||||
"resource_id" => resource.id,
|
||||
"gateway_id" => gateway.id,
|
||||
"device_rtc_session_description" => "RTC_SD",
|
||||
"device_preshared_key" => "PSK"
|
||||
"client_rtc_session_description" => "RTC_SD",
|
||||
"client_preshared_key" => "PSK"
|
||||
}
|
||||
|
||||
ref = push(socket, "request_connection", attrs)
|
||||
@@ -354,8 +354,8 @@ defmodule API.Device.ChannelTest do
|
||||
attrs = %{
|
||||
"resource_id" => resource.id,
|
||||
"gateway_id" => gateway.id,
|
||||
"device_rtc_session_description" => "RTC_SD",
|
||||
"device_preshared_key" => "PSK"
|
||||
"client_rtc_session_description" => "RTC_SD",
|
||||
"client_preshared_key" => "PSK"
|
||||
}
|
||||
|
||||
ref = push(socket, "request_connection", attrs)
|
||||
@@ -365,12 +365,12 @@ defmodule API.Device.ChannelTest do
|
||||
test "broadcasts request_connection to the gateways and then returns connect message", %{
|
||||
dns_resource: resource,
|
||||
gateway: gateway,
|
||||
device: device,
|
||||
client: client,
|
||||
socket: socket
|
||||
} do
|
||||
public_key = gateway.public_key
|
||||
resource_id = resource.id
|
||||
device_id = device.id
|
||||
client_id = client.id
|
||||
|
||||
:ok = Domain.Gateways.connect_gateway(gateway)
|
||||
Phoenix.PubSub.subscribe(Domain.PubSub, API.Gateway.Socket.id(gateway))
|
||||
@@ -378,8 +378,8 @@ defmodule API.Device.ChannelTest do
|
||||
attrs = %{
|
||||
"resource_id" => resource.id,
|
||||
"gateway_id" => gateway.id,
|
||||
"device_rtc_session_description" => "RTC_SD",
|
||||
"device_preshared_key" => "PSK"
|
||||
"client_rtc_session_description" => "RTC_SD",
|
||||
"client_preshared_key" => "PSK"
|
||||
}
|
||||
|
||||
ref = push(socket, "request_connection", attrs)
|
||||
@@ -388,9 +388,9 @@ defmodule API.Device.ChannelTest do
|
||||
|
||||
assert %{
|
||||
resource_id: ^resource_id,
|
||||
device_id: ^device_id,
|
||||
device_preshared_key: "PSK",
|
||||
device_rtc_session_description: "RTC_SD",
|
||||
client_id: ^client_id,
|
||||
client_preshared_key: "PSK",
|
||||
client_rtc_session_description: "RTC_SD",
|
||||
authorization_expires_at: authorization_expires_at
|
||||
} = payload
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
defmodule API.Device.SocketTest do
|
||||
defmodule API.Client.SocketTest do
|
||||
use API.ChannelCase, async: true
|
||||
import API.Device.Socket, only: [id: 1]
|
||||
alias API.Device.Socket
|
||||
import API.Client.Socket, only: [id: 1]
|
||||
alias API.Client.Socket
|
||||
alias Domain.Auth
|
||||
|
||||
@connect_info %{
|
||||
@@ -20,41 +20,41 @@ defmodule API.Device.SocketTest do
|
||||
assert connect(Socket, attrs, connect_info: @connect_info) == {:error, :invalid_token}
|
||||
end
|
||||
|
||||
test "creates a new device" do
|
||||
test "creates a new client" do
|
||||
subject = Fixtures.Auth.create_subject()
|
||||
{:ok, token} = Auth.create_session_token_from_subject(subject)
|
||||
|
||||
attrs = connect_attrs(token: token)
|
||||
|
||||
assert {:ok, socket} = connect(Socket, attrs, connect_info: connect_info(subject))
|
||||
assert device = Map.fetch!(socket.assigns, :device)
|
||||
assert client = Map.fetch!(socket.assigns, :client)
|
||||
|
||||
assert device.external_id == attrs["external_id"]
|
||||
assert device.public_key == attrs["public_key"]
|
||||
assert device.last_seen_user_agent == subject.context.user_agent
|
||||
assert device.last_seen_remote_ip.address == subject.context.remote_ip
|
||||
assert device.last_seen_version == "0.7.412"
|
||||
assert client.external_id == attrs["external_id"]
|
||||
assert client.public_key == attrs["public_key"]
|
||||
assert client.last_seen_user_agent == subject.context.user_agent
|
||||
assert client.last_seen_remote_ip.address == subject.context.remote_ip
|
||||
assert client.last_seen_version == "0.7.412"
|
||||
end
|
||||
|
||||
test "updates existing device" do
|
||||
test "updates existing client" do
|
||||
subject = Fixtures.Auth.create_subject()
|
||||
existing_device = Fixtures.Devices.create_device(subject: subject)
|
||||
existing_client = Fixtures.Clients.create_client(subject: subject)
|
||||
{:ok, token} = Auth.create_session_token_from_subject(subject)
|
||||
|
||||
attrs = connect_attrs(token: token, external_id: existing_device.external_id)
|
||||
attrs = connect_attrs(token: token, external_id: existing_client.external_id)
|
||||
|
||||
assert {:ok, socket} = connect(Socket, attrs, connect_info: connect_info(subject))
|
||||
assert device = Repo.one(Domain.Devices.Device)
|
||||
assert device.id == socket.assigns.device.id
|
||||
assert client = Repo.one(Domain.Clients.Client)
|
||||
assert client.id == socket.assigns.client.id
|
||||
end
|
||||
end
|
||||
|
||||
describe "id/1" do
|
||||
test "creates a channel for a device" do
|
||||
device = Fixtures.Devices.create_device()
|
||||
socket = socket(API.Device.Socket, "", %{device: device})
|
||||
test "creates a channel for a client" do
|
||||
client = Fixtures.Clients.create_client()
|
||||
socket = socket(API.Client.Socket, "", %{client: client})
|
||||
|
||||
assert id(socket) == "device:#{device.id}"
|
||||
assert id(socket) == "client:#{client.id}"
|
||||
end
|
||||
end
|
||||
|
||||
@@ -67,7 +67,7 @@ defmodule API.Device.SocketTest do
|
||||
end
|
||||
|
||||
defp connect_attrs(attrs) do
|
||||
Fixtures.Devices.device_attrs()
|
||||
Fixtures.Clients.client_attrs()
|
||||
|> Map.take(~w[external_id public_key]a)
|
||||
|> Map.merge(Enum.into(attrs, %{}))
|
||||
|> Enum.into(%{}, fn {k, v} -> {to_string(k), v} end)
|
||||
|
||||
@@ -6,7 +6,7 @@ defmodule API.Gateway.ChannelTest do
|
||||
actor = Fixtures.Actors.create_actor(type: :account_admin_user, account: account)
|
||||
identity = Fixtures.Auth.create_identity(actor: actor, account: account)
|
||||
subject = Fixtures.Auth.create_subject(identity: identity)
|
||||
device = Fixtures.Devices.create_device(subject: subject)
|
||||
client = Fixtures.Clients.create_client(subject: subject)
|
||||
gateway = Fixtures.Gateways.create_gateway(account: account)
|
||||
|
||||
resource =
|
||||
@@ -27,7 +27,7 @@ defmodule API.Gateway.ChannelTest do
|
||||
actor: actor,
|
||||
identity: identity,
|
||||
subject: subject,
|
||||
device: device,
|
||||
client: client,
|
||||
gateway: gateway,
|
||||
resource: resource,
|
||||
relay: relay,
|
||||
@@ -61,7 +61,7 @@ defmodule API.Gateway.ChannelTest do
|
||||
|
||||
describe "handle_info/2 :allow_access" do
|
||||
test "pushes allow_access message", %{
|
||||
device: device,
|
||||
client: client,
|
||||
resource: resource,
|
||||
relay: relay,
|
||||
socket: socket
|
||||
@@ -75,7 +75,7 @@ defmodule API.Gateway.ChannelTest do
|
||||
socket.channel_pid,
|
||||
{:allow_access,
|
||||
%{
|
||||
device_id: device.id,
|
||||
client_id: client.id,
|
||||
resource_id: resource.id,
|
||||
authorization_expires_at: expires_at
|
||||
}}
|
||||
@@ -97,14 +97,14 @@ defmodule API.Gateway.ChannelTest do
|
||||
]
|
||||
}
|
||||
|
||||
assert payload.device_id == device.id
|
||||
assert payload.client_id == client.id
|
||||
assert DateTime.from_unix!(payload.expires_at) == DateTime.truncate(expires_at, :second)
|
||||
end
|
||||
end
|
||||
|
||||
describe "handle_info/2 :request_connection" do
|
||||
test "pushes request_connection message", %{
|
||||
device: device,
|
||||
client: client,
|
||||
resource: resource,
|
||||
relay: relay,
|
||||
socket: socket
|
||||
@@ -122,18 +122,18 @@ defmodule API.Gateway.ChannelTest do
|
||||
socket.channel_pid,
|
||||
{:request_connection, {channel_pid, socket_ref},
|
||||
%{
|
||||
device_id: device.id,
|
||||
client_id: client.id,
|
||||
resource_id: resource.id,
|
||||
authorization_expires_at: expires_at,
|
||||
device_rtc_session_description: rtc_session_description,
|
||||
device_preshared_key: preshared_key
|
||||
client_rtc_session_description: rtc_session_description,
|
||||
client_preshared_key: preshared_key
|
||||
}}
|
||||
)
|
||||
|
||||
assert_push "request_connection", payload
|
||||
|
||||
assert is_binary(payload.ref)
|
||||
assert payload.actor == %{id: device.actor_id}
|
||||
assert payload.actor == %{id: client.actor_id}
|
||||
|
||||
ipv4_stun_uri = "stun:#{relay.ipv4}:#{relay.port}"
|
||||
ipv4_turn_uri = "turn:#{relay.ipv4}:#{relay.port}"
|
||||
@@ -186,14 +186,14 @@ defmodule API.Gateway.ChannelTest do
|
||||
]
|
||||
}
|
||||
|
||||
assert payload.device == %{
|
||||
id: device.id,
|
||||
assert payload.client == %{
|
||||
id: client.id,
|
||||
peer: %{
|
||||
ipv4: device.ipv4,
|
||||
ipv6: device.ipv6,
|
||||
ipv4: client.ipv4,
|
||||
ipv6: client.ipv6,
|
||||
persistent_keepalive: 25,
|
||||
preshared_key: preshared_key,
|
||||
public_key: device.public_key
|
||||
public_key: client.public_key
|
||||
},
|
||||
rtc_session_description: rtc_session_description
|
||||
}
|
||||
@@ -203,8 +203,8 @@ defmodule API.Gateway.ChannelTest do
|
||||
end
|
||||
|
||||
describe "handle_in/3 connection_ready" do
|
||||
test "forwards RFC session description to the device channel", %{
|
||||
device: device,
|
||||
test "forwards RFC session description to the client channel", %{
|
||||
client: client,
|
||||
resource: resource,
|
||||
relay: relay,
|
||||
gateway: gateway,
|
||||
@@ -224,11 +224,11 @@ defmodule API.Gateway.ChannelTest do
|
||||
socket.channel_pid,
|
||||
{:request_connection, {channel_pid, socket_ref},
|
||||
%{
|
||||
device_id: device.id,
|
||||
client_id: client.id,
|
||||
resource_id: resource.id,
|
||||
authorization_expires_at: expires_at,
|
||||
device_rtc_session_description: rtc_session_description,
|
||||
device_preshared_key: preshared_key
|
||||
client_rtc_session_description: rtc_session_description,
|
||||
client_preshared_key: preshared_key
|
||||
}}
|
||||
)
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ defmodule API.ChannelCase do
|
||||
use Domain.CaseTemplate
|
||||
|
||||
@presences [
|
||||
Domain.Devices.Presence,
|
||||
Domain.Clients.Presence,
|
||||
Domain.Gateways.Presence,
|
||||
Domain.Relays.Presence
|
||||
]
|
||||
|
||||
@@ -21,7 +21,7 @@ defmodule Domain.Accounts.Account do
|
||||
has_many :resources, Domain.Resources.Resource, where: [deleted_at: nil]
|
||||
has_many :resource_connections, Domain.Resources.Connection, where: [deleted_at: nil]
|
||||
|
||||
has_many :devices, Domain.Devices.Device, where: [deleted_at: nil]
|
||||
has_many :clients, Domain.Clients.Client, where: [deleted_at: nil]
|
||||
|
||||
has_many :gateways, Domain.Gateways.Gateway, where: [deleted_at: nil]
|
||||
has_many :gateway_groups, Domain.Gateways.Group, where: [deleted_at: nil]
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
defmodule Domain.Actors do
|
||||
alias Domain.Actors.Membership
|
||||
alias Web.Devices
|
||||
alias Web.Clients
|
||||
alias Domain.{Repo, Validator}
|
||||
alias Domain.{Accounts, Auth, Devices}
|
||||
alias Domain.{Accounts, Auth, Clients}
|
||||
alias Domain.Actors.{Authorizer, Actor, Group}
|
||||
require Ecto.Query
|
||||
|
||||
@@ -306,7 +306,7 @@ defmodule Domain.Actors do
|
||||
with: fn actor ->
|
||||
if actor.type != :account_admin_user or other_enabled_admins_exist?(actor) do
|
||||
:ok = Auth.delete_actor_identities(actor)
|
||||
:ok = Devices.delete_actor_devices(actor)
|
||||
:ok = Clients.delete_actor_clients(actor)
|
||||
|
||||
Actor.Changeset.delete_actor(actor)
|
||||
else
|
||||
|
||||
@@ -7,7 +7,7 @@ defmodule Domain.Actors.Actor do
|
||||
field :name, :string
|
||||
|
||||
has_many :identities, Domain.Auth.Identity, where: [deleted_at: nil]
|
||||
has_many :devices, Domain.Devices.Device, where: [deleted_at: nil]
|
||||
has_many :clients, Domain.Clients.Client, where: [deleted_at: nil]
|
||||
has_many :memberships, Domain.Actors.Membership, on_replace: :delete
|
||||
has_many :groups, through: [:memberships, :group]
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@ defmodule Domain.Application do
|
||||
Domain.Auth,
|
||||
Domain.Relays,
|
||||
Domain.Gateways,
|
||||
Domain.Devices,
|
||||
Domain.Clients,
|
||||
|
||||
# Observability
|
||||
# Domain.Telemetry
|
||||
|
||||
@@ -3,7 +3,7 @@ defmodule Domain.Auth.Context do
|
||||
This structure represents an authentication context for a user or an API token.
|
||||
|
||||
Context is then used in the audit logging to persist additional metadata about
|
||||
the device and IP address used to perform the action.
|
||||
the client and IP address used to perform the action.
|
||||
"""
|
||||
@type t :: %__MODULE__{
|
||||
remote_ip: :inet.ip_address(),
|
||||
|
||||
@@ -18,7 +18,7 @@ defmodule Domain.Auth.Identity do
|
||||
field :created_by, Ecto.Enum, values: ~w[system provider identity]a
|
||||
belongs_to :created_by_identity, Domain.Auth.Identity
|
||||
|
||||
has_many :devices, Domain.Devices.Device, where: [deleted_at: nil]
|
||||
has_many :clients, Domain.Clients.Client, where: [deleted_at: nil]
|
||||
|
||||
field :deleted_at, :utc_datetime_usec
|
||||
timestamps(updated_at: false)
|
||||
|
||||
@@ -14,7 +14,7 @@ defmodule Domain.Auth.Roles do
|
||||
Domain.Actors.Authorizer,
|
||||
Domain.Auth.Authorizer,
|
||||
Domain.Config.Authorizer,
|
||||
Domain.Devices.Authorizer,
|
||||
Domain.Clients.Authorizer,
|
||||
Domain.Gateways.Authorizer,
|
||||
Domain.Policies.Authorizer,
|
||||
Domain.Relays.Authorizer,
|
||||
|
||||
@@ -3,7 +3,7 @@ defmodule Domain.Config.Configuration do
|
||||
alias Domain.Config.Logo
|
||||
|
||||
schema "configurations" do
|
||||
field :devices_upstream_dns, {:array, :string}, default: []
|
||||
field :clients_upstream_dns, {:array, :string}, default: []
|
||||
|
||||
embeds_one :logo, Logo, on_replace: :delete
|
||||
|
||||
|
||||
@@ -2,14 +2,14 @@ defmodule Domain.Config.Configuration.Changeset do
|
||||
use Domain, :changeset
|
||||
import Domain.Config, only: [config_changeset: 2]
|
||||
|
||||
@fields ~w[devices_upstream_dns]a
|
||||
@fields ~w[clients_upstream_dns]a
|
||||
|
||||
def changeset(configuration, attrs) do
|
||||
changeset =
|
||||
configuration
|
||||
|> cast(attrs, @fields)
|
||||
|> cast_embed(:logo)
|
||||
|> trim_change(:devices_upstream_dns)
|
||||
|> trim_change(:clients_upstream_dns)
|
||||
|
||||
Enum.reduce(@fields, changeset, fn field, changeset ->
|
||||
config_changeset(changeset, field)
|
||||
|
||||
@@ -86,9 +86,9 @@ defmodule Domain.Config.Definitions do
|
||||
:cookie_signing_salt,
|
||||
:cookie_encryption_salt
|
||||
]},
|
||||
{"Devices",
|
||||
{"Clients",
|
||||
[
|
||||
:devices_upstream_dns
|
||||
:clients_upstream_dns
|
||||
]},
|
||||
{"Authorization",
|
||||
"""
|
||||
@@ -403,19 +403,19 @@ defmodule Domain.Config.Definitions do
|
||||
)
|
||||
|
||||
##############################################
|
||||
## Devices
|
||||
## Clients
|
||||
##############################################
|
||||
|
||||
@doc """
|
||||
Comma-separated list of upstream DNS servers to use for devices.
|
||||
Comma-separated list of upstream DNS servers to use for clients.
|
||||
|
||||
It can be either an IP address or a FQDN if you intend to use a DNS-over-TLS server.
|
||||
|
||||
Leave this blank to omit the `DNS` section from generated configs,
|
||||
which will make devices use default system-provided DNS even when VPN session is active.
|
||||
which will make clients use default system-provided DNS even when VPN session is active.
|
||||
"""
|
||||
defconfig(
|
||||
:devices_upstream_dns,
|
||||
:clients_upstream_dns,
|
||||
{:array, ",", {:one_of, [Types.IP, :string]}, validate_unique: true},
|
||||
default: [],
|
||||
changeset: fn
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
defmodule Domain.Devices do
|
||||
defmodule Domain.Clients do
|
||||
use Supervisor
|
||||
alias Domain.{Repo, Auth, Validator}
|
||||
alias Domain.Actors
|
||||
alias Domain.Devices.{Device, Authorizer, Presence}
|
||||
alias Domain.Clients.{Client, Authorizer, Presence}
|
||||
|
||||
def start_link(opts) do
|
||||
Supervisor.start_link(__MODULE__, opts, name: __MODULE__)
|
||||
@@ -17,38 +17,38 @@ defmodule Domain.Devices do
|
||||
end
|
||||
|
||||
def count_by_account_id(account_id) do
|
||||
Device.Query.by_account_id(account_id)
|
||||
Client.Query.by_account_id(account_id)
|
||||
|> Repo.aggregate(:count)
|
||||
end
|
||||
|
||||
def count_by_actor_id(actor_id) do
|
||||
Device.Query.by_actor_id(actor_id)
|
||||
Client.Query.by_actor_id(actor_id)
|
||||
|> Repo.aggregate(:count)
|
||||
end
|
||||
|
||||
def fetch_device_by_id(id, %Auth.Subject{} = subject, opts \\ []) do
|
||||
def fetch_client_by_id(id, %Auth.Subject{} = subject, opts \\ []) do
|
||||
required_permissions =
|
||||
{:one_of,
|
||||
[
|
||||
Authorizer.manage_devices_permission(),
|
||||
Authorizer.manage_own_devices_permission()
|
||||
Authorizer.manage_clients_permission(),
|
||||
Authorizer.manage_own_clients_permission()
|
||||
]}
|
||||
|
||||
with :ok <- Auth.ensure_has_permissions(subject, required_permissions),
|
||||
true <- Validator.valid_uuid?(id) do
|
||||
{preload, _opts} = Keyword.pop(opts, :preload, [])
|
||||
|
||||
Device.Query.by_id(id)
|
||||
Client.Query.by_id(id)
|
||||
|> Authorizer.for_subject(subject)
|
||||
|> Repo.fetch()
|
||||
|> case do
|
||||
{:ok, device} ->
|
||||
device =
|
||||
device
|
||||
{:ok, client} ->
|
||||
client =
|
||||
client
|
||||
|> Repo.preload(preload)
|
||||
|> preload_online_status()
|
||||
|
||||
{:ok, device}
|
||||
{:ok, client}
|
||||
|
||||
{:error, reason} ->
|
||||
{:error, reason}
|
||||
@@ -59,63 +59,63 @@ defmodule Domain.Devices do
|
||||
end
|
||||
end
|
||||
|
||||
def fetch_device_by_id!(id, opts \\ []) do
|
||||
def fetch_client_by_id!(id, opts \\ []) do
|
||||
{preload, _opts} = Keyword.pop(opts, :preload, [])
|
||||
|
||||
Device.Query.by_id(id)
|
||||
Client.Query.by_id(id)
|
||||
|> Repo.one!()
|
||||
|> Repo.preload(preload)
|
||||
|> preload_online_status()
|
||||
end
|
||||
|
||||
def list_devices(%Auth.Subject{} = subject, opts \\ []) do
|
||||
def list_clients(%Auth.Subject{} = subject, opts \\ []) do
|
||||
{preload, _opts} = Keyword.pop(opts, :preload, [])
|
||||
|
||||
required_permissions =
|
||||
{:one_of,
|
||||
[
|
||||
Authorizer.manage_devices_permission(),
|
||||
Authorizer.manage_own_devices_permission()
|
||||
Authorizer.manage_clients_permission(),
|
||||
Authorizer.manage_own_clients_permission()
|
||||
]}
|
||||
|
||||
with :ok <- Auth.ensure_has_permissions(subject, required_permissions) do
|
||||
{:ok, devices} =
|
||||
Device.Query.all()
|
||||
{:ok, clients} =
|
||||
Client.Query.all()
|
||||
|> Authorizer.for_subject(subject)
|
||||
|> Repo.list()
|
||||
|
||||
devices =
|
||||
devices
|
||||
clients =
|
||||
clients
|
||||
|> preload_online_statuses()
|
||||
|
||||
{:ok, Repo.preload(devices, preload)}
|
||||
{:ok, Repo.preload(clients, preload)}
|
||||
end
|
||||
end
|
||||
|
||||
def list_devices_for_actor(%Actors.Actor{} = actor, %Auth.Subject{} = subject) do
|
||||
list_devices_by_actor_id(actor.id, subject)
|
||||
def list_clients_for_actor(%Actors.Actor{} = actor, %Auth.Subject{} = subject) do
|
||||
list_clients_by_actor_id(actor.id, subject)
|
||||
end
|
||||
|
||||
def list_devices_by_actor_id(actor_id, %Auth.Subject{} = subject) do
|
||||
def list_clients_by_actor_id(actor_id, %Auth.Subject{} = subject) do
|
||||
required_permissions =
|
||||
{:one_of,
|
||||
[
|
||||
Authorizer.manage_devices_permission(),
|
||||
Authorizer.manage_own_devices_permission()
|
||||
Authorizer.manage_clients_permission(),
|
||||
Authorizer.manage_own_clients_permission()
|
||||
]}
|
||||
|
||||
with :ok <- Auth.ensure_has_permissions(subject, required_permissions),
|
||||
true <- Validator.valid_uuid?(actor_id) do
|
||||
{:ok, devices} =
|
||||
Device.Query.by_actor_id(actor_id)
|
||||
{:ok, clients} =
|
||||
Client.Query.by_actor_id(actor_id)
|
||||
|> Authorizer.for_subject(subject)
|
||||
|> Repo.list()
|
||||
|
||||
devices =
|
||||
devices
|
||||
clients =
|
||||
clients
|
||||
|> preload_online_statuses()
|
||||
|
||||
{:ok, devices}
|
||||
{:ok, clients}
|
||||
else
|
||||
false -> {:error, :not_found}
|
||||
other -> other
|
||||
@@ -123,67 +123,67 @@ defmodule Domain.Devices do
|
||||
end
|
||||
|
||||
# TODO: this is ugly!
|
||||
defp preload_online_status(device) do
|
||||
connected_devices = Presence.list("devices:#{device.id}")
|
||||
%{device | online?: Map.has_key?(connected_devices, device.id)}
|
||||
defp preload_online_status(client) do
|
||||
connected_clients = Presence.list("clients:#{client.id}")
|
||||
%{client | online?: Map.has_key?(connected_clients, client.id)}
|
||||
end
|
||||
|
||||
defp preload_online_statuses([]), do: []
|
||||
|
||||
defp preload_online_statuses([device | _] = devices) do
|
||||
connected_devices = Presence.list("devices:#{device.account_id}")
|
||||
defp preload_online_statuses([client | _] = clients) do
|
||||
connected_clients = Presence.list("clients:#{client.account_id}")
|
||||
|
||||
Enum.map(devices, fn device ->
|
||||
%{device | online?: Map.has_key?(connected_devices, device.id)}
|
||||
Enum.map(clients, fn client ->
|
||||
%{client | online?: Map.has_key?(connected_clients, client.id)}
|
||||
end)
|
||||
end
|
||||
|
||||
def change_device(%Device{} = device, attrs \\ %{}) do
|
||||
Device.Changeset.update(device, attrs)
|
||||
def change_client(%Client{} = client, attrs \\ %{}) do
|
||||
Client.Changeset.update(client, attrs)
|
||||
end
|
||||
|
||||
def upsert_device(attrs \\ %{}, %Auth.Subject{identity: %Auth.Identity{} = identity} = subject) do
|
||||
with :ok <- Auth.ensure_has_permissions(subject, Authorizer.manage_own_devices_permission()) do
|
||||
changeset = Device.Changeset.upsert(identity, subject.context, attrs)
|
||||
def upsert_client(attrs \\ %{}, %Auth.Subject{identity: %Auth.Identity{} = identity} = subject) do
|
||||
with :ok <- Auth.ensure_has_permissions(subject, Authorizer.manage_own_clients_permission()) do
|
||||
changeset = Client.Changeset.upsert(identity, subject.context, attrs)
|
||||
|
||||
Ecto.Multi.new()
|
||||
|> Ecto.Multi.insert(:device, changeset,
|
||||
conflict_target: Device.Changeset.upsert_conflict_target(),
|
||||
on_conflict: Device.Changeset.upsert_on_conflict(),
|
||||
|> Ecto.Multi.insert(:client, changeset,
|
||||
conflict_target: Client.Changeset.upsert_conflict_target(),
|
||||
on_conflict: Client.Changeset.upsert_on_conflict(),
|
||||
returning: true
|
||||
)
|
||||
|> resolve_address_multi(:ipv4)
|
||||
|> resolve_address_multi(:ipv6)
|
||||
|> Ecto.Multi.update(:device_with_address, fn
|
||||
%{device: %Device{} = device, ipv4: ipv4, ipv6: ipv6} ->
|
||||
Device.Changeset.finalize_upsert(device, ipv4, ipv6)
|
||||
|> Ecto.Multi.update(:client_with_address, fn
|
||||
%{client: %Client{} = client, ipv4: ipv4, ipv6: ipv6} ->
|
||||
Client.Changeset.finalize_upsert(client, ipv4, ipv6)
|
||||
end)
|
||||
|> Repo.transaction()
|
||||
|> case do
|
||||
{:ok, %{device_with_address: device}} -> {:ok, device}
|
||||
{:error, :device, changeset, _effects_so_far} -> {:error, changeset}
|
||||
{:ok, %{client_with_address: client}} -> {:ok, client}
|
||||
{:error, :client, changeset, _effects_so_far} -> {:error, changeset}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defp resolve_address_multi(multi, type) do
|
||||
Ecto.Multi.run(multi, type, fn _repo, %{device: %Device{} = device} ->
|
||||
if address = Map.get(device, type) do
|
||||
Ecto.Multi.run(multi, type, fn _repo, %{client: %Client{} = client} ->
|
||||
if address = Map.get(client, type) do
|
||||
{:ok, address}
|
||||
else
|
||||
{:ok, Domain.Network.fetch_next_available_address!(device.account_id, type)}
|
||||
{:ok, Domain.Network.fetch_next_available_address!(client.account_id, type)}
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
def update_device(%Device{} = device, attrs, %Auth.Subject{} = subject) do
|
||||
with :ok <- authorize_actor_device_management(device.actor_id, subject) do
|
||||
Device.Query.by_id(device.id)
|
||||
def update_client(%Client{} = client, attrs, %Auth.Subject{} = subject) do
|
||||
with :ok <- authorize_actor_client_management(client.actor_id, subject) do
|
||||
Client.Query.by_id(client.id)
|
||||
|> Authorizer.for_subject(subject)
|
||||
|> Repo.fetch_and_update(with: &Device.Changeset.update(&1, attrs))
|
||||
|> Repo.fetch_and_update(with: &Client.Changeset.update(&1, attrs))
|
||||
|> case do
|
||||
{:ok, device} ->
|
||||
{:ok, preload_online_status(device)}
|
||||
{:ok, client} ->
|
||||
{:ok, preload_online_status(client)}
|
||||
|
||||
{:error, reason} ->
|
||||
{:error, reason}
|
||||
@@ -191,49 +191,49 @@ defmodule Domain.Devices do
|
||||
end
|
||||
end
|
||||
|
||||
def delete_device(%Device{} = device, %Auth.Subject{} = subject) do
|
||||
with :ok <- authorize_actor_device_management(device.actor_id, subject) do
|
||||
Device.Query.by_id(device.id)
|
||||
def delete_client(%Client{} = client, %Auth.Subject{} = subject) do
|
||||
with :ok <- authorize_actor_client_management(client.actor_id, subject) do
|
||||
Client.Query.by_id(client.id)
|
||||
|> Authorizer.for_subject(subject)
|
||||
|> Repo.fetch_and_update(with: &Device.Changeset.delete/1)
|
||||
|> Repo.fetch_and_update(with: &Client.Changeset.delete/1)
|
||||
end
|
||||
end
|
||||
|
||||
def delete_actor_devices(%Actors.Actor{} = actor) do
|
||||
def delete_actor_clients(%Actors.Actor{} = actor) do
|
||||
{_count, nil} =
|
||||
Device.Query.by_actor_id(actor.id)
|
||||
Client.Query.by_actor_id(actor.id)
|
||||
|> Repo.update_all(set: [deleted_at: DateTime.utc_now()])
|
||||
|
||||
:ok
|
||||
end
|
||||
|
||||
def authorize_actor_device_management(%Actors.Actor{} = actor, %Auth.Subject{} = subject) do
|
||||
authorize_actor_device_management(actor.id, subject)
|
||||
def authorize_actor_client_management(%Actors.Actor{} = actor, %Auth.Subject{} = subject) do
|
||||
authorize_actor_client_management(actor.id, subject)
|
||||
end
|
||||
|
||||
def authorize_actor_device_management(actor_id, %Auth.Subject{actor: %{id: actor_id}} = subject) do
|
||||
Auth.ensure_has_permissions(subject, Authorizer.manage_own_devices_permission())
|
||||
def authorize_actor_client_management(actor_id, %Auth.Subject{actor: %{id: actor_id}} = subject) do
|
||||
Auth.ensure_has_permissions(subject, Authorizer.manage_own_clients_permission())
|
||||
end
|
||||
|
||||
def authorize_actor_device_management(_actor_id, %Auth.Subject{} = subject) do
|
||||
Auth.ensure_has_permissions(subject, Authorizer.manage_devices_permission())
|
||||
def authorize_actor_client_management(_actor_id, %Auth.Subject{} = subject) do
|
||||
Auth.ensure_has_permissions(subject, Authorizer.manage_clients_permission())
|
||||
end
|
||||
|
||||
def connect_device(%Device{} = device) do
|
||||
def connect_client(%Client{} = client) do
|
||||
{:ok, _} =
|
||||
Presence.track(self(), "devices:#{device.account_id}", device.id, %{
|
||||
Presence.track(self(), "clients:#{client.account_id}", client.id, %{
|
||||
online_at: System.system_time(:second)
|
||||
})
|
||||
|
||||
{:ok, _} = Presence.track(self(), "actor_devices:#{device.actor_id}", device.id, %{})
|
||||
{:ok, _} = Presence.track(self(), "actor_clients:#{client.actor_id}", client.id, %{})
|
||||
|
||||
:ok
|
||||
end
|
||||
|
||||
def fetch_device_config!(%Device{} = device) do
|
||||
def fetch_client_config!(%Client{} = client) do
|
||||
%{
|
||||
devices_upstream_dns: upstream_dns
|
||||
} = Domain.Config.fetch_resolved_configs!(device.account_id, [:devices_upstream_dns])
|
||||
clients_upstream_dns: upstream_dns
|
||||
} = Domain.Config.fetch_resolved_configs!(client.account_id, [:clients_upstream_dns])
|
||||
|
||||
[upstream_dns: upstream_dns]
|
||||
end
|
||||
|
||||
@@ -1,22 +1,22 @@
|
||||
defmodule Domain.Devices.Authorizer do
|
||||
defmodule Domain.Clients.Authorizer do
|
||||
use Domain.Auth.Authorizer
|
||||
alias Domain.Devices.Device
|
||||
alias Domain.Clients.Client
|
||||
|
||||
def manage_own_devices_permission, do: build(Device, :manage_own)
|
||||
def manage_devices_permission, do: build(Device, :manage)
|
||||
def manage_own_clients_permission, do: build(Client, :manage_own)
|
||||
def manage_clients_permission, do: build(Client, :manage)
|
||||
|
||||
@impl Domain.Auth.Authorizer
|
||||
|
||||
def list_permissions_for_role(:account_admin_user) do
|
||||
[
|
||||
manage_own_devices_permission(),
|
||||
manage_devices_permission()
|
||||
manage_own_clients_permission(),
|
||||
manage_clients_permission()
|
||||
]
|
||||
end
|
||||
|
||||
def list_permissions_for_role(:account_user) do
|
||||
[
|
||||
manage_own_devices_permission()
|
||||
manage_own_clients_permission()
|
||||
]
|
||||
end
|
||||
|
||||
@@ -27,13 +27,13 @@ defmodule Domain.Devices.Authorizer do
|
||||
@impl Domain.Auth.Authorizer
|
||||
def for_subject(queryable, %Subject{} = subject) do
|
||||
cond do
|
||||
has_permission?(subject, manage_devices_permission()) ->
|
||||
Device.Query.by_account_id(queryable, subject.account.id)
|
||||
has_permission?(subject, manage_clients_permission()) ->
|
||||
Client.Query.by_account_id(queryable, subject.account.id)
|
||||
|
||||
has_permission?(subject, manage_own_devices_permission()) ->
|
||||
has_permission?(subject, manage_own_clients_permission()) ->
|
||||
queryable
|
||||
|> Device.Query.by_account_id(subject.account.id)
|
||||
|> Device.Query.by_actor_id(subject.actor.id)
|
||||
|> Client.Query.by_account_id(subject.account.id)
|
||||
|> Client.Query.by_actor_id(subject.actor.id)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
defmodule Domain.Devices.Device do
|
||||
defmodule Domain.Clients.Client do
|
||||
use Domain, :schema
|
||||
|
||||
schema "devices" do
|
||||
schema "clients" do
|
||||
field :external_id, :string
|
||||
|
||||
field :name, :string
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
defmodule Domain.Devices.Device.Changeset do
|
||||
defmodule Domain.Clients.Client.Changeset do
|
||||
use Domain, :changeset
|
||||
alias Domain.{Version, Auth}
|
||||
alias Domain.Devices
|
||||
alias Domain.Clients
|
||||
|
||||
@upsert_fields ~w[external_id name public_key]a
|
||||
@conflict_replace_fields ~w[public_key
|
||||
@@ -20,7 +20,7 @@ defmodule Domain.Devices.Device.Changeset do
|
||||
def upsert_on_conflict, do: {:replace, @conflict_replace_fields}
|
||||
|
||||
def upsert(%Auth.Identity{} = identity, %Auth.Context{} = context, attrs) do
|
||||
%Devices.Device{}
|
||||
%Clients.Client{}
|
||||
|> cast(attrs, @upsert_fields)
|
||||
|> put_default_value(:name, &generate_name/0)
|
||||
|> put_change(:identity_id, identity.id)
|
||||
@@ -32,30 +32,30 @@ defmodule Domain.Devices.Device.Changeset do
|
||||
|> validate_required(@required_fields)
|
||||
|> validate_base64(:public_key)
|
||||
|> validate_length(:public_key, is: @key_length)
|
||||
|> unique_constraint(:ipv4, name: :devices_account_id_ipv4_index)
|
||||
|> unique_constraint(:ipv6, name: :devices_account_id_ipv6_index)
|
||||
|> unique_constraint(:ipv4, name: :clients_account_id_ipv4_index)
|
||||
|> unique_constraint(:ipv6, name: :clients_account_id_ipv6_index)
|
||||
|> put_change(:last_seen_at, DateTime.utc_now())
|
||||
|> put_device_version()
|
||||
|> put_client_version()
|
||||
end
|
||||
|
||||
def finalize_upsert(%Devices.Device{} = device, ipv4, ipv6) do
|
||||
device
|
||||
def finalize_upsert(%Clients.Client{} = client, ipv4, ipv6) do
|
||||
client
|
||||
|> change()
|
||||
|> put_change(:ipv4, ipv4)
|
||||
|> put_change(:ipv6, ipv6)
|
||||
|> unique_constraint(:ipv4, name: :devices_account_id_ipv4_index)
|
||||
|> unique_constraint(:ipv6, name: :devices_account_id_ipv6_index)
|
||||
|> unique_constraint(:ipv4, name: :clients_account_id_ipv4_index)
|
||||
|> unique_constraint(:ipv6, name: :clients_account_id_ipv6_index)
|
||||
end
|
||||
|
||||
def update(%Devices.Device{} = device, attrs) do
|
||||
device
|
||||
def update(%Clients.Client{} = client, attrs) do
|
||||
client
|
||||
|> cast(attrs, @update_fields)
|
||||
|> validate_required(@required_fields)
|
||||
|> changeset()
|
||||
end
|
||||
|
||||
def delete(%Devices.Device{} = device) do
|
||||
device
|
||||
def delete(%Clients.Client{} = client) do
|
||||
client
|
||||
|> change()
|
||||
|> put_default_value(:deleted_at, DateTime.utc_now())
|
||||
end
|
||||
@@ -68,10 +68,10 @@ defmodule Domain.Devices.Device.Changeset do
|
||||
|> unique_constraint([:actor_id, :name])
|
||||
|> unique_constraint([:actor_id, :public_key])
|
||||
|> unique_constraint(:external_id)
|
||||
|> unique_constraint(:name, name: :devices_account_id_actor_id_name_index)
|
||||
|> unique_constraint(:name, name: :clients_account_id_actor_id_name_index)
|
||||
end
|
||||
|
||||
defp put_device_version(changeset) do
|
||||
defp put_client_version(changeset) do
|
||||
with {_data_or_changes, user_agent} when not is_nil(user_agent) <-
|
||||
fetch_field(changeset, :last_seen_user_agent),
|
||||
{:ok, version} <- Version.fetch_version(user_agent) do
|
||||
|
||||
@@ -1,40 +1,40 @@
|
||||
defmodule Domain.Devices.Device.Query do
|
||||
defmodule Domain.Clients.Client.Query do
|
||||
use Domain, :query
|
||||
|
||||
def all do
|
||||
from(devices in Domain.Devices.Device, as: :devices)
|
||||
|> where([devices: devices], is_nil(devices.deleted_at))
|
||||
from(clients in Domain.Clients.Client, as: :clients)
|
||||
|> where([clients: clients], is_nil(clients.deleted_at))
|
||||
end
|
||||
|
||||
def by_id(queryable \\ all(), id) do
|
||||
where(queryable, [devices: devices], devices.id == ^id)
|
||||
where(queryable, [clients: clients], clients.id == ^id)
|
||||
end
|
||||
|
||||
def by_actor_id(queryable \\ all(), actor_id) do
|
||||
where(queryable, [devices: devices], devices.actor_id == ^actor_id)
|
||||
where(queryable, [clients: clients], clients.actor_id == ^actor_id)
|
||||
end
|
||||
|
||||
def by_account_id(queryable \\ all(), account_id) do
|
||||
where(queryable, [devices: devices], devices.account_id == ^account_id)
|
||||
where(queryable, [clients: clients], clients.account_id == ^account_id)
|
||||
end
|
||||
|
||||
def returning_all(queryable \\ all()) do
|
||||
select(queryable, [devices: devices], devices)
|
||||
select(queryable, [clients: clients], clients)
|
||||
end
|
||||
|
||||
def with_preloaded_actor(queryable \\ all()) do
|
||||
with_named_binding(queryable, :actor, fn queryable, binding ->
|
||||
queryable
|
||||
|> join(:inner, [devices: devices], actor in assoc(devices, ^binding), as: ^binding)
|
||||
|> preload([devices: devices, actor: actor], actor: actor)
|
||||
|> join(:inner, [clients: clients], actor in assoc(clients, ^binding), as: ^binding)
|
||||
|> preload([clients: clients, actor: actor], actor: actor)
|
||||
end)
|
||||
end
|
||||
|
||||
def with_preloaded_identity(queryable \\ all()) do
|
||||
with_named_binding(queryable, :identity, fn queryable, binding ->
|
||||
queryable
|
||||
|> join(:inner, [devices: devices], identity in assoc(devices, ^binding), as: ^binding)
|
||||
|> preload([devices: devices, identity: identity], identity: identity)
|
||||
|> join(:inner, [clients: clients], identity in assoc(clients, ^binding), as: ^binding)
|
||||
|> preload([clients: clients, identity: identity], identity: identity)
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
defmodule Domain.Devices.Presence do
|
||||
defmodule Domain.Clients.Presence do
|
||||
use Phoenix.Presence,
|
||||
otp_app: :domain,
|
||||
pubsub_server: Domain.PubSub
|
||||
|
||||
@@ -147,7 +147,7 @@ defmodule Domain.NameGenerator do
|
||||
recommendation response selection storage version alcohol argument complaint contract
|
||||
emphasis highway loss membership possession preparation steak union agreement cancer currency
|
||||
employment engineering entry interaction mixture preference region republic tradition virus
|
||||
actor classroom delivery device difficulty drama election engine football guidance hotel
|
||||
actor classroom delivery client difficulty drama election engine football guidance hotel
|
||||
owner priority protection suggestion tension variation anxiety atmosphere awareness bath
|
||||
bread candidate climate comparison confusion construction elevator emotion employee employer
|
||||
guest height leadership mall manager operation recording sample transportation charity cousin
|
||||
|
||||
@@ -116,7 +116,7 @@ defmodule Domain.Resources do
|
||||
# {:ok, actors} = list_authorized_actors(resource)
|
||||
# Phoenix.PubSub.broadcast(
|
||||
# Domain.PubSub,
|
||||
# "actor_device:#{subject.actor.id}",
|
||||
# "actor_client:#{subject.actor.id}",
|
||||
# {:resource_added, resource.id}
|
||||
# )
|
||||
|
||||
@@ -151,7 +151,7 @@ defmodule Domain.Resources do
|
||||
{:ok, resource} ->
|
||||
# Phoenix.PubSub.broadcast(
|
||||
# Domain.PubSub,
|
||||
# "actor_device:#{resource.actor_id}",
|
||||
# "actor_client:#{resource.actor_id}",
|
||||
# {:resource_updated, resource.id}
|
||||
# )
|
||||
|
||||
@@ -172,7 +172,7 @@ defmodule Domain.Resources do
|
||||
{:ok, resource} ->
|
||||
# Phoenix.PubSub.broadcast(
|
||||
# Domain.PubSub,
|
||||
# "actor_device:#{resource.actor_id}",
|
||||
# "actor_client:#{resource.actor_id}",
|
||||
# {:resource_removed, resource.id}
|
||||
# )
|
||||
|
||||
|
||||
@@ -39,8 +39,8 @@
|
||||
# :ok
|
||||
# end
|
||||
|
||||
# def add_device do
|
||||
# PostHog.capture("add_device", common_fields())
|
||||
# def add_client do
|
||||
# PostHog.capture("add_client", common_fields())
|
||||
# :ok
|
||||
# end
|
||||
|
||||
@@ -54,8 +54,8 @@
|
||||
# :ok
|
||||
# end
|
||||
|
||||
# def delete_device do
|
||||
# PostHog.capture("delete_device", common_fields())
|
||||
# def delete_client do
|
||||
# PostHog.capture("delete_client", common_fields())
|
||||
# :ok
|
||||
# end
|
||||
|
||||
@@ -94,8 +94,8 @@
|
||||
# :ok
|
||||
# end
|
||||
|
||||
# # How far back to count handshakes as an active device
|
||||
# # @active_device_window 86_400
|
||||
# # How far back to count handshakes as an active client
|
||||
# # @active_client_window 86_400
|
||||
# def ping_data do
|
||||
# %{
|
||||
# local_auth_enabled: {_, local_auth_enabled},
|
||||
@@ -108,12 +108,12 @@
|
||||
|
||||
# common_fields() ++
|
||||
# [
|
||||
# # devices_active_within_24h: Devices.count_active_within(@active_device_window),
|
||||
# # clients_active_within_24h: Clients.count_active_within(@active_client_window),
|
||||
# # admin_count: Users.count_by_role(:account_admin_user),
|
||||
# # actor_count: Users.count(),
|
||||
# in_docker: in_docker?(),
|
||||
# # device_count: Devices.count(),
|
||||
# # max_devices_for_actors: Devices.count_maximum_for_a_actor(),
|
||||
# # client_count: Clients.count(),
|
||||
# # max_clients_for_actors: Clients.count_maximum_for_a_actor(),
|
||||
# # actors_with_mfa: MFA.count_actors_with_mfa_enabled(),
|
||||
# # actors_with_mfa_totp: MFA.count_actors_with_totp_method(),
|
||||
# local_authentication: local_auth_enabled,
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
defmodule Domain.Repo.Migrations.RenameDevicesToClients do
|
||||
use Ecto.Migration
|
||||
|
||||
def change do
|
||||
rename(table(:configurations), :devices_upstream_dns, to: :clients_upstream_dns)
|
||||
|
||||
execute("""
|
||||
ALTER INDEX devices_account_id_ipv4_index
|
||||
RENAME TO clients_account_id_ipv4_index
|
||||
""")
|
||||
|
||||
execute("""
|
||||
ALTER INDEX devices_account_id_ipv6_index
|
||||
RENAME TO clients_account_id_ipv6_index
|
||||
""")
|
||||
|
||||
execute("""
|
||||
ALTER INDEX devices_account_id_actor_id_external_id_index
|
||||
RENAME TO clients_account_id_actor_id_external_id_index
|
||||
""")
|
||||
|
||||
execute("""
|
||||
ALTER INDEX devices_account_id_actor_id_name_index
|
||||
RENAME TO clients_account_id_actor_id_name_index
|
||||
""")
|
||||
|
||||
execute("""
|
||||
ALTER INDEX devices_account_id_actor_id_public_key_index
|
||||
RENAME TO clients_account_id_actor_id_public_key_index
|
||||
""")
|
||||
|
||||
rename(table(:devices), to: table(:clients))
|
||||
end
|
||||
end
|
||||
@@ -222,7 +222,7 @@ IO.puts(
|
||||
IO.puts("")
|
||||
|
||||
_user_iphone =
|
||||
Domain.Devices.upsert_device(
|
||||
Domain.Clients.upsert_client(
|
||||
%{
|
||||
name: "FZ User iPhone",
|
||||
external_id: Ecto.UUID.generate(),
|
||||
@@ -233,7 +233,7 @@ _user_iphone =
|
||||
)
|
||||
|
||||
_admin_iphone =
|
||||
Domain.Devices.upsert_device(
|
||||
Domain.Clients.upsert_client(
|
||||
%{
|
||||
name: "FZ Admin iPhone",
|
||||
external_id: Ecto.UUID.generate(),
|
||||
@@ -243,7 +243,7 @@ _admin_iphone =
|
||||
admin_subject
|
||||
)
|
||||
|
||||
IO.puts("Devices created")
|
||||
IO.puts("Clients created")
|
||||
IO.puts("")
|
||||
|
||||
IO.puts("Created Actor Groups: ")
|
||||
@@ -453,6 +453,6 @@ IO.puts("")
|
||||
{:ok, unprivileged_subject_client_token} =
|
||||
Auth.create_client_token_from_subject(unprivileged_subject)
|
||||
|
||||
IO.puts("Created device tokens:")
|
||||
IO.puts("Created client tokens:")
|
||||
IO.puts(" #{unprivileged_actor_email} token: #{unprivileged_subject_client_token}")
|
||||
IO.puts("")
|
||||
|
||||
@@ -1810,7 +1810,7 @@ defmodule Domain.ActorsTest do
|
||||
assert is_nil(other_actor.deleted_at)
|
||||
end
|
||||
|
||||
test "deletes actor identities and devices" do
|
||||
test "deletes actor identities and clients" do
|
||||
account = Fixtures.Accounts.create_account()
|
||||
actor = Fixtures.Actors.create_actor(type: :account_admin_user, account: account)
|
||||
identity = Fixtures.Auth.create_identity(account: account, actor: actor)
|
||||
@@ -1818,12 +1818,12 @@ defmodule Domain.ActorsTest do
|
||||
|
||||
actor_to_delete = Fixtures.Actors.create_actor(type: :account_admin_user, account: account)
|
||||
Fixtures.Auth.create_identity(account: account, actor: actor_to_delete)
|
||||
Fixtures.Devices.create_device(account: account, actor: actor_to_delete)
|
||||
Fixtures.Clients.create_client(account: account, actor: actor_to_delete)
|
||||
|
||||
assert {:ok, actor} = delete_actor(actor_to_delete, subject)
|
||||
assert actor.deleted_at
|
||||
|
||||
assert Repo.aggregate(Domain.Devices.Device.Query.all(), :count) == 0
|
||||
assert Repo.aggregate(Domain.Clients.Client.Query.all(), :count) == 0
|
||||
assert Repo.aggregate(Domain.Auth.Identity.Query.all(), :count) == 1
|
||||
end
|
||||
|
||||
|
||||
@@ -57,8 +57,8 @@ defmodule Domain.Config.DefinitionTest do
|
||||
end
|
||||
|
||||
test "inserts a function which returns definition doc" do
|
||||
assert {:ok, doc} = fetch_doc(Domain.Config.Definitions, :devices_upstream_dns)
|
||||
assert doc =~ "Comma-separated list of upstream DNS servers to use for devices."
|
||||
assert {:ok, doc} = fetch_doc(Domain.Config.Definitions, :clients_upstream_dns)
|
||||
assert doc =~ "Comma-separated list of upstream DNS servers to use for clients."
|
||||
|
||||
assert fetch_doc(Foo, :bar) ==
|
||||
{:error, :module_not_found}
|
||||
|
||||
@@ -37,10 +37,10 @@ defmodule Domain.Config.ResolverTest do
|
||||
|
||||
test "returns variable from database" do
|
||||
env_configurations = %{}
|
||||
db_configurations = %Domain.Config.Configuration{devices_upstream_dns: "1.2.3.4"}
|
||||
db_configurations = %Domain.Config.Configuration{clients_upstream_dns: "1.2.3.4"}
|
||||
|
||||
assert resolve(:devices_upstream_dns, env_configurations, db_configurations, []) ==
|
||||
{:ok, {{:db, :devices_upstream_dns}, "1.2.3.4"}}
|
||||
assert resolve(:clients_upstream_dns, env_configurations, db_configurations, []) ==
|
||||
{:ok, {{:db, :clients_upstream_dns}, "1.2.3.4"}}
|
||||
end
|
||||
|
||||
test "precedence" do
|
||||
|
||||
@@ -90,9 +90,9 @@ defmodule Domain.ConfigTest do
|
||||
end
|
||||
|
||||
test "returns source and config values", %{account: account} do
|
||||
assert fetch_resolved_configs!(account.id, [:devices_upstream_dns, :devices_upstream_dns]) ==
|
||||
assert fetch_resolved_configs!(account.id, [:clients_upstream_dns, :clients_upstream_dns]) ==
|
||||
%{
|
||||
devices_upstream_dns: [%Postgrex.INET{address: {1, 1, 1, 1}, netmask: nil}]
|
||||
clients_upstream_dns: [%Postgrex.INET{address: {1, 1, 1, 1}, netmask: nil}]
|
||||
}
|
||||
end
|
||||
|
||||
@@ -137,10 +137,10 @@ defmodule Domain.ConfigTest do
|
||||
end
|
||||
|
||||
test "returns source and config values", %{account: account} do
|
||||
assert fetch_resolved_configs_with_sources!(account.id, [:devices_upstream_dns]) ==
|
||||
assert fetch_resolved_configs_with_sources!(account.id, [:clients_upstream_dns]) ==
|
||||
%{
|
||||
devices_upstream_dns:
|
||||
{{:db, :devices_upstream_dns},
|
||||
clients_upstream_dns:
|
||||
{{:db, :clients_upstream_dns},
|
||||
[%Postgrex.INET{address: {1, 1, 1, 1}, netmask: nil}]}
|
||||
}
|
||||
end
|
||||
@@ -350,7 +350,7 @@ defmodule Domain.ConfigTest do
|
||||
} do
|
||||
assert get_account_config_by_account_id(account.id) == %Domain.Config.Configuration{
|
||||
account_id: account.id,
|
||||
devices_upstream_dns: []
|
||||
clients_upstream_dns: []
|
||||
}
|
||||
end
|
||||
end
|
||||
@@ -387,7 +387,7 @@ defmodule Domain.ConfigTest do
|
||||
|
||||
assert config == %Domain.Config.Configuration{
|
||||
account_id: account.id,
|
||||
devices_upstream_dns: []
|
||||
clients_upstream_dns: []
|
||||
}
|
||||
end
|
||||
|
||||
@@ -442,13 +442,13 @@ defmodule Domain.ConfigTest do
|
||||
config = get_account_config_by_account_id(account.id)
|
||||
|
||||
attrs = %{
|
||||
devices_upstream_dns: ["!!!"]
|
||||
clients_upstream_dns: ["!!!"]
|
||||
}
|
||||
|
||||
assert {:error, changeset} = update_config(config, attrs)
|
||||
|
||||
assert errors_on(changeset) == %{
|
||||
devices_upstream_dns: [
|
||||
clients_upstream_dns: [
|
||||
"!!! is not a valid FQDN",
|
||||
"must be one of: Elixir.Domain.Types.IP, string"
|
||||
]
|
||||
@@ -456,20 +456,20 @@ defmodule Domain.ConfigTest do
|
||||
end
|
||||
|
||||
test "returns error when trying to change overridden value", %{account: account} do
|
||||
put_system_env_override(:devices_upstream_dns, ["1.2.3.4"])
|
||||
put_system_env_override(:clients_upstream_dns, ["1.2.3.4"])
|
||||
|
||||
config = get_account_config_by_account_id(account.id)
|
||||
|
||||
attrs = %{
|
||||
devices_upstream_dns: ["4.1.2.3"]
|
||||
clients_upstream_dns: ["4.1.2.3"]
|
||||
}
|
||||
|
||||
assert {:error, changeset} = update_config(config, attrs)
|
||||
|
||||
assert errors_on(changeset) ==
|
||||
%{
|
||||
devices_upstream_dns: [
|
||||
"cannot be changed; it is overridden by DEVICES_UPSTREAM_DNS environment variable"
|
||||
clients_upstream_dns: [
|
||||
"cannot be changed; it is overridden by CLIENTS_UPSTREAM_DNS environment variable"
|
||||
]
|
||||
}
|
||||
end
|
||||
@@ -478,27 +478,27 @@ defmodule Domain.ConfigTest do
|
||||
config = get_account_config_by_account_id(account.id)
|
||||
|
||||
attrs = %{
|
||||
devices_upstream_dns: [" foobar.com", "google.com "]
|
||||
clients_upstream_dns: [" foobar.com", "google.com "]
|
||||
}
|
||||
|
||||
assert {:ok, config} = update_config(config, attrs)
|
||||
assert config.devices_upstream_dns == ["foobar.com", "google.com"]
|
||||
assert config.clients_upstream_dns == ["foobar.com", "google.com"]
|
||||
end
|
||||
|
||||
test "changes database config value when it did not exist", %{account: account} do
|
||||
config = get_account_config_by_account_id(account.id)
|
||||
attrs = %{devices_upstream_dns: ["foobar.com", "google.com"]}
|
||||
attrs = %{clients_upstream_dns: ["foobar.com", "google.com"]}
|
||||
assert {:ok, config} = update_config(config, attrs)
|
||||
assert config.devices_upstream_dns == attrs.devices_upstream_dns
|
||||
assert config.clients_upstream_dns == attrs.clients_upstream_dns
|
||||
end
|
||||
|
||||
test "changes database config value when it existed", %{account: account} do
|
||||
Fixtures.Config.upsert_configuration(account: account)
|
||||
|
||||
config = get_account_config_by_account_id(account.id)
|
||||
attrs = %{devices_upstream_dns: ["foobar.com", "google.com"]}
|
||||
attrs = %{clients_upstream_dns: ["foobar.com", "google.com"]}
|
||||
assert {:ok, config} = update_config(config, attrs)
|
||||
assert config.devices_upstream_dns == attrs.devices_upstream_dns
|
||||
assert config.clients_upstream_dns == attrs.clients_upstream_dns
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
defmodule Domain.DevicesTest do
|
||||
defmodule Domain.ClientsTest do
|
||||
use Domain.DataCase, async: true
|
||||
import Domain.Devices
|
||||
alias Domain.Devices
|
||||
import Domain.Clients
|
||||
alias Domain.Clients
|
||||
|
||||
setup do
|
||||
account = Fixtures.Accounts.create_account()
|
||||
@@ -29,11 +29,11 @@ defmodule Domain.DevicesTest do
|
||||
end
|
||||
|
||||
describe "count_by_account_id/0" do
|
||||
test "counts devices for an account", %{account: account} do
|
||||
Fixtures.Devices.create_device(account: account)
|
||||
Fixtures.Devices.create_device(account: account)
|
||||
Fixtures.Devices.create_device(account: account)
|
||||
Fixtures.Devices.create_device()
|
||||
test "counts clients for an account", %{account: account} do
|
||||
Fixtures.Clients.create_client(account: account)
|
||||
Fixtures.Clients.create_client(account: account)
|
||||
Fixtures.Clients.create_client(account: account)
|
||||
Fixtures.Clients.create_client()
|
||||
|
||||
assert count_by_account_id(account.id) == 3
|
||||
end
|
||||
@@ -44,258 +44,258 @@ defmodule Domain.DevicesTest do
|
||||
assert count_by_actor_id(Ecto.UUID.generate()) == 0
|
||||
end
|
||||
|
||||
test "returns count of devices for a actor" do
|
||||
device = Fixtures.Devices.create_device()
|
||||
assert count_by_actor_id(device.actor_id) == 1
|
||||
test "returns count of clients for a actor" do
|
||||
client = Fixtures.Clients.create_client()
|
||||
assert count_by_actor_id(client.actor_id) == 1
|
||||
end
|
||||
end
|
||||
|
||||
describe "fetch_device_by_id/2" do
|
||||
describe "fetch_client_by_id/2" do
|
||||
test "returns error when UUID is invalid", %{unprivileged_subject: subject} do
|
||||
assert fetch_device_by_id("foo", subject) == {:error, :not_found}
|
||||
assert fetch_client_by_id("foo", subject) == {:error, :not_found}
|
||||
end
|
||||
|
||||
test "does not return deleted devices", %{
|
||||
test "does not return deleted clients", %{
|
||||
unprivileged_actor: actor,
|
||||
unprivileged_subject: subject
|
||||
} do
|
||||
device =
|
||||
Fixtures.Devices.create_device(actor: actor)
|
||||
|> Fixtures.Devices.delete_device()
|
||||
client =
|
||||
Fixtures.Clients.create_client(actor: actor)
|
||||
|> Fixtures.Clients.delete_client()
|
||||
|
||||
assert fetch_device_by_id(device.id, subject) == {:error, :not_found}
|
||||
assert fetch_client_by_id(client.id, subject) == {:error, :not_found}
|
||||
end
|
||||
|
||||
test "returns device by id", %{unprivileged_actor: actor, unprivileged_subject: subject} do
|
||||
device = Fixtures.Devices.create_device(actor: actor)
|
||||
assert fetch_device_by_id(device.id, subject) == {:ok, device}
|
||||
test "returns client by id", %{unprivileged_actor: actor, unprivileged_subject: subject} do
|
||||
client = Fixtures.Clients.create_client(actor: actor)
|
||||
assert fetch_client_by_id(client.id, subject) == {:ok, client}
|
||||
end
|
||||
|
||||
test "returns device that belongs to another actor with manage permission", %{
|
||||
test "returns client that belongs to another actor with manage permission", %{
|
||||
account: account,
|
||||
unprivileged_subject: subject
|
||||
} do
|
||||
device = Fixtures.Devices.create_device(account: account)
|
||||
client = Fixtures.Clients.create_client(account: account)
|
||||
|
||||
subject =
|
||||
subject
|
||||
|> Fixtures.Auth.remove_permissions()
|
||||
|> Fixtures.Auth.add_permission(Devices.Authorizer.manage_devices_permission())
|
||||
|> Fixtures.Auth.add_permission(Clients.Authorizer.manage_clients_permission())
|
||||
|
||||
assert fetch_device_by_id(device.id, subject) == {:ok, device}
|
||||
assert fetch_client_by_id(client.id, subject) == {:ok, client}
|
||||
end
|
||||
|
||||
test "does not returns device that belongs to another account with manage permission", %{
|
||||
test "does not returns client that belongs to another account with manage permission", %{
|
||||
unprivileged_subject: subject
|
||||
} do
|
||||
device = Fixtures.Devices.create_device()
|
||||
client = Fixtures.Clients.create_client()
|
||||
|
||||
subject =
|
||||
subject
|
||||
|> Fixtures.Auth.remove_permissions()
|
||||
|> Fixtures.Auth.add_permission(Devices.Authorizer.manage_devices_permission())
|
||||
|> Fixtures.Auth.add_permission(Clients.Authorizer.manage_clients_permission())
|
||||
|
||||
assert fetch_device_by_id(device.id, subject) == {:error, :not_found}
|
||||
assert fetch_client_by_id(client.id, subject) == {:error, :not_found}
|
||||
end
|
||||
|
||||
test "does not return device that belongs to another actor with manage_own permission", %{
|
||||
test "does not return client that belongs to another actor with manage_own permission", %{
|
||||
unprivileged_subject: subject
|
||||
} do
|
||||
device = Fixtures.Devices.create_device()
|
||||
client = Fixtures.Clients.create_client()
|
||||
|
||||
subject =
|
||||
subject
|
||||
|> Fixtures.Auth.remove_permissions()
|
||||
|> Fixtures.Auth.add_permission(Devices.Authorizer.manage_own_devices_permission())
|
||||
|> Fixtures.Auth.add_permission(Clients.Authorizer.manage_own_clients_permission())
|
||||
|
||||
assert fetch_device_by_id(device.id, subject) == {:error, :not_found}
|
||||
assert fetch_client_by_id(client.id, subject) == {:error, :not_found}
|
||||
end
|
||||
|
||||
test "returns error when device does not exist", %{unprivileged_subject: subject} do
|
||||
assert fetch_device_by_id(Ecto.UUID.generate(), subject) ==
|
||||
test "returns error when client does not exist", %{unprivileged_subject: subject} do
|
||||
assert fetch_client_by_id(Ecto.UUID.generate(), subject) ==
|
||||
{:error, :not_found}
|
||||
end
|
||||
|
||||
test "returns error when subject has no permission to view devices", %{
|
||||
test "returns error when subject has no permission to view clients", %{
|
||||
unprivileged_subject: subject
|
||||
} do
|
||||
subject = Fixtures.Auth.remove_permissions(subject)
|
||||
|
||||
assert fetch_device_by_id(Ecto.UUID.generate(), subject) ==
|
||||
assert fetch_client_by_id(Ecto.UUID.generate(), subject) ==
|
||||
{:error,
|
||||
{:unauthorized,
|
||||
[
|
||||
missing_permissions: [
|
||||
{:one_of,
|
||||
[
|
||||
Devices.Authorizer.manage_devices_permission(),
|
||||
Devices.Authorizer.manage_own_devices_permission()
|
||||
Clients.Authorizer.manage_clients_permission(),
|
||||
Clients.Authorizer.manage_own_clients_permission()
|
||||
]}
|
||||
]
|
||||
]}}
|
||||
end
|
||||
end
|
||||
|
||||
describe "list_devices/1" do
|
||||
test "returns empty list when there are no devices", %{admin_subject: subject} do
|
||||
assert list_devices(subject) == {:ok, []}
|
||||
describe "list_clients/1" do
|
||||
test "returns empty list when there are no clients", %{admin_subject: subject} do
|
||||
assert list_clients(subject) == {:ok, []}
|
||||
end
|
||||
|
||||
test "does not list deleted devices", %{
|
||||
test "does not list deleted clients", %{
|
||||
unprivileged_actor: actor,
|
||||
unprivileged_subject: subject
|
||||
} do
|
||||
Fixtures.Devices.create_device(actor: actor)
|
||||
|> Fixtures.Devices.delete_device()
|
||||
Fixtures.Clients.create_client(actor: actor)
|
||||
|> Fixtures.Clients.delete_client()
|
||||
|
||||
assert list_devices(subject) == {:ok, []}
|
||||
assert list_clients(subject) == {:ok, []}
|
||||
end
|
||||
|
||||
test "does not list devices in other accounts", %{
|
||||
test "does not list clients in other accounts", %{
|
||||
unprivileged_subject: subject
|
||||
} do
|
||||
Fixtures.Devices.create_device()
|
||||
Fixtures.Clients.create_client()
|
||||
|
||||
assert list_devices(subject) == {:ok, []}
|
||||
assert list_clients(subject) == {:ok, []}
|
||||
end
|
||||
|
||||
test "shows all devices owned by a actor for unprivileged subject", %{
|
||||
test "shows all clients owned by a actor for unprivileged subject", %{
|
||||
unprivileged_actor: actor,
|
||||
admin_actor: other_actor,
|
||||
unprivileged_subject: subject
|
||||
} do
|
||||
device = Fixtures.Devices.create_device(actor: actor)
|
||||
Fixtures.Devices.create_device(actor: other_actor)
|
||||
client = Fixtures.Clients.create_client(actor: actor)
|
||||
Fixtures.Clients.create_client(actor: other_actor)
|
||||
|
||||
assert list_devices(subject) == {:ok, [device]}
|
||||
assert list_clients(subject) == {:ok, [client]}
|
||||
end
|
||||
|
||||
test "shows all devices for admin subject", %{
|
||||
test "shows all clients for admin subject", %{
|
||||
unprivileged_actor: other_actor,
|
||||
admin_actor: admin_actor,
|
||||
admin_subject: subject
|
||||
} do
|
||||
Fixtures.Devices.create_device(actor: admin_actor)
|
||||
Fixtures.Devices.create_device(actor: other_actor)
|
||||
Fixtures.Clients.create_client(actor: admin_actor)
|
||||
Fixtures.Clients.create_client(actor: other_actor)
|
||||
|
||||
assert {:ok, devices} = list_devices(subject)
|
||||
assert length(devices) == 2
|
||||
assert {:ok, clients} = list_clients(subject)
|
||||
assert length(clients) == 2
|
||||
end
|
||||
|
||||
test "returns error when subject has no permission to manage devices", %{
|
||||
test "returns error when subject has no permission to manage clients", %{
|
||||
unprivileged_subject: subject
|
||||
} do
|
||||
subject = Fixtures.Auth.remove_permissions(subject)
|
||||
|
||||
assert list_devices(subject) ==
|
||||
assert list_clients(subject) ==
|
||||
{:error,
|
||||
{:unauthorized,
|
||||
[
|
||||
missing_permissions: [
|
||||
{:one_of,
|
||||
[
|
||||
Devices.Authorizer.manage_devices_permission(),
|
||||
Devices.Authorizer.manage_own_devices_permission()
|
||||
Clients.Authorizer.manage_clients_permission(),
|
||||
Clients.Authorizer.manage_own_clients_permission()
|
||||
]}
|
||||
]
|
||||
]}}
|
||||
end
|
||||
end
|
||||
|
||||
describe "list_devices_by_actor_id/2" do
|
||||
test "returns empty list when there are no devices for a given actor", %{
|
||||
describe "list_clients_by_actor_id/2" do
|
||||
test "returns empty list when there are no clients for a given actor", %{
|
||||
admin_actor: actor,
|
||||
admin_subject: subject
|
||||
} do
|
||||
assert list_devices_by_actor_id(Ecto.UUID.generate(), subject) == {:ok, []}
|
||||
assert list_devices_by_actor_id(actor.id, subject) == {:ok, []}
|
||||
Fixtures.Devices.create_device()
|
||||
assert list_devices_by_actor_id(actor.id, subject) == {:ok, []}
|
||||
assert list_clients_by_actor_id(Ecto.UUID.generate(), subject) == {:ok, []}
|
||||
assert list_clients_by_actor_id(actor.id, subject) == {:ok, []}
|
||||
Fixtures.Clients.create_client()
|
||||
assert list_clients_by_actor_id(actor.id, subject) == {:ok, []}
|
||||
end
|
||||
|
||||
test "returns error when actor id is invalid", %{admin_subject: subject} do
|
||||
assert list_devices_by_actor_id("foo", subject) == {:error, :not_found}
|
||||
assert list_clients_by_actor_id("foo", subject) == {:error, :not_found}
|
||||
end
|
||||
|
||||
test "does not list deleted devices", %{
|
||||
test "does not list deleted clients", %{
|
||||
unprivileged_actor: actor,
|
||||
unprivileged_identity: identity,
|
||||
unprivileged_subject: subject
|
||||
} do
|
||||
Fixtures.Devices.create_device(identity: identity)
|
||||
|> Fixtures.Devices.delete_device()
|
||||
Fixtures.Clients.create_client(identity: identity)
|
||||
|> Fixtures.Clients.delete_client()
|
||||
|
||||
assert list_devices_by_actor_id(actor.id, subject) == {:ok, []}
|
||||
assert list_clients_by_actor_id(actor.id, subject) == {:ok, []}
|
||||
end
|
||||
|
||||
test "does not deleted devices for actors in other accounts", %{
|
||||
test "does not deleted clients for actors in other accounts", %{
|
||||
unprivileged_subject: unprivileged_subject,
|
||||
admin_subject: admin_subject
|
||||
} do
|
||||
actor = Fixtures.Actors.create_actor(type: :account_user)
|
||||
Fixtures.Devices.create_device(actor: actor)
|
||||
Fixtures.Clients.create_client(actor: actor)
|
||||
|
||||
assert list_devices_by_actor_id(actor.id, unprivileged_subject) == {:ok, []}
|
||||
assert list_devices_by_actor_id(actor.id, admin_subject) == {:ok, []}
|
||||
assert list_clients_by_actor_id(actor.id, unprivileged_subject) == {:ok, []}
|
||||
assert list_clients_by_actor_id(actor.id, admin_subject) == {:ok, []}
|
||||
end
|
||||
|
||||
test "shows only devices owned by a actor for unprivileged subject", %{
|
||||
test "shows only clients owned by a actor for unprivileged subject", %{
|
||||
unprivileged_actor: actor,
|
||||
admin_actor: other_actor,
|
||||
unprivileged_subject: subject
|
||||
} do
|
||||
device = Fixtures.Devices.create_device(actor: actor)
|
||||
Fixtures.Devices.create_device(actor: other_actor)
|
||||
client = Fixtures.Clients.create_client(actor: actor)
|
||||
Fixtures.Clients.create_client(actor: other_actor)
|
||||
|
||||
assert list_devices_by_actor_id(actor.id, subject) == {:ok, [device]}
|
||||
assert list_devices_by_actor_id(other_actor.id, subject) == {:ok, []}
|
||||
assert list_clients_by_actor_id(actor.id, subject) == {:ok, [client]}
|
||||
assert list_clients_by_actor_id(other_actor.id, subject) == {:ok, []}
|
||||
end
|
||||
|
||||
test "shows all devices owned by another actor for admin subject", %{
|
||||
test "shows all clients owned by another actor for admin subject", %{
|
||||
unprivileged_actor: other_actor,
|
||||
admin_actor: admin_actor,
|
||||
admin_subject: subject
|
||||
} do
|
||||
Fixtures.Devices.create_device(actor: admin_actor)
|
||||
Fixtures.Devices.create_device(actor: other_actor)
|
||||
Fixtures.Clients.create_client(actor: admin_actor)
|
||||
Fixtures.Clients.create_client(actor: other_actor)
|
||||
|
||||
assert {:ok, [_device]} = list_devices_by_actor_id(admin_actor.id, subject)
|
||||
assert {:ok, [_device]} = list_devices_by_actor_id(other_actor.id, subject)
|
||||
assert {:ok, [_client]} = list_clients_by_actor_id(admin_actor.id, subject)
|
||||
assert {:ok, [_client]} = list_clients_by_actor_id(other_actor.id, subject)
|
||||
end
|
||||
|
||||
test "returns error when subject has no permission to manage devices", %{
|
||||
test "returns error when subject has no permission to manage clients", %{
|
||||
unprivileged_subject: subject
|
||||
} do
|
||||
subject = Fixtures.Auth.remove_permissions(subject)
|
||||
|
||||
assert list_devices_by_actor_id(Ecto.UUID.generate(), subject) ==
|
||||
assert list_clients_by_actor_id(Ecto.UUID.generate(), subject) ==
|
||||
{:error,
|
||||
{:unauthorized,
|
||||
[
|
||||
missing_permissions: [
|
||||
{:one_of,
|
||||
[
|
||||
Devices.Authorizer.manage_devices_permission(),
|
||||
Devices.Authorizer.manage_own_devices_permission()
|
||||
Clients.Authorizer.manage_clients_permission(),
|
||||
Clients.Authorizer.manage_own_clients_permission()
|
||||
]}
|
||||
]
|
||||
]}}
|
||||
end
|
||||
end
|
||||
|
||||
describe "change_device/1" do
|
||||
describe "change_client/1" do
|
||||
test "returns changeset with given changes", %{admin_actor: actor} do
|
||||
device = Fixtures.Devices.create_device(actor: actor)
|
||||
device_attrs = Fixtures.Devices.device_attrs()
|
||||
client = Fixtures.Clients.create_client(actor: actor)
|
||||
client_attrs = Fixtures.Clients.client_attrs()
|
||||
|
||||
assert changeset = change_device(device, device_attrs)
|
||||
assert %Ecto.Changeset{data: %Domain.Devices.Device{}} = changeset
|
||||
assert changeset = change_client(client, client_attrs)
|
||||
assert %Ecto.Changeset{data: %Domain.Clients.Client{}} = changeset
|
||||
|
||||
assert changeset.changes == %{name: device_attrs.name}
|
||||
assert changeset.changes == %{name: client_attrs.name}
|
||||
end
|
||||
end
|
||||
|
||||
describe "upsert_device/2" do
|
||||
describe "upsert_client/2" do
|
||||
test "returns errors on invalid attrs", %{
|
||||
admin_subject: subject
|
||||
} do
|
||||
@@ -306,7 +306,7 @@ defmodule Domain.DevicesTest do
|
||||
ipv6: "fd01::10000"
|
||||
}
|
||||
|
||||
assert {:error, changeset} = upsert_device(attrs, subject)
|
||||
assert {:error, changeset} = upsert_client(attrs, subject)
|
||||
|
||||
assert errors_on(changeset) == %{
|
||||
public_key: ["should be 44 character(s)", "must be a base64-encoded string"],
|
||||
@@ -314,39 +314,39 @@ defmodule Domain.DevicesTest do
|
||||
}
|
||||
end
|
||||
|
||||
test "allows creating device with just required attributes", %{
|
||||
test "allows creating client with just required attributes", %{
|
||||
admin_actor: actor,
|
||||
admin_identity: identity,
|
||||
admin_subject: subject
|
||||
} do
|
||||
attrs =
|
||||
Fixtures.Devices.device_attrs()
|
||||
Fixtures.Clients.client_attrs()
|
||||
|> Map.delete(:name)
|
||||
|
||||
assert {:ok, device} = upsert_device(attrs, subject)
|
||||
assert {:ok, client} = upsert_client(attrs, subject)
|
||||
|
||||
assert device.name
|
||||
assert client.name
|
||||
|
||||
assert device.public_key == attrs.public_key
|
||||
assert client.public_key == attrs.public_key
|
||||
|
||||
assert device.actor_id == actor.id
|
||||
assert device.identity_id == identity.id
|
||||
assert device.account_id == actor.account_id
|
||||
assert client.actor_id == actor.id
|
||||
assert client.identity_id == identity.id
|
||||
assert client.account_id == actor.account_id
|
||||
|
||||
refute is_nil(device.ipv4)
|
||||
refute is_nil(device.ipv6)
|
||||
refute is_nil(client.ipv4)
|
||||
refute is_nil(client.ipv6)
|
||||
|
||||
assert device.last_seen_remote_ip == %Postgrex.INET{address: subject.context.remote_ip}
|
||||
assert device.last_seen_user_agent == subject.context.user_agent
|
||||
assert device.last_seen_version == "0.7.412"
|
||||
assert device.last_seen_at
|
||||
assert client.last_seen_remote_ip == %Postgrex.INET{address: subject.context.remote_ip}
|
||||
assert client.last_seen_user_agent == subject.context.user_agent
|
||||
assert client.last_seen_version == "0.7.412"
|
||||
assert client.last_seen_at
|
||||
end
|
||||
|
||||
test "updates device when it already exists", %{
|
||||
test "updates client when it already exists", %{
|
||||
admin_subject: subject
|
||||
} do
|
||||
device = Fixtures.Devices.create_device(subject: subject)
|
||||
attrs = Fixtures.Devices.device_attrs(external_id: device.external_id)
|
||||
client = Fixtures.Clients.create_client(subject: subject)
|
||||
attrs = Fixtures.Clients.client_attrs(external_id: client.external_id)
|
||||
|
||||
subject = %{
|
||||
subject
|
||||
@@ -357,40 +357,40 @@ defmodule Domain.DevicesTest do
|
||||
}
|
||||
}
|
||||
|
||||
assert {:ok, updated_device} = upsert_device(attrs, subject)
|
||||
assert {:ok, updated_client} = upsert_client(attrs, subject)
|
||||
|
||||
assert Repo.aggregate(Devices.Device, :count, :id) == 1
|
||||
assert Repo.aggregate(Clients.Client, :count, :id) == 1
|
||||
|
||||
assert updated_device.name
|
||||
assert updated_device.last_seen_remote_ip.address == subject.context.remote_ip
|
||||
assert updated_device.last_seen_remote_ip != device.last_seen_remote_ip
|
||||
assert updated_device.last_seen_user_agent == subject.context.user_agent
|
||||
assert updated_device.last_seen_user_agent != device.last_seen_user_agent
|
||||
assert updated_device.last_seen_version == "0.7.411"
|
||||
assert updated_device.public_key != device.public_key
|
||||
assert updated_device.public_key == attrs.public_key
|
||||
assert updated_client.name
|
||||
assert updated_client.last_seen_remote_ip.address == subject.context.remote_ip
|
||||
assert updated_client.last_seen_remote_ip != client.last_seen_remote_ip
|
||||
assert updated_client.last_seen_user_agent == subject.context.user_agent
|
||||
assert updated_client.last_seen_user_agent != client.last_seen_user_agent
|
||||
assert updated_client.last_seen_version == "0.7.411"
|
||||
assert updated_client.public_key != client.public_key
|
||||
assert updated_client.public_key == attrs.public_key
|
||||
|
||||
assert updated_device.actor_id == device.actor_id
|
||||
assert updated_device.identity_id == device.identity_id
|
||||
assert updated_device.ipv4 == device.ipv4
|
||||
assert updated_device.ipv6 == device.ipv6
|
||||
assert updated_device.last_seen_at
|
||||
assert updated_device.last_seen_at != device.last_seen_at
|
||||
assert updated_client.actor_id == client.actor_id
|
||||
assert updated_client.identity_id == client.identity_id
|
||||
assert updated_client.ipv4 == client.ipv4
|
||||
assert updated_client.ipv6 == client.ipv6
|
||||
assert updated_client.last_seen_at
|
||||
assert updated_client.last_seen_at != client.last_seen_at
|
||||
end
|
||||
|
||||
test "does not reserve additional addresses on update", %{
|
||||
admin_subject: subject
|
||||
} do
|
||||
device = Fixtures.Devices.create_device(subject: subject)
|
||||
client = Fixtures.Clients.create_client(subject: subject)
|
||||
|
||||
attrs =
|
||||
Fixtures.Devices.device_attrs(
|
||||
external_id: device.external_id,
|
||||
Fixtures.Clients.client_attrs(
|
||||
external_id: client.external_id,
|
||||
last_seen_user_agent: "iOS/12.5 (iPhone) connlib/0.7.411",
|
||||
last_seen_remote_ip: %Postgrex.INET{address: {100, 64, 100, 100}}
|
||||
)
|
||||
|
||||
assert {:ok, updated_device} = upsert_device(attrs, subject)
|
||||
assert {:ok, updated_client} = upsert_client(attrs, subject)
|
||||
|
||||
addresses =
|
||||
Domain.Network.Address
|
||||
@@ -400,26 +400,26 @@ defmodule Domain.DevicesTest do
|
||||
end)
|
||||
|
||||
assert length(addresses) == 2
|
||||
assert %{address: updated_device.ipv4, type: :ipv4} in addresses
|
||||
assert %{address: updated_device.ipv6, type: :ipv6} in addresses
|
||||
assert %{address: updated_client.ipv4, type: :ipv4} in addresses
|
||||
assert %{address: updated_client.ipv6, type: :ipv6} in addresses
|
||||
end
|
||||
|
||||
test "allows unprivileged actor to create a device for himself", %{
|
||||
test "allows unprivileged actor to create a client for himself", %{
|
||||
admin_subject: subject
|
||||
} do
|
||||
attrs =
|
||||
Fixtures.Devices.device_attrs()
|
||||
Fixtures.Clients.client_attrs()
|
||||
|> Map.delete(:name)
|
||||
|
||||
assert {:ok, _device} = upsert_device(attrs, subject)
|
||||
assert {:ok, _client} = upsert_client(attrs, subject)
|
||||
end
|
||||
|
||||
test "does not allow to reuse IP addresses", %{
|
||||
account: account,
|
||||
admin_subject: subject
|
||||
} do
|
||||
attrs = Fixtures.Devices.device_attrs(account: account)
|
||||
assert {:ok, device} = upsert_device(attrs, subject)
|
||||
attrs = Fixtures.Clients.client_attrs(account: account)
|
||||
assert {:ok, client} = upsert_client(attrs, subject)
|
||||
|
||||
addresses =
|
||||
Domain.Network.Address
|
||||
@@ -429,15 +429,15 @@ defmodule Domain.DevicesTest do
|
||||
end)
|
||||
|
||||
assert length(addresses) == 2
|
||||
assert %{address: device.ipv4, type: :ipv4} in addresses
|
||||
assert %{address: device.ipv6, type: :ipv6} in addresses
|
||||
assert %{address: client.ipv4, type: :ipv4} in addresses
|
||||
assert %{address: client.ipv6, type: :ipv6} in addresses
|
||||
|
||||
assert_raise Ecto.ConstraintError, fn ->
|
||||
Fixtures.Network.create_address(address: device.ipv4, account: account)
|
||||
Fixtures.Network.create_address(address: client.ipv4, account: account)
|
||||
end
|
||||
|
||||
assert_raise Ecto.ConstraintError, fn ->
|
||||
Fixtures.Network.create_address(address: device.ipv6, account: account)
|
||||
Fixtures.Network.create_address(address: client.ipv6, account: account)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -445,101 +445,101 @@ defmodule Domain.DevicesTest do
|
||||
account: account,
|
||||
admin_subject: subject
|
||||
} do
|
||||
attrs = Fixtures.Devices.device_attrs(account: account)
|
||||
assert {:ok, device} = upsert_device(attrs, subject)
|
||||
attrs = Fixtures.Clients.client_attrs(account: account)
|
||||
assert {:ok, client} = upsert_client(attrs, subject)
|
||||
|
||||
assert %Domain.Network.Address{} = Fixtures.Network.create_address(address: device.ipv4)
|
||||
assert %Domain.Network.Address{} = Fixtures.Network.create_address(address: device.ipv6)
|
||||
assert %Domain.Network.Address{} = Fixtures.Network.create_address(address: client.ipv4)
|
||||
assert %Domain.Network.Address{} = Fixtures.Network.create_address(address: client.ipv6)
|
||||
end
|
||||
|
||||
test "returns error when subject has no permission to create devices", %{
|
||||
test "returns error when subject has no permission to create clients", %{
|
||||
admin_subject: subject
|
||||
} do
|
||||
subject = Fixtures.Auth.remove_permissions(subject)
|
||||
|
||||
assert upsert_device(%{}, subject) ==
|
||||
assert upsert_client(%{}, subject) ==
|
||||
{:error,
|
||||
{:unauthorized,
|
||||
[missing_permissions: [Devices.Authorizer.manage_own_devices_permission()]]}}
|
||||
[missing_permissions: [Clients.Authorizer.manage_own_clients_permission()]]}}
|
||||
end
|
||||
end
|
||||
|
||||
describe "update_device/3" do
|
||||
test "allows admin actor to update own devices", %{admin_actor: actor, admin_subject: subject} do
|
||||
device = Fixtures.Devices.create_device(actor: actor)
|
||||
describe "update_client/3" do
|
||||
test "allows admin actor to update own clients", %{admin_actor: actor, admin_subject: subject} do
|
||||
client = Fixtures.Clients.create_client(actor: actor)
|
||||
attrs = %{name: "new name"}
|
||||
|
||||
assert {:ok, device} = update_device(device, attrs, subject)
|
||||
assert {:ok, client} = update_client(client, attrs, subject)
|
||||
|
||||
assert device.name == attrs.name
|
||||
assert client.name == attrs.name
|
||||
end
|
||||
|
||||
test "allows admin actor to update other actors devices", %{
|
||||
test "allows admin actor to update other actors clients", %{
|
||||
account: account,
|
||||
admin_subject: subject
|
||||
} do
|
||||
device = Fixtures.Devices.create_device(account: account)
|
||||
client = Fixtures.Clients.create_client(account: account)
|
||||
attrs = %{name: "new name"}
|
||||
|
||||
assert {:ok, device} = update_device(device, attrs, subject)
|
||||
assert {:ok, client} = update_client(client, attrs, subject)
|
||||
|
||||
assert device.name == attrs.name
|
||||
assert client.name == attrs.name
|
||||
end
|
||||
|
||||
test "allows unprivileged actor to update own devices", %{
|
||||
test "allows unprivileged actor to update own clients", %{
|
||||
unprivileged_actor: actor,
|
||||
unprivileged_subject: subject
|
||||
} do
|
||||
device = Fixtures.Devices.create_device(actor: actor)
|
||||
client = Fixtures.Clients.create_client(actor: actor)
|
||||
attrs = %{name: "new name"}
|
||||
|
||||
assert {:ok, device} = update_device(device, attrs, subject)
|
||||
assert {:ok, client} = update_client(client, attrs, subject)
|
||||
|
||||
assert device.name == attrs.name
|
||||
assert client.name == attrs.name
|
||||
end
|
||||
|
||||
test "does not allow unprivileged actor to update other actors devices", %{
|
||||
test "does not allow unprivileged actor to update other actors clients", %{
|
||||
account: account,
|
||||
unprivileged_subject: subject
|
||||
} do
|
||||
device = Fixtures.Devices.create_device(account: account)
|
||||
client = Fixtures.Clients.create_client(account: account)
|
||||
attrs = %{name: "new name"}
|
||||
|
||||
assert update_device(device, attrs, subject) ==
|
||||
assert update_client(client, attrs, subject) ==
|
||||
{:error,
|
||||
{:unauthorized,
|
||||
[missing_permissions: [Devices.Authorizer.manage_devices_permission()]]}}
|
||||
[missing_permissions: [Clients.Authorizer.manage_clients_permission()]]}}
|
||||
end
|
||||
|
||||
test "does not allow admin actor to update devices in other accounts", %{
|
||||
test "does not allow admin actor to update clients in other accounts", %{
|
||||
admin_subject: subject
|
||||
} do
|
||||
device = Fixtures.Devices.create_device()
|
||||
client = Fixtures.Clients.create_client()
|
||||
attrs = %{name: "new name"}
|
||||
|
||||
assert update_device(device, attrs, subject) == {:error, :not_found}
|
||||
assert update_client(client, attrs, subject) == {:error, :not_found}
|
||||
end
|
||||
|
||||
test "does not allow to reset required fields to empty values", %{
|
||||
admin_actor: actor,
|
||||
admin_subject: subject
|
||||
} do
|
||||
device = Fixtures.Devices.create_device(actor: actor)
|
||||
client = Fixtures.Clients.create_client(actor: actor)
|
||||
attrs = %{name: nil, public_key: nil}
|
||||
|
||||
assert {:error, changeset} = update_device(device, attrs, subject)
|
||||
assert {:error, changeset} = update_client(client, attrs, subject)
|
||||
|
||||
assert errors_on(changeset) == %{name: ["can't be blank"]}
|
||||
end
|
||||
|
||||
test "returns error on invalid attrs", %{admin_actor: actor, admin_subject: subject} do
|
||||
device = Fixtures.Devices.create_device(actor: actor)
|
||||
client = Fixtures.Clients.create_client(actor: actor)
|
||||
|
||||
attrs = %{
|
||||
name: String.duplicate("a", 256)
|
||||
}
|
||||
|
||||
assert {:error, changeset} = update_device(device, attrs, subject)
|
||||
assert {:error, changeset} = update_client(client, attrs, subject)
|
||||
|
||||
assert errors_on(changeset) == %{
|
||||
name: ["should be at most 255 character(s)"]
|
||||
@@ -550,145 +550,145 @@ defmodule Domain.DevicesTest do
|
||||
admin_actor: actor,
|
||||
admin_subject: subject
|
||||
} do
|
||||
device = Fixtures.Devices.create_device(actor: actor)
|
||||
client = Fixtures.Clients.create_client(actor: actor)
|
||||
|
||||
fields = Devices.Device.__schema__(:fields) -- [:name]
|
||||
fields = Clients.Client.__schema__(:fields) -- [:name]
|
||||
value = -1
|
||||
|
||||
for field <- fields do
|
||||
assert {:ok, updated_device} = update_device(device, %{field => value}, subject)
|
||||
assert updated_device == device
|
||||
assert {:ok, updated_client} = update_client(client, %{field => value}, subject)
|
||||
assert updated_client == client
|
||||
end
|
||||
end
|
||||
|
||||
test "returns error when subject has no permission to update devices", %{
|
||||
test "returns error when subject has no permission to update clients", %{
|
||||
admin_actor: actor,
|
||||
admin_subject: subject
|
||||
} do
|
||||
device = Fixtures.Devices.create_device(actor: actor)
|
||||
client = Fixtures.Clients.create_client(actor: actor)
|
||||
|
||||
subject = Fixtures.Auth.remove_permissions(subject)
|
||||
|
||||
assert update_device(device, %{}, subject) ==
|
||||
assert update_client(client, %{}, subject) ==
|
||||
{:error,
|
||||
{:unauthorized,
|
||||
[missing_permissions: [Devices.Authorizer.manage_own_devices_permission()]]}}
|
||||
[missing_permissions: [Clients.Authorizer.manage_own_clients_permission()]]}}
|
||||
|
||||
device = Fixtures.Devices.create_device()
|
||||
client = Fixtures.Clients.create_client()
|
||||
|
||||
assert update_device(device, %{}, subject) ==
|
||||
assert update_client(client, %{}, subject) ==
|
||||
{:error,
|
||||
{:unauthorized,
|
||||
[missing_permissions: [Devices.Authorizer.manage_devices_permission()]]}}
|
||||
[missing_permissions: [Clients.Authorizer.manage_clients_permission()]]}}
|
||||
end
|
||||
end
|
||||
|
||||
describe "delete_device/2" do
|
||||
describe "delete_client/2" do
|
||||
test "returns error on state conflict", %{admin_actor: actor, admin_subject: subject} do
|
||||
device = Fixtures.Devices.create_device(actor: actor)
|
||||
client = Fixtures.Clients.create_client(actor: actor)
|
||||
|
||||
assert {:ok, deleted} = delete_device(device, subject)
|
||||
assert delete_device(deleted, subject) == {:error, :not_found}
|
||||
assert delete_device(device, subject) == {:error, :not_found}
|
||||
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 devices", %{admin_actor: actor, admin_subject: subject} do
|
||||
device = Fixtures.Devices.create_device(actor: actor)
|
||||
test "admin can delete own clients", %{admin_actor: actor, admin_subject: subject} do
|
||||
client = Fixtures.Clients.create_client(actor: actor)
|
||||
|
||||
assert {:ok, deleted} = delete_device(device, subject)
|
||||
assert {:ok, deleted} = delete_client(client, subject)
|
||||
assert deleted.deleted_at
|
||||
end
|
||||
|
||||
test "admin can delete other people devices", %{
|
||||
test "admin can delete other people clients", %{
|
||||
unprivileged_actor: actor,
|
||||
admin_subject: subject
|
||||
} do
|
||||
device = Fixtures.Devices.create_device(actor: actor)
|
||||
client = Fixtures.Clients.create_client(actor: actor)
|
||||
|
||||
assert {:ok, deleted} = delete_device(device, subject)
|
||||
assert {:ok, deleted} = delete_client(client, subject)
|
||||
assert deleted.deleted_at
|
||||
end
|
||||
|
||||
test "admin can not delete devices in other accounts", %{
|
||||
test "admin can not delete clients in other accounts", %{
|
||||
admin_subject: subject
|
||||
} do
|
||||
device = Fixtures.Devices.create_device()
|
||||
client = Fixtures.Clients.create_client()
|
||||
|
||||
assert delete_device(device, subject) == {:error, :not_found}
|
||||
assert delete_client(client, subject) == {:error, :not_found}
|
||||
end
|
||||
|
||||
test "unprivileged can delete own devices", %{
|
||||
test "unprivileged can delete own clients", %{
|
||||
account: account,
|
||||
unprivileged_actor: actor,
|
||||
unprivileged_subject: subject
|
||||
} do
|
||||
device = Fixtures.Devices.create_device(account: account, actor: actor)
|
||||
client = Fixtures.Clients.create_client(account: account, actor: actor)
|
||||
|
||||
assert {:ok, deleted} = delete_device(device, subject)
|
||||
assert {:ok, deleted} = delete_client(client, subject)
|
||||
assert deleted.deleted_at
|
||||
end
|
||||
|
||||
test "unprivileged can not delete other people devices", %{
|
||||
test "unprivileged can not delete other people clients", %{
|
||||
account: account,
|
||||
unprivileged_subject: subject
|
||||
} do
|
||||
device = Fixtures.Devices.create_device()
|
||||
client = Fixtures.Clients.create_client()
|
||||
|
||||
assert delete_device(device, subject) ==
|
||||
assert delete_client(client, subject) ==
|
||||
{:error,
|
||||
{:unauthorized,
|
||||
[missing_permissions: [Devices.Authorizer.manage_devices_permission()]]}}
|
||||
[missing_permissions: [Clients.Authorizer.manage_clients_permission()]]}}
|
||||
|
||||
device = Fixtures.Devices.create_device(account: account)
|
||||
client = Fixtures.Clients.create_client(account: account)
|
||||
|
||||
assert delete_device(device, subject) ==
|
||||
assert delete_client(client, subject) ==
|
||||
{:error,
|
||||
{:unauthorized,
|
||||
[missing_permissions: [Devices.Authorizer.manage_devices_permission()]]}}
|
||||
[missing_permissions: [Clients.Authorizer.manage_clients_permission()]]}}
|
||||
|
||||
assert Repo.aggregate(Devices.Device, :count) == 2
|
||||
assert Repo.aggregate(Clients.Client, :count) == 2
|
||||
end
|
||||
|
||||
test "returns error when subject has no permission to delete devices", %{
|
||||
test "returns error when subject has no permission to delete clients", %{
|
||||
admin_actor: actor,
|
||||
admin_subject: subject
|
||||
} do
|
||||
device = Fixtures.Devices.create_device(actor: actor)
|
||||
client = Fixtures.Clients.create_client(actor: actor)
|
||||
|
||||
subject = Fixtures.Auth.remove_permissions(subject)
|
||||
|
||||
assert delete_device(device, subject) ==
|
||||
assert delete_client(client, subject) ==
|
||||
{:error,
|
||||
{:unauthorized,
|
||||
[missing_permissions: [Devices.Authorizer.manage_own_devices_permission()]]}}
|
||||
[missing_permissions: [Clients.Authorizer.manage_own_clients_permission()]]}}
|
||||
|
||||
device = Fixtures.Devices.create_device()
|
||||
client = Fixtures.Clients.create_client()
|
||||
|
||||
assert delete_device(device, subject) ==
|
||||
assert delete_client(client, subject) ==
|
||||
{:error,
|
||||
{:unauthorized,
|
||||
[missing_permissions: [Devices.Authorizer.manage_devices_permission()]]}}
|
||||
[missing_permissions: [Clients.Authorizer.manage_clients_permission()]]}}
|
||||
end
|
||||
end
|
||||
|
||||
describe "delete_actor_devices/1" do
|
||||
test "removes all devices that belong to an actor" do
|
||||
describe "delete_actor_clients/1" do
|
||||
test "removes all clients that belong to an actor" do
|
||||
actor = Fixtures.Actors.create_actor()
|
||||
Fixtures.Devices.create_device(actor: actor)
|
||||
Fixtures.Devices.create_device(actor: actor)
|
||||
Fixtures.Devices.create_device(actor: actor)
|
||||
Fixtures.Clients.create_client(actor: actor)
|
||||
Fixtures.Clients.create_client(actor: actor)
|
||||
Fixtures.Clients.create_client(actor: actor)
|
||||
|
||||
assert Repo.aggregate(Devices.Device.Query.all(), :count) == 3
|
||||
assert delete_actor_devices(actor) == :ok
|
||||
assert Repo.aggregate(Devices.Device.Query.all(), :count) == 0
|
||||
assert Repo.aggregate(Clients.Client.Query.all(), :count) == 3
|
||||
assert delete_actor_clients(actor) == :ok
|
||||
assert Repo.aggregate(Clients.Client.Query.all(), :count) == 0
|
||||
end
|
||||
|
||||
test "does not remove devices that belong to another actor" do
|
||||
test "does not remove clients that belong to another actor" do
|
||||
actor = Fixtures.Actors.create_actor()
|
||||
Fixtures.Devices.create_device()
|
||||
Fixtures.Clients.create_client()
|
||||
|
||||
assert delete_actor_devices(actor) == :ok
|
||||
assert Repo.aggregate(Devices.Device.Query.all(), :count) == 1
|
||||
assert delete_actor_clients(actor) == :ok
|
||||
assert Repo.aggregate(Clients.Client.Query.all(), :count) == 1
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -37,7 +37,7 @@ defmodule Domain.Network.Address.QueryTest do
|
||||
assert Repo.one(queryable) == %Postgrex.INET{address: {10, 3, 3, 3}}
|
||||
end
|
||||
|
||||
test "forward scans available address after offset it it's assigned to a device", %{
|
||||
test "forward scans available address after offset it it's assigned to a client", %{
|
||||
account: account
|
||||
} do
|
||||
cidr = string_to_cidr("10.3.4.0/29")
|
||||
@@ -141,7 +141,7 @@ defmodule Domain.Network.Address.QueryTest do
|
||||
assert Repo.one(queryable) == %Postgrex.INET{address: {64_768, 0, 0, 0, 0, 0, 64, 0}}
|
||||
end
|
||||
|
||||
test "works when netmask allows a large number of devices", %{account: account} do
|
||||
test "works when netmask allows a large number of clients", %{account: account} do
|
||||
cidr = string_to_cidr("fd00::/70")
|
||||
offset = 9_223_372_036_854_775_807
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ defmodule Domain.Fixtures.Config do
|
||||
|
||||
def configuration_attrs(attrs \\ %{}) do
|
||||
Enum.into(attrs, %{
|
||||
devices_upstream_dns: ["1.1.1.1"]
|
||||
clients_upstream_dns: ["1.1.1.1"]
|
||||
})
|
||||
end
|
||||
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
defmodule Domain.Fixtures.Devices do
|
||||
defmodule Domain.Fixtures.Clients do
|
||||
use Domain.Fixture
|
||||
alias Domain.Devices
|
||||
alias Domain.Clients
|
||||
|
||||
def device_attrs(attrs \\ %{}) do
|
||||
def client_attrs(attrs \\ %{}) do
|
||||
Enum.into(attrs, %{
|
||||
external_id: Ecto.UUID.generate(),
|
||||
name: "device-#{unique_integer()}",
|
||||
name: "client-#{unique_integer()}",
|
||||
public_key: unique_public_key()
|
||||
})
|
||||
end
|
||||
|
||||
def create_device(attrs \\ %{}) do
|
||||
attrs = device_attrs(attrs)
|
||||
def create_client(attrs \\ %{}) do
|
||||
attrs = client_attrs(attrs)
|
||||
|
||||
{account, attrs} =
|
||||
pop_assoc_fixture(attrs, :account, fn assoc_attrs ->
|
||||
@@ -47,20 +47,20 @@ defmodule Domain.Fixtures.Devices do
|
||||
|> Fixtures.Auth.create_subject()
|
||||
end)
|
||||
|
||||
{:ok, device} = Devices.upsert_device(attrs, subject)
|
||||
%{device | online?: false}
|
||||
{:ok, client} = Clients.upsert_client(attrs, subject)
|
||||
%{client | online?: false}
|
||||
end
|
||||
|
||||
def delete_device(device) do
|
||||
device = Repo.preload(device, :account)
|
||||
def delete_client(client) do
|
||||
client = Repo.preload(client, :account)
|
||||
|
||||
subject =
|
||||
Fixtures.Auth.create_subject(
|
||||
account: device.account,
|
||||
account: client.account,
|
||||
actor: [type: :account_admin_user]
|
||||
)
|
||||
|
||||
{:ok, device} = Devices.delete_device(device, subject)
|
||||
device
|
||||
{:ok, client} = Clients.delete_client(client, subject)
|
||||
client
|
||||
end
|
||||
end
|
||||
|
||||
@@ -17,7 +17,7 @@ defmodule Domain.Mocks.OpenIDConnect do
|
||||
"issuer" => "#{endpoint}/",
|
||||
"authorization_endpoint" => "#{endpoint}/authorize",
|
||||
"token_endpoint" => "#{endpoint}/oauth/token",
|
||||
"device_authorization_endpoint" => "#{endpoint}/oauth/device/code",
|
||||
"client_authorization_endpoint" => "#{endpoint}/oauth/client/code",
|
||||
"userinfo_endpoint" => "#{endpoint}/userinfo",
|
||||
"mfa_challenge_endpoint" => "#{endpoint}/mfa/challenge",
|
||||
"jwks_uri" => "#{endpoint}/.well-known/jwks.json",
|
||||
|
||||
@@ -11,8 +11,8 @@
|
||||
<.sidebar_item navigate={~p"/#{@account}/groups"} icon="hero-user-group-solid">
|
||||
Groups
|
||||
</.sidebar_item>
|
||||
<.sidebar_item navigate={~p"/#{@account}/devices"} icon="hero-device-phone-mobile-solid">
|
||||
Devices
|
||||
<.sidebar_item navigate={~p"/#{@account}/clients"} icon="hero-client-phone-mobile-solid">
|
||||
Clients
|
||||
</.sidebar_item>
|
||||
|
||||
<.sidebar_item
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta name="viewport" content="width=client-width, initial-scale=1" />
|
||||
<!-- iOS App Favicon -->
|
||||
<link rel="apple-touch-icon" sizes="180x180" href={~p"/images/apple-touch-icon.png"} />
|
||||
<link rel="icon" type="image/png" sizes="32x32" href={~p"/images/favicon-32x32.png"} />
|
||||
|
||||
@@ -236,7 +236,7 @@ defmodule Web.TableComponents do
|
||||
Renders a table with 2 columns and generic styling.
|
||||
|
||||
The component will likely be used when displaying the properties of an
|
||||
individual resource (e.g. Gateway, Resource, Device, etc...)
|
||||
individual resource (e.g. Gateway, Resource, Client, etc...)
|
||||
|
||||
The component renders a table that is meant to be viewed vertically, with
|
||||
the first column being the label and the second column being the value.
|
||||
@@ -277,7 +277,7 @@ defmodule Web.TableComponents do
|
||||
the header and the second column will be the value.
|
||||
|
||||
The component will likely be used when displaying the properties of an
|
||||
individual resource (e.g. Gateway, Resource, Device, etc...)
|
||||
individual resource (e.g. Gateway, Resource, Client, etc...)
|
||||
|
||||
This component is intended to be used with the `vertical_table` component.
|
||||
|
||||
|
||||
@@ -1,28 +1,28 @@
|
||||
defmodule Web.Devices.Edit do
|
||||
defmodule Web.Clients.Edit do
|
||||
use Web, :live_view
|
||||
alias Domain.Devices
|
||||
alias Domain.Clients
|
||||
|
||||
def mount(%{"id" => id}, _session, socket) do
|
||||
with {:ok, device} <- Devices.fetch_device_by_id(id, socket.assigns.subject) do
|
||||
changeset = Devices.change_device(device)
|
||||
{:ok, assign(socket, device: device, form: to_form(changeset))}
|
||||
with {:ok, client} <- Clients.fetch_client_by_id(id, socket.assigns.subject) do
|
||||
changeset = Clients.change_client(client)
|
||||
{:ok, assign(socket, client: client, form: to_form(changeset))}
|
||||
else
|
||||
{:error, _reason} -> raise Web.LiveErrors.NotFoundError
|
||||
end
|
||||
end
|
||||
|
||||
def handle_event("change", %{"device" => attrs}, socket) do
|
||||
def handle_event("change", %{"client" => attrs}, socket) do
|
||||
changeset =
|
||||
Devices.change_device(socket.assigns.device, attrs)
|
||||
Clients.change_client(socket.assigns.client, attrs)
|
||||
|> Map.put(:action, :insert)
|
||||
|
||||
{:noreply, assign(socket, form: to_form(changeset))}
|
||||
end
|
||||
|
||||
def handle_event("submit", %{"device" => attrs}, socket) do
|
||||
with {:ok, device} <-
|
||||
Devices.update_device(socket.assigns.device, attrs, socket.assigns.subject) do
|
||||
socket = redirect(socket, to: ~p"/#{socket.assigns.account}/devices/#{device}")
|
||||
def handle_event("submit", %{"client" => attrs}, socket) do
|
||||
with {:ok, client} <-
|
||||
Clients.update_client(socket.assigns.client, attrs, socket.assigns.subject) do
|
||||
socket = redirect(socket, to: ~p"/#{socket.assigns.account}/clients/#{client}")
|
||||
{:noreply, socket}
|
||||
else
|
||||
{:error, changeset} ->
|
||||
@@ -33,23 +33,23 @@ defmodule Web.Devices.Edit do
|
||||
def render(assigns) do
|
||||
~H"""
|
||||
<.breadcrumbs home_path={~p"/#{@account}/dashboard"}>
|
||||
<.breadcrumb path={~p"/#{@account}/devices"}>Devices</.breadcrumb>
|
||||
<.breadcrumb path={~p"/#{@account}/devices/#{@device}"}>
|
||||
<%= @device.name %>
|
||||
<.breadcrumb path={~p"/#{@account}/clients"}>Clients</.breadcrumb>
|
||||
<.breadcrumb path={~p"/#{@account}/clients/#{@client}"}>
|
||||
<%= @client.name %>
|
||||
</.breadcrumb>
|
||||
<.breadcrumb path={~p"/#{@account}/devices/#{@device}/edit"}>
|
||||
<.breadcrumb path={~p"/#{@account}/clients/#{@client}/edit"}>
|
||||
Edit
|
||||
</.breadcrumb>
|
||||
</.breadcrumbs>
|
||||
<.header>
|
||||
<:title>
|
||||
Editing device <code>Engineering</code>
|
||||
Editing client <code>Engineering</code>
|
||||
</:title>
|
||||
</.header>
|
||||
<!-- Update Group -->
|
||||
<section class="bg-white dark:bg-gray-900">
|
||||
<div class="max-w-2xl px-4 py-8 mx-auto lg:py-16">
|
||||
<h2 class="mb-4 text-xl font-bold text-gray-900 dark:text-white">Edit device details</h2>
|
||||
<h2 class="mb-4 text-xl font-bold text-gray-900 dark:text-white">Edit client details</h2>
|
||||
<.form for={@form} phx-change={:change} phx-submit={:submit}>
|
||||
<div class="grid gap-4 mb-4 sm:grid-cols-1 sm:gap-6 sm:mb-6">
|
||||
<div>
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
defmodule Web.Devices.Index do
|
||||
defmodule Web.Clients.Index do
|
||||
use Web, :live_view
|
||||
|
||||
alias Domain.Devices
|
||||
alias Domain.Clients
|
||||
|
||||
def mount(_params, _session, socket) do
|
||||
with {:ok, devices} <- Devices.list_devices(socket.assigns.subject, preload: :actor) do
|
||||
{:ok, assign(socket, devices: devices)}
|
||||
with {:ok, clients} <- Clients.list_clients(socket.assigns.subject, preload: :actor) do
|
||||
{:ok, assign(socket, clients: clients)}
|
||||
else
|
||||
{:error, _reason} -> raise Web.LiveErrors.NotFoundError
|
||||
end
|
||||
@@ -14,45 +14,45 @@ defmodule Web.Devices.Index do
|
||||
def render(assigns) do
|
||||
~H"""
|
||||
<.breadcrumbs home_path={~p"/#{@account}/dashboard"}>
|
||||
<.breadcrumb path={~p"/#{@account}/devices"}>Devices</.breadcrumb>
|
||||
<.breadcrumb path={~p"/#{@account}/clients"}>Clients</.breadcrumb>
|
||||
</.breadcrumbs>
|
||||
<.header>
|
||||
<:title>
|
||||
All devices
|
||||
All clients
|
||||
</:title>
|
||||
</.header>
|
||||
<!-- Devices Table -->
|
||||
<!-- Clients Table -->
|
||||
<div class="bg-white dark:bg-gray-800 overflow-hidden">
|
||||
<div :if={Enum.empty?(@devices)} class="text-center align-middle pb-8 pt-4">
|
||||
<h3 class="mt-2 text-lg font-semibold text-gray-900">There are no devices to display.</h3>
|
||||
<div :if={Enum.empty?(@clients)} class="text-center align-middle pb-8 pt-4">
|
||||
<h3 class="mt-2 text-lg font-semibold text-gray-900">There are no clients to display.</h3>
|
||||
|
||||
<div class="mt-6">
|
||||
Devices are created automatically when user connects to a Resource.
|
||||
Clients are created automatically when user connects to a Resource.
|
||||
</div>
|
||||
</div>
|
||||
<!--<.resource_filter />-->
|
||||
<.table :if={not Enum.empty?(@devices)} id="devices" rows={@devices} row_id={&"device-#{&1.id}"}>
|
||||
<:col :let={device} label="NAME" sortable="true">
|
||||
<.table :if={not Enum.empty?(@clients)} id="clients" rows={@clients} row_id={&"client-#{&1.id}"}>
|
||||
<:col :let={client} label="NAME" sortable="true">
|
||||
<.link
|
||||
navigate={~p"/#{@account}/devices/#{device.id}"}
|
||||
navigate={~p"/#{@account}/clients/#{client.id}"}
|
||||
class="font-medium text-blue-600 dark:text-blue-500 hover:underline"
|
||||
>
|
||||
<%= device.name %>
|
||||
<%= client.name %>
|
||||
</.link>
|
||||
</:col>
|
||||
<:col :let={device} label="USER" sortable="true">
|
||||
<:col :let={client} label="USER" sortable="true">
|
||||
<.link
|
||||
navigate={~p"/#{@account}/actors/#{device.actor.id}"}
|
||||
navigate={~p"/#{@account}/actors/#{client.actor.id}"}
|
||||
class="font-medium text-blue-600 dark:text-blue-500 hover:underline"
|
||||
>
|
||||
<%= device.actor.name %>
|
||||
<%= client.actor.name %>
|
||||
</.link>
|
||||
</:col>
|
||||
<:col :let={device} label="STATUS" sortable="true">
|
||||
<.connection_status schema={device} />
|
||||
<:col :let={client} label="STATUS" sortable="true">
|
||||
<.connection_status schema={client} />
|
||||
</:col>
|
||||
</.table>
|
||||
<!--<.paginator page={3} total_pages={100} collection_base_path={~p"/#{@account}/devices"} />-->
|
||||
<!--<.paginator page={3} total_pages={100} collection_base_path={~p"/#{@account}/clients"} />-->
|
||||
</div>
|
||||
"""
|
||||
end
|
||||
|
||||
@@ -1,81 +1,81 @@
|
||||
defmodule Web.Devices.Show do
|
||||
defmodule Web.Clients.Show do
|
||||
use Web, :live_view
|
||||
|
||||
alias Domain.Devices
|
||||
alias Domain.Clients
|
||||
|
||||
def mount(%{"id" => id}, _session, socket) do
|
||||
with {:ok, device} <- Devices.fetch_device_by_id(id, socket.assigns.subject, preload: :actor) do
|
||||
{:ok, assign(socket, device: device)}
|
||||
with {:ok, client} <- Clients.fetch_client_by_id(id, socket.assigns.subject, preload: :actor) do
|
||||
{:ok, assign(socket, client: client)}
|
||||
else
|
||||
{:error, _reason} -> raise Web.LiveErrors.NotFoundError
|
||||
end
|
||||
end
|
||||
|
||||
def handle_event("delete", _params, socket) do
|
||||
{:ok, _device} = Devices.delete_device(socket.assigns.device, socket.assigns.subject)
|
||||
{:noreply, redirect(socket, to: ~p"/#{socket.assigns.account}/devices")}
|
||||
{:ok, _client} = Clients.delete_client(socket.assigns.client, socket.assigns.subject)
|
||||
{:noreply, redirect(socket, to: ~p"/#{socket.assigns.account}/clients")}
|
||||
end
|
||||
|
||||
def render(assigns) do
|
||||
~H"""
|
||||
<.breadcrumbs home_path={~p"/#{@account}/dashboard"}>
|
||||
<.breadcrumb path={~p"/#{@account}/devices"}>Devices</.breadcrumb>
|
||||
<.breadcrumb path={~p"/#{@account}/devices/#{@device.id}"}>
|
||||
<%= @device.name %>
|
||||
<.breadcrumb path={~p"/#{@account}/clients"}>Clients</.breadcrumb>
|
||||
<.breadcrumb path={~p"/#{@account}/clients/#{@client.id}"}>
|
||||
<%= @client.name %>
|
||||
</.breadcrumb>
|
||||
</.breadcrumbs>
|
||||
|
||||
<.header>
|
||||
<:title>
|
||||
Device Details
|
||||
Client Details
|
||||
</:title>
|
||||
<:actions>
|
||||
<.edit_button navigate={~p"/#{@account}/devices/#{@device}/edit"}>
|
||||
Edit Device
|
||||
<.edit_button navigate={~p"/#{@account}/clients/#{@client}/edit"}>
|
||||
Edit Client
|
||||
</.edit_button>
|
||||
</:actions>
|
||||
</.header>
|
||||
<!-- Device Details -->
|
||||
<div id="device" class="bg-white dark:bg-gray-800 overflow-hidden">
|
||||
<!-- Client Details -->
|
||||
<div id="client" class="bg-white dark:bg-gray-800 overflow-hidden">
|
||||
<.vertical_table>
|
||||
<.vertical_table_row>
|
||||
<:label>Identifier</:label>
|
||||
<:value><%= @device.id %></:value>
|
||||
<:value><%= @client.id %></:value>
|
||||
</.vertical_table_row>
|
||||
<.vertical_table_row>
|
||||
<:label>Name</:label>
|
||||
<:value><%= @device.name %></:value>
|
||||
<:value><%= @client.name %></:value>
|
||||
</.vertical_table_row>
|
||||
<.vertical_table_row>
|
||||
<:label>Owner</:label>
|
||||
<:value>
|
||||
<.link
|
||||
navigate={~p"/#{@account}/actors/#{@device.actor.id}"}
|
||||
navigate={~p"/#{@account}/actors/#{@client.actor.id}"}
|
||||
class="font-medium text-blue-600 dark:text-blue-500 hover:underline"
|
||||
>
|
||||
<%= @device.actor.name %>
|
||||
<%= @client.actor.name %>
|
||||
</.link>
|
||||
</:value>
|
||||
</.vertical_table_row>
|
||||
<.vertical_table_row>
|
||||
<:label>Created</:label>
|
||||
<:value>
|
||||
<.relative_datetime datetime={@device.inserted_at} />
|
||||
<.relative_datetime datetime={@client.inserted_at} />
|
||||
</:value>
|
||||
</.vertical_table_row>
|
||||
<.vertical_table_row>
|
||||
<:label>Last Seen</:label>
|
||||
<:value>
|
||||
<.relative_datetime datetime={@device.last_seen_at} />
|
||||
<.relative_datetime datetime={@client.last_seen_at} />
|
||||
</:value>
|
||||
</.vertical_table_row>
|
||||
<.vertical_table_row>
|
||||
<:label>Remote IPv4</:label>
|
||||
<:value><code><%= @device.ipv4 %></code></:value>
|
||||
<:value><code><%= @client.ipv4 %></code></:value>
|
||||
</.vertical_table_row>
|
||||
<.vertical_table_row>
|
||||
<:label>Remote IPv6</:label>
|
||||
<:value><code><%= @device.ipv6 %></code></:value>
|
||||
<:value><code><%= @client.ipv6 %></code></:value>
|
||||
</.vertical_table_row>
|
||||
<.vertical_table_row>
|
||||
<:label>Transfer</:label>
|
||||
@@ -83,11 +83,11 @@ defmodule Web.Devices.Show do
|
||||
</.vertical_table_row>
|
||||
<.vertical_table_row>
|
||||
<:label>Client Version</:label>
|
||||
<:value><%= @device.last_seen_version %></:value>
|
||||
<:value><%= @client.last_seen_version %></:value>
|
||||
</.vertical_table_row>
|
||||
<.vertical_table_row>
|
||||
<:label>User Agent</:label>
|
||||
<:value><%= @device.last_seen_user_agent %></:value>
|
||||
<:value><%= @client.last_seen_user_agent %></:value>
|
||||
</.vertical_table_row>
|
||||
</.vertical_table>
|
||||
</div>
|
||||
@@ -100,11 +100,11 @@ defmodule Web.Devices.Show do
|
||||
<.delete_button
|
||||
phx-click="delete"
|
||||
data-confirm={
|
||||
"Are you sure want to delete this device? " <>
|
||||
"Are you sure want to delete this client? " <>
|
||||
"User still will be able to create a new one by reconnecting to the Firezone."
|
||||
}
|
||||
>
|
||||
Delete Device
|
||||
Delete Client
|
||||
</.delete_button>
|
||||
</:actions>
|
||||
</.header>
|
||||
|
||||
@@ -103,7 +103,7 @@ defmodule Web.Policies.Show do
|
||||
Authorized at
|
||||
</th>
|
||||
<th scope="col" class="px-6 py-3">
|
||||
Device
|
||||
Client
|
||||
</th>
|
||||
<th scope="col" class="px-6 py-3">
|
||||
User
|
||||
@@ -121,7 +121,7 @@ defmodule Web.Policies.Show do
|
||||
<td class="px-6 py-4">
|
||||
<.link
|
||||
class="text-blue-600 dark:text-blue-500 hover:underline"
|
||||
navigate={~p"/#{@account}/devices/DF43E951-7DFB-4921-8F7F-BF0F8D31FA89"}
|
||||
navigate={~p"/#{@account}/clients/DF43E951-7DFB-4921-8F7F-BF0F8D31FA89"}
|
||||
>
|
||||
2425BD07A38D
|
||||
</.link>
|
||||
|
||||
@@ -12,7 +12,7 @@ defmodule Web.Settings.DNS do
|
||||
</:title>
|
||||
</.header>
|
||||
<p class="ml-4 mb-4 font-medium bg-gray-50 dark:bg-gray-800 text-gray-600 dark:text-gray-500">
|
||||
Configure the default resolver used by connected Devices in your Firezone network. Queries for
|
||||
Configure the default resolver used by connected Clients in your Firezone network. Queries for
|
||||
defined Resources will <strong>always</strong>
|
||||
use Firezone's internal DNS. All other queries will
|
||||
use the resolver configured below.
|
||||
@@ -29,7 +29,7 @@ defmodule Web.Settings.DNS do
|
||||
</p>
|
||||
<section class="bg-white dark:bg-gray-900">
|
||||
<div class="max-w-2xl px-4 py-8 mx-auto lg:py-16">
|
||||
<h2 class="mb-4 text-xl font-bold text-gray-900 dark:text-white">Device DNS</h2>
|
||||
<h2 class="mb-4 text-xl font-bold text-gray-900 dark:text-white">Client DNS</h2>
|
||||
<form action="#">
|
||||
<div class="grid gap-4 mb-4 sm:grid-cols-1 sm:gap-6 sm:mb-6">
|
||||
<div>
|
||||
|
||||
@@ -127,7 +127,7 @@ defmodule Web.Router do
|
||||
live "/:id", Show
|
||||
end
|
||||
|
||||
scope "/devices", Devices do
|
||||
scope "/clients", Clients do
|
||||
live "/", Index
|
||||
live "/:id", Show
|
||||
live "/:id/edit", Edit
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
defmodule Web.Live.Devices.EditTest do
|
||||
defmodule Web.Live.Clients.EditTest do
|
||||
use Web.ConnCase, async: true
|
||||
|
||||
setup do
|
||||
@@ -6,22 +6,22 @@ defmodule Web.Live.Devices.EditTest do
|
||||
actor = Fixtures.Actors.create_actor(type: :account_admin_user, account: account)
|
||||
identity = Fixtures.Auth.create_identity(account: account, actor: actor)
|
||||
|
||||
device = Fixtures.Devices.create_device(account: account, actor: actor, identity: identity)
|
||||
client = Fixtures.Clients.create_client(account: account, actor: actor, identity: identity)
|
||||
|
||||
%{
|
||||
account: account,
|
||||
actor: actor,
|
||||
identity: identity,
|
||||
device: device
|
||||
client: client
|
||||
}
|
||||
end
|
||||
|
||||
test "redirects to sign in page for unauthorized user", %{
|
||||
account: account,
|
||||
device: device,
|
||||
client: client,
|
||||
conn: conn
|
||||
} do
|
||||
assert live(conn, ~p"/#{account}/devices/#{device}/edit") ==
|
||||
assert live(conn, ~p"/#{account}/clients/#{client}/edit") ==
|
||||
{:error,
|
||||
{:redirect,
|
||||
%{
|
||||
@@ -30,80 +30,80 @@ defmodule Web.Live.Devices.EditTest do
|
||||
}}}
|
||||
end
|
||||
|
||||
test "renders not found error when device is deleted", %{
|
||||
test "renders not found error when client is deleted", %{
|
||||
account: account,
|
||||
identity: identity,
|
||||
device: device,
|
||||
client: client,
|
||||
conn: conn
|
||||
} do
|
||||
device = Fixtures.Devices.delete_device(device)
|
||||
client = Fixtures.Clients.delete_client(client)
|
||||
|
||||
assert_raise Web.LiveErrors.NotFoundError, fn ->
|
||||
conn
|
||||
|> authorize_conn(identity)
|
||||
|> live(~p"/#{account}/devices/#{device}/edit")
|
||||
|> live(~p"/#{account}/clients/#{client}/edit")
|
||||
end
|
||||
end
|
||||
|
||||
test "renders breadcrumbs item", %{
|
||||
account: account,
|
||||
identity: identity,
|
||||
device: device,
|
||||
client: client,
|
||||
conn: conn
|
||||
} do
|
||||
{:ok, _lv, html} =
|
||||
conn
|
||||
|> authorize_conn(identity)
|
||||
|> live(~p"/#{account}/devices/#{device}/edit")
|
||||
|> live(~p"/#{account}/clients/#{client}/edit")
|
||||
|
||||
assert item = Floki.find(html, "[aria-label='Breadcrumb']")
|
||||
breadcrumbs = String.trim(Floki.text(item))
|
||||
assert breadcrumbs =~ "Devices"
|
||||
assert breadcrumbs =~ device.name
|
||||
assert breadcrumbs =~ "Clients"
|
||||
assert breadcrumbs =~ client.name
|
||||
assert breadcrumbs =~ "Edit"
|
||||
end
|
||||
|
||||
test "renders form", %{
|
||||
account: account,
|
||||
identity: identity,
|
||||
device: device,
|
||||
client: client,
|
||||
conn: conn
|
||||
} do
|
||||
{:ok, lv, _html} =
|
||||
conn
|
||||
|> authorize_conn(identity)
|
||||
|> live(~p"/#{account}/devices/#{device}/edit")
|
||||
|> live(~p"/#{account}/clients/#{client}/edit")
|
||||
|
||||
form = form(lv, "form")
|
||||
|
||||
assert find_inputs(form) == [
|
||||
"device[name]"
|
||||
"client[name]"
|
||||
]
|
||||
end
|
||||
|
||||
test "renders changeset errors on input change", %{
|
||||
account: account,
|
||||
identity: identity,
|
||||
device: device,
|
||||
client: client,
|
||||
conn: conn
|
||||
} do
|
||||
attrs = Fixtures.Devices.device_attrs() |> Map.take([:name])
|
||||
attrs = Fixtures.Clients.client_attrs() |> Map.take([:name])
|
||||
|
||||
{:ok, lv, _html} =
|
||||
conn
|
||||
|> authorize_conn(identity)
|
||||
|> live(~p"/#{account}/devices/#{device}/edit")
|
||||
|> live(~p"/#{account}/clients/#{client}/edit")
|
||||
|
||||
lv
|
||||
|> form("form", device: attrs)
|
||||
|> validate_change(%{device: %{name: String.duplicate("a", 256)}}, fn form, _html ->
|
||||
|> form("form", client: attrs)
|
||||
|> validate_change(%{client: %{name: String.duplicate("a", 256)}}, fn form, _html ->
|
||||
assert form_validation_errors(form) == %{
|
||||
"device[name]" => ["should be at most 255 character(s)"]
|
||||
"client[name]" => ["should be at most 255 character(s)"]
|
||||
}
|
||||
end)
|
||||
|> validate_change(%{device: %{name: ""}}, fn form, _html ->
|
||||
|> validate_change(%{client: %{name: ""}}, fn form, _html ->
|
||||
assert form_validation_errors(form) == %{
|
||||
"device[name]" => ["can't be blank"]
|
||||
"client[name]" => ["can't be blank"]
|
||||
}
|
||||
end)
|
||||
end
|
||||
@@ -112,44 +112,44 @@ defmodule Web.Live.Devices.EditTest do
|
||||
account: account,
|
||||
actor: actor,
|
||||
identity: identity,
|
||||
device: device,
|
||||
client: client,
|
||||
conn: conn
|
||||
} do
|
||||
other_device = Fixtures.Devices.create_device(account: account, actor: actor)
|
||||
attrs = %{name: other_device.name}
|
||||
other_client = Fixtures.Clients.create_client(account: account, actor: actor)
|
||||
attrs = %{name: other_client.name}
|
||||
|
||||
{:ok, lv, _html} =
|
||||
conn
|
||||
|> authorize_conn(identity)
|
||||
|> live(~p"/#{account}/devices/#{device}/edit")
|
||||
|> live(~p"/#{account}/clients/#{client}/edit")
|
||||
|
||||
assert lv
|
||||
|> form("form", device: attrs)
|
||||
|> form("form", client: attrs)
|
||||
|> render_submit()
|
||||
|> form_validation_errors() == %{
|
||||
"device[name]" => ["has already been taken"]
|
||||
"client[name]" => ["has already been taken"]
|
||||
}
|
||||
end
|
||||
|
||||
test "creates a new device on valid attrs", %{
|
||||
test "creates a new client on valid attrs", %{
|
||||
account: account,
|
||||
identity: identity,
|
||||
device: device,
|
||||
client: client,
|
||||
conn: conn
|
||||
} do
|
||||
attrs = Fixtures.Devices.device_attrs() |> Map.take([:name])
|
||||
attrs = Fixtures.Clients.client_attrs() |> Map.take([:name])
|
||||
|
||||
{:ok, lv, _html} =
|
||||
conn
|
||||
|> authorize_conn(identity)
|
||||
|> live(~p"/#{account}/devices/#{device}/edit")
|
||||
|> live(~p"/#{account}/clients/#{client}/edit")
|
||||
|
||||
assert lv
|
||||
|> form("form", device: attrs)
|
||||
|> form("form", client: attrs)
|
||||
|> render_submit() ==
|
||||
{:error, {:redirect, %{to: ~p"/#{account}/devices/#{device}"}}}
|
||||
{:error, {:redirect, %{to: ~p"/#{account}/clients/#{client}"}}}
|
||||
|
||||
assert device = Repo.get_by(Domain.Devices.Device, id: device.id)
|
||||
assert device.name == attrs.name
|
||||
assert client = Repo.get_by(Domain.Clients.Client, id: client.id)
|
||||
assert client.name == attrs.name
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
defmodule Web.Live.Devices.IndexTest do
|
||||
defmodule Web.Live.Clients.IndexTest do
|
||||
use Web.ConnCase, async: true
|
||||
|
||||
setup do
|
||||
@@ -12,7 +12,7 @@ defmodule Web.Live.Devices.IndexTest do
|
||||
end
|
||||
|
||||
test "redirects to sign in page for unauthorized user", %{account: account, conn: conn} do
|
||||
assert live(conn, ~p"/#{account}/devices") ==
|
||||
assert live(conn, ~p"/#{account}/clients") ==
|
||||
{:error,
|
||||
{:redirect,
|
||||
%{
|
||||
@@ -29,14 +29,14 @@ defmodule Web.Live.Devices.IndexTest do
|
||||
{:ok, _lv, html} =
|
||||
conn
|
||||
|> authorize_conn(identity)
|
||||
|> live(~p"/#{account}/devices")
|
||||
|> live(~p"/#{account}/clients")
|
||||
|
||||
assert item = Floki.find(html, "[aria-label='Breadcrumb']")
|
||||
breadcrumbs = String.trim(Floki.text(item))
|
||||
assert breadcrumbs =~ "Devices"
|
||||
assert breadcrumbs =~ "Clients"
|
||||
end
|
||||
|
||||
test "renders empty table when there are no devices", %{
|
||||
test "renders empty table when there are no clients", %{
|
||||
account: account,
|
||||
identity: identity,
|
||||
conn: conn
|
||||
@@ -44,39 +44,39 @@ defmodule Web.Live.Devices.IndexTest do
|
||||
{:ok, _lv, html} =
|
||||
conn
|
||||
|> authorize_conn(identity)
|
||||
|> live(~p"/#{account}/devices")
|
||||
|> live(~p"/#{account}/clients")
|
||||
|
||||
assert html =~ "There are no devices to display."
|
||||
assert html =~ "There are no clients to display."
|
||||
refute html =~ "tbody"
|
||||
end
|
||||
|
||||
test "renders devices table", %{
|
||||
test "renders clients table", %{
|
||||
account: account,
|
||||
identity: identity,
|
||||
conn: conn
|
||||
} do
|
||||
online_device = Fixtures.Devices.create_device(account: account)
|
||||
offline_device = Fixtures.Devices.create_device(account: account)
|
||||
online_client = Fixtures.Clients.create_client(account: account)
|
||||
offline_client = Fixtures.Clients.create_client(account: account)
|
||||
|
||||
:ok = Domain.Devices.connect_device(online_device)
|
||||
:ok = Domain.Clients.connect_client(online_client)
|
||||
|
||||
{:ok, lv, _html} =
|
||||
conn
|
||||
|> authorize_conn(identity)
|
||||
|> live(~p"/#{account}/devices")
|
||||
|> live(~p"/#{account}/clients")
|
||||
|
||||
lv
|
||||
|> element("#devices")
|
||||
|> element("#clients")
|
||||
|> render()
|
||||
|> table_to_map()
|
||||
|> with_table_row("name", online_device.name, fn row ->
|
||||
|> with_table_row("name", online_client.name, fn row ->
|
||||
assert row["status"] == "Online"
|
||||
name = Repo.preload(online_device, :actor).actor.name
|
||||
name = Repo.preload(online_client, :actor).actor.name
|
||||
assert row["user"] =~ name
|
||||
end)
|
||||
|> with_table_row("name", offline_device.name, fn row ->
|
||||
|> with_table_row("name", offline_client.name, fn row ->
|
||||
assert row["status"] == "Offline"
|
||||
name = Repo.preload(offline_device, :actor).actor.name
|
||||
name = Repo.preload(offline_client, :actor).actor.name
|
||||
assert row["user"] =~ name
|
||||
end)
|
||||
end
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
defmodule Web.Live.Devices.ShowTest do
|
||||
defmodule Web.Live.Clients.ShowTest do
|
||||
use Web.ConnCase, async: true
|
||||
|
||||
setup do
|
||||
@@ -7,23 +7,23 @@ defmodule Web.Live.Devices.ShowTest do
|
||||
identity = Fixtures.Auth.create_identity(account: account, actor: actor)
|
||||
subject = Fixtures.Auth.create_subject(account: account, actor: actor, identity: identity)
|
||||
|
||||
device = Fixtures.Devices.create_device(account: account, actor: actor, identity: identity)
|
||||
client = Fixtures.Clients.create_client(account: account, actor: actor, identity: identity)
|
||||
|
||||
%{
|
||||
account: account,
|
||||
actor: actor,
|
||||
identity: identity,
|
||||
subject: subject,
|
||||
device: device
|
||||
client: client
|
||||
}
|
||||
end
|
||||
|
||||
test "redirects to sign in page for unauthorized user", %{
|
||||
account: account,
|
||||
device: device,
|
||||
client: client,
|
||||
conn: conn
|
||||
} do
|
||||
assert live(conn, ~p"/#{account}/devices/#{device}") ==
|
||||
assert live(conn, ~p"/#{account}/clients/#{client}") ==
|
||||
{:error,
|
||||
{:redirect,
|
||||
%{
|
||||
@@ -32,41 +32,41 @@ defmodule Web.Live.Devices.ShowTest do
|
||||
}}}
|
||||
end
|
||||
|
||||
test "renders not found error when device is deleted", %{
|
||||
test "renders not found error when client is deleted", %{
|
||||
account: account,
|
||||
device: device,
|
||||
client: client,
|
||||
identity: identity,
|
||||
conn: conn
|
||||
} do
|
||||
device = Fixtures.Devices.delete_device(device)
|
||||
client = Fixtures.Clients.delete_client(client)
|
||||
|
||||
assert_raise Web.LiveErrors.NotFoundError, fn ->
|
||||
conn
|
||||
|> authorize_conn(identity)
|
||||
|> live(~p"/#{account}/devices/#{device}")
|
||||
|> live(~p"/#{account}/clients/#{client}")
|
||||
end
|
||||
end
|
||||
|
||||
test "renders breadcrumbs item", %{
|
||||
account: account,
|
||||
device: device,
|
||||
client: client,
|
||||
identity: identity,
|
||||
conn: conn
|
||||
} do
|
||||
{:ok, _lv, html} =
|
||||
conn
|
||||
|> authorize_conn(identity)
|
||||
|> live(~p"/#{account}/devices/#{device}")
|
||||
|> live(~p"/#{account}/clients/#{client}")
|
||||
|
||||
assert item = Floki.find(html, "[aria-label='Breadcrumb']")
|
||||
breadcrumbs = String.trim(Floki.text(item))
|
||||
assert breadcrumbs =~ "Devices"
|
||||
assert breadcrumbs =~ device.name
|
||||
assert breadcrumbs =~ "Clients"
|
||||
assert breadcrumbs =~ client.name
|
||||
end
|
||||
|
||||
test "renders device details", %{
|
||||
test "renders client details", %{
|
||||
account: account,
|
||||
device: device,
|
||||
client: client,
|
||||
actor: actor,
|
||||
identity: identity,
|
||||
conn: conn
|
||||
@@ -74,79 +74,79 @@ defmodule Web.Live.Devices.ShowTest do
|
||||
{:ok, lv, _html} =
|
||||
conn
|
||||
|> authorize_conn(identity)
|
||||
|> live(~p"/#{account}/devices/#{device}")
|
||||
|> live(~p"/#{account}/clients/#{client}")
|
||||
|
||||
table =
|
||||
lv
|
||||
|> element("#device")
|
||||
|> element("#client")
|
||||
|> render()
|
||||
|> vertical_table_to_map()
|
||||
|
||||
assert table["identifier"] == device.id
|
||||
assert table["name"] == device.name
|
||||
assert table["identifier"] == client.id
|
||||
assert table["name"] == client.name
|
||||
assert table["owner"] =~ actor.name
|
||||
assert table["created"]
|
||||
assert table["last seen"]
|
||||
assert table["remote ipv4"] =~ to_string(device.ipv4)
|
||||
assert table["remote ipv6"] =~ to_string(device.ipv6)
|
||||
assert table["client version"] =~ device.last_seen_version
|
||||
assert table["user agent"] =~ device.last_seen_user_agent
|
||||
assert table["remote ipv4"] =~ to_string(client.ipv4)
|
||||
assert table["remote ipv6"] =~ to_string(client.ipv6)
|
||||
assert table["client version"] =~ client.last_seen_version
|
||||
assert table["user agent"] =~ client.last_seen_user_agent
|
||||
end
|
||||
|
||||
test "renders device owner", %{
|
||||
test "renders client owner", %{
|
||||
account: account,
|
||||
device: device,
|
||||
client: client,
|
||||
identity: identity,
|
||||
conn: conn
|
||||
} do
|
||||
actor = Repo.preload(device, :actor).actor
|
||||
actor = Repo.preload(client, :actor).actor
|
||||
|
||||
{:ok, lv, _html} =
|
||||
conn
|
||||
|> authorize_conn(identity)
|
||||
|> live(~p"/#{account}/devices/#{device}")
|
||||
|> live(~p"/#{account}/clients/#{client}")
|
||||
|
||||
assert lv
|
||||
|> element("#device")
|
||||
|> element("#client")
|
||||
|> render()
|
||||
|> vertical_table_to_map()
|
||||
|> Map.fetch!("owner") =~ actor.name
|
||||
end
|
||||
|
||||
test "allows editing devices", %{
|
||||
test "allows editing clients", %{
|
||||
account: account,
|
||||
device: device,
|
||||
client: client,
|
||||
identity: identity,
|
||||
conn: conn
|
||||
} do
|
||||
{:ok, lv, _html} =
|
||||
conn
|
||||
|> authorize_conn(identity)
|
||||
|> live(~p"/#{account}/devices/#{device}")
|
||||
|> live(~p"/#{account}/clients/#{client}")
|
||||
|
||||
assert lv
|
||||
|> element("a", "Edit Device")
|
||||
|> element("a", "Edit Client")
|
||||
|> render_click() ==
|
||||
{:error,
|
||||
{:live_redirect, %{to: ~p"/#{account}/devices/#{device}/edit", kind: :push}}}
|
||||
{:live_redirect, %{to: ~p"/#{account}/clients/#{client}/edit", kind: :push}}}
|
||||
end
|
||||
|
||||
test "allows deleting devices", %{
|
||||
test "allows deleting clients", %{
|
||||
account: account,
|
||||
device: device,
|
||||
client: client,
|
||||
identity: identity,
|
||||
conn: conn
|
||||
} do
|
||||
{:ok, lv, _html} =
|
||||
conn
|
||||
|> authorize_conn(identity)
|
||||
|> live(~p"/#{account}/devices/#{device}")
|
||||
|> live(~p"/#{account}/clients/#{client}")
|
||||
|
||||
assert lv
|
||||
|> element("button", "Delete Device")
|
||||
|> element("button", "Delete Client")
|
||||
|> render_click() ==
|
||||
{:error, {:redirect, %{to: ~p"/#{account}/devices"}}}
|
||||
{:error, {:redirect, %{to: ~p"/#{account}/clients"}}}
|
||||
|
||||
assert Repo.get(Domain.Devices.Device, device.id).deleted_at
|
||||
assert Repo.get(Domain.Clients.Client, client.id).deleted_at
|
||||
end
|
||||
end
|
||||
|
||||
@@ -29,7 +29,7 @@ config :domain, Domain.Repo,
|
||||
migration_timestamps: [type: :timestamptz],
|
||||
start_apps_before_migration: [:ssl, :logger_json]
|
||||
|
||||
config :domain, Domain.Devices, upstream_dns: ["1.1.1.1"]
|
||||
config :domain, Domain.Clients, upstream_dns: ["1.1.1.1"]
|
||||
|
||||
config :domain, Domain.Gateways,
|
||||
gateway_ipv4_masquerade: true,
|
||||
|
||||
@@ -28,7 +28,7 @@ if config_env() == :prod do
|
||||
path: external_url_path
|
||||
} = URI.parse(external_url)
|
||||
|
||||
config :domain, Domain.Devices, upstream_dns: compile_config!(:devices_upstream_dns)
|
||||
config :domain, Domain.Clients, upstream_dns: compile_config!(:clients_upstream_dns)
|
||||
|
||||
config :domain, Domain.Gateways,
|
||||
gateway_ipv4_masquerade: compile_config!(:gateway_ipv4_masquerade),
|
||||
|
||||
@@ -280,7 +280,7 @@ impl<CB: Callbacks + 'static> ControlSession<Messages, CB> for ControlPlane<CB>
|
||||
}
|
||||
|
||||
fn socket_path() -> &'static str {
|
||||
"device"
|
||||
"client"
|
||||
}
|
||||
|
||||
fn retry_strategy() -> ExponentialBackoff {
|
||||
|
||||
@@ -139,7 +139,7 @@ mod test {
|
||||
fn connection_ready_deserialization() {
|
||||
let message = r#"{
|
||||
"ref": "0",
|
||||
"topic": "device",
|
||||
"topic": "client",
|
||||
"event": "phx_reply",
|
||||
"payload": {
|
||||
"status": "ok",
|
||||
@@ -160,7 +160,7 @@ mod test {
|
||||
#[test]
|
||||
fn init_phoenix_message() {
|
||||
let m = PhoenixMessage::new(
|
||||
"device",
|
||||
"client",
|
||||
IngressMessages::Init(InitClient {
|
||||
interface: Interface {
|
||||
ipv4: "100.72.112.111".parse().unwrap(),
|
||||
@@ -210,7 +210,7 @@ mod test {
|
||||
]
|
||||
},
|
||||
"ref": null,
|
||||
"topic": "device"
|
||||
"topic": "client"
|
||||
}"#;
|
||||
let ingress_message: PhoenixMessage<IngressMessages, ReplyMessages> =
|
||||
serde_json::from_str(message).unwrap();
|
||||
@@ -220,7 +220,7 @@ mod test {
|
||||
#[test]
|
||||
fn list_relays_message() {
|
||||
let m = PhoenixMessage::<EgressMessages, ()>::new(
|
||||
"device",
|
||||
"client",
|
||||
EgressMessages::PrepareConnection {
|
||||
resource_id: "f16ecfa0-a94f-4bfd-a2ef-1cc1f2ef3da3".parse().unwrap(),
|
||||
connected_gateway_ids: vec![],
|
||||
@@ -235,7 +235,7 @@ mod test {
|
||||
"connected_gateway_ids": []
|
||||
},
|
||||
"ref":null,
|
||||
"topic": "device"
|
||||
"topic": "client"
|
||||
}
|
||||
"#;
|
||||
let egress_message = serde_json::from_str(message).unwrap();
|
||||
@@ -245,7 +245,7 @@ mod test {
|
||||
#[test]
|
||||
fn connection_details_reply() {
|
||||
let m = PhoenixMessage::<IngressMessages, ReplyMessages>::new_reply(
|
||||
"device",
|
||||
"client",
|
||||
ReplyMessages::ConnectionDetails(ConnectionDetails {
|
||||
gateway_id: "73037362-715d-4a83-a749-f18eadd970e6".parse().unwrap(),
|
||||
gateway_remote_ip: "172.28.0.1".parse().unwrap(),
|
||||
@@ -279,7 +279,7 @@ mod test {
|
||||
let message = r#"
|
||||
{
|
||||
"ref":null,
|
||||
"topic":"device",
|
||||
"topic":"client",
|
||||
"event": "phx_reply",
|
||||
"payload": {
|
||||
"response": {
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
pub struct DeviceRef {
|
||||
device_ref: std::os::fd::RawFd,
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
pub struct DeviceRef {
|
||||
device_ref: std::os::windows::io::RawHandle,
|
||||
}
|
||||
@@ -40,9 +40,9 @@ pub struct RequestConnection {
|
||||
/// Resource id the request is for.
|
||||
pub resource_id: Id,
|
||||
/// The preshared key the client generated for the connection that it is trying to establish.
|
||||
pub device_preshared_key: Key,
|
||||
pub client_preshared_key: Key,
|
||||
/// Client's local RTC Session Description that the client will use for this connection.
|
||||
pub device_rtc_session_description: RTCSessionDescription,
|
||||
pub client_rtc_session_description: RTCSessionDescription,
|
||||
}
|
||||
|
||||
/// Represent a request to reuse an existing gateway connection from a client to a given resource.
|
||||
@@ -61,7 +61,7 @@ pub struct ReuseConnection {
|
||||
impl PartialEq for RequestConnection {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.resource_id == other.resource_id
|
||||
&& self.device_preshared_key == other.device_preshared_key
|
||||
&& self.client_preshared_key == other.client_preshared_key
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -82,10 +82,10 @@ impl<CB: Callbacks + 'static> ControlPlane<CB> {
|
||||
tokio::spawn(async move {
|
||||
match tunnel
|
||||
.set_peer_connection_request(
|
||||
connection_request.device.rtc_session_description,
|
||||
connection_request.device.peer.into(),
|
||||
connection_request.client.rtc_session_description,
|
||||
connection_request.client.peer.into(),
|
||||
connection_request.relays,
|
||||
connection_request.device.id,
|
||||
connection_request.client.id,
|
||||
connection_request.expires_at,
|
||||
connection_request.resource,
|
||||
)
|
||||
@@ -100,12 +100,12 @@ impl<CB: Callbacks + 'static> ControlPlane<CB> {
|
||||
}))
|
||||
.await
|
||||
{
|
||||
tunnel.cleanup_connection(connection_request.device.id);
|
||||
tunnel.cleanup_connection(connection_request.client.id);
|
||||
let _ = tunnel.callbacks().on_error(&err);
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
tunnel.cleanup_connection(connection_request.device.id);
|
||||
tunnel.cleanup_connection(connection_request.client.id);
|
||||
let _ = tunnel.callbacks().on_error(&err);
|
||||
}
|
||||
}
|
||||
@@ -116,12 +116,12 @@ impl<CB: Callbacks + 'static> ControlPlane<CB> {
|
||||
fn allow_access(
|
||||
&self,
|
||||
AllowAccess {
|
||||
device_id,
|
||||
client_id,
|
||||
resource,
|
||||
expires_at,
|
||||
}: AllowAccess,
|
||||
) {
|
||||
self.tunnel.allow_access(resource, device_id, expires_at)
|
||||
self.tunnel.allow_access(resource, client_id, expires_at)
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "trace", skip(self))]
|
||||
|
||||
@@ -19,7 +19,7 @@ pub struct Actor {
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize, Clone)]
|
||||
pub struct Device {
|
||||
pub struct Client {
|
||||
pub id: Id,
|
||||
pub rtc_session_description: RTCSessionDescription,
|
||||
pub peer: Peer,
|
||||
@@ -27,20 +27,20 @@ pub struct Device {
|
||||
|
||||
// rtc_sdp is ignored from eq since RTCSessionDescription doesn't implement this
|
||||
// this will probably be changed in the future.
|
||||
impl PartialEq for Device {
|
||||
impl PartialEq for Client {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.id == other.id && self.peer == other.peer
|
||||
}
|
||||
}
|
||||
|
||||
impl Eq for Device {}
|
||||
impl Eq for Client {}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq)]
|
||||
pub struct RequestConnection {
|
||||
pub actor: Actor,
|
||||
pub relays: Vec<Relay>,
|
||||
pub resource: ResourceDescription,
|
||||
pub device: Device,
|
||||
pub client: Client,
|
||||
#[serde(rename = "ref")]
|
||||
pub reference: String,
|
||||
#[serde(with = "ts_seconds")]
|
||||
@@ -73,7 +73,7 @@ pub struct RemoveResource {
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq)]
|
||||
pub struct AllowAccess {
|
||||
pub device_id: Id,
|
||||
pub client_id: Id,
|
||||
pub resource: ResourceDescription,
|
||||
#[serde(with = "ts_seconds")]
|
||||
pub expires_at: DateTime<Utc>,
|
||||
@@ -122,7 +122,7 @@ mod test {
|
||||
"topic": "gateway",
|
||||
"event": "request_connection",
|
||||
"payload": {
|
||||
"device": {
|
||||
"client": {
|
||||
"id": "3a25ff38-f8d7-47de-9b30-c7c40c206083",
|
||||
"peer": {
|
||||
"ipv6": "fd00:2021:1111::3a:ab1b",
|
||||
|
||||
@@ -402,8 +402,8 @@ where
|
||||
Ok(Request::NewConnection(RequestConnection {
|
||||
resource_id,
|
||||
gateway_id,
|
||||
device_preshared_key: Key(preshared_key.to_bytes()),
|
||||
device_rtc_session_description: local_description,
|
||||
client_preshared_key: Key(preshared_key.to_bytes()),
|
||||
client_rtc_session_description: local_description,
|
||||
}))
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user