Repository: AzureAD/azure-activedirectory-library-for-python

Issue:
Issue while trying to obtain a token using client credentials once the token has expired
I am able to obtain a valid access token by issuing the following command
```
>>> token = context.acquire_token_with_client_credentials(RESOURCE, client_id, client_secret)
```

However, when I issue the same command after the above token has expired, I get the following error message. Please let me know if I am missing something here or if I am expected to issue a different command in order to obtain a new token. Thanks in advance.

```
>>> token = context.acquire_token_with_client_credentials(RESOURCE, client_id, client_secret)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/Users/shetty/.virtualenvs/ad/lib/python2.7/site-packages/adal/authentication_context.py", line 160, in acquire_token_with_client_credentials
    return self._acquire_token(token_func)
  File "/Users/shetty/.virtualenvs/ad/lib/python2.7/site-packages/adal/authentication_context.py", line 109, in _acquire_token
    return token_func(self)
  File "/Users/shetty/.virtualenvs/ad/lib/python2.7/site-packages/adal/authentication_context.py", line 158, in token_func
    return token_request.get_token_with_client_credentials(client_secret)
  File "/Users/shetty/.virtualenvs/ad/lib/python2.7/site-packages/adal/token_request.py", line 304, in get_token_with_client_credentials
    token = self._find_token_from_cache()
  File "/Users/shetty/.virtualenvs/ad/lib/python2.7/site-packages/adal/token_request.py", line 128, in _find_token_from_cache
    return self._cache_driver.find(cache_query)
  File "/Users/shetty/.virtualenvs/ad/lib/python2.7/site-packages/adal/cache_driver.py", line 182, in find
    is_resource_tenant_specific)
  File "/Users/shetty/.virtualenvs/ad/lib/python2.7/site-packages/adal/cache_driver.py", line 171, in _refresh_entry_if_necessary
    return self._acquire_new_token_from_mrrt(entry)
  File "/Users/shetty/.virtualenvs/ad/lib/python2.7/site-packages/adal/cache_driver.py", line 153, in _acquire_new_token_from_mrrt
    token_response = self._refresh_function(entry, self._resource)
  File "/Users/shetty/.virtualenvs/ad/lib/python2.7/site-packages/adal/token_request.py", line 137, in _get_token_with_token_response
    refresh_token = entry[TOKEN_RESPONSE_FIELDS.REFRESH_TOKEN]
KeyError: 'refreshToken'
```

Comments:

Patch:
diff --git a/adal/cache_driver.py b/adal/cache_driver.py
index fba053b..9683dca 100644
--- a/adal/cache_driver.py
+++ b/adal/cache_driver.py
@@ -164,11 +164,19 @@ class CacheDriver(object):
         now_plus_buffer = now + timedelta(minutes=Misc.CLOCK_BUFFER)
 
         if is_resource_specific and now_plus_buffer > expiry_date:
-            self._log.info('Cached token is expired.  Refreshing: %s', expiry_date)
-            return self._refresh_expired_entry(entry)
+            if TokenResponseFields.REFRESH_TOKEN in entry:
+                self._log.info('Cached token is expired.  Refreshing: %s', expiry_date)
+                return self._refresh_expired_entry(entry)
+            else:
+                self.remove(entry)
+                return None
         elif not is_resource_specific and entry.get(TokenResponseFields.IS_MRRT):
-            self._log.info('Acquiring new access token from MRRT token.')
-            return self._acquire_new_token_from_mrrt(entry)
+            if TokenResponseFields.REFRESH_TOKEN in entry:
+                self._log.info('Acquiring new access token from MRRT token.')
+                return self._acquire_new_token_from_mrrt(entry)
+            else:
+                self.remove(entry)
+                return None
         else:
             return entry
 
diff --git a/adal/oauth2_client.py b/adal/oauth2_client.py
index dd26c4a..979a292 100644
--- a/adal/oauth2_client.py
+++ b/adal/oauth2_client.py
@@ -191,10 +191,10 @@ class OAuth2Client(object):
             wire_response[OAuth2.ResponseParameters.CREATED_ON] = str(temp_date)
 
         if not wire_response.get(OAuth2.ResponseParameters.TOKEN_TYPE):
-            raise AdalError('wire_response is missing token_type')
+            raise AdalError('wire_response is missing token_type', wire_response)
 
         if not wire_response.get(OAuth2.ResponseParameters.ACCESS_TOKEN):
-            raise AdalError('wire_response is missing access_token')
+            raise AdalError('wire_response is missing access_token', wire_response)
 
         token_response = map_fields(wire_response, TOKEN_RESPONSE_MAP)
 


Test patch:
diff --git a/tests/test_cache_driver.py b/tests/test_cache_driver.py
new file mode 100644
index 0000000..b3c4e07
--- /dev/null
+++ b/tests/test_cache_driver.py
@@ -0,0 +1,58 @@
+#------------------------------------------------------------------------------
+#
+# Copyright (c) Microsoft Corporation.
+# All rights reserved.
+#
+# This code is licensed under the MIT License.
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files(the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and / or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions :
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+# THE SOFTWARE.
+#
+#------------------------------------------------------------------------------
+
+import unittest
+try:
+    from unittest import mock
+except ImportError:
+    import mock
+
+from adal.log import create_log_context
+from adal.cache_driver import CacheDriver
+
+
+class TestCacheDriver(unittest.TestCase):
+    def test_rt_less_item_wont_cause_exception(self):  # Github issue #82
+        rt_less_entry_came_from_previous_client_credentials_grant = {
+            "expiresIn": 3600,
+            "_authority": "https://login.microsoftonline.com/foo",
+            "resource": "spn:00000002-0000-0000-c000-000000000000",
+            "tokenType": "Bearer",
+            "expiresOn": "1999-05-22 16:31:46.202000",
+            "isMRRT": True,
+            "_clientId": "client_id",
+            "accessToken": "this is an AT",
+            }
+        refresh_function = mock.MagicMock(return_value={})
+        cache_driver = CacheDriver(
+            {"log_context": create_log_context()}, "authority", "resource",
+            "client_id", mock.MagicMock(), refresh_function)
+        entry = cache_driver._refresh_entry_if_necessary(
+            rt_less_entry_came_from_previous_client_credentials_grant, False)
+        refresh_function.assert_not_called()  # Otherwise it will cause an exception
+        self.assertIsNone(entry)
+