Masking: Add MaskingRules

MXS-910: Initial implementation of rule handling, now only the
parsing of JSON file and the building of corresponding objects.
Rudimentary testing.
This commit is contained in:
Johan Wikman 2016-12-29 17:36:00 +02:00
parent 0b4c379539
commit dc87663c95
5 changed files with 1141 additions and 8 deletions

View File

@ -1,9 +1,18 @@
add_library(masking SHARED
maskingfilter.cc
maskingfiltersession.cc
)
if (JANSSON_FOUND)
add_library(masking SHARED
maskingfilter.cc
maskingfiltersession.cc
maskingrules.cc
)
target_link_libraries(masking maxscale-common)
set_target_properties(masking PROPERTIES VERSION "1.0.0")
set_target_properties(masking PROPERTIES LINK_FLAGS -Wl,-z,defs)
install_module(masking experimental)
target_link_libraries(masking maxscale-common jansson)
set_target_properties(masking PROPERTIES VERSION "1.0.0")
set_target_properties(masking PROPERTIES LINK_FLAGS -Wl,-z,defs)
install_module(masking experimental)
if(BUILD_TESTS)
add_subdirectory(test)
endif()
else()
message(STATUS "No Jansson libraries found, not building cache filter.")
endif()

View File

@ -0,0 +1,704 @@
/*
* Copyright (c) 2016 MariaDB Corporation Ab
*
* Use of this software is governed by the Business Source License included
* in the LICENSE.TXT file and at www.mariadb.com/bsl.
*
* Change Date: 2019-07-01
*
* On the date above, in accordance with the Business Source License, use
* of this software will be governed by version 2 or later of the General
* Public License.
*/
#include "maskingrules.hh"
#include <errno.h>
#include <string.h>
#include <maxscale/debug.h>
#include <maxscale/jansson.hh>
#include <maxscale/mysql_utils.h>
#include <maxscale/pcre2.hh>
#include <maxscale/utils.hh>
using std::auto_ptr;
using std::string;
using std::vector;
using std::tr1::shared_ptr;
using maxscale::Closer;
namespace
{
static const char KEY_APPLIES_TO[] = "applies_to";
static const char KEY_COLUMN[] = "column";
static const char KEY_DATABASE[] = "database";
static const char KEY_EXEMPTED[] = "exempted";
static const char KEY_FILL[] = "fill";
static const char KEY_REPLACE[] = "replace";
static const char KEY_RULES[] = "rules";
static const char KEY_TABLE[] = "table";
static const char KEY_VALUE[] = "value";
static const char KEY_WITH[] = "with";
/**
* @class AccountVerbatim
*
* Implementation of @c MaskingRules::Rule::Account that compares user and
* host names verbatim, that is, without regexp matching.
*/
class AccountVerbatim : public MaskingRules::Rule::Account
{
public:
~AccountVerbatim()
{
}
static shared_ptr<MaskingRules::Rule::Account> create(const string& user, const string& host)
{
return shared_ptr<MaskingRules::Rule::Account>(new AccountVerbatim(user, host));
}
string user() const
{
return m_user;
}
string host() const
{
return m_host;
}
bool matches(const char* zUser, const char* zHost) const
{
ss_dassert(zUser);
ss_dassert(zHost);
return
(m_user.empty() || (m_user == zUser)) &&
(m_host.empty() || (m_host == zHost));
}
private:
AccountVerbatim(const string& user, const string& host)
: m_user(user)
, m_host(host)
{
}
AccountVerbatim(const AccountVerbatim&);
AccountVerbatim& operator = (const AccountVerbatim&);
private:
string m_user;
string m_host;
};
/**
* @class AccountRegexp
*
* Implementation of @c MaskingRules::Rule::Account that compares user names
* verbatim, that is, without regexp matching, and host names using regexp
* matching.
*/
class AccountRegexp : public MaskingRules::Rule::Account
{
public:
~AccountRegexp()
{
pcre2_match_data_free(m_pData);
pcre2_code_free(m_pCode);
}
static shared_ptr<MaskingRules::Rule::Account> create(const string& user, const string& host)
{
shared_ptr<MaskingRules::Rule::Account> sAccount;
int errcode;
PCRE2_SIZE erroffset;
pcre2_code* pCode = pcre2_compile((PCRE2_SPTR)host.c_str(), PCRE2_ZERO_TERMINATED, 0,
&errcode, &erroffset, NULL);
if (pCode)
{
Closer<pcre2_code*> code(pCode);
pcre2_match_data* pData = pcre2_match_data_create_from_pattern(pCode, NULL);
if (pData)
{
Closer<pcre2_match_data*> data(pData);
sAccount = shared_ptr<AccountRegexp>(new AccountRegexp(user, host, pCode, pData));
// Ownership of pCode and pData has been moved to the
// AccountRegexp instance.
data.release();
code.release();
}
else
{
MXS_ERROR("PCRE2 match data creation failed. Most likely due to a "
"lack of available memory.");
}
}
else
{
PCRE2_UCHAR errbuf[512];
pcre2_get_error_message(errcode, errbuf, sizeof(errbuf));
MXS_ERROR("Regex compilation failed at %d for regex '%s': %s",
(int)erroffset, host.c_str(), errbuf);
}
return sAccount;
}
string user() const
{
return m_user;
}
string host() const
{
return m_host;
}
bool matches(const char* zUser, const char* zHost) const
{
ss_dassert(zUser);
ss_dassert(zHost);
return
(m_user.empty() || (m_user == zUser)) &&
pcre2_match(m_pCode, (PCRE2_SPTR)zHost, 0, 0, 0, m_pData, NULL) >= 0;
}
private:
AccountRegexp(const string& user,
const string& host,
pcre2_code* pCode,
pcre2_match_data* pData)
: m_user(user)
, m_host(host)
, m_pCode(pCode)
, m_pData(pData)
{
}
AccountRegexp(const AccountRegexp&);
AccountRegexp& operator = (const AccountRegexp&);
private:
string m_user;
string m_host;
pcre2_code* m_pCode;
pcre2_match_data* m_pData;
};
/**
* Create MaxskingRules::Rule::Account instance
*
* @param zAccount The account name as specified in the JSON rules file.
*
* @return Either an AccountVerbatim or AccountRegexp, depending on whether
* the provided account name contains wildcards or not.
*/
shared_ptr<MaskingRules::Rule::Account> create_account(const char* zAccount)
{
shared_ptr<MaskingRules::Rule::Account> sAccount;
size_t len = strlen(zAccount);
char account[len + 1];
strcpy(account, zAccount);
char* zAt = strchr(account, '@');
char* zUser = account;
char* zHost = NULL;
if (zAt)
{
*zAt = 0;
zHost = zAt + 1;
}
if (mxs_mysql_trim_quotes(zUser))
{
char pcre_host[2 * len + 1]; // Surely enough
mxs_mysql_name_kind_t rv = MXS_MYSQL_NAME_WITHOUT_WILDCARD;
if (zHost)
{
if (mxs_mysql_trim_quotes(zHost))
{
rv = mxs_mysql_name_to_pcre(pcre_host, zHost, MXS_PCRE_QUOTE_WILDCARD);
if (rv == MXS_MYSQL_NAME_WITH_WILDCARD)
{
zHost = pcre_host;
}
}
else
{
MXS_ERROR("Could not trim quotes from host part of %s.", zAccount);
zHost = NULL;
}
}
else
{
zHost = const_cast<char*>("");
}
if (zHost)
{
if (rv == MXS_MYSQL_NAME_WITH_WILDCARD)
{
sAccount = AccountRegexp::create(zUser, zHost);
}
else
{
sAccount = AccountVerbatim::create(zUser, zHost);
}
}
}
else
{
MXS_ERROR("Could not trim quotes from user part of %s.", zAccount);
}
return sAccount;
}
/**
* Converts a list of account names into a vector of Account instances.
*
* @param zName The key of the JSON array we are processing (error reporting).
* @param pString A JSON array of account names.
* @param accounts Vector of Account instances, to be filled by this function.
*
* @return True, if all account names could be converted, false otherwise.
*/
bool get_accounts(const char* zName,
json_t* pStrings,
vector<shared_ptr<MaskingRules::Rule::Account> >& accounts)
{
ss_dassert(json_is_array(pStrings));
bool success = true;
size_t n = json_array_size(pStrings);
size_t i = 0;
while (success && (i < n))
{
json_t* pString = json_array_get(pStrings, i);
ss_dassert(pString);
if (json_is_string(pString))
{
shared_ptr<MaskingRules::Rule::Account> sAccount = create_account(json_string_value(pString));
if (sAccount)
{
accounts.push_back(sAccount);
}
else
{
success = false;
}
}
else
{
MXS_ERROR("An element in a '%s' array is not a string.", zName);
success = false;
}
++i;
}
return success;
}
/**
* Create a MaskingRules::Rule instance
*
* @param pColumn A JSON string containing a column name.
* @param pTable A JSON string containing a table name, or NULL.
* @param pDatabase A JSON string containing a table name, or NULL.
* @param pValue A JSON string representing the 'value' of a 'with' object from the rules file.
* @param pFill A JSON string representing the 'fill' of a 'with' object from the rules file.
* @param pApplies_to A JSON array representing the 'applies_to' account names.
* @param pExempted A JSON array representing the 'exempted' account names.
*
* @return A Rule instance or NULL in case of error.
*/
auto_ptr<MaskingRules::Rule> create_rule_from_elements(json_t* pColumn,
json_t* pTable,
json_t* pDatabase,
json_t* pValue,
json_t* pFill,
json_t* pApplies_to,
json_t* pExempted)
{
ss_dassert(pColumn && json_is_string(pColumn));
ss_dassert(!pTable || json_is_string(pTable));
ss_dassert(!pDatabase || json_is_string(pDatabase));
ss_dassert(pValue || pFill);
ss_dassert(!pValue || json_is_string(pValue));
ss_dassert(!pFill || json_is_string(pFill));
ss_dassert(!pApplies_to || json_is_array(pApplies_to));
ss_dassert(!pExempted || json_is_array(pExempted));
auto_ptr<MaskingRules::Rule> sRule;
string column(json_string_value(pColumn));
string table(pTable ? json_string_value(pTable) : "");
string database(pDatabase ? json_string_value(pDatabase) : "");
string value(pValue ? json_string_value(pValue) : "");
string fill(pFill ? json_string_value(pFill) : "");
bool ok = true;
vector<shared_ptr<MaskingRules::Rule::Account> > applies_to;
vector<shared_ptr<MaskingRules::Rule::Account> > exempted;
if (ok && pApplies_to)
{
ok = get_accounts(KEY_APPLIES_TO, pApplies_to, applies_to);
}
if (ok && pExempted)
{
ok = get_accounts(KEY_EXEMPTED, pExempted, exempted);
}
if (ok)
{
sRule = auto_ptr<MaskingRules::Rule>(new MaskingRules::Rule(column, table, database,
value, fill,
applies_to, exempted));
}
return sRule;
}
/**
* Create a MaskingRules::Rule instance
*
* @param pReplace A JSON object representing 'replace' of a rule from the rules file.
* @param pWith A JSON object representing 'with' of a rule from the rules file.
* @param pApplies_to A JSON object representing 'applies_to' of a rule from the rules file.
* @param pExempted A JSON object representing 'exempted' of a rule from the rules file.
*
* @return A Rule instance or NULL in case of error.
*/
auto_ptr<MaskingRules::Rule> create_rule_from_elements(json_t* pReplace,
json_t* pWith,
json_t* pApplies_to,
json_t* pExempted)
{
ss_dassert(pReplace && json_is_object(pReplace));
ss_dassert(pWith && json_is_object(pWith));
ss_dassert(!pApplies_to || json_is_array(pApplies_to));
ss_dassert(!pExempted || json_is_array(pExempted));
auto_ptr<MaskingRules::Rule> sRule;
json_t* pDatabase = json_object_get(pReplace, KEY_DATABASE);
json_t* pTable = json_object_get(pReplace, KEY_TABLE);
json_t* pColumn = json_object_get(pReplace, KEY_COLUMN);
// A column is mandatory; both table and database are optional.
if ((pColumn && json_is_string(pColumn)) &&
(!pTable || json_is_string(pTable)) &&
(!pDatabase || json_is_string(pDatabase)))
{
json_t* pValue = json_object_get(pWith, KEY_VALUE);
json_t* pFill = json_object_get(pWith, KEY_FILL);
if ((pValue || pFill) &&
(!pValue || json_is_string(pValue)) &&
(!pFill || json_is_string(pFill)))
{
sRule = create_rule_from_elements(pColumn, pTable, pDatabase,
pValue, pFill,
pApplies_to, pExempted);
}
else
{
MXS_ERROR("The '%s' object of a masking rule does not have either '%s' "
"or '%s' as keys, or their values are not strings.",
KEY_WITH, KEY_VALUE, KEY_FILL);
}
}
else
{
MXS_ERROR("The '%s' object of a masking rule does not have a '%s' key, or "
"the values of that key and/or possible '%s' and '%s' keys are "
"not strings.",
KEY_REPLACE, KEY_COLUMN, KEY_TABLE, KEY_DATABASE);
}
return sRule;
}
/**
* Create all MaskingRules::Rule instances
*
* @param pRules A JSON array representing 'rules' from the rules file.
* @param rules Vector where corresponding Rule instances will be pushed.
*
* @return True, if all rules could be created.
*/
bool create_rules_from_array(json_t* pRules, vector<shared_ptr<MaskingRules::Rule> >& rules)
{
ss_dassert(json_is_array(pRules));
bool parsed = true;
size_t n = json_array_size(pRules);
size_t i = 0;
while (parsed && (i < n))
{
json_t* pRule = json_array_get(pRules, i);
ss_dassert(pRule);
if (json_is_object(pRule))
{
auto_ptr<MaskingRules::Rule> sRule = MaskingRules::Rule::create_from(pRule);
if (sRule.get())
{
rules.push_back(shared_ptr<MaskingRules::Rule>(sRule.release()));
}
else
{
parsed = false;
}
}
else
{
MXS_ERROR("Element %lu of the '%s' array is not an object.", i, KEY_RULES);
parsed = false;
}
++i;
}
return parsed;
}
/**
* Create all MaskingRules::Rule instances
*
* @param pRoo A JSON object, representing the rules file.
* @param rules Vector where all Rule instances will be pushed.
*
* @return True, if all rules could be created.
*/
bool create_rules_from_root(json_t* pRoot, vector<shared_ptr<MaskingRules::Rule> >& rules)
{
bool parsed = false;
json_t* pRules = json_object_get(pRoot, KEY_RULES);
if (pRules)
{
if (json_is_array(pRules))
{
parsed = create_rules_from_array(pRules, rules);
}
else
{
MXS_ERROR("The masking rules object contains a `%s` key, but it is not an array.", KEY_RULES);
}
}
return parsed;
}
}
//
// MaskingRules::Rule::Account
//
MaskingRules::Rule::Account::Account()
{
}
MaskingRules::Rule::Account::~Account()
{
}
//
// MaskingRules::Rule
//
MaskingRules::Rule::Rule(const std::string& column,
const std::string& table,
const std::string& database,
const std::string& value,
const std::string& fill,
const std::vector<SAccount>& applies_to,
const std::vector<SAccount>& exempted)
: m_column(column)
, m_table(table)
, m_database(database)
, m_value(value)
, m_fill(fill)
, m_applies_to(applies_to)
, m_exempted(exempted)
{
}
MaskingRules::Rule::~Rule()
{
}
//static
auto_ptr<MaskingRules::Rule> MaskingRules::Rule::create_from(json_t* pRule)
{
ss_dassert(json_is_object(pRule));
auto_ptr<MaskingRules::Rule> sRule;
json_t* pReplace = json_object_get(pRule, KEY_REPLACE);
json_t* pWith = json_object_get(pRule, KEY_WITH);
json_t* pApplies_to = json_object_get(pRule, KEY_APPLIES_TO);
json_t* pExempted = json_object_get(pRule, KEY_EXEMPTED);
if (pReplace && pWith)
{
bool ok = true;
if (!json_is_object(pReplace))
{
MXS_ERROR("A masking rule contains a '%s' key, but the value is not an object.",
KEY_REPLACE);
ok = false;
}
if (!json_is_object(pWith))
{
MXS_ERROR("A masking rule contains a '%s' key, but the value is not an object.",
KEY_WITH);
ok = false;
}
if (pApplies_to && !json_is_array(pApplies_to))
{
MXS_ERROR("A masking rule contains a '%s' key, but the value is not an array.",
KEY_APPLIES_TO);
ok = false;
}
if (pExempted && !json_is_array(pExempted))
{
MXS_ERROR("A masking rule contains a '%s' key, but the value is not an array.",
KEY_EXEMPTED);
ok = false;
}
if (ok)
{
sRule = create_rule_from_elements(pReplace, pWith, pApplies_to, pExempted);
}
}
else
{
MXS_ERROR("A masking rule does not contain a '%s' and/or a '%s' key.", KEY_REPLACE, KEY_WITH);
}
return sRule;
}
//
// MaskingRules
//
MaskingRules::MaskingRules(json_t* pRoot, const std::vector<SRule>& rules)
: m_pRoot(pRoot)
, m_rules(rules)
{
json_incref(m_pRoot);
}
MaskingRules::~MaskingRules()
{
json_decref(m_pRoot);
}
//static
auto_ptr<MaskingRules> MaskingRules::load(const char* zPath)
{
auto_ptr<MaskingRules> sRules;
FILE* pFile = fopen(zPath, "r");
if (pFile)
{
Closer<FILE*> file(pFile);
json_error_t error;
json_t* pRoot = json_loadf(file.get(), JSON_DISABLE_EOF_CHECK, &error);
if (pRoot)
{
Closer<json_t*> root(pRoot);
sRules = create_from(root.get());
}
else
{
MXS_ERROR("Loading rules file failed: (%s:%d:%d): %s",
zPath, error.line, error.column, error.text);
}
}
else
{
char errbuf[MXS_STRERROR_BUFLEN];
MXS_ERROR("Could not open rules file %s for reading: %s",
zPath, strerror_r(errno, errbuf, sizeof(errbuf)));
}
return sRules;
}
//static
auto_ptr<MaskingRules> MaskingRules::parse(const char* zJson)
{
auto_ptr<MaskingRules> sRules;
json_error_t error;
json_t* pRoot = json_loads(zJson, JSON_DISABLE_EOF_CHECK, &error);
if (pRoot)
{
Closer<json_t*> root(pRoot);
sRules = create_from(root.get());
}
else
{
MXS_ERROR("Parsing rules failed: (%d:%d): %s",
error.line, error.column, error.text);
}
return sRules;
}
//static
std::auto_ptr<MaskingRules> MaskingRules::create_from(json_t* pRoot)
{
auto_ptr<MaskingRules> sRules;
vector<SRule> rules;
if (create_rules_from_root(pRoot, rules))
{
sRules = auto_ptr<MaskingRules>(new MaskingRules(pRoot, rules));
}
return sRules;
}

