
package com.google.refine.commands;

import java.security.SecureRandom;
import java.time.Instant;
import java.util.Map;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;

import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import org.apache.commons.lang3.RandomStringUtils;

/**
 * Generates CSRF tokens and checks their validity.
 * 
 * @author Antonin Delpeuch
 *
 */
public class CSRFTokenFactory {

    /**
     * Maps each token to the time it was generated
     */
    protected final LoadingCache<String, Instant> tokenCache;

    /**
     * Time to live for tokens, in seconds
     */
    protected final long timeToLive;

    /**
     * Length of the tokens to generate
     */
    protected final int tokenLength;

    /**
     * Random number generator used to create tokens
     */
    protected final SecureRandom rng;

    /**
     * Constructs a new CSRF token factory.
     * 
     * @param timeToLive
     *            Time to live for tokens, in seconds
     * @param tokenLength
     *            Length of the tokens generated
     */
    public CSRFTokenFactory(long timeToLive, int tokenLength) {
        tokenCache = CacheBuilder.newBuilder()
                .expireAfterWrite(timeToLive, TimeUnit.SECONDS)
                .build(
                        new CacheLoader<String, Instant>() {

                            @Override
                            public Instant load(String key) {
                                return Instant.now();
                            }

                        });
        this.timeToLive = timeToLive;
        this.rng = new SecureRandom();
        this.tokenLength = tokenLength;
    }

    /**
     * Generates a fresh CSRF token, which will remain valid for the configured amount of time.
     */
    public String getFreshToken() {
        // Generate a random token
        String token = RandomStringUtils.random(tokenLength, 0, 0, true, true, null, rng);
        // Put it in the cache
        try {
            tokenCache.get(token);
        } catch (ExecutionException e) {
            // cannot happen
        }
        return token;
    }

    /**
     * Checks that a given CSRF token is valid.
     * 
     * @param token
     *            the token to verify
     * @return true if the token is valid
     */
    public boolean validToken(String token) {
        Map<String, Instant> map = tokenCache.asMap();
        Instant cutoff = Instant.now().minusSeconds(timeToLive);
        return map.containsKey(token) && map.get(token).isAfter(cutoff);
    }
}
