/*
 * The Alluxio Open Foundation licenses this work under the Apache License, version 2.0
 * (the "License"). You may not use this work except in compliance with the License, which is
 * available at www.apache.org/licenses/LICENSE-2.0
 *
 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
 * either express or implied, as more fully set forth in the License.
 *
 * See the NOTICE file distributed with this work for information regarding copyright ownership.
 */

package alluxio.security.authentication.plain;

import alluxio.security.authentication.AuthenticatedClientUser;
import alluxio.security.authentication.AuthenticationProvider;
import alluxio.security.authentication.ImpersonationAuthenticator;

import com.google.common.base.Preconditions;

import java.io.IOException;
import javax.security.auth.callback.Callback;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.callback.NameCallback;
import javax.security.auth.callback.PasswordCallback;
import javax.security.auth.callback.UnsupportedCallbackException;
import javax.security.sasl.AuthorizeCallback;

/**
 * A callback that is used by the SASL mechanisms to get further information to
 * complete the authentication. For example, a SASL mechanism might use this callback handler to
 * do verification operation.
 */
public final class PlainSaslServerCallbackHandler implements CallbackHandler {
  private final AuthenticationProvider mAuthenticationProvider;
  private final ImpersonationAuthenticator mImpersonationAuthenticator;

  /**
   * Constructs a new callback handler.
   *
   * @param authenticationProvider the authentication provider used
   * @param authenticator the impersonation authenticator
   */
  public PlainSaslServerCallbackHandler(AuthenticationProvider authenticationProvider,
      ImpersonationAuthenticator authenticator) {
    mAuthenticationProvider = Preconditions.checkNotNull(authenticationProvider,
        "authenticationProvider");
    mImpersonationAuthenticator = authenticator;
  }

  @Override
  public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {
    String username = null;
    String password = null;
    AuthorizeCallback ac = null;

    // Iterate over given callbacks for callback activation.
    // We need to do an initial pass since callbacks may depend on each other.
    for (Callback callback : callbacks) {
      if (callback instanceof NameCallback) {
        NameCallback nc = (NameCallback) callback;
        username = nc.getName();
      } else if (callback instanceof PasswordCallback) {
        PasswordCallback pc = (PasswordCallback) callback;
        password = new String(pc.getPassword());
      } else if (callback instanceof AuthorizeCallback) {
        ac = (AuthorizeCallback) callback;
      } else {
        throw new UnsupportedCallbackException(callback, "Unsupport callback");
      }
    }

    mAuthenticationProvider.authenticate(username, password);

    if (ac != null) {
      ac.setAuthorized(true);

      try {
        // getAuthorizedID() only works after the AuthorizeCallback is authorized
        mImpersonationAuthenticator.authenticate(username, ac.getAuthorizedID());
      } catch (Exception e) {
        ac.setAuthorized(false);
        throw e;
      }

      // After verification succeeds, a user with this authz id will be set to a Threadlocal.
      AuthenticatedClientUser.set(ac.getAuthorizedID());
    }
  }
}
