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: -include_lib("common_test/include/ct.hrl"). 22: 23: -define(DOMAIN, <<"localhost">>). 24: -define(HOST_TYPE, <<"test host type">>). 25: -define(AUTH_HOST, "http://localhost:12000"). 26: -define(BASIC_AUTH, "softkitty:purrpurrpurr"). 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 = {http, host, auth, 79: [{strategy, random_worker}, {call_timeout, 5000}, {workers, 20}], 80: [{path_prefix, "/auth/"}, {http_opts, []}, {server, ?AUTH_HOST}]}, 81: HostTypes = [?HOST_TYPE, <<"another host type">>], 82: mongoose_wpool:start_configured_pools([Pool], HostTypes), 83: mongoose_wpool_http:init(), 84: ejabberd_auth_http:start(?HOST_TYPE) 85: end), 86: Config. 87: 88: end_per_suite(Config) -> 89: ejabberd_auth_http:stop(?HOST_TYPE), 90: ok = mim_ct_rest:stop(), 91: unset_opts(), 92: Config. 93: 94: init_per_group(cert_auth, Config) -> 95: Root = small_path_helper:repo_dir(Config), 96: SslDir = filename:join(Root, "tools/ssl"), 97: try 98: {ok, Cert1} = file:read_file(filename:join(SslDir, "mongooseim/cert.pem")), 99: {ok, Cert2} = file:read_file(filename:join(SslDir, "ca/cacert.pem")), 100: [{'Certificate', DerBin, not_encrypted} | _] = public_key:pem_decode(Cert2), 101: [{der_cert, DerBin}, {pem_cert1, Cert1}, {pem_cert2, Cert2} | Config] 102: catch 103: _:E -> 104: {skip, {E, SslDir, element(2, file:get_cwd())}} 105: end; 106: init_per_group(GroupName, Config) -> 107: Config2 = lists:keystore(scram_group, 1, Config, 108: {scram_group, GroupName == auth_requests_scram}), 109: set_opts(Config2), 110: mim_ct_rest:register(<<"alice">>, ?DOMAIN, do_scram(<<"makota">>, Config2)), 111: mim_ct_rest:register(<<"bob">>, ?DOMAIN, do_scram(<<"niema5klepki">>, Config2)), 112: Config2. 113: 114: end_per_group(cert_auth, Config) -> 115: Config; 116: end_per_group(_GroupName, Config) -> 117: mim_ct_rest:remove_user(<<"alice">>, ?DOMAIN), 118: mim_ct_rest:remove_user(<<"bob">>, ?DOMAIN), 119: Config. 120: 121: init_per_testcase(remove_user, Config) -> 122: mim_ct_rest:register(<<"toremove1">>, ?DOMAIN, do_scram(<<"pass">>, Config)), 123: mim_ct_rest:register(<<"toremove2">>, ?DOMAIN, do_scram(<<"pass">>, Config)), 124: Config; 125: init_per_testcase(cert_auth_fail, Config) -> 126: Cert = proplists:get_value(pem_cert1, Config), 127: mim_ct_rest:register(<<"cert_user">>, ?DOMAIN, Cert), 128: Config; 129: init_per_testcase(cert_auth_success, Config) -> 130: Cert1 = proplists:get_value(pem_cert1, Config), 131: Cert2 = proplists:get_value(pem_cert2, Config), 132: SeveralCerts = <<Cert1/bitstring, Cert2/bitstring>>, 133: mim_ct_rest:register(<<"cert_user">>, ?DOMAIN, SeveralCerts), 134: Config; 135: init_per_testcase(_CaseName, Config) -> 136: Config. 137: 138: end_per_testcase(try_register, Config) -> 139: mim_ct_rest:remove_user(<<"nonexistent">>, ?DOMAIN), 140: Config; 141: end_per_testcase(remove_user, Config) -> 142: mim_ct_rest:remove_user(<<"toremove1">>, ?DOMAIN), 143: mim_ct_rest:remove_user(<<"toremove2">>, ?DOMAIN), 144: Config; 145: end_per_testcase(cert_auth_fail, Config) -> 146: mim_ct_rest:remove_user(<<"cert_user">>, ?DOMAIN), 147: Config; 148: end_per_testcase(cert_auth_success, Config) -> 149: mim_ct_rest:remove_user(<<"cert_user">>, ?DOMAIN), 150: Config; 151: end_per_testcase(_CaseName, Config) -> 152: Config. 153: 154: %%-------------------------------------------------------------------- 155: %% Authentication tests 156: %%-------------------------------------------------------------------- 157: 158: check_password(_Config) -> 159: true = ejabberd_auth_http:check_password(?HOST_TYPE, <<"alice">>, 160: ?DOMAIN, <<"makota">>), 161: false = ejabberd_auth_http:check_password(?HOST_TYPE, <<"alice">>, 162: ?DOMAIN, <<"niemakota">>), 163: false = ejabberd_auth_http:check_password(?HOST_TYPE, <<"kate">>, 164: ?DOMAIN, <<"mapsa">>). 165: 166: set_password(_Config) -> 167: ok = ejabberd_auth_http:set_password(?HOST_TYPE, <<"alice">>, 168: ?DOMAIN, <<"mialakota">>), 169: true = ejabberd_auth_http:check_password(?HOST_TYPE, <<"alice">>, 170: ?DOMAIN, <<"mialakota">>), 171: ok = ejabberd_auth_http:set_password(?HOST_TYPE, <<"alice">>, 172: ?DOMAIN, <<"makota">>). 173: 174: try_register(_Config) -> 175: ok = ejabberd_auth_http:try_register(?HOST_TYPE, <<"nonexistent">>, 176: ?DOMAIN, <<"newpass">>), 177: true = ejabberd_auth_http:check_password(?HOST_TYPE, <<"nonexistent">>, 178: ?DOMAIN, <<"newpass">>), 179: {error, exists} = ejabberd_auth_http:try_register(?HOST_TYPE, <<"nonexistent">>, 180: ?DOMAIN, <<"anypass">>). 181: 182: % get_password + get_password_s 183: get_password(_Config) -> 184: case mongoose_scram:enabled(?HOST_TYPE) of 185: false -> 186: <<"makota">> = ejabberd_auth_http:get_password(?HOST_TYPE, <<"alice">>, ?DOMAIN), 187: <<"makota">> = ejabberd_auth_http:get_password_s(?HOST_TYPE, <<"alice">>, ?DOMAIN); 188: true -> 189: % map with SCRAM data 190: true = is_map(ejabberd_auth_http:get_password(?HOST_TYPE, <<"alice">>, ?DOMAIN)), 191: <<>> = ejabberd_auth_http:get_password_s(?HOST_TYPE, <<"alice">>, ?DOMAIN) 192: end, 193: false = ejabberd_auth_http:get_password(?HOST_TYPE, <<"anakin">>, ?DOMAIN), 194: <<>> = ejabberd_auth_http:get_password_s(?HOST_TYPE, <<"anakin">>, ?DOMAIN). 195: 196: does_user_exist(_Config) -> 197: true = ejabberd_auth_http:does_user_exist(?HOST_TYPE, <<"alice">>, ?DOMAIN), 198: false = ejabberd_auth_http:does_user_exist(?HOST_TYPE, <<"madhatter">>, ?DOMAIN). 199: 200: % remove_user/2 201: remove_user(_Config) -> 202: true = ejabberd_auth_http:does_user_exist(?HOST_TYPE, <<"toremove1">>, ?DOMAIN), 203: ok = ejabberd_auth_http:remove_user(?HOST_TYPE, <<"toremove1">>, ?DOMAIN), 204: false = ejabberd_auth_http:does_user_exist(?HOST_TYPE, <<"toremove1">>, ?DOMAIN). 205: 206: supported_sasl_mechanisms(Config) -> 207: Modules = [cyrsasl_plain, cyrsasl_digest, cyrsasl_external, 208: cyrsasl_scram_sha1, cyrsasl_scram_sha224, cyrsasl_scram_sha256, 209: cyrsasl_scram_sha384, cyrsasl_scram_sha512], 210: DigestSupported = case lists:keyfind(scram_group, 1, Config) of 211: {_, true} -> false; 212: _ -> true 213: end, 214: [true, DigestSupported, false, true, true, true, true, true] = 215: [ejabberd_auth_http:supports_sasl_module(?HOST_TYPE, Mod) || Mod <- Modules]. 216: 217: cert_auth_fail(Config) -> 218: Creds = creds_with_cert(Config, <<"cert_user">>), 219: {error, not_authorized} = ejabberd_auth_http:authorize(Creds). 220: 221: cert_auth_success(Config) -> 222: Creds = creds_with_cert(Config, <<"cert_user">>), 223: {ok, _} = ejabberd_auth_http:authorize(Creds). 224: 225: cert_auth_nonexistent(Config) -> 226: Creds = creds_with_cert(Config, <<"nonexistent">>), 227: {error, not_authorized} = ejabberd_auth_http:authorize(Creds). 228: 229: %%-------------------------------------------------------------------- 230: %% Helpers 231: %%-------------------------------------------------------------------- 232: creds_with_cert(Config, Username) -> 233: Cert = proplists:get_value(der_cert, Config), 234: NewCreds = mongoose_credentials:new(?DOMAIN, ?HOST_TYPE), 235: mongoose_credentials:extend(NewCreds, [{der_cert, Cert}, 236: {username, Username}]). 237: 238: set_opts(Config) -> 239: AuthOpts = case lists:keyfind(scram_group, 1, Config) of 240: {_, false} -> #{password_format => plain}; 241: _ -> #{} 242: end, 243: HttpOpts = #{basic_auth => ?BASIC_AUTH}, 244: mongoose_config:set_opt({auth, ?HOST_TYPE}, AuthOpts#{http => HttpOpts}). 245: 246: unset_opts() -> 247: mongoose_config:unset_opt({auth, ?HOST_TYPE}). 248: 249: do_scram(Pass, Config) -> 250: case lists:keyfind(scram_group, 1, Config) of 251: {_, true} -> 252: Iterations = mongoose_scram:iterations(?HOST_TYPE), 253: Scram = mongoose_scram:password_to_scram(?HOST_TYPE, Pass, Iterations), 254: mongoose_scram:serialize(Scram); 255: _ -> 256: Pass 257: end.