Fix token introspection "aud" and "client_id" response

Based on the OAuth 2.0 Token Introspection spec the "aud" field should
be based on the token. Previously "aud" was populated with the id of the
client making the introspection request which seems wrong. This changes
the endpoint to return the value from the token.

The "client_id" field is then changed to return the client id for the
client that originally requested the token rather than returning the
"aud" value from the token.

From the spec https://tools.ietf.org/html/rfc7662:

   client_id
      OPTIONAL.  Client identifier for the OAuth 2.0 client that
      requested this token.

   aud
      OPTIONAL.  Service-specific string identifier or list of string
      identifiers representing the intended audience for this token, as
      defined in JWT [RFC7519].
This commit is contained in:
Andy Clayton 2018-07-03 11:32:29 -05:00
parent 50733f8135
commit b1e994aa7e
2 changed files with 11 additions and 10 deletions

View file

@ -18,6 +18,7 @@ class TokenIntrospectionEndpoint(object):
def __init__(self, request): def __init__(self, request):
self.request = request self.request = request
self.params = {} self.params = {}
self.token = None
self.id_token = None self.id_token = None
self.client = None self.client = None
self._extract_params() self._extract_params()
@ -37,19 +38,19 @@ class TokenIntrospectionEndpoint(object):
logger.debug('[Introspection] No token provided') logger.debug('[Introspection] No token provided')
raise TokenIntrospectionError() raise TokenIntrospectionError()
try: try:
token = Token.objects.get(access_token=self.params['token']) self.token = Token.objects.get(access_token=self.params['token'])
except Token.DoesNotExist: except Token.DoesNotExist:
logger.debug('[Introspection] Token does not exist: %s', self.params['token']) logger.debug('[Introspection] Token does not exist: %s', self.params['token'])
raise TokenIntrospectionError() raise TokenIntrospectionError()
if token.has_expired(): if self.token.has_expired():
logger.debug('[Introspection] Token is not valid: %s', self.params['token']) logger.debug('[Introspection] Token is not valid: %s', self.params['token'])
raise TokenIntrospectionError() raise TokenIntrospectionError()
if not token.id_token: if not self.token.id_token:
logger.debug('[Introspection] Token not an authentication token: %s', logger.debug('[Introspection] Token not an authentication token: %s',
self.params['token']) self.params['token'])
raise TokenIntrospectionError() raise TokenIntrospectionError()
self.id_token = token.id_token self.id_token = self.token.id_token
audience = self.id_token.get('aud') audience = self.id_token.get('aud')
if not audience: if not audience:
logger.debug('[Introspection] No audience found for token: %s', self.params['token']) logger.debug('[Introspection] No audience found for token: %s', self.params['token'])
@ -74,10 +75,9 @@ class TokenIntrospectionEndpoint(object):
raise TokenIntrospectionError() raise TokenIntrospectionError()
def create_response_dic(self): def create_response_dic(self):
response_dic = dict((k, self.id_token[k]) for k in ('sub', 'exp', 'iat', 'iss')) response_dic = dict((k, self.id_token[k]) for k in ('aud', 'sub', 'exp', 'iat', 'iss'))
response_dic['active'] = True response_dic['active'] = True
response_dic['client_id'] = self.id_token.get('aud') response_dic['client_id'] = self.token.client.client_id
response_dic['aud'] = self.client.client_id
response_dic = run_processing_hook(response_dic, response_dic = run_processing_hook(response_dic,
'OIDC_INTROSPECTION_PROCESSING_HOOK', 'OIDC_INTROSPECTION_PROCESSING_HOOK',

View file

@ -30,16 +30,17 @@ class IntrospectionTestCase(TestCase):
call_command('creatersakey') call_command('creatersakey')
self.factory = RequestFactory() self.factory = RequestFactory()
self.user = create_fake_user() self.user = create_fake_user()
self.aud = 'testaudience'
self.client = create_fake_client(response_type='id_token token') self.client = create_fake_client(response_type='id_token token')
self.resource = create_fake_client(response_type='id_token token') self.resource = create_fake_client(response_type='id_token token')
self.resource.scope = ['token_introspection', self.client.client_id] self.resource.scope = ['token_introspection', self.aud]
self.resource.save() self.resource.save()
self.token = create_fake_token(self.user, self.client.scope, self.client) self.token = create_fake_token(self.user, self.client.scope, self.client)
self.token.access_token = str(random.randint(1, 999999)).zfill(6) self.token.access_token = str(random.randint(1, 999999)).zfill(6)
self.now = time.time() self.now = time.time()
with patch('oidc_provider.lib.utils.token.time.time') as time_func: with patch('oidc_provider.lib.utils.token.time.time') as time_func:
time_func.return_value = self.now time_func.return_value = self.now
self.token.id_token = create_id_token(self.token, self.user, self.client.client_id) self.token.id_token = create_id_token(self.token, self.user, self.aud)
self.token.save() self.token.save()
def _assert_inactive(self, response): def _assert_inactive(self, response):
@ -50,7 +51,7 @@ class IntrospectionTestCase(TestCase):
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
expected_content = { expected_content = {
'active': True, 'active': True,
'aud': self.resource.client_id, 'aud': self.aud,
'client_id': self.client.client_id, 'client_id': self.client.client_id,
'sub': str(self.user.pk), 'sub': str(self.user.pk),
'iat': int(self.now), 'iat': int(self.now),