From 90073899a17fefd9df8c789a5fbe4e8474f92d9b Mon Sep 17 00:00:00 2001 From: einhornimmond Date: Thu, 10 Jun 2021 13:06:08 +0200 Subject: [PATCH 01/85] 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/85] 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/85] 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/85] 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/85] 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/85] 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/85] 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/85] 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/85] 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/85] 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/85] 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/85] 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 From f09905e00c443c01018f381d7995ae7db2cfadb8 Mon Sep 17 00:00:00 2001 From: einhornimmond Date: Thu, 10 Jun 2021 13:06:08 +0200 Subject: [PATCH 13/85] for password change old password is required --- .../cpp/JSONInterface/JsonUpdateUserInfos.cpp | 39 +++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/login_server/src/cpp/JSONInterface/JsonUpdateUserInfos.cpp b/login_server/src/cpp/JSONInterface/JsonUpdateUserInfos.cpp index 804d06987..afcfca929 100644 --- a/login_server/src/cpp/JSONInterface/JsonUpdateUserInfos.cpp +++ b/login_server/src/cpp/JSONInterface/JsonUpdateUserInfos.cpp @@ -149,7 +149,44 @@ Poco::JSON::Object* JsonUpdateUserInfos::handle(Poco::Dynamic::Var params) if (str_val.size() > 0) { + 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()); @@ -170,6 +207,8 @@ Poco::JSON::Object* JsonUpdateUserInfos::handle(Poco::Dynamic::Var params) } + + } } } From 12188c77e86aef0d7a06c053a3d320b7563f1b50 Mon Sep 17 00:00:00 2001 From: einhornimmond Date: Thu, 10 Jun 2021 13:06:50 +0200 Subject: [PATCH 14/85] 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 34162de1ed2c0f3a21a9a54a243bbb3f74fbea57 Mon Sep 17 00:00:00 2001 From: einhornimmond Date: Thu, 10 Jun 2021 13:07:15 +0200 Subject: [PATCH 15/85] 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 a2261f08d041c938c6026071dce80be91c70b62e Mon Sep 17 00:00:00 2001 From: einhornimmond Date: Thu, 10 Jun 2021 15:26:39 +0200 Subject: [PATCH 16/85] 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 de4243b245f6dbffaaea4f23d3523561f5288994 Mon Sep 17 00:00:00 2001 From: einhornimmond Date: Thu, 10 Jun 2021 15:27:09 +0200 Subject: [PATCH 17/85] fix bug shown through testing --- .../src/cpp/JSONInterface/JsonUpdateUserInfos.cpp | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/login_server/src/cpp/JSONInterface/JsonUpdateUserInfos.cpp b/login_server/src/cpp/JSONInterface/JsonUpdateUserInfos.cpp index afcfca929..cadf19be7 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,12 +188,14 @@ Poco::JSON::Object* JsonUpdateUserInfos::handle(Poco::Dynamic::Var params) } } - + 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 { + else + { auto result_new_password = user->setNewPassword(value.toString()); switch (result_new_password) { @@ -205,9 +209,8 @@ Poco::JSON::Object* JsonUpdateUserInfos::handle(Poco::Dynamic::Var params) case -1: jsonErrorsArray.add("stored pubkey and private key didn't match"); break; } - } - + } } } From 5170c8e61b868a17ffd48561e349c5d3954229f3 Mon Sep 17 00:00:00 2001 From: einhornimmond Date: Thu, 10 Jun 2021 15:27:28 +0200 Subject: [PATCH 18/85] 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 34af447638747e4433b719ec7ebfd97d1adfd81d Mon Sep 17 00:00:00 2001 From: einhornimmond Date: Fri, 11 Jun 2021 12:30:23 +0200 Subject: [PATCH 19/85] 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 eb5f723c5749a8d2fb0fc79dbae177e383cf1571 Mon Sep 17 00:00:00 2001 From: einhornimmond Date: Fri, 11 Jun 2021 12:35:52 +0200 Subject: [PATCH 20/85] 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 e3f52c46673c7c5f3743b1fabec2180fdc9d94b5 Mon Sep 17 00:00:00 2001 From: einhornimmond Date: Fri, 11 Jun 2021 14:42:50 +0200 Subject: [PATCH 21/85] 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 670f7cfff9dd097529c0d9f9dab57f0c63966f82 Mon Sep 17 00:00:00 2001 From: einhornimmond Date: Fri, 11 Jun 2021 14:43:15 +0200 Subject: [PATCH 22/85] more tests and fixes to get test working --- .../cpp/JSONInterface/JsonUpdateUserInfos.cpp | 15 +-- .../JSONInterface/TestJsonUpdateUserInfos.cpp | 91 ++++++++++++++++++- 2 files changed, 96 insertions(+), 10 deletions(-) diff --git a/login_server/src/cpp/JSONInterface/JsonUpdateUserInfos.cpp b/login_server/src/cpp/JSONInterface/JsonUpdateUserInfos.cpp index cadf19be7..49a0e0502 100644 --- a/login_server/src/cpp/JSONInterface/JsonUpdateUserInfos.cpp +++ b/login_server/src/cpp/JSONInterface/JsonUpdateUserInfos.cpp @@ -171,21 +171,17 @@ 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); } - } } if (old_password_valid) @@ -230,7 +226,12 @@ Poco::JSON::Object* JsonUpdateUserInfos::handle(Poco::Dynamic::Var params) } result->set("errors", jsonErrorsArray); result->set("valid_values", extractet_values); + if (!jsonErrorsArray.size()) { result->set("state", "success"); + } + else { + result->set("state", "error"); + } return result; } 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 4df9ad380f5f848f76626a818e8f9b68ad7c7722 Mon Sep 17 00:00:00 2001 From: einhornimmond Date: Mon, 14 Jun 2021 13:37:18 +0200 Subject: [PATCH 23/85] 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 | 2 + .../JSONInterface/TestJsonUpdateUserInfos.cpp | 106 ++++++++++++++-- 4 files changed, 175 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 49a0e0502..003cbc59c 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; @@ -149,43 +164,10 @@ Poco::JSON::Object* JsonUpdateUserInfos::handle(Poco::Dynamic::Var params) if (str_val.size() > 0) { - 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()); @@ -198,9 +180,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; } @@ -217,7 +206,9 @@ Poco::JSON::Object* JsonUpdateUserInfos::handle(Poco::Dynamic::Var params) jsonErrorsArray.add(error_message); } } - 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(); @@ -253,4 +244,47 @@ std::string JsonUpdateUserInfos::validateString(Poco::Dynamic::Var value, const return ""; } return string_value; +}} + +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 f651fb345..38e105ee7 100644 --- a/login_server/src/cpp/JSONInterface/JsonUpdateUserInfos.h +++ b/login_server/src/cpp/JSONInterface/JsonUpdateUserInfos.h @@ -14,11 +14,13 @@ class JsonUpdateUserInfos : public JsonRequestHandler { public: + JsonUpdateUserInfos(Session* session); Poco::JSON::Object* handle(Poco::Dynamic::Var params); protected: std::string validateString(Poco::Dynamic::Var value, const char* fieldName, Poco::JSON::Array& errorArray); + 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 060b8e3fcc6f87381f5a610b38ebc98502abc734 Mon Sep 17 00:00:00 2001 From: einhornimmond Date: Mon, 14 Jun 2021 13:39:30 +0200 Subject: [PATCH 24/85] 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 From bdbe5c4a935a1ef5ab10a9bad3395aa5686542f7 Mon Sep 17 00:00:00 2001 From: einhornimmond Date: Mon, 14 Jun 2021 16:50:24 +0200 Subject: [PATCH 25/85] rebase onto master, fix bug, update layout (wasn't pretty because of rebase) --- .../cpp/JSONInterface/JsonUpdateUserInfos.cpp | 51 +++++++++---------- 1 file changed, 25 insertions(+), 26 deletions(-) diff --git a/login_server/src/cpp/JSONInterface/JsonUpdateUserInfos.cpp b/login_server/src/cpp/JSONInterface/JsonUpdateUserInfos.cpp index 003cbc59c..b708d346f 100644 --- a/login_server/src/cpp/JSONInterface/JsonUpdateUserInfos.cpp +++ b/login_server/src/cpp/JSONInterface/JsonUpdateUserInfos.cpp @@ -164,37 +164,36 @@ Poco::JSON::Object* JsonUpdateUserInfos::handle(Poco::Dynamic::Var params) if (str_val.size() > 0) { - 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()); - } + 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()); + 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++; + 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"); + 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; + } - 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++; - 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"); - 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; } - - } } } @@ -240,11 +239,11 @@ std::string JsonUpdateUserInfos::validateString(Poco::Dynamic::Var value, const if (string_value.size() == 0) { errorMessage += " is empty"; - errorArray.add(errorArray); + errorArray.add(errorMessage); return ""; } return string_value; -}} +} bool JsonUpdateUserInfos::isOldPasswordValid(Poco::JSON::Object::Ptr updates, Poco::JSON::Array& errors) { From 1e95f7a52fbafa8bc1943d996ad69d6d21d42d80 Mon Sep 17 00:00:00 2001 From: einhornimmond Date: Mon, 14 Jun 2021 16:56:03 +0200 Subject: [PATCH 26/85] fix syntax error occuring by merging --- .../src/cpp/JSONInterface/JsonUpdateUserInfos.cpp | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/login_server/src/cpp/JSONInterface/JsonUpdateUserInfos.cpp b/login_server/src/cpp/JSONInterface/JsonUpdateUserInfos.cpp index 53764b301..5aa137ce0 100644 --- a/login_server/src/cpp/JSONInterface/JsonUpdateUserInfos.cpp +++ b/login_server/src/cpp/JSONInterface/JsonUpdateUserInfos.cpp @@ -144,7 +144,8 @@ Poco::JSON::Object* JsonUpdateUserInfos::handle(Poco::Dynamic::Var params) jsonErrorsArray.add("User.disabled isn't a boolean or integer"); } } - else if ("User.language" == name && value.size() > 0) { + else if ("User.language" == name && value.size() > 0) + { std::string str_val = validateString(value, "User.language", jsonErrorsArray); if (str_val.size() > 0) { @@ -159,14 +160,13 @@ Poco::JSON::Object* JsonUpdateUserInfos::handle(Poco::Dynamic::Var params) } } - else if ("User.password" == name && (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) + { std::string str_val = validateString(value, "User.password", jsonErrorsArray); - if (str_val.size() > 0) { + if (str_val.size() > 0) + { - if (!user->hasPassword() || isOldPasswordValid(updates, jsonErrorsArray)) - { - if (!user->hasPassword() || isOldPasswordValid(updates, jsonErrorsArray)) { NotificationList errors; @@ -193,7 +193,7 @@ Poco::JSON::Object* JsonUpdateUserInfos::handle(Poco::Dynamic::Var params) 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; + case -1: jsonErrorsArray.add("stored pubkey and private key didn't match"); break; } } From 966202eded94771b7867d24d74a63d5b9870531a Mon Sep 17 00:00:00 2001 From: einhornimmond Date: Mon, 14 Jun 2021 17:15:02 +0200 Subject: [PATCH 27/85] add username rules in form of regular expression --- login_server/src/cpp/SingletonManager/SessionManager.cpp | 1 + login_server/src/cpp/SingletonManager/SessionManager.h | 1 + 2 files changed, 2 insertions(+) diff --git a/login_server/src/cpp/SingletonManager/SessionManager.cpp b/login_server/src/cpp/SingletonManager/SessionManager.cpp index 30a6f6f61..a26ecb0ab 100644 --- a/login_server/src/cpp/SingletonManager/SessionManager.cpp +++ b/login_server/src/cpp/SingletonManager/SessionManager.cpp @@ -44,6 +44,7 @@ bool SessionManager::init() switch (i) { //case VALIDATE_NAME: mValidations[i] = new Poco::RegularExpression("/^[a-zA-Z_ -]{3,}$/"); break; case VALIDATE_NAME: mValidations[i] = new Poco::RegularExpression("^[^<>&;]{3,}$"); break; + case VALIDATE_USERNAME: mValidations[i] = new Poco::RegularExpression("^[a-zA-Z][a-zA-Z0-9_-]*$"); break; case VALIDATE_EMAIL: mValidations[i] = new Poco::RegularExpression("^[a-zA-Z0-9.!#$%&?*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$"); break; case VALIDATE_PASSWORD: mValidations[i] = new Poco::RegularExpression("^(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.*[@$!%*?&+-_])[A-Za-z0-9@$!%*?&+-_]{8,}$"); break; case VALIDATE_PASSPHRASE: mValidations[i] = new Poco::RegularExpression("^(?:[a-z]* ){23}[a-z]*\s*$"); break; diff --git a/login_server/src/cpp/SingletonManager/SessionManager.h b/login_server/src/cpp/SingletonManager/SessionManager.h index 2e05b907a..e75ef42f7 100644 --- a/login_server/src/cpp/SingletonManager/SessionManager.h +++ b/login_server/src/cpp/SingletonManager/SessionManager.h @@ -24,6 +24,7 @@ enum SessionValidationTypes { VALIDATE_NAME, + VALIDATE_USERNAME, VALIDATE_EMAIL, VALIDATE_PASSWORD, VALIDATE_PASSPHRASE, From d578f5bbd81b982c57236047213cac01933c1f8d Mon Sep 17 00:00:00 2001 From: einhornimmond Date: Mon, 14 Jun 2021 17:15:44 +0200 Subject: [PATCH 28/85] check if new values the same as the current values and skip if so, use username rules regexp --- .../cpp/JSONInterface/JsonUpdateUserInfos.cpp | 62 ++++++++++--------- 1 file changed, 33 insertions(+), 29 deletions(-) diff --git a/login_server/src/cpp/JSONInterface/JsonUpdateUserInfos.cpp b/login_server/src/cpp/JSONInterface/JsonUpdateUserInfos.cpp index 5aa137ce0..86e79372d 100644 --- a/login_server/src/cpp/JSONInterface/JsonUpdateUserInfos.cpp +++ b/login_server/src/cpp/JSONInterface/JsonUpdateUserInfos.cpp @@ -86,7 +86,7 @@ Poco::JSON::Object* JsonUpdateUserInfos::handle(Poco::Dynamic::Var params) if ( "User.first_name" == name) { std::string str_val = validateString(value, "User.first_name", jsonErrorsArray); - if (str_val.size() > 0) { + if (str_val.size() > 0 && user_model->getFirstName() != str_val) { user_model->setFirstName(str_val); extractet_values++; } @@ -94,7 +94,7 @@ Poco::JSON::Object* JsonUpdateUserInfos::handle(Poco::Dynamic::Var params) else if ("User.last_name" == name ) { std::string str_val = validateString(value, "User.last_name", jsonErrorsArray); - if (str_val.size() > 0) { + if (str_val.size() > 0 && user_model->getLastName() != str_val) { user_model->setLastName(str_val); extractet_values++; } @@ -103,14 +103,18 @@ Poco::JSON::Object* JsonUpdateUserInfos::handle(Poco::Dynamic::Var params) else if ("User.username" == name) { std::string str_val = validateString(value, "User.username", jsonErrorsArray); - if (str_val.size() > 0) { + if (str_val.size() > 0 && user_model->getUsername() != str_val) { if (user_model->getUsername() != "") { jsonErrorsArray.add("change username currently not supported!"); } - else if (user_model->getUsername() != str_val) { + else + { if (user->isUsernameAlreadyUsed(str_val)) { jsonErrorsArray.add("username already used"); } + else if (!sm->isValid(str_val, VALIDATE_USERNAME)) { + jsonErrorsArray.add("username must start with [a-z] or [A-Z] and than can contain also [0-9], - and _"); + } else { user_model->setUsername(str_val); extractet_values++; @@ -121,34 +125,35 @@ Poco::JSON::Object* JsonUpdateUserInfos::handle(Poco::Dynamic::Var params) else if ("User.description" == name) { std::string str_val = validateString(value, "User.description", jsonErrorsArray); - if (str_val.size() > 0) { + if (str_val.size() > 0 && str_val != user_model->getDescription()) { user_model->setDescription(str_val); extractet_values++; } } else if ("User.disabled" == name) { - if (value.isBoolean()) { - bool disabled; + bool disabled; + + if (value.isInteger()) { + int idisabled; + value.convert(idisabled); + disabled = static_cast(idisabled); + } else if (value.isBoolean()) { value.convert(disabled); - user_model->setDisabled(disabled); - extractet_values++; - } - else if (value.isInteger()) { - int disabled; - value.convert(disabled); - user_model->setDisabled(static_cast(disabled)); - extractet_values++; } else { jsonErrorsArray.add("User.disabled isn't a boolean or integer"); } + if (user_model->isDisabled() != disabled) { + user_model->setDisabled(disabled); + extractet_values++; + } } else if ("User.language" == name && value.size() > 0) { std::string str_val = validateString(value, "User.language", jsonErrorsArray); - if (str_val.size() > 0) { + if (str_val.size() > 0 && user_model->getLanguageKey() != str_val) { auto lang = LanguageManager::languageFromString(str_val); if (LANG_NULL == lang) { jsonErrorsArray.add("User.language isn't a valid language"); @@ -180,20 +185,19 @@ Poco::JSON::Object* JsonUpdateUserInfos::handle(Poco::Dynamic::Var params) 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++; - 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"); - 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; + 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"); + 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; } } From 5f543738b89b7516b1f0c69694b018c654b1047c Mon Sep 17 00:00:00 2001 From: einhornimmond Date: Mon, 14 Jun 2021 17:17:21 +0200 Subject: [PATCH 29/85] add test for changes not changed --- .../JSONInterface/TestJsonUpdateUserInfos.cpp | 39 +++++++++++++++++-- 1 file changed, 36 insertions(+), 3 deletions(-) diff --git a/login_server/src/cpp/test/JSONInterface/TestJsonUpdateUserInfos.cpp b/login_server/src/cpp/test/JSONInterface/TestJsonUpdateUserInfos.cpp index a309f679e..0dbd01888 100644 --- a/login_server/src/cpp/test/JSONInterface/TestJsonUpdateUserInfos.cpp +++ b/login_server/src/cpp/test/JSONInterface/TestJsonUpdateUserInfos.cpp @@ -191,9 +191,8 @@ TEST_F(TestJsonUpdateUserInfos, NewPasswordSameAsOldPassword) 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"); + ASSERT_EQ(error_array.size(), 0); + ASSERT_EQ(state.toString(), "success"); delete result; } @@ -276,5 +275,39 @@ TEST_F(TestJsonUpdateUserInfos, PasswordCorrect) ASSERT_EQ(state.toString(), "success"); + delete result; +} + +TEST_F(TestJsonUpdateUserInfos, NoChanges) +{ + JsonUpdateUserInfos jsonCall(mUserSession); + + Poco::JSON::Object::Ptr update = new Poco::JSON::Object; + + update->set("User.first_name", "Darios"); + update->set("User.last_name", "Bruder"); + + auto params = chooseAccount(update); + Profiler timeUsed; + 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); + + 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(), 0); + ASSERT_EQ(state.toString(), "success"); + + delete result; } \ No newline at end of file From fb34135665cb9370c68e9d49712576d8f4208608 Mon Sep 17 00:00:00 2001 From: einhornimmond Date: Mon, 14 Jun 2021 17:24:40 +0200 Subject: [PATCH 30/85] prevent query email in our database with reset password function --- 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 26b00314c..c9e8b17e9 100644 --- a/login_server/src/cpp/JSONInterface/JsonSendEmail.cpp +++ b/login_server/src/cpp/JSONInterface/JsonSendEmail.cpp @@ -102,7 +102,7 @@ Poco::JSON::Object* JsonSendEmail::handle(Poco::Dynamic::Var params) Poco::Thread::sleep(ServerConfig::g_FakeLoginSleepTime); auto receiver_user = controller::User::create(); if (1 != receiver_user->load(email)) { - return stateError("invalid email"); + return stateSuccess(); } auto receiver_user_id = receiver_user->getModel()->getID(); std::string checkEmailUrl = receiver_user->getGroupBaseUrl() + ServerConfig::g_frontend_checkEmailPath; From 03bb9b82c4e0b88b94fddb7a6ccbc42892b86d24 Mon Sep 17 00:00:00 2001 From: einhornimmond Date: Mon, 14 Jun 2021 17:29:31 +0200 Subject: [PATCH 31/85] add new test folder to CMakeLists.txt for docker runs --- login_server/CMakeLists.txt.lib | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/login_server/CMakeLists.txt.lib b/login_server/CMakeLists.txt.lib index cb45dcfce..7f5f10e39 100644 --- a/login_server/CMakeLists.txt.lib +++ b/login_server/CMakeLists.txt.lib @@ -129,6 +129,7 @@ FILE(GLOB TEST_CRYPTO "src/cpp/test/crypto/*.cpp" "src/cpp/test/crypto/*.h") FILE(GLOB TEST_MODEL "src/cpp/test/model/*.cpp" "src/cpp/test/model/*.h") FILE(GLOB TEST_MODEL_TABLE "src/cpp/test/model/table/*.cpp" "src/cpp/test/model/table/*.h") FILE(GLOB TEST_CONTROLLER "src/cpp/test/controller/*.cpp" "src/cpp/test/controller/*.h") +FILE(GLOB TEST_JSON_INTERFACE "src/cpp/test/JSONInterface/*.cpp" "src/cpp/test/JSONInterface/*.h") SET(LOCAL_SRCS ${CONTROLLER} ${TINF} ${MAIN} ${HTTPInterface} ${COMPILED_PAGES} @@ -138,7 +139,7 @@ SET(LOCAL_SRCS ${PROTO_GRADIDO} ${PROTO_HEDERA} ) SET(LOCAL_TEST_SRC - ${TEST} ${TEST_CRYPTO} ${TEST_MODEL} ${TEST_MODEL_TABLE} ${TEST_CONTROLLER} + ${TEST} ${TEST_CRYPTO} ${TEST_MODEL} ${TEST_MODEL_TABLE} ${TEST_CONTROLLER} ${TEST_JSON_INTERFACE} ) aux_source_directory("src/cpp" LOCAL_SRCS) From f4069f6441215577981a1f98edb403466fefa9ef Mon Sep 17 00:00:00 2001 From: einhornimmond Date: Mon, 14 Jun 2021 18:12:50 +0200 Subject: [PATCH 32/85] update test infos on terminal, usd std::clog instead of printf because std::clog is also seen in docker runs --- login_server/src/cpp/test/main.cpp | 29 +++++++++++++++-------------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/login_server/src/cpp/test/main.cpp b/login_server/src/cpp/test/main.cpp index c875ec6a0..590849fb6 100644 --- a/login_server/src/cpp/test/main.cpp +++ b/login_server/src/cpp/test/main.cpp @@ -41,7 +41,7 @@ int runMysql(std::string sqlQuery) mysqlStatement.execute(true); } catch (Poco::Exception& ex) { - printf("exception in runMysql: %s\n", ex.displayText().data()); + std::clog << "exception in runMysql: " << ex.displayText() << std::endl; return -1; } return 0; @@ -74,11 +74,11 @@ int load(int argc, char* argv[]) { if (!ServerConfig::initServerCrypto(*test_config)) { //printf("[Gradido_LoginServer::%s] error init server crypto\n", __FUNCTION__); - printf("[load] error init server crypto"); + std::clog << "[load] error init server crypto" << std::endl; return -1; } if (!ServerConfig::loadMnemonicWordLists()) { - printf("[load] error in loadMnemonicWordLists"); + std::clog << "[load] error in loadMnemonicWordLists" << std::endl; return -2; } @@ -106,7 +106,7 @@ int load(int argc, char* argv[]) { } catch (Poco::Exception& ex) { // maybe we in docker environment and db needs some time to start up - printf("Poco Exception by connecting to db: %s, let's try again\n", ex.displayText().data()); + std::clog << "Poco Exception by connecting to db: " << ex.displayText() << ", let's try again" << std::endl; } if(!connected) { // let's wait 10 seconds @@ -121,7 +121,7 @@ int load(int argc, char* argv[]) { connected = true; } } catch(Poco::Exception& ex) { - printf("Poco Exception by connecting to db: %s, let's wait another 10 seconds\n", ex.displayText().data()); + std::clog << "Poco Exception by connecting to db: " << ex.displayText() << ", let's wait another 10 seconds" << std::endl; } } if(!connected) { @@ -129,17 +129,17 @@ int load(int argc, char* argv[]) { try { conn->setConnectionsFromConfig(*test_config, CONNECTION_MYSQL_LOGIN_SERVER); } catch(Poco::Exception& ex) { - printf("Poco Exception by connecting to db: %s, exit\n", ex.displayText().data()); + std::clog << "Poco Exception by connecting to db: " << ex.displayText() << ", exit" << std::endl; return -4; } } - printf("measure Time for secret key generation...\n"); + std::clog << "measure Time for secret key generation..." << std::endl; 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::clog << "time for secret key generation: " << timeForArgon2.string() << std::endl; std::string log_Path = "/var/log/grd_login/"; //#ifdef _WIN32 @@ -209,7 +209,7 @@ int load(int argc, char* argv[]) { - printf("init db in : %s\n", timeUsed.string().data()); + std::clog << "init db in : " << timeUsed.string() << std::endl; fillTests(); @@ -226,10 +226,10 @@ int run() std::clog << "[Gradido_LoginServer_Test::run]" << std::endl; for (std::list::iterator it = gTests.begin(); it != gTests.end(); it++) { - //printf("running: %s\n", it->getName()); - printf("running test: %s\n", (*it)->getName()); + std::string name = (*it)->getName(); + std::clog << "running test: " << name << std::endl; try { - if (!(*it)->test()) printf("success\n"); + if (!(*it)->test()) std::clog << "Success" << std::endl; } catch(std::exception& ex) { std::clog << "exception in running test: " << ex.what() << std::endl; } @@ -255,11 +255,12 @@ int main(int argc, char** argv) { try { if (load(argc, argv) < 0) { - printf("early exit\n"); + std::clog << "early exit" << std::endl; return -42; } } catch(std::exception& ex) { - printf("no catched exception while loading: %s\n", ex.what()); + std::string exception_text = ex.what(); + std::clog << "no catched exception while loading: " << exception_text << std::endl; } //printf ("\nStack Limit = %ld and %ld max\n", limit.rlim_cur, limit.rlim_max); From 73b105e71ac2e2116602b02479f5b07c24e93e5e Mon Sep 17 00:00:00 2001 From: einhornimmond Date: Mon, 14 Jun 2021 18:34:20 +0200 Subject: [PATCH 33/85] fix wrong Bit-Operator --- .../src/cpp/test/JSONInterface/TestJsonUpdateUserInfos.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/login_server/src/cpp/test/JSONInterface/TestJsonUpdateUserInfos.cpp b/login_server/src/cpp/test/JSONInterface/TestJsonUpdateUserInfos.cpp index 0dbd01888..80ac047a1 100644 --- a/login_server/src/cpp/test/JSONInterface/TestJsonUpdateUserInfos.cpp +++ b/login_server/src/cpp/test/JSONInterface/TestJsonUpdateUserInfos.cpp @@ -224,7 +224,7 @@ TEST_F(TestJsonUpdateUserInfos, PasswordNotSecureEnough) ASSERT_FALSE(state.isEmpty()); ASSERT_TRUE(state.isString()); - if ((ServerConfig::g_AllowUnsecureFlags | ServerConfig::UNSECURE_ALLOW_ALL_PASSWORDS) == ServerConfig::UNSECURE_ALLOW_ALL_PASSWORDS) { + 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"); From f39b2e2d39efd89a6c48c926ac7307762e85761b Mon Sep 17 00:00:00 2001 From: Moriz Wahl Date: Mon, 14 Jun 2021 21:05:09 +0200 Subject: [PATCH 34/85] Validate Change Username --- frontend/src/apis/loginAPI.js | 5 +- frontend/src/locales/de.json | 6 +- frontend/src/locales/en.json | 6 +- frontend/src/main.js | 17 ++++ .../UserProfile/UserCard_FormUsername.vue | 86 +++++++++++-------- 5 files changed, 80 insertions(+), 40 deletions(-) diff --git a/frontend/src/apis/loginAPI.js b/frontend/src/apis/loginAPI.js index 3453bd296..d9d0b2b9c 100644 --- a/frontend/src/apis/loginAPI.js +++ b/frontend/src/apis/loginAPI.js @@ -15,7 +15,7 @@ const apiGet = async (url) => { if (result.status !== 200) { throw new Error('HTTP Status Error ' + result.status) } - if (result.data.state !== 'success') { + if (!['success', 'warning'].includes(result.data.state)) { throw new Error(result.data.msg) } return { success: true, result } @@ -139,6 +139,9 @@ const loginAPI = { } return apiPost(CONFIG.LOGIN_API_URL + 'updateUserInfos', payload) }, + checkUsername: async (username, groupId = 1) => { + return apiGet(CONFIG.LOGIN_API_URL + `checkUsername?username=${username}&group_id=${groupId}`) + }, } export default loginAPI diff --git a/frontend/src/locales/de.json b/frontend/src/locales/de.json index 6536a143b..8554996bb 100644 --- a/frontend/src/locales/de.json +++ b/frontend/src/locales/de.json @@ -57,9 +57,11 @@ "send_transaction_error":"Leider konnte die Transaktion nicht ausgeführt werden!", "validation": { "gddSendAmount": "Das Feld {_field_} muss eine Zahl zwischen {min} und {max} mit höchstens zwei Nachkommastellen sein", - "is-not": "Du kannst dir selbst keine Gradidos überweisen" + "is-not": "Du kannst dir selbst keine Gradidos überweisen", + "usernmae-unique": "Der Username ist bereits vergeben.", + "usernmae-regex": "Der Username muss mit einem Buchstaben beginnen auf den mindestens zwei alfanumerische Zeichen folgen müssen." }, - "change_username_info": "Das ändern des Usernamens bedarf mehrerer Schritte." + "change_username_info": "Einmal gespeichert, kann der Username ncht mehr geändert werden!" }, "error": { "error":"Fehler" diff --git a/frontend/src/locales/en.json b/frontend/src/locales/en.json index f611e4b91..7b97c2240 100644 --- a/frontend/src/locales/en.json +++ b/frontend/src/locales/en.json @@ -57,9 +57,11 @@ "send_transaction_error":"Unfortunately, the transaction could not be executed!", "validation": { "gddSendAmount": "The {_field_} field must be a number between {min} and {max} with at most two digits", - "is-not": "You cannot send Gradidos to yourself" + "is-not": "You cannot send Gradidos to yourself", + "usernmae-unique": "The username is already taken.", + "usernmae-regex": "The username must start with a letter, followed by at least two alphanumeric characters." }, - "change_username_info": "Changing the username requires several steps." + "change_username_info": "Once saved, the username cannot be changed again!" }, "error": { "error":"Error" diff --git a/frontend/src/main.js b/frontend/src/main.js index 160ff73c7..b5e109fbf 100755 --- a/frontend/src/main.js +++ b/frontend/src/main.js @@ -9,6 +9,8 @@ import { required, email, min, max, is_not } from 'vee-validate/dist/rules' // store import { store } from './store/store' +import loginAPI from './apis/loginAPI' + // router setup import router from './routes/router' @@ -64,6 +66,21 @@ extend('gddSendAmount', { }, }) +extend('gddUsernameUnique', { + async validate(value) { + const result = await loginAPI.checkUsername(value) + return result.result.data.state === 'success' + }, + message: (_, values) => i18n.t('form.validation.usernmae-unique', values), +}) + +extend('gddUsernameRgex', { + validate(value) { + return !!value.match(/^[a-zA-Z][-_a-zA-Z0-9]{2,}$/) + }, + message: (_, values) => i18n.t('form.validation.usernmae-regex', values), +}) + // eslint-disable-next-line camelcase extend('is_not', { // eslint-disable-next-line camelcase diff --git a/frontend/src/views/Pages/UserProfile/UserCard_FormUsername.vue b/frontend/src/views/Pages/UserProfile/UserCard_FormUsername.vue index 2b3b451fe..21fa01aad 100644 --- a/frontend/src/views/Pages/UserProfile/UserCard_FormUsername.vue +++ b/frontend/src/views/Pages/UserProfile/UserCard_FormUsername.vue @@ -1,20 +1,19 @@ @@ -66,7 +73,7 @@ export default { name: 'FormUsername', data() { return { - editUsername: true, + showUsername: true, username: this.$store.state.username, form: { username: this.$store.state.username, @@ -74,6 +81,10 @@ export default { } }, methods: { + cancelEdit() { + this.username = this.$store.state.username + this.showUsername = true + }, async onSubmit() { const result = await loginAPI.changeUsernameProfile( this.$store.state.sessionId, @@ -82,8 +93,9 @@ export default { ) if (result.success) { this.$store.commit('username', this.form.username) - this.editUserdata = this.editUsername = !this.editUsername - alert('Dein Username wurde geändert.') + this.username = this.form.username + this.showUsername = true + this.$toast.success(this.$t('site.profil.user-data.change-success')) } else { alert(result.result.message) } @@ -91,4 +103,8 @@ export default { }, } - + From 2ed08841a1f65cbaaf81a640674437ea3773d353 Mon Sep 17 00:00:00 2001 From: Moriz Wahl Date: Mon, 14 Jun 2021 22:56:57 +0200 Subject: [PATCH 35/85] test user card form username --- .../UserProfile/UserCard_FormUsername.spec.js | 121 ++++++++++++++++++ .../UserProfile/UserCard_FormUsername.vue | 7 +- 2 files changed, 126 insertions(+), 2 deletions(-) create mode 100644 frontend/src/views/Pages/UserProfile/UserCard_FormUsername.spec.js diff --git a/frontend/src/views/Pages/UserProfile/UserCard_FormUsername.spec.js b/frontend/src/views/Pages/UserProfile/UserCard_FormUsername.spec.js new file mode 100644 index 000000000..b1d705952 --- /dev/null +++ b/frontend/src/views/Pages/UserProfile/UserCard_FormUsername.spec.js @@ -0,0 +1,121 @@ +import { mount } from '@vue/test-utils' +import { extend } from 'vee-validate' +import UserCardFormUsername from './UserCard_FormUsername' +import loginAPI from '../../../apis/loginAPI' +import flushPromises from 'flush-promises' + +jest.mock('../../../apis/loginAPI') + +extend('gddUsernameRgex', { + validate(value) { + return true + }, +}) + +extend('gddUsernameUnique', { + validate(value) { + return true + }, +}) + +const localVue = global.localVue + +const mockAPIcall = jest.fn((args) => { + return { success: true } +}) + +loginAPI.changeUsernameProfile = mockAPIcall + +describe('UserCard_FormUsername', () => { + let wrapper + + const mocks = { + $t: jest.fn((t) => t), + $store: { + state: { + sessionId: 1, + email: 'user@example.org', + username: '', + }, + commit: jest.fn(), + }, + $toast: { + success: jest.fn(), + }, + } + + const Wrapper = () => { + return mount(UserCardFormUsername, { localVue, mocks }) + } + + describe('mount', () => { + beforeEach(() => { + wrapper = Wrapper() + }) + + it('renders the component', () => { + expect(wrapper.find('div#formusername').exists()).toBeTruthy() + }) + + describe('username in store is empty', () => { + it('renders an empty username', () => { + expect(wrapper.find('div.display-username').text()).toEqual('@') + }) + + it('has an edit icon', () => { + expect(wrapper.find('svg.bi-pencil').exists()).toBeTruthy() + }) + + describe('edit username', () => { + beforeEach(async () => { + await wrapper.find('svg.bi-pencil').trigger('click') + }) + + it('shows an input field for the username', () => { + expect(wrapper.find('input[placeholder="Username"]').exists()).toBeTruthy() + }) + + it('shows an cancel icon', () => { + expect(wrapper.find('svg.bi-x-circle').exists()).toBeTruthy() + }) + + it('closes the input when cancel icon is clicked', async () => { + wrapper.find('svg.bi-x-circle').trigger('click') + await wrapper.vm.$nextTick() + expect(wrapper.find('input[placeholder="Username"]').exists()).toBeFalsy() + }) + + it('does not change the username when cancel is clicked', async () => { + wrapper.find('input[placeholder="Username"]').setValue('username') + wrapper.find('svg.bi-x-circle').trigger('click') + await wrapper.vm.$nextTick() + expect(wrapper.find('div.display-username').text()).toEqual('@') + }) + + it('has a submit button', () => { + expect(wrapper.find('button[type="submit"]').exists()).toBeTruthy() + }) + + describe('successfull submit', () => { + beforeEach(async () => { + await wrapper.find('input[placeholder="Username"]').setValue('username') + await wrapper.find('form').trigger('submit') + await flushPromises() + }) + + it('calls the loginAPI', () => { + expect(mockAPIcall).toHaveBeenCalledWith(1, 'user@example.org', 'username') + }) + + it('displays the new username', () => { + expect(wrapper.find('div.display-username').text()).toEqual('@username') + }) + + it('has no edit button anymore', () => { + expect(wrapper.find('svg.bi-pencil').exists()).toBeFalsy() + }) + }) + }) + }) + }) +}) diff --git a/frontend/src/views/Pages/UserProfile/UserCard_FormUsername.vue b/frontend/src/views/Pages/UserProfile/UserCard_FormUsername.vue index 21fa01aad..e908fd08c 100644 --- a/frontend/src/views/Pages/UserProfile/UserCard_FormUsername.vue +++ b/frontend/src/views/Pages/UserProfile/UserCard_FormUsername.vue @@ -26,7 +26,7 @@ {{ $t('form.username') }} - @{{ form.username }} + @{{ username }} @@ -97,7 +97,10 @@ export default { this.showUsername = true this.$toast.success(this.$t('site.profil.user-data.change-success')) } else { - alert(result.result.message) + this.$toast.error(result.result.message) + this.showUsername = true + this.username = this.$store.state.username + this.form.username = this.$store.state.username } }, }, From 5a1e90f3f7ea7d423833327fef90e3ffb92c583d Mon Sep 17 00:00:00 2001 From: Moriz Wahl Date: Mon, 14 Jun 2021 23:04:21 +0200 Subject: [PATCH 36/85] coverage frontend to 29& --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index c6e1bbc8e..fedb64676 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -212,7 +212,7 @@ jobs: report_name: Coverage Frontend type: lcov result_path: ./coverage/lcov.info - min_coverage: 28 + min_coverage: 29 token: ${{ github.token }} ############################################################################## From 5d42d64fec67871cb722bfcd5354529863d5df2a Mon Sep 17 00:00:00 2001 From: Moriz Wahl Date: Tue, 15 Jun 2021 01:25:01 +0200 Subject: [PATCH 37/85] test forget password, remove router from test setup --- .../views/Layout/DashboardLayout_gdd.spec.js | 15 ++- .../src/views/Pages/ForgotPassword.spec.js | 114 ++++++++++++++++++ frontend/src/views/Pages/ForgotPassword.vue | 3 +- .../src/views/Pages/ResetPassword.spec.js | 13 +- frontend/test/testSetup.js | 2 - 5 files changed, 132 insertions(+), 15 deletions(-) create mode 100644 frontend/src/views/Pages/ForgotPassword.spec.js diff --git a/frontend/src/views/Layout/DashboardLayout_gdd.spec.js b/frontend/src/views/Layout/DashboardLayout_gdd.spec.js index 62ce94322..9221ff59a 100644 --- a/frontend/src/views/Layout/DashboardLayout_gdd.spec.js +++ b/frontend/src/views/Layout/DashboardLayout_gdd.spec.js @@ -1,16 +1,12 @@ import { mount, RouterLinkStub } from '@vue/test-utils' -import VueRouter from 'vue-router' import Vuex from 'vuex' import flushPromises from 'flush-promises' -import routes from '../../routes/routes' import DashboardLayoutGdd from './DashboardLayout_gdd' jest.useFakeTimers() const localVue = global.localVue -const router = new VueRouter({ routes }) - const transitionStub = () => ({ render(h) { return this.$options._renderChildren @@ -26,6 +22,14 @@ describe('DashboardLayoutGdd', () => { }, $t: jest.fn((t) => t), $n: jest.fn(), + $route: { + meta: { + hideFooter: false, + }, + }, + $router: { + push: jest.fn(), + }, } const state = { @@ -40,6 +44,7 @@ describe('DashboardLayoutGdd', () => { const stubs = { RouterLink: RouterLinkStub, FadeTransition: transitionStub(), + RouterView: transitionStub(), } const store = new Vuex.Store({ @@ -50,7 +55,7 @@ describe('DashboardLayoutGdd', () => { }) const Wrapper = () => { - return mount(DashboardLayoutGdd, { localVue, mocks, router, store, stubs }) + return mount(DashboardLayoutGdd, { localVue, mocks, store, stubs }) } describe('mount', () => { diff --git a/frontend/src/views/Pages/ForgotPassword.spec.js b/frontend/src/views/Pages/ForgotPassword.spec.js new file mode 100644 index 000000000..9e33c785e --- /dev/null +++ b/frontend/src/views/Pages/ForgotPassword.spec.js @@ -0,0 +1,114 @@ +import { mount, RouterLinkStub } from '@vue/test-utils' +import flushPromises from 'flush-promises' +import loginAPI from '../../apis/loginAPI.js' +import ForgotPassword from './ForgotPassword' + +jest.mock('../../apis/loginAPI.js') + +const mockAPIcall = jest.fn() +loginAPI.sendEmail = mockAPIcall + +const localVue = global.localVue + +const mockRouterPush = jest.fn() + +describe('ForgotPassword', () => { + let wrapper + + const mocks = { + $t: jest.fn((t) => t), + $router: { + push: mockRouterPush, + }, + } + + const stubs = { + RouterLink: RouterLinkStub, + } + + const Wrapper = () => { + return mount(ForgotPassword, { localVue, mocks, stubs }) + } + + describe('mount', () => { + beforeEach(() => { + wrapper = Wrapper() + }) + + it('renders the component', () => { + expect(wrapper.find('div.forgot-password').exists()).toBeTruthy() + }) + + it('has a title', () => { + expect(wrapper.find('h1').text()).toEqual('site.password.title') + }) + + it('has a subtitle', () => { + expect(wrapper.find('p.text-lead').text()).toEqual('site.password.subtitle') + }) + + describe('back button', () => { + it('has a "back" button', () => { + expect(wrapper.findComponent(RouterLinkStub).text()).toEqual('back') + }) + + it('links to login', () => { + expect(wrapper.findComponent(RouterLinkStub).props().to).toEqual('/Login') + }) + }) + + describe('input form', () => { + let form + + beforeEach(() => { + form = wrapper.find('form') + }) + + it('has the label "Email"', () => { + expect(form.find('label').text()).toEqual('Email') + }) + + it('has the placeholder "Email"', () => { + expect(form.find('input').attributes('placeholder')).toEqual('Email') + }) + + it('has a submit button', () => { + expect(form.find('button[type="submit"]').exists()).toBeTruthy() + }) + + describe('invalid Email', () => { + beforeEach(async () => { + await form.find('input').setValue('no-email') + await flushPromises() + }) + + it('displays an error', () => { + expect(form.find('#reset-pwd--live-feedback').text()).toEqual( + 'The Email field must be a valid email', + ) + }) + + it('does not call the API', () => { + expect(mockAPIcall).not.toHaveBeenCalled() + }) + }) + + describe('valid Email', () => { + beforeEach(async () => { + await form.find('input').setValue('user@example.org') + form.trigger('submit') + await wrapper.vm.$nextTick() + await flushPromises() + }) + + it('calls the API', () => { + expect(mockAPIcall).toHaveBeenCalledWith('user@example.org') + }) + + it('pushes "/thx/password" to the route', () => { + expect(mockRouterPush).toHaveBeenCalledWith('/thx/password') + }) + }) + }) + }) +}) diff --git a/frontend/src/views/Pages/ForgotPassword.vue b/frontend/src/views/Pages/ForgotPassword.vue index eebba0ced..e407f3590 100644 --- a/frontend/src/views/Pages/ForgotPassword.vue +++ b/frontend/src/views/Pages/ForgotPassword.vue @@ -1,5 +1,5 @@