me.lazerka.gae.jersey.oauth2.OauthModule.java Source code

Java tutorial

Introduction

Here is the source code for me.lazerka.gae.jersey.oauth2.OauthModule.java

Source

/*
 * Copyright (c) 2016 Dzmitry Lazerka
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package me.lazerka.gae.jersey.oauth2;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.api.client.extensions.appengine.http.UrlFetchTransport;
import com.google.api.client.googleapis.auth.oauth2.GoogleIdTokenVerifier;
import com.google.api.client.googleapis.auth.oauth2.GooglePublicKeysManager;
import com.google.api.client.json.jackson2.JacksonFactory;
import com.google.appengine.api.urlfetch.URLFetchServiceFactory;
import com.google.common.collect.ImmutableSet;
import com.google.common.io.Files;
import com.google.inject.AbstractModule;
import com.google.inject.multibindings.Multibinder;
import com.google.inject.name.Names;
import me.lazerka.gae.jersey.oauth2.facebook.TokenVerifierFacebookDebugToken;
import me.lazerka.gae.jersey.oauth2.google.TokenVerifierGoogleSignature;
import org.joda.time.DateTime;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.annotation.Nonnull;
import javax.inject.Provider;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.Set;

import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import static java.nio.charset.StandardCharsets.UTF_8;
import static org.joda.time.DateTimeZone.UTC;

/**
 * Configures Google and Facebook token verifiers.
 *
 * You do not have to use this module.
 * Here's only one important binding: `Set<TokenVerifier>` -- you can bind your own.
 *
 * @author Dzmitry Lazerka
 */
public class OauthModule extends AbstractModule {
    private static final Logger logger = LoggerFactory.getLogger(OauthModule.class);

    private static final Set<String> ALLOWED_ISSUERS = ImmutableSet.of("accounts.google.com",
            "https://accounts.google.com");

    private final String googleClientId;
    private final String facebookAppId;
    private final String facebookAppSecret;

    public OauthModule(@Nonnull File googleClientId, @Nonnull File facebookAppId, @Nonnull File facebookAppSecret) {
        this(readKey(googleClientId), readKey(facebookAppId), readKey(facebookAppSecret));
    }

    /**
     * @param googleClientId Issued by authorization server.
     */
    public OauthModule(@Nonnull String googleClientId, @Nonnull String facebookAppId,
            @Nonnull String facebookAppSecret) {
        checkArgument(googleClientId.endsWith(".apps.googleusercontent.com"),
                "Must end with '.apps.googleusercontent.com'");
        this.googleClientId = checkNotNull(googleClientId);
        this.facebookAppId = checkNotNull(facebookAppId);
        this.facebookAppSecret = checkNotNull(facebookAppSecret);
    }

    @Override
    protected void configure() {

        // This guy is recommended to be a singleton, because it keeps a shared store of Google's public keys.
        GooglePublicKeysManager googlePublicKeysManager = getGooglePublicKeysManager();
        bind(GooglePublicKeysManager.class).toInstance(googlePublicKeysManager);
        TokenVerifier googleVerifier = new TokenVerifierGoogleSignature(
                getGoogleIdTokenVerifier(googlePublicKeysManager, googleClientId), new NowProvider());

        ObjectMapper jackson = new ObjectMapper();

        bind(String.class).annotatedWith(Names.named("facebook.app.id")).toInstance(facebookAppId);
        bind(String.class).annotatedWith(Names.named("facebook.app.secret")).toInstance(facebookAppSecret);
        TokenVerifier facebookVerifierSignedRequest = new TokenVerifierFacebookDebugToken(
                URLFetchServiceFactory.getURLFetchService(), jackson, facebookAppId, facebookAppSecret,
                new NowProvider());

        Multibinder<TokenVerifier> multibinder = Multibinder.newSetBinder(binder(), TokenVerifier.class);
        multibinder.addBinding().toInstance(googleVerifier);
        multibinder.addBinding().toInstance(facebookVerifierSignedRequest);

        bind(TokenVerifier.class).annotatedWith(Names.named("default")).toInstance(googleVerifier);
    }

    private GooglePublicKeysManager getGooglePublicKeysManager() {
        logger.trace("Creating " + GooglePublicKeysManager.class.getSimpleName());

        UrlFetchTransport transport = new UrlFetchTransport.Builder().validateCertificate().build();

        // This guy should be singleton.
        return new GooglePublicKeysManager(transport, JacksonFactory.getDefaultInstance());
    }

    private GoogleIdTokenVerifier getGoogleIdTokenVerifier(GooglePublicKeysManager publicKeysManager,
            String clientId) {
        return new GoogleIdTokenVerifier.Builder(publicKeysManager).setAudience(ImmutableSet.of(clientId))
                .setIssuers(ALLOWED_ISSUERS).build();
    }

    /**
     * Reads whole file as a string.
     */
    static String readKey(File file) {
        logger.trace("Reading {}", file.getAbsolutePath());

        try {
            String result = Files.toString(file, UTF_8).trim();
            if (result.isEmpty()) {
                throw new RuntimeException("File is empty: " + file.getAbsolutePath());
            }

            return result;
        } catch (FileNotFoundException e) {
            throw new RuntimeException("File " + file.getAbsolutePath() + " not found.");
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * Simply returns current time. Helps mocking in unit-tests.
     */
    static class NowProvider implements Provider<DateTime> {
        @Override
        public DateTime get() {
            return DateTime.now(UTC);
        }
    }
}