/**
* Copyright (c) Microsoft Corporation
* <p/>
* All rights reserved.
* <p/>
* MIT License
* <p/>
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
* documentation files (the "Software"), to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and
* to permit persons to whom the Software is furnished to do so, subject to the following conditions:
* <p/>
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of
* the Software.
* <p/>
* THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO
* THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package com.microsoft.intellij.ui;
import com.intellij.openapi.fileChooser.FileChooserDescriptorFactory;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.ui.Messages;
import com.intellij.openapi.ui.ValidationInfo;
import com.intellij.openapi.vfs.LocalFileSystem;
import com.interopbridges.tools.windowsazure.WindowsAzureInvalidProjectOperationException;
import com.interopbridges.tools.windowsazure.WindowsAzureProjectManager;
import com.michaelbaranov.microba.calendar.DatePicker;
import com.microsoft.intellij.AzurePlugin;
import com.microsoft.intellij.ui.util.UIUtils;
import com.microsoft.intellij.util.PluginUtil;
import com.microsoft.wacommon.commoncontrols.NewCertificateDialogData;
import com.microsoftopentechnologies.azurecommons.util.WAEclipseHelperMethods;
import com.microsoftopentechnologies.azurecommons.wacommonutil.CerPfxUtil;
import com.microsoftopentechnologies.azurecommons.wacommonutil.EncUtilHelper;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import java.awt.event.*;
import java.beans.PropertyVetoException;
import java.io.File;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.Locale;
import static com.microsoft.intellij.AzurePlugin.log;
import static com.microsoft.intellij.ui.messages.AzureBundle.message;
public class WARemoteAccessPanel implements AzureAbstractPanel {
private static final String BASE_PATH = "${basedir}";
private JPanel contentPane;
private JCheckBox remoteChkBtn;
private JTextField txtUserName;
private JLabel confirmPwdLbl;
private JPasswordField txtConfirmPwd;
private JPasswordField txtPassword;
private JLabel expiryDateLabel;
private DatePicker txtExpiryDate;
private JLabel pathLabel;
private JTextField txtPath;
private JLabel noteLabel;
private JButton fileSystemButton;
private JButton newButton;
private JLabel userNameLabel;
private JLabel passwordLabel;
private boolean isPwdChanged;
private boolean isInconsistent;
private boolean isFrmEncLink;
private Module myModule;
private WindowsAzureProjectManager waProjManager;
public WARemoteAccessPanel(Module module, boolean isFrmEncLink, String uname, String pwd, String cnfPwd) {
this.myModule = module;
this.isFrmEncLink = isFrmEncLink;
loadProject();
init(uname, pwd, cnfPwd);
}
protected void init(String uname, String pwd, String cnfPwd) {
remoteChkBtn.addItemListener(createRemoteChkBtnListener());
newButton.addActionListener(createNewButtonListener());
fileSystemButton.addActionListener(UIUtils.createFileChooserListener(txtPath, myModule.getProject(),
FileChooserDescriptorFactory.createSingleLocalFileDescriptor()));
try {
remoteChkBtn.setSelected(waProjManager.getRemoteAccessAllRoles());
} catch (WindowsAzureInvalidProjectOperationException e2) {
PluginUtil.displayErrorDialogAndLog(message("remAccErrTitle"), message("remAccErAllRoles"), e2);
}
try {
txtUserName.setText(waProjManager.getRemoteAccessUsername());
} catch (WindowsAzureInvalidProjectOperationException e1) {
txtUserName.setText("");
remoteChkBtn.setSelected(false);
/*
* When user data is not consistent we are making
* isInconsistent as true and later on we are checking the status
* of this variable and throwing the error message to user.
*/
isInconsistent = true;
log(message("remAccErUserName"), e1);
}
createPasswordComponent();
createConfPwdComponent();
createExpiryDateComponent();
try {
txtPath.setText(waProjManager.getRemoteAccessCertificatePath());
} catch (WindowsAzureInvalidProjectOperationException e1) {
txtPath.setText("");
remoteChkBtn.setSelected(false);
/*
* When user data is not consistent we are making
* isInconsistent as true and later on we are checking the status
* of this variable and throwing the error message to user.
*/
isInconsistent = true;
log(message("remAccErCertPath"), e1);
}
/*
* Check if we are coming from Publish wizard link,
*/
if (isFrmEncLink) {
if (uname.isEmpty()) {
// disable remote access
remoteChkBtn.setSelected(false);
makeAllTextBlank();
} else {
/*
* enable remote access and
* show values given on publish wizard
*/
remoteChkBtn.setSelected(true);
txtUserName.setText(uname);
txtPassword.setText(pwd);
txtConfirmPwd.setText(cnfPwd);
try {
if (!waProjManager.getRemoteAccessEncryptedPassword().equals(pwd)) {
isPwdChanged = true;
}
} catch (WindowsAzureInvalidProjectOperationException e) {
log(message("remAccErPwd"), e);
}
isFrmEncLink = true;
}
} else {
if (remoteChkBtn.isSelected()) {
getDefaultValues();
} else {
makeAllTextBlank();
}
}
setComponentStatus(remoteChkBtn.isSelected());
/*
* Here we are checking the isInconsistent value
* and showing the error message to user on UI.
*/
if (isInconsistent) {
PluginUtil.displayErrorDialog(message("remAccErTxtTitle"), message("remAccDataInc"));
}
/*
* Non windows OS then disable components,
* but keep values as it is
*/
if (!AzurePlugin.IS_WINDOWS) {
setComponentStatus(false);
if (!remoteChkBtn.isSelected()) {
remoteChkBtn.setEnabled(false);
}
}
}
public String getDisplayName() {
return message("cmhLblRmtAces");
}
public String getSelectedValue() {
return null;
}
private void createPasswordComponent() {
txtPassword.addFocusListener(new FocusListener() {
/**
* making text box blank on focus gained.
*/
@Override
public void focusGained(FocusEvent e) {
txtPassword.setText("");
}
@Override
public void focusLost(FocusEvent e) {
UIUtils.checkRdpPwd(isPwdChanged, txtPassword, waProjManager, !isFrmEncLink, txtConfirmPwd);
}
});
/*
* Listener for key event when user click on password text box
* it will set flag for entering the new values.
*/
txtPassword.addKeyListener(createTxtPasswordKeyListener());
try {
txtPassword.setText(waProjManager.getRemoteAccessEncryptedPassword());
} catch (WindowsAzureInvalidProjectOperationException e1) {
txtPassword.setText("");
remoteChkBtn.setSelected(false);
/*
* When user data is not consistent we are making
* isInconsistent as true and later on we are checking the status
* of this variable and throwing the error message to user.
*/
isInconsistent = true;
log(message("remAccErPwd"), e1);
}
}
/**
* Creates label and text box for confirm password.
*/
private void createConfPwdComponent() {
try {
txtConfirmPwd.setText(waProjManager.getRemoteAccessEncryptedPassword());
} catch (WindowsAzureInvalidProjectOperationException e1) {
txtConfirmPwd.setText("");
remoteChkBtn.setSelected(false);
/*
* When user data is not consistent we are making
* isInconsistent as true and later on we are checking the status
* of this variable and throwing the error message to user.
*/
isInconsistent = true;
log(message("remAccErPwd"), e1);
}
txtConfirmPwd.addFocusListener(new FocusListener() {
@Override
public void focusLost(FocusEvent event) {
try {
if (!isPwdChanged) {
if (txtPassword.getPassword().length == 0) {
txtConfirmPwd.setText("");
} else {
txtConfirmPwd.setText(waProjManager.getRemoteAccessEncryptedPassword());
}
}
} catch (WindowsAzureInvalidProjectOperationException e1) {
PluginUtil.displayErrorDialogAndLog(message("remAccErrTitle"), message("remAccErPwd"), e1);
}
}
@Override
public void focusGained(FocusEvent event) {
txtConfirmPwd.setText("");
}
});
}
private void createExpiryDateComponent() {
txtExpiryDate.setDateFormat(new SimpleDateFormat(message("remAccDateFormat"), Locale.getDefault()));
try {
Date date = waProjManager.getRemoteAccessAccountExpiration();
if (date == null) {
GregorianCalendar currentCal = new GregorianCalendar();
currentCal.add(Calendar.YEAR, 1);
Date today = currentCal.getTime();
if (txtExpiryDate.isEnabled()) {
txtExpiryDate.setDate(today);
}
} else {
txtExpiryDate.setDate(date);
}
} catch (WindowsAzureInvalidProjectOperationException e1) {
// txtExpiryDate.setText("");
remoteChkBtn.setSelected(false);
/*
* When user data is not consistent we are making
* isInconsistent as true and later on we are checking the status
* of this variable and throwing the error message to user.
*/
isInconsistent = true;
log(message("remAccErExpDate"), e1);
} catch (PropertyVetoException e) {
log(message("remAccErExpDate"), e);
}
}
private ItemListener createRemoteChkBtnListener() {
return new ItemListener() {
@Override
public void itemStateChanged(ItemEvent e) {
setComponentStatus(remoteChkBtn.isSelected());
if (remoteChkBtn.isSelected()) {
getDefaultValues();
} else {
makeAllTextBlank();
}
}
};
}
private KeyListener createTxtPasswordKeyListener() {
return new KeyListener() {
@Override
public void keyReleased(KeyEvent event) {
}
@Override
public void keyTyped(KeyEvent e) {
isPwdChanged = true;
}
@Override
public void keyPressed(KeyEvent event) {
}
};
}
private ActionListener createNewButtonListener() {
return new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
NewCertificateDialogData data = new NewCertificateDialogData();
NewCertificateDialog dialog = new NewCertificateDialog(data, WAEclipseHelperMethods.findJdkPathFromRole(waProjManager), myModule.getProject());
dialog.show();
if (dialog.isOK()) {
String certPath = data.getCerFilePath();
if (certPath != null && certPath.contains(PluginUtil.getModulePath(myModule) + File.separator)) {
String workspacePath = PluginUtil.getModulePath(myModule); // todo: ??
String replaceString = certPath;
String subString = certPath.substring(certPath.indexOf(workspacePath), workspacePath.length());
replaceString = replaceString.replace(subString, BASE_PATH);
txtPath.setText(replaceString);
} else {
txtPath.setText(certPath);
}
}
}
};
}
/**
* This method will set all fields to blank,
* if remote check button is disabled.
*/
private void makeAllTextBlank() {
txtUserName.setText("");
txtPassword.setText("");
txtConfirmPwd.setText("");
// txtExpiryDate.setDate(null);
txtPath.setText("");
}
/**
* This method will set default values to all fields.
*/
private void getDefaultValues() {
try {
txtUserName.setText(waProjManager.getRemoteAccessUsername());
} catch (WindowsAzureInvalidProjectOperationException e) {
PluginUtil.displayErrorDialogAndLog(message("remAccErrTitle"), message("remAccErUserName"), e);
}
try {
txtPassword.setText(waProjManager.getRemoteAccessEncryptedPassword());
txtConfirmPwd.setText(waProjManager.getRemoteAccessEncryptedPassword());
} catch (WindowsAzureInvalidProjectOperationException e) {
PluginUtil.displayErrorDialogAndLog(message("remAccErrTitle"), message("remAccErPwd"), e);
}
try {
txtPath.setText(waProjManager.getRemoteAccessCertificatePath());
} catch (WindowsAzureInvalidProjectOperationException e) {
PluginUtil.displayErrorDialogAndLog(message("remAccErrTitle"), message("remAccErCertPath"), e);
}
try {
Date date = waProjManager.getRemoteAccessAccountExpiration();
if (date == null) {
GregorianCalendar currentCal = new GregorianCalendar();
currentCal.add(Calendar.YEAR, 1);
Date today = currentCal.getTime();
if (txtExpiryDate.isEnabled()) {
txtExpiryDate.setDate(today);
}
} else {
txtExpiryDate.setDate(date);
}
} catch (WindowsAzureInvalidProjectOperationException e) {
PluginUtil.displayErrorDialogAndLog(message("remAccErrTitle"), message("remAccErExpDate"), e);
} catch (PropertyVetoException e) {
PluginUtil.displayErrorDialogAndLog(message("remAccErrTitle"), message("remAccErExpDate"), e);
}
}
/**
* This method will disable/enable all fields based on
* the remote check button status.
*
* @param status : status of the remote check check box.
*/
private void setComponentStatus(boolean status) {
userNameLabel.setEnabled(status);
passwordLabel.setEnabled(status);
confirmPwdLbl.setEnabled(status);
expiryDateLabel.setEnabled(status);
txtUserName.setEnabled(status);
txtPassword.setEnabled(status);
txtConfirmPwd.setEnabled(status);
txtExpiryDate.setEnabled(status);
txtPath.setEnabled(status);
pathLabel.setEnabled(status);
noteLabel.setEnabled(status);
newButton.setEnabled(status);
// workspaceButton.setEnabled(status);
fileSystemButton.setEnabled(status);
// cal.setEnabled(status);
}
@Nullable
@Override
public ValidationInfo doValidate() {
String userName = txtUserName.getText();
String newPath = txtPath.getText();
// String expDate = txtExpiryDate.getText();
if (newPath.startsWith(BASE_PATH)) {
newPath = newPath.substring(newPath.indexOf("}") + 1, newPath.length());
newPath = String.format("%s%s", PluginUtil.getModulePath(myModule), newPath);
}
File cerFile = new File(newPath);
boolean isRemoteEnabled = remoteChkBtn.isSelected();
if (isRemoteEnabled && userName.isEmpty()) {
return new ValidationInfo(message("remAccNameNull"), txtUserName);
} /*else if (isRemoteEnabled && txtExpiryDate.isEmpty()) {
return new ValidationInfo(message("remAccExpDateNull"), txtExpiryDate);
} */ else if (isRemoteEnabled && (!cerFile.exists() || (!newPath.endsWith(".cer")))) {
return new ValidationInfo(message("remAccInvldPath"), txtPath);
} else {
return null;
}
}
/**
* Validates the expiry date.
* Expiry date should be greater than current date.
*
* @param expDate
* @return
* @throws ParseException
* @throws WindowsAzureInvalidProjectOperationException
*/
private boolean validateExpDate(Date expDate) throws ParseException, WindowsAzureInvalidProjectOperationException {
boolean isValid = true;
long todaySeconds, userDateSeconds;
userDateSeconds = expDate.getTime();
GregorianCalendar todayCal = new GregorianCalendar();
todaySeconds = todayCal.getTimeInMillis();
if ((userDateSeconds - todaySeconds) < 0) {
PluginUtil.displayErrorDialog(message("remAccErTxtTitle"), message("remAccDateWrong"));
isValid = false;
} else {
waProjManager.setRemoteAccessAccountExpiration(expDate);
}
return isValid;
}
public boolean doOKAction() {
try {
loadProject();
if (remoteChkBtn.isSelected()) {
waProjManager.setRemoteAccessAllRoles(true);
String userName = txtUserName.getText();
String pwd = txtPassword.getText();
String cnfPwd = txtConfirmPwd.getText();
String newPath = txtPath.getText();
Date expDate = txtExpiryDate.getDate();
String tempPath = newPath;
boolean isPathChanged = false;
waProjManager.setRemoteAccessUsername(userName);
if (!newPath.equals(waProjManager.getRemoteAccessCertificatePath()) && !newPath.isEmpty()) {
isPathChanged = true;
/*
* check If certificate file path has changed,
* If yes then prompt user
* for changing the password as well,
* if that is not changed.
* Because we have to encrypt the new password
* and then we will generate certificate
* based on that.
* Case 1 :- If user has changed the path
* and password is old then it
* will prompt for new password or re-enter the password.
* If user changes the password
* then it will generate certificate based
* on that new password.
* Case 2 :- If user set the blank password
* even after displaying that
* password change prompt, in that case
* we will display warning messages
* to user that whether he want to continue
* with empty password, If yes
* then we will consider that blank password
* else use will have to enter
* new password.
*/
if (pwd.equals(waProjManager.getRemoteAccessEncryptedPassword()) && !pwd.isEmpty()) {
txtPassword.setText("");
txtConfirmPwd.setText("");
PluginUtil.displayErrorDialog(message("remAccErTxtTitle"), message("remAccPwdMstChng"));
return false;
}
}
if (pwd.isEmpty()) {
int choice = Messages.showOkCancelDialog(message("remAccErTxtTitle"), message("remAccWarnPwd"), Messages.getQuestionIcon());
if (!(choice == Messages.OK)) {
return false;
}
}
if (expDate == null) {
PluginUtil.displayErrorDialog(message("remAccErTxtTitle"), message("remAccExpDateNull"));
return false;
} else {
boolean status = validateExpDate(expDate);
if (!status) {
return false;
}
}
if (newPath.equalsIgnoreCase("")) {
PluginUtil.displayErrorDialog(message("remAccErTxtTitle"), message("remAccPathNull"));
return false;
}
/*
* Check for displaying the relative path
* in case when user select the certificate file path
* as workspace or of current project.
* We will be showing relative path in that case on UI.
*/
if (tempPath.startsWith(BASE_PATH)) {
tempPath = tempPath.substring(tempPath.indexOf("}") + 1, tempPath.length());
tempPath = String.format("%s%s", PluginUtil.getModulePath(myModule), tempPath);
}
File file = new File(tempPath);
//if path is not correct.display error message for that.
if (file.exists() && tempPath.endsWith(".cer")) {
waProjManager.setRemoteAccessCertificatePath(newPath);
} else {
PluginUtil.displayErrorDialog(message("remAccErTxtTitle"), message("remAccInvldPath"));
return false;
}
try {
if (isPathChanged) {
String thumbprint = CerPfxUtil.getThumbPrint(tempPath);
if (waProjManager.isRemoteAccessTryingToUseSSLCert(thumbprint)) {
PluginUtil.displayErrorDialog(message("remAccSyntaxErr"), message("usedBySSL"));
return false;
} else {
waProjManager.setRemoteAccessCertificateFingerprint(thumbprint);
}
}
} catch (Exception e) {
PluginUtil.displayErrorDialogAndLog(message("remAccSyntaxErr"), message("remAccErTmbPrint"), e);
return false;
}
if (cnfPwd.equals(pwd)) {
try {
/*
* Encrypting the password
* if it is not dummy & blank from xml
* and isPwdChanged is true that means
* user has changes the password.
*/
String modifiedPwd = message("remAccDummyPwd");
if (!pwd.equals(modifiedPwd) && !pwd.isEmpty() && isPwdChanged) {
String encryptedPwd = EncUtilHelper.encryptPassword(pwd, tempPath, AzurePlugin.pluginFolder);
waProjManager.setRemoteAccessEncryptedPassword(encryptedPwd);
} else {
waProjManager.setRemoteAccessEncryptedPassword(pwd);
}
} catch (Exception e) {
PluginUtil.displayErrorDialogAndLog(message("remAccSyntaxErr"), message("remAccErPwd"), e);
return false;
}
} else {
PluginUtil.displayErrorDialog(message("remAccErTxtTitle"), message("remAccPwdNotMatch"));
return false;
}
} else {
waProjManager.setRemoteAccessAllRoles(false);
}
waProjManager.save();
} catch (WindowsAzureInvalidProjectOperationException e) {
PluginUtil.displayErrorDialogAndLog(message("remAccSyntaxErr"), message("proPageErrMsgBox1") + message("proPageErrMsgBox2"), e);
} catch (ParseException e) {
PluginUtil.displayErrorDialogAndLog(message("remAccErrTitle"), message("remAccErDateParse"), e);
return false;
}
LocalFileSystem.getInstance().findFileByPath(PluginUtil.getModulePath(myModule)).refresh(true, true);
isFrmEncLink = false;
return true;
}
public JComponent getPanel() {
return contentPane;
}
/**
* This method loads the projects available in workspace.
* selProject variable will contain value of current selected project.
*/
private void loadProject() {
try {
waProjManager = WindowsAzureProjectManager.load(new File(PluginUtil.getModulePath(myModule)));
} catch (Exception e) {
PluginUtil.displayErrorDialog(message("remAccSyntaxErr"), message("proPageErrMsgBox1") + message("proPageErrMsgBox2"));
log(message("remAccErProjLoad"), e);
}
}
@Override
public String getHelpTopic() {
return "windows_azure_project_remote_access_property";
}
}