/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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.apache.cassandra.transport.messages;

import java.util.function.BiFunction;

import org.apache.cassandra.auth.AuthEvents;
import org.apache.cassandra.auth.AuthenticatedUser;
import org.apache.cassandra.auth.IAuthenticator;
import org.apache.cassandra.exceptions.AuthenticationException;
import org.apache.cassandra.metrics.ClientMetrics;
import org.apache.cassandra.service.QueryState;
import org.apache.cassandra.transport.Connection;
import org.apache.cassandra.transport.Message.Response;
import org.apache.cassandra.transport.ServerConnection;

public final class AuthUtil
{

    /**
     * Attempts to authenticate a user on the current connection by negotiating with the underlying
     * {@link org.apache.cassandra.auth.IAuthenticator.SaslNegotiator} using the given state of the connection
     * (e.g. client certificates), the provided query state and the provided token.
     * <p>
     * If unsuccessful an {@link ErrorMessage} is returned and the authentication failure is recorded.
     * <p>
     * If negotiation is complete, a successful login is recorded and the given function is called with
     * <code>true</code> and the challenge returned from
     * {@link org.apache.cassandra.auth.IAuthenticator.SaslNegotiator#evaluateResponse(byte[])}.
     * <p>
     * If negotiation is incomplete, <code>false</code> and the challenge is passed to the given function.
     *
     * @param connection                      The connection to authenticate
     * @param queryState                      The current query state
     * @param token                           The token provided in an {@link AuthResponse} from the client (or empty
     *                                        if not handling an AuthResponse).
     * @param messageToSendBasedOnNegotiation Determines what response to return on based on whether sasl negotiation
     *                                        is complete (1st parameter) and the challenege token returned from the
     *                                        negotiator (2nd parameter).
     * @return the response to send back to the client.
     */
    static Response handleLogin(Connection connection, QueryState queryState, byte[] token,
                                BiFunction<Boolean, byte[], Response> messageToSendBasedOnNegotiation)
    {
        IAuthenticator.SaslNegotiator negotiator = ((ServerConnection) connection).getSaslNegotiator(queryState);
        try
        {
            // client-side timeout can disconnect while sitting in auth executor queue so (client default 12s)
            // discard if connection closed anyway
            if (!connection.channel().isActive())
            {
                throw new AuthenticationException("Auth check after connection closed");
            }
            byte[] challenge = negotiator.evaluateResponse(token);
            if (negotiator.isComplete())
            {
                AuthenticatedUser user = negotiator.getAuthenticatedUser();
                queryState.getClientState().login(user);
                ClientMetrics.instance.markAuthSuccess(user.getAuthenticationMode());
                AuthEvents.instance.notifyAuthSuccess(queryState);
                // authentication is complete, complete the authentication flow.
                return messageToSendBasedOnNegotiation.apply(true, challenge);
            }
            else
            {
                // authentication is incomplete, continue the authentication flow.
                return messageToSendBasedOnNegotiation.apply(false, challenge);
            }
        }
        catch (AuthenticationException e)
        {
            ClientMetrics.instance.markAuthFailure(negotiator.getAuthenticationMode());
            AuthEvents.instance.notifyAuthFailure(queryState, e);
            return ErrorMessage.fromTransportException(e);
        }
    }

    /**
     * The class must not be instantiated.
     */
    private AuthUtil()
    {
    }
}
