Add SFTP adapter storage location support (#5307)

This commit is contained in:
Vaalyn 2022-04-23 18:45:47 +02:00 committed by GitHub
parent ab0fee4f78
commit 15f8fffc52
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 250 additions and 18 deletions

2
composer.lock generated
View File

@ -14248,5 +14248,5 @@
"ext-xmlwriter": "*"
},
"platform-dev": [],
"plugin-api-version": "2.3.0"
"plugin-api-version": "2.1.0"
}

View File

@ -116,6 +116,9 @@ export default {
case 'dropbox':
return this.$gettext('Remote: Dropbox');
case 'sftp':
return this.$gettext('Remote: SFTP');
}
},
getSpaceUsed(item) {

View File

@ -31,42 +31,46 @@ export default {
let validations = {
form: {
'adapter': {required},
'storageQuota': {}
'storageQuota': {},
'path': {},
's3CredentialKey': {},
's3CredentialSecret': {},
's3Region': {},
's3Version': {},
's3Bucket': {},
's3Endpoint': {},
'dropboxAuthToken': {},
'sftpHost': {},
'sftpPort': {},
'sftpUsername': {},
'sftpPassword': {},
'sftpPrivateKey': {},
'sftpPrivateKeyPassPhrase': {}
}
};
switch (this.form.adapter) {
case 'local':
validations.form.path = {required};
validations.form.s3CredentialKey = {};
validations.form.s3CredentialSecret = {};
validations.form.s3Region = {};
validations.form.s3Version = {};
validations.form.s3Bucket = {};
validations.form.s3Endpoint = {};
validations.form.dropboxAuthToken = {};
break;
case 'dropbox':
validations.form.path = {};
validations.form.s3CredentialKey = {};
validations.form.s3CredentialSecret = {};
validations.form.s3Region = {};
validations.form.s3Version = {};
validations.form.s3Bucket = {};
validations.form.s3Endpoint = {};
validations.form.dropboxAuthToken = { required };
break;
case 's3':
validations.form.path = {};
validations.form.s3CredentialKey = { required };
validations.form.s3CredentialSecret = { required };
validations.form.s3Region = { required };
validations.form.s3Version = { required };
validations.form.s3Bucket = { required };
validations.form.s3Endpoint = { required };
validations.form.dropboxAuthToken = {};
break;
case 'sftp':
validations.form.sftpHost = { required };
validations.form.sftpPort = { required };
validations.form.sftpUsername = { required };
break;
}
@ -84,6 +88,12 @@ export default {
's3Bucket': null,
's3Endpoint': null,
'dropboxAuthToken': null,
'sftpHost': null,
'sftpPort': '22',
'sftpUsername': null,
'sftpPassword': null,
'sftpPrivateKey': null,
'sftpPrivateKeyPassPhrase': null,
'storageQuota': ''
};
},

View File

@ -17,6 +17,9 @@
<b-form-radio value="dropbox">
<translate key="lang_form_adapter_dropbox">Remote: Dropbox</translate>
</b-form-radio>
<b-form-radio value="sftp">
<translate key="lang_form_adapter_sftp">Remote: SFTP</translate>
</b-form-radio>
</b-form-radio-group>
</template>
</b-wrapped-form-group>
@ -117,6 +120,61 @@
</b-form-group>
</b-card-body>
</b-card>
<b-card v-show="form.adapter.$model === 'sftp'" class="mb-3" no-body>
<div class="card-header bg-primary-dark">
<h2 class="card-title">
<translate key="lang_form_adapter_sftp">Remote: SFTP</translate>
</h2>
</div>
<b-card-body>
<b-form-group>
<b-form-row>
<b-wrapped-form-group class="col-md-12 col-lg-6" id="form_edit_sftpHost"
:field="form.sftpHost">
<template #label="{lang}">
<translate :key="lang">SFTP Host</translate>
</template>
</b-wrapped-form-group>
<b-wrapped-form-group class="col-md-12 col-lg-6" id="form_edit_sftpPort" input-type="number" min="1" step="1"
:field="form.sftpPort">
<template #label="{lang}">
<translate :key="lang">SFTP Port</translate>
</template>
</b-wrapped-form-group>
<b-wrapped-form-group class="col-md-12 col-lg-6" id="form_edit_sftpUsername"
:field="form.sftpUsername">
<template #label="{lang}">
<translate :key="lang">SFTP Username</translate>
</template>
</b-wrapped-form-group>
<b-wrapped-form-group class="col-md-12 col-lg-6" id="form_edit_sftpPassword"
:field="form.sftpPassword">
<template #label="{lang}">
<translate :key="lang">SFTP Password</translate>
</template>
</b-wrapped-form-group>
<b-wrapped-form-group class="col-md-12" id="form_edit_sftpPrivateKeyPassPhrase"
:field="form.sftpPrivateKeyPassPhrase">
<template #label="{lang}">
<translate :key="lang">SFTP Private Key Pass Phrase</translate>
</template>
</b-wrapped-form-group>
<b-wrapped-form-group class="col-md-12" id="form_edit_sftpPrivateKey" input-type="textarea"
:field="form.sftpPrivateKey">
<template #label="{lang}">
<translate :key="lang">SFTP Private Key</translate>
</template>
</b-wrapped-form-group>
</b-form-row>
</b-form-group>
</b-card-body>
</b-card>
</div>
</template>

View File

@ -74,6 +74,40 @@ class StorageLocation
)]
public ?string $s3Endpoint = null;
#[OA\Property(
description: 'The host for SFTP adapters',
example: '127.0.0.1'
)]
public ?string $sftpHost = null;
#[OA\Property(
description: 'The username for SFTP adapters',
example: 'root'
)]
public ?string $sftpUsername = null;
#[OA\Property(
description: 'The password for SFTP adapters',
example: 'abc123'
)]
public ?string $sftpPassword = null;
#[OA\Property(
description: 'The port for SFTP adapters',
example: 20
)]
public ?int $sftpPort = null;
#[OA\Property(
description: 'The private key for SFTP adapters'
)]
public ?string $sftpPrivateKey = null;
#[OA\Property(
description: 'The private key pass phrase for SFTP adapters'
)]
public ?string $sftpPrivateKeyPassPhrase = null;
#[OA\Property(example: '50 GB')]
public ?string $storageQuota = null;

