./ct_report/coverage/mongoose_api_admin.COVER.html

1 %%%-------------------------------------------------------------------
2 %%% @author ludwikbukowski
3 %%% @copyright (C) 2016, Erlang Solutions Ltd.
4 %%% Created : 05. Jul 2016 12:59
5 %%%-------------------------------------------------------------------
6
7 %% @doc MongooseIM REST HTTP API for administration.
8 %% This module implements cowboy REST callbacks and
9 %% passes the requests on to the http api backend module.
10 %% @end
11 -module(mongoose_api_admin).
12 -author("ludwikbukowski").
13 -behaviour(cowboy_rest).
14
15 %% ejabberd_cowboy exports
16 -export([cowboy_router_paths/2]).
17
18 %% cowboy_rest exports
19 -export([allowed_methods/2,
20 content_types_provided/2,
21 terminate/3,
22 init/2,
23 options/2,
24 content_types_accepted/2,
25 delete_resource/2,
26 is_authorized/2]).
27 %% local callbacks
28 -export([to_json/2, from_json/2]).
29
30 -ignore_xref([cowboy_router_paths/2, from_json/2, to_json/2]).
31
32 -include("mongoose_api.hrl").
33 -include("mongoose.hrl").
34
35 -import(mongoose_api_common, [error_response/3,
36 error_response/4,
37 action_to_method/1,
38 method_to_action/1,
39 error_code/1,
40 process_request/4,
41 parse_request_body/1]).
42
43 -type credentials() :: {Username :: binary(), Password :: binary()} | any.
44
45 %%--------------------------------------------------------------------
46 %% ejabberd_cowboy callbacks
47 %%--------------------------------------------------------------------
48
49 %% @doc This is implementation of ejabberd_cowboy callback.
50 %% Returns list of all available http paths.
51 -spec cowboy_router_paths(ejabberd_cowboy:path(), ejabberd_cowboy:options()) ->
52 ejabberd_cowboy:implemented_result().
53 cowboy_router_paths(Base, Opts) ->
54 115 ejabberd_hooks:add(register_command, global, mongoose_api_common, reload_dispatches, 50),
55 115 ejabberd_hooks:add(unregister_command, global, mongoose_api_common, reload_dispatches, 50),
56 115 try
57 115 Commands = mongoose_commands:list(admin),
58 115 [handler_path(Base, Command, Opts) || Command <- Commands]
59 catch
60 Class:Err:StackTrace ->
61
:-(
?LOG_ERROR(#{what => getting_command_list_error,
62
:-(
class => Class, reason => Err, stacktrace => StackTrace}),
63
:-(
[]
64 end.
65
66 %%--------------------------------------------------------------------
67 %% cowboy_rest callbacks
68 %%--------------------------------------------------------------------
69
70 init(Req, Opts) ->
71 112 Bindings = maps:to_list(cowboy_req:bindings(Req)),
72 112 CommandCategory = proplists:get_value(command_category, Opts),
73 112 CommandSubCategory = proplists:get_value(command_subcategory, Opts),
74 112 Auth = proplists:get_value(auth, Opts, any),
75 112 State = #http_api_state{allowed_methods = mongoose_api_common:get_allowed_methods(admin),
76 bindings = Bindings,
77 command_category = CommandCategory,
78 command_subcategory = CommandSubCategory,
79 auth = Auth},
80 112 {cowboy_rest, Req, State}.
81
82 options(Req, State) ->
83
:-(
Req1 = set_cors_headers(Req),
84
:-(
{ok, Req1, State}.
85
86 set_cors_headers(Req) ->
87
:-(
Req1 = cowboy_req:set_resp_header(<<"Access-Control-Allow-Methods">>,
88 <<"GET, OPTIONS, PUT, POST, DELETE">>, Req),
89
:-(
Req2 = cowboy_req:set_resp_header(<<"Access-Control-Allow-Origin">>,
90 <<"*">>, Req1),
91
:-(
cowboy_req:set_resp_header(<<"Access-Control-Allow-Headers">>,
92 <<"Content-Type">>, Req2).
93
94 allowed_methods(Req, #http_api_state{command_category = Name} = State) ->
95 112 CommandList = mongoose_commands:list(admin, Name),
96 112 AllowedMethods = [action_to_method(mongoose_commands:action(Command))
97 112 || Command <- CommandList],
98 112 {[<<"OPTIONS">> | AllowedMethods], Req, State}.
99
100 content_types_provided(Req, State) ->
101 111 CTP = [{{<<"application">>, <<"json">>, '*'}, to_json}],
102 111 {CTP, Req, State}.
103
104 content_types_accepted(Req, State) ->
105 60 CTA = [{{<<"application">>, <<"json">>, '*'}, from_json}],
106 60 {CTA, Req, State}.
107
108 terminate(_Reason, _Req, _State) ->
109 112 ok.
110
111 %% @doc Called for a method of type "DELETE"
112 delete_resource(Req, #http_api_state{command_category = Category,
113 command_subcategory = SubCategory,
114 bindings = B} = State) ->
115 11 Arity = length(B),
116 11 Cmds = mongoose_commands:list(admin, Category, method_to_action(<<"DELETE">>), SubCategory),
117 11 [Command] = [C || C <- Cmds, mongoose_commands:arity(C) == Arity],
118 11 process_request(<<"DELETE">>, Command, Req, State).
119
120
121 %%--------------------------------------------------------------------
122 %% Authorization
123 %%--------------------------------------------------------------------
124
125 % @doc Cowboy callback
126 is_authorized(Req, State) ->
127 112 ControlCreds = get_control_creds(State),
128 112 AuthDetails = mongoose_api_common:get_auth_details(Req),
129 112 case authorize(ControlCreds, AuthDetails) of
130 true ->
131 111 {true, Req, State};
132 false ->
133 1 mongoose_api_common:make_unauthorized_response(Req, State)
134 end.
135
136 -spec authorize(credentials(), {AuthMethod :: atom(),
137 Username :: binary(),
138 Password :: binary()}) -> boolean().
139 110 authorize(any, _) -> true;
140
:-(
authorize(_, undefined) -> false;
141 authorize(ControlCreds, {AuthMethod, User, Password}) ->
142 2 compare_creds(ControlCreds, {User, Password}) andalso
143 1 mongoose_api_common:is_known_auth_method(AuthMethod).
144
145 % @doc Checks if credentials are the same (if control creds are 'any'
146 % it is equal to everything).
147 -spec compare_creds(credentials(), credentials() | undefined) -> boolean().
148 1 compare_creds({User, Pass}, {User, Pass}) -> true;
149 1 compare_creds(_, _) -> false.
150
151 get_control_creds(#http_api_state{auth = Creds}) ->
152 112 Creds.
153
154 %%--------------------------------------------------------------------
155 %% Internal funs
156 %%--------------------------------------------------------------------
157
158 %% @doc Called for a method of type "GET"
159 to_json(Req, #http_api_state{command_category = Category,
160 command_subcategory = SubCategory,
161 bindings = B} = State) ->
162 40 Cmds = mongoose_commands:list(admin, Category, method_to_action(<<"GET">>), SubCategory),
163 40 Arity = length(B),
164 40 case [C || C <- Cmds, mongoose_commands:arity(C) == Arity] of
165 [Command] ->
166 39 process_request(<<"GET">>, Command, Req, State);
167 [] ->
168 1 error_response(not_found, ?ARGS_LEN_ERROR, Req, State)
169 end.
170
171 %% @doc Called for a method of type "POST" and "PUT"
172 from_json(Req, #http_api_state{command_category = Category,
173 command_subcategory = SubCategory,
174 bindings = B} = State) ->
175 60 case parse_request_body(Req) of
176 {error, _R}->
177
:-(
error_response(bad_request, ?BODY_MALFORMED, Req, State);
178 {Params, _} ->
179 60 Method = cowboy_req:method(Req),
180 60 Cmds = mongoose_commands:list(admin, Category, method_to_action(Method), SubCategory),
181 60 QVals = cowboy_req:parse_qs(Req),
182 60 Arity = length(B) + length(Params) + length(QVals),
183 60 case [C || C <- Cmds, mongoose_commands:arity(C) == Arity] of
184 [Command] ->
185 60 process_request(Method, Command, {Params, Req}, State);
186 [] ->
187
:-(
error_response(not_found, ?ARGS_LEN_ERROR, Req, State)
188 end
189 end.
190
191 -spec handler_path(ejabberd_cowboy:path(), mongoose_commands:t(), [{atom(), term()}]) ->
192 ejabberd_cowboy:route().
193 handler_path(Base, Command, ExtraOpts) ->
194 2820 {[Base, mongoose_api_common:create_admin_url_path(Command)],
195 ?MODULE, [{command_category, mongoose_commands:category(Command)},
196 {command_subcategory, mongoose_commands:subcategory(Command)} | ExtraOpts]}.
197
198
Line Hits Source