Skip to content

Commit 9c696b9

Browse files
committed
Merge branch 'release/0.4.0'
2 parents b2d605a + a4ab0c3 commit 9c696b9

85 files changed

Lines changed: 1645 additions & 1117 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.env.example

Lines changed: 30 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ FIREFLY_III_URL=
1818
# 1) Make sure you ADD http:// or https://
1919
# 2) Make sure you REMOVE any trailing slash from the end of the URL.
2020
#
21+
# IF YOU SET THIS VALUE, ALSO SET THE FIREFLY_III_URL
22+
#
2123
VANITY_URL=
2224

2325
#
@@ -40,27 +42,50 @@ FIREFLY_III_ACCESS_TOKEN=
4042
FIREFLY_III_CLIENT_ID=
4143

4244
#
43-
# Nordigen
45+
# Nordigen information.
4446
#
4547
NORDIGEN_ID=
4648
NORDIGEN_KEY=
4749
NORDIGEN_SANDBOX=false
4850

4951
#
50-
# Spectre
52+
# Spectre information
5153
#
5254
SPECTRE_APP_ID=
5355
SPECTRE_SECRET=
5456

5557
#
56-
# Use cache
58+
# Use cache. No need to do this.
5759
#
5860
USE_CACHE=false
5961

6062
#
61-
# Post import directory white list
63+
# Auto import settings. Due to security constraints, you MUST enable each feature individually.
64+
# You must also set a secret. The secret is used for the web routes.
65+
#
66+
# The auto-import secret must be a string of at least 16 characters.
67+
# Visit this page for inspiration: https://www.random.org/passwords/?num=1&len=16&format=html&rnd=new
68+
#
69+
# Submit it using ?secret=X
70+
#
71+
AUTO_IMPORT_SECRET=
72+
73+
#
74+
# Is the /autoimport even endpoint enabled?
75+
# By default it's disabled, and the secret alone will not enable it.
76+
#
77+
CAN_POST_AUTOIMPORT=false
78+
79+
#
80+
# Is the /autoupload endpoint enabled?
81+
# By default it's disabled, and the secret alone will not enable it.
82+
#
83+
CAN_POST_FILES=false
84+
85+
#
86+
# Import directory white list. You need to set this before the auto importer will accept a directory to import from.
6287
#
63-
POST_DIR_WHITELIST=
88+
IMPORT_DIR_WHITELIST=
6489

6590
#
6691
# When you're running Firefly III under a (self-signed) certificate,

.github/mergify.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,4 +10,4 @@ pull_request_rules:
1010
- base=main
1111
actions:
1212
close:
13-
message: Do not open PR's on the main branch.
13+
message: Please do not open PR's on the `main` branch, but on the `develop` branch only. Thank you!

app/Console/AutoImports.php

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,9 +161,19 @@ protected function importFiles(string $directory, array $files): void
161161
*/
162162
private function importFile(string $directory, string $file): void
163163
{
164+
app('log')->debug(sprintf('ImportFile: directory "%s"', $directory));
165+
app('log')->debug(sprintf('ImportFile: file "%s"', $file));
164166
$csvFile = sprintf('%s/%s', $directory, $file);
165167
$jsonFile = sprintf('%s/%s.json', $directory, substr($file, 0, -5));
166168

169+
// TODO not yet sure why the distinction is necessary.
170+
if ('csv' === $this->getExtension($file)) {
171+
$jsonFile = sprintf('%s/%s.json', $directory, substr($file, 0, -4));
172+
}
173+
174+
app('log')->debug(sprintf('ImportFile: CSV "%s"', $csvFile));
175+
app('log')->debug(sprintf('ImportFile: JSON "%s"', $jsonFile));
176+
167177
// do JSON check
168178
$jsonResult = $this->verifyJSON($jsonFile);
169179
if (false === $jsonResult) {
@@ -390,4 +400,43 @@ private function reportImport(): void
390400
}
391401
}
392402
}
403+
404+
405+
/**
406+
* @param string $csvFile
407+
* @param string $jsonFile
408+
* @throws ImporterErrorException
409+
*/
410+
private function importUpload(string $csvFile, string $jsonFile): void
411+
{
412+
// do JSON check
413+
$jsonResult = $this->verifyJSON($jsonFile);
414+
if (false === $jsonResult) {
415+
$message = sprintf('The importer can\'t import %s: could not decode the JSON in config file %s.', $csvFile, $jsonFile);
416+
$this->error($message);
417+
418+
return;
419+
}
420+
$configuration = Configuration::fromArray(json_decode(file_get_contents($jsonFile), true));
421+
$configuration->updateDateRange();
422+
423+
424+
$this->line(sprintf('Going to convert from file %s using configuration %s and flow "%s".', $csvFile, $jsonFile, $configuration->getFlow()));
425+
426+
// this is it!
427+
$this->startConversion($configuration, $csvFile);
428+
$this->reportConversion();
429+
430+
$this->line(sprintf('Done converting from file %s using configuration %s.', $csvFile, $jsonFile));
431+
$this->startImport($configuration);
432+
$this->reportImport();
433+
434+
$this->line('Done!');
435+
event(new ImportedTransactions(
436+
array_merge($this->conversionMessages, $this->importMessages),
437+
array_merge($this->conversionWarnings, $this->importWarnings),
438+
array_merge($this->conversionErrors, $this->importErrors)
439+
)
440+
);
441+
}
393442
}

