package net.unicon.cas.addons.authentication.strong.yubikey;

import com.yubico.client.v2.YubicoClient;
import com.yubico.client.v2.YubicoResponse;
import com.yubico.client.v2.YubicoResponseStatus;
import org.jasig.cas.authentication.handler.AuthenticationException;
import org.jasig.cas.authentication.handler.BadUsernameOrPasswordAuthenticationException;
import org.jasig.cas.authentication.handler.support.AbstractUsernamePasswordAuthenticationHandler;
import org.jasig.cas.authentication.principal.UsernamePasswordCredentials;

/**
 * An authentication handler that uses the Yubico cloud validation platform to authenticate
 * one-time password tokens that are issued by a YubiKey device. To use YubiCloud you need a
 * client id and an API key which must be obtained from Yubico.
 *
 * <p>For more info, please visit <a href="http://yubico.github.io/yubico-java-client/">this link</a></p>
 *
 * @author Misagh Moayyed mmoayyed@unicon.net
 * @since 1.5
 */
public class YubiKeyAuthenticationHandler extends AbstractUsernamePasswordAuthenticationHandler {

    private YubiKeyAccountRegistry registry = new WhitelistYubiKeyAccountRegistry();

    private YubicoClient client;

    /**
     * Prepares the Yubico client with the received clientId and secretKey. By default,
     * all YubiKey accounts are allowed to authenticate.
     *
     * @param clientId
     * @param secretKey
     */
    public YubiKeyAuthenticationHandler(final Integer clientId, final String secretKey) {
        this.client = YubicoClient.getClient(clientId);
        this.client.setKey(secretKey);
    }

    /**
     * Prepares the Yubico client with the received clientId and secretKey. If you wish to
     * limit the usage of this handler only to a particular set of yubikey accounts for a special
     * group of users, you may provide an compliant implementation of {@link YubiKeyAccountRegistry}.
     * By default, all accounts are allowed.
     *
     * @param clientId
     * @param secretKey
     * @param registry
     */
    public YubiKeyAuthenticationHandler(final Integer clientId, final String secretKey, final YubiKeyAccountRegistry registry) {
        this(clientId, secretKey);
        this.registry = registry;
    }

    /**
     * {@inheritDoc}
     *
     * Attempts to authenticate the received credentials using the Yubico cloud validation platform.
     * In this implementation, the {@link org.jasig.cas.authentication.principal.UsernamePasswordCredentials#getUsername()}
     * is mapped to the <code>uid</code> which will be used by the plugged-in instance of the {@link YubiKeyAccountRegistry}
     * and the {@link org.jasig.cas.authentication.principal.UsernamePasswordCredentials#getPassword()} is the received
     * one-time password token issued sby the YubiKey device.
     *
     * @param usernamePasswordCredentials
     * @return true if the authentication succeeds. False, otherwise.
     * @throws AuthenticationException
     */
    @Override
    protected boolean authenticateUsernamePasswordInternal(final UsernamePasswordCredentials usernamePasswordCredentials) throws AuthenticationException {
        try {
            final String uid = usernamePasswordCredentials.getUsername();
            final String otp = usernamePasswordCredentials.getPassword();
            
            if (YubicoClient.isValidOTPFormat(otp)) {

                final String publicId = YubicoClient.getPublicId(otp);

                if (this.registry.isYubiKeyRegisteredFor(uid, publicId)) {
                    final YubicoResponse response = client.verify(otp);
                    log.debug("YubiKey response status {} at {}", response.getStatus(), response.getTimestamp());
                    return (response.getStatus() == YubicoResponseStatus.OK);

                } else {
                    log.debug("YubiKey public id [{}] is not registered for user [{}]", publicId, uid);
                }
            } else{
                log.debug("Invalid OTP format [{}]", otp);
            }
            return false;
        } catch (final Exception e) {
            throw new BadUsernameOrPasswordAuthenticationException(e);
        }

    }

    private static class WhitelistYubiKeyAccountRegistry implements YubiKeyAccountRegistry {

        @Override
        public boolean isYubiKeyRegisteredFor(final String uid, final String yubikeyPublicId) {
            return true;
        }
    }
}

@@ -1103,25 +1103,25 @@
 y = new 
-Whitelist
+AcceptAny
 YubiKeyA
@@ -1326,16 +1326,235 @@
 ticate.%0A
+     * %0A     * WARNING: THIS CONSTRUCTOR RESULTS IN AN EXAMPLE YubiKeyAuthenticationHandler%0A     * CONFIGURATION THAT CONSIDERS ALL Yubikeys VALID FOR ALL USERS.  YOU MUST NOT USE%0A     * THIS CONSTRUCTOR IN PRODUCTION.%0A
      *%0A 
@@ -1749,24 +1749,24 @@
 (clientId);%0A
-
         this
@@ -1788,24 +1788,188 @@
 secretKey);%0A
+        log.warning(%22YubiKeyAuthenticationHandler instantiated with example accept-any configuration.  %22 +%0A          %22THIS IS NOT OKAY IN PRODUCTION. NO. NO. NO.%22)%0A
     %7D%0A%0A    /
@@ -4531,24 +4531,303 @@
   %7D%0A%0A    %7D%0A%0A
+    /**%0A     * Example implementation of YubiKeyAccountRegistry that considers all yubikey Ids %0A     * registered for all users.%0A     * THIS IS OBVIOUSLY COMPLETELY UNACCEPTABLE FOR PRODUCTION USE AND YOU MUST USE%0A     * A REGISTRY THAT ACTUALLY REGISTERS IN PRODUCTION.%0A     */%0A
     private 
@@ -4843,17 +4843,17 @@
 ass 
-Whitelist
+AcceptAny
 Yubi

rename WhitelistYubiKeyAccountRegistry to AcceptAnyYubikeyAccountRegistry