/*
 * Licensed to the University of California, Berkeley 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 tachyon.security.authentication;

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.sasl.AuthenticationException;
import javax.security.sasl.AuthorizeCallback;

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

import tachyon.Constants;
import tachyon.conf.TachyonConf;

/**
 * Tests the {@link tachyon.security.authentication.PlainSaslServer.PlainServerCallbackHandler}
 * class.
 */
public class PlainServerCallbackHandlerTest {
  private TachyonConf mConf;
  private CallbackHandler mPlainServerCBHandler;

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

  /**
   * Sets up the configuration and callback handler before running a test.
   *
   * @throws Exception thrown if the authentication provider cannot be set up
   */
  @Before
  public void before() throws Exception {
    mConf = new TachyonConf();
    mConf.set(Constants.SECURITY_AUTHENTICATION_CUSTOM_PROVIDER,
        NameMatchAuthenticationProvider.class.getName());
    mPlainServerCBHandler = new PlainSaslServer.PlainServerCallbackHandler(
        AuthenticationProvider.Factory.create(AuthType.CUSTOM, mConf));
  }

  /**
   * Tests that the authentication callbacks matches.
   *
   * @throws Exception thrown if the handler fails
   */
  @Test
  public void authenticateNameMatchTest() throws Exception {
    String authenticateId = "tachyon-1";
    NameCallback ncb = new NameCallback(" authentication id: ");
    ncb.setName(authenticateId);

    PasswordCallback pcb = new PasswordCallback(" password: ", false);
    pcb.setPassword("anonymous".toCharArray());

    Callback[] callbacks = new Callback[]{ncb, pcb,
        new AuthorizeCallback(authenticateId, authenticateId)};
    mPlainServerCBHandler.handle(callbacks);
  }

  /**
   * Tests that the authentication callbacks do not match.
   *
   * @throws Exception thrown if the handler fails
   */
  @Test
  public void authenticateNameNotMatchTest() throws Exception {
    mThrown.expect(AuthenticationException.class);
    mThrown.expectMessage("Only allow the user starting with tachyon");

    String authenticateId = "not-tachyon-1";
    NameCallback ncb = new NameCallback(" authentication id: ");
    ncb.setName(authenticateId);

    PasswordCallback pcb = new PasswordCallback(" password: ", false);
    pcb.setPassword("anonymous".toCharArray());

    Callback[] callbacks = new Callback[]{ncb, pcb,
        new AuthorizeCallback(authenticateId, authenticateId)};
    mPlainServerCBHandler.handle(callbacks);
  }

  /**
   * An {@link AuthenticationProvider} that only allows users starting with tachyon.
   */
  public static class NameMatchAuthenticationProvider implements AuthenticationProvider {
    @Override
    public void authenticate(String user, String password) throws AuthenticationException {
      if (!user.matches("^tachyon.*")) {
        throw new AuthenticationException("Only allow the user starting with tachyon");
      }
    }
  }
}

@@ -2617,33 +2617,32 @@
 etPassword(%22
-anonymous
+password
 %22.toCharArra
@@ -3377,17 +3377,700 @@
 rd(%22
-anonymous
+password%22.toCharArray());%0A%0A    Callback%5B%5D callbacks = new Callback%5B%5D%7Bncb, pcb,%0A        new AuthorizeCallback(authenticateId, authenticateId)%7D;%0A    mPlainServerCBHandler.handle(callbacks);%0A  %7D%0A%0A  /**%0A   * Tests that the incorrect password should fail the authentication.%0A   */%0A  @Test%0A  public void authenticateCorrectPasswordTest() throws Exception %7B%0A    mThrown.expect(AuthenticationException.class);%0A    mThrown.expectMessage(%22Wrong password%22);%0A%0A    String authenticateId = %22tachyon-1%22;%0A    NameCallback ncb = new NameCallback(%22 authentication id: %22);%0A    ncb.setName(authenticateId);%0A%0A    PasswordCallback pcb = new PasswordCallback(%22 password: %22, false);%0A    pcb.setPassword(%22not-password
 %22.to
@@ -4339,17 +4339,52 @@
  tachyon
-.
+, password%0A   * should be %22password%22
 %0A   */%0A 
@@ -4619,16 +4619,16 @@
 .*%22)) %7B%0A
-
         
@@ -4715,16 +4715,129 @@
       %7D%0A
+      if (!password.matches(%22%5Epassword%22)) %7B%0A        throw new AuthenticationException(%22Wrong password%22);%0A      %7D%0A
     %7D%0A  

Add password check in PlainServerCallbackHandlerTest.