Skip to content

Commit

Permalink
feat(request): Introduce required headers property
Browse files Browse the repository at this point in the history
  • Loading branch information
julienloizelet committed Sep 6, 2024
1 parent 6d0afcc commit b5ade8f
Show file tree
Hide file tree
Showing 8 changed files with 235 additions and 46 deletions.
11 changes: 11 additions & 0 deletions src/Client/HttpMessage/AppSecRequest.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,17 @@ class AppSecRequest extends Request
* @var array
*/
protected $headers = [];
/**
* @var string[]
*/
protected $requiredHeaders = [
'X-Crowdsec-Appsec-Ip',
'X-Crowdsec-Appsec-User-Agent',
'X-Crowdsec-Appsec-Verb',
'X-Crowdsec-Appsec-Uri',
'X-Crowdsec-Appsec-Host',
'X-Crowdsec-Appsec-Api-Key',
];
/**
* @var string
*/
Expand Down
25 changes: 25 additions & 0 deletions src/Client/HttpMessage/Request.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

namespace CrowdSec\Common\Client\HttpMessage;

use CrowdSec\Common\Client\ClientException;

/**
* Request that will be sent to CrowdSec.
*
Expand All @@ -23,6 +25,12 @@ class Request extends AbstractMessage
'Accept' => 'application/json',
'Content-Type' => 'application/json',
];
/**
* @var string[]
*/
protected $requiredHeaders = [
'User-Agent',
];
/**
* @var string
*/
Expand Down Expand Up @@ -62,4 +70,21 @@ public function getUri(): string
{
return $this->uri;
}

/**
* Retrieve validated headers.
*
* @throws ClientException
*/
public function getValidatedHeaders(): array
{
$headers = $this->getHeaders();
foreach ($this->requiredHeaders as $requiredHeader) {
if (!isset($headers[$requiredHeader])) {
throw new ClientException("Header \"$requiredHeader\" is required", 400);
}
}

return $headers;
}
}
20 changes: 8 additions & 12 deletions src/Client/RequestHandler/Curl.php
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,11 @@ private function handleSSL(Request $request): array
{
$result = [\CURLOPT_SSL_VERIFYPEER => false];
if ($request instanceof AppSecRequest) {
// AppSec does not require SSL verification
/**
* AppSec does not currently support TLS authentication.
*
* @see https://github.com/crowdsecurity/crowdsec/issues/3172
*/
return $result;
}

Expand All @@ -113,7 +117,7 @@ private function handleSSL(Request $request): array
return $result;
}

