mirror of
https://github.com/firefly-iii/firefly-iii.git
synced 2025-02-25 18:45:27 -06:00
Initial code to get providers from Spectre.
This commit is contained in:
parent
0774258516
commit
aa9500f5ad
77
app/Jobs/GetSpectreProviders.php
Normal file
77
app/Jobs/GetSpectreProviders.php
Normal file
@ -0,0 +1,77 @@
|
||||
<?php
|
||||
|
||||
namespace FireflyIII\Jobs;
|
||||
|
||||
use FireflyIII\Models\Configuration;
|
||||
use FireflyIII\Models\SpectreProvider;
|
||||
use FireflyIII\Services\Spectre\Request\ListProvidersRequest;
|
||||
use FireflyIII\User;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Foundation\Bus\Dispatchable;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
use Log;
|
||||
|
||||
class GetSpectreProviders implements ShouldQueue
|
||||
{
|
||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||
|
||||
protected $user;
|
||||
|
||||
/**
|
||||
* Create a new job instance.
|
||||
*/
|
||||
public function __construct(User $user)
|
||||
{
|
||||
$this->user = $user;
|
||||
Log::debug('Constructed job GetSpectreProviders');
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the job.
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
/** @var Configuration $configValue */
|
||||
$configValue = app('fireflyconfig')->get('spectre_provider_download', 0);
|
||||
$now = time();
|
||||
if ($now - intval($configValue->data) < 86400) {
|
||||
Log::debug(sprintf('Difference is %d, so will NOT execute job.', ($now - intval($configValue->data))));
|
||||
|
||||
return;
|
||||
}
|
||||
Log::debug(sprintf('Difference is %d, so will execute job.', ($now - intval($configValue->data))));
|
||||
|
||||
// get user
|
||||
|
||||
// fire away!
|
||||
$request = new ListProvidersRequest($this->user);
|
||||
$request->call();
|
||||
|
||||
// store all providers:
|
||||
$providers = $request->getProviders();
|
||||
foreach ($providers as $provider) {
|
||||
// find provider?
|
||||
$dbProvider = SpectreProvider::where('spectre_id', $provider['id'])->first();
|
||||
if (is_null($dbProvider)) {
|
||||
$dbProvider = new SpectreProvider;
|
||||
}
|
||||
// update fields:
|
||||
$dbProvider->spectre_id = $provider['id'];
|
||||
$dbProvider->code = $provider['code'];
|
||||
$dbProvider->mode = $provider['mode'];
|
||||
$dbProvider->status = $provider['status'];
|
||||
$dbProvider->interactive = $provider['interactive'] === 1;
|
||||
$dbProvider->automatic_fetch = $provider['automatic_fetch'] === 1;
|
||||
$dbProvider->country_code = $provider['country_code'];
|
||||
$dbProvider->data = $provider;
|
||||
$dbProvider->save();
|
||||
Log::debug(sprintf('Stored provider #%d under ID #%d', $provider['id'], $dbProvider->id));
|
||||
}
|
||||
|
||||
app('fireflyconfig')->set('spectre_provider_download', time());
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
32
app/Models/SpectreProvider.php
Normal file
32
app/Models/SpectreProvider.php
Normal file
@ -0,0 +1,32 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Models;
|
||||
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
/**
|
||||
* Class SpectreProvider
|
||||
*/
|
||||
class SpectreProvider extends Model
|
||||
{
|
||||
/**
|
||||
* The attributes that should be casted to native types.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $casts
|
||||
= [
|
||||
'spectre_id' => 'int',
|
||||
'created_at' => 'datetime',
|
||||
'updated_at' => 'datetime',
|
||||
'deleted_at' => 'datetime',
|
||||
'interactive' => 'boolean',
|
||||
'automatic_fetch' => 'boolean',
|
||||
'data' => 'array',
|
||||
];
|
||||
|
||||
protected $fillable = ['spectre_id', 'code', 'mode', 'name', 'status', 'interactive', 'automatic_fetch', 'country_code', 'data'];
|
||||
|
||||
}
|
@ -160,6 +160,9 @@ abstract class BunqRequest
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
protected function getDefaultHeaders(): array
|
||||
{
|
||||
$userAgent = sprintf('FireflyIII v%s', config('firefly.version'));
|
||||
|
80
app/Services/Spectre/Request/ListProvidersRequest.php
Normal file
80
app/Services/Spectre/Request/ListProvidersRequest.php
Normal file
@ -0,0 +1,80 @@
|
||||
<?php
|
||||
/**
|
||||
* ListUserRequest.php
|
||||
* Copyright (c) 2017 thegrumpydictator@gmail.com
|
||||
*
|
||||
* This file is part of Firefly III.
|
||||
*
|
||||
* Firefly III is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Firefly III is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Firefly III. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Services\Spectre\Request;
|
||||
|
||||
use Log;
|
||||
|
||||
/**
|
||||
* Class ListUserRequest.
|
||||
*/
|
||||
class ListProvidersRequest extends SpectreRequest
|
||||
{
|
||||
protected $providers = [];
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public function call(): void
|
||||
{
|
||||
$hasNextPage = true;
|
||||
$nextId = 0;
|
||||
while ($hasNextPage) {
|
||||
Log::debug(sprintf('Now calling for next_id %d', $nextId));
|
||||
$parameters = ['include_fake_providers' => 'true', 'include_provider_fields' => 'true', 'from_id' => $nextId];
|
||||
$uri = '/api/v3/providers?' . http_build_query($parameters);
|
||||
$response = $this->sendSignedSpectreGet($uri, []);
|
||||
|
||||
// count entries:
|
||||
Log::debug(sprintf('Found %d entries in data-array', count($response['data'])));
|
||||
|
||||
// extract next ID
|
||||
$hasNextPage = false;
|
||||
if (isset($response['meta']['next_id']) && intval($response['meta']['next_id']) > $nextId) {
|
||||
$hasNextPage = true;
|
||||
$nextId = $response['meta']['next_id'];
|
||||
Log::debug(sprintf('Next ID is now %d.', $nextId));
|
||||
} else {
|
||||
Log::debug('No next page.');
|
||||
}
|
||||
|
||||
// store providers:
|
||||
foreach ($response['data'] as $providerArray) {
|
||||
$providerId = $providerArray['id'];
|
||||
$this->providers[$providerId] = $providerArray;
|
||||
Log::debug(sprintf('Stored provider #%d', $providerId));
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getProviders(): array
|
||||
{
|
||||
return $this->providers;
|
||||
}
|
||||
|
||||
|
||||
}
|
378
app/Services/Spectre/Request/SpectreRequest.php
Normal file
378
app/Services/Spectre/Request/SpectreRequest.php
Normal file
@ -0,0 +1,378 @@
|
||||
<?php
|
||||
/**
|
||||
* BunqRequest.php
|
||||
* Copyright (c) 2017 thegrumpydictator@gmail.com
|
||||
*
|
||||
* This file is part of Firefly III.
|
||||
*
|
||||
* Firefly III is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Firefly III is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Firefly III. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Services\Spectre\Request;
|
||||
|
||||
use Exception;
|
||||
use FireflyIII\Exceptions\FireflyException;
|
||||
use FireflyIII\User;
|
||||
use Log;
|
||||
use Requests;
|
||||
use Requests_Exception;
|
||||
|
||||
//use FireflyIII\Services\Bunq\Object\ServerPublicKey;
|
||||
|
||||
/**
|
||||
* Class BunqRequest.
|
||||
*/
|
||||
abstract class SpectreRequest
|
||||
{
|
||||
/** @var string */
|
||||
protected $clientId = '';
|
||||
protected $expiresAt = 0;
|
||||
/** @var ServerPublicKey */
|
||||
protected $serverPublicKey;
|
||||
/** @var string */
|
||||
protected $serviceSecret = '';
|
||||
/** @var string */
|
||||
private $privateKey = '';
|
||||
/** @var string */
|
||||
private $server = '';
|
||||
/** @var User */
|
||||
private $user;
|
||||
|
||||
/**
|
||||
* SpectreRequest constructor.
|
||||
*/
|
||||
public function __construct(User $user)
|
||||
{
|
||||
$this->user = $user;
|
||||
$this->server = config('firefly.spectre.server');
|
||||
$this->expiresAt = time() + 180;
|
||||
$privateKey = app('preferences')->get('spectre_private_key', null);
|
||||
$this->privateKey = $privateKey->data;
|
||||
|
||||
// set client ID
|
||||
$clientId = app('preferences')->get('spectre_client_id', null);
|
||||
$this->clientId = $clientId->data;
|
||||
|
||||
// set service secret
|
||||
$serviceSecret = app('preferences')->get('spectre_service_secret', null);
|
||||
$this->serviceSecret = $serviceSecret->data;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
abstract public function call(): void;
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getClientId(): string
|
||||
{
|
||||
return $this->clientId;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $clientId
|
||||
*/
|
||||
public function setClientId(string $clientId): void
|
||||
{
|
||||
$this->clientId = $clientId;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getServer(): string
|
||||
{
|
||||
return $this->server;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ServerPublicKey
|
||||
*/
|
||||
public function getServerPublicKey(): ServerPublicKey
|
||||
{
|
||||
return $this->serverPublicKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ServerPublicKey $serverPublicKey
|
||||
*/
|
||||
public function setServerPublicKey(ServerPublicKey $serverPublicKey)
|
||||
{
|
||||
$this->serverPublicKey = $serverPublicKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getServiceSecret(): string
|
||||
{
|
||||
return $this->serviceSecret;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $serviceSecret
|
||||
*/
|
||||
public function setServiceSecret(string $serviceSecret): void
|
||||
{
|
||||
$this->serviceSecret = $serviceSecret;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $privateKey
|
||||
*/
|
||||
public function setPrivateKey(string $privateKey)
|
||||
{
|
||||
$this->privateKey = $privateKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $secret
|
||||
*/
|
||||
public function setSecret(string $secret)
|
||||
{
|
||||
$this->secret = $secret;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $method
|
||||
* @param string $uri
|
||||
* @param string $data
|
||||
*
|
||||
* @return string
|
||||
*
|
||||
* @throws FireflyException
|
||||
*/
|
||||
protected function generateSignature(string $method, string $uri, string $data): string
|
||||
{
|
||||
if (0 === strlen($this->privateKey)) {
|
||||
throw new FireflyException('No private key present.');
|
||||
}
|
||||
if ('get' === strtolower($method) || 'delete' === strtolower($method)) {
|
||||
$data = '';
|
||||
}
|
||||
// base64(sha1_signature(private_key, "Expires-at|request_method|original_url|post_body|md5_of_uploaded_file|")))
|
||||
// Prepare the signature
|
||||
$toSign = $this->expiresAt . '|' . strtoupper($method) . '|' . $uri . '|' . $data . ''; // no file so no content there.
|
||||
Log::debug(sprintf('String to sign: %s', $toSign));
|
||||
$signature = '';
|
||||
|
||||
// Sign the data
|
||||
openssl_sign($toSign, $signature, $this->privateKey, OPENSSL_ALGO_SHA1);
|
||||
$signature = base64_encode($signature);
|
||||
|
||||
return $signature;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
protected function getDefaultHeaders(): array
|
||||
{
|
||||
$userAgent = sprintf('FireflyIII v%s', config('firefly.version'));
|
||||
|
||||
return [
|
||||
'Client-Id' => $this->getClientId(),
|
||||
'Service-Secret' => $this->getServiceSecret(),
|
||||
'Accept' => 'application/json',
|
||||
'Content-type' => 'application/json',
|
||||
'Cache-Control' => 'no-cache',
|
||||
'User-Agent' => $userAgent,
|
||||
'Expires-at' => $this->expiresAt,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $uri
|
||||
* @param array $headers
|
||||
*
|
||||
* @return array
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
protected function sendSignedBunqDelete(string $uri, array $headers): array
|
||||
{
|
||||
if (0 === strlen($this->server)) {
|
||||
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();
|
||||
$statusCode = intval($response->status_code);
|
||||
$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;
|
||||
}
|
||||
|
||||
/**
|
||||
* @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()]]];
|
||||
}
|
||||
|
||||
$body = $response->body;
|
||||
$array = json_decode($body, true);
|
||||
$responseHeaders = $response->headers->getAll();
|
||||
$statusCode = intval($response->status_code);
|
||||
$array['ResponseHeaders'] = $responseHeaders;
|
||||
$array['ResponseStatusCode'] = $statusCode;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $uri
|
||||
* @param array $data
|
||||
* @param array $headers
|
||||
*
|
||||
* @return array
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
protected function sendSignedSpectreGet(string $uri, array $data): array
|
||||
{
|
||||
if (0 === strlen($this->server)) {
|
||||
throw new FireflyException('No Spectre server defined');
|
||||
}
|
||||
|
||||
$headers = $this->getDefaultHeaders();
|
||||
$body = json_encode($data);
|
||||
$fullUri = $this->server . $uri;
|
||||
$signature = $this->generateSignature('get', $fullUri, $body);
|
||||
$headers['Signature'] = $signature;
|
||||
|
||||
Log::debug('Final headers for spectre signed get request:', $headers);
|
||||
try {
|
||||
$response = Requests::get($fullUri, $headers);
|
||||
} catch (Requests_Exception $e) {
|
||||
throw new FireflyException(sprintf('Request Exception: %s', $e->getMessage()));
|
||||
}
|
||||
$statusCode = intval($response->status_code);
|
||||
|
||||
if ($statusCode !== 200) {
|
||||
throw new FireflyException(sprintf('Status code %d: %s', $statusCode, $response->body));
|
||||
}
|
||||
|
||||
$body = $response->body;
|
||||
$array = json_decode($body, true);
|
||||
$responseHeaders = $response->headers->getAll();
|
||||
$array['ResponseHeaders'] = $responseHeaders;
|
||||
$array['ResponseStatusCode'] = $statusCode;
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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()]]];
|
||||
}
|
||||
$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);
|
||||
}
|
||||
|
||||
return $array;
|
||||
}
|
||||
}
|
229
app/Support/Import/Prerequisites/SpectrePrerequisites.php
Normal file
229
app/Support/Import/Prerequisites/SpectrePrerequisites.php
Normal file
@ -0,0 +1,229 @@
|
||||
<?php
|
||||
/**
|
||||
* BunqPrerequisites.php
|
||||
* Copyright (c) 2017 thegrumpydictator@gmail.com
|
||||
*
|
||||
* This file is part of Firefly III.
|
||||
*
|
||||
* Firefly III is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Firefly III is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Firefly III. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Support\Import\Prerequisites;
|
||||
|
||||
use FireflyIII\Exceptions\FireflyException;
|
||||
use FireflyIII\Jobs\GetSpectreProviders;
|
||||
use FireflyIII\Models\Configuration;
|
||||
use FireflyIII\Models\Preference;
|
||||
use FireflyIII\User;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\MessageBag;
|
||||
use Log;
|
||||
use Preferences;
|
||||
use Requests;
|
||||
use Requests_Exception;
|
||||
|
||||
/**
|
||||
* This class contains all the routines necessary to connect to Bunq.
|
||||
*/
|
||||
class SpectrePrerequisites implements PrerequisitesInterface
|
||||
{
|
||||
/** @var User */
|
||||
private $user;
|
||||
|
||||
/**
|
||||
* Returns view name that allows user to fill in prerequisites. Currently asks for the API key.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getView(): string
|
||||
{
|
||||
return 'import.spectre.prerequisites';
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns any values required for the prerequisites-view.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getViewParameters(): array
|
||||
{
|
||||
$publicKey = $this->getPublicKey();
|
||||
$subTitle = strval(trans('bank.spectre_title'));
|
||||
$subTitleIcon = 'fa-archive';
|
||||
|
||||
return compact('publicKey', 'subTitle', 'subTitleIcon');
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns if this import method has any special prerequisites such as config
|
||||
* variables or other things. The only thing we verify is the presence of the API key. Everything else
|
||||
* tumbles into place: no installation token? Will be requested. No device server? Will be created. Etc.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function hasPrerequisites(): bool
|
||||
{
|
||||
$values = [
|
||||
Preferences::getForUser($this->user, 'spectre_client_id', false),
|
||||
Preferences::getForUser($this->user, 'spectre_app_secret', false),
|
||||
Preferences::getForUser($this->user, 'spectre_service_secret', false),
|
||||
];
|
||||
/** @var Preference $value */
|
||||
foreach ($values as $value) {
|
||||
if (false === $value->data || null === $value->data) {
|
||||
Log::info(sprintf('Config var "%s" is missing.', $value->name));
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
Log::debug('All prerequisites are here!');
|
||||
|
||||
// at this point, check if all providers are present. Providers are shared amongst
|
||||
// users in a multi-user environment.
|
||||
GetSpectreProviders::dispatch($this->user);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the user for this Prerequisites-routine. Class is expected to implement and save this.
|
||||
*
|
||||
* @param User $user
|
||||
*/
|
||||
public function setUser(User $user): void
|
||||
{
|
||||
$this->user = $user;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method responds to the user's submission of an API key. It tries to register this instance as a new Firefly III device.
|
||||
* If this fails, the error is returned in a message bag and the user is notified (this is fairly friendly).
|
||||
*
|
||||
* @param Request $request
|
||||
*
|
||||
* @return MessageBag
|
||||
*/
|
||||
public function storePrerequisites(Request $request): MessageBag
|
||||
{
|
||||
Log::debug('Storing Spectre API keys..');
|
||||
Preferences::setForUser($this->user, 'spectre_client_id', $request->get('client_id'));
|
||||
Preferences::setForUser($this->user, 'spectre_app_secret', $request->get('app_secret'));
|
||||
Preferences::setForUser($this->user, 'spectre_service_secret', $request->get('service_secret'));
|
||||
Log::debug('Done!');
|
||||
|
||||
return new MessageBag;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method creates a new public/private keypair for the user. This isn't really secure, since the key is generated on the fly with
|
||||
* no regards for HSM's, smart cards or other things. It would require some low level programming to get this right. But the private key
|
||||
* is stored encrypted in the database so it's something.
|
||||
*/
|
||||
private function createKeyPair(): void
|
||||
{
|
||||
Log::debug('Generate new Spectre 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, 'spectre_private_key', $privKey);
|
||||
Preferences::setForUser($this->user, 'spectre_public_key', $pubKey['key']);
|
||||
Log::debug('Created key pair');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the private key from the users preferences.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function getPrivateKey(): string
|
||||
{
|
||||
Log::debug('get private key');
|
||||
$preference = Preferences::getForUser($this->user, 'spectre_private_key', null);
|
||||
if (null === $preference) {
|
||||
Log::debug('private key is null');
|
||||
// create key pair
|
||||
$this->createKeyPair();
|
||||
}
|
||||
$preference = Preferences::getForUser($this->user, 'spectre_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, 'spectre_public_key', null);
|
||||
if (null === $preference) {
|
||||
Log::debug('public key is null');
|
||||
// create key pair
|
||||
$this->createKeyPair();
|
||||
}
|
||||
$preference = Preferences::getForUser($this->user, 'spectre_public_key', null);
|
||||
Log::debug('Return public key for user');
|
||||
|
||||
return $preference->data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Request users server remote IP. Let's assume this value will not change any time soon.
|
||||
*
|
||||
* @return string
|
||||
*
|
||||
* @throws FireflyException
|
||||
*/
|
||||
private function getRemoteIp(): string
|
||||
{
|
||||
$preference = Preferences::getForUser($this->user, 'external_ip', null);
|
||||
if (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 (200 !== $response->status_code) {
|
||||
throw new FireflyException(sprintf('Could not retrieve external IP: %d %s', $response->status_code, $response->body));
|
||||
}
|
||||
$serverIp = $response->body;
|
||||
Preferences::setForUser($this->user, 'external_ip', $serverIp);
|
||||
|
||||
return $serverIp;
|
||||
}
|
||||
|
||||
return $preference->data;
|
||||
}
|
||||
|
||||
}
|
@ -28,39 +28,49 @@ declare(strict_types=1);
|
||||
*/
|
||||
|
||||
return [
|
||||
'configuration' => [
|
||||
'configuration' => [
|
||||
'single_user_mode' => true,
|
||||
'is_demo_site' => false,
|
||||
],
|
||||
'encryption' => (is_null(env('USE_ENCRYPTION')) || env('USE_ENCRYPTION') === true),
|
||||
'version' => '4.6.11.1',
|
||||
'maxUploadSize' => 15242880,
|
||||
'allowedMimes' => ['image/png', 'image/jpeg', 'application/pdf','text/plain'],
|
||||
'list_length' => 10,
|
||||
'export_formats' => [
|
||||
'encryption' => (is_null(env('USE_ENCRYPTION')) || env('USE_ENCRYPTION') === true),
|
||||
'version' => '4.6.11.1',
|
||||
'maxUploadSize' => 15242880,
|
||||
'allowedMimes' => ['image/png', 'image/jpeg', 'application/pdf', 'text/plain'],
|
||||
'list_length' => 10,
|
||||
'export_formats' => [
|
||||
'csv' => 'FireflyIII\Export\Exporter\CsvExporter',
|
||||
],
|
||||
'import_formats' => [
|
||||
'import_formats' => [
|
||||
'csv' => 'FireflyIII\Import\Configurator\CsvConfigurator',
|
||||
],
|
||||
'import_configurators' => [
|
||||
'import_configurators' => [
|
||||
'csv' => 'FireflyIII\Import\Configurator\CsvConfigurator',
|
||||
],
|
||||
'import_processors' => [
|
||||
'import_processors' => [
|
||||
'csv' => 'FireflyIII\Import\FileProcessor\CsvProcessor',
|
||||
],
|
||||
'import_pre' => [
|
||||
'bunq' => 'FireflyIII\Support\Import\Prerequisites\BunqPrerequisites',
|
||||
'import_pre' => [
|
||||
'bunq' => 'FireflyIII\Support\Import\Prerequisites\BunqPrerequisites',
|
||||
'spectre' => 'FireflyIII\Support\Import\Prerequisites\SpectrePrerequisites',
|
||||
'plaid' => 'FireflyIII\Support\Import\Prerequisites\PlairPrerequisites',
|
||||
],
|
||||
'import_info' => [
|
||||
'bunq' => 'FireflyIII\Support\Import\Information\BunqInformation',
|
||||
'import_info' => [
|
||||
'bunq' => 'FireflyIII\Support\Import\Information\BunqInformation',
|
||||
'spectre' => 'FireflyIII\Support\Import\Information\SpectreInformation',
|
||||
'plaid' => 'FireflyIII\Support\Import\Information\PlaidInformation',
|
||||
],
|
||||
'import_transactions' => [
|
||||
'bunq' => 'FireflyIII\Support\Import\Transactions\BunqTransactions',
|
||||
'import_transactions' => [
|
||||
'bunq' => 'FireflyIII\Support\Import\Transactions\BunqTransactions',
|
||||
'spectre' => 'FireflyIII\Support\Import\Transactions\SpectreTransactions',
|
||||
'plaid' => 'FireflyIII\Support\Import\Transactions\PlaidTransactions',
|
||||
],
|
||||
'bunq' => [
|
||||
'bunq' => [
|
||||
'server' => 'https://sandbox.public.api.bunq.com',
|
||||
],
|
||||
'spectre' => [
|
||||
'server' => 'https://www.saltedge.com',
|
||||
],
|
||||
|
||||
'default_export_format' => 'csv',
|
||||
'default_import_format' => 'csv',
|
||||
'bill_periods' => ['weekly', 'monthly', 'quarterly', 'half-year', 'yearly'],
|
||||
|
@ -0,0 +1,47 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class ChangesForSpectre extends Migration
|
||||
{
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
// create provider table:
|
||||
if (!Schema::hasTable('spectre_providers')) {
|
||||
Schema::create(
|
||||
'spectre_providers',
|
||||
function (Blueprint $table) {
|
||||
$table->increments('id');
|
||||
$table->timestamps();
|
||||
$table->softDeletes();
|
||||
//'spectre_id', 'code', 'mode', 'name', 'status', 'interactive', 'automatic_fetch', 'country_code', 'data'
|
||||
$table->integer('spectre_id', false, true);
|
||||
$table->string('code', 100);
|
||||
$table->string('mode', 20);
|
||||
$table->string('status', 20);
|
||||
$table->boolean('interactive')->default(0);
|
||||
$table->boolean('automatic_fetch')->default(0);
|
||||
$table->string('country_code', 3);
|
||||
$table->text('data');
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
Binary file not shown.
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 3.2 KiB |
BIN
public/images/logos/csv.png
Normal file
BIN
public/images/logos/csv.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 3.4 KiB |
BIN
public/images/logos/plaid.png
Normal file
BIN
public/images/logos/plaid.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 8.1 KiB |
BIN
public/images/logos/spectre.png
Normal file
BIN
public/images/logos/spectre.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 3.4 KiB |
@ -3,6 +3,12 @@ declare(strict_types=1);
|
||||
|
||||
|
||||
return [
|
||||
'bunq_prerequisites_title' => 'Prerequisites for an import from bunq',
|
||||
'bunq_prerequisites_text' => 'In order to import from bunq, you need to obtain an API key. You can do this through the app.',
|
||||
'bunq_prerequisites_title' => 'Prerequisites for an import from bunq',
|
||||
'bunq_prerequisites_text' => 'In order to import from bunq, you need to obtain an API key. You can do this through the app.',
|
||||
|
||||
// Spectre:
|
||||
'spectre_title' => 'Import using Spectre',
|
||||
'spectre_prerequisites_title' => 'Prerequisites for an import using Spectre',
|
||||
'spectre_prerequisites_text' => 'In order to import data using the Spectre API, you need to prove some secrets. They can be found on the <a href="https://www.saltedge.com/clients/profile/secrets">secrets page</a>.',
|
||||
'spectre_enter_pub_key' => 'The import will only work when you enter this public key on your <a href="https://www.saltedge.com/clients/security/edit">security page</a>.',
|
||||
];
|
||||
|
@ -186,6 +186,10 @@ return [
|
||||
'csv_delimiter' => 'CSV field delimiter',
|
||||
'csv_import_account' => 'Default import account',
|
||||
'csv_config' => 'CSV import configuration',
|
||||
'client_id' => 'Client ID',
|
||||
'service_secret' => 'Service secret',
|
||||
'app_secret' => 'App secret',
|
||||
'public_key' => 'Public key',
|
||||
|
||||
|
||||
'due_date' => 'Due date',
|
||||
|
@ -20,21 +20,34 @@
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-lg-8">
|
||||
<p>
|
||||
<div class="col-lg-1 text-center">
|
||||
{# file import #}
|
||||
<a href="{{ route('import.file.index') }}" class="btn btn-app">
|
||||
<i class="fa fa-file-text-o"></i>
|
||||
<a href="{{ route('import.file.index') }}">
|
||||
<img src="images/logos/csv.png" alt="bunq"/><br />
|
||||
{{ 'import_general_index_csv_file'|_ }}
|
||||
</a>
|
||||
</div>
|
||||
<div class="col-lg-1 text-center">
|
||||
{# bunq import #}
|
||||
{#
|
||||
<a href="{{ route('import.bank.prerequisites', ['bunq']) }}" class="btn btn-app">
|
||||
<a href="{{ route('import.bank.prerequisites', ['bunq']) }}">
|
||||
<img src="images/logos/bunq.png" alt="bunq"/><br />
|
||||
Import from bunq
|
||||
</a>
|
||||
#}
|
||||
</p>
|
||||
</div>
|
||||
<div class="col-lg-1 text-center">
|
||||
{# import from Spectre #}
|
||||
<a href="{{ route('import.bank.prerequisites', ['spectre']) }}">
|
||||
<img src="images/logos/spectre.png" alt="Spectre"/><br />
|
||||
Import using Spectre
|
||||
</a>
|
||||
</div>
|
||||
<div class="col-lg-1 text-center">
|
||||
{# import from Plaid #}
|
||||
<a href="{{ route('import.bank.prerequisites', ['plaid']) }}">
|
||||
<img src="images/logos/plaid.png" alt="Plaid"/><br />
|
||||
Import using Plaid
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
58
resources/views/import/spectre/prerequisites.twig
Normal file
58
resources/views/import/spectre/prerequisites.twig
Normal file
@ -0,0 +1,58 @@
|
||||
{% extends "./layout/default" %}
|
||||
|
||||
{% block breadcrumbs %}
|
||||
{{ Breadcrumbs.renderIfExists }}
|
||||
{% endblock %}
|
||||
{% block content %}
|
||||
<div class="row">
|
||||
<form class="form-horizontal" action="{{ route('import.bank.prerequisites.post',['spectre']) }}" method="post">
|
||||
<input type="hidden" name="_token" value="{{ csrf_token() }}"/>
|
||||
<div class="col-lg-12 col-md-12 col-sm-12">
|
||||
<div class="box box-default">
|
||||
<div class="box-header with-border">
|
||||
<h3 class="box-title">{{ trans('bank.spectre_prerequisites_title') }}</h3>
|
||||
</div>
|
||||
<div class="box-body">
|
||||
<div class="row">
|
||||
<div class="col-lg-8">
|
||||
<p>
|
||||
{{ trans('bank.spectre_prerequisites_text')|raw }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-lg-8">
|
||||
{{ ExpandedForm.text('client_id') }}
|
||||
{{ ExpandedForm.text('service_secret') }}
|
||||
{{ ExpandedForm.text('app_secret') }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-lg-8">
|
||||
<p>{{ trans('bank.spectre_enter_pub_key')|raw }}</p>
|
||||
<div class="form-group" id="pub_key_holder">
|
||||
<label for="ffInput_pub_key_holder" class="col-sm-4 control-label">{{ trans('form.public_key') }}</label>
|
||||
|
||||
<div class="col-sm-8">
|
||||
<textarea class="form-control"
|
||||
rows="10"
|
||||
id="ffInput_pub_key_holder" name="pub_key_holder" contenteditable="false">{{ publicKey }}</textarea>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="box-footer">
|
||||
<button type="submit" class="btn pull-right btn-success">
|
||||
{{ ('submit')|_ }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
{% endblock %}
|
||||
{% block scripts %}
|
||||
{% endblock %}
|
||||
{% block styles %}
|
||||
{% endblock %}
|
Loading…
Reference in New Issue
Block a user