001// License: GPL. For details, see LICENSE file. 002package org.openstreetmap.josm.gui.preferences.remotecontrol; 003 004import static org.openstreetmap.josm.tools.I18n.tr; 005 006import java.awt.Color; 007import java.awt.Font; 008import java.awt.GridBagLayout; 009import java.awt.event.ActionEvent; 010import java.awt.event.ActionListener; 011import java.io.IOException; 012import java.security.GeneralSecurityException; 013import java.security.KeyStore; 014import java.security.KeyStoreException; 015import java.security.NoSuchAlgorithmException; 016import java.security.cert.CertificateException; 017import java.util.LinkedHashMap; 018import java.util.Map; 019import java.util.Map.Entry; 020 021import javax.swing.BorderFactory; 022import javax.swing.Box; 023import javax.swing.JButton; 024import javax.swing.JCheckBox; 025import javax.swing.JLabel; 026import javax.swing.JOptionPane; 027import javax.swing.JPanel; 028import javax.swing.JSeparator; 029 030import org.openstreetmap.josm.Main; 031import org.openstreetmap.josm.gui.preferences.DefaultTabPreferenceSetting; 032import org.openstreetmap.josm.gui.preferences.PreferenceSetting; 033import org.openstreetmap.josm.gui.preferences.PreferenceSettingFactory; 034import org.openstreetmap.josm.gui.preferences.PreferenceTabbedPane; 035import org.openstreetmap.josm.gui.util.GuiHelper; 036import org.openstreetmap.josm.io.remotecontrol.PermissionPrefWithDefault; 037import org.openstreetmap.josm.io.remotecontrol.RemoteControl; 038import org.openstreetmap.josm.io.remotecontrol.RemoteControlHttpsServer; 039import org.openstreetmap.josm.io.remotecontrol.handler.RequestHandler; 040import org.openstreetmap.josm.tools.GBC; 041import org.openstreetmap.josm.tools.PlatformHookWindows; 042 043/** 044 * Preference settings for Remote Control. 045 * 046 * @author Frederik Ramm 047 */ 048public final class RemoteControlPreference extends DefaultTabPreferenceSetting { 049 050 /** 051 * Factory used to build a new instance of this preference setting 052 */ 053 public static class Factory implements PreferenceSettingFactory { 054 055 @Override 056 public PreferenceSetting createPreferenceSetting() { 057 return new RemoteControlPreference(); 058 } 059 } 060 061 private RemoteControlPreference() { 062 super(/* ICON(preferences/) */ "remotecontrol", tr("Remote Control"), tr("Settings for the remote control feature.")); 063 for (PermissionPrefWithDefault p : PermissionPrefWithDefault.getPermissionPrefs()) { 064 JCheckBox cb = new JCheckBox(p.preferenceText); 065 cb.setSelected(p.isAllowed()); 066 prefs.put(p, cb); 067 } 068 } 069 private final Map<PermissionPrefWithDefault, JCheckBox> prefs = new LinkedHashMap<>(); 070 private JCheckBox enableRemoteControl; 071 private JCheckBox enableHttpsSupport; 072 073 private JButton installCertificate; 074 private JButton uninstallCertificate; 075 076 private final JCheckBox loadInNewLayer = new JCheckBox(tr("Download objects to new layer")); 077 private final JCheckBox alwaysAskUserConfirm = new JCheckBox(tr("Confirm all Remote Control actions manually")); 078 079 @Override 080 public void addGui(final PreferenceTabbedPane gui) { 081 082 JPanel remote = new JPanel(new GridBagLayout()); 083 084 final JLabel descLabel = new JLabel("<html>" 085 + tr("Allows JOSM to be controlled from other applications, e.g. from a web browser.") 086 + "</html>"); 087 descLabel.setFont(descLabel.getFont().deriveFont(Font.PLAIN)); 088 remote.add(descLabel, GBC.eol().insets(5, 5, 0, 10).fill(GBC.HORIZONTAL)); 089 090 final JLabel portLabel = new JLabel("<html>" + tr("JOSM will always listen at <b>port {0}</b> (http) and <b>port {1}</b> (https) on localhost." 091 + "<br>These ports are not configurable because they are referenced by external applications talking to JOSM.", 092 Main.pref.get("remote.control.port", "8111"), 093 Main.pref.get("remote.control.https.port", "8112")) + "</html>"); 094 portLabel.setFont(portLabel.getFont().deriveFont(Font.PLAIN)); 095 remote.add(portLabel, GBC.eol().insets(5, 5, 0, 10).fill(GBC.HORIZONTAL)); 096 097 enableRemoteControl = new JCheckBox(tr("Enable remote control"), RemoteControl.PROP_REMOTECONTROL_ENABLED.get()); 098 remote.add(enableRemoteControl, GBC.eol()); 099 100 final JPanel wrapper = new JPanel(); 101 wrapper.setLayout(new GridBagLayout()); 102 wrapper.setBorder(BorderFactory.createTitledBorder(BorderFactory.createLineBorder(Color.gray))); 103 104 remote.add(wrapper, GBC.eol().fill(GBC.HORIZONTAL).insets(5, 5, 5, 5)); 105 106 boolean https = RemoteControl.PROP_REMOTECONTROL_HTTPS_ENABLED.get(); 107 108 enableHttpsSupport = new JCheckBox(tr("Enable HTTPS support"), https); 109 wrapper.add(enableHttpsSupport, GBC.eol().fill(GBC.HORIZONTAL)); 110 111 // Certificate installation only available on Windows for now, see #10033 112 if (Main.isPlatformWindows()) { 113 installCertificate = new JButton(tr("Install...")); 114 uninstallCertificate = new JButton(tr("Uninstall...")); 115 installCertificate.setToolTipText(tr("Install JOSM localhost certificate to system/browser root keystores")); 116 uninstallCertificate.setToolTipText(tr("Uninstall JOSM localhost certificate from system/browser root keystores")); 117 wrapper.add(new JLabel(tr("Certificate:")), GBC.std().insets(15, 5, 0, 0)); 118 wrapper.add(installCertificate, GBC.std().insets(5, 5, 0, 0)); 119 wrapper.add(uninstallCertificate, GBC.eol().insets(5, 5, 0, 0)); 120 enableHttpsSupport.addActionListener(new ActionListener() { 121 @Override 122 public void actionPerformed(ActionEvent e) { 123 installCertificate.setEnabled(enableHttpsSupport.isSelected()); 124 } 125 }); 126 installCertificate.addActionListener(new ActionListener() { 127 @Override 128 public void actionPerformed(ActionEvent e) { 129 try { 130 boolean changed = RemoteControlHttpsServer.setupPlatform( 131 RemoteControlHttpsServer.loadJosmKeystore()); 132 String msg = changed ? 133 tr("Certificate has been successfully installed.") : 134 tr("Certificate is already installed. Nothing to do."); 135 Main.info(msg); 136 JOptionPane.showMessageDialog(wrapper, msg); 137 } catch (IOException | GeneralSecurityException ex) { 138 Main.error(ex); 139 } 140 } 141 }); 142 uninstallCertificate.addActionListener(new ActionListener() { 143 @Override 144 public void actionPerformed(ActionEvent e) { 145 try { 146 String msg; 147 KeyStore ks = PlatformHookWindows.getRootKeystore(); 148 if (ks.containsAlias(RemoteControlHttpsServer.ENTRY_ALIAS)) { 149 Main.info(tr("Removing certificate {0} from root keystore.", RemoteControlHttpsServer.ENTRY_ALIAS)); 150 ks.deleteEntry(RemoteControlHttpsServer.ENTRY_ALIAS); 151 msg = tr("Certificate has been successfully uninstalled."); 152 } else { 153 msg = tr("Certificate is not installed. Nothing to do."); 154 } 155 Main.info(msg); 156 JOptionPane.showMessageDialog(wrapper, msg); 157 } catch (KeyStoreException | NoSuchAlgorithmException | CertificateException | IOException ex) { 158 Main.error(ex); 159 } 160 } 161 }); 162 installCertificate.setEnabled(https); 163 } 164 165 wrapper.add(new JSeparator(), GBC.eop().fill(GBC.HORIZONTAL).insets(15, 5, 15, 5)); 166 167 wrapper.add(new JLabel(tr("Permitted actions:")), GBC.eol().insets(5, 0, 0, 0)); 168 for (JCheckBox p : prefs.values()) { 169 wrapper.add(p, GBC.eol().insets(15, 5, 0, 0).fill(GBC.HORIZONTAL)); 170 } 171 172 wrapper.add(new JSeparator(), GBC.eop().fill(GBC.HORIZONTAL).insets(15, 5, 15, 5)); 173 wrapper.add(loadInNewLayer, GBC.eol().fill(GBC.HORIZONTAL)); 174 wrapper.add(alwaysAskUserConfirm, GBC.eol().fill(GBC.HORIZONTAL)); 175 176 remote.add(Box.createVerticalGlue(), GBC.eol().fill(GBC.VERTICAL)); 177 178 loadInNewLayer.setSelected(Main.pref.getBoolean(RequestHandler.loadInNewLayerKey, RequestHandler.loadInNewLayerDefault)); 179 alwaysAskUserConfirm.setSelected(Main.pref.getBoolean(RequestHandler.globalConfirmationKey, RequestHandler.globalConfirmationDefault)); 180 181 ActionListener remoteControlEnabled = new ActionListener() { 182 183 @Override 184 public void actionPerformed(ActionEvent e) { 185 GuiHelper.setEnabledRec(wrapper, enableRemoteControl.isSelected()); 186 // 'setEnabled(false)' does not work for JLabel with html text, so do it manually 187 // FIXME: use QuadStateCheckBox to make checkboxes unset when disabled 188 if (installCertificate != null && uninstallCertificate != null) { 189 // Install certificate button is enabled if HTTPS is also enabled 190 installCertificate.setEnabled(enableRemoteControl.isSelected() && enableHttpsSupport.isSelected()); 191 // Uninstall certificate button is always enabled 192 uninstallCertificate.setEnabled(true); 193 } 194 } 195 }; 196 enableRemoteControl.addActionListener(remoteControlEnabled); 197 remoteControlEnabled.actionPerformed(null); 198 createPreferenceTabWithScrollPane(gui, remote); 199 } 200 201 @Override 202 public boolean ok() { 203 boolean enabled = enableRemoteControl.isSelected(); 204 boolean httpsEnabled = enableHttpsSupport.isSelected(); 205 boolean changed = RemoteControl.PROP_REMOTECONTROL_ENABLED.put(enabled); 206 boolean httpsChanged = RemoteControl.PROP_REMOTECONTROL_HTTPS_ENABLED.put(httpsEnabled); 207 if (enabled) { 208 for (Entry<PermissionPrefWithDefault, JCheckBox> p : prefs.entrySet()) { 209 Main.pref.put(p.getKey().pref, p.getValue().isSelected()); 210 } 211 Main.pref.put(RequestHandler.loadInNewLayerKey, loadInNewLayer.isSelected()); 212 Main.pref.put(RequestHandler.globalConfirmationKey, alwaysAskUserConfirm.isSelected()); 213 } 214 if (changed) { 215 if (enabled) { 216 RemoteControl.start(); 217 } else { 218 RemoteControl.stop(); 219 } 220 } else if (httpsChanged) { 221 if (httpsEnabled) { 222 RemoteControlHttpsServer.restartRemoteControlHttpsServer(); 223 } else { 224 RemoteControlHttpsServer.stopRemoteControlHttpsServer(); 225 } 226 } 227 return false; 228 } 229}