-
Notifications
You must be signed in to change notification settings - Fork 7
/
Goa.gs
380 lines (310 loc) · 9.84 KB
/
Goa.gs
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
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
/**
* create a goa class
* @constructor
* @param {string} packageName the pockage name
* @param {PropertyStore} propertyStore the property store
* @param {number} [optTimeout] in seconds
* @param {string} [impersonate] email address to impersonate for service accounts
*/
var Goa = function (packageName, propertyStore, optTimeout , impersonate) {
var _propertyStore = propertyStore,
package_name = packageName ,
self = this ,
_phase,
_id ,
_params ,
_callback,
_package,
_needsConsent,
_timeout = optTimeout,
_impersonate = impersonate,
_consentScreen,
_name,
_onToken,
_onTokenResult,
_uiOpts;
/**
* the function to call when a token is created
* @param {string} onTokenFunction the function to call
* @return {Goa} self
*/
self.setOnToken = function (onTokenFunction) {
if (typeof onTokenFunction !== 'function') throw 'ontoken callback must be a function';
_onToken = onTokenFunction;
return self;
};
/**
* any special UI options
* @param {object} opts
*/
self.setUiBehavior = function (opts) {
_uiOpts = opts;
return self;
};
/**
* execute the requested phase
* @param {string} params the callback params or user params
* @return {Goa} self
*/
self.execute = function (params) {
// store these for later
_params = params;
// the phase & id to execute is in the state token, if it exists
_phase = GoaApp.getCustomParameter(_params).goaphase || 'init';
_id = GoaApp.getCustomParameter(_params).goaid;
// the name
_name = GoaApp.getName(_params);
// load in the pockage on initialization
_package = GoaApp.getPackage (_propertyStore , package_name);
if (!_package) throw 'cannot find pockage ' + package_name + ' in given property store';
// check we have parameters matching the pockage
if (_name && _name !== _package.packageName) throw 'the param name ' + _name +
' is different than the pockage name ' + _package.packageName;
// make sure we dont get into a loop with expiry being less than grace period
_timeout = Math.floor(Math.max (GoaApp.gracePeriod /1000 ,
Utils.applyDefault(_timeout, GoaApp.getServicePackage(_package).defaultDuration || 0)));
// if we have a token our work is done
if (self.hasToken() ) {
return self;
}
// try to get one.
GoaApp.start (_package, undefined, _impersonate, _timeout );
if (GoaApp.hasToken(_package)) {
self.writePackage();
// if there's a call back then do it.
exec_onToken();
return self;
}
// apparently we don't have one, so need to enter a consent flow
// this is able to figure out which function is managing the goa flow
if(!_callback) {
// using whereAMI no longer works - so just defaulting to doGet
self.setCallback ('doGet');
}
// if this is the first time in, we need to signal a consent screen is needed
if (_phase === "init") {
// need to store these for later
_id = Utils.generateUniqueString();
self.writePackage();
GoaApp.cachePut ( _id , _package.packageName , _params, _onToken);
var offline = Utils.applyDefault(_package.offline, true);
var apack = {
callback : _callback,
timeout: _timeout,
offline:offline,
force: true
};
var bpack = {
goaid:_id,
goaphase:'fetch',
goaname:_package.packageName
};
// set up the consent screen
_needsConsent = (_consentScreen || GoaApp.defaultConsentScreen) ( GoaApp.createAuthenticationUrl (
_package, apack, bpack) ,GoaApp.createRedirectUri(), _package.packageName, _package.service, offline, _uiOpts);
return self;
}
// if this is a fetch iteration then we've been called back by a consent requests
if (_phase === "fetch") {
var result = GoaApp.fetchAccessToken (_package , params);
if (!self.hasToken()) {
throw 'Failed to get access token : operation was cancelled';
}
// store it
self.writePackage ();
// if there's a call back then do it.
exec_onToken();
return self;
}
throw 'unknown phase:' + _phase
};
function getCacheContents_() {
var p = GoaApp.cacheGet (_id);
if (!p) throw 'cached arguments not found for ' + _package.packageName;
if (p.name !== _package.packageName) throw 'cache mismatch for ' + p.name + ':should have been ' + _package.packageName;
return p;
}
/**
* get parameters for function
* @return {object} the parameters
*/
self.getParams = function () {
return _phase === "init" ? _params : getCacheContents_().args;
};
/**
* get ontoken callback
* @return {object} the callback
*/
self.getOnToken = () => {
if (_phase !== "init") {
var o = getCacheContents_().onToken;
_onToken = o ? eval(o) : undefined;
}
return _onToken; // just return the function to be executed on completion
};
/**
* get ontoken result
* @return {object} the callback
*/
self.getOnTokenResult = function () {
return _onTokenResult;
};
/**
* get the consent page
* @return {HtmlOutput} the consent page
*/
self.getConsent = function () {
return HtmlService.createHtmlOutput(_needsConsent);
};
self.done = function () {
// set up close message or go away.
return HtmlService.createHtmlOutput(
GoaApp.closeWindow(self.hasToken() ,_uiOpts || {
close:false,
}));
};
/**
* get consent in a sidebar/dialog
* @param {UI} ui the ui to use
* @param {object} opts {width:300 , title: "goa oauth2 dialog", type:"SIDEBAR" || "DIALOG" , modal:true }
*/
self.getConsentUi = function (ui, opts) {
// clone
var options = opts ? JSON.parse(JSON.stringify(opts)) : {};
options.type = options.type || "SIDEBAR";
options.width = options.type === "DIALOG" ? (options.width || 600) : 0;
options.height = options.type === "DIALOG" ? (options.height || 400) : 0;
options.title = options.hasOwnProperty("title") ? options.title : ' goa oauth2 dialog for ' + self.getPackage().packageName;
options.modal = options.hasOwnProperty("modal") ? options.modal : true;
// set up the dialog. consent returns an htmlservice
var html = self.getConsent()
.setTitle(options.title);
if (options.height)html.setHeight(options.height);
if (options.width)html.setWidth(options.width);
// where to do it
if (options.type === "SIDEBAR") {
ui.showSidebar (html);
}
else if (options.type === "DIALOG") {
ui[options.modal ? 'showModalDialog' : 'showModelessDialog'] (html, options.title);
}
else {
throw 'unknown dialog type ' + options.type;
}
return self;
}
/**
* get the consent page
* @return {boolean} whether consent is needed
*/
self.needsConsent = function () {
return _needsConsent ? true : false ;
};
/**
* set the callback
* @param {string} callback callback name
* @return {Goa} self
*/
self.setCallback = function (callback) {
// convert the string into a function
//var callbackFunction = eval(callback);
// make sure it is a function
//if (typeof callbackFunction !== 'function' || !callbackFunction.name) throw 'callback must be a named function';
//_callback = callbackFunction;
_callback = callback;
return self;
};
/**
* set the callback function for the consent screen
* it will receive two args - the userconsent url, and the redirect url
* @param {function} consentCallback user consent callback
* @return {Goa} self
*/
self.setConsentScreen = function (consentCallback) {
_consentScreen = consentCallback;
return self;
};
/**
* test for token
* @param {boolean} check whether to check against infra
* @return {boolean} there is one or not
*/
self.hasToken = function (check) {
return GoaApp.hasToken (_package,check);
};
/**
* get token
* @return {string | null} the token
*/
self.getToken = function () {
const token = GoaApp.getToken (_package);
if (token) return token
// we could try to refresh one
if (GoaApp.hasRefreshToken(_package)) {
GoaApp.tryRefresh(_package);
if (self.hasToken()) {
self.writePackage()
}
return GoaApp.getToken (_package)
}
return null
};
self.getPropertyStore = () => _propertyStore
/**
* get property
* @param {string} key the key
* @return {string | undefined} the property value
*/
self.getProperty = function (key) {
return GoaApp.getProperty (_package , key);
};
/**
* get pockage
* @return {object | undefined} the pockage
*/
self.getPackage = function () {
return _package ;
};
/**
* fetch pockage
* @return {object | null} the package
*/
self.fetchPackage = () => {
return _package ? {..._package} : null
}
/**
* write the pockage
* @return self
*/
self.writePackage = function () {
_package.revised = new Date().getTime();
GoaApp.setPackage ( _propertyStore , _package);
return self;
};
/**
* update the package
*/
self.updatePackage = (pockage) => {
_package = pockage
self.writePackage()
return self
}
/**
* kill the pockage
*/
self.kill = function () {
GoaApp.killPackage(_package);
return self.writePackage();
};
/**
* remove the pockage
*/
self.remove = function () {
return GoaApp.removePackage ( _propertyStore, _package.packageName );
};
function exec_onToken() {
var onToken = self.getOnToken();
_onTokenResult = onToken ? onToken(self.getToken() , _package.packageName , self.getParams()) : undefined;
}
return self;
};