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