001// License: GPL. For details, see LICENSE file. 002package org.openstreetmap.josm.io.auth; 003 004import java.net.Authenticator.RequestorType; 005import java.net.PasswordAuthentication; 006import java.util.EnumMap; 007import java.util.Map; 008import java.util.Objects; 009 010import org.openstreetmap.josm.tools.Logging; 011 012/** 013 * Partial implementation of the {@link CredentialsAgent} interface. 014 * <p> 015 * Provides a memory cache for the credentials and means to query the information from the user. 016 * @since 4246 017 */ 018public abstract class AbstractCredentialsAgent implements CredentialsAgent { 019 020 /** 021 * Synchronous credentials provider. Called if no credentials are cached. Can be used for user login prompt. 022 * @since 12821 023 */ 024 @FunctionalInterface 025 public interface CredentialsProvider { 026 /** 027 * Fills the given response with appropriate user credentials. 028 * @param requestorType type of the entity requesting authentication 029 * @param agent the credentials agent requesting credentials 030 * @param response authentication response to fill 031 * @param username the known username, if any. Likely to be empty 032 * @param password the known password, if any. Likely to be empty 033 * @param host the host against authentication will be performed 034 */ 035 void provideCredentials(RequestorType requestorType, AbstractCredentialsAgent agent, CredentialsAgentResponse response, 036 String username, String password, String host); 037 } 038 039 private static volatile CredentialsProvider credentialsProvider = 040 (a, b, c, d, e, f) -> Logging.error("Credentials provider has not been set"); 041 042 /** 043 * Sets the global credentials provider. 044 * @param provider credentials provider. Called if no credentials are cached. Can be used for user login prompt 045 */ 046 public static void setCredentialsProvider(CredentialsProvider provider) { 047 credentialsProvider = Objects.requireNonNull(provider, "provider"); 048 } 049 050 protected Map<RequestorType, PasswordAuthentication> memoryCredentialsCache = new EnumMap<>(RequestorType.class); 051 052 @Override 053 public CredentialsAgentResponse getCredentials(final RequestorType requestorType, final String host, boolean noSuccessWithLastResponse) 054 throws CredentialsAgentException { 055 if (requestorType == null) 056 return null; 057 PasswordAuthentication credentials = lookup(requestorType, host); 058 final String username = (credentials == null || credentials.getUserName() == null) ? "" : credentials.getUserName(); 059 final String password = (credentials == null || credentials.getPassword() == null) ? "" : String.valueOf(credentials.getPassword()); 060 061 final CredentialsAgentResponse response = new CredentialsAgentResponse(); 062 063 /* 064 * Last request was successful and there was no credentials stored in file (or only the username is stored). 065 * -> Try to recall credentials that have been entered manually in this session. 066 */ 067 if (!noSuccessWithLastResponse && memoryCredentialsCache.containsKey(requestorType) && 068 (credentials == null || credentials.getPassword() == null || credentials.getPassword().length == 0)) { 069 PasswordAuthentication pa = memoryCredentialsCache.get(requestorType); 070 response.setUsername(pa.getUserName()); 071 response.setPassword(pa.getPassword()); 072 response.setCanceled(false); 073 /* 074 * Prompt the user for credentials. This happens the first time each 075 * josm start if the user does not save the credentials to preference 076 * file (username=="") and each time after authentication failed 077 * (noSuccessWithLastResponse == true). 078 */ 079 } else if (noSuccessWithLastResponse || username.isEmpty() || password.isEmpty()) { 080 credentialsProvider.provideCredentials(requestorType, this, response, username, password, host); 081 if (response.isCanceled() || response.getUsername() == null || response.getPassword() == null) { 082 return response; 083 } 084 if (response.isSaveCredentials()) { 085 store(requestorType, host, new PasswordAuthentication( 086 response.getUsername(), 087 response.getPassword() 088 )); 089 } else { 090 // User decides not to save credentials to file. Keep it in memory so we don't have to ask over and over again. 091 memoryCredentialsCache.put(requestorType, new PasswordAuthentication(response.getUsername(), response.getPassword())); 092 } 093 } else { 094 // We got it from file. 095 response.setUsername(username); 096 response.setPassword(password.toCharArray()); 097 response.setCanceled(false); 098 } 099 return response; 100 } 101 102 @Override 103 public final void purgeCredentialsCache(RequestorType requestorType) { 104 memoryCredentialsCache.remove(requestorType); 105 } 106 107 /** 108 * Provide the text for a checkbox that offers to save the 109 * username and password that has been entered by the user. 110 * @return checkbox text 111 */ 112 public abstract String getSaveUsernameAndPasswordCheckboxText(); 113}