mirror of
https://github.com/firefly-iii/firefly-iii.git
synced 2025-02-25 18:45:27 -06:00
Can now register with Bunq and get device server ID.
This commit is contained in:
parent
df443aa34c
commit
684c9773c9
@ -14,17 +14,54 @@ namespace FireflyIII\Http\Controllers\Import;
|
|||||||
|
|
||||||
use FireflyIII\Http\Controllers\Controller;
|
use FireflyIII\Http\Controllers\Controller;
|
||||||
use FireflyIII\Support\Import\Prerequisites\PrerequisitesInterface;
|
use FireflyIII\Support\Import\Prerequisites\PrerequisitesInterface;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Log;
|
||||||
|
use Session;
|
||||||
|
|
||||||
class BankController extends Controller
|
class BankController extends Controller
|
||||||
{
|
{
|
||||||
|
|
||||||
public function postPrerequisites()
|
public function form()
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* @param Request $request
|
||||||
* @param string $bank
|
* @param string $bank
|
||||||
|
*
|
||||||
|
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
|
||||||
|
*/
|
||||||
|
public function postPrerequisites(Request $request, string $bank)
|
||||||
|
{
|
||||||
|
Log::debug(sprintf('Now in postPrerequisites for %s', $bank));
|
||||||
|
$class = config(sprintf('firefly.import_pre.%s', $bank));
|
||||||
|
/** @var PrerequisitesInterface $object */
|
||||||
|
$object = app($class);
|
||||||
|
$object->setUser(auth()->user());
|
||||||
|
if (!$object->hasPrerequisites()) {
|
||||||
|
//Log::debug(sprintf('No more prerequisites for %s, move to form.', $bank));
|
||||||
|
//return redirect(route('import.bank.form', [$bank]));
|
||||||
|
}
|
||||||
|
Log::debug('Going to store entered preprerequisites.');
|
||||||
|
// store post data
|
||||||
|
$result = $object->storePrerequisites($request);
|
||||||
|
echo 'done with prereq';
|
||||||
|
exit;
|
||||||
|
|
||||||
|
if ($result->count() > 0) {
|
||||||
|
Session::flash('error', $result->first());
|
||||||
|
|
||||||
|
return redirect(route('import.bank.prerequisites', [$bank]));
|
||||||
|
}
|
||||||
|
|
||||||
|
return redirect(route('import.bank.form', [$bank]));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $bank
|
||||||
|
*
|
||||||
|
* @return \Illuminate\Contracts\View\Factory|\Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector|\Illuminate\View\View
|
||||||
*/
|
*/
|
||||||
public function prerequisites(string $bank)
|
public function prerequisites(string $bank)
|
||||||
{
|
{
|
||||||
@ -33,17 +70,16 @@ class BankController extends Controller
|
|||||||
$object = app($class);
|
$object = app($class);
|
||||||
$object->setUser(auth()->user());
|
$object->setUser(auth()->user());
|
||||||
|
|
||||||
if ($object->hasPrerequisites()) {
|
//if ($object->hasPrerequisites()) {
|
||||||
$view = $object->getView();
|
$view = $object->getView();
|
||||||
$parameters = $object->getViewParameters();
|
$parameters = $object->getViewParameters();
|
||||||
|
|
||||||
return view($view, $parameters);
|
return view($view, $parameters);
|
||||||
}
|
//}
|
||||||
|
|
||||||
if (!$object->hasPrerequisites()) {
|
if (!$object->hasPrerequisites()) {
|
||||||
echo 'redirect to import form.';
|
return redirect(route('import.bank.form', [$bank]));
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -14,6 +14,7 @@ declare(strict_types=1);
|
|||||||
namespace FireflyIII\Models;
|
namespace FireflyIII\Models;
|
||||||
|
|
||||||
use Crypt;
|
use Crypt;
|
||||||
|
use Exception;
|
||||||
use FireflyIII\Exceptions\FireflyException;
|
use FireflyIII\Exceptions\FireflyException;
|
||||||
use Illuminate\Contracts\Encryption\DecryptException;
|
use Illuminate\Contracts\Encryption\DecryptException;
|
||||||
use Illuminate\Database\Eloquent\Model;
|
use Illuminate\Database\Eloquent\Model;
|
||||||
@ -56,7 +57,15 @@ class Preference extends Model
|
|||||||
sprintf('Could not decrypt preference #%d. If this error persists, please run "php artisan cache:clear" on the command line.', $this->id)
|
sprintf('Could not decrypt preference #%d. If this error persists, please run "php artisan cache:clear" on the command line.', $this->id)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
$unserialized = false;
|
||||||
|
try {
|
||||||
|
$unserialized = unserialize($data);
|
||||||
|
} catch (Exception $e) {
|
||||||
|
// don't care, assume is false.
|
||||||
|
}
|
||||||
|
if (!($unserialized === false)) {
|
||||||
|
return $unserialized;
|
||||||
|
}
|
||||||
|
|
||||||
return json_decode($data, true);
|
return json_decode($data, true);
|
||||||
}
|
}
|
||||||
@ -66,7 +75,7 @@ class Preference extends Model
|
|||||||
*/
|
*/
|
||||||
public function setDataAttribute($value)
|
public function setDataAttribute($value)
|
||||||
{
|
{
|
||||||
$this->attributes['data'] = Crypt::encrypt(json_encode($value));
|
$this->attributes['data'] = Crypt::encrypt(serialize($value));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
63
app/Services/Bunq/Object/DeviceServer.php
Normal file
63
app/Services/Bunq/Object/DeviceServer.php
Normal 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
@ -12,10 +12,10 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace FireflyIII\Services\Bunq\Request;
|
namespace FireflyIII\Services\Bunq\Request;
|
||||||
|
|
||||||
use Bunq\Object\ServerPublicKey;
|
|
||||||
use Exception;
|
use Exception;
|
||||||
use Monolog\Handler\StreamHandler;
|
use FireflyIII\Exceptions\FireflyException;
|
||||||
use Monolog\Logger;
|
use FireflyIII\Services\Bunq\Object\ServerPublicKey;
|
||||||
|
use Log;
|
||||||
use Requests;
|
use Requests;
|
||||||
use Requests_Exception;
|
use Requests_Exception;
|
||||||
|
|
||||||
@ -26,12 +26,8 @@ use Requests_Exception;
|
|||||||
*/
|
*/
|
||||||
abstract class BunqRequest
|
abstract class BunqRequest
|
||||||
{
|
{
|
||||||
/** @var bool */
|
|
||||||
protected $fake = false;
|
|
||||||
/** @var string */
|
/** @var string */
|
||||||
protected $secret = '';
|
protected $secret = '';
|
||||||
/** @var Logger */
|
|
||||||
private $logger;
|
|
||||||
/** @var string */
|
/** @var string */
|
||||||
private $privateKey = '';
|
private $privateKey = '';
|
||||||
/** @var string */
|
/** @var string */
|
||||||
@ -44,14 +40,11 @@ abstract class BunqRequest
|
|||||||
'x-bunq-client-request-id' => 'X-Bunq-Client-Request-Id',
|
'x-bunq-client-request-id' => 'X-Bunq-Client-Request-Id',
|
||||||
];
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* BunqRequest constructor.
|
||||||
|
*/
|
||||||
public function __construct()
|
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;
|
$this->server = $server;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @param bool $fake
|
|
||||||
*/
|
|
||||||
public function setFake(bool $fake)
|
|
||||||
{
|
|
||||||
$this->fake = $fake;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param string $privateKey
|
* @param string $privateKey
|
||||||
*/
|
*/
|
||||||
@ -142,12 +127,36 @@ abstract class BunqRequest
|
|||||||
return $signature;
|
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
|
protected function getDefaultHeaders(): array
|
||||||
{
|
{
|
||||||
|
$userAgent = sprintf('FireflyIII v%s', config('firefly.version'));
|
||||||
|
|
||||||
return [
|
return [
|
||||||
'X-Bunq-Client-Request-Id' => uniqid('sander'),
|
'X-Bunq-Client-Request-Id' => uniqid('FFIII'),
|
||||||
'Cache-Control' => 'no-cache',
|
'Cache-Control' => 'no-cache',
|
||||||
'User-Agent' => 'pre-Firefly III test thing',
|
'User-Agent' => $userAgent,
|
||||||
'X-Bunq-Language' => 'en_US',
|
'X-Bunq-Language' => 'en_US',
|
||||||
'X-Bunq-Region' => 'nl_NL',
|
'X-Bunq-Region' => 'nl_NL',
|
||||||
'X-Bunq-Geolocation' => '0 0 0 0 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
|
protected function sendSignedBunqGet(string $uri, array $data, array $headers): array
|
||||||
{
|
{
|
||||||
if (strlen($this->server) === 0) {
|
if (strlen($this->server) === 0) {
|
||||||
throw new Exception('No bunq server defined');
|
throw new FireflyException('No bunq server defined');
|
||||||
}
|
}
|
||||||
|
|
||||||
$body = json_encode($data);
|
$body = json_encode($data);
|
||||||
@ -207,7 +216,7 @@ abstract class BunqRequest
|
|||||||
$responseHeaders = $response->headers->getAll();
|
$responseHeaders = $response->headers->getAll();
|
||||||
$statusCode = $response->status_code;
|
$statusCode = $response->status_code;
|
||||||
if (!$this->verifyServerSignature($body, $responseHeaders, $statusCode)) {
|
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;
|
$array['ResponseHeaders'] = $responseHeaders;
|
||||||
|
|
||||||
@ -242,7 +251,7 @@ abstract class BunqRequest
|
|||||||
$responseHeaders = $response->headers->getAll();
|
$responseHeaders = $response->headers->getAll();
|
||||||
$statusCode = $response->status_code;
|
$statusCode = $response->status_code;
|
||||||
if (!$this->verifyServerSignature($body, $responseHeaders, $statusCode)) {
|
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;
|
$array['ResponseHeaders'] = $responseHeaders;
|
||||||
|
|
||||||
@ -298,14 +307,13 @@ abstract class BunqRequest
|
|||||||
*/
|
*/
|
||||||
private function throwResponseError(array $response)
|
private function throwResponseError(array $response)
|
||||||
{
|
{
|
||||||
echo '<hr><pre>' . print_r($response, true) . '</pre><hr>';
|
|
||||||
$message = [];
|
$message = [];
|
||||||
if (isset($response['Error'])) {
|
if (isset($response['Error'])) {
|
||||||
foreach ($response['Error'] as $error) {
|
foreach ($response['Error'] as $error) {
|
||||||
$message[] = $error['error_description'];
|
$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
|
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";
|
$dataToVerify = $statusCode . "\n";
|
||||||
$verifyHeaders = [];
|
$verifyHeaders = [];
|
||||||
|
|
||||||
// false when no public key is present
|
// false when no public key is present
|
||||||
if (is_null($this->serverPublicKey)) {
|
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;
|
return false;
|
||||||
}
|
}
|
||||||
//$this->logger->debug('Given headers', $headers);
|
|
||||||
foreach ($headers as $header => $value) {
|
foreach ($headers as $header => $value) {
|
||||||
|
|
||||||
// skip non-bunq headers or signature
|
// skip non-bunq headers or signature
|
||||||
@ -337,7 +344,7 @@ abstract class BunqRequest
|
|||||||
}
|
}
|
||||||
// need to have upper case variant of header:
|
// need to have upper case variant of header:
|
||||||
if (!isset($this->upperCaseHeaders[$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];
|
$header = $this->upperCaseHeaders[$header];
|
||||||
$verifyHeaders[$header] = $value[0];
|
$verifyHeaders[$header] = $value[0];
|
||||||
@ -345,8 +352,6 @@ abstract class BunqRequest
|
|||||||
// sort verification headers:
|
// sort verification headers:
|
||||||
ksort($verifyHeaders);
|
ksort($verifyHeaders);
|
||||||
|
|
||||||
//$this->logger->debug('Final headers for verification', $verifyHeaders);
|
|
||||||
|
|
||||||
// add them to data to sign:
|
// add them to data to sign:
|
||||||
foreach ($verifyHeaders as $header => $value) {
|
foreach ($verifyHeaders as $header => $value) {
|
||||||
$dataToVerify .= $header . ': ' . trim($value) . "\n";
|
$dataToVerify .= $header . ': ' . trim($value) . "\n";
|
||||||
@ -354,20 +359,17 @@ abstract class BunqRequest
|
|||||||
|
|
||||||
$signature = $headers['x-bunq-server-signature'][0];
|
$signature = $headers['x-bunq-server-signature'][0];
|
||||||
$dataToVerify .= "\n" . $body;
|
$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) {
|
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;
|
return false;
|
||||||
}
|
}
|
||||||
if (!is_int($result)) {
|
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;
|
return true;
|
||||||
}
|
}
|
||||||
|
82
app/Services/Bunq/Request/DeviceServerRequest.php
Normal file
82
app/Services/Bunq/Request/DeviceServerRequest.php
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
@ -13,6 +13,9 @@ declare(strict_types=1);
|
|||||||
namespace FireflyIII\Services\Bunq\Request;
|
namespace FireflyIII\Services\Bunq\Request;
|
||||||
|
|
||||||
use FireflyIII\Services\Bunq\Id\InstallationId;
|
use FireflyIII\Services\Bunq\Id\InstallationId;
|
||||||
|
use FireflyIII\Services\Bunq\Object\ServerPublicKey;
|
||||||
|
use FireflyIII\Services\Bunq\Token\InstallationToken;
|
||||||
|
use Log;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class InstallationTokenRequest
|
* Class InstallationTokenRequest
|
||||||
@ -38,22 +41,17 @@ class InstallationTokenRequest extends BunqRequest
|
|||||||
$uri = '/v1/installation';
|
$uri = '/v1/installation';
|
||||||
$data = ['client_public_key' => $this->publicKey,];
|
$data = ['client_public_key' => $this->publicKey,];
|
||||||
$headers = $this->getDefaultHeaders();
|
$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);
|
$response = $this->sendUnsignedBunqPost($uri, $data, $headers);
|
||||||
}
|
Log::debug('Installation request response', $response);
|
||||||
//echo '<hr><pre>' . json_encode($response) . '</pre><hr>';
|
|
||||||
|
|
||||||
$this->installationId = $this->extractInstallationId($response);
|
$this->installationId = $this->extractInstallationId($response);
|
||||||
$this->serverPublicKey = $this->extractServerPublicKey($response);
|
$this->serverPublicKey = $this->extractServerPublicKey($response);
|
||||||
$this->installationToken = $this->extractInstallationToken($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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
74
app/Services/Bunq/Request/ListDeviceServerRequest.php
Normal file
74
app/Services/Bunq/Request/ListDeviceServerRequest.php
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
@ -11,8 +11,21 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace FireflyIII\Support\Import\Prerequisites;
|
namespace FireflyIII\Support\Import\Prerequisites;
|
||||||
|
|
||||||
|
use FireflyIII\Exceptions\FireflyException;
|
||||||
|
use FireflyIII\Services\Bunq\Id\DeviceServerId;
|
||||||
|
use FireflyIII\Services\Bunq\Object\DeviceServer;
|
||||||
|
use FireflyIII\Services\Bunq\Object\ServerPublicKey;
|
||||||
|
use FireflyIII\Services\Bunq\Request\DeviceServerRequest;
|
||||||
|
use FireflyIII\Services\Bunq\Request\InstallationTokenRequest;
|
||||||
|
use FireflyIII\Services\Bunq\Request\ListDeviceServerRequest;
|
||||||
|
use FireflyIII\Services\Bunq\Token\InstallationToken;
|
||||||
use FireflyIII\User;
|
use FireflyIII\User;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Support\MessageBag;
|
||||||
|
use Log;
|
||||||
use Preferences;
|
use Preferences;
|
||||||
|
use Requests;
|
||||||
|
use Requests_Exception;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class BunqPrerequisites
|
* Class BunqPrerequisites
|
||||||
@ -68,4 +81,232 @@ class BunqPrerequisites implements PrerequisitesInterface
|
|||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Request $request
|
||||||
|
*
|
||||||
|
* @return MessageBag
|
||||||
|
*/
|
||||||
|
public function storePrerequisites(Request $request): MessageBag
|
||||||
|
{
|
||||||
|
$apiKey = $request->get('api_key');
|
||||||
|
Log::debug('Storing bunq API key');
|
||||||
|
Preferences::setForUser($this->user, 'bunq_api_key', $apiKey);
|
||||||
|
// register Firefly III as a new device.
|
||||||
|
$serverId = $this->registerDevice();
|
||||||
|
|
||||||
|
|
||||||
|
return new MessageBag;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
private function createKeyPair(): void
|
||||||
|
{
|
||||||
|
Log::debug('Generate new key pair for user.');
|
||||||
|
$keyConfig = [
|
||||||
|
"digest_alg" => "sha512",
|
||||||
|
"private_key_bits" => 2048,
|
||||||
|
"private_key_type" => OPENSSL_KEYTYPE_RSA,
|
||||||
|
];
|
||||||
|
// Create the private and public key
|
||||||
|
$res = openssl_pkey_new($keyConfig);
|
||||||
|
|
||||||
|
// Extract the private key from $res to $privKey
|
||||||
|
$privKey = '';
|
||||||
|
openssl_pkey_export($res, $privKey);
|
||||||
|
|
||||||
|
// Extract the public key from $res to $pubKey
|
||||||
|
$pubKey = openssl_pkey_get_details($res);
|
||||||
|
|
||||||
|
Preferences::setForUser($this->user, 'bunq_private_key', $privKey);
|
||||||
|
Preferences::setForUser($this->user, 'bunq_public_key', $pubKey['key']);
|
||||||
|
Log::debug('Created key pair');
|
||||||
|
|
||||||
|
return;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a list of servers and return the one that is this FF instance, if one can be found.
|
||||||
|
*
|
||||||
|
* @return DeviceServerId
|
||||||
|
* @throws FireflyException
|
||||||
|
*/
|
||||||
|
private function getExistingDevice(): DeviceServerId
|
||||||
|
{
|
||||||
|
$installationToken = $this->getInstallationToken();
|
||||||
|
$serverPublicKey = $this->getServerPublicKey();
|
||||||
|
$request = new ListDeviceServerRequest;
|
||||||
|
$remoteIp = $this->getRemoteIp();
|
||||||
|
$request->setInstallationToken($installationToken);
|
||||||
|
$request->setServerPublicKey($serverPublicKey);
|
||||||
|
$request->setPrivateKey($this->getPrivateKey());
|
||||||
|
$request->setServer(config('firefly.bunq.server'));
|
||||||
|
$request->call();
|
||||||
|
$devices = $request->getDevices();
|
||||||
|
/** @var DeviceServer $device */
|
||||||
|
foreach ($devices as $device) {
|
||||||
|
if ($device->getIp() === $remoteIp) {
|
||||||
|
return $device->getId();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new FireflyException('Cannot find existing Server Device that can be used by this instance of Firefly III.');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the installation token, either from the users preferences or from Bunq.
|
||||||
|
*
|
||||||
|
* @return InstallationToken
|
||||||
|
*/
|
||||||
|
private function getInstallationToken(): InstallationToken
|
||||||
|
{
|
||||||
|
Log::debug('Get installation token.');
|
||||||
|
$token = Preferences::getForUser($this->user, 'bunq_installation_token', null);
|
||||||
|
if (!is_null($token)) {
|
||||||
|
return $token->data;
|
||||||
|
}
|
||||||
|
Log::debug('Have no token, request one.');
|
||||||
|
|
||||||
|
// verify bunq api code:
|
||||||
|
$publicKey = $this->getPublicKey();
|
||||||
|
$request = new InstallationTokenRequest;
|
||||||
|
$request->setServer(strval(config('firefly.bunq.server')));
|
||||||
|
$request->setPublicKey($publicKey);
|
||||||
|
$request->call();
|
||||||
|
Log::debug('Sent request');
|
||||||
|
|
||||||
|
$installationToken = $request->getInstallationToken();
|
||||||
|
$installationId = $request->getInstallationId();
|
||||||
|
$serverPublicKey = $request->getServerPublicKey();
|
||||||
|
|
||||||
|
Preferences::setForUser($this->user, 'bunq_installation_token', $installationToken);
|
||||||
|
Preferences::setForUser($this->user, 'bunq_installation_id', $installationId);
|
||||||
|
Preferences::setForUser($this->user, 'bunq_server_public_key', $serverPublicKey);
|
||||||
|
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the private key from the users preferences.
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
private function getPrivateKey(): string
|
||||||
|
{
|
||||||
|
Log::debug('get private key');
|
||||||
|
$preference = Preferences::getForUser($this->user, 'bunq_private_key', null);
|
||||||
|
if (is_null($preference)) {
|
||||||
|
Log::debug('private key is null');
|
||||||
|
// create key pair
|
||||||
|
$this->createKeyPair();
|
||||||
|
}
|
||||||
|
$preference = Preferences::getForUser($this->user, 'bunq_private_key', null);
|
||||||
|
Log::debug('Return private key for user');
|
||||||
|
|
||||||
|
return $preference->data;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a public key from the users preferences.
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
private function getPublicKey(): string
|
||||||
|
{
|
||||||
|
Log::debug('get public key');
|
||||||
|
$preference = Preferences::getForUser($this->user, 'bunq_public_key', null);
|
||||||
|
if (is_null($preference)) {
|
||||||
|
Log::debug('public key is null');
|
||||||
|
// create key pair
|
||||||
|
$this->createKeyPair();
|
||||||
|
}
|
||||||
|
$preference = Preferences::getForUser($this->user, 'bunq_public_key', null);
|
||||||
|
Log::debug('Return public key for user');
|
||||||
|
|
||||||
|
return $preference->data;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Let's assume this value will not change any time soon.
|
||||||
|
*/
|
||||||
|
private function getRemoteIp(): string
|
||||||
|
{
|
||||||
|
$preference = Preferences::getForUser($this->user, 'external_ip', null);
|
||||||
|
if (is_null($preference)) {
|
||||||
|
try {
|
||||||
|
$response = Requests::get('https://api.ipify.org');
|
||||||
|
} catch (Requests_Exception $e) {
|
||||||
|
throw new FireflyException(sprintf('Could not retrieve external IP: %s', $e->getMessage()));
|
||||||
|
}
|
||||||
|
if ($response->status_code !== 200) {
|
||||||
|
throw new FireflyException(sprintf('Could not retrieve external IP: %d %s', $response->status_code, $response->body));
|
||||||
|
}
|
||||||
|
$ip = $response->body;
|
||||||
|
Preferences::setForUser($this->user, 'external_ip', $ip);
|
||||||
|
|
||||||
|
return $ip;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $preference->data;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return ServerPublicKey
|
||||||
|
*/
|
||||||
|
private function getServerPublicKey(): ServerPublicKey
|
||||||
|
{
|
||||||
|
return Preferences::getForUser($this->user, 'bunq_server_public_key', null)->data;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* To install Firefly III as a new device:
|
||||||
|
* - Send an installation token request.
|
||||||
|
* - Use this token to send a device server request
|
||||||
|
* - Store the installation token
|
||||||
|
* - Use the installation token each time we need a session.
|
||||||
|
*/
|
||||||
|
private function registerDevice(): DeviceServerId
|
||||||
|
{
|
||||||
|
Log::debug('Now in registerDevice');
|
||||||
|
$deviceServerId = Preferences::getForUser($this->user, 'bunq_device_server_id', null);
|
||||||
|
$serverIp = $this->getRemoteIp();
|
||||||
|
if (!is_null($deviceServerId)) {
|
||||||
|
Log::debug('Have device server ID.');
|
||||||
|
|
||||||
|
return $deviceServerId->data;
|
||||||
|
}
|
||||||
|
Log::debug('Device server id is null, do register.');
|
||||||
|
$installationToken = $this->getInstallationToken();
|
||||||
|
$serverPublicKey = $this->getServerPublicKey();
|
||||||
|
$apiKey = Preferences::getForUser($this->user, 'bunq_api_key', '');
|
||||||
|
$request = new DeviceServerRequest;
|
||||||
|
$request->setServer(strval(config('firefly.bunq.server')));
|
||||||
|
$request->setPrivateKey($this->getPrivateKey());
|
||||||
|
$request->setDescription('Firefly III v' . config('firefly.version') . ' for ' . $this->user->email);
|
||||||
|
$request->setSecret($apiKey->data);
|
||||||
|
$request->setPermittedIps([$serverIp]);
|
||||||
|
$request->setInstallationToken($installationToken);
|
||||||
|
$request->setServerPublicKey($serverPublicKey);
|
||||||
|
$deviceServerId = null;
|
||||||
|
// try to register device:
|
||||||
|
try {
|
||||||
|
$request->call();
|
||||||
|
$deviceServerId = $request->getDeviceServerId();
|
||||||
|
} catch (FireflyException $e) {
|
||||||
|
Log::error($e->getMessage());
|
||||||
|
}
|
||||||
|
if (is_null($deviceServerId)) {
|
||||||
|
// try get the current from a list:
|
||||||
|
$deviceServerId = $this->getExistingDevice();
|
||||||
|
}
|
||||||
|
|
||||||
|
Preferences::setForUser($this->user, 'bunq_device_server_id', $deviceServerId);
|
||||||
|
Log::debug(sprintf('Server ID: %s', serialize($deviceServerId)));
|
||||||
|
var_dump($deviceServerId);
|
||||||
|
var_dump($installationToken);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -13,6 +13,8 @@ namespace FireflyIII\Support\Import\Prerequisites;
|
|||||||
|
|
||||||
|
|
||||||
use FireflyIII\User;
|
use FireflyIII\User;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Support\MessageBag;
|
||||||
|
|
||||||
interface PrerequisitesInterface
|
interface PrerequisitesInterface
|
||||||
{
|
{
|
||||||
@ -30,6 +32,13 @@ interface PrerequisitesInterface
|
|||||||
*/
|
*/
|
||||||
public function getViewParameters(): array;
|
public function getViewParameters(): array;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Request $request
|
||||||
|
*
|
||||||
|
* @return MessageBag
|
||||||
|
*/
|
||||||
|
public function storePrerequisites(Request $request): MessageBag;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns if this import method has any special prerequisites such as config
|
* Returns if this import method has any special prerequisites such as config
|
||||||
* variables or other things.
|
* variables or other things.
|
||||||
|
@ -10,13 +10,13 @@
|
|||||||
<div class="col-lg-12 col-md-12 col-sm-12">
|
<div class="col-lg-12 col-md-12 col-sm-12">
|
||||||
<div class="box box-default">
|
<div class="box box-default">
|
||||||
<div class="box-header with-border">
|
<div class="box-header with-border">
|
||||||
<h3 class="box-title">{{ trans('bank.prerequisites_for_import_title') }}</h3>
|
<h3 class="box-title">{{ trans('bank.bunq_prerequisites_title') }}</h3>
|
||||||
</div>
|
</div>
|
||||||
<div class="box-body">
|
<div class="box-body">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-lg-8">
|
<div class="col-lg-8">
|
||||||
<p>
|
<p>
|
||||||
{{ trans('bank.prerequisites_for_import_text') }}
|
{{ trans('bank.bunq_prerequisites_text') }}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -401,6 +401,8 @@ Route::group(
|
|||||||
// banks:
|
// banks:
|
||||||
Route::get('bank/{bank}/prerequisites', ['uses' => 'Import\BankController@prerequisites', 'as' => 'bank.prerequisites']);
|
Route::get('bank/{bank}/prerequisites', ['uses' => 'Import\BankController@prerequisites', 'as' => 'bank.prerequisites']);
|
||||||
Route::post('bank/{bank}/prerequisites', ['uses' => 'Import\BankController@postPrerequisites', 'as' => 'bank.prerequisites.post']);
|
Route::post('bank/{bank}/prerequisites', ['uses' => 'Import\BankController@postPrerequisites', 'as' => 'bank.prerequisites.post']);
|
||||||
|
|
||||||
|
Route::get('bank/{bank}/form', ['uses' => 'Import\BankController@form', 'as' => 'bank.form']);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user