001    // License: GPL. Copyright 2007 by Immanuel Scholz and others
002    package org.openstreetmap.josm.io;
003    
004    import static org.openstreetmap.josm.tools.I18n.tr;
005    
006    import java.net.HttpURLConnection;
007    import java.net.Authenticator.RequestorType;
008    import java.nio.ByteBuffer;
009    import java.nio.CharBuffer;
010    import java.nio.charset.CharacterCodingException;
011    import java.nio.charset.Charset;
012    import java.nio.charset.CharsetEncoder;
013    
014    import oauth.signpost.OAuthConsumer;
015    import oauth.signpost.exception.OAuthException;
016    
017    import org.openstreetmap.josm.Main;
018    import org.openstreetmap.josm.data.oauth.OAuthParameters;
019    import org.openstreetmap.josm.gui.preferences.server.OAuthAccessTokenHolder;
020    import org.openstreetmap.josm.io.auth.CredentialsAgentException;
021    import org.openstreetmap.josm.io.auth.CredentialsManager;
022    import org.openstreetmap.josm.io.auth.CredentialsAgentResponse;
023    import org.openstreetmap.josm.tools.Base64;
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     */
031    public class OsmConnection {
032        protected boolean cancel = false;
033        protected HttpURLConnection activeConnection;
034        protected OAuthParameters oauthParameters;
035    
036        /**
037         * Initialize the http defaults and the authenticator.
038         */
039        static {
040            try {
041                HttpURLConnection.setFollowRedirects(true);
042            } catch (SecurityException e) {
043                e.printStackTrace();
044            }
045        }
046    
047        public void cancel() {
048            cancel = true;
049            synchronized (this) {
050                if (activeConnection != null) {
051                    activeConnection.setConnectTimeout(100);
052                    activeConnection.setReadTimeout(100);
053                }
054            }
055            try {
056                Thread.sleep(100);
057            } catch (InterruptedException ex) {
058            }
059    
060            synchronized (this) {
061                if (activeConnection != null) {
062                    activeConnection.disconnect();
063                }
064            }
065        }
066    
067        /**
068         * Adds an authentication header for basic authentication
069         *
070         * @param con the connection
071         * @throws OsmTransferException thrown if something went wrong. Check for nested exceptions
072         */
073        protected void addBasicAuthorizationHeader(HttpURLConnection con) throws OsmTransferException {
074            CharsetEncoder encoder = Charset.forName("UTF-8").newEncoder();
075            CredentialsAgentResponse response;
076            String token;
077            try {
078                synchronized (CredentialsManager.getInstance()) {
079                    response = CredentialsManager.getInstance().getCredentials(RequestorType.SERVER,
080                    con.getURL().getHost(), false /* don't know yet whether the credentials will succeed */);
081                }
082            } catch (CredentialsAgentException e) {
083                throw new OsmTransferException(e);
084            }
085            if (response == null) {
086                token = ":";
087            } else if (response.isCanceled()) {
088                cancel = true;
089                return;
090            } else {
091                String username= response.getUsername() == null ? "" : response.getUsername();
092                String password = response.getPassword() == null ? "" : String.valueOf(response.getPassword());
093                token = username + ":" + password;
094                try {
095                    ByteBuffer bytes = encoder.encode(CharBuffer.wrap(token));
096                    con.addRequestProperty("Authorization", "Basic "+Base64.encode(bytes));
097                } catch(CharacterCodingException e) {
098                    throw new OsmTransferException(e);
099                }
100            }
101        }
102    
103        /**
104         * Signs the connection with an OAuth authentication header
105         *
106         * @param connection the connection
107         *
108         * @throws OsmTransferException thrown if there is currently no OAuth Access Token configured
109         * @throws OsmTransferException thrown if signing fails
110         */
111        protected void addOAuthAuthorizationHeader(HttpURLConnection connection) throws OsmTransferException {
112            if (oauthParameters == null) {
113                oauthParameters = OAuthParameters.createFromPreferences(Main.pref);
114            }
115            OAuthConsumer consumer = oauthParameters.buildConsumer();
116            OAuthAccessTokenHolder holder = OAuthAccessTokenHolder.getInstance();
117            if (! holder.containsAccessToken())
118                throw new MissingOAuthAccessTokenException();
119            consumer.setTokenWithSecret(holder.getAccessTokenKey(), holder.getAccessTokenSecret());
120            try {
121                consumer.sign(connection);
122            } catch(OAuthException e) {
123                throw new OsmTransferException(tr("Failed to sign a HTTP connection with an OAuth Authentication header"), e);
124            }
125        }
126    
127        protected void addAuth(HttpURLConnection connection) throws OsmTransferException {
128            String authMethod = Main.pref.get("osm-server.auth-method", "basic");
129            if (authMethod.equals("basic")) {
130                addBasicAuthorizationHeader(connection);
131            } else if (authMethod.equals("oauth")) {
132                addOAuthAuthorizationHeader(connection);
133            } else {
134                String msg = tr("Warning: unexpected value for preference ''{0}''. Got ''{1}''.", "osm-server.auth-method", authMethod);
135                System.err.println(msg);
136                throw new OsmTransferException(msg);
137            }
138        }
139    
140        /**
141         * Replies true if this connection is canceled
142         *
143         * @return true if this connection is canceled
144         * @return
145         */
146        public boolean isCanceled() {
147            return cancel;
148        }
149    }