001package org.syncany.plugins.transfer.oauth;
002
003import java.io.IOException;
004import java.io.InputStream;
005import java.net.URI;
006import java.util.logging.Level;
007import java.util.logging.Logger;
008
009import org.apache.commons.io.IOUtils;
010import io.undertow.server.HttpServerExchange;
011import io.undertow.util.Headers;
012import io.undertow.util.StatusCodes;
013
014/**
015 * Factory class to generate some common {@link OAuthTokenInterceptor}s.
016 *
017 * @author Christian Roth (christian.roth@port17.de)
018 */
019public abstract class OAuthTokenInterceptors {
020
021        private static final Logger logger = Logger.getLogger(OAuthTokenInterceptors.class.getName());
022
023        /**
024         * Has to be {@value} because it's the first step of the OAuth process.
025         */
026        static final String PATH_PREFIX = "/";
027
028        /**
029         * Get a common {@link OAuthTokenInterceptor} depending on the chosen {@link OAuthMode}.
030         * If {@link OAuthMode#BROWSER} is used a {@link HashTokenInterceptor}
031         * is returned and a {@link OAuthTokenInterceptors.RedirectTokenInterceptor} in {@link OAuthMode#SERVER}.
032         *
033         * @param mode {@link OAuthMode} supported by the {@link org.syncany.plugins.transfer.TransferPlugin}.
034         * @return Either a {@link HashTokenInterceptor} or a {@link OAuthTokenInterceptors.RedirectTokenInterceptor}
035         */
036        public static OAuthTokenInterceptor newTokenInterceptorForMode(OAuthMode mode) {
037                switch (mode) {
038                        case BROWSER:
039                                return new HashTokenInterceptor();
040
041                        case SERVER:
042                                return new RedirectTokenInterceptor();
043
044                        default:
045                                throw new RuntimeException("Unknown OAuth mode");
046                }
047        }
048
049        /**
050         * {@link OAuthTokenInterceptor} implementation which bypasses some protection mechanisms to allow the token extraction.
051         * In {@link OAuthMode#BROWSER}, the service provider uses the fragment part (the part after the #) of a URL to send over
052         * a token. However, this part cannot be retrieved by a WebServer. A {@link HashTokenInterceptor}
053         * appends the fragment variables to the query parameters of the URL.
054         */
055        public static class HashTokenInterceptor implements OAuthTokenInterceptor {
056
057                public static final String PLACEHOLDER_FOR_EXTRACT_PATH = "%extractPath%";
058                private static final String HTML_SITE_RESOURCE_PATH = "/org/syncany/plugins/oauth/HashTokenInterceptor.html";
059
060                private final String html;
061
062                public HashTokenInterceptor() {
063                        try(InputStream htmlSiteStream = HashTokenInterceptor.class.getResourceAsStream(HTML_SITE_RESOURCE_PATH)) {
064                                this.html = IOUtils.toString(htmlSiteStream).replace(PLACEHOLDER_FOR_EXTRACT_PATH, OAuthTokenWebListener.ExtractingTokenInterceptor.PATH_PREFIX);
065                        }
066                        catch (IOException e) {
067                                logger.log(Level.SEVERE, "Unable to read html site from " + HTML_SITE_RESOURCE_PATH, e);
068                                throw new RuntimeException("Unable to read html site from " + HTML_SITE_RESOURCE_PATH);
069                        }
070                }
071
072                @Override
073                public String getPathPrefix() {
074                        return PATH_PREFIX;
075                }
076
077                @Override
078                public void handleRequest(HttpServerExchange exchange) throws Exception {
079                        exchange.getResponseHeaders().put(Headers.CONTENT_TYPE, "text/html");
080                        exchange.setResponseCode(StatusCodes.OK);
081                        exchange.getResponseSender().send(html);
082                        exchange.endExchange();
083                }
084        }
085
086        /**
087         * A {@link RedirectTokenInterceptor} can be seen as an empty {@link OAuthTokenInterceptor} because it only redirects
088         * to the next step of the OAuth process which is the extraction of the token from the URL. It's needed in {@link OAuthMode#SERVER}
089         * since the token parameter is already provided in the URL's query part.
090         */
091        public static class RedirectTokenInterceptor implements OAuthTokenInterceptor {
092
093                @Override
094                public String getPathPrefix() {
095                        return PATH_PREFIX;
096                }
097
098                @Override
099                public void handleRequest(HttpServerExchange exchange) throws Exception {
100                        final String redirectToUrl = String.format("%s/%s?%s", exchange.getRequestURL(), OAuthTokenWebListener.ExtractingTokenInterceptor.PATH_PREFIX, exchange.getQueryString());
101                        final URI redirectToUri = URI.create(redirectToUrl).normalize();
102
103                        logger.log(Level.INFO, "Redirecting to " + redirectToUri);
104
105                        exchange.setResponseCode(StatusCodes.FOUND);
106                        exchange.getResponseHeaders().put(Headers.LOCATION, redirectToUri.toString());
107                        exchange.endExchange();
108                }
109        }
110
111}