001    // License: GPL. For details, see LICENSE file.
002    package org.openstreetmap.josm.gui.io;
003    
004    import static org.openstreetmap.josm.tools.I18n.tr;
005    
006    import java.awt.BorderLayout;
007    import java.awt.Dimension;
008    import java.awt.FlowLayout;
009    import java.awt.Font;
010    import java.awt.GridBagConstraints;
011    import java.awt.GridBagLayout;
012    import java.awt.Insets;
013    import java.awt.event.ActionEvent;
014    import java.awt.event.FocusAdapter;
015    import java.awt.event.FocusEvent;
016    import java.awt.event.KeyEvent;
017    import java.awt.event.KeyListener;
018    import java.awt.event.WindowAdapter;
019    import java.awt.event.WindowEvent;
020    
021    import javax.swing.AbstractAction;
022    import javax.swing.BorderFactory;
023    import javax.swing.JCheckBox;
024    import javax.swing.JComponent;
025    import javax.swing.JDialog;
026    import javax.swing.JLabel;
027    import javax.swing.JPanel;
028    import javax.swing.JPasswordField;
029    import javax.swing.JTextField;
030    import javax.swing.KeyStroke;
031    
032    import org.openstreetmap.josm.Main;
033    import org.openstreetmap.josm.gui.JMultilineLabel;
034    import org.openstreetmap.josm.gui.SideButton;
035    import org.openstreetmap.josm.gui.help.ContextSensitiveHelpAction;
036    import org.openstreetmap.josm.gui.help.HelpUtil;
037    import org.openstreetmap.josm.gui.preferences.server.ProxyPreferencesPanel;
038    import org.openstreetmap.josm.io.OsmApi;
039    import org.openstreetmap.josm.tools.ImageProvider;
040    import org.openstreetmap.josm.tools.Utils;
041    import org.openstreetmap.josm.tools.WindowGeometry;
042    
043    public class CredentialDialog extends JDialog {
044    
045        static public CredentialDialog getOsmApiCredentialDialog(String username, String password, String host, String saveUsernameAndPasswordCheckboxText) {
046            CredentialDialog dialog = new CredentialDialog(saveUsernameAndPasswordCheckboxText);
047            if (Utils.equal(OsmApi.getOsmApi().getHost(), host)) {
048                dialog.prepareForOsmApiCredentials(username, password);
049            } else {
050                dialog.prepareForOtherHostCredentials(username, password, host);
051            }
052            dialog.pack();
053            return dialog;
054        }
055    
056        static public CredentialDialog getHttpProxyCredentialDialog(String username, String password, String host, String saveUsernameAndPasswordCheckboxText) {
057            CredentialDialog dialog = new CredentialDialog(saveUsernameAndPasswordCheckboxText);
058            dialog.prepareForProxyCredentials(username, password);
059            dialog.pack();
060            return dialog;
061        }
062    
063        private boolean canceled;
064        protected CredentialPanel pnlCredentials;
065        String saveUsernameAndPasswordCheckboxText;
066    
067        public boolean isCanceled() {
068            return canceled;
069        }
070    
071        protected void setCanceled(boolean canceled) {
072            this.canceled = canceled;
073        }
074    
075        @Override
076        public void setVisible(boolean visible) {
077            if (visible) {
078                WindowGeometry.centerInWindow(Main.parent, new Dimension(350,300)).applySafe(this);
079            }
080            super.setVisible(visible);
081        }
082    
083        protected JPanel createButtonPanel() {
084            JPanel pnl = new JPanel(new FlowLayout());
085            pnl.add(new SideButton(new OKAction()));
086            pnl.add(new SideButton(new CancelAction()));
087            pnl.add(new SideButton(new ContextSensitiveHelpAction(HelpUtil.ht("/Dialog/Password"))));
088            return pnl;
089        }
090    
091        protected void build() {
092            getContentPane().setLayout(new BorderLayout());
093            getContentPane().add(createButtonPanel(), BorderLayout.SOUTH);
094    
095            addWindowListener(new WindowEventHander());
096            getRootPane().getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), "escape");
097            getRootPane().getActionMap().put("escape", new CancelAction());
098    
099            getRootPane().setBorder(BorderFactory.createEmptyBorder(5,5,5,5));
100        }
101    
102        public CredentialDialog(String saveUsernameAndPasswordCheckboxText) {
103            this.saveUsernameAndPasswordCheckboxText = saveUsernameAndPasswordCheckboxText;
104            setModalityType(ModalityType.DOCUMENT_MODAL);
105            try {
106                setAlwaysOnTop(true);
107            } catch(SecurityException e) {
108                System.out.println(tr("Warning: failed to put Credential Dialog always on top. Caught security exception."));
109            }
110            build();
111        }
112    
113        public void prepareForOsmApiCredentials(String username, String password) {
114            setTitle(tr("Enter credentials for OSM API"));
115            getContentPane().add(pnlCredentials = new OsmApiCredentialsPanel(this), BorderLayout.CENTER);
116            pnlCredentials.init(username, password);
117            validate();
118        }
119    
120        public void prepareForOtherHostCredentials(String username, String password, String host) {
121            setTitle(tr("Enter credentials for host"));
122            getContentPane().add(pnlCredentials = new OtherHostCredentialsPanel(this, host), BorderLayout.CENTER);
123            pnlCredentials.init(username, password);
124            validate();
125        }
126    
127        public void prepareForProxyCredentials(String username, String password) {
128            setTitle(tr("Enter credentials for HTTP proxy"));
129            getContentPane().add(pnlCredentials = new HttpProxyCredentialsPanel(this), BorderLayout.CENTER);
130            pnlCredentials.init(username, password);
131            validate();
132        }
133    
134        public String getUsername() {
135            if (pnlCredentials== null) return null;
136            return pnlCredentials.getUserName();
137        }
138    
139        public char[] getPassword() {
140            if (pnlCredentials== null) return null;
141            return pnlCredentials.getPassword();
142        }
143    
144        public boolean isSaveCredentials() {
145            if (pnlCredentials== null) return false;
146            return pnlCredentials.isSaveCredentials();
147        }
148    
149        protected static class CredentialPanel extends JPanel {
150            protected JTextField tfUserName;
151            protected JPasswordField tfPassword;
152            protected JCheckBox cbSaveCredentials;
153            protected JMultilineLabel lblHeading;
154            protected JMultilineLabel lblWarning;
155            protected CredentialDialog owner; // owner Dependency Injection to use Key listeners for username and password text fields
156    
157            protected void build() {
158                tfUserName = new JTextField(20);
159                tfPassword = new JPasswordField(20);
160                tfUserName.addFocusListener(new SelectAllOnFocusHandler());
161                tfPassword.addFocusListener(new SelectAllOnFocusHandler());
162                tfUserName.addKeyListener(new TFKeyListener(owner, tfUserName, tfPassword));
163                tfPassword.addKeyListener(new TFKeyListener(owner, tfPassword, tfUserName));
164                cbSaveCredentials =  new JCheckBox(owner.saveUsernameAndPasswordCheckboxText);
165    
166                setLayout(new GridBagLayout());
167                GridBagConstraints gc = new GridBagConstraints();
168                gc.gridwidth = 2;
169                gc.gridheight = 1;
170                gc.fill = GridBagConstraints.HORIZONTAL;
171                gc.weightx = 1.0;
172                gc.weighty = 0.0;
173                gc.insets = new Insets(0,0,10,0);
174                add(lblHeading = new JMultilineLabel(""), gc);
175    
176                gc.gridx = 0;
177                gc.gridy = 1;
178                gc.gridwidth = 1;
179                gc.gridheight = 1;
180                gc.fill = GridBagConstraints.HORIZONTAL;
181                gc.weightx = 0.0;
182                gc.weighty = 0.0;
183                gc.insets = new Insets(0,0,10,10);
184                add(new JLabel(tr("Username")), gc);
185                gc.gridx = 1;
186                gc.gridy = 1;
187                gc.weightx = 1.0;
188                add(tfUserName, gc);
189                gc.gridx = 0;
190                gc.gridy = 2;
191                gc.weightx = 0.0;
192                add(new JLabel(tr("Password")), gc);
193    
194                gc.gridx = 1;
195                gc.gridy = 2;
196                gc.weightx = 0.0;
197                add(tfPassword, gc);
198    
199                gc.gridx = 0;
200                gc.gridy = 3;
201                gc.gridwidth = 2;
202                gc.gridheight = 1;
203                gc.fill = GridBagConstraints.BOTH;
204                gc.weightx = 1.0;
205                gc.weighty = 0.0;
206                lblWarning = new JMultilineLabel("");
207                lblWarning.setFont(lblWarning.getFont().deriveFont(Font.ITALIC));
208                add(lblWarning, gc);
209    
210                gc.gridx = 0;
211                gc.gridy = 4;
212                gc.weighty = 0.0;
213                add(cbSaveCredentials, gc);
214    
215                // consume the remaining space
216                gc.gridx = 0;
217                gc.gridy = 5;
218                gc.weighty = 1.0;
219                add(new JPanel(),gc);
220    
221            }
222    
223            public CredentialPanel(CredentialDialog owner) {
224                this.owner = owner;
225            }
226    
227            public void init(String username, String password) {
228                username = username == null ? "" : username;
229                password = password == null ? "" : password;
230                tfUserName.setText(username);
231                tfPassword.setText(password);
232                cbSaveCredentials.setSelected(!username.equals("") && ! password.equals(""));
233            }
234    
235            public void startUserInput() {
236                tfUserName.requestFocusInWindow();
237            }
238    
239            public String getUserName() {
240                return tfUserName.getText();
241            }
242    
243            public char[] getPassword() {
244                return tfPassword.getPassword();
245            }
246    
247            public boolean isSaveCredentials() {
248                return cbSaveCredentials.isSelected();
249            }
250        }
251    
252        private static class OsmApiCredentialsPanel extends CredentialPanel {
253    
254            @Override
255            protected void build() {
256                super.build();
257                tfUserName.setToolTipText(tr("Please enter the user name of your OSM account"));
258                tfPassword.setToolTipText(tr("Please enter the password of your OSM account"));
259                lblHeading.setText(
260                        "<html>" + tr("Authenticating at the OSM API ''{0}'' failed. Please enter a valid username and a valid password.",
261                                OsmApi.getOsmApi().getBaseUrl()) + "</html>");
262                lblWarning.setText(tr("Warning: The password is transferred unencrypted."));
263            }
264    
265            public OsmApiCredentialsPanel(CredentialDialog owner) {
266                super(owner);
267                build();
268            }
269        }
270    
271        private static class OtherHostCredentialsPanel extends CredentialPanel {
272    
273            String host;
274    
275            @Override
276            protected void build() {
277                super.build();
278                tfUserName.setToolTipText(tr("Please enter the user name of your account"));
279                tfPassword.setToolTipText(tr("Please enter the password of your account"));
280                lblHeading.setText(
281                        "<html>" + tr("Authenticating at the host ''{0}'' failed. Please enter a valid username and a valid password.",
282                                host) + "</html>");
283                lblWarning.setText(tr("Warning: The password is transferred unencrypted."));
284            }
285    
286            public OtherHostCredentialsPanel(CredentialDialog owner, String host) {
287                super(owner);
288                this.host = host;
289                build();
290            }
291        }
292    
293        private static class HttpProxyCredentialsPanel extends CredentialPanel {
294            @Override
295            protected void build() {
296                super.build();
297                tfUserName.setToolTipText(tr("Please enter the user name for authenticating at your proxy server"));
298                tfPassword.setToolTipText(tr("Please enter the password for authenticating at your proxy server"));
299                lblHeading.setText(
300                        "<html>" + tr("Authenticating at the HTTP proxy ''{0}'' failed. Please enter a valid username and a valid password.",
301                                Main.pref.get(ProxyPreferencesPanel.PROXY_HTTP_HOST) + ":" + Main.pref.get(ProxyPreferencesPanel.PROXY_HTTP_PORT)) + "</html>");
302                lblWarning.setText("<html>" + tr("Warning: depending on the authentication method the proxy server uses the password may be transferred unencrypted.") + "</html>");
303            }
304    
305            public HttpProxyCredentialsPanel(CredentialDialog owner) {
306                super(owner);
307                build();
308            }
309        }
310    
311        static private class SelectAllOnFocusHandler extends FocusAdapter {
312            @Override
313            public void focusGained(FocusEvent e) {
314                if (e.getSource() instanceof JTextField) {
315                    JTextField tf = (JTextField)e.getSource();
316                    tf.selectAll();
317                }
318            }
319        }
320    
321        /**
322         * Listener for username and password text fields key events.
323         * When user presses Enter:
324         *   If current text field is empty (or just contains a sequence of spaces), nothing happens (or all spaces become selected).
325         *   If current text field is not empty, but the next one is (or just contains a sequence of spaces), focuses the next text field.
326         *   If both text fields contain characters, submits the form by calling owner's {@link OKAction}.
327         */
328        static private class TFKeyListener implements KeyListener{
329            protected CredentialDialog owner; // owner Dependency Injection to call OKAction
330            protected JTextField currentTF;
331            protected JTextField nextTF;
332    
333            public TFKeyListener (CredentialDialog owner, JTextField currentTF, JTextField nextTF)
334            {
335                this.owner = owner;
336                this.currentTF = currentTF;
337                this.nextTF = nextTF;
338            }
339    
340            @Override
341            public void keyPressed(KeyEvent e) {
342                if(e.getKeyChar() == KeyEvent.VK_ENTER) {
343                    if (currentTF.getText().trim().isEmpty()) {
344                        currentTF.selectAll();
345                        return;
346                    } else if (nextTF.getText().trim().isEmpty()) {
347                        nextTF.requestFocusInWindow();
348                        nextTF.selectAll();
349                        return;
350                    } else {
351                        OKAction okAction = owner.new OKAction();
352                        okAction.actionPerformed(null);
353                    }
354                }
355            }
356    
357            @Override
358            public void keyReleased ( KeyEvent e ){
359            }
360    
361            @Override
362            public void keyTyped ( KeyEvent e ){
363            }
364        }
365    
366        class OKAction extends AbstractAction {
367            public OKAction() {
368                putValue(NAME, tr("Authenticate"));
369                putValue(SHORT_DESCRIPTION, tr("Authenticate with the supplied username and password"));
370                putValue(SMALL_ICON, ImageProvider.get("ok"));
371            }
372    
373            @Override
374            public void actionPerformed(ActionEvent arg0) {
375                setCanceled(false);
376                setVisible(false);
377            }
378        }
379    
380        class CancelAction extends AbstractAction {
381            public CancelAction() {
382                putValue(NAME, tr("Cancel"));
383                putValue(SHORT_DESCRIPTION, tr("Cancel authentication"));
384                putValue(SMALL_ICON, ImageProvider.get("cancel"));
385            }
386    
387            public void cancel() {
388                setCanceled(true);
389                setVisible(false);
390            }
391    
392            @Override
393            public void actionPerformed(ActionEvent arg0) {
394                cancel();
395            }
396        }
397    
398        class WindowEventHander extends WindowAdapter {
399    
400            @Override
401            public void windowActivated(WindowEvent e) {
402                if (pnlCredentials != null) {
403                    pnlCredentials.startUserInput();
404                }
405            }
406    
407            @Override
408            public void windowClosing(WindowEvent e) {
409                new CancelAction().cancel();
410            }
411        }
412    }