1 /*
  2  * ***** BEGIN LICENSE BLOCK *****
  3  * Zimbra Collaboration Suite Web Client
  4  * Copyright (C) 2015, 2016 Synacor, Inc.
  5  *
  6  * The contents of this file are subject to the Common Public Attribution License Version 1.0 (the "License");
  7  * you may not use this file except in compliance with the License.
  8  * You may obtain a copy of the License at: https://www.zimbra.com/license
  9  * The License is based on the Mozilla Public License Version 1.1 but Sections 14 and 15
 10  * have been added to cover use of software over a computer network and provide for limited attribution
 11  * for the Original Developer. In addition, Exhibit A has been modified to be consistent with Exhibit B.
 12  *
 13  * Software distributed under the License is distributed on an "AS IS" basis,
 14  * WITHOUT WARRANTY OF ANY KIND, either express or implied.
 15  * See the License for the specific language governing rights and limitations under the License.
 16  * The Original Code is Zimbra Open Source Web Client.
 17  * The Initial Developer of the Original Code is Zimbra, Inc.  All rights to the Original Code were
 18  * transferred by Zimbra, Inc. to Synacor, Inc. on September 14, 2015.
 19  *
 20  * All portions of the code are Copyright (C) 2015, 2016 Synacor, Inc. All Rights Reserved.
 21  * ***** END LICENSE BLOCK *****
 22  */
 23 
 24 /**
 25  * Creates a dialog for Two factor initial setup
 26  * @constructor
 27  * @class
 28  * @author  Hem Aravind
 29  *
 30  * @extends	DwtDialog
 31  */
 32 ZmTwoFactorSetupDialog = function(params) {
 33 	this.username = typeof appCtxt !== "undefined" ? appCtxt.getLoggedInUsername() : params.userName;
 34 	this.twoStepAuthSpan = params.twoStepAuthSpan;
 35 	this.twoStepAuthLink = params.twoStepAuthLink;
 36 	this.twoStepAuthCodesContainer = params.twoStepAuthCodesContainer;
 37 	this.twoStepAuthEnabledCallback = params.twoStepAuthEnabledCallback;
 38 	// this.isFromLoginPage will be true if ZmTwoFactorSetupDialog is created from TwoFactorSetup.jsp, which is forwarded from login.jsp file.
 39 	this.isFromLoginPage = params.isFromLoginPage;
 40 	var previousButton = new DwtDialog_ButtonDescriptor(ZmTwoFactorSetupDialog.PREVIOUS_BUTTON, ZmMsg.previous, DwtDialog.ALIGN_RIGHT, this._previousButtonListener.bind(this));
 41 	var beginSetupButton = new DwtDialog_ButtonDescriptor(ZmTwoFactorSetupDialog.BEGIN_SETUP_BUTTON, ZmMsg.twoStepAuthBeginSetup, DwtDialog.ALIGN_RIGHT, this._beginSetupButtonListener.bind(this));
 42 	var nextButton = new DwtDialog_ButtonDescriptor(ZmTwoFactorSetupDialog.NEXT_BUTTON, ZmMsg.next, DwtDialog.ALIGN_RIGHT, this._nextButtonListener.bind(this));
 43 	var finishButton = new DwtDialog_ButtonDescriptor(ZmTwoFactorSetupDialog.FINISH_BUTTON, ZmMsg.twoStepAuthSuccessFinish, DwtDialog.ALIGN_RIGHT, this._finishButtonListener.bind(this));
 44 	var cancelButton = new DwtDialog_ButtonDescriptor(ZmTwoFactorSetupDialog.CANCEL_BUTTON, ZmMsg.cancel, DwtDialog.ALIGN_RIGHT, this._cancelButtonListener.bind(this));
 45 	var shell = typeof appCtxt !== "undefined" ? appCtxt.getShell() : new DwtShell({});
 46 	var newParams = {
 47 		parent : shell,
 48 		title : ZmMsg.twoStepAuthSetup,
 49 		standardButtons : [DwtDialog.NO_BUTTONS],
 50 		extraButtons : [previousButton, beginSetupButton, nextButton, finishButton, cancelButton]
 51 	};
 52 	DwtDialog.call(this, newParams);
 53 	this.setContent(this._contentHtml());
 54 	this._createControls();
 55 	this._setAllowSelection();
 56 };
 57 
 58 ZmTwoFactorSetupDialog.prototype = new DwtDialog;
 59 ZmTwoFactorSetupDialog.prototype.constructor = ZmTwoFactorSetupDialog;
 60 
 61 ZmTwoFactorSetupDialog.PREVIOUS_BUTTON = ++DwtDialog.LAST_BUTTON;
 62 ZmTwoFactorSetupDialog.BEGIN_SETUP_BUTTON = ++DwtDialog.LAST_BUTTON;
 63 ZmTwoFactorSetupDialog.NEXT_BUTTON = ++DwtDialog.LAST_BUTTON;
 64 ZmTwoFactorSetupDialog.FINISH_BUTTON = ++DwtDialog.LAST_BUTTON;
 65 ZmTwoFactorSetupDialog.CANCEL_BUTTON = ++DwtDialog.LAST_BUTTON;
 66 ZmTwoFactorSetupDialog.ONE_TIME_CODES = "ZIMBRA_TWO_FACTOR_ONE_TIME_CODES";
 67 
 68 ZmTwoFactorSetupDialog.prototype.toString =
 69 function() {
 70 	return "ZmTwoFactorSetupDialog";
 71 };
 72 
 73 /**
 74  * Gets the HTML that forms the basic framework of the dialog.
 75  *
 76  * @private
 77  */
 78 ZmTwoFactorSetupDialog.prototype._contentHtml =
 79 function() {
 80 	var id = this._htmlElId;
 81 	this._descriptionDivId = id + "_description";
 82 	this._passwordDivId = id + "_password";
 83 	this._passwordErrorDivId = id + "_password_error";
 84 	this._authenticationDivId = id + "_authentication";
 85 	this._emailDivId = id + "_email";
 86 	this._codeDivId = id + "_code";
 87 	this._codeDescriptionDivId = id + "_code_description";
 88 	this._codeErrorDivId = id + "_code_error";
 89 	this._successDivId = id + "_success";
 90 	this._divIdArray = [this._descriptionDivId, this._passwordDivId, this._authenticationDivId, this._emailDivId, this._codeDivId, this._successDivId];
 91 	return AjxTemplate.expand("share.Dialogs#ZmTwoFactorSetup", {id : id, username : this.username});
 92 };
 93 
 94 ZmTwoFactorSetupDialog.prototype._createControls =
 95 function() {
 96 	var id = this._htmlElId;
 97 	this._passwordInput = Dwt.getElement(id + "_password_input");
 98 	this._codeInput = Dwt.getElement(id + "_code_input");
 99 	this._keySpan = Dwt.getElement(id + "_email_key");
100 	var keyupHandler = this._handleKeyUp.bind(this);
101 	Dwt.setHandler(this._passwordInput, DwtEvent.ONKEYUP, keyupHandler);
102 	Dwt.setHandler(this._passwordInput, DwtEvent.ONINPUT, keyupHandler);
103 	Dwt.setHandler(this._codeInput, DwtEvent.ONKEYUP, keyupHandler);
104 	Dwt.setHandler(this._codeInput, DwtEvent.ONINPUT, keyupHandler);
105 };
106 
107 /**
108 ** an array of input fields that will be cleaned up between instances of the dialog being popped up and down
109 *
110 * @return An array of the input fields to be reset
111 */
112 ZmTwoFactorSetupDialog.prototype._getInputFields =
113 function() {
114 	return [this._passwordInput, this._codeInput];
115 };
116 
117 /**
118  * Pops-up the dialog.
119  */
120 ZmTwoFactorSetupDialog.prototype.popup =
121 function() {
122 	this.reset();
123 	DwtDialog.prototype.popup.call(this);
124 };
125 
126 /**
127  * Resets the dialog back to its original state.
128  */
129 ZmTwoFactorSetupDialog.prototype.reset =
130 function() {
131 	Dwt.show(this._descriptionDivId);
132 	Dwt.hide(this._passwordDivId);
133 	Dwt.hide(this._passwordErrorDivId);
134 	Dwt.hide(this._authenticationDivId);
135 	Dwt.hide(this._emailDivId);
136 	Dwt.hide(this._codeDivId);
137 	Dwt.hide(this._codeErrorDivId);
138 	Dwt.hide(this._successDivId);
139 	this.setButtonVisible(ZmTwoFactorSetupDialog.PREVIOUS_BUTTON, false);
140 	this.setButtonVisible(ZmTwoFactorSetupDialog.BEGIN_SETUP_BUTTON, true);
141 	this.setButtonVisible(ZmTwoFactorSetupDialog.NEXT_BUTTON, false);
142 	this.setButtonVisible(ZmTwoFactorSetupDialog.FINISH_BUTTON, false);
143 	this.setButtonVisible(ZmTwoFactorSetupDialog.CANCEL_BUTTON, true);
144 	this._divIdArrayIndex = 0;
145 	DwtDialog.prototype.reset.call(this);
146 };
147 
148 ZmTwoFactorSetupDialog.prototype._beginSetupButtonListener =
149 function() {
150 	Dwt.hide(this._descriptionDivId);
151 	Dwt.show(this._passwordDivId);
152 	this.setButtonVisible(ZmTwoFactorSetupDialog.BEGIN_SETUP_BUTTON, false);
153 	this.setButtonVisible(ZmTwoFactorSetupDialog.PREVIOUS_BUTTON, true);
154 	this.setButtonEnabled(ZmTwoFactorSetupDialog.NEXT_BUTTON, this._passwordInput.value !== "");
155 	this.setButtonVisible(ZmTwoFactorSetupDialog.NEXT_BUTTON, true);
156 	this._passwordInput.focus();
157 	this._divIdArrayIndex = 1;
158 };
159 
160 ZmTwoFactorSetupDialog.prototype._previousButtonListener =
161 function() {
162 	var currentDivId = this._divIdArray[this._divIdArrayIndex];
163 	if (currentDivId === this._passwordDivId) {
164 		Dwt.hide(this._passwordErrorDivId);
165 		this.setButtonVisible(ZmTwoFactorSetupDialog.PREVIOUS_BUTTON, false);
166 		this.setButtonVisible(ZmTwoFactorSetupDialog.NEXT_BUTTON, false);
167 		this.setButtonVisible(ZmTwoFactorSetupDialog.BEGIN_SETUP_BUTTON, true);
168 	}
169 	else if (currentDivId === this._codeDivId) {
170 		this.setButtonEnabled(ZmTwoFactorSetupDialog.NEXT_BUTTON, true);
171 	}
172 	else if (currentDivId === this._successDivId) {
173 		this.setButtonVisible(ZmTwoFactorSetupDialog.FINISH_BUTTON, false);
174 		this.setButtonVisible(ZmTwoFactorSetupDialog.NEXT_BUTTON, true);
175 	}
176 	Dwt.show(this._divIdArray[this._divIdArrayIndex - 1]);
177 	Dwt.hide(this._divIdArray[this._divIdArrayIndex]);
178 	if (this._divIdArrayIndex > -1) {
179 		this._divIdArrayIndex--;
180 	}
181 };
182 
183 ZmTwoFactorSetupDialog.prototype._nextButtonListener =
184 function() {
185 	var currentDivId = this._divIdArray[this._divIdArrayIndex];
186 	if (currentDivId === this._passwordDivId || currentDivId === this._codeDivId) {
187 		this._enableTwoFactorAuth(currentDivId);
188 		return;
189 	}
190 	Dwt.show(this._divIdArray[this._divIdArrayIndex + 1]);
191 	Dwt.hide(this._divIdArray[this._divIdArrayIndex]);
192 	if (this._divIdArrayIndex < this._divIdArray.length) {
193 		this._divIdArrayIndex++;
194 	}
195 	if (currentDivId === this._emailDivId) {
196 		Dwt.hide(this._codeErrorDivId);
197 		Dwt.show(this._codeDescriptionDivId);
198 		this._codeInput.focus();
199 		this.setButtonEnabled(ZmTwoFactorSetupDialog.NEXT_BUTTON, this._codeInput.value !== "");
200 	}
201 };
202 
203 ZmTwoFactorSetupDialog.prototype._finishButtonListener =
204 function() {
205 	//If the user clicks finish button, redirect to the login page
206 	if (this.isFromLoginPage) {
207 		location.replace(location.href);
208 	}
209 	else {
210 		this.popdown();
211 		if (this.twoStepAuthSpan) {
212 			Dwt.setInnerHtml(this.twoStepAuthSpan, ZmMsg.twoStepAuth);
213 		}
214 		if (this.twoStepAuthLink) {
215 			Dwt.setInnerHtml(this.twoStepAuthLink, ZmMsg.twoStepAuthDisableLink);
216 		}
217 		if (this.twoStepAuthCodesContainer) {
218 			Dwt.setDisplay(this.twoStepAuthCodesContainer, "");
219 		}
220 		if (this.twoStepAuthEnabledCallback) {
221 			this.twoStepAuthEnabledCallback();
222 		}
223 	}
224 };
225 
226 ZmTwoFactorSetupDialog.prototype._cancelButtonListener =
227 function() {
228 	//If the user clicks cancel button, redirect to the login page
229 	if (this.isFromLoginPage) {
230 		location.replace(location.href);
231 	}
232 	else {
233 		this.popdown();
234 	}
235 };
236 
237 ZmTwoFactorSetupDialog.prototype._handleKeyUp =
238 function(ev) {
239 	var value = ev && ev.target && ev.target.value && ev.target.value.length;
240 	this.setButtonEnabled(ZmTwoFactorSetupDialog.NEXT_BUTTON, !!value);
241 };
242 
243 /**
244  * Sends first EnableTwoFactorAuthRequest with username and password
245  * Sends second EnableTwoFactorAuthRequest with username, temporary authToken and twoFactorCode
246 */
247 ZmTwoFactorSetupDialog.prototype._enableTwoFactorAuth =
248 function(currentDivId) {
249 	var passwordInput = this._passwordInput;
250 	passwordInput.setAttribute("disabled", true);
251 	this.setButtonEnabled(ZmTwoFactorSetupDialog.PREVIOUS_BUTTON, false);
252 	this.setButtonEnabled(ZmTwoFactorSetupDialog.NEXT_BUTTON, false);
253 	var command = new ZmCsfeCommand();
254 	if (currentDivId === this._codeDivId) {
255 		var codeInput = this._codeInput;
256 		codeInput.setAttribute("disabled", true);
257 		var jsonObj = {EnableTwoFactorAuthRequest : {_jsns:"urn:zimbraAccount", csrfTokenSecured:1, name:{_content : this.username}, authToken:{_content : this._authToken}, twoFactorCode:{_content : codeInput.value}}};
258 	}
259 	else {
260 		var jsonObj = {EnableTwoFactorAuthRequest : {_jsns:"urn:zimbraAccount", csrfTokenSecured:1, name:{_content : this.username}, password:{_content : passwordInput.value}}};
261 	}
262 	var callback = this._enableTwoFactorAuthCallback.bind(this, currentDivId);
263 	command.invoke({jsonObj:jsonObj, noAuthToken: true, asyncMode: true, callback: callback, serverUri:"/service/soap/"});
264 };
265 
266 ZmTwoFactorSetupDialog.prototype._enableTwoFactorAuthCallback =
267 function(currentDivId, result) {
268 	if (!result || result.isException()) {
269 		this._handleTwoFactorAuthError(currentDivId, result.getException());
270 	}
271 	else {
272 		var response = result.getResponse();
273 		if (!response || !response.Body || !response.Body.EnableTwoFactorAuthResponse) {
274 			this._handleTwoFactorAuthError(currentDivId);
275 			return;
276 		}
277 		var enableTwoFactorAuthResponse = response.Body.EnableTwoFactorAuthResponse;
278 		var authToken = enableTwoFactorAuthResponse.authToken;
279 		this._authToken = authToken && authToken[0] && authToken[0]._content;
280 		if (enableTwoFactorAuthResponse.csrfToken && enableTwoFactorAuthResponse.csrfToken[0] && enableTwoFactorAuthResponse.csrfToken[0]._content) {
281 			window.csrfToken = enableTwoFactorAuthResponse.csrfToken[0]._content;
282 		}
283 		var secret = enableTwoFactorAuthResponse.secret;
284 		var scratchCodes = enableTwoFactorAuthResponse.scratchCodes;
285 		if (secret && secret[0] && secret[0]._content) {
286 			Dwt.setInnerHtml(this._keySpan, secret[0]._content);
287 			this._handleTwoFactorAuthSuccess(currentDivId);
288 			return;
289 		}
290 		else if (scratchCodes && scratchCodes[0] && scratchCodes[0].scratchCode) {
291 			if (typeof appCtxt !== "undefined") {
292 				//Only the server will set ZmSetting.TWO_FACTOR_AUTH_ENABLED. Don’t try to save the setting from the UI.
293 				appCtxt.set(ZmSetting.TWO_FACTOR_AUTH_ENABLED, true, false, false, true);
294 				var scratchCode = AjxUtil.map(scratchCodes[0].scratchCode, function(obj) {return obj._content});
295 				appCtxt.cacheSet(ZmTwoFactorSetupDialog.ONE_TIME_CODES, scratchCode);
296 			}
297 			this._handleTwoFactorAuthSuccess(currentDivId);
298 			return;
299 		}
300 		this._handleTwoFactorAuthError(currentDivId);
301 	}
302 };
303 
304 ZmTwoFactorSetupDialog.prototype._handleTwoFactorAuthSuccess =
305 function(currentDivId) {
306 	if (currentDivId === this._passwordDivId) {
307 		Dwt.hide(this._passwordDivId);
308 		Dwt.show(this._authenticationDivId);
309 		Dwt.hide(this._passwordErrorDivId);
310 		this.setButtonEnabled(ZmTwoFactorSetupDialog.NEXT_BUTTON, true);
311 		this.setButtonEnabled(ZmTwoFactorSetupDialog.PREVIOUS_BUTTON, true);
312 		if (this._divIdArrayIndex < this._divIdArray.length) {
313 			this._divIdArrayIndex++;
314 		}
315 	}
316 	else if (currentDivId === this._codeDivId) {
317 		Dwt.show(this._successDivId);
318 		Dwt.hide(this._codeDivId);
319 		this.setButtonVisible(ZmTwoFactorSetupDialog.PREVIOUS_BUTTON, false);
320 		this.setButtonVisible(ZmTwoFactorSetupDialog.NEXT_BUTTON, false);
321 		this.setButtonVisible(ZmTwoFactorSetupDialog.FINISH_BUTTON, true);
322 		this.setButtonVisible(ZmTwoFactorSetupDialog.CANCEL_BUTTON, false);
323 	}
324 };
325 
326 ZmTwoFactorSetupDialog.prototype._handleTwoFactorAuthError =
327 function(currentDivId, exception) {
328 	if (currentDivId === this._passwordDivId) {
329 		if (exception && exception.code === ZmCsfeException.ACCT_AUTH_FAILED) {
330 			Dwt.show(this._passwordErrorDivId);
331 		}
332 		var passwordInput = this._passwordInput;
333 		passwordInput.removeAttribute("disabled");
334 		passwordInput.value = "";
335 		passwordInput.focus();
336 	}
337 	else if (currentDivId === this._codeDivId) {
338 		Dwt.show(this._codeErrorDivId);
339 		Dwt.hide(this._codeDescriptionDivId);
340 		var codeInput = this._codeInput;
341 		codeInput.removeAttribute("disabled");
342 		codeInput.value = "";
343 		codeInput.focus();
344 	}
345 	this.setButtonEnabled(ZmTwoFactorSetupDialog.NEXT_BUTTON, false);
346 	this.setButtonEnabled(ZmTwoFactorSetupDialog.PREVIOUS_BUTTON, true);
347 };
348 
349 /**
350  * Determines whether to prevent the browser from displaying its context menu.
351  */
352 ZmTwoFactorSetupDialog.prototype.preventContextMenu =
353 function() {
354 	return false;
355 };
356 
357 ZmTwoFactorSetupDialog.disableTwoFactorAuth =
358 function(params, dialog) {
359 	var command = new ZmCsfeCommand();
360 	var jsonObj = {DisableTwoFactorAuthRequest : {_jsns:"urn:zimbraAccount"}};
361 	var callback = ZmTwoFactorSetupDialog.disableTwoFactorAuthCallback.bind(window, params, dialog);
362 	command.invoke({jsonObj: jsonObj, noAuthToken: true, asyncMode: true, callback: callback, serverUri:"/service/soap/"});
363 };
364 
365 ZmTwoFactorSetupDialog.disableTwoFactorAuthCallback =
366 function(params, dialog) {
367 	dialog.popdown();
368 	Dwt.setInnerHtml(params.twoStepAuthSpan, ZmMsg.twoStepStandardAuth);
369 	Dwt.setInnerHtml(params.twoStepAuthLink, ZmMsg.twoStepAuthSetupLink);
370 	Dwt.setDisplay(params.twoStepAuthCodesContainer, Dwt.DISPLAY_NONE);
371 	appCtxt.set(ZmSetting.TWO_FACTOR_AUTH_ENABLED, false, false, false, true);
372 };