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

Implement Key Generation Command for Enhanced Security #54

Merged
merged 4 commits into from
May 14, 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
4 changes: 1 addition & 3 deletions .github/workflows/run-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,11 @@ jobs:
matrix:
os: [ubuntu-latest, windows-latest]
php: [8.2, 8.1]
laravel: ['9.*', '10.*', '11.*']
laravel: ['10.*', '11.*']
stability: [prefer-stable]
include:
- laravel: 10.*
testbench: 8.*
- laravel: 9.*
testbench: 7.*
- laravel: 11.*
testbench: 9.*
exclude:
Expand Down
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,5 @@ composer.lock
docs
vendor
.php-cs-fixer.cache

.phpunit.cache/
7 changes: 4 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,9 +55,10 @@ composer require spatie/laravel-url-signer
You must set an environment variable called `URL_SIGNER_SIGNATURE_KEY` and set it to a long secret value. This value will be used to sign and validate signed URLs.

```
# in your .env file

URL_SIGNER_SIGNATURE_KEY=some_random_value
php artisan generate:url-signer-signature-key
{--s|show : Display the key instead of modifying files.}
{--always-no : Skip generating key if it already exists.}
{--f|force : Skip confirmation when overwriting an existing key.}
```

The configuration file can optionally be published via:
Expand Down
9 changes: 5 additions & 4 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,13 @@
],
"require": {
"php": "^8.1",
"spatie/url-signer": "^2.0",
"illuminate/support": "^9.0|^10.0|^11.0",
"spatie/laravel-package-tools": "^1.13.6"
"illuminate/support": "^10.0|^11.0",
"illuminate/console": "^10.10.0|^11.0",
"spatie/laravel-package-tools": "^1.13.6",
"spatie/url-signer": "^2.0"
},
"require-dev": {
"orchestra/testbench": "^7.12.1|^8.0|^9.0",
"orchestra/testbench": "^8.0|^9.0",
"pestphp/pest": "^1.22.2|^1.22|^2.34"
},
"autoload": {
Expand Down
1 change: 1 addition & 0 deletions phpunit.xml.dist
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
<testsuites>
<testsuite name="Package Test Suite">
<directory>./tests/</directory>
<directory suffix=".php">./tests/Commands/</directory>
</testsuite>
</testsuites>
<php>
Expand Down
115 changes: 115 additions & 0 deletions src/Commands/GenerateUrlSignerSignatureKey.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
<?php

namespace Spatie\UrlSigner\Laravel\Commands;

use Illuminate\Console\Command;
use Illuminate\Support\Str;

class GenerateUrlSignerSignatureKey extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'generate:url-signer-signature-key
{--s|show : Display the key instead of modifying files.}
{--always-no : Skip generating key if it already exists.}
{--f|force : Skip confirmation when overwriting an existing key.}';

/**
* The console command description.
*
* @var string
*/
protected $description = 'Generate a new URL signer signature key.';

/**
* Execute the console command.
* @return void
*/
public function handle(): void
{
$key = Str::random(64);

if ($this->option('show')) {
$this->comment($key);

return;
}

if (file_exists($path = $this->envPath()) === false) {
$this->displayKey($key);
return;
}

if (Str::contains(file_get_contents($path), 'URL_SIGNER_SIGNATURE_KEY') === false) {
// create new entry
file_put_contents($path, PHP_EOL."URL_SIGNER_SIGNATURE_KEY=$key".PHP_EOL, FILE_APPEND);
} else {
if ($this->option('always-no')) {
$this->comment('Secret key already exists. Skipping...');

return;
}

if ($this->isConfirmed() === false) {
$this->comment('Phew... No changes were made to your secret key.');

return;
}

// update existing entry
file_put_contents($path, str_replace(
'URL_SIGNER_SIGNATURE_KEY='.$this->laravel['config']['url-signer.signature_key'],
'URL_SIGNER_SIGNATURE_KEY='.$key,
file_get_contents($path)
));
}

$this->displayKey($key);

return;
}

/**
* Display the key.
*
* @param string $key
* @return void
*/
protected function displayKey(string $key): void
{
$this->laravel['config']['url-signer.signature_key'] = $key;

$this->info("Url-signer key [$key] set successfully.");

return;
}

/**
* Check if the modification is confirmed.
*
* @return bool
*/
protected function isConfirmed(): bool
{
return $this->option('force') || $this->confirm(
'This will invalidate all existing tokens. Are you sure you want to override the secret key?'
);
}

/**
* Get the .env file path.
*
* @return string
*/
protected function envPath(): string
{
if (method_exists($this->laravel, 'environmentFilePath')) {
return $this->laravel->environmentFilePath();
}

return $this->laravel->basePath('.env');
}
}
8 changes: 6 additions & 2 deletions src/UrlSignerServiceProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

