Add token introspection endpoint to satisfy https://tools.ietf.org/html/rfc7662

This commit is contained in:
Maxim Daniline 2018-02-05 15:29:08 +00:00 committed by Maxim Daniline
parent eca5b06760
commit 180aad9a36
13 changed files with 492 additions and 14 deletions

View file

@ -21,6 +21,21 @@ If not specified, it will be automatically generated using ``request.scheme`` an
For example ``http://localhost:8000``. For example ``http://localhost:8000``.
OIDC_RESOURCE_MODEL
===================
OPTIONAL. ``str``. Path to a custom API resource model.
Default is ``oidc_provider.Resource``.
Similar to the Django custom user model, you can extend the default model by adding ``AbstractResource`` as a mixin.
For example::
class CustomResource(AbstractResource):
custom_field = models.CharField(max_length=255, _(u'Some Custom Field'))
OIDC_AFTER_USERLOGIN_HOOK OIDC_AFTER_USERLOGIN_HOOK
========================= =========================
@ -90,6 +105,22 @@ Default is::
return id_token return id_token
OIDC_INTROSPECTION_PROCESSING_HOOK
==================================
OPTIONAL. ``str`` or ``(list, tuple)``.
A string with the location of your function hook or ``list`` or ``tuple`` with hook functions.
Here you can add extra dictionary values specific to your valid response value for token introspection.
The function receives an ``introspection_response`` dictionary, a ``resource`` instance and an ``id_token`` dictionary.
Default is::
def default_introspection_processing_hook(introspection_response, resource, id_token):
return introspection_response
OIDC_IDTOKEN_SUB_GENERATOR OIDC_IDTOKEN_SUB_GENERATOR
========================== ==========================

View file

@ -6,7 +6,9 @@ from django.forms import ModelForm
from django.contrib import admin from django.contrib import admin
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from oidc_provider.models import Client, Code, Token, RSAKey from oidc_provider.models import Client, Code, Token, RSAKey, get_resource_model
Resource = get_resource_model()
class ClientForm(ModelForm): class ClientForm(ModelForm):
@ -72,6 +74,43 @@ class ClientAdmin(admin.ModelAdmin):
raw_id_fields = ['owner'] raw_id_fields = ['owner']
class ResourceForm(ModelForm):
def __init__(self, *args, **kwargs):
super(ResourceForm, self).__init__(*args, **kwargs)
self.fields['resource_secret'].required = False
def clean_resource_secret(self):
if self.cleaned_data['resource_secret']:
secret = self.cleaned_data['resource_secret']
else:
secret = sha224(uuid4().hex.encode()).hexdigest()
return secret
class Meta:
model = Resource
exclude = []
@admin.register(Resource)
class ResourceAdmin(admin.ModelAdmin):
fieldsets = [
[None, {
'fields': ('name', 'owner', 'active',),
}],
[_('Credentials'), {
'fields': ('resource_id', 'resource_secret',),
}],
[_('Permissions'), {
'fields': ('allowed_clients',),
}],
]
form = ResourceForm
list_display = ['name', 'resource_id', 'date_created']
readonly_fields = ['date_created']
search_fields = ['name']
raw_id_fields = ['owner']
@admin.register(Code) @admin.register(Code)
class CodeAdmin(admin.ModelAdmin): class CodeAdmin(admin.ModelAdmin):

View file

