Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add option to modify Cowboy server name returned in headers #2308

Merged
merged 4 commits into from
May 29, 2019
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 46 additions & 15 deletions big_tests/tests/rest_client_SUITE.erl
Original file line number Diff line number Diff line change
Expand Up @@ -7,47 +7,46 @@
-include_lib("common_test/include/ct.hrl").

-import(rest_helper,
[assert_inlist/2,
assert_notinlist/2,
decode_maplist/1,
gett/2,
[decode_maplist/1,
gett/3,
post/3,
post/4,
putt/3,
putt/4,
delete/2,
delete/3,
delete/4]
).

-import(muc_light_helper,
[set_mod_config/3]).

-import(escalus_ejabberd, [rpc/3]).

-define(PRT(X, Y), ct:pal("~p: ~p", [X, Y])).
-define(OK, {<<"200">>, <<"OK">>}).
-define(CREATED, {<<"201">>, <<"Created">>}).
-define(NOCONTENT, {<<"204">>, <<"No Content">>}).
-define(ERROR, {<<"500">>, _}).
-define(NOT_FOUND, {<<"404">>, _}).
-define(NOT_IMPLEMENTED, {<<"501">>, _}).
-define(UNAUTHORIZED, {<<"401">>, <<"Unauthorized">>}).
-define(MUCHOST, <<"muclight.localhost">>).

%% --------------------------------------------------------------------
%% Common Test stuff
%% --------------------------------------------------------------------

all() ->
[{group, messages},
{group, muc},
{group, muc_config},
{group, roster},
{group, messages_with_props}].
{group, messages_with_props},
{group, security}].

groups() ->
G = [{messages_with_props, [parallel], message_with_props_test_cases()},
{messages, [parallel], message_test_cases()},
{muc, [pararell], muc_test_cases()},
{muc_config, [], muc_config_cases()},
{roster, [parallel], roster_test_cases()}],
{roster, [parallel], roster_test_cases()},
{security, [], security_test_cases()}],
ct_helper:repeat_all_until_all_ok(G).

message_test_cases() ->
Expand Down Expand Up @@ -106,6 +105,12 @@ message_with_props_test_cases() ->
msg_with_malformed_props_is_sent_and_delivered_over_xmpp
].

security_test_cases() ->
[
default_http_server_name_is_returned_if_not_changed,
non_default_http_server_name_is_returned_if_configured
].

init_per_suite(C) ->
application:ensure_all_started(shotgun),
Host = ct:get_config({hosts, mim, domain}),
Expand Down Expand Up @@ -158,6 +163,10 @@ end_per_testcase(config_can_be_changed_by_all = CaseName, Config) ->
end_per_testcase(TC, C) ->
escalus:end_per_testcase(TC, C).

%% --------------------------------------------------------------------
%% Test cases
%% --------------------------------------------------------------------

msg_is_sent_and_delivered_over_xmpp(Config) ->
escalus:fresh_story(Config, [{alice, 1}, {bob, 1}], fun(Alice, Bob) ->
M = send_message(alice, Alice, Bob),
Expand Down Expand Up @@ -581,7 +590,6 @@ msg_with_malformed_props_can_be_parsed(Config) ->

end).


assert_room_messages(RecvMsg, {_ID, _GenFrom, GenMsg}) ->
escalus:assert(is_chat_message, [maps:get(body, RecvMsg)], GenMsg),
ok.
Expand Down Expand Up @@ -652,7 +660,7 @@ when_config_change(User, RoomID, NewName, NewSubject) ->
Creds = credentials(User),
Config = #{name => NewName, subject => NewSubject},
Path = <<"/rooms/", RoomID/binary, "/config">>,
rest_helper:putt(client, Path, Config, Creds).
putt(client, Path, Config, Creds).

