From de7dc4affa6c6996bed838b5d4b0b53511962ef0 Mon Sep 17 00:00:00 2001 From: Holger Weiss Date: Mon, 16 Apr 2018 23:18:03 +0200 Subject: [PATCH] mod_push: Optionally include message sender/body Add 'include_sender' and 'include_body' options. If one or both of them are set to 'true', a urn:xmpp:push:summary form with the enabled field(s) is included in push notifications that are generated for messages with a body. The 'include_body' option can instead be set to a static text. In this case, the specified text will be included in place of the actual message body. This can be useful to signal the push service whether the notification was triggered by a message with body (as opposed to other types of traffic) without leaking actual message contents. --- rebar.config | 2 +- src/mod_push.erl | 113 ++++++++++++++++++++++++++++++++----- src/mod_push_keepalive.erl | 4 +- 3 files changed, 101 insertions(+), 18 deletions(-) diff --git a/rebar.config b/rebar.config index 823bc3824..a63e6b965 100644 --- a/rebar.config +++ b/rebar.config @@ -25,7 +25,7 @@ {fast_tls, ".*", {git, "https://github.com/processone/fast_tls", "a166f0e"}}, {stringprep, ".*", {git, "https://github.com/processone/stringprep", {tag, "1.0.11"}}}, {fast_xml, ".*", {git, "https://github.com/processone/fast_xml", {tag, "1.1.29"}}}, - {xmpp, ".*", {git, "https://github.com/processone/xmpp", "bb88d59"}}, + {xmpp, ".*", {git, "https://github.com/processone/xmpp", "626a28e"}}, {fast_yaml, ".*", {git, "https://github.com/processone/fast_yaml", {tag, "1.0.13"}}}, {jiffy, ".*", {git, "https://github.com/davisp/jiffy", {tag, "0.14.8"}}}, {p1_oauth2, ".*", {git, "https://github.com/processone/p1_oauth2", {tag, "0.6.2"}}}, diff --git a/src/mod_push.erl b/src/mod_push.erl index 40cfaa625..dd16e87fa 100644 --- a/src/mod_push.erl +++ b/src/mod_push.erl @@ -44,7 +44,7 @@ -export([get_commands_spec/0, delete_old_sessions/1]). %% API (used by mod_push_keepalive). --export([notify/1, notify/3, notify/5]). +-export([notify/2, notify/4, notify/6]). %% For IQ callbacks -export([delete_session/3]). @@ -125,6 +125,12 @@ depends(_Host, _Opts) -> []. -spec mod_opt_type(atom()) -> fun((term()) -> term()) | [atom()]. +mod_opt_type(include_sender) -> + fun (B) when is_boolean(B) -> B end; +mod_opt_type(include_body) -> + fun (B) when is_boolean(B) -> B; + (S) -> iolist_to_binary(S) + end; mod_opt_type(db_type) -> fun(T) -> ejabberd_config:v_db(?MODULE, T) end; mod_opt_type(O) when O == cache_life_time; O == cache_size -> @@ -136,7 +142,9 @@ mod_opt_type(O) when O == use_cache; O == cache_missed -> -spec mod_options(binary()) -> [{atom(), any()}]. mod_options(Host) -> - [{db_type, ejabberd_config:default_db(Host, ?MODULE)}, + [{include_sender, false}, + {include_body, false}, + {db_type, ejabberd_config:default_db(Host, ?MODULE)}, {use_cache, ejabberd_config:use_cache(Host)}, {cache_size, ejabberd_config:cache_size(Host)}, {cache_missed, ejabberd_config:cache_missed(Host)}, @@ -336,9 +344,9 @@ disable(#jid{luser = LUser, lserver = LServer, lresource = LResource} = JID, c2s_stanza(State, #stream_error{}, _SendResult) -> State; c2s_stanza(#{push_enabled := true, mgmt_state := pending} = State, - _Pkt, _SendResult) -> + Pkt, _SendResult) -> ?DEBUG("Notifying client of stanza", []), - notify(State), + notify(State, Pkt), State; c2s_stanza(State, _Pkt, _SendResult) -> State. @@ -351,7 +359,7 @@ mam_message(#message{} = Pkt, LUser, LServer, _Peer, chat, _Dir) -> case drop_online_sessions(LUser, LServer, Clients) of [_|_] = Clients1 -> ?DEBUG("Notifying ~s@~s of MAM message", [LUser, LServer]), - notify(LUser, LServer, Clients1); + notify(LUser, LServer, Clients1, Pkt); [] -> ok end; @@ -369,7 +377,7 @@ offline_message(#message{to = #jid{luser = LUser, lserver = LServer}} = Pkt) -> case lookup_sessions(LUser, LServer) of {ok, [_|_] = Clients} -> ?DEBUG("Notifying ~s@~s of offline message", [LUser, LServer]), - notify(LUser, LServer, Clients); + notify(LUser, LServer, Clients, Pkt); _ -> ok end, @@ -380,7 +388,8 @@ c2s_session_pending(#{push_enabled := true, mgmt_queue := Queue} = State) -> case p1_queue:len(Queue) of Len when Len > 0 -> ?DEBUG("Notifying client of unacknowledged stanza(s)", []), - notify(State), + Pkt = queue_find(fun is_message_with_body/1, Queue), + notify(State, Pkt), State; 0 -> State @@ -412,17 +421,18 @@ remove_user(LUser, LServer) -> %%-------------------------------------------------------------------- %% Generate push notifications. %%-------------------------------------------------------------------- --spec notify(c2s_state()) -> ok. -notify(#{jid := #jid{luser = LUser, lserver = LServer}, sid := {TS, _}}) -> +-spec notify(c2s_state(), xmpp_element() | xmlel() | none) -> ok. +notify(#{jid := #jid{luser = LUser, lserver = LServer}, sid := {TS, _}}, Pkt) -> case lookup_session(LUser, LServer, TS) of {ok, Client} -> - notify(LUser, LServer, [Client]); + notify(LUser, LServer, [Client], Pkt); _Err -> ok end. --spec notify(binary(), binary(), [push_session()]) -> ok. -notify(LUser, LServer, Clients) -> +-spec notify(binary(), binary(), [push_session()], + xmpp_element() | xmlel() | none) -> ok. +notify(LUser, LServer, Clients, Pkt) -> lists:foreach( fun({TS, PushLJID, Node, XData}) -> HandleResponse = fun(#iq{type = result}) -> @@ -433,14 +443,16 @@ notify(LUser, LServer, Clients) -> (timeout) -> ok % Hmm. end, - notify(LServer, PushLJID, Node, XData, HandleResponse) + notify(LServer, PushLJID, Node, XData, Pkt, HandleResponse) end, Clients). -spec notify(binary(), ljid(), binary(), xdata(), + xmpp_element() | xmlel() | none, fun((iq() | timeout) -> any())) -> ok. -notify(LServer, PushLJID, Node, XData, HandleResponse) -> +notify(LServer, PushLJID, Node, XData, Pkt, HandleResponse) -> From = jid:make(LServer), - Item = #ps_item{sub_els = [#push_notification{}]}, + Summary = make_summary(LServer, Pkt), + Item = #ps_item{sub_els = [#push_notification{xdata = Summary}]}, PubSub = #pubsub{publish = #ps_publish{node = Node, items = [Item]}, publish_options = XData}, IQ = #iq{type = set, @@ -571,6 +583,77 @@ drop_online_sessions(LUser, LServer, Clients) -> [Client || {TS, _, _, _} = Client <- Clients, lists:keyfind(TS, 1, SessIDs) == false]. +-spec queue_find(fun((stanza()) -> boolean()), p1_queue:queue()) + -> stanza() | none. +queue_find(Pred, Queue) -> + case p1_queue:out(Queue) of + {{value, {_, _, Pkt}}, Queue1} -> + case Pred(Pkt) of + true -> + Pkt; + false -> + queue_find(Pred, Queue1) + end; + {empty, _Queue1} -> + none + end. + +-spec make_summary(binary(), xmpp_element() | xmlel() | none) + -> xdata() | undefined. +make_summary(Host, #message{from = From} = Pkt) -> + case {gen_mod:get_module_opt(Host, ?MODULE, include_sender), + gen_mod:get_module_opt(Host, ?MODULE, include_body)} of + {false, false} -> + undefined; + {IncludeSender, IncludeBody} -> + case get_body_text(Pkt) of + none -> + undefined; + Text -> + Fields1 = case IncludeBody of + StaticText when is_binary(StaticText) -> + [{'last-message-body', StaticText}]; + true -> + [{'last-message-body', Text}]; + false -> + [] + end, + Fields2 = case IncludeSender of + true -> + [{'last-message-sender', From} | Fields1]; + false -> + Fields1 + end, + #xdata{type = submit, fields = push_summary:encode(Fields2)} + end + end; +make_summary(_Host, _Pkt) -> + undefined. + +-spec is_message_with_body(stanza()) -> boolean(). +is_message_with_body(#message{} = Msg) -> + get_body_text(Msg) /= none; +is_message_with_body(_Stanza) -> + false. + +-spec get_body_text(message()) -> binary() | none. +get_body_text(#message{body = Body} = Msg) -> + case xmpp:get_text(Body) of + Text when byte_size(Text) > 0 -> + Text; + <<>> -> + case body_is_encrypted(Msg) of + true -> + <<"(encrypted)">>; + false -> + none + end + end. + +-spec body_is_encrypted(message()) -> boolean(). +body_is_encrypted(#message{sub_els = SubEls}) -> + lists:keyfind(<<"encrypted">>, #xmlel.name, SubEls) /= false. + %%-------------------------------------------------------------------- %% Caching. %%-------------------------------------------------------------------- diff --git a/src/mod_push_keepalive.erl b/src/mod_push_keepalive.erl index 750427ee1..7c1815c02 100644 --- a/src/mod_push_keepalive.erl +++ b/src/mod_push_keepalive.erl @@ -184,7 +184,7 @@ c2s_handle_cast(State, _Msg) -> c2s_handle_info(#{push_enabled := true, mgmt_state := pending, jid := JID} = State, {timeout, _, push_keepalive}) -> ?INFO_MSG("Waking ~s before session times out", [jid:encode(JID)]), - mod_push:notify(State), + mod_push:notify(State, none), {stop, State}; c2s_handle_info(State, _) -> State. @@ -229,7 +229,7 @@ wake_all(LServer) -> IgnoreResponse = fun(_) -> ok end, lists:foreach(fun({_, PushLJID, Node, XData}) -> mod_push:notify(LServer, PushLJID, Node, - XData, IgnoreResponse) + XData, none, IgnoreResponse) end, Sessions); error -> error