Added Encryption to Caching

- Access Token is encrypted with Secret Key, and stored in file
- Code to auto-update existing cache files to use encrypted token
- Tests fixes accordingly
This commit is contained in:
japatel
2015-01-26 13:38:17 -06:00
parent d84ddf85c9
commit 830cb16f0f
6 changed files with 128 additions and 11 deletions

View File

@@ -11,6 +11,7 @@ use PayPal\Exception\PayPalConfigurationException;
use PayPal\Exception\PayPalConnectionException; use PayPal\Exception\PayPalConnectionException;
use PayPal\Handler\IPayPalHandler; use PayPal\Handler\IPayPalHandler;
use PayPal\Rest\ApiContext; use PayPal\Rest\ApiContext;
use PayPal\Security\Cipher;
/** /**
* Class OAuthTokenCredential * Class OAuthTokenCredential
@@ -74,6 +75,13 @@ class OAuthTokenCredential extends PayPalResourceModel
*/ */
private $tokenCreateTime; private $tokenCreateTime;
/**
* Instance of cipher used to encrypt/decrypt data while storing in cache.
*
* @var Cipher
*/
private $cipher;
/** /**
* Construct * Construct
* *
@@ -84,6 +92,7 @@ class OAuthTokenCredential extends PayPalResourceModel
{ {
$this->clientId = $clientId; $this->clientId = $clientId;
$this->clientSecret = $clientSecret; $this->clientSecret = $clientSecret;
$this->cipher = new Cipher($this->clientSecret);
$this->logger = PayPalLoggingManager::getInstance(__CLASS__); $this->logger = PayPalLoggingManager::getInstance(__CLASS__);
} }
@@ -120,9 +129,20 @@ class OAuthTokenCredential extends PayPalResourceModel
$token = AuthorizationCache::pull($config, $this->clientId); $token = AuthorizationCache::pull($config, $this->clientId);
if ($token) { if ($token) {
// We found it // We found it
$this->accessToken = $token['accessToken']; // This code block is for backward compatibility only.
if (array_key_exists('accessToken', $token)) {
$this->accessToken = $token['accessToken'];
}
$this->tokenCreateTime = $token['tokenCreateTime']; $this->tokenCreateTime = $token['tokenCreateTime'];
$this->tokenExpiresIn = $token['tokenExpiresIn']; $this->tokenExpiresIn = $token['tokenExpiresIn'];
// Case where we have an old unencrypted cache file
if (!array_key_exists('accessTokenEncrypted', $token)) {
AuthorizationCache::push($config, $this->clientId, $this->encrypt($this->accessToken), $this->tokenCreateTime, $this->tokenExpiresIn);
} else {
$this->accessToken = $this->decrypt($token['accessTokenEncrypted']);
}
} }
// Check if Access Token is not null and has not expired. // Check if Access Token is not null and has not expired.
@@ -137,11 +157,12 @@ class OAuthTokenCredential extends PayPalResourceModel
$this->accessToken = null; $this->accessToken = null;
} }
// If accessToken is Null, obtain a new token // If accessToken is Null, obtain a new token
if ($this->accessToken == null) { if ($this->accessToken == null) {
// Get a new one by making calls to API // Get a new one by making calls to API
$this->updateAccessToken($config); $this->updateAccessToken($config);
AuthorizationCache::push($config, $this->clientId, $this->accessToken, $this->tokenCreateTime, $this->tokenExpiresIn); AuthorizationCache::push($config, $this->clientId, $this->encrypt($this->accessToken), $this->tokenCreateTime, $this->tokenExpiresIn);
} }
return $this->accessToken; return $this->accessToken;
@@ -231,6 +252,7 @@ class OAuthTokenCredential extends PayPalResourceModel
* *
* @param array $config * @param array $config
* @return null * @return null
* @throws PayPalConnectionException
*/ */
private function generateAccessToken($config, $refreshToken = null) private function generateAccessToken($config, $refreshToken = null)
{ {
@@ -259,4 +281,26 @@ class OAuthTokenCredential extends PayPalResourceModel
return $this->accessToken; return $this->accessToken;
} }
/**
* Helper method to encrypt data using clientSecret as key
*
* @param $data
* @return string
*/
public function encrypt($data)
{
return $this->cipher->encrypt($data);
}
/**
* Helper method to decrypt data using clientSecret as key
*
* @param $data
* @return string
*/
public function decrypt($data)
{
return $this->cipher->decrypt($data);
}
} }

