Can now register with Bunq and get device server ID.

This commit is contained in:
James Cole
2017-08-18 21:09:22 +02:00
parent df443aa34c
commit 684c9773c9
11 changed files with 577 additions and 61 deletions

View File

@@ -0,0 +1,63 @@
<?php
/**
* DeviceServer.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\Object;
use Carbon\Carbon;
use FireflyIII\Services\Bunq\Id\DeviceServerId;
class DeviceServer extends BunqObject
{
/** @var Carbon */
private $created;
/** @var string */
private $description;
/** @var DeviceServerId */
private $id;
/** @var string */
private $ip;
/** @var string */
private $status;
/** @var Carbon */
private $updated;
public function __construct(array $data)
{
$id = new DeviceServerId();
$id->setId($data['id']);
$this->id = $id;
$this->created = Carbon::createFromFormat('Y-m-d H:i:s.u', $data['created']);
$this->updated = Carbon::createFromFormat('Y-m-d H:i:s.u', $data['updated']);
$this->ip = $data['ip'];
$this->description = $data['description'];
$this->status = $data['status'];
}
/**
* @return DeviceServerId
*/
public function getId(): DeviceServerId
{
return $this->id;
}
/**
* @return string
*/
public function getIp(): string
{
return $this->ip;
}
}

View File

