1: -module(auth_jwt_SUITE).
    2: -compile([export_all, nowarn_export_all]).
    3: 
    4: -include_lib("common_test/include/ct.hrl").
    5: 
    6: -define(DOMAIN, <<"localhost">>).
    7: -define(HOST_TYPE, <<"test host type">>).
    8: -define(USERNAME, <<"10857839">>).
    9: -define(WRONG_USERNAME, <<"alice">>).
   10: -define(JWT_KEY, <<"testtesttest">>).
   11: %%--------------------------------------------------------------------
   12: %% Suite configuration
   13: %%--------------------------------------------------------------------
   14: 
   15: all() ->
   16:     [{group, generic}, {group, public_key}].
   17: 
   18: groups() ->
   19:     [
   20:      {generic, [parallel], generic_tests()},
   21:      {public_key, [], public_key_tests()}
   22:     ].
   23: 
   24: generic_tests() ->
   25:     [
   26:      check_password_succeeds_for_correct_token,
   27:      check_password_fails_for_wrong_token,
   28:      check_password_fails_for_correct_token_but_wrong_username,
   29:      authorize,
   30:      does_user_exist,
   31:      supported_sasl_mechanisms
   32:     ].
   33: 
   34: public_key_tests() ->
   35:     [
   36:      check_password_succeeds_for_pubkey_signed_token
   37:     ].
   38: 
   39: suite() ->
   40:     [].
   41: 
   42: %%--------------------------------------------------------------------
   43: %% Init & teardown
   44: %%--------------------------------------------------------------------
   45: 
   46: init_per_suite(Config) ->
   47:     application:ensure_all_started(jid),
   48:     meck:new(ejabberd_auth, [no_link, passthrough]),
   49:     meck:expect(ejabberd_auth, auth_modules_for_host_type,
   50:                 fun(_) -> [] end),
   51:     Config.
   52: 
   53: end_per_suite(Config) ->
   54:     unset_auth_opts(),
   55:     meck:unload(ejabberd_auth),
   56:     Config.
   57: 
   58: init_per_group(public_key, Config) ->
   59:     Root = small_path_helper:repo_dir(Config),
   60:     PrivkeyPath = filename:join([Root, "tools", "ssl", "mongooseim", "privkey.pem"]),
   61:     PubkeyPath = filename:join([Root, "tools", "ssl", "mongooseim", "pubkey.pem"]),
   62:     {ok, PrivKey} = file:read_file(PrivkeyPath),
   63:     set_auth_opts({file, PubkeyPath}, "RS256", bookingNumber),
   64:     ok = ejabberd_auth_jwt:start(?HOST_TYPE),
   65:     [{priv_key, PrivKey} | Config];
   66: init_per_group(_, Config) ->
   67:     set_auth_opts({value, ?JWT_KEY}, "HS256", bookingNumber),
   68:     ok = ejabberd_auth_jwt:start(?HOST_TYPE),
   69:     Config.
   70: 
   71: end_per_group(_, Config) ->
   72:     ok = ejabberd_auth_jwt:stop(?HOST_TYPE),
   73:     Config.
   74: 
   75: 
   76: init_per_testcase(_CaseName, Config) ->
   77:     Config.
   78: 
   79: end_per_testcase(_CaseName, Config) ->
   80:     Config.
   81: 
   82: %%--------------------------------------------------------------------
   83: %% Authentication tests
   84: %%--------------------------------------------------------------------
   85: 
   86: check_password_succeeds_for_correct_token(_Config) ->
   87:     true = ejabberd_auth_jwt:check_password(?HOST_TYPE, ?USERNAME, ?DOMAIN,
   88:                                             generate_token(hs256, 0, ?JWT_KEY)).
   89: 
   90: check_password_fails_for_wrong_token(_C) ->
   91:     false = ejabberd_auth_jwt:check_password(?HOST_TYPE, ?USERNAME, ?DOMAIN,
   92:                                              generate_token(hs256, 60, ?JWT_KEY)).
   93: 
   94: check_password_fails_for_correct_token_but_wrong_username(_C) ->
   95:     false = ejabberd_auth_jwt:check_password(?HOST_TYPE, ?WRONG_USERNAME, ?DOMAIN,
   96:                                              generate_token(hs256, 0, ?JWT_KEY)).
   97: 
   98: authorize(_C) ->
   99:     Creds0 = mongoose_credentials:new(?DOMAIN, ?HOST_TYPE, #{}),
  100:     Creds = mongoose_credentials:extend(Creds0, [{username, ?USERNAME},
  101:                                                  {password, generate_token(hs256, 0, ?JWT_KEY)},
  102:                                                  {digest, fake},
  103:                                                  {digest_gen, fun(A) -> A end}]),
  104:     {ok, Creds2} = ejabberd_auth_jwt:authorize(Creds),
  105:     ejabberd_auth_jwt = mongoose_credentials:get(Creds2, auth_module).
  106: 
  107: does_user_exist(_Config) ->
  108:     true = ejabberd_auth_jwt:does_user_exist(?HOST_TYPE, <<"madhatter">>, ?DOMAIN).
  109: 
  110: supported_sasl_mechanisms(_C) ->
  111:     Modules = [cyrsasl_plain, cyrsasl_digest, cyrsasl_external,
  112:                cyrsasl_scram_sha1, cyrsasl_scram_sha224, cyrsasl_scram_sha256,
  113:                cyrsasl_scram_sha384, cyrsasl_scram_sha512],
  114:     [true, false, false, false, false, false, false, false] =
  115:         [ejabberd_auth_jwt:supports_sasl_module(?DOMAIN, Mod) || Mod <- Modules].
  116: 
  117: check_password_succeeds_for_pubkey_signed_token(C) ->
  118:     Key = proplists:get_value(priv_key, C),
  119:     true = ejabberd_auth_jwt:check_password(?HOST_TYPE, ?USERNAME, ?DOMAIN,
  120:                                             generate_token(rs256, 0, Key)).
  121: 
  122: %%--------------------------------------------------------------------
  123: %% Helpers
  124: %%--------------------------------------------------------------------
  125: 
  126: set_auth_opts(Secret, Algorithm, Key) ->
  127:     mongoose_config:set_opts(#{{auth, ?HOST_TYPE} => #{jwt => #{secret => Secret,
  128:                                                                 algorithm => Algorithm,
  129:                                                                 username_key => Key}}}).
  130: 
  131: unset_auth_opts() ->
  132:     mongoose_config:erase_opts().
  133: 
  134: generate_token(Alg, NbfDelta, Key) ->
  135:     Now = erlang:system_time(second),
  136:     Data = #{bookingNumber => ?USERNAME,
  137:              exp => Now + 60,
  138:              nbf => Now + NbfDelta,
  139:              iat => Now},
  140:     jwerl:sign(Data, Alg, Key).