Warenwirtschaft: Review-Fix — AP-32-Label als Base64 übertragen (Connector 1.5.0)
Die Brücke hätte live nicht funktioniert: Connector 1.4.0 sendete die label_url aus dem per 'deny from all' geschützten DHL-Ordner (403), dazu SSRF-Risiko durch serverseitigen Abruf einer Client-URL und Timeout- Mismatch (20 s Download vs. 5 s WordPress-Default). Der Connector liest die PDF jetzt vom Shop-Dateisystem (realpath-Pinning auf wp-uploads) und sendet sie Base64; storeWpLabel() validiert und speichert ohne Http::get; tote Shop-Link-Fallbacks aus den Views entfernt; Connector-Timeout 30 s. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
ddd6dde593
commit
1b9b6adc44
11 changed files with 173 additions and 83 deletions
|
|
@ -16,7 +16,6 @@ use App\Services\Shop;
|
|||
use Carbon\Carbon;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Support\Facades\Mail;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
|
|
@ -32,6 +31,9 @@ class ShoppingUserController extends Controller
|
|||
|
||||
protected $member_id = 2; // thomas.krummel@gmail.com
|
||||
|
||||
/** AP-32: Obergrenze für per Base64 übertragene DHL-Label-PDFs (dekodiert). */
|
||||
private const WP_LABEL_MAX_BYTES = 10 * 1024 * 1024;
|
||||
|
||||
/**
|
||||
* @param Request $request
|
||||
* wp_order_numbers[1234, 1234]
|
||||
|
|
@ -368,6 +370,7 @@ class ShoppingUserController extends Controller
|
|||
}
|
||||
|
||||
$wp_invoice_path = isset($request->wp_invoice_path) ? $request->wp_invoice_path : null;
|
||||
$wp_label_pdf = isset($request->wp_label_pdf) ? $request->wp_label_pdf : null;
|
||||
$wp_label_url = isset($request->wp_label_url) ? $request->wp_label_url : null;
|
||||
$wp_tracking_number = isset($request->wp_tracking_number) ? $request->wp_tracking_number : null;
|
||||
$wp_advertising = isset($request->wp_advertising) ? $request->wp_advertising : '';
|
||||
|
|
@ -382,7 +385,7 @@ class ShoppingUserController extends Controller
|
|||
|
||||
if ($wp_order) {
|
||||
// AP-32 Brückenlösung: DHL-Label aus dem Shop übernehmen (Kopie lokal speichern)
|
||||
$this->storeWpLabel($shopping_user->shopping_order, $wp_label_url, $wp_tracking_number);
|
||||
$this->storeWpLabel($shopping_user->shopping_order, $wp_label_pdf, $wp_label_url, $wp_tracking_number);
|
||||
|
||||
// AP-13: Übertragung inkl. SKU-Zuordnungsergebnis je Position protokollieren
|
||||
ApiOrderLog::recordTransfer($request->wp_order_number, $shopping_user, $shopping_user->shopping_order, $wp_order);
|
||||
|
|
@ -427,6 +430,7 @@ class ShoppingUserController extends Controller
|
|||
{
|
||||
$request->validate([
|
||||
'wp_order_number' => 'required|int',
|
||||
'wp_label_pdf' => 'nullable|string',
|
||||
'wp_label_url' => 'nullable|url',
|
||||
'wp_tracking_number' => 'nullable',
|
||||
]);
|
||||
|
|
@ -441,7 +445,7 @@ class ShoppingUserController extends Controller
|
|||
}
|
||||
|
||||
$shopping_order = $shopping_user->shopping_order;
|
||||
$this->storeWpLabel($shopping_order, $request->wp_label_url, $request->wp_tracking_number);
|
||||
$this->storeWpLabel($shopping_order, $request->wp_label_pdf, $request->wp_label_url, $request->wp_tracking_number);
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
|
|
@ -456,15 +460,18 @@ class ShoppingUserController extends Controller
|
|||
}
|
||||
|
||||
/**
|
||||
* Label-URL + Sendungsnummer aus dem WooCommerce-Shop an der Bestellung speichern.
|
||||
* Das PDF wird als lokale Kopie in den privaten Storage geladen (Auslieferung nur
|
||||
* über die Admin-Route) — so bleibt es verfügbar, auch wenn es im Shop gelöscht
|
||||
* wird, und die öffentliche Upload-URL muss nicht verlinkt werden. Ein
|
||||
* fehlgeschlagener Download darf die Übertragung nie scheitern lassen.
|
||||
* Label + Sendungsnummer aus dem WooCommerce-Shop an der Bestellung speichern.
|
||||
* Das PDF kommt seit Connector 1.5.0 als Base64 direkt aus dem Shop-Dateisystem
|
||||
* mit (Review 20.07.2026: der Label-Ordner im Shop ist per „deny from all"
|
||||
* geschützt, ein URL-Abruf lief ins Leere — und der serverseitige Abruf einer
|
||||
* Client-URL war ein SSRF-Risiko und blockierte den Request bis 20 s). Die Kopie
|
||||
* liegt im privaten Storage (Auslieferung nur über die Admin-Route) und bleibt
|
||||
* verfügbar, auch wenn das Label im Shop gelöscht wird. Ein ungültiges Label
|
||||
* darf die Übertragung nie scheitern lassen.
|
||||
*/
|
||||
private function storeWpLabel($shopping_order, $wp_label_url, $wp_tracking_number): void
|
||||
private function storeWpLabel($shopping_order, $wp_label_pdf, $wp_label_url, $wp_tracking_number): void
|
||||
{
|
||||
if (! $shopping_order || (! $wp_label_url && ! $wp_tracking_number)) {
|
||||
if (! $shopping_order || (! $wp_label_pdf && ! $wp_label_url && ! $wp_tracking_number)) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -475,25 +482,21 @@ class ShoppingUserController extends Controller
|
|||
$shopping_order->tracking = $wp_tracking_number;
|
||||
}
|
||||
|
||||
// Reines Anzeige-Metadatum (Herkunft im Shop) — wird nie abgerufen oder verlinkt
|
||||
if ($wp_label_url) {
|
||||
$shopping_order->wp_label_url = $wp_label_url;
|
||||
try {
|
||||
$response = Http::timeout(20)->get($wp_label_url);
|
||||
if ($response->successful() && str_starts_with($response->body(), '%PDF')) {
|
||||
$path = 'wp-labels/label-'.$shopping_order->id.'.pdf';
|
||||
Storage::disk('local')->put($path, $response->body());
|
||||
$shopping_order->wp_label_path = $path;
|
||||
} else {
|
||||
Log::warning('WP-Label-Download fehlgeschlagen (kein PDF oder HTTP-Fehler)', [
|
||||
'shopping_order_id' => $shopping_order->id,
|
||||
'wp_label_url' => $wp_label_url,
|
||||
'status' => $response->status(),
|
||||
]);
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
Log::warning('WP-Label-Download fehlgeschlagen: '.$e->getMessage(), [
|
||||
}
|
||||
|
||||
if ($wp_label_pdf) {
|
||||
$content = base64_decode($wp_label_pdf, true);
|
||||
if ($content !== false && str_starts_with($content, '%PDF') && strlen($content) <= self::WP_LABEL_MAX_BYTES) {
|
||||
$path = 'wp-labels/label-'.$shopping_order->id.'.pdf';
|
||||
Storage::disk('local')->put($path, $content);
|
||||
$shopping_order->wp_label_path = $path;
|
||||
} else {
|
||||
Log::warning('WP-Label verworfen (kein gültiges Base64-PDF oder zu groß)', [
|
||||
'shopping_order_id' => $shopping_order->id,
|
||||
'wp_label_url' => $wp_label_url,
|
||||
'bytes' => is_string($content) ? strlen($content) : null,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue