Add email_hash to user db, model::table::user and a json interface to search for publickey by email hash

This commit is contained in:
Dario 2020-09-28 10:01:13 +02:00
parent 6a155285c1
commit 3614ed691c
12 changed files with 230 additions and 22 deletions

8
README
View File

@ -21,16 +21,18 @@ cd ../../../
cd dependencies/grpc
mkdir _build
cd _build
mkdir build
cd build
cmake ..
make
# under windows build at least release for protoc.exe and grpc c++ plugin
cd ../../../
./unix_parse_proto.sh
# get more dependencies with conan (need conan from https://conan.io/)
mkdir build && cd build
conan remote add inexor https://api.bintray.com/conan/inexorgame/inexor-conan
# // not used anymore
# conan remote add inexor https://api.bintray.com/conan/inexorgame/inexor-conan
# conan install .. -s build_type=Debug
conan install ..

View File

@ -6,6 +6,7 @@ CREATE TABLE `users` (
`password` bigint unsigned NOT NULL,
`pubkey` binary(32) DEFAULT NULL,
`privkey` binary(80) DEFAULT NULL,
`email_hash` binary(32) DEFAULT NULL,
`created` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
`email_checked` tinyint NOT NULL DEFAULT '0',
`passphrase_shown` tinyint NOT NULL DEFAULT '0',

View File

@ -198,6 +198,7 @@ int Gradido_LoginServer::main(const std::vector<std::string>& args)
// schedule email verification resend
controller::User::checkIfVerificationEmailsShouldBeResend(ServerConfig::g_CronJobsTimer);
controller::User::addMissingEmailHashes();
// HTTP Interface Server
// set-up a server socket

View File

@ -13,6 +13,7 @@
#include "JsonAdminEmailVerificationResend.h"
#include "JsonGetUserInfos.h"
#include "JsonUpdateUserInfos.h"
#include "JsonSearch.h"
JsonRequestHandlerFactory::JsonRequestHandlerFactory()
: mRemoveGETParameters("^/([a-zA-Z0-9_-]*)"), mLogging(Poco::Logger::get("requestLog"))
@ -56,6 +57,9 @@ Poco::Net::HTTPRequestHandler* JsonRequestHandlerFactory::createRequestHandler(c
else if (url_first_part == "/updateUserInfos") {
return new JsonUpdateUserInfos;
}
else if (url_first_part == "/search") {
return new JsonSearch;
}
return new JsonUnknown;
}

View File

@ -0,0 +1,89 @@
#include "JsonSearch.h"
#include "../lib/DataTypeConverter.h"
#include "../controller/User.h"
#include "../SingletonManager/SessionManager.h"
Poco::JSON::Object* JsonSearch::handle(Poco::Dynamic::Var params)
{
/*
'ask' = ['account_publickey' => '<email_blake2b_base64>']
*/
// incoming
Poco::JSON::Object::Ptr ask;
// if is json object
if (params.type() == typeid(Poco::JSON::Object::Ptr)) {
Poco::JSON::Object::Ptr paramJsonObject = params.extract<Poco::JSON::Object::Ptr>();
/// Throws a RangeException if the value does not fit
/// into the result variable.
/// Throws a NotImplementedException if conversion is
/// not available for the given type.
/// Throws InvalidAccessException if Var is empty.
try {
ask = paramJsonObject->getObject("ask");
}
catch (Poco::Exception& ex) {
return stateError("json exception", ex.displayText());
}
}
else {
return stateError("parameter format unknown");
}
if (ask.isNull()) {
return stateError("ask is zero or not an object");
}
Poco::JSON::Object* result = new Poco::JSON::Object;
result->set("state", "success");
Poco::JSON::Array jsonErrorsArray;
Poco::JSON::Object result_fields;
auto sm = SessionManager::getInstance();
auto mm = MemoryManager::getInstance();
for (auto it = ask->begin(); it != ask->end(); it++) {
std::string name = it->first;
auto value = it->second;
try {
if ("account_publickey" == name) {
if (!value.isString()) {
jsonErrorsArray.add("account_publickey isn't a string");
}
else {
MemoryBin* email_hash = nullptr;
if (sm->isValid(value, VALIDATE_ONLY_HEX)) {
email_hash = DataTypeConverter::hexToBin(value);
}
if (!email_hash) {
email_hash = DataTypeConverter::base64ToBin(value);
}
if (!email_hash) {
jsonErrorsArray.add("account_publickey isn't valid base64 or hex");
}
else {
auto user = controller::User::create();
user->load(email_hash);
mm->releaseMemory(email_hash);
auto user_model = user->getModel();
auto public_key_base64 = DataTypeConverter::binToBase64(user_model->getPublicKey(), user_model->getPublicKeySize());
result_fields.set("account_publickey", public_key_base64);
}
}
}
}
catch (Poco::Exception& ex) {
jsonErrorsArray.add("update parameter invalid");
}
}
result->set("errors", jsonErrorsArray);
result->set("results", result_fields);
result->set("state", "success");
return result;
}

View File

@ -0,0 +1,24 @@
#ifndef __JSON_INTERFACE_JSON_SEARCH_
#define __JSON_INTERFACE_JSON_SEARCH_
#include "JsonRequestHandler.h"
/*!
* @author Dario Rekowski
* @date 2020-09-28
* @brief search for public informations (no session_id needed), like account id for email hash
*
*
*/
class JsonSearch : public JsonRequestHandler
{
public:
Poco::JSON::Object* handle(Poco::Dynamic::Var params);
protected:
};
#endif // __JSON_INTERFACE_JSON_SEARCH_

View File

@ -228,16 +228,7 @@ bool SessionManager::releaseSession(int requestHandleSession)
//mWorkingMutex.unlock();
return false;
}
Session* session = it->second;
// simply delete session to overcome current crashes, it is a workaround for now
mRequestSessionMap.erase(requestHandleSession);
delete session;
return true;
// check if dead locked
if (session->tryLock()) {
session->unlock();

View File

@ -29,7 +29,7 @@ namespace controller {
inline bool deleteFromDB() { return mDBModel->deleteFromDB(); }
std::string HederaAccount::toShortSelectOptionName();
std::string toShortSelectOptionName();
inline Poco::AutoPtr<model::table::HederaAccount> getModel() { return _getModel<model::table::HederaAccount>(); }
inline const model::table::HederaAccount* getModel() const { return _getModel<model::table::HederaAccount>(); }

View File

@ -83,6 +83,11 @@ namespace controller {
return getModel()->loadFromDB("pubkey", pubkey);
}
int User::load(MemoryBin* emailHash)
{
Poco::Data::BLOB email_hash(*emailHash, crypto_generichash_BYTES);
return getModel()->loadFromDB("email_hash", email_hash);
}
const std::string& User::getPublicHex()
{
if (mPublicHex != "") {
@ -404,4 +409,58 @@ namespace controller {
return 0;
}
int User::addMissingEmailHashes()
{
auto cm = ConnectionManager::getInstance();
auto em = ErrorManager::getInstance();
static const char* function_name = "User::addMissingEmailHashes";
auto session = cm->getConnection(CONNECTION_MYSQL_LOGIN_SERVER);
Poco::Data::Statement select(session);
std::vector<Poco::Tuple<int, std::string>> results;
select << "select id, email from users "
<< "where email_hash IS NULL "
, Poco::Data::Keywords::into(results)
;
int result_count = 0;
try {
result_count = select.execute();
}
catch (Poco::Exception& ex) {
em->addError(new ParamError(function_name, "mysql error by select", ex.displayText().data()));
em->sendErrorsAsEmail();
//return -1;
}
if (0 == result_count) return 0;
std::vector<Poco::Tuple<Poco::Data::BLOB, int>> updates;
// calculate hashes
updates.reserve(results.size());
unsigned char email_hash[crypto_generichash_BYTES];
for (auto it = results.begin(); it != results.end(); it++) {
memset(email_hash, 0, crypto_generichash_BYTES);
auto id = it->get<0>();
auto email = it->get<1>();
crypto_generichash(email_hash, crypto_generichash_BYTES,
(const unsigned char*)email.data(), email.size(),
NULL, 0);
updates.push_back(Poco::Tuple<Poco::Data::BLOB, int>(Poco::Data::BLOB(email_hash, crypto_generichash_BYTES), id));
}
// update db
// reuse connection, I hope it's working
Poco::Data::Statement update(session);
update << "UPDATE users set email_hash = ? where id = ?"
, Poco::Data::Keywords::use(updates);
int updated_count = 0;
try {
updated_count = update.execute();
}
catch (Poco::Exception& ex) {
em->addError(new ParamError(function_name, "mysql error by update", ex.displayText().data()));
em->sendErrorsAsEmail();
}
return updated_count;
}
}

View File

@ -37,6 +37,9 @@ namespace controller {
// TODO: instead scheduling all, scheduling only for next day and run this function every day (own task for that)
static int checkIfVerificationEmailsShouldBeResend(const Poco::Util::Timer& timer);
//! \brief go through whole db and search for user without email hash and set this in db
static int addMissingEmailHashes();
//! \brief try to find correct passphrase for this user from db
//!
//! select entries from user_backups db table belonging to user
@ -53,6 +56,7 @@ namespace controller {
//! \return count of found rows, should be 1 or 0
inline size_t load(int user_id) { return getModel()->loadFromDB("id", user_id); }
int load(const unsigned char* pubkey_array);
int load(MemoryBin* emailHash);
Poco::JSON::Object getJson();
inline Poco::AutoPtr<model::table::User> getModel() { return _getModel<model::table::User>(); }

View File

@ -18,8 +18,9 @@ namespace model {
}
User::User(const std::string& email, const std::string& first_name, const std::string& last_name, Poco::UInt64 passwordHashed/* = 0*/, std::string languageKey/* = "de"*/)
: mEmail(email), mFirstName(first_name), mLastName(last_name), mPasswordHashed(passwordHashed), mEmailChecked(false), mLanguageKey(languageKey), mDisabled(false), mRole(ROLE_NOT_LOADED)
: mFirstName(first_name), mLastName(last_name), mPasswordHashed(passwordHashed), mEmailChecked(false), mLanguageKey(languageKey), mDisabled(false), mRole(ROLE_NOT_LOADED)
{
setEmail(email);
}
//id, first_name, last_name, email, pubkey, created, email_checked
@ -60,18 +61,31 @@ namespace model {
}
}
void User::setEmail(const std::string& email)
{
std::unique_lock<std::shared_mutex> _lock(mSharedMutex);
mEmail = email;
unsigned char email_hash[crypto_generichash_BYTES];
crypto_generichash(email_hash, crypto_generichash_BYTES,
(const unsigned char*)email.data(), email.size(),
NULL, 0);
mEmailHash = Poco::Nullable<Poco::Data::BLOB>(Poco::Data::BLOB(email_hash, crypto_generichash_BYTES));
}
Poco::Data::Statement User::_insertIntoDB(Poco::Data::Session session)
{
Poco::Data::Statement insert(session);
if (mPasswordHashed) {
insert << "INSERT INTO users (email, first_name, last_name, password, language) VALUES(?,?,?,?,?);",
use(mEmail), use(mFirstName), use(mLastName), bind(mPasswordHashed), use(mLanguageKey);
insert << "INSERT INTO users (email, first_name, last_name, password, email_hash, language) VALUES(?,?,?,?,?,?);",
use(mEmail), use(mFirstName), use(mLastName), bind(mPasswordHashed), use(mEmailHash), use(mLanguageKey);
}
else {
insert << "INSERT INTO users (email, first_name, last_name, language) VALUES(?,?,?,?);",
use(mEmail), use(mFirstName), use(mLastName), use(mLanguageKey);
insert << "INSERT INTO users (email, first_name, last_name, email_hash, language) VALUES(?,?,?,?,?);",
use(mEmail), use(mFirstName), use(mLastName), use(mEmailHash), use(mLanguageKey);
}
return insert;
@ -84,12 +98,12 @@ namespace model {
_fieldName = getTableName() + std::string(".id");
}
Poco::Data::Statement select(session);
select << "SELECT " << getTableName() << ".id, email, first_name, last_name, password, pubkey, privkey, created, email_checked, language, disabled, user_roles.role_id "
select << "SELECT " << getTableName() << ".id, email, first_name, last_name, password, pubkey, privkey, email_hash, created, email_checked, language, disabled, user_roles.role_id "
<< " FROM " << getTableName()
<< " LEFT JOIN user_roles ON " << getTableName() << ".id = user_roles.user_id "
<< " WHERE " << _fieldName << " = ?" ,
into(mID), into(mEmail), into(mFirstName), into(mLastName), into(mPasswordHashed),
into(mPublicKey), into(mPrivateKey), into(mCreated), into(mEmailChecked),
into(mPublicKey), into(mPrivateKey), into(mEmailHash), into(mCreated), into(mEmailChecked),
into(mLanguageKey), into(mDisabled), into(mRole);
@ -266,12 +280,14 @@ namespace model {
auto mm = MemoryManager::getInstance();
auto pubkeyHex = mm->getFreeMemory(65);
auto privkeyHex = mm->getFreeMemory(161);
auto email_hash = mm->getFreeMemory(crypto_generichash_BYTES+1);
//char pubkeyHex[65], privkeyHex[161];
//memset(pubkeyHex, 0, 65);
//memset(privkeyHex, 0, 161);
memset(*pubkeyHex, 0, 65);
memset(*privkeyHex, 0, 161);
memset(*email_hash, 0, crypto_generichash_BYTES + 1);
std::stringstream ss;
@ -281,11 +297,16 @@ namespace model {
if (!mPrivateKey.isNull()) {
sodium_bin2hex(*privkeyHex, 161, mPrivateKey.value().content().data(), mPrivateKey.value().content().size());
}
if (!mEmailHash.isNull()) {
sodium_bin2hex(*email_hash, crypto_generichash_BYTES + 1, mEmailHash.value().content().data(), mEmailHash.value().content().size());
}
ss << mFirstName << " " << mLastName << " <" << mEmail << ">" << std::endl;
ss << "password hash: " << mPasswordHashed << std::endl;
ss << "public key: " << (char*)*pubkeyHex << std::endl;
ss << "private key: " << (char*)*privkeyHex << std::endl;
ss << "email hash: " << (char*)*email_hash << std::endl;
ss << "created: " << Poco::DateTimeFormatter::format(mCreated, "%f.%m.%Y %H:%M:%S") << std::endl;
ss << "email checked: " << mEmailChecked << std::endl;
ss << "language key: " << mLanguageKey << std::endl;
@ -293,6 +314,7 @@ namespace model {
mm->releaseMemory(pubkeyHex);
mm->releaseMemory(privkeyHex);
mm->releaseMemory(email_hash);
return ss.str();
}
@ -301,8 +323,10 @@ namespace model {
{
auto mm = MemoryManager::getInstance();
auto pubkeyHex = mm->getFreeMemory(65);
auto email_hash = mm->getFreeMemory(crypto_generichash_BYTES + 1);
memset(*pubkeyHex, 0, 65);
memset(*email_hash, 0, crypto_generichash_BYTES + 1);
std::stringstream ss;
@ -310,8 +334,13 @@ namespace model {
sodium_bin2hex(*pubkeyHex, 65, mPublicKey.value().content().data(), mPublicKey.value().content().size());
}
if (!mEmailHash.isNull()) {
sodium_bin2hex(*email_hash, crypto_generichash_BYTES + 1, mEmailHash.value().content().data(), mEmailHash.value().content().size());
}
ss << "<b>" << mFirstName << " " << mLastName << " <" << mEmail << "></b>" << "<br>";
ss << "public key: " << (char*)*pubkeyHex << "<br>";
ss << "email hash: " << (char*)*email_hash << "<br>";
ss << "created: " << Poco::DateTimeFormatter::format(mCreated, "%f.%m.%Y %H:%M:%S") << "<br>";
ss << "email checked: " << mEmailChecked << "<br>";
ss << "language key: " << mLanguageKey << "<br>";
@ -319,6 +348,7 @@ namespace model {
ss << "disabled: " << mDisabled << "<br>";
mm->releaseMemory(pubkeyHex);
mm->releaseMemory(email_hash);
return ss.str();
}

View File

@ -60,17 +60,19 @@ namespace model {
inline std::string getNameWithEmailHtml() const { std::shared_lock<std::shared_mutex> _lock(mSharedMutex); return mFirstName + "&nbsp;" + mLastName + "&nbsp;&lt;" + mEmail + "&gt;"; }
inline const Poco::UInt64 getPasswordHashed() const { std::shared_lock<std::shared_mutex> _lock(mSharedMutex); return mPasswordHashed; }
inline RoleType getRole() const { std::shared_lock<std::shared_mutex> _lock(mSharedMutex); if (mRole.isNull()) return ROLE_NONE; return static_cast<RoleType>(mRole.value()); }
inline const unsigned char* getPublicKey() const { if (mPublicKey.isNull()) return nullptr; return mPublicKey.value().content().data(); }
inline const unsigned char* getPublicKey() const { std::shared_lock<std::shared_mutex> _lock(mSharedMutex); if (mPublicKey.isNull()) return nullptr; return mPublicKey.value().content().data(); }
inline size_t getPublicKeySize() const { std::shared_lock<std::shared_mutex> _lock(mSharedMutex); if (mPublicKey.isNull()) return 0; return mPublicKey.value().content().size(); }
std::string getPublicKeyHex() const;
inline bool hasPrivateKeyEncrypted() const { std::shared_lock<std::shared_mutex> _lock(mSharedMutex); return !mPrivateKey.isNull(); }
inline bool hasEmailHash() const { std::shared_lock<std::shared_mutex> _lock(mSharedMutex); return !mEmailHash.isNull(); }
inline const std::vector<unsigned char>& getPrivateKeyEncrypted() const { return mPrivateKey.value().content(); }
inline bool isEmailChecked() const { std::shared_lock<std::shared_mutex> _lock(mSharedMutex); return mEmailChecked; }
inline const std::string getLanguageKey() const { std::shared_lock<std::shared_mutex> _lock(mSharedMutex); return mLanguageKey; }
inline bool isDisabled() const { std::shared_lock<std::shared_mutex> _lock(mSharedMutex); return mDisabled; }
// default setter unlocked
inline void setEmail(const std::string& email) { std::unique_lock<std::shared_mutex> _lock(mSharedMutex); mEmail = email; }
void setEmail(const std::string& email);
inline void setFirstName(const std::string& first_name) { std::unique_lock<std::shared_mutex> _lock(mSharedMutex); mFirstName = first_name; }
inline void setLastName(const std::string& last_name) { std::unique_lock<std::shared_mutex> _lock(mSharedMutex); mLastName = last_name; }
inline void setPasswordHashed(const Poco::UInt64& passwordHashed) { std::unique_lock<std::shared_mutex> _lock(mSharedMutex); mPasswordHashed = passwordHashed; }
@ -101,6 +103,7 @@ namespace model {
Poco::Nullable<Poco::Data::BLOB> mPublicKey;
Poco::Nullable<Poco::Data::BLOB> mPrivateKey;
Poco::Nullable<Poco::Data::BLOB> mEmailHash; // sodium generic hash (currently blake2b)
// created: Mysql DateTime
Poco::DateTime mCreated;