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