@ -0,0 +1,86 @@
import logging
from django.http import JsonResponse
from oidc_provider.lib.errors import TokenIntrospectionError
from oidc_provider.lib.utils.common import get_basic_client_credentials, run_processing_hook
from oidc_provider.models import Token, get_resource_model
Resource = get_resource_model()
logger = logging.getLogger(__name__)
class TokenIntrospectionEndpoint(object):
def __init__(self, request):
self.request = request
self.params = {}
self._extract_params()
def _extract_params(self):
# Introspection only supports POST requests
self.params['token'] = self.request.POST.get('token')
resource_id, resource_secret = get_basic_client_credentials(self.request)
self.params['resource_id'] = resource_id
self.params['resource_secret'] = resource_secret
def validate_params(self):
if not (self.params['resource_id'] and self.params['resource_secret']):
logger.debug('[Introspection] No resource credentials provided')
raise TokenIntrospectionError()
if not self.params['token']:
logger.debug('[Introspection] No token provided')
raise TokenIntrospectionError()
try:
token = Token.objects.get(access_token=self.params['token'])
except Token.DoesNotExist:
logger.debug('[Introspection] Token does not exist: %s', self.params['token'])
raise TokenIntrospectionError()
if token.has_expired():
logger.debug('[Introspection] Token is not valid: %s', self.params['token'])
raise TokenIntrospectionError()
if not token.id_token:
logger.debug('[Introspection] Token not an authentication token: %s', self.params['token'])
raise TokenIntrospectionError()
self.id_token = token.id_token
audience = self.id_token.get('aud')
if not audience:
logger.debug('[Introspection] No audience found for token: %s', self.params['token'])
raise TokenIntrospectionError()
try:
self.resource = Resource.objects.get(
resource_id=self.params['resource_id'],
resource_secret=self.params['resource_secret'],
active=True,
allowed_clients__client_id__contains=audience)
except Resource.DoesNotExist:
logger.debug('[Introspection] No valid resource id and audience: %s, %s',
self.params['resource_id'], audience)
raise TokenIntrospectionError()
def create_response_dic(self):
response_dic = dict((k, self.id_token[k]) for k in ('sub', 'exp', 'iat', 'iss'))
response_dic['active'] = True
response_dic['client_id'] = self.id_token.get('aud')
response_dic['aud'] = self.resource.resource_id
response_dic = run_processing_hook(response_dic, 'OIDC_INTROSPECTION_PROCESSING_HOOK',
resource=self.resource,
id_token=self.id_token)
return response_dic
@classmethod
def response(cls, dic, status=200):
"""
Create and return a response object.
"""
response = JsonResponse(dic, status=status)
response['Cache-Control'] = 'no-store'
response['Pragma'] = 'no-cache'
return response

View file

