1
+ # It is currently shipped inside msal library.
2
+ # Pros: It is always available wherever msal is installed.
3
+ # Cons: Its 3rd-party dependencies (if any) may become msal's dependency.
4
+ """MSAL Python Tester
5
+
6
+ Usage 1: Run it on the fly.
7
+ python -m msal
8
+
9
+ Usage 2: Build an all-in-one executable file for bug bash.
10
+ shiv -e msal.__main__._main -o msaltest-on-os-name.pyz .
11
+ Note: We choose to not define a console script to avoid name conflict.
12
+ """
1
13
import base64 , getpass , json , logging , sys , msal
2
14
3
-
4
- AZURE_CLI = "04b07795-8ddb-461a-bbee-02f9e1bf7b46"
5
- VISUAL_STUDIO = "04f0c124-f2bc-4f59-8241-bf6df9866bbd"
15
+ _AZURE_CLI = "04b07795-8ddb-461a-bbee-02f9e1bf7b46"
16
+ _VISUAL_STUDIO = "04f0c124-f2bc-4f59-8241-bf6df9866bbd"
6
17
7
18
def print_json (blob ):
8
19
print (json .dumps (blob , indent = 2 , sort_keys = True ))
@@ -61,7 +72,7 @@ def _select_account(app):
61
72
else :
62
73
print ("No account available inside MSAL Python. Use other methods to acquire token first." )
63
74
64
- def acquire_token_silent (app ):
75
+ def _acquire_token_silent (app ):
65
76
"""acquire_token_silent() - with an account already signed into MSAL Python."""
66
77
account = _select_account (app )
67
78
if account :
@@ -71,95 +82,94 @@ def acquire_token_silent(app):
71
82
force_refresh = _input_boolean ("Bypass MSAL Python's token cache?" ),
72
83
))
73
84
74
- def _acquire_token_interactive (app , scopes , data = None ):
85
+ def _acquire_token_interactive (app , scopes = None , data = None ):
86
+ """acquire_token_interactive() - User will be prompted if app opts to do select_account."""
87
+ scopes = scopes or _input_scopes () # Let user input scope param before less important prompt and login_hint
75
88
prompt = _select_options ([
76
89
{"value" : None , "description" : "Unspecified. Proceed silently with a default account (if any), fallback to prompt." },
77
90
{"value" : "none" , "description" : "none. Proceed silently with a default account (if any), or error out." },
78
91
{"value" : "select_account" , "description" : "select_account. Prompt with an account picker." },
79
92
],
80
93
option_renderer = lambda o : o ["description" ],
81
94
header = "Prompt behavior?" )["value" ]
82
- raw_login_hint = _select_options (
83
- # login_hint is unnecessary when prompt=select_account,
84
- # but we still let tester input login_hint, just for testing purpose.
85
- [None ] + [a ["username" ] for a in app .get_accounts ()],
86
- header = "login_hint? (If you have multiple signed-in sessions in browser/broker, and you specify a login_hint to match one of them, you will bypass the account picker.)" ,
87
- accept_nonempty_string = True ,
88
- )
89
- login_hint = raw_login_hint ["username" ] if isinstance (raw_login_hint , dict ) else raw_login_hint
95
+ if prompt == "select_account" :
96
+ login_hint = None # login_hint is unnecessary when prompt=select_account
97
+ else :
98
+ raw_login_hint = _select_options (
99
+ [None ] + [a ["username" ] for a in app .get_accounts ()],
100
+ header = "login_hint? (If you have multiple signed-in sessions in browser/broker, and you specify a login_hint to match one of them, you will bypass the account picker.)" ,
101
+ accept_nonempty_string = True ,
102
+ )
103
+ login_hint = raw_login_hint ["username" ] if isinstance (raw_login_hint , dict ) else raw_login_hint
90
104
result = app .acquire_token_interactive (
91
105
scopes ,
92
106
parent_window_handle = app .CONSOLE_WINDOW_HANDLE , # This test app is a console app
93
107
enable_msa_passthrough = app .client_id in [ # Apps are expected to set this right
94
- AZURE_CLI , VISUAL_STUDIO ,
108
+ _AZURE_CLI , _VISUAL_STUDIO ,
95
109
], # Here this test app mimics the setting for some known MSA-PT apps
96
- prompt = prompt , login_hint = login_hint , data = data or {})
110
+ prompt = prompt , login_hint = login_hint , data = data or {},
111
+ )
97
112
if login_hint and "id_token_claims" in result :
98
113
signed_in_user = result .get ("id_token_claims" , {}).get ("preferred_username" )
99
114
if signed_in_user != login_hint :
100
115
logging .warning ('Signed-in user "%s" does not match login_hint' , signed_in_user )
116
+ print_json (result )
101
117
return result
102
118
103
- def acquire_token_interactive (app ):
104
- """acquire_token_interactive() - User will be prompted if app opts to do select_account."""
105
- print_json (_acquire_token_interactive (app , _input_scopes ()))
106
-
107
- def acquire_token_by_username_password (app ):
119
+ def _acquire_token_by_username_password (app ):
108
120
"""acquire_token_by_username_password() - See constraints here: https://docs.microsoft.com/en-us/azure/active-directory/develop/msal-authentication-flows#constraints-for-ropc"""
109
121
print_json (app .acquire_token_by_username_password (
110
122
_input ("username: " ), getpass .getpass ("password: " ), scopes = _input_scopes ()))
111
123
112
124
_JWK1 = """{"kty":"RSA", "n":"2tNr73xwcj6lH7bqRZrFzgSLj7OeLfbn8216uOMDHuaZ6TEUBDN8Uz0ve8jAlKsP9CQFCSVoSNovdE-fs7c15MxEGHjDcNKLWonznximj8pDGZQjVdfK-7mG6P6z-lgVcLuYu5JcWU_PeEqIKg5llOaz-qeQ4LEDS4T1D2qWRGpAra4rJX1-kmrWmX_XIamq30C9EIO0gGuT4rc2hJBWQ-4-FnE1NXmy125wfT3NdotAJGq5lMIfhjfglDbJCwhc8Oe17ORjO3FsB5CLuBRpYmP7Nzn66lRY3Fe11Xz8AEBl3anKFSJcTvlMnFtu3EpD-eiaHfTgRBU7CztGQqVbiQ", "e":"AQAB"}"""
113
- SSH_CERT_DATA = {"token_type" : "ssh-cert" , "key_id" : "key1" , "req_cnf" : _JWK1 }
114
- SSH_CERT_SCOPE = ["https://pas.windows.net/CheckMyAccess/Linux/.default" ]
125
+ _SSH_CERT_DATA = {"token_type" : "ssh-cert" , "key_id" : "key1" , "req_cnf" : _JWK1 }
126
+ _SSH_CERT_SCOPE = ["https://pas.windows.net/CheckMyAccess/Linux/.default" ]
115
127
116
- def acquire_ssh_cert_silently (app ):
128
+ def _acquire_ssh_cert_silently (app ):
117
129
"""Acquire an SSH Cert silently- This typically only works with Azure CLI"""
118
130
account = _select_account (app )
119
131
if account :
120
132
result = app .acquire_token_silent (
121
- SSH_CERT_SCOPE ,
133
+ _SSH_CERT_SCOPE ,
122
134
account ,
123
- data = SSH_CERT_DATA ,
135
+ data = _SSH_CERT_DATA ,
124
136
force_refresh = _input_boolean ("Bypass MSAL Python's token cache?" ),
125
137
)
126
138
print_json (result )
127
139
if result and result .get ("token_type" ) != "ssh-cert" :
128
140
logging .error ("Unable to acquire an ssh-cert." )
129
141
130
- def acquire_ssh_cert_interactive (app ):
142
+ def _acquire_ssh_cert_interactive (app ):
131
143
"""Acquire an SSH Cert interactively - This typically only works with Azure CLI"""
132
- result = _acquire_token_interactive (app , SSH_CERT_SCOPE , data = SSH_CERT_DATA )
133
- print_json (result )
144
+ result = _acquire_token_interactive (app , scopes = _SSH_CERT_SCOPE , data = _SSH_CERT_DATA )
134
145
if result .get ("token_type" ) != "ssh-cert" :
135
146
logging .error ("Unable to acquire an ssh-cert" )
136
147
137
- POP_KEY_ID = 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA-AAAAAAAA' # Fake key with a certain format and length
138
- RAW_REQ_CNF = json .dumps ({"kid" : POP_KEY_ID , "xms_ksl" : "sw" })
139
- POP_DATA = { # Sampled from Azure CLI's plugin connectedk8s
148
+ _POP_KEY_ID = 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA-AAAAAAAA' # Fake key with a certain format and length
149
+ _RAW_REQ_CNF = json .dumps ({"kid" : _POP_KEY_ID , "xms_ksl" : "sw" })
150
+ _POP_DATA = { # Sampled from Azure CLI's plugin connectedk8s
140
151
'token_type' : 'pop' ,
141
- 'key_id' : POP_KEY_ID ,
142
- "req_cnf" : base64 .urlsafe_b64encode (RAW_REQ_CNF .encode ('utf-8' )).decode ('utf-8' ).rstrip ('=' ),
143
- # Note: Sending RAW_REQ_CNF without base64 encoding would result in an http 500 error
152
+ 'key_id' : _POP_KEY_ID ,
153
+ "req_cnf" : base64 .urlsafe_b64encode (_RAW_REQ_CNF .encode ('utf-8' )).decode ('utf-8' ).rstrip ('=' ),
154
+ # Note: Sending _RAW_REQ_CNF without base64 encoding would result in an http 500 error
144
155
} # See also https://github.com/Azure/azure-cli-extensions/blob/main/src/connectedk8s/azext_connectedk8s/_clientproxyutils.py#L86-L92
145
156
146
- def acquire_pop_token_interactive (app ):
157
+ def _acquire_pop_token_interactive (app ):
147
158
"""Acquire a POP token interactively - This typically only works with Azure CLI"""
148
159
POP_SCOPE = ['6256c85f-0aad-4d50-b960-e6e9b21efe35/.default' ] # KAP 1P Server App Scope, obtained from https://github.com/Azure/azure-cli-extensions/pull/4468/files#diff-a47efa3186c7eb4f1176e07d0b858ead0bf4a58bfd51e448ee3607a5b4ef47f6R116
149
- result = _acquire_token_interactive (app , POP_SCOPE , data = POP_DATA )
160
+ result = _acquire_token_interactive (app , scopes = POP_SCOPE , data = _POP_DATA )
150
161
print_json (result )
151
162
if result .get ("token_type" ) != "pop" :
152
163
logging .error ("Unable to acquire a pop token" )
153
164
154
-
155
- def remove_account (app ):
165
+ def _remove_account (app ):
156
166
"""remove_account() - Invalidate account and/or token(s) from cache, so that acquire_token_silent() would be reset"""
157
167
account = _select_account (app )
158
168
if account :
159
169
app .remove_account (account )
160
170
print ('Account "{}" and/or its token(s) are signed out from MSAL Python' .format (account ["username" ]))
161
171
162
- def exit (app ):
172
+ def _exit (app ):
163
173
"""Exit"""
164
174
bug_link = (
165
175
"https://identitydivision.visualstudio.com/Engineering/_queries/query/79b3a352-a775-406f-87cd-a487c382a8ed/"
@@ -169,11 +179,11 @@ def exit(app):
169
179
print ("Bye. If you found a bug, please report it here: {}" .format (bug_link ))
170
180
sys .exit ()
171
181
172
- def main ():
173
- print ("Welcome to the Msal Python {} Tester\n " .format (msal .__version__ ))
182
+ def _main ():
183
+ print ("Welcome to the Msal Python {} Tester (Experimental) \n " .format (msal .__version__ ))
174
184
chosen_app = _select_options ([
175
- {"client_id" : AZURE_CLI , "name" : "Azure CLI (Correctly configured for MSA-PT)" },
176
- {"client_id" : VISUAL_STUDIO , "name" : "Visual Studio (Correctly configured for MSA-PT)" },
185
+ {"client_id" : _AZURE_CLI , "name" : "Azure CLI (Correctly configured for MSA-PT)" },
186
+ {"client_id" : _VISUAL_STUDIO , "name" : "Visual Studio (Correctly configured for MSA-PT)" },
177
187
{"client_id" : "95de633a-083e-42f5-b444-a4295d8e9314" , "name" : "Whiteboard Services (Non MSA-PT app. Accepts AAD & MSA accounts.)" },
178
188
],
179
189
option_renderer = lambda a : a ["name" ],
@@ -201,14 +211,14 @@ def main():
201
211
logging .basicConfig (level = logging .DEBUG )
202
212
while True :
203
213
func = _select_options ([
204
- acquire_token_silent ,
205
- acquire_token_interactive ,
206
- acquire_token_by_username_password ,
207
- acquire_ssh_cert_silently ,
208
- acquire_ssh_cert_interactive ,
209
- acquire_pop_token_interactive ,
210
- remove_account ,
211
- exit ,
214
+ _acquire_token_silent ,
215
+ _acquire_token_interactive ,
216
+ _acquire_token_by_username_password ,
217
+ _acquire_ssh_cert_silently ,
218
+ _acquire_ssh_cert_interactive ,
219
+ _acquire_pop_token_interactive ,
220
+ _remove_account ,
221
+ _exit ,
212
222
], option_renderer = lambda f : f .__doc__ , header = "MSAL Python APIs:" )
213
223
try :
214
224
func (app )
@@ -218,5 +228,5 @@ def main():
218
228
print ("Aborted" )
219
229
220
230
if __name__ == "__main__" :
221
- main ()
231
+ _main ()
222
232
0 commit comments