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