@ -32,6 +32,15 @@ class UserAuthError(Exception):
} }
class TokenIntrospectionError(Exception):
"""
Specific to the introspection endpoint. This error will be converted
to an "active: false" response, as per the spec.
See https://tools.ietf.org/html/rfc7662
"""
pass
class AuthorizeError(Exception): class AuthorizeError(Exception):
_errors = { _errors = {

View file

@ -1,7 +1,11 @@
from base64 import b64decode
from hashlib import sha224 from hashlib import sha224
from django.http import HttpResponse
from oidc_provider import settings
import django import django
from django.http import HttpResponse from django.http import HttpResponse
import re
from oidc_provider import settings from oidc_provider import settings
@ -12,6 +16,9 @@ else:
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
basic_re = re.compile('^Basic\s(.+)$', re.I)
def redirect(uri): def redirect(uri):
""" """
Custom Response object for redirecting to a Non-HTTP url scheme. Custom Response object for redirecting to a Non-HTTP url scheme.
@ -123,6 +130,17 @@ def default_idtoken_processing_hook(id_token, user):
return id_token return id_token
def default_introspection_processing_hook(introspection_response, resource, id_token):
"""
Hook to customise the returned data from the token introspection endpoint
:param introspection_response:
:param resource:
:param id_token:
:return:
"""
return introspection_response
def get_browser_state_or_default(request): def get_browser_state_or_default(request):
""" """
Determine value to use as session state. Determine value to use as session state.
@ -130,3 +148,38 @@ def get_browser_state_or_default(request):
key = (request.session.session_key or key = (request.session.session_key or
settings.get('OIDC_UNAUTHENTICATED_SESSION_MANAGEMENT_KEY')) settings.get('OIDC_UNAUTHENTICATED_SESSION_MANAGEMENT_KEY'))
return sha224(key.encode('utf-8')).hexdigest() return sha224(key.encode('utf-8')).hexdigest()
def get_basic_client_credentials(request):
"""
Get client credentials using HTTP Basic Authentication method.
Or try getting parameters via POST.
See: http://tools.ietf.org/html/rfc6750#section-2.1
:param request:
:return: tuple of client_id, client_secret
:rtype: tuple
"""
auth_header = request.META.get('HTTP_AUTHORIZATION', '')
result = basic_re.match(auth_header)
if result:
b64_user_pass = result.group(1)
try:
user_pass = b64decode(b64_user_pass).decode('utf-8').split(':', 1)
client_id, client_secret = tuple(user_pass)
except (ValueError, UnicodeDecodeError):
client_id = client_secret = ''
else:
client_id = request.POST.get('client_id')
client_secret = request.POST.get('client_secret')
return client_id, client_secret
def run_processing_hook(subject, hook_settings_name, **kwargs):
processing_hook = settings.get(hook_settings_name)
if isinstance(processing_hook, (list, tuple)):
for hook in processing_hook:
subject = settings.import_from_str(hook)(subject, **kwargs)
else:
subject = settings.import_from_str(processing_hook)(subject, **kwargs)
return subject

View file

@ -9,7 +9,7 @@ from jwkest.jwk import SYMKey
from jwkest.jws import JWS from jwkest.jws import JWS
from jwkest.jwt import JWT from jwkest.jwt import JWT
from oidc_provider.lib.utils.common import get_issuer from oidc_provider.lib.utils.common import get_issuer, run_processing_hook
from oidc_provider.lib.claims import StandardScopeClaims from oidc_provider.lib.claims import StandardScopeClaims
from oidc_provider.models import ( from oidc_provider.models import (
Code, Code,
@ -62,13 +62,7 @@ def create_id_token(token, user, aud, nonce='', at_hash='', request=None, scope=
claims = StandardScopeClaims(token).create_response_dic() claims = StandardScopeClaims(token).create_response_dic()
dic.update(claims) dic.update(claims)
processing_hook = settings.get('OIDC_IDTOKEN_PROCESSING_HOOK') dic = run_processing_hook(dic, 'OIDC_IDTOKEN_PROCESSING_HOOK', user=user)
if isinstance(processing_hook, (list, tuple)):
for hook in processing_hook:
dic = settings.import_from_str(hook)(dic, user=user)
else:
dic = settings.import_from_str(processing_hook)(dic, user=user)
return dic return dic

View file

@ -0,0 +1,35 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10 on 2018-02-05 14:19
from __future__ import unicode_literals
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('oidc_provider', '0023_client_owner'),
]
operations = [
migrations.CreateModel(
name='Resource',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(default='', max_length=100, verbose_name='Name')),
('resource_id', models.CharField(max_length=255, unique=True, verbose_name='Resource ID')),
('resource_secret', models.CharField(max_length=255, verbose_name='Resource Secret')),
('date_created', models.DateField(auto_now_add=True, verbose_name='Date Created')),
('date_updated', models.DateField(auto_now=True, verbose_name='Date Updated')),
('active', models.BooleanField(default=False, verbose_name='Is Active')),
('allowed_clients', models.ManyToManyField(blank=True, help_text='Select which clients can be used to access this resource.', related_name='accessible_resources', to='oidc_provider.Client', verbose_name='Allowed Clients')),
('owner', models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='oidc_resource_set', to=settings.AUTH_USER_MODEL, verbose_name='Owner')),
],
options={
'swappable': 'OIDC_RESOURCE_MODEL',
},
),
]

View file

@ -4,6 +4,7 @@ import binascii
from hashlib import md5, sha256 from hashlib import md5, sha256
import json import json
from django.apps import apps
from django.db import models from django.db import models
from django.utils import timezone from django.utils import timezone
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
@ -128,6 +129,45 @@ class Client(models.Model):
return self.redirect_uris[0] if self.redirect_uris else '' return self.redirect_uris[0] if self.redirect_uris else ''
class AbstractResource(models.Model):
name = models.CharField(max_length=100, default='', verbose_name=_(u'Name'))
owner = models.ForeignKey(settings.AUTH_USER_MODEL,
verbose_name=_(u'Owner'),
blank=True, null=True, default=None,
on_delete=models.SET_NULL,
related_name='oidc_resource_set')
resource_id = models.CharField(max_length=255, unique=True, verbose_name=_(u'Resource ID'))
resource_secret = models.CharField(max_length=255, verbose_name=_(u'Resource Secret'))
date_created = models.DateField(auto_now_add=True, verbose_name=_(u'Date Created'))
date_updated = models.DateField(auto_now=True, verbose_name=_(u'Date Updated'))
active = models.BooleanField(default=False, verbose_name=_(u'Is Active'))
allowed_clients = models.ManyToManyField(Client,
blank=True,
verbose_name=_(u'Allowed Clients'),
related_name='accessible_resources',
help_text=_(u'Select which clients can be used to access this resource.'))
def __str__(self):
return u'{0}'.format(self.name)
def __unicode__(self):
return self.__str__()
class Meta:
verbose_name = _(u'Resource')
verbose_name_plural = _(u'Resources')
abstract = True
class Resource(AbstractResource):
class Meta:
swappable = 'OIDC_RESOURCE_MODEL'
class BaseCodeTokenModel(models.Model): class BaseCodeTokenModel(models.Model):
client = models.ForeignKey(Client, verbose_name=_(u'Client'), on_delete=models.CASCADE) client = models.ForeignKey(Client, verbose_name=_(u'Client'), on_delete=models.CASCADE)
@ -232,3 +272,7 @@ class RSAKey(models.Model):
@property @property
def kid(self): def kid(self):
return u'{0}'.format(md5(self.key.encode('utf-8')).hexdigest() if self.key else '') return u'{0}'.format(md5(self.key.encode('utf-8')).hexdigest() if self.key else '')
def get_resource_model():
return apps.get_model(getattr(settings, 'OIDC_RESOURCE_MODEL', 'oidc_provider.Resource'))

View file

@ -129,6 +129,10 @@ class DefaultSettings(object):
""" """
return 'oidc_provider.lib.utils.common.default_idtoken_processing_hook' return 'oidc_provider.lib.utils.common.default_idtoken_processing_hook'
@property
def OIDC_INTROSPECTION_PROCESSING_HOOK(self):
return 'oidc_provider.lib.utils.common.default_introspection_processing_hook'
@property @property
def OIDC_GRANT_TYPE_PASSWORD_ENABLE(self): def OIDC_GRANT_TYPE_PASSWORD_ENABLE(self):
""" """
@ -152,6 +156,14 @@ class DefaultSettings(object):
'error': 'oidc_provider/error.html' 'error': 'oidc_provider/error.html'
} }
@property
def OIDC_RESOURCE_MODEL(self):
"""
Model w
:return:
"""
return 'oidc_provider.Resource'
default_settings = DefaultSettings() default_settings = DefaultSettings()

