25
1
mirror of https://github.com/processone/ejabberd.git synced 2024-12-24 17:29:28 +01:00

Rewrite captcha to use XML generator

This commit is contained in:
Evgeniy Khramtsov 2016-07-28 15:10:41 +03:00
parent 96e912b09a
commit b31ebd2ea0
7 changed files with 862 additions and 361 deletions

View File

@ -294,6 +294,12 @@
type :: 'none' | 'pending' | 'subscribed' | 'unconfigured'}).
-type pubsub_subscription() :: #pubsub_subscription{}.
-record(bob_data, {cid :: binary(),
'max-age' :: non_neg_integer(),
type :: binary(),
data = <<>> :: any()}).
-type bob_data() :: #bob_data{}.
-record(muc_item, {actor :: #muc_actor{},
continue :: binary(),
reason = <<>> :: 'undefined' | binary(),
@ -404,7 +410,8 @@
required = false :: boolean(),
desc :: binary(),
values = [] :: [binary()],
options = [] :: [#xdata_option{}]}).
options = [] :: [#xdata_option{}],
sub_els = [] :: [any()]}).
-type xdata_field() :: #xdata_field{}.
-record(version, {name :: binary(),
@ -482,6 +489,15 @@
number :: binary()}).
-type vcard_tel() :: #vcard_tel{}.
-record(media_uri, {type :: binary(),
uri = <<>> :: binary()}).
-type media_uri() :: #media_uri{}.
-record(media, {height :: non_neg_integer(),
width :: non_neg_integer(),
uri = [] :: [#media_uri{}]}).
-type media() :: #media{}.
-record(muc_destroy, {xmlns :: binary(),
jid :: any(),
reason = <<>> :: 'undefined' | binary(),
@ -531,6 +547,11 @@
url = [] :: [#bookmark_url{}]}).
-type bookmark_storage() :: #bookmark_storage{}.
-record(oob_x, {url :: binary(),
desc = <<>> :: binary(),
sid = <<>> :: binary()}).
-type oob_x() :: #oob_x{}.
-record(vcard_sound, {phonetic :: binary(),
binval :: any(),
extval :: binary()}).
@ -582,6 +603,9 @@
fields = [] :: [#xdata_field{}]}).
-type xdata() :: #xdata{}.
-record(xcaptcha, {xdata :: #xdata{}}).
-type xcaptcha() :: #xcaptcha{}.
-record(adhoc_command, {node :: binary(),
action = execute :: 'cancel' | 'complete' | 'execute' | 'next' | 'prev',
sid :: binary(),
@ -647,7 +671,8 @@
misc :: 'none' | binary(),
text :: 'none' | binary(),
key :: 'none' | binary(),
xdata :: #xdata{}}).
xdata :: #xdata{},
sub_els = [] :: [any()]}).
-type register() :: #register{}.
-record(disco_info, {node :: binary(),
@ -807,6 +832,8 @@
version() |
pubsub_affiliation() |
mam_fin() |
bob_data() |
media() |
sm_a() |
carbons_sent() |
mam_archived() |
@ -880,11 +907,8 @@
sasl_failure() |
bookmark_storage() |
muc_decline() |
sasl_auth() |
p1_push() |
legacy_auth() |
search() |
pubsub_publish() |
unblock() |
nick() |
p1_ack() |
@ -892,6 +916,7 @@
mix_join() |
xmpp_session() |
xdata() |
xcaptcha() |
iq() |
streamhost() |
bind() |
@ -917,6 +942,7 @@
starttls() |
mam_prefs() |
sasl_mechanisms() |
media_uri() |
muc_destroy() |
vcard_key() |
csi() |
@ -949,4 +975,8 @@
expire() |
muc_unsubscribe() |
pubsub_unsubscribe() |
chatstate().
chatstate() |
sasl_auth() |
p1_push() |
oob_x() |
pubsub_publish().

View File

@ -41,31 +41,17 @@
-export([create_captcha/6, build_captcha_html/2,
check_captcha/2, process_reply/1, process/2,
is_feature_available/0, create_captcha_x/5,
create_captcha_x/6, opt_type/1]).
-include("jlib.hrl").
opt_type/1]).
-include("xmpp.hrl").
-include("ejabberd.hrl").
-include("logger.hrl").
-include("ejabberd_http.hrl").
-define(VFIELD(Type, Var, Value),
#xmlel{name = <<"field">>,
attrs = [{<<"type">>, Type}, {<<"var">>, Var}],
children =
[#xmlel{name = <<"value">>, attrs = [],
children = [Value]}]}).
-define(CAPTCHA_TEXT(Lang),
translate:translate(Lang,
<<"Enter the text you see">>)).
-define(CAPTCHA_LIFETIME, 120000).
-define(LIMIT_PERIOD, 60*1000*1000).
-type error() :: efbig | enodata | limit | malformed_image | timeout.
-type image_error() :: efbig | enodata | limit | malformed_image | timeout.
-record(state, {limits = treap:empty() :: treap:treap()}).
@ -79,188 +65,82 @@ start_link() ->
gen_server:start_link({local, ?MODULE}, ?MODULE, [],
[]).
-spec captcha_text(undefined | binary()) -> binary().
captcha_text(Lang) ->
translate:translate(Lang, <<"Enter the text you see">>).
-spec mk_ocr_field(binary() | undefined, binary(), binary()) -> xdata_field().
mk_ocr_field(Lang, CID, Type) ->
URI = #media_uri{type = Type, uri = <<"cid:", CID/binary>>},
#xdata_field{var = <<"ocr">>,
label = captcha_text(Lang),
required = true,
sub_els = [#media{uri = [URI]}]}.
mk_field(Type, Var, Value) ->
#xdata_field{type = Type, var = Var, values = [Value]}.
-spec create_captcha(binary(), jid(), jid(),
binary(), any(), any()) -> {error, error()} |
{ok, binary(), [xmlel()]}.
binary(), any(), any()) -> {error, image_error()} |
{ok, binary(), [text()], [xmlel()]}.
create_captcha(SID, From, To, Lang, Limiter, Args) ->
case create_image(Limiter) of
{ok, Type, Key, Image} ->
Id = <<(randoms:get_string())/binary>>,
B64Image = jlib:encode_base64((Image)),
JID = jid:to_string(From),
CID = <<"sha1+", (p1_sha:sha(Image))/binary,
"@bob.xmpp.org">>,
Data = #xmlel{name = <<"data">>,
attrs =
[{<<"xmlns">>, ?NS_BOB}, {<<"cid">>, CID},
{<<"max-age">>, <<"0">>}, {<<"type">>, Type}],
children = [{xmlcdata, B64Image}]},
Captcha = #xmlel{name = <<"captcha">>,
attrs = [{<<"xmlns">>, ?NS_CAPTCHA}],
children =
[#xmlel{name = <<"x">>,
attrs =
[{<<"xmlns">>, ?NS_XDATA},
{<<"type">>, <<"form">>}],
children =
[?VFIELD(<<"hidden">>,
<<"FORM_TYPE">>,
{xmlcdata, ?NS_CAPTCHA}),
?VFIELD(<<"hidden">>, <<"from">>,
{xmlcdata,
jid:to_string(To)}),
?VFIELD(<<"hidden">>,
<<"challenge">>,
{xmlcdata, Id}),
?VFIELD(<<"hidden">>, <<"sid">>,
{xmlcdata, SID}),
#xmlel{name = <<"field">>,
attrs =
[{<<"var">>, <<"ocr">>},
{<<"label">>,
?CAPTCHA_TEXT(Lang)}],
children =
[#xmlel{name =
<<"required">>,
attrs = [],
children = []},
#xmlel{name =
<<"media">>,
attrs =
[{<<"xmlns">>,
?NS_MEDIA}],
children =
[#xmlel{name
=
<<"uri">>,
attrs
=
[{<<"type">>,
Type}],
children
=
[{xmlcdata,
<<"cid:",
CID/binary>>}]}]}]}]}]},
CID = <<"sha1+", (p1_sha:sha(Image))/binary, "@bob.xmpp.org">>,
Data = #bob_data{cid = CID, 'max-age' = 0, type = Type,
data = Image},
Fs = [mk_field(hidden, <<"FORM_TYPE">>, ?NS_CAPTCHA),
mk_field(hidden, <<"from">>, jid:to_string(To)),
mk_field(hidden, <<"challenge">>, Id),
mk_field(hidden, <<"sid">>, SID),
mk_ocr_field(Lang, CID, Type)],
X = #xdata{type = form, fields = Fs},
Captcha = #xcaptcha{xdata = X},
BodyString1 = translate:translate(Lang,
<<"Your messages to ~s are being blocked. "
"To unblock them, visit ~s">>),
BodyString = iolist_to_binary(io_lib:format(BodyString1,
[JID, get_url(Id)])),
Body = #xmlel{name = <<"body">>, attrs = [],
children = [{xmlcdata, BodyString}]},
OOB = #xmlel{name = <<"x">>,
attrs = [{<<"xmlns">>, ?NS_OOB}],
children =
[#xmlel{name = <<"url">>, attrs = [],
children = [{xmlcdata, get_url(Id)}]}]},
Body = xmpp:mk_text(BodyString, Lang),
OOB = #oob_x{url = get_url(Id)},
Tref = erlang:send_after(?CAPTCHA_LIFETIME, ?MODULE,
{remove_id, Id}),
ets:insert(captcha,
#captcha{id = Id, pid = self(), key = Key, tref = Tref,
args = Args}),
{ok, Id, [Body, OOB, Captcha, Data]};
{ok, Id, Body, [OOB, Captcha, Data]};
Err -> Err
end.
-spec create_captcha_x(binary(), jid(), binary(),
any(), [xmlel()]) -> {ok, [xmlel()]} |
{error, error()}.
-spec create_captcha_x(binary(), jid(), binary(), any(), xdata()) ->
{ok, xdata()} | {error, image_error()}.
create_captcha_x(SID, To, Lang, Limiter, HeadEls) ->
create_captcha_x(SID, To, Lang, Limiter, HeadEls, []).
-spec create_captcha_x(binary(), jid(), binary(),
any(), [xmlel()], [xmlel()]) -> {ok, [xmlel()]} |
{error, error()}.
create_captcha_x(SID, To, Lang, Limiter, HeadEls,
TailEls) ->
create_captcha_x(SID, To, Lang, Limiter, #xdata{fields = Fs} = X) ->
case create_image(Limiter) of
{ok, Type, Key, Image} ->
Id = <<(randoms:get_string())/binary>>,
B64Image = jlib:encode_base64((Image)),
CID = <<"sha1+", (p1_sha:sha(Image))/binary,
"@bob.xmpp.org">>,
Data = #xmlel{name = <<"data">>,
attrs =
[{<<"xmlns">>, ?NS_BOB}, {<<"cid">>, CID},
{<<"max-age">>, <<"0">>}, {<<"type">>, Type}],
children = [{xmlcdata, B64Image}]},
CID = <<"sha1+", (p1_sha:sha(Image))/binary, "@bob.xmpp.org">>,
Data = #bob_data{cid = CID, 'max-age' = 0, type = Type, data = Image},
HelpTxt = translate:translate(Lang,
<<"If you don't see the CAPTCHA image here, "
"visit the web page.">>),
Imageurl = get_url(<<Id/binary, "/image">>),
Captcha = #xmlel{name = <<"x">>,
attrs =
[{<<"xmlns">>, ?NS_XDATA},
{<<"type">>, <<"form">>}],
children =
[?VFIELD(<<"hidden">>, <<"FORM_TYPE">>,
{xmlcdata, ?NS_CAPTCHA})
| HeadEls]
++
[#xmlel{name = <<"field">>,
attrs = [{<<"type">>, <<"fixed">>}],
children =
[#xmlel{name = <<"value">>,
attrs = [],
children =
[{xmlcdata,
HelpTxt}]}]},
#xmlel{name = <<"field">>,
attrs =
[{<<"type">>, <<"hidden">>},
{<<"var">>, <<"captchahidden">>}],
children =
[#xmlel{name = <<"value">>,
attrs = [],
children =
[{xmlcdata,
<<"workaround-for-psi">>}]}]},
#xmlel{name = <<"field">>,
attrs =
[{<<"type">>, <<"text-single">>},
{<<"label">>,
translate:translate(Lang,
<<"CAPTCHA web page">>)},
{<<"var">>, <<"url">>}],
children =
[#xmlel{name = <<"value">>,
attrs = [],
children =
[{xmlcdata,
Imageurl}]}]},
?VFIELD(<<"hidden">>, <<"from">>,
{xmlcdata, jid:to_string(To)}),
?VFIELD(<<"hidden">>, <<"challenge">>,
{xmlcdata, Id}),
?VFIELD(<<"hidden">>, <<"sid">>,
{xmlcdata, SID}),
#xmlel{name = <<"field">>,
attrs =
[{<<"var">>, <<"ocr">>},
{<<"label">>,
?CAPTCHA_TEXT(Lang)}],
children =
[#xmlel{name = <<"required">>,
attrs = [], children = []},
#xmlel{name = <<"media">>,
attrs =
[{<<"xmlns">>,
?NS_MEDIA}],
children =
[#xmlel{name =
<<"uri">>,
attrs =
[{<<"type">>,
Type}],
children =
[{xmlcdata,
<<"cid:",
CID/binary>>}]}]}]}]
++ TailEls},
NewFs = [mk_field(hidden, <<"FORM_TYPE">>, ?NS_CAPTCHA)|Fs] ++
[#xdata_field{type = fixed, values = [HelpTxt]},
#xdata_field{type = hidden, var = <<"captchahidden">>,
values = [<<"workaround-for-psi">>]},
#xdata_field{type = 'text-single', var = <<"url">>,
label = translate:translate(
Lang, <<"CAPTCHA web page">>),
values = [Imageurl]},
mk_field(hidden, <<"from">>, jid:to_string(To)),
mk_field(hidden, <<"challenge">>, Id),
mk_field(hidden, <<"sid">>, SID),
mk_ocr_field(Lang, CID, Type)],
Captcha = X#xdata{type = form, fields = NewFs},
Tref = erlang:send_after(?CAPTCHA_LIFETIME, ?MODULE,
{remove_id, Id}),
ets:insert(captcha,
@ -281,7 +161,7 @@ build_captcha_html(Id, Lang) ->
attrs =
[{<<"src">>, get_url(<<Id/binary, "/image">>)}],
children = []},
TextEl = {xmlcdata, ?CAPTCHA_TEXT(Lang)},
TextEl = {xmlcdata, captcha_text(Lang)},
IdEl = #xmlel{name = <<"input">>,
attrs =
[{<<"type">>, <<"hidden">>}, {<<"name">>, <<"id">>},
@ -317,27 +197,24 @@ build_captcha_html(Id, Lang) ->
_ -> captcha_not_found
end.
-spec process_reply(xmlel()) -> ok | {error, bad_match | not_found | malformed}.
-spec process_reply(xmpp_element()) -> ok | {error, bad_match | not_found | malformed}.
process_reply(#xmlel{} = El) ->
case fxml:get_subtag(El, <<"x">>) of
false -> {error, malformed};
Xdata ->
Fields = jlib:parse_xdata_submit(Xdata),
case catch {proplists:get_value(<<"challenge">>,
Fields),
proplists:get_value(<<"ocr">>, Fields)}
of
{[Id | _], [OCR | _]} ->
case check_captcha(Id, OCR) of
captcha_valid -> ok;
captcha_non_valid -> {error, bad_match};
captcha_not_found -> {error, not_found}
end;
_ -> {error, malformed}
end
process_reply(#xdata{} = X) ->
case {xmpp_util:get_xdata_values(<<"challenge">>, X),
xmpp_util:get_xdata_values(<<"ocr">>, X)} of
{[Id], [OCR]} ->
case check_captcha(Id, OCR) of
captcha_valid -> ok;
captcha_non_valid -> {error, bad_match};
captcha_not_found -> {error, not_found}
end;
_ ->
{error, malformed}
end;
process_reply(_) -> {error, malformed}.
process_reply(#xcaptcha{xdata = #xdata{} = X}) ->
process_reply(X);
process_reply(_) ->
{error, malformed}.
process(_Handlers,
#request{method = 'GET', lang = Lang,

View File

@ -1824,8 +1824,9 @@ add_new_user(From, Nick, Packet, StateData) ->
case ejabberd_captcha:create_captcha(SID, RoomJID, To,
Lang, Limiter, From)
of
{ok, ID, CaptchaEls} ->
MsgPkt = #message{id = ID, sub_els = CaptchaEls},
{ok, ID, Body, CaptchaEls} ->
MsgPkt = #message{id = ID, body = Body,
sub_els = CaptchaEls},
Robots = (?DICT):store(From, {Nick, Packet},
StateData#state.robots),
ejabberd_router:route(RoomJID, From, MsgPkt),

View File

@ -218,10 +218,10 @@ process_iq(#iq{type = get, from = From, to = To, id = ID, lang = Lang} = IQ,
X = #xdata{type = form, instructions = [Instr],
fields = [UField, PField]},
case ejabberd_captcha:create_captcha_x(ID, To, Lang, Source, X) of
{ok, Captcha} ->
{ok, CaptchaEls} ->
xmpp:make_iq_result(
IQ, #register{instructions = TopInstr,
xdata = Captcha});
sub_els = CaptchaEls});
{error, limit} ->
ErrText = <<"Too many CAPTCHA requests">>,
xmpp:make_error(

View File

@ -60,7 +60,7 @@
-include("ejabberd.hrl").
-include("logger.hrl").
-include("jlib.hrl").
-include("xmpp.hrl").
-include("ejabberd_http.hrl").
@ -334,7 +334,7 @@ build_captcha_li_list2(Lang, IP) ->
case ejabberd_captcha:create_captcha(SID, From, To,
Lang, IP, Args)
of
{ok, Id, _} ->
{ok, Id, _, _} ->
{_, {CImg, CText, CId, CKey}} =
ejabberd_captcha:build_captcha_html(Id, Lang),
[?XE(<<"li">>,

File diff suppressed because it is too large Load Diff

View File

@ -1010,7 +1010,7 @@
'$username', '$nick', '$password', '$name',
'$first', '$last', '$email', '$address',
'$city', '$state', '$zip', '$phone', '$url',
'$date', '$misc', '$text', '$key', '$xdata'},
'$date', '$misc', '$text', '$key', '$xdata', '$_els'},
refs = [#ref{name = xdata, min = 0, max = 1,
label = '$xdata'},
#ref{name = register_registered, min = 0, max = 1,
@ -1600,7 +1600,7 @@
#elem{name = <<"field">>,
xmlns = <<"jabber:x:data">>,
result = {xdata_field, '$label', '$type', '$var',
'$required', '$desc', '$values', '$options'},
'$required', '$desc', '$values', '$options', '$_els'},
attrs = [#attr{name = <<"label">>},
#attr{name = <<"type">>,
enc = {enc_enum, []},
@ -2937,6 +2937,66 @@
#attr{name = <<"version">>, default = <<"">>},
#attr{name = <<"id">>, default = <<"">>}]}).
-xml(bob_data,
#elem{name = <<"data">>,
xmlns = <<"urn:xmpp:bob">>,
result = {bob_data, '$cid', '$max-age', '$type', '$data'},
attrs = [#attr{name = <<"cid">>, required = true},
#attr{name = <<"max-age">>,
dec = {dec_int, [0, infinity]},
enc = {enc_int, []}},
#attr{name = <<"type">>}],
cdata = #cdata{label = '$data', default = <<"">>,
dec = {base64, decode, []},
enc = {base64, encode, []}}}).
-xml(captcha,
#elem{name = <<"captcha">>,
xmlns = <<"urn:xmpp:captcha">>,
result = {xcaptcha, '$xdata'},
refs = [#ref{name = xdata, min = 1, max = 1}]}).
-xml(media_uri,
#elem{name = <<"uri">>,
xmlns = <<"urn:xmpp:media-element">>,
result = {media_uri, '$type', '$uri'},
attrs = [#attr{name = <<"type">>, required = true}],
cdata = #cdata{label = '$uri', default = <<"">>}}).
-xml(media,
#elem{name = <<"media">>,
xmlns = <<"urn:xmpp:media-element">>,
result = {media, '$height', '$width', '$uri'},
attrs = [#attr{name = <<"height">>,
dec = {dec_int, [0, infinity]},
enc = {enc_int, []}},
#attr{name = <<"width">>,
dec = {dec_int, [0, inifinity]},
enc = {enc_int, []}}],
refs = [#ref{name = media_uri, label = '$uri'}]}).
-xml(oob_url,
#elem{name = <<"url">>,
xmlns = <<"jabber:x:oob">>,
result = '$cdata',
cdata = #cdata{required = true}}).
-xml(oob_desc,
#elem{name = <<"desc">>,
xmlns = <<"jabber:x:oob">>,
result = '$cdata',
cdata = #cdata{default = <<"">>}}).
-xml(oob_x,
#elem{name = <<"x">>,
xmlns = <<"jabber:x:oob">>,
result = {oob_x, '$url', '$desc', '$sid'},
attrs = [#attr{name = <<"sid">>, default = <<"">>}],
refs = [#ref{name = oob_url, min = 1, max = 1,
label = '$url'},
#ref{name = oob_desc, default = <<"">>,
min = 0, max = 1, label = '$desc'}]}).
dec_tzo(Val) ->
[H1, M1] = str:tokens(Val, <<":">>),
H = jlib:binary_to_integer(H1),