diff --git a/composer.json b/composer.json index 917bbffa..8ab5d95a 100644 --- a/composer.json +++ b/composer.json @@ -29,6 +29,7 @@ "symfony/options-resolver": "^2.7 || ^3.0" }, "require-dev": { + "defuse/php-encryption": "^2.0", "phpunit/phpunit": "^4.0 || ^5.1", "mockery/mockery": "^0.9", "cache/integration-tests": "^0.11", diff --git a/src/Adapter/Common/CacheItem.php b/src/Adapter/Common/CacheItem.php index ef618647..ca443a62 100644 --- a/src/Adapter/Common/CacheItem.php +++ b/src/Adapter/Common/CacheItem.php @@ -161,6 +161,9 @@ public function expiresAfter($time) return $this; } + /** + * {@inheritdoc} + */ public function getTags() { $this->initialize(); @@ -168,6 +171,9 @@ public function getTags() return $this->tags; } + /** + * {@inheritdoc} + */ public function addTag($tag) { $this->initialize(); @@ -177,6 +183,9 @@ public function addTag($tag) return $this; } + /** + * {@inheritdoc} + */ public function setTags(array $tags) { $this->initialize(); diff --git a/src/Encryption/.github/PULL_REQUEST_TEMPLATE.md b/src/Encryption/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 00000000..4a339b4c --- /dev/null +++ b/src/Encryption/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,5 @@ +This is a READ ONLY repository. + +Please make your pull request to https://github.com/php-cache/cache + +Thank you for contributing. diff --git a/src/Encryption/.gitignore b/src/Encryption/.gitignore new file mode 100644 index 00000000..987e2a25 --- /dev/null +++ b/src/Encryption/.gitignore @@ -0,0 +1,2 @@ +composer.lock +vendor diff --git a/src/Encryption/.travis.yml b/src/Encryption/.travis.yml new file mode 100644 index 00000000..7d62c416 --- /dev/null +++ b/src/Encryption/.travis.yml @@ -0,0 +1,22 @@ +language: php +sudo: false + +matrix: + include: + - php: 7.0 + +cache: + directories: + - "$HOME/.composer/cache" + +install: + - composer update + +script: + - ./vendor/bin/phpunit --coverage-clover=coverage.xml + +after_success: + - pip install --user codecov && codecov + +notifications: + email: false diff --git a/src/Encryption/Changelog.md b/src/Encryption/Changelog.md new file mode 100644 index 00000000..3ac102f1 --- /dev/null +++ b/src/Encryption/Changelog.md @@ -0,0 +1,7 @@ +# Change Log + +The change log describes what is "Added", "Removed", "Changed" or "Fixed" between each release. + +## UNRELEASED + + diff --git a/src/Encryption/EncryptedCachePool.php b/src/Encryption/EncryptedCachePool.php new file mode 100644 index 00000000..ec240567 --- /dev/null +++ b/src/Encryption/EncryptedCachePool.php @@ -0,0 +1,137 @@ +, Tobias Nyholm + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + + +namespace Cache\Encryption; + +use Defuse\Crypto\Key; +use Psr\Cache\CacheItemInterface; +use Psr\Cache\CacheItemPoolInterface; + +/** + * Wrapps a CacheItemInterface with EncryptedItemDecorator. + * + * @author Daniel Bannert + */ +class EncryptedCachePool implements CacheItemPoolInterface +{ + /** + * @type CacheItemPoolInterface + */ + private $cachePool; + + /** + * @type Key + */ + private $key; + + /** + * @param CacheItemPoolInterface $cachePool + * @param Key $key + */ + public function __construct(CacheItemPoolInterface $cachePool, Key $key) + { + $this->cachePool = $cachePool; + $this->key = $key; + } + + /** + * {@inheritdoc} + */ + public function getItem($key) + { + $item = $this->cachePool->getItem($key); + + if (!($item instanceof EncryptedItemDecorator)) { + return new EncryptedItemDecorator($item, $this->key); + } + + return $item; + } + + /** + * {@inheritdoc} + */ + public function getItems(array $keys = []) + { + return array_map(function (CacheItemInterface $inner) { + if (!($inner instanceof EncryptedItemDecorator)) { + return new EncryptedItemDecorator($inner, $this->key); + } + + return $inner; + }, $this->cachePool->getItems($keys)); + } + + /** + * {@inheritdoc} + */ + public function hasItem($key) + { + return $this->cachePool->hasItem($key); + } + + /** + * {@inheritdoc} + */ + public function clear() + { + return $this->cachePool->clear(); + } + + /** + * {@inheritdoc} + */ + public function deleteItem($key) + { + return $this->cachePool->deleteItem($key); + } + + /** + * {@inheritdoc} + */ + public function deleteItems(array $keys) + { + return $this->cachePool->deleteItems($keys); + } + + /** + * {@inheritdoc} + */ + public function save(CacheItemInterface $item) + { + if (!($item instanceof EncryptedItemDecorator)) { + $item = new EncryptedItemDecorator($item, $this->key); + } + + return $this->cachePool->save($item); + } + + /** + * {@inheritdoc} + */ + public function saveDeferred(CacheItemInterface $item) + { + if (!($item instanceof EncryptedItemDecorator)) { + $item = new EncryptedItemDecorator($item, $this->key); + } + + return $this->cachePool->saveDeferred($item); + } + + /** + * {@inheritdoc} + */ + public function commit() + { + return $this->cachePool->commit(); + } +} diff --git a/src/Encryption/EncryptedItemDecorator.php b/src/Encryption/EncryptedItemDecorator.php new file mode 100644 index 00000000..2842b189 --- /dev/null +++ b/src/Encryption/EncryptedItemDecorator.php @@ -0,0 +1,179 @@ +, Tobias Nyholm + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + + +namespace Cache\Encryption; + +use Cache\Adapter\Common\HasExpirationDateInterface; +use Cache\Taggable\TaggableItemInterface; +use Defuse\Crypto\Crypto; +use Defuse\Crypto\Key; +use Psr\Cache\CacheItemInterface; + +/** + * Encrypt and Decrypt all the stored items. + * + * @author Daniel Bannert + */ +class EncryptedItemDecorator implements CacheItemInterface, HasExpirationDateInterface, TaggableItemInterface +{ + /** + * @type CacheItemInterface + */ + private $cacheItem; + + /** + * @type Key + */ + private $key; + + /** + * @param CacheItemInterface $cacheItem + * @param Key $key + */ + public function __construct(CacheItemInterface $cacheItem, Key $key) + { + $this->cacheItem = $cacheItem; + $this->key = $key; + } + + /** + * {@inheritdoc} + */ + public function getKey() + { + return $this->cacheItem->getKey(); + } + + /** + * {@inheritdoc} + */ + public function set($value) + { + $type = gettype($value); + + if ($type === 'object') { + $value = serialize($value); + } + + $json = json_encode(['type' => $type, 'value' => $value]); + + $this->cacheItem->set(Crypto::encrypt($json, $this->key)); + + return $this; + } + + /** + * {@inheritdoc} + */ + public function get() + { + if (!$this->isHit()) { + return; + } + + $item = json_decode(Crypto::decrypt($this->cacheItem->get(), $this->key), true); + + return $this->transform($item); + } + + /** + * {@inheritdoc} + */ + public function isHit() + { + return $this->cacheItem->isHit(); + } + + /** + * {@inheritdoc} + */ + public function getExpirationDate() + { + return $this->cacheItem->getExpirationDate(); + } + + /** + * {@inheritdoc} + */ + public function expiresAt($expiration) + { + $this->cacheItem->expiresAt($expiration); + + return $this; + } + + /** + * {@inheritdoc} + */ + public function expiresAfter($time) + { + $this->cacheItem->expiresAfter($time); + + return $this; + } + + /** + * {@inheritdoc} + */ + public function getTags() + { + return $this->cacheItem->getTags(); + } + + /** + * {@inheritdoc} + */ + public function addTag($tag) + { + $this->cacheItem->addTag($tag); + + return $this; + } + + /** + * {@inheritdoc} + */ + public function setTags(array $tags) + { + $this->cacheItem->setTags($tags); + + return $this; + } + + /** + * Creating a copy of the orginal CacheItemInterface object. + */ + public function __clone() + { + $this->cacheItem = clone $this->cacheItem; + } + + /** + * Transfrom value back to it orginal type. + * + * @param array $item + * + * @return mixed + */ + private function transform(array $item) + { + if ($item['type'] === 'object') { + return unserialize($item['value']); + } + + $value = $item['value']; + + settype($value, $item['type']); + + return $value; + } +} diff --git a/src/Encryption/LICENSE b/src/Encryption/LICENSE new file mode 100644 index 00000000..26e041d0 --- /dev/null +++ b/src/Encryption/LICENSE @@ -0,0 +1,22 @@ +The MIT License (MIT) + +Copyright (c) 2015 - 2016 Aaron Scherer + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + diff --git a/src/Encryption/README.md b/src/Encryption/README.md new file mode 100644 index 00000000..5de3664a --- /dev/null +++ b/src/Encryption/README.md @@ -0,0 +1,31 @@ +# Encryption PSR-6 Cache pool +[![Gitter](https://badges.gitter.im/php-cache/cache.svg)](https://gitter.im/php-cache/cache?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge) +[![Latest Stable Version](https://poser.pugx.org/cache/encryption-cache/v/stable)](https://packagist.org/packages/cache/encryption-cache) +[![codecov.io](https://codecov.io/github/php-cache/encryption-cache/coverage.svg?branch=master)](https://codecov.io/github/php-cache/encryption-cache?branch=master) +[![Total Downloads](https://poser.pugx.org/cache/encryption-cache/downloads)](https://packagist.org/packages/cache/encryption-cache) +[![Monthly Downloads](https://poser.pugx.org/cache/encryption-cache/d/monthly.png)](https://packagist.org/packages/cache/encryption-cache) +[![Software License](https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square)](LICENSE) + +This repository has a encryption wrapper that makes the PSR-6 cache implementation encrypted. + +Encryption and decryption are both expensive operations, and frequent reads from an encrypted data store can quickly become a bottleneck in otherwise performant applications. Use encrypted caches sparingly. + + +### Install + +```bash +composer require cache/encryption-cache +``` + +### Use + +Read the [documentation on usage](http://www.php-cache.com/en/latest/encryption/). + +### Implement + +Read the [documentation on implementation](http://www.php-cache.com/en/latest/implementing-cache-pools/encryption/). + +### Contribute + +Contributions are very welcome! Send a pull request to the [main repository](https://github.com/php-cache/cache) or +report any issues you find on the [issue tracker](http://issues.php-cache.com). diff --git a/src/Encryption/Tests/IntegrationPoolTest.php b/src/Encryption/Tests/IntegrationPoolTest.php new file mode 100644 index 00000000..01e6802b --- /dev/null +++ b/src/Encryption/Tests/IntegrationPoolTest.php @@ -0,0 +1,31 @@ +, Tobias Nyholm + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + + +namespace Cache\Encryption\Tests; + +use Cache\Adapter\PHPArray\ArrayCachePool; +use Cache\Encryption\EncryptedCachePool; +use Cache\IntegrationTests\CachePoolTest; +use Defuse\Crypto\Key; + +class IntegrationPoolTest extends CachePoolTest +{ + private $cacheArray = []; + + public function createCachePool() + { + return new EncryptedCachePool( + new ArrayCachePool(null, $this->cacheArray), + Key::loadFromAsciiSafeString('def000007c57b06c65b0df4bcac939924e42605d8d76e1462b619318bf94107c28db30c5394b4242db5e45563e1226cffcdff8123fa214ea1fcc4aa10b0ddb1b4a587b7e') + ); + } +} diff --git a/src/Encryption/composer.json b/src/Encryption/composer.json new file mode 100644 index 00000000..ee9da6b5 --- /dev/null +++ b/src/Encryption/composer.json @@ -0,0 +1,48 @@ +{ + "name": "cache/encryption-cache", + "type": "library", + "description": "Add tag support to your PSR-6 cache implementation", + "keywords": [ + "cache", + "psr6", + "encrypted", + "encryption" + ], + "homepage": "http://www.php-cache.com/en/latest/", + "license": "MIT", + "authors": [ + { + "name": "Aaron Scherer", + "email": "aequasi@gmail.com", + "homepage": "https://github.com/aequasi" + }, + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com", + "homepage": "https://github.com/nyholm" + }, + { + "name": "Daniel Bannert", + "email": "d.bannert@anolilab.de", + "homepage": "https://github.com/prisis" + } + ], + "require": { + "php": "^5.5 || ^7.0", + "defuse/php-encryption": "^2.0", + "psr/cache": "^1.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.0 || ^5.1", + "cache/integration-tests": "^0.11", + "cache/array-adapter": "^0.4" + }, + "autoload": { + "psr-4": { + "Cache\\Encryption\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + } +} diff --git a/src/Encryption/phpunit.xml.dist b/src/Encryption/phpunit.xml.dist new file mode 100644 index 00000000..3a59138d --- /dev/null +++ b/src/Encryption/phpunit.xml.dist @@ -0,0 +1,36 @@ + + + + + + ./Tests/ + + + + + + benchmark + + + + + + ./ + + ./Resources + ./Tests + ./vendor + + + + diff --git a/src/Taggable/phpunit.xml.dist b/src/Taggable/phpunit.xml.dist index 063ed326..3a59138d 100644 --- a/src/Taggable/phpunit.xml.dist +++ b/src/Taggable/phpunit.xml.dist @@ -9,7 +9,7 @@ processIsolation="false" stopOnFailure="false" syntaxCheck="false" - bootstrap="vendor/autoload.php" + bootstrap="vendor/autoload.php" >