View File

@ -11,6 +11,7 @@ enum StorageLocationAdapters: string
case Local = 'local';
case S3 = 's3';
case Dropbox = 'dropbox';
case Sftp = 'sftp';
public function isLocal(): bool
{
@ -23,6 +24,7 @@ enum StorageLocationAdapters: string
self::Local => 'Local',
self::S3 => 'S3',
self::Dropbox => 'Dropbox',
self::Sftp => 'SFTP',
};
}
}

View File

@ -0,0 +1,26 @@
<?php
declare(strict_types=1);
namespace App\Entity\Migration;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
final class Version20220414214828 extends AbstractMigration
{
public function getDescription(): string
{
return 'Add SFTP details to storage_location table.';
}
public function up(Schema $schema): void
{
$this->addSql('ALTER TABLE storage_location ADD sftp_host VARCHAR(255) DEFAULT NULL, ADD sftp_username VARCHAR(255) DEFAULT NULL, ADD sftp_password VARCHAR(255) DEFAULT NULL, ADD sftp_port INT DEFAULT NULL, ADD sftp_private_key LONGTEXT DEFAULT NULL, ADD sftp_private_key_pass_phrase VARCHAR(255) DEFAULT NULL');
}
public function down(Schema $schema): void
{
$this->addSql('ALTER TABLE storage_location DROP sftp_host, DROP sftp_username, DROP sftp_password, DROP sftp_port, DROP sftp_private_key, DROP sftp_private_key_pass_phrase');
}
}

View File