View File

@ -0,0 +1,166 @@
#pragma once
/*
* Copyright (c) 2016 MariaDB Corporation Ab
*
* Use of this software is governed by the Business Source License included
* in the LICENSE.TXT file and at www.mariadb.com/bsl.
*
* Change Date: 2019-07-01
*
* On the date above, in accordance with the Business Source License, use
* of this software will be governed by version 2 or later of the General
* Public License.
*/
#include <maxscale/cppdefs.hh>
#include <memory>
#include <tr1/memory>
#include <string>
#include <vector>
#include <jansson.h>
/**
* @class MaskingRules
*
* MaskingRules abstracts the rules of a masking filter.
*/
class MaskingRules
{
public:
/**
* @class Rule
*
* A Rule represents a single masking rule.
*/
class Rule
{
public:
/**
* @class Account
*
* An instance of this class is capable of answering the question
* whether the current user is subject to masking.
*/
class Account
{
public:
Account();
virtual ~Account();
virtual std::string user() const = 0;
virtual std::string host() const = 0;
/**
* Is a user subject to masking?
*
* @param zUser The name of the user.
* @param zHost The host of the user.
*
* @return True, if the data should be masked.
*/
virtual bool matches(const char* zUser, const char* zHost) const = 0;
};
typedef std::tr1::shared_ptr<Account> SAccount;
/**
* Constructor
*
* @param column The column value from the json file.
* @param table The table value from the json file.
* @param database The database value from the json file.
* @param value The value value from the json file.
* @param fill The file value from the json file.
* @param applies_to Account instances corresponding to the
* accounts listed in 'applies_to' in the json file.
* @param exempted Account instances corresponding to the
* accounts listed in 'exempted' in the json file.
*/
Rule(const std::string& column,
const std::string& table,
const std::string& database,
const std::string& value,
const std::string& fill,
const std::vector<SAccount>& applies_to,
const std::vector<SAccount>& exempted);
~Rule();
const std::string& column() const { return m_column; }
const std::string& table() const { return m_table; }
const std::string& database() const { return m_database; }
const std::string& value() const { return m_value; }
const std::string& fill() const { return m_fill; }
const std::vector<SAccount>& applies_to() const { return m_applies_to; }
const std::vector<SAccount>& exempted() const { return m_exempted; }
/**
* Create a Rule instance
*
* @param pRule A json object corresponding to a single
* rule in the rules json file.
*
* @return A Rule instance or NULL.
*/
static std::auto_ptr<Rule> create_from(json_t* pRule);
private:
Rule(const Rule&);
Rule& operator = (const Rule&);
private:
std::string m_column;
std::string m_table;
std::string m_database;
std::string m_value;
std::string m_fill;
std::vector<SAccount> m_applies_to;
std::vector<SAccount> m_exempted;
};
~MaskingRules();
/**
* Load rules
*
* @param zPath Path to rules file.
*
* @return A rules object, or NULL if the rules could not be loaded.
* or parsed.
*/
static std::auto_ptr<MaskingRules> load(const char* zPath);
/**
* Parse rules
*
* @param zPath Path to rules file.
*
* @return A rules object, or NULL if the rules could not be parsed.
*/
static std::auto_ptr<MaskingRules> parse(const char* zJson);
/**
* Create rules from JSON object.
*
* @param pRoot Pointer to JSON object.
*
* @return A rules object, or NULL if the rules could not be created.
*/
static std::auto_ptr<MaskingRules> create_from(json_t* pRoot);
typedef std::tr1::shared_ptr<Rule> SRule;
const std::vector<SRule>& rules() const
{
return m_rules;
}
private:
MaskingRules(json_t* pRoot, const std::vector<SRule>& rules);
private:
MaskingRules(const MaskingRules&);
MaskingRules& operator = (const MaskingRules&);
private:
json_t* m_pRoot;
std::vector<SRule> m_rules;
};

