001// License: GPL. For details, see LICENSE file. 002package org.openstreetmap.josm.io.auth; 003 004import java.net.Authenticator; 005import java.net.PasswordAuthentication; 006import java.util.Collection; 007import java.util.HashSet; 008import java.util.Objects; 009 010import org.openstreetmap.josm.io.OsmApi; 011import org.openstreetmap.josm.tools.Logging; 012import org.openstreetmap.josm.tools.Pair; 013 014/** 015 * This is the default authenticator used in JOSM. It delegates lookup of credentials 016 * for the OSM API and an optional proxy server to the currently configured {@link CredentialsManager}. 017 * @since 2641 018 */ 019public final class DefaultAuthenticator extends Authenticator { 020 private static final DefaultAuthenticator INSTANCE = new DefaultAuthenticator(); 021 022 /** 023 * Returns the unique instance 024 * @return The unique instance 025 */ 026 public static DefaultAuthenticator getInstance() { 027 return INSTANCE; 028 } 029 030 private final Collection<Pair<String, RequestorType>> failedCredentials = new HashSet<>(); 031 private boolean enabled = true; 032 033 private DefaultAuthenticator() { 034 } 035 036 /** 037 * Called by the Java HTTP stack when either the OSM API server or a proxy requires authentication. 038 */ 039 @Override 040 protected PasswordAuthentication getPasswordAuthentication() { 041 if (!enabled) 042 return null; 043 try { 044 if (OsmApi.isUsingOAuth() 045 && Objects.equals(OsmApi.getOsmApi().getHost(), getRequestingHost()) 046 && RequestorType.SERVER.equals(getRequestorType())) { 047 // if we are working with OAuth we don't prompt for a password 048 return null; 049 } 050 final Pair<String, RequestorType> hostTypePair = Pair.create(getRequestingHost(), getRequestorType()); 051 final boolean hasFailedPreviously = failedCredentials.contains(hostTypePair); 052 final CredentialsAgentResponse response = CredentialsManager.getInstance().getCredentials( 053 getRequestorType(), getRequestingHost(), hasFailedPreviously); 054 if (response == null || response.isCanceled()) { 055 return null; 056 } 057 if (RequestorType.PROXY.equals(getRequestorType())) { 058 // Query user in case this authenticator is called (indicating that the authentication failed) the next time. 059 failedCredentials.add(hostTypePair); 060 } else { 061 // Other parallel requests should not ask the user again, thus wait till this request is finished. 062 // In case of invalid authentication, the host is added again to failedCredentials at HttpClient.connect() 063 failedCredentials.remove(hostTypePair); 064 } 065 return new PasswordAuthentication(response.getUsername(), response.getPassword()); 066 } catch (CredentialsAgentException e) { 067 Logging.error(e); 068 return null; 069 } 070 } 071 072 /** 073 * Determines whether this authenticator is enabled, i.e., 074 * provides {@link #getPasswordAuthentication() password authentication} via {@link CredentialsManager}. 075 * @return whether this authenticator is enabled 076 */ 077 public boolean isEnabled() { 078 return enabled; 079 } 080 081 /** 082 * Enabled/disables this authenticator, i.e., decides whether it 083 * should provide {@link #getPasswordAuthentication() password authentication} via {@link CredentialsManager}. 084 * @param enabled whether this authenticator should be enabled 085 */ 086 public void setEnabled(boolean enabled) { 087 this.enabled = enabled; 088 } 089 090 /** 091 * Marks for this host that the authentication failed, i.e., 092 * the {@link CredentialsManager} will show a dialog at the next time. 093 * @param host the host to mark 094 * @return as per {@link Collection#add(Object)} 095 */ 096 public boolean addFailedCredentialHost(String host) { 097 return failedCredentials.add(Pair.create(host, RequestorType.SERVER)); 098 } 099 100 /** 101 * Un-marks the failed authentication attempt for the host 102 * @param host the host to un-mark 103 * @return as per {@link Collection#remove(Object)} 104 */ 105 public boolean removeFailedCredentialHost(String host) { 106 return failedCredentials.remove(Pair.create(host, RequestorType.SERVER)); 107 } 108}