/** * Handler for all exercise objects * $Id$ * $HeadURL$ * $Author$ * Created by Hanna E. Rathje, SLSE, 2015-09-03 */ #include "handler_operator.h" #include "handler_locked_topics.h" #include "handler_active_apps.h" #include "object_operator.h" #include "handler_group.h" #include "opm_db.h" #include #include #include #include namespace OperatorManagement { HandlerOperator::HandlerOperator(MainHandler *mainHandler) : Interface::Fts::OperatorChangePasswordRequestHandler(mainHandler->fts()) , Interface::Fts::OperatorCreateModifyRequestHandler(mainHandler->fts()) , Interface::Fts::OperatorRemoveRequestHandler(mainHandler->fts()) , Interface::Fts::OperatorLoginRequestHandler(mainHandler->fts()) , Interface::Fts::OperatorLogoutRequestHandler(mainHandler->fts()) , Interface::Fts::OperatorGetSetSettingsRequestHandler(mainHandler->fts()) , Interface::Fts::OperatorApplyForeignAclsRequestHandler(mainHandler->fts()) , Interface::Fts::OperatorUpdateVisiblePartiesByExerciseRequestHandler(mainHandler->fts()) , m_aliveCheckTimer(new QTimer(this)) , m_mainHandler(mainHandler) , m_logger(getLogger("HandlerOperator")) { connect(m_aliveCheckTimer, &QTimer::timeout, this, &HandlerOperator::onCheckAliveTimeout); m_aliveCheckTimer->setInterval(10000); m_aliveCheckTimer->setSingleShot(false); m_aliveCheckTimer->start(); } HandlerOperator::~HandlerOperator() { qDeleteAll(m_operatorObjMap); } bool HandlerOperator::init() { QVector exMappingVec; m_mainHandler->db()->select(exMappingVec); QVector dataVec; // read all Operators from database if (!m_mainHandler->db()->select(dataVec)) { ERROR_LOG(m_logger) << "Error reading data from database!"; return true; } QVector partyExerciseVec; // read all Operators from database if (!m_mainHandler->db()->select(partyExerciseVec)) { ERROR_LOG(m_logger) << "Error reading data from database!"; return true; } QMap > opMap; for (const auto &elem : partyExerciseVec) { INFO_LOG(m_logger) << "---------- elem.operatorId:" << elem.operatorId << ", elem size " << elem.visibleParties().size(); opMap[elem.operatorId] << elem; } INFO_LOG(m_logger) << "------- opMap size" << opMap.size(); OperatorManagement::OperatorWorkstationAlive alive; alive.lastAlive(QDateTime::currentMSecsSinceEpoch()); for (const Database::OpmOperator &op : qAsConst(dataVec)) { ObjectOperator *ifs(new ObjectOperator(m_mainHandler)); Core::FunctionReplyObject ret = ifs->createObject(op, opMap[op.id]); if (ret.error()) { delete ifs; ERROR_LOG(m_logger) << ret(); return false; } if (m_logger.isDebugEnabled()) { QStringList stringList; const auto &acls = ifs->effectiveAcls(); stringList.reserve(acls.size()); for (int acl : acls) stringList << QString::number(acl); DEBUG_LOG(m_logger) << "Effective ACLs for Operator " << op.name << "(id: " << op.id << "): " << stringList.join(QLatin1String(", ")); } ifs->updateExercises(getExercisesForOperator(exMappingVec, op.id), true); m_operatorObjMap.insert(ifs->getId(), ifs); if (!op.workstationId.isNull()) { alive.operatorId(op.id); alive.workstationId(op.workstationId.toInt()); onOperatorWorkstationAliveChanged(alive, DataModification::eAdded); } } auto dtc = m_mainHandler->dtc(); connect(dtc.get(), &Interface::Dtc::operatorWorkstationAliveChanged, this, &HandlerOperator::onOperatorWorkstationAliveChanged); dtc->enableOperatorWorkstationAlive(); DEBUG_LOG(m_logger) << "m_exeObjMap.size() = " << m_operatorObjMap.size(); return true; } QString HandlerOperator::operatorNameById(int operatorId) const { ObjectOperator *obj = m_operatorObjMap.value(operatorId); if (!obj) return QStringLiteral("?"); return obj->data().name; } std::vector HandlerOperator::getExercisesForOperator(const QVector &mapping, int operatorId) { std::vector exercises; for (const Database::OpmOperatorExercise &exMapping : mapping) if (exMapping.operatorId == operatorId) exercises.push_back(exMapping.exerciseId); return exercises; } void HandlerOperator::onOperatorChangePasswordRequest(const rti::core::SampleIdentity &cookie, const OperatorChangePasswordRequest &request) { DEBUG_LOG(m_logger) << "onOperatorChangePasswordRequest()"; ObjectOperator *ifs = m_operatorObjMap.value(request.id()); if (!ifs) { m_mainHandler->fts()->operatorChangePasswordReply(cookie, Core::FunctionReplyObject(0, tr_lts("dds", "No valid operator id for changing password."))); return; } Core::FunctionReplyObject ret; Database::OpmOperator op = ifs->data(); const auto &pw = request.oldPassword(); const QByteArray ba(QByteArray::fromRawData(pw.c_str(), pw.size())); const QString enteredPassword(QString::fromLatin1(QCryptographicHash::hash(ba, QCryptographicHash::Md5).toHex())); const auto &pwNew = request.password(); const QByteArray baNew(QByteArray::fromRawData(pwNew.c_str(), pwNew.size())); const QString enteredPasswordNew(QString::fromLatin1(QCryptographicHash::hash(baNew, QCryptographicHash::Md5).toHex())); if (request.forceSet() || op.password == enteredPassword) { if (enteredPassword == enteredPasswordNew) ret = Core::FunctionReplyObject(0, tr_lts("dds", "Password is identical to old password.")); // else if (request.password().size() < n && $minimum1digit && minimum1extrachar && ...) // { // ret = Core::FunctionReplyObject(0, tr_lts("dds", "Password must be at least n characters")); // } else { op.password = enteredPasswordNew; ret = ifs->updateObject(op); } } else { ret = Core::FunctionReplyObject(0, tr_lts("dds", "Old password does not match.")); } m_mainHandler->fts()->operatorChangePasswordReply(cookie, ret); } void HandlerOperator::onOperatorCreateModifyRequest(const rti::core::SampleIdentity &cookie, const OperatorCreateModifyRequest &request) { Core::FunctionReplyObject ret; DEBUG_LOG(m_logger) << "onOperatorCreateModifyRequest()"; if (!request.id().is_set()) ret = operatorCreateRequest(request); else ret = operatorModifyRequest(request); if (ret.error()) WARNING_LOG(m_logger) << ret(); m_mainHandler->fts()->operatorCreateModifyReply(cookie, ret); } Core::FunctionReplyObject HandlerOperator::operatorCreateRequest(const OperatorCreateModifyRequest &request) { if (!request.name().is_set() || !request.password().is_set()) return Core::FunctionReplyObject(tr_lts("dds", "Can not create operator - name or password is empty.")); // Group anlegen GroupCreateRequest groupRequest; groupRequest.name(request.name().get()); groupRequest.description(request.description().get()); groupRequest.type(GroupType_def::OperatorGroup); const Core::FunctionReplyObject groupRet = m_mainHandler->getHandlerGroup()->create(groupRequest); if (groupRet.error()) return groupRet; // Operator anlegen Database::OpmOperator op; op.name = QString::fromStdString(request.name().get()); if (request.description().is_set()) op.description = QString::fromStdString(request.description().get()); const auto &pw = request.password().get(); const QByteArray ba(QByteArray::fromRawData(pw.c_str(), pw.size())); const QString enteredPassword(QString::fromLatin1(QCryptographicHash::hash(ba, QCryptographicHash::Md5).toHex())); op.password = enteredPassword; op.primaryGroupId = groupRet.id(); if (request.image().is_set()) op.image = LtsUtils::toQByteArray(request.image().get()); const auto vpbe = request.visiblePartiesByExercise(); for (const auto &pbe : vpbe) { if (!pbe.visibleParties().is_set() || pbe.exerciseId() != 0) //has_value not support on windows yet so we need depricated is_set continue; op.visibleParties = LtsUtils::IntArray(pbe.visibleParties().get()); } if (request.rolePresetId().is_set()) { op.rolePresetId = request.rolePresetId().get(); } op.projectionPriority = request.projectionPriority().is_set() ? request.projectionPriority().get() : 1; op.audioChannelPriority = request.audioChannelPriority().is_set() ? request.audioChannelPriority().get() : 1; ObjectOperator *ifs(new ObjectOperator(m_mainHandler)); const Core::FunctionReplyObject ret = ifs->createObject(op, {}); if (ret.error()) { Core::RemoveElementRequest groupRemoveRequest(groupRet.id()); m_mainHandler->getHandlerGroup()->remove(groupRemoveRequest); delete ifs; return ret; } if (request.visibleExercises().is_set()) ifs->updateExercises(request.visibleExercises().get()); if (request.operatorRights().is_set()) { GroupChangeMembersRequest grChReq; grChReq.id(op.primaryGroupId); grChReq.memberships(request.operatorRights().get()); // We don't actually care, if this fails... m_mainHandler->getHandlerGroup()->changeMembership(grChReq); ifs->reloadAcls(true); } m_operatorObjMap.insert(ifs->getId(), ifs); return ret; } void HandlerOperator::operatorModifyRolePresetId( int operatorId, int rolePresetId) { DEBUG_LOG(m_logger)<< "HandlerOperator::operatorModifyRolePresetId" << " operatorId=" << operatorId << " rolePresetId=" << rolePresetId; ObjectOperator *ifs = m_operatorObjMap.value(operatorId); if (!ifs) { ERROR_LOG(m_logger)<< "No valid operator id given: "<< operatorId; return; } Database::OpmOperator op = ifs->data(); op.rolePresetId = rolePresetId; ifs->updateObject(op); } Core::FunctionReplyObject HandlerOperator::operatorModifyRequest(const OperatorCreateModifyRequest &request) { if (request.password().is_set()) return Core::FunctionReplyObject(tr_lts("dds", "Can not modify operator password with this function.")); ObjectOperator *ifs = m_operatorObjMap.value(request.id().get()); if (!ifs) return Core::FunctionReplyObject(request.id().get(), tr_lts("dds", "No valid operator id given.")); Database::OpmOperator op = ifs->data(); if (request.name().is_set()) { op.name = QString::fromStdString(request.name().get()); for (const ObjectOperator *o : qAsConst(m_operatorObjMap)) { if (o == ifs) continue; if (o->name().compare(op.name, Qt::CaseInsensitive) == 0) return Core::FunctionReplyObject(-1, tr_lts("dds", "Name '%1' already exists")).arg(op.name); } } if (request.description().is_set()) op.description = QString::fromStdString(request.description().get()); if (request.image().is_set()) op.image = LtsUtils::toQByteArray(request.image().get()); const auto vpbe = request.visiblePartiesByExercise(); for (const auto &pbe : vpbe) { // only ex0 updated if (!pbe.visibleParties().is_set() || pbe.exerciseId() != 0) //has_value not support on windows yet so we need depricated is_set continue; op.visibleParties = LtsUtils::IntArray(pbe.visibleParties().get()); } if (request.rolePresetId().is_set()) { op.rolePresetId = request.rolePresetId().get(); } if (request.visibleExercises().is_set()) ifs->updateExercises(request.visibleExercises().get()); if (request.projectionPriority().is_set()) op.projectionPriority = request.projectionPriority().get(); if (request.audioChannelPriority().is_set()) op.audioChannelPriority = request.audioChannelPriority().get(); op.properties = QString::fromStdString(request.properties()); return ifs->updateObject(op); } void HandlerOperator::onOperatorRemoveRequest(const rti::core::SampleIdentity &cookie, const Core::RemoveElementRequest &request) { DEBUG_LOG(m_logger) << "onOperatorRemoveRequest()" << request; ObjectOperator *ifs = m_operatorObjMap.value(request.id()); if (!ifs) { m_mainHandler->fts()->operatorRemoveReply(cookie, Core::FunctionReplyObject(0, tr_lts("dds", "No valid operator id for deleting."))); return; } if (!ifs->data().workstationId.isNull()) { m_mainHandler->fts()->operatorRemoveReply(cookie, Core::FunctionReplyObject(0, tr_lts("dds", "A logged in operator may not be deleted."))); return; } OperatorManagement::User ddsData; ddsData.id(request.id()); if (m_mainHandler->dds()->topicLockMaster()->isLocked(ddsData, 0)) { m_mainHandler->fts()->operatorRemoveReply(cookie, Core::FunctionReplyObject(request.id(), tr_lts("dds", "Operator '%1' is used by another user!")).arg(ifs->name())); return; } Core::RemoveElementRequest groupRemoveRequest(ifs->data().primaryGroupId); Core::FunctionReplyObject ret = m_mainHandler->getHandlerGroup()->remove(groupRemoveRequest); if (ret.error()) { ERROR_LOG(m_logger) << ret(); } else { ret = ifs->removeObject(); // Aus der lokalen Map löschen m_operatorObjMap.remove(ifs->getId()); // und aus TopicLock entfernen m_mainHandler->getHandlerLockedTopics()->operatorLoggedOut(ifs->getId()); delete ifs; } m_mainHandler->fts()->operatorRemoveReply(cookie, ret); } void HandlerOperator::onOperatorLoginRequest(const rti::core::SampleIdentity &cookie, const OperatorLoginRequest &request) { DEBUG_LOG(m_logger) << "OperatorLoginRequest: " << request; ObjectOperator *ifs = m_operatorObjMap.value(request.id()); if (!ifs) { m_mainHandler->fts()->operatorLoginReply(cookie, Core::FunctionReplyObject(0, tr_lts("dds", "No operator found by the given id."))); return; } const auto &pw = request.password(); const QByteArray ba(QByteArray::fromRawData(pw.c_str(), pw.size())); const QString enteredPassword(QString::fromLatin1(QCryptographicHash::hash(ba, QCryptographicHash::Md5).toHex())); if (enteredPassword != ifs->data().password) { m_mainHandler->fts()->operatorLoginReply(cookie, Core::FunctionReplyObject(ifs->data().workstationId.toInt(), tr_lts("dds", "The password you have entered is invalid."))); return; } // If no WS is claimed, just return operator ID Core::FunctionReplyObject ret(ifs->data().id); // Request claim a WS if (!ifs->data().workstationId.isNull() && ifs->data().workstationId.toInt() > 0) // operator already logged in { if (ifs->data().workstationId == request.workstationId()) { // do nothing if same operator claims same workstation } else if (!request.force()) { // Already claimed: Return WSid as ID const auto ws = m_mainHandler->dds()->dtcAsm()->asset(ifs->data().workstationId.toInt()); ret = Core::FunctionReplyObject(ifs->data().workstationId.toInt(), tr_lts("dds", "Operator already logged in on workstation %1.")).arg(QString::fromStdString(ws.name())); } else { // force login, logout the other operator logged in on this WS, if there is any int otherOperatorId = getLoggedOperatorId(ifs->data().workstationId.toInt()); if (otherOperatorId != 0 && otherOperatorId != ifs->data().id) { ObjectOperator *other = m_operatorObjMap.value(otherOperatorId); if (other) { OperatorLogoutRequest req; req.id(otherOperatorId); onOperatorLogoutRequest(cookie, req); } } Database::OpmOperator op = ifs->data(); op.workstationIp = request.workstationIp(); op.workstationId = request.workstationId(); ret = ifs->updateObject(op); } } else // operator not logged in on any workstation { // if other operator is logged in on same WS -> logout other operator and login our operator int otherOperatorId = getLoggedOperatorId(request.workstationId()); if (otherOperatorId != 0 && otherOperatorId != ifs->data().id) { ObjectOperator *other = m_operatorObjMap.value(otherOperatorId); if (other) { OperatorLogoutRequest req; req.id(otherOperatorId); onOperatorLogoutRequest(cookie, req); WARNING_LOG(m_logger) << "Logged out operator " << otherOperatorId << " on workstation id " << request.workstationId(); } } // Claim WS: Database::OpmOperator op = ifs->data(); op.workstationIp = request.workstationIp(); op.workstationId = request.workstationId(); ret = ifs->updateObject(op); } if (ret.error()) WARNING_LOG(m_logger) << ret(); else { m_mainHandler->getHandlerLockedTopics()->operatorLoggedIn(request.id(), request.workstationId()); m_mainHandler->getHandlerActiveApps()->cleanupOperator(request.id()); } m_mainHandler->fts()->operatorLoginReply(cookie, ret); } int HandlerOperator::getLoggedOperatorId(int workstationId) const { auto it = std::find_if(m_operatorObjMap.begin(), m_operatorObjMap.end(), [workstationId](const ObjectOperator *oper) { return oper->data().workstationId == workstationId; }); return (it == m_operatorObjMap.end() ? 0 : it.value()->getId()); } void HandlerOperator::onOperatorLogoutRequest(const rti::core::SampleIdentity &cookie, const OperatorLogoutRequest &request) { DEBUG_LOG(m_logger) << "OperatorLogoutRequest: " << request; ObjectOperator *ifs = m_operatorObjMap.value(request.id()); if (!ifs) { m_mainHandler->fts()->operatorLogoutReply(cookie, Core::FunctionReplyObject(0, tr_lts("dds", "No valid operator id for logout."))); return; } if (ifs->data().workstationId.isNull() || ifs->data().workstationId == 0) // && ifs->data().workstationIp == 0 ? { m_mainHandler->fts()->operatorLogoutReply(cookie, Core::FunctionReplyObject(ifs->data().id)); return; } Database::OpmOperator op = ifs->data(); op.workstationIp = 0; op.workstationId = QVariant(); m_mainHandler->getHandlerLockedTopics()->operatorLoggedOut(request.id()); // workstationId = 0 -> logout m_mainHandler->getHandlerActiveApps()->cleanupOperator(op.id); m_mainHandler->fts()->operatorLogoutReply(cookie, ifs->updateObject(op)); } void HandlerOperator::onOperatorGetSetSettingsRequest(const rti::core::SampleIdentity &cookie, const OperatorGetSetSettingsRequest &request) { Core::FunctionReplyObject fro; OperatorGetSetSettingsReply reply; reply.id(request.id()); ObjectOperator *ifs = m_operatorObjMap.value(request.id()); if (ifs) { if (request.settings().is_set()) { const auto &data = request.settings().get(); const QByteArray settings(QByteArray(reinterpret_cast(&data[0]), data.size())); if (!ifs->saveSettings(settings)) fro = Core::FunctionReplyObject(request.id(), tr_lts("dds", "Error saving settings in database.") ); } else { QByteArray settings; if (!ifs->loadSettings(settings)) { fro = Core::FunctionReplyObject(request.id(), tr_lts("dds", "Error loading settings from database.") ); } else { if (!settings.isEmpty()) { reply.settings().set({}); reply.settings().get() = LtsUtils::convert>(settings); } } } } else { fro = Core::FunctionReplyObject(request.id(), tr_lts("dds", "No valid operator id for retrieving settings.")); } if (fro.error()) { reply.id(fro.id()); reply.errorString(fro.str()); reply.errorParams(fro.args()); } m_mainHandler->fts()->operatorGetSetSettingsReply(cookie, reply); } void HandlerOperator::onOperatorApplyForeignAclsRequest(const rti::core::SampleIdentity &cookie, const OperatorApplyForeignAclsRequest &request) { DEBUG_LOG(m_logger) << "peratorApplyForeignAclsRequest: " << request; auto reply = operatorApplyForeignAclsRequest(request); if (reply.error()) WARNING_LOG(m_logger) << reply(); m_mainHandler->fts()->operatorApplyForeignAclsReply(cookie, reply); } Core::FunctionReplyObject HandlerOperator::operatorApplyForeignAclsRequest(const OperatorApplyForeignAclsRequest &request) { ObjectOperator *receiver = m_operatorObjMap.value(request.id()); if (!receiver) return Core::FunctionReplyObject(0, tr_lts("dds", "Receiving operator not present.")); ObjectOperator *remoteOperator = m_operatorObjMap.value(request.remoteOperatorId()); if (!remoteOperator) return Core::FunctionReplyObject(0, tr_lts("dds", "Remote operator not present.")); return receiver->setPrimaryGroupId(remoteOperator->data().primaryGroupId); } void HandlerOperator::onOperatorUpdateVisiblePartiesByExerciseRequest(const rti::core::SampleIdentity &cookie, const OperatorUpdateVisiblePartiesByExerciseRequest &request) { DEBUG_LOG(m_logger) << LOGFUNCTION << request; // DEBUG_LOG(m_logger) << "onOperatorUpdateVisiblePartiesByExerciseRequest: " << request; Core::FunctionReplyObject reply = operatorUpdateVisiblePartiesByExerciseRequest(request); if (reply.error()) WARNING_LOG(m_logger) << reply(); m_mainHandler->fts()->operatorUpdateVisiblePartiesByExerciseReply(cookie, reply); } Core::FunctionReplyObject HandlerOperator::operatorUpdateVisiblePartiesByExerciseRequest(const OperatorUpdateVisiblePartiesByExerciseRequest &request) { ObjectOperator *ifs = m_operatorObjMap.value(request.id()); if (!ifs) return Core::FunctionReplyObject(request.id(), tr_lts("dds", "No valid operator id given.")); Database::OpmOperatorExercise opEx; opEx.operatorId = request.id(); opEx.exerciseId = request.exerciseId(); std::vector hiddenParties; if (request.hiddenParties().is_set()) opEx.hiddenParties = LtsUtils::IntArray(request.hiddenParties().get()); if (request.visibleParties().is_set()) opEx.visibleParties = LtsUtils::IntArray(request.visibleParties().get()); opEx.properties = QString::fromStdString(request.properties()); return ifs->updateOperatorExercise(opEx); } void HandlerOperator::reloadAcls() { for (ObjectOperator *ifs : qAsConst(m_operatorObjMap)) ifs->reloadAcls(); } void HandlerOperator::onOperatorWorkstationAliveChanged(const OperatorManagement::OperatorWorkstationAlive &data, DataModification modType) { INFO_LOG(m_logger) << "onOperatorWorkstationAliveChanged: " << data << ", " << modType; if (data.operatorId() < 0) { m_aliveData.remove(data.workstationId()); return; } auto &events = m_aliveData[data.workstationId()]; events.push_back(data); // do not store more than 30 history events if (events.size() > 30) events = events.mid(10); } void HandlerOperator::onCheckAliveTimeout() { if (m_aliveData.isEmpty()) return; const qint64 tTimeoutTime = QDateTime::currentMSecsSinceEpoch() - 90 * 1000; // 90s DEBUG_LOG(m_logger) << "onCheckAliveTimeout, timeoutTime: " << tTimeoutTime; auto it = m_aliveData.begin(); while (it != m_aliveData.end()) { DEBUG_LOG(m_logger) << "Checking alive for workstation " << it.key(); auto &events = it.value(); if (events.isEmpty() || events.last().lastAlive() >= tTimeoutTime) { ++it; continue; } // no reference, will be a dangling one after m_aliveData.erase(it) ! const OperatorWorkstationAlive owa = events.last(); WARNING_LOG(m_logger) << "Timeout for workstation " << owa.workstationId() << " and operator " << owa.operatorId() << " - logging out"; if (m_logger.isDebugEnabled()) { for (const auto &event : events) DEBUG_LOG(m_logger) << "Alive history: " << event; } ObjectOperator *ifs = m_operatorObjMap.value(owa.operatorId()); it = m_aliveData.erase(it); if (!ifs) continue; Database::OpmOperator op = ifs->data(); op.workstationIp = 0; op.workstationId = QVariant(); m_mainHandler->getHandlerLockedTopics()->operatorLoggedOut(owa.operatorId()); // workstationId = 0 -> logout m_mainHandler->getHandlerActiveApps()->cleanupOperator(owa.operatorId()); ifs->updateObject(op); } } } // Namespace OperatorManagement