@@ -7,6 +7,7 @@ import http
7
7
import http .server
8
8
import json
9
9
import logging
10
+ import msal
10
11
import os
11
12
import subprocess
12
13
import sys
@@ -179,9 +180,31 @@ def outlook_get_initial_tokens(client_id:str, client_secret:str, tenant:str, cod
179
180
raise Exception (f"Tokens not found in response: { content } " )
180
181
181
182
182
- def get_token_outlook (client_id :str , client_secret :str , tenant :str , output_file :IO [str ]) -> None :
183
- code = outlook_get_authorization_code (client_id , tenant )
184
- tokens = outlook_get_initial_tokens (client_id , client_secret , tenant , code )
183
+ def outlook_get_initial_tokens_by_device_flow (client_id :str , tenant :str ) -> Dict [str ,Union [str ,int ]]:
184
+ authority = f"https://login.microsoftonline.com/{ tenant } "
185
+ app = msal .PublicClientApplication (client_id , authority = authority )
186
+ flow = app .initiate_device_flow ([OUTLOOK_SCOPE ])
187
+ if "user_code" not in flow :
188
+ raise Exception ("Failed to create device flow. Ensure that public client flows are enabled for the application. Flow: %s" % json .dumps (flow , indent = 4 ))
189
+ print (flow ["message" ])
190
+ sys .stdout .flush ()
191
+ result = app .acquire_token_by_device_flow (flow )
192
+ if "access_token" not in result or "refresh_token" not in result :
193
+ raise Exception ("Failed to acquire token. Result: %s" % json .dumps (result , indent = 4 ))
194
+ print ("Acquired token." )
195
+ return {
196
+ 'access_token' : result ["access_token" ],
197
+ 'refresh_token' : result ["refresh_token" ],
198
+ 'expiry' : 0 ,
199
+ }
200
+
201
+
202
+ def get_token_outlook (client_id :str , client_secret :str , tenant :str , use_device_flow :bool , output_file :IO [str ]) -> None :
203
+ if use_device_flow :
204
+ tokens = outlook_get_initial_tokens_by_device_flow (client_id , tenant )
205
+ else :
206
+ code = outlook_get_authorization_code (client_id , tenant )
207
+ tokens = outlook_get_initial_tokens (client_id , client_secret , tenant , code )
185
208
json .dump (tokens , output_file , indent = 4 )
186
209
187
210
##########
@@ -199,12 +222,13 @@ def subcommand_get_token(args:argparse.Namespace) -> None:
199
222
if args .service == 'outlook' :
200
223
if not args .tenant :
201
224
parser .error ("'outlook' service requires 'tenant' argument." )
202
- if not args .client_secret :
225
+ if not args .client_secret and not args . use_device_flow :
203
226
args .client_secret = input ('Please enter OAuth2 client secret (not always required; Azure docs are unclear): ' )
204
227
get_token_outlook (
205
228
args .client_id ,
206
229
args .client_secret ,
207
230
args .tenant ,
231
+ args .use_device_flow ,
208
232
args .output_file ,
209
233
)
210
234
elif args .service == 'gmail' :
@@ -244,6 +268,11 @@ sp_get_token.add_argument(
244
268
'--scope' ,
245
269
help = "required for 'gmail'" ,
246
270
)
271
+ sp_get_token .add_argument (
272
+ "--use-device-flow" ,
273
+ action = argparse .BooleanOptionalAction ,
274
+ help = "use simplified device flow for Outlook/Azure" ,
275
+ )
247
276
sp_get_token .add_argument (
248
277
'output_file' , nargs = '?' , type = argparse .FileType ('w' ), default = '-' ,
249
278
help = "output file, '-' for stdout" ,
0 commit comments