mirror of
https://github.com/invoiceninja/invoiceninja.git
synced 2026-03-03 02:47:02 +00:00
Updates for company import'
This commit is contained in:
@@ -72,6 +72,68 @@ class StoreSubscriptionRequest extends Request
|
||||
return $this->globalRules($rules);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \Illuminate\Validation\Validator $validator
|
||||
* @return void
|
||||
*/
|
||||
public function withValidator(\Illuminate\Validation\Validator $validator): void
|
||||
{
|
||||
$validator->after(function ($validator) {
|
||||
$this->validateWebhookUrl($validator, 'webhook_configuration.post_purchase_url');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate that a URL doesn't point to internal/private IP addresses.
|
||||
*
|
||||
* @param \Illuminate\Validation\Validator $validator
|
||||
* @param string $field
|
||||
* @return void
|
||||
*/
|
||||
private function validateWebhookUrl(\Illuminate\Validation\Validator $validator, string $field): void
|
||||
{
|
||||
$url = $this->input($field);
|
||||
|
||||
if (empty($url)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Validate URL format
|
||||
if (!filter_var($url, FILTER_VALIDATE_URL)) {
|
||||
$validator->errors()->add($field, ctrans('texts.invalid_url'));
|
||||
return;
|
||||
}
|
||||
|
||||
$parsed = parse_url($url);
|
||||
|
||||
// Only allow http/https protocols
|
||||
$scheme = $parsed['scheme'] ?? '';
|
||||
if (!in_array(strtolower($scheme), ['http', 'https'])) {
|
||||
$validator->errors()->add($field, ctrans('texts.invalid_url'));
|
||||
return;
|
||||
}
|
||||
|
||||
$host = $parsed['host'] ?? '';
|
||||
if (empty($host)) {
|
||||
$validator->errors()->add($field, ctrans('texts.invalid_url'));
|
||||
return;
|
||||
}
|
||||
|
||||
// Resolve hostname to IP and check for private/reserved ranges
|
||||
$ip = gethostbyname($host);
|
||||
|
||||
// gethostbyname returns the hostname if resolution fails
|
||||
if ($ip === $host && !filter_var($host, FILTER_VALIDATE_IP)) {
|
||||
// DNS resolution failed - allow it (external DNS might resolve differently)
|
||||
return;
|
||||
}
|
||||
|
||||
// Block private and reserved IP ranges (SSRF protection)
|
||||
if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE) === false) {
|
||||
$validator->errors()->add($field, ctrans('texts.invalid_url'));
|
||||
}
|
||||
}
|
||||
|
||||
public function prepareForValidation()
|
||||
{
|
||||
$input = $this->all();
|
||||
|
||||
@@ -73,6 +73,69 @@ class UpdateSubscriptionRequest extends Request
|
||||
return $this->globalRules($rules);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param \Illuminate\Validation\Validator $validator
|
||||
* @return void
|
||||
*/
|
||||
public function withValidator(\Illuminate\Validation\Validator $validator): void
|
||||
{
|
||||
$validator->after(function ($validator) {
|
||||
$this->validateWebhookUrl($validator, 'webhook_configuration.post_purchase_url');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate that a URL doesn't point to internal/private IP addresses.
|
||||
*
|
||||
* @param \Illuminate\Validation\Validator $validator
|
||||
* @param string $field
|
||||
* @return void
|
||||
*/
|
||||
private function validateWebhookUrl(\Illuminate\Validation\Validator $validator, string $field): void
|
||||
{
|
||||
$url = $this->input($field);
|
||||
|
||||
if (empty($url)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Validate URL format
|
||||
if (!filter_var($url, FILTER_VALIDATE_URL)) {
|
||||
$validator->errors()->add($field, ctrans('texts.invalid_url'));
|
||||
return;
|
||||
}
|
||||
|
||||
$parsed = parse_url($url);
|
||||
|
||||
// Only allow http/https protocols
|
||||
$scheme = $parsed['scheme'] ?? '';
|
||||
if (!in_array(strtolower($scheme), ['http', 'https'])) {
|
||||
$validator->errors()->add($field, ctrans('texts.invalid_url'));
|
||||
return;
|
||||
}
|
||||
|
||||
$host = $parsed['host'] ?? '';
|
||||
if (empty($host)) {
|
||||
$validator->errors()->add($field, ctrans('texts.invalid_url'));
|
||||
return;
|
||||
}
|
||||
|
||||
// Resolve hostname to IP and check for private/reserved ranges
|
||||
$ip = gethostbyname($host);
|
||||
|
||||
// gethostbyname returns the hostname if resolution fails
|
||||
if ($ip === $host && !filter_var($host, FILTER_VALIDATE_IP)) {
|
||||
// DNS resolution failed - allow it (external DNS might resolve differently)
|
||||
return;
|
||||
}
|
||||
|
||||
// Block private and reserved IP ranges (SSRF protection)
|
||||
if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE) === false) {
|
||||
$validator->errors()->add($field, ctrans('texts.invalid_url'));
|
||||
}
|
||||
}
|
||||
|
||||
public function prepareForValidation()
|
||||
{
|
||||
$input = $this->all();
|
||||
|
||||
@@ -472,16 +472,56 @@ class Import implements ShouldQueue
|
||||
$company_repository->save($data, $this->company);
|
||||
|
||||
if (isset($data['settings']->company_logo) && strlen($data['settings']->company_logo) > 0) {
|
||||
|
||||
|
||||
try {
|
||||
$tempImage = tempnam(sys_get_temp_dir(), basename($data['settings']->company_logo));
|
||||
copy($data['settings']->company_logo, $tempImage);
|
||||
$this->uploadLogo($tempImage, $this->company, $this->company);
|
||||
$logoUrl = $data['settings']->company_logo;
|
||||
|
||||
// 1. Validate URL format
|
||||
if (!filter_var($logoUrl, FILTER_VALIDATE_URL)) {
|
||||
throw new \Exception('Invalid URL format');
|
||||
}
|
||||
|
||||
// 2. Restrict protocols
|
||||
$parsed = parse_url($logoUrl);
|
||||
if (!in_array($parsed['scheme'] ?? '', ['http', 'https'])) {
|
||||
throw new \Exception('Only HTTP/HTTPS allowed');
|
||||
}
|
||||
|
||||
// 3. Block internal/private IPs (SSRF protection)
|
||||
$host = $parsed['host'] ?? '';
|
||||
$ip = gethostbyname($host);
|
||||
if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE) === false) {
|
||||
throw new \Exception('Internal hosts not allowed');
|
||||
}
|
||||
|
||||
// 4. Use HTTP client with timeout and size limits instead of copy()
|
||||
$response = \Illuminate\Support\Facades\Http::timeout(20)->get($logoUrl);
|
||||
|
||||
if ($response->successful() && strlen($response->body()) < 20 * 1024 * 1024) { // 5MB limit
|
||||
$tempImage = tempnam(sys_get_temp_dir(), 'logo_');
|
||||
file_put_contents($tempImage, $response->body());
|
||||
$this->uploadLogo($tempImage, $this->company, $this->company);
|
||||
@unlink($tempImage); // Cleanup
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
$settings = $this->company->settings;
|
||||
$settings->company_logo = '';
|
||||
$this->company->settings = $settings;
|
||||
$this->company->save();
|
||||
nlog("Logo import failed: " . $e->getMessage());
|
||||
}
|
||||
|
||||
// try {
|
||||
// $tempImage = tempnam(sys_get_temp_dir(), basename($data['settings']->company_logo));
|
||||
// copy($data['settings']->company_logo, $tempImage);
|
||||
// $this->uploadLogo($tempImage, $this->company, $this->company);
|
||||
// } catch (\Exception $e) {
|
||||
// $settings = $this->company->settings;
|
||||
// $settings->company_logo = '';
|
||||
// $this->company->settings = $settings;
|
||||
// $this->company->save();
|
||||
// }
|
||||
}
|
||||
|
||||
Company::reguard();
|
||||
|
||||
@@ -701,7 +701,7 @@ class StripePaymentDriver extends BaseDriver implements SupportsHeadlessInterfac
|
||||
|
||||
public function processWebhookRequest(PaymentWebhookRequest $request)
|
||||
{
|
||||
nlog($request->all());
|
||||
// nlog($request->all());
|
||||
$webhook_secret = $this->company_gateway->getConfigField('webhookSecret');
|
||||
|
||||
if ($webhook_secret) {
|
||||
|
||||
@@ -12,11 +12,11 @@
|
||||
|
||||
namespace App\Services\EDocument\Standards;
|
||||
|
||||
use App\DataMapper\Tax\BaseRule;
|
||||
use App\Models\Company;
|
||||
use App\Models\Invoice;
|
||||
use App\Models\Product;
|
||||
use App\Helpers\Invoice\Taxer;
|
||||
use App\DataMapper\Tax\BaseRule;
|
||||
use App\Services\AbstractService;
|
||||
use App\Helpers\Invoice\InvoiceSum;
|
||||
use InvoiceNinja\EInvoice\EInvoice;
|
||||
@@ -654,9 +654,11 @@ class Peppol extends AbstractService
|
||||
case Product::PRODUCT_TYPE_DIGITAL:
|
||||
case Product::PRODUCT_TYPE_PHYSICAL:
|
||||
case Product::PRODUCT_TYPE_SHIPPING:
|
||||
case Product::PRODUCT_TYPE_REDUCED_TAX:
|
||||
$tax_type = 'S';
|
||||
break;
|
||||
case Product::PRODUCT_TYPE_REDUCED_TAX:
|
||||
$tax_type = 'AA';
|
||||
break;
|
||||
case Product::PRODUCT_TYPE_EXEMPT:
|
||||
$tax_type = 'E';
|
||||
break;
|
||||
@@ -1405,7 +1407,7 @@ class Peppol extends AbstractService
|
||||
$tax_total = new TaxTotal();
|
||||
$taxes = $this->calc->getTaxMap();
|
||||
|
||||
if (count($taxes) < 1) {
|
||||
if (count($taxes) < 1 || (count($taxes) == 1 && $this->invoice->total_taxes == 0)) {
|
||||
|
||||
$tax_amount = new TaxAmount();
|
||||
$tax_amount->currencyID = $this->invoice->client->currency()->code;
|
||||
@@ -1458,7 +1460,8 @@ class Peppol extends AbstractService
|
||||
// Required: TaxAmount (BT-110)
|
||||
$tax_amount = new TaxAmount();
|
||||
$tax_amount->currencyID = $this->invoice->client->currency()->code;
|
||||
$tax_amount->amount = (string)$grouped_tax['total'];
|
||||
// $tax_amount->amount = (string)$grouped_tax['total'];
|
||||
$tax_amount->amount = (string)round($this->invoice->total_taxes, 2);
|
||||
$tax_total->TaxAmount = $tax_amount;
|
||||
|
||||
// Required: TaxSubtotal (BG-23)
|
||||
@@ -1490,6 +1493,11 @@ class Peppol extends AbstractService
|
||||
$category_id = new ID();
|
||||
$category_id->value = $this->getTaxType($grouped_tax['tax_id']); // Standard rate
|
||||
|
||||
// Temp fix for reduced tax rate categorization.
|
||||
if($grouped_tax['tax_rate'] < 15 && $grouped_tax['tax_rate'] >= 0) {
|
||||
$category_id->value = 'AA';
|
||||
}
|
||||
|
||||
$tax_category->ID = $category_id;
|
||||
|
||||
// Required: TaxCategory Rate (BT-119)
|
||||
@@ -1504,7 +1512,8 @@ class Peppol extends AbstractService
|
||||
$tax_scheme->ID = $scheme_id;
|
||||
$tax_category->TaxScheme = $tax_scheme;
|
||||
|
||||
$tax_subtotal->TaxCategory = $this->globalTaxCategories[0];
|
||||
$tax_subtotal->TaxCategory = $tax_category;
|
||||
// $tax_subtotal->TaxCategory = $this->globalTaxCategories[0];
|
||||
|
||||
$tax_total->TaxSubtotal[] = $tax_subtotal;
|
||||
|
||||
@@ -1533,8 +1542,8 @@ class Peppol extends AbstractService
|
||||
|
||||
$country_code = $this->invoice->client->country->iso_3166_2;
|
||||
|
||||
if (isset($this->ninja_invoice->company->tax_data->regions->EU->subregions->{$country_code}->vat_number)) {
|
||||
$this->override_vat_number = $this->ninja_invoice->company->tax_data->regions->EU->subregions->{$country_code}->vat_number;
|
||||
if (isset($this->company->tax_data->regions->EU->subregions->{$country_code}->vat_number)) {
|
||||
$this->override_vat_number = $this->company->tax_data->regions->EU->subregions->{$country_code}->vat_number;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user