001// License: GPL. For details, see LICENSE file. 002package org.openstreetmap.josm.gui.preferences.server; 003 004import static java.awt.GridBagConstraints.HORIZONTAL; 005import static org.openstreetmap.josm.tools.I18n.tr; 006import static org.openstreetmap.josm.tools.I18n.trc; 007 008import java.awt.Component; 009import java.awt.Dimension; 010import java.awt.GridBagConstraints; 011import java.awt.GridBagLayout; 012import java.awt.Insets; 013import java.awt.event.ItemEvent; 014import java.awt.event.ItemListener; 015import java.net.Authenticator.RequestorType; 016import java.net.PasswordAuthentication; 017import java.net.ProxySelector; 018import java.util.Arrays; 019import java.util.EnumMap; 020import java.util.Map; 021import java.util.Optional; 022 023import javax.swing.BorderFactory; 024import javax.swing.Box; 025import javax.swing.ButtonGroup; 026import javax.swing.JLabel; 027import javax.swing.JPanel; 028import javax.swing.JRadioButton; 029 030import org.openstreetmap.josm.gui.help.HelpUtil; 031import org.openstreetmap.josm.gui.widgets.JMultilineLabel; 032import org.openstreetmap.josm.gui.widgets.JosmPasswordField; 033import org.openstreetmap.josm.gui.widgets.JosmTextField; 034import org.openstreetmap.josm.gui.widgets.VerticallyScrollablePanel; 035import org.openstreetmap.josm.io.DefaultProxySelector; 036import org.openstreetmap.josm.io.ProxyPolicy; 037import org.openstreetmap.josm.io.auth.CredentialsAgent; 038import org.openstreetmap.josm.io.auth.CredentialsAgentException; 039import org.openstreetmap.josm.io.auth.CredentialsManager; 040import org.openstreetmap.josm.spi.preferences.Config; 041import org.openstreetmap.josm.spi.preferences.IPreferences; 042import org.openstreetmap.josm.tools.GBC; 043import org.openstreetmap.josm.tools.Logging; 044 045/** 046 * Component allowing input of proxy settings. 047 */ 048public class ProxyPreferencesPanel extends VerticallyScrollablePanel { 049 050 static final class AutoSizePanel extends JPanel { 051 AutoSizePanel() { 052 super(new GridBagLayout()); 053 } 054 055 @Override 056 public Dimension getMinimumSize() { 057 return getPreferredSize(); 058 } 059 } 060 061 private transient Map<ProxyPolicy, JRadioButton> rbProxyPolicy; 062 private final JosmTextField tfProxyHttpHost = new JosmTextField(); 063 private final JosmTextField tfProxyHttpPort = new JosmTextField(5); 064 private final JosmTextField tfProxySocksHost = new JosmTextField(20); 065 private final JosmTextField tfProxySocksPort = new JosmTextField(5); 066 private final JosmTextField tfProxyHttpUser = new JosmTextField(20); 067 private final JosmPasswordField tfProxyHttpPassword = new JosmPasswordField(20); 068 069 private JPanel pnlHttpProxyConfigurationPanel; 070 private JPanel pnlSocksProxyConfigurationPanel; 071 072 /** 073 * Builds the panel for the HTTP proxy configuration 074 * 075 * @return panel with HTTP proxy configuration 076 */ 077 protected final JPanel buildHttpProxyConfigurationPanel() { 078 JPanel pnl = new AutoSizePanel(); 079 GridBagConstraints gc = new GridBagConstraints(); 080 081 gc.anchor = GridBagConstraints.LINE_START; 082 gc.insets = new Insets(5, 5, 0, 0); 083 gc.fill = HORIZONTAL; 084 gc.weightx = 0.0; 085 pnl.add(new JLabel(tr("Host:")), gc); 086 087 gc.gridx = 1; 088 gc.weightx = 1.0; 089 pnl.add(tfProxyHttpHost, gc); 090 091 gc.gridy = 1; 092 gc.gridx = 0; 093 gc.fill = GridBagConstraints.NONE; 094 gc.weightx = 0.0; 095 pnl.add(new JLabel(trc("server", "Port:")), gc); 096 097 gc.gridx = 1; 098 gc.weightx = 1.0; 099 pnl.add(tfProxyHttpPort, gc); 100 tfProxyHttpPort.setMinimumSize(tfProxyHttpPort.getPreferredSize()); 101 102 gc.gridy = 2; 103 gc.gridx = 0; 104 gc.gridwidth = 2; 105 gc.fill = HORIZONTAL; 106 gc.weightx = 1.0; 107 pnl.add(new JMultilineLabel(tr("Please enter a username and a password if your proxy requires authentication.")), gc); 108 109 gc.gridy = 3; 110 gc.gridx = 0; 111 gc.gridwidth = 1; 112 gc.fill = GridBagConstraints.NONE; 113 gc.weightx = 0.0; 114 pnl.add(new JLabel(tr("User:")), gc); 115 116 gc.gridy = 3; 117 gc.gridx = 1; 118 gc.weightx = 1.0; 119 pnl.add(tfProxyHttpUser, gc); 120 tfProxyHttpUser.setMinimumSize(tfProxyHttpUser.getPreferredSize()); 121 122 gc.gridy = 4; 123 gc.gridx = 0; 124 gc.weightx = 0.0; 125 pnl.add(new JLabel(tr("Password:")), gc); 126 127 gc.gridx = 1; 128 gc.weightx = 1.0; 129 pnl.add(tfProxyHttpPassword, gc); 130 tfProxyHttpPassword.setMinimumSize(tfProxyHttpPassword.getPreferredSize()); 131 132 // add an extra spacer, otherwise the layout is broken 133 gc.gridy = 5; 134 gc.gridx = 0; 135 gc.gridwidth = 2; 136 gc.fill = GridBagConstraints.BOTH; 137 gc.weightx = 1.0; 138 gc.weighty = 1.0; 139 pnl.add(new JPanel(), gc); 140 return pnl; 141 } 142 143 /** 144 * Builds the panel for the SOCKS proxy configuration 145 * 146 * @return panel with SOCKS proxy configuration 147 */ 148 protected final JPanel buildSocksProxyConfigurationPanel() { 149 JPanel pnl = new AutoSizePanel(); 150 GridBagConstraints gc = new GridBagConstraints(); 151 gc.anchor = GridBagConstraints.LINE_START; 152 gc.insets = new Insets(5, 5, 0, 0); 153 gc.fill = HORIZONTAL; 154 gc.weightx = 0.0; 155 pnl.add(new JLabel(tr("Host:")), gc); 156 157 gc.gridx = 1; 158 gc.weightx = 1.0; 159 pnl.add(tfProxySocksHost, gc); 160 161 gc.gridy = 1; 162 gc.gridx = 0; 163 gc.weightx = 0.0; 164 gc.fill = GridBagConstraints.NONE; 165 pnl.add(new JLabel(trc("server", "Port:")), gc); 166 167 gc.gridx = 1; 168 gc.weightx = 1.0; 169 pnl.add(tfProxySocksPort, gc); 170 tfProxySocksPort.setMinimumSize(tfProxySocksPort.getPreferredSize()); 171 172 // add an extra spacer, otherwise the layout is broken 173 gc.gridy = 2; 174 gc.gridx = 0; 175 gc.gridwidth = 2; 176 gc.fill = GridBagConstraints.BOTH; 177 gc.weightx = 1.0; 178 gc.weighty = 1.0; 179 pnl.add(new JPanel(), gc); 180 return pnl; 181 } 182 183 protected final JPanel buildProxySettingsPanel() { 184 JPanel pnl = new JPanel(new GridBagLayout()); 185 186 ButtonGroup bgProxyPolicy = new ButtonGroup(); 187 rbProxyPolicy = new EnumMap<>(ProxyPolicy.class); 188 ProxyPolicyChangeListener policyChangeListener = new ProxyPolicyChangeListener(); 189 for (ProxyPolicy pp: ProxyPolicy.values()) { 190 rbProxyPolicy.put(pp, new JRadioButton()); 191 bgProxyPolicy.add(rbProxyPolicy.get(pp)); 192 rbProxyPolicy.get(pp).addItemListener(policyChangeListener); 193 } 194 195 // radio button "No proxy" 196 pnl.add(newRadioButton(ProxyPolicy.NO_PROXY, tr("No proxy")), GBC.eop().anchor(GBC.NORTHWEST)); 197 198 // radio button "System settings" 199 pnl.add(newRadioButton(ProxyPolicy.USE_SYSTEM_SETTINGS, tr("Use standard system settings")), GBC.eol()); 200 if (!DefaultProxySelector.willJvmRetrieveSystemProxies()) { 201 String msg = tr("Use standard system settings (disabled. Start JOSM with <tt>-Djava.net.useSystemProxies=true</tt> to enable)"); 202 pnl.add(new JMultilineLabel("<html>" + msg + "</html>"), GBC.eop().fill(HORIZONTAL)); 203 } 204 205 // radio button http proxy 206 pnl.add(newRadioButton(ProxyPolicy.USE_HTTP_PROXY, tr("Manually configure a HTTP proxy")), GBC.eol()); 207 208 // the panel with the http proxy configuration parameters 209 pnlHttpProxyConfigurationPanel = buildHttpProxyConfigurationPanel(); 210 pnl.add(pnlHttpProxyConfigurationPanel, GBC.eop().fill(HORIZONTAL)); 211 212 // radio button SOCKS proxy 213 pnl.add(newRadioButton(ProxyPolicy.USE_SOCKS_PROXY, tr("Use a SOCKS proxy")), GBC.eol()); 214 215 // the panel with the SOCKS configuration parameters 216 pnlSocksProxyConfigurationPanel = buildSocksProxyConfigurationPanel(); 217 pnl.add(pnlSocksProxyConfigurationPanel, GBC.eop().fill(HORIZONTAL)); 218 219 pnl.add(Box.createVerticalGlue(), GBC.eol().fill()); 220 221 return pnl; 222 } 223 224 private JRadioButton newRadioButton(ProxyPolicy policy, String text) { 225 JRadioButton radioButton = rbProxyPolicy.get(policy); 226 radioButton.setText(text); 227 return radioButton; 228 } 229 230 /** 231 * Initializes the panel with the values from the preferences 232 */ 233 public final void initFromPreferences() { 234 IPreferences pref = Config.getPref(); 235 ProxyPolicy pp = Optional.ofNullable(ProxyPolicy.fromName(pref.get(DefaultProxySelector.PROXY_POLICY, null))) 236 .orElse(ProxyPolicy.NO_PROXY); 237 rbProxyPolicy.get(pp).setSelected(true); 238 tfProxyHttpHost.setText(pref.get(DefaultProxySelector.PROXY_HTTP_HOST, "")); 239 tfProxyHttpPort.setText(pref.get(DefaultProxySelector.PROXY_HTTP_PORT, "")); 240 tfProxySocksHost.setText(pref.get(DefaultProxySelector.PROXY_SOCKS_HOST, "")); 241 tfProxySocksPort.setText(pref.get(DefaultProxySelector.PROXY_SOCKS_PORT, "")); 242 243 if (pp == ProxyPolicy.USE_SYSTEM_SETTINGS && !DefaultProxySelector.willJvmRetrieveSystemProxies()) { 244 Logging.warn(tr("JOSM is configured to use proxies from the system setting, but the JVM is not configured to retrieve them. " + 245 "Resetting preferences to ''No proxy''")); 246 pp = ProxyPolicy.NO_PROXY; 247 rbProxyPolicy.get(pp).setSelected(true); 248 } 249 250 // save the proxy user and the proxy password to a credentials store managed by 251 // the credentials manager 252 CredentialsAgent cm = CredentialsManager.getInstance(); 253 try { 254 PasswordAuthentication pa = cm.lookup(RequestorType.PROXY, tfProxyHttpHost.getText()); 255 if (pa == null) { 256 tfProxyHttpUser.setText(""); 257 tfProxyHttpPassword.setText(""); 258 } else { 259 tfProxyHttpUser.setText(pa.getUserName() == null ? "" : pa.getUserName()); 260 tfProxyHttpPassword.setText(pa.getPassword() == null ? "" : String.valueOf(pa.getPassword())); 261 } 262 } catch (CredentialsAgentException e) { 263 Logging.error(e); 264 tfProxyHttpUser.setText(""); 265 tfProxyHttpPassword.setText(""); 266 } 267 } 268 269 protected final void updateEnabledState() { 270 boolean isHttpProxy = rbProxyPolicy.get(ProxyPolicy.USE_HTTP_PROXY).isSelected(); 271 for (Component c: pnlHttpProxyConfigurationPanel.getComponents()) { 272 c.setEnabled(isHttpProxy); 273 } 274 275 boolean isSocksProxy = rbProxyPolicy.get(ProxyPolicy.USE_SOCKS_PROXY).isSelected(); 276 for (Component c: pnlSocksProxyConfigurationPanel.getComponents()) { 277 c.setEnabled(isSocksProxy); 278 } 279 280 rbProxyPolicy.get(ProxyPolicy.USE_SYSTEM_SETTINGS).setEnabled(DefaultProxySelector.willJvmRetrieveSystemProxies()); 281 } 282 283 class ProxyPolicyChangeListener implements ItemListener { 284 @Override 285 public void itemStateChanged(ItemEvent e) { 286 updateEnabledState(); 287 } 288 } 289 290 /** 291 * Constructs a new {@code ProxyPreferencesPanel}. 292 */ 293 public ProxyPreferencesPanel() { 294 setLayout(new GridBagLayout()); 295 setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5)); 296 add(buildProxySettingsPanel(), GBC.eop().anchor(GridBagConstraints.NORTHWEST).fill(GridBagConstraints.BOTH)); 297 298 initFromPreferences(); 299 updateEnabledState(); 300 301 HelpUtil.setHelpContext(this, HelpUtil.ht("/Preferences/Connection#ProxySettings")); 302 } 303 304 /** 305 * Saves the current values to the preferences 306 */ 307 public void saveToPreferences() { 308 ProxyPolicy policy = Arrays.stream(ProxyPolicy.values()) 309 .filter(pp -> rbProxyPolicy.get(pp).isSelected()) 310 .findFirst().orElse(null); 311 IPreferences pref = Config.getPref(); 312 pref.put(DefaultProxySelector.PROXY_POLICY, Optional.ofNullable(policy).orElse(ProxyPolicy.NO_PROXY).getName()); 313 pref.put(DefaultProxySelector.PROXY_HTTP_HOST, tfProxyHttpHost.getText()); 314 pref.put(DefaultProxySelector.PROXY_HTTP_PORT, tfProxyHttpPort.getText()); 315 pref.put(DefaultProxySelector.PROXY_SOCKS_HOST, tfProxySocksHost.getText()); 316 pref.put(DefaultProxySelector.PROXY_SOCKS_PORT, tfProxySocksPort.getText()); 317 318 // update the proxy selector 319 ProxySelector selector = ProxySelector.getDefault(); 320 if (selector instanceof DefaultProxySelector) { 321 ((DefaultProxySelector) selector).initFromPreferences(); 322 } 323 324 CredentialsAgent cm = CredentialsManager.getInstance(); 325 try { 326 PasswordAuthentication pa = new PasswordAuthentication( 327 tfProxyHttpUser.getText().trim(), 328 tfProxyHttpPassword.getPassword() 329 ); 330 cm.store(RequestorType.PROXY, tfProxyHttpHost.getText(), pa); 331 } catch (CredentialsAgentException e) { 332 Logging.error(e); 333 } 334 } 335}