diff --git a/src/Modules/Settings/AllegroIntegrationController.php b/src/Modules/Settings/AllegroIntegrationController.php index 44cce5c..be195b0 100644 --- a/src/Modules/Settings/AllegroIntegrationController.php +++ b/src/Modules/Settings/AllegroIntegrationController.php @@ -124,27 +124,14 @@ final class AllegroIntegrationController } $environment = trim((string) $request->input('environment', 'sandbox')); - if (!in_array($environment, ['sandbox', 'production'], true)) { - Flash::set('settings_error', $this->translator->get('settings.allegro.validation.environment_invalid')); - return Response::redirect($redirectTo); - } - $clientId = trim((string) $request->input('client_id', '')); - if ($clientId !== '' && mb_strlen($clientId) > 128) { - Flash::set('settings_error', $this->translator->get('settings.allegro.validation.client_id_too_long')); - return Response::redirect($redirectTo); - } - $redirectUriInput = trim((string) $request->input('redirect_uri', '')); $redirectUri = $redirectUriInput !== '' ? $redirectUriInput : $this->defaultRedirectUri(); - if (!$this->isValidHttpUrl($redirectUri)) { - Flash::set('settings_error', $this->translator->get('settings.allegro.validation.redirect_uri_invalid')); - return Response::redirect($redirectTo); - } - $ordersFetchStartDate = trim((string) $request->input('orders_fetch_start_date', '')); - if ($ordersFetchStartDate !== '' && !$this->isValidDate($ordersFetchStartDate)) { - Flash::set('settings_error', $this->translator->get('settings.allegro.validation.orders_fetch_start_date_invalid')); + + $validationError = $this->validateSaveInput($environment, $clientId, $redirectUri, $ordersFetchStartDate); + if ($validationError !== null) { + Flash::set('settings_error', $validationError); return Response::redirect($redirectTo); } @@ -178,27 +165,27 @@ final class AllegroIntegrationController $intervalMinutesRaw = (int) $request->input('orders_import_interval_minutes', 5); $intervalMinutes = max(1, min(1440, $intervalMinutesRaw)); - if ($intervalMinutesRaw !== $intervalMinutes) { - Flash::set('settings_error', $this->translator->get('settings.allegro.validation.orders_import_interval_invalid')); - return Response::redirect(RedirectPaths::ALLEGRO_SETTINGS_TAB); - } $statusSyncDirection = trim((string) $request->input( 'status_sync_direction', self::STATUS_SYNC_DIRECTION_ALLEGRO_TO_ORDERPRO )); - if (!in_array($statusSyncDirection, $this->allowedStatusSyncDirections(), true)) { - Flash::set('settings_error', $this->translator->get('settings.allegro.validation.status_sync_direction_invalid')); - return Response::redirect(RedirectPaths::ALLEGRO_SETTINGS_TAB); - } $statusSyncIntervalRaw = (int) $request->input( 'status_sync_interval_minutes', self::STATUS_SYNC_DEFAULT_INTERVAL_MINUTES ); $statusSyncInterval = max(1, min(1440, $statusSyncIntervalRaw)); - if ($statusSyncIntervalRaw !== $statusSyncInterval) { - Flash::set('settings_error', $this->translator->get('settings.allegro.validation.status_sync_interval_invalid')); + + $validationError = $this->validateImportSettingsInput( + $intervalMinutesRaw, + $intervalMinutes, + $statusSyncDirection, + $statusSyncIntervalRaw, + $statusSyncInterval + ); + if ($validationError !== null) { + Flash::set('settings_error', $validationError); return Response::redirect(RedirectPaths::ALLEGRO_SETTINGS_TAB); } @@ -419,14 +406,11 @@ final class AllegroIntegrationController $state = trim((string) $request->input('state', '')); $expectedState = trim((string) ($_SESSION[self::OAUTH_STATE_SESSION_KEY] ?? '')); unset($_SESSION[self::OAUTH_STATE_SESSION_KEY]); - if ($state === '' || $expectedState === '' || !hash_equals($expectedState, $state)) { - Flash::set('settings_error', $this->translator->get('settings.allegro.flash.oauth_state_invalid')); - return Response::redirect(RedirectPaths::ALLEGRO_INTEGRATION); - } - $authorizationCode = trim((string) $request->input('code', '')); - if ($authorizationCode === '') { - Flash::set('settings_error', $this->translator->get('settings.allegro.flash.oauth_code_missing')); + + $validationError = $this->validateOAuthCallbackParams($state, $expectedState, $authorizationCode); + if ($validationError !== null) { + Flash::set('settings_error', $validationError); return Response::redirect(RedirectPaths::ALLEGRO_INTEGRATION); } @@ -896,6 +880,60 @@ final class AllegroIntegrationController ]; } + private function validateSaveInput( + string $environment, + string $clientId, + string $redirectUri, + string $ordersFetchStartDate + ): ?string { + if (!in_array($environment, ['sandbox', 'production'], true)) { + return $this->translator->get('settings.allegro.validation.environment_invalid'); + } + if ($clientId !== '' && mb_strlen($clientId) > 128) { + return $this->translator->get('settings.allegro.validation.client_id_too_long'); + } + if (!$this->isValidHttpUrl($redirectUri)) { + return $this->translator->get('settings.allegro.validation.redirect_uri_invalid'); + } + if ($ordersFetchStartDate !== '' && !$this->isValidDate($ordersFetchStartDate)) { + return $this->translator->get('settings.allegro.validation.orders_fetch_start_date_invalid'); + } + return null; + } + + private function validateImportSettingsInput( + int $intervalMinutesRaw, + int $intervalMinutes, + string $statusSyncDirection, + int $statusSyncIntervalRaw, + int $statusSyncInterval + ): ?string { + if ($intervalMinutesRaw !== $intervalMinutes) { + return $this->translator->get('settings.allegro.validation.orders_import_interval_invalid'); + } + if (!in_array($statusSyncDirection, $this->allowedStatusSyncDirections(), true)) { + return $this->translator->get('settings.allegro.validation.status_sync_direction_invalid'); + } + if ($statusSyncIntervalRaw !== $statusSyncInterval) { + return $this->translator->get('settings.allegro.validation.status_sync_interval_invalid'); + } + return null; + } + + private function validateOAuthCallbackParams( + string $state, + string $expectedState, + string $authorizationCode + ): ?string { + if ($state === '' || $expectedState === '' || !hash_equals($expectedState, $state)) { + return $this->translator->get('settings.allegro.flash.oauth_state_invalid'); + } + if ($authorizationCode === '') { + return $this->translator->get('settings.allegro.flash.oauth_code_missing'); + } + return null; + } + private function ensureDefaultSchedulesExist(): void { try { diff --git a/src/Modules/Settings/ShopproIntegrationsController.php b/src/Modules/Settings/ShopproIntegrationsController.php index 6e283ee..9ca9f28 100644 --- a/src/Modules/Settings/ShopproIntegrationsController.php +++ b/src/Modules/Settings/ShopproIntegrationsController.php @@ -118,44 +118,21 @@ final class ShopproIntegrationsController $redirectBase = RedirectPaths::SHOPPRO_INTEGRATION; $redirectTo = $this->buildRedirectUrl($integrationId, $tab); - if (!Csrf::validate((string) $request->input('_token', ''))) { - Flash::set('settings_error', $this->translator->get('auth.errors.csrf_expired')); - return Response::redirect($redirectTo); - } - $existing = $integrationId > 0 ? $this->repository->findIntegration($integrationId) : null; - if ($integrationId > 0 && $existing === null) { - Flash::set('settings_error', $this->translator->get('settings.integrations.flash.not_found')); - return Response::redirect($this->buildRedirectUrl(0, $tab)); + $accessError = $this->validateSaveAccess((string) $request->input('_token', ''), $integrationId, $existing, $tab); + if ($accessError !== null) { + return $accessError; } $name = trim((string) $request->input('name', '')); - if (mb_strlen($name) < 2) { - Flash::set('settings_error', $this->translator->get('settings.integrations.validation.name_min')); - return Response::redirect($redirectTo); - } - $baseUrl = rtrim(trim((string) $request->input('base_url', '')), '/'); - if (!$this->isValidHttpUrl($baseUrl)) { - Flash::set('settings_error', $this->translator->get('settings.integrations.validation.base_url_invalid')); - return Response::redirect($redirectTo); - } - $apiKey = trim((string) $request->input('api_key', '')); $hasExistingApiKey = (bool) ($existing['has_api_key'] ?? false); - if ($tab === 'integration' && $apiKey === '' && !$hasExistingApiKey) { - Flash::set('settings_error', $this->translator->get('settings.integrations.validation.api_key_required')); - return Response::redirect($redirectTo); - } - $ordersFetchStartDate = trim((string) $request->input('orders_fetch_start_date', '')); - if ($ordersFetchStartDate !== '' && !$this->isValidYmdDate($ordersFetchStartDate)) { - Flash::set('settings_error', $this->translator->get('settings.integrations.validation.orders_fetch_start_date_invalid')); - return Response::redirect($redirectTo); - } - if ($this->isDuplicateName($integrationId, $name)) { - Flash::set('settings_error', $this->translator->get('settings.integrations.validation.name_taken')); + $validationError = $this->validateSaveInput($name, $baseUrl, $apiKey, $hasExistingApiKey, $tab, $ordersFetchStartDate, $integrationId); + if ($validationError !== null) { + Flash::set('settings_error', $validationError); return Response::redirect($redirectTo); } @@ -193,12 +170,12 @@ final class ShopproIntegrationsController ? 'settings.integrations.flash.updated' : 'settings.integrations.flash.created'; Flash::set('settings_success', $this->translator->get($flashKey)); - - return Response::redirect($this->buildRedirectUrl($savedId, $tab)); + $redirectTo = $this->buildRedirectUrl($savedId, $tab); } catch (Throwable) { Flash::set('settings_error', $this->translator->get('settings.integrations.flash.failed')); - return Response::redirect($redirectTo); } + + return Response::redirect($redirectTo); } public function test(Request $request): Response @@ -242,14 +219,9 @@ final class ShopproIntegrationsController $integrationId = max(0, (int) $request->input('integration_id', 0)); $redirectTo = $this->buildRedirectUrl($integrationId, 'statuses'); - if (!Csrf::validate((string) $request->input('_token', ''))) { - Flash::set('settings_error', $this->translator->get('auth.errors.csrf_expired')); - return Response::redirect($redirectTo); - } - - if ($integrationId <= 0 || $this->repository->findIntegration($integrationId) === null) { - Flash::set('settings_error', $this->translator->get('settings.integrations.flash.not_found')); - return Response::redirect($this->buildRedirectUrl(0, 'statuses')); + $accessError = $this->validateCsrfAndIntegrationAccess((string) $request->input('_token', ''), $integrationId, 'statuses'); + if ($accessError !== null) { + return $accessError; } $shopCodes = $request->input('shoppro_status_code', []); @@ -305,14 +277,9 @@ final class ShopproIntegrationsController $integrationId = max(0, (int) $request->input('integration_id', 0)); $redirectTo = $this->buildRedirectUrl($integrationId, 'statuses'); - if (!Csrf::validate((string) $request->input('_token', ''))) { - Flash::set('settings_error', $this->translator->get('auth.errors.csrf_expired')); - return Response::redirect($redirectTo); - } - - if ($integrationId <= 0 || $this->repository->findIntegration($integrationId) === null) { - Flash::set('settings_error', $this->translator->get('settings.integrations.flash.not_found')); - return Response::redirect($this->buildRedirectUrl(0, 'statuses')); + $accessError = $this->validateCsrfAndIntegrationAccess((string) $request->input('_token', ''), $integrationId, 'statuses'); + if ($accessError !== null) { + return $accessError; } $result = $this->repository->fetchOrderStatuses($integrationId); @@ -431,6 +398,62 @@ final class ShopproIntegrationsController ]; } + /** + * @param array|null $existing + */ + private function validateSaveAccess(string $token, int $integrationId, ?array $existing, string $tab): ?Response + { + if (!Csrf::validate($token)) { + Flash::set('settings_error', $this->translator->get('auth.errors.csrf_expired')); + return Response::redirect($this->buildRedirectUrl($integrationId, $tab)); + } + if ($integrationId > 0 && $existing === null) { + Flash::set('settings_error', $this->translator->get('settings.integrations.flash.not_found')); + return Response::redirect($this->buildRedirectUrl(0, $tab)); + } + return null; + } + + private function validateSaveInput( + string $name, + string $baseUrl, + string $apiKey, + bool $hasExistingApiKey, + string $tab, + string $ordersFetchStartDate, + int $integrationId + ): ?string { + if (mb_strlen($name) < 2) { + return $this->translator->get('settings.integrations.validation.name_min'); + } + if (!$this->isValidHttpUrl($baseUrl)) { + return $this->translator->get('settings.integrations.validation.base_url_invalid'); + } + if ($tab === 'integration' && $apiKey === '' && !$hasExistingApiKey) { + return $this->translator->get('settings.integrations.validation.api_key_required'); + } + if ($ordersFetchStartDate !== '' && !$this->isValidYmdDate($ordersFetchStartDate)) { + return $this->translator->get('settings.integrations.validation.orders_fetch_start_date_invalid'); + } + if ($this->isDuplicateName($integrationId, $name)) { + return $this->translator->get('settings.integrations.validation.name_taken'); + } + return null; + } + + private function validateCsrfAndIntegrationAccess(string $token, int $integrationId, string $tab): ?Response + { + if (!Csrf::validate($token)) { + Flash::set('settings_error', $this->translator->get('auth.errors.csrf_expired')); + return Response::redirect($this->buildRedirectUrl($integrationId, $tab)); + } + if ($integrationId <= 0 || $this->repository->findIntegration($integrationId) === null) { + Flash::set('settings_error', $this->translator->get('settings.integrations.flash.not_found')); + return Response::redirect($this->buildRedirectUrl(0, $tab)); + } + return null; + } + private function isDuplicateName(int $currentId, string $name): bool { $needle = mb_strtolower(trim($name));