#    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 datetime
from unittest import mock

from oslo_utils import timeutils

from keystoneclient import access
from keystoneclient import httpclient
from keystoneclient.tests.unit import utils
from keystoneclient.tests.unit.v2_0 import client_fixtures
from keystoneclient import utils as client_utils

try:
    import keyring  # noqa
    import pickle  # noqa
except ImportError:
    keyring = None


PROJECT_SCOPED_TOKEN = client_fixtures.project_scoped_token()

# These mirror values from PROJECT_SCOPED_TOKEN
USERNAME = 'exampleuser'
AUTH_URL = 'http://public.com:5000/v2.0'
TOKEN = '04c7d5ffaeef485f9dc69c06db285bdb'

PASSWORD = 'password'
TENANT = 'tenant'
TENANT_ID = 'tenant_id'


class KeyringTest(utils.TestCase):

    def setUp(self):
        if keyring is None:
            self.skipTest(
                'optional package keyring or pickle is not installed')

        class MemoryKeyring(keyring.backend.KeyringBackend):
            """A Simple testing keyring.

            This class supports stubbing an initial password to be returned by
            setting password, and allows easy password and key retrieval. Also
            records if a password was retrieved.
            """

            def __init__(self):
                self.key = None
                self.password = None
                self.fetched = False
                self.get_password_called = False
                self.set_password_called = False

            def supported(self):
                return 1

            def get_password(self, service, username):
                self.get_password_called = True
                key = username + '@' + service
                # make sure we don't get passwords crossed if one is enforced.
                if self.key and self.key != key:
                    return None
                if self.password:
                    self.fetched = True
                return self.password

            def set_password(self, service, username, password):
                self.set_password_called = True
                self.key = username + '@' + service
                self.password = password

        super(KeyringTest, self).setUp()
        self.memory_keyring = MemoryKeyring()
        keyring.set_keyring(self.memory_keyring)

    def test_no_keyring_key(self):
        """Test case when no keyring set.

        Ensure that if we don't have use_keyring set in the client that
        the keyring is never accessed.
        """
        # Creating a HTTPClient not using session is deprecated.
        with self.deprecations.expect_deprecations_here():
            cl = httpclient.HTTPClient(username=USERNAME, password=PASSWORD,
                                       project_id=TENANT_ID, auth_url=AUTH_URL)

        # stub and check that a new token is received
        method = 'get_raw_token_from_identity_service'
        with mock.patch.object(cl, method) as meth:
            meth.return_value = (True, PROJECT_SCOPED_TOKEN)

            self.assertTrue(cl.authenticate())

            self.assertEqual(1, meth.call_count)

        # make sure that we never touched the keyring
        self.assertFalse(self.memory_keyring.get_password_called)
        self.assertFalse(self.memory_keyring.set_password_called)

    def test_build_keyring_key(self):
        # Creating a HTTPClient not using session is deprecated.
        with self.deprecations.expect_deprecations_here():
            cl = httpclient.HTTPClient(username=USERNAME, password=PASSWORD,
                                       project_id=TENANT_ID, auth_url=AUTH_URL)

        keyring_key = cl._build_keyring_key(auth_url=AUTH_URL,
                                            username=USERNAME,
                                            tenant_name=TENANT,
                                            tenant_id=TENANT_ID,
                                            token=TOKEN)

        self.assertEqual(keyring_key,
                         '%s/%s/%s/%s/%s' %
                         (AUTH_URL, TENANT_ID, TENANT, TOKEN, USERNAME))

    def test_set_and_get_keyring_expired(self):
        # Creating a HTTPClient not using session is deprecated.
        with self.deprecations.expect_deprecations_here():
            cl = httpclient.HTTPClient(username=USERNAME, password=PASSWORD,
                                       project_id=TENANT_ID, auth_url=AUTH_URL,
                                       use_keyring=True)

        # set an expired token into the keyring
        auth_ref = access.AccessInfo.factory(body=PROJECT_SCOPED_TOKEN)
        expired = timeutils.utcnow() - datetime.timedelta(minutes=30)
        auth_ref['token']['expires'] = client_utils.isotime(expired)
        self.memory_keyring.password = pickle.dumps(auth_ref)

        # stub and check that a new token is received, so not using expired
        method = 'get_raw_token_from_identity_service'
        with mock.patch.object(cl, method) as meth:
            meth.return_value = (True, PROJECT_SCOPED_TOKEN)

            self.assertTrue(cl.authenticate())

            self.assertEqual(1, meth.call_count)

        # check that a value was returned from the keyring
        self.assertTrue(self.memory_keyring.fetched)

        # check that the new token has been loaded into the keyring
        new_auth_ref = pickle.loads(self.memory_keyring.password)
        self.assertEqual(new_auth_ref['token']['expires'],
                         PROJECT_SCOPED_TOKEN['access']['token']['expires'])

    def test_get_keyring(self):
        # Creating a HTTPClient not using session is deprecated.
        with self.deprecations.expect_deprecations_here():
            cl = httpclient.HTTPClient(username=USERNAME, password=PASSWORD,
                                       project_id=TENANT_ID, auth_url=AUTH_URL,
                                       use_keyring=True)

        # set an token into the keyring
        auth_ref = access.AccessInfo.factory(body=PROJECT_SCOPED_TOKEN)
        future = timeutils.utcnow() + datetime.timedelta(minutes=30)
        auth_ref['token']['expires'] = client_utils.isotime(future)
        self.memory_keyring.password = pickle.dumps(auth_ref)

        # don't stub get_raw_token so will fail if authenticate happens

        self.assertTrue(cl.authenticate())
        self.assertTrue(self.memory_keyring.fetched)

    def test_set_keyring(self):
        # Creating a HTTPClient not using session is deprecated.
        with self.deprecations.expect_deprecations_here():
            cl = httpclient.HTTPClient(username=USERNAME, password=PASSWORD,
                                       project_id=TENANT_ID, auth_url=AUTH_URL,
                                       use_keyring=True)

        # stub and check that a new token is received
        method = 'get_raw_token_from_identity_service'
        with mock.patch.object(cl, method) as meth:
            meth.return_value = (True, PROJECT_SCOPED_TOKEN)

            self.assertTrue(cl.authenticate())

            self.assertEqual(1, meth.call_count)

        # we checked the keyring, but we didn't find anything
        self.assertTrue(self.memory_keyring.get_password_called)
        self.assertFalse(self.memory_keyring.fetched)

        # check that the new token has been loaded into the keyring
        self.assertTrue(self.memory_keyring.set_password_called)
        new_auth_ref = pickle.loads(self.memory_keyring.password)
        self.assertEqual(new_auth_ref.auth_token, TOKEN)
        self.assertEqual(new_auth_ref['token'],
                         PROJECT_SCOPED_TOKEN['access']['token'])
        self.assertEqual(new_auth_ref.username, USERNAME)
