b1e994aa7e
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].
119 lines
4.6 KiB
Python
119 lines
4.6 KiB
Python
import time
|
|
import random
|
|
|
|
from mock import patch
|
|
try:
|
|
from urllib.parse import urlencode
|
|
except ImportError:
|
|
from urllib import urlencode
|
|
from django.utils.encoding import force_text
|
|
from django.core.management import call_command
|
|
from django.test import TestCase, RequestFactory, override_settings
|
|
from django.utils import timezone
|
|
try:
|
|
from django.urls import reverse
|
|
except ImportError:
|
|
from django.core.urlresolvers import reverse
|
|
|
|
from oidc_provider.tests.app.utils import (
|
|
create_fake_user,
|
|
create_fake_client,
|
|
create_fake_token,
|
|
FAKE_RANDOM_STRING)
|
|
from oidc_provider.lib.utils.token import create_id_token
|
|
from oidc_provider.views import TokenIntrospectionView
|
|
|
|
|
|
class IntrospectionTestCase(TestCase):
|
|
|
|
def setUp(self):
|
|
call_command('creatersakey')
|
|
self.factory = RequestFactory()
|
|
self.user = create_fake_user()
|
|
self.aud = 'testaudience'
|
|
self.client = create_fake_client(response_type='id_token token')
|
|
self.resource = create_fake_client(response_type='id_token token')
|
|
self.resource.scope = ['token_introspection', self.aud]
|
|
self.resource.save()
|
|
self.token = create_fake_token(self.user, self.client.scope, self.client)
|
|
self.token.access_token = str(random.randint(1, 999999)).zfill(6)
|
|
self.now = time.time()
|
|
with patch('oidc_provider.lib.utils.token.time.time') as time_func:
|
|
time_func.return_value = self.now
|
|
self.token.id_token = create_id_token(self.token, self.user, self.aud)
|
|
self.token.save()
|
|
|
|
def _assert_inactive(self, response):
|
|
self.assertEqual(response.status_code, 200)
|
|
self.assertJSONEqual(force_text(response.content), {'active': False})
|
|
|
|
def _assert_active(self, response, **kwargs):
|
|
self.assertEqual(response.status_code, 200)
|
|
expected_content = {
|
|
'active': True,
|
|
'aud': self.aud,
|
|
'client_id': self.client.client_id,
|
|
'sub': str(self.user.pk),
|
|
'iat': int(self.now),
|
|
'exp': int(self.now + 600),
|
|
'iss': 'http://localhost:8000/openid',
|
|
}
|
|
expected_content.update(kwargs)
|
|
self.assertJSONEqual(force_text(response.content), expected_content)
|
|
|
|
def _make_request(self, **kwargs):
|
|
url = reverse('oidc_provider:token-introspection')
|
|
data = {
|
|
'client_id': kwargs.get('client_id', self.resource.client_id),
|
|
'client_secret': kwargs.get('client_secret', self.resource.client_secret),
|
|
'token': kwargs.get('access_token', self.token.access_token),
|
|
}
|
|
|
|
request = self.factory.post(url, data=urlencode(data),
|
|
content_type='application/x-www-form-urlencoded')
|
|
|
|
return TokenIntrospectionView.as_view()(request)
|
|
|
|
def test_no_client_params_returns_inactive(self):
|
|
response = self._make_request(client_id='')
|
|
self._assert_inactive(response)
|
|
|
|
def test_no_client_secret_returns_inactive(self):
|
|
response = self._make_request(client_secret='')
|
|
self._assert_inactive(response)
|
|
|
|
def test_invalid_client_returns_inactive(self):
|
|
response = self._make_request(client_id='invalid')
|
|
self._assert_inactive(response)
|
|
|
|
def test_token_not_found_returns_inactive(self):
|
|
response = self._make_request(access_token='invalid')
|
|
self._assert_inactive(response)
|
|
|
|
def test_scope_no_audience_returns_inactive(self):
|
|
self.resource.scope = ['token_introspection']
|
|
self.resource.save()
|
|
response = self._make_request()
|
|
self._assert_inactive(response)
|
|
|
|
def test_token_expired_returns_inactive(self):
|
|
self.token.expires_at = timezone.now() - timezone.timedelta(seconds=60)
|
|
self.token.save()
|
|
response = self._make_request()
|
|
self._assert_inactive(response)
|
|
|
|
def test_valid_request_returns_default_properties(self):
|
|
response = self._make_request()
|
|
self._assert_active(response)
|
|
|
|
@override_settings(OIDC_INTROSPECTION_PROCESSING_HOOK='oidc_provider.tests.app.utils.fake_introspection_processing_hook') # NOQA
|
|
def test_custom_introspection_hook_called_on_valid_request(self):
|
|
response = self._make_request()
|
|
self._assert_active(response, test_introspection_processing_hook=FAKE_RANDOM_STRING)
|
|
|
|
@override_settings(OIDC_INTROSPECTION_VALIDATE_AUDIENCE_SCOPE=False)
|
|
def test_disable_audience_validation(self):
|
|
self.resource.scope = ['token_introspection']
|
|
self.resource.save()
|
|
response = self._make_request()
|
|
self._assert_active(response)
|