Skip to content

Commit

Permalink
security #11832 n/a (fabpot)
Browse files Browse the repository at this point in the history
This PR was merged into the 2.3 branch.

Discussion
----------

n/a

n/a

Commits
-------

f38536a [WebProfiler] replaced the import/export feature from the web interface to a CLI tool
  • Loading branch information
fabpot committed Sep 3, 2014
2 parents 439c5a3 + f38536a commit b60b5d4
Show file tree
Hide file tree
Showing 8 changed files with 287 additions and 83 deletions.
75 changes: 75 additions & 0 deletions src/Symfony/Bundle/WebProfilerBundle/Command/ExportCommand.php
@@ -0,0 +1,75 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Symfony\Bundle\WebProfilerBundle\Command;

use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\HttpKernel\Profiler\Profiler;

/**
* Exports a profile.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class ExportCommand extends Command
{
private $profiler;

public function __construct(Profiler $profiler = null)
{
$this->profiler = $profiler;

parent::__construct();
}

/**
* {@inheritdoc}
*/
public function isEnabled()
{
if (null === $this->profiler) {
return false;
}

return parent::isEnabled();
}

protected function configure()
{
$this
->setName('profiler:export')
->setDescription('Exports a profile')
->setDefinition(array(
new InputArgument('token', InputArgument::REQUIRED, 'The profile token'),
))
->setHelp(<<<EOF
The <info>%command.name%</info> command exports a profile to the standard output:
<info>php %command.full_name% profile_token</info>
EOF
)
;
}

protected function execute(InputInterface $input, OutputInterface $output)
{
$token = $input->getArgument('token');

if (!$profile = $this->profiler->loadProfile($token)) {
throw new \LogicException(sprintf('Profile with token "%s" does not exist.', $token));
}

$output->writeln($this->profiler->export($profile), OutputInterface::OUTPUT_RAW);
}
}
90 changes: 90 additions & 0 deletions src/Symfony/Bundle/WebProfilerBundle/Command/ImportCommand.php
@@ -0,0 +1,90 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Symfony\Bundle\WebProfilerBundle\Command;

use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\HttpKernel\Profiler\Profiler;

/**
* Imports a profile.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class ImportCommand extends Command
{
private $profiler;

public function __construct(Profiler $profiler = null)
{
$this->profiler = $profiler;

parent::__construct();
}

/**
* {@inheritdoc}
*/
public function isEnabled()
{
if (null === $this->profiler) {
return false;
}

return parent::isEnabled();
}

protected function configure()
{
$this
->setName('profiler:import')
->setDescription('Imports a profile')
->setDefinition(array(
new InputArgument('filename', InputArgument::OPTIONAL, 'The profile path'),
))
->setHelp(<<<EOF
The <info>%command.name%</info> command imports a profile:
<info>php %command.full_name% profile_filepath</info>
You can also pipe the profile via STDIN:
<info>cat profile_file | php %command.full_name%</info>
EOF
)
;
}

protected function execute(InputInterface $input, OutputInterface $output)
{
$data = '';
if ($input->getArgument('filename')) {
$data = file_get_contents($input->getArgument('filename'));
} else {
if (0 !== ftell(STDIN)) {
throw new \RuntimeException('Please provide a filename or pipe the profile to STDIN.');
}

while (!feof(STDIN)) {
$data .= fread(STDIN, 1024);
}
}

if (!$profile = $this->profiler->import($data)) {
throw new \LogicException('The profile already exists in the database.');
}

$output->writeln(sprintf('Profile "%s" has been successfully imported.', $profile->getToken()));
}
}
Expand Up @@ -111,33 +111,6 @@ public function panelAction(Request $request, $token)
)), 200, array('Content-Type' => 'text/html'));
}

/**
* Exports data for a given token.
*
* @param string $token The profiler token
*
* @return Response A Response instance
*
* @throws NotFoundHttpException
*/
public function exportAction($token)
{
if (null === $this->profiler) {
throw new NotFoundHttpException('The profiler must be enabled.');
}

$this->profiler->disable();

if (!$profile = $this->profiler->loadProfile($token)) {
throw new NotFoundHttpException(sprintf('Token "%s" does not exist.', $token));
}

return new Response($this->profiler->export($profile), 200, array(
'Content-Type' => 'text/plain',
'Content-Disposition' => 'attachment; filename= '.$token.'.txt',
));
}

