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