1 /*Copyright (C) 2014 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.policyeditor;
38 
39 import static net.sourceforge.jnlp.runtime.Translator.R;
40 
41 import java.awt.Color;
42 import java.awt.Container;
43 import java.awt.Dialog.ModalityType;
44 import java.awt.GridBagConstraints;
45 import java.awt.GridBagLayout;
46 import java.awt.Window;
47 import java.awt.datatransfer.UnsupportedFlavorException;
48 import java.awt.event.ActionEvent;
49 import java.awt.event.ActionListener;
50 import java.awt.event.KeyEvent;
51 import java.awt.event.KeyAdapter;
52 import java.awt.event.MouseAdapter;
53 import java.awt.event.MouseEvent;
54 import java.awt.event.WindowAdapter;
55 import java.awt.event.WindowEvent;
56 import java.io.File;
57 import java.io.FileNotFoundException;
58 import java.io.IOException;
59 import java.lang.ref.WeakReference;
60 import java.lang.reflect.InvocationTargetException;
61 import java.net.MalformedURLException;
62 import java.net.URISyntaxException;
63 import java.net.URI;
64 import java.net.URL;
65 import java.util.ArrayList;
66 import java.util.Collection;
67 import java.util.LinkedList;
68 import java.util.List;
69 import java.util.Map;
70 import java.util.Set;
71 import java.util.TreeMap;
72 
73 import javax.swing.AbstractAction;
74 import javax.swing.AbstractButton;
75 import javax.swing.Action;
76 import javax.swing.ActionMap;
77 import javax.swing.DefaultListModel;
78 import javax.swing.InputMap;
79 import javax.swing.JButton;
80 import javax.swing.JCheckBox;
81 import javax.swing.JDialog;
82 import javax.swing.JFileChooser;
83 import javax.swing.JFrame;
84 import javax.swing.JLabel;
85 import javax.swing.JList;
86 import javax.swing.JMenu;
87 import javax.swing.JMenuBar;
88 import javax.swing.JMenuItem;
89 import javax.swing.JOptionPane;
90 import javax.swing.JPanel;
91 import javax.swing.JScrollPane;
92 import javax.swing.KeyStroke;
93 import javax.swing.ListSelectionModel;
94 import javax.swing.ScrollPaneConstants;
95 import javax.swing.SwingUtilities;
96 import javax.swing.SwingWorker;
97 import javax.swing.UIManager;
98 import javax.swing.WindowConstants;
99 import javax.swing.border.EmptyBorder;
100 import javax.swing.border.LineBorder;
101 import javax.swing.event.ListSelectionEvent;
102 import javax.swing.event.ListSelectionListener;
103 import net.sourceforge.jnlp.about.AboutDialog;
104 import net.sourceforge.jnlp.OptionsDefinitions;
105 import net.sourceforge.jnlp.config.PathsAndFiles;
106 import net.sourceforge.jnlp.runtime.JNLPRuntime;
107 
108 import net.sourceforge.jnlp.security.policyeditor.PolicyEditorPermissions.Group;
109 import net.sourceforge.jnlp.util.FileUtils;
110 import net.sourceforge.jnlp.util.FileUtils.OpenFileResult;
111 import net.sourceforge.jnlp.util.docprovider.PolicyEditorTextsProvider;
112 import net.sourceforge.jnlp.util.docprovider.TextsProvider;
113 import net.sourceforge.jnlp.util.docprovider.formatters.formatters.PlainTextFormatter;
114 import net.sourceforge.jnlp.util.logging.OutputController;
115 import net.sourceforge.jnlp.util.optionparser.OptionParser;
116 
117 /**
118  * This class provides a policy editing tool as a simpler alternate to
119  * the JDK PolicyTool. It is much simpler than PolicyTool - only
120  * a handful of pre-defined permissions can be enabled or disabled,
121  * on a per-codebase basis. There are no considerations for Principals,
122  * who signed the code, or custom permissions.
123  *
124  * This editor has a very simple idea of a policy file's contents. If any
125  * entries are found which it does not recognize, eg 'grant' blocks which
126  * have more than zero or one simple codeBase attributes, or 'Principal'
127  * or other attributes assigned to the "grant block", or any other type
128  * of complication to a "grant block" beyond a single codebase,
129  * then all of these pieces of data are disregarded. When the editor saves
130  * its work, all of this unrecognized data will be overwritten. Since
131  * the editor has no way to display any of these contents anyway, it would
132  * be potentially dangerous to allow this information to persist in the
133  * policy file even after it has been edited and saved, as this would mean
134  * the policy file contents may not be what the user thinks they are.
135  *
136  * Comments in policy files are loosely supported, using both block-style
137  * comment delimiters and double slashes. Block comments may not, however,
138  * be placed on a line with "functional" text on the same line. To be
139  * safe, comments should not be adjacent to "functional" text in the file
140  * unless those lines are intended to be disregarded, ie commented out.
141  * Comments will *not* be preserved when PolicyEditor next saves to the
142  * file.
143  */
144 public class PolicyEditor extends JPanel {
145 
146     /**
147      * Command line switch to print a help message.
148      */
149     public static final String HELP_FLAG = OptionsDefinitions.OPTIONS.HELP1.option;
150 
151     /**
152      * Command line switch to specify the location of the policy file.
153      * If not given, then the default DeploymentConfiguration path is used.
154      */
155     public static final String FILE_FLAG = OptionsDefinitions.OPTIONS.FILE.option;
156 
157     /**
158      * Command line switch to specify a new codebase entry to be made.
159      * Can only be used once, presently.
160      */
161     public static final String CODEBASE_FLAG = OptionsDefinitions.OPTIONS.CODEBASE.option;
162 
163 
164       private boolean closed = false;
165     private final Map<PolicyEditorPermissions, JCheckBox> checkboxMap = new TreeMap<>();
166     private final List<JCheckBoxWithGroup> groupBoxList = new ArrayList<>(Group.values().length);
167     private final JScrollPane scrollPane = new JScrollPane();
168     private final DefaultListModel<String> listModel = new DefaultListModel<>();
169     private final JList<String> list = new JList<>(listModel);
170     private final JButton okButton = new JButton(), closeButton = new JButton(),
171             addCodebaseButton = new JButton(), removeCodebaseButton = new JButton();
172     private final JFileChooser fileChooser;
173     private CustomPolicyViewer cpViewer = null;
174     /**
175      * See showChangesSavedDialog/showCouldNotSaveDialog. This weak reference is needed because
176      * there is a modal child dialog which can sometimes appear after the editor has been closed
177      * and disposed. In this case, its parent should be set to 'null', but otherwise the parent
178      * should be the editor so that the dialog is modal.
179      */
180     private final WeakReference<PolicyEditor> parentPolicyEditor = new WeakReference<>(this);
181     public final PolicyEditorController policyEditorController = new PolicyEditorController();
182 
183     private final ActionListener okButtonAction, addCodebaseButtonAction,
184             removeCodebaseButtonAction, newButtonAction, openButtonAction, openDefaultButtonAction, saveAsButtonAction, viewCustomButtonAction,
185             renameCodebaseButtonAction, copyCodebaseButtonAction, pasteCodebaseButtonAction,
186             policyEditorHelpButtonAction, aboutPolicyEditorButtonAction, aboutItwButtonAction;
187     private final ActionListener closeButtonAction;
188 
189     private static class JCheckBoxWithGroup extends JCheckBox {
190 
191         private final PolicyEditorPermissions.Group group;
192 
JCheckBoxWithGroup(Group group)193         private JCheckBoxWithGroup(Group group) {
194             super(group.getTitle());
195             this.group = group;
196         }
197 
getGroup()198         public Group getGroup() {
199             return group;
200         }
201 
setState(final Map<PolicyEditorPermissions, Boolean> map)202         private void setState(final Map<PolicyEditorPermissions, Boolean> map) {
203             final List<ActionListener> backup = new LinkedList<>();
204             for (final ActionListener l : this.getActionListeners()) {
205                 backup.add(l);
206                 this.removeActionListener(l);
207             }
208             final int i = group.getState(map);
209             this.setBackground(getParent().getBackground());
210             if (i > 0) {
211                 this.setSelected(true);
212             }
213             if (i < 0) {
214                 this.setSelected(false);
215             }
216             if (i == 0) {
217                 this.setBackground(Color.yellow);
218                 this.setSelected(false);
219             }
220 
221             for (final ActionListener al : backup) {
222                 this.addActionListener(al);
223             }
224         }
225     }
226 
PolicyEditor(final String filepath)227     public PolicyEditor(final String filepath) {
228         super();
229         setLayout(new GridBagLayout());
230 
231         for (final PolicyEditorPermissions perm : PolicyEditorPermissions.values()) {
232             final JCheckBox box = new JCheckBox();
233             box.setText(perm.getName());
234             box.setToolTipText(perm.getDescription());
235             checkboxMap.put(perm, box);
236         }
237 
238         setFile(filepath);
239         if (filepath != null) {
240             try {
241                 policyEditorController.openAndParsePolicyFile();
242             } catch (final FileNotFoundException fnfe) {
243                 OutputController.getLogger().log(fnfe);
244                 FileUtils.showCouldNotOpenDialog(PolicyEditor.this, R("PECouldNotOpen"));
245             } catch (final IOException | InvalidPolicyException e) {
246                 OutputController.getLogger().log(e);
247                 OutputController.getLogger().log(OutputController.Level.ERROR_ALL, R("RCantOpenFile", policyEditorController.getFile().getPath()));
248                 FileUtils.showCouldNotOpenDialog(PolicyEditor.this, R("PECouldNotOpen"));
249             }
250             try {
251                 invokeRunnableOrEnqueueAndWait(new Runnable() {
252                     @Override
253                     public void run() {
254                         for (final String codebase : policyEditorController.getCodebases()) {
255                             final String model;
256                             if (codebase.isEmpty()) {
257                                 model = R("PEGlobalSettings");
258                             } else {
259                                 model = codebase;
260                             }
261                             if (!listModel.contains(model)) {
262                                 listModel.addElement(model);
263                             }
264                         }
265                         addNewCodebase("");
266                     }
267                 });
268             } catch (final InvocationTargetException | InterruptedException e) {
269                 OutputController.getLogger().log(e);
270             }
271         }
272         setChangesMade(false);
273 
274         fileChooser = new JFileChooser(policyEditorController.getFile());
275         fileChooser.setFileHidingEnabled(false);
276 
277         okButtonAction = new ActionListener() {
278             @Override
279             public void actionPerformed(final ActionEvent event) {
280                 if (policyEditorController.getFile() == null) {
281                     final int choice = fileChooser.showOpenDialog(PolicyEditor.this);
282                     if (choice == JFileChooser.APPROVE_OPTION) {
283                         PolicyEditor.this.setFile(fileChooser.getSelectedFile().getAbsolutePath());
284                     }
285                 }
286 
287                 // May still be null if user cancelled the file chooser
288                 if (policyEditorController.getFile() != null) {
289                     savePolicyFile();
290                 }
291             }
292         };
293         okButton.setText(R("ButApply"));
294         okButton.addActionListener(okButtonAction);
295 
296         addCodebaseButtonAction = new ActionListener() {
297             @Override
298             public void actionPerformed(final ActionEvent e) {
299                 addNewCodebaseInteractive();
300             }
301         };
302         addCodebaseButton.setText(R("PEAddCodebase"));
303         addCodebaseButton.addActionListener(addCodebaseButtonAction);
304 
305         removeCodebaseButtonAction = new ActionListener() {
306             @Override
307             public void actionPerformed(final ActionEvent e) {
308                 removeCodebase(getSelectedCodebase());
309             }
310         };
311         removeCodebaseButton.setText(R("PERemoveCodebase"));
312         removeCodebaseButton.addActionListener(removeCodebaseButtonAction);
313 
314         newButtonAction = new ActionListener() {
315             @Override
316             public void actionPerformed(final ActionEvent e) {
317                 if (!promptOnSaveChangesMade(false)) return;
318                 setFile(null);
319                 setChangesMade(false);
320             }
321         };
322 
323         openButtonAction = new ActionListener() {
324             @Override
325             public void actionPerformed(final ActionEvent e) {
326                 if (!promptOnSaveChangesMade(true)) return;
327                 final int choice = fileChooser.showOpenDialog(PolicyEditor.this);
328                 if (choice == JFileChooser.APPROVE_OPTION) {
329                     PolicyEditor.this.setFile(fileChooser.getSelectedFile().getAbsolutePath());
330                     openAndParsePolicyFile();
331                 }
332             }
333         };
334 
335          openDefaultButtonAction = new ActionListener() {
336             @Override
337             public void actionPerformed(final ActionEvent e) {
338                 if (!promptOnSaveChangesMade(true)) {
339                     return;
340                 }
341                 try {
342                     PolicyEditor.this.setFile(getDefaultPolicyFilePath());
343                     PolicyEditor.this.getFile().createNewFile();
344                 } catch (final IOException ex) {
345                     OutputController.getLogger().log(ex);
346                 } catch (final URISyntaxException ex) {
347                     OutputController.getLogger().log(ex);
348                 }
349                 openAndParsePolicyFile();
350             }
351         };
352 
353         saveAsButtonAction = new ActionListener() {
354             @Override
355             public void actionPerformed(final ActionEvent e) {
356                 final int choice = fileChooser.showSaveDialog(PolicyEditor.this);
357                 if (choice == JFileChooser.APPROVE_OPTION) {
358                     PolicyEditor.this.setFile(fileChooser.getSelectedFile().getAbsolutePath());
359                     setChangesMade(true);
360                     savePolicyFile();
361                 }
362             }
363         };
364 
365         renameCodebaseButtonAction = new ActionListener() {
366             @Override
367             public void actionPerformed(final ActionEvent e) {
368                 final String oldCodebase = getSelectedCodebase();
369                 if (oldCodebase.isEmpty()) {
370                     return;
371                 }
372                 String newCodebase = "";
373                 while (!validateCodebase(newCodebase) || policyEditorController.getCopyOfPermissions().containsKey(newCodebase)) {
374                     newCodebase = JOptionPane.showInputDialog(PolicyEditor.this, R("PERenameCodebase"), oldCodebase);
375                     if (newCodebase == null) {
376                         return;
377                     }
378                 }
379                 renameCodebase(oldCodebase, newCodebase);
380             }
381         };
382 
383         copyCodebaseButtonAction = new ActionListener() {
384             @Override
385             public void actionPerformed(final ActionEvent e) {
386                 copyCodebase(getSelectedCodebase());
387             }
388         };
389 
390         pasteCodebaseButtonAction = new ActionListener() {
391             @Override
392             public void actionPerformed(final ActionEvent e) {
393                 String clipboardCodebase = null;
394                 try {
395                     clipboardCodebase = policyEditorController.getCodebaseFromClipboard();
396                     final String newCodebase;
397                     if (getCodebases().contains(clipboardCodebase)) {
398                         String cb = "";
399                         while (!validateCodebase(cb) || policyEditorController.getCopyOfPermissions().containsKey(cb)) {
400                             cb = JOptionPane.showInputDialog(PolicyEditor.this, R("PEPasteCodebase"), "http://");
401                             if (cb == null) {
402                                 return;
403                             }
404                         }
405                         newCodebase = cb;
406                     } else {
407                         newCodebase = clipboardCodebase;
408                     }
409                     if (validateCodebase(newCodebase)) {
410                         pasteCodebase(newCodebase);
411                     }
412                 } catch (final UnsupportedFlavorException ufe) {
413                     OutputController.getLogger().log(ufe);
414                     showClipboardErrorDialog();
415                 } catch (final InvalidPolicyException ipe) {
416                     OutputController.getLogger().log(ipe);
417                     showInvalidPolicyExceptionDialog(clipboardCodebase);
418                 } catch (final IOException ioe) {
419                     OutputController.getLogger().log(ioe);
420                     showCouldNotAccessClipboardDialog();
421                 }
422             }
423         };
424 
425         viewCustomButtonAction = new ActionListener() {
426             @Override
427             public void actionPerformed(final ActionEvent e) {
428                 invokeRunnableOrEnqueueLater(new Runnable() {
429                     @Override
430                     public void run() {
431                         String codebase = getSelectedCodebase();
432                         if (codebase == null) {
433                             return;
434                         }
435                         if (cpViewer == null) {
436                             cpViewer = new CustomPolicyViewer(PolicyEditor.this, codebase);
437                             cpViewer.setVisible(true);
438                         } else {
439                             cpViewer.toFront();
440                             cpViewer.repaint();
441                         }
442                     }
443                 });
444             }
445         };
446 
447         policyEditorHelpButtonAction = new ActionListener() {
448             @Override
449             public void actionPerformed(final ActionEvent e) {
450                 new PolicyEditorAboutDialog(R("PEHelpDialogTitle"), R("PEHelpDialogContent")).setVisible(true);
451             }
452         };
453 
454         aboutPolicyEditorButtonAction = new ActionListener() {
455             @Override
456             public void actionPerformed(final ActionEvent e) {
457                 boolean modal = getModality();
458                 AboutDialog.display(modal, TextsProvider.POLICY_EDITOR,AboutDialog.ShowPage.HELP);
459             }
460         };
461 
462          aboutItwButtonAction = new ActionListener() {
463             @Override
464             public void actionPerformed(final ActionEvent e) {
465                 boolean modal = getModality();
466                 AboutDialog.display(modal, TextsProvider.POLICY_EDITOR);
467             }
468         };
469 
470         closeButtonAction = new ActionListener() {
471             @Override
472             public void actionPerformed(final ActionEvent event) {
473                 final Window parentWindow = SwingUtilities.getWindowAncestor(PolicyEditor.this);
474                 if (parentWindow instanceof PolicyEditorWindow) {
475                     ((PolicyEditorWindow) parentWindow).quit();
476                 }
477             }
478         };
479         closeButton.setText(R("ButClose"));
480         closeButton.addActionListener(closeButtonAction);
481 
482         setupLayout();
483     }
484 
getDefaultPolicyFilePath()485     private static String getDefaultPolicyFilePath() throws URISyntaxException {
486         return new File(new URI(PathsAndFiles.JAVA_POLICY.getFullPath())).getAbsolutePath();
487     }
488 
getModality()489     private boolean getModality() {
490         boolean modal = false;
491         Container parent = PolicyEditor.this;
492         while (true) {
493             if (parent == null) {
494                 break;
495             }
496             if (parent instanceof JDialog) {
497                 modal = ((JDialog) parent).isModal();
498                 break;
499             }
500             parent = parent.getParent();
501         }
502         return modal;
503     }
504 
505     /**
506      *
507      * @param async use asynchronous saving, which displays a progress dialog, or use synchronous, which blocks the
508      *              EDT but allows for eg the on-disk file to be changed without resorting to a busy-wait loop
509      * @return false iff the user wishes to cancel the operation and keep the current editor state
510      */
promptOnSaveChangesMade(final boolean async)511     private boolean promptOnSaveChangesMade(final boolean async) {
512         if (policyEditorController.changesMade()) {
513             final int save = JOptionPane.showConfirmDialog(this, R("PESaveChanges"));
514             if (save == JOptionPane.YES_OPTION) {
515                 if (policyEditorController.getFile() == null) {
516                     final int choice = fileChooser.showSaveDialog(this);
517                     if (choice == JFileChooser.APPROVE_OPTION) {
518                         this.setFile(fileChooser.getSelectedFile().getAbsolutePath());
519                     } else if (choice == JFileChooser.CANCEL_OPTION) {
520                         return false;
521                     }
522                 }
523                 if (async) {
524                     savePolicyFile();
525                 } else {
526                     try {
527                         policyEditorController.savePolicyFile();
528                     } catch (final IOException e) {
529                         showCouldNotSaveDialog();
530                     }
531                 }
532             } else if (save == JOptionPane.CANCEL_OPTION) {
533                 return false;
534             }
535         }
536         return true;
537     }
538 
setFile(final String filepath)539     public void setFile(final String filepath) {
540         if (filepath != null) {
541             policyEditorController.setFile(new File(filepath));
542         } else {
543             policyEditorController.setFile(null);
544             resetCodebases();
545             addNewCodebase("");
546         }
547         setParentWindowTitle(getWindowTitleForStatus());
548     }
549 
setParentWindowTitle(final String title)550     private void setParentWindowTitle(final String title) {
551         invokeRunnableOrEnqueueLater(new Runnable() {
552             @Override
553             public void run() {
554                 final Window parent = SwingUtilities.getWindowAncestor(PolicyEditor.this);
555                 if (!(parent instanceof PolicyEditorWindow)) {
556                     return;
557                 }
558                 final PolicyEditorWindow window = (PolicyEditorWindow) parent;
559                 window.setTitle(title);
560             }
561         });
562     }
563 
getWindowTitleForStatus()564     private String getWindowTitleForStatus() {
565         final String filepath;
566         final File file = getFile();
567         if (file != null) {
568             filepath = file.getPath();
569         } else {
570             filepath = null;
571         }
572         final String titleAndPath;
573         if (filepath != null) {
574             titleAndPath = R("PETitleWithPath", filepath);
575         } else {
576             titleAndPath = R("PETitle");
577         }
578         final String result;
579         if (policyEditorController.changesMade()) {
580             result = R("PETitleWithChangesMade", titleAndPath);
581         } else {
582             result = titleAndPath;
583         }
584         return result;
585     }
586 
getSelectedCodebase()587     private String getSelectedCodebase() {
588         final String codebase = list.getSelectedValue();
589         if (codebase == null || codebase.isEmpty()) {
590             return null;
591         }
592         if (codebase.equals(R("PEGlobalSettings"))) {
593             return "";
594         }
595         return codebase;
596     }
597 
preparePolicyEditorWindow(final PolicyEditorWindow w, final PolicyEditor e)598     private static void preparePolicyEditorWindow(final PolicyEditorWindow w, final PolicyEditor e) {
599         w.setModalityType(ModalityType.MODELESS); //at least some default
600         w.setPolicyEditor(e);
601         w.setTitle(R("PETitle"));
602         w.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
603         w.setJMenuBar(createMenuBar(w.getPolicyEditor()));
604         setupPolicyEditorWindow(w.asWindow(), w.getPolicyEditor());
605     }
606 
setupPolicyEditorWindow(final Window window, final PolicyEditor editor)607     private static void setupPolicyEditorWindow(final Window window, final PolicyEditor editor) {
608         window.add(editor);
609         window.pack();
610         editor.setVisible(true);
611 
612         window.addWindowListener(new WindowAdapter() {
613             @Override
614             public void windowClosing(final WindowEvent e) {
615                 ((PolicyEditorWindow) window).quit();
616             }
617         });
618     }
619 
620     public static interface PolicyEditorWindow {
621 
setTitle(String s)622         public void setTitle(String s);
623 
setDefaultCloseOperation(int i)624         public void setDefaultCloseOperation(int i);
625 
getPolicyEditor()626         public PolicyEditor getPolicyEditor();
627 
setPolicyEditor(PolicyEditor e)628         public void setPolicyEditor(PolicyEditor e);
629 
setJMenuBar(JMenuBar menu)630         public void setJMenuBar(JMenuBar menu);
631 
asWindow()632         public Window asWindow();
633 
setModalityType(ModalityType modalityType)634         public void setModalityType(ModalityType modalityType);
635 
quit()636         public void quit();
637     }
638 
639     private static class PolicyEditorFrame extends JFrame implements PolicyEditorWindow {
640 
641         private PolicyEditor editor;
642 
PolicyEditorFrame(final PolicyEditor editor)643         private PolicyEditorFrame(final PolicyEditor editor) {
644             super();
645             preparePolicyEditorWindow((PolicyEditorWindow) this, editor);
646         }
647 
648         @Override
setTitle(String title)649         public final void setTitle(String title) {
650             super.setTitle(title);
651         }
652 
653         @Override
getPolicyEditor()654         public final PolicyEditor getPolicyEditor() {
655             return editor;
656         }
657 
658         @Override
setPolicyEditor(final PolicyEditor e)659         public final void setPolicyEditor(final PolicyEditor e) {
660             editor = e;
661         }
662 
663         @Override
setDefaultCloseOperation(final int operation)664         public final void setDefaultCloseOperation(final int operation) {
665             super.setDefaultCloseOperation(operation);
666         }
667 
668         @Override
setJMenuBar(final JMenuBar menu)669         public final void setJMenuBar(final JMenuBar menu) {
670             super.setJMenuBar(menu);
671         }
672 
673         @Override
asWindow()674         public final Window asWindow() {
675             return this;
676         }
677 
678         @Override
setModalityType(final ModalityType type)679         public void setModalityType(final ModalityType type) {
680             //no op for frame
681         }
682 
683         @Override
quit()684         public void quit() {
685             policyEditorWindowQuit(this);
686         }
687     }
688 
689     /*
690      * Casting a Window to PolicyEditorWindow is not generally safe - be sure that
691      * the argument passed to this method is actually a PolicyEditorDialog or PolicyEditorFrame.
692      */
policyEditorWindowQuit(final Window window)693     private static void policyEditorWindowQuit(final Window window) {
694         final PolicyEditor editor = ((PolicyEditorWindow) window).getPolicyEditor();
695         editor.parentPolicyEditor.clear();
696         if (editor.policyEditorController.changesMade()) {
697             final int save = JOptionPane.showConfirmDialog(window, R("PESaveChanges"));
698             if (save == JOptionPane.YES_OPTION) {
699                 if (editor.policyEditorController.getFile() == null) {
700                     final int choice = editor.fileChooser.showSaveDialog(window);
701                     if (choice == JFileChooser.APPROVE_OPTION) {
702                         editor.setFile(editor.fileChooser.getSelectedFile().getAbsolutePath());
703                     } else if (choice == JFileChooser.CANCEL_OPTION) {
704                         return;
705                     }
706                 }
707                 try {
708                     editor.policyEditorController.savePolicyFile();
709                 } catch (final IOException e) {
710                     OutputController.getLogger().log(e);
711                     editor.showCouldNotSaveDialog();
712                     return;
713                 }
714             } else if (save == JOptionPane.CANCEL_OPTION) {
715                 return;
716             }
717         }
718         editor.setClosed();
719         window.dispose();
720     }
721 
getPolicyEditorFrame(final String filepath)722     public static PolicyEditorWindow getPolicyEditorFrame(final String filepath) {
723         return new PolicyEditorFrame(new PolicyEditor(filepath));
724     }
725 
726     private static class PolicyEditorDialog extends JDialog implements PolicyEditorWindow {
727 
728         private PolicyEditor editor;
729 
PolicyEditorDialog(final PolicyEditor editor)730         private PolicyEditorDialog(final PolicyEditor editor) {
731             super();
732             preparePolicyEditorWindow((PolicyEditorWindow) this, editor);
733         }
734 
735         @Override
setTitle(final String title)736         public final void setTitle(final String title) {
737             super.setTitle(title);
738         }
739 
740         @Override
getPolicyEditor()741         public final PolicyEditor getPolicyEditor() {
742             return editor;
743         }
744 
745         @Override
setPolicyEditor(final PolicyEditor e)746         public final void setPolicyEditor(final PolicyEditor e) {
747             editor = e;
748         }
749 
750         @Override
setDefaultCloseOperation(final int operation)751         public final void setDefaultCloseOperation(final int operation) {
752             super.setDefaultCloseOperation(operation);
753         }
754 
755         @Override
setJMenuBar(final JMenuBar menu)756         public final void setJMenuBar(final JMenuBar menu) {
757             super.setJMenuBar(menu);
758         }
759 
760         @Override
asWindow()761         public final Window asWindow() {
762             return this;
763         }
764 
765         @Override
setModalityType(final ModalityType type)766         public void setModalityType(final ModalityType type) {
767             super.setModalityType(type);
768         }
769 
770         @Override
quit()771         public void quit() {
772             policyEditorWindowQuit(this);
773         }
774     }
775 
getPolicyEditorDialog(final String filepath)776     public static PolicyEditorWindow getPolicyEditorDialog(final String filepath) {
777         return new PolicyEditorDialog(new PolicyEditor(filepath));
778     }
779 
setClosed()780     private void setClosed() {
781         closed = true;
782     }
783 
784     /**
785      * Check if the PolicyEditor instance has been visually closed
786      * @return if the PolicyEditor instance has been closed
787      */
isClosed()788     public boolean isClosed() {
789         return closed;
790     }
791 
792     /**
793      * Called by the Custom Policy Viewer on its parent Policy Editor when
794      * the Custom Policy Viewer is closing
795      */
customPolicyViewerClosing()796     void customPolicyViewerClosing() {
797         cpViewer = null;
798     }
799 
800     /**
801      * Add a new codebase to the editor's model. If the codebase is not a valid URL,
802      * the codebase is not added.
803      * @param codebase to be added
804      */
addNewCodebase(final String codebase)805     public void addNewCodebase(final String codebase) {
806         if (!codebase.isEmpty() && !validateCodebase(codebase)) {
807             OutputController.getLogger().log("Could not add codebase " + codebase);
808             return;
809         }
810         final String model;
811         if (codebase.isEmpty()) {
812             model = R("PEGlobalSettings");
813         } else {
814             model = codebase;
815         }
816         policyEditorController.addCodebase(codebase);
817         invokeRunnableOrEnqueueLater(new Runnable() {
818             @Override
819             public void run() {
820                 if (!listModel.contains(model)) {
821                     listModel.addElement(model);
822                     setChangesMade(true);
823                 }
824                 list.setSelectedValue(model, true);
825                 updateCheckboxes(codebase);
826             }
827         });
828     }
829 
validateCodebase(final String codebase)830     private static boolean validateCodebase(final String codebase) {
831         try {
832             new URL(codebase);
833         } catch (final MalformedURLException mue) {
834             return false;
835         }
836         return true;
837     }
838 
839 
getFile()840     public File getFile() {
841         return policyEditorController.getFile();
842     }
843 
844     /**
845      * Display an input dialog, which will disappear when the user enters a valid URL
846      * or when the user presses cancel. If an invalid URL is entered, the dialog reappears.
847      * When a valid URL is entered, it is used to create a new codebase entry in the editor's
848      * policy file model.
849      */
addNewCodebaseInteractive()850     public void addNewCodebaseInteractive() {
851         invokeRunnableOrEnqueueLater(new Runnable() {
852             @Override
853             public void run() {
854                 String codebase = "";
855                 while (!validateCodebase(codebase)) {
856                     codebase = JOptionPane.showInputDialog(PolicyEditor.this, R("PECodebasePrompt"), "http://");
857                     if (codebase == null) {
858                         return;
859                     }
860                 }
861                 addNewCodebase(codebase);
862             }
863         });
864     }
865 
866     /**
867      * Remove a codebase from the editor's model
868      * @param codebase to be removed
869      */
removeCodebase(final String codebase)870     public void removeCodebase(final String codebase) {
871         if (codebase.equals(R("PEGlobalSettings")) || codebase.isEmpty()) {
872             return;
873         }
874         int previousIndex = list.getSelectedIndex() - 1;
875         if (previousIndex < 0) {
876             previousIndex = 0;
877         }
878         policyEditorController.removeCodebase(codebase);
879         final int fIndex = previousIndex;
880         invokeRunnableOrEnqueueLater(new Runnable() {
881             @Override
882             public void run() {
883                 listModel.removeElement(codebase);
884                 list.setSelectedIndex(fIndex);
885             }
886         });
887         setChangesMade(true);
888     }
889 
890     /**
891      * Rename a codebase, preserving its permissions
892      * @param oldCodebase the codebase to rename
893      * @param newCodebase the new name for the codebase
894      */
renameCodebase(final String oldCodebase, final String newCodebase)895     public void renameCodebase(final String oldCodebase, final String newCodebase) {
896         final Map<PolicyEditorPermissions, Boolean> permissions = getPermissions(oldCodebase);
897         final Collection<CustomPermission> customPermissions = getCustomPermissions(oldCodebase);
898 
899         removeCodebase(oldCodebase);
900         addNewCodebase(newCodebase);
901 
902         for (final Map.Entry<PolicyEditorPermissions, Boolean> entry : permissions.entrySet()) {
903             setPermission(newCodebase, entry.getKey(), entry.getValue());
904         }
905 
906         for (final CustomPermission permission : customPermissions) {
907             addCustomPermission(newCodebase, permission);
908         }
909 
910         updateCheckboxes(newCodebase);
911     }
912 
913     /**
914      * Copy a codebase to the system clipboard, preserving its permissions
915      * @param codebase the codebase to copy
916      */
copyCodebase(final String codebase)917     public void copyCodebase(final String codebase) {
918         if (!getCodebases().contains(codebase)) {
919             return;
920         }
921         policyEditorController.copyCodebaseToClipboard(codebase);
922     }
923 
924     /**
925      * Paste a codebase entry from the system clipboard with a new codebase name
926      */
pasteCodebase(final String newCodebase)927     public void pasteCodebase(final String newCodebase) throws UnsupportedFlavorException, InvalidPolicyException, IOException {
928         final Map<PolicyEditorPermissions, Boolean> permissions = policyEditorController.getPermissionsFromClipboard();
929         final Set<CustomPermission> customPermissions = policyEditorController.getCustomPermissionsFromClipboard();
930         addNewCodebase(newCodebase);
931         for (final Map.Entry<PolicyEditorPermissions, Boolean> entry : permissions.entrySet()) {
932             policyEditorController.setPermission(newCodebase, entry.getKey(), entry.getValue());
933         }
934         policyEditorController.addCustomPermissions(newCodebase, customPermissions);
935         setChangesMade(true);
936         updateCheckboxes(newCodebase);
937     }
938 
getCodebases()939     public Set<String> getCodebases() {
940         return policyEditorController.getCodebases();
941     }
942 
setPermission(final String codebase, final PolicyEditorPermissions permission, final boolean state)943     public void setPermission(final String codebase, final PolicyEditorPermissions permission, final boolean state) {
944         policyEditorController.setPermission(codebase, permission, state);
945     }
946 
getPermissions(final String codebase)947     public Map<PolicyEditorPermissions, Boolean> getPermissions(final String codebase) {
948         return policyEditorController.getPermissions(codebase);
949     }
950 
addCustomPermission(final String codebase, final CustomPermission permission)951     public void addCustomPermission(final String codebase, final CustomPermission permission) {
952         policyEditorController.addCustomPermission(codebase, permission);
953     }
954 
getCustomPermissions(final String codebase)955     public Collection<CustomPermission> getCustomPermissions(final String codebase) {
956         return policyEditorController.getCustomPermissions(codebase);
957     }
958 
clearCustomPermissions(final String codebase)959     public void clearCustomPermissions(final String codebase) {
960         policyEditorController.clearCustomCodebase(codebase);
961     }
962 
invokeRunnableOrEnqueueLater(final Runnable runnable)963     private void invokeRunnableOrEnqueueLater(final Runnable runnable) {
964         if (SwingUtilities.isEventDispatchThread()) {
965             runnable.run();
966         } else {
967             SwingUtilities.invokeLater(runnable);
968         }
969     }
970 
invokeRunnableOrEnqueueAndWait(final Runnable runnable)971     private void invokeRunnableOrEnqueueAndWait(final Runnable runnable) throws InvocationTargetException, InterruptedException {
972         if (SwingUtilities.isEventDispatchThread()) {
973             runnable.run();
974         } else {
975             SwingUtilities.invokeAndWait(runnable);
976         }
977     }
978 
979     /**
980      * Update the checkboxes to show the permissions granted to the specified codebase
981      * @param codebase whose permissions to display
982      */
updateCheckboxes(final String codebase)983     private void updateCheckboxes(final String codebase) {
984         try {
985             invokeRunnableOrEnqueueAndWait(new Runnable() {
986                 @Override
987                 public void run() {
988                     updateCheckboxesImpl(codebase);
989                 }
990             });
991         } catch (InterruptedException ex) {
992             OutputController.getLogger().log(ex);
993         } catch (InvocationTargetException ex) {
994             OutputController.getLogger().log(ex);
995         }
996     }
997 
updateCheckboxesImpl(final String codebase)998     private void updateCheckboxesImpl(final String codebase) {
999         if (!getCodebases().contains(codebase)) {
1000             return;
1001         }
1002         final Map<PolicyEditorPermissions, Boolean> map = policyEditorController.getCopyOfPermissions().get(codebase);
1003         for (final PolicyEditorPermissions perm : PolicyEditorPermissions.values()) {
1004             final JCheckBox box = checkboxMap.get(perm);
1005             for (final ActionListener l : box.getActionListeners()) {
1006                 box.removeActionListener(l);
1007             }
1008             final boolean state = policyEditorController.getPermission(codebase, perm);
1009             for (final JCheckBoxWithGroup jg : groupBoxList) {
1010                 jg.setState(map);
1011             }
1012             box.setSelected(state);
1013             box.addActionListener(new ActionListener() {
1014                 @Override
1015                 public void actionPerformed(final ActionEvent e) {
1016                     setChangesMade(true);
1017                     policyEditorController.setPermission(codebase, perm, box.isSelected());
1018                     for (JCheckBoxWithGroup jg : groupBoxList) {
1019                         jg.setState(map);
1020                     }
1021                 }
1022             });
1023         }
1024     }
1025 
1026     /**
1027      * Set a mnemonic key for a menu item or button
1028      * @param button the component for which to set a mnemonic
1029      * @param mnemonic the mnemonic to set
1030      */
setButtonMnemonic(final AbstractButton button, final String mnemonic)1031     private static void setButtonMnemonic(final AbstractButton button, final String mnemonic) {
1032         if (mnemonic.length() != 1) {
1033             OutputController.getLogger().log(OutputController.Level.WARNING_DEBUG, "Could not set mnemonic \"" + mnemonic + "\" for " + button);
1034             return;
1035         }
1036         final char ch = mnemonic.charAt(0);
1037         button.setMnemonic(ch);
1038     }
1039 
setMenuItemAccelerator(final JMenuItem menuItem, final String accelerator)1040     private static void setMenuItemAccelerator(final JMenuItem menuItem, final String accelerator) {
1041         final KeyStroke ks = KeyStroke.getKeyStroke(accelerator);
1042         menuItem.setAccelerator(ks);
1043     }
1044 
createMenuBar(final PolicyEditor editor)1045     private static JMenuBar createMenuBar(final PolicyEditor editor) {
1046         final JMenuBar menuBar = new JMenuBar();
1047 
1048         final JMenu fileMenu = new JMenu(R("PEFileMenu"));
1049         setButtonMnemonic(fileMenu, R("PEFileMenuMnemonic"));
1050 
1051         final JMenuItem newItem = new JMenuItem(R("PENewMenuItem"));
1052         setButtonMnemonic(newItem, R("PENewMenuItemMnemonic"));
1053         setMenuItemAccelerator(newItem, R("PENewMenuItemAccelerator"));
1054         newItem.addActionListener(editor.newButtonAction);
1055         fileMenu.add(newItem);
1056 
1057         final JMenuItem openItem = new JMenuItem(R("PEOpenMenuItem"));
1058         setButtonMnemonic(openItem, R("PEOpenMenuItemMnemonic"));
1059         setMenuItemAccelerator(openItem, R("PEOpenMenuItemAccelerator"));
1060         openItem.addActionListener(editor.openButtonAction);
1061         fileMenu.add(openItem);
1062 
1063         final JMenuItem openDefaultItem = new JMenuItem(R("PEOpenDefaultMenuItem"));
1064         setButtonMnemonic(openDefaultItem, R("PEOpenDefaultMenuItemMnemonic"));
1065         setMenuItemAccelerator(openDefaultItem, R("PEOpenDefaultMenuItemAccelerator"));
1066         openDefaultItem.addActionListener(editor.openDefaultButtonAction);
1067         fileMenu.add(openDefaultItem);
1068 
1069         final JMenuItem saveItem = new JMenuItem(R("PESaveMenuItem"));
1070         setButtonMnemonic(saveItem, R("PESaveMenuItemMnemonic"));
1071         setMenuItemAccelerator(saveItem, R("PESaveMenuItemAccelerator"));
1072         saveItem.addActionListener(editor.okButtonAction);
1073         fileMenu.add(saveItem);
1074 
1075         final JMenuItem saveAsItem = new JMenuItem(R("PESaveAsMenuItem"));
1076         setButtonMnemonic(saveAsItem, R("PESaveAsMenuItemMnemonic"));
1077         setMenuItemAccelerator(saveAsItem, R("PESaveAsMenuItemAccelerator"));
1078         saveAsItem.addActionListener(editor.saveAsButtonAction);
1079         fileMenu.add(saveAsItem);
1080 
1081         final JMenuItem exitItem = new JMenuItem(R("PEExitMenuItem"));
1082         setButtonMnemonic(exitItem, R("PEExitMenuItemMnemonic"));
1083         setMenuItemAccelerator(exitItem, R("PEExitMenuItemAccelerator"));
1084         exitItem.addActionListener(editor.closeButtonAction);
1085         fileMenu.add(exitItem);
1086         menuBar.add(fileMenu);
1087 
1088         final JMenu codebaseMenu = new JMenu(R("PECodebaseMenu"));
1089         setButtonMnemonic(codebaseMenu, R("PECodebaseMenuMnemonic"));
1090 
1091         final JMenuItem addNewCodebaseItem = new JMenuItem(R("PEAddCodebaseItem"));
1092         setButtonMnemonic(addNewCodebaseItem, R("PEAddCodebaseItemMnemonic"));
1093         setMenuItemAccelerator(addNewCodebaseItem, R("PEAddCodebaseItemAccelerator"));
1094         addNewCodebaseItem.addActionListener(editor.addCodebaseButtonAction);
1095         codebaseMenu.add(addNewCodebaseItem);
1096 
1097         final JMenuItem removeCodebaseItem = new JMenuItem(R("PERemoveCodebaseItem"));
1098         setButtonMnemonic(removeCodebaseItem, R("PERemoveCodebaseItemMnemonic"));
1099         setMenuItemAccelerator(removeCodebaseItem, R("PERemoveCodebaseItemAccelerator"));
1100         removeCodebaseItem.addActionListener(editor.removeCodebaseButtonAction);
1101         codebaseMenu.add(removeCodebaseItem);
1102 
1103         codebaseMenu.addSeparator();
1104 
1105         final JMenuItem renameCodebaseItem = new JMenuItem(R("PERenameCodebaseItem"));
1106         setButtonMnemonic(renameCodebaseItem, R("PERenameCodebaseItemMnemonic"));
1107         setMenuItemAccelerator(renameCodebaseItem, R("PERenameCodebaseItemAccelerator"));
1108         renameCodebaseItem.addActionListener(editor.renameCodebaseButtonAction);
1109         codebaseMenu.add(renameCodebaseItem);
1110 
1111         final JMenuItem copyCodebaseItem = new JMenuItem(R("PECopyCodebaseItem"));
1112         setButtonMnemonic(copyCodebaseItem, R("PECopyCodebaseItemMnemonic"));
1113         setMenuItemAccelerator(copyCodebaseItem, R("PECopyCodebaseItemAccelerator"));
1114         copyCodebaseItem.addActionListener(editor.copyCodebaseButtonAction);
1115         codebaseMenu.add(copyCodebaseItem);
1116         menuBar.add(codebaseMenu);
1117 
1118         final JMenuItem pasteCodebaseItem = new JMenuItem(R("PEPasteCodebaseItem"));
1119         setButtonMnemonic(pasteCodebaseItem, R("PEPasteCodebaseItemMnemonic"));
1120         setMenuItemAccelerator(pasteCodebaseItem, R("PEPasteCodebaseItemAccelerator"));
1121         pasteCodebaseItem.addActionListener(editor.pasteCodebaseButtonAction);
1122         codebaseMenu.add(pasteCodebaseItem);
1123 
1124         final JMenu viewMenu = new JMenu(R("PEViewMenu"));
1125         setButtonMnemonic(viewMenu, R("PEViewMenuMnemonic"));
1126 
1127         final JMenuItem customPermissionsItem = new JMenuItem(R("PECustomPermissionsItem"));
1128         setButtonMnemonic(customPermissionsItem, R("PECustomPermissionsItemMnemonic"));
1129         setMenuItemAccelerator(customPermissionsItem, R("PECustomPermissionsItemAccelerator"));
1130         customPermissionsItem.addActionListener(editor.viewCustomButtonAction);
1131 
1132         viewMenu.add(customPermissionsItem);
1133         menuBar.add(viewMenu);
1134 
1135         final JMenu helpMenu = new JMenu(R("PEHelpMenu"));
1136         setButtonMnemonic(helpMenu, R("PEHelpMenuMnemonic"));
1137 
1138         final JMenuItem aboutPolicyEditorItem = new JMenuItem(R("PEAboutPolicyEditorItem"));
1139         setButtonMnemonic(aboutPolicyEditorItem, R("PEAboutPolicyEditorItemMnemonic"));
1140         aboutPolicyEditorItem.addActionListener(editor.aboutPolicyEditorButtonAction);
1141         helpMenu.add(aboutPolicyEditorItem);
1142 
1143         final JMenuItem aboutITW = new JMenuItem(R("CPTabAbout"));
1144         //setButtonMnemonic(aboutPolicyEditorItem, R("PEAboutPolicyEditorItemMnemonic"));
1145         aboutITW.addActionListener(editor.aboutItwButtonAction);
1146         helpMenu.add(aboutITW);
1147 
1148         final JMenuItem policyEditorHelpItem = new JMenuItem(R("PEPolicyEditorHelpItem"));
1149         setButtonMnemonic(policyEditorHelpItem, R("PEPolicyEditorHelpItemMnemonic"));
1150         policyEditorHelpItem.addActionListener(editor.policyEditorHelpButtonAction);
1151         helpMenu.addSeparator();
1152         helpMenu.add(policyEditorHelpItem);
1153 
1154         menuBar.add(helpMenu);
1155         /*
1156          * JList has default Ctrl-C and Ctrl-V bindings, which we want to override with custom actions
1157          */
1158         final InputMap listInputMap = editor.list.getInputMap();
1159         final ActionMap listActionMap = editor.list.getActionMap();
1160 
1161         final Action listCopyOverrideAction = new AbstractAction() {
1162             @Override
1163             public void actionPerformed(final ActionEvent e) {
1164                 editor.copyCodebaseButtonAction.actionPerformed(e);
1165             }
1166         };
1167 
1168         final Action listPasteOverrideAction = new AbstractAction() {
1169             @Override
1170             public void actionPerformed(final ActionEvent e) {
1171                 editor.pasteCodebaseButtonAction.actionPerformed(e);
1172             }
1173         };
1174 
1175         listInputMap.put(copyCodebaseItem.getAccelerator(), "CopyCodebaseOverride");
1176         listActionMap.put("CopyCodebaseOverride", listCopyOverrideAction);
1177         listInputMap.put(pasteCodebaseItem.getAccelerator(), "PasteCodebaseOverride");
1178         listActionMap.put("PasteCodebaseOverride", listPasteOverrideAction);
1179 
1180         return menuBar;
1181     }
1182 
1183     /**
1184      * Lay out all controls, tooltips, etc.
1185      */
setupLayout()1186     private void setupLayout() {
1187         final JLabel checkboxLabel = new JLabel();
1188         checkboxLabel.setText(R("PECheckboxLabel"));
1189         checkboxLabel.setBorder(new EmptyBorder(2, 2, 2, 2));
1190         final GridBagConstraints checkboxLabelConstraints = new GridBagConstraints();
1191         checkboxLabelConstraints.gridx = 2;
1192         checkboxLabelConstraints.gridy = 0;
1193         checkboxLabelConstraints.fill = GridBagConstraints.HORIZONTAL;
1194         add(checkboxLabel, checkboxLabelConstraints);
1195 
1196         final GridBagConstraints checkboxConstraints = new GridBagConstraints();
1197         checkboxConstraints.anchor = GridBagConstraints.LINE_START;
1198         checkboxConstraints.fill = GridBagConstraints.HORIZONTAL;
1199         checkboxConstraints.weightx = 0;
1200         checkboxConstraints.weighty = 0;
1201         checkboxConstraints.gridx = 2;
1202         checkboxConstraints.gridy = 1;
1203 
1204         for (final JCheckBox box : checkboxMap.values()) {
1205             if (PolicyEditorPermissions.Group.anyContains(box, checkboxMap)) {
1206                 //do not show boxes in any group
1207                 continue;
1208             }
1209             add(box, checkboxConstraints);
1210             checkboxConstraints.gridx++;
1211             // Two columns of checkboxes
1212             if (checkboxConstraints.gridx > 3) {
1213                 checkboxConstraints.gridx = 2;
1214                 checkboxConstraints.gridy++;
1215             }
1216         }
1217         //add groups
1218         for (final PolicyEditorPermissions.Group g : PolicyEditorPermissions.Group.values()) {
1219             //no metter what, put group title on new line
1220             checkboxConstraints.gridy++;
1221             //all groups are in second column
1222             checkboxConstraints.gridx = 2;
1223             final JCheckBoxWithGroup groupCh = new JCheckBoxWithGroup(g);
1224             groupBoxList.add(groupCh);
1225             final JPanel groupPanel = new JPanel(new GridBagLayout());
1226             groupPanel.setBorder(new LineBorder(Color.black));
1227             groupCh.setToolTipText(R("PEGrightClick"));
1228             groupCh.addMouseListener(new MouseAdapter() {
1229                 @Override
1230                 public void mouseClicked(final MouseEvent e) {
1231                     if (e.getButton() == MouseEvent.BUTTON3) {
1232                         toggleExpandedCheckboxGroupPanel(groupPanel);
1233                     }
1234                 }
1235             });
1236             groupCh.addKeyListener(new KeyAdapter() {
1237                 @Override
1238                 public void keyPressed(final KeyEvent e) {
1239                     if (e.getKeyCode() == KeyEvent.VK_ENTER || e.getKeyCode() == KeyEvent.VK_CONTEXT_MENU) {
1240                         toggleExpandedCheckboxGroupPanel(groupPanel);
1241                     }
1242                 }
1243             });
1244             groupCh.addActionListener(new ActionListener() {
1245                 @Override
1246                 public void actionPerformed(final ActionEvent e) {
1247                     final String codebase = getSelectedCodebase();
1248                     if (codebase == null) {
1249                         return;
1250                     }
1251                     List<ActionListener> backup = new LinkedList<>();
1252                     for (final ActionListener l : groupCh.getActionListeners()) {
1253                         backup.add(l);
1254                         groupCh.removeActionListener(l);
1255                     }
1256                     for (final PolicyEditorPermissions p : groupCh.getGroup().getPermissions()) {
1257                         policyEditorController.setPermission(codebase, p, groupCh.isSelected());
1258                     }
1259                     setChangesMade(true);
1260                     updateCheckboxes(codebase);
1261                     for (final ActionListener al : backup) {
1262                         groupCh.addActionListener(al);
1263                     }
1264 
1265                 }
1266             });
1267             add(groupCh, checkboxConstraints);
1268             // place panel with members below the title
1269             checkboxConstraints.gridy++;
1270             checkboxConstraints.gridx = 2;
1271             // spread group's panel over two columns
1272             checkboxConstraints.gridwidth = 2;
1273             checkboxConstraints.fill = GridBagConstraints.BOTH;
1274             add(groupPanel, checkboxConstraints);
1275             final GridBagConstraints groupCheckboxLabelConstraints = new GridBagConstraints();
1276             groupCheckboxLabelConstraints.anchor = GridBagConstraints.LINE_START;
1277             groupCheckboxLabelConstraints.weightx = 0;
1278             groupCheckboxLabelConstraints.weighty = 0;
1279             groupCheckboxLabelConstraints.gridx = 1;
1280             groupCheckboxLabelConstraints.gridy = 1;
1281             for (final PolicyEditorPermissions p : g.getPermissions()) {
1282                 groupPanel.add(checkboxMap.get(p), groupCheckboxLabelConstraints);
1283                 // Two columns of checkboxes
1284                 groupCheckboxLabelConstraints.gridx++;
1285                 if (groupCheckboxLabelConstraints.gridx > 2) {
1286                     groupCheckboxLabelConstraints.gridx = 1;
1287                     groupCheckboxLabelConstraints.gridy++;
1288                 }
1289             }
1290             groupPanel.setVisible(false);
1291             //reset
1292             checkboxConstraints.gridwidth = 1;
1293         }
1294 
1295         final JLabel codebaseListLabel = new JLabel(R("PECodebaseLabel"));
1296         codebaseListLabel.setBorder(new EmptyBorder(2, 2, 2, 2));
1297         final GridBagConstraints listLabelConstraints = new GridBagConstraints();
1298         listLabelConstraints.fill = GridBagConstraints.HORIZONTAL;
1299         listLabelConstraints.gridx = 0;
1300         listLabelConstraints.gridy = 0;
1301         add(codebaseListLabel, listLabelConstraints);
1302 
1303         list.addListSelectionListener(new ListSelectionListener() {
1304             @Override
1305             public void valueChanged(final ListSelectionEvent e) {
1306                 if (e.getValueIsAdjusting()) {
1307                     return; // ignore first click, act on release
1308                 }
1309                 final String codebase = getSelectedCodebase();
1310                 if (codebase == null) {
1311                     return;
1312                 }
1313                 updateCheckboxes(codebase);
1314             }
1315         });
1316         list.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
1317 
1318         scrollPane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED);
1319         scrollPane.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS);
1320         scrollPane.setViewportView(list);
1321         final GridBagConstraints listConstraints = new GridBagConstraints();
1322         listConstraints.fill = GridBagConstraints.BOTH;
1323         listConstraints.weightx = 1;
1324         listConstraints.weighty = 1;
1325         listConstraints.gridheight = checkboxConstraints.gridy + 1;
1326         listConstraints.gridwidth = 2;
1327         listConstraints.gridx = 0;
1328         listConstraints.gridy = 1;
1329         add(scrollPane, listConstraints);
1330 
1331         final GridBagConstraints addCodebaseButtonConstraints = new GridBagConstraints();
1332         addCodebaseButtonConstraints.fill = GridBagConstraints.HORIZONTAL;
1333         addCodebaseButtonConstraints.gridx = 0;
1334         addCodebaseButtonConstraints.gridy = listConstraints.gridy + listConstraints.gridheight + 1;
1335         setButtonMnemonic(addCodebaseButton, R("PEAddCodebaseMnemonic"));
1336         add(addCodebaseButton, addCodebaseButtonConstraints);
1337 
1338         final GridBagConstraints removeCodebaseButtonConstraints = new GridBagConstraints();
1339         removeCodebaseButtonConstraints.fill = GridBagConstraints.HORIZONTAL;
1340         removeCodebaseButtonConstraints.gridx = addCodebaseButtonConstraints.gridx + 1;
1341         removeCodebaseButtonConstraints.gridy = addCodebaseButtonConstraints.gridy;
1342         setButtonMnemonic(removeCodebaseButton, R("PERemoveCodebaseMnemonic"));
1343         removeCodebaseButton.setPreferredSize(addCodebaseButton.getPreferredSize());
1344         add(removeCodebaseButton, removeCodebaseButtonConstraints);
1345 
1346         final GridBagConstraints okButtonConstraints = new GridBagConstraints();
1347         okButtonConstraints.fill = GridBagConstraints.HORIZONTAL;
1348         okButtonConstraints.gridx = removeCodebaseButtonConstraints.gridx + 2;
1349         okButtonConstraints.gridy = removeCodebaseButtonConstraints.gridy;
1350         add(okButton, okButtonConstraints);
1351 
1352         final GridBagConstraints cancelButtonConstraints = new GridBagConstraints();
1353         cancelButtonConstraints.fill = GridBagConstraints.HORIZONTAL;
1354         cancelButtonConstraints.gridx = okButtonConstraints.gridx + 1;
1355         cancelButtonConstraints.gridy = okButtonConstraints.gridy;
1356         add(closeButton, cancelButtonConstraints);
1357 
1358         setMinimumSize(getPreferredSize());
1359     }
1360 
setChangesMade(final boolean b)1361     void setChangesMade(final boolean b) {
1362         policyEditorController.setChangesMade(b);
1363         invokeRunnableOrEnqueueLater(new Runnable() {
1364             @Override
1365             public void run() {
1366                 setParentWindowTitle(getWindowTitleForStatus());
1367             }
1368         });
1369     }
1370 
resetCodebases()1371     private void resetCodebases() {
1372         listModel.clear();
1373         policyEditorController.clearPermissions();
1374         policyEditorController.clearCustomPermissions();
1375     }
1376 
1377     /**
1378      * @return whether this PolicyEditor is currently opening or saving a policy file to disk
1379      */
isPerformingIO()1380     public boolean isPerformingIO() {
1381         return policyEditorController.performingIO();
1382     }
1383 
openAndParsePolicyFile()1384     private void openAndParsePolicyFile() {
1385         resetCodebases();
1386         try {
1387             policyEditorController.getFile().createNewFile();
1388         } catch (final IOException e) {
1389             OutputController.getLogger().log(e);
1390         }
1391         final OpenFileResult ofr = FileUtils.testFilePermissions(policyEditorController.getFile());
1392         if (ofr == OpenFileResult.FAILURE || ofr == OpenFileResult.NOT_FILE) {
1393             FileUtils.showCouldNotOpenFilepathDialog(PolicyEditor.this, policyEditorController.getFile().getPath());
1394             return;
1395         }
1396         if (ofr == OpenFileResult.CANT_WRITE) {
1397             FileUtils.showReadOnlyDialog(PolicyEditor.this);
1398         }
1399 
1400 
1401         final Window parentWindow = SwingUtilities.getWindowAncestor(this);
1402         final JDialog progressIndicator = new IndeterminateProgressDialog(parentWindow, "Loading...");
1403         final SwingWorker<Void, Void> openPolicyFileWorker = new SwingWorker<Void, Void>() {
1404             @Override
1405             protected Void doInBackground() throws Exception {
1406                 try {
1407                     if (parentWindow != null) {
1408                         invokeRunnableOrEnqueueLater(new Runnable() {
1409                             @Override
1410                             public void run() {
1411                                 progressIndicator.setLocationRelativeTo(parentWindow);
1412                                 progressIndicator.setVisible(true);
1413                             }
1414                         });
1415                     }
1416                     policyEditorController.openAndParsePolicyFile();
1417                 } catch (final FileNotFoundException fnfe) {
1418                     OutputController.getLogger().log(fnfe);
1419                     FileUtils.showCouldNotOpenDialog(PolicyEditor.this, R("PECouldNotOpen"));
1420                 } catch (final IOException | InvalidPolicyException e) {
1421                     OutputController.getLogger().log(e);
1422                     OutputController.getLogger().log(OutputController.Level.ERROR_ALL, R("RCantOpenFile", policyEditorController.getFile().getPath()));
1423                     FileUtils.showCouldNotOpenDialog(PolicyEditor.this, R("PECouldNotOpen"));
1424                 }
1425                 return null;
1426             }
1427 
1428             @Override
1429             public void done() {
1430                 for (final String codebase : policyEditorController.getCodebases()) {
1431                     final String model;
1432                     if (codebase.isEmpty()) {
1433                         model = R("PEGlobalSettings");
1434                     } else {
1435                         model = codebase;
1436                     }
1437                     if (!listModel.contains(model)) {
1438                         listModel.addElement(model);
1439                     }
1440                 }
1441                 addNewCodebase("");
1442                 progressIndicator.setVisible(false);
1443                 progressIndicator.dispose();
1444                 setChangesMade(false);
1445             }
1446         };
1447         openPolicyFileWorker.execute();
1448     }
1449 
1450     /**
1451      * Save the policy model into the file pointed to by the filePath field.
1452      */
savePolicyFile()1453     private void savePolicyFile() {
1454         final int overwriteChanges = checkPolicyChangesWithDialog();
1455         switch (overwriteChanges) {
1456             case JOptionPane.YES_OPTION:
1457                 openAndParsePolicyFile();
1458                 return;
1459             case JOptionPane.NO_OPTION:
1460                 break;
1461             case JOptionPane.CANCEL_OPTION:
1462                 return;
1463             default:
1464                 break;
1465         }
1466 
1467         final Window parentWindow = SwingUtilities.getWindowAncestor(this);
1468         final JDialog progressIndicator = new IndeterminateProgressDialog(parentWindow, "Saving...");
1469         final SwingWorker<Void, Void> savePolicyFileWorker = new SwingWorker<Void, Void>() {
1470             @Override
1471             public Void doInBackground() throws Exception {
1472                 try {
1473                     if (parentWindow != null) {
1474                         invokeRunnableOrEnqueueLater(new Runnable() {
1475                             @Override
1476                             public void run() {
1477                                 progressIndicator.setLocationRelativeTo(parentWindow);
1478                                 progressIndicator.setVisible(true);
1479                             }
1480                         });
1481                     }
1482                     policyEditorController.savePolicyFile();
1483                 } catch (final IOException e) {
1484                     OutputController.getLogger().log(e);
1485                     showCouldNotSaveDialog();
1486                 }
1487                 return null;
1488             }
1489 
1490             @Override
1491             public void done() {
1492                 showChangesSavedDialog();
1493                 progressIndicator.setVisible(false);
1494                 progressIndicator.dispose();
1495                 setChangesMade(false);
1496             }
1497         };
1498         savePolicyFileWorker.execute();
1499     }
1500 
1501     /**
1502      * Show a dialog informing the user that their changes have been saved.
1503      */
showChangesSavedDialog()1504     private void showChangesSavedDialog() {
1505         // This dialog is often displayed when closing the editor, and so PolicyEditor
1506         // may already be disposed when this dialog appears. Give a weak reference so
1507         // that this dialog doesn't prevent the JVM from exiting
1508         invokeRunnableOrEnqueueLater(new Runnable() {
1509             @Override
1510             public void run() {
1511                 JOptionPane.showMessageDialog(parentPolicyEditor.get(), R("PEChangesSaved"));
1512             }
1513         });
1514     }
1515 
1516     /**
1517      * Show a dialog informing the user that their changes could not be saved.
1518      */
showCouldNotSaveDialog()1519     private void showCouldNotSaveDialog() {
1520         // This dialog is often displayed when closing the editor, and so PolicyEditor
1521         // may already be disposed when this dialog appears. Give a weak reference so
1522         // that this dialog doesn't prevent the JVM from exiting
1523         invokeRunnableOrEnqueueLater(new Runnable() {
1524             @Override
1525             public void run() {
1526                 JOptionPane.showMessageDialog(parentPolicyEditor.get(), R("PECouldNotSave"), R("Error"), JOptionPane.ERROR_MESSAGE);
1527             }
1528         });
1529     }
1530 
showClipboardErrorDialog()1531     private void showClipboardErrorDialog() {
1532         invokeRunnableOrEnqueueLater(new Runnable() {
1533             @Override
1534             public void run() {
1535                 JOptionPane.showMessageDialog(parentPolicyEditor.get(), R("PEClipboardError"), R("Error"), JOptionPane.ERROR_MESSAGE);
1536             }
1537         });
1538     }
1539 
showInvalidPolicyExceptionDialog(final String codebase)1540     private void showInvalidPolicyExceptionDialog(final String codebase) {
1541         invokeRunnableOrEnqueueLater(new Runnable() {
1542             @Override
1543             public void run() {
1544                 JOptionPane.showMessageDialog(parentPolicyEditor.get(), R("PEInvalidPolicy", codebase), R("Error"), JOptionPane.ERROR_MESSAGE);
1545             }
1546         });
1547     }
1548 
showCouldNotAccessClipboardDialog()1549     private void showCouldNotAccessClipboardDialog() {
1550         invokeRunnableOrEnqueueLater(new Runnable() {
1551             @Override
1552             public void run() {
1553                 JOptionPane.showMessageDialog(parentPolicyEditor.get(), R("PEClipboardAccessError"), R("Error"), JOptionPane.ERROR_MESSAGE);
1554             }
1555         });
1556     }
1557 
1558     /**
1559      * Detect if the policy settings have changed, either on-disk or in-app.
1560      * If an on-disk change has occurred, update the Md5.
1561      * @return The user's choice (Yes/No/Cancel - see JOptionPane constants).
1562      * "Cancel" if the file hasn't changed but the user has made modifications
1563      * to the settings. "No" otherwise
1564      * @throws IOException if the file cannot be read
1565      */
checkPolicyChangesWithDialog()1566     private int checkPolicyChangesWithDialog() {
1567         boolean changed;
1568         try {
1569             changed = policyEditorController.fileHasChanged();
1570         } catch (FileNotFoundException e) {
1571             OutputController.getLogger().log(e);
1572             JOptionPane.showMessageDialog(PolicyEditor.this, R("PEFileMissing"), R("PEFileModified"), JOptionPane.WARNING_MESSAGE);
1573             return JOptionPane.NO_OPTION;
1574         } catch (IOException e) {
1575             OutputController.getLogger().log(e);
1576             changed = true;
1577         }
1578         if (changed) {
1579             String policyFilePath;
1580             try {
1581                 policyFilePath = policyEditorController.getFile().getCanonicalPath();
1582             } catch (final IOException e) {
1583                 OutputController.getLogger().log(e);
1584                 policyFilePath = policyEditorController.getFile().getPath();
1585             }
1586             return JOptionPane.showConfirmDialog(PolicyEditor.this, R("PEFileModifiedDetail", policyFilePath,
1587                     R("PEFileModified"), JOptionPane.YES_NO_CANCEL_OPTION));
1588         } else if (!policyEditorController.changesMade()) {
1589             //Return without saving or reloading
1590             return JOptionPane.CANCEL_OPTION;
1591         }
1592         return JOptionPane.NO_OPTION;
1593     }
1594 
toggleExpandedCheckboxGroupPanel(final JPanel groupPanel)1595     private void toggleExpandedCheckboxGroupPanel(final JPanel groupPanel) {
1596         groupPanel.setVisible(!groupPanel.isVisible());
1597         PolicyEditor.this.validate();
1598         final Window w = SwingUtilities.getWindowAncestor(PolicyEditor.this);
1599         if (w != null) {
1600             w.pack();
1601         }
1602     }
1603 
1604     /**
1605      * Start a Policy Editor instance.
1606      * @param args "-file $FILENAME" and/or "-codebase $CODEBASE" are accepted flag/value pairs.
1607      * -file specifies a file path to be opened by the editor. If none is provided, the default
1608      * policy file location for the user is opened.
1609      * -codebase specifies (a) codebase(s) to start the editor with. If the entry already exists,
1610      * it will be selected. If it does not exist, it will be created, then selected. Multiple
1611      * codebases can be used, separated by spaces.
1612      * -help will print a help message and immediately return (no editor instance opens)
1613      */
main(final String[] args)1614     public static void main(final String[] args) {
1615         final OptionParser optionParser = new OptionParser(args, OptionsDefinitions.getPolicyEditorOptions());
1616 
1617         if (optionParser.hasOption(OptionsDefinitions.OPTIONS.VERBOSE)) {
1618             JNLPRuntime.setDebug(true);
1619         }
1620 
1621         if (optionParser.hasOption(OptionsDefinitions.OPTIONS.HELP1)) {
1622             final TextsProvider helpMessagesProvider = new PolicyEditorTextsProvider("utf-8", new PlainTextFormatter(), true, true);
1623             String HELP_MESSAGE = "\n";
1624             if (JNLPRuntime.isDebug()) {
1625                 HELP_MESSAGE = HELP_MESSAGE + helpMessagesProvider.writeToString();
1626             } else {
1627                 HELP_MESSAGE = HELP_MESSAGE
1628                         + helpMessagesProvider.prepare().getSynopsis()
1629                         + helpMessagesProvider.getFormatter().getNewLine()
1630                         + helpMessagesProvider.prepare().getOptions()
1631                         + helpMessagesProvider.getFormatter().getNewLine();
1632             }
1633             OutputController.getLogger().printOut(HELP_MESSAGE);
1634             return;
1635         }
1636 
1637         try {
1638             UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
1639         } catch (final Exception e) {
1640             // not really important, so just ignore
1641         }
1642 
1643         SwingUtilities.invokeLater(new Runnable() {
1644             @Override
1645             public void run() {
1646                 final String filepath = getFilePathArgument(optionParser);
1647                 final PolicyEditorWindow frame = getPolicyEditorFrame(filepath);
1648                 frame.asWindow().setVisible(true);
1649                 final List<String> codebases = optionParser.getParams(OptionsDefinitions.OPTIONS.CODEBASE);
1650                 for (final String url : codebases) {
1651                     frame.getPolicyEditor().addNewCodebase(url);
1652                 }
1653             }
1654         });
1655     }
1656 
getFilePathArgument(OptionParser optionParser)1657     private static String getFilePathArgument(OptionParser optionParser) {
1658         final boolean openDefaultFile = optionParser.hasOption(OptionsDefinitions.OPTIONS.DEFAULTFILE);
1659         final boolean hasFileArgument = optionParser.hasOption(OptionsDefinitions.OPTIONS.FILE);
1660         final boolean hasMainArgument = optionParser.mainArgExists();
1661         if ((hasFileArgument && openDefaultFile) || (hasMainArgument && openDefaultFile)) {
1662             throw new IllegalArgumentException(R("PEDefaultFileFilePathSpecifiedError"));
1663         } else if (hasFileArgument && hasMainArgument) {
1664             throw new IllegalArgumentException(R("PEMainArgAndFileSwitchSpecifiedError"));
1665         }
1666 
1667         String filepath = null;
1668         if (hasFileArgument) {
1669             filepath = cleanFilePathArgument(optionParser.getParam(OptionsDefinitions.OPTIONS.FILE));
1670         } else if (hasMainArgument) {
1671             filepath = cleanFilePathArgument(optionParser.getMainArg());
1672         } else if (openDefaultFile) {
1673             try {
1674                 filepath = getDefaultPolicyFilePath();
1675             } catch (URISyntaxException e) {
1676                 OutputController.getLogger().log(e);
1677                 throw new RuntimeException(e);
1678             }
1679         }
1680         return filepath;
1681     }
1682 
cleanFilePathArgument(String filepath)1683     private static String cleanFilePathArgument(String filepath) {
1684         if (filepath == null) {
1685             return null;
1686         } else if (filepath.isEmpty() || filepath.trim().isEmpty()) {
1687             return null;
1688         } else {
1689             return filepath;
1690         }
1691     }
1692 
1693     /**
1694      * Create a new PolicyEditor instance without passing argv. The returned instance is not
1695      * yet set visible.
1696      * @param filepath a policy file to open at start, or null if no file to load
1697      * @return a reference to a new PolicyEditor instance
1698      */
createInstance(final String filepath)1699     public static PolicyEditor createInstance(final String filepath) {
1700         return new PolicyEditor(filepath);
1701     }
1702 
1703 }
1704