Implement cache for mod_privacy/mod_blocking

This commit is contained in:
Evgeniy Khramtsov 2017-05-20 22:36:32 +03:00
parent 654d907dcf
commit 35d19b32f4
13 changed files with 718 additions and 994 deletions

View File

@ -38,11 +38,3 @@
-type listitem_type() :: none | jid | group | subscription. -type listitem_type() :: none | jid | group | subscription.
-type listitem_value() :: none | both | from | to | jid:ljid() | binary(). -type listitem_value() :: none | both | from | to | jid:ljid() | binary().
-type listitem_action() :: allow | deny. -type listitem_action() :: allow | deny.
-record(userlist, {name = none :: none | binary(),
list = [] :: [listitem()],
needdb = false :: boolean()}).
-type userlist() :: #userlist{}.
-export_type([userlist/0]).

View File

@ -1485,10 +1485,7 @@ privacy_set(Username, Host, QueryS) ->
SubEl = xmpp:decode(QueryEl), SubEl = xmpp:decode(QueryEl),
IQ = #iq{type = set, id = <<"push">>, sub_els = [SubEl], IQ = #iq{type = set, id = <<"push">>, sub_els = [SubEl],
from = From, to = To}, from = From, to = To},
ejabberd_hooks:run_fold(privacy_iq_set, mod_privacy:process_iq(IQ),
Host,
{error, xmpp:err_feature_not_implemented()},
[IQ, #userlist{}]),
ok. ok.
%%% %%%

View File

@ -39,12 +39,6 @@
-include("mod_privacy.hrl"). -include("mod_privacy.hrl").
-callback process_blocklist_block(binary(), binary(), function()) -> {atomic, any()}.
-callback unblock_by_filter(binary(), binary(), function()) -> {atomic, any()}.
-callback process_blocklist_get(binary(), binary()) -> [listitem()] | error.
-type block_event() :: {block, [jid()]} | {unblock, [jid()]} | unblock_all.
start(Host, Opts) -> start(Host, Opts) ->
IQDisc = gen_mod:get_opt(iqdisc, Opts, gen_iq_handler:iqdisc(Host)), IQDisc = gen_mod:get_opt(iqdisc, Opts, gen_iq_handler:iqdisc(Host)),
ejabberd_hooks:add(disco_local_features, Host, ?MODULE, disco_features, 50), ejabberd_hooks:add(disco_local_features, Host, ?MODULE, disco_features, 50),
@ -142,101 +136,111 @@ listitems_to_jids([_ | Items], JIDs) ->
listitems_to_jids(Items, JIDs). listitems_to_jids(Items, JIDs).
-spec process_block(iq(), [ljid()]) -> iq(). -spec process_block(iq(), [ljid()]) -> iq().
process_block(#iq{from = #jid{luser = LUser, lserver = LServer}, process_block(#iq{from = From} = IQ, LJIDs) ->
lang = Lang} = IQ, JIDs) -> #jid{luser = LUser, lserver = LServer} = From,
Filter = fun (List) -> case mod_privacy:get_user_list(LUser, LServer, default) of
AlreadyBlocked = listitems_to_jids(List, []), {error, _} ->
lists:foldr(fun (JID, List1) -> err_db_failure(IQ);
case lists:member(JID, AlreadyBlocked) Res ->
of {Name, List} = case Res of
true -> List1; error -> {<<"Blocked contacts">>, []};
false -> {ok, NameList} -> NameList
[#listitem{type = jid, end,
value = JID, AlreadyBlocked = listitems_to_jids(List, []),
action = deny, NewList = lists:foldr(
order = 0, fun(LJID, List1) ->
match_all = true} case lists:member(LJID, AlreadyBlocked) of
| List1] true ->
end List1;
end, false ->
List, JIDs) [#listitem{type = jid,
end, value = LJID,
Mod = db_mod(LServer), action = deny,
case Mod:process_blocklist_block(LUser, LServer, Filter) of order = 0,
{atomic, {ok, Default, List}} -> match_all = true}|List1]
UserList = make_userlist(Default, List), end
broadcast_list_update(LUser, LServer, UserList, Default), end, List, LJIDs),
broadcast_event(LUser, LServer, case mod_privacy:set_list(LUser, LServer, Name, NewList) of
#block{items = [jid:make(J) || J <- JIDs]}), ok ->
xmpp:make_iq_result(xmpp:put_meta(IQ, privacy_list, UserList)); case (if Res == error ->
_Err -> mod_privacy:set_default_list(
?ERROR_MSG("Error processing ~p: ~p", [{LUser, LServer, JIDs}, _Err]), LUser, LServer, Name);
Err = xmpp:err_internal_server_error(<<"Database failure">>, Lang), true ->
xmpp:make_error(IQ, Err) ok
end) of
ok ->
mod_privacy:push_list_update(From, Name),
Items = [jid:make(LJID) || LJID <- LJIDs],
broadcast_event(From, #block{items = Items}),
xmpp:make_iq_result(IQ);
{error, notfound} ->
?ERROR_MSG("Failed to set default list '~s': "
"the list should exist, but not found",
[Name]),
err_db_failure(IQ);
{error, _} ->
err_db_failure(IQ)
end;
{error, _} ->
err_db_failure(IQ)
end
end. end.
-spec process_unblock_all(iq()) -> iq(). -spec process_unblock_all(iq()) -> iq().
process_unblock_all(#iq{from = #jid{luser = LUser, lserver = LServer}, process_unblock_all(#iq{from = From} = IQ) ->
lang = Lang} = IQ) -> #jid{luser = LUser, lserver = LServer} = From,
Filter = fun (List) -> case mod_privacy:get_user_list(LUser, LServer, default) of
lists:filter(fun (#listitem{action = A}) -> A =/= deny {ok, {Name, List}} ->
end, NewList = lists:filter(
List) fun(#listitem{action = A}) ->
end, A /= deny
Mod = db_mod(LServer), end, List),
case Mod:unblock_by_filter(LUser, LServer, Filter) of case mod_privacy:set_list(LUser, LServer, Name, NewList) of
{atomic, ok} -> ok ->
mod_privacy:push_list_update(From, Name),
broadcast_event(From, #unblock{}),
xmpp:make_iq_result(IQ);
{error, _} ->
err_db_failure(IQ)
end;
error ->
broadcast_event(From, #unblock{}),
xmpp:make_iq_result(IQ); xmpp:make_iq_result(IQ);
{atomic, {ok, Default, List}} -> {error, _} ->
UserList = make_userlist(Default, List), err_db_failure(IQ)
broadcast_list_update(LUser, LServer, UserList, Default),
broadcast_event(LUser, LServer, #unblock{}),
xmpp:make_iq_result(xmpp:put_meta(IQ, privacy_list, UserList));
_Err ->
?ERROR_MSG("Error processing ~p: ~p", [{LUser, LServer}, _Err]),
Err = xmpp:err_internal_server_error(<<"Database failure">>, Lang),
xmpp:make_error(IQ, Err)
end. end.
-spec process_unblock(iq(), [ljid()]) -> iq(). -spec process_unblock(iq(), [ljid()]) -> iq().
process_unblock(#iq{from = #jid{luser = LUser, lserver = LServer}, process_unblock(#iq{from = From} = IQ, LJIDs) ->
lang = Lang} = IQ, JIDs) -> #jid{luser = LUser, lserver = LServer} = From,
Filter = fun (List) -> case mod_privacy:get_user_list(LUser, LServer, default) of
lists:filter(fun (#listitem{action = deny, type = jid, {ok, {Name, List}} ->
value = JID}) -> NewList = lists:filter(
not lists:member(JID, JIDs); fun(#listitem{action = deny, type = jid,
(_) -> true value = LJID}) ->
end, not lists:member(LJID, LJIDs);
List) (_) ->
end, true
Mod = db_mod(LServer), end, List),
case Mod:unblock_by_filter(LUser, LServer, Filter) of case mod_privacy:set_list(LUser, LServer, Name, NewList) of
{atomic, ok} -> ok ->
mod_privacy:push_list_update(From, Name),
Items = [jid:make(LJID) || LJID <- LJIDs],
broadcast_event(From, #unblock{items = Items}),
xmpp:make_iq_result(IQ);
{error, _} ->
err_db_failure(IQ)
end;
error ->
Items = [jid:make(LJID) || LJID <- LJIDs],
broadcast_event(From, #unblock{items = Items}),
xmpp:make_iq_result(IQ); xmpp:make_iq_result(IQ);
{atomic, {ok, Default, List}} -> {error, _} ->
UserList = make_userlist(Default, List), err_db_failure(IQ)
broadcast_list_update(LUser, LServer, UserList, Default),
broadcast_event(LUser, LServer,
#unblock{items = [jid:make(J) || J <- JIDs]}),
xmpp:make_iq_result(xmpp:put_meta(IQ, privacy_list, UserList));
_Err ->
?ERROR_MSG("Error processing ~p: ~p", [{LUser, LServer, JIDs}, _Err]),
Err = xmpp:err_internal_server_error(<<"Database failure">>, Lang),
xmpp:make_error(IQ, Err)
end. end.
-spec make_userlist(binary(), [listitem()]) -> userlist(). -spec broadcast_event(jid(), block() | unblock()) -> ok.
make_userlist(Name, List) -> broadcast_event(#jid{luser = LUser, lserver = LServer} = From, Event) ->
NeedDb = mod_privacy:is_list_needdb(List),
#userlist{name = Name, list = List, needdb = NeedDb}.
-spec broadcast_list_update(binary(), binary(), userlist(), binary()) -> ok.
broadcast_list_update(LUser, LServer, UserList, Name) ->
mod_privacy:push_list_update(jid:make(LUser, LServer), UserList, Name).
-spec broadcast_event(binary(), binary(), block_event()) -> ok.
broadcast_event(LUser, LServer, Event) ->
From = jid:make(LUser, LServer),
lists:foreach( lists:foreach(
fun(R) -> fun(R) ->
To = jid:replace_resource(From, R), To = jid:replace_resource(From, R),
@ -247,23 +251,21 @@ broadcast_event(LUser, LServer, Event) ->
end, ejabberd_sm:get_user_resources(LUser, LServer)). end, ejabberd_sm:get_user_resources(LUser, LServer)).
-spec process_get(iq()) -> iq(). -spec process_get(iq()) -> iq().
process_get(#iq{from = #jid{luser = LUser, lserver = LServer}, process_get(#iq{from = #jid{luser = LUser, lserver = LServer}} = IQ) ->
lang = Lang} = IQ) -> case mod_privacy:get_user_list(LUser, LServer, default) of
Mod = db_mod(LServer), {ok, {_, List}} ->
case Mod:process_blocklist_get(LUser, LServer) of
error ->
Err = xmpp:err_internal_server_error(<<"Database failure">>, Lang),
xmpp:make_error(IQ, Err);
List ->
LJIDs = listitems_to_jids(List, []), LJIDs = listitems_to_jids(List, []),
Items = [jid:make(J) || J <- LJIDs], Items = [jid:make(J) || J <- LJIDs],
xmpp:make_iq_result(IQ, #block_list{items = Items}) xmpp:make_iq_result(IQ, #block_list{items = Items});
error ->
xmpp:make_iq_result(IQ, #block_list{});
{error, _} ->
err_db_failure(IQ)
end. end.
-spec db_mod(binary()) -> module(). err_db_failure(#iq{lang = Lang} = IQ) ->
db_mod(LServer) -> Txt = <<"Database failure">>,
DBType = gen_mod:db_type(LServer, mod_privacy), xmpp:make_error(IQ, xmpp:err_internal_server_error(Txt, Lang)).
gen_mod:db_mod(DBType, ?MODULE).
mod_opt_type(iqdisc) -> fun gen_iq_handler:check_type/1; mod_opt_type(iqdisc) -> fun gen_iq_handler:check_type/1;
mod_opt_type(_) -> [iqdisc]. mod_opt_type(_) -> [iqdisc].

View File

@ -1,100 +0,0 @@
%%%-------------------------------------------------------------------
%%% File : mod_blocking_mnesia.erl
%%% Author : Evgeny Khramtsov <ekhramtsov@process-one.net>
%%% Created : 14 Apr 2016 by Evgeny Khramtsov <ekhramtsov@process-one.net>
%%%
%%%
%%% ejabberd, Copyright (C) 2002-2017 ProcessOne
%%%
%%% This program is free software; you can redistribute it and/or
%%% modify it under the terms of the GNU General Public License as
%%% published by the Free Software Foundation; either version 2 of the
%%% License, or (at your option) any later version.
%%%
%%% This program is distributed in the hope that it will be useful,
%%% but WITHOUT ANY WARRANTY; without even the implied warranty of
%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
%%% General Public License for more details.
%%%
%%% You should have received a copy of the GNU General Public License along
%%% with this program; if not, write to the Free Software Foundation, Inc.,
%%% 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
%%%
%%%----------------------------------------------------------------------
-module(mod_blocking_mnesia).
-behaviour(mod_blocking).
%% API
-export([process_blocklist_block/3, unblock_by_filter/3,
process_blocklist_get/2]).
-include("mod_privacy.hrl").
%%%===================================================================
%%% API
%%%===================================================================
process_blocklist_block(LUser, LServer, Filter) ->
F = fun () ->
case mnesia:wread({privacy, {LUser, LServer}}) of
[] ->
P = #privacy{us = {LUser, LServer}},
NewDefault = <<"Blocked contacts">>,
NewLists1 = [],
List = [];
[#privacy{default = Default, lists = Lists} = P] ->
case lists:keysearch(Default, 1, Lists) of
{value, {_, List}} ->
NewDefault = Default,
NewLists1 = lists:keydelete(Default, 1, Lists);
false ->
NewDefault = <<"Blocked contacts">>,
NewLists1 = Lists,
List = []
end
end,
NewList = Filter(List),
NewLists = [{NewDefault, NewList} | NewLists1],
mnesia:write(P#privacy{default = NewDefault,
lists = NewLists}),
{ok, NewDefault, NewList}
end,
mnesia:transaction(F).
unblock_by_filter(LUser, LServer, Filter) ->
F = fun () ->
case mnesia:read({privacy, {LUser, LServer}}) of
[] ->
%% No lists, nothing to unblock
ok;
[#privacy{default = Default, lists = Lists} = P] ->
case lists:keysearch(Default, 1, Lists) of
{value, {_, List}} ->
NewList = Filter(List),
NewLists1 = lists:keydelete(Default, 1, Lists),
NewLists = [{Default, NewList} | NewLists1],
mnesia:write(P#privacy{lists = NewLists}),
{ok, Default, NewList};
false ->
%% No default list, nothing to unblock
ok
end
end
end,
mnesia:transaction(F).
process_blocklist_get(LUser, LServer) ->
case catch mnesia:dirty_read(privacy, {LUser, LServer}) of
{'EXIT', _Reason} -> error;
[] -> [];
[#privacy{default = Default, lists = Lists}] ->
case lists:keysearch(Default, 1, Lists) of
{value, {_, List}} -> List;
_ -> []
end
end.
%%%===================================================================
%%% Internal functions
%%%===================================================================

View File

@ -1,113 +0,0 @@
%%%-------------------------------------------------------------------
%%% File : mod_blocking_riak.erl
%%% Author : Evgeny Khramtsov <ekhramtsov@process-one.net>
%%% Created : 14 Apr 2016 by Evgeny Khramtsov <ekhramtsov@process-one.net>
%%%
%%%
%%% ejabberd, Copyright (C) 2002-2017 ProcessOne
%%%
%%% This program is free software; you can redistribute it and/or
%%% modify it under the terms of the GNU General Public License as
%%% published by the Free Software Foundation; either version 2 of the
%%% License, or (at your option) any later version.
%%%
%%% This program is distributed in the hope that it will be useful,
%%% but WITHOUT ANY WARRANTY; without even the implied warranty of
%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
%%% General Public License for more details.
%%%
%%% You should have received a copy of the GNU General Public License along
%%% with this program; if not, write to the Free Software Foundation, Inc.,
%%% 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
%%%
%%%----------------------------------------------------------------------
-module(mod_blocking_riak).
-behaviour(mod_blocking).
%% API
-export([process_blocklist_block/3, unblock_by_filter/3,
process_blocklist_get/2]).
-include("mod_privacy.hrl").
%%%===================================================================
%%% API
%%%===================================================================
process_blocklist_block(LUser, LServer, Filter) ->
{atomic,
begin
case ejabberd_riak:get(privacy, mod_privacy_riak:privacy_schema(),
{LUser, LServer}) of
{ok, #privacy{default = Default, lists = Lists} = P} ->
case lists:keysearch(Default, 1, Lists) of
{value, {_, List}} ->
NewDefault = Default,
NewLists1 = lists:keydelete(Default, 1, Lists);
false ->
NewDefault = <<"Blocked contacts">>,
NewLists1 = Lists,
List = []
end;
{error, _} ->
P = #privacy{us = {LUser, LServer}},
NewDefault = <<"Blocked contacts">>,
NewLists1 = [],
List = []
end,
NewList = Filter(List),
NewLists = [{NewDefault, NewList} | NewLists1],
case ejabberd_riak:put(P#privacy{default = NewDefault,
lists = NewLists},
mod_privacy_riak:privacy_schema()) of
ok ->
{ok, NewDefault, NewList};
Err ->
Err
end
end}.
unblock_by_filter(LUser, LServer, Filter) ->
{atomic,
case ejabberd_riak:get(privacy, mod_privacy_riak:privacy_schema(),
{LUser, LServer}) of
{error, _} ->
%% No lists, nothing to unblock
ok;
{ok, #privacy{default = Default, lists = Lists} = P} ->
case lists:keysearch(Default, 1, Lists) of
{value, {_, List}} ->
NewList = Filter(List),
NewLists1 = lists:keydelete(Default, 1, Lists),
NewLists = [{Default, NewList} | NewLists1],
case ejabberd_riak:put(P#privacy{lists = NewLists},
mod_privacy_riak:privacy_schema()) of
ok ->
{ok, Default, NewList};
Err ->
Err
end;
false ->
%% No default list, nothing to unblock
ok
end
end}.
process_blocklist_get(LUser, LServer) ->
case ejabberd_riak:get(privacy, mod_privacy_riak:privacy_schema(),
{LUser, LServer}) of
{ok, #privacy{default = Default, lists = Lists}} ->
case lists:keysearch(Default, 1, Lists) of
{value, {_, List}} -> List;
_ -> []
end;
{error, notfound} ->
[];
{error, _} ->
error
end.
%%%===================================================================
%%% Internal functions
%%%===================================================================

View File

@ -1,107 +0,0 @@
%%%-------------------------------------------------------------------
%%% File : mod_blocking_sql.erl
%%% Author : Evgeny Khramtsov <ekhramtsov@process-one.net>
%%% Created : 14 Apr 2016 by Evgeny Khramtsov <ekhramtsov@process-one.net>
%%%
%%%
%%% ejabberd, Copyright (C) 2002-2017 ProcessOne
%%%
%%% This program is free software; you can redistribute it and/or
%%% modify it under the terms of the GNU General Public License as
%%% published by the Free Software Foundation; either version 2 of the
%%% License, or (at your option) any later version.
%%%
%%% This program is distributed in the hope that it will be useful,
%%% but WITHOUT ANY WARRANTY; without even the implied warranty of
%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
%%% General Public License for more details.
%%%
%%% You should have received a copy of the GNU General Public License along
%%% with this program; if not, write to the Free Software Foundation, Inc.,
%%% 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
%%%
%%%----------------------------------------------------------------------
-module(mod_blocking_sql).
-behaviour(mod_blocking).
%% API
-export([process_blocklist_block/3, unblock_by_filter/3,
process_blocklist_get/2]).
-include("mod_privacy.hrl").
%%%===================================================================
%%% API
%%%===================================================================
process_blocklist_block(LUser, LServer, Filter) ->
F = fun () ->
Default = case mod_privacy_sql:sql_get_default_privacy_list_t(LUser) of
{selected, []} ->
Name = <<"Blocked contacts">>,
case mod_privacy_sql:sql_get_privacy_list_id_t(LUser, Name) of
{selected, []} ->
mod_privacy_sql:sql_add_privacy_list(LUser, Name);
{selected, [{_ID}]} ->
ok
end,
mod_privacy_sql:sql_set_default_privacy_list(LUser, Name),
Name;
{selected, [{Name}]} -> Name
end,
{selected, [{ID}]} =
mod_privacy_sql:sql_get_privacy_list_id_t(LUser, Default),
case mod_privacy_sql:sql_get_privacy_list_data_by_id_t(ID) of
{selected, RItems = [_ | _]} ->
List = lists:flatmap(fun mod_privacy_sql:raw_to_item/1, RItems);
_ ->
List = []
end,
NewList = Filter(List),
NewRItems = lists:map(fun mod_privacy_sql:item_to_raw/1,
NewList),
mod_privacy_sql:sql_set_privacy_list(ID, NewRItems),
{ok, Default, NewList}
end,
ejabberd_sql:sql_transaction(LServer, F).
unblock_by_filter(LUser, LServer, Filter) ->
F = fun () ->
case mod_privacy_sql:sql_get_default_privacy_list_t(LUser) of
{selected, []} -> ok;
{selected, [{Default}]} ->
{selected, [{ID}]} =
mod_privacy_sql:sql_get_privacy_list_id_t(LUser, Default),
case mod_privacy_sql:sql_get_privacy_list_data_by_id_t(ID) of
{selected, RItems = [_ | _]} ->
List = lists:flatmap(fun mod_privacy_sql:raw_to_item/1,
RItems),
NewList = Filter(List),
NewRItems = lists:map(fun mod_privacy_sql:item_to_raw/1,
NewList),
mod_privacy_sql:sql_set_privacy_list(ID, NewRItems),
{ok, Default, NewList};
_ -> ok
end;
_ -> ok
end
end,
ejabberd_sql:sql_transaction(LServer, F).
process_blocklist_get(LUser, LServer) ->
case catch mod_privacy_sql:sql_get_default_privacy_list(LUser, LServer) of
{selected, []} -> [];
{selected, [{Default}]} ->
case catch mod_privacy_sql:sql_get_privacy_list_data(
LUser, LServer, Default) of
{selected, RItems} ->
lists:flatmap(fun mod_privacy_sql:raw_to_item/1, RItems);
{'EXIT', _} -> error
end;
{'EXIT', _} -> error
end.
%%%===================================================================
%%% Internal functions
%%%===================================================================

View File

@ -31,42 +31,51 @@
-behaviour(gen_mod). -behaviour(gen_mod).
-export([start/2, stop/1, reload/3, process_iq/1, export/1, import_info/0, -export([start/2, stop/1, reload/3, process_iq/1, export/1,
c2s_session_opened/1, c2s_copy_session/2, push_list_update/3, c2s_copy_session/2, push_list_update/2, disco_features/5,
user_send_packet/1, user_receive_packet/1, disco_features/5,
check_packet/4, remove_user/2, encode_list_item/1, check_packet/4, remove_user/2, encode_list_item/1,
is_list_needdb/1, import_start/2, import_stop/2, get_user_lists/2, get_user_list/3,
item_to_xml/1, get_user_lists/2, import/5, set_list/1, set_list/4, set_default_list/3,
set_privacy_list/1, mod_opt_type/1, depends/2]). user_send_packet/1, user_receive_packet/1,
import_start/2, import_stop/2, import/5, import_info/0,
mod_opt_type/1, depends/2]).
-include("ejabberd.hrl"). -include("ejabberd.hrl").
-include("logger.hrl"). -include("logger.hrl").
-include("xmpp.hrl"). -include("xmpp.hrl").
-include("mod_privacy.hrl"). -include("mod_privacy.hrl").
-define(PRIVACY_CACHE, privacy_cache).
-define(PRIVACY_LIST_CACHE, privacy_list_cache).
-type c2s_state() :: ejabberd_c2s:state().
-callback init(binary(), gen_mod:opts()) -> any(). -callback init(binary(), gen_mod:opts()) -> any().
-callback import(#privacy{}) -> ok. -callback import(#privacy{}) -> ok.
-callback process_lists_get(binary(), binary()) -> {none | binary(), [binary()]} | error. -callback set_default(binary(), binary(), binary()) ->
-callback process_list_get(binary(), binary(), binary()) -> [listitem()] | error | not_found. ok | {error, notfound | any()}.
-callback process_default_set(binary(), binary(), binary() | none) -> {atomic, any()}. -callback unset_default(binary(), binary()) -> ok | {error, any()}.
-callback process_active_set(binary(), binary(), binary()) -> [listitem()] | error. -callback remove_list(binary(), binary(), binary()) ->
-callback remove_privacy_list(binary(), binary(), binary()) -> {atomic, any()}. ok | {error, notfound | conflict | any()}.
-callback set_privacy_list(#privacy{}) -> any(). -callback remove_lists(binary(), binary()) -> ok | {error, any()}.
-callback set_privacy_list(binary(), binary(), binary(), [listitem()]) -> {atomic, any()}. -callback set_lists(#privacy{}) -> ok | {error, any()}.
-callback get_user_list(binary(), binary()) -> {none | binary(), [listitem()]}. -callback set_list(binary(), binary(), binary(), listitem()) ->
-callback get_user_lists(binary(), binary()) -> {ok, #privacy{}} | error. ok | {error, any()}.
-callback remove_user(binary(), binary()) -> any(). -callback get_list(binary(), binary(), binary() | default) ->
{ok, {binary(), [listitem()]}} | error | {error, any()}.
-callback get_lists(binary(), binary()) ->
{ok, #privacy{}} | error | {error, any()}.
-callback use_cache(binary()) -> boolean().
-callback cache_nodes(binary()) -> [node()].
-optional_callbacks([use_cache/1, cache_nodes/1]).
start(Host, Opts) -> start(Host, Opts) ->
IQDisc = gen_mod:get_opt(iqdisc, Opts, gen_iq_handler:iqdisc(Host)), IQDisc = gen_mod:get_opt(iqdisc, Opts, gen_iq_handler:iqdisc(Host)),
Mod = gen_mod:db_mod(Host, Opts, ?MODULE), Mod = gen_mod:db_mod(Host, Opts, ?MODULE),
Mod:init(Host, Opts), Mod:init(Host, Opts),
init_cache(Mod, Host, Opts),
ejabberd_hooks:add(disco_local_features, Host, ?MODULE, ejabberd_hooks:add(disco_local_features, Host, ?MODULE,
disco_features, 50), disco_features, 50),
ejabberd_hooks:add(c2s_session_opened, Host, ?MODULE,
c2s_session_opened, 50),
ejabberd_hooks:add(c2s_copy_session, Host, ?MODULE, ejabberd_hooks:add(c2s_copy_session, Host, ?MODULE,
c2s_copy_session, 50), c2s_copy_session, 50),
ejabberd_hooks:add(user_send_packet, Host, ?MODULE, ejabberd_hooks:add(user_send_packet, Host, ?MODULE,
@ -83,8 +92,6 @@ start(Host, Opts) ->
stop(Host) -> stop(Host) ->
ejabberd_hooks:delete(disco_local_features, Host, ?MODULE, ejabberd_hooks:delete(disco_local_features, Host, ?MODULE,
disco_features, 50), disco_features, 50),
ejabberd_hooks:delete(c2s_session_opened, Host, ?MODULE,
c2s_session_opened, 50),
ejabberd_hooks:delete(c2s_copy_session, Host, ?MODULE, ejabberd_hooks:delete(c2s_copy_session, Host, ?MODULE,
c2s_copy_session, 50), c2s_copy_session, 50),
ejabberd_hooks:delete(user_send_packet, Host, ?MODULE, ejabberd_hooks:delete(user_send_packet, Host, ?MODULE,
@ -106,6 +113,7 @@ reload(Host, NewOpts, OldOpts) ->
true -> true ->
ok ok
end, end,
init_cache(NewMod, Host, NewOpts),
case gen_mod:is_equal_opt(iqdisc, NewOpts, OldOpts, gen_iq_handler:iqdisc(Host)) of case gen_mod:is_equal_opt(iqdisc, NewOpts, OldOpts, gen_iq_handler:iqdisc(Host)) of
{false, IQDisc, _} -> {false, IQDisc, _} ->
gen_iq_handler:add_iq_handler(ejabberd_sm, Host, ?NS_PRIVACY, gen_iq_handler:add_iq_handler(ejabberd_sm, Host, ?NS_PRIVACY,
@ -162,47 +170,41 @@ process_iq_get(#iq{lang = Lang} = IQ) ->
-spec process_lists_get(iq()) -> iq(). -spec process_lists_get(iq()) -> iq().
process_lists_get(#iq{from = #jid{luser = LUser, lserver = LServer}, process_lists_get(#iq{from = #jid{luser = LUser, lserver = LServer},
lang = Lang, lang = Lang} = IQ) ->
meta = #{privacy_active_list := Active}} = IQ) -> case get_user_lists(LUser, LServer) of
Mod = gen_mod:db_mod(LServer, ?MODULE), {ok, #privacy{default = Default, lists = Lists}} ->
case Mod:process_lists_get(LUser, LServer) of Active = xmpp:get_meta(IQ, privacy_active_list, none),
error ->
Txt = <<"Database failure">>,
xmpp:make_error(IQ, xmpp:err_internal_server_error(Txt, Lang));
{_Default, []} ->
xmpp:make_iq_result(IQ, #privacy_query{});
{Default, ListNames} ->
xmpp:make_iq_result( xmpp:make_iq_result(
IQ, IQ, #privacy_query{active = Active,
#privacy_query{active = Active, default = Default,
default = Default, lists = [#privacy_list{name = Name}
lists = [#privacy_list{name = ListName} || {Name, _} <- Lists]});
|| ListName <- ListNames]}) error ->
xmpp:make_iq_result(
IQ, #privacy_query{active = none, default = none});
{error, _} ->
Txt = <<"Database failure">>,
xmpp:make_error(IQ, xmpp:err_internal_server_error(Txt, Lang))
end. end.
-spec process_list_get(iq(), binary()) -> iq(). -spec process_list_get(iq(), binary()) -> iq().
process_list_get(#iq{from = #jid{luser = LUser, lserver = LServer}, process_list_get(#iq{from = #jid{luser = LUser, lserver = LServer},
lang = Lang} = IQ, Name) -> lang = Lang} = IQ, Name) ->
Mod = gen_mod:db_mod(LServer, ?MODULE), case get_user_list(LUser, LServer, Name) of
case Mod:process_list_get(LUser, LServer, Name) of {ok, {_, List}} ->
error -> Items = lists:map(fun encode_list_item/1, List),
Txt = <<"Database failure">>,
xmpp:make_error(IQ, xmpp:err_internal_server_error(Txt, Lang));
not_found ->
Txt = <<"No privacy list with this name found">>,
xmpp:make_error(IQ, xmpp:err_item_not_found(Txt, Lang));
Items ->
LItems = lists:map(fun encode_list_item/1, Items),
xmpp:make_iq_result( xmpp:make_iq_result(
IQ, IQ,
#privacy_query{ #privacy_query{
lists = [#privacy_list{name = Name, items = LItems}]}) lists = [#privacy_list{name = Name, items = Items}]});
error ->
Txt = <<"No privacy list with this name found">>,
xmpp:make_error(IQ, xmpp:err_item_not_found(Txt, Lang));
{error, _} ->
Txt = <<"Database failure">>,
xmpp:make_error(IQ, xmpp:err_internal_server_error(Txt, Lang))
end. end.
-spec item_to_xml(listitem()) -> xmlel().
item_to_xml(ListItem) ->
xmpp:encode(encode_list_item(ListItem)).
-spec encode_list_item(listitem()) -> privacy_item(). -spec encode_list_item(listitem()) -> privacy_item().
encode_list_item(#listitem{action = Action, encode_list_item(#listitem{action = Action,
order = Order, order = Order,
@ -283,69 +285,69 @@ process_iq_set(#iq{lang = Lang} = IQ) ->
Txt = <<"No module is handling this query">>, Txt = <<"No module is handling this query">>,
xmpp:make_error(IQ, xmpp:err_service_unavailable(Txt, Lang)). xmpp:make_error(IQ, xmpp:err_service_unavailable(Txt, Lang)).
-spec process_default_set(iq(), binary()) -> iq(). -spec process_default_set(iq(), none | binary()) -> iq().
process_default_set(#iq{from = #jid{luser = LUser, lserver = LServer}, process_default_set(#iq{from = #jid{luser = LUser, lserver = LServer},
lang = Lang} = IQ, Value) -> lang = Lang} = IQ, Value) ->
Mod = gen_mod:db_mod(LServer, ?MODULE), case set_default_list(LUser, LServer, Value) of
case Mod:process_default_set(LUser, LServer, Value) of ok ->
{atomic, error} -> xmpp:make_iq_result(IQ);
Txt = <<"Database failure">>, {error, notfound} ->
xmpp:make_error(IQ, xmpp:err_internal_server_error(Txt, Lang));
{atomic, not_found} ->
Txt = <<"No privacy list with this name found">>, Txt = <<"No privacy list with this name found">>,
xmpp:make_error(IQ, xmpp:err_item_not_found(Txt, Lang)); xmpp:make_error(IQ, xmpp:err_item_not_found(Txt, Lang));
{atomic, ok} -> {error, _} ->
xmpp:make_iq_result(IQ); Txt = <<"Database failure">>,
Err -> xmpp:make_error(IQ, xmpp:err_internal_server_error(Txt, Lang))
?ERROR_MSG("failed to set default list '~s' for user ~s@~s: ~p",
[Value, LUser, LServer, Err]),
xmpp:make_error(IQ, xmpp:err_internal_server_error())
end. end.
-spec process_active_set(IQ, none | binary()) -> IQ. -spec process_active_set(IQ, none | binary()) -> IQ.
process_active_set(IQ, none) -> process_active_set(IQ, none) ->
xmpp:make_iq_result(xmpp:put_meta(IQ, privacy_list, #userlist{})); xmpp:make_iq_result(xmpp:put_meta(IQ, privacy_active_list, none));
process_active_set(#iq{from = #jid{luser = LUser, lserver = LServer}, process_active_set(#iq{from = #jid{luser = LUser, lserver = LServer},
lang = Lang} = IQ, Name) -> lang = Lang} = IQ, Name) ->
Mod = gen_mod:db_mod(LServer, ?MODULE), case get_user_list(LUser, LServer, Name) of
case Mod:process_active_set(LUser, LServer, Name) of {ok, _} ->
xmpp:make_iq_result(xmpp:put_meta(IQ, privacy_active_list, Name));
error -> error ->
Txt = <<"No privacy list with this name found">>, Txt = <<"No privacy list with this name found">>,
xmpp:make_error(IQ, xmpp:err_item_not_found(Txt, Lang)); xmpp:make_error(IQ, xmpp:err_item_not_found(Txt, Lang));
Items -> {error, _} ->
NeedDb = is_list_needdb(Items),
List = #userlist{name = Name, list = Items, needdb = NeedDb},
xmpp:make_iq_result(xmpp:put_meta(IQ, privacy_list, List))
end.
-spec set_privacy_list(privacy()) -> any().
set_privacy_list(#privacy{us = {_, LServer}} = Privacy) ->
Mod = gen_mod:db_mod(LServer, ?MODULE),
Mod:set_privacy_list(Privacy).
-spec process_lists_set(iq(), binary(), [privacy_item()]) -> iq().
process_lists_set(#iq{meta = #{privacy_active_list := Name},
lang = Lang} = IQ, Name, []) ->
Txt = <<"Cannot remove active list">>,
xmpp:make_error(IQ, xmpp:err_conflict(Txt, Lang));
process_lists_set(#iq{from = #jid{luser = LUser, lserver = LServer} = From,
lang = Lang} = IQ, Name, []) ->
Mod = gen_mod:db_mod(LServer, ?MODULE),
case Mod:remove_privacy_list(LUser, LServer, Name) of
{atomic, conflict} ->
Txt = <<"Cannot remove default list">>,
xmpp:make_error(IQ, xmpp:err_conflict(Txt, Lang));
{atomic, not_found} ->
Txt = <<"No privacy list with this name found">>,
xmpp:make_error(IQ, xmpp:err_item_not_found(Txt, Lang));
{atomic, ok} ->
push_list_update(From, #userlist{name = Name}, Name),
xmpp:make_iq_result(IQ);
Err ->
?ERROR_MSG("failed to remove privacy list '~s' for user ~s@~s: ~p",
[Name, LUser, LServer, Err]),
Txt = <<"Database failure">>, Txt = <<"Database failure">>,
xmpp:make_error(IQ, xmpp:err_internal_server_error(Txt, Lang)) xmpp:make_error(IQ, xmpp:err_internal_server_error(Txt, Lang))
end.
-spec set_list(privacy()) -> ok | {error, any()}.
set_list(#privacy{us = {LUser, LServer}, lists = Lists} = Privacy) ->
Mod = gen_mod:db_mod(LServer, ?MODULE),
case Mod:set_lists(Privacy) of
ok ->
Names = [Name || {Name, _} <- Lists],
delete_cache(Mod, LUser, LServer, Names);
{error, _} = Err ->
Err
end.
-spec process_lists_set(iq(), binary(), [privacy_item()]) -> iq().
process_lists_set(#iq{from = #jid{luser = LUser, lserver = LServer},
lang = Lang} = IQ, Name, []) ->
case xmpp:get_meta(IQ, privacy_active_list, none) of
Name ->
Txt = <<"Cannot remove active list">>,
xmpp:make_error(IQ, xmpp:err_conflict(Txt, Lang));
_ ->
case remove_list(LUser, LServer, Name) of
ok ->
xmpp:make_iq_result(IQ);
{error, conflict} ->
Txt = <<"Cannot remove default list">>,
xmpp:make_error(IQ, xmpp:err_conflict(Txt, Lang));
{error, notfound} ->
Txt = <<"No privacy list with this name found">>,
xmpp:make_error(IQ, xmpp:err_item_not_found(Txt, Lang));
{error, _} ->
Txt = <<"Database failure">>,
Err = xmpp:err_internal_server_error(Txt, Lang),
xmpp:make_error(IQ, Err)
end
end; end;
process_lists_set(#iq{from = #jid{luser = LUser, lserver = LServer} = From, process_lists_set(#iq{from = #jid{luser = LUser, lserver = LServer} = From,
lang = Lang} = IQ, Name, Items) -> lang = Lang} = IQ, Name, Items) ->
@ -354,24 +356,18 @@ process_lists_set(#iq{from = #jid{luser = LUser, lserver = LServer} = From,
Txt = xmpp:format_error(Why), Txt = xmpp:format_error(Why),
xmpp:make_error(IQ, xmpp:err_bad_request(Txt, Lang)); xmpp:make_error(IQ, xmpp:err_bad_request(Txt, Lang));
List -> List ->
Mod = gen_mod:db_mod(LServer, ?MODULE), case set_list(LUser, LServer, Name, List) of
case Mod:set_privacy_list(LUser, LServer, Name, List) of ok ->
{atomic, ok} -> push_list_update(From, Name),
UserList = #userlist{name = Name, list = List,
needdb = is_list_needdb(List)},
push_list_update(From, UserList, Name),
xmpp:make_iq_result(IQ); xmpp:make_iq_result(IQ);
Err -> {error, _} ->
?ERROR_MSG("failed to set privacy list '~s' "
"for user ~s@~s: ~p",
[Name, LUser, LServer, Err]),
Txt = <<"Database failure">>, Txt = <<"Database failure">>,
xmpp:make_error(IQ, xmpp:err_internal_server_error(Txt, Lang)) xmpp:make_error(IQ, xmpp:err_internal_server_error(Txt, Lang))
end end
end. end.
-spec push_list_update(jid(), #userlist{}, binary() | none) -> ok. -spec push_list_update(jid(), binary()) -> ok.
push_list_update(From, List, Name) -> push_list_update(From, Name) ->
BareFrom = jid:remove_resource(From), BareFrom = jid:remove_resource(From),
lists:foreach( lists:foreach(
fun(R) -> fun(R) ->
@ -379,44 +375,10 @@ push_list_update(From, List, Name) ->
IQ = #iq{type = set, from = BareFrom, to = To, IQ = #iq{type = set, from = BareFrom, to = To,
id = <<"push", (randoms:get_string())/binary>>, id = <<"push", (randoms:get_string())/binary>>,
sub_els = [#privacy_query{ sub_els = [#privacy_query{
lists = [#privacy_list{name = Name}]}], lists = [#privacy_list{name = Name}]}]},
meta = #{privacy_updated_list => List}},
ejabberd_router:route(IQ) ejabberd_router:route(IQ)
end, ejabberd_sm:get_user_resources(From#jid.luser, From#jid.lserver)). end, ejabberd_sm:get_user_resources(From#jid.luser, From#jid.lserver)).
-spec user_send_packet({stanza(), ejabberd_c2s:state()}) -> {stanza(), ejabberd_c2s:state()}.
user_send_packet({#iq{type = Type,
to = #jid{luser = U, lserver = S, lresource = <<"">>},
from = #jid{luser = U, lserver = S},
sub_els = [_]} = IQ,
#{privacy_list := #userlist{name = Name}} = State})
when Type == get; Type == set ->
NewIQ = case xmpp:has_subtag(IQ, #privacy_query{}) of
true -> xmpp:put_meta(IQ, privacy_active_list, Name);
false -> IQ
end,
{NewIQ, State};
user_send_packet(Acc) ->
Acc.
-spec user_receive_packet({stanza(), ejabberd_c2s:state()}) -> {stanza(), ejabberd_c2s:state()}.
user_receive_packet({#iq{type = result, meta = #{privacy_list := List}} = IQ,
State}) ->
{IQ, State#{privacy_list => List}};
user_receive_packet({#iq{type = set, meta = #{privacy_updated_list := New}} = IQ,
#{user := U, server := S, resource := R,
privacy_list := Old} = State}) ->
State1 = if Old#userlist.name == New#userlist.name ->
State#{privacy_list => New};
true ->
State
end,
From = jid:make(U, S),
To = jid:make(U, S, R),
{xmpp:set_from_to(IQ, From, To), State1};
user_receive_packet(Acc) ->
Acc.
-spec decode_item(privacy_item()) -> listitem(). -spec decode_item(privacy_item()) -> listitem().
decode_item(#privacy_item{order = Order, decode_item(#privacy_item{order = Order,
action = Action, action = Action,
@ -448,47 +410,145 @@ decode_item(#privacy_item{order = Order,
match_presence_out = MatchPresenceOut} match_presence_out = MatchPresenceOut}
end. end.
-spec is_list_needdb([listitem()]) -> boolean(). -spec c2s_copy_session(ejabberd_c2s:state(), c2s_state()) -> c2s_state().
is_list_needdb(Items) -> c2s_copy_session(State, #{privacy_active_list := List}) ->
lists:any(fun (X) -> State#{privacy_active_list => List}.
case X#listitem.type of
subscription -> true;
group -> true;
_ -> false
end
end,
Items).
-spec get_user_list(binary(), binary()) -> #userlist{}. -spec user_send_packet({stanza(), c2s_state()}) -> {stanza(), c2s_state()}.
get_user_list(LUser, LServer) -> user_send_packet({#iq{type = Type,
to = #jid{luser = U, lserver = S, lresource = <<"">>},
from = #jid{luser = U, lserver = S},
sub_els = [_]} = IQ,
#{privacy_active_list := Name} = State})
when Type == get; Type == set ->
NewIQ = case xmpp:has_subtag(IQ, #privacy_query{}) of
true -> xmpp:put_meta(IQ, privacy_active_list, Name);
false -> IQ
end,
{NewIQ, State};
user_send_packet(Acc) ->
Acc.
-spec user_receive_packet({stanza(), c2s_state()}) -> {stanza(), c2s_state()}.
user_receive_packet({#iq{type = result,
meta = #{privacy_active_list := Name}} = IQ, State}) ->
{IQ, State#{privacy_active_list => Name}};
user_receive_packet(Acc) ->
Acc.
-spec set_list(binary(), binary(), binary(), [listitem()]) -> ok | {error, any()}.
set_list(LUser, LServer, Name, List) ->
Mod = gen_mod:db_mod(LServer, ?MODULE), Mod = gen_mod:db_mod(LServer, ?MODULE),
{Default, Items} = Mod:get_user_list(LUser, LServer), case Mod:set_list(LUser, LServer, Name, List) of
NeedDb = is_list_needdb(Items), ok ->
#userlist{name = Default, list = Items, needdb = NeedDb}. delete_cache(Mod, LUser, LServer, [Name]);
{error, _} = Err ->
Err
end.
-spec c2s_session_opened(ejabberd_c2s:state()) -> ejabberd_c2s:state(). -spec remove_list(binary(), binary(), binary()) ->
c2s_session_opened(#{jid := #jid{luser = LUser, lserver = LServer}} = State) -> ok | {error, conflict | notfound | any()}.
State#{privacy_list => get_user_list(LUser, LServer)}. remove_list(LUser, LServer, Name) ->
Mod = gen_mod:db_mod(LServer, ?MODULE),
case Mod:remove_list(LUser, LServer, Name) of
ok ->
delete_cache(Mod, LUser, LServer, [Name]);
Err ->
Err
end.
-spec c2s_copy_session(ejabberd_c2s:state(), ejabberd_c2s:state()) -> ejabberd_c2s:state(). -spec get_user_lists(binary(), binary()) -> {ok, privacy()} | error | {error, any()}.
c2s_copy_session(State, #{privacy_list := List}) ->
State#{privacy_list => List}.
-spec get_user_lists(binary(), binary()) -> {ok, privacy()} | error.
get_user_lists(User, Server) -> get_user_lists(User, Server) ->
LUser = jid:nodeprep(User), LUser = jid:nodeprep(User),
LServer = jid:nameprep(Server), LServer = jid:nameprep(Server),
Mod = gen_mod:db_mod(LServer, ?MODULE), Mod = gen_mod:db_mod(LServer, ?MODULE),
Mod:get_user_lists(LUser, LServer). case use_cache(Mod, LServer) of
true ->
ets_cache:lookup(
?PRIVACY_CACHE, {LUser, LServer},
fun() -> Mod:get_lists(LUser, LServer) end);
false ->
Mod:get_lists(LUser, LServer)
end.
-spec get_user_list(binary(), binary(), binary() | default) ->
{ok, {binary(), [listitem()]}} | error | {error, any()}.
get_user_list(LUser, LServer, Name) ->
Mod = gen_mod:db_mod(LServer, ?MODULE),
case use_cache(Mod, LServer) of
true ->
ets_cache:lookup(
?PRIVACY_LIST_CACHE, {LUser, LServer, Name},
fun() ->
case ets_cache:lookup(
?PRIVACY_CACHE, {LUser, LServer}) of
{ok, Privacy} ->
get_list_by_name(Privacy, Name);
error ->
Mod:get_list(LUser, LServer, Name)
end
end);
false ->
Mod:get_list(LUser, LServer, Name)
end.
-spec get_list_by_name(#privacy{}, binary() | default) ->
{ok, {binary(), [listitem()]}} | error.
get_list_by_name(#privacy{default = Default} = Privacy, default) ->
get_list_by_name(Privacy, Default);
get_list_by_name(#privacy{lists = Lists}, Name) ->
case lists:keyfind(Name, 1, Lists) of
{_, List} -> {ok, {Name, List}};
false -> error
end.
-spec set_default_list(binary(), binary(), binary() | none) ->
ok | {error, notfound | any()}.
set_default_list(LUser, LServer, Name) ->
Mod = gen_mod:db_mod(LServer, ?MODULE),
Res = case Name of
none -> Mod:unset_default(LUser, LServer);
_ -> Mod:set_default(LUser, LServer, Name)
end,
case Res of
ok ->
delete_cache(Mod, LUser, LServer, []);
Err ->
Err
end.
-spec check_packet(allow | deny, c2s_state() | jid(), stanza(), in | out) -> allow | deny.
check_packet(Acc, #{jid := JID} = State, Packet, Dir) ->
case maps:get(privacy_active_list, State, none) of
none ->
check_packet(Acc, JID, Packet, Dir);
ListName ->
#jid{luser = LUser, lserver = LServer} = JID,
case get_user_list(LUser, LServer, ListName) of
{ok, {_, List}} ->
do_check_packet(JID, List, Packet, Dir);
_ ->
?DEBUG("Non-existing active list '~s' is set "
"for user '~s'", [ListName, jid:encode(JID)]),
check_packet(Acc, JID, Packet, Dir)
end
end;
check_packet(_, JID, Packet, Dir) ->
#jid{luser = LUser, lserver = LServer} = JID,
case get_user_list(LUser, LServer, default) of
{ok, {_, List}} ->
do_check_packet(JID, List, Packet, Dir);
_ ->
allow
end.
%% From is the sender, To is the destination. %% From is the sender, To is the destination.
%% If Dir = out, User@Server is the sender account (From). %% If Dir = out, User@Server is the sender account (From).
%% If Dir = in, User@Server is the destination account (To). %% If Dir = in, User@Server is the destination account (To).
-spec check_packet(allow | deny, ejabberd_c2s:state() | jid(), -spec do_check_packet(jid(), [listitem()], stanza(), in | out) -> allow | deny.
stanza(), in | out) -> allow | deny. do_check_packet(_, [], _, _) ->
check_packet(_, #{jid := #jid{luser = LUser, lserver = LServer}, allow;
privacy_list := #userlist{list = List, needdb = NeedDb}}, do_check_packet(#jid{luser = LUser, lserver = LServer}, List, Packet, Dir) ->
Packet, Dir) ->
From = xmpp:get_from(Packet), From = xmpp:get_from(Packet),
To = xmpp:get_to(Packet), To = xmpp:get_to(Packet),
case {From, To} of case {From, To} of
@ -508,8 +568,6 @@ check_packet(_, #{jid := #jid{luser = LUser, lserver = LServer},
#jid{luser = LUser, lserver = LServer, lresource = <<"">>}} when Dir == out -> #jid{luser = LUser, lserver = LServer, lresource = <<"">>}} when Dir == out ->
%% Allow outgoing packets from user's full jid to his bare JID %% Allow outgoing packets from user's full jid to his bare JID
allow; allow;
_ when List == [] ->
allow;
_ -> _ ->
PType = case Packet of PType = case Packet of
#message{} -> message; #message{} -> message;
@ -529,21 +587,11 @@ check_packet(_, #{jid := #jid{luser = LUser, lserver = LServer},
in -> jid:tolower(From); in -> jid:tolower(From);
out -> jid:tolower(To) out -> jid:tolower(To)
end, end,
{Subscription, Groups} = {Subscription, Groups} = ejabberd_hooks:run_fold(
case NeedDb of roster_get_jid_info, LServer,
true -> {none, []}, [LUser, LServer, LJID]),
ejabberd_hooks:run_fold(roster_get_jid_info, check_packet_aux(List, PType2, LJID, Subscription, Groups)
LServer, end.
{none, []},
[LUser, LServer, LJID]);
false ->
{[], []}
end,
check_packet_aux(List, PType2, LJID, Subscription, Groups)
end;
check_packet(Acc, #jid{luser = LUser, lserver = LServer} = JID, Packet, Dir) ->
List = get_user_list(LUser, LServer),
check_packet(Acc, #{jid => JID, privacy_list => List}, Packet, Dir).
-spec check_packet_aux([listitem()], -spec check_packet_aux([listitem()],
message | iq | presence_in | presence_out | other, message | iq | presence_in | presence_out | other,
@ -608,12 +656,82 @@ is_type_match(Type, Value, JID, Subscription, Groups) ->
group -> lists:member(Value, Groups) group -> lists:member(Value, Groups)
end. end.
-spec remove_user(binary(), binary()) -> any(). -spec remove_user(binary(), binary()) -> ok.
remove_user(User, Server) -> remove_user(User, Server) ->
LUser = jid:nodeprep(User), LUser = jid:nodeprep(User),
LServer = jid:nameprep(Server), LServer = jid:nameprep(Server),
Privacy = get_user_lists(LUser, LServer),
Mod = gen_mod:db_mod(LServer, ?MODULE), Mod = gen_mod:db_mod(LServer, ?MODULE),
Mod:remove_user(LUser, LServer). Mod:remove_lists(LUser, LServer),
case Privacy of
{ok, #privacy{lists = Lists}} ->
Names = [Name || {Name, _} <- Lists],
delete_cache(Mod, LUser, LServer, Names);
_ ->
ok
end.
-spec init_cache(module(), binary(), gen_mod:opts()) -> ok.
init_cache(Mod, Host, Opts) ->
case use_cache(Mod, Host) of
true ->
CacheOpts = cache_opts(Host, Opts),
ets_cache:new(?PRIVACY_CACHE, CacheOpts),
ets_cache:new(?PRIVACY_LIST_CACHE, CacheOpts);
false ->
ets_cache:delete(?PRIVACY_CACHE),
ets_cache:delete(?PRIVACY_LIST_CACHE)
end.
-spec cache_opts(binary(), gen_mod:opts()) -> [proplists:property()].
cache_opts(Host, Opts) ->
MaxSize = gen_mod:get_opt(
cache_size, Opts,
ejabberd_config:cache_size(Host)),
CacheMissed = gen_mod:get_opt(
cache_missed, Opts,
ejabberd_config:cache_missed(Host)),
LifeTime = case gen_mod:get_opt(
cache_life_time, Opts,
ejabberd_config:cache_life_time(Host)) of
infinity -> infinity;
I -> timer:seconds(I)
end,
[{max_size, MaxSize}, {cache_missed, CacheMissed}, {life_time, LifeTime}].
-spec use_cache(module(), binary()) -> boolean().
use_cache(Mod, Host) ->
case erlang:function_exported(Mod, use_cache, 1) of
true -> Mod:use_cache(Host);
false ->
gen_mod:get_module_opt(
Host, ?MODULE, use_cache,
ejabberd_config:use_cache(Host))
end.
-spec cache_nodes(module(), binary()) -> [node()].
cache_nodes(Mod, Host) ->
case erlang:function_exported(Mod, cache_nodes, 1) of
true -> Mod:cache_nodes(Host);
false -> ejabberd_cluster:get_nodes()
end.
-spec delete_cache(module(), binary(), binary(), [binary()]) -> ok.
delete_cache(Mod, LUser, LServer, Names) ->
case use_cache(Mod, LServer) of
true ->
Nodes = cache_nodes(Mod, LServer),
ets_cache:delete(?PRIVACY_CACHE, {LUser, LServer}, Nodes),
lists:foreach(
fun(Name) ->
ets_cache:delete(
?PRIVACY_LIST_CACHE,
{LUser, LServer, Name},
Nodes)
end, [default|Names]);
false ->
ok
end.
numeric_to_binary(<<0, 0, _/binary>>) -> numeric_to_binary(<<0, 0, _/binary>>) ->
<<"0">>; <<"0">>;

View File

@ -27,11 +27,9 @@
-behaviour(mod_privacy). -behaviour(mod_privacy).
%% API %% API
-export([init/2, process_lists_get/2, process_list_get/3, -export([init/2, set_default/3, unset_default/2, set_lists/1,
process_default_set/3, process_active_set/3, set_list/4, get_lists/2, get_list/3, remove_lists/2,
remove_privacy_list/3, set_privacy_list/1, remove_list/3, use_cache/1, import/1]).
set_privacy_list/4, get_user_list/2, get_user_lists/2,
remove_user/2, import/1]).
-export([need_transform/1, transform/1]). -export([need_transform/1, transform/1]).
-include("xmpp.hrl"). -include("xmpp.hrl").
@ -43,122 +41,106 @@
%%%=================================================================== %%%===================================================================
init(_Host, _Opts) -> init(_Host, _Opts) ->
ejabberd_mnesia:create(?MODULE, privacy, ejabberd_mnesia:create(?MODULE, privacy,
[{disc_copies, [node()]}, [{disc_only_copies, [node()]},
{attributes, record_info(fields, privacy)}]). {attributes, record_info(fields, privacy)}]).
process_lists_get(LUser, LServer) -> use_cache(Host) ->
case catch mnesia:dirty_read(privacy, {LUser, LServer}) of case mnesia:table_info(privacy, storage_type) of
{'EXIT', _Reason} -> error; disc_only_copies ->
[] -> {none, []}; gen_mod:get_module_opt(
[#privacy{default = Default, lists = Lists}] -> Host, mod_privacy, use_cache,
LItems = lists:map(fun ({N, _}) -> N end, Lists), ejabberd_config:use_cache(Host));
{Default, LItems} _ ->
false
end. end.
process_list_get(LUser, LServer, Name) -> unset_default(LUser, LServer) ->
case catch mnesia:dirty_read(privacy, {LUser, LServer}) of
{'EXIT', _Reason} -> error;
[] -> not_found;
[#privacy{lists = Lists}] ->
case lists:keysearch(Name, 1, Lists) of
{value, {_, List}} -> List;
_ -> not_found
end
end.
process_default_set(LUser, LServer, none) ->
F = fun () -> F = fun () ->
case mnesia:read({privacy, {LUser, LServer}}) of case mnesia:read({privacy, {LUser, LServer}}) of
[] -> ok; [] -> ok;
[R] -> mnesia:write(R#privacy{default = none}) [R] -> mnesia:write(R#privacy{default = none})
end end
end, end,
mnesia:transaction(F); transaction(F).
process_default_set(LUser, LServer, Name) ->
set_default(LUser, LServer, Name) ->
F = fun () -> F = fun () ->
case mnesia:read({privacy, {LUser, LServer}}) of case mnesia:read({privacy, {LUser, LServer}}) of
[] -> not_found; [] ->
{error, notfound};
[#privacy{lists = Lists} = P] -> [#privacy{lists = Lists} = P] ->
case lists:keymember(Name, 1, Lists) of case lists:keymember(Name, 1, Lists) of
true -> true ->
mnesia:write(P#privacy{default = Name, mnesia:write(P#privacy{default = Name,
lists = Lists}), lists = Lists});
ok; false ->
false -> not_found {error, notfound}
end end
end end
end, end,
mnesia:transaction(F). transaction(F).
process_active_set(LUser, LServer, Name) -> remove_list(LUser, LServer, Name) ->
case catch mnesia:dirty_read(privacy, {LUser, LServer}) of
[] -> error;
[#privacy{lists = Lists}] ->
case lists:keysearch(Name, 1, Lists) of
{value, {_, List}} -> List;
false -> error
end
end.
remove_privacy_list(LUser, LServer, Name) ->
F = fun () -> F = fun () ->
case mnesia:read({privacy, {LUser, LServer}}) of case mnesia:read({privacy, {LUser, LServer}}) of
[] -> ok; [] ->
[#privacy{default = Default, lists = Lists} = P] -> {error, notfound};
if Name == Default -> conflict; [#privacy{default = Default, lists = Lists} = P] ->
true -> if Name == Default ->
NewLists = lists:keydelete(Name, 1, Lists), {error, conflict};
mnesia:write(P#privacy{lists = NewLists}) true ->
end NewLists = lists:keydelete(Name, 1, Lists),
mnesia:write(P#privacy{lists = NewLists})
end
end end
end, end,
mnesia:transaction(F). transaction(F).
set_privacy_list(Privacy) -> set_lists(Privacy) ->
mnesia:dirty_write(Privacy). mnesia:dirty_write(Privacy).
set_privacy_list(LUser, LServer, Name, List) -> set_list(LUser, LServer, Name, List) ->
F = fun () -> F = fun () ->
case mnesia:wread({privacy, {LUser, LServer}}) of case mnesia:wread({privacy, {LUser, LServer}}) of
[] -> [] ->
NewLists = [{Name, List}], NewLists = [{Name, List}],
mnesia:write(#privacy{us = {LUser, LServer}, mnesia:write(#privacy{us = {LUser, LServer},
lists = NewLists}); lists = NewLists});
[#privacy{lists = Lists} = P] -> [#privacy{lists = Lists} = P] ->
NewLists1 = lists:keydelete(Name, 1, Lists), NewLists1 = lists:keydelete(Name, 1, Lists),
NewLists = [{Name, List} | NewLists1], NewLists = [{Name, List} | NewLists1],
mnesia:write(P#privacy{lists = NewLists}) mnesia:write(P#privacy{lists = NewLists})
end end
end, end,
mnesia:transaction(F). transaction(F).
get_user_list(LUser, LServer) -> get_list(LUser, LServer, Name) ->
case catch mnesia:dirty_read(privacy, {LUser, LServer}) case mnesia:dirty_read(privacy, {LUser, LServer}) of
of [#privacy{default = Default, lists = Lists}] when Name == default ->
[] -> {none, []}; case lists:keyfind(Default, 1, Lists) of
[#privacy{default = Default, lists = Lists}] -> {_, List} -> {ok, {Default, List}};
case Default of false -> error
none -> {none, []}; end;
_ -> [#privacy{lists = Lists}] ->
case lists:keysearch(Default, 1, Lists) of case lists:keyfind(Name, 1, Lists) of
{value, {_, List}} -> {Default, List}; {_, List} -> {ok, {Name, List}};
_ -> {none, []} false -> error
end end;
end; [] ->
_ -> {none, []} error
end. end.
get_user_lists(LUser, LServer) -> get_lists(LUser, LServer) ->
case catch mnesia:dirty_read(privacy, {LUser, LServer}) of case mnesia:dirty_read(privacy, {LUser, LServer}) of
[#privacy{} = P] -> [#privacy{} = P] ->
{ok, P}; {ok, P};
_ -> _ ->
error error
end. end.
remove_user(LUser, LServer) -> remove_lists(LUser, LServer) ->
F = fun () -> mnesia:delete({privacy, {LUser, LServer}}) end, F = fun () -> mnesia:delete({privacy, {LUser, LServer}}) end,
mnesia:transaction(F). transaction(F).
import(#privacy{} = P) -> import(#privacy{} = P) ->
mnesia:dirty_write(P). mnesia:dirty_write(P).
@ -199,3 +181,11 @@ transform(#privacy{us = {U, S}, default = Def, lists = Lists} = R) ->
%%%=================================================================== %%%===================================================================
%%% Internal functions %%% Internal functions
%%%=================================================================== %%%===================================================================
transaction(F) ->
case mnesia:transaction(F) of
{atomic, Result} ->
Result;
{aborted, Reason} ->
?ERROR_MSG("Mnesia transaction failed: ~p", [Reason]),
{error, db_failure}
end.

View File

@ -27,11 +27,9 @@
-behaviour(mod_privacy). -behaviour(mod_privacy).
%% API %% API
-export([init/2, process_lists_get/2, process_list_get/3, -export([init/2, set_default/3, unset_default/2, set_lists/1,
process_default_set/3, process_active_set/3, set_list/4, get_lists/2, get_list/3, remove_lists/2,
remove_privacy_list/3, set_privacy_list/1, remove_list/3, import/1]).
set_privacy_list/4, get_user_list/2, get_user_lists/2,
remove_user/2, import/1]).
-export([privacy_schema/0]). -export([privacy_schema/0]).
@ -44,122 +42,93 @@
init(_Host, _Opts) -> init(_Host, _Opts) ->
ok. ok.
process_lists_get(LUser, LServer) -> unset_default(LUser, LServer) ->
case ejabberd_riak:get(privacy, privacy_schema(), {LUser, LServer}) of case ejabberd_riak:get(privacy, privacy_schema(), {LUser, LServer}) of
{ok, #privacy{default = Default, lists = Lists}} -> {ok, R} ->
LItems = lists:map(fun ({N, _}) -> N end, Lists), ejabberd_riak:put(R#privacy{default = none}, privacy_schema());
{Default, LItems}; {error, notfound} ->
{error, notfound} -> ok;
{none, []}; Err ->
{error, _} -> Err
error
end. end.
process_list_get(LUser, LServer, Name) -> set_default(LUser, LServer, Name) ->
case ejabberd_riak:get(privacy, privacy_schema(), {LUser, LServer}) of case ejabberd_riak:get(privacy, privacy_schema(), {LUser, LServer}) of
{ok, #privacy{lists = Lists}} -> {ok, #privacy{lists = Lists} = P} ->
case lists:keysearch(Name, 1, Lists) of case lists:keymember(Name, 1, Lists) of
{value, {_, List}} -> List; true ->
_ -> not_found ejabberd_riak:put(P#privacy{default = Name,
end; lists = Lists},
{error, notfound} -> privacy_schema());
not_found; false ->
{error, _} -> {error, notfound}
error end;
Err ->
Err
end. end.
process_default_set(LUser, LServer, none) -> remove_list(LUser, LServer, Name) ->
{atomic,
case ejabberd_riak:get(privacy, privacy_schema(), {LUser, LServer}) of
{ok, R} ->
ejabberd_riak:put(R#privacy{default = none}, privacy_schema());
{error, _} ->
ok
end};
process_default_set(LUser, LServer, Name) ->
{atomic,
case ejabberd_riak:get(privacy, privacy_schema(), {LUser, LServer}) of
{ok, #privacy{lists = Lists} = P} ->
case lists:keymember(Name, 1, Lists) of
true ->
ejabberd_riak:put(P#privacy{default = Name,
lists = Lists},
privacy_schema());
false ->
not_found
end;
{error, _} ->
not_found
end}.
process_active_set(LUser, LServer, Name) ->
case ejabberd_riak:get(privacy, privacy_schema(), {LUser, LServer}) of case ejabberd_riak:get(privacy, privacy_schema(), {LUser, LServer}) of
{ok, #privacy{lists = Lists}} -> {ok, #privacy{default = Default, lists = Lists} = P} ->
case lists:keysearch(Name, 1, Lists) of if Name == Default ->
{value, {_, List}} -> List; {error, conflict};
false -> error true ->
end; NewLists = lists:keydelete(Name, 1, Lists),
{error, _} -> ejabberd_riak:put(P#privacy{lists = NewLists},
error privacy_schema())
end;
Err ->
Err
end. end.
remove_privacy_list(LUser, LServer, Name) -> set_lists(Privacy) ->
{atomic,
case ejabberd_riak:get(privacy, privacy_schema(), {LUser, LServer}) of
{ok, #privacy{default = Default, lists = Lists} = P} ->
if Name == Default ->
conflict;
true ->
NewLists = lists:keydelete(Name, 1, Lists),
ejabberd_riak:put(P#privacy{lists = NewLists},
privacy_schema())
end;
{error, _} ->
ok
end}.
set_privacy_list(Privacy) ->
ejabberd_riak:put(Privacy, privacy_schema()). ejabberd_riak:put(Privacy, privacy_schema()).
set_privacy_list(LUser, LServer, Name, List) -> set_list(LUser, LServer, Name, List) ->
{atomic,
case ejabberd_riak:get(privacy, privacy_schema(), {LUser, LServer}) of
{ok, #privacy{lists = Lists} = P} ->
NewLists1 = lists:keydelete(Name, 1, Lists),
NewLists = [{Name, List} | NewLists1],
ejabberd_riak:put(P#privacy{lists = NewLists}, privacy_schema());
{error, _} ->
NewLists = [{Name, List}],
ejabberd_riak:put(#privacy{us = {LUser, LServer},
lists = NewLists},
privacy_schema())
end}.
get_user_list(LUser, LServer) ->
case ejabberd_riak:get(privacy, privacy_schema(), {LUser, LServer}) of case ejabberd_riak:get(privacy, privacy_schema(), {LUser, LServer}) of
{ok, #privacy{default = Default, lists = Lists}} -> {ok, #privacy{lists = Lists} = P} ->
case Default of NewLists1 = lists:keydelete(Name, 1, Lists),
none -> {none, []}; NewLists = [{Name, List} | NewLists1],
_ -> ejabberd_riak:put(P#privacy{lists = NewLists}, privacy_schema());
case lists:keysearch(Default, 1, Lists) of {error, notfound} ->
{value, {_, List}} -> {Default, List}; NewLists = [{Name, List}],
_ -> {none, []} ejabberd_riak:put(#privacy{us = {LUser, LServer},
end lists = NewLists},
end; privacy_schema());
{error, _} -> Err ->
{none, []} Err
end. end.
get_user_lists(LUser, LServer) -> get_list(LUser, LServer, Name) ->
case ejabberd_riak:get(privacy, privacy_schema(), {LUser, LServer}) of
{ok, #privacy{default = Default, lists = Lists}} when Name == default ->
case lists:keyfind(Default, 1, Lists) of
{_, List} -> {ok, {Default, List}};
false -> error
end;
{ok, #privacy{lists = Lists}} ->
case lists:keyfind(Name, 1, Lists) of
{_, List} -> {ok, {Name, List}};
false -> error
end;
{error, notfound} ->
error;
Err ->
Err
end.
get_lists(LUser, LServer) ->
case ejabberd_riak:get(privacy, privacy_schema(), {LUser, LServer}) of case ejabberd_riak:get(privacy, privacy_schema(), {LUser, LServer}) of
{ok, #privacy{} = P} -> {ok, #privacy{} = P} ->
{ok, P}; {ok, P};
{error, _} -> {error, notfound} ->
error error;
Err ->
Err
end. end.
remove_user(LUser, LServer) -> remove_lists(LUser, LServer) ->
{atomic, ejabberd_riak:delete(privacy, {LUser, LServer})}. ejabberd_riak:delete(privacy, {LUser, LServer}).
import(#privacy{} = P) -> import(#privacy{} = P) ->
ejabberd_riak:put(P, privacy_schema()). ejabberd_riak:put(P, privacy_schema()).

View File

@ -29,20 +29,11 @@
-behaviour(mod_privacy). -behaviour(mod_privacy).
%% API %% API
-export([init/2, process_lists_get/2, process_list_get/3, -export([init/2, set_default/3, unset_default/2, set_lists/1,
process_default_set/3, process_active_set/3, set_list/4, get_lists/2, get_list/3, remove_lists/2,
remove_privacy_list/3, set_privacy_list/1, remove_list/3, import/1, export/1]).
set_privacy_list/4, get_user_list/2, get_user_lists/2,
remove_user/2, import/1, export/1]).
-export([item_to_raw/1, raw_to_item/1, -export([item_to_raw/1, raw_to_item/1]).
sql_add_privacy_list/2,
sql_get_default_privacy_list/2,
sql_get_default_privacy_list_t/1,
sql_get_privacy_list_data/3,
sql_get_privacy_list_data_by_id_t/1,
sql_get_privacy_list_id_t/2,
sql_set_default_privacy_list/2, sql_set_privacy_list/2]).
-include("xmpp.hrl"). -include("xmpp.hrl").
-include("mod_privacy.hrl"). -include("mod_privacy.hrl").
@ -55,159 +46,143 @@
init(_Host, _Opts) -> init(_Host, _Opts) ->
ok. ok.
process_lists_get(LUser, LServer) -> unset_default(LUser, LServer) ->
Default = case catch sql_get_default_privacy_list(LUser, LServer) of case unset_default_privacy_list(LUser, LServer) of
{selected, []} -> none; ok ->
{selected, [{DefName}]} -> DefName; ok;
_ -> none _Err ->
end, {error, db_failure}
case catch sql_get_privacy_list_names(LUser, LServer) of
{selected, Names} ->
LItems = lists:map(fun ({N}) -> N end, Names),
{Default, LItems};
_ -> error
end. end.
process_list_get(LUser, LServer, Name) -> set_default(LUser, LServer, Name) ->
case catch sql_get_privacy_list_id(LUser, LServer, Name) of
{selected, []} -> not_found;
{selected, [{ID}]} ->
case catch sql_get_privacy_list_data_by_id(ID, LServer) of
{selected, RItems} ->
lists:flatmap(fun raw_to_item/1, RItems);
_ -> error
end;
_ -> error
end.
process_default_set(LUser, LServer, none) ->
case catch sql_unset_default_privacy_list(LUser,
LServer)
of
{'EXIT', _Reason} -> {atomic, error};
{error, _Reason} -> {atomic, error};
_ -> {atomic, ok}
end;
process_default_set(LUser, LServer, Name) ->
F = fun () -> F = fun () ->
case sql_get_privacy_list_names_t(LUser) of case get_privacy_list_names_t(LUser) of
{selected, []} -> not_found; {selected, []} ->
{selected, Names} -> {error, notfound};
case lists:member({Name}, Names) of {selected, Names} ->
true -> sql_set_default_privacy_list(LUser, Name), ok; case lists:member({Name}, Names) of
false -> not_found true ->
end set_default_privacy_list(LUser, Name);
false ->
{error, notfound}
end
end end
end, end,
sql_queries:sql_transaction(LServer, F). transaction(LServer, F).
process_active_set(LUser, LServer, Name) -> remove_list(LUser, LServer, Name) ->
case catch sql_get_privacy_list_id(LUser, LServer, Name) of
{selected, []} -> error;
{selected, [{ID}]} ->
case catch sql_get_privacy_list_data_by_id(ID, LServer) of
{selected, RItems} ->
lists:flatmap(fun raw_to_item/1, RItems);
_ -> error
end;
_ -> error
end.
remove_privacy_list(LUser, LServer, Name) ->
F = fun () -> F = fun () ->
case sql_get_default_privacy_list_t(LUser) of case get_default_privacy_list_t(LUser) of
{selected, []} -> {selected, []} ->
sql_remove_privacy_list(LUser, Name), ok; remove_privacy_list(LUser, Name);
{selected, [{Default}]} -> {selected, [{Default}]} ->
if Name == Default -> conflict; if Name == Default ->
true -> sql_remove_privacy_list(LUser, Name), ok {error, conflict};
end true ->
remove_privacy_list(LUser, Name)
end
end end
end, end,
sql_queries:sql_transaction(LServer, F). transaction(LServer, F).
set_privacy_list(#privacy{us = {LUser, LServer}, set_lists(#privacy{us = {LUser, LServer},
default = Default, default = Default,
lists = Lists}) -> lists = Lists}) ->
F = fun() -> F = fun() ->
lists:foreach( lists:foreach(
fun({Name, List}) -> fun({Name, List}) ->
sql_add_privacy_list(LUser, Name), add_privacy_list(LUser, Name),
{selected, [<<"id">>], [[I]]} = {selected, [<<"id">>], [[I]]} =
sql_get_privacy_list_id_t(LUser, Name), get_privacy_list_id_t(LUser, Name),
RItems = lists:map(fun item_to_raw/1, List), RItems = lists:map(fun item_to_raw/1, List),
sql_set_privacy_list(I, RItems), set_privacy_list(I, RItems),
if is_binary(Default) -> if is_binary(Default) ->
sql_set_default_privacy_list(LUser, Default), set_default_privacy_list(LUser, Default);
ok;
true -> true ->
ok ok
end end
end, Lists) end, Lists)
end, end,
sql_queries:sql_transaction(LServer, F). transaction(LServer, F).
set_privacy_list(LUser, LServer, Name, List) -> set_list(LUser, LServer, Name, List) ->
RItems = lists:map(fun item_to_raw/1, List), RItems = lists:map(fun item_to_raw/1, List),
F = fun () -> F = fun () ->
ID = case sql_get_privacy_list_id_t(LUser, Name) of ID = case get_privacy_list_id_t(LUser, Name) of
{selected, []} -> {selected, []} ->
sql_add_privacy_list(LUser, Name), add_privacy_list(LUser, Name),
{selected, [{I}]} = {selected, [{I}]} =
sql_get_privacy_list_id_t(LUser, Name), get_privacy_list_id_t(LUser, Name),
I; I;
{selected, [{I}]} -> I {selected, [{I}]} -> I
end, end,
sql_set_privacy_list(ID, RItems), set_privacy_list(ID, RItems)
ok
end, end,
sql_queries:sql_transaction(LServer, F). transaction(LServer, F).
get_user_list(LUser, LServer) -> get_list(LUser, LServer, default) ->
case catch sql_get_default_privacy_list(LUser, LServer) case get_default_privacy_list(LUser, LServer) of
of {selected, []} ->
{selected, []} -> {none, []}; error;
{selected, [{Default}]} -> {selected, [{Default}]} ->
case catch sql_get_privacy_list_data(LUser, LServer, get_list(LUser, LServer, Default);
Default) of _Err ->
{selected, RItems} -> {error, db_failure}
{Default, lists:flatmap(fun raw_to_item/1, RItems)}; end;
_ -> {none, []} get_list(LUser, LServer, Name) ->
end; case get_privacy_list_data(LUser, LServer, Name) of
_ -> {none, []} {selected, []} ->
error;
{selected, RItems} ->
{ok, {Name, lists:flatmap(fun raw_to_item/1, RItems)}};
_Err ->
{error, db_failure}
end. end.
get_user_lists(LUser, LServer) -> get_lists(LUser, LServer) ->
Default = case catch sql_get_default_privacy_list(LUser, LServer) of case get_default_privacy_list(LUser, LServer) of
{selected, []} -> {selected, Selected} ->
none; Default = case Selected of
{selected, [{DefName}]} -> [] -> none;
DefName; [{DefName}] -> DefName
_ -> end,
none case get_privacy_list_names(LUser, LServer) of
end, {selected, Names} ->
case catch sql_get_privacy_list_names(LUser, LServer) of case lists:foldl(
{selected, Names} -> fun(_, {error, _} = Err) ->
Lists = Err;
lists:flatmap( ({Name}, Acc) ->
fun({Name}) -> case get_privacy_list_data(LUser, LServer, Name) of
case catch sql_get_privacy_list_data( {selected, RItems} ->
LUser, LServer, Name) of Items = lists:flatmap(
{selected, RItems} -> fun raw_to_item/1,
[{Name, lists:flatmap(fun raw_to_item/1, RItems)}]; RItems),
_ -> [{Name, Items}|Acc];
[] _Err ->
end {error, db_failure}
end, Names), end
{ok, #privacy{default = Default, end, [], Names) of
us = {LUser, LServer}, {error, Reason} ->
lists = Lists}}; {error, Reason};
_ -> Lists ->
error {ok, #privacy{default = Default,
us = {LUser, LServer},
lists = Lists}}
end;
_Err ->
{error, db_failure}
end;
_Err ->
{error, db_failure}
end. end.
remove_user(LUser, LServer) -> remove_lists(LUser, LServer) ->
sql_del_privacy_lists(LUser, LServer). case del_privacy_lists(LUser, LServer) of
ok ->
ok;
_Err ->
{error, db_failure}
end.
export(Server) -> export(Server) ->
case catch ejabberd_sql:sql_query(jid:nameprep(Server), case catch ejabberd_sql:sql_query(jid:nameprep(Server),
@ -271,6 +246,12 @@ import(_) ->
%%%=================================================================== %%%===================================================================
%%% Internal functions %%% Internal functions
%%%=================================================================== %%%===================================================================
transaction(LServer, F) ->
case ejabberd_sql:sql_transaction(LServer, F) of
{atomic, Res} -> Res;
{aborted, _Reason} -> {error, db_failure}
end.
raw_to_item({SType, SValue, SAction, Order, MatchAll, raw_to_item({SType, SValue, SAction, Order, MatchAll,
MatchIQ, MatchMessage, MatchPresenceIn, MatchIQ, MatchMessage, MatchPresenceIn,
MatchPresenceOut} = Row) -> MatchPresenceOut} = Row) ->
@ -327,47 +308,48 @@ item_to_raw(#listitem{type = Type, value = Value,
{SType, SValue, SAction, Order, MatchAll, MatchIQ, {SType, SValue, SAction, Order, MatchAll, MatchIQ,
MatchMessage, MatchPresenceIn, MatchPresenceOut}. MatchMessage, MatchPresenceIn, MatchPresenceOut}.
sql_get_default_privacy_list(LUser, LServer) -> get_default_privacy_list(LUser, LServer) ->
sql_queries:get_default_privacy_list(LServer, LUser). sql_queries:get_default_privacy_list(LServer, LUser).
sql_get_default_privacy_list_t(LUser) -> get_default_privacy_list_t(LUser) ->
sql_queries:get_default_privacy_list_t(LUser). sql_queries:get_default_privacy_list_t(LUser).
sql_get_privacy_list_names(LUser, LServer) -> get_privacy_list_names(LUser, LServer) ->
sql_queries:get_privacy_list_names(LServer, LUser). sql_queries:get_privacy_list_names(LServer, LUser).
sql_get_privacy_list_names_t(LUser) -> get_privacy_list_names_t(LUser) ->
sql_queries:get_privacy_list_names_t(LUser). sql_queries:get_privacy_list_names_t(LUser).
sql_get_privacy_list_id(LUser, LServer, Name) -> get_privacy_list_id_t(LUser, Name) ->
sql_queries:get_privacy_list_id(LServer, LUser, Name).
sql_get_privacy_list_id_t(LUser, Name) ->
sql_queries:get_privacy_list_id_t(LUser, Name). sql_queries:get_privacy_list_id_t(LUser, Name).
sql_get_privacy_list_data(LUser, LServer, Name) -> get_privacy_list_data(LUser, LServer, Name) ->
sql_queries:get_privacy_list_data(LServer, LUser, Name). sql_queries:get_privacy_list_data(LServer, LUser, Name).
sql_get_privacy_list_data_by_id(ID, LServer) -> set_default_privacy_list(LUser, Name) ->
sql_queries:get_privacy_list_data_by_id(LServer, ID).
sql_get_privacy_list_data_by_id_t(ID) ->
sql_queries:get_privacy_list_data_by_id_t(ID).
sql_set_default_privacy_list(LUser, Name) ->
sql_queries:set_default_privacy_list(LUser, Name). sql_queries:set_default_privacy_list(LUser, Name).
sql_unset_default_privacy_list(LUser, LServer) -> unset_default_privacy_list(LUser, LServer) ->
sql_queries:unset_default_privacy_list(LServer, LUser). case sql_queries:unset_default_privacy_list(LServer, LUser) of
{updated, _} -> ok;
Err -> Err
end.
sql_remove_privacy_list(LUser, Name) -> remove_privacy_list(LUser, Name) ->
sql_queries:remove_privacy_list(LUser, Name). case sql_queries:remove_privacy_list(LUser, Name) of
{updated, 0} -> {error, notfound};
{updated, _} -> ok;
Err -> Err
end.
sql_add_privacy_list(LUser, Name) -> add_privacy_list(LUser, Name) ->
sql_queries:add_privacy_list(LUser, Name). sql_queries:add_privacy_list(LUser, Name).
sql_set_privacy_list(ID, RItems) -> set_privacy_list(ID, RItems) ->
sql_queries:set_privacy_list(ID, RItems). sql_queries:set_privacy_list(ID, RItems).
sql_del_privacy_lists(LUser, LServer) -> del_privacy_lists(LUser, LServer) ->
sql_queries:del_privacy_lists(LServer, LUser). case sql_queries:del_privacy_lists(LServer, LUser) of
{updated, _} -> ok;
Err -> Err
end.

View File

@ -368,15 +368,16 @@ get_roster_item(LUser, LServer, LJID) ->
{ok, Item} -> {ok, Item} ->
Item; Item;
error -> error ->
#roster{usj = {LUser, LServer, LJID}, LBJID = jid:remove_resource(LJID),
us = {LUser, LServer}, jid = LJID} #roster{usj = {LUser, LServer, LBJID},
us = {LUser, LServer}, jid = LBJID}
end. end.
get_subscription_and_groups(LUser, LServer, LJID) -> get_subscription_and_groups(LUser, LServer, LJID) ->
LBJID = jid:remove_resource(LJID),
Mod = gen_mod:db_mod(LServer, ?MODULE), Mod = gen_mod:db_mod(LServer, ?MODULE),
Res = case use_cache(Mod, LServer, roster) of Res = case use_cache(Mod, LServer, roster) of
true -> true ->
LBJID = jid:remove_resource(LJID),
ets_cache:lookup( ets_cache:lookup(
?ROSTER_ITEM_CACHE, {LUser, LServer, LBJID}, ?ROSTER_ITEM_CACHE, {LUser, LServer, LBJID},
fun() -> fun() ->
@ -384,19 +385,12 @@ get_subscription_and_groups(LUser, LServer, LJID) ->
case lists:keyfind(LBJID, #roster.jid, Items) of case lists:keyfind(LBJID, #roster.jid, Items) of
#roster{subscription = Sub, groups = Groups} -> #roster{subscription = Sub, groups = Groups} ->
{ok, {Sub, Groups}}; {ok, {Sub, Groups}};
false when element(3, LJID) == <<"">> ->
error;
false -> false ->
case lists:keyfind(LJID, #roster.jid, Items) of error
{Sub, Groups} ->
{ok, {Sub, Groups}};
false ->
error
end
end end
end); end);
false -> false ->
Mod:read_subscription_and_groups(LUser, LServer, LJID) Mod:read_subscription_and_groups(LUser, LServer, LBJID)
end, end,
case Res of case Res of
{ok, SubAndGroups} -> {ok, SubAndGroups} ->

View File

@ -210,7 +210,7 @@ convert_data(Host, "privacy", User, [Data]) ->
ListItems -> [{Name, ListItems}] ListItems -> [{Name, ListItems}]
end end
end, Lists)}, end, Lists)},
mod_privacy:set_privacy_list(Priv); mod_privacy:set_list(Priv);
convert_data(_Host, _Type, _User, _Data) -> convert_data(_Host, _Type, _User, _Data) ->
ok. ok.

View File

@ -43,6 +43,7 @@ single_cases() ->
[single_test(feature_enabled), [single_test(feature_enabled),
single_test(set_get_list), single_test(set_get_list),
single_test(get_list_non_existent), single_test(get_list_non_existent),
single_test(get_empty_lists),
single_test(set_default), single_test(set_default),
single_test(del_default), single_test(del_default),
single_test(set_default_non_existent), single_test(set_default_non_existent),
@ -52,8 +53,7 @@ single_cases() ->
single_test(remove_list), single_test(remove_list),
single_test(remove_default_list), single_test(remove_default_list),
single_test(remove_active_list), single_test(remove_active_list),
%% TODO: this should be fixed single_test(remove_list_non_existent),
%% single_test(remove_list_non_existent),
single_test(allow_local_server), single_test(allow_local_server),
single_test(malformed_iq_query), single_test(malformed_iq_query),
single_test(malformed_get), single_test(malformed_get),
@ -98,6 +98,12 @@ get_list_non_existent(Config) ->
#stanza_error{reason = 'item-not-found'} = get_list(Config, ListName), #stanza_error{reason = 'item-not-found'} = get_list(Config, ListName),
disconnect(Config). disconnect(Config).
get_empty_lists(Config) ->
#privacy_query{default = none,
active = none,
lists = []} = get_lists(Config),
disconnect(Config).
set_default(Config) -> set_default(Config) ->
ListName = <<"set-default">>, ListName = <<"set-default">>,
Item = #privacy_item{order = 0, action = deny}, Item = #privacy_item{order = 0, action = deny},
@ -561,12 +567,6 @@ del_list(Config, Name) ->
lists = [#privacy_list{ lists = [#privacy_list{
name = Name}]}]}) of name = Name}]}]}) of
#iq{type = result, sub_els = []} -> #iq{type = result, sub_els = []} ->
ct:comment("Receiving privacy list push"),
#iq{type = set, id = ID,
sub_els = [#privacy_query{lists = [#privacy_list{
name = Name}]}]} =
recv_iq(Config),
send(Config, #iq{type = result, id = ID}),
ok; ok;
#iq{type = error} = Err -> #iq{type = error} = Err ->
xmpp:get_error(Err) xmpp:get_error(Err)