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