MXS-1354: Add JSON serialization of users

The users can now be dumped and loaded as JSON objects. This allows easier
parsing and handling of users while still retaining the possibility to
manually edit the output. Added tests for dumping and loading the JSON
form users.

Also fixed a deadlock in Users::remove() where the same lock was acquired
twice and a faulty test case where failed authentication was expected to
work.
This commit is contained in:
Markus Mäkelä 2017-08-15 15:16:50 +03:00
parent 253d6d211f
commit 3aebe0f91e
3 changed files with 136 additions and 3 deletions

View File

@ -21,6 +21,7 @@
#include <maxscale/dcb.h>
#include <maxscale/listener.h>
#include <maxscale/service.h>
#include <maxscale/jansson.h>
#include <openssl/sha.h>
MXS_BEGIN_DECLS
@ -28,6 +29,7 @@ MXS_BEGIN_DECLS
/** User account types */
enum account_type
{
ACCOUNT_UNKNOWN,
ACCOUNT_BASIC, /**< Allows read-only access */
ACCOUNT_ADMIN /**< Allows complete access */
};
@ -126,6 +128,26 @@ bool users_promote(USERS* users, const char* user);
*/
bool users_demote(USERS* users, const char* user);
/**
* Dump users as JSON
*
* The resulting JSON can be loaded later to restore the users.
*
* @param users Users to dump
*
* @return JSON form of the users that can be used for serialization
*/
json_t* users_to_json(USERS *users);
/**
* Load users from JSON
*
* @param json JSON to load
*
* @return The loaded users
*/
USERS* users_from_json(json_t* json);
/**
* @brief Default user loading function
*

View File

@ -56,7 +56,7 @@ static int test1()
ss_info_dassert(rv, "Fetch valid user must not return NULL");
rv = users_auth(users, "username", "newauth");
mxs_log_flush_sync();
ss_info_dassert(rv, "Fetch valid user must not return NULL");
ss_info_dassert(rv == 0, "Fetch invalid user must return NULL");
ss_dfprintf(stderr, "\t..done\nAdd another user");
rv = users_add(users, "username2", "authorisation2", ACCOUNT_ADMIN);
@ -66,6 +66,15 @@ static int test1()
rv = users_delete(users, "username");
mxs_log_flush_sync();
ss_info_dassert(rv, "Should delete just one user");
ss_dfprintf(stderr, "\t..done\nDump users table.");
json_t* dump = users_to_json(users);
ss_info_dassert(dump, "Users should be dumped");
USERS* loaded_users = users_from_json(dump);
ss_info_dassert(dump, "Users should be loaded");
rv = users_auth(loaded_users, "username2", "authorisation2");
ss_info_dassert(rv, "Loaded users should contain users");
ss_dfprintf(stderr, "\t..done\nFree user table.");
users_free(users);
mxs_log_flush_sync();

View File

@ -20,10 +20,48 @@
#include <maxscale/users.h>
#include <maxscale/authenticator.h>
#include <maxscale/spinlock.hh>
#include <maxscale/log_manager.h>
#include <maxscale/jansson.hh>
namespace
{
static const char STR_BASIC[] = "basic";
static const char STR_ADMIN[] = "admin";
static const char* account_type_to_str(account_type type)
{
switch (type)
{
case ACCOUNT_BASIC:
return STR_BASIC;
case ACCOUNT_ADMIN:
return STR_ADMIN;
default:
MXS_ERROR("Unknown enum account_type value: %d", (int)type);
ss_dassert(!true);
return "unknown";
}
}
static account_type json_to_account_type(json_t* json)
{
std::string str = json_string_value(json);
if (str == STR_BASIC)
{
return ACCOUNT_BASIC;
}
else if (str == STR_ADMIN)
{
return ACCOUNT_ADMIN;
}
MXS_ERROR("Unknown account type string: %s", str.c_str());
ss_dassert(!true);
return ACCOUNT_UNKNOWN;
}
struct UserInfo
{
UserInfo():
@ -68,10 +106,11 @@ public:
{
mxs::SpinLockGuard guard(m_lock);
bool rval = false;
UserMap::iterator it = m_data.find(user);
if (get(user))
if (it != m_data.end())
{
m_data.erase(user);
m_data.erase(it);
rval = true;
}
@ -161,9 +200,61 @@ public:
return m_data.size() > 0;
}
json_t* to_json() const
{
json_t* arr = json_array();
mxs::SpinLockGuard guard(m_lock);
for (UserMap::const_iterator it = m_data.begin(); it != m_data.end(); it++)
{
json_t* obj = json_object();
json_object_set_new(obj, CN_NAME, json_string(it->first.c_str()));
json_object_set_new(obj, CN_TYPE, json_string(account_type_to_str(it->second.permissions)));
json_object_set_new(obj, CN_PASSWORD, json_string(it->second.password.c_str()));
json_array_append_new(arr, obj);
}
return arr;
}
static Users* from_json(json_t* json)
{
Users* u = reinterpret_cast<Users*>(users_alloc());
u->load_json(json);
return u;
}
private:
void load_json(json_t* json)
{
// This function is always called in a single-threaded context
size_t i;
json_t* value;
json_array_foreach(json, i, value)
{
json_t* name = json_object_get(value, CN_NAME);
json_t* type = json_object_get(value, CN_TYPE);
json_t* password = json_object_get(value, CN_PASSWORD);
if (name && json_is_string(name) &&
type && json_is_string(type) &&
password && json_is_string(password) &&
json_to_account_type(type) != ACCOUNT_UNKNOWN)
{
add(json_string_value(name), json_string_value(password),
json_to_account_type(type));
}
else
{
MXS_ERROR("Corrupt JSON value in users file: %s", mxs::json_dump(value).c_str());
}
}
}
mxs::SpinLock m_lock;
UserMap m_data;
};
}
@ -193,6 +284,17 @@ bool users_delete(USERS *users, const char *user)
return u->remove(user);
}
json_t* users_to_json(USERS *users)
{
Users* u = reinterpret_cast<Users*>(users);
return u->to_json();
}
USERS* users_from_json(json_t* json)
{
return reinterpret_cast<USERS*>(Users::from_json(json));
}
bool users_find(USERS* users, const char* user)
{
Users* u = reinterpret_cast<Users*>(users);