@@ -12,10 +12,10 @@ declare(strict_types=1);
namespace FireflyIII\Services\Bunq\Request;
use Bunq\Object\ServerPublicKey;
use Exception;
use Monolog\Handler\StreamHandler;
use Monolog\Logger;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Services\Bunq\Object\ServerPublicKey;
use Log;
use Requests;
use Requests_Exception;
@@ -26,12 +26,8 @@ use Requests_Exception;
*/
abstract class BunqRequest
{
/** @var bool */
protected $fake = false;
/** @var string */
protected $secret = '';
/** @var Logger */
private $logger;
/** @var string */
private $privateKey = '';
/** @var string */
@@ -44,14 +40,11 @@ abstract class BunqRequest
'x-bunq-client-request-id' => 'X-Bunq-Client-Request-Id',
];
/**
* BunqRequest constructor.
*/
public function __construct()
{
// create a log channel
$this->logger = new Logger('bunq-request');
$this->logger->pushHandler(new StreamHandler('logs/bunq.log', Logger::DEBUG));
$this->logger->debug('Hallo dan');
}
/**
@@ -75,14 +68,6 @@ abstract class BunqRequest
$this->server = $server;
}
/**
* @param bool $fake
*/
public function setFake(bool $fake)
{
$this->fake = $fake;
}
/**
* @param string $privateKey
*/
@@ -142,12 +127,36 @@ abstract class BunqRequest
return $signature;
}
/**
* @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;
}
protected function getDefaultHeaders(): array
{
$userAgent = sprintf('FireflyIII v%s', config('firefly.version'));
return [
'X-Bunq-Client-Request-Id' => uniqid('sander'),
'X-Bunq-Client-Request-Id' => uniqid('FFIII'),
'Cache-Control' => 'no-cache',
'User-Agent' => 'pre-Firefly III test thing',
'User-Agent' => $userAgent,
'X-Bunq-Language' => 'en_US',
'X-Bunq-Region' => 'nl_NL',
'X-Bunq-Geolocation' => '0 0 0 0 NL',
@@ -186,7 +195,7 @@ abstract class BunqRequest
protected function sendSignedBunqGet(string $uri, array $data, array $headers): array
{
if (strlen($this->server) === 0) {
throw new Exception('No bunq server defined');
throw new FireflyException('No bunq server defined');
}
$body = json_encode($data);
@@ -207,7 +216,7 @@ abstract class BunqRequest
$responseHeaders = $response->headers->getAll();
$statusCode = $response->status_code;
if (!$this->verifyServerSignature($body, $responseHeaders, $statusCode)) {
throw new Exception(sprintf('Could not verify signature for request to "%s"', $uri));
throw new FireflyException(sprintf('Could not verify signature for request to "%s"', $uri));
}
$array['ResponseHeaders'] = $responseHeaders;
@@ -242,7 +251,7 @@ abstract class BunqRequest
$responseHeaders = $response->headers->getAll();
$statusCode = $response->status_code;
if (!$this->verifyServerSignature($body, $responseHeaders, $statusCode)) {
throw new Exception(sprintf('Could not verify signature for request to "%s"', $uri));
throw new FireflyException(sprintf('Could not verify signature for request to "%s"', $uri));
}
$array['ResponseHeaders'] = $responseHeaders;
@@ -298,14 +307,13 @@ abstract class BunqRequest
*/
private function throwResponseError(array $response)
{
echo '<hr><pre>' . print_r($response, true) . '</pre><hr>';
$message = [];
if (isset($response['Error'])) {
foreach ($response['Error'] as $error) {
$message[] = $error['error_description'];
}
}
throw new Exception(join(', ', $message));
throw new FireflyException(join(', ', $message));
}
/**
@@ -318,17 +326,16 @@ abstract class BunqRequest
*/
private function verifyServerSignature(string $body, array $headers, int $statusCode): bool
{
$this->logger->debug('Going to verify signature for body+headers+status');
Log::debug('Going to verify signature for body+headers+status');
$dataToVerify = $statusCode . "\n";
$verifyHeaders = [];
// false when no public key is present
if (is_null($this->serverPublicKey)) {
$this->logger->error('No public key present in class, so return FALSE.');
Log::error('No public key present in class, so return FALSE.');
return false;
}
//$this->logger->debug('Given headers', $headers);
foreach ($headers as $header => $value) {
// skip non-bunq headers or signature
@@ -337,7 +344,7 @@ abstract class BunqRequest
}
// need to have upper case variant of header:
if (!isset($this->upperCaseHeaders[$header])) {
throw new Exception(sprintf('No upper case variant for header "%s"', $header));
throw new FireflyException(sprintf('No upper case variant for header "%s"', $header));
}
$header = $this->upperCaseHeaders[$header];
$verifyHeaders[$header] = $value[0];
@@ -345,8 +352,6 @@ abstract class BunqRequest
// sort verification headers:
ksort($verifyHeaders);
//$this->logger->debug('Final headers for verification', $verifyHeaders);
// add them to data to sign:
foreach ($verifyHeaders as $header => $value) {
$dataToVerify .= $header . ': ' . trim($value) . "\n";
@@ -354,20 +359,17 @@ abstract class BunqRequest
$signature = $headers['x-bunq-server-signature'][0];
$dataToVerify .= "\n" . $body;
//$this->logger->debug(sprintf('Signature to verify: "%s"', $signature));
$result = openssl_verify($dataToVerify, base64_decode($signature), $this->serverPublicKey->getPublicKey(), OPENSSL_ALGO_SHA256);
$result = openssl_verify($dataToVerify, base64_decode($signature), $this->serverPublicKey->getPublicKey(), OPENSSL_ALGO_SHA256);
if (is_int($result) && $result < 1) {
$this->logger->error(sprintf('Result of verification is %d, return false.', $result));
Log::error(sprintf('Result of verification is %d, return false.', $result));
return false;
}
if (!is_int($result)) {
$this->logger->error(sprintf('Result of verification is a boolean (%d), return false.', $result));
Log::error(sprintf('Result of verification is a boolean (%d), return false.', $result));
}
$this->logger->info('Signature is a match, return true.');
Log::info('Signature is a match, return true.');
return true;
}

View File

@@ -0,0 +1,82 @@
<?php
/**
* DeviceServerRequest.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 FireflyIII\Services\Bunq\Id\DeviceServerId;
use FireflyIII\Services\Bunq\Token\InstallationToken;
/**
* Class DeviceServerRequest
*
* @package Bunq\Request
*/
class DeviceServerRequest extends BunqRequest
{
/** @var string */
private $description = '';
/** @var DeviceServerId */
private $deviceServerId;
/** @var InstallationToken */
private $installationToken;
/** @var array */
private $permittedIps = [];
/**
*
*/
public function call(): void
{
$uri = '/v1/device-server';
$data = ['description' => $this->description, 'secret' => $this->secret, 'permitted_ips' => $this->permittedIps];
$headers = $this->getDefaultHeaders();
$headers['X-Bunq-Client-Authentication'] = $this->installationToken->getToken();
$response = $this->sendSignedBunqPost($uri, $data, $headers);
$deviceServerId = new DeviceServerId;
$deviceServerId->setId(intval($response['Response'][0]['Id']['id']));
$this->deviceServerId = $deviceServerId;
return;
}
/**
* @return DeviceServerId
*/
public function getDeviceServerId(): DeviceServerId
{
return $this->deviceServerId;
}
/**
* @param string $description
*/
public function setDescription(string $description)
{
$this->description = $description;
}
/**
* @param InstallationToken $installationToken
*/
public function setInstallationToken(InstallationToken $installationToken)
{
$this->installationToken = $installationToken;
}
/**
* @param array $permittedIps
*/
public function setPermittedIps(array $permittedIps)
{
$this->permittedIps = $permittedIps;
}
}

View File

@@ -13,6 +13,9 @@ declare(strict_types=1);
namespace FireflyIII\Services\Bunq\Request;
use FireflyIII\Services\Bunq\Id\InstallationId;
use FireflyIII\Services\Bunq\Object\ServerPublicKey;
use FireflyIII\Services\Bunq\Token\InstallationToken;
use Log;
/**
* Class InstallationTokenRequest
@@ -38,22 +41,17 @@ class InstallationTokenRequest extends BunqRequest
$uri = '/v1/installation';
$data = ['client_public_key' => $this->publicKey,];
$headers = $this->getDefaultHeaders();
$response = [];
if ($this->fake) {
$response = json_decode(
'{"Response":[{"Id":{"id":875936}},{"Token":{"id":13172597,"created":"2017-08-05 11:46:07.061740","updated":"2017-08-05 11:46:07.061740","token":"35278fcc8b0615261fe23285e6d2e6ccd05ac4c93454981bd5e985ec453e5b5d"}},{"ServerPublicKey":{"server_public_key":"-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAon5y6OZGvTN8kIqPBdro\ndG8TWVw6sl34hAWI47NK6Pi7gmnTtd\/k9gfwq56iI4Er8uMM5e4QmjD++XrBIqcw\nHohDVK03li3xsyJPZ4EBSUOkv4VKXKL\/quqlSgDmPnxtT39BowUZl1um5QbTm0hW\npGI\/0bK7jQk7mbEan9yDOpXnczKgfNlo4o+zbFquPdUfA5LE8R8X057dB6ab7eqA\n9Aybo+I6xyrsOOztufg3Yfe5RA6a0Sikqe\/L8HCP+9TJByUI2pwydPou3KONfYhK\n1NQJZ+RCZ6V+jmcuzKe2vq0jhBZd26wNscl48Sm7etJeuBOpHE+MgO24JiTEYlLS\nVQIDAQAB\n-----END PUBLIC KEY-----\n"}}]}',
true
);
}
if (!$this->fake) {
$response = $this->sendUnsignedBunqPost($uri, $data, $headers);
}
//echo '<hr><pre>' . json_encode($response) . '</pre><hr>';
$response = $this->sendUnsignedBunqPost($uri, $data, $headers);
Log::debug('Installation request response', $response);
$this->installationId = $this->extractInstallationId($response);
$this->serverPublicKey = $this->extractServerPublicKey($response);
$this->installationToken = $this->extractInstallationToken($response);
Log::debug(sprintf('Installation ID: %s', serialize($this->installationId)));
Log::debug(sprintf('Installation token: %s', serialize($this->installationToken)));
Log::debug(sprintf('server public key: %s', serialize($this->serverPublicKey)));
return;
}

View File

@@ -0,0 +1,74 @@
<?php
/**
* ListDeviceServerRequest.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 FireflyIII\Services\Bunq\Object\DeviceServer;
use FireflyIII\Services\Bunq\Token\InstallationToken;
use Illuminate\Support\Collection;
/**
* Class ListDeviceServerRequest
*
* @package FireflyIII\Services\Bunq\Request
*/
class ListDeviceServerRequest extends BunqRequest
{
/** @var Collection */
private $devices;
/** @var InstallationToken */
private $installationToken;
public function __construct()
{
parent::__construct();
$this->devices = new Collection;
}
/**
* @return Collection
*/
public function getDevices(): Collection
{
return $this->devices;
}
/**
*
*/
public function call(): void
{
$uri = '/v1/device-server';
$data = [];
$headers = $this->getDefaultHeaders();
$headers['X-Bunq-Client-Authentication'] = $this->installationToken->getToken();
$response = $this->sendSignedBunqGet($uri, $data, $headers);
// create device server objects:
$raw = $this->getArrayFromResponse('DeviceServer', $response);
/** @var array $entry */
foreach ($raw as $entry) {
$this->devices->push(new DeviceServer($entry));
}
return;
}
/**
* @param InstallationToken $installationToken
*/
public function setInstallationToken(InstallationToken $installationToken)
{
$this->installationToken = $installationToken;
}
}