View File

@@ -49,6 +49,7 @@ abstract class AuthorizationCache
* @param $accessToken * @param $accessToken
* @param $tokenCreateTime * @param $tokenCreateTime
* @param $tokenExpiresIn * @param $tokenExpiresIn
* @throws \Exception
*/ */
public static function push($config = null, $clientId, $accessToken, $tokenCreateTime, $tokenExpiresIn) public static function push($config = null, $clientId, $accessToken, $tokenCreateTime, $tokenExpiresIn)
{ {
@@ -58,7 +59,7 @@ abstract class AuthorizationCache
$cachePath = self::cachePath($config); $cachePath = self::cachePath($config);
if (!is_dir(dirname($cachePath))) { if (!is_dir(dirname($cachePath))) {
if (mkdir(dirname($cachePath), 0755, true) == false) { if (mkdir(dirname($cachePath), 0755, true) == false) {
return; throw new \Exception("Failed to create directory at $cachePath");
} }
} }
@@ -68,12 +69,14 @@ abstract class AuthorizationCache
if (is_array($tokens)) { if (is_array($tokens)) {
$tokens[$clientId] = array( $tokens[$clientId] = array(
'clientId' => $clientId, 'clientId' => $clientId,
'accessToken' => $accessToken, 'accessTokenEncrypted' => $accessToken,
'tokenCreateTime' => $tokenCreateTime, 'tokenCreateTime' => $tokenCreateTime,
'tokenExpiresIn' => $tokenExpiresIn 'tokenExpiresIn' => $tokenExpiresIn
); );
} }
file_put_contents($cachePath, json_encode($tokens)); if(!file_put_contents($cachePath, json_encode($tokens))) {
throw new \Exception("Failed to write cache");
};
} }
/** /**

View File

@@ -0,0 +1,57 @@
<?php
namespace PayPal\Security;
/**
* Class Cipher
*
* Helper class to encrypt/decrypt data with secret key
*
* @package PayPal\Security
*/
class Cipher
{
private $secretKey;
/**
* Fixed IV Size
*/
const IV_SIZE = 16;
function __construct($secretKey)
{
$this->$secretKey = $secretKey;
}
/**
* Encrypts the input text using the cipher key
*
* @param $input
* @return string
*/
function encrypt($input)
{
// Create a random IV. Not using mcrypt to generate one, as to not have a dependency on it.
$iv = substr(uniqid("", true), 0, Cipher::IV_SIZE);
// Encrypt the data
$encrypted = openssl_encrypt($input, "AES-256-CBC", $this->secretKey, 0, $iv);
// Encode the data with IV as prefix
return base64_encode($iv . $encrypted);
}
/**
* Decrypts the input text from the cipher key
*
* @param $input
* @return string
*/
function decrypt($input)
{
// Decode the IV + data
$input = base64_decode($input);
// Remove the IV
$iv = substr($input, 0, Cipher::IV_SIZE);
// Return Decrypted Data
return openssl_decrypt(substr($input, Cipher::IV_SIZE), "AES-256-CBC", $this->secretKey, 0, $iv);
}
}

View File

