// License: GPL. Copyright 2007 by Immanuel Scholz and others
package org.openstreetmap.josm.io;
import static org.openstreetmap.josm.tools.I18n.tr;
import java.awt.Font;
import java.awt.GridBagLayout;
import java.net.Authenticator;
import java.net.HttpURLConnection;
import java.net.PasswordAuthentication;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.charset.CharacterCodingException;
import java.nio.charset.Charset;
import java.nio.charset.CharsetEncoder;
import java.util.logging.Logger;
import javax.swing.JCheckBox;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JPasswordField;
import javax.swing.JTextField;
import org.openstreetmap.josm.Main;
import org.openstreetmap.josm.gui.ExtendedDialog;
import org.openstreetmap.josm.tools.Base64;
import org.openstreetmap.josm.tools.GBC;
/**
* Base class that handles common things like authentication for the reader and writer
* to the osm server.
*
* @author imi
*/
public class OsmConnection {
private static final Logger logger = Logger.getLogger(OsmConnection.class.getName());
protected boolean cancel = false;
protected HttpURLConnection activeConnection;
/**
* Handles password storage and some related gui-components.
* It can be set by a plugin. This may happen at startup and
* by changing the preferences.
* Syncronize on this object to get or set a consistent
* username/password pair.
*/
public static CredentialsManager credentialsManager = new PlainCredentialsManager();
private static OsmAuth authentication = new OsmAuth();
/**
* Initialize the http defaults and the authenticator.
*/
static {
// TODO: current authentication handling is sub-optimal in that it seems to use the same authenticator for
// any kind of request. HTTP requests executed by plugins, e.g. to password-protected WMS servers,
// will use the same username/password which is undesirable.
try {
HttpURLConnection.setFollowRedirects(true);
Authenticator.setDefault(authentication);
} catch (SecurityException e) {
}
}
/**
* The authentication class handling the login requests.
*/
public static class OsmAuth extends Authenticator {
/**
* Set to true, when the autenticator tried the password once.
*/
public boolean passwordtried = false;
/**
* Whether the user cancelled the password dialog
*/
public boolean authCancelled = false;
@Override protected PasswordAuthentication getPasswordAuthentication() {
return credentialsManager.getPasswordAuthentication(this);
}
}
/**
* Must be called before each connection attemp to initialize the authentication.
*/
protected final void initAuthentication() {
authentication.authCancelled = false;
authentication.passwordtried = false;
}
/**
* @return Whether the connection was cancelled.
*/
protected final boolean isAuthCancelled() {
return authentication.authCancelled;
}
public void cancel() {
cancel = true;
synchronized (this) {
if (activeConnection != null) {
activeConnection.setConnectTimeout(100);
activeConnection.setReadTimeout(100);
}
}
try {
Thread.sleep(100);
} catch (InterruptedException ex) {
}
synchronized (this) {
if (activeConnection != null) {
activeConnection.disconnect();
}
}
}
protected void addAuth(HttpURLConnection con) throws CharacterCodingException {
CharsetEncoder encoder = Charset.forName("UTF-8").newEncoder();
String auth;
try {
synchronized (credentialsManager) {
auth = credentialsManager.lookup(CredentialsManager.Key.USERNAME) + ":" +
credentialsManager.lookup(CredentialsManager.Key.PASSWORD);
}
} catch (CredentialsManager.CMException e) {
auth = ":";
}
ByteBuffer bytes = encoder.encode(CharBuffer.wrap(auth));
con.addRequestProperty("Authorization", "Basic "+Base64.encode(bytes));
}
/**
* Replies true if this connection is canceled
*
* @return true if this connection is canceled
* @return
*/
public boolean isCanceled() {
return cancel;
}
/**
* Default implementation of the CredentialsManager interface.
* Saves passwords in plain text file.
*/
public static class PlainCredentialsManager implements CredentialsManager {
public String lookup(CredentialsManager.Key key) throws CMException {
String secret = Main.pref.get("osm-server." + key.toString(), null);
if (secret == null) throw new CredentialsManager.NoContentException();
return secret;
}
public void store(CredentialsManager.Key key, String secret) {
Main.pref.put("osm-server." + key.toString(), secret);
}
public PasswordAuthentication getPasswordAuthentication(OsmAuth caller) {
String username, password;
try {
username = lookup(Key.USERNAME);
} catch (CMException e) {
username = "";
}
try {
password = lookup(Key.PASSWORD);
} catch (CMException e) {
password = "";
}
if (caller.passwordtried || username.equals("") || password.equals("")) {
JPanel p = new JPanel(new GridBagLayout());
if (!username.equals("") && !password.equals("")) {
p.add(new JLabel(tr("Incorrect password or username.")), GBC.eop());
}
p.add(new JLabel(tr("Username")), GBC.std().insets(0,0,10,0));
JTextField usernameField = new JTextField(username, 20);
p.add(usernameField, GBC.eol());
p.add(new JLabel(tr("Password")), GBC.std().insets(0,0,10,0));
JPasswordField passwordField = new JPasswordField(password, 20);
p.add(passwordField, GBC.eol());
JLabel warning = new JLabel(tr("Warning: The password is transferred unencrypted."));
warning.setFont(warning.getFont().deriveFont(Font.ITALIC));
p.add(warning, GBC.eop());
JCheckBox savePassword = new JCheckBox(tr("Save user and password (unencrypted)"),
!username.equals("") && !password.equals(""));
p.add(savePassword, GBC.eop());
ExtendedDialog dialog = new ExtendedDialog(
Main.parent,
tr("Enter Password"),
new String[] {tr("Login"), tr("Cancel")}
);
dialog.setContent(p);
dialog.setButtonIcons( new String[] {"ok.png", "cancel.png"});
dialog.showDialog();
if (dialog.getValue() != 1) {
caller.authCancelled = true;
return null;
}
username = usernameField.getText();
password = String.valueOf(passwordField.getPassword());
if (savePassword.isSelected()) {
store(Key.USERNAME, username);
store(Key.PASSWORD, password);
}
if (username.equals(""))
return null;
}
caller.passwordtried = true;
return new PasswordAuthentication(username, password.toCharArray());
}
public PreferenceAdditions newPreferenceAdditions() {
return new PreferenceAdditions() {
/**
* Editfield for the Base url to the REST API from OSM.
*/
final private JTextField osmDataServerURL = new JTextField(20);
/**
* Editfield for the username to the OSM account.
*/
final private JTextField osmDataUsername = new JTextField(20);
/**
* Passwordfield for the userpassword of the REST API.
*/
final private JPasswordField osmDataPassword = new JPasswordField(20);
private String oldServerURL = "";
private String oldUsername = "";
private String oldPassword = "";
public void addPreferenceOptions(JPanel panel) {
try {
oldServerURL = lookup(Key.OSM_SERVER_URL); // result is not null (see CredentialsManager)
} catch (CMException e) {
oldServerURL = "";
}
if (oldServerURL.equals("")) {
oldServerURL = "http://api.openstreetmap.org/api";
}
try {
oldUsername = lookup(Key.USERNAME);
} catch (CMException e) {
oldUsername = "";
}
try {
oldPassword = lookup(Key.PASSWORD);
} catch (CMException e) {
oldPassword = "";
}
osmDataServerURL.setText(oldServerURL);
osmDataUsername.setText(oldUsername);
osmDataPassword.setText(oldPassword);
osmDataServerURL.setToolTipText(tr("The base URL for the OSM server (REST API)"));
osmDataUsername.setToolTipText(tr("Login name (e-mail) to the OSM account."));
osmDataPassword.setToolTipText(tr("Login password to the OSM account. Leave blank to not store any password."));
panel.add(new JLabel(tr("Base Server URL")), GBC.std());
panel.add(osmDataServerURL, GBC.eol().fill(GBC.HORIZONTAL).insets(5,0,0,5));
panel.add(new JLabel(tr("OSM username (e-mail)")), GBC.std());
panel.add(osmDataUsername, GBC.eol().fill(GBC.HORIZONTAL).insets(5,0,0,5));
panel.add(new JLabel(tr("OSM password")), GBC.std());
panel.add(osmDataPassword, GBC.eol().fill(GBC.HORIZONTAL).insets(5,0,0,0));
JLabel warning = new JLabel(tr("" +
"WARNING: The password is stored in plain text in the preferences file.
" +
"The password is transferred in plain text to the server, encoded in the URL.
" +
"Do not use a valuable Password."));
warning.setFont(warning.getFont().deriveFont(Font.ITALIC));
panel.add(warning, GBC.eop().fill(GBC.HORIZONTAL));
}
public void preferencesChanged() {
String newServerURL = osmDataServerURL.getText();
String newUsername = osmDataUsername.getText();
String newPassword = String.valueOf(osmDataPassword.getPassword());
if (!oldServerURL.equals(newServerURL)) {
store(Key.OSM_SERVER_URL, newServerURL);
}
if (!oldUsername.equals(newUsername)) {
store(Key.USERNAME, newUsername);
}
if (!oldPassword.equals(newPassword)) {
store(Key.PASSWORD, newPassword);
}
}
};
}
}
}