Fix OAuth 2 handler to support retrieving parameters from other places; various OAuth client (such as the Go library) send the values in the request body or even the Auth header
This commit is contained in:
parent
91b464d0de
commit
fb8e718c44
3 changed files with 35 additions and 14 deletions
|
@ -89,12 +89,13 @@ def truthy_param(param):
|
||||||
return param not in {False, 'false', 'False', '0', 'FALSE', '', 'null'}
|
return param not in {False, 'false', 'False', '0', 'FALSE', '', 'null'}
|
||||||
|
|
||||||
|
|
||||||
def param_required(param_name):
|
def param_required(param_name, allow_body=False):
|
||||||
def wrapper(wrapped):
|
def wrapper(wrapped):
|
||||||
@wraps(wrapped)
|
@wraps(wrapped)
|
||||||
def decorated(*args, **kwargs):
|
def decorated(*args, **kwargs):
|
||||||
if param_name not in request.args:
|
if param_name not in request.args:
|
||||||
abort(make_response('Required param: %s' % param_name, 400))
|
if not allow_body and param_name not in request.values:
|
||||||
|
abort(make_response('Required param: %s' % param_name, 400))
|
||||||
return wrapped(*args, **kwargs)
|
return wrapped(*args, **kwargs)
|
||||||
return decorated
|
return decorated
|
||||||
return wrapper
|
return wrapper
|
||||||
|
|
|
@ -25,6 +25,7 @@ from endpoints.trigger import (CustomBuildTrigger, BitbucketBuildTrigger, Trigge
|
||||||
from util.names import parse_repository_name, parse_repository_name_and_tag
|
from util.names import parse_repository_name, parse_repository_name_and_tag
|
||||||
from util.useremails import send_email_changed
|
from util.useremails import send_email_changed
|
||||||
from util.systemlogs import build_logs_archive
|
from util.systemlogs import build_logs_archive
|
||||||
|
from util.headers import parse_basic_auth
|
||||||
from auth import scopes
|
from auth import scopes
|
||||||
|
|
||||||
import features
|
import features
|
||||||
|
@ -462,19 +463,22 @@ def request_authorization_code():
|
||||||
|
|
||||||
@web.route('/oauth/access_token', methods=['POST'])
|
@web.route('/oauth/access_token', methods=['POST'])
|
||||||
@no_cache
|
@no_cache
|
||||||
@param_required('grant_type')
|
@param_required('grant_type', allow_body=True)
|
||||||
@param_required('client_id')
|
@param_required('client_id', allow_body=True)
|
||||||
@param_required('client_secret')
|
@param_required('redirect_uri', allow_body=True)
|
||||||
@param_required('redirect_uri')
|
@param_required('code', allow_body=True)
|
||||||
@param_required('code')
|
@param_required('scope', allow_body=True)
|
||||||
@param_required('scope')
|
|
||||||
def exchange_code_for_token():
|
def exchange_code_for_token():
|
||||||
grant_type = request.form.get('grant_type', None)
|
grant_type = request.values.get('grant_type', None)
|
||||||
client_id = request.form.get('client_id', None)
|
client_id = request.values.get('client_id', None)
|
||||||
client_secret = request.form.get('client_secret', None)
|
redirect_uri = request.values.get('redirect_uri', None)
|
||||||
redirect_uri = request.form.get('redirect_uri', None)
|
code = request.values.get('code', None)
|
||||||
code = request.form.get('code', None)
|
scope = request.values.get('scope', None)
|
||||||
scope = request.form.get('scope', None)
|
|
||||||
|
client_secret = request.values.get('client_secret', None)
|
||||||
|
if client_secret is None:
|
||||||
|
# Sometimes OAuth2 clients place the client secret in the Auth header.
|
||||||
|
client_secret = parse_basic_auth(request.headers.get('Authorization'))
|
||||||
|
|
||||||
provider = FlaskAuthorizationProvider()
|
provider = FlaskAuthorizationProvider()
|
||||||
return provider.get_token(grant_type, client_id, client_secret, redirect_uri, code, scope=scope)
|
return provider.get_token(grant_type, client_id, client_secret, redirect_uri, code, scope=scope)
|
||||||
|
|
16
util/headers.py
Normal file
16
util/headers.py
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
import base64
|
||||||
|
|
||||||
|
def parse_basic_auth(header_value):
|
||||||
|
""" Attempts to parse the given header value as a Base64-encoded Basic auth header. """
|
||||||
|
|
||||||
|
if not header_value:
|
||||||
|
return None
|
||||||
|
|
||||||
|
parts = header_value.split(' ')
|
||||||
|
if len(parts) != 2 or parts[0].lower() != 'basic':
|
||||||
|
return None
|
||||||
|
|
||||||
|
try:
|
||||||
|
return base64.b64decode(parts[1])
|
||||||
|
except ValueError:
|
||||||
|
return None
|
Reference in a new issue