tachyon.security.authentication.AuthenticationUtilsTest.java Source code

Java tutorial

Introduction

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

Source

/*
 * 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 java.net.InetSocketAddress;

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

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 tachyon.Constants;
import tachyon.conf.TachyonConf;
import tachyon.util.network.NetworkAddressUtils;

/**
 * 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 TachyonConf mTachyonConf;
    private InetSocketAddress mServerAddress;
    private TServerSocket mServerTSocket;
    private TSocket mClientTSocket;

    @Rule
    public ExpectedException mThrown = ExpectedException.none();

    @Before
    public void before() throws Exception {
        mTachyonConf = new TachyonConf();
        // Use port 0 to assign each test case an available port (possibly different)
        mServerTSocket = new TServerSocket(new InetSocketAddress("localhost", 0));
        int port = NetworkAddressUtils.getThriftPort(mServerTSocket);
        mServerAddress = new InetSocketAddress("localhost", port);
        mClientTSocket = AuthenticationUtils.createTSocket(mServerAddress,
                mTachyonConf.getInt(Constants.SECURITY_AUTHENTICATION_SOCKET_TIMEOUT_MS));
    }

    /**
     * In NOSASL mode, the TTransport used should be the same as Tachyon original code.
     */
    @Test
    public void nosaslAuthenticationTest() throws Exception {
        mTachyonConf.set(Constants.SECURITY_AUTHENTICATION_TYPE, "NOSASL");

        // start server
        startServerThread();

        // create client and connect to server
        TTransport client = AuthenticationUtils.getClientTransport(mTachyonConf, 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 tachyon.security.authentication.SimpleAuthenticationProviderImpl}.
     */
    @Test
    public void simpleAuthenticationTest() throws Exception {
        mTachyonConf.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.
     */
    @Test
    public void simpleAuthenticationNullUserTest() throws Exception {
        mTachyonConf.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.
     */
    @Test
    public void simpleAuthenticationNullPasswordTest() throws Exception {
        mTachyonConf.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.
     */
    @Test
    public void simpleAuthenticationEmptyUserTest() throws Exception {
        mTachyonConf.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.
     */
    @Test
    public void simpleAuthenticationEmptyPasswordTest() throws Exception {
        mTachyonConf.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.
     */
    @Test
    public void customAuthenticationExactNamePasswordMatchTest() throws Exception {
        mTachyonConf.set(Constants.SECURITY_AUTHENTICATION_TYPE, "CUSTOM");
        mTachyonConf.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("tachyon", "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.
     */
    @Test
    public void customAuthenticationExactNamePasswordNotMatchTest() throws Exception {
        mTachyonConf.set(Constants.SECURITY_AUTHENTICATION_TYPE, "CUSTOM");
        mTachyonConf.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("tachyon", "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.
     */
    @Test
    public void customAuthenticationNullUserTest() throws Exception {
        mTachyonConf.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.
     */
    @Test
    public void customAuthenticationNullPasswordTest() throws Exception {
        mTachyonConf.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("tachyon", null, mClientTSocket);
    }

    /**
     * In CUSTOM mode, if client's username is empty, an exception should be thrown in server side.
     */
    @Test
    public void customAuthenticationEmptyUserTest() throws Exception {
        mTachyonConf.set(Constants.SECURITY_AUTHENTICATION_TYPE, "CUSTOM");
        mTachyonConf.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.
     */
    @Test
    public void customAuthenticationEmptyPasswordTest() throws Exception {
        mTachyonConf.set(Constants.SECURITY_AUTHENTICATION_TYPE, "CUSTOM");
        mTachyonConf.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("tachyon", "", mClientTSocket);
        try {
            client.open();
        } finally {
            mServer.stop();
        }
    }

    /**
     * TODO(dong): In KERBEROS mode, ...
     */
    @Test
    public void kerberosAuthenticationTest() throws Exception {
        mTachyonConf.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(mTachyonConf);

        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("tachyon") || !password.equals("correct-password")) {
                throw new AuthenticationException("User authentication fails");
            }
        }
    }

}