diff --git a/docu/login_server.api.md b/docu/login_server.api.md index b466be7fc..fb9409cf0 100644 --- a/docu/login_server.api.md +++ b/docu/login_server.api.md @@ -89,6 +89,56 @@ In case of success returns: nginx was wrong configured. - `session_id`: can be also negative +## Check username +### Request +`GET http://localhost/login_api/checkUsername?username=&group_id=` + +`POST http://localhost/login_api/checkUsername` +with +```json +{ + "username": "Maxilein", + "group_id": 1, + "group_alias": "gdd1" +} +``` + +group_id or group_alias, one of both is enough. +group_id is better, because one db request less + +### Response + +If username is not already taken +```json +{ + "state":"success" +} +``` + +If username is already taken +```json +{ + "state":"warning", + "msg":"username already in use" +} +``` + +If only group_alias was given and group with that alias was found in db +```json +{ + "state":"success", + "group_id": 1 +} +``` + +If group_id or group_alias unknown +```json +{ + "state":"error", + "msg": "unknown group" +} +``` + ## Create user Register a new User diff --git a/login_server/CMakeLists.txt b/login_server/CMakeLists.txt index 40954b5a7..91f7f1734 100644 --- a/login_server/CMakeLists.txt +++ b/login_server/CMakeLists.txt @@ -170,6 +170,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} @@ -179,7 +180,7 @@ SET(LOCAL_SRCS ${PROTO_GRADIDO} ) 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) @@ -204,6 +205,7 @@ if(MSVC) source_group("Test\\model\\table" FILES ${TEST_MODEL_TABLE}) source_group("Test\\model" FILES ${TEST_MODEL}) source_group("Test\\controller" FILES ${TEST_CONTROLLER}) + source_group("Test\\Json-Interface" FILES ${TEST_JSON_INTERFACE}) source_group("Test" FILES ${TEST}) endif() @@ -341,7 +343,7 @@ target_compile_definitions(Gradido_LoginServer_Test PUBLIC "_TEST_BUILD") target_link_libraries(Gradido_LoginServer_Test ${GRPC_LIBS} ) if(WIN32) - target_link_libraries(Gradido_LoginServer_Test ${CONAN_LIBS} ) + target_link_libraries(Gradido_LoginServer_Test ${CONAN_LIBS} libmariadb libprotobuf) #TARGET_LINK_LIBRARIES(Gradido_LoginServer_Test optimized ${MYSQL_LIBRARIES} Shlwapi) #TARGET_LINK_LIBRARIES(Gradido_LoginServer_Test debug ${COMPILED_MARIADB_CLIENT_DEBUG} Shlwapi) #TARGET_LINK_LIBRARIES(Gradido_LoginServer_Test debug ${GRPC_LIBS} ${PROTOBUF_DEBUG_LIBS}) diff --git a/login_server/conanfile.txt b/login_server/conanfile.txt index 276925116..121e3693b 100644 --- a/login_server/conanfile.txt +++ b/login_server/conanfile.txt @@ -2,6 +2,7 @@ Poco/1.9.4@pocoproject/stable libsodium/1.0.18@bincrafters/stable boost/1.71.0@conan/stable +gtest/1.10.0 [options] Poco:enable_pagecompiler=True diff --git a/login_server/src/cpp/JSONInterface/JsonCheckUsername.cpp b/login_server/src/cpp/JSONInterface/JsonCheckUsername.cpp new file mode 100644 index 000000000..f05f503ee --- /dev/null +++ b/login_server/src/cpp/JSONInterface/JsonCheckUsername.cpp @@ -0,0 +1,92 @@ +#include "JsonCheckUsername.h" +#include "Poco/URI.h" +#include "controller/User.h" +#include "lib/DataTypeConverter.h" + +Poco::JSON::Object* JsonCheckUsername::handle(Poco::Dynamic::Var params) +{ + std::string username; + int group_id = 0; + std::string group_alias; + + // if is json object + if (params.type() == typeid(Poco::JSON::Object::Ptr)) { + Poco::JSON::Object::Ptr paramJsonObject = params.extract(); + /// Throws a RangeException if the value does not fit + /// into the result variable. + /// Throws a NotImplementedException if conversion is + /// not available for the given type. + /// Throws InvalidAccessException if Var is empty. + + auto username_obj = paramJsonObject->get("username"); + auto group_id_obj = paramJsonObject->get("group_id"); + auto group_alias_obj = paramJsonObject->get("group_alias"); + + try { + + if (!username_obj.isEmpty()) { + username_obj.convert(username); + } + + if (!group_id_obj.isEmpty()) { + group_id_obj.convert(group_id); + } + if (!group_alias_obj.isEmpty()) { + group_alias_obj.convert(group_alias); + } + + } + catch (Poco::Exception& ex) { + return stateError("Poco Exception", ex.displayText()); + } + + } + else if (params.isVector()) { + const Poco::URI::QueryParameters queryParams = params.extract(); + for (auto it = queryParams.begin(); it != queryParams.end(); it++) { + if (it->first == "username") { + username = it->second; + } + else if (it->first == "group_id") { + DataTypeConverter::strToInt(it->second, group_id); + } + else if (it->first == "group_alias") { + group_alias = it->second; + } + } + } + else { + return stateError("format not implemented", std::string(params.type().name())); + } + + if (!group_id && group_alias == "") { + return stateError("no group given"); + } + if (!group_id) { + auto groups = controller::Group::load(group_alias); + if (groups.size() > 1) { + return stateError("group is ambiguous"); + } + if (!groups.size()) { + return stateError("unknown group"); + } + group_id = groups[0]->getModel()->getID(); + } + auto group = controller::Group::load(group_id); + if (group.isNull()) { + return stateError("unknown group"); + } + auto user = controller::User::create(); + user->getModel()->setGroupId(group_id); + if (username == "") { + Poco::JSON::Object* result = new Poco::JSON::Object; + result->set("state", "success"); + result->set("group_id", group_id); + return result; + } + if (user->isUsernameAlreadyUsed(username)) { + return stateWarning("username already in use"); + } + return stateSuccess(); + +} \ No newline at end of file diff --git a/login_server/src/cpp/JSONInterface/JsonCheckUsername.h b/login_server/src/cpp/JSONInterface/JsonCheckUsername.h new file mode 100644 index 000000000..71873a6ac --- /dev/null +++ b/login_server/src/cpp/JSONInterface/JsonCheckUsername.h @@ -0,0 +1,16 @@ +#ifndef __JSON_INTERFACE_JSON_CHECK_USERNAME_ +#define __JSON_INTERFACE_JSON_CHECK_USERNAME_ + +#include "JsonRequestHandler.h" + +class JsonCheckUsername : public JsonRequestHandler +{ +public: + Poco::JSON::Object* handle(Poco::Dynamic::Var params); + +protected: + + +}; + +#endif // __JSON_INTERFACE_JSON_CHECK_USERNAME_ \ No newline at end of file diff --git a/login_server/src/cpp/JSONInterface/JsonRequestHandlerFactory.cpp b/login_server/src/cpp/JSONInterface/JsonRequestHandlerFactory.cpp index 4f0e10d6f..3f8536a74 100644 --- a/login_server/src/cpp/JSONInterface/JsonRequestHandlerFactory.cpp +++ b/login_server/src/cpp/JSONInterface/JsonRequestHandlerFactory.cpp @@ -7,6 +7,7 @@ #include "JsonAdminEmailVerificationResend.h" #include "JsonCheckSessionState.h" +#include "JsonCheckUsername.h" #include "JsonAppLogin.h" #include "JsonAquireAccessToken.h" #include "JsonCreateTransaction.h" @@ -74,6 +75,9 @@ Poco::Net::HTTPRequestHandler* JsonRequestHandlerFactory::createRequestHandler(c else if (url_first_part == "/checkSessionState") { return new JsonCheckSessionState; } + else if (url_first_part == "/checkUsername") { + return new JsonCheckUsername; + } else if (url_first_part == "/createTransaction") { return new JsonCreateTransaction; } diff --git a/login_server/src/cpp/controller/User.cpp b/login_server/src/cpp/controller/User.cpp index cd71da96b..5c26e3a12 100644 --- a/login_server/src/cpp/controller/User.cpp +++ b/login_server/src/cpp/controller/User.cpp @@ -108,7 +108,9 @@ namespace controller { bool User::isUsernameAlreadyUsed(const std::string& username) { auto db = getModel(); - return db->loadFromDB({ "username", "group_id" }, username, db->getGroupId(), model::table::MYSQL_CONDITION_AND) > 0; + auto results = db->loadMultipleFromDB({ "username", "group_id" }, username, db->getGroupId(), model::table::MYSQL_CONDITION_AND); + return results.size() > 0; + } int User::load(const unsigned char* pubkey_array) diff --git a/login_server/src/cpp/model/table/ModelBase.h b/login_server/src/cpp/model/table/ModelBase.h index 15f08f66c..c769dcfd9 100644 --- a/login_server/src/cpp/model/table/ModelBase.h +++ b/login_server/src/cpp/model/table/ModelBase.h @@ -61,6 +61,12 @@ namespace model { template size_t loadFromDB(const std::vector& fieldNames, const T1& field1Value, const T2& field2Value, MysqlConditionType conditionType = MYSQL_CONDITION_AND); + template + std::vector loadMultipleFromDB( + const std::vector& fieldNames, + const T1& field1Value, const T2& field2Value, + MysqlConditionType conditionType = MYSQL_CONDITION_AND); + template std::vector loadMultipleFromDB( const std::vector& fieldNames, @@ -290,6 +296,43 @@ namespace model { return resultCount; } + template + std::vector ModelBase::loadMultipleFromDB( + const std::vector& fieldNames, + const T1& field1Value, const T2& field2Value, + MysqlConditionType conditionType/* = MYSQL_CONDITION_AND*/) + { + auto cm = ConnectionManager::getInstance(); + std::vector results; + if (fieldNames.size() != 2) { + addError(new Error(getTableName(), "error in loadFromDB with 2 different field values, fieldNames count isn't 2")); + return results; + } + Poco::ScopedLock _lock(mWorkMutex); + + auto session = cm->getConnection(CONNECTION_MYSQL_LOGIN_SERVER); + Poco::Data::Statement select = _loadMultipleFromDB(session, fieldNames, conditionType); + select, Poco::Data::Keywords::into(results), + Poco::Data::Keywords::useRef(field1Value), Poco::Data::Keywords::useRef(field2Value); + + size_t resultCount = 0; + try { + resultCount = select.execute(); + } + catch (Poco::Exception& ex) { + lock(); + addError(new ParamError(getTableName(), "mysql error by selecting with 2 different field types", ex.displayText())); + int count = 0; + for (auto it = fieldNames.begin(); it != fieldNames.end(); it++) { + addError(new ParamError(getTableName(), "field name for select: ", *it)); + } + + //addError(new ParamError(getTableName(), "field name for select: ", fieldName.data())); + unlock(); + } + return results; + } + template std::vector ModelBase::loadMultipleFromDB( const std::vector& fieldNames, diff --git a/login_server/src/cpp/test/JSONInterface/TestJsonCheckUsername.cpp b/login_server/src/cpp/test/JSONInterface/TestJsonCheckUsername.cpp new file mode 100644 index 000000000..d399689ee --- /dev/null +++ b/login_server/src/cpp/test/JSONInterface/TestJsonCheckUsername.cpp @@ -0,0 +1,123 @@ +#include "gtest/gtest.h" + +#include "JSONInterface/JsonCheckUsername.h" + +TEST(TestJsonCheckUsername, InvalidGroupAlias) +{ + JsonCheckUsername jsonCall; + Poco::JSON::Object::Ptr params = new Poco::JSON::Object; + params->set("group_alias", "robert"); + auto result = jsonCall.handle(params); + auto msg = result->get("msg"); + ASSERT_FALSE(msg.isEmpty()); + ASSERT_TRUE(msg.isString()); + ASSERT_EQ(msg.toString(), "unknown group"); + + delete result; +} + +TEST(TestJsonCheckUsername, InvalidGroupId) +{ + JsonCheckUsername jsonCall; + Poco::JSON::Object::Ptr params = new Poco::JSON::Object; + params->set("group_id", "4"); + auto result = jsonCall.handle(params); + auto msg = result->get("msg"); + ASSERT_FALSE(msg.isEmpty()); + ASSERT_TRUE(msg.isString()); + ASSERT_EQ(msg.toString(), "unknown group"); + + delete result; +} + +TEST(TestJsonCheckUsername, ValidGroupAlias) +{ + JsonCheckUsername jsonCall; + Poco::JSON::Object::Ptr params = new Poco::JSON::Object; + params->set("group_alias", "gdd1"); + auto result = jsonCall.handle(params); + auto state = result->get("state"); + ASSERT_FALSE(state.isEmpty()); + ASSERT_TRUE(state.isString()); + ASSERT_EQ(state.toString(), "success"); + + auto group_id = result->get("group_id"); + ASSERT_FALSE(group_id.isEmpty()); + ASSERT_TRUE(group_id.isInteger()); + int group_id_int = 0; + group_id.convert(group_id_int); + ASSERT_EQ(group_id_int, 1); + + delete result; +} + +TEST(TestJsonCheckUsername, UsernameWithoutGroup) +{ + JsonCheckUsername jsonCall; + Poco::JSON::Object::Ptr params = new Poco::JSON::Object; + params->set("username", "maxi"); + auto result = jsonCall.handle(params); + + auto state = result->get("state"); + ASSERT_FALSE(state.isEmpty()); + ASSERT_TRUE(state.isString()); + ASSERT_EQ(state.toString(), "error"); + + auto msg = result->get("msg"); + ASSERT_FALSE(msg.isEmpty()); + ASSERT_TRUE(msg.isString()); + ASSERT_EQ(msg.toString(), "no group given"); + + + delete result; +} + +TEST(TestJsonCheckUsername, ExistingUsername) +{ + JsonCheckUsername jsonCall; + Poco::JSON::Object::Ptr params = new Poco::JSON::Object; + params->set("username", "Erfinder"); + params->set("group_id", 1); + auto result = jsonCall.handle(params); + + auto state = result->get("state"); + ASSERT_FALSE(state.isEmpty()); + ASSERT_TRUE(state.isString()); + ASSERT_EQ(state.toString(), "warning"); + + auto msg = result->get("msg"); + ASSERT_FALSE(msg.isEmpty()); + ASSERT_TRUE(msg.isString()); + ASSERT_EQ(msg.toString(), "username already in use"); +} + +TEST(TestJsonCheckUsername, NewUsername) +{ + JsonCheckUsername jsonCall; + Poco::JSON::Object::Ptr params = new Poco::JSON::Object; + params->set("username", "Maxi"); + params->set("group_id", 1); + auto result = jsonCall.handle(params); + + auto state = result->get("state"); + ASSERT_FALSE(state.isEmpty()); + ASSERT_TRUE(state.isString()); + ASSERT_EQ(state.toString(), "success"); +} + +TEST(TestJsonCheckUsername, UsernameExistInOtherGroup) +{ + JsonCheckUsername jsonCall; + Poco::JSON::Object::Ptr params = new Poco::JSON::Object; + params->set("username", "Erfinder"); + params->set("group_id", 2); + auto result = jsonCall.handle(params); + + auto state = result->get("state"); + ASSERT_FALSE(state.isEmpty()); + ASSERT_TRUE(state.isString()); + ASSERT_EQ(state.toString(), "success"); + +} + + diff --git a/login_server/src/cpp/test/main.cpp b/login_server/src/cpp/test/main.cpp index 933d555d3..d4773bd9f 100644 --- a/login_server/src/cpp/test/main.cpp +++ b/login_server/src/cpp/test/main.cpp @@ -152,46 +152,38 @@ int load(int argc, char* argv[]) { Profiler timeUsed; // clean up and fill db - std::string tables[] = { - "hedera_accounts", - "hedera_ids", - "crypto_keys", - "hedera_topics", + std::string tables[] = { "groups", - "node_servers", "users" }; - for (int i = 0; i < 7; i++) { + for (int i = 0; i < 2; i++) { runMysql("TRUNCATE " + tables[i]); runMysql("ALTER TABLE " + tables[i] + " AUTO_INCREMENT = 1"); } std::stringstream ss; - ss << "INSERT INTO `hedera_ids` (`id`, `shardNum`, `realmNum`, `num`) VALUES " - << "(1, 0, 0, 37281), " - << "(2, 0, 0, 21212), " - << "(3, 0, 0, 212);"; + 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()); ss.str(std::string()); - ss << "INSERT INTO `crypto_keys` (`id`, `private_key`, `public_key`, `crypto_key_type_id`) VALUES " - << "(1, 0x263f1da712c3b47286b463c2de3784f364f2534d2c34722a3b483c3f3e36476857564f564d476c32d3e342f5ef2763cd23e23a2b429bab62e352f46ba273e2f2, 0xfe5237c2d1ab1361b33163f15634e261c1d217ae32b327cbd88db8ebffedb271, 3), " - << "(2, 0x721f3e73e3263f1da712c3b47286b463c2de3784f364f2534d2c34722a3b483c3f3e36476857564f564d476c32d3e342f5ef2763cd23e23a2b429bab62e352f46ba273e2f2ef3264fe2452da62bc2739, 0xe3f253d1a2deb25362d2e374baf37bc1d3ef3781cfe1e127f3cd0abcdf372ea6, 1); "; + 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()); ss.str(std::string()); - ss << "INSERT INTO `hedera_accounts` (`id`, `user_id`, `account_hedera_id`, `account_key_id`, `balance`, `network_type`, `updated`) VALUES " - << "(1, 1, 1, 1, 1000000000000, 1, '2019-09-03 11:13:52'), " - << "(2, 1, 2, 2, 4312881211, 0, '2019-09-03 11:13:56'); "; - runMysql(ss.str()); - ss.str(std::string()); - - ss << "INSERT INTO `hedera_topics` (`id`, `topic_hedera_id`, `name`, `auto_renew_account_hedera_id`, `auto_renew_period`, `group_id`, `admin_key_id`, `submit_key_id`, `current_timeout`, `sequence_number`, `updated`) VALUES " - << "(1, 3, 'gdd_test_topic', 1, 0, 1, NULL, NULL, '1999-12-31 23:00:00', 0, '2020-09-14 18:29:04'); "; - runMysql(ss.str()); - ss.str(std::string()); - std::clog << "after inserting everything in db" << std::endl; + + printf("init db in : %s\n", timeUsed.string().data()); + + fillTests(); for (std::list::iterator it = gTests.begin(); it != gTests.end(); it++) {