1 (function (GCN) { 2 3 'use strict'; 4 5 /** 6 * @type {Array.<function>} A set of functions to be invoked after the next 7 * `authenticated' message is broadcasted. Once 8 * this event is proceeded, all functions in this 9 * array will be invoked in order, and the array 10 * will be flushed in preparation for the next 11 * `authenticated' event. 12 */ 13 var afterAuthenticationQueue = []; 14 15 /** 16 * Fetches the details of the user who is logged in to this session. 17 * 18 * @param {function} success Callback to receive the requested user data. 19 * @param {function} error Handler for failures 20 * @throws HTTP_ERROR 21 */ 22 function fetchUserDetails(success, error) { 23 GCN.ajax({ 24 url: GCN.settings.BACKEND_PATH + '/rest/user/me?sid=' + GCN.sid, 25 dataType: 'json', 26 contentType: 'application/json; charset=utf-8', 27 28 success: function (response) { 29 if (GCN.getResponseCode(response) === 'OK') { 30 success(response.user); 31 } else { 32 GCN.handleResponseError(response, error); 33 } 34 }, 35 36 error: function (xhr, status, msg) { 37 GCN.handleHttpError(xhr, msg, error); 38 } 39 }); 40 } 41 42 jQuery.extend(GCN, { 43 /** @lends GCN */ 44 45 /** 46 * @const 47 * @type {boolean} Whether or not Single-SignOn is used for automatic 48 * authentication. 49 */ 50 usingSSO: false, 51 52 isAuthenticating: false, 53 54 /** 55 * @type {number} Stores the user's session id. It is required for 56 * making REST-API requests. 57 */ 58 sid: null, 59 60 /** 61 * Sets the `sid'. If one has already been set, the it will be 62 * overwritten. 63 * 64 * @param {id} sid The value to set the `sid' to. 65 */ 66 setSid: function (sid) { 67 GCN.sid = sid; 68 GCN.pub('session-set', [sid]); 69 GCN.pub('session.sid-set', [sid]); 70 }, 71 72 /** 73 * Log into Content.Node, with the given credentials. 74 * 75 * @param {string} username 76 * @param {string} password 77 * @param {function} success Invoked when login attempt completes 78 * regardless of whether or not 79 * authentication succeeded. 80 * @param {function} error Called if there an HTTP error occured when 81 * performing the ajax request. 82 */ 83 login: function (username, password, success, error) { 84 GCN.isAuthenticating = true; 85 GCN.ajax({ 86 87 /** 88 * Why we add a ".json" suffix to the login url: In the context 89 * of the GCN environment, the ".json" suffix is appended to 90 * REST-API requests to ensure that the server returns JSON 91 * data rather than XML data, which is what browsers seem to 92 * automatically request. The usage of the ".json" suffix here 93 * is for an entirely different reason. We use it as a (fairly 94 * innocent) hack to prevent this request from being processed 95 * by CAS filters that are targeting "rest/auth/login" . In 96 * most production cases, this would not be necessary, since it 97 * is not common that this login url would be used for both 98 * credential based logins and SSO logins, but having it does 99 * not do anything bad. 100 */ 101 url: GCN.settings.BACKEND_PATH + '/rest/auth/login.json', 102 type: 'POST', 103 dataType: 'json', 104 contentType: 'application/json; charset=utf-8', 105 data: JSON.stringify({ 106 login: username || '', 107 password: password || '' 108 }), 109 success: function (response, textStatus, jqXHR) { 110 GCN.isAuthenticating = false; 111 if (GCN.getResponseCode(response) === 'OK') { 112 if (GCN.global.isNode) { 113 var header = jqXHR.getResponseHeader('Set-Cookie'); 114 if (!header) { 115 GCN.handleError(GCN.createError( 116 'AUTHENTICATION_FAILED', 117 'Could not find authentication cookie', 118 jqXHR 119 ), error); 120 return; 121 } 122 var secret = header.substr(19, 15); 123 GCN.setSid(response.sid + secret); 124 } else { 125 GCN.setSid(response.sid); 126 } 127 128 if (success) { 129 success(true, { user: response.user }); 130 } 131 132 // Because 'authenticated' has been deprecated in favor 133 // of 'session.authenticated' 134 GCN.pub('authenticated', {user: response.user}); 135 GCN.pub('session.authenticated', {user: response.user}); 136 } else { 137 var info = response.responseInfo; 138 if (success) { 139 success(false, { 140 error: GCN.createError(info.responseCode, 141 info.responseMessage, response) 142 }); 143 } 144 } 145 }, 146 147 error: function (xhr, status, msg) { 148 GCN.handleHttpError(xhr, msg, error); 149 } 150 151 }); 152 }, 153 154 /** 155 * Triggers the `authentication-required' event. Provides the handler 156 * a `proceed' and a `cancel' function to branch the continuation of 157 * the program's control flow depending on the success or failure of 158 * the authentication attempt. 159 * 160 * @param {function(GCNError=)} cancelCallback A function to be invoked 161 * if authentication fails. 162 * @throws NO_AUTH_HANDLER Thrown if now handler has been registered 163 * `onAuthenticatedRequired' method. 164 */ 165 authenticate: function (cancelCallback) { 166 // Check whether an authentication handler has been set. 167 if (!this._hasAuthenticationHandler()) { 168 afterAuthenticationQueue = []; 169 170 var error = GCN.createError('NO_AUTH_HANDLER', 'Could not ' + 171 'authenticate because no authentication handler has been' + 172 ' registered.'); 173 174 if (cancelCallback) { 175 cancelCallback(error); 176 } else { 177 GCN.error(error.code, error.message, error.data); 178 } 179 180 return; 181 } 182 183 GCN.isAuthenticating = true; 184 185 var proceed = GCN.onAuthenticated; 186 var cancel = function (error) { 187 afterAuthenticationQueue = []; 188 cancelCallback(error); 189 }; 190 191 // Because 'authentication-required' has been deprecated in favor of 192 // 'session.authentication-required' 193 GCN.pub('authentication-required', [proceed, cancel]); 194 GCN.pub('session.authentication-required', [proceed, cancel]); 195 }, 196 197 afterNextAuthentication: function (callback) { 198 if (callback) { 199 afterAuthenticationQueue.push(callback); 200 } 201 }, 202 203 /** 204 * This is the method that is passed as `proceed()' to the handler 205 * registered through `onAuthenticationRequired()'. It ensures that 206 * all functions that are pending authentication will be executed in 207 * FIFO order. 208 */ 209 onAuthenticated: function () { 210 GCN.isAuthenticating = false; 211 212 var i; 213 var j = afterAuthenticationQueue.length; 214 215 for (i = 0; i < j; ++i) { 216 afterAuthenticationQueue[i](); 217 } 218 219 afterAuthenticationQueue = []; 220 }, 221 222 /** 223 * Destroys the saved session data. 224 * At the moment this only involves clearing the stored SID. 225 */ 226 clearSession: function () { 227 GCN.setSid(null); 228 }, 229 230 /** 231 * Attemps to authenticate using Single-Sign-On. 232 * 233 * @param {function} success 234 * @param {function} error 235 * @throws HTTP_ERROR 236 */ 237 loginWithSSO: function (success, error) { 238 GCN.isAuthenticating = true; 239 240 // The following must happen after the dom is ready, and not before. 241 jQuery(function () { 242 var iframe = jQuery('<iframe id="gcn-sso-frame">').hide(); 243 244 jQuery('body').append(iframe); 245 246 iframe.load(function () { 247 GCN.isAuthenticating = false; 248 249 var response = iframe.contents().text(); 250 251 switch (response) { 252 case '': 253 case 'FAILURE': 254 var err = GCN.createError('HTTP_ERROR', 255 'Error encountered while making HTTP request'); 256 257 GCN.handleError(err, error); 258 break; 259 case 'NOTFOUND': 260 success(false); 261 break; 262 default: 263 GCN.setSid(response); 264 265 fetchUserDetails(function (user) { 266 if (success) { 267 success(true, {user: user}); 268 } 269 270 // Because 'authenticated' has been deprecated in 271 // favor of 'session.authenticated' 272 GCN.pub('authenticated', {user: user}); 273 GCN.pub('session.authenticated', {user: user}); 274 }); 275 } 276 277 iframe.remove(); 278 }); 279 280 iframe.attr('src', GCN.settings.BACKEND_PATH + 281 '/rest/auth/ssologin?ts=' + (new Date()).getTime()); 282 }); 283 }, 284 285 /** 286 * Do a logout and clear the session id. 287 * 288 * @param {function} success 289 * @param {function} error A callback that will be invoked if an ajax 290 * error occurs while trying to accomplish the 291 * logout request. 292 */ 293 logout: function (success, error) { 294 // If no `sid' exists, the logout fails. 295 if (!GCN.sid) { 296 success(false, GCN.createError('NO_SESSION', 297 'There is no session to log out of.')); 298 299 return; 300 } 301 302 GCN.ajax({ 303 url: GCN.settings.BACKEND_PATH + '/rest/auth/logout/' + 304 GCN.sid, 305 type: 'POST', 306 dataType: 'json', 307 contentType: 'application/json; charset=utf-8', 308 309 success: function (response) { 310 if (GCN.getResponseCode(response) === 'OK') { 311 GCN.clearSession(); 312 success(true); 313 } else { 314 var info = response.responseInfo; 315 success(false, GCN.createError(info.responseCode, 316 info.responseMessage, response)); 317 } 318 }, 319 320 error: function (xhr, status, msg) { 321 GCN.handleHttpError(xhr, msg, error); 322 } 323 }); 324 }, 325 326 /** 327 * Given a GCN ajax response object, return the response code. 328 * 329 * @param {object} response GCN response object return in the ajax 330 * request callback. 331 */ 332 getResponseCode: function (response) { 333 return (response && response.responseInfo && 334 response.responseInfo.responseCode); 335 } 336 337 }); 338 339 }(GCN)); 340