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.net.HttpURLConnection; 008import java.nio.ByteBuffer; 009import java.nio.CharBuffer; 010import java.nio.charset.CharacterCodingException; 011import java.nio.charset.Charset; 012import java.nio.charset.CharsetEncoder; 013 014import oauth.signpost.OAuthConsumer; 015import oauth.signpost.exception.OAuthException; 016 017import org.openstreetmap.josm.Main; 018import org.openstreetmap.josm.data.oauth.OAuthParameters; 019import org.openstreetmap.josm.gui.preferences.server.OAuthAccessTokenHolder; 020import org.openstreetmap.josm.io.auth.CredentialsAgentException; 021import org.openstreetmap.josm.io.auth.CredentialsAgentResponse; 022import org.openstreetmap.josm.io.auth.CredentialsManager; 023import 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 */ 031public 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 Main.warn("InterruptedException in "+getClass().getSimpleName()+" during cancel"); 059 } 060 061 synchronized (this) { 062 if (activeConnection != null) { 063 activeConnection.disconnect(); 064 } 065 } 066 } 067 068 /** 069 * Adds an authentication header for basic authentication 070 * 071 * @param con the connection 072 * @throws OsmTransferException thrown if something went wrong. Check for nested exceptions 073 */ 074 protected void addBasicAuthorizationHeader(HttpURLConnection con) throws OsmTransferException { 075 CharsetEncoder encoder = Charset.forName("UTF-8").newEncoder(); 076 CredentialsAgentResponse response; 077 String token; 078 try { 079 synchronized (CredentialsManager.getInstance()) { 080 response = CredentialsManager.getInstance().getCredentials(RequestorType.SERVER, 081 con.getURL().getHost(), false /* don't know yet whether the credentials will succeed */); 082 } 083 } catch (CredentialsAgentException e) { 084 throw new OsmTransferException(e); 085 } 086 if (response == null) { 087 token = ":"; 088 } else if (response.isCanceled()) { 089 cancel = true; 090 return; 091 } else { 092 String username= response.getUsername() == null ? "" : response.getUsername(); 093 String password = response.getPassword() == null ? "" : String.valueOf(response.getPassword()); 094 token = username + ":" + password; 095 try { 096 ByteBuffer bytes = encoder.encode(CharBuffer.wrap(token)); 097 con.addRequestProperty("Authorization", "Basic "+Base64.encode(bytes)); 098 } catch(CharacterCodingException e) { 099 throw new OsmTransferException(e); 100 } 101 } 102 } 103 104 /** 105 * Signs the connection with an OAuth authentication header 106 * 107 * @param connection the connection 108 * 109 * @throws OsmTransferException thrown if there is currently no OAuth Access Token configured 110 * @throws OsmTransferException thrown if signing fails 111 */ 112 protected void addOAuthAuthorizationHeader(HttpURLConnection connection) throws OsmTransferException { 113 if (oauthParameters == null) { 114 oauthParameters = OAuthParameters.createFromPreferences(Main.pref); 115 } 116 OAuthConsumer consumer = oauthParameters.buildConsumer(); 117 OAuthAccessTokenHolder holder = OAuthAccessTokenHolder.getInstance(); 118 if (! holder.containsAccessToken()) 119 throw new MissingOAuthAccessTokenException(); 120 consumer.setTokenWithSecret(holder.getAccessTokenKey(), holder.getAccessTokenSecret()); 121 try { 122 consumer.sign(connection); 123 } catch(OAuthException e) { 124 throw new OsmTransferException(tr("Failed to sign a HTTP connection with an OAuth Authentication header"), e); 125 } 126 } 127 128 protected void addAuth(HttpURLConnection connection) throws OsmTransferException { 129 String authMethod = Main.pref.get("osm-server.auth-method", "basic"); 130 if (authMethod.equals("basic")) { 131 addBasicAuthorizationHeader(connection); 132 } else if (authMethod.equals("oauth")) { 133 addOAuthAuthorizationHeader(connection); 134 } else { 135 String msg = tr("Unexpected value for preference ''{0}''. Got ''{1}''.", "osm-server.auth-method", authMethod); 136 Main.warn(msg); 137 throw new OsmTransferException(msg); 138 } 139 } 140 141 /** 142 * Replies true if this connection is canceled 143 * 144 * @return true if this connection is canceled 145 */ 146 public boolean isCanceled() { 147 return cancel; 148 } 149}