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,7 +82,8 @@ 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."""
75
87
prompt = _select_options ([
76
88
{"value" : None , "description" : "Unspecified. Proceed silently with a default account (if any), fallback to prompt." },
77
89
{"value" : "none" , "description" : "none. Proceed silently with a default account (if any), or error out." },
@@ -88,78 +100,73 @@ def _acquire_token_interactive(app, scopes, data=None):
88
100
)
89
101
login_hint = raw_login_hint ["username" ] if isinstance (raw_login_hint , dict ) else raw_login_hint
90
102
result = app .acquire_token_interactive (
91
- scopes ,
103
+ scopes or _input_scopes () ,
92
104
parent_window_handle = app .CONSOLE_WINDOW_HANDLE , # This test app is a console app
93
105
enable_msa_passthrough = app .client_id in [ # Apps are expected to set this right
94
- AZURE_CLI , VISUAL_STUDIO ,
106
+ _AZURE_CLI , _VISUAL_STUDIO ,
95
107
], # Here this test app mimics the setting for some known MSA-PT apps
96
108
prompt = prompt , login_hint = login_hint , data = data or {})
97
109
if login_hint and "id_token_claims" in result :
98
110
signed_in_user = result .get ("id_token_claims" , {}).get ("preferred_username" )
99
111
if signed_in_user != login_hint :
100
112
logging .warning ('Signed-in user "%s" does not match login_hint' , signed_in_user )
113
+ print_json (result )
101
114
return result
102
115
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 ):
116
+ def _acquire_token_by_username_password (app ):
108
117
"""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
118
print_json (app .acquire_token_by_username_password (
110
119
_input ("username: " ), getpass .getpass ("password: " ), scopes = _input_scopes ()))
111
120
112
121
_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" ]
122
+ _SSH_CERT_DATA = {"token_type" : "ssh-cert" , "key_id" : "key1" , "req_cnf" : _JWK1 }
123
+ _SSH_CERT_SCOPE = ["https://pas.windows.net/CheckMyAccess/Linux/.default" ]
115
124
116
- def acquire_ssh_cert_silently (app ):
125
+ def _acquire_ssh_cert_silently (app ):
117
126
"""Acquire an SSH Cert silently- This typically only works with Azure CLI"""
118
127
account = _select_account (app )
119
128
if account :
120
129
result = app .acquire_token_silent (
121
- SSH_CERT_SCOPE ,
130
+ _SSH_CERT_SCOPE ,
122
131
account ,
123
- data = SSH_CERT_DATA ,
132
+ data = _SSH_CERT_DATA ,
124
133
force_refresh = _input_boolean ("Bypass MSAL Python's token cache?" ),
125
134
)
126
135
print_json (result )
127
136
if result and result .get ("token_type" ) != "ssh-cert" :
128
137
logging .error ("Unable to acquire an ssh-cert." )
129
138
130
- def acquire_ssh_cert_interactive (app ):
139
+ def _acquire_ssh_cert_interactive (app ):
131
140
"""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 )
141
+ result = _acquire_token_interactive (app , scopes = _SSH_CERT_SCOPE , data = _SSH_CERT_DATA )
134
142
if result .get ("token_type" ) != "ssh-cert" :
135
143
logging .error ("Unable to acquire an ssh-cert" )
136
144
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
145
+ _POP_KEY_ID = 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA-AAAAAAAA' # Fake key with a certain format and length
146
+ _RAW_REQ_CNF = json .dumps ({"kid" : _POP_KEY_ID , "xms_ksl" : "sw" })
147
+ _POP_DATA = { # Sampled from Azure CLI's plugin connectedk8s
140
148
'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
149
+ 'key_id' : _POP_KEY_ID ,
150
+ "req_cnf" : base64 .urlsafe_b64encode (_RAW_REQ_CNF .encode ('utf-8' )).decode ('utf-8' ).rstrip ('=' ),
151
+ # Note: Sending _RAW_REQ_CNF without base64 encoding would result in an http 500 error
144
152
} # See also https://github.com/Azure/azure-cli-extensions/blob/main/src/connectedk8s/azext_connectedk8s/_clientproxyutils.py#L86-L92
145
153
146
- def acquire_pop_token_interactive (app ):
154
+ def _acquire_pop_token_interactive (app ):
147
155
"""Acquire a POP token interactively - This typically only works with Azure CLI"""
148
156
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 )
157
+ result = _acquire_token_interactive (app , scopes = POP_SCOPE , data = _POP_DATA )
150
158
print_json (result )
151
159
if result .get ("token_type" ) != "pop" :
152
160
logging .error ("Unable to acquire a pop token" )
153
161
154
-
155
- def remove_account (app ):
162
+ def _remove_account (app ):
156
163
"""remove_account() - Invalidate account and/or token(s) from cache, so that acquire_token_silent() would be reset"""
157
164
account = _select_account (app )
158
165
if account :
159
166
app .remove_account (account )
160
167
print ('Account "{}" and/or its token(s) are signed out from MSAL Python' .format (account ["username" ]))
161
168
162
- def exit (app ):
169
+ def _exit (app ):
163
170
"""Exit"""
164
171
bug_link = (
165
172
"https://identitydivision.visualstudio.com/Engineering/_queries/query/79b3a352-a775-406f-87cd-a487c382a8ed/"
@@ -169,11 +176,11 @@ def exit(app):
169
176
print ("Bye. If you found a bug, please report it here: {}" .format (bug_link ))
170
177
sys .exit ()
171
178
172
- def main ():
173
- print ("Welcome to the Msal Python {} Tester\n " .format (msal .__version__ ))
179
+ def _main ():
180
+ print ("Welcome to the Msal Python {} Tester (Experimental) \n " .format (msal .__version__ ))
174
181
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)" },
182
+ {"client_id" : _AZURE_CLI , "name" : "Azure CLI (Correctly configured for MSA-PT)" },
183
+ {"client_id" : _VISUAL_STUDIO , "name" : "Visual Studio (Correctly configured for MSA-PT)" },
177
184
{"client_id" : "95de633a-083e-42f5-b444-a4295d8e9314" , "name" : "Whiteboard Services (Non MSA-PT app. Accepts AAD & MSA accounts.)" },
178
185
],
179
186
option_renderer = lambda a : a ["name" ],
@@ -201,14 +208,14 @@ def main():
201
208
logging .basicConfig (level = logging .DEBUG )
202
209
while True :
203
210
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 ,
211
+ _acquire_token_silent ,
212
+ _acquire_token_interactive ,
213
+ _acquire_token_by_username_password ,
214
+ _acquire_ssh_cert_silently ,
215
+ _acquire_ssh_cert_interactive ,
216
+ _acquire_pop_token_interactive ,
217
+ _remove_account ,
218
+ _exit ,
212
219
], option_renderer = lambda f : f .__doc__ , header = "MSAL Python APIs:" )
213
220
try :
214
221
func (app )
@@ -218,5 +225,5 @@ def main():
218
225
print ("Aborted" )
219
226
220
227
if __name__ == "__main__" :
221
- main ()
228
+ _main ()
222
229
0 commit comments