Skip to content
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
90 changes: 75 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ correlation_id:
validation:
enabled: true
max_length: 255
pattern: null
pattern: '/^[a-zA-Z0-9-_]+$/'

monolog:
enabled: true
Expand All @@ -85,7 +85,49 @@ correlation_id:
cli:
enabled: true
prefix: 'CLI-'
allow_option: true
allow_env_var: true
```

### Validation & Security

By default, the bundle validates correlation IDs to prevent malicious input:

```yaml
correlation_id:
validation:
enabled: true
max_length: 255
pattern: '/^[a-zA-Z0-9-_]+$/' # Default: alphanumeric, dashes, underscores only
```

**Security features:**
- Rejects empty values
- Limits length to prevent buffer overflow attacks
- Blocks special characters that could be used for injection attacks (XSS, SQL injection, shell injection)
- Automatically generates a safe ID if validation fails

**Customize the pattern** for your needs:

#### UUID v4 only (strictest)
```yaml
correlation_id:
validation:
max_length: 36
pattern: '/^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i'
```

#### Allow more characters
```yaml
correlation_id:
validation:
pattern: '/^[a-zA-Z0-9-_:]+$/' # Add colon for custom formats like "APP:REQ:123"
```

#### Disable pattern validation (not recommended)
```yaml
correlation_id:
validation:
pattern: null # Only length validation will apply
```

### Configuration Examples
Expand All @@ -100,15 +142,6 @@ Always generate a new ID, ignoring incoming headers:
correlation_id:
trust_header: false
```
Strict Validation
Validate incoming IDs with specific rules:
```yaml
correlation_id:
validation:
enabled: true
max_length: 36
pattern: '/^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i'
```
#### Enable Monolog Integration
Automatically add correlation ID to all logs:
```yaml
Expand Down Expand Up @@ -214,15 +247,42 @@ If Monolog is not installed, the integration is automatically disabled.

When CLI integration is enabled (default), the bundle manages correlation IDs for Symfony Console commands.

### Global Option
If `cli.allow_option` is `true`, a global `--correlation-id` option is added to all commands:
### Environment Variable
If `cli.allow_env_var` is `true` (default), you can pass a correlation ID via the `CORRELATION_ID` environment variable:

```bash
php bin/console app:my-command --correlation-id=custom-id-123
CORRELATION_ID=custom-id-123 php bin/console app:my-command
```

This is particularly useful when executing commands from a Process within an HTTP request context:

```php
use Symfony\Component\Process\Process;

