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.DocumentBuilderFactory; 013import javax.xml.parsers.ParserConfigurationException; 014 015import oauth.signpost.OAuthConsumer; 016import oauth.signpost.exception.OAuthException; 017 018import org.openstreetmap.josm.Main; 019import org.openstreetmap.josm.data.oauth.OAuthParameters; 020import org.openstreetmap.josm.data.oauth.OAuthToken; 021import org.openstreetmap.josm.data.osm.UserInfo; 022import org.openstreetmap.josm.gui.HelpAwareOptionPane; 023import org.openstreetmap.josm.gui.PleaseWaitRunnable; 024import org.openstreetmap.josm.gui.help.HelpUtil; 025import org.openstreetmap.josm.io.OsmApiException; 026import org.openstreetmap.josm.io.OsmServerUserInfoReader; 027import org.openstreetmap.josm.io.OsmTransferException; 028import org.openstreetmap.josm.io.auth.DefaultAuthenticator; 029import org.openstreetmap.josm.tools.CheckParameterUtil; 030import org.openstreetmap.josm.tools.Utils; 031import org.openstreetmap.josm.tools.XmlParsingException; 032import org.w3c.dom.Document; 033import 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 */ 042public 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, XmlParsingException, 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 = Utils.openHttpConnection(url); 107 } 108 109 connection.setDoOutput(true); 110 connection.setRequestMethod("GET"); 111 sign(connection); 112 connection.connect(); 113 114 if (connection.getResponseCode() == HttpURLConnection.HTTP_UNAUTHORIZED) 115 throw new OsmApiException(HttpURLConnection.HTTP_UNAUTHORIZED, tr("Retrieving user details with Access Token Key ''{0}'' was rejected.", token.getKey()), null); 116 117 if (connection.getResponseCode() == HttpURLConnection.HTTP_FORBIDDEN) 118 throw new OsmApiException(HttpURLConnection.HTTP_FORBIDDEN, tr("Retrieving user details with Access Token Key ''{0}'' was forbidden.", token.getKey()), null); 119 120 if (connection.getResponseCode() != HttpURLConnection.HTTP_OK) 121 throw new OsmApiException(connection.getResponseCode(),connection.getHeaderField("Error"), null); 122 Document d = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(connection.getInputStream()); 123 return OsmServerUserInfoReader.buildFromXML(d); 124 } catch(SAXException | ParserConfigurationException e) { 125 throw new XmlParsingException(e); 126 } catch(IOException e) { 127 throw new OsmTransferException(e); 128 } catch(OAuthException e) { 129 throw new OsmOAuthAuthorizationException(e); 130 } finally { 131 DefaultAuthenticator.getInstance().setEnabled(authenticatorEnabled); 132 } 133 } 134 135 protected void notifySuccess(UserInfo userInfo) { 136 HelpAwareOptionPane.showMessageDialogInEDT( 137 parent, 138 tr("<html>" 139 + "Successfully used the Access Token ''{0}'' to<br>" 140 + "access the OSM server at ''{1}''.<br>" 141 + "You are accessing the OSM server as user ''{2}'' with id ''{3}''." 142 + "</html>", 143 token.getKey(), 144 apiUrl, 145 userInfo.getDisplayName(), 146 userInfo.getId() 147 ), 148 tr("Success"), 149 JOptionPane.INFORMATION_MESSAGE, 150 HelpUtil.ht("/Dialog/OAuthAuthorisationWizard#AccessTokenOK") 151 ); 152 } 153 154 protected void alertFailedAuthentication() { 155 HelpAwareOptionPane.showMessageDialogInEDT( 156 parent, 157 tr("<html>" 158 + "Failed to access the OSM server ''{0}''<br>" 159 + "with the Access Token ''{1}''.<br>" 160 + "The server rejected the Access Token as unauthorized. You will not<br>" 161 + "be able to access any protected resource on this server using this token." 162 +"</html>", 163 apiUrl, 164 token.getKey() 165 ), 166 tr("Test failed"), 167 JOptionPane.ERROR_MESSAGE, 168 HelpUtil.ht("/Dialog/OAuthAuthorisationWizard#AccessTokenFailed") 169 ); 170 } 171 172 protected void alertFailedAuthorisation() { 173 HelpAwareOptionPane.showMessageDialogInEDT( 174 parent, 175 tr("<html>" 176 + "The Access Token ''{1}'' is known to the OSM server ''{0}''.<br>" 177 + "The test to retrieve the user details for this token failed, though.<br>" 178 + "Depending on what rights are granted to this token you may nevertheless use it<br>" 179 + "to upload data, upload GPS traces, and/or access other protected resources." 180 +"</html>", 181 apiUrl, 182 token.getKey() 183 ), 184 tr("Token allows restricted access"), 185 JOptionPane.WARNING_MESSAGE, 186 HelpUtil.ht("/Dialog/OAuthAuthorisationWizard#AccessTokenFailed") 187 ); 188 } 189 190 protected void alertFailedConnection() { 191 HelpAwareOptionPane.showMessageDialogInEDT( 192 parent, 193 tr("<html>" 194 + "Failed to retrieve information about the current user" 195 + " from the OSM server ''{0}''.<br>" 196 + "This is probably not a problem caused by the tested Access Token, but<br>" 197 + "rather a problem with the server configuration. Carefully check the server<br>" 198 + "URL and your Internet connection." 199 +"</html>", 200 apiUrl, 201 token.getKey() 202 ), 203 tr("Test failed"), 204 JOptionPane.ERROR_MESSAGE, 205 HelpUtil.ht("/Dialog/OAuthAuthorisationWizard#AccessTokenFailed") 206 ); 207 } 208 209 protected void alertFailedSigning() { 210 HelpAwareOptionPane.showMessageDialogInEDT( 211 parent, 212 tr("<html>" 213 + "Failed to sign the request for the OSM server ''{0}'' with the " 214 + "token ''{1}''.<br>" 215 + "The token ist probably invalid." 216 +"</html>", 217 apiUrl, 218 token.getKey() 219 ), 220 tr("Test failed"), 221 JOptionPane.ERROR_MESSAGE, 222 HelpUtil.ht("/Dialog/OAuthAuthorisationWizard#AccessTokenFailed") 223 ); 224 } 225 226 protected void alertInternalError() { 227 HelpAwareOptionPane.showMessageDialogInEDT( 228 parent, 229 tr("<html>" 230 + "The test failed because the server responded with an internal error.<br>" 231 + "JOSM could not decide whether the token is valid. Please try again later." 232 + "</html>", 233 apiUrl, 234 token.getKey() 235 ), 236 tr("Test failed"), 237 JOptionPane.WARNING_MESSAGE, 238 HelpUtil.ht("/Dialog/OAuthAuthorisationWizard#AccessTokenFailed") 239 ); 240 } 241 242 @Override 243 protected void realRun() throws SAXException, IOException, OsmTransferException { 244 try { 245 getProgressMonitor().indeterminateSubTask(tr("Retrieving user info...")); 246 UserInfo userInfo = getUserDetails(); 247 if (canceled) return; 248 notifySuccess(userInfo); 249 }catch(OsmOAuthAuthorizationException e) { 250 if (canceled) return; 251 Main.error(e); 252 alertFailedSigning(); 253 } catch(OsmApiException e) { 254 if (canceled) return; 255 Main.error(e); 256 if (e.getResponseCode() == HttpURLConnection.HTTP_INTERNAL_ERROR) { 257 alertInternalError(); 258 return; 259 } else if (e.getResponseCode() == HttpURLConnection.HTTP_UNAUTHORIZED) { 260 alertFailedAuthentication(); 261 return; 262 } else if (e.getResponseCode() == HttpURLConnection.HTTP_FORBIDDEN) { 263 alertFailedAuthorisation(); 264 return; 265 } 266 alertFailedConnection(); 267 } catch(OsmTransferException e) { 268 if (canceled) return; 269 Main.error(e); 270 alertFailedConnection(); 271 } 272 } 273}