use Spatie\LaravelPackageTools\Package;
use Spatie\LaravelPackageTools\PackageServiceProvider;
use Spatie\UrlSigner\Laravel\Commands\GenerateUrlSignerSignatureKey;
use Spatie\UrlSigner\UrlSigner as BaseUrlSigner;

class UrlSignerServiceProvider extends PackageServiceProvider
Expand All @@ -12,10 +13,13 @@ public function configurePackage(Package $package): void
{
$package
->name('laravel-url-signer')
->hasConfigFile();
->hasConfigFile()
->hasCommands([
GenerateUrlSignerSignatureKey::class
]);;
}

public function registeringPackage()
public function registeringPackage(): void
{
$this->app->singleton(BaseUrlSigner::class, function () {
$config = config('url-signer');
Expand Down
27 changes: 27 additions & 0 deletions tests/Commands/GenerateUrlSignerSignatureKey.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<?php

use Illuminate\Support\Env;
use Illuminate\Support\Facades\Artisan;
use Illuminate\Support\Facades\File;

beforeEach(function () {
File::copy(__DIR__.'/../Stubs/.env', base_path('.env'));
});

test('it can generate a URL signer signature key', function () {
Artisan::call('generate:url-signer-signature-key');

$env = trim(File::get(base_path('.env')));

expect($env)->toContain('URL_SIGNER_SIGNATURE_KEY=');
});

test('it can display a URL signer signature key', function () {
Artisan::call('generate:url-signer-signature-key --show');

$output = trim(Artisan::output());

expect($output)
->toBeString()
->toHaveLength(64);
});
65 changes: 65 additions & 0 deletions tests/Stubs/.env
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
# .env.example from Laravel/Laravel repository (commit: 27907e0)
APP_NAME=Laravel
APP_ENV=local
APP_KEY=
APP_DEBUG=true
APP_TIMEZONE=UTC
APP_URL=http://localhost

APP_LOCALE=en
APP_FALLBACK_LOCALE=en
APP_FAKER_LOCALE=en_US

APP_MAINTENANCE_DRIVER=file
APP_MAINTENANCE_STORE=database

BCRYPT_ROUNDS=12

LOG_CHANNEL=stack
LOG_STACK=single
LOG_DEPRECATIONS_CHANNEL=null
LOG_LEVEL=debug

DB_CONNECTION=sqlite
# DB_HOST=127.0.0.1
# DB_PORT=3306
# DB_DATABASE=laravel
# DB_USERNAME=root
# DB_PASSWORD=

SESSION_DRIVER=database
SESSION_LIFETIME=120
SESSION_ENCRYPT=false
SESSION_PATH=/
SESSION_DOMAIN=null

BROADCAST_CONNECTION=log
FILESYSTEM_DISK=local
QUEUE_CONNECTION=database

CACHE_STORE=database
CACHE_PREFIX=

MEMCACHED_HOST=127.0.0.1

REDIS_CLIENT=phpredis
REDIS_HOST=127.0.0.1
REDIS_PASSWORD=null
REDIS_PORT=6379

MAIL_MAILER=log
MAIL_HOST=127.0.0.1
MAIL_PORT=2525
MAIL_USERNAME=null
MAIL_PASSWORD=null
MAIL_ENCRYPTION=null
MAIL_FROM_ADDRESS="[email protected]"
MAIL_FROM_NAME="${APP_NAME}"

AWS_ACCESS_KEY_ID=
AWS_SECRET_ACCESS_KEY=
AWS_DEFAULT_REGION=us-east-1
AWS_BUCKET=
AWS_USE_PATH_STYLE_ENDPOINT=false

VITE_APP_NAME="${APP_NAME}"
Loading