blob: 8e201b418fbe3894de357ff617d1a301dfb7d4b3 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2000, 2006 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* IBM Corporation - initial API and implementation
*******************************************************************************/
package org.eclipse.core.internal.runtime.auth;
import java.io.*;
import java.net.URL;
import java.util.*;
import org.eclipse.core.runtime.*;
import org.eclipse.osgi.util.NLS;
/**
* A database that remembers information, such as user-names and
* passwords. The information is stored in memory and can be saved
* to disk in an encrypted format. While the API is phrased in terms of
* URLs, realms and authentication schemes, not all of these must have
* significant values. For example, if "realm" is not relevant to a
* particular application, it can be left blank (though not
* <code>null</code>).
*/
public class AuthorizationDatabase {
public static final String PI_RUNTIME_AUTH = "org.eclipse.core.runtime.auth.compatibility"; //$NON-NLS-1$
/**
* Status code constant (value 4) indicating the platform could not read
* some of its metadata.
*/
public static final int FAILED_READ_METADATA = 4;
/**
* Status code constant (value 5) indicating the platform could not write
* some of its metadata.
*/
public static final int FAILED_WRITE_METADATA = 5;
/**
* Version number for the format of the key-ring file.
*/
private static final int KEYRING_FILE_VERSION = 1;
/**
* A nested hashtable that stores authorization information. The
* table maps server URLs to realms to authentication schemes to
* authorization information.
*/
private Hashtable authorizationInfo = new Hashtable(5);
/**
* A hashtable mapping resource URLs to realms.
*/
private Hashtable protectionSpace = new Hashtable(5);
private File file = null;
private String password = null;
private boolean needsSaving = true;
/**
* Creates a new authorization database whose data cannot be saved to
* disk.
*/
public AuthorizationDatabase() {
super();
}
/**
* Creates a new authorization database, or opens an existing one, whose
* data is, or can be, saved to a file with the given filename. A
* password must be given to create a new database and an existing
* database is opened by supplying the password that was given to create
* it.
*
* @param filename the location of the database on disk. For example,
* "c:/temp/database"
* @param password the password to access the database. For example,
* "secret"
* @exception CoreException if there are problems creating the database.
* Reasons include:
* <ul>
* <li>The database could not be opened because the wrong password was given.
* <li>The database could not be opened because the specified file is corrupt.
* </ul>
*/
public AuthorizationDatabase(String filename, String password) throws CoreException {
Assert.isNotNull(filename);
Assert.isNotNull(password);
this.password = password;
file = new File(filename);
load();
}
/**
* Adds the given authorization information to the database. The
* information is relevant for the specified protection space and the
* given authorization scheme. The protection space is defined by the
* combination of the given server URL and realm. The authorization
* scheme determines what the authorization information contains and how
* it should be used. The authorization information is a <code>Map</code>
* of <code>String</code> to <code>String</code> and typically
* contain information such as usernames and passwords.
*
* @param serverUrl the URL identifying the server for this authorization
* information. For example, "http://www.hostname.com/".
* @param realm the subsection of the given server to which this
* authorization information applies. For example,
* "realm1@hostname.com" or "" for no realm.
* @param authScheme the scheme for which this authorization information
* applies. For example, "Basic" or "" for no authorization scheme
* @param info a <code>Map</code> containing authorization information
* such as usernames and passwords
*/
public void addAuthorizationInfo(URL serverUrl, String realm, String authScheme, Map info) {
Assert.isNotNull(serverUrl);
Assert.isNotNull(realm);
Assert.isNotNull(authScheme);
Assert.isNotNull(info);
String url = serverUrl.toString();
Hashtable realmToAuthScheme = (Hashtable) authorizationInfo.get(url);
if (realmToAuthScheme == null) {
realmToAuthScheme = new Hashtable(5);
authorizationInfo.put(url, realmToAuthScheme);
}
Hashtable authSchemeToInfo = (Hashtable) realmToAuthScheme.get(realm);
if (authSchemeToInfo == null) {
authSchemeToInfo = new Hashtable(5);
realmToAuthScheme.put(realm, authSchemeToInfo);
}
authSchemeToInfo.put(authScheme.toLowerCase(), info);
needsSaving = true;
}
/**
* Adds the specified resource to the protection space specified by the
* given realm. All resources at or deeper than the depth of the last
* symbolic element in the path of the given resource URL are assumed to
* be in the same protection space.
*
* @param resourceUrl the URL identifying the resources to be added to
* the specified protection space. For example,
* "http://www.hostname.com/folder/".
* @param realm the name of the protection space. For example,
* "realm1@hostname.com"
*/
public void addProtectionSpace(URL resourceUrl, String realm) {
Assert.isNotNull(resourceUrl);
Assert.isNotNull(realm);
if (!resourceUrl.getFile().endsWith("/")) { //$NON-NLS-1$
resourceUrl = URLTool.getParent(resourceUrl);
}
String oldRealm = getProtectionSpace(resourceUrl);
if (oldRealm != null && oldRealm.equals(realm)) {
return;
}
String url1 = resourceUrl.toString();
Enumeration urls = protectionSpace.keys();
while (urls.hasMoreElements()) {
String url2 = (String) urls.nextElement();
if (url1.startsWith(url2) || url2.startsWith(url1)) {
protectionSpace.remove(url2);
break;
}
}
protectionSpace.put(url1, realm);
needsSaving = true;
}
/**
* Removes the authorization information for the specified protection
* space and given authorization scheme. The protection space is defined
* by the given server URL and realm.
*
* @param serverUrl the URL identifying the server to remove the
* authorization information for. For example,
* "http://www.hostname.com/".
* @param realm the subsection of the given server to remove the
* authorization information for. For example,
* "realm1@hostname.com" or "" for no realm.
* @param authScheme the scheme for which the authorization information
* to remove applies. For example, "Basic" or "" for no
* authorization scheme.
*/
public void flushAuthorizationInfo(URL serverUrl, String realm, String authScheme) {
Hashtable realmToAuthScheme = (Hashtable) authorizationInfo.get(serverUrl.toString());
if (realmToAuthScheme == null) {
return;
}
Hashtable authSchemeToInfo = (Hashtable) realmToAuthScheme.get(realm);
if (authSchemeToInfo == null) {
return;
}
authSchemeToInfo.remove(authScheme.toLowerCase());
needsSaving = true;
}
/**
* Returns the authorization information for the specified protection
* space and given authorization scheme. The protection space is defined
* by the given server URL and realm. Returns <code>null</code> if no
* such information exists.
*
* @param serverUrl the URL identifying the server for the authorization
* information. For example, "http://www.hostname.com/".
* @param realm the subsection of the given server to which the
* authorization information applies. For example,
* "realm1@hostname.com" or "" for no realm.
* @param authScheme the scheme for which the authorization information
* applies. For example, "Basic" or "" for no authorization scheme
* @return the authorization information for the specified protection
* space and given authorization scheme, or <code>null</code> if no
* such information exists
*/
public Map getAuthorizationInfo(URL serverUrl, String realm, String authScheme) {
Hashtable realmToAuthScheme = (Hashtable) authorizationInfo.get(serverUrl.toString());
if (realmToAuthScheme == null) {
return null;
}
Hashtable authSchemeToInfo = (Hashtable) realmToAuthScheme.get(realm);
if (authSchemeToInfo == null) {
return null;
}
return (Map) authSchemeToInfo.get(authScheme.toLowerCase());
}
/**
* Returns the protection space (realm) for the specified resource, or
* <code>null</code> if the realm is unknown.
*
* @param resourceUrl the URL of the resource whose protection space is
* returned. For example, "http://www.hostname.com/folder/".
* @return the protection space (realm) for the specified resource, or
* <code>null</code> if the realm is unknown
*/
public String getProtectionSpace(URL resourceUrl) {
while (resourceUrl != null) {
String realm = (String) protectionSpace.get(resourceUrl.toString());
if (realm != null) {
return realm;
}
resourceUrl = URLTool.getParent(resourceUrl);
}
return null;
}
private void load() throws CoreException {
if (file == null)
return;
if (!file.exists()) {
save();
return;
}
try {
InputStream input = new FileInputStream(file);
try {
load(input);
} finally {
input.close();
}
} catch (IOException e) {
throw new CoreException(new Status(IStatus.ERROR, PI_RUNTIME_AUTH, FAILED_READ_METADATA, NLS.bind(Messages.meta_unableToReadAuthorization, file), e));
} catch (ClassNotFoundException e) {
throw new CoreException(new Status(IStatus.ERROR, PI_RUNTIME_AUTH, FAILED_READ_METADATA, NLS.bind(Messages.meta_unableToReadAuthorization, file), e));
}
}
private void load(InputStream is) throws IOException, ClassNotFoundException, CoreException {
//try to read the file version number. Pre 2.0 versions had no number
int version = is.read();
if (version == KEYRING_FILE_VERSION) {
//read the authorization data
CipherInputStream cis = new CipherInputStream(is, password);
ObjectInputStream ois = new ObjectInputStream(cis);
try {
authorizationInfo = (Hashtable) ois.readObject();
protectionSpace = (Hashtable) ois.readObject();
} finally {
ois.close();
}
} else {
//the format has changed, just log a warning
Activator.log(new Status(IStatus.WARNING, PI_RUNTIME_AUTH, FAILED_READ_METADATA, Messages.meta_authFormatChanged, null));
//close the stream and save a new file in the correct format
try {
is.close();
} catch (IOException e) {
//ignore failure to close
}
needsSaving = true;
save();
}
}
/**
* Saves the authorization database to disk.
*/
public void save() throws CoreException {
if (!needsSaving || file == null)
return;
try {
file.delete();
if ((!file.getParentFile().exists() && !file.getParentFile().mkdirs()) || !canWrite(file.getParentFile()))
throw new CoreException(new Status(IStatus.ERROR, PI_RUNTIME_AUTH, FAILED_WRITE_METADATA, NLS.bind(Messages.meta_unableToWriteAuthorization, file), null));
file.createNewFile();
FileOutputStream out = new FileOutputStream(file);
try {
save(out);
} finally {
out.close();
}
} catch (IOException e) {
throw new CoreException(new Status(IStatus.ERROR, PI_RUNTIME_AUTH, FAILED_WRITE_METADATA, NLS.bind(Messages.meta_unableToWriteAuthorization, file), e));
}
needsSaving = false;
}
private static boolean canWrite(File installDir) {
if (!installDir.canWrite())
return false;
if (!installDir.isDirectory())
return false;
File fileTest = null;
try {
fileTest = File.createTempFile("writtableArea", null, installDir); //$NON-NLS-1$
} catch (IOException e) {
// If an exception occurred while trying to create the file, it means that it is not writable
return false;
} finally {
if (fileTest != null)
fileTest.delete();
}
return true;
}
private void save(FileOutputStream os) throws IOException {
//write the version number
os.write(KEYRING_FILE_VERSION);
CipherOutputStream cos = new CipherOutputStream(os, password);
ObjectOutputStream oos = new ObjectOutputStream(cos);
//write the data
try {
oos.writeObject(authorizationInfo);
oos.writeObject(protectionSpace);
os.flush();
os.getFD().sync();
} finally {
oos.close();
}
}
/**
* Sets the password to use for accessing this database. If the database
* is subsequently saved, this new password is used.
*/
public boolean setPassword(String oldValue, String newValue) {
if (!oldValue.equals(password))
return false;
password = newValue;
needsSaving = true;
return true;
}
}