alluxio.security.authentication.AuthenticationUtilsTest.java Source code

Java tutorial

Introduction

Here is the source code for alluxio.security.authentication.AuthenticationUtilsTest.java

Source

/*
 * 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 alluxio.Configuration;
import alluxio.Constants;
import alluxio.util.network.NetworkAddressUtils;

import org.apache.thrift.protocol.TBinaryProtocol;
import org.apache.thrift.server.TThreadPoolServer;
import org.apache.thrift.transport.TServerSocket;
import org.apache.thrift.transport.TSocket;
import org.apache.thrift.transport.TTransport;
import org.apache.thrift.transport.TTransportException;
import org.apache.thrift.transport.TTransportFactory;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;

import java.net.InetSocketAddress;

import javax.security.sasl.AuthenticationException;
import javax.security.sasl.SaslException;

/**
 * Unit test for methods of {@link AuthenticationUtils}
 *
 * In order to test methods that return kinds of TTransport for connection in different mode, we
 * build Thrift servers and clients with specific TTransport, and let them connect.
 */
public class AuthenticationUtilsTest {

    private TThreadPoolServer mServer;
    private Configuration mConfiguration;
    private InetSocketAddress mServerAddress;
    private TServerSocket mServerTSocket;
    private TSocket mClientTSocket;

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

    /**
     * Sets up the server before running a test.
     *
     * @throws Exception thrown when the {@link TServerSocket} cannot be constructed
     */
    @Before
    public void before() throws Exception {
        mConfiguration = new Configuration();
        // Use port 0 to assign each test case an available port (possibly different)
        String localhost = NetworkAddressUtils.getLocalHostName(new Configuration());
        mServerTSocket = new TServerSocket(new InetSocketAddress(localhost, 0));
        int port = NetworkAddressUtils.getThriftPort(mServerTSocket);
        mServerAddress = new InetSocketAddress(localhost, port);
        mClientTSocket = AuthenticationUtils.createTSocket(mServerAddress,
                mConfiguration.getInt(Constants.SECURITY_AUTHENTICATION_SOCKET_TIMEOUT_MS));
    }

    /**
     * In NOSASL mode, the TTransport used should be the same as Alluxio original code.
     *
     * @throws Exception thrown when the server cannot be started
     */
    @Test
    public void nosaslAuthenticationTest() throws Exception {
        mConfiguration.set(Constants.SECURITY_AUTHENTICATION_TYPE, "NOSASL");

        // start server
        startServerThread();

        // create client and connect to server
        TTransport client = AuthenticationUtils.getClientTransport(mConfiguration, mServerAddress);
        client.open();
        Assert.assertTrue(client.isOpen());

        // clean up
        client.close();
        mServer.stop();
    }

    /**
     * In SIMPLE mode, the TTransport mechanism is PLAIN. When server authenticate the connected
     * client user, it use {@link SimpleAuthenticationProvider}.
     *
     * @throws Exception thrown when the server cannot be started or the retrieval of the plain client
     *                   transport fails
     */
    @Test
    public void simpleAuthenticationTest() throws Exception {
        mConfiguration.set(Constants.SECURITY_AUTHENTICATION_TYPE, "SIMPLE");

        // start server
        startServerThread();

        // when connecting, authentication happens. It is a no-op in Simple mode.
        TTransport client = PlainSaslUtils.getPlainClientTransport("anyone", "whatever", mClientTSocket);
        client.open();
        Assert.assertTrue(client.isOpen());

        // clean up
        client.close();
        mServer.stop();
    }

    /**
     * In SIMPLE mode, if client's username is null, an exception should be thrown in client side.
     *
     * @throws Exception thrown when the retrieval of the plain client transport fails
     */
    @Test
    public void simpleAuthenticationNullUserTest() throws Exception {
        mConfiguration.set(Constants.SECURITY_AUTHENTICATION_TYPE, "SIMPLE");

        // check case that user is null
        mThrown.expect(SaslException.class);
        mThrown.expectMessage("PLAIN: authorization ID and password must be specified");
        PlainSaslUtils.getPlainClientTransport(null, "whatever", mClientTSocket);
    }

    /**
     * In SIMPLE mode, if client's password is null, an exception should be thrown in client side.
     *
     * @throws Exception thrown when the retrieval of the plain client transport fails
     */
    @Test
    public void simpleAuthenticationNullPasswordTest() throws Exception {
        mConfiguration.set(Constants.SECURITY_AUTHENTICATION_TYPE, "SIMPLE");

        // check case that password is null
        mThrown.expect(SaslException.class);
        mThrown.expectMessage("PLAIN: authorization ID and password must be specified");
        PlainSaslUtils.getPlainClientTransport("anyone", null, mClientTSocket);
    }

