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.Locale;
019import java.util.Map;
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.Main;
028import org.openstreetmap.josm.gui.help.HelpUtil;
029import org.openstreetmap.josm.gui.widgets.JMultilineLabel;
030import org.openstreetmap.josm.gui.widgets.JosmPasswordField;
031import org.openstreetmap.josm.gui.widgets.JosmTextField;
032import org.openstreetmap.josm.gui.widgets.VerticallyScrollablePanel;
033import org.openstreetmap.josm.io.DefaultProxySelector;
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.tools.GBC;
038
039/**
040 * Component allowing input of proxy settings.
041 */
042public class ProxyPreferencesPanel extends VerticallyScrollablePanel {
043
044    /**
045     * The proxy policy is how JOSM will use proxy information.
046     */
047    public enum ProxyPolicy {
048        /** No proxy: JOSM will access Internet resources directly */
049        NO_PROXY("no-proxy"),
050        /** Use system settings: JOSM will use system proxy settings */
051        USE_SYSTEM_SETTINGS("use-system-settings"),
052        /** Use HTTP proxy: JOSM will use the given HTTP proxy, configured manually */
053        USE_HTTP_PROXY("use-http-proxy"),
054        /** Use HTTP proxy: JOSM will use the given SOCKS proxy */
055        USE_SOCKS_PROXY("use-socks-proxy");
056
057        private String policyName;
058        ProxyPolicy(String policyName) {
059            this.policyName = policyName;
060        }
061
062        /**
063         * Replies the policy name, to be stored in proxy preferences.
064         * @return the policy unique name
065         */
066        public String getName() {
067            return policyName;
068        }
069
070        /**
071         * Retrieves a proxy policy from its name found in preferences.
072         * @param policyName The policy name
073         * @return The proxy policy matching the given name, or {@code null}
074         */
075        public static ProxyPolicy fromName(String policyName) {
076            if (policyName == null) return null;
077            policyName = policyName.trim().toLowerCase(Locale.ENGLISH);
078            for (ProxyPolicy pp: values()) {
079                if (pp.getName().equals(policyName))
080                    return pp;
081            }
082            return null;
083        }
084    }
085
086    /** Property key for proxy policy */
087    public static final String PROXY_POLICY = "proxy.policy";
088    /** Property key for HTTP proxy host */
089    public static final String PROXY_HTTP_HOST = "proxy.http.host";
090    /** Property key for HTTP proxy port */
091    public static final String PROXY_HTTP_PORT = "proxy.http.port";
092    /** Property key for SOCKS proxy host */
093    public static final String PROXY_SOCKS_HOST = "proxy.socks.host";
094    /** Property key for SOCKS proxy port */
095    public static final String PROXY_SOCKS_PORT = "proxy.socks.port";
096    /** Property key for proxy username */
097    public static final String PROXY_USER = "proxy.user";
098    /** Property key for proxy password */
099    public static final String PROXY_PASS = "proxy.pass";
100    /** Property key for proxy exceptions list */
101    public static final String PROXY_EXCEPTIONS = "proxy.exceptions";
102
103    private transient Map<ProxyPolicy, JRadioButton> rbProxyPolicy;
104    private final JosmTextField tfProxyHttpHost = new JosmTextField();
105    private final JosmTextField tfProxyHttpPort = new JosmTextField(5);
106    private final JosmTextField tfProxySocksHost = new JosmTextField(20);
107    private final JosmTextField tfProxySocksPort = new JosmTextField(5);
108    private final JosmTextField tfProxyHttpUser = new JosmTextField(20);
109    private final JosmPasswordField tfProxyHttpPassword = new JosmPasswordField(20);
110
111    private JPanel pnlHttpProxyConfigurationPanel;
112    private JPanel pnlSocksProxyConfigurationPanel;
113
114    /**
115     * Builds the panel for the HTTP proxy configuration
116     *
117     * @return panel with HTTP proxy configuration
118     */
119    protected final JPanel buildHttpProxyConfigurationPanel() {
120        JPanel pnl = new JPanel(new GridBagLayout()) {
121            @Override
122            public Dimension getMinimumSize() {
123                return getPreferredSize();
124            }
125        };
126        GridBagConstraints gc = new GridBagConstraints();
127
128        gc.anchor = GridBagConstraints.WEST;
129        gc.insets = new Insets(5, 5, 0, 0);
130        gc.fill = GridBagConstraints.HORIZONTAL;
131        gc.weightx = 0.0;
132        pnl.add(new JLabel(tr("Host:")), gc);
133
134        gc.gridx = 1;
135        gc.weightx = 1.0;
136        pnl.add(tfProxyHttpHost, gc);
137
138        gc.gridy = 1;
139        gc.gridx = 0;
140        gc.fill = GridBagConstraints.NONE;
141        gc.weightx = 0.0;
142        pnl.add(new JLabel(trc("server", "Port:")), gc);
143
144        gc.gridx = 1;
145        gc.weightx = 1.0;
146        pnl.add(tfProxyHttpPort, gc);
147        tfProxyHttpPort.setMinimumSize(tfProxyHttpPort.getPreferredSize());
148
149        gc.gridy = 2;
150        gc.gridx = 0;
151        gc.gridwidth = 2;
152        gc.fill = GridBagConstraints.HORIZONTAL;
153        gc.weightx = 1.0;
154        pnl.add(new JMultilineLabel(tr("Please enter a username and a password if your proxy requires authentication.")), gc);
155
156        gc.gridy = 3;
157        gc.gridx = 0;
158        gc.gridwidth = 1;
159        gc.fill = GridBagConstraints.NONE;
160        gc.weightx = 0.0;
161        pnl.add(new JLabel(tr("User:")), gc);
162
163        gc.gridy = 3;
164        gc.gridx = 1;
165        gc.weightx = 1.0;
166        pnl.add(tfProxyHttpUser, gc);
167        tfProxyHttpUser.setMinimumSize(tfProxyHttpUser.getPreferredSize());
168
169        gc.gridy = 4;
170        gc.gridx = 0;
171        gc.weightx = 0.0;
172        pnl.add(new JLabel(tr("Password:")), gc);
173
174        gc.gridx = 1;
175        gc.weightx = 1.0;
176        pnl.add(tfProxyHttpPassword, gc);
177        tfProxyHttpPassword.setMinimumSize(tfProxyHttpPassword.getPreferredSize());
178
179        // add an extra spacer, otherwise the layout is broken
180        gc.gridy = 5;
181        gc.gridx = 0;
182        gc.gridwidth = 2;
183        gc.fill = GridBagConstraints.BOTH;
184        gc.weightx = 1.0;
185        gc.weighty = 1.0;
186        pnl.add(new JPanel(), gc);
187        return pnl;
188    }
189
190    /**
191     * Builds the panel for the SOCKS proxy configuration
192     *
193     * @return panel with SOCKS proxy configuration
194     */
195    protected final JPanel buildSocksProxyConfigurationPanel() {
196        JPanel pnl = new JPanel(new GridBagLayout()) {
197            @Override
198            public Dimension getMinimumSize() {
199                return getPreferredSize();
200            }
201        };
202        GridBagConstraints gc = new GridBagConstraints();
203        gc.anchor = GridBagConstraints.WEST;
204        gc.insets = new Insets(5, 5, 0, 0);
205        gc.fill = GridBagConstraints.HORIZONTAL;
206        gc.weightx = 0.0;
207        pnl.add(new JLabel(tr("Host:")), gc);
208
209        gc.gridx = 1;
210        gc.weightx = 1.0;
211        pnl.add(tfProxySocksHost, gc);
212
213        gc.gridy = 1;
214        gc.gridx = 0;
215        gc.weightx = 0.0;
216        gc.fill = GridBagConstraints.NONE;
217        pnl.add(new JLabel(trc("server", "Port:")), gc);
218
219        gc.gridx = 1;
220        gc.weightx = 1.0;
221        pnl.add(tfProxySocksPort, gc);
222        tfProxySocksPort.setMinimumSize(tfProxySocksPort.getPreferredSize());
223
224        // add an extra spacer, otherwise the layout is broken
225        gc.gridy = 2;
226        gc.gridx = 0;
227        gc.gridwidth = 2;
228        gc.fill = GridBagConstraints.BOTH;
229        gc.weightx = 1.0;
230        gc.weighty = 1.0;
231        pnl.add(new JPanel(), gc);
232        return pnl;
233    }
234
235    protected final JPanel buildProxySettingsPanel() {
236        JPanel pnl = new JPanel(new GridBagLayout());
237        GridBagConstraints gc = new GridBagConstraints();
238
239        ButtonGroup bgProxyPolicy = new ButtonGroup();
240        rbProxyPolicy = new EnumMap<>(ProxyPolicy.class);
241        ProxyPolicyChangeListener policyChangeListener = new ProxyPolicyChangeListener();
242        for (ProxyPolicy pp: ProxyPolicy.values()) {
243            rbProxyPolicy.put(pp, new JRadioButton());
244            bgProxyPolicy.add(rbProxyPolicy.get(pp));
245            rbProxyPolicy.get(pp).addItemListener(policyChangeListener);
246        }
247
248        // radio button "No proxy"
249        gc.gridx = 0;
250        gc.gridy = 0;
251        gc.fill = GridBagConstraints.HORIZONTAL;
252        gc.anchor = GridBagConstraints.NORTHWEST;
253        gc.weightx = 0.0;
254        pnl.add(rbProxyPolicy.get(ProxyPolicy.NO_PROXY), gc);
255
256        gc.gridx = 1;
257        gc.weightx = 1.0;
258        pnl.add(new JLabel(tr("No proxy")), gc);
259
260        // radio button "System settings"
261        gc.gridx = 0;
262        gc.gridy = 1;
263        gc.weightx = 0.0;
264        pnl.add(rbProxyPolicy.get(ProxyPolicy.USE_SYSTEM_SETTINGS), gc);
265
266        gc.gridx = 1;
267        gc.weightx = 1.0;
268        String msg;
269        if (DefaultProxySelector.willJvmRetrieveSystemProxies()) {
270            msg = tr("Use standard system settings");
271        } else {
272            msg = tr("Use standard system settings (disabled. Start JOSM with <tt>-Djava.net.useSystemProxies=true</tt> to enable)");
273        }
274        pnl.add(new JMultilineLabel("<html>" + msg + "</html>"), gc);
275
276        // radio button http proxy
277        gc.gridx = 0;
278        gc.gridy = 2;
279        gc.weightx = 0.0;
280        pnl.add(rbProxyPolicy.get(ProxyPolicy.USE_HTTP_PROXY), gc);
281
282        gc.gridx = 1;
283        gc.weightx = 1.0;
284        pnl.add(new JLabel(tr("Manually configure a HTTP proxy")), gc);
285
286        // the panel with the http proxy configuration parameters
287        gc.gridx = 1;
288        gc.gridy = 3;
289        gc.fill = GridBagConstraints.HORIZONTAL;
290        gc.weightx = 1.0;
291        gc.weighty = 0.0;
292        pnlHttpProxyConfigurationPanel = buildHttpProxyConfigurationPanel();
293        pnl.add(pnlHttpProxyConfigurationPanel, gc);
294
295        // radio button SOCKS proxy
296        gc.gridx = 0;
297        gc.gridy = 4;
298        gc.weightx = 0.0;
299        pnl.add(rbProxyPolicy.get(ProxyPolicy.USE_SOCKS_PROXY), gc);
300
301        gc.gridx = 1;
302        gc.weightx = 1.0;
303        pnl.add(new JLabel(tr("Use a SOCKS proxy")), gc);
304
305        // the panel with the SOCKS configuration parameters
306        gc.gridx = 1;
307        gc.gridy = 5;
308        gc.fill = GridBagConstraints.BOTH;
309        gc.anchor = GridBagConstraints.WEST;
310        gc.weightx = 1.0;
311        gc.weighty = 0.0;
312        pnlSocksProxyConfigurationPanel = buildSocksProxyConfigurationPanel();
313        pnl.add(pnlSocksProxyConfigurationPanel, gc);
314
315        return pnl;
316    }
317
318    /**
319     * Initializes the panel with the values from the preferences
320     */
321    public final void initFromPreferences() {
322        String policy = Main.pref.get(PROXY_POLICY, null);
323        ProxyPolicy pp = ProxyPolicy.fromName(policy);
324        if (pp == null) {
325            pp = ProxyPolicy.NO_PROXY;
326        }
327        rbProxyPolicy.get(pp).setSelected(true);
328        String value = Main.pref.get("proxy.host", null);
329        if (value != null) {
330            // legacy support
331            tfProxyHttpHost.setText(value);
332            Main.pref.put("proxy.host", null);
333        } else {
334            tfProxyHttpHost.setText(Main.pref.get(PROXY_HTTP_HOST, ""));
335        }
336        value = Main.pref.get("proxy.port", null);
337        if (value != null) {
338            // legacy support
339            tfProxyHttpPort.setText(value);
340            Main.pref.put("proxy.port", null);
341        } else {
342            tfProxyHttpPort.setText(Main.pref.get(PROXY_HTTP_PORT, ""));
343        }
344        tfProxySocksHost.setText(Main.pref.get(PROXY_SOCKS_HOST, ""));
345        tfProxySocksPort.setText(Main.pref.get(PROXY_SOCKS_PORT, ""));
346
347        if (pp.equals(ProxyPolicy.USE_SYSTEM_SETTINGS) && !DefaultProxySelector.willJvmRetrieveSystemProxies()) {
348            Main.warn(tr("JOSM is configured to use proxies from the system setting, but the JVM is not configured to retrieve them. " +
349                         "Resetting preferences to ''No proxy''"));
350            pp = ProxyPolicy.NO_PROXY;
351            rbProxyPolicy.get(pp).setSelected(true);
352        }
353
354        // save the proxy user and the proxy password to a credentials store managed by
355        // the credentials manager
356        CredentialsAgent cm = CredentialsManager.getInstance();
357        try {
358            PasswordAuthentication pa = cm.lookup(RequestorType.PROXY, tfProxyHttpHost.getText());
359            if (pa == null) {
360                tfProxyHttpUser.setText("");
361                tfProxyHttpPassword.setText("");
362            } else {
363                tfProxyHttpUser.setText(pa.getUserName() == null ? "" : pa.getUserName());
364                tfProxyHttpPassword.setText(pa.getPassword() == null ? "" : String.valueOf(pa.getPassword()));
365            }
366        } catch (CredentialsAgentException e) {
367            Main.error(e);
368            tfProxyHttpUser.setText("");
369            tfProxyHttpPassword.setText("");
370        }
371    }
372
373    protected final void updateEnabledState() {
374        boolean isHttpProxy = rbProxyPolicy.get(ProxyPolicy.USE_HTTP_PROXY).isSelected();
375        for (Component c: pnlHttpProxyConfigurationPanel.getComponents()) {
376            c.setEnabled(isHttpProxy);
377        }
378
379        boolean isSocksProxy = rbProxyPolicy.get(ProxyPolicy.USE_SOCKS_PROXY).isSelected();
380        for (Component c: pnlSocksProxyConfigurationPanel.getComponents()) {
381            c.setEnabled(isSocksProxy);
382        }
383
384        rbProxyPolicy.get(ProxyPolicy.USE_SYSTEM_SETTINGS).setEnabled(DefaultProxySelector.willJvmRetrieveSystemProxies());
385    }
386
387    class ProxyPolicyChangeListener implements ItemListener {
388        @Override
389        public void itemStateChanged(ItemEvent arg0) {
390            updateEnabledState();
391        }
392    }
393
394    /**
395     * Constructs a new {@code ProxyPreferencesPanel}.
396     */
397    public ProxyPreferencesPanel() {
398        setLayout(new GridBagLayout());
399        setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
400        add(buildProxySettingsPanel(), GBC.eop().anchor(GridBagConstraints.NORTHWEST).fill(GridBagConstraints.BOTH));
401
402        initFromPreferences();
403        updateEnabledState();
404
405        HelpUtil.setHelpContext(this, HelpUtil.ht("/Preferences/Connection#ProxySettings"));
406    }
407
408    /**
409     * Saves the current values to the preferences
410     */
411    public void saveToPreferences() {
412        ProxyPolicy policy = null;
413        for (ProxyPolicy pp: ProxyPolicy.values()) {
414            if (rbProxyPolicy.get(pp).isSelected()) {
415                policy = pp;
416                break;
417            }
418        }
419        if (policy == null) {
420            policy = ProxyPolicy.NO_PROXY;
421        }
422        Main.pref.put(PROXY_POLICY, policy.getName());
423        Main.pref.put(PROXY_HTTP_HOST, tfProxyHttpHost.getText());
424        Main.pref.put(PROXY_HTTP_PORT, tfProxyHttpPort.getText());
425        Main.pref.put(PROXY_SOCKS_HOST, tfProxySocksHost.getText());
426        Main.pref.put(PROXY_SOCKS_PORT, tfProxySocksPort.getText());
427
428        // update the proxy selector
429        ProxySelector selector = ProxySelector.getDefault();
430        if (selector instanceof DefaultProxySelector) {
431            ((DefaultProxySelector) selector).initFromPreferences();
432        }
433
434        CredentialsAgent cm = CredentialsManager.getInstance();
435        try {
436            PasswordAuthentication pa = new PasswordAuthentication(
437                    tfProxyHttpUser.getText().trim(),
438                    tfProxyHttpPassword.getPassword()
439            );
440            cm.store(RequestorType.PROXY, tfProxyHttpHost.getText(), pa);
441        } catch (CredentialsAgentException e) {
442            Main.error(e);
443        }
444    }
445}