Repository: symfony/symfony
File: src/Symfony/Component/Lock/Bridge/DynamoDb/Store/DynamoDbStore.php (PHP)

Code before:
                'TableName' => $this->tableName,
                'Item' => [
                    $this->idAttr => new AttributeValue(['S' => $this->getHashedKey($key)]),
                    $this->tokenAttr => new AttributeValue(['S' => $uniqueToken]),
                    $this->expirationAttr => new AttributeValue(['N' => (string) (\microtime(true) + $ttl)]),
                ],
                'ConditionExpression' => 'attribute_exists(#key) AND (#token = :token OR #expires_at <= :now)',
                'ExpressionAttributeNames' => [
                    '#key' => $this->idAttr,
                    '#expires_at' => $this->expirationAttr,
                    '#token' => $this->tokenAttr,
                ],
                'ExpressionAttributeValues' => [
                    ':now' => new AttributeValue(['N' => (string) \microtime(true)]),
                    ':token' => new AttributeValue(['S' => $uniqueToken]),
                ],
            ]));
        } catch (ConditionalCheckFailedException) {
            // The item doesn't exist or was acquired by someone else
            throw new LockConflictedException();
        } catch (\Throwable $throwable) {
            throw new LockAcquiringException('Failed to acquire lock.', 0, $throwable);
        }

        $this->checkNotExpired($key);
    }

    public function createTable(): void
    {
        $this->client->createTable(new CreateTableInput([
            'TableName' => $this->tableName,
            'AttributeDefinitions' => [
                new AttributeDefinition(['AttributeName' => $this->idAttr, 'AttributeType' => 'S']),
            ],
            'KeySchema' => [
                new KeySchemaElement(['AttributeName' => $this->idAttr, 'KeyType' => 'HASH']),
            ],
            'ProvisionedThroughput' => new ProvisionedThroughput([
                'ReadCapacityUnits' => $this->readCapacityUnits,
                'WriteCapacityUnits' => $this->writeCapacityUnits,
            ]),
        ]));

        $this->client->tableExists(new DescribeTableInput(['TableName' => $this->tableName]))->wait();
    }

    private function getHashedKey(Key $key): string
    {
        return hash('sha256', (string) $key);
    }


Review comment (security):
shouldn't `createTable` stay public, to make it possible to use it in a setup script.
Some projects will use a low-priviledge account for the runtime usage, that is not able to create a table, but is allowed to access the specific table being used. They will then have a setup script run with different AWS credentials with higher priviledges to create the table. If this method is not public, they cannot rely on it as their implementation, having to duplicate it (hoping it is in sync), or use Reflection to call a private method (not covered by our BC policy).

Having such setup method public is the common case for our stores in various components (see PdoStore for instance)

Code after:
                    $this->idAttr => new AttributeValue(['S' => $this->getHashedKey($key)]),
                    $this->tokenAttr => new AttributeValue(['S' => $uniqueToken]),
                    $this->expirationAttr => new AttributeValue(['N' => (string) (microtime(true) + $ttl)]),
                ],
                'ConditionExpression' => 'attribute_exists(#key) AND (#token = :token OR #expires_at <= :now)',
                'ExpressionAttributeNames' => [
                    '#key' => $this->idAttr,
                    '#expires_at' => $this->expirationAttr,
                    '#token' => $this->tokenAttr,
                ],
                'ExpressionAttributeValues' => [
                    ':now' => new AttributeValue(['N' => (string) microtime(true)]),
                    ':token' => new AttributeValue(['S' => $uniqueToken]),
                ],
            ]));
        } catch (ConditionalCheckFailedException) {
            // The item doesn't exist or was acquired by someone else
            throw new LockConflictedException();
        } catch (\Throwable $throwable) {
            throw new LockAcquiringException('Failed to acquire lock.', 0, $throwable);
        }

        $this->checkNotExpired($key);
    }

    private function createTable(): void
    {
        $this->client->createTable(new CreateTableInput([
            'TableName' => $this->tableName,
            'AttributeDefinitions' => [
                new AttributeDefinition(['AttributeName' => $this->idAttr, 'AttributeType' => 'S']),
            ],
            'KeySchema' => [
                new KeySchemaElement(['AttributeName' => $this->idAttr, 'KeyType' => 'HASH']),
            ],
            'ProvisionedThroughput' => new ProvisionedThroughput([
                'ReadCapacityUnits' => $this->readCapacityUnits,
                'WriteCapacityUnits' => $this->writeCapacityUnits,
            ]),
        ]));

        $this->client->tableExists(new DescribeTableInput(['TableName' => $this->tableName]))->wait();
    }

    private function getHashedKey(Key $key): string
    {
        return hash('sha256', (string) $key);
    }

    private function getUniqueToken(Key $key): string
    {