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