@ -5,10 +5,14 @@
namespace Chamilo\PluginBundle\XApi\Lrs;
use Chamilo\PluginBundle\Entity\XApi\LrsAuth;
use Database;
use Symfony\Component\HttpFoundation\Request as HttpRequest;
use Symfony\Component\HttpFoundation\Response as HttpResponse;
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
use Symfony\Component\HttpKernel\Exception\HttpException;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Xabbuh\XApi\Common\Exception\AccessDeniedException;
use Xabbuh\XApi\Common\Exception\XApiException;
/**
* Class LrsRequest.
@ -32,56 +36,105 @@ class LrsRequest
public function send()
{
$this->validAuth();
try {
$this->alternateRequestSyntax();
$controllerName = $this->getControllerName();
$methodName = $this->getMethodName();
$response = $this->generateResponse($controllerName, $methodName);
} catch (XApiException $xApiException) {
$response = HttpResponse::create('', HttpResponse::HTTP_BAD_REQUEST);
} catch (HttpException $httpException) {
$response = HttpResponse::create(
$httpException->getMessage(),
$httpException->getStatusCode()
);
} catch (\Exception $exception) {
$response = HttpResponse::create($exception->getMessage(), HttpResponse::HTTP_BAD_REQUEST);
}
$version = $this->request->headers->get('X-Experience-API-Version');
$response->headers->set('X-Experience-API-Version', '1.0.3 ');
if (null === $version) {
throw new BadRequestHttpException('The "X-Experience-API-Version" header is required.');
$response->send();
}
/**
* @throws \Xabbuh\XApi\Common\Exception\AccessDeniedException
*/
private function validateAuth(): bool
{
if (!$this->request->headers->has('Authorization')) {
throw new AccessDeniedException();
}
if (!$this->isValidVersion($version)) {
throw new BadRequestHttpException("The xAPI version \"$version\" is not supported.");
$authHeader = $this->request->headers->get('Authorization');
$parts = explode('Basic ', $authHeader, 2);
if (empty($parts[1])) {
throw new AccessDeniedException();
}
$controllerName = $this->getControllerName();
$methodName = $this->getMethodName();
$authDecoded = base64_decode($parts[1]);
if ($controllerName
& & class_exists($controllerName)
& & method_exists($controllerName, $methodName)
) {
/** @var HttpResponse $response */
$response = call_user_func([new $controllerName(), $methodName]);
} else {
$response = HttpResponse::create('Not Found', HttpResponse::HTTP_NOT_FOUND);
$parts = explode(':', $authDecoded, 2);
if (empty($parts) || count($parts) !== 2) {
throw new AccessDeniedException();
}
$response->headers->set('X-Experience-API-Version', '1.0.3') ;
list($username, $password) = $parts;
$response->send();
$auth = Database::getManager()
->getRepository(LrsAuth::class)
->findOneBy(
['username' => $username, 'password' => $password, 'enabled' => true]
);
if (null == $auth) {
throw new AccessDeniedException();
}
return true;
}
/**
* @return string|null
*/
private function getControllerName()
private function validateVersion()
{
$version = $this->request->headers->get('X-Experience-API-Version');
if (null === $version) {
throw new BadRequestHttpException('The "X-Experience-API-Version" header is required.');
}
if (preg_match('/^1\.0(?:\.\d+)?$/', $version)) {
if ('1.0' === $version) {
$this->request->headers->set('X-Experience-API-Version', '1.0.0');
}
return;
}
throw new BadRequestHttpException("The xAPI version \"$version\" is not supported.");
}
private function getControllerName(): ?string
{
$segments = explode('/', $this->request->getPathInfo());
$segments = array_filter($segments);
$segments = array_values($segments);
if (empty($segments[1])) {
return null;
if (empty($segments)) {
throw new BadRequestHttpException('Bad request') ;
}
$controllerName = ucfirst($segments[1]).'Controller';
$segments = array_map('ucfirst', $segments);
$controllerName = implode('', $segments).'Controller';
return "Chamilo\\PluginBundle\\XApi\Lrs\\$controllerName";
}
/**
* @return string
*/
private function getMethodName()
private function getMethodName(): string
{
$method = $this->request->getMethod();
@ -89,60 +142,80 @@ class LrsRequest
}
/**
* @param string $version
*
* @return bool
* @throws \Xabbuh\XApi\Common\Exception\AccessDeniedException
*/
private function isValidVersion($version)
private function generateResponse(string $controllerName, string $methodName): HttpResponse
{
if (preg_match('/^1\.0(?:\.\d+)?$/', $version)) {
if ('1.0' === $version) {
$this->request->headers->set('X-Experience-API-Version', '1.0.0');
}
if (!class_exists($controllerName)
|| !method_exists($controllerName, $methodName)
) {
throw new NotFoundHttpException();
}
return true;
if ($controllerName !== AboutController::class) {
$this->validateAuth();
$this->validateVersion();
}
return false;
/** @var HttpResponse $response */
$response = call_user_func(
[
new $controllerName($this->request),
$methodName,
]
);
return $response;
}
/**
* @return bool
*/
private function validAuth()
private function alternateRequestSyntax()
{
if (!$this->request->headers->has('Authorization')) {
throw new AccessDeniedHttpException() ;
if ('POST' !== $this->request->getMethod()) {
return;
}
$authHeader = $this->request->headers->get('Authorization');
$parts = explode('Basic ', $authHeader, 2);
if (null === $method = $this->request->query->get('method')) {
return;
}
if (empty($parts[1]) ) {
throw new AccessDeniedHttpException( );
if ($this->request->query->count() > 1 ) {
throw new BadRequestHttpException('Including other query parameters than "method" is not allowed. You have to send them as POST parameters inside the request body.' );
}
$authDecoded = base64_decode($parts[1]);
$this->request->setMethod($method);
$this->request->query->remove('method');
$parts = explode(':', $authDecoded, 2);
if (null !== $content = $this->request->request->get('content')) {
$this->request->request->remove('content');
if (empty($parts) || count($parts) !== 2) {
throw new AccessDeniedHttpException();
$this->request->initialize(
$this->request->query->all(),
$this->request->request->all(),
$this->request->attributes->all(),
$this->request->cookies->all(),
$this->request->files->all(),
$this->request->server->all(),
$content
);
}
list($username, $password) = $parts;
$auth = \Database::getManager()
->getRepository(LrsAuth::class)
->findOneBy(
['username' => $username, 'password' => $password, 'enabled' => true]
);
$headerNames = [
'Authorization',
'X-Experience-API-Version',
'Content-Type',
'Content-Length',
'If-Match',
'If-None-Match',
];
foreach ($this->request->request as $key => $value) {
if (in_array($key, $headerNames, true)) {
$this->request->headers->set($key, $value);
} else {
$this->request->query->set($key, $value);
}
if (null == $auth) {
throw new AccessDeniedHttpException();
$this->request->request->remove($key);
}
return true;
}
}