./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 37 JID = jid:make_bare(Username, Domain),
19 37 case user_exists(JID) of
20 true ->
21 35 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 35 DataToWrite = lists:filter(fun({_, _, Items}) -> Items /= [] end, DataFromModules),
25
26 35 TmpDir = make_tmp_dir(),
27
28 35 CsvFiles = lists:map(
29 fun({DataGroup, Schema, Entries}) ->
30 30 BinDataGroup = atom_to_binary(DataGroup, utf8),
31 30 FileName = <<BinDataGroup/binary, ".csv">>,
32 30 to_csv_file(FileName, Schema, Entries, TmpDir),
33 30 binary_to_list(FileName)
34 end,
35 DataToWrite),
36
37 35 LogFiles = get_all_logs(Username, Domain, TmpDir),
38 35 ZipFile = binary_to_list(ResultFilePath),
39 35 try
40 35 {ok, ZipFile} = zip:create(ZipFile, CsvFiles ++ LogFiles, [{cwd, TmpDir}]),
41 25 remove_tmp_dir(TmpDir),
42 25 ok
43 catch
44 _:Reason ->
45 10 process_error(Reason, ZipFile)
46 end;
47 false ->
48 2 {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 2 ErrorMessage = "It is impossible to create file named '" ++ ZipFile ++ "'",
57 2 {wrong_filename_error, ErrorMessage};
58 process_error({badmatch, {error, ErrorCode}}, ZipFile)
59 when ErrorCode =:= eacces orelse ErrorCode =:= erofs ->
60 4 ErrorMessage = "Permission to create file in location '" ++ ZipFile ++ "' denied",
61 4 {file_creation_permission_denied_error, ErrorMessage};
62 process_error({badmatch, {error, eisdir}}, ZipFile) ->
63 2 ErrorMessage = "Given location '" ++ ZipFile ++ "' is a directory",
64 2 {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 2 ErrorMessage = "Permission to create file in location '" ++ ZipFile ++ "' denied",
71 2 {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 14 JID = jid:make_bare(Username, Domain),
76 14 get_data_from_modules(JID).
77
78 -spec get_data_from_modules(jid:jid()) -> gdpr:personal_data().
79 get_data_from_modules(JID) ->
80 49 {ok, HostType} = mongoose_domain_api:get_domain_host_type(JID#jid.lserver),
81 49 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 30 FilePath = <<(list_to_binary(TmpDir))/binary, "/", Filename/binary>>,
86 30 {ok, File} = file:open(FilePath, [write]),
87 30 Encoded = erl_csv:encode([DataSchema | DataRows]),
88 30 file:write(File, Encoded),
89 30 file:close(File).
90
91 -spec user_exists(jid:jid()) -> boolean().
92 user_exists(JID) ->
93 37 ejabberd_auth:does_user_exist(JID).
94
95 -spec make_tmp_dir() -> file:name().
96 make_tmp_dir() ->
97 70 TmpDirName = lists:flatten(io_lib:format("/tmp/gdpr-~4.36.0b", [rand:uniform(36#zzzz)])),
98 70 case file:make_dir(TmpDirName) of
99 70 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 60 {ok, FileNames} = file:list_dir(TmpDir),
107 60 [file:delete(TmpDir ++ "/" ++ File) || File <- FileNames],
108 60 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 70 Port = erlang:open_port({spawn_executable, Cmd}, [exit_status, {args, Args}]),
114 70 receive
115 70 {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 35 TmpDir = make_tmp_dir(),
127 35 LogFile = get_logs(Username, Domain, TmpDir),
128 35 {ok, {_, ZippedLogs}} = zip:create("archive.zip", [LogFile], [memory, {cwd, TmpDir}]),
129 35 remove_tmp_dir(TmpDir),
130 35 {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 35 OtherNodes = mongoose_cluster:other_cluster_nodes(),
135 35 LogFile = get_logs(Username, Domain, TmpDir),
136 35 LogFilesFromOtherNodes = [get_logs_from_node(Node, Username, Domain, TmpDir) || Node <- OtherNodes],
137 35 [LogFile | LogFilesFromOtherNodes].
138
139 -spec get_logs(gdpr:username(), mongooseim:domain_name(), file:name()) -> file:name().
140 get_logs(Username, Domain, TmpDir) ->
141 70 FileList = [filename:absname(F) || F <- mongoose_logs:get_log_files()],
142 70 Cmd = code:priv_dir(mongooseim) ++ "/parse_logs.sh",
143 70 FileName = "logs-" ++ atom_to_list(node()) ++ ".txt",
144 70 FilePath = TmpDir ++ "/" ++ FileName,
145 70 Args = [FilePath, Username, Domain | FileList],
146 70 0 = run(Cmd, Args, ?CMD_TIMEOUT),
147 70 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 70 {ok, ZippedData} = rpc:call(Node, ?MODULE, retrieve_logs, [Username, Domain]),
152 70 {ok, [File]} = zip:unzip(ZippedData, [{cwd, TmpDir}]),
153 70 filename:basename(File).
Line Hits Source