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