View file

@ -11,7 +11,9 @@ from django.contrib.auth.models import User
from oidc_provider.models import ( from oidc_provider.models import (
Client, Client,
Code, Code,
Token) Token, get_resource_model)
Resource = get_resource_model()
FAKE_NONCE = 'cb584e44c43ed6bd0bc2d9c7e242837d' FAKE_NONCE = 'cb584e44c43ed6bd0bc2d9c7e242837d'
@ -63,9 +65,22 @@ def create_fake_client(response_type, is_public=False, require_consent=True):
return client return client
def create_fake_resource(allowed_clients, active=True):
resource = Resource(name='Some API',
resource_id=str(random.randint(1, 999999)).zfill(6),
resource_secret=str(random.randint(1, 999999)).zfill(6),
active=active)
resource.name = 'Some API'
resource.save()
resource.allowed_clients.add(*allowed_clients)
resource.save()
return resource
def create_fake_token(user, scopes, client): def create_fake_token(user, scopes, client):
expires_at = timezone.now() + timezone.timedelta(seconds=60) expires_at = timezone.now() + timezone.timedelta(seconds=60)
token = Token(user=user, client=client, expires_at=expires_at) token = Token(user=user, client=client, expires_at=expires_at, access_token=str(random.randint(1, 999999)).zfill(6))
token.scope = scopes token.scope = scopes
token.save() token.save()
@ -126,3 +141,8 @@ def fake_idtoken_processing_hook2(id_token, user):
id_token['test_idtoken_processing_hook2'] = FAKE_RANDOM_STRING id_token['test_idtoken_processing_hook2'] = FAKE_RANDOM_STRING
id_token['test_idtoken_processing_hook_user_email2'] = user.email id_token['test_idtoken_processing_hook_user_email2'] = user.email
return id_token return id_token
def fake_introspection_processing_hook(response_dict, resource, id_token):
response_dict['test_introspection_processing_hook'] = FAKE_RANDOM_STRING
return response_dict

