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