diff --git a/examples/easit-oauth-integration/EasitTestOauthDatastore.js b/examples/easit-oauth-integration/EasitTestOauthDatastore.js index 7fcf781e1..c8793cf9b 100644 --- a/examples/easit-oauth-integration/EasitTestOauthDatastore.js +++ b/examples/easit-oauth-integration/EasitTestOauthDatastore.js @@ -77,6 +77,14 @@ fluid.defaults("gpii.oauth2.easitSampleDataStore", { oauth2ClientSecret: false, redirectUri: false, allowDirectGpiiTokenAccess: false + }, + { + id: 6, + name: "First Discovery", + oauth2ClientId: "net.gpii.prefsEditors.firstDiscovery", + oauth2ClientSecret: "client_secret_firstDiscovery", + allowDirectGpiiTokenAccess: false, + allowAddPrefs: true } ], authDecisionsIdSeq: 6, @@ -126,7 +134,8 @@ fluid.defaults("gpii.oauth2.easitSampleDataStore", { selectedPreferences: { "": true }, revoked: false } - ] + ], + clientCredentialsTokensIdSeq: 1 } }); diff --git a/gpii/configs/untrusted.development.all.local.json b/gpii/configs/untrusted.development.all.local.json index 69eda8e98..efed86476 100644 --- a/gpii/configs/untrusted.development.all.local.json +++ b/gpii/configs/untrusted.development.all.local.json @@ -30,14 +30,7 @@ { "record": "http://localhost:8088", "target": "{that cloudBasedConfig}.options.matchMakers.flat.url" - }, - { - "record": "gpii.oauth2.testDataStore", - "target": "{that cloudBasedConfig gpii.oauth2.dataStore}.options.gradeNames" } ] - }, - "modules": [ - "../../testData/security/TestOAuth2DataStore.js" - ] + } } diff --git a/gpii/node_modules/deviceReporter/src/DeviceReporter.js b/gpii/node_modules/deviceReporter/src/DeviceReporter.js index cec37cf9a..fa33a7474 100644 --- a/gpii/node_modules/deviceReporter/src/DeviceReporter.js +++ b/gpii/node_modules/deviceReporter/src/DeviceReporter.js @@ -23,6 +23,7 @@ var fluid = require("infusion"), fluid.require("kettle", require); fluid.require("./DeviceGet.js", require); +fluid.require("./DeviceReporterUtilities.js", require); fluid.defaults("gpii.deviceReporter.base", { gradeNames: ["kettle.app", "autoInit"], diff --git a/gpii/node_modules/deviceReporter/src/DeviceReporterUtilities.js b/gpii/node_modules/deviceReporter/src/DeviceReporterUtilities.js new file mode 100644 index 000000000..b450ddf80 --- /dev/null +++ b/gpii/node_modules/deviceReporter/src/DeviceReporterUtilities.js @@ -0,0 +1,37 @@ +/** + * GPII Device Reporter Utilities. + * + * Copyright 2015 Emergya + * + * Licensed under the New BSD license. You may not use this file except in + * compliance with this License. + * + * The research leading to these results has received funding from the European Union's + * Seventh Framework Programme (FP7/2007-2013) + * under grant agreement no. 289016. + * + * You may obtain a copy of the License at + * https://github.com/GPII/universal/blob/master/LICENSE.txt + */ + +"use strict"; + +var fluid = require("infusion"), + gpii = fluid.registerNamespace("gpii"); + +fluid.registerNamespace("gpii.deviceReporter"); + +/* Marker function for use in isInstalled sections of Solutions Registry + * to identify in a meaningful way that the solution is always installed or + * available to the system. + * This is meant to be used by any OS built-in solution that needs to report + * to the dynamic device reporter that they are always installed. + */ +fluid.defaults("gpii.deviceReporter.alwaysInstalled", { + gradeNames: "fluid.function", + argumentMap: {} +}); + +gpii.deviceReporter.alwaysInstalled = function () { + return true; +}; \ No newline at end of file diff --git a/gpii/node_modules/flowManager/configs/cloudBased.development.all.local.json b/gpii/node_modules/flowManager/configs/cloudBased.development.all.local.json index 99b9c7b2b..cc2dc7065 100644 --- a/gpii/node_modules/flowManager/configs/cloudBased.development.all.local.json +++ b/gpii/node_modules/flowManager/configs/cloudBased.development.all.local.json @@ -17,6 +17,12 @@ }, { "source": "{that}.options.matchMakers", "target": "{that flowManager}.options.matchMakers" + }, { + "source": "{that}.options.authServerUrls", + "target": "{that authServer}.options.urls" + }, { + "source": "{that}.options.oauth2DataStoreGrades", + "target": "{that oauth2DataStore}.options.gradeNames" }], "cloudBasedUrls": { "preferences": "http://localhost:8081/preferences/%userToken", @@ -31,11 +37,19 @@ "url": "http://localhost:8078" } }, + "authServerUrls": { + "_comment": "This is a dependency weak spot in the design. The author of this config has to be able to get this URL right, and the details of it depend on, and duplicate, other parts of the configuration of the system.", + "preferencesPost": "http://localhost:8081/preferences?view=%view" + }, "cloudBasedLogging": true, "flowManagerCloudBasedGrades": ["kettle.urlExpander.distributeDevVariables", "gpii.flowManager.cloudBased", "gpii.flowManager.cloudBased.oauth2"], - "flowManagerCloudBasedServerGrades": ["kettle.use.CORS"] + "flowManagerCloudBasedServerGrades": ["kettle.use.CORS"], + "oauth2DataStoreGrades": ["gpii.oauth2.testDataStore"] }, "includes": [ "./production.json" + ], + "modules": [ + "../../../../testData/security/TestOAuth2DataStore.js" ] } diff --git a/gpii/node_modules/flowManager/test/SaveTests.js b/gpii/node_modules/flowManager/test/SaveTests.js index b5e793eb2..9fe76fc2b 100644 --- a/gpii/node_modules/flowManager/test/SaveTests.js +++ b/gpii/node_modules/flowManager/test/SaveTests.js @@ -30,10 +30,7 @@ kettle.loadTestingSupport(); fluid.registerNamespace("gpii.tests.userSave"); -fluid.require("ontologyHandler", require); -fluid.require("matchMakerFramework", require); -fluid.require("contextManager", require); -fluid.require("transformer", require); +require("universal"); gpii.tests.userSave.testToken1 = "testToken1"; gpii.tests.userSave.prefsDir = path.resolve(__dirname, "../../../../testData/preferences/"); diff --git a/gpii/node_modules/gpii-oauth2/gpii-oauth2-authz-server/src/AuthServer.js b/gpii/node_modules/gpii-oauth2/gpii-oauth2-authz-server/src/AuthServer.js index 7a5dacfa6..0a6bc376b 100644 --- a/gpii/node_modules/gpii-oauth2/gpii-oauth2-authz-server/src/AuthServer.js +++ b/gpii/node_modules/gpii-oauth2/gpii-oauth2-authz-server/src/AuthServer.js @@ -23,6 +23,8 @@ var LocalStrategy = require("passport-local").Strategy; var ClientPasswordStrategy = require("passport-oauth2-client-password").Strategy; var fluid = require("infusion"); +var $ = fluid.registerNamespace("jQuery"); + require("../../gpii-oauth2-datastore"); require("../../gpii-oauth2-utilities"); require("./UserService"); @@ -73,6 +75,11 @@ gpii.oauth2.oauth2orizeServer.listenOauth2orize = function (oauth2orizeServer, c oauth2orizeServer.exchange(oauth2orize.exchange.code(function (client, code, redirectUri, done) { return done(null, authorizationService.exchangeCodeForAccessToken(code, client.id, redirectUri)); })); + + oauth2orizeServer.exchange(oauth2orize.exchange.clientCredentials(function (client, scope, done) { + return done(null, authorizationService.grantClientCredentialsAccessToken(client.id, scope)); + })); + }; // gpii.oauth2.passport @@ -134,6 +141,9 @@ fluid.defaults("gpii.oauth2.dataStoreHolder", { fluid.defaults("gpii.oauth2.authServer", { gradeNames: ["fluid.eventedComponent", "gpii.oauth2.dataStoreHolder", "autoInit"], + urls: { + preferencesPost: "" + }, members: { expressApp: { expander: { @@ -168,7 +178,8 @@ fluid.defaults("gpii.oauth2.authServer", { options: { components: { dataStore: "{gpii.oauth2.dataStoreHolder}.dataStore" - } + }, + urls: "{authServer}.options.urls" } }, clientService: { @@ -506,4 +517,32 @@ gpii.oauth2.authServer.contributeRouteHandlers = function (that, oauth2orizeServ } ); + that.expressApp.post("/add-preferences", + function (req, res) { + var accessToken = gpii.oauth2.parseBearerAuthorizationHeader(req); + if (!accessToken) { + res.send(401); + } else { + var client = that.authorizationService.getClientByClientCredentialsAccessToken(accessToken); + if (!client) { + res.send(404); + } + + var tokenPrivs = that.authorizationService.getClientCredentialsTokenPrivs(accessToken); + if (!tokenPrivs || !tokenPrivs.allowAddPrefs) { + res.send(401); + } else { + var savePrefsPromise = that.authorizationService.savePrefs(req.body, req.query.view); + savePrefsPromise.then(function (data) { + res.json(data); + }, function (err) { + var error = $.extend(err, { + message: "Error when saving preferences: " + err.message + }); + res.status(500).send(error); + }); + } + } + } + ); }; diff --git a/gpii/node_modules/gpii-oauth2/gpii-oauth2-authz-server/src/AuthorizationService.js b/gpii/node_modules/gpii-oauth2/gpii-oauth2-authz-server/src/AuthorizationService.js index 0e6ea47bc..45c1b3124 100644 --- a/gpii/node_modules/gpii-oauth2/gpii-oauth2-authz-server/src/AuthorizationService.js +++ b/gpii/node_modules/gpii-oauth2/gpii-oauth2-authz-server/src/AuthorizationService.js @@ -21,9 +21,23 @@ fluid.registerNamespace("gpii.oauth2"); fluid.defaults("gpii.oauth2.authorizationService", { gradeNames: ["fluid.eventedComponent", "autoInit"], + urls: { + preferencesPost: "" + }, components: { dataStore: { type: "gpii.oauth2.dataStore" + }, + preferencesDataSource: { + type: "kettle.dataSource.URL", + options: { + url: "{authorizationService}.options.urls.preferencesPost", + termMap: { + view: "%view" + }, + writable: true, + writeMethod: "POST" + } } }, invokers: { @@ -67,6 +81,24 @@ fluid.defaults("gpii.oauth2.authorizationService", { getAuthForAccessToken: { func: "{dataStore}.findAuthByAccessToken" // accessToken + }, + getClientByClientCredentialsAccessToken: { + func: "{dataStore}.findClientByClientCredentialsAccessToken" + // accessToken + }, + getClientCredentialsTokenPrivs: { + func: "{dataStore}.findClientCredentialsTokenPrivs" + // accessToken + }, + grantClientCredentialsAccessToken: { + funcName: "gpii.oauth2.authorizationService.grantClientCredentialsAccessToken", + args: ["{dataStore}", "{arguments}.0", "{arguments}.1"] + // clientId, scope + }, + savePrefs: { + funcName: "gpii.oauth2.authorizationService.savePrefs", + args: ["{preferencesDataSource}", "{arguments}.0", "{arguments}.1"] + // preferences, view } } }); @@ -137,3 +169,28 @@ gpii.oauth2.authorizationService.setSelectedPreferences = function (dataStore, u } // TODO else communicate not found? }; + +gpii.oauth2.authorizationService.grantClientCredentialsAccessToken = function (dataStore, clientId, scope) { + // Record the credential client access token if we haven't already + var client = dataStore.findClientById(clientId); + if (!scope || scope.indexOf("add_preferences") === -1 || !client.allowAddPrefs) { + return false; + } + + var clientCredentialsToken = dataStore.findClientCredentialsTokenByClientId(clientId); + if (!clientCredentialsToken) { + var accessToken = gpii.oauth2.authorizationService.generateAccessToken(); + clientCredentialsToken = dataStore.addClientCredentialsToken({ + clientId: clientId, + accessToken: accessToken, + allowAddPrefs: true + }); + } + + return clientCredentialsToken.accessToken; +}; + +gpii.oauth2.authorizationService.savePrefs = function (preferencesDataSource, preferences, view) { + view = view || ""; + return preferencesDataSource.set({"view": view}, preferences); +}; diff --git a/gpii/node_modules/gpii-oauth2/gpii-oauth2-datastore/src/InMemoryDataStore.js b/gpii/node_modules/gpii-oauth2/gpii-oauth2-datastore/src/InMemoryDataStore.js index a3a26b40f..067292c28 100644 --- a/gpii/node_modules/gpii-oauth2/gpii-oauth2-datastore/src/InMemoryDataStore.js +++ b/gpii/node_modules/gpii-oauth2/gpii-oauth2-datastore/src/InMemoryDataStore.js @@ -39,7 +39,9 @@ var fluid = fluid || require("infusion"); clients: [], authDecisionsIdSeq: 1, authDecisions: [], - authCodes: [] + authCodes: [], + clientCredentialsTokensIdSeq: 1, + clientCredentialsTokens: [] }, invokers: { findUserById: { @@ -126,9 +128,43 @@ var fluid = fluid || require("infusion"); funcName: "gpii.oauth2.dataStore.findAccessTokenByOAuth2ClientIdAndGpiiToken", args: ["{that}.model.authDecisions", "{that}.model.users", "{that}.model.clients", "{arguments}.0", "{arguments}.1"] // oauth2ClientId, gpiiToken + }, + findClientCredentialsTokenById: { + funcName: "gpii.oauth2.dataStore.findClientCredentialsTokenById", + args: ["{that}.model.clientCredentialsTokens", "{arguments}.0"] + // clientCredentialsTokenId + }, + findClientCredentialsTokenByClientId: { + funcName: "gpii.oauth2.dataStore.findClientCredentialsTokenByClientId", + args: ["{that}.model.clientCredentialsTokens", "{arguments}.0"] + // clientId + }, + findClientCredentialsTokenByAccessToken: { + funcName: "gpii.oauth2.dataStore.findClientCredentialsTokenByAccessToken", + args: ["{that}.model.clientCredentialsTokens", "{arguments}.0"] + // accessToken + }, + addClientCredentialsToken: { + funcName: "gpii.oauth2.dataStore.addClientCredentialsToken", + args: ["{that}.model", "{that}.applier", "{arguments}.0"] + // clientCredentialsTokenData + }, + revokeClientCredentialsToken: { + funcName: "gpii.oauth2.dataStore.revokeClientCredentialsToken", + args: ["{that}.model.clientCredentialsTokens", "{that}.applier", "{arguments}.0"] + // clientCredentialsTokenId + }, + findClientByClientCredentialsAccessToken: { + funcName: "gpii.oauth2.dataStore.findClientByClientCredentialsAccessToken", + args: ["{that}.model.clientCredentialsTokens", "{that}.model.clients", "{arguments}.0"] + // accessToken + }, + findClientCredentialsTokenPrivs: { + funcName: "gpii.oauth2.dataStore.findClientCredentialsTokenPrivs", + args: ["{that}.model.clientCredentialsTokens", "{arguments}.0"] + // accessToken } } - }); // Users @@ -341,4 +377,60 @@ var fluid = fluid || require("infusion"); return undefined; }; + gpii.oauth2.dataStore.findClientCredentialsTokenById = function (clientCredentialsTokens, id) { + return fluid.find_if(clientCredentialsTokens, function (cct) { + return cct.id === id; + }); + }; + + // Join clientCredentialsTokens and clients + gpii.oauth2.dataStore.findClientCredentialsTokenByClientId = function (clientCredentialsTokens, clientId) { + var clientCredentialsToken = fluid.find_if(clientCredentialsTokens, function (cct) { + return cct.clientId === clientId && cct.revoked === false; + }); + return clientCredentialsToken ? clientCredentialsToken : undefined; + }; + + gpii.oauth2.dataStore.findClientCredentialsTokenByAccessToken = function (clientCredentialsTokens, accessToken) { + var clientCredentialsToken = fluid.find_if(clientCredentialsTokens, function (cct) { + return cct.accessToken === accessToken && cct.revoked === false; + }); + return clientCredentialsToken ? clientCredentialsToken : undefined; + }; + + gpii.oauth2.dataStore.findClientCredentialsTokenPrivs = function (clientCredentialsTokens, accessToken) { + var clientCredentialsToken = gpii.oauth2.dataStore.findClientCredentialsTokenByAccessToken(clientCredentialsTokens, accessToken); + return clientCredentialsToken ? { + allowAddPrefs: clientCredentialsToken.allowAddPrefs + } : undefined; + }; + + gpii.oauth2.dataStore.addClientCredentialsToken = function (model, applier, clientCredentialsTokenData) { + var clientCredentialsTokenId = model.clientCredentialsTokensIdSeq; + applier.change("clientCredentialsTokensIdSeq", model.clientCredentialsTokensIdSeq + 1); + var clientCredentialsToken = { + id: clientCredentialsTokenId, // primary key + clientId: clientCredentialsTokenData.clientId, // foreign key + accessToken: clientCredentialsTokenData.accessToken, + allowAddPrefs: clientCredentialsTokenData.allowAddPrefs, + revoked: false + }; + model.clientCredentialsTokens.push(clientCredentialsToken); + applier.change("clientCredentialsTokens", model.clientCredentialsTokens); + return clientCredentialsToken; + }; + + gpii.oauth2.dataStore.revokeClientCredentialsToken = function (clientCredentialsTokens, applier, clientCredentialsTokenId) { + var clientCredentialsToken = gpii.oauth2.dataStore.findClientCredentialsTokenById(clientCredentialsTokens, clientCredentialsTokenId); + if (clientCredentialsToken) { + clientCredentialsToken.revoked = true; + applier.change("clientCredentialsTokens", clientCredentialsTokens); + } + }; + + // Join clientCredentialsTokens and clients + gpii.oauth2.dataStore.findClientByClientCredentialsAccessToken = function (clientCredentialsTokens, clients, accessToken) { + var clientCredentialsToken = gpii.oauth2.dataStore.findClientCredentialsTokenByAccessToken(clientCredentialsTokens, accessToken); + return clientCredentialsToken ? gpii.oauth2.dataStore.findClientById(clients, clientCredentialsToken.clientId) : clientCredentialsToken; + }; })(); diff --git a/gpii/node_modules/gpii-oauth2/gpii-oauth2-datastore/test/js/DataStoreTests.js b/gpii/node_modules/gpii-oauth2/gpii-oauth2-datastore/test/js/DataStoreTests.js index f051889a8..396ac5396 100644 --- a/gpii/node_modules/gpii-oauth2/gpii-oauth2-datastore/test/js/DataStoreTests.js +++ b/gpii/node_modules/gpii-oauth2/gpii-oauth2-datastore/test/js/DataStoreTests.js @@ -56,6 +56,14 @@ var fluid = fluid || require("infusion"); oauth2ClientSecret: "client_secret_C", redirectUri: "http://example.com/callback_C", allowDirectGpiiTokenAccess: false + }, + { + id: 4, + name: "Client D", + oauth2ClientId: "client_id_D", + oauth2ClientSecret: "client_secret_D", + allowDirectGpiiTokenAccess: false, + allowAddPrefs: true } ] } @@ -87,6 +95,13 @@ var fluid = fluid || require("infusion"); selectedPreferences: "selected preferences 3" }; + gpii.tests.oauth2.dataStore.testdata.clientCredentialsToken1 = { + clientId: 4, + accessToken: "access_token_4", + revoked: false, + allowAddPrefs: true + }; + gpii.tests.oauth2.dataStore.verifyAlice = function (user) { jqUnit.assertEquals("username is alice", "alice", user.username); jqUnit.assertEquals("password is a", "a", user.password); @@ -111,6 +126,14 @@ var fluid = fluid || require("infusion"); jqUnit.assertEquals("redirectUri", "http://example.com/callback_B", client.redirectUri); }; + gpii.tests.oauth2.dataStore.verifyClientD = function (client) { + jqUnit.assertEquals("name", "Client D", client.name); + jqUnit.assertEquals("oauth2ClientId", "client_id_D", client.oauth2ClientId); + jqUnit.assertEquals("oauth2ClientSecret", "client_secret_D", client.oauth2ClientSecret); + jqUnit.assertFalse("allowDirectGpiiTokenAccess", client.allowDirectGpiiTokenAccess); + jqUnit.assertTrue("allowAddPrefs", client.allowAddPrefs); + }; + gpii.tests.oauth2.dataStore.addAuthDecision1 = function (dataStore) { return dataStore.addAuthDecision(gpii.tests.oauth2.dataStore.testdata.authDecision1); }; @@ -147,6 +170,13 @@ var fluid = fluid || require("infusion"); jqUnit.assertFalse("not revoked", authDecision.revoked); }; + gpii.tests.oauth2.dataStore.verifyClientCredentialsToken1 = function (clientCredentialsToken, expectedRevoked) { + jqUnit.assertEquals("The value of clientId is expected", gpii.tests.oauth2.dataStore.testdata.clientCredentialsToken1.clientId, clientCredentialsToken.clientId); + jqUnit.assertEquals("The value of accessToken is expected", gpii.tests.oauth2.dataStore.testdata.clientCredentialsToken1.accessToken, clientCredentialsToken.accessToken); + jqUnit.assertEquals("The value of allowAddPrefs is expected", gpii.tests.oauth2.dataStore.testdata.clientCredentialsToken1.allowAddPrefs, clientCredentialsToken.allowAddPrefs); + jqUnit.assertEquals("The value of revoked is expected", expectedRevoked, clientCredentialsToken.revoked); + }; + gpii.tests.oauth2.dataStore.saveAuthCode1 = function (dataStore, authDecisionId) { dataStore.saveAuthCode(authDecisionId, "code_1"); }; @@ -467,6 +497,76 @@ var fluid = fluid || require("infusion"); dataStore.findAccessTokenByOAuth2ClientIdAndGpiiToken("client_id_C", "alice_gpii_token")); }); + jqUnit.test("addClientCredentialsToken(), find added token by id and client id, revoke, then find revoked token by id and client id", function () { + var dataStore = gpii.tests.oauth2.dataStore.dataStoreWithTestData(); + var clientCredentialsToken = dataStore.addClientCredentialsToken(gpii.tests.oauth2.dataStore.testdata.clientCredentialsToken1); + gpii.tests.oauth2.dataStore.verifyClientCredentialsToken1(clientCredentialsToken, gpii.tests.oauth2.dataStore.testdata.clientCredentialsToken1.revoked); + jqUnit.assertValue("Id has been assigned", clientCredentialsToken.id); + + var retrieved = dataStore.findClientCredentialsTokenById(clientCredentialsToken.id); + gpii.tests.oauth2.dataStore.verifyClientCredentialsToken1(retrieved, gpii.tests.oauth2.dataStore.testdata.clientCredentialsToken1.revoked); + + retrieved = dataStore.findClientCredentialsTokenByClientId(clientCredentialsToken.clientId); + gpii.tests.oauth2.dataStore.verifyClientCredentialsToken1(retrieved, gpii.tests.oauth2.dataStore.testdata.clientCredentialsToken1.revoked); + + dataStore.revokeClientCredentialsToken(clientCredentialsToken.id); + + retrieved = dataStore.findClientCredentialsTokenById(clientCredentialsToken.id); + gpii.tests.oauth2.dataStore.verifyClientCredentialsToken1(retrieved, true); + + retrieved = dataStore.findClientCredentialsTokenByClientId(clientCredentialsToken.clientId); + jqUnit.assertUndefined("Revoked credential client token is not found", retrieved); + }); + + jqUnit.test("findClientCredentialsTokenByAccessToken(), find by a correct token, find by a wrong token, revoke and find again", function () { + var dataStore = gpii.tests.oauth2.dataStore.dataStoreWithTestData(); + + var clientCredentialsToken = dataStore.addClientCredentialsToken(gpii.tests.oauth2.dataStore.testdata.clientCredentialsToken1); + jqUnit.assertValue("Id has been assigned", clientCredentialsToken.id); + + var retrieved = dataStore.findClientCredentialsTokenByAccessToken(gpii.tests.oauth2.dataStore.testdata.clientCredentialsToken1.accessToken); + gpii.tests.oauth2.dataStore.verifyClientCredentialsToken1(retrieved, gpii.tests.oauth2.dataStore.testdata.clientCredentialsToken1.revoked); + + var token = dataStore.findClientCredentialsTokenByAccessToken("wrong-token"); + jqUnit.assertUndefined("non-existing token returns undefined", token); + + dataStore.revokeClientCredentialsToken(clientCredentialsToken.id); + + retrieved = dataStore.findClientCredentialsTokenByAccessToken(gpii.tests.oauth2.dataStore.testdata.clientCredentialsToken1.accessToken); + jqUnit.assertUndefined("Revoked credential client token is not found", retrieved); + }); + + jqUnit.test("findClientByClientCredentialsAccessToken()", function () { + var dataStore = gpii.tests.oauth2.dataStore.dataStoreWithTestData(); + + var clientCredentialsToken = dataStore.addClientCredentialsToken(gpii.tests.oauth2.dataStore.testdata.clientCredentialsToken1); + jqUnit.assertValue("Id has been assigned", clientCredentialsToken.id); + + var client = dataStore.findClientByClientCredentialsAccessToken("wrong-token"); + jqUnit.assertUndefined("non-existing token returns undefined", client); + + var retrieved = dataStore.findClientByClientCredentialsAccessToken(gpii.tests.oauth2.dataStore.testdata.clientCredentialsToken1.accessToken); + gpii.tests.oauth2.dataStore.verifyClientD(retrieved); + + dataStore.revokeClientCredentialsToken(clientCredentialsToken.id); + + retrieved = dataStore.findClientByClientCredentialsAccessToken(gpii.tests.oauth2.dataStore.testdata.clientCredentialsToken1.accessToken); + jqUnit.assertUndefined("Revoked credential client token is not found", retrieved); + }); + + jqUnit.test("findClientCredentialsTokenPrivs()", function () { + var dataStore = gpii.tests.oauth2.dataStore.dataStoreWithTestData(); + + var clientCredentialsToken = dataStore.addClientCredentialsToken(gpii.tests.oauth2.dataStore.testdata.clientCredentialsToken1); + jqUnit.assertValue("Id has been assigned", clientCredentialsToken.id); + + var tokenPrivs = dataStore.findClientCredentialsTokenPrivs("wrong-token"); + jqUnit.assertUndefined("non-existing token returns undefined", tokenPrivs); + + tokenPrivs = dataStore.findClientCredentialsTokenPrivs(gpii.tests.oauth2.dataStore.testdata.clientCredentialsToken1.accessToken); + jqUnit.assertDeepEq("The returned privileges for the client credentials token is expected", {allowAddPrefs: true}, tokenPrivs); + }); + }; })(); diff --git a/gpii/node_modules/lifecycleManager/src/LifecycleManager.js b/gpii/node_modules/lifecycleManager/src/LifecycleManager.js index f65c72db1..a290b94b0 100644 --- a/gpii/node_modules/lifecycleManager/src/LifecycleManager.js +++ b/gpii/node_modules/lifecycleManager/src/LifecycleManager.js @@ -316,19 +316,20 @@ var gpii = fluid.registerNamespace("gpii"); }; /** Invoked on both "start" and "update" phases - in addition to forwarding to gpii.lifecycleManager.executeActions, - * it is responsible for saving the settings that are being set (when the saveSnapshot is true) and storing + * it is responsible for saving the settings that are being set (when the fullSnapshot is true) and storing * the list of applied solutions to the sessionState * * @param solutionId {String} the ID of the solution * @param solutionRecord {Object} a solution record with settings that are to be applied to the system * @param sessionState {Object} the object holding the state of the system * @param lifecycleBlockKeys {Array} Array of ordered strings denoting which lifecycle blocks to run (usually "configure", "start" and/or "update") - * @param saveSnapshot {boolean} indicates whether a snapshot of the original settings should be taken - * before the new settings are written + * @param fullSnapshot {boolean} indicates whether a full snapshot of the original settings should be taken + * before the new settings are written. If false, only changed settings that are not part of + * an already existing snapshot will be saved * @return {Promise} The same promise yielded by executeActions - the stateful construction of the session state is tacked onto this promise * as a side-effect */ - gpii.lifecycleManager.applySolution = function (that, solutionId, solutionRecord, sessionState, lifecycleBlockKeys, saveSnapshot) { + gpii.lifecycleManager.applySolution = function (that, solutionId, solutionRecord, sessionState, lifecycleBlockKeys, fullSnapshot) { var promises = []; fluid.each(lifecycleBlockKeys, function (key) { promises.push(that.executeActions(solutionId, solutionRecord, sessionState, key)); @@ -336,17 +337,34 @@ var gpii = fluid.registerNamespace("gpii"); var promiseSequence = fluid.promise.sequence(promises); // snapshots is an array of snapshotted settingshandler results (one for each lifecycle action block) promiseSequence.then(function (snapshots) { - if (saveSnapshot) { - var toSnapshot = fluid.copy(solutionRecord); - toSnapshot.settingsHandlers = {}; - fluid.each(snapshots, function (snapshot) { - if (snapshot !== undefined) { // can be removed once everything is settingsHandlers (GPII-1235) - fluid.each(snapshot, function (handlerBlock, blockKey) { - toSnapshot.settingsHandlers[blockKey] = handlerBlock; - }); - } - }); + var toSnapshot = fluid.copy(solutionRecord); + toSnapshot.settingsHandlers = {}; + fluid.each(snapshots, function (snapshot) { + if (snapshot !== undefined) { // can be removed once everything is settingsHandlers (GPII-1235) + fluid.each(snapshot, function (handlerBlock, blockKey) { + toSnapshot.settingsHandlers[blockKey] = handlerBlock; + }); + } + }); + if (fullSnapshot) { sessionState.originalSettings[solutionId] = toSnapshot; + } else { + // if we're doing an update, keep the settings that are already stored from the + // original snapshot, but augment it with any settings from the new snapshot + // that were not present in the original snapshot. + // First merge the basic structures: + var tmpOrigSettings = $.extend(true, {}, toSnapshot, sessionState.originalSettings[solutionId]); + + // ensure that our augmented snapshot has any 'undefined' settings value copied over as well + gpii.settingsHandlers.copySettings(tmpOrigSettings.settingsHandlers, toSnapshot.settingsHandlers); + + // now recopy the original settings snapshot on top of the stored settings, but this time + // make sure that the settings from the original snapshot with a stored value of 'undefined' are copied over + // properly + if (sessionState.originalSettings[solutionId]) { + gpii.settingsHandlers.copySettings(tmpOrigSettings.settingsHandlers, sessionState.originalSettings[solutionId].settingsHandlers); + } + sessionState.originalSettings[solutionId] = tmpOrigSettings; } }); return promiseSequence; diff --git a/gpii/node_modules/lifecycleManager/test/js/LifecycleManagerTests.js b/gpii/node_modules/lifecycleManager/test/js/LifecycleManagerTests.js index 5b8359c12..28bd0c814 100644 --- a/gpii/node_modules/lifecycleManager/test/js/LifecycleManagerTests.js +++ b/gpii/node_modules/lifecycleManager/test/js/LifecycleManagerTests.js @@ -233,7 +233,7 @@ var fluid = fluid || require("infusion"); gpii.tests.lifecycleManager.initBackingMock = function () { gpii.tests.lifecycleManager.backingMockSettingsHandler = gpii.test.integration.mockSettingsHandler(); // initialise the mock with the initial settings expected at the end of the test - gpii.tests.lifecycleManager.backingMockSettingsHandler.set(gpii.tests.lifecycleManager.settingsHandlerExpectedInputRestoreSettings); + gpii.tests.lifecycleManager.backingMockSettingsHandler.set(gpii.tests.lifecycleManager.settingsHandlerOriginalSystemSettings); }; gpii.tests.lifecycleManager.mockSettingsHandler = { @@ -267,6 +267,23 @@ var fluid = fluid || require("infusion"); jqUnit.assertDeepEq("expected input sent to settingsHandler" + message, expected, gpii.tests.lifecycleManager.staticRepository.settingsHandler); }; + gpii.tests.lifecycleManager.settingsHandlerOriginalSystemSettings = { + "org.gnome.desktop.a11y.magnifier": [{ + "settings": { + "cross-hairs-clip": false, + "cross-hairs-color": "red", + "iamasetting": 200, + "undefSetting": undefined + }, + "options": {} + }], + "other.application": [{ + "settings": { + "mysetting": "Hello World" + }, + "type": "gpii.tests.lifecycleManager.mockSettingsHandler" + }] + }; gpii.tests.lifecycleManager.settingsHandlerExpectedInputNewSettings = { "org.gnome.desktop.a11y.magnifier": [{ @@ -289,7 +306,7 @@ var fluid = fluid || require("infusion"); }; gpii.tests.lifecycleManager.updateTestDefs = [{ - name: "Updating with the same prefs as already applied", + name: "Updating with the same prefs and values as already applied", activeSessions: gpii.tests.lifecycleManager.sampleActiveSession, startPayload: gpii.tests.lifecycleManager.startPayload, updateSpec: gpii.tests.lifecycleManager.buildLifecycleInstructions("org.gnome.desktop.a11y.magnifier", @@ -305,7 +322,7 @@ var fluid = fluid || require("infusion"); gpii.tests.lifecycleManager.noUpdateLifecycle, { "update": [ "settings.myconf" ] }) }, { - name: "single pref changed without 'update' directive", + name: "single pref changed without 'update' directive in solution registry entry", activeSessions: gpii.tests.lifecycleManager.sampleActiveSession, startPayload: $.extend(true, {}, gpii.tests.lifecycleManager.userOptions, { lifecycleInstructions: gpii.tests.lifecycleManager.buildLifecycleInstructions("org.gnome.desktop.a11y.magnifier", @@ -343,11 +360,11 @@ var fluid = fluid || require("infusion"); activeSessions: gpii.tests.lifecycleManager.sampleActiveSession, startPayload: gpii.tests.lifecycleManager.startPayload, updateSpec: gpii.tests.lifecycleManager.buildLifecycleInstructions("org.gnome.desktop.a11y.magnifier", - gpii.tests.lifecycleManager.buildSettingsHandlersEntry({ "cross-hairs-color": "green", "iamasetting": true }), + gpii.tests.lifecycleManager.buildSettingsHandlersEntry({ "cross-hairs-color": "green", "cross-hairs-clip": false }), gpii.tests.lifecycleManager.noUpdateLifecycle, { "update": [ "settings.myconf" ] }), expectedAppliedSolutions: gpii.tests.lifecycleManager.buildLifecycleInstructions("org.gnome.desktop.a11y.magnifier", - gpii.tests.lifecycleManager.buildSettingsHandlersEntry({ "cross-hairs-clip": true, "cross-hairs-color": "green", "iamasetting": true }), + gpii.tests.lifecycleManager.buildSettingsHandlersEntry({ "cross-hairs-clip": false, "cross-hairs-color": "green" }), gpii.tests.lifecycleManager.noUpdateLifecycle, { "update": [ "settings.myconf" ] }), expectedOriginalSettings: gpii.tests.lifecycleManager.buildLifecycleInstructions("org.gnome.desktop.a11y.magnifier", @@ -375,6 +392,170 @@ var fluid = fluid || require("infusion"); gpii.tests.lifecycleManager.buildSettingsHandlersEntry({ "cross-hairs-clip": false, "cross-hairs-color": "red" }), gpii.tests.lifecycleManager.noUpdateLifecycle, { "update": [ "configure" ] }) + }, { + name: "Updating with normal reference to settingsHandler block, and a setting not in the original settings applied to the system", + activeSessions: gpii.tests.lifecycleManager.sampleActiveSession, + startPayload: gpii.tests.lifecycleManager.startPayload, + updateSpec: gpii.tests.lifecycleManager.buildLifecycleInstructions("org.gnome.desktop.a11y.magnifier", + gpii.tests.lifecycleManager.buildSettingsHandlersEntry({ "cross-hairs-color": "green", "iamasetting": 100 }), + gpii.tests.lifecycleManager.noUpdateLifecycle, + { "update": [ "settings.myconf" ] }), + expectedAppliedSolutions: gpii.tests.lifecycleManager.buildLifecycleInstructions("org.gnome.desktop.a11y.magnifier", + gpii.tests.lifecycleManager.buildSettingsHandlersEntry({ "cross-hairs-clip": true, "cross-hairs-color": "green", "iamasetting": 100 }), + gpii.tests.lifecycleManager.noUpdateLifecycle, + { "update": [ "settings.myconf" ] }), + expectedOriginalSettings: gpii.tests.lifecycleManager.buildLifecycleInstructions("org.gnome.desktop.a11y.magnifier", + gpii.tests.lifecycleManager.buildSettingsHandlersEntry({ "cross-hairs-clip": false, "cross-hairs-color": "red", "iamasetting": 200 }), + gpii.tests.lifecycleManager.noUpdateLifecycle, + { "update": [ "settings.myconf" ] }) + }, { + name: "Updating with normal reference to settingsHandler block, and a an application not in the original settings applied to the system", + activeSessions: gpii.tests.lifecycleManager.sampleActiveSession, + startPayload: gpii.tests.lifecycleManager.startPayload, + updateSpec: gpii.tests.lifecycleManager.buildLifecycleInstructions("other.application", + gpii.tests.lifecycleManager.buildSettingsHandlersEntry({ "mysetting": "Hallow World" }), + gpii.tests.lifecycleManager.noUpdateLifecycle, + { "update": [ "settings.myconf" ] }), + expectedAppliedSolutions: $.extend(true, {}, + gpii.tests.lifecycleManager.buildLifecycleInstructions("org.gnome.desktop.a11y.magnifier", + gpii.tests.lifecycleManager.buildSettingsHandlersEntry({ "cross-hairs-clip": true, "cross-hairs-color": "red" }), + gpii.tests.lifecycleManager.noUpdateLifecycle, + { "update": [ "settings.myconf" ] }), + gpii.tests.lifecycleManager.buildLifecycleInstructions("other.application", + gpii.tests.lifecycleManager.buildSettingsHandlersEntry({ "mysetting": "Hallow World" }), + gpii.tests.lifecycleManager.noUpdateLifecycle, + { "update": [ "settings.myconf" ] })), + expectedOriginalSettings: $.extend(true, {}, + gpii.tests.lifecycleManager.buildLifecycleInstructions("org.gnome.desktop.a11y.magnifier", + gpii.tests.lifecycleManager.buildSettingsHandlersEntry({ "cross-hairs-clip": false, "cross-hairs-color": "red" }), + gpii.tests.lifecycleManager.noUpdateLifecycle, + { "update": [ "settings.myconf" ] }), + gpii.tests.lifecycleManager.buildLifecycleInstructions("other.application", + gpii.tests.lifecycleManager.buildSettingsHandlersEntry({ "mysetting": "Hello World" }), + gpii.tests.lifecycleManager.noUpdateLifecycle, + { "update": [ "settings.myconf" ] })) + }, { + name: "Updating with normal reference to settingsHandler block, and 'undefined' value stored in snapshot", + activeSessions: gpii.tests.lifecycleManager.sampleActiveSession, + startPayload: $.extend(true, {}, gpii.tests.lifecycleManager.userOptions, { + lifecycleInstructions: $.extend(true, {}, + gpii.tests.lifecycleManager.buildLifecycleInstructions("org.gnome.desktop.a11y.magnifier", + gpii.tests.lifecycleManager.buildSettingsHandlersEntry({ "cross-hairs-clip": true, "undefSetting": "some value" }), + gpii.tests.lifecycleManager.noUpdateLifecycle, + { "update": [ "settings.myconf" ] })) + }), + expectedFirstAppliedSettings: { + "org.gnome.desktop.a11y.magnifier": [{ + "options": {}, + "settings": { + "cross-hairs-clip": true, + "undefSetting": "some value" + } + }] + }, + updateSpec: gpii.tests.lifecycleManager.buildLifecycleInstructions("org.gnome.desktop.a11y.magnifier", + gpii.tests.lifecycleManager.buildSettingsHandlersEntry({ "undefSetting": "some other value" }), + gpii.tests.lifecycleManager.noUpdateLifecycle, + { "update": [ "settings.myconf" ] }), + expectedAppliedSolutions: gpii.tests.lifecycleManager.buildLifecycleInstructions("org.gnome.desktop.a11y.magnifier", + gpii.tests.lifecycleManager.buildSettingsHandlersEntry({ "cross-hairs-clip": true, "undefSetting": "some other value" }), + gpii.tests.lifecycleManager.noUpdateLifecycle, + { "update": [ "settings.myconf" ] }), + expectedOriginalSettings: { + "org.gnome.desktop.a11y.magnifier": { + "active": true, + "configure": [ + "settings.myconf" + ], + "restore": [ + "settings.myconf" + ], + "settingsHandlers": { + "myconf": { + "options": {}, + "settings": { + "cross-hairs-clip": false, + "undefSetting": undefined + }, + "type": "gpii.tests.lifecycleManager.mockSettingsHandler" + } + }, + "start": [ + { + "args": [ + "-settingsDirectory", + "${{environment}.WINDIR}" + ], + "command": "${{environment}.JAWS_DIR}jaws.exe", + "name": "exec", + "type": "gpii.tests.lifecycleManager.mockExecHandler" + } + ], + "stop": [ + { + "pid": "${{exec}.pid}", + "type": "gpii.tests.lifecycleManager.mockKillHandler" + } + ], + "update": [ + "settings.myconf" + ] + } + } + }, { + name: "Updating with normal reference to settingsHandler block, and 'undefined' value stored in snapshot on update", + activeSessions: gpii.tests.lifecycleManager.sampleActiveSession, + startPayload: gpii.tests.lifecycleManager.startPayload, + updateSpec: gpii.tests.lifecycleManager.buildLifecycleInstructions("org.gnome.desktop.a11y.magnifier", + gpii.tests.lifecycleManager.buildSettingsHandlersEntry({ "undefSetting": "some value" }), + gpii.tests.lifecycleManager.noUpdateLifecycle, + { "update": [ "settings.myconf" ] }), + expectedAppliedSolutions: gpii.tests.lifecycleManager.buildLifecycleInstructions("org.gnome.desktop.a11y.magnifier", + gpii.tests.lifecycleManager.buildSettingsHandlersEntry({ "cross-hairs-clip": true, "cross-hairs-color": "red", "undefSetting": "some value" }), + gpii.tests.lifecycleManager.noUpdateLifecycle, + { "update": [ "settings.myconf" ] }), + expectedOriginalSettings: { + "org.gnome.desktop.a11y.magnifier": { + "active": true, + "configure": [ + "settings.myconf" + ], + "restore": [ + "settings.myconf" + ], + "settingsHandlers": { + "myconf": { + "options": {}, + "settings": { + "cross-hairs-clip": false, + "cross-hairs-color": "red", + "undefSetting": undefined + }, + "type": "gpii.tests.lifecycleManager.mockSettingsHandler" + } + }, + "start": [ + { + "args": [ + "-settingsDirectory", + "${{environment}.WINDIR}" + ], + "command": "${{environment}.JAWS_DIR}jaws.exe", + "name": "exec", + "type": "gpii.tests.lifecycleManager.mockExecHandler" + } + ], + "stop": [ + { + "pid": "${{exec}.pid}", + "type": "gpii.tests.lifecycleManager.mockKillHandler" + } + ], + "update": [ + "settings.myconf" + ] + } + } }]; gpii.tests.lifecycleManager.buildUpdateTests = function () { @@ -392,13 +573,14 @@ var fluid = fluid || require("infusion"); lifecycleInstructions: test.updateSpec }); gpii.tests.lifecycleManager.assertExpectedExec(); - gpii.tests.lifecycleManager.assertExpectedSettingsHandler(" - on start", gpii.tests.lifecycleManager.settingsHandlerExpectedInputNewSettings); + var expectedFirstAppliedSettings = (test.expectedFirstAppliedSettings !== undefined) ? test.expectedFirstAppliedSettings : gpii.tests.lifecycleManager.settingsHandlerExpectedInputNewSettings; + gpii.tests.lifecycleManager.assertExpectedSettingsHandler(" - on start", expectedFirstAppliedSettings); lifecycleManager.update(updatePayload, function () { var appliedSolutions = lifecycleManager.activeSessions[test.startPayload.userToken].appliedSolutions; var originalSettings = lifecycleManager.activeSessions[test.startPayload.userToken].originalSettings; - jqUnit.assertDeepEq("Checking appliedSolutions after Update", appliedSolutions, test.expectedAppliedSolutions); - jqUnit.assertDeepEq("Checking originalSettings after Update", originalSettings, test.expectedOriginalSettings); + jqUnit.assertDeepEq("Checking appliedSolutions after Update", test.expectedAppliedSolutions, appliedSolutions); + jqUnit.assertDeepEq("Checking originalSettings after Update", test.expectedOriginalSettings, originalSettings); lifecycleManager.stop(gpii.tests.lifecycleManager.userOptions, function () { jqUnit.assertUndefined("no active sessions running after stop", lifecycleManager.activeSessions[test.startPayload.userToken]); jqUnit.start(); diff --git a/gpii/node_modules/settingsHandlers/src/settingsHandlerUtilities.js b/gpii/node_modules/settingsHandlers/src/settingsHandlerUtilities.js index d00a0575b..149449799 100644 --- a/gpii/node_modules/settingsHandlers/src/settingsHandlerUtilities.js +++ b/gpii/node_modules/settingsHandlers/src/settingsHandlerUtilities.js @@ -378,3 +378,19 @@ gpii.settingsHandlers.makeFileSet = function (parser) { }; }; +/** + * Given two settingshandler blocks, copy the settings (even those with a value of undefined) from + * the source to the target - overwriting the target settings. Note that the keys/values immediately + * within the source settingshandler block (ie. the named settinghandler sub blocks) must be present + * in the target block structure + * Also note that the target object *will be modified* + */ +gpii.settingsHandlers.copySettings = function (target, source) { + for (var shName in source) { + var shBlock = source[shName]; + for (var setting in shBlock.settings) { // will loop over undefined vals + target[shName].settings[setting] = shBlock.settings[setting]; + } + } +}; + diff --git a/gpii/node_modules/settingsHandlers/test/web/js/SettingsHandlerUtilitiesTests.js b/gpii/node_modules/settingsHandlers/test/web/js/SettingsHandlerUtilitiesTests.js index 9f84dc003..9c2c7b313 100644 --- a/gpii/node_modules/settingsHandlers/test/web/js/SettingsHandlerUtilitiesTests.js +++ b/gpii/node_modules/settingsHandlers/test/web/js/SettingsHandlerUtilitiesTests.js @@ -24,6 +24,7 @@ // a stub tester here to test filesystem integration fluid.registerNamespace("gpii.tests"); + fluid.registerNamespace("gpii.tests.settingsHandlersUtilitiesTests"); jqUnit.test("settingsHandlers.setSettings", function () { // check simple json structure @@ -77,4 +78,109 @@ jqUnit.assertDeepEq("Array of numbers", [1, "3a", 59.5], gpii.settingsHandlers.numberify(["1", "3a", "59.5"])); jqUnit.assertDeepEq("Structure of numbers", {a: 3.9998, b: [false, 55]}, gpii.settingsHandlers.numberify({a: "3.9998", b: [false, "55"]})); }); + + gpii.tests.settingsHandlersUtilitiesTests.copySettingsDefs = [{ + name: "Copy with extra setting in source", + source: { + "myconf": { + "settings": { + "cross-hairs-clip": true, + "mysetting": "some value" + }, + "type": "gpii.tests.lifecycleManager.mockSettingsHandler", + "options": {} + } + }, + target: { + "myconf": { + "settings": { + "cross-hairs-clip": false + }, + "type": "gpii.tests.lifecycleManager.mockSettingsHandler", + "options": {} + } + }, + expected: { + "myconf": { + "settings": { + "cross-hairs-clip": true, + "mysetting": "some value" + }, + "type": "gpii.tests.lifecycleManager.mockSettingsHandler", + "options": {} + } + } + }, { + name: "Copy with extra setting in target", + source: { + "myconf": { + "settings": { + "cross-hairs-clip": true + }, + "type": "gpii.tests.lifecycleManager.mockSettingsHandler", + "options": {} + } + }, + target: { + "myconf": { + "settings": { + "cross-hairs-clip": false, + "mysetting": "some value" + }, + "type": "gpii.tests.lifecycleManager.mockSettingsHandler", + "options": {} + } + }, + expected: { + "myconf": { + "settings": { + "cross-hairs-clip": true, + "mysetting": "some value" + }, + "type": "gpii.tests.lifecycleManager.mockSettingsHandler", + "options": {} + } + } + }, { + name: "Copy with 'undefined' value in source and target", + source: { + "myconf": { + "settings": { + "cross-hairs-clip": true, + "undefSetting": undefined + }, + "type": "gpii.tests.lifecycleManager.mockSettingsHandler", + "options": {} + } + }, + target: { + "myconf": { + "settings": { + "cross-hairs-clip": false, + "undefSetting2": undefined, + "undefSetting": "some value" + }, + "type": "gpii.tests.lifecycleManager.mockSettingsHandler", + "options": {} + } + }, + expected: { + "myconf": { + "settings": { + "cross-hairs-clip": true, + "undefSetting": undefined, + "undefSetting2": undefined + }, + "type": "gpii.tests.lifecycleManager.mockSettingsHandler", + "options": {} + } + } + }]; + + jqUnit.test("Testing gpii.settingsHandlers.copySettings function", function () { + fluid.each(gpii.tests.settingsHandlersUtilitiesTests.copySettingsDefs, function (testDef) { + gpii.settingsHandlers.copySettings(testDef.target, testDef.source); + jqUnit.assertDeepEq(testDef.name, testDef.expected, testDef.target); + }); + }); }()); diff --git a/gpii/node_modules/testing/src/CloudBasedOAuth2.js b/gpii/node_modules/testing/src/CloudBasedOAuth2.js index 249333415..a8115dbc7 100644 --- a/gpii/node_modules/testing/src/CloudBasedOAuth2.js +++ b/gpii/node_modules/testing/src/CloudBasedOAuth2.js @@ -21,7 +21,8 @@ var fluid = require("infusion"), gpii = fluid.registerNamespace("gpii"), $ = fluid.registerNamespace("jQuery"), jqUnit = fluid.registerNamespace("jqUnit"), - kettle = fluid.registerNamespace("kettle"); + kettle = fluid.registerNamespace("kettle"), + path = require("path"); fluid.registerNamespace("gpii.test.cloudBased.oauth2"); @@ -183,7 +184,12 @@ gpii.test.cloudBased.oauth2.verifyDecisionResponse = function (decisionRequest, decisionRequest.authorizationCode = redirect.code; }; -gpii.test.cloudBased.oauth2.sendAccessTokenRequest = function (accessTokenRequest, decisionRequest, options) { +gpii.test.cloudBased.oauth2.sendRequest = function (request, options, formBody, filterName) { + formBody = gpii.test.cloudBased.oauth2.filter(formBody, options, filterName); + request.send(gpii.test.stringifyFormBody(formBody)); +}; + +gpii.test.cloudBased.oauth2.sendAccessTokenRequestInAuthCode = function (accessTokenRequest, decisionRequest, options) { var formBody = { grant_type: "authorization_code", code: decisionRequest.authorizationCode, @@ -192,12 +198,21 @@ gpii.test.cloudBased.oauth2.sendAccessTokenRequest = function (accessTokenReques client_secret: options.client_secret }; - formBody = gpii.test.cloudBased.oauth2.filter(formBody, options, "accessTokenForm"); + gpii.test.cloudBased.oauth2.sendRequest(accessTokenRequest, options, formBody, "accessTokenForm"); +}; + +gpii.test.cloudBased.oauth2.sendAccessTokenRequestInClientCredentials = function (accessTokenRequest, options) { + var formBody = { + grant_type: "client_credentials", + scope: options.scope, + client_id: options.client_id, + client_secret: options.client_secret + }; - accessTokenRequest.send(gpii.test.stringifyFormBody(formBody)); + gpii.test.cloudBased.oauth2.sendRequest(accessTokenRequest, options, formBody, "accessTokenForm"); }; -gpii.test.cloudBased.oauth2.verifyAccessTokenResponse = function (body, accessTokenRequest) { +gpii.test.cloudBased.oauth2.verifyAccessTokenInResponse = function (body, accessTokenRequest) { var response = gpii.test.verifyJSONResponse(body, accessTokenRequest); var token = response.access_token; jqUnit.assertValue("Should have received an access token", token); @@ -261,6 +276,52 @@ gpii.test.cloudBased.oauth2.verifyPrivacySettingsResponse = function (body, priv gpii.test.verifyBodyContents(body, expectedParts); }; +gpii.test.cloudBased.oauth2.sendAddPrefsRequest = function (securedAddPrefsRequest, prefsData, secured, accessToken, view) { + view = view || "flat"; + secured = secured || true; + + var options = { + path: "/add-preferences?view=" + view + }; + + var securedHeader = { + headers: { + Authorization: "Bearer " + accessToken, + "Content-Type": "application/json" + } + }; + + var securedOptions = $.extend({}, options, securedHeader); + + options = secured ? securedOptions : options; + + securedAddPrefsRequest.send(prefsData, options); +}; + +gpii.test.cloudBased.oauth2.verifyAddPrefsResponse = function (body, addPrefsRequest, expectedPrefs, prefsDir) { + var response = gpii.test.verifyJSONResponse(body, addPrefsRequest); + jqUnit.assertValue("Should have received a user token", response.userToken); + jqUnit.assertDeepEq("The received saved preferences data should be expect", expectedPrefs, response.preferences); + addPrefsRequest.userToken = response.userToken; + + var filePath = path.resolve(prefsDir, response.userToken) + ".json"; + gpii.tests.cloud.oauth2.addPrefs.filesToDelete.push(filePath); +}; + +gpii.test.cloudBased.oauth2.sendGetPrefsRequest = function (getPrefsRequest, addPrefsRequest, view) { + view = view || "flat"; + var options = { + path: "/preferences/" + addPrefsRequest.userToken + "?view=" + view + }; + + getPrefsRequest.send(null, options); +}; + +gpii.test.cloudBased.oauth2.verifyGetPrefsResponse = function (body, getPrefsResponse, expectedPrefs) { + var response = gpii.test.verifyJSONResponse(body, getPrefsResponse); + jqUnit.assertDeepEq("The received saved preferences data should be expect", expectedPrefs, response); +}; + gpii.test.cloudBased.verifyPayloadMatchMakerOutput = function (body, expectedMatchMakerOutput) { var payload = JSON.parse(body); jqUnit.assertDeepEq("Verify expected matchMakerOutput", @@ -271,7 +332,7 @@ fluid.defaults("gpii.test.cloudBased.oauth2.testCaseHolder", { gradeNames: ["kettle.test.testCaseHolder", "autoInit"], distributeOptions: [{ record: "gpii.oauth2.dataStore.acceptanceData", - target: "{that gpii.oauth2.dataStore}.options.gradeNames" + target: "{that flowManager.cloudBased}.options.oauth2DataStoreGrades" }, { // TODO: Correct this strategy once FLUID-5556 is implemented record: { funcName: "gpii.test.cloudBased.oauth2.populateDataStore", @@ -356,6 +417,22 @@ fluid.defaults("gpii.test.cloudBased.oauth2.testCaseHolder", { port: 8081, method: "GET" } + }, + addPrefsRequest: { + type: "kettle.test.request.http", + options: { + // path: "/add-preferences?view=%view", // Cannot dynamically config path + port: 8081, + method: "POST" + } + }, + getPrefsRequest: { + type: "kettle.test.request.http", + options: { + // path: "/preferences?view=%view", // Cannot dynamically config path + port: 8081, + method: "GET" + } } } }); @@ -392,11 +469,11 @@ gpii.test.cloudBased.oauth2.mainSequence = [{ // 0 args: ["{decisionRequest}", "{testCaseHolder}.options.redirect_uri", "{testCaseHolder}.options.state"] }, { // 8 - 5 variants here for omitting each form field - funcName: "gpii.test.cloudBased.oauth2.sendAccessTokenRequest", + funcName: "gpii.test.cloudBased.oauth2.sendAccessTokenRequestInAuthCode", args: ["{accessTokenRequest}", "{decisionRequest}", "{testCaseHolder}.options"] }, { // 9 event: "{accessTokenRequest}.events.onComplete", - listener: "gpii.test.cloudBased.oauth2.verifyAccessTokenResponse", + listener: "gpii.test.cloudBased.oauth2.verifyAccessTokenInResponse", args: ["{arguments}.0", "{accessTokenRequest}"] }, { // 10 func: "{getAuthorizedServicesRequest}.send" diff --git a/gpii/node_modules/testing/src/Integration.js b/gpii/node_modules/testing/src/Integration.js index e9bd8c450..f2d6ba8c6 100644 --- a/gpii/node_modules/testing/src/Integration.js +++ b/gpii/node_modules/testing/src/Integration.js @@ -330,7 +330,7 @@ fluid.defaults("gpii.test.integration.mockSettingsHandlerRegistry.universal", { "gpii.settingsHandlers.INISettingsHandler": { optionsPathKey: "filename" }, - "gpii.settingsHandlers.XMLSettingsHandler": { + "gpii.settingsHandlers.XMLHandler": { optionsPathKey: "filename" }, "gpii.settingsHandlers.JSONSettingsHandler": { @@ -408,6 +408,12 @@ gpii.test.integration.deviceReporterAware.mockDeviceReporters.findNameInList = f return expectInstalled.some(function (el) { return el === name; }); }; +gpii.test.integration.deviceReporterAware.mockDeviceReporters.registryKeyExists = function (expectInstalled, hKey, path, subPath) { + return expectInstalled.some(function (el) { + return el.hKey === hKey && el.path === path && el.subPath === subPath; + }); +}; + gpii.test.integration.deviceReporterAware.populate = function (that) { fluid.each(that.options.deviceReporters, function (options, key) { var mock = that.options.mockDeviceReporters[key]; @@ -431,7 +437,15 @@ gpii.test.integration.deviceReporterAware.populate = function (that) { }; gpii.test.integration.deviceReporterAware.resolveName = function (that, name) { - return that.options.rootPath + "." + name; + var resolvedName; + + if (name === "gpii.deviceReporter.alwaysInstalled") { + resolvedName = name; + } else { + resolvedName = that.options.rootPath + "." + name; + } + + return resolvedName; }; fluid.defaults("gpii.test.integration.deviceReporterAware.linux", { @@ -448,3 +462,20 @@ fluid.defaults("gpii.test.integration.deviceReporterAware.linux", { } } }); + +fluid.defaults("gpii.test.integration.deviceReporterAware.windows", { + gradeNames: ["gpii.test.integration.deviceReporterAware", "autoInit"], + mockDeviceReporters: { + "gpii.deviceReporter.registryKeyExists": { + mockFunc: "registryKeyExists", + defaults: { + gradeNames: "fluid.function", + argumentMap: { + hKey: 0, + path: 1, + subPath: 2 + } + } + } + } +}); diff --git a/testData/ontologies/firstDiscovery-flat.json b/testData/ontologies/firstDiscovery-flat.json new file mode 100644 index 000000000..ab1212a2a --- /dev/null +++ b/testData/ontologies/firstDiscovery-flat.json @@ -0,0 +1,11 @@ +{ + "http://registry\\.gpii\\.net/common/language": "gpii_firstDiscovery_language", + "http://registry\\.gpii\\.net/common/screenReaderTTSEnabled": "gpii_firstDiscovery_speak", + "http://registry\\.gpii\\.net/common/speechRate": "gpii_firstDiscovery_speechRate", + "http://registry\\.gpii\\.net/common/highContrastTheme": "fluid_prefs_contrast", + "http://registry\\.gpii\\.net/common/fontSize": "fluid_prefs_textSize", + "http://registry\\.gpii\\.net/common/onScreenKeyboardEnabled": "gpii_firstDiscovery_onScreenKeyboard", + "http://registry\\.gpii\\.net/common/captionsEnabled": "gpii_firstDiscovery_captions", + "http://registry\\.gpii\\.net/common/trackingTTS": "gpii_firstDiscovery_showSounds", + "http://registry\\.gpii\\.net/common/stickyKeys": "gpii_firstDiscovery_stickyKeys" +} diff --git a/testData/preferences/acceptanceTests/readwritegold_application1.json b/testData/preferences/acceptanceTests/readwritegold_application1.json new file mode 100644 index 000000000..597e679a0 --- /dev/null +++ b/testData/preferences/acceptanceTests/readwritegold_application1.json @@ -0,0 +1,31 @@ +{ + "flat": { + "contexts": { + "gpii-default": { + "name": "Default preferences", + "preferences": { + "http://registry.gpii.net/applications/com.texthelp.readWriteGold": { + "ApplicationSettings.AppBar.optToolbarIconSet.$t": "Fun", + "ApplicationSettings.AppBar.optToolbarButtonGroupNameCurrent.$t": "Writing Features", + "ApplicationSettings.AppBar.DocType.$t": "1", + "ApplicationSettings.AppBar.ShowText.$t": true, + "ApplicationSettings.AppBar.optToolbarShowText.$t": true, + "ApplicationSettings.AppBar.LargeIcons.$t": true, + "ApplicationSettings.AppBar.optToolbarLargeIcons.$t": true, + "ApplicationSettings.Speech.optSAPI5Pitch.$t": 36, + "ApplicationSettings.Speech.optSAPI5Speed.$t": 38, + "ApplicationSettings.Speech.optSAPI5Volume.$t": 72, + "ApplicationSettings.Speech.optSAPI5PauseBetweenWords.$t": 0, + "ApplicationSettings.Speech.optSAPI5Voice.$t": "ScanSoft UK English Daniel", + "ApplicationSettings.Speech.WebHighlighting.$t": false, + "ApplicationSettings.Translation.ToLanguage.$t": "fr", + "ApplicationSettings.Speech.optSAPI5SpeechHighlightContext.$t": 2, + "ApplicationSettings.Scanning.ScanDestination.$t": "PDF", + "ApplicationSettings.Scanning.ScanToFile.$t": false, + "ApplicationSettings.Spelling.SpellAsIType.$t": true + } + } + } + } + } +} diff --git a/testData/preferences/alice.json b/testData/preferences/alice.json new file mode 100644 index 000000000..a028952ce --- /dev/null +++ b/testData/preferences/alice.json @@ -0,0 +1,12 @@ +{ + "flat": { + "contexts": { + "gpii-default": { + "name": "Default preferences", + "preferences": { + "http://registry.gpii.net/applications/com.microsoft.windows.onscreenKeyboard": {} + } + } + } + } +} diff --git a/testData/preferences/alice.md b/testData/preferences/alice.md new file mode 100644 index 000000000..7f9a841ea --- /dev/null +++ b/testData/preferences/alice.md @@ -0,0 +1,13 @@ +# alice.json + +## Demo Personas with User Stories + +This preference set is part of our set of personas used for the demo install, +presentations, and in general conveying an array of compelling user stories for +the GPII. + +## Details + +Alice — On screen Keyboard + +Alice has a high spinal cord injury and cannot control anything below her chin. She uses a head-controlled mouse point. At the library she will ask anyone nearby to touch the GPII card to the computer for her and she is all set. diff --git a/testData/preferences/davey.json b/testData/preferences/davey.json new file mode 100644 index 000000000..d3cbed665 --- /dev/null +++ b/testData/preferences/davey.json @@ -0,0 +1,31 @@ +{ + "flat": { + "contexts": { + "gpii-default": { + "name": "Default preferences", + "preferences": { + "http://registry.gpii.net/applications/com.texthelp.readWriteGold": { + "ApplicationSettings.AppBar.optToolbarIconSet.$t": "Fun", + "ApplicationSettings.AppBar.optToolbarButtonGroupNameCurrent.$t": "Writing Features", + "ApplicationSettings.AppBar.DocType.$t": "1", + "ApplicationSettings.AppBar.ShowText.$t": true, + "ApplicationSettings.AppBar.optToolbarShowText.$t": true, + "ApplicationSettings.AppBar.LargeIcons.$t": true, + "ApplicationSettings.AppBar.optToolbarLargeIcons.$t": true, + "ApplicationSettings.Speech.optSAPI5Pitch.$t": 36, + "ApplicationSettings.Speech.optSAPI5Speed.$t": 38, + "ApplicationSettings.Speech.optSAPI5Volume.$t": 72, + "ApplicationSettings.Speech.optSAPI5PauseBetweenWords.$t": 0, + "ApplicationSettings.Speech.optSAPI5Voice.$t": "ScanSoft UK English Daniel", + "ApplicationSettings.Speech.WebHighlighting.$t": false, + "ApplicationSettings.Translation.ToLanguage.$t": "fr", + "ApplicationSettings.Speech.optSAPI5SpeechHighlightContext.$t": 2, + "ApplicationSettings.Scanning.ScanDestination.$t": "PDF", + "ApplicationSettings.Scanning.ScanToFile.$t": false, + "ApplicationSettings.Spelling.SpellAsIType.$t": true + } + } + } + } + } +} \ No newline at end of file diff --git a/testData/preferences/davey.md b/testData/preferences/davey.md new file mode 100644 index 000000000..fc05fa489 --- /dev/null +++ b/testData/preferences/davey.md @@ -0,0 +1,13 @@ +# davey.json + +## Demo Personas with User Stories + +This preference set is part of our set of personas used for the demo install, +presentations, and in general conveying an array of compelling user stories for +the GPII. + +## Details + +Davey - ReadWrite Gold with “fun” icons + +Davey is 6 and has a learning disability and when he looks at text it is visually scrambled. He needs to have text highlighted and read to him to make it easier for him to learn and read for school (and for any reading). diff --git a/testData/preferences/david.json b/testData/preferences/david.json new file mode 100644 index 000000000..92125aefe --- /dev/null +++ b/testData/preferences/david.json @@ -0,0 +1,33 @@ +{ + "flat": { + "contexts": { + "gpii-default": { + "name": "Default preferences", + "preferences": { + "http://registry.gpii.net/applications/com.texthelp.readWriteGold": { + "ApplicationSettings.AppBar.optToolbarIconSet.$t": "Professional", + "ApplicationSettings.AppBar.optToolbarButtonGroupNameCurrent.$t": "Reading Features", + "ApplicationSettings.AppBar.DocType.$t": "1", + "ApplicationSettings.AppBar.Width.$t": 788, + "ApplicationSettings.AppBar.ShowText.$t": true, + "ApplicationSettings.AppBar.optToolbarShowText.$t": true, + "ApplicationSettings.AppBar.LargeIcons.$t": true, + "ApplicationSettings.AppBar.optToolbarLargeIcons.$t": true, + "ApplicationSettings.Speech.optSAPI5Pitch.$t": 33, + "ApplicationSettings.Speech.optSAPI5Speed.$t": 31, + "ApplicationSettings.Speech.optSAPI5Volume.$t": 90, + "ApplicationSettings.Speech.optSAPI5PauseBetweenWords.$t": 115, + "ApplicationSettings.Speech.optAutoUseScreenReading.$t": false, + "ApplicationSettings.Speech.optSAPI5Voice.$t": "ScanSoft UK Indian Sangeeta", + "ApplicationSettings.Speech.WebHighlighting.$t": true, + "ApplicationSettings.Translation.ToLanguage.$t": "hi", + "ApplicationSettings.Speech.optSAPI5SpeechHighlightContext.$t": 0, + "ApplicationSettings.Scanning.ScanDestination.$t": "Word", + "ApplicationSettings.Scanning.ScanToFile.$t": true, + "ApplicationSettings.Spelling.SpellAsIType.$t": false + } + } + } + } + } +} \ No newline at end of file diff --git a/testData/preferences/david.md b/testData/preferences/david.md new file mode 100644 index 000000000..90efb69c4 --- /dev/null +++ b/testData/preferences/david.md @@ -0,0 +1,13 @@ +# david.json + +## Demo Personas with User Stories + +This preference set is part of our set of personas used for the demo install, +presentations, and in general conveying an array of compelling user stories for +the GPII. + +## Details + +David - (Davey’s dad) Read Write Gold with Adult Icons + +Uses Read Write Gold to highlight and read text. Dave is Davey’s dad, and has the same problem as Davey. But he wants a more adult looking interface for his setting, and a faster reading rate. diff --git a/testData/preferences/elod.json b/testData/preferences/elod.json new file mode 100644 index 000000000..e5783fe2e --- /dev/null +++ b/testData/preferences/elod.json @@ -0,0 +1,12 @@ +{ + "flat": { + "contexts": { + "gpii-default": { + "name": "Default preferences", + "preferences": { + "http://registry.gpii.net/common/magnification": 2.0 + } + } + } + } +} diff --git a/testData/preferences/elod.md b/testData/preferences/elod.md new file mode 100644 index 000000000..53ce64b57 --- /dev/null +++ b/testData/preferences/elod.md @@ -0,0 +1,13 @@ +# elod.json + +## Demo Personas with User Stories + +This preference set is part of our set of personas used for the demo install, +presentations, and in general conveying an array of compelling user stories for +the GPII. + +## Details + +Elod - Magnifier 200 + +Brings up a floating magnifying window. Elod has low vision and can’t see anything unless magnified 200 percent. diff --git a/testData/preferences/livia.json b/testData/preferences/livia.json new file mode 100644 index 000000000..5eb1e88ee --- /dev/null +++ b/testData/preferences/livia.json @@ -0,0 +1,12 @@ +{ + "flat": { + "contexts": { + "gpii-default": { + "name": "Default preferences", + "preferences": { + "http://registry.gpii.net/applications/org.nvda-project": {} + } + } + } + } +} diff --git a/testData/preferences/livia.md b/testData/preferences/livia.md new file mode 100644 index 000000000..d377a28ef --- /dev/null +++ b/testData/preferences/livia.md @@ -0,0 +1,13 @@ +# livia.json + +## Demo Personas with User Stories + +This preference set is part of our set of personas used for the demo install, +presentations, and in general conveying an array of compelling user stories for +the GPII. + +## Details + +Livia - NVDA + +Livia is blind. She feels the GPII logo, and touches it with her card to it to launch her screen reader. diff --git a/testData/security/TestOAuth2DataStore.js b/testData/security/TestOAuth2DataStore.js index a6722ca41..30b4cc795 100644 --- a/testData/security/TestOAuth2DataStore.js +++ b/testData/security/TestOAuth2DataStore.js @@ -90,6 +90,14 @@ fluid.defaults("gpii.oauth2.testDataStore", { oauth2ClientSecret: false, redirectUri: false, allowDirectGpiiTokenAccess: false + }, + { + id: 8, + name: "First Discovery", + oauth2ClientId: "net.gpii.prefsEditors.firstDiscovery", + oauth2ClientSecret: "client_secret_firstDiscovery", + allowDirectGpiiTokenAccess: false, + allowAddPrefs: true } ], authDecisionsIdSeq: 1, diff --git a/testData/solutions/linux.json b/testData/solutions/linux.json index fd83685c4..3e6d8ce2b 100644 --- a/testData/solutions/linux.json +++ b/testData/solutions/linux.json @@ -1524,6 +1524,11 @@ "start": [ ], "stop": [ + ], + "isInstalled": [ + { + "type": "gpii.deviceReporter.alwaysInstalled" + } ] } } diff --git a/testData/solutions/win32.json b/testData/solutions/win32.json index 03cb1e2aa..92d5ad94f 100644 --- a/testData/solutions/win32.json +++ b/testData/solutions/win32.json @@ -66,6 +66,15 @@ "type": "gpii.windows.killProcessByName", "filename": "jfw.exe" } + ], + "isInstalled": [ + { + "type": "gpii.deviceReporter.registryKeyExists", + "hKey": "HKEY_LOCAL_MACHINE", + "path": "Software\\Microsoft\\Windows\\CurrentVersion\\App Paths\\JAWS15.exe", + "subPath": "", + "dataType": "REG_SZ" + } ] }, @@ -116,6 +125,15 @@ "type": "gpii.launch.exec", "command": "echo 'kill' > ${{environment}.TEMP}\\RW8Updates.dat" } + ], + "isInstalled": [ + { + "type": "gpii.deviceReporter.registryKeyExists", + "hKey": "HKEY_CURRENT_USER", + "path": "Software\\Texthelp\\Read&Write10\\InstallPath", + "subPath": "ReadAndWrite.exe", + "dataType": "REG_SZ" + } ] }, @@ -228,6 +246,11 @@ "type": "gpii.windows.killProcessByName", "filename": "Magnify.exe" } + ], + "isInstalled": [ + { + "type": "gpii.deviceReporter.alwaysInstalled" + } ] }, @@ -262,6 +285,11 @@ "type": "gpii.windows.killProcessByName", "filename": "osk.exe" } + ], + "isInstalled": [ + { + "type": "gpii.deviceReporter.alwaysInstalled" + } ] }, @@ -630,6 +658,15 @@ "type": "gpii.windows.killProcessByName", "filename": "nvda.exe" } + ], + "isInstalled": [ + { + "type": "gpii.deviceReporter.registryKeyExists", + "hKey": "HKEY_LOCAL_MACHINE", + "path": "Software\\Microsoft\\Windows\\CurrentVersion\\App Paths\\nvda.exe", + "subPath": "", + "dataType": "REG_SZ" + } ] }, @@ -665,6 +702,11 @@ "type": "gpii.windows.killProcessByName", "filename": "firefox.exe" } + ], + "isInstalled": [ + { + "type": "gpii.deviceReporter.alwaysInstalled" + } ] }, @@ -699,6 +741,11 @@ "type": "gpii.windows.killProcessByName", "filename": "firefox.exe" } + ], + "isInstalled": [ + { + "type": "gpii.deviceReporter.alwaysInstalled" + } ] }, @@ -733,6 +780,11 @@ "type": "gpii.windows.killProcessByName", "filename": "firefox.exe" } + ], + "isInstalled": [ + { + "type": "gpii.deviceReporter.alwaysInstalled" + } ] }, @@ -861,6 +913,11 @@ "type": "gpii.windows.killProcessByName", "filename": "firefox.exe" } + ], + "isInstalled": [ + { + "type": "gpii.deviceReporter.alwaysInstalled" + } ] }, @@ -913,6 +970,11 @@ ], "restore": [ "settings.configure" + ], + "isInstalled": [ + { + "type": "gpii.deviceReporter.alwaysInstalled" + } ] }, @@ -967,6 +1029,11 @@ ], "restore": [ "settings.configure" + ], + "isInstalled": [ + { + "type": "gpii.deviceReporter.alwaysInstalled" + } ] }, @@ -1247,6 +1314,11 @@ { "type": "gpii.windows.spiSettingsHandler.updateCursors" } + ], + "isInstalled": [ + { + "type": "gpii.deviceReporter.alwaysInstalled" + } ] }, @@ -1399,6 +1471,11 @@ "start": [ ], "stop": [ + ], + "isInstalled": [ + { + "type": "gpii.deviceReporter.alwaysInstalled" + } ] } } diff --git a/tests/all-tests.js b/tests/all-tests.js index e23fc07ad..b045b9b83 100644 --- a/tests/all-tests.js +++ b/tests/all-tests.js @@ -33,6 +33,7 @@ var testIncludes = [ "./platform/cloud/AcceptanceTests_empty.js", "./platform/cloud/AcceptanceTests_gnome_keyboard.js", "./platform/cloud/AcceptanceTests_jme.js", + "./platform/cloud/AcceptanceTests_oauth2_addPrefs.js", "./platform/cloud/AcceptanceTests_oauth2_privacySettings.js", "./platform/cloud/AcceptanceTests_smarthouses.js", "./platform/cloud/AcceptanceTests_tvm.js", diff --git a/tests/platform/cloud/AcceptanceTests_oauth2_addPrefs.js b/tests/platform/cloud/AcceptanceTests_oauth2_addPrefs.js new file mode 100644 index 000000000..365d15ba9 --- /dev/null +++ b/tests/platform/cloud/AcceptanceTests_oauth2_addPrefs.js @@ -0,0 +1,273 @@ +/*! +Copyright 2015 OCAD university + +Licensed under the New BSD license. You may not use this file except in +compliance with this License. + +The research leading to these results has received funding from the European Union's +Seventh Framework Programme (FP7/2007-2013) under grant agreement no. 289016. + +You may obtain a copy of the License at +https://github.com/GPII/universal/blob/master/LICENSE.txt +*/ + +"use strict"; + +var fluid = require("universal"), + gpii = fluid.registerNamespace("gpii"), + path = require("path"), + fs = require("fs"); + +gpii.loadTestingSupport(); + +require("./OAuth2AcceptanceDataStore.js"); + +fluid.registerNamespace("gpii.tests.cloud.oauth2.addPrefs"); + +gpii.tests.cloud.oauth2.addPrefs.prefsData = { + "contexts": { + "gpii-default": { + "name": "Default preferences", + "preferences": { + "gpii_firstDiscovery_language": "en-US" + } + } + } +}; + +gpii.tests.cloud.oauth2.addPrefs.transformedPrefs = { + "contexts": { + "gpii-default": { + "name": "Default preferences", + "preferences": { + "http://registry.gpii.net/common/language": "en-US" + } + } + } +}; + +gpii.tests.cloud.oauth2.addPrefs.prefsDir = path.resolve(__dirname, "../../../testData/preferences/acceptanceTests/"); +gpii.tests.cloud.oauth2.addPrefs.filesToDelete = []; + +gpii.tests.cloud.oauth2.addPrefs.cleanUpTmpFiles = function () { + fluid.each(gpii.tests.cloud.oauth2.addPrefs.filesToDelete, function (filePath) { + fs.unlinkSync(filePath); + }); + gpii.tests.cloud.oauth2.addPrefs.filesToDelete.length = 0; +}; + +fluid.defaults("gpii.tests.cloud.oauth2.addPrefs.cleanupTmpFiles", { + gradeNames: ["fluid.eventedComponent", "autoInit"], + listeners: { + "onDestroy.cleanupTmpFiles": gpii.tests.cloud.oauth2.addPrefs.cleanUpTmpFiles + } +}); + +gpii.tests.cloud.oauth2.addPrefs.mainSequence = [ + { // 0 + funcName: "gpii.test.cloudBased.oauth2.sendAccessTokenRequestInClientCredentials", + args: ["{accessTokenRequest}", "{testCaseHolder}.options"] + }, + { // 1 + event: "{accessTokenRequest}.events.onComplete", + listener: "gpii.test.cloudBased.oauth2.verifyAccessTokenInResponse", + args: ["{arguments}.0", "{accessTokenRequest}"] + }, + { // 2 + funcName: "gpii.test.cloudBased.oauth2.sendAddPrefsRequest", + args: ["{addPrefsRequest}", gpii.tests.cloud.oauth2.addPrefs.prefsData, true, "{accessTokenRequest}.accessToken", "firstDiscovery"] + }, + { // 3 + event: "{addPrefsRequest}.events.onComplete", + listener: "gpii.test.cloudBased.oauth2.verifyAddPrefsResponse", + args: ["{arguments}.0", "{addPrefsRequest}", gpii.tests.cloud.oauth2.addPrefs.prefsData, gpii.tests.cloud.oauth2.addPrefs.prefsDir] + }, + { // 4 + funcName: "gpii.test.cloudBased.oauth2.sendGetPrefsRequest", + args: ["{getPrefsRequest}", "{addPrefsRequest}", "firstDiscovery"] + }, + { // 5 + event: "{getPrefsRequest}.events.onComplete", + listener: "gpii.test.cloudBased.oauth2.verifyGetPrefsResponse", + args: ["{arguments}.0", "{getPrefsRequest}", gpii.tests.cloud.oauth2.addPrefs.prefsData] + }, + { // 6 + funcName: "gpii.test.cloudBased.oauth2.sendGetPrefsRequest", + args: ["{getPrefsRequest}", "{addPrefsRequest}"] + }, + { // 7 + event: "{getPrefsRequest}.events.onComplete", + listener: "gpii.test.cloudBased.oauth2.verifyGetPrefsResponse", + args: ["{arguments}.0", "{getPrefsRequest}", gpii.tests.cloud.oauth2.addPrefs.transformedPrefs] + } +]; + +// To test invalid /add-preferences requests +gpii.tests.cloud.oauth2.addPrefs.addPrefsSequence = [ + { // 0 + funcName: "gpii.test.cloudBased.oauth2.sendAddPrefsRequest", + args: ["{addPrefsRequest}", gpii.tests.cloud.oauth2.addPrefs.prefsData, true, "{testCaseHolder}.options.accessToken"] + }, + { // 1 + event: "{addPrefsRequest}.events.onComplete", + listener: "gpii.test.verifyStatusCodeResponse", + args: ["{arguments}.0", "{addPrefsRequest}", "{testCaseHolder}.options.expectedStatusCode"] + } +]; + + +fluid.defaults("gpii.tests.cloud.oauth2.addPrefs.disruption.mainSequence", { + gradeNames: ["gpii.test.disruption"], + sequenceName: "gpii.tests.cloud.oauth2.addPrefs.mainSequence" +}); + +fluid.defaults("gpii.tests.cloud.oauth2.addPrefs.disruption.addPrefsSequence", { + gradeNames: ["gpii.test.disruption"], + sequenceName: "gpii.tests.cloud.oauth2.addPrefs.addPrefsSequence" +}); + +fluid.defaults("gpii.tests.cloud.oauth2.addPrefs.disruption.accessToken", { + gradeNames: ["gpii.tests.cloud.oauth2.addPrefs.disruption.mainSequence"], + truncateAt: 1, + expect: 1, + recordName: "accessTokenForm", + finalRecord: { + event: "{accessTokenRequest}.events.onComplete", + listener: "gpii.test.verifyStatusCodeResponse", + args: ["{arguments}.0", "{accessTokenRequest}", "{testCaseHolder}.options.expectedStatusCode"] + } +}); + +gpii.tests.cloud.oauth2.addPrefs.disruptions = [ + { + name: "A success access token request using the client credentials grant type", + gradeName: "gpii.tests.cloud.oauth2.addPrefs.disruption.mainSequence" + }, + { + name: "Attempt to get access token without sending client_id", + gradeName: "gpii.tests.cloud.oauth2.addPrefs.disruption.accessToken", + changes: { + path: "client_id", + type: "DELETE" + }, + expectedStatusCode: 401 + }, + { + name: "Attempt to get access token without sending client_secret", + gradeName: "gpii.tests.cloud.oauth2.addPrefs.disruption.accessToken", + changes: { + path: "client_secret", + type: "DELETE" + }, + expectedStatusCode: 401 + }, + { + name: "Attempt to get access token without sending scope", + gradeName: "gpii.tests.cloud.oauth2.addPrefs.disruption.accessToken", + changes: { + path: "scope", + type: "DELETE" + }, + expectedStatusCode: 403 + }, + { + name: "Attempt to get access token without sending grant_type", + gradeName: "gpii.tests.cloud.oauth2.addPrefs.disruption.accessToken", + changes: { + path: "grant_type", + type: "DELETE" + }, + expectedStatusCode: 501 + } +]; + +gpii.tests.cloud.oauth2.addPrefs.disruptionsWithWrongClient = [{ + name: "Attempt to get access token with sending a wrong client", + gradeName: "gpii.tests.cloud.oauth2.addPrefs.disruption.accessToken", + expectedStatusCode: 403 +}]; + +gpii.tests.cloud.oauth2.addPrefs.disruptionsWithWrongScope = [{ + name: "Attempt to get access token with sending a wrong scope", + gradeName: "gpii.tests.cloud.oauth2.addPrefs.disruption.accessToken", + expectedStatusCode: 403 +}]; + +gpii.tests.cloud.oauth2.addPrefs.disruptionsWithFalseToken = [{ + name: "Attempt to add preference sets with a false token", + gradeName: "gpii.tests.cloud.oauth2.addPrefs.disruption.addPrefsSequence", + expectedStatusCode: 401 +}]; + +gpii.tests.cloud.oauth2.addPrefs.disruptionsWithNonExistentClient = [{ + name: "Attempt to add preference sets with a non existent client", + gradeName: "gpii.tests.cloud.oauth2.addPrefs.disruption.addPrefsSequence", + expectedStatusCode: 404 +}]; + +gpii.tests.cloud.oauth2.addPrefs.disruptionsWithNotAllowedAddPrefs = [{ + name: "Attempt to add preference sets with no privilege to add prefs", + gradeName: "gpii.tests.cloud.oauth2.addPrefs.disruption.addPrefsSequence", + expectedStatusCode: 401 +}]; + +gpii.tests.cloud.oauth2.addPrefs.disruptedTests = [ + { + testDef: { + name: "Acceptance test for adding preferences - a successful entire work flow", + scope: "add_preferences", + client_id: "net.gpii.prefsEditors.firstDiscovery", + client_secret: "client_secret_firstDiscovery", + gradeNames: ["gpii.tests.cloud.oauth2.addPrefs.cleanupTmpFiles"] + }, + disruptions: gpii.tests.cloud.oauth2.addPrefs.disruptions + }, + { + testDef: { + name: "Acceptance test for suppporting client credentials grant type (with wrong client)", + scope: "add_preferences", + client_id: "com.bdigital.easit4all", + client_secret: "client_secret_easit4all" + }, + disruptions: gpii.tests.cloud.oauth2.addPrefs.disruptionsWithWrongClient + }, + { + testDef: { + name: "Acceptance test for suppporting client credentials grant type (with wrong client)", + scope: "update_preferences", + client_id: "net.gpii.prefsEditors.firstDiscovery", + client_secret: "client_secret_firstDiscovery" + }, + disruptions: gpii.tests.cloud.oauth2.addPrefs.disruptionsWithWrongScope + }, + { + testDef: { + name: "Acceptance test for suppporting /add-preferences (with false access token)", + accessToken: "false token" + }, + disruptions: gpii.tests.cloud.oauth2.addPrefs.disruptionsWithFalseToken + }, + { + testDef: { + name: "Acceptance test for suppporting /add-preferences (with false access token)", + accessToken: "non_existent_client" + }, + disruptions: gpii.tests.cloud.oauth2.addPrefs.disruptionsWithNonExistentClient + }, + { + testDef: { + name: "Acceptance test for suppporting /add-preferences (with false access token)", + accessToken: "not_allowed_to_add_prefs" + }, + disruptions: gpii.tests.cloud.oauth2.addPrefs.disruptionsWithNotAllowedAddPrefs + } +]; + +fluid.each(gpii.tests.cloud.oauth2.addPrefs.disruptedTests, function (oneTest) { + gpii.test.cloudBased.oauth2.bootstrapDisruptedTest( + oneTest.testDef, + {}, + oneTest.disruptions, + __dirname + ); +}); diff --git a/tests/platform/cloud/OAuth2AcceptanceDataStore.js b/tests/platform/cloud/OAuth2AcceptanceDataStore.js index bca6d09e6..e510b4210 100644 --- a/tests/platform/cloud/OAuth2AcceptanceDataStore.js +++ b/tests/platform/cloud/OAuth2AcceptanceDataStore.js @@ -55,6 +55,22 @@ fluid.defaults("gpii.oauth2.dataStore.acceptanceData", { oauth2ClientSecret: false, redirectUri: false, allowDirectGpiiTokenAccess: false + }, + { + id: 5, + name: "First Discovery", + oauth2ClientId: "net.gpii.prefsEditors.firstDiscovery", + oauth2ClientSecret: "client_secret_firstDiscovery", + allowDirectGpiiTokenAccess: false, + allowAddPrefs: true + }, + { + id: 6, + name: "Cloud4ClientCredentials", + oauth2ClientId: "client_for_client_credentials", + oauth2ClientSecret: "client_secret_for_client_credentials", + allowDirectGpiiTokenAccess: false, + allowAddPrefs: true } ], authDecisionsIdSeq: 2, @@ -68,6 +84,23 @@ fluid.defaults("gpii.oauth2.dataStore.acceptanceData", { selectedPreferences: { "": true }, revoked: false } + ], + clientCredentialsTokensIdSeq: 3, + clientCredentialsTokens: [ + { + id: 1, + clientId: 99, + accessToken: "non_existent_client", + revoked: false, + allowAddPrefs: true + }, + { + id: 2, + clientId: 6, + accessToken: "not_allowed_to_add_prefs", + revoked: false, + allowAddPrefs: false + } ] } }); diff --git a/tests/platform/index-windows.js b/tests/platform/index-windows.js index 144122c6c..ae16bdfa3 100644 --- a/tests/platform/index-windows.js +++ b/tests/platform/index-windows.js @@ -24,5 +24,6 @@ module.exports = [ "windows/windows-jaws-testSpec.js", "windows/windows-nvda-testSpec.js", "windows/windows-maavis-testSpec.js", - "windows/windows-chrome-testSpec.js" -]; \ No newline at end of file + "windows/windows-chrome-testSpec.js", + "windows/windows-dynamicDeviceReporter-testSpec.js" +]; diff --git a/tests/platform/windows/configs/windows-dynamicDeviceReporter-config.json b/tests/platform/windows/configs/windows-dynamicDeviceReporter-config.json new file mode 100644 index 000000000..52cccdf97 --- /dev/null +++ b/tests/platform/windows/configs/windows-dynamicDeviceReporter-config.json @@ -0,0 +1,6 @@ +{ + "typeName": "acceptanceTests.windows.dynamicDeviceReporter", + "includes": [ + "${universal}/tests/configs/localInstallWithDynamicDeviceReporter.json" + ] +} diff --git a/tests/platform/windows/configs/windows-dynamicDeviceReporter-config.txt b/tests/platform/windows/configs/windows-dynamicDeviceReporter-config.txt new file mode 100644 index 000000000..4c8de6355 --- /dev/null +++ b/tests/platform/windows/configs/windows-dynamicDeviceReporter-config.txt @@ -0,0 +1,4 @@ +This configuration file is used by the acceptance test: windows-dynamicDeviceReporter-config.js. + +This config makes use of dynamic device reporting, so it uses localInstallWithDynamicDeviceReporter +as its base config, which uses the all.development.dr.production gpii's config. diff --git a/tests/platform/windows/windows-dynamicDeviceReporter-testSpec.js b/tests/platform/windows/windows-dynamicDeviceReporter-testSpec.js new file mode 100644 index 000000000..17cc71b24 --- /dev/null +++ b/tests/platform/windows/windows-dynamicDeviceReporter-testSpec.js @@ -0,0 +1,101 @@ +/* +GPII Integration and Acceptance Testing + +Copyright 2015 Raising The Floor US + +Licensed under the New BSD license. You may not use this file except in +compliance with this License. + +You may obtain a copy of the License at +https://github.com/gpii/universal/LICENSE.txt +*/ +"use strict"; +var fluid = require("universal"), + gpii = fluid.registerNamespace("gpii"); + +gpii.loadTestingSupport(); + +fluid.registerNamespace("gpii.tests.deviceReporterAware.windows"); + +gpii.tests.deviceReporterAware.windows = [ + { + name: "Testing screenreader_nvda using Flat matchmaker", + userToken: "screenreader_nvda", + gradeNames: "gpii.test.integration.deviceReporterAware.windows", + settingsHandlers: { + "gpii.settingsHandlers.INISettingsHandler": { + "data": [ + { + "settings": { + "speech.espeak.rate": 17, + "speech.espeak.volume": 80, + "speech.espeak.pitch": 60, + "speech.espeak.rateBoost": true, + "speech.synth": "espeak", + "speech.outputDevice": "Microsoft Sound Mapper", + "speech.symbolLevel": 300, + "speech.espeak.voice": "en\\en-wi", + "reviewCursor.followFocus": false, + "reviewCursor.followCaret": true, + "reviewCursor.followMouse": true, + "keyboard.speakTypedWords": true, + "keyboard.speakTypedCharacters": false, + "presentation.reportHelpBalloons": false, + "speech.espeak.sayCapForCapitals": true + }, + "options": { + "filename": "${{environment}.APPDATA}\\nvda\\nvda.ini", + "allowNumberSignComments": true, + "allowSubSections": true + } + } + ] + } + }, + processes: [ + { + "command": "tasklist /fi \"STATUS eq RUNNING\" /FI \"IMAGENAME eq nvda.exe\" | find /I \"nvda.exe\" /C", + "expectConfigured": "1", + "expectRestored": "0" + } + ], + deviceReporters: { + "gpii.deviceReporter.registryKeyExists": { + "expectInstalled": [{ + "hKey": "HKEY_LOCAL_MACHINE", + "path": "Software\\Microsoft\\Windows\\CurrentVersion\\App Paths\\nvda.exe", + "subPath": "" + }] + } + } + }, + { + name: "Testing readwritegold_application1 using Flat matchmaker", + userToken: "readwritegold_application1", + gradeNames: "gpii.test.integration.deviceReporterAware.windows", + settingsHandlers: {}, + processes: [ + { + "command": "tasklist /fi \"STATUS eq RUNNING\" /FI \"IMAGENAME eq ReadAndWrite.exe\" | find /I \"ReadAndWrite.exe\" /C", + "expectConfigured": "0", + "expectRestored": "0" + } + ], + deviceReporters: { + "gpii.deviceReporter.registryKeyExists": { + "expectInstalled": [{ + "hKey": "HKEY_LOCAL_MACHINE", + "path": "Software\\Microsoft\\Windows\\CurrentVersion\\App Paths\\nvda.exe", + "subPath": "" + }] + } + } + } +]; + +module.exports = gpii.test.bootstrap({ + testDefs: "gpii.tests.deviceReporterAware.windows", + configName: "windows-dynamicDeviceReporter-config", + configPath: "configs" +}, ["gpii.test.integration.testCaseHolder.windows", "gpii.test.integration.deviceReporterAware.windows"], + module, require, __dirname); diff --git a/tests/platform/windows/windows-dynamicDeviceReporter-testSpec.txt b/tests/platform/windows/windows-dynamicDeviceReporter-testSpec.txt new file mode 100644 index 000000000..f0a627886 --- /dev/null +++ b/tests/platform/windows/windows-dynamicDeviceReporter-testSpec.txt @@ -0,0 +1,16 @@ +windows-dynamicDeviceReporter-testSpec.js + +Descriptions: + +This will run the acceptance tests with dynamic device reporting. +It uses 2 NP sets: screenreader_nvda and readwritegold_application1. + +Due to some current limitations/issues with the current flat matchmaker, JAWS +shoudl not be installed, and sociable should not be listed as installed, because +they will both attempt to launch with an NVDA NP. + +Requirements: +* NVDA installed +* Read and Write Gold not installed + +To run these tests, you need to have NVDA INSTALLED. NVDA is free and can be downloaded from: http://www.nvaccess.org/download/