Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
186 changes: 186 additions & 0 deletions 186 Controller/IntrospectionController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
<?php

declare(strict_types=1);

/*
* This file is part of the FOSOAuthServerBundle package.
*
* (c) FriendsOfSymfony <http://friendsofsymfony.github.com/>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace FOS\OAuthServerBundle\Controller;

use FOS\OAuthServerBundle\Form\Model\Introspect;
use FOS\OAuthServerBundle\Form\Type\IntrospectionFormType;
use FOS\OAuthServerBundle\Model\AccessTokenInterface;
use FOS\OAuthServerBundle\Model\RefreshTokenInterface;
use FOS\OAuthServerBundle\Model\TokenInterface;
use FOS\OAuthServerBundle\Model\TokenManagerInterface;
use FOS\OAuthServerBundle\Security\Authentication\Token\OAuthToken;
use Symfony\Component\Form\FormFactory;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
use Symfony\Component\Security\Core\Exception\AccessDeniedException;

class IntrospectionController
{
/**
* @var TokenStorageInterface
*/
private $tokenStorage;

/**
* @var TokenManagerInterface
*/
private $accessTokenManager;

/**
* @var TokenManagerInterface
*/
private $refreshTokenManager;

/**
* @var FormFactory
*/
private $formFactory;

/**
* @var array
*/
private $allowedIntrospectionClients;

public function __construct(
TokenStorageInterface $tokenStorage,
TokenManagerInterface $accessTokenManager,
TokenManagerInterface $refreshTokenManager,
FormFactory $formFactory,
array $allowedIntrospectionClients
) {
$this->tokenStorage = $tokenStorage;
$this->accessTokenManager = $accessTokenManager;
$this->refreshTokenManager = $refreshTokenManager;
$this->formFactory = $formFactory;
$this->allowedIntrospectionClients = $allowedIntrospectionClients;
}

public function introspectAction(Request $request): JsonResponse
{
$this->denyAccessIfNotAuthorizedClient();

$token = $this->getToken($request);

$isActive = $token && !$token->hasExpired();

if (!$isActive) {
return new JsonResponse([
'active' => false,
]);
}

return new JsonResponse([
'active' => true,
'scope' => $token->getScope(),
'client_id' => $token->getClientId(),
'username' => $this->getUsername($token),
'token_type' => $this->getTokenType($token),
'exp' => $token->getExpiresAt(),
]);
}

/**
* Check that the caller has a token generated by an allowed client
*/
private function denyAccessIfNotAuthorizedClient(): void
{
$clientToken = $this->tokenStorage->getToken();

if (!$clientToken instanceof OAuthToken) {
throw new AccessDeniedException('The introspect endpoint must be behind a secure firewall.');
}

$callerToken = $this->accessTokenManager->findTokenByToken($clientToken->getToken());

if (!$callerToken) {
throw new AccessDeniedException('The access token must have a valid token.');
}

if (!in_array($callerToken->getClientId(), $this->allowedIntrospectionClients)) {
throw new AccessDeniedException('This access token is not autorised to do introspection.');
}
}

/**
* @return TokenInterface|null
*/
private function getToken(Request $request)
{
$formData = $this->processIntrospectionForm($request);
$tokenString = $formData->token;
$tokenTypeHint = $formData->token_type_hint;

$tokenManagerList = [];
if (!$tokenTypeHint || 'access_token' === $tokenTypeHint) {
$tokenManagerList[] = $this->accessTokenManager;
}
if (!$tokenTypeHint || 'refresh_token' === $tokenTypeHint) {
$tokenManagerList[] = $this->refreshTokenManager;
}

foreach ($tokenManagerList as $tokenManager) {
$token = $tokenManager->findTokenByToken($tokenString);

if ($token) {
return $token;
}
}
}

/**
* @return string|null
*/
private function getTokenType(TokenInterface $token)
{
if ($token instanceof AccessTokenInterface) {
return 'access_token';
} elseif ($token instanceof RefreshTokenInterface) {
return 'refresh_token';
}

return null;
}

/**
* @return string|null
*/
private function getUsername(TokenInterface $token)
{
$user = $token->getUser();
if (!$user) {
return null;
}

return $user->getUserName();
}

private function processIntrospectionForm(Request $request): Introspect
{
$formData = new Introspect();
$form = $this->formFactory->create(IntrospectionFormType::class, $formData);
$form->handleRequest($request);

if (!$form->isSubmitted() || !$form->isValid()) {
$errors = $form->getErrors();
if (count($errors) > 0) {
throw new BadRequestHttpException((string) $errors);
} else {
throw new BadRequestHttpException('Introspection endpoint needs to have at least a "token" form parameter');
}
}
return $form->getData();
}
}
36 changes: 36 additions & 0 deletions 36 DependencyInjection/Configuration.php
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,8 @@ public function getConfigTreeBuilder()

$this->addAuthorizeSection($rootNode);
$this->addServiceSection($rootNode);
$this->addTemplateSection($rootNode);
$this->addIntrospectionSection($rootNode);