View File

@ -0,0 +1,6 @@
include_directories(..)
add_executable(masking_testrules testrules.cc ../maskingrules.cc)
target_link_libraries(masking_testrules maxscale-common jansson)
add_test(TestMasking_rules masking_testrules)

View File

@ -0,0 +1,248 @@
/*
* Copyright (c) 2016 MariaDB Corporation Ab
*
* Use of this software is governed by the Business Source License included
* in the LICENSE.TXT file and at www.mariadb.com/bsl.
*
* Change Date: 2019-07-01
*
* On the date above, in accordance with the Business Source License, use
* of this software will be governed by version 2 or later of the General
* Public License.
*/
#include "maskingrules.hh"
#include <iostream>
#include <maxscale/debug.h>
using namespace std;
using namespace std::tr1;
const char valid_minimal[] =
"{"
" \"rules\": ["
" {"
" \"replace\": { "
" \"column\": \"a\" "
" },"
" \"with\": {"
" \"value\": \"blah\" "
" }"
" }"
" ]"
"}";
const char valid_maximal[] =
"{"
" \"rules\": ["
" {"
" \"replace\": { "
" \"column\": \"a\", "
" \"table\": \"b\", "
" \"database\": \"c\" "
" },"
" \"with\": {"
" \"value\": \"blah\", "
" \"fill\": \"blah\" "
" },"
" \"applies_to\": ["
" \"'alice'@'host'\","
" \"'bob'@'%'\","
" \"'cecil'@'%.123.45.2'\""
" ],"
" \"exempted\": ["
" \"'admin'\""
" ]"
" }"
" ]"
"}";
// Neither "replace", nor "with".
const char invalid1[] =
"{"
" \"rules\": ["
" {"
" \"applies_to\": ["
" \"'alice'@'host'\","
" \"'bob'@'%'\""
" ],"
" \"exempted\": ["
" \"'admin'\""
" ]"
" }"
" ]"
"}";
// No "column" in "replace"
const char invalid2[] =
"{"
" \"rules\": ["
" {"
" \"replace\": { "
" },"
" \"with\": { "
" \"value\": \"blah\", "
" }"
" }"
" ]"
"}";
// No "value" or "fill" in "with"
const char invalid3[] =
"{"
" \"rules\": ["
" {"
" \"replace\": { "
" \"column\": \"a\", "
" },"
" \"with\": {"
" },"
" }"
" ]"
"}";
struct rule_test
{
const char* zJson;
bool valid;
} rule_tests[] =
{
{ valid_minimal, true },
{ valid_maximal, true },
{ invalid1, false },
{ invalid2, false },
{ invalid3, false },
};
const size_t nRule_tests = (sizeof(rule_tests) / sizeof(rule_tests[0]));
int test_parsing()
{
int rc = EXIT_SUCCESS;
for (size_t i = 0; i < nRule_tests; i++)
{
const rule_test& test = rule_tests[i];
auto_ptr<MaskingRules> sRules = MaskingRules::parse(test.zJson);
if ((sRules.get() && !test.valid) || (!sRules.get() && test.valid))
{
rc = EXIT_FAILURE;
}
}
return rc;
}
// Valid, lot's of users.
const char valid_users[] =
"{"
" \"rules\": ["
" {"
" \"replace\": { "
" \"column\": \"a\" "
" },"
" \"with\": {"
" \"value\": \"blah\" "
" },"
" \"applies_to\": ["
" \"'alice'@'host'\","
" \"'bob'@'%'\","
" \"'cecil'@'%.123.45.2'\","
" \"'david'\","
" \"@'host'\""
" ],"
" \"exempted\": ["
" \"'admin'\""
" ]"
" }"
" ]"
"}";
struct expected_account
{
const char* zUser;
const char* zHost;
} expected_accounts[] =
{
{
"alice",
"host",
},
{
"bob",
".*"
},
{
"cecil",
".*\\.123\\.45\\.2"
},
{
"david",
""
},
{
"",
"host"
}
};
const size_t nExpected_accounts = (sizeof(expected_accounts)/sizeof(expected_accounts[0]));
int test_account_handling()
{
int rc = EXIT_SUCCESS;
auto_ptr<MaskingRules> sRules = MaskingRules::parse(valid_users);
ss_dassert(sRules.get());
const vector<shared_ptr<MaskingRules::Rule> >& rules = sRules->rules();
ss_dassert(rules.size() == 1);
shared_ptr<MaskingRules::Rule> sRule = rules[0];
const vector<shared_ptr<MaskingRules::Rule::Account> >& accounts = sRule->applies_to();
ss_dassert(accounts.size() == nExpected_accounts);
int j = 0;
for (vector<shared_ptr<MaskingRules::Rule::Account> >::const_iterator i = accounts.begin();
i != accounts.end();
++i)
{
const expected_account& account = expected_accounts[j];
string user = (*i)->user();
if (user != account.zUser)
{
cout << j << ": Expected \"" << account.zUser << "\", got \"" << user << "\"." << endl;
rc = EXIT_FAILURE;
}
string host = (*i)->host();
if (host != account.zHost)
{
cout << j << ": Expected \"" << account.zHost << "\", got \"" << host << "\"." << endl;
rc = EXIT_FAILURE;
}
++j;
}
return rc;
}
int main()
{
int rc = EXIT_SUCCESS;
if (mxs_log_init(NULL, ".", MXS_LOG_TARGET_DEFAULT))
{
rc = (test_parsing() == EXIT_FAILURE) ? EXIT_FAILURE : EXIT_SUCCESS;
rc = (test_account_handling() == EXIT_FAILURE) ? EXIT_FAILURE : EXIT_SUCCESS;
}
return rc;
}