1 /**
  2 * Copyright (c) 2014, salesforce.com, inc.
  3 * All rights reserved.
  4 *
  5 * Redistribution and use in source and binary forms, with or without modification, are permitted provided
  6 * that the following conditions are met:
  7 *
  8 * Redistributions of source code must retain the above copyright notice, this list of conditions and the
  9 * following disclaimer.
 10 *
 11 * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and
 12 * the following disclaimer in the documentation and/or other materials provided with the distribution.
 13 *
 14 * Neither the name of salesforce.com, inc. nor the names of its contributors may be used to endorse or
 15 * promote products derived from this software without specific prior written permission.
 16 *
 17 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED
 18 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
 19 * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
 20 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
 21 * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 22 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 23 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 24 * POSSIBILITY OF SUCH DAMAGE.
 25 */
 26 
 27 
 28 /**
 29  *@namespace Sfdc.canvas.oauth
 30  *@name Sfdc.canvas.oauth
 31  */
 32 (function ($$) {
 33 
 34     "use strict";
 35 
 36     var storage = (function() {
 37 
 38         function isLocalStorage() {
 39             try {
 40                 return 'sessionStorage' in window && window.sessionStorage !== null;
 41             } catch (e) {
 42                 return false;
 43             }
 44         }
 45 
 46         return {
 47             get : function get(key) {
 48                 if (isLocalStorage()) {
 49                     return sessionStorage.getItem(key);
 50                 }
 51                 return $$.cookies.get(key);
 52             },
 53             set : function set(key, value) {
 54                 if (isLocalStorage()) {
 55                     return sessionStorage.setItem(key, value);
 56                 }
 57                 return $$.cookies.set(key, value);
 58             },
 59             remove : function remove(key) {
 60                 if (isLocalStorage()) {
 61                     return sessionStorage.removeItem(key);
 62                 }
 63                 return $$.cookies.remove(key);
 64             }
 65         };
 66 
 67     }());
 68 
 69     var module =   (function() {
 70 
 71         var accessToken,
 72             instUrl,
 73             instId,
 74             tOrigin,
 75             childWindow;
 76 
 77         function init() {
 78             // Get the access token from the sessionStorage or cookie (needed to survive refresh),
 79             // and then remove the cookie per security's request.
 80             accessToken = storage.get("access_token");
 81             storage.remove("access_token");
 82         }
 83 
 84         function query(params) {
 85             var r = [], n;
 86             if (!$$.isUndefined(params)) {
 87                 for (n in params) {
 88                     if (params.hasOwnProperty(n)) {
 89                         // probably should encode these
 90                         r.push(n + "=" + params[n]);
 91                     }
 92                 }
 93                 return "?" + r.join('&');
 94             }
 95             return '';
 96         }
 97         /**
 98          *@private
 99          */
100         function refresh() {
101             // Temporarily set the oauth token in a sessionStorage or cookie and then remove it
102             // after the refresh.
103             storage.set("access_token", accessToken);
104             self.location.reload();
105         }
106         /**
107          * @name Sfdc.canvas.oauth#login
108          * @function
109          * @description Opens the OAuth popup window to retrieve an OAuth token.
110          * @param {Object} ctx  The context object that contains the URL, the response type, the client ID, and the callback URL
111          * @docneedsimprovement
112          * @example
113          * function clickHandler(e)
114          * {
115          *  var uri;
116          *  if (! connect.oauth.loggedin())
117          *  {
118          *   uri = connect.oauth.loginUrl();
119          *   connect.oauth.login(
120          *    {uri : uri,
121          *     params: {
122          *      response_type : "token",
123          *      client_id :  "<%=consumerKey%>",
124          *      redirect_uri : encodeURIComponent("/sdk/callback.html")
125          *      }});
126          *  } else {
127          *     connect.oauth.logout();
128          *  }
129          *  return false;
130          * }
131          */
132         function login(ctx) {
133             var uri;
134 
135             ctx = ctx || {};
136             uri = ctx.uri || "/rest/oauth2";
137             ctx.params = ctx.params || {state : ""};
138             ctx.params.state = ctx.params.state || ctx.callback || window.location.pathname;  // @TODO REVIEW THIS
139             ctx.params.display= ctx.params.display || 'popup';
140             ctx.params.redirect_uri = $$.startsWithHttp(ctx.params.redirect_uri, 
141                     encodeURIComponent(window.location.protocol + "//" + window.location.hostname + ":" + window.location.port) + ctx.params.redirect_uri);
142             uri = uri + query(ctx.params);
143             childWindow = window.open(uri, 'OAuth', 'status=0,toolbar=0,menubar=0,resizable=0,scrollbars=1,top=50,left=50,height=500,width=680');
144         }
145 
146         /**
147          * @name Sfdc.canvas.oauth#token
148          * @function
149          * @description Sets, gets, or removes the <code>access_token</code> from this JavaScript object. <br>
150          <p>This function does one of three things: <br>
151          	1) If the 't' parameter isn't passed in, the current value for the <code>access_token</code> value is returned. <br>
152          	2) If the the 't' parameter is null, the <code>access_token</code> value is removed. <br>
153          	3) Otherwise the <code>access_token</code>  value is set to the 't' parameter and then returned.<br><br>
154          	Note: for longer-term storage of the OAuth token, store it server-side in the session.  Access tokens
155              should never be stored in cookies.
156          * @param {String} [t] The OAuth token to set as the <code>access_token</code> value
157          * @returns {String} The resulting <code>access_token</code> value if set; otherwise null
158          */
159         function token(t) {
160             if (arguments.length === 0) {
161                 if (!$$.isNil(accessToken)) {return accessToken;}
162             }
163             else {
164                 accessToken = t;
165             }
166 
167             return accessToken;
168         }
169 
170         /**
171          * @name Sfdc.canvas.oauth#instance
172          * @function
173          * @description Sets, gets, or removes the <code>instance_url</code> cookie. <br>
174          <p> This function does one of three things: <br>
175          1) If the 'i' parameter is not passed in, the current value for the <code>instance_url</code> cookie is returned. <br>
176          2) If the 'i' parameter is null, the <code>instance_url</code> cookie is removed. <br>
177          3) Otherwise, the <code>instance_url</code> cookie value is set to the 'i' parameter and then returned.
178          * @param {String} [i] The value to set as the <code>instance_url</code> cookie
179          * @returns {String} The resulting <code>instance_url</code> cookie value if set; otherwise null
180          */
181         function instanceUrl(i) {
182             if (arguments.length === 0) {
183                 if (!$$.isNil(instUrl)) {return instUrl;}
184                 instUrl = storage.get("instance_url");
185             }
186             else if (i === null) {
187                 storage.remove("instance_url");
188                 instUrl = null;
189             }
190             else {
191                 storage.set("instance_url", i);
192                 instUrl = i;
193             }
194             return instUrl;
195         }
196 
197         /**
198          *@private
199          */
200             // Example Results of tha hash....
201             // Name [access_token] Value [00DU0000000Xthw!ARUAQMdYg9ScuUXB5zPLpVyfYQr9qXFO7RPbKf5HyU6kAmbeKlO3jJ93gETlJxvpUDsz3mqMRL51N1E.eYFykHpoda8dPg_z]
202             // Name [instance_url] Value [https://na12.salesforce.com]
203             // Name [id] Value [https://login.salesforce.com/id/00DU0000000XthwMAC/005U0000000e6PoIAI]
204             // Name [issued_at] Value [1331000888967]
205             // Name [signature] Value [LOSzVZIF9dpKvPU07icIDOf8glCFeyd4vNGdj1dhW50]
206             // Name [state] Value [/crazyrefresh.html]
207         function parseHash(hash) {
208             var i, nv, nvp, n, v;
209 
210             if (! $$.isNil(hash)) {
211                 if (hash.indexOf('#') === 0) {
212                     hash = hash.substr(1);
213                 }
214                 nvp = hash.split("&");
215 
216                 for (i = 0; i < nvp.length; i += 1) {
217                     nv = nvp[i].split("=");
218                     n = nv[0];
219                     v = decodeURIComponent(nv[1]);
220                     if ("access_token" === n) {
221                         token(v);
222                     }
223                     else if ("instance_url" === n) {
224                         instanceUrl(v);
225                     }
226                     else if ("target_origin" === n) {
227                         tOrigin = decodeURIComponent(v);
228                     }
229                     else if ("instance_id" === n) {
230                         instId = v;
231                     }
232                 }
233             }
234         }
235 
236         /**
237          * @name Sfdc.canvas.oauth#checkChildWindowStatus
238          * @function
239          * @description Refreshes the parent window only if the child window is closed. This
240          * method is no longer used. Leaving in for backwards compatability.
241          */
242         function checkChildWindowStatus() {
243             if (!childWindow || childWindow.closed) {
244                 refresh();
245             }
246         }
247 
248         /**
249          * @name Sfdc.canvas.oauth#childWindowUnloadNotification
250          * @function
251          * @description Parses the hash value that is passed in and sets the
252          <code>access_token</code> and <code>instance_url</code> cookies if they exist.  Use this method during
253          User-Agent OAuth Authentication Flow to pass the OAuth token.
254          * @param {String} hash A string of key-value pairs delimited by
255          the ampersand character.
256          * @example
257          * Sfdc.canvas.oauth.childWindowUnloadNotification(self.location.hash);
258          */
259         function childWindowUnloadNotification(hash) {
260             // Here we get notification from child window. Here we can decide if such notification is
261             // raised because user closed child window, or because user is playing with F5 key.
262             // NOTE: We can not trust on "onUnload" event of child window, because if user reload or refresh
263             // such window in fact he is not closing child. (However "onUnload" event is raised!)
264 
265             var retry = 0, maxretries = 10;
266 
267             // Internal check child window status with max retry logic
268             function cws() {
269 
270                 retry++;
271                 if (!childWindow || childWindow.closed) {
272                     refresh();
273                 }
274                 else if (retry < maxretries) {
275                     setTimeout(cws, 50);
276                 }
277             }
278 
279             parseHash(hash);
280             setTimeout(cws, 50);
281         }
282 
283         /**
284          * @name Sfdc.canvas.oauth#logout
285          * @function
286          * @description Removes the <code>access_token</code> OAuth token from this object.
287          */
288         function logout() {
289             // Remove the oauth token and refresh the browser
290             token(null);
291         }
292 
293         /**
294          * @name Sfdc.canvas.oauth#loggedin
295          * @function
296          * @description Returns the login state.
297          * @returns {Boolean} <code>true</code> if the <code>access_token</code> is available in this JS object.
298          * Note: <code>access tokens</code> (for example, OAuth tokens) should be stored server-side for more durability.
299          * Never store OAuth tokens in cookies as this can lead to a security risk.
300          */
301         function loggedin() {
302             return !$$.isNil(token());
303         }
304 
305         /**
306          * @name Sfdc.canvas.oauth#loginUrl
307          * @function
308          * @description Returns the URL for the OAuth authorization service.
309          * @returns {String} The URL for the OAuth authorization service or default if there's
310          *   no value for loginUrl in the current URL's query string
311          */
312         function loginUrl() {
313             var i, nvs, nv, q = self.location.search;
314 
315             if (q) {
316                 q = q.substring(1);
317                 nvs = q.split("&");
318                 for (i = 0; i < nvs.length; i += 1)
319                 {
320                     nv = nvs[i].split("=");
321                     if ("loginUrl" === nv[0]) {
322                         return decodeURIComponent(nv[1]) + "/services/oauth2/authorize";
323                     }
324                 }
325             }
326             return "https://login.salesforce.com/services/oauth2/authorize";
327         }
328 
329         function targetOrigin(to) {
330 
331             if (!$$.isNil(to)) {
332                 tOrigin = to;
333                 return to;
334             }
335 
336             if (!$$.isNil(tOrigin)) {return tOrigin;}
337 
338             // This relies on the parent passing it in. This may not be there as the client can do a
339             // redirect or link to another page
340             parseHash(document.location.hash);
341             return tOrigin;
342         }
343 
344         function instanceId(id) {
345 
346             if (!$$.isNil(id)) {
347                 instId = id;
348                 return id;
349             }
350 
351             if (!$$.isNil(instId)) {return instId;}
352 
353             // This relies on the parent passing it in. This may not be there as the client can do a
354             // redirect or link to another page
355             parseHash(document.location.hash);
356             return instId;
357         }
358 
359         function client() {
360             return {oauthToken : token(), instanceId : instanceId(), targetOrigin : targetOrigin()};
361         }
362 
363         return {
364             init : init,
365             login : login,
366             logout : logout,
367             loggedin : loggedin,
368             loginUrl : loginUrl,
369             token : token,
370             instance : instanceUrl,
371             client : client,
372             checkChildWindowStatus : checkChildWindowStatus,
373             childWindowUnloadNotification: childWindowUnloadNotification
374         };
375     }());
376 
377     $$.module('Sfdc.canvas.oauth', module);
378 
379     $$.oauth.init();
380 
381 }(Sfdc.canvas));