Skip to content

Commit 6c6f20d

Browse files
committed
A built-in API for RT migration
1 parent e3e9740 commit 6c6f20d

File tree

2 files changed

+97
-0
lines changed

2 files changed

+97
-0
lines changed

msal/application.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -700,6 +700,28 @@ def _validate_ssh_cert_input_data(self, data):
700700
"you must include a string parameter named 'key_id' "
701701
"which identifies the key in the 'req_cnf' argument.")
702702

703+
def import_refresh_token(self, refresh_token, scopes):
704+
"""Import an RT from elsewhere into MSAL's token cache.
705+
706+
:param str refresh_token: The old refresh token, as a string.
707+
708+
:param list scopes:
709+
The scopes associate with this old RT.
710+
Each scope needs to be in the Microsoft identity platform (v2) format.
711+
https://docs.microsoft.com/en-us/azure/active-directory/develop/migrate-python-adal-msal#scopes-not-resources
712+
713+
:return:
714+
* A dict contains "error" and some other keys, when error happened.
715+
* A dict contains no "error" key.
716+
"""
717+
result = self.client.obtain_token_by_refresh_token(
718+
refresh_token,
719+
decorate_scope(scopes, self.client_id),
720+
rt_getter=lambda rt: rt,
721+
on_updating_rt=False,
722+
)
723+
return {} if "error" not in result else result # Returns NO token
724+
703725

704726
class PublicClientApplication(ClientApplication): # browser app or mobile app
705727

sample/migrate_rt.py

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
"""
2+
The configuration file would look like this:
3+
4+
{
5+
"authority": "https://login.microsoftonline.com/organizations",
6+
"client_id": "your_client_id",
7+
"scope": ["User.ReadBasic.All"],
8+
// You can find the other permission names from this document
9+
// https://docs.microsoft.com/en-us/graph/permissions-reference
10+
}
11+
12+
You can then run this sample with a JSON configuration file:
13+
14+
python sample.py parameters.json
15+
"""
16+
17+
import sys # For simplicity, we'll read config file from 1st CLI param sys.argv[1]
18+
import json
19+
import logging
20+
21+
import msal
22+
23+
24+
# Optional logging
25+
# logging.basicConfig(level=logging.DEBUG) # Enable DEBUG log for entire script
26+
# logging.getLogger("msal").setLevel(logging.INFO) # Optionally disable MSAL DEBUG logs
27+
28+
config = json.load(open(sys.argv[1]))
29+
30+
def get_rt_via_old_app():
31+
# Let's pretend this is an old app powered by ADAL
32+
app = msal.PublicClientApplication(
33+
config["client_id"], authority=config["authority"])
34+
flow = app.initiate_device_flow(scopes=config["scope"])
35+
if "user_code" not in flow:
36+
raise ValueError(
37+
"Fail to create device flow. Err: %s" % json.dumps(flow, indent=4))
38+
print(flow["message"])
39+
sys.stdout.flush() # Some terminal needs this to ensure the message is shown
40+
41+
# Ideally you should wait here, in order to save some unnecessary polling
42+
# input("Press Enter after signing in from another device to proceed, CTRL+C to abort.")
43+
44+
result = app.acquire_token_by_device_flow(flow) # By default it will block
45+
assert "refresh_token" in result, "We should have a successful result"
46+
return result["refresh_token"]
47+
48+
try: # For easier testing, we try to reload a RT from previous run
49+
old_rt = json.load(open("rt.json"))[0]
50+
except: # If that is not possible, we acquire a RT
51+
old_rt = get_rt_via_old_app()
52+
json.dump([old_rt], open("rt.json", "w"))
53+
54+
# Now we will try to migrate this old_rt into a new app powered by MSAL
55+
56+
token_cache = msal.SerializableTokenCache()
57+
assert token_cache.serialize() == '{}', "Token cache is initially empty"
58+
app = msal.PublicClientApplication(
59+
config["client_id"], authority=config["authority"], token_cache=token_cache)
60+
result = app.import_refresh_token(old_rt, config["scope"])
61+
if "error" in result:
62+
print("Migration unsuccessful. Error: ", json.dumps(result, indent=2))
63+
else:
64+
print("Migration is successful")
65+
logging.debug("Token cache contains: %s", token_cache.serialize())
66+
67+
# From now on, the RT is saved inside MSAL's cache,
68+
# and becomes available in normal MSAL coding pattern. For example:
69+
accounts = app.get_accounts()
70+
if accounts:
71+
account = accounts[0] # Assuming end user pick this account
72+
result = app.acquire_token_silent(config["scope"], account)
73+
if "access_token" in result:
74+
print("RT is available in MSAL's cache, and can be used to acquire new AT")
75+

0 commit comments

Comments
 (0)