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