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 }