diff options
27 files changed, 701 insertions, 242 deletions
diff --git a/apps/dav/l10n/sv.js b/apps/dav/l10n/sv.js index 28eea0c5867..25cd0d797c8 100644 --- a/apps/dav/l10n/sv.js +++ b/apps/dav/l10n/sv.js @@ -2,7 +2,7 @@ OC.L10N.register( "dav", { "Calendar" : "Kalender", - "Todos" : "Att göra", + "Todos" : "Uppgifter", "{actor} created calendar {calendar}" : "{actor} skapade kalender {calendar}", "You created calendar {calendar}" : "Du skapade kalender {calendar}", "{actor} deleted calendar {calendar}" : "{actor} raderade kalender {calendar}", @@ -26,20 +26,20 @@ OC.L10N.register( "You deleted event {event} from calendar {calendar}" : "Du raderade händelse {event} från kalender {calendar}", "{actor} updated event {event} in calendar {calendar}" : "{actor} uppdaterade händelse {event} i kalender {calendar}", "You updated event {event} in calendar {calendar}" : "Du uppdaterade händelse {event} i kalender {calendar}", - "{actor} created todo {todo} in list {calendar}" : "{actor} skapade att-göra {todo} i listan {calendar}", - "You created todo {todo} in list {calendar}" : "Du skapade att-göra {todo} i listan {calendar}", - "{actor} deleted todo {todo} from list {calendar}" : "{actor} raderade att-göra {todo} från listan {calendar}", - "You deleted todo {todo} from list {calendar}" : "Du raderade att-göra {todo} från listan {calendar}", - "{actor} updated todo {todo} in list {calendar}" : "{actor} uppdaterade att-göra {todo} i listan {calendar}", - "You updated todo {todo} in list {calendar}" : "Du uppdaterade att-göra {todo} i listan {calendar}", - "{actor} solved todo {todo} in list {calendar}" : "{actor} löste att-göra {todo} i listan {calendar}", - "You solved todo {todo} in list {calendar}" : "Du löste att-göra {todo} i listan {calendar}", - "{actor} reopened todo {todo} in list {calendar}" : "{actor} återupptog att-göra {todo} i listan {calendar}", - "You reopened todo {todo} in list {calendar}" : "Du återupptog att-göra {todo} i listan {calendar}", + "{actor} created todo {todo} in list {calendar}" : "{actor} skapade uppgift {todo} i listan {calendar}", + "You created todo {todo} in list {calendar}" : "Du skapade uppgift {todo} i listan {calendar}", + "{actor} deleted todo {todo} from list {calendar}" : "{actor} raderade uppgift {todo} från listan {calendar}", + "You deleted todo {todo} from list {calendar}" : "Du raderade uppgift {todo} från listan {calendar}", + "{actor} updated todo {todo} in list {calendar}" : "{actor} uppdaterade uppgift {todo} i listan {calendar}", + "You updated todo {todo} in list {calendar}" : "Du uppdaterade uppgift {todo} i listan {calendar}", + "{actor} solved todo {todo} in list {calendar}" : "{actor} löste uppgift {todo} i listan {calendar}", + "You solved todo {todo} in list {calendar}" : "Du löste uppgift {todo} i listan {calendar}", + "{actor} reopened todo {todo} in list {calendar}" : "{actor} återupptog uppgift {todo} i listan {calendar}", + "You reopened todo {todo} in list {calendar}" : "Du återupptog uppgift {todo} i listan {calendar}", "A <strong>calendar</strong> was modified" : "En <strong>kalender</strong> modifierades", "A calendar <strong>event</strong> was modified" : "En kalender-<strong>händelse</strong> modifierades", - "A calendar <strong>todo</strong> was modified" : "En kalender <strong>att-göra</strong> modifierades", - "Contact birthdays" : "Kontakters födelsedagar", + "A calendar <strong>todo</strong> was modified" : "En kalender <strong>uppgift</strong> modifierades", + "Contact birthdays" : "Födelsedagar", "Personal" : "Privat", "Contacts" : "Kontakter", "Technical details" : "Tekniska detaljer", diff --git a/apps/dav/l10n/sv.json b/apps/dav/l10n/sv.json index 8dfb9e4e6ba..ba76ca55329 100644 --- a/apps/dav/l10n/sv.json +++ b/apps/dav/l10n/sv.json @@ -1,6 +1,6 @@ { "translations": { "Calendar" : "Kalender", - "Todos" : "Att göra", + "Todos" : "Uppgifter", "{actor} created calendar {calendar}" : "{actor} skapade kalender {calendar}", "You created calendar {calendar}" : "Du skapade kalender {calendar}", "{actor} deleted calendar {calendar}" : "{actor} raderade kalender {calendar}", @@ -24,20 +24,20 @@ "You deleted event {event} from calendar {calendar}" : "Du raderade händelse {event} från kalender {calendar}", "{actor} updated event {event} in calendar {calendar}" : "{actor} uppdaterade händelse {event} i kalender {calendar}", "You updated event {event} in calendar {calendar}" : "Du uppdaterade händelse {event} i kalender {calendar}", - "{actor} created todo {todo} in list {calendar}" : "{actor} skapade att-göra {todo} i listan {calendar}", - "You created todo {todo} in list {calendar}" : "Du skapade att-göra {todo} i listan {calendar}", - "{actor} deleted todo {todo} from list {calendar}" : "{actor} raderade att-göra {todo} från listan {calendar}", - "You deleted todo {todo} from list {calendar}" : "Du raderade att-göra {todo} från listan {calendar}", - "{actor} updated todo {todo} in list {calendar}" : "{actor} uppdaterade att-göra {todo} i listan {calendar}", - "You updated todo {todo} in list {calendar}" : "Du uppdaterade att-göra {todo} i listan {calendar}", - "{actor} solved todo {todo} in list {calendar}" : "{actor} löste att-göra {todo} i listan {calendar}", - "You solved todo {todo} in list {calendar}" : "Du löste att-göra {todo} i listan {calendar}", - "{actor} reopened todo {todo} in list {calendar}" : "{actor} återupptog att-göra {todo} i listan {calendar}", - "You reopened todo {todo} in list {calendar}" : "Du återupptog att-göra {todo} i listan {calendar}", + "{actor} created todo {todo} in list {calendar}" : "{actor} skapade uppgift {todo} i listan {calendar}", + "You created todo {todo} in list {calendar}" : "Du skapade uppgift {todo} i listan {calendar}", + "{actor} deleted todo {todo} from list {calendar}" : "{actor} raderade uppgift {todo} från listan {calendar}", + "You deleted todo {todo} from list {calendar}" : "Du raderade uppgift {todo} från listan {calendar}", + "{actor} updated todo {todo} in list {calendar}" : "{actor} uppdaterade uppgift {todo} i listan {calendar}", + "You updated todo {todo} in list {calendar}" : "Du uppdaterade uppgift {todo} i listan {calendar}", + "{actor} solved todo {todo} in list {calendar}" : "{actor} löste uppgift {todo} i listan {calendar}", + "You solved todo {todo} in list {calendar}" : "Du löste uppgift {todo} i listan {calendar}", + "{actor} reopened todo {todo} in list {calendar}" : "{actor} återupptog uppgift {todo} i listan {calendar}", + "You reopened todo {todo} in list {calendar}" : "Du återupptog uppgift {todo} i listan {calendar}", "A <strong>calendar</strong> was modified" : "En <strong>kalender</strong> modifierades", "A calendar <strong>event</strong> was modified" : "En kalender-<strong>händelse</strong> modifierades", - "A calendar <strong>todo</strong> was modified" : "En kalender <strong>att-göra</strong> modifierades", - "Contact birthdays" : "Kontakters födelsedagar", + "A calendar <strong>todo</strong> was modified" : "En kalender <strong>uppgift</strong> modifierades", + "Contact birthdays" : "Födelsedagar", "Personal" : "Privat", "Contacts" : "Kontakter", "Technical details" : "Tekniska detaljer", diff --git a/apps/federatedfilesharing/lib/Controller/MountPublicLinkController.php b/apps/federatedfilesharing/lib/Controller/MountPublicLinkController.php index 55329338a92..3c399268124 100644 --- a/apps/federatedfilesharing/lib/Controller/MountPublicLinkController.php +++ b/apps/federatedfilesharing/lib/Controller/MountPublicLinkController.php @@ -25,18 +25,24 @@ namespace OCA\FederatedFileSharing\Controller; +use OC\Files\Filesystem; use OC\HintException; +use OC\Share\Helper; use OCA\FederatedFileSharing\AddressHandler; +use OCA\FederatedFileSharing\DiscoveryManager; use OCA\FederatedFileSharing\FederatedShareProvider; +use OCA\Files_Sharing\External\Manager; use OCP\AppFramework\Controller; use OCP\AppFramework\Http; use OCP\AppFramework\Http\JSONResponse; +use OCP\Files\StorageInvalidException; use OCP\Http\Client\IClientService; use OCP\IL10N; use OCP\IRequest; use OCP\ISession; use OCP\IUserSession; use OCP\Share\IManager; +use OCP\Util; /** * Class MountPublicLinkController @@ -107,6 +113,7 @@ class MountPublicLinkController extends Controller { * * @NoCSRFRequired * @PublicPage + * @BruteForceProtection publicLink2FederatedShare * * @param string $shareWith * @param string $token @@ -226,22 +233,22 @@ class MountPublicLinkController extends Controller { private function legacyMountPublicLink($token, $remote, $password, $name, $owner, $ownerDisplayName) { // Check for invalid name - if (!\OCP\Util::isValidFileName($name)) { + if (!Util::isValidFileName($name)) { return new JSONResponse(['message' => $this->l->t('The mountpoint name contains invalid characters.')], Http::STATUS_BAD_REQUEST); } $currentUser = $this->userSession->getUser()->getUID(); $currentServer = $this->addressHandler->generateRemoteURL(); - if (\OC\Share\Helper::isSameUserOnSameServer($owner, $remote, $currentUser, $currentServer)) { + if (Helper::isSameUserOnSameServer($owner, $remote, $currentUser, $currentServer)) { return new JSONResponse(['message' => $this->l->t('Not allowed to create a federated share with the owner.')], Http::STATUS_BAD_REQUEST); } - $discoveryManager = new \OCA\FederatedFileSharing\DiscoveryManager( + $discoveryManager = new DiscoveryManager( \OC::$server->getMemCacheFactory(), \OC::$server->getHTTPClientService() ); - $externalManager = new \OCA\Files_Sharing\External\Manager( + $externalManager = new Manager( \OC::$server->getDatabaseConnection(), - \OC\Files\Filesystem::getMountManager(), - \OC\Files\Filesystem::getLoader(), + Filesystem::getMountManager(), + Filesystem::getLoader(), \OC::$server->getHTTPClientService(), \OC::$server->getNotificationManager(), $discoveryManager, @@ -249,7 +256,8 @@ class MountPublicLinkController extends Controller { ); // check for ssl cert - if (substr($remote, 0, 5) === 'https') { + + if (strpos($remote, 'https') === 0) { try { $client = $this->clientService->newClient(); $client->get($remote, [ @@ -268,19 +276,19 @@ class MountPublicLinkController extends Controller { try { // check if storage exists $storage->checkStorageAvailability(); - } catch (\OCP\Files\StorageInvalidException $e) { + } catch (StorageInvalidException $e) { // note: checkStorageAvailability will already remove the invalid share - \OCP\Util::writeLog( + Util::writeLog( 'federatedfilesharing', 'Invalid remote storage: ' . get_class($e) . ': ' . $e->getMessage(), - \OCP\Util::DEBUG + Util::DEBUG ); return new JSONResponse(['message' => $this->l->t('Could not authenticate to remote share, password might be wrong')], Http::STATUS_BAD_REQUEST); } catch (\Exception $e) { - \OCP\Util::writeLog( + Util::writeLog( 'federatedfilesharing', 'Invalid remote storage: ' . get_class($e) . ': ' . $e->getMessage(), - \OCP\Util::DEBUG + Util::DEBUG ); $externalManager->removeShare($mount->getMountPoint()); return new JSONResponse(['message' => $this->l->t('Storage not valid')], Http::STATUS_BAD_REQUEST); @@ -295,27 +303,27 @@ class MountPublicLinkController extends Controller { 'legacyMount' => '1' ] ); - } catch (\OCP\Files\StorageInvalidException $e) { - \OCP\Util::writeLog( + } catch (StorageInvalidException $e) { + Util::writeLog( 'federatedfilesharing', 'Invalid remote storage: ' . get_class($e) . ': ' . $e->getMessage(), - \OCP\Util::DEBUG + Util::DEBUG ); return new JSONResponse(['message' => $this->l->t('Storage not valid')], Http::STATUS_BAD_REQUEST); } catch (\Exception $e) { - \OCP\Util::writeLog( + Util::writeLog( 'federatedfilesharing', 'Invalid remote storage: ' . get_class($e) . ': ' . $e->getMessage(), - \OCP\Util::DEBUG + Util::DEBUG ); return new JSONResponse(['message' => $this->l->t('Couldn\'t add remote share')], Http::STATUS_BAD_REQUEST); } } else { $externalManager->removeShare($mount->getMountPoint()); - \OCP\Util::writeLog( + Util::writeLog( 'federatedfilesharing', 'Couldn\'t add remote share', - \OCP\Util::DEBUG + Util::DEBUG ); return new JSONResponse(['message' => $this->l->t('Couldn\'t add remote share')], Http::STATUS_BAD_REQUEST); } diff --git a/apps/theming/lib/Controller/ThemingController.php b/apps/theming/lib/Controller/ThemingController.php index 15bbfc30c54..24ac1c7d8d5 100644 --- a/apps/theming/lib/Controller/ThemingController.php +++ b/apps/theming/lib/Controller/ThemingController.php @@ -374,6 +374,7 @@ class ThemingController extends Controller { '; $responseCss .= sprintf('.nc-theming-main-background {background-color: %s}' . "\n", $color); $responseCss .= sprintf('.nc-theming-main-text {color: %s}' . "\n", $color); + $responseCss .= sprintf('#app-navigation li:hover > a, #app-navigation li:focus > a, #app-navigation a:focus, #app-navigation .selected, #app-navigation .selected a, #app-navigation .active, #app-navigation .active a {box-shadow: inset 2px 0 %s}' . "\n", $color); } $logo = $this->config->getAppValue($this->appName, 'logoMime'); diff --git a/apps/theming/tests/Controller/ThemingControllerTest.php b/apps/theming/tests/Controller/ThemingControllerTest.php index f7c4a9f120e..97a5e985860 100644 --- a/apps/theming/tests/Controller/ThemingControllerTest.php +++ b/apps/theming/tests/Controller/ThemingControllerTest.php @@ -487,6 +487,7 @@ class ThemingControllerTest extends TestCase { '; $expectedData .= sprintf('.nc-theming-main-background {background-color: %s}' . "\n", $color); $expectedData .= sprintf('.nc-theming-main-text {color: %s}' . "\n", $color); + $expectedData .= sprintf('#app-navigation li:hover > a, #app-navigation li:focus > a, #app-navigation a:focus, #app-navigation .selected, #app-navigation .selected a, #app-navigation .active, #app-navigation .active a {box-shadow: inset 2px 0 %s}' . "\n", $color); $expectedData .= '.nc-theming-contrast {color: #ffffff}' . "\n"; $expectedData .= '.icon-file,.icon-filetype-text {' . 'background-image: url(\'./img/core/filetypes/text.svg?v=0\');' . "}\n" . @@ -581,6 +582,7 @@ class ThemingControllerTest extends TestCase { '; $expectedData .= sprintf('.nc-theming-main-background {background-color: %s}' . "\n", $color); $expectedData .= sprintf('.nc-theming-main-text {color: %s}' . "\n", $color); + $expectedData .= sprintf('#app-navigation li:hover > a, #app-navigation li:focus > a, #app-navigation a:focus, #app-navigation .selected, #app-navigation .selected a, #app-navigation .active, #app-navigation .active a {box-shadow: inset 2px 0 %s}' . "\n", $color); $expectedData .= '#header .header-appname, #expandDisplayName { color: #000000; }' . "\n"; $expectedData .= '#header .icon-caret { background-image: url(\'' . \OC::$WEBROOT . '/core/img/actions/caret-dark.svg\'); }' . "\n"; $expectedData .= '.searchbox input[type="search"] { background: transparent url(\'' . \OC::$WEBROOT . '/core/img/actions/search.svg\') no-repeat 6px center; color: #000; }' . "\n"; @@ -768,6 +770,7 @@ class ThemingControllerTest extends TestCase { '; $expectedData .= sprintf('.nc-theming-main-background {background-color: %s}' . "\n", $color); $expectedData .= sprintf('.nc-theming-main-text {color: %s}' . "\n", $color); + $expectedData .= sprintf('#app-navigation li:hover > a, #app-navigation li:focus > a, #app-navigation a:focus, #app-navigation .selected, #app-navigation .selected a, #app-navigation .active, #app-navigation .active a {box-shadow: inset 2px 0 %s}' . "\n", $color); $expectedData .= sprintf( '#header .logo {' . 'background-image: url(\'./logo?v=0\');' . @@ -879,6 +882,7 @@ class ThemingControllerTest extends TestCase { '; $expectedData .= sprintf('.nc-theming-main-background {background-color: %s}' . "\n", $color); $expectedData .= sprintf('.nc-theming-main-text {color: %s}' . "\n", $color); + $expectedData .= sprintf('#app-navigation li:hover > a, #app-navigation li:focus > a, #app-navigation a:focus, #app-navigation .selected, #app-navigation .selected a, #app-navigation .active, #app-navigation .active a {box-shadow: inset 2px 0 %s}' . "\n", $color); $expectedData .= sprintf( '#header .logo {' . 'background-image: url(\'./logo?v=0\');' . diff --git a/apps/twofactor_backupcodes/appinfo/app.php b/apps/twofactor_backupcodes/appinfo/app.php index 31f9b6b8eae..0cb10531360 100644 --- a/apps/twofactor_backupcodes/appinfo/app.php +++ b/apps/twofactor_backupcodes/appinfo/app.php @@ -19,4 +19,7 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. * */ + +// @codeCoverageIgnoreStart OC_App::registerPersonal('twofactor_backupcodes', 'settings/personal'); +// @codeCoverageIgnoreEnd diff --git a/apps/twofactor_backupcodes/appinfo/routes.php b/apps/twofactor_backupcodes/appinfo/routes.php index f2af12e9b45..0119bfd0b08 100644 --- a/apps/twofactor_backupcodes/appinfo/routes.php +++ b/apps/twofactor_backupcodes/appinfo/routes.php @@ -19,6 +19,7 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. * */ +// @codeCoverageIgnoreStart return [ 'routes' => [ [ @@ -33,3 +34,4 @@ return [ ], ] ]; +// @codeCoverageIgnoreEnd diff --git a/apps/twofactor_backupcodes/l10n/de.js b/apps/twofactor_backupcodes/l10n/de.js index 96e1fca0759..039b8bea68b 100644 --- a/apps/twofactor_backupcodes/l10n/de.js +++ b/apps/twofactor_backupcodes/l10n/de.js @@ -13,7 +13,7 @@ OC.L10N.register( "Two-factor authentication" : "Zwei-Faktor Authentifizierung", "You successfully logged in using two-factor authentication (%1$s)" : "Erfolgreich mittels Zwei-Faktorauthentifizierung angemeldet (%1$s)", "A login attempt using two-factor authentication failed (%1$s)" : "Ein Anmeldeversuch mittels Zwei-Faktorauthentifizierung gescheitert (%1$s)", - "You created two-factor backup codes for your account" : "Du hast Zwei-Faktor Sicherungs-Codes für Ihr Konto erstellt", + "You created two-factor backup codes for your account" : "Du hast Zwei-Faktor Sicherungs-Codes für Dein Konto erstellt", "Backup code" : "Backup-Code", "Use backup code" : "Backup-Code verwenden", "Second-factor backup codes" : "Zweitfaktor-Backup-Codes" diff --git a/apps/twofactor_backupcodes/l10n/de.json b/apps/twofactor_backupcodes/l10n/de.json index 6f582fc8bf0..6afdfa52ac4 100644 --- a/apps/twofactor_backupcodes/l10n/de.json +++ b/apps/twofactor_backupcodes/l10n/de.json @@ -11,7 +11,7 @@ "Two-factor authentication" : "Zwei-Faktor Authentifizierung", "You successfully logged in using two-factor authentication (%1$s)" : "Erfolgreich mittels Zwei-Faktorauthentifizierung angemeldet (%1$s)", "A login attempt using two-factor authentication failed (%1$s)" : "Ein Anmeldeversuch mittels Zwei-Faktorauthentifizierung gescheitert (%1$s)", - "You created two-factor backup codes for your account" : "Du hast Zwei-Faktor Sicherungs-Codes für Ihr Konto erstellt", + "You created two-factor backup codes for your account" : "Du hast Zwei-Faktor Sicherungs-Codes für Dein Konto erstellt", "Backup code" : "Backup-Code", "Use backup code" : "Backup-Code verwenden", "Second-factor backup codes" : "Zweitfaktor-Backup-Codes" diff --git a/apps/twofactor_backupcodes/lib/Activity/Provider.php b/apps/twofactor_backupcodes/lib/Activity/Provider.php index cfb16c9f8d3..9c7aaeae630 100644 --- a/apps/twofactor_backupcodes/lib/Activity/Provider.php +++ b/apps/twofactor_backupcodes/lib/Activity/Provider.php @@ -40,6 +40,11 @@ class Provider implements IProvider { /** @var ILogger */ private $logger; + /** + * @param L10nFactory $l10n + * @param IURLGenerator $urlGenerator + * @param ILogger $logger + */ public function __construct(L10nFactory $l10n, IURLGenerator $urlGenerator, ILogger $logger) { $this->logger = $logger; $this->urlGenerator = $urlGenerator; diff --git a/apps/twofactor_backupcodes/lib/Db/BackupCodeMapper.php b/apps/twofactor_backupcodes/lib/Db/BackupCodeMapper.php index f64e2e9e60b..85cc174fb6a 100644 --- a/apps/twofactor_backupcodes/lib/Db/BackupCodeMapper.php +++ b/apps/twofactor_backupcodes/lib/Db/BackupCodeMapper.php @@ -22,7 +22,6 @@ namespace OCA\TwoFactorBackupCodes\Db; use OCP\AppFramework\Db\Mapper; -use OCP\DB\QueryBuilder\IQueryBuilder; use OCP\IDBConnection; use OCP\IUser; diff --git a/apps/twofactor_backupcodes/settings/personal.php b/apps/twofactor_backupcodes/settings/personal.php index 037516e39a3..0a018c0ff28 100644 --- a/apps/twofactor_backupcodes/settings/personal.php +++ b/apps/twofactor_backupcodes/settings/personal.php @@ -1,5 +1,6 @@ <?php - +// @codeCoverageIgnoreStart $tmpl = new \OCP\Template('twofactor_backupcodes', 'personal'); return $tmpl->fetchPage(); +// @codeCoverageIgnoreEnd diff --git a/apps/twofactor_backupcodes/tests/Unit/Activity/GenericProviderTest.php b/apps/twofactor_backupcodes/tests/Unit/Activity/GenericProviderTest.php new file mode 100644 index 00000000000..242c4ab4e8d --- /dev/null +++ b/apps/twofactor_backupcodes/tests/Unit/Activity/GenericProviderTest.php @@ -0,0 +1,132 @@ +<?php + +/** + * @author Christoph Wurst <christoph@winzerhof-wurst.at> + * @copyright Copyright (c) 2016 Christoph Wurst <christoph@winzerhof-wurst.at> + * + * Two-factor backup codes + * + * 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 OCA\TwoFactorBackupCodes\Test\Unit\Activity; + +use InvalidArgumentException; +use OCA\TwoFactorBackupCodes\Activity\GenericProvider; +use OCP\Activity\IEvent; +use OCP\IL10N; +use OCP\ILogger; +use OCP\IURLGenerator; +use OCP\L10N\IFactory; +use PHPUnit_Framework_MockObject_MockObject; +use Test\TestCase; + +class GenericProviderTest extends TestCase { + + /** @var IL10N|PHPUnit_Framework_MockObject_MockObject */ + private $l10n; + + /** @var IURLGenerator|PHPUnit_Framework_MockObject_MockObject */ + private $urlGenerator; + + /** @var ILogger|PHPUnit_Framework_MockObject_MockObject */ + private $logger; + + /** @var GenericProvider */ + private $provider; + + protected function setUp() { + parent::setUp(); + + $this->l10n = $this->createMock(IFactory::class); + $this->urlGenerator = $this->createMock(IURLGenerator::class); + $this->logger = $this->createMock(ILogger::class); + + $this->provider = new GenericProvider($this->l10n, $this->urlGenerator, $this->logger); + } + + public function testParseUnrelated() { + $lang = 'ru'; + $event = $this->createMock(IEvent::class); + $event->expects($this->once()) + ->method('getType') + ->willReturn('comments'); + $this->setExpectedException(InvalidArgumentException::class); + + $this->provider->parse($lang, $event); + } + + public function subjectData() { + return [ + ['twofactor_success'], + ['twofactor_failed'], + ]; + } + + /** + * @dataProvider subjectData + */ + public function testParse($subject) { + $lang = 'ru'; + $event = $this->createMock(IEvent::class); + $l = $this->createMock(IL10N::class); + + $event->expects($this->once()) + ->method('getType') + ->willReturn('twofactor'); + $this->l10n->expects($this->once()) + ->method('get') + ->with('core', $lang) + ->willReturn($l); + $this->urlGenerator->expects($this->once()) + ->method('imagePath') + ->with('core', 'actions/password.svg') + ->willReturn('path/to/image'); + $this->urlGenerator->expects($this->once()) + ->method('getAbsoluteURL') + ->with('path/to/image') + ->willReturn('absolute/path/to/image'); + $event->expects($this->once()) + ->method('setIcon') + ->with('absolute/path/to/image'); + $event->expects($this->once()) + ->method('getSubject') + ->willReturn($subject); + $event->expects($this->once()) + ->method('setParsedSubject'); + + $this->provider->parse($lang, $event); + } + + public function testParseInvalidSubject() { + $lang = 'ru'; + $l = $this->createMock(IL10N::class); + $event = $this->createMock(IEvent::class); + + $event->expects($this->once()) + ->method('getType') + ->willReturn('twofactor'); + $this->l10n->expects($this->once()) + ->method('get') + ->with('core', $lang) + ->willReturn($l); + $event->expects($this->once()) + ->method('getSubject') + ->willReturn('unrelated'); + + $this->expectException(InvalidArgumentException::class); + $this->provider->parse($lang, $event); + } + +} diff --git a/apps/twofactor_backupcodes/tests/Unit/Activity/ProviderTest.php b/apps/twofactor_backupcodes/tests/Unit/Activity/ProviderTest.php index 36e85ec1872..e1a13c89c10 100644 --- a/apps/twofactor_backupcodes/tests/Unit/Activity/ProviderTest.php +++ b/apps/twofactor_backupcodes/tests/Unit/Activity/ProviderTest.php @@ -2,7 +2,7 @@ /** * @author Christoph Wurst <christoph@winzerhof-wurst.at> - * @copyright Copyright (c) 2016 Christoph Wurst <christoph@winzerhof-wurst.at> + * @copyright Copyright (c) 2017 Christoph Wurst <christoph@winzerhof-wurst.at> * * Two-factor backup codes * @@ -23,21 +23,27 @@ namespace OCA\TwoFactorBackupCodes\Test\Unit\Activity; use InvalidArgumentException; -use OCA\TwoFactorBackupCodes\Activity\GenericProvider; +use OCA\TwoFactorBackupCodes\Activity\Provider; use OCP\Activity\IEvent; use OCP\IL10N; use OCP\ILogger; use OCP\IURLGenerator; use OCP\L10N\IFactory; +use PHPUnit_Framework_MockObject_MockObject; use Test\TestCase; class ProviderTest extends TestCase { + /** @var IL10N|PHPUnit_Framework_MockObject_MockObject */ private $l10n; + + /** @var IURLGenerator|PHPUnit_Framework_MockObject_MockObject */ private $urlGenerator; + + /** @var ILogger|PHPUnit_Framework_MockObject_MockObject */ private $logger; - /** @var GenericProvider */ + /** @var Provider */ private $provider; protected function setUp() { @@ -47,15 +53,15 @@ class ProviderTest extends TestCase { $this->urlGenerator = $this->createMock(IURLGenerator::class); $this->logger = $this->createMock(ILogger::class); - $this->provider = new GenericProvider($this->l10n, $this->urlGenerator, $this->logger); + $this->provider = new Provider($this->l10n, $this->urlGenerator, $this->logger); } public function testParseUnrelated() { $lang = 'ru'; $event = $this->createMock(IEvent::class); $event->expects($this->once()) - ->method('getType') - ->will($this->returnValue('comments')); + ->method('getApp') + ->willReturn('comments'); $this->setExpectedException(InvalidArgumentException::class); $this->provider->parse($lang, $event); @@ -63,8 +69,7 @@ class ProviderTest extends TestCase { public function subjectData() { return [ - ['twofactor_success'], - ['twofactor_failed'], + ['codes_generated'], ]; } @@ -77,30 +82,50 @@ class ProviderTest extends TestCase { $l = $this->createMock(IL10N::class); $event->expects($this->once()) - ->method('getType') - ->will($this->returnValue('twofactor')); + ->method('getApp') + ->willReturn('twofactor_backupcodes'); $this->l10n->expects($this->once()) ->method('get') - ->with('core', $lang) - ->will($this->returnValue($l)); + ->with('twofactor_backupcodes', $lang) + ->willReturn($l); $this->urlGenerator->expects($this->once()) ->method('imagePath') ->with('core', 'actions/password.svg') - ->will($this->returnValue('path/to/image')); + ->willReturn('path/to/image'); $this->urlGenerator->expects($this->once()) ->method('getAbsoluteURL') ->with('path/to/image') - ->will($this->returnValue('absolute/path/to/image')); + ->willReturn('absolute/path/to/image'); $event->expects($this->once()) ->method('setIcon') ->with('absolute/path/to/image'); $event->expects($this->once()) ->method('getSubject') - ->will($this->returnValue($subject)); + ->willReturn($subject); $event->expects($this->once()) ->method('setParsedSubject'); $this->provider->parse($lang, $event); } + public function testParseInvalidSubject() { + $lang = 'ru'; + $l = $this->createMock(IL10N::class); + $event = $this->createMock(IEvent::class); + + $event->expects($this->once()) + ->method('getApp') + ->willReturn('twofactor_backupcodes'); + $this->l10n->expects($this->once()) + ->method('get') + ->with('twofactor_backupcodes', $lang) + ->willReturn($l); + $event->expects($this->once()) + ->method('getSubject') + ->willReturn('unrelated'); + + $this->expectException(InvalidArgumentException::class); + $this->provider->parse($lang, $event); + } + } diff --git a/config/config.sample.php b/config/config.sample.php index a9bb0067e5b..534af5a3a31 100644 --- a/config/config.sample.php +++ b/config/config.sample.php @@ -74,8 +74,10 @@ $CONFIG = array( /** - * Where user files are stored; this defaults to ``data/`` in the Nextcloud - * directory. The SQLite database is also stored here, when you use SQLite. + * Where user files are stored. The SQLite database is also stored here, when + * you use SQLite. + * + * Default to ``data/`` in the Nextcloud directory. */ 'datadirectory' => '/var/www/nextcloud/data', @@ -93,7 +95,8 @@ $CONFIG = array( * - sqlite (SQLite3) * - mysql (MySQL/MariaDB) * - pgsql (PostgreSQL) - * - oci (Oracle) + * + * Defaults to ``sqlite`` */ 'dbtype' => 'sqlite', @@ -126,6 +129,8 @@ $CONFIG = array( /** * Prefix for the Nextcloud tables in the database. + * + * Default to ``oc_`` */ 'dbtableprefix' => '', @@ -134,6 +139,8 @@ $CONFIG = array( * Indicates whether the Nextcloud instance was installed successfully; ``true`` * indicates a successful installation, and ``false`` indicates an unsuccessful * installation. + * + * Defaults to ``false`` */ 'installed' => false, @@ -151,6 +158,8 @@ $CONFIG = array( * French. It overrides automatic language detection on public pages like login * or shared items. User's language preferences configured under "personal -> * language" override this setting after they have logged in. + * + * Defaults to ``en`` */ 'default_language' => 'en', @@ -160,6 +169,8 @@ $CONFIG = array( * gallery. You can use a comma-separated list of app names, so if the first * app is not enabled for a user then Nextcloud will try the second one, and so * on. If no enabled apps are found it defaults to the Files app. + * + * Defaults to ``files`` */ 'defaultapp' => 'files', @@ -173,6 +184,8 @@ $CONFIG = array( * ``true`` enables avatars, or user profile photos. These appear on the User * page, on user's Personal pages and are used by some apps (contacts, mail, * etc). ``false`` disables them. + * + * Defaults to ``true`` */ 'enable_avatars' => true, @@ -183,21 +196,25 @@ $CONFIG = array( 'allow_user_to_change_display_name' => true, /** - * Lifetime of the remember login cookie, which is set when the user clicks the - * ``remember`` checkbox on the login screen. The default is 15 days, expressed - * in seconds. + * Lifetime of the remember login cookie, which is set when the user clicks + * the ``remember`` checkbox on the login screen. + * + * Defaults to ``60*60*24*15`` seconds (15 days) */ 'remember_login_cookie_lifetime' => 60*60*24*15, /** - * The lifetime of a session after inactivity; the default is 24 hours, - * expressed in seconds. + * The lifetime of a session after inactivity. + * + * Defaults to ``60*60*24`` seconds (24 hours) */ 'session_lifetime' => 60 * 60 * 24, /** * Enable or disable session keep-alive when a user is logged in to the Web UI. * Enabling this sends a "heartbeat" to the server to keep it from timing out. + * + * Defaults to ``true`` */ 'session_keepalive' => true, @@ -205,6 +222,8 @@ $CONFIG = array( * Enforce token authentication for clients, which blocks requests using the user * password for enhanced security. Users need to generate tokens in personal settings * which can be used as passwords on their clients. + * + * Defaults to ``false`` */ 'token_auth_enforced' => false, @@ -212,6 +231,8 @@ $CONFIG = array( * Whether the bruteforce protection shipped with Nextcloud should be enabled or not. * * Disabling this is discouraged for security reasons. + * + * Defaults to ``true`` */ 'auth.bruteforce.protection.enabled' => true, @@ -219,6 +240,8 @@ $CONFIG = array( * The directory where the skeleton files are located. These files will be * copied to the data directory of new users. Leave empty to not copy any * skeleton files. + * + * Defaults to ``core/skeleton`` in the Nextcloud directory. */ 'skeletondirectory' => '/path/to/nextcloud/core/skeleton', @@ -259,11 +282,15 @@ $CONFIG = array( /** * FROM address that overrides the built-in ``sharing-noreply`` and * ``lostpassword-noreply`` FROM addresses. + * + * Defaults to different from addresses depending on the feature. */ 'mail_from_address' => 'nextcloud', /** * Enable SMTP class debugging. + * + * Defaults to ``false`` */ 'mail_smtpdebug' => false, @@ -282,6 +309,8 @@ $CONFIG = array( * * For ``qmail`` the binary is /var/qmail/bin/sendmail, and it must be installed * on your Unix system. + * + * Defaults to ``sendmail`` */ 'mail_smtpmode' => 'sendmail', @@ -290,11 +319,15 @@ $CONFIG = array( * server host. This may contain multiple hosts separated by a semi-colon. If * you need to specify the port number append it to the IP address separated by * a colon, like this: ``127.0.0.1:24``. + * + * Defaults to ``127.0.0.1`` */ 'mail_smtphost' => '127.0.0.1', /** * This depends on ``mail_smtpmode``. Specify the port for sending mail. + * + * Defaults to ``25`` */ 'mail_smtpport' => 25, @@ -302,36 +335,48 @@ $CONFIG = array( * This depends on ``mail_smtpmode``. This sets the SMTP server timeout, in * seconds. You may need to increase this if you are running an anti-malware or * spam scanner. + * + * Defaults to ``10`` seconds */ 'mail_smtptimeout' => 10, /** * This depends on ``mail_smtpmode``. Specify when you are using ``ssl`` or * ``tls``, or leave empty for no encryption. + * + * Defaults to ``''`` (empty string) */ 'mail_smtpsecure' => '', /** * This depends on ``mail_smtpmode``. Change this to ``true`` if your mail * server requires authentication. + * + * Defaults to ``false`` */ 'mail_smtpauth' => false, /** * This depends on ``mail_smtpmode``. If SMTP authentication is required, choose - * the authentication type as ``LOGIN`` (default) or ``PLAIN``. + * the authentication type as ``LOGIN`` or ``PLAIN``. + * + * Defaults to ``LOGIN`` */ 'mail_smtpauthtype' => 'LOGIN', /** * This depends on ``mail_smtpauth``. Specify the username for authenticating to * the SMTP server. + * + * Defaults to ``''`` (empty string) */ 'mail_smtpname' => '', /** * This depends on ``mail_smtpauth``. Specify the password for authenticating to * the SMTP server. + * + * Default to ``''`` (empty string) */ 'mail_smtppassword' => '', @@ -371,6 +416,8 @@ $CONFIG = array( * expression for the remote IP address. For example, defining a range of IP * addresses starting with ``10.0.0.`` and ending with 1 to 3: * ``^10\.0\.0\.[1-3]$`` + * + * Defaults to ``''`` (empty string) */ 'overwritecondaddr' => '', @@ -379,6 +426,8 @@ $CONFIG = array( * are generated within Nextcloud using any kind of command line tools (cron or * occ). The value should contain the full base URL: * ``https://www.example.com/nextcloud`` + * + * Defaults to ``''`` (empty string) */ 'overwrite.cli.url' => '', @@ -404,6 +453,8 @@ $CONFIG = array( * * - `mod_rewrite` is installed * - `mod_env` is installed + * + * Defaults to ``''`` (empty string) */ 'htaccess.RewriteBase' => '/', @@ -420,12 +471,16 @@ $CONFIG = array( /** * The URL of your proxy server, for example ``proxy.example.com:8081``. + * + * Defaults to ``''`` (empty string) */ 'proxy' => '', /** * The optional authentication for the proxy to use to connect to the internet. * The format is: ``username:password``. + * + * Defaults to ``''`` (empty string) */ 'proxyuserpwd' => '', @@ -466,6 +521,8 @@ $CONFIG = array( * delete when exceeds D2 days * * ``disabled`` * trash bin auto clean disabled, files and folders will be kept forever + * + * Defaults to ``auto`` */ 'trashbin_retention_obligation' => 'auto', @@ -505,6 +562,8 @@ $CONFIG = array( * keep versions for at least D1 days and delete when exceeds D2 days * * ``disabled`` * versions auto clean disabled, versions will be kept forever + * + * Defaults to ``auto`` */ 'versions_retention_obligation' => 'auto', @@ -519,17 +578,23 @@ $CONFIG = array( * Checks an app before install whether it uses private APIs instead of the * proper public APIs. If this is set to true it will only allow to install or * enable apps that pass this check. + * + * Defaults to ``false`` */ 'appcodechecker' => true, /** * Check if Nextcloud is up-to-date and shows a notification if a new version is * available. + * + * Defaults to ``true`` */ 'updatechecker' => true, /** * URL that Nextcloud should use to look for updates + * + * Defaults to ``https://updates.nextcloud.com/updater_server/`` */ 'updater.server.url' => 'https://updates.nextcloud.com/updater_server/', @@ -538,7 +603,7 @@ $CONFIG = array( * * Supported values: * - ``daily`` - * - ``beta` + * - ``beta`` * - ``stable`` * - ``production`` */ @@ -546,6 +611,8 @@ $CONFIG = array( /** * Is Nextcloud connected to the Internet or running in a closed network? + * + * Defaults to ``true`` */ 'has_internet_connection' => true, @@ -559,6 +626,8 @@ $CONFIG = array( * Allows Nextcloud to verify a working .well-known URL redirects. This is done * by attempting to make a request from JS to * https://your-domain.com/.well-known/caldav/ + * + * Defaults to ``true`` */ 'check_for_working_wellknown_setup' => true, @@ -568,6 +637,8 @@ $CONFIG = array( * If it is not, then any options controlled by ``.htaccess``, such as large * file uploads, will not work. It also runs checks on the ``data/`` directory, * which verifies that it can't be accessed directly through the Web server. + * + * Defaults to ``true`` */ 'check_for_working_htaccess' => true, @@ -578,6 +649,8 @@ $CONFIG = array( * all options via the Web interface. Furthermore, when updating Nextcloud * it is required to make the configuration file writable again for the update * process. + * + * Defaults to ``false`` */ 'config_is_read_only' => false, @@ -591,11 +664,14 @@ $CONFIG = array( * If syslogging is desired, set this parameter to ``syslog``. * Setting this parameter to ``errorlog`` will use the PHP error_log function * for logging. + * + * Defaults to ``file`` */ 'log_type' => 'file', /** * Log file path for the Nextcloud logging type. + * * Defaults to ``[datadirectory]/nextcloud.log`` */ 'logfile' => '/var/log/nextcloud.log', @@ -603,6 +679,8 @@ $CONFIG = array( /** * Loglevel to start logging at. Valid values are: 0 = Debug, 1 = Info, 2 = * Warning, 3 = Error, and 4 = Fatal. The default value is Warning. + * + * Defaults to ``2`` */ 'loglevel' => 2, @@ -637,12 +715,16 @@ $CONFIG = array( /** * This uses PHP.date formatting; see http://php.net/manual/en/function.date.php + * + * Defaults to ``Y-m-d\TH:i:sO`` (ISO8601) */ 'logdateformat' => 'F d, Y H:i:s', /** - * The default timezone for logfiles is UTC. You may change this; see + * The timezone for logfiles. You may change this; see * http://php.net/manual/en/timezones.php + * + * Defaults to ``UTC`` */ 'logtimezone' => 'Europe/Berlin', @@ -654,6 +736,8 @@ $CONFIG = array( /** * Log successful cron runs. + * + * Defaults to ``true`` */ 'cron_log' => true, @@ -663,6 +747,8 @@ $CONFIG = array( * = 100 * 1024 * 1024 bytes). A new logfile is created with a new name when the * old logfile reaches your limit. If a rotated log file is already present, it * will be overwritten. + * + * Defaults to ``0`` (no rotation) */ 'log_rotate_size' => false, @@ -676,6 +762,11 @@ $CONFIG = array( /** * This section is for configuring the download links for Nextcloud clients, as * seen in the first-run wizard and on Personal pages. + * + * Defaults to + * * Desktop client: ``https://nextcloud.com/install/#install-clients`` + * * Android client: ``https://play.google.com/store/apps/details?id=com.nextcloud.client`` + * * iOS client : ``https://itunes.apple.com/us/app/nextcloud/id1125420102?mt=8`` */ 'customclient_desktop' => 'https://nextcloud.com/install/#install-clients', @@ -692,6 +783,8 @@ $CONFIG = array( /** * When enabled, admins may install apps from the Nextcloud app store. + * + * Defaults to ``true`` */ 'appstoreenabled' => true, @@ -733,16 +826,22 @@ $CONFIG = array( * * Valid values are ``true``, to enable previews, or * ``false``, to disable previews + * + * Defaults to ``true`` */ 'enable_previews' => true, /** * The maximum width, in pixels, of a preview. A value of ``null`` means there * is no limit. + * + * Defaults to ``2048`` */ 'preview_max_x' => 2048, /** * The maximum height, in pixels, of a preview. A value of ``null`` means there * is no limit. + * + * Defaults to ``2048`` */ 'preview_max_y' => 2048, /** @@ -750,26 +849,30 @@ $CONFIG = array( * preview system generates blurry previews, you might want to consider setting * a maximum scale factor. By default, pictures are upscaled to 10 times the * original size. A value of ``1`` or ``null`` disables scaling. + * + * Defaults to ``2`` */ 'preview_max_scale_factor' => 10, /** * max file size for generating image previews with imagegd (default behaviour) - * If the image is bigger, it'll try other preview generators, - * but will most likely show the default mimetype icon + * If the image is bigger, it'll try other preview generators, but will most + * likely show the default mimetype icon. Set to -1 for no limit. * - * Value represents the maximum filesize in megabytes - * Default is 50 - * Set to -1 for no limit + * Defaults to ``50`` megabytes */ 'preview_max_filesize_image' => 50, /** * custom path for LibreOffice/OpenOffice binary + * + * Defaults to ``''`` (empty string) */ 'preview_libreoffice_path' => '/usr/bin/libreoffice', /** * Use this if LibreOffice/OpenOffice requires additional arguments. + * + * Defaults to ``''`` (empty string) */ 'preview_office_cl_parameters' => ' --headless --nologo --nofirststartwizard --invisible --norestore '. @@ -778,17 +881,6 @@ $CONFIG = array( /** * Only register providers that have been explicitly enabled * - * The following providers are enabled by default: - * - * - OC\Preview\PNG - * - OC\Preview\JPEG - * - OC\Preview\GIF - * - OC\Preview\BMP - * - OC\Preview\XBitmap - * - OC\Preview\MarkDown - * - OC\Preview\MP3 - * - OC\Preview\TXT - * * The following providers are disabled by default due to performance or privacy * concerns: * @@ -818,6 +910,17 @@ $CONFIG = array( * - OC\Preview\MSOffice2007 * - OC\Preview\OpenDocument * - OC\Preview\StarOffice + * + * Defaults to the following providers: + * + * - OC\Preview\BMP + * - OC\Preview\GIF + * - OC\Preview\JPEG + * - OC\Preview\MarkDown + * - OC\Preview\MP3 + * - OC\Preview\PNG + * - OC\Preview\TXT + * - OC\Preview\XBitmap */ 'enabledPreviewProviders' => array( 'OC\Preview\PNG', @@ -841,6 +944,8 @@ $CONFIG = array( * existence and marks them as ready to be cleaned up. The number is always * minutes. Setting it to 0 disables the feature. * See command line (occ) methods ``ldap:show-remnants`` and ``user:delete`` + * + * Defaults to ``51`` minutes */ 'ldapUserCleanupInterval' => 51, @@ -854,6 +959,8 @@ $CONFIG = array( * Replaces the default Comments Manager Factory. This can be utilized if an * own or 3rdParty CommentsManager should be used that – for instance – uses the * filesystem instead of the database to keep the comments. + * + * Defaults to ``\OC\Comments\ManagerFactory`` */ 'comments.managerFactory' => '\OC\Comments\ManagerFactory', @@ -861,6 +968,8 @@ $CONFIG = array( * Replaces the default System Tags Manager Factory. This can be utilized if an * own or 3rdParty SystemTagsManager should be used that – for instance – uses the * filesystem instead of the database to keep the comments. + * + * Defaults to ``\OC\SystemTag\ManagerFactory`` */ 'systemtags.managerFactory' => '\OC\SystemTag\ManagerFactory', @@ -878,12 +987,16 @@ $CONFIG = array( * doing some maintenance work, you need to set the value of the maintenance * parameter to true. Please keep in mind that users who are already logged-in * are kicked out of Nextcloud instantly. + * + * Defaults to ``false`` */ 'maintenance' => false, /** * When set to ``true``, the Nextcloud instance will be unavailable for all * users who are not in the ``admin`` group. + * + * Defaults to ``false`` */ 'singleuser' => false, @@ -894,6 +1007,8 @@ $CONFIG = array( /** * Extra SSL options to be used for configuration. + * + * Defaults to an empty array. */ 'openssl' => array( 'config' => '/absolute/location/of/openssl.cnf', @@ -927,6 +1042,8 @@ $CONFIG = array( * Memory caching backend for locally stored data * * * Used for host-specific data, e.g. file paths + * + * Defaults to ``none`` */ 'memcache.local' => '\OC\Memcache\APCu', @@ -935,6 +1052,8 @@ $CONFIG = array( * * * Used for installation-specific data, e.g. database caching * * If unset, defaults to the value of memcache.local + * + * Defaults to ``none`` */ 'memcache.distributed' => '\OC\Memcache\Memcached', @@ -994,6 +1113,8 @@ $CONFIG = array( * ``$user`` is the current user. When specified, the format will change to * ``$cache_path/$user`` where ``$cache_path`` is the configured cache directory * and ``$user`` is the user. + * + * Defaults to ``''`` (empty string) */ 'cache_path' => '', @@ -1002,8 +1123,10 @@ $CONFIG = array( * garbage collection (in seconds). Increase this value if users have * issues uploading very large files via the Nextcloud Client as upload isn't * completed within one day. + * + * Defaults to ``60*60*24`` (1 day) */ -'cache_chunk_gc_ttl' => 86400, // 60*60*24 = 1 day +'cache_chunk_gc_ttl' => 60*60*24, /** * Using Object Store with Nextcloud @@ -1065,6 +1188,8 @@ $CONFIG = array( * Replaces the default Share Provider Factory. This can be utilized if * own or 3rdParty Share Providers be used that – for instance – uses the * filesystem instead of the database to keep the share information. + * + * Defaults to ``\OC\Share20\ProviderFactory`` */ 'sharing.managerFactory' => '\OC\Share20\ProviderFactory', @@ -1125,6 +1250,11 @@ $CONFIG = array( * - mysql (MySQL) * - pgsql (PostgreSQL) * - oci (Oracle) + * + * Defaults to the following databases: + * - sqlite (SQLite3) + * - mysql (MySQL) + * - pgsql (PostgreSQL) */ 'supportedDatabases' => array( 'sqlite', @@ -1153,17 +1283,23 @@ $CONFIG = array( * Blacklist a specific file or files and disallow the upload of files * with this name. ``.htaccess`` is blocked by default. * WARNING: USE THIS ONLY IF YOU KNOW WHAT YOU ARE DOING. + * + * Defaults to ``array('.htaccess')`` */ 'blacklisted_files' => array('.htaccess'), /** * Define a default folder for shared files and folders other than root. + * + * Defaults to ``/`` */ 'share_folder' => '/', /** * If you are applying a theme to Nextcloud, enter the name of the theme here. * The default location for themes is ``nextcloud/themes/``. + * + * Defaults to the theming app which is shipped since Nextcloud 9 */ 'theme' => '', @@ -1182,12 +1318,16 @@ $CONFIG = array( * When changing this, note that older unsupported versions of the Nextcloud desktop * client may not function as expected, and could lead to permanent data loss for * clients or other unexpected results. + * + * Defaults to ``2.0.0`` */ 'minimum.supported.desktop.version' => '2.0.0', /** * EXPERIMENTAL: option whether to include external storage in quota * calculation, defaults to false. + * + * Defaults to ``false`` */ 'quota_include_external_storage' => false, @@ -1202,6 +1342,8 @@ $CONFIG = array( * * 1 -> Check each file or folder at most once per request, recommended for * general use if outside changes might happen. + * + * Defaults to ``0`` */ 'filesystem_check_changes' => 0, @@ -1210,18 +1352,24 @@ $CONFIG = array( * same storage as the upload target. Setting this to false will store the part * files in the root of the users folder which might be required to work with certain * external storage setups that have limited rename capabilities. + * + * Defaults to ``true`` */ 'part_file_in_storage' => true, /** * Where ``mount.json`` file should be stored, defaults to ``data/mount.json`` * in the Nextcloud directory. + * + * Defaults to ``data/mount.json`` in the Nextcloud directory. */ 'mount_file' => '/var/www/nextcloud/data/mount.json', /** * When ``true``, prevent Nextcloud from changing the cache due to changes in * the filesystem for all storage. + * + * Defaults to ``false`` */ 'filesystem_cache_readonly' => false, @@ -1236,6 +1384,7 @@ $CONFIG = array( * * If you configure these also consider setting `forwarded_for_headers` which * otherwise defaults to `HTTP_X_FORWARDED_FOR` (the `X-Forwarded-For` header). + * Defaults to an empty array. */ 'trusted_proxies' => array('203.0.113.45', '198.51.100.128'), @@ -1247,7 +1396,7 @@ $CONFIG = array( * If set incorrectly, a client can spoof their IP address as visible to * Nextcloud, bypassing access controls and making logs useless! * - * Defaults to 'HTTP_X_FORWARED_FOR' if unset + * Defaults to ``'HTTP_X_FORWARED_FOR'`` */ 'forwarded_for_headers' => array('HTTP_X_FORWARDED', 'HTTP_FORWARDED_FOR'), @@ -1255,8 +1404,10 @@ $CONFIG = array( * max file size for animating gifs on public-sharing-site. * If the gif is bigger, it'll show a static preview * - * Value represents the maximum filesize in megabytes. Default is ``10``. Set to - * ``-1`` for no limit. + * Value represents the maximum filesize in megabytes. Set to ``-1`` for + * no limit. + * + * Defaults to ``10`` megabytes */ 'max_filesize_animated_gifs_public_sharing' => 10, @@ -1270,6 +1421,8 @@ $CONFIG = array( * be caused by concurrent operations. Mainly relevant for * very large installations with many users working with * shared files. + * + * Defaults to ``true`` */ 'filelocking.enabled' => true, @@ -1278,15 +1431,18 @@ $CONFIG = array( * * Any lock older than this will be automatically cleaned up. * - * If not set this defaults to either 1 hour or the php max_execution_time, whichever is higher. + * Defaults to ``60*60`` seconds (1 hour) or the php + * max_execution_time, whichever is higher. */ -'filelocking.ttl' => 3600, +'filelocking.ttl' => 60*60, /** * Memory caching backend for file locking * * Because most memcache backends can clean values without warning using redis * is highly recommended to *avoid data loss*. + * + * Defaults to ``none`` */ 'memcache.locking' => '\\OC\\Memcache\\Redis', @@ -1300,6 +1456,8 @@ $CONFIG = array( * * Only enable this for local development and not in production environments * This will disable the minifier and outputs some additional debug information + * + * Defaults to ``false`` */ 'debug' => false, @@ -1313,6 +1471,8 @@ $CONFIG = array( * * Updating/Deleting this value can make connected clients stall until * the user has resolved conflicts. + * + * Defaults to ``''`` (empty string) */ 'data-fingerprint' => '', diff --git a/core/Controller/LoginController.php b/core/Controller/LoginController.php index 3c81ed5242a..187c818b9e1 100644 --- a/core/Controller/LoginController.php +++ b/core/Controller/LoginController.php @@ -205,8 +205,8 @@ class LoginController extends Controller { * @return RedirectResponse */ public function tryLogin($user, $password, $redirect_url, $remember_login = false, $timezone = '', $timezone_offset = '') { - $currentDelay = $this->throttler->getDelay($this->request->getRemoteAddress()); - $this->throttler->sleepDelay($this->request->getRemoteAddress()); + $currentDelay = $this->throttler->getDelay($this->request->getRemoteAddress(), 'login'); + $this->throttler->sleepDelay($this->request->getRemoteAddress(), 'login'); // If the user is already logged in and the CSRF check does not pass then // simply redirect the user to the correct page as required. This is the @@ -230,7 +230,7 @@ class LoginController extends Controller { if ($loginResult === false) { $this->throttler->registerAttempt('login', $this->request->getRemoteAddress(), ['user' => $originalUser]); if($currentDelay === 0) { - $this->throttler->sleepDelay($this->request->getRemoteAddress()); + $this->throttler->sleepDelay($this->request->getRemoteAddress(), 'login'); } $this->session->set('loginMessages', [ ['invalidpassword'], [] @@ -295,15 +295,15 @@ class LoginController extends Controller { * @return DataResponse */ public function confirmPassword($password) { - $currentDelay = $this->throttler->getDelay($this->request->getRemoteAddress()); - $this->throttler->sleepDelay($this->request->getRemoteAddress()); + $currentDelay = $this->throttler->getDelay($this->request->getRemoteAddress(), 'sudo'); + $this->throttler->sleepDelay($this->request->getRemoteAddress(), 'sudo'); $loginName = $this->userSession->getLoginName(); $loginResult = $this->userManager->checkPassword($loginName, $password); if ($loginResult === false) { $this->throttler->registerAttempt('sudo', $this->request->getRemoteAddress(), ['user' => $loginName]); if ($currentDelay === 0) { - $this->throttler->sleepDelay($this->request->getRemoteAddress()); + $this->throttler->sleepDelay($this->request->getRemoteAddress(), 'sudo'); } return new DataResponse([], Http::STATUS_FORBIDDEN); diff --git a/core/Controller/LostController.php b/core/Controller/LostController.php index a0ef87e50d8..8a8a50343ed 100644 --- a/core/Controller/LostController.php +++ b/core/Controller/LostController.php @@ -202,6 +202,7 @@ class LostController extends Controller { /** * @PublicPage + * @BruteForceProtection passwordResetEmail * * @param string $user * @return array diff --git a/core/Controller/OCSController.php b/core/Controller/OCSController.php index c59b0d7ad3f..dc9775f2603 100644 --- a/core/Controller/OCSController.php +++ b/core/Controller/OCSController.php @@ -128,7 +128,7 @@ class OCSController extends \OCP\AppFramework\OCSController { */ public function personCheck($login = '', $password = '') { if ($login !== '' && $password !== '') { - $this->throttler->sleepDelay($this->request->getRemoteAddress()); + $this->throttler->sleepDelay($this->request->getRemoteAddress(), 'login'); if ($this->userManager->checkPassword($login, $password)) { return new DataResponse([ 'person' => [ diff --git a/core/css/apps.scss b/core/css/apps.scss index 88fd223ba64..93e60fbfbf1 100644 --- a/core/css/apps.scss +++ b/core/css/apps.scss @@ -1,4 +1,5 @@ /** + * @copyright Copyright (c) 2014, Jan-Christoph Borchardt (http://jancborchardt.net) * @copyright Copyright (c) 2017, John Molakvoæ (skjnldsv@protonmail.com) * * @license GNU AGPL version 3 or any later version @@ -101,6 +102,7 @@ em { .active, .active a { opacity: 1; + box-shadow: inset 2px 0 #0082c9; } .collapse { display: none; @@ -284,7 +286,7 @@ em { .app-navigation-entry-utils ul, .app-navigation-entry-menu ul { list-style-type: none; } - + /* editing an entry */ .app-navigation-entry-edit { padding-left: 5px; diff --git a/core/css/header.scss b/core/css/header.scss index 8035f7e568a..886c2489a63 100644 --- a/core/css/header.scss +++ b/core/css/header.scss @@ -1,13 +1,23 @@ -/* prevent ugly selection effect on accidental selection */ +/** + * @copyright Copyright (c) 2017, John Molakvoæ (skjnldsv@protonmail.com) + * @copyright Copyright (c) 2016, Lukas Reschke (lukas@statuscode.ch) + * @copyright Copyright (c) 2016, Julius Härtl (jus@bitgrid.net) + * @copyright Copyright (c) 2016, Jos Poortvliet (jospoortvliet@gmail.com) + * + * @license GNU AGPL version 3 or any later version + * + */ -#header, #navigation, #expanddiv { +/* prevent ugly selection effect on accidental selection */ +#header, +#navigation, +#expanddiv { -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none; } /* removed until content-focusing issue is fixed */ - #skip-to-content a { position: absolute; left: -10000px; @@ -25,8 +35,9 @@ } /* HEADERS ------------------------------------------------------------------ */ - -#body-user #header, #body-settings #header, #body-public #header { +#body-user #header, +#body-settings #header, +#body-public #header { position: fixed; top: 0; left: 0; @@ -39,7 +50,6 @@ } /* LOGO and APP NAME -------------------------------------------------------- */ - #nextcloud { position: absolute; top: 0; @@ -88,6 +98,16 @@ padding-top: 18px; padding-right: 10px; } + /* show caret indicator next to logo to make clear it is tappable */ + .icon-caret { + display: inline-block; + width: 12px; + height: 12px; + margin: 0; + margin-top: -21px; + padding: 0; + vertical-align: middle; + } } /* hover effect for app switcher label */ @@ -123,7 +143,6 @@ } /* show appname next to logo */ - .header-appname { display: inline-block; position: relative; @@ -136,26 +155,14 @@ vertical-align: middle; } -/* show caret indicator next to logo to make clear it is tappable */ -#header .icon-caret { - display: inline-block; - width: 12px; - height: 12px; - margin: 0; - margin-top: -21px; - padding: 0; - vertical-align: middle; -} /* do not show menu toggle on public share links as there is no menu */ - #body-public #header .icon-caret { display: none; } /* NAVIGATION --------------------------------------------------------------- */ - #navigation { position: fixed; top: 45px; @@ -170,6 +177,7 @@ border-top-left-radius: 0; border-top-right-radius: 0; display: none; + box-sizing: border-box; /*overflow-y: auto; overflow-x: hidden;*/ z-index: 2000; @@ -185,37 +193,8 @@ border-bottom-color: rgba(255, 255, 255, 0.97); border-width: 10px; margin-left: -10px; + left: 47%; } -} - -/* arrow look */ - -#expanddiv:after { - bottom: 100%; - border: solid transparent; - content: ' '; - height: 0; - width: 0; - position: absolute; - pointer-events: none; - border-color: rgba(0, 0, 0, 0); - border-bottom-color: rgba(255, 255, 255, 0.97); - border-width: 10px; - margin-left: -10px; -} - -/* position of dropdown arrow */ - -#navigation:after { - left: 47%; -} - -#expanddiv:after { - right: 15px; -} - -#navigation { - box-sizing: border-box; * { box-sizing: border-box; } @@ -241,11 +220,15 @@ overflow: hidden; text-overflow: ellipsis; } - svg, span { + svg, + span { -ms-filter: 'progid:DXImageTransform.Microsoft.Alpha(Opacity=50)'; opacity: .5; } - &:hover svg, &:focus svg, &:hover span, &:focus span { + &:hover svg, + &:focus svg, + &:hover span, + &:focus span { -ms-filter: 'progid:DXImageTransform.Microsoft.Alpha(Opacity=100)'; opacity: 1; } @@ -256,52 +239,50 @@ } } } -} - -/* icon opacity and hover effect */ - -#apps-management a { - &:hover svg, &:focus svg, &.active svg, &:hover span, &:focus span, &.active span { - -ms-filter: 'progid:DXImageTransform.Microsoft.Alpha(Opacity=100)'; - opacity: 1; + .app-icon { + margin: 0 auto; + padding: 0; + max-height: 32px; + max-width: 32px; + } + /* loading feedback for apps */ + .app-loading { + .icon-loading-dark { + display: inline !important; + position: absolute; + top: 20px; + left: 24px; + width: 32px; + height: 32px; + } + .app-icon { + -ms-filter: 'progid:DXImageTransform.Microsoft.Alpha(Opacity=0)'; + opacity: 0; + } } -} - -#navigation .app-icon { - margin: 0 auto; - padding: 0; - max-height: 32px; - max-width: 32px; } /* Apps management */ - #apps-management { min-height: initial; height: initial; margin: 0; a { - svg, span { + svg, + span { -ms-filter: 'progid:DXImageTransform.Microsoft.Alpha(Opacity=30)'; opacity: .3; } - } -} - -/* loading feedback for apps */ - -#navigation .app-loading { - .icon-loading-dark { - display: inline !important; - position: absolute; - top: 20px; - left: 24px; - width: 32px; - height: 32px; - } - .app-icon { - -ms-filter: 'progid:DXImageTransform.Microsoft.Alpha(Opacity=0)'; - opacity: 0; + /* icon opacity and hover effect */ + &:hover svg, + &:focus svg, + &.active svg, + &:hover span, + &:focus span, + &.active span { + -ms-filter: 'progid:DXImageTransform.Microsoft.Alpha(Opacity=100)'; + opacity: 1; + } } } @@ -313,7 +294,6 @@ /* USER MENU -----------------------------------------------------------------*/ /* info part on the right, used e.g. for info on who shared something */ - .header-right { position: absolute; right: 0; @@ -325,21 +305,6 @@ box-sizing: border-box; } -/* Profile picture in header */ - -#header .avatardiv { - float: left; - display: inline-block; - margin-right: 8px; - cursor: pointer; - height: 32px; - width: 32px; - img { - opacity: 1; - cursor: pointer; - } -} - #settings { float: right; color: #ddd; @@ -352,6 +317,7 @@ } } +/* User menu on the right */ #expand { display: block; padding: 7px 30px 6px 10px; @@ -359,21 +325,41 @@ * { cursor: pointer; } - &:hover, &:focus, &:active { - color: #fff; - } img { -ms-filter: 'progid:DXImageTransform.Microsoft.Alpha(Opacity=70)'; opacity: .7; margin-bottom: -2px; } - &:hover img, &:focus img, &:active img { - -ms-filter: 'progid:DXImageTransform.Microsoft.Alpha(Opacity=100)'; - opacity: 1; + &:hover, + &:focus, + &:active { + color: #fff; + img { + -ms-filter: 'progid:DXImageTransform.Microsoft.Alpha(Opacity=100)'; + opacity: 1; + } } .icon-caret { margin-top: 0; } + + /* Profile picture in header */ + .avatardiv { + float: left; + display: inline-block; + margin-right: 8px; + cursor: pointer; + height: 32px; + width: 32px; + img { + opacity: 1; + cursor: pointer; + } + /* do not show display name when profile picture is present */ + &.avatardiv-shown + #expandDisplayName { + display: none; + } + } } #expanddiv { @@ -389,8 +375,19 @@ border-top-right-radius: 0; box-sizing: border-box; &:after { + /* position of dropdown arrow */ + right: 15px; border-color: rgba(0, 0, 0, 0); border-bottom-color: rgba(255, 255, 255, 1); + bottom: 100%; + border: solid transparent; + content: ' '; + height: 0; + width: 0; + position: absolute; + pointer-events: none; + border-width: 10px; + margin-left: -10px; } a { display: block; @@ -404,20 +401,12 @@ margin-bottom: -3px; margin-right: 6px; } - &:hover, &:focus, &:active, &.active { + &:hover, + &:focus, + &:active, + &.active { -ms-filter: 'progid:DXImageTransform.Microsoft.Alpha(Opacity=100)'; opacity: 1; } } } - -/* do not show display name when profile picture is present */ - -#header { - .avatardiv.avatardiv-shown + #expandDisplayName { - display: none; - } - #expand { - display: block; - } -} diff --git a/lib/private/AppFramework/DependencyInjection/DIContainer.php b/lib/private/AppFramework/DependencyInjection/DIContainer.php index ac42960f54d..57499f3ffe8 100644 --- a/lib/private/AppFramework/DependencyInjection/DIContainer.php +++ b/lib/private/AppFramework/DependencyInjection/DIContainer.php @@ -43,8 +43,10 @@ use OC\AppFramework\Middleware\OCSMiddleware; use OC\AppFramework\Middleware\Security\SecurityMiddleware; use OC\AppFramework\Middleware\SessionMiddleware; use OC\AppFramework\Utility\SimpleContainer; +use OC\AppFramework\Utility\TimeFactory; use OC\Core\Middleware\TwoFactorMiddleware; use OC\RichObjectStrings\Validator; +use OC\Security\Bruteforce\Throttler; use OCP\AppFramework\IApi; use OCP\AppFramework\IAppContainer; use OCP\Files\IAppData; @@ -376,20 +378,25 @@ class DIContainer extends SimpleContainer implements IAppContainer { */ $app = $this; $this->registerService('SecurityMiddleware', function($c) use ($app){ + /** @var \OC\Server $server */ + $server = $app->getServer(); + return new SecurityMiddleware( $c['Request'], $c['ControllerMethodReflector'], - $app->getServer()->getNavigationManager(), - $app->getServer()->getURLGenerator(), - $app->getServer()->getLogger(), - $app->getServer()->getSession(), + $server->getNavigationManager(), + $server->getURLGenerator(), + $server->getLogger(), + $server->getSession(), $c['AppName'], $app->isLoggedIn(), $app->isAdminUser(), - $app->getServer()->getContentSecurityPolicyManager(), - $app->getServer()->getCsrfTokenManager(), - $app->getServer()->getContentSecurityPolicyNonceManager() + $server->getContentSecurityPolicyManager(), + $server->getCsrfTokenManager(), + $server->getContentSecurityPolicyNonceManager(), + $server->getBruteForceThrottler() ); + }); $this->registerService('CORSMiddleware', function($c) { diff --git a/lib/private/AppFramework/Middleware/Security/SecurityMiddleware.php b/lib/private/AppFramework/Middleware/Security/SecurityMiddleware.php index d60d5749d57..edba6a3e759 100644 --- a/lib/private/AppFramework/Middleware/Security/SecurityMiddleware.php +++ b/lib/private/AppFramework/Middleware/Security/SecurityMiddleware.php @@ -36,6 +36,7 @@ use OC\AppFramework\Middleware\Security\Exceptions\NotConfirmedException; use OC\AppFramework\Middleware\Security\Exceptions\NotLoggedInException; use OC\AppFramework\Middleware\Security\Exceptions\StrictCookieMissingException; use OC\AppFramework\Utility\ControllerMethodReflector; +use OC\Security\Bruteforce\Throttler; use OC\Security\CSP\ContentSecurityPolicyManager; use OC\Security\CSP\ContentSecurityPolicyNonceManager; use OC\Security\CSRF\CsrfTokenManager; @@ -87,6 +88,8 @@ class SecurityMiddleware extends Middleware { private $csrfTokenManager; /** @var ContentSecurityPolicyNonceManager */ private $cspNonceManager; + /** @var Throttler */ + private $throttler; /** * @param IRequest $request @@ -101,6 +104,7 @@ class SecurityMiddleware extends Middleware { * @param ContentSecurityPolicyManager $contentSecurityPolicyManager * @param CSRFTokenManager $csrfTokenManager * @param ContentSecurityPolicyNonceManager $cspNonceManager + * @param Throttler $throttler */ public function __construct(IRequest $request, ControllerMethodReflector $reflector, @@ -113,7 +117,8 @@ class SecurityMiddleware extends Middleware { $isAdminUser, ContentSecurityPolicyManager $contentSecurityPolicyManager, CsrfTokenManager $csrfTokenManager, - ContentSecurityPolicyNonceManager $cspNonceManager) { + ContentSecurityPolicyNonceManager $cspNonceManager, + Throttler $throttler) { $this->navigationManager = $navigationManager; $this->request = $request; $this->reflector = $reflector; @@ -126,6 +131,7 @@ class SecurityMiddleware extends Middleware { $this->contentSecurityPolicyManager = $contentSecurityPolicyManager; $this->csrfTokenManager = $csrfTokenManager; $this->cspNonceManager = $cspNonceManager; + $this->throttler = $throttler; } @@ -185,6 +191,12 @@ class SecurityMiddleware extends Middleware { } } + if($this->reflector->hasAnnotation('BruteForceProtection')) { + $action = $this->reflector->getAnnotationParameter('BruteForceProtection'); + $this->throttler->sleepDelay($this->request->getRemoteAddress(), $action); + $this->throttler->registerAttempt($action, $this->request->getRemoteAddress()); + } + /** * FIXME: Use DI once available * Checks if app is enabled (also includes a check whether user is allowed to access the resource) diff --git a/lib/private/AppFramework/Utility/ControllerMethodReflector.php b/lib/private/AppFramework/Utility/ControllerMethodReflector.php index 33a117d8121..034fc3a1759 100644 --- a/lib/private/AppFramework/Utility/ControllerMethodReflector.php +++ b/lib/private/AppFramework/Utility/ControllerMethodReflector.php @@ -55,8 +55,10 @@ class ControllerMethodReflector implements IControllerMethodReflector{ $docs = $reflection->getDocComment(); // extract everything prefixed by @ and first letter uppercase - preg_match_all('/@([A-Z]\w+)/', $docs, $matches); - $this->annotations = $matches[1]; + preg_match_all('/^\h+\*\h+@(?P<annotation>[A-Z]\w+)(\h+(?P<parameter>\w+))?$/m', $docs, $matches); + foreach($matches['annotation'] as $key => $annontation) { + $this->annotations[$annontation] = $matches['parameter'][$key]; + } // extract type parameter information preg_match_all('/@param\h+(?P<type>\w+)\h+\$(?P<var>\w+)/', $docs, $matches); @@ -112,7 +114,22 @@ class ControllerMethodReflector implements IControllerMethodReflector{ * @return bool true if the annotation is found */ public function hasAnnotation($name){ - return in_array($name, $this->annotations); + return array_key_exists($name, $this->annotations); + } + + + /** + * Get optional annotation parameter + * @param string $name the name of the annotation + * @return string + */ + public function getAnnotationParameter($name){ + $parameter = ''; + if($this->hasAnnotation($name)) { + $parameter = $this->annotations[$name]; + } + + return $parameter; } diff --git a/lib/private/Security/Bruteforce/Throttler.php b/lib/private/Security/Bruteforce/Throttler.php index 031c5ffd411..765f109fdb3 100644 --- a/lib/private/Security/Bruteforce/Throttler.php +++ b/lib/private/Security/Bruteforce/Throttler.php @@ -189,9 +189,10 @@ class Throttler { * Get the throttling delay (in milliseconds) * * @param string $ip + * @param string $action optionally filter by action * @return int */ - public function getDelay($ip) { + public function getDelay($ip, $action = '') { $cutoffTime = (new \DateTime()) ->sub($this->getCutoff(43200)) ->getTimestamp(); @@ -201,6 +202,11 @@ class Throttler { ->from('bruteforce_attempts') ->where($qb->expr()->gt('occurred', $qb->createNamedParameter($cutoffTime))) ->andWhere($qb->expr()->eq('subnet', $qb->createNamedParameter($this->getSubnet($ip)))); + + if ($action !== '') { + $qb->andWhere($qb->expr()->eq('action', $qb->createNamedParameter($action))); + } + $attempts = count($qb->execute()->fetchAll()); if ($attempts === 0) { @@ -225,10 +231,11 @@ class Throttler { * Will sleep for the defined amount of time * * @param string $ip + * @param string $action optionally filter by action * @return int the time spent sleeping */ - public function sleepDelay($ip) { - $delay = $this->getDelay($ip); + public function sleepDelay($ip, $action = '') { + $delay = $this->getDelay($ip, $action); usleep($delay * 1000); return $delay; } diff --git a/lib/private/User/Session.php b/lib/private/User/Session.php index 1834bd025d1..9cc42e671a8 100644 --- a/lib/private/User/Session.php +++ b/lib/private/User/Session.php @@ -317,7 +317,7 @@ class Session implements IUserSession, Emitter { $password, IRequest $request, OC\Security\Bruteforce\Throttler $throttler) { - $currentDelay = $throttler->sleepDelay($request->getRemoteAddress()); + $currentDelay = $throttler->sleepDelay($request->getRemoteAddress(), 'login'); $isTokenPassword = $this->isTokenPassword($password); if (!$isTokenPassword && $this->isTokenAuthEnforced()) { @@ -334,7 +334,7 @@ class Session implements IUserSession, Emitter { $throttler->registerAttempt('login', $request->getRemoteAddress(), ['uid' => $user]); if($currentDelay === 0) { - $throttler->sleepDelay($request->getRemoteAddress()); + $throttler->sleepDelay($request->getRemoteAddress(), 'login'); } return false; } @@ -768,7 +768,7 @@ class Session implements IUserSession, Emitter { try { $this->tokenProvider->invalidateToken($this->session->getId()); } catch (SessionNotAvailableException $ex) { - + } } $this->setUser(null); diff --git a/tests/lib/AppFramework/Middleware/Security/SecurityMiddlewareTest.php b/tests/lib/AppFramework/Middleware/Security/SecurityMiddlewareTest.php index 5a988751070..164ea48de70 100644 --- a/tests/lib/AppFramework/Middleware/Security/SecurityMiddlewareTest.php +++ b/tests/lib/AppFramework/Middleware/Security/SecurityMiddlewareTest.php @@ -34,6 +34,7 @@ use OC\AppFramework\Middleware\Security\Exceptions\SecurityException; use OC\Appframework\Middleware\Security\Exceptions\StrictCookieMissingException; use OC\AppFramework\Middleware\Security\SecurityMiddleware; use OC\AppFramework\Utility\ControllerMethodReflector; +use OC\Security\Bruteforce\Throttler; use OC\Security\CSP\ContentSecurityPolicy; use OC\Security\CSP\ContentSecurityPolicyManager; use OC\Security\CSP\ContentSecurityPolicyNonceManager; @@ -82,6 +83,8 @@ class SecurityMiddlewareTest extends \Test\TestCase { private $csrfTokenManager; /** @var ContentSecurityPolicyNonceManager|\PHPUnit_Framework_MockObject_MockObject */ private $cspNonceManager; + /** @var Throttler|\PHPUnit_Framework_MockObject_MockObject */ + private $bruteForceThrottler; protected function setUp() { parent::setUp(); @@ -96,6 +99,7 @@ class SecurityMiddlewareTest extends \Test\TestCase { $this->contentSecurityPolicyManager = $this->createMock(ContentSecurityPolicyManager::class); $this->csrfTokenManager = $this->createMock(CsrfTokenManager::class); $this->cspNonceManager = $this->createMock(ContentSecurityPolicyNonceManager::class); + $this->bruteForceThrottler = $this->getMockBuilder(Throttler::class)->disableOriginalConstructor()->getMock(); $this->middleware = $this->getMiddleware(true, true); $this->secException = new SecurityException('hey', false); $this->secAjaxException = new SecurityException('hey', true); @@ -119,7 +123,8 @@ class SecurityMiddlewareTest extends \Test\TestCase { $isAdminUser, $this->contentSecurityPolicyManager, $this->csrfTokenManager, - $this->cspNonceManager + $this->cspNonceManager, + $this->bruteForceThrottler ); } @@ -652,4 +657,70 @@ class SecurityMiddlewareTest extends \Test\TestCase { $this->assertEquals($response, $this->middleware->afterController($this->controller, 'test', $response)); } + + /** + * @dataProvider dataTestBeforeControllerBruteForce + */ + public function testBeforeControllerBruteForce($bruteForceProtectionEnabled) { + /** @var ControllerMethodReflector|\PHPUnit_Framework_MockObject_MockObject $reader */ + $reader = $this->getMockBuilder(ControllerMethodReflector::class)->disableOriginalConstructor()->getMock(); + + $middleware = new SecurityMiddleware( + $this->request, + $reader, + $this->navigationManager, + $this->urlGenerator, + $this->logger, + $this->session, + 'files', + false, + false, + $this->contentSecurityPolicyManager, + $this->csrfTokenManager, + $this->cspNonceManager, + $this->bruteForceThrottler + ); + + $reader->expects($this->any())->method('hasAnnotation') + ->willReturnCallback( + function($annotation) use ($bruteForceProtectionEnabled) { + + switch ($annotation) { + case 'BruteForceProtection': + return $bruteForceProtectionEnabled; + case 'PasswordConfirmationRequired': + case 'StrictCookieRequired': + return false; + case 'PublicPage': + case 'NoCSRFRequired': + return true; + } + + return true; + } + ); + + $reader->expects($this->any())->method('getAnnotationParameter')->willReturn('action'); + $this->request->expects($this->any())->method('getRemoteAddress')->willReturn('remoteAddress'); + + if ($bruteForceProtectionEnabled) { + $this->bruteForceThrottler->expects($this->once())->method('sleepDelay') + ->with('remoteAddress', 'action'); + $this->bruteForceThrottler->expects($this->once())->method('registerAttempt') + ->with('action', 'remoteAddress'); + } else { + $this->bruteForceThrottler->expects($this->never())->method('sleepDelay'); + $this->bruteForceThrottler->expects($this->never())->method('registerAttempt'); + } + + $middleware->beforeController($this->controller, 'test'); + + } + + public function dataTestBeforeControllerBruteForce() { + return [ + [true], + [false] + ]; + } } diff --git a/tests/lib/AppFramework/Utility/ControllerMethodReflectorTest.php b/tests/lib/AppFramework/Utility/ControllerMethodReflectorTest.php index 92d767e9987..644245e1967 100644 --- a/tests/lib/AppFramework/Utility/ControllerMethodReflectorTest.php +++ b/tests/lib/AppFramework/Utility/ControllerMethodReflectorTest.php @@ -77,6 +77,19 @@ class ControllerMethodReflectorTest extends \Test\TestCase { /** + * @Annotation parameter + */ + public function testGetAnnotationParameter(){ + $reader = new ControllerMethodReflector(); + $reader->reflect( + '\Test\AppFramework\Utility\ControllerMethodReflectorTest', + 'testGetAnnotationParameter' + ); + + $this->assertSame('parameter', $reader->getAnnotationParameter('Annotation')); + } + + /** * @Annotation * @param test */ |