Skip to content

Commit

Permalink
Unify API for sending stanzas
Browse files Browse the repository at this point in the history
  • Loading branch information
chrzaszcz committed Oct 17, 2022
1 parent a9ee17f commit 9cccf4e
Show file tree
Hide file tree
Showing 7 changed files with 119 additions and 118 deletions.
22 changes: 10 additions & 12 deletions src/graphql/admin/mongoose_graphql_stanza_admin_mutation.erl
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@
-ignore_xref([execute/4]).

-include("../mongoose_graphql_types.hrl").
-include("mongoose_logger.hrl").
-include("jlib.hrl").

-import(mongoose_graphql_helper, [format_result/2]).

execute(_Ctx, _Obj, <<"sendMessage">>, Args) ->
send_message(Args);
Expand All @@ -17,16 +17,14 @@ execute(_Ctx, _Obj, <<"sendStanza">>, Args) ->
send_stanza(Args).

send_message(#{<<"from">> := From, <<"to">> := To, <<"body">> := Body}) ->
Packet = mongoose_stanza_helper:build_message(
jid:to_binary(From), jid:to_binary(To), Body),
mongoose_stanza_helper:route(From, To, Packet, true).
Res = mongoose_stanza_api:send_chat_message(null, From, To, Body),
format_result(Res, #{from => jid:to_binary(From)}).

send_message_headline(Args = #{<<"from">> := From, <<"to">> := To}) ->
Packet = mongoose_stanza_helper:build_message_with_headline(
jid:to_binary(From), jid:to_binary(To), Args),
mongoose_stanza_helper:route(From, To, Packet, true).
send_message_headline(#{<<"from">> := From, <<"to">> := To,
<<"body">> := Body, <<"subject">> := Subject}) ->
Res = mongoose_stanza_api:send_headline_message(null, From, To, Body, Subject),
format_result(Res, #{from => jid:to_binary(From)}).

send_stanza(#{<<"stanza">> := Packet}) ->
From = jid:from_binary(exml_query:attr(Packet, <<"from">>)),
To = jid:from_binary(exml_query:attr(Packet, <<"to">>)),
mongoose_stanza_helper:route(From, To, Packet, true).
Res = mongoose_stanza_api:send_stanza(null, Packet),
format_result(Res, #{}).
51 changes: 11 additions & 40 deletions src/graphql/user/mongoose_graphql_stanza_user_mutation.erl
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@
-ignore_xref([execute/4]).

-include("../mongoose_graphql_types.hrl").
-include("mongoose_logger.hrl").
-include("jlib.hrl").

-import(mongoose_graphql_helper, [format_result/2]).

execute(Ctx, _Obj, <<"sendMessage">>, Args) ->
send_message(Ctx, Args);
Expand All @@ -16,44 +16,15 @@ execute(Ctx, _Obj, <<"sendMessageHeadLine">>, Args) ->
execute(Ctx, _Obj, <<"sendStanza">>, Args) ->
send_stanza(Ctx, Args).

send_message(Ctx, Args) ->
with_from(Ctx, Args, fun send_message2/1).

send_message_headline(Ctx, Args) ->
with_from(Ctx, Args, fun send_message_headline2/1).

send_message2(#{<<"from">> := From, <<"to">> := To, <<"body">> := Body}) ->
Packet = mongoose_stanza_helper:build_message(jid:to_binary(From), jid:to_binary(To), Body),
%% SkipAuth = false, because we already checked if From exists
mongoose_stanza_helper:route(From, To, Packet, false).
send_message(#{user := User}, #{<<"from">> := From, <<"to">> := To, <<"body">> := Body}) ->
Res = mongoose_stanza_api:send_chat_message(User, From, To, Body),
format_result(Res, #{}).

send_message_headline2(Args = #{<<"from">> := From, <<"to">> := To}) ->
Packet = mongoose_stanza_helper:build_message_with_headline(
jid:to_binary(From), jid:to_binary(To), Args),
mongoose_stanza_helper:route(From, To, Packet, false).
send_message_headline(#{user := User}, #{<<"from">> := From, <<"to">> := To, <<"body">> := Body,
<<"subject">> := Subject}) ->
Res = mongoose_stanza_api:send_headline_message(User, From, To, Body, Subject),
format_result(Res, #{}).

send_stanza(#{user := User}, #{<<"stanza">> := Packet}) ->
From = jid:from_binary(exml_query:attr(Packet, <<"from">>)),
To = jid:from_binary(exml_query:attr(Packet, <<"to">>)),
case jid:are_bare_equal(User, From) of
true ->
mongoose_stanza_helper:route(From, To, Packet, false);
false ->
{error, #{what => bad_from_jid}}
end.

with_from(_Ctx = #{user := User}, Args, Next) ->
case maps:get(<<"from">>, Args, null) of
null ->
Next(Args#{<<"from">> => User});
From ->
case jid:are_bare_equal(User, From) of
true ->
%% We still can allow a custom resource
Next(Args#{<<"from">> => From});
false ->
?LOG_ERROR(#{what => bad_from_jid,
user_jid => User, from_jid => From}),
{error, #{what => bad_from_jid}}
end
end.
Res = mongoose_stanza_api:send_stanza(User, Packet),
format_result(Res, #{}).
12 changes: 4 additions & 8 deletions src/mongoose_admin_api/mongoose_admin_api_messages.erl
Original file line number Diff line number Diff line change
Expand Up @@ -78,15 +78,11 @@ handle_post(Req, State) ->
From = get_caller(Args),
To = get_to(Args),
Body = get_body(Args),
Packet = mongoose_stanza_helper:build_message(
jid:to_binary(From), jid:to_binary(To), Body),
case mongoose_stanza_helper:route(From, To, Packet, true) of
{error, #{what := unknown_domain}} ->
throw_error(bad_request, <<"Unknown domain">>);
{error, #{what := unknown_user}} ->
throw_error(bad_request, <<"Unknown user">>);
case mongoose_stanza_api:send_chat_message(null, From, To, Body) of
{ok, _} ->
{true, Req, State}
{true, Req, State};
{_Error, Msg} ->
throw_error(bad_request, Msg)
end.

-spec row_to_map(mod_mam:message_row()) -> map().
Expand Down
37 changes: 6 additions & 31 deletions src/mongoose_admin_api/mongoose_admin_api_stanzas.erl
Original file line number Diff line number Diff line change
Expand Up @@ -50,15 +50,11 @@ from_json(Req, State) ->
handle_post(Req, State) ->
Args = parse_body(Req),
Stanza = get_stanza(Args),
From = get_from_jid(Stanza),
To = get_to_jid(Stanza),
case mongoose_stanza_helper:route(From, To, Stanza, true) of
{error, #{what := unknown_domain}} ->
throw_error(bad_request, <<"Unknown domain">>);
{error, #{what := unknown_user}} ->
throw_error(bad_request, <<"Unknown user">>);
case mongoose_stanza_api:send_stanza(null, Stanza) of
{ok, _} ->
{true, Req, State}
{true, Req, State};
{_Error, Msg} ->
throw_error(bad_request, Msg)
end.

get_stanza(#{stanza := BinStanza}) ->
Expand All @@ -68,26 +64,5 @@ get_stanza(#{stanza := BinStanza}) ->
{error, _} ->
throw_error(bad_request, <<"Malformed stanza">>)
end;
get_stanza(#{}) -> throw_error(bad_request, <<"Missing stanza">>).

get_from_jid(Stanza) ->
case exml_query:attr(Stanza, <<"from">>) of
undefined ->
throw_error(bad_request, <<"Missing sender JID">>);
JidBin ->
case jid:from_binary(JidBin) of
error -> throw_error(bad_request, <<"Invalid sender JID">>);
Jid -> Jid
end
end.

get_to_jid(Stanza) ->
case exml_query:attr(Stanza, <<"to">>) of
undefined ->
throw_error(bad_request, <<"Missing recipient JID">>);
JidBin ->
case jid:from_binary(JidBin) of
error -> throw_error(bad_request, <<"Invalid recipient JID">>);
Jid -> Jid
end
end.
get_stanza(#{}) ->
throw_error(bad_request, <<"Missing stanza">>).
7 changes: 2 additions & 5 deletions src/mongoose_client_api/mongoose_client_api_messages.erl
Original file line number Diff line number Diff line change
Expand Up @@ -78,14 +78,11 @@ handle_get(Req, State = #{jid := OwnerJid}) ->
Resp = [make_json_msg(Msg, MAMId) || #{id := MAMId, packet := Msg} <- Rows],
{jiffy:encode(Resp), Req, State}.

handle_post(Req, State = #{jid := From}) ->
handle_post(Req, State = #{jid := UserJid}) ->
Args = parse_body(Req),
To = get_to(Args),
Body = get_body(Args),
Packet = mongoose_stanza_helper:build_message(jid:to_binary(From), jid:to_binary(To), Body),
{ok, _} = mongoose_stanza_helper:route(From, To, Packet, true),
Id = exml_query:attr(Packet, <<"id">>),
Resp = #{<<"id">> => Id},
{ok, Resp} = mongoose_stanza_api:send_chat_message(UserJid, null, To, Body),
Req2 = cowboy_req:set_resp_body(jiffy:encode(Resp), Req),
{true, Req2, State}.

Expand Down
78 changes: 76 additions & 2 deletions src/mongoose_stanza_api.erl
Original file line number Diff line number Diff line change
@@ -1,15 +1,84 @@
-module(mongoose_stanza_api).
-export([lookup_recent_messages/4]).
-export([send_chat_message/4, send_headline_message/5, send_stanza/2, lookup_recent_messages/4]).

-include("jlib.hrl").
-include("mongoose_rsm.hrl").

-spec send_chat_message(jid:jid() | null, jid:jid() | null, jid:jid(), binary()) ->
{unknown_sender | invalid_sender, iodata()} | {ok, map()}.
send_chat_message(User, From, To, Body) ->
M = #{user => User, from => From, to => To, body => Body},
fold(M, [fun get_sender_jid/1, fun prepare_chat_message/1, fun send/1]).

-spec send_headline_message(jid:jid() | null, jid:jid() | null, jid:jid(),
binary() | null, binary() | null) ->
{unknown_sender | invalid_sender | no_sender |
invalid_recipient | no_recipient, iodata()} | {ok, map()}.
send_headline_message(User, From, To, Body, Subject) ->
M = #{user => User, from => From, to => To, body => Body, subject => Subject},
fold(M, [fun get_sender_jid/1, fun prepare_headline_message/1, fun send/1]).

-spec send_stanza(jid:jid() | null, exml:element()) ->
{unknown_sender | invalid_sender, iodata()} | {ok, map()}.
send_stanza(User, Stanza) ->
M = #{user => User, stanza => Stanza},
fold(M, [fun get_from_jid/1, fun get_to_jid/1, fun get_sender_jid/1, fun send/1]).

get_sender_jid(M = #{user := null, from := From = #jid{}}) ->
case ejabberd_auth:does_user_exist(From) of
true ->
M;
false ->
{unknown_sender, <<"Sender's account does not exist">>}
end;
get_sender_jid(M = #{user := User = #jid{}, from := null}) ->
M#{from => User};
get_sender_jid(M = #{user := User = #jid{}, from := From = #jid{}}) ->
case jid:are_bare_equal(User, From) of
true ->
M;
false ->
{invalid_sender, <<"Sender's JID is different from the user's JID">>}
end.

send(#{from := #jid{lserver = SenderDomain} = From, to := To, stanza := Stanza}) ->
{ok, HostType} = mongoose_domain_api:get_domain_host_type(SenderDomain),
mongoose_stanza_helper:route(HostType, SenderDomain, From, To, Stanza).

get_from_jid(M = #{stanza := Stanza}) ->
case exml_query:attr(Stanza, <<"from">>) of
undefined ->
{no_sender, <<"Missing sender JID">>};
JidBin ->
case jid:from_binary(JidBin) of
error -> {invalid_sender, <<"Invalid sender JID">>};
Jid -> M#{from => Jid}
end
end.

get_to_jid(M = #{stanza := Stanza}) ->
case exml_query:attr(Stanza, <<"to">>) of
undefined ->
{no_recipient, <<"Missing recipient JID">>};
JidBin ->
case jid:from_binary(JidBin) of
error -> {invalid_recipient, <<"Invalid recipient JID">>};
Jid -> M#{to => Jid}
end
end.

prepare_chat_message(M = #{from := From, to := To, body := Body}) ->
M#{stanza => mongoose_stanza_helper:build_message(From, To, Body)}.

prepare_headline_message(M = #{from := From, to := To, body := Body, subject := Subject}) ->
M#{stanza => mongoose_stanza_helper:build_message_with_headline(From, To, Body, Subject)}.

%% TODO fix error handling, do not crash for non-existing users
%% Before is in microseconds
-spec lookup_recent_messages(
ArcJID :: jid:jid(),
With :: jid:jid() | undefined,
Before :: mod_mam:unix_timestamp() | undefined,
Before :: mod_mam:unix_timestamp() | undefined, % microseconds
Limit :: non_neg_integer()) ->
[mod_mam:message_row()].
lookup_recent_messages(_, _, _, Limit) when Limit > 500 ->
Expand Down Expand Up @@ -37,3 +106,8 @@ lookup_recent_messages(ArcJID, WithJID, Before, Limit) ->
R = mod_mam_pm:lookup_messages(HostType, Params),
{ok, {_, _, L}} = R,
L.

fold({_, _} = Result, _) ->
Result;
fold(M, [Step | Rest]) when is_map(M) ->
fold(Step(M), Rest).
30 changes: 10 additions & 20 deletions src/mongoose_stanza_helper.erl
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
-module(mongoose_stanza_helper).
-export([build_message/3]).
-export([build_message_with_headline/3]).
-export([build_message_with_headline/4]).
-export([get_last_messages/4]).
-export([route/4]).
-export([route/5]).

-include("jlib.hrl").
-include("mongoose_logger.hrl").
Expand All @@ -18,8 +18,7 @@ build_message(From, To, Body) ->
children = [#xmlcdata{content = Body}]}]
}.

build_message_with_headline(FromBin, ToBin,
#{<<"body">> := Body, <<"subject">> := Subject}) ->
build_message_with_headline(FromBin, ToBin, Body, Subject) ->
Children = maybe_cdata_elem(<<"subject">>, Subject) ++
maybe_cdata_elem(<<"body">>, Body),
Attrs = [{<<"type">>, <<"headline">>},
Expand Down Expand Up @@ -59,24 +58,15 @@ row_to_map(#{id := Id, jid := From, packet := Msg}) ->
<<"stanza_id">> => StanzaID, <<"stanza">> => Msg},
{ok, Map}.

-spec route(From :: jid:jid(), To :: jid:jid(),
Packet :: exml:element(), SkipAuth :: boolean()) ->
-spec route(mongoosim:host_type(), jid:lserver(), From :: jid:jid(), To :: jid:jid(),
Packet :: exml:element()) ->
{ok, map()} | {error, term()}.
route(From = #jid{lserver = LServer}, To, Packet, SkipAuth) ->
case mongoose_graphql_helper:check_user(From, SkipAuth) of
{ok, HostType} ->
do_route(HostType, LServer, From, To, Packet);
Error ->
Error
end.

do_route(HostType, LServer, From, To, Packet) ->
%% Based on mod_commands:do_send_packet/3
route(HostType, LServer, From, To, Packet) ->
Acc = mongoose_acc:new(#{location => ?LOCATION,
host_type => HostType,
lserver => LServer,
element => Packet}),
host_type => HostType,
lserver => LServer,
element => Packet}),
Acc1 = mongoose_hooks:user_send_packet(Acc, From, To, Packet),
Acc2 = ejabberd_router:route(From, To, Acc1),
MessID = mod_mam_utils:get_mam_id_ext(Acc2),
{ok, #{ <<"id">> => MessID }}.
{ok, #{id => MessID}}.

0 comments on commit 9cccf4e

Please sign in to comment.