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