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:     Config.
   49: 
   50: end_per_suite(Config) ->
   51:     unset_auth_opts(),
   52:     Config.
   53: 
   54: init_per_group(public_key, Config) ->
   55:     Root = small_path_helper:repo_dir(Config),
   56:     PrivkeyPath = filename:join([Root, "tools", "ssl", "mongooseim", "privkey.pem"]),
   57:     PubkeyPath = filename:join([Root, "tools", "ssl", "mongooseim", "pubkey.pem"]),
   58:     {ok, PrivKey} = file:read_file(PrivkeyPath),
   59:     set_auth_opts({file, PubkeyPath}, "RS256", bookingNumber),
   60:     ok = ejabberd_auth_jwt:start(?HOST_TYPE),
   61:     [{priv_key, PrivKey} | Config];
   62: init_per_group(_, Config) ->
   63:     set_auth_opts({value, ?JWT_KEY}, "HS256", bookingNumber),
   64:     ok = ejabberd_auth_jwt:start(?HOST_TYPE),
   65:     Config.
   66: 
   67: end_per_group(_, Config) ->
   68:     ok = ejabberd_auth_jwt:stop(?HOST_TYPE),
   69:     Config.
   70: 
   71: 
   72: init_per_testcase(_CaseName, Config) ->
   73:     Config.
   74: 
   75: end_per_testcase(_CaseName, Config) ->
   76:     Config.
   77: 
   78: %%--------------------------------------------------------------------
   79: %% Authentication tests
   80: %%--------------------------------------------------------------------
   81: 
   82: check_password_succeeds_for_correct_token(_Config) ->
   83:     true = ejabberd_auth_jwt:check_password(?HOST_TYPE, ?USERNAME, ?DOMAIN,
   84:                                             generate_token(hs256, 0, ?JWT_KEY)).
   85: 
   86: check_password_fails_for_wrong_token(_C) ->
   87:     false = ejabberd_auth_jwt:check_password(?HOST_TYPE, ?USERNAME, ?DOMAIN,
   88:                                              generate_token(hs256, 60, ?JWT_KEY)).
   89: 
   90: check_password_fails_for_correct_token_but_wrong_username(_C) ->
   91:     false = ejabberd_auth_jwt:check_password(?HOST_TYPE, ?WRONG_USERNAME, ?DOMAIN,
   92:                                              generate_token(hs256, 0, ?JWT_KEY)).
   93: 
   94: authorize(_C) ->
   95:     Creds0 = mongoose_credentials:new(?DOMAIN, ?HOST_TYPE),
   96:     Creds = mongoose_credentials:extend(Creds0, [{username, ?USERNAME},
   97:                                                  {password, generate_token(hs256, 0, ?JWT_KEY)},
   98:                                                  {digest, fake},
   99:                                                  {digest_gen, fun(A) -> A end}]),
  100:     {ok, Creds2} = ejabberd_auth_jwt:authorize(Creds),
  101:     ejabberd_auth_jwt = mongoose_credentials:get(Creds2, auth_module).
  102: 
  103: does_user_exist(_Config) ->
  104:     true = ejabberd_auth_jwt:does_user_exist(?HOST_TYPE, <<"madhatter">>, ?DOMAIN).
  105: 
  106: supported_sasl_mechanisms(_C) ->
  107:     Modules = [cyrsasl_plain, cyrsasl_digest, cyrsasl_external,
  108:                cyrsasl_scram_sha1, cyrsasl_scram_sha224, cyrsasl_scram_sha256,
  109:                cyrsasl_scram_sha384, cyrsasl_scram_sha512],
  110:     [true, false, false, false, false, false, false, false] =
  111:         [ejabberd_auth_jwt:supports_sasl_module(?DOMAIN, Mod) || Mod <- Modules].
  112: 
  113: check_password_succeeds_for_pubkey_signed_token(C) ->
  114:     Key = proplists:get_value(priv_key, C),
  115:     true = ejabberd_auth_jwt:check_password(?HOST_TYPE, ?USERNAME, ?DOMAIN,
  116:                                             generate_token(rs256, 0, Key)).
  117: 
  118: %%--------------------------------------------------------------------
  119: %% Helpers
  120: %%--------------------------------------------------------------------
  121: 
  122: set_auth_opts(Secret, Algorithm, Key) ->
  123:     mongoose_config:set_opt({auth, ?HOST_TYPE}, #{jwt => #{secret => Secret,
  124:                                                            algorithm => Algorithm,
  125:                                                            username_key => Key}}).
  126: 
  127: unset_auth_opts() ->
  128:     mongoose_config:unset_opt({auth, ?HOST_TYPE}).
  129: 
  130: generate_token(Alg, NbfDelta, Key) ->
  131:     Now = erlang:system_time(second),
  132:     Data = #{bookingNumber => ?USERNAME,
  133:              exp => Now + 60,
  134:              nbf => Now + NbfDelta,
  135:              iat => Now},
  136:     jwerl:sign(Data, Alg, Key).