Skip to content

Commit b2b00eb

Browse files
committed
AuthCodeReceiver checks state early now
1 parent 3427c25 commit b2b00eb

File tree

2 files changed

+16
-13
lines changed

2 files changed

+16
-13
lines changed

oauth2cli/authcode.py

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -95,17 +95,22 @@ def do_GET(self):
9595
if qs.get('code') or qs.get("error"): # So, it is an auth response
9696
auth_response = _qs2kv(qs)
9797
logger.debug("Got auth response: %s", auth_response)
98-
template = (self.server.success_template
99-
if "code" in qs else self.server.error_template)
100-
if _is_html(template.template):
101-
safe_data = _escape(auth_response)
98+
if self.server.auth_state and self.server.auth_state != auth_response.get("state"):
99+
# OAuth2 successful and error responses contain state when it was used
100+
# https://www.rfc-editor.org/rfc/rfc6749#section-4.2.2.1
101+
self._send_full_response("State mismatch") # Possibly an attack
102102
else:
103-
safe_data = auth_response
104-
self._send_full_response(template.safe_substitute(**safe_data))
105-
self.server.auth_response = auth_response # Set it now, after the response is likely sent
106-
# NOTE: Don't do self.server.shutdown() here. It'll halt the server.
103+
template = (self.server.success_template
104+
if "code" in qs else self.server.error_template)
105+
if _is_html(template.template):
106+
safe_data = _escape(auth_response) # Foiling an XSS attack
107+
else:
108+
safe_data = auth_response
109+
self._send_full_response(template.safe_substitute(**safe_data))
110+
self.server.auth_response = auth_response # Set it now, after the response is likely sent
107111
else:
108112
self._send_full_response(self.server.welcome_page)
113+
# NOTE: Don't do self.server.shutdown() here. It'll halt the server.
109114

110115
def _send_full_response(self, body, is_ok=True):
111116
self.send_response(200 if is_ok else 400)
@@ -295,16 +300,14 @@ def _get_auth_response(self, result, auth_uri=None, timeout=None, state=None,
295300

296301
self._server.timeout = timeout # Otherwise its handle_timeout() won't work
297302
self._server.auth_response = {} # Shared with _AuthCodeHandler
303+
self._server.auth_state = state # So handler will check it before sending response
298304
while not self._closing: # Otherwise, the handle_request() attempt
299305
# would yield noisy ValueError trace
300306
# Derived from
301307
# https://docs.python.org/2/library/basehttpserver.html#more-examples
302308
self._server.handle_request()
303309
if self._server.auth_response:
304-
if state and state != self._server.auth_response.get("state"):
305-
logger.debug("State mismatch. Ignoring this noise.")
306-
else:
307-
break
310+
break
308311
result.update(self._server.auth_response) # Return via writable result param
309312

310313
def close(self):

oauth2cli/oauth2.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -666,7 +666,7 @@ def _obtain_token_by_browser(
666666
**(auth_params or {}))
667667
auth_response = auth_code_receiver.get_auth_response(
668668
auth_uri=flow["auth_uri"],
669-
state=flow["state"], # Optional but we choose to do it upfront
669+
state=flow["state"], # So receiver can check it early
670670
timeout=timeout,
671671
welcome_template=welcome_template,
672672
success_template=success_template,

0 commit comments

Comments
 (0)