| 1 | // License: GPL. For details, see LICENSE file. |
|---|
| 2 | package org.openstreetmap.josm.gui.oauth; |
|---|
| 3 | |
|---|
| 4 | import static org.openstreetmap.josm.tools.I18n.tr; |
|---|
| 5 | |
|---|
| 6 | import java.awt.Component; |
|---|
| 7 | import java.io.IOException; |
|---|
| 8 | import java.net.HttpURLConnection; |
|---|
| 9 | import java.net.MalformedURLException; |
|---|
| 10 | import java.net.URL; |
|---|
| 11 | |
|---|
| 12 | import javax.swing.JOptionPane; |
|---|
| 13 | import javax.xml.parsers.DocumentBuilderFactory; |
|---|
| 14 | import javax.xml.parsers.ParserConfigurationException; |
|---|
| 15 | |
|---|
| 16 | import oauth.signpost.OAuthConsumer; |
|---|
| 17 | import oauth.signpost.exception.OAuthException; |
|---|
| 18 | |
|---|
| 19 | import org.openstreetmap.josm.data.Version; |
|---|
| 20 | import org.openstreetmap.josm.data.oauth.OAuthParameters; |
|---|
| 21 | import org.openstreetmap.josm.data.oauth.OAuthToken; |
|---|
| 22 | import org.openstreetmap.josm.data.osm.UserInfo; |
|---|
| 23 | import org.openstreetmap.josm.gui.HelpAwareOptionPane; |
|---|
| 24 | import org.openstreetmap.josm.gui.PleaseWaitRunnable; |
|---|
| 25 | import org.openstreetmap.josm.gui.help.HelpUtil; |
|---|
| 26 | import org.openstreetmap.josm.io.OsmApiException; |
|---|
| 27 | import org.openstreetmap.josm.io.OsmDataParsingException; |
|---|
| 28 | import org.openstreetmap.josm.io.OsmServerUserInfoReader; |
|---|
| 29 | import org.openstreetmap.josm.io.OsmTransferException; |
|---|
| 30 | import org.openstreetmap.josm.io.auth.DefaultAuthenticator; |
|---|
| 31 | import org.openstreetmap.josm.tools.CheckParameterUtil; |
|---|
| 32 | import org.w3c.dom.Document; |
|---|
| 33 | import org.xml.sax.SAXException; |
|---|
| 34 | |
|---|
| 35 | /** |
|---|
| 36 | * Checks whether an OSM API server can be accessed with a specific Access Token. |
|---|
| 37 | * |
|---|
| 38 | * It retrieves the user details for the user which is authorized to access the server with |
|---|
| 39 | * this token. |
|---|
| 40 | * |
|---|
| 41 | */ |
|---|
| 42 | public class TestAccessTokenTask extends PleaseWaitRunnable { |
|---|
| 43 | private OAuthToken token; |
|---|
| 44 | private OAuthParameters oauthParameters; |
|---|
| 45 | private boolean canceled; |
|---|
| 46 | private Component parent; |
|---|
| 47 | private String apiUrl; |
|---|
| 48 | private HttpURLConnection connection; |
|---|
| 49 | |
|---|
| 50 | /** |
|---|
| 51 | * Create the task |
|---|
| 52 | * |
|---|
| 53 | * @param parent the parent component relative to which the {@see PleaseWaitRunnable}-Dialog is displayed |
|---|
| 54 | * @param apiUrl the API URL. Must not be null. |
|---|
| 55 | * @param parameters the OAuth parameters. Must not be null. |
|---|
| 56 | * @param accessToken the Access Token. Must not be null. |
|---|
| 57 | */ |
|---|
| 58 | public TestAccessTokenTask(Component parent, String apiUrl, OAuthParameters parameters, OAuthToken accessToken) { |
|---|
| 59 | super(parent, tr("Testing OAuth Access Token"), false /* don't ignore exceptions */); |
|---|
| 60 | CheckParameterUtil.ensureParameterNotNull(apiUrl, "apiUrl"); |
|---|
| 61 | CheckParameterUtil.ensureParameterNotNull(parameters, "parameters"); |
|---|
| 62 | CheckParameterUtil.ensureParameterNotNull(accessToken, "accessToken"); |
|---|
| 63 | this.token = accessToken; |
|---|
| 64 | this.oauthParameters = parameters; |
|---|
| 65 | this.parent = parent; |
|---|
| 66 | this.apiUrl = apiUrl; |
|---|
| 67 | } |
|---|
| 68 | |
|---|
| 69 | @Override |
|---|
| 70 | protected void cancel() { |
|---|
| 71 | canceled = true; |
|---|
| 72 | synchronized(this) { |
|---|
| 73 | if (connection != null) { |
|---|
| 74 | connection.disconnect(); |
|---|
| 75 | } |
|---|
| 76 | } |
|---|
| 77 | } |
|---|
| 78 | |
|---|
| 79 | @Override |
|---|
| 80 | protected void finish() {} |
|---|
| 81 | |
|---|
| 82 | protected void sign(HttpURLConnection con) throws OAuthException{ |
|---|
| 83 | OAuthConsumer consumer = oauthParameters.buildConsumer(); |
|---|
| 84 | consumer.setTokenWithSecret(token.getKey(), token.getSecret()); |
|---|
| 85 | consumer.sign(con); |
|---|
| 86 | } |
|---|
| 87 | |
|---|
| 88 | protected String normalizeApiUrl(String url) { |
|---|
| 89 | // remove leading and trailing white space |
|---|
| 90 | url = url.trim(); |
|---|
| 91 | |
|---|
| 92 | // remove trailing slashes |
|---|
| 93 | while(url.endsWith("/")) { |
|---|
| 94 | url = url.substring(0, url.lastIndexOf("/")); |
|---|
| 95 | } |
|---|
| 96 | return url; |
|---|
| 97 | } |
|---|
| 98 | |
|---|
| 99 | 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 ''{0}''.<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 | } |
|---|