/**
* Purges all tokens.
*
Expand All @@ -157,36 +130,6 @@ public function purgeAction()
return new RedirectResponse($this->generator->generate('_profiler_info', array('about' => 'purge')), 302, array('Content-Type' => 'text/html'));
}

/**
* Imports token data.
*
* @param Request $request The current HTTP Request
*
* @return Response A Response instance
*
* @throws NotFoundHttpException
*/
public function importAction(Request $request)
{
if (null === $this->profiler) {
throw new NotFoundHttpException('The profiler must be enabled.');
}

$this->profiler->disable();

$file = $request->files->get('file');

if (empty($file) || !$file->isValid()) {
return new RedirectResponse($this->generator->generate('_profiler_info', array('about' => 'upload_error')), 302, array('Content-Type' => 'text/html'));
}

if (!$profile = $this->profiler->import(file_get_contents($file->getPathname()))) {
return new RedirectResponse($this->generator->generate('_profiler_info', array('about' => 'already_exists')), 302, array('Content-Type' => 'text/html'));
}

return new RedirectResponse($this->generator->generate('_profiler', array('token' => $profile->getToken())), 302, array('Content-Type' => 'text/html'));
}

/**
* Displays information page.
*
Expand Down
23 changes: 23 additions & 0 deletions src/Symfony/Bundle/WebProfilerBundle/Resources/config/commands.xml
@@ -0,0 +1,23 @@
<?xml version="1.0" ?>

<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">

<parameters>
<parameter key="web_profiler.command.import.class">Symfony\Bundle\WebProfilerBundle\Command\ImportCommand</parameter>
<parameter key="web_profiler.command.export.class">Symfony\Bundle\WebProfilerBundle\Command\ExportCommand</parameter>
</parameters>

<services>
<service id="web_profiler.command.import" class="%web_profiler.command.import.class%">
<argument type="service" id="profiler" on-invalid="null" />
<tag name="console.command" />
</service>

<service id="web_profiler.command.export" class="%web_profiler.command.export.class%">
<argument type="service" id="profiler" on-invalid="null" />
<tag name="console.command" />
</service>
</services>
</container>
@@ -1,27 +1,10 @@
<div class="search import clearfix" id="adminBar">
<h3>
<img style="margin: 0 5px 0 0; vertical-align: middle; height: 16px" width="16" height="16" alt="Import" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAADo0lEQVR42u2XS0hUURjHD5njA1oYbXQ2MqCmIu2iEEISUREEEURxFB8ovt+DEsLgaxBRQQeUxnQ0ZRYSQasgiDaFqxAy2jUtCjdCoEjFwHj6/+F+dbvN6PQAN37wm++c7/z/35x7uPcOo7TW58rFBs59A7GGQ51XBAIBlZmZuYOhE1zm/A/4PxvY3NwMO53OYEJCgp+nccqXXQc94D54boAxalyLNayNtra2NJmbmzvOyMj4cRqoKYK4AsZzc3Nft7e3f5qZmTnCpk8Ix6xxjRpDGzmkUU5Ozuu2trZP09PTR+vr6ycbGxtaWFtbC9fU1AQTExPdmNNzLSUlZXt4ePhANNGghlp6lDWkkcvlOsCX6LNYXV0N8BTS0tK2cDJfWIsFaumhV0lIIxzXl5WVFX0aPp8vhDwJbMnJyc6JiYkji8YP7oI4YowfmDX00KskOHG73UfLy8vahB/cBXFSW1pa2kPOA7RdqqysfGtaCyOXA2VGgmvUiJ5e9lD8qKioeOv1ejVZXFwMI5eLEWOFWgh5Etg4J0lJSTdwYiHxLSwseFi3Yg5qRE8veyh+TE1Nhebn5zWZnZ31mE2okTxmM6WlpS7xeDyeQ2Qb61bMQQ214mMPVVxc7MJuNBkfHz9EtplNmEcET4JPfL29va+i6azR19f3UnzV1dUrqqqqyocT0KSzs/OV1YB6ROrr67fF19TU9DSazhp1dXXPxdfS0vJQNTY2+sfGxjSpra19YTWgHhHs/pn40OhRNJ0lLuON+kF8ra2tY9yAe3R0VBMc6wfr84n6b1BDrfiam5snImgczObAq7ylv7//q/hGRkbuqMHBwTt4Q2nS3d39jSKzCfXfoKarq+ur+NhD1owLcNrt9h3OTXGrqKgoKJ6hoaFD5DhuIA43xiGyJoWFhUGKxYXaL3CNGtH39PR8Zg9jzREfH+8vKCgI4krDRu0GcGVnZ78ZGBg4ER/Wf+4OVzOMRhrwFE6ysrLe0EQzaopII65RI3p478lVp6am7uDmPJY11F44HI7dsrKyfc5Nnj1km5Lo6Oiw4cdnD1kLJSUl++np6btsQjhmzayB5x29uGp3fn5+EPMw66eBX8b3yHZlDdyRdtzN75F1LED7kR6gMA7E6HsMrqpogbv5KngM9Bk8MbTKwAYmQSiCdhd4wW0VazQ0NNwEXrALNDHGS+A2UFHIA3smj/rX4JvrT7GBSRDi/J8Db8e/JY/5jLj4Y3KxgfPfwHc53iL+IQDMOgAAAABJRU5ErkJggg==">
Admin
</h3>
{% if token is not empty %}
<div class="search import clearfix" id="adminBar">
<h3>
<img style="margin: 0 5px 0 0; vertical-align: middle; height: 16px" width="16" height="16" alt="Import" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAADo0lEQVR42u2XS0hUURjHD5njA1oYbXQ2MqCmIu2iEEISUREEEURxFB8ovt+DEsLgaxBRQQeUxnQ0ZRYSQasgiDaFqxAy2jUtCjdCoEjFwHj6/+F+dbvN6PQAN37wm++c7/z/35x7uPcOo7TW58rFBs59A7GGQ51XBAIBlZmZuYOhE1zm/A/4PxvY3NwMO53OYEJCgp+nccqXXQc94D54boAxalyLNayNtra2NJmbmzvOyMj4cRqoKYK4AsZzc3Nft7e3f5qZmTnCpk8Ix6xxjRpDGzmkUU5Ozuu2trZP09PTR+vr6ycbGxtaWFtbC9fU1AQTExPdmNNzLSUlZXt4ePhANNGghlp6lDWkkcvlOsCX6LNYXV0N8BTS0tK2cDJfWIsFaumhV0lIIxzXl5WVFX0aPp8vhDwJbMnJyc6JiYkji8YP7oI4YowfmDX00KskOHG73UfLy8vahB/cBXFSW1pa2kPOA7RdqqysfGtaCyOXA2VGgmvUiJ5e9lD8qKioeOv1ejVZXFwMI5eLEWOFWgh5Etg4J0lJSTdwYiHxLSwseFi3Yg5qRE8veyh+TE1Nhebn5zWZnZ31mE2okTxmM6WlpS7xeDyeQ2Qb61bMQQ214mMPVVxc7MJuNBkfHz9EtplNmEcET4JPfL29va+i6azR19f3UnzV1dUrqqqqyocT0KSzs/OV1YB6ROrr67fF19TU9DSazhp1dXXPxdfS0vJQNTY2+sfGxjSpra19YTWgHhHs/pn40OhRNJ0lLuON+kF8ra2tY9yAe3R0VBMc6wfr84n6b1BDrfiam5snImgczObAq7ylv7//q/hGRkbuqMHBwTt4Q2nS3d39jSKzCfXfoKarq+ur+NhD1owLcNrt9h3OTXGrqKgoKJ6hoaFD5DhuIA43xiGyJoWFhUGKxYXaL3CNGtH39PR8Zg9jzREfH+8vKCgI4krDRu0GcGVnZ78ZGBg4ER/Wf+4OVzOMRhrwFE6ysrLe0EQzaopII65RI3p478lVp6am7uDmPJY11F44HI7dsrKyfc5Nnj1km5Lo6Oiw4cdnD1kLJSUl++np6btsQjhmzayB5x29uGp3fn5+EPMw66eBX8b3yHZlDdyRdtzN75F1LED7kR6gMA7E6HsMrqpogbv5KngM9Bk8MbTKwAYmQSiCdhd4wW0VazQ0NNwEXrALNDHGS+A2UFHIA3smj/rX4JvrT7GBSRDi/J8Db8e/JY/5jLj4Y3KxgfPfwHc53iL+IQDMOgAAAABJRU5ErkJggg==">
Admin
</h3>

<form action="{{ path('_profiler_import') }}" method="post" enctype="multipart/form-data">
{% if token is not empty %}
<div style="margin-bottom: 10px">
&#187;&#160;<a href="{{ path('_profiler_purge', { 'token': token }) }}">Purge</a>
</div>
<div style="margin-bottom: 10px">
&#187;&#160;<a href="{{ path('_profiler_export', { 'token': token }) }}">Export</a>
</div>
{% endif %}
&#187;&#160;<label for="file">Import</label><br>
<input type="file" name="file" id="file"><br>
<button type="submit" class="sf-button">
<span class="border-l">
<span class="border-r">
<span class="btn-bg">UPLOAD</span>
</span>
</span>
</button>
<div class="clear-fix"></div>
</form>
</div>
<div style="margin-bottom: 10px">&#187;&#160;<a href="{{ path('_profiler_purge', { 'token': token }) }}">Purge</a></div>
</div>
{% endif %}
@@ -0,0 +1,53 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Symfony\Bundle\WebProfilerBundle\Tests\Command;

use Symfony\Bundle\WebProfilerBundle\Command\ExportCommand;
use Symfony\Component\Console\Tester\CommandTester;
use Symfony\Component\Console\Application;
use Symfony\Component\HttpKernel\Profiler\Profile;

class ExportCommandTest extends \PHPUnit_Framework_TestCase
{
/**
* @expectedException \LogicException
*/
public function testExecuteWithUnknownToken()
{
$profiler = $this
->getMockBuilder('Symfony\Component\HttpKernel\Profiler\Profiler')
->disableOriginalConstructor()
->getMock()
;

$command = new ExportCommand($profiler);
$commandTester = new CommandTester($command);
$commandTester->execute(array('token' => 'TOKEN'));
}

