Repository: AzureAD/microsoft-authentication-library-for-python

Issue:
[Bug] Token cache incompatibility with MSAL.python
**Which Version of MSAL are you using ?**
4.21

Details in this PowerShell issue: 
https://github.com/Azure/azure-powershell/issues/13467

## Repro

Start with PWSH, then use Az CLI, the go back to PWSH. For example:

0. Use Windows (probably the same on Mac and Linux, but repro steps are on Win)
1. Delete the token cache file (C:\Users\<user>\AppData\Local\.IdentityService\msal.cache)
2. `connect-azaccount` (this is a PWSH command, so **MSAL.NET**)
3. `get-azsubscription` (again PWSH command, so **MSAL.NET**)
4. `az group list` (az cli command, so **MSAL.PY**)
5. disconnect-azaccount (PWSH comand, so **MSAL.NET**)

**Actual**: serialization exception from MSAL.NET


## Investigation

I have snapshots of the cache after step2, step3 and step4 and can provide them on request (will send them via email to you @rayluo ). 

At step 3, the refresh token section looks like this: 

```json
 "RefreshToken": {
        "6eeda3a1-c3b9-4e92-a94d-965a50c06de7.72f988bf-86f1-41af-91ab-2d7cd011db47-login.windows.net-refreshtoken-1--": {
            "home_account_id": "6eeda3a1-c3b9-4e92-a94d-965a50c06de7.72f988bf-86f1-41af-91ab-2d7cd011db47",
            "environment": "login.windows.net",
            "client_info": "eyJ1aWQiOiI2ZWVkYTNhMS1jM2I5LTRlOTItYTk0ZC05NjVhNTBjMDZkZTciLCJ1dGlkIjoiNzJmOTg4YmYtODZmMS00MWFmLTkxYWItMmQ3Y2QwMTFkYjQ3In0",
            "client_id": "1950a258-227b-4e31-a9cf-717495945fc2",
            "secret": "secret",
            "credential_type": "RefreshToken",
            "family_id": "1"
        }
    },
```

At step4, it looks like this: 

```json
"RefreshToken": {
        "6eeda3a1-c3b9-4e92-a94d-965a50c06de7.72f988bf-86f1-41af-91ab-2d7cd011db47-login.windows.net-refreshtoken-1--": {
            "home_account_id": "6eeda3a1-c3b9-4e92-a94d-965a50c06de7.72f988bf-86f1-41af-91ab-2d7cd011db47",
            "environment": "login.windows.net",
            "client_info": "eyJ1aWQiOiI2ZWVkYTNhMS1jM2I5LTRlOTItYTk0ZC05NjVhNTBjMDZkZTciLCJ1dGlkIjoiNzJmOTg4YmYtODZmMS00MWFmLTkxYWItMmQ3Y2QwMTFkYjQ3In0",
            "client_id": "1950a258-227b-4e31-a9cf-717495945fc2",
            "secret": "secret",
            "credential_type": "RefreshToken",
            "family_id": "1"
        },
        "6eeda3a1-c3b9-4e92-a94d-965a50c06de7.72f988bf-86f1-41af-91ab-2d7cd011db47-login.windows.net-refreshtoken-1950a258-227b-4e31-a9cf-717495945fc2--": {
            "secret": "secret"
        }
    },
```

The second entry here in step 4 is invalid. 

CC: @erich-wang 

Discussion:

Patch:
diff --git a/msal/token_cache.py b/msal/token_cache.py
index b7ebbb9..34eff37 100644
--- a/msal/token_cache.py
+++ b/msal/token_cache.py
@@ -234,8 +234,9 @@ class TokenCache(object):
         with self._lock:
             if new_key_value_pairs:  # Update with them
                 entries = self._cache.setdefault(credential_type, {})
-                entry = entries.setdefault(key, {})  # Create it if not yet exist
-                entry.update(new_key_value_pairs)
+                entries[key] = dict(
+                    old_entry,  # Do not use entries[key] b/c it might not exist
+                    **new_key_value_pairs)
             else:  # Remove old_entry
                 self._cache.setdefault(credential_type, {}).pop(key, None)
 


Test patch:
diff --git a/tests/test_token_cache.py b/tests/test_token_cache.py
index 1666bba..c846883 100644
--- a/tests/test_token_cache.py
+++ b/tests/test_token_cache.py
@@ -222,6 +222,24 @@ class TokenCacheTestCase(unittest.TestCase):
             {}).get("key_id")
         self.assertEqual(my_key_id, cached_key_id, "AT should be bound to the key")
 
+    def test_old_rt_data_with_wrong_key_should_still_be_salvaged_into_new_rt(self):
+        sample = {
+            'client_id': 'my_client_id',
+            'credential_type': 'RefreshToken',
+            'environment': 'login.example.com',
+            'home_account_id': "uid.utid",
+            'secret': 'a refresh token',
+            'target': 's2 s1 s3',
+            }
+        new_rt = "this is a new RT"
+        self.cache._cache["RefreshToken"] = {"wrong-key": sample}
+        self.cache.modify(
+            self.cache.CredentialType.REFRESH_TOKEN, sample, {"secret": new_rt})
+        self.assertEqual(
+            dict(sample, secret=new_rt),
+            self.cache._cache["RefreshToken"].get(
+                'uid.utid-login.example.com-refreshtoken-my_client_id--s2 s1 s3')
+            )
 
 class SerializableTokenCacheTestCase(TokenCacheTestCase):
     # Run all inherited test methods, and have extra check in tearDown()