Skip to content

Commit

Permalink
Add support for vcard removal
Browse files Browse the repository at this point in the history
  • Loading branch information
ludwikbukowski authored and fenek committed May 14, 2019
1 parent 46149d7 commit 1e23530
Show file tree
Hide file tree
Showing 3 changed files with 82 additions and 12 deletions.
38 changes: 36 additions & 2 deletions big_tests/tests/gdpr_SUITE.erl
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
-export([init_per_testcase/2, end_per_testcase/2]).
-export([
retrieve_vcard/1,
remove_vcard/1,
try_remove_data_of_non_existing_user/1,
retrieve_roster/1,
retrieve_mam/1,
retrieve_offline/1,
Expand Down Expand Up @@ -48,7 +50,8 @@ all() ->
[
{group, retrieve_personal_data},
{group, retrieve_personal_data_with_mods_disabled},
{group, retrieve_negative}
{group, retrieve_negative},
{group, remove_personal_data}
].

groups() ->
Expand Down Expand Up @@ -89,7 +92,12 @@ groups() ->
]},
{retrieve_negative, [], [
data_is_not_retrieved_for_missing_user
]}
]},
{remove_personal_data, [parallel], [
% per type
try_remove_data_of_non_existing_user,
remove_vcard
]}
].

init_per_suite(Config) ->
Expand Down Expand Up @@ -125,6 +133,13 @@ init_per_testcase(retrieve_vcard = CN, Config) ->
_ ->
escalus:init_per_testcase(CN, Config)
end;
init_per_testcase(remove_vcard = CN, Config) ->
case vcard_update:is_vcard_ldap() of
true ->
{skip, skipped_for_simplicity_for_now}; % TODO: Fix the case for LDAP as well
_ ->
escalus:init_per_testcase(CN, Config)
end;
init_per_testcase(retrieve_mam = CN, Config) ->
case pick_backend_for_mam() of
skip ->
Expand Down Expand Up @@ -189,6 +204,8 @@ pubsub_required_modules() ->
%% -------------------------------------------------------------

%% ------------------------- Data retrieval - per type verification -------------------------
try_remove_data_of_non_existing_user(Config) ->
{"Error: \"User does not exist\"\n", 1} = ejabberdctl("remove_personal_data", [<<"user_not_exist">>, <<"localhost">>], Config).

retrieve_vcard(Config) ->
escalus:fresh_story(Config, [{alice, 1}], fun(Alice) ->
Expand All @@ -210,6 +227,17 @@ retrieve_vcard(Config) ->
Alice, Config, "vcard", ExpectedHeader, ExpectedItems)
end).

remove_vcard(Config) ->
escalus:fresh_story(Config, [{alice, 1}], fun(Alice) ->
AliceFields = [{<<"FN">>, <<"Alice">>}, {<<"LN">>, <<"Ecila">>}],
AliceSetResultStanza
= escalus:send_and_wait(Alice, escalus_stanza:vcard_update(AliceFields)),
escalus:assert(is_iq_result, AliceSetResultStanza),

{0, _} = remove_personal_data(Alice, Config),
retrieve_and_validate_personal_data(Alice, Config, "vcard", ["jid", "vcard"], [])
end).

retrieve_roster(Config) ->
escalus:fresh_story(Config, [{alice, 1}, {bob, 1}], fun(Alice, Bob) ->
escalus_story:make_all_clients_friends([Alice, Bob]),
Expand Down Expand Up @@ -615,6 +643,12 @@ retrieve_personal_data(User, Domain, Config) ->
{CommandOutput, Code} = ejabberdctl("retrieve_personal_data", [User, Domain, Filename], Config),
{Filename, Code, CommandOutput}.

remove_personal_data(Client, Config) ->
User = escalus_client:username(Client),
Domain = escalus_client:server(Client),
{CommandOutput, Code} = ejabberdctl("remove_personal_data", [User, Domain], Config),
{Code, CommandOutput}.

random_filename(Config) ->
TCName = atom_to_list(?config(tc_name, Config)),
TCName ++ "." ++ integer_to_list(erlang:system_time()) ++ ".zip".
Expand Down
38 changes: 29 additions & 9 deletions src/admin_extra/service_admin_extra_gdpr.erl
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,7 @@
-include("ejabberd_commands.hrl").
-include("mongoose_logger.hrl").

-export([commands/0,
retrieve_all/3]).
-export([commands/0, retrieve_all/3, remove_all/2]).

% Exported for RPC call
-export([retrieve_logs/2]).
Expand All @@ -16,16 +15,22 @@ commands() -> [
#ejabberd_commands{name = retrieve_personal_data, tags = [gdpr],
desc = "Retrieve user's presonal data.",
longdesc = "Retrieves all personal data from MongooseIM for a given user. Example:\n"
" mongooseimctl alice localhost /home/mim/alice.smith.zip ",
" mongooseimctl retrieve_personal_data alice localhost /home/mim/alice.smith.zip ",
module = ?MODULE,
function = retrieve_all,
args = [{username, binary}, {domain, binary}, {path, binary}],
result = {res, rescode}},
#ejabberd_commands{name = remove_personal_data, tags = [gdpr],
desc = "Remove all user's presonal data.",
longdesc = "Removes all personal data from MongooseIM for a given user. Example:\n"
" mongooseimctl remove_personal_data alice localhost ",
module = ?MODULE,
function = remove_all,
args = [{username, binary}, {domain, binary}],
result = {res, rescode}}

].