public function testExecuteWithToken()
{
$profiler = $this
->getMockBuilder('Symfony\Component\HttpKernel\Profiler\Profiler')
->disableOriginalConstructor()
->getMock()
;

$profile = new Profile('TOKEN');
$profiler->expects($this->once())->method('loadProfile')->with('TOKEN')->will($this->returnValue($profile));

$command = new ExportCommand($profiler);
$commandTester = new CommandTester($command);
$commandTester->execute(array('token' => 'TOKEN'));
$this->assertEquals($profiler->export($profile), $commandTester->getDisplay());
}
}
@@ -0,0 +1,36 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Symfony\Bundle\WebProfilerBundle\Tests\Command;

use Symfony\Bundle\WebProfilerBundle\Command\ImportCommand;
use Symfony\Component\Console\Tester\CommandTester;
use Symfony\Component\Console\Application;
use Symfony\Component\HttpKernel\Profiler\Profile;

class ImportCommandTest extends \PHPUnit_Framework_TestCase
{
public function testExecute()
{
$profiler = $this
->getMockBuilder('Symfony\Component\HttpKernel\Profiler\Profiler')
->disableOriginalConstructor()
->getMock()
;

$profiler->expects($this->once())->method('import')->will($this->returnValue(new Profile('TOKEN')));

$command = new ImportCommand($profiler);
$commandTester = new CommandTester($command);
$commandTester->execute(array('filename' => __DIR__.'/../Fixtures/profile.data'));
$this->assertRegExp('/Profile "TOKEN" has been successfully imported\./', $commandTester->getDisplay());
}
}
@@ -0,0 +1 @@
Tzo0NToiU3ltZm9ueVxDb21wb25lbnRcSHR0cEtlcm5lbFxQcm9maWxlclxQcm9maWxlIjo4OntzOjUyOiIAU3ltZm9ueVxDb21wb25lbnRcSHR0cEtlcm5lbFxQcm9maWxlclxQcm9maWxlAHRva2VuIjtzOjU6IlRPS0VOIjtzOjUzOiIAU3ltZm9ueVxDb21wb25lbnRcSHR0cEtlcm5lbFxQcm9maWxlclxQcm9maWxlAHBhcmVudCI7TjtzOjU1OiIAU3ltZm9ueVxDb21wb25lbnRcSHR0cEtlcm5lbFxQcm9maWxlclxQcm9maWxlAGNoaWxkcmVuIjthOjA6e31zOjU3OiIAU3ltZm9ueVxDb21wb25lbnRcSHR0cEtlcm5lbFxQcm9maWxlclxQcm9maWxlAGNvbGxlY3RvcnMiO2E6MDp7fXM6NDk6IgBTeW1mb255XENvbXBvbmVudFxIdHRwS2VybmVsXFByb2ZpbGVyXFByb2ZpbGUAaXAiO047czo1MzoiAFN5bWZvbnlcQ29tcG9uZW50XEh0dHBLZXJuZWxcUHJvZmlsZXJcUHJvZmlsZQBtZXRob2QiO047czo1MDoiAFN5bWZvbnlcQ29tcG9uZW50XEh0dHBLZXJuZWxcUHJvZmlsZXJcUHJvZmlsZQB1cmwiO047czo1MToiAFN5bWZvbnlcQ29tcG9uZW50XEh0dHBLZXJuZWxcUHJvZmlsZXJcUHJvZmlsZQB0aW1lIjtOO30=

0 comments on commit b60b5d4

Please sign in to comment.