Add support for xep-0402 - PEP Native Bookmarks

This commit is contained in:
Paweł Chmielowski 2023-09-27 18:36:30 +02:00
parent c3e0b746d7
commit 765770aaa5
3 changed files with 166 additions and 14 deletions

View File

@ -114,7 +114,7 @@ defmodule Ejabberd.MixProject do
{:p1_utils, "~> 1.0"},
{:pkix, "~> 1.0"},
{:stringprep, ">= 1.0.26"},
{:xmpp, git: "https://github.com/processone/xmpp.git", ref: "ef62a043e93c0f472b987847a2a6718714772dcc", override: true},
{:xmpp, git: "https://github.com/processone/xmpp.git", ref: "68cb07d5d0f36d5c51bfea496c638f3ee9b36027", override: true},
{:yconf, "~> 1.0"}]
++ cond_deps()
end

View File

@ -77,7 +77,7 @@
{stringprep, ".*", {git, "https://github.com/processone/stringprep", {tag, "1.0.29"}}},
{if_var_true, stun,
{stun, ".*", {git, "https://github.com/processone/stun", {tag, "1.2.7"}}}},
{xmpp, ".*", {git, "https://github.com/processone/xmpp", "ef62a043e93c0f472b987847a2a6718714772dcc"}},
{xmpp, ".*", {git, "https://github.com/processone/xmpp", "68cb07d5d0f36d5c51bfea496c638f3ee9b36027"}},
{yconf, ".*", {git, "https://github.com/processone/yconf", {tag, "1.0.15"}}}
]}.

View File

