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