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