Merge pull request #18019 from nextcloud/enhancement/password-policy-events

Add typed events for password_policy
This commit is contained in:
Roeland Jago Douma 2019-11-27 11:11:17 +01:00 committed by GitHub
commit 0532f8116d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 163 additions and 48 deletions

View File

@ -386,6 +386,8 @@ return array(
'OCP\\Search\\Provider' => $baseDir . '/lib/public/Search/Provider.php', 'OCP\\Search\\Provider' => $baseDir . '/lib/public/Search/Provider.php',
'OCP\\Search\\Result' => $baseDir . '/lib/public/Search/Result.php', 'OCP\\Search\\Result' => $baseDir . '/lib/public/Search/Result.php',
'OCP\\Security\\CSP\\AddContentSecurityPolicyEvent' => $baseDir . '/lib/public/Security/CSP/AddContentSecurityPolicyEvent.php', 'OCP\\Security\\CSP\\AddContentSecurityPolicyEvent' => $baseDir . '/lib/public/Security/CSP/AddContentSecurityPolicyEvent.php',
'OCP\\Security\\Events\\GenerateSecurePasswordEvent' => $baseDir . '/lib/public/Security/Events/GenerateSecurePasswordEvent.php',
'OCP\\Security\\Events\\ValidatePasswordPolicyEvent' => $baseDir . '/lib/public/Security/Events/ValidatePasswordPolicyEvent.php',
'OCP\\Security\\FeaturePolicy\\AddFeaturePolicyEvent' => $baseDir . '/lib/public/Security/FeaturePolicy/AddFeaturePolicyEvent.php', 'OCP\\Security\\FeaturePolicy\\AddFeaturePolicyEvent' => $baseDir . '/lib/public/Security/FeaturePolicy/AddFeaturePolicyEvent.php',
'OCP\\Security\\IContentSecurityPolicyManager' => $baseDir . '/lib/public/Security/IContentSecurityPolicyManager.php', 'OCP\\Security\\IContentSecurityPolicyManager' => $baseDir . '/lib/public/Security/IContentSecurityPolicyManager.php',
'OCP\\Security\\ICredentialsManager' => $baseDir . '/lib/public/Security/ICredentialsManager.php', 'OCP\\Security\\ICredentialsManager' => $baseDir . '/lib/public/Security/ICredentialsManager.php',

View File

@ -415,6 +415,8 @@ class ComposerStaticInit53792487c5a8370acc0b06b1a864ff4c
'OCP\\Search\\Provider' => __DIR__ . '/../../..' . '/lib/public/Search/Provider.php', 'OCP\\Search\\Provider' => __DIR__ . '/../../..' . '/lib/public/Search/Provider.php',
'OCP\\Search\\Result' => __DIR__ . '/../../..' . '/lib/public/Search/Result.php', 'OCP\\Search\\Result' => __DIR__ . '/../../..' . '/lib/public/Search/Result.php',
'OCP\\Security\\CSP\\AddContentSecurityPolicyEvent' => __DIR__ . '/../../..' . '/lib/public/Security/CSP/AddContentSecurityPolicyEvent.php', 'OCP\\Security\\CSP\\AddContentSecurityPolicyEvent' => __DIR__ . '/../../..' . '/lib/public/Security/CSP/AddContentSecurityPolicyEvent.php',
'OCP\\Security\\Events\\GenerateSecurePasswordEvent' => __DIR__ . '/../../..' . '/lib/public/Security/Events/GenerateSecurePasswordEvent.php',
'OCP\\Security\\Events\\ValidatePasswordPolicyEvent' => __DIR__ . '/../../..' . '/lib/public/Security/Events/ValidatePasswordPolicyEvent.php',
'OCP\\Security\\FeaturePolicy\\AddFeaturePolicyEvent' => __DIR__ . '/../../..' . '/lib/public/Security/FeaturePolicy/AddFeaturePolicyEvent.php', 'OCP\\Security\\FeaturePolicy\\AddFeaturePolicyEvent' => __DIR__ . '/../../..' . '/lib/public/Security/FeaturePolicy/AddFeaturePolicyEvent.php',
'OCP\\Security\\IContentSecurityPolicyManager' => __DIR__ . '/../../..' . '/lib/public/Security/IContentSecurityPolicyManager.php', 'OCP\\Security\\IContentSecurityPolicyManager' => __DIR__ . '/../../..' . '/lib/public/Security/IContentSecurityPolicyManager.php',
'OCP\\Security\\ICredentialsManager' => __DIR__ . '/../../..' . '/lib/public/Security/ICredentialsManager.php', 'OCP\\Security\\ICredentialsManager' => __DIR__ . '/../../..' . '/lib/public/Security/ICredentialsManager.php',

View File

@ -55,6 +55,7 @@ use OCP\IUser;
use OCP\IUserManager; use OCP\IUserManager;
use OCP\L10N\IFactory; use OCP\L10N\IFactory;
use OCP\Mail\IMailer; use OCP\Mail\IMailer;
use OCP\Security\Events\ValidatePasswordPolicyEvent;
use OCP\Security\IHasher; use OCP\Security\IHasher;
use OCP\Security\ISecureRandom; use OCP\Security\ISecureRandom;
use OCP\Share; use OCP\Share;
@ -191,8 +192,7 @@ class Manager implements IManager {
// Let others verify the password // Let others verify the password
try { try {
$event = new GenericEvent($password); $this->eventDispatcher->dispatch(new ValidatePasswordPolicyEvent($password));
$this->eventDispatcher->dispatch('OCP\PasswordPolicy::validate', $event);
} catch (HintException $e) { } catch (HintException $e) {
throw new \Exception($e->getHint()); throw new \Exception($e->getHint());
} }

View File

@ -58,7 +58,9 @@ declare(strict_types=1);
namespace OC\User; namespace OC\User;
use OC\Cache\CappedMemoryCache; use OC\Cache\CappedMemoryCache;
use OCP\EventDispatcher\IEventDispatcher;
use OCP\IDBConnection; use OCP\IDBConnection;
use OCP\Security\Events\ValidatePasswordPolicyEvent;
use OCP\User\Backend\ABackend; use OCP\User\Backend\ABackend;
use OCP\User\Backend\ICheckPasswordBackend; use OCP\User\Backend\ICheckPasswordBackend;
use OCP\User\Backend\ICountUsersBackend; use OCP\User\Backend\ICountUsersBackend;
@ -68,7 +70,6 @@ use OCP\User\Backend\IGetHomeBackend;
use OCP\User\Backend\IGetRealUIDBackend; use OCP\User\Backend\IGetRealUIDBackend;
use OCP\User\Backend\ISetDisplayNameBackend; use OCP\User\Backend\ISetDisplayNameBackend;
use OCP\User\Backend\ISetPasswordBackend; use OCP\User\Backend\ISetPasswordBackend;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\EventDispatcher\GenericEvent; use Symfony\Component\EventDispatcher\GenericEvent;
/** /**
@ -86,7 +87,7 @@ class Database extends ABackend
/** @var CappedMemoryCache */ /** @var CappedMemoryCache */
private $cache; private $cache;
/** @var EventDispatcherInterface */ /** @var IEventDispatcher */
private $eventDispatcher; private $eventDispatcher;
/** @var IDBConnection */ /** @var IDBConnection */
@ -98,13 +99,13 @@ class Database extends ABackend
/** /**
* \OC\User\Database constructor. * \OC\User\Database constructor.
* *
* @param EventDispatcherInterface $eventDispatcher * @param IEventDispatcher $eventDispatcher
* @param string $table * @param string $table
*/ */
public function __construct($eventDispatcher = null, $table = 'users') { public function __construct($eventDispatcher = null, $table = 'users') {
$this->cache = new CappedMemoryCache(); $this->cache = new CappedMemoryCache();
$this->table = $table; $this->table = $table;
$this->eventDispatcher = $eventDispatcher ? $eventDispatcher : \OC::$server->getEventDispatcher(); $this->eventDispatcher = $eventDispatcher ? $eventDispatcher : \OC::$server->query(IEventDispatcher::class);
} }
/** /**
@ -130,8 +131,7 @@ class Database extends ABackend
$this->fixDI(); $this->fixDI();
if (!$this->userExists($uid)) { if (!$this->userExists($uid)) {
$event = new GenericEvent($password); $this->eventDispatcher->dispatchTyped(new ValidatePasswordPolicyEvent($password));
$this->eventDispatcher->dispatch('OCP\PasswordPolicy::validate', $event);
$qb = $this->dbConn->getQueryBuilder(); $qb = $this->dbConn->getQueryBuilder();
$qb->insert($this->table) $qb->insert($this->table)
@ -199,8 +199,7 @@ class Database extends ABackend
$this->fixDI(); $this->fixDI();
if ($this->userExists($uid)) { if ($this->userExists($uid)) {
$event = new GenericEvent($password); $this->eventDispatcher->dispatchTyped(new ValidatePasswordPolicyEvent($password));
$this->eventDispatcher->dispatch('OCP\PasswordPolicy::validate', $event);
$hasher = \OC::$server->getHasher(); $hasher = \OC::$server->getHasher();
$hashedPassword = $hasher->hash($password); $hashedPassword = $hasher->hash($password);

View File

@ -0,0 +1,50 @@
<?php declare(strict_types=1);
/**
* @copyright 2019 Christoph Wurst <christoph@winzerhof-wurst.at>
*
* @author 2019 Christoph Wurst <christoph@winzerhof-wurst.at>
*
* @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 OCP\Security\Events;
use OCP\EventDispatcher\Event;
/**
* @since 18.0.0
*/
class GenerateSecurePasswordEvent extends Event {
/** @var null|string */
private $password;
/**
* @since 18.0.0
*/
public function getPassword(): ?string {
return $this->password;
}
/**
* @since 18.0.0
*/
public function setPassword(string $password): void {
$this->password = $password;
}
}

View File

@ -0,0 +1,51 @@
<?php declare(strict_types=1);
/**
* @copyright 2019 Christoph Wurst <christoph@winzerhof-wurst.at>
*
* @author 2019 Christoph Wurst <christoph@winzerhof-wurst.at>
*
* @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 OCP\Security\Events;
use OCP\EventDispatcher\Event;
/**
* @since 18.0.0
*/
class ValidatePasswordPolicyEvent extends Event {
/** @var string */
private $password;
/**
* @since 18.0.0
*/
public function __construct(string $password) {
parent::__construct();
$this->password = $password;
}
/**
* @since 18.0.0
*/
public function getPassword(): string {
return $this->password;
}
}

View File

@ -27,6 +27,7 @@ use OC\Share20\DefaultShareProvider;
use OC\Share20\Exception; use OC\Share20\Exception;
use OC\Share20\Manager; use OC\Share20\Manager;
use OC\Share20\Share; use OC\Share20\Share;
use OCP\EventDispatcher\Event;
use OCP\Files\File; use OCP\Files\File;
use OCP\Files\Folder; use OCP\Files\Folder;
use OCP\Files\IRootFolder; use OCP\Files\IRootFolder;
@ -42,16 +43,18 @@ use OCP\ILogger;
use OCP\IServerContainer; use OCP\IServerContainer;
use OCP\IURLGenerator; use OCP\IURLGenerator;
use OCP\IUser; use OCP\IUser;
use OCP\IUserManager; use OCP\IUserManager;
use OCP\L10N\IFactory; use OCP\L10N\IFactory;
use OCP\Mail\IMailer; use OCP\Mail\IMailer;
use OCP\Security\Events\ValidatePasswordPolicyEvent;
use OCP\Security\IHasher; use OCP\Security\IHasher;
use OCP\Security\ISecureRandom; use OCP\Security\ISecureRandom;
use OCP\Share\Exceptions\ShareNotFound; use OCP\Share\Exceptions\ShareNotFound;
use OCP\Share\IProviderFactory; use OCP\Share\IProviderFactory;
use OCP\Share\IShare; use OCP\Share\IShare;
use OCP\Share\IShareProvider; use OCP\Share\IShareProvider;
use PHPUnit\Framework\MockObject\MockBuilder;
use PHPUnit\Framework\MockObject\MockObject;
use Symfony\Component\EventDispatcher\EventDispatcherInterface; use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\EventDispatcher\GenericEvent; use Symfony\Component\EventDispatcher\GenericEvent;
@ -65,37 +68,37 @@ class ManagerTest extends \Test\TestCase {
/** @var Manager */ /** @var Manager */
protected $manager; protected $manager;
/** @var ILogger|\PHPUnit_Framework_MockObject_MockObject */ /** @var ILogger|MockObject */
protected $logger; protected $logger;
/** @var IConfig|\PHPUnit_Framework_MockObject_MockObject */ /** @var IConfig|MockObject */
protected $config; protected $config;
/** @var ISecureRandom|\PHPUnit_Framework_MockObject_MockObject */ /** @var ISecureRandom|MockObject */
protected $secureRandom; protected $secureRandom;
/** @var IHasher|\PHPUnit_Framework_MockObject_MockObject */ /** @var IHasher|MockObject */
protected $hasher; protected $hasher;
/** @var IShareProvider|\PHPUnit_Framework_MockObject_MockObject */ /** @var IShareProvider|MockObject */
protected $defaultProvider; protected $defaultProvider;
/** @var IMountManager|\PHPUnit_Framework_MockObject_MockObject */ /** @var IMountManager|MockObject */
protected $mountManager; protected $mountManager;
/** @var IGroupManager|\PHPUnit_Framework_MockObject_MockObject */ /** @var IGroupManager|MockObject */
protected $groupManager; protected $groupManager;
/** @var IL10N|\PHPUnit_Framework_MockObject_MockObject */ /** @var IL10N|MockObject */
protected $l; protected $l;
/** @var IFactory|\PHPUnit_Framework_MockObject_MockObject */ /** @var IFactory|MockObject */
protected $l10nFactory; protected $l10nFactory;
/** @var DummyFactory */ /** @var DummyFactory */
protected $factory; protected $factory;
/** @var IUserManager|\PHPUnit_Framework_MockObject_MockObject */ /** @var IUserManager|MockObject */
protected $userManager; protected $userManager;
/** @var IRootFolder | \PHPUnit_Framework_MockObject_MockObject */ /** @var IRootFolder | MockObject */
protected $rootFolder; protected $rootFolder;
/** @var EventDispatcherInterface | \PHPUnit_Framework_MockObject_MockObject */ /** @var EventDispatcherInterface | MockObject */
protected $eventDispatcher; protected $eventDispatcher;
/** @var IMailer|\PHPUnit_Framework_MockObject_MockObject */ /** @var IMailer|MockObject */
protected $mailer; protected $mailer;
/** @var IURLGenerator|\PHPUnit_Framework_MockObject_MockObject */ /** @var IURLGenerator|MockObject */
protected $urlGenerator; protected $urlGenerator;
/** @var \OC_Defaults|\PHPUnit_Framework_MockObject_MockObject */ /** @var \OC_Defaults|MockObject */
protected $defaults; protected $defaults;
public function setUp() { public function setUp() {
@ -146,7 +149,7 @@ class ManagerTest extends \Test\TestCase {
} }
/** /**
* @return \PHPUnit_Framework_MockObject_MockBuilder * @return MockBuilder
*/ */
private function createManagerMock() { private function createManagerMock() {
return $this->getMockBuilder('\OC\Share20\Manager') return $this->getMockBuilder('\OC\Share20\Manager')
@ -496,11 +499,12 @@ class ManagerTest extends \Test\TestCase {
])); ]));
$this->eventDispatcher->expects($this->once())->method('dispatch') $this->eventDispatcher->expects($this->once())->method('dispatch')
->willReturnCallback(function($eventName, GenericEvent $event) { ->willReturnCallback(function(Event $event) {
$this->assertSame('OCP\PasswordPolicy::validate', $eventName); $this->assertInstanceOf(ValidatePasswordPolicyEvent::class, $event);
$this->assertSame('password', $event->getSubject()); /** @var ValidatePasswordPolicyEvent $event */
$this->assertSame('password', $event->getPassword());
} }
); );
$result = self::invokePrivate($this->manager, 'verifyPassword', ['password']); $result = self::invokePrivate($this->manager, 'verifyPassword', ['password']);
$this->assertNull($result); $this->assertNull($result);
@ -516,12 +520,13 @@ class ManagerTest extends \Test\TestCase {
])); ]));
$this->eventDispatcher->expects($this->once())->method('dispatch') $this->eventDispatcher->expects($this->once())->method('dispatch')
->willReturnCallback(function($eventName, GenericEvent $event) { ->willReturnCallback(function(Event $event) {
$this->assertSame('OCP\PasswordPolicy::validate', $eventName); $this->assertInstanceOf(ValidatePasswordPolicyEvent::class, $event);
$this->assertSame('password', $event->getSubject()); /** @var ValidatePasswordPolicyEvent $event */
$this->assertSame('password', $event->getPassword());
throw new HintException('message', 'password not accepted'); throw new HintException('message', 'password not accepted');
} }
); );
self::invokePrivate($this->manager, 'verifyPassword', ['password']); self::invokePrivate($this->manager, 'verifyPassword', ['password']);
} }

