/*
 * 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;

import static org.junit.Assert.assertEquals;

import alluxio.security.authentication.plain.PlainSaslClientCallbackHandler;

import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;

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.RealmCallback;

/**
 * Tests the {@link PlainSaslClientCallbackHandler}
 * class.
 */
public final class PlainSaslClientCallbackHandlerTest {

  /**
   * The exception expected to be thrown.
   */
  @Rule
  public ExpectedException mThrown = ExpectedException.none();

  /**
   * Tests that the callback is handled correctly.
   */
  @Test
  public void clientCallbackHandler() throws Exception {
    Callback[] callbacks = new Callback[2];
    callbacks[0] = new NameCallback("Username:");
    callbacks[1] = new PasswordCallback("Password:", true);

    String user = "alluxio-user-1";
    String password = "alluxio-user-1-password";

    CallbackHandler clientCBHandler =
        new PlainSaslClientCallbackHandler(user, password);
    clientCBHandler.handle(callbacks);

    validateCallbacks(user, password, callbacks);
  }

  /**
   * Tests that an exception is thrown in case an unsupported callback is used.
   */
  @Test
  public void unsupportCallback() throws Exception {
    mThrown.expect(UnsupportedCallbackException.class);
    mThrown.expectMessage(RealmCallback.class + " is unsupported.");

    Callback[] callbacks = new Callback[3];
    callbacks[0] = new NameCallback("Username:");
    callbacks[1] = new PasswordCallback("Password:", true);
    callbacks[2] = new RealmCallback("Realm:");

    String user = "alluxio-user-2";
    String password = "alluxio-user-2-password";
    CallbackHandler clientCBHandler =
        new PlainSaslClientCallbackHandler(user, password);
    clientCBHandler.handle(callbacks);
  }

  /**
   * Tests that the callback can handle a non-existent user.
   */
  @Test
  public void nullNameCallback() throws Exception {
    Callback[] callbacks = new Callback[2];
    callbacks[0] = new NameCallback("Username:");
    callbacks[1] = new PasswordCallback("Password:", true);

    String user = null;
    String password = "alluxio-user-3-password";
    CallbackHandler clientCBHandler =
        new PlainSaslClientCallbackHandler(user, password);
    clientCBHandler.handle(callbacks);

    validateCallbacks(user, password, callbacks);
  }

  /**
   * Tests that the callback can handle a non-existent password.
   */
  @Test
  public void nullPasswordCallback() throws Exception {
    Callback[] callbacks = new Callback[2];
    callbacks[0] = new NameCallback("Username:");
    callbacks[1] = new PasswordCallback("Password:", true);

    String user = "alluxio-user-4";
    String password = null;
    CallbackHandler clientCBHandler =
        new PlainSaslClientCallbackHandler(user, password);
    clientCBHandler.handle(callbacks);

    validateCallbacks(user, password, callbacks);
  }

  /**
   * Tests that an exception is thrown when a callback is {@code null}.
   */
  @Test
  public void nullCallback() throws Exception {
    mThrown.expect(UnsupportedCallbackException.class);
    mThrown.expectMessage(null + " is unsupported.");

    Callback[] callbacks = new Callback[3];
    callbacks[0] = new NameCallback("Username:");
    callbacks[1] = new PasswordCallback("Password:", true);
    callbacks[2] = null;

    String user = "alluxio-user-5";
    String password = "alluxio-user-5-password";
    CallbackHandler clientCBHandler =
        new PlainSaslClientCallbackHandler(user, password);
    clientCBHandler.handle(callbacks);
  }

  private void validateCallbacks(String user, String passwd, Callback[] callbacks)
      throws IOException, UnsupportedCallbackException {
    for (Callback cb : callbacks) {
      if (cb instanceof NameCallback) {
        assertEquals(user, ((NameCallback) cb).getName());
      } else if (cb instanceof PasswordCallback) {
        char[] passwordChar = ((PasswordCallback) cb).getPassword();
        assertEquals(passwd, passwordChar == null ? null : String.copyValueOf(passwordChar));
      }
    }
  }
}
