Skip to content

Commit 0d28722

Browse files
authored
feat: presence enabled accepted as a valid to connect (#2071)
1 parent bed9ac0 commit 0d28722

5 files changed

Lines changed: 345 additions & 76 deletions

File tree

lib/realtime_web/channels/realtime_channel.ex

Lines changed: 63 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -89,35 +89,13 @@ defmodule RealtimeWeb.RealtimeChannel do
8989
socket = assign_authorization_context(socket, sub_topic, claims),
9090
{:ok, db_conn} <- Connect.lookup_or_start_connection(tenant_id),
9191
{:ok, socket} <- maybe_assign_policies(sub_topic, db_conn, socket),
92+
:ok <- can_replay?(params["config"], sub_topic, socket),
9293
{:ok, replayed_message_ids} <-
9394
maybe_replay_messages(params["config"], sub_topic, db_conn, tenant_id, socket.assigns.private?) do
9495
tenant_topic = Tenants.tenant_topic(tenant_id, sub_topic, !socket.assigns.private?)
9596

96-
# presence.read gate carried in the fastlane metadata so the dispatcher can withhold
97-
# presence_diff from members denied presence.read:
98-
# * public channel (no policies) -> true (no presence authorization, always receive diffs)
99-
# * private + presence enabled at join -> the authorized presence.read value (true/false)
100-
# * private + presence not enabled -> nil (read not evaluated yet). The dispatcher routes
101-
# these diffs to the channel process (handle_info) instead of fastlaning, where presence.read
102-
# is consulted at delivery time (it is authorized on-demand when presence is auto-enabled via
103-
# a track message - see PresenceHandler).
104-
presence_read? =
105-
case socket.assigns.policies do
106-
nil -> true
107-
%Policies{presence: %{read: read}} -> read
108-
end
109-
11097
# fastlane subscription
111-
metadata =
112-
MessageDispatcher.fastlane_metadata(
113-
transport_pid,
114-
serializer,
115-
topic,
116-
log_level,
117-
tenant_id,
118-
replayed_message_ids,
119-
presence_read?
120-
)
98+
metadata = fastlane_metadata(socket, replayed_message_ids)
12199

122100
RealtimeWeb.Endpoint.subscribe(tenant_topic, metadata: metadata)
123101
RealtimeWeb.Endpoint.subscribe("realtime:operations:" <> tenant_id, metadata: metadata)
@@ -152,7 +130,9 @@ defmodule RealtimeWeb.RealtimeChannel do
152130
self_broadcast: Join.self_broadcast?(join),
153131
tenant_topic: tenant_topic,
154132
channel_name: sub_topic,
155-
presence_enabled?: presence_enabled?
133+
presence_enabled?: presence_enabled?,
134+
fastlane_metadata: metadata,
135+
replayed_message_ids: replayed_message_ids
156136
}
157137

158138
assigns =
@@ -350,7 +330,10 @@ defmodule RealtimeWeb.RealtimeChannel do
350330
end
351331
end
352332

353-
def handle_info(_msg, %{assigns: %{policies: %Policies{broadcast: %BroadcastPolicies{read: false}}}} = socket) do
333+
def handle_info(
334+
%{event: "broadcast"},
335+
%{assigns: %{policies: %Policies{broadcast: %BroadcastPolicies{read: false}}}} = socket
336+
) do
354337
Logger.warning("Broadcast message ignored")
355338
{:noreply, socket}
356339
end
@@ -572,6 +555,8 @@ defmodule RealtimeWeb.RealtimeChannel do
572555
{:ok, db_conn} <- Connect.lookup_or_start_connection(tenant_id),
573556
{:ok, socket} <- maybe_assign_policies(channel_name, db_conn, socket),
574557
:ok <- check_read_permissions_revoked(previous_policies, socket.assigns.policies) do
558+
socket = maybe_resubscribe_fastlane(socket)
559+
575560
Helpers.cancel_timer(pg_sub_ref)
576561
pg_change_params = Enum.map(pg_change_params, &Map.put(&1, :claims, claims))
577562

@@ -989,9 +974,11 @@ defmodule RealtimeWeb.RealtimeChannel do
989974
) do
990975
socket = assign(socket, :policies, policies)
991976