@@ -23,7 +23,6 @@ $invoice = new Invoice();
$invoice $invoice
->setMerchantInfo(new MerchantInfo()) ->setMerchantInfo(new MerchantInfo())
->setBillingInfo(array(new BillingInfo())) ->setBillingInfo(array(new BillingInfo()))
->setItems(array(new InvoiceItem()))
->setNote("Medical Invoice 16 Jul, 2013 PST") ->setNote("Medical Invoice 16 Jul, 2013 PST")
->setPaymentTerm(new PaymentTerm()) ->setPaymentTerm(new PaymentTerm())
->setShippingInfo(new ShippingInfo()); ->setShippingInfo(new ShippingInfo());
@@ -61,7 +60,8 @@ $billing[0]
// ### Items List // ### Items List
// You could provide the list of all items for // You could provide the list of all items for
// detailed breakdown of invoice // detailed breakdown of invoice
$items = $invoice->getItems(); $items = array();
$items[0] = new InvoiceItem();
$items[0] $items[0]
->setName("Sutures") ->setName("Sutures")
->setQuantity(100) ->setQuantity(100)
@@ -70,6 +70,17 @@ $items[0]
$items[0]->getUnitPrice() $items[0]->getUnitPrice()
->setCurrency("USD") ->setCurrency("USD")
->setValue(5); ->setValue(5);
// Second Item
$items[1] = new InvoiceItem();
$items[1]
->setName("Injection")
->setQuantity(5)
->setUnitPrice(new Currency());
$items[1]->getUnitPrice()
->setCurrency("USD")
->setValue(5);
$invoice->setItems($items);
$invoice->getPaymentTerm() $invoice->getPaymentTerm()
->setTermType("NET_45"); ->setTermType("NET_45");

View File

@@ -48,9 +48,11 @@ class OAuthTokenCredentialTest extends \PHPUnit_Framework_TestCase
'cache.enabled' => true, 'cache.enabled' => true,
'cache.FileName' => AuthorizationCacheTest::CACHE_FILE 'cache.FileName' => AuthorizationCacheTest::CACHE_FILE
); );
$cred = new OAuthTokenCredential('clientId', 'clientSecret');
//{"clientId":{"clientId":"clientId","accessToken":"accessToken","tokenCreateTime":1421204091,"tokenExpiresIn":288000000}} //{"clientId":{"clientId":"clientId","accessToken":"accessToken","tokenCreateTime":1421204091,"tokenExpiresIn":288000000}}
AuthorizationCache::push($config, 'clientId', 'accessToken', 1421204091, 288000000); AuthorizationCache::push($config, 'clientId', $cred->encrypt('accessToken'), 1421204091, 288000000);
$cred = new OAuthTokenCredential('clientId', 'clientSecret');
$apiContext = new ApiContext($cred); $apiContext = new ApiContext($cred);
$apiContext->setConfig($config); $apiContext->setConfig($config);
$this->assertEquals('clientId', $cred->getClientId()); $this->assertEquals('clientId', $cred->getClientId());

View File

@@ -76,7 +76,7 @@ class AuthorizationCacheTest extends \PHPUnit_Framework_TestCase
$tokens = json_decode($contents, true); $tokens = json_decode($contents, true);
$this->assertNotNull($contents); $this->assertNotNull($contents);
$this->assertEquals('clientId', $tokens['clientId']['clientId']); $this->assertEquals('clientId', $tokens['clientId']['clientId']);
$this->assertEquals('accessToken', $tokens['clientId']['accessToken']); $this->assertEquals('accessToken', $tokens['clientId']['accessTokenEncrypted']);
$this->assertEquals('tokenCreateTime', $tokens['clientId']['tokenCreateTime']); $this->assertEquals('tokenCreateTime', $tokens['clientId']['tokenCreateTime']);
$this->assertEquals('tokenExpiresIn', $tokens['clientId']['tokenExpiresIn']); $this->assertEquals('tokenExpiresIn', $tokens['clientId']['tokenExpiresIn']);
@@ -97,7 +97,7 @@ class AuthorizationCacheTest extends \PHPUnit_Framework_TestCase
$this->assertNotNull($result); $this->assertNotNull($result);
$this->assertTrue(is_array($result)); $this->assertTrue(is_array($result));
$this->assertEquals('clientId', $result['clientId']); $this->assertEquals('clientId', $result['clientId']);
$this->assertEquals('accessToken', $result['accessToken']); $this->assertEquals('accessToken', $result['accessTokenEncrypted']);
$this->assertEquals('tokenCreateTime', $result['tokenCreateTime']); $this->assertEquals('tokenCreateTime', $result['tokenCreateTime']);
$this->assertEquals('tokenExpiresIn', $result['tokenExpiresIn']); $this->assertEquals('tokenExpiresIn', $result['tokenExpiresIn']);