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'
```

Discussion:

Patch:
diff --git a/README.md b/README.md
index a6f8d42..a60795f 100644
--- a/README.md
+++ b/README.md
@@ -8,19 +8,16 @@ The ADAL for python library makes it easy for python applications to authenticat
 
 To support 'service principal' with certificate, ADAL depends on the 'cryptography' package. For smooth installation, some suggestions:
 
-* For Windows and OSX
+*For Windows and OSX
 
 Upgrade to the latest pip (8.1.2 as of June 2016) and just do `pip install adal`.
 
-* For Linux
-
-Upgrade to the latest pip (8.1.2 as of June 2016).
+*For Linux
 
 You'll need a C compiler, libffi + its development headers, and openssl + its development headers. Refer to [cryptography installation](https://cryptography.io/en/latest/installation/)
 
-* To install from source:
+*To install from source:
 
-Upgrade to the latest pip (8.1.2 as of June 2016).
 Before run `python setup.py install`, to avoid dealing with compilation errors from cryptography, run `pip install cryptography` first to use statically-linked wheels.
 If you still like build from source, refer to [cryptography installation](https://cryptography.io/en/latest/installation/).
 
@@ -31,9 +28,9 @@ The convinient methods in 0.1.0 have been removed, and now your application shou
 
 2 Reasons:
 
-* Each adal client should have a unique id representing an valid application registered in a tenant. The old methods borrowed the client-id of [azure-cli](https://github.com/Azure/azure-xplat-cli), which is never right. It is simple to register your application and get a client id. Many walkthroughs exist. You can follow [one of those](http://www.bradygaster.com/post/using-windows-azure-active-directory-to-authenticate-the-management-libraries). Though that involves C# client, but the flow, and particularly the wizard snapshots are the same with adal-python. Do check out if you are new to AAD.
+* Each adal client should have a unique id representing an valid application registered in a tenant. The old methods borrowed the client-id of [azure-cli](https://github.com/Azure/azure-xplat-cli), which is never right. It is simple to register your application and get a client id. Many walkthroughs exist. You can follow [one of those] (http://www.bradygaster.com/post/using-windows-azure-active-directory-to-authenticate-the-management-libraries). Though that involves C# client, but the flow, and particularly the wizard snapshots are the same with adal-python. Do check out if you are new to AAD.
 
-* The old method defaults the `resource` argument to 'https://management.core.windows.net/', now you can just supply this value explictly. Please note, there are lots of different azure resources you can acquire tokens through adal though, for example, the samples in the repository acquire for the 'graph' resource. Because it is not an appropriate assumption to be made at the library level, we removed the old defaults.
+* The old mmethod defaults the `resource` argument to 'https://management.core.windows.net/', now you can just supply this value explictly. Please note, there are lots of different azure resources you can acquire tokens through adal though, for example, the samples in the repository acquire for the 'graph' resource. Because it is not an appropriate assumption to be made at the library level, we removed the old defaults.
 
 ### Acquire Token with Client Credentials
 
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
 


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)
+