@ -29,13 +29,15 @@
-protocol({xep, 49, '1.2'}).
-protocol({xep, 411, '0.2.0', '18.12', "", ""}).
-protocol({xep, 402, '1.1.3', '23.09', "", ""}).
-behaviour(gen_mod).
-export([start/2, stop/1, reload/3, process_sm_iq/1, import_info/0,
remove_user/2, get_data/2, get_data/3, export/1, mod_doc/0,
import/5, import_start/2, mod_opt_type/1, set_data/2,
mod_options/1, depends/2, get_sm_features/5, pubsub_publish_item/6]).
mod_options/1, depends/2, get_sm_features/5, pubsub_publish_item/6,
pubsub_delete_item/5]).
-export([get_commands_spec/0, bookmarks_to_pep/2]).
@ -44,6 +46,7 @@
-include("mod_private.hrl").
-include("ejabberd_commands.hrl").
-include("translate.hrl").
-include("pubsub.hrl").
-define(PRIVATE_CACHE, private_cache).
@ -66,6 +69,7 @@ start(Host, Opts) ->
{ok, [{hook, remove_user, remove_user, 50},
{hook, disco_sm_features, get_sm_features, 50},
{hook, pubsub_publish_item, pubsub_publish_item, 50},
{hook, pubsub_delete_item, pubsub_delete_item, 50},
{iq_handler, ejabberd_sm, ?NS_PRIVATE, process_sm_iq}]}.
stop(Host) ->
@ -150,7 +154,7 @@ get_sm_features({error, _Error} = Acc, _From, _To, _Node, _Lang) ->
get_sm_features(Acc, _From, To, <<"">>, _Lang) ->
case gen_mod:is_loaded(To#jid.lserver, mod_pubsub) of
true ->
{result, [?NS_BOOKMARKS_CONVERSION_0 |
{result, [?NS_BOOKMARKS_CONVERSION_0, ?NS_PEP_BOOKMARKS_COMPAT, ?NS_PEP_BOOKMARKS_COMPAT_PEP |
case Acc of
{result, Features} -> Features;
empty -> []
@ -207,17 +211,21 @@ filter_xmlels(Els) ->
-spec set_data(jid(), [{binary(), xmlel()}]) -> ok | {error, _}.
set_data(JID, Data) ->
set_data(JID, Data, true).
set_data(JID, Data, true, true).
-spec set_data(jid(), [{binary(), xmlel()}], boolean()) -> ok | {error, _}.
set_data(JID, Data, Publish) ->
-spec set_data(jid(), [{binary(), xmlel()}], boolean(), boolean()) -> ok | {error, _}.
set_data(JID, Data, PublishPepStorageBookmarks, PublishPepXmppBookmarks) ->
{LUser, LServer, _} = jid:tolower(JID),
Mod = gen_mod:db_mod(LServer, ?MODULE),
case Mod:set_data(LUser, LServer, Data) of
ok ->
delete_cache(Mod, LUser, LServer, Data),
case Publish of
true -> publish_data(JID, Data);
case PublishPepStorageBookmarks of
true -> publish_pep_storage_bookmarks(JID, Data);
false -> ok
end,
case PublishPepXmppBookmarks of
true -> publish_pep_xmpp_bookmarks(JID, Data);
false -> ok
end;
{error, _} = Err ->
@ -278,8 +286,8 @@ remove_user(User, Server) ->
%%%===================================================================
%%% Pubsub
%%%===================================================================
-spec publish_data(jid(), [{binary(), xmlel()}]) -> ok | {error, stanza_error()}.
publish_data(JID, Data) ->
-spec publish_pep_storage_bookmarks(jid(), [{binary(), xmlel()}]) -> ok | {error, stanza_error()}.
publish_pep_storage_bookmarks(JID, Data) ->
{_, LServer, _} = LBJID = jid:remove_resource(jid:tolower(JID)),
case gen_mod:is_loaded(LServer, mod_pubsub) of
true ->
@ -299,16 +307,154 @@ publish_data(JID, Data) ->
ok
end.
-spec publish_pep_xmpp_bookmarks(jid(), [{binary(), xmlel()}]) -> ok | {error, stanza_error()}.
publish_pep_xmpp_bookmarks(JID, Data) ->
{_, LServer, _} = LBJID = jid:remove_resource(jid:tolower(JID)),
case gen_mod:is_loaded(LServer, mod_pubsub) of
true ->
case lists:keyfind(?NS_STORAGE_BOOKMARKS, 1, Data) of
{_, Bookmarks0} ->
Bookmarks = case xmpp:decode(Bookmarks0) of
#bookmark_storage{conference = C} -> C;
_ -> []
end,
PubOpts = [{persist_items, true},
{access_model, whitelist},
{max_items, max},
{notify_retract,true},
{notify_delete,true},
{send_last_published_item, never}],
case mod_pubsub:get_items(LBJID, ?NS_PEP_BOOKMARKS) of
PepBookmarks when is_list(PepBookmarks) ->
put(mod_private_pep_update, true),
PepBookmarksMap = lists:foldl(fun pubsub_item_to_map/2, #{}, PepBookmarks),
ToDelete =
lists:foldl(
fun(#bookmark_conference{jid = BookmarkJID} = Bookmark, Map2) ->
PB = storage_bookmark_to_xmpp_bookmark(Bookmark),
case maps:take(jid:tolower(BookmarkJID), Map2) of
{StoredBookmark, Map3} when StoredBookmark == PB ->
Map3;
{_, Map4} ->
mod_pubsub:publish_item(
LBJID, LServer, ?NS_PEP_BOOKMARKS, JID,
jid:encode(BookmarkJID), [xmpp:encode(PB)], PubOpts, all),
Map4;
_ ->
mod_pubsub:publish_item(
LBJID, LServer, ?NS_PEP_BOOKMARKS, JID,
jid:encode(BookmarkJID), [xmpp:encode(PB)], PubOpts, all),
Map2
end
end, PepBookmarksMap, Bookmarks),
maps:fold(
fun(DeleteJid, _, _) ->
mod_pubsub:delete_item(LBJID, ?NS_PEP_BOOKMARKS, JID, jid:encode(DeleteJid))
end, ok, ToDelete),
erase(mod_private_pep_update),
ok;
{error, #stanza_error{reason = 'item-not-found'}} ->
put(mod_private_pep_update, true),
lists:foreach(
fun(#bookmark_conference{jid = BookmarkJID} = Bookmark) ->
PB = storage_bookmark_to_xmpp_bookmark(Bookmark),
mod_pubsub:publish_item(
LBJID, LServer, ?NS_PEP_BOOKMARKS, JID,
jid:encode(BookmarkJID), [xmpp:encode(PB)], PubOpts, all)
end, Bookmarks),
erase(mod_private_pep_update),
ok;
_ ->
ok
end;
_ ->
ok
end;
false ->
ok
end.
-spec pubsub_publish_item(binary(), binary(), jid(), jid(),
binary(), [xmlel()]) -> any().
pubsub_publish_item(LServer, ?NS_STORAGE_BOOKMARKS,
#jid{luser = LUser, lserver = LServer} = From,
#jid{luser = LUser, lserver = LServer},
_ItemId, [Payload|_]) ->
set_data(From, [{?NS_STORAGE_BOOKMARKS, Payload}], false);
set_data(From, [{?NS_STORAGE_BOOKMARKS, Payload}], false, true);
pubsub_publish_item(LServer, ?NS_PEP_BOOKMARKS,
#jid{luser = LUser, lserver = LServer} = From,
#jid{luser = LUser, lserver = LServer},
_ItemId, _Payload) ->
NotRecursion = get(mod_private_pep_update) == undefined,
case mod_pubsub:get_items({LUser, LServer, <<>>}, ?NS_PEP_BOOKMARKS) of
Bookmarks when is_list(Bookmarks), NotRecursion ->
Bookmarks2 = lists:filtermap(fun pubsub_item_to_storage_bookmark/1, Bookmarks),
Payload = xmpp:encode(#bookmark_storage{conference = Bookmarks2}),
set_data(From, [{?NS_STORAGE_BOOKMARKS, Payload}], true, false);
_ ->
ok
end;
pubsub_publish_item(_, _, _, _, _, _) ->
ok.
-spec pubsub_delete_item(binary(), binary(), jid(), jid(), binary()) -> any().
pubsub_delete_item(LServer, ?NS_PEP_BOOKMARKS,
#jid{luser = LUser, lserver = LServer} = From,
#jid{luser = LUser, lserver = LServer},
_ItemId) ->
NotRecursion = get(mod_private_pep_update) == undefined,
case mod_pubsub:get_items({LUser, LServer, <<>>}, ?NS_PEP_BOOKMARKS) of
Bookmarks when is_list(Bookmarks), NotRecursion ->
Bookmarks2 = lists:filtermap(fun pubsub_item_to_storage_bookmark/1, Bookmarks),
Payload = xmpp:encode(#bookmark_storage{conference = Bookmarks2}),
set_data(From, [{?NS_STORAGE_BOOKMARKS, Payload}], true, false);
_ ->
ok
end;
pubsub_delete_item(_, _, _, _, _) ->
ok.
-spec pubsub_item_to_storage_bookmark(#pubsub_item{}) -> {true, bookmark_conference()} | false.
pubsub_item_to_storage_bookmark(#pubsub_item{itemid = {Id, _}, payload = [#xmlel{} = B | _]}) ->
case xmpp:decode(B) of
#pep_bookmarks_conference{name = Name, autojoin = AutoJoin, nick = Nick,
password = Password} ->
try jid:decode(Id) of
#jid{} = Jid ->
{true, #bookmark_conference{jid = Jid, name = Name, autojoin = AutoJoin, nick = Nick,
password = Password}}
catch _:_ ->
false
end;
_ ->
false
end;
pubsub_item_to_storage_bookmark(_) ->
false.
-spec storage_bookmark_to_xmpp_bookmark(bookmark_conference()) -> pep_bookmarks_conference().
storage_bookmark_to_xmpp_bookmark(#bookmark_conference{name = Name, autojoin = AutoJoin, nick = Nick,
password = Password}) ->
#pep_bookmarks_conference{name = Name, autojoin = AutoJoin, nick = Nick,
password = Password}.
-spec pubsub_item_to_map(#pubsub_item{}, map()) -> map().
pubsub_item_to_map(#pubsub_item{itemid = {Id, _}, payload = [#xmlel{} = B | _]}, Map) ->
?INFO_MSG("DECODING ~p", [B]),
case xmpp:decode(B) of
#pep_bookmarks_conference{} = B2 ->
try jid:decode(Id) of
#jid{} = Jid ->
maps:put(jid:tolower(Jid), B2#pep_bookmarks_conference{extensions = undefined}, Map)
catch _:_ ->
Map
end;
_ ->
Map
end;
pubsub_item_to_map(_, Map) ->
Map.
%%%===================================================================
%%% Commands
%%%===================================================================
@ -344,11 +490,17 @@ bookmarks_to_pep(User, Server) ->
case Res of
{ok, El} ->
Data = [{?NS_STORAGE_BOOKMARKS, El}],
case publish_data(jid:make(User, Server), Data) of
case publish_pep_storage_bookmarks(jid:make(User, Server), Data) of
ok ->
{ok, <<"Bookmarks exported to PEP node">>};
case publish_pep_xmpp_bookmarks(jid:make(User, Server), Data) of
ok ->
{ok, <<"Bookmarks exported to PEP node">>};
{error, Err} ->
{error, xmpp:format_stanza_error(Err)}
end;
{error, Err} ->
{error, xmpp:format_stanza_error(Err)}
end;
_ ->
{error, <<"Cannot retrieve bookmarks from private XML storage">>}