class MyService
{
public function __construct(
private readonly CorrelationIdStorage $correlationIdStorage
) {}

public function executeCommand(): void
{
// Get current correlation ID from HTTP request
$correlationId = $this->correlationIdStorage->get();

// Pass it to the console command via environment variable
$process = new Process(
['php', 'bin/console', 'app:my-command'],
env: ['CORRELATION_ID' => $correlationId]
);

$process->run();
}
}
```

### Automatic ID Generation
If no option is provided, an ID is automatically generated using the configured generator and prefixed with `cli.prefix` (default: `CLI-`):
If no environment variable is provided, an ID is automatically generated using the configured generator and prefixed with `cli.prefix` (default: `CLI-`):

**Example output for a generated ID:** `CLI-550e8400-e29b-41d4-a716-446655440000`

Expand Down
4 changes: 2 additions & 2 deletions config/services.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,6 @@ services:
$generator: '@MdavidDev\SymfonyCorrelationIdBundle\Service\Generator\CorrelationIdGeneratorInterface'
$validator: '@MdavidDev\SymfonyCorrelationIdBundle\Validator\CorrelationIdValidator'
$prefix: '%correlation_id.cli.prefix%'
$allowOption: '%correlation_id.cli.allow_option%'
$allowEnvVar: '%correlation_id.cli.allow_env_var%'
tags:
- { name: kernel.event_subscriber }
- { name: kernel.event_subscriber }
8 changes: 4 additions & 4 deletions src/DependencyInjection/Configuration.php
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,8 @@ public function getConfigTreeBuilder(): TreeBuilder
->min(1)
->end()
->scalarNode('pattern')
->info('Regex pattern to validate correlation ID format')
->defaultNull()
->info('Regex pattern to validate correlation ID format (security: alphanumeric, dashes, underscores only)')
->defaultValue('/^[a-zA-Z0-9-_]+$/')
->end()
->end()
->end()
Expand Down Expand Up @@ -91,8 +91,8 @@ public function getConfigTreeBuilder(): TreeBuilder
->info('Prefix for CLI-generated correlation IDs')
->defaultValue('CLI-')
->end()
->booleanNode('allow_option')
->info('Allow --correlation-id option in commands')
->booleanNode('allow_env_var')
->info('Allow CORRELATION_ID environment variable')
->defaultTrue()
->end()
->end()
Expand Down
2 changes: 1 addition & 1 deletion src/DependencyInjection/CorrelationIdExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ public function load(array $configs, ContainerBuilder $container): void
$container->setParameter('correlation_id.cli', $config['cli']);
$container->setParameter('correlation_id.cli.enabled', $config['cli']['enabled']);
$container->setParameter('correlation_id.cli.prefix', $config['cli']['prefix']);
$container->setParameter('correlation_id.cli.allow_option', $config['cli']['allow_option']);
$container->setParameter('correlation_id.cli.allow_env_var', $config['cli']['allow_env_var']);

$loader = new YamlFileLoader(
$container,
Expand Down
11 changes: 5 additions & 6 deletions src/EventListener/ConsoleListener.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ public function __construct(
private readonly CorrelationIdGeneratorInterface $generator,
private readonly CorrelationIdValidator $validator,
private readonly string $prefix = 'CLI-',
private readonly bool $allowOption = true
private readonly bool $allowEnvVar = true
)
{
}
Expand All @@ -43,13 +43,12 @@ public function onConsoleCommand(ConsoleCommandEvent $event): void
return;
}

$input = $event->getInput();
$correlationId = null;

if ($this->allowOption) {
$value = $input->getParameterOption('--' . self::OPTION_NAME, null);
if (is_string($value) && $value !== '') {
$correlationId = $this->validator->sanitize($value);
if ($this->allowEnvVar) {
$envValue = $_SERVER['CORRELATION_ID'] ?? $_ENV['CORRELATION_ID'] ?? null;
if (is_string($envValue) && $envValue !== '') {
$correlationId = $this->validator->sanitize($envValue);
}
}

Expand Down
20 changes: 6 additions & 14 deletions tests/Functional/Integration/ConsoleIntegrationTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -68,8 +68,10 @@ protected function execute($input, $output): int
/**
* @throws Exception
*/
public function testConsoleCommandUsesProvidedOption(): void
public function testConsoleCommandUsesProvidedEnvVar(): void
{
$_SERVER['CORRELATION_ID'] = 'manual-id-123';

$kernel = new ConsoleTestKernel('test', true);
$kernel->boot();

Expand All @@ -83,20 +85,12 @@ public function testConsoleCommandUsesProvidedOption(): void
$application->setDispatcher($dispatcher);
$application->setAutoExit(false);

$definition = $application->getDefinition();
$definition->addOption(new InputOption(
'correlation-id',
null,
InputOption::VALUE_REQUIRED,
'Correlation ID for this command execution'
));

$capturedId = null;
$command = new class($storage, $capturedId) extends Command {
public ?string $capturedId = null;
public function __construct(private readonly CorrelationIdStorage $storage, &$capturedId)
{
parent::__construct('test:option');
parent::__construct('test:envvar');
$this->capturedId = &$capturedId;
}

Expand All @@ -108,15 +102,13 @@ protected function execute($input, $output): int
};
$application->addCommands([$command]);

$input = new ArrayInput([
'command' => 'test:option',
'--correlation-id' => 'manual-id-123'
]);
$input = new ArrayInput(['command' => 'test:envvar']);
$application->run($input, new NullOutput());

$this->assertSame('manual-id-123', $capturedId);
$this->assertFalse($storage->has());

unset($_SERVER['CORRELATION_ID']);
$kernel->shutdown();
}
}
Expand Down
4 changes: 2 additions & 2 deletions tests/Unit/DependencyInjection/ConfigurationTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ public function testDefaultConfiguration(): void

$this->assertTrue($config['validation']['enabled']);
$this->assertSame(255, $config['validation']['max_length']);
$this->assertNull($config['validation']['pattern']);
$this->assertSame('/^[a-zA-Z0-9-_]+$/', $config['validation']['pattern']);

$this->assertTrue($config['monolog']['enabled']);
$this->assertSame('correlation_id', $config['monolog']['key']);
Expand All @@ -39,7 +39,7 @@ public function testDefaultConfiguration(): void

$this->assertTrue($config['cli']['enabled']);
$this->assertSame('CLI-', $config['cli']['prefix']);
$this->assertTrue($config['cli']['allow_option']);
$this->assertTrue($config['cli']['allow_env_var']);
}

public function testCustomConfiguration(): void
Expand Down
Loading