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