1: -module(auth_internal_SUITE).
    2: 
    3: -compile([export_all, nowarn_export_all]).
    4: -include_lib("eunit/include/eunit.hrl").
    5: -include("scram.hrl").
    6: 
    7: all() ->
    8:     [passwords_as_records_are_still_supported,
    9:      passwords_in_plain_can_be_converted_to_scram].
   10: 
   11: init_per_suite(C) ->
   12:     application:ensure_all_started(jid),
   13:     ok = mnesia:create_schema([node()]),
   14:     ok = mnesia:start(),
   15:     AuthOpts = #{methods => [internal],
   16:                  internal => #{},
   17:                  password => #{format => scram, scram_iterations => 10}},
   18:     mongoose_config:set_opts(#{{auth, host_type()} => AuthOpts}),
   19:     ejabberd_auth_internal:start(host_type()),
   20:     C.
   21: 
   22: end_per_suite(_C) ->
   23:     ejabberd_auth_internal:stop(host_type()),
   24:     mongoose_config:erase_opts(),
   25:     mnesia:stop(),
   26:     mnesia:delete_schema([node()]).
   27: 
   28: init_per_testcase(_TC, Config) ->
   29:     mongoose_domain_core:start_link([{domain(), host_type()}], []),
   30:     Config.
   31: 
   32: end_per_testcase(_TC, _Config) ->
   33:     ok.
   34: 
   35: passwords_as_records_are_still_supported(_C) ->
   36:     %% given in mnesia there is a user with password in old scram format
   37:     {U, S, P} = gen_user(),
   38:     OldScramFormat = old_password_to_scram(P, 340),
   39:     UserRecord = {passwd, {U, S}, OldScramFormat},
   40:     mnesia:dirty_write(UserRecord),
   41:     %% when we read the password via ejabberd_auth_internal:get_password/3
   42:     NewScramMap = ejabberd_auth_internal:get_password(host_type(), U, S),
   43:     %% then new map with sha key is returned
   44:     ?assertMatch(#{iteration_count := _,
   45:                    sha := #{salt       := _,
   46:                             server_key := _,
   47:                             stored_key := _}}, NewScramMap),
   48:     %% even though in db there is old record stored
   49:     OldScram = mnesia:dirty_read({passwd, {U, S}}),
   50:     ?assertMatch([{passwd, _, {scram, _, _, _, _}}], OldScram).
   51: 
   52: passwords_in_plain_can_be_converted_to_scram(_C) ->
   53:     %% Given there in mnesia there are users
   54:     %% with plain text password
   55:     {U, S, P} = gen_user(),
   56:     UserRecord = {passwd, {U, S}, P},
   57:     mnesia:dirty_write(UserRecord),
   58:     %% and password in the old record format
   59:     {U2, S2, P2} = gen_user(),
   60:     OldScramFormat = old_password_to_scram(P2, 340),
   61:     UserRecord2 = {passwd, {U2, S2}, OldScramFormat},
   62:     mnesia:dirty_write(UserRecord2),
   63:     %% when the migration function is run
   64:     ejabberd_auth_internal:scram_passwords(),
   65:     AfterMigrationPlain = mnesia:dirty_read({passwd, {U, S}}),
   66:     %% then plain text passwords are converted to new map with sha and sha256
   67:     ?assertMatch([{passwd, _,
   68:                    #{iteration_count := _,
   69:                      sha    := #{salt       := _,
   70:                                  server_key := _,
   71:                                  stored_key := _},
   72:                      sha224 := #{salt       := _,
   73:                                  server_key := _,
   74:                                  stored_key := _},
   75:                      sha256 := #{salt       := _,
   76:                                  server_key := _,
   77:                                  stored_key := _},
   78:                      sha384 := #{salt       := _,
   79:                                  server_key := _,
   80:                                  stored_key := _},
   81:                      sha512 := #{salt       := _,
   82:                                  server_key := _,
   83:                                  stored_key := _}}}], AfterMigrationPlain),
   84:     %% and the old scram format remains the same
   85:     AfterMigrationScram = mnesia:dirty_read({passwd, {U2, S2}}),
   86:     ?assertMatch([{passwd, _,
   87:                  #{iteration_count := _,
   88:                    sha := #{salt       := _,
   89:                             server_key := _,
   90:                             stored_key := _}}}], AfterMigrationScram).
   91: 
   92: gen_user() ->
   93:     {base64:encode(crypto:strong_rand_bytes(5)),
   94:      domain(),
   95:      base64:encode(crypto:strong_rand_bytes(6))}.
   96: 
   97: old_password_to_scram(Password, IterationCount) ->
   98:     Salt = crypto:strong_rand_bytes(16),
   99:     SaltedPassword = fast_scram:salted_password(sha, jid:resourceprep(Password), Salt, IterationCount),
  100:     ClientKey = fast_scram:client_key(sha, SaltedPassword),
  101:     StoredKey = fast_scram:stored_key(sha, ClientKey),
  102:     ServerKey = fast_scram:server_key(sha, SaltedPassword),
  103:     #scram{storedkey = base64:encode(StoredKey),
  104:            serverkey = base64:encode(ServerKey),
  105:            salt = base64:encode(Salt),
  106:            iterationcount = IterationCount}.
  107: 
  108: domain() ->
  109:     <<"server">>.
  110: 
  111: host_type() ->
  112:     <<"test host type">>.