diff --git a/README.md b/README.md index ab1212f656..d5fa8de1b4 100644 --- a/README.md +++ b/README.md @@ -39,7 +39,7 @@ you to run the full stack of the virtual world. In order to set up your own virtual world, you need to set up and run your own local "domain". -The domain-server gives a number different types of assignments to the assignment-client for different features: audio, avatars, voxels, particles, and meta-voxels. +The domain-server gives a number different types of assignments to the assignment-client for different features: audio, avatars, voxels, particles, meta-voxels and models. Follow the instructions in the [build guide](BUILD.md) to build the various components. @@ -57,7 +57,7 @@ Any target can be terminated with Ctrl-C (SIGINT) in the associated Terminal win This assignment-client will grab one assignment from the domain-server. You can tell the assignment-client what type you want it to be with the `-t` option. You can also run an assignment-client that forks off *n* assignment-clients with the `-n` option. - ./assignment-client -n 5 + ./assignment-client -n 6 To test things out you'll want to run the Interface client. diff --git a/assignment-client/src/Agent.cpp b/assignment-client/src/Agent.cpp index 184083b28e..9b2134ba44 100644 --- a/assignment-client/src/Agent.cpp +++ b/assignment-client/src/Agent.cpp @@ -191,9 +191,10 @@ void Agent::run() { // figure out the URL for the script for this agent assignment QUrl scriptURL; if (_payload.isEmpty()) { - scriptURL = QUrl(QString("http://%1:8080/assignment/%2") - .arg(NodeList::getInstance()->getDomainHandler().getIP().toString(), - uuidStringWithoutCurlyBraces(_uuid))); + scriptURL = QUrl(QString("http://%1:%2/assignment/%3") + .arg(NodeList::getInstance()->getDomainHandler().getIP().toString()) + .arg(DOMAIN_SERVER_HTTP_PORT) + .arg(uuidStringWithoutCurlyBraces(_uuid))); } else { scriptURL = QUrl(_payload); } diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index 77d5afffd7..d9927f3833 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -16,6 +16,7 @@ #include #include #include +#include #include @@ -31,18 +32,22 @@ #include "DomainServer.h" -const quint16 DOMAIN_SERVER_HTTP_PORT = 8080; - DomainServer::DomainServer(int argc, char* argv[]) : QCoreApplication(argc, argv), - _HTTPManager(DOMAIN_SERVER_HTTP_PORT, QString("%1/resources/web/").arg(QCoreApplication::applicationDirPath()), this), - _staticAssignmentHash(), - _assignmentQueue(), + _httpManager(DOMAIN_SERVER_HTTP_PORT, QString("%1/resources/web/").arg(QCoreApplication::applicationDirPath()), this), + _httpsManager(NULL), + _allAssignments(), + _unfulfilledAssignments(), _isUsingDTLS(false), _x509Credentials(NULL), _dhParams(NULL), _priorityCache(NULL), - _dtlsSessions() + _dtlsSessions(), + _oauthProviderURL(), + _oauthClientID(), + _hostname(), + _networkReplyUUIDMap(), + _sessionAuthenticationHash() { gnutls_global_init(); @@ -53,22 +58,20 @@ DomainServer::DomainServer(int argc, char* argv[]) : _argumentVariantMap = HifiConfigVariantMap::mergeCLParametersWithJSONConfig(arguments()); - if (optionallySetupDTLS()) { + if (optionallyReadX509KeyAndCertificate() && optionallySetupOAuth()) { // we either read a certificate and private key or were not passed one, good to load assignments // and set up the node list qDebug() << "Setting up LimitedNodeList and assignments."; setupNodeListAndAssignments(); if (_isUsingDTLS) { - // we're using DTLS and our NodeList socket is good to go, so make the required DTLS changes - // DTLS requires that IP_DONTFRAG be set - // This is not accessible on some platforms (OS X) so we need to make sure DTLS still works without it - LimitedNodeList* nodeList = LimitedNodeList::getInstance(); // connect our socket to read datagrams received on the DTLS socket connect(&nodeList->getDTLSSocket(), &QUdpSocket::readyRead, this, &DomainServer::readAvailableDTLSDatagrams); } + + _networkAccessManager = new QNetworkAccessManager(this); } } @@ -86,44 +89,7 @@ DomainServer::~DomainServer() { gnutls_global_deinit(); } -bool DomainServer::optionallySetupDTLS() { - if (readX509KeyAndCertificate()) { - if (_x509Credentials) { - qDebug() << "Generating Diffie-Hellman parameters."; - - // generate Diffie-Hellman parameters - // When short bit length is used, it might be wise to regenerate parameters often. - int dhBits = gnutls_sec_param_to_pk_bits(GNUTLS_PK_DH, GNUTLS_SEC_PARAM_LEGACY); - - _dhParams = new gnutls_dh_params_t; - gnutls_dh_params_init(_dhParams); - gnutls_dh_params_generate2(*_dhParams, dhBits); - - qDebug() << "Successfully generated Diffie-Hellman parameters."; - - // set the D-H paramters on the X509 credentials - gnutls_certificate_set_dh_params(*_x509Credentials, *_dhParams); - - // setup the key used for cookie verification - _cookieKey = new gnutls_datum_t; - gnutls_key_generate(_cookieKey, GNUTLS_COOKIE_KEY_SIZE); - - _priorityCache = new gnutls_priority_t; - const char DTLS_PRIORITY_STRING[] = "PERFORMANCE:-VERS-TLS-ALL:+VERS-DTLS1.2:%SERVER_PRECEDENCE"; - gnutls_priority_init(_priorityCache, DTLS_PRIORITY_STRING, NULL); - - _isUsingDTLS = true; - - qDebug() << "Initial DTLS setup complete."; - } - - return true; - } else { - return false; - } -} - -bool DomainServer::readX509KeyAndCertificate() { +bool DomainServer::optionallyReadX509KeyAndCertificate() { const QString X509_CERTIFICATE_OPTION = "cert"; const QString X509_PRIVATE_KEY_OPTION = "key"; const QString X509_KEY_PASSPHRASE_ENV = "DOMAIN_SERVER_KEY_PASSPHRASE"; @@ -157,6 +123,22 @@ bool DomainServer::readX509KeyAndCertificate() { qDebug() << "Successfully read certificate and private key."; + // we need to also pass this certificate and private key to the HTTPS manager + // this is used for Oauth callbacks when authorizing users against a data server + + QFile certFile(certPath); + certFile.open(QIODevice::ReadOnly); + + QFile keyFile(keyPath); + keyFile.open(QIODevice::ReadOnly); + + QSslCertificate sslCertificate(&certFile); + QSslKey privateKey(&keyFile, QSsl::Rsa, QSsl::Pem, QSsl::PrivateKey, keyPassphraseString.toUtf8()); + + _httpsManager = new HTTPSManager(DOMAIN_SERVER_HTTPS_PORT, sslCertificate, privateKey, QString(), this, this); + + qDebug() << "TCP server listening for HTTPS connections on" << DOMAIN_SERVER_HTTPS_PORT; + } else if (!certPath.isEmpty() || !keyPath.isEmpty()) { qDebug() << "Missing certificate or private key. domain-server will now quit."; QMetaObject::invokeMethod(this, "quit", Qt::QueuedConnection); @@ -166,6 +148,67 @@ bool DomainServer::readX509KeyAndCertificate() { return true; } +bool DomainServer::optionallySetupOAuth() { + const QString OAUTH_PROVIDER_URL_OPTION = "oauth-provider"; + const QString OAUTH_CLIENT_ID_OPTION = "oauth-client-id"; + const QString OAUTH_CLIENT_SECRET_ENV = "DOMAIN_SERVER_CLIENT_SECRET"; + const QString REDIRECT_HOSTNAME_OPTION = "hostname"; + + _oauthProviderURL = QUrl(_argumentVariantMap.value(OAUTH_PROVIDER_URL_OPTION).toString()); + _oauthClientID = _argumentVariantMap.value(OAUTH_CLIENT_ID_OPTION).toString(); + _oauthClientSecret = QProcessEnvironment::systemEnvironment().value(OAUTH_CLIENT_SECRET_ENV); + _hostname = _argumentVariantMap.value(REDIRECT_HOSTNAME_OPTION).toString(); + + if (!_oauthProviderURL.isEmpty() || !_hostname.isEmpty() || !_oauthClientID.isEmpty()) { + if (_oauthProviderURL.isEmpty() + || _hostname.isEmpty() + || _oauthClientID.isEmpty() + || _oauthClientSecret.isEmpty()) { + qDebug() << "Missing OAuth provider URL, hostname, client ID, or client secret. domain-server will now quit."; + QMetaObject::invokeMethod(this, "quit", Qt::QueuedConnection); + return false; + } else { + qDebug() << "OAuth will be used to identify clients using provider at" << _oauthProviderURL.toString(); + qDebug() << "OAuth Client ID is" << _oauthClientID; + } + } + + return true; +} + +bool DomainServer::optionallySetupDTLS() { + if (_x509Credentials) { + qDebug() << "Generating Diffie-Hellman parameters."; + + // generate Diffie-Hellman parameters + // When short bit length is used, it might be wise to regenerate parameters often. + int dhBits = gnutls_sec_param_to_pk_bits(GNUTLS_PK_DH, GNUTLS_SEC_PARAM_LEGACY); + + _dhParams = new gnutls_dh_params_t; + gnutls_dh_params_init(_dhParams); + gnutls_dh_params_generate2(*_dhParams, dhBits); + + qDebug() << "Successfully generated Diffie-Hellman parameters."; + + // set the D-H paramters on the X509 credentials + gnutls_certificate_set_dh_params(*_x509Credentials, *_dhParams); + + // setup the key used for cookie verification + _cookieKey = new gnutls_datum_t; + gnutls_key_generate(_cookieKey, GNUTLS_COOKIE_KEY_SIZE); + + _priorityCache = new gnutls_priority_t; + const char DTLS_PRIORITY_STRING[] = "PERFORMANCE:-VERS-TLS-ALL:+VERS-DTLS1.2:%SERVER_PRECEDENCE"; + gnutls_priority_init(_priorityCache, DTLS_PRIORITY_STRING, NULL); + + _isUsingDTLS = true; + + qDebug() << "Initial DTLS setup complete."; + } + + return true; +} + void DomainServer::setupNodeListAndAssignments(const QUuid& sessionUUID) { const QString CUSTOM_PORT_OPTION = "port"; @@ -246,7 +289,8 @@ void DomainServer::parseAssignmentConfigs(QSet& excludedTypes) void DomainServer::addStaticAssignmentToAssignmentHash(Assignment* newAssignment) { qDebug() << "Inserting assignment" << *newAssignment << "to static assignment hash."; - _staticAssignmentHash.insert(newAssignment->getUUID(), SharedAssignmentPointer(newAssignment)); + newAssignment->setIsStatic(true); + _allAssignments.insert(newAssignment->getUUID(), SharedAssignmentPointer(newAssignment)); } void DomainServer::createScriptedAssignmentsFromArray(const QJsonArray &configArray) { @@ -279,7 +323,9 @@ void DomainServer::createScriptedAssignmentsFromArray(const QJsonArray &configAr qDebug() << "URL for script is" << assignmentURL; // scripts passed on CL or via JSON are static - so they are added back to the queue if the node dies - _assignmentQueue.enqueue(SharedAssignmentPointer(scriptAssignment)); + SharedAssignmentPointer sharedScriptAssignment(scriptAssignment); + _unfulfilledAssignments.enqueue(sharedScriptAssignment); + _allAssignments.insert(sharedScriptAssignment->getUUID(), sharedScriptAssignment); } } } @@ -340,47 +386,71 @@ void DomainServer::populateDefaultStaticAssignmentsExcludingTypes(const QSetwriteUnverifiedDatagram(oauthRequestByteArray, senderSockAddr); + + return; + } + } + + if ((!isFulfilledOrUnfulfilledAssignment && !STATICALLY_ASSIGNED_NODES.contains(nodeType)) + || (isFulfilledOrUnfulfilledAssignment && matchingQueuedAssignment)) { + // this was either not a static assignment or it was and we had a matching one in the queue + // create a new session UUID for this node QUuid nodeUUID = QUuid::createUuid(); SharedNodePointer newNode = LimitedNodeList::getInstance()->addOrUpdateNode(nodeUUID, nodeType, publicSockAddr, localSockAddr); - // when the newNode is created the linked data is also created // if this was a static assignment set the UUID, set the sendingSockAddr DomainServerNodeData* nodeData = reinterpret_cast(newNode->getLinkedData()); - nodeData->setStaticAssignmentUUID(assignmentUUID); + if (isFulfilledOrUnfulfilledAssignment) { + nodeData->setAssignmentUUID(packetUUID); + } + nodeData->setSendingSockAddr(senderSockAddr); // reply back to the user with a PacketTypeDomainList @@ -388,6 +458,40 @@ void DomainServer::addNodeToNodeListAndConfirmConnection(const QByteArray& packe } } +QUrl DomainServer::oauthRedirectURL() { + return QString("https://%1:%2/oauth").arg(_hostname).arg(_httpsManager->serverPort()); +} + +const QString OAUTH_CLIENT_ID_QUERY_KEY = "client_id"; +const QString OAUTH_REDIRECT_URI_QUERY_KEY = "redirect_uri"; + +QUrl DomainServer::oauthAuthorizationURL(const QUuid& stateUUID) { + // for now these are all interface clients that have a GUI + // so just send them back the full authorization URL + QUrl authorizationURL = _oauthProviderURL; + + const QString OAUTH_AUTHORIZATION_PATH = "/oauth/authorize"; + authorizationURL.setPath(OAUTH_AUTHORIZATION_PATH); + + QUrlQuery authorizationQuery; + + authorizationQuery.addQueryItem(OAUTH_CLIENT_ID_QUERY_KEY, _oauthClientID); + + const QString OAUTH_RESPONSE_TYPE_QUERY_KEY = "response_type"; + const QString OAUTH_REPSONSE_TYPE_QUERY_VALUE = "code"; + authorizationQuery.addQueryItem(OAUTH_RESPONSE_TYPE_QUERY_KEY, OAUTH_REPSONSE_TYPE_QUERY_VALUE); + + const QString OAUTH_STATE_QUERY_KEY = "state"; + // create a new UUID that will be the state parameter for oauth authorization AND the new session UUID for that node + authorizationQuery.addQueryItem(OAUTH_STATE_QUERY_KEY, uuidStringWithoutCurlyBraces(stateUUID)); + + authorizationQuery.addQueryItem(OAUTH_REDIRECT_URI_QUERY_KEY, oauthRedirectURL().toString()); + + authorizationURL.setQuery(authorizationQuery); + + return authorizationURL; +} + int DomainServer::parseNodeDataFromByteArray(NodeType_t& nodeType, HifiSockAddr& publicSockAddr, HifiSockAddr& localSockAddr, const QByteArray& packet, const HifiSockAddr& senderSockAddr) { @@ -451,52 +555,54 @@ void DomainServer::sendDomainListToNode(const SharedNodePointer& node, const Hif DTLSServerSession* dtlsSession = _isUsingDTLS ? _dtlsSessions[senderSockAddr] : NULL; int dataMTU = dtlsSession ? (int)gnutls_dtls_get_data_mtu(*dtlsSession->getGnuTLSSession()) : MAX_PACKET_SIZE; - // if the node has any interest types, send back those nodes as well - foreach (const SharedNodePointer& otherNode, nodeList->getNodeHash()) { - - // reset our nodeByteArray and nodeDataStream - QByteArray nodeByteArray; - QDataStream nodeDataStream(&nodeByteArray, QIODevice::Append); - - if (otherNode->getUUID() != node->getUUID() && nodeInterestList.contains(otherNode->getType())) { + if (nodeData->isAuthenticated()) { + // if this authenticated node has any interest types, send back those nodes as well + foreach (const SharedNodePointer& otherNode, nodeList->getNodeHash()) { - // don't send avatar nodes to other avatars, that will come from avatar mixer - nodeDataStream << *otherNode.data(); + // reset our nodeByteArray and nodeDataStream + QByteArray nodeByteArray; + QDataStream nodeDataStream(&nodeByteArray, QIODevice::Append); - // pack the secret that these two nodes will use to communicate with each other - QUuid secretUUID = nodeData->getSessionSecretHash().value(otherNode->getUUID()); - if (secretUUID.isNull()) { - // generate a new secret UUID these two nodes can use - secretUUID = QUuid::createUuid(); + if (otherNode->getUUID() != node->getUUID() && nodeInterestList.contains(otherNode->getType())) { - // set that on the current Node's sessionSecretHash - nodeData->getSessionSecretHash().insert(otherNode->getUUID(), secretUUID); + // don't send avatar nodes to other avatars, that will come from avatar mixer + nodeDataStream << *otherNode.data(); - // set it on the other Node's sessionSecretHash - reinterpret_cast(otherNode->getLinkedData()) + // pack the secret that these two nodes will use to communicate with each other + QUuid secretUUID = nodeData->getSessionSecretHash().value(otherNode->getUUID()); + if (secretUUID.isNull()) { + // generate a new secret UUID these two nodes can use + secretUUID = QUuid::createUuid(); + + // set that on the current Node's sessionSecretHash + nodeData->getSessionSecretHash().insert(otherNode->getUUID(), secretUUID); + + // set it on the other Node's sessionSecretHash + reinterpret_cast(otherNode->getLinkedData()) ->getSessionSecretHash().insert(node->getUUID(), secretUUID); - - } - - nodeDataStream << secretUUID; - - if (broadcastPacket.size() + nodeByteArray.size() > dataMTU) { - // we need to break here and start a new packet - // so send the current one - - if (!dtlsSession) { - nodeList->writeDatagram(broadcastPacket, node, senderSockAddr); - } else { - dtlsSession->writeDatagram(broadcastPacket); + } - // reset the broadcastPacket structure - broadcastPacket.resize(numBroadcastPacketLeadBytes); - broadcastDataStream.device()->seek(numBroadcastPacketLeadBytes); + nodeDataStream << secretUUID; + + if (broadcastPacket.size() + nodeByteArray.size() > dataMTU) { + // we need to break here and start a new packet + // so send the current one + + if (!dtlsSession) { + nodeList->writeDatagram(broadcastPacket, node, senderSockAddr); + } else { + dtlsSession->writeDatagram(broadcastPacket); + } + + // reset the broadcastPacket structure + broadcastPacket.resize(numBroadcastPacketLeadBytes); + broadcastDataStream.device()->seek(numBroadcastPacketLeadBytes); + } + + // append the nodeByteArray to the current state of broadcastDataStream + broadcastPacket.append(nodeByteArray); } - - // append the nodeByteArray to the current state of broadcastDataStream - broadcastPacket.append(nodeByteArray); } } @@ -670,9 +776,7 @@ void DomainServer::processDatagram(const QByteArray& receivedPacket, const HifiS PacketType requestType = packetTypeForPacket(receivedPacket); if (requestType == PacketTypeDomainConnectRequest) { - // add this node to our NodeList - // and send back session UUID right away - addNodeToNodeListAndConfirmConnection(receivedPacket, senderSockAddr); + handleConnectRequest(receivedPacket, senderSockAddr); } else if (requestType == PacketTypeDomainListRequest) { QUuid nodeUUID = uuidFromPacketHeader(receivedPacket); @@ -738,7 +842,8 @@ QJsonObject DomainServer::jsonObjectForNode(const SharedNodePointer& node) { nodeJson[JSON_KEY_WAKE_TIMESTAMP] = QString::number(node->getWakeTimestamp()); // if the node has pool information, add it - SharedAssignmentPointer matchingAssignment = _staticAssignmentHash.value(node->getUUID()); + DomainServerNodeData* nodeData = reinterpret_cast(node->getLinkedData()); + SharedAssignmentPointer matchingAssignment = _allAssignments.value(nodeData->getAssignmentUUID()); if (matchingAssignment) { nodeJson[JSON_KEY_POOL] = matchingAssignment->getPool(); } @@ -774,9 +879,11 @@ bool DomainServer::handleHTTPRequest(HTTPConnection* connection, const QUrl& url // enumerate the NodeList to find the assigned nodes foreach (const SharedNodePointer& node, LimitedNodeList::getInstance()->getNodeHash()) { - if (_staticAssignmentHash.value(node->getUUID())) { + DomainServerNodeData* nodeData = reinterpret_cast(node->getLinkedData()); + + if (!nodeData->getAssignmentUUID().isNull()) { // add the node using the UUID as the key - QString uuidString = uuidStringWithoutCurlyBraces(node->getUUID()); + QString uuidString = uuidStringWithoutCurlyBraces(nodeData->getAssignmentUUID()); assignedNodesJSON[uuidString] = jsonObjectForNode(node); } } @@ -786,7 +893,7 @@ bool DomainServer::handleHTTPRequest(HTTPConnection* connection, const QUrl& url QJsonObject queuedAssignmentsJSON; // add the queued but unfilled assignments to the json - foreach(const SharedAssignmentPointer& assignment, _assignmentQueue) { + foreach(const SharedAssignmentPointer& assignment, _unfulfilledAssignments) { QJsonObject queuedAssignmentJSON; QString uuidString = uuidStringWithoutCurlyBraces(assignment->getUUID()); @@ -903,7 +1010,9 @@ bool DomainServer::handleHTTPRequest(HTTPConnection* connection, const QUrl& url .arg(newPath).arg(assignmentPool == emptyPool ? "" : " - pool is " + assignmentPool)); // add the script assigment to the assignment queue - _assignmentQueue.enqueue(SharedAssignmentPointer(scriptAssignment)); + SharedAssignmentPointer sharedScriptedAssignment(scriptAssignment); + _unfulfilledAssignments.enqueue(sharedScriptedAssignment); + _allAssignments.insert(sharedScriptedAssignment->getUUID(), sharedScriptedAssignment); } // respond with a 200 code for successful upload @@ -950,6 +1059,114 @@ bool DomainServer::handleHTTPRequest(HTTPConnection* connection, const QUrl& url return false; } +bool DomainServer::handleHTTPSRequest(HTTPSConnection* connection, const QUrl &url) { + const QString URI_OAUTH = "/oauth"; + if (url.path() == URI_OAUTH) { + + QUrlQuery codeURLQuery(url); + + const QString CODE_QUERY_KEY = "code"; + QString authorizationCode = codeURLQuery.queryItemValue(CODE_QUERY_KEY); + + const QString STATE_QUERY_KEY = "state"; + QUuid stateUUID = QUuid(codeURLQuery.queryItemValue(STATE_QUERY_KEY)); + + + if (!authorizationCode.isEmpty() && !stateUUID.isNull()) { + // fire off a request with this code and state to get an access token for the user + + const QString OAUTH_TOKEN_REQUEST_PATH = "/oauth/token"; + QUrl tokenRequestUrl = _oauthProviderURL; + tokenRequestUrl.setPath(OAUTH_TOKEN_REQUEST_PATH); + + const QString OAUTH_GRANT_TYPE_POST_STRING = "grant_type=authorization_code"; + QString tokenPostBody = OAUTH_GRANT_TYPE_POST_STRING; + tokenPostBody += QString("&code=%1&redirect_uri=%2&client_id=%3&client_secret=%4") + .arg(authorizationCode, oauthRedirectURL().toString(), _oauthClientID, _oauthClientSecret); + + QNetworkRequest tokenRequest(tokenRequestUrl); + tokenRequest.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded"); + + QNetworkReply* tokenReply = _networkAccessManager->post(tokenRequest, tokenPostBody.toLocal8Bit()); + + qDebug() << "Requesting a token for user with session UUID" << uuidStringWithoutCurlyBraces(stateUUID); + + // insert this to our pending token replies so we can associate the returned access token with the right UUID + _networkReplyUUIDMap.insert(tokenReply, stateUUID); + + connect(tokenReply, &QNetworkReply::finished, this, &DomainServer::handleTokenRequestFinished); + } + + // respond with a 200 code indicating that login is complete + connection->respond(HTTPConnection::StatusCode200); + + return true; + } else { + return false; + } +} + +const QString OAUTH_JSON_ACCESS_TOKEN_KEY = "access_token"; + +void DomainServer::handleTokenRequestFinished() { + QNetworkReply* networkReply = reinterpret_cast(sender()); + QUuid matchingSessionUUID = _networkReplyUUIDMap.take(networkReply); + + if (!matchingSessionUUID.isNull() && networkReply->error() == QNetworkReply::NoError) { + // pull the access token from the returned JSON and store it with the matching session UUID + QJsonDocument returnedJSON = QJsonDocument::fromJson(networkReply->readAll()); + QString accessToken = returnedJSON.object()[OAUTH_JSON_ACCESS_TOKEN_KEY].toString(); + + qDebug() << "Received access token for user with UUID" << uuidStringWithoutCurlyBraces(matchingSessionUUID); + + // fire off a request to get this user's identity so we can see if we will let them in + QUrl profileURL = _oauthProviderURL; + profileURL.setPath("/api/v1/users/profile"); + profileURL.setQuery(QString("%1=%2").arg(OAUTH_JSON_ACCESS_TOKEN_KEY, accessToken)); + + QNetworkReply* profileReply = _networkAccessManager->get(QNetworkRequest(profileURL)); + + qDebug() << "Requesting access token for user with session UUID" << uuidStringWithoutCurlyBraces(matchingSessionUUID); + + connect(profileReply, &QNetworkReply::finished, this, &DomainServer::handleProfileRequestFinished); + + _networkReplyUUIDMap.insert(profileReply, matchingSessionUUID); + } +} + +void DomainServer::handleProfileRequestFinished() { + QNetworkReply* networkReply = reinterpret_cast(sender()); + QUuid matchingSessionUUID = _networkReplyUUIDMap.take(networkReply); + + if (!matchingSessionUUID.isNull() && networkReply->error() == QNetworkReply::NoError) { + QJsonDocument profileJSON = QJsonDocument::fromJson(networkReply->readAll()); + + if (profileJSON.object()["status"].toString() == "success") { + // pull the user roles from the response + QJsonArray userRolesArray = profileJSON.object()["data"].toObject()["user"].toObject()["roles"].toArray(); + + QJsonArray allowedRolesArray = _argumentVariantMap.value(ALLOWED_ROLES_CONFIG_KEY).toJsonValue().toArray(); + + bool shouldAllowUserToConnect = false; + + foreach(const QJsonValue& roleValue, userRolesArray) { + if (allowedRolesArray.contains(roleValue)) { + // the user has a role that lets them in + // set the bool to true and break + shouldAllowUserToConnect = true; + break; + } + } + + qDebug() << "Confirmed authentication state for user" << uuidStringWithoutCurlyBraces(matchingSessionUUID) + << "-" << shouldAllowUserToConnect; + + // insert this UUID and a flag that indicates if they are allowed to connect + _sessionAuthenticationHash.insert(matchingSessionUUID, shouldAllowUserToConnect); + } + } +} + void DomainServer::refreshStaticAssignmentAndAddToQueue(SharedAssignmentPointer& assignment) { QUuid oldUUID = assignment->getUUID(); assignment->resetUUID(); @@ -963,13 +1180,8 @@ void DomainServer::refreshStaticAssignmentAndAddToQueue(SharedAssignmentPointer& } // add the static assignment back under the right UUID, and to the queue - _staticAssignmentHash.insert(assignment->getUUID(), assignment); - - _assignmentQueue.enqueue(assignment); - - // remove the old assignment from the _staticAssignmentHash - // this must be done last so copies are created before the assignment passed by reference is killed - _staticAssignmentHash.remove(oldUUID); + _allAssignments.insert(assignment->getUUID(), assignment); + _unfulfilledAssignments.enqueue(assignment); } void DomainServer::nodeAdded(SharedNodePointer node) { @@ -983,10 +1195,10 @@ void DomainServer::nodeKilled(SharedNodePointer node) { if (nodeData) { // if this node's UUID matches a static assignment we need to throw it back in the assignment queue - if (!nodeData->getStaticAssignmentUUID().isNull()) { - SharedAssignmentPointer matchedAssignment = _staticAssignmentHash.value(nodeData->getStaticAssignmentUUID()); + if (!nodeData->getAssignmentUUID().isNull()) { + SharedAssignmentPointer matchedAssignment = _allAssignments.take(nodeData->getAssignmentUUID()); - if (matchedAssignment) { + if (matchedAssignment && matchedAssignment->isStatic()) { refreshStaticAssignmentAndAddToQueue(matchedAssignment); } } @@ -1010,11 +1222,11 @@ void DomainServer::nodeKilled(SharedNodePointer node) { } SharedAssignmentPointer DomainServer::matchingQueuedAssignmentForCheckIn(const QUuid& checkInUUID, NodeType_t nodeType) { - QQueue::iterator i = _assignmentQueue.begin(); + QQueue::iterator i = _unfulfilledAssignments.begin(); - while (i != _assignmentQueue.end()) { + while (i != _unfulfilledAssignments.end()) { if (i->data()->getType() == Assignment::typeForNodeType(nodeType) && i->data()->getUUID() == checkInUUID) { - return _assignmentQueue.takeAt(i - _assignmentQueue.begin()); + return _unfulfilledAssignments.takeAt(i - _unfulfilledAssignments.begin()); } else { ++i; } @@ -1026,9 +1238,9 @@ SharedAssignmentPointer DomainServer::matchingQueuedAssignmentForCheckIn(const Q SharedAssignmentPointer DomainServer::deployableAssignmentForRequest(const Assignment& requestAssignment) { // this is an unassigned client talking to us directly for an assignment // go through our queue and see if there are any assignments to give out - QQueue::iterator sharedAssignment = _assignmentQueue.begin(); + QQueue::iterator sharedAssignment = _unfulfilledAssignments.begin(); - while (sharedAssignment != _assignmentQueue.end()) { + while (sharedAssignment != _unfulfilledAssignments.end()) { Assignment* assignment = sharedAssignment->data(); bool requestIsAllTypes = requestAssignment.getType() == Assignment::AllTypes; bool assignmentTypesMatch = assignment->getType() == requestAssignment.getType(); @@ -1038,16 +1250,12 @@ SharedAssignmentPointer DomainServer::deployableAssignmentForRequest(const Assig if ((requestIsAllTypes || assignmentTypesMatch) && (nietherHasPool || assignmentPoolsMatch)) { // remove the assignment from the queue - SharedAssignmentPointer deployableAssignment = _assignmentQueue.takeAt(sharedAssignment - - _assignmentQueue.begin()); + SharedAssignmentPointer deployableAssignment = _unfulfilledAssignments.takeAt(sharedAssignment + - _unfulfilledAssignments.begin()); - if (deployableAssignment->getType() != Assignment::AgentType - || _staticAssignmentHash.contains(deployableAssignment->getUUID())) { - // this is a static assignment - // until we get a check-in from that GUID - // put assignment back in queue but stick it at the back so the others have a chance to go out - _assignmentQueue.enqueue(deployableAssignment); - } + // until we get a connection for this assignment + // put assignment back in queue but stick it at the back so the others have a chance to go out + _unfulfilledAssignments.enqueue(deployableAssignment); // stop looping, we've handed out an assignment return deployableAssignment; @@ -1061,10 +1269,10 @@ SharedAssignmentPointer DomainServer::deployableAssignmentForRequest(const Assig } void DomainServer::removeMatchingAssignmentFromQueue(const SharedAssignmentPointer& removableAssignment) { - QQueue::iterator potentialMatchingAssignment = _assignmentQueue.begin(); - while (potentialMatchingAssignment != _assignmentQueue.end()) { + QQueue::iterator potentialMatchingAssignment = _unfulfilledAssignments.begin(); + while (potentialMatchingAssignment != _unfulfilledAssignments.end()) { if (potentialMatchingAssignment->data()->getUUID() == removableAssignment->getUUID()) { - _assignmentQueue.erase(potentialMatchingAssignment); + _unfulfilledAssignments.erase(potentialMatchingAssignment); // we matched and removed an assignment, bail out break; @@ -1078,7 +1286,7 @@ void DomainServer::addStaticAssignmentsToQueue() { // if the domain-server has just restarted, // check if there are static assignments that we need to throw into the assignment queue - QHash staticHashCopy = _staticAssignmentHash; + QHash staticHashCopy = _allAssignments; QHash::iterator staticAssignment = staticHashCopy.begin(); while (staticAssignment != staticHashCopy.end()) { // add any of the un-matched static assignments to the queue diff --git a/domain-server/src/DomainServer.h b/domain-server/src/DomainServer.h index 1bc9b71064..4c0f9447ea 100644 --- a/domain-server/src/DomainServer.h +++ b/domain-server/src/DomainServer.h @@ -23,20 +23,21 @@ #include #include -#include +#include #include #include "DTLSServerSession.h" typedef QSharedPointer SharedAssignmentPointer; -class DomainServer : public QCoreApplication, public HTTPRequestHandler { +class DomainServer : public QCoreApplication, public HTTPSRequestHandler { Q_OBJECT public: DomainServer(int argc, char* argv[]); ~DomainServer(); bool handleHTTPRequest(HTTPConnection* connection, const QUrl& url); + bool handleHTTPSRequest(HTTPSConnection* connection, const QUrl& url); void exit(int retCode = 0); @@ -52,12 +53,13 @@ private slots: void readAvailableDTLSDatagrams(); private: void setupNodeListAndAssignments(const QUuid& sessionUUID = QUuid::createUuid()); + bool optionallySetupOAuth(); bool optionallySetupDTLS(); - bool readX509KeyAndCertificate(); + bool optionallyReadX509KeyAndCertificate(); void processDatagram(const QByteArray& receivedPacket, const HifiSockAddr& senderSockAddr); - void addNodeToNodeListAndConfirmConnection(const QByteArray& packet, const HifiSockAddr& senderSockAddr); + void handleConnectRequest(const QByteArray& packet, const HifiSockAddr& senderSockAddr); int parseNodeDataFromByteArray(NodeType_t& nodeType, HifiSockAddr& publicSockAddr, HifiSockAddr& localSockAddr, const QByteArray& packet, const HifiSockAddr& senderSockAddr); NodeSet nodeInterestListFromPacket(const QByteArray& packet, int numPreceedingBytes); @@ -76,13 +78,20 @@ private: void refreshStaticAssignmentAndAddToQueue(SharedAssignmentPointer& assignment); void addStaticAssignmentsToQueue(); + QUrl oauthRedirectURL(); + QUrl oauthAuthorizationURL(const QUuid& stateUUID = QUuid::createUuid()); + + void handleTokenRequestFinished(); + void handleProfileRequestFinished(); + QJsonObject jsonForSocket(const HifiSockAddr& socket); QJsonObject jsonObjectForNode(const SharedNodePointer& node); - HTTPManager _HTTPManager; + HTTPManager _httpManager; + HTTPSManager* _httpsManager; - QHash _staticAssignmentHash; - QQueue _assignmentQueue; + QHash _allAssignments; + QQueue _unfulfilledAssignments; QVariantMap _argumentVariantMap; @@ -93,6 +102,15 @@ private: gnutls_priority_t* _priorityCache; QHash _dtlsSessions; + + QNetworkAccessManager* _networkAccessManager; + + QUrl _oauthProviderURL; + QString _oauthClientID; + QString _oauthClientSecret; + QString _hostname; + QMap _networkReplyUUIDMap; + QHash _sessionAuthenticationHash; }; #endif // hifi_DomainServer_h diff --git a/domain-server/src/DomainServerNodeData.cpp b/domain-server/src/DomainServerNodeData.cpp index 59d60659de..a43e17ae60 100644 --- a/domain-server/src/DomainServerNodeData.cpp +++ b/domain-server/src/DomainServerNodeData.cpp @@ -19,9 +19,10 @@ DomainServerNodeData::DomainServerNodeData() : _sessionSecretHash(), - _staticAssignmentUUID(), + _assignmentUUID(), _statsJSONObject(), - _sendingSockAddr() + _sendingSockAddr(), + _isAuthenticated(true) { } diff --git a/domain-server/src/DomainServerNodeData.h b/domain-server/src/DomainServerNodeData.h index 6026e65f25..011bee57c1 100644 --- a/domain-server/src/DomainServerNodeData.h +++ b/domain-server/src/DomainServerNodeData.h @@ -27,20 +27,24 @@ public: void parseJSONStatsPacket(const QByteArray& statsPacket); - void setStaticAssignmentUUID(const QUuid& staticAssignmentUUID) { _staticAssignmentUUID = staticAssignmentUUID; } - const QUuid& getStaticAssignmentUUID() const { return _staticAssignmentUUID; } + void setAssignmentUUID(const QUuid& assignmentUUID) { _assignmentUUID = assignmentUUID; } + const QUuid& getAssignmentUUID() const { return _assignmentUUID; } void setSendingSockAddr(const HifiSockAddr& sendingSockAddr) { _sendingSockAddr = sendingSockAddr; } const HifiSockAddr& getSendingSockAddr() { return _sendingSockAddr; } + void setIsAuthenticated(bool isAuthenticated) { _isAuthenticated = isAuthenticated; } + bool isAuthenticated() const { return _isAuthenticated; } + QHash& getSessionSecretHash() { return _sessionSecretHash; } private: QJsonObject mergeJSONStatsFromNewObject(const QJsonObject& newObject, QJsonObject destinationObject); QHash _sessionSecretHash; - QUuid _staticAssignmentUUID; + QUuid _assignmentUUID; QJsonObject _statsJSONObject; HifiSockAddr _sendingSockAddr; + bool _isAuthenticated; }; #endif // hifi_DomainServerNodeData_h diff --git a/examples/editModels.js b/examples/editModels.js new file mode 100644 index 0000000000..50b0137c4f --- /dev/null +++ b/examples/editModels.js @@ -0,0 +1,342 @@ +// +// editModels.js +// examples +// +// Created by Clément Brisset on 4/24/14. +// Copyright 2014 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 +// + +var LASER_WIDTH = 4; +var LASER_COLOR = { red: 255, green: 0, blue: 0 }; +var LASER_LENGTH_FACTOR = 1.5; + +var LEFT = 0; +var RIGHT = 1; + +function controller(wichSide) { + this.side = wichSide; + this.palm = 2 * wichSide; + this.tip = 2 * wichSide + 1; + this.trigger = wichSide; + + this.oldPalmPosition = Controller.getSpatialControlPosition(this.palm); + this.palmPosition = Controller.getSpatialControlPosition(this.palm); + + this.oldTipPosition = Controller.getSpatialControlPosition(this.tip); + this.tipPosition = Controller.getSpatialControlPosition(this.tip); + + this.oldUp = Controller.getSpatialControlNormal(this.palm); + this.up = this.oldUp; + + this.oldFront = Vec3.normalize(Vec3.subtract(this.tipPosition, this.palmPosition)); + this.front = this.oldFront; + + this.oldRight = Vec3.cross(this.front, this.up); + this.right = this.oldRight; + + this.oldRotation = Quat.multiply(MyAvatar.orientation, Controller.getSpatialControlRawRotation(this.palm)); + this.rotation = this.oldRotation; + + this.triggerValue = Controller.getTriggerValue(this.trigger); + + this.pressed = false; // is trigger pressed + this.pressing = false; // is trigger being pressed (is pressed now but wasn't previously) + + this.grabbing = false; + this.modelID; + + this.laser = Overlays.addOverlay("line3d", { + position: this.palmPosition, + end: this.tipPosition, + color: LASER_COLOR, + alpha: 1, + visible: false, + lineWidth: LASER_WIDTH + }); + + this.guideScale = 0.02; + this.ball = Overlays.addOverlay("sphere", { + position: this.palmPosition, + size: this.guideScale, + solid: true, + color: { red: 0, green: 255, blue: 0 }, + alpha: 1, + visible: false, + }); + this.leftRight = Overlays.addOverlay("line3d", { + position: this.palmPosition, + end: this.tipPosition, + color: { red: 0, green: 0, blue: 255 }, + alpha: 1, + visible: false, + lineWidth: LASER_WIDTH + }); + this.topDown = Overlays.addOverlay("line3d", { + position: this.palmPosition, + end: this.tipPosition, + color: { red: 0, green: 0, blue: 255 }, + alpha: 1, + visible: false, + lineWidth: LASER_WIDTH + }); + + + + this.grab = function (modelID) { + if (!modelID.isKnownID) { + var identify = Models.identifyModel(modelID); + if (!identify.isKnownID) { + print("Unknown ID " + identify.id + "(grab)"); + return; + } + modelID = identify; + } + print("Grabbing " + modelID.id); + this.grabbing = true; + this.modelID = modelID; + } + + this.release = function () { + this.grabbing = false; + this.modelID = 0; + } + + this.checkTrigger = function () { + if (this.triggerValue > 0.9) { + if (this.pressed) { + this.pressing = false; + } else { + this.pressing = true; + } + this.pressed = true; + } else { + this.pressing = false; + this.pressed = false; + } + } + + this.moveLaser = function () { + var endPosition = Vec3.sum(this.palmPosition, Vec3.multiply(this.front, LASER_LENGTH_FACTOR)); + + Overlays.editOverlay(this.laser, { + position: this.palmPosition, + end: endPosition, + visible: true + }); + + + Overlays.editOverlay(this.ball, { + position: endPosition, + visible: true + }); + Overlays.editOverlay(this.leftRight, { + position: Vec3.sum(endPosition, Vec3.multiply(this.right, 2 * this.guideScale)), + end: Vec3.sum(endPosition, Vec3.multiply(this.right, -2 * this.guideScale)), + visible: true + }); + Overlays.editOverlay(this.topDown, {position: Vec3.sum(endPosition, Vec3.multiply(this.up, 2 * this.guideScale)), + end: Vec3.sum(endPosition, Vec3.multiply(this.up, -2 * this.guideScale)), + visible: true + }); + } + + this.checkModel = function (modelID) { + if (!modelID.isKnownID) { + var identify = Models.identifyModel(modelID); + if (!identify.isKnownID) { + print("Unknown ID " + identify.id + "(checkModel)"); + return; + } + modelID = identify; + } + // P P - Model + // /| A - Palm + // / | d B - unit vector toward tip + // / | X - base of the perpendicular line + // A---X----->B d - distance fom axis + // x x - distance from A + // + // |X-A| = (P-A).B + // X == A + ((P-A).B)B + // d = |P-X| + + var A = this.palmPosition; + var B = this.front; + var P = Models.getModelProperties(modelID).position; + + this.x = Vec3.dot(Vec3.subtract(P, A), B); + this.y = Vec3.dot(Vec3.subtract(P, A), this.up); + this.z = Vec3.dot(Vec3.subtract(P, A), this.right); + var X = Vec3.sum(A, Vec3.multiply(B, this.x)); + var d = Vec3.length(Vec3.subtract(P, X)); + +// Vec3.print("A: ", A); +// Vec3.print("B: ", B); +// Vec3.print("Particle pos: ", P); +// print("d: " + d + ", x: " + this.x); + if (d < Models.getModelProperties(modelID).radius && 0 < this.x && this.x < LASER_LENGTH_FACTOR) { + return true; + } + return false; + } + + this.update = function () { + this.oldPalmPosition = this.palmPosition; + this.oldTipPosition = this.tipPosition; + this.palmPosition = Controller.getSpatialControlPosition(this.palm); + this.tipPosition = Controller.getSpatialControlPosition(this.tip); + + this.oldUp = this.up; + this.up = Vec3.normalize(Controller.getSpatialControlNormal(this.palm)); + + this.oldFront = this.front; + this.front = Vec3.normalize(Vec3.subtract(this.tipPosition, this.palmPosition)); + + this.oldRight = this.right; + this.right = Vec3.normalize(Vec3.cross(this.front, this.up)); + + this.oldRotation = this.rotation; + this.rotation = Quat.multiply(MyAvatar.orientation, Controller.getSpatialControlRawRotation(this.palm)); + + this.triggerValue = Controller.getTriggerValue(this.trigger); + + this.checkTrigger(); + + if (this.pressing) { + Vec3.print("Looking at: ", this.palmPosition); + var foundModels = Models.findModels(this.palmPosition, LASER_LENGTH_FACTOR); + for (var i = 0; i < foundModels.length; i++) { + print("Model found ID (" + foundModels[i].id + ")"); + if (this.checkModel(foundModels[i])) { + if (this.grab(foundModels[i])) { + return; + } + } + } + } + + if (!this.pressed && this.grabbing) { + // release if trigger not pressed anymore. + this.release(); + } + + this.moveLaser(); + } + + this.cleanup = function () { + Overlays.deleteOverlay(this.laser); + Overlays.deleteOverlay(this.ball); + Overlays.deleteOverlay(this.leftRight); + Overlays.deleteOverlay(this.topDown); + } +} + +var leftController = new controller(LEFT); +var rightController = new controller(RIGHT); + +function moveModels() { + if (leftController.grabbing) { + if (rightController.grabbing) { + var properties = Models.getModelProperties(leftController.modelID); + + var oldLeftPoint = Vec3.sum(leftController.oldPalmPosition, Vec3.multiply(leftController.oldFront, leftController.x)); + var oldRightPoint = Vec3.sum(rightController.oldPalmPosition, Vec3.multiply(rightController.oldFront, rightController.x)); + + var oldMiddle = Vec3.multiply(Vec3.sum(oldLeftPoint, oldRightPoint), 0.5); + var oldLength = Vec3.length(Vec3.subtract(oldLeftPoint, oldRightPoint)); + + + var leftPoint = Vec3.sum(leftController.palmPosition, Vec3.multiply(leftController.front, leftController.x)); + var rightPoint = Vec3.sum(rightController.palmPosition, Vec3.multiply(rightController.front, rightController.x)); + + var middle = Vec3.multiply(Vec3.sum(leftPoint, rightPoint), 0.5); + var length = Vec3.length(Vec3.subtract(leftPoint, rightPoint)); + + var ratio = length / oldLength; + + var newPosition = Vec3.sum(middle, + Vec3.multiply(Vec3.subtract(properties.position, oldMiddle), ratio)); + Vec3.print("Ratio : " + ratio + " New position: ", newPosition); + var rotation = Quat.multiply(leftController.rotation, + Quat.inverse(leftController.oldRotation)); + rotation = Quat.multiply(rotation, properties.modelRotation); + + Models.editModel(leftController.modelID, { + position: newPosition, + //modelRotation: rotation, + radius: properties.radius * ratio + }); + + return; + } else { + var newPosition = Vec3.sum(leftController.palmPosition, + Vec3.multiply(leftController.front, leftController.x)); + newPosition = Vec3.sum(newPosition, + Vec3.multiply(leftController.up, leftController.y)); + newPosition = Vec3.sum(newPosition, + Vec3.multiply(leftController.right, leftController.z)); + + var rotation = Quat.multiply(leftController.rotation, + Quat.inverse(leftController.oldRotation)); + rotation = Quat.multiply(rotation, + Models.getModelProperties(leftController.modelID).modelRotation); + + Models.editModel(leftController.modelID, { + position: newPosition, + modelRotation: rotation + }); + } + } + + + if (rightController.grabbing) { + var newPosition = Vec3.sum(rightController.palmPosition, + Vec3.multiply(rightController.front, rightController.x)); + newPosition = Vec3.sum(newPosition, + Vec3.multiply(rightController.up, rightController.y)); + newPosition = Vec3.sum(newPosition, + Vec3.multiply(rightController.right, rightController.z)); + + var rotation = Quat.multiply(rightController.rotation, + Quat.inverse(rightController.oldRotation)); + rotation = Quat.multiply(rotation, + Models.getModelProperties(rightController.modelID).modelRotation); + + Models.editModel(rightController.modelID, { + position: newPosition, + modelRotation: rotation + }); + } +} + +function checkController(deltaTime) { + var numberOfButtons = Controller.getNumberOfButtons(); + var numberOfTriggers = Controller.getNumberOfTriggers(); + var numberOfSpatialControls = Controller.getNumberOfSpatialControls(); + var controllersPerTrigger = numberOfSpatialControls / numberOfTriggers; + + // this is expected for hydras + if (!(numberOfButtons==12 && numberOfTriggers == 2 && controllersPerTrigger == 2)) { + //print("no hydra connected?"); + return; // bail if no hydra + } + + leftController.update(); + rightController.update(); + moveModels(); +} + +function scriptEnding() { + leftController.cleanup(); + rightController.cleanup(); +} +Script.scriptEnding.connect(scriptEnding); + +// register the call back so it fires before each data send +Script.update.connect(checkController); + + + diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index c6308193d0..86054f3fcd 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -81,9 +81,10 @@ #include "scripting/LocationScriptingInterface.h" #include "ui/InfoView.h" +#include "ui/OAuthWebViewHandler.h" #include "ui/Snapshot.h" -#include "ui/TextRenderer.h" #include "ui/Stats.h" +#include "ui/TextRenderer.h" using namespace std; @@ -361,12 +362,17 @@ Application::Application(int& argc, char** argv, QElapsedTimer &startup_time) : QMutexLocker locker(&_settingsMutex); _previousScriptLocation = _settings->value("LastScriptLocation", QVariant("")).toString(); } - + connect(_window, &MainWindow::windowGeometryChanged, _runningScriptsWidget, &RunningScriptsWidget::setBoundary); - - //When -url in command line, teleport to location - urlGoTo(argc, constArgv); + + //When -url in command line, teleport to location + urlGoTo(argc, constArgv); + + // call the OAuthWebviewHandler static getter so that its instance lives in our thread + OAuthWebViewHandler::getInstance(); + // make sure the High Fidelity root CA is in our list of trusted certs + OAuthWebViewHandler::addHighFidelityRootCAToSSLConfig(); } Application::~Application() { @@ -555,41 +561,6 @@ void Application::paintGL() { _myCamera.setTargetPosition(_myAvatar->getHead()->calculateAverageEyePosition()); _myCamera.setTargetRotation(_myAvatar->getHead()->getCameraOrientation()); - glm::vec3 planeNormal = _myCamera.getTargetRotation() * IDENTITY_FRONT; - const float BASE_PUSHBACK_RADIUS = 0.25f; - float pushbackRadius = _myCamera.getNearClip() + _myAvatar->getScale() * BASE_PUSHBACK_RADIUS; - glm::vec4 plane(planeNormal, -glm::dot(planeNormal, _myCamera.getTargetPosition()) - pushbackRadius); - - // push camera out of any intersecting avatars - foreach (const AvatarSharedPointer& avatarData, _avatarManager.getAvatarHash()) { - Avatar* avatar = static_cast(avatarData.data()); - if (avatar->isMyAvatar()) { - continue; - } - if (glm::distance(avatar->getPosition(), _myCamera.getTargetPosition()) > - avatar->getBoundingRadius() + pushbackRadius) { - continue; - } - float angle = angleBetween(avatar->getPosition() - _myCamera.getTargetPosition(), planeNormal); - if (angle > PI_OVER_TWO) { - continue; - } - float scale = 1.0f - angle / PI_OVER_TWO; - scale = qMin(1.0f, scale * 2.5f); - static CollisionList collisions(64); - collisions.clear(); - if (!avatar->findPlaneCollisions(plane, collisions)) { - continue; - } - for (int i = 0; i < collisions.size(); i++) { - pushback = qMax(pushback, glm::length(collisions.getCollision(i)->_penetration) * scale); - } - } - const float MAX_PUSHBACK = 0.35f; - pushback = qMin(pushback, MAX_PUSHBACK * _myAvatar->getScale()); - const float BASE_PUSHBACK_FOCAL_LENGTH = 0.5f; - pushbackFocalLength = BASE_PUSHBACK_FOCAL_LENGTH * _myAvatar->getScale(); - } else if (_myCamera.getMode() == CAMERA_MODE_THIRD_PERSON) { _myCamera.setTightness(0.0f); // Camera is directly connected to head without smoothing _myCamera.setTargetPosition(_myAvatar->getUprightHeadPosition()); @@ -3124,9 +3095,9 @@ void Application::updateWindowTitle(){ QString buildVersion = " (build " + applicationVersion() + ")"; NodeList* nodeList = NodeList::getInstance(); - QString username = AccountManager::getInstance().getUsername(); - QString title = QString() + (!username.isEmpty() ? username + " " : QString()) + nodeList->getSessionUUID().toString() - + " @ " + nodeList->getDomainHandler().getHostname() + buildVersion; + QString username = AccountManager::getInstance().getAccountInfo().getUsername(); + QString title = QString() + (!username.isEmpty() ? username + " @ " : QString()) + + nodeList->getDomainHandler().getHostname() + buildVersion; qDebug("Application title set to: %s", title.toStdString().c_str()); _window->setWindowTitle(title); } @@ -3150,6 +3121,9 @@ void Application::domainChanged(const QString& domainHostname) { // reset the voxels renderer _voxels.killLocalVoxels(); + + // reset the auth URL for OAuth web view handler + OAuthWebViewHandler::getInstance().clearLastAuthorizationURL(); } void Application::connectedToDomain(const QString& hostname) { diff --git a/interface/src/DatagramProcessor.cpp b/interface/src/DatagramProcessor.cpp index 56df4042ac..287744eba2 100644 --- a/interface/src/DatagramProcessor.cpp +++ b/interface/src/DatagramProcessor.cpp @@ -15,6 +15,7 @@ #include "Application.h" #include "Menu.h" +#include "ui/OAuthWebViewHandler.h" #include "DatagramProcessor.h" @@ -56,13 +57,11 @@ void DatagramProcessor::processDatagrams() { Particle::handleAddParticleResponse(incomingPacket); application->getParticles()->getTree()->handleAddParticleResponse(incomingPacket); break; - case PacketTypeModelAddResponse: // this will keep creatorTokenIDs to IDs mapped correctly ModelItem::handleAddModelResponse(incomingPacket); application->getModels()->getTree()->handleAddModelResponse(incomingPacket); break; - case PacketTypeParticleData: case PacketTypeParticleErase: case PacketTypeModelData: @@ -120,6 +119,18 @@ void DatagramProcessor::processDatagrams() { application->_bandwidthMeter.inputStream(BandwidthMeter::AVATARS).updateValue(incomingPacket.size()); break; } + case PacketTypeDomainOAuthRequest: { + QDataStream readStream(incomingPacket); + readStream.skipRawData(numBytesForPacketHeader(incomingPacket)); + + QUrl authorizationURL; + readStream >> authorizationURL; + + QMetaObject::invokeMethod(&OAuthWebViewHandler::getInstance(), "displayWebviewForAuthorizationURL", + Q_ARG(const QUrl&, authorizationURL)); + + break; + } default: nodeList->processNodeData(senderSockAddr, incomingPacket); break; diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index 3049d71e40..c492af3964 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -336,7 +336,6 @@ Menu::Menu() : SLOT(setFilter(bool))); addCheckableActionToQMenuAndActionHash(handOptionsMenu, MenuOption::DisplayHands, 0, true); addCheckableActionToQMenuAndActionHash(handOptionsMenu, MenuOption::DisplayHandTargets, 0, false); - addCheckableActionToQMenuAndActionHash(handOptionsMenu, MenuOption::PlaySlaps, 0, false); addCheckableActionToQMenuAndActionHash(handOptionsMenu, MenuOption::HandsCollideWithSelf, 0, false); addCheckableActionToQMenuAndActionHash(handOptionsMenu, MenuOption::ShowIKConstraints, 0, false); addCheckableActionToQMenuAndActionHash(handOptionsMenu, MenuOption::AlignForearmsWithWrists, 0, true); @@ -1100,7 +1099,7 @@ void Menu::toggleLoginMenuItem() { if (accountManager.isLoggedIn()) { // change the menu item to logout - _loginAction->setText("Logout " + accountManager.getUsername()); + _loginAction->setText("Logout " + accountManager.getAccountInfo().getUsername()); connect(_loginAction, &QAction::triggered, &accountManager, &AccountManager::logout); } else { // change the menu item to login diff --git a/interface/src/Menu.h b/interface/src/Menu.h index da2585f81a..9a7dda9111 100644 --- a/interface/src/Menu.h +++ b/interface/src/Menu.h @@ -340,7 +340,6 @@ namespace MenuOption { const QString Particles = "Particles"; const QString PasteToVoxel = "Paste to Voxel..."; const QString PipelineWarnings = "Show Render Pipeline Warnings"; - const QString PlaySlaps = "Play Slaps"; const QString Preferences = "Preferences..."; const QString Quit = "Quit"; const QString ReloadAllScripts = "Reload All Scripts"; diff --git a/interface/src/ModelUploader.cpp b/interface/src/ModelUploader.cpp index 799301b541..0ffd725716 100644 --- a/interface/src/ModelUploader.cpp +++ b/interface/src/ModelUploader.cpp @@ -153,14 +153,14 @@ bool ModelUploader::zip() { // mixamo/autodesk defaults if (!mapping.contains(SCALE_FIELD)) { - mapping.insert(SCALE_FIELD, 10.0); + mapping.insert(SCALE_FIELD, geometry.author == "www.makehuman.org" ? 150.0 : 15.0); } QVariantHash joints = mapping.value(JOINT_FIELD).toHash(); if (!joints.contains("jointEyeLeft")) { - joints.insert("jointEyeLeft", "LeftEye"); + joints.insert("jointEyeLeft", geometry.jointIndices.contains("EyeLeft") ? "EyeLeft" : "LeftEye"); } if (!joints.contains("jointEyeRight")) { - joints.insert("jointEyeRight", "RightEye"); + joints.insert("jointEyeRight", geometry.jointIndices.contains("EyeRight") ? "EyeRight" : "RightEye"); } if (!joints.contains("jointNeck")) { joints.insert("jointNeck", "Neck"); @@ -172,7 +172,8 @@ bool ModelUploader::zip() { joints.insert("jointLean", "Spine"); } if (!joints.contains("jointHead")) { - joints.insert("jointHead", geometry.applicationName == "mixamo.com" ? "HeadTop_End" : "HeadEnd"); + const char* topName = (geometry.applicationName == "mixamo.com") ? "HeadTop_End" : "HeadEnd"; + joints.insert("jointHead", geometry.jointIndices.contains(topName) ? topName : "Head"); } if (!joints.contains("jointLeftHand")) { joints.insert("jointLeftHand", "LeftHand"); @@ -600,7 +601,7 @@ static void setJointText(QComboBox* box, const QString& text) { void ModelPropertiesDialog::reset() { _name->setText(_originalMapping.value(NAME_FIELD).toString()); _textureDirectory->setText(_originalMapping.value(TEXDIR_FIELD).toString()); - _scale->setValue(_originalMapping.value(SCALE_FIELD, 1.0).toDouble()); + _scale->setValue(_originalMapping.value(SCALE_FIELD).toDouble()); QVariantHash jointHash = _originalMapping.value(JOINT_FIELD).toHash(); setJointText(_leftEyeJoint, jointHash.value("jointEyeLeft").toString()); diff --git a/interface/src/XmppClient.cpp b/interface/src/XmppClient.cpp index d930c16b53..48c1f5d976 100644 --- a/interface/src/XmppClient.cpp +++ b/interface/src/XmppClient.cpp @@ -34,12 +34,13 @@ XmppClient& XmppClient::getInstance() { void XmppClient::xmppConnected() { _publicChatRoom = _xmppMUCManager.addRoom(DEFAULT_CHAT_ROOM); - _publicChatRoom->setNickName(AccountManager::getInstance().getUsername()); + _publicChatRoom->setNickName(AccountManager::getInstance().getAccountInfo().getUsername()); _publicChatRoom->join(); } void XmppClient::xmppError(QXmppClient::Error error) { - qDebug() << "Error connnecting to XMPP for user " << AccountManager::getInstance().getUsername() << ": " << error; + qDebug() << "Error connnecting to XMPP for user " + << AccountManager::getInstance().getAccountInfo().getUsername() << ": " << error; } void XmppClient::connectToServer() { @@ -50,8 +51,8 @@ void XmppClient::connectToServer() { connect(&_xmppClient, SIGNAL(error(QXmppClient::Error)), this, SLOT(xmppError(QXmppClient::Error))); } AccountManager& accountManager = AccountManager::getInstance(); - QString user = accountManager.getUsername(); - const QString& password = accountManager.getXMPPPassword(); + QString user = accountManager.getAccountInfo().getUsername(); + const QString& password = accountManager.getAccountInfo().getXMPPPassword(); _xmppClient.connectToServer(user + "@" + DEFAULT_XMPP_SERVER, password); } diff --git a/interface/src/avatar/Avatar.cpp b/interface/src/avatar/Avatar.cpp index 71bd05a7c6..e8ac93234c 100644 --- a/interface/src/avatar/Avatar.cpp +++ b/interface/src/avatar/Avatar.cpp @@ -776,7 +776,16 @@ float Avatar::getSkeletonHeight() const { float Avatar::getHeadHeight() const { Extents extents = getHead()->getFaceModel().getBindExtents(); - return extents.maximum.y - extents.minimum.y; + if (!extents.isEmpty()) { + return extents.maximum.y - extents.minimum.y; + } + glm::vec3 neckPosition; + glm::vec3 headPosition; + if (_skeletonModel.getNeckPosition(neckPosition) && _skeletonModel.getHeadPosition(headPosition)) { + return glm::distance(neckPosition, headPosition); + } + const float DEFAULT_HEAD_HEIGHT = 0.1f; + return DEFAULT_HEAD_HEIGHT; } bool Avatar::collisionWouldMoveAvatar(CollisionInfo& collision) const { diff --git a/interface/src/avatar/FaceModel.cpp b/interface/src/avatar/FaceModel.cpp index 709a9fc79d..90e596bde5 100644 --- a/interface/src/avatar/FaceModel.cpp +++ b/interface/src/avatar/FaceModel.cpp @@ -29,11 +29,11 @@ void FaceModel::simulate(float deltaTime, bool fullUpdate) { neckPosition = owningAvatar->getPosition(); } setTranslation(neckPosition); - glm::quat neckRotation; - if (!owningAvatar->getSkeletonModel().getNeckRotation(neckRotation)) { - neckRotation = owningAvatar->getOrientation(); + glm::quat neckParentRotation; + if (!owningAvatar->getSkeletonModel().getNeckParentRotation(neckParentRotation)) { + neckParentRotation = owningAvatar->getOrientation(); } - setRotation(neckRotation); + setRotation(neckParentRotation); const float MODEL_SCALE = 0.0006f; setScale(glm::vec3(1.0f, 1.0f, 1.0f) * _owningHead->getScale() * MODEL_SCALE); diff --git a/interface/src/avatar/FaceModel.h b/interface/src/avatar/FaceModel.h index fdf8ab1fd2..0b7fea8ec4 100644 --- a/interface/src/avatar/FaceModel.h +++ b/interface/src/avatar/FaceModel.h @@ -26,8 +26,6 @@ public: virtual void simulate(float deltaTime, bool fullUpdate = true); -protected: - virtual void maybeUpdateNeckRotation(const JointState& parentState, const FBXJoint& joint, JointState& state); virtual void maybeUpdateEyeRotation(const JointState& parentState, const FBXJoint& joint, JointState& state); diff --git a/interface/src/avatar/Hand.cpp b/interface/src/avatar/Hand.cpp index 0f0df8a484..1ee22d3edf 100644 --- a/interface/src/avatar/Hand.cpp +++ b/interface/src/avatar/Hand.cpp @@ -55,76 +55,10 @@ void Hand::simulate(float deltaTime, bool isMine) { } } -void Hand::playSlaps(PalmData& palm, Avatar* avatar) { - // Check for palm collisions - glm::vec3 myPalmPosition = palm.getPosition(); - float palmCollisionDistance = 0.1f; - bool wasColliding = palm.getIsCollidingWithPalm(); - palm.setIsCollidingWithPalm(false); - // If 'Play Slaps' is enabled, look for palm-to-palm collisions and make sound - for (size_t j = 0; j < avatar->getHand()->getNumPalms(); j++) { - PalmData& otherPalm = avatar->getHand()->getPalms()[j]; - if (!otherPalm.isActive()) { - continue; - } - glm::vec3 otherPalmPosition = otherPalm.getPosition(); - if (glm::length(otherPalmPosition - myPalmPosition) < palmCollisionDistance) { - palm.setIsCollidingWithPalm(true); - if (!wasColliding) { - const float PALM_COLLIDE_VOLUME = 1.f; - const float PALM_COLLIDE_FREQUENCY = 1000.f; - const float PALM_COLLIDE_DURATION_MAX = 0.75f; - const float PALM_COLLIDE_DECAY_PER_SAMPLE = 0.01f; - Application::getInstance()->getAudio()->startDrumSound(PALM_COLLIDE_VOLUME, - PALM_COLLIDE_FREQUENCY, - PALM_COLLIDE_DURATION_MAX, - PALM_COLLIDE_DECAY_PER_SAMPLE); - // If the other person's palm is in motion, move mine downward to show I was hit - const float MIN_VELOCITY_FOR_SLAP = 0.05f; - if (glm::length(otherPalm.getVelocity()) > MIN_VELOCITY_FOR_SLAP) { - // add slapback here - } - } - } - } -} - // We create a static CollisionList that is recycled for each collision test. const float MAX_COLLISIONS_PER_AVATAR = 32; static CollisionList handCollisions(MAX_COLLISIONS_PER_AVATAR); -void Hand::collideAgainstAvatarOld(Avatar* avatar, bool isMyHand) { - if (!avatar || avatar == _owningAvatar) { - // don't collide with our own hands (that is done elsewhere) - return; - } - float scaledPalmRadius = PALM_COLLISION_RADIUS * _owningAvatar->getScale(); - for (size_t i = 0; i < getNumPalms(); i++) { - PalmData& palm = getPalms()[i]; - if (!palm.isActive()) { - continue; - } - if (isMyHand && Menu::getInstance()->isOptionChecked(MenuOption::PlaySlaps)) { - playSlaps(palm, avatar); - } - - glm::vec3 totalPenetration; - handCollisions.clear(); - if (avatar->findSphereCollisions(palm.getPosition(), scaledPalmRadius, handCollisions)) { - for (int j = 0; j < handCollisions.size(); ++j) { - CollisionInfo* collision = handCollisions.getCollision(j); - if (isMyHand) { - totalPenetration = addPenetrations(totalPenetration, collision->_penetration); - } - } - } - if (isMyHand) { - // resolve penetration - palm.addToPosition(-totalPenetration); - } - } -} - void Hand::collideAgainstAvatar(Avatar* avatar, bool isMyHand) { if (!avatar || avatar == _owningAvatar) { // don't collide hands against ourself (that is done elsewhere) diff --git a/interface/src/avatar/Hand.h b/interface/src/avatar/Hand.h index 1c857a198a..65a7dcb74a 100755 --- a/interface/src/avatar/Hand.h +++ b/interface/src/avatar/Hand.h @@ -58,7 +58,6 @@ public: const glm::vec3& getLeapFingerTipBallPosition (int ball) const { return _leapFingerTipBalls [ball].position;} const glm::vec3& getLeapFingerRootBallPosition(int ball) const { return _leapFingerRootBalls[ball].position;} - void collideAgainstAvatarOld(Avatar* avatar, bool isMyHand); void collideAgainstAvatar(Avatar* avatar, bool isMyHand); void collideAgainstOurself(); @@ -80,8 +79,6 @@ private: void renderLeapFingerTrails(); void calculateGeometry(); - - void playSlaps(PalmData& palm, Avatar* avatar); }; #endif // hifi_Hand_h diff --git a/interface/src/avatar/Head.cpp b/interface/src/avatar/Head.cpp index a213603a89..19aebba25c 100644 --- a/interface/src/avatar/Head.cpp +++ b/interface/src/avatar/Head.cpp @@ -161,7 +161,9 @@ void Head::simulate(float deltaTime, bool isMine, bool billboard) { _leftEyePosition = _rightEyePosition = getPosition(); if (!billboard) { _faceModel.simulate(deltaTime); - _faceModel.getEyePositions(_leftEyePosition, _rightEyePosition); + if (!_faceModel.getEyePositions(_leftEyePosition, _rightEyePosition)) { + static_cast(_owningAvatar)->getSkeletonModel().getEyePositions(_leftEyePosition, _rightEyePosition); + } } _eyePosition = calculateAverageEyePosition(); } diff --git a/interface/src/avatar/SkeletonModel.cpp b/interface/src/avatar/SkeletonModel.cpp index f8ebba676f..e4c796d3ce 100644 --- a/interface/src/avatar/SkeletonModel.cpp +++ b/interface/src/avatar/SkeletonModel.cpp @@ -221,6 +221,14 @@ void SkeletonModel::maybeUpdateLeanRotation(const JointState& parentState, const glm::normalize(inverse * axes[0])) * joint.rotation; } +void SkeletonModel::maybeUpdateNeckRotation(const JointState& parentState, const FBXJoint& joint, JointState& state) { + _owningAvatar->getHead()->getFaceModel().maybeUpdateNeckRotation(parentState, joint, state); +} + +void SkeletonModel::maybeUpdateEyeRotation(const JointState& parentState, const FBXJoint& joint, JointState& state) { + _owningAvatar->getHead()->getFaceModel().maybeUpdateEyeRotation(parentState, joint, state); +} + void SkeletonModel::renderJointConstraints(int jointIndex) { if (jointIndex == -1) { return; diff --git a/interface/src/avatar/SkeletonModel.h b/interface/src/avatar/SkeletonModel.h index 0a87fcf89d..ee6b3b9de3 100644 --- a/interface/src/avatar/SkeletonModel.h +++ b/interface/src/avatar/SkeletonModel.h @@ -46,6 +46,8 @@ protected: virtual void updateJointState(int index); virtual void maybeUpdateLeanRotation(const JointState& parentState, const FBXJoint& joint, JointState& state); + virtual void maybeUpdateNeckRotation(const JointState& parentState, const FBXJoint& joint, JointState& state); + virtual void maybeUpdateEyeRotation(const JointState& parentState, const FBXJoint& joint, JointState& state); private: diff --git a/interface/src/renderer/Model.cpp b/interface/src/renderer/Model.cpp index 5c06d2fcf3..a177783955 100644 --- a/interface/src/renderer/Model.cpp +++ b/interface/src/renderer/Model.cpp @@ -434,6 +434,17 @@ bool Model::getNeckRotation(glm::quat& neckRotation) const { return isActive() && getJointRotation(_geometry->getFBXGeometry().neckJointIndex, neckRotation); } +bool Model::getNeckParentRotation(glm::quat& neckParentRotation) const { + if (!isActive()) { + return false; + } + const FBXGeometry& geometry = _geometry->getFBXGeometry(); + if (geometry.neckJointIndex == -1) { + return false; + } + return getJointRotation(geometry.joints.at(geometry.neckJointIndex).parentIndex, neckParentRotation); +} + bool Model::getEyePositions(glm::vec3& firstEyePosition, glm::vec3& secondEyePosition) const { if (!isActive()) { return false; diff --git a/interface/src/renderer/Model.h b/interface/src/renderer/Model.h index 14589d1464..ae2bcd79b8 100644 --- a/interface/src/renderer/Model.h +++ b/interface/src/renderer/Model.h @@ -132,6 +132,10 @@ public: /// \return whether or not the neck was found bool getNeckRotation(glm::quat& neckRotation) const; + /// Returns the rotation of the neck joint's parent. + /// \return whether or not the neck was found + bool getNeckParentRotation(glm::quat& neckRotation) const; + /// Retrieve the positions of up to two eye meshes. /// \return whether or not both eye meshes were found bool getEyePositions(glm::vec3& firstEyePosition, glm::vec3& secondEyePosition) const; diff --git a/interface/src/ui/ChatWindow.cpp b/interface/src/ui/ChatWindow.cpp index e0802c6bc5..3f2df5593b 100644 --- a/interface/src/ui/ChatWindow.cpp +++ b/interface/src/ui/ChatWindow.cpp @@ -254,7 +254,7 @@ void ChatWindow::messageReceived(const QXmppMessage& message) { } // Update background if this is a message from the current user - bool fromSelf = getParticipantName(message.from()) == AccountManager::getInstance().getUsername(); + bool fromSelf = getParticipantName(message.from()) == AccountManager::getInstance().getAccountInfo().getUsername(); // Create message area ChatMessageArea* messageArea = new ChatMessageArea(true); diff --git a/interface/src/ui/OAuthWebViewHandler.cpp b/interface/src/ui/OAuthWebViewHandler.cpp new file mode 100644 index 0000000000..b3b914bd64 --- /dev/null +++ b/interface/src/ui/OAuthWebViewHandler.cpp @@ -0,0 +1,116 @@ +// +// OAuthWebViewHandler.cpp +// interface/src/ui +// +// Created by Stephen Birarda on 2014-05-01. +// Copyright 2014 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 + +#include "Application.h" + +#include "OAuthWebViewHandler.h" + +OAuthWebViewHandler& OAuthWebViewHandler::getInstance() { + static OAuthWebViewHandler sharedInstance; + return sharedInstance; +} + +OAuthWebViewHandler::OAuthWebViewHandler() : + _activeWebView(NULL), + _webViewRedisplayTimer(), + _lastAuthorizationURL() +{ + +} + +void OAuthWebViewHandler::addHighFidelityRootCAToSSLConfig() { + QSslConfiguration sslConfig = QSslConfiguration::defaultConfiguration(); + + // add the High Fidelity root CA to the list of trusted CA certificates + QByteArray highFidelityCACertificate(reinterpret_cast(DTLSSession::highFidelityCADatum()->data), + DTLSSession::highFidelityCADatum()->size); + sslConfig.setCaCertificates(sslConfig.caCertificates() + QSslCertificate::fromData(highFidelityCACertificate)); + + // set the modified configuration + QSslConfiguration::setDefaultConfiguration(sslConfig); +} + +const int WEB_VIEW_REDISPLAY_ELAPSED_MSECS = 5 * 1000; + +void OAuthWebViewHandler::displayWebviewForAuthorizationURL(const QUrl& authorizationURL) { + if (!_activeWebView) { + + if (!_lastAuthorizationURL.isEmpty()) { + if (_lastAuthorizationURL.host() == authorizationURL.host() + && _webViewRedisplayTimer.elapsed() < WEB_VIEW_REDISPLAY_ELAPSED_MSECS) { + // this would be re-displaying an OAuth dialog for the same auth URL inside of the redisplay ms + // so return instead + return; + } + } + + _lastAuthorizationURL = authorizationURL; + + _activeWebView = new QWebView; + + // keep the window on top and delete it when it closes + _activeWebView->setWindowFlags(Qt::WindowStaysOnTopHint); + _activeWebView->setAttribute(Qt::WA_DeleteOnClose); + + qDebug() << "Displaying QWebView for OAuth authorization at" << authorizationURL.toString(); + + AccountManager& accountManager = AccountManager::getInstance(); + + QUrl codedAuthorizationURL = authorizationURL; + + // check if we have an access token for this host - if so we can bypass login by adding it to the URL + if (accountManager.getAuthURL().host() == authorizationURL.host() + && accountManager.hasValidAccessToken()) { + + const QString ACCESS_TOKEN_QUERY_STRING_KEY = "access_token"; + + QUrlQuery authQuery(codedAuthorizationURL); + authQuery.addQueryItem(ACCESS_TOKEN_QUERY_STRING_KEY, accountManager.getAccountInfo().getAccessToken().token); + + codedAuthorizationURL.setQuery(authQuery); + } + + _activeWebView->load(codedAuthorizationURL); + _activeWebView->show(); + + connect(_activeWebView->page()->networkAccessManager(), &QNetworkAccessManager::sslErrors, + this, &OAuthWebViewHandler::handleSSLErrors); + connect(_activeWebView.data(), &QWebView::loadFinished, this, &OAuthWebViewHandler::handleLoadFinished); + + // connect to the destroyed signal so after the web view closes we can start a timer + connect(_activeWebView.data(), &QWebView::destroyed, this, &OAuthWebViewHandler::handleWebViewDestroyed); + } +} + +void OAuthWebViewHandler::handleSSLErrors(QNetworkReply* networkReply, const QList& errorList) { + qDebug() << "SSL Errors:" << errorList; +} + +void OAuthWebViewHandler::handleLoadFinished(bool success) { + if (success && _activeWebView->url().host() == NodeList::getInstance()->getDomainHandler().getHostname()) { + qDebug() << "OAuth authorization code passed successfully to domain-server."; + + // grab the UUID that is set as the state parameter in the auth URL + // since that is our new session UUID + QUrlQuery authQuery(_activeWebView->url()); + + const QString AUTH_STATE_QUERY_KEY = "state"; + NodeList::getInstance()->setSessionUUID(QUuid(authQuery.queryItemValue(AUTH_STATE_QUERY_KEY))); + + _activeWebView->close(); + } +} + +void OAuthWebViewHandler::handleWebViewDestroyed(QObject* destroyedObject) { + _webViewRedisplayTimer.restart(); +} diff --git a/interface/src/ui/OAuthWebViewHandler.h b/interface/src/ui/OAuthWebViewHandler.h new file mode 100644 index 0000000000..3a30705f88 --- /dev/null +++ b/interface/src/ui/OAuthWebViewHandler.h @@ -0,0 +1,41 @@ +// +// OAuthWebviewHandler.h +// interface/src/ui +// +// Created by Stephen Birarda on 2014-05-01. +// Copyright 2014 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 +// + +#ifndef hifi_OAuthWebviewHandler_h +#define hifi_OAuthWebviewHandler_h + +#include + +class QWebView; + +class OAuthWebViewHandler : public QObject { + Q_OBJECT +public: + OAuthWebViewHandler(); + static OAuthWebViewHandler& getInstance(); + static void addHighFidelityRootCAToSSLConfig(); + + void clearLastAuthorizationURL() { _lastAuthorizationURL = QUrl(); } + +public slots: + void displayWebviewForAuthorizationURL(const QUrl& authorizationURL); + +private slots: + void handleSSLErrors(QNetworkReply* networkReply, const QList& errorList); + void handleLoadFinished(bool success); + void handleWebViewDestroyed(QObject* destroyedObject); +private: + QPointer _activeWebView; + QElapsedTimer _webViewRedisplayTimer; + QUrl _lastAuthorizationURL; +}; + +#endif // hifi_OAuthWebviewHandler_h \ No newline at end of file diff --git a/interface/src/ui/Snapshot.cpp b/interface/src/ui/Snapshot.cpp index 29c2d3c90f..243acdfb10 100644 --- a/interface/src/ui/Snapshot.cpp +++ b/interface/src/ui/Snapshot.cpp @@ -86,7 +86,7 @@ void Snapshot::saveSnapshot(QGLWidget* widget, Avatar* avatar) { // replace decimal . with '-' formattedLocation.replace('.', '-'); - QString username = AccountManager::getInstance().getUsername(); + QString username = AccountManager::getInstance().getAccountInfo().getUsername(); // normalize username, replace all non alphanumeric with '-' username.replace(QRegExp("[^A-Za-z0-9_]"), "-"); diff --git a/interface/src/ui/overlays/Sphere3DOverlay.cpp b/interface/src/ui/overlays/Sphere3DOverlay.cpp index e5f31fa1be..c2ac92f45c 100644 --- a/interface/src/ui/overlays/Sphere3DOverlay.cpp +++ b/interface/src/ui/overlays/Sphere3DOverlay.cpp @@ -33,9 +33,9 @@ void Sphere3DOverlay::render() { glDisable(GL_LIGHTING); glPushMatrix(); - glTranslatef(_position.x + _size * 0.5f, - _position.y + _size * 0.5f, - _position.z + _size * 0.5f); + glTranslatef(_position.x, + _position.y, + _position.z); glLineWidth(_lineWidth); const int slices = 15; if (_isSolid) { diff --git a/libraries/embedded-webserver/src/HTTPConnection.cpp b/libraries/embedded-webserver/src/HTTPConnection.cpp index 276b4e7f64..a6eb391138 100755 --- a/libraries/embedded-webserver/src/HTTPConnection.cpp +++ b/libraries/embedded-webserver/src/HTTPConnection.cpp @@ -28,8 +28,8 @@ HTTPConnection::HTTPConnection (QTcpSocket* socket, HTTPManager* parentManager) _parentManager(parentManager), _socket(socket), _stream(socket), - _address(socket->peerAddress()) { - + _address(socket->peerAddress()) +{ // take over ownership of the socket _socket->setParent(this); @@ -42,7 +42,7 @@ HTTPConnection::HTTPConnection (QTcpSocket* socket, HTTPManager* parentManager) HTTPConnection::~HTTPConnection() { // log the destruction if (_socket->error() != QAbstractSocket::UnknownSocketError) { - qDebug() << _socket->errorString(); + qDebug() << _socket->errorString() << "-" << _socket->error(); } } diff --git a/libraries/embedded-webserver/src/HTTPConnection.h b/libraries/embedded-webserver/src/HTTPConnection.h index a131a22a9e..d5214ee3a8 100644 --- a/libraries/embedded-webserver/src/HTTPConnection.h +++ b/libraries/embedded-webserver/src/HTTPConnection.h @@ -18,10 +18,10 @@ #include #include -#include +#include #include #include -#include +#include #include #include #include diff --git a/libraries/embedded-webserver/src/HTTPManager.cpp b/libraries/embedded-webserver/src/HTTPManager.cpp index 1fc859014a..ec5f16012e 100755 --- a/libraries/embedded-webserver/src/HTTPManager.cpp +++ b/libraries/embedded-webserver/src/HTTPManager.cpp @@ -18,116 +18,6 @@ #include "HTTPConnection.h" #include "HTTPManager.h" -bool HTTPManager::handleHTTPRequest(HTTPConnection* connection, const QUrl& url) { - if (_requestHandler && _requestHandler->handleHTTPRequest(connection, url)) { - // this request was handled by our _requestHandler object - // so we don't need to attempt to do so in the document root - return true; - } - - // check to see if there is a file to serve from the document root for this path - QString subPath = url.path(); - - // remove any slash at the beginning of the path - if (subPath.startsWith('/')) { - subPath.remove(0, 1); - } - - QString filePath; - - if (QFileInfo(_documentRoot + subPath).isFile()) { - filePath = _documentRoot + subPath; - } else if (subPath.size() > 0 && !subPath.endsWith('/')) { - // this could be a directory with a trailing slash - // send a redirect to the path with a slash so we can - QString redirectLocation = '/' + subPath + '/'; - - if (!url.query().isEmpty()) { - redirectLocation += "?" + url.query(); - } - - QHash redirectHeader; - redirectHeader.insert(QByteArray("Location"), redirectLocation.toUtf8()); - - connection->respond(HTTPConnection::StatusCode301, "", HTTPConnection::DefaultContentType, redirectHeader); - } - - // if the last thing is a trailing slash then we want to look for index file - if (subPath.endsWith('/') || subPath.size() == 0) { - QStringList possibleIndexFiles = QStringList() << "index.html" << "index.shtml"; - - foreach (const QString& possibleIndexFilename, possibleIndexFiles) { - if (QFileInfo(_documentRoot + subPath + possibleIndexFilename).exists()) { - filePath = _documentRoot + subPath + possibleIndexFilename; - break; - } - } - } - - if (!filePath.isEmpty()) { - // file exists, serve it - static QMimeDatabase mimeDatabase; - - QFile localFile(filePath); - localFile.open(QIODevice::ReadOnly); - QByteArray localFileData = localFile.readAll(); - - QFileInfo localFileInfo(filePath); - - if (localFileInfo.completeSuffix() == "shtml") { - // this is a file that may have some SSI statements - // the only thing we support is the include directive, but check the contents for that - - // setup our static QRegExp that will catch and directives - const QString includeRegExpString = ""; - QRegExp includeRegExp(includeRegExpString); - - int matchPosition = 0; - - QString localFileString(localFileData); - - while ((matchPosition = includeRegExp.indexIn(localFileString, matchPosition)) != -1) { - // check if this is a file or vitual include - bool isFileInclude = includeRegExp.cap(1) == "file"; - - // setup the correct file path for the included file - QString includeFilePath = isFileInclude - ? localFileInfo.canonicalPath() + "/" + includeRegExp.cap(2) - : _documentRoot + includeRegExp.cap(2); - - QString replacementString; - - if (QFileInfo(includeFilePath).isFile()) { - - QFile includedFile(includeFilePath); - includedFile.open(QIODevice::ReadOnly); - - replacementString = QString(includedFile.readAll()); - } else { - qDebug() << "SSI include directive referenced a missing file:" << includeFilePath; - } - - // replace the match with the contents of the file, or an empty string if the file was not found - localFileString.replace(matchPosition, includeRegExp.matchedLength(), replacementString); - - // push the match position forward so we can check the next match - matchPosition += includeRegExp.matchedLength(); - } - - localFileData = localFileString.toLocal8Bit(); - } - - connection->respond(HTTPConnection::StatusCode200, localFileData, - qPrintable(mimeDatabase.mimeTypeForFile(filePath).name())); - } else { - - // respond with a 404 - connection->respond(HTTPConnection::StatusCode404, "Resource not found."); - } - - return true; -} - HTTPManager::HTTPManager(quint16 port, const QString& documentRoot, HTTPRequestHandler* requestHandler, QObject* parent) : QTcpServer(parent), _documentRoot(documentRoot), @@ -138,14 +28,131 @@ HTTPManager::HTTPManager(quint16 port, const QString& documentRoot, HTTPRequestH qDebug() << "Failed to open HTTP server socket:" << errorString(); return; } - - // connect the connection signal - connect(this, SIGNAL(newConnection()), SLOT(acceptConnections())); } -void HTTPManager::acceptConnections() { - QTcpSocket* socket; - while ((socket = nextPendingConnection()) != 0) { +void HTTPManager::incomingConnection(qintptr socketDescriptor) { + QTcpSocket* socket = new QTcpSocket(this); + + if (socket->setSocketDescriptor(socketDescriptor)) { new HTTPConnection(socket, this); + } else { + delete socket; } } + +bool HTTPManager::handleHTTPRequest(HTTPConnection* connection, const QUrl& url) { + if (requestHandledByRequestHandler(connection, url)) { + // this request was handled by our request handler object + // so we don't need to attempt to do so in the document root + return true; + } + + if (!_documentRoot.isEmpty()) { + // check to see if there is a file to serve from the document root for this path + QString subPath = url.path(); + + // remove any slash at the beginning of the path + if (subPath.startsWith('/')) { + subPath.remove(0, 1); + } + + QString filePath; + + if (QFileInfo(_documentRoot + subPath).isFile()) { + filePath = _documentRoot + subPath; + } else if (subPath.size() > 0 && !subPath.endsWith('/')) { + // this could be a directory with a trailing slash + // send a redirect to the path with a slash so we can + QString redirectLocation = '/' + subPath + '/'; + + if (!url.query().isEmpty()) { + redirectLocation += "?" + url.query(); + } + + QHash redirectHeader; + redirectHeader.insert(QByteArray("Location"), redirectLocation.toUtf8()); + + connection->respond(HTTPConnection::StatusCode301, "", HTTPConnection::DefaultContentType, redirectHeader); + } + + // if the last thing is a trailing slash then we want to look for index file + if (subPath.endsWith('/') || subPath.size() == 0) { + QStringList possibleIndexFiles = QStringList() << "index.html" << "index.shtml"; + + foreach (const QString& possibleIndexFilename, possibleIndexFiles) { + if (QFileInfo(_documentRoot + subPath + possibleIndexFilename).exists()) { + filePath = _documentRoot + subPath + possibleIndexFilename; + break; + } + } + } + + if (!filePath.isEmpty()) { + // file exists, serve it + static QMimeDatabase mimeDatabase; + + QFile localFile(filePath); + localFile.open(QIODevice::ReadOnly); + QByteArray localFileData = localFile.readAll(); + + QFileInfo localFileInfo(filePath); + + if (localFileInfo.completeSuffix() == "shtml") { + // this is a file that may have some SSI statements + // the only thing we support is the include directive, but check the contents for that + + // setup our static QRegExp that will catch and directives + const QString includeRegExpString = ""; + QRegExp includeRegExp(includeRegExpString); + + int matchPosition = 0; + + QString localFileString(localFileData); + + while ((matchPosition = includeRegExp.indexIn(localFileString, matchPosition)) != -1) { + // check if this is a file or vitual include + bool isFileInclude = includeRegExp.cap(1) == "file"; + + // setup the correct file path for the included file + QString includeFilePath = isFileInclude + ? localFileInfo.canonicalPath() + "/" + includeRegExp.cap(2) + : _documentRoot + includeRegExp.cap(2); + + QString replacementString; + + if (QFileInfo(includeFilePath).isFile()) { + + QFile includedFile(includeFilePath); + includedFile.open(QIODevice::ReadOnly); + + replacementString = QString(includedFile.readAll()); + } else { + qDebug() << "SSI include directive referenced a missing file:" << includeFilePath; + } + + // replace the match with the contents of the file, or an empty string if the file was not found + localFileString.replace(matchPosition, includeRegExp.matchedLength(), replacementString); + + // push the match position forward so we can check the next match + matchPosition += includeRegExp.matchedLength(); + } + + localFileData = localFileString.toLocal8Bit(); + } + + connection->respond(HTTPConnection::StatusCode200, localFileData, + qPrintable(mimeDatabase.mimeTypeForFile(filePath).name())); + + return true; + } + } + + // respond with a 404 + connection->respond(HTTPConnection::StatusCode404, "Resource not found."); + + return true; +} + +bool HTTPManager::requestHandledByRequestHandler(HTTPConnection* connection, const QUrl& url) { + return _requestHandler && _requestHandler->handleHTTPRequest(connection, url); +} \ No newline at end of file diff --git a/libraries/embedded-webserver/src/HTTPManager.h b/libraries/embedded-webserver/src/HTTPManager.h index 1e3afca1b5..e8745521dc 100755 --- a/libraries/embedded-webserver/src/HTTPManager.h +++ b/libraries/embedded-webserver/src/HTTPManager.h @@ -19,6 +19,7 @@ #include class HTTPConnection; +class HTTPSConnection; class HTTPRequestHandler { public: @@ -35,9 +36,10 @@ public: bool handleHTTPRequest(HTTPConnection* connection, const QUrl& url); -protected slots: +protected: /// Accepts all pending connections - void acceptConnections(); + virtual void incomingConnection(qintptr socketDescriptor); + virtual bool requestHandledByRequestHandler(HTTPConnection* connection, const QUrl& url); protected: QString _documentRoot; HTTPRequestHandler* _requestHandler; diff --git a/libraries/embedded-webserver/src/HTTPSConnection.cpp b/libraries/embedded-webserver/src/HTTPSConnection.cpp new file mode 100644 index 0000000000..54893d91c2 --- /dev/null +++ b/libraries/embedded-webserver/src/HTTPSConnection.cpp @@ -0,0 +1,23 @@ +// +// HTTPSConnection.cpp +// libraries/embedded-webserver/src +// +// Created by Stephen Birarda on 2014-04-24. +// Copyright 2014 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 "HTTPSConnection.h" + +HTTPSConnection::HTTPSConnection(QSslSocket* sslSocket, HTTPSManager* parentManager) : + HTTPConnection(sslSocket, parentManager) +{ + connect(sslSocket, SIGNAL(sslErrors(const QList&)), this, SLOT(handleSSLErrors(const QList&))); + sslSocket->startServerEncryption(); +} + +void HTTPSConnection::handleSSLErrors(const QList& errors) { + qDebug() << "SSL errors:" << errors; +} \ No newline at end of file diff --git a/libraries/embedded-webserver/src/HTTPSConnection.h b/libraries/embedded-webserver/src/HTTPSConnection.h new file mode 100644 index 0000000000..7b53dc0063 --- /dev/null +++ b/libraries/embedded-webserver/src/HTTPSConnection.h @@ -0,0 +1,26 @@ +// +// HTTPSConnection.h +// libraries/embedded-webserver/src +// +// Created by Stephen Birarda on 2014-04-24. +// Copyright 2014 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 +// + +#ifndef hifi_HTTPSConnection_h +#define hifi_HTTPSConnection_h + +#include "HTTPConnection.h" +#include "HTTPSManager.h" + +class HTTPSConnection : public HTTPConnection { + Q_OBJECT +public: + HTTPSConnection(QSslSocket* sslSocket, HTTPSManager* parentManager); +protected slots: + void handleSSLErrors(const QList& errors); +}; + +#endif // hifi_HTTPSConnection_h \ No newline at end of file diff --git a/libraries/embedded-webserver/src/HTTPSManager.cpp b/libraries/embedded-webserver/src/HTTPSManager.cpp new file mode 100644 index 0000000000..4e40a0e02c --- /dev/null +++ b/libraries/embedded-webserver/src/HTTPSManager.cpp @@ -0,0 +1,51 @@ +// +// HTTPSManager.cpp +// libraries/embedded-webserver/src +// +// Created by Stephen Birarda on 2014-04-24. +// Copyright 2014 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 + +#include "HTTPSConnection.h" + +#include "HTTPSManager.h" + +HTTPSManager::HTTPSManager(quint16 port, const QSslCertificate& certificate, const QSslKey& privateKey, + const QString& documentRoot, HTTPSRequestHandler* requestHandler, QObject* parent) : + HTTPManager(port, documentRoot, requestHandler, parent), + _certificate(certificate), + _privateKey(privateKey), + _sslRequestHandler(requestHandler) +{ + +} + +void HTTPSManager::incomingConnection(qintptr socketDescriptor) { + QSslSocket* sslSocket = new QSslSocket(this); + + sslSocket->setLocalCertificate(_certificate); + sslSocket->setPrivateKey(_privateKey); + + if (sslSocket->setSocketDescriptor(socketDescriptor)) { + new HTTPSConnection(sslSocket, this); + } else { + delete sslSocket; + } +} + +bool HTTPSManager::handleHTTPRequest(HTTPConnection* connection, const QUrl &url) { + return handleHTTPSRequest(reinterpret_cast(connection), url); +} + +bool HTTPSManager::handleHTTPSRequest(HTTPSConnection* connection, const QUrl& url) { + return HTTPManager::handleHTTPRequest(connection, url); +} + +bool HTTPSManager::requestHandledByRequestHandler(HTTPConnection* connection, const QUrl& url) { + return _sslRequestHandler && _sslRequestHandler->handleHTTPSRequest(reinterpret_cast(connection), url); +} \ No newline at end of file diff --git a/libraries/embedded-webserver/src/HTTPSManager.h b/libraries/embedded-webserver/src/HTTPSManager.h new file mode 100644 index 0000000000..fe7c4dc065 --- /dev/null +++ b/libraries/embedded-webserver/src/HTTPSManager.h @@ -0,0 +1,50 @@ +// +// HTTPSManager.h +// libraries/embedded-webserver/src +// +// Created by Stephen Birarda on 2014-04-24. +// Copyright 2014 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 +// + +#ifndef hifi_HTTPSManager_h +#define hifi_HTTPSManager_h + +#include +#include + +#include "HTTPManager.h" + +class HTTPSRequestHandler : public HTTPRequestHandler { +public: + /// Handles an HTTPS request + virtual bool handleHTTPSRequest(HTTPSConnection* connection, const QUrl& url) = 0; +}; + +class HTTPSManager : public HTTPManager, public HTTPSRequestHandler { + Q_OBJECT +public: + HTTPSManager(quint16 port, + const QSslCertificate& certificate, + const QSslKey& privateKey, + const QString& documentRoot, + HTTPSRequestHandler* requestHandler = NULL, QObject* parent = 0); + + void setCertificate(const QSslCertificate& certificate) { _certificate = certificate; } + void setPrivateKey(const QSslKey& privateKey) { _privateKey = privateKey; } + + bool handleHTTPRequest(HTTPConnection* connection, const QUrl& url); + bool handleHTTPSRequest(HTTPSConnection* connection, const QUrl& url); + +protected: + void incomingConnection(qintptr socketDescriptor); + bool requestHandledByRequestHandler(HTTPConnection* connection, const QUrl& url); +private: + QSslCertificate _certificate; + QSslKey _privateKey; + HTTPSRequestHandler* _sslRequestHandler; +}; + +#endif // hifi_HTTPSManager_h \ No newline at end of file diff --git a/libraries/fbx/src/FBXReader.cpp b/libraries/fbx/src/FBXReader.cpp index 9389d0abf8..a21ed2627a 100644 --- a/libraries/fbx/src/FBXReader.cpp +++ b/libraries/fbx/src/FBXReader.cpp @@ -406,7 +406,7 @@ QVariantHash parseMapping(QIODevice* device) { QVector createVec3Vector(const QVector& doubleVector) { QVector values; - for (const double* it = doubleVector.constData(), *end = it + doubleVector.size(); it != end; ) { + for (const double* it = doubleVector.constData(), *end = it + (doubleVector.size() / 3 * 3); it != end; ) { float x = *it++; float y = *it++; float z = *it++; @@ -417,7 +417,7 @@ QVector createVec3Vector(const QVector& doubleVector) { QVector createVec2Vector(const QVector& doubleVector) { QVector values; - for (const double* it = doubleVector.constData(), *end = it + doubleVector.size(); it != end; ) { + for (const double* it = doubleVector.constData(), *end = it + (doubleVector.size() / 2 * 2); it != end; ) { float s = *it++; float t = *it++; values.append(glm::vec2(s, -t)); @@ -432,58 +432,59 @@ glm::mat4 createMat4(const QVector& doubleVector) { doubleVector.at(12), doubleVector.at(13), doubleVector.at(14), doubleVector.at(15)); } -QVector getIntVector(const QVariantList& properties, int index) { - if (index >= properties.size()) { +QVector getIntVector(const FBXNode& node) { + foreach (const FBXNode& child, node.children) { + if (child.name == "a") { + return getIntVector(child); + } + } + if (node.properties.isEmpty()) { return QVector(); } - QVector vector = properties.at(index).value >(); + QVector vector = node.properties.at(0).value >(); if (!vector.isEmpty()) { return vector; } - for (; index < properties.size(); index++) { - vector.append(properties.at(index).toInt()); + for (int i = 0; i < node.properties.size(); i++) { + vector.append(node.properties.at(i).toInt()); } return vector; } -QVector getLongVector(const QVariantList& properties, int index) { - if (index >= properties.size()) { - return QVector(); +QVector getFloatVector(const FBXNode& node) { + foreach (const FBXNode& child, node.children) { + if (child.name == "a") { + return getFloatVector(child); + } } - QVector vector = properties.at(index).value >(); - if (!vector.isEmpty()) { - return vector; - } - for (; index < properties.size(); index++) { - vector.append(properties.at(index).toLongLong()); - } - return vector; -} - -QVector getFloatVector(const QVariantList& properties, int index) { - if (index >= properties.size()) { + if (node.properties.isEmpty()) { return QVector(); } - QVector vector = properties.at(index).value >(); + QVector vector = node.properties.at(0).value >(); if (!vector.isEmpty()) { return vector; } - for (; index < properties.size(); index++) { - vector.append(properties.at(index).toFloat()); + for (int i = 0; i < node.properties.size(); i++) { + vector.append(node.properties.at(i).toFloat()); } return vector; } -QVector getDoubleVector(const QVariantList& properties, int index) { - if (index >= properties.size()) { +QVector getDoubleVector(const FBXNode& node) { + foreach (const FBXNode& child, node.children) { + if (child.name == "a") { + return getDoubleVector(child); + } + } + if (node.properties.isEmpty()) { return QVector(); } - QVector vector = properties.at(index).value >(); + QVector vector = node.properties.at(0).value >(); if (!vector.isEmpty()) { return vector; } - for (; index < properties.size(); index++) { - vector.append(properties.at(index).toDouble()); + for (int i = 0; i < node.properties.size(); i++) { + vector.append(node.properties.at(i).toDouble()); } return vector; } @@ -697,21 +698,30 @@ public: }; void appendIndex(MeshData& data, QVector& indices, int index) { + if (index >= data.polygonIndices.size()) { + return; + } int vertexIndex = data.polygonIndices.at(index); if (vertexIndex < 0) { vertexIndex = -vertexIndex - 1; } - Vertex vertex; vertex.originalIndex = vertexIndex; + + glm::vec3 position; + if (vertexIndex < data.vertices.size()) { + position = data.vertices.at(vertexIndex); + } glm::vec3 normal; - if (data.normalIndices.isEmpty()) { - normal = data.normals.at(data.normalsByVertex ? vertexIndex : index); - - } else { - int normalIndex = data.normalIndices.at(data.normalsByVertex ? vertexIndex : index); - if (normalIndex >= 0) { + int normalIndex = data.normalsByVertex ? vertexIndex : index; + if (data.normalIndices.isEmpty()) { + if (normalIndex < data.normals.size()) { + normal = data.normals.at(normalIndex); + } + } else if (normalIndex < data.normalIndices.size()) { + normalIndex = data.normalIndices.at(normalIndex); + if (normalIndex >= 0 && normalIndex < data.normals.size()) { normal = data.normals.at(normalIndex); } } @@ -720,9 +730,9 @@ void appendIndex(MeshData& data, QVector& indices, int index) { if (index < data.texCoords.size()) { vertex.texCoord = data.texCoords.at(index); } - } else { + } else if (index < data.texCoordIndices.size()) { int texCoordIndex = data.texCoordIndices.at(index); - if (texCoordIndex >= 0) { + if (texCoordIndex >= 0 && texCoordIndex < data.texCoords.size()) { vertex.texCoord = data.texCoords.at(texCoordIndex); } } @@ -733,7 +743,7 @@ void appendIndex(MeshData& data, QVector& indices, int index) { indices.append(newIndex); data.indices.insert(vertex, newIndex); data.extracted.newIndices.insert(vertexIndex, newIndex); - data.extracted.mesh.vertices.append(data.vertices.at(vertexIndex)); + data.extracted.mesh.vertices.append(position); data.extracted.mesh.normals.append(normal); data.extracted.mesh.texCoords.append(vertex.texCoord); @@ -749,44 +759,51 @@ ExtractedMesh extractMesh(const FBXNode& object) { QVector textures; foreach (const FBXNode& child, object.children) { if (child.name == "Vertices") { - data.vertices = createVec3Vector(getDoubleVector(child.properties, 0)); + data.vertices = createVec3Vector(getDoubleVector(child)); } else if (child.name == "PolygonVertexIndex") { - data.polygonIndices = getIntVector(child.properties, 0); + data.polygonIndices = getIntVector(child); } else if (child.name == "LayerElementNormal") { data.normalsByVertex = false; + bool indexToDirect = false; foreach (const FBXNode& subdata, child.children) { if (subdata.name == "Normals") { - data.normals = createVec3Vector(getDoubleVector(subdata.properties, 0)); + data.normals = createVec3Vector(getDoubleVector(subdata)); } else if (subdata.name == "NormalsIndex") { - data.normalIndices = getIntVector(subdata.properties, 0); + data.normalIndices = getIntVector(subdata); - } else if (subdata.name == "MappingInformationType" && - subdata.properties.at(0) == "ByVertice") { + } else if (subdata.name == "MappingInformationType" && subdata.properties.at(0) == "ByVertice") { data.normalsByVertex = true; + + } else if (subdata.name == "ReferenceInformationType" && subdata.properties.at(0) == "IndexToDirect") { + indexToDirect = true; } } + if (indexToDirect && data.normalIndices.isEmpty()) { + // hack to work around wacky Makehuman exports + data.normalsByVertex = true; + } } else if (child.name == "LayerElementUV" && child.properties.at(0).toInt() == 0) { foreach (const FBXNode& subdata, child.children) { if (subdata.name == "UV") { - data.texCoords = createVec2Vector(getDoubleVector(subdata.properties, 0)); + data.texCoords = createVec2Vector(getDoubleVector(subdata)); } else if (subdata.name == "UVIndex") { - data.texCoordIndices = getIntVector(subdata.properties, 0); + data.texCoordIndices = getIntVector(subdata); } } } else if (child.name == "LayerElementMaterial") { foreach (const FBXNode& subdata, child.children) { if (subdata.name == "Materials") { - materials = getIntVector(subdata.properties, 0); + materials = getIntVector(subdata); } } } else if (child.name == "LayerElementTexture") { foreach (const FBXNode& subdata, child.children) { if (subdata.name == "TextureId") { - textures = getIntVector(subdata.properties, 0); + textures = getIntVector(subdata); } } } @@ -797,7 +814,7 @@ ExtractedMesh extractMesh(const FBXNode& object) { QHash, int> materialTextureParts; for (int beginIndex = 0; beginIndex < data.polygonIndices.size(); polygonIndex++) { int endIndex = beginIndex; - while (data.polygonIndices.at(endIndex++) >= 0); + while (endIndex < data.polygonIndices.size() && data.polygonIndices.at(endIndex++) >= 0); QPair materialTexture((polygonIndex < materials.size()) ? materials.at(polygonIndex) : 0, (polygonIndex < textures.size()) ? textures.at(polygonIndex) : 0); @@ -820,7 +837,7 @@ ExtractedMesh extractMesh(const FBXNode& object) { appendIndex(data, part.triangleIndices, beginIndex); appendIndex(data, part.triangleIndices, nextIndex++); appendIndex(data, part.triangleIndices, nextIndex); - if (data.polygonIndices.at(nextIndex) < 0) { + if (nextIndex >= data.polygonIndices.size() || data.polygonIndices.at(nextIndex) < 0) { break; } } @@ -835,13 +852,13 @@ FBXBlendshape extractBlendshape(const FBXNode& object) { FBXBlendshape blendshape; foreach (const FBXNode& data, object.children) { if (data.name == "Indexes") { - blendshape.indices = getIntVector(data.properties, 0); + blendshape.indices = getIntVector(data); } else if (data.name == "Vertices") { - blendshape.vertices = createVec3Vector(getDoubleVector(data.properties, 0)); + blendshape.vertices = createVec3Vector(getDoubleVector(data)); } else if (data.name == "Normals") { - blendshape.normals = createVec3Vector(getDoubleVector(data.properties, 0)); + blendshape.normals = createVec3Vector(getDoubleVector(data)); } } return blendshape; @@ -1016,7 +1033,13 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping) foreach (const FBXNode& object, child.children) { if (object.name == "SceneInfo") { foreach (const FBXNode& subobject, object.children) { - if (subobject.name == "Properties70") { + if (subobject.name == "MetaData") { + foreach (const FBXNode& subsubobject, subobject.children) { + if (subsubobject.name == "Author") { + geometry.author = subsubobject.properties.at(0).toString(); + } + } + } else if (subobject.name == "Properties70") { foreach (const FBXNode& subsubobject, subobject.children) { if (subsubobject.name == "P" && subsubobject.properties.size() >= 5 && subsubobject.properties.at(0) == "Original|ApplicationName") { @@ -1262,13 +1285,13 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping) Cluster cluster; foreach (const FBXNode& subobject, object.children) { if (subobject.name == "Indexes") { - cluster.indices = getIntVector(subobject.properties, 0); + cluster.indices = getIntVector(subobject); } else if (subobject.name == "Weights") { - cluster.weights = getDoubleVector(subobject.properties, 0); + cluster.weights = getDoubleVector(subobject); } else if (subobject.name == "TransformLink") { - QVector values = getDoubleVector(subobject.properties, 0); + QVector values = getDoubleVector(subobject); cluster.transformLink = createMat4(values); } } @@ -1290,7 +1313,7 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping) AnimationCurve curve; foreach (const FBXNode& subobject, object.children) { if (subobject.name == "KeyValueFloat") { - curve.values = getFloatVector(subobject.properties, 0); + curve.values = getFloatVector(subobject); } } animationCurves.insert(getID(object.properties), curve); diff --git a/libraries/fbx/src/FBXReader.h b/libraries/fbx/src/FBXReader.h index ea8b8f517d..a4b04825ef 100644 --- a/libraries/fbx/src/FBXReader.h +++ b/libraries/fbx/src/FBXReader.h @@ -47,6 +47,9 @@ public: /// \return true if point is within current limits bool containsPoint(const glm::vec3& point) const; + /// \return whether or not the extents are empty + bool isEmpty() { return minimum == maximum; } + glm::vec3 minimum; glm::vec3 maximum; }; @@ -174,6 +177,7 @@ public: class FBXGeometry { public: + QString author; QString applicationName; ///< the name of the application that generated the model QVector joints; diff --git a/libraries/networking/src/AccountManager.h b/libraries/networking/src/AccountManager.h index 8df75195cf..6bdf5d76d8 100644 --- a/libraries/networking/src/AccountManager.h +++ b/libraries/networking/src/AccountManager.h @@ -55,9 +55,7 @@ public: void requestAccessToken(const QString& login, const QString& password); - QString getUsername() const { return _accountInfo.getUsername(); } - - const QString& getXMPPPassword() const { return _accountInfo.getXMPPPassword(); } + const DataServerAccountInfo& getAccountInfo() const { return _accountInfo; } void destroy() { delete _networkAccessManager; } diff --git a/libraries/networking/src/Assignment.cpp b/libraries/networking/src/Assignment.cpp index 620c826846..925ed2930f 100644 --- a/libraries/networking/src/Assignment.cpp +++ b/libraries/networking/src/Assignment.cpp @@ -50,7 +50,8 @@ Assignment::Assignment() : _type(Assignment::AllTypes), _pool(), _location(Assignment::LocalLocation), - _payload() + _payload(), + _isStatic(false) { } @@ -61,7 +62,8 @@ Assignment::Assignment(Assignment::Command command, Assignment::Type type, const _type(type), _pool(pool), _location(location), - _payload() + _payload(), + _isStatic(false) { if (_command == Assignment::CreateCommand) { // this is a newly created assignment, generate a random UUID diff --git a/libraries/networking/src/Assignment.h b/libraries/networking/src/Assignment.h index 54aebf9257..1d97b08bb8 100644 --- a/libraries/networking/src/Assignment.h +++ b/libraries/networking/src/Assignment.h @@ -78,6 +78,9 @@ public: void setPool(const QString& pool) { _pool = pool; }; const QString& getPool() const { return _pool; } + void setIsStatic(bool isStatic) { _isStatic = isStatic; } + bool isStatic() const { return _isStatic; } + const char* getTypeName() const; // implement parseData to return 0 so we can be a subclass of NodeData @@ -94,6 +97,7 @@ protected: QString _pool; /// the destination pool for this assignment Assignment::Location _location; /// the location of the assignment, allows a domain to preferentially use local ACs QByteArray _payload; /// an optional payload attached to this assignment, a maximum for 1024 bytes will be packed + bool _isStatic; /// defines if this assignment needs to be re-queued in the domain-server if it stops being fulfilled }; #endif // hifi_Assignment_h diff --git a/libraries/networking/src/DTLSSession.cpp b/libraries/networking/src/DTLSSession.cpp index 7d375ec327..f0649e4fc8 100644 --- a/libraries/networking/src/DTLSSession.cpp +++ b/libraries/networking/src/DTLSSession.cpp @@ -78,7 +78,7 @@ gnutls_datum_t* DTLSSession::highFidelityCADatum() { static bool datumInitialized = false; static unsigned char HIGHFIDELITY_ROOT_CA_CERT[] = - "-----BEGIN CERTIFICATE-----" + "-----BEGIN CERTIFICATE-----\n" "MIID6TCCA1KgAwIBAgIJANlfRkRD9A8bMA0GCSqGSIb3DQEBBQUAMIGqMQswCQYD\n" "VQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNU2FuIEZyYW5j\n" "aXNjbzEbMBkGA1UEChMSSGlnaCBGaWRlbGl0eSwgSW5jMRMwEQYDVQQLEwpPcGVy\n" @@ -100,7 +100,7 @@ gnutls_datum_t* DTLSSession::highFidelityCADatum() { "SIb3DQEBBQUAA4GBAEkQl3p+lH5vuoCNgyfa67nL0MsBEt+5RSBOgjwCjjASjzou\n" "FTv5w0he2OypgMQb8i/BYtS1lJSFqjPJcSM1Salzrm3xDOK5pOXJ7h6SQLPDVEyf\n" "Hy2/9d/to+99+SOUlvfzfgycgjOc+s/AV7Y+GBd7uzGxUdrN4egCZW1F6/mH\n" - "-----END CERTIFICATE-----"; + "-----END CERTIFICATE-----\n"; if (!datumInitialized) { hifiCADatum.data = HIGHFIDELITY_ROOT_CA_CERT; diff --git a/libraries/networking/src/DomainHandler.h b/libraries/networking/src/DomainHandler.h index 2cc520991c..b78b8875c4 100644 --- a/libraries/networking/src/DomainHandler.h +++ b/libraries/networking/src/DomainHandler.h @@ -22,8 +22,11 @@ #include "HifiSockAddr.h" const QString DEFAULT_DOMAIN_HOSTNAME = "alpha.highfidelity.io"; + const unsigned short DEFAULT_DOMAIN_SERVER_PORT = 40102; const unsigned short DEFAULT_DOMAIN_SERVER_DTLS_PORT = 40103; +const quint16 DOMAIN_SERVER_HTTP_PORT = 40100; +const quint16 DOMAIN_SERVER_HTTPS_PORT = 40101; class DomainHandler : public QObject { Q_OBJECT diff --git a/libraries/networking/src/LimitedNodeList.cpp b/libraries/networking/src/LimitedNodeList.cpp index ce78ec2d10..5692023ab1 100644 --- a/libraries/networking/src/LimitedNodeList.cpp +++ b/libraries/networking/src/LimitedNodeList.cpp @@ -106,6 +106,10 @@ QUdpSocket& LimitedNodeList::getDTLSSocket() { _dtlsSocket->bind(QHostAddress::AnyIPv4, 0, QAbstractSocket::DontShareAddress); + // we're using DTLS and our socket is good to go, so make the required DTLS changes + // DTLS requires that IP_DONTFRAG be set + // This is not accessible on some platforms (OS X) so we need to make sure DTLS still works without it + #if defined(IP_DONTFRAG) || defined(IP_MTU_DISCOVER) qDebug() << "Making required DTLS changes to LimitedNodeList DTLS socket."; diff --git a/libraries/networking/src/NodeList.cpp b/libraries/networking/src/NodeList.cpp index fbf2655269..d5c02f8eed 100644 --- a/libraries/networking/src/NodeList.cpp +++ b/libraries/networking/src/NodeList.cpp @@ -378,12 +378,17 @@ void NodeList::sendDomainServerCheckIn() { } } - PacketType domainPacketType = _sessionUUID.isNull() + PacketType domainPacketType = !_domainHandler.isConnected() ? PacketTypeDomainConnectRequest : PacketTypeDomainListRequest; // construct the DS check in packet - QUuid packetUUID = (domainPacketType == PacketTypeDomainListRequest - ? _sessionUUID : _domainHandler.getAssignmentUUID()); + QUuid packetUUID = _sessionUUID; + + if (!_domainHandler.getAssignmentUUID().isNull() && domainPacketType == PacketTypeDomainConnectRequest) { + // this is a connect request and we're an assigned node + // so set our packetUUID as the assignment UUID + packetUUID = _domainHandler.getAssignmentUUID(); + } QByteArray domainServerPacket = byteArrayWithPopulatedHeader(domainPacketType, packetUUID); QDataStream packetStream(&domainServerPacket, QIODevice::Append); @@ -404,7 +409,6 @@ void NodeList::sendDomainServerCheckIn() { dtlsSession->writeDatagram(domainServerPacket); } - const int NUM_DOMAIN_SERVER_CHECKINS_PER_STUN_REQUEST = 5; static unsigned int numDomainCheckins = 0; diff --git a/libraries/networking/src/PacketHeaders.h b/libraries/networking/src/PacketHeaders.h index c6b4e3b9e5..844fce77fe 100644 --- a/libraries/networking/src/PacketHeaders.h +++ b/libraries/networking/src/PacketHeaders.h @@ -38,7 +38,7 @@ enum PacketType { PacketTypeDomainListRequest, PacketTypeRequestAssignment, PacketTypeCreateAssignment, - PacketTypeDataServerPut, // reusable + PacketTypeDomainOAuthRequest, PacketTypeDataServerGet, // reusable PacketTypeDataServerSend, // reusable PacketTypeDataServerConfirm, @@ -72,7 +72,7 @@ typedef char PacketVersion; const QSet NON_VERIFIED_PACKETS = QSet() << PacketTypeDomainServerRequireDTLS << PacketTypeDomainConnectRequest - << PacketTypeDomainList << PacketTypeDomainListRequest + << PacketTypeDomainList << PacketTypeDomainListRequest << PacketTypeDomainOAuthRequest << PacketTypeCreateAssignment << PacketTypeRequestAssignment << PacketTypeStunResponse << PacketTypeNodeJsonStats << PacketTypeVoxelQuery << PacketTypeParticleQuery << PacketTypeModelQuery; diff --git a/libraries/script-engine/src/Vec3.cpp b/libraries/script-engine/src/Vec3.cpp index badc980913..0cbb43f89a 100644 --- a/libraries/script-engine/src/Vec3.cpp +++ b/libraries/script-engine/src/Vec3.cpp @@ -17,6 +17,10 @@ glm::vec3 Vec3::cross(const glm::vec3& v1, const glm::vec3& v2) { return glm::cross(v1,v2); } +float Vec3::dot(const glm::vec3& v1, const glm::vec3& v2) { + return glm::dot(v1,v2); +} + glm::vec3 Vec3::multiply(const glm::vec3& v1, float f) { return v1 * f; } diff --git a/libraries/script-engine/src/Vec3.h b/libraries/script-engine/src/Vec3.h index e401cd71bd..5a3eeca7be 100644 --- a/libraries/script-engine/src/Vec3.h +++ b/libraries/script-engine/src/Vec3.h @@ -26,6 +26,7 @@ class Vec3 : public QObject { public slots: glm::vec3 cross(const glm::vec3& v1, const glm::vec3& v2); + float dot(const glm::vec3& v1, const glm::vec3& v2); glm::vec3 multiply(const glm::vec3& v1, float f); glm::vec3 multiplyQbyV(const glm::quat& q, const glm::vec3& v); glm::vec3 sum(const glm::vec3& v1, const glm::vec3& v2);