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