maybe_wait_for_aff_stanza(#client{} = Client, Invitee) ->
Stanza = escalus:wait_for_stanza(Client),
Expand Down Expand Up @@ -690,7 +698,7 @@ send_message(User, From, To) ->
BobJID = user_jid(To),
M = #{to => BobJID, body => <<"hello, ", BobJID/binary, " it's me">>},
Cred = credentials({User, From}),
{{<<"200">>, <<"OK">>}, {Result}} = rest_helper:post(client, <<"/messages">>, M, Cred),
{{<<"200">>, <<"OK">>}, {Result}} = post(client, <<"/messages">>, M, Cred),
ID = proplists:get_value(<<"id">>, Result),
M#{id => ID, from => AliceJID}.

Expand Down Expand Up @@ -762,7 +770,7 @@ create_room_with_id_request(Creds, RoomName, Subject, RoomID) ->
Room = #{name => RoomName,
subject => Subject},
Path = <<"/rooms/", RoomID/binary>>,
rest_helper:putt(client, Path, Room, Creds).
putt(client, Path, Room, Creds).

get_my_rooms(User) ->
Creds = credentials(User),
Expand Down Expand Up @@ -1163,3 +1171,26 @@ room_jid(RoomID, Config) ->

domain(Config) ->
?config(muc_light_host, Config).

default_http_server_name_is_returned_if_not_changed(_Config) ->
%% GIVEN MIM1 uses default name
verify_server_name_in_header(distributed_helper:mim(), <<"Cowboy">>).

non_default_http_server_name_is_returned_if_configured(_Config) ->
%% GIVEN MIM2 uses name "Classified"
verify_server_name_in_header(distributed_helper:mim2(), <<"Classified">>).

verify_server_name_in_header(Server, ExpectedName) ->
% WHEN unathenticated user makes a request to nonexistent path
ReqParams = #{
role => client,
method => <<"GET">>,
path => "/contacts/zorro@localhost",
body => <<>>,
return_headers => true,
server => Server
},
{?UNAUTHORIZED, Headers2, _} = rest_helper:make_request(ReqParams),
% THEN expected server name is returned
ExpectedName = proplists:get_value(<<"server">>, Headers2).

120 changes: 77 additions & 43 deletions big_tests/tests/rest_helper.erl
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
delete/2,
delete/3,
delete/4,
make_request/1,
maybe_enable_mam/3,
maybe_disable_mam/2,
maybe_skip_mam_test_cases/3,
Expand All @@ -32,6 +33,15 @@
-define(PATHPREFIX, <<"/api">>).

-type role() :: admin | client.
-type credentials() :: {Username :: binary(), Password :: binary()}.
-type request_params() :: #{
role := role(),
method := binary(),
creds => credentials(),
path := binary(),
body := binary(),
return_headers := boolean(),
server := atom() }.

%%--------------------------------------------------------------------
%% Helpers
Expand Down Expand Up @@ -87,51 +97,66 @@ assert_notinmaplist([K|Keys], Map, L, Orig) ->


