mirror of
https://github.com/processone/ejabberd.git
synced 2024-06-28 22:52:27 +02:00
CAPTCHA IBR support (EJAB-1262)(thanks to Evgeniy Khramtsov)
This commit is contained in:
parent
81546f3270
commit
f310292da4
|
@ -52,7 +52,8 @@
|
||||||
terminate/2, code_change/3]).
|
terminate/2, code_change/3]).
|
||||||
|
|
||||||
-export([create_captcha/5, build_captcha_html/2, check_captcha/2,
|
-export([create_captcha/5, 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_lib("exmpp/include/exmpp.hrl").
|
-include_lib("exmpp/include/exmpp.hrl").
|
||||||
|
|
||||||
|
@ -213,6 +214,85 @@ create_captcha(SID, From, To, Lang, Args)
|
||||||
error
|
error
|
||||||
end.
|
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() ++ "-" ++ ejabberd_cluster:node_id(),
|
||||||
|
B64Image = list_to_binary(jlib:encode_base64(binary_to_list(Image))),
|
||||||
|
CID = list_to_binary(["sha1+", sha:sha(Image), "@bob.xmpp.org"]),
|
||||||
|
Data =
|
||||||
|
#xmlel{
|
||||||
|
name = 'data',
|
||||||
|
ns = ?NS_BOB,
|
||||||
|
attrs = [
|
||||||
|
#xmlattr{name = 'cid',
|
||||||
|
value = CID
|
||||||
|
},
|
||||||
|
#xmlattr{name = 'max-age',
|
||||||
|
value = <<"0">>
|
||||||
|
},
|
||||||
|
#xmlattr{name = 'type',
|
||||||
|
value = Type
|
||||||
|
}
|
||||||
|
],
|
||||||
|
children = [#xmlcdata{cdata = B64Image}]},
|
||||||
|
Captcha =
|
||||||
|
#xmlel{name = 'x',
|
||||||
|
ns = ?NS_DATA_FORMS_s,
|
||||||
|
attrs = [
|
||||||
|
#xmlattr{name = 'type',
|
||||||
|
value = <<"form">>
|
||||||
|
}
|
||||||
|
],
|
||||||
|
children = [
|
||||||
|
?VFIELD(<<"hidden">>, <<"FORM_TYPE">>, #xmlcdata{cdata = ?NS_CAPTCHA_b}) | HeadEls] ++ [
|
||||||
|
?VFIELD(<<"hidden">>, <<"from">>, #xmlcdata{cdata = exmpp_jid:to_binary(To)}),
|
||||||
|
?VFIELD(<<"hidden">>, <<"challenge">>, #xmlcdata{cdata = list_to_binary(Id)}),
|
||||||
|
?VFIELD(<<"hidden">>, <<"sid">>, #xmlcdata{cdata = SID}),
|
||||||
|
#xmlel{name = 'field',
|
||||||
|
attrs = [
|
||||||
|
#xmlattr{name = 'var',
|
||||||
|
value = <<"ocr">>
|
||||||
|
},
|
||||||
|
#xmlattr{name = 'label',
|
||||||
|
value = ?CAPTCHA_TEXT(Lang)
|
||||||
|
}
|
||||||
|
],
|
||||||
|
children = [
|
||||||
|
#xmlel{name = 'media',
|
||||||
|
ns = ?NS_DATA_FORMS_MEDIA_s,
|
||||||
|
children = [
|
||||||
|
#xmlel{name = 'uri',
|
||||||
|
attrs = [
|
||||||
|
#xmlattr{name = 'type',
|
||||||
|
value = Type
|
||||||
|
}
|
||||||
|
],
|
||||||
|
children = [
|
||||||
|
#xmlcdata{cdata = list_to_binary(["cid:", CID])}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
] ++ TailEls
|
||||||
|
},
|
||||||
|
Tref = erlang:send_after(?CAPTCHA_LIFETIME, ?MODULE, {remove_id, Id}),
|
||||||
|
case ets:insert(captcha, #captcha{id=Id, pid=self(), key=Key,
|
||||||
|
tref=Tref}) of
|
||||||
|
true ->
|
||||||
|
{ok, [Captcha, Data]};
|
||||||
|
_Err ->
|
||||||
|
error
|
||||||
|
end;
|
||||||
|
_ ->
|
||||||
|
error
|
||||||
|
end.
|
||||||
|
|
||||||
%% @spec (Id::string(), Lang::string()) -> {FormEl, {ImgEl, TextEl, IdEl, KeyEl}} | captcha_not_found
|
%% @spec (Id::string(), Lang::string()) -> {FormEl, {ImgEl, TextEl, IdEl, KeyEl}} | captcha_not_found
|
||||||
%% where FormEl = xmlelement()
|
%% where FormEl = xmlelement()
|
||||||
%% ImgEl = xmlelement()
|
%% ImgEl = xmlelement()
|
||||||
|
@ -348,16 +428,13 @@ check_captcha(Id, ProvidedKey) ->
|
||||||
end.
|
end.
|
||||||
|
|
||||||
process_reply(El) ->
|
process_reply(El) ->
|
||||||
case {exmpp_xml:element_matches(El, captcha),
|
case exmpp_xml:get_element(El, x) of
|
||||||
exmpp_xml:get_element(El, x)} of
|
undefined ->
|
||||||
{false, _} ->
|
|
||||||
{error, malformed};
|
{error, malformed};
|
||||||
{_, undefined} ->
|
Xdata ->
|
||||||
{error, malformed};
|
|
||||||
{true, Xdata} ->
|
|
||||||
Fields = jlib:parse_xdata_submit(Xdata),
|
Fields = jlib:parse_xdata_submit(Xdata),
|
||||||
case {proplists:get_value("challenge", Fields),
|
case catch {proplists:get_value("challenge", Fields),
|
||||||
proplists:get_value("ocr", Fields)} of
|
proplists:get_value("ocr", Fields)} of
|
||||||
{[Id|_], [OCR|_]} ->
|
{[Id|_], [OCR|_]} ->
|
||||||
case check_captcha(Id, OCR) of
|
case check_captcha(Id, OCR) of
|
||||||
captcha_valid ->
|
captcha_valid ->
|
||||||
|
@ -452,7 +529,11 @@ handle_info({remove_id, Id}, State) ->
|
||||||
?DEBUG("captcha ~p timed out", [Id]),
|
?DEBUG("captcha ~p timed out", [Id]),
|
||||||
case ets:lookup(captcha, Id) of
|
case ets:lookup(captcha, Id) of
|
||||||
[#captcha{args=Args, pid=Pid}] ->
|
[#captcha{args=Args, pid=Pid}] ->
|
||||||
Pid ! {captcha_failed, Args},
|
if is_pid(Pid) ->
|
||||||
|
Pid ! {captcha_failed, Args};
|
||||||
|
true ->
|
||||||
|
ok
|
||||||
|
end,
|
||||||
ets:delete(captcha, Id);
|
ets:delete(captcha, Id);
|
||||||
_ ->
|
_ ->
|
||||||
ok
|
ok
|
||||||
|
@ -632,10 +713,18 @@ do_check_captcha(Id, ProvidedKey) ->
|
||||||
ets:delete(captcha, Id),
|
ets:delete(captcha, Id),
|
||||||
erlang:cancel_timer(Tref),
|
erlang:cancel_timer(Tref),
|
||||||
if ValidKey == ProvidedKey ->
|
if ValidKey == ProvidedKey ->
|
||||||
Pid ! {captcha_succeed, Args},
|
if is_pid(Pid) ->
|
||||||
|
Pid ! {captcha_succeed, Args};
|
||||||
|
true ->
|
||||||
|
ok
|
||||||
|
end,
|
||||||
captcha_valid;
|
captcha_valid;
|
||||||
true ->
|
true ->
|
||||||
Pid ! {captcha_failed, Args},
|
if is_pid(Pid) ->
|
||||||
|
Pid ! {captcha_failed, Args};
|
||||||
|
true ->
|
||||||
|
ok
|
||||||
|
end,
|
||||||
captcha_non_valid
|
captcha_non_valid
|
||||||
end;
|
end;
|
||||||
_ ->
|
_ ->
|
||||||
|
|
|
@ -93,8 +93,15 @@ process_iq(From, To, IQ) ->
|
||||||
exmpp_jid:prep_resource_as_list(From)}).
|
exmpp_jid:prep_resource_as_list(From)}).
|
||||||
|
|
||||||
process_iq(From, To,
|
process_iq(From, To,
|
||||||
#iq{type = Type, lang = Lang, payload = SubEl} = IQ_Rec,
|
#iq{type = Type, lang = Lang, payload = SubEl, id = ID} = IQ_Rec,
|
||||||
Source) ->
|
Source) ->
|
||||||
|
IsCaptchaEnabled = case gen_mod:get_module_opt(
|
||||||
|
exmpp_jid:domain(To), ?MODULE, captcha, false) of
|
||||||
|
true ->
|
||||||
|
true;
|
||||||
|
_ ->
|
||||||
|
false
|
||||||
|
end,
|
||||||
case Type of
|
case Type of
|
||||||
set ->
|
set ->
|
||||||
UTag = exmpp_xml:get_element(SubEl, 'username'),
|
UTag = exmpp_xml:get_element(SubEl, 'username'),
|
||||||
|
@ -159,40 +166,77 @@ process_iq(From, To,
|
||||||
(UTag /= undefined) and (PTag /= undefined) ->
|
(UTag /= undefined) and (PTag /= undefined) ->
|
||||||
User = exmpp_xml:get_cdata_as_list(UTag),
|
User = exmpp_xml:get_cdata_as_list(UTag),
|
||||||
Password = exmpp_xml:get_cdata_as_list(PTag),
|
Password = exmpp_xml:get_cdata_as_list(PTag),
|
||||||
case {exmpp_jid:node_as_list(From), exmpp_jid:prep_domain_as_list(From)} of
|
try_register_or_set_password(
|
||||||
{User, Server} ->
|
User, Server, Password, From,
|
||||||
try_set_password(User, Server, Password, IQ_Rec, SubEl);
|
IQ_Rec, 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_Rec, SubEl, Source, Lang, true);
|
||||||
|
_ ->
|
||||||
|
exmpp_iq:error(IQ_Rec, 'bad-request')
|
||||||
|
end;
|
||||||
|
{error, malformed} ->
|
||||||
|
exmpp_iq:error(IQ_Rec, 'bad-request');
|
||||||
_ ->
|
_ ->
|
||||||
case check_from(From, Server) of
|
%% ErrText = "Captcha test failed",
|
||||||
allow ->
|
exmpp_iq:error(IQ_Rec, 'not-allowed')
|
||||||
case try_register(User, Server, Password,
|
end;
|
||||||
Source, Lang) of
|
|
||||||
ok ->
|
|
||||||
exmpp_iq:result(IQ_Rec, SubEl);
|
|
||||||
{error, Error} ->
|
|
||||||
exmpp_iq:error(IQ_Rec, Error)
|
|
||||||
end;
|
|
||||||
deny ->
|
|
||||||
exmpp_iq:error(IQ_Rec, 'forbidden')
|
|
||||||
end
|
|
||||||
end;
|
|
||||||
true ->
|
true ->
|
||||||
exmpp_iq:error(IQ_Rec, 'bad-request')
|
exmpp_iq:error(IQ_Rec, 'bad-request')
|
||||||
end;
|
end;
|
||||||
get ->
|
get ->
|
||||||
{UsernameSubels, QuerySubels} =
|
{IsRegistered, UsernameSubels, QuerySubels} =
|
||||||
case {exmpp_jid:node_as_list(From), exmpp_jid:prep_domain_as_list(From)} of
|
case {exmpp_jid:node_as_list(From), exmpp_jid:prep_domain_as_list(From)} of
|
||||||
{User, Server} when is_list(User) and is_list(Server) ->
|
{User, Server} when is_list(User) and is_list(Server) ->
|
||||||
case ejabberd_auth:is_user_exists(User,Server) of
|
case ejabberd_auth:is_user_exists(User,Server) of
|
||||||
true ->
|
true ->
|
||||||
{[#xmlcdata{cdata = list_to_binary(User)}],
|
{true, [#xmlcdata{cdata = list_to_binary(User)}],
|
||||||
[#xmlel{ns = ?NS_INBAND_REGISTER, name = 'registered'}]};
|
[#xmlel{ns = ?NS_INBAND_REGISTER, name = 'registered'}]};
|
||||||
false ->
|
false ->
|
||||||
{[#xmlcdata{cdata = list_to_binary(User)}], []}
|
{false, [#xmlcdata{cdata = list_to_binary(User)}], []}
|
||||||
end;
|
end;
|
||||||
_ ->
|
_ ->
|
||||||
{[], []}
|
{false, [], []}
|
||||||
end,
|
end,
|
||||||
|
if IsCaptchaEnabled and not IsRegistered ->
|
||||||
|
InstrEl =
|
||||||
|
#xmlel{ns = ?NS_INBAND_REGISTER, name = 'instructions',
|
||||||
|
children =
|
||||||
|
[#xmlcdata{cdata =
|
||||||
|
list_to_binary(
|
||||||
|
translate:translate(Lang,
|
||||||
|
"Choose a username and password "
|
||||||
|
"to register with this server"))}]},
|
||||||
|
UField = #xmlel{ns = ?NS_DATA_FORMS, name = 'field', attrs =
|
||||||
|
[?XMLATTR('var', <<"username">>),
|
||||||
|
?XMLATTR('type', <<"text-single">>),
|
||||||
|
?XMLATTR('label', translate:translate(Lang, "User"))],
|
||||||
|
children =
|
||||||
|
[#xmlel{ns = ?NS_DATA_FORMS, name = 'required'}]},
|
||||||
|
PField =
|
||||||
|
#xmlel{ns = ?NS_DATA_FORMS, name = 'field', attrs =
|
||||||
|
[?XMLATTR('var', <<"password">>),
|
||||||
|
?XMLATTR('type', <<"text-private">>),
|
||||||
|
?XMLATTR('label', translate:translate(Lang, "Password"))],
|
||||||
|
children = [
|
||||||
|
#xmlel{ns = ?NS_DATA_FORMS, name = 'required'}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
case ejabberd_captcha:create_captcha_x(
|
||||||
|
ID, To, Lang, [InstrEl, UField, PField]) of
|
||||||
|
{ok, CaptchaEls} ->
|
||||||
|
Result = #xmlel{ns = ?NS_INBAND_REGISTER, name = 'query', children = CaptchaEls},
|
||||||
|
exmpp_iq:result(IQ_Rec, Result);
|
||||||
|
error ->
|
||||||
|
%% ErrText = "Unable to generate a captcha",
|
||||||
|
exmpp_iq:error(IQ_Rec, 'internal-server-error')
|
||||||
|
end;
|
||||||
|
true ->
|
||||||
Result = #xmlel{ns = ?NS_INBAND_REGISTER, name = 'query', children =
|
Result = #xmlel{ns = ?NS_INBAND_REGISTER, name = 'query', children =
|
||||||
[#xmlel{ns = ?NS_INBAND_REGISTER, name = 'instructions', children =
|
[#xmlel{ns = ?NS_INBAND_REGISTER, name = 'instructions', children =
|
||||||
[#xmlcdata{cdata = list_to_binary(
|
[#xmlcdata{cdata = list_to_binary(
|
||||||
|
@ -203,6 +247,29 @@ process_iq(From, To,
|
||||||
#xmlel{ns = ?NS_INBAND_REGISTER, name = 'password'}
|
#xmlel{ns = ?NS_INBAND_REGISTER, name = 'password'}
|
||||||
| QuerySubels]},
|
| QuerySubels]},
|
||||||
exmpp_iq:result(IQ_Rec, Result)
|
exmpp_iq:result(IQ_Rec, Result)
|
||||||
|
end
|
||||||
|
end.
|
||||||
|
|
||||||
|
try_register_or_set_password(User, Server, Password, From, IQ_Rec,
|
||||||
|
SubEl, Source, Lang, CaptchaSucceed) ->
|
||||||
|
case {exmpp_jid:node_as_list(From), exmpp_jid:prep_domain_as_list(From)} of
|
||||||
|
{User, Server} ->
|
||||||
|
try_set_password(User, Server, Password, IQ_Rec, SubEl);
|
||||||
|
_ when CaptchaSucceed ->
|
||||||
|
case check_from(From, Server) of
|
||||||
|
allow ->
|
||||||
|
case try_register(User, Server, Password,
|
||||||
|
Source, Lang) of
|
||||||
|
ok ->
|
||||||
|
exmpp_iq:result(IQ_Rec, SubEl);
|
||||||
|
{error, Error} ->
|
||||||
|
exmpp_iq:error(IQ_Rec, Error)
|
||||||
|
end;
|
||||||
|
deny ->
|
||||||
|
exmpp_iq:error(IQ_Rec, 'forbidden')
|
||||||
|
end;
|
||||||
|
_ ->
|
||||||
|
exmpp_iq:error(IQ_Rec, 'not-allowed')
|
||||||
end.
|
end.
|
||||||
|
|
||||||
%% @doc Try to change password and return IQ response
|
%% @doc Try to change password and return IQ response
|
||||||
|
@ -410,3 +477,18 @@ get_time_string() -> write_time(erlang:localtime()).
|
||||||
write_time({{Y,Mo,D},{H,Mi,S}}) ->
|
write_time({{Y,Mo,D},{H,Mi,S}}) ->
|
||||||
io_lib:format("~w-~.2.0w-~.2.0w ~.2.0w:~.2.0w:~.2.0w",
|
io_lib:format("~w-~.2.0w-~.2.0w ~.2.0w:~.2.0w:~.2.0w",
|
||||||
[Y, Mo, D, H, Mi, S]).
|
[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