mirror of
https://github.com/processone/ejabberd.git
synced 2024-09-27 14:30:55 +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]).
|
||||
|
||||
-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").
|
||||
|
||||
@ -213,6 +214,85 @@ create_captcha(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() ++ "-" ++ 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
|
||||
%% where FormEl = xmlelement()
|
||||
%% ImgEl = xmlelement()
|
||||
@ -348,16 +428,13 @@ check_captcha(Id, ProvidedKey) ->
|
||||
end.
|
||||
|
||||
process_reply(El) ->
|
||||
case {exmpp_xml:element_matches(El, captcha),
|
||||
exmpp_xml:get_element(El, x)} of
|
||||
{false, _} ->
|
||||
case exmpp_xml:get_element(El, x) of
|
||||
undefined ->
|
||||
{error, malformed};
|
||||
{_, undefined} ->
|
||||
{error, malformed};
|
||||
{true, Xdata} ->
|
||||
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|_]} ->
|
||||
case check_captcha(Id, OCR) of
|
||||
captcha_valid ->
|
||||
@ -452,7 +529,11 @@ handle_info({remove_id, Id}, State) ->
|
||||
?DEBUG("captcha ~p timed out", [Id]),
|
||||
case ets:lookup(captcha, Id) of
|
||||
[#captcha{args=Args, pid=Pid}] ->
|
||||
Pid ! {captcha_failed, Args},
|
||||
if is_pid(Pid) ->
|
||||
Pid ! {captcha_failed, Args};
|
||||
true ->
|
||||
ok
|
||||
end,
|
||||
ets:delete(captcha, Id);
|
||||
_ ->
|
||||
ok
|
||||
@ -632,10 +713,18 @@ do_check_captcha(Id, ProvidedKey) ->
|
||||
ets:delete(captcha, Id),
|
||||
erlang:cancel_timer(Tref),
|
||||
if ValidKey == 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;
|
||||
_ ->
|
||||
|
@ -93,8 +93,15 @@ process_iq(From, To, IQ) ->
|
||||
exmpp_jid:prep_resource_as_list(From)}).
|
||||
|
||||
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) ->
|
||||
IsCaptchaEnabled = case gen_mod:get_module_opt(
|
||||
exmpp_jid:domain(To), ?MODULE, captcha, false) of
|
||||
true ->
|
||||
true;
|
||||
_ ->
|
||||
false
|
||||
end,
|
||||
case Type of
|
||||
set ->
|
||||
UTag = exmpp_xml:get_element(SubEl, 'username'),
|
||||
@ -159,40 +166,77 @@ process_iq(From, To,
|
||||
(UTag /= undefined) and (PTag /= undefined) ->
|
||||
User = exmpp_xml:get_cdata_as_list(UTag),
|
||||
Password = exmpp_xml:get_cdata_as_list(PTag),
|
||||
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);
|
||||
try_register_or_set_password(
|
||||
User, Server, Password, From,
|
||||
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
|
||||
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
|
||||
end;
|
||||
%% ErrText = "Captcha test failed",
|
||||
exmpp_iq:error(IQ_Rec, 'not-allowed')
|
||||
end;
|
||||
true ->
|
||||
exmpp_iq:error(IQ_Rec, 'bad-request')
|
||||
end;
|
||||
get ->
|
||||
{UsernameSubels, QuerySubels} =
|
||||
{IsRegistered, UsernameSubels, QuerySubels} =
|
||||
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) ->
|
||||
case ejabberd_auth:is_user_exists(User,Server) of
|
||||
true ->
|
||||
{[#xmlcdata{cdata = list_to_binary(User)}],
|
||||
{true, [#xmlcdata{cdata = list_to_binary(User)}],
|
||||
[#xmlel{ns = ?NS_INBAND_REGISTER, name = 'registered'}]};
|
||||
false ->
|
||||
{[#xmlcdata{cdata = list_to_binary(User)}], []}
|
||||
{false, [#xmlcdata{cdata = list_to_binary(User)}], []}
|
||||
end;
|
||||
_ ->
|
||||
{[], []}
|
||||
{false, [], []}
|
||||
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 =
|
||||
[#xmlel{ns = ?NS_INBAND_REGISTER, name = 'instructions', children =
|
||||
[#xmlcdata{cdata = list_to_binary(
|
||||
@ -203,6 +247,29 @@ process_iq(From, To,
|
||||
#xmlel{ns = ?NS_INBAND_REGISTER, name = 'password'}
|
||||
| QuerySubels]},
|
||||
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.
|
||||
|
||||
%% @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}}) ->
|
||||
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