Skip to content

Instantly share code, notes, and snippets.

@ekkinox
Created October 16, 2021 18:30
Show Gist options
  • Save ekkinox/fdc90a51f73aed92f004becd81ddc5fa to your computer and use it in GitHub Desktop.
Save ekkinox/fdc90a51f73aed92f004becd81ddc5fa to your computer and use it in GitHub Desktop.
<?php
namespace App\Security;
use OAT\Library\Lti1p3Core\Message\Launch\Validator\Result\LaunchValidationResultInterface;
use Symfony\Component\Security\Http\Authenticator\Passport\Badge\BadgeInterface;
class LtiBadge implements BadgeInterface
{
/** @var LaunchValidationResultInterface */
private $validationResult;
public function __construct(LaunchValidationResultInterface $validationResult)
{
$this->validationResult = $validationResult;
}
public function getValidationResult(): LaunchValidationResultInterface
{
return $this->validationResult;
}
public function isResolved(): bool
{
return !$this->validationResult->hasError();
}
}
<?php
namespace App\Security;
use OAT\Bundle\Lti1p3Bundle\Security\Authentication\Token\Message\LtiToolMessageSecurityToken;
use OAT\Library\Lti1p3Core\Message\Launch\Validator\Tool\ToolLaunchValidatorInterface;
use Symfony\Bridge\PsrHttpMessage\HttpMessageFactoryInterface;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Exception\AuthenticationException;
use Symfony\Component\Security\Core\Exception\CustomUserMessageAuthenticationException;
use Symfony\Component\Security\Core\Exception\LogicException;
use Symfony\Component\Security\Http\Authenticator\AbstractAuthenticator;
use Symfony\Component\Security\Http\Authenticator\Passport\PassportInterface;
class LtiMessageAuthenticator extends AbstractAuthenticator
{
/** @var ToolLaunchValidatorInterface */
private $validator;
/** @var HttpMessageFactoryInterface */
private $factory;
/** string[] */
private $types;
public function __construct(ToolLaunchValidatorInterface $validator, HttpMessageFactoryInterface $factory, array $types = [])
{
$this->validator = $validator;
$this->factory = $factory;
$this->types = $types;
}
public function supports(Request $request): ?bool
{
return null !== $request->get('id_token');
}
public function authenticate(Request $request): PassportInterface
{
$validationResult = $this->validator->validatePlatformOriginatingLaunch(
$this->factory->createRequest($request)
);
if ($validationResult->hasError()) {
throw new CustomUserMessageAuthenticationException($validationResult->getError());
}
$messageType = $validationResult->getPayload()->getMessageType();
if (!empty($this->types) && !in_array($messageType, $this->types)) {
throw new BadRequestHttpException(sprintf('Invalid LTI message type %s', $messageType));
}
return new LtiPassport(new LtiBadge($validationResult));
}
public function createAuthenticatedToken(PassportInterface $passport, string $firewallName): TokenInterface
{
if (!$passport instanceof LtiPassport) {
throw new LogicException(sprintf('Provided passport must be a %s instance', LtiPassport::class));
}
return new LtiToolMessageSecurityToken($passport->getLtiBadge()->getValidationResult());
}
public function onAuthenticationSuccess(Request $request, TokenInterface $token, string $firewallName): ?Response
{
return null;
}
public function onAuthenticationFailure(Request $request, AuthenticationException $exception): ?Response
{
$data = [
'message' => strtr($exception->getMessageKey(), $exception->getMessageData())
];
return new JsonResponse($data, Response::HTTP_UNAUTHORIZED);
}
}
<?php
namespace App\Security;
use OAT\Library\Lti1p3Core\Exception\LtiException;
use OAT\Library\Lti1p3Core\Message\Launch\Validator\Tool\ToolLaunchValidatorInterface;
use Symfony\Bridge\PsrHttpMessage\HttpMessageFactoryInterface;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Exception\AuthenticationException;
use Symfony\Component\Security\Core\Exception\CustomUserMessageAuthenticationException;
use Symfony\Component\Security\Http\Authenticator\AbstractAuthenticator;
use Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge;
use Symfony\Component\Security\Http\Authenticator\Passport\PassportInterface;
use Symfony\Component\Security\Http\Authenticator\Passport\PassportTrait;
use Symfony\Component\Security\Http\Authenticator\Passport\SelfValidatingPassport;
class LtiPassport implements PassportInterface
{
use PassportTrait;
public function __construct(LtiBadge $ltiBadge)
{
$this->addBadge($ltiBadge);
}
public function getLtiBadge(): ?LtiBadge
{
return $this->getBadge(LtiBadge::class);
}
}
<?php
/**
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public License
* as published by the Free Software Foundation; under version 2
* of the License (non-upgradable).
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* Copyright (c) 2020 (original work) Open Assessment Technologies SA;
*/
declare(strict_types=1);
namespace OAT\Bundle\Lti1p3Bundle\DependencyInjection\Security\Factory\Message;
use App\Security\LtiMessageAuthenticator;
use OAT\Bundle\Lti1p3Bundle\Security\Authentication\Provider\Message\LtiToolMessageAuthenticationProvider;
use OAT\Bundle\Lti1p3Bundle\Security\Firewall\Message\LtiToolMessageAuthenticationListener;
use OAT\Library\Lti1p3Core\Message\Launch\Validator\Tool\ToolLaunchValidatorInterface;
use Symfony\Bridge\PsrHttpMessage\HttpMessageFactoryInterface;
use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\AuthenticatorFactoryInterface;
use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\SecurityFactoryInterface;
use Symfony\Component\Config\Definition\Builder\NodeDefinition;
use Symfony\Component\DependencyInjection\ChildDefinition;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Reference;
class LtiToolMessageSecurityFactory implements SecurityFactoryInterface, AuthenticatorFactoryInterface
{
public function getPosition(): string
{
return 'pre_auth';
}
public function getKey(): string
{
return 'lti1p3_message_tool';
}
public function create(
ContainerBuilder $container,
$id,
$config,
$userProvider,
$defaultEntryPoint = null
) {
$providerId = sprintf('security.authentication.provider.%s.%s', $this->getKey(), $id);
$providerDefinition = new Definition(LtiToolMessageAuthenticationProvider::class);
$providerDefinition
->setShared(false)
->setArguments(
[
new Reference(ToolLaunchValidatorInterface::class),
$id,
$config['types'] ?? []
]
);
$container->setDefinition($providerId, $providerDefinition);
$listenerId = sprintf('security.authentication.listener.%s.%s', $this->getKey(), $id);
$container->setDefinition($listenerId, new ChildDefinition(LtiToolMessageAuthenticationListener::class));
return [$providerId, $listenerId, $defaultEntryPoint];
}
public function createAuthenticator(ContainerBuilder $container, string $firewallName, array $config, string $userProviderId): string
{
$authenticatorId = sprintf('security.authenticator.%s.%s', $this->getKey(), $firewallName);
$authenticatorDefinition = new Definition(LtiMessageAuthenticator::class);
$authenticatorDefinition
->setShared(false)
->setArguments(
[
new Reference(ToolLaunchValidatorInterface::class),
new Reference(HttpMessageFactoryInterface::class),
$config['types'] ?? []
]
);
$container->setDefinition($authenticatorId, $authenticatorDefinition);
return $authenticatorId;
}
public function addConfiguration(NodeDefinition $node): void
{
$node->children()->arrayNode('types')->scalarPrototype()->end();
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment