Skip to content
This repository was archived by the owner on Mar 7, 2020. It is now read-only.

Commit a02cedc

Browse files
author
Doug Mahugh
committed
touch up code
1 parent 527235e commit a02cedc

1 file changed

Lines changed: 53 additions & 54 deletions

File tree

graphrest.py

Lines changed: 53 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
"""sample Microsoft Graph authentication library"""
1+
"""Sample Microsoft Graph authentication library."""
22
# Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license.
33
# See LICENSE in the project root for license information.
44
import json
@@ -13,13 +13,16 @@
1313

1414
import config
1515

16-
# disable warnings to allow use of non-HTTPS for local dev/test
16+
17+
# Disable warnings to allow use of non-HTTPS for local dev/test.
1718
urllib3.disable_warnings()
1819

1920
class GraphSession(object):
20-
"""Microsoft Graph connection class. Implements OAuth 2.0 Authorization Code
21-
Grant workflow, handles configuration and state management, adding tokens
22-
for authenticated calls to Graph, related details.
21+
"""Microsoft Graph connection class.
22+
23+
Implements OAuth 2.0 Authorization Code Grant workflow, handles
24+
configuration and state management, adding tokens for authenticated calls to
25+
Graph, related details.
2326
"""
2427

2528
def __init__(self, **kwargs):
@@ -60,7 +63,7 @@ def __init__(self, **kwargs):
6063
# Print warning if any unknown arguments were passed, since those may be
6164
# errors/typos.
6265
for key in kwargs:
63-
if not key in self.config:
66+
if key not in self.config:
6467
print(f'WARNING: unknown "{key}" argument passed to GraphSession')
6568

6669
self.config.update(kwargs.items()) # add passed arguments to config
@@ -80,8 +83,7 @@ def __init__(self, **kwargs):
8083
if self.config['refresh_enable']:
8184
if refresh_scope not in self.config['scopes']:
8285
self.config['scopes'].append(refresh_scope)
83-
else:
84-
if refresh_scope in self.config['scopes']:
86+
elif refresh_scope in self.config['scopes']:
8587
self.config['scopes'].remove(refresh_scope)
8688

8789
def __repr__(self):
@@ -92,14 +94,14 @@ def __repr__(self):
9294

9395
def api_endpoint(self, url):
9496
"""Convert relative endpoint (e.g., 'me') to full Graph API endpoint."""
95-
9697
if urllib.parse.urlparse(url).scheme in ['http', 'https']:
9798
return url
9899
return urllib.parse.urljoin(
99100
f"{self.config['resource']}{self.config['api_version']}/",
100101
url.lstrip('/'))
101102

102-
def delete(self, endpoint, *, headers=None, data=None, verify=False, params=None):
103+
def delete(self, endpoint, *, headers=None, data=None, verify=False,
104+
params=None):
103105
"""Wrapper for authenticated HTTP DELETE to API endpoint.
104106
105107
endpoint = URL (can be partial; for example, 'me/contacts')
@@ -132,10 +134,8 @@ def get(self, endpoint='me', *, headers=None, stream=False, verify=False, params
132134
133135
Returns Requests response object.
134136
"""
135-
136137
self.token_validation()
137-
138-
# merge passed headers with default headers
138+
# Merge passed headers with default headers.
139139
merged_headers = self.headers()
140140
if headers:
141141
merged_headers.update(headers)
@@ -145,7 +145,7 @@ def get(self, endpoint='me', *, headers=None, stream=False, verify=False, params
145145
stream=stream, verify=verify, params=params)
146146

147147
def headers(self, headers=None):
148-
"""Returns dictionary of default HTTP headers for calls to Microsoft Graph API,
148+
"""Return a dict of default HTTP headers for calls to Microsoft Graph API,
149149
including access token and a unique client-request-id.
150150
151151
Keyword arguments:
@@ -172,25 +172,29 @@ def login(self, login_redirect=None):
172172
"""
173173
if login_redirect:
174174
self.login_redirect = login_redirect
175-
176-
# if caching is enabled, attempt silent SSO first
175+
# If caching is enabled, attempt silent SSO first.
177176
if self.config['cache_state']:
178177
if self.silent_sso():
179178
return bottle.redirect(self.login_redirect)
180179

181180
self.authstate = str(uuid.uuid4())
182-
params = urllib.parse.urlencode({'response_type': 'code',
183-
'client_id': self.config['client_id'],
184-
'redirect_uri': self.config['redirect_uri'],
185-
'scope': ' '.join(self.config['scopes']),
186-
'state': self.authstate,
187-
'prompt': 'select_account'})
188-
self.state['authorization_url'] = self.config['auth_endpoint'] + '?' + params
181+
data = {
182+
'response_type': 'code',
183+
'client_id': self.config['client_id'],
184+
'redirect_uri': self.config['redirect_uri'],
185+
'scope': ' '.join(self.config['scopes']),
186+
'state': self.authstate,
187+
'prompt': 'select_account',
188+
}
189+
params = urllib.parse.urlencode(data)
190+
url = f"{self.config['auth_endpoint']}?{params}"
191+
self.state['authorization_url'] = url
189192
bottle.redirect(self.state['authorization_url'], 302)
190193

191194
def logout(self, redirect_to=None):
192195
"""Clear current Graph connection state and redirect to specified route.
193-
If redirect_to == None, no redirection will take place and just clears
196+
197+
If redirect_to is false, no redirection will take place and just clears
194198
the current logged-in status.
195199
"""
196200

@@ -226,9 +230,7 @@ def post(self, endpoint, headers=None, data=None, verify=False, params=None):
226230
to False for demo purposes. For more information see:
227231
http://docs.python-requests.org/en/master/user/advanced/#ssl-cert-verification
228232
"""
229-
230233
self.token_validation()
231-
232234
merged_headers = self.headers()
233235
if headers:
234236
merged_headers.update(headers)
@@ -261,20 +263,21 @@ def redirect_uri_handler(self):
261263
code received from auth endpoint to call the token endpoint and obtain
262264
an access token.
263265
"""
264-
265266
# Verify that this authorization attempt came from this app, by checking
266267
# the received state against what we sent with our authorization request.
267268
if self.authstate != bottle.request.query.state:
268-
raise Exception(f"STATE MISMATCH: {self.authstate} sent,"
269-
f"{bottle.request.query.state} received")
269+
raise ValueError(f"STATE MISMATCH: {self.authstate} sent, "
270+
f"{bottle.request.query.state} received")
270271
self.authstate = '' # clear state to prevent re-use
271-
272+
data = {
273+
'client_id': self.config['client_id'],
274+
'client_secret': self.config['client_secret'],
275+
'grant_type': 'authorization_code',
276+
'code': bottle.request.query.code,
277+
'redirect_uri': self.config['redirect_uri']
278+
}
272279
token_response = requests.post(self.config['token_endpoint'],
273-
data={'client_id': self.config['client_id'],
274-
'client_secret': self.config['client_secret'],
275-
'grant_type': 'authorization_code',
276-
'code': bottle.request.query.code,
277-
'redirect_uri': self.config['redirect_uri']})
280+
data=data)
278281
self.token_save(token_response)
279282

280283
if token_response and token_response.ok:
@@ -289,12 +292,10 @@ def silent_sso(self):
289292
"""
290293
if self.token_seconds() > 0:
291294
return True # current token is vald
292-
293-
if self.state['refresh_token']:
295+
elif self.state['refresh_token']:
294296
# we have a refresh token, so use it to refresh the access token
295297
self.token_refresh()
296298
return True
297-
298299
return False # can't do silent SSO at this time
299300

300301
def state_manager(self, action):
@@ -304,7 +305,6 @@ def state_manager(self, action):
304305
'init' -- initialize state (set properties to defaults)
305306
'save' -- save current state (if self.config['cache_state'])
306307
"""
307-
308308
initialized_state = {'access_token': None, 'refresh_token': None,
309309
'token_expires_at': 0, 'authorization_url': '',
310310
'token_scope': '', 'loggedin': False}
@@ -325,12 +325,14 @@ def state_manager(self, action):
325325

326326
def token_refresh(self):
327327
"""Refresh the current access token."""
328+
data = {
329+
'client_id': self.config['client_id'],
330+
'client_secret': self.config['client_secret'],
331+
'grant_type': 'refresh_token',
332+
'refresh_token': self.state['refresh_token'],
333+
}
328334
response = requests.post(self.config['token_endpoint'],
329-
data={'client_id': self.config['client_id'],
330-
'client_secret': self.config['client_secret'],
331-
'grant_type': 'refresh_token',
332-
'refresh_token': self.state['refresh_token']},
333-
verify=False)
335+
data=data, verify=False)
334336
self.token_save(response)
335337

336338
def token_save(self, response):
@@ -343,19 +345,18 @@ def token_save(self, response):
343345
Returns True if the token was successfully saved, False if not.
344346
To manually inspect the contents of a JWT, see http://jwt.ms/.
345347
"""
346-
347-
jsondata = response.json()
348-
if not 'access_token' in jsondata:
348+
json_data = response.json()
349+
if 'access_token' not in json_data:
349350
self.logout()
350351
return False
351352

352-
self.verify_scopes(jsondata['scope'])
353-
self.state['access_token'] = jsondata['access_token']
353+
self.verify_scopes(json_data['scope'])
354+
self.state['access_token'] = json_data['access_token']
354355
self.state['loggedin'] = True
355356

356357
# token_expires_at = time.time() value (seconds) at which it expires
357-
self.state['token_expires_at'] = time.time() + int(jsondata['expires_in'])
358-
self.state['refresh_token'] = jsondata.get('refresh_token', None)
358+
self.state['token_expires_at'] = time.time() + int(json_data['expires_in'])
359+
self.state['refresh_token'] = json_data.get('refresh_token')
359360
return True
360361

361362
def token_seconds(self):
@@ -376,9 +377,7 @@ def token_validation(self, nseconds=5):
376377

377378
def verify_scopes(self, token_scopes):
378379
"""Verify that the list of scopes returned with an access token match
379-
the scopes that we requested.
380-
"""
381-
380+
the scopes that we requested."""
382381
self.state['token_scope'] = token_scopes
383382
scopes_returned = frozenset({_.lower() for _ in token_scopes.split(' ')})
384383
scopes_expected = frozenset({_.lower() for _ in self.config['scopes']

0 commit comments

Comments
 (0)