1: %%============================================================================== 2: %% Copyright 2014 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: 17: -module(auth_http_SUITE). 18: -compile([export_all, nowarn_export_all]). 19: -author('piotr.nosek@erlang-solutions.com'). 20: 21: -define(DOMAIN, <<"localhost">>). 22: -define(HOST_TYPE, <<"test host type">>). 23: -define(AUTH_HOST, "http://localhost:12000"). 24: -define(BASIC_AUTH, "softkitty:purrpurrpurr"). 25: 26: -import(config_parser_helper, [config/2]). 27: 28: %%-------------------------------------------------------------------- 29: %% Suite configuration 30: %%-------------------------------------------------------------------- 31: 32: all() -> 33: [{group, auth_requests_plain}, {group, auth_requests_scram}]. 34: 35: groups() -> 36: [ 37: {cert_auth, cert_auth()}, 38: {auth_requests_plain, [sequence], all_tests()}, 39: {auth_requests_scram, [sequence], [{group, cert_auth} | all_tests()]} 40: ]. 41: 42: all_tests() -> 43: [ 44: check_password, 45: set_password, 46: try_register, 47: get_password, 48: does_user_exist, 49: remove_user, 50: supported_sasl_mechanisms 51: ]. 52: 53: cert_auth() -> 54: [ 55: cert_auth_fail, 56: cert_auth_success, 57: cert_auth_nonexistent 58: ]. 59: 60: suite() -> 61: []. 62: 63: %%-------------------------------------------------------------------- 64: %% Init & teardown 65: %%-------------------------------------------------------------------- 66: 67: init_per_suite(Config) -> 68: {ok, _} = application:ensure_all_started(jid), 69: set_opts(Config), 70: mim_ct_rest:start(?BASIC_AUTH, Config), 71: % Separate process needs to do this, because this one will terminate 72: % so will supervisor and children and ETS tables 73: mim_ct_rest:do( 74: fun() -> 75: mim_ct_sup:start_link(ejabberd_sup), 76: mongoose_wpool:ensure_started(), 77: % This would be started via outgoing_pools in normal case 78: Pool = config([outgoing_pools, http, auth], pool_opts()), 79: HostTypes = [?HOST_TYPE, <<"another host type">>], 80: mongoose_wpool:start_configured_pools([Pool], [], HostTypes), 81: mongoose_wpool_http:init(), 82: ejabberd_auth_http:start(?HOST_TYPE) 83: end), 84: Config. 85: 86: pool_opts() -> 87: #{scope => host_type, 88: opts => #{strategy => random_worker, call_timeout => 5000, workers => 20}, 89: conn_opts => #{host => ?AUTH_HOST, path_prefix => <<"/auth/">>}}. 90: 91: end_per_suite(Config) -> 92: ejabberd_auth_http:stop(?HOST_TYPE), 93: ok = mim_ct_rest:stop(), 94: unset_opts(), 95: Config. 96: 97: init_per_group(cert_auth, Config) -> 98: Root = small_path_helper:repo_dir(Config), 99: SslDir = filename:join(Root, "tools/ssl"), 100: try 101: {ok, Cert1} = file:read_file(filename:join(SslDir, "mongooseim/cert.pem")), 102: {ok, Cert2} = file:read_file(filename:join(SslDir, "ca/cacert.pem")), 103: [{'Certificate', DerBin, not_encrypted} | _] = public_key:pem_decode(Cert2), 104: [{der_cert, DerBin}, {pem_cert1, Cert1}, {pem_cert2, Cert2} | Config] 105: catch 106: _:E -> 107: {skip, {E, SslDir, element(2, file:get_cwd())}} 108: end; 109: init_per_group(GroupName, Config) -> 110: Config2 = lists:keystore(scram_group, 1, Config, 111: {scram_group, GroupName == auth_requests_scram}), 112: set_opts(Config2), 113: mim_ct_rest:register(<<"alice">>, ?DOMAIN, do_scram(<<"makota">>, Config2)), 114: mim_ct_rest:register(<<"bob">>, ?DOMAIN, do_scram(<<"niema5klepki">>, Config2)), 115: Config2. 116: 117: end_per_group(cert_auth, Config) -> 118: Config; 119: end_per_group(_GroupName, Config) -> 120: mim_ct_rest:remove_user(<<"alice">>, ?DOMAIN), 121: mim_ct_rest:remove_user(<<"bob">>, ?DOMAIN), 122: Config. 123: 124: init_per_testcase(remove_user, Config) -> 125: mim_ct_rest:register(<<"toremove1">>, ?DOMAIN, do_scram(<<"pass">>, Config)), 126: mim_ct_rest:register(<<"toremove2">>, ?DOMAIN, do_scram(<<"pass">>, Config)), 127: Config; 128: init_per_testcase(cert_auth_fail, Config) -> 129: Cert = proplists:get_value(pem_cert1, Config), 130: mim_ct_rest:register(<<"cert_user">>, ?DOMAIN, Cert), 131: Config; 132: init_per_testcase(cert_auth_success, Config) -> 133: Cert1 = proplists:get_value(pem_cert1, Config), 134: Cert2 = proplists:get_value(pem_cert2, Config), 135: SeveralCerts = <<Cert1/bitstring, Cert2/bitstring>>, 136: mim_ct_rest:register(<<"cert_user">>, ?DOMAIN, SeveralCerts), 137: Config; 138: init_per_testcase(_CaseName, Config) -> 139: Config. 140: 141: end_per_testcase(try_register, Config) -> 142: mim_ct_rest:remove_user(<<"nonexistent">>, ?DOMAIN), 143: Config; 144: end_per_testcase(remove_user, Config) -> 145: mim_ct_rest:remove_user(<<"toremove1">>, ?DOMAIN), 146: mim_ct_rest:remove_user(<<"toremove2">>, ?DOMAIN), 147: Config; 148: end_per_testcase(cert_auth_fail, Config) -> 149: mim_ct_rest:remove_user(<<"cert_user">>, ?DOMAIN), 150: Config; 151: end_per_testcase(cert_auth_success, Config) -> 152: mim_ct_rest:remove_user(<<"cert_user">>, ?DOMAIN), 153: Config; 154: end_per_testcase(_CaseName, Config) -> 155: Config. 156: 157: %%-------------------------------------------------------------------- 158: %% Authentication tests 159: %%-------------------------------------------------------------------- 160: 161: check_password(_Config) -> 162: true = ejabberd_auth_http:check_password(?HOST_TYPE, <<"alice">>, 163: ?DOMAIN, <<"makota">>), 164: false = ejabberd_auth_http:check_password(?HOST_TYPE, <<"alice">>, 165: ?DOMAIN, <<"niemakota">>), 166: false = ejabberd_auth_http:check_password(?HOST_TYPE, <<"kate">>, 167: ?DOMAIN, <<"mapsa">>). 168: 169: set_password(_Config) -> 170: ok = ejabberd_auth_http:set_password(?HOST_TYPE, <<"alice">>, 171: ?DOMAIN, <<"mialakota">>), 172: true = ejabberd_auth_http:check_password(?HOST_TYPE, <<"alice">>, 173: ?DOMAIN, <<"mialakota">>), 174: ok = ejabberd_auth_http:set_password(?HOST_TYPE, <<"alice">>, 175: ?DOMAIN, <<"makota">>). 176: 177: try_register(_Config) -> 178: ok = ejabberd_auth_http:try_register(?HOST_TYPE, <<"nonexistent">>, 179: ?DOMAIN, <<"newpass">>), 180: true = ejabberd_auth_http:check_password(?HOST_TYPE, <<"nonexistent">>, 181: ?DOMAIN, <<"newpass">>), 182: {error, exists} = ejabberd_auth_http:try_register(?HOST_TYPE, <<"nonexistent">>, 183: ?DOMAIN, <<"anypass">>). 184: 185: % get_password + get_password_s 186: get_password(_Config) -> 187: case mongoose_scram:enabled(?HOST_TYPE) of 188: false -> 189: <<"makota">> = ejabberd_auth_http:get_password(?HOST_TYPE, <<"alice">>, ?DOMAIN), 190: <<"makota">> = ejabberd_auth_http:get_password_s(?HOST_TYPE, <<"alice">>, ?DOMAIN); 191: true -> 192: % map with SCRAM data 193: true = is_map(ejabberd_auth_http:get_password(?HOST_TYPE, <<"alice">>, ?DOMAIN)), 194: <<>> = ejabberd_auth_http:get_password_s(?HOST_TYPE, <<"alice">>, ?DOMAIN) 195: end, 196: false = ejabberd_auth_http:get_password(?HOST_TYPE, <<"anakin">>, ?DOMAIN), 197: <<>> = ejabberd_auth_http:get_password_s(?HOST_TYPE, <<"anakin">>, ?DOMAIN). 198: 199: does_user_exist(_Config) -> 200: true = ejabberd_auth_http:does_user_exist(?HOST_TYPE, <<"alice">>, ?DOMAIN), 201: false = ejabberd_auth_http:does_user_exist(?HOST_TYPE, <<"madhatter">>, ?DOMAIN). 202: 203: % remove_user/2 204: remove_user(_Config) -> 205: true = ejabberd_auth_http:does_user_exist(?HOST_TYPE, <<"toremove1">>, ?DOMAIN), 206: ok = ejabberd_auth_http:remove_user(?HOST_TYPE, <<"toremove1">>, ?DOMAIN), 207: false = ejabberd_auth_http:does_user_exist(?HOST_TYPE, <<"toremove1">>, ?DOMAIN). 208: 209: supported_sasl_mechanisms(Config) -> 210: Modules = [cyrsasl_plain, cyrsasl_digest, cyrsasl_external, 211: cyrsasl_scram_sha1, cyrsasl_scram_sha224, cyrsasl_scram_sha256, 212: cyrsasl_scram_sha384, cyrsasl_scram_sha512], 213: DigestSupported = case lists:keyfind(scram_group, 1, Config) of 214: {_, true} -> false; 215: _ -> true 216: end, 217: [true, DigestSupported, false, true, true, true, true, true] = 218: [ejabberd_auth_http:supports_sasl_module(?HOST_TYPE, Mod) || Mod <- Modules]. 219: 220: cert_auth_fail(Config) -> 221: Creds = creds_with_cert(Config, <<"cert_user">>), 222: {error, not_authorized} = ejabberd_auth_http:authorize(Creds). 223: 224: cert_auth_success(Config) -> 225: Creds = creds_with_cert(Config, <<"cert_user">>), 226: {ok, _} = ejabberd_auth_http:authorize(Creds). 227: 228: cert_auth_nonexistent(Config) -> 229: Creds = creds_with_cert(Config, <<"nonexistent">>), 230: {error, not_authorized} = ejabberd_auth_http:authorize(Creds). 231: 232: %%-------------------------------------------------------------------- 233: %% Helpers 234: %%-------------------------------------------------------------------- 235: creds_with_cert(Config, Username) -> 236: Cert = proplists:get_value(der_cert, Config), 237: NewCreds = mongoose_credentials:new(?DOMAIN, ?HOST_TYPE, #{}), 238: mongoose_credentials:extend(NewCreds, [{der_cert, Cert}, 239: {username, Username}]). 240: 241: set_opts(Config) -> 242: PasswordFormat = case lists:keyfind(scram_group, 1, Config) of 243: {_, false} -> plain; 244: _ -> scram 245: end, 246: HttpOpts = #{basic_auth => ?BASIC_AUTH}, 247: mongoose_config:set_opts(#{{auth, ?HOST_TYPE} => #{methods => [http], 248: password => #{format => PasswordFormat, 249: scram_iterations => 10}, 250: http => HttpOpts}}). 251: 252: unset_opts() -> 253: mongoose_config:erase_opts(). 254: 255: do_scram(Pass, Config) -> 256: case lists:keyfind(scram_group, 1, Config) of 257: {_, true} -> 258: Iterations = mongoose_scram:iterations(?HOST_TYPE), 259: Scram = mongoose_scram:password_to_scram(?HOST_TYPE, Pass, Iterations), 260: mongoose_scram:serialize(Scram); 261: _ -> 262: Pass 263: end.