Warn for password reset when files_encryption is enabled

This patch wil warn the user of the consequences when resetting the password and requires checking a checkbox (as we had in the past) to reset a password.

Furthermore I updated the code to use our new classes and added some unit tests for it 👯

Fixes https://github.com/owncloud/core/issues/11438
This commit is contained in:
Lukas Reschke 2014-10-20 19:05:48 +02:00
parent af7688ec17
commit 1b50d4f7ce
10 changed files with 341 additions and 149 deletions

View File

@ -10,13 +10,22 @@
namespace OC\Core; namespace OC\Core;
use OC\AppFramework\Utility\SimpleContainer;
use \OCP\AppFramework\App; use \OCP\AppFramework\App;
use OC\Core\LostPassword\Controller\LostController; use OC\Core\LostPassword\Controller\LostController;
use OC\Core\User\UserController; use OC\Core\User\UserController;
use \OCP\Util;
/**
* Class Application
*
* @package OC\Core
*/
class Application extends App { class Application extends App {
/**
* @param array $urlParams
*/
public function __construct(array $urlParams=array()){ public function __construct(array $urlParams=array()){
parent::__construct('core', $urlParams); parent::__construct('core', $urlParams);
@ -25,29 +34,56 @@ class Application extends App {
/** /**
* Controllers * Controllers
*/ */
$container->registerService('LostController', function($c) { $container->registerService('LostController', function(SimpleContainer $c) {
return new LostController( return new LostController(
$c->query('AppName'), $c->query('AppName'),
$c->query('Request'), $c->query('Request'),
$c->query('ServerContainer')->getURLGenerator(), $c->query('URLGenerator'),
$c->query('ServerContainer')->getUserManager(), $c->query('UserManager'),
new \OC_Defaults(), $c->query('Defaults'),
$c->query('ServerContainer')->getL10N('core'), $c->query('L10N'),
$c->query('ServerContainer')->getConfig(), $c->query('Config'),
$c->query('ServerContainer')->getUserSession(), $c->query('SecureRandom'),
\OCP\Util::getDefaultEmailAddress('lostpassword-noreply'), $c->query('DefaultEmailAddress'),
\OC_App::isEnabled('files_encryption') $c->query('IsEncryptionEnabled')
); );
}); });
$container->registerService('UserController', function($c) { $container->registerService('UserController', function(SimpleContainer $c) {
return new UserController( return new UserController(
$c->query('AppName'), $c->query('AppName'),
$c->query('Request'), $c->query('Request'),
$c->query('ServerContainer')->getUserManager(), $c->query('UserManager'),
new \OC_Defaults() $c->query('Defaults')
); );
}); });
/**
* Core class wrappers
*/
$container->registerService('IsEncryptionEnabled', function() {
return \OC_App::isEnabled('files_encryption');
});
$container->registerService('URLGenerator', function(SimpleContainer $c) {
return $c->query('ServerContainer')->getURLGenerator();
});
$container->registerService('UserManager', function(SimpleContainer $c) {
return $c->query('ServerContainer')->getUserManager();
});
$container->registerService('Config', function(SimpleContainer $c) {
return $c->query('ServerContainer')->getConfig();
});
$container->registerService('L10N', function(SimpleContainer $c) {
return $c->query('ServerContainer')->getL10N('core');
});
$container->registerService('SecureRandom', function(SimpleContainer $c) {
return $c->query('ServerContainer')->getSecureRandom();
});
$container->registerService('Defaults', function() {
return new \OC_Defaults;
});
$container->registerService('DefaultEmailAddress', function() {
return Util::getDefaultEmailAddress('lostpassword-noreply');
});
} }
} }

View File

@ -8,19 +8,12 @@ OC.Lostpassword = {
+ ('<br /><input type="checkbox" id="encrypted-continue" value="Yes" />') + ('<br /><input type="checkbox" id="encrypted-continue" value="Yes" />')
+ '<label for="encrypted-continue">' + '<label for="encrypted-continue">'
+ t('core', 'I know what I\'m doing') + t('core', 'I know what I\'m doing')
+ '</label><br />' + '</label><br />',
+ '<a id="lost-password-encryption" href>'
+ t('core', 'Reset password')
+ '</a>',
resetErrorMsg : t('core', 'Password can not be changed. Please contact your administrator.'), resetErrorMsg : t('core', 'Password can not be changed. Please contact your administrator.'),
init : function() { init : function() {
if ($('#lost-password-encryption').length){ $('#lost-password').click(OC.Lostpassword.sendLink);
$('#lost-password-encryption').click(OC.Lostpassword.sendLink);
} else {
$('#lost-password').click(OC.Lostpassword.sendLink);
}
$('#reset-password #submit').click(OC.Lostpassword.resetPassword); $('#reset-password #submit').click(OC.Lostpassword.resetPassword);
}, },
@ -32,8 +25,7 @@ OC.Lostpassword = {
$.post( $.post(
OC.generateUrl('/lostpassword/email'), OC.generateUrl('/lostpassword/email'),
{ {
user : $('#user').val(), user : $('#user').val()
proceed: $('#encrypted-continue').attr('checked') ? 'Yes' : 'No'
}, },
OC.Lostpassword.sendLinkDone OC.Lostpassword.sendLinkDone
); );
@ -84,7 +76,8 @@ OC.Lostpassword = {
$.post( $.post(
$('#password').parents('form').attr('action'), $('#password').parents('form').attr('action'),
{ {
password : $('#password').val() password : $('#password').val(),
proceed: $('#encrypted-continue').attr('checked') ? 'true' : 'false'
}, },
OC.Lostpassword.resetDone OC.Lostpassword.resetDone
); );
@ -126,7 +119,7 @@ OC.Lostpassword = {
getResetStatusNode : function (){ getResetStatusNode : function (){
if (!$('#lost-password').length){ if (!$('#lost-password').length){
$('<p id="lost-password"></p>').insertAfter($('#submit')); $('<p id="lost-password"></p>').insertBefore($('#reset-password fieldset'));
} else { } else {
$('#lost-password').replaceWith($('<p id="lost-password"></p>')); $('#lost-password').replaceWith($('<p id="lost-password"></p>'));
} }

View File

@ -9,68 +9,72 @@
namespace OC\Core\LostPassword\Controller; namespace OC\Core\LostPassword\Controller;
use \OCP\AppFramework\Controller; use \OCP\AppFramework\Controller;
use \OCP\AppFramework\Http\JSONResponse;
use \OCP\AppFramework\Http\TemplateResponse; use \OCP\AppFramework\Http\TemplateResponse;
use \OCP\IURLGenerator; use \OCP\IURLGenerator;
use \OCP\IRequest; use \OCP\IRequest;
use \OCP\IL10N; use \OCP\IL10N;
use \OCP\IConfig; use \OCP\IConfig;
use \OCP\IUserSession; use OCP\IUserManager;
use \OC\Core\LostPassword\EncryptedDataException; use \OC\Core\LostPassword\EncryptedDataException;
use OCP\Security\ISecureRandom;
use \OC_Defaults;
use OCP\Security\StringUtils;
/**
* Class LostController
*
* @package OC\Core\LostPassword\Controller
*/
class LostController extends Controller { class LostController extends Controller {
/** /** @var IURLGenerator */
* @var \OCP\IURLGenerator
*/
protected $urlGenerator; protected $urlGenerator;
/** @var IUserManager */
/**
* @var \OCP\IUserManager
*/
protected $userManager; protected $userManager;
/** @var OC_Defaults */
/**
* @var \OC_Defaults
*/
protected $defaults; protected $defaults;
/** @var IL10N */
/**
* @var IL10N
*/
protected $l10n; protected $l10n;
/** @var string */
protected $from; protected $from;
/** @var bool */
protected $isDataEncrypted; protected $isDataEncrypted;
/** @var IConfig */
/**
* @var IConfig
*/
protected $config; protected $config;
/** @var ISecureRandom */
protected $secureRandom;
/** /**
* @var IUserSession * @param string $appName
* @param IRequest $request
* @param IURLGenerator $urlGenerator
* @param $userManager
* @param $defaults
* @param IL10N $l10n
* @param IConfig $config
* @param ISecureRandom $secureRandom
* @param $from
* @param $isDataEncrypted
*/ */
protected $userSession;
public function __construct($appName, public function __construct($appName,
IRequest $request, IRequest $request,
IURLGenerator $urlGenerator, IURLGenerator $urlGenerator,
$userManager, IUserManager $userManager,
$defaults, OC_Defaults $defaults,
IL10N $l10n, IL10N $l10n,
IConfig $config, IConfig $config,
IUserSession $userSession, ISecureRandom $secureRandom,
$from, $from,
$isDataEncrypted) { $isDataEncrypted) {
parent::__construct($appName, $request); parent::__construct($appName, $request);
$this->urlGenerator = $urlGenerator; $this->urlGenerator = $urlGenerator;
$this->userManager = $userManager; $this->userManager = $userManager;
$this->defaults = $defaults; $this->defaults = $defaults;
$this->l10n = $l10n; $this->l10n = $l10n;
$this->secureRandom = $secureRandom;
$this->from = $from; $this->from = $from;
$this->isDataEncrypted = $isDataEncrypted; $this->isDataEncrypted = $isDataEncrypted;
$this->config = $config; $this->config = $config;
$this->userSession = $userSession;
} }
/** /**
@ -81,23 +85,31 @@ class LostController extends Controller {
* *
* @param string $token * @param string $token
* @param string $userId * @param string $userId
* @return TemplateResponse
*/ */
public function resetform($token, $userId) { public function resetform($token, $userId) {
return new TemplateResponse( return new TemplateResponse(
'core/lostpassword', 'core/lostpassword',
'resetpassword', 'resetpassword',
array( array(
'isEncrypted' => $this->isDataEncrypted, 'link' => $this->urlGenerator->linkToRouteAbsolute('core.lost.setPassword', array('userId' => $userId, 'token' => $token)),
'link' => $this->getLink('core.lost.setPassword', $userId, $token),
), ),
'guest' 'guest'
); );
} }
/**
* @param $message
* @param array $additional
* @return array
*/
private function error($message, array $additional=array()) { private function error($message, array $additional=array()) {
return array_merge(array('status' => 'error', 'msg' => $message), $additional); return array_merge(array('status' => 'error', 'msg' => $message), $additional);
} }
/**
* @return array
*/
private function success() { private function success() {
return array('status'=>'success'); return array('status'=>'success');
} }
@ -106,14 +118,12 @@ class LostController extends Controller {
* @PublicPage * @PublicPage
* *
* @param string $user * @param string $user
* @param bool $proceed * @return array
*/ */
public function email($user, $proceed){ public function email($user){
// FIXME: use HTTP error codes // FIXME: use HTTP error codes
try { try {
$this->sendEmail($user, $proceed); $this->sendEmail($user);
} catch (EncryptedDataException $e){
return $this->error('', array('encryption' => '1'));
} catch (\Exception $e){ } catch (\Exception $e){
return $this->error($e->getMessage()); return $this->error($e->getMessage());
} }
@ -121,15 +131,23 @@ class LostController extends Controller {
return $this->success(); return $this->success();
} }
/** /**
* @PublicPage * @PublicPage
* @param string $token
* @param string $userId
* @param string $password
* @param boolean $proceed
* @return array
*/ */
public function setPassword($token, $userId, $password) { public function setPassword($token, $userId, $password, $proceed) {
if ($this->isDataEncrypted && !$proceed){
return $this->error('', array('encryption' => true));
}
try { try {
$user = $this->userManager->get($userId); $user = $this->userManager->get($userId);
if (!$this->checkToken($userId, $token)) { if (!StringUtils::equals($this->config->getUserValue($userId, 'owncloud', 'lostpassword'), $token)) {
throw new \Exception($this->l10n->t('Couldn\'t reset password because the token is invalid')); throw new \Exception($this->l10n->t('Couldn\'t reset password because the token is invalid'));
} }
@ -137,9 +155,8 @@ class LostController extends Controller {
throw new \Exception(); throw new \Exception();
} }
// FIXME: should be added to the all config at some point $this->config->deleteUserValue($userId, 'owncloud', 'lostpassword');
\OC_Preferences::deleteKey($userId, 'owncloud', 'lostpassword'); @\OC_User::unsetMagicInCookie();
$this->userSession->unsetMagicInCookie();
} catch (\Exception $e){ } catch (\Exception $e){
return $this->error($e->getMessage()); return $this->error($e->getMessage());
@ -148,36 +165,32 @@ class LostController extends Controller {
return $this->success(); return $this->success();
} }
/**
protected function sendEmail($user, $proceed) { * @param string $user
if ($this->isDataEncrypted && !$proceed){ * @throws \Exception
throw new EncryptedDataException(); */
} protected function sendEmail($user) {
if (!$this->userManager->userExists($user)) { if (!$this->userManager->userExists($user)) {
throw new \Exception( throw new \Exception($this->l10n->t('Couldn\'t send reset email. Please make sure your username is correct.'));
$this->l10n->t('Couldn\'t send reset email. Please make sure '.
'your username is correct.'));
} }
$token = hash('sha256', \OC_Util::generateRandomBytes(30));
// Hash the token again to prevent timing attacks
$this->config->setUserValue(
$user, 'owncloud', 'lostpassword', hash('sha256', $token)
);
$email = $this->config->getUserValue($user, 'settings', 'email'); $email = $this->config->getUserValue($user, 'settings', 'email');
if (empty($email)) { if (empty($email)) {
throw new \Exception( throw new \Exception(
$this->l10n->t('Couldn\'t send reset email because there is no '. $this->l10n->t('Couldn\'t send reset email because there is no '.
'email address for this username. Please ' . 'email address for this username. Please ' .
'contact your administrator.') 'contact your administrator.')
); );
} }
$link = $this->getLink('core.lost.resetform', $user, $token); $token = $this->secureRandom->getMediumStrengthGenerator()->generate(21,
ISecureRandom::CHAR_DIGITS.
ISecureRandom::CHAR_LOWER.
ISecureRandom::CHAR_UPPER);
$this->config->setUserValue($user, 'owncloud', 'lostpassword', $token);
$link = $this->urlGenerator->linkToRouteAbsolute('core.lost.setPassword', array('userId' => $user, 'token' => $token));
$tmpl = new \OC_Template('core/lostpassword', 'email'); $tmpl = new \OC_Template('core/lostpassword', 'email');
$tmpl->assign('link', $link, false); $tmpl->assign('link', $link, false);
@ -200,23 +213,4 @@ class LostController extends Controller {
} }
} }
protected function getLink($route, $user, $token){
$parameters = array(
'token' => $token,
'userId' => $user
);
$link = $this->urlGenerator->linkToRoute($route, $parameters);
return $this->urlGenerator->getAbsoluteUrl($link);
}
protected function checkToken($user, $token) {
return $this->config->getUserValue(
$user, 'owncloud', 'lostpassword'
) === hash('sha256', $token);
}
} }

View File

@ -1,14 +0,0 @@
<?php
/**
* @author Victor Dubiniuk
* @copyright 2013 Victor Dubiniuk victor.dubiniuk@gmail.com
*
* This file is licensed under the Affero General Public License version 3 or
* later.
* See the COPYING-README file.
*/
namespace OC\Core\LostPassword;
class EncryptedDataException extends \Exception{
}

View File

@ -1,20 +0,0 @@
<?php
//load the file we need
OCP\Util::addStyle('lostpassword', 'lostpassword'); ?>
<form action="<?php print_unescaped($_['link']) ?>" method="post">
<fieldset>
<div class="update"><?php p($l->t('You will receive a link to reset your password via Email.')); ?></div>
<p>
<input type="text" name="user" id="user" placeholder="<?php p($l->t( 'Username' )); ?>" value="" autocomplete="off" required autofocus />
<label for="user" class="infield"><?php p($l->t( 'Username' )); ?></label>
<img class="svg" src="<?php print_unescaped(image_path('', 'actions/user.svg')); ?>" alt=""/>
<?php if ($_['isEncrypted']): ?>
<br />
<p class="warning"><?php p($l->t("Your files are encrypted. If you haven't enabled the recovery key, there will be no way to get your data back after your password is reset. If you are not sure what to do, please contact your administrator before you continue. Do you really want to continue?")); ?><br />
<input type="checkbox" name="continue" value="Yes" />
<?php p($l->t('Yes, I really want to reset my password now')); ?></p>
<?php endif; ?>
</p>
<input type="submit" id="submit" value="<?php p($l->t('Reset')); ?>" />
</fieldset>
</form>

View File

@ -1,4 +1,10 @@
<?php OCP\Util::addStyle('lostpassword', 'resetpassword'); ?> <?php
/** @var array $_ */
/** @var $l OC_L10N */
style('lostpassword', 'resetpassword');
script('core', 'lostpassword');
?>
<form action="<?php print_unescaped($_['link']) ?>" id="reset-password" method="post"> <form action="<?php print_unescaped($_['link']) ?>" id="reset-password" method="post">
<fieldset> <fieldset>
<p> <p>
@ -9,4 +15,3 @@
<input type="submit" id="submit" value="<?php p($l->t('Reset password')); ?>" /> <input type="submit" id="submit" value="<?php p($l->t('Reset password')); ?>" />
</fieldset> </fieldset>
</form> </form>
<?php OCP\Util::addScript('core', 'lostpassword'); ?>

View File

@ -0,0 +1,195 @@
<?php
/**
* Copyright (c) 2014 Lukas Reschke <lukas@owncloud.com>
* This file is licensed under the Affero General Public License version 3 or
* later.
* See the COPYING-README file.
*/
namespace OC\Core\LostPassword\Controller;
use OC\Core\Application;
use OCP\AppFramework\Http\TemplateResponse;
/**
* Class LostControllerTest
*
* @package OC\Core\LostPassword\Controller
*/
class LostControllerTest extends \PHPUnit_Framework_TestCase {
private $container;
/** @var LostController */
private $lostController;
protected function setUp() {
$app = new Application();
$this->container = $app->getContainer();
$this->container['AppName'] = 'core';
$this->container['Config'] = $this->getMockBuilder('\OCP\IConfig')
->disableOriginalConstructor()->getMock();
$this->container['L10N'] = $this->getMockBuilder('\OCP\IL10N')
->disableOriginalConstructor()->getMock();
$this->container['Defaults'] = $this->getMockBuilder('\OC_Defaults')
->disableOriginalConstructor()->getMock();
$this->container['UserManager'] = $this->getMockBuilder('\OCP\IUserManager')
->disableOriginalConstructor()->getMock();
$this->container['Config'] = $this->getMockBuilder('\OCP\IConfig')
->disableOriginalConstructor()->getMock();
$this->container['URLGenerator'] = $this->getMockBuilder('\OCP\IURLGenerator')
->disableOriginalConstructor()->getMock();
$this->container['SecureRandom'] = $this->getMockBuilder('\OCP\Security\ISecureRandom')
->disableOriginalConstructor()->getMock();
$this->container['IsEncryptionEnabled'] = true;
$this->lostController = $this->container['LostController'];
}
public function testResetFormUnsuccessful() {
$userId = 'admin';
$token = 'MySecretToken';
$this->container['URLGenerator']
->expects($this->once())
->method('linkToRouteAbsolute')
->with('core.lost.setPassword', array('userId' => 'admin', 'token' => 'MySecretToken'))
->will($this->returnValue('https://ownCloud.com/index.php/lostpassword/'));
$response = $this->lostController->resetform($token, $userId);
$expectedResponse = new TemplateResponse('core/lostpassword',
'resetpassword',
array(
'link' => 'https://ownCloud.com/index.php/lostpassword/',
),
'guest');
$this->assertEquals($expectedResponse, $response);
}
public function testEmailUnsucessful() {
$existingUser = 'ExistingUser';
$nonExistingUser = 'NonExistingUser';
$this->container['UserManager']
->expects($this->any())
->method('userExists')
->will($this->returnValueMap(array(
array(true, $existingUser),
array(false, $nonExistingUser)
)));
$this->container['L10N']
->expects($this->any())
->method('t')
->will(
$this->returnValueMap(
array(
array('Couldn\'t send reset email. Please make sure your username is correct.', array(),
'Couldn\'t send reset email. Please make sure your username is correct.'),
)
));
// With a non existing user
$response = $this->lostController->email($nonExistingUser);
$expectedResponse = array('status' => 'error', 'msg' => 'Couldn\'t send reset email. Please make sure your username is correct.');
$this->assertSame($expectedResponse, $response);
// With no mail address
$this->container['Config']
->expects($this->any())
->method('getUserValue')
->with($existingUser, 'settings', 'email')
->will($this->returnValue(null));
$response = $this->lostController->email($existingUser);
$expectedResponse = array('status' => 'error', 'msg' => 'Couldn\'t send reset email. Please make sure your username is correct.');
$this->assertSame($expectedResponse, $response);
}
public function testEmailSuccessful() {
$randomToken = $this->container['SecureRandom'];
$this->container['SecureRandom']
->expects($this->once())
->method('generate')
->with('21')
->will($this->returnValue('ThisIsMaybeANotSoSecretToken!'));
$this->container['UserManager']
->expects($this->once())
->method('userExists')
->with('ExistingUser')
->will($this->returnValue(true));
$this->container['Config']
->expects($this->once())
->method('getUserValue')
->with('ExistingUser', 'settings', 'email')
->will($this->returnValue('test@example.com'));
$this->container['SecureRandom']
->expects($this->once())
->method('getMediumStrengthGenerator')
->will($this->returnValue($randomToken));
$this->container['Config']
->expects($this->once())
->method('setUserValue')
->with('ExistingUser', 'owncloud', 'lostpassword', 'ThisIsMaybeANotSoSecretToken!');
$this->container['URLGenerator']
->expects($this->once())
->method('linkToRouteAbsolute')
->with('core.lost.setPassword', array('userId' => 'ExistingUser', 'token' => 'ThisIsMaybeANotSoSecretToken!'))
->will($this->returnValue('https://ownCloud.com/index.php/lostpassword/'));
$response = $this->lostController->email('ExistingUser', true);
$expectedResponse = array('status' => 'success');
$this->assertSame($expectedResponse, $response);
}
public function testSetPasswordUnsuccessful() {
$this->container['L10N']
->expects($this->any())
->method('t')
->will(
$this->returnValueMap(
array(
array('Couldn\'t reset password because the token is invalid', array(),
'Couldn\'t reset password because the token is invalid'),
)
));
$this->container['Config']
->expects($this->once())
->method('getUserValue')
->with('InvalidTokenUser', 'owncloud', 'lostpassword')
->will($this->returnValue('TheOnlyAndOnlyOneTokenToResetThePassword'));
// With an invalid token
$userName = 'InvalidTokenUser';
$response = $this->lostController->setPassword('wrongToken', $userName, 'NewPassword', true);
$expectedResponse = array('status' => 'error', 'msg' => 'Couldn\'t reset password because the token is invalid');
$this->assertSame($expectedResponse, $response);
// With a valid token and no proceed
$response = $this->lostController->setPassword('TheOnlyAndOnlyOneTokenToResetThePassword!', $userName, 'NewPassword', false);
$expectedResponse = array('status' => 'error', 'msg' => '', 'encryption' => true);
$this->assertSame($expectedResponse, $response);
}
public function testSetPasswordSuccessful() {
$this->container['Config']
->expects($this->once())
->method('getUserValue')
->with('ValidTokenUser', 'owncloud', 'lostpassword')
->will($this->returnValue('TheOnlyAndOnlyOneTokenToResetThePassword'));
$user = $this->getMockBuilder('\OCP\IUser')
->disableOriginalConstructor()->getMock();
$user->expects($this->once())
->method('setPassword')
->with('NewPassword')
->will($this->returnValue(true));
$this->container['UserManager']
->expects($this->once())
->method('get')
->with('ValidTokenUser')
->will($this->returnValue($user));
$this->container['Config']
->expects($this->once())
->method('deleteUserValue')
->with('ValidTokenUser', 'owncloud', 'lostpassword');
$response = $this->lostController->setPassword('TheOnlyAndOnlyOneTokenToResetThePassword', 'ValidTokenUser', 'NewPassword', true);
$expectedResponse = array('status' => 'success');
$this->assertSame($expectedResponse, $response);
}
}

View File

@ -9,6 +9,7 @@
<testsuite name='ownCloud'> <testsuite name='ownCloud'>
<directory suffix='.php'>lib/</directory> <directory suffix='.php'>lib/</directory>
<directory suffix='.php'>settings/</directory> <directory suffix='.php'>settings/</directory>
<directory suffix='.php'>core/</directory>
<file>apps.php</file> <file>apps.php</file>
</testsuite> </testsuite>
<!-- filters for code coverage --> <!-- filters for code coverage -->

View File

@ -2,6 +2,8 @@
<phpunit bootstrap="bootstrap.php"> <phpunit bootstrap="bootstrap.php">
<testsuite name='ownCloud'> <testsuite name='ownCloud'>
<directory suffix='.php'>lib/</directory> <directory suffix='.php'>lib/</directory>
<directory suffix='.php'>settings/</directory>
<directory suffix='.php'>core/</directory>
<file>apps.php</file> <file>apps.php</file>
</testsuite> </testsuite>
<!-- filters for code coverage --> <!-- filters for code coverage -->

View File

@ -14,7 +14,7 @@ use \OC\Settings\Application;
/** /**
* @package OC\Settings\Controller * @package OC\Settings\Controller
*/ */
class MailSettingscontrollerTest extends \PHPUnit_Framework_TestCase { class MailSettingsControllerTest extends \PHPUnit_Framework_TestCase {
private $container; private $container;