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 JosmTextField tfProxyHttpHost;
105    private JosmTextField tfProxyHttpPort;
106    private JosmTextField tfProxySocksHost;
107    private JosmTextField tfProxySocksPort;
108    private JosmTextField tfProxyHttpUser;
109    private JosmPasswordField tfProxyHttpPassword;
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 = new JosmTextField(), 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 = new JosmTextField(5), 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 = new JosmTextField(20), 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 = new JosmPasswordField(20), 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 = new JosmTextField(20), 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 = new JosmTextField(5), 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        pnl.add(pnlHttpProxyConfigurationPanel = buildHttpProxyConfigurationPanel(), gc);
293
294        // radio button SOCKS proxy
295        gc.gridx = 0;
296        gc.gridy = 4;
297        gc.weightx = 0.0;
298        pnl.add(rbProxyPolicy.get(ProxyPolicy.USE_SOCKS_PROXY), gc);
299
300        gc.gridx = 1;
301        gc.weightx = 1.0;
302        pnl.add(new JLabel(tr("Use a SOCKS proxy")), gc);
303
304        // the panel with the SOCKS configuration parameters
305        gc.gridx = 1;
306        gc.gridy = 5;
307        gc.fill = GridBagConstraints.BOTH;
308        gc.anchor = GridBagConstraints.WEST;
309        gc.weightx = 1.0;
310        gc.weighty = 0.0;
311        pnl.add(pnlSocksProxyConfigurationPanel = buildSocksProxyConfigurationPanel(), gc);
312
313        return pnl;
314    }
315
316    /**
317     * Initializes the panel with the values from the preferences
318     */
319    public final void initFromPreferences() {
320        String policy = Main.pref.get(PROXY_POLICY, null);
321        ProxyPolicy pp = ProxyPolicy.fromName(policy);
322        if (pp == null) {
323            pp = ProxyPolicy.NO_PROXY;
324        }
325        rbProxyPolicy.get(pp).setSelected(true);
326        String value = Main.pref.get("proxy.host", null);
327        if (value != null) {
328            // legacy support
329            tfProxyHttpHost.setText(value);
330            Main.pref.put("proxy.host", null);
331        } else {
332            tfProxyHttpHost.setText(Main.pref.get(PROXY_HTTP_HOST, ""));
333        }
334        value = Main.pref.get("proxy.port", null);
335        if (value != null) {
336            // legacy support
337            tfProxyHttpPort.setText(value);
338            Main.pref.put("proxy.port", null);
339        } else {
340            tfProxyHttpPort.setText(Main.pref.get(PROXY_HTTP_PORT, ""));
341        }
342        tfProxySocksHost.setText(Main.pref.get(PROXY_SOCKS_HOST, ""));
343        tfProxySocksPort.setText(Main.pref.get(PROXY_SOCKS_PORT, ""));
344
345        if (pp.equals(ProxyPolicy.USE_SYSTEM_SETTINGS) && !DefaultProxySelector.willJvmRetrieveSystemProxies()) {
346            Main.warn(tr("JOSM is configured to use proxies from the system setting, but the JVM is not configured to retrieve them. " +
347                         "Resetting preferences to ''No proxy''"));
348            pp = ProxyPolicy.NO_PROXY;
349            rbProxyPolicy.get(pp).setSelected(true);
350        }
351
352        // save the proxy user and the proxy password to a credentials store managed by
353        // the credentials manager
354        CredentialsAgent cm = CredentialsManager.getInstance();
355        try {
356            PasswordAuthentication pa = cm.lookup(RequestorType.PROXY, tfProxyHttpHost.getText());
357            if (pa == null) {
358                tfProxyHttpUser.setText("");
359                tfProxyHttpPassword.setText("");
360            } else {
361                tfProxyHttpUser.setText(pa.getUserName() == null ? "" : pa.getUserName());
362                tfProxyHttpPassword.setText(pa.getPassword() == null ? "" : String.valueOf(pa.getPassword()));
363            }
364        } catch (CredentialsAgentException e) {
365            Main.error(e);
366            tfProxyHttpUser.setText("");
367            tfProxyHttpPassword.setText("");
368        }
369    }
370
371    protected final void updateEnabledState() {
372        boolean isHttpProxy = rbProxyPolicy.get(ProxyPolicy.USE_HTTP_PROXY).isSelected();
373        for (Component c: pnlHttpProxyConfigurationPanel.getComponents()) {
374            c.setEnabled(isHttpProxy);
375        }
376
377        boolean isSocksProxy = rbProxyPolicy.get(ProxyPolicy.USE_SOCKS_PROXY).isSelected();
378        for (Component c: pnlSocksProxyConfigurationPanel.getComponents()) {
379            c.setEnabled(isSocksProxy);
380        }
381
382        rbProxyPolicy.get(ProxyPolicy.USE_SYSTEM_SETTINGS).setEnabled(DefaultProxySelector.willJvmRetrieveSystemProxies());
383    }
384
385    class ProxyPolicyChangeListener implements ItemListener {
386        @Override
387        public void itemStateChanged(ItemEvent arg0) {
388            updateEnabledState();
389        }
390    }
391
392    /**
393     * Constructs a new {@code ProxyPreferencesPanel}.
394     */
395    public ProxyPreferencesPanel() {
396        setLayout(new GridBagLayout());
397        setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
398        add(buildProxySettingsPanel(), GBC.eop().anchor(GridBagConstraints.NORTHWEST).fill(GridBagConstraints.BOTH));
399
400        initFromPreferences();
401        updateEnabledState();
402
403        HelpUtil.setHelpContext(this, HelpUtil.ht("/Preferences/Connection#ProxySettings"));
404    }
405
406    /**
407     * Saves the current values to the preferences
408     */
409    public void saveToPreferences() {
410        ProxyPolicy policy = null;
411        for (ProxyPolicy pp: ProxyPolicy.values()) {
412            if (rbProxyPolicy.get(pp).isSelected()) {
413                policy = pp;
414                break;
415            }
416        }
417        if (policy == null) {
418            policy = ProxyPolicy.NO_PROXY;
419        }
420        Main.pref.put(PROXY_POLICY, policy.getName());
421        Main.pref.put(PROXY_HTTP_HOST, tfProxyHttpHost.getText());
422        Main.pref.put(PROXY_HTTP_PORT, tfProxyHttpPort.getText());
423        Main.pref.put(PROXY_SOCKS_HOST, tfProxySocksHost.getText());
424        Main.pref.put(PROXY_SOCKS_PORT, tfProxySocksPort.getText());
425
426        // update the proxy selector
427        ProxySelector selector = ProxySelector.getDefault();
428        if (selector instanceof DefaultProxySelector) {
429            ((DefaultProxySelector) selector).initFromPreferences();
430        }
431
432        CredentialsAgent cm = CredentialsManager.getInstance();
433        try {
434            PasswordAuthentication pa = new PasswordAuthentication(
435                    tfProxyHttpUser.getText().trim(),
436                    tfProxyHttpPassword.getPassword()
437            );
438            cm.store(RequestorType.PROXY, tfProxyHttpHost.getText(), pa);
439        } catch (CredentialsAgentException e) {
440            Main.error(e);
441        }
442    }
443}