./ct_report/coverage/ejabberd_auth_http.COVER.html

1 %%%----------------------------------------------------------------------
2 %%% File : ejabberd_auth_http.erl
3 %%% Author : Piotr Nosek <piotr.nosek@erlang-solutions.com>
4 %%% Purpose : Authentication via HTTP request
5 %%% Created : 23 Sep 2013 by Piotr Nosek <piotr.nosek@erlang-solutions.com>
6 %%%----------------------------------------------------------------------
7
8 -module(ejabberd_auth_http).
9 -author('piotr.nosek@erlang-solutions.com').
10
11 -behaviour(mongoose_gen_auth).
12
13 %% External exports
14 -export([start/1,
15 stop/1,
16 config_spec/0,
17 supports_sasl_module/2,
18 set_password/4,
19 authorize/1,
20 try_register/4,
21 get_password/3,
22 get_password_s/3,
23 does_user_exist/3,
24 remove_user/3,
25 supported_features/0]).
26
27 %% Pre-mongoose_credentials API
28 -export([check_password/4,
29 check_password/6]).
30
31 -include("mongoose.hrl").
32 -include("mongoose_config_spec.hrl").
33
34 -type http_error_atom() :: conflict | not_found | not_authorized | not_allowed.
35 -type params() :: #{luser := jid:luser(),
36 lserver := jid:lserver(),
37 host_type := mongooseim:host_type(),
38 password => binary()}.
39
40 %%%----------------------------------------------------------------------
41 %%% API
42 %%%----------------------------------------------------------------------
43
44 -spec start(mongooseim:host_type()) -> ok.
45 start(_HostType) ->
46
:-(
ok.
47
48 -spec stop(mongooseim:host_type()) -> ok.
49 stop(_HostType) ->
50
:-(
ok.
51
52 -spec config_spec() -> mongoose_config_spec:config_section().
53 config_spec() ->
54 200 #section{items = #{<<"basic_auth">> => #option{type = string}}}.
55
56 -spec supports_sasl_module(mongooseim:host_type(), cyrsasl:sasl_module()) -> boolean().
57
:-(
supports_sasl_module(_HostType, cyrsasl_plain) -> true;
58
:-(
supports_sasl_module(HostType, cyrsasl_digest) -> not mongoose_scram:enabled(HostType);
59
:-(
supports_sasl_module(HostType, Mechanism) -> mongoose_scram:enabled(HostType, Mechanism).
60
61 -spec authorize(mongoose_credentials:t()) -> {ok, mongoose_credentials:t()}
62 | {error, any()}.
63 authorize(Creds) ->
64
:-(
case mongoose_credentials:get(Creds, der_cert, undefined) of
65
:-(
undefined -> ejabberd_auth:authorize_with_check_password(?MODULE, Creds);
66 DerCert ->
67
:-(
case verify_cert(Creds, DerCert) of
68
:-(
true -> {ok, mongoose_credentials:set(Creds, auth_module, ?MODULE)};
69
:-(
false -> {error, not_authorized}
70 end
71 end.
72
73
74 -spec check_password(mongooseim:host_type(), jid:luser(), jid:lserver(), binary()) -> boolean().
75 check_password(HostType, LUser, LServer, Password) ->
76
:-(
Params = #{luser => LUser, lserver => LServer, host_type => HostType, password => Password},
77
:-(
case mongoose_scram:enabled(HostType) of
78 false ->
79
:-(
case make_req(get, <<"check_password">>, Params) of
80
:-(
{ok, <<"true">>} -> true;
81
:-(
_ -> false
82 end;
83 true ->
84
:-(
{ok, true} =:= verify_scram_password(Params)
85 end.
86
87 -spec check_password(mongooseim:host_type(), jid:luser(), jid:lserver(),
88 binary(), binary(), fun()) -> boolean().
89 check_password(HostType, LUser, LServer, Password, Digest, DigestGen) ->
90
:-(
case make_req(get, <<"get_password">>, #{luser => LUser,
91 lserver => LServer,
92 host_type => HostType}) of
93 {error, _} ->
94
:-(
false;
95 {ok, GotPasswd} ->
96
:-(
case mongoose_scram:enabled(HostType) of
97 true ->
98
:-(
check_scram_password(GotPasswd, Password, Digest, DigestGen);
99 false ->
100
:-(
ejabberd_auth:check_digest(Digest, DigestGen, Password, GotPasswd)
101 end
102 end.
103
104 -spec set_password(mongooseim:host_type(), jid:luser(), jid:lserver(),
105 binary()) -> ok | {error, not_allowed}.
106 set_password(HostType, LUser, LServer, Password) ->
107
:-(
PasswordFinal = case mongoose_scram:enabled(HostType) of
108
:-(
true -> mongoose_scram:serialize(mongoose_scram:password_to_scram(HostType,
109 Password, mongoose_scram:iterations(HostType)));
110
:-(
false -> Password
111 end,
112
:-(
case make_req(post, <<"set_password">>, #{luser => LUser,
113 lserver => LServer,
114 host_type => HostType,
115 password => PasswordFinal}) of
116
:-(
{ok, _} -> ok;
117
:-(
{error, invalid_jid} = Error -> Error;
118
:-(
{error, _} -> {error, not_allowed}
119 end.
120
121 -spec try_register(mongooseim:host_type(), jid:luser(), jid:lserver(), binary()) ->
122 ok | {error, exists | not_allowed}.
123 try_register(HostType, LUser, LServer, Password) ->
124
:-(
PasswordFinal = case mongoose_scram:enabled(HostType) of
125
:-(
true -> mongoose_scram:serialize(mongoose_scram:password_to_scram(HostType,
126 Password, mongoose_scram:iterations(HostType)));
127
:-(
false -> Password
128 end,
129
:-(
case make_req(post, <<"register">>, #{luser => LUser,
130 lserver => LServer,
131 host_type => HostType,
132 password => PasswordFinal}) of
133
:-(
{ok, created} -> ok;
134
:-(
{error, conflict} -> {error, exists};
135
:-(
_Error -> {error, not_allowed}
136 end.
137
138 -spec get_password(mongooseim:host_type(), jid:luser(), jid:lserver()) ->
139 ejabberd_auth:passterm() | false.
140 get_password(HostType, LUser, LServer) ->
141
:-(
case make_req(get, <<"get_password">>, #{luser => LUser,
142 lserver => LServer,
143 host_type => HostType}) of
144 {error, _} ->
145
:-(
false;
146 {ok, Password} ->
147
:-(
case mongoose_scram:enabled(HostType) of
148 true ->
149
:-(
convert_scram_to_tuple(Password);
150 false ->
151
:-(
Password
152 end
153 end.
154
155 -spec get_password_s(mongooseim:host_type(), jid:luser(), jid:lserver()) -> binary().
156 get_password_s(HostType, User, Server) ->
157
:-(
case get_password(HostType, User, Server) of
158
:-(
Pass when is_binary(Pass) -> Pass;
159
:-(
_ -> <<>>
160 end.
161
162 -spec does_user_exist(mongooseim:host_type(), jid:luser(), jid:lserver()) -> boolean().
163 does_user_exist(HostType, LUser, LServer) ->
164
:-(
case make_req(get, <<"user_exists">>, #{luser => LUser,
165 lserver => LServer,
166 host_type => HostType}) of
167
:-(
{ok, <<"true">>} -> true;
168
:-(
_ -> false
169 end.
170
171 -spec remove_user(mongooseim:host_type(), jid:luser(), jid:lserver()) ->
172 ok | {error, not_allowed}.
173 remove_user(HostType, LUser, LServer) ->
174
:-(
case make_req(post, <<"remove_user">>, #{luser => LUser,
175 lserver => LServer,
176 host_type => HostType}) of
177
:-(
{ok, _} -> ok;
178
:-(
{error, _} -> {error, not_allowed}
179 end.
180
181 -spec supported_features() -> [atom()].
182
:-(
supported_features() -> [dynamic_domains].
183
184 %%%----------------------------------------------------------------------
185 %%% Request maker
186 %%%----------------------------------------------------------------------
187
188 -spec make_req(post | get, binary(), params()) ->
189 {ok, BodyOrCreated :: binary() | created} | {error, invalid_jid
190 | http_error_atom() | binary()}.
191 make_req(_, _, #{luser := LUser, lserver := LServer}) when LUser == error orelse LServer == error ->
192
:-(
{error, invalid_jid};
193 make_req(Method, Path, Params = #{luser := LUser, lserver := LServer, host_type := HostType}) ->
194
:-(
Password = maps:get(password, Params, <<>>),
195
:-(
Query = uri_string:compose_query([{<<"user">>, LUser},
196 {<<"server">>, LServer},
197 {<<"pass">>, Password}]),
198
:-(
Header = case mongoose_config:lookup_opt([{auth, HostType}, http, basic_auth]) of
199 {error, not_found} ->
200
:-(
[];
201 {ok, BasicAuth} ->
202
:-(
BasicAuth64 = base64:encode(BasicAuth),
203
:-(
[{<<"Authorization">>, <<"Basic ", BasicAuth64/binary>>}]
204 end,
205
206
:-(
Result = case Method of
207
:-(
get -> mongoose_http_client:get(HostType, auth, <<Path/binary, "?", Query/binary>>, Header);
208
:-(
post -> mongoose_http_client:post(HostType, auth, Path, Header, Query)
209 end,
210
:-(
handle_result(Result, Method, Path, LUser, HostType).
211
212 handle_result({ok, {Code, RespBody}}, Method, Path, LUser, HostType) ->
213
:-(
?LOG_DEBUG(#{what => http_auth_request, text => <<"Received HTTP request result">>,
214 path => Path, method => Method, user => LUser, host_type => HostType,
215
:-(
code => Code, result => RespBody}),
216
:-(
case Code of
217
:-(
<<"409">> -> {error, conflict};
218
:-(
<<"404">> -> {error, not_found};
219
:-(
<<"401">> -> {error, not_authorized};
220
:-(
<<"403">> -> {error, not_allowed};
221
:-(
<<"400">> -> {error, RespBody};
222
:-(
<<"204">> -> {ok, <<>>};
223
:-(
<<"201">> -> {ok, created};
224
:-(
<<"200">> -> {ok, RespBody}
225 end;
226 handle_result({error, Reason}, Method, Path, LUser, HostType) ->
227
:-(
?LOG_DEBUG(#{what => http_auth_request_failed, text => <<"HTTP request failed">>,
228 path => Path, method => Method, user => LUser, host_type => HostType,
229
:-(
reason => Reason}),
230
:-(
{error, bad_request}.
231
232 %%%----------------------------------------------------------------------
233 %%% Other internal functions
234 %%%----------------------------------------------------------------------
235
236 -spec verify_scram_password(params()) -> {ok, boolean()} | {error, bad_request | not_exists}.
237 verify_scram_password(Params) ->
238
:-(
{Password, ParamsWithoutPass} = maps:take(password, Params),
239
:-(
case make_req(get, <<"get_password">>, ParamsWithoutPass) of
240 {ok, RawPassword} ->
241
:-(
case mongoose_scram:deserialize(RawPassword) of
242 {ok, DeserializedScramMap} ->
243
:-(
{ok, mongoose_scram:check_password(Password, DeserializedScramMap)};
244 {error, Reason} ->
245
:-(
?LOG_WARNING(#{what => scram_serialisation_incorrect, reason => Reason,
246
:-(
params => Params}),
247
:-(
{error, bad_request}
248 end;
249 _ ->
250
:-(
{error, not_exists}
251 end.
252
253 verify_cert(Creds, DerCert) ->
254
:-(
Params = creds_to_params(Creds),
255
:-(
case make_req(get, <<"get_certs">>, Params) of
256 {ok, PemCert} ->
257
:-(
UserCert = {'Certificate', DerCert, not_encrypted},
258
:-(
[] =/= [Cert || Cert <- public_key:pem_decode(PemCert), Cert =:= UserCert];
259
:-(
_ -> false
260 end.
261
262 creds_to_params(Creds) ->
263
:-(
User = mongoose_credentials:get(Creds, username),
264
:-(
LUser = jid:nodeprep(User),
265
:-(
LUser == error andalso error({nodeprep_error, User}),
266
:-(
HostType = mongoose_credentials:host_type(Creds),
267
:-(
LServer = mongoose_credentials:lserver(Creds),
268
:-(
#{host_type => HostType, luser => LUser, lserver => LServer}.
269
270 -spec check_scram_password(binary(), binary(), binary(), fun()) -> boolean().
271 check_scram_password(OriginalPassword, GotPassword, Digest, DigestGen) ->
272
:-(
case mongoose_scram:deserialize(GotPassword) of
273 {ok, Scram} ->
274
:-(
mongoose_scram:check_digest(Scram, Digest, DigestGen, OriginalPassword);
275 _ ->
276
:-(
false
277 end.
278
279 -spec convert_scram_to_tuple(binary()) -> ejabberd_auth:passterm() | false.
280 convert_scram_to_tuple(Password) ->
281
:-(
case mongoose_scram:deserialize(Password) of
282 {ok, Scram} ->
283
:-(
Scram;
284 _ ->
285
:-(
false
286 end.
Line Hits Source