/**
* 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.libraries;
import com.intellij.openapi.fileChooser.FileChooserDescriptor;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.ui.Messages;
import com.intellij.openapi.ui.TextFieldWithBrowseButton;
import com.intellij.openapi.ui.ValidationInfo;
import com.intellij.openapi.vfs.VirtualFile;
import com.microsoft.intellij.AzurePlugin;
import com.microsoft.intellij.ui.AzureAbstractPanel;
import com.microsoft.intellij.ui.NewCertificateDialog;
import com.microsoft.intellij.ui.util.UIUtils;
import com.microsoft.intellij.util.PluginUtil;
import com.microsoft.wacommon.commoncontrols.NewCertificateDialogData;
import com.microsoftopentechnologies.azurecommons.wacommonutil.CerPfxUtil;
import com.microsoftopentechnologies.azuremanagementutil.util.Base64;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.swing.*;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.*;
import java.security.cert.X509Certificate;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Map;
import static com.microsoft.intellij.ui.messages.AzureBundle.message;
class LibraryPropertiesPanel implements AzureAbstractPanel {
private static final int BUFF_SIZE = 1024;
private JPanel rootPanel;
private JCheckBox depCheck;
private JPanel acsFilterPanel;
private JTextField acsTxt;
private JTextField relTxt;
private TextFieldWithBrowseButton certTxt;
private JButton newCertBtn;
private JTextPane certInfoTxt;
private JCheckBox embedCertCheck;
private JCheckBox requiresHttpsCheck;
private JLabel libraryVersion;
private JLabel location;
private AzureLibrary azureLibrary;
private Module module;
private boolean isEdit;
public LibraryPropertiesPanel(Module module, AzureLibrary azureLibrary, boolean isEdit, boolean isExported) {
this.module = module;
this.azureLibrary = azureLibrary;
this.isEdit = isEdit;
init();
depCheck.setSelected(isExported);
}
public void init() {
acsTxt.setText(message("acsTxt"));
certTxt.getTextField().getDocument().addDocumentListener(createCertTxtListener());
Messages.configureMessagePaneUi(certInfoTxt, message("embedCertDefTxt"));
FileChooserDescriptor fileChooserDescriptor = new FileChooserDescriptor(true, false, false, false, false, false) {
@Override
public boolean isFileVisible(VirtualFile file, boolean showHiddenFiles) {
return file.isDirectory() || (file.getExtension() != null && (file.getExtension().equals("cer") || file.getExtension().equals(".CER")));
}
@Override
public boolean isFileSelectable(VirtualFile file) {
return (file.getExtension() != null && (file.getExtension().equals("cer") || file.getExtension().equals(".CER")));
}
};
fileChooserDescriptor.setTitle("Select Certificate");
newCertBtn.addActionListener(createNewCertListener());
certTxt.addActionListener(UIUtils.createFileChooserListener(certTxt, null, fileChooserDescriptor));
requiresHttpsCheck.addActionListener(createRequiredHttpsCheckListener());
if (isEdit()) {
// Edit library scenario
try {
// com.intellij.psi.search.PsiShortNamesCache.getInstance(module.getProject()).getFilesByName("web.xml");
ACSFilterHandler editHandler = new ACSFilterHandler(String.format("%s%s%s", PluginUtil.getModulePath(module), File.separator, message("xmlPath")));
Map<String, String> paramMap = editHandler.getAcsFilterParams();
acsTxt.setText(paramMap.get(message("acsAttr")));
relTxt.setText(paramMap.get(message("relAttr")));
if (paramMap.get(message("certAttr")) != null) {
certTxt.setText(paramMap.get(message("certAttr")));
certInfoTxt.setText(getCertInfo(certTxt.getText()));
} else {
certInfoTxt.setText(getEmbeddedCertInfo());
embedCertCheck.setSelected(true);
}
requiresHttpsCheck.setSelected(!Boolean.valueOf(paramMap.get(message("allowHTTPAttr"))));
} catch (Exception e) {
AzurePlugin.log(e.getMessage(), e);
}
} else {
// Add library scenario
depCheck.setSelected(true);
}
}
private ActionListener createNewCertListener() {
return new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
NewCertificateDialogData data = new NewCertificateDialogData();
NewCertificateDialog dialog = new NewCertificateDialog(data, "", module.getProject());
dialog.show();
if (dialog.isOK()) {
String certPath = data.getCerFilePath();
certTxt.setText(certPath != null ? certPath.replace('\\', '/') : certPath);
certInfoTxt.setText(getCertInfo(certTxt.getText()));
}
}
};
}
private DocumentListener createCertTxtListener() {
return new DocumentListener() {
@Override
public void insertUpdate(DocumentEvent e) {
handleUpdate();
}
@Override
public void removeUpdate(DocumentEvent e) {
handleUpdate();
}
@Override
public void changedUpdate(DocumentEvent e) {
handleUpdate();
}
private void handleUpdate() {
String certInfo = getCertInfo(certTxt.getText());
if (certInfo != null)
certInfoTxt.setText(certInfo);
else
certInfoTxt.setText("");
}
};
}
private ActionListener createRequiredHttpsCheckListener() {
return new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
if (requiresHttpsCheck.isSelected()) {
//Do nothing
} else {
int choice = Messages.showYesNoDialog(message("requiresHttpsDlgMsg"), message("requiresHttpsDlgTitle"), Messages.getQuestionIcon());
if (choice == Messages.NO) {
requiresHttpsCheck.setSelected(true);
}
}
}
};
}
public JComponent prepare() {
acsFilterPanel.setVisible(azureLibrary == AzureLibrary.ACS_FILTER);
libraryVersion.setText(azureLibrary.getName());
location.setText((String.format("%s%s%s", AzurePlugin.pluginFolder, File.separator, azureLibrary.getLocation())));
rootPanel.revalidate();
return rootPanel;
}
public boolean onFinish() {
if (azureLibrary == AzureLibrary.ACS_FILTER) {
if (doValidate() != null) {
return false;
}
configureDeployment();
}
return true;
}
@Override
public JComponent getPanel() {
return prepare();
}
@Override
public String getDisplayName() {
return message("edtLbrTtl");
}
@Override
public boolean doOKAction() {
try {
configureDeployment();
return true;
} catch (Exception ex) {
PluginUtil.displayErrorDialogAndLog(message("error"), "Error saving configuration", ex);
return false;
}
}
@Override
public String getSelectedValue() {
return null;
}
public boolean isExported() {
return depCheck.isSelected();
}
public ValidationInfo doValidate() {
boolean isEdit = isEdit();
StringBuilder errorMessage = new StringBuilder();
// Display error if acs login page URL is null. Applicable for first time and edit scenarios.
if (acsTxt.getText().isEmpty() || acsTxt.getText().equalsIgnoreCase(message("acsTxt")))
errorMessage.append(message("acsTxtErr")).append("\n");
// Display error if relying part realm is null. Applicable for first time and edit scenarios.
if (relTxt.getText().isEmpty())
errorMessage.append(message("relTxtErr")).append("\n");
// if certificate location does not end with .cer then display error
if (!certTxt.getText().isEmpty() && !certTxt.getText().toLowerCase().endsWith(".cer"))
errorMessage.append(message("certTxtInvalidExt")).append("\n");
// Display error if cert location is empty for first time and for edit scenarios if
// embedded cert option is not selected
if ((!isEdit && certTxt.getText().isEmpty()) || (isEdit && certTxt.getText().isEmpty() && !embedCertCheck.isSelected()))
errorMessage.append(message("certTxtErr")).append("\n");
// For first time , if embedded cert option is selected , display error if file does not exist at source
if (!isEdit && !certTxt.getText().isEmpty() && embedCertCheck.isSelected() == true) {
if (!new File(CerPfxUtil.getCertificatePath(certTxt.getText())).exists()) {
errorMessage.append(message("acsNoValidCert")).append("\n");
}
}
if (errorMessage.length() > 0) {
return new ValidationInfo(errorMessage.toString());
} else {
return null;
}
}
@Override
public String getHelpTopic() {
return null;
}
/**
* Method generates key using
* Advanced Encryption Standard algorithm.
*
* @return String
* @throws Exception
*/
private String generateKey() throws Exception {
KeyGenerator keyGenerator = KeyGenerator.getInstance("AES");
keyGenerator.init(128);
SecretKey secretKey = keyGenerator.generateKey();
byte[] keyInBytes = secretKey.getEncoded();
String key = Base64.encode(keyInBytes);
return key;
}
/**
* Method creates web.xml (deployment descriptor)
* in WebContent\WEB-INF folder of dynamic web project
* if does not present already.
*
* @return String
*/
private String createWebXml() {
String path = null;
try {
File cmpntFileLoc = new File(String.format("%s%s%s", PluginUtil.getModulePath(module), File.separator, message("depDirLoc")));
String cmpntFile = String.format("%s%s%s", cmpntFileLoc, File.separator, message("depFileName"));
if (!cmpntFileLoc.exists()) {
cmpntFileLoc.mkdirs();
}
AzurePlugin.copyResourceFile(message("resFileLoc"), cmpntFile);
path = cmpntFile;
} catch (Exception e) {
PluginUtil.displayErrorDialogAndLog(message("acsErrTtl"), message("fileCrtErrMsg"), e);
}
return new File(path).getPath();
}
private String getEmbeddedCertInfo() {
String webinfLoc = String.format("%s%s%s", PluginUtil.getModulePath(module), File.separator, message("depDirLoc"));
String certLoc = String.format("%s%s%s", webinfLoc, File.separator, message("acsCertLoc"));
return getCertInfo(certLoc);
}
public static void copy(File source, final File destination) throws IOException {
InputStream instream = null;
if (source.isDirectory()) {
if (!destination.exists()) {
destination.mkdirs();
}
String[] kid = source.list();
for (int i = 0; i < kid.length; i++) {
copy(new File(source, kid[i]),
new File(destination, kid[i]));
}
} else {
//InputStream instream = null;
OutputStream out = null;
try {
if (destination != null && destination.isFile() && !destination.getParentFile().exists())
destination.getParentFile().mkdirs();
instream = new FileInputStream(source);
out = new FileOutputStream(destination);
byte[] buf = new byte[BUFF_SIZE];
int len = instream.read(buf);
while (len > 0) {
out.write(buf, 0, len);
len = instream.read(buf);
}
} finally {
if (instream != null) {
instream.close();
}
if (out != null) {
out.close();
}
}
}
}
public void removeEmbedCert() {
String webinfLoc = String.format("%s%s%s", PluginUtil.getModulePath(module), File.separator, message("depDirLoc"));
String certLoc = String.format("%s%s%s", webinfLoc, File.separator, message("acsCertLoc"));
File destination = new File(certLoc);
if (destination.exists())
destination.delete();
if (destination.getParentFile().exists() && destination.getParentFile().list().length == 0)
destination.getParentFile().delete();
}
private static String getCertInfo(String certURL) {
X509Certificate acsCert = CerPfxUtil.getCert(certURL, null);
if (acsCert != null) {
DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
StringBuilder certInfo = new StringBuilder();
certInfo.append(String.format("%1$-10s", "Subject")).append(" : ").append(acsCert.getSubjectDN()).append("\n");
certInfo.append(String.format("%1$-11s", "Issuer")).append(" : ").append(acsCert.getIssuerDN()).append("\n");
certInfo.append(String.format("%1$-13s", "Valid")).append(" : ").append(dateFormat.format(acsCert.getNotBefore())).
append(" to ").append(dateFormat.format(acsCert.getNotAfter()));
return certInfo.toString();
} else {
return null;
}
}
/**
* Method adds ACS filter and filter mapping tags in web.xml
* and saves input values given on ACS library page.
* In case of edit, populates previously set values.
*/
private void configureDeployment() {
ACSFilterHandler handler = null;
try {
String xmlPath = String.format("%s%s%s", PluginUtil.getModulePath(module), File.separator, message("xmlPath"));
File webXml = new File(xmlPath);
if (webXml.exists()) {
handler = new ACSFilterHandler(xmlPath);
handler.setAcsFilterParams(message("acsAttr"), acsTxt.getText());
handler.setAcsFilterParams(message("relAttr"), relTxt.getText());
if (!embedCertCheck.isSelected()) {
handler.setAcsFilterParams(message("certAttr"), certTxt.getText());
if (getEmbeddedCertInfo() != null)
removeEmbedCert();
} else {
handler.removeParamsIfExists(message("certAttr"));
if (!certTxt.getText().isEmpty()) {
String webinfLoc = String.format("%s%s%s", PluginUtil.getModulePath(module), File.separator, message("depDirLoc"));
String certLoc = String.format("%s%s%s", webinfLoc, File.separator, message("acsCertLoc"));
File destination = new File(certLoc);
if (!destination.getParentFile().exists())
destination.getParentFile().mkdir();
copy(new File(CerPfxUtil.getCertificatePath(certTxt.getText())), destination);
}
}
handler.setAcsFilterParams(message("secretKeyAttr"), generateKey());
handler.setAcsFilterParams(message("allowHTTPAttr"), requiresHttpsCheck.isSelected() ? "false" : "true");
} else {
int choice = Messages.showYesNoDialog(message("depDescMsg"), message("depDescTtl"), Messages.getQuestionIcon());
if (choice == Messages.YES) {
String path = createWebXml();
//copy cert into WEB-INF/cert/_acs_signing.cer location if embed cert is selected
if (embedCertCheck.isSelected()) {
String webinfLoc = String.format("%s%s%s", PluginUtil.getModulePath(module), File.separator, message("depDirLoc"));
String certLoc = String.format("%s%s%s", webinfLoc, File.separator, message("acsCertLoc"));
File destination = new File(certLoc);
if (!destination.getParentFile().exists())
destination.getParentFile().mkdir();
copy(new File(CerPfxUtil.getCertificatePath(certTxt.getText())), destination);
}
handler = new ACSFilterHandler(path);
handler.setAcsFilterParams(message("acsAttr"), acsTxt.getText());
handler.setAcsFilterParams(message("relAttr"), relTxt.getText());
if (!embedCertCheck.isSelected()) { //Do not make entry if embed cert is selected
handler.setAcsFilterParams(message("certAttr"), certTxt.getText());
if (getEmbeddedCertInfo() != null)
removeEmbedCert();
}
handler.setAcsFilterParams(message("secretKeyAttr"), generateKey());
handler.setAcsFilterParams(message("allowHTTPAttr"), requiresHttpsCheck.isSelected() ? "false" : "true");
} else {
return;
}
}
} catch (Exception e) {
PluginUtil.displayErrorDialogAndLog(message("acsErrTtl"), message("acsErrMsg"), e);
}
try {
handler.save();
} catch (Exception e) {
PluginUtil.displayErrorDialogAndLog(message("acsErrTtl"), message("saveErrMsg"), e);
}
}
/**
* @return current window is edit or not
*/
private boolean isEdit() {
return isEdit;
}
public String getHelpId() {
return "acs_config_dialog";
}
}