1 /* Copyright (C) 2013 Red Hat, Inc. 2 3 This file is part of IcedTea. 4 5 IcedTea is free software; you can redistribute it and/or 6 modify it under the terms of the GNU General Public License as published by 7 the Free Software Foundation, version 2. 8 9 IcedTea is distributed in the hope that it will be useful, 10 but WITHOUT ANY WARRANTY; without even the implied warranty of 11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 General Public License for more details. 13 14 You should have received a copy of the GNU General Public License 15 along with IcedTea; see the file COPYING. If not, write to 16 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 17 02110-1301 USA. 18 19 Linking this library statically or dynamically with other modules is 20 making a combined work based on this library. Thus, the terms and 21 conditions of the GNU General Public License cover the whole 22 combination. 23 24 As a special exception, the copyright holders of this library give you 25 permission to link this library with independent modules to produce an 26 executable, regardless of the license terms of these independent 27 modules, and to copy and distribute the resulting executable under 28 terms of your choice, provided that you also meet, for each linked 29 independent module, the terms and conditions of the license of that 30 module. An independent module is a module which is not derived from 31 or based on this library. If you modify this library, you may extend 32 this exception to your version of the library, but you are not 33 obligated to do so. If you do not wish to do so, delete this 34 exception statement from your version. 35 */ 36 37 package net.sourceforge.jnlp.security.dialogs.apptrustwarningpanel; 38 39 import static net.sourceforge.jnlp.runtime.Translator.R; 40 41 import java.awt.BorderLayout; 42 import java.awt.Color; 43 import java.awt.Desktop; 44 import java.awt.Dimension; 45 import java.awt.FlowLayout; 46 import java.awt.Font; 47 import java.awt.GridLayout; 48 import java.awt.event.ActionEvent; 49 import java.awt.event.ActionListener; 50 import java.io.IOException; 51 import java.net.URISyntaxException; 52 import java.util.ArrayList; 53 import java.util.List; 54 55 import javax.swing.BorderFactory; 56 import javax.swing.BoxLayout; 57 import javax.swing.ButtonGroup; 58 import javax.swing.ImageIcon; 59 import javax.swing.JButton; 60 import javax.swing.JCheckBox; 61 import javax.swing.JDialog; 62 import javax.swing.JEditorPane; 63 import javax.swing.JLabel; 64 import javax.swing.JPanel; 65 import javax.swing.JRadioButton; 66 import javax.swing.SwingConstants; 67 import javax.swing.event.HyperlinkEvent; 68 import javax.swing.event.HyperlinkListener; 69 70 import net.sourceforge.jnlp.JNLPFile; 71 import net.sourceforge.jnlp.PluginBridge; 72 import static net.sourceforge.jnlp.runtime.Translator.R; 73 import net.sourceforge.jnlp.security.appletextendedsecurity.ExecuteAppletAction; 74 import net.sourceforge.jnlp.security.appletextendedsecurity.ExtendedAppletSecurityHelp; 75 import net.sourceforge.jnlp.util.ScreenFinder; 76 import net.sourceforge.jnlp.util.logging.OutputController; 77 78 /* 79 * This class is meant to provide a common layout and functionality for warning dialogs 80 * that appear when the user needs to confirm the running of applets/applications. 81 * Subclasses include UnsignedAppletTrustWarningPanel, for unsigned plugin applets, and 82 * PartiallySignedAppTrustWarningPanel, for partially signed JNLP applications as well as 83 * plugin applets. New implementations should be added to the unit test at 84 * unit/net/sourceforge/jnlp/security/AppTrustWarningPanelTest 85 */ 86 public abstract class AppTrustWarningPanel extends JPanel { 87 88 /* 89 * Details of decided action. 90 */ 91 public static class AppSigningWarningAction { 92 private ExecuteAppletAction action; 93 private boolean applyToCodeBase; 94 AppSigningWarningAction(ExecuteAppletAction action, boolean applyToCodeBase)95 public AppSigningWarningAction(ExecuteAppletAction action, 96 boolean applyToCodeBase) { 97 this.action = action; 98 this.applyToCodeBase = applyToCodeBase; 99 } 100 getAction()101 public ExecuteAppletAction getAction() { 102 return action; 103 } 104 rememberForCodeBase()105 public boolean rememberForCodeBase() { 106 return applyToCodeBase; 107 } 108 } 109 110 /* 111 * Callback for when action is decided. 112 */ 113 public static interface ActionChoiceListener { actionChosen(AppSigningWarningAction action)114 void actionChosen(AppSigningWarningAction action); 115 } 116 117 protected int PANE_WIDTH = 500; 118 119 protected int TOP_PANEL_HEIGHT = 60; 120 protected int INFO_PANEL_HEIGHT = 160; 121 protected int INFO_PANEL_HINT_HEIGHT = 25; 122 protected int QUESTION_PANEL_HEIGHT = 35; 123 124 protected List<JButton> buttons; 125 protected JButton allowButton; 126 protected JButton rejectButton; 127 protected JButton helpButton; 128 protected JCheckBox permanencyCheckBox; 129 protected JRadioButton applyToAppletButton; 130 protected JRadioButton applyToCodeBaseButton; 131 132 protected JNLPFile file; 133 134 protected ActionChoiceListener actionChoiceListener; 135 136 /* 137 * Subclasses should call addComponents() IMMEDIATELY after calling the super() constructor! 138 */ AppTrustWarningPanel(JNLPFile file, ActionChoiceListener actionChoiceListener)139 public AppTrustWarningPanel(JNLPFile file, ActionChoiceListener actionChoiceListener) { 140 this.file = file; 141 this.actionChoiceListener = actionChoiceListener; 142 this.buttons = new ArrayList<JButton>(); 143 144 allowButton = new JButton(R("ButProceed")); 145 rejectButton = new JButton(R("ButCancel")); 146 helpButton = new JButton(R("APPEXTSECguiPanelHelpButton")); 147 148 allowButton.addActionListener(chosenActionSetter(ExecuteAppletAction.YES)); 149 rejectButton.addActionListener(chosenActionSetter(ExecuteAppletAction.NO)); 150 151 helpButton.addActionListener(getHelpButtonAction()); 152 153 buttons.add(allowButton); 154 buttons.add(rejectButton); 155 buttons.add(helpButton); 156 } 157 158 /* 159 * Provides an image to be displayed near the upper left corner of the dialog. 160 */ getInfoImage()161 protected abstract ImageIcon getInfoImage(); 162 163 /* 164 * Provides a short description of why the dialog is appearing. The message is expected to be HTML-formatted. 165 */ getTopPanelText()166 protected abstract String getTopPanelText(); 167 168 /* 169 * Provides in-depth information on why the dialog is appearing. The message is expected to be HTML-formatted. 170 */ getInfoPanelText()171 protected abstract String getInfoPanelText(); 172 173 /* 174 * This provides the text for the final prompt to the user. The message is expected to be HTML formatted. 175 * The user's action is a direct response to this question. 176 */ getQuestionPanelText()177 protected abstract String getQuestionPanelText(); 178 getAllowButton()179 public final JButton getAllowButton() { 180 return allowButton; 181 } 182 getRejectButton()183 public final JButton getRejectButton() { 184 return rejectButton; 185 } 186 getHelpButtonAction()187 protected ActionListener getHelpButtonAction() { 188 return new ActionListener() { 189 190 @Override 191 public void actionPerformed(ActionEvent e) { 192 JDialog d = new ExtendedAppletSecurityHelp(null, false, "dialogue"); 193 ScreenFinder.centerWindowsToCurrentScreen(d); 194 d.setVisible(true); 195 } 196 }; 197 } 198 199 protected static String htmlWrap(String text) { 200 return "<html>" + text + "</html>"; 201 } 202 203 private void setupTopPanel() { 204 final String topLabelText = getTopPanelText(); 205 206 JLabel topLabel = new JLabel(topLabelText, getInfoImage(), 207 SwingConstants.LEFT); 208 topLabel.setFont(new Font(topLabel.getFont().toString(), Font.BOLD, 12)); 209 210 JPanel topPanel = new JPanel(new BorderLayout()); 211 topPanel.setBackground(Color.WHITE); 212 topPanel.add(topLabel, BorderLayout.CENTER); 213 topPanel.setPreferredSize(new Dimension(PANE_WIDTH, TOP_PANEL_HEIGHT)); 214 topPanel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10)); 215 216 add(topPanel); 217 } 218 219 protected String getAppletTitle() { 220 String title; 221 try { 222 if (file instanceof PluginBridge) { 223 title = file.getTitle(); 224 } else { 225 title = file.getInformation().getTitle(); 226 } 227 } catch (Exception e) { 228 title = ""; 229 } 230 return title; 231 } 232 233 private void setupInfoPanel() { 234 JPanel infoPanel = new JPanel(new BorderLayout()); 235 String titleText = getAppletTitle(); 236 JLabel titleLabel = new JLabel(titleText); 237 titleLabel.setFont(new Font(titleLabel.getFont().getName(), Font.BOLD, 18)); 238 239 String infoLabelText = getInfoPanelText(); 240 JEditorPane infoLabel = new JEditorPane("text/html", htmlWrap(infoLabelText)); 241 infoLabel.setBackground(infoPanel.getBackground()); 242 infoLabel.setEditable(false); 243 infoLabel.addHyperlinkListener(new HyperlinkListener() { 244 @Override 245 public void hyperlinkUpdate(HyperlinkEvent e) { 246 try { 247 if (e.getEventType() == HyperlinkEvent.EventType.ACTIVATED) { 248 Desktop.getDesktop().browse(e.getURL().toURI()); 249 } 250 } catch (IOException ex) { 251 OutputController.getLogger().log(ex); 252 } catch (URISyntaxException ex) { 253 OutputController.getLogger().log(ex); 254 } 255 } 256 }); 257 258 int panelHeight = titleLabel.getHeight() + INFO_PANEL_HEIGHT + INFO_PANEL_HINT_HEIGHT; 259 infoPanel.add(titleLabel, BorderLayout.PAGE_START); 260 infoPanel.add(infoLabel, BorderLayout.CENTER); 261 infoPanel.setPreferredSize(new Dimension(PANE_WIDTH, panelHeight)); 262 infoPanel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10)); 263 264 add(infoPanel); 265 } 266 267 private void setupQuestionsPanel() { 268 JPanel questionPanel = new JPanel(new BorderLayout()); 269 270 final String questionPanelText = getQuestionPanelText(); 271 questionPanel.add(new JLabel(questionPanelText), BorderLayout.EAST); 272 273 questionPanel.setPreferredSize(new Dimension(PANE_WIDTH, QUESTION_PANEL_HEIGHT)); 274 questionPanel.setBorder(BorderFactory.createEmptyBorder(0, 10, 0, 10)); 275 276 add(questionPanel); 277 } 278 279 private JPanel createMatchOptionsPanel() { 280 JPanel matchOptionsPanel = new JPanel(new FlowLayout(FlowLayout.LEFT)); 281 282 ButtonGroup group = new ButtonGroup(); 283 applyToAppletButton = new JRadioButton(R("SRememberAppletOnly")); 284 applyToAppletButton.setSelected(true); 285 applyToAppletButton.setEnabled(false); // Start disabled until 'Remember this option' is selected 286 287 applyToCodeBaseButton = new JRadioButton(htmlWrap(R("SRememberCodebase", file.getCodeBase()))); 288 applyToCodeBaseButton.setEnabled(false); 289 290 group.add(applyToAppletButton); 291 group.add(applyToCodeBaseButton); 292 293 matchOptionsPanel.add(applyToAppletButton); 294 matchOptionsPanel.add(applyToCodeBaseButton); 295 296 return matchOptionsPanel; 297 } 298 299 private JPanel createCheckBoxPanel() { 300 JPanel checkBoxPanel = new JPanel(new BorderLayout()); 301 302 permanencyCheckBox = new JCheckBox(htmlWrap(R("SRememberOption"))); 303 permanencyCheckBox.addActionListener(permanencyListener()); 304 checkBoxPanel.setBorder(BorderFactory.createEmptyBorder(0, 15, 0, 0)); 305 checkBoxPanel.add(permanencyCheckBox, BorderLayout.SOUTH); 306 307 return checkBoxPanel; 308 } 309 310 private JPanel createButtonPanel() { 311 JPanel buttonPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT)); 312 313 for (final JButton button : buttons) { 314 buttonPanel.add(button); 315 } 316 317 buttonPanel.setBorder(BorderFactory.createEmptyBorder(0, 10, 10, 10)); 318 319 return buttonPanel; 320 } 321 322 // Set up 'Remember Option' checkbox & Proceed/Cancel buttons 323 private void setupButtonAndCheckBoxPanel() { 324 JPanel outerPanel = new JPanel(new BorderLayout()); 325 JPanel rememberPanel = new JPanel(new GridLayout(2 /*rows*/, 1 /*column*/)); 326 rememberPanel.add(createMatchOptionsPanel()); 327 rememberPanel.setBorder(BorderFactory.createEmptyBorder(0, 10, 0, 10)); 328 329 outerPanel.add(createCheckBoxPanel(), BorderLayout.WEST); 330 outerPanel.add(rememberPanel, BorderLayout.SOUTH); 331 outerPanel.add(createButtonPanel(), BorderLayout.EAST); 332 333 add(outerPanel); 334 } 335 336 /** 337 * Creates the actual GUI components, and adds it to this panel. This should be called by all subclasses 338 * IMMEDIATELY after calling the super() constructor! 339 */ 340 protected final void addComponents() { 341 setLayout(new BoxLayout(this, BoxLayout.Y_AXIS)); 342 343 setupTopPanel(); 344 setupInfoPanel(); 345 setupQuestionsPanel(); 346 setupButtonAndCheckBoxPanel(); 347 } 348 349 // Toggles whether 'match applet' or 'match codebase' options are greyed out 350 protected ActionListener permanencyListener() { 351 return new ActionListener() { 352 @Override 353 public void actionPerformed(ActionEvent e) { 354 applyToAppletButton.setEnabled(permanencyCheckBox.isSelected()); 355 applyToCodeBaseButton.setEnabled(permanencyCheckBox.isSelected()); 356 } 357 }; 358 } 359 360 protected ActionListener chosenActionSetter(final ExecuteAppletAction action) { 361 return new ActionListener() { 362 @Override 363 public void actionPerformed(ActionEvent e) { 364 ExecuteAppletAction realAction; 365 366 if (action == ExecuteAppletAction.YES) { 367 realAction = permanencyCheckBox.isSelected() ? ExecuteAppletAction.ALWAYS : ExecuteAppletAction.YES; 368 } else if (action == ExecuteAppletAction.NO) { 369 realAction = permanencyCheckBox.isSelected() ? ExecuteAppletAction.NEVER : ExecuteAppletAction.NO; 370 } else { 371 realAction = action; 372 } 373 374 boolean applyToCodeBase = applyToCodeBaseButton.isSelected(); 375 actionChoiceListener.actionChosen(new AppSigningWarningAction(realAction, applyToCodeBase)); 376 } 377 }; 378 } 379 } 380