View file

@ -0,0 +1,120 @@
import time
from mock import patch
from django.utils.encoding import force_text
from oidc_provider.lib.utils.token import create_id_token
try:
from urllib.parse import urlencode
except ImportError:
from urllib import urlencode
from django.core.management import call_command
from django.test import TestCase, RequestFactory, override_settings
from django.core.urlresolvers import reverse
from django.utils import timezone
from oidc_provider.tests.app.utils import (
create_fake_user,
create_fake_client,
create_fake_resource,
create_fake_token,
FAKE_RANDOM_STRING)
from oidc_provider.views import TokenIntrospectionView
class IntrospectionTestCase(TestCase):
def setUp(self):
call_command('creatersakey')
self.factory = RequestFactory()
self.user = create_fake_user()
self.client = create_fake_client(response_type='id_token token')
self.resource = create_fake_resource(allowed_clients=[self.client])
self.scopes = ['openid', 'profile']
self.token = create_fake_token(self.user, self.scopes, self.client)
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.user, self.client.client_id)
self.token.save()
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_no_allowed_clients_returns_inactive(self):
self.resource.allowed_clients.clear()
self.resource.save()
response = self._make_request()
self._assert_inactive(response)
def test_resource_inactive_returns_inactive(self):
self.resource.active = False
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.assertEqual(response.status_code, 200)
self.assertJSONEqual(force_text(response.content), {
'active': True,
'aud': self.resource.resource_id,
'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',
})
@override_settings(
OIDC_INTROSPECTION_PROCESSING_HOOK='oidc_provider.tests.app.utils.fake_introspection_processing_hook')
def test_custom_introspection_hook_called_on_valid_request(self):
response = self._make_request()
self.assertEqual(response.status_code, 200)
self.assertJSONEqual(force_text(response.content), {
'active': True,
'aud': self.resource.resource_id,
'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',
'test_introspection_processing_hook': FAKE_RANDOM_STRING
})
def _assert_inactive(self, response):
self.assertEqual(response.status_code, 200)
self.assertJSONEqual(force_text(response.content), {'active': False})
def _make_request(self, **kwargs):
url = reverse('oidc_provider:token-introspection')
data = {
'client_id': kwargs.get('client_id', self.resource.resource_id),
'client_secret': kwargs.get('client_secret', self.resource.resource_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)

View file

@ -17,6 +17,7 @@ urlpatterns = [
url(r'^end-session/?$', views.EndSessionView.as_view(), name='end-session'), url(r'^end-session/?$', views.EndSessionView.as_view(), name='end-session'),
url(r'^\.well-known/openid-configuration/?$', views.ProviderInfoView.as_view(), url(r'^\.well-known/openid-configuration/?$', views.ProviderInfoView.as_view(),
name='provider-info'), name='provider-info'),
url(r'^introspect/?$', views.TokenIntrospectionView.as_view(), name='token-introspection'),
url(r'^jwks/?$', views.JwksView.as_view(), name='jwks'), url(r'^jwks/?$', views.JwksView.as_view(), name='jwks'),
] ]

