From 90073899a17fefd9df8c789a5fbe4e8474f92d9b Mon Sep 17 00:00:00 2001 From: einhornimmond Date: Thu, 10 Jun 2021 13:06:08 +0200 Subject: [PATCH 01/12] for password change old password is required --- .../cpp/JSONInterface/JsonUpdateUserInfos.cpp | 47 +++++++++++++++++-- 1 file changed, 43 insertions(+), 4 deletions(-) diff --git a/login_server/src/cpp/JSONInterface/JsonUpdateUserInfos.cpp b/login_server/src/cpp/JSONInterface/JsonUpdateUserInfos.cpp index 7db31df33..e18b66bec 100644 --- a/login_server/src/cpp/JSONInterface/JsonUpdateUserInfos.cpp +++ b/login_server/src/cpp/JSONInterface/JsonUpdateUserInfos.cpp @@ -149,14 +149,51 @@ Poco::JSON::Object* JsonUpdateUserInfos::handle(Poco::Dynamic::Var params) jsonErrorsArray.add("User.password isn't string"); } else { - NotificationList errors; + std::string old_password; + auto old_password_obj = updates->get("User.password_old"); + if (old_password_obj.isEmpty()) { + jsonErrorsArray.add("User.password_old not found"); + } + else if (!old_password_obj.isString()) { + jsonErrorsArray.add("User.password_old isn't a string"); + } + else { + old_password_obj.convert(old_password); + } + + bool old_password_valid = false; + NotificationList errors; + if (old_password.size()) { + if (!sm->checkPwdValidation(old_password, &errors, LanguageManager::getInstance()->getFreeCatalog(LANG_EN))) { + jsonErrorsArray.add("User.password_old didn't match"); + Poco::Thread::sleep(ServerConfig::g_FakeLoginSleepTime); + } + else { + auto result = user->login(old_password); + if (result == 1) { + old_password_valid = true; + } + else if (result == -3) { + jsonErrorsArray.add("Password calculation for this user already running, please try again later"); + } + else { + jsonErrorsArray.add("User.password_old didn't match"); + } + + if (result == 2) { + Poco::Thread::sleep(ServerConfig::g_FakeLoginSleepTime); + } + } + + } + if (!sm->checkPwdValidation(value.toString(), &errors, LanguageManager::getInstance()->getFreeCatalog(LANG_EN))) { jsonErrorsArray.add("User.password isn't valid"); jsonErrorsArray.add(errors.getErrorsArray()); } else { auto result_new_password = user->setNewPassword(value.toString()); - + switch (result_new_password) { // 0 = new and current passwords are the same case 0: jsonErrorsArray.add("new password is the same as old password"); break; @@ -167,9 +204,11 @@ Poco::JSON::Object* JsonUpdateUserInfos::handle(Poco::Dynamic::Var params) // -1 = stored pubkey and private key didn't match case -1: jsonErrorsArray.add("stored pubkey and private key didn't match"); break; } - - + + } + + } } } From 8659823f231048b7459a91f1d1f3dc03ef11f345 Mon Sep 17 00:00:00 2001 From: einhornimmond Date: Thu, 10 Jun 2021 13:06:50 +0200 Subject: [PATCH 02/12] prepare for tests which needed valid user and known password in db --- .../PageRequestHandlerFactory.cpp | 6 ++ login_server/src/cpp/test/main.cpp | 42 +++++--- login_server/src/cpsp/TestUserGenerator.cpsp | 102 ++++++++++++++++++ 3 files changed, 138 insertions(+), 12 deletions(-) create mode 100644 login_server/src/cpsp/TestUserGenerator.cpsp diff --git a/login_server/src/cpp/HTTPInterface/PageRequestHandlerFactory.cpp b/login_server/src/cpp/HTTPInterface/PageRequestHandlerFactory.cpp index 47d7b8c9a..863ef12f1 100644 --- a/login_server/src/cpp/HTTPInterface/PageRequestHandlerFactory.cpp +++ b/login_server/src/cpp/HTTPInterface/PageRequestHandlerFactory.cpp @@ -12,6 +12,7 @@ #include "CheckEmailPage.h" #include "PassphrasePage.h" #include "SaveKeysPage.h" +#include "TestUserGenerator.h" #include "ElopageWebhook.h" #include "ElopageWebhookLight.h" #include "UserUpdatePasswordPage.h" @@ -229,6 +230,11 @@ Poco::Net::HTTPRequestHandler* PageRequestHandlerFactory::createRequestHandler(c return basicSetup(new LoginPage(nullptr), request, timeUsed); } } + if (ServerConfig::g_ServerSetupType != ServerConfig::SERVER_TYPE_PRODUCTION) { + if (url_first_part == "/testUserGenerator") { + return basicSetup(new TestUserGenerator, request, timeUsed); + } + } return basicSetup(new LoginPage(nullptr), request, timeUsed); //return new HandleFileRequest; //return new PageRequestHandlerFactory; diff --git a/login_server/src/cpp/test/main.cpp b/login_server/src/cpp/test/main.cpp index d4773bd9f..8f94dc1c3 100644 --- a/login_server/src/cpp/test/main.cpp +++ b/login_server/src/cpp/test/main.cpp @@ -13,6 +13,7 @@ #include "Poco/SplitterChannel.h" #include "../SingletonManager/ConnectionManager.h" +#include "../SingletonManager/SessionManager.h" #include "../lib/Profiler.h" @@ -27,7 +28,7 @@ void fillTests() // gTests.push_back(new LoginTest()); } -void runMysql(std::string sqlQuery) +int runMysql(std::string sqlQuery) { auto cm = ConnectionManager::getInstance(); auto session = cm->getConnection(CONNECTION_MYSQL_LOGIN_SERVER); @@ -39,7 +40,9 @@ void runMysql(std::string sqlQuery) } catch (Poco::Exception& ex) { printf("exception in runMysql: %s\n", ex.displayText().data()); + return -1; } + return 0; } int load(int argc, char* argv[]) { @@ -47,6 +50,9 @@ int load(int argc, char* argv[]) { std::clog << "[load]" << std::endl; Poco::AutoPtr test_config(new Poco::Util::LayeredConfiguration); std::string config_file_name = Poco::Path::config() + "grd_login/grd_login_test.properties"; +#ifdef WIN32 + config_file_name = "Gradido_LoginServer_Test.properties"; +#endif if(argc > 1 && strlen(argv[1]) > 4) { config_file_name = argv[1]; } @@ -144,6 +150,8 @@ int load(int argc, char* argv[]) { log.error("Test Error"); + SessionManager::getInstance()->init(); + //errorLog //printf("try connect php server mysql \n"); @@ -157,26 +165,35 @@ int load(int argc, char* argv[]) { "users" }; for (int i = 0; i < 2; i++) { - runMysql("TRUNCATE " + tables[i]); - runMysql("ALTER TABLE " + tables[i] + " AUTO_INCREMENT = 1"); + if (runMysql("TRUNCATE " + tables[i])) { + return -1; + } + if (runMysql("ALTER TABLE " + tables[i] + " AUTO_INCREMENT = 1")) { + return -1; + } } std::stringstream ss; + // password = TestP4ssword&H ss << "INSERT INTO `users` (`id`, `email`, `first_name`, `last_name`, `username`, `password`, `pubkey`, `privkey`, `created`, `email_checked`, `passphrase_shown`, `language`, `disabled`, `group_id`) VALUES " - << "(1, 'd_schultz32@gmx.de', 'DDD', 'Schultz', 'Diddel', 13134558453895551556, 0x146d3fb9e88abc0fca0b0091c1ab1b32b399be037436f340befa8bf004461889, 0x0dcc08960f45f631fe23bc7ddee0724cedc9ec0c861ce30f5091d20ffd96062d08ca215726fb9bd64860c754772e945eea4cc872ed0a36c7b640e8b0bf7a873ec6765fa510711622341347ce2307b5ce, '2020-02-20 16:05:44', 1, 0, 'de', 0, 1), " - << "(2, 'Jeet_bb@gmail.com', 'Darios', 'Bruder', 'Jeet', 12910944485867375321, 0x952e215a21d4376b4ac252c4bf41e156e1498e1b6b8ccf2a6826d96712f4f461, 0x4d40bf0860655f728312140dc3741e897bc2d13d00ea80a63e2961046a5a7bd8315397dfb488b89377087bc1a5f4f3af8ffdcf203329ae23ba04be7d38ad3852699d90ff1fc00e5b1ca92b64cc59c01f, '2020-02-20 16:05:44', 1, 0, 'de', 0, 1), " - << "(3, 'Tiger_231@yahoo.com', 'Dieter', 'Schultz', 'Tiger', 13528673707291575501, 0xb539944bf6444a2bfc988244f0c0c9dc326452be9b8a2a43fcd90663719f4f6d, 0x5461fda60b719b65ba00bd6298e48410c4cbf0e89deb13cc784ba8978cf047454e8556ee3eddc8487ee835c33a83163bc8d8babbf2a5c431876bc0a0c114ff0a0d6b57baa12cf8f23c64fb642c862db5, '2020-02-20 16:05:45', 1, 0, 'de', 0, 1), " - << "(4, 'Nikola_Tesla@email.de', 'Nikola', 'Tesla', 'Erfinder', 15522411320147607375, 0x476b059744f08b0995522b484c90f8d2f47d9b59f4b3c96d9dc0ae6ab7b84979, 0x5277bf044cba4fec64e6f4d38da132755b029161231daefc9a7b4692ad37e05cdd88e0a2c2215baf854dd3a813578c214167af1113607e9f999ca848a7598ba5068e38f2a1afb097e4752a88024d79c8, '2020-02-20 16:05:46', 1, 0, 'de', 0, 1), " - << "(5, 'Elfenhausen@arcor.de', 'Thomas', 'Markuk', 'Elf', 7022671043835614958, 0xb1584e169d60a7e771d3a348235dfd7b5f9e8235fcc26090761a0264b0daa6ff, 0xb46fb7110bf91e28f367aa80f84d1bbd639b6f689f4b0aa28c0f71529232df9bf9ee0fb02fa4c1b9f5a6799c82d119e5646f7231d011517379faaacf6513d973ac3043d4c786490ba62d56d75b86164d, '2020-02-20 16:05:46', 1, 0, 'de', 0, 1), " - << "(6, 'coin-info12@gradido.net', 'coin-info12', 'Test', 'Test Username', 1548398919826089202, 0x4046ae49c1b620f2a321aba0c874fa2bc7ba844ab808bb0eeb18a908d468db14, 0x9522657ecd7456eedf86d065aa087ba7a94a8961a8e4950d044136155d38fe0840f2c0a2876ce055b3eaa6e9ab95c5feba89e535e0434fb2648d94d6e6ec68211aa2ea9e42d1ccd40b6b3c31e41f848e, '2020-02-20 16:05:47', 1, 0, 'de', 0, 1), " - << "(7, 'AlexWesper@gmail.com', 'Alex', 'Wesper', 'Wespe', 5822761891727948301, 0xb13ede3402abb8f29722b14fec0a2006ae7a3a51fb677cd6a2bbd797ac6905a5, 0x6aa39d7670c64a31639c7d89b874ad929b2eaeb2e5992dbad71b6cea700bf9e3c6cf866d0f0fdc22b44a0ebf51a860799e880ef86266199931dd0a301e5552db44b9b7fa99ed5945652bc7b31eff767c, '2020-02-20 16:05:47', 1, 0, 'de', 0, 1); "; - runMysql(ss.str()); + << "(1, 'd_schultz32@gmx.de', 'DDD', 'Schultz', 'Diddel', 18242007140018938940, 0x69f2fefd6fa6947a370b9f8d3147f6617cf67416517ce25cb2d63901c666933c, 0x567f3e623a1899d1f8d69190c5799433c134ce0137c0c38cc0347874586d6234a19f2a0b484e6cc1863502e580ae6c17db1131f29a35eba45a46be29c7ee592940a3bd3ad519075fdeed6e368f0eb818, '2020-02-20 16:05:44', 1, 0, 'de', 0, 1), " + << "(2, 'Jeet_bb@gmail.com', 'Darios', 'Bruder', 'Jeet', 10417562666175322069, 0x6afd24f46eb79a839281fe537a1888155b102d4fbe0613ea92d51845bd8036cb, 0xe7aed71cd4ae2d1aba9343ffb3822b759f972e41b63a6032b7f6c69f566217784c2e7bcdaeaa2f7dd16bf3b6f1540b22afa65fc054550a9296454c6ecdbd4131eac7f9c703318a867e666691e1808a6e, '2020-02-20 16:05:44', 1, 0, 'de', 0, 1), " + << "(3, 'Tiger_231@yahoo.com', 'Dieter', 'Schultz', 'Tiger', 13790258844849208764, 0x9a79a5daea92218608fa1e3a657d78961dc04c97ff996cc0ea17d6896b5368e6, 0x4993a156a120728f0fa93fc63ab01482ed85ecf433c729c8426c4bb93f0b7ce6142fda531b11f5d5e925acd1d2e55fdfef94fe07dbb78d43322f7df1234c7251aa58946c96ec6e551395f0fb5e87decf, '2020-02-20 16:05:45', 1, 0, 'de', 0, 1), " + << "(4, 'Nikola_Tesla@email.de', 'Nikola', 'Tesla', 'Erfinder', 1914014100253540772, 0x1c199421a66070afb28cb7c37de98865b28924bff26161bb65faaf5695050ee3, 0xe38ca460ca748954b29d79f0e943eed3ba85e7e13b18f69349666e31a8e3b06c9df105171796b37b4201895a2f3fe8ec8bf58a181700caaa5752a94a968c50e90ebb6280002a056126b2055ff75d69d1, '2020-02-20 16:05:46', 1, 0, 'de', 0, 1), " + << "(5, 'Elfenhausen@arcor.de', 'Thomas', 'Markuk', 'Elf', 8105871797752167168, 0x98d703f0ea1def3ef9e6265a76281d125a94c80665425bd7a844580ec1a2ce98, 0x63612a1d07d78a0c945d765a10a30d9de2be602e79e3f39268d731bc6f7fa945d7d04c638000bae089ac058263f52e7c1f2c3550b35b5727e41523f2f592781add65d12b8b8c0b3226f32174cfa1bcee, '2020-02-20 16:05:46', 1, 0, 'de', 0, 1), " + << "(6, 'coin-info12@gradido.net', 'coin-info12', 'Test', 'Test Username', 9005874071610817324, 0xb3ee1c82a9877f664d05364106e259621b2e203bfbb5323edb7b597051efecc2, 0xa039da7d59e2475dd1aaa635f803ec1aeffc2506e7a96a934bf8d7cf4ac2a96dc962d4e1bdf8e11c5ce7e18189edc36014b89e9e72628004ec5901be6c407a955efb5142a1ee9a2f3aed888125a44aa2, '2020-02-20 16:05:47', 1, 0, 'de', 0, 1), " + << "(7, 'AlexWesper@gmail.com', 'Alex', 'Wesper', 'Wespe', 7264393213873828644, 0x735a5c22ebe84ab1d6453991d50019b677b82b0663b023c30127ec906ee9b59a, 0xaec30051ad3ab2d2132a76e9dfe5a396d2dfbcc83a4eb27223b4da8803893959af9e29c6963f9e73eddc447cb3d3995527b94054e7fdecd7d5f8cb45c3954ff9bb2c9e0374f2124b3170301f990c5d7d, '2020-02-20 16:05:47', 1, 0, 'de', 0, 1); "; + if (runMysql(ss.str())) { + return -1; + } ss.str(std::string()); ss << "INSERT INTO `groups` (`id`, `alias`, `name`, `url`, `description`) VALUES" << "(1, 'gdd1', 'Gradido1', 'gdd1.gradido.com', 'Der erste offizielle Gradido Server (zum Testen)'), " << "(2, 'gdd_test', 'Gradido Test', 'gdd1.gradido.com', 'Testgroup (zum Testen)'); "; - runMysql(ss.str()); + if (runMysql(ss.str())) { + return -1; + } ss.str(std::string()); @@ -219,6 +236,7 @@ void ende() } gTests.clear(); + SessionManager::getInstance()->deinitalize(); } diff --git a/login_server/src/cpsp/TestUserGenerator.cpsp b/login_server/src/cpsp/TestUserGenerator.cpsp new file mode 100644 index 000000000..41c3ad99d --- /dev/null +++ b/login_server/src/cpsp/TestUserGenerator.cpsp @@ -0,0 +1,102 @@ +<%@ page class="TestUserGenerator" %> +<%@ page form="true" %> +<%@ page baseClass="PageRequestMessagedHandler" %> +<%@ header include="HTTPInterface/PageRequestMessagedHandler.h" %> +<%! + #include "Crypto/SecretKeyCryptography.h" + #include "Crypto/KeyPairEd25519.h" + #include "ServerConfig.h" + #include "lib/DataTypeConverter.h" + + #include "controller/User.h" +%> +<%% + const char* pageName = "Test User Generator"; + // needed for header_large + auto user = controller::User::create(); + + std::string email; + std::string password_hashed; + std::string pubkey_hex; + std::string privkey_hex_encrypted; + std::string passphrase_str; + + bool user_created = false; + // add + if(!form.empty()) { + email = form.get("email", ""); + auto password = form.get("password", ""); + if(email == "") { + addError(new Error("Create User", "E-Mail is empty!")); + } + else if(password == "") { + addError(new Error("Create User", "Password is empty!")); + } + else + { + auto passphrase = Passphrase::generate(&ServerConfig::g_Mnemonic_WordLists[ServerConfig::MNEMONIC_BIP0039_SORTED_ORDER]); + passphrase_str = passphrase->getString(); + auto key_pair = KeyPairEd25519::create(passphrase); + Poco::AutoPtr secret_key = new SecretKeyCryptography; + secret_key->createKey(email, password); + password_hashed = std::to_string(secret_key->getKeyHashed()); + auto privkey_encrypted = key_pair->getCryptedPrivKey(secret_key); + privkey_hex_encrypted = DataTypeConverter::binToHex(privkey_encrypted); + pubkey_hex = key_pair->getPublicKeyHex(); + user_created = true; + delete key_pair; + } + } + + // select all + auto groups = controller::Group::listAll(); + //auto groups = controller::Group::load("gdd1"); + //std::vector> groups; + +%><%@ include file="include/header_large.cpsp" %> +<%= getErrorsHtml() %> +
+
+

Einen neuen User anlegen

+
+
+
+ + + + + +
+
+ <% if(user_created) { %> +
+
+

Generierte Daten

+
+
+
+
E-Mail
+
<%= email %>
+
+
+
Password hash
+
<%= password_hashed %>
+
+
+
public key
+
0x<%= pubkey_hex %>
+
+
+
private key encrypted
+
0x<%= privkey_hex_encrypted %>
+
+
+
Passphrase
+
<%= passphrase_str %>
+
+
+
+
'<%= email %>', <%= password_hashed %>, 0x<%= pubkey_hex %>, 0x<%= privkey_hex_encrypted %>
+ <% } %> +
+<%@ include file="include/footer.cpsp" %> From 713394c226820f03f1f89ef2b6a73acb38649b15 Mon Sep 17 00:00:00 2001 From: einhornimmond Date: Thu, 10 Jun 2021 13:07:15 +0200 Subject: [PATCH 03/12] add test for UpdateUserInfo, fix CheckUsername Test --- .../JSONInterface/TestJsonCheckUsername.cpp | 6 ++- .../JSONInterface/TestJsonUpdateUserInfos.cpp | 52 +++++++++++++++++++ .../JSONInterface/TestJsonUpdateUserInfos.h | 23 ++++++++ 3 files changed, 80 insertions(+), 1 deletion(-) create mode 100644 login_server/src/cpp/test/JSONInterface/TestJsonUpdateUserInfos.cpp create mode 100644 login_server/src/cpp/test/JSONInterface/TestJsonUpdateUserInfos.h diff --git a/login_server/src/cpp/test/JSONInterface/TestJsonCheckUsername.cpp b/login_server/src/cpp/test/JSONInterface/TestJsonCheckUsername.cpp index d399689ee..64e70055a 100644 --- a/login_server/src/cpp/test/JSONInterface/TestJsonCheckUsername.cpp +++ b/login_server/src/cpp/test/JSONInterface/TestJsonCheckUsername.cpp @@ -68,7 +68,6 @@ TEST(TestJsonCheckUsername, UsernameWithoutGroup) ASSERT_TRUE(msg.isString()); ASSERT_EQ(msg.toString(), "no group given"); - delete result; } @@ -89,6 +88,8 @@ TEST(TestJsonCheckUsername, ExistingUsername) ASSERT_FALSE(msg.isEmpty()); ASSERT_TRUE(msg.isString()); ASSERT_EQ(msg.toString(), "username already in use"); + + delete result; } TEST(TestJsonCheckUsername, NewUsername) @@ -103,6 +104,8 @@ TEST(TestJsonCheckUsername, NewUsername) ASSERT_FALSE(state.isEmpty()); ASSERT_TRUE(state.isString()); ASSERT_EQ(state.toString(), "success"); + + delete result; } TEST(TestJsonCheckUsername, UsernameExistInOtherGroup) @@ -118,6 +121,7 @@ TEST(TestJsonCheckUsername, UsernameExistInOtherGroup) ASSERT_TRUE(state.isString()); ASSERT_EQ(state.toString(), "success"); + delete result; } diff --git a/login_server/src/cpp/test/JSONInterface/TestJsonUpdateUserInfos.cpp b/login_server/src/cpp/test/JSONInterface/TestJsonUpdateUserInfos.cpp new file mode 100644 index 000000000..506fbb80f --- /dev/null +++ b/login_server/src/cpp/test/JSONInterface/TestJsonUpdateUserInfos.cpp @@ -0,0 +1,52 @@ +#include "gtest/gtest.h" + +#include "JSONInterface/JsonUpdateUserInfos.h" +#include "TestJsonUpdateUserInfos.h" + + +void TestJsonUpdateUserInfos::SetUp() +{ + auto sm = SessionManager::getInstance(); + sm->init(); + mUserSession = sm->getNewSession(); + mUserSession->loadUser("Jeet_bb@gmail.com", "TestP4ssword&H"); +} + +void TestJsonUpdateUserInfos::TearDown() +{ + auto sm = SessionManager::getInstance(); + if (!mUserSession) { + sm->releaseSession(mUserSession); + } + sm->deinitalize(); +} + +Poco::JSON::Object::Ptr TestJsonUpdateUserInfos::chooseAccount(const Poco::JSON::Object::Ptr update) +{ + Poco::JSON::Object::Ptr params = new Poco::JSON::Object; + params->set("session_id", mUserSession->getHandle()); + params->set("email", mUserSession->getNewUser()->getModel()->getEmail()); + params->set("update", update); + return params; +} + +TEST_F(TestJsonUpdateUserInfos, EmptyOldPassword) +{ + JsonUpdateUserInfos jsonCall; + Poco::JSON::Object::Ptr update = new Poco::JSON::Object; + + update->set("User.password", "haLL1o_/%s"); + + auto params = chooseAccount(update); + auto result = jsonCall.handle(params); + + auto errors = result->get("errors"); + ASSERT_TRUE(errors.isArray()); + //User.password_old not found + Poco::JSON::Array error_array = errors.extract(); + + ASSERT_EQ(error_array.size(), 1); + ASSERT_EQ(error_array.getElement(0), "User.password_old not found"); + + delete result; +} \ No newline at end of file diff --git a/login_server/src/cpp/test/JSONInterface/TestJsonUpdateUserInfos.h b/login_server/src/cpp/test/JSONInterface/TestJsonUpdateUserInfos.h new file mode 100644 index 000000000..4c759ce67 --- /dev/null +++ b/login_server/src/cpp/test/JSONInterface/TestJsonUpdateUserInfos.h @@ -0,0 +1,23 @@ +#ifndef __GRADIDO_LOGIN_SERVER_TEST_JSON_INTERFACE_TEST_JSON_UPDATE_USER_INFOS_H +#define __GRADIDO_LOGIN_SERVER_TEST_JSON_INTERFACE_TEST_JSON_UPDATE_USER_INFOS_H + +#include "gtest/gtest.h" +#include "SingletonManager/SessionManager.h" + +#include "Poco/JSON/Object.h" + +class TestJsonUpdateUserInfos : public ::testing::Test +{ + +protected: + void SetUp() override; + void TearDown() override; + + Poco::JSON::Object::Ptr chooseAccount(const Poco::JSON::Object::Ptr update); + + Session* mUserSession; + + +}; + +#endif //__GRADIDO_LOGIN_SERVER_TEST_JSON_INTERFACE_TEST_JSON_UPDATE_USER_INFOS_H \ No newline at end of file From 1476951d9371f914892d5cf6685e10cabc9a5c21 Mon Sep 17 00:00:00 2001 From: einhornimmond Date: Thu, 10 Jun 2021 15:26:39 +0200 Subject: [PATCH 04/12] wait on check email for send password reset email to prevent misuse to find out which emails on server exist --- login_server/src/cpp/JSONInterface/JsonSendEmail.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/login_server/src/cpp/JSONInterface/JsonSendEmail.cpp b/login_server/src/cpp/JSONInterface/JsonSendEmail.cpp index 3179d81ce..26b00314c 100644 --- a/login_server/src/cpp/JSONInterface/JsonSendEmail.cpp +++ b/login_server/src/cpp/JSONInterface/JsonSendEmail.cpp @@ -99,7 +99,7 @@ Poco::JSON::Object* JsonSendEmail::handle(Poco::Dynamic::Var params) return stateError("invalid session"); } } - + Poco::Thread::sleep(ServerConfig::g_FakeLoginSleepTime); auto receiver_user = controller::User::create(); if (1 != receiver_user->load(email)) { return stateError("invalid email"); From e5c37606d2134b01d7e2a0d5e479121ec5ed795b Mon Sep 17 00:00:00 2001 From: einhornimmond Date: Thu, 10 Jun 2021 15:27:09 +0200 Subject: [PATCH 05/12] fix bug shown through testing --- .../cpp/JSONInterface/JsonUpdateUserInfos.cpp | 45 ++++++++++--------- 1 file changed, 24 insertions(+), 21 deletions(-) diff --git a/login_server/src/cpp/JSONInterface/JsonUpdateUserInfos.cpp b/login_server/src/cpp/JSONInterface/JsonUpdateUserInfos.cpp index e18b66bec..c4ab519fd 100644 --- a/login_server/src/cpp/JSONInterface/JsonUpdateUserInfos.cpp +++ b/login_server/src/cpp/JSONInterface/JsonUpdateUserInfos.cpp @@ -163,12 +163,14 @@ Poco::JSON::Object* JsonUpdateUserInfos::handle(Poco::Dynamic::Var params) bool old_password_valid = false; NotificationList errors; - if (old_password.size()) { + if (old_password.size()) + { if (!sm->checkPwdValidation(old_password, &errors, LanguageManager::getInstance()->getFreeCatalog(LANG_EN))) { jsonErrorsArray.add("User.password_old didn't match"); Poco::Thread::sleep(ServerConfig::g_FakeLoginSleepTime); } - else { + else + { auto result = user->login(old_password); if (result == 1) { old_password_valid = true; @@ -186,28 +188,29 @@ Poco::JSON::Object* JsonUpdateUserInfos::handle(Poco::Dynamic::Var params) } } - - if (!sm->checkPwdValidation(value.toString(), &errors, LanguageManager::getInstance()->getFreeCatalog(LANG_EN))) { - jsonErrorsArray.add("User.password isn't valid"); - jsonErrorsArray.add(errors.getErrorsArray()); - } - else { - auto result_new_password = user->setNewPassword(value.toString()); - - switch (result_new_password) { - // 0 = new and current passwords are the same - case 0: jsonErrorsArray.add("new password is the same as old password"); break; - // 1 = password changed, private key re-encrypted and saved into db - //case 1: extractet_values++; break; - // 2 = password changed, only hash stored in db, couldn't load private key for re-encryption - case 2: jsonErrorsArray.add("password changed, couldn't load private key for re-encryption"); break; - // -1 = stored pubkey and private key didn't match - case -1: jsonErrorsArray.add("stored pubkey and private key didn't match"); break; + if (old_password_valid) + { + if (!sm->checkPwdValidation(value.toString(), &errors, LanguageManager::getInstance()->getFreeCatalog(LANG_EN))) { + jsonErrorsArray.add("User.password isn't valid"); + jsonErrorsArray.add(errors.getErrorsArray()); } + else + { + auto result_new_password = user->setNewPassword(value.toString()); + switch (result_new_password) { + // 0 = new and current passwords are the same + case 0: jsonErrorsArray.add("new password is the same as old password"); break; + // 1 = password changed, private key re-encrypted and saved into db + //case 1: extractet_values++; break; + // 2 = password changed, only hash stored in db, couldn't load private key for re-encryption + case 2: jsonErrorsArray.add("password changed, couldn't load private key for re-encryption"); break; + // -1 = stored pubkey and private key didn't match + case -1: jsonErrorsArray.add("stored pubkey and private key didn't match"); break; + } - } - + } + } } } From 6f70d4c8b81443d7e66ca52a5f290b07c883272c Mon Sep 17 00:00:00 2001 From: einhornimmond Date: Thu, 10 Jun 2021 15:27:28 +0200 Subject: [PATCH 06/12] update test and test environment --- .../JSONInterface/TestJsonUpdateUserInfos.cpp | 65 ++++++++++++++++++- .../JSONInterface/TestJsonUpdateUserInfos.h | 2 +- login_server/src/cpp/test/main.cpp | 24 +++++-- 3 files changed, 82 insertions(+), 9 deletions(-) diff --git a/login_server/src/cpp/test/JSONInterface/TestJsonUpdateUserInfos.cpp b/login_server/src/cpp/test/JSONInterface/TestJsonUpdateUserInfos.cpp index 506fbb80f..64891fb01 100644 --- a/login_server/src/cpp/test/JSONInterface/TestJsonUpdateUserInfos.cpp +++ b/login_server/src/cpp/test/JSONInterface/TestJsonUpdateUserInfos.cpp @@ -2,14 +2,17 @@ #include "JSONInterface/JsonUpdateUserInfos.h" #include "TestJsonUpdateUserInfos.h" +#include "lib/Profiler.h" void TestJsonUpdateUserInfos::SetUp() { auto sm = SessionManager::getInstance(); - sm->init(); + //sm->init(); mUserSession = sm->getNewSession(); - mUserSession->loadUser("Jeet_bb@gmail.com", "TestP4ssword&H"); + auto user = controller::User::create(); + user->getModel()->setEmail("Jeet_bb@gmail.com"); + mUserSession->setUser(user); } void TestJsonUpdateUserInfos::TearDown() @@ -18,7 +21,6 @@ void TestJsonUpdateUserInfos::TearDown() if (!mUserSession) { sm->releaseSession(mUserSession); } - sm->deinitalize(); } Poco::JSON::Object::Ptr TestJsonUpdateUserInfos::chooseAccount(const Poco::JSON::Object::Ptr update) @@ -30,6 +32,7 @@ Poco::JSON::Object::Ptr TestJsonUpdateUserInfos::chooseAccount(const Poco::JSON: return params; } + TEST_F(TestJsonUpdateUserInfos, EmptyOldPassword) { JsonUpdateUserInfos jsonCall; @@ -42,11 +45,67 @@ TEST_F(TestJsonUpdateUserInfos, EmptyOldPassword) auto errors = result->get("errors"); ASSERT_TRUE(errors.isArray()); + auto valid_values_obj = result->get("valid_values"); + ASSERT_TRUE(valid_values_obj.isInteger()); + int valid_values = 0; + valid_values_obj.convert(valid_values); + ASSERT_EQ(valid_values, 0); //User.password_old not found Poco::JSON::Array error_array = errors.extract(); ASSERT_EQ(error_array.size(), 1); ASSERT_EQ(error_array.getElement(0), "User.password_old not found"); + delete result; +} + +TEST_F(TestJsonUpdateUserInfos, OnlyOldPassword) +{ + JsonUpdateUserInfos jsonCall; + Poco::JSON::Object::Ptr update = new Poco::JSON::Object; + + update->set("User.password_old", "TestP4ssword&H"); + + auto params = chooseAccount(update); + auto result = jsonCall.handle(params); + + auto errors = result->get("errors"); + ASSERT_TRUE(errors.isArray()); + auto valid_values_obj = result->get("valid_values"); + ASSERT_TRUE(valid_values_obj.isInteger()); + int valid_values = 0; + valid_values_obj.convert(valid_values); + ASSERT_EQ(valid_values, 0); + Poco::JSON::Array error_array = errors.extract(); + ASSERT_EQ(error_array.size(), 0); + + delete result; +} + +TEST_F(TestJsonUpdateUserInfos, WrongPassword) +{ + JsonUpdateUserInfos jsonCall; + mUserSession->loadUser("Jeet_bb@gmail.com", "TestP4ssword&H"); + Poco::JSON::Object::Ptr update = new Poco::JSON::Object; + + update->set("User.password", "newPassword"); + update->set("User.password_old", "TestP4sswordH"); + + auto params = chooseAccount(update); + Profiler timeUsed; + auto result = jsonCall.handle(params); + ASSERT_GE(timeUsed.millis(), ServerConfig::g_FakeLoginSleepTime-200); + + auto errors = result->get("errors"); + ASSERT_TRUE(errors.isArray()); + auto valid_values_obj = result->get("valid_values"); + ASSERT_TRUE(valid_values_obj.isInteger()); + int valid_values = 0; + valid_values_obj.convert(valid_values); + ASSERT_EQ(valid_values, 0); + Poco::JSON::Array error_array = errors.extract(); + ASSERT_EQ(error_array.size(), 1); + ASSERT_EQ(error_array.getElement(0), "User.password_old didn't match"); + delete result; } \ No newline at end of file diff --git a/login_server/src/cpp/test/JSONInterface/TestJsonUpdateUserInfos.h b/login_server/src/cpp/test/JSONInterface/TestJsonUpdateUserInfos.h index 4c759ce67..dfef547a8 100644 --- a/login_server/src/cpp/test/JSONInterface/TestJsonUpdateUserInfos.h +++ b/login_server/src/cpp/test/JSONInterface/TestJsonUpdateUserInfos.h @@ -16,7 +16,7 @@ protected: Poco::JSON::Object::Ptr chooseAccount(const Poco::JSON::Object::Ptr update); Session* mUserSession; - + std::string mEmail; }; diff --git a/login_server/src/cpp/test/main.cpp b/login_server/src/cpp/test/main.cpp index 8f94dc1c3..c875ec6a0 100644 --- a/login_server/src/cpp/test/main.cpp +++ b/login_server/src/cpp/test/main.cpp @@ -17,6 +17,8 @@ #include "../lib/Profiler.h" +#include "Crypto/SecretKeyCryptography.h" + std::list gTests; @@ -85,6 +87,9 @@ int load(int argc, char* argv[]) { ServerConfig::g_CPUScheduler = new UniLib::controller::CPUSheduler(worker_count, "Default Worker"); ServerConfig::g_CryptoCPUScheduler = new UniLib::controller::CPUSheduler(2, "Crypto Worker"); + ServerConfig::g_disableEmail = true; + + SessionManager::getInstance()->init(); // load up connection configs // register MySQL connector @@ -128,6 +133,13 @@ int load(int argc, char* argv[]) { return -4; } } + + printf("measure Time for secret key generation...\n"); + Profiler timeForArgon2; + SecretKeyCryptography secret_cryptografie; + secret_cryptografie.createKey("test.email@gmx.de", "skaSI2WSEIs/"); + ServerConfig::g_FakeLoginSleepTime = timeForArgon2.millis(); + printf("time for secret key generation: %s\n", timeForArgon2.string().data()); std::string log_Path = "/var/log/grd_login/"; //#ifdef _WIN32 @@ -150,7 +162,6 @@ int load(int argc, char* argv[]) { log.error("Test Error"); - SessionManager::getInstance()->init(); //errorLog @@ -226,7 +237,7 @@ int run() return 0; } -void ende() +void endegTests() { for (std::list::iterator it = gTests.begin(); it != gTests.end(); it++) { @@ -236,7 +247,7 @@ void ende() } gTests.clear(); - SessionManager::getInstance()->deinitalize(); + } @@ -254,10 +265,13 @@ int main(int argc, char** argv) //printf ("\nStack Limit = %ld and %ld max\n", limit.rlim_cur, limit.rlim_max); run(); - ende(); - ::testing::InitGoogleTest(&argc, argv); + endegTests(); + ::testing::InitGoogleTest(&argc, argv); auto result = RUN_ALL_TESTS(); + + SessionManager::getInstance()->deinitalize(); ServerConfig::unload(); + return result; } From 4abe4b8da0e110eca89d75e9f4fdcb1b0d73cbd5 Mon Sep 17 00:00:00 2001 From: einhornimmond Date: Fri, 11 Jun 2021 12:30:23 +0200 Subject: [PATCH 07/12] update doc --- docu/login_server.api.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docu/login_server.api.md b/docu/login_server.api.md index fb9409cf0..8d3e1a812 100644 --- a/docu/login_server.api.md +++ b/docu/login_server.api.md @@ -231,6 +231,7 @@ with: "User.disabled": 0, "User.language": "de", "User.password": "1234" + "User.password_old": "4321" } } ``` @@ -240,6 +241,7 @@ Notes: - User will be disabled if he wants his account deleted, but has transactions. Until transactions are saved in real blockchain, we need this data because the public key is in db only saved in state_users so if we delete this entry, validating all transactions is no longer possible. - Disabled Users can neither login nor receive transactions. - It is not required to provide all fields of `update`, it can be a subset depending on what you intend to change. +- `User.password`: to change user password, needed current passwort in `User.password_old` ### Response In case of success: From b57dd722696545ab429e08534eb1a6909b01cd6c Mon Sep 17 00:00:00 2001 From: einhornimmond Date: Fri, 11 Jun 2021 12:35:52 +0200 Subject: [PATCH 08/12] add missing comma --- docu/login_server.api.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docu/login_server.api.md b/docu/login_server.api.md index 8d3e1a812..c779e543c 100644 --- a/docu/login_server.api.md +++ b/docu/login_server.api.md @@ -230,7 +230,7 @@ with: "User.description" : "Tischler", "User.disabled": 0, "User.language": "de", - "User.password": "1234" + "User.password": "1234", "User.password_old": "4321" } } From 4ee36c6e3645827c4ade53333cfd29266ce40e7a Mon Sep 17 00:00:00 2001 From: einhornimmond Date: Fri, 11 Jun 2021 14:42:50 +0200 Subject: [PATCH 09/12] move creating secret key for password comparisation in extra function - needed for check for old passwort for passwort update in frontend --- login_server/src/cpp/controller/User.cpp | 26 ++++++++++++++++-------- login_server/src/cpp/controller/User.h | 3 +++ 2 files changed, 21 insertions(+), 8 deletions(-) diff --git a/login_server/src/cpp/controller/User.cpp b/login_server/src/cpp/controller/User.cpp index 5c26e3a12..0ca3ac988 100644 --- a/login_server/src/cpp/controller/User.cpp +++ b/login_server/src/cpp/controller/User.cpp @@ -194,20 +194,14 @@ namespace controller { return json; } - int User::login(const std::string& password) + Poco::AutoPtr User::createSecretKey(const std::string& password) { - if (!mPassword.isNull() && mPassword->hasKey()) { - return 2; - } auto observer = SingletonTaskObserver::getInstance(); - std::unique_lock _lock(mSharedMutex); - assert(mPassword.isNull()); - auto model = getModel(); auto email_hash = observer->makeHash(model->getEmail()); if (observer->getTaskCount(email_hash, TASK_OBSERVER_PASSWORD_CREATION) > 0) { - return -3; + return nullptr; } observer->addTask(email_hash, TASK_OBSERVER_PASSWORD_CREATION); Poco::AutoPtr authenticated_encryption(new SecretKeyCryptography); @@ -215,7 +209,23 @@ namespace controller { authenticated_encryption->createKey(model->getEmail(), password); observer->removeTask(email_hash, TASK_OBSERVER_PASSWORD_CREATION); + return authenticated_encryption; + } + int User::login(const std::string& password) + { + std::unique_lock _lock(mSharedMutex); + + if (!mPassword.isNull() && mPassword->hasKey()) { + return 2; + } + assert(mPassword.isNull()); + + auto authenticated_encryption = createSecretKey(password); + if (authenticated_encryption.isNull()) { + return -3; + } + auto model = getModel(); if (authenticated_encryption->getKeyHashed() == model->getPasswordHashed()) { // printf("[User::login] password key hashed is the same as saved password hash\n"); diff --git a/login_server/src/cpp/controller/User.h b/login_server/src/cpp/controller/User.h index ecbac086f..a12308550 100644 --- a/login_server/src/cpp/controller/User.h +++ b/login_server/src/cpp/controller/User.h @@ -96,6 +96,9 @@ namespace controller { //! - create authenticated encryption key from password and email //! - compare hash with in db saved hash int login(const std::string& password); + + //! \brief simply check if password is correct, independent if user is already logged in or not + Poco::AutoPtr createSecretKey(const std::string& password); // *********************************************************************************** // password related From 28e586e99c5c2a8202f763b7a2ac36ff1209c035 Mon Sep 17 00:00:00 2001 From: einhornimmond Date: Fri, 11 Jun 2021 14:43:15 +0200 Subject: [PATCH 10/12] more tests and fixes to get test working --- .../cpp/JSONInterface/JsonUpdateUserInfos.cpp | 24 +++-- .../JSONInterface/TestJsonUpdateUserInfos.cpp | 91 ++++++++++++++++++- 2 files changed, 103 insertions(+), 12 deletions(-) diff --git a/login_server/src/cpp/JSONInterface/JsonUpdateUserInfos.cpp b/login_server/src/cpp/JSONInterface/JsonUpdateUserInfos.cpp index c4ab519fd..6756ca437 100644 --- a/login_server/src/cpp/JSONInterface/JsonUpdateUserInfos.cpp +++ b/login_server/src/cpp/JSONInterface/JsonUpdateUserInfos.cpp @@ -144,10 +144,15 @@ Poco::JSON::Object* JsonUpdateUserInfos::handle(Poco::Dynamic::Var params) } } } - else if ("User.password" == name && value.size() > 0 && (ServerConfig::g_AllowUnsecureFlags & ServerConfig::UNSECURE_PASSWORD_REQUESTS) == ServerConfig::UNSECURE_PASSWORD_REQUESTS) { + else if ("User.password" == name && (ServerConfig::g_AllowUnsecureFlags & ServerConfig::UNSECURE_PASSWORD_REQUESTS) == ServerConfig::UNSECURE_PASSWORD_REQUESTS) { + if (!value.isString()) { jsonErrorsArray.add("User.password isn't string"); } + std::string value_str = value.toString(); + if (!value_str.size()) { + jsonErrorsArray.add("User.password is empty"); + } else { std::string old_password; auto old_password_obj = updates->get("User.password_old"); @@ -171,20 +176,16 @@ Poco::JSON::Object* JsonUpdateUserInfos::handle(Poco::Dynamic::Var params) } else { - auto result = user->login(old_password); - if (result == 1) { + auto secret_key = user->createSecretKey(old_password); + if (secret_key->getKeyHashed() == user_model->getPasswordHashed()) { old_password_valid = true; } - else if (result == -3) { + else if (secret_key.isNull()) { jsonErrorsArray.add("Password calculation for this user already running, please try again later"); } else { jsonErrorsArray.add("User.password_old didn't match"); } - - if (result == 2) { - Poco::Thread::sleep(ServerConfig::g_FakeLoginSleepTime); - } } } @@ -228,7 +229,12 @@ Poco::JSON::Object* JsonUpdateUserInfos::handle(Poco::Dynamic::Var params) } result->set("errors", jsonErrorsArray); result->set("valid_values", extractet_values); - result->set("state", "success"); + if (!jsonErrorsArray.size()) { + result->set("state", "success"); + } + else { + result->set("state", "error"); + } return result; } \ No newline at end of file diff --git a/login_server/src/cpp/test/JSONInterface/TestJsonUpdateUserInfos.cpp b/login_server/src/cpp/test/JSONInterface/TestJsonUpdateUserInfos.cpp index 64891fb01..a1e2fbf79 100644 --- a/login_server/src/cpp/test/JSONInterface/TestJsonUpdateUserInfos.cpp +++ b/login_server/src/cpp/test/JSONInterface/TestJsonUpdateUserInfos.cpp @@ -11,7 +11,7 @@ void TestJsonUpdateUserInfos::SetUp() //sm->init(); mUserSession = sm->getNewSession(); auto user = controller::User::create(); - user->getModel()->setEmail("Jeet_bb@gmail.com"); + user->load("Jeet_bb@gmail.com"); mUserSession->setUser(user); } @@ -41,7 +41,9 @@ TEST_F(TestJsonUpdateUserInfos, EmptyOldPassword) update->set("User.password", "haLL1o_/%s"); auto params = chooseAccount(update); + Profiler timeUsed; auto result = jsonCall.handle(params); + ASSERT_LE(timeUsed.millis(), 300); auto errors = result->get("errors"); ASSERT_TRUE(errors.isArray()); @@ -56,6 +58,11 @@ TEST_F(TestJsonUpdateUserInfos, EmptyOldPassword) ASSERT_EQ(error_array.size(), 1); ASSERT_EQ(error_array.getElement(0), "User.password_old not found"); + auto state = result->get("state"); + ASSERT_FALSE(state.isEmpty()); + ASSERT_TRUE(state.isString()); + ASSERT_EQ(state.toString(), "error"); + delete result; } @@ -67,7 +74,9 @@ TEST_F(TestJsonUpdateUserInfos, OnlyOldPassword) update->set("User.password_old", "TestP4ssword&H"); auto params = chooseAccount(update); + Profiler timeUsed; auto result = jsonCall.handle(params); + ASSERT_LE(timeUsed.millis(), 200); auto errors = result->get("errors"); ASSERT_TRUE(errors.isArray()); @@ -79,13 +88,18 @@ TEST_F(TestJsonUpdateUserInfos, OnlyOldPassword) Poco::JSON::Array error_array = errors.extract(); ASSERT_EQ(error_array.size(), 0); + auto state = result->get("state"); + ASSERT_FALSE(state.isEmpty()); + ASSERT_TRUE(state.isString()); + ASSERT_EQ(state.toString(), "success"); + delete result; } TEST_F(TestJsonUpdateUserInfos, WrongPassword) { JsonUpdateUserInfos jsonCall; - mUserSession->loadUser("Jeet_bb@gmail.com", "TestP4ssword&H"); + ASSERT_EQ(mUserSession->loadUser("Jeet_bb@gmail.com", "TestP4ssword&H"), USER_COMPLETE); Poco::JSON::Object::Ptr update = new Poco::JSON::Object; update->set("User.password", "newPassword"); @@ -94,7 +108,7 @@ TEST_F(TestJsonUpdateUserInfos, WrongPassword) auto params = chooseAccount(update); Profiler timeUsed; auto result = jsonCall.handle(params); - ASSERT_GE(timeUsed.millis(), ServerConfig::g_FakeLoginSleepTime-200); + ASSERT_GE(timeUsed.millis(), ServerConfig::g_FakeLoginSleepTime * 0.75); auto errors = result->get("errors"); ASSERT_TRUE(errors.isArray()); @@ -107,5 +121,76 @@ TEST_F(TestJsonUpdateUserInfos, WrongPassword) ASSERT_EQ(error_array.size(), 1); ASSERT_EQ(error_array.getElement(0), "User.password_old didn't match"); + auto state = result->get("state"); + ASSERT_FALSE(state.isEmpty()); + ASSERT_TRUE(state.isString()); + ASSERT_EQ(state.toString(), "error"); + + delete result; +} + +TEST_F(TestJsonUpdateUserInfos, EmptyPassword) +{ + JsonUpdateUserInfos jsonCall; + Poco::JSON::Object::Ptr update = new Poco::JSON::Object; + + update->set("User.password", ""); + update->set("User.password_old", "TestP4sswordH"); + + auto params = chooseAccount(update); + Profiler timeUsed; + auto result = jsonCall.handle(params); + ASSERT_LE(timeUsed.millis(), 200); + + auto errors = result->get("errors"); + ASSERT_TRUE(errors.isArray()); + auto valid_values_obj = result->get("valid_values"); + ASSERT_TRUE(valid_values_obj.isInteger()); + int valid_values = 0; + valid_values_obj.convert(valid_values); + ASSERT_EQ(valid_values, 0); + Poco::JSON::Array error_array = errors.extract(); + ASSERT_EQ(error_array.size(), 1); + ASSERT_EQ(error_array.getElement(0), "User.password is empty"); + + auto state = result->get("state"); + ASSERT_FALSE(state.isEmpty()); + ASSERT_TRUE(state.isString()); + ASSERT_EQ(state.toString(), "error"); + + delete result; +} + + +TEST_F(TestJsonUpdateUserInfos, CorrectPassword) +{ + JsonUpdateUserInfos jsonCall; + ASSERT_EQ(mUserSession->loadUser("Jeet_bb@gmail.com", "TestP4ssword&H"), USER_COMPLETE); + + Poco::JSON::Object::Ptr update = new Poco::JSON::Object; + + update->set("User.password", "newPassword"); + update->set("User.password_old", "TestP4ssword&H"); + + auto params = chooseAccount(update); + Profiler timeUsed; + auto result = jsonCall.handle(params); + ASSERT_GE(timeUsed.millis(), ServerConfig::g_FakeLoginSleepTime * 0.75); + + auto errors = result->get("errors"); + ASSERT_TRUE(errors.isArray()); + auto valid_values_obj = result->get("valid_values"); + ASSERT_TRUE(valid_values_obj.isInteger()); + int valid_values = 0; + valid_values_obj.convert(valid_values); + EXPECT_EQ(valid_values, 1); + Poco::JSON::Array error_array = errors.extract(); + ASSERT_EQ(error_array.size(), 0); + + auto state = result->get("state"); + ASSERT_FALSE(state.isEmpty()); + ASSERT_TRUE(state.isString()); + ASSERT_EQ(state.toString(), "success"); + delete result; } \ No newline at end of file From 23477aed53b49abe95a3ff5b8120dff2e1813d04 Mon Sep 17 00:00:00 2001 From: einhornimmond Date: Mon, 14 Jun 2021 13:37:18 +0200 Subject: [PATCH 11/12] make change password behave more like the other - more tests - make session_id optional, if not set take Login-Server Cookie - check only for password_old if user was logged in with password, else reset password from email wouldn't work - make successfull change password also add 1 to valid_values - don't update other fields in db if only update password was called --- .../JsonRequestHandlerFactory.cpp | 2 +- .../cpp/JSONInterface/JsonUpdateUserInfos.cpp | 120 +++++++++++------- .../cpp/JSONInterface/JsonUpdateUserInfos.h | 3 + .../JSONInterface/TestJsonUpdateUserInfos.cpp | 106 ++++++++++++++-- 4 files changed, 176 insertions(+), 55 deletions(-) diff --git a/login_server/src/cpp/JSONInterface/JsonRequestHandlerFactory.cpp b/login_server/src/cpp/JSONInterface/JsonRequestHandlerFactory.cpp index 3f8536a74..19772f3e1 100644 --- a/login_server/src/cpp/JSONInterface/JsonRequestHandlerFactory.cpp +++ b/login_server/src/cpp/JSONInterface/JsonRequestHandlerFactory.cpp @@ -100,7 +100,7 @@ Poco::Net::HTTPRequestHandler* JsonRequestHandlerFactory::createRequestHandler(c return new JsonGetUserInfos; } else if (url_first_part == "/updateUserInfos") { - return new JsonUpdateUserInfos; + return new JsonUpdateUserInfos(s); } else if (url_first_part == "/search") { return new JsonSearch; diff --git a/login_server/src/cpp/JSONInterface/JsonUpdateUserInfos.cpp b/login_server/src/cpp/JSONInterface/JsonUpdateUserInfos.cpp index 6756ca437..52115dee2 100644 --- a/login_server/src/cpp/JSONInterface/JsonUpdateUserInfos.cpp +++ b/login_server/src/cpp/JSONInterface/JsonUpdateUserInfos.cpp @@ -4,6 +4,13 @@ #include "../SingletonManager/LanguageManager.h" #include "../tasks/AuthenticatedEncryptionCreateKeyTask.h" + +JsonUpdateUserInfos::JsonUpdateUserInfos(Session* session) + : JsonRequestHandler(session) +{ + +} + Poco::JSON::Object* JsonUpdateUserInfos::handle(Poco::Dynamic::Var params) { /* @@ -28,7 +35,11 @@ Poco::JSON::Object* JsonUpdateUserInfos::handle(Poco::Dynamic::Var params) /// Throws InvalidAccessException if Var is empty. try { paramJsonObject->get("email").convert(email); - paramJsonObject->get("session_id").convert(session_id); + + auto session_id_obj = paramJsonObject->get("session_id"); + if (!session_id_obj.isEmpty()) { + session_id_obj.convert(session_id); + } updates = paramJsonObject->getObject("update"); } catch (Poco::Exception& ex) { @@ -39,18 +50,21 @@ Poco::JSON::Object* JsonUpdateUserInfos::handle(Poco::Dynamic::Var params) return stateError("parameter format unknown"); } - if (!session_id) { + if (!session_id && !mSession) { return stateError("session_id invalid"); } if (updates.isNull()) { return stateError("update is zero or not an object"); } - auto session = sm->getSession(session_id); - if (!session) { + if (session_id) { + mSession = sm->getSession(session_id); + } + + if (!mSession) { return customStateError("not found", "session not found"); } - auto user = session->getNewUser(); + auto user = mSession->getNewUser(); auto user_model = user->getModel(); if (user_model->getEmail() != email) { return customStateError("not same", "email don't belong to logged in user"); @@ -61,6 +75,7 @@ Poco::JSON::Object* JsonUpdateUserInfos::handle(Poco::Dynamic::Var params) Poco::JSON::Array jsonErrorsArray; int extractet_values = 0; + bool password_changed = false; //['User.first_name' => 'first_name', 'User.last_name' => 'last_name', 'User.disabled' => 0|1, 'User.language' => 'de'] for (auto it = updates->begin(); it != updates->end(); it++) { std::string name = it->first; @@ -154,43 +169,10 @@ Poco::JSON::Object* JsonUpdateUserInfos::handle(Poco::Dynamic::Var params) jsonErrorsArray.add("User.password is empty"); } else { - std::string old_password; - auto old_password_obj = updates->get("User.password_old"); - if (old_password_obj.isEmpty()) { - jsonErrorsArray.add("User.password_old not found"); - } - else if (!old_password_obj.isString()) { - jsonErrorsArray.add("User.password_old isn't a string"); - } - else { - old_password_obj.convert(old_password); - } - bool old_password_valid = false; - NotificationList errors; - if (old_password.size()) - { - if (!sm->checkPwdValidation(old_password, &errors, LanguageManager::getInstance()->getFreeCatalog(LANG_EN))) { - jsonErrorsArray.add("User.password_old didn't match"); - Poco::Thread::sleep(ServerConfig::g_FakeLoginSleepTime); - } - else - { - auto secret_key = user->createSecretKey(old_password); - if (secret_key->getKeyHashed() == user_model->getPasswordHashed()) { - old_password_valid = true; - } - else if (secret_key.isNull()) { - jsonErrorsArray.add("Password calculation for this user already running, please try again later"); - } - else { - jsonErrorsArray.add("User.password_old didn't match"); - } - } - - } - if (old_password_valid) + if (!user->hasPassword() || isOldPasswordValid(updates, jsonErrorsArray)) { + NotificationList errors; if (!sm->checkPwdValidation(value.toString(), &errors, LanguageManager::getInstance()->getFreeCatalog(LANG_EN))) { jsonErrorsArray.add("User.password isn't valid"); jsonErrorsArray.add(errors.getErrorsArray()); @@ -203,9 +185,16 @@ Poco::JSON::Object* JsonUpdateUserInfos::handle(Poco::Dynamic::Var params) // 0 = new and current passwords are the same case 0: jsonErrorsArray.add("new password is the same as old password"); break; // 1 = password changed, private key re-encrypted and saved into db - //case 1: extractet_values++; break; + case 1: + extractet_values++; + password_changed = true; + break; // 2 = password changed, only hash stored in db, couldn't load private key for re-encryption - case 2: jsonErrorsArray.add("password changed, couldn't load private key for re-encryption"); break; + case 2: + jsonErrorsArray.add("password changed, couldn't load private key for re-encryption"); + extractet_values++; + password_changed = true; + break; // -1 = stored pubkey and private key didn't match case -1: jsonErrorsArray.add("stored pubkey and private key didn't match"); break; } @@ -220,7 +209,9 @@ Poco::JSON::Object* JsonUpdateUserInfos::handle(Poco::Dynamic::Var params) jsonErrorsArray.add("update parameter invalid"); } } - if (extractet_values > 0) { + // if only password was changed, no need to call an additional db update + // password db entry will be updated inside of controller::User::setNewPassword method + if (extractet_values - (int)password_changed > 0) { if (1 != user_model->updateFieldsFromCommunityServer()) { user_model->addError(new Error("JsonUpdateUserInfos", "error by saving update to db")); user_model->sendErrorsAsEmail(); @@ -237,4 +228,47 @@ Poco::JSON::Object* JsonUpdateUserInfos::handle(Poco::Dynamic::Var params) } return result; +} + +bool JsonUpdateUserInfos::isOldPasswordValid(Poco::JSON::Object::Ptr updates, Poco::JSON::Array& errors) +{ + auto sm = SessionManager::getInstance(); + auto user = mSession->getNewUser(); + + std::string old_password; + + auto old_password_obj = updates->get("User.password_old"); + if (old_password_obj.isEmpty()) { + errors.add("User.password_old not found"); + } + else if (!old_password_obj.isString()) { + errors.add("User.password_old isn't a string"); + } + else { + old_password_obj.convert(old_password); + } + + NotificationList local_errors; + if (old_password.size()) + { + if (!sm->checkPwdValidation(old_password, &local_errors, LanguageManager::getInstance()->getFreeCatalog(LANG_EN))) { + errors.add("User.password_old didn't match"); + Poco::Thread::sleep(ServerConfig::g_FakeLoginSleepTime); + } + else + { + auto secret_key = user->createSecretKey(old_password); + if (secret_key->getKeyHashed() == user->getModel()->getPasswordHashed()) { + return true; + } + else if (secret_key.isNull()) { + errors.add("Password calculation for this user already running, please try again later"); + } + else { + errors.add("User.password_old didn't match"); + } + } + + } + return false; } \ No newline at end of file diff --git a/login_server/src/cpp/JSONInterface/JsonUpdateUserInfos.h b/login_server/src/cpp/JSONInterface/JsonUpdateUserInfos.h index 2c1ca94fc..f2b3b9c64 100644 --- a/login_server/src/cpp/JSONInterface/JsonUpdateUserInfos.h +++ b/login_server/src/cpp/JSONInterface/JsonUpdateUserInfos.h @@ -14,10 +14,13 @@ class JsonUpdateUserInfos : public JsonRequestHandler { public: + JsonUpdateUserInfos(Session* session); Poco::JSON::Object* handle(Poco::Dynamic::Var params); protected: + bool isOldPasswordValid(Poco::JSON::Object::Ptr updates, Poco::JSON::Array& errors); + }; diff --git a/login_server/src/cpp/test/JSONInterface/TestJsonUpdateUserInfos.cpp b/login_server/src/cpp/test/JSONInterface/TestJsonUpdateUserInfos.cpp index a1e2fbf79..a309f679e 100644 --- a/login_server/src/cpp/test/JSONInterface/TestJsonUpdateUserInfos.cpp +++ b/login_server/src/cpp/test/JSONInterface/TestJsonUpdateUserInfos.cpp @@ -26,7 +26,6 @@ void TestJsonUpdateUserInfos::TearDown() Poco::JSON::Object::Ptr TestJsonUpdateUserInfos::chooseAccount(const Poco::JSON::Object::Ptr update) { Poco::JSON::Object::Ptr params = new Poco::JSON::Object; - params->set("session_id", mUserSession->getHandle()); params->set("email", mUserSession->getNewUser()->getModel()->getEmail()); params->set("update", update); return params; @@ -35,7 +34,9 @@ Poco::JSON::Object::Ptr TestJsonUpdateUserInfos::chooseAccount(const Poco::JSON: TEST_F(TestJsonUpdateUserInfos, EmptyOldPassword) { - JsonUpdateUserInfos jsonCall; + JsonUpdateUserInfos jsonCall(mUserSession); + ASSERT_EQ(mUserSession->loadUser("Jeet_bb@gmail.com", "TestP4ssword&H"), USER_COMPLETE); + Poco::JSON::Object::Ptr update = new Poco::JSON::Object; update->set("User.password", "haLL1o_/%s"); @@ -68,7 +69,7 @@ TEST_F(TestJsonUpdateUserInfos, EmptyOldPassword) TEST_F(TestJsonUpdateUserInfos, OnlyOldPassword) { - JsonUpdateUserInfos jsonCall; + JsonUpdateUserInfos jsonCall(mUserSession); Poco::JSON::Object::Ptr update = new Poco::JSON::Object; update->set("User.password_old", "TestP4ssword&H"); @@ -98,7 +99,7 @@ TEST_F(TestJsonUpdateUserInfos, OnlyOldPassword) TEST_F(TestJsonUpdateUserInfos, WrongPassword) { - JsonUpdateUserInfos jsonCall; + JsonUpdateUserInfos jsonCall(mUserSession); ASSERT_EQ(mUserSession->loadUser("Jeet_bb@gmail.com", "TestP4ssword&H"), USER_COMPLETE); Poco::JSON::Object::Ptr update = new Poco::JSON::Object; @@ -131,7 +132,7 @@ TEST_F(TestJsonUpdateUserInfos, WrongPassword) TEST_F(TestJsonUpdateUserInfos, EmptyPassword) { - JsonUpdateUserInfos jsonCall; + JsonUpdateUserInfos jsonCall(mUserSession); Poco::JSON::Object::Ptr update = new Poco::JSON::Object; update->set("User.password", ""); @@ -161,10 +162,45 @@ TEST_F(TestJsonUpdateUserInfos, EmptyPassword) delete result; } - -TEST_F(TestJsonUpdateUserInfos, CorrectPassword) +TEST_F(TestJsonUpdateUserInfos, NewPasswordSameAsOldPassword) { - JsonUpdateUserInfos jsonCall; + JsonUpdateUserInfos jsonCall(mUserSession); + ASSERT_EQ(mUserSession->loadUser("Jeet_bb@gmail.com", "TestP4ssword&H"), USER_COMPLETE); + + Poco::JSON::Object::Ptr update = new Poco::JSON::Object; + + update->set("User.password", "TestP4ssword&H"); + update->set("User.password_old", "TestP4ssword&H"); + + auto params = chooseAccount(update); + Profiler timeUsed; + auto result = jsonCall.handle(params); + ASSERT_GE(timeUsed.millis(), ServerConfig::g_FakeLoginSleepTime * 0.75); + + auto errors = result->get("errors"); + ASSERT_TRUE(errors.isArray()); + auto valid_values_obj = result->get("valid_values"); + ASSERT_TRUE(valid_values_obj.isInteger()); + int valid_values = 0; + valid_values_obj.convert(valid_values); + + Poco::JSON::Array error_array = errors.extract(); + auto state = result->get("state"); + ASSERT_FALSE(state.isEmpty()); + ASSERT_TRUE(state.isString()); + + + EXPECT_EQ(valid_values, 0); + ASSERT_EQ(error_array.size(), 1); + ASSERT_EQ(state.toString(), "error"); + ASSERT_EQ(error_array.getElement(0), "new password is the same as old password"); + + delete result; +} + +TEST_F(TestJsonUpdateUserInfos, PasswordNotSecureEnough) +{ + JsonUpdateUserInfos jsonCall(mUserSession); ASSERT_EQ(mUserSession->loadUser("Jeet_bb@gmail.com", "TestP4ssword&H"), USER_COMPLETE); Poco::JSON::Object::Ptr update = new Poco::JSON::Object; @@ -183,14 +219,62 @@ TEST_F(TestJsonUpdateUserInfos, CorrectPassword) ASSERT_TRUE(valid_values_obj.isInteger()); int valid_values = 0; valid_values_obj.convert(valid_values); - EXPECT_EQ(valid_values, 1); - Poco::JSON::Array error_array = errors.extract(); - ASSERT_EQ(error_array.size(), 0); + Poco::JSON::Array error_array = errors.extract(); auto state = result->get("state"); ASSERT_FALSE(state.isEmpty()); ASSERT_TRUE(state.isString()); + + if ((ServerConfig::g_AllowUnsecureFlags | ServerConfig::UNSECURE_ALLOW_ALL_PASSWORDS) == ServerConfig::UNSECURE_ALLOW_ALL_PASSWORDS) { + EXPECT_EQ(valid_values, 1); + ASSERT_EQ(error_array.size(), 0); + ASSERT_EQ(state.toString(), "success"); + } + else { + EXPECT_EQ(valid_values, 0); + + ASSERT_EQ(error_array.size(), 2); + ASSERT_EQ(error_array.getElement(0), "User.password isn't valid"); + + ASSERT_EQ(state.toString(), "error"); + } + + delete result; +} + + +TEST_F(TestJsonUpdateUserInfos, PasswordCorrect) +{ + JsonUpdateUserInfos jsonCall(mUserSession); + ASSERT_EQ(mUserSession->loadUser("Jeet_bb@gmail.com", "TestP4ssword&H"), USER_COMPLETE); + + Poco::JSON::Object::Ptr update = new Poco::JSON::Object; + + update->set("User.password", "uasjUs7ZS/as12"); + update->set("User.password_old", "TestP4ssword&H"); + + auto params = chooseAccount(update); + Profiler timeUsed; + auto result = jsonCall.handle(params); + ASSERT_GE(timeUsed.millis(), ServerConfig::g_FakeLoginSleepTime * 0.75); + + auto errors = result->get("errors"); + ASSERT_TRUE(errors.isArray()); + auto valid_values_obj = result->get("valid_values"); + ASSERT_TRUE(valid_values_obj.isInteger()); + int valid_values = 0; + valid_values_obj.convert(valid_values); + + Poco::JSON::Array error_array = errors.extract(); + auto state = result->get("state"); + ASSERT_FALSE(state.isEmpty()); + ASSERT_TRUE(state.isString()); + + + EXPECT_EQ(valid_values, 1); + ASSERT_EQ(error_array.size(), 0); ASSERT_EQ(state.toString(), "success"); + delete result; } \ No newline at end of file From d84fb0480673595c81e99f061538499d9d91354c Mon Sep 17 00:00:00 2001 From: einhornimmond Date: Mon, 14 Jun 2021 13:39:30 +0200 Subject: [PATCH 12/12] update doc --- docu/login_server.api.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docu/login_server.api.md b/docu/login_server.api.md index c779e543c..ab9157edf 100644 --- a/docu/login_server.api.md +++ b/docu/login_server.api.md @@ -241,7 +241,7 @@ Notes: - User will be disabled if he wants his account deleted, but has transactions. Until transactions are saved in real blockchain, we need this data because the public key is in db only saved in state_users so if we delete this entry, validating all transactions is no longer possible. - Disabled Users can neither login nor receive transactions. - It is not required to provide all fields of `update`, it can be a subset depending on what you intend to change. -- `User.password`: to change user password, needed current passwort in `User.password_old` +- `User.password`: to change user password, needed current passwort in `User.password_old` (only if user was logged in with his password, not by reset password email code) ### Response In case of success: @@ -254,7 +254,7 @@ In case of success: } ``` -- `valid_values`: should contain count of entries in update if no error occurred (User.password will not be counted) +- `valid_values`: should contain count of entries in update if no error occurred (User.password will now be counted also) - `errors`: contain on error string for every entry in update, which type isn't like expected - `password`: - "new password is the same as old password": no change taking place