diff --git a/include/maxscale/users.h b/include/maxscale/users.h index f213f8373..83ad092c8 100644 --- a/include/maxscale/users.h +++ b/include/maxscale/users.h @@ -21,6 +21,7 @@ #include #include #include +#include #include 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 * diff --git a/server/core/test/testusers.cc b/server/core/test/testusers.cc index 3453e0e71..5af85ec34 100644 --- a/server/core/test/testusers.cc +++ b/server/core/test/testusers.cc @@ -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(); diff --git a/server/core/users.cc b/server/core/users.cc index d6af61db3..b5bbfec05 100644 --- a/server/core/users.cc +++ b/server/core/users.cc @@ -20,10 +20,48 @@ #include #include #include +#include +#include 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_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); + return u->to_json(); +} + +USERS* users_from_json(json_t* json) +{ + return reinterpret_cast(Users::from_json(json)); +} + bool users_find(USERS* users, const char* user) { Users* u = reinterpret_cast(users);