Skip to content
This repository has been archived by the owner on Apr 18, 2024. It is now read-only.

Commit

Permalink
Update Cloudflare driver to implement scoped API tokens (#39)
Browse files Browse the repository at this point in the history
* Update Cloudflare driver to support scoped CF api tokens, deprecate using account-wide CF api keys

* Attach custom behaviours to Response before registering front end events to prevent error

* review fixes
  • Loading branch information
Tom Davies authored Apr 9, 2020
1 parent f7b9797 commit 8543f5a
Show file tree
Hide file tree
Showing 4 changed files with 42 additions and 23 deletions.
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,8 +61,7 @@ KEYCDN_ZONE_ID=<REPLACE-ME>

```
UPPER_DRIVER=cloudflare
CLOUDFLARE_API_KEY=<REPLACE-ME>
CLOUDFLARE_API_EMAIL=<REPLACE-ME>
CLOUDFLARE_API_TOKEN=<REPLACE-ME>
CLOUDFLARE_ZONE_ID=<REPLACE-ME>
CLOUDFLARE_DOMAIN=https://<REPLACE-ME>
```
Expand All @@ -71,6 +70,8 @@ By default, Cloudflare's CDN does not cache HTML content. You need to create a

If you don't use Cloudflare Enterprise with native `Cache-Tag` support, make sure to enable `useLocalTags` in your `config/upper.php` file (default), otherwise disable it.

You can generate a token in the Cloudflare dashboard. You want to create a custom token with the "Zone.Cache Purge" permission that is restricted to the DNS zone(s) you wish to clear Cloudflare's cache for.


### Varnish Setup
Varnish URL supports multiple servers, separate with comma. E.g `http://1.1.1.1,http://2.2.2.2`
Expand Down
9 changes: 4 additions & 5 deletions src/Plugin.php
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,10 @@ public function init()
'tagCollection' => TagCollection::class
]);

// Attach Behaviors
\Craft::$app->getResponse()->attachBehavior('cache-control', CacheControlBehavior::class);
\Craft::$app->getResponse()->attachBehavior('tag-header', TagHeaderBehavior::class);

// Register event handlers
EventRegistrar::registerFrontendEvents();
EventRegistrar::registerCpEvents();
Expand All @@ -69,11 +73,6 @@ public function init()
if ($this->getSettings()->useLocalTags) {
EventRegistrar::registerFallback();
}

// Attach Behaviors
\Craft::$app->getResponse()->attachBehavior('cache-control', CacheControlBehavior::class);
\Craft::$app->getResponse()->attachBehavior('tag-header', TagHeaderBehavior::class);

}

// ServiceLocators
Expand Down
6 changes: 4 additions & 2 deletions src/config.example.php
Original file line number Diff line number Diff line change
Expand Up @@ -53,10 +53,12 @@
'cloudflare' => [
'tagHeaderName' => 'Cache-Tag',
'tagHeaderDelimiter' => ',',
'apiToken' => getenv('CLOUDFLARE_API_TOKEN'),
'zoneId' => getenv('CLOUDFLARE_ZONE_ID'),
'domain' => getenv('CLOUDFLARE_DOMAIN'),
// deprecated, do not use for new installs
'apiKey' => getenv('CLOUDFLARE_API_KEY'),
'apiEmail' => getenv('CLOUDFLARE_API_EMAIL'),
'zoneId' => getenv('CLOUDFLARE_ZONE_ID'),
'domain' => getenv('CLOUDFLARE_DOMAIN')
],

// Dummy driver (default)
Expand Down
45 changes: 31 additions & 14 deletions src/drivers/Cloudflare.php
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
<?php namespace ostark\upper\drivers;

use Craft;
use GuzzleHttp\Client;
use GuzzleHttp\Exception\BadResponseException;
use ostark\upper\exceptions\CloudflareApiException;
Expand All @@ -22,6 +23,8 @@ class Cloudflare extends AbstractPurger implements CachePurgeInterface

public $apiEmail;

public $apiToken;

public $zoneId;

public $domain;
Expand All @@ -35,7 +38,7 @@ class Cloudflare extends AbstractPurger implements CachePurgeInterface
public function purgeTag(string $tag)
{
if ($this->useLocalTags) {
return $this->purgeUrlsByTag($tag);
return $this->purgeUrlsByTag($tag);
}

return $this->sendRequest('DELETE', 'purge_cache', [
Expand All @@ -57,12 +60,12 @@ public function purgeUrls(array $urls)
}

// prefix urls with domain
$files = array_map(function ($url) {
$files = array_map(function($url) {
return rtrim($this->domain, '/') . $url;
}, $urls);

// Chunk larger collections to meet the API constraints
foreach(array_chunk($files, self::MAX_URLS_PER_PURGE) as $fileGroup) {
foreach (array_chunk($files, self::MAX_URLS_PER_PURGE) as $fileGroup) {
$this->sendRequest('DELETE', 'purge_cache', [
'files' => $fileGroup
]);
Expand Down Expand Up @@ -100,21 +103,12 @@ public function purgeAll()
*/
protected function sendRequest($method = 'DELETE', string $type, array $params = [])
{
$client = new Client([
'base_uri' => self::API_ENDPOINT,
'headers' => [
'Content-Type' => 'application/json',
'X-Auth-Key' => $this->apiKey,
'X-Auth-Email' => $this->apiEmail,
]
]);
$client = $this->getClient();

try {

$uri = "zones/{$this->zoneId}/$type";
$uri = "zones/{$this->zoneId}/$type";
$options = (count($params)) ? ['json' => $params] : [];
$client->request($method, $uri, $options);

} catch (BadResponseException $e) {

throw CloudflareApiException::create(
Expand All @@ -126,6 +120,29 @@ protected function sendRequest($method = 'DELETE', string $type, array $params =
return true;
}

private function getClient()
{
$headers = [
'Content-Type' => 'application/json',
];

if ($this->usesLegacyApiKey()) {
Craft::$app->getDeprecator()->log('Upper Config: Cloudflare $apiKey', 'Globally scoped Cloudflare API keys are deprecated for security. Create a scoped token instead and use via the `apiToken` key in the driver config.');

$headers['X-Auth-Key'] = $this->apiKey;
$headers['X-Auth-Email'] = $this->apiEmail;
} else {
$headers['Authorization'] = 'Bearer ' . $this->apiToken;
}

return new Client([
'base_uri' => self::API_ENDPOINT,
'headers' => $headers,
]);
}

private function usesLegacyApiKey()
{
return !isset($this->apiToken) && isset($this->apiKey);
}
}

0 comments on commit 8543f5a

Please sign in to comment.