Merge pull request #221 from nextcloud/password_policy_events

add events to check passwords with the password policy app
This commit is contained in:
Morris Jobke 2016-06-27 16:55:16 +02:00 committed by GitHub
commit ed25d73d31
8 changed files with 142 additions and 64 deletions

View File

@ -235,7 +235,7 @@ class Server extends ServerContainer implements IServerContainer {
} else { } else {
$defaultTokenProvider = null; $defaultTokenProvider = null;
} }
$userSession = new \OC\User\Session($manager, $session, $timeFactory, $defaultTokenProvider, $c->getConfig()); $userSession = new \OC\User\Session($manager, $session, $timeFactory, $defaultTokenProvider, $c->getConfig());
$userSession->listen('\OC\User', 'preCreateUser', function ($uid, $password) { $userSession->listen('\OC\User', 'preCreateUser', function ($uid, $password) {
\OC_Hook::emit('OC_User', 'pre_createUser', array('run' => true, 'uid' => $uid, 'password' => $password)); \OC_Hook::emit('OC_User', 'pre_createUser', array('run' => true, 'uid' => $uid, 'password' => $password));
@ -674,7 +674,8 @@ class Server extends ServerContainer implements IServerContainer {
$c->getL10N('core'), $c->getL10N('core'),
$factory, $factory,
$c->getUserManager(), $c->getUserManager(),
$c->getLazyRootFolder() $c->getLazyRootFolder(),
$c->getEventDispatcher()
); );
return $manager; return $manager;

View File

@ -26,6 +26,7 @@ namespace OC\Share20;
use OC\Cache\CappedMemoryCache; use OC\Cache\CappedMemoryCache;
use OC\Files\Mount\MoveableMount; use OC\Files\Mount\MoveableMount;
use OC\HintException;
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,6 +43,8 @@ use OCP\Share\Exceptions\GenericShareException;
use OCP\Share\Exceptions\ShareNotFound; use OCP\Share\Exceptions\ShareNotFound;
use OCP\Share\IManager; use OCP\Share\IManager;
use OCP\Share\IProviderFactory; use OCP\Share\IProviderFactory;
use Symfony\Component\EventDispatcher\EventDispatcher;
use Symfony\Component\EventDispatcher\GenericEvent;
/** /**
* This class is the communication hub for all sharing related operations. * This class is the communication hub for all sharing related operations.
@ -70,6 +73,8 @@ class Manager implements IManager {
private $rootFolder; private $rootFolder;
/** @var CappedMemoryCache */ /** @var CappedMemoryCache */
private $sharingDisabledForUsersCache; private $sharingDisabledForUsersCache;
/** @var EventDispatcher */
private $eventDispatcher;
/** /**
@ -85,6 +90,7 @@ class Manager implements IManager {
* @param IProviderFactory $factory * @param IProviderFactory $factory
* @param IUserManager $userManager * @param IUserManager $userManager
* @param IRootFolder $rootFolder * @param IRootFolder $rootFolder
* @param EventDispatcher $eventDispatcher
*/ */
public function __construct( public function __construct(
ILogger $logger, ILogger $logger,
@ -96,7 +102,8 @@ class Manager implements IManager {
IL10N $l, IL10N $l,
IProviderFactory $factory, IProviderFactory $factory,
IUserManager $userManager, IUserManager $userManager,
IRootFolder $rootFolder IRootFolder $rootFolder,
EventDispatcher $eventDispatcher
) { ) {
$this->logger = $logger; $this->logger = $logger;
$this->config = $config; $this->config = $config;
@ -108,6 +115,7 @@ class Manager implements IManager {
$this->factory = $factory; $this->factory = $factory;
$this->userManager = $userManager; $this->userManager = $userManager;
$this->rootFolder = $rootFolder; $this->rootFolder = $rootFolder;
$this->eventDispatcher = $eventDispatcher;
$this->sharingDisabledForUsersCache = new CappedMemoryCache(); $this->sharingDisabledForUsersCache = new CappedMemoryCache();
} }
@ -138,16 +146,11 @@ class Manager implements IManager {
} }
// Let others verify the password // Let others verify the password
$accepted = true; try {
$message = ''; $event = new GenericEvent($password);
\OCP\Util::emitHook('\OC\Share', 'verifyPassword', [ $this->eventDispatcher->dispatch('OCP\PasswordPolicy::validate', $event);
'password' => $password, } catch (HintException $e) {
'accepted' => &$accepted, throw new \Exception($e->getHint());
'message' => &$message
]);
if (!$accepted) {
throw new \Exception($message);
} }
} }

View File

@ -51,6 +51,8 @@
namespace OC\User; namespace OC\User;
use OC\Cache\CappedMemoryCache; use OC\Cache\CappedMemoryCache;
use Symfony\Component\EventDispatcher\EventDispatcher;
use Symfony\Component\EventDispatcher\GenericEvent;
/** /**
* Class for user management in a SQL Database (e.g. MySQL, SQLite) * Class for user management in a SQL Database (e.g. MySQL, SQLite)
@ -58,12 +60,14 @@ use OC\Cache\CappedMemoryCache;
class Database extends \OC\User\Backend implements \OCP\IUserBackend { class Database extends \OC\User\Backend implements \OCP\IUserBackend {
/** @var CappedMemoryCache */ /** @var CappedMemoryCache */
private $cache; private $cache;
/** @var EventDispatcher */
private $eventDispatcher;
/** /**
* OC_User_Database constructor. * OC_User_Database constructor.
*/ */
public function __construct() { public function __construct($eventDispatcher = null) {
$this->cache = new CappedMemoryCache(); $this->cache = new CappedMemoryCache();
$this->eventDispatcher = $eventDispatcher ? $eventDispatcher : \OC::$server->getEventDispatcher();
} }
/** /**
@ -115,6 +119,8 @@ class Database extends \OC\User\Backend implements \OCP\IUserBackend {
*/ */
public function setPassword($uid, $password) { public function setPassword($uid, $password) {
if ($this->userExists($uid)) { if ($this->userExists($uid)) {
$event = new GenericEvent($password);
$this->eventDispatcher->dispatch('OCP\PasswordPolicy::validate', $event);
$query = \OC_DB::prepare('UPDATE `*PREFIX*users` SET `password` = ? WHERE `uid` = ?'); $query = \OC_DB::prepare('UPDATE `*PREFIX*users` SET `password` = ? WHERE `uid` = ?');
$result = $query->execute(array(\OC::$server->getHasher()->hash($password), $uid)); $result = $query->execute(array(\OC::$server->getHasher()->hash($password), $uid));

View File

@ -30,6 +30,8 @@
*/ */
namespace OC\Settings\ChangePassword; namespace OC\Settings\ChangePassword;
use OC\HintException;
class Controller { class Controller {
public static function changePersonalPassword($args) { public static function changePersonalPassword($args) {
// Check if we are an user // Check if we are an user
@ -39,17 +41,22 @@ class Controller {
$username = \OC_User::getUser(); $username = \OC_User::getUser();
$password = isset($_POST['personal-password']) ? $_POST['personal-password'] : null; $password = isset($_POST['personal-password']) ? $_POST['personal-password'] : null;
$oldPassword = isset($_POST['oldpassword']) ? $_POST['oldpassword'] : ''; $oldPassword = isset($_POST['oldpassword']) ? $_POST['oldpassword'] : '';
$l = new \OC_L10n('settings');
if (!\OC_User::checkPassword($username, $oldPassword)) { if (!\OC_User::checkPassword($username, $oldPassword)) {
$l = new \OC_L10n('settings');
\OC_JSON::error(array("data" => array("message" => $l->t("Wrong password")) )); \OC_JSON::error(array("data" => array("message" => $l->t("Wrong password")) ));
exit(); exit();
} }
if (!is_null($password) && \OC_User::setPassword($username, $password)) {
\OC::$server->getUserSession()->updateSessionTokenPassword($password); try {
\OC_JSON::success(); if (!is_null($password) && \OC_User::setPassword($username, $password)) {
} else { \OC::$server->getUserSession()->updateSessionTokenPassword($password);
\OC_JSON::error(); \OC_JSON::success(['data' => ['message' => $l->t('Saved')]]);
} else {
\OC_JSON::error();
}
} catch (HintException $e) {
\OC_JSON::error(['data' => ['message' => $e->getHint()]]);
} }
} }
@ -150,10 +157,14 @@ class Controller {
} }
} else { // if encryption is disabled, proceed } else { // if encryption is disabled, proceed
if (!is_null($password) && \OC_User::setPassword($username, $password)) { try {
\OC_JSON::success(array('data' => array('username' => $username))); if (!is_null($password) && \OC_User::setPassword($username, $password)) {
} else { \OC_JSON::success(array('data' => array('username' => $username)));
\OC_JSON::error(array('data' => array('message' => $l->t('Unable to change password')))); } else {
\OC_JSON::error(array('data' => array('message' => $l->t('Unable to change password'))));
}
} catch (HintException $e) {
\OC_JSON::error(array('data' => array('message' => $e->getHint())));
} }
} }
} }

View File

@ -192,6 +192,7 @@ $(document).ready(function () {
$('#pass2').showPassword().keyup(); $('#pass2').showPassword().keyup();
} }
$("#passwordbutton").click(function () { $("#passwordbutton").click(function () {
OC.msg.startSaving('#password-error-msg');
var isIE8or9 = $('html').hasClass('lte9'); var isIE8or9 = $('html').hasClass('lte9');
// FIXME - TODO - once support for IE8 and IE9 is dropped // FIXME - TODO - once support for IE8 and IE9 is dropped
// for IE8 and IE9 this will check additionally if the typed in password // for IE8 and IE9 this will check additionally if the typed in password
@ -208,25 +209,32 @@ $(document).ready(function () {
if (data.status === "success") { if (data.status === "success") {
$('#pass1').val(''); $('#pass1').val('');
$('#pass2').val('').change(); $('#pass2').val('').change();
// Hide a possible errormsg and show successmsg OC.msg.finishedSaving('#password-error-msg', data);
$('#password-changed').removeClass('hidden').addClass('inlineblock');
$('#password-error').removeClass('inlineblock').addClass('hidden');
} else { } else {
if (typeof(data.data) !== "undefined") { if (typeof(data.data) !== "undefined") {
$('#password-error').text(data.data.message); OC.msg.finishedSaving('#password-error-msg', data);
} else { } else {
$('#password-error').text(t('Unable to change password')); OC.msg.finishedSaving('#password-error-msg',
{
'status' : 'error',
'data' : {
'message' : t('core', 'Unable to change password')
}
}
);
} }
// Hide a possible successmsg and show errormsg
$('#password-changed').removeClass('inlineblock').addClass('hidden');
$('#password-error').removeClass('hidden').addClass('inlineblock');
} }
}); });
return false; return false;
} else { } else {
// Hide a possible successmsg and show errormsg OC.msg.finishedSaving('#password-error-msg',
$('#password-changed').removeClass('inlineblock').addClass('hidden'); {
$('#password-error').removeClass('hidden').addClass('inlineblock'); 'status' : 'error',
'data' : {
'message' : t('core', 'Unable to change password')
}
}
);
return false; return false;
} }

View File

@ -118,8 +118,7 @@ if($_['passwordChangeSupported']) {
?> ?>
<form id="passwordform" class="section"> <form id="passwordform" class="section">
<h2 class="inlineblock"><?php p($l->t('Password'));?></h2> <h2 class="inlineblock"><?php p($l->t('Password'));?></h2>
<div class="hidden icon-checkmark" id="password-changed"></div> <div id="password-error-msg" class="msg success inlineblock" style="display: none;">Saved</div>
<div class="hidden" id="password-error"><?php p($l->t('Unable to change your password'));?></div>
<br> <br>
<label for="pass1" class="hidden-visually"><?php echo $l->t('Current password');?>: </label> <label for="pass1" class="hidden-visually"><?php echo $l->t('Current password');?>: </label>
<input type="password" id="pass1" name="oldpassword" <input type="password" id="pass1" name="oldpassword"

View File

@ -20,6 +20,7 @@
*/ */
namespace Test\Share20; namespace Test\Share20;
use OC\HintException;
use OCP\Files\IRootFolder; use OCP\Files\IRootFolder;
use OCP\IUserManager; use OCP\IUserManager;
use OCP\Share\Exceptions\ShareNotFound; use OCP\Share\Exceptions\ShareNotFound;
@ -37,6 +38,8 @@ use OCP\Security\ISecureRandom;
use OCP\Security\IHasher; use OCP\Security\IHasher;
use OCP\Files\Mount\IMountManager; use OCP\Files\Mount\IMountManager;
use OCP\IGroupManager; use OCP\IGroupManager;
use Symfony\Component\EventDispatcher\EventDispatcher;
use Symfony\Component\EventDispatcher\GenericEvent;
/** /**
* Class ManagerTest * Class ManagerTest
@ -70,9 +73,11 @@ class ManagerTest extends \Test\TestCase {
protected $userManager; protected $userManager;
/** @var IRootFolder | \PHPUnit_Framework_MockObject_MockObject */ /** @var IRootFolder | \PHPUnit_Framework_MockObject_MockObject */
protected $rootFolder; protected $rootFolder;
/** @var EventDispatcher | \PHPUnit_Framework_MockObject_MockObject */
protected $eventDispatcher;
public function setUp() { public function setUp() {
$this->logger = $this->getMock('\OCP\ILogger'); $this->logger = $this->getMock('\OCP\ILogger');
$this->config = $this->getMock('\OCP\IConfig'); $this->config = $this->getMock('\OCP\IConfig');
$this->secureRandom = $this->getMock('\OCP\Security\ISecureRandom'); $this->secureRandom = $this->getMock('\OCP\Security\ISecureRandom');
@ -81,6 +86,7 @@ class ManagerTest extends \Test\TestCase {
$this->groupManager = $this->getMock('\OCP\IGroupManager'); $this->groupManager = $this->getMock('\OCP\IGroupManager');
$this->userManager = $this->getMock('\OCP\IUserManager'); $this->userManager = $this->getMock('\OCP\IUserManager');
$this->rootFolder = $this->getMock('\OCP\Files\IRootFolder'); $this->rootFolder = $this->getMock('\OCP\Files\IRootFolder');
$this->eventDispatcher = $this->getMock('Symfony\Component\EventDispatcher\EventDispatcher');
$this->l = $this->getMock('\OCP\IL10N'); $this->l = $this->getMock('\OCP\IL10N');
$this->l->method('t') $this->l->method('t')
@ -100,7 +106,8 @@ class ManagerTest extends \Test\TestCase {
$this->l, $this->l,
$this->factory, $this->factory,
$this->userManager, $this->userManager,
$this->rootFolder $this->rootFolder,
$this->eventDispatcher
); );
$this->defaultProvider = $this->getMockBuilder('\OC\Share20\DefaultShareProvider') $this->defaultProvider = $this->getMockBuilder('\OC\Share20\DefaultShareProvider')
@ -127,7 +134,8 @@ class ManagerTest extends \Test\TestCase {
$this->l, $this->l,
$this->factory, $this->factory,
$this->userManager, $this->userManager,
$this->rootFolder $this->rootFolder,
$this->eventDispatcher
]); ]);
} }
@ -146,7 +154,7 @@ class ManagerTest extends \Test\TestCase {
$group = $this->getMock('\OCP\IGroup'); $group = $this->getMock('\OCP\IGroup');
$group->method('getGID')->willReturn('sharedWithGroup'); $group->method('getGID')->willReturn('sharedWithGroup');
return [ return [
[\OCP\Share::SHARE_TYPE_USER, 'sharedWithUser'], [\OCP\Share::SHARE_TYPE_USER, 'sharedWithUser'],
[\OCP\Share::SHARE_TYPE_GROUP, 'sharedWithGroup'], [\OCP\Share::SHARE_TYPE_GROUP, 'sharedWithGroup'],
@ -543,16 +551,12 @@ class ManagerTest extends \Test\TestCase {
['core', 'shareapi_enforce_links_password', 'no', 'no'], ['core', 'shareapi_enforce_links_password', 'no', 'no'],
])); ]));
$hookListner = $this->getMockBuilder('Dummy')->setMethods(['listner'])->getMock(); $this->eventDispatcher->expects($this->once())->method('dispatch')
\OCP\Util::connectHook('\OC\Share', 'verifyPassword', $hookListner, 'listner'); ->willReturnCallback(function($eventName, GenericEvent $event) {
$this->assertSame('OCP\PasswordPolicy::validate', $eventName);
$hookListner->expects($this->once()) $this->assertSame('password', $event->getSubject());
->method('listner') }
->with([ );
'password' => 'password',
'accepted' => true,
'message' => ''
]);
$result = $this->invokePrivate($this->manager, 'verifyPassword', ['password']); $result = $this->invokePrivate($this->manager, 'verifyPassword', ['password']);
$this->assertNull($result); $this->assertNull($result);
@ -567,8 +571,14 @@ class ManagerTest extends \Test\TestCase {
['core', 'shareapi_enforce_links_password', 'no', 'no'], ['core', 'shareapi_enforce_links_password', 'no', 'no'],
])); ]));
$dummy = new DummyPassword(); $this->eventDispatcher->expects($this->once())->method('dispatch')
\OCP\Util::connectHook('\OC\Share', 'verifyPassword', $dummy, 'listner'); ->willReturnCallback(function($eventName, GenericEvent $event) {
$this->assertSame('OCP\PasswordPolicy::validate', $eventName);
$this->assertSame('password', $event->getSubject());
throw new HintException('message', 'password not accepted');
}
);
$this->invokePrivate($this->manager, 'verifyPassword', ['password']); $this->invokePrivate($this->manager, 'verifyPassword', ['password']);
} }
@ -2022,7 +2032,8 @@ class ManagerTest extends \Test\TestCase {
$this->l, $this->l,
$factory, $factory,
$this->userManager, $this->userManager,
$this->rootFolder $this->rootFolder,
$this->eventDispatcher
); );
$share = $this->getMock('\OCP\Share\IShare'); $share = $this->getMock('\OCP\Share\IShare');
@ -2054,7 +2065,8 @@ class ManagerTest extends \Test\TestCase {
$this->l, $this->l,
$factory, $factory,
$this->userManager, $this->userManager,
$this->rootFolder $this->rootFolder,
$this->eventDispatcher
); );
$share = $this->getMock('\OCP\Share\IShare'); $share = $this->getMock('\OCP\Share\IShare');
@ -2531,13 +2543,6 @@ class ManagerTest extends \Test\TestCase {
} }
} }
class DummyPassword {
public function listner($array) {
$array['accepted'] = false;
$array['message'] = 'password not accepted';
}
}
class DummyFactory implements IProviderFactory { class DummyFactory implements IProviderFactory {
/** @var IShareProvider */ /** @var IShareProvider */

View File

@ -21,6 +21,9 @@
*/ */
namespace Test\User; namespace Test\User;
use OC\HintException;
use Symfony\Component\EventDispatcher\EventDispatcher;
use Symfony\Component\EventDispatcher\GenericEvent;
/** /**
* Class DatabaseTest * Class DatabaseTest
@ -30,6 +33,8 @@ namespace Test\User;
class DatabaseTest extends Backend { class DatabaseTest extends Backend {
/** @var array */ /** @var array */
private $users; private $users;
/** @var EventDispatcher | \PHPUnit_Framework_MockObject_MockObject */
private $eventDispatcher;
public function getUser() { public function getUser() {
$user = parent::getUser(); $user = parent::getUser();
@ -39,7 +44,10 @@ class DatabaseTest extends Backend {
protected function setUp() { protected function setUp() {
parent::setUp(); parent::setUp();
$this->backend=new \OC\User\Database();
$this->eventDispatcher = $this->getMock('Symfony\Component\EventDispatcher\EventDispatcher');
$this->backend=new \OC\User\Database($this->eventDispatcher);
} }
protected function tearDown() { protected function tearDown() {
@ -51,4 +59,41 @@ class DatabaseTest extends Backend {
} }
parent::tearDown(); parent::tearDown();
} }
public function testVerifyPasswordEvent() {
$user = $this->getUser();
$this->backend->createUser($user, 'pass1');
$this->eventDispatcher->expects($this->once())->method('dispatch')
->willReturnCallback(
function ($eventName, GenericEvent $event) {
$this->assertSame('OCP\PasswordPolicy::validate', $eventName);
$this->assertSame('newpass', $event->getSubject());
}
);
$this->backend->setPassword($user, 'newpass');
$this->assertSame($user, $this->backend->checkPassword($user, 'newpass'));
}
/**
* @expectedException \OC\HintException
* @expectedExceptionMessage password change failed
*/
public function testVerifyPasswordEventFail() {
$user = $this->getUser();
$this->backend->createUser($user, 'pass1');
$this->eventDispatcher->expects($this->once())->method('dispatch')
->willReturnCallback(
function ($eventName, GenericEvent $event) {
$this->assertSame('OCP\PasswordPolicy::validate', $eventName);
$this->assertSame('newpass', $event->getSubject());
throw new HintException('password change failed', 'password change failed');
}
);
$this->backend->setPassword($user, 'newpass');
$this->assertSame($user, $this->backend->checkPassword($user, 'newpass'));
}
} }