./ct_report/coverage/mongoose_api_client.COVER.html

1 %%%-------------------------------------------------------------------
2 %%% @author ludwikbukowski
3 %%% @copyright (C) 2016, Erlang Solutions Ltd.
4 %%%
5 %%% @end
6 %%% Created : 19. Jul 2016 17:55
7 %%%-------------------------------------------------------------------
8 %% @doc MongooseIM REST HTTP API for clients.
9 %% This module implements cowboy REST callbacks and
10 %% passes the requests on to the http api backend module.
11 %% It provides also client authorization mechanism
12
13 -module(mongoose_api_client).
14 -author("ludwikbukowski").
15 -include("mongoose_api.hrl").
16 -include("jlib.hrl").
17 -include("mongoose.hrl").
18
19 -behaviour(mongoose_http_handler).
20
21 %% mongoose_http_handler callbacks
22 -export([routes/1]).
23
24 -export([to_json/2, from_json/2]).
25
26 %% API
27 -export([is_authorized/2,
28 init/2,
29 allowed_methods/2,
30 content_types_provided/2,
31 content_types_accepted/2,
32 rest_terminate/2,
33 delete_resource/2]).
34
35 -ignore_xref([allowed_methods/2, content_types_accepted/2, content_types_provided/2,
36 cowboy_router_paths/2, delete_resource/2, from_json/2, init/2,
37 is_authorized/2, rest_terminate/2, to_json/2]).
38
39 -import(mongoose_api_common, [action_to_method/1,
40 method_to_action/1,
41 process_request/4,
42 error_response/4,
43 parse_request_body/1]).
44
45 %%--------------------------------------------------------------------
46 %% mongoose_http_handler callbacks
47 %%--------------------------------------------------------------------
48
49 -spec routes(mongoose_http_handler:options()) -> mongoose_http_handler:routes().
50 routes(#{path := BasePath}) ->
51
:-(
ejabberd_hooks:add(register_command, global, mongoose_api_common, reload_dispatches, 50),
52
:-(
ejabberd_hooks:add(unregister_command, global, mongoose_api_common, reload_dispatches, 50),
53
:-(
try
54
:-(
Commands = mongoose_commands:list(user),
55
:-(
[handler_path(BasePath, Command) || Command <- Commands]
56 catch
57 Class:Err:Stacktrace ->
58
:-(
?LOG_ERROR(#{what => rest_getting_command_list_failed,
59
:-(
class => Class, reason => Err, stacktrace => Stacktrace}),
60
:-(
[]
61 end.
62
63 %%--------------------------------------------------------------------
64 %% cowboy_rest callbacks
65 %%--------------------------------------------------------------------
66
67
68 init(Req, #{command_category := CommandCategory}) ->
69
:-(
Bindings = maps:to_list(cowboy_req:bindings(Req)),
70
:-(
State = #http_api_state{allowed_methods = mongoose_api_common:get_allowed_methods(user),
71 bindings = Bindings,
72 command_category = CommandCategory},
73
:-(
{cowboy_rest, Req, State}.
74
75 allowed_methods(Req, #http_api_state{command_category = Name} = State) ->
76
:-(
CommandList = mongoose_commands:list(user, Name),
77
:-(
AllowedMethods = [action_to_method(mongoose_commands:action(Command))
78
:-(
|| Command <- CommandList],
79
:-(
{AllowedMethods, Req, State}.
80
81 content_types_provided(Req, State) ->
82
:-(
CTP = [{{<<"application">>, <<"json">>, '*'}, to_json}],
83
:-(
{CTP, Req, State}.
84
85 content_types_accepted(Req, State) ->
86
:-(
CTA = [{{<<"application">>, <<"json">>, '*'}, from_json}],
87
:-(
{CTA, Req, State}.
88
89 rest_terminate(_Req, _State) ->
90
:-(
ok.
91
92 is_authorized(Req, State) ->
93
:-(
AuthDetails = cowboy_req:parse_header(<<"authorization">>, Req),
94
:-(
do_authorize(AuthDetails, Req, State).
95
96 %% @doc Called for a method of type "DELETE"
97 delete_resource(Req, #http_api_state{command_category = Category,
98 command_subcategory = SubCategory,
99 bindings = B} = State) ->
100
:-(
Arity = length(B),
101
:-(
Cmds = mongoose_commands:list(user, Category, method_to_action(<<"DELETE">>), SubCategory),
102
:-(
[Command] = [C || C <- Cmds, arity(C) == Arity],
103
:-(
mongoose_api_common:process_request(<<"DELETE">>, Command, Req, State).
104
105 %%--------------------------------------------------------------------
106 %% internal funs
107 %%--------------------------------------------------------------------
108
109 %% @doc Called for a method of type "GET"
110 to_json(Req, #http_api_state{command_category = Category,
111 command_subcategory = SubCategory,
112 bindings = B} = State) ->
113
:-(
Arity = length(B),
114
:-(
Cmds = mongoose_commands:list(user, Category, method_to_action(<<"GET">>), SubCategory),
115
:-(
[Command] = [C || C <- Cmds, arity(C) == Arity],
116
:-(
mongoose_api_common:process_request(<<"GET">>, Command, Req, State).
117
118
119 %% @doc Called for a method of type "POST" and "PUT"
120 from_json(Req, #http_api_state{command_category = Category,
121 command_subcategory = SubCategory,
122 bindings = B} = State) ->
123
:-(
Method = cowboy_req:method(Req),
124
:-(
case parse_request_body(Req) of
125 {error, _R}->
126
:-(
error_response(bad_request, ?BODY_MALFORMED, Req, State);
127 {Params, _} ->
128
:-(
Arity = length(B) + length(Params),
129
:-(
Cmds = mongoose_commands:list(user, Category, method_to_action(Method), SubCategory),
130
:-(
case [C || C <- Cmds, arity(C) == Arity] of
131 [Command] ->
132
:-(
process_request(Method, Command, {Params, Req}, State);
133 [] ->
134
:-(
error_response(not_found, ?ARGS_LEN_ERROR, Req, State)
135 end
136 end.
137
138 arity(C) ->
139 % we don't have caller in bindings (we know it from authorisation),
140 % so it doesn't count when checking arity
141
:-(
Args = mongoose_commands:args(C),
142
:-(
length([N || {N, _} <- Args, N =/= caller]).
143
144 do_authorize({basic, User, Password}, Req, State) ->
145
:-(
case jid:from_binary(User) of
146 error ->
147
:-(
make_unauthorized_response(Req, State);
148 JID ->
149
:-(
do_check_password(JID, Password, Req, State)
150 end;
151 do_authorize(_, Req, State) ->
152
:-(
make_unauthorized_response(Req, State).
153
154 do_check_password(#jid{} = JID, Password, Req, State) ->
155
:-(
case ejabberd_auth:check_password(JID, Password) of
156 true ->
157
:-(
{true, Req, State#http_api_state{entity = jid:to_binary(JID)}};
158 _ ->
159
:-(
make_unauthorized_response(Req, State)
160 end.
161
162 make_unauthorized_response(Req, State) ->
163
:-(
{{false, <<"Basic realm=\"mongooseim\"">>}, Req, State}.
164
165 -spec handler_path(ejabberd_cowboy:path(), mongoose_commands:t()) -> ejabberd_cowboy:route().
166 handler_path(Base, Command) ->
167
:-(
{[Base, mongoose_api_common:create_user_url_path(Command)],
168 ?MODULE, #{command_category => mongoose_commands:category(Command)}}.
169
Line Hits Source