Skip to content
Closed
2 changes: 2 additions & 0 deletions app/Activity/ActivityType.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ class ActivityType
const PAGE_DELETE = 'page_delete';
const PAGE_RESTORE = 'page_restore';
const PAGE_MOVE = 'page_move';
const PAGE_ENCRYPTED = 'page_encrypted';
const PAGE_DECRYPTED = 'page_decrypted';

const CHAPTER_CREATE = 'chapter_create';
const CHAPTER_UPDATE = 'chapter_update';
Expand Down
7 changes: 7 additions & 0 deletions app/App/HomeController.php
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,16 @@ public function index(
$recents = $this->isSignedIn() ?
$recentlyViewed->run(12 * $recentFactor, 1)
: $this->queries->books->visibleForList()->orderBy('created_at', 'desc')->take(12 * $recentFactor)->get();
$recents = $recents->filter(function ($recent) {
if ($recent instanceof Page) {
return !$recent->is_encrypted;
}
return true;
});
$favourites = $topFavourites->run(6);
$recentlyUpdatedPages = $this->queries->pages->visibleForList()
->where('draft', false)
->where('is_encrypted', false)
->orderBy('updated_at', 'desc')
->take($favourites->count() > 0 ? 5 : 10)
->get();
Expand Down
57 changes: 57 additions & 0 deletions app/Entities/Controllers/PageController.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
use Exception;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Hash;
use Illuminate\Validation\ValidationException;
use Throwable;

Expand Down Expand Up @@ -198,6 +199,10 @@ public function edit(Request $request, string $bookSlug, string $pageSlug)
$page = $this->queries->findVisibleBySlugsOrFail($bookSlug, $pageSlug);
$this->checkOwnablePermission('page-update', $page);

if (session()->get('is_decrypt') == 'FOR_EDIT') {
$page->html = decrypt($page->html);
}

$editorData = new PageEditorData($page, $this->entityQueries, $request->query('editor', ''));
if ($editorData->getWarnings()) {
$this->showWarningNotification(implode("\n", $editorData->getWarnings()));
Expand All @@ -222,6 +227,11 @@ public function update(Request $request, string $bookSlug, string $pageSlug)
$page = $this->queries->findVisibleBySlugsOrFail($bookSlug, $pageSlug);
$this->checkOwnablePermission('page-update', $page);

if (session()->get('is_decrypt') == 'FOR_EDIT') {
$request['html'] = encrypt($request['html']);
session()->put('is_decrypt', 'NONE');
}

$this->pageRepo->update($page, $request->all());

return redirect($page->getUrl());
Expand Down Expand Up @@ -467,4 +477,51 @@ public function copy(Request $request, Cloner $cloner, string $bookSlug, string

return redirect($pageCopy->getUrl());
}

public function encrypt(Request $request, string $bookSlug, string $pageSlug)
{
$page = $this->queries->findVisibleBySlugsOrFail($bookSlug, $pageSlug);
return $this->pageRepo->encryptPageContent($request->all(), $page);
}

public function decrypt(Request $request, string $bookSlug, string $pageSlug)
{
$page = $this->queries->findVisibleBySlugsOrFail($bookSlug, $pageSlug);
return $this->pageRepo->decryptPageContent($request->all(), $page);
}

public function updateEncryption(Request $request, string $bookSlug, string $pageSlug)
{
$this->validate($request, [
'html' => ['required'],
'password' => ['required'],
'is_encrypted' => ['required','boolean'],
]);

$page = $this->queries->findVisibleBySlugsOrFail($bookSlug, $pageSlug);
$this->pageRepo->update($page, $request->all());
return response()->json(['message' => 'Encryption Update SuccessFully','success' => true]);
}

public function updateDecryption(Request $request, string $bookSlug, string $pageSlug)
{
$page = $this->queries->findVisibleBySlugsOrFail($bookSlug, $pageSlug);
if ($this->validateDecryptPassword($request, $bookSlug, $pageSlug)) {
if ($request->has('is_decrypt')) {
session()->put('is_decrypt', $request->get('is_decrypt'));
}
$this->pageRepo->update($page, $request->all());
return response()->json(['contents' => [],'message' => 'Decrypted SuccessFully','success' => true]);
} else {
return response()->json(['contents' => [],'message' => 'Decrypt Password is Wrong','success' => false]);
}
}

public function validateDecryptPassword(Request $request, string $bookSlug, string $pageSlug)
{
$page = $this->queries->findVisibleBySlugsOrFail($bookSlug, $pageSlug);
return Hash::check($request->get('password'), $page->password) ?
response()->json(['success' => true]) :
response()->json(['success' => false]);
}
}
21 changes: 20 additions & 1 deletion app/Entities/Controllers/PageExportController.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

namespace BookStack\Entities\Controllers;

use BookStack\Entities\Models\Page;
use BookStack\Entities\Queries\PageQueries;
use BookStack\Entities\Tools\ExportFormatter;
use BookStack\Entities\Tools\PageContent;
Expand All @@ -28,6 +29,7 @@ public function __construct(
public function pdf(string $bookSlug, string $pageSlug)
{
$page = $this->queries->findVisibleBySlugsOrFail($bookSlug, $pageSlug);
$this->validatePageEncrypted($page);
$page->html = (new PageContent($page))->render();
$pdfContent = $this->exportFormatter->pageToPdf($page);

Expand All @@ -43,6 +45,7 @@ public function pdf(string $bookSlug, string $pageSlug)
public function html(string $bookSlug, string $pageSlug)
{
$page = $this->queries->findVisibleBySlugsOrFail($bookSlug, $pageSlug);
$this->validatePageEncrypted($page);
$page->html = (new PageContent($page))->render();
$containedHtml = $this->exportFormatter->pageToContainedHtml($page);

Expand All @@ -57,7 +60,8 @@ public function html(string $bookSlug, string $pageSlug)
public function plainText(string $bookSlug, string $pageSlug)
{
$page = $this->queries->findVisibleBySlugsOrFail($bookSlug, $pageSlug);
$pageText = $this->exportFormatter->pageToPlainText($page);
$this->validatePageEncrypted($page);
$pageText = $this->exportFormatter->pageToPlainText($page, $page->is_encrypted);

return $this->download()->directly($pageText, $pageSlug . '.txt');
}
Expand All @@ -70,8 +74,23 @@ public function plainText(string $bookSlug, string $pageSlug)
public function markdown(string $bookSlug, string $pageSlug)
{
$page = $this->queries->findVisibleBySlugsOrFail($bookSlug, $pageSlug);
$this->validatePageEncrypted($page);
$pageText = $this->exportFormatter->pageToMarkdown($page);

return $this->download()->directly($pageText, $pageSlug . '.md');
}

public function validatePageEncrypted(Page $page)
{
if ($page->is_encrypted) {
if (session()->get('is_decrypt') == 'FOR_EXPORT') {
$page->html = decrypt($page->html);
session()->put('is_decrypt', 'NONE');
return true;
} else {
return redirect($page->getUrl());
}
}
return true;
}
}
2 changes: 1 addition & 1 deletion app/Entities/Models/Page.php
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ class Page extends BookChild
{
use HasFactory;

protected $fillable = ['name', 'priority'];
protected $fillable = ['name', 'priority','is_encrypted','password'];

public string $textField = 'text';
public string $htmlField = 'html';
Expand Down
4 changes: 2 additions & 2 deletions app/Entities/Queries/PageQueries.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,11 @@ class PageQueries implements ProvidesEntityQueries
protected static array $contentAttributes = [
'name', 'id', 'slug', 'book_id', 'chapter_id', 'draft',
'template', 'html', 'text', 'created_at', 'updated_at', 'priority',
'created_by', 'updated_by', 'owned_by',
'created_by', 'updated_by', 'owned_by', 'is_encrypted',
];
protected static array $listAttributes = [
'name', 'id', 'slug', 'book_id', 'chapter_id', 'draft',
'template', 'text', 'created_at', 'updated_at', 'priority', 'owned_by',
'template', 'text', 'created_at', 'updated_at', 'priority', 'owned_by', 'is_encrypted',
];

public function start(): Builder
Expand Down
5 changes: 4 additions & 1 deletion app/Entities/Repos/BaseRepo.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
use BookStack\Entities\Models\Entity;
use BookStack\Entities\Models\HasCoverImage;
use BookStack\Entities\Models\HasHtmlDescription;
use BookStack\Entities\Models\Page;
use BookStack\Entities\Queries\PageQueries;
use BookStack\Exceptions\ImageUploadException;
use BookStack\References\ReferenceStore;
Expand Down Expand Up @@ -75,7 +76,9 @@ public function update(Entity $entity, array $input)
}

$entity->rebuildPermissions();
$entity->indexForSearch();
if ((array_key_exists('is_encrypted', $input) && $input['is_encrypted'] == true) || !(($entity instanceof Page) && $entity->is_encrypted == true)) {
$entity->indexForSearch();
}
$this->referenceStore->updateForEntity($entity);

if ($oldUrl !== $entity->getUrl()) {
Expand Down
39 changes: 38 additions & 1 deletion app/Entities/Repos/PageRepo.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@
use BookStack\References\ReferenceStore;
use BookStack\References\ReferenceUpdater;
use Exception;
use Illuminate\Support\Facades\Hash;
use Str;

class PageRepo
{
Expand Down Expand Up @@ -97,6 +99,11 @@ public function update(Page $page, array $input): Page
$oldName = $page->name;
$oldMarkdown = $page->markdown;

//Make Hashable decrypt Password
if (array_key_exists('password', $input)) {
$input['password'] = Hash::make($input['password']);
}

$this->updateTemplateStatusAndContentFromInput($page, $input);
$this->baseRepo->update($page, $input);

Expand All @@ -116,7 +123,15 @@ public function update(Page $page, array $input): Page
$this->revisionRepo->storeNewForPage($page, $summary);
}

Activity::add(ActivityType::PAGE_UPDATE, $page);
if (array_key_exists('is_encrypted', $input)) {
if ($input['is_encrypted'] == true) {
Activity::add(ActivityType::PAGE_ENCRYPTED, $page);
} else if ($input['is_encrypted'] == false) {
Activity::add(ActivityType::PAGE_DECRYPTED, $page);
}
} else if (!array_key_exists('is_decrypt', $input)) {
Activity::add(ActivityType::PAGE_UPDATE, $page);
}

return $page;
}
Expand Down Expand Up @@ -279,4 +294,26 @@ protected function getNewPriority(Page $page): int

return (new BookContents($page->book))->getLastPriority() + 1;
}

public function encryptPageContent(array $data, Page $page)
{
if (!$page->is_encrypted) {
$content = encrypt($data['content']);
return response()->json(['content' => $content,'message' => 'Encrypted SuccessFully','success' => true]);
} else {
return response()->json(['content' => '','message' => 'Already Encrypted','success' => false]);
}
}

public function decryptPageContent(array $data, Page $page)
{
if ($page->is_encrypted) {
if (Hash::check($data['password'], $page->password)) {
$content = decrypt($data['content']);
return response()->json(['content' => $content,'message' => 'Decrypted SuccessFully','success' => true]);
} else {
return response()->json(['contents' => [],'message' => 'Decrypt Password is Wrong','success' => false]);
}
}
}
}
6 changes: 6 additions & 0 deletions app/Entities/Tools/BookContents.php
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,12 @@ public function getLastPriority(): int
public function getTree(bool $showDrafts = false, bool $renderPages = false): Collection
{
$pages = $this->getPages($showDrafts, $renderPages);

// Check For Encrypted Pages
$pages->each(function ($page) {
$page->html = $page->is_encrypted ? '<p>This page is encrypted</p>' : $page->html;
});

$chapters = $this->book->chapters()->scopes('visible')->get();
$all = collect()->concat($pages)->concat($chapters);
$chapterMap = $chapters->keyBy('id');
Expand Down
18 changes: 16 additions & 2 deletions app/Entities/Tools/ExportFormatter.php
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ public function pageToContainedHtml(Page $page): string
'format' => 'html',
'cspContent' => $this->cspService->getCspMetaTagValue(),
'locale' => user()->getLocale(),
'export' => true,
])->render();