-spec retrieve_all(jid:user(), jid:server(), Path :: binary()) ->
RetrievedFilesInZipName :: binary() | {error, Reason :: any()}.
-spec retrieve_all(jid:user(), jid:server(), Path :: binary()) -> ok | {error, Reason :: any()}.
retrieve_all(Username, Domain, ResultFilePath) ->
case user_exists(Username, Domain) of
true ->
Expand All @@ -51,6 +56,15 @@ retrieve_all(Username, Domain, ResultFilePath) ->
{error, "User does not exist"}
end.

-spec remove_all(jid:user(), jid:server()) -> ok | {error, Reason :: any()}.
remove_all(Username, Domain) ->
case user_exists(Username, Domain) of
true ->
remove_data_from_modules(Username, Domain);
false ->
{error, "User does not exist"}
end.

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%% Private funs
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
Expand All @@ -66,9 +80,8 @@ get_data_from_modules(Username, Domain) ->
lists:flatmap(fun(M) -> try_get_data_from_module(M, Username, Domain) end, Modules).

try_get_data_from_module(Module, Username, Domain) ->
try Module:get_personal_data(Username, Domain) of
[{_, _, []}] -> [];
Val -> Val
try
Module:get_personal_data(Username, Domain)
catch
C:R ->
?WARNING_MSG("event=cannot_retrieve_personal_data,"
Expand All @@ -77,6 +90,13 @@ try_get_data_from_module(Module, Username, Domain) ->
[]
end.

-spec remove_data_from_modules(jid:user(), jid:server()) -> ok.
remove_data_from_modules(Username, Domain) ->
%%TODO: when all modules ready use the one from below
%% Modules = modules_with_personal_data(),
Modules = [mod_vcard],
lists:foreach(fun(M) -> M:remove_personal_data(Username, Domain) end, Modules).

-spec to_csv_file(CsvFilename :: binary(), gdpr:schema(), gdpr:entities(), file:name()) -> ok.
to_csv_file(Filename, DataSchema, DataRows, TmpDir) ->
FilePath = <<(list_to_binary(TmpDir))/binary, "/", Filename/binary>>,
Expand Down
18 changes: 17 additions & 1 deletion src/mod_vcard.erl
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,8 @@

-export([config_change/4]).

-export([get_personal_data/2]).
%% GDPR related
-export([get_personal_data/2, remove_personal_data/2]).

-define(PROCNAME, ejabberd_mod_vcard).

Expand Down Expand Up @@ -147,6 +148,21 @@ get_personal_data(Username, Server) ->
end, mongoose_lib:find_behaviour_implementations(mod_vcard)),
[{vcard, Schema, Entries}].

-spec remove_personal_data(jid:user(), jid:server()) -> ok.
remove_personal_data(Username, Server) ->
LUser = jid:nodeprep(Username),
LServer = jid:nameprep(Server),
lists:foreach(fun(B) -> try_remove_personal_data(LUser, LServer, B) end, mongoose_lib:find_behaviour_implementations(mod_vcard)).

try_remove_personal_data(LUser, LServer, Module) ->
try
Module:remove_user(LUser, LServer)
catch
_:_ ->
ok
end.


-spec default_search_fields() -> list().
default_search_fields() ->
[{<<"User">>, <<"user">>},
Expand Down

0 comments on commit 1e23530

Please sign in to comment.