    /**
     * In SIMPLE mode, if client's username is empty, an exception should be thrown in server side.
     *
     * @throws Exception thrown when the server cannot be started or the retrieval of the plain client
     *                   transport fails
     */
    @Test
    public void simpleAuthenticationEmptyUserTest() throws Exception {
        mConfiguration.set(Constants.SECURITY_AUTHENTICATION_TYPE, "SIMPLE");

        // start server
        startServerThread();

        // check case that user is empty
        mThrown.expect(TTransportException.class);
        mThrown.expectMessage(
                "Peer indicated failure: Plain authentication failed: No authentication" + " identity provided");
        TTransport client = PlainSaslUtils.getPlainClientTransport("", "whatever", mClientTSocket);
        try {
            client.open();
        } finally {
            mServer.stop();
        }
    }

    /**
     * In SIMPLE mode, if client's password is empty, an exception should be thrown in server side.
     * Although password is actually not used and we do not really authenticate the user in SIMPLE
     * mode, we need the Plain SASL server has ability to check empty password.
     *
     * @throws Exception thrown when the server cannot be started or the retrieval of the plain client
     *                   transport fails
     */
    @Test
    public void simpleAuthenticationEmptyPasswordTest() throws Exception {
        mConfiguration.set(Constants.SECURITY_AUTHENTICATION_TYPE, "SIMPLE");

        // start server
        startServerThread();

        // check case that password is empty
        mThrown.expect(TTransportException.class);
        mThrown.expectMessage("Peer indicated failure: Plain authentication failed: No password " + "provided");
        TTransport client = PlainSaslUtils.getPlainClientTransport("anyone", "", mClientTSocket);
        try {
            client.open();
        } finally {
            mServer.stop();
        }
    }

    /**
     * In CUSTOM mode, the TTransport mechanism is PLAIN. When server authenticate the connected
     * client user, it use configured AuthenticationProvider. If the username:password pair matches, a
     * connection should be built.
     *
     * @throws Exception thrown when the server cannot be started or the retrieval of the plain client
     *                   transport fails
     */
    @Test
    public void customAuthenticationExactNamePasswordMatchTest() throws Exception {
        mConfiguration.set(Constants.SECURITY_AUTHENTICATION_TYPE, "CUSTOM");
        mConfiguration.set(Constants.SECURITY_AUTHENTICATION_CUSTOM_PROVIDER,
                ExactlyMatchAuthenticationProvider.class.getName());

        // start server
        startServerThread();

        // when connecting, authentication happens. User's name:pwd pair matches and auth pass.
        TTransport client = PlainSaslUtils.getPlainClientTransport("alluxio", "correct-password", mClientTSocket);
        client.open();
        Assert.assertTrue(client.isOpen());

        // clean up
        client.close();
        mServer.stop();
    }

    /**
     * In CUSTOM mode, If the username:password pair does not match based on the configured
     * AuthenticationProvider, an exception should be thrown in server side.
     *
     * @throws Exception thrown when the server cannot be started or the retrieval of the plain client
     *                   transport fails
     */
    @Test
    public void customAuthenticationExactNamePasswordNotMatchTest() throws Exception {
        mConfiguration.set(Constants.SECURITY_AUTHENTICATION_TYPE, "CUSTOM");
        mConfiguration.set(Constants.SECURITY_AUTHENTICATION_CUSTOM_PROVIDER,
                ExactlyMatchAuthenticationProvider.class.getName());

        // start server
        startServerThread();

        // User with wrong password can not pass auth, and throw exception.
        TTransport wrongClient = PlainSaslUtils.getPlainClientTransport("alluxio", "wrong-password",
                mClientTSocket);
        mThrown.expect(TTransportException.class);
        mThrown.expectMessage(
                "Peer indicated failure: Plain authentication failed: " + "User authentication fails");
        try {
            wrongClient.open();
        } finally {
            mServer.stop();
        }
    }

    /**
     * In CUSTOM mode, if client's username is null, an exception should be thrown in client side.
     *
     * @throws Exception thrown when the retrieval of the plain client transport fails
     */
    @Test
    public void customAuthenticationNullUserTest() throws Exception {
        mConfiguration.set(Constants.SECURITY_AUTHENTICATION_TYPE, "CUSTOM");

        // check case that user is null
        mThrown.expect(SaslException.class);
        mThrown.expectMessage("PLAIN: authorization ID and password must be specified");
        PlainSaslUtils.getPlainClientTransport(null, "correct-password", mClientTSocket);
    }

