Browse Source
Add the new PublicShareController and PublicShareMiddleware
Add the new PublicShareController and PublicShareMiddleware
Signed-off-by: Roeland Jago Douma <roeland@famdouma.nl>pull/9518/head
No known key found for this signature in database
GPG Key ID: F941078878347C0C
7 changed files with 433 additions and 1 deletions
-
4lib/composer/composer/autoload_classmap.php
-
4lib/composer/composer/autoload_static.php
-
8lib/private/AppFramework/DependencyInjection/DIContainer.php
-
7lib/private/AppFramework/Middleware/PublicShare/Exceptions/NeedAuthenticationException.php
-
85lib/private/AppFramework/Middleware/PublicShare/PublicShareMiddleware.php
-
188lib/public/AppFramework/AuthPublicShareController.php
-
138lib/public/AppFramework/PublicShareController.php
@ -0,0 +1,7 @@ |
|||
<?php |
|||
|
|||
namespace OC\AppFramework\Middleware\PublicShare\Exceptions; |
|||
|
|||
class NeedAuthenticationException extends \Exception { |
|||
|
|||
} |
|||
@ -0,0 +1,85 @@ |
|||
<?php |
|||
|
|||
namespace OC\AppFramework\Middleware\PublicShare; |
|||
|
|||
use OC\AppFramework\Middleware\PublicShare\Exceptions\NeedAuthenticationException; |
|||
use OCP\AppFramework\AuthPublicShareController; |
|||
use OCP\AppFramework\Http\NotFoundResponse; |
|||
use OCP\AppFramework\Http\Response; |
|||
use OCP\AppFramework\Middleware; |
|||
use OCP\AppFramework\PublicShareController; |
|||
use OCP\Files\NotFoundException; |
|||
use OCP\IRequest; |
|||
use OCP\ISession; |
|||
|
|||
class PublicShareMiddleware extends Middleware { |
|||
/** @var IRequest */ |
|||
private $request; |
|||
|
|||
/** @var ISession */ |
|||
private $session; |
|||
|
|||
public function __construct(IRequest $request, ISession $session) { |
|||
$this->request = $request; |
|||
$this->session = $session; |
|||
} |
|||
|
|||
public function beforeController($controller, $methodName) { |
|||
if (!($controller instanceof PublicShareController)) { |
|||
return; |
|||
} |
|||
|
|||
// We require the token parameter to be set
|
|||
$token = $this->request->getParam('token'); |
|||
if ($token === null) { |
|||
throw new NotFoundException(); |
|||
} |
|||
|
|||
// Set the token
|
|||
$controller->setToken($token); |
|||
|
|||
if (!$controller->isValidToken()) { |
|||
$controller->shareNotFound(); |
|||
throw new NotFoundException(); |
|||
} |
|||
|
|||
// No need to check for authentication when we try to authenticate
|
|||
if ($methodName === 'authenticate' || $methodName === 'showAuthenticate') { |
|||
return; |
|||
} |
|||
|
|||
// If authentication succeeds just continue
|
|||
if ($controller->isAuthenticated($token)) { |
|||
return; |
|||
} |
|||
|
|||
// If we can authenticate to this controller do it else we throw a 404 to not leak any info
|
|||
if ($controller instanceof AuthPublicShareController) { |
|||
$this->session->set('public_link_authenticate_redirect', json_encode($this->request->getParams())); |
|||
throw new NeedAuthenticationException(); |
|||
} |
|||
|
|||
throw new NotFoundException(); |
|||
|
|||
} |
|||
|
|||
public function afterException($controller, $methodName, \Exception $exception) { |
|||
if (!($controller instanceof PublicShareController)) { |
|||
throw $exception; |
|||
} |
|||
|
|||
if ($exception instanceof NotFoundException) { |
|||
return new NotFoundResponse(); |
|||
} |
|||
|
|||
if ($controller instanceof AuthPublicShareController && $exception instanceof NeedAuthenticationException) { |
|||
return $controller->getAuthenticationRedirect($this->getFunctionForRoute($this->request->getParam('_route'))); |
|||
} |
|||
|
|||
throw $exception; |
|||
} |
|||
|
|||
private function getFunctionForRoute(string $route): string { |
|||
return array_pop(explode('.', $route)); |
|||
} |
|||
} |
|||
@ -0,0 +1,188 @@ |
|||
<?php |
|||
/** |
|||
* @copyright 2018, Roeland Jago Douma <roeland@famdouma.nl> |
|||
* |
|||
* @author Roeland Jago Douma <roeland@famdouma.nl> |
|||
* |
|||
* @license GNU AGPL version 3 or any later version |
|||
* |
|||
* This program is free software: you can redistribute it and/or modify |
|||
* it under the terms of the GNU Affero General Public License as |
|||
* published by the Free Software Foundation, either version 3 of the |
|||
* License, or (at your option) any later version. |
|||
* |
|||
* This program is distributed in the hope that it will be useful, |
|||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
|||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|||
* GNU Affero General Public License for more details. |
|||
* |
|||
* You should have received a copy of the GNU Affero General Public License |
|||
* along with this program. If not, see <http://www.gnu.org/licenses/>. |
|||
* |
|||
*/ |
|||
declare(strict_types=1); |
|||
|
|||
namespace OCP\AppFramework; |
|||
|
|||
use OCP\AppFramework\Http\RedirectResponse; |
|||
use OCP\AppFramework\Http\TemplateResponse; |
|||
use OCP\Files\NotFoundException; |
|||
use OCP\IRequest; |
|||
use OCP\ISession; |
|||
use OCP\IURLGenerator; |
|||
|
|||
/** |
|||
* Base controller for interactive public shares |
|||
* |
|||
* It will verify if the user is properly authenticated to the share. If not the |
|||
* user will be redirected to an authentication page. |
|||
* |
|||
* Use this for a controller that is to be called directly by a user. So the |
|||
* normal public share page for files/calendars etc. |
|||
* |
|||
* @since 14.0.0 |
|||
*/ |
|||
abstract class AuthPublicShareController extends PublicShareController { |
|||
|
|||
/** @var IURLGenerator */ |
|||
protected $urlGenerator; |
|||
|
|||
/** |
|||
* @since 14.0.0 |
|||
*/ |
|||
public function __construct(string $appName, |
|||
IRequest $request, |
|||
ISession $session, |
|||
IURLGenerator $urlGenerator) { |
|||
parent::__construct($appName, $request, $session); |
|||
|
|||
$this->urlGenerator = $urlGenerator; |
|||
} |
|||
|
|||
/** |
|||
* @PublicPage |
|||
* @NoCSRFRequired |
|||
* |
|||
* Show the authentication page |
|||
* The form has to submit to the authenticate method route |
|||
* |
|||
* @since 14.0.0 |
|||
*/ |
|||
abstract public function showAuthenticate(): TemplateResponse; |
|||
|
|||
/** |
|||
* The template to show when authentication failed |
|||
* |
|||
* @since 14.0.0 |
|||
*/ |
|||
abstract protected function showAuthFailed(): TemplateResponse; |
|||
|
|||
/** |
|||
* Verify the password |
|||
* |
|||
* @since 14.0.0 |
|||
*/ |
|||
abstract protected function verifyPassword(string $password): bool; |
|||
|
|||
/** |
|||
* Function called after failed authentication |
|||
* |
|||
* You can use this to do some logging for example |
|||
* |
|||
* @since 14.0.0 |
|||
*/ |
|||
protected function authFailed() { |
|||
} |
|||
|
|||
/** |
|||
* Function called after successfull authentication |
|||
* |
|||
* You can use this to do some logging for example |
|||
* |
|||
* @since 14.0.0 |
|||
*/ |
|||
protected function authSucceeded() { |
|||
} |
|||
|
|||
/** |
|||
* @UseSession |
|||
* @PublicPage |
|||
* @BruteForceProtection(action=publicLinkAuth) |
|||
* |
|||
* Authenticate the share |
|||
* |
|||
* @since 14.0.0 |
|||
*/ |
|||
final public function authenticate(string $password = '') { |
|||
// Already authenticated
|
|||
if ($this->isAuthenticated()) { |
|||
return $this->getRedirect(); |
|||
} |
|||
|
|||
if (!$this->verifyPassword($password)) { |
|||
$this->authFailed(); |
|||
$response = $this->showAuthFailed(); |
|||
$response->throttle(); |
|||
return $response; |
|||
} |
|||
|
|||
$this->session->regenerateId(); |
|||
$response = $this->getRedirect(); |
|||
|
|||
$this->session->clear(); |
|||
$this->session->set('public_link_authenticated_token', $this->getToken()); |
|||
$this->session->set('public_link_authenticated_password_hash', $this->getPasswordHash()); |
|||
|
|||
$this->authSucceeded(); |
|||
|
|||
return $response; |
|||
} |
|||
|
|||
/** |
|||
* Default landing page |
|||
* |
|||
* @since 14.0.0 |
|||
*/ |
|||
abstract public function showShare(): TemplateResponse; |
|||
|
|||
/** |
|||
* @since 14.0.0 |
|||
*/ |
|||
final public function getAuthenticationRedirect(string $redirect): RedirectResponse { |
|||
return new RedirectResponse( |
|||
$this->urlGenerator->linkToRoute($this->getRoute('showAuthenticate'), ['token' => $this->getToken(), 'redirect' => $redirect]) |
|||
); |
|||
} |
|||
|
|||
/** |
|||
* @since 14.0.0 |
|||
*/ |
|||
private function getRoute(string $function): string { |
|||
$app = strtolower($this->appName); |
|||
$class = strtolower((new \ReflectionClass($this))->getShortName()); |
|||
|
|||
return $app . '.' . $class . '.' . $function; |
|||
} |
|||
|
|||
/** |
|||
* @since 14.0.0 |
|||
*/ |
|||
private function getRedirect(): RedirectResponse { |
|||
//Get all the stored redirect parameters:
|
|||
$params = $this->session->get('public_link_authenticate_redirect'); |
|||
|
|||
$route = $this->getRoute('showShare'); |
|||
|
|||
if ($params === null) { |
|||
$params = []; |
|||
} else { |
|||
$params = json_decode($params, true); |
|||
if (isset($params['_route'])) { |
|||
$route = $params['_route']; |
|||
unset($params['_route']); |
|||
} |
|||
} |
|||
|
|||
return new RedirectResponse($this->urlGenerator->linkToRoute($route, $params)); |
|||
} |
|||
} |
|||
@ -0,0 +1,138 @@ |
|||
<?php |
|||
/** |
|||
* @copyright 2018, Roeland Jago Douma <roeland@famdouma.nl> |
|||
* |
|||
* @author Roeland Jago Douma <roeland@famdouma.nl> |
|||
* |
|||
* @license GNU AGPL version 3 or any later version |
|||
* |
|||
* This program is free software: you can redistribute it and/or modify |
|||
* it under the terms of the GNU Affero General Public License as |
|||
* published by the Free Software Foundation, either version 3 of the |
|||
* License, or (at your option) any later version. |
|||
* |
|||
* This program is distributed in the hope that it will be useful, |
|||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
|||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|||
* GNU Affero General Public License for more details. |
|||
* |
|||
* You should have received a copy of the GNU Affero General Public License |
|||
* along with this program. If not, see <http://www.gnu.org/licenses/>. |
|||
* |
|||
*/ |
|||
declare(strict_types=1); |
|||
|
|||
namespace OCP\AppFramework; |
|||
|
|||
use OCP\IRequest; |
|||
use OCP\ISession; |
|||
|
|||
/** |
|||
* Base controller for public shares |
|||
* |
|||
* It will verify if the user is properly authenticated to the share. If not a 404 |
|||
* is thrown by the PublicShareMiddleware. |
|||
* |
|||
* Use this for example for a controller that is not to be called via a webbrowser |
|||
* directly. For example a PublicPreviewController. As this is not meant to be |
|||
* called by a user direclty. |
|||
* |
|||
* To show an auth page extend the AuthPublicShareController |
|||
* |
|||
* @since 14.0.0 |
|||
*/ |
|||
abstract class PublicShareController extends Controller { |
|||
|
|||
/** @var ISession */ |
|||
protected $session; |
|||
|
|||
/** @var string */ |
|||
private $token; |
|||
|
|||
/** |
|||
* @since 14.0.0 |
|||
*/ |
|||
public function __construct(string $appName, |
|||
IRequest $request, |
|||
ISession $session) { |
|||
parent::__construct($appName, $request); |
|||
|
|||
$this->session = $session; |
|||
} |
|||
|
|||
/** |
|||
* Middleware set the token for the request |
|||
* |
|||
* @since 14.0.0 |
|||
*/ |
|||
final public function setToken(string $token) { |
|||
$this->token = $token; |
|||
} |
|||
|
|||
/** |
|||
* Get the token for this request |
|||
* |
|||
* @since 14.0.0 |
|||
*/ |
|||
public function getToken(): string { |
|||
return $this->token; |
|||
} |
|||
|
|||
/** |
|||
* Get a hash of the password for this share |
|||
* |
|||
* To ensure access is blocked when the password to a share is changed we store |
|||
* a hash of the password for this token. |
|||
* |
|||
* @since 14.0.0 |
|||
*/ |
|||
abstract protected function getPasswordHash(): string; |
|||
|
|||
/** |
|||
* Is the provided token a valid token |
|||
* |
|||
* This function is already called from the middleware directly after setting the token. |
|||
* |
|||
* @since 14.0.0 |
|||
*/ |
|||
abstract public function isValidToken(): bool; |
|||
|
|||
/** |
|||
* Is a share with this token password protected |
|||
* |
|||
* @since 14.0.0 |
|||
*/ |
|||
abstract protected function isPasswordProtected(): bool; |
|||
|
|||
/** |
|||
* Check if a share is authenticated or not |
|||
* |
|||
* @since 14.0.0 |
|||
*/ |
|||
final public function isAuthenticated(): bool { |
|||
// Always authenticated against non password protected shares
|
|||
if (!$this->isPasswordProtected()) { |
|||
return true; |
|||
} |
|||
|
|||
// If we are authenticated properly
|
|||
if ($this->session->get('public_link_authenticated_token') === $this->getToken() && |
|||
$this->session->get('public_link_authenticated_password_hash') === $this->getPasswordHash()) { |
|||
return true; |
|||
} |
|||
|
|||
// Fail by default if nothing matches
|
|||
return false; |
|||
} |
|||
|
|||
/** |
|||
* Function called if the share is not found. |
|||
* |
|||
* You can use this to do some logging for example |
|||
* |
|||
* @since 14.0.0 |
|||
*/ |
|||
public function shareNotFound() { |
|||
|
|||
} |
|||
} |
|||
Write
Preview
Loading…
Cancel
Save
Reference in new issue