/*******************************************************************************
* Copyright (c) 2017 Rogue Wave Software Inc. and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Rogue Wave Software Inc. - initial implementation
*******************************************************************************/
package org.eclipse.php.profile.ui.launcher;
import java.security.MessageDigest;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.OperationCanceledException;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.debug.core.ILaunchConfiguration;
import org.eclipse.debug.core.ILaunchConfigurationWorkingCopy;
import org.eclipse.equinox.security.storage.StorageException;
import org.eclipse.jface.dialogs.IMessageProvider;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.php.internal.debug.core.IPHPDebugConstants;
import org.eclipse.php.internal.debug.core.launching.PHPLaunchUtilities;
import org.eclipse.php.internal.debug.ui.Logger;
import org.eclipse.php.internal.server.core.tunneling.TunnelTester;
import org.eclipse.php.profile.ui.launcher.AbstractPHPLaunchConfigurationProfilerTab.StatusMessage;
import org.eclipse.php.profile.ui.launcher.AbstractPHPLaunchConfigurationProfilerTab.WidgetListener;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.CLabel;
import org.eclipse.swt.events.*;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.*;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.progress.UIJob;
/**
* Abstract implementation of profiler launch settings section that corresponds
* to PHP web launch configuration type.
*
* @author Bartlomiej Laczkowski
*/
public abstract class AbstractProfileWebLaunchSettingsSection
implements IProfilerLaunchSettingsSection {
protected static class Digester {
/**
* Returns a MD5 digest in a hex format for the given string.
*
* @param content
* The string to digest
* @return MD5 digested string in a hex format; null, in case of an
* error or a null input
*/
public static String digest(String content) {
if (content == null) {
return null;
}
if (content.length() == 0) {
return ""; //$NON-NLS-1$
}
String passwordDigest = null;
try {
MessageDigest md = MessageDigest.getInstance("MD5"); //$NON-NLS-1$
md.reset();
md.update(content.getBytes());
byte digest[] = md.digest();
StringBuffer buffer = new StringBuffer();
for (int i = 0; i < digest.length; i++) {
String hex = Integer.toHexString(0xff & digest[i]);
if (hex.length() == 1) {
buffer.append('0');
}
buffer.append(hex);
}
passwordDigest = buffer.toString();
} catch (Exception e) {
Logger.logException("Message digest error", e); //$NON-NLS-1$
}
if (passwordDigest == null) {
return null;
}
return passwordDigest;
}
}
protected WidgetListener widgetListener;
protected Group tunnelGroup;
protected Button profileThroughTunnel;
protected Label nameLabel;
protected Text userName;
protected Label passwordLabel;
protected Text password;
protected Button testButton;
protected CLabel testResultLabel;
private ILaunchConfiguration configuration;
private boolean isSSHCredentialsChange;
/*
* (non-Javadoc)
*
* @see org.eclipse.php.profile.ui.launcher.IProfilerLaunchSettingsSection#
* createSection(org.eclipse.swt.widgets.Composite,
* org.eclipse.php.profile.ui.launcher.
* AbstractPHPLaunchConfigurationProfilerTab.WidgetListener)
*/
@Override
public final void createSection(Composite parent,
WidgetListener widgetListener) {
this.widgetListener = widgetListener;
buildSection(parent);
}
/*
* (non-Javadoc)
*
* @see org.eclipse.php.profile.ui.launcher.IProfilerLaunchSettingsSection#
* initialize(org.eclipse.debug.core.ILaunchConfiguration)
*/
@Override
public void initialize(ILaunchConfiguration configuration) {
this.configuration = configuration;
if (tunnelGroup != null) {
try {
boolean isUsingTunnel = configuration
.getAttribute(IPHPDebugConstants.USE_SSH_TUNNEL, false);
profileThroughTunnel.setSelection(isUsingTunnel);
updateTunnelComponents(isUsingTunnel);
if (isUsingTunnel && tunnelGroup != null) {
userName.setText(configuration.getAttribute(
IPHPDebugConstants.SSH_TUNNEL_USER_NAME, "")); //$NON-NLS-1$
if (userName.getText().length() > 0) {
// Load the password from the Secured Storage
try {
password.setText(PHPLaunchUtilities
.getSecurePreferences(PHPLaunchUtilities
.getDebugHost(getConfiguration()))
.get(userName.getText(), "")); //$NON-NLS-1$
} catch (StorageException e) {
Logger.logException(
"Error accessing the secured storage", e); //$NON-NLS-1$
password.setText(""); //$NON-NLS-1$
}
} else {
password.setText(""); //$NON-NLS-1$
}
}
} catch (CoreException e) {
}
}
isValid(configuration);
}
/*
* (non-Javadoc)
*
* @see org.eclipse.php.profile.ui.launcher.IProfilerLaunchSettingsSection#
* performApply(org.eclipse.debug.core.ILaunchConfigurationWorkingCopy)
*/
@Override
public void performApply(ILaunchConfigurationWorkingCopy configuration) {
if (tunnelGroup != null) {
configuration.setAttribute(IPHPDebugConstants.USE_SSH_TUNNEL,
profileThroughTunnel.getSelection());
if (profileThroughTunnel.getSelection()) {
configuration.setAttribute(
IPHPDebugConstants.SSH_TUNNEL_USER_NAME,
userName.getText().trim());
/*
* We save a hash of the password and not the real one. This is
* only used to allow an apply when a password change happens.
* The real password saving is done through the secured storage
* right after that line.
*/
String passwordDigest = Digester
.digest(password.getText().trim());
if (passwordDigest == null) {
// As a default, use the string hash.
passwordDigest = String
.valueOf(password.getText().trim().hashCode());
}
configuration.setAttribute(
IPHPDebugConstants.SSH_TUNNEL_PASSWORD, passwordDigest);
// Save to secured storage
try {
/*
* Note: At this point we write to the secure storage at any
* apply. This might put in the storage some un-needed keys,
* so we also scan the launch configurations on startup and
* make sure that the storage contains only what we need.
*/
if (!isSSHCredentialsChange) {
/*
* We'll save to the secured storage only if the change
* was done outside text fields (that might contains the
* changes in the user-name and password as we type
* them). This flag will be off when the apply button is
* actually clicked (or when other widgets are
* triggering the apply call).
*/
PHPLaunchUtilities
.getSecurePreferences(PHPLaunchUtilities
.getDebugHost(getConfiguration()))
.put(userName.getText(),
password.getText().trim(),
true /* encrypt */);
}
} catch (StorageException e) {
Logger.logException("Error saving to the secured storage", //$NON-NLS-1$
e);
}
} else {
configuration.setAttribute(
IPHPDebugConstants.SSH_TUNNEL_USER_NAME, ""); //$NON-NLS-1$
configuration.setAttribute(
IPHPDebugConstants.SSH_TUNNEL_PASSWORD, ""); //$NON-NLS-1$
}
}
isSSHCredentialsChange = false; // Reset this flag here.
}
/*
* (non-Javadoc)
*
* @see org.eclipse.php.profile.ui.launcher.IProfilerLaunchSettingsSection#
* setDefaults(org.eclipse.debug.core.ILaunchConfigurationWorkingCopy)
*/
@Override
public void setDefaults(ILaunchConfigurationWorkingCopy configuration) {
this.configuration = configuration;
}
/*
* (non-Javadoc)
*
* @see
* org.eclipse.php.profile.ui.launcher.IProfilerLaunchSettingsSection#isValid(
* org.eclipse.debug.core.ILaunchConfiguration)
*/
@Override
public StatusMessage isValid(ILaunchConfiguration configuration) {
if (tunnelGroup != null) {
if (profileThroughTunnel.getSelection()) {
boolean valid = userName.getText().trim().length() > 0;
testButton.setEnabled(valid);
if (!valid) {
return new StatusMessage(IMessageProvider.ERROR,
Messages.AbstractProfileWebLaunchSettingsSection_Missing_SSH_user_name);
}
}
}
return new StatusMessage(IMessageProvider.NONE, ""); //$NON-NLS-1$
}
protected void buildSection(Composite parent) {
createTunnelGroup(parent);
}
protected void createTunnelGroup(Composite composite) {
// Add the tunnel group
tunnelGroup = new Group(composite, SWT.NONE);
tunnelGroup.setLayout(new GridLayout(1, false));
tunnelGroup.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
tunnelGroup.setText(
Messages.AbstractProfileWebLaunchSettingsSection_SSH_tunnel);
// Add the tunneling controls
profileThroughTunnel = new Button(tunnelGroup, SWT.CHECK);
profileThroughTunnel.setText(
Messages.AbstractProfileWebLaunchSettingsSection_Profile_through_SSH_tunnel);
Composite credentialsComposite = new Composite(tunnelGroup, SWT.NONE);
credentialsComposite.setLayout(new GridLayout(2, false));
GridData data = new GridData(GridData.FILL_HORIZONTAL);
data.horizontalIndent = 20;
credentialsComposite.setLayoutData(data);
nameLabel = new Label(credentialsComposite, SWT.NONE);
nameLabel.setText(
Messages.AbstractProfileWebLaunchSettingsSection_User_name);
userName = new Text(credentialsComposite, SWT.BORDER | SWT.SINGLE);
data = new GridData(GridData.FILL_HORIZONTAL);
data.widthHint = 200;
userName.setLayoutData(data);
userName.addModifyListener(new ModifyListener() {
@Override
public void modifyText(ModifyEvent e) {
isSSHCredentialsChange = true;
updateTunnelComponents(true);
}
});
passwordLabel = new Label(credentialsComposite, SWT.NONE);
passwordLabel.setText(
Messages.AbstractProfileWebLaunchSettingsSection_Password);
password = new Text(credentialsComposite,
SWT.PASSWORD | SWT.BORDER | SWT.SINGLE);
data = new GridData(GridData.FILL_HORIZONTAL);
data.widthHint = 200;
password.setLayoutData(data);
password.addModifyListener(new ModifyListener() {
@Override
public void modifyText(ModifyEvent e) {
isSSHCredentialsChange = true;
updateTunnelComponents(true);
}
});
final Composite testConnectionComposite = new Composite(
credentialsComposite, SWT.NONE);
GridLayout layout = new GridLayout(2, false);
layout.marginWidth = 0;
layout.marginHeight = 0;
testConnectionComposite.setLayout(layout);
data = new GridData(GridData.FILL_HORIZONTAL);
data.horizontalSpan = 2;
testConnectionComposite.setLayoutData(data);
testButton = new Button(testConnectionComposite, SWT.PUSH);
testButton.setText(
Messages.AbstractProfileWebLaunchSettingsSection_Test_connection);
testResultLabel = new CLabel(testConnectionComposite, SWT.NONE);
testResultLabel.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
testButton.addSelectionListener(new SelectionAdapter() {
public void widgetSelected(SelectionEvent e) {
// Run a test for the connection
testTunnelConnection();
}
});
testResultLabel.addMouseListener(new MouseAdapter() {
public void mouseUp(MouseEvent e) {
Object messageData = testResultLabel.getData("info"); //$NON-NLS-1$
if (messageData != null) {
MessageDialog.openInformation(
PlatformUI.getWorkbench().getActiveWorkbenchWindow()
.getShell(),
Messages.AbstractProfileWebLaunchSettingsSection_SSH_tunnel_test,
messageData.toString());
}
}
});
profileThroughTunnel.addSelectionListener(new SelectionAdapter() {
public void widgetSelected(SelectionEvent se) {
Button b = (Button) se.getSource();
boolean selection = b.getSelection();
updateTunnelComponents(selection);
}
});
// Register widget listener for triggering changes
userName.addModifyListener(widgetListener);
password.addModifyListener(widgetListener);
profileThroughTunnel.addSelectionListener(widgetListener);
}
protected ILaunchConfiguration getConfiguration() {
return configuration;
}
protected void updateTunnelComponents(boolean enabled) {
testResultLabel.setText(""); //$NON-NLS-1$
setEnabled(enabled, userName, password, nameLabel, passwordLabel,
testResultLabel);
testButton
.setEnabled(enabled && userName.getText().trim().length() > 0);
}
protected void setEnabled(boolean enabled, Control... controls) {
for (Control c : controls) {
c.setEnabled(enabled);
}
}
/**
* Test a connection with the user name and password that are currently
* typed in their designated boxes. We assume here that the validation of
* the dialog already eliminated a situation where the Test button is
* enabled when there is a missing user-name or password.
*/
protected void testTunnelConnection() {
testButton.setEnabled(false);
testResultLabel.setForeground(
Display.getDefault().getSystemColor(SWT.COLOR_BLUE));
testResultLabel.setText(
Messages.AbstractProfileWebLaunchSettingsSection_Testing_connection);
testResultLabel.setCursor(
Display.getDefault().getSystemCursor(SWT.CURSOR_WAIT));
testResultLabel.setData("info", null); //$NON-NLS-1$
Job connectionTest = new UIJob(
Messages.AbstractProfileWebLaunchSettingsSection_SSH_tunnel_test) {
public IStatus runInUIThread(IProgressMonitor monitor) {
try {
String remoteHost = PHPLaunchUtilities
.getDebugHost(getConfiguration());
int port = PHPLaunchUtilities
.getDebugPort(getConfiguration());
if (remoteHost == null || remoteHost.length() == 0
|| port < 0) {
// The host was not yet set in the launch configuration.
testButton.setEnabled(true);
testResultLabel.setCursor(Display.getDefault()
.getSystemCursor(SWT.CURSOR_HAND));
testResultLabel.setForeground(Display.getDefault()
.getSystemColor(SWT.COLOR_DARK_RED));
if (port > -1) {
testResultLabel.setText(
Messages.AbstractProfileWebLaunchSettingsSection_Missing_host);
testResultLabel.setData("info", //$NON-NLS-1$
Messages.AbstractProfileWebLaunchSettingsSection_Missing_host_address);
} else {
testResultLabel.setText(
Messages.AbstractProfileWebLaunchSettingsSection_Error);
testResultLabel.setData("info", //$NON-NLS-1$
Messages.AbstractProfileWebLaunchSettingsSection_Could_not_determine_port);
}
}
testResultLabel.setCursor(Display.getDefault()
.getSystemCursor(SWT.CURSOR_WAIT));
IStatus connectionStatus = TunnelTester.test(remoteHost,
userName.getText().trim(),
password.getText().trim(), port, port);
testButton.setEnabled(true);
testResultLabel.setCursor(null);
if (connectionStatus.isOK()) {
testResultLabel.setForeground(Display.getDefault()
.getSystemColor(SWT.COLOR_DARK_GREEN));
testResultLabel.setText(
Messages.AbstractProfileWebLaunchSettingsSection_Successfully_connected);
} else if (connectionStatus.isMultiStatus()) {
/*
* A case where the connection indicate that it was
* successful, however, we were still not able to verify
* that.
*/
testResultLabel.setCursor(Display.getDefault()
.getSystemCursor(SWT.CURSOR_HAND));
testResultLabel.setForeground(Display.getDefault()
.getSystemColor(SWT.COLOR_DARK_YELLOW));
testResultLabel.setText(
Messages.AbstractProfileWebLaunchSettingsSection_Undetermined);
testResultLabel.setData("info", //$NON-NLS-1$
connectionStatus.getMessage());
/*
* Update the password fields in case the multi status
* also contains a password change information.
*/
IStatus[] children = connectionStatus.getChildren();
if (children != null) {
for (IStatus child : children) {
if (child.getSeverity() == IStatus.INFO && child
.getCode() == TunnelTester.PASSWORD_CHANGED_CODE) {
password.setText(child.getMessage());
break;
}
}
}
} else if (connectionStatus
.getSeverity() == IStatus.WARNING) {
testResultLabel.setCursor(Display.getDefault()
.getSystemCursor(SWT.CURSOR_HAND));
testResultLabel.setForeground(Display.getDefault()
.getSystemColor(SWT.COLOR_DARK_GREEN));
testResultLabel.setText(
Messages.AbstractProfileWebLaunchSettingsSection_Connected_with_warnings);
testResultLabel.setData("info", //$NON-NLS-1$
connectionStatus.getMessage());
} else if (connectionStatus.getSeverity() == IStatus.INFO) {
testResultLabel.setForeground(Display.getDefault()
.getSystemColor(SWT.COLOR_DARK_GREEN));
testResultLabel.setText(
Messages.AbstractProfileWebLaunchSettingsSection_Connected_with_warnings);
/*
* Update the password field in case that the info
* indicated a password change.
*/
if (connectionStatus
.getCode() == TunnelTester.PASSWORD_CHANGED_CODE) {
password.setText(connectionStatus.getMessage());
}
} else if (connectionStatus
.getSeverity() == IStatus.ERROR) {
testResultLabel.setCursor(Display.getDefault()
.getSystemCursor(SWT.CURSOR_HAND));
testResultLabel.setForeground(Display.getDefault()
.getSystemColor(SWT.COLOR_DARK_RED));
testResultLabel.setText(
Messages.AbstractProfileWebLaunchSettingsSection_Failed_to_connect);
testResultLabel.setData("info", //$NON-NLS-1$
connectionStatus.getMessage());
}
} catch (OperationCanceledException oce) {
testButton.setEnabled(true);
testResultLabel.setCursor(null);
testResultLabel.setForeground(null);
testResultLabel.setText(
Messages.AbstractProfileWebLaunchSettingsSection_Canceled);
}
return org.eclipse.core.runtime.Status.OK_STATUS;
}
};
connectionTest.setUser(true);
connectionTest.setPriority(Job.LONG);
connectionTest.schedule();
}
}