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}