992-
if match?(%Policies{broadcast: %BroadcastPolicies{read: false}}, socket.assigns.policies),
993-
do: {:error, :unauthorized, "You do not have permissions to read from this Channel topic: #{topic}"},
994-
else: {:ok, socket}
977+
%Policies{broadcast: %{read: broadcast_read?}, presence: %{read: presence_read?}} = socket.assigns.policies
978+
979+
if broadcast_read? || presence_read?,
980+
do: {:ok, socket},
981+
else: {:error, :unauthorized, "You do not have permissions to read from this Channel topic: #{topic}"}
995982
else
996983
{:error, :increase_connection_pool} ->
997984
{:error, :increase_connection_pool}
@@ -1011,6 +998,53 @@ defmodule RealtimeWeb.RealtimeChannel do
1011998

1012999
defp maybe_assign_policies(_, _, socket), do: {:ok, assign(socket, policies: nil)}
10131000

1001+
# presence.read gate carried in the fastlane metadata so the dispatcher can withhold
1002+
# presence_diff from members denied presence.read:
1003+
# * public channel (no policies) -> true (no presence authorization, always receive diffs)
1004+
# * private + presence enabled at join -> the authorized presence.read value (true/false)
1005+
# * private + presence not enabled -> nil (read not evaluated yet). The dispatcher routes
1006+
# these diffs to the channel process (handle_info) instead of fastlaning, where presence.read
1007+
# is consulted at delivery time (it is authorized on-demand when presence is auto-enabled via
1008+
# a track message - see PresenceHandler).
1009+
defp fastlane_metadata(socket, replayed_message_ids) do
1010+
%{assigns: %{tenant: tenant_id, log_level: log_level, policies: policies}} = socket
1011+
1012+
MessageDispatcher.fastlane_metadata(
1013+
socket.transport_pid,
1014+
socket.serializer,
1015+
socket.topic,
1016+
log_level,
1017+
tenant_id,
1018+
replayed_message_ids,
1019+
if(policies, do: policies.presence.read, else: true),
1020+
if(policies, do: policies.broadcast.read, else: true)
1021+
)
1022+
end
1023+
1024+
defp maybe_resubscribe_fastlane(socket) do
1025+
%{assigns: %{fastlane_metadata: current, tenant: tenant_id, tenant_topic: tenant_topic}} = socket
1026+
1027+
case fastlane_metadata(socket, socket.assigns.replayed_message_ids) do
1028+
^current ->
1029+
socket
1030+
1031+
updated ->
1032+
for pubsub_topic <- [tenant_topic, "realtime:operations:" <> tenant_id] do
1033+
RealtimeWeb.Endpoint.unsubscribe(pubsub_topic)
1034+
RealtimeWeb.Endpoint.subscribe(pubsub_topic, metadata: updated)
1035+
end
1036+
1037+
assign(socket, :fastlane_metadata, updated)
1038+
end
1039+
end
1040+
1041+
defp can_replay?(%{"broadcast" => %{"replay" => _}}, topic, %{
1042+
assigns: %{policies: %Policies{broadcast: %BroadcastPolicies{read: false}}}
1043+
}),
1044+
do: {:error, :unauthorized, "You do not have permissions to read from this Channel topic: #{topic}"}
1045+
1046+
defp can_replay?(_config, _topic, _socket), do: :ok
1047+
10141048
# Detects read permissions that were granted under the previous token but are no longer allowed
10151049
# after re-evaluating the policies with the new token. When that happens we disconnect the channel.
10161050
defp check_read_permissions_revoked(%Policies{} = previous, %Policies{} = current) do

lib/realtime_web/channels/realtime_channel/message_dispatcher.ex

Lines changed: 20 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,15 @@ defmodule RealtimeWeb.RealtimeChannel.MessageDispatcher do
1414
log_level,
1515
tenant_id,
1616
replayed_message_ids \\ MapSet.new(),
17-
presence_read? \\ true
17+
presence_read? \\ true,
18+
broadcast_read? \\ true
1819
) do
19-
{:rc_fastlane, fastlane_pid, serializer, topic, log_level, tenant_id, replayed_message_ids, presence_read?}
20+
{:rc_fastlane, fastlane_pid, serializer, topic, log_level, tenant_id, replayed_message_ids, presence_read?,
21+
broadcast_read?}
2022
end
2123

2224
@presence_diff "presence_diff"
25+
@broadcast "broadcast"
2326

