overte/domain-server/src/DomainMetadata.cpp

268 lines
9.8 KiB
C++

//
// DomainMetadata.cpp
// domain-server/src
//
// Created by Zach Pomerantz on 5/25/2016.
// Copyright 2016 High Fidelity, Inc.
// Copyright 2020 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 "DomainMetadata.h"
#include "HTTPConnection.h"
#include <AccountManager.h>
#include <DependencyManager.h>
#include <HifiConfigVariantMap.h>
#include <LimitedNodeList.h>
#include <QLoggingCategory>
#include "DomainServer.h"
#include "DomainServerNodeData.h"
Q_LOGGING_CATEGORY(domain_metadata_exporter, "hifi.domain_server.metadata_exporter")
const QString DomainMetadata::USERS = "users";
const QString DomainMetadata::Users::NUM_TOTAL = "num_users";
const QString DomainMetadata::Users::NUM_ANON = "num_anon_users";
const QString DomainMetadata::Users::HOSTNAMES = "user_hostnames";
// users metadata will appear as (JSON):
// { "num_users": Number,
// "num_anon_users": Number,
// "user_hostnames": { <HOSTNAME>: Number }
// }
const QString DomainMetadata::DESCRIPTORS = "descriptors";
const QString DomainMetadata::Descriptors::NAME = "world_name";
const QString DomainMetadata::Descriptors::DESCRIPTION = "description";
const QString DomainMetadata::Descriptors::THUMBNAIL = "thumbnail";
const QString DomainMetadata::Descriptors::IMAGES = "images";
const QString DomainMetadata::Descriptors::CAPACITY = "capacity"; // parsed from security
const QString DomainMetadata::Descriptors::RESTRICTION = "restriction"; // parsed from ACL
const QString DomainMetadata::Descriptors::MATURITY = "maturity";
const QString DomainMetadata::Descriptors::CONTACT = "contact_info";
const QString DomainMetadata::Descriptors::MANAGERS = "managers";
const QString DomainMetadata::Descriptors::TAGS = "tags";
// descriptors metadata will appear as (JSON):
// {
// "world_name": String, // capped name
// "description": String, // capped description
// "thumbnail": String, // capped thumbnail URL
// "images": [ String ], // capped list of image URLs
// "capacity": Number,
// "restriction": String, // enum of either open, hifi, or acl
// "maturity": String, // enum corresponding to ESRB ratings
// "contact_info": [ String ], // capped list of usernames
// "managers": [ String ], // capped list of usernames
// "tags": [ String ], // capped list of tags
// }
// metadata will appear as (JSON):
// { users: <USERS>, descriptors: <DESCRIPTORS> }
//
// it is meant to be sent to and consumed by an external API
DomainMetadata::DomainMetadata(QObject* domainServer) : QObject(domainServer) {
// set up the structure necessary for casting during parsing
_metadata[USERS] = QVariantMap {};
_metadata[DESCRIPTORS] = QVariantMap {};
// initialize the descriptors
securityChanged(false);
descriptorsChanged();
}
QJsonObject DomainMetadata::get() {
maybeUpdateUsers();
return QJsonObject::fromVariantMap(_metadata);
}
QJsonObject DomainMetadata::get(const QString& group) {
maybeUpdateUsers();
return QJsonObject::fromVariantMap(_metadata[group].toMap());
}
void DomainMetadata::descriptorsChanged() {
// get descriptors
assert(_metadata[DESCRIPTORS].canConvert<QVariantMap>());
auto& state = *static_cast<QVariantMap*>(_metadata[DESCRIPTORS].data());
static const QString DESCRIPTORS_GROUP_KEYPATH = "descriptors";
auto descriptorsMap = static_cast<DomainServer*>(parent())->_settingsManager.valueForKeyPath(DESCRIPTORS).toMap();
// copy simple descriptors
if (!descriptorsMap[Descriptors::NAME].isNull()) {
state[Descriptors::NAME] = descriptorsMap[Descriptors::NAME];
} else {
state[Descriptors::NAME] = "";
}
if (!descriptorsMap[Descriptors::DESCRIPTION].isNull()) {
state[Descriptors::DESCRIPTION] = descriptorsMap[Descriptors::DESCRIPTION];
} else {
state[Descriptors::DESCRIPTION] = "";
}
if (!descriptorsMap[Descriptors::THUMBNAIL].isNull()) {
state[Descriptors::THUMBNAIL] = descriptorsMap[Descriptors::THUMBNAIL];
} else {
state[Descriptors::THUMBNAIL] = "";
}
if (!descriptorsMap[Descriptors::MATURITY].isNull()) {
state[Descriptors::MATURITY] = descriptorsMap[Descriptors::MATURITY];
} else {
state[Descriptors::MATURITY] = "";
}
if (!descriptorsMap[Descriptors::CONTACT].isNull()) {
state[Descriptors::CONTACT] = descriptorsMap[Descriptors::CONTACT];
} else {
state[Descriptors::CONTACT] = "";
}
// copy array descriptors
state[Descriptors::IMAGES] = descriptorsMap[Descriptors::IMAGES].toList();
state[Descriptors::MANAGERS] = descriptorsMap[Descriptors::MANAGERS].toList();
state[Descriptors::TAGS] = descriptorsMap[Descriptors::TAGS].toList();
// parse capacity
static const QString CAPACITY = "security.maximum_user_capacity";
QVariant capacityVariant = static_cast<DomainServer*>(parent())->_settingsManager.valueForKeyPath(CAPACITY);
unsigned int capacity = capacityVariant.isValid() ? capacityVariant.toUInt() : 0;
state[Descriptors::CAPACITY] = capacity;
#if DEV_BUILD || PR_BUILD
qDebug() << "Domain metadata descriptors set:" << QJsonObject::fromVariantMap(_metadata[DESCRIPTORS].toMap());
#endif
sendDescriptors();
}
void DomainMetadata::securityChanged(bool send) {
// get descriptors
assert(_metadata[DESCRIPTORS].canConvert<QVariantMap>());
auto& state = *static_cast<QVariantMap*>(_metadata[DESCRIPTORS].data());
const QString RESTRICTION_OPEN = "open";
const QString RESTRICTION_HIFI = "hifi";
const QString RESTRICTION_ACL = "acl";
QString restriction;
const auto& settingsManager = static_cast<DomainServer*>(parent())->_settingsManager;
bool hasAnonymousAccess = settingsManager.getStandardPermissionsForName(NodePermissions::standardNameAnonymous).can(
NodePermissions::Permission::canConnectToDomain);
bool hasHifiAccess = settingsManager.getStandardPermissionsForName(NodePermissions::standardNameLoggedIn).can(
NodePermissions::Permission::canConnectToDomain);
if (hasAnonymousAccess) {
restriction = RESTRICTION_OPEN;
} else if (hasHifiAccess) {
restriction = RESTRICTION_HIFI;
} else {
restriction = RESTRICTION_ACL;
}
state[Descriptors::RESTRICTION] = restriction;
#if DEV_BUILD || PR_BUILD
qDebug() << "Domain metadata restriction set:" << restriction;
#endif
if (send) {
sendDescriptors();
}
}
void DomainMetadata::usersChanged() {
++_tic;
#if DEV_BUILD || PR_BUILD
qDebug() << "Domain metadata users change detected";
#endif
}
void DomainMetadata::maybeUpdateUsers() {
if (_lastTic == _tic) {
return;
}
_lastTic = _tic;
static const QString DEFAULT_HOSTNAME = "*";
auto nodeList = DependencyManager::get<LimitedNodeList>();
int numConnected = 0;
int numConnectedAnonymously = 0;
QVariantMap userHostnames;
// figure out the breakdown of currently connected interface clients
nodeList->eachNode([&numConnected, &numConnectedAnonymously, &userHostnames](const SharedNodePointer& node) {
auto linkedData = node->getLinkedData();
if (linkedData) {
auto nodeData = static_cast<DomainServerNodeData*>(linkedData);
if (!nodeData->wasAssigned() && node->getType() == NodeType::Agent) {
++numConnected;
if (nodeData->getUsername().isEmpty()) {
++numConnectedAnonymously;
}
// increment the count for this hostname (or the default if we don't have one)
auto placeName = nodeData->getPlaceName();
auto hostname = placeName.isEmpty() ? DEFAULT_HOSTNAME : placeName;
userHostnames[hostname] = userHostnames[hostname].toInt() + 1;
}
}
});
assert(_metadata[USERS].canConvert<QVariantMap>());
auto& users = *static_cast<QVariantMap*>(_metadata[USERS].data());
users[Users::NUM_TOTAL] = numConnected;
users[Users::NUM_ANON] = numConnectedAnonymously;
users[Users::HOSTNAMES] = userHostnames;
#if DEV_BUILD || PR_BUILD
qDebug() << "Domain metadata users set:" << QJsonObject::fromVariantMap(_metadata[USERS].toMap());
#endif
}
void DomainMetadata::sendDescriptors() {
QString domainUpdateJSON = QString("{\"domain\":{\"meta\":%1}}").arg(QString(QJsonDocument(get(DESCRIPTORS)).toJson(QJsonDocument::Compact)));
const QUuid& domainID = DependencyManager::get<LimitedNodeList>()->getSessionUUID();
if (!domainID.isNull()) {
static const QString DOMAIN_UPDATE = "/api/v1/domains/%1";
QString path { DOMAIN_UPDATE.arg(uuidStringWithoutCurlyBraces(domainID)) };
DependencyManager::get<AccountManager>()->sendRequest(path,
AccountManagerAuth::Required,
QNetworkAccessManager::PutOperation,
JSONCallbackParameters(),
domainUpdateJSON.toUtf8());
#if DEV_BUILD || PR_BUILD
qDebug() << "Domain metadata sent to" << path;
qDebug() << "Domain metadata update:" << domainUpdateJSON;
#endif
}
}
bool DomainMetadata::handleHTTPRequest(HTTPConnection* connection, const QUrl& url, bool skipSubHandler) {
QString domainMetadataJSON = QString("{\"domain\":{\"meta\":%1}, \"users\":%2}")
.arg(QString(QJsonDocument(get(DESCRIPTORS)).toJson(QJsonDocument::Compact)))
.arg(QString(QJsonDocument(get(USERS)).toJson(QJsonDocument::Compact)));
const QString URI_METADATA = "/metadata";
const QString EXPORTER_MIME_TYPE = "application/json";
if (url.path() == URI_METADATA) {
connection->respond(HTTPConnection::StatusCode200, domainMetadataJSON.toUtf8(), qPrintable(EXPORTER_MIME_TYPE));
return true;
}
#if DEV_BUILD || PR_BUILD
qCDebug(domain_metadata_exporter) << "Metadata request on URL " << url;
#endif
return false;
}