mirror of
https://github.com/processone/ejabberd.git
synced 2024-10-09 15:06:54 +02:00
CAPTCHA IBR support (EJAB-1262)
This commit is contained in:
parent
91cf9194d8
commit
f4beeb1706
@ -36,7 +36,8 @@
|
||||
terminate/2, code_change/3]).
|
||||
|
||||
-export([create_captcha/6, build_captcha_html/2, check_captcha/2,
|
||||
process_reply/1, process/2, is_feature_available/0]).
|
||||
process_reply/1, process/2, is_feature_available/0,
|
||||
create_captcha_x/4, create_captcha_x/5]).
|
||||
|
||||
-include("jlib.hrl").
|
||||
-include("ejabberd.hrl").
|
||||
@ -112,6 +113,40 @@ create_captcha(Id, SID, From, To, Lang, Args)
|
||||
error
|
||||
end.
|
||||
|
||||
create_captcha_x(SID, To, Lang, HeadEls) ->
|
||||
create_captcha_x(SID, To, Lang, HeadEls, []).
|
||||
|
||||
create_captcha_x(SID, To, Lang, HeadEls, TailEls) ->
|
||||
case create_image() of
|
||||
{ok, Type, Key, Image} ->
|
||||
Id = randoms:get_string(),
|
||||
B64Image = jlib:encode_base64(binary_to_list(Image)),
|
||||
CID = "sha1+" ++ sha:sha(Image) ++ "@bob.xmpp.org",
|
||||
Data = {xmlelement, "data",
|
||||
[{"xmlns", ?NS_BOB}, {"cid", CID},
|
||||
{"max-age", "0"}, {"type", Type}],
|
||||
[{xmlcdata, B64Image}]},
|
||||
Captcha =
|
||||
{xmlelement, "x", [{"xmlns", ?NS_XDATA}, {"type", "form"}],
|
||||
[?VFIELD("hidden", "FORM_TYPE", {xmlcdata, ?NS_CAPTCHA}) | HeadEls] ++
|
||||
[?VFIELD("hidden", "from", {xmlcdata, jlib:jid_to_string(To)}),
|
||||
?VFIELD("hidden", "challenge", {xmlcdata, Id}),
|
||||
?VFIELD("hidden", "sid", {xmlcdata, SID}),
|
||||
{xmlelement, "field", [{"var", "ocr"}, {"label", ?CAPTCHA_TEXT(Lang)}],
|
||||
[{xmlelement, "media", [{"xmlns", ?NS_MEDIA}],
|
||||
[{xmlelement, "uri", [{"type", Type}],
|
||||
[{xmlcdata, "cid:" ++ CID}]}]}]}] ++ TailEls},
|
||||
Tref = erlang:send_after(?CAPTCHA_LIFETIME, ?MODULE, {remove_id, Id}),
|
||||
case ?T(mnesia:write(#captcha{id=Id, key=Key, tref=Tref})) of
|
||||
ok ->
|
||||
{ok, [Captcha, Data]};
|
||||
_Err ->
|
||||
error
|
||||
end;
|
||||
_ ->
|
||||
error
|
||||
end.
|
||||
|
||||
%% @spec (Id::string(), Lang::string()) -> {FormEl, {ImgEl, TextEl, IdEl, KeyEl}} | captcha_not_found
|
||||
%% where FormEl = xmlelement()
|
||||
%% ImgEl = xmlelement()
|
||||
@ -155,10 +190,18 @@ check_captcha(Id, ProvidedKey) ->
|
||||
mnesia:delete({captcha, Id}),
|
||||
erlang:cancel_timer(Tref),
|
||||
if StoredKey == ProvidedKey ->
|
||||
Pid ! {captcha_succeed, Args},
|
||||
if is_pid(Pid) ->
|
||||
Pid ! {captcha_succeed, Args};
|
||||
true ->
|
||||
ok
|
||||
end,
|
||||
captcha_valid;
|
||||
true ->
|
||||
Pid ! {captcha_failed, Args},
|
||||
if is_pid(Pid) ->
|
||||
Pid ! {captcha_failed, Args};
|
||||
true ->
|
||||
ok
|
||||
end,
|
||||
captcha_non_valid
|
||||
end;
|
||||
_ ->
|
||||
@ -166,24 +209,32 @@ check_captcha(Id, ProvidedKey) ->
|
||||
end).
|
||||
|
||||
|
||||
process_reply({xmlelement, "captcha", _, _} = El) ->
|
||||
process_reply({xmlelement, _, _, _} = El) ->
|
||||
case xml:get_subtag(El, "x") of
|
||||
false ->
|
||||
{error, malformed};
|
||||
Xdata ->
|
||||
Fields = jlib:parse_xdata_submit(Xdata),
|
||||
case {proplists:get_value("challenge", Fields),
|
||||
proplists:get_value("ocr", Fields)} of
|
||||
case catch {proplists:get_value("challenge", Fields),
|
||||
proplists:get_value("ocr", Fields)} of
|
||||
{[Id|_], [OCR|_]} ->
|
||||
?T(case mnesia:read(captcha, Id, write) of
|
||||
[#captcha{pid=Pid, args=Args, key=Key, tref=Tref}] ->
|
||||
mnesia:delete({captcha, Id}),
|
||||
erlang:cancel_timer(Tref),
|
||||
if OCR == Key ->
|
||||
Pid ! {captcha_succeed, Args},
|
||||
if is_pid(Pid) ->
|
||||
Pid ! {captcha_succeed, Args};
|
||||
true ->
|
||||
ok
|
||||
end,
|
||||
ok;
|
||||
true ->
|
||||
Pid ! {captcha_failed, Args},
|
||||
if is_pid(Pid) ->
|
||||
Pid ! {captcha_failed, Args};
|
||||
true ->
|
||||
ok
|
||||
end,
|
||||
{error, bad_match}
|
||||
end;
|
||||
_ ->
|
||||
@ -266,7 +317,11 @@ handle_info({remove_id, Id}, State) ->
|
||||
?DEBUG("captcha ~p timed out", [Id]),
|
||||
_ = ?T(case mnesia:read(captcha, Id, write) of
|
||||
[#captcha{args=Args, pid=Pid}] ->
|
||||
Pid ! {captcha_failed, Args},
|
||||
if is_pid(Pid) ->
|
||||
Pid ! {captcha_failed, Args};
|
||||
true ->
|
||||
ok
|
||||
end,
|
||||
mnesia:delete({captcha, Id});
|
||||
_ ->
|
||||
ok
|
||||
|
@ -93,6 +93,13 @@ process_iq(From, To, IQ) ->
|
||||
process_iq(From, To,
|
||||
#iq{type = Type, lang = Lang, sub_el = SubEl, id = ID} = IQ,
|
||||
Source) ->
|
||||
IsCaptchaEnabled = case gen_mod:get_module_opt(
|
||||
To#jid.lserver, ?MODULE, captcha, false) of
|
||||
true ->
|
||||
true;
|
||||
_ ->
|
||||
false
|
||||
end,
|
||||
case Type of
|
||||
set ->
|
||||
UTag = xml:get_subtag(SubEl, "username"),
|
||||
@ -162,56 +169,119 @@ process_iq(From, To,
|
||||
(UTag /= false) and (PTag /= false) ->
|
||||
User = xml:get_tag_cdata(UTag),
|
||||
Password = xml:get_tag_cdata(PTag),
|
||||
case From of
|
||||
#jid{user = User, lserver = Server} ->
|
||||
try_set_password(User, Server, Password, IQ, SubEl);
|
||||
_ ->
|
||||
case check_from(From, Server) of
|
||||
allow ->
|
||||
case try_register(User, Server, Password,
|
||||
Source, Lang) of
|
||||
ok ->
|
||||
IQ#iq{type = result,
|
||||
sub_el = [SubEl]};
|
||||
{error, Error} ->
|
||||
IQ#iq{type = error,
|
||||
sub_el = [SubEl, Error]}
|
||||
end;
|
||||
deny ->
|
||||
try_register_or_set_password(
|
||||
User, Server, Password, From,
|
||||
IQ, SubEl, Source, Lang, not IsCaptchaEnabled);
|
||||
IsCaptchaEnabled ->
|
||||
case ejabberd_captcha:process_reply(SubEl) of
|
||||
ok ->
|
||||
case process_xdata_submit(SubEl) of
|
||||
{ok, User, Password} ->
|
||||
try_register_or_set_password(
|
||||
User, Server, Password, From,
|
||||
IQ, SubEl, Source, Lang, true);
|
||||
_ ->
|
||||
IQ#iq{type = error,
|
||||
sub_el = [SubEl, ?ERR_FORBIDDEN]}
|
||||
end
|
||||
sub_el = [SubEl, ?ERR_BAD_REQUEST]}
|
||||
end;
|
||||
{error, malformed} ->
|
||||
IQ#iq{type = error,
|
||||
sub_el = [SubEl, ?ERR_BAD_REQUEST]};
|
||||
_ ->
|
||||
ErrText = "Captcha test failed",
|
||||
IQ#iq{type = error,
|
||||
sub_el = [SubEl,
|
||||
?ERRT_NOT_ALLOWED(Lang, ErrText)]}
|
||||
end;
|
||||
true ->
|
||||
IQ#iq{type = error,
|
||||
sub_el = [SubEl, ?ERR_BAD_REQUEST]}
|
||||
end;
|
||||
get ->
|
||||
{UsernameSubels, QuerySubels} =
|
||||
{IsRegistered, UsernameSubels, QuerySubels} =
|
||||
case From of
|
||||
#jid{user = User, lserver = Server} ->
|
||||
case ejabberd_auth:is_user_exists(User,Server) of
|
||||
true ->
|
||||
{[{xmlcdata, User}], [{xmlelement, "registered", [], []}]};
|
||||
{true, [{xmlcdata, User}],
|
||||
[{xmlelement, "registered", [], []}]};
|
||||
false ->
|
||||
{[{xmlcdata, User}], []}
|
||||
{false, [{xmlcdata, User}], []}
|
||||
end;
|
||||
_ ->
|
||||
{[], []}
|
||||
{false, [], []}
|
||||
end,
|
||||
IQ#iq{type = result,
|
||||
sub_el = [{xmlelement,
|
||||
"query",
|
||||
[{"xmlns", "jabber:iq:register"}],
|
||||
[{xmlelement, "instructions", [],
|
||||
if IsCaptchaEnabled and not IsRegistered ->
|
||||
InstrEl = {xmlelement, "instructions", [],
|
||||
[{xmlcdata,
|
||||
translate:translate(
|
||||
Lang,
|
||||
"Choose a username and password "
|
||||
"to register with this server")}]},
|
||||
{xmlelement, "username", [], UsernameSubels},
|
||||
{xmlelement, "password", [], []}
|
||||
| QuerySubels]}]}
|
||||
UField = {xmlelement, "field",
|
||||
[{"type", "text-single"},
|
||||
{"label", translate:translate(Lang, "User")},
|
||||
{"var", "username"}],
|
||||
[{xmlelement, "required", [], []}]},
|
||||
PField = {xmlelement, "field",
|
||||
[{"type", "text-private"},
|
||||
{"label", translate:translate(Lang, "Password")},
|
||||
{"var", "password"}],
|
||||
[{xmlelement, "required", [], []}]},
|
||||
case ejabberd_captcha:create_captcha_x(
|
||||
ID, To, Lang, [InstrEl, UField, PField]) of
|
||||
{ok, CaptchaEls} ->
|
||||
IQ#iq{type = result,
|
||||
sub_el = [{xmlelement, "query",
|
||||
[{"xmlns", "jabber:iq:register"}],
|
||||
CaptchaEls}]};
|
||||
error ->
|
||||
ErrText = "Unable to generate a captcha",
|
||||
IQ#iq{type = error,
|
||||
sub_el = [SubEl, ?ERRT_INTERNAL_SERVER_ERROR(
|
||||
Lang, ErrText)]}
|
||||
end;
|
||||
true ->
|
||||
IQ#iq{type = result,
|
||||
sub_el = [{xmlelement,
|
||||
"query",
|
||||
[{"xmlns", "jabber:iq:register"}],
|
||||
[{xmlelement, "instructions", [],
|
||||
[{xmlcdata,
|
||||
translate:translate(
|
||||
Lang,
|
||||
"Choose a username and password "
|
||||
"to register with this server")}]},
|
||||
{xmlelement, "username", [], UsernameSubels},
|
||||
{xmlelement, "password", [], []}
|
||||
| QuerySubels]}]}
|
||||
end
|
||||
end.
|
||||
|
||||
try_register_or_set_password(User, Server, Password, From, IQ,
|
||||
SubEl, Source, Lang, CaptchaSucceed) ->
|
||||
case From of
|
||||
#jid{user = User, lserver = Server} ->
|
||||
try_set_password(User, Server, Password, IQ, SubEl);
|
||||
_ when CaptchaSucceed ->
|
||||
case check_from(From, Server) of
|
||||
allow ->
|
||||
case try_register(User, Server, Password,
|
||||
Source, Lang) of
|
||||
ok ->
|
||||
IQ#iq{type = result,
|
||||
sub_el = [SubEl]};
|
||||
{error, Error} ->
|
||||
IQ#iq{type = error,
|
||||
sub_el = [SubEl, Error]}
|
||||
end;
|
||||
deny ->
|
||||
IQ#iq{type = error,
|
||||
sub_el = [SubEl, ?ERR_FORBIDDEN]}
|
||||
end;
|
||||
_ ->
|
||||
IQ#iq{type = error,
|
||||
sub_el = [SubEl, ?ERR_NOT_ALLOWED]}
|
||||
end.
|
||||
|
||||
%% @doc Try to change password and return IQ response
|
||||
@ -417,3 +487,18 @@ get_time_string() -> write_time(erlang:localtime()).
|
||||
write_time({{Y,Mo,D},{H,Mi,S}}) ->
|
||||
io_lib:format("~w-~.2.0w-~.2.0w ~.2.0w:~.2.0w:~.2.0w",
|
||||
[Y, Mo, D, H, Mi, S]).
|
||||
|
||||
process_xdata_submit(El) ->
|
||||
case xml:get_subtag(El, "x") of
|
||||
false ->
|
||||
error;
|
||||
Xdata ->
|
||||
Fields = jlib:parse_xdata_submit(Xdata),
|
||||
case catch {proplists:get_value("username", Fields),
|
||||
proplists:get_value("password", Fields)} of
|
||||
{[User|_], [Pass|_]} ->
|
||||
{ok, User, Pass};
|
||||
_ ->
|
||||
error
|
||||
end
|
||||
end.
|
||||
|
Loading…
Reference in New Issue
Block a user