2427
@doc """
2528
This dispatch function caches encoded messages if fastlane is used
@@ -46,22 +49,24 @@ defmodule RealtimeWeb.RealtimeChannel.MessageDispatcher do
4649
{pid, _}, {cache, count} when pid == from ->
4750
{cache, count}
4851

49-
# Subscriber is authorized for broadcast.read but denied presence.read: withhold the
50-
# presence_diff. Mirrors the can_read_presence?/1 gate on the presence_state push.
51-
{_pid, {:rc_fastlane, _fastlane_pid, _serializer, _join_topic, _log_level, _tenant_id, _replayed, false}},
52+
# Subscriber is denied presence.read: withhold the presence_diff. Mirrors the
53+
# can_read_presence?/1 gate on the presence_state push.
54+
{_pid,
55+
{:rc_fastlane, _fastlane_pid, _serializer, _join_topic, _log_level, _tenant_id, _replayed, false, _bcast}},
5256
{cache, count} ->
5357
{cache, count}
5458

5559
# presence.read not yet authorized (presence was not enabled at join): route to the channel
5660
# process so it can consult presence.read at delivery time. Wrapped in a
5761
# tuple so it is handled by RealtimeChannel.handle_info rather than intercepted and pushed by
5862
# Phoenix.Channel.Server's built-in %Broadcast{} handling.
59-
{pid, {:rc_fastlane, _fastlane_pid, _serializer, _join_topic, _log_level, _tenant_id, _replayed, nil}},
63+
{pid, {:rc_fastlane, _fastlane_pid, _serializer, _join_topic, _log_level, _tenant_id, _replayed, nil, _bcast}},
6064
{cache, count} ->
6165
send(pid, {:authorize_presence_diff, msg})
6266
{cache, count}
6367

64-
{_pid, {:rc_fastlane, fastlane_pid, serializer, join_topic, log_level, tenant_id, _replayed_message_ids, true}},
68+
{_pid,
69+
{:rc_fastlane, fastlane_pid, serializer, join_topic, log_level, tenant_id, _replayed_message_ids, true, _bcast}},
6570
{cache, count} ->
6671
maybe_log(log_level, join_topic, msg, tenant_id)
6772

@@ -81,6 +86,7 @@ defmodule RealtimeWeb.RealtimeChannel.MessageDispatcher do
8186

8287
def dispatch(subscribers, from, msg) do
8388
message_id = message_id(msg)
89+
broadcast? = broadcast?(msg)
8490

8591
_ =
8692
Enum.reduce(subscribers, %{}, fn
@@ -89,10 +95,9 @@ defmodule RealtimeWeb.RealtimeChannel.MessageDispatcher do
8995

9096
{pid,
9197
{:rc_fastlane, fastlane_pid, serializer, join_topic, log_level, tenant_id, replayed_message_ids,
92-
_presence_read?}},
98+
_presence_read?, broadcast_read?}},
9399
cache ->
94-
if already_replayed?(message_id, replayed_message_ids) do
95-
# skip already replayed message
100+
if (broadcast? and broadcast_read? != true) or already_replayed?(message_id, replayed_message_ids) do
96101
cache
97102
else
98103
send(pid, :update_rate_counter)
@@ -110,6 +115,10 @@ defmodule RealtimeWeb.RealtimeChannel.MessageDispatcher do
110115
:ok
111116
end
112117

118+
defp broadcast?(%UserBroadcast{}), do: true
119+
defp broadcast?(%Broadcast{event: @broadcast}), do: true
120+
defp broadcast?(_msg), do: false
121+
113122
defp maybe_log(:info, join_topic, msg, tenant_id) when is_struct(msg) do
114123
log = "Received message on #{join_topic} with payload: #{inspect(msg, pretty: true)}"
115124
Logger.info(log, external_id: tenant_id, project: tenant_id)
@@ -160,7 +169,7 @@ defmodule RealtimeWeb.RealtimeChannel.MessageDispatcher do
160169

161170
defp fastlane!(serializer, msg), do: {:ok, serializer.fastlane!(msg)}
162171

163-
defp tenant_id([{_pid, {:rc_fastlane, _, _, _, _, tenant_id, _, _}} | _]), do: tenant_id
172+
defp tenant_id([{_pid, {:rc_fastlane, _, _, _, _, tenant_id, _, _, _}} | _]), do: tenant_id
164173
defp tenant_id(_), do: nil
165174

166175
defp increment_presence_counter(tenant_id, "presence_diff", count) when is_binary(tenant_id) do

0 commit comments

Comments
 (0)