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