| 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);
};
|