diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
new file mode 100644
index 0000000..f5b83f7
--- /dev/null
+++ b/.github/workflows/main.yml
@@ -0,0 +1,40 @@
+name: CI Tests
+on:
+ push:
+ branches: [ master ]
+ pull_request:
+ branches: [ master ]
+jobs:
+ test:
+ runs-on: ubuntu-latest
+ strategy:
+ matrix:
+ php-versions: ['5.6', '7.4']
+ name: Testing PHP ${{ matrix.php-versions }}
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v2
+ - name: Install PHP
+ uses: shivammathur/setup-php@v2
+ with:
+ php-version: ${{ matrix.php-versions }}
+ - name: Check PHP Version
+ run: php -v
+ - name: Install Dependencies for PHP ${{ matrix.php-versions }}
+ run: composer install -q --no-ansi --no-interaction --no-scripts --no-suggest --no-progress --prefer-dist
+ - name: Execute tests against PHP ${{ matrix.php-versions }}
+ run: composer test
+ typecheck:
+ runs-on: ubuntu-latest
+ name: Typechecks against PSALM
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v2
+ - name: Check PHP Version
+ run: php -v
+ - name: Install Dependencies
+ run: composer install -q --no-ansi --no-interaction --no-scripts --no-suggest --no-progress --prefer-dist
+ - name: Downloading
+ run: wget https://github.com/vimeo/psalm/releases/download/3.12.1/psalm.phar
+ - name: Typechecking
+ run: php psalm.phar
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..cac762f
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,2 @@
+/vendor/
+/.idea/
diff --git a/README.md b/README.md
index 6a6cabb..2335133 100644
--- a/README.md
+++ b/README.md
@@ -1,2 +1,105 @@
-# vaultPHP
-A PHP library for vault
+# PHP Hashicorp Vault Client
+
+PHP Client Library for the Hashicorp Vault Service.
+This Client follows the Request and Response Data equal to the Hashicorp Vault Client Documentation.
+- Authentication https://www.vaultproject.io/api-docs/auth
+- Secret Engines https://www.vaultproject.io/api-docs/secret
+
+Feel free to open Pull Requests to add improvements or missing functionality.
+
+## Implemented Functionality:
+- Auth
+ - User/Password
+ - Token
+ - Kubernetes
+- Secret Engines
+ - Transit Engine
+ - Encrypt/Decrypt
+ - Update Key Config
+ - Create Key
+ - Delete Key
+ - List Keys
+
+## Basic Usage
+
+```php
+// setting up independent http client
+$httpClient = new Client();
+
+// setting up vault auth provider
+$auth = new Token('foo');
+
+// creating the vault request client
+$client = new VaultClient(
+ $httpClient,
+ $auth,
+ 'http://127.0.0.1:8200'
+);
+
+// selecting the desired secret engine
+// e.g. Transit Secret Engine
+$api = new Transit($client);
+
+// calling specific endpoint
+$response = $api->listKeys();
+
+//reading results
+var_dump($response->getKeys());
+//...
+//...
+//Profit...
+```
+
+#### VaultClient
+
+````php
+public function __construct(
+ HttpClient $httpClient,
+ AuthenticationProviderInterface $authProvider,
+ string $apiHost
+)
+````
+
+`HttpClient` takes every PSR-18 compliant HTTP Client Adapter like `"php-http/curl-client": "^1.7"`
+
+`AuthenticationProviderInterface` Authentication Provider from `/authentication/provider/*`
+
+`$apiHost` Hashicorp Vault REST Endpoint URL
+
+## Bulk Requests
+Bulk Requests **will not** throw `InvalidDataExceptions`. Using Bulk Requests requires to iterate through the Response
+and calling `hasErrors` within the `BasicMetaResponse`.
+
+## Exceptions
+Calling library methods will throw exceptions, indicating where ever invalid data was provided
+or HTTP errors occurred or Vault Generic Endpoint Errors are encountered.
+___
+
+`VaultException`
+
+Generic Root Exception where every exception in this library extends from.
+___
+
+`VaultHttpException`
+
+Exception will thrown when something inside the HTTP handling will cause an error.
+___
+
+`VaultAuthenticationException`
+
+Will be thrown when API Endpoint Authentication fails.
+___
+
+`VaultResponseException`
+
+Will be thrown on 5xx status code errors.
+___
+
+`InvalidRouteException`
+
+Calling an Invalid/Non Existing/Disabled Vault API Endpoint will throw this Exception.
+___
+
+`InvalidDataException`
+
+Exception indicates a failed server payload validation.
diff --git a/composer.json b/composer.json
new file mode 100644
index 0000000..5006cd4
--- /dev/null
+++ b/composer.json
@@ -0,0 +1,39 @@
+{
+ "name": "mittwald/vault-php",
+ "type": "library",
+ "license": "MIT",
+ "version": "1.0.0",
+ "homepage": "https://www.mittwald.de/",
+ "description": "PHP library for Vault",
+ "require": {
+ "ext-json": "*",
+ "guzzlehttp/psr7": ">=1.6",
+ "php": ">=5.6",
+ "php-http/httplug": ">=1.1.0"
+ },
+ "suggest": {
+ "php-http/curl-client": "CURL Client Adapter"
+ },
+ "require-dev": {
+ "phpunit/phpunit": ">=5.0.0"
+ },
+ "authors": [
+ {
+ "name": "Marco Rieger",
+ "email": "m.rieger@mittwald.de"
+ }
+ ],
+ "autoload": {
+ "psr-4": {
+ "VaultPHP\\": "src\\VaultPHP\\"
+ }
+ },
+ "autoload-dev": {
+ "psr-4": {
+ "Test\\VaultPHP\\": "tests\\VaultPHP\\"
+ }
+ },
+ "scripts": {
+ "test": "php ./vendor/bin/phpunit --configuration ./phpunit.xml.dist"
+ }
+}
diff --git a/docker-compose.yml b/docker-compose.yml
new file mode 100644
index 0000000..5f642fb
--- /dev/null
+++ b/docker-compose.yml
@@ -0,0 +1,17 @@
+version: "3"
+services:
+ vault:
+ image: vault:latest
+ container_name: vault
+ restart: unless-stopped
+ ports:
+ - "8200:8200"
+ environment:
+ VAULT_ADDR: 'http://0.0.0.0:8200'
+ VAULT_DEV_ROOT_TOKEN_ID: 'test'
+ VAULT_TOKEN: 'test'
+ cap_add:
+ - IPC_LOCK
+ healthcheck:
+ retries: 5
+ command: server -dev
diff --git a/examples/BulkOperations.php b/examples/BulkOperations.php
new file mode 100644
index 0000000..7a4d7bb
--- /dev/null
+++ b/examples/BulkOperations.php
@@ -0,0 +1,83 @@
+ './ssl.pem',
+ CURLOPT_SSLCERTTYPE => 'PEM',
+ CURLOPT_SSLCERTPASSWD => 'fooBar',
+]);
+
+// provide hashicorp vault auth
+$authenticationProvider = new Token('test');
+
+// initalize the vault request client
+$vaultClient = new VaultClient(
+ $httpClient,
+ $authenticationProvider,
+ 'https://127.0.0.1:8200'
+);
+
+// choose your secret engine api
+$transitApi = new Transit($vaultClient);
+
+// do fancy stuff
+try {
+ // create key
+ $exampleKey = new CreateKeyRequest('exampleKeyName');
+ $exampleKey->setType(EncryptionType::RSA_2048);
+ $transitApi->createKey($exampleKey);
+
+ $encryptRequest = new EncryptDataBulkRequest('exampleKeyName');
+ $encryptRequest->addBulkRequests([
+ new EncryptData('cryptMeBabyOneMoreTime::1'),
+ new EncryptData('cryptMeBabyOneMoreTime::2'),
+ new EncryptData('cryptMeBabyOneMoreTime::3'),
+ new EncryptData('cryptMeBabyOneMoreTime::4'),
+ ]);
+ $encryptBulkResponse = $transitApi->encryptDataBulk($encryptRequest);
+
+ foreach($encryptBulkResponse as $bulkResult) {
+ // BULK REQUEST WON'T THROW INVALID DATA EXCEPTIONS
+ // SO YOU ARE RESPONSABLE TO CHECK IF EVERY BULK WAS
+ // SUCCESSFULLY PROCESSED
+ if (!$bulkResult->getBasicMetaResponse()->hasErrors()) {
+ var_dump($bulkResult->getCiphertext());
+ }
+ }
+
+ // update key config and allow deletion
+ $keyConfigExample = new UpdateKeyConfigRequest('exampleKeyName');
+ $keyConfigExample->setDeletionAllowed(true);
+ $transitApi->updateKeyConfig($keyConfigExample);
+
+ // delete key
+ $transitApi->deleteKey('exampleKeyName');
+
+ // list keys
+ $listKeyResponse = $transitApi->listKeys();
+ var_dump($listKeyResponse->getKeys());
+
+} catch (VaultResponseException $exception) {
+ var_dump($exception->getMessage());
+ var_dump($exception->getResponse());
+ var_dump($exception->getRequest());
+
+} catch (VaultException $exception) {
+ var_dump($exception->getMessage());
+}
diff --git a/examples/TransitEncryption.php b/examples/TransitEncryption.php
new file mode 100644
index 0000000..e245271
--- /dev/null
+++ b/examples/TransitEncryption.php
@@ -0,0 +1,81 @@
+ './ssl.pem',
+ CURLOPT_SSLCERTTYPE => 'PEM',
+ CURLOPT_SSLCERTPASSWD => 'fooBar',
+]);
+
+// provide hashicorp vault auth
+$authenticationProvider = new Token('test');
+
+// initalize the vault request client
+$vaultClient = new VaultClient(
+ $httpClient,
+ $authenticationProvider,
+ 'https://127.0.0.1:8200'
+);
+
+// choose your secret engine api
+$transitApi = new Transit($vaultClient);
+
+// do fancy stuff
+try {
+ // create key
+ $exampleKey = new CreateKeyRequest('exampleKeyName');
+ $exampleKey->setType(EncryptionType::CHA_CHA_20_POLY_1305);
+ $transitApi->createKey($exampleKey);
+
+ // list keys
+ $listKeyResponse = $transitApi->listKeys();
+ var_dump($listKeyResponse->getKeys());
+
+ // encrypt data
+ $encryptExample = new EncryptDataRequest('exampleKeyName', 'encryptMe');
+ $encryptResponse = $transitApi->encryptData($encryptExample);
+
+ var_dump($encryptResponse->getCiphertext());
+
+ // decrypt data
+ $decryptExample = new DecryptDataRequest('exampleKeyName', $encryptResponse->getCiphertext());
+ $decryptResponse = $transitApi->decryptData($decryptExample);
+
+ var_dump($decryptResponse->getPlaintext());
+
+ // update key config and allow deletion
+ $keyConfigExample = new UpdateKeyConfigRequest('exampleKeyName');
+ $keyConfigExample->setDeletionAllowed(true);
+ $transitApi->updateKeyConfig($keyConfigExample);
+
+ // delete key
+ $transitApi->deleteKey('exampleKeyName');
+
+ // list keys
+ $listKeyResponse = $transitApi->listKeys();
+ var_dump($listKeyResponse->getKeys());
+
+} catch (VaultResponseException $exception) {
+ var_dump($exception->getMessage());
+ var_dump($exception->getResponse());
+ var_dump($exception->getRequest());
+
+} catch (VaultException $exception) {
+ var_dump($exception->getMessage());
+}
diff --git a/phpunit.xml.dist b/phpunit.xml.dist
new file mode 100644
index 0000000..ae314b9
--- /dev/null
+++ b/phpunit.xml.dist
@@ -0,0 +1,13 @@
+
+
+
+
+ ./src/
+
+
+
+
+ ./tests
+
+
+
diff --git a/psalm.xml b/psalm.xml
new file mode 100644
index 0000000..4057dcb
--- /dev/null
+++ b/psalm.xml
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/VaultPHP/Authentication/AbstractAuthenticationProvider.php b/src/VaultPHP/Authentication/AbstractAuthenticationProvider.php
new file mode 100644
index 0000000..04989d8
--- /dev/null
+++ b/src/VaultPHP/Authentication/AbstractAuthenticationProvider.php
@@ -0,0 +1,38 @@
+vaultClient = $VaultClient;
+ }
+
+ /**
+ * @return VaultClient
+ * @throws VaultException
+ */
+ public function getVaultClient()
+ {
+ if (!$this->vaultClient) {
+ throw new VaultException('Trying to request the VaultClient before initialization');
+ }
+
+ return $this->vaultClient;
+ }
+}
diff --git a/src/VaultPHP/Authentication/AuthenticationMetaData.php b/src/VaultPHP/Authentication/AuthenticationMetaData.php
new file mode 100644
index 0000000..8f8b906
--- /dev/null
+++ b/src/VaultPHP/Authentication/AuthenticationMetaData.php
@@ -0,0 +1,39 @@
+token = $fromAuth->client_token;
+ }
+ }
+
+ /**
+ * @return string
+ */
+ public function getClientToken() {
+ return $this->token;
+ }
+
+ /**
+ * @return bool
+ */
+ public function isClientTokenPresent() {
+ return !!$this->token;
+ }
+}
diff --git a/src/VaultPHP/Authentication/AuthenticationProviderInterface.php b/src/VaultPHP/Authentication/AuthenticationProviderInterface.php
new file mode 100644
index 0000000..b06619a
--- /dev/null
+++ b/src/VaultPHP/Authentication/AuthenticationProviderInterface.php
@@ -0,0 +1,30 @@
+role = $role;
+ $this->jwt = $jwt;
+ }
+
+ /**
+ * @return bool|AuthenticationMetaData
+ * @throws InvalidDataException
+ * @throws InvalidRouteException
+ * @throws VaultAuthenticationException
+ * @throws VaultException
+ * @throws VaultHttpException
+ */
+ public function authenticate()
+ {
+ /** @var EndpointResponse $response */
+ $response = $this->getVaultClient()->sendApiRequest(
+ 'POST',
+ $this->endpoint,
+ EndpointResponse::class,
+ [
+ 'role' => $this->role,
+ 'jwt' => $this->jwt,
+ ],
+ false
+ );
+
+ if ($auth = $response->getBasicMetaResponse()->getAuth()) {
+ return new AuthenticationMetaData($auth);
+ }
+
+ return false;
+ }
+}
diff --git a/src/VaultPHP/Authentication/Provider/Token.php b/src/VaultPHP/Authentication/Provider/Token.php
new file mode 100644
index 0000000..552e213
--- /dev/null
+++ b/src/VaultPHP/Authentication/Provider/Token.php
@@ -0,0 +1,35 @@
+token = $token;
+ }
+
+ /**
+ * @return AuthenticationMetaData
+ */
+ public function authenticate()
+ {
+ return new AuthenticationMetaData((object) [
+ 'client_token' => $this->token,
+ ]);
+ }
+}
diff --git a/src/VaultPHP/Authentication/Provider/UserPassword.php b/src/VaultPHP/Authentication/Provider/UserPassword.php
new file mode 100644
index 0000000..4b4f056
--- /dev/null
+++ b/src/VaultPHP/Authentication/Provider/UserPassword.php
@@ -0,0 +1,73 @@
+username = $username;
+ $this->password = $password;
+ }
+
+ /**
+ * @return bool|AuthenticationMetaData
+ * @throws InvalidDataException
+ * @throws InvalidRouteException
+ * @throws VaultAuthenticationException
+ * @throws VaultException
+ * @throws VaultHttpException
+ */
+ public function authenticate()
+ {
+ /** @var EndpointResponse $response */
+ $response = $this->getVaultClient()->sendApiRequest(
+ 'POST',
+ $this->getAuthUrl(),
+ EndpointResponse::class,
+ [
+ 'password' => $this->password
+ ],
+ false
+ );
+
+ if ($auth = $response->getBasicMetaResponse()->getAuth()) {
+ return new AuthenticationMetaData($auth);
+ }
+
+ return false;
+ }
+
+ /**
+ * @return string
+ */
+ private function getAuthUrl()
+ {
+ return sprintf($this->endpoint, urlencode($this->username));
+ }
+}
diff --git a/src/VaultPHP/Exceptions/InvalidDataException.php b/src/VaultPHP/Exceptions/InvalidDataException.php
new file mode 100644
index 0000000..2483bcb
--- /dev/null
+++ b/src/VaultPHP/Exceptions/InvalidDataException.php
@@ -0,0 +1,11 @@
+response = $response;
+ $this->request = $request;
+
+ $parsedResponse = EndpointResponse::fromResponse($response);
+ $returnedErrors = $parsedResponse->getBasicMetaResponse()->getErrors();
+ $errors = implode(', ', is_array($returnedErrors) ? $returnedErrors : []);
+
+ parent::__construct($errors, $response->getStatusCode(), $prevException);
+ }
+
+ /**
+ * @return RequestInterface
+ */
+ public function getRequest()
+ {
+ return $this->request;
+ }
+
+ /**
+ * @return ResponseInterface
+ */
+ public function getResponse()
+ {
+ return $this->response;
+ }
+}
diff --git a/src/VaultPHP/Response/BasicMetaResponse.php b/src/VaultPHP/Response/BasicMetaResponse.php
new file mode 100644
index 0000000..8a9e697
--- /dev/null
+++ b/src/VaultPHP/Response/BasicMetaResponse.php
@@ -0,0 +1,131 @@
+populateData($data);
+ }
+
+ /**
+ * @param array|object $data
+ * @return void
+ */
+ private function populateData($data)
+ {
+ /** @var string $key */
+ /** @var mixed $value */
+ foreach ($data as $key => $value) {
+ if (property_exists(self::class, (string) $key)) {
+ $this->$key = $value;
+ }
+ }
+ }
+
+ /**
+ * @return string|null
+ */
+ public function getRequestId()
+ {
+ return $this->request_id;
+ }
+
+ /**
+ * @return string|null
+ */
+ public function getLeaseId()
+ {
+ return $this->lease_id;
+ }
+
+ /**
+ * @return bool|null
+ */
+ public function getRenewable()
+ {
+ return $this->renewable;
+ }
+
+ /**
+ * @return int|null
+ */
+ public function getLeaseDuration()
+ {
+ return $this->lease_duration;
+ }
+
+ /**
+ * @return string|null
+ */
+ public function getWrapInfo()
+ {
+ return $this->wrap_info;
+ }
+
+ /**
+ * @return array|null
+ */
+ public function getWarnings()
+ {
+ return $this->warnings;
+ }
+
+ /**
+ * @return object|null
+ */
+ public function getAuth()
+ {
+ return $this->auth;
+ }
+
+ /**
+ * @return array|null
+ */
+ public function getErrors()
+ {
+ return $this->errors;
+ }
+
+ /**
+ * @return boolean
+ */
+ public function hasErrors()
+ {
+ $errors = $this->getErrors();
+ return $errors !== NULL && is_array($errors) && count($errors) >= 1;
+ }
+}
diff --git a/src/VaultPHP/Response/BasicMetaResponseInterface.php b/src/VaultPHP/Response/BasicMetaResponseInterface.php
new file mode 100644
index 0000000..fb67f97
--- /dev/null
+++ b/src/VaultPHP/Response/BasicMetaResponseInterface.php
@@ -0,0 +1,61 @@
+basicMetaResponse = new BasicMetaResponse();
+ $this->populateData($data);
+ }
+
+ /**
+ * @param $response
+ * @return array
+ */
+ private static function getResponseContent(ResponseInterface $response) {
+ $responseBody = $response->getBody();
+ $responseBody->rewind();
+ $responseBodyContents = $responseBody->getContents();
+
+ // cast to array because we only want the first root
+ // as array and not the complete response
+ return (array) json_decode($responseBodyContents);
+ }
+
+ /**
+ * @param ResponseInterface $response
+ * @return static
+ */
+ static function fromResponse(ResponseInterface $response)
+ {
+ $metaData = static::getResponseContent($response);
+
+ /** @var object|array $domainData */
+ $domainData = isset($metaData['data']) ? $metaData['data'] : [];
+ unset($metaData['data']);
+
+ $responseDTO = new static($domainData);
+ $responseDTO->basicMetaResponse = new BasicMetaResponse($metaData);
+
+ return $responseDTO;
+ }
+
+ /**
+ * @param ResponseInterface $response
+ * @return static[]
+ */
+ static function fromBulkResponse(ResponseInterface $response)
+ {
+ $resultArray = [];
+ $metaData = static::getResponseContent($response);
+
+ /** @var object $domainData */
+ $domainData = isset($metaData['data']) ? $metaData['data'] : [];
+ unset($metaData['data']);
+
+ if ($domainData && is_array($domainData->batch_results)) {
+ /** @var object $batchResult */
+ foreach($domainData->batch_results as $batchResult) {
+ /** @var array $batchMetaData */
+ $batchMetaData = $metaData;
+
+ if (isset($batchResult->error)) {
+ /** @var array $currentErrors */
+ $currentErrors = isset($metaData['errors']) ? $metaData['errors'] : [];
+ array_push($currentErrors, $batchResult->error);
+
+ $batchMetaData['errors'] = $currentErrors;
+ }
+
+ $responseDTO = new static($batchResult);
+ $responseDTO->basicMetaResponse = new BasicMetaResponse($batchMetaData);
+ $resultArray[] = $responseDTO;
+ }
+ }
+
+ return $resultArray;
+ }
+
+ /**
+ * @param array|object $data
+ * @return void
+ */
+ private function populateData($data)
+ {
+ /** @var string $key */
+ /** @var mixed $value */
+ foreach ($data as $key => $value) {
+ if (property_exists(static::class, (string) $key)) {
+ $this->$key = $value;
+ }
+ }
+ }
+
+ /**
+ * @return BasicMetaResponse
+ */
+ public function getBasicMetaResponse()
+ {
+ return $this->basicMetaResponse;
+ }
+}
diff --git a/src/VaultPHP/Response/EndpointResponseInterface.php b/src/VaultPHP/Response/EndpointResponseInterface.php
new file mode 100644
index 0000000..25073cf
--- /dev/null
+++ b/src/VaultPHP/Response/EndpointResponseInterface.php
@@ -0,0 +1,23 @@
+vaultClient = $VaultClient;
+ }
+}
diff --git a/src/VaultPHP/SecretEngines/Engines/Transit/EncryptionType.php b/src/VaultPHP/SecretEngines/Engines/Transit/EncryptionType.php
new file mode 100644
index 0000000..e47d6d4
--- /dev/null
+++ b/src/VaultPHP/SecretEngines/Engines/Transit/EncryptionType.php
@@ -0,0 +1,21 @@
+setName($name);
+ }
+
+ /**
+ * @param string $type
+ * @return void
+ */
+ public function setType($type)
+ {
+ $this->type = $type;
+ }
+
+ /**
+ * @return string|null
+ */
+ public function getType()
+ {
+ return $this->type;
+ }
+}
diff --git a/src/VaultPHP/SecretEngines/Engines/Transit/Request/DecryptData/DecryptData.php b/src/VaultPHP/SecretEngines/Engines/Transit/Request/DecryptData/DecryptData.php
new file mode 100644
index 0000000..4e0ada4
--- /dev/null
+++ b/src/VaultPHP/SecretEngines/Engines/Transit/Request/DecryptData/DecryptData.php
@@ -0,0 +1,88 @@
+setCiphertext($ciphertext);
+ $this->setContext($context);
+ $this->setNonce($nonce);
+ }
+
+ /**
+ * @return string
+ */
+ public function getCiphertext()
+ {
+ return $this->ciphertext;
+ }
+
+ /**
+ * @param string $ciphertext
+ * @return void
+ */
+ public function setCiphertext($ciphertext)
+ {
+ $this->ciphertext = $ciphertext;
+ }
+
+ /**
+ * @return string|null
+ */
+ public function getNonce()
+ {
+ return $this->nonce;
+ }
+
+ /**
+ * @param string|null $nonce
+ * @return void
+ */
+ public function setNonce($nonce)
+ {
+ $this->nonce = $nonce;
+ }
+
+ /**
+ * @return string|null
+ */
+ public function getContext()
+ {
+ return $this->context;
+ }
+
+ /**
+ * @param string|null $context
+ * @return void
+ */
+ public function setContext($context)
+ {
+ $this->context = $context;
+ }
+}
diff --git a/src/VaultPHP/SecretEngines/Engines/Transit/Request/DecryptData/DecryptDataBulkRequest.php b/src/VaultPHP/SecretEngines/Engines/Transit/Request/DecryptData/DecryptDataBulkRequest.php
new file mode 100644
index 0000000..5402169
--- /dev/null
+++ b/src/VaultPHP/SecretEngines/Engines/Transit/Request/DecryptData/DecryptDataBulkRequest.php
@@ -0,0 +1,32 @@
+setName($name);
+ $this->addBulkRequests($batchRequests);
+ }
+
+}
diff --git a/src/VaultPHP/SecretEngines/Engines/Transit/Request/DecryptData/DecryptDataRequest.php b/src/VaultPHP/SecretEngines/Engines/Transit/Request/DecryptData/DecryptDataRequest.php
new file mode 100644
index 0000000..cb28f20
--- /dev/null
+++ b/src/VaultPHP/SecretEngines/Engines/Transit/Request/DecryptData/DecryptDataRequest.php
@@ -0,0 +1,27 @@
+setName($name);
+ }
+}
diff --git a/src/VaultPHP/SecretEngines/Engines/Transit/Request/EncryptData/EncryptData.php b/src/VaultPHP/SecretEngines/Engines/Transit/Request/EncryptData/EncryptData.php
new file mode 100644
index 0000000..e7811fe
--- /dev/null
+++ b/src/VaultPHP/SecretEngines/Engines/Transit/Request/EncryptData/EncryptData.php
@@ -0,0 +1,88 @@
+setPlaintext($plaintext);
+ $this->setContext($context);
+ $this->setNonce($nonce);
+ }
+
+ /**
+ * @return string
+ */
+ public function getPlaintext()
+ {
+ return $this->plaintext;
+ }
+
+ /**
+ * @param string $plaintext
+ * @return void
+ */
+ public function setPlaintext($plaintext)
+ {
+ $this->plaintext = base64_encode($plaintext);
+ }
+
+ /**
+ * @return string|null
+ */
+ public function getNonce()
+ {
+ return $this->nonce;
+ }
+
+ /**
+ * @param string|null $nonce
+ * @return void
+ */
+ public function setNonce($nonce)
+ {
+ $this->nonce = $nonce;
+ }
+
+ /**
+ * @return string|null
+ */
+ public function getContext()
+ {
+ return $this->context;
+ }
+
+ /**
+ * @param string|null $context
+ * @return void
+ */
+ public function setContext($context)
+ {
+ $this->context = $context;
+ }
+}
diff --git a/src/VaultPHP/SecretEngines/Engines/Transit/Request/EncryptData/EncryptDataBulkRequest.php b/src/VaultPHP/SecretEngines/Engines/Transit/Request/EncryptData/EncryptDataBulkRequest.php
new file mode 100644
index 0000000..005087e
--- /dev/null
+++ b/src/VaultPHP/SecretEngines/Engines/Transit/Request/EncryptData/EncryptDataBulkRequest.php
@@ -0,0 +1,27 @@
+setName($name);
+ $this->addBulkRequests($batchRequests);
+ }
+}
diff --git a/src/VaultPHP/SecretEngines/Engines/Transit/Request/EncryptData/EncryptDataRequest.php b/src/VaultPHP/SecretEngines/Engines/Transit/Request/EncryptData/EncryptDataRequest.php
new file mode 100644
index 0000000..0035b94
--- /dev/null
+++ b/src/VaultPHP/SecretEngines/Engines/Transit/Request/EncryptData/EncryptDataRequest.php
@@ -0,0 +1,27 @@
+setName($name);
+ }
+}
diff --git a/src/VaultPHP/SecretEngines/Engines/Transit/Request/UpdateKeyConfigRequest.php b/src/VaultPHP/SecretEngines/Engines/Transit/Request/UpdateKeyConfigRequest.php
new file mode 100644
index 0000000..a47e077
--- /dev/null
+++ b/src/VaultPHP/SecretEngines/Engines/Transit/Request/UpdateKeyConfigRequest.php
@@ -0,0 +1,127 @@
+setName($name);
+ }
+
+ /**
+ * @param $allow boolean
+ * @return void
+ */
+ public function setDeletionAllowed($allow)
+ {
+ $this->deletion_allowed = (boolean)$allow;
+ }
+
+ /**
+ * @param int $min_decryption_version
+ * @return void
+ */
+ public function setMinDecryptionVersion($min_decryption_version)
+ {
+ $this->min_decryption_version = (int)$min_decryption_version;
+ }
+
+ /**
+ * @param int $min_encryption_version
+ * @return void
+ */
+ public function setMinEncryptionVersion($min_encryption_version)
+ {
+ $this->min_encryption_version = (int)$min_encryption_version;
+ }
+
+ /**
+ * @param bool $exportable
+ * @return void
+ */
+ public function setExportable($exportable)
+ {
+ $this->exportable = (bool)$exportable;
+ }
+
+ /**
+ * @param bool $allow_plaintext_backup
+ * @return void
+ */
+ public function setAllowPlaintextBackup($allow_plaintext_backup)
+ {
+ $this->allow_plaintext_backup = (bool)$allow_plaintext_backup;
+ }
+
+ /**
+ * @return int|null
+ */
+ public function getMinDecryptionVersion()
+ {
+ return $this->min_decryption_version;
+ }
+
+ /**
+ * @return int|null
+ */
+ public function getMinEncryptionVersion()
+ {
+ return $this->min_encryption_version;
+ }
+
+ /**
+ * @return bool|null
+ */
+ public function getExportable()
+ {
+ return $this->exportable;
+ }
+
+ /**
+ * @return bool|null
+ */
+ public function getAllowPlaintextBackup()
+ {
+ return $this->allow_plaintext_backup;
+ }
+
+ /**
+ * @return bool|null
+ */
+ public function getDeletionAllowed()
+ {
+ return $this->deletion_allowed;
+ }
+}
diff --git a/src/VaultPHP/SecretEngines/Engines/Transit/Response/CreateKeyResponse.php b/src/VaultPHP/SecretEngines/Engines/Transit/Response/CreateKeyResponse.php
new file mode 100644
index 0000000..6761681
--- /dev/null
+++ b/src/VaultPHP/SecretEngines/Engines/Transit/Response/CreateKeyResponse.php
@@ -0,0 +1,13 @@
+plaintext);
+ }
+}
diff --git a/src/VaultPHP/SecretEngines/Engines/Transit/Response/DeleteKeyResponse.php b/src/VaultPHP/SecretEngines/Engines/Transit/Response/DeleteKeyResponse.php
new file mode 100644
index 0000000..ccf4215
--- /dev/null
+++ b/src/VaultPHP/SecretEngines/Engines/Transit/Response/DeleteKeyResponse.php
@@ -0,0 +1,13 @@
+ciphertext;
+ }
+}
diff --git a/src/VaultPHP/SecretEngines/Engines/Transit/Response/ListKeysResponse.php b/src/VaultPHP/SecretEngines/Engines/Transit/Response/ListKeysResponse.php
new file mode 100644
index 0000000..099113a
--- /dev/null
+++ b/src/VaultPHP/SecretEngines/Engines/Transit/Response/ListKeysResponse.php
@@ -0,0 +1,23 @@
+keys;
+ }
+}
diff --git a/src/VaultPHP/SecretEngines/Engines/Transit/Response/UpdateKeyConfigResponse.php b/src/VaultPHP/SecretEngines/Engines/Transit/Response/UpdateKeyConfigResponse.php
new file mode 100644
index 0000000..5192874
--- /dev/null
+++ b/src/VaultPHP/SecretEngines/Engines/Transit/Response/UpdateKeyConfigResponse.php
@@ -0,0 +1,13 @@
+vaultClient->sendApiRequest(
+ 'POST',
+ sprintf('/v1/transit/keys/%s', urlencode($createKeyRequest->getName())),
+ CreateKeyResponse::class,
+ $createKeyRequest
+ );
+ }
+
+ /**
+ * @param EncryptDataRequest $encryptDataRequest
+ * @return EncryptDataResponse
+ * @throws InvalidDataException
+ * @throws InvalidRouteException
+ * @throws VaultException
+ */
+ public function encryptData(EncryptDataRequest $encryptDataRequest)
+ {
+ /** @var EncryptDataResponse */
+ return $this->vaultClient->sendApiRequest(
+ 'POST',
+ sprintf('/v1/transit/encrypt/%s', urlencode($encryptDataRequest->getName())),
+ EncryptDataResponse::class,
+ $encryptDataRequest
+ );
+ }
+
+ /**
+ * @param EncryptDataBulkRequest $encryptDataBulkRequest
+ * @return EncryptDataResponse[]
+ * @throws InvalidDataException
+ * @throws InvalidRouteException
+ * @throws VaultException
+ */
+ public function encryptDataBulk(EncryptDataBulkRequest $encryptDataBulkRequest)
+ {
+ /** @var EncryptDataResponse[] */
+ return $this->vaultClient->sendApiRequest(
+ 'POST',
+ sprintf('/v1/transit/encrypt/%s', urlencode($encryptDataBulkRequest->getName())),
+ EncryptDataResponse::class,
+ $encryptDataBulkRequest
+ );
+ }
+
+ /**
+ * @param DecryptDataRequest $decryptDataRequest
+ * @return DecryptDataResponse
+ * @throws InvalidDataException
+ * @throws InvalidRouteException
+ * @throws VaultException
+ */
+ public function decryptData(DecryptDataRequest $decryptDataRequest)
+ {
+ /** @var DecryptDataResponse */
+ return $this->vaultClient->sendApiRequest(
+ 'POST',
+ sprintf('/v1/transit/decrypt/%s', urlencode($decryptDataRequest->getName())),
+ DecryptDataResponse::class,
+ $decryptDataRequest
+ );
+ }
+
+ /**
+ * @param DecryptDataBulkRequest $decryptDataBulkRequest
+ * @return EndpointResponse|EndpointResponse[]
+ * @throws InvalidDataException
+ * @throws InvalidRouteException
+ * @throws VaultException
+ * @throws VaultAuthenticationException
+ * @throws VaultHttpException
+ */
+ public function decryptDataBulk(DecryptDataBulkRequest $decryptDataBulkRequest)
+ {
+ /** @var DecryptDataResponse[] */
+ return $this->vaultClient->sendApiRequest(
+ 'POST',
+ sprintf('/v1/transit/decrypt/%s', urlencode($decryptDataBulkRequest->getName())),
+ DecryptDataResponse::class,
+ $decryptDataBulkRequest
+ );
+ }
+
+ /**
+ * @return ListKeysResponse
+ * @throws InvalidDataException
+ * @throws InvalidRouteException
+ * @throws VaultException
+ */
+ public function listKeys()
+ {
+ /** @var ListKeysResponse */
+ return $this->vaultClient->sendApiRequest(
+ 'LIST',
+ '/v1/transit/keys',
+ ListKeysResponse::class,
+ []
+ );
+ }
+
+ /**
+ * @param string $name
+ * @return EndpointResponse
+ * @throws InvalidDataException
+ * @throws InvalidRouteException
+ * @throws VaultException
+ */
+ public function deleteKey($name)
+ {
+ /** @var EndpointResponse */
+ return $this->vaultClient->sendApiRequest(
+ 'DELETE',
+ sprintf('/v1/transit/keys/%s', urlencode($name)),
+ DeleteKeyResponse::class,
+ []
+ );
+ }
+
+ /**
+ * @param UpdateKeyConfigRequest $updateKeyConfigRequest
+ * @return UpdateKeyConfigResponse
+ * @throws InvalidDataException
+ * @throws InvalidRouteException
+ * @throws VaultException
+ */
+ public function updateKeyConfig(UpdateKeyConfigRequest $updateKeyConfigRequest)
+ {
+ /** @var UpdateKeyConfigResponse */
+ return $this->vaultClient->sendApiRequest(
+ 'POST',
+ sprintf('/v1/transit/keys/%s/config', urlencode($updateKeyConfigRequest->getName())),
+ UpdateKeyConfigResponse::class,
+ $updateKeyConfigRequest
+ );
+ }
+}
diff --git a/src/VaultPHP/SecretEngines/Interfaces/ArrayExportInterface.php b/src/VaultPHP/SecretEngines/Interfaces/ArrayExportInterface.php
new file mode 100644
index 0000000..29ee56b
--- /dev/null
+++ b/src/VaultPHP/SecretEngines/Interfaces/ArrayExportInterface.php
@@ -0,0 +1,15 @@
+ $data) {
+ if (is_array($data)) {
+ $output[$key] = $this->array_map_r($callback, $data);
+ } else {
+ $output[$key] = $callback($data);
+ }
+ }
+
+ return $output;
+ }
+
+ /**
+ * @return array
+ */
+ public function toArray()
+ {
+ $data = get_object_vars($this);
+ return $this->array_map_r(
+ /** @psalm-suppress MissingClosureParamType */
+ function ($v) {
+ if ($v instanceof ArrayExportInterface) {
+ return $v->toArray();
+ }
+ return $v;
+ },
+ $data
+ );
+ }
+}
diff --git a/src/VaultPHP/SecretEngines/Traits/BulkRequestTrait.php b/src/VaultPHP/SecretEngines/Traits/BulkRequestTrait.php
new file mode 100644
index 0000000..ca743cf
--- /dev/null
+++ b/src/VaultPHP/SecretEngines/Traits/BulkRequestTrait.php
@@ -0,0 +1,34 @@
+batch_input[] = $request;
+ }
+
+ /**
+ * @param mixed[] $requests
+ * @return void
+ */
+ public function addBulkRequests($requests)
+ {
+ /** @var mixed $request */
+ foreach ($requests as $request) {
+ $this->addBulkRequest($request);
+ }
+ }
+}
diff --git a/src/VaultPHP/SecretEngines/Traits/NamedRequestTrait.php b/src/VaultPHP/SecretEngines/Traits/NamedRequestTrait.php
new file mode 100644
index 0000000..c9de61f
--- /dev/null
+++ b/src/VaultPHP/SecretEngines/Traits/NamedRequestTrait.php
@@ -0,0 +1,30 @@
+name;
+ }
+
+ /**
+ * @param string $name
+ * @return void
+ */
+ public function setName($name)
+ {
+ $this->name = $name;
+ }
+}
diff --git a/src/VaultPHP/VaultClient.php b/src/VaultPHP/VaultClient.php
new file mode 100644
index 0000000..6231b98
--- /dev/null
+++ b/src/VaultPHP/VaultClient.php
@@ -0,0 +1,244 @@
+httpClient = $httpClient;
+ $this->apiHost = $apiHost;
+
+ $this->authProvider = $authProvider;
+ $this->authProvider->setVaultClient($this);
+ }
+
+ /**
+ * @return void
+ * @throws VaultAuthenticationException
+ */
+ private function authenticate()
+ {
+ if (!$this->authenticationMetaData) {
+ try {
+ $metaData = $this->authProvider->authenticate();
+
+ if (!$metaData instanceof AuthenticationMetaData || !$metaData->isClientTokenPresent()) {
+ throw new VaultException('Client Token is missing');
+ }
+
+ $this->authenticationMetaData = $metaData;
+ } catch (\Exception $e) {
+ throw new VaultAuthenticationException(
+ sprintf('AuthProvider %s failed to fetch token', get_class($this->authProvider)),
+ 0,
+ $e
+ );
+ }
+ }
+ }
+
+ /**
+ * @param array|object $data
+ * @return string
+ */
+ private function extractPayload($data)
+ {
+ if (is_object($data) && $data instanceof ArrayExportInterface) {
+ $data = $data->toArray();
+ }
+
+ return json_encode($data);
+ }
+
+ /**
+ * @param string $method
+ * @param string $endpoint
+ * @param string $returnClass
+ * @param array|ResourceRequestInterface $data
+ * @param bool $authRequired
+ * @return mixed|mixed[]
+ * @throws InvalidDataException
+ * @throws InvalidRouteException
+ * @throws VaultAuthenticationException
+ * @throws VaultException
+ * @throws VaultHttpException
+ */
+ public function sendApiRequest($method, $endpoint, $returnClass, $data = [], $authRequired = true)
+ {
+ if ($authRequired) {
+ $this->authenticate();
+ }
+
+ $extractedPayload = $this->extractPayload($data);
+ $request = new Request(
+ $method,
+ $endpoint,
+ [],
+ $extractedPayload
+ );
+
+ $response = $this->sendRequest($request);
+ return $this->parseResponse(
+ $request,
+ $response,
+ $returnClass,
+ $data instanceof BulkResourceRequestInterface
+ );
+ }
+
+ /**
+ * @param RequestInterface $request
+ * @param ResponseInterface $response
+ * @param string $returnClass
+ * @param boolean $isBulkRequest
+ * @return mixed|mixed[]
+ * @throws InvalidDataException
+ * @throws InvalidRouteException
+ * @throws VaultAuthenticationException
+ * @throws VaultException
+ * @throws VaultResponseException
+ */
+ private function parseResponse(
+ RequestInterface $request,
+ ResponseInterface $response,
+ $returnClass,
+ $isBulkRequest
+ )
+ {
+ $status = $response->getStatusCode();
+
+ /**
+ * Looks like psalm can't handle the method exists with static functions
+ */
+ if (!$isBulkRequest) {
+ /** @psalm-suppress ArgumentTypeCoercion */
+ if (!$returnClass || !method_exists($returnClass, 'fromResponse')) {
+ throw new VaultException('Return Class declaration lacks static::fromResponse');
+ }
+
+ /** @var EndpointResponse $responseDataDTO */
+ $responseDataDTO = $returnClass::fromResponse($response);
+ } else {
+ /** @psalm-suppress ArgumentTypeCoercion */
+ if (!$returnClass || !method_exists($returnClass, 'fromBulkResponse')) {
+ throw new VaultException('Return Class declaration lacks static::fromBulkResponse');
+ }
+
+ /** @var EndpointResponse[] $responseDataDTO */
+ $responseDataDTO = $returnClass::fromBulkResponse($response);
+ }
+
+ if (!is_array($responseDataDTO) && !$responseDataDTO instanceof EndpointResponseInterface) {
+ throw new VaultException('Result from "fromResponse/fromBulkResponse" isn\'t an instance of EndpointResponse or Array');
+ }
+
+ if ($status >= 200 && $status < 300) {
+ return $responseDataDTO;
+
+ } elseif ($status >= 400 && $status < 500) {
+ if ($status === 400) {
+ throw new InvalidDataException($response, $request);
+ } elseif ($status === 403) {
+ throw new VaultAuthenticationException('Authentication with provided Token failed');
+ } elseif ($status === 404) {
+ // if 404 and no error this indicates no data for e.g. List
+ // makes no sense but hey - the vault rest is a magical unicorn
+ if (!is_array($responseDataDTO) && !$responseDataDTO->getBasicMetaResponse()->hasErrors()) {
+ return $responseDataDTO;
+ }
+
+ // otherwise 404 and error object
+ // indicates a route that is not defined
+ throw new InvalidRouteException($response, $request);
+ }
+ } elseif ($status >= 500) {
+ throw new VaultResponseException($response, $request);
+ }
+
+ throw new VaultException(sprintf("server responded with unhandled status code %s", $response->getStatusCode()));
+ }
+
+ /**
+ * @param RequestInterface $request
+ * @return ResponseInterface
+ * @throws VaultException
+ * @throws VaultHttpException
+ */
+ private function sendRequest(RequestInterface $request)
+ {
+ $requestWithDefaults = $this->getDefaultRequest($request);
+ try {
+ return $this->httpClient->sendRequest($requestWithDefaults);
+ } catch (\Exception $exception) {
+ throw new VaultHttpException($exception->getMessage(), 0, $exception);
+ }
+ }
+
+ /**
+ * @param $request RequestInterface
+ * @return RequestInterface
+ * @throws VaultException
+ */
+ private function getDefaultRequest(RequestInterface $request)
+ {
+ $token = $this->authenticationMetaData ? $this->authenticationMetaData->getClientToken() : '';
+ $hostEndpoint = parse_url($this->apiHost);
+
+ if (!is_array($hostEndpoint) || !isset($hostEndpoint['scheme']) || !isset($hostEndpoint['host']) || !isset($hostEndpoint['port'])) {
+ throw new VaultException('can\'t parse provided apiHost - malformed uri');
+ }
+
+ $uriWithHost = $request
+ ->getUri()
+ ->withScheme($hostEndpoint['scheme'])
+ ->withHost($hostEndpoint['host'])
+ ->withPort($hostEndpoint['port']);
+
+ return $request
+ ->withUri($uriWithHost)
+ ->withAddedHeader('X-Vault-Request', '1')
+ ->withAddedHeader('X-Vault-Token', $token)
+ ->withAddedHeader('Content-Type', 'application/json');
+ }
+}
diff --git a/tests/VaultPHP/Authentication/AuthenticationMetaDataTest.php b/tests/VaultPHP/Authentication/AuthenticationMetaDataTest.php
new file mode 100644
index 0000000..6ba5166
--- /dev/null
+++ b/tests/VaultPHP/Authentication/AuthenticationMetaDataTest.php
@@ -0,0 +1,22 @@
+client_token = "foobar";
+
+ $meta = new AuthenticationMetaData($testStd);
+ $this->assertEquals($testStd->client_token, $meta->getClientToken());
+ }
+}
diff --git a/tests/VaultPHP/Authentication/Provider/KubernetesTest.php b/tests/VaultPHP/Authentication/Provider/KubernetesTest.php
new file mode 100644
index 0000000..0a7dc03
--- /dev/null
+++ b/tests/VaultPHP/Authentication/Provider/KubernetesTest.php
@@ -0,0 +1,71 @@
+ [
+ 'client_token' => 'fooToken',
+ ],
+ ]));
+ $returnResponseClass = EndpointResponse::fromResponse($apiResponse);
+
+ $clientMock = $this->createMock(VaultClient::class);
+ $clientMock
+ ->expects($this->once())
+ ->method('sendApiRequest')
+ ->with('POST', '/v1/auth/kubernetes/login', EndpointResponse::class, ['role' => 'foo', 'jwt' => 'bar'], false)
+ ->willReturn($returnResponseClass);
+
+ $kubernetesAuth = new Kubernetes('foo', 'bar');
+ $kubernetesAuth->setVaultClient($clientMock);
+
+ $tokenMeta = $kubernetesAuth->authenticate();
+
+ $this->assertInstanceOf(AuthenticationMetaData::class, $tokenMeta);
+ $this->assertEquals('fooToken', $tokenMeta->getClientToken());
+ }
+
+ public function testWillReturnNothingWhenTokenReceiveFails()
+ {
+ $apiResponse = new Response(200, [], json_encode([]));
+ $returnResponseClass = EndpointResponse::fromResponse($apiResponse);
+
+ $clientMock = $this->createMock(VaultClient::class);
+ $clientMock
+ ->expects($this->once())
+ ->method('sendApiRequest')
+ ->willReturn($returnResponseClass);
+
+ $userPasswordAuth = new Kubernetes('foo', 'bar');
+ $userPasswordAuth->setVaultClient($clientMock);
+
+ $tokenMeta = $userPasswordAuth->authenticate();
+
+ $this->assertFalse( $tokenMeta);
+ }
+
+ public function testWillThrowWhenTryingToGetRequestClientBeforeInit()
+ {
+ $this->expectException(VaultException::class);
+ $this->expectExceptionMessage('Trying to request the VaultClient before initialization');
+
+ $auth = new Kubernetes('foo', 'bar');
+ $auth->getVaultClient();
+ }
+}
diff --git a/tests/VaultPHP/Authentication/Provider/TokenTest.php b/tests/VaultPHP/Authentication/Provider/TokenTest.php
new file mode 100644
index 0000000..1799867
--- /dev/null
+++ b/tests/VaultPHP/Authentication/Provider/TokenTest.php
@@ -0,0 +1,23 @@
+authenticate();
+
+ $this->assertInstanceOf(AuthenticationMetaData::class, $tokenMeta);
+ $this->assertEquals('foobar', $tokenMeta->getClientToken());
+ }
+}
diff --git a/tests/VaultPHP/Authentication/Provider/UserPasswordTest.php b/tests/VaultPHP/Authentication/Provider/UserPasswordTest.php
new file mode 100644
index 0000000..71eee00
--- /dev/null
+++ b/tests/VaultPHP/Authentication/Provider/UserPasswordTest.php
@@ -0,0 +1,71 @@
+ [
+ 'client_token' => 'fooToken',
+ ],
+ ]));
+ $returnResponseClass = EndpointResponse::fromResponse($apiResponse);
+
+ $clientMock = $this->createMock(VaultClient::class);
+ $clientMock
+ ->expects($this->once())
+ ->method('sendApiRequest')
+ ->with('POST', '/v1/auth/userpass/login/foo', EndpointResponse::class, ['password' => 'bar'], false)
+ ->willReturn($returnResponseClass);
+
+ $userPasswordAuth = new UserPassword('foo', 'bar');
+ $userPasswordAuth->setVaultClient($clientMock);
+
+ $tokenMeta = $userPasswordAuth->authenticate();
+
+ $this->assertInstanceOf(AuthenticationMetaData::class, $tokenMeta);
+ $this->assertEquals('fooToken', $tokenMeta->getClientToken());
+ }
+
+ public function testWillReturnNothingWhenTokenReceiveFails()
+ {
+ $apiResponse = new Response(200, [], json_encode([]));
+ $returnResponseClass = EndpointResponse::fromResponse($apiResponse);
+
+ $clientMock = $this->createMock(VaultClient::class);
+ $clientMock
+ ->expects($this->once())
+ ->method('sendApiRequest')
+ ->willReturn($returnResponseClass);
+
+ $userPasswordAuth = new UserPassword('foo', 'bar');
+ $userPasswordAuth->setVaultClient($clientMock);
+
+ $tokenMeta = $userPasswordAuth->authenticate();
+
+ $this->assertFalse($tokenMeta);
+ }
+
+ public function testWillThrowWhenTryingToGetRequestClientBeforeInit()
+ {
+ $this->expectException(VaultException::class);
+ $this->expectExceptionMessage('Trying to request the VaultClient before initialization');
+
+ $auth = new UserPassword('foo', 'bar');
+ $auth->getVaultClient();
+ }
+}
diff --git a/tests/VaultPHP/Mocks/EndpointResponseMock.php b/tests/VaultPHP/Mocks/EndpointResponseMock.php
new file mode 100644
index 0000000..1c1c578
--- /dev/null
+++ b/tests/VaultPHP/Mocks/EndpointResponseMock.php
@@ -0,0 +1,23 @@
+getName();
+ }, $reflectionClass->getProperties());
+
+ return array_combine(
+ $classPropertyNames,
+ array_map('md5', $classPropertyNames)
+ );
+ }
+
+ private function checkDtoData($testData, $basicMetaData)
+ {
+ $this->assertEquals($testData['errors'], $basicMetaData->getErrors());
+ $this->assertEquals(false, $basicMetaData->hasErrors());
+ $this->assertEquals($testData['lease_duration'], $basicMetaData->getLeaseDuration());
+ $this->assertEquals($testData['auth'], $basicMetaData->getAuth());
+ $this->assertEquals($testData['lease_id'], $basicMetaData->getLeaseId());
+ $this->assertEquals($testData['renewable'], $basicMetaData->getRenewable());
+ $this->assertEquals($testData['request_id'], $basicMetaData->getRequestId());
+ $this->assertEquals($testData['warnings'], $basicMetaData->getWarnings());
+ $this->assertEquals($testData['wrap_info'], $basicMetaData->getWrapInfo());
+ }
+
+ public function testCanPopulateArrayDataToSelf()
+ {
+ $testData = $this->createTestData();
+ $basicMetaData = new BasicMetaResponse((array)$testData);
+ $this->checkDtoData($testData, $basicMetaData);
+ }
+
+ public function testCanPopulateObjectDataToSelf()
+ {
+ $testData = $this->createTestData();
+ $basicMetaData = new BasicMetaResponse((object)$testData);
+ $this->checkDtoData($testData, $basicMetaData);
+ }
+
+ public function testCheckForErrors()
+ {
+ $error = ["foo"];
+ $basicMetaData = new BasicMetaResponse(['errors' => $error]);
+
+ $this->assertTrue($basicMetaData->hasErrors());
+ $this->assertEquals($error, $basicMetaData->getErrors());
+
+ $basicMetaData = new BasicMetaResponse(['errors' => []]);
+
+ $this->assertFalse($basicMetaData->hasErrors());
+ $this->assertEquals([], $basicMetaData->getErrors());
+ }
+}
diff --git a/tests/VaultPHP/Response/EndpointResponseTest.php b/tests/VaultPHP/Response/EndpointResponseTest.php
new file mode 100644
index 0000000..158e1f8
--- /dev/null
+++ b/tests/VaultPHP/Response/EndpointResponseTest.php
@@ -0,0 +1,131 @@
+ [
+ 'metaDataError',
+ 'metaDataError2',
+ ],
+ ]));
+ $endpointResponse = EndpointResponse::fromResponse($response);
+ $basicMeta = $endpointResponse->getBasicMetaResponse();
+
+ $this->assertInstanceOf(EndpointResponse::class, $endpointResponse);
+ $this->assertInstanceOf(BasicMetaResponse::class, $basicMeta);
+ $this->assertEquals(
+ ['metaDataError', 'metaDataError2'],
+ $basicMeta->getErrors()
+ );
+ }
+
+ public function testCanGetPopulatePayloadDataFromResponse()
+ {
+ $response = new Response(200, [], json_encode([
+ 'data' => [
+ 'plaintext' => base64_encode('fooPlaintext'),
+ ],
+ ]));
+ $endpointResponse = DecryptDataResponse::fromResponse($response);
+
+ $this->assertInstanceOf(EndpointResponse::class, $endpointResponse);
+ $this->assertEquals('fooPlaintext', $endpointResponse->getPlaintext());
+ }
+
+ public function testCanGetPopulateMetaDataFromBulkResponse()
+ {
+ $response = new Response(200, [], json_encode([
+ 'errors' => [
+ 'metaDataError',
+ 'metaDataError2',
+ ],
+ 'data' => [
+ 'batch_results' => [
+ [],
+ [],
+ ],
+ ],
+ ]));
+ $arrayEndpointResponse = EndpointResponse::fromBulkResponse($response);
+ $this->assertSame(2, count($arrayEndpointResponse));
+
+ foreach($arrayEndpointResponse as $response) {
+ $basicMeta = $response->getBasicMetaResponse();
+
+ $this->assertInstanceOf(EndpointResponse::class, $response);
+ $this->assertInstanceOf(BasicMetaResponse::class, $basicMeta);
+ $this->assertEquals(
+ ['metaDataError', 'metaDataError2'],
+ $basicMeta->getErrors()
+ );
+ }
+ }
+
+ public function testBulkErrorsWillBeMergedInMetaDataErrors()
+ {
+ $batchErrors = [
+ ['error' => 'OH NO'],
+ ['error' => 'WHHAAT'],
+ [],
+ ];
+
+ $response = new Response(200, [], json_encode([
+ 'errors' => [
+ 'metaDataError',
+ 'metaDataError2',
+ ],
+ 'data' => [
+ 'batch_results' => $batchErrors,
+ ],
+ ]));
+
+ $arrayEndpointResponse = EndpointResponse::fromBulkResponse($response);
+ foreach($arrayEndpointResponse as $response) {
+ $basicMeta = $response->getBasicMetaResponse();
+
+ $this->assertEquals(
+ array_merge(
+ ['metaDataError', 'metaDataError2'],
+ array_values(current($batchErrors))
+ ),
+ $basicMeta->getErrors()
+ );
+ next($batchErrors);
+ }
+ }
+
+ public function testBulkPayloadWillBePopulatedToResponseClass()
+ {
+ $batchResponse = [
+ ['plaintext' => base64_encode('OH NO')],
+ ['plaintext' => base64_encode('WHHAAT')],
+ ];
+
+ $response = new Response(200, [], json_encode([
+ 'data' => [
+ 'batch_results' => $batchResponse,
+ ],
+ ]));
+
+ $arrayEndpointResponse = DecryptDataResponse::fromBulkResponse($response);
+ foreach($arrayEndpointResponse as $bulkResponse) {
+ $expected = array_map('base64_decode', current($batchResponse));
+ $this->assertEquals(current($expected), $bulkResponse->getPlaintext());
+ next($batchResponse);
+ }
+ }
+}
diff --git a/tests/VaultPHP/SecretEngines/Engines/Transit/CreateKeyTest.php b/tests/VaultPHP/SecretEngines/Engines/Transit/CreateKeyTest.php
new file mode 100644
index 0000000..208af2d
--- /dev/null
+++ b/tests/VaultPHP/SecretEngines/Engines/Transit/CreateKeyTest.php
@@ -0,0 +1,37 @@
+setType(EncryptionType::CHA_CHA_20_POLY_1305);
+
+ $client = $this->createApiClient(
+ 'POST',
+ '/v1/transit/keys/foobar',
+ $createKey->toArray(),
+ []
+ );
+
+ $api = new Transit($client);
+ $response = $api->createKey($createKey);
+
+ $this->assertInstanceOf(CreateKeyResponse::class, $response);
+
+ $this->assertEquals('foobar', $createKey->getName());
+ $this->assertEquals(EncryptionType::CHA_CHA_20_POLY_1305, $createKey->getType());
+ }
+}
diff --git a/tests/VaultPHP/SecretEngines/Engines/Transit/DecryptDataBulkTest.php b/tests/VaultPHP/SecretEngines/Engines/Transit/DecryptDataBulkTest.php
new file mode 100644
index 0000000..fdcca99
--- /dev/null
+++ b/tests/VaultPHP/SecretEngines/Engines/Transit/DecryptDataBulkTest.php
@@ -0,0 +1,55 @@
+createApiClient(
+ 'POST',
+ '/v1/transit/decrypt/foobar',
+ $decryptDataRequest->toArray(),
+ [
+ 'data' => [
+ 'batch_results' => [
+ ['plaintext' => base64_encode('plain')],
+ ['plaintext' => base64_encode('plain2')],
+ ]
+ ]
+ ]
+ );
+
+ $api = new Transit($client);
+ $response = $api->decryptDataBulk($decryptDataRequest);
+
+ $this->assertEquals(count($response), 2);
+
+ /** @var DecryptDataResponse $bulkResponseOne */
+ $bulkResponseOne = $response[0];
+ $this->assertEquals('plain', $bulkResponseOne->getPlaintext());
+
+ /** @var DecryptDataResponse $bulkResponseTwo */
+ $bulkResponseTwo = $response[1];
+ $this->assertEquals('plain2', $bulkResponseTwo->getPlaintext());
+
+ }
+}
diff --git a/tests/VaultPHP/SecretEngines/Engines/Transit/DecryptDataTest.php b/tests/VaultPHP/SecretEngines/Engines/Transit/DecryptDataTest.php
new file mode 100644
index 0000000..524e4be
--- /dev/null
+++ b/tests/VaultPHP/SecretEngines/Engines/Transit/DecryptDataTest.php
@@ -0,0 +1,44 @@
+setNonce('fooNonce');
+ $decryptDataRequest->setContext('fooContext');
+
+ $client = $this->createApiClient(
+ 'POST',
+ '/v1/transit/decrypt/fooName',
+ $decryptDataRequest->toArray(),
+ [
+ 'data' => [
+ 'plaintext' => base64_encode('fooBar'),
+ ]
+ ]
+ );
+
+ $api = new Transit($client);
+ $response = $api->decryptData($decryptDataRequest);
+
+ $this->assertInstanceOf(DecryptDataResponse::class, $response);
+ $this->assertEquals('fooBar', $response->getPlaintext());
+
+ $this->assertEquals('fooName', $decryptDataRequest->getName());
+ $this->assertEquals('fooContext', $decryptDataRequest->getContext());
+ $this->assertEquals('fooNonce', $decryptDataRequest->getNonce());
+ $this->assertEquals('fooCipher', $decryptDataRequest->getCiphertext());
+ }
+}
diff --git a/tests/VaultPHP/SecretEngines/Engines/Transit/DeleteKeyTest.php b/tests/VaultPHP/SecretEngines/Engines/Transit/DeleteKeyTest.php
new file mode 100644
index 0000000..be68a45
--- /dev/null
+++ b/tests/VaultPHP/SecretEngines/Engines/Transit/DeleteKeyTest.php
@@ -0,0 +1,28 @@
+createApiClient(
+ 'DELETE',
+ '/v1/transit/keys/foobar',
+ [],
+ []
+ );
+
+ $api = new Transit($client);
+ $response = $api->deleteKey('foobar');
+ $this->assertInstanceOf(DeleteKeyResponse::class, $response);
+ }
+}
diff --git a/tests/VaultPHP/SecretEngines/Engines/Transit/EncryptDataBulkTest.php b/tests/VaultPHP/SecretEngines/Engines/Transit/EncryptDataBulkTest.php
new file mode 100644
index 0000000..9630b69
--- /dev/null
+++ b/tests/VaultPHP/SecretEngines/Engines/Transit/EncryptDataBulkTest.php
@@ -0,0 +1,55 @@
+createApiClient(
+ 'POST',
+ '/v1/transit/encrypt/foobar',
+ $encryptRequest->toArray(),
+ [
+ 'data' => [
+ 'batch_results' => [
+ ['ciphertext' => 'foo1'],
+ ['ciphertext' => 'foo2'],
+ ]
+ ]
+ ]
+ );
+
+ $api = new Transit($client);
+ $response = $api->encryptDataBulk($encryptRequest);
+
+ $this->assertEquals(count($response), 2);
+
+ /** @var EncryptDataResponse $bulkResponseOne */
+ $bulkResponseOne = $response[0];
+ $this->assertEquals('foo1', $bulkResponseOne->getCiphertext());
+
+ /** @var EncryptDataResponse $bulkResponseTwo */
+ $bulkResponseTwo = $response[1];
+ $this->assertEquals('foo2', $bulkResponseTwo->getCiphertext());
+
+ }
+}
diff --git a/tests/VaultPHP/SecretEngines/Engines/Transit/EncryptDataTest.php b/tests/VaultPHP/SecretEngines/Engines/Transit/EncryptDataTest.php
new file mode 100644
index 0000000..7541650
--- /dev/null
+++ b/tests/VaultPHP/SecretEngines/Engines/Transit/EncryptDataTest.php
@@ -0,0 +1,47 @@
+setContext('fooContext');
+ $encryptDataRequest->setNonce('fooNonce');
+
+ $client = $this->createApiClient(
+ 'POST',
+ '/v1/transit/encrypt/foobar',
+ $encryptDataRequest->toArray(),
+ [
+ 'data' => [
+ 'ciphertext' => 'fooCipher'
+ ]
+ ]
+ );
+
+ $api = new Transit($client);
+
+ $response = $api->encryptData($encryptDataRequest);
+ $this->assertInstanceOf(EncryptDataResponse::class, $response);
+ $this->assertEquals('fooCipher', $response->getCiphertext());
+
+ $this->assertEquals('foobar', $encryptDataRequest->getName());
+ $this->assertEquals('fooNonce', $encryptDataRequest->getNonce());
+ $this->assertEquals('fooContext', $encryptDataRequest->getContext());
+ $this->assertEquals(base64_encode('encryptMe'), $encryptDataRequest->getPlaintext());
+ }
+}
diff --git a/tests/VaultPHP/SecretEngines/Engines/Transit/ListKeyTest.php b/tests/VaultPHP/SecretEngines/Engines/Transit/ListKeyTest.php
new file mode 100644
index 0000000..e3f9951
--- /dev/null
+++ b/tests/VaultPHP/SecretEngines/Engines/Transit/ListKeyTest.php
@@ -0,0 +1,51 @@
+createApiClient(
+ 'LIST',
+ '/v1/transit/keys',
+ [],
+ [
+ 'data' => [
+ 'keys' => [
+ 'key1',
+ 'key2',
+ ]
+ ]
+ ]
+ );
+ $api = new Transit($client);
+ $response = $api->listKeys();
+
+ $this->assertInstanceOf(ListKeysResponse::class, $response);
+ $this->assertEquals(['key1', 'key2'], $response->getKeys());
+ }
+
+ public function testListKeyRequestHasNoData()
+ {
+ $client = $this->createApiClient(
+ 'LIST',
+ '/v1/transit/keys',
+ [],
+ [],
+ 404
+ );
+ $api = new Transit($client);
+ $response = $api->listKeys();
+
+ $this->assertInstanceOf(ListKeysResponse::class, $response);
+ }
+}
diff --git a/tests/VaultPHP/SecretEngines/Engines/Transit/UpdateKeyConfigTest.php b/tests/VaultPHP/SecretEngines/Engines/Transit/UpdateKeyConfigTest.php
new file mode 100644
index 0000000..3c70220
--- /dev/null
+++ b/tests/VaultPHP/SecretEngines/Engines/Transit/UpdateKeyConfigTest.php
@@ -0,0 +1,51 @@
+setDeletionAllowed(true);
+ $request->setExportable(true);
+ $request->setAllowPlaintextBackup(true);
+ $request->setMinDecryptionVersion(1337);
+ $request->setMinEncryptionVersion(1338);
+
+ $client = $this->createApiClient(
+ 'POST',
+ '/v1/transit/keys/foo/config',
+ $request->toArray(),
+ [
+ 'data' => [
+ 'keys' => [
+ 'key1',
+ 'key2',
+ ]
+ ]
+ ]
+ );
+
+ $api = new Transit($client);
+
+ $response = $api->updateKeyConfig($request);
+ $this->assertInstanceOf(UpdateKeyConfigResponse::class, $response);
+
+ $this->assertEquals('foo', $request->getName());
+ $this->assertTrue($request->getDeletionAllowed());
+ $this->assertTrue($request->getExportable());
+ $this->assertTrue($request->getAllowPlaintextBackup());
+ $this->assertEquals(1337, $request->getMinDecryptionVersion());
+ $this->assertEquals(1338, $request->getMinEncryptionVersion());
+ }
+}
diff --git a/tests/VaultPHP/SecretEngines/SecretEngineTest.php b/tests/VaultPHP/SecretEngines/SecretEngineTest.php
new file mode 100644
index 0000000..b33fd27
--- /dev/null
+++ b/tests/VaultPHP/SecretEngines/SecretEngineTest.php
@@ -0,0 +1,34 @@
+createMock(HttpClient::class);
+ $httpMock
+ ->expects($this->once())
+ ->method('sendRequest')
+ ->with($this->callback(function(RequestInterface $request) use ($expectedMethod, $expectedPath, $expectedData) {
+ $this->assertEquals($request->getMethod(), $expectedMethod);
+ $this->assertEquals($request->getUri()->getPath(), $expectedPath);
+ $this->assertEquals($request->getBody()->getContents(), json_encode($expectedData));
+ return true;
+ }))
+ ->willReturn(new Response($responseStatus, [], json_encode($responseData)));
+
+ return new VaultClient($httpMock, new Token('foo'), 'http://iDontCare.de:443');
+ }
+}
diff --git a/tests/VaultPHP/SecretEngines/Traits/TraitTest.php b/tests/VaultPHP/SecretEngines/Traits/TraitTest.php
new file mode 100644
index 0000000..b519cbd
--- /dev/null
+++ b/tests/VaultPHP/SecretEngines/Traits/TraitTest.php
@@ -0,0 +1,73 @@
+setType(EncryptionType::RSA_2048);
+
+ $this->assertInstanceOf(ArrayExportInterface::class, $request);
+
+ $expectedArray = [
+ 'type' => 'rsa-2048',
+ 'name' => 'fooTest',
+ ];
+ $this->assertEquals($expectedArray, $request->toArray());
+ }
+
+ public function testNestedArrayExtractionFromRequest()
+ {
+ $request = new DecryptDataBulkRequest('fooTest');
+ $request->addBulkRequests([
+ new DecryptData('foo', 'fooContext', 'fooNonce'),
+ new DecryptData('foo2', 'fooContext2', 'fooNonce2'),
+ ]);
+
+ $request->addBulkRequest(
+ new DecryptData('foo3', 'fooContext3', 'fooNonce3')
+ );
+
+ $this->assertInstanceOf(ArrayExportInterface::class, $request);
+ $this->assertInstanceOf(BulkResourceRequestInterface::class, $request);
+ $this->assertInstanceOf(NamedRequestInterface::class, $request);
+
+ $expectedArray = [
+ 'name' => 'fooTest',
+ 'batch_input' => [
+ [
+ 'ciphertext' => 'foo',
+ 'context' => 'fooContext',
+ 'nonce' => 'fooNonce',
+ ],
+ [
+ 'ciphertext' => 'foo2',
+ 'context' => 'fooContext2',
+ 'nonce' => 'fooNonce2',
+ ],
+ [
+ 'ciphertext' => 'foo3',
+ 'context' => 'fooContext3',
+ 'nonce' => 'fooNonce3',
+ ],
+ ],
+ ];
+ $this->assertEquals($expectedArray, $request->toArray());
+ $this->assertEquals('fooTest', $request->getName());
+ }
+}
diff --git a/tests/VaultPHP/VaultClientTest.php b/tests/VaultPHP/VaultClientTest.php
new file mode 100644
index 0000000..9d164a6
--- /dev/null
+++ b/tests/VaultPHP/VaultClientTest.php
@@ -0,0 +1,289 @@
+createMock(HttpClient::class);
+ $client = new VaultClient($httpClient, $auth, TEST_VAULT_ENDPOINT);
+
+ $this->assertSame($client, $auth->getVaultClient());
+ }
+
+ public function testRequestWillExtendedWithDefaultVars() {
+ $auth = new Token('fooToken');
+
+ $httpClient = $this->createMock(HttpClient::class);
+ $httpClient
+ ->expects($this->once())
+ ->method('sendRequest')
+ ->with($this->callback(function(RequestInterface $requestWithDefaults) {
+ // test if values from last request are preserved
+ $this->assertEquals('LOL', $requestWithDefaults->getMethod());
+ $this->assertEquals('/i/should/be/preserved', $requestWithDefaults->getUri()->getPath());
+ $this->assertEquals(json_encode(['dontReplaceMe']), $requestWithDefaults->getBody()->getContents());
+
+ // test default values that should be added
+ $this->assertEquals('http', $requestWithDefaults->getUri()->getScheme());
+ $this->assertEquals('foo.bar', $requestWithDefaults->getUri()->getHost());
+ $this->assertEquals(1337, $requestWithDefaults->getUri()->getPort());
+
+ $this->assertSame('1', $requestWithDefaults->getHeader('X-Vault-Request')[0]);
+ $this->assertSame('fooToken', $requestWithDefaults->getHeader('X-Vault-Token')[0]);
+
+ return true;
+ }))
+ ->willReturn(new Response());
+
+ $client = new VaultClient($httpClient, $auth, "http://foo.bar:1337");
+ $client->sendApiRequest('LOL', '/i/should/be/preserved', EndpointResponse::class, ['dontReplaceMe']);
+ }
+
+ public function testWillThrowWhenApiHostMalformed() {
+ $this->expectException(VaultException::class);
+ $this->expectExceptionMessage('can\'t parse provided apiHost - malformed uri');
+
+ $auth = new Token('fooToken');
+ $httpClient = $this->createMock(HttpClient::class);
+
+ $client = new VaultClient($httpClient, $auth, "imInvalidHost");
+ $client->sendApiRequest('LOL', '/i/should/be/preserved', EndpointResponse::class, ['dontReplaceMe']);
+ }
+
+ public function testAuthenticateWillThrowWhenNoTokenIsReturned() {
+ $this->expectException(VaultAuthenticationException::class);
+
+ $httpClient = $this->createMock(HttpClient::class);
+ $httpClient
+ ->expects($this->once())
+ ->method('sendRequest')
+ ->willReturn(new Response(403));
+
+ $auth = new Token("fooBar");
+
+ $client = new VaultClient($httpClient, $auth, TEST_VAULT_ENDPOINT);
+ $client->sendApiRequest('GET', '/foo', EndpointResponse::class);
+ }
+
+ public function testWillThrowWhenAPIReturns403() {
+ $this->expectException(VaultAuthenticationException::class);
+
+ $httpClient = $this->createMock(HttpClient::class);
+ $auth = $this->createMock(AuthenticationProviderInterface::class);
+ $auth
+ ->expects($this->once())
+ ->method('authenticate')
+ ->willReturn(null);
+
+ $client = new VaultClient($httpClient, $auth, TEST_VAULT_ENDPOINT);
+ $client->sendApiRequest('GET', '/foo', EndpointResponse::class);
+ }
+
+ public function testSendRequest() {
+ $request = new Request('GET', 'foo');
+ $response = new Response();
+
+ $httpClient = $this->createMock(HttpClient::class);
+ $httpClient
+ ->expects($this->once())
+ ->method('sendRequest')
+ ->with($this->callback(function ($request) {
+ $this->assertInstanceOf(RequestInterface::class, $request);
+ return true;
+ }))
+ ->willReturn($response);
+
+ $auth = $this->createMock(AuthenticationProviderInterface::class);
+
+ $client = new VaultClient($httpClient, $auth, TEST_VAULT_ENDPOINT);
+ $client->sendApiRequest('GET', '/foo', EndpointResponse::class, [], false);
+ }
+
+ public function testSendRequestWillThrow() {
+ $this->expectException(VaultHttpException::class);
+ $this->expectExceptionMessage('foobarMessage');
+
+ $httpClient = $this->createMock(HttpClient::class);
+ $httpClient
+ ->expects($this->once())
+ ->method('sendRequest')
+ ->willThrowException(new \Exception('foobarMessage'));
+
+ $auth = $this->createMock(AuthenticationProviderInterface::class);
+ $auth->expects($this->once())
+ ->method('authenticate')
+ ->willReturn(new AuthenticationMetaData((object) [
+ 'client_token' => 'foo',
+ ]));
+
+ $client = new VaultClient($httpClient, $auth, TEST_VAULT_ENDPOINT);
+ $client->sendApiRequest('GET', '/foo', EndpointResponse::class);
+ }
+
+ public function testWillThrowWhenReturnClassDeclarationIsInvalid() {
+ $this->expectException(VaultException::class);
+ $this->expectExceptionMessage('Return Class declaration lacks static::fromResponse');
+
+ $httpClient = $this->createMock(HttpClient::class);
+ $httpClient
+ ->expects($this->once())
+ ->method('sendRequest')
+ ->willReturn(new Response(200));
+
+ $auth = $this->createMock(AuthenticationProviderInterface::class);
+ $auth->expects($this->once())
+ ->method('authenticate')
+ ->willReturn(new AuthenticationMetaData((object) [
+ 'client_token' => 'foo',
+ ]));
+
+ $client = new VaultClient($httpClient, $auth, TEST_VAULT_ENDPOINT);
+ $client->sendApiRequest('GET', '/foo', [], BasicMetaResponse::class);
+ }
+
+ public function testWillThrowWhenReturnClassDeclarationIsInvalidForBulk() {
+ $this->expectException(VaultException::class);
+ $this->expectExceptionMessage('Return Class declaration lacks static::fromBulkResponse');
+
+ $httpClient = $this->createMock(HttpClient::class);
+ $httpClient
+ ->expects($this->once())
+ ->method('sendRequest')
+ ->willReturn(new Response(200));
+
+ $auth = $this->createMock(AuthenticationProviderInterface::class);
+ $auth->expects($this->once())
+ ->method('authenticate')
+ ->willReturn(new AuthenticationMetaData((object) [
+ 'client_token' => 'foo',
+ ]));
+
+ $client = new VaultClient($httpClient, $auth, TEST_VAULT_ENDPOINT);
+ $client->sendApiRequest('GET', '/foo', BasicMetaResponse::class, new EncryptDataBulkRequest('foo'));
+ }
+
+ public function testWillThrowWhenResultOfReturnClassDeclarationIsInvalid() {
+ $this->expectException(VaultException::class);
+ $this->expectExceptionMessage('Result from "fromResponse/fromBulkResponse" isn\'t an instance of EndpointResponse');
+
+ $httpClient = $this->createMock(HttpClient::class);
+ $httpClient
+ ->expects($this->once())
+ ->method('sendRequest')
+ ->willReturn(new Response(200));
+
+ $auth = $this->createMock(AuthenticationProviderInterface::class);
+ $auth->expects($this->once())
+ ->method('authenticate')
+ ->willReturn(new AuthenticationMetaData((object) [
+ 'client_token' => 'foo',
+ ]));
+
+ $client = new VaultClient($httpClient, $auth, TEST_VAULT_ENDPOINT);
+ $client->sendApiRequest('GET', '/foo', EndpointResponseMock::class, []);
+ }
+
+ private function simulateApiResponse($responseStatus, $responseBody = '', $responseHeader = []) {
+ $response = new Response($responseStatus, $responseHeader, $responseBody);
+ $auth = new Token('fooToken');
+
+ $httpClient = $this->createMock(HttpClient::class);
+
+ $httpClient
+ ->expects($this->once())
+ ->method('sendRequest')
+ ->willReturn($response);
+
+ $client = new VaultClient($httpClient, $auth, TEST_VAULT_ENDPOINT);
+ return $client->sendApiRequest('GET', '/foo', EndpointResponse::class);
+ }
+
+ public function testSuccessApiResponse() {
+ $response = $this->simulateApiResponse(200, '');
+ $this->assertInstanceOf(EndpointResponse::class, $response);
+ }
+
+ public function testInvalidDataResponse() {
+ $this->expectException(InvalidDataException::class);
+ $this->expectExceptionMessage('looks malformed');
+
+ $this->simulateApiResponse(400, json_encode([
+ 'errors' => [
+ 'looks malformed',
+ ]
+ ]));
+ }
+
+ public function testInvalidDataResponseWillConcatErrorMessages() {
+ $this->expectException(InvalidDataException::class);
+ $this->expectExceptionMessage('looks malformed, oh no');
+
+ $this->simulateApiResponse(400, json_encode([
+ 'errors' => [
+ 'looks malformed',
+ 'oh no'
+ ]
+ ]));
+ }
+
+ public function testInvalidRouteResponse() {
+ $this->expectException(InvalidRouteException::class);
+ $this->simulateApiResponse(404, json_encode([
+ 'errors' => [
+ 'no handler',
+ ]
+ ]));
+ }
+
+ public function testEmptyResponse() {
+ $response = $this->simulateApiResponse(404);
+ $this->assertInstanceOf(EndpointResponse::class, $response);
+ }
+
+ public function testServerErrorResponse() {
+ $this->expectException(VaultResponseException::class);
+ $this->simulateApiResponse(500);
+ }
+
+ public function testNotHandledStatusCodeResponse() {
+ $this->expectException(VaultException::class);
+ $this->simulateApiResponse(100);
+ }
+
+ public function testResponseExceptionHasRequestResponseMeta() {
+ try {
+ $this->simulateApiResponse(555);
+ } catch (VaultResponseException $e) {
+ $this->assertInstanceOf(RequestInterface::class, $e->getRequest());
+ $this->assertInstanceOf(ResponseInterface::class, $e->getResponse());
+ }
+ }
+}
diff --git a/tests/bootstrap.php b/tests/bootstrap.php
new file mode 100644
index 0000000..e5d91aa
--- /dev/null
+++ b/tests/bootstrap.php
@@ -0,0 +1,15 @@
+= 70400) {
+ error_reporting(E_ALL ^ E_DEPRECATED);
+}