return $treeBuilder;
}
Expand Down Expand Up @@ -134,4 +136,38 @@ private function addServiceSection(ArrayNodeDefinition $node)
->end()
;
}

private function addTemplateSection(ArrayNodeDefinition $node)
{
$node
->children()
->arrayNode('template')
->addDefaultsIfNotSet()
->children()
->scalarNode('engine')->defaultValue('twig')->end()
->end()
->end()
->end()
;
}

private function addIntrospectionSection(ArrayNodeDefinition $node)
{
$node
->addDefaultsIfNotSet()
->children()
->arrayNode('introspection')
->addDefaultsIfNotSet()
->children()
->arrayNode('allowed_clients')
->useAttributeAsKey('key')
->treatNullLike([])
->prototype('variable')->end()
->end()
->end()
->end()
->end()
->end()
;
}
}
10 changes: 10 additions & 0 deletions 10 DependencyInjection/FOSOAuthServerExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,8 @@ public function load(array $configs, ContainerBuilder $container)
$authorizeFormDefinition = $container->getDefinition('fos_oauth_server.authorize.form');
$authorizeFormDefinition->setFactory([new Reference('form.factory'), 'createNamed']);
}

$this->loadIntrospection($config, $container, $loader);
}

/**
Expand Down Expand Up @@ -140,6 +142,14 @@ protected function remapParametersNamespaces(array $config, ContainerBuilder $co
}
}

protected function loadIntrospection(array $config, ContainerBuilder $container, XmlFileLoader $loader)
{
$loader->load('introspection.xml');

$allowedClients = $config['introspection']['allowed_clients'];
$container->setParameter('fos_oauth_server.introspection.allowed_clients', $allowedClients);
}

protected function loadAuthorize(array $config, ContainerBuilder $container, XmlFileLoader $loader)
{
$loader->load('authorize.xml');
Expand Down
30 changes: 30 additions & 0 deletions 30 Form/Model/Introspect.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<?php

declare(strict_types=1);

/*
* This file is part of the FOSOAuthServerBundle package.
*
* (c) FriendsOfSymfony <http://friendsofsymfony.github.com/>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace FOS\OAuthServerBundle\Form\Model;

use Symfony\Component\Validator\Constraints as Assert;

class Introspect
{
/**
* @var string
* @Assert\NotBlank()
*/
public $token;

/**
* @var string
*/
public $token_type_hint;
}
54 changes: 54 additions & 0 deletions 54 Form/Type/IntrospectionFormType.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
<?php

declare(strict_types=1);

/*
* This file is part of the FOSOAuthServerBundle package.
*
* (c) FriendsOfSymfony <http://friendsofsymfony.github.com/>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace FOS\OAuthServerBundle\Form\Type;

use FOS\OAuthServerBundle\Form\Model\Introspect;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\Extension\Core\Type\HiddenType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;

class IntrospectionFormType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('token', HiddenType::class);
$builder->add('token_type_hint', ChoiceType::class, [ // can be `access_token`, `refresh_token` See https://tools.ietf.org/html/rfc7009#section-4.1.2
'choices' => [
'access_token' => 'access_token',
'refresh_token' => 'refresh_token',
]
]);
}

/**
* {@inheritdoc}
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => Introspect::class,
'csrf_protection' => false,
]);
}

/**
* @return string
*/
public function getBlockPrefix()
{
return '';
}
}
17 changes: 17 additions & 0 deletions 17 Resources/config/introspection.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8" ?>

<container xmlns="http://symfony.com/schema/dic/services"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">

<services>
<service id="fos_oauth_server.controller.introspection" class="FOS\OAuthServerBundle\Controller\IntrospectionController" public="true">
<argument type="service" id="security.token_storage" />
<argument type="service" id="fos_oauth_server.access_token_manager" />
<argument type="service" id="fos_oauth_server.refresh_token_manager" />
<argument type="service" id="form.factory" />
<argument>%fos_oauth_server.introspection.allowed_clients%</argument>
</service>
</services>

</container>
12 changes: 12 additions & 0 deletions 12 Resources/config/routing/introspection.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8" ?>

<routes xmlns="http://symfony.com/schema/routing"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/routing http://symfony.com/schema/routing/routing-1.0.xsd">

<route id="fos_oauth_server_introspection" path="/oauth/v2/introspect" methods="POST">
<default key="_controller">fos_oauth_server.controller.introspection:introspectAction</default>
</route>

</routes>

2 changes: 2 additions & 0 deletions 2 Resources/doc/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -624,3 +624,5 @@ The `authorize` endpoint is at `/oauth/v2/auth` by default (see `Resources/confi
[Adding Grant Extensions](adding_grant_extensions.md)

[Custom DB Driver](custom_db_driver.md)

[Introspection endpoint](introspection_endpoint.md)
Loading
Morty Proxy This is a proxified and sanitized view of the page, visit original site.