mirror of
https://github.com/invoiceninja/invoiceninja.git
synced 2024-11-08 20:22:42 +01:00
Add protected download route with signed route signatures
This commit is contained in:
parent
71523ecde3
commit
fb37fc40a3
@ -11,10 +11,12 @@
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Http\Requests\Export\StoreExportRequest;
|
||||
use App\Jobs\Company\CompanyExport;
|
||||
use App\Utils\Traits\MakesHash;
|
||||
use Illuminate\Support\Str;
|
||||
use Illuminate\Http\Response;
|
||||
use App\Utils\Traits\MakesHash;
|
||||
use App\Jobs\Company\CompanyExport;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use App\Http\Requests\Export\StoreExportRequest;
|
||||
|
||||
class ExportController extends BaseController
|
||||
{
|
||||
@ -54,8 +56,12 @@ class ExportController extends BaseController
|
||||
*/
|
||||
public function index(StoreExportRequest $request)
|
||||
{
|
||||
CompanyExport::dispatch(auth()->user()->getCompany(), auth()->user());
|
||||
$hash = Str::uuid();
|
||||
$url = \Illuminate\Support\Facades\URL::temporarySignedRoute('protected_download', now()->addHour(), ['hash' => $hash]);
|
||||
Cache::put($hash, $url, now()->addHour());
|
||||
|
||||
return response()->json(['message' => 'Processing'], 200);
|
||||
CompanyExport::dispatch(auth()->user()->getCompany(), auth()->user(), $hash);
|
||||
|
||||
return response()->json(['message' => 'Processing', 'url' => $url], 200);
|
||||
}
|
||||
}
|
||||
|
41
app/Http/Controllers/ProtectedDownloadController.php
Normal file
41
app/Http/Controllers/ProtectedDownloadController.php
Normal file
@ -0,0 +1,41 @@
|
||||
<?php
|
||||
/**
|
||||
* Invoice Ninja (https://invoiceninja.com).
|
||||
*
|
||||
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||
*
|
||||
* @copyright Copyright (c) 2023. Invoice Ninja LLC (https://invoiceninja.com)
|
||||
*
|
||||
* @license https://www.elastic.co/licensing/elastic-license
|
||||
*/
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use App\Jobs\Util\UnlinkFile;
|
||||
use App\Exceptions\SystemError;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
|
||||
class ProtectedDownloadController extends BaseController
|
||||
{
|
||||
|
||||
public function index(Request $request)
|
||||
{
|
||||
|
||||
$hashed_path = Cache::pull($request->hash);
|
||||
|
||||
if (!$hashed_path) {
|
||||
throw new SystemError('File no longer available', 404);
|
||||
abort(404, 'File no longer available');
|
||||
}
|
||||
|
||||
UnlinkFile::dispatch(config('filesystems.default'), $hashed_path)->delay(now()->addSeconds(10));
|
||||
|
||||
return response()->streamDownload(function () use ($hashed_path) {
|
||||
echo Storage::get($hashed_path);
|
||||
}, basename($hashed_path), []);
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -41,6 +41,7 @@ use App\Http\Middleware\VerifyCsrfToken;
|
||||
use App\Http\Middleware\ContactTokenAuth;
|
||||
use Illuminate\Auth\Middleware\Authorize;
|
||||
use App\Http\Middleware\SetDbByCompanyKey;
|
||||
use App\Http\Middleware\ValidateSignature;
|
||||
use App\Http\Middleware\PasswordProtection;
|
||||
use App\Http\Middleware\ClientPortalEnabled;
|
||||
use App\Http\Middleware\CheckClientExistence;
|
||||
@ -49,16 +50,13 @@ use Illuminate\Http\Middleware\SetCacheHeaders;
|
||||
use Illuminate\Session\Middleware\StartSession;
|
||||
use App\Http\Middleware\CheckForMaintenanceMode;
|
||||
use App\Http\Middleware\RedirectIfAuthenticated;
|
||||
use Illuminate\Routing\Middleware\ThrottleRequests;
|
||||
use Illuminate\Foundation\Http\Kernel as HttpKernel;
|
||||
use Illuminate\Routing\Middleware\ValidateSignature;
|
||||
use Illuminate\Auth\Middleware\EnsureEmailIsVerified;
|
||||
use Illuminate\Routing\Middleware\SubstituteBindings;
|
||||
use Illuminate\View\Middleware\ShareErrorsFromSession;
|
||||
use Illuminate\Auth\Middleware\AuthenticateWithBasicAuth;
|
||||
use Illuminate\Foundation\Http\Middleware\ValidatePostSize;
|
||||
use Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse;
|
||||
use Illuminate\Routing\Middleware\ThrottleRequestsWithRedis;
|
||||
use Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull;
|
||||
|
||||
class Kernel extends HttpKernel
|
||||
|
49
app/Http/Middleware/ValidateSignature.php
Normal file
49
app/Http/Middleware/ValidateSignature.php
Normal file
@ -0,0 +1,49 @@
|
||||
<?php
|
||||
/**
|
||||
* Invoice Ninja (https://invoiceninja.com).
|
||||
*
|
||||
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||
*
|
||||
* @copyright Copyright (c) 2023. Invoice Ninja LLC (https://invoiceninja.com)
|
||||
*
|
||||
* @license https://www.elastic.co/licensing/elastic-license
|
||||
*/
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
|
||||
use Closure;
|
||||
use Illuminate\Routing\Exceptions\InvalidSignatureException;
|
||||
|
||||
class ValidateSignature
|
||||
{
|
||||
/**
|
||||
* The names of the parameters that should be ignored.
|
||||
*
|
||||
* @var array<int, string>
|
||||
*/
|
||||
protected $ignore = [
|
||||
'q'
|
||||
];
|
||||
|
||||
/**
|
||||
* Handle an incoming request.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @param \Closure $next
|
||||
* @param string|null $relative
|
||||
* @return \Illuminate\Http\Response
|
||||
*
|
||||
* @throws \Illuminate\Routing\Exceptions\InvalidSignatureException
|
||||
*/
|
||||
public function handle($request, Closure $next, $relative = null)
|
||||
{
|
||||
$ignore = property_exists($this, 'except') ? $this->except : $this->ignore;
|
||||
|
||||
if ($request->hasValidSignatureWhileIgnoring($ignore, $relative !== 'relative')) {
|
||||
return $next($request);
|
||||
}
|
||||
|
||||
throw new InvalidSignatureException;
|
||||
}
|
||||
}
|
@ -11,39 +11,39 @@
|
||||
|
||||
namespace App\Jobs\Company;
|
||||
|
||||
use App\Jobs\Mail\NinjaMailerJob;
|
||||
use App\Jobs\Mail\NinjaMailerObject;
|
||||
use App\Libraries\MultiDB;
|
||||
use App\Mail\DownloadBackup;
|
||||
use App\Models\Company;
|
||||
use App\Models\CreditInvitation;
|
||||
use App\Models\InvoiceInvitation;
|
||||
use App\Models\PurchaseOrderInvitation;
|
||||
use App\Models\QuoteInvitation;
|
||||
use App\Models\RecurringInvoiceInvitation;
|
||||
use App\Models\User;
|
||||
use App\Models\VendorContact;
|
||||
use App\Utils\Ninja;
|
||||
use App\Utils\Traits\MakesHash;
|
||||
use App\Models\Company;
|
||||
use App\Libraries\MultiDB;
|
||||
use Illuminate\Support\Str;
|
||||
use App\Mail\DownloadBackup;
|
||||
use App\Jobs\Util\UnlinkFile;
|
||||
use App\Models\VendorContact;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use App\Models\QuoteInvitation;
|
||||
use App\Utils\Traits\MakesHash;
|
||||
use App\Models\CreditInvitation;
|
||||
use App\Jobs\Mail\NinjaMailerJob;
|
||||
use App\Models\InvoiceInvitation;
|
||||
use Illuminate\Support\Facades\App;
|
||||
use App\Jobs\Mail\NinjaMailerObject;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
use App\Models\PurchaseOrderInvitation;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use App\Models\RecurringInvoiceInvitation;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Foundation\Bus\Dispatchable;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
use Illuminate\Support\Facades\App;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
|
||||
class CompanyExport implements ShouldQueue
|
||||
{
|
||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels, MakesHash;
|
||||
|
||||
public $company;
|
||||
|
||||
private $export_format;
|
||||
private $export_format = 'json';
|
||||
|
||||
private $export_data = [];
|
||||
|
||||
public $user;
|
||||
|
||||
/**
|
||||
* Create a new job instance.
|
||||
@ -52,11 +52,8 @@ class CompanyExport implements ShouldQueue
|
||||
* @param User $user
|
||||
* @param string $custom_token_name
|
||||
*/
|
||||
public function __construct(Company $company, User $user, $export_format = 'json')
|
||||
public function __construct(public Company $company, private User $user, public string $hash)
|
||||
{
|
||||
$this->company = $company;
|
||||
$this->user = $user;
|
||||
$this->export_format = $export_format;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -467,7 +464,12 @@ class CompanyExport implements ShouldQueue
|
||||
}
|
||||
|
||||
$storage_file_path = Storage::disk(config('filesystems.default'))->url('backups/'.$file_name);
|
||||
$storage_path = Storage::disk(config('filesystems.default'))->path('backups/'.$file_name);
|
||||
|
||||
|
||||
$url = Cache::get($this->hash);
|
||||
Cache::put($this->hash, $storage_path, now()->addHour());
|
||||
|
||||
App::forgetInstance('translator');
|
||||
$t = app('translator');
|
||||
$t->replace(Ninja::transformTranslations($this->company->settings));
|
||||
@ -475,12 +477,14 @@ class CompanyExport implements ShouldQueue
|
||||
$company_reference = Company::find($this->company->id);
|
||||
|
||||
$nmo = new NinjaMailerObject;
|
||||
$nmo->mailable = new DownloadBackup($storage_file_path, $company_reference);
|
||||
$nmo->mailable = new DownloadBackup($url, $company_reference);
|
||||
$nmo->to_user = $this->user;
|
||||
$nmo->company = $company_reference;
|
||||
$nmo->settings = $this->company->settings;
|
||||
|
||||
NinjaMailerJob::dispatch($nmo, true);
|
||||
|
||||
UnlinkFile::dispatch(config('filesystems.default'), $storage_path)->delay(now()->addHours(1));
|
||||
|
||||
if (Ninja::isHosted()) {
|
||||
sleep(3);
|
||||
|
@ -1,5 +1,5 @@
|
||||
@extends('portal.ninja2020.layout.error')
|
||||
|
||||
@section('title', __($title) ?? 'Server Error')
|
||||
@section('title', $title ?? 'Error')
|
||||
@section('code', __($code) ?? '500')
|
||||
@section('message', __($message) ?? 'System Error')
|
||||
|
@ -101,6 +101,7 @@ use App\Http\Controllers\Support\Messages\SendingController;
|
||||
use App\Http\Controllers\Reports\ClientSalesReportController;
|
||||
use App\Http\Controllers\Reports\InvoiceItemReportController;
|
||||
use App\Http\Controllers\PaymentNotificationWebhookController;
|
||||
use App\Http\Controllers\ProtectedDownloadController;
|
||||
use App\Http\Controllers\Reports\ProductSalesReportController;
|
||||
use App\Http\Controllers\Reports\ClientBalanceReportController;
|
||||
use App\Http\Controllers\Reports\ClientContactReportController;
|
||||
@ -401,4 +402,6 @@ Route::post('api/v1/yodlee/data_updates', [YodleeController::class, 'dataUpdates
|
||||
Route::post('api/v1/yodlee/refresh_updates', [YodleeController::class, 'refreshUpdatesWebhook'])->middleware('throttle:100,1');
|
||||
Route::post('api/v1/yodlee/balance', [YodleeController::class, 'balanceWebhook'])->middleware('throttle:100,1');
|
||||
|
||||
Route::get('api/v1/protected_download/{hash}', [ProtectedDownloadController::class, 'index'])->name('protected_download')->middleware('signed')->middleware('throttle:300,1');
|
||||
|
||||
Route::fallback([BaseController::class, 'notFound'])->middleware('throttle:404');
|
Loading…
Reference in New Issue
Block a user