UNPKG

gpii-universal

Version:

Cross platform, core components of the GPII personalization infrastructure.

244 lines (222 loc) 9.87 kB
/** * GPII Untrusted Settings Data Source * * Copyright 2017 OCAD University * * 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/blob/master/LICENSE.txt */ "use strict"; var fluid = require("infusion"), gpii = fluid.registerNamespace("gpii"); require("accessRequester"); // To reuse gpii.oauth2.getExpiresIn() and gpii.oauth2.getTimestampExpires() fluid.require("%gpii-universal/gpii/node_modules/gpii-oauth2/gpii-oauth2-utilities/src/OAuth2Utilities.js"); // gpii.flowManager.settingsDataSource provides a get() API that returns a promise // whose resolved value is user settings. The internal steps performed by this API: // 1. check the access token requested last time. If it's still valid, use it to request then return user settings; // 2. If the access token has not been requested or it has expired, request an access token via accessRequester subcomponent; // 3. Save the received access token as member options; // 4. Use the access token to request then return user settings. fluid.defaults("gpii.flowManager.settingsDataSource", { gradeNames: ["fluid.component"], // This option should be distributed down from, e.g., gpii.flowManager.config.untrusted.base cloudURL: null, settingsGetUrlTemplate: "%cloudURL/%gpiiKey/settings/%device", settingsPutUrlTemplate: "%cloudURL/%gpiiKey/settings", accessTokenUrlTemplate: "%cloudURL/access_token", // The URLs below are calculated from the flow manager base URL and their templates. settingsGetUrl: { expander: { funcName: "fluid.stringTemplate", args: ["{that}.options.settingsGetUrlTemplate", { cloudURL: "{that}.options.cloudURL" }] } }, settingsPutUrl: { expander: { funcName: "fluid.stringTemplate", args: ["{that}.options.settingsPutUrlTemplate", { cloudURL: "{that}.options.cloudURL" }] } }, accessTokenUrl: { expander: { funcName: "fluid.stringTemplate", args: ["{that}.options.accessTokenUrlTemplate", { cloudURL: "{that}.options.cloudURL" }] } }, // TODO: Reading the client credential from the file system is a temporary solution. This option // should be removed once a proper access requester is in place (https://issues.gpii.net/browse/GPII-2436). clientCredentialFilePath: null, // The minimum number of seconds of the lfe time of an access token for it to continuing to be used. minAccessTokenLifeTimeInSecond: 10, distributeOptions: { settingsGetUrl: { source: "{that}.options.settingsGetUrl", target: "{that > settingsDataSourceGetImpl}.options.url" }, settingsPutUrl: { source: "{that}.options.settingsPutUrl", target: "{that > settingsDataSourcePutImpl}.options.url" }, accessTokenUrl: { source: "{that}.options.accessTokenUrl", target: "{that > accessRequester}.options.url" }, clientCredentialFilePath: { source: "{that}.options.clientCredentialFilePath", target: "{that clientCredentialDataSource}.options.path" } }, members: { accessTokens: { // To keep track of the most recent access token and its expiresIn timestamp for each GPII key. The structure looks like: // "gpiiKey1": { // accessToken: "a-accessToken-value", // timestampExpires: "an-timestamp-the-accessToken-expires" // } // ... } }, components: { settingsDataSourceGetImpl: { type: "kettle.dataSource.URL", options: { // url: distributed down from the parent component gpii.flowManager.settingsDataSource termMap: { "gpiiKey": "%gpiiKey", "device": "%device" } } }, settingsDataSourcePutImpl: { type: "kettle.dataSource.URL", options: { // url: distributed down from the parent component gpii.flowManager.settingsDataSource termMap: { "gpiiKey": "%gpiiKey" }, writable: true, writeMethod: "PUT" } }, accessRequester: { type: "gpii.accessRequester", options: { clientCredentialDataSourceGrade: "gpii.accessRequester.clientCredentialDataSource.file" } } }, invokers: { get: { funcName: "gpii.flowManager.settingsDataSource.get", args: ["{that}", "{arguments}.0", "{arguments}.1"] // gpiiKey, device }, set: { funcName: "gpii.flowManager.settingsDataSource.set", args: ["{that}", "{arguments}.0", "{arguments}.1"] // gpiiKey, preferences }, save: { funcName: "gpii.flowManager.settingsDataSource.save", args: ["{that}.accessTokens", "{arguments}.0", "{arguments}.1", "{arguments}.2"] // gpiiKey, accessToken, timestampExpires } } }); /** * Retrieve user settings from the cloud using the access token requested for the keyed in GPII key. * @param {Component} that - An instance of gpii.flowManager.settingsDataSource. * @param {String} gpiiKey - A GPII key. * @param {Object} device - The device information provided by the device reporter. * @return {Promise} A promise whose resolved value is the user settings. */ gpii.flowManager.settingsDataSource.get = function (that, gpiiKey, device) { var accessTokenPromise = gpii.flowManager.settingsDataSource.findValidAccessToken(that, gpiiKey); var promiseTogo = fluid.promise(); accessTokenPromise.then(function (accessToken) { var settingsPromise = that.settingsDataSourceGetImpl.get({ gpiiKey: gpiiKey, device: JSON.stringify(device) }, { headers: { "Authorization": "Bearer " + accessToken } }); fluid.promise.follow(settingsPromise, promiseTogo); }, function (err) { promiseTogo.reject(err); }); return promiseTogo; }; /** * Update user preferences to the cloud using the access token requested for the keyed in GPII key. * @param {Component} that - An instance of gpii.flowManager.settingsDataSource. * @param {String} gpiiKey A GPII key. * @param {Object} preferences The to-be-updated preferences. * @return {Promise} A promise whose resolved value is the status of the update. */ gpii.flowManager.settingsDataSource.set = function (that, gpiiKey, preferences) { var accessTokenPromise = gpii.flowManager.settingsDataSource.findValidAccessToken(that, gpiiKey); var promiseTogo = fluid.promise(); accessTokenPromise.then(function (accessToken) { var updatePromise = that.settingsDataSourcePutImpl.set({ gpiiKey: gpiiKey }, preferences, { headers: { "Authorization": "Bearer " + accessToken } }); fluid.promise.follow(updatePromise, promiseTogo); }, function (err) { promiseTogo.reject(err); }); return promiseTogo; }; /** * Find a valid access token. It first checks the saved access token for the keyed in GPII key, * If it has expired, request and return a new one from the cloud, otherwise, return the saved access token. * @param {Component} that - An instance of gpii.flowManager.settingsDataSource. * @param {String} gpiiKey - A GPII key. * @return {Promise} A promise whose resolved value is a valid access token. */ gpii.flowManager.settingsDataSource.findValidAccessToken = function (that, gpiiKey) { var allAccessTokens = that.accessTokens; var accessToken = fluid.get(allAccessTokens, [gpiiKey, "accessToken"]); var expiresIn = gpii.oauth2.getExpiresIn(new Date(), fluid.get(allAccessTokens, [gpiiKey, "timestampExpires"])); // If the locally saved access token exists and is still valid, return it. // Otherwise, request an new access token from the cloud and return. // The new access token is saved locally for the continuing use. if (!accessToken || !expiresIn || expiresIn < that.options.minAccessTokenLifeTimeInSecond) { var accessTokenPromise = that.accessRequester.getAccessToken(gpiiKey); var mapper = function (accessTokenObj) { var accessTokenFromCloud = accessTokenObj.access_token; var timestampExpiresFromCloud = gpii.oauth2.getTimestampExpires(new Date(), accessTokenObj.expiresIn); that.save(gpiiKey, accessTokenFromCloud, timestampExpiresFromCloud); return accessTokenFromCloud; }; return fluid.promise.map(accessTokenPromise, mapper); } else { return fluid.promise().resolve(accessToken); } }; /** * Save the access token and its timestampExpires in the index of the GPII key for the next use. * @param {Component} allAccessTokens - All saved access tokens * @param {String} gpiiKey - The GPII key that the access token associates with. * @param {String} accessToken - The access token to be saved. * @param {String} timestampExpires - A timestampExpires to be saved. */ gpii.flowManager.settingsDataSource.save = function (allAccessTokens, gpiiKey, accessToken, timestampExpires) { fluid.set(allAccessTokens, [gpiiKey, "accessToken"], accessToken); fluid.set(allAccessTokens, [gpiiKey, "timestampExpires"], timestampExpires); };