320 lines
11 KiB
C++
320 lines
11 KiB
C++
/* Copyright (c) 2015, 2019, Oracle and/or its affiliates. All rights reserved.
|
|
|
|
This program is free software; you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License, version 2.0,
|
|
as published by the Free Software Foundation.
|
|
|
|
This program is also distributed with certain software (including
|
|
but not limited to OpenSSL) that is licensed under separate terms,
|
|
as designated in a particular file or component or in included license
|
|
documentation. The authors of MySQL hereby grant you an additional
|
|
permission to link the program and your derivative works with the
|
|
separately licensed software that they have included with MySQL.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License, version 2.0, for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program; if not, write to the Free Software
|
|
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */
|
|
|
|
#include <gmock/gmock.h>
|
|
#include <gtest/gtest.h>
|
|
#include <string>
|
|
|
|
#include "plugin/x/src/account_verification_handler.h"
|
|
#include "plugin/x/src/sql_user_require.h"
|
|
#include "plugin/x/src/xpl_resultset.h"
|
|
#include "unittest/gunit/xplugin/xpl/mock/ngs_general.h"
|
|
#include "unittest/gunit/xplugin/xpl/mock/session.h"
|
|
#include "unittest/gunit/xplugin/xpl/one_row_resultset.h"
|
|
|
|
namespace xpl {
|
|
|
|
#define ER_SUCCESS 0
|
|
#define NOT !
|
|
|
|
namespace test {
|
|
using namespace ::testing;
|
|
|
|
namespace {
|
|
const char *const EMPTY = "";
|
|
const char *const USER_NAME = "TEST";
|
|
const char *const USER_DB = "TEST_DB";
|
|
const char *const USER_IP = "100.20.20.10";
|
|
const char *const EXPECTED_HASH = "AABBCCDD";
|
|
const char *const NOT_EXPECTED_HASH = "ALA_MA_KOTA";
|
|
const bool REQUIRE_SECURE_TRANSPORT = true;
|
|
const bool ACCOUNT_LOCKED = true;
|
|
const bool PASSWORD_EXPIRED = true;
|
|
const bool DISCONNECT_ON_EXPIRED_PASSWORD = true;
|
|
const bool OFFLINE_MODE = true;
|
|
const char *const AUTH_PLUGIN_NAME = "mysql_native_password";
|
|
const char *const WRONG_AUTH_PLUGIN_NAME = "wrong_password";
|
|
} // namespace
|
|
|
|
class User_verification_test : public Test {
|
|
public:
|
|
StrictMock<xpl::test::Mock_client> mock_client;
|
|
StrictMock<ngs::test::Mock_session> mock_session;
|
|
StrictMock<ngs::test::Mock_vio> mock_connection;
|
|
StrictMock<ngs::test::Mock_sql_data_context> mock_sql_data_context;
|
|
ngs::test::Mock_account_verification *mock_account_verification{
|
|
ngs::allocate_object<StrictMock<ngs::test::Mock_account_verification>>()};
|
|
|
|
ngs::Authentication_info m_auth_info;
|
|
|
|
Account_verification_handler handler{
|
|
&mock_session, ngs::Account_verification_interface::Account_native,
|
|
mock_account_verification};
|
|
|
|
void SetUp() {
|
|
EXPECT_CALL(mock_session, data_context())
|
|
.WillRepeatedly(ReturnRef(mock_sql_data_context));
|
|
EXPECT_CALL(mock_session, client()).WillRepeatedly(ReturnRef(mock_client));
|
|
}
|
|
};
|
|
|
|
TEST_F(User_verification_test, everything_matches_and_hash_is_right) {
|
|
One_row_resultset data{NOT REQUIRE_SECURE_TRANSPORT,
|
|
EXPECTED_HASH,
|
|
AUTH_PLUGIN_NAME,
|
|
NOT ACCOUNT_LOCKED,
|
|
NOT PASSWORD_EXPIRED,
|
|
NOT DISCONNECT_ON_EXPIRED_PASSWORD,
|
|
NOT OFFLINE_MODE,
|
|
EMPTY,
|
|
EMPTY,
|
|
EMPTY,
|
|
EMPTY};
|
|
|
|
EXPECT_CALL(mock_sql_data_context, execute(_, _, _))
|
|
.WillOnce(DoAll(SetUpResultset(data), Return(ngs::Success())));
|
|
|
|
EXPECT_CALL(mock_client, connection())
|
|
.WillRepeatedly(ReturnRef(mock_connection));
|
|
|
|
EXPECT_CALL(*mock_account_verification,
|
|
verify_authentication_string(_, _, _, _))
|
|
.WillOnce(Return(true));
|
|
|
|
EXPECT_EQ(
|
|
ER_SUCCESS,
|
|
handler.verify_account(USER_NAME, USER_IP, EXPECTED_HASH, &m_auth_info)
|
|
.error);
|
|
}
|
|
|
|
TEST_F(User_verification_test, forwards_error_from_query_execution) {
|
|
const ngs::Error_code expected_error(ER_DATA_OUT_OF_RANGE, "");
|
|
|
|
EXPECT_CALL(mock_sql_data_context, execute(_, _, _))
|
|
.WillOnce(Return(expected_error));
|
|
|
|
EXPECT_EQ(
|
|
expected_error.error,
|
|
handler.verify_account(USER_NAME, USER_IP, EXPECTED_HASH, &m_auth_info)
|
|
.error);
|
|
}
|
|
|
|
TEST_F(User_verification_test, dont_match_anything_when_hash_isnt_right) {
|
|
One_row_resultset data{NOT REQUIRE_SECURE_TRANSPORT,
|
|
NOT_EXPECTED_HASH,
|
|
AUTH_PLUGIN_NAME,
|
|
NOT ACCOUNT_LOCKED,
|
|
NOT PASSWORD_EXPIRED,
|
|
NOT DISCONNECT_ON_EXPIRED_PASSWORD,
|
|
NOT OFFLINE_MODE,
|
|
EMPTY,
|
|
EMPTY,
|
|
EMPTY,
|
|
EMPTY};
|
|
|
|
EXPECT_CALL(mock_sql_data_context, execute(_, _, _))
|
|
.WillOnce(DoAll(SetUpResultset(data), Return(ngs::Success())));
|
|
|
|
EXPECT_CALL(*mock_account_verification,
|
|
verify_authentication_string(_, _, _, _))
|
|
.WillOnce(Return(false));
|
|
|
|
EXPECT_EQ(
|
|
ER_ACCESS_DENIED_ERROR,
|
|
handler.verify_account(USER_NAME, USER_IP, EXPECTED_HASH, &m_auth_info)
|
|
.error);
|
|
}
|
|
|
|
struct Test_param {
|
|
bool account_locked;
|
|
bool offline_mode;
|
|
bool password_expired;
|
|
std::string plugin_name;
|
|
int expected_error;
|
|
};
|
|
|
|
class User_verification_param_test : public User_verification_test,
|
|
public WithParamInterface<Test_param> {};
|
|
|
|
TEST_P(User_verification_param_test, User_verification_on_given_account_param) {
|
|
const Test_param ¶m = GetParam();
|
|
|
|
One_row_resultset data{NOT REQUIRE_SECURE_TRANSPORT,
|
|
EXPECTED_HASH,
|
|
param.plugin_name.c_str(),
|
|
param.account_locked,
|
|
param.password_expired,
|
|
NOT DISCONNECT_ON_EXPIRED_PASSWORD,
|
|
param.offline_mode,
|
|
EMPTY,
|
|
EMPTY,
|
|
EMPTY,
|
|
EMPTY};
|
|
|
|
EXPECT_CALL(mock_client, client_hostname_or_address())
|
|
.WillRepeatedly(Return(""));
|
|
|
|
EXPECT_CALL(mock_sql_data_context, execute(_, _, _))
|
|
.WillOnce(DoAll(SetUpResultset(data), Return(ngs::Success())));
|
|
|
|
if (param.plugin_name == AUTH_PLUGIN_NAME)
|
|
EXPECT_CALL(*mock_account_verification,
|
|
verify_authentication_string(_, _, _, _))
|
|
.WillOnce(Return(true));
|
|
|
|
EXPECT_EQ(
|
|
param.expected_error,
|
|
handler.verify_account(USER_NAME, USER_IP, EXPECTED_HASH, &m_auth_info)
|
|
.error);
|
|
}
|
|
|
|
Test_param combinations[] = {
|
|
{ACCOUNT_LOCKED, NOT OFFLINE_MODE, NOT PASSWORD_EXPIRED, AUTH_PLUGIN_NAME,
|
|
ER_ACCOUNT_HAS_BEEN_LOCKED},
|
|
{NOT ACCOUNT_LOCKED, NOT OFFLINE_MODE, PASSWORD_EXPIRED, AUTH_PLUGIN_NAME,
|
|
ER_MUST_CHANGE_PASSWORD_LOGIN},
|
|
{NOT ACCOUNT_LOCKED, OFFLINE_MODE, NOT PASSWORD_EXPIRED, AUTH_PLUGIN_NAME,
|
|
ER_SERVER_OFFLINE_MODE},
|
|
{NOT ACCOUNT_LOCKED, NOT OFFLINE_MODE, NOT PASSWORD_EXPIRED,
|
|
WRONG_AUTH_PLUGIN_NAME, ER_ACCESS_DENIED_ERROR}};
|
|
|
|
INSTANTIATE_TEST_CASE_P(User_verification, User_verification_param_test,
|
|
ValuesIn(combinations));
|
|
|
|
struct Test_param_connection_type {
|
|
bool requires_secure;
|
|
Connection_type type;
|
|
int expected_error;
|
|
};
|
|
|
|
class User_verification_param_test_with_connection_type_combinations
|
|
: public User_verification_test,
|
|
public WithParamInterface<Test_param_connection_type> {};
|
|
|
|
TEST_P(User_verification_param_test_with_connection_type_combinations,
|
|
User_verification_on_given_account_connection_type) {
|
|
const Test_param_connection_type ¶m = GetParam();
|
|
|
|
EXPECT_CALL(mock_client, connection())
|
|
.WillRepeatedly(ReturnRef(mock_connection));
|
|
|
|
if (param.requires_secure)
|
|
EXPECT_CALL(mock_connection, get_type()).WillOnce(Return(param.type));
|
|
|
|
EXPECT_CALL(*mock_account_verification,
|
|
verify_authentication_string(_, _, _, _))
|
|
.WillOnce(Return(true));
|
|
|
|
One_row_resultset data{param.requires_secure,
|
|
EXPECTED_HASH,
|
|
AUTH_PLUGIN_NAME,
|
|
NOT ACCOUNT_LOCKED,
|
|
NOT PASSWORD_EXPIRED,
|
|
NOT DISCONNECT_ON_EXPIRED_PASSWORD,
|
|
NOT OFFLINE_MODE,
|
|
EMPTY,
|
|
EMPTY,
|
|
EMPTY,
|
|
EMPTY};
|
|
|
|
EXPECT_CALL(mock_sql_data_context, execute(_, _, _))
|
|
.WillOnce(DoAll(SetUpResultset(data), Return(ngs::Success())));
|
|
|
|
EXPECT_EQ(
|
|
param.expected_error,
|
|
handler.verify_account(USER_NAME, USER_IP, EXPECTED_HASH, &m_auth_info)
|
|
.error);
|
|
}
|
|
|
|
Test_param_connection_type connection_combinations[] = {
|
|
{NOT REQUIRE_SECURE_TRANSPORT, Connection_tcpip, ER_SUCCESS},
|
|
{NOT REQUIRE_SECURE_TRANSPORT, Connection_namedpipe, ER_SUCCESS},
|
|
{NOT REQUIRE_SECURE_TRANSPORT, Connection_tls, ER_SUCCESS},
|
|
{NOT REQUIRE_SECURE_TRANSPORT, Connection_unixsocket, ER_SUCCESS},
|
|
{REQUIRE_SECURE_TRANSPORT, Connection_unixsocket, ER_SUCCESS},
|
|
{REQUIRE_SECURE_TRANSPORT, Connection_tls, ER_SUCCESS},
|
|
{REQUIRE_SECURE_TRANSPORT, Connection_tcpip, ER_SECURE_TRANSPORT_REQUIRED},
|
|
{REQUIRE_SECURE_TRANSPORT, Connection_namedpipe,
|
|
ER_SECURE_TRANSPORT_REQUIRED}};
|
|
|
|
INSTANTIATE_TEST_CASE_P(
|
|
User_verification,
|
|
User_verification_param_test_with_connection_type_combinations,
|
|
ValuesIn(connection_combinations));
|
|
|
|
struct Test_param_sasl_message {
|
|
std::string schema;
|
|
std::string user;
|
|
std::string password;
|
|
int expected_error;
|
|
std::string get_message() const {
|
|
return schema + '\0' + user + '\0' + password;
|
|
}
|
|
};
|
|
|
|
class Split_sasl_message_test
|
|
: public User_verification_test,
|
|
public WithParamInterface<Test_param_sasl_message> {
|
|
public:
|
|
StrictMock<ngs::test::Mock_authentication_interface> mock_authentication;
|
|
};
|
|
|
|
TEST_P(Split_sasl_message_test, Split_sasl_message_on_given_param) {
|
|
const Test_param_sasl_message ¶m = GetParam();
|
|
|
|
if (param.expected_error == ER_SUCCESS) {
|
|
EXPECT_CALL(mock_client, client_address());
|
|
EXPECT_CALL(mock_client, client_hostname());
|
|
EXPECT_CALL(mock_client, supports_expired_passwords());
|
|
EXPECT_CALL(mock_sql_data_context, password_expired())
|
|
.WillOnce(Return(false));
|
|
EXPECT_CALL(mock_session, data_context())
|
|
.WillOnce(ReturnRef(mock_sql_data_context));
|
|
EXPECT_CALL(
|
|
mock_sql_data_context,
|
|
authenticate(StrEq(param.user), _, _, StrEq(param.schema),
|
|
StrEq(param.password), Ref(mock_authentication), _))
|
|
.WillOnce(Return(ngs::Success()));
|
|
}
|
|
|
|
EXPECT_EQ(
|
|
param.expected_error,
|
|
handler
|
|
.authenticate(mock_authentication, &m_auth_info, param.get_message())
|
|
.error);
|
|
}
|
|
|
|
Test_param_sasl_message sasl_message[] = {
|
|
{EMPTY, EMPTY, EMPTY, ER_ACCESS_DENIED_ERROR},
|
|
{USER_DB, EMPTY, EMPTY, ER_ACCESS_DENIED_ERROR},
|
|
{EMPTY, USER_NAME, EMPTY, ER_SUCCESS},
|
|
{EMPTY, EMPTY, EXPECTED_HASH, ER_ACCESS_DENIED_ERROR},
|
|
{USER_DB, USER_NAME, EMPTY, ER_SUCCESS},
|
|
{EMPTY, USER_NAME, EXPECTED_HASH, ER_SUCCESS},
|
|
{USER_DB, EMPTY, EXPECTED_HASH, ER_ACCESS_DENIED_ERROR},
|
|
{USER_DB, USER_NAME, EXPECTED_HASH, ER_SUCCESS}};
|
|
|
|
INSTANTIATE_TEST_CASE_P(User_verification, Split_sasl_message_test,
|
|
ValuesIn(sasl_message));
|
|
} // namespace test
|
|
} // namespace xpl
|