diff options
Diffstat (limited to 'bundles/org.eclipse.equinox.p2.ui/src/org/eclipse/equinox/internal/p2/ui/dialogs/TrustCertificateDialog.java')
-rw-r--r-- | bundles/org.eclipse.equinox.p2.ui/src/org/eclipse/equinox/internal/p2/ui/dialogs/TrustCertificateDialog.java | 829 |
1 files changed, 691 insertions, 138 deletions
diff --git a/bundles/org.eclipse.equinox.p2.ui/src/org/eclipse/equinox/internal/p2/ui/dialogs/TrustCertificateDialog.java b/bundles/org.eclipse.equinox.p2.ui/src/org/eclipse/equinox/internal/p2/ui/dialogs/TrustCertificateDialog.java index 8ee921daa..2d6cc8237 100644 --- a/bundles/org.eclipse.equinox.p2.ui/src/org/eclipse/equinox/internal/p2/ui/dialogs/TrustCertificateDialog.java +++ b/bundles/org.eclipse.equinox.p2.ui/src/org/eclipse/equinox/internal/p2/ui/dialogs/TrustCertificateDialog.java @@ -16,100 +16,256 @@ package org.eclipse.equinox.internal.p2.ui.dialogs; import static org.eclipse.swt.events.SelectionListener.widgetSelectedAdapter; -import java.security.cert.X509Certificate; -import java.util.ArrayList; -import java.util.Iterator; -import org.eclipse.equinox.internal.p2.ui.ProvUIMessages; +import java.io.*; +import java.security.cert.*; +import java.time.Instant; +import java.time.temporal.ChronoUnit; +import java.util.*; +import java.util.List; +import java.util.function.Function; +import java.util.stream.Collectors; +import org.bouncycastle.bcpg.ArmoredOutputStream; +import org.bouncycastle.openpgp.PGPPublicKey; +import org.eclipse.core.runtime.*; +import org.eclipse.equinox.internal.p2.ui.*; import org.eclipse.equinox.internal.p2.ui.viewers.CertificateLabelProvider; -import org.eclipse.equinox.internal.provisional.security.ui.X509CertificateViewDialog; +import org.eclipse.equinox.internal.provisional.security.ui.X500PrincipalHelper; +import org.eclipse.equinox.p2.metadata.IArtifactKey; +import org.eclipse.equinox.p2.repository.spi.PGPPublicKeyService; +import org.eclipse.jface.dialogs.*; import org.eclipse.jface.dialogs.Dialog; -import org.eclipse.jface.dialogs.IDialogConstants; +import org.eclipse.jface.layout.TableColumnLayout; +import org.eclipse.jface.resource.JFaceResources; +import org.eclipse.jface.util.Policy; import org.eclipse.jface.viewers.*; +import org.eclipse.jface.widgets.LabelFactory; +import org.eclipse.jface.widgets.WidgetFactory; +import org.eclipse.jface.window.Window; +import org.eclipse.osgi.util.NLS; import org.eclipse.swt.SWT; +import org.eclipse.swt.custom.SashForm; +import org.eclipse.swt.dnd.*; import org.eclipse.swt.events.SelectionEvent; import org.eclipse.swt.events.SelectionListener; -import org.eclipse.swt.layout.GridData; -import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.graphics.*; +import org.eclipse.swt.layout.*; import org.eclipse.swt.widgets.*; +import org.eclipse.ui.PlatformUI; import org.eclipse.ui.dialogs.SelectionDialog; /** - * A dialog that displays a certificate chain and asks the user if they - * trust the certificate providers. + * A dialog that displays a certificate chain and asks the user if they trust + * the certificate providers. It also supports prompting about unsigned content. */ public class TrustCertificateDialog extends SelectionDialog { + private static final String EXPORT_FILTER_PATH = "exportFilterPath"; //$NON-NLS-1$ - private Object inputElement; - private IStructuredContentProvider contentProvider; - private ILabelProvider labelProvider; + private CheckboxTableViewer certifcateViewer; - private final static int SIZING_SELECTION_WIDGET_HEIGHT = 250; - private final static int SIZING_SELECTION_WIDGET_WIDTH = 300; + private TreeViewer certificateChainViewer; - CheckboxTableViewer listViewer; + private CheckboxTableViewer artifactViewer; - private TreeViewer certificateChainViewer; private Button detailsButton; - protected TreeNode parentElement; - protected Object selectedCertificate; - public TrustCertificateDialog(Shell parentShell, Object input, ILabelProvider labelProvider, ITreeContentProvider contentProvider) { + private Button exportButton; + + private boolean rememberSelectedSigners = true; + + private boolean trustAlways; + + private final Map<TreeNode, List<IArtifactKey>> artifactMap = new LinkedHashMap<>(); + + public TrustCertificateDialog(Shell parentShell, Object input) { super(parentShell); - inputElement = input; - this.contentProvider = contentProvider; - this.labelProvider = labelProvider; + + setShellStyle(SWT.DIALOG_TRIM | SWT.MODELESS | SWT.RESIZE | SWT.MAX | getDefaultOrientation()); + + if (input instanceof TreeNode[]) { + for (TreeNode node : (TreeNode[]) input) { + IArtifactKey[] associatedArtifacts = null; + if (node instanceof IAdaptable) { + associatedArtifacts = ((IAdaptable) node).getAdapter(IArtifactKey[].class); + } + artifactMap.put(node, associatedArtifacts == null ? List.of() : Arrays.asList(associatedArtifacts)); + } + } + setTitle(ProvUIMessages.TrustCertificateDialog_Title); - setMessage(ProvUIMessages.TrustCertificateDialog_Message); - setShellStyle(SWT.DIALOG_TRIM | SWT.MODELESS | SWT.RESIZE | getDefaultOrientation()); + + boolean unsignedContent = artifactMap.keySet().stream().map(TreeNode::getValue).anyMatch(Objects::isNull); + boolean pgpContent = containsInstance(input, PGPPublicKey.class); + boolean certifcateContent = containsInstance(input, Certificate.class); + + List<String> messages = new ArrayList<>(); + if (certifcateContent || pgpContent) { + messages.add(ProvUIMessages.TrustCertificateDialog_Message); + } + if (unsignedContent) { + messages.add(ProvUIMessages.TrustCertificateDialog_MessageUnsigned); + } + if (certifcateContent || pgpContent) { + messages.add(ProvUIMessages.TrustCertificateDialog_MessageNameWarning); + } + if (pgpContent) { + messages.add(ProvUIMessages.TrustCertificateDialog_MessagePGP); + } + setMessage(String.join(" ", messages)); //$NON-NLS-1$ + + if (PlatformUI.isWorkbenchRunning()) { + PlatformUI.getWorkbench().getHelpSystem().setHelp(parentShell, IProvHelpContextIds.TRUST_DIALOG); + } + } + + public boolean isRememberSelectedSigners() { + return rememberSelectedSigners; + } + + public boolean isTrustAlways() { + return trustAlways; + } + + @Override + protected Label createMessageArea(Composite composite) { + // Ensure that the message supports wrapping for a long text message. + GridData data = new GridData(SWT.FILL, SWT.FILL, true, false); + data.widthHint = convertWidthInCharsToPixels(120); + LabelFactory factory = WidgetFactory.label(SWT.WRAP).font(composite.getFont()).layoutData(data); + if (getMessage() != null) { + factory.text(getMessage()); + } + return factory.create(composite); } @Override protected Control createDialogArea(Composite parent) { - Composite composite = createUpperDialogArea(parent); - certificateChainViewer = new TreeViewer(composite, SWT.BORDER); - GridLayout layout = new GridLayout(); - certificateChainViewer.getTree().setLayout(layout); - GridData data = new GridData(GridData.FILL_BOTH); - data.grabExcessHorizontalSpace = true; - certificateChainViewer.getTree().setLayoutData(data); - certificateChainViewer.setAutoExpandLevel(AbstractTreeViewer.ALL_LEVELS); - certificateChainViewer.setContentProvider(new TreeNodeContentProvider()); - certificateChainViewer.setLabelProvider(new CertificateLabelProvider()); - certificateChainViewer.addSelectionChangedListener(getChainSelectionListener()); - if (inputElement instanceof Object[]) { - ISelection selection = null; - Object[] nodes = (Object[]) inputElement; - if (nodes.length > 0) { - selection = new StructuredSelection(nodes[0]); - certificateChainViewer.setInput(new TreeNode[] {(TreeNode) nodes[0]}); - selectedCertificate = nodes[0]; - } - listViewer.setSelection(selection); - } - listViewer.addDoubleClickListener(getDoubleClickListener()); - listViewer.addSelectionChangedListener(getParentSelectionListener()); - createButtons(composite); - return composite; + Composite mainComposite = (Composite) super.createDialogArea(parent); + Dialog.applyDialogFont(mainComposite); + initializeDialogUnits(mainComposite); + + createMessageArea(mainComposite); + + SashForm sashForm = new SashForm(mainComposite, SWT.VERTICAL); + sashForm.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); + + createCertificateViewerArea(createSashFormArea(sashForm)); + + // Create this area only if we have keys or certificates. + boolean containsCertificates = containsInstance(artifactMap.keySet(), PGPPublicKey.class) + || containsInstance(artifactMap.keySet(), Certificate.class); + if (containsCertificates) { + createCertficateChainViewerArea(createSashFormArea(sashForm)); + } + + // Sort the set of all artifacts and create the lower area only if there are + // artifacts. + Comparator<Object> comparator = Policy.getComparator(); + Set<IArtifactKey> artifacts = artifactMap.values().stream().flatMap(Collection::stream) + .collect(Collectors.toCollection(() -> new TreeSet<>((a1, a2) -> { + int result = comparator.compare(a1.getId(), a2.getId()); + if (result == 0) { + result = a1.getVersion().compareTo(a2.getVersion()); + if (result == 0) { + result = a1.getClassifier().compareTo(a2.getClassifier()); + } + } + return result; + }))); + if (!artifacts.isEmpty()) { + crreateArtifactViewerArea(createSashFormArea(sashForm), artifacts); + } + + // Set weights based on the children's preferred size. + Control[] children = sashForm.getChildren(); + int[] weights = new int[children.length]; + for (int i = 0; i < children.length; ++i) { + weights[i] = children[i].computeSize(SWT.DEFAULT, SWT.DEFAULT, false).y; + } + sashForm.setWeights(weights); + + if (!getInitialElementSelections().isEmpty()) { + checkInitialSelections(); + } + + if (!artifactMap.isEmpty()) { + certifcateViewer.setSelection(new StructuredSelection(artifactMap.keySet().iterator().next())); + } + + return mainComposite; } @Override protected void createButtonsForButtonBar(Composite parent) { - createButton(parent, IDialogConstants.OK_ID, ProvUIMessages.TrustCertificateDialog_AcceptSelectedButtonLabel, true); + createButton(parent, IDialogConstants.OK_ID, ProvUIMessages.TrustCertificateDialog_AcceptSelectedButtonLabel, + true); createButton(parent, IDialogConstants.CANCEL_ID, IDialogConstants.CANCEL_LABEL, false); - super.getOkButton().setEnabled(false); + updateOkButton(); } private void createButtons(Composite composite) { - // Details button to view certificate chain - detailsButton = new Button(composite, SWT.NONE); + Composite buttonComposite = new Composite(composite, SWT.NONE); + buttonComposite.setLayout(new RowLayout()); + + detailsButton = new Button(buttonComposite, SWT.NONE); detailsButton.setText(ProvUIMessages.TrustCertificateDialog_Details); detailsButton.addSelectionListener(new SelectionListener() { @Override public void widgetDefaultSelected(SelectionEvent e) { - if (selectedCertificate != null) { - X509Certificate cert = (X509Certificate) ((TreeNode) selectedCertificate).getValue(); - X509CertificateViewDialog certificateDialog = new X509CertificateViewDialog(getShell(), cert); - certificateDialog.open(); + X509Certificate cert = getInstance(certificateChainViewer.getSelection(), X509Certificate.class); + if (cert != null) { + CertificateLabelProvider.openDialog(getShell(), cert); + } + } + + @Override + public void widgetSelected(SelectionEvent e) { + widgetDefaultSelected(e); + } + }); + + exportButton = new Button(buttonComposite, SWT.NONE); + exportButton.setText(ProvUIMessages.TrustCertificateDialog_Export); + exportButton.addSelectionListener(new SelectionListener() { + @Override + public void widgetDefaultSelected(SelectionEvent e) { + ISelection selection = certificateChainViewer.getSelection(); + X509Certificate cert = getInstance(selection, X509Certificate.class); + PGPPublicKey key = getInstance(selection, PGPPublicKey.class); + if (cert != null || key != null) { + FileDialog destination = new FileDialog(exportButton.getShell(), SWT.SAVE); + destination.setFilterPath(getFilterPath(EXPORT_FILTER_PATH)); + destination.setText(ProvUIMessages.TrustCertificateDialog_Export); + if (cert != null) { + destination.setFilterExtensions(new String[] { "*.der" }); //$NON-NLS-1$ + destination.setFileName(cert.getSerialNumber().toString() + ".der"); //$NON-NLS-1$ + String path = destination.open(); + setFilterPath(EXPORT_FILTER_PATH, destination.getFilterPath()); + if (path == null) { + return; + } + File destinationFile = new File(path); + try (FileOutputStream output = new FileOutputStream(destinationFile)) { + output.write(cert.getEncoded()); + } catch (IOException | CertificateEncodingException ex) { + ProvUIActivator.getDefault().getLog() + .log(new Status(IStatus.ERROR, ProvUIActivator.PLUGIN_ID, ex.getMessage(), ex)); + } + } else { + destination.setFilterExtensions(new String[] { "*.asc" }); //$NON-NLS-1$ + destination.setFileName(userFriendlyFingerPrint(key) + ".asc"); //$NON-NLS-1$ + String path = destination.open(); + setFilterPath(EXPORT_FILTER_PATH, destination.getFilterPath()); + if (path == null) { + return; + } + File destinationFile = new File(path); + try (OutputStream output = new ArmoredOutputStream(new FileOutputStream(destinationFile))) { + key.encode(output); + } catch (IOException ex) { + ProvUIActivator.getDefault().getLog() + .log(new Status(IStatus.ERROR, ProvUIActivator.PLUGIN_ID, ex.getMessage(), ex)); + } + } } } @@ -120,31 +276,274 @@ public class TrustCertificateDialog extends SelectionDialog { }); } - private Composite createUpperDialogArea(Composite parent) { - Composite composite = (Composite) super.createDialogArea(parent); - initializeDialogUnits(composite); - createMessageArea(composite); + private Composite createSashFormArea(SashForm sashForm) { + Composite composite = new Composite(sashForm, SWT.NONE); + GridLayout layout = new GridLayout(); + layout.marginHeight = 0; + layout.marginWidth = 0; + layout.verticalSpacing = convertVerticalDLUsToPixels(IDialogConstants.VERTICAL_SPACING); + layout.horizontalSpacing = convertHorizontalDLUsToPixels(IDialogConstants.HORIZONTAL_SPACING); + composite.setLayout(layout); + return composite; + } - listViewer = CheckboxTableViewer.newCheckList(composite, SWT.BORDER); - GridData data = new GridData(GridData.FILL_BOTH); - data.heightHint = SIZING_SELECTION_WIDGET_HEIGHT; - data.widthHint = SIZING_SELECTION_WIDGET_WIDTH; - listViewer.getTable().setLayoutData(data); + private void createCertificateViewerArea(Composite composite) { - listViewer.setLabelProvider(labelProvider); - listViewer.setContentProvider(contentProvider); + TableColumnLayout tableColumnLayout = new TableColumnLayout(true); + Composite tableComposite = WidgetFactory.composite(SWT.NONE) + .layoutData(new GridData(SWT.FILL, SWT.FILL, true, true)).layout(tableColumnLayout).create(composite); + Table table = WidgetFactory + .table(SWT.H_SCROLL | SWT.V_SCROLL | SWT.MULTI | SWT.BORDER | SWT.FULL_SELECTION | SWT.CHECK) + .headerVisible(true).linesVisible(true).font(composite.getFont()).create(tableComposite); + certifcateViewer = new CheckboxTableViewer(table); + certifcateViewer.setContentProvider(new TreeNodeContentProvider()); + + GridData data = new GridData(GridData.FILL_BOTH); + data.heightHint = convertHeightInCharsToPixels(Math.min(artifactMap.keySet().size() + 1, 6)) * 3 / 2; + data.widthHint = convertWidthInCharsToPixels(120); + tableComposite.setLayoutData(data); + + // This column is packed later. + TableViewerColumn typeColumn = createColumn(certifcateViewer, ProvUIMessages.TrustCertificateDialog_ObjectType, + new PGPOrX509ColumnLabelProvider(key -> "PGP", cert -> "x509", //$NON-NLS-1$ //$NON-NLS-2$ + ProvUIMessages.TrustCertificateDialog_Unsigned), + tableColumnLayout, 1); + + createColumn(certifcateViewer, ProvUIMessages.TrustCertificateDialog_Id, + new PGPOrX509ColumnLabelProvider(TrustCertificateDialog::userFriendlyFingerPrint, + cert -> cert.getSerialNumber().toString(), ProvUIMessages.TrustCertificateDialog_NotApplicable), + tableColumnLayout, 10); + + createColumn(certifcateViewer, ProvUIMessages.TrustCertificateDialog_Name, + new PGPOrX509ColumnLabelProvider(pgp -> { + java.util.List<String> users = new ArrayList<>(); + pgp.getUserIDs().forEachRemaining(users::add); + return String.join(", ", users); //$NON-NLS-1$ + }, x509 -> { + X500PrincipalHelper principalHelper = new X500PrincipalHelper(x509.getSubjectX500Principal()); + return principalHelper.getCN() + "; " + principalHelper.getOU() + "; " //$NON-NLS-1$ //$NON-NLS-2$ + + principalHelper.getO(); + }, ProvUIMessages.TrustCertificateDialog_Unknown), tableColumnLayout, 15); + + createColumn(certifcateViewer, ProvUIMessages.TrustCertificateDialog_dates, + new PGPOrX509ColumnLabelProvider(pgp -> { + if (pgp.getCreationTime().after(Date.from(Instant.now()))) { + return NLS.bind(ProvUIMessages.TrustCertificateDialog_NotYetValidStartDate, + pgp.getCreationTime()); + } + long validSeconds = pgp.getValidSeconds(); + if (validSeconds == 0) { + return ProvUIMessages.TrustCertificateDialog_valid; + } + Instant expires = pgp.getCreationTime().toInstant().plus(validSeconds, ChronoUnit.SECONDS); + return expires.isBefore(Instant.now()) + ? NLS.bind(ProvUIMessages.TrustCertificateDialog_expiredSince, expires) + : NLS.bind(ProvUIMessages.TrustCertificateDialog_validExpires, expires); + }, x509 -> { + try { + x509.checkValidity(); + return ProvUIMessages.TrustCertificateDialog_valid; + } catch (CertificateExpiredException expired) { + return ProvUIMessages.TrustCertificateDialog_expired; + } catch (CertificateNotYetValidException notYetValid) { + return ProvUIMessages.TrustCertificateDialog_notYetValid; + } + }, ProvUIMessages.TrustCertificateDialog_NotApplicable), tableColumnLayout, 10); + + createMenu(certifcateViewer); addSelectionButtons(composite); - listViewer.setInput(inputElement); + certifcateViewer.addDoubleClickListener(e -> { + StructuredSelection selection = (StructuredSelection) e.getSelection(); + X509Certificate cert = getInstance(selection, X509Certificate.class); + if (cert != null) { + // create and open dialog for certificate chain + CertificateLabelProvider.openDialog(getShell(), cert); + } + }); - if (!getInitialElementSelections().isEmpty()) { - checkInitialSelections(); + certifcateViewer.addSelectionChangedListener(e -> { + if (certificateChainViewer != null) { + TreeNode treeNode = getInstance(e.getSelection(), TreeNode.class); + if (treeNode != null) { + certificateChainViewer.setInput(new TreeNode[] { treeNode }); + certificateChainViewer.setSelection(new StructuredSelection(treeNode)); + } else { + certificateChainViewer.setInput(new TreeNode[] {}); + } + } + + updateOkButton(); + }); + + certifcateViewer.setInput(artifactMap.keySet().toArray(TreeNode[]::new)); + + typeColumn.getColumn().pack(); + } + + private void createCertficateChainViewerArea(Composite composite) { + certificateChainViewer = new TreeViewer(composite, SWT.BORDER | SWT.V_SCROLL | SWT.H_SCROLL); + GridData data = new GridData(GridData.FILL_BOTH); + int treeSize = artifactMap.keySet().stream().map(TrustCertificateDialog::computeTreeSize) + .max(Integer::compareTo).orElse(0); + data.heightHint = convertHeightInCharsToPixels(Math.min(treeSize, 5)); + data.widthHint = convertWidthInCharsToPixels(120); + certificateChainViewer.getTree().setLayoutData(data); + + certificateChainViewer.setAutoExpandLevel(3); + certificateChainViewer.setContentProvider(new TreeNodeContentProvider()); + certificateChainViewer.setLabelProvider(new CertificateLabelProvider() { + @Override + public String getText(Object element) { + if (element instanceof TreeNode) { + Object o = ((TreeNode) element).getValue(); + if (o instanceof PGPPublicKey) { + PGPPublicKey key = (PGPPublicKey) o; + String userFriendlyFingerPrint = userFriendlyFingerPrint(key); + java.util.List<String> users = new ArrayList<>(); + key.getUserIDs().forEachRemaining(users::add); + String userIDs = String.join(", ", users); //$NON-NLS-1$ + if (!userIDs.isEmpty()) { + return userFriendlyFingerPrint + " [" + userIDs + "]"; //$NON-NLS-1$//$NON-NLS-2$ + } + return userFriendlyFingerPrint; + } else if (o == null) { + return ProvUIMessages.TrustCertificateDialog_Unsigned; + } + } + + return super.getText(element); + } + }); + + certificateChainViewer.addSelectionChangedListener(event -> { + ISelection selection = event.getSelection(); + boolean containsCertificate = containsInstance(selection, X509Certificate.class); + detailsButton.setEnabled(containsCertificate); + exportButton.setEnabled(containsCertificate || containsInstance(selection, PGPPublicKey.class)); + }); + + createMenu(certificateChainViewer); + + createButtons(composite); + } + + private void crreateArtifactViewerArea(Composite composite, Set<IArtifactKey> artifacts) { + TableColumnLayout tableColumnLayout = new TableColumnLayout(true); + Composite tableComposite = WidgetFactory.composite(SWT.NONE) + .layoutData(new GridData(SWT.FILL, SWT.FILL, true, true)).layout(tableColumnLayout).create(composite); + Table table = WidgetFactory.table(SWT.H_SCROLL | SWT.V_SCROLL | SWT.MULTI | SWT.BORDER | SWT.FULL_SELECTION) + .headerVisible(true).linesVisible(true).font(composite.getFont()).create(tableComposite); + artifactViewer = new CheckboxTableViewer(table); + artifactViewer.setContentProvider(ArrayContentProvider.getInstance()); + + GridData data = new GridData(GridData.FILL_BOTH); + data.heightHint = convertHeightInCharsToPixels(Math.min(artifacts.size() + 1, 10)) * 3 / 2; + data.widthHint = convertWidthInCharsToPixels(120); + tableComposite.setLayoutData(data); + + Font font = table.getFont(); + FontData[] fontDatas = font.getFontData(); + for (FontData fontData : fontDatas) { + fontData.setStyle(fontData.getStyle() | SWT.BOLD); } + Font boldFont = new Font(table.getDisplay(), fontDatas); + composite.addDisposeListener(e -> boldFont.dispose()); + + Function<IArtifactKey, Font> fontProvider = e -> { + for (Object object : certifcateViewer.getCheckedElements()) { + List<IArtifactKey> list = artifactMap.get(object); + if (list != null && list.contains(e)) { + return boldFont; + } + } + return null; + }; - Dialog.applyDialogFont(composite); + certifcateViewer.addCheckStateListener(e -> artifactViewer.refresh(true)); + + artifactViewer.addPostSelectionChangedListener(e -> { + if (table.isFocusControl()) { + List<?> selection = e.getStructuredSelection().toList(); + List<TreeNode> newSelection = new ArrayList<>(); + LOOP: for (Map.Entry<TreeNode, List<IArtifactKey>> entry : artifactMap.entrySet()) { + List<IArtifactKey> value = entry.getValue(); + if (value != null) { + for (IArtifactKey key : value) { + if (selection.contains(key)) { + newSelection.add(entry.getKey()); + continue LOOP; + } + } + } + } - return composite; + certifcateViewer.setSelection(new StructuredSelection(newSelection), true); + } + }); + + certifcateViewer.addPostSelectionChangedListener(e -> { + if (!table.isFocusControl()) { + Set<IArtifactKey> associatedArtifacts = new LinkedHashSet<>(); + for (Object object : e.getStructuredSelection()) { + List<IArtifactKey> list = artifactMap.get(object); + if (list != null) { + associatedArtifacts.addAll(list); + } + } + + artifactViewer.setSelection(new StructuredSelection(associatedArtifacts.toArray()), true); + + // Reorder the artifacts so that the selected ones are first. + LinkedHashSet<IArtifactKey> newInput = new LinkedHashSet<>(artifacts); + newInput.retainAll(associatedArtifacts); + newInput.addAll(artifacts); + artifactViewer.setInput(newInput); + + artifactViewer.setSelection(new StructuredSelection(associatedArtifacts.toArray()), true); + } + }); + + TableViewerColumn classifierColumn = createColumn(artifactViewer, + ProvUIMessages.TrustCertificateDialog_Classifier, + new ArtifactLabelProvider(a -> a.getClassifier(), fontProvider), tableColumnLayout, 1); + createColumn(artifactViewer, ProvUIMessages.TrustCertificateDialog_ArtifactId, + new ArtifactLabelProvider(a -> a.getId(), fontProvider), tableColumnLayout, 10); + createColumn(artifactViewer, ProvUIMessages.TrustCertificateDialog_Version, + new ArtifactLabelProvider(a -> a.getVersion().toString(), fontProvider), tableColumnLayout, 10); + + artifactViewer.setInput(artifacts); + + classifierColumn.getColumn().pack(); + } + + private void createMenu(StructuredViewer viewer) { + Control control = viewer.getControl(); + Menu menu = new Menu(control); + control.setMenu(menu); + MenuItem item = new MenuItem(menu, SWT.PUSH); + item.setText(ProvUIMessages.TrustCertificateDialog_CopyFingerprint); + item.addSelectionListener(widgetSelectedAdapter(e -> { + PGPPublicKey key = getInstance(viewer.getSelection(), PGPPublicKey.class); + if (key != null) { + Clipboard clipboard = new Clipboard(getShell().getDisplay()); + clipboard.setContents(new Object[] { userFriendlyFingerPrint(key) }, + new Transfer[] { TextTransfer.getInstance() }); + clipboard.dispose(); + } + })); + viewer.addSelectionChangedListener( + e -> item.setEnabled(containsInstance(e.getSelection(), PGPPublicKey.class))); + } + + private TableViewerColumn createColumn(TableViewer tableViewer, String text, ColumnLabelProvider labelProvider, + TableColumnLayout tableColumnLayout, int columnWeight) { + TableViewerColumn column = new TableViewerColumn(tableViewer, SWT.NONE); + column.getColumn().setText(text); + column.setLabelProvider(labelProvider); + tableColumnLayout.setColumnData(column.getColumn(), new ColumnWeightData(columnWeight)); + return column; } /** @@ -154,98 +553,252 @@ public class TrustCertificateDialog extends SelectionDialog { private void checkInitialSelections() { Iterator<?> itemsToCheck = getInitialElementSelections().iterator(); while (itemsToCheck.hasNext()) { - listViewer.setChecked(itemsToCheck.next(), true); + certifcateViewer.setChecked(itemsToCheck.next(), true); + if (artifactViewer != null) { + artifactViewer.refresh(true); + } } } /** * Add the selection and deselection buttons to the dialog. + * * @param composite org.eclipse.swt.widgets.Composite */ private void addSelectionButtons(Composite composite) { - Composite buttonComposite = new Composite(composite, SWT.NONE); - GridLayout layout = new GridLayout(); - layout.numColumns = 0; - layout.marginWidth = 0; - layout.horizontalSpacing = convertHorizontalDLUsToPixels(IDialogConstants.HORIZONTAL_SPACING); - buttonComposite.setLayout(layout); - buttonComposite.setLayoutData(new GridData(SWT.END, SWT.TOP, true, false)); + int horizontalSpacing = convertHorizontalDLUsToPixels(IDialogConstants.HORIZONTAL_SPACING); + + Composite buttonArea = new Composite(composite, SWT.NONE); + GridLayout buttonAreaLayout = new GridLayout(); + buttonAreaLayout.numColumns = 2; + buttonAreaLayout.marginWidth = 0; + buttonAreaLayout.horizontalSpacing = horizontalSpacing; + buttonArea.setLayout(buttonAreaLayout); + buttonArea.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false)); + + Composite leftButtonArea = new Composite(buttonArea, SWT.NONE); + GridLayout leftButtonAreaLayout = new GridLayout(); + leftButtonAreaLayout.numColumns = 0; + leftButtonAreaLayout.marginWidth = 0; + leftButtonAreaLayout.horizontalSpacing = horizontalSpacing; + leftButtonArea.setLayout(leftButtonAreaLayout); + leftButtonArea.setLayoutData(new GridData(SWT.BEGINNING, SWT.CENTER, true, false)); + + if (containsInstance(artifactMap.keySet(), PGPPublicKey.class) + || containsInstance(artifactMap.keySet(), Certificate.class)) { + Button rememberSelectionButton = createCheckButton(leftButtonArea, + ProvUIMessages.TrustCertificateDialog_RememberSigners); + rememberSelectionButton.setSelection(rememberSelectedSigners); + rememberSelectionButton.addSelectionListener(widgetSelectedAdapter(e -> { + rememberSelectedSigners = rememberSelectionButton.getSelection(); + })); + } - Button selectButton = createButton(buttonComposite, IDialogConstants.SELECT_ALL_ID, ProvUIMessages.TrustCertificateDialog_SelectAll, false); + Button trustAlwaysButton = createCheckButton(leftButtonArea, ProvUIMessages.TrustCertificateDialog_AlwaysTrust); + trustAlwaysButton.addSelectionListener(widgetSelectedAdapter(e -> { + if (trustAlwaysButton.getSelection()) { + // Prompt the user to ensure they really understand what they've chosen, the + // risk, and where the preference is stored if they wish to change it in the + // future. Also ensure that the default button is no so that they must + // explicitly click the yes button, not just hit enter. + MessageDialog messageDialog = new MessageDialog(getShell(), + ProvUIMessages.TrustCertificateDialog_AlwaysTrustConfirmationTitle, null, + ProvUIMessages.TrustCertificateDialog_AlwaysTrustConfirmationMessage, MessageDialog.QUESTION, + new String[] { ProvUIMessages.TrustCertificateDialog_AlwaysTrustYes, + ProvUIMessages.TrustCertificateDialog_AlwaysTrustNo }, + 1) { + @Override + public Image getImage() { + return getWarningImage(); + } + }; + int result = messageDialog.open(); + if (result != Window.OK) { + // Restore the checkbox state. + trustAlwaysButton.setSelection(false); + } else { + certifcateViewer.setAllChecked(true); + if (artifactViewer != null) { + artifactViewer.refresh(true); + } + updateOkButton(); + } + } + trustAlways = trustAlwaysButton.getSelection(); + })); + + Composite rightButtonArea = new Composite(buttonArea, SWT.NONE); + GridLayout rightButtonAreaLayout = new GridLayout(); + rightButtonAreaLayout.numColumns = 0; + rightButtonAreaLayout.marginWidth = 0; + rightButtonAreaLayout.horizontalSpacing = horizontalSpacing; + rightButtonArea.setLayout(rightButtonAreaLayout); + rightButtonArea.setLayoutData(new GridData(SWT.END, SWT.TOP, true, false)); + + Button selectButton = createButton(rightButtonArea, IDialogConstants.SELECT_ALL_ID, + ProvUIMessages.TrustCertificateDialog_SelectAll, false); + selectButton.addSelectionListener(widgetSelectedAdapter(e -> { + certifcateViewer.setAllChecked(true); + if (artifactViewer != null) { + artifactViewer.refresh(true); + } + updateOkButton(); + })); + + Button deselectButton = createButton(rightButtonArea, IDialogConstants.DESELECT_ALL_ID, + ProvUIMessages.TrustCertificateDialog_DeselectAll, false); + deselectButton.addSelectionListener(widgetSelectedAdapter(e -> { + certifcateViewer.setAllChecked(false); + if (artifactViewer != null) { + artifactViewer.refresh(true); + } + updateOkButton(); + })); + } - SelectionListener listener = widgetSelectedAdapter(e -> { - listViewer.setAllChecked(true); - getOkButton().setEnabled(true); - }); - selectButton.addSelectionListener(listener); + protected Button createCheckButton(Composite parent, String label) { + ((GridLayout) parent.getLayout()).numColumns++; + Button button = WidgetFactory.button(SWT.CHECK).text(label).font(JFaceResources.getDialogFont()).create(parent); + setButtonLayoutData(button); + return button; + } - Button deselectButton = createButton(buttonComposite, IDialogConstants.DESELECT_ALL_ID, ProvUIMessages.TrustCertificateDialog_DeselectAll, false); + private String getFilterPath(String key) { + IDialogSettings dialogSettings = DialogSettings + .getOrCreateSection(ProvUIActivator.getDefault().getDialogSettings(), getClass().getName()); + String filterPath = dialogSettings.get(key); + if (filterPath == null) { + filterPath = System.getProperty("user.home"); //$NON-NLS-1$ + } + return filterPath; + } - listener = widgetSelectedAdapter(e -> { - listViewer.setAllChecked(false); - getOkButton().setEnabled(false); - }); - deselectButton.addSelectionListener(listener); + private void setFilterPath(String key, String filterPath) { + if (filterPath != null) { + IDialogSettings dialogSettings = DialogSettings + .getOrCreateSection(ProvUIActivator.getDefault().getDialogSettings(), getClass().getName()); + dialogSettings.put(key, filterPath); + } } - private ISelectionChangedListener getChainSelectionListener() { - return event -> { - ISelection selection = event.getSelection(); - if (selection instanceof StructuredSelection) { - selectedCertificate = ((StructuredSelection) selection).getFirstElement(); + private void updateOkButton() { + Button okButton = getOkButton(); + if (okButton != null) { + certifcateViewer.getCheckedElements(); + Object[] checkedElements = certifcateViewer.getCheckedElements(); + Set<IArtifactKey> artifacts = artifactMap.values().stream().flatMap(Collection::stream) + .collect(Collectors.toSet()); + if (artifacts.isEmpty()) { + okButton.setEnabled(checkedElements.length > 0); + } else { + for (Object checkElement : checkedElements) { + artifacts.removeAll(artifactMap.get(checkElement)); + } + okButton.setEnabled(artifacts.isEmpty()); } - }; + } } - public TreeViewer getCertificateChainViewer() { - return certificateChainViewer; + @Override + protected void okPressed() { + setResult(Arrays.asList(certifcateViewer.getCheckedElements())); + super.okPressed(); } - private IDoubleClickListener getDoubleClickListener() { - return event -> { - StructuredSelection selection = (StructuredSelection) event.getSelection(); - Object selectedElement = selection.getFirstElement(); - if (selectedElement instanceof TreeNode) { - TreeNode treeNode = (TreeNode) selectedElement; - // create and open dialog for certificate chain - X509CertificateViewDialog certificateViewDialog = new X509CertificateViewDialog(getShell(), (X509Certificate) treeNode.getValue()); - certificateViewDialog.open(); + private static class PGPOrX509ColumnLabelProvider extends ColumnLabelProvider { + private Function<PGPPublicKey, String> pgpMap; + private Function<X509Certificate, String> x509map; + private String unsignedValue; + + public PGPOrX509ColumnLabelProvider(Function<PGPPublicKey, String> pgpMap, + Function<X509Certificate, String> x509map, String unsignedValue) { + this.pgpMap = pgpMap; + this.x509map = x509map; + this.unsignedValue = unsignedValue; + } + + @Override + public String getText(Object element) { + if (element instanceof TreeNode) { + element = ((TreeNode) element).getValue(); + } + if (element instanceof PGPPublicKey) { + return pgpMap.apply((PGPPublicKey) element); + } + if (element instanceof X509Certificate) { + return x509map.apply((X509Certificate) element); } - }; - } - private ISelectionChangedListener getParentSelectionListener() { - return event -> { - ISelection selection = event.getSelection(); - if (selection instanceof StructuredSelection) { - TreeNode firstElement = (TreeNode) ((StructuredSelection) selection).getFirstElement(); - getCertificateChainViewer().setInput(new TreeNode[] {firstElement}); - getOkButton().setEnabled(listViewer.getChecked(firstElement)); - getCertificateChainViewer().refresh(); + if (element == null) { + return unsignedValue; } - }; + return super.getText(element); + } } - /** - * The <code>ListSelectionDialog</code> implementation of this - * <code>Dialog</code> method builds a list of the selected elements for later - * retrieval by the client and closes this dialog. - */ - @Override - protected void okPressed() { - // Get the input children. - Object[] children = contentProvider.getElements(inputElement); + private static class ArtifactLabelProvider extends ColumnLabelProvider { + private Function<IArtifactKey, String> labelProvider; + private Function<IArtifactKey, Font> fontProvider; - // Build a list of selected children. + public ArtifactLabelProvider(Function<IArtifactKey, String> labelProvider, + Function<IArtifactKey, Font> fontProvider) { + this.labelProvider = labelProvider; + this.fontProvider = fontProvider; + } + + @Override + public String getText(Object element) { + return labelProvider.apply((IArtifactKey) element); + } + + @Override + public Font getFont(Object element) { + return fontProvider.apply((IArtifactKey) element); + } + } + + private static int computeTreeSize(TreeNode node) { + int count = 1; + TreeNode[] children = node.getChildren(); if (children != null) { - ArrayList<Object> list = new ArrayList<>(); - for (Object element : children) { - if (listViewer.getChecked(element)) { - list.add(element); + for (TreeNode child : children) { + count += computeTreeSize(child); + } + } + return count; + } + + private static <T> T getInstance(Object element, Class<T> type) { + if (type.isInstance(element)) { + return type.cast(element); + } else if (element instanceof Iterable) { + for (Object object : ((Iterable<?>) element)) { + T instance = getInstance(object, type); + if (instance != null) { + return instance; + } + } + } else if (element instanceof TreeNode) { + return getInstance(((TreeNode) element).getValue(), type); + } else if (element instanceof TreeNode[]) { + for (TreeNode child : (TreeNode[]) element) { + T instance = getInstance(child, type); + if (instance != null) { + return instance; } } - setResult(list); } - super.okPressed(); + return null; + } + + private static boolean containsInstance(Object element, Class<?> type) { + return getInstance(element, type) != null; + } + + private static String userFriendlyFingerPrint(PGPPublicKey key) { + if (key == null) { + return null; + } + return PGPPublicKeyService.toHexFingerprint(key); } } |