Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[11.x] Prevent destructive commands from running #51376

Merged
merged 9 commits into from
May 21, 2024
Merged
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
43 changes: 43 additions & 0 deletions src/Illuminate/Console/Prohibitable.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
<?php

namespace Illuminate\Console;

trait Prohibitable
{
/**
* Indicates if the command should be prohibited from running.
*
* @var bool
*/
protected static $prohibitedFromRunning = false;

/**
* Indicate whether the command should be prohibited from running.
*
* @param bool $prohibit
* @return bool
*/
public static function prohibit($prohibit = true)
{
static::$prohibitedFromRunning = $prohibit;
}

/**
* Determine if the command is prohibited from running and display a warning if so.
*
* @param bool $quiet
* @return bool
*/
protected function isProhibited(bool $quiet = false)
{
if (! static::$prohibitedFromRunning) {
return false;
}

if (! $quiet) {
$this->components->warn('This command is prohibited from running in this environment.');
}

return true;
}
}
6 changes: 4 additions & 2 deletions src/Illuminate/Database/Console/Migrations/FreshCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

use Illuminate\Console\Command;
use Illuminate\Console\ConfirmableTrait;
use Illuminate\Console\Prohibitable;
use Illuminate\Contracts\Events\Dispatcher;
use Illuminate\Database\Events\DatabaseRefreshed;
use Illuminate\Database\Migrations\Migrator;
Expand All @@ -13,7 +14,7 @@
#[AsCommand(name: 'migrate:fresh')]
class FreshCommand extends Command
{
use ConfirmableTrait;
use ConfirmableTrait, Prohibitable;

/**
* The console command name.
Expand Down Expand Up @@ -56,7 +57,8 @@ public function __construct(Migrator $migrator)
*/
public function handle()
{
if (! $this->confirmToProceed()) {
if ($this->isProhibited() ||
! $this->confirmToProceed()) {
return 1;
}

Expand Down
6 changes: 4 additions & 2 deletions src/Illuminate/Database/Console/Migrations/RefreshCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

use Illuminate\Console\Command;
use Illuminate\Console\ConfirmableTrait;
use Illuminate\Console\Prohibitable;
use Illuminate\Contracts\Events\Dispatcher;
use Illuminate\Database\Events\DatabaseRefreshed;
use Symfony\Component\Console\Attribute\AsCommand;
Expand All @@ -12,7 +13,7 @@
#[AsCommand(name: 'migrate:refresh')]
class RefreshCommand extends Command
{
use ConfirmableTrait;
use ConfirmableTrait, Prohibitable;

/**
* The console command name.
Expand All @@ -35,7 +36,8 @@ class RefreshCommand extends Command
*/
public function handle()
{
if (! $this->confirmToProceed()) {
if ($this->isProhibited() ||
! $this->confirmToProceed()) {
return 1;
}

Expand Down
6 changes: 4 additions & 2 deletions src/Illuminate/Database/Console/Migrations/ResetCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,15 @@
namespace Illuminate\Database\Console\Migrations;

use Illuminate\Console\ConfirmableTrait;
use Illuminate\Console\Prohibitable;
use Illuminate\Database\Migrations\Migrator;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Input\InputOption;

#[AsCommand(name: 'migrate:reset')]
class ResetCommand extends BaseCommand
{
use ConfirmableTrait;
use ConfirmableTrait, Prohibitable;

/**
* The console command name.
Expand Down Expand Up @@ -53,7 +54,8 @@ public function __construct(Migrator $migrator)
*/
public function handle()
{
if (! $this->confirmToProceed()) {
if ($this->isProhibited() ||
! $this->confirmToProceed()) {
return 1;
}

Expand Down
6 changes: 4 additions & 2 deletions src/Illuminate/Database/Console/WipeCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,14 @@

use Illuminate\Console\Command;
use Illuminate\Console\ConfirmableTrait;
use Illuminate\Console\Prohibitable;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Input\InputOption;

#[AsCommand(name: 'db:wipe')]
class WipeCommand extends Command
{
use ConfirmableTrait;
use ConfirmableTrait, Prohibitable;

/**
* The console command name.
Expand All @@ -33,7 +34,8 @@ class WipeCommand extends Command
*/
public function handle()
{
if (! $this->confirmToProceed()) {
if ($this->isProhibited() ||
! $this->confirmToProceed()) {
return 1;
}

Expand Down
21 changes: 21 additions & 0 deletions src/Illuminate/Support/Facades/DB.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@

namespace Illuminate\Support\Facades;

use Illuminate\Database\Console\Migrations\FreshCommand;
use Illuminate\Database\Console\Migrations\RefreshCommand;
use Illuminate\Database\Console\Migrations\ResetCommand;
use Illuminate\Database\Console\WipeCommand;

/**
* @method static \Illuminate\Database\Connection connection(string|null $name = null)
* @method static \Illuminate\Database\ConnectionInterface connectUsing(string $name, array $config, bool $force = false)
Expand Down Expand Up @@ -109,6 +114,22 @@
*/
class DB extends Facade
{
/**
* Indicate if destructive Artisan commands should be prohibited.
*
* Prohibits: db:wipe, migrate:fresh, migrate:refresh, and migrate:reset
*
* @param bool $prohibit
* @return void
*/
public static function prohibitDestructiveCommands(bool $prohibit = true)
{
FreshCommand::prohibit($prohibit);
RefreshCommand::prohibit($prohibit);
ResetCommand::prohibit($prohibit);
WipeCommand::prohibit($prohibit);
}

/**
* Get the registered name of the component.
*
Expand Down
22 changes: 22 additions & 0 deletions tests/Database/DatabaseMigrationRefreshCommandTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ class DatabaseMigrationRefreshCommandTest extends TestCase
{
protected function tearDown(): void
{
RefreshCommand::prohibit(false);
m::close();
}

Expand Down Expand Up @@ -72,6 +73,27 @@ public function testRefreshCommandCallsCommandsWithStep()
$this->runCommand($command, ['--step' => 2]);
}

public function testRefreshCommandExitsWhenPrevented()
{
$command = new RefreshCommand;

$app = new ApplicationDatabaseRefreshStub(['path.database' => __DIR__]);
$dispatcher = $app->instance(Dispatcher::class, $events = m::mock());
$console = m::mock(ConsoleApplication::class)->makePartial();
$console->__construct();
$command->setLaravel($app);
$command->setApplication($console);

RefreshCommand::prohibit();

$code = $this->runCommand($command);

$this->assertSame(1, $code);

$console->shouldNotHaveBeenCalled();
$dispatcher->shouldNotReceive('dispatch');
}

protected function runCommand($command, $input = [])
{
return $command->run(new ArrayInput($input), new NullOutput);
Expand Down
18 changes: 18 additions & 0 deletions tests/Database/DatabaseMigrationResetCommandTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ class DatabaseMigrationResetCommandTest extends TestCase
{
protected function tearDown(): void
{
ResetCommand::prohibit(false);
m::close();
}

Expand Down Expand Up @@ -52,6 +53,23 @@ public function testResetCommandCanBePretended()
$this->runCommand($command, ['--pretend' => true, '--database' => 'foo']);
}

public function testRefreshCommandExitsWhenPrevented()
{
$command = new ResetCommand($migrator = m::mock(Migrator::class));

$app = new ApplicationDatabaseResetStub(['path.database' => __DIR__]);
$app->useDatabasePath(__DIR__);
$command->setLaravel($app);

ResetCommand::prohibit();

$code = $this->runCommand($command);

$this->assertSame(1, $code);

$migrator->shouldNotHaveBeenCalled();
}

protected function runCommand($command, $input = [])
{
return $command->run(new ArrayInput($input), new NullOutput);
Expand Down