/*
 * JBoss, Home of Professional Open Source.
 * Copyright 2015 Red Hat, Inc., and individual contributors
 * as indicated by the @author tags.
 *
 * 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 org.wildfly.security.sasl.otp;

import java.util.Map;
import java.util.function.BiPredicate;
import java.util.function.Predicate;

import javax.security.auth.callback.ChoiceCallback;
import javax.security.sasl.Sasl;

import org.wildfly.security.auth.callback.ExtendedChoiceCallback;
import org.wildfly.security.password.spec.OneTimePasswordAlgorithmSpec;
import org.wildfly.security.sasl.WildFlySasl;

/**
 * Constants for the OTP SASL mechanism.
 *
 * @author <a href="mailto:fjuma@redhat.com">Farah Juma</a>
 */
public final class OTP {

    public static final String OTP_PREFIX = "otp-";
    public static final String EXT = "ext";

    // Supported algorithms
    public static final String MD5 = "md5";
    public static final String SHA1 = "sha1";
    public static final String SHA256 = "sha256";
    public static final String SHA384 = "sha384";
    public static final String SHA512 = "sha512";

    // Response types
    public static final String HEX_RESPONSE = "hex";
    public static final String WORD_RESPONSE = "word";
    public static final String INIT_HEX_RESPONSE = "init-hex";
    public static final String INIT_WORD_RESPONSE = "init-word";

    /**
     * Pass phrase format.
     */
    public static final String PASS_PHRASE = "pass-phrase";

    /**
     * Direct OTP format (either hexadecimal or multi-word OTP).
     */
    public static final String DIRECT_OTP = "direct-otp";

    // OTP parameters
    public static final int MAX_AUTHORIZATION_ID_LENGTH = 255;
    public static final int MAX_AUTHENTICATION_ID_LENGTH = 255;
    public static final int MIN_SEED_LENGTH = 1;
    public static final int MAX_SEED_LENGTH = 16;
    public static final int DEFAULT_SEED_LENGTH = 10;
    public static final int MIN_PASS_PHRASE_LENGTH = 10;
    public static final int MAX_PASS_PHRASE_LENGTH = 63;
    public static final int MIN_SEQUENCE_NUMBER = 10;
    public static final int DEFAULT_SEQUENCE_NUMBER = 499;
    public static final char DICTIONARY_DELIMITER = ' ';
    public static final int DICTIONARY_SIZE = 2048;

    // OTP prompts
    public static final String RESPONSE_TYPE_PROMPT = "One-time password response type";
    public static final String PASSWORD_FORMAT_TYPE_PROMPT = "One-time password format type";
    public static final String NEW_PASSWORD_FORMAT_TYPE_PROMPT = "New one-time password format type";
    public static final String PASSWORD_PROMPT = "Pass phrase or one-time password";
    public static final String NEW_PASSWORD_PROMPT = "New pass phrase or one-time password";

    /**
     * A predicate which is true when the given callback type and prompt match the OTP response type choice callback.
     */
    public static final BiPredicate<Class<? extends ChoiceCallback>, String> MATCH_RESPONSE_CHOICE =
            (choiceCallbackType, prompt) -> ExtendedChoiceCallback.class.isAssignableFrom(choiceCallbackType) && RESPONSE_TYPE_PROMPT.equals(prompt);

    /**
     * A predicate which is true when the given callback type and prompt match the OTP password format type choice callback.
     */
    public static final BiPredicate<Class<? extends ChoiceCallback>, String> MATCH_PASSWORD_FORMAT_CHOICE =
            (choiceCallbackType, prompt) -> ExtendedChoiceCallback.class.isAssignableFrom(choiceCallbackType) && PASSWORD_FORMAT_TYPE_PROMPT.equals(prompt);

    /**
     * A predicate which is true when the given callback type and prompt match the OTP new password format type choice callback.
     */
    public static final BiPredicate<Class<? extends ChoiceCallback>, String> MATCH_NEW_PASSWORD_FORMAT_CHOICE =
            (choiceCallbackType, prompt) -> ExtendedChoiceCallback.class.isAssignableFrom(choiceCallbackType) && NEW_PASSWORD_FORMAT_TYPE_PROMPT.equals(prompt);

    /**
     * A predicate which is true when the given callback type and prompt match the OTP password callback.
     */
    public static final Predicate<String> MATCH_PASSWORD = (prompt) -> PASSWORD_PROMPT.equals(prompt);

    /**
     * A predicate which is true when the given callback type and prompt match the OTP new password callback.
     */
    public static final Predicate<String> MATCH_NEW_PASSWORD = (prompt) -> NEW_PASSWORD_PROMPT.equals(prompt);

    /**
     * Get the parameter specification for a one-time password generated using the given algorithm, seed, and sequence number.
     *
     * @param algorithm the algorithm
     * @param seed the seed
     * @param sequenceNumber the sequence number
     * @return the parameter specification for a one-time password generated using the given algorithm, seed, and sequence number
     */
    public static OneTimePasswordAlgorithmSpec getOTPParameterSpec(final String algorithm, final String seed, final int sequenceNumber) {
        return new OneTimePasswordAlgorithmSpec(algorithm, seed, sequenceNumber);
    }

    static boolean isMatched(final Map<String, ?> props, final boolean query) {
        if (props == null) {
            return true;
        }
        if ("true".equals(props.get(WildFlySasl.MECHANISM_QUERY_ALL)) && query) {
            return true;
        }
        if ("true".equals(props.get(Sasl.POLICY_NOACTIVE))) {
            return false;
        }
        if ("true".equals(props.get(Sasl.POLICY_PASS_CREDENTIALS))) {
            return false;
        }
        if ("true".equals(props.get(Sasl.POLICY_NODICTIONARY))) {
            return false;
        }
        return true;
    }
}
