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/login?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