View File

@ -21,10 +21,14 @@
*/ */
namespace Test\User; namespace Test\User;
use OC\HintException; use OC\HintException;
use OC\User\User; use OC\User\User;
use OCP\EventDispatcher\Event;
use OCP\EventDispatcher\IEventDispatcher;
use OCP\Security\Events\ValidatePasswordPolicyEvent;
use PHPUnit\Framework\MockObject\MockObject;
use Symfony\Component\EventDispatcher\EventDispatcherInterface; use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\EventDispatcher\GenericEvent;
/** /**
* Class DatabaseTest * Class DatabaseTest
@ -34,7 +38,7 @@ use Symfony\Component\EventDispatcher\GenericEvent;
class DatabaseTest extends Backend { class DatabaseTest extends Backend {
/** @var array */ /** @var array */
private $users; private $users;
/** @var EventDispatcherInterface|\PHPUnit_Framework_MockObject_MockObject */ /** @var IEventDispatcher|MockObject */
private $eventDispatcher; private $eventDispatcher;
public function getUser() { public function getUser() {
@ -46,7 +50,7 @@ class DatabaseTest extends Backend {
protected function setUp() { protected function setUp() {
parent::setUp(); parent::setUp();
$this->eventDispatcher = $this->createMock(EventDispatcherInterface::class); $this->eventDispatcher = $this->createMock(IEventDispatcher::class);
$this->backend=new \OC\User\Database($this->eventDispatcher); $this->backend=new \OC\User\Database($this->eventDispatcher);
} }
@ -65,11 +69,12 @@ class DatabaseTest extends Backend {
$user = $this->getUser(); $user = $this->getUser();
$this->backend->createUser($user, 'pass1'); $this->backend->createUser($user, 'pass1');
$this->eventDispatcher->expects($this->once())->method('dispatch') $this->eventDispatcher->expects($this->once())->method('dispatchTyped')
->willReturnCallback( ->willReturnCallback(
function ($eventName, GenericEvent $event) { function (Event $event) {
$this->assertSame('OCP\PasswordPolicy::validate', $eventName); $this->assertInstanceOf(ValidatePasswordPolicyEvent::class, $event);
$this->assertSame('newpass', $event->getSubject()); /** @var ValidatePasswordPolicyEvent $event */
$this->assertSame('newpass', $event->getPassword());
} }
); );
@ -85,11 +90,12 @@ class DatabaseTest extends Backend {
$user = $this->getUser(); $user = $this->getUser();
$this->backend->createUser($user, 'pass1'); $this->backend->createUser($user, 'pass1');
$this->eventDispatcher->expects($this->once())->method('dispatch') $this->eventDispatcher->expects($this->once())->method('dispatchTyped')
->willReturnCallback( ->willReturnCallback(
function ($eventName, GenericEvent $event) { function (Event $event) {
$this->assertSame('OCP\PasswordPolicy::validate', $eventName); $this->assertInstanceOf(ValidatePasswordPolicyEvent::class, $event);
$this->assertSame('newpass', $event->getSubject()); /** @var ValidatePasswordPolicyEvent $event */
$this->assertSame('newpass', $event->getPassword());
throw new HintException('password change failed', 'password change failed'); throw new HintException('password change failed', 'password change failed');
} }
); );
@ -124,8 +130,8 @@ class DatabaseTest extends Backend {
$user2 = $this->getUser(); $user2 = $this->getUser();
$this->backend->createUser($user2, 'pass1'); $this->backend->createUser($user2, 'pass1');
$user1Obj = new User($user1, $this->backend, $this->eventDispatcher); $user1Obj = new User($user1, $this->backend, $this->createMock(EventDispatcherInterface::class));
$user2Obj = new User($user2, $this->backend, $this->eventDispatcher); $user2Obj = new User($user2, $this->backend, $this->createMock(EventDispatcherInterface::class));
$emailAddr1 = "$user1@nextcloud.com"; $emailAddr1 = "$user1@nextcloud.com";
$emailAddr2 = "$user2@nextcloud.com"; $emailAddr2 = "$user2@nextcloud.com";