View file

@ -1,5 +1,6 @@
import logging import logging
from oidc_provider.lib.endpoints.introspection import TokenIntrospectionEndpoint
try: try:
from urllib import urlencode from urllib import urlencode
from urlparse import urlsplit, parse_qs, urlunsplit from urlparse import urlsplit, parse_qs, urlunsplit
@ -34,7 +35,8 @@ from oidc_provider.lib.errors import (
ClientIdError, ClientIdError,
RedirectUriError, RedirectUriError,
TokenError, TokenError,
UserAuthError) UserAuthError,
TokenIntrospectionError)
from oidc_provider.lib.utils.common import ( from oidc_provider.lib.utils.common import (
redirect, redirect,
get_site_url, get_site_url,
@ -50,6 +52,25 @@ from oidc_provider.models import (
from oidc_provider import settings from oidc_provider import settings
from oidc_provider import signals from oidc_provider import signals
try:
from urllib import urlencode
from urlparse import urlsplit, parse_qs, urlunsplit
except ImportError:
from urllib.parse import urlsplit, parse_qs, urlunsplit, urlencode
from Cryptodome.PublicKey import RSA
from django.contrib.auth.views import (
redirect_to_login,
logout,
)
import django
if django.VERSION >= (1, 11):
from django.urls import reverse
else:
from django.core.urlresolvers import reverse
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
OIDC_TEMPLATES = settings.get('OIDC_TEMPLATES') OIDC_TEMPLATES = settings.get('OIDC_TEMPLATES')
@ -230,10 +251,10 @@ class TokenView(View):
@protected_resource_view(['openid']) @protected_resource_view(['openid'])
def userinfo(request, *args, **kwargs): def userinfo(request, *args, **kwargs):
""" """
Create a diccionary with all the requested claims about the End-User. Create a dictionary with all the requested claims about the End-User.
See: http://openid.net/specs/openid-connect-core-1_0.html#UserInfoResponse See: http://openid.net/specs/openid-connect-core-1_0.html#UserInfoResponse
Return a diccionary. Return a dictionary.
""" """
token = kwargs['token'] token = kwargs['token']
@ -267,6 +288,7 @@ class ProviderInfoView(View):
dic['token_endpoint'] = site_url + reverse('oidc_provider:token') dic['token_endpoint'] = site_url + reverse('oidc_provider:token')
dic['userinfo_endpoint'] = site_url + reverse('oidc_provider:userinfo') dic['userinfo_endpoint'] = site_url + reverse('oidc_provider:userinfo')
dic['end_session_endpoint'] = site_url + reverse('oidc_provider:end-session') dic['end_session_endpoint'] = site_url + reverse('oidc_provider:end-session')
dic['introspection_endpoint'] = site_url + reverse('oidc_provider:token-introspection')
types_supported = [x[0] for x in RESPONSE_TYPE_CHOICES] types_supported = [x[0] for x in RESPONSE_TYPE_CHOICES]
dic['response_types_supported'] = types_supported dic['response_types_supported'] = types_supported
@ -356,3 +378,15 @@ class CheckSessionIframeView(View):
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):
return render(request, 'oidc_provider/check_session_iframe.html', kwargs) return render(request, 'oidc_provider/check_session_iframe.html', kwargs)
class TokenIntrospectionView(View):
def post(self, request, *args, **kwargs):
introspection = TokenIntrospectionEndpoint(request)
try:
introspection.validate_params()
dic = introspection.create_response_dic()
return TokenIntrospectionEndpoint.response(dic)
except TokenIntrospectionError:
return TokenIntrospectionEndpoint.response({'active': False})