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