./ct_report/coverage/mongoose_domain_handler.COVER.html

1 %% REST API for domain actions.
2 -module(mongoose_domain_handler).
3 -behaviour(cowboy_rest).
4
5 %% ejabberd_cowboy exports
6 -export([cowboy_router_paths/2]).
7
8 %% Standard cowboy_rest callbacks.
9 -export([init/2,
10 allowed_methods/2,
11 content_types_accepted/2,
12 content_types_provided/2,
13 is_authorized/2,
14 delete_resource/2]).
15
16 %% Custom cowboy_rest callbacks.
17 -export([handle_domain/2,
18 to_json/2]).
19
20 -ignore_xref([cowboy_router_paths/2, handle_domain/2, to_json/2]).
21
22 -include("mongoose_logger.hrl").
23 -type state() :: map().
24
25 -spec cowboy_router_paths(ejabberd_cowboy:path(), ejabberd_cowboy:options()) ->
26 ejabberd_cowboy:implemented_result().
27 cowboy_router_paths(Base, Opts) ->
28 124 [{[Base, "/domains/:domain"], ?MODULE, Opts}].
29
30 %% cowboy_rest callbacks:
31 %% Opts could be `[{password, <<\"secret\">>}, {username, <<\"admin\">>}]'.
32 init(Req, Opts) ->
33
:-(
{cowboy_rest, Req, maps:from_list(Opts)}.
34
35 allowed_methods(Req, State) ->
36
:-(
{[<<"GET">>, <<"PUT">>, <<"PATCH">>, <<"DELETE">>], Req, State}.
37
38 content_types_accepted(Req, State) ->
39
:-(
{[{{<<"application">>, <<"json">>, '*'}, handle_domain}],
40 Req, State}.
41
42 content_types_provided(Req, State) ->
43
:-(
{[{{<<"application">>, <<"json">>, '*'}, to_json}], Req, State}.
44
45 is_authorized(Req, State) ->
46
:-(
HeaderDetails = cowboy_req:parse_header(<<"authorization">>, Req),
47
:-(
ConfigDetails = state_to_details(State),
48
:-(
case check_auth(HeaderDetails, ConfigDetails) of
49 ok ->
50
:-(
{true, Req, State};
51 {error, auth_header_passed_but_not_expected} ->
52
:-(
{false, reply_error(403, <<"basic auth provided, but not configured">>, Req), State};
53 {error, auth_password_invalid} ->
54
:-(
{false, reply_error(403, <<"basic auth provided, invalid password">>, Req), State};
55 {error, no_basic_auth_provided} ->
56
:-(
{false, reply_error(403, <<"basic auth is required">>, Req), State}
57 end.
58
59 state_to_details(#{username := User, password := Pass}) ->
60
:-(
{basic, User, Pass};
61 state_to_details(_) ->
62
:-(
not_configured.
63
64 check_auth({basic, _User, _Pass}, _ConfigDetails = not_configured) ->
65
:-(
{error, auth_header_passed_but_not_expected};
66 check_auth(_HeaderDetails, _ConfigDetails = not_configured) ->
67
:-(
ok;
68 check_auth({basic, User, Pass}, {basic, User, Pass}) ->
69
:-(
ok;
70 check_auth({basic, _, _}, {basic, _, _}) ->
71
:-(
{error, auth_password_invalid};
72 check_auth(_, {basic, _, _}) ->
73
:-(
{error, no_basic_auth_provided}.
74
75 %% Custom cowboy_rest callbacks:
76 -spec to_json(Req, State) -> {Body, Req, State} | {stop, Req, State}
77 when Req :: cowboy_req:req(), State :: state(), Body :: binary().
78 to_json(Req, State) ->
79
:-(
ExtDomain = cowboy_req:binding(domain, Req),
80
:-(
Domain = jid:nameprep(ExtDomain),
81
:-(
case mongoose_domain_sql:select_domain(Domain) of
82 {ok, Props} ->
83
:-(
{jiffy:encode(Props), Req, State};
84 {error, not_found} ->
85
:-(
{stop, reply_error(404, <<"domain not found">>, Req), State}
86 end.
87
88 -spec handle_domain(Req, State) -> {boolean(), Req, State}
89 when Req :: cowboy_req:req(), State :: state().
90 handle_domain(Req, State) ->
91
:-(
Method = cowboy_req:method(Req),
92
:-(
ExtDomain = cowboy_req:binding(domain, Req),
93
:-(
Domain = jid:nameprep(ExtDomain),
94
:-(
{ok, Body, Req2} = cowboy_req:read_body(Req),
95
:-(
MaybeParams = json_decode(Body),
96
:-(
case Method of
97 <<"PUT">> ->
98
:-(
insert_domain(Domain, MaybeParams, Req2, State);
99 <<"PATCH">> ->
100
:-(
patch_domain(Domain, MaybeParams, Req2, State)
101 end.
102
103 %% Private helper functions:
104 insert_domain(Domain, {ok, #{<<"host_type">> := HostType}}, Req, State) ->
105
:-(
case mongoose_domain_api:insert_domain(Domain, HostType) of
106 ok ->
107
:-(
{true, Req, State};
108 {error, duplicate} ->
109
:-(
{false, reply_error(409, <<"duplicate">>, Req), State};
110 {error, static} ->
111
:-(
{false, reply_error(403, <<"domain is static">>, Req), State};
112 {error, {db_error, _}} ->
113
:-(
{false, reply_error(500, <<"database error">>, Req), State};
114 {error, service_disabled} ->
115
:-(
{false, reply_error(403, <<"service disabled">>, Req), State};
116 {error, unknown_host_type} ->
117
:-(
{false, reply_error(403, <<"unknown host type">>, Req), State}
118 end;
119 insert_domain(_Domain, {ok, #{}}, Req, State) ->
120
:-(
{false, reply_error(400, <<"'host_type' field is missing">>, Req), State};
121 insert_domain(_Domain, {error, empty}, Req, State) ->
122
:-(
{false, reply_error(400, <<"body is empty">>, Req), State};
123 insert_domain(_Domain, {error, _}, Req, State) ->
124
:-(
{false, reply_error(400, <<"failed to parse JSON">>, Req), State}.
125
126 patch_domain(Domain, {ok, #{<<"enabled">> := true}}, Req, State) ->
127
:-(
Res = mongoose_domain_api:enable_domain(Domain),
128
:-(
handle_enabled_result(Res, Req, State);
129 patch_domain(Domain, {ok, #{<<"enabled">> := false}}, Req, State) ->
130
:-(
Res = mongoose_domain_api:disable_domain(Domain),
131
:-(
handle_enabled_result(Res, Req, State);
132 patch_domain(_Domain, {ok, #{}}, Req, State) ->
133
:-(
{false, reply_error(400, <<"'enabled' field is missing">>, Req), State};
134 patch_domain(_Domain, {error, empty}, Req, State) ->
135
:-(
{false, reply_error(400, <<"body is empty">>, Req), State};
136 patch_domain(_Domain, {error, _}, Req, State) ->
137
:-(
{false, reply_error(400, <<"failed to parse JSON">>, Req), State}.
138
139 handle_enabled_result(Res, Req, State) ->
140
:-(
case Res of
141 ok ->
142
:-(
{true, Req, State};
143 {error, not_found} ->
144
:-(
{false, reply_error(404, <<"domain not found">>, Req), State};
145 {error, static} ->
146
:-(
{false, reply_error(403, <<"domain is static">>, Req), State};
147 {error, service_disabled} ->
148
:-(
{false, reply_error(403, <<"service disabled">>, Req), State};
149 {error, {db_error, _}} ->
150
:-(
{false, reply_error(500, <<"database error">>, Req), State}
151 end.
152
153 delete_resource(Req, State) ->
154
:-(
ExtDomain = cowboy_req:binding(domain, Req),
155
:-(
Domain = jid:nameprep(ExtDomain),
156
:-(
{ok, Body, Req2} = cowboy_req:read_body(Req),
157
:-(
MaybeParams = json_decode(Body),
158
:-(
delete_domain(Domain, MaybeParams, Req2, State).
159
160 delete_domain(Domain, {ok, #{<<"host_type">> := HostType}}, Req, State) ->
161
:-(
case mongoose_domain_api:delete_domain(Domain, HostType) of
162 ok ->
163
:-(
{true, Req, State};
164 {error, {db_error, _}} ->
165
:-(
{false, reply_error(500, <<"database error">>, Req), State};
166 {error, static} ->
167
:-(
{false, reply_error(403, <<"domain is static">>, Req), State};
168 {error, service_disabled} ->
169
:-(
{false, reply_error(403, <<"service disabled">>, Req), State};
170 {error, wrong_host_type} ->
171
:-(
{false, reply_error(403, <<"wrong host type">>, Req), State};
172 {error, unknown_host_type} ->
173
:-(
{false, reply_error(403, <<"unknown host type">>, Req), State}
174 end;
175 delete_domain(_Domain, {ok, #{}}, Req, State) ->
176
:-(
{false, reply_error(400, <<"'host_type' field is missing">>, Req), State};
177 delete_domain(_Domain, {error, empty}, Req, State) ->
178
:-(
{false, reply_error(400, <<"body is empty">>, Req), State};
179 delete_domain(_Domain, {error, _}, Req, State) ->
180
:-(
{false, reply_error(400, <<"failed to parse JSON">>, Req), State}.
181
182 reply_error(Code, What, Req) ->
183
:-(
?LOG_ERROR(#{what => rest_domain_failed, reason => What,
184
:-(
code => Code, req => Req}),
185
:-(
Body = jiffy:encode(#{what => What}),
186
:-(
cowboy_req:reply(Code, #{<<"content-type">> => <<"application/json">>}, Body, Req).
187
188 json_decode(<<>>) ->
189
:-(
{error, empty};
190 json_decode(Bin) ->
191
:-(
try
192
:-(
{ok, jiffy:decode(Bin, [return_maps])}
193 catch
194 Class:Reason ->
195
:-(
{error, {Class, Reason}}
196 end.
Line Hits Source