Skip to content
Open
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: 24 additions & 19 deletions system/Commands/Encryption/GenerateKey.php
Original file line number Diff line number Diff line change
Expand Up @@ -171,36 +171,41 @@ protected function writeNewEncryptionKeyToFile(string $oldKey, string $newKey):
}

$oldFileContents = (string) file_get_contents($envFile);
$replacementKey = "\nencryption.key = {$newKey}";

if (! str_contains($oldFileContents, 'encryption.key')) {
return file_put_contents($envFile, $replacementKey, FILE_APPEND) !== false;
// Match an active setting line, preserving any leading whitespace and `export` prefix.
$activePattern = $this->keyPattern($oldKey);

if (preg_match($activePattern, $oldFileContents) === 1) {
$newFileContents = (string) preg_replace($activePattern, '$1' . $newKey, $oldFileContents, 1);

return file_put_contents($envFile, $newFileContents) !== false;
}

$newFileContents = preg_replace($this->keyPattern($oldKey), $replacementKey, $oldFileContents);
// Match a commented-out setting line (e.g., from the shipped `env` template) and
// uncomment it. The optional `export` prefix is dropped on uncomment for predictability.
$commentedPattern = '/^\h*#\h*(?:export\h+)?encryption\.key\h*=\h*[^\r\n]*$/m';

if ($newFileContents === $oldFileContents) {
$newFileContents = preg_replace(
'/^[#\s]*encryption.key[=\s]*(?:hex2bin\:[a-f0-9]{64}|base64\:(?:[A-Za-z0-9+\/]{4})*(?:[A-Za-z0-9+\/]{2}==|[A-Za-z0-9+\/]{3}=)?)$/m',
$replacementKey,
$oldFileContents,
);
if (preg_match($commentedPattern, $oldFileContents) === 1) {
$newFileContents = (string) preg_replace($commentedPattern, "encryption.key = {$newKey}", $oldFileContents, 1);

return file_put_contents($envFile, $newFileContents) !== false;
}

return file_put_contents($envFile, $newFileContents) !== false;
// No setting present (active or commented); append.
return file_put_contents($envFile, "\nencryption.key = {$newKey}", FILE_APPEND) !== false;
}

/**
* Get the regex of the current encryption key.
* Returns the regex used to locate an active `encryption.key = ...` setting in the `.env`
* contents. The single capture group spans everything up to (and including) the `=` and any
* separating whitespace, so a `preg_replace` substitution preserves an optional `export`
* prefix while rewriting only the value.
*
* The `$oldKey` parameter is retained for backward compatibility with subclasses that
* override this method; it is no longer consulted because the pattern matches any value.
*/
protected function keyPattern(string $oldKey): string
{
$escaped = preg_quote($oldKey, '/');

if ($escaped !== '') {
$escaped = "[{$escaped}]*";
}

return "/^[#\\s]*encryption.key[=\\s]*{$escaped}$/m";
return '/^(\h*(?:export\h+)?encryption\.key\h*=\h*)[^\r\n]*$/m';
}
}
36 changes: 36 additions & 0 deletions tests/system/Commands/Encryption/GenerateKeyTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -169,4 +169,40 @@ public function testKeyGenerateWhenNewBase64KeyIsSubsequentlyCommentedOut(): voi
$this->assertStringContainsString('was successfully set.', $this->getBuffer());
$this->assertNotSame($key, env('encryption.key', $key), 'Failed replacing the commented out key.');
}

public function testKeyGenerateReplacesExportPrefixedEncryptionKey(): void
{
$existingKey = 'hex2bin:' . str_repeat('a', 64);
file_put_contents($this->envPath, "export encryption.key = {$existingKey}\n");

command('key:generate --force');

$contents = (string) file_get_contents($this->envPath);
$this->assertMatchesRegularExpression(
'/^export encryption\.key = hex2bin:[a-f0-9]{64}$/m',
$contents,
'The `export` prefix should be preserved and the value rewritten.',
);
$this->assertStringNotContainsString($existingKey, $contents, 'The old key value should be replaced.');
}

public function testKeyGenerateNotFooledByCommentMentioningEncryptionKey(): void
{
$envContents = "# Note: encryption.key is set automatically by spark key:generate.\n";
file_put_contents($this->envPath, $envContents);

command('key:generate --force');

$contents = (string) file_get_contents($this->envPath);
$this->assertStringContainsString(
$envContents,
$contents,
'The doc comment must be left intact.',
);
$this->assertMatchesRegularExpression(
'/^encryption\.key = hex2bin:[a-f0-9]{64}$/m',
$contents,
'A real `encryption.key` setting must be appended even when a comment mentions the name.',
);
}
}
1 change: 1 addition & 0 deletions user_guide_src/source/changelogs/v4.7.3.rst
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ Bugs Fixed
- **CLI:** Fixed a bug where ``CLI::generateDimensions()`` leaked ``stty`` error output (e.g., ``stty: 'standard input': Inappropriate ioctl for device``) to stderr when stdin was not a TTY.
- **CLI:** Fixed a bug where ``CLI::generateDimensions()`` leaked ``tput`` error output (``tput: No value for $TERM and no -T specified``) to stderr when the ``stty`` fallback was reached and the ``TERM`` environment variable was not set.
- **Commands:** Fixed a bug in the ``env`` command where passing options only would cause the command to throw a ``TypeError`` instead of showing the current environment.
- **Commands:** Fixed a bug in ``key:generate`` command where the regex used to locate the ``encryption.key`` line was fooled by a comment containing the substring (silently writing nothing), and did not handle DotEnv's ``export encryption.key = ...`` syntax.
- **Common:** Fixed a bug where the ``command()`` helper function did not properly clean up output buffers, which could lead to risky tests when exceptions were thrown.
- **Database:** Fixed a bug where the SQLSRV driver's decrement method was adding instead of subtracting the decrement value when ``$castTextToInt`` was false.
- **Database:** Fixed a bug where the PostgreSQL driver's ``increment()`` and ``decrement()`` methods were not working for numeric columns.
Expand Down
Loading