001package org.syncany.plugins.transfer.oauth;
002
003import java.net.URI;
004import java.util.List;
005import java.util.logging.Level;
006import java.util.logging.Logger;
007
008import org.apache.http.NameValuePair;
009import org.apache.http.client.utils.URLEncodedUtils;
010import com.google.common.base.Charsets;
011
012/**
013 * Factory class to generate some common {@link OAuthTokenExtractor}s.
014 *
015 * @author Christian Roth (christian.roth@port17.de)
016 */
017public abstract class OAuthTokenExtractors {
018        private static final Logger logger = Logger.getLogger(OAuthTokenExtractors.class.getName());
019
020        public static final String RFC_CODE_FIELD = "code";
021        public static final String RFC_ACCESS_TOKEN_FIELD = "access_token";
022        public static final String RFC_STATE_FIELD = "state";
023
024        /**
025         * Get a common {@link OAuthTokenExtractor} depending on the chosen {@link OAuthMode}. More precisely, this creates a
026         * {@link OAuthTokenExtractors.NamedQueryTokenExtractor} with token field id set to
027         * {@value #RFC_STATE_FIELD} in {@link OAuthMode#SERVER} and {@value #RFC_ACCESS_TOKEN_FIELD} in {@link OAuthMode#BROWSER}.
028         * However, {@value #RFC_STATE_FIELD} is used in both cases to identify a potential CSRF value.
029         *
030         * @param mode {@link OAuthMode} supported by the {@link org.syncany.plugins.transfer.TransferPlugin}.
031         * @return A corresponding {@link OAuthTokenExtractors.NamedQueryTokenExtractor}.
032         */
033        public static OAuthTokenExtractor newTokenExtractorForMode(OAuthMode mode) {
034                switch (mode) {
035                        case BROWSER:
036                                return new NamedQueryTokenExtractor(RFC_ACCESS_TOKEN_FIELD, RFC_STATE_FIELD);
037
038                        case SERVER:
039                                return new NamedQueryTokenExtractor(RFC_CODE_FIELD, RFC_STATE_FIELD);
040
041                        default:
042                                throw new RuntimeException("Unknown OAuth mode");
043                }
044        }
045
046        /**
047         * A {@link NamedQueryTokenExtractor} is a simple {@link OAuthTokenExtractor} which looks for a token and a CSRF secret
048         * in the redirect URL. Field names a variables.
049         */
050        public static class NamedQueryTokenExtractor implements OAuthTokenExtractor {
051                private final String tokenId;
052                private final String stateId;
053
054                NamedQueryTokenExtractor(String tokenId, String stateId) {
055                        this.tokenId = tokenId;
056                        this.stateId = stateId;
057                }
058
059                @Override
060                public OAuthTokenFinish parse(String uriWithToken) throws NoSuchFieldException {
061                        List<NameValuePair> params = URLEncodedUtils.parse(URI.create(uriWithToken), Charsets.UTF_8.name());
062
063                        String token = null;
064                        String state = null;
065
066                        for (NameValuePair param : params) {
067                                if (tokenId.equalsIgnoreCase(param.getName())) {
068                                        token = param.getValue();
069                                        logger.log(Level.FINE, "Found token in URL " + token);
070                                }
071                                else if (stateId.equalsIgnoreCase(param.getName())) {
072                                        state = param.getValue();
073                                        logger.log(Level.FINE, "Found state in URL " + state);
074                                }
075                        }
076
077                        if (token == null || state == null) {
078                                throw new NoSuchFieldException(String.format("URI (%s) does not contain token field (%s, %s)", uriWithToken, tokenId, stateId));
079                        }
080
081                        return new OAuthTokenFinish(token, state);
082                }
083        }
084
085}