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