Signed-off-by: Roeland Jago Douma <roeland@famdouma.nl>pull/9518/head
parent
cad8824a8e
commit
f36ef8ca80
@ -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() { |
||||
|
||||
} |
||||
} |
Loading…
Reference in new issue