Fix bug with missing & in authorization URL for OIDC
Also adds testing to ensure we don't break this again
This commit is contained in:
parent
4c0ab81ac8
commit
22a39c3007
8 changed files with 131 additions and 86 deletions
|
@ -1,5 +1,7 @@
|
|||
import copy
|
||||
import logging
|
||||
import urllib
|
||||
import urlparse
|
||||
|
||||
from abc import ABCMeta, abstractmethod
|
||||
from six import add_metaclass
|
||||
|
@ -8,6 +10,34 @@ from util import get_app_url
|
|||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class OAuthEndpoint(object):
|
||||
def __init__(self, base_url, params=None):
|
||||
self.base_url = base_url
|
||||
self.params = params or {}
|
||||
|
||||
def with_param(self, name, value):
|
||||
params_copy = copy.copy(self.params)
|
||||
params_copy[name] = value
|
||||
return OAuthEndpoint(self.base_url, params_copy)
|
||||
|
||||
def with_params(self, parameters):
|
||||
params_copy = copy.copy(self.params)
|
||||
params_copy.update(parameters)
|
||||
return OAuthEndpoint(self.base_url, params_copy)
|
||||
|
||||
def to_url_prefix(self):
|
||||
prefix = self.to_url()
|
||||
if self.params:
|
||||
return prefix + '&'
|
||||
else:
|
||||
return prefix + '?'
|
||||
|
||||
def to_url(self):
|
||||
(scheme, netloc, path, _, fragment) = urlparse.urlsplit(self.base_url)
|
||||
updated_query = urllib.urlencode(self.params)
|
||||
return urlparse.urlunsplit((scheme, netloc, path, updated_query, fragment))
|
||||
|
||||
class OAuthExchangeCodeException(Exception):
|
||||
""" Exception raised if a code exchange fails. """
|
||||
pass
|
||||
|
@ -36,12 +66,17 @@ class OAuthService(object):
|
|||
|
||||
@abstractmethod
|
||||
def token_endpoint(self):
|
||||
""" The endpoint at which the OAuth code can be exchanged for a token. """
|
||||
""" Returns the endpoint at which the OAuth code can be exchanged for a token. """
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def user_endpoint(self):
|
||||
""" The endpoint at which user information can be looked up. """
|
||||
""" Returns the endpoint at which user information can be looked up. """
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def authorize_endpoint(self):
|
||||
""" Returns the for authorization of the OAuth service. """
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
|
@ -49,11 +84,6 @@ class OAuthService(object):
|
|||
""" Performs validation of the client ID and secret, raising an exception on failure. """
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def authorize_endpoint(self):
|
||||
""" Endpoint for authorization. """
|
||||
pass
|
||||
|
||||
def requires_form_encoding(self):
|
||||
""" Returns True if form encoding is necessary for the exchange_code_for_token call. """
|
||||
return False
|
||||
|
@ -86,8 +116,7 @@ class OAuthService(object):
|
|||
'state': csrf_token,
|
||||
}
|
||||
|
||||
authorize_url = '%s%s' % (self.authorize_endpoint(), urllib.urlencode(params))
|
||||
return authorize_url
|
||||
return self.authorize_endpoint().with_params(params).to_url()
|
||||
|
||||
def get_redirect_uri(self, app_config, redirect_suffix=''):
|
||||
return '%s://%s/oauth2/%s/callback%s' % (app_config['PREFERRED_URL_SCHEME'],
|
||||
|
@ -104,7 +133,7 @@ class OAuthService(object):
|
|||
'Authorization': 'Bearer %s' % token,
|
||||
}
|
||||
|
||||
got_user = http_client.get(self.user_endpoint(), params=token_param, headers=headers)
|
||||
got_user = http_client.get(self.user_endpoint().to_url(), params=token_param, headers=headers)
|
||||
if got_user.status_code // 100 != 2:
|
||||
raise OAuthGetUserInfoException('Non-2XX response code for user_info call: %s' %
|
||||
got_user.status_code)
|
||||
|
@ -148,7 +177,7 @@ class OAuthService(object):
|
|||
payload['client_id'] = self.client_id()
|
||||
payload['client_secret'] = self.client_secret()
|
||||
|
||||
token_url = self.token_endpoint()
|
||||
token_url = self.token_endpoint().to_url()
|
||||
if form_encode:
|
||||
get_access_token = http_client.post(token_url, data=payload, headers=headers, auth=auth)
|
||||
else:
|
||||
|
|
Reference in a new issue