blob: 482fe4844a440ceb4e1284e6666cabe75af2aab8 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2008, 2018 IBM Corporation and others.
*
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* IBM Corporation - initial API and implementation
* Rapicorp, Inc. - add support for information dialog
*******************************************************************************/
package org.eclipse.equinox.internal.p2.ui;
import java.net.URL;
import java.security.cert.Certificate;
import java.util.*;
import java.util.List;
import org.bouncycastle.openpgp.PGPPublicKey;
import org.eclipse.core.runtime.*;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.equinox.internal.p2.ui.dialogs.TrustCertificateDialog;
import org.eclipse.equinox.internal.p2.ui.dialogs.UserValidationDialog;
import org.eclipse.equinox.p2.core.UIServices;
import org.eclipse.equinox.p2.ui.LoadMetadataRepositoryJob;
import org.eclipse.jface.dialogs.*;
import org.eclipse.jface.viewers.TreeNode;
import org.eclipse.jface.window.Window;
import org.eclipse.osgi.util.NLS;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.SelectionListener;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.widgets.*;
import org.eclipse.ui.PlatformUI;
/**
* The default GUI-based implementation of {@link UIServices}. The service
* declaration is made in the serviceui_component.xml file.
*
*/
public class ValidationDialogServiceUI extends UIServices {
static final class MessageDialogWithLink extends MessageDialog {
private final String linkText;
MessageDialogWithLink(Shell parentShell, String dialogTitle, Image dialogTitleImage, String dialogMessage,
int dialogImageType, String[] dialogButtonLabels, int defaultIndex, String linkText) {
super(parentShell, dialogTitle, dialogTitleImage, dialogMessage, dialogImageType, dialogButtonLabels,
defaultIndex);
this.linkText = linkText;
}
@Override
protected Control createCustomArea(Composite parent) {
if (linkText == null)
return super.createCustomArea(parent);
Link link = new Link(parent, SWT.NONE);
link.setText(linkText);
link.addSelectionListener(SelectionListener.widgetSelectedAdapter(e -> {
try {
URL url = new URL(e.text);
PlatformUI.getWorkbench().getBrowserSupport().getExternalBrowser().openURL(url);
} catch (Exception x) {
ProvUIActivator.getDefault().getLog().log(//
new Status(IStatus.ERROR, ProvUIActivator.PLUGIN_ID, x.getMessage(), x));
}
}));
return link;
}
}
/**
* Subclassed to add a cancel button to the error dialog.
*/
static class OkCancelErrorDialog extends ErrorDialog {
public OkCancelErrorDialog(Shell parentShell, String dialogTitle, String message, IStatus status,
int displayMask) {
super(parentShell, dialogTitle, message, status, displayMask);
}
@Override
protected void createButtonsForButtonBar(Composite parent) {
// create OK and Details buttons
createButton(parent, IDialogConstants.OK_ID, ProvUIMessages.ServiceUI_InstallAnywayAction_Label, true);
createButton(parent, IDialogConstants.CANCEL_ID, IDialogConstants.CANCEL_LABEL, true);
createDetailsButton(parent);
}
}
@Override
public AuthenticationInfo getUsernamePassword(final String location) {
final AuthenticationInfo[] result = new AuthenticationInfo[1];
if (!suppressAuthentication() && !isHeadless()) {
PlatformUI.getWorkbench().getDisplay().syncExec(() -> {
Shell shell = ProvUI.getDefaultParentShell();
String message = NLS.bind(ProvUIMessages.ServiceUI_LoginDetails, location);
UserValidationDialog dialog = new UserValidationDialog(shell, ProvUIMessages.ServiceUI_LoginRequired,
null, message);
int dialogCode = dialog.open();
if (dialogCode == Window.OK) {
result[0] = dialog.getResult();
} else if (dialogCode == Window.CANCEL) {
result[0] = AUTHENTICATION_PROMPT_CANCELED;
}
});
}
return result[0];
}
private boolean suppressAuthentication() {
Job job = Job.getJobManager().currentJob();
if (job != null) {
return job.getProperty(LoadMetadataRepositoryJob.SUPPRESS_AUTHENTICATION_JOB_MARKER) != null;
}
return false;
}
@Override
public TrustInfo getTrustInfo(Certificate[][] untrustedChains, final String[] unsignedDetail) {
return getTrustInfo(untrustedChains, Collections.emptyList(), unsignedDetail);
}
@Override
public TrustInfo getTrustInfo(Certificate[][] untrustedChains, Collection<PGPPublicKey> untrustedPublicKeys,
final String[] unsignedDetail) {
if (untrustedChains == null) {
untrustedChains = new Certificate[][] {};
}
boolean trustUnsigned = true;
boolean persistTrust = false;
List<Certificate> trustedCertificates = new ArrayList<>();
List<PGPPublicKey> trustedKeys = new ArrayList<>();
// Some day we may summarize all of this in one UI, or perhaps we'll have a
// preference to honor regarding
// unsigned content. For now we prompt separately first as to whether unsigned
// detail should be trusted
if (!isHeadless() && unsignedDetail != null && unsignedDetail.length > 0) {
final boolean[] result = new boolean[] { false };
PlatformUI.getWorkbench().getDisplay().syncExec(new Runnable() {
@Override
public void run() {
Shell shell = ProvUI.getDefaultParentShell();
OkCancelErrorDialog dialog = new OkCancelErrorDialog(shell, ProvUIMessages.ServiceUI_warning_title,
null, createStatus(), IStatus.WARNING);
result[0] = dialog.open() == IDialogConstants.OK_ID;
}
private IStatus createStatus() {
MultiStatus parent = new MultiStatus(ProvUIActivator.PLUGIN_ID, 0,
ProvUIMessages.ServiceUI_unsigned_message, null);
for (String element : unsignedDetail) {
parent.add(new Status(IStatus.WARNING, ProvUIActivator.PLUGIN_ID, element));
}
return parent;
}
});
trustUnsigned = result[0];
}
// For now, there is no need to show certificates if there was unsigned content
// and we don't trust it.
if (!trustUnsigned)
return new TrustInfo(trustedCertificates, trustedKeys, persistTrust, trustUnsigned);
// We've established trust for unsigned content, now examine the untrusted
// chains
if (!isHeadless() && (untrustedChains.length > 0 || !untrustedPublicKeys.isEmpty())) {
final TrustCertificateDialog[] dialog = new TrustCertificateDialog[1];
final TreeNode[] input = createTreeNodes(untrustedChains, untrustedPublicKeys);
PlatformUI.getWorkbench().getDisplay().syncExec(() -> {
Shell shell = ProvUI.getDefaultParentShell();
TrustCertificateDialog trustCertificateDialog = new TrustCertificateDialog(shell, input);
dialog[0] = trustCertificateDialog;
trustCertificateDialog.open();
});
persistTrust = true;
if (dialog[0].getResult() != null) {
for (Object o : dialog[0].getResult()) {
if (o instanceof TreeNode) {
o = ((TreeNode) o).getValue();
}
if (o instanceof Certificate) {
trustedCertificates.add((Certificate) o);
} else if (o instanceof PGPPublicKey) {
trustedKeys.add((PGPPublicKey) o);
}
}
}
}
return new TrustInfo(trustedCertificates, trustedKeys, persistTrust, trustUnsigned);
}
private TreeNode[] createTreeNodes(Certificate[][] certificates, Collection<PGPPublicKey> untrustedPublicKeys) {
TreeNode[] children = new TreeNode[certificates.length + untrustedPublicKeys.size()];
int i = 0;
for (i = 0; i < certificates.length; i++) {
TreeNode head = new TreeNode(certificates[i][0]);
TreeNode parent = head;
children[i] = head;
for (int j = 0; j < certificates[i].length; j++) {
TreeNode node = new TreeNode(certificates[i][j]);
node.setParent(parent);
parent.setChildren(new TreeNode[] { node });
parent = node;
}
}
for (PGPPublicKey key : untrustedPublicKeys) {
children[i] = new TreeNode(key);
i++;
}
return children;
}
@Override
public AuthenticationInfo getUsernamePassword(final String location, final AuthenticationInfo previousInfo) {
final AuthenticationInfo[] result = new AuthenticationInfo[1];
if (!suppressAuthentication() && !isHeadless()) {
PlatformUI.getWorkbench().getDisplay().syncExec(() -> {
Shell shell = ProvUI.getDefaultParentShell();
String message = null;
if (previousInfo.saveResult())
message = NLS.bind(ProvUIMessages.ProvUIMessages_SavedNotAccepted_EnterFor_0, location);
else
message = NLS.bind(ProvUIMessages.ProvUIMessages_NotAccepted_EnterFor_0, location);
UserValidationDialog dialog = new UserValidationDialog(previousInfo, shell,
ProvUIMessages.ServiceUI_LoginRequired, null, message);
int dialogCode = dialog.open();
if (dialogCode == Window.OK) {
result[0] = dialog.getResult();
} else if (dialogCode == Window.CANCEL) {
result[0] = AUTHENTICATION_PROMPT_CANCELED;
}
});
}
return result[0];
}
@Override
public void showInformationMessage(final String title, final String text, final String linkText) {
if (isHeadless()) {
super.showInformationMessage(title, text, linkText);
return;
}
PlatformUI.getWorkbench().getDisplay().syncExec(() -> {
MessageDialog dialog = new MessageDialogWithLink(ProvUI.getDefaultParentShell(), title, null, text,
MessageDialog.INFORMATION, new String[] { IDialogConstants.OK_LABEL }, 0, linkText);
dialog.open();
});
}
private boolean isHeadless() {
// If there is no UI available and we are still the IServiceUI,
// assume that the operation should proceed. See
// https://bugs.eclipse.org/bugs/show_bug.cgi?id=291049
return !PlatformUI.isWorkbenchRunning();
}
}