| 
									
										
										
										
											2017-08-18 17:08:30 +02:00
										 |  |  | <?php | 
					
						
							|  |  |  | /** | 
					
						
							|  |  |  |  * BunqRequest.php | 
					
						
							|  |  |  |  * Copyright (c) 2017 thegrumpydictator@gmail.com | 
					
						
							|  |  |  |  * This software may be modified and distributed under the terms of the | 
					
						
							|  |  |  |  * Creative Commons Attribution-ShareAlike 4.0 International License. | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  * See the LICENSE file for details. | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | declare(strict_types=1); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | namespace FireflyIII\Services\Bunq\Request; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | use Exception; | 
					
						
							| 
									
										
										
										
											2017-08-18 21:09:22 +02:00
										 |  |  | use FireflyIII\Exceptions\FireflyException; | 
					
						
							|  |  |  | use FireflyIII\Services\Bunq\Object\ServerPublicKey; | 
					
						
							|  |  |  | use Log; | 
					
						
							| 
									
										
										
										
											2017-08-18 17:08:30 +02:00
										 |  |  | use Requests; | 
					
						
							|  |  |  | use Requests_Exception; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /** | 
					
						
							|  |  |  |  * Class BunqRequest | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  * @package Bunq\Request | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | abstract class BunqRequest | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     /** @var string */ | 
					
						
							|  |  |  |     protected $secret = ''; | 
					
						
							| 
									
										
										
										
											2017-09-16 07:17:58 +02:00
										 |  |  |     /** @var  ServerPublicKey */ | 
					
						
							|  |  |  |     protected $serverPublicKey; | 
					
						
							| 
									
										
										
										
											2017-08-18 17:08:30 +02:00
										 |  |  |     /** @var string */ | 
					
						
							|  |  |  |     private $privateKey = ''; | 
					
						
							|  |  |  |     /** @var string */ | 
					
						
							|  |  |  |     private $server = ''; | 
					
						
							|  |  |  |     private $upperCaseHeaders | 
					
						
							| 
									
										
										
										
											2017-09-16 07:17:58 +02:00
										 |  |  |                     = [ | 
					
						
							| 
									
										
										
										
											2017-08-18 17:08:30 +02:00
										 |  |  |             'x-bunq-client-response-id' => 'X-Bunq-Client-Response-Id', | 
					
						
							|  |  |  |             'x-bunq-client-request-id'  => 'X-Bunq-Client-Request-Id', | 
					
						
							|  |  |  |         ]; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-09-28 08:17:32 +02:00
										 |  |  |     /** | 
					
						
							|  |  |  |      * @return ServerPublicKey | 
					
						
							|  |  |  |      */ | 
					
						
							|  |  |  |     public function getServerPublicKey(): ServerPublicKey | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |         return $this->serverPublicKey; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-08-18 21:09:22 +02:00
										 |  |  |     /** | 
					
						
							|  |  |  |      * BunqRequest constructor. | 
					
						
							|  |  |  |      */ | 
					
						
							| 
									
										
										
										
											2017-08-18 17:08:30 +02:00
										 |  |  |     public function __construct() | 
					
						
							|  |  |  |     { | 
					
						
							| 
									
										
										
										
											2017-08-27 08:54:58 +02:00
										 |  |  |         $this->server = config('firefly.bunq.server'); | 
					
						
							| 
									
										
										
										
											2017-08-18 17:08:30 +02:00
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     /** | 
					
						
							|  |  |  |      * | 
					
						
							|  |  |  |      */ | 
					
						
							|  |  |  |     abstract public function call(): void; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     /** | 
					
						
							|  |  |  |      * @return string | 
					
						
							|  |  |  |      */ | 
					
						
							|  |  |  |     public function getServer(): string | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |         return $this->server; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     /** | 
					
						
							|  |  |  |      * @param string $privateKey | 
					
						
							|  |  |  |      */ | 
					
						
							|  |  |  |     public function setPrivateKey(string $privateKey) | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |         $this->privateKey = $privateKey; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     /** | 
					
						
							|  |  |  |      * @param string $secret | 
					
						
							|  |  |  |      */ | 
					
						
							|  |  |  |     public function setSecret(string $secret) | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |         $this->secret = $secret; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     /** | 
					
						
							|  |  |  |      * @param ServerPublicKey $serverPublicKey | 
					
						
							|  |  |  |      */ | 
					
						
							|  |  |  |     public function setServerPublicKey(ServerPublicKey $serverPublicKey) | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |         $this->serverPublicKey = $serverPublicKey; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     /** | 
					
						
							|  |  |  |      * @param string $method | 
					
						
							|  |  |  |      * @param string $uri | 
					
						
							|  |  |  |      * @param array  $headers | 
					
						
							|  |  |  |      * @param string $data | 
					
						
							|  |  |  |      * | 
					
						
							|  |  |  |      * @return string | 
					
						
							| 
									
										
										
										
											2017-08-19 09:22:44 +02:00
										 |  |  |      * @throws FireflyException | 
					
						
							| 
									
										
										
										
											2017-08-18 17:08:30 +02:00
										 |  |  |      */ | 
					
						
							|  |  |  |     protected function generateSignature(string $method, string $uri, array $headers, string $data): string | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |         if (strlen($this->privateKey) === 0) { | 
					
						
							| 
									
										
										
										
											2017-08-19 09:22:44 +02:00
										 |  |  |             throw new FireflyException('No private key present.'); | 
					
						
							| 
									
										
										
										
											2017-08-18 17:08:30 +02:00
										 |  |  |         } | 
					
						
							| 
									
										
										
										
											2017-08-19 09:22:44 +02:00
										 |  |  |         if (strtolower($method) === 'get' || strtolower($method) === 'delete') { | 
					
						
							| 
									
										
										
										
											2017-08-18 17:08:30 +02:00
										 |  |  |             $data = ''; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         $uri           = str_replace(['https://api.bunq.com', 'https://sandbox.public.api.bunq.com'], '', $uri); | 
					
						
							| 
									
										
										
										
											2017-08-19 09:22:44 +02:00
										 |  |  |         $toSign        = sprintf("%s %s\n", strtoupper($method), $uri); | 
					
						
							| 
									
										
										
										
											2017-08-18 17:08:30 +02:00
										 |  |  |         $headersToSign = ['Cache-Control', 'User-Agent']; | 
					
						
							|  |  |  |         ksort($headers); | 
					
						
							|  |  |  |         foreach ($headers as $name => $value) { | 
					
						
							|  |  |  |             if (in_array($name, $headersToSign) || substr($name, 0, 7) === 'X-Bunq-') { | 
					
						
							| 
									
										
										
										
											2017-08-19 09:22:44 +02:00
										 |  |  |                 $toSign .= sprintf("%s: %s\n", $name, $value); | 
					
						
							| 
									
										
										
										
											2017-08-18 17:08:30 +02:00
										 |  |  |             } | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         $toSign    .= "\n" . $data; | 
					
						
							|  |  |  |         $signature = ''; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         openssl_sign($toSign, $signature, $this->privateKey, OPENSSL_ALGO_SHA256); | 
					
						
							|  |  |  |         $signature = base64_encode($signature); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         return $signature; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-08-18 21:09:22 +02:00
										 |  |  |     /** | 
					
						
							|  |  |  |      * @param string $key | 
					
						
							|  |  |  |      * @param array  $response | 
					
						
							|  |  |  |      * | 
					
						
							|  |  |  |      * @return array | 
					
						
							|  |  |  |      */ | 
					
						
							|  |  |  |     protected function getArrayFromResponse(string $key, array $response): array | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |         $result = []; | 
					
						
							|  |  |  |         if (isset($response['Response'])) { | 
					
						
							|  |  |  |             foreach ($response['Response'] as $entry) { | 
					
						
							|  |  |  |                 $currentKey = key($entry); | 
					
						
							|  |  |  |                 $data       = current($entry); | 
					
						
							|  |  |  |                 if ($currentKey === $key) { | 
					
						
							|  |  |  |                     $result[] = $data; | 
					
						
							|  |  |  |                 } | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         return $result; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-08-18 17:08:30 +02:00
										 |  |  |     protected function getDefaultHeaders(): array | 
					
						
							|  |  |  |     { | 
					
						
							| 
									
										
										
										
											2017-08-18 21:09:22 +02:00
										 |  |  |         $userAgent = sprintf('FireflyIII v%s', config('firefly.version')); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-08-18 17:08:30 +02:00
										 |  |  |         return [ | 
					
						
							| 
									
										
										
										
											2017-08-18 21:09:22 +02:00
										 |  |  |             'X-Bunq-Client-Request-Id' => uniqid('FFIII'), | 
					
						
							| 
									
										
										
										
											2017-08-18 17:08:30 +02:00
										 |  |  |             'Cache-Control'            => 'no-cache', | 
					
						
							| 
									
										
										
										
											2017-08-18 21:09:22 +02:00
										 |  |  |             'User-Agent'               => $userAgent, | 
					
						
							| 
									
										
										
										
											2017-08-18 17:08:30 +02:00
										 |  |  |             'X-Bunq-Language'          => 'en_US', | 
					
						
							|  |  |  |             'X-Bunq-Region'            => 'nl_NL', | 
					
						
							|  |  |  |             'X-Bunq-Geolocation'       => '0 0 0 0 NL', | 
					
						
							|  |  |  |         ]; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     /** | 
					
						
							|  |  |  |      * @param string $key | 
					
						
							|  |  |  |      * @param array  $response | 
					
						
							|  |  |  |      * | 
					
						
							|  |  |  |      * @return array | 
					
						
							|  |  |  |      */ | 
					
						
							|  |  |  |     protected function getKeyFromResponse(string $key, array $response): array | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |         if (isset($response['Response'])) { | 
					
						
							|  |  |  |             foreach ($response['Response'] as $entry) { | 
					
						
							|  |  |  |                 $currentKey = key($entry); | 
					
						
							|  |  |  |                 $data       = current($entry); | 
					
						
							|  |  |  |                 if ($currentKey === $key) { | 
					
						
							|  |  |  |                     return $data; | 
					
						
							|  |  |  |                 } | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         return []; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-08-19 09:22:44 +02:00
										 |  |  |     /** | 
					
						
							|  |  |  |      * @param string $uri | 
					
						
							|  |  |  |      * @param array  $headers | 
					
						
							|  |  |  |      * | 
					
						
							|  |  |  |      * @return array | 
					
						
							|  |  |  |      * @throws Exception | 
					
						
							|  |  |  |      */ | 
					
						
							|  |  |  |     protected function sendSignedBunqDelete(string $uri, array $headers): array | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |         if (strlen($this->server) === 0) { | 
					
						
							|  |  |  |             throw new FireflyException('No bunq server defined'); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         $fullUri                            = $this->server . $uri; | 
					
						
							|  |  |  |         $signature                          = $this->generateSignature('delete', $uri, $headers, ''); | 
					
						
							|  |  |  |         $headers['X-Bunq-Client-Signature'] = $signature; | 
					
						
							|  |  |  |         try { | 
					
						
							|  |  |  |             $response = Requests::delete($fullUri, $headers); | 
					
						
							|  |  |  |         } catch (Requests_Exception $e) { | 
					
						
							|  |  |  |             return ['Error' => [0 => ['error_description' => $e->getMessage(), 'error_description_translated' => $e->getMessage()],]]; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         $body                        = $response->body; | 
					
						
							|  |  |  |         $array                       = json_decode($body, true); | 
					
						
							|  |  |  |         $responseHeaders             = $response->headers->getAll(); | 
					
						
							| 
									
										
										
										
											2017-09-16 07:17:58 +02:00
										 |  |  |         $statusCode                  = intval($response->status_code); | 
					
						
							| 
									
										
										
										
											2017-08-19 09:22:44 +02:00
										 |  |  |         $array['ResponseHeaders']    = $responseHeaders; | 
					
						
							|  |  |  |         $array['ResponseStatusCode'] = $statusCode; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         Log::debug(sprintf('Response to DELETE %s is %s', $fullUri, $body)); | 
					
						
							|  |  |  |         if ($this->isErrorResponse($array)) { | 
					
						
							|  |  |  |             $this->throwResponseError($array); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         if (!$this->verifyServerSignature($body, $responseHeaders, $statusCode)) { | 
					
						
							|  |  |  |             throw new FireflyException(sprintf('Could not verify signature for request to "%s"', $uri)); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         return $array; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-08-18 17:08:30 +02:00
										 |  |  |     /** | 
					
						
							|  |  |  |      * @param string $uri | 
					
						
							|  |  |  |      * @param array  $data | 
					
						
							|  |  |  |      * @param array  $headers | 
					
						
							|  |  |  |      * | 
					
						
							|  |  |  |      * @return array | 
					
						
							|  |  |  |      * @throws Exception | 
					
						
							|  |  |  |      */ | 
					
						
							|  |  |  |     protected function sendSignedBunqGet(string $uri, array $data, array $headers): array | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |         if (strlen($this->server) === 0) { | 
					
						
							| 
									
										
										
										
											2017-08-18 21:09:22 +02:00
										 |  |  |             throw new FireflyException('No bunq server defined'); | 
					
						
							| 
									
										
										
										
											2017-08-18 17:08:30 +02:00
										 |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         $body                               = json_encode($data); | 
					
						
							|  |  |  |         $fullUri                            = $this->server . $uri; | 
					
						
							|  |  |  |         $signature                          = $this->generateSignature('get', $uri, $headers, $body); | 
					
						
							|  |  |  |         $headers['X-Bunq-Client-Signature'] = $signature; | 
					
						
							|  |  |  |         try { | 
					
						
							|  |  |  |             $response = Requests::get($fullUri, $headers); | 
					
						
							|  |  |  |         } catch (Requests_Exception $e) { | 
					
						
							|  |  |  |             return ['Error' => [0 => ['error_description' => $e->getMessage(), 'error_description_translated' => $e->getMessage()],]]; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-08-19 09:22:44 +02:00
										 |  |  |         $body                        = $response->body; | 
					
						
							|  |  |  |         $array                       = json_decode($body, true); | 
					
						
							|  |  |  |         $responseHeaders             = $response->headers->getAll(); | 
					
						
							| 
									
										
										
										
											2017-09-16 07:17:58 +02:00
										 |  |  |         $statusCode                  = intval($response->status_code); | 
					
						
							| 
									
										
										
										
											2017-08-19 09:22:44 +02:00
										 |  |  |         $array['ResponseHeaders']    = $responseHeaders; | 
					
						
							|  |  |  |         $array['ResponseStatusCode'] = $statusCode; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-08-18 17:08:30 +02:00
										 |  |  |         if ($this->isErrorResponse($array)) { | 
					
						
							|  |  |  |             $this->throwResponseError($array); | 
					
						
							|  |  |  |         } | 
					
						
							| 
									
										
										
										
											2017-08-19 09:22:44 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-08-18 17:08:30 +02:00
										 |  |  |         if (!$this->verifyServerSignature($body, $responseHeaders, $statusCode)) { | 
					
						
							| 
									
										
										
										
											2017-08-18 21:09:22 +02:00
										 |  |  |             throw new FireflyException(sprintf('Could not verify signature for request to "%s"', $uri)); | 
					
						
							| 
									
										
										
										
											2017-08-18 17:08:30 +02:00
										 |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         return $array; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     /** | 
					
						
							|  |  |  |      * @param string $uri | 
					
						
							|  |  |  |      * @param array  $data | 
					
						
							|  |  |  |      * @param array  $headers | 
					
						
							|  |  |  |      * | 
					
						
							|  |  |  |      * @return array | 
					
						
							|  |  |  |      * @throws Exception | 
					
						
							|  |  |  |      */ | 
					
						
							|  |  |  |     protected function sendSignedBunqPost(string $uri, array $data, array $headers): array | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |         $body                               = json_encode($data); | 
					
						
							|  |  |  |         $fullUri                            = $this->server . $uri; | 
					
						
							|  |  |  |         $signature                          = $this->generateSignature('post', $uri, $headers, $body); | 
					
						
							|  |  |  |         $headers['X-Bunq-Client-Signature'] = $signature; | 
					
						
							|  |  |  |         try { | 
					
						
							|  |  |  |             $response = Requests::post($fullUri, $headers, $body); | 
					
						
							|  |  |  |         } catch (Requests_Exception $e) { | 
					
						
							|  |  |  |             return ['Error' => [0 => ['error_description' => $e->getMessage(), 'error_description_translated' => $e->getMessage()],]]; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-08-19 09:22:44 +02:00
										 |  |  |         $body                        = $response->body; | 
					
						
							|  |  |  |         $array                       = json_decode($body, true); | 
					
						
							|  |  |  |         $responseHeaders             = $response->headers->getAll(); | 
					
						
							| 
									
										
										
										
											2017-09-16 07:17:58 +02:00
										 |  |  |         $statusCode                  = intval($response->status_code); | 
					
						
							| 
									
										
										
										
											2017-08-19 09:22:44 +02:00
										 |  |  |         $array['ResponseHeaders']    = $responseHeaders; | 
					
						
							|  |  |  |         $array['ResponseStatusCode'] = $statusCode; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-08-18 17:08:30 +02:00
										 |  |  |         if ($this->isErrorResponse($array)) { | 
					
						
							|  |  |  |             $this->throwResponseError($array); | 
					
						
							|  |  |  |         } | 
					
						
							| 
									
										
										
										
											2017-08-19 09:22:44 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-08-18 17:08:30 +02:00
										 |  |  |         if (!$this->verifyServerSignature($body, $responseHeaders, $statusCode)) { | 
					
						
							| 
									
										
										
										
											2017-08-18 21:09:22 +02:00
										 |  |  |             throw new FireflyException(sprintf('Could not verify signature for request to "%s"', $uri)); | 
					
						
							| 
									
										
										
										
											2017-08-18 17:08:30 +02:00
										 |  |  |         } | 
					
						
							| 
									
										
										
										
											2017-08-19 09:22:44 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         return $array; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     /** | 
					
						
							|  |  |  |      * @param string $uri | 
					
						
							|  |  |  |      * @param array  $headers | 
					
						
							|  |  |  |      * | 
					
						
							|  |  |  |      * @return array | 
					
						
							|  |  |  |      */ | 
					
						
							|  |  |  |     protected function sendUnsignedBunqDelete(string $uri, array $headers): array | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |         $fullUri = $this->server . $uri; | 
					
						
							|  |  |  |         try { | 
					
						
							|  |  |  |             $response = Requests::delete($fullUri, $headers); | 
					
						
							|  |  |  |         } catch (Requests_Exception $e) { | 
					
						
							|  |  |  |             return ['Error' => [0 => ['error_description' => $e->getMessage(), 'error_description_translated' => $e->getMessage()],]]; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         $body                        = $response->body; | 
					
						
							|  |  |  |         $array                       = json_decode($body, true); | 
					
						
							|  |  |  |         $responseHeaders             = $response->headers->getAll(); | 
					
						
							|  |  |  |         $statusCode                  = $response->status_code; | 
					
						
							|  |  |  |         $array['ResponseHeaders']    = $responseHeaders; | 
					
						
							|  |  |  |         $array['ResponseStatusCode'] = $statusCode; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         if ($this->isErrorResponse($array)) { | 
					
						
							|  |  |  |             $this->throwResponseError($array); | 
					
						
							|  |  |  |         } | 
					
						
							| 
									
										
										
										
											2017-08-18 17:08:30 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |         return $array; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     /** | 
					
						
							|  |  |  |      * @param string $uri | 
					
						
							|  |  |  |      * @param array  $data | 
					
						
							|  |  |  |      * @param array  $headers | 
					
						
							|  |  |  |      * | 
					
						
							|  |  |  |      * @return array | 
					
						
							|  |  |  |      */ | 
					
						
							|  |  |  |     protected function sendUnsignedBunqPost(string $uri, array $data, array $headers): array | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |         $body    = json_encode($data); | 
					
						
							|  |  |  |         $fullUri = $this->server . $uri; | 
					
						
							|  |  |  |         try { | 
					
						
							|  |  |  |             $response = Requests::post($fullUri, $headers, $body); | 
					
						
							|  |  |  |         } catch (Requests_Exception $e) { | 
					
						
							|  |  |  |             return ['Error' => [0 => ['error_description' => $e->getMessage(), 'error_description_translated' => $e->getMessage()],]]; | 
					
						
							|  |  |  |         } | 
					
						
							| 
									
										
										
										
											2017-08-19 09:22:44 +02:00
										 |  |  |         $body                        = $response->body; | 
					
						
							|  |  |  |         $array                       = json_decode($body, true); | 
					
						
							|  |  |  |         $responseHeaders             = $response->headers->getAll(); | 
					
						
							|  |  |  |         $statusCode                  = $response->status_code; | 
					
						
							|  |  |  |         $array['ResponseHeaders']    = $responseHeaders; | 
					
						
							|  |  |  |         $array['ResponseStatusCode'] = $statusCode; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-08-18 17:08:30 +02:00
										 |  |  |         if ($this->isErrorResponse($array)) { | 
					
						
							|  |  |  |             $this->throwResponseError($array); | 
					
						
							|  |  |  |         } | 
					
						
							| 
									
										
										
										
											2017-08-19 09:22:44 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-08-18 17:08:30 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |         return $array; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     /** | 
					
						
							|  |  |  |      * @param array $response | 
					
						
							|  |  |  |      * | 
					
						
							|  |  |  |      * @return bool | 
					
						
							|  |  |  |      */ | 
					
						
							|  |  |  |     private function isErrorResponse(array $response): bool | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |         $key = key($response); | 
					
						
							|  |  |  |         if ($key === 'Error') { | 
					
						
							|  |  |  |             return true; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         return false; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     /** | 
					
						
							|  |  |  |      * @param array $response | 
					
						
							|  |  |  |      * | 
					
						
							|  |  |  |      * @throws Exception | 
					
						
							|  |  |  |      */ | 
					
						
							|  |  |  |     private function throwResponseError(array $response) | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |         $message = []; | 
					
						
							|  |  |  |         if (isset($response['Error'])) { | 
					
						
							|  |  |  |             foreach ($response['Error'] as $error) { | 
					
						
							|  |  |  |                 $message[] = $error['error_description']; | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |         } | 
					
						
							| 
									
										
										
										
											2017-08-19 09:22:44 +02:00
										 |  |  |         throw new FireflyException('Bunq ERROR ' . $response['ResponseStatusCode'] . ': ' . join(', ', $message)); | 
					
						
							| 
									
										
										
										
											2017-08-18 17:08:30 +02:00
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     /** | 
					
						
							|  |  |  |      * @param string $body | 
					
						
							|  |  |  |      * @param array  $headers | 
					
						
							|  |  |  |      * @param int    $statusCode | 
					
						
							|  |  |  |      * | 
					
						
							|  |  |  |      * @return bool | 
					
						
							|  |  |  |      * @throws Exception | 
					
						
							|  |  |  |      */ | 
					
						
							|  |  |  |     private function verifyServerSignature(string $body, array $headers, int $statusCode): bool | 
					
						
							|  |  |  |     { | 
					
						
							| 
									
										
										
										
											2017-08-18 21:09:22 +02:00
										 |  |  |         Log::debug('Going to verify signature for body+headers+status'); | 
					
						
							| 
									
										
										
										
											2017-08-18 17:08:30 +02:00
										 |  |  |         $dataToVerify  = $statusCode . "\n"; | 
					
						
							|  |  |  |         $verifyHeaders = []; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         // false when no public key is present
 | 
					
						
							|  |  |  |         if (is_null($this->serverPublicKey)) { | 
					
						
							| 
									
										
										
										
											2017-08-18 21:09:22 +02:00
										 |  |  |             Log::error('No public key present in class, so return FALSE.'); | 
					
						
							| 
									
										
										
										
											2017-08-18 17:08:30 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |             return false; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         foreach ($headers as $header => $value) { | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             // skip non-bunq headers or signature
 | 
					
						
							|  |  |  |             if (substr($header, 0, 7) !== 'x-bunq-' || $header === 'x-bunq-server-signature') { | 
					
						
							|  |  |  |                 continue; | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |             // need to have upper case variant of header:
 | 
					
						
							|  |  |  |             if (!isset($this->upperCaseHeaders[$header])) { | 
					
						
							| 
									
										
										
										
											2017-08-18 21:09:22 +02:00
										 |  |  |                 throw new FireflyException(sprintf('No upper case variant for header "%s"', $header)); | 
					
						
							| 
									
										
										
										
											2017-08-18 17:08:30 +02:00
										 |  |  |             } | 
					
						
							|  |  |  |             $header                 = $this->upperCaseHeaders[$header]; | 
					
						
							|  |  |  |             $verifyHeaders[$header] = $value[0]; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         // sort verification headers:
 | 
					
						
							|  |  |  |         ksort($verifyHeaders); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         // add them to data to sign:
 | 
					
						
							|  |  |  |         foreach ($verifyHeaders as $header => $value) { | 
					
						
							|  |  |  |             $dataToVerify .= $header . ': ' . trim($value) . "\n"; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         $signature    = $headers['x-bunq-server-signature'][0]; | 
					
						
							|  |  |  |         $dataToVerify .= "\n" . $body; | 
					
						
							| 
									
										
										
										
											2017-08-18 21:09:22 +02:00
										 |  |  |         $result       = openssl_verify($dataToVerify, base64_decode($signature), $this->serverPublicKey->getPublicKey(), OPENSSL_ALGO_SHA256); | 
					
						
							| 
									
										
										
										
											2017-08-18 17:08:30 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |         if (is_int($result) && $result < 1) { | 
					
						
							| 
									
										
										
										
											2017-08-18 21:09:22 +02:00
										 |  |  |             Log::error(sprintf('Result of verification is %d, return false.', $result)); | 
					
						
							| 
									
										
										
										
											2017-08-18 17:08:30 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |             return false; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         if (!is_int($result)) { | 
					
						
							| 
									
										
										
										
											2017-08-18 21:09:22 +02:00
										 |  |  |             Log::error(sprintf('Result of verification is a boolean (%d), return false.', $result)); | 
					
						
							| 
									
										
										
										
											2017-08-18 17:08:30 +02:00
										 |  |  |         } | 
					
						
							| 
									
										
										
										
											2017-08-18 21:09:22 +02:00
										 |  |  |         Log::info('Signature is a match, return true.'); | 
					
						
							| 
									
										
										
										
											2017-08-18 17:08:30 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |         return true; | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2017-08-31 06:47:18 +02:00
										 |  |  | } |