Merge branch 'master' of github.com:highfidelity/hifi into atp-client

This commit is contained in:
Seth Alves 2017-07-06 17:28:55 -07:00
commit b8875a8902
21 changed files with 490 additions and 152 deletions

View file

@ -17,8 +17,8 @@
#include <QtCore/QThread>
#include <LogHandler.h>
#include <SharedUtil.h>
#include <HifiConfigVariantMap.h>
#include <SharedUtil.h>
#include <ShutdownEventListener.h>
#include "Assignment.h"

View file

@ -50,6 +50,7 @@
{
"label": "Places / Paths",
"html_id": "places_paths",
"restart": false,
"settings": [
{
"name": "paths",
@ -75,6 +76,7 @@
{
"name": "descriptors",
"label": "Description",
"restart": false,
"help": "This data will be queryable from your server. It may be collected by High Fidelity and used to share your domain with others.",
"settings": [
{

View file

@ -34,10 +34,24 @@ $(document).ready(function(){
});
$('#nodes-table tbody').html(nodeTemplate(json));
}).fail(function(jqXHR, textStatus, errorThrown) {
// we assume a 401 means the DS has restarted
// and no longer has our OAuth produced uuid
// so just reload and re-auth
if (jqXHR.status == 401) {
location.reload();
}
});
$.getJSON("assignments.json", function(json){
$('#assignments-table tbody').html(queuedTemplate(json));
}).fail(function(jqXHR, textStatus, errorThrown) {
// we assume a 401 means the DS has restarted
// and no longer has our OAuth produced uuid
// so just reload and re-auth
if (jqXHR.status == 401) {
location.reload();
}
});
}

View file

@ -40,11 +40,11 @@
#include <LogHandler.h>
#include <PathUtils.h>
#include <NumericalConstants.h>
#include <Trace.h>
#include <StatTracker.h>
#include "DomainServerNodeData.h"
#include "NodeConnectionData.h"
#include <Trace.h>
#include <StatTracker.h>
int const DomainServer::EXIT_CODE_REBOOT = 234923;
@ -162,8 +162,10 @@ DomainServer::DomainServer(int argc, char* argv[]) :
_gatekeeper.preloadAllowedUserPublicKeys(); // so they can connect on first request
//send signal to DomainMetadata when descriptors changed
_metadata = new DomainMetadata(this);
connect(&_settingsManager, &DomainServerSettingsManager::settingsUpdated,
_metadata, &DomainMetadata::descriptorsChanged);
qDebug() << "domain-server is running";
static const QString AC_SUBNET_WHITELIST_SETTING_PATH = "security.ac_subnet_whitelist";
@ -1972,7 +1974,8 @@ bool DomainServer::handleHTTPRequest(HTTPConnection* connection, const QUrl& url
return _settingsManager.handleAuthenticatedHTTPRequest(connection, url);
}
const QString HIFI_SESSION_COOKIE_KEY = "DS_WEB_SESSION_UUID";
static const QString HIFI_SESSION_COOKIE_KEY = "DS_WEB_SESSION_UUID";
static const QString STATE_QUERY_KEY = "state";
bool DomainServer::handleHTTPSRequest(HTTPSConnection* connection, const QUrl &url, bool skipSubHandler) {
qDebug() << "HTTPS request received at" << url.toString();
@ -1983,10 +1986,9 @@ bool DomainServer::handleHTTPSRequest(HTTPSConnection* connection, const QUrl &u
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()) {
if (!authorizationCode.isEmpty() && !stateUUID.isNull() && _webAuthenticationStateSet.remove(stateUUID)) {
// 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";
@ -2004,47 +2006,83 @@ bool DomainServer::handleHTTPSRequest(HTTPSConnection* connection, const QUrl &u
tokenRequest.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded");
QNetworkReply* tokenReply = NetworkAccessManager::getInstance().post(tokenRequest, tokenPostBody.toLocal8Bit());
connect(tokenReply, &QNetworkReply::finished, this, &DomainServer::tokenGrantFinished);
if (_webAuthenticationStateSet.remove(stateUUID)) {
// this is a web user who wants to auth to access web interface
// we hold the response back to them until we get their profile information
// and can decide if they are let in or not
// add this connection to our list of pending connections so that we can hold the response
_pendingOAuthConnections.insert(stateUUID, connection);
QEventLoop loop;
connect(tokenReply, &QNetworkReply::finished, &loop, &QEventLoop::quit);
// set the state UUID on the reply so that we can associate the response with the connection later
tokenReply->setProperty(STATE_QUERY_KEY.toLocal8Bit(), stateUUID);
// start the loop for the token request
loop.exec();
return true;
} else {
connection->respond(HTTPConnection::StatusCode400);
return true;
}
} else {
return false;
}
}
HTTPSConnection* DomainServer::connectionFromReplyWithState(QNetworkReply* reply) {
// grab the UUID state property from the reply
QUuid stateUUID = reply->property(STATE_QUERY_KEY.toLocal8Bit()).toUuid();
if (!stateUUID.isNull()) {
return _pendingOAuthConnections.take(stateUUID);
} else {
return nullptr;
}
}
void DomainServer::tokenGrantFinished() {
auto tokenReply = qobject_cast<QNetworkReply*>(sender());
if (tokenReply) {
if (tokenReply->error() == QNetworkReply::NoError) {
// now that we have a token for this profile, send off a profile request
QNetworkReply* profileReply = profileRequestGivenTokenReply(tokenReply);
// stop the loop once the profileReply is complete
connect(profileReply, &QNetworkReply::finished, &loop, &QEventLoop::quit);
// forward along the state UUID that we kept with the token request
profileReply->setProperty(STATE_QUERY_KEY.toLocal8Bit(), tokenReply->property(STATE_QUERY_KEY.toLocal8Bit()));
// restart the loop for the profile request
loop.exec();
connect(profileReply, &QNetworkReply::finished, this, &DomainServer::profileRequestFinished);
} else {
// the token grant failed, send back a 500 (assuming the connection is still around)
auto connection = connectionFromReplyWithState(tokenReply);
if (connection) {
connection->respond(HTTPConnection::StatusCode500);
}
}
tokenReply->deleteLater();
}
}
void DomainServer::profileRequestFinished() {
auto profileReply = qobject_cast<QNetworkReply*>(sender());
if (profileReply) {
auto connection = connectionFromReplyWithState(profileReply);
if (connection) {
if (profileReply->error() == QNetworkReply::NoError) {
// call helper method to get cookieHeaders
Headers cookieHeaders = setupCookieHeadersFromProfileReply(profileReply);
connection->respond(HTTPConnection::StatusCode302, QByteArray(),
HTTPConnection::DefaultContentType, cookieHeaders);
delete tokenReply;
delete profileReply;
// we've redirected the user back to our homepage
return true;
}
}
// respond with a 200 code indicating that login is complete
connection->respond(HTTPConnection::StatusCode200);
return true;
} else {
return false;
// the profile request failed, send back a 500 (assuming the connection is still around)
connection->respond(HTTPConnection::StatusCode500);
}
}
profileReply->deleteLater();
}
}
@ -2103,6 +2141,14 @@ bool DomainServer::isAuthenticatedRequest(HTTPConnection* connection, const QUrl
// the user does not have allowed username or role, return 401
return false;
} else {
static const QByteArray REQUESTED_WITH_HEADER = "X-Requested-With";
static const QString XML_REQUESTED_WITH = "XMLHttpRequest";
if (connection->requestHeaders().value(REQUESTED_WITH_HEADER) == XML_REQUESTED_WITH) {
// unauthorized XHR requests get a 401 and not a 302, since there isn't an XHR
// path to OAuth authorize
connection->respond(HTTPConnection::StatusCode401, UNAUTHENTICATED_BODY);
} else {
// re-direct this user to OAuth page
@ -2120,6 +2166,7 @@ bool DomainServer::isAuthenticatedRequest(HTTPConnection* connection, const QUrl
connection->respond(HTTPConnection::StatusCode302,
QByteArray(), HTTPConnection::DefaultContentType, redirectHeaders);
}
// we don't know about this user yet, so they are not yet authenticated
return false;

View file

@ -111,6 +111,9 @@ private slots:
void updateDownstreamNodes();
void updateUpstreamNodes();
void tokenGrantFinished();
void profileRequestFinished();
signals:
void iceServerChanged();
void userConnected();
@ -178,6 +181,8 @@ private:
void updateReplicationNodes(ReplicationServerDirection direction);
HTTPSConnection* connectionFromReplyWithState(QNetworkReply* reply);
SubnetList _acSubnetWhitelist;
std::vector<QString> _replicatedUsernames;
@ -235,6 +240,8 @@ private:
bool _sendICEServerAddressToMetaverseAPIInProgress { false };
bool _sendICEServerAddressToMetaverseAPIRedo { false };
QHash<QUuid, QPointer<HTTPSConnection>> _pendingOAuthConnections;
};

View file

@ -1198,6 +1198,7 @@ bool DomainServerSettingsManager::recurseJSONObjectAndOverwriteSettings(const QJ
static const QString SECURITY_ROOT_KEY = "security";
static const QString AC_SUBNET_WHITELIST_KEY = "ac_subnet_whitelist";
static const QString BROADCASTING_KEY = "broadcasting";
static const QString DESCRIPTION_ROOT_KEY = "descriptors";
auto& settingsVariant = _configMap.getConfig();
bool needRestart = false;
@ -1249,7 +1250,7 @@ bool DomainServerSettingsManager::recurseJSONObjectAndOverwriteSettings(const QJ
if (!matchingDescriptionObject.isEmpty()) {
updateSetting(rootKey, rootValue, *thisMap, matchingDescriptionObject);
if (rootKey != SECURITY_ROOT_KEY && rootKey != BROADCASTING_KEY) {
if (rootKey != SECURITY_ROOT_KEY && rootKey != BROADCASTING_KEY && rootKey != SETTINGS_PATHS_KEY ) {
needRestart = true;
}
} else {
@ -1265,7 +1266,7 @@ bool DomainServerSettingsManager::recurseJSONObjectAndOverwriteSettings(const QJ
if (!matchingDescriptionObject.isEmpty()) {
const QJsonValue& settingValue = rootValue.toObject()[settingKey];
updateSetting(settingKey, settingValue, *thisMap, matchingDescriptionObject);
if ((rootKey != SECURITY_ROOT_KEY && rootKey != BROADCASTING_KEY)
if ((rootKey != SECURITY_ROOT_KEY && rootKey != BROADCASTING_KEY && rootKey != DESCRIPTION_ROOT_KEY)
|| settingKey == AC_SUBNET_WHITELIST_KEY) {
needRestart = true;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

View file

@ -0,0 +1,64 @@
//
// ImageMessageBox.qml
//
// Created by Dante Ruiz on 7/5/2017
// Copyright 2017 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
//
import QtQuick 2.5
import QtQuick.Controls 1.4
import "../styles-uit"
Item {
id: imageBox
visible: false
anchors.fill: parent
property alias source: image.source
property alias imageWidth: image.width
property alias imageHeight: image.height
Rectangle {
anchors.fill: parent
color: "black"
opacity: 0.3
}
Image {
id: image
anchors.centerIn: parent
HiFiGlyphs {
id: closeGlyphButton
text: hifi.glyphs.close
size: 25
anchors {
top: parent.top
topMargin: 15
right: parent.right
rightMargin: 15
}
MouseArea {
anchors.fill: parent
hoverEnabled: true
onEntered: {
parent.text = hifi.glyphs.closeInverted;
}
onExited: {
parent.text = hifi.glyphs.close;
}
onClicked: {
imageBox.visible = false;
}
}
}
}
}

View file

@ -65,7 +65,7 @@ Rectangle {
HiFiGlyphs {
id: image
text: hifi.glyphs.avatar1
text: hifi.glyphs.avatarTPose
size: 190
color: hifi.colors.white

View file

@ -16,6 +16,7 @@ import "../../controls-uit" as HifiControls
StackView {
id: stack
initialItem: inputConfiguration
property alias messageVisible: imageMessageBox.visible
Rectangle {
id: inputConfiguration
anchors.fill: parent
@ -26,6 +27,15 @@ StackView {
property var pluginSettings: null
HifiControls.ImageMessageBox {
id: imageMessageBox
anchors.fill: parent
z: 2000
imageWidth: 442
imageHeight: 670
source: "../../../images/calibration-help.png"
}
Rectangle {
width: inputConfiguration.width
height: 1

View file

@ -54,6 +54,9 @@ Rectangle {
}
MouseArea {
id: mouseArea
@ -64,6 +67,7 @@ Rectangle {
mouse.accepted = false;
}
}
color: hifi.colors.baseGray
RalewayBold {
@ -146,6 +150,7 @@ Rectangle {
label: "Y: offset"
minimumValue: -10
stepSize: 0.0254
value: -0.05
colorScheme: hifi.colorSchemes.dark
onEditingFinished: {
@ -161,6 +166,7 @@ Rectangle {
minimumValue: -10
stepSize: 0.0254
decimals: 4
value: -0.05
colorScheme: hifi.colorSchemes.dark
onEditingFinished: {
@ -290,6 +296,52 @@ Rectangle {
anchors.leftMargin: leftMargin
}
RalewayRegular {
id: info
text: "See Recommended Tracker Placement"
color: hifi.colors.blueHighlight
size: 10
anchors {
left: additional.right
leftMargin: 10
verticalCenter: additional.verticalCenter
}
Rectangle {
id: selected
color: hifi.colors.blueHighlight
width: info.width
height: 1
anchors {
top: info.bottom
topMargin: 1
left: info.left
right: info.right
}
visible: false
}
MouseArea {
anchors.fill: parent;
hoverEnabled: true
onEntered: {
selected.visible = true;
}
onExited: {
selected.visible = false;
}
onClicked: {
stack.messageVisible = true;
}
}
}
Row {
id: feetConfig
anchors.top: additional.bottom
@ -379,6 +431,7 @@ Rectangle {
if (checked) {
hipBox.checked = true;
feetBox.checked = true;
shoulderBox.checked = false;
}
sendConfigurationSettings();
}
@ -416,6 +469,7 @@ Rectangle {
if (checked) {
hipBox.checked = true;
feetBox.checked = true;
chestBox.checked = false;
}
sendConfigurationSettings();
}
@ -652,7 +706,7 @@ Rectangle {
RalewayBold {
id: advanceSettings
text: "Advance Settings"
text: "Advanced Settings"
size: 12
color: hifi.colors.white
@ -683,7 +737,7 @@ Rectangle {
RalewayBold {
id: viveDesktopText
size: 10
text: "Use vive devices in desktop mode"
text: "Use Vive devices in desktop mode"
color: hifi.colors.white
anchors {

View file

@ -98,6 +98,16 @@ StackView {
width: parent.width
height: parent.height
MouseArea {
anchors.fill: parent
propagateComposedEvents: true
onPressed: {
parent.forceActiveFocus();
addressBarDialog.keyboardEnabled = false;
mouse.accepted = false;
}
}
anchors {
right: parent.right
left: parent.left
@ -227,9 +237,9 @@ StackView {
MouseArea {
anchors.fill: parent;
onClicked: {
if (!addressLine.focus || !HMD.active) {
addressLine.focus = true;
addressLine.forceActiveFocus();
if (HMD.active) {
addressBarDialog.keyboardEnabled = HMD.active;
}
tabletRoot.playButtonClickSound();

View file

@ -336,5 +336,6 @@ Item {
readonly property string source: "\ue01c"
readonly property string playback_play: "\ue01d"
readonly property string stop_square: "\ue01e"
readonly property string avatarTPose: "\ue01f"
}
}

View file

@ -952,7 +952,18 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
// Make sure we don't time out during slow operations at startup
updateHeartbeat();
Setting::Handle<bool> firstRun { Settings::firstRun, true };
// once the settings have been loaded, check if we need to flip the default for UserActivityLogger
auto& userActivityLogger = UserActivityLogger::getInstance();
if (!userActivityLogger.isDisabledSettingSet()) {
// the user activity logger is opt-out for Interface
// but it's defaulted to disabled for other targets
// so we need to enable it here if it has never been disabled by the user
userActivityLogger.disable(false);
}
if (userActivityLogger.isEnabled()) {
// sessionRunTime will be reset soon by loadSettings. Grab it now to get previous session value.
// The value will be 0 if the user blew away settings this session, which is both a feature and a bug.
static const QString TESTER = "HIFI_TESTER";
@ -996,14 +1007,13 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
properties["processor_l3_cache_count"] = procInfo.numProcessorCachesL3;
}
// add firstRun flag from settings to launch event
Setting::Handle<bool> firstRun { Settings::firstRun, true };
properties["first_run"] = firstRun.get();
// add the user's machine ID to the launch event
properties["machine_fingerprint"] = uuidStringWithoutCurlyBraces(FingerprintUtils::getMachineFingerprint());
UserActivityLogger::getInstance().logAction("launch", properties);
userActivityLogger.logAction("launch", properties);
}
// Tell our entity edit sender about our known jurisdictions
_entityEditSender.setServerJurisdictions(&_entityServerJurisdictions);

View file

@ -319,7 +319,7 @@ Menu::Menu() {
QString("../../hifi/tablet/TabletLodPreferences.qml"), "LodPreferencesDialog");
});
action = addActionToQMenuAndActionHash(settingsMenu, "Controller Settings");
action = addActionToQMenuAndActionHash(settingsMenu, "Controller Settings...");
connect(action, &QAction::triggered, [] {
auto tablet = DependencyManager::get<TabletScriptingInterface>()->getTablet("com.highfidelity.interface.tablet.system");
auto hmd = DependencyManager::get<HMDScriptingInterface>();

View file

@ -33,6 +33,7 @@ public:
public slots:
bool isEnabled() { return !_disabled.get(); }
bool isDisabledSettingSet() const { return _disabled.isSet(); }
void disable(bool disable);
void logAction(QString action, QJsonObject details = QJsonObject(), JSONCallbackParameters params = JSONCallbackParameters());
@ -53,7 +54,7 @@ private slots:
private:
UserActivityLogger();
Setting::Handle<bool> _disabled { "UserActivityLoggerDisabled", false };
Setting::Handle<bool> _disabled { "UserActivityLoggerDisabled", true };
QElapsedTimer _timer;
};

View file

@ -107,6 +107,7 @@ namespace Setting {
}
bool isSet() const {
maybeInit();
return _isSet;
}

View file

@ -0,0 +1,116 @@
"use strict";
//
// godView.js
// scripts/system/
//
// Created by Brad Hefta-Gaub on 1 Jun 2017
// Copyright 2017 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
//
/* globals HMD, Script, Menu, Tablet, Camera */
/* eslint indent: ["error", 4, { "outerIIFEBody": 0 }] */
(function() { // BEGIN LOCAL_SCOPE
var godView = false;
var GOD_CAMERA_OFFSET = -1; // 1 meter below the avatar
var GOD_VIEW_HEIGHT = 300; // 300 meter above the ground
var ABOVE_GROUND_DROP = 2;
var MOVE_BY = 1;
function moveTo(position) {
if (godView) {
MyAvatar.position = position;
Camera.position = Vec3.sum(MyAvatar.position, {x:0, y: GOD_CAMERA_OFFSET, z: 0});
} else {
MyAvatar.position = position;
}
}
function keyPressEvent(event) {
if (godView) {
switch(event.text) {
case "UP":
moveTo(Vec3.sum(MyAvatar.position, {x:0.0, y: 0, z: -1 * MOVE_BY}));
break;
case "DOWN":
moveTo(Vec3.sum(MyAvatar.position, {x:0, y: 0, z: MOVE_BY}));
break;
case "LEFT":
moveTo(Vec3.sum(MyAvatar.position, {x:-1 * MOVE_BY, y: 0, z: 0}));
break;
case "RIGHT":
moveTo(Vec3.sum(MyAvatar.position, {x:MOVE_BY, y: 0, z: 0}));
break;
}
}
}
function mousePress(event) {
if (godView) {
var pickRay = Camera.computePickRay(event.x, event.y);
var pointingAt = Vec3.sum(pickRay.origin, Vec3.multiply(pickRay.direction,300));
var moveToPosition = { x: pointingAt.x, y: MyAvatar.position.y, z: pointingAt.z };
moveTo(moveToPosition);
}
}
var oldCameraMode = Camera.mode;
function startGodView() {
if (!godView) {
oldCameraMode = Camera.mode;
MyAvatar.position = Vec3.sum(MyAvatar.position, {x:0, y: GOD_VIEW_HEIGHT, z: 0});
Camera.mode = "independent";
Camera.position = Vec3.sum(MyAvatar.position, {x:0, y: GOD_CAMERA_OFFSET, z: 0});
Camera.orientation = Quat.fromPitchYawRollDegrees(-90,0,0);
godView = true;
}
}
function endGodView() {
if (godView) {
Camera.mode = oldCameraMode;
MyAvatar.position = Vec3.sum(MyAvatar.position, {x:0, y: (-1 * GOD_VIEW_HEIGHT) + ABOVE_GROUND_DROP, z: 0});
godView = false;
}
}
var button;
var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system");
function onClicked() {
if (godView) {
endGodView();
} else {
startGodView();
}
}
button = tablet.addButton({
icon: "icons/tablet-icons/switch-desk-i.svg", // FIXME - consider a better icon from Alan
text: "God View"
});
button.clicked.connect(onClicked);
Controller.keyPressEvent.connect(keyPressEvent);
Controller.mousePressEvent.connect(mousePress);
Script.scriptEnding.connect(function () {
if (godView) {
endGodView();
}
button.clicked.disconnect(onClicked);
if (tablet) {
tablet.removeButton(button);
}
Controller.keyPressEvent.disconnect(keyPressEvent);
Controller.mousePressEvent.disconnect(mousePress);
});
}()); // END LOCAL_SCOPE

View file

@ -482,10 +482,10 @@ function populateNearbyUserList(selectData, oldAudioData) {
isPresent: true,
isReplicated: avatar.isReplicated
};
if (id) {
addAvatarNode(id); // No overlay for ourselves
// Everyone needs to see admin status. Username and fingerprint returns default constructor output if the requesting user isn't an admin.
Users.requestUsernameFromID(id);
if (id) {
addAvatarNode(id); // No overlay for ourselves
avatarsOfInterest[id] = true;
} else {
// Return our username from the Account API

View file

@ -46,7 +46,6 @@ ACClientApp::ACClientApp(int argc, char* argv[]) :
const QCommandLineOption listenPortOption("listenPort", "listen port", QString::number(INVALID_PORT));
parser.addOption(listenPortOption);
if (!parser.parse(QCoreApplication::arguments())) {
qCritical() << parser.errorText() << endl;
parser.showHelp();
@ -71,6 +70,7 @@ ACClientApp::ACClientApp(int argc, char* argv[]) :
const_cast<QLoggingCategory*>(&shared())->setEnabled(QtWarningMsg, false);
}
QString domainServerAddress = "127.0.0.1:40103";
if (parser.isSet(domainAddressOption)) {
domainServerAddress = parser.value(domainAddressOption);