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