Skip to content

Commit

Permalink
feat: Ruleset endpoint
Browse files Browse the repository at this point in the history
  • Loading branch information
nikosid committed Dec 23, 2024
1 parent 2d3f198 commit c353eb1
Show file tree
Hide file tree
Showing 4 changed files with 298 additions and 6 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ Each API call is provided via a similarly named function within various classes
- [x] User Administration (partial)
- [x] [Cloudflare IPs](https://www.cloudflare.com/ips/)
- [x] [Page Rules](https://support.cloudflare.com/hc/en-us/articles/200168306-Is-there-a-tutorial-for-Page-Rules-)
- [x] [Rulesets](https://developers.cloudflare.com/ruleset-engine/rulesets-api/)
- [x] [Web Application Firewall (WAF)](https://www.cloudflare.com/waf/)
- [ ] Virtual DNS Management
- [x] Custom hostnames
Expand Down
15 changes: 9 additions & 6 deletions composer.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

145 changes: 145 additions & 0 deletions src/Endpoints/Ruleset.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
<?php

namespace Cloudflare\API\Endpoints;

use Cloudflare\API\Adapter\Adapter;

class Ruleset implements API
{
private $adapter;

public function __construct(Adapter $adapter)
{
$this->adapter = $adapter;
}

/**
* Get all rulesets for a zone.
*
* @param string $zoneID The ID of the zone.
* @return array The list of rulesets.
*/
public function listZoneRulesets(string $zoneID): array
{
$response = $this->adapter->get("zones/{$zoneID}/rulesets");

$body = $response->getBody()->getContents();
$data = json_decode($body, true);

return $data["result"] ?? [];
}

/**
* Get rulesets for a specific phase within a zone.
*
* @param string $zoneID The ID of the zone.
* @param string $phase The phase of the ruleset (e.g., http_request_dynamic_redirect).
* @return array The filtered list of rulesets.
*/
public function getRulesetsByPhase(string $zoneID, string $phase): array
{
$rulesets = $this->listZoneRulesets($zoneID);
return array_filter($rulesets, function ($ruleset) use ($phase) {
return isset($ruleset['phase']) && $ruleset['phase'] === $phase;
});
}

/**
* Get a specific ruleset by ID.
*
* @param string $zoneID The ID of the zone.
* @param string $rulesetID The ID of the ruleset.
* @return array The ruleset details.
*/
public function getRuleset(string $zoneID, string $rulesetID): array
{
$response = $this->adapter->get("zones/{$zoneID}/rulesets/{$rulesetID}");

$body = $response->getBody()->getContents();

$data = json_decode($body, true);

return $data["result"] ?? [];
}

/**
* Create a new ruleset for a zone.
*
* @param string $zoneID The ID of the zone.
* @param array $payload The payload for the new ruleset.
* @return array The created ruleset details.
*/
public function createRuleset(string $zoneID, array $payload): array
{
$response = $this->adapter->post("zones/{$zoneID}/rulesets", $payload);

$body = $response->getBody()->getContents();
$data = json_decode($body, true);

return $data["result"] ?? [];
}

/**
* Update an existing ruleset.
*
* @param string $zoneID The ID of the zone.
* @param string $rulesetID The ID of the ruleset.
* @param array $payload The payload with updated ruleset details.
* @return array The updated ruleset details.
*/
public function updateRuleset(string $zoneID, string $rulesetID, array $payload): array
{
$response = $this->adapter->put("zones/{$zoneID}/rulesets/{$rulesetID}", $payload);

$body = $response->getBody()->getContents();
$data = json_decode($body, true);

return $data["result"] ?? [];
}

/**
* Delete a specific rule by name from a ruleset.
*
* @param string $zoneID The ID of the zone.
* @param string $rulesetID The ID of the ruleset.
* @param string $ruleName The name of the rule to delete.
* @return bool True if deletion was successful, false otherwise.
*/
public function deleteRuleByName(string $zoneID, string $rulesetID, string $ruleName): bool
{
$rulesetDetails = $this->getRuleset($zoneID, $rulesetID);
$rules = $rulesetDetails['rules'] ?? [];

// Filter out the rule with the specified name
$updatedRules = array_filter($rules, function ($rule) use ($ruleName) {
return $rule['description'] !== $ruleName;
});

if (count($updatedRules) === count($rules)) {
// No rule was removed
return false;
}

$payload = ['rules' => array_values($updatedRules)];
$updatedRuleset = $this->updateRuleset($zoneID, $rulesetID, $payload);

return !empty($updatedRuleset);
}

/**
* Delete a ruleset from a zone.
*
* @param string $zoneID The ID of the zone.
* @param string $rulesetID The ID of the ruleset.
* @return bool True if deletion was successful, false otherwise.
*/
public function deleteRuleset(string $zoneID, string $rulesetID): bool
{
$response = $this->adapter->delete("zones/{$zoneID}/rulesets/{$rulesetID}");

$body = $response->getBody()->getContents();
$data = json_decode($body, true);

return $data["success"] ?? false;
}
}
143 changes: 143 additions & 0 deletions tests/Endpoints/RulesetTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
<?php

use Cloudflare\API\Adapter\Adapter;
use Cloudflare\API\Endpoints\Ruleset;
use PHPUnit\Framework\TestCase;

class RulesetTest extends TestCase
{
private $adapterMock;
private $ruleset;

protected function setUp(): void
{
$this->adapterMock = $this->createMock(Adapter::class);
$this->ruleset = new Ruleset($this->adapterMock);
}

public function testListZoneRulesets(): void
{
$zoneID = 'example-zone-id';
$expectedResult = [
'result' => [
[
'id' => 'example-ruleset-id',
'name' => 'Example Ruleset',
'phase' => 'http_request_dynamic_redirect',
],
],
];

$this->adapterMock->expects($this->once())
->method('get')
->with("zones/{$zoneID}/rulesets")
->willReturn($this->createResponseMock($expectedResult));

$result = $this->ruleset->listZoneRulesets($zoneID);

$this->assertEquals($expectedResult['result'], $result);
}

public function testGetRulesetsByPhase(): void
{
$zoneID = 'example-zone-id';
$phase = 'http_request_dynamic_redirect';
$rulesets = [
[
'id' => 'example-ruleset-id',
'name' => 'Example Ruleset',
'phase' => $phase,
],
[
'id' => 'another-ruleset-id',
'name' => 'Another Ruleset',
'phase' => 'http_request_firewall',
],
];

$this->adapterMock->expects($this->once())
->method('get')
->with("zones/{$zoneID}/rulesets")
->willReturn($this->createResponseMock(['result' => $rulesets]));

$result = $this->ruleset->getRulesetsByPhase($zoneID, $phase);

$this->assertCount(1, $result);
$this->assertEquals($phase, $result[0]['phase']);
}

public function testCreateRuleset(): void
{
$zoneID = 'example-zone-id';
$payload = [
'name' => 'Test Ruleset',
'description' => 'A ruleset for testing',
'kind' => 'zone',
'phase' => 'http_request_dynamic_redirect',
'rules' => [],
];
$expectedResult = ['id' => 'new-ruleset-id'];

$this->adapterMock->expects($this->once())
->method('post')
->with("zones/{$zoneID}/rulesets", $payload)
->willReturn($this->createResponseMock(['result' => $expectedResult]));

$result = $this->ruleset->createRuleset($zoneID, $payload);

$this->assertEquals($expectedResult, $result);
}

public function testDeleteRuleByName(): void
{
$zoneID = 'example-zone-id';
$rulesetID = 'example-ruleset-id';
$ruleName = 'Example Rule';
$rulesetDetails = [
'rules' => [
[
'description' => $ruleName,
],
[
'description' => 'Another Rule',
],
],
];

$updatedRuleset = [
'rules' => [
[
'description' => 'Another Rule',
],
],
];

$this->adapterMock->expects($this->once())
->method('get')
->with("zones/{$zoneID}/rulesets/{$rulesetID}")
->willReturn($this->createResponseMock(['result' => $rulesetDetails]));

$this->adapterMock->expects($this->once())
->method('put')
->with("zones/{$zoneID}/rulesets/{$rulesetID}", ['rules' => $updatedRuleset['rules']])
->willReturn($this->createResponseMock(['result' => $updatedRuleset]));

$result = $this->ruleset->deleteRuleByName($zoneID, $rulesetID, $ruleName);

$this->assertTrue($result);
}

private function createResponseMock(array $body): object
{
$responseMock = $this->createMock(\Psr\Http\Message\ResponseInterface::class);
$responseMock->method('getBody')->willReturn($this->createStreamMock(json_encode($body)));
return $responseMock;
}

private function createStreamMock(string $content): object
{
$streamMock = $this->createMock(\Psr\Http\Message\StreamInterface::class);
$streamMock->method('getContents')->willReturn($content);
return $streamMock;
}
}

0 comments on commit c353eb1

Please sign in to comment.