diff --git a/src/server/Authentication.cpp b/src/server/Authentication.cpp new file mode 100644 index 000000000..92ba3f6e4 --- /dev/null +++ b/src/server/Authentication.cpp @@ -0,0 +1,17 @@ +#include "Authentication.h" +#include "Database.h" +#include + +Authentication::Authentication(): db(nullptr) +{ + db = Database::getInstance(); +} + +bool Authentication::loginAuth(const Login& data) +{ + + if(db->verifyUser(data._name, data._password)) + return true; + + return false; +} diff --git a/src/server/Authentication.h b/src/server/Authentication.h new file mode 100644 index 000000000..6a9aa7356 --- /dev/null +++ b/src/server/Authentication.h @@ -0,0 +1,14 @@ +#include "Messages.h" + +class Database; + +class Authentication +{ + public: + Authentication(); + bool loginAuth(const Login& data); + + + private: + Database* db; +}; diff --git a/src/server/CMakeLists.txt b/src/server/CMakeLists.txt index 98a994e94..f3a5b433d 100644 --- a/src/server/CMakeLists.txt +++ b/src/server/CMakeLists.txt @@ -1,279 +1,281 @@ include(qt_helper) set(GCOMPRIS_SERVER_EXECUTABLE_NAME gcompris-server) # Note: put .cpp files before .h files in the following or cmake messes up # moc generation -- WTF! set(server_SRCS MessageHandler.cpp MessageHandler.h ActivityData.cpp ActivityData.h ClientData.cpp ClientData.h Database.cpp Database.h GroupData.cpp GroupData.h UserData.cpp UserData.h Server.cpp Server.h + Authentication.cpp + Authentication.h ) qt5_wrap_cpp(server_MOC ${server_SRCS}) # Add files that do no need to pass through the moc processor set(server_SRCS ${server_SRCS} main.cpp ) include_directories( ../core/ # messages "${CMAKE_CURRENT_BINARY_DIR}/../core" # to find config.h ) set(used_qt_modules Qml Quick Gui Multimedia Core Svg Xml XmlPatterns Sensors Sql) if(ANDROID) add_library(${GCOMPRIS_SERVER_EXECUTABLE_NAME} SHARED ${server_SRCS} ${server_MOC}) set(used_qt_modules ${used_qt_modules} AndroidExtras) elseif(CMAKE_HOST_APPLE) add_executable(${GCOMPRIS_SERVER_EXECUTABLE_NAME} MACOSX_BUNDLE ${server_SRCS} ${server_MOC} ${gcompris_RES}) elseif(CMAKE_HOST_WIN32) add_executable(${GCOMPRIS_SERVER_EXECUTABLE_NAME} WIN32 ${server_SRCS} ${server_MOC} ${gcompris_RES}) else() add_executable(${GCOMPRIS_SERVER_EXECUTABLE_NAME} ${server_SRCS} ${server_MOC} ${gcompris_RES}) endif() qt5_use_modules(${GCOMPRIS_SERVER_EXECUTABLE_NAME} ${used_qt_modules}) target_link_libraries(${GCOMPRIS_SERVER_EXECUTABLE_NAME} gcompris_core) GCOMPRIS_ADD_RCC(server main.qml views/*.qml views/*/*/*.qml) # Installation # ============ install(TARGETS ${GCOMPRIS_SERVER_EXECUTABLE_NAME} ARCHIVE DESTINATION bin RUNTIME DESTINATION bin LIBRARY DESTINATION lib BUNDLE DESTINATION .) if(BUILD_STANDALONE) # Qt plugins to install set(_qt_plugins "") if(NOT SAILFISHOS) list(APPEND _qt_plugins Qt5::QJpegPlugin) endif() if(APPLE) list(APPEND _qt_plugins Qt5::QTgaPlugin Qt5::QTiffPlugin) elseif(WIN32) list(APPEND _qt_plugins Qt5::QWindowsIntegrationPlugin Qt5::QWindowsAudioPlugin Qt5::AudioCaptureServicePlugin Qt5::DSServicePlugin) elseif(UNIX AND NOT ANDROID AND NOT SAILFISHOS) list(APPEND _qt_plugins Qt5::QXcbIntegrationPlugin) endif() list(APPEND _qt_plugins Qt5::genericSensorPlugin Qt5::QtSensorGesturePlugin Qt5::QShakeSensorGesturePlugin) # Qml plugins to install if(WIN32) set(_lib_prefix "") else() set(_lib_prefix "lib") endif() set(_qt_plugins2 imageformats/${_lib_prefix}qsvg) if(UNIX AND NOT ANDROID AND NOT APPLE AND NOT SAILFISHOS) list(APPEND _qt_plugins2 mediaservice/${_lib_prefix}gstaudiodecoder mediaservice/${_lib_prefix}gstcamerabin mediaservice/${_lib_prefix}gstmediacapture mediaservice/${_lib_prefix}gstmediaplayer) elseif(APPLE) list(APPEND _qt_plugins2 mediaservice/${_lib_prefix}qavfmediaplayer mediaservice/${_lib_prefix}qtmedia_audioengine mediaservice/${_lib_prefix}qavfcamera) endif() set(_qml_plugins QtQuick/Window.2/${_lib_prefix}windowplugin QtQuick/Particles.2/${_lib_prefix}particlesplugin QtQuick.2/${_lib_prefix}qtquick2plugin QtMultimedia/${_lib_prefix}declarative_multimedia QtSensors/${_lib_prefix}declarative_sensors) if(NOT SAILFISHOS) list(APPEND _qml_plugins QtQuick/Controls/${_lib_prefix}qtquickcontrolsplugin QtQuick/Layouts/${_lib_prefix}qquicklayoutsplugin) endif() if(NOT WIN32 AND NOT SAILFISHOS) list(APPEND _qml_plugins QtAudioEngine/${_lib_prefix}declarative_audioengine) if (NOT "${QML_BOX2D_MODULE}" STREQUAL "submodule") list(APPEND _qml_plugins Box2D.2.0/${_lib_prefix}Box2D) endif() endif() set(GCOMPRIS_OTHER_LIBS) if(APPLE) set(_app gcompris-qt.app) set(_qtconf_destdir ${_app}/Contents/Resources) set(_qt_plugins_destdir ${_app}/Contents/plugins) set(_qt_qml_destdir ${_app}/Contents/qml) set(GCOMPRIS_BUNDLE "\${CMAKE_INSTALL_PREFIX}/${_app}") set_target_properties(gcompris-qt PROPERTIES MACOSX_BUNDLE_INFO_STRING "GCompris, Educational game for children 2 to 10" MACOSX_BUNDLE_ICON_FILE "${gcompris_icon}" MACOSX_BUNDLE_GUI_IDENTIFIER "net.gcompris" MACOSX_BUNDLE_LONG_VERSION_STRING "${GCOMPRIS_MAJOR_VERSION}.${GCOMPRIS_MINOR_VERSION}.${GCOMPRIS_PATCH_VERSION}" MACOSX_BUNDLE_BUNDLE_NAME "gcompris-qt" MACOSX_BUNDLE_SHORT_VERSION_STRING "${GCOMPRIS_VERSION}" MACOSX_BUNDLE_BUNDLE_VERSION "${GCOMPRIS_VERSION}" MACOSX_BUNDLE_COPYRIGHT "GPL License, Copyright 2000-2016 Bruno Coudoin and Others.") set_source_files_properties(${GCOMPRIS_RESOURCES}/${gcompris_icon} PROPERTIES MACOSX_PACKAGE_LOCATION "Resources") else() set(_qtconf_destdir bin) set(_qt_plugins_destdir bin/plugins) set(_qt_qml_destdir bin/qml) if(CMAKE_HOST_WIN32) set(GCOMPRIS_BUNDLE "\${CMAKE_INSTALL_PREFIX}/bin/${GCOMPRIS_SERVER_EXECUTABLE_NAME}.exe") else() set(GCOMPRIS_BUNDLE "\${CMAKE_INSTALL_PREFIX}/bin/${GCOMPRIS_SERVER_EXECUTABLE_NAME}") endif() endif() # install qt.conf file install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/qt.conf DESTINATION ${_qtconf_destdir}) # install qt plugins foreach(_plugin ${_qt_plugins}) installQtPlugin(${_plugin} ${_qt_plugins_destdir} _lib) list(APPEND GCOMPRIS_OTHER_LIBS ${_lib}) endforeach() foreach(_plugin ${_qt_plugins2}) installQtPlugin2(${_plugin} ${_qt_plugins_destdir} _lib) list(APPEND GCOMPRIS_OTHER_LIBS ${_lib}) endforeach() # install qml plugins foreach(_plugin ${_qml_plugins}) installQmlPlugin(${_plugin} ${_qt_qml_destdir} _lib) list(APPEND GCOMPRIS_OTHER_LIBS ${_lib}) endforeach() # install QtGraphicalEffects (which is not a lib but only qml files) set(_qml_subdir QtGraphicalEffects) getQtQmlPath(_qt_qml_path) install(DIRECTORY ${_qt_qml_path}/QtGraphicalEffects DESTINATION ${_qt_qml_destdir}) # Fix for Linux 'make package' that fails to link with libicu if(UNIX AND NOT APPLE AND NOT SAILFISHOS AND NOT ANDROID) add_library( libicudata SHARED IMPORTED ) FILE(GLOB LIBICUDATA_SO "${Qt5_DIR}/../../libicudata.so.[0-9][0-9]") if ("${LIBICUDATA_SO}" STREQUAL "") FILE(GLOB LIBICUDATA_SO "/usr/lib/*/libicudata.so.[0-9][0-9]") endif() set_target_properties( libicudata PROPERTIES IMPORTED_LOCATION ${LIBICUDATA_SO} ) add_library( libicui18n SHARED IMPORTED ) FILE(GLOB LIBICUI18N_SO "${Qt5_DIR}/../../libicui18n.so.[0-9][0-9]") if ("${LIBICUI18N_SO}" STREQUAL "") FILE(GLOB LIBICUI18N_SO "/usr/lib/*/libicui18n.so.[0-9][0-9]") endif() set_target_properties( libicui18n PROPERTIES IMPORTED_LOCATION ${LIBICUI18N_SO} ) add_library( libicuuc SHARED IMPORTED ) FILE(GLOB LIBICUUC_SO "${Qt5_DIR}/../../libicuuc.so.[0-9][0-9]") if ("${LIBICUUC_SO}" STREQUAL "") FILE(GLOB LIBICUUC_SO "/usr/lib/*/libicuuc.so.[0-9][0-9]") endif() set_target_properties( libicuuc PROPERTIES IMPORTED_LOCATION ${LIBICUUC_SO} ) TARGET_LINK_LIBRARIES(${GCOMPRIS_SERVER_EXECUTABLE_NAME} libicudata libicui18n libicuuc) endif() endif(BUILD_STANDALONE) # Hack: do not fixup Qt and Qml plugins on Windows because fixup_bundle takes ages (cmake bug ?) -> Johnny : we need this even if it takes time because some required dependencies are pulled here #if(WIN32) set(GCOMPRIS_OTHER_LIBS "") endif() # install fixup_bundle script to resolve and fixup runtime dependencies if(BUILD_STANDALONE) configure_file(${CMAKE_SOURCE_DIR}/cmake/FixBundle.cmake.in FixBundle.cmake) install(SCRIPT ${CMAKE_CURRENT_BINARY_DIR}/FixBundle.cmake) # install a startup script for linux bundle if(UNIX AND NOT APPLE AND NOT SAILFISHOS) install(PROGRAMS ../../tools/gcompris-qt.sh DESTINATION bin) endif() endif() if(WIN32) set(VCREDIST_DIR "" CACHE PATH "Path to vcredist dlls") # install libEGL.dll, libGLESv2.dll, d3dcompiler_47.dll from Qt installation. Not sure if there is a clean way to get them... # Qt5_Dir is like C:/Qt/Qt5.5.1/5.5/mingw492_32/lib/cmake/Qt5 install(FILES ${Qt5_DIR}/../../../bin/icudt54.dll DESTINATION bin) install(FILES ${Qt5_DIR}/../../../bin/icuin54.dll DESTINATION bin) install(FILES ${Qt5_DIR}/../../../bin/icuuc54.dll DESTINATION bin) install(FILES ${Qt5_DIR}/../../../bin/libEGL.dll DESTINATION bin) install(FILES ${Qt5_DIR}/../../../bin/libGLESv2.dll DESTINATION bin) install(FILES ${Qt5_DIR}/../../../../../Tools/QtCreator/bin/d3dcompiler_47.dll DESTINATION bin) install(FILES ${VCREDIST_DIR}/msvcp140.dll DESTINATION bin) install(FILES ${VCREDIST_DIR}/vcruntime140.dll DESTINATION bin) endif() # Packaging # ========= set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "GCompris is a high quality educational software suite comprising of numerous activities for children aged 2 to 10.") set(CPACK_PACKAGE_VERSION_MAJOR ${GCOMPRIS_MAJOR_VERSION}) set(CPACK_PACKAGE_VERSION_MINOR ${GCOMPRIS_MINOR_VERSION}) set(CPACK_PACKAGE_VERSION_PATCH ${GCOMPRIS_PATCH_VERSION}) set(CPACK_PACKAGE_VERSION ${GCOMPRIS_VERSION}) set(CPACK_RESOURCE_FILE_LICENSE "${PROJECT_SOURCE_DIR}/COPYING") set(CPACK_RESOURCE_FILE_README "${PROJECT_SOURCE_DIR}/README") if(WIN32) set(CPACK_PACKAGE_INSTALL_DIRECTORY "${GCOMPRIS_SERVER_EXECUTABLE_NAME}-Qt") set(CPACK_PACKAGE_INSTALL_REGISTRY_KEY "${GCOMPRIS_SERVER_EXECUTABLE_NAME}-Qt") set(CPACK_PACKAGE_ICON "${PROJECT_SOURCE_DIR}/installer\\\\gcompris-header.bmp") set(CPACK_NSIS_MUI_ICON "${PROJECT_SOURCE_DIR}/installer\\\\GCompris-install.ico") set(CPACK_NSIS_MUI_UNIICON "${PROJECT_SOURCE_DIR}/installer\\\\GCompris-uninstall.ico") set(CPACK_NSIS_EXECUTABLES_DIRECTORY "bin") set(CPACK_PACKAGE_EXECUTABLES "${GCOMPRIS_SERVER_EXECUTABLE_NAME};GCompris") set(CPACK_CREATE_DESKTOP_LINKS "${GCOMPRIS_SERVER_EXECUTABLE_NAME};GCompris") set(CPACK_NSIS_URL_INFO_ABOUT "http:\\\\\\\\gcompris.net") set(CPACK_NSIS_DISPLAY_NAME "GCompris Educational Software") set(CPACK_NSIS_MUI_FINISHPAGE_RUN "${GCOMPRIS_SERVER_EXECUTABLE_NAME}") else(WIN32) set(CPACK_PACKAGE_ICON "${PROJECT_SOURCE_DIR}/GCompris64.png") endif() if(APPLE) set(CPACK_GENERATOR "DragNDrop") set(CPACK_DMG_DS_STORE "${GCOMPRIS_RESOURCES}/dmg_DS_Store") set(CPACK_DMG_BACKGROUND_IMAGE "${GCOMPRIS_RESOURCES}/dmg_background.png") elseif(WIN32) set(CPACK_GENERATOR "NSIS") elseif(SAILFISHOS) configure_file(${PROJECT_SOURCE_DIR}/platforms/sailfishOS/harbour-gcompris-qt.spec.cmake ${CMAKE_BINARY_DIR}/harbour-gcompris-qt.spec @ONLY) install(FILES ${PROJECT_SOURCE_DIR}/platforms/sailfishOS/harbour-gcompris-qt.desktop DESTINATION share/applications) install(FILES ${PROJECT_SOURCE_DIR}/platforms/sailfishOS/harbour-gcompris-qt.png DESTINATION share/icons/hicolor/86x86/apps) set(CPACK_RPM_PACKAGE_SUMMARY "gcompris-qt") # BUILD_ARCH is either armv7hl or i486 set(CPACK_RPM_PACKAGE_ARCHITECTURE "${BUILD_ARCH}") set(CPACK_RPM_PACKAGE_NAME "${GCOMPRIS_SERVER_EXECUTABLE_NAME}") set(CPACK_RPM_PACKAGE_VERSION "${GCOMPRIS_VERSION}") set(CPACK_RPM_PACKAGE_LICENSED "GPLv3") set(CPACK_RPM_PACKAGE_URL "https://www.gcompris.org") set(CPACK_RPM_PACKAGE_DESCRIPTION "GCompris is a high quality educational software suite comprising of numerous activities for children aged 2 to 10.") set(CPACK_RPM_USER_BINARY_SPECFILE "${CMAKE_BINARY_DIR}/harbour-gcompris-qt.spec") set(CMAKE_INSTALL_PREFIX "/usr") set(CPACK_PACKAGING_INSTALL_PREFIX "/usr") set(CPACK_GENERATOR "RPM") else() set(CPACK_GENERATOR "STGZ") endif() include(CPack) diff --git a/src/server/Database.cpp b/src/server/Database.cpp index 2cbb3d7a6..5ed645ac4 100644 --- a/src/server/Database.cpp +++ b/src/server/Database.cpp @@ -1,387 +1,416 @@ /* GCompris - Database.cpp * * Copyright (C) 2016 Johnny Jazeix * * Authors: * Johnny Jazeix * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . */ #include #include #include #include #include #include #include #include #include #include #include #include "UserData.h" #include "Database.h" #include "GroupData.h" #include "Messages.h" #include "DataStreamConverter.h" #define CREATE_TABLE_USERS \ "CREATE TABLE users (user_name TEXT PRIMARY KEY NOT NULL, dateOfBirth TEXT, password TEXT); " #define CREATE_TABLE_GROUPS \ "CREATE TABLE groups (group_name TEXT PRIMARY KEY NOT NULL, description TEXT); " #define CREATE_TABLE_USERGROUP \ "CREATE TABLE group_users(user_name TEXT NOT NULL, group_name TEXT NOT NULL)" #define CREATE_TABLE_ACTIVITY_DATA \ "CREATE TABLE activity_data(user_name TEXT NOT NULL, activity_name TEXT NOT NULL, " \ "date TEXT NOT NULL,data TEXT NOT NULL,PRIMARY KEY(user_name,activity_name,date))" Database* Database::_instance = nullptr; Database::Database() { } Database::~Database() { } Database* Database::getInstance() { if (!_instance) _instance = new Database; return _instance; } bool Database::addGroup(const QString &groupName, const QString& description, const QStringList& users) { bool groupAdded = false; QSqlDatabase dbConnection = QSqlDatabase::database(); QSqlQuery query(dbConnection); // add group to db only if it has not been added before query.prepare("SELECT group_name FROM groups WHERE group_name=:groupName"); query.bindValue(":groupName", groupName); query.exec(); if(query.next()) { qDebug()<< "group "<< groupName << " already exists"; return false; } // since the group does not exist ,create the new group and add description and users to it query.prepare("INSERT INTO groups (group_name, description) VALUES (:groupName,:description)"); query.bindValue(":groupName", groupName); query.bindValue(":description",description); groupAdded = query.exec(); if(groupAdded) { //add users to the group for(const auto &user: users) { addUserToGroup(groupName, user); } } else qDebug()<<"group could not be added " << query.lastError(); return groupAdded; } bool Database::deleteGroup(const QString &groupName) { bool groupDeleted = false; QSqlDatabase dbConnection = QSqlDatabase::database(); QSqlQuery query(dbConnection); query.prepare("DELETE FROM groups WHERE group_name=:gname"); query.bindValue(":gname", groupName); if(query.exec()) { query.prepare("DELETE FROM group_users WHERE group_name=:gname"); query.bindValue(":gname", groupName); if(query.exec()) groupDeleted = true; } return groupDeleted; } bool Database::addUserToGroup(const QString& group, const QString& user) { // insert in table group_users // add (user, group) to db only if they don't exist bool userAdded = false; QSqlDatabase dbConnection = QSqlDatabase::database(); QSqlQuery query(dbConnection); query.prepare("SELECT * FROM group_users WHERE user_name=:user and group_name=:group"); query.bindValue(":user",user); query.bindValue(":group",group); query.exec(); if(query.next()) { qDebug() << "user " << user << "already exists in group " << group; return false; } query.prepare("INSERT INTO group_users (user_name, group_name) values(:user,:group)"); query.bindValue(":user",user); query.bindValue(":group",group); userAdded = query.exec(); if(!userAdded) { qDebug() << "user could not be added "<< query.lastError(); } return userAdded; } bool Database::addDataToDatabase(const ActivityRawData &rawData) { bool dataAdded = false; QByteArray data; QDataStream out(&data, QIODevice::WriteOnly); out << ACTIVITY_DATA << rawData.data; QString activityData = QTextCodec::codecForMib(106)->toUnicode(data); QSqlDatabase dbConnection = QSqlDatabase::database(); QSqlQuery query(dbConnection); // get the data from database and then prepend/append new data query.prepare("SELECT * FROM activity_data where user_name=:userName and activity_name=:activityName and date=:date"); query.bindValue(":userName",rawData.username); query.bindValue(":activityName",rawData.activityName); query.bindValue(":date", rawData.date.toString("dd/MM/yyyy")); query.exec(); if(query.next()) { // update the data QString oldData; const int dataIndex = query.record().indexOf("data"); oldData = query.value(dataIndex).toString(); oldData.prepend(activityData); query.prepare("UPDATE activity_data SET data = :oldData where user_name=:userName and activity_name=:activityName " "and date=:date"); query.bindValue(":oldData",oldData); query.bindValue(":userName",rawData.username); query.bindValue(":activityName",rawData.activityName); query.bindValue(":date",rawData.date.toString("dd/MM/yyyy")); if(!query.exec()) { qDebug() << "could not update the data " << query.lastError(); } else dataAdded = true; } else { //insert the data query.prepare("INSERT INTO activity_data (user_name, activity_name, date, data) " "VALUES(:userName, :activityName, :date, :data)"); query.bindValue(":userName", rawData.username); query.bindValue(":activityName", rawData.activityName); query.bindValue(":date",rawData.date.toString("dd/MM/yyyy")); query.bindValue(":data", activityData); if(!query.exec()) qDebug() << "could not insert the data " << query.lastError(); else dataAdded = true; } return dataAdded; } bool Database::addUser(const UserData& user) { // check whether user already exists before adding to database bool userAdded = false; QSqlDatabase dbConnection = QSqlDatabase::database(); QSqlQuery query(dbConnection); query.prepare("SELECT user_name FROM users WHERE user_name=:name"); query.bindValue(":name", user.getName()); query.exec(); if(query.next()) { qDebug() << "user " << user.getName() << "already exists"; return false; } query.prepare("INSERT INTO users (user_name, dateOfBirth, password) VALUES(:name, :dateOfBirth, :password)"); query.bindValue(":name", user.getName()); query.bindValue(":dateOfBirth", user.getDateOfBirth()); query.bindValue(":password", user.getPassword()); userAdded = query.exec(); if(!userAdded) { qDebug()<< query.lastError(); } return userAdded; } bool Database::deleteUser(const QString& name) { bool userDeleted = false; QSqlDatabase dbConnection = QSqlDatabase::database(); QSqlQuery query(dbConnection); query.prepare("DELETE FROM users WHERE user_name=:name"); query.bindValue(":name", name); if(query.exec()) { query.prepare("DELETE FROM group_users WHERE user_name=:name"); query.bindValue(":name",name); if(query.exec()) { query.prepare("DELETE FROM activity_data WHERE user_name=:name"); query.bindValue(":name",name); if(query.exec()) { userDeleted = true; } else { qDebug() << query.executedQuery() << " failed"; } } else { qDebug() << query.executedQuery() << " failed"; } } else { qDebug() << query.executedQuery() << " failed"; } return userDeleted; } +bool Database::verifyUser(const QString& userName, const QString& password) +{ + QSqlDatabase dbConnection = QSqlDatabase::database(); + QSqlQuery query(dbConnection); + + +// the following query does not work, not sure why +// TODO: make this query work +// query.prepare("SELECT user_name, password FROM users WHERE user_name=:userName and password=:password"); +// query.bindValue(":name", userName); +// query.bindValue(":password", password); + query.prepare("SELECT user_name,password FROM users"); + + if(query.exec()) { + int nameIndex = query.record().indexOf("user_name"); + int passIndex = query.record().indexOf("password"); + + while(query.next()) { + QString name = query.value(nameIndex).toString(); + QString pass = query.value(passIndex).toString(); + if( name == userName && pass == password) + return true; + + } + } + + return false; +} + void Database::retrieveActivityData(UserData* user) { QSqlDatabase dbConnection = QSqlDatabase::database(); QSqlQuery query(dbConnection); query.prepare("SELECT * FROM activity_data WHERE user_name=:userName"); query.bindValue(":userName",user->getName()); query.exec(); const int dataIndex = query.record().indexOf("data"); const int dateIndex = query.record().indexOf("date"); const int userIndex = query.record().indexOf("user_name"); const int activityIndex = query.record().indexOf("activity_name"); while(query.next()) { QString activityData = query.value(dataIndex).toString(); QByteArray data = activityData.toUtf8(); QDataStream in(&data, QIODevice::ReadOnly); while(!in.atEnd()) { ActivityRawData rawData; Identifier id; QVariantMap mapData; in >> id; in >> mapData; rawData.activityName = query.value(activityIndex).toString(); rawData.username = query.value(userIndex).toString(); rawData.date = QDateTime::fromString(query.value(dateIndex).toString(), "dd/MM/yyyy"); rawData.data = mapData; user->addData(rawData); } } } void Database::retrieveAllExistingGroups(QList &allGroups) { QSqlDatabase dbConnection = QSqlDatabase::database(); // Don't add twice the same login QSqlQuery query(dbConnection); query.prepare("SELECT * FROM groups"); query.exec(); const int nameIndex = query.record().indexOf("group_name"); const int descriptionIndex = query.record().indexOf("description"); while(query.next()) { GroupData *g = new GroupData(); g->setName(query.value(nameIndex).toString()); g->setDescription(query.value(descriptionIndex).toString()); allGroups.push_back(g); } } QMultiMap Database::retrieveGroupUsers() { QSqlDatabase dbConnection = QSqlDatabase::database(); QSqlQuery query(dbConnection); query.prepare("SELECT * FROM group_users"); query.exec(); int userIndex = query.record().indexOf("user_name"); int groupIndex = query.record().indexOf("group_name"); QMultiMap groupUsers; while(query.next()) { groupUsers.insert(query.value(groupIndex).toString(),query.value(userIndex).toString()); } return groupUsers; } void Database::retrieveAllExistingUsers(QList &allUsers) { QSqlDatabase dbConnection = QSqlDatabase::database(); QSqlQuery query(dbConnection); query.prepare("SELECT * FROM users"); query.exec(); const int nameIndex = query.record().indexOf("user_name"); const int dateIndex = query.record().indexOf("dateOfBirth"); const int passwordIndex = query.record().indexOf("password"); while(query.next()) { UserData *u = new UserData(); u->setName(query.value(nameIndex).toString()); u->setDateOfBirth(query.value(dateIndex).toString()); u->setPassword(query.value(passwordIndex).toString()); retrieveActivityData(u); allUsers.push_back(u); } } void createDatabase(const QString &path) { QSqlDatabase dbConnection = QSqlDatabase::database(); QSqlQuery query(dbConnection); if(query.exec(CREATE_TABLE_USERS)) qDebug()<< "created table users"; else qDebug() << query.lastError(); if(query.exec(CREATE_TABLE_GROUPS)) qDebug()<< "created table groups"; else qDebug() << query.lastError(); if(query.exec(CREATE_TABLE_USERGROUP)) qDebug()<< "created table group_users"; else qDebug() << query.lastError(); if(query.exec(CREATE_TABLE_ACTIVITY_DATA)) qDebug() << "created table activity_data"; else qDebug() << query.lastError(); } void Database::init() { QDir databasePath; QString path = databasePath.currentPath()+"/gcompris-qt.db"; // todo set cache/data path instead of current folder (same with filename?) QSqlDatabase dbConnection = QSqlDatabase::addDatabase("QSQLITE"); dbConnection.setDatabaseName(path); QFileInfo fileInfo(path); if(!fileInfo.exists()) { if (!dbConnection.open()) { qDebug() << "Error: connection with database fail"; } createDatabase(path); } if (!dbConnection.open()) { qDebug() << "Error: connection with database fail"; } } diff --git a/src/server/Database.h b/src/server/Database.h index d4a8701c4..4328f8858 100644 --- a/src/server/Database.h +++ b/src/server/Database.h @@ -1,68 +1,69 @@ /* GCompris - Database.h * * Copyright (C) 2016 Johnny Jazeix * * Authors: * Johnny Jazeix * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . */ #ifndef DATABASE_H #define DATABASE_H #include #include #include #include class UserData; class GroupData; class QSqlError; struct ActivityRawData; class Database : public QObject { Q_OBJECT private: Database(); static Database* _instance; // singleton instance public: /** * Registers Database singleton in the QML engine. */ static void init(); static QObject *systeminfoProvider(QQmlEngine *engine, QJSEngine *scriptEngine); static Database* getInstance(); ~Database(); bool addGroup(const QString &groupName, const QString& description = QString(), const QStringList& users=QStringList()); bool deleteGroup(const QString& groupName); void retrieveAllExistingUsers(QList &allUsers); void retrieveAllExistingGroups(QList &allGroups); bool addUser(const UserData &user); bool addUserToGroup(const QString& group, const QString& user); bool deleteUser(const QString& userName); + bool verifyUser(const QString& userName, const QString& password); QMultiMap retrieveGroupUsers(); bool addDataToDatabase(const ActivityRawData &rawData); void retrieveActivityData(UserData* user); }; #endif diff --git a/src/server/MessageHandler.cpp b/src/server/MessageHandler.cpp index 013946e3d..b5f50712c 100644 --- a/src/server/MessageHandler.cpp +++ b/src/server/MessageHandler.cpp @@ -1,325 +1,332 @@ /* GComprisServer - MessageHandler.cpp * * Copyright (C) 2016 Johnny Jazeix * * Authors: * Johnny Jazeix * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . */ #include #include "Server.h" #include "Database.h" #include "MessageHandler.h" +#include "Authentication.h" #include #include MessageHandler* MessageHandler::_instance = 0; MessageHandler::MessageHandler() { + m_auth = new Authentication; Server &server = *Server::getInstance(); connect(&server, &Server::loginReceived, this, &MessageHandler::onLoginReceived); connect(&server, &Server::activityDataReceived, this, &MessageHandler::onActivityDataReceived); connect(&server, &Server::newClientReceived, this, &MessageHandler::onNewClientReceived); connect(&server, &Server::clientDisconnected, this, &MessageHandler::onClientDisconnected); // retrieve all the existing users and groups QList users; Database::getInstance()->retrieveAllExistingUsers(users); for(auto it = users.begin(); it != users.end(); it++) { m_users.push_back(*it); } QList groups; Database::getInstance()->retrieveAllExistingGroups(groups); for(auto it = groups.begin(); it != groups.end(); it++) { m_groups.push_back(*it); } // add users to their respective groups QMultiMap groupUsers = Database::getInstance()->retrieveGroupUsers(); QMapIterator itr(groupUsers); while(itr.hasNext()) { itr.next(); GroupData* grp = getGroup(itr.key()); QList values = groupUsers.values(itr.key()); for(const auto &user: values) { UserData* usr = getUser(user); if(grp && usr) { grp->addUser(usr); } } } } MessageHandler* MessageHandler::getInstance() { if (!_instance) _instance = new MessageHandler; return _instance; } QObject *MessageHandler::systeminfoProvider(QQmlEngine *engine, QJSEngine *scriptEngine) { Q_UNUSED(engine) Q_UNUSED(scriptEngine) return getInstance(); } void MessageHandler::init() { getInstance(); qmlRegisterType(); qmlRegisterType(); qmlRegisterSingletonType("GCompris", 1, 0, "MessageHandler", systeminfoProvider); } void MessageHandler::createGroup(const QString &newGroup, const QString &description, const QStringList &users) { //1. add group to database //2. make a new a group and add it to m_groups; qDebug() << "Create group" << users; if(Database::getInstance()->addGroup(newGroup, description,users)) { GroupData *c = new GroupData(); c->setName(newGroup); c->setDescription(description); m_groups.push_back((QObject*)c); for(const auto &user: users) { UserData* usr = getUser(user); if(usr) { c->addUser(usr); } } emit newGroups(); } } void MessageHandler::updateGroup(const QString &oldGroup, const QString &groupName, const QString &description, const QStringList& users) { // todo // update in database: // * table groups (for the name) // * group_users (for all the users, remove previous group name and for each added user, add the group name) // if both OK, update the group to update the name, clean all the users and add them } void MessageHandler::deleteGroup(const QString &groupName) { //delete from database if(Database::getInstance()->deleteGroup(groupName)) { GroupData *c = getGroup(groupName); qDebug() << c; m_groups.removeAll(c); delete c; emit newGroups(); } else { qDebug() << "could not delete the group from database"; } } void MessageHandler::createUser(const QString &name, const QString &age, const QString &password, const QStringList &groups) { // Add the user in the database UserData *u = new UserData(name, age, password); if(Database::getInstance()->addUser(*u)) { qDebug() << "createUser '" << name << "' in groups " << groups; for(const QString &aGroup: groups) { GroupData *group = getGroup(aGroup); if(group) { group->addUser(u); } } m_users.push_back((QObject*)u); emit newUsers(); } else { delete u; qDebug() << "Error while creating user " << name; } } void MessageHandler::addUserToGroup(const QStringList &groups, const QStringList &users) { // 1. add user to group -In database; // 2. associate users with groups; for(const auto &user: users) { UserData* usr = getUser(user); for (const auto &group: groups) { if(Database::getInstance()->addUserToGroup(group, user)) { GroupData* grp = getGroup(group); if(grp && usr) { grp->addUser(usr); } } } } } void MessageHandler::updateUser(const QString &oldUser, const QString &newUser, const QString &avatar, const QStringList &groups) { UserData *user = getUser(oldUser); if (user) { user->setName(newUser); // for each group, remove the user if not in the new groups and add it in the new ones removeUserFromAllGroups(user); for(const QString &aGroup: groups) { GroupData *group = getGroup(aGroup); group->addUser(user); } emit newUsers(); } } UserData *MessageHandler::getUser(const QString &userName) { for (QObject *oUser: m_users) { UserData *user = (UserData *) oUser; if (user->getName() == userName) { return user; } } return nullptr; } GroupData *MessageHandler::getGroup(const QString &groupName) { for (QObject *oGroup: m_groups) { GroupData *group = (GroupData *) oGroup; if (group->getName() == groupName) { return group; } } return nullptr; } void MessageHandler::removeUserFromAllGroups(UserData *user) { for (QObject *oGroup: m_groups) { GroupData *group = (GroupData *) oGroup; group->removeUser(user); } } void MessageHandler::deleteUser(const QString &userName) { if(Database::getInstance()->deleteUser(userName)) { for (QObject *oUser: m_users) { UserData *user = (UserData *) oUser; if (user->getName() == userName) { m_users.removeAll(user); removeUserFromAllGroups(user); delete user; emit newUsers(); } } } else { qDebug() << "Unable to delete" << userName; } } void MessageHandler::onLoginReceived(QTcpSocket *socket, const Login &data) { - qDebug() << "Login received '" << data._name << "'"; - ClientData *c = getClientData(socket); - - for(QObject *oClient: m_clients) { - ClientData *c = (ClientData*)oClient; - if(c->getUserData() && c->getUserData()->getName() == data._name) { - // found a client with the same user name.(i.e someone chose the wrong login) - qDebug() << "a client with the same user name already exists"; - return; - //todo: - // return an error message to client and inform that you have chosen the wrong login + qDebug() << "user request: " << data._name << " : " << data._password; + if(m_auth && m_auth->loginAuth(data)) { + qDebug() << "user verified: " << data._name << " : " << data._password; + ClientData *c = getClientData(socket); + + for(QObject *oClient: m_clients) { + ClientData *c = (ClientData*)oClient; + if(c->getUserData() && c->getUserData()->getName() == data._name) { + // found a client with the same user name.(i.e someone chose the wrong login) + qDebug() << "a client with the same user name already exists"; + return; + //todo: + // return an error message to client and inform that you have chosen the wrong login + } } - } - for(QObject *oUser: m_users) { - UserData *user = (UserData*)oUser; - qDebug() << "received login " << data._name << " " << c->getSocket(); - if(user->getName() == data._name) { - c->setUser(user); - return; + for(QObject *oUser: m_users) { + UserData *user = (UserData*)oUser; + qDebug() << "received login " << data._name << " " << c->getSocket(); + if(user->getName() == data._name) { + c->setUser(user); + return; + } } + // Should not happen when login will be done properly... todo display an error message + qDebug() << "Error: login " << data._name << " received, but no user found"; } - // Should not happen when login will be done properly... todo display an error message - qDebug() << "Error: login " << data._name << " received, but no user found"; + else + qDebug() << "Authentication failed"; } void MessageHandler::onActivityDataReceived(QTcpSocket* socket, const ActivityRawData &act) { qDebug() << "Activity: " << act.activityName << ", date: " << act.date << ", data:" << act.data << ", user: " << act.username; Database::getInstance()->addDataToDatabase(act); ClientData* client = getClientData(socket); if(client) { if(client->getUserData()) { client->getUserData()->addData(act); } } emit newActivityData(); } void MessageHandler::onNewClientReceived(QTcpSocket* socket) { qDebug() << "New client"; ClientData *c = new ClientData; c->setSocket(socket); m_clients.push_back((QObject*)c); emit newClients(); } void MessageHandler::onClientDisconnected(QTcpSocket* socket) { qDebug() << "client disconnected"; ClientData *c = getClientData(socket); c->setUser(nullptr); m_clients.removeAll(c); delete c; emit newClients(); } ClientData *MessageHandler::getClientData(QTcpSocket* socket) { for (QObject *oc: m_clients) { ClientData *c = (ClientData *) oc; if (c->getSocket() == socket) { return c; } } } diff --git a/src/server/MessageHandler.h b/src/server/MessageHandler.h index 098a8f090..f9e200388 100644 --- a/src/server/MessageHandler.h +++ b/src/server/MessageHandler.h @@ -1,128 +1,132 @@ /* GComprisServer - MessageHandler.h * * Copyright (C) 2016 Johnny Jazeix * * Authors: * Johnny Jazeix * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . */ #ifndef MESSAGEHANDLER_H #define MESSAGEHANDLER_H #include "Messages.h" #include "ClientData.h" #include "GroupData.h" #include "UserData.h" #include #include +class Authentication; + /** * @class MessageHandler * @short Handles all the messages received by the socket * * JOB: * -Handle messages received by the socket * -Create, update, delete information about: * --users * --groups * -Linking users, clients and groups with each other * * * @sa UserData * @sa GroupData * @sa ClientData */ class MessageHandler: public QObject { Q_OBJECT Q_PROPERTY(QList clients MEMBER m_clients NOTIFY newClients) Q_PROPERTY(QList groups MEMBER m_groups NOTIFY newGroups) Q_PROPERTY(QList users MEMBER m_users NOTIFY newUsers) private: MessageHandler(); // prohibit external creation, we are a singleton! static MessageHandler* _instance; // singleton instance public: /** * Registers MessageHandler singleton in the QML engine. */ static void init(); static QObject *systeminfoProvider(QQmlEngine *engine, QJSEngine *scriptEngine); static MessageHandler* getInstance(); Q_INVOKABLE void createGroup(const QString &groupName, const QString &description = QString(), const QStringList &users = QStringList()); Q_INVOKABLE void updateGroup(const QString &oldGroup, const QString &groupName, const QString &description = QString(), const QStringList& users = QStringList()); Q_INVOKABLE void deleteGroup(const QString &groupName); Q_INVOKABLE void createUser(const QString &userName, const QString &age, const QString &avatar = QString(), const QStringList &groups = QStringList()); Q_INVOKABLE void updateUser(const QString &oldUser, const QString &newUser, const QString &avatar = QString(), const QStringList &groups = QStringList()); Q_INVOKABLE void deleteUser(const QString &userName); Q_INVOKABLE void addUserToGroup(const QStringList& groups, const QStringList& users); Q_INVOKABLE UserData *getUser(const QString &userName); Q_INVOKABLE GroupData *getGroup(const QString &groupName); Q_INVOKABLE QList returnGroupUsers(const QString& group) { GroupData* g = getGroup(group); if(g) { return g->getUsers(); } } QList returnUsers() { return m_users; } /*QList returnClients() { return m_clients; }*/ public slots: void onLoginReceived(QTcpSocket* socket, const Login &data); void onActivityDataReceived(QTcpSocket* socket, const ActivityRawData &act); void onNewClientReceived(QTcpSocket* socket); void onClientDisconnected(QTcpSocket* socket); signals: void newClients(); void newGroups(); void newUsers(); void newActivityData(); private: ClientData *getClientData(QTcpSocket* socket); void removeUserFromAllGroups(UserData *user); // ClientData* QList m_clients; // GroupData* QList m_groups; // UserData* QList m_users; + + Authentication* m_auth; }; #endif diff --git a/src/server/views/AddUpdateUser.qml b/src/server/views/AddUpdateUser.qml index e09471aac..c45fe20d5 100644 --- a/src/server/views/AddUpdateUser.qml +++ b/src/server/views/AddUpdateUser.qml @@ -1,305 +1,305 @@ /* GCompris - AddUpdateUser.qml * * Copyright (C) 2017 Johnny Jazeix * * Authors: * Johnny Jazeix * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . */ import QtQuick 2.1 import GCompris 1.0 import QtQuick.Controls 1.1 import QtQuick.Controls.Styles 1.0 import "../../core" import "qrc:/gcompris/src/core/core.js" as Core Rectangle { id: addUpdateRectangle anchors.fill: parent color: "white" signal addUsers(ListModel model) ListModel { id: userToUpdateModel } TableView { id: users width: parent.width - 50 height: parent.height - (bar.height * 2) property var name: ""; property var dateOfBirth: "" property var password: ""; function save(){ if(name != "" && dateOfBirth != "") { console.log("name of the user: ", name, "date of birth of the user: ", dateOfBirth, "password : ", password) console.log userToUpdateModel.append({ "name": name, "dateOfBirth": dateOfBirth, "password": password }) name="" dateOfBirth="" password="" console.log("total number of users: ", userToUpdateModel.count) } } model: userToUpdateModel selectionMode: SelectionMode.MultiSelection rowDelegate: Rectangle { height: 50 width: childrenRect.width SystemPalette { id: myPalette; colorGroup: SystemPalette.Active } color: { var baseColor = styleData.alternate ? myPalette.alternateBase : myPalette.base return styleData.selected ? myPalette.highlight : baseColor } } TableViewColumn { role: "name" title: qsTr("Name") width: 100 } TableViewColumn { role: "dateOfBirth" title: qsTr("Birth year") width: 100 } TableViewColumn { id: passwordColumn role: "password" width: 100 title: qsTr("Password") delegate: Item { id: pc width: passwordColumn.width // same as rowDelegate Item { id: passwordField anchors.fill: parent Component.onCompleted: { users.password = passwordImage.source.toString() } MouseArea { anchors.fill: parent onClicked: { passwordImage.visible = !passwordImage.visible } } Text { id: passwordText visible: !passwordImage.visible verticalAlignment: Text.AlignVCenter fontSizeMode: Text.Fit text: modelData ? modelData.password : "" } Image { id: passwordImage source: "qrc:/gcompris/src/activities/sudoku/sudoku.svg" sourceSize.height: 50 } } } } TableViewColumn { resizable: true role: "saveDelete" title: qsTr("Save or Delete") width: users.width/3 delegate: Item{ id: itemDel width: 100 height: 40 anchors.topMargin: 20 Rectangle{ id: save width: 100 height: 40 color: "black" anchors.rightMargin: 10 Text{ anchors.centerIn: parent color: "white" text: "Save" } MouseArea{ id: saveMouseArea anchors.fill: parent onClicked: { users.save() } } } Rectangle{ - id: delete + id: deleteUser width: 100 height: 40 anchors.leftMargin: 10 anchors.left: save.right color: "black" Text{ anchors.centerIn: parent color: "white" text: "Delete" } } } } itemDelegate: Rectangle { id: rect SystemPalette { id: myPalette; colorGroup: SystemPalette.Active } color: { var baseColor = styleData.row % 2 == 1 ? myPalette.alternateBase : myPalette.base return styleData.selected ? myPalette.highlight : baseColor } MouseArea { id: cellMouseArea anchors.fill: parent onClicked: { // Column index are zero based if(styleData.column === 0 || styleData.column === 1) { loader.visible = true loader.item.forceActiveFocus() } } } Loader { id: loader anchors { verticalCenter: parent.verticalCenter left: parent.left } height: parent.height width: parent.width visible: false sourceComponent: visible ? input : undefined Component { id: input TextField { anchors.fill: parent visible: true text: "" onAccepted: { loader.visible = false } onEditingFinished:{ console.log("finished editing") switch(styleData.column) { case 0: { if(text != "") users.name = text break; } case 1: { if(text != "") users.dateOfBirth = text break; } } } } } } } } Column { id: addRemoveColumn anchors.left: users.right anchors.bottom: users.bottom spacing: 5 width: 50 Button { id: removeSelectedUsers text: qsTr("-") enabled: users.selection.count != 0 width: parent.width style: GCButtonStyle {} onClicked: { var idToRemove = [] users.selection.forEach(function(rowIndex) { idToRemove.push(rowIndex) }); for(var i = idToRemove.length-1 ; i >= 0 ; -- i) { userToUpdateModel.remove(idToRemove[i]); } users.selection.clear() } } Button { id: addUsersButton text: qsTr("+") width: parent.width style: GCButtonStyle {} onClicked: { // add empty user at first index. The first user is always going to be empty userToUpdateModel.insert(0, {"name": "", "dateOfBirth": "", "password": ""}) // userToUpdateModel.append({"name": "", "dateOfBirth": "", "password": ""}) } } } Grid { rows: 1 anchors.top: users.bottom spacing: 5 Button { id: createButton text: qsTr("Create") style: GCButtonStyle {} onClicked: { console.log("Creating user"); console.log(userToUpdateModel.get(0).name); console.log(userToUpdateModel.get(0).dateOfBirth); addUsers(userToUpdateModel) addUpdateRectangle.visible = false } } Button { id: cancelUserButton text: qsTr("Cancel") style: GCButtonStyle {} onClicked: { addUpdateRectangle.visible = false userToUpdateModel.clear() } } } }