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    }