./ct_report/coverage/mongoose_domain_handler.COVER.html

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