001// License: GPL. For details, see LICENSE file.
002package org.openstreetmap.josm.io.auth;
003
004import java.awt.Component;
005import java.net.Authenticator.RequestorType;
006import java.net.PasswordAuthentication;
007import java.util.Objects;
008
009import org.openstreetmap.josm.data.UserIdentityManager;
010import org.openstreetmap.josm.data.oauth.OAuthToken;
011import org.openstreetmap.josm.io.OsmApi;
012import org.openstreetmap.josm.tools.CheckParameterUtil;
013import org.openstreetmap.josm.tools.Logging;
014import org.openstreetmap.josm.tools.Utils;
015
016/**
017 * CredentialManager is a factory for the single credential agent used.
018 *
019 * Currently, it defaults to replying an instance of {@link JosmPreferencesCredentialAgent}.
020 * @since 2641
021 */
022public class CredentialsManager implements CredentialsAgent {
023
024    private static volatile CredentialsManager instance;
025
026    /**
027     * Replies the single credential agent used in JOSM
028     *
029     * @return the single credential agent used in JOSM
030     */
031    public static CredentialsManager getInstance() {
032        if (instance == null) {
033            CredentialsAgent delegate;
034            if (agentFactory == null) {
035                delegate = new JosmPreferencesCredentialAgent();
036            } else {
037                delegate = agentFactory.getCredentialsAgent();
038            }
039            instance = new CredentialsManager(delegate);
040        }
041        return instance;
042    }
043
044    private static CredentialsAgentFactory agentFactory;
045
046    /**
047     * Credentials agent factory.
048     */
049    @FunctionalInterface
050    public interface CredentialsAgentFactory {
051        /**
052         * Returns the credentials agent instance.
053         * @return the credentials agent instance
054         */
055        CredentialsAgent getCredentialsAgent();
056    }
057
058    /**
059     * Plugins can register a CredentialsAgentFactory, thereby overriding
060     * JOSM's default credentials agent.
061     * @param agentFactory The Factory that provides the custom CredentialsAgent.
062     * Can be null to clear the factory and switch back to default behavior.
063     */
064    public static void registerCredentialsAgentFactory(CredentialsAgentFactory agentFactory) {
065        CredentialsManager.agentFactory = agentFactory;
066        CredentialsManager.instance = null;
067    }
068
069    /* non-static fields and methods */
070
071    /**
072     * The credentials agent doing the real stuff
073     */
074    private final CredentialsAgent delegate;
075
076    /**
077     * Constructs a new {@code CredentialsManager}.
078     * @param delegate The credentials agent backing this credential manager. Must not be {@code null}
079     */
080    public CredentialsManager(CredentialsAgent delegate) {
081        CheckParameterUtil.ensureParameterNotNull(delegate, "delegate");
082        this.delegate = delegate;
083    }
084
085    /**
086     * Returns type of credentials agent backing this credentials manager.
087     * @return The type of credentials agent
088     */
089    public final Class<? extends CredentialsAgent> getCredentialsAgentClass() {
090        return delegate.getClass();
091    }
092
093    /**
094     * Returns the username for OSM API
095     * @return the username for OSM API
096     */
097    public String getUsername() {
098        return getUsername(OsmApi.getOsmApi().getHost());
099    }
100
101    /**
102     * Returns the username for a given host
103     * @param host The host for which username is wanted
104     * @return The username for {@code host}
105     */
106    public String getUsername(String host) {
107        String username = null;
108        try {
109            PasswordAuthentication auth = lookup(RequestorType.SERVER, host);
110            if (auth != null) {
111                username = auth.getUserName();
112            }
113        } catch (CredentialsAgentException ex) {
114            Logging.debug(ex);
115            return null;
116        }
117        if (username == null) return null;
118        username = username.trim();
119        return username.isEmpty() ? null : username;
120    }
121
122    @Override
123    public PasswordAuthentication lookup(RequestorType requestorType, String host) throws CredentialsAgentException {
124        return delegate.lookup(requestorType, host);
125    }
126
127    @Override
128    public void store(RequestorType requestorType, String host, PasswordAuthentication credentials) throws CredentialsAgentException {
129        if (requestorType == RequestorType.SERVER && Objects.equals(OsmApi.getOsmApi().getHost(), host)) {
130            String username = credentials.getUserName();
131            if (!Utils.isBlank(username)) {
132                UserIdentityManager.getInstance().setPartiallyIdentified(username);
133            }
134        }
135        // see #11914: clear cache before we store new value
136        purgeCredentialsCache(requestorType);
137        delegate.store(requestorType, host, credentials);
138    }
139
140    @Override
141    public CredentialsAgentResponse getCredentials(RequestorType requestorType, String host, boolean noSuccessWithLastResponse)
142            throws CredentialsAgentException {
143        CredentialsAgentResponse credentials = delegate.getCredentials(requestorType, host, noSuccessWithLastResponse);
144        if (requestorType == RequestorType.SERVER) {
145            // see #11914 : Keep UserIdentityManager up to date
146            String userName = credentials.getUsername();
147            userName = userName == null ? "" : userName.trim();
148            if (!Objects.equals(UserIdentityManager.getInstance().getUserName(), userName)) {
149                if (userName.isEmpty())
150                    UserIdentityManager.getInstance().setAnonymous();
151                else
152                    UserIdentityManager.getInstance().setPartiallyIdentified(userName);
153            }
154        }
155        return credentials;
156    }
157
158    @Override
159    public OAuthToken lookupOAuthAccessToken() throws CredentialsAgentException {
160        return delegate.lookupOAuthAccessToken();
161    }
162
163    @Override
164    public void storeOAuthAccessToken(OAuthToken accessToken) throws CredentialsAgentException {
165        delegate.storeOAuthAccessToken(accessToken);
166    }
167
168    @Override
169    public Component getPreferencesDecorationPanel() {
170        return delegate.getPreferencesDecorationPanel();
171    }
172
173    @Override
174    public void purgeCredentialsCache(RequestorType requestorType) {
175        delegate.purgeCredentialsCache(requestorType);
176    }
177}