diff --git a/big_tests/dynamic_domains.spec b/big_tests/dynamic_domains.spec index 43bdf70d5e..e42ad3d837 100644 --- a/big_tests/dynamic_domains.spec +++ b/big_tests/dynamic_domains.spec @@ -43,6 +43,8 @@ {suites, "tests", presence_SUITE}. +{suites, "tests", privacy_SUITE}. + {suites, "tests", race_conditions_SUITE}. {suites, "tests", rest_client_SUITE}. diff --git a/big_tests/test.config b/big_tests/test.config index 3879799de9..1653c3f47a 100644 --- a/big_tests/test.config +++ b/big_tests/test.config @@ -252,8 +252,7 @@ connection.tls.server_name_indication = false"}, {mod_last, "[modules.mod_last] backend = \"rdbms\""}, - {mod_privacy, "[modules.mod_privacy] - backend = \"rdbms\""}, + {mod_privacy, " backend = \"rdbms\""}, {mod_private, "[modules.mod_private] backend = \"rdbms\""}, {mod_offline, " backend = \"rdbms\"\n"}, @@ -275,8 +274,7 @@ connection.settings = \"DSN=mongoose-mssql;UID=sa;PWD=mongooseim_secret+ESL123\""}, {mod_last, "[modules.mod_last] backend = \"rdbms\""}, - {mod_privacy, "[modules.mod_privacy] - backend = \"rdbms\""}, + {mod_privacy, " backend = \"rdbms\""}, {mod_private, "[modules.mod_private] backend = \"rdbms\""}, {mod_offline, " backend = \"rdbms\"\n"}, @@ -308,8 +306,7 @@ connection.tls.versions = [\"tlsv1.2\"]"}, {mod_last, "[modules.mod_last] backend = \"rdbms\""}, - {mod_privacy, "[modules.mod_privacy] - backend = \"rdbms\""}, + {mod_privacy, " backend = \"rdbms\""}, {mod_private, "[modules.mod_private] backend = \"rdbms\""}, {mod_offline, " backend = \"rdbms\"\n"}, @@ -377,8 +374,7 @@ connection.tls.cacertfile = \"priv/ssl/cacert.pem\""}, {mod_last, "[modules.mod_last] backend = \"riak\""}, - {mod_privacy, "[modules.mod_privacy] - backend = \"riak\""}, + {mod_privacy, " backend = \"riak\""}, {mod_private, "[modules.mod_private] backend = \"riak\""}, {mod_offline, " backend = \"riak\"\n"}, diff --git a/big_tests/tests/privacy_SUITE.erl b/big_tests/tests/privacy_SUITE.erl index 7210833a9a..606ce0d0c3 100644 --- a/big_tests/tests/privacy_SUITE.erl +++ b/big_tests/tests/privacy_SUITE.erl @@ -29,22 +29,22 @@ %%-------------------------------------------------------------------- all() -> - [{group, management}, + [ + {group, management}, {group, blocking}, {group, allowing} ]. groups() -> - G = [{management, [sequence], management_test_cases()}, - {blocking, [sequence], blocking_test_cases()}, - {my, [sequence], mytest()}, - {allowing, [sequence], allowing_test_cases()}], + G = [{management, [parallel], management_test_cases()}, + {blocking, [parallel], blocking_test_cases()}, + {allowing, [parallel], allowing_test_cases()}], ct_helper:repeat_all_until_all_ok(G). -mytest() -> - [block_jid_message_but_not_presence]. management_test_cases() -> - [get_all_lists, + [ + discover_support, + get_all_lists, get_existing_list, get_many_lists, get_nonexistent_list, @@ -62,7 +62,8 @@ management_test_cases() -> ]. blocking_test_cases() -> - [block_jid_message, + [ + block_jid_message, block_group_message, block_subscription_message, block_all_message, @@ -81,7 +82,6 @@ allowing_test_cases() -> [allow_subscription_to_from_message, allow_subscription_both_message]. - suite() -> escalus:suite(). @@ -94,31 +94,18 @@ init_per_suite(Config) -> escalus:init_per_suite(Config)]. end_per_suite(Config) -> - escalus_fresh:clean(), escalus:end_per_suite(Config). init_per_group(_GroupName, Config) -> - escalus:create_users(Config, escalus:get_users([alice, bob])). + Config. end_per_group(_GroupName, Config) -> - escalus:delete_users(Config, escalus:get_users([alice, bob])). + Config. init_per_testcase(CaseName, Config) -> escalus:init_per_testcase(CaseName, Config). end_per_testcase(CaseName, Config) -> - %% Two reasons for it to be here. - %% - %% Reason 1. - %% After case set_list, alice@localhost/res1 presence unavailable - %% is sometimes received in activate, but not expected. - %% The proper handling on the server side is done after introducing - %% ejabberd_c2s:process_incoming_stanza_with_conflict_check. - %% - %% Reason 2. - %% More than 2 users. One user receives a precence of another one. - %% It can be presence available or unavailable. - mongoose_helper:kick_everyone(), escalus:end_per_testcase(CaseName, Config). %%-------------------------------------------------------------------- @@ -173,8 +160,16 @@ end_per_testcase(CaseName, Config) -> %% or block all of them, when the item has no children %% - blocking: messages, presence (in/out), iqs, all +discover_support(Config) -> + escalus:fresh_story(Config, [{alice, 1}], fun(Alice) -> + Server = escalus_client:server(Alice), + IqGet = escalus_stanza:disco_info(Server), + Result = escalus:send_iq_and_wait_for_result(Alice, IqGet), + escalus:assert(has_feature, [?NS_PRIVACY], Result) + end). + get_all_lists(Config) -> - escalus:story(Config, [{alice, 1}], fun(Alice) -> + escalus:fresh_story(Config, [{alice, 1}], fun(Alice) -> escalus:send(Alice, escalus_stanza:privacy_get_all()), escalus:assert(is_privacy_result, escalus:wait_for_stanza(Alice)) @@ -182,7 +177,7 @@ get_all_lists(Config) -> end). get_all_lists_with_active(Config) -> - escalus:story(Config, [{alice, 1}, {bob, 1}], fun(Alice, Bob) -> + escalus:fresh_story(Config, [{alice, 1}, {bob, 1}], fun(Alice, Bob) -> privacy_helper:set_and_activate(Alice, {<<"deny_client">>, Bob}), @@ -193,7 +188,7 @@ get_all_lists_with_active(Config) -> end). get_all_lists_with_default(Config) -> - escalus:story(Config, [{alice, 1}, {bob, 1}], fun(Alice, Bob) -> + escalus:fresh_story(Config, [{alice, 1}, {bob, 1}], fun(Alice, Bob) -> privacy_helper:set_list(Alice, {<<"deny_client">>, Bob}), privacy_helper:set_list(Alice, {<<"allow_client">>, Bob}), @@ -206,7 +201,7 @@ get_all_lists_with_default(Config) -> end). get_nonexistent_list(Config) -> - escalus:story(Config, [{alice, 1}], fun(Alice) -> + escalus:fresh_story(Config, [{alice, 1}], fun(Alice) -> escalus_client:send(Alice, escalus_stanza:privacy_get_lists([<<"public">>])), @@ -216,7 +211,7 @@ get_nonexistent_list(Config) -> end). get_many_lists(Config) -> - escalus:story(Config, [{alice, 1}], fun(Alice) -> + escalus:fresh_story(Config, [{alice, 1}], fun(Alice) -> Request = escalus_stanza:privacy_get_lists([<<"public">>, <<"private">>]), escalus_client:send(Alice, Request), @@ -226,7 +221,7 @@ get_many_lists(Config) -> end). get_existing_list(Config) -> - escalus:story(Config, [{alice, 1}, {bob, 1}], fun(Alice, Bob) -> + escalus:fresh_story(Config, [{alice, 1}, {bob, 1}], fun(Alice, Bob) -> privacy_helper:set_list(Alice, {<<"deny_client">>, Bob}), @@ -240,7 +235,7 @@ get_existing_list(Config) -> end). activate(Config) -> - escalus:story(Config, [{alice, 1}, {bob, 1}], fun(Alice, Bob) -> + escalus:fresh_story(Config, [{alice, 1}, {bob, 1}], fun(Alice, Bob) -> privacy_helper:set_list(Alice, {<<"deny_client">>, Bob}), @@ -253,7 +248,7 @@ activate(Config) -> end). activate_nonexistent(Config) -> - escalus:story(Config, [{alice, 1}], fun(Alice) -> + escalus:fresh_story(Config, [{alice, 1}], fun(Alice) -> Request = escalus_stanza:privacy_activate(<<"some_list">>), escalus_client:send(Alice, Request), @@ -264,7 +259,7 @@ activate_nonexistent(Config) -> end). deactivate(Config) -> - escalus:story(Config, [{alice, 1}], fun(Alice) -> + escalus:fresh_story(Config, [{alice, 1}], fun(Alice) -> Request = escalus_stanza:privacy_deactivate(), escalus_client:send(Alice, Request), @@ -275,7 +270,7 @@ deactivate(Config) -> end). default(Config) -> - escalus:story(Config, [{alice, 1}, {bob, 1}], fun(Alice, Bob) -> + escalus:fresh_story(Config, [{alice, 1}, {bob, 1}], fun(Alice, Bob) -> privacy_helper:set_list(Alice, {<<"deny_client">>, Bob}), @@ -288,7 +283,7 @@ default(Config) -> end). default_conflict(Config) -> - escalus:story(Config, [{alice, 2}, {bob, 1}], fun(Alice, Alice2, Bob) -> + escalus:fresh_story(Config, [{alice, 2}, {bob, 1}], fun(Alice, Alice2, Bob) -> %% testcase setup %% setup list on server @@ -316,7 +311,7 @@ default_conflict(Config) -> end). default_nonexistent(Config) -> - escalus:story(Config, [{alice, 1}], fun(Alice) -> + escalus:fresh_story(Config, [{alice, 1}], fun(Alice) -> Request = escalus_stanza:privacy_set_default(<<"some_list">>), escalus_client:send(Alice, Request), @@ -327,7 +322,7 @@ default_nonexistent(Config) -> end). no_default(Config) -> - escalus:story(Config, [{alice, 1}], fun(Alice) -> + escalus:fresh_story(Config, [{alice, 1}], fun(Alice) -> Request = escalus_stanza:privacy_no_default(), escalus_client:send(Alice, Request), @@ -338,7 +333,7 @@ no_default(Config) -> end). set_list(Config) -> - escalus:story(Config, [{alice, 3}, {bob, 1}], fun(Alice, Alice2, Alice3, Bob) -> + escalus:fresh_story(Config, [{alice, 3}, {bob, 1}], fun(Alice, Alice2, Alice3, Bob) -> privacy_helper:send_set_list(Alice, {<<"deny_client">>, Bob}), @@ -364,7 +359,7 @@ set_list(Config) -> end). remove_list(Config) -> - escalus:story(Config, [{alice, 1}, {bob, 1}], fun(Alice, Bob) -> + escalus:fresh_story(Config, [{alice, 1}, {bob, 1}], fun(Alice, Bob) -> privacy_helper:send_set_list(Alice, {<<"deny_client">>, Bob}), @@ -392,7 +387,7 @@ remove_list(Config) -> end). block_jid_message(Config) -> - escalus:story(Config, [{alice, 1}, {bob, 1}], fun(Alice, Bob) -> + escalus:fresh_story(Config, [{alice, 1}, {bob, 1}], fun(Alice, Bob) -> %% Alice should receive message escalus_client:send(Bob, @@ -420,7 +415,7 @@ block_jid_message(Config) -> end). block_group_message(Config) -> - escalus:story(Config, [{alice, 1}, {bob, 1}], fun(Alice, Bob) -> + escalus:fresh_story(Config, [{alice, 1}, {bob, 1}], fun(Alice, Bob) -> %% Alice should receive message escalus_client:send(Bob, escalus_stanza:chat_to(Alice, <<"Hi!">>)), @@ -442,7 +437,7 @@ block_group_message(Config) -> end). block_subscription_message(Config) -> - escalus:story(Config, [{alice, 1}, {bob, 1}], fun(Alice, Bob) -> + escalus:fresh_story(Config, [{alice, 1}, {bob, 1}], fun(Alice, Bob) -> %% Alice should receive message escalus_client:send(Bob, escalus_stanza:chat_to(Alice, <<"Hi!">>)), @@ -466,7 +461,7 @@ block_subscription_message(Config) -> end). allow_subscription_to_from_message(Config) -> - escalus:story(Config, [{alice, 1}, {bob, 1}], fun(Alice, Bob) -> + escalus:fresh_story(Config, [{alice, 1}, {bob, 1}], fun(Alice, Bob) -> %% deny all message but not from subscribed "to" privacy_helper:set_and_activate(Alice, <<"deny_all_message_but_subscription_to">>), @@ -485,20 +480,9 @@ allow_subscription_to_from_message(Config) -> escalus_assert:has_no_stanzas(Bob), %% Alice subscribes to Bob - SubStanza = escalus_stanza:presence_direct(escalus_utils:get_short_jid(Bob), - <<"subscribe">>), - escalus_client:send(Alice, SubStanza), - escalus_client:wait_for_stanza(Alice), - escalus_client:wait_for_stanza(Bob), - - SubConfirmStanza = escalus_stanza:presence_direct(escalus_utils:get_short_jid(Alice), - <<"subscribed">>), - %% Bob accepts Alice - escalus_client:send(Bob, SubConfirmStanza), - escalus_client:wait_for_stanza(Bob), - escalus_client:wait_for_stanzas(Alice, 3), - - %% Now Alice is subscirbed "to" Bob + subscribe_from_to(Alice, Bob, false), + + %% Now Alice is subscribed "to" Bob %% And Bob is subscribed "from" Alice escalus_client:send(Bob, escalus_stanza:chat_to(Alice, <<"Hi, Alice XYZ!">>)), @@ -511,64 +495,46 @@ allow_subscription_to_from_message(Config) -> end). - allow_subscription_both_message(Config) -> - escalus:story(Config, [{alice, 1}], fun(Alice) -> + escalus:fresh_story(Config, [{alice, 1}, {bob, 1}], fun(Alice, Bob) -> - BobBareJID = escalus_users:get_jid(Config, bob), - [{_, Spec}] = escalus_users:get_users([bob]), - {ok, Bob, _Features} = escalus_connection:start(Spec), + %% Alice subscribes to Bob + subscribe_from_to(Alice, Bob, false), - %escalus_story:send_initial_presence(Alice), - escalus_story:send_initial_presence(Bob), - escalus_client:wait_for_stanza(Alice), - escalus_client:wait_for_stanza(Bob), %% deny all message but not from subscribed "to" privacy_helper:set_and_activate(Alice, <<"deny_all_message_but_subscription_both">>), %% deny all message but not from subscribed "from" privacy_helper:set_and_activate(Bob, <<"deny_all_message_but_subscription_both">>), - %% Bob and Alice cannot sent to each other now + %% Bob and Alice cannot write to each other now %% Even though they are in subscription "to" and "from" respectively escalus_client:send(Bob, escalus_stanza:chat_to( Alice, <<"Hi, Alice XYZ!">>)), escalus_client:send(Alice, escalus_stanza:chat_to( - BobBareJID, <<"Hi, Bob XYZ 1!">>)), + Bob, <<"Hi, Bob XYZ 1!">>)), - ct:sleep(?SLEEP_TIME), privacy_helper:gets_error(Alice, <<"service-unavailable">>), privacy_helper:gets_error(Bob, <<"service-unavailable">>), escalus_assert:has_no_stanzas(Alice), escalus_assert:has_no_stanzas(Bob), - %% Alice subscribes to Bob - SubStanza = escalus_stanza:presence_direct(escalus_utils:get_short_jid(Alice), - <<"subscribe">>), - escalus_client:send(Bob, SubStanza), - escalus_client:wait_for_stanza(Alice), - escalus_client:wait_for_stanza(Bob), - - %% Bob accepts Alice - SubConfirmStanza = escalus_stanza:presence_direct(BobBareJID, - <<"subscribed">>), - escalus_client:send(Alice, SubConfirmStanza), - escalus_client:wait_for_stanzas(Alice, 2), - escalus_client:wait_for_stanzas(Bob, 3), + %% Bob subscribes to Alice + subscribe_from_to(Bob, Alice, true), %% Now their subscription is in state "both" escalus_client:send(Bob, escalus_stanza:chat_to(Alice, <<"Hi, Alice XYZ!">>)), escalus_assert:is_chat_message(<<"Hi, Alice XYZ!">>, escalus_client:wait_for_stanza(Alice)), - escalus_client:send(Alice, escalus_stanza:chat_to(BobBareJID, <<"Hi, Bob XYZ! 2">>)), + escalus_client:send(Alice, escalus_stanza:chat_to(Bob, <<"Hi, Bob XYZ! 2">>)), escalus_assert:is_chat_message(<<"Hi, Bob XYZ! 2">>, escalus_client:wait_for_stanza(Bob)) end). block_all_message(Config) -> - escalus:story(Config, [{alice, 1}, {bob, 1}], fun(Alice, Bob) -> + escalus:fresh_story(Config, [{alice, 1}, {bob, 1}], fun(Alice, Bob) -> %% Alice should receive message escalus_client:send(Bob, escalus_stanza:chat_to(Alice, <<"Hi!">>)), @@ -587,10 +553,10 @@ block_all_message(Config) -> end). block_jid_presence_in(Config) -> - escalus:story(Config, [{alice, 1}, {bob, 1}], fun(Alice, Bob) -> + escalus:fresh_story(Config, [{alice, 1}, {bob, 1}], fun(Alice, Bob) -> %% Alice should receive presence in - Presence1 = escalus_stanza:presence_direct(escalus_utils:get_short_jid(alice), + Presence1 = escalus_stanza:presence_direct(escalus_utils:get_short_jid(Alice), <<"available">>), escalus_client:send(Bob, Presence1), Received = escalus_client:wait_for_stanza(Alice), @@ -612,7 +578,7 @@ block_jid_presence_in(Config) -> end). block_jid_presence_out(Config) -> - escalus:story(Config, [{alice, 1}, {bob, 1}], fun(Alice, Bob) -> + escalus:fresh_story(Config, [{alice, 1}, {bob, 1}], fun(Alice, Bob) -> BobBareJID = escalus_utils:get_short_jid(Bob), @@ -646,7 +612,7 @@ version_iq(Type, From, To) -> Req2. iq_reply_doesnt_crash_user_process(Config) -> - escalus:story(Config, [{alice, 1}, {bob, 1}], fun(Alice, Bob) -> + escalus:fresh_story(Config, [{alice, 1}, {bob, 1}], fun(Alice, Bob) -> QueryWithPrivacyNS = escalus_stanza:query_el(?NS_PRIVACY, []), %% Send IQ reply with privacy ns @@ -665,11 +631,12 @@ iq_reply_doesnt_crash_user_process(Config) -> end). block_jid_iq(Config) -> - escalus:story(Config, [{alice, 1}, {bob, 1}], fun(Alice, Bob) -> + escalus:fresh_story(Config, [{alice, 1}, {bob, 1}], fun(Alice, Bob) -> - privacy_helper:set_list(Alice, <<"deny_localhost_iq">>), + LServer = escalus_utils:get_server(Bob), + privacy_helper:set_list(Alice, {<<"deny_server_iq">>, LServer}), %% activate it - Stanza = escalus_stanza:privacy_activate(<<"deny_localhost_iq">>), + Stanza = escalus_stanza:privacy_activate(<<"deny_server_iq">>), escalus_client:send(Alice, Stanza), timer:sleep(500), %% we must let it sink in @@ -692,9 +659,7 @@ block_jid_iq(Config) -> end). block_jid_all(Config) -> - %% unexprected presence unavalable - mongoose_helper:kick_everyone(), - escalus:story(Config, [{alice, 1}, {bob, 1}], fun(Alice, Bob) -> + escalus:fresh_story(Config, [{alice, 1}, {bob, 1}], fun(Alice, Bob) -> privacy_helper:set_list(Alice, {<<"deny_jid_all">>, Bob}), @@ -744,7 +709,7 @@ block_jid_all(Config) -> end). block_jid_message_but_not_presence(Config) -> - escalus:story(Config, [{alice, 1}, {bob, 1}], fun(Alice, Bob) -> + escalus:fresh_story(Config, [{alice, 1}, {bob, 1}], fun(Alice, Bob) -> %% Alice should receive message Msg = escalus_stanza:chat_to(Alice, <<"Hi! What's your name?">>), @@ -895,3 +860,25 @@ check_subscription_stanzas(Stanzas, Type) -> escalus_pred:is_presence_with_type(Type, S) end, escalus:assert_many([is_roster_set, IsPresWithType], Stanzas). + +subscribe_from_to(From, To, IsSecondSubscription) -> + %% From subscribes to To + ToBareJid = escalus_utils:get_short_jid(To), + SubStanza = escalus_stanza:presence_direct(ToBareJid, <<"subscribe">>), + escalus_client:send(From, SubStanza), + PushReq = escalus_client:wait_for_stanza(From), + escalus:assert(is_roster_set, PushReq), + Received = escalus_client:wait_for_stanza(To), + %% To accepts From + FromBareJid = escalus_utils:get_short_jid(From), + SubConfirmStanza = escalus_stanza:presence_direct(FromBareJid, <<"subscribed">>), + escalus_client:send(To, SubConfirmStanza), + case IsSecondSubscription of + true -> + escalus:assert(is_roster_set, Received), + escalus_client:wait_for_stanzas(To, 2); + false -> + escalus:assert(is_presence_with_type, [<<"subscribe">>], Received), + escalus_client:wait_for_stanza(To) + end, + escalus_client:wait_for_stanzas(From, 3). diff --git a/big_tests/tests/privacy_helper.erl b/big_tests/tests/privacy_helper.erl index a46ee501a9..337084aaeb 100644 --- a/big_tests/tests/privacy_helper.erl +++ b/big_tests/tests/privacy_helper.erl @@ -5,8 +5,6 @@ -include_lib("escalus/include/escalus_xmlns.hrl"). -include_lib("common_test/include/ct.hrl"). --import(escalus_compat, [bin/1]). - -export([set_and_activate/2, set_list/2, set_list/3, @@ -126,6 +124,8 @@ is_presence_error(Stanza) -> privacy_list({Name, #client{} = Target}) -> JID = escalus_utils:get_short_jid(Target), escalus_stanza:privacy_list(Name, list_content(Name, JID)); +privacy_list({Name, Target}) -> + escalus_stanza:privacy_list(Name, list_content(Name, Target)); privacy_list(Name) -> escalus_stanza:privacy_list(Name, list_content(Name)). @@ -188,10 +188,6 @@ list_content(<<"deny_unsubscribed_presence_out">>) -> [ list_content(<<"deny_all_presence_out">>) -> [ escalus_stanza:privacy_list_item(<<"1">>, <<"deny">>, [<<"presence-out">>]) ]; -list_content(<<"deny_localhost_iq">>) -> [ - escalus_stanza:privacy_list_jid_item(<<"1">>, <<"deny">>, - <<"localhost">>, [<<"iq">>]) - ]; list_content(<<"deny_group_iq">>) -> [ escalus_stanza:privacy_list_item(<<"1">>, <<"deny">>, <<"group">>, <<"ignored">>, [<<"iq">>]) @@ -219,6 +215,9 @@ list_content(<<"deny_all_iq">>) -> [ escalus_stanza:privacy_list_item(<<"1">>, <<"deny">>, [<<"iq">>]) ]. +list_content(<<"deny_server_iq">>, LServer) -> [ + escalus_stanza:privacy_list_jid_item(<<"1">>, <<"deny">>, LServer, [<<"iq">>]) + ]; list_content(<<"deny_client">>, JID) -> [ escalus_stanza:privacy_list_jid_item(<<"1">>, <<"deny">>, JID, []) ]; @@ -237,10 +236,11 @@ list_content(<<"deny_client_presence_out">>, JID) -> [ escalus_stanza:privacy_list_jid_item(<<"1">>, <<"deny">>, JID, [<<"presence-out">>]) ]; -list_content(<<"deny_jid_all">>, JID) -> [ +list_content(<<"deny_jid_all">>, JID) -> + LServer = escalus_utils:get_server(JID), + [ escalus_stanza:privacy_list_jid_item(<<"1">>, <<"deny">>, JID, []), - escalus_stanza:privacy_list_jid_item(<<"2">>, <<"deny">>, - <<"localhost">>, []) + escalus_stanza:privacy_list_jid_item(<<"2">>, <<"deny">>, LServer, []) ]; list_content(<<"deny_3_items">>, JID) -> [ escalus_stanza:privacy_list_jid_item(<<"1">>, <<"deny">>, JID, []), diff --git a/doc/migrations/4.2.0_4.3.0.md b/doc/migrations/4.2.0_4.3.0.md index d7782ffee6..012acd7c88 100644 --- a/doc/migrations/4.2.0_4.3.0.md +++ b/doc/migrations/4.2.0_4.3.0.md @@ -79,6 +79,59 @@ DROP INDEX i_muc_light_blocking ON muc_light_blocking; GO ``` +### mod_privacy + +Server fields are added to privacy data, as well as their primary key having been expanded: + +For Postgres: + +```sql +-- Create a new index for the new primary key. +CREATE UNIQUE INDEX i_privacy_default_list_su ON privacy_default_list (server, username); +CREATE UNIQUE INDEX i_privacy_list_sun ON privacy_list (server, username, name); + +-- Now enter a transaction block to replace the primary with the new one. +BEGIN; +ALTER TABLE privacy_default_list ADD COLUMN server varchar(250); +ALTER TABLE privacy_default_list DROP CONSTRAINT privacy_default_list_pkey; +ALTER TABLE privacy_default_list ADD CONSTRAINT privacy_default_list_pkey PRIMARY KEY USING INDEX i_privacy_default_list_su; + +ALTER TABLE privacy_list ADD COLUMN server varchar(250) NOT NULL; +ALTER TABLE privacy_list DROP CONSTRAINT privacy_list_pkey; +ALTER TABLE privacy_list ADD CONSTRAINT privacy_list_pkey PRIMARY KEY USING INDEX i_privacy_list_sun; +COMMIT; +``` + +For MySQL: + +```sql +BEGIN; +ALTER TABLE privacy_default_list ADD COLUMN server varchar(250); +ALTER TABLE privacy_default_list DROP PRIMARY KEY; +ALTER TABLE privacy_default_list ADD PRIMARY KEY USING BTREE(server, username); + +ALTER TABLE privacy_list ADD COLUMN server varchar(250) NOT NULL; +ALTER TABLE privacy_list DROP PRIMARY KEY; +ALTER TABLE privacy_list ADD PRIMARY KEY USING BTREE(server, username, name); +COMMIT; +``` + +For MSSQL: + +```sql +ALTER TABLE privacy_default_list ADD COLUMN server varchar(250); +ALTER TABLE privacy_default_list DROP CONSTRAINT PK_privacy_default_list; +ALTER TABLE privacy_default_list ADD CONSTRAINT PK_privacy_default_list PRIMARY KEY CLUSTERED( + server ASC, username ASC); +GO + +ALTER TABLE privacy_list ADD COLUMN server varchar(250); +ALTER TABLE privacy_list DROP CONSTRAINT PK_privacy_list; +ALTER TABLE privacy_list ADD CONSTRAINT PK_privacy_list PRIMARY KEY CLUSTERED( + server ASC, username ASC, name ASC); +GO +``` + ## Hook migrations diff --git a/include/mod_privacy.hrl b/include/mod_privacy.hrl index 00b142b91b..aaf8a5fffa 100644 --- a/include/mod_privacy.hrl +++ b/include/mod_privacy.hrl @@ -18,21 +18,26 @@ %%% %%%---------------------------------------------------------------------- --record(privacy, {us, - default = none, - lists = []}). - --record(listitem, {type = none, - value = none, - action, - order, - match_all = false, - match_iq = false, - match_message = false, - match_presence_in = false, - match_presence_out = false - }). - --record(userlist, {name = none, list = [], needdb = false }). +-record(privacy, { + us :: jid:simple_bare_jid(), + default = none :: mod_privacy:list_name(), + lists = [] :: [{mod_privacy:list_name(), mod_privacy:list_item()}] + }). +-record(listitem, { + type = none :: mod_privacy:privacy_item_type(), + value = none, + action :: allow | deny | block, + order :: undefined | non_neg_integer(), + match_all = false :: boolean(), + match_iq = false :: boolean(), + match_message = false :: boolean(), + match_presence_in = false :: boolean(), + match_presence_out = false :: boolean() + }). +-record(userlist, { + name = none :: mod_privacy:list_name(), + list = [] :: [mod_privacy:list_item()], + needdb = false :: boolean() + }). diff --git a/priv/mssql2012.sql b/priv/mssql2012.sql index 89a64793a8..0f537e7a60 100644 --- a/priv/mssql2012.sql +++ b/priv/mssql2012.sql @@ -181,11 +181,12 @@ GO SET ANSI_PADDING ON GO CREATE TABLE [dbo].[privacy_default_list]( + [server] [nvarchar](250) NOT NULL, [username] [nvarchar](250) NOT NULL, [name] [nvarchar](250) NOT NULL, CONSTRAINT [PK_privacy_default_list_username] PRIMARY KEY CLUSTERED ( - [username] ASC + [server], [username] )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY] ) ON [PRIMARY] @@ -200,6 +201,7 @@ GO SET ANSI_PADDING ON GO CREATE TABLE [dbo].[privacy_list]( + [server] [nvarchar](250) NOT NULL, [username] [nvarchar](250) NOT NULL, [name] [nvarchar](250) NOT NULL, [id] [bigint] IDENTITY(1,1) NOT NULL, diff --git a/priv/mysql.sql b/priv/mysql.sql index 6fa56db84e..1e1653e00a 100644 --- a/priv/mysql.sql +++ b/priv/mysql.sql @@ -139,17 +139,20 @@ CREATE INDEX i_vcard_search_lorgname ON vcard_search(lorgname); CREATE INDEX i_vcard_search_lorgunit ON vcard_search(lorgunit); CREATE TABLE privacy_default_list ( - username varchar(250) PRIMARY KEY, - name varchar(250) NOT NULL + server varchar(250) NOT NULL, + username varchar(250) NOT NULL, + name varchar(250) NOT NULL, + PRIMARY KEY (server, username) ) CHARACTER SET utf8mb4 ROW_FORMAT=DYNAMIC; CREATE TABLE privacy_list ( + server varchar(250) NOT NULL, username varchar(250) NOT NULL, name varchar(250) NOT NULL, id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT UNIQUE, created_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, - PRIMARY KEY (username(75), name(75)) + PRIMARY KEY (server, username(75), name(75)) ) CHARACTER SET utf8mb4 ROW_FORMAT=DYNAMIC; diff --git a/priv/pg.sql b/priv/pg.sql index 42ef269fd8..797266857f 100644 --- a/priv/pg.sql +++ b/priv/pg.sql @@ -127,16 +127,19 @@ CREATE INDEX i_vcard_search_lorgname ON vcard_search(lorgname); CREATE INDEX i_vcard_search_lorgunit ON vcard_search(lorgunit); CREATE TABLE privacy_default_list ( - username varchar(250) PRIMARY KEY, - name text NOT NULL + server varchar(250), + username varchar(250), + name text NOT NULL, + PRIMARY KEY (server, username) ); CREATE TABLE privacy_list ( + server varchar(250) NOT NULL, username varchar(250) NOT NULL, name text NOT NULL, id SERIAL UNIQUE, created_at TIMESTAMP NOT NULL DEFAULT now(), - PRIMARY KEY (username,name) + PRIMARY KEY (server, username, name) ); CREATE TABLE privacy_list_data ( diff --git a/rel/files/mongooseim.toml b/rel/files/mongooseim.toml index cce55fa6e9..135aa91d93 100644 --- a/rel/files/mongooseim.toml +++ b/rel/files/mongooseim.toml @@ -240,8 +240,8 @@ {{{mod_offline}}} {{/mod_offline}} {{#mod_privacy}} +[modules.mod_privacy] {{{mod_privacy}}} - {{/mod_privacy}} {{#mod_blocking}} {{{mod_blocking}}} diff --git a/rel/mim1.vars-toml.config b/rel/mim1.vars-toml.config index 16b92af283..4d05c8ea57 100644 --- a/rel/mim1.vars-toml.config +++ b/rel/mim1.vars-toml.config @@ -8,6 +8,7 @@ {host_types, "\"test type\""}. {default_server_domain, "\"localhost\""}. +{mod_privacy, ""}. {host_config, "[[host_config]] host = \"anonymous.localhost\" @@ -38,7 +39,11 @@ {{#mod_roster}} [host_config.modules.mod_roster] {{{mod_roster}}} - {{/mod_roster}}"}. + {{/mod_roster}} + {{#mod_privacy}} + [host_config.modules.mod_privacy] + {{{mod_privacy}}} + {{/mod_privacy}}"}. {password_format, "password.format = \"scram\" password.hash = [\"sha256\"]"}. {scram_iterations, 64}. diff --git a/rel/vars-toml.config.in b/rel/vars-toml.config.in index e6db3e4e55..36077eaa55 100644 --- a/rel/vars-toml.config.in +++ b/rel/vars-toml.config.in @@ -19,9 +19,10 @@ shaper_rule = \"fast\" ip_address = \"127.0.0.1\" password = \"secret\""}. +{mod_cache_users, ""}. % enabled, no options {mod_last, "[modules.mod_last]"}. {mod_offline, ""}. % enabled, no options -{mod_privacy, "[modules.mod_privacy]"}. +{mod_privacy, ""}. {mod_blocking, "[modules.mod_blocking]"}. {mod_private, "[modules.mod_private]"}. {mod_roster, ""}. % enabled, no options diff --git a/src/mod_blocking.erl b/src/mod_blocking.erl index 597f02d82b..0955781da8 100644 --- a/src/mod_blocking.erl +++ b/src/mod_blocking.erl @@ -44,10 +44,10 @@ disco_local_features(Acc = #{node := <<>>}) -> disco_local_features(Acc) -> Acc. - process_iq_get(Acc, _From = #jid{luser = LUser, lserver = LServer}, _, #iq{xmlns = ?NS_BLOCKING}, _) -> - Res = case mod_privacy_backend:get_privacy_list(LUser, LServer, <<"blocking">>) of + HostType = mongoose_acc:host_type(Acc), + Res = case mod_privacy_backend:get_privacy_list(HostType, LUser, LServer, <<"blocking">>) of {error, not_found} -> {ok, []}; {ok, L} -> @@ -67,11 +67,12 @@ process_iq_get(Val, _, _, _, _) -> process_iq_set(Acc, From, _To, #iq{xmlns = ?NS_BLOCKING, sub_el = SubEl}) -> %% collect needed data + HostType = mongoose_acc:host_type(Acc), #jid{luser = LUser, lserver = LServer} = From, #xmlel{name = BType} = SubEl, Type = parse_command_type(BType), Usrs = exml_query:paths(SubEl, [{element, <<"item">>}, {attr, <<"jid">>}]), - CurrList = case mod_privacy_backend:get_privacy_list(LUser, LServer, <<"blocking">>) of + CurrList = case mod_privacy_backend:get_privacy_list(HostType, LUser, LServer, <<"blocking">>) of {ok, List} -> List; {error, not_found} -> @@ -111,12 +112,13 @@ process_blocking_iq_set(block, Acc, _, _, _, []) -> {Acc, {error, mongoose_xmpp_errors:bad_request()}}; process_blocking_iq_set(Type, Acc, LUser, LServer, CurrList, Usrs) -> %% check who is being added / removed + HostType = mongoose_acc:host_type(Acc), {NType, Changed, NewList} = blocking_list_modify(Type, Usrs, CurrList), - case mod_privacy_backend:replace_privacy_list(LUser, LServer, <<"blocking">>, NewList) of + case mod_privacy_backend:replace_privacy_list(HostType, LUser, LServer, <<"blocking">>, NewList) of {error, E} -> {error, E}; ok -> - case mod_privacy_backend:set_default_list(LUser, LServer, <<"blocking">>) of + case mod_privacy_backend:set_default_list(HostType, LUser, LServer, <<"blocking">>) of ok -> {Acc, {ok, Changed, NewList, NType}}; {error, not_found} -> diff --git a/src/mod_privacy_riak.erl b/src/mod_privacy_riak.erl deleted file mode 100644 index c1fb6c0df8..0000000000 --- a/src/mod_privacy_riak.erl +++ /dev/null @@ -1,160 +0,0 @@ -%%============================================================================== -%% Copyright 2015 Guillaume Bour. -%% -%% Licensed under the Apache License, Version 2.0 (the "License"); -%% you may not use this file except in compliance with the License. -%% You may obtain a copy of the License at -%% -%% http://www.apache.org/licenses/LICENSE-2.0 -%% -%% Unless required by applicable law or agreed to in writing, software -%% distributed under the License is distributed on an "AS IS" BASIS, -%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -%% See the License for the specific language governing permissions and -%% limitations under the License. -%%============================================================================== - -% -% NOTES: -% user default privacy is stored in bucket {<<"privacy_defaults">>, LServer}, key LUser -% user privacy lists names are stored in set {<<"privacy_lists_names">>, <<"LServer">>}, key LUser -% user privacy lists content are stored in bucket -% {<<"privacy_lists">>, LServer}, key <> - --module(mod_privacy_riak). --author('guillaume@bour.cc'). --behaviour(mod_privacy). - --export([init/2, - get_default_list/2, - get_list_names/2, - get_privacy_list/3, - forget_default_list/2, - set_default_list/3, - remove_privacy_list/3, - replace_privacy_list/4, - remove_user/2]). - --include("mongoose.hrl"). --include("jlib.hrl"). --include("mod_privacy.hrl"). - --include_lib("riakc/include/riakc.hrl"). - -get_bucket_name(LServer, Opt, Default) -> - {gen_mod:get_module_opt(LServer, mod_privacy, Opt, Default), LServer}. - --define(BKT_DEFAULT_LIST(Server), - get_bucket_name(Server, defaults_bucket_type, <<"privacy_defaults">>)). --define(BKT_LISTS_NAMES(Server), - get_bucket_name(Server, names_bucket_type, <<"privacy_lists_names">>)). --define(BKT_LISTS(Server), - get_bucket_name(Server, bucket_type, <<"privacy_lists">>)). - -init(_Host, _Opts) -> - ok. - -get_default_list(LUser, LServer) -> - case get_default_list_name(LUser, LServer) of - none -> - {error, not_found}; - Default -> - case get_privacy_list(LUser, LServer, Default) of - {ok, List} -> - {ok, {Default, List}}; - {error, Reason} -> - {error, Reason} - end - end. - -get_list_names(LUser, LServer) -> - Default = get_default_list_name(LUser, LServer), - Names = get_list_names_only(LUser, LServer), - {ok, {Default, Names}}. - --spec get_default_list_name(jid:luser(), jid:lserver()) -> binary() | none. -get_default_list_name(LUser, LServer) -> - case mongoose_riak:get(?BKT_DEFAULT_LIST(LServer), LUser) of - {ok, Obj} -> - riakc_obj:get_value(Obj); - _ -> none - end. - --spec get_list_names_only(jid:luser(), jid:lserver()) -> list(binary()). -get_list_names_only(LUser, LServer) -> - case mongoose_riak:fetch_type(?BKT_LISTS_NAMES(LServer), LUser) of - {ok, Set} -> - riakc_set:value(Set); - {error, {notfound, set}} -> - []; - Err -> - ?LOG_ERROR(#{what => privacy_get_list_names_only_failed, - user => LUser, server => LServer, reason => Err}), - [] - end. - -get_privacy_list(LUser, LServer, Name) -> - case mongoose_riak:get(?BKT_LISTS(LServer), <>) of - {ok, Obj} -> - Val = binary_to_term(riakc_obj:get_value(Obj)), - {ok, Val}; - {error, notfound} -> - {error, not_found}; - Err -> - ?LOG_ERROR(#{what => privacy_get_list_names_only_failed, - user => LUser, server => LServer, list_name => Name, - reason => Err}), - Err - end. - -forget_default_list(LUser, LServer) -> - mongoose_riak:delete(?BKT_DEFAULT_LIST(LServer), LUser). - -set_default_list(LUser, LServer, Name) -> - case mongoose_riak:get(?BKT_LISTS(LServer), <>) of - {ok, _} -> - % create or update - Obj = riakc_obj:new(?BKT_DEFAULT_LIST(LServer), LUser, Name), - mongoose_riak:put(Obj); - % in case list name is not found - {error, notfound} -> - {error, not_found}; - Err -> Err - end. - -remove_privacy_list(LUser, LServer, Name) -> - mongoose_riak:delete(?BKT_LISTS(LServer), <>), - - case mongoose_riak:fetch_type(?BKT_LISTS_NAMES(LServer), LUser) of - {ok, S1} -> - S2 = riakc_set:del_element(Name, S1), - mongoose_riak:update_type(?BKT_LISTS_NAMES(LServer), LUser, riakc_set:to_op(S2)); - Err -> - ?LOG_ERROR(#{what => privacy_remove_privacy_list_failed, - user => LUser, server => LServer, list_name => Name, - reason => Err}), - Err - end. - -replace_privacy_list(LUser, LServer, Name, List) -> - % store privacy-list content - BinaryList = term_to_binary(List), - Obj = riakc_obj:new(?BKT_LISTS(LServer), <>, BinaryList), - mongoose_riak:put(Obj), - - % add new list name to user privacy-lists set - S = riakc_set:add_element(Name, riakc_set:new()), - mongoose_riak:update_type(?BKT_LISTS_NAMES(LServer), LUser, riakc_set:to_op(S)). - -remove_user(LUser, LServer) -> - forget_default_list(LUser, LServer), - - lists:foreach( - fun(Name) -> remove_privacy_list(LUser, LServer, Name) end, - get_list_names_only(LUser, LServer) - ), - - % delete user privacy_lists set - mongoose_riak:delete(?BKT_LISTS_NAMES(LServer), LUser). - - diff --git a/src/mod_private.erl b/src/mod_private.erl index 3cfeb2aab6..7fd3a8d3cf 100644 --- a/src/mod_private.erl +++ b/src/mod_private.erl @@ -39,18 +39,8 @@ -export([config_metrics/1]). --define(MOD_PRIVACY_BACKEND, mod_privacy_backend). -define(MOD_PRIVATE_BACKEND, mod_private_backend). -ignore_xref([ - {?MOD_PRIVACY_BACKEND, get_default_list, 2}, - {?MOD_PRIVACY_BACKEND, get_privacy_list, 3}, - {?MOD_PRIVACY_BACKEND, forget_default_list, 2}, - {?MOD_PRIVACY_BACKEND, set_default_list, 3}, - {?MOD_PRIVACY_BACKEND, get_list_names, 2}, - {?MOD_PRIVACY_BACKEND, remove_privacy_list, 3}, - {?MOD_PRIVACY_BACKEND, remove_user, 2}, - {?MOD_PRIVACY_BACKEND, replace_privacy_list, 4}, - {?MOD_PRIVACY_BACKEND, init, 2}, {?MOD_PRIVATE_BACKEND, get_all_nss, 2}, {?MOD_PRIVATE_BACKEND, multi_get_data, 3}, {?MOD_PRIVATE_BACKEND, multi_set_data, 3}, diff --git a/src/mongoose_hooks.erl b/src/mongoose_hooks.erl index ea2810ac7a..f8da8376b4 100644 --- a/src/mongoose_hooks.erl +++ b/src/mongoose_hooks.erl @@ -625,7 +625,7 @@ privacy_check_packet(Acc, JID, PrivacyList, FromToNameType, Dir) -> Result :: mongoose_privacy:userlist(). privacy_get_user_list(HostType, JID) -> %% TODO: ejabberd_c2s calls this hook with host type, fix other places. - run_hook_for_host_type(privacy_get_user_list, HostType, #userlist{}, [JID]). + run_hook_for_host_type(privacy_get_user_list, HostType, #userlist{}, [HostType, JID]). -spec privacy_iq_get(HostType, Acc, From, To, IQ, PrivList) -> Result when HostType :: binary(), diff --git a/src/mod_privacy.erl b/src/privacy/mod_privacy.erl similarity index 70% rename from src/mod_privacy.erl rename to src/privacy/mod_privacy.erl index 08067e0944..dc0d149022 100644 --- a/src/mod_privacy.erl +++ b/src/privacy/mod_privacy.erl @@ -30,131 +30,142 @@ -behaviour(gen_mod). -behaviour(mongoose_module_metrics). --export([start/2, - stop/1, - config_spec/0, - process_iq_set/4, +%% gen_mod +-export([start/2]). +-export([stop/1]). +-export([supported_features/0]). +-export([config_spec/0]). + +-export([process_iq_set/4, process_iq_get/5, - get_user_list/2, + get_user_list/3, check_packet/5, remove_user/3, - updated_list/3]). + remove_domain/3, + updated_list/3, + disco_local_features/1 + ]). -export([config_metrics/1]). --ignore_xref([behaviour_info/1, check_packet/5, get_user_list/2, process_iq_get/5, - process_iq_set/4, remove_user/3, updated_list/3, behaviour_info/1, - remove_user/3]). +-ignore_xref([ + {mod_privacy_backend, init, 2}, + {mod_privacy_backend, get_default_list, 3}, + {mod_privacy_backend, get_list_names, 3}, + {mod_privacy_backend, get_privacy_list, 4}, + {mod_privacy_backend, set_default_list, 4}, + {mod_privacy_backend, forget_default_list, 3}, + {mod_privacy_backend, remove_privacy_list, 4}, + {mod_privacy_backend, replace_privacy_list, 5}, + {mod_privacy_backend, remove_user, 3}, + {mod_privacy_backend, remove_domain, 2}, + behaviour_info/1, check_packet/5, get_user_list/3, process_iq_get/5, + process_iq_set/4, remove_user/3, updated_list/3, + remove_user/3, remove_domain/3, disco_local_features/1]). -include("mongoose.hrl"). -include("jlib.hrl"). -include("mod_privacy.hrl"). -include("mongoose_config_spec.hrl"). +-export_type([list_name/0]). -export_type([list_item/0]). +-export_type([privacy_item_type/0]). --type list_name() :: binary(). +-type list_name() :: binary() | none. -type list_item() :: #listitem{}. +-type privacy_item_type() :: none | jid | group | subscription. %% ------------------------------------------------------------------ %% Backend callbacks +%% ------------------------------------------------------------------ --callback init(Host, Opts) -> ok when - Host :: binary(), - Opts :: list(). - --callback remove_user(LUser, LServer) -> any() when - LUser :: binary(), - LServer :: binary(). - --callback get_list_names(LUser, LServer) -> - {ok, {Default, Names}} | {error, Reason} when - LUser :: binary(), - LServer :: binary(), - Default :: list_name(), - Names :: list(list_name()), - Reason :: not_found | term(). - --callback get_privacy_list(LUser, LServer, Name) -> - {ok, Items} | {error, Reason} when - LUser :: binary(), - LServer :: binary(), - Name :: list_name(), - Items :: list(list_item()), - Reason :: not_found | term(). - --callback set_default_list(LUser, LServer, Name) -> ok | {error, Reason} when - LUser :: binary(), - LServer :: binary(), - Name :: list_name(), - Reason :: not_found | term(). - --callback forget_default_list(LUser, LServer) -> ok | {error, Reason} when - LUser :: binary(), - LServer :: binary(), - Reason :: not_found | term(). - --callback remove_privacy_list(LUser, LServer, Name) -> ok | {error, Reason} when - LUser :: binary(), - LServer :: binary(), - Name :: list_name(), - Reason :: conflict | term(). - --callback replace_privacy_list(LUser, LServer, Name, Items) -> - ok | {error, Reason} when - LUser :: binary(), - LServer :: binary(), - Name :: list_name(), - Items :: list(list_item()), - Reason :: conflict | term(). - --callback get_default_list(LUser, LServer) -> - {ok, {Default, Items}} | {error, Reason} when - LUser :: binary(), - LServer :: binary(), - Default :: list_name(), - Items :: list(list_item()), - Reason :: not_found | term(). +-callback init(HostType, Opts) -> ok when + HostType :: mongooseim:host_type(), + Opts :: gen_mod:module_opts(). + +-callback remove_user(HostType, LUser, LServer) -> any() when + HostType :: mongooseim:host_type(), + LUser :: jid:luser(), + LServer :: jid:lserver(). + +-callback remove_domain(HostType, LServer) -> any() when + HostType :: mongooseim:host_type(), + LServer :: jid:lserver(). + +-callback get_list_names(HostType, LUser, LServer) -> + {ok, {Default, Names}} | {error, Reason} when + HostType :: mongooseim:host_type(), + LUser :: jid:luser(), + LServer :: jid:lserver(), + Default :: list_name(), + Names :: list(list_name()), + Reason :: not_found | term(). + +-callback get_privacy_list(HostType, LUser, LServer, Name) -> + {ok, Items} | {error, Reason} when + HostType :: mongooseim:host_type(), + LUser :: jid:luser(), + LServer :: jid:lserver(), + Name :: list_name(), + Items :: list(list_item()), + Reason :: not_found | term(). + +-callback set_default_list(HostType, LUser, LServer, Name) -> + ok | {error, Reason} when + HostType :: mongooseim:host_type(), + LUser :: jid:luser(), + LServer :: jid:lserver(), + Name :: list_name(), + Reason :: not_found | term(). + +-callback forget_default_list(HostType, LUser, LServer) -> + ok | {error, Reason} when + HostType :: mongooseim:host_type(), + LUser :: jid:luser(), + LServer :: jid:lserver(), + Reason :: not_found | term(). + +-callback remove_privacy_list(HostType, LUser, LServer, Name) -> + ok | {error, Reason} when + HostType :: mongooseim:host_type(), + LUser :: jid:luser(), + LServer :: jid:lserver(), + Name :: list_name(), + Reason :: conflict | term(). + +-callback replace_privacy_list(HostType, LUser, LServer, Name, Items) -> + ok | {error, Reason} when + HostType :: mongooseim:host_type(), + LUser :: jid:luser(), + LServer :: jid:lserver(), + Name :: list_name(), + Items :: list(list_item()), + Reason :: conflict | term(). + +-callback get_default_list(HostType, LUser, LServer) -> + {ok, {Default, Items}} | {error, Reason} when + HostType :: mongooseim:host_type(), + LUser :: jid:luser(), + LServer :: jid:lserver(), + Default :: list_name(), + Items :: list(list_item()), + Reason :: not_found | term(). +%% ------------------------------------------------------------------ %% gen_mod callbacks %% ------------------------------------------------------------------ -start(Host, Opts) -> +start(HostType, Opts) -> gen_mod:start_backend_module(?MODULE, Opts, [get_privacy_list, get_list_names, set_default_list, forget_default_list, remove_privacy_list, replace_privacy_list, get_default_list]), - mod_privacy_backend:init(Host, Opts), - ejabberd_hooks:add(privacy_iq_get, Host, - ?MODULE, process_iq_get, 50), - ejabberd_hooks:add(privacy_iq_set, Host, - ?MODULE, process_iq_set, 50), - ejabberd_hooks:add(privacy_get_user_list, Host, - ?MODULE, get_user_list, 50), - ejabberd_hooks:add(privacy_check_packet, Host, - ?MODULE, check_packet, 50), - ejabberd_hooks:add(privacy_updated_list, Host, - ?MODULE, updated_list, 50), - ejabberd_hooks:add(remove_user, Host, - ?MODULE, remove_user, 50), - ejabberd_hooks:add(anonymous_purge_hook, Host, - ?MODULE, remove_user, 50). - -stop(Host) -> - ejabberd_hooks:delete(privacy_iq_get, Host, - ?MODULE, process_iq_get, 50), - ejabberd_hooks:delete(privacy_iq_set, Host, - ?MODULE, process_iq_set, 50), - ejabberd_hooks:delete(privacy_get_user_list, Host, - ?MODULE, get_user_list, 50), - ejabberd_hooks:delete(privacy_check_packet, Host, - ?MODULE, check_packet, 50), - ejabberd_hooks:delete(privacy_updated_list, Host, - ?MODULE, updated_list, 50), - ejabberd_hooks:delete(remove_user, Host, - ?MODULE, remove_user, 50), - ejabberd_hooks:delete(anonymous_purge_hook, Host, - ?MODULE, remove_user, 50). + mod_privacy_backend:init(HostType, Opts), + ejabberd_hooks:add(hooks(HostType)). + +stop(HostType) -> + ejabberd_hooks:delete(hooks(HostType)). config_spec() -> #section{ @@ -175,22 +186,47 @@ riak_config_spec() -> format = none }. +-spec supported_features() -> [atom()]. +supported_features() -> + [dynamic_domains]. + +hooks(HostType) -> + [ + {disco_local_features, HostType, ?MODULE, disco_local_features, 98}, + {privacy_iq_get, HostType, ?MODULE, process_iq_get, 50}, + {privacy_iq_set, HostType, ?MODULE, process_iq_set, 50}, + {privacy_get_user_list, HostType, ?MODULE, get_user_list, 50}, + {privacy_check_packet, HostType, ?MODULE, check_packet, 50}, + {privacy_updated_list, HostType, ?MODULE, updated_list, 50}, + {remove_user, HostType, ?MODULE, remove_user, 50}, + {remove_domain, HostType, ?MODULE, remove_domain, 50}, + {anonymous_purge_hook, HostType, ?MODULE, remove_user, 50} + ]. + +%% ------------------------------------------------------------------ %% Handlers %% ------------------------------------------------------------------ +-spec disco_local_features(mongoose_disco:feature_acc()) -> mongoose_disco:feature_acc(). +disco_local_features(Acc = #{node := <<>>}) -> + mongoose_disco:add_features([?NS_PRIVACY], Acc); +disco_local_features(Acc) -> + Acc. + process_iq_get(Acc, _From = #jid{luser = LUser, lserver = LServer}, _To, #iq{xmlns = ?NS_PRIVACY, sub_el = #xmlel{children = Els}}, #userlist{name = Active}) -> + HostType = mongoose_acc:host_type(Acc), Res = case xml:remove_cdata(Els) of [] -> - process_lists_get(LUser, LServer, Active); + process_lists_get(HostType, LUser, LServer, Active); [#xmlel{name = Name, attrs = Attrs}] -> case Name of <<"list">> -> ListName = xml:get_attr(<<"name">>, Attrs), - process_list_get(LUser, LServer, ListName); + process_list_get(HostType, LUser, LServer, ListName); _ -> {error, mongoose_xmpp_errors:bad_request()} end; @@ -201,8 +237,8 @@ process_iq_get(Acc, process_iq_get(Val, _, _, _, _) -> Val. -process_lists_get(LUser, LServer, Active) -> - case mod_privacy_backend:get_list_names(LUser, LServer) of +process_lists_get(HostType, LUser, LServer, Active) -> + case mod_privacy_backend:get_list_names(HostType, LUser, LServer) of {ok, {Default, ListNames}} -> {result, [list_names_query(Active, Default, ListNames)]}; {error, not_found} -> @@ -211,8 +247,8 @@ process_lists_get(LUser, LServer, Active) -> {error, mongoose_xmpp_errors:internal_server_error()} end. -process_list_get(LUser, LServer, {value, Name}) -> - case mod_privacy_backend:get_privacy_list(LUser, LServer, Name) of +process_list_get(HostType, LUser, LServer, {value, Name}) -> + case mod_privacy_backend:get_privacy_list(HostType, LUser, LServer, Name) of {ok, List} -> LItems = lists:map(fun item_to_xml/1, List), {result, [list_query_result(Name, LItems)]}; @@ -221,22 +257,23 @@ process_list_get(LUser, LServer, {value, Name}) -> {error, _Reason} -> {error, mongoose_xmpp_errors:internal_server_error()} end; -process_list_get(_LUser, _LServer, false) -> +process_list_get(_HostType, _LUser, _LServer, false) -> {error, mongoose_xmpp_errors:bad_request()}. process_iq_set(Acc, From, _To, #iq{xmlns = ?NS_PRIVACY, sub_el = SubEl}) -> #xmlel{children = Els} = SubEl, + HostType = mongoose_acc:host_type(Acc), Res = case xml:remove_cdata(Els) of [#xmlel{name = Name, attrs = Attrs, children = SubEls}] -> ListName = xml:get_attr(<<"name">>, Attrs), case Name of <<"list">> -> - process_list_set(From, ListName, + process_list_set(HostType, From, ListName, xml:remove_cdata(SubEls)); <<"active">> -> - process_active_set(From, ListName); + process_active_set(HostType, From, ListName); <<"default">> -> - process_default_set(From, ListName); + process_default_set(HostType, From, ListName); _ -> {error, mongoose_xmpp_errors:bad_request()} end; @@ -247,8 +284,8 @@ process_iq_set(Acc, From, _To, #iq{xmlns = ?NS_PRIVACY, sub_el = SubEl}) -> process_iq_set(Val, _, _, _) -> Val. -process_default_set(#jid{luser = LUser, lserver = LServer}, {value, Name}) -> - case mod_privacy_backend:set_default_list(LUser, LServer, Name) of +process_default_set(HostType, #jid{luser = LUser, lserver = LServer}, {value, Name}) -> + case mod_privacy_backend:set_default_list(HostType, LUser, LServer, Name) of ok -> {result, []}; {error, not_found} -> @@ -256,16 +293,16 @@ process_default_set(#jid{luser = LUser, lserver = LServer}, {value, Name}) -> {error, _Reason} -> {error, mongoose_xmpp_errors:internal_server_error()} end; -process_default_set(#jid{luser = LUser, lserver = LServer}, false) -> - case mod_privacy_backend:forget_default_list(LUser, LServer) of +process_default_set(HostType, #jid{luser = LUser, lserver = LServer}, false) -> + case mod_privacy_backend:forget_default_list(HostType, LUser, LServer) of ok -> {result, []}; {error, _Reason} -> {error, mongoose_xmpp_errors:internal_server_error()} end. -process_active_set(#jid{luser = LUser, lserver = LServer}, {value, Name}) -> - case mod_privacy_backend:get_privacy_list(LUser, LServer, Name) of +process_active_set(HostType, #jid{luser = LUser, lserver = LServer}, {value, Name}) -> + case mod_privacy_backend:get_privacy_list(HostType, LUser, LServer, Name) of {ok, List} -> NeedDb = is_list_needdb(List), {result, [], #userlist{name = Name, list = List, needdb = NeedDb}}; @@ -274,23 +311,23 @@ process_active_set(#jid{luser = LUser, lserver = LServer}, {value, Name}) -> {error, _Reason} -> {error, mongoose_xmpp_errors:internal_server_error()} end; -process_active_set(_UserJID, false) -> +process_active_set(_HostType, _UserJID, false) -> {result, [], #userlist{}}. -process_list_set(UserJID, {value, Name}, Els) -> +process_list_set(HostType, UserJID, {value, Name}, Els) -> case parse_items(Els) of false -> {error, mongoose_xmpp_errors:bad_request()}; remove -> - remove_privacy_list(UserJID, Name); + remove_privacy_list(HostType, UserJID, Name); List -> - replace_privacy_list(UserJID, Name, List) + replace_privacy_list(HostType, UserJID, Name, List) end; -process_list_set(_UserJID, false, _Els) -> +process_list_set(_HostType, _UserJID, false, _Els) -> {error, mongoose_xmpp_errors:bad_request()}. -remove_privacy_list(#jid{luser = LUser, lserver = LServer} = UserJID, Name) -> - case mod_privacy_backend:remove_privacy_list(LUser, LServer, Name) of +remove_privacy_list(HostType, #jid{luser = LUser, lserver = LServer} = UserJID, Name) -> + case mod_privacy_backend:remove_privacy_list(HostType, LUser, LServer, Name) of ok -> UserList = #userlist{name = Name, list = []}, broadcast_privacy_list(UserJID, Name, UserList), @@ -302,8 +339,8 @@ remove_privacy_list(#jid{luser = LUser, lserver = LServer} = UserJID, Name) -> {error, mongoose_xmpp_errors:internal_server_error()} end. -replace_privacy_list(#jid{luser = LUser, lserver = LServer} = UserJID, Name, List) -> - case mod_privacy_backend:replace_privacy_list(LUser, LServer, Name, List) of +replace_privacy_list(HostType, #jid{luser = LUser, lserver = LServer} = UserJID, Name, List) -> + case mod_privacy_backend:replace_privacy_list(HostType, LUser, LServer, Name, List) of ok -> NeedDb = is_list_needdb(List), UserList = #userlist{name = Name, list = List, needdb = NeedDb}, @@ -320,8 +357,8 @@ is_item_needdb(#listitem{type = subscription}) -> true; is_item_needdb(#listitem{type = group}) -> true; is_item_needdb(_) -> false. -get_user_list(_, #jid{luser = LUser, lserver = LServer}) -> - case mod_privacy_backend:get_default_list(LUser, LServer) of +get_user_list(_, HostType, #jid{luser = LUser, lserver = LServer}) -> + case mod_privacy_backend:get_default_list(HostType, LUser, LServer) of {ok, {Default, List}} -> NeedDb = is_list_needdb(List), #userlist{name = Default, list = List, needdb = NeedDb}; @@ -334,9 +371,10 @@ get_user_list(_, #jid{luser = LUser, lserver = LServer}) -> %% If Dir = in, User@Server is the destination account (To). check_packet(Acc, _JID, #userlist{list = []}, _, _Dir) -> mongoose_acc:set(hook, result, allow, Acc); -check_packet(Acc, #jid{lserver = LServer} = JID, +check_packet(Acc, JID, #userlist{list = List, needdb = NeedDb}, {From, To, Name, Type}, Dir) -> + HostType = mongoose_acc:host_type(Acc), PType = packet_directed_type(Dir, packet_type(Name, Type)), LJID = case Dir of in -> jid:to_lower(From); @@ -345,7 +383,7 @@ check_packet(Acc, #jid{lserver = LServer} = JID, {Subscription, Groups} = case NeedDb of true -> - roster_get_jid_info(LServer, JID, LJID); + roster_get_jid_info(HostType, JID, LJID); false -> {[], []} end, @@ -429,18 +467,25 @@ is_type_match(group, Value, _JID, _Subscription, Groups) -> remove_user(Acc, User, Server) -> LUser = jid:nodeprep(User), LServer = jid:nameprep(Server), - R = mod_privacy_backend:remove_user(LUser, LServer), + HostType = mongoose_acc:host_type(Acc), + R = mod_privacy_backend:remove_user(HostType, LUser, LServer), mongoose_lib:log_if_backend_error(R, ?MODULE, ?LINE, {Acc, User, Server}), Acc. +-spec remove_domain(mongoose_hooks:simple_acc(), + mongooseim:host_type(), jid:lserver()) -> + mongoose_hooks:simple_acc(). +remove_domain(Acc, HostType, Domain) -> + mod_privacy_backend:remove_domain(HostType, Domain), + Acc. + updated_list(_, #userlist{name = SameName}, #userlist{name = SameName} = New) -> New; updated_list(_, Old, _) -> Old. - +%% ------------------------------------------------------------------ %% Deserialization %% ------------------------------------------------------------------ - packet_directed_type(out, message) -> message_out; packet_directed_type(in, message) -> message; packet_directed_type(in, iq) -> iq; @@ -562,7 +607,7 @@ add_item(false, Items) -> add_item(Item, Items) -> [Item | Items]. - +%% ------------------------------------------------------------------ %% Serialization %% ------------------------------------------------------------------ @@ -676,7 +721,7 @@ binary_to_order_s(Order) -> false end. - +%% ------------------------------------------------------------------ %% Ejabberd %% ------------------------------------------------------------------ @@ -688,9 +733,9 @@ broadcast_privacy_list(UserJID, Name, UserList) -> broadcast_privacy_list_packet(Name, UserList) -> {broadcast, {privacy_list, UserList, Name}}. -roster_get_jid_info(Host, ToJID, LJID) -> - mongoose_hooks:roster_get_jid_info(Host, ToJID, LJID). +roster_get_jid_info(HostType, ToJID, LJID) -> + mongoose_hooks:roster_get_jid_info(HostType, ToJID, LJID). -config_metrics(Host) -> +config_metrics(HostType) -> OptsToReport = [{backend, mnesia}], %list of tuples {option, defualt_value} - mongoose_module_metrics:opts_for_module(Host, ?MODULE, OptsToReport). + mongoose_module_metrics:opts_for_module(HostType, ?MODULE, OptsToReport). diff --git a/src/mod_privacy_mnesia.erl b/src/privacy/mod_privacy_mnesia.erl similarity index 83% rename from src/mod_privacy_mnesia.erl rename to src/privacy/mod_privacy_mnesia.erl index a140ea2821..71a59c2524 100644 --- a/src/mod_privacy_mnesia.erl +++ b/src/privacy/mod_privacy_mnesia.erl @@ -31,26 +31,27 @@ -behaviour(mod_privacy). -export([init/2, - get_default_list/2, - get_list_names/2, - get_privacy_list/3, - forget_default_list/2, - set_default_list/3, - remove_privacy_list/3, - replace_privacy_list/4, - remove_user/2]). + get_default_list/3, + get_list_names/3, + get_privacy_list/4, + set_default_list/4, + forget_default_list/3, + remove_privacy_list/4, + replace_privacy_list/5, + remove_user/3, + remove_domain/2]). -include("mongoose.hrl"). -include("jlib.hrl"). -include("mod_privacy.hrl"). -init(_Host, _Opts) -> +init(_HostType, _Opts) -> mnesia:create_table(privacy, [{disc_copies, [node()]}, - {attributes, record_info(fields, privacy)}]), + {attributes, record_info(fields, privacy)}]), mnesia:add_table_copy(privacy, node(), disc_copies), ok. -get_default_list(LUser, LServer) -> +get_default_list(_HostType, LUser, LServer) -> case catch mnesia:dirty_read(privacy, {LUser, LServer}) of [] -> {error, not_found}; @@ -65,7 +66,7 @@ get_default_list(LUser, LServer) -> {error, Reason} end. -get_list_names(LUser, LServer) -> +get_list_names(_HostType, LUser, LServer) -> case catch mnesia:dirty_read(privacy, {LUser, LServer}) of {'EXIT', Reason} -> {error, Reason}; @@ -76,7 +77,7 @@ get_list_names(LUser, LServer) -> {ok, {Default, Names}} end. -get_privacy_list(LUser, LServer, Name) -> +get_privacy_list(_HostType, LUser, LServer, Name) -> case catch mnesia:dirty_read(privacy, {LUser, LServer}) of {'EXIT', Reason} -> {error, Reason}; @@ -92,7 +93,7 @@ get_privacy_list(LUser, LServer, Name) -> end. %% @doc Set no default list for user. -forget_default_list(LUser, LServer) -> +forget_default_list(_HostType, LUser, LServer) -> F = fun() -> case mnesia:read({privacy, {LUser, LServer}}) of [] -> @@ -109,7 +110,7 @@ forget_default_list(LUser, LServer) -> {error, {aborted, Reason}} end. -set_default_list(LUser, LServer, Name) -> +set_default_list(_HostType, LUser, LServer, Name) -> case mnesia:transaction(fun() -> set_default_list_t(LUser, LServer, Name) end) of {atomic, ok} -> ok; @@ -135,7 +136,7 @@ set_default_list_t(LUser, LServer, Name) -> end end. -remove_privacy_list(LUser, LServer, Name) -> +remove_privacy_list(_HostType, LUser, LServer, Name) -> F = fun() -> case mnesia:read({privacy, {LUser, LServer}}) of [] -> @@ -157,7 +158,7 @@ remove_privacy_list(LUser, LServer, Name) -> {error, {aborted, Reason}} end. -replace_privacy_list(LUser, LServer, Name, List) -> +replace_privacy_list(_HostType, LUser, LServer, Name, List) -> US = {LUser, LServer}, F = fun() -> case mnesia:wread({privacy, US}) of @@ -179,7 +180,15 @@ replace_privacy_list(LUser, LServer, Name, List) -> {error, {aborted, Reason}} end. -remove_user(LUser, LServer) -> +remove_user(_HostType, LUser, LServer) -> F = fun() -> mnesia:delete({privacy, {LUser, LServer}}) end, mnesia:transaction(F). +remove_domain(_HostType, LServer) -> + F = fun(#privacy{us = {_, LS}} = Rec, _) when LS =:= LServer -> + mnesia:delete_object(Rec), + ok; + (_, _) -> + ok + end, + mnesia:transaction(fun mnesia:foldl/3, [F, ok, privacy]). diff --git a/src/mod_privacy_rdbms.erl b/src/privacy/mod_privacy_rdbms.erl similarity index 66% rename from src/mod_privacy_rdbms.erl rename to src/privacy/mod_privacy_rdbms.erl index b4399319be..0db45e6215 100644 --- a/src/mod_privacy_rdbms.erl +++ b/src/privacy/mod_privacy_rdbms.erl @@ -31,41 +31,42 @@ -behaviour(mod_privacy). -export([init/2, - get_default_list/2, - get_list_names/2, - get_privacy_list/3, - forget_default_list/2, - set_default_list/3, - remove_privacy_list/3, - replace_privacy_list/4, - remove_user/2]). + get_default_list/3, + get_list_names/3, + get_privacy_list/4, + set_default_list/4, + forget_default_list/3, + remove_privacy_list/4, + replace_privacy_list/5, + remove_user/3, + remove_domain/2]). -include("mongoose.hrl"). -include("jlib.hrl"). -include("mod_privacy.hrl"). -init(Host, _Opts) -> - prepare_queries(Host), +init(HostType, _Opts) -> + prepare_queries(HostType), ok. -prepare_queries(Host) -> +prepare_queries(HostType) -> %% Queries to privacy_list table - mongoose_rdbms:prepare(privacy_list_get_id, privacy_list, [username, name], - <<"SELECT id FROM privacy_list where username=? AND name=?">>), - mongoose_rdbms:prepare(privacy_list_get_names, privacy_list, [username], - <<"SELECT name FROM privacy_list WHERE username=?">>), - mongoose_rdbms:prepare(privacy_list_delete_by_name, privacy_list, [username, name], - <<"DELETE FROM privacy_list WHERE username=? AND name=?">>), - mongoose_rdbms:prepare(privacy_list_delete_multiple, privacy_list, [username], - <<"DELETE FROM privacy_list WHERE username=?">>), - mongoose_rdbms:prepare(privacy_list_insert, privacy_list, [username, name], - <<"INSERT INTO privacy_list(username, name) VALUES (?, ?)">>), + mongoose_rdbms:prepare(privacy_list_get_id, privacy_list, [server, username, name], + <<"SELECT id FROM privacy_list WHERE server=? AND username=? AND name=?">>), + mongoose_rdbms:prepare(privacy_list_get_names, privacy_list, [server, username], + <<"SELECT name FROM privacy_list WHERE server=? AND username=?">>), + mongoose_rdbms:prepare(privacy_list_delete_by_name, privacy_list, [server, username, name], + <<"DELETE FROM privacy_list WHERE server=? AND username=? AND name=?">>), + mongoose_rdbms:prepare(privacy_list_delete_multiple, privacy_list, [server, username], + <<"DELETE FROM privacy_list WHERE server=? AND username=?">>), + mongoose_rdbms:prepare(privacy_list_insert, privacy_list, [server, username, name], + <<"INSERT INTO privacy_list(server, username, name) VALUES (?, ?, ?)">>), %% Queries to privacy_default_list table - mongoose_rdbms:prepare(privacy_default_get_name, privacy_default_list, [username], - <<"SELECT name FROM privacy_default_list WHERE username=?">>), - mongoose_rdbms:prepare(privacy_default_delete, privacy_default_list, [username], - <<"DELETE from privacy_default_list WHERE username=?">>), - prepare_default_list_upsert(Host), + mongoose_rdbms:prepare(privacy_default_get_name, privacy_default_list, [server, username], + <<"SELECT name FROM privacy_default_list WHERE server=? AND username=?">>), + mongoose_rdbms:prepare(privacy_default_delete, privacy_default_list, [server, username], + <<"DELETE from privacy_default_list WHERE server=? AND username=?">>), + prepare_default_list_upsert(HostType), %% Queries to privacy_list_data table mongoose_rdbms:prepare(privacy_data_get_by_id, privacy_list_data, [id], <<"SELECT ord, t, value, action, match_all, match_iq, " @@ -91,30 +92,38 @@ prepare_queries(Host) -> "match_message, match_presence_in, match_presence_out) " "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)">>), %% This query uses multiple tables - mongoose_rdbms:prepare(privacy_data_delete_user, privacy_list, [username], + mongoose_rdbms:prepare(privacy_data_delete_user, privacy_list, [server, username], <<"DELETE FROM privacy_list_data WHERE id IN " - "(SELECT id FROM privacy_list WHERE username=?)">>), + "(SELECT id FROM privacy_list WHERE server=? AND username=?)">>), + %% delete domain queries + mongoose_rdbms:prepare(privacy_default_delete_domain, privacy_default_list, [server], + <<"DELETE from privacy_default_list WHERE server=?">>), + mongoose_rdbms:prepare(privacy_list_delete_domain, privacy_list, [server], + <<"DELETE FROM privacy_list WHERE server=?">>), + mongoose_rdbms:prepare(privacy_data_delete_domain, privacy_list, [server], + <<"DELETE FROM privacy_list_data WHERE id IN " + "(SELECT id FROM privacy_list WHERE server=?)">>), ok. -prepare_default_list_upsert(Host) -> +prepare_default_list_upsert(HostType) -> Fields = [<<"name">>], - Filter = [<<"username">>], - rdbms_queries:prepare_upsert(Host, privacy_default_upsert, privacy_default_list, + Filter = [<<"server">>, <<"username">>], + rdbms_queries:prepare_upsert(HostType, privacy_default_upsert, privacy_default_list, Filter ++ Fields, Fields, Filter). -default_list_upsert(Host, LUser, Name) -> - InsertParams = [LUser, Name], +default_list_upsert(HostType, LServer, LUser, Name) -> + InsertParams = [LServer, LUser, Name], UpdateParams = [Name], - UniqueKeyValues = [LUser], - rdbms_queries:execute_upsert(Host, privacy_default_upsert, + UniqueKeyValues = [LServer, LUser], + rdbms_queries:execute_upsert(HostType, privacy_default_upsert, InsertParams, UpdateParams, UniqueKeyValues). -get_default_list(LUser, LServer) -> - case get_default_list_name(LUser, LServer) of +get_default_list(HostType, LUser, LServer) -> + case get_default_list_name(HostType, LUser, LServer) of none -> {error, not_found}; Default -> - case get_privacy_list(LUser, LServer, Default) of + case get_privacy_list(HostType, LUser, LServer, Default) of {ok, List} -> {ok, {Default, List}}; {error, Reason} -> @@ -122,13 +131,13 @@ get_default_list(LUser, LServer) -> end end. -get_list_names(LUser, LServer) -> - Default = get_default_list_name(LUser, LServer), - Names = get_list_names_only(LUser, LServer), +get_list_names(HostType, LUser, LServer) -> + Default = get_default_list_name(HostType, LUser, LServer), + Names = get_list_names_only(HostType, LUser, LServer), {ok, {Default, Names}}. -get_default_list_name(LUser, LServer) -> - try execute_privacy_default_get_name(LServer, LUser) of +get_default_list_name(HostType, LUser, LServer) -> + try execute_privacy_default_get_name(HostType, LServer, LUser) of {selected, []} -> none; {selected, [{DefName}]} -> @@ -145,8 +154,8 @@ get_default_list_name(LUser, LServer) -> none end. -get_list_names_only(LUser, LServer) -> - try execute_privacy_list_get_names(LServer, LUser) of +get_list_names_only(HostType, LUser, LServer) -> + try execute_privacy_list_get_names(HostType, LServer, LUser) of {selected, Names} -> [Name || {Name} <- Names]; Other -> @@ -161,13 +170,13 @@ get_list_names_only(LUser, LServer) -> [] end. -get_privacy_list(LUser, LServer, Name) -> - try execute_privacy_list_get_id(LServer, LUser, Name) of +get_privacy_list(HostType, LUser, LServer, Name) -> + try execute_privacy_list_get_id(HostType, LServer, LUser, Name) of {selected, []} -> {error, not_found}; {selected, [{ID}]} -> IntID = mongoose_rdbms:result_to_integer(ID), - get_privacy_list_by_id(LUser, LServer, Name, IntID, LServer); + get_privacy_list_by_id(HostType, LUser, LServer, Name, IntID, LServer); Other -> ?LOG_ERROR(#{what => privacy_get_privacy_list_failed, user => LUser, server => LServer, list_name => Name, @@ -181,8 +190,8 @@ get_privacy_list(LUser, LServer, Name) -> {error, Reason} end. -get_privacy_list_by_id(LUser, LServer, Name, ID, LServer) when is_integer(ID) -> - try execute_privacy_data_get_by_id(LServer, ID) of +get_privacy_list_by_id(HostType, LUser, LServer, Name, ID, LServer) when is_integer(ID) -> + try execute_privacy_data_get_by_id(HostType, ID) of {selected, Rows} -> {ok, raw_to_items(Rows)}; Other -> @@ -199,8 +208,8 @@ get_privacy_list_by_id(LUser, LServer, Name, ID, LServer) when is_integer(ID) -> end. %% @doc Set no default list for user. -forget_default_list(LUser, LServer) -> - try execute_privacy_default_delete(LServer, LUser) of +forget_default_list(HostType, LUser, LServer) -> + try execute_privacy_default_delete(HostType, LServer, LUser) of {updated, _} -> ok; Other -> @@ -215,9 +224,9 @@ forget_default_list(LUser, LServer) -> {error, Reason} end. -set_default_list(LUser, LServer, Name) -> - F = fun() -> set_default_list_t(LServer, LUser, Name) end, - case rdbms_queries:sql_transaction(LServer, F) of +set_default_list(HostType, LUser, LServer, Name) -> + F = fun() -> set_default_list_t(HostType, LServer, LUser, Name) end, + case rdbms_queries:sql_transaction(HostType, F) of {atomic, ok} -> ok; {atomic, {error, Reason}} -> @@ -228,31 +237,31 @@ set_default_list(LUser, LServer, Name) -> {error, Reason} end. -set_default_list_t(LServer, LUser, Name) -> - case execute_privacy_list_get_names(LServer, LUser) of +set_default_list_t(HostType, LServer, LUser, Name) -> + case execute_privacy_list_get_names(HostType, LServer, LUser) of {selected, []} -> {error, not_found}; {selected, Names} -> case lists:member({Name}, Names) of true -> - default_list_upsert(LServer, LUser, Name), + default_list_upsert(HostType, LServer, LUser, Name), ok; false -> {error, not_found} end end. -remove_privacy_list(LUser, LServer, Name) -> +remove_privacy_list(HostType, LUser, LServer, Name) -> F = fun() -> - case execute_privacy_default_get_name(LServer, LUser) of + case execute_privacy_default_get_name(HostType, LServer, LUser) of {selected, [{Name}]} -> %% Matches Name variable {error, conflict}; {selected, _} -> - execute_privacy_list_delete_by_name(LServer, LUser, Name), + execute_privacy_list_delete_by_name(HostType, LServer, LUser, Name), ok end end, - case rdbms_queries:sql_transaction(LServer, F) of + case rdbms_queries:sql_transaction(HostType, F) of {atomic, {error, _} = Error} -> Error; {atomic, ok} -> @@ -263,66 +272,75 @@ remove_privacy_list(LUser, LServer, Name) -> {error, Reason} end. -replace_privacy_list(LUser, LServer, Name, List) -> +replace_privacy_list(HostType, LUser, LServer, Name, List) -> Rows = lists:map(fun item_to_raw/1, List), F = fun() -> - ResultID = case execute_privacy_list_get_id(LServer, LUser, Name) of + ResultID = case execute_privacy_list_get_id(HostType, LServer, LUser, Name) of {selected, []} -> - execute_privacy_list_insert(LServer, LUser, Name), - {selected, [{I}]} = execute_privacy_list_get_id(LServer, LUser, Name), + execute_privacy_list_insert(HostType, LServer, LUser, Name), + {selected, [{I}]} = execute_privacy_list_get_id(HostType, LServer, LUser, Name), I; {selected, [{I}]} -> I end, ID = mongoose_rdbms:result_to_integer(ResultID), - replace_data_rows(LServer, ID, Rows), + replace_data_rows(HostType, ID, Rows), ok end, - {atomic, ok} = mongoose_rdbms:transaction_with_delayed_retry(LServer, F, #{retries => 5, delay => 100}), + {atomic, ok} = mongoose_rdbms:transaction_with_delayed_retry(HostType, F, #{retries => 5, delay => 100}), ok. -remove_user(LUser, LServer) -> - F = fun() -> remove_user_t(LUser, LServer) end, - rdbms_queries:sql_transaction(LServer, F). +remove_domain(HostType, LServer) -> + F = fun() -> remove_domain_t(HostType, LServer) end, + rdbms_queries:sql_transaction(HostType, F). + +remove_domain_t(HostType, LServer) -> + mongoose_rdbms:execute_successfully(HostType, privacy_data_delete_domain, [LServer]), + mongoose_rdbms:execute_successfully(HostType, privacy_list_delete_domain, [LServer]), + mongoose_rdbms:execute_successfully(HostType, privacy_default_delete_domain, [LServer]). + +remove_user(HostType, LUser, LServer) -> + F = fun() -> remove_user_t(HostType, LUser, LServer) end, + rdbms_queries:sql_transaction(HostType, F). -remove_user_t(LUser, LServer) -> - mongoose_rdbms:execute_successfully(LServer, privacy_data_delete_user, [LUser]), - mongoose_rdbms:execute_successfully(LServer, privacy_list_delete_multiple, [LUser]), - mongoose_rdbms:execute_successfully(LServer, privacy_default_delete, [LUser]). +remove_user_t(HostType, LUser, LServer) -> + mongoose_rdbms:execute_successfully(HostType, privacy_data_delete_user, [LServer, LUser]), + mongoose_rdbms:execute_successfully(HostType, privacy_list_delete_multiple, [LServer, LUser]), + execute_privacy_default_delete(HostType, LServer, LUser). -execute_privacy_list_get_id(LServer, LUser, Name) -> - mongoose_rdbms:execute_successfully(LServer, privacy_list_get_id, [LUser, Name]). +execute_privacy_list_get_id(HostType, LServer, LUser, Name) -> + mongoose_rdbms:execute_successfully(HostType, privacy_list_get_id, [LServer, LUser, Name]). -execute_privacy_default_get_name(LServer, LUser) -> - mongoose_rdbms:execute_successfully(LServer, privacy_default_get_name, [LUser]). +execute_privacy_default_get_name(HostType, LServer, LUser) -> + mongoose_rdbms:execute_successfully(HostType, privacy_default_get_name, [LServer, LUser]). -execute_privacy_list_get_names(LServer, LUser) -> - mongoose_rdbms:execute_successfully(LServer, privacy_list_get_names, [LUser]). +execute_privacy_list_get_names(HostType, LServer, LUser) -> + mongoose_rdbms:execute_successfully(HostType, privacy_list_get_names, [LServer, LUser]). -execute_privacy_data_get_by_id(LServer, ID) -> - mongoose_rdbms:execute_successfully(LServer, privacy_data_get_by_id, [ID]). +execute_privacy_data_get_by_id(HostType, ID) -> + mongoose_rdbms:execute_successfully(HostType, privacy_data_get_by_id, [ID]). -execute_privacy_default_delete(LServer, LUser) -> - mongoose_rdbms:execute_successfully(LServer, privacy_default_delete, [LUser]). +execute_privacy_default_delete(HostType, LServer, LUser) -> + mongoose_rdbms:execute_successfully(HostType, privacy_default_delete, [LServer, LUser]). -execute_privacy_list_delete_by_name(LServer, LUser, Name) -> - mongoose_rdbms:execute_successfully(LServer, privacy_list_delete_by_name, [LUser, Name]). +execute_privacy_list_delete_by_name(HostType, LServer, LUser, Name) -> + mongoose_rdbms:execute_successfully(HostType, privacy_list_delete_by_name, [LServer, LUser, Name]). -execute_privacy_list_insert(LServer, LUser, Name) -> - mongoose_rdbms:execute_successfully(LServer, privacy_list_insert, [LUser, Name]). +execute_privacy_list_insert(HostType, LServer, LUser, Name) -> + mongoose_rdbms:execute_successfully(HostType, privacy_list_insert, [LServer, LUser, Name]). -execute_delete_data_by_id(LServer, ID) -> - mongoose_rdbms:execute_successfully(LServer, delete_data_by_id, [ID]). +execute_delete_data_by_id(HostType, ID) -> + mongoose_rdbms:execute_successfully(HostType, delete_data_by_id, [ID]). -replace_data_rows(LServer, ID, []) when is_integer(ID) -> +replace_data_rows(HostType, ID, []) when is_integer(ID) -> %% Just remove the data, nothing should be inserted - execute_delete_data_by_id(LServer, ID); -replace_data_rows(LServer, ID, Rows) when is_integer(ID) -> - {selected, OldRows} = execute_privacy_data_get_by_id(LServer, ID), + execute_delete_data_by_id(HostType, ID); +replace_data_rows(HostType, ID, Rows) when is_integer(ID) -> + {selected, OldRows} = execute_privacy_data_get_by_id(HostType, ID), New = lists:sort(Rows), Old = lists:sort([tuple_to_list(Row) || Row <- OldRows]), Diff = diff_rows(ID, New, Old, []), - F = fun({Q, Args}) -> mongoose_rdbms:execute_successfully(LServer, Q, Args) end, + F = fun({Q, Args}) -> mongoose_rdbms:execute_successfully(HostType, Q, Args) end, lists:foreach(F, Diff), ok. diff --git a/src/privacy/mod_privacy_riak.erl b/src/privacy/mod_privacy_riak.erl new file mode 100644 index 0000000000..6c0d754391 --- /dev/null +++ b/src/privacy/mod_privacy_riak.erl @@ -0,0 +1,173 @@ +%%============================================================================== +%% Copyright 2015 Guillaume Bour. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%%============================================================================== + +% +% NOTES: +% user default privacy is stored in +% bucket {<<"privacy_defaults">>, LServer}, key LUser@LServer +% user privacy lists names are stored in +% set {<<"privacy_lists_names">>, <<"LServer">>}, key LUser@LServer +% user privacy lists content are stored in +% bucket {<<"privacy_lists">>, LServer}, key LUser@LServer/ListName + +-module(mod_privacy_riak). +-author('guillaume@bour.cc'). +-behaviour(mod_privacy). + +-export([init/2, + get_default_list/3, + get_list_names/3, + get_privacy_list/4, + set_default_list/4, + forget_default_list/3, + remove_privacy_list/4, + replace_privacy_list/5, + remove_user/3, + remove_domain/2]). + +-include("mongoose.hrl"). +-include("jlib.hrl"). +-include("mod_privacy.hrl"). + +-include_lib("riakc/include/riakc.hrl"). + +get_bucket_name(HostType, Opt, Default) -> + {gen_mod:get_module_opt(HostType, mod_privacy, Opt, Default), HostType}. + +-define(BKT_DEFAULT_LIST(HostType), + get_bucket_name(HostType, defaults_bucket_type, <<"privacy_defaults">>)). +-define(BKT_LISTS_NAMES(HostType), + get_bucket_name(HostType, names_bucket_type, <<"privacy_lists_names">>)). +-define(BKT_LISTS(HostType), + get_bucket_name(HostType, bucket_type, <<"privacy_lists">>)). + +init(_HostType, _Opts) -> + ok. + +get_default_list(HostType, LUser, LServer) -> + case get_default_list_name(HostType, LUser, LServer) of + none -> + {error, not_found}; + Default -> + case get_privacy_list(HostType, LUser, LServer, Default) of + {ok, List} -> + {ok, {Default, List}}; + {error, Reason} -> + {error, Reason} + end + end. + +get_list_names(HostType, LUser, LServer) -> + Default = get_default_list_name(HostType, LUser, LServer), + Names = get_list_names_only(HostType, LUser, LServer), + {ok, {Default, Names}}. + +-spec get_default_list_name(mongooseim:host_type(), jid:luser(), jid:lserver()) -> binary() | none. +get_default_list_name(HostType, LUser, LServer) -> + case mongoose_riak:get(?BKT_DEFAULT_LIST(HostType), key(LUser, LServer)) of + {ok, Obj} -> + riakc_obj:get_value(Obj); + _ -> none + end. + +-spec get_list_names_only(mongooseim:host_type(), jid:luser(), jid:lserver()) -> list(binary()). +get_list_names_only(HostType, LUser, LServer) -> + case mongoose_riak:fetch_type(?BKT_LISTS_NAMES(HostType), key(LUser, LServer)) of + {ok, Set} -> + riakc_set:value(Set); + {error, {notfound, set}} -> + []; + Err -> + ?LOG_ERROR(#{what => privacy_get_list_names_only_failed, + user => LUser, server => LServer, reason => Err}), + [] + end. + +get_privacy_list(HostType, LUser, LServer, Name) -> + case mongoose_riak:get(?BKT_LISTS(HostType), key(LUser, LServer, Name)) of + {ok, Obj} -> + Val = binary_to_term(riakc_obj:get_value(Obj)), + {ok, Val}; + {error, notfound} -> + {error, not_found}; + Err -> + ?LOG_ERROR(#{what => privacy_get_list_names_only_failed, + user => LUser, server => LServer, list_name => Name, + reason => Err}), + Err + end. + +forget_default_list(HostType, LUser, LServer) -> + mongoose_riak:delete(?BKT_DEFAULT_LIST(HostType), key(LUser, LServer)). + +set_default_list(HostType, LUser, LServer, Name) -> + case mongoose_riak:get(?BKT_LISTS(HostType), key(LUser, LServer, Name)) of + {ok, _} -> + % create or update + Obj = riakc_obj:new(?BKT_DEFAULT_LIST(HostType), key(LUser, LServer), Name), + mongoose_riak:put(Obj); + % in case list name is not found + {error, notfound} -> + {error, not_found}; + Err -> Err + end. + +remove_privacy_list(HostType, LUser, LServer, Name) -> + mongoose_riak:delete(?BKT_LISTS(HostType), key(LUser, LServer, Name)), + + case mongoose_riak:fetch_type(?BKT_LISTS_NAMES(HostType), key(LUser, LServer)) of + {ok, S1} -> + S2 = riakc_set:del_element(Name, S1), + mongoose_riak:update_type(?BKT_LISTS_NAMES(HostType), key(LUser, LServer), riakc_set:to_op(S2)); + Err -> + ?LOG_ERROR(#{what => privacy_remove_privacy_list_failed, + user => LUser, server => LServer, list_name => Name, + reason => Err}), + Err + end. + +replace_privacy_list(HostType, LUser, LServer, Name, List) -> + % store privacy-list content + BinaryList = term_to_binary(List), + Obj = riakc_obj:new(?BKT_LISTS(HostType), key(LUser, LServer, Name), BinaryList), + mongoose_riak:put(Obj), + + % add new list name to user privacy-lists set + S = riakc_set:add_element(Name, riakc_set:new()), + mongoose_riak:update_type(?BKT_LISTS_NAMES(HostType), key(LUser, LServer), riakc_set:to_op(S)). + +remove_user(HostType, LUser, LServer) -> + forget_default_list(HostType, LUser, LServer), + lists:foreach( + fun(Name) -> remove_privacy_list(HostType, LUser, LServer, Name) end, + get_list_names_only(HostType, LUser, LServer) + ), + mongoose_riak:delete(?BKT_LISTS_NAMES(HostType), key(LUser, LServer)). + +%% TODO +remove_domain(_HostType, _LServer) -> + ok. + % forget_default_list(HostType, <<>>, LServer), + % lists:foreach( + % fun(Name) -> remove_privacy_list(HostType, <<>>, LServer, Name) end, + % get_list_names_only(HostType, <<>>, LServer) + % ), + % mongoose_riak:delete(?BKT_LISTS_NAMES(HostType), key(<<>>, LServer)). + +key(LUser, LServer) -> + jid:to_binary({LUser, LServer}). +key(LUser, LServer, Name) -> + jid:to_binary({LUser, LServer, Name}).