1: %%==============================================================================
    2: %% Copyright 2020 Erlang Solutions Ltd.
    3: %%
    4: %% Licensed under the Apache License, Version 2.0 (the "License");
    5: %% you may not use this file except in compliance with the License.
    6: %% You may obtain a copy of the License at
    7: %%
    8: %% http://www.apache.org/licenses/LICENSE-2.0
    9: %%
   10: %% Unless required by applicable law or agreed to in writing, software
   11: %% distributed under the License is distributed on an "AS IS" BASIS,
   12: %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   13: %% See the License for the specific language governing permissions and
   14: %% limitations under the License.
   15: %%==============================================================================
   16: -module(users_api_SUITE).
   17: -compile([export_all, nowarn_export_all]).
   18: 
   19: -include_lib("eunit/include/eunit.hrl").
   20: 
   21: -import(distributed_helper, [mim/0,
   22:                              require_rpc_nodes/1,
   23:                              rpc/4]).
   24: -import(rest_helper, [assert_status/2, simple_request/2, simple_request/3, simple_request/4]).
   25: -import(domain_helper, [domain/0]).
   26: -import(mongoose_helper, [auth_modules/0]).
   27: 
   28: -define(DOMAIN, (domain())).
   29: -define(PORT, (ct:get_config({hosts, mim, metrics_rest_port}))).
   30: -define(USERNAME, "http_guy").
   31: 
   32: %%--------------------------------------------------------------------
   33: %% Suite configuration
   34: %%--------------------------------------------------------------------
   35: 
   36: all() ->
   37:     [{group, transaction},
   38:      {group, negative}].
   39: 
   40: groups() ->
   41:     G = [{transaction, [{repeat_until_any_fail, 10}], [user_transaction]},
   42:          {negative, [], negative_calls_test_cases()}
   43:         ],
   44:     ct_helper:repeat_all_until_all_ok(G).
   45: 
   46: negative_calls_test_cases() ->
   47:     [
   48:         add_malformed_user,
   49:         add_user_without_proper_fields,
   50:         delete_non_existent_user
   51:     ].
   52: 
   53: init_per_suite(_Config) ->
   54:     case is_external_auth() of
   55:         true ->
   56:             {skip, "users api not compatible with external authentication"};
   57:         false ->
   58:             [{riak_auth, is_riak_auth()}]
   59:     end.
   60: 
   61: end_per_suite(_Config) ->
   62:     ok.
   63: 
   64: suite() ->
   65:     require_rpc_nodes([mim]).
   66: 
   67: %%--------------------------------------------------------------------
   68: %% users_api tests
   69: %%--------------------------------------------------------------------
   70: 
   71: user_transaction(Config) ->
   72:     Count1 = fetch_list_of_users(Config),
   73:     add_user(?USERNAME, <<"my_http_password">>),
   74:     Count2 = fetch_list_of_users(Config),
   75:     % add user again = change their password
   76:     % check idempotence
   77:     add_user(?USERNAME, <<"some_other_password">>),
   78:     Count2 = fetch_list_of_users(Config),
   79:     add_user("http_guy2", <<"my_http_password">>),
   80:     Count3 = fetch_list_of_users(Config),
   81:     delete_user(?USERNAME),
   82:     delete_user("http_guy2"),
   83:     Count1 = fetch_list_of_users(Config),
   84: 
   85:     ?assertEqual(Count2, Count1+1),
   86:     ?assertEqual(Count3, Count2+1),
   87: 
   88:     wait_for_user_removal(proplists:get_value(riak_auth, Config)).
   89: 
   90: add_malformed_user(_Config) ->
   91:     Path = unicode:characters_to_list(["/users/host/", ?DOMAIN, "/username/" ?USERNAME]),
   92:     % cannot use jiffy here, because the JSON is malformed
   93:     Res = simple_request(<<"PUT">>, Path, ?PORT,
   94:                          <<"{
   95:                         \"user\": {
   96:                             \"password\": \"my_http_password\"
   97:                   }">>),
   98:     assert_status(400, Res).
   99: 
  100: add_user_without_proper_fields(_Config) ->
  101:     Path = unicode:characters_to_list(["/users/host/", ?DOMAIN, "/username/" ?USERNAME]),
  102:     Body = jiffy:encode(#{<<"user">> => #{<<"pazzwourd">> => <<"my_http_password">>}}),
  103:     Res = simple_request(<<"PUT">>, Path, ?PORT, Body),
  104:     assert_status(422, Res).
  105: 
  106: delete_non_existent_user(_Config) ->
  107:     Path = unicode:characters_to_list(["/users/host/", ?DOMAIN, "/username/i_don_exist"]),
  108:     Res = simple_request(<<"DELETE">>, Path, ?PORT),
  109:     assert_status(404, Res).
  110: 
  111: %%--------------------------------------------------------------------
  112: %% internal functions
  113: %%--------------------------------------------------------------------
  114: 
  115: fetch_list_of_users(_Config) ->
  116:     Result = simple_request(<<"GET">>, unicode:characters_to_list(["/users/host/", ?DOMAIN]), ?PORT),
  117:     assert_status(200, Result),
  118:     {_S, H, B} = Result,
  119:     ?assertEqual(<<"application/json">>, proplists:get_value(<<"content-type">>, H)),
  120:     #{<<"count">> := Count, <<"users">> := _} = B,
  121:     ?assertEqual(2, maps:size(B)),
  122:     Count.
  123: 
  124: add_user(UserName, Password) ->
  125:     Path = unicode:characters_to_list(["/users/host/", ?DOMAIN, "/username/", UserName]),
  126:     Body = jiffy:encode(#{<<"user">> => #{<<"password">> => Password}}),
  127:     Res = simple_request(<<"PUT">>, Path, ?PORT, Body),
  128:     assert_status(204, Res),
  129:     Res.
  130: 
  131: delete_user(UserName) ->
  132:     Path = unicode:characters_to_list(["/users/host/", ?DOMAIN, "/username/", UserName]),
  133:     Res = simple_request(<<"DELETE">>, Path, ?PORT),
  134:     assert_status(204, Res),
  135:     Res.
  136: 
  137: is_external_auth() ->
  138:     lists:member(ejabberd_auth_external, auth_modules()).
  139: 
  140: is_riak_auth() ->
  141:     lists:member(ejabberd_auth_riak, auth_modules()).
  142: 
  143: wait_for_user_removal(false) ->
  144:     ok;
  145: wait_for_user_removal(_) ->
  146:     Domain = domain(),
  147:     try mongoose_helper:wait_until(
  148:             fun() ->
  149:                 rpc(mim(), ejabberd_auth_riak, get_vh_registered_users_number, [Domain])
  150:             end,
  151:             0,
  152:             #{ time_sleep => 500, time_left => 5000, name => rpc})
  153:     of
  154: 	    {ok, 0} ->
  155: 		    ok
  156:     catch
  157: 	_Error:Reason ->
  158: 		ct:pal("~p", [Reason]),
  159: 		ok
  160:     end.