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