migrate therinaldos.com data
Build & Deploy to DigitalOcean Space / build (push) Failing after 2m38s

This commit is contained in:
2024-05-05 15:50:45 -04:00
commit ef1ff240d4
23182 changed files with 3801898 additions and 0 deletions
@@ -0,0 +1,59 @@
<?php
declare(strict_types=1);
/**
* @copyright Copyright (c) 2019, Roeland Jago Douma <roeland@famdouma.nl>
*
* @author Julius Härtl <jus@bitgrid.net>
* @author Robin Appelman <robin@icewind.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/>.
*
*/
namespace OC\AppFramework\Middleware;
use OC\Core\Controller\LoginController;
use OCP\AppFramework\Http\Events\BeforeLoginTemplateRenderedEvent;
use OCP\AppFramework\Http\Events\BeforeTemplateRenderedEvent;
use OCP\AppFramework\Http\Response;
use OCP\AppFramework\Http\StandaloneTemplateResponse;
use OCP\AppFramework\Http\TemplateResponse;
use OCP\AppFramework\Middleware;
use OCP\EventDispatcher\IEventDispatcher;
use OCP\IUserSession;
class AdditionalScriptsMiddleware extends Middleware {
public function __construct(
private IUserSession $userSession,
private IEventDispatcher $dispatcher,
) {
}
public function afterController($controller, $methodName, Response $response): Response {
if ($response instanceof TemplateResponse) {
if ($controller instanceof LoginController) {
$this->dispatcher->dispatchTyped(new BeforeLoginTemplateRenderedEvent($response));
} else {
$isLoggedIn = !($response instanceof StandaloneTemplateResponse) && $this->userSession->isLoggedIn();
$this->dispatcher->dispatchTyped(new BeforeTemplateRenderedEvent($isLoggedIn, $response));
}
}
return $response;
}
}
@@ -0,0 +1,90 @@
<?php
declare(strict_types=1);
/**
* @copyright Copyright (c) 2020, Roeland Jago Douma <roeland@famdouma.nl>
*
* @author Morris Jobke <hey@morrisjobke.de>
* @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/>.
*
*/
namespace OC\AppFramework\Middleware;
use OC\AppFramework\OCS\BaseResponse;
use OCP\AppFramework\Http;
use OCP\AppFramework\Http\JSONResponse;
use OCP\AppFramework\Http\Response;
use OCP\AppFramework\Http\TemplateResponse;
use OCP\AppFramework\Middleware;
use OCP\IRequest;
class CompressionMiddleware extends Middleware {
/** @var bool */
private $useGZip;
/** @var IRequest */
private $request;
public function __construct(IRequest $request) {
$this->request = $request;
$this->useGZip = false;
}
public function afterController($controller, $methodName, Response $response) {
// By default we do not gzip
$allowGzip = false;
// Only return gzipped content for 200 responses
if ($response->getStatus() !== Http::STATUS_OK) {
return $response;
}
// Check if we are even asked for gzip
$header = $this->request->getHeader('Accept-Encoding');
if (!str_contains($header, 'gzip')) {
return $response;
}
// We only allow gzip in some cases
if ($response instanceof BaseResponse) {
$allowGzip = true;
}
if ($response instanceof JSONResponse) {
$allowGzip = true;
}
if ($response instanceof TemplateResponse) {
$allowGzip = true;
}
if ($allowGzip) {
$this->useGZip = true;
$response->addHeader('Content-Encoding', 'gzip');
}
return $response;
}
public function beforeOutput($controller, $methodName, $output) {
if (!$this->useGZip) {
return $output;
}
return gzencode($output);
}
}
@@ -0,0 +1,165 @@
<?php
declare(strict_types=1);
/**
* @copyright Copyright (c) 2016, ownCloud, Inc.
*
* @author Christoph Wurst <christoph@winzerhof-wurst.at>
* @author Jörn Friedrich Dreyer <jfd@butonic.de>
* @author Lukas Reschke <lukas@statuscode.ch>
* @author Morris Jobke <hey@morrisjobke.de>
* @author Roeland Jago Douma <roeland@famdouma.nl>
* @author Stefan Weil <sw@weilnetz.de>
* @author Thomas Müller <thomas.mueller@tmit.eu>
* @author Thomas Tanghus <thomas@tanghus.net>
*
* @license AGPL-3.0
*
* This code is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* 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, version 3,
* along with this program. If not, see <http://www.gnu.org/licenses/>
*
*/
namespace OC\AppFramework\Middleware;
use OCP\AppFramework\Controller;
use OCP\AppFramework\Http\Response;
use OCP\AppFramework\Middleware;
/**
* This class is used to store and run all the middleware in correct order
*/
class MiddlewareDispatcher {
/**
* @var array array containing all the middlewares
*/
private $middlewares;
/**
* @var int counter which tells us what middleware was executed once an
* exception occurs
*/
private $middlewareCounter;
/**
* Constructor
*/
public function __construct() {
$this->middlewares = [];
$this->middlewareCounter = 0;
}
/**
* Adds a new middleware
* @param Middleware $middleWare the middleware which will be added
*/
public function registerMiddleware(Middleware $middleWare) {
$this->middlewares[] = $middleWare;
}
/**
* returns an array with all middleware elements
* @return array the middlewares
*/
public function getMiddlewares(): array {
return $this->middlewares;
}
/**
* This is being run in normal order before the controller is being
* called which allows several modifications and checks
*
* @param Controller $controller the controller that is being called
* @param string $methodName the name of the method that will be called on
* the controller
*/
public function beforeController(Controller $controller, string $methodName) {
// we need to count so that we know which middlewares we have to ask in
// case there is an exception
$middlewareCount = \count($this->middlewares);
for ($i = 0; $i < $middlewareCount; $i++) {
$this->middlewareCounter++;
$middleware = $this->middlewares[$i];
$middleware->beforeController($controller, $methodName);
}
}
/**
* This is being run when either the beforeController method or the
* controller method itself is throwing an exception. The middleware is asked
* in reverse order to handle the exception and to return a response.
* If the response is null, it is assumed that the exception could not be
* handled and the error will be thrown again
*
* @param Controller $controller the controller that is being called
* @param string $methodName the name of the method that will be called on
* the controller
* @param \Exception $exception the thrown exception
* @return Response a Response object if the middleware can handle the
* exception
* @throws \Exception the passed in exception if it can't handle it
*/
public function afterException(Controller $controller, string $methodName, \Exception $exception): Response {
for ($i = $this->middlewareCounter - 1; $i >= 0; $i--) {
$middleware = $this->middlewares[$i];
try {
return $middleware->afterException($controller, $methodName, $exception);
} catch (\Exception $exception) {
continue;
}
}
throw $exception;
}
/**
* This is being run after a successful controllermethod call and allows
* the manipulation of a Response object. The middleware is run in reverse order
*
* @param Controller $controller the controller that is being called
* @param string $methodName the name of the method that will be called on
* the controller
* @param Response $response the generated response from the controller
* @return Response a Response object
*/
public function afterController(Controller $controller, string $methodName, Response $response): Response {
for ($i = \count($this->middlewares) - 1; $i >= 0; $i--) {
$middleware = $this->middlewares[$i];
$response = $middleware->afterController($controller, $methodName, $response);
}
return $response;
}
/**
* This is being run after the response object has been rendered and
* allows the manipulation of the output. The middleware is run in reverse order
*
* @param Controller $controller the controller that is being called
* @param string $methodName the name of the method that will be called on
* the controller
* @param string $output the generated output from a response
* @return string the output that should be printed
*/
public function beforeOutput(Controller $controller, string $methodName, string $output): string {
for ($i = \count($this->middlewares) - 1; $i >= 0; $i--) {
$middleware = $this->middlewares[$i];
$output = $middleware->beforeOutput($controller, $methodName, $output);
}
return $output;
}
}
@@ -0,0 +1,56 @@
<?php
declare(strict_types=1);
/**
* @copyright Copyright (c) 2020, 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/>.
*
*/
namespace OC\AppFramework\Middleware;
use OCP\AppFramework\Http;
use OCP\AppFramework\Http\Response;
use OCP\AppFramework\Middleware;
use OCP\IRequest;
class NotModifiedMiddleware extends Middleware {
/** @var IRequest */
private $request;
public function __construct(IRequest $request) {
$this->request = $request;
}
public function afterController($controller, $methodName, Response $response) {
$etagHeader = $this->request->getHeader('IF_NONE_MATCH');
if ($etagHeader !== '' && $response->getETag() !== null && trim($etagHeader) === '"' . $response->getETag() . '"') {
$response->setStatus(Http::STATUS_NOT_MODIFIED);
return $response;
}
$modifiedSinceHeader = $this->request->getHeader('IF_MODIFIED_SINCE');
if ($modifiedSinceHeader !== '' && $response->getLastModified() !== null && trim($modifiedSinceHeader) === $response->getLastModified()->format(\DateTimeInterface::RFC2822)) {
$response->setStatus(Http::STATUS_NOT_MODIFIED);
return $response;
}
return $response;
}
}
@@ -0,0 +1,161 @@
<?php
/**
* @copyright Copyright (c) 2016 Roeland Jago Douma <roeland@famdouma.nl>
*
* @author Christoph Wurst <christoph@winzerhof-wurst.at>
* @author Joas Schilling <coding@schilljs.com>
* @author Lukas Reschke <lukas@statuscode.ch>
* @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/>.
*
*/
namespace OC\AppFramework\Middleware;
use OC\AppFramework\Http;
use OC\AppFramework\OCS\BaseResponse;
use OC\AppFramework\OCS\V1Response;
use OC\AppFramework\OCS\V2Response;
use OCP\AppFramework\Controller;
use OCP\AppFramework\Http\DataResponse;
use OCP\AppFramework\Http\JSONResponse;
use OCP\AppFramework\Http\Response;
use OCP\AppFramework\Middleware;
use OCP\AppFramework\OCS\OCSException;
use OCP\AppFramework\OCSController;
use OCP\IRequest;
class OCSMiddleware extends Middleware {
/** @var IRequest */
private $request;
/** @var int */
private $ocsVersion;
/**
* @param IRequest $request
*/
public function __construct(IRequest $request) {
$this->request = $request;
}
/**
* @param Controller $controller
* @param string $methodName
*/
public function beforeController($controller, $methodName) {
if ($controller instanceof OCSController) {
if (substr_compare($this->request->getScriptName(), '/ocs/v2.php', -strlen('/ocs/v2.php')) === 0) {
$this->ocsVersion = 2;
} else {
$this->ocsVersion = 1;
}
$controller->setOCSVersion($this->ocsVersion);
}
}
/**
* @param Controller $controller
* @param string $methodName
* @param \Exception $exception
* @throws \Exception
* @return BaseResponse
*/
public function afterException($controller, $methodName, \Exception $exception) {
if ($controller instanceof OCSController && $exception instanceof OCSException) {
$code = $exception->getCode();
if ($code === 0) {
$code = \OCP\AppFramework\OCSController::RESPOND_UNKNOWN_ERROR;
}
return $this->buildNewResponse($controller, $code, $exception->getMessage());
}
throw $exception;
}
/**
* @param Controller $controller
* @param string $methodName
* @param Response $response
* @return \OCP\AppFramework\Http\Response
*/
public function afterController($controller, $methodName, Response $response) {
/*
* If a different middleware has detected that a request unauthorized or forbidden
* we need to catch the response and convert it to a proper OCS response.
*/
if ($controller instanceof OCSController && !($response instanceof BaseResponse)) {
if ($response->getStatus() === Http::STATUS_UNAUTHORIZED) {
$message = '';
if ($response instanceof JSONResponse) {
/** @var DataResponse $response */
$message = $response->getData()['message'];
}
return $this->buildNewResponse($controller, OCSController::RESPOND_UNAUTHORISED, $message);
}
if ($response->getStatus() === Http::STATUS_FORBIDDEN) {
$message = '';
if ($response instanceof JSONResponse) {
/** @var DataResponse $response */
$message = $response->getData()['message'];
}
return $this->buildNewResponse($controller, Http::STATUS_FORBIDDEN, $message);
}
}
return $response;
}
/**
* @param Controller $controller
* @param int $code
* @param string $message
* @return V1Response|V2Response
*/
private function buildNewResponse(Controller $controller, $code, $message) {
$format = $this->getFormat($controller);
$data = new DataResponse();
$data->setStatus($code);
if ($this->ocsVersion === 1) {
$response = new V1Response($data, $format, $message);
} else {
$response = new V2Response($data, $format, $message);
}
return $response;
}
/**
* @param Controller $controller
* @return string
*/
private function getFormat(Controller $controller) {
// get format from the url format or request format parameter
$format = $this->request->getParam('format');
// if none is given try the first Accept header
if ($format === null) {
$headers = $this->request->getHeader('Accept');
$format = $controller->getResponderByHTTPHeader($headers, 'xml');
}
return $format;
}
}
@@ -0,0 +1,27 @@
<?php
/**
* @copyright Copyright (c) 2016 Roeland Jago Douma <roeland@famdouma.nl>
*
* @author Christoph Wurst <christoph@winzerhof-wurst.at>
* @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/>.
*
*/
namespace OC\AppFramework\Middleware\PublicShare\Exceptions;
class NeedAuthenticationException extends \Exception {
}
@@ -0,0 +1,150 @@
<?php
/**
* @copyright Copyright (c) 2016 Roeland Jago Douma <roeland@famdouma.nl>
*
* @author Christoph Wurst <christoph@winzerhof-wurst.at>
* @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/>.
*
*/
namespace OC\AppFramework\Middleware\PublicShare;
use OC\AppFramework\Middleware\PublicShare\Exceptions\NeedAuthenticationException;
use OCP\AppFramework\AuthPublicShareController;
use OCP\AppFramework\Http\NotFoundResponse;
use OCP\AppFramework\Middleware;
use OCP\AppFramework\PublicShareController;
use OCP\Files\NotFoundException;
use OCP\IConfig;
use OCP\IRequest;
use OCP\ISession;
use OCP\Security\Bruteforce\IThrottler;
class PublicShareMiddleware extends Middleware {
/** @var IRequest */
private $request;
/** @var ISession */
private $session;
/** @var IConfig */
private $config;
/** @var IThrottler */
private $throttler;
public function __construct(IRequest $request, ISession $session, IConfig $config, IThrottler $throttler) {
$this->request = $request;
$this->session = $session;
$this->config = $config;
$this->throttler = $throttler;
}
public function beforeController($controller, $methodName) {
if (!($controller instanceof PublicShareController)) {
return;
}
$controllerClassPath = explode('\\', get_class($controller));
$controllerShortClass = end($controllerClassPath);
$bruteforceProtectionAction = $controllerShortClass . '::' . $methodName;
$this->throttler->sleepDelayOrThrowOnMax($this->request->getRemoteAddress(), $bruteforceProtectionAction);
if (!$this->isLinkSharingEnabled()) {
throw new NotFoundException('Link sharing is disabled');
}
// 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()) {
$this->throttle($bruteforceProtectionAction, $token);
$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()) {
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();
}
$this->throttle($bruteforceProtectionAction, $token);
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 {
$tmp = explode('.', $route);
return array_pop($tmp);
}
/**
* Check if link sharing is allowed
*/
private function isLinkSharingEnabled(): bool {
// Check if the shareAPI is enabled
if ($this->config->getAppValue('core', 'shareapi_enabled', 'yes') !== 'yes') {
return false;
}
// Check whether public sharing is enabled
if ($this->config->getAppValue('core', 'shareapi_allow_links', 'yes') !== 'yes') {
return false;
}
return true;
}
private function throttle($bruteforceProtectionAction, $token): void {
$ip = $this->request->getRemoteAddress();
$this->throttler->sleepDelay($ip, $bruteforceProtectionAction);
$this->throttler->registerAttempt($bruteforceProtectionAction, $ip, ['token' => $token]);
}
}
@@ -0,0 +1,157 @@
<?php
declare(strict_types=1);
/**
* @copyright Copyright (c) 2023 Joas Schilling <coding@schilljs.com>
* @copyright Copyright (c) 2017 Lukas Reschke <lukas@statuscode.ch>
*
* @author Christoph Wurst <christoph@winzerhof-wurst.at>
* @author Joas Schilling <coding@schilljs.com>
* @author Lukas Reschke <lukas@statuscode.ch>
*
* @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/>.
*
*/
namespace OC\AppFramework\Middleware\Security;
use OC\AppFramework\Utility\ControllerMethodReflector;
use OCP\AppFramework\Controller;
use OCP\AppFramework\Http;
use OCP\AppFramework\Http\Attribute\BruteForceProtection;
use OCP\AppFramework\Http\Response;
use OCP\AppFramework\Http\TooManyRequestsResponse;
use OCP\AppFramework\Middleware;
use OCP\AppFramework\OCS\OCSException;
use OCP\AppFramework\OCSController;
use OCP\IRequest;
use OCP\Security\Bruteforce\IThrottler;
use OCP\Security\Bruteforce\MaxDelayReached;
use Psr\Log\LoggerInterface;
use ReflectionMethod;
/**
* Class BruteForceMiddleware performs the bruteforce protection for controllers
* that are annotated with @BruteForceProtection(action=$action) whereas $action
* is the action that should be logged within the database.
*
* @package OC\AppFramework\Middleware\Security
*/
class BruteForceMiddleware extends Middleware {
private int $delaySlept = 0;
public function __construct(
protected ControllerMethodReflector $reflector,
protected IThrottler $throttler,
protected IRequest $request,
protected LoggerInterface $logger,
) {
}
/**
* {@inheritDoc}
*/
public function beforeController($controller, $methodName) {
parent::beforeController($controller, $methodName);
if ($this->reflector->hasAnnotation('BruteForceProtection')) {
$action = $this->reflector->getAnnotationParameter('BruteForceProtection', 'action');
$this->delaySlept += $this->throttler->sleepDelayOrThrowOnMax($this->request->getRemoteAddress(), $action);
} else {
$reflectionMethod = new ReflectionMethod($controller, $methodName);
$attributes = $reflectionMethod->getAttributes(BruteForceProtection::class);
if (!empty($attributes)) {
$remoteAddress = $this->request->getRemoteAddress();
foreach ($attributes as $attribute) {
/** @var BruteForceProtection $protection */
$protection = $attribute->newInstance();
$action = $protection->getAction();
$this->delaySlept += $this->throttler->sleepDelayOrThrowOnMax($remoteAddress, $action);
}
}
}
}
/**
* {@inheritDoc}
*/
public function afterController($controller, $methodName, Response $response) {
if ($response->isThrottled()) {
try {
if ($this->reflector->hasAnnotation('BruteForceProtection')) {
$action = $this->reflector->getAnnotationParameter('BruteForceProtection', 'action');
$ip = $this->request->getRemoteAddress();
$this->throttler->registerAttempt($action, $ip, $response->getThrottleMetadata());
$this->delaySlept += $this->throttler->sleepDelayOrThrowOnMax($ip, $action);
} else {
$reflectionMethod = new ReflectionMethod($controller, $methodName);
$attributes = $reflectionMethod->getAttributes(BruteForceProtection::class);
if (!empty($attributes)) {
$ip = $this->request->getRemoteAddress();
$metaData = $response->getThrottleMetadata();
foreach ($attributes as $attribute) {
/** @var BruteForceProtection $protection */
$protection = $attribute->newInstance();
$action = $protection->getAction();
if (!isset($metaData['action']) || $metaData['action'] === $action) {
$this->throttler->registerAttempt($action, $ip, $metaData);
$this->delaySlept += $this->throttler->sleepDelayOrThrowOnMax($ip, $action);
}
}
} else {
$this->logger->debug('Response for ' . get_class($controller) . '::' . $methodName . ' got bruteforce throttled but has no annotation nor attribute defined.');
}
}
} catch (MaxDelayReached $e) {
if ($controller instanceof OCSController) {
throw new OCSException($e->getMessage(), Http::STATUS_TOO_MANY_REQUESTS);
}
return new TooManyRequestsResponse();
}
}
if ($this->delaySlept) {
$response->addHeader('X-Nextcloud-Bruteforce-Throttled', $this->delaySlept . 'ms');
}
return parent::afterController($controller, $methodName, $response);
}
/**
* @param Controller $controller
* @param string $methodName
* @param \Exception $exception
* @throws \Exception
* @return Response
*/
public function afterException($controller, $methodName, \Exception $exception): Response {
if ($exception instanceof MaxDelayReached) {
if ($controller instanceof OCSController) {
throw new OCSException($exception->getMessage(), Http::STATUS_TOO_MANY_REQUESTS);
}
return new TooManyRequestsResponse();
}
throw $exception;
}
}
@@ -0,0 +1,189 @@
<?php
/**
* @copyright Copyright (c) 2016, ownCloud, Inc.
*
* @author Bernhard Posselt <dev@bernhard-posselt.com>
* @author Christoph Wurst <christoph@winzerhof-wurst.at>
* @author korelstar <korelstar@users.noreply.github.com>
* @author Lukas Reschke <lukas@statuscode.ch>
* @author Morris Jobke <hey@morrisjobke.de>
* @author Stefan Weil <sw@weilnetz.de>
*
* @license AGPL-3.0
*
* This code is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* 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, version 3,
* along with this program. If not, see <http://www.gnu.org/licenses/>
*
*/
namespace OC\AppFramework\Middleware\Security;
use OC\AppFramework\Middleware\Security\Exceptions\SecurityException;
use OC\AppFramework\Utility\ControllerMethodReflector;
use OC\Authentication\Exceptions\PasswordLoginForbiddenException;
use OC\User\Session;
use OCP\AppFramework\Controller;
use OCP\AppFramework\Http;
use OCP\AppFramework\Http\Attribute\CORS;
use OCP\AppFramework\Http\Attribute\PublicPage;
use OCP\AppFramework\Http\JSONResponse;
use OCP\AppFramework\Http\Response;
use OCP\AppFramework\Middleware;
use OCP\IRequest;
use OCP\ISession;
use OCP\Security\Bruteforce\IThrottler;
use ReflectionMethod;
/**
* This middleware sets the correct CORS headers on a response if the
* controller has the @CORS annotation. This is needed for webapps that want
* to access an API and don't run on the same domain, see
* https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS
*/
class CORSMiddleware extends Middleware {
/** @var IRequest */
private $request;
/** @var ControllerMethodReflector */
private $reflector;
/** @var Session */
private $session;
/** @var IThrottler */
private $throttler;
public function __construct(IRequest $request,
ControllerMethodReflector $reflector,
Session $session,
IThrottler $throttler) {
$this->request = $request;
$this->reflector = $reflector;
$this->session = $session;
$this->throttler = $throttler;
}
/**
* This is being run in normal order before the controller is being
* called which allows several modifications and checks
*
* @param Controller $controller the controller that is being called
* @param string $methodName the name of the method that will be called on
* the controller
* @throws SecurityException
* @since 6.0.0
*/
public function beforeController($controller, $methodName) {
$reflectionMethod = new ReflectionMethod($controller, $methodName);
// ensure that @CORS annotated API routes are not used in conjunction
// with session authentication since this enables CSRF attack vectors
if ($this->hasAnnotationOrAttribute($reflectionMethod, 'CORS', CORS::class) &&
(!$this->hasAnnotationOrAttribute($reflectionMethod, 'PublicPage', PublicPage::class) || $this->session->isLoggedIn())) {
$user = array_key_exists('PHP_AUTH_USER', $this->request->server) ? $this->request->server['PHP_AUTH_USER'] : null;
$pass = array_key_exists('PHP_AUTH_PW', $this->request->server) ? $this->request->server['PHP_AUTH_PW'] : null;
// Allow to use the current session if a CSRF token is provided
if ($this->request->passesCSRFCheck()) {
return;
}
// Skip CORS check for requests with AppAPI auth.
if ($this->session->getSession() instanceof ISession && $this->session->getSession()->get('app_api') === true) {
return;
}
$this->session->logout();
try {
if ($user === null || $pass === null || !$this->session->logClientIn($user, $pass, $this->request, $this->throttler)) {
throw new SecurityException('CORS requires basic auth', Http::STATUS_UNAUTHORIZED);
}
} catch (PasswordLoginForbiddenException $ex) {
throw new SecurityException('Password login forbidden, use token instead', Http::STATUS_UNAUTHORIZED);
}
}
}
/**
* @template T
*
* @param ReflectionMethod $reflectionMethod
* @param string $annotationName
* @param class-string<T> $attributeClass
* @return boolean
*/
protected function hasAnnotationOrAttribute(ReflectionMethod $reflectionMethod, string $annotationName, string $attributeClass): bool {
if ($this->reflector->hasAnnotation($annotationName)) {
return true;
}
if (!empty($reflectionMethod->getAttributes($attributeClass))) {
return true;
}
return false;
}
/**
* This is being run after a successful controller method call and allows
* the manipulation of a Response object. The middleware is run in reverse order
*
* @param Controller $controller the controller that is being called
* @param string $methodName the name of the method that will be called on
* the controller
* @param Response $response the generated response from the controller
* @return Response a Response object
* @throws SecurityException
*/
public function afterController($controller, $methodName, Response $response) {
// only react if it's a CORS request and if the request sends origin and
if (isset($this->request->server['HTTP_ORIGIN'])) {
$reflectionMethod = new ReflectionMethod($controller, $methodName);
if ($this->hasAnnotationOrAttribute($reflectionMethod, 'CORS', CORS::class)) {
// allow credentials headers must not be true or CSRF is possible
// otherwise
foreach ($response->getHeaders() as $header => $value) {
if (strtolower($header) === 'access-control-allow-credentials' &&
strtolower(trim($value)) === 'true') {
$msg = 'Access-Control-Allow-Credentials must not be '.
'set to true in order to prevent CSRF';
throw new SecurityException($msg);
}
}
$origin = $this->request->server['HTTP_ORIGIN'];
$response->addHeader('Access-Control-Allow-Origin', $origin);
}
}
return $response;
}
/**
* If an SecurityException is being caught return a JSON error response
*
* @param Controller $controller the controller that is being called
* @param string $methodName the name of the method that will be called on
* the controller
* @param \Exception $exception the thrown exception
* @throws \Exception the passed in exception if it can't handle it
* @return Response a Response object or null in case that the exception could not be handled
*/
public function afterException($controller, $methodName, \Exception $exception) {
if ($exception instanceof SecurityException) {
$response = new JSONResponse(['message' => $exception->getMessage()]);
if ($exception->getCode() !== 0) {
$response->setStatus($exception->getCode());
} else {
$response->setStatus(Http::STATUS_INTERNAL_SERVER_ERROR);
}
return $response;
}
throw $exception;
}
}
@@ -0,0 +1,81 @@
<?php
declare(strict_types=1);
/**
* @copyright Copyright (c) 2019, Roeland Jago Douma <roeland@famdouma.nl>
*
* @author Christoph Wurst <christoph@winzerhof-wurst.at>
* @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/>.
*
*/
namespace OC\AppFramework\Middleware\Security;
use OC\Security\CSP\ContentSecurityPolicyManager;
use OC\Security\CSP\ContentSecurityPolicyNonceManager;
use OC\Security\CSRF\CsrfTokenManager;
use OCP\AppFramework\Controller;
use OCP\AppFramework\Http\ContentSecurityPolicy;
use OCP\AppFramework\Http\EmptyContentSecurityPolicy;
use OCP\AppFramework\Http\Response;
use OCP\AppFramework\Middleware;
class CSPMiddleware extends Middleware {
/** @var ContentSecurityPolicyManager */
private $contentSecurityPolicyManager;
/** @var ContentSecurityPolicyNonceManager */
private $cspNonceManager;
/** @var CsrfTokenManager */
private $csrfTokenManager;
public function __construct(ContentSecurityPolicyManager $policyManager,
ContentSecurityPolicyNonceManager $cspNonceManager,
CsrfTokenManager $csrfTokenManager) {
$this->contentSecurityPolicyManager = $policyManager;
$this->cspNonceManager = $cspNonceManager;
$this->csrfTokenManager = $csrfTokenManager;
}
/**
* Performs the default CSP modifications that may be injected by other
* applications
*
* @param Controller $controller
* @param string $methodName
* @param Response $response
* @return Response
*/
public function afterController($controller, $methodName, Response $response): Response {
$policy = !is_null($response->getContentSecurityPolicy()) ? $response->getContentSecurityPolicy() : new ContentSecurityPolicy();
if (get_class($policy) === EmptyContentSecurityPolicy::class) {
return $response;
}
$defaultPolicy = $this->contentSecurityPolicyManager->getDefaultPolicy();
$defaultPolicy = $this->contentSecurityPolicyManager->mergePolicies($defaultPolicy, $policy);
if ($this->cspNonceManager->browserSupportsCspV3()) {
$defaultPolicy->useJsNonce($this->csrfTokenManager->getToken()->getEncryptedValue());
}
$response->setContentSecurityPolicy($defaultPolicy);
return $response;
}
}
@@ -0,0 +1,39 @@
<?php
/**
* @copyright Copyright (c) 2016, ownCloud, Inc.
*
* @author Lukas Reschke <lukas@statuscode.ch>
* @author Morris Jobke <hey@morrisjobke.de>
* @author Roeland Jago Douma <roeland@famdouma.nl>
* @author Thomas Müller <thomas.mueller@tmit.eu>
*
* @license AGPL-3.0
*
* This code is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* 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, version 3,
* along with this program. If not, see <http://www.gnu.org/licenses/>
*
*/
namespace OC\AppFramework\Middleware\Security\Exceptions;
use OCP\AppFramework\Http;
/**
* Class AppNotEnabledException is thrown when a resource for an application is
* requested that is not enabled.
*
* @package OC\AppFramework\Middleware\Security\Exceptions
*/
class AppNotEnabledException extends SecurityException {
public function __construct() {
parent::__construct('App is not enabled', Http::STATUS_PRECONDITION_FAILED);
}
}
@@ -0,0 +1,39 @@
<?php
/**
* @copyright Copyright (c) 2016, ownCloud, Inc.
*
* @author Lukas Reschke <lukas@statuscode.ch>
* @author Morris Jobke <hey@morrisjobke.de>
* @author Roeland Jago Douma <roeland@famdouma.nl>
* @author Thomas Müller <thomas.mueller@tmit.eu>
*
* @license AGPL-3.0
*
* This code is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* 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, version 3,
* along with this program. If not, see <http://www.gnu.org/licenses/>
*
*/
namespace OC\AppFramework\Middleware\Security\Exceptions;
use OCP\AppFramework\Http;
/**
* Class CrossSiteRequestForgeryException is thrown when a CSRF exception has
* been encountered.
*
* @package OC\AppFramework\Middleware\Security\Exceptions
*/
class CrossSiteRequestForgeryException extends SecurityException {
public function __construct() {
parent::__construct('CSRF check failed', Http::STATUS_PRECONDITION_FAILED);
}
}
@@ -0,0 +1,37 @@
<?php
/**
* @copyright 2017, 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/>.
*
*/
namespace OC\AppFramework\Middleware\Security\Exceptions;
use OCP\AppFramework\Http;
/**
* Class LaxSameSiteCookieFailedException is thrown when a request doesn't pass
* the required LaxCookie check on index.php
*
* @package OC\AppFramework\Middleware\Security\Exceptions
*/
class LaxSameSiteCookieFailedException extends SecurityException {
public function __construct() {
parent::__construct('Lax Same Site Cookie is invalid in request.', Http::STATUS_PRECONDITION_FAILED);
}
}
@@ -0,0 +1,43 @@
<?php
declare(strict_types=1);
/**
* @copyright Copyright (c) 2016, ownCloud, Inc.
*
* @author Joas Schilling <coding@schilljs.com>
* @author Lukas Reschke <lukas@statuscode.ch>
* @author Morris Jobke <hey@morrisjobke.de>
* @author Roeland Jago Douma <roeland@famdouma.nl>
* @author Thomas Müller <thomas.mueller@tmit.eu>
*
* @license AGPL-3.0
*
* This code is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* 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, version 3,
* along with this program. If not, see <http://www.gnu.org/licenses/>
*
*/
namespace OC\AppFramework\Middleware\Security\Exceptions;
use OCP\AppFramework\Http;
/**
* Class NotAdminException is thrown when a resource has been requested by a
* non-admin user that is not accessible to non-admin users.
*
* @package OC\AppFramework\Middleware\Security\Exceptions
*/
class NotAdminException extends SecurityException {
public function __construct(string $message) {
parent::__construct($message, Http::STATUS_FORBIDDEN);
}
}
@@ -0,0 +1,37 @@
<?php
/**
* @copyright Copyright (c) 2016 Joas Schilling <coding@schilljs.com>
*
* @author Joas Schilling <coding@schilljs.com>
*
* @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/>.
*
*/
namespace OC\AppFramework\Middleware\Security\Exceptions;
use OCP\AppFramework\Http;
/**
* Class NotConfirmedException is thrown when a resource has been requested by a
* user that has not confirmed their password in the last 30 minutes.
*
* @package OC\AppFramework\Middleware\Security\Exceptions
*/
class NotConfirmedException extends SecurityException {
public function __construct() {
parent::__construct('Password confirmation is required', Http::STATUS_FORBIDDEN);
}
}
@@ -0,0 +1,39 @@
<?php
/**
* @copyright Copyright (c) 2016, ownCloud, Inc.
*
* @author Lukas Reschke <lukas@statuscode.ch>
* @author Morris Jobke <hey@morrisjobke.de>
* @author Roeland Jago Douma <roeland@famdouma.nl>
* @author Thomas Müller <thomas.mueller@tmit.eu>
*
* @license AGPL-3.0
*
* This code is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* 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, version 3,
* along with this program. If not, see <http://www.gnu.org/licenses/>
*
*/
namespace OC\AppFramework\Middleware\Security\Exceptions;
use OCP\AppFramework\Http;
/**
* Class NotLoggedInException is thrown when a resource has been requested by a
* guest user that is not accessible to the public.
*
* @package OC\AppFramework\Middleware\Security\Exceptions
*/
class NotLoggedInException extends SecurityException {
public function __construct() {
parent::__construct('Current user is not logged in', Http::STATUS_UNAUTHORIZED);
}
}
@@ -0,0 +1,29 @@
<?php
declare(strict_types=1);
/**
* @copyright Copyright (c) 2019, 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/>.
*
*/
namespace OC\AppFramework\Middleware\Security\Exceptions;
class ReloadExecutionException extends SecurityException {
}
@@ -0,0 +1,37 @@
<?php
declare(strict_types=1);
/**
* @copyright Copyright (c) 2016, ownCloud, Inc.
*
* @author Christoph Wurst <christoph@winzerhof-wurst.at>
* @author Lukas Reschke <lukas@statuscode.ch>
* @author Morris Jobke <hey@morrisjobke.de>
* @author Thomas Müller <thomas.mueller@tmit.eu>
*
* @license AGPL-3.0
*
* This code is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* 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, version 3,
* along with this program. If not, see <http://www.gnu.org/licenses/>
*
*/
namespace OC\AppFramework\Middleware\Security\Exceptions;
/**
* Class SecurityException is the base class for security exceptions thrown by
* the security middleware.
*
* @package OC\AppFramework\Middleware\Security\Exceptions
*/
class SecurityException extends \Exception {
}
@@ -0,0 +1,40 @@
<?php
declare(strict_types=1);
/**
* @copyright Copyright (c) 2016 Lukas Reschke <lukas@statuscode.ch>
*
* @author Lukas Reschke <lukas@statuscode.ch>
*
* @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/>.
*
*/
namespace OC\AppFramework\Middleware\Security\Exceptions;
use OCP\AppFramework\Http;
/**
* Class StrictCookieMissingException is thrown when the strict cookie has not
* been sent with the request but is required.
*
* @package OC\AppFramework\Middleware\Security\Exceptions
*/
class StrictCookieMissingException extends SecurityException {
public function __construct() {
parent::__construct('Strict Cookie has not been found in request.', Http::STATUS_PRECONDITION_FAILED);
}
}
@@ -0,0 +1,65 @@
<?php
declare(strict_types=1);
/**
* @copyright Copyright (c) 2019, 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/>.
*
*/
namespace OC\AppFramework\Middleware\Security;
use OC\Security\FeaturePolicy\FeaturePolicy;
use OC\Security\FeaturePolicy\FeaturePolicyManager;
use OCP\AppFramework\Controller;
use OCP\AppFramework\Http\EmptyFeaturePolicy;
use OCP\AppFramework\Http\Response;
use OCP\AppFramework\Middleware;
class FeaturePolicyMiddleware extends Middleware {
/** @var FeaturePolicyManager */
private $policyManager;
public function __construct(FeaturePolicyManager $policyManager) {
$this->policyManager = $policyManager;
}
/**
* Performs the default FeaturePolicy modifications that may be injected by other
* applications
*
* @param Controller $controller
* @param string $methodName
* @param Response $response
* @return Response
*/
public function afterController($controller, $methodName, Response $response): Response {
$policy = !is_null($response->getFeaturePolicy()) ? $response->getFeaturePolicy() : new FeaturePolicy();
if (get_class($policy) === EmptyFeaturePolicy::class) {
return $response;
}
$defaultPolicy = $this->policyManager->getDefaultPolicy();
$defaultPolicy = $this->policyManager->mergePolicies($defaultPolicy, $policy);
$response->setFeaturePolicy($defaultPolicy);
return $response;
}
}
@@ -0,0 +1,116 @@
<?php
/**
* @copyright 2018, Roeland Jago Douma <roeland@famdouma.nl>
*
* @author Bjoern Schiessle <bjoern@schiessle.org>
* @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/>.
*
*/
namespace OC\AppFramework\Middleware\Security;
use OC\AppFramework\Middleware\Security\Exceptions\NotConfirmedException;
use OC\AppFramework\Utility\ControllerMethodReflector;
use OCP\AppFramework\Controller;
use OCP\AppFramework\Http\Attribute\PasswordConfirmationRequired;
use OCP\AppFramework\Middleware;
use OCP\AppFramework\Utility\ITimeFactory;
use OCP\ISession;
use OCP\IUserSession;
use OCP\User\Backend\IPasswordConfirmationBackend;
use ReflectionMethod;
class PasswordConfirmationMiddleware extends Middleware {
/** @var ControllerMethodReflector */
private $reflector;
/** @var ISession */
private $session;
/** @var IUserSession */
private $userSession;
/** @var ITimeFactory */
private $timeFactory;
/** @var array */
private $excludedUserBackEnds = ['user_saml' => true, 'user_globalsiteselector' => true];
/**
* PasswordConfirmationMiddleware constructor.
*
* @param ControllerMethodReflector $reflector
* @param ISession $session
* @param IUserSession $userSession
* @param ITimeFactory $timeFactory
*/
public function __construct(ControllerMethodReflector $reflector,
ISession $session,
IUserSession $userSession,
ITimeFactory $timeFactory) {
$this->reflector = $reflector;
$this->session = $session;
$this->userSession = $userSession;
$this->timeFactory = $timeFactory;
}
/**
* @param Controller $controller
* @param string $methodName
* @throws NotConfirmedException
*/
public function beforeController($controller, $methodName) {
$reflectionMethod = new ReflectionMethod($controller, $methodName);
if ($this->hasAnnotationOrAttribute($reflectionMethod, 'PasswordConfirmationRequired', PasswordConfirmationRequired::class)) {
$user = $this->userSession->getUser();
$backendClassName = '';
if ($user !== null) {
$backend = $user->getBackend();
if ($backend instanceof IPasswordConfirmationBackend) {
if (!$backend->canConfirmPassword($user->getUID())) {
return;
}
}
$backendClassName = $user->getBackendClassName();
}
$lastConfirm = (int) $this->session->get('last-password-confirm');
// we can't check the password against a SAML backend, so skip password confirmation in this case
if (!isset($this->excludedUserBackEnds[$backendClassName]) && $lastConfirm < ($this->timeFactory->getTime() - (30 * 60 + 15))) { // allow 15 seconds delay
throw new NotConfirmedException();
}
}
}
/**
* @template T
*
* @param ReflectionMethod $reflectionMethod
* @param string $annotationName
* @param class-string<T> $attributeClass
* @return boolean
*/
protected function hasAnnotationOrAttribute(ReflectionMethod $reflectionMethod, string $annotationName, string $attributeClass): bool {
if (!empty($reflectionMethod->getAttributes($attributeClass))) {
return true;
}
if ($this->reflector->hasAnnotation($annotationName)) {
return true;
}
return false;
}
}
@@ -0,0 +1,165 @@
<?php
declare(strict_types=1);
/**
* @copyright Copyright (c) 2023 Joas Schilling <coding@schilljs.com>
* @copyright Copyright (c) 2017 Lukas Reschke <lukas@statuscode.ch>
*
* @author Christoph Wurst <christoph@winzerhof-wurst.at>
* @author Joas Schilling <coding@schilljs.com>
* @author Lukas Reschke <lukas@statuscode.ch>
*
* @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/>.
*
*/
namespace OC\AppFramework\Middleware\Security;
use OC\AppFramework\Utility\ControllerMethodReflector;
use OC\Security\RateLimiting\Exception\RateLimitExceededException;
use OC\Security\RateLimiting\Limiter;
use OCP\AppFramework\Controller;
use OCP\AppFramework\Http\Attribute\AnonRateLimit;
use OCP\AppFramework\Http\Attribute\ARateLimit;
use OCP\AppFramework\Http\Attribute\UserRateLimit;
use OCP\AppFramework\Http\DataResponse;
use OCP\AppFramework\Http\Response;
use OCP\AppFramework\Http\TemplateResponse;
use OCP\AppFramework\Middleware;
use OCP\IRequest;
use OCP\IUserSession;
use ReflectionMethod;
/**
* Class RateLimitingMiddleware is the middleware responsible for implementing the
* ratelimiting in Nextcloud.
*
* It parses annotations such as:
*
* @UserRateThrottle(limit=5, period=100)
* @AnonRateThrottle(limit=1, period=100)
*
* Or attributes such as:
*
* #[UserRateLimit(limit: 5, period: 100)]
* #[AnonRateLimit(limit: 1, period: 100)]
*
* Both sets would mean that logged-in users can access the page 5
* times within 100 seconds, and anonymous users 1 time within 100 seconds. If
* only an AnonRateThrottle is specified that one will also be applied to logged-in
* users.
*
* @package OC\AppFramework\Middleware\Security
*/
class RateLimitingMiddleware extends Middleware {
public function __construct(
protected IRequest $request,
protected IUserSession $userSession,
protected ControllerMethodReflector $reflector,
protected Limiter $limiter,
) {
}
/**
* {@inheritDoc}
* @throws RateLimitExceededException
*/
public function beforeController(Controller $controller, string $methodName): void {
parent::beforeController($controller, $methodName);
$rateLimitIdentifier = get_class($controller) . '::' . $methodName;
if ($this->userSession->isLoggedIn()) {
$rateLimit = $this->readLimitFromAnnotationOrAttribute($controller, $methodName, 'UserRateThrottle', UserRateLimit::class);
if ($rateLimit !== null) {
$this->limiter->registerUserRequest(
$rateLimitIdentifier,
$rateLimit->getLimit(),
$rateLimit->getPeriod(),
$this->userSession->getUser()
);
return;
}
// If not user specific rate limit is found the Anon rate limit applies!
}
$rateLimit = $this->readLimitFromAnnotationOrAttribute($controller, $methodName, 'AnonRateThrottle', AnonRateLimit::class);
if ($rateLimit !== null) {
$this->limiter->registerAnonRequest(
$rateLimitIdentifier,
$rateLimit->getLimit(),
$rateLimit->getPeriod(),
$this->request->getRemoteAddress()
);
}
}
/**
* @template T of ARateLimit
*
* @param Controller $controller
* @param string $methodName
* @param string $annotationName
* @param class-string<T> $attributeClass
* @return ?ARateLimit
*/
protected function readLimitFromAnnotationOrAttribute(Controller $controller, string $methodName, string $annotationName, string $attributeClass): ?ARateLimit {
$annotationLimit = $this->reflector->getAnnotationParameter($annotationName, 'limit');
$annotationPeriod = $this->reflector->getAnnotationParameter($annotationName, 'period');
if ($annotationLimit !== '' && $annotationPeriod !== '') {
return new $attributeClass(
(int) $annotationLimit,
(int) $annotationPeriod,
);
}
$reflectionMethod = new ReflectionMethod($controller, $methodName);
$attributes = $reflectionMethod->getAttributes($attributeClass);
$attribute = current($attributes);
if ($attribute !== false) {
return $attribute->newInstance();
}
return null;
}
/**
* {@inheritDoc}
*/
public function afterException(Controller $controller, string $methodName, \Exception $exception): Response {
if ($exception instanceof RateLimitExceededException) {
if (stripos($this->request->getHeader('Accept'), 'html') === false) {
$response = new DataResponse([], $exception->getCode());
} else {
$response = new TemplateResponse(
'core',
'429',
[],
TemplateResponse::RENDER_AS_GUEST
);
$response->setStatus($exception->getCode());
}
return $response;
}
throw $exception;
}
}
@@ -0,0 +1,67 @@
<?php
declare(strict_types=1);
/**
* @copyright Copyright (c) 2019, 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/>.
*
*/
namespace OC\AppFramework\Middleware\Security;
use OC\AppFramework\Middleware\Security\Exceptions\ReloadExecutionException;
use OCP\AppFramework\Http\RedirectResponse;
use OCP\AppFramework\Middleware;
use OCP\ISession;
use OCP\IURLGenerator;
/**
* Simple middleware to handle the clearing of the execution context. This will trigger
* a reload but if the session variable is set we properly redirect to the login page.
*/
class ReloadExecutionMiddleware extends Middleware {
/** @var ISession */
private $session;
/** @var IURLGenerator */
private $urlGenerator;
public function __construct(ISession $session, IURLGenerator $urlGenerator) {
$this->session = $session;
$this->urlGenerator = $urlGenerator;
}
public function beforeController($controller, $methodName) {
if ($this->session->exists('clearingExecutionContexts')) {
throw new ReloadExecutionException();
}
}
public function afterException($controller, $methodName, \Exception $exception) {
if ($exception instanceof ReloadExecutionException) {
$this->session->remove('clearingExecutionContexts');
return new RedirectResponse($this->urlGenerator->linkToRouteAbsolute(
'core.login.showLoginForm',
['clear' => true] // this param the code in login.js may be removed when the "Clear-Site-Data" is working in the browsers
));
}
return parent::afterException($controller, $methodName, $exception);
}
}
@@ -0,0 +1,106 @@
<?php
/**
* @copyright 2017, Roeland Jago Douma <roeland@famdouma.nl>
*
* @author Christoph Wurst <christoph@winzerhof-wurst.at>
* @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/>.
*
*/
namespace OC\AppFramework\Middleware\Security;
use OC\AppFramework\Http\Request;
use OC\AppFramework\Middleware\Security\Exceptions\LaxSameSiteCookieFailedException;
use OC\AppFramework\Utility\ControllerMethodReflector;
use OCP\AppFramework\Http;
use OCP\AppFramework\Http\Response;
use OCP\AppFramework\Middleware;
class SameSiteCookieMiddleware extends Middleware {
/** @var Request */
private $request;
/** @var ControllerMethodReflector */
private $reflector;
public function __construct(Request $request,
ControllerMethodReflector $reflector) {
$this->request = $request;
$this->reflector = $reflector;
}
public function beforeController($controller, $methodName) {
$requestUri = $this->request->getScriptName();
$processingScript = explode('/', $requestUri);
$processingScript = $processingScript[count($processingScript) - 1];
if ($processingScript !== 'index.php') {
return;
}
$noSSC = $this->reflector->hasAnnotation('NoSameSiteCookieRequired');
if ($noSSC) {
return;
}
if (!$this->request->passesLaxCookieCheck()) {
throw new LaxSameSiteCookieFailedException();
}
}
public function afterException($controller, $methodName, \Exception $exception) {
if ($exception instanceof LaxSameSiteCookieFailedException) {
$respone = new Response();
$respone->setStatus(Http::STATUS_FOUND);
$respone->addHeader('Location', $this->request->getRequestUri());
$this->setSameSiteCookie();
return $respone;
}
throw $exception;
}
protected function setSameSiteCookie() {
$cookieParams = $this->request->getCookieParams();
$secureCookie = ($cookieParams['secure'] === true) ? 'secure; ' : '';
$policies = [
'lax',
'strict',
];
// Append __Host to the cookie if it meets the requirements
$cookiePrefix = '';
if ($cookieParams['secure'] === true && $cookieParams['path'] === '/') {
$cookiePrefix = '__Host-';
}
foreach ($policies as $policy) {
header(
sprintf(
'Set-Cookie: %snc_sameSiteCookie%s=true; path=%s; httponly;' . $secureCookie . 'expires=Fri, 31-Dec-2100 23:59:59 GMT; SameSite=%s',
$cookiePrefix,
$policy,
$cookieParams['path'],
$policy
),
false
);
}
}
}
@@ -0,0 +1,345 @@
<?php
declare(strict_types=1);
/**
* @copyright Copyright (c) 2016, ownCloud, Inc.
*
* @author Bernhard Posselt <dev@bernhard-posselt.com>
* @author Bjoern Schiessle <bjoern@schiessle.org>
* @author Christoph Wurst <christoph@winzerhof-wurst.at>
* @author Daniel Kesselberg <mail@danielkesselberg.de>
* @author Holger Hees <holger.hees@gmail.com>
* @author Joas Schilling <coding@schilljs.com>
* @author Julien Veyssier <eneiluj@posteo.net>
* @author Lukas Reschke <lukas@statuscode.ch>
* @author Morris Jobke <hey@morrisjobke.de>
* @author Roeland Jago Douma <roeland@famdouma.nl>
* @author Stefan Weil <sw@weilnetz.de>
* @author Thomas Müller <thomas.mueller@tmit.eu>
* @author Thomas Tanghus <thomas@tanghus.net>
*
* @license AGPL-3.0
*
* This code is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* 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, version 3,
* along with this program. If not, see <http://www.gnu.org/licenses/>
*
*/
namespace OC\AppFramework\Middleware\Security;
use OC\AppFramework\Middleware\Security\Exceptions\AppNotEnabledException;
use OC\AppFramework\Middleware\Security\Exceptions\CrossSiteRequestForgeryException;
use OC\AppFramework\Middleware\Security\Exceptions\NotAdminException;
use OC\AppFramework\Middleware\Security\Exceptions\NotLoggedInException;
use OC\AppFramework\Middleware\Security\Exceptions\SecurityException;
use OC\AppFramework\Middleware\Security\Exceptions\StrictCookieMissingException;
use OC\AppFramework\Utility\ControllerMethodReflector;
use OC\Settings\AuthorizedGroupMapper;
use OCP\App\AppPathNotFoundException;
use OCP\App\IAppManager;
use OCP\AppFramework\Controller;
use OCP\AppFramework\Http\Attribute\AuthorizedAdminSetting;
use OCP\AppFramework\Http\Attribute\NoAdminRequired;
use OCP\AppFramework\Http\Attribute\NoCSRFRequired;
use OCP\AppFramework\Http\Attribute\PublicPage;
use OCP\AppFramework\Http\Attribute\StrictCookiesRequired;
use OCP\AppFramework\Http\Attribute\SubAdminRequired;
use OCP\AppFramework\Http\JSONResponse;
use OCP\AppFramework\Http\RedirectResponse;
use OCP\AppFramework\Http\Response;
use OCP\AppFramework\Http\TemplateResponse;
use OCP\AppFramework\Middleware;
use OCP\AppFramework\OCSController;
use OCP\IL10N;
use OCP\INavigationManager;
use OCP\IRequest;
use OCP\IURLGenerator;
use OCP\IUserSession;
use OCP\Util;
use Psr\Log\LoggerInterface;
use ReflectionMethod;
/**
* Used to do all the authentication and checking stuff for a controller method
* It reads out the annotations of a controller method and checks which if
* security things should be checked and also handles errors in case a security
* check fails
*/
class SecurityMiddleware extends Middleware {
/** @var INavigationManager */
private $navigationManager;
/** @var IRequest */
private $request;
/** @var ControllerMethodReflector */
private $reflector;
/** @var string */
private $appName;
/** @var IURLGenerator */
private $urlGenerator;
/** @var LoggerInterface */
private $logger;
/** @var bool */
private $isLoggedIn;
/** @var bool */
private $isAdminUser;
/** @var bool */
private $isSubAdmin;
/** @var IAppManager */
private $appManager;
/** @var IL10N */
private $l10n;
/** @var AuthorizedGroupMapper */
private $groupAuthorizationMapper;
/** @var IUserSession */
private $userSession;
public function __construct(IRequest $request,
ControllerMethodReflector $reflector,
INavigationManager $navigationManager,
IURLGenerator $urlGenerator,
LoggerInterface $logger,
string $appName,
bool $isLoggedIn,
bool $isAdminUser,
bool $isSubAdmin,
IAppManager $appManager,
IL10N $l10n,
AuthorizedGroupMapper $mapper,
IUserSession $userSession
) {
$this->navigationManager = $navigationManager;
$this->request = $request;
$this->reflector = $reflector;
$this->appName = $appName;
$this->urlGenerator = $urlGenerator;
$this->logger = $logger;
$this->isLoggedIn = $isLoggedIn;
$this->isAdminUser = $isAdminUser;
$this->isSubAdmin = $isSubAdmin;
$this->appManager = $appManager;
$this->l10n = $l10n;
$this->groupAuthorizationMapper = $mapper;
$this->userSession = $userSession;
}
/**
* This runs all the security checks before a method call. The
* security checks are determined by inspecting the controller method
* annotations
*
* @param Controller $controller the controller
* @param string $methodName the name of the method
* @throws SecurityException when a security check fails
*
* @suppress PhanUndeclaredClassConstant
*/
public function beforeController($controller, $methodName) {
// this will set the current navigation entry of the app, use this only
// for normal HTML requests and not for AJAX requests
$this->navigationManager->setActiveEntry($this->appName);
if (get_class($controller) === \OCA\Talk\Controller\PageController::class && $methodName === 'showCall') {
$this->navigationManager->setActiveEntry('spreed');
}
$reflectionMethod = new ReflectionMethod($controller, $methodName);
// security checks
$isPublicPage = $this->hasAnnotationOrAttribute($reflectionMethod, 'PublicPage', PublicPage::class);
if (!$isPublicPage) {
if (!$this->isLoggedIn) {
throw new NotLoggedInException();
}
$authorized = false;
if ($this->hasAnnotationOrAttribute($reflectionMethod, 'AuthorizedAdminSetting', AuthorizedAdminSetting::class)) {
$authorized = $this->isAdminUser;
if (!$authorized && $this->hasAnnotationOrAttribute($reflectionMethod, 'SubAdminRequired', SubAdminRequired::class)) {
$authorized = $this->isSubAdmin;
}
if (!$authorized) {
$settingClasses = $this->getAuthorizedAdminSettingClasses($reflectionMethod);
$authorizedClasses = $this->groupAuthorizationMapper->findAllClassesForUser($this->userSession->getUser());
foreach ($settingClasses as $settingClass) {
$authorized = in_array($settingClass, $authorizedClasses, true);
if ($authorized) {
break;
}
}
}
if (!$authorized) {
throw new NotAdminException($this->l10n->t('Logged in user must be an admin, a sub admin or gotten special right to access this setting'));
}
}
if ($this->hasAnnotationOrAttribute($reflectionMethod, 'SubAdminRequired', SubAdminRequired::class)
&& !$this->isSubAdmin
&& !$this->isAdminUser
&& !$authorized) {
throw new NotAdminException($this->l10n->t('Logged in user must be an admin or sub admin'));
}
if (!$this->hasAnnotationOrAttribute($reflectionMethod, 'SubAdminRequired', SubAdminRequired::class)
&& !$this->hasAnnotationOrAttribute($reflectionMethod, 'NoAdminRequired', NoAdminRequired::class)
&& !$this->isAdminUser
&& !$authorized) {
throw new NotAdminException($this->l10n->t('Logged in user must be an admin'));
}
}
// Check for strict cookie requirement
if ($this->hasAnnotationOrAttribute($reflectionMethod, 'StrictCookieRequired', StrictCookiesRequired::class) ||
!$this->hasAnnotationOrAttribute($reflectionMethod, 'NoCSRFRequired', NoCSRFRequired::class)) {
if (!$this->request->passesStrictCookieCheck()) {
throw new StrictCookieMissingException();
}
}
// CSRF check - also registers the CSRF token since the session may be closed later
Util::callRegister();
if ($this->isInvalidCSRFRequired($reflectionMethod)) {
/*
* Only allow the CSRF check to fail on OCS Requests. This kind of
* hacks around that we have no full token auth in place yet and we
* do want to offer CSRF checks for web requests.
*
* Additionally we allow Bearer authenticated requests to pass on OCS routes.
* This allows oauth apps (e.g. moodle) to use the OCS endpoints
*/
if (!$controller instanceof OCSController || !$this->isValidOCSRequest()) {
throw new CrossSiteRequestForgeryException();
}
}
/**
* Checks if app is enabled (also includes a check whether user is allowed to access the resource)
* The getAppPath() check is here since components such as settings also use the AppFramework and
* therefore won't pass this check.
* If page is public, app does not need to be enabled for current user/visitor
*/
try {
$appPath = $this->appManager->getAppPath($this->appName);
} catch (AppPathNotFoundException $e) {
$appPath = false;
}
if ($appPath !== false && !$isPublicPage && !$this->appManager->isEnabledForUser($this->appName)) {
throw new AppNotEnabledException();
}
}
private function isInvalidCSRFRequired(ReflectionMethod $reflectionMethod): bool {
if ($this->hasAnnotationOrAttribute($reflectionMethod, 'NoCSRFRequired', NoCSRFRequired::class)) {
return false;
}
return !$this->request->passesCSRFCheck();
}
private function isValidOCSRequest(): bool {
return $this->request->getHeader('OCS-APIREQUEST') === 'true'
|| str_starts_with($this->request->getHeader('Authorization'), 'Bearer ');
}
/**
* @template T
*
* @param ReflectionMethod $reflectionMethod
* @param string $annotationName
* @param class-string<T> $attributeClass
* @return boolean
*/
protected function hasAnnotationOrAttribute(ReflectionMethod $reflectionMethod, string $annotationName, string $attributeClass): bool {
if (!empty($reflectionMethod->getAttributes($attributeClass))) {
return true;
}
if ($this->reflector->hasAnnotation($annotationName)) {
return true;
}
return false;
}
/**
* @param ReflectionMethod $reflectionMethod
* @return string[]
*/
protected function getAuthorizedAdminSettingClasses(ReflectionMethod $reflectionMethod): array {
$classes = [];
if ($this->reflector->hasAnnotation('AuthorizedAdminSetting')) {
$classes = explode(';', $this->reflector->getAnnotationParameter('AuthorizedAdminSetting', 'settings'));
}
$attributes = $reflectionMethod->getAttributes(AuthorizedAdminSetting::class);
if (!empty($attributes)) {
foreach ($attributes as $attribute) {
/** @var AuthorizedAdminSetting $setting */
$setting = $attribute->newInstance();
$classes[] = $setting->getSettings();
}
}
return $classes;
}
/**
* If an SecurityException is being caught, ajax requests return a JSON error
* response and non ajax requests redirect to the index
*
* @param Controller $controller the controller that is being called
* @param string $methodName the name of the method that will be called on
* the controller
* @param \Exception $exception the thrown exception
* @return Response a Response object or null in case that the exception could not be handled
* @throws \Exception the passed in exception if it can't handle it
*/
public function afterException($controller, $methodName, \Exception $exception): Response {
if ($exception instanceof SecurityException) {
if ($exception instanceof StrictCookieMissingException) {
return new RedirectResponse(\OC::$WEBROOT . '/');
}
if (stripos($this->request->getHeader('Accept'), 'html') === false) {
$response = new JSONResponse(
['message' => $exception->getMessage()],
$exception->getCode()
);
} else {
if ($exception instanceof NotLoggedInException) {
$params = [];
if (isset($this->request->server['REQUEST_URI'])) {
$params['redirect_url'] = $this->request->server['REQUEST_URI'];
}
$usernamePrefill = $this->request->getParam('user', '');
if ($usernamePrefill !== '') {
$params['user'] = $usernamePrefill;
}
if ($this->request->getParam('direct')) {
$params['direct'] = 1;
}
$url = $this->urlGenerator->linkToRoute('core.login.showLoginForm', $params);
$response = new RedirectResponse($url);
} else {
$response = new TemplateResponse('core', '403', ['message' => $exception->getMessage()], 'guest');
$response->setStatus($exception->getCode());
}
}
$this->logger->debug($exception->getMessage(), [
'exception' => $exception,
]);
return $response;
}
throw $exception;
}
}
@@ -0,0 +1,97 @@
<?php
declare(strict_types=1);
/**
* @copyright Copyright (c) 2016, ownCloud, Inc.
*
* @author Christoph Wurst <christoph@winzerhof-wurst.at>
* @author Lukas Reschke <lukas@statuscode.ch>
* @author Morris Jobke <hey@morrisjobke.de>
* @author Roeland Jago Douma <roeland@famdouma.nl>
* @author Thomas Müller <thomas.mueller@tmit.eu>
*
* @license AGPL-3.0
*
* This code is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* 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, version 3,
* along with this program. If not, see <http://www.gnu.org/licenses/>
*
*/
namespace OC\AppFramework\Middleware;
use OC\AppFramework\Utility\ControllerMethodReflector;
use OCP\AppFramework\Controller;
use OCP\AppFramework\Http\Attribute\UseSession;
use OCP\AppFramework\Http\Response;
use OCP\AppFramework\Middleware;
use OCP\ISession;
use ReflectionMethod;
class SessionMiddleware extends Middleware {
/** @var ControllerMethodReflector */
private $reflector;
/** @var ISession */
private $session;
public function __construct(ControllerMethodReflector $reflector,
ISession $session) {
$this->reflector = $reflector;
$this->session = $session;
}
/**
* @param Controller $controller
* @param string $methodName
*/
public function beforeController($controller, $methodName) {
/**
* Annotation deprecated with Nextcloud 26
*/
$hasAnnotation = $this->reflector->hasAnnotation('UseSession');
if ($hasAnnotation) {
$this->session->reopen();
return;
}
$reflectionMethod = new ReflectionMethod($controller, $methodName);
$hasAttribute = !empty($reflectionMethod->getAttributes(UseSession::class));
if ($hasAttribute) {
$this->session->reopen();
}
}
/**
* @param Controller $controller
* @param string $methodName
* @param Response $response
* @return Response
*/
public function afterController($controller, $methodName, Response $response) {
/**
* Annotation deprecated with Nextcloud 26
*/
$hasAnnotation = $this->reflector->hasAnnotation('UseSession');
if ($hasAnnotation) {
$this->session->close();
return $response;
}
$reflectionMethod = new ReflectionMethod($controller, $methodName);
$hasAttribute = !empty($reflectionMethod->getAttributes(UseSession::class));
if ($hasAttribute) {
$this->session->close();
}
return $response;
}
}