All files / universal.klown/gpii/node_modules/flowManager/src UntrustedSettingsDataSource.js

97.3% Statements 36/37
100% Branches 5/5
88.89% Functions 8/9
97.3% Lines 36/37

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217                            1x 1x   1x     1x               1x                                                                                                                                                                                                   1x 20x 20x   20x 19x               19x   1x     20x                   1x 3x 3x   3x 3x             3x         3x                   1x 23x   23x 23x         23x 21x 21x 20x 20x 20x 20x   21x   2x                       1x 20x 20x    
/**
 * 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.untrustedSettingsDataSource 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.untrustedSettingsDataSource", {
    gradeNames: ["fluid.component"],
 
    // Options that are distributed down from, e.g., gpii.flowManager.untrusted.config.development
    untrustedSettingsGetUrl: null,
    untrustedSettingsPutUrl: null,
    accessTokenUrl: null,
 
    // 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: {
        untrustedSettingsGetUrl: {
            source: "{that}.options.untrustedSettingsGetUrl",
            target: "{that > untrustedSettingsDataSourceGetImpl}.options.url"
        },
        untrustedSettingsPutUrl: {
            source: "{that}.options.untrustedSettingsPutUrl",
            target: "{that > untrustedSettingsDataSourcePutImpl}.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 user token. The structure looks like:
            // "userToken1": {
            //     accessToken: "a-token-value",
            //     timestampExpires: "an-timestamp-the-token-expires"
            // }
            // ...
        }
    },
    components: {
        untrustedSettingsDataSourceGetImpl: {
            type: "kettle.dataSource.URL",
            options: {
                // url: distributed down from the parent component gpii.flowManager.untrustedSettingsDataSource
                termMap: {
                    "userToken": "%userToken",
                    "device": "%device"
                }
            }
        },
        untrustedSettingsDataSourcePutImpl: {
            type: "kettle.dataSource.URL",
            options: {
                // url: distributed down from the parent component gpii.flowManager.untrustedSettingsDataSource
                termMap: {
                    "userToken": "%userToken"
                },
                writable: true,
                writeMethod: "PUT"
            }
        },
        accessRequester: {
            type: "gpii.accessRequester",
            options: {
                clientCredentialDataSourceGrade: "gpii.accessRequester.clientCredentialDataSource.file"
            }
        }
    },
    invokers: {
        get: {
            funcName: "gpii.flowManager.untrustedSettingsDataSource.get",
            args: ["{that}", "{arguments}.0", "{arguments}.1"]
            // userToken, device
        },
        set: {
            funcName: "gpii.flowManager.untrustedSettingsDataSource.set",
            args: ["{that}", "{arguments}.0", "{arguments}.1"]
            // userToken, preferences
        },
        save: {
            funcName: "gpii.flowManager.untrustedSettingsDataSource.save",
            args: ["{that}.accessTokens", "{arguments}.0", "{arguments}.1", "{arguments}.2"]
            // userToken, accessToken, timestampExpires
        }
    }
});
 
/**
 * Retrieve user settings from the cloud using the access token requested for the keyed in user token.
 * @param that {Component} An instance of gpii.flowManager.untrustedSettingsDataSource
 * @param userToken {String} A user token
 * @param device {Object} The device information provided by the device reporter
 * @return {Promise} A promise whose resolved value is the user settings
 */
gpii.flowManager.untrustedSettingsDataSource.get = function (that, userToken, device) {
    var accessTokenPromise = gpii.flowManager.untrustedSettingsDataSource.findValidAccessToken(that, userToken);
    var promiseTogo = fluid.promise();
 
    accessTokenPromise.then(function (accessToken) {
        var settingsPromise = that.untrustedSettingsDataSourceGetImpl.get({
            userToken: userToken,
            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 user token.
 * @param that {Component} An instance of gpii.flowManager.untrustedSettingsDataSource
 * @param userToken {String} A user token
 * @param preferences {Object} The to-be-updated preferences
 * @return {Promise} A promise whose resolved value is the status of the update
 */
gpii.flowManager.untrustedSettingsDataSource.set = function (that, userToken, preferences) {
    var accessTokenPromise = gpii.flowManager.untrustedSettingsDataSource.findValidAccessToken(that, userToken);
    var promiseTogo = fluid.promise();
 
    accessTokenPromise.then(function (accessToken) {
        var updatePromise = that.untrustedSettingsDataSourcePutImpl.set({
            userToken: userToken
        }, 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 user token,
 * If it has expired, request and return a new one from the cloud, otherwise, return the saved access token.
 * @param that {Component} An instance of gpii.flowManager.untrustedSettingsDataSource
 * @param userToken {String} A user token
 * @return {Promise} A promise whose resolved value is a valid access token
 */
gpii.flowManager.untrustedSettingsDataSource.findValidAccessToken = function (that, userToken) {
    var allAccessTokens = that.accessTokens;
 
    var accessToken = fluid.get(allAccessTokens, [userToken, "accessToken"]);
    var expiresIn = gpii.oauth2.getExpiresIn(new Date(), fluid.get(allAccessTokens, [userToken, "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(userToken);
        var mapper = function (accessTokenObj) {
            var accessTokenFromCloud = accessTokenObj.access_token;
            var timestampExpiresFromCloud = gpii.oauth2.getTimestampExpires(new Date(), accessTokenObj.expiresIn);
            that.save(userToken, 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 user token for the next use.
 * @param that {Component} An instance of gpii.flowManager.untrustedSettingsDataSource
 * @param userToken {String} The user token that the access token associates with
 * @param accessToken {String} The access token to be saved
 * @param timestampExpires {String} A timestampExpires to be saved
 * @return {Promise} A promise whose resolved value is a valid access token
 */
gpii.flowManager.untrustedSettingsDataSource.save = function (allAccessTokens, userToken, accessToken, timestampExpires) {
    fluid.set(allAccessTokens, [userToken, "accessToken"], accessToken);
    fluid.set(allAccessTokens, [userToken, "timestampExpires"], timestampExpires);
};