Do not burn auth codes by authenticating before requesting an access
token This broke authorization for endpoints that only allow a code to be used once (e.g. Known). A side effect is that authorization no longer falls back to authentication if the token endpoint does not exist or returns an error.
This commit is contained in:
parent
cc9c7dfd21
commit
9fdd45a0de
|
@ -1,6 +1,14 @@
|
||||||
# Change Log
|
# Change Log
|
||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
|
|
||||||
|
## 0.2.5 - 2016-01-27
|
||||||
|
### Changed
|
||||||
|
- Bugfix: authorization_handler was burning the auth code by
|
||||||
|
delegating to authentication_handler. This broke authorization for
|
||||||
|
endpoints that only allow codes to be used once. A side effect of
|
||||||
|
this is that authorization no longer falls back to authentication
|
||||||
|
when there is no token_endpoint or the token_endpoint request fails.
|
||||||
|
|
||||||
## 0.2.4 - 2015-12-13
|
## 0.2.4 - 2015-12-13
|
||||||
### Changed
|
### Changed
|
||||||
- Replace `next_url` parameter with more general `state`
|
- Replace `next_url` parameter with more general `state`
|
||||||
|
|
|
@ -224,28 +224,37 @@ class MicropubClient:
|
||||||
return AuthResponse(me=confirmed_me, state=state)
|
return AuthResponse(me=confirmed_me, state=state)
|
||||||
|
|
||||||
def _handle_authorize_response(self):
|
def _handle_authorize_response(self):
|
||||||
authenticate_response = self._handle_authenticate_response()
|
|
||||||
code = flask.request.args.get('code')
|
code = flask.request.args.get('code')
|
||||||
wrapped_state = flask.request.args.get('state')
|
wrapped_state = flask.request.args.get('state')
|
||||||
me = flask.request.args.get('me')
|
me = flask.request.args.get('me')
|
||||||
redirect_uri = flask.url_for(flask.request.endpoint, _external=True)
|
redirect_uri = flask.url_for(flask.request.endpoint, _external=True)
|
||||||
|
|
||||||
if authenticate_response.error:
|
if wrapped_state and '|' in wrapped_state:
|
||||||
return authenticate_response
|
csrf_token, state = wrapped_state.split('|', 1)
|
||||||
|
else:
|
||||||
|
csrf_token = state = None
|
||||||
|
|
||||||
|
if not csrf_token:
|
||||||
|
return AuthResponse(
|
||||||
|
state=state, error='no CSRF token in response')
|
||||||
|
|
||||||
|
if csrf_token != flask.session.get('_micropub_csrf_token'):
|
||||||
|
return AuthResponse(
|
||||||
|
state=state, error='mismatched CSRF token')
|
||||||
|
|
||||||
token_url, micropub_url = self._discover_endpoints(me)[1:]
|
token_url, micropub_url = self._discover_endpoints(me)[1:]
|
||||||
|
|
||||||
if not token_url or not micropub_url:
|
if not token_url or not micropub_url:
|
||||||
# successfully auth'ed user, no micropub endpoint
|
# successfully auth'ed user, no micropub endpoint
|
||||||
return AuthResponse(
|
return AuthResponse(
|
||||||
me=authenticate_response.me,
|
me=me,
|
||||||
state=authenticate_response.state,
|
state=state,
|
||||||
error='no micropub endpoint found.')
|
error='no micropub endpoint found.')
|
||||||
|
|
||||||
# request an access token
|
# request an access token
|
||||||
token_data = {
|
token_data = {
|
||||||
'code': code,
|
'code': code,
|
||||||
'me': authenticate_response.me,
|
'me': me,
|
||||||
'redirect_uri': redirect_uri,
|
'redirect_uri': redirect_uri,
|
||||||
'client_id': self.client_id,
|
'client_id': self.client_id,
|
||||||
'state': wrapped_state,
|
'state': wrapped_state,
|
||||||
|
@ -260,26 +269,29 @@ class MicropubClient:
|
||||||
|
|
||||||
if token_response.status_code != 200:
|
if token_response.status_code != 200:
|
||||||
return AuthResponse(
|
return AuthResponse(
|
||||||
me=authenticate_response.me,
|
me=me,
|
||||||
state=authenticate_response.state,
|
state=state,
|
||||||
error='bad response from token endpoint: {}'
|
error='bad response from token endpoint: {}'
|
||||||
.format(token_response))
|
.format(token_response))
|
||||||
|
|
||||||
tdata = parse_qs(token_response.text)
|
tdata = parse_qs(token_response.text)
|
||||||
if 'access_token' not in tdata:
|
if 'access_token' not in tdata:
|
||||||
return AuthResponse(
|
return AuthResponse(
|
||||||
me=authenticate_response.me,
|
me=me,
|
||||||
state=authenticate_response.state,
|
state=state,
|
||||||
error='response from token endpoint missing access_token: {}'
|
error='response from token endpoint missing access_token: {}'
|
||||||
.format(tdata))
|
.format(tdata))
|
||||||
|
|
||||||
# success!
|
# success!
|
||||||
access_token = tdata.get('access_token')[0]
|
access_token = tdata.get('access_token')[0]
|
||||||
|
confirmed_me = tdata.get('me')[0]
|
||||||
|
confirmed_scope = tdata.get('scope')[0]
|
||||||
return AuthResponse(
|
return AuthResponse(
|
||||||
me=authenticate_response.me,
|
me=confirmed_me,
|
||||||
micropub_endpoint=micropub_url,
|
micropub_endpoint=micropub_url,
|
||||||
access_token=access_token,
|
access_token=access_token,
|
||||||
state=authenticate_response.state)
|
scope=confirmed_scope,
|
||||||
|
state=state)
|
||||||
|
|
||||||
def _discover_endpoints(self, me):
|
def _discover_endpoints(self, me):
|
||||||
me_response = requests.get(me)
|
me_response = requests.get(me)
|
||||||
|
@ -311,15 +323,18 @@ class AuthResponse:
|
||||||
micropub_endpoint (string): The endpoint to POST micropub requests to.
|
micropub_endpoint (string): The endpoint to POST micropub requests to.
|
||||||
access_token (string): The authorized user's micropub access token.
|
access_token (string): The authorized user's micropub access token.
|
||||||
state (string): The optional state that was passed to authorize.
|
state (string): The optional state that was passed to authorize.
|
||||||
|
scope (string): The scope that comes with the micropub access token
|
||||||
error (string): describes the error encountered if any. It is possible
|
error (string): describes the error encountered if any. It is possible
|
||||||
that the authentication step will succeed but the access token step
|
that the authentication step will succeed but the access token step
|
||||||
will fail, in which case me will be non-None, and error will describe
|
will fail, in which case me will be non-None, and error will describe
|
||||||
this condition.
|
this condition.
|
||||||
"""
|
"""
|
||||||
def __init__(self, me=None, micropub_endpoint=None,
|
def __init__(self, me=None, micropub_endpoint=None,
|
||||||
access_token=None, state=None, error=None):
|
access_token=None, state=None, scope=None,
|
||||||
|
error=None):
|
||||||
self.me = me
|
self.me = me
|
||||||
self.micropub_endpoint = micropub_endpoint
|
self.micropub_endpoint = micropub_endpoint
|
||||||
self.access_token = access_token
|
self.access_token = access_token
|
||||||
self.next_url = self.state = state
|
self.next_url = self.state = state
|
||||||
|
self.scope = scope
|
||||||
self.error = error
|
self.error = error
|
||||||
|
|
2
setup.py
2
setup.py
|
@ -11,7 +11,7 @@ from setuptools import setup
|
||||||
|
|
||||||
setup(
|
setup(
|
||||||
name='Flask-Micropub',
|
name='Flask-Micropub',
|
||||||
version='0.2.4',
|
version='0.2.5',
|
||||||
url='https://github.com/kylewm/flask-micropub/',
|
url='https://github.com/kylewm/flask-micropub/',
|
||||||
license='BSD',
|
license='BSD',
|
||||||
author='Kyle Mahan',
|
author='Kyle Mahan',
|
||||||
|
|
Loading…
Reference in New Issue