001// License: GPL. For details, see LICENSE file.
002package org.openstreetmap.josm.io;
003
004import static org.openstreetmap.josm.tools.I18n.tr;
005
006import java.net.Authenticator.RequestorType;
007import java.nio.ByteBuffer;
008import java.nio.CharBuffer;
009import java.nio.charset.CharacterCodingException;
010import java.nio.charset.CharsetEncoder;
011import java.nio.charset.StandardCharsets;
012
013import org.openstreetmap.josm.Main;
014import org.openstreetmap.josm.data.oauth.OAuthParameters;
015import org.openstreetmap.josm.gui.preferences.server.OAuthAccessTokenHolder;
016import org.openstreetmap.josm.io.auth.CredentialsAgentException;
017import org.openstreetmap.josm.io.auth.CredentialsAgentResponse;
018import org.openstreetmap.josm.io.auth.CredentialsManager;
019import org.openstreetmap.josm.tools.Base64;
020import org.openstreetmap.josm.tools.HttpClient;
021
022import oauth.signpost.OAuthConsumer;
023import oauth.signpost.exception.OAuthException;
024
025/**
026 * Base class that handles common things like authentication for the reader and writer
027 * to the osm server.
028 *
029 * @author imi
030 */
031public class OsmConnection {
032    protected boolean cancel;
033    protected HttpClient activeConnection;
034    protected OAuthParameters oauthParameters;
035
036    /**
037     * Cancels the connection.
038     */
039    public void cancel() {
040        cancel = true;
041        synchronized (this) {
042            if (activeConnection != null) {
043                activeConnection.disconnect();
044            }
045        }
046    }
047
048    /**
049     * Adds an authentication header for basic authentication
050     *
051     * @param con the connection
052     * @throws OsmTransferException if something went wrong. Check for nested exceptions
053     */
054    protected void addBasicAuthorizationHeader(HttpClient con) throws OsmTransferException {
055        CharsetEncoder encoder = StandardCharsets.UTF_8.newEncoder();
056        CredentialsAgentResponse response;
057        String token;
058        try {
059            synchronized (CredentialsManager.getInstance()) {
060                response = CredentialsManager.getInstance().getCredentials(RequestorType.SERVER,
061                con.getURL().getHost(), false /* don't know yet whether the credentials will succeed */);
062            }
063        } catch (CredentialsAgentException e) {
064            throw new OsmTransferException(e);
065        }
066        if (response == null) {
067            token = ":";
068        } else if (response.isCanceled()) {
069            cancel = true;
070            return;
071        } else {
072            String username = response.getUsername() == null ? "" : response.getUsername();
073            String password = response.getPassword() == null ? "" : String.valueOf(response.getPassword());
074            token = username + ':' + password;
075            try {
076                ByteBuffer bytes = encoder.encode(CharBuffer.wrap(token));
077                con.setHeader("Authorization", "Basic "+Base64.encode(bytes));
078            } catch (CharacterCodingException e) {
079                throw new OsmTransferException(e);
080            }
081        }
082    }
083
084    /**
085     * Signs the connection with an OAuth authentication header
086     *
087     * @param connection the connection
088     *
089     * @throws OsmTransferException if there is currently no OAuth Access Token configured
090     * @throws OsmTransferException if signing fails
091     */
092    protected void addOAuthAuthorizationHeader(HttpClient connection) throws OsmTransferException {
093        if (oauthParameters == null) {
094            oauthParameters = OAuthParameters.createFromPreferences(Main.pref);
095        }
096        OAuthConsumer consumer = oauthParameters.buildConsumer();
097        OAuthAccessTokenHolder holder = OAuthAccessTokenHolder.getInstance();
098        if (!holder.containsAccessToken())
099            throw new MissingOAuthAccessTokenException();
100        consumer.setTokenWithSecret(holder.getAccessTokenKey(), holder.getAccessTokenSecret());
101        try {
102            consumer.sign(connection);
103        } catch (OAuthException e) {
104            throw new OsmTransferException(tr("Failed to sign a HTTP connection with an OAuth Authentication header"), e);
105        }
106    }
107
108    protected void addAuth(HttpClient connection) throws OsmTransferException {
109        String authMethod = Main.pref.get("osm-server.auth-method", "basic");
110        if ("basic".equals(authMethod)) {
111            addBasicAuthorizationHeader(connection);
112        } else if ("oauth".equals(authMethod)) {
113            addOAuthAuthorizationHeader(connection);
114        } else {
115            String msg = tr("Unexpected value for preference ''{0}''. Got ''{1}''.", "osm-server.auth-method", authMethod);
116            Main.warn(msg);
117            throw new OsmTransferException(msg);
118        }
119    }
120
121    /**
122     * Replies true if this connection is canceled
123     *
124     * @return true if this connection is canceled
125     */
126    public boolean isCanceled() {
127        return cancel;
128    }
129}