@ -16,6 +16,7 @@ use Azura\Files\Adapter\Dropbox\DropboxAdapter;
use Azura\Files\Adapter\ExtendedAdapterInterface;
use Azura\Files\Adapter\Local\LocalFilesystemAdapter;
use Azura\Files\Adapter\LocalAdapterInterface;
use Azura\Files\Adapter\Sftp\SftpAdapter;
use Azura\Files\ExtendedFilesystemInterface;
use Azura\Files\LocalFilesystem;
use Azura\Files\RemoteFilesystem;
@ -24,6 +25,7 @@ use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
use InvalidArgumentException;
use League\Flysystem\PhpseclibV3\SftpConnectionProvider;
use League\Flysystem\UnixVisibility\PortableVisibilityConverter;
use League\Flysystem\Visibility;
use RuntimeException;
@ -73,6 +75,24 @@ class StorageLocation implements Stringable, IdentifiableEntityInterface
#[ORM\Column(name: 'dropbox_auth_token', length: 255, nullable: true)]
protected ?string $dropboxAuthToken = null;
#[ORM\Column(name: 'sftp_host', length: 255, nullable: true)]
protected ?string $sftpHost = null;
#[ORM\Column(name: 'sftp_username', length: 255, nullable: true)]
protected ?string $sftpUsername = null;
#[ORM\Column(name: 'sftp_password', length: 255, nullable: true)]
protected ?string $sftpPassword = null;
#[ORM\Column(name: 'sftp_port', nullable: true)]
protected ?int $sftpPort = null;
#[ORM\Column(name: 'sftp_private_key', type: 'text', nullable: true)]
protected ?string $sftpPrivateKey = null;
#[ORM\Column(name: 'sftp_private_key_pass_phrase', length: 255, nullable: true)]
protected ?string $sftpPrivateKeyPassPhrase = null;
#[ORM\Column(name: 'storage_quota', type: 'bigint', nullable: true)]
protected ?string $storageQuota = null;
@ -217,6 +237,66 @@ class StorageLocation implements Stringable, IdentifiableEntityInterface
$this->dropboxAuthToken = $dropboxAuthToken;
}
public function getSftpHost(): ?string
{
return $this->sftpHost;
}
public function setSftpHost(?string $sftpHost): void
{
$this->sftpHost = $sftpHost;
}
public function getSftpUsername(): ?string
{
return $this->sftpUsername;
}
public function setSftpUsername(?string $sftpUsername): void
{
$this->sftpUsername = $sftpUsername;
}
public function getSftpPassword(): ?string
{
return $this->sftpPassword;
}
public function setSftpPassword(?string $sftpPassword): void
{
$this->sftpPassword = $sftpPassword;
}
public function getSftpPort(): ?int
{
return $this->sftpPort;
}
public function setSftpPort(?int $sftpPort): void
{
$this->sftpPort = $sftpPort;
}
public function getSftpPrivateKey(): ?string
{
return $this->sftpPrivateKey;
}
public function setSftpPrivateKey(?string $sftpPrivateKey): void
{
$this->sftpPrivateKey = $sftpPrivateKey;
}
public function getSftpPrivateKeyPassPhrase(): ?string
{
return $this->sftpPrivateKeyPassPhrase;
}
public function setSftpPrivateKeyPassPhrase(?string $sftpPrivateKeyPassPhrase): void
{
$this->sftpPrivateKeyPassPhrase = $sftpPrivateKeyPassPhrase;
}
public function isLocal(): bool
{
return $this->getAdapterEnum()->isLocal();
@ -453,6 +533,9 @@ class StorageLocation implements Stringable, IdentifiableEntityInterface
case StorageLocationAdapters::Dropbox:
return new DropboxAdapter($this->getDropboxClient(), $filteredPath);
case StorageLocationAdapters::Sftp:
return new SftpAdapter($this->getSftpConnectionProvider(), $filteredPath);
default:
return new LocalFilesystemAdapter($filteredPath);
}
@ -487,6 +570,22 @@ class StorageLocation implements Stringable, IdentifiableEntityInterface
return new Client($this->dropboxAuthToken);
}
protected function getSftpConnectionProvider(): SftpConnectionProvider
{
if (StorageLocationAdapters::Sftp !== $this->getAdapterEnum()) {
throw new InvalidArgumentException('This storage location is not using the SFTP adapter.');
}
return new SftpConnectionProvider(
$this->sftpHost ?? '',
$this->sftpUsername ?? '',
$this->sftpPassword,
$this->sftpPrivateKey,
$this->sftpPrivateKeyPassPhrase,
$this->sftpPort ?? 22
);
}
public function getFilesystem(): ExtendedFilesystemInterface
{
$adapter = $this->getStorageAdapter();