OAuthTokenExtractors.java

package org.syncany.plugins.transfer.oauth;

import java.net.URI;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;

import org.apache.http.NameValuePair;
import org.apache.http.client.utils.URLEncodedUtils;
import com.google.common.base.Charsets;

/**
 * Factory class to generate some common {@link OAuthTokenExtractor}s.
 *
 * @author Christian Roth (christian.roth@port17.de)
 */
public abstract class OAuthTokenExtractors {
	private static final Logger logger = Logger.getLogger(OAuthTokenExtractors.class.getName());

	public static final String RFC_CODE_FIELD = "code";
	public static final String RFC_ACCESS_TOKEN_FIELD = "access_token";
	public static final String RFC_STATE_FIELD = "state";

	/**
	 * Get a common {@link OAuthTokenExtractor} depending on the chosen {@link OAuthMode}. More precisely, this creates a
	 * {@link OAuthTokenExtractors.NamedQueryTokenExtractor} with token field id set to
	 * {@value #RFC_STATE_FIELD} in {@link OAuthMode#SERVER} and {@value #RFC_ACCESS_TOKEN_FIELD} in {@link OAuthMode#BROWSER}.
	 * However, {@value #RFC_STATE_FIELD} is used in both cases to identify a potential CSRF value.
	 *
	 * @param mode {@link OAuthMode} supported by the {@link org.syncany.plugins.transfer.TransferPlugin}.
	 * @return A corresponding {@link OAuthTokenExtractors.NamedQueryTokenExtractor}.
	 */
	public static OAuthTokenExtractor newTokenExtractorForMode(OAuthMode mode) {
		switch (mode) {
			case BROWSER:
				return new NamedQueryTokenExtractor(RFC_ACCESS_TOKEN_FIELD, RFC_STATE_FIELD);

			case SERVER:
				return new NamedQueryTokenExtractor(RFC_CODE_FIELD, RFC_STATE_FIELD);

			default:
				throw new RuntimeException("Unknown OAuth mode");
		}
	}

	/**
	 * A {@link NamedQueryTokenExtractor} is a simple {@link OAuthTokenExtractor} which looks for a token and a CSRF secret
	 * in the redirect URL. Field names a variables.
	 */
	public static class NamedQueryTokenExtractor implements OAuthTokenExtractor {
		private final String tokenId;
		private final String stateId;

		NamedQueryTokenExtractor(String tokenId, String stateId) {
			this.tokenId = tokenId;
			this.stateId = stateId;
		}

		@Override
		public OAuthTokenFinish parse(String uriWithToken) throws NoSuchFieldException {
			List<NameValuePair> params = URLEncodedUtils.parse(URI.create(uriWithToken), Charsets.UTF_8.name());

			String token = null;
			String state = null;

			for (NameValuePair param : params) {
				if (tokenId.equalsIgnoreCase(param.getName())) {
					token = param.getValue();
					logger.log(Level.FINE, "Found token in URL " + token);
				}
				else if (stateId.equalsIgnoreCase(param.getName())) {
					state = param.getValue();
					logger.log(Level.FINE, "Found state in URL " + state);
				}
			}

			if (token == null || state == null) {
				throw new NoSuchFieldException(String.format("URI (%s) does not contain token field (%s, %s)", uriWithToken, tokenId, stateId));
			}

			return new OAuthTokenFinish(token, state);
		}
	}

}