gett(Role, Path) ->
make_request(Role, <<"GET">>, Path).
make_request(#{ role => Role, method => <<"GET">>, path => Path }).

post(Role, Path, Body) ->
make_request(Role, <<"POST">>, Path, Body).
make_request(#{ role => Role, method => <<"POST">>, path => Path, body => Body }).

putt(Role, Path, Body) ->
make_request(Role, <<"PUT">>, Path, Body).
make_request(#{ role => Role, method => <<"PUT">>, path => Path, body => Body }).

delete(Role, Path) ->
make_request(Role, <<"DELETE">>, Path).
make_request(#{ role => Role, method => <<"DELETE">>, path => Path }).

-spec gett(role(), Path :: string()|binary(), Cred :: {Username :: binary(), Password :: binary()}) -> term().
gett(Role, Path, Cred) ->
make_request(Role, {<<"GET">>, Cred}, Path).
make_request(#{ role => Role, method => <<"GET">>, creds => Cred, path => Path}).

post(Role, Path, Body, Cred) ->
make_request(Role, {<<"POST">>, Cred}, Path, Body).
make_request(#{ role => Role, method => <<"POST">>, creds => Cred, path => Path, body => Body }).

putt(Role, Path, Body, Cred) ->
make_request(Role, {<<"PUT">>, Cred}, Path, Body).
make_request(#{ role => Role, method => <<"PUT">>, creds => Cred, path => Path, body => Body }).

delete(Role, Path, Cred) ->
make_request(Role, {<<"DELETE">>, Cred}, Path).
make_request(#{ role => Role, method => <<"DELETE">>, creds => Cred, path => Path }).

delete(Role, Path, Cred, Body) ->
make_request(Role, {<<"DELETE">>, Cred}, Path, Body).



make_request(Role, Method, Path) ->
make_request(Role, Method, Path, <<"">>).

make_request(Role, Method, Path, ReqBody) when is_map(ReqBody) ->
make_request(Role, Method, Path, jiffy:encode(ReqBody));
make_request(Role, Method, Path, ReqBody) when not is_binary(Path) ->
make_request(Role, Method, list_to_binary(Path), ReqBody);
make_request(Role, Method, Path, ReqBody) ->
CPath = <<?PATHPREFIX/binary, Path/binary>>,
{Code, RespBody} = case fusco_request(Role, Method, CPath, ReqBody) of
{RCode, _, Body, _, _} ->
{RCode, Body};
{RCode, _, Body, _, _, _} ->
{RCode, Body}
end,
{Code, decode(RespBody)}.
make_request(#{ role => Role, method => <<"DELETE">>, creds => Cred, path => Path, body => Body }).

-spec make_request(request_params()) ->
{{Number :: binary(), Text :: binary()},
Headers :: [{binary(), binary()}],
Body :: map() | binary()}.
make_request(#{ return_headers := true } = Params) ->
NormalizedParams = normalize_path(normalize_body(fill_default_server(Params))),
case fusco_request(NormalizedParams) of
{RCode, RHeaders, Body, _, _} ->
{RCode, normalize_headers(RHeaders), decode(Body)};
{RCode, RHeaders, Body, _, _, _} ->
{RCode, normalize_headers(RHeaders), decode(Body)}
end;
make_request(#{ return_headers := false } = Params) ->
{Code, _, Body} = make_request(Params#{ return_headers := true }),
{Code, Body};
make_request(Params) ->
make_request(Params#{ return_headers => false }).

normalize_path(#{ path := Path } = Params) when not is_binary(Path) ->
normalize_path(Params#{ path := list_to_binary(Path) });
normalize_path(#{ path := Path } = Params) ->
Params#{ path := <<?PATHPREFIX/binary, Path/binary>> }.

normalize_body(#{ body := Body } = Params) when is_map(Body) ->
Params#{ body := jiffy:encode(Body) };
normalize_body(#{ body := Body } = Params) when is_binary(Body) ->
Params;
normalize_body(Params) ->
Params#{ body => <<>> }.

fill_default_server(#{ server := _Server } = Params) ->
Params;
fill_default_server(Params) ->
Params#{ server => mim() }.

decode(<<>>) ->
<<"">>;
Expand All @@ -143,14 +168,21 @@ decode(RespBody) ->
RespBody
end.

normalize_headers(Headers) ->
lists:map(fun({K, V}) when is_binary(V) -> {K, V};
({K, V}) when is_list(V) -> {K, iolist_to_binary(V)} end, Headers).

%% a request specyfying credentials is directed to client http listener
fusco_request(Role, {Method, {User, Password}}, Path, Body) ->
Basic = list_to_binary("Basic " ++ base64:encode_to_string(to_list(User) ++ ":"++ to_list(Password))),
fusco_request(#{ role := Role, method := Method, creds := {User, Password},
path := Path, body := Body, server := Server }) ->
EncodedAuth = base64:encode_to_string(to_list(User) ++ ":"++ to_list(Password)),
Basic = list_to_binary("Basic " ++ EncodedAuth),
Headers = [{<<"authorization">>, Basic}],
fusco_request(Method, Path, Body, Headers, get_port(Role), get_ssl_status(Role));
fusco_request(Method, Path, Body, Headers,
get_port(Role, Server), get_ssl_status(Role, Server));
%% without them it is for admin (secure) interface
fusco_request(Role, Method, Path, Body) ->
fusco_request(Method, Path, Body, [], get_port(Role), get_ssl_status(Role)).
fusco_request(#{ role := Role, method := Method, path := Path, body := Body, server := Server }) ->
fusco_request(Method, Path, Body, [], get_port(Role, Server), get_ssl_status(Role, Server)).

fusco_request(Method, Path, Body, HeadersIn, Port, SSL) ->
{ok, Client} = fusco_cp:start_link({"localhost", Port, SSL}, [], 1),
Expand All @@ -159,20 +191,22 @@ fusco_request(Method, Path, Body, HeadersIn, Port, SSL) ->
fusco_cp:stop(Client),
Result.

-spec get_port(role()) -> Port :: integer().
get_port(Role) ->
Listeners = rpc(mim(), ejabberd_config, get_local_option, [listen]),
[{PortIpNet, ejabberd_cowboy, _Opts}] = lists:filter(fun(Config) -> is_roles_config(Config, Role) end, Listeners),
-spec get_port(Role :: role(), Server :: atom()) -> Port :: integer().
get_port(Role, Server) ->
Listeners = rpc(Server, ejabberd_config, get_local_option, [listen]),
[{PortIpNet, ejabberd_cowboy, _Opts}] =
lists:filter(fun(Config) -> is_roles_config(Config, Role) end, Listeners),
case PortIpNet of
{Port, _Host, _Net} -> Port;
{Port, _Host} -> Port;
Port -> Port
end.

-spec get_ssl_status(role()) -> boolean().
get_ssl_status(Role) ->
Listeners = rpc(mim(), ejabberd_config, get_local_option, [listen]),
[{_PortIpNet, _Module, Opts}] = lists:filter(fun (Opts) -> is_roles_config(Opts, Role) end, Listeners),
-spec get_ssl_status(Role :: role(), Server :: atom()) -> boolean().
get_ssl_status(Role, Server) ->
Listeners = rpc(Server, ejabberd_config, get_local_option, [listen]),
[{_PortIpNet, _Module, Opts}] =
lists:filter(fun (Opts) -> is_roles_config(Opts, Role) end, Listeners),
lists:keymember(ssl, 1, Opts).

% @doc Changes the control credentials for admin by restarting the listener
Expand Down Expand Up @@ -403,4 +437,4 @@ make_malformed_msg_stanza_with_props(ToJID,MsgID) ->
<value type='long'>1234567890</value>
</property2>
</properties>
</message>">>).
</message>">>).
2 changes: 2 additions & 0 deletions rel/files/mongooseim.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -646,6 +646,8 @@

{all_metrics_are_global, {{{all_metrics_are_global}}} }.

{{{cowboy_server_name}}}

%%%. ========
%%%' SERVICES

Expand Down
1 change: 1 addition & 0 deletions rel/mim2.vars.config
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
" {password, \"secret\"}\n"
" ]}"}.
{all_metrics_are_global, true}.
{cowboy_server_name, "{cowboy_server_name, \"Classified\"}."}.
{c2s_dhfile, ",{dhfile, \"priv/ssl/fake_dh_server.pem\"}"}.
{s2s_dhfile, ",{dhfile, \"priv/ssl/fake_dh_server.pem\"}"}.

Expand Down
2 changes: 2 additions & 0 deletions src/config/mongoose_config_parser.erl
Original file line number Diff line number Diff line change
Expand Up @@ -298,6 +298,8 @@ process_term(Term, State) ->
add_option(sasl_mechanisms, Mechanisms, State);
{all_metrics_are_global, Value} ->
add_option(all_metrics_are_global, Value, State);
{cowboy_server_name, Value} ->
add_option(cowboy_server_name, Value, State);
{services, Value} ->
add_option(services, Value, State);
{_Opt, _Val} ->
Expand Down
Loading