private function handleMethod(string $method, string $url, array $parameters = [], $rawBody = ''): array
private function handleMethod(string $method, string $url, array $parameters = [], string $rawBody = ''): array
{
$result = [];
if ('POST' === strtoupper($method)) {
Expand Down Expand Up @@ -145,19 +149,11 @@ private function handleMethod(string $method, string $url, array $parameters = [
*/
private function createOptions(Request $request): array
{
$isAppSec = $request instanceof AppSecRequest;
$headers = $request->getHeaders();
$headers = $request->getValidatedHeaders();
$method = $request->getMethod();
$url = $request->getUri();
$parameters = $request->getParams();
if (!isset($headers['User-Agent']) && !$isAppSec) {
throw new ClientException('User agent is required', 400);
}
$rawBody = '';
if ($isAppSec) {
/** @var AppSecRequest $request */
$rawBody = $request->getRawBody();
}
$rawBody = $request instanceof AppSecRequest ? $request->getRawBody() : '';
$options = [
\CURLOPT_HEADER => false,
\CURLOPT_RETURNTRANSFER => true,
Expand Down
37 changes: 19 additions & 18 deletions src/Client/RequestHandler/FileGetContents.php
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,19 @@ public function handle(Request $request): Response
return new Response($responseBody, $status);
}

/**
* Convert a key-value array of headers to the official HTTP header string.
*/
protected function convertHeadersToString(array $headers): string
{
$builtHeaderString = '';
foreach ($headers as $key => $value) {
$builtHeaderString .= "$key: $value\r\n";
}

return $builtHeaderString;
}

/**
* @codeCoverageIgnore
*
Expand All @@ -73,19 +86,6 @@ protected function getResponseHttpCode(array $parts): int
return $status;
}

/**
* Convert a key-value array of headers to the official HTTP header string.
*/
protected function convertHeadersToString(array $headers): string
{
$builtHeaderString = '';
foreach ($headers as $key => $value) {
$builtHeaderString .= "$key: $value\r\n";
}

return $builtHeaderString;
}

/**
* Retrieve configuration for the stream content.
*
Expand All @@ -95,11 +95,8 @@ protected function convertHeadersToString(array $headers): string
*/
private function createContextConfig(Request $request): array
{
$headers = $request->getHeaders();
$headers = $request->getValidatedHeaders();
$isAppSec = $request instanceof AppSecRequest;
if (!isset($headers['User-Agent']) && !$isAppSec) {
throw new ClientException('User agent is required', 400);
}
$rawBody = '';
if ($isAppSec) {
/** @var AppSecRequest $request */
Expand Down Expand Up @@ -133,7 +130,11 @@ private function handleSSL(Request $request): array
{
$result = ['ssl' => ['verify_peer' => false]];
if ($request instanceof AppSecRequest) {
// AppSec does not require SSL verification
/**
* AppSec does not currently support TLS authentication.
*
* @see https://github.com/crowdsecurity/crowdsec/issues/3172
*/
return $result;
}
$authType = $this->getConfig('auth_type');
Expand Down
8 changes: 0 additions & 8 deletions src/Constants.php
Original file line number Diff line number Diff line change
Expand Up @@ -84,14 +84,6 @@ class Constants
* @var string The CrowdSec range scope for decisions
*/
public const SCOPE_RANGE = 'range';
/**
* @var string The REST API type
*/
public const TYPE_REST = 'rest';
/**
* @var string The APPSEC API type
*/
public const TYPE_APPSEC = 'app_sec';
/**
* @var string The current version of this library
*/
Expand Down
46 changes: 42 additions & 4 deletions tests/Unit/CurlTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,25 @@ public function testHandleError()
$this->assertEquals(400, $code);

$this->assertEquals(
'User agent is required',
'Header "User-Agent" is required',
$error,
'Should failed and throw if no user agent'
);
// Test 1 bis AppSec header required
$request = new AppSecRequest('test-uri', 'POST', ['User-Agent' => 'ok']);
$error = '';
$code = 0;
try {
$mockCurlRequest->handle($request);
} catch (ClientException $e) {
$error = $e->getMessage();
$code = $e->getCode();
}

$this->assertEquals(400, $code);

$this->assertEquals(
'Header "X-Crowdsec-Appsec-Ip" is required',
$error,
'Should failed and throw if no user agent'
);
Expand Down Expand Up @@ -275,7 +293,15 @@ public function testOptionsForAppSec()
{
$url = TestConstants::APPSEC_URL . '/';
$method = 'POST';
$headers = ['X-Crowdsec-Appsec-test' => 'test-value'];
$headers = [
'X-Crowdsec-Appsec-Ip' => 'test-value',
'X-Crowdsec-Appsec-Host' => 'test-value',
'X-Crowdsec-Appsec-User-Agent' => 'test-value',
'X-Crowdsec-Appsec-Verb' => 'test-value',
'X-Crowdsec-Appsec-Method' => 'test-value',
'X-Crowdsec-Appsec-Uri' => 'test-value',
'X-Crowdsec-Appsec-Api-Key' => 'test-value',
];
$rawBody = 'this is raw body';
$configs = $this->tlsConfigs;

Expand All @@ -291,7 +317,13 @@ public function testOptionsForAppSec()
\CURLOPT_HEADER => false,
\CURLOPT_RETURNTRANSFER => true,
\CURLOPT_HTTPHEADER => [
'X-Crowdsec-Appsec-test:test-value',
'X-Crowdsec-Appsec-Ip:test-value',
'X-Crowdsec-Appsec-Host:test-value',
'X-Crowdsec-Appsec-User-Agent:test-value',
'X-Crowdsec-Appsec-Verb:test-value',
'X-Crowdsec-Appsec-Method:test-value',
'X-Crowdsec-Appsec-Uri:test-value',
'X-Crowdsec-Appsec-Api-Key:test-value',
],
\CURLOPT_POST => true,
\CURLOPT_POSTFIELDS => 'this is raw body',
Expand Down Expand Up @@ -325,7 +357,13 @@ public function testOptionsForAppSec()
\CURLOPT_RETURNTRANSFER => true,
\CURLOPT_USERAGENT => TestConstants::USER_AGENT_SUFFIX,
\CURLOPT_HTTPHEADER => [
'X-Crowdsec-Appsec-test:test-value',
'X-Crowdsec-Appsec-Ip:test-value',
'X-Crowdsec-Appsec-Host:test-value',
'X-Crowdsec-Appsec-User-Agent:test-value',
'X-Crowdsec-Appsec-Verb:test-value',
'X-Crowdsec-Appsec-Method:test-value',
'X-Crowdsec-Appsec-Uri:test-value',
'X-Crowdsec-Appsec-Api-Key:test-value',
'User-Agent:' . TestConstants::USER_AGENT_SUFFIX,
],
\CURLOPT_POST => false,
Expand Down
42 changes: 38 additions & 4 deletions tests/Unit/FileGetContentsTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,15 @@ public function testContextConfig()
public function testContextConfigForAppSec()
{
$method = 'POST';
$headers = ['X-CrowdSec-AppSec-test' => 'test-value'];
$headers = [
'X-Crowdsec-Appsec-Ip' => 'test-value',
'X-Crowdsec-Appsec-Host' => 'test-value',
'X-Crowdsec-Appsec-User-Agent' => 'test-value',
'X-Crowdsec-Appsec-Verb' => 'test-value',
'X-Crowdsec-Appsec-Method' => 'test-value',
'X-Crowdsec-Appsec-Uri' => 'test-value',
'X-Crowdsec-Appsec-Api-Key' => 'test-value',
];
$rawBody = 'This is a raw body';
$configs = $this->tlsConfigs;

Expand All @@ -179,7 +187,13 @@ public function testContextConfigForAppSec()
$expected = [
'http' => [
'method' => $method,
'header' => 'X-CrowdSec-AppSec-test: test-value
'header' => 'X-Crowdsec-Appsec-Ip: test-value
X-Crowdsec-Appsec-Host: test-value
X-Crowdsec-Appsec-User-Agent: test-value
X-Crowdsec-Appsec-Verb: test-value
X-Crowdsec-Appsec-Method: test-value
X-Crowdsec-Appsec-Uri: test-value
X-Crowdsec-Appsec-Api-Key: test-value
',
'ignore_errors' => true,
'content' => 'This is a raw body',
Expand Down Expand Up @@ -211,7 +225,13 @@ public function testContextConfigForAppSec()
$expected = [
'http' => [
'method' => $method,
'header' => 'X-CrowdSec-AppSec-test: test-value
'header' => 'X-Crowdsec-Appsec-Ip: test-value
X-Crowdsec-Appsec-Host: test-value
X-Crowdsec-Appsec-User-Agent: test-value
X-Crowdsec-Appsec-Verb: test-value
X-Crowdsec-Appsec-Method: test-value
X-Crowdsec-Appsec-Uri: test-value
X-Crowdsec-Appsec-Api-Key: test-value
User-Agent: ' . TestConstants::USER_AGENT_SUFFIX . '
',
'ignore_errors' => true,
Expand Down Expand Up @@ -242,7 +262,21 @@ public function testHandleError()
}

$this->assertEquals(
'User agent is required',
'Header "User-Agent" is required',
$error,
'Should failed and throw if no user agent'
);

$request = new AppSecRequest('test-uri', 'POST', ['User-Agent' => 'test']);
$error = false;
try {
$mockFGCRequest->handle($request);
} catch (ClientException $e) {
$error = $e->getMessage();
}

$this->assertEquals(
'Header "X-Crowdsec-Appsec-Ip" is required',
$error,
'Should failed and throw if no user agent'
);
Expand Down
Loading

0 comments on commit b5ade8f

Please sign in to comment.