diff --git a/assignment-client/src/entities/EntityServer.cpp b/assignment-client/src/entities/EntityServer.cpp index e68f95bda05..59a8a66d4fd 100644 --- a/assignment-client/src/entities/EntityServer.cpp +++ b/assignment-client/src/entities/EntityServer.cpp @@ -310,13 +310,24 @@ void EntityServer::readAdditionalConfiguration(const QJsonObject& settingsSectio bool wantEditLogging = false; readOptionBool(QString("wantEditLogging"), settingsSectionObject, wantEditLogging); qDebug("wantEditLogging=%s", debug::valueOf(wantEditLogging)); + + bool wantAuditEditLogging = false; + readOptionBool(QString("wantAuditEditLogging"), settingsSectionObject, wantAuditEditLogging); + qDebug("wantAuditEditLogging=%s", debug::valueOf(wantAuditEditLogging)); + + EntityTreePointer tree = std::static_pointer_cast(_tree); + + int auditEditLoggingInterval; + if (readOptionInt("auditEditLoggingInterval", settingsSectionObject, auditEditLoggingInterval)) { + tree->setAuditEditLoggingInterval(auditEditLoggingInterval); + } else { + tree->setAuditEditLoggingInterval(EntityTree::DEFAULT_AUDIT_EDIT_INTERVAL); + } bool wantTerseEditLogging = false; readOptionBool(QString("wantTerseEditLogging"), settingsSectionObject, wantTerseEditLogging); qDebug("wantTerseEditLogging=%s", debug::valueOf(wantTerseEditLogging)); - EntityTreePointer tree = std::static_pointer_cast(_tree); - int maxTmpEntityLifetime; if (readOptionInt("maxTmpLifetime", settingsSectionObject, maxTmpEntityLifetime)) { tree->setEntityMaxTmpLifetime(maxTmpEntityLifetime); @@ -337,6 +348,7 @@ void EntityServer::readAdditionalConfiguration(const QJsonObject& settingsSectio startDynamicDomainVerification(); tree->setWantEditLogging(wantEditLogging); + tree->setWantAuditEditLogging(wantAuditEditLogging); tree->setWantTerseEditLogging(wantTerseEditLogging); QString entityScriptSourceWhitelist; diff --git a/assignment-client/src/entities/EntityServer.h b/assignment-client/src/entities/EntityServer.h index 9bb3a237e06..7c0b451a451 100644 --- a/assignment-client/src/entities/EntityServer.h +++ b/assignment-client/src/entities/EntityServer.h @@ -45,7 +45,7 @@ class EntityServer : public OctreeServer, public NewlyCreatedEntityHook { virtual PacketType getMyEditNackType() const override { return PacketType::EntityEditNack; } virtual QString getMyDomainSettingsKey() const override { return QString("entity_server_settings"); } - // subclass may implement these method + // subclass may implement these methods virtual void beforeRun() override; virtual bool hasSpecialPacketsToSend(const SharedNodePointer& node) override; virtual int sendSpecialPackets(const SharedNodePointer& node, OctreeQueryNode* queryNode, int& packetsSent) override; diff --git a/domain-server/resources/describe-settings.json b/domain-server/resources/describe-settings.json index 32868c9b804..7333aa635b2 100644 --- a/domain-server/resources/describe-settings.json +++ b/domain-server/resources/describe-settings.json @@ -1634,6 +1634,22 @@ "default": false, "advanced": true }, + { + "name": "wantAuditEditLogging", + "type": "checkbox", + "label": "Audit Edit Logging", + "help": "Log edits with audit information.", + "default": false, + "advanced": true + }, + { + "name": "auditEditLoggingInterval", + "label": "Audit Entity Edit Logging Interval", + "help": "Milliseconds between the outputting and clearing of audit logs for entity edits/adds.", + "placeholder": "10000", + "default": "10000", + "advanced": true + }, { "name": "verboseDebug", "type": "checkbox", diff --git a/libraries/entities/src/EntitiesAuditLogging.cpp b/libraries/entities/src/EntitiesAuditLogging.cpp new file mode 100644 index 00000000000..d3ae2f72adc --- /dev/null +++ b/libraries/entities/src/EntitiesAuditLogging.cpp @@ -0,0 +1,88 @@ +// +// EntitiesAuditLogging.cpp +// libraries/entities/src +// +// Created by Kalila L on Feb 5 2021. +// Copyright 2021 Vircadia contributors. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include "EntitiesAuditLogging.h" + +#include +#include + +Q_LOGGING_CATEGORY(entities_audit, "vircadia.entities.audit"); + +EntitiesAuditLogging::~EntitiesAuditLogging() { + EntitiesAuditLogging::stopAuditLogProcessor(); +} + +void EntitiesAuditLogging::processAuditLogBuffers() { + if (!auditLogAddBuffer.isEmpty()) { + QJsonObject objectToOutput; + objectToOutput.insert("add", auditLogAddBuffer); + qCDebug(entities_audit) << objectToOutput; + auditLogAddBuffer = QJsonObject(); + } + if (!auditLogEditBuffer.isEmpty()) { + QJsonObject objectToOutput; + objectToOutput.insert("edit", auditLogEditBuffer); + qCDebug(entities_audit) << objectToOutput; + auditLogEditBuffer = QJsonObject(); + } +} + +void EntitiesAuditLogging::startAuditLogProcessor() { + _auditLogProcessorTimer = new QTimer(); + connect(_auditLogProcessorTimer, &QTimer::timeout, this, &EntitiesAuditLogging::processAuditLogBuffers); + _auditLogProcessorTimer->start(_auditEditLoggingInterval); +} + +void EntitiesAuditLogging::stopAuditLogProcessor() { + if (_auditLogProcessorTimer) { + _auditLogProcessorTimer->stop(); + _auditLogProcessorTimer->deleteLater(); + _auditLogProcessorTimer = nullptr; + } +} + +bool EntitiesAuditLogging::isProcessorRunning() { + if (_auditLogProcessorTimer && _auditLogProcessorTimer != NULL && _auditLogProcessorTimer->isActive()) { + return true; + } else { + return false; + } +} + +void EntitiesAuditLogging::processAddEntityPacket(const QString& sender, const QString& entityID, const QString& entityType) { + QJsonValue findExisting = auditLogAddBuffer.take(sender); + if (!findExisting.isUndefined()) { + QJsonObject existingObject = findExisting.toObject(); + if (!existingObject.contains(entityID)) { + existingObject.insert(entityID, entityType); + } + auditLogAddBuffer.insert(sender, existingObject); + } else { + QJsonObject newEntry{ { entityID, entityType } }; + auditLogAddBuffer.insert(sender, newEntry); + } +} + +void EntitiesAuditLogging::processEditEntityPacket(const QString& sender, const QString& entityID) { + QJsonValue findExisting = auditLogEditBuffer.take(sender); + if (!findExisting.isUndefined()) { + QJsonObject existingObject = findExisting.toObject(); + if (!existingObject.contains(entityID)) { + existingObject.insert(entityID, 1); + } else { + existingObject[entityID] = existingObject[entityID].toInt() + 1; + } + auditLogEditBuffer.insert(sender, existingObject); + } else { + QJsonObject newEntry{ { entityID, 1 } }; + auditLogEditBuffer.insert(sender, newEntry); + } +} \ No newline at end of file diff --git a/libraries/entities/src/EntitiesAuditLogging.h b/libraries/entities/src/EntitiesAuditLogging.h new file mode 100644 index 00000000000..126af281e07 --- /dev/null +++ b/libraries/entities/src/EntitiesAuditLogging.h @@ -0,0 +1,42 @@ +// +// EntitiesAuditLogging.h +// libraries/entities/src +// +// Created by Kalila L on Feb 5 2021. +// Copyright 2021 Vircadia contributors. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#ifndef vircadia_EntitiesAuditLogging_h +#define vircadia_EntitiesAuditLogging_h + +#include +#include +#include + +Q_DECLARE_LOGGING_CATEGORY(entities_audit); + +class EntitiesAuditLogging : public QObject { + Q_OBJECT +public: + virtual ~EntitiesAuditLogging(); + + bool isProcessorRunning(); + void startAuditLogProcessor(); + void stopAuditLogProcessor(); + void setAuditEditLoggingInterval(float interval) { _auditEditLoggingInterval = interval; }; + void processAddEntityPacket(const QString& sender, const QString& entityID, const QString& entityType); + void processEditEntityPacket(const QString& sender, const QString& entityID); + +private: + void processAuditLogBuffers(); + + QJsonObject auditLogAddBuffer; + QJsonObject auditLogEditBuffer; + QTimer* _auditLogProcessorTimer { nullptr }; + float _auditEditLoggingInterval; +}; + +#endif // vircadia_EntitiesAuditLogging_h diff --git a/libraries/entities/src/EntitiesLogging.h b/libraries/entities/src/EntitiesLogging.h index 393c7f0e9ff..12f753e96a7 100644 --- a/libraries/entities/src/EntitiesLogging.h +++ b/libraries/entities/src/EntitiesLogging.h @@ -14,6 +14,6 @@ #include -Q_DECLARE_LOGGING_CATEGORY(entities) +Q_DECLARE_LOGGING_CATEGORY(entities); #endif // hifi_EntitiesLogging_h diff --git a/libraries/entities/src/EntityTree.cpp b/libraries/entities/src/EntityTree.cpp index eeb42626c25..22744a96ccd 100644 --- a/libraries/entities/src/EntityTree.cpp +++ b/libraries/entities/src/EntityTree.cpp @@ -37,6 +37,7 @@ #include "UpdateEntityOperator.h" #include "QVariantGLM.h" #include "EntitiesLogging.h" +#include "EntitiesAuditLogging.h" #include "RecurseOctreeToMapOperator.h" #include "RecurseOctreeToJSONOperator.h" #include "LogHandler.h" @@ -45,6 +46,7 @@ static const quint64 DELETED_ENTITIES_EXTRA_USECS_TO_CONSIDER = USECS_PER_MSEC * 50; const float EntityTree::DEFAULT_MAX_TMP_ENTITY_LIFETIME = 60 * 60; // 1 hour +const float EntityTree::DEFAULT_AUDIT_EDIT_INTERVAL = 10000; // 10 seconds static const QString DOMAIN_UNLIMITED = "domainUnlimited"; EntityTree::EntityTree(bool shouldReaverage) : @@ -65,6 +67,7 @@ EntityTree::~EntityTree() { // TODO: EntityTreeElement::_tree should be raw back pointer. // AND: EntityItem::_element should be a raw back pointer. //eraseAllOctreeElements(false); // KEEP THIS + qCDebug(entities) << "Killing entityTree..."; } void EntityTree::setEntityScriptSourceWhitelist(const QString& entityScriptSourceWhitelist) { @@ -1799,6 +1802,10 @@ void EntityTree::processChallengeOwnershipPacket(ReceivedMessage& message, const } } +void EntityTree::setAuditEditLoggingInterval(float interval) { + entitiesAuditLogProcessor.setAuditEditLoggingInterval(interval); +} + // NOTE: Caller must lock the tree before calling this. int EntityTree::processEditPacketData(ReceivedMessage& message, const unsigned char* editData, int maxLength, const SharedNodePointer& senderNode) { @@ -1807,6 +1814,12 @@ int EntityTree::processEditPacketData(ReceivedMessage& message, const unsigned c return 0; } + if (wantAuditEditLogging() && !entitiesAuditLogProcessor.isProcessorRunning()) { + entitiesAuditLogProcessor.startAuditLogProcessor(); + } + + qDebug() << "PROCESSING" << wantAuditEditLogging() << " - " << entitiesAuditLogProcessor.isProcessorRunning(); + int processedBytes = 0; bool isAdd = false; bool isClone = false; @@ -1833,6 +1846,7 @@ int EntityTree::processEditPacketData(ReceivedMessage& message, const unsigned c quint64 startFilter = 0, endFilter = 0; quint64 startLogging = 0, endLogging = 0; + bool suppressDisallowedClientScript = false; bool suppressDisallowedServerScript = false; bool suppressDisallowedPrivateUserData = false; @@ -2007,6 +2021,10 @@ int EntityTree::processEditPacketData(ReceivedMessage& message, const unsigned c qCDebug(entities) << "User [" << senderNode->getUUID() << "] editing entity. ID:" << entityItemID; qCDebug(entities) << " properties:" << properties; } + if (wantAuditEditLogging()) { + entitiesAuditLogProcessor.processEditEntityPacket(senderNode->getPublicSocket().toString(), + entityItemID.toString()); + } if (wantTerseEditLogging()) { QList changedProperties = properties.listChangedProperties(); fixupTerseEditLogging(properties, changedProperties); @@ -2086,6 +2104,11 @@ int EntityTree::processEditPacketData(ReceivedMessage& message, const unsigned c << newEntity->getEntityItemID(); qCDebug(entities) << " properties:" << properties; } + if (wantAuditEditLogging()) { + entitiesAuditLogProcessor.processAddEntityPacket(senderNode->getPublicSocket().toString(), + entityItemID.toString(), + EntityTypes::getEntityTypeName(properties.getType())); + } if (wantTerseEditLogging()) { QList changedProperties = properties.listChangedProperties(); fixupTerseEditLogging(properties, changedProperties); @@ -2109,7 +2132,6 @@ int EntityTree::processEditPacketData(ReceivedMessage& message, const unsigned c } } - _totalDecodeTime += endDecode - startDecode; _totalLookupTime += endLookup - startLookup; _totalUpdateTime += endUpdate - startUpdate; diff --git a/libraries/entities/src/EntityTree.h b/libraries/entities/src/EntityTree.h index 66e761f7a04..7cf56568b45 100644 --- a/libraries/entities/src/EntityTree.h +++ b/libraries/entities/src/EntityTree.h @@ -20,6 +20,7 @@ #include "AddEntityOperator.h" #include "EntityTreeElement.h" +#include "EntitiesAuditLogging.h" #include "DeleteEntityOperator.h" #include "MovingEntitiesOperator.h" @@ -190,6 +191,11 @@ class EntityTree : public Octree, public SpatialParentTree { bool wantEditLogging() const { return _wantEditLogging; } void setWantEditLogging(bool value) { _wantEditLogging = value; } + + bool wantAuditEditLogging() const { return _wantAuditEditLogging; } + void setWantAuditEditLogging(bool value) { _wantAuditEditLogging = value; } + + void setAuditEditLoggingInterval(float value); bool wantTerseEditLogging() const { return _wantTerseEditLogging; } void setWantTerseEditLogging(bool value) { _wantTerseEditLogging = value; } @@ -250,6 +256,8 @@ class EntityTree : public Octree, public SpatialParentTree { void notifyNewCollisionSoundURL(const QString& newCollisionSoundURL, const EntityItemID& entityID); static const float DEFAULT_MAX_TMP_ENTITY_LIFETIME; + + static const float DEFAULT_AUDIT_EDIT_INTERVAL; QByteArray computeNonce(const EntityItemID& entityID, const QString ownerKey); bool verifyNonce(const EntityItemID& entityID, const QString& nonce); @@ -339,6 +347,8 @@ class EntityTree : public Octree, public SpatialParentTree { EntitySimulationPointer _simulation; bool _wantEditLogging = false; + bool _wantAuditEditLogging { false }; + float _auditEditLoggingInterval { DEFAULT_AUDIT_EDIT_INTERVAL }; bool _wantTerseEditLogging = false; @@ -401,6 +411,8 @@ class EntityTree : public Octree, public SpatialParentTree { bool _serverlessDomain { false }; + EntitiesAuditLogging entitiesAuditLogProcessor; + std::map _namedPaths; // Return an AACube containing object and all its entity descendants