mirror of
https://github.com/BookStackApp/BookStack.git
synced 2025-01-19 17:22:48 +08:00
ZIP Imports: Added parent and permission check pre-import
Some checks failed
analyse-php / build (push) Has been cancelled
lint-php / build (push) Has been cancelled
test-migrations / build (8.1) (push) Has been cancelled
test-migrations / build (8.2) (push) Has been cancelled
test-migrations / build (8.3) (push) Has been cancelled
test-php / build (8.1) (push) Has been cancelled
test-php / build (8.2) (push) Has been cancelled
test-php / build (8.3) (push) Has been cancelled
Some checks failed
analyse-php / build (push) Has been cancelled
lint-php / build (push) Has been cancelled
test-migrations / build (8.1) (push) Has been cancelled
test-migrations / build (8.2) (push) Has been cancelled
test-migrations / build (8.3) (push) Has been cancelled
test-php / build (8.1) (push) Has been cancelled
test-php / build (8.2) (push) Has been cancelled
test-php / build (8.3) (push) Has been cancelled
This commit is contained in:
parent
92cfde495e
commit
7b84558ca1
12
app/Exceptions/ZipImportException.php
Normal file
12
app/Exceptions/ZipImportException.php
Normal file
|
@ -0,0 +1,12 @@
|
|||
<?php
|
||||
|
||||
namespace BookStack\Exceptions;
|
||||
|
||||
class ZipImportException extends \Exception
|
||||
{
|
||||
public function __construct(
|
||||
public array $errors
|
||||
) {
|
||||
parent::__construct();
|
||||
}
|
||||
}
|
|
@ -65,8 +65,6 @@ class ImportController extends Controller
|
|||
{
|
||||
$import = $this->imports->findVisible($id);
|
||||
|
||||
// dd($import->decodeMetadata());
|
||||
|
||||
$this->setPageTitle(trans('entities.import_continue'));
|
||||
|
||||
return view('exports.import-show', [
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
namespace BookStack\Exports;
|
||||
|
||||
use BookStack\Entities\Queries\EntityQueries;
|
||||
use BookStack\Exceptions\FileUploadException;
|
||||
use BookStack\Exceptions\ZipExportException;
|
||||
use BookStack\Exceptions\ZipValidationException;
|
||||
|
@ -10,6 +11,7 @@ use BookStack\Exports\ZipExports\Models\ZipExportChapter;
|
|||
use BookStack\Exports\ZipExports\Models\ZipExportPage;
|
||||
use BookStack\Exports\ZipExports\ZipExportReader;
|
||||
use BookStack\Exports\ZipExports\ZipExportValidator;
|
||||
use BookStack\Exports\ZipExports\ZipImportRunner;
|
||||
use BookStack\Uploads\FileStorage;
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
use Symfony\Component\HttpFoundation\File\UploadedFile;
|
||||
|
@ -18,6 +20,8 @@ class ImportRepo
|
|||
{
|
||||
public function __construct(
|
||||
protected FileStorage $storage,
|
||||
protected ZipImportRunner $importer,
|
||||
protected EntityQueries $entityQueries,
|
||||
) {
|
||||
}
|
||||
|
||||
|
@ -54,13 +58,13 @@ class ImportRepo
|
|||
public function storeFromUpload(UploadedFile $file): Import
|
||||
{
|
||||
$zipPath = $file->getRealPath();
|
||||
$reader = new ZipExportReader($zipPath);
|
||||
|
||||
$errors = (new ZipExportValidator($zipPath))->validate();
|
||||
$errors = (new ZipExportValidator($reader))->validate();
|
||||
if ($errors) {
|
||||
throw new ZipValidationException($errors);
|
||||
}
|
||||
|
||||
$reader = new ZipExportReader($zipPath);
|
||||
$exportModel = $reader->decodeDataToExportModel();
|
||||
|
||||
$import = new Import();
|
||||
|
@ -90,11 +94,17 @@ class ImportRepo
|
|||
return $import;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws ZipValidationException
|
||||
*/
|
||||
public function runImport(Import $import, ?string $parent = null)
|
||||
{
|
||||
// TODO - Download import zip (if needed)
|
||||
// TODO - Validate zip file again
|
||||
// TODO - Check permissions before (create for main item, create for children, create for related items [image, attachments])
|
||||
$parentModel = null;
|
||||
if ($import->type === 'page' || $import->type === 'chapter') {
|
||||
$parentModel = $parent ? $this->entityQueries->findVisibleByStringIdentifier($parent) : null;
|
||||
}
|
||||
|
||||
return $this->importer->run($import, $parentModel);
|
||||
}
|
||||
|
||||
public function deleteImport(Import $import): void
|
||||
|
|
|
@ -10,20 +10,19 @@ use BookStack\Exports\ZipExports\Models\ZipExportPage;
|
|||
class ZipExportValidator
|
||||
{
|
||||
public function __construct(
|
||||
protected string $zipPath,
|
||||
protected ZipExportReader $reader,
|
||||
) {
|
||||
}
|
||||
|
||||
public function validate(): array
|
||||
{
|
||||
$reader = new ZipExportReader($this->zipPath);
|
||||
try {
|
||||
$importData = $reader->readData();
|
||||
$importData = $this->reader->readData();
|
||||
} catch (ZipExportException $exception) {
|
||||
return ['format' => $exception->getMessage()];
|
||||
}
|
||||
|
||||
$helper = new ZipValidationHelper($reader);
|
||||
$helper = new ZipValidationHelper($this->reader);
|
||||
|
||||
if (isset($importData['book'])) {
|
||||
$modelErrors = ZipExportBook::validate($helper, $importData['book']);
|
||||
|
|
143
app/Exports/ZipExports/ZipImportRunner.php
Normal file
143
app/Exports/ZipExports/ZipImportRunner.php
Normal file
|
@ -0,0 +1,143 @@
|
|||
<?php
|
||||
|
||||
namespace BookStack\Exports\ZipExports;
|
||||
|
||||
use BookStack\Entities\Models\Book;
|
||||
use BookStack\Entities\Models\Chapter;
|
||||
use BookStack\Entities\Models\Entity;
|
||||
use BookStack\Exceptions\ZipExportException;
|
||||
use BookStack\Exceptions\ZipImportException;
|
||||
use BookStack\Exports\Import;
|
||||
use BookStack\Exports\ZipExports\Models\ZipExportBook;
|
||||
use BookStack\Exports\ZipExports\Models\ZipExportChapter;
|
||||
use BookStack\Exports\ZipExports\Models\ZipExportPage;
|
||||
use BookStack\Uploads\FileStorage;
|
||||
|
||||
class ZipImportRunner
|
||||
{
|
||||
public function __construct(
|
||||
protected FileStorage $storage,
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws ZipImportException
|
||||
*/
|
||||
public function run(Import $import, ?Entity $parent = null): void
|
||||
{
|
||||
$zipPath = $this->getZipPath($import);
|
||||
$reader = new ZipExportReader($zipPath);
|
||||
|
||||
$errors = (new ZipExportValidator($reader))->validate();
|
||||
if ($errors) {
|
||||
throw new ZipImportException(["ZIP failed to validate"]);
|
||||
}
|
||||
|
||||
try {
|
||||
$exportModel = $reader->decodeDataToExportModel();
|
||||
} catch (ZipExportException $e) {
|
||||
throw new ZipImportException([$e->getMessage()]);
|
||||
}
|
||||
|
||||
// Validate parent type
|
||||
if ($exportModel instanceof ZipExportBook && ($parent !== null)) {
|
||||
throw new ZipImportException(["Must not have a parent set for a Book import"]);
|
||||
} else if ($exportModel instanceof ZipExportChapter && (!$parent instanceof Book)) {
|
||||
throw new ZipImportException(["Parent book required for chapter import"]);
|
||||
} else if ($exportModel instanceof ZipExportPage && !($parent instanceof Book || $parent instanceof Chapter)) {
|
||||
throw new ZipImportException(["Parent book or chapter required for page import"]);
|
||||
}
|
||||
|
||||
$this->ensurePermissionsPermitImport($exportModel);
|
||||
|
||||
// TODO - Run import
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws ZipImportException
|
||||
*/
|
||||
protected function ensurePermissionsPermitImport(ZipExportPage|ZipExportChapter|ZipExportBook $exportModel, Book|Chapter|null $parent = null): void
|
||||
{
|
||||
$errors = [];
|
||||
|
||||
// TODO - Extract messages to language files
|
||||
// TODO - Ensure these are shown to users on failure
|
||||
|
||||
$chapters = [];
|
||||
$pages = [];
|
||||
$images = [];
|
||||
$attachments = [];
|
||||
|
||||
if ($exportModel instanceof ZipExportBook) {
|
||||
if (!userCan('book-create-all')) {
|
||||
$errors[] = 'You are lacking the required permission to create books.';
|
||||
}
|
||||
array_push($pages, ...$exportModel->pages);
|
||||
array_push($chapters, ...$exportModel->chapters);
|
||||
} else if ($exportModel instanceof ZipExportChapter) {
|
||||
$chapters[] = $exportModel;
|
||||
} else if ($exportModel instanceof ZipExportPage) {
|
||||
$pages[] = $exportModel;
|
||||
}
|
||||
|
||||
foreach ($chapters as $chapter) {
|
||||
array_push($pages, ...$chapter->pages);
|
||||
}
|
||||
|
||||
if (count($chapters) > 0) {
|
||||
$permission = 'chapter-create' . ($parent ? '' : '-all');
|
||||
if (!userCan($permission, $parent)) {
|
||||
$errors[] = 'You are lacking the required permission to create chapters.';
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($pages as $page) {
|
||||
array_push($attachments, ...$page->attachments);
|
||||
array_push($images, ...$page->images);
|
||||
}
|
||||
|
||||
if (count($pages) > 0) {
|
||||
if ($parent) {
|
||||
if (!userCan('page-create', $parent)) {
|
||||
$errors[] = 'You are lacking the required permission to create pages.';
|
||||
}
|
||||
} else {
|
||||
$hasPermission = userCan('page-create-all') || userCan('page-create-own');
|
||||
if (!$hasPermission) {
|
||||
$errors[] = 'You are lacking the required permission to create pages.';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (count($images) > 0) {
|
||||
if (!userCan('image-create-all')) {
|
||||
$errors[] = 'You are lacking the required permissions to create images.';
|
||||
}
|
||||
}
|
||||
|
||||
if (count($attachments) > 0) {
|
||||
if (userCan('attachment-create-all')) {
|
||||
$errors[] = 'You are lacking the required permissions to create attachments.';
|
||||
}
|
||||
}
|
||||
|
||||
if (count($errors)) {
|
||||
throw new ZipImportException($errors);
|
||||
}
|
||||
}
|
||||
|
||||
protected function getZipPath(Import $import): string
|
||||
{
|
||||
if (!$this->storage->isRemote()) {
|
||||
return $this->storage->getSystemPath($import->path);
|
||||
}
|
||||
|
||||
$tempFilePath = tempnam(sys_get_temp_dir(), 'bszip-import-');
|
||||
$tempFile = fopen($tempFilePath, 'wb');
|
||||
$stream = $this->storage->getReadStream($import->path);
|
||||
stream_copy_to_stream($stream, $tempFile);
|
||||
fclose($tempFile);
|
||||
|
||||
return $tempFilePath;
|
||||
}
|
||||
}
|
|
@ -5,6 +5,7 @@ namespace BookStack\Uploads;
|
|||
use BookStack\Exceptions\FileUploadException;
|
||||
use Exception;
|
||||
use Illuminate\Contracts\Filesystem\Filesystem as Storage;
|
||||
use Illuminate\Filesystem\FilesystemAdapter;
|
||||
use Illuminate\Filesystem\FilesystemManager;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Support\Str;
|
||||
|
@ -70,6 +71,26 @@ class FileStorage
|
|||
return $filePath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether the configured storage is remote from the host of this app.
|
||||
*/
|
||||
public function isRemote(): bool
|
||||
{
|
||||
return $this->getStorageDiskName() === 's3';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the actual path on system for the given relative file path.
|
||||
*/
|
||||
public function getSystemPath(string $filePath): string
|
||||
{
|
||||
if ($this->isRemote()) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return storage_path('uploads/files/' . ltrim($this->adjustPathForStorageDisk($filePath), '/'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the storage that will be used for storing files.
|
||||
*/
|
||||
|
@ -83,7 +104,7 @@ class FileStorage
|
|||
*/
|
||||
protected function getStorageDiskName(): string
|
||||
{
|
||||
$storageType = config('filesystems.attachments');
|
||||
$storageType = trim(strtolower(config('filesystems.attachments')));
|
||||
|
||||
// Change to our secure-attachment disk if any of the local options
|
||||
// are used to prevent escaping that location.
|
||||
|
|
Loading…
Reference in New Issue
Block a user