001 // License: GPL. For details, see LICENSE file. 002 package org.openstreetmap.josm.gui.oauth; 003 004 import static org.openstreetmap.josm.tools.I18n.tr; 005 006 import java.awt.Component; 007 import java.io.IOException; 008 import java.net.HttpURLConnection; 009 import java.net.MalformedURLException; 010 import java.net.URL; 011 012 import javax.swing.JOptionPane; 013 import javax.xml.parsers.DocumentBuilderFactory; 014 import javax.xml.parsers.ParserConfigurationException; 015 016 import oauth.signpost.OAuthConsumer; 017 import oauth.signpost.exception.OAuthException; 018 019 import org.openstreetmap.josm.data.Version; 020 import org.openstreetmap.josm.data.oauth.OAuthParameters; 021 import org.openstreetmap.josm.data.oauth.OAuthToken; 022 import org.openstreetmap.josm.data.osm.UserInfo; 023 import org.openstreetmap.josm.gui.HelpAwareOptionPane; 024 import org.openstreetmap.josm.gui.PleaseWaitRunnable; 025 import org.openstreetmap.josm.gui.help.HelpUtil; 026 import org.openstreetmap.josm.io.OsmApiException; 027 import org.openstreetmap.josm.io.OsmDataParsingException; 028 import org.openstreetmap.josm.io.OsmServerUserInfoReader; 029 import org.openstreetmap.josm.io.OsmTransferException; 030 import org.openstreetmap.josm.io.auth.DefaultAuthenticator; 031 import org.openstreetmap.josm.tools.CheckParameterUtil; 032 import org.w3c.dom.Document; 033 import org.xml.sax.SAXException; 034 035 /** 036 * Checks whether an OSM API server can be accessed with a specific Access Token. 037 * 038 * It retrieves the user details for the user which is authorized to access the server with 039 * this token. 040 * 041 */ 042 public class TestAccessTokenTask extends PleaseWaitRunnable { 043 private OAuthToken token; 044 private OAuthParameters oauthParameters; 045 private boolean canceled; 046 private Component parent; 047 private String apiUrl; 048 private HttpURLConnection connection; 049 050 /** 051 * Create the task 052 * 053 * @param parent the parent component relative to which the {@link PleaseWaitRunnable}-Dialog is displayed 054 * @param apiUrl the API URL. Must not be null. 055 * @param parameters the OAuth parameters. Must not be null. 056 * @param accessToken the Access Token. Must not be null. 057 */ 058 public TestAccessTokenTask(Component parent, String apiUrl, OAuthParameters parameters, OAuthToken accessToken) { 059 super(parent, tr("Testing OAuth Access Token"), false /* don't ignore exceptions */); 060 CheckParameterUtil.ensureParameterNotNull(apiUrl, "apiUrl"); 061 CheckParameterUtil.ensureParameterNotNull(parameters, "parameters"); 062 CheckParameterUtil.ensureParameterNotNull(accessToken, "accessToken"); 063 this.token = accessToken; 064 this.oauthParameters = parameters; 065 this.parent = parent; 066 this.apiUrl = apiUrl; 067 } 068 069 @Override 070 protected void cancel() { 071 canceled = true; 072 synchronized(this) { 073 if (connection != null) { 074 connection.disconnect(); 075 } 076 } 077 } 078 079 @Override 080 protected void finish() {} 081 082 protected void sign(HttpURLConnection con) throws OAuthException{ 083 OAuthConsumer consumer = oauthParameters.buildConsumer(); 084 consumer.setTokenWithSecret(token.getKey(), token.getSecret()); 085 consumer.sign(con); 086 } 087 088 protected String normalizeApiUrl(String url) { 089 // remove leading and trailing white space 090 url = url.trim(); 091 092 // remove trailing slashes 093 while(url.endsWith("/")) { 094 url = url.substring(0, url.lastIndexOf("/")); 095 } 096 return url; 097 } 098 099 protected UserInfo getUserDetails() throws OsmOAuthAuthorizationException, OsmDataParsingException,OsmTransferException { 100 boolean authenticatorEnabled = true; 101 try { 102 URL url = new URL(normalizeApiUrl(apiUrl) + "/0.6/user/details"); 103 authenticatorEnabled = DefaultAuthenticator.getInstance().isEnabled(); 104 DefaultAuthenticator.getInstance().setEnabled(false); 105 synchronized(this) { 106 connection = (HttpURLConnection)url.openConnection(); 107 } 108 109 connection.setDoOutput(true); 110 connection.setRequestMethod("GET"); 111 connection.setRequestProperty("User-Agent", Version.getInstance().getAgentString()); 112 connection.setRequestProperty("Host", connection.getURL().getHost()); 113 sign(connection); 114 connection.connect(); 115 116 if (connection.getResponseCode() == HttpURLConnection.HTTP_UNAUTHORIZED) 117 throw new OsmApiException(HttpURLConnection.HTTP_UNAUTHORIZED, tr("Retrieving user details with Access Token Key ''{0}'' was rejected.", token.getKey()), null); 118 119 if (connection.getResponseCode() == HttpURLConnection.HTTP_FORBIDDEN) 120 throw new OsmApiException(HttpURLConnection.HTTP_FORBIDDEN, tr("Retrieving user details with Access Token Key ''{0}'' was forbidden.", token.getKey()), null); 121 122 if (connection.getResponseCode() != HttpURLConnection.HTTP_OK) 123 throw new OsmApiException(connection.getResponseCode(),connection.getHeaderField("Error"), null); 124 Document d = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(connection.getInputStream()); 125 return OsmServerUserInfoReader.buildFromXML(d); 126 } catch(SAXException e) { 127 throw new OsmDataParsingException(e); 128 } catch(ParserConfigurationException e){ 129 throw new OsmDataParsingException(e); 130 } catch(MalformedURLException e) { 131 throw new OsmTransferException(e); 132 } catch(IOException e){ 133 throw new OsmTransferException(e); 134 } catch(OAuthException e) { 135 throw new OsmOAuthAuthorizationException(e); 136 } finally { 137 DefaultAuthenticator.getInstance().setEnabled(authenticatorEnabled); 138 } 139 } 140 141 protected void notifySuccess(UserInfo userInfo) { 142 HelpAwareOptionPane.showOptionDialog( 143 parent, 144 tr("<html>" 145 + "Successfully used the Access Token ''{0}'' to<br>" 146 + "access the OSM server at ''{1}''.<br>" 147 + "You are accessing the OSM server as user ''{2}'' with id ''{3}''." 148 + "</html>", 149 token.getKey(), 150 apiUrl, 151 userInfo.getDisplayName(), 152 userInfo.getId() 153 ), 154 tr("Success"), 155 JOptionPane.INFORMATION_MESSAGE, 156 HelpUtil.ht("/Dialog/OAuthAuthorisationWizard#AccessTokenOK") 157 ); 158 } 159 160 protected void alertFailedAuthentication() { 161 HelpAwareOptionPane.showOptionDialog( 162 parent, 163 tr("<html>" 164 + "Failed to access the OSM server ''{0}''<br>" 165 + "with the Access Token ''{1}''.<br>" 166 + "The server rejected the Access Token as unauthorized. You will not<br>" 167 + "be able to access any protected resource on this server using this token." 168 +"</html>", 169 apiUrl, 170 token.getKey() 171 ), 172 tr("Test failed"), 173 JOptionPane.ERROR_MESSAGE, 174 HelpUtil.ht("/Dialog/OAuthAuthorisationWizard#AccessTokenFailed") 175 ); 176 } 177 178 protected void alertFailedAuthorisation() { 179 HelpAwareOptionPane.showOptionDialog( 180 parent, 181 tr("<html>" 182 + "The Access Token ''{1}'' is known to the OSM server ''{0}''.<br>" 183 + "The test to retrieve the user details for this token failed, though.<br>" 184 + "Depending on what rights are granted to this token you may nevertheless use it<br>" 185 + "to upload data, upload GPS traces, and/or access other protected resources." 186 +"</html>", 187 apiUrl, 188 token.getKey() 189 ), 190 tr("Token allows restricted access"), 191 JOptionPane.WARNING_MESSAGE, 192 HelpUtil.ht("/Dialog/OAuthAuthorisationWizard#AccessTokenFailed") 193 ); 194 } 195 196 protected void alertFailedConnection() { 197 HelpAwareOptionPane.showOptionDialog( 198 parent, 199 tr("<html>" 200 + "Failed to retrieve information about the current user" 201 + " from the OSM server ''{0}''.<br>" 202 + "This is probably not a problem caused by the tested Access Token, but<br>" 203 + "rather a problem with the server configuration. Carefully check the server<br>" 204 + "URL and your Internet connection." 205 +"</html>", 206 apiUrl, 207 token.getKey() 208 ), 209 tr("Test failed"), 210 JOptionPane.ERROR_MESSAGE, 211 HelpUtil.ht("/Dialog/OAuthAuthorisationWizard#AccessTokenFailed") 212 ); 213 } 214 215 protected void alertFailedSigning() { 216 HelpAwareOptionPane.showOptionDialog( 217 parent, 218 tr("<html>" 219 + "Failed to sign the request for the OSM server ''{0}'' with the " 220 + "token ''{1}''.<br>" 221 + "The token ist probably invalid." 222 +"</html>", 223 apiUrl, 224 token.getKey() 225 ), 226 tr("Test failed"), 227 JOptionPane.ERROR_MESSAGE, 228 HelpUtil.ht("/Dialog/OAuthAuthorisationWizard#AccessTokenFailed") 229 ); 230 } 231 232 protected void alertInternalError() { 233 HelpAwareOptionPane.showOptionDialog( 234 parent, 235 tr("<html>" 236 + "The test failed because the server responded with an internal error.<br>" 237 + "JOSM could not decide whether the token is valid. Please try again later." 238 + "</html>", 239 apiUrl, 240 token.getKey() 241 ), 242 tr("Test failed"), 243 JOptionPane.WARNING_MESSAGE, 244 HelpUtil.ht("/Dialog/OAuthAuthorisationWizard#AccessTokenFailed") 245 ); 246 } 247 248 @Override 249 protected void realRun() throws SAXException, IOException, OsmTransferException { 250 try { 251 getProgressMonitor().indeterminateSubTask(tr("Retrieving user info...")); 252 UserInfo userInfo = getUserDetails(); 253 if (canceled) return; 254 notifySuccess(userInfo); 255 }catch(OsmOAuthAuthorizationException e) { 256 if (canceled) return; 257 e.printStackTrace(); 258 alertFailedSigning(); 259 } catch(OsmApiException e) { 260 if (canceled) return; 261 e.printStackTrace(); 262 if (e.getResponseCode() == HttpURLConnection.HTTP_INTERNAL_ERROR) { 263 alertInternalError(); 264 return; 265 } if (e.getResponseCode() == HttpURLConnection.HTTP_UNAUTHORIZED) { 266 alertFailedAuthentication(); 267 return; 268 } else if (e.getResponseCode() == HttpURLConnection.HTTP_FORBIDDEN) { 269 alertFailedAuthorisation(); 270 return; 271 } 272 alertFailedConnection(); 273 } catch(OsmTransferException e) { 274 if (canceled) return; 275 e.printStackTrace(); 276 alertFailedConnection(); 277 } 278 } 279 }