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