Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/pages encryption #42

Open
wants to merge 10 commits into
base: development
Choose a base branch
from
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