From 7f8f0617254f58f477f8534ae91541e309d5f3b3 Mon Sep 17 00:00:00 2001 From: Thomas Peterson Date: Mon, 26 May 2025 16:40:36 +0200 Subject: [PATCH] SFTP Plugin --- .gitea/workflows/run_tests.yaml | 6 - src/new/composer.json | 1 + src/new/composer.lock | 179 +++++++++++++++++- .../PSC/Ftp/Controller/CheckController.php | 62 +++--- .../plugins/System/PSC/Ftp/Document/Ftp.php | 16 +- src/new/var/plugins/System/PSC/Ftp/Plugin.php | 6 +- .../var/plugins/System/PSC/Ftp/Queue/Ftp.php | 108 +++++++---- .../Ftp/Resources/views/queue/ftp.html.twig | 19 +- 8 files changed, 325 insertions(+), 72 deletions(-) diff --git a/.gitea/workflows/run_tests.yaml b/.gitea/workflows/run_tests.yaml index 4761abd6b..0118bfb90 100644 --- a/.gitea/workflows/run_tests.yaml +++ b/.gitea/workflows/run_tests.yaml @@ -30,9 +30,6 @@ jobs: - name: Run Application Image run: | make docker-compose-up DOCKER_SERVICE_NAME=application - - name: Load Data - run: | - docker exec -i psc_ci-mongodb-1 /usr/bin/mongorestore --archive < ./dev_db/mongodb.dump - name: Run Composer Install run: | chmod -R 0777 src/new @@ -84,9 +81,6 @@ jobs: - name: Run Application Image run: | make docker-compose-up DOCKER_SERVICE_NAME=application - - name: Load Data - run: | - docker exec -i psc_ci-mongodb-1 /usr/bin/mongorestore --archive < ./dev_db/mongodb.dump - name: Run Composer Install run: | chmod -R 0777 src/new diff --git a/src/new/composer.json b/src/new/composer.json index b82ef5182..95440fd2b 100755 --- a/src/new/composer.json +++ b/src/new/composer.json @@ -55,6 +55,7 @@ "paypal/rest-api-sdk-php": "dev-master", "php-http/guzzle6-adapter": "^1.1", "phpoffice/phpspreadsheet": "^1.28", + "phpseclib/phpseclib": "~3.0", "picqer/sendcloud-php-client": "v2.8.1", "portphp/csv": ">=1.1.0", "portphp/excel": ">=1.1.0", diff --git a/src/new/composer.lock b/src/new/composer.lock index 69a6fd44e..5647c45e6 100755 --- a/src/new/composer.lock +++ b/src/new/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "99334202e137c912623234d7f71ca0ce", + "content-hash": "73e98a07bb5f5f1bcb7f9ff9649aa135", "packages": [ { "name": "azuyalabs/yasumi", @@ -6014,6 +6014,73 @@ "abandoned": true, "time": "2017-03-28T22:19:25+00:00" }, + { + "name": "paragonie/constant_time_encoding", + "version": "v3.0.0", + "source": { + "type": "git", + "url": "https://github.com/paragonie/constant_time_encoding.git", + "reference": "df1e7fde177501eee2037dd159cf04f5f301a512" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/paragonie/constant_time_encoding/zipball/df1e7fde177501eee2037dd159cf04f5f301a512", + "reference": "df1e7fde177501eee2037dd159cf04f5f301a512", + "shasum": "" + }, + "require": { + "php": "^8" + }, + "require-dev": { + "phpunit/phpunit": "^9", + "vimeo/psalm": "^4|^5" + }, + "type": "library", + "autoload": { + "psr-4": { + "ParagonIE\\ConstantTime\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Paragon Initiative Enterprises", + "email": "security@paragonie.com", + "homepage": "https://paragonie.com", + "role": "Maintainer" + }, + { + "name": "Steve 'Sc00bz' Thomas", + "email": "steve@tobtu.com", + "homepage": "https://www.tobtu.com", + "role": "Original Developer" + } + ], + "description": "Constant-time Implementations of RFC 4648 Encoding (Base-64, Base-32, Base-16)", + "keywords": [ + "base16", + "base32", + "base32_decode", + "base32_encode", + "base64", + "base64_decode", + "base64_encode", + "bin2hex", + "encoding", + "hex", + "hex2bin", + "rfc4648" + ], + "support": { + "email": "info@paragonie.com", + "issues": "https://github.com/paragonie/constant_time_encoding/issues", + "source": "https://github.com/paragonie/constant_time_encoding" + }, + "time": "2024-05-08T12:36:18+00:00" + }, { "name": "paypal/paypal-checkout-sdk", "version": "dev-master", @@ -6685,6 +6752,116 @@ }, "time": "2025-02-08T02:56:14+00:00" }, + { + "name": "phpseclib/phpseclib", + "version": "3.0.43", + "source": { + "type": "git", + "url": "https://github.com/phpseclib/phpseclib.git", + "reference": "709ec107af3cb2f385b9617be72af8cf62441d02" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpseclib/phpseclib/zipball/709ec107af3cb2f385b9617be72af8cf62441d02", + "reference": "709ec107af3cb2f385b9617be72af8cf62441d02", + "shasum": "" + }, + "require": { + "paragonie/constant_time_encoding": "^1|^2|^3", + "paragonie/random_compat": "^1.4|^2.0|^9.99.99", + "php": ">=5.6.1" + }, + "require-dev": { + "phpunit/phpunit": "*" + }, + "suggest": { + "ext-dom": "Install the DOM extension to load XML formatted public keys.", + "ext-gmp": "Install the GMP (GNU Multiple Precision) extension in order to speed up arbitrary precision integer arithmetic operations.", + "ext-libsodium": "SSH2/SFTP can make use of some algorithms provided by the libsodium-php extension.", + "ext-mcrypt": "Install the Mcrypt extension in order to speed up a few other cryptographic operations.", + "ext-openssl": "Install the OpenSSL extension in order to speed up a wide variety of cryptographic operations." + }, + "type": "library", + "autoload": { + "files": [ + "phpseclib/bootstrap.php" + ], + "psr-4": { + "phpseclib3\\": "phpseclib/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jim Wigginton", + "email": "terrafrost@php.net", + "role": "Lead Developer" + }, + { + "name": "Patrick Monnerat", + "email": "pm@datasphere.ch", + "role": "Developer" + }, + { + "name": "Andreas Fischer", + "email": "bantu@phpbb.com", + "role": "Developer" + }, + { + "name": "Hans-Jürgen Petrich", + "email": "petrich@tronic-media.com", + "role": "Developer" + }, + { + "name": "Graham Campbell", + "email": "graham@alt-three.com", + "role": "Developer" + } + ], + "description": "PHP Secure Communications Library - Pure-PHP implementations of RSA, AES, SSH2, SFTP, X.509 etc.", + "homepage": "http://phpseclib.sourceforge.net", + "keywords": [ + "BigInteger", + "aes", + "asn.1", + "asn1", + "blowfish", + "crypto", + "cryptography", + "encryption", + "rsa", + "security", + "sftp", + "signature", + "signing", + "ssh", + "twofish", + "x.509", + "x509" + ], + "support": { + "issues": "https://github.com/phpseclib/phpseclib/issues", + "source": "https://github.com/phpseclib/phpseclib/tree/3.0.43" + }, + "funding": [ + { + "url": "https://github.com/terrafrost", + "type": "github" + }, + { + "url": "https://www.patreon.com/phpseclib", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpseclib/phpseclib", + "type": "tidelift" + } + ], + "time": "2024-12-14T21:12:59+00:00" + }, { "name": "phpstan/phpdoc-parser", "version": "2.1.0", diff --git a/src/new/var/plugins/System/PSC/Ftp/Controller/CheckController.php b/src/new/var/plugins/System/PSC/Ftp/Controller/CheckController.php index 4c40e30c0..92cf20cf5 100755 --- a/src/new/var/plugins/System/PSC/Ftp/Controller/CheckController.php +++ b/src/new/var/plugins/System/PSC/Ftp/Controller/CheckController.php @@ -3,7 +3,10 @@ namespace Plugin\System\PSC\Ftp\Controller; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\HttpFoundation\JsonResponse; +use Symfony\Component\HttpFoundation\Request; use Symfony\Component\Routing\Annotation\Route; +use phpseclib3\Net\SFTP; +use phpseclib3\Net\SSH2; #[Route('/check')] class CheckController extends AbstractController @@ -11,30 +14,45 @@ class CheckController extends AbstractController #[Route('/', name: 'psc_plugin_ftp_check')] public function indexAction(Request $request) { - $ftp = new \FtpClient\FtpClient(); - - if($request->get('ftpSsl', 'false') == 'false') { - $ftp->connect($request->get('ftpHost'), false, $request->get('ftpPort', 21)); - }else{ - $ftp->connect($request->get('ftpHost'), true, $request->get('ftpPort', 21)); - } - - $ftp->login($request->get('ftpUsername'), $request->get('ftpPassword')); - - $ftp->pasv($request->get('ftpPassiv', 0)); - if($request->get('ftpPath') == '' ) { - $count = $ftp->count('.', null, false); - $data = $ftp->scanDir('.', false); - }else{ - $count = $ftp->count($request->get('ftpPath', '.'), null,false); - $data = $ftp->scanDir($request->get('ftpPath', '.', false)); - } - + $count = 0; $temp = []; - foreach($data as $key => $row) { - $temp[] = $row; + if($request->get('sftp', 'false') == 'false') { + $ftp = new \FtpClient\FtpClient(); + + if($request->get('ftpSsl', 'false') == 'false') { + $ftp->connect($request->get('ftpHost'), false, $request->get('ftpPort', 21)); + }else{ + $ftp->connect($request->get('ftpHost'), true, $request->get('ftpPort', 21)); + } + + $ftp->login($request->get('ftpUsername'), $request->get('ftpPassword')); + + $ftp->pasv($request->get('ftpPassiv', 0)); + if($request->get('ftpPath') == '' ) { + $count = $ftp->count('.', null, false); + $data = $ftp->scanDir('.', false); + }else{ + $count = $ftp->count($request->get('ftpPath', '.'), null,false); + $data = $ftp->scanDir($request->get('ftpPath', '.', false)); + } + + + foreach($data as $key => $row) { + $temp[] = $row; + } + }else{ + $sftp = new SFTP($request->get('ftpHost')); + if (!$sftp->login($request->get('ftpUsername'), $request->get('ftpPassword'))) { + return new JsonResponse(['success' => false, 'data' => $temp, 'count' => $count]); + } + + $data = $sftp->rawlist(); + foreach($data as $key => $row) { + $temp[] = ['type' => $row['type'], 'name' => utf8_encode($row['filename'])]; + } + $count = count($data); } return new JsonResponse(['success' => true, 'data' => $temp, 'count' => $count]); } -} \ No newline at end of file +} diff --git a/src/new/var/plugins/System/PSC/Ftp/Document/Ftp.php b/src/new/var/plugins/System/PSC/Ftp/Document/Ftp.php index 0d0b03c64..3e22de687 100755 --- a/src/new/var/plugins/System/PSC/Ftp/Document/Ftp.php +++ b/src/new/var/plugins/System/PSC/Ftp/Document/Ftp.php @@ -31,6 +31,9 @@ class Ftp #[Field(type: 'bool')] protected $passiv; + #[Field(type: 'bool')] + protected $sftp; + /** * @var string $username @@ -154,6 +157,12 @@ class Ftp return $this->passiv; } + public function isSftp() + { + return $this->sftp; + } + + /** * @param bool $passiv */ @@ -162,5 +171,10 @@ class Ftp $this->passiv = $passiv; } + public function setSftp($sftp) + { + $this->sftp = $sftp; + } -} \ No newline at end of file + +} diff --git a/src/new/var/plugins/System/PSC/Ftp/Plugin.php b/src/new/var/plugins/System/PSC/Ftp/Plugin.php index c8dd37bf6..5011422b2 100755 --- a/src/new/var/plugins/System/PSC/Ftp/Plugin.php +++ b/src/new/var/plugins/System/PSC/Ftp/Plugin.php @@ -5,7 +5,7 @@ use PSC\System\PluginBundle\Plugin\Base; class Plugin extends Base implements \PSC\System\PluginBundle\Interfaces\Plugin { - protected $name = 'FTP Queue'; + protected $name = 'FTP / SFTP Queue'; public function getType() { @@ -14,11 +14,11 @@ class Plugin extends Base implements \PSC\System\PluginBundle\Interfaces\Plugin public function getDescription() { - return 'Überträgt Pakete per FTP auf einen entfernten Rechner'; + return 'Überträgt Pakete per FTP / SFTP auf einen entfernten Rechner'; } public function getVersion() { return 1; } -} \ No newline at end of file +} diff --git a/src/new/var/plugins/System/PSC/Ftp/Queue/Ftp.php b/src/new/var/plugins/System/PSC/Ftp/Queue/Ftp.php index ca235b5cd..d5567f653 100755 --- a/src/new/var/plugins/System/PSC/Ftp/Queue/Ftp.php +++ b/src/new/var/plugins/System/PSC/Ftp/Queue/Ftp.php @@ -24,6 +24,7 @@ use Symfony\Component\Form\FormFactory; use Symfony\Component\Form\FormFactoryInterface; use Symfony\Component\HttpFoundation\StreamedResponse; use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorage; +use phpseclib3\Net\SFTP; class Ftp implements QueueInterface, ConfigurableElementInterface { @@ -53,7 +54,7 @@ class Ftp implements QueueInterface, ConfigurableElementInterface public function getDescription() { - return 'Verschiebt Package auf einen FTP Server'; + return 'Verschiebt Package auf einen FTP / SFTP Server'; } public function getGroup() @@ -63,11 +64,12 @@ class Ftp implements QueueInterface, ConfigurableElementInterface public function getName() { - return 'Ftp'; + return 'Ftp / SFTP'; } public function getForm(FormBuilderInterface $builder, $form_options, EventInterface $event) { + $builder->add("sftp", CheckboxType::class, array('label' => 'SFTP', 'required' => false)); $builder->add("host", TextType::class, array('label' => 'Server')); $builder->add("username", TextType::class, array('label' => 'Benutzer')); $builder->add("password", TextType::class, array('label' => 'Passwort')); @@ -80,6 +82,7 @@ class Ftp implements QueueInterface, ConfigurableElementInterface public function injectDocument(Form $form, EventInterface $event, Queue $objQueue) { $url = new \Plugin\System\PSC\Ftp\Document\Ftp(); + $url->setSftp($form->get('sftp')->getData()); $url->setHost($form->get('host')->getData()); $url->setUsername($form->get('username')->getData()); $url->setPassword($form->get('password')->getData()); @@ -92,6 +95,7 @@ class Ftp implements QueueInterface, ConfigurableElementInterface public function setFormData(Form $form, EventInterface $event, Queue $queueObj) { + $form->get('sftp')->setData($queueObj->getQueueDocument()->isSftp()); $form->get('host')->setData($queueObj->getQueueDocument()->getHost()); $form->get('username')->setData($queueObj->getQueueDocument()->getUsername()); $form->get('password')->setData($queueObj->getQueueDocument()->getPassword()); @@ -130,22 +134,42 @@ class Ftp implements QueueInterface, ConfigurableElementInterface ->findOneBy(['id' => new \MongoId($order[1])]); try { - $ftp = new \FtpClient\FtpClient(); + if($ftpDoc->isSftp()) { + + $sftp = new SFTP($ftpDoc->getHost()); + if ($sftp->login($ftpDoc->getUsername(), $ftpDoc->getPassword())) { + $sftp->chdir($ftpDoc->getPath()); + $sftp->put($upload->getFilename(), $upload->getFile()->getMongoGridFSFile()->getBytes(), SFTP::SOURCE_LOCAL_FILE); + } + $pc = new Uploaded(); + $pc->setOrder($eventDoc->getOrder()); + $pc->setShop($eventDoc->getShop()); + + $this->_eventManager->addJob($pc); + }else{ + $ftp = new \FtpClient\FtpClient(); + + if ($ftpDoc->getPort() == "") { + $ftp->connect($ftpDoc->getHost(), $ftpDoc->isSsl(), 21); + } else { + $ftp->connect($ftpDoc->getHost(), $ftpDoc->isSsl(), $ftpDoc->getPort()); + } + + $ftp->login($ftpDoc->getUsername(), $ftpDoc->getPassword()); + + $ftp->pasv($ftpDoc->isPassiv()); + + $ftp->chdir($ftpDoc->getPath()); + + $ftp->putFromString($upload->getFilename(), $upload->getFile()->getMongoGridFSFile()->getBytes()); + $pc = new Uploaded(); + $pc->setOrder($eventDoc->getOrder()); + $pc->setShop($eventDoc->getShop()); + + $this->_eventManager->addJob($pc); - if ($ftpDoc->getPort() == "") { - $ftp->connect($ftpDoc->getHost(), $ftpDoc->isSsl(), 21); - } else { - $ftp->connect($ftpDoc->getHost(), $ftpDoc->isSsl(), $ftpDoc->getPort()); } - $ftp->login($ftpDoc->getUsername(), $ftpDoc->getPassword()); - - $ftp->pasv($ftpDoc->isPassiv()); - - $ftp->chdir($ftpDoc->getPath()); - - $ftp->putFromString($upload->getFilename(), $upload->getFile()->getMongoGridFSFile()->getBytes()); - } catch (\Exception $e) { $this->_error = $e->getMessage(); return false; @@ -153,27 +177,41 @@ class Ftp implements QueueInterface, ConfigurableElementInterface }else { try { - $ftp = new \FtpClient\FtpClient(); + if($ftpDoc->isSftp()) { + + $sftp = new SFTP($ftpDoc->getHost()); + if ($sftp->login($ftpDoc->getUsername(), $ftpDoc->getPassword())) { + $sftp->chdir($ftpDoc->getPath()); - if ($ftpDoc->getPort() == "") { - $ftp->connect($ftpDoc->getHost(), $ftpDoc->isSsl(), 21); - } else { - $ftp->connect($ftpDoc->getHost(), $ftpDoc->isSsl(), $ftpDoc->getPort()); + $sftp->put(basename($eventDoc->getPath()), $eventDoc->getPath(), SFTP::SOURCE_LOCAL_FILE); + $pc = new Uploaded(); + $pc->setOrder($eventDoc->getOrder()); + $pc->setShop($eventDoc->getShop()); + + $this->_eventManager->addJob($pc); + } + }else{ + $ftp = new \FtpClient\FtpClient(); + + if ($ftpDoc->getPort() == "") { + $ftp->connect($ftpDoc->getHost(), $ftpDoc->isSsl(), 21); + } else { + $ftp->connect($ftpDoc->getHost(), $ftpDoc->isSsl(), $ftpDoc->getPort()); + } + + $ftp->login($ftpDoc->getUsername(), $ftpDoc->getPassword()); + + $ftp->pasv($ftpDoc->isPassiv()); + + $ftp->chdir($ftpDoc->getPath()); + $ftp->putFromPath($eventDoc->getPath()); + + $pc = new Uploaded(); + $pc->setOrder($eventDoc->getOrder()); + $pc->setShop($eventDoc->getShop()); + + $this->_eventManager->addJob($pc); } - - $ftp->login($ftpDoc->getUsername(), $ftpDoc->getPassword()); - - $ftp->pasv($ftpDoc->isPassiv()); - - $ftp->chdir($ftpDoc->getPath()); - $ftp->putFromPath($eventDoc->getPath()); - - $pc = new Uploaded(); - $pc->setOrder($eventDoc->getOrder()); - $pc->setShop($eventDoc->getShop()); - - $this->_eventManager->addJob($pc); - } catch (\Exception $e) { $this->_error = $e->getMessage(); return false; @@ -187,4 +225,4 @@ class Ftp implements QueueInterface, ConfigurableElementInterface { return $this->_error; } -} \ No newline at end of file +} diff --git a/src/new/var/plugins/System/PSC/Ftp/Resources/views/queue/ftp.html.twig b/src/new/var/plugins/System/PSC/Ftp/Resources/views/queue/ftp.html.twig index 08fe997dd..b54d0da08 100755 --- a/src/new/var/plugins/System/PSC/Ftp/Resources/views/queue/ftp.html.twig +++ b/src/new/var/plugins/System/PSC/Ftp/Resources/views/queue/ftp.html.twig @@ -1,9 +1,19 @@
-

Details FTP

+

Details

+
+
+
+ {{ form_label(form.sftp) }} +
+ {{ form_widget(form.sftp) }} +
+
+
+
@@ -78,14 +88,15 @@
- \ No newline at end of file +