app/Console/Commands/AutoImport.php

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,10 @@ public function handle(): int
6666

6767
$argument = (string) ($this->argument('directory') ?? './');
6868
$directory = realpath($argument);
69+
if (!$this->isAllowedPath($directory)) {
70+
$this->error(sprintf('Path "%s" is not in the list of allowed paths (IMPORT_DIR_WHITELIST).', $directory));
71+
return 1;
72+
}
6973
$this->line(sprintf('Going to automatically import everything found in %s (%s)', $directory, $argument));
7074

7175
$files = $this->getFiles($directory);
@@ -86,5 +90,4 @@ public function handle(): int
8690

8791
return 0;
8892
}
89-
9093
}

app/Console/Commands/Import.php

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,22 @@ public function handle(): int
7575
$file = (string) $this->argument('file');
7676
$config = (string) $this->argument('config');
7777

78+
// validate config path:
79+
if ('' !== $config) {
80+
$directory = dirname($config);
81+
if (!$this->isAllowedPath($directory)) {
82+
$this->error(sprintf('Path "%s" is not in the list of allowed paths (IMPORT_DIR_WHITELIST).', $directory));
83+
return 1;
84+
}
85+
}
86+
if ('' !== $file) {
87+
$directory = dirname($file);
88+
if (!$this->isAllowedPath($directory)) {
89+
$this->error(sprintf('Path "%s" is not in the list of allowed paths (IMPORT_DIR_WHITELIST).', $directory));
90+
return 1;
91+
}
92+
}
93+
7894
if (!file_exists($config) || (file_exists($config) && !is_file($config))) {
7995
$message = sprintf('The importer can\'t import: configuration file "%s" does not exist or could not be read.', $config);
8096
$this->error($message);

app/Console/HaveAccess.php

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,4 +62,27 @@ private function haveAccess(): bool
6262
* @return void
6363
*/
6464
abstract public function error($string, $verbosity = null);
65+
66+
/**
67+
* @param string $path
68+
* @return bool
69+
*/
70+
private function isAllowedPath(string $path): bool
71+
{
72+
$paths = config('importer.import_dir_whitelist');
73+
if (null === $paths) {
74+
$this->warn('No valid paths in IMPORT_DIR_WHITELIST, cannot continue.');
75+
return false;
76+
}
77+
if (is_array($paths) && 0 === count($paths)) {
78+
$this->warn('No valid paths in IMPORT_DIR_WHITELIST, cannot continue.');
79+
return false;
80+
}
81+
if (is_array($paths) && 1 === count($paths) && '' === $paths[0]) {
82+
$this->warn('No valid paths in IMPORT_DIR_WHITELIST, cannot continue.');
83+
return false;
84+
}
85+
86+
return in_array($path, $paths, true);
87+
}
6588
}

app/Events/ImportedTransactions.php

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<?php
2-
declare(strict_types=1);
2+
33
/*
44
* ImportedTransactions.php
55
* Copyright (c) 2021 james@firefly-iii.org
@@ -21,6 +21,8 @@
2121
* along with this program. If not, see <https://www.gnu.org/licenses/>.
2222
*/
2323

24+
declare(strict_types=1);
25+
2426
namespace App\Events;
2527

2628
use Illuminate\Queue\SerializesModels;

app/Handlers/Events/ImportedTransactionsEventHandler.php

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<?php
2-
declare(strict_types=1);
2+
33
/*
44
* ImportedTransactionsEventHandler.php
55
* Copyright (c) 2021 james@firefly-iii.org
@@ -21,6 +21,8 @@
2121
* along with this program. If not, see <https://www.gnu.org/licenses/>.
2222
*/
2323

24+
declare(strict_types=1);
25+
2426
namespace App\Handlers\Events;
2527

