# Copyright 2013-2015 DataStax, Inc.
#
# Licensed 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.

import logging
import time

from cassandra.cluster import Cluster, NoHostAvailable
from cassandra.auth import PlainTextAuthProvider, SASLClient, SaslAuthProvider

from tests.integration import use_singledc, get_cluster, remove_cluster, PROTOCOL_VERSION
from tests.integration.util import assert_quiescent_pool_state

try:
    import unittest2 as unittest
except ImportError:
    import unittest

log = logging.getLogger(__name__)


def setup_module():
    use_singledc(start=False)
    ccm_cluster = get_cluster()
    ccm_cluster.stop()
    config_options = {'authenticator': 'PasswordAuthenticator',
                      'authorizer': 'CassandraAuthorizer'}
    ccm_cluster.set_configuration_options(config_options)
    log.debug("Starting ccm test cluster with %s", config_options)
    ccm_cluster.start(wait_for_binary_proto=True, wait_other_notice=True)
    # there seems to be some race, with some versions of C* taking longer to 
    # get the auth (and default user) setup. Sleep here to give it a chance
    time.sleep(10)


def teardown_module():
    remove_cluster()  # this test messes with config


class AuthenticationTests(unittest.TestCase):
    """
    Tests to cover basic authentication functionality
    """

    def get_authentication_provider(self, username, password):
        """
        Return correct authentication provider based on protocol version.
        There is a difference in the semantics of authentication provider argument with protocol versions 1 and 2
        For protocol version 2 and higher it should be a PlainTextAuthProvider object.
        For protocol version 1 it should be a function taking hostname as an argument and returning a dictionary
        containing username and password.
        :param username: authentication username
        :param password: authentication password
        :return: authentication object suitable for Cluster.connect()
        """
        if PROTOCOL_VERSION < 2:
            return lambda hostname: dict(username=username, password=password)
        else:
            return PlainTextAuthProvider(username=username, password=password)

    def cluster_as(self, usr, pwd):
        return Cluster(protocol_version=PROTOCOL_VERSION,
                       idle_heartbeat_interval=0,
                       auth_provider=self.get_authentication_provider(username=usr, password=pwd))

    def test_auth_connect(self):
        user = 'u'
        passwd = 'password'

        root_session = self.cluster_as('cassandra', 'cassandra').connect()
        root_session.execute('CREATE USER %s WITH PASSWORD %s', (user, passwd))

        try:
            cluster = self.cluster_as(user, passwd)
            session = cluster.connect()
            try:
                self.assertTrue(session.execute('SELECT release_version FROM system.local'))
                assert_quiescent_pool_state(self, cluster)
                for pool in session.get_pools():
                    connection, _ = pool.borrow_connection(timeout=0)
                    self.assertEqual(connection.authenticator.server_authenticator_class, 'org.apache.cassandra.auth.PasswordAuthenticator')
                    pool.return_connection(connection)
            finally:
                cluster.shutdown()
        finally:
            root_session.execute('DROP USER %s', user)
            assert_quiescent_pool_state(self, root_session.cluster)
            root_session.cluster.shutdown()

    def test_connect_wrong_pwd(self):
        cluster = self.cluster_as('cassandra', 'wrong_pass')
        self.assertRaisesRegexp(NoHostAvailable,
                                '.*AuthenticationFailed.*Bad credentials.*Username and/or '
                                'password are incorrect.*',
                                cluster.connect)
        assert_quiescent_pool_state(self, cluster)
        cluster.shutdown()

    def test_connect_wrong_username(self):
        cluster = self.cluster_as('wrong_user', 'cassandra')
        self.assertRaisesRegexp(NoHostAvailable,
                                '.*AuthenticationFailed.*Bad credentials.*Username and/or '
                                'password are incorrect.*',
                                cluster.connect)
        assert_quiescent_pool_state(self, cluster)
        cluster.shutdown()

    def test_connect_empty_pwd(self):
        cluster = self.cluster_as('Cassandra', '')
        self.assertRaisesRegexp(NoHostAvailable,
                                '.*AuthenticationFailed.*Bad credentials.*Username and/or '
                                'password are incorrect.*',
                                cluster.connect)
        assert_quiescent_pool_state(self, cluster)
        cluster.shutdown()

    def test_connect_no_auth_provider(self):
        cluster = Cluster(protocol_version=PROTOCOL_VERSION)
        self.assertRaisesRegexp(NoHostAvailable,
                                '.*AuthenticationFailed.*Remote end requires authentication.*',
                                cluster.connect)
        assert_quiescent_pool_state(self, cluster)
        cluster.shutdown()


class SaslAuthenticatorTests(AuthenticationTests):
    """
    Test SaslAuthProvider as PlainText
    """

    def setUp(self):
        if PROTOCOL_VERSION < 2:
            raise unittest.SkipTest('Sasl authentication not available for protocol v1')
        if SASLClient is None:
            raise unittest.SkipTest('pure-sasl is not installed')

    def get_authentication_provider(self, username, password):
        sasl_kwargs = {'service': 'cassandra',
                       'mechanism': 'PLAIN',
                       'qops': ['auth'],
                       'username': username,
                       'password': password}
        return SaslAuthProvider(**sasl_kwargs)

    # these could equally be unit tests
    def test_host_passthrough(self):
        sasl_kwargs = {'service': 'cassandra',
                       'mechanism': 'PLAIN'}
        provider = SaslAuthProvider(**sasl_kwargs)
        host = 'thehostname'
        authenticator = provider.new_authenticator(host)
        self.assertEqual(authenticator.sasl.host, host)

    def test_host_rejected(self):
        sasl_kwargs = {'host': 'something'}
        self.assertRaises(ValueError, SaslAuthProvider, **sasl_kwargs)

@@ -734,16 +734,53 @@
 Provider
+%0Afrom dse.auth import DSEAuthProvider
 %0A%0Afrom t
@@ -6882,20 +6882,483 @@
 der, **sasl_kwargs)%0A
+%0A%0Aclass DSEAuthenticatorTests(AuthenticationTests):%0A    %22%22%22%0A    Test SaslAuthProvider as PlainText%0A    %22%22%22%0A%0A    def setUp(self):%0A        if PROTOCOL_VERSION %3C 2:%0A            raise unittest.SkipTest('Sasl authentication not available for protocol v1')%0A        if SASLClient is None:%0A            raise unittest.SkipTest('pure-sasl is not installed')%0A%0A    def get_authentication_provider(self, username, password):%0A        return DSEAuthProvider(username, password)%0A

Test for DSE Authenticator