// License: GPL. For details, see LICENSE file.
package org.openstreetmap.josm.io.auth;
import java.net.Authenticator;
import java.net.PasswordAuthentication;
import java.util.Collection;
import java.util.HashSet;
import java.util.Objects;
import org.openstreetmap.josm.Main;
import org.openstreetmap.josm.io.OsmApi;
import org.openstreetmap.josm.tools.Pair;
/**
* This is the default authenticator used in JOSM. It delegates lookup of credentials
* for the OSM API and an optional proxy server to the currently configured {@link CredentialsManager}.
* @since 2641
*/
public final class DefaultAuthenticator extends Authenticator {
private static final DefaultAuthenticator INSTANCE = new DefaultAuthenticator();
/**
* Returns the unique instance
* @return The unique instance
*/
public static DefaultAuthenticator getInstance() {
return INSTANCE;
}
private final Collection<Pair<String, RequestorType>> failedCredentials = new HashSet<>();
private boolean enabled = true;
private DefaultAuthenticator() {
}
/**
* Called by the Java HTTP stack when either the OSM API server or a proxy requires authentication.
*/
@Override
protected PasswordAuthentication getPasswordAuthentication() {
if (!enabled)
return null;
try {
if (OsmApi.isUsingOAuth()
&& Objects.equals(OsmApi.getOsmApi().getHost(), getRequestingHost())
&& RequestorType.SERVER.equals(getRequestorType())) {
// if we are working with OAuth we don't prompt for a password
return null;
}
final Pair<String, RequestorType> hostTypePair = Pair.create(getRequestingHost(), getRequestorType());
final boolean hasFailedPreviously = failedCredentials.contains(hostTypePair);
final CredentialsAgentResponse response = CredentialsManager.getInstance().getCredentials(
getRequestorType(), getRequestingHost(), hasFailedPreviously);
if (response == null || response.isCanceled()) {
return null;
}
if (RequestorType.PROXY.equals(getRequestorType())) {
// Query user in case this authenticator is called (indicating that the authentication failed) the next time.
failedCredentials.add(hostTypePair);
} else {
// Other parallel requests should not ask the user again, thus wait till this request is finished.
// In case of invalid authentication, the host is added again to failedCredentials at HttpClient.connect()
failedCredentials.remove(hostTypePair);
}
return new PasswordAuthentication(response.getUsername(), response.getPassword());
} catch (CredentialsAgentException e) {
Main.error(e);
return null;
}
}
/**
* Determines whether this authenticator is enabled, i.e.,
* provides {@link #getPasswordAuthentication() password authentication} via {@link CredentialsManager}.
* @return whether this authenticator is enabled
*/
public boolean isEnabled() {
return enabled;
}
/**
* Enabled/disables this authenticator, i.e., decides whether it
* should provide {@link #getPasswordAuthentication() password authentication} via {@link CredentialsManager}.
* @param enabled whether this authenticator should be enabled
*/
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
/**
* Marks for this host that the authentication failed, i.e.,
* the {@link CredentialsManager} will show a dialog at the next time.
* @param host the host to mark
* @return as per {@link Collection#add(Object)}
*/
public boolean addFailedCredentialHost(String host) {
return failedCredentials.add(Pair.create(host, RequestorType.SERVER));
}
/**
* Un-marks the failed authentication attempt for the host
* @param host the host to un-mark
* @return as per {@link Collection#remove(Object)}
*/
public boolean removeFailedCredentialHost(String host) {
return failedCredentials.remove(Pair.create(host, RequestorType.SERVER));
}
}