fix(Core): Fix character tracking permissions

This commit is contained in:
Dmitry Popov
2024-11-06 15:12:44 +01:00
parent dee6e86db1
commit e6cfb29c6f
5 changed files with 319 additions and 165 deletions
@@ -4,8 +4,6 @@ defmodule WandererApp.Api.Calculations.CalcMapPermissions do
use Ash.Resource.Calculation
require Ash.Query
import Bitwise
@impl true
def load(_query, _opts, _context) do
[
@@ -17,116 +15,8 @@ defmodule WandererApp.Api.Calculations.CalcMapPermissions do
end
@impl true
def calculate([record], _opts, %{actor: actor}) do
characters = actor.characters
character_ids = characters |> Enum.map(& &1.id)
character_eve_ids = characters |> Enum.map(& &1.eve_id)
character_corporation_ids =
characters |> Enum.map(& &1.corporation_id) |> Enum.map(&to_string/1)
character_alliance_ids = characters |> Enum.map(& &1.alliance_id) |> Enum.map(&to_string/1)
result =
record.acls
|> Enum.reduce([0, 0], fn acl, acc ->
is_owner? = acl.owner_id in character_ids
is_character_member? =
acl.members |> Enum.any?(fn member -> member.eve_character_id in character_eve_ids end)
is_corporation_member? =
acl.members
|> Enum.any?(fn member -> member.eve_corporation_id in character_corporation_ids end)
is_alliance_member? =
acl.members
|> Enum.any?(fn member -> member.eve_alliance_id in character_alliance_ids end)
if is_owner? || is_character_member? || is_corporation_member? || is_alliance_member? do
case acc do
[_, -1] ->
[-1, -1]
[-1, char_acc] ->
char_acl_mask =
acl.members
|> Enum.filter(fn member ->
member.eve_character_id in character_eve_ids
end)
|> Enum.reduce(0, fn member, acc ->
case acc do
-1 -> -1
_ -> WandererApp.Permissions.calc_role_mask(member.role, acc)
end
end)
char_acc =
case char_acl_mask do
-1 -> -1
_ -> char_acc ||| char_acl_mask
end
[-1, char_acc]
[any_acc, char_acc] ->
any_acl_mask =
acl.members
|> Enum.filter(fn member ->
member.eve_character_id in character_eve_ids ||
member.eve_corporation_id in character_corporation_ids ||
member.eve_alliance_id in character_alliance_ids
end)
|> Enum.reduce(0, fn member, acc ->
case acc do
-1 -> -1
_ -> WandererApp.Permissions.calc_role_mask(member.role, acc)
end
end)
char_acl_mask =
acl.members
|> Enum.filter(fn member ->
member.eve_character_id in character_eve_ids
end)
|> Enum.reduce(0, fn member, acc ->
case acc do
-1 -> -1
_ -> WandererApp.Permissions.calc_role_mask(member.role, acc)
end
end)
any_acc =
case any_acl_mask do
-1 -> -1
_ -> any_acc ||| any_acl_mask
end
char_acc =
case char_acl_mask do
-1 -> -1
_ -> char_acc ||| char_acl_mask
end
[any_acc, char_acc]
end
else
acc
end
end)
case result do
[_, -1] ->
[-1]
[-1, char_acc] ->
[char_acc]
[any_acc, _char_acc] ->
[any_acc]
end
end
def calculate([record], _opts, %{actor: actor}),
do: WandererApp.Permissions.check_characters_access(actor.characters, record.acls)
@impl true
def calculate(_records, _opts, _context) do
+3 -1
View File
@@ -8,6 +8,7 @@ defmodule WandererApp.Map do
defstruct map_id: nil,
name: nil,
scope: :none,
owner_id: nil,
characters: [],
systems: Map.new(),
hubs: [],
@@ -16,11 +17,12 @@ defmodule WandererApp.Map do
characters_limit: nil,
hubs_limit: nil
def new(%{id: map_id, name: name, scope: scope, acls: acls, hubs: hubs}) do
def new(%{id: map_id, name: name, scope: scope, owner_id: owner_id, acls: acls, hubs: hubs}) do
map =
struct!(__MODULE__,
map_id: map_id,
scope: scope,
owner_id: owner_id,
name: name,
acls: acls,
hubs: hubs
+204 -51
View File
@@ -77,6 +77,7 @@ defmodule WandererApp.Map.Server.Impl do
# @unknown 100_100
@systems_cleanup_timeout :timer.minutes(30)
@characters_cleanup_timeout :timer.minutes(1)
@connections_cleanup_timeout :timer.minutes(2)
@connection_time_status_eol 1
@@ -112,20 +113,28 @@ defmodule WandererApp.Map.Server.Impl do
end
def load_state(%__MODULE__{map_id: map_id} = state) do
with {:ok, map} <- WandererApp.MapRepo.get(map_id, [:acls, :characters]),
with {:ok, map} <-
WandererApp.MapRepo.get(map_id, [
:owner,
:characters,
acls: [
:owner_id,
members: [:role, :eve_character_id, :eve_corporation_id, :eve_alliance_id]
]
]),
{:ok, systems} <- WandererApp.MapSystemRepo.get_visible_by_map(map_id),
{:ok, connections} <- WandererApp.MapConnectionRepo.get_by_map(map_id),
{:ok, subscription_settings} <-
WandererApp.Map.SubscriptionManager.get_active_map_subscription(map_id) do
state
|> _init_map(
|> init_map(
map,
subscription_settings,
systems,
connections
)
|> _init_map_systems(systems)
|> _init_map_cache()
|> init_map_systems(systems)
|> init_map_cache()
else
error ->
@logger.error("Failed to load map state: #{inspect(error, pretty: true)}")
@@ -145,6 +154,7 @@ defmodule WandererApp.Map.Server.Impl do
Process.send_after(self(), :update_presence, @update_presence_timeout)
Process.send_after(self(), :cleanup_connections, 5000)
Process.send_after(self(), :cleanup_systems, 10000)
Process.send_after(self(), :cleanup_characters, @characters_cleanup_timeout)
Process.send_after(self(), :backup_state, @backup_state_timeout)
WandererApp.Cache.insert("map_#{map_id}:started", true)
@@ -169,7 +179,7 @@ defmodule WandererApp.Map.Server.Impl do
:telemetry.execute([:wanderer_app, :map, :stopped], %{count: 1})
state
|> _maybe_stop_rtree()
|> maybe_stop_rtree()
end
def get_map(%{map: map} = _state), do: {:ok, map}
@@ -509,11 +519,11 @@ defmodule WandererApp.Map.Server.Impl do
|> Enum.map(fn character_id ->
Task.start_link(fn ->
character_updates =
_maybe_update_online(map_id, character_id) ++
_maybe_update_location(map_id, character_id) ++
_maybe_update_ship(map_id, character_id) ++
_maybe_update_alliance(map_id, character_id) ++
_maybe_update_corporation(map_id, character_id)
maybe_update_online(map_id, character_id) ++
maybe_update_location(map_id, character_id) ++
maybe_update_ship(map_id, character_id) ++
maybe_update_alliance(map_id, character_id) ++
maybe_update_corporation(map_id, character_id)
character_updates
|> Enum.filter(fn update -> update != :skip end)
@@ -537,9 +547,25 @@ defmodule WandererApp.Map.Server.Impl do
:broadcast
{:character_alliance, _info} ->
WandererApp.Cache.insert_or_update(
"map_#{map_id}:invalidate_character_ids",
[character_id],
fn ids ->
[character_id | ids]
end
)
:broadcast
{:character_corporation, _info} ->
WandererApp.Cache.insert_or_update(
"map_#{map_id}:invalidate_character_ids",
[character_id],
fn ids ->
[character_id | ids]
end
)
:broadcast
_ ->
@@ -602,17 +628,25 @@ defmodule WandererApp.Map.Server.Impl do
state
end
def handle_event({:map_acl_updated, added_acls, removed_acls}, %{map: old_map} = state) do
{:ok, map} = WandererApp.MapRepo.get(old_map.map_id, [:acls])
def handle_event(
{:map_acl_updated, added_acls, removed_acls},
%{map_id: map_id, map: old_map} = state
) do
{:ok, map} =
WandererApp.MapRepo.get(map_id,
acls: [
:owner_id,
members: [:role, :eve_character_id, :eve_corporation_id, :eve_alliance_id]
]
)
track_acls(added_acls)
result =
[added_acls | removed_acls]
|> List.flatten()
(added_acls ++ removed_acls)
|> Task.async_stream(
fn acl_id ->
_update_acl(acl_id)
update_acl(acl_id)
end,
max_concurrency: 10,
timeout: :timer.seconds(15)
@@ -641,29 +675,45 @@ defmodule WandererApp.Map.Server.Impl do
}
error ->
@logger.error(
"Failed to update map #{old_map.map_id} acl: #{inspect(error, pretty: true)}"
)
@logger.error("Failed to update map #{map_id} acl: #{inspect(error, pretty: true)}")
acc
end
end
)
_broadcast_acl_updates({:ok, result})
map_update = %{acls: map.acls, scope: map.scope}
%{state | map: %{old_map | acls: map.acls, scope: map.scope}}
WandererApp.Map.update_map(map_id, map_update)
broadcast_acl_updates({:ok, result}, map_id)
%{state | map: Map.merge(old_map, map_update)}
end
def handle_event({:acl_updated, %{acl_id: acl_id}}, %{map: map} = state) do
def handle_event({:acl_updated, %{acl_id: acl_id}}, %{map_id: map_id, map: old_map} = state) do
{:ok, map} =
WandererApp.MapRepo.get(map_id,
acls: [
:owner_id,
members: [:role, :eve_character_id, :eve_corporation_id, :eve_alliance_id]
]
)
if map.acls |> Enum.map(& &1.id) |> Enum.member?(acl_id) do
map_update = %{acls: map.acls}
WandererApp.Map.update_map(map_id, map_update)
:ok =
acl_id
|> _update_acl()
|> _broadcast_acl_updates()
end
|> update_acl()
|> broadcast_acl_updates(map_id)
state
state
else
state
end
end
def handle_event(:cleanup_connections, %{map_id: map_id} = state) do
@@ -752,6 +802,76 @@ defmodule WandererApp.Map.Server.Impl do
state
end
def handle_event(:cleanup_characters, %{map_id: map_id, map: %{owner_id: owner_id}} = state) do
Process.send_after(self(), :cleanup_characters, @characters_cleanup_timeout)
{:ok, character_ids} =
WandererApp.Cache.lookup(
"map_#{map_id}:invalidate_character_ids",
[]
)
character_ids
|> Task.async_stream(
fn character_id ->
character_id
|> WandererApp.Character.get_character()
|> case do
{:ok, character} ->
acls =
map_id
|> WandererApp.Map.get_map!()
|> Map.get(:acls, [])
[character_permissions] =
WandererApp.Permissions.check_characters_access([character], acls)
map_permissions =
WandererApp.Permissions.get_map_permissions(
character_permissions,
owner_id,
[character_id]
)
case map_permissions do
%{view_system: false} ->
{:remove_character, character_id}
%{track_character: false} ->
{:remove_character, character_id}
_ ->
:ok
end
_ ->
:ok
end
end,
timeout: :timer.seconds(60),
max_concurrency: System.schedulers_online(),
on_timeout: :kill_task
)
|> Enum.each(fn
{:ok, {:remove_character, character_id}} ->
state |> remove_and_untrack_characters([character_id])
:ok
{:ok, _result} ->
:ok
{:error, reason} ->
@logger.error("Error in cleanup_characters: #{inspect(reason)}")
end)
WandererApp.Cache.insert(
"map_#{map_id}:invalidate_character_ids",
[]
)
state
end
def handle_event(:cleanup_systems, %{map_id: map_id} = state) do
Process.send_after(self(), :cleanup_systems, @systems_cleanup_timeout)
@@ -837,6 +957,26 @@ defmodule WandererApp.Map.Server.Impl do
:ok
end
defp remove_and_untrack_characters(%{map_id: map_id} = state, character_ids) do
map_id
|> _untrack_characters(character_ids)
case WandererApp.Api.MapCharacterSettings.tracked_by_map(%{
map_id: map_id,
character_ids: character_ids
}) do
{:ok, settings} ->
settings
|> Enum.map(fn s ->
s |> WandererApp.Api.MapCharacterSettings.untrack()
state |> remove_character(s.character_id)
end)
_ ->
:ok
end
end
defp get_connection_mark_eol_time(map_id, connection_id) do
WandererApp.Cache.get("map_#{map_id}:conn_#{connection_id}:mark_eol_time")
|> case do
@@ -881,7 +1021,7 @@ defmodule WandererApp.Map.Server.Impl do
end
end
defp _maybe_update_location(map_id, character_id) do
defp maybe_update_location(map_id, character_id) do
WandererApp.Cache.lookup!(
"character:#{character_id}:location_started",
false
@@ -932,7 +1072,7 @@ defmodule WandererApp.Map.Server.Impl do
end
end
defp _maybe_update_alliance(map_id, character_id) do
defp maybe_update_alliance(map_id, character_id) do
with {:ok, old_alliance_id} <-
WandererApp.Cache.lookup("map:#{map_id}:character:#{character_id}:alliance_id"),
{:ok, %{alliance_id: alliance_id}} <-
@@ -956,7 +1096,7 @@ defmodule WandererApp.Map.Server.Impl do
end
end
defp _maybe_update_corporation(map_id, character_id) do
defp maybe_update_corporation(map_id, character_id) do
with {:ok, old_corporation_id} <-
WandererApp.Cache.lookup("map:#{map_id}:character:#{character_id}:corporation_id"),
{:ok, %{corporation_id: corporation_id}} <-
@@ -980,7 +1120,7 @@ defmodule WandererApp.Map.Server.Impl do
end
end
defp _maybe_update_online(map_id, character_id) do
defp maybe_update_online(map_id, character_id) do
with {:ok, old_online} <-
WandererApp.Cache.lookup("map:#{map_id}:character:#{character_id}:online"),
{:ok, %{online: online}} <-
@@ -1004,7 +1144,7 @@ defmodule WandererApp.Map.Server.Impl do
end
end
defp _maybe_update_ship(map_id, character_id) do
defp maybe_update_ship(map_id, character_id) do
with {:ok, old_ship_type_id} <-
WandererApp.Cache.lookup("map:#{map_id}:character:#{character_id}:ship_type_id"),
{:ok, old_ship_name} <-
@@ -1252,7 +1392,7 @@ defmodule WandererApp.Map.Server.Impl do
})
end
defp _maybe_stop_rtree(%{rtree_name: rtree_name} = state) do
defp maybe_stop_rtree(%{rtree_name: rtree_name} = state) do
case Process.whereis(rtree_name) do
nil ->
:ok
@@ -1264,7 +1404,7 @@ defmodule WandererApp.Map.Server.Impl do
state
end
defp _init_map_cache(%__MODULE__{map_id: map_id} = state) do
defp init_map_cache(%__MODULE__{map_id: map_id} = state) do
case WandererApp.Api.MapState.by_map_id(map_id) do
{:ok,
%{
@@ -1296,9 +1436,9 @@ defmodule WandererApp.Map.Server.Impl do
end
end
defp _init_map(
defp init_map(
state,
%{characters: characters} = initial_map,
%{id: map_id, characters: characters} = initial_map,
subscription_settings,
systems,
connections
@@ -1319,12 +1459,19 @@ defmodule WandererApp.Map.Server.Impl do
map_options |> Map.get("store_custom_labels", "false") |> String.to_existing_atom()
]
character_ids =
map_id
|> WandererApp.Map.get_map!()
|> Map.get(:characters, [])
WandererApp.Cache.insert("map_#{map_id}:invalidate_character_ids", character_ids)
%{state | map: map, map_opts: map_opts}
end
defp _init_map_systems(state, [] = _systems), do: state
defp init_map_systems(state, [] = _systems), do: state
defp _init_map_systems(%__MODULE__{map_id: map_id, rtree_name: rtree_name} = state, systems) do
defp init_map_systems(%__MODULE__{map_id: map_id, rtree_name: rtree_name} = state, systems) do
systems
|> Enum.each(fn %{id: system_id, solar_system_id: solar_system_id} = system ->
@ddrt.insert(
@@ -1544,7 +1691,7 @@ defmodule WandererApp.Map.Server.Impl do
not Enum.member?(presence_character_ids, character_id)
end)
_track_characters(presence_character_ids, map_id)
track_characters(presence_character_ids, map_id)
map_id
|> _untrack_characters(not_present_character_ids)
@@ -1566,23 +1713,21 @@ defmodule WandererApp.Map.Server.Impl do
defp track_acls([]), do: :ok
defp track_acls([acl_id | rest]) do
_track_acl(acl_id)
track_acl(acl_id)
track_acls(rest)
end
defp _track_acl(acl_id),
do:
WandererApp.PubSub
|> @pubsub_client.subscribe("acls:#{acl_id}")
defp track_acl(acl_id),
do: @pubsub_client.subscribe(WandererApp.PubSub, "acls:#{acl_id}")
defp _track_characters([], _map_id), do: :ok
defp track_characters([], _map_id), do: :ok
defp _track_characters([character_id | rest], map_id) do
_track_character(character_id, map_id)
_track_characters(rest, map_id)
defp track_characters([character_id | rest], map_id) do
track_character(character_id, map_id)
track_characters(rest, map_id)
end
defp _track_character(character_id, map_id) do
defp track_character(character_id, map_id) do
WandererApp.Character.TrackerManager.update_track_settings(character_id, %{
map_id: map_id,
track: true,
@@ -1773,13 +1918,14 @@ defmodule WandererApp.Map.Server.Impl do
|> WandererApp.Map.find_system_by_location(old_location)
|> WandererApp.Map.PositionCalculator.get_new_system_position(rtree_name, opts)}
defp _broadcast_acl_updates(
defp broadcast_acl_updates(
{:ok,
%{
eve_character_ids: eve_character_ids,
eve_corporation_ids: eve_corporation_ids,
eve_alliance_ids: eve_alliance_ids
}}
}},
map_id
) do
eve_character_ids
|> Enum.uniq()
@@ -1811,12 +1957,19 @@ defmodule WandererApp.Map.Server.Impl do
)
end)
character_ids =
map_id
|> WandererApp.Map.get_map!()
|> Map.get(:characters, [])
WandererApp.Cache.insert("map_#{map_id}:invalidate_character_ids", character_ids)
:ok
end
defp _broadcast_acl_updates(_), do: :ok
defp broadcast_acl_updates(_, _map_id), do: :ok
defp _update_acl(acl_id) do
defp update_acl(acl_id) do
{:ok, %{owner: owner, members: members}} =
WandererApp.AccessListRepo.get(acl_id, [:owner, :members])
+109
View File
@@ -87,4 +87,113 @@ defmodule WandererApp.Permissions do
delete_map: check_permission(user_permissions, @delete_map)
}
end
def check_characters_access(characters, acls) do
character_ids = characters |> Enum.map(& &1.id)
character_eve_ids = characters |> Enum.map(& &1.eve_id)
character_corporation_ids =
characters |> Enum.map(& &1.corporation_id) |> Enum.map(&to_string/1)
character_alliance_ids = characters |> Enum.map(& &1.alliance_id) |> Enum.map(&to_string/1)
result =
acls
|> Enum.reduce([0, 0], fn acl, acc ->
is_owner? = acl.owner_id in character_ids
is_character_member? =
acl.members |> Enum.any?(fn member -> member.eve_character_id in character_eve_ids end)
is_corporation_member? =
acl.members
|> Enum.any?(fn member -> member.eve_corporation_id in character_corporation_ids end)
is_alliance_member? =
acl.members
|> Enum.any?(fn member -> member.eve_alliance_id in character_alliance_ids end)
if is_owner? || is_character_member? || is_corporation_member? || is_alliance_member? do
case acc do
[_, -1] ->
[-1, -1]
[-1, char_acc] ->
char_acl_mask =
acl.members
|> Enum.filter(fn member ->
member.eve_character_id in character_eve_ids
end)
|> Enum.reduce(0, fn member, acc ->
case acc do
-1 -> -1
_ -> calc_role_mask(member.role, acc)
end
end)
char_acc =
case char_acl_mask do
-1 -> -1
_ -> char_acc ||| char_acl_mask
end
[-1, char_acc]
[any_acc, char_acc] ->
any_acl_mask =
acl.members
|> Enum.filter(fn member ->
member.eve_character_id in character_eve_ids ||
member.eve_corporation_id in character_corporation_ids ||
member.eve_alliance_id in character_alliance_ids
end)
|> Enum.reduce(0, fn member, acc ->
case acc do
-1 -> -1
_ -> calc_role_mask(member.role, acc)
end
end)
char_acl_mask =
acl.members
|> Enum.filter(fn member ->
member.eve_character_id in character_eve_ids
end)
|> Enum.reduce(0, fn member, acc ->
case acc do
-1 -> -1
_ -> calc_role_mask(member.role, acc)
end
end)
any_acc =
case any_acl_mask do
-1 -> -1
_ -> any_acc ||| any_acl_mask
end
char_acc =
case char_acl_mask do
-1 -> -1
_ -> char_acc ||| char_acl_mask
end
[any_acc, char_acc]
end
else
acc
end
end)
case result do
[_, -1] ->
[-1]
[-1, char_acc] ->
[char_acc]
[any_acc, _char_acc] ->
[any_acc]
end
end
end
@@ -153,7 +153,7 @@ defmodule WandererAppWeb.MapEventHandler do
def handle_ui_event(event, body, socket)
when event in @map_characters_ui_events,
do: MapSystemsEventHandler.handle_ui_event(event, body, socket)
do: MapCharactersEventHandler.handle_ui_event(event, body, socket)
def handle_ui_event(event, body, socket)
when event in @map_system_ui_events,