Allow password protected folder shares (fix #165)
parent
d860d05895
commit
cb04070a92
|
@ -6,6 +6,7 @@ This file is manually updated. Please file an issue if something is missing.
|
||||||
|
|
||||||
- **Feature**: Native sharing from the viewer (images only)
|
- **Feature**: Native sharing from the viewer (images only)
|
||||||
- **Feature**: Deep linking to photos
|
- **Feature**: Deep linking to photos
|
||||||
|
- **Feature**: Password protected folder shares ([#165](https://github.com/pulsejet/memories/issues/165))
|
||||||
- Improvements to viewer UX
|
- Improvements to viewer UX
|
||||||
|
|
||||||
## v4.6.0, v3.6.0 (2022-11-06)
|
## v4.6.0, v3.6.0 (2022-11-06)
|
||||||
|
|
|
@ -14,43 +14,53 @@ function w($base, $param) {
|
||||||
return [
|
return [
|
||||||
'routes' => [
|
'routes' => [
|
||||||
// Vue routes for deep links
|
// Vue routes for deep links
|
||||||
['name' => 'page#main', 'url' => '/', 'verb' => 'GET'],
|
['name' => 'Page#main', 'url' => '/', 'verb' => 'GET'],
|
||||||
['name' => 'page#favorites', 'url' => '/favorites', 'verb' => 'GET'],
|
['name' => 'Page#favorites', 'url' => '/favorites', 'verb' => 'GET'],
|
||||||
['name' => 'page#videos', 'url' => '/videos', 'verb' => 'GET'],
|
['name' => 'Page#videos', 'url' => '/videos', 'verb' => 'GET'],
|
||||||
['name' => 'page#archive', 'url' => '/archive', 'verb' => 'GET'],
|
['name' => 'Page#archive', 'url' => '/archive', 'verb' => 'GET'],
|
||||||
['name' => 'page#thisday', 'url' => '/thisday', 'verb' => 'GET'],
|
['name' => 'Page#thisday', 'url' => '/thisday', 'verb' => 'GET'],
|
||||||
|
|
||||||
// Routes with params
|
// Routes with params
|
||||||
w(['name' => 'page#folder', 'url' => '/folders/{path}', 'verb' => 'GET'], 'path'),
|
w(['name' => 'Page#folder', 'url' => '/folders/{path}', 'verb' => 'GET'], 'path'),
|
||||||
w(['name' => 'page#albums', 'url' => '/albums/{id}', 'verb' => 'GET'], 'id'),
|
w(['name' => 'Page#albums', 'url' => '/albums/{id}', 'verb' => 'GET'], 'id'),
|
||||||
w(['name' => 'page#people', 'url' => '/people/{name}', 'verb' => 'GET'], 'name'),
|
w(['name' => 'Page#people', 'url' => '/people/{name}', 'verb' => 'GET'], 'name'),
|
||||||
w(['name' => 'page#tags', 'url' => '/tags/{name}', 'verb' => 'GET'], 'name'),
|
w(['name' => 'Page#tags', 'url' => '/tags/{name}', 'verb' => 'GET'], 'name'),
|
||||||
|
|
||||||
// Public pages
|
// Public folder share
|
||||||
['name' => 'page#sharedfolder', 'url' => '/s/{token}', 'verb' => 'GET'],
|
['name' => 'Public#showShare', 'url' => '/s/{token}', 'verb' => 'GET'],
|
||||||
|
[
|
||||||
|
'name' => 'Public#showAuthenticate',
|
||||||
|
'url' => '/s/{token}/authenticate/{redirect}',
|
||||||
|
'verb' => 'GET',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'name' => 'Public#authenticate',
|
||||||
|
'url' => '/s/{token}/authenticate/{redirect}',
|
||||||
|
'verb' => 'POST',
|
||||||
|
],
|
||||||
|
|
||||||
// API Routes
|
// API Routes
|
||||||
['name' => 'days#days', 'url' => '/api/days', 'verb' => 'GET'],
|
['name' => 'Days#days', 'url' => '/api/days', 'verb' => 'GET'],
|
||||||
['name' => 'days#day', 'url' => '/api/days/{id}', 'verb' => 'GET'],
|
['name' => 'Days#day', 'url' => '/api/days/{id}', 'verb' => 'GET'],
|
||||||
['name' => 'days#dayPost', 'url' => '/api/days', 'verb' => 'POST'],
|
['name' => 'Days#dayPost', 'url' => '/api/days', 'verb' => 'POST'],
|
||||||
|
|
||||||
['name' => 'tags#tags', 'url' => '/api/tags', 'verb' => 'GET'],
|
['name' => 'Tags#tags', 'url' => '/api/tags', 'verb' => 'GET'],
|
||||||
['name' => 'tags#previews', 'url' => '/api/tag-previews', 'verb' => 'GET'],
|
['name' => 'Tags#previews', 'url' => '/api/tag-previews', 'verb' => 'GET'],
|
||||||
|
|
||||||
['name' => 'albums#albums', 'url' => '/api/albums', 'verb' => 'GET'],
|
['name' => 'Albums#albums', 'url' => '/api/albums', 'verb' => 'GET'],
|
||||||
|
|
||||||
['name' => 'faces#faces', 'url' => '/api/faces', 'verb' => 'GET'],
|
['name' => 'Faces#faces', 'url' => '/api/faces', 'verb' => 'GET'],
|
||||||
['name' => 'faces#preview', 'url' => '/api/faces/preview/{id}', 'verb' => 'GET'],
|
['name' => 'Faces#preview', 'url' => '/api/faces/preview/{id}', 'verb' => 'GET'],
|
||||||
|
|
||||||
['name' => 'image#info', 'url' => '/api/info/{id}', 'verb' => 'GET'],
|
['name' => 'Image#info', 'url' => '/api/info/{id}', 'verb' => 'GET'],
|
||||||
['name' => 'image#edit', 'url' => '/api/edit/{id}', 'verb' => 'PATCH'],
|
['name' => 'Image#edit', 'url' => '/api/edit/{id}', 'verb' => 'PATCH'],
|
||||||
|
|
||||||
['name' => 'archive#archive', 'url' => '/api/archive/{id}', 'verb' => 'PATCH'],
|
['name' => 'Archive#archive', 'url' => '/api/archive/{id}', 'verb' => 'PATCH'],
|
||||||
|
|
||||||
// Config API
|
// Config API
|
||||||
['name' => 'other#setUserConfig', 'url' => '/api/config/{key}', 'verb' => 'PUT'],
|
['name' => 'Other#setUserConfig', 'url' => '/api/config/{key}', 'verb' => 'PUT'],
|
||||||
|
|
||||||
// Service worker
|
// Service worker
|
||||||
['name' => 'other#serviceWorker', 'url' => '/service-worker.js', 'verb' => 'GET'],
|
['name' => 'Other#serviceWorker', 'url' => '/service-worker.js', 'verb' => 'GET'],
|
||||||
]
|
]
|
||||||
];
|
];
|
||||||
|
|
|
@ -7,7 +7,6 @@ use OCA\Memories\AppInfo\Application;
|
||||||
use OCP\App\IAppManager;
|
use OCP\App\IAppManager;
|
||||||
use OCP\AppFramework\Controller;
|
use OCP\AppFramework\Controller;
|
||||||
use OCP\AppFramework\Http\ContentSecurityPolicy;
|
use OCP\AppFramework\Http\ContentSecurityPolicy;
|
||||||
use OCP\AppFramework\Http\Template\PublicTemplateResponse;
|
|
||||||
use OCP\AppFramework\Http\TemplateResponse;
|
use OCP\AppFramework\Http\TemplateResponse;
|
||||||
use OCP\AppFramework\Services\IInitialState;
|
use OCP\AppFramework\Services\IInitialState;
|
||||||
use OCP\EventDispatcher\IEventDispatcher;
|
use OCP\EventDispatcher\IEventDispatcher;
|
||||||
|
@ -102,30 +101,6 @@ class PageController extends Controller
|
||||||
return $response;
|
return $response;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @PublicPage
|
|
||||||
*
|
|
||||||
* @NoCSRFRequired
|
|
||||||
*/
|
|
||||||
public function sharedfolder(string $token)
|
|
||||||
{
|
|
||||||
// Scripts
|
|
||||||
Util::addScript($this->appName, 'memories-main');
|
|
||||||
$this->eventDispatcher->dispatchTyped(new LoadSidebar());
|
|
||||||
|
|
||||||
// App version
|
|
||||||
$this->initialState->provideInitialState('version', $this->appManager->getAppInfo('memories')['version']);
|
|
||||||
|
|
||||||
$policy = new ContentSecurityPolicy();
|
|
||||||
$policy->addAllowedWorkerSrcDomain("'self'");
|
|
||||||
$policy->addAllowedScriptDomain("'self'");
|
|
||||||
|
|
||||||
$response = new PublicTemplateResponse($this->appName, 'main');
|
|
||||||
$response->setContentSecurityPolicy($policy);
|
|
||||||
|
|
||||||
return $response;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @NoAdminRequired
|
* @NoAdminRequired
|
||||||
*
|
*
|
||||||
|
|
|
@ -0,0 +1,163 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace OCA\Memories\Controller;
|
||||||
|
|
||||||
|
use OCA\Files\Event\LoadSidebar;
|
||||||
|
use OCP\App\IAppManager;
|
||||||
|
use OCP\AppFramework\AuthPublicShareController;
|
||||||
|
use OCP\AppFramework\Http\ContentSecurityPolicy;
|
||||||
|
use OCP\AppFramework\Http\TemplateResponse;
|
||||||
|
use OCP\AppFramework\Services\IInitialState;
|
||||||
|
use OCP\EventDispatcher\IEventDispatcher;
|
||||||
|
use OCP\Files\NotFoundException;
|
||||||
|
use OCP\IConfig;
|
||||||
|
use OCP\IRequest;
|
||||||
|
use OCP\ISession;
|
||||||
|
use OCP\IURLGenerator;
|
||||||
|
use OCP\IUserManager;
|
||||||
|
use OCP\Share\IManager as IShareManager;
|
||||||
|
use OCP\Share\IShare;
|
||||||
|
use OCP\Util;
|
||||||
|
|
||||||
|
class PublicController extends AuthPublicShareController
|
||||||
|
{
|
||||||
|
protected $appName;
|
||||||
|
protected IEventDispatcher $eventDispatcher;
|
||||||
|
protected IInitialState $initialState;
|
||||||
|
protected IShareManager $shareManager;
|
||||||
|
protected IUserManager $userManager;
|
||||||
|
protected IAppManager $appManager;
|
||||||
|
protected IConfig $config;
|
||||||
|
|
||||||
|
protected IShare $share;
|
||||||
|
|
||||||
|
public function __construct(
|
||||||
|
string $AppName,
|
||||||
|
IRequest $request,
|
||||||
|
ISession $session,
|
||||||
|
IURLGenerator $urlGenerator,
|
||||||
|
IEventDispatcher $eventDispatcher,
|
||||||
|
IInitialState $initialState,
|
||||||
|
IShareManager $shareManager,
|
||||||
|
IUserManager $userManager,
|
||||||
|
IAppManager $appManager,
|
||||||
|
IConfig $config
|
||||||
|
) {
|
||||||
|
parent::__construct($AppName, $request, $session, $urlGenerator);
|
||||||
|
$this->eventDispatcher = $eventDispatcher;
|
||||||
|
$this->initialState = $initialState;
|
||||||
|
$this->shareManager = $shareManager;
|
||||||
|
$this->userManager = $userManager;
|
||||||
|
$this->appManager = $appManager;
|
||||||
|
$this->config = $config;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @PublicPage
|
||||||
|
*
|
||||||
|
* @NoCSRFRequired
|
||||||
|
*
|
||||||
|
* Show the authentication page
|
||||||
|
* The form has to submit to the authenticate method route
|
||||||
|
*/
|
||||||
|
public function showAuthenticate(): TemplateResponse
|
||||||
|
{
|
||||||
|
$templateParameters = ['share' => $this->share];
|
||||||
|
|
||||||
|
return new TemplateResponse('core', 'publicshareauth', $templateParameters, 'guest');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function isValidToken(): bool
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$this->share = $this->shareManager->getShareByToken($this->getToken());
|
||||||
|
|
||||||
|
return true;
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @PublicPage
|
||||||
|
*
|
||||||
|
* @NoCSRFRequired
|
||||||
|
*/
|
||||||
|
public function showShare(): TemplateResponse
|
||||||
|
{
|
||||||
|
\OC_User::setIncognitoMode(true);
|
||||||
|
|
||||||
|
// Check whether share exists
|
||||||
|
try {
|
||||||
|
$share = $this->shareManager->getShareByToken($this->getToken());
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
throw new NotFoundException();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$this->validateShare($share)) {
|
||||||
|
throw new NotFoundException();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Scripts
|
||||||
|
Util::addScript($this->appName, 'memories-main');
|
||||||
|
$this->eventDispatcher->dispatchTyped(new LoadSidebar());
|
||||||
|
|
||||||
|
// App version
|
||||||
|
$this->initialState->provideInitialState('version', $this->appManager->getAppInfo('memories')['version']);
|
||||||
|
|
||||||
|
$policy = new ContentSecurityPolicy();
|
||||||
|
$policy->addAllowedWorkerSrcDomain("'self'");
|
||||||
|
$policy->addAllowedScriptDomain("'self'");
|
||||||
|
|
||||||
|
$response = new TemplateResponse($this->appName, 'main');
|
||||||
|
$response->setContentSecurityPolicy($policy);
|
||||||
|
|
||||||
|
return $response;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function showAuthFailed(): TemplateResponse
|
||||||
|
{
|
||||||
|
$templateParameters = ['share' => $this->share, 'wrongpw' => true];
|
||||||
|
|
||||||
|
return new TemplateResponse('core', 'publicshareauth', $templateParameters, 'guest');
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function verifyPassword(string $password): bool
|
||||||
|
{
|
||||||
|
return $this->shareManager->checkPassword($this->share, $password);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function getPasswordHash(): string
|
||||||
|
{
|
||||||
|
return $this->share->getPassword();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function isPasswordProtected(): bool
|
||||||
|
{
|
||||||
|
return null !== $this->share->getPassword();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate the permissions of the share.
|
||||||
|
*
|
||||||
|
* @param Share\IShare $share
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
private function validateShare(IShare $share)
|
||||||
|
{
|
||||||
|
// If the owner is disabled no access to the linke is granted
|
||||||
|
$owner = $this->userManager->get($share->getShareOwner());
|
||||||
|
if (null === $owner || !$owner->isEnabled()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the initiator of the share is disabled no access is granted
|
||||||
|
$initiator = $this->userManager->get($share->getSharedBy());
|
||||||
|
if (null === $initiator || !$initiator->isEnabled()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $share->getNode()->isReadable() && $share->getNode()->isShareable();
|
||||||
|
}
|
||||||
|
}
|
20
lib/Util.php
20
lib/Util.php
|
@ -68,4 +68,24 @@ class Util
|
||||||
|
|
||||||
return version_compare($v, '3.0.0-alpha', '>=');
|
return version_compare($v, '3.0.0-alpha', '>=');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if link sharing is allowed.
|
||||||
|
*
|
||||||
|
* @param mixed $config
|
||||||
|
*/
|
||||||
|
public static function isLinkSharingEnabled(&$config): bool
|
||||||
|
{
|
||||||
|
// Check if the shareAPI is enabled
|
||||||
|
if ('yes' !== $config->getAppValue('core', 'shareapi_enabled', 'yes')) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check whether public sharing is enabled
|
||||||
|
if ('yes' !== $config->getAppValue('core', 'shareapi_allow_links', 'yes')) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue