./ct_report/coverage/gdpr_api.COVER.html

1 -module(gdpr_api).
2
3 -include("jlib.hrl").
4
5 % Exported for RPC call
6 -export([retrieve_all/3, retrieve_logs/2, get_data_from_modules/2]).
7
8 -ignore_xref([retrieve_all/3, retrieve_logs/2, get_data_from_modules/2]).
9
10 -define(CMD_TIMEOUT, 300000).
11
12 -type error_code() :: user_does_not_exist_error | wrong_filename_error |
13 file_creation_permission_denied_error | location_is_a_directory_error.
14
15 -spec retrieve_all(jid:user(), jid:server(), Path :: binary()) ->
16 ok | {error_code(), Reason :: string()}.
17 retrieve_all(Username, Domain, ResultFilePath) ->
18 61 JID = jid:make_bare(Username, Domain),
19 61 case user_exists(JID) of
20 true ->
21 61 DataFromModules = get_data_from_modules(JID),
22 % The contract is that we create personal data files only when there are any items
23 % returned for the data group.
24 61 DataToWrite = lists:filter(fun({_, _, Items}) -> Items /= [] end, DataFromModules),
25
26 61 TmpDir = make_tmp_dir(),
27
28 61 CsvFiles = lists:map(
29 fun({DataGroup, Schema, Entries}) ->
30 76 BinDataGroup = atom_to_binary(DataGroup, utf8),
31 76 FileName = <<BinDataGroup/binary, ".csv">>,
32 76 to_csv_file(FileName, Schema, Entries, TmpDir),
33 76 binary_to_list(FileName)
34 end,
35 DataToWrite),
36
37 61 LogFiles = get_all_logs(Username, Domain, TmpDir),
38 61 ZipFile = binary_to_list(ResultFilePath),
39 61 try
40 61 {ok, ZipFile} = zip:create(ZipFile, CsvFiles ++ LogFiles, [{cwd, TmpDir}]),
41 61 remove_tmp_dir(TmpDir),
42 61 ok
43 catch
44 _:Reason ->
45
:-(
process_error(Reason, ZipFile)
46 end;
47 false ->
48
:-(
{user_does_not_exist_error, "User does not exist"}
49 end.
50
51 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
52 %%% Private funs
53 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
54
55 process_error({badmatch, {error, enoent}}, ZipFile) ->
56
:-(
ErrorMessage = "It is impossible to create file named '" ++ ZipFile ++ "'",
57
:-(
{wrong_filename_error, ErrorMessage};
58 process_error({badmatch, {error, ErrorCode}}, ZipFile)
59 when ErrorCode =:= eacces orelse ErrorCode =:= erofs ->
60
:-(
ErrorMessage = "Permission to create file in location '" ++ ZipFile ++ "' denied",
61
:-(
{file_creation_permission_denied_error, ErrorMessage};
62 process_error({badmatch, {error, eisdir}}, ZipFile) ->
63
:-(
ErrorMessage = "Given location '" ++ ZipFile ++ "' is a directory",
64
:-(
{location_is_a_directory_error, ErrorMessage};
65 process_error({badmatch, {error, eexist}}, ZipFile) ->
66
:-(
ErrorMessage = "File '" ++ ZipFile ++ "' already exist",
67
:-(
{file_already_exist_error, ErrorMessage};
68 process_error({badmatch, {error, {'EXIT', {{badmatch, {error, ErrorCode}}, _}}}}, ZipFile)
69 when ErrorCode =:= eacces orelse ErrorCode =:= erofs ->
70
:-(
ErrorMessage = "Permission to create file in location '" ++ ZipFile ++ "' denied",
71
:-(
{file_creation_permission_denied_error, ErrorMessage}.
72
73 -spec get_data_from_modules(jid:user(), jid:server()) -> gdpr:personal_data().
74 get_data_from_modules(Username, Domain) ->
75 22 JID = jid:make_bare(Username, Domain),
76 22 get_data_from_modules(JID).
77
78 -spec get_data_from_modules(jid:jid()) -> gdpr:personal_data().
79 get_data_from_modules(JID) ->
80 83 {ok, HostType} = mongoose_domain_api:get_domain_host_type(JID#jid.lserver),
81 83 mongoose_hooks:get_personal_data(HostType, JID).
82
83 -spec to_csv_file(file:name_all(), gdpr:schema(), gdpr:entries(), file:name()) -> ok.
84 to_csv_file(Filename, DataSchema, DataRows, TmpDir) ->
85 76 FilePath = <<(list_to_binary(TmpDir))/binary, "/", Filename/binary>>,
86 76 {ok, File} = file:open(FilePath, [write]),
87 76 Encoded = erl_csv:encode([DataSchema | DataRows]),
88 76 file:write(File, Encoded),
89 76 file:close(File).
90
91 -spec user_exists(jid:jid()) -> boolean().
92 user_exists(JID) ->
93 61 ejabberd_auth:does_user_exist(JID).
94
95 -spec make_tmp_dir() -> file:name().
96 make_tmp_dir() ->
97 119 TmpDirName = lists:flatten(io_lib:format("/tmp/gdpr-~4.36.0b", [rand:uniform(36#zzzz)])),
98 119 case file:make_dir(TmpDirName) of
99 119 ok -> TmpDirName;
100
:-(
{error, eexist} -> make_tmp_dir();
101
:-(
{error, Error} -> {error, Error}
102 end.
103
104 -spec remove_tmp_dir(file:name()) -> ok.
105 remove_tmp_dir(TmpDir) ->
106 119 {ok, FileNames} = file:list_dir(TmpDir),
107 119 [file:delete(TmpDir ++ "/" ++ File) || File <- FileNames],
108 119 file:del_dir(TmpDir).
109
110 -type cmd() :: string() | binary().
111 -spec run(cmd(), [cmd()], timeout()) -> non_neg_integer() | timeout.
112 run(Cmd, Args, Timeout) ->
113 119 Port = erlang:open_port({spawn_executable, Cmd}, [exit_status, {args, Args}]),
114 119 receive
115 119 {Port, {exit_status, ExitStatus}} -> ExitStatus
116 after Timeout ->
117
:-(
timeout
118 end.
119
120 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
121 %%% Logs retrieval
122 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
123
124 -spec retrieve_logs(gdpr:username(), mongooseim:domain_name()) -> {ok, ZippedLogs :: binary()}.
125 retrieve_logs(Username, Domain) ->
126 58 TmpDir = make_tmp_dir(),
127 58 LogFile = get_logs(Username, Domain, TmpDir),
128 58 {ok, {_, ZippedLogs}} = zip:create("archive.zip", [LogFile], [memory, {cwd, TmpDir}]),
129 58 remove_tmp_dir(TmpDir),
130 58 {ok, ZippedLogs}.
131
132 -spec get_all_logs(gdpr:username(), mongooseim:domain_name(), file:name()) -> [file:name()].
133 get_all_logs(Username, Domain, TmpDir) ->
134 61 OtherNodes = mongoose_cluster:other_cluster_nodes(),
135 61 LogFile = get_logs(Username, Domain, TmpDir),
136 61 LogFilesFromOtherNodes = [get_logs_from_node(Node, Username, Domain, TmpDir) || Node <- OtherNodes],
137 61 [LogFile | LogFilesFromOtherNodes].
138
139 -spec get_logs(gdpr:username(), mongooseim:domain_name(), file:name()) -> file:name().
140 get_logs(Username, Domain, TmpDir) ->
141 119 FileList = [filename:absname(F) || F <- mongoose_logs:get_log_files()],
142 119 Cmd = code:priv_dir(mongooseim) ++ "/parse_logs.sh",
143 119 FileName = "logs-" ++ atom_to_list(node()) ++ ".txt",
144 119 FilePath = TmpDir ++ "/" ++ FileName,
145 119 Args = [FilePath, Username, Domain | FileList],
146 119 0 = run(Cmd, Args, ?CMD_TIMEOUT),
147 119 FileName.
148
149 -spec get_logs_from_node(node(), gdpr:username(), mongooseim:domain_name(), file:name()) -> file:name().
150 get_logs_from_node(Node, Username, Domain, TmpDir) ->
151 58 {ok, ZippedData} = rpc:call(Node, ?MODULE, retrieve_logs, [Username, Domain]),
152 58 {ok, [File]} = zip:unzip(ZippedData, [{cwd, TmpDir}]),
153 58 filename:basename(File).
Line Hits Source