Merge pull request #2286 from nextcloud/ldap_password_pr-1

LDAP PR with tests
This commit is contained in:
Lukas Reschke 2016-11-23 20:54:31 +01:00 committed by GitHub
commit 38658da12b
15 changed files with 507 additions and 6 deletions

View File

@ -3,6 +3,11 @@
width: 85%;
}
.inlinetable {
display: inline-table;
vertical-align: bottom;
}
.tablerow {
display: table-row;
white-space: nowrap;

View File

@ -95,6 +95,10 @@ OCA = OCA || {};
$element: $('#ldap_paging_size'),
setMethod: 'setPagingSize'
},
ldap_turn_on_pwd_change: {
$element: $('#ldap_turn_on_pwd_change'),
setMethod: 'setPasswordChangeEnabled'
},
//Special Attributes
ldap_quota_attr: {
@ -288,6 +292,17 @@ OCA = OCA || {};
setPagingSize: function(size) {
this.setElementValue(this.managedItems.ldap_paging_size.$element, size);
},
/**
* sets whether the password changes per user should be enabled
*
* @param {string} doPasswordChange contains an int
*/
setPasswordChangeEnabled: function(doPasswordChange) {
this.setElementValue(
this.managedItems.ldap_turn_on_pwd_change.$element, doPasswordChange
);
},
/**
* sets the email attribute

View File

@ -40,6 +40,8 @@
namespace OCA\User_LDAP;
use OC\HintException;
use OCA\User_LDAP\Exceptions\ConstraintViolationException;
use OCA\User_LDAP\User\IUserTools;
use OCA\User_LDAP\User\Manager;
use OCA\User_LDAP\User\OfflineUser;
@ -221,6 +223,33 @@ class Access extends LDAPUtility implements IUserTools {
\OCP\Util::writeLog('user_ldap', 'Requested attribute '.$attr.' not found for '.$dn, \OCP\Util::DEBUG);
return false;
}
/**
* Set password for an LDAP user identified by a DN
*
* @param string $userDN the user in question
* @param string $password the new password
* @return bool
* @throws HintException
* @throws \Exception
*/
public function setPassword($userDN, $password) {
if(intval($this->connection->turnOnPasswordChange) !== 1) {
throw new \Exception('LDAP password changes are disabled.');
}
$cr = $this->connection->getConnectionResource();
if(!$this->ldap->isResource($cr)) {
//LDAP not available
\OCP\Util::writeLog('user_ldap', 'LDAP resource not available.', \OCP\Util::DEBUG);
return false;
}
try {
return $this->ldap->modReplace($cr, $userDN, $password);
} catch(ConstraintViolationException $e) {
throw new HintException('Password change rejected.', \OC::$server->getL10N('user_ldap')->t('Password change rejected. Hint: ').$e->getMessage(), $e->getCode());
}
}
/**
* checks whether the given attributes value is probably a DN

View File

@ -11,6 +11,7 @@
* @author Lukas Reschke <lukas@statuscode.ch>
* @author Morris Jobke <hey@morrisjobke.de>
* @author Robin McCorkell <robin@mccorkell.me.uk>
* @author Roger Szabo <roger.szabo@web.de>
*
* @license AGPL-3.0
*
@ -90,6 +91,7 @@ class Configuration {
'lastJpegPhotoLookup' => null,
'ldapNestedGroups' => false,
'ldapPagingSize' => null,
'turnOnPasswordChange' => false,
'ldapDynamicGroupMemberURL' => null,
);
@ -449,6 +451,7 @@ class Configuration {
'last_jpegPhoto_lookup' => 0,
'ldap_nested_groups' => 0,
'ldap_paging_size' => 500,
'ldap_turn_on_pwd_change' => 0,
'ldap_experienced_admin' => 0,
'ldap_dynamic_group_member_url' => '',
);
@ -505,6 +508,7 @@ class Configuration {
'last_jpegPhoto_lookup' => 'lastJpegPhotoLookup',
'ldap_nested_groups' => 'ldapNestedGroups',
'ldap_paging_size' => 'ldapPagingSize',
'ldap_turn_on_pwd_change' => 'turnOnPasswordChange',
'ldap_experienced_admin' => 'ldapExperiencedAdmin',
'ldap_dynamic_group_member_url' => 'ldapDynamicGroupMemberURL',
);

View File

@ -42,6 +42,7 @@ use OC\ServerNotAvailableException;
* @property string ldapUserFilter
* @property string ldapUserDisplayName
* @property string ldapUserDisplayName2
* @property boolean turnOnPasswordChange
* @property boolean hasPagedResultSupport
* @property string[] ldapBaseUsers
* @property int|string ldapPagingSize holds an integer

View File

@ -0,0 +1,26 @@
<?php
/**
* @copyright Copyright (c) 2016 Roger Szabo <roger.szabo@web.de>
*
* @author Roger Szabo <roger.szabo@web.de>
*
* @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 OCA\User_LDAP\Exceptions;
class ConstraintViolationException extends \Exception {}

View File

@ -163,6 +163,15 @@ interface ILDAPWrapper {
* @return resource|false an LDAP search result resource, false on error
*/
public function search($link, $baseDN, $filter, $attr, $attrsOnly = 0, $limit = 0);
/**
* Replace the value of a userPassword by $password
* @param resource $link LDAP link resource
* @param string $userDN the DN of the user whose password is to be replaced
* @param string $password the new value for the userPassword
* @return bool true on success, false otherwise
*/
public function modReplace($link, $userDN, $password);
/**
* Sets the value of the specified option to be $value

View File

@ -9,6 +9,7 @@
* @author Lukas Reschke <lukas@statuscode.ch>
* @author Morris Jobke <hey@morrisjobke.de>
* @author Robin McCorkell <robin@mccorkell.me.uk>
* @author Roger Szabo <roger.szabo@web.de>
*
* @license AGPL-3.0
*
@ -29,6 +30,7 @@
namespace OCA\User_LDAP;
use OC\ServerNotAvailableException;
use OCA\User_LDAP\Exceptions\ConstraintViolationException;
class LDAP implements ILDAPWrapper {
protected $curFunc = '';
@ -192,6 +194,16 @@ class LDAP implements ILDAPWrapper {
return $this->invokeLDAPMethod('search', $link, $baseDN, $filter, $attr, $attrsOnly, $limit);
}
/**
* @param LDAP $link
* @param string $userDN
* @param string $password
* @return bool
*/
public function modReplace($link, $userDN, $password) {
return $this->invokeLDAPMethod('mod_replace', $link, $userDN, array('userPassword' => $password));
}
/**
* @param LDAP $link
* @param string $option
@ -248,7 +260,7 @@ class LDAP implements ILDAPWrapper {
/**
* @return mixed
*/
private function invokeLDAPMethod() {
protected function invokeLDAPMethod() {
$arguments = func_get_args();
$func = 'ldap_' . array_shift($arguments);
if(function_exists($func)) {
@ -288,6 +300,9 @@ class LDAP implements ILDAPWrapper {
throw new \Exception('LDAP authentication method rejected', $errorCode);
} else if ($errorCode === 1) {
throw new \Exception('LDAP Operations error', $errorCode);
} else if ($errorCode === 19) {
ldap_get_option($this->curArgs[0], LDAP_OPT_ERROR_STRING, $extended_error);
throw new ConstraintViolationException(!empty($extended_error)?$extended_error:$errorMsg, $errorCode);
} else {
\OCP\Util::writeLog('user_ldap',
'LDAP error '.$errorMsg.' (' .

View File

@ -35,6 +35,7 @@
namespace OCA\User_LDAP;
use OC\User\Backend;
use OC\User\NoUserException;
use OCA\User_LDAP\Exceptions\NotOnLDAP;
use OCA\User_LDAP\User\OfflineUser;
@ -174,6 +175,26 @@ class User_LDAP extends BackendUtility implements \OCP\IUserBackend, \OCP\UserIn
return false;
}
/**
* Set password
* @param string $uid The username
* @param string $password The new password
* @return bool
*/
public function setPassword($uid, $password) {
$user = $this->access->userManager->get($uid);
if(!$user instanceof User) {
throw new \Exception('LDAP setPassword: Could not get user object for uid ' . $uid .
'. Maybe the LDAP entry has no set display name attribute?');
}
if($user->getUsername() !== false) {
return $this->access->setPassword($user->getDN(), $password);
}
return false;
}
/**
* Get a list of all users
*
@ -449,11 +470,12 @@ class User_LDAP extends BackendUtility implements \OCP\IUserBackend, \OCP\UserIn
* compared with OC_USER_BACKEND_CREATE_USER etc.
*/
public function implementsActions($actions) {
return (bool)((\OC\User\Backend::CHECK_PASSWORD
| \OC\User\Backend::GET_HOME
| \OC\User\Backend::GET_DISPLAYNAME
| \OC\User\Backend::PROVIDE_AVATAR
| \OC\User\Backend::COUNT_USERS)
return (bool)((Backend::CHECK_PASSWORD
| Backend::GET_HOME
| Backend::GET_DISPLAYNAME
| Backend::PROVIDE_AVATAR
| Backend::COUNT_USERS
| ((intval($this->access->connection->turnOnPasswordChange) === 1)?(Backend::SET_PASSWORD):0))
& $actions);
}

View File

@ -262,6 +262,17 @@ class User_Proxy extends Proxy implements \OCP\IUserBackend, \OCP\UserInterface,
public function deleteUser($uid) {
return $this->handleRequest($uid, 'deleteUser', array($uid));
}
/**
* Set password
* @param string $uid The username
* @param string $password The new password
* @return bool
*
*/
public function setPassword($uid, $password) {
return $this->handleRequest($uid, 'setPassword', array($uid, $password));
}
/**
* @return bool

View File

@ -101,6 +101,8 @@ style('user_ldap', 'settings');
<p><label for="ldap_dynamic_group_member_url"><?php p($l->t('Dynamic Group Member URL'));?></label><input type="text" id="ldap_dynamic_group_member_url" name="ldap_dynamic_group_member_url" title="<?php p($l->t('The LDAP attribute that on group objects contains an LDAP search URL that determines what objects belong to the group. (An empty setting disables dynamic group membership functionality.)'));?>" data-default="<?php p($_['ldap_dynamic_group_member_url_default']); ?>" /></p>
<p><label for="ldap_nested_groups"><?php p($l->t('Nested Groups'));?></label><input type="checkbox" id="ldap_nested_groups" name="ldap_nested_groups" value="1" data-default="<?php p($_['ldap_nested_groups_default']); ?>" title="<?php p($l->t('When switched on, groups that contain groups are supported. (Only works if the group member attribute contains DNs.)'));?>" /></p>
<p><label for="ldap_paging_size"><?php p($l->t('Paging chunksize'));?></label><input type="number" id="ldap_paging_size" name="ldap_paging_size" title="<?php p($l->t('Chunksize used for paged LDAP searches that may return bulky results like user or group enumeration. (Setting it 0 disables paged LDAP searches in those situations.)'));?>" data-default="<?php p($_['ldap_paging_size_default']); ?>" /></p>
<p><label for="ldap_turn_on_pwd_change"><?php p($l->t('Enable LDAP password changes per user'));?></label><span class="inlinetable"><span class="tablerow left"><input type="checkbox" id="ldap_turn_on_pwd_change" name="ldap_turn_on_pwd_change" value="1" data-default="<?php p($_['ldap_turn_on_pwd_change_default']); ?>" title="<?php p($l->t('Allow LDAP users to change their password and allow Super Administrators and Group Administrators to change the password of their LDAP users. Only works when access control policies are configured accordingly on the LDAP server. As passwords are sent in plaintext to the LDAP server, transport encryption must be used and password hashing should be configured on the LDAP server.'));?>" /><span class="tablecell"><?php p($l->t('(New password is sent as plain text to LDAP)'));?></span></span>
</span><br/></p>
</div>
<h3><?php p($l->t('Special Attributes'));?></h3>
<div>

View File

@ -1,12 +1,14 @@
<?php
/**
* @copyright Copyright (c) 2016, ownCloud, Inc.
* @copyright Copyright (c) 2016, Lukas Reschke <lukas@statuscode.ch>
*
* @author Andreas Fischer <bantu@owncloud.com>
* @author Arthur Schiwon <blizzz@arthur-schiwon.de>
* @author Joas Schilling <coding@schilljs.com>
* @author Morris Jobke <hey@morrisjobke.de>
* @author Thomas Müller <thomas.mueller@tmit.eu>
* @author Lukas Reschke <lukas@statuscode.ch>
*
* @license AGPL-3.0
*
@ -28,9 +30,11 @@ namespace OCA\User_LDAP\Tests;
use OCA\User_LDAP\Access;
use OCA\User_LDAP\Connection;
use OCA\User_LDAP\Exceptions\ConstraintViolationException;
use OCA\User_LDAP\FilesystemHelper;
use OCA\User_LDAP\Helper;
use OCA\User_LDAP\ILDAPWrapper;
use OCA\User_LDAP\LDAP;
use OCA\User_LDAP\LogWrapper;
use OCA\User_LDAP\User\Manager;
use OCP\IAvatarManager;
@ -47,6 +51,31 @@ use OCP\IUserManager;
* @package OCA\User_LDAP\Tests
*/
class AccessTest extends \Test\TestCase {
/** @var Connection|\PHPUnit_Framework_MockObject_MockObject */
private $connection;
/** @var LDAP|\PHPUnit_Framework_MockObject_MockObject */
private $ldap;
/** @var Manager|\PHPUnit_Framework_MockObject_MockObject */
private $userManager;
/** @var Helper|\PHPUnit_Framework_MockObject_MockObject */
private $helper;
/** @var Access */
private $access;
public function setUp() {
$this->connection = $this->createMock(Connection::class);
$this->ldap = $this->createMock(LDAP::class);
$this->userManager = $this->createMock(Manager::class);
$this->helper = $this->createMock(Helper::class);
$this->access = new Access(
$this->connection,
$this->ldap,
$this->userManager,
$this->helper
);
}
private function getConnectorAndLdapMock() {
$lw = $this->createMock(ILDAPWrapper::class);
$connector = $this->getMockBuilder(Connection::class)
@ -317,4 +346,84 @@ class AccessTest extends \Test\TestCase {
$values = $access->readAttribute('uid=whoever,dc=example,dc=org', $attribute);
$this->assertSame($values[0], strtolower($dnFromServer));
}
/**
* @expectedException \Exception
* @expectedExceptionMessage LDAP password changes are disabled
*/
public function testSetPasswordWithDisabledChanges() {
$this->connection
->method('__get')
->willReturn(false);
$this->access->setPassword('CN=foo', 'MyPassword');
}
public function testSetPasswordWithLdapNotAvailable() {
$this->connection
->method('__get')
->willReturn(true);
$connection = $this->createMock(LDAP::class);
$this->connection
->expects($this->once())
->method('getConnectionResource')
->willReturn($connection);
$this->ldap
->expects($this->once())
->method('isResource')
->with($connection)
->willReturn(false);
$this->assertFalse($this->access->setPassword('CN=foo', 'MyPassword'));
}
/**
* @expectedException \OC\HintException
* @expectedExceptionMessage Password change rejected.
*/
public function testSetPasswordWithRejectedChange() {
$this->connection
->method('__get')
->willReturn(true);
$connection = $this->createMock(LDAP::class);
$this->connection
->expects($this->once())
->method('getConnectionResource')
->willReturn($connection);
$this->ldap
->expects($this->once())
->method('isResource')
->with($connection)
->willReturn(true);
$this->ldap
->expects($this->once())
->method('modReplace')
->with($connection, 'CN=foo', 'MyPassword')
->willThrowException(new ConstraintViolationException());
$this->access->setPassword('CN=foo', 'MyPassword');
}
public function testSetPassword() {
$this->connection
->method('__get')
->willReturn(true);
$connection = $this->createMock(LDAP::class);
$this->connection
->expects($this->once())
->method('getConnectionResource')
->willReturn($connection);
$this->ldap
->expects($this->once())
->method('isResource')
->with($connection)
->willReturn(true);
$this->ldap
->expects($this->once())
->method('modReplace')
->with($connection, 'CN=foo', 'MyPassword')
->willReturn(true);
$this->assertTrue($this->access->setPassword('CN=foo', 'MyPassword'));
}
}

View File

@ -0,0 +1,50 @@
<?php
/**
* @copyright Copyright (c) 2016 Lukas Reschke <lukas@statuscode.ch>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\User_LDAP\Tests;
use OCA\User_LDAP\LDAP;
use Test\TestCase;
class LDAPTest extends TestCase {
/** @var LDAP|\PHPUnit_Framework_MockObject_MockObject */
private $ldap;
public function setUp() {
parent::setUp();
$this->ldap = $this->getMockBuilder(LDAP::class)
->setMethods(['invokeLDAPMethod'])
->getMock();
}
public function testModReplace() {
$link = $this->createMock(LDAP::class);
$userDN = 'CN=user';
$password = 'MyPassword';
$this->ldap
->expects($this->once())
->method('invokeLDAPMethod')
->with('mod_replace', $link, $userDN, array('userPassword' => $password))
->willReturn(true);
$this->assertTrue($this->ldap->modReplace($link, $userDN, $password));
}
}

View File

@ -9,6 +9,7 @@
* @author Morris Jobke <hey@morrisjobke.de>
* @author Robin McCorkell <robin@mccorkell.me.uk>
* @author Thomas Müller <thomas.mueller@tmit.eu>
* @author Roger Szabo <roger.szabo@web.de>
*
* @license AGPL-3.0
*
@ -36,8 +37,10 @@ use OCA\User_LDAP\ILDAPWrapper;
use OCA\User_LDAP\LogWrapper;
use OCA\User_LDAP\User\Manager;
use OCA\User_LDAP\User\OfflineUser;
use OC\HintException;
use OCA\User_LDAP\User\User;
use OCA\User_LDAP\User_LDAP as UserLDAP;
use OCA\User_LDAP\User_LDAP;
use OCP\IAvatarManager;
use OCP\IConfig;
use OCP\IDBConnection;
@ -958,5 +961,144 @@ class User_LDAPTest extends TestCase {
// and once again to verify that caching works
$backend->loginName2UserName($loginName);
}
/**
* Prepares the Access mock for setPassword tests
* @param \OCA\User_LDAP\Access|\PHPUnit_Framework_MockObject_MockObject $access mock
* @return void
*/
private function prepareAccessForSetPassword(&$access, $enablePasswordChange = true) {
$access->connection->expects($this->any())
->method('__get')
->will($this->returnCallback(function($name) use (&$enablePasswordChange) {
if($name === 'ldapLoginFilter') {
return '%uid';
}
if($name === 'turnOnPasswordChange') {
return $enablePasswordChange?1:0;
}
return null;
}));
$access->connection->expects($this->any())
->method('getFromCache')
->will($this->returnCallback(function($uid) {
if($uid === 'userExists'.'roland') {
return true;
}
return null;
}));
$access->expects($this->any())
->method('fetchListOfUsers')
->will($this->returnCallback(function($filter) {
if($filter === 'roland') {
return array(array('dn' => ['dnOfRoland,dc=test']));
}
return array();
}));
$access->expects($this->any())
->method('fetchUsersByLoginName')
->will($this->returnCallback(function($uid) {
if($uid === 'roland') {
return array(array('dn' => ['dnOfRoland,dc=test']));
}
return array();
}));
$access->expects($this->any())
->method('dn2username')
->with($this->equalTo('dnOfRoland,dc=test'))
->will($this->returnValue('roland'));
$access->expects($this->any())
->method('stringResemblesDN')
->with($this->equalTo('dnOfRoland,dc=test'))
->will($this->returnValue(true));
$access->expects($this->any())
->method('setPassword')
->will($this->returnCallback(function($uid, $password) {
if(strlen($password) <= 5) {
throw new HintException('Password fails quality checking policy', '', 19);
}
return true;
}));
}
/**
* @expectedException \OC\HintException
* @expectedExceptionMessage Password fails quality checking policy
*/
public function testSetPasswordInvalid() {
$access = $this->getAccessMock();
$this->prepareAccessForSetPassword($access);
$backend = new UserLDAP($access, $this->createMock(IConfig::class));
\OC_User::useBackend($backend);
$this->assertTrue(\OC_User::setPassword('roland', 'dt'));
}
public function testSetPasswordValid() {
$access = $this->getAccessMock();
$this->prepareAccessForSetPassword($access);
$backend = new UserLDAP($access, $this->createMock(IConfig::class));
\OC_User::useBackend($backend);
$this->assertTrue(\OC_User::setPassword('roland', 'dt12234$'));
}
public function testSetPasswordValidDisabled() {
$access = $this->getAccessMock();
$this->prepareAccessForSetPassword($access, false);
$backend = new UserLDAP($access, $this->createMock(IConfig::class));
\OC_User::useBackend($backend);
$this->assertFalse(\OC_User::setPassword('roland', 'dt12234$'));
}
/**
* @expectedException \Exception
* @expectedExceptionMessage LDAP setPassword: Could not get user object for uid NotExistingUser. Maybe the LDAP entry has no set display name attribute?
*/
public function testSetPasswordWithInvalidUser() {
$access = $this->createMock(Access::class);
$access->userManager = $this->createMock(IUserManager::class);
$access->userManager
->expects($this->once())
->method('get')
->with('NotExistingUser')
->willReturn(null);
$config = $this->createMock(IConfig::class);
$ldap = new User_LDAP(
$access,
$config
);
$ldap->setPassword('NotExistingUser', 'Password');
}
public function testSetPasswordWithUsernameFalse() {
$user = $this->createMock(User::class);
$user
->expects($this->once())
->method('getUsername')
->willReturn(false);
$access = $this->createMock(Access::class);
$access->userManager = $this->createMock(IUserManager::class);
$access->userManager
->expects($this->once())
->method('get')
->with('NotExistingUser')
->willReturn($user);
$config = $this->createMock(IConfig::class);
$ldap = new User_LDAP(
$access,
$config
);
$this->assertFalse($ldap->setPassword('NotExistingUser', 'Password'));
}
}

View File

@ -0,0 +1,61 @@
<?php
/**
* @copyright Copyright (c) 2016 Lukas Reschke <lukas@statuscode.ch>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\User_LDAP\Tests;
use OCA\User_LDAP\ILDAPWrapper;
use OCA\User_LDAP\User_Proxy;
use OCP\IConfig;
use Test\TestCase;
class User_ProxyTest extends TestCase {
/** @var ILDAPWrapper|\PHPUnit_Framework_MockObject_MockObject */
private $ldapWrapper;
/** @var IConfig|\PHPUnit_Framework_MockObject_MockObject */
private $config;
/** @var User_Proxy|\PHPUnit_Framework_MockObject_MockObject */
private $proxy;
public function setUp() {
parent::setUp();
$this->ldapWrapper = $this->createMock(ILDAPWrapper::class);
$this->config = $this->createMock(IConfig::class);
$this->proxy = $this->getMockBuilder(User_Proxy::class)
->setConstructorArgs([
[],
$this->ldapWrapper,
$this->config,
])
->setMethods(['handleRequest'])
->getMock();
}
public function testSetPassword() {
$this->proxy
->expects($this->once())
->method('handleRequest')
->with('MyUid', 'setPassword', ['MyUid', 'MyPassword'])
->willReturn(true);
$this->assertTrue($this->proxy->setPassword('MyUid', 'MyPassword'));
}
}