// // DomainMetadata.cpp // domain-server/src // // Created by Zach Pomerantz on 5/25/2016. // Copyright 2016 High Fidelity, Inc. // // 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 #include #include #include #include "DomainServer.h" #include "DomainServerNodeData.h" 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": { : Number } // } const QString DomainMetadata::DESCRIPTORS = "descriptors"; const QString DomainMetadata::Descriptors::DESCRIPTION = "description"; 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::HOSTS = "hosts"; const QString DomainMetadata::Descriptors::TAGS = "tags"; const QString DomainMetadata::Descriptors::HOURS = "hours"; const QString DomainMetadata::Descriptors::Hours::WEEKDAY = "weekday"; const QString DomainMetadata::Descriptors::Hours::WEEKEND = "weekend"; const QString DomainMetadata::Descriptors::Hours::UTC_OFFSET = "utc_offset"; const QString DomainMetadata::Descriptors::Hours::OPEN = "open"; const QString DomainMetadata::Descriptors::Hours::CLOSE = "close"; // descriptors metadata will appear as (JSON): // { "description": String, // capped description // "capacity": Number, // "restriction": String, // enum of either open, hifi, or acl // "maturity": String, // enum corresponding to ESRB ratings // "hosts": [ String ], // capped list of usernames // "tags": [ String ], // capped list of tags // "hours": { // "utc_offset": Number, // "weekday": [ [ Time, Time ] ], // "weekend": [ [ Time, Time ] ], // } // } // metadata will appear as (JSON): // { users: , descriptors: } // // it is meant to be sent to and consumed by an external API // merge delta into target // target should be of the form [ OpenTime, CloseTime ], // delta should be of the form [ { open: Time, close: Time } ] void parseHours(QVariant delta, QVariant& target) { using Hours = DomainMetadata::Descriptors::Hours; static const QVariantList DEFAULT_HOURS{ { QVariantList{ "00:00", "23:59" } } }; target.setValue(DEFAULT_HOURS); if (!delta.canConvert()) { return; } auto& deltaList = *static_cast(delta.data()); if (deltaList.isEmpty()) { return; } auto& deltaHours = *static_cast(deltaList.first().data()); auto open = deltaHours.find(Hours::OPEN); auto close = deltaHours.find(Hours::CLOSE); if (open == deltaHours.end() || close == deltaHours.end()) { return; } // merge delta into new hours static const int OPEN_INDEX = 0; static const int CLOSE_INDEX = 1; auto& hours = *static_cast(static_cast(target.data())->first().data()); hours[OPEN_INDEX] = open.value(); hours[CLOSE_INDEX] = close.value(); assert(hours[OPEN_INDEX].canConvert()); assert(hours[CLOSE_INDEX].canConvert()); } DomainMetadata::DomainMetadata(QObject* domainServer) : QObject(domainServer) { // set up the structure necessary for casting during parsing (see parseHours, esp.) _metadata[USERS] = QVariantMap {}; _metadata[DESCRIPTORS] = QVariantMap { { Descriptors::HOURS, QVariantMap { { Descriptors::Hours::WEEKDAY, QVariant{} }, { Descriptors::Hours::WEEKEND, QVariant{} } } } }; assert(dynamic_cast(domainServer)); DomainServer* server = static_cast(domainServer); // update the metadata when a user (dis)connects connect(server, &DomainServer::userConnected, this, &DomainMetadata::usersChanged); connect(server, &DomainServer::userDisconnected, this, &DomainMetadata::usersChanged); // update the metadata when security changes connect(&server->_settingsManager, &DomainServerSettingsManager::updateNodePermissions, this, static_cast(&DomainMetadata::securityChanged)); // 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()); auto& state = *static_cast(_metadata[DESCRIPTORS].data()); auto& settings = static_cast(parent())->_settingsManager.getSettingsMap(); auto& descriptors = static_cast(parent())->_settingsManager.getDescriptorsMap(); // copy simple descriptors (description/maturity) state[Descriptors::DESCRIPTION] = descriptors[Descriptors::DESCRIPTION]; state[Descriptors::MATURITY] = descriptors[Descriptors::MATURITY]; // copy array descriptors (hosts/tags) state[Descriptors::HOSTS] = descriptors[Descriptors::HOSTS].toList(); state[Descriptors::TAGS] = descriptors[Descriptors::TAGS].toList(); // parse capacity static const QString CAPACITY = "security.maximum_user_capacity"; const QVariant* capacityVariant = valueForKeyPath(settings, CAPACITY); unsigned int capacity = capacityVariant ? capacityVariant->toUInt() : 0; state[Descriptors::CAPACITY] = capacity; // parse operating hours static const QString WEEKDAY_HOURS = "weekday_hours"; static const QString WEEKEND_HOURS = "weekend_hours"; static const QString UTC_OFFSET = "utc_offset"; assert(state[Descriptors::HOURS].canConvert()); auto& hours = *static_cast(state[Descriptors::HOURS].data()); hours[Descriptors::Hours::UTC_OFFSET] = descriptors.take(UTC_OFFSET); parseHours(descriptors[WEEKDAY_HOURS], hours[Descriptors::Hours::WEEKDAY]); parseHours(descriptors[WEEKEND_HOURS], hours[Descriptors::Hours::WEEKEND]); #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()); auto& state = *static_cast(_metadata[DESCRIPTORS].data()); const QString RESTRICTION_OPEN = "open"; const QString RESTRICTION_ANON = "anon"; const QString RESTRICTION_HIFI = "hifi"; const QString RESTRICTION_ACL = "acl"; QString restriction; const auto& settingsManager = static_cast(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 = hasHifiAccess ? RESTRICTION_OPEN : RESTRICTION_ANON; } 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(); 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(linkedData); if (!nodeData->wasAssigned()) { ++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()); auto& users = *static_cast(_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\":%1}").arg(QString(QJsonDocument(get(DESCRIPTORS)).toJson(QJsonDocument::Compact))); const QUuid& domainID = DependencyManager::get()->getSessionUUID(); if (!domainID.isNull()) { static const QString DOMAIN_UPDATE = "/api/v1/domains/%1"; QString path { DOMAIN_UPDATE.arg(uuidStringWithoutCurlyBraces(domainID)) }; DependencyManager::get()->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 } }