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 };