diff --git a/elixir/apps/domain/lib/domain/cache/client.ex b/elixir/apps/domain/lib/domain/cache/client.ex index e86720277..03883d79b 100644 --- a/elixir/apps/domain/lib/domain/cache/client.ex +++ b/elixir/apps/domain/lib/domain/cache/client.ex @@ -51,7 +51,7 @@ defmodule Domain.Cache.Client do """ - alias Domain.{Actors, Auth, Clients, Cache, Gateways, Resources, Policies} + alias Domain.{Actors, Auth, Clients, Cache, Gateways, Resources, Policies, Version} require Logger require OpenTelemetry.Tracer import Ecto.UUID, only: [dump!: 1, load!: 1] @@ -159,8 +159,8 @@ defmodule Domain.Cache.Client do added_ids = Enum.map(added, & &1.id) - # connlib can handle all resource attribute changes except for changing sites, so we can omit the deleted IDs - # of added resources since they'll be updated gracefully. + # connlib can handle all resource attribute changes except for changing sites (on older clients), + # so we can omit the deleted IDs of added resources since they'll be updated gracefully. removed_ids = for r <- cache.connectable_resources -- connectable_resources, toggle or r.id not in added_ids do @@ -273,9 +273,11 @@ defmodule Domain.Cache.Client do cache = %{cache | resources: resources} + toggle = Version.resource_cannot_change_sites_on_client?(client) + # For these updates we need to make sure the resource is toggled deleted then created. # See https://github.com/firezone/firezone/issues/9881 - recompute_connectable_resources(cache, client, toggle: true) + recompute_connectable_resources(cache, client, toggle: toggle) end @doc """ diff --git a/elixir/apps/domain/lib/domain/component_versions.ex b/elixir/apps/domain/lib/domain/component_versions.ex index 9459551ca..8fe84fbdb 100644 --- a/elixir/apps/domain/lib/domain/component_versions.ex +++ b/elixir/apps/domain/lib/domain/component_versions.ex @@ -58,6 +58,16 @@ defmodule Domain.ComponentVersions do end end + def get_component_type(%Client{last_seen_user_agent: "Mac OS" <> _rest}), do: :apple + def get_component_type(%Client{last_seen_user_agent: "iOS" <> _rest}), do: :apple + + def get_component_type(%Client{last_seen_user_agent: "Android" <> _rest}), + do: :android + + def get_component_type(%Client{actor: %Actor{type: :service_account}}), do: :headless + + def get_component_type(_), do: :gui + defp fetch_config! do Domain.Config.fetch_env!(:domain, __MODULE__) end @@ -98,14 +108,4 @@ defmodule Domain.ComponentVersions do |> Keyword.fetch!(:versions) end end - - defp get_component_type(%Client{last_seen_user_agent: "Mac OS" <> _rest}), do: :apple - defp get_component_type(%Client{last_seen_user_agent: "iOS" <> _rest}), do: :apple - - defp get_component_type(%Client{last_seen_user_agent: "Android" <> _rest}), - do: :android - - defp get_component_type(%Client{actor: %Actor{type: :service_account}}), do: :headless - - defp get_component_type(_), do: :gui end diff --git a/elixir/apps/domain/lib/domain/version.ex b/elixir/apps/domain/lib/domain/version.ex index c75a63e13..0e8d746a0 100644 --- a/elixir/apps/domain/lib/domain/version.ex +++ b/elixir/apps/domain/lib/domain/version.ex @@ -1,4 +1,9 @@ defmodule Domain.Version do + alias Domain.{ + Clients.Client, + ComponentVersions + } + def fetch_version(user_agent) when is_binary(user_agent) do user_agent |> String.split(" ") @@ -21,4 +26,15 @@ defmodule Domain.Version do def fetch_gateway_version(_user_agent) do {:error, :invalid_user_agent} end + + # TODO: Remove once all clients are on versions that support resources changing sites. + # Connlib didn't support resources changing sites until https://github.com/firezone/firezone/pull/10604 + def resource_cannot_change_sites_on_client?(%Client{last_seen_version: version} = client) do + case ComponentVersions.get_component_type(client) do + :apple -> Version.compare(version, "1.5.9") == :lt + :android -> Version.compare(version, "1.5.5") == :lt + :headless -> Version.compare(version, "1.5.5") == :lt + :gui -> Version.compare(version, "1.5.9") == :lt + end + end end diff --git a/elixir/apps/domain/test/domain/version_test.exs b/elixir/apps/domain/test/domain/version_test.exs index ecd2944d3..3ec60d5c8 100644 --- a/elixir/apps/domain/test/domain/version_test.exs +++ b/elixir/apps/domain/test/domain/version_test.exs @@ -1,7 +1,7 @@ defmodule Domain.VersionTest do use ExUnit.Case, async: true - describe "fetch_version" do + describe "fetch_version/1" do test "can decode linux headless-client version" do assert Domain.Version.fetch_version("Fedora/42.0.0 headless-client/1.4.5 (arm64; 24.1.0)") == {:ok, "1.4.5"} @@ -39,4 +39,114 @@ defmodule Domain.VersionTest do {:ok, "1.4.5"} end end + + describe "resource_cannot_change_sites_on_client?/1" do + test "apple client below version cannot change sites" do + client = %Domain.Clients.Client{ + last_seen_version: "1.5.7", + last_seen_user_agent: "Mac OS X" + } + + assert Domain.Version.resource_cannot_change_sites_on_client?(client) + end + + test "apple client at version cannot change sites" do + client = %Domain.Clients.Client{ + last_seen_version: "1.5.8", + last_seen_user_agent: "Mac OS X" + } + + assert Domain.Version.resource_cannot_change_sites_on_client?(client) + end + + test "apple client above version can change sites" do + client = %Domain.Clients.Client{ + last_seen_version: "1.5.9", + last_seen_user_agent: "Mac OS X" + } + + refute Domain.Version.resource_cannot_change_sites_on_client?(client) + end + + test "android client below version cannot change sites" do + client = %Domain.Clients.Client{ + last_seen_version: "1.5.3", + last_seen_user_agent: "Android" + } + + assert Domain.Version.resource_cannot_change_sites_on_client?(client) + end + + test "android client at version cannot change sites" do + client = %Domain.Clients.Client{ + last_seen_version: "1.5.4", + last_seen_user_agent: "Android" + } + + assert Domain.Version.resource_cannot_change_sites_on_client?(client) + end + + test "android client above version can change sites" do + client = %Domain.Clients.Client{ + last_seen_version: "1.5.5", + last_seen_user_agent: "Android" + } + + refute Domain.Version.resource_cannot_change_sites_on_client?(client) + end + + test "headless client below version cannot change sites" do + client = %Domain.Clients.Client{ + last_seen_version: "1.5.3", + actor: %Domain.Actors.Actor{type: :service_account} + } + + assert Domain.Version.resource_cannot_change_sites_on_client?(client) + end + + test "headless client at version cannot change sites" do + client = %Domain.Clients.Client{ + last_seen_version: "1.5.4", + actor: %Domain.Actors.Actor{type: :service_account} + } + + assert Domain.Version.resource_cannot_change_sites_on_client?(client) + end + + test "headless client above version can change sites" do + client = %Domain.Clients.Client{ + last_seen_version: "1.5.5", + actor: %Domain.Actors.Actor{type: :service_account} + } + + refute Domain.Version.resource_cannot_change_sites_on_client?(client) + end + + test "gui client below version cannot change sites" do + client = %Domain.Clients.Client{ + last_seen_version: "1.5.7", + last_seen_user_agent: "Windows" + } + + assert Domain.Version.resource_cannot_change_sites_on_client?(client) + end + + test "gui client at version cannot change sites" do + client = %Domain.Clients.Client{ + last_seen_version: "1.5.8", + last_seen_user_agent: "Windows" + } + + assert Domain.Version.resource_cannot_change_sites_on_client?(client) + end + + test "gui client above version can change sites" do + client = %Domain.Clients.Client{ + last_seen_version: "1.5.9", + last_seen_user_agent: "Windows" + } + + refute Domain.Version.resource_cannot_change_sites_on_client?(client) + end + end end