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