2628
use App\Events\ImportedTransactions;
@@ -36,21 +38,21 @@ public function sendReportOverMail(ImportedTransactions $event): void
3638
{
3739
app('log')->debug('Now in sendReportOverMail');
3840

39-
$mailer = config('mail.default');
41+
$mailer = config('mail.default');
4042
$receiver = config('mail.destination');
41-
if('' === $mailer) {
43+
if ('' === $mailer) {
4244
app('log')->info('No mailer configured, will not mail.');
4345
return;
4446
}
45-
if('' === $receiver) {
47+
if ('' === $receiver) {
4648
app('log')->info('No mail receiver configured, will not mail.');
4749
return;
4850
}
4951

50-
$log =[
52+
$log = [
5153
'messages' => $event->messages,
5254
'warnings' => $event->warnings,
53-
'errors' => $event->errors,
55+
'errors' => $event->errors,
5456
];
5557
app('log')->info('Will send report message.');
5658
app('log')->debug('If no error below this line, mail was sent!');

app/Http/Controllers/AutoImportController.php

Lines changed: 33 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
use App\Console\VerifyJSON;
3232
use App\Exceptions\ImporterErrorException;
3333
use Illuminate\Http\Request;
34+
use Illuminate\Http\Response;
3435
use Log;
3536

3637
/**
@@ -45,40 +46,45 @@ class AutoImportController extends Controller
4546
/**
4647
*
4748
*/
48-
public function index(Request $request)
49+
public function index(Request $request): Response
4950
{
50-
die('todo' . __METHOD__);
51+
if (false === config('importer.can_post_autoimport')) {
52+
throw new ImporterErrorException('Disabled, not allowed to import.');
53+
}
54+
55+
$secret = (string) ($request->get('secret') ?? '');
56+
$systemSecret = (string) config('importer.auto_import_secret');
57+
if ('' === $secret || '' === $systemSecret || $secret !== config('importer.auto_import_secret') || strlen($systemSecret) < 16) {
58+
throw new ImporterErrorException('Bad secret, not allowed to import.');
59+
}
60+
61+
$argument = (string) ($request->get('directory') ?? './');
62+
$directory = realpath($argument);
63+
64+
if (!$this->isAllowedPath($directory)) {
65+
throw new ImporterErrorException('Not allowed to import from this path.');
66+
}
67+
5168
$access = $this->haveAccess();
5269
if (false === $access) {
5370
throw new ImporterErrorException('Could not connect to your local Firefly III instance.');
5471
}
5572

56-
$argument = (string) ($request->get('directory') ?? './');
57-
$this->directory = realpath($argument);
58-
$this->line(sprintf('Going to automatically import everything found in %s (%s)', $this->directory, $argument));
73+
// take code from auto importer.
74+
app('log')->info(sprintf('Going to automatically import everything found in %s (%s)', $directory, $argument));
5975

60-
$files = $this->getFiles();
76+
$files = $this->getFiles($directory);
6177
if (0 === count($files)) {
62-
$this->line(sprintf('There are no files in directory %s', $this->directory));
63-
$this->line('To learn more about this process, read the docs:');
64-
$this->line('https://docs.firefly-iii.org/data-importer/');
65-
66-
return ' ';
78+
return response('');
6779
}
68-
$this->line(sprintf('Found %d CSV + JSON file sets in %s', count($files), $this->directory));
80+
app('log')->info(sprintf('Found %d (CSV +) JSON file sets in %s', count($files), $directory));
6981
try {
70-
$this->importFiles($files);
82+
$this->importFiles($directory, $files);
7183
} catch (ImporterErrorException $e) {
72-
Log::error($e->getMessage());
73-
$this->line(sprintf('Import exception (see the logs): %s', $e->getMessage()));
84+
app('log')->error($e->getMessage());
85+
throw new ImporterErrorException(sprintf('Import exception (see the logs): %s', $e->getMessage()));
7486
}
75-
76-
return ' ';
77-
}
78-
79-
public function line(string $string)
80-
{
81-
echo sprintf("%s: %s\n", date('Y-m-d H:i:s'), $string);
87+
return response('');
8288
}
8389

8490
/**
@@ -89,6 +95,11 @@ public function error($string, $verbosity = null)
8995
$this->line($string);
9096
}
9197

98+
public function line(string $string)
99+
{
100+
echo sprintf("%s: %s\n", date('Y-m-d H:i:s'), $string);
101+
}
102+
92103
/**
93104
* @param $string
94105
* @param null $verbosity

app/Http/Controllers/AutoUploadController.php

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,16 @@ class AutoUploadController extends Controller
4545
*/
4646
public function index(AutoUploadRequest $request)
4747
{
48-
die('todo' . __METHOD__);
48+
if (false === config('importer.can_post_files')) {
49+
throw new ImporterErrorException('Disabled, not allowed to import.');
50+
}
51+
52+
$secret = (string) ($request->get('secret') ?? '');
53+
$systemSecret = (string) config('importer.auto_import_secret');
54+
if ('' === $secret || '' === $systemSecret || $secret !== config('importer.auto_import_secret') || strlen($systemSecret) < 16) {
55+
throw new ImporterErrorException('Bad secret, not allowed to import.');
56+
}
57+
4958
$access = $this->haveAccess();
5059
if (false === $access) {
5160
throw new ImporterErrorException('Could not connect to your local Firefly III instance.');

0 commit comments

Comments
 (0)