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