Skip to content

Commit

Permalink
feat(files_sharing): add public name prompt for files requests
Browse files Browse the repository at this point in the history
Signed-off-by: skjnldsv <skjnldsv@protonmail.com>
  • Loading branch information
skjnldsv committed Jul 18, 2024
1 parent 1a5deb8 commit 85a5c0d
Show file tree
Hide file tree
Showing 12 changed files with 299 additions and 52 deletions.
73 changes: 64 additions & 9 deletions apps/dav/lib/Files/Sharing/FilesDropPlugin.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,10 @@
namespace OCA\DAV\Files\Sharing;

use OC\Files\View;
use OCP\Share\IManager;
use Sabre\DAV\Exception\MethodNotAllowed;
use Sabre\DAV\Exception\NotFound;
use Sabre\DAV\Server;
use Sabre\DAV\ServerPlugin;
use Sabre\HTTP\RequestInterface;
use Sabre\HTTP\ResponseInterface;
Expand All @@ -16,17 +19,14 @@
*/
class FilesDropPlugin extends ServerPlugin {

/** @var View */
private $view;
private ?View $view = null;
private bool $enabled = false;

/** @var bool */
private $enabled = false;

public function setView(View $view) {
public function setView(View $view): void {
$this->view = $view;
}

public function enable() {
public function enable(): void {
$this->enabled = true;
}

Expand All @@ -39,25 +39,80 @@ public function enable() {
* @return void
* @throws MethodNotAllowed
*/
public function initialize(\Sabre\DAV\Server $server) {
public function initialize(\Sabre\DAV\Server $server): void {
$server->on('beforeMethod:*', [$this, 'beforeMethod'], 999);
$this->enabled = false;
}

public function beforeMethod(RequestInterface $request, ResponseInterface $response) {
public function beforeMethod(RequestInterface $request, ResponseInterface $response): void {
if (!$this->enabled) {
return;
}

$paths = explode('/', $request->getPath());

// Only allow file drop or folder creation
if ($request->getMethod() !== 'PUT' && $request->getMethod() !== 'MKCOL') {
throw new MethodNotAllowed('Only PUT is allowed on files drop');
}

// Only allow folder creation at the root of the files drop
if ($request->getMethod() === 'MKCOL' && count($paths) > 1) {
throw new MethodNotAllowed('Cannot create deep folders inside the files drop');
}

// Always upload at the root level
$path = explode('/', $request->getPath());
$path = array_pop($path);

$isFileRequest = false;
$nickName = $request->getHeader('X-NC-Nickname');
try {
$shareManager = \OCP\Server::get(IManager::class);
$token = $this->getToken($request);
$share = $shareManager->getShareByToken($token);
$attributes = $share->getAttributes();
$isFileRequest = $attributes->getAttribute('fileRequest', 'enabled') === true;
} catch (\Exception $e) {
// Continue
}

// We need a valid nickname for file requests
if ($isFileRequest && ($nickName == null || trim($nickName) === '')) {
throw new MethodNotAllowed('Nickname is required for file requests');
}

// If this is a file request we need to create a folder for the user
if ($isFileRequest) {
// Check if the folder already exists
if (!$this->view->file_exists($nickName) === true) {
$this->view->mkdir($nickName);
}
$path = $nickName . '/' . $path;
}

$newName = \OC_Helper::buildNotExistingFileNameForView('/', $path, $this->view);
$url = $request->getBaseUrl() . $newName;
$request->setUrl($url);
}



/**
* Extract token from request url
* @param RequestInterface $request
* @return string
* @throws NotFound
*/
private function getToken(RequestInterface $request): string {
$path = $request->getBaseUrl() ?: '';
// ['', 'public.php', 'dav', 'files', 'token']
$splittedPath = explode('/', $path);

if (count($splittedPath) < 5 || $splittedPath[4] === '') {
throw new NotFound();
}

return $splittedPath[4];
}
}
1 change: 1 addition & 0 deletions apps/files_sharing/composer/composer/autoload_classmap.php
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@
'OCA\\Files_Sharing\\Listener\\BeforeDirectFileDownloadListener' => $baseDir . '/../lib/Listener/BeforeDirectFileDownloadListener.php',
'OCA\\Files_Sharing\\Listener\\BeforeZipCreatedListener' => $baseDir . '/../lib/Listener/BeforeZipCreatedListener.php',
'OCA\\Files_Sharing\\Listener\\LoadAdditionalListener' => $baseDir . '/../lib/Listener/LoadAdditionalListener.php',
'OCA\\Files_Sharing\\Listener\\LoadPublicFileRequestAuthListener' => $baseDir . '/../lib/Listener/LoadPublicFileRequestAuthListener.php',
'OCA\\Files_Sharing\\Listener\\LoadSidebarListener' => $baseDir . '/../lib/Listener/LoadSidebarListener.php',
'OCA\\Files_Sharing\\Listener\\ShareInteractionListener' => $baseDir . '/../lib/Listener/ShareInteractionListener.php',
'OCA\\Files_Sharing\\Listener\\UserAddedToGroupListener' => $baseDir . '/../lib/Listener/UserAddedToGroupListener.php',
Expand Down
1 change: 1 addition & 0 deletions apps/files_sharing/composer/composer/autoload_static.php
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ class ComposerStaticInitFiles_Sharing
'OCA\\Files_Sharing\\Listener\\BeforeDirectFileDownloadListener' => __DIR__ . '/..' . '/../lib/Listener/BeforeDirectFileDownloadListener.php',
'OCA\\Files_Sharing\\Listener\\BeforeZipCreatedListener' => __DIR__ . '/..' . '/../lib/Listener/BeforeZipCreatedListener.php',
'OCA\\Files_Sharing\\Listener\\LoadAdditionalListener' => __DIR__ . '/..' . '/../lib/Listener/LoadAdditionalListener.php',
'OCA\\Files_Sharing\\Listener\\LoadPublicFileRequestAuthListener' => __DIR__ . '/..' . '/../lib/Listener/LoadPublicFileRequestAuthListener.php',
'OCA\\Files_Sharing\\Listener\\LoadSidebarListener' => __DIR__ . '/..' . '/../lib/Listener/LoadSidebarListener.php',
'OCA\\Files_Sharing\\Listener\\ShareInteractionListener' => __DIR__ . '/..' . '/../lib/Listener/ShareInteractionListener.php',
'OCA\\Files_Sharing\\Listener\\UserAddedToGroupListener' => __DIR__ . '/..' . '/../lib/Listener/UserAddedToGroupListener.php',
Expand Down
6 changes: 4 additions & 2 deletions apps/files_sharing/js/files_drop.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
// note: password not be required, the endpoint
// will recognize previous validation from the session
root: OC.getRootPath() + '/public.php/dav/files/' + $('#sharingToken').val() + '/',
useHTTPS: OC.getProtocol() === 'https'
useHTTPS: OC.getProtocol() === 'https',
});

// We only process one file at a time 🤷‍♀️
Expand All @@ -44,7 +44,9 @@
data.multipart = false;

if (!data.headers) {
data.headers = {};
data.headers = {
'X-NC-Nickname': localStorage.getItem('nick'),
};
}

$('#drop-upload-done-indicator').addClass('hidden');
Expand Down
7 changes: 6 additions & 1 deletion apps/files_sharing/lib/AppInfo/Application.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
use OCA\Files_Sharing\Listener\BeforeDirectFileDownloadListener;
use OCA\Files_Sharing\Listener\BeforeZipCreatedListener;
use OCA\Files_Sharing\Listener\LoadAdditionalListener;
use OCA\Files_Sharing\Listener\LoadPublicFileRequestAuthListener;
use OCA\Files_Sharing\Listener\LoadSidebarListener;
use OCA\Files_Sharing\Listener\ShareInteractionListener;
use OCA\Files_Sharing\Listener\UserAddedToGroupListener;
Expand All @@ -34,6 +35,7 @@
use OCP\AppFramework\Bootstrap\IBootContext;
use OCP\AppFramework\Bootstrap\IBootstrap;
use OCP\AppFramework\Bootstrap\IRegistrationContext;
use OCP\AppFramework\Http\Events\BeforeTemplateRenderedEvent;
use OCP\Collaboration\Resources\LoadAdditionalScriptsEvent as ResourcesLoadAdditionalScriptsEvent;
use OCP\EventDispatcher\IEventDispatcher;
use OCP\Federation\ICloudIdManager;
Expand Down Expand Up @@ -85,7 +87,7 @@ function () use ($c) {
$context->registerEventListener(GroupChangedEvent::class, GroupDisplayNameCache::class);
$context->registerEventListener(GroupDeletedEvent::class, GroupDisplayNameCache::class);

// sidebar and files scripts
// Sidebar and files scripts
$context->registerEventListener(LoadAdditionalScriptsEvent::class, LoadAdditionalListener::class);
$context->registerEventListener(LoadSidebar::class, LoadSidebarListener::class);
$context->registerEventListener(ShareCreatedEvent::class, ShareInteractionListener::class);
Expand All @@ -95,6 +97,9 @@ function () use ($c) {
// Handle download events for view only checks
$context->registerEventListener(BeforeZipCreatedEvent::class, BeforeZipCreatedListener::class);
$context->registerEventListener(BeforeDirectFileDownloadEvent::class, BeforeDirectFileDownloadListener::class);

// File request auth
$context->registerEventListener(BeforeTemplateRenderedEvent::class, LoadPublicFileRequestAuthListener::class);
}

public function boot(IBootContext $context): void {
Expand Down
49 changes: 19 additions & 30 deletions apps/files_sharing/lib/DefaultPublicShareTemplateProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
use OCP\AppFramework\Http\Template\PublicTemplateResponse;
use OCP\AppFramework\Http\Template\SimpleMenuAction;
use OCP\AppFramework\Http\TemplateResponse;
use OCP\AppFramework\Services\IInitialState;
use OCP\Constants;
use OCP\Defaults;
use OCP\EventDispatcher\IEventDispatcher;
Expand All @@ -37,39 +38,20 @@
use OCP\Util;

class DefaultPublicShareTemplateProvider implements IPublicShareTemplateProvider {
private IUserManager $userManager;
private IAccountManager $accountManager;
private IPreview $previewManager;
protected FederatedShareProvider $federatedShareProvider;
private IURLGenerator $urlGenerator;
private IEventDispatcher $eventDispatcher;
private IL10N $l10n;
private Defaults $defaults;
private IConfig $config;
private IRequest $request;

public function __construct(
IUserManager $userManager,
IAccountManager $accountManager,
IPreview $previewManager,
FederatedShareProvider $federatedShareProvider,
IUrlGenerator $urlGenerator,
IEventDispatcher $eventDispatcher,
IL10N $l10n,
Defaults $defaults,
IConfig $config,
IRequest $request
private IUserManager $userManager,
private IAccountManager $accountManager,
private IPreview $previewManager,
protected FederatedShareProvider $federatedShareProvider,
private IUrlGenerator $urlGenerator,
private IEventDispatcher $eventDispatcher,
private IL10N $l10n,
private Defaults $defaults,
private IConfig $config,
private IRequest $request,
private IInitialState $initialState,
) {
$this->userManager = $userManager;
$this->accountManager = $accountManager;
$this->previewManager = $previewManager;
$this->federatedShareProvider = $federatedShareProvider;
$this->urlGenerator = $urlGenerator;
$this->eventDispatcher = $eventDispatcher;
$this->l10n = $l10n;
$this->defaults = $defaults;
$this->config = $config;
$this->request = $request;
}

public function shouldRespond(IShare $share): bool {
Expand All @@ -91,9 +73,16 @@ public function renderPage(IShare $share, string $token, string $path): Template
if ($ownerName->getScope() === IAccountManager::SCOPE_PUBLISHED) {
$shareTmpl['owner'] = $owner->getUID();
$shareTmpl['shareOwner'] = $owner->getDisplayName();
$this->initialState->provideInitialState('owner', $shareTmpl['owner']);
$this->initialState->provideInitialState('ownerDisplayName', $shareTmpl['shareOwner']);
}
}

// Provide initial state
$this->initialState->provideInitialState('label', $share->getLabel());
$this->initialState->provideInitialState('note', $share->getNote());
$this->initialState->provideInitialState('filename', $shareNode->getName());

$shareTmpl['filename'] = $shareNode->getName();
$shareTmpl['directory_path'] = $share->getTarget();
$shareTmpl['label'] = $share->getLabel();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<?php

declare(strict_types=1);
/**
* SPDX-FileCopyrightText: 2020 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OCA\Files_Sharing\Listener;

use OCA\Files_Sharing\AppInfo\Application;
use OCP\AppFramework\Http\Events\BeforeTemplateRenderedEvent;
use OCP\AppFramework\Http\TemplateResponse;
use OCP\EventDispatcher\Event;
use OCP\EventDispatcher\IEventListener;
use OCP\Util;

/** @template-implements IEventListener<BeforeTemplateRenderedEvent> */
class LoadPublicFileRequestAuthListener implements IEventListener {
public function handle(Event $event): void {
if (!$event instanceof BeforeTemplateRenderedEvent) {
return;
}

// Make sure we are on a public page rendering
if ($event->getResponse()->getRenderAs() !== TemplateResponse::RENDER_AS_PUBLIC) {
return;
}
Util::addScript(Application::APP_ID, Application::APP_ID . '-public');
}
}
23 changes: 23 additions & 0 deletions apps/files_sharing/src/public-file-request.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/**
* SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/

import { spawnDialog } from '@nextcloud/dialogs'
import { defineAsyncComponent } from 'vue'
import logger from './services/logger'

const nick = localStorage.getItem('nick')
const publicAuthPromptShown = localStorage.getItem('publicAuthPromptShown')

// If we don't have a nickname or the public auth prompt hasn't been shown yet, show it
// We still show the prompt if the user has a nickname to double check
if (!nick || !publicAuthPromptShown) {
spawnDialog(
defineAsyncComponent(() => import('./views/PublicAuthPrompt.vue')),
{},
() => localStorage.setItem('publicAuthPromptShown', 'true'),
)
} else {
logger.debug(`Public auth prompt already shown. Current nickname is '${nick}'`)
}
Loading

0 comments on commit 85a5c0d

Please sign in to comment.