    /**
     * In CUSTOM mode, if client's password is null, an exception should be thrown in client side.
     *
     * @throws Exception thrown when the retrieval of the plain client transport fails
     */
    @Test
    public void customAuthenticationNullPasswordTest() throws Exception {
        mConfiguration.set(Constants.SECURITY_AUTHENTICATION_TYPE, "CUSTOM");

        // check case that password is null
        mThrown.expect(SaslException.class);
        mThrown.expectMessage("PLAIN: authorization ID and password must be specified");
        PlainSaslUtils.getPlainClientTransport("alluxio", null, mClientTSocket);
    }

    /**
     * In CUSTOM mode, if client's username is empty, an exception should be thrown in server side.
     *
     * @throws Exception thrown when the server cannot be started or the retrieval of the plain client
     *                   transport fails
     */
    @Test
    public void customAuthenticationEmptyUserTest() throws Exception {
        mConfiguration.set(Constants.SECURITY_AUTHENTICATION_TYPE, "CUSTOM");
        mConfiguration.set(Constants.SECURITY_AUTHENTICATION_CUSTOM_PROVIDER,
                ExactlyMatchAuthenticationProvider.class.getName());

        // start server
        startServerThread();

        // check case that user is empty
        mThrown.expect(TTransportException.class);
        mThrown.expectMessage(
                "Peer indicated failure: Plain authentication failed: No authentication" + " identity provided");
        TTransport client = PlainSaslUtils.getPlainClientTransport("", "correct-password", mClientTSocket);
        try {
            client.open();
        } finally {
            mServer.stop();
        }
    }

    /**
     * In CUSTOM mode, if client's password is empty, an exception should be thrown in server side.
     *
     * @throws Exception thrown when the server cannot be started or the retrieval of the plain client
     *                   transport fails
     */
    @Test
    public void customAuthenticationEmptyPasswordTest() throws Exception {
        mConfiguration.set(Constants.SECURITY_AUTHENTICATION_TYPE, "CUSTOM");
        mConfiguration.set(Constants.SECURITY_AUTHENTICATION_CUSTOM_PROVIDER,
                ExactlyMatchAuthenticationProvider.class.getName());

        // start server
        startServerThread();

        // check case that password is empty
        mThrown.expect(TTransportException.class);
        mThrown.expectMessage("Peer indicated failure: Plain authentication failed: No password " + "provided");
        TTransport client = PlainSaslUtils.getPlainClientTransport("alluxio", "", mClientTSocket);
        try {
            client.open();
        } finally {
            mServer.stop();
        }
    }

    /**
     * TODO(dong): In KERBEROS mode, ...
     * Tests that an exception is thrown when trying to use KERBEROS mode.
     *
     * @throws Exception thrown when the server cannot be started
     */
    @Test
    public void kerberosAuthenticationTest() throws Exception {
        mConfiguration.set(Constants.SECURITY_AUTHENTICATION_TYPE, "KERBEROS");

        // throw unsupported exception currently
        mThrown.expect(UnsupportedOperationException.class);
        mThrown.expectMessage("Kerberos is not supported currently.");
        startServerThread();
    }

    private void startServerThread() throws Exception {
        // create args and use them to build a Thrift TServer
        TTransportFactory tTransportFactory = AuthenticationUtils.getServerTransportFactory(mConfiguration);

        mServer = new TThreadPoolServer(new TThreadPoolServer.Args(mServerTSocket).maxWorkerThreads(2)
                .minWorkerThreads(1).processor(null).transportFactory(tTransportFactory)
                .protocolFactory(new TBinaryProtocol.Factory(true, true)));

        // start the server in a new thread
        Thread serverThread = new Thread(new Runnable() {
            @Override
            public void run() {
                mServer.serve();
            }
        });

        serverThread.start();

        // ensure server is running, and break if it does not start serving in 2 seconds.
        int count = 40;
        while (!mServer.isServing() && serverThread.isAlive()) {
            if (count <= 0) {
                throw new RuntimeException("TThreadPoolServer does not start serving");
            }
            Thread.sleep(50);
            count--;
        }
    }

    /**
     * This customized authentication provider is used in CUSTOM mode. It authenticate the user by
     * verifying the specific username:password pair.
     */
    public static class ExactlyMatchAuthenticationProvider implements AuthenticationProvider {
        @Override
        public void authenticate(String user, String password) throws AuthenticationException {
            if (!user.equals("alluxio") || !password.equals("correct-password")) {
                throw new AuthenticationException("User authentication fails");
            }
        }
    }

}