return $this->containHtml($pageHtml);
Expand All @@ -50,7 +51,7 @@ public function chapterToContainedHtml(Chapter $chapter): string
{
$pages = $chapter->getVisiblePages();
$pages->each(function ($page) {
$page->html = (new PageContent($page))->render();
$page->html = $page->is_encrypted ? "<p>This page is Encrypted</p>" : (new PageContent($page))->render();
});
$html = view('exports.chapter', [
'chapter' => $chapter,
Expand Down Expand Up @@ -95,6 +96,7 @@ public function pageToPdf(Page $page): string
'format' => 'pdf',
'engine' => $this->pdfGenerator->getActiveEngine(),
'locale' => user()->getLocale(),
'export' => true,
])->render();

return $this->htmlToPdf($html);
Expand All @@ -109,7 +111,7 @@ public function chapterToPdf(Chapter $chapter): string
{
$pages = $chapter->getVisiblePages();
$pages->each(function ($page) {
$page->html = (new PageContent($page))->render();
$page->html = $page->is_encrypted ? "<p>This page is Encrypted</p>" : (new PageContent($page))->render();
});

$html = view('exports.chapter', [
Expand Down Expand Up @@ -270,6 +272,9 @@ public function chapterToPlainText(Chapter $chapter): string

$parts = [];
foreach ($chapter->getVisiblePages() as $page) {
if ($page->is_encrypted) {
$page->html = "<p>This page is Encrypted</p>";
}
$parts[] = $this->pageToPlainText($page, false, true);
}

Expand All @@ -290,6 +295,9 @@ public function bookToPlainText(Book $book): string
if ($bookChild->isA('chapter')) {
$parts[] = $this->chapterToPlainText($bookChild);
} else {
if ($bookChild->is_encrypted) {
$bookChild->html = "<p>This page is Encrypted</p>";
}
$parts[] = $this->pageToPlainText($bookChild, true, true);
}
}
Expand Down Expand Up @@ -317,6 +325,9 @@ public function chapterToMarkdown(Chapter $chapter): string
$text = '# ' . $chapter->name . "\n\n";
$text .= $chapter->description . "\n\n";
foreach ($chapter->pages as $page) {
if ($page->is_encrypted) {
$page->html = "<p>This page is Encrypted</p>";
}
$text .= $this->pageToMarkdown($page) . "\n\n";
}

Expand All @@ -334,6 +345,9 @@ public function bookToMarkdown(Book $book): string
if ($bookChild instanceof Chapter) {
$text .= $this->chapterToMarkdown($bookChild) . "\n\n";
} else {
if ($bookChild->is_encrypted) {
$bookChild->html = "<p>This page is Encrypted</p>";
}
$text .= $this->pageToMarkdown($bookChild) . "\n\n";
}
}
Expand Down
2 changes: 1 addition & 1 deletion app/Permissions/Models/EntityPermission.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
*/
class EntityPermission extends Model
{
public const PERMISSIONS = ['view', 'create', 'update', 'delete'];
public const PERMISSIONS = ['view', 'create', 'update', 'delete', 'encrypt'];

protected $fillable = ['role_id', 'view', 'create', 'update', 'delete'];
public $timestamps = false;
Expand Down
3 changes: 3 additions & 0 deletions app/Search/SearchRunner.php
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,9 @@ protected function buildQuery(SearchOptions $searchOpts, string $entityType): El
$inputTerm = str_replace('\\', '\\\\', $exact->value);
$query->where('name', 'like', '%' . $inputTerm . '%')
->orWhere($entityModelInstance->textField, 'like', '%' . $inputTerm . '%');
if ($entityModelInstance instanceof Page) {
$query->Where('is_encrypted', '!=', 1);
}
};

$exact->negated ? $entityQuery->whereNot($filter) : $entityQuery->where($filter);
Expand Down
2 changes: 1 addition & 1 deletion app/Uploads/ImageService.php
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ public function saveNew(string $imageName, string $imageData, string $type, int
'type' => $type,
'uploaded_to' => $uploadedTo,
];

dump($this->storage->getPublicUrl($fullPath));
if (user()->id !== 0) {
$userId = user()->id;
$imageDetails['created_by'] = $userId;
Expand Down
Empty file modified database/.gitignore
100644 → 100755
Empty file.
Loading
Loading