• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

esl / MongooseIM / 5076995463

25 May 2023 07:04AM UTC coverage: 15.93% (-66.0%) from 81.94%
5076995463

push

github

GitHub
Merge pull request #4027 from esl/docker-script

5388 of 33824 relevant lines covered (15.93%)

1117.97 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

10.34
/src/mongoose_admin_api/mongoose_admin_api.erl
1
-module(mongoose_admin_api).
2

3
-behaviour(mongoose_http_handler).
4

5
%% mongoose_http_handler callbacks
6
-export([config_spec/0, routes/1]).
7

8
%% config processing callbacks
9
-export([process_config/1]).
10

11
%% Utilities for the handler modules
12
-export([init/2,
13
         is_authorized/2,
14
         parse_body/1,
15
         parse_qs/1,
16
         try_handle_request/3,
17
         throw_error/2,
18
         resource_created/4,
19
         respond/3]).
20

21
-include("mongoose.hrl").
22
-include("mongoose_config_spec.hrl").
23

24
-type handler_options() :: #{path := string(), username => binary(), password => binary(),
25
                             atom() => any()}.
26
-type req() :: cowboy_req:req().
27
-type state() :: #{atom() => any()}.
28
-type error_type() :: bad_request | denied | not_found | duplicate | internal.
29

30
-export_type([state/0]).
31

32
-callback routes(state()) -> mongoose_http_handler:routes().
33

34
%% mongoose_http_handler callbacks
35

36
-spec config_spec() -> mongoose_config_spec:config_section().
37
config_spec() ->
38
    Handlers = all_handlers(),
6,404✔
39
    #section{items = #{<<"username">> => #option{type = binary},
6,404✔
40
                       <<"password">> => #option{type = binary},
41
                       <<"handlers">> => #list{items = #option{type = atom,
42
                                                               validate = {enum, Handlers}},
43
                                               validate = unique}},
44
             defaults = #{<<"handlers">> => Handlers},
45
             process = fun ?MODULE:process_config/1}.
46

47
-spec process_config(handler_options()) -> handler_options().
48
process_config(Opts) ->
49
    case maps:is_key(username, Opts) =:= maps:is_key(password, Opts) of
20✔
50
        true ->
51
            Opts;
16✔
52
        false ->
53
            error(#{what => both_username_and_password_required, opts => Opts})
4✔
54
    end.
55

56
-spec routes(handler_options()) -> mongoose_http_handler:routes().
57
routes(Opts = #{path := BasePath}) ->
58
    [{[BasePath, Path], Module, ModuleOpts} || {Path, Module, ModuleOpts} <- api_paths(Opts)].
×
59

60
all_handlers() ->
61
    [contacts, users, sessions, messages, stanzas, muc_light, muc, inbox, domain, metrics].
6,404✔
62

63
-spec api_paths(handler_options()) -> mongoose_http_handler:routes().
64
api_paths(#{handlers := Handlers} = Opts) ->
65
    State = maps:with([username, password], Opts),
×
66
    lists:flatmap(fun(Handler) -> api_paths_for_handler(Handler, State) end, Handlers).
×
67

68
api_paths_for_handler(Handler, State) ->
69
    HandlerModule = list_to_existing_atom("mongoose_admin_api_" ++ atom_to_list(Handler)),
×
70
    HandlerModule:routes(State).
×
71

72
%% Utilities for the handler modules
73

74
-spec init(req(), state()) -> {cowboy_rest, req(), state()}.
75
init(Req, State) ->
76
    {cowboy_rest, Req, State}.
×
77

78
-spec is_authorized(req(), state()) -> {true | {false, iodata()}, req(), state()}.
79
is_authorized(Req, State) ->
80
    AuthDetails = mongoose_api_common:get_auth_details(Req),
×
81
    case authorize(State, AuthDetails) of
×
82
        true ->
83
            {true, Req, State};
×
84
        false ->
85
            mongoose_api_common:make_unauthorized_response(Req, State)
×
86
    end.
87

88
authorize(#{username := Username, password := Password}, AuthDetails) ->
89
    case AuthDetails of
×
90
        {AuthMethod, Username, Password} ->
91
            mongoose_api_common:is_known_auth_method(AuthMethod);
×
92
        _ ->
93
            false
×
94
    end;
95
authorize(#{}, AuthDetails) ->
96
    AuthDetails =:= undefined. % Do not accept basic auth when not configured
×
97

98
-spec parse_body(req()) -> #{atom() => jiffy:json_value()}.
99
parse_body(Req) ->
100
    try
×
101
        {ok, Body, _Req2} = cowboy_req:read_body(Req),
×
102
        {DecodedBody} = jiffy:decode(Body),
×
103
        maps:from_list([{binary_to_existing_atom(K), V} || {K, V} <- DecodedBody])
×
104
    catch Class:Reason:Stacktrace ->
105
            ?LOG_WARNING(#{what => parse_body_failed,
×
106
                           class => Class, reason => Reason, stacktrace => Stacktrace}),
×
107
            throw_error(bad_request, <<"Invalid request body">>)
×
108
    end.
109

110
-spec parse_qs(req()) -> #{atom() => binary() | true}.
111
parse_qs(Req) ->
112
    try
×
113
        maps:from_list([{binary_to_existing_atom(K), V} || {K, V} <- cowboy_req:parse_qs(Req)])
×
114
    catch Class:Reason:Stacktrace ->
115
            ?LOG_WARNING(#{what => parse_qs_failed,
×
116
                           class => Class, reason => Reason, stacktrace => Stacktrace}),
×
117
            throw_error(bad_request, <<"Invalid query string">>)
×
118
    end.
119

120
-spec try_handle_request(req(), state(), fun((req(), state()) -> Result)) -> Result.
121
try_handle_request(Req, State, F) ->
122
    try
×
123
        F(Req, State)
×
124
    catch throw:#{error_type := ErrorType, message := Msg} ->
125
            error_response(ErrorType, Msg, Req, State)
×
126
    end.
127

128
-spec throw_error(error_type(), iodata()) -> no_return().
129
throw_error(ErrorType, Msg) ->
130
    throw(#{error_type => ErrorType, message => Msg}).
×
131

132
-spec resource_created(req(), state(), iodata(), iodata()) -> {stop, req(), state()}.
133
resource_created(Req, State, Path, Body) ->
134
    Req2 = cowboy_req:set_resp_body(Body, Req),
×
135
    Headers = #{<<"location">> => Path},
×
136
    Req3 = cowboy_req:reply(201, Headers, Req2),
×
137
    {stop, Req3, State}.
×
138

139
%% @doc Send response when it can't be returned in a tuple from the handler (e.g. for DELETE)
140
-spec respond(req(), state(), jiffy:json_value()) -> {stop, req(), state()}.
141
respond(Req, State, Response) ->
142
    Req2 = cowboy_req:set_resp_body(jiffy:encode(Response), Req),
×
143
    Req3 = cowboy_req:reply(200, Req2),
×
144
    {stop, Req3, State}.
×
145

146
-spec error_response(error_type(), iodata(), req(), state()) -> {stop, req(), state()}.
147
error_response(ErrorType, Message, Req, State) ->
148
    BinMessage = iolist_to_binary(Message),
×
149
    ?LOG(log_level(ErrorType), #{what => mongoose_admin_api_error_response,
×
150
                                 error_type => ErrorType,
151
                                 message => BinMessage,
152
                                 req => Req}),
×
153
    Req1 = cowboy_req:reply(error_code(ErrorType), #{}, jiffy:encode(BinMessage), Req),
×
154
    {stop, Req1, State}.
×
155

156
-spec error_code(error_type()) -> non_neg_integer().
157
error_code(bad_request) -> 400;
×
158
error_code(denied) -> 403;
×
159
error_code(not_found) -> 404;
×
160
error_code(duplicate) -> 409;
×
161
error_code(internal) -> 500.
×
162

163
-spec log_level(error_type()) -> logger:level().
164
log_level(bad_request) -> warning;
×
165
log_level(denied) -> warning;
×
166
log_level(not_found) -> warning;
×
167
log_level(duplicate) -